Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

aws: iam roles anywhere patch part 1 #38786

Open
wants to merge 38 commits into
base: main
Choose a base branch
from
Open
Changes from 1 commit
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
94bf911
more refactoring
nbaws Mar 1, 2025
8aa90cf
add dwyu
nbaws Mar 1, 2025
f039dcc
refactor
nbaws Mar 1, 2025
9397db0
finished refactor
nbaws Mar 2, 2025
896f17b
remove commented
nbaws Mar 2, 2025
0c8fc0e
update includes
nbaws Mar 2, 2025
ec51df0
update test cases
nbaws Mar 3, 2025
3a2c047
update test cases
nbaws Mar 3, 2025
56eceec
dwyu commit
nbaws Mar 5, 2025
0f23f99
update credential_provider_chains
nbaws Mar 5, 2025
ba7a1e9
update deps
nbaws Mar 6, 2025
d18330a
update tests
nbaws Mar 6, 2025
6c82f54
remove extraneous comments
nbaws Mar 6, 2025
1f28f19
fix logging
nbaws Mar 6, 2025
98a6a6d
more log statements
nbaws Mar 6, 2025
c88732c
coverage
nbaws Mar 7, 2025
ced9f29
remove working file
nbaws Mar 7, 2025
0772ec0
update tests
nbaws Mar 7, 2025
decf4a1
test leak
nbaws Mar 7, 2025
f0cabb8
test case coverage
nbaws Mar 7, 2025
1a44c4d
remove untestable code path
nbaws Mar 7, 2025
307ffc5
fix test cases
nbaws Mar 7, 2025
48e2ca1
format
nbaws Mar 7, 2025
e9e5645
format
nbaws Mar 7, 2025
bee28f3
remove comments
nbaws Mar 8, 2025
4e2ac1e
dedupe friend class
nbaws Mar 8, 2025
19a9031
revert key derivation change
nbaws Mar 10, 2025
794478f
stub key derivation
nbaws Mar 11, 2025
2568d6c
stub tests
nbaws Mar 11, 2025
b537f5d
fix stubbed test
nbaws Mar 11, 2025
208d812
fix test spacing
nbaws Mar 11, 2025
ace346f
missed a comment
nbaws Mar 11, 2025
e67d7cd
statusor change
nbaws Mar 11, 2025
79dc4cd
more coverage
nbaws Mar 12, 2025
1dc9a8b
more coverage
nbaws Mar 12, 2025
d29035a
signer base and x509 credentials
nbaws Mar 18, 2025
1e5a40e
merge main
nbaws Mar 18, 2025
a5cabc8
file naming
nbaws Mar 19, 2025
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
Prev Previous commit
Next Next commit
signer base and x509 credentials
Signed-off-by: Nigel Brittain <nbaws@amazon.com>
  • Loading branch information
nbaws committed Mar 18, 2025
commit d29035a2d18bc504c0e771c03b2e54063a11e903
15 changes: 15 additions & 0 deletions source/extensions/common/aws/BUILD
Original file line number Diff line number Diff line change
@@ -54,6 +54,21 @@ envoy_cc_library(
],
)

envoy_cc_library(
name = "iam_roles_anywhere_signer_base",
srcs = ["iam_roles_anywhere_signer_base.cc"],
hdrs = ["iam_roles_anywhere_signer_base.h"],
deps = [
":credentials_provider_interface",
":signer_interface",
":utility_lib",
"//source/common/buffer:buffer_lib",
"//source/common/common:utility_lib",
"//source/common/crypto:utility_lib",
"//source/common/http:message_lib",
],
)

