Skip to content

Commit

Permalink
feat(storage): SA credentials default to self-signed JWTs (#9629)
Browse files Browse the repository at this point in the history
Change the default implementation of SA (Service Account) credentials to
use self-signed JWTs by default.  Applications can disable this
by setting the `GOOGLE_CLOUD_CPP_EXPERIMENTAL_DISABLE_SELF_SIGNED_JWT`
environment variable (to any value).
  • Loading branch information
coryan authored Aug 4, 2022
1 parent ab81c8c commit 11b1783
Show file tree
Hide file tree
Showing 3 changed files with 266 additions and 27 deletions.
27 changes: 26 additions & 1 deletion google/cloud/storage/oauth2/service_account_credentials.cc
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
#include "google/cloud/storage/internal/make_jwt_assertion.h"
#include "google/cloud/storage/internal/openssl_util.h"
#include "google/cloud/internal/absl_str_join_quiet.h"
#include "google/cloud/internal/oauth2_service_account_credentials.h"
#include <nlohmann/json.hpp>
#include <openssl/err.h>
#include <openssl/pem.h>
Expand All @@ -29,6 +30,8 @@ namespace storage {
GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_BEGIN
namespace oauth2 {

auto constexpr kP12PrivateKeyIdMarker = "--unknown--";

StatusOr<ServiceAccountCredentialsInfo> ParseServiceAccountCredentials(
std::string const& content, std::string const& source,
std::string const& default_token_uri) {
Expand Down Expand Up @@ -178,7 +181,7 @@ StatusOr<ServiceAccountCredentialsInfo> ParseServiceAccountP12File(
std::string private_key(buf_mem->data, buf_mem->length);

return ServiceAccountCredentialsInfo{std::move(service_account_id),
"--unknown--",
kP12PrivateKeyIdMarker,
std::move(private_key),
default_token_uri,
/*scopes*/ {},
Expand Down Expand Up @@ -271,6 +274,28 @@ ParseServiceAccountRefreshResponse(
new_expiration};
}

StatusOr<std::string> MakeSelfSignedJWT(
ServiceAccountCredentialsInfo const& info,
std::chrono::system_clock::time_point tp) {
// This only runs about once an hour, the copies are ugly, but should be
// harmless.
oauth2_internal::ServiceAccountCredentialsInfo mapped;
mapped.client_email = info.client_email;
mapped.private_key_id = info.private_key_id;
mapped.private_key = info.private_key;
mapped.token_uri = info.token_uri;
mapped.scopes = info.scopes;
mapped.subject = info.subject;
return ::google::cloud::oauth2_internal::MakeSelfSignedJWT(mapped, tp);
}

bool ServiceAccountUseOAuth(ServiceAccountCredentialsInfo const& info) {
if (info.private_key_id == kP12PrivateKeyIdMarker) return true;
auto disable_jwt = google::cloud::internal::GetEnv(
"GOOGLE_CLOUD_CPP_EXPERIMENTAL_DISABLE_SELF_SIGNED_JWT");
return disable_jwt.has_value();
}

} // namespace oauth2
GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_END
} // namespace storage
Expand Down
49 changes: 49 additions & 0 deletions google/cloud/storage/oauth2/service_account_credentials.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
#include "google/cloud/storage/oauth2/credentials.h"
#include "google/cloud/storage/oauth2/refreshing_credentials_wrapper.h"
#include "google/cloud/storage/version.h"
#include "google/cloud/internal/getenv.h"
#include "google/cloud/internal/sha256_hash.h"
#include "google/cloud/optional.h"
#include "google/cloud/status_or.h"
Expand Down Expand Up @@ -106,6 +107,37 @@ std::string CreateServiceAccountRefreshPayload(
ServiceAccountCredentialsInfo const& info, std::string const& grant_type,
std::chrono::system_clock::time_point now);

/**
* Make a self-signed JWT from the service account.
*
* [Self-signed JWTs] bypass the intermediate step of exchanging client
* assertions for OAuth tokens. The advantages of self-signed JTWs include:
*
* - They are more efficient, as they require more or less the same amount of
* local work, and save a round-trip to the token endpoint, typically
* https://oauth2.googleapis.com/token.
* - While this service is extremely reliable, removing external dependencies in
* the critical path almost always improves reliability.
* - They work better in VPC-SC environments and other environments with limited
* Internet access.
*
* @warning At this time only scope-based self-signed JWTs are supported.
*
* [Self-signed JWTs]: https://google.aip.dev/auth/4111
*
* @param info the parsed service account information, see
* `ParseServiceAccountCredentials()`
* @param tp the current time
* @return a bearer token for authentication. Include this value in the
* `Authorization` header with the "Bearer" type.
*/
StatusOr<std::string> MakeSelfSignedJWT(
ServiceAccountCredentialsInfo const& info,
std::chrono::system_clock::time_point tp);

/// Return true if we need to use the OAuth path to create tokens
bool ServiceAccountUseOAuth(ServiceAccountCredentialsInfo const& info);

/**
* Wrapper class for Google OAuth 2.0 service account credentials.
*
Expand Down Expand Up @@ -178,7 +210,14 @@ class ServiceAccountCredentials : public Credentials {
std::string KeyId() const override { return info_.private_key_id; }

private:
bool UseOAuth() const { return ServiceAccountUseOAuth(info_); }

StatusOr<RefreshingCredentialsWrapper::TemporaryToken> Refresh() {
if (UseOAuth()) return RefreshOAuth();
return RefreshSelfSigned();
}

StatusOr<RefreshingCredentialsWrapper::TemporaryToken> RefreshOAuth() const {
HttpRequestBuilderType builder(
info_.token_uri,
storage::internal::GetDefaultCurlHandleFactory(options_));
Expand All @@ -198,6 +237,16 @@ class ServiceAccountCredentials : public Credentials {
return ParseServiceAccountRefreshResponse(*response, clock_.now());
}

StatusOr<RefreshingCredentialsWrapper::TemporaryToken> RefreshSelfSigned()
const {
auto const tp = clock_.now();
auto token = MakeSelfSignedJWT(info_, tp);
if (!token) return std::move(token).status();
return RefreshingCredentialsWrapper::TemporaryToken{
"Authorization: Bearer " + *token,
tp + GoogleOAuthAccessTokenLifetime()};
}

ServiceAccountCredentialsInfo info_;
Options options_;
mutable std::mutex mu_;
Expand Down
Loading

0 comments on commit 11b1783

Please sign in to comment.