Skip to content
Merged
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
8 changes: 7 additions & 1 deletion google/cloud/credentials.cc
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,13 @@ std::shared_ptr<Credentials> MakeImpersonateServiceAccountCredentials(
std::shared_ptr<Credentials> MakeServiceAccountCredentials(
std::string json_object, Options opts) {
return std::make_shared<internal::ServiceAccountConfig>(
std::move(json_object), std::move(opts));
std::move(json_object), absl::nullopt, std::move(opts));
}

std::shared_ptr<Credentials> MakeServiceAccountCredentialsFromFile(
std::string const& file_path, Options opts) {
return std::make_shared<internal::ServiceAccountConfig>(
absl::nullopt, file_path, std::move(opts));
}

std::shared_ptr<Credentials> MakeExternalAccountCredentials(
Expand Down
67 changes: 66 additions & 1 deletion google/cloud/credentials.h
Original file line number Diff line number Diff line change
Expand Up @@ -301,6 +301,58 @@ std::shared_ptr<Credentials> MakeImpersonateServiceAccountCredentials(
std::shared_ptr<Credentials> MakeServiceAccountCredentials(
std::string json_object, Options opts = {});

/**
* Creates service account credentials from a service account key contained in
* a file.
*
* A [service account] is an account for an application or compute workload
* instead of an individual end user. The recommended practice is to use
* Google Default Credentials, which relies on the configuration of the Google
* Cloud system hosting your application (GCE, GKE, Cloud Run) to authenticate
* your workload or application. But sometimes you may need to create and
* download a [service account key], for example, to use a service account
* when running your application on a system that is not part of Google Cloud.
*
* Service account credentials are used in this latter case.
*
* You can create multiple service account keys for a single service account.
* When you create a service account key, the key is returned as string, in the
* format described by [aip/4112]. This string contains an id for the service
* account, as well as the cryptographical materials (a RSA private key)
* required to authenticate the caller.
*
* Therefore, services account keys should be treated as any other secret
* with security implications. Think of them as unencrypted passwords. Do not
* store them where unauthorized persons or programs may read them.
*
* As stated above, most applications should probably use default credentials,
* maybe pointing them to a file with these contents. Using this function may be
* useful when the service account key is obtained from Cloud Secret Manager or
* a similar service.
*
* [aip/4112]: https://google.aip.dev/auth/4112
* [service account]: https://cloud.google.com/iam/docs/overview#service_account
* [service account key]:
* https://cloud.google.com/iam/docs/creating-managing-service-account-keys#iam-service-account-keys-create-cpp
*
* Use `ScopesOption` to restrict the authentication scope for the obtained
* credentials.
*
* @ingroup guac
*
* @note While JSON file formats are supported for both REST and gRPC transport,
* PKCS#12 is only supported for REST transport.
*
* @param file_path path to file containing the service account key
* Typically applications read this from a file, or download the contents from
* something like Google's secret manager service.
* @param opts optional configuration values. Note that the effect of these
* parameters depends on the underlying transport. For example,
* `LoggingComponentsOption` is ignored by gRPC-based services.
*/
std::shared_ptr<Credentials> MakeServiceAccountCredentialsFromFile(
std::string const& file_path, Options opts = {});

/**
* Creates credentials based on external accounts.
*
Expand Down Expand Up @@ -406,7 +458,9 @@ struct DelegatesOption {
};

/**
* Configure the scopes for `MakeImpersonateServiceAccountCredentials()`
* Configure the scopes for `MakeImpersonateServiceAccountCredentials()`.
* Override the scopes for `MakeServiceAccountCredentials` and
* `MakeServiceAccountCredentialsFromFile()`.
*
* @ingroup options
* @ingroup guac
Expand All @@ -415,6 +469,17 @@ struct ScopesOption {
using Type = std::vector<std::string>;
};

/**
* Overrides the subject for `MakeServiceAccountCredentials` and
* `MakeServiceAccountCredentialsFromFile()`.
*
* @ingroup options
* @ingroup guac
*/
struct SubjectOption {
using Type = std::string;
};

/**
* Configure the access token lifetime
*
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 @@ -87,9 +87,11 @@ std::vector<std::string> const& ImpersonateServiceAccountConfig::delegates()
return options_.get<DelegatesOption>();
}

ServiceAccountConfig::ServiceAccountConfig(std::string json_object,
Options opts)
ServiceAccountConfig::ServiceAccountConfig(
absl::optional<std::string> json_object,
absl::optional<std::string> file_path, Options opts)
: json_object_(std::move(json_object)),
file_path_(std::move(file_path)),
options_(PopulateAuthOptions(std::move(opts))) {}

ExternalAccountConfig::ExternalAccountConfig(std::string json_object,
Expand Down
16 changes: 12 additions & 4 deletions google/cloud/internal/credentials_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -144,15 +144,23 @@ class ImpersonateServiceAccountConfig : public Credentials {

class ServiceAccountConfig : public Credentials {
public:
ServiceAccountConfig(std::string json_object, Options opts);

std::string const& json_object() const { return json_object_; }
// Only one of json_object or file_path should have a value.
// TODO(#15886): Use the C++ type system to make better constructors that
// enforces this comment.
ServiceAccountConfig(absl::optional<std::string> json_object,
absl::optional<std::string> file_path, Options opts);

absl::optional<std::string> const& json_object() const {
return json_object_;
}
absl::optional<std::string> const& file_path() const { return file_path_; }
Options const& options() const { return options_; }

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

std::string json_object_;
absl::optional<std::string> json_object_;
absl::optional<std::string> file_path_;
Options options_;
};

Expand Down
83 changes: 82 additions & 1 deletion google/cloud/internal/oauth2_service_account_credentials.cc
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,18 @@
// limitations under the License.

#include "google/cloud/internal/oauth2_service_account_credentials.h"
#include "google/cloud/credentials.h"
#include "google/cloud/internal/absl_str_join_quiet.h"
#include "google/cloud/internal/getenv.h"
#include "google/cloud/internal/make_jwt_assertion.h"
#include "google/cloud/internal/make_status.h"
#include "google/cloud/internal/oauth2_google_credentials.h"
#include "google/cloud/internal/oauth2_universe_domain.h"
#include "google/cloud/internal/parse_service_account_p12_file.h"
#include "google/cloud/internal/rest_response.h"
#include "google/cloud/internal/sign_using_sha256.h"
#include <nlohmann/json.hpp>
#include <fstream>
#include <functional>

namespace google {
Expand Down Expand Up @@ -240,6 +243,83 @@ StatusOr<std::string> MakeSelfSignedJWT(
info.private_key);
}

StatusOr<std::shared_ptr<Credentials>>
CreateServiceAccountCredentialsFromJsonContents(
std::string const& contents, Options const& options,
HttpClientFactory client_factory) {
auto info = ParseServiceAccountCredentials(contents, "memory");
if (!info) return info.status();

if (options.has<ScopesOption>()) {
auto const& s = options.get<ScopesOption>();
std::set<std::string> scopes{s.begin(), s.end()};
info->scopes = std::move(scopes);
}
Comment thread
scotthart marked this conversation as resolved.

if (options.has<SubjectOption>()) {
info->subject = options.get<SubjectOption>();
}

// Verify this is usable before returning it.
auto const tp = std::chrono::system_clock::time_point{};
auto const components = AssertionComponentsFromInfo(*info, tp);
auto jwt = MakeJWTAssertionNoThrow(components.first, components.second,
info->private_key);
if (!jwt) return jwt.status();
return StatusOr<std::shared_ptr<Credentials>>(
std::make_shared<ServiceAccountCredentials>(*info, options,
std::move(client_factory)));
}

StatusOr<std::shared_ptr<Credentials>>
CreateServiceAccountCredentialsFromJsonFilePath(
std::string const& path, Options const& options,
HttpClientFactory client_factory) {
std::ifstream is(path);
if (!is.is_open()) {
// We use kUnknown here because we don't know if the file does not exist, or
// if we were unable to open it for some other reason.
return internal::UnknownError("Cannot open credentials file " + path,
GCP_ERROR_INFO());
}
std::string contents(std::istreambuf_iterator<char>{is}, {});
return CreateServiceAccountCredentialsFromJsonContents(
std::move(contents), options, std::move(client_factory));
}

StatusOr<std::shared_ptr<Credentials>>
CreateServiceAccountCredentialsFromP12FilePath(
std::string const& path, Options const& options,
HttpClientFactory client_factory) {
auto info = ParseServiceAccountP12File(path);
if (!info) return std::move(info).status();

if (options.has<ScopesOption>()) {
auto const& s = options.get<ScopesOption>();
std::set<std::string> scopes{s.begin(), s.end()};
info->scopes = std::move(scopes);
}

if (options.has<SubjectOption>()) {
info->subject = options.get<SubjectOption>();
}

return StatusOr<std::shared_ptr<Credentials>>(
std::make_shared<ServiceAccountCredentials>(*info, options,
std::move(client_factory)));
}

StatusOr<std::shared_ptr<Credentials>>
CreateServiceAccountCredentialsFromFilePath(std::string const& path,
Options const& options,
HttpClientFactory client_factory) {
auto credentials = CreateServiceAccountCredentialsFromJsonFilePath(
path, options, client_factory);
if (credentials) return *credentials;
return CreateServiceAccountCredentialsFromP12FilePath(
path, options, std::move(client_factory));
}

ServiceAccountCredentials::ServiceAccountCredentials(
ServiceAccountCredentialsInfo info, Options options,
HttpClientFactory client_factory)
Expand Down Expand Up @@ -313,7 +393,8 @@ bool ServiceAccountUseOAuth(ServiceAccountCredentialsInfo const& info) {
}

bool ServiceAccountCredentials::UseOAuth() {
return ServiceAccountUseOAuth(info_);
return options_.has<DisableSelfSignedJWTOption>() ||
ServiceAccountUseOAuth(info_);
}

StatusOr<AccessToken> ServiceAccountCredentials::GetTokenOAuth(
Expand Down
53 changes: 53 additions & 0 deletions google/cloud/internal/oauth2_service_account_credentials.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
#include "absl/types/optional.h"
#include <chrono>
#include <string>
#include <variant>
#include <vector>

namespace google {
Expand Down Expand Up @@ -127,6 +128,58 @@ StatusOr<std::string> MakeSelfSignedJWT(
ServiceAccountCredentialsInfo const& info,
std::chrono::system_clock::time_point tp);

/**
* Creates a ServiceAccountCredentials from a JSON string.
*/
StatusOr<std::shared_ptr<Credentials>>
CreateServiceAccountCredentialsFromJsonContents(
std::string const& contents, Options const& options,
HttpClientFactory client_factory);

/**
* Creates a ServiceAccountCredentials from a JSON file at the specified path.
*/
StatusOr<std::shared_ptr<Credentials>>
CreateServiceAccountCredentialsFromJsonFilePath(
std::string const& path, Options const& options,
HttpClientFactory client_factory);

/**
* Creates a ServiceAccountCredentials from a P12 file at the specified path.
*/
StatusOr<std::shared_ptr<Credentials>>
CreateServiceAccountCredentialsFromP12FilePath(
std::string const& path, Options const& options,
HttpClientFactory client_factory);

/**
* Creates a ServiceAccountCredentials from a file at the specified path.
*
* @note This function automatically detects if the file is a JSON or P12 (aka
* PFX aka PKCS#12) file and tries to load the file as a service account
* credential. We strongly recommend that applications use JSON files for
* service account key files.
*
* These credentials use the cloud-platform OAuth 2.0 scope, defined by
* `GoogleOAuthScopeCloudPlatform()`. To specify alternate scopes, use the
* `google::cloud::ScopesOption`.
*/

StatusOr<std::shared_ptr<Credentials>>
CreateServiceAccountCredentialsFromFilePath(std::string const& path,
Options const& options,
HttpClientFactory client_factory);

/**
* Specifying this Option prevents self-signed JWTs from being used.
*
* Some services, namely storage, have more stringent requirements w.r.t.
* self-signed JWTs.
*/
struct DisableSelfSignedJWTOption {
using Type = std::monostate;
};

/**
* Implements service account credentials for REST clients.
*
Expand Down
24 changes: 22 additions & 2 deletions google/cloud/internal/unified_grpc_credentials.cc
Original file line number Diff line number Diff line change
Expand Up @@ -115,8 +115,28 @@ std::shared_ptr<GrpcAuthenticationStrategy> CreateAuthenticationStrategy(
std::move(options));
}
void visit(ServiceAccountConfig const& cfg) override {
result = std::make_unique<GrpcServiceAccountAuthentication>(
cfg.json_object(), std::move(options));
if (cfg.file_path().has_value()) {
std::ifstream is(*cfg.file_path());
if (!is.is_open()) {
// We use kUnknown here because we don't know if the file does not
// exist, or if we were unable to open it for some other reason.
result = std::make_unique<GrpcErrorCredentialsAuthentication>(
ErrorCredentialsConfig{UnknownError(
"Cannot open credentials file " + *cfg.file_path(),
GCP_ERROR_INFO())});
}
std::string contents(std::istreambuf_iterator<char>{is}, {});
result = std::make_unique<GrpcServiceAccountAuthentication>(
std::move(contents), std::move(options));
} else if (cfg.json_object().has_value()) {
result = std::make_unique<GrpcServiceAccountAuthentication>(
*cfg.json_object(), std::move(options));
} else {
result = std::make_unique<GrpcErrorCredentialsAuthentication>(
ErrorCredentialsConfig{InternalError(
"ServiceAccountConfig has neither json_object nor file_path",
GCP_ERROR_INFO())});
}
}
void visit(ExternalAccountConfig const& cfg) override {
grpc::SslCredentialsOptions ssl_options;
Expand Down
Loading