envoy_cc_library(
name = "credentials_provider_interface",
srcs = ["credentials_provider.cc"],
84 changes: 82 additions & 2 deletions source/extensions/common/aws/credentials_provider.h
Original file line number Diff line number Diff line change
@@ -4,12 +4,14 @@
#include <string>

#include "envoy/common/pure.h"
#include "envoy/common/time.h"

#include "source/common/common/cleanup.h"
#include "source/common/common/lock_guard.h"
#include "source/common/common/thread.h"

#include "absl/types/optional.h"
#include "openssl/evp.h"

namespace Envoy {
namespace Extensions {
@@ -21,13 +23,14 @@ constexpr char AWS_SECRET_ACCESS_KEY[] = "AWS_SECRET_ACCESS_KEY";
constexpr char AWS_SESSION_TOKEN[] = "AWS_SESSION_TOKEN";

/**
* AWS credentials container
* AWS credentials containers
*
* If a credential component was not found in the execution environment, it's getter method will
* return absl::nullopt. Credential components with the empty string value are treated as not found.
*/
class Credentials {
public:
// Access Key Credentials
explicit Credentials(absl::string_view access_key_id = absl::string_view(),
absl::string_view secret_access_key = absl::string_view(),
absl::string_view session_token = absl::string_view()) {
@@ -66,7 +69,67 @@ class Credentials {

using CredentialsPendingCallback = std::function<void()>;

/**
/*
* X509 Credentials used for IAM Roles Anywhere
*/

class X509Credentials {
public:
enum class PublicKeySignatureAlgorithm {
RSA,
ECDSA,
};

// X509 Credentials
X509Credentials(absl::string_view certificate_b64,
PublicKeySignatureAlgorithm certificate_signature_algorithm,
absl::string_view certificate_serial,
absl::optional<absl::string_view> certificate_chain_b64,
absl::string_view certificate_private_key_pem,
SystemTime certificate_expiration_time)
: certificate_b64_(certificate_b64),
certificate_private_key_pem_(certificate_private_key_pem),
certificate_serial_(certificate_serial),
certificate_expiration_(certificate_expiration_time),
certificate_signature_algorithm_(certificate_signature_algorithm) {
if (certificate_chain_b64.has_value()) {
certificate_chain_b64_ = certificate_chain_b64.value();
}
}

X509Credentials() = default;

const absl::optional<std::string>& certificateDerB64() const { return certificate_b64_; }

const absl::optional<std::string>& certificateSerial() const { return certificate_serial_; }

const absl::optional<SystemTime>& certificateExpiration() const {
return certificate_expiration_;
}

const absl::optional<std::string>& certificateChainDerB64() const {
return certificate_chain_b64_;
}

const absl::optional<PublicKeySignatureAlgorithm>& publicKeySignatureAlgorithm() const {
return certificate_signature_algorithm_;
}

const absl::optional<std::string> certificatePrivateKey() const {
return certificate_private_key_pem_;
}

private:
// RolesAnywhere certificate based credentials
absl::optional<std::string> certificate_b64_ = absl::nullopt;
absl::optional<std::string> certificate_chain_b64_ = absl::nullopt;
absl::optional<std::string> certificate_private_key_pem_ = absl::nullopt;
absl::optional<std::string> certificate_serial_ = absl::nullopt;
absl::optional<SystemTime> certificate_expiration_ = absl::nullopt;
absl::optional<PublicKeySignatureAlgorithm> certificate_signature_algorithm_ = absl::nullopt;
};

/*
* Interface for classes able to fetch AWS credentials from the execution environment.
*/
class CredentialsProvider {
@@ -155,6 +218,23 @@ class CredentialsProviderChain : public CredentialSubscriberCallbacks,

using CredentialsProviderChainSharedPtr = std::shared_ptr<CredentialsProviderChain>;

/*
* X509 credential provider used for IAM Roles Anywhere
*/
class X509CredentialsProvider {
public:
virtual ~X509CredentialsProvider() = default;

/**
* Get credentials from the environment.
*
* @return AWS credentials
*/
virtual X509Credentials getCredentials() PURE;
};

using X509CredentialsProviderSharedPtr = std::shared_ptr<X509CredentialsProvider>;

} // namespace Aws
} // namespace Common
} // namespace Extensions
154 changes: 154 additions & 0 deletions source/extensions/common/aws/iam_roles_anywhere_signer_base.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
#include "source/extensions/common/aws/iam_roles_anywhere_signer_base.h"

namespace Envoy {
namespace Extensions {
namespace Common {
namespace Aws {

absl::Status IAMRolesAnywhereSignerBaseImpl::sign(Http::RequestMessage& message, bool sign_body,
const absl::string_view override_region) {
const auto content_hash = createContentHash(message, sign_body);
auto& headers = message.headers();
return sign(headers, content_hash, override_region);
}

absl::Status
IAMRolesAnywhereSignerBaseImpl::signEmptyPayload(Http::RequestHeaderMap& headers,
const absl::string_view override_region) {
headers.setReference(IAMRolesAnywhereSignatureHeaders::get().ContentSha256,
IAMRolesAnywhereSignatureConstants::HashedEmptyString);
return sign(headers, std::string(IAMRolesAnywhereSignatureConstants::HashedEmptyString),
override_region);
}

absl::Status
IAMRolesAnywhereSignerBaseImpl::signUnsignedPayload(Http::RequestHeaderMap& headers,
const absl::string_view override_region) {
headers.setReference(IAMRolesAnywhereSignatureHeaders::get().ContentSha256,
IAMRolesAnywhereSignatureConstants::UnsignedPayload);
return sign(headers, std::string(IAMRolesAnywhereSignatureConstants::UnsignedPayload),
override_region);
}

bool IAMRolesAnywhereSignerBaseImpl::addCallbackIfCredentialsPending(
CredentialsPendingCallback&& cb) {
return credentials_provider_chain_->addCallbackIfChainCredentialsPending(std::move(cb));
}

absl::Status IAMRolesAnywhereSignerBaseImpl::sign(Http::RequestHeaderMap& headers,
const std::string& content_hash,
const absl::string_view override_region) {

const auto& x509_credentials = x509_credentials_provider_->getCredentials();

ASSERT(credentials_provider_chain_ == nullptr);
ASSERT(x509_credentials_provider_ != nullptr);

if (!x509_credentials.certificateDerB64().has_value() ||
!x509_credentials.certificatePrivateKey().has_value() ||
!x509_credentials.publicKeySignatureAlgorithm().has_value()) {
return absl::Status{absl::StatusCode::kInvalidArgument,
"Unable to sign IAM Roles Anywhere payload - no x509 credentials found"};
}

if (headers.Method() == nullptr) {
return absl::Status{absl::StatusCode::kInvalidArgument, "Message is missing :method header"};
}
if (headers.Path() == nullptr) {
return absl::Status{absl::StatusCode::kInvalidArgument, "Message is missing :path header"};
}

ENVOY_LOG(debug, "Begin IAM Roles Anywhere signing");

const auto long_date = long_date_formatter_.now(time_source_);
const auto short_date = short_date_formatter_.now(time_source_);

if (!content_hash.empty()) {
headers.setReferenceKey(IAMRolesAnywhereSignatureHeaders::get().ContentSha256, content_hash);
}

addRequiredHeaders(headers, long_date);
addRequiredCertHeaders(headers, x509_credentials);

const auto canonical_headers =
Utility::canonicalizeHeaders(headers, std::vector<Matchers::StringMatcherPtr>{});

// Phase 1: Create a canonical request
const auto credential_scope = createCredentialScope(short_date, override_region);

// Handle query string parameters by appending them all to the path. Case is important for these
// query parameters.
auto query_params =
Envoy::Http::Utility::QueryParamsMulti::parseQueryString(headers.getPathValue());

auto canonical_request = Utility::createCanonicalRequest(
headers.Method()->value().getStringView(), headers.Path()->value().getStringView(),
canonical_headers,
content_hash.empty() ? IAMRolesAnywhereSignatureConstants::HashedEmptyString : content_hash,
Utility::shouldNormalizeUriPath(service_name_), Utility::useDoubleUriEncode(service_name_));
ENVOY_LOG(debug, "Canonical request:\n{}", canonical_request);

// Phase 2: Create a string to sign
std::string string_to_sign =
createStringToSign(x509_credentials, canonical_request, long_date, credential_scope);
ENVOY_LOG(debug, "String to sign:\n{}", string_to_sign);

// Phase 3: Create a signature
std::string signature = createSignature(x509_credentials, string_to_sign);
// Phase 4: Sign request

std::string authorization_header =
createAuthorizationHeader(x509_credentials, credential_scope, canonical_headers, signature);

headers.setCopy(Http::CustomHeaders::get().Authorization, authorization_header);

// Sanitize logged authorization header
std::vector<std::string> sanitised_header =
absl::StrSplit(authorization_header, absl::ByString("Signature="));
ENVOY_LOG(debug, "Header signing - Authorization header (sanitised): {}Signature=*****",
sanitised_header[0]);
return absl::OkStatus();
}

void IAMRolesAnywhereSignerBaseImpl::addRequiredCertHeaders(Http::RequestHeaderMap& headers,
X509Credentials x509_credentials) {
headers.setCopy(IAMRolesAnywhereSignatureHeaders::get().X509,
x509_credentials.certificateDerB64().value());
if (x509_credentials.certificateChainDerB64().has_value()) {
headers.setCopy(IAMRolesAnywhereSignatureHeaders::get().X509Chain,
x509_credentials.certificateChainDerB64().value());
}
}

void IAMRolesAnywhereSignerBaseImpl::addRequiredHeaders(Http::RequestHeaderMap& headers,
const std::string long_date) {
// Explicitly remove Authorization and security token header if present
headers.remove(Http::CustomHeaders::get().Authorization);

headers.setCopy(IAMRolesAnywhereSignatureHeaders::get().Date, long_date);
// addRegionHeader(headers, override_region);
}

std::string IAMRolesAnywhereSignerBaseImpl::createAuthorizationCredential(
const X509Credentials x509_credentials, absl::string_view credential_scope) const {
return fmt::format(IAMRolesAnywhereSignatureConstants::AuthorizationCredentialFormat,
x509_credentials.certificateSerial().value(), credential_scope);
}

std::string IAMRolesAnywhereSignerBaseImpl::createContentHash(Http::RequestMessage& message,
bool sign_body) const {
if (!sign_body) {
return std::string(IAMRolesAnywhereSignatureConstants::HashedEmptyString);
}
auto& crypto_util = Envoy::Common::Crypto::UtilitySingleton::get();
const auto content_hash =
message.body().length() > 0
? Hex::encode(crypto_util.getSha256Digest(message.body()))
: std::string(IAMRolesAnywhereSignatureConstants::HashedEmptyString);
return content_hash;
}

} // namespace Aws
} // namespace Common
} // namespace Extensions
} // namespace Envoy
Loading
Oops, something went wrong.