1
#include "source/extensions/common/aws/iam_roles_anywhere_signer_base_impl.h"
2

            
3
#include "envoy/http/query_params.h"
4

            
5
#include "source/common/common/hex.h"
6
#include "source/common/crypto/utility.h"
7
#include "source/common/http/headers.h"
8
#include "source/extensions/common/aws/utility.h"
9

            
10
namespace Envoy {
11
namespace Extensions {
12
namespace Common {
13
namespace Aws {
14

            
15
absl::Status IAMRolesAnywhereSignerBaseImpl::sign(Http::RequestMessage& message, bool sign_body,
16
18
                                                  const absl::string_view override_region) {
17
18
  const auto content_hash = createContentHash(message, sign_body);
18
18
  auto& headers = message.headers();
19
18
  return sign(headers, content_hash, override_region);
20
18
}
21

            
22
absl::Status
23
IAMRolesAnywhereSignerBaseImpl::signEmptyPayload(Http::RequestHeaderMap& headers,
24
2
                                                 const absl::string_view override_region) {
25
2
  headers.setReference(IAMRolesAnywhereSignatureHeaders::get().ContentSha256,
26
2
                       IAMRolesAnywhereSignatureConstants::HashedEmptyString);
27
2
  return sign(headers, std::string(IAMRolesAnywhereSignatureConstants::HashedEmptyString),
28
2
              override_region);
29
2
}
30

            
31
absl::Status
32
IAMRolesAnywhereSignerBaseImpl::signUnsignedPayload(Http::RequestHeaderMap& headers,
33
3
                                                    const absl::string_view override_region) {
34
3
  headers.setReference(IAMRolesAnywhereSignatureHeaders::get().ContentSha256,
35
3
                       IAMRolesAnywhereSignatureConstants::UnsignedPayload);
36
3
  return sign(headers, std::string(IAMRolesAnywhereSignatureConstants::UnsignedPayload),
37
3
              override_region);
38
3
}
39

            
40
bool IAMRolesAnywhereSignerBaseImpl::addCallbackIfCredentialsPending(
41
    CredentialsPendingCallback&& cb) {
42
  return credentials_provider_chain_->addCallbackIfChainCredentialsPending(std::move(cb));
43
}
44

            
45
absl::Status IAMRolesAnywhereSignerBaseImpl::sign(Http::RequestHeaderMap& headers,
46
                                                  const std::string& content_hash,
47
23
                                                  const absl::string_view override_region) {
48

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

            
51
23
  if (!x509_credentials.certificateDerB64().has_value() ||
52
23
      !x509_credentials.certificatePrivateKey().has_value() ||
53
23
      !x509_credentials.publicKeySignatureAlgorithm().has_value()) {
54
1
    return absl::Status{absl::StatusCode::kInvalidArgument,
55
1
                        "Unable to sign IAM Roles Anywhere payload - no x509 credentials found"};
56
1
  }
57

            
58
22
  if (headers.Method() == nullptr) {
59
1
    return absl::Status{absl::StatusCode::kInvalidArgument, "Message is missing :method header"};
60
1
  }
61
21
  if (headers.Path() == nullptr) {
62
1
    return absl::Status{absl::StatusCode::kInvalidArgument, "Message is missing :path header"};
63
1
  }
64

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

            
67
20
  const auto long_date = long_date_formatter_.now(time_source_);
68
20
  const auto short_date = short_date_formatter_.now(time_source_);
69

            
70
20
  if (!content_hash.empty()) {
71
20
    headers.setReferenceKey(IAMRolesAnywhereSignatureHeaders::get().ContentSha256, content_hash);
72
20
  }
73

            
74
20
  addRequiredHeaders(headers, long_date);
75
20
  addRequiredCertHeaders(headers, x509_credentials);
76

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

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

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

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

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

            
101
  // Phase 3: Create a signature
102
20
  auto signature = createSignature(x509_credentials, string_to_sign);
103
20
  if (!signature.ok()) {
104
2
    return absl::Status{absl::StatusCode::kInvalidArgument, signature.status().message()};
105
2
  }
106

            
107
  // Phase 4: Sign request
108

            
109
18
  std::string authorization_header = createAuthorizationHeader(
110
18
      x509_credentials, credential_scope, canonical_headers, signature.value());
111

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

            
114
  // Sanitize logged authorization header
115
18
  std::vector<std::string> sanitised_header =
116
18
      absl::StrSplit(authorization_header, absl::ByString("Signature="));
117
18
  ENVOY_LOG(debug, "Header signing - Authorization header (sanitised): {}Signature=*****",
118
18
            sanitised_header[0]);
119
18
  return absl::OkStatus();
120
20
}
121

            
122
void IAMRolesAnywhereSignerBaseImpl::addRequiredCertHeaders(
123
20
    Http::RequestHeaderMap& headers, const X509Credentials& x509_credentials) {
124
20
  headers.setCopy(IAMRolesAnywhereSignatureHeaders::get().X509,
125
20
                  x509_credentials.certificateDerB64().value());
126
20
  if (x509_credentials.certificateChainDerB64().has_value()) {
127
14
    headers.setCopy(IAMRolesAnywhereSignatureHeaders::get().X509Chain,
128
14
                    x509_credentials.certificateChainDerB64().value());
129
14
  }
130
20
}
131

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

            
137
20
  headers.setCopy(IAMRolesAnywhereSignatureHeaders::get().Date, long_date);
138
  // addRegionHeader(headers, override_region);
139
20
}
140

            
141
std::string IAMRolesAnywhereSignerBaseImpl::createAuthorizationCredential(
142
18
    const X509Credentials& x509_credentials, absl::string_view credential_scope) const {
143
18
  return fmt::format(IAMRolesAnywhereSignatureConstants::AuthorizationCredentialFormat,
144
18
                     x509_credentials.certificateSerial().value(), credential_scope);
145
18
}
146

            
147
std::string IAMRolesAnywhereSignerBaseImpl::createContentHash(Http::RequestMessage& message,
148
18
                                                              bool sign_body) const {
149
18
  if (!sign_body) {
150
    return std::string(IAMRolesAnywhereSignatureConstants::HashedEmptyString);
151
  }
152
18
  auto& crypto_util = Envoy::Common::Crypto::UtilitySingleton::get();
153
18
  const auto content_hash =
154
18
      message.body().length() > 0
155
18
          ? Hex::encode(crypto_util.getSha256Digest(message.body()))
156
18
          : std::string(IAMRolesAnywhereSignatureConstants::HashedEmptyString);
157
18
  return content_hash;
158
18
}
159

            
160
} // namespace Aws
161
} // namespace Common
162
} // namespace Extensions
163
} // namespace Envoy