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