Skip to content

Commit 69724ae

Browse files
authored
impl(v3): switch GCS to use GUAC (#15890)
1 parent d8fe957 commit 69724ae

77 files changed

Lines changed: 346 additions & 6253 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
-90.7 KB
Binary file not shown.
449 Bytes
Binary file not shown.

ci/cloudbuild/builds/cmake-install.sh

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,6 @@ expected_dirs+=(
133133
./include/google/cloud/storage/internal/grpc
134134
./include/google/cloud/storage/internal/rest
135135
./include/google/cloud/storage/mocks
136-
./include/google/cloud/storage/oauth2
137136
./include/google/cloud/storage/testing
138137
# no gRPC services in google/cloud/workflows/type.
139138
./include/google/cloud/workflows/type

doc/v3-migration-guide.md

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -628,6 +628,95 @@ void UseRawClient(google::cloud::storage::Client client) {
628628

629629
</details>
630630

631+
<details>
632+
<summary>Removed deprecated <code>Oauth2CredentialsOption</code></summary>
633+
634+
The `google::cloud::UnifiedCredentialsOption` and the unified credentials API
635+
documented at
636+
https://docs.cloud.google.com/cpp/docs/reference/common/latest/group__guac
637+
should be used instead.
638+
639+
**Before:**
640+
641+
```cpp
642+
#include "google/cloud/options.h"
643+
#include "google/cloud/storage/client.h"
644+
#include "google/cloud/storage/oauth2/google_credentials.h"
645+
646+
namespace gc = ::google::cloud;
647+
namespace gcs = ::google::cloud::storage;
648+
namespace oauth2 = ::google::cloud::storage::oauth2;
649+
650+
auto options = gc::Options{}
651+
.set<gcs::Oauth2CredentialsOption>(oauth2::CreateAnonymousCredentials());
652+
auto client = gcs::Client(options);
653+
```
654+
655+
**After:**
656+
657+
```cpp
658+
#include "google/cloud/options.h"
659+
#include "google/cloud/storage/client.h"
660+
#include "google/cloud/credentials.h"
661+
662+
namespace gc = ::google::cloud;
663+
namespace gcs = ::google::cloud::storage;
664+
665+
auto options = gc::Options{}
666+
.set<gc::UnifiedCredentialsOption>(gc::MakeInsecureCredentials());
667+
auto client = gcs::Client(options);
668+
```
669+
670+
</details>
671+
672+
<details>
673+
<summary>Removed deprecated <code>CreateServiceAccountCredentialsFromFilePath</code></summary>
674+
675+
The `google::cloud::MakeServiceAccountCredentialsFromFile` factory function and
676+
associated override options `google::cloud::ScopesOption` and
677+
`google::cloud::subjectOption` should be used instead.
678+
679+
**Before:**
680+
681+
```cpp
682+
#include "google/cloud/options.h"
683+
#include "google/cloud/storage/client.h"
684+
#include "google/cloud/storage/oauth2/google_credentials.h"
685+
686+
namespace gc = ::google::cloud;
687+
namespace gcs = ::google::cloud::storage;
688+
namespace oauth2 = ::google::cloud::storage::oauth2;
689+
690+
std::set<std::string> scopes = {"scope1", "scope2"};
691+
auto credentials = CreateServiceAccountCredentialsFromFilePath(
692+
"path-to-file", scopes, "my-subject");
693+
694+
auto options = gc::Options{}
695+
.set<gcs::Oauth2CredentialsOption>(credentials);
696+
auto client = gcs::Client(options);
697+
```
698+
699+
**After:**
700+
701+
```cpp
702+
#include "google/cloud/options.h"
703+
#include "google/cloud/storage/client.h"
704+
#include "google/cloud/credentials.h"
705+
706+
namespace gc = ::google::cloud;
707+
namespace gcs = ::google::cloud::storage;
708+
709+
auto options = gc::Options{}
710+
.set<gc::ScopesOption>(std::vector<std::string>({"scope1", "scope2"}))
711+
.set<gc::SubjectOption>("my-subject");
712+
713+
options = options.set<gc::UnifiedCredentialsOption>(
714+
gc::MakeServiceAccountCredentialsFromFile("path-to-file", options));
715+
auto client = gcs::Client(options);
716+
```
717+
718+
</details>
719+
631720
### IAM
632721

633722
<details>

google/cloud/credentials.h

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -346,9 +346,15 @@ std::shared_ptr<Credentials> MakeServiceAccountCredentials(
346346
* @param file_path path to file containing the service account key
347347
* Typically applications read this from a file, or download the contents from
348348
* something like Google's secret manager service.
349-
* @param opts optional configuration values. Note that the effect of these
350-
* parameters depends on the underlying transport. For example,
351-
* `LoggingComponentsOption` is ignored by gRPC-based services.
349+
* @param opts optional configuration values.
350+
*
351+
* `ScopesOption` the scopes to request during the authorization grant. If
352+
* omitted, the cloud-platform scope, defined by
353+
* `GoogleOAuthScopeCloudPlatform()`, is used as a default.
354+
* `SubjectOption` for domain-wide delegation; the email address of the user for
355+
* which to request delegated access. If omitted AND no "subject" is defined
356+
* in the provided file, no "subject" attribute is included in the
357+
* authorization grant.
352358
*/
353359
std::shared_ptr<Credentials> MakeServiceAccountCredentialsFromFile(
354360
std::string const& file_path, Options opts = {});

google/cloud/internal/oauth2_service_account_credentials.cc

Lines changed: 15 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,19 @@ StatusOr<ServiceAccountCredentialsInfo> ParseServiceAccountCredentials(
133133
return info;
134134
}
135135

136+
void ApplyServiceAccountCredentialsInfoOverrides(
137+
Options const& options, ServiceAccountCredentialsInfo& info) {
138+
if (options.has<ScopesOption>()) {
139+
auto const& s = options.get<ScopesOption>();
140+
std::set<std::string> scopes{s.begin(), s.end()};
141+
info.scopes = std::move(scopes);
142+
}
143+
144+
if (options.has<SubjectOption>()) {
145+
info.subject = options.get<SubjectOption>();
146+
}
147+
}
148+
136149
std::pair<std::string, std::string> AssertionComponentsFromInfo(
137150
ServiceAccountCredentialsInfo const& info,
138151
std::chrono::system_clock::time_point now) {
@@ -249,17 +262,7 @@ CreateServiceAccountCredentialsFromJsonContents(
249262
HttpClientFactory client_factory) {
250263
auto info = ParseServiceAccountCredentials(contents, "memory");
251264
if (!info) return info.status();
252-
253-
if (options.has<ScopesOption>()) {
254-
auto const& s = options.get<ScopesOption>();
255-
std::set<std::string> scopes{s.begin(), s.end()};
256-
info->scopes = std::move(scopes);
257-
}
258-
259-
if (options.has<SubjectOption>()) {
260-
info->subject = options.get<SubjectOption>();
261-
}
262-
265+
ApplyServiceAccountCredentialsInfoOverrides(options, *info);
263266
// Verify this is usable before returning it.
264267
auto const tp = std::chrono::system_clock::time_point{};
265268
auto const components = AssertionComponentsFromInfo(*info, tp);
@@ -293,17 +296,7 @@ CreateServiceAccountCredentialsFromP12FilePath(
293296
HttpClientFactory client_factory) {
294297
auto info = ParseServiceAccountP12File(path);
295298
if (!info) return std::move(info).status();
296-
297-
if (options.has<ScopesOption>()) {
298-
auto const& s = options.get<ScopesOption>();
299-
std::set<std::string> scopes{s.begin(), s.end()};
300-
info->scopes = std::move(scopes);
301-
}
302-
303-
if (options.has<SubjectOption>()) {
304-
info->subject = options.get<SubjectOption>();
305-
}
306-
299+
ApplyServiceAccountCredentialsInfoOverrides(options, *info);
307300
return StatusOr<std::shared_ptr<Credentials>>(
308301
std::make_shared<ServiceAccountCredentials>(*info, options,
309302
std::move(client_factory)));

google/cloud/internal/oauth2_service_account_credentials.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,10 @@ StatusOr<ServiceAccountCredentialsInfo> ParseServiceAccountCredentials(
6666
std::string const& content, std::string const& source,
6767
std::string const& default_token_uri = GoogleOAuthRefreshEndpoint());
6868

69+
/// Applies any overrides contained in `ScopesOption` or `SubjectOption`.
70+
void ApplyServiceAccountCredentialsInfoOverrides(
71+
Options const& options, ServiceAccountCredentialsInfo& info);
72+
6973
/// Parses a refresh response JSON string to create an access token.
7074
StatusOr<AccessToken> ParseServiceAccountRefreshResponse(
7175
rest_internal::RestResponse& response,

google/cloud/internal/oauth2_service_account_credentials_test.cc

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
// limitations under the License.
1414

1515
#include "google/cloud/internal/oauth2_service_account_credentials.h"
16+
#include "google/cloud/credentials.h"
1617
#include "google/cloud/internal/base64_transforms.h"
1718
#include "google/cloud/internal/oauth2_credential_constants.h"
1819
#include "google/cloud/internal/oauth2_universe_domain.h"
@@ -896,6 +897,61 @@ TEST(ServiceAccountCredentialsTest, ParseServiceAccountRefreshResponse) {
896897
EXPECT_EQ(token.token, "access-token-r1");
897898
}
898899

900+
TEST(ServiceAccountCredentialsTest,
901+
ApplyServiceAccountCredentialsInfoOverrides) {
902+
auto info = ParseServiceAccountCredentials(MakeTestContents(), "test");
903+
ASSERT_STATUS_OK(info);
904+
905+
auto options = Options{}
906+
.set<google::cloud::ScopesOption>(std::vector<std::string>(
907+
{"https://www.googleapis.com/auth/userinfo.email",
908+
"https://www.googleapis.com/auth/cloud-platform"}))
909+
.set<google::cloud::SubjectOption>("my-subject");
910+
911+
ApplyServiceAccountCredentialsInfoOverrides(options, *info);
912+
913+
auto const tp = std::chrono::system_clock::from_time_t(kFixedJwtTimestamp);
914+
auto components = AssertionComponentsFromInfo(*info, tp);
915+
auto assertion =
916+
MakeJWTAssertion(components.first, components.second, info->private_key);
917+
918+
std::vector<std::string> actual_tokens = absl::StrSplit(assertion, '.');
919+
ASSERT_EQ(actual_tokens.size(), 3);
920+
std::vector<std::vector<std::uint8_t>> decoded(actual_tokens.size());
921+
std::transform(
922+
actual_tokens.begin(), actual_tokens.end(), decoded.begin(),
923+
[](std::string const& e) { return UrlsafeBase64Decode(e).value(); });
924+
925+
// Verify this is a valid key.
926+
auto const signature =
927+
SignUsingSha256(actual_tokens[0] + '.' + actual_tokens[1], kPrivateKey);
928+
ASSERT_STATUS_OK(signature);
929+
EXPECT_EQ(*signature, decoded[2]);
930+
931+
// Verify the header and payloads are valid.
932+
auto const header =
933+
nlohmann::json::parse(decoded[0].begin(), decoded[0].end());
934+
auto const expected_header =
935+
nlohmann::json{{"alg", "RS256"}, {"typ", "JWT"}, {"kid", kPrivateKeyId}};
936+
EXPECT_EQ(header, expected_header);
937+
938+
auto const payload = nlohmann::json::parse(decoded[1]);
939+
auto const iat = static_cast<std::intmax_t>(kFixedJwtTimestamp);
940+
auto const exp = iat + 3600;
941+
auto const expected_payload = nlohmann::json{
942+
{"iss", kClientEmail},
943+
{"scope",
944+
"https://www.googleapis.com/auth/cloud-platform "
945+
"https://www.googleapis.com/auth/userinfo.email"},
946+
{"aud", kTokenUri},
947+
{"iat", iat},
948+
{"exp", exp},
949+
{"sub", "my-subject"},
950+
};
951+
952+
EXPECT_EQ(payload, expected_payload);
953+
}
954+
899955
} // namespace
900956
GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_END
901957
} // namespace oauth2_internal

google/cloud/storage/benchmarks/throughput_experiment.cc

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
#include "google/cloud/storage/benchmarks/benchmark_utils.h"
1717
#include "google/cloud/storage/client.h"
1818
#include "google/cloud/internal/make_status.h"
19+
#include "google/cloud/internal/unified_rest_credentials.h"
1920
#if GOOGLE_CLOUD_CPP_STORAGE_HAVE_GRPC
2021
#include "google/cloud/storage/internal/grpc/ctype_cord_workaround.h"
2122
#include "google/cloud/grpc_error_delegate.h"
@@ -260,8 +261,7 @@ class DownloadObjectLibcurl : public ThroughputExperiment {
260261
: endpoint_(options.rest_options.get<gcs::RestEndpointOption>()),
261262
target_api_version_path_(
262263
options.rest_options.get<gcs::internal::TargetApiVersionOption>()),
263-
creds_(google::cloud::storage::oauth2::GoogleDefaultCredentials()
264-
.value()) {
264+
creds_(rest_internal::MapCredentials(*MakeGoogleDefaultCredentials())) {
265265
if (target_api_version_path_.empty()) {
266266
target_api_version_path_ = "v1";
267267
}
@@ -271,13 +271,14 @@ class DownloadObjectLibcurl : public ThroughputExperiment {
271271
ThroughputResult Run(std::string const& bucket_name,
272272
std::string const& object_name,
273273
ThroughputExperimentConfig const& config) override {
274-
auto header = creds_->AuthorizationHeader();
275-
if (!header) return {};
274+
auto token = creds_->GetToken(std::chrono::system_clock::now());
275+
if (!token) return {};
276+
auto header = std::string{"Authorization: "} + token->token;
276277

277278
auto const start = std::chrono::system_clock::now();
278279
auto timer = Timer::PerThread();
279280
struct curl_slist* slist1 = nullptr;
280-
slist1 = curl_slist_append(slist1, header->c_str());
281+
slist1 = curl_slist_append(slist1, header.c_str());
281282

282283
auto* hnd = curl_easy_init();
283284
curl_easy_setopt(hnd, CURLOPT_BUFFERSIZE, 102400L);
@@ -339,7 +340,7 @@ class DownloadObjectLibcurl : public ThroughputExperiment {
339340
private:
340341
std::string endpoint_;
341342
std::string target_api_version_path_;
342-
std::shared_ptr<google::cloud::storage::oauth2::Credentials> creds_;
343+
std::shared_ptr<oauth2_internal::Credentials> creds_;
343344
};
344345

345346
#if GOOGLE_CLOUD_CPP_STORAGE_HAVE_GRPC

0 commit comments

Comments
 (0)