1 //
2 // Copyright 2022 gRPC authors.
3 //
4 // Licensed under the Apache License, Version 2.0 (the "License");
5 // you may not use this file except in compliance with the License.
6 // You may obtain a copy of the License at
7 //
8 //     http://www.apache.org/licenses/LICENSE-2.0
9 //
10 // Unless required by applicable law or agreed to in writing, software
11 // distributed under the License is distributed on an "AS IS" BASIS,
12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 // See the License for the specific language governing permissions and
14 // limitations under the License.
15 //
16 
17 #include <grpc/support/port_platform.h>
18 
19 #include "src/cpp/ext/gcp/observability_config.h"
20 
21 #include <stddef.h>
22 
23 #include <algorithm>
24 #include <utility>
25 
26 #include "absl/status/status.h"
27 #include "absl/strings/match.h"
28 #include "absl/strings/str_cat.h"
29 #include "absl/strings/str_split.h"
30 #include "absl/strings/string_view.h"
31 #include "absl/types/optional.h"
32 
33 #include <grpc/slice.h>
34 #include <grpc/status.h>
35 
36 #include "src/core/lib/gprpp/env.h"
37 #include "src/core/lib/gprpp/status_helper.h"
38 #include "src/core/lib/gprpp/validation_errors.h"
39 #include "src/core/lib/iomgr/error.h"
40 #include "src/core/lib/iomgr/load_file.h"
41 #include "src/core/lib/json/json.h"
42 #include "src/core/lib/json/json_reader.h"
43 #include "src/core/lib/slice/slice_internal.h"
44 #include "src/core/lib/transport/error_utils.h"
45 
46 namespace grpc {
47 namespace internal {
48 
49 namespace {
50 
51 // Loads the contents of the file pointed by env var
52 // GRPC_GCP_OBSERVABILITY_CONFIG_FILE. If unset, falls back to the contents of
53 // GRPC_GCP_OBSERVABILITY_CONFIG.
GetGcpObservabilityConfigContents()54 absl::StatusOr<std::string> GetGcpObservabilityConfigContents() {
55   // First, try GRPC_GCP_OBSERVABILITY_CONFIG_FILE
56   std::string contents_str;
57   auto path = grpc_core::GetEnv("GRPC_GCP_OBSERVABILITY_CONFIG_FILE");
58   if (path.has_value() && !path.value().empty()) {
59     grpc_slice contents;
60     grpc_error_handle error =
61         grpc_load_file(path->c_str(), /*add_null_terminator=*/true, &contents);
62     if (!error.ok()) {
63       return grpc_error_to_absl_status(
64           grpc_error_set_int(error, grpc_core::StatusIntProperty::kRpcStatus,
65                              GRPC_STATUS_FAILED_PRECONDITION));
66     }
67     std::string contents_str(grpc_core::StringViewFromSlice(contents));
68     grpc_slice_unref(contents);
69     return std::move(contents_str);
70   }
71   // Next, try GRPC_GCP_OBSERVABILITY_CONFIG env var.
72   auto env_config = grpc_core::GetEnv("GRPC_GCP_OBSERVABILITY_CONFIG");
73   if (env_config.has_value() && !env_config.value().empty()) {
74     return std::move(*env_config);
75   }
76   // No observability config found.
77   return absl::FailedPreconditionError(
78       "Environment variables GRPC_GCP_OBSERVABILITY_CONFIG_FILE or "
79       "GRPC_GCP_OBSERVABILITY_CONFIG "
80       "not defined");
81 }
82 
83 // Tries to get the GCP Project ID from environment variables, or returns an
84 // empty string if not found.
GetProjectIdFromGcpEnvVar()85 std::string GetProjectIdFromGcpEnvVar() {
86   // First check GCP_PROEJCT
87   absl::optional<std::string> project_id = grpc_core::GetEnv("GCP_PROJECT");
88   if (project_id.has_value() && !project_id->empty()) {
89     return project_id.value();
90   }
91   // Next, try GCLOUD_PROJECT
92   project_id = grpc_core::GetEnv("GCLOUD_PROJECT");
93   if (project_id.has_value() && !project_id->empty()) {
94     return project_id.value();
95   }
96   // Lastly, try GOOGLE_CLOUD_PROJECT
97   project_id = grpc_core::GetEnv("GOOGLE_CLOUD_PROJECT");
98   if (project_id.has_value() && !project_id->empty()) {
99     return project_id.value();
100   }
101   return "";
102 }
103 
104 }  // namespace
105 
106 //
107 // GcpObservabilityConfig::CloudLogging::RpcEventConfiguration
108 //
109 
110 const grpc_core::JsonLoaderInterface*
JsonLoader(const grpc_core::JsonArgs &)111 GcpObservabilityConfig::CloudLogging::RpcEventConfiguration::JsonLoader(
112     const grpc_core::JsonArgs&) {
113   static const auto* loader =
114       grpc_core::JsonObjectLoader<RpcEventConfiguration>()
115           .OptionalField("methods", &RpcEventConfiguration::qualified_methods)
116           .OptionalField("exclude", &RpcEventConfiguration::exclude)
117           .OptionalField("max_metadata_bytes",
118                          &RpcEventConfiguration::max_metadata_bytes)
119           .OptionalField("max_message_bytes",
120                          &RpcEventConfiguration::max_message_bytes)
121           .Finish();
122   return loader;
123 }
124 
JsonPostLoad(const grpc_core::Json &,const grpc_core::JsonArgs &,grpc_core::ValidationErrors * errors)125 void GcpObservabilityConfig::CloudLogging::RpcEventConfiguration::JsonPostLoad(
126     const grpc_core::Json& /* json */, const grpc_core::JsonArgs& /* args */,
127     grpc_core::ValidationErrors* errors) {
128   grpc_core::ValidationErrors::ScopedField methods_field(errors, ".methods");
129   parsed_methods.reserve(qualified_methods.size());
130   for (size_t i = 0; i < qualified_methods.size(); ++i) {
131     grpc_core::ValidationErrors::ScopedField methods_index(
132         errors, absl::StrCat("[", i, "]"));
133     std::vector<absl::string_view> parts =
134         absl::StrSplit(qualified_methods[i], '/', absl::SkipEmpty());
135     if (parts.size() > 2) {
136       errors->AddError("methods[] can have at most a single '/'");
137       continue;
138     } else if (parts.empty()) {
139       errors->AddError("Empty configuration");
140       continue;
141     } else if (parts.size() == 1) {
142       if (parts[0] != "*") {
143         errors->AddError("Illegal methods[] configuration");
144         continue;
145       }
146       if (exclude) {
147         errors->AddError(
148             "Wildcard match '*' not allowed when 'exclude' is set");
149         continue;
150       }
151       parsed_methods.push_back(ParsedMethod{parts[0], ""});
152     } else {
153       // parts.size() == 2
154       if (absl::StrContains(parts[0], '*')) {
155         errors->AddError("Configuration of type '*/method' not allowed");
156         continue;
157       }
158       if (absl::StrContains(parts[1], '*') && parts[1].size() != 1) {
159         errors->AddError("Wildcard specified for method in incorrect manner");
160         continue;
161       }
162       parsed_methods.push_back(ParsedMethod{parts[0], parts[1]});
163     }
164   }
165 }
166 
ReadFromEnv()167 absl::StatusOr<GcpObservabilityConfig> GcpObservabilityConfig::ReadFromEnv() {
168   auto config_contents = GetGcpObservabilityConfigContents();
169   if (!config_contents.ok()) {
170     return config_contents.status();
171   }
172   auto config_json = grpc_core::JsonParse(*config_contents);
173   if (!config_json.ok()) {
174     return config_json.status();
175   }
176   auto config = grpc_core::LoadFromJson<GcpObservabilityConfig>(*config_json);
177   if (!config.ok()) {
178     return config.status();
179   }
180   if (config->project_id.empty()) {
181     // Get project ID from GCP environment variables since project ID was not
182     // set it in the GCP observability config.
183     config->project_id = GetProjectIdFromGcpEnvVar();
184     if (config->project_id.empty()) {
185       // Could not find project ID from GCP environment variables either.
186       return absl::FailedPreconditionError("GCP Project ID not found.");
187     }
188   }
189   return config;
190 }
191 
192 }  // namespace internal
193 }  // namespace grpc
194