Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions google/cloud/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,7 @@ cc_library(
],
deps = [
":google_cloud_cpp_common",
":google_cloud_cpp_rest_internal",
"@com_github_grpc_grpc//:grpc++",
"@com_google_absl//absl/functional:function_ref",
"@com_google_absl//absl/time",
Expand All @@ -175,6 +176,7 @@ cc_library(
"@com_google_googleapis//google/longrunning:longrunning_cc_grpc",
"@com_google_googleapis//google/rpc:error_details_cc_proto",
"@com_google_googleapis//google/rpc:status_cc_proto",
"@com_github_nlohmann_json//:json",
],
)

Expand Down
2 changes: 2 additions & 0 deletions google/cloud/google_cloud_cpp_grpc_utils.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ google_cloud_cpp_grpc_utils_hdrs = [
"internal/grpc_api_key_authentication.h",
"internal/grpc_async_access_token_cache.h",
"internal/grpc_channel_credentials_authentication.h",
"internal/grpc_google_credentials.h",
"internal/grpc_impersonate_service_account.h",
"internal/grpc_metadata_view.h",
"internal/grpc_opentelemetry.h",
Expand Down Expand Up @@ -102,6 +103,7 @@ google_cloud_cpp_grpc_utils_srcs = [
"internal/grpc_api_key_authentication.cc",
"internal/grpc_async_access_token_cache.cc",
"internal/grpc_channel_credentials_authentication.cc",
"internal/grpc_google_credentials.cc",
"internal/grpc_impersonate_service_account.cc",
"internal/grpc_opentelemetry.cc",
"internal/grpc_request_metadata.cc",
Expand Down
6 changes: 4 additions & 2 deletions google/cloud/internal/credentials_impl.cc
Original file line number Diff line number Diff line change
Expand Up @@ -68,10 +68,12 @@ AccessTokenConfig::AccessTokenConfig(

ImpersonateServiceAccountConfig::ImpersonateServiceAccountConfig(
std::shared_ptr<Credentials> base_credentials,
std::string target_service_account, Options opts)
std::string target_service_account, Options opts,
absl::optional<std::string> service_account_impersonation_url)
: base_credentials_(std::move(base_credentials)),
target_service_account_(std::move(target_service_account)),
options_(PopulateAuthOptions(std::move(opts))) {}
options_(PopulateAuthOptions(std::move(opts))),
service_account_impersonation_url_(std::move(service_account_impersonation_url)) {}

std::chrono::seconds ImpersonateServiceAccountConfig::lifetime() const {
return options_.get<AccessTokenLifetimeOption>();
Expand Down
7 changes: 6 additions & 1 deletion google/cloud/internal/credentials_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,8 @@ class ImpersonateServiceAccountConfig : public Credentials {
public:
ImpersonateServiceAccountConfig(std::shared_ptr<Credentials> base_credentials,
std::string target_service_account,
Options opts);
Options opts,
absl::optional<std::string> service_account_impersonation_url = absl::nullopt);

std::shared_ptr<Credentials> base_credentials() const {
return base_credentials_;
Expand All @@ -131,13 +132,17 @@ class ImpersonateServiceAccountConfig : public Credentials {
std::vector<std::string> const& scopes() const;
std::vector<std::string> const& delegates() const;
Options const& options() const { return options_; }
absl::optional<std::string> const& service_account_impersonation_url() const {
return service_account_impersonation_url_;
}

private:
void dispatch(CredentialsVisitor& v) const override { v.visit(*this); }

std::shared_ptr<Credentials> base_credentials_;
std::string target_service_account_;
Options options_;
absl::optional<std::string> service_account_impersonation_url_;
};

class ServiceAccountConfig : public Credentials {
Expand Down
29 changes: 29 additions & 0 deletions google/cloud/internal/grpc_google_credentials.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
#include "google/cloud/internal/grpc_google_credentials.h"

namespace google {
namespace cloud {
GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_BEGIN
namespace internal {

StatusOr<std::shared_ptr<GrpcAuthenticationStrategy>> MaybeLoadImpersonationCredentials(CompletionQueue cq, Options const& options) {
std::string path = std::string(std::getenv("GOOGLE_APPLICATION_CREDENTIALS"));
auto ec = internal::ErrorContext{};
if (path.empty()) {
return {nullptr};
}
std::ifstream ifs(path);
auto const contents = std::string(std::istreambuf_iterator<char>{ifs}, {});
auto json = nlohmann::json::parse(contents, nullptr, false);

auto cfg = oauth2_internal::MakeImpersonateServiceAccountConfig(contents, options, internal::ErrorContext{});
if (!cfg) return std::move(cfg).status();

return std::shared_ptr<GrpcAuthenticationStrategy>(
GrpcImpersonateServiceAccount::Create(std::move(cq), *cfg, std::move(options)));

}

} // namespace internal
GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_END
} // namespace cloud
} // namespace google
27 changes: 27 additions & 0 deletions google/cloud/internal/grpc_google_credentials.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
#ifndef GOOGLE_CLOUD_CPP_GOOGLE_CLOUD_INTERNAL_GRPC_GOOGLE_CREDENTIALS_H
#define GOOGLE_CLOUD_CPP_GOOGLE_CLOUD_INTERNAL_GRPC_GOOGLE_CREDENTIALS_H

#include "google/cloud/version.h"
#include "google/cloud/internal/make_status.h"
#include "google/cloud/internal/unified_grpc_credentials.h"
#include "google/cloud/internal/oauth2_google_application_default_credentials_file.h"
#include <fstream>
#include <nlohmann/json.hpp>
#include "google/cloud/internal/json_parsing.h"
#include "absl/strings/str_split.h"
#include "google/cloud/internal/oauth2_impersonate_service_account_credentials.h"
#include "google/cloud/internal/grpc_impersonate_service_account.h"

namespace google {
namespace cloud {
GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_BEGIN
namespace internal {

StatusOr<std::shared_ptr<GrpcAuthenticationStrategy>> MaybeLoadImpersonationCredentials(CompletionQueue cq, Options const& options);

} // namespace internal
GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_END
} // namespace cloud
} // namespace google

#endif // GOOGLE_CLOUD_CPP_GOOGLE_CLOUD_INTERNAL_GRPC_GOOGLE_CREDENTIALS_H
2 changes: 1 addition & 1 deletion google/cloud/internal/grpc_impersonate_service_account.cc
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ AsyncAccessTokenSource MakeSource(ImpersonateServiceAccountConfig const& config,
auto stub = MakeMinimalIamCredentialsStub(
CreateAuthenticationStrategy(*config.base_credentials(), std::move(cq),
options),
MakeMinimalIamCredentialsOptions(options));
MakeMinimalIamCredentialsOptions(options, config.service_account_impersonation_url()));

GenerateAccessTokenRequest request;
request.set_name("projects/-/serviceAccounts/" +
Expand Down
6 changes: 5 additions & 1 deletion google/cloud/internal/minimal_iam_credentials_stub.cc
Original file line number Diff line number Diff line change
Expand Up @@ -228,10 +228,14 @@ std::shared_ptr<MinimalIamCredentialsStub> MakeMinimalIamCredentialsStub(
return DecorateMinimalIamCredentialsStub(std::move(impl), options);
}

Options MakeMinimalIamCredentialsOptions(Options options) {
Options MakeMinimalIamCredentialsOptions(Options options, absl::optional<std::string> service_account_impersonation_url) {
// The supplied options come from a service. We are overriding the value of
// its `EndpointOption`.
options.unset<EndpointOption>();
if (service_account_impersonation_url) {
return options.set<EndpointOption>(
*service_account_impersonation_url);
}
auto ep = UniverseDomainEndpoint("iamcredentials.googleapis.com", options);
return options.set<EndpointOption>(std::move(ep));
}
Expand Down
2 changes: 1 addition & 1 deletion google/cloud/internal/minimal_iam_credentials_stub.h
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ std::shared_ptr<MinimalIamCredentialsStub> MakeMinimalIamCredentialsStub(
std::shared_ptr<GrpcAuthenticationStrategy> auth_strategy,
Options const& options);

Options MakeMinimalIamCredentialsOptions(Options options);
Options MakeMinimalIamCredentialsOptions(Options options, absl::optional<std::string> service_account_impersonation_url = absl::nullopt);

} // namespace internal
GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_END
Expand Down
7 changes: 7 additions & 0 deletions google/cloud/internal/oauth2_google_credentials.cc
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
#include "google/cloud/internal/oauth2_http_client_factory.h"
#include "google/cloud/internal/oauth2_service_account_credentials.h"
#include "google/cloud/internal/parse_service_account_p12_file.h"
#include "google/cloud/internal/oauth2_impersonate_service_account_credentials.h"
#include "google/cloud/internal/throw_delegate.h"
#include <nlohmann/json.hpp>
#include <fstream>
Expand Down Expand Up @@ -97,6 +98,12 @@ StatusOr<std::unique_ptr<Credentials>> LoadCredsFromPath(
std::make_unique<ServiceAccountCredentials>(*info, options,
std::move(client_factory)));
}
if (cred_type == "impersonated_service_account") {
auto cfg = MakeImpersonateServiceAccountConfig(contents, options, internal::ErrorContext{});
if (!cfg) return std::move(cfg).status();
return std::unique_ptr<Credentials>(
std::make_unique<ImpersonateServiceAccountCredentials>(*cfg, std::move(client_factory)));
}
return internal::InvalidArgumentError(
"Unsupported credential type (" + cred_type +
") when reading Application Default Credentials file "
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,89 @@ GenerateAccessTokenRequest MakeRequest(

} // namespace

StatusOr<google::cloud::internal::ImpersonateServiceAccountConfig> MakeImpersonateServiceAccountConfig(
std::string const& content, Options options, internal::ErrorContext const& ec) {
auto json = nlohmann::json::parse(content, nullptr, false);
if (!json.is_object()) {
return InvalidArgumentError(
"impersonate service account configuration was not a JSON object",
GCP_ERROR_INFO().WithContext(ec));
}
auto type = ValidateStringField(json, "type", "credentials-file", ec);
if (!type) return std::move(type).status();
if (*type != "impersonated_service_account") {
return InvalidArgumentError(
"mismatched type (" + *type + ") in external account configuration",
GCP_ERROR_INFO().WithContext(ec));
}

auto service_account_impersonation_url = ValidateStringField(json, "service_account_impersonation_url", "credentials-file", ec);
if (!service_account_impersonation_url) return std::move(service_account_impersonation_url).status();

std::vector<std::string> v = absl::StrSplit(*service_account_impersonation_url, "/serviceAccounts/");
if (v.size() != 2) {
return InvalidArgumentError(
absl::StrCat("malformed service_account_impersonation_url: ", *service_account_impersonation_url),
GCP_ERROR_INFO().WithContext(ec));
}
v = absl::StrSplit(v[1], ':');
if (v.size() != 2) {
return InvalidArgumentError(
absl::StrCat("malformed service_account_impersonation_url: ", *service_account_impersonation_url),
GCP_ERROR_INFO().WithContext(ec));
}
auto target_service_account = v[0];

auto delegates = json.find("delegates");
if (delegates != json.end()) {
if (!delegates->is_array()) {
return InvalidArgumentError(
"delegates must be an array", GCP_ERROR_INFO().WithContext(ec));
}
options.set<DelegatesOption>(delegates->get<std::vector<std::string>>());
}

auto source_credentials = json.find("source_credentials");
if (source_credentials == json.end()) {
return InvalidArgumentError(
"missing `source_credentials` field in impersonation service account configuration",
GCP_ERROR_INFO().WithContext(ec));
}

if (!source_credentials->is_object()) {
return InvalidArgumentError(
"`source_credentials` field is not a JSON object in impersonation service account configuration",
GCP_ERROR_INFO().WithContext(ec));
}
auto const source_credentials_contents = source_credentials->dump();
// save source_credentials_contents to a tmp file
(void)setenv("GOOGLE_APPLICATION_CREDENTIALS", "/tmp/base_credentials.json", 1);

std::ofstream myfile;
myfile.open ("/tmp/base_credentials.json");
myfile << source_credentials_contents;
myfile.close();

auto const cred_type = ValidateStringField(*source_credentials, "type", "credentials-file", ec);
if (!type) return std::move(type).status();

if ((*cred_type != "authorized_user" && *cred_type != "external_account" && *cred_type != "service_account")) {
return internal::InvalidArgumentError(
"Unsupported credential type : " + *cred_type,
GCP_ERROR_INFO().WithContext(ec));
}

return google::cloud::internal::ImpersonateServiceAccountConfig(
MakeGoogleDefaultCredentials(options), target_service_account, options, *service_account_impersonation_url);
}

ImpersonateServiceAccountCredentials::ImpersonateServiceAccountCredentials(
google::cloud::internal::ImpersonateServiceAccountConfig const& config,
HttpClientFactory client_factory)
: ImpersonateServiceAccountCredentials(
config, MakeMinimalIamCredentialsRestStub(
rest_internal::MapCredentials(*config.base_credentials()),
config.options(), std::move(client_factory))) {}
config.options(), std::move(client_factory), config.service_account_impersonation_url())) {}

ImpersonateServiceAccountCredentials::ImpersonateServiceAccountCredentials(
google::cloud::internal::ImpersonateServiceAccountConfig const& config,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,27 @@
#include "google/cloud/version.h"
#include <memory>
#include <string>
#include <nlohmann/json.hpp>
#include "google/cloud/internal/make_status.h"
#include "google/cloud/internal/absl_str_cat_quiet.h"
#include "absl/strings/str_split.h"

#include "google/cloud/internal/json_parsing.h"
#include "google/cloud/internal/oauth2_external_account_credentials.h"
#include "google/cloud/internal/oauth2_authorized_user_credentials.h"
#include "google/cloud/internal/oauth2_service_account_credentials.h"

#include <fstream>
#include <stdlib.h>

namespace google {
namespace cloud {
namespace oauth2_internal {
GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_BEGIN

StatusOr<google::cloud::internal::ImpersonateServiceAccountConfig> MakeImpersonateServiceAccountConfig(
std::string const& content, Options options, internal::ErrorContext const& ec);

/**
* Provides Credentials when impersonating an existing service account.
*/
Expand Down
13 changes: 8 additions & 5 deletions google/cloud/internal/oauth2_minimal_iam_credentials_rest.cc
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,13 @@ using ::google::cloud::internal::InvalidArgumentError;

MinimalIamCredentialsRestStub::MinimalIamCredentialsRestStub(
std::shared_ptr<oauth2_internal::Credentials> credentials, Options options,
HttpClientFactory client_factory)
HttpClientFactory client_factory,
absl::optional<std::string> service_account_impersonation_url)
: credentials_(std::move(credentials)),
options_(std::move(options)),
client_factory_(std::move(client_factory)) {}
client_factory_(std::move(client_factory)),
service_account_impersonation_url_(
std::move(service_account_impersonation_url)) {}

StatusOr<google::cloud::AccessToken>
MinimalIamCredentialsRestStub::GenerateAccessToken(
Expand All @@ -52,7 +55,7 @@ MinimalIamCredentialsRestStub::GenerateAccessToken(
rest_internal::RestRequest rest_request;
rest_request.AddHeader(auth_header.value());
rest_request.AddHeader("Content-Type", "application/json");
rest_request.SetPath(MakeRequestPath(request));
rest_request.SetPath(service_account_impersonation_url_ ? *service_account_impersonation_url_ : MakeRequestPath(request));
nlohmann::json payload{
{"delegates", request.delegates},
{"scope", request.scopes},
Expand Down Expand Up @@ -134,14 +137,14 @@ StatusOr<AccessToken> ParseGenerateAccessTokenResponse(

std::shared_ptr<MinimalIamCredentialsRest> MakeMinimalIamCredentialsRestStub(
std::shared_ptr<oauth2_internal::Credentials> credentials, Options options,
HttpClientFactory client_factory) {
HttpClientFactory client_factory, absl::optional<std::string> service_account_impersonation_url) {
auto enable_logging =
options.get<LoggingComponentsOption>().count("rpc") != 0 ||
options.get<LoggingComponentsOption>().count("raw-client") != 0;
std::shared_ptr<MinimalIamCredentialsRest> stub =
std::make_shared<MinimalIamCredentialsRestStub>(
std::move(credentials), std::move(options),
std::move(client_factory));
std::move(client_factory), std::move(service_account_impersonation_url));
if (enable_logging) {
stub = std::make_shared<MinimalIamCredentialsRestLogging>(std::move(stub));
}
Expand Down
7 changes: 5 additions & 2 deletions google/cloud/internal/oauth2_minimal_iam_credentials_rest.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ struct GenerateAccessTokenRequest {
std::chrono::seconds lifetime;
std::vector<std::string> scopes;
std::vector<std::string> delegates;
absl::optional<std::string> service_account_impersonation_url;
};

/// Parse the HTTP response from a `GenerateAccessToken()` call.
Expand Down Expand Up @@ -73,7 +74,8 @@ class MinimalIamCredentialsRestStub : public MinimalIamCredentialsRest {
*/
MinimalIamCredentialsRestStub(
std::shared_ptr<oauth2_internal::Credentials> credentials,
Options options, HttpClientFactory client_factory);
Options options, HttpClientFactory client_factory,
absl::optional<std::string> service_account_impersonation_url = absl::nullopt);

StatusOr<google::cloud::AccessToken> GenerateAccessToken(
GenerateAccessTokenRequest const& request) override;
Expand All @@ -88,6 +90,7 @@ class MinimalIamCredentialsRestStub : public MinimalIamCredentialsRest {
std::shared_ptr<oauth2_internal::Credentials> credentials_;
Options options_;
HttpClientFactory client_factory_;
absl::optional<std::string> service_account_impersonation_url_;
};

/**
Expand All @@ -111,7 +114,7 @@ class MinimalIamCredentialsRestLogging : public MinimalIamCredentialsRest {

std::shared_ptr<MinimalIamCredentialsRest> MakeMinimalIamCredentialsRestStub(
std::shared_ptr<oauth2_internal::Credentials> credentials, Options options,
HttpClientFactory client_factory);
HttpClientFactory client_factory, absl::optional<std::string> service_account_impersonation_url = absl::nullopt);

GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_END
} // namespace oauth2_internal
Expand Down
Loading