1 // Copyright 2018 Google LLC
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 //     http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
15 #include "google/cloud/storage/oauth2/google_credentials.h"
16 #include "google/cloud/storage/oauth2/anonymous_credentials.h"
17 #include "google/cloud/storage/oauth2/authorized_user_credentials.h"
18 #include "google/cloud/storage/oauth2/compute_engine_credentials.h"
19 #include "google/cloud/storage/oauth2/google_application_default_credentials_file.h"
20 #include "google/cloud/storage/oauth2/service_account_credentials.h"
21 #include "google/cloud/internal/filesystem.h"
22 #include "google/cloud/internal/throw_delegate.h"
23 #include "absl/memory/memory.h"
24 #include <nlohmann/json.hpp>
25 #include <fstream>
26 #include <iterator>
27 #include <memory>
28 
29 namespace google {
30 namespace cloud {
31 namespace storage {
32 inline namespace STORAGE_CLIENT_NS {
33 namespace oauth2 {
34 
35 char constexpr kAdcLink[] =
36     "https://developers.google.com/identity/protocols/"
37     "application-default-credentials";
38 
39 // Parses the JSON or P12 file at `path` and creates the appropriate
40 // Credentials type.
41 //
42 // If `service_account_scopes` or `service_account_subject` are specified, the
43 // file at `path` must be a P12 service account or a JSON service account. If
44 // a different type of credential file is found, this function returns
45 // nullptr to indicate a service account file wasn't found.
LoadCredsFromPath(std::string const & path,bool non_service_account_ok,absl::optional<std::set<std::string>> service_account_scopes,absl::optional<std::string> service_account_subject,ChannelOptions const & options)46 StatusOr<std::unique_ptr<Credentials>> LoadCredsFromPath(
47     std::string const& path, bool non_service_account_ok,
48     absl::optional<std::set<std::string>> service_account_scopes,
49     absl::optional<std::string> service_account_subject,
50     ChannelOptions const& options) {
51   std::ifstream ifs(path);
52   if (!ifs.is_open()) {
53     // We use kUnknown here because we don't know if the file does not exist, or
54     // if we were unable to open it for some other reason.
55     return Status(StatusCode::kUnknown, "Cannot open credentials file " + path);
56   }
57   std::string contents(std::istreambuf_iterator<char>{ifs}, {});
58   auto cred_json = nlohmann::json::parse(contents, nullptr, false);
59   if (cred_json.is_discarded()) {
60     // This is not a JSON file, try to load it as a P12 service account.
61     auto info = ParseServiceAccountP12File(path);
62     if (!info) {
63       // Ignore the error returned by the P12 parser, because those are too
64       // specific, they typically say "error in PKCS#12" and the application
65       // may not even be trying to load a PKCS#12 file.
66       return Status(StatusCode::kInvalidArgument,
67                     "Invalid credentials file " + path);
68     }
69     info->subject = std::move(service_account_subject);
70     info->scopes = std::move(service_account_scopes);
71     auto credentials = absl::make_unique<ServiceAccountCredentials<>>(*info);
72     return std::unique_ptr<Credentials>(std::move(credentials));
73   }
74   std::string cred_type = cred_json.value("type", "no type given");
75   // If non_service_account_ok==false and the cred_type is authorized_user,
76   // we'll return "Unsupported credential type (authorized_user)".
77   if (cred_type == "authorized_user" && non_service_account_ok) {
78     if (service_account_scopes || service_account_subject) {
79       // No ptr indicates that the file we found was not a service account file.
80       return StatusOr<std::unique_ptr<Credentials>>(nullptr);
81     }
82     auto info = ParseAuthorizedUserCredentials(contents, path);
83     if (!info) {
84       return info.status();
85     }
86     std::unique_ptr<Credentials> ptr =
87         absl::make_unique<AuthorizedUserCredentials<>>(*info);
88     return StatusOr<std::unique_ptr<Credentials>>(std::move(ptr));
89   }
90   if (cred_type == "service_account") {
91     auto info = ParseServiceAccountCredentials(contents, path);
92     if (!info) {
93       return info.status();
94     }
95     info->subject = std::move(service_account_subject);
96     info->scopes = std::move(service_account_scopes);
97     std::unique_ptr<Credentials> ptr =
98         absl::make_unique<ServiceAccountCredentials<>>(*info, options);
99     return StatusOr<std::unique_ptr<Credentials>>(std::move(ptr));
100   }
101   return StatusOr<std::unique_ptr<Credentials>>(
102       Status(StatusCode::kInvalidArgument,
103              "Unsupported credential type (" + cred_type +
104                  ") when reading Application Default Credentials file from " +
105                  path + "."));
106 }
107 
108 // Tries to load the file at the path specified by the value of the Application
109 // Default %Credentials environment variable and to create the appropriate
110 // Credentials type.
111 //
112 // Returns nullptr if the environment variable is not set or the path does not
113 // exist.
114 //
115 // If `service_account_scopes` or `service_account_subject` are specified, the
116 // found file must be a P12 service account or a JSON service account. If a
117 // different type of credential file is found, this function returns nullptr
118 // to indicate a service account file wasn't found.
MaybeLoadCredsFromAdcPaths(bool non_service_account_ok,absl::optional<std::set<std::string>> service_account_scopes,absl::optional<std::string> service_account_subject,ChannelOptions const & options={})119 StatusOr<std::unique_ptr<Credentials>> MaybeLoadCredsFromAdcPaths(
120     bool non_service_account_ok,
121     absl::optional<std::set<std::string>> service_account_scopes,
122     absl::optional<std::string> service_account_subject,
123     ChannelOptions const& options = {}) {
124   // 1) Check if the GOOGLE_APPLICATION_CREDENTIALS environment variable is set.
125   auto path = GoogleAdcFilePathFromEnvVarOrEmpty();
126   if (path.empty()) {
127     // 2) If no path was specified via environment variable, check if the
128     // gcloud ADC file exists.
129     path = GoogleAdcFilePathFromWellKnownPathOrEmpty();
130     if (path.empty()) {
131       return StatusOr<std::unique_ptr<Credentials>>(nullptr);
132     }
133     // Just because we had the necessary information to build the path doesn't
134     // mean that a file exists there.
135     std::error_code ec;
136     auto adc_file_status = google::cloud::internal::status(path, ec);
137     if (!google::cloud::internal::exists(adc_file_status)) {
138       return StatusOr<std::unique_ptr<Credentials>>(nullptr);
139     }
140   }
141 
142   // If the path was specified, try to load that file; explicitly fail if it
143   // doesn't exist or can't be read and parsed.
144   return LoadCredsFromPath(path, non_service_account_ok,
145                            std::move(service_account_scopes),
146                            std::move(service_account_subject), options);
147 }
148 
GoogleDefaultCredentials(ChannelOptions const & options)149 StatusOr<std::shared_ptr<Credentials>> GoogleDefaultCredentials(
150     ChannelOptions const& options) {
151   // 1 and 2) Check if the GOOGLE_APPLICATION_CREDENTIALS environment variable
152   // is set or if the gcloud ADC file exists.
153   auto creds = MaybeLoadCredsFromAdcPaths(true, {}, {}, options);
154   if (!creds) {
155     return StatusOr<std::shared_ptr<Credentials>>(creds.status());
156   }
157   if (*creds) {
158     return StatusOr<std::shared_ptr<Credentials>>(std::move(*creds));
159   }
160 
161   // 3) Check for implicit environment-based credentials (GCE, GAE Flexible,
162   // Cloud Run or GKE Environment).
163   auto gce_creds = std::make_shared<ComputeEngineCredentials<>>();
164   auto override_val =
165       google::cloud::internal::GetEnv(internal::GceCheckOverrideEnvVar());
166   if (override_val.has_value() ? (std::string("1") == *override_val)
167                                : gce_creds->AuthorizationHeader().ok()) {
168     return StatusOr<std::shared_ptr<Credentials>>(std::move(gce_creds));
169   }
170 
171   // We've exhausted all search points, thus credentials cannot be constructed.
172   return StatusOr<std::shared_ptr<Credentials>>(
173       Status(StatusCode::kUnknown,
174              "Could not automatically determine credentials. For more "
175              "information, please see " +
176                  std::string(kAdcLink)));
177 }
178 
CreateAnonymousCredentials()179 std::shared_ptr<Credentials> CreateAnonymousCredentials() {
180   return std::make_shared<AnonymousCredentials>();
181 }
182 
183 StatusOr<std::shared_ptr<Credentials>>
CreateAuthorizedUserCredentialsFromJsonFilePath(std::string const & path)184 CreateAuthorizedUserCredentialsFromJsonFilePath(std::string const& path) {
185   std::ifstream is(path);
186   std::string contents(std::istreambuf_iterator<char>{is}, {});
187   auto info = ParseAuthorizedUserCredentials(contents, path);
188   if (!info) {
189     return StatusOr<std::shared_ptr<Credentials>>(info.status());
190   }
191   return StatusOr<std::shared_ptr<Credentials>>(
192       std::make_shared<AuthorizedUserCredentials<>>(*info));
193 }
194 
195 StatusOr<std::shared_ptr<Credentials>>
CreateAuthorizedUserCredentialsFromJsonContents(std::string const & contents,ChannelOptions const & options)196 CreateAuthorizedUserCredentialsFromJsonContents(std::string const& contents,
197                                                 ChannelOptions const& options) {
198   auto info = ParseAuthorizedUserCredentials(contents, "memory");
199   if (!info) {
200     return StatusOr<std::shared_ptr<Credentials>>(info.status());
201   }
202   return StatusOr<std::shared_ptr<Credentials>>(
203       std::make_shared<AuthorizedUserCredentials<>>(*info, options));
204 }
205 
206 StatusOr<std::shared_ptr<Credentials>>
CreateServiceAccountCredentialsFromFilePath(std::string const & path)207 CreateServiceAccountCredentialsFromFilePath(std::string const& path) {
208   return CreateServiceAccountCredentialsFromFilePath(path, {}, {});
209 }
210 
211 StatusOr<std::shared_ptr<Credentials>>
CreateServiceAccountCredentialsFromFilePath(std::string const & path,absl::optional<std::set<std::string>> scopes,absl::optional<std::string> subject)212 CreateServiceAccountCredentialsFromFilePath(
213     std::string const& path, absl::optional<std::set<std::string>> scopes,
214     absl::optional<std::string> subject) {
215   auto credentials =
216       CreateServiceAccountCredentialsFromJsonFilePath(path, scopes, subject);
217   if (credentials) {
218     return credentials;
219   }
220   return CreateServiceAccountCredentialsFromP12FilePath(path, std::move(scopes),
221                                                         std::move(subject));
222 }
223 
224 StatusOr<std::shared_ptr<Credentials>>
CreateServiceAccountCredentialsFromJsonFilePath(std::string const & path)225 CreateServiceAccountCredentialsFromJsonFilePath(std::string const& path) {
226   return CreateServiceAccountCredentialsFromJsonFilePath(path, {}, {});
227 }
228 
229 StatusOr<std::shared_ptr<Credentials>>
CreateServiceAccountCredentialsFromJsonFilePath(std::string const & path,absl::optional<std::set<std::string>> scopes,absl::optional<std::string> subject,ChannelOptions const & options)230 CreateServiceAccountCredentialsFromJsonFilePath(
231     std::string const& path, absl::optional<std::set<std::string>> scopes,
232     absl::optional<std::string> subject, ChannelOptions const& options) {
233   std::ifstream is(path);
234   std::string contents(std::istreambuf_iterator<char>{is}, {});
235   auto info = ParseServiceAccountCredentials(contents, path);
236   if (!info) {
237     return StatusOr<std::shared_ptr<Credentials>>(info.status());
238   }
239   // These are supplied as extra parameters to this method, not in the JSON
240   // file.
241   info->subject = std::move(subject);
242   info->scopes = std::move(scopes);
243   return StatusOr<std::shared_ptr<Credentials>>(
244       std::make_shared<ServiceAccountCredentials<>>(*info, options));
245 }
246 
247 StatusOr<std::shared_ptr<Credentials>>
CreateServiceAccountCredentialsFromP12FilePath(std::string const & path,absl::optional<std::set<std::string>> scopes,absl::optional<std::string> subject,ChannelOptions const & options)248 CreateServiceAccountCredentialsFromP12FilePath(
249     std::string const& path, absl::optional<std::set<std::string>> scopes,
250     absl::optional<std::string> subject, ChannelOptions const& options) {
251   auto info = ParseServiceAccountP12File(path);
252   if (!info) {
253     return StatusOr<std::shared_ptr<Credentials>>(info.status());
254   }
255   // These are supplied as extra parameters to this method, not in the P12
256   // file.
257   info->subject = std::move(subject);
258   info->scopes = std::move(scopes);
259   return StatusOr<std::shared_ptr<Credentials>>(
260       std::make_shared<ServiceAccountCredentials<>>(*info, options));
261 }
262 
263 StatusOr<std::shared_ptr<Credentials>>
CreateServiceAccountCredentialsFromP12FilePath(std::string const & path)264 CreateServiceAccountCredentialsFromP12FilePath(std::string const& path) {
265   return CreateServiceAccountCredentialsFromP12FilePath(path, {}, {});
266 }
267 
268 StatusOr<std::shared_ptr<Credentials>>
CreateServiceAccountCredentialsFromDefaultPaths(ChannelOptions const & options)269 CreateServiceAccountCredentialsFromDefaultPaths(ChannelOptions const& options) {
270   return CreateServiceAccountCredentialsFromDefaultPaths({}, {}, options);
271 }
272 
273 StatusOr<std::shared_ptr<Credentials>>
CreateServiceAccountCredentialsFromDefaultPaths(absl::optional<std::set<std::string>> scopes,absl::optional<std::string> subject,ChannelOptions const & options)274 CreateServiceAccountCredentialsFromDefaultPaths(
275     absl::optional<std::set<std::string>> scopes,
276     absl::optional<std::string> subject, ChannelOptions const& options) {
277   auto creds = MaybeLoadCredsFromAdcPaths(false, std::move(scopes),
278                                           std::move(subject), options);
279   if (!creds) {
280     return StatusOr<std::shared_ptr<Credentials>>(creds.status());
281   }
282   if (*creds) {
283     return StatusOr<std::shared_ptr<Credentials>>(std::move(*creds));
284   }
285 
286   // We've exhausted all search points, thus credentials cannot be constructed.
287   return StatusOr<std::shared_ptr<Credentials>>(
288       Status(StatusCode::kUnknown,
289              "Could not create service account credentials using Application"
290              "Default Credentials paths. For more information, please see " +
291                  std::string(kAdcLink)));
292 }
293 
294 StatusOr<std::shared_ptr<Credentials>>
CreateServiceAccountCredentialsFromJsonContents(std::string const & contents,ChannelOptions const & options)295 CreateServiceAccountCredentialsFromJsonContents(std::string const& contents,
296                                                 ChannelOptions const& options) {
297   return CreateServiceAccountCredentialsFromJsonContents(contents, {}, {},
298                                                          options);
299 }
300 
301 StatusOr<std::shared_ptr<Credentials>>
CreateServiceAccountCredentialsFromJsonContents(std::string const & contents,absl::optional<std::set<std::string>> scopes,absl::optional<std::string> subject,ChannelOptions const & options)302 CreateServiceAccountCredentialsFromJsonContents(
303     std::string const& contents, absl::optional<std::set<std::string>> scopes,
304     absl::optional<std::string> subject, ChannelOptions const& options) {
305   auto info = ParseServiceAccountCredentials(contents, "memory");
306   if (!info) {
307     return StatusOr<std::shared_ptr<Credentials>>(info.status());
308   }
309   std::chrono::system_clock::time_point now;
310   auto components = AssertionComponentsFromInfo(*info, now);
311   auto jwt_assertion = internal::MakeJWTAssertionNoThrow(
312       components.first, components.second, info->private_key);
313   if (!jwt_assertion) {
314     return std::move(jwt_assertion).status();
315   }
316   // These are supplied as extra parameters to this method, not in the JSON
317   // file.
318   info->subject = std::move(subject);
319   info->scopes = std::move(scopes);
320   return StatusOr<std::shared_ptr<Credentials>>(
321       std::make_shared<ServiceAccountCredentials<>>(*info, options));
322 }
323 
CreateComputeEngineCredentials()324 std::shared_ptr<Credentials> CreateComputeEngineCredentials() {
325   return std::make_shared<ComputeEngineCredentials<>>();
326 }
327 
CreateComputeEngineCredentials(std::string const & service_account_email)328 std::shared_ptr<Credentials> CreateComputeEngineCredentials(
329     std::string const& service_account_email) {
330   return std::make_shared<ComputeEngineCredentials<>>(service_account_email);
331 }
332 
333 }  // namespace oauth2
334 }  // namespace STORAGE_CLIENT_NS
335 }  // namespace storage
336 }  // namespace cloud
337 }  // namespace google
338