mirror of https://github.com/grpc/grpc.git
parent
6046624c42
commit
1faf030cc4
18 changed files with 1005 additions and 2 deletions
@ -0,0 +1,415 @@ |
||||
//
|
||||
// Copyright 2020 gRPC authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
#include <grpc/support/port_platform.h> |
||||
|
||||
#include "src/core/lib/security/credentials/external/aws_external_account_credentials.h" |
||||
|
||||
#include "absl/strings/str_format.h" |
||||
#include "absl/strings/str_join.h" |
||||
#include "absl/strings/str_replace.h" |
||||
|
||||
#include "src/core/lib/gpr/env.h" |
||||
|
||||
namespace grpc_core { |
||||
|
||||
namespace { |
||||
|
||||
const char* kExpectedEnvironmentId = "aws1"; |
||||
|
||||
const char* kRegionEnvVar = "AWS_REGION"; |
||||
const char* kAccessKeyIdEnvVar = "AWS_ACCESS_KEY_ID"; |
||||
const char* kSecretAccessKeyEnvVar = "AWS_SECRET_ACCESS_KEY"; |
||||
const char* kSessionTokenEnvVar = "AWS_SESSION_TOKEN"; |
||||
|
||||
std::string UrlEncode(const absl::string_view& s) { |
||||
const char* hex = "0123456789ABCDEF"; |
||||
std::string result; |
||||
result.reserve(s.length()); |
||||
for (auto c : s) { |
||||
if ((c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') || |
||||
(c >= 'a' && c <= 'z') || c == '-' || c == '_' || c == '!' || |
||||
c == '\'' || c == '(' || c == ')' || c == '*' || c == '~' || c == '.') { |
||||
result.push_back(c); |
||||
} else { |
||||
result.push_back('%'); |
||||
result.push_back(hex[static_cast<unsigned char>(c) >> 4]); |
||||
result.push_back(hex[static_cast<unsigned char>(c) & 15]); |
||||
} |
||||
} |
||||
return result; |
||||
} |
||||
|
||||
} // namespace
|
||||
|
||||
RefCountedPtr<AwsExternalAccountCredentials> |
||||
AwsExternalAccountCredentials::Create(ExternalAccountCredentialsOptions options, |
||||
std::vector<std::string> scopes, |
||||
grpc_error** error) { |
||||
auto creds = MakeRefCounted<AwsExternalAccountCredentials>( |
||||
std::move(options), std::move(scopes), error); |
||||
if (*error == GRPC_ERROR_NONE) { |
||||
return creds; |
||||
} else { |
||||
return nullptr; |
||||
} |
||||
} |
||||
|
||||
AwsExternalAccountCredentials::AwsExternalAccountCredentials( |
||||
ExternalAccountCredentialsOptions options, std::vector<std::string> scopes, |
||||
grpc_error** error) |
||||
: ExternalAccountCredentials(options, std::move(scopes)) { |
||||
auto it = options.credential_source.object_value().find("environment_id"); |
||||
if (it == options.credential_source.object_value().end()) { |
||||
*error = GRPC_ERROR_CREATE_FROM_STATIC_STRING( |
||||
"environment_id field not present."); |
||||
return; |
||||
} |
||||
if (it->second.type() != Json::Type::STRING) { |
||||
*error = GRPC_ERROR_CREATE_FROM_STATIC_STRING( |
||||
"environment_id field must be a string."); |
||||
return; |
||||
} |
||||
if (it->second.string_value() != kExpectedEnvironmentId) { |
||||
*error = |
||||
GRPC_ERROR_CREATE_FROM_STATIC_STRING("environment_id does not match."); |
||||
return; |
||||
} |
||||
it = options.credential_source.object_value().find("region_url"); |
||||
if (it == options.credential_source.object_value().end()) { |
||||
*error = |
||||
GRPC_ERROR_CREATE_FROM_STATIC_STRING("region_url field not present."); |
||||
return; |
||||
} |
||||
if (it->second.type() != Json::Type::STRING) { |
||||
*error = GRPC_ERROR_CREATE_FROM_STATIC_STRING( |
||||
"region_url field must be a string."); |
||||
return; |
||||
} |
||||
region_url_ = it->second.string_value(); |
||||
it = options.credential_source.object_value().find("url"); |
||||
if (it != options.credential_source.object_value().end() && |
||||
it->second.type() == Json::Type::STRING) { |
||||
url_ = it->second.string_value(); |
||||
} |
||||
it = options.credential_source.object_value().find( |
||||
"regional_cred_verification_url"); |
||||
if (it == options.credential_source.object_value().end()) { |
||||
*error = GRPC_ERROR_CREATE_FROM_STATIC_STRING( |
||||
"regional_cred_verification_url field not present."); |
||||
return; |
||||
} |
||||
if (it->second.type() != Json::Type::STRING) { |
||||
*error = GRPC_ERROR_CREATE_FROM_STATIC_STRING( |
||||
"regional_cred_verification_url field must be a string."); |
||||
return; |
||||
} |
||||
regional_cred_verification_url_ = it->second.string_value(); |
||||
} |
||||
|
||||
void AwsExternalAccountCredentials::RetrieveSubjectToken( |
||||
HTTPRequestContext* ctx, const ExternalAccountCredentialsOptions& options, |
||||
std::function<void(std::string, grpc_error*)> cb) { |
||||
if (ctx == nullptr) { |
||||
FinishRetrieveSubjectToken( |
||||
"", |
||||
GRPC_ERROR_CREATE_FROM_STATIC_STRING( |
||||
"Missing HTTPRequestContext to start subject token retrieval.")); |
||||
return; |
||||
} |
||||
ctx_ = ctx; |
||||
cb_ = cb; |
||||
if (signer_ != nullptr) { |
||||
BuildSubjectToken(); |
||||
} else { |
||||
RetrieveRegion(); |
||||
} |
||||
} |
||||
|
||||
void AwsExternalAccountCredentials::RetrieveRegion() { |
||||
UniquePtr<char> region_from_env(gpr_getenv(kRegionEnvVar)); |
||||
if (region_from_env != nullptr) { |
||||
region_ = std::string(region_from_env.get()); |
||||
if (url_.empty()) { |
||||
RetrieveSigningKeys(); |
||||
} else { |
||||
RetrieveRoleName(); |
||||
} |
||||
return; |
||||
} |
||||
grpc_uri* uri = grpc_uri_parse(region_url_, false); |
||||
if (uri == nullptr) { |
||||
FinishRetrieveSubjectToken( |
||||
"", |
||||
GRPC_ERROR_CREATE_FROM_COPIED_STRING( |
||||
absl::StrFormat("Invalid region url: %s.", region_url_).c_str())); |
||||
return; |
||||
} |
||||
grpc_httpcli_request request; |
||||
memset(&request, 0, sizeof(grpc_httpcli_request)); |
||||
request.host = const_cast<char*>(uri->authority); |
||||
request.http.path = gpr_strdup(uri->path); |
||||
request.handshaker = (strcmp(uri->scheme, "https") == 0) |
||||
? &grpc_httpcli_ssl |
||||
: &grpc_httpcli_plaintext; |
||||
grpc_resource_quota* resource_quota = |
||||
grpc_resource_quota_create("external_account_credentials"); |
||||
grpc_http_response_destroy(&ctx_->response); |
||||
ctx_->response = {}; |
||||
GRPC_CLOSURE_INIT(&ctx_->closure, OnRetrieveRegion, this, nullptr); |
||||
grpc_httpcli_get(ctx_->httpcli_context, ctx_->pollent, resource_quota, |
||||
&request, ctx_->deadline, &ctx_->closure, &ctx_->response); |
||||
grpc_resource_quota_unref_internal(resource_quota); |
||||
grpc_http_request_destroy(&request.http); |
||||
grpc_uri_destroy(uri); |
||||
} |
||||
|
||||
void AwsExternalAccountCredentials::OnRetrieveRegion(void* arg, |
||||
grpc_error* error) { |
||||
AwsExternalAccountCredentials* self = |
||||
static_cast<AwsExternalAccountCredentials*>(arg); |
||||
self->OnRetrieveRegionInternal(GRPC_ERROR_REF(error)); |
||||
} |
||||
|
||||
void AwsExternalAccountCredentials::OnRetrieveRegionInternal( |
||||
grpc_error* error) { |
||||
if (error != GRPC_ERROR_NONE) { |
||||
FinishRetrieveSubjectToken("", error); |
||||
return; |
||||
} |
||||
// Remove the last letter of availability zone to get pure region
|
||||
absl::string_view response_body(ctx_->response.body, |
||||
ctx_->response.body_length); |
||||
region_ = std::string(response_body.substr(0, response_body.size() - 1)); |
||||
if (url_.empty()) { |
||||
RetrieveSigningKeys(); |
||||
} else { |
||||
RetrieveRoleName(); |
||||
} |
||||
} |
||||
|
||||
void AwsExternalAccountCredentials::RetrieveRoleName() { |
||||
grpc_uri* uri = grpc_uri_parse(url_, false); |
||||
if (uri == nullptr) { |
||||
FinishRetrieveSubjectToken( |
||||
"", GRPC_ERROR_CREATE_FROM_COPIED_STRING( |
||||
absl::StrFormat("Invalid url: %s.", url_).c_str())); |
||||
return; |
||||
} |
||||
grpc_httpcli_request request; |
||||
memset(&request, 0, sizeof(grpc_httpcli_request)); |
||||
request.host = const_cast<char*>(uri->authority); |
||||
request.http.path = gpr_strdup(uri->path); |
||||
request.handshaker = (strcmp(uri->scheme, "https") == 0) |
||||
? &grpc_httpcli_ssl |
||||
: &grpc_httpcli_plaintext; |
||||
grpc_resource_quota* resource_quota = |
||||
grpc_resource_quota_create("external_account_credentials"); |
||||
grpc_http_response_destroy(&ctx_->response); |
||||
ctx_->response = {}; |
||||
GRPC_CLOSURE_INIT(&ctx_->closure, OnRetrieveRoleName, this, nullptr); |
||||
grpc_httpcli_get(ctx_->httpcli_context, ctx_->pollent, resource_quota, |
||||
&request, ctx_->deadline, &ctx_->closure, &ctx_->response); |
||||
grpc_resource_quota_unref_internal(resource_quota); |
||||
grpc_http_request_destroy(&request.http); |
||||
grpc_uri_destroy(uri); |
||||
} |
||||
|
||||
void AwsExternalAccountCredentials::OnRetrieveRoleName(void* arg, |
||||
grpc_error* error) { |
||||
AwsExternalAccountCredentials* self = |
||||
static_cast<AwsExternalAccountCredentials*>(arg); |
||||
self->OnRetrieveRoleNameInternal(GRPC_ERROR_REF(error)); |
||||
} |
||||
|
||||
void AwsExternalAccountCredentials::OnRetrieveRoleNameInternal( |
||||
grpc_error* error) { |
||||
if (error != GRPC_ERROR_NONE) { |
||||
FinishRetrieveSubjectToken("", error); |
||||
return; |
||||
} |
||||
role_name_ = std::string(ctx_->response.body); |
||||
RetrieveSigningKeys(); |
||||
} |
||||
|
||||
void AwsExternalAccountCredentials::RetrieveSigningKeys() { |
||||
UniquePtr<char> access_key_id_from_env(gpr_getenv(kAccessKeyIdEnvVar)); |
||||
UniquePtr<char> secret_access_key_from_env( |
||||
gpr_getenv(kSecretAccessKeyEnvVar)); |
||||
UniquePtr<char> token_from_env(gpr_getenv(kSessionTokenEnvVar)); |
||||
if (access_key_id_from_env != nullptr && |
||||
secret_access_key_from_env != nullptr && token_from_env != nullptr) { |
||||
access_key_id_ = std::string(access_key_id_from_env.get()); |
||||
secret_access_key_ = std::string(secret_access_key_from_env.get()); |
||||
token_ = std::string(token_from_env.get()); |
||||
BuildSubjectToken(); |
||||
return; |
||||
} |
||||
if (role_name_.empty()) { |
||||
FinishRetrieveSubjectToken( |
||||
"", GRPC_ERROR_CREATE_FROM_STATIC_STRING( |
||||
"Missing role name when retrieving signing keys.")); |
||||
return; |
||||
} |
||||
std::string url_with_role_name = absl::StrCat(url_, "/", role_name_); |
||||
grpc_uri* uri = grpc_uri_parse(url_with_role_name, false); |
||||
if (uri == nullptr) { |
||||
FinishRetrieveSubjectToken( |
||||
"", GRPC_ERROR_CREATE_FROM_COPIED_STRING( |
||||
absl::StrFormat("Invalid url with role name: %s.", |
||||
url_with_role_name) |
||||
.c_str())); |
||||
return; |
||||
} |
||||
grpc_httpcli_request request; |
||||
memset(&request, 0, sizeof(grpc_httpcli_request)); |
||||
request.host = const_cast<char*>(uri->authority); |
||||
request.http.path = gpr_strdup(uri->path); |
||||
request.handshaker = (strcmp(uri->scheme, "https") == 0) |
||||
? &grpc_httpcli_ssl |
||||
: &grpc_httpcli_plaintext; |
||||
grpc_resource_quota* resource_quota = |
||||
grpc_resource_quota_create("external_account_credentials"); |
||||
grpc_http_response_destroy(&ctx_->response); |
||||
ctx_->response = {}; |
||||
GRPC_CLOSURE_INIT(&ctx_->closure, OnRetrieveSigningKeys, this, nullptr); |
||||
grpc_httpcli_get(ctx_->httpcli_context, ctx_->pollent, resource_quota, |
||||
&request, ctx_->deadline, &ctx_->closure, &ctx_->response); |
||||
grpc_resource_quota_unref_internal(resource_quota); |
||||
grpc_http_request_destroy(&request.http); |
||||
grpc_uri_destroy(uri); |
||||
} |
||||
|
||||
void AwsExternalAccountCredentials::OnRetrieveSigningKeys(void* arg, |
||||
grpc_error* error) { |
||||
AwsExternalAccountCredentials* self = |
||||
static_cast<AwsExternalAccountCredentials*>(arg); |
||||
self->OnRetrieveSigningKeysInternal(GRPC_ERROR_REF(error)); |
||||
} |
||||
|
||||
void AwsExternalAccountCredentials::OnRetrieveSigningKeysInternal( |
||||
grpc_error* error) { |
||||
if (error != GRPC_ERROR_NONE) { |
||||
FinishRetrieveSubjectToken("", error); |
||||
return; |
||||
} |
||||
absl::string_view response_body(ctx_->response.body, |
||||
ctx_->response.body_length); |
||||
Json json = Json::Parse(response_body, &error); |
||||
if (error != GRPC_ERROR_NONE || json.type() != Json::Type::OBJECT) { |
||||
FinishRetrieveSubjectToken( |
||||
"", GRPC_ERROR_CREATE_REFERENCING_FROM_STATIC_STRING( |
||||
"Invalid retrieve signing keys response.", &error, 1)); |
||||
GRPC_ERROR_UNREF(error); |
||||
return; |
||||
} |
||||
auto it = json.object_value().find("access_key_id"); |
||||
if (it != json.object_value().end() && |
||||
it->second.type() == Json::Type::STRING) { |
||||
access_key_id_ = it->second.string_value(); |
||||
} else { |
||||
FinishRetrieveSubjectToken( |
||||
"", GRPC_ERROR_CREATE_FROM_COPIED_STRING( |
||||
absl::StrFormat("Missing or invalid access_key_id in %s.", |
||||
response_body) |
||||
.c_str())); |
||||
return; |
||||
} |
||||
it = json.object_value().find("secret_access_key"); |
||||
if (it != json.object_value().end() && |
||||
it->second.type() == Json::Type::STRING) { |
||||
secret_access_key_ = it->second.string_value(); |
||||
} else { |
||||
FinishRetrieveSubjectToken( |
||||
"", GRPC_ERROR_CREATE_FROM_COPIED_STRING( |
||||
absl::StrFormat("Missing or invalid secret_access_key in %s.", |
||||
response_body) |
||||
.c_str())); |
||||
return; |
||||
} |
||||
it = json.object_value().find("token"); |
||||
if (it != json.object_value().end() && |
||||
it->second.type() == Json::Type::STRING) { |
||||
token_ = it->second.string_value(); |
||||
} else { |
||||
FinishRetrieveSubjectToken( |
||||
"", |
||||
GRPC_ERROR_CREATE_FROM_COPIED_STRING( |
||||
absl::StrFormat("Missing or invalid token in %s.", response_body) |
||||
.c_str())); |
||||
return; |
||||
} |
||||
BuildSubjectToken(); |
||||
} |
||||
|
||||
void AwsExternalAccountCredentials::BuildSubjectToken() { |
||||
grpc_error* error = GRPC_ERROR_NONE; |
||||
if (signer_ == nullptr) { |
||||
cred_verification_url_ = absl::StrReplaceAll( |
||||
regional_cred_verification_url_, {{"{region}", region_}}); |
||||
signer_ = absl::make_unique<AwsRequestSigner>( |
||||
access_key_id_, secret_access_key_, token_, "POST", |
||||
cred_verification_url_, region_, "", |
||||
std::map<std::string, std::string>(), &error); |
||||
if (error != GRPC_ERROR_NONE) { |
||||
FinishRetrieveSubjectToken( |
||||
"", GRPC_ERROR_CREATE_REFERENCING_FROM_STATIC_STRING( |
||||
"Creating aws request signer failed.", &error, 1)); |
||||
GRPC_ERROR_UNREF(error); |
||||
return; |
||||
} |
||||
} |
||||
auto signed_headers = signer_->GetSignedRequestHeaders(); |
||||
if (error != GRPC_ERROR_NONE) { |
||||
FinishRetrieveSubjectToken("", |
||||
GRPC_ERROR_CREATE_REFERENCING_FROM_STATIC_STRING( |
||||
"Invalid getting signed request" |
||||
"headers.", |
||||
&error, 1)); |
||||
GRPC_ERROR_UNREF(error); |
||||
return; |
||||
} |
||||
// Construct subject token
|
||||
Json::Array headers; |
||||
headers.push_back(Json( |
||||
{{"key", "Authorization"}, {"value", signed_headers["Authorization"]}})); |
||||
headers.push_back(Json({{"key", "host"}, {"value", signed_headers["host"]}})); |
||||
headers.push_back( |
||||
Json({{"key", "x-amz-date"}, {"value", signed_headers["x-amz-date"]}})); |
||||
Json::Object object{{"url", Json(cred_verification_url_)}, |
||||
{"method", Json("POST")}, |
||||
{"body", Json("")}, |
||||
{"headers", Json(headers)}}; |
||||
Json subject_token_json(object); |
||||
std::string subject_token = UrlEncode(subject_token_json.Dump()); |
||||
FinishRetrieveSubjectToken(subject_token, GRPC_ERROR_NONE); |
||||
} |
||||
|
||||
void AwsExternalAccountCredentials::FinishRetrieveSubjectToken( |
||||
std::string subject_token, grpc_error* error) { |
||||
// Reset context
|
||||
ctx_ = nullptr; |
||||
// Move object state into local variables.
|
||||
auto cb = cb_; |
||||
cb_ = nullptr; |
||||
// Invoke the callback.
|
||||
if (error != GRPC_ERROR_NONE) { |
||||
cb("", error); |
||||
} else { |
||||
cb(subject_token, GRPC_ERROR_NONE); |
||||
} |
||||
} |
||||
|
||||
} // namespace grpc_core
|
@ -0,0 +1,79 @@ |
||||
//
|
||||
// Copyright 2020 gRPC authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
#ifndef GRPC_CORE_LIB_SECURITY_CREDENTIALS_EXTERNAL_AWS_EXTERNAL_ACCOUNT_CREDENTIALS_H |
||||
#define GRPC_CORE_LIB_SECURITY_CREDENTIALS_EXTERNAL_AWS_EXTERNAL_ACCOUNT_CREDENTIALS_H |
||||
|
||||
#include <grpc/support/port_platform.h> |
||||
|
||||
#include "src/core/lib/security/credentials/external/external_account_credentials.h" |
||||
|
||||
#include "src/core/lib/security/credentials/external/aws_request_signer.h" |
||||
|
||||
namespace grpc_core { |
||||
|
||||
class AwsExternalAccountCredentials final : public ExternalAccountCredentials { |
||||
public: |
||||
static RefCountedPtr<AwsExternalAccountCredentials> Create( |
||||
ExternalAccountCredentialsOptions options, |
||||
std::vector<std::string> scopes, grpc_error** error); |
||||
|
||||
AwsExternalAccountCredentials(ExternalAccountCredentialsOptions options, |
||||
std::vector<std::string> scopes, |
||||
grpc_error** error); |
||||
|
||||
private: |
||||
void RetrieveSubjectToken( |
||||
HTTPRequestContext* ctx, const ExternalAccountCredentialsOptions& options, |
||||
std::function<void(std::string, grpc_error*)> cb) override; |
||||
|
||||
void RetrieveRegion(); |
||||
static void OnRetrieveRegion(void* arg, grpc_error* error); |
||||
void OnRetrieveRegionInternal(grpc_error* error); |
||||
|
||||
void RetrieveRoleName(); |
||||
static void OnRetrieveRoleName(void* arg, grpc_error* error); |
||||
void OnRetrieveRoleNameInternal(grpc_error* error); |
||||
|
||||
void RetrieveSigningKeys(); |
||||
static void OnRetrieveSigningKeys(void* arg, grpc_error* error); |
||||
void OnRetrieveSigningKeysInternal(grpc_error* error); |
||||
|
||||
void BuildSubjectToken(); |
||||
void FinishRetrieveSubjectToken(std::string subject_token, grpc_error* error); |
||||
|
||||
// Fields of credential source
|
||||
std::string region_url_; |
||||
std::string url_; |
||||
std::string regional_cred_verification_url_; |
||||
|
||||
// Information required by request signer
|
||||
std::string region_; |
||||
std::string role_name_; |
||||
std::string access_key_id_; |
||||
std::string secret_access_key_; |
||||
std::string token_; |
||||
|
||||
std::unique_ptr<AwsRequestSigner> signer_; |
||||
std::string cred_verification_url_; |
||||
|
||||
HTTPRequestContext* ctx_ = nullptr; |
||||
std::function<void(std::string, grpc_error*)> cb_ = nullptr; |
||||
}; |
||||
|
||||
} // namespace grpc_core
|
||||
|
||||
#endif // GRPC_CORE_LIB_SECURITY_CREDENTIALS_EXTERNAL_AWS_EXTERNAL_ACCOUNT_CREDENTIALS_H
|
Loading…
Reference in new issue