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
1 change: 0 additions & 1 deletion ci/cloudbuild/builds/cmake-install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,6 @@ expected_dirs+=(
./include/google/cloud/storage/internal/grpc
./include/google/cloud/storage/internal/rest
./include/google/cloud/storage/mocks
./include/google/cloud/storage/oauth2
./include/google/cloud/storage/testing
# no gRPC services in google/cloud/workflows/type.
./include/google/cloud/workflows/type
Expand Down
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
52 changes: 52 additions & 0 deletions 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
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
74 changes: 73 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,74 @@ 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);
}

// 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);
}
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 +384,8 @@ bool ServiceAccountUseOAuth(ServiceAccountCredentialsInfo const& info) {
}

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

StatusOr<AccessToken> ServiceAccountCredentials::GetTokenOAuth(
Expand Down
25 changes: 25 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,30 @@ StatusOr<std::string> MakeSelfSignedJWT(
ServiceAccountCredentialsInfo const& info,
std::chrono::system_clock::time_point tp);

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

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

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

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

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
47 changes: 24 additions & 23 deletions google/cloud/internal/unified_rest_credentials.cc
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
#include "google/cloud/internal/unified_rest_credentials.h"
#include "google/cloud/common_options.h"
#include "google/cloud/internal/make_jwt_assertion.h"
#include "google/cloud/internal/make_status.h"
#include "google/cloud/internal/oauth2_access_token_credentials.h"
#include "google/cloud/internal/oauth2_anonymous_credentials.h"
#include "google/cloud/internal/oauth2_api_key_credentials.h"
Expand Down Expand Up @@ -49,25 +50,6 @@ std::shared_ptr<oauth2_internal::Credentials> MakeErrorCredentials(
return std::make_shared<oauth2_internal::ErrorCredentials>(std::move(status));
}

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

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

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

} // namespace

std::shared_ptr<oauth2_internal::Credentials> MapCredentials(
Expand Down Expand Up @@ -120,10 +102,29 @@ std::shared_ptr<oauth2_internal::Credentials> MapCredentials(
}

void visit(ServiceAccountConfig const& cfg) override {
result = Decorate(
CreateServiceAccountCredentialsFromJsonContents(
cfg.json_object(), cfg.options(), std::move(client_factory_)),
cfg.options());
if (cfg.file_path().has_value()) {
auto creds =
oauth2_internal::CreateServiceAccountCredentialsFromFilePath(
*cfg.file_path(), cfg.options(), std::move(client_factory_));
if (creds) {
result = Decorate(*creds, cfg.options());
} else {
result = MakeErrorCredentials(std::move(creds).status());
}
} else if (cfg.json_object().has_value()) {
auto creds =
oauth2_internal::CreateServiceAccountCredentialsFromJsonContents(
*cfg.json_object(), cfg.options(), std::move(client_factory_));
if (creds) {
result = Decorate(std::move(*creds), cfg.options());
return;
}
result = MakeErrorCredentials(std::move(creds).status());
} else {
result = MakeErrorCredentials(internal::InternalError(
"ServiceAccountConfig has neither json_object nor file_path",
GCP_ERROR_INFO()));
}
}

void visit(ExternalAccountConfig const& cfg) override {
Expand Down
2 changes: 1 addition & 1 deletion google/cloud/internal/unified_rest_credentials_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -405,7 +405,7 @@ TEST(UnifiedRestCredentialsTest, ServiceAccount) {
EXPECT_CALL(client_factory, Call).Times(0);

auto const config =
internal::ServiceAccountConfig(contents.dump(), Options{});
internal::ServiceAccountConfig(contents.dump(), absl::nullopt, Options{});
auto credentials = MapCredentials(config, client_factory.AsStdFunction());
auto access_token = credentials->GetToken(now);
ASSERT_STATUS_OK(access_token);
Expand Down
Loading