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

            
3
#include "source/common/common/hex.h"
4
#include "source/common/crypto/utility.h"
5
#include "source/common/http/utility.h"
6
#include "source/extensions/common/aws/utility.h"
7

            
8
namespace Envoy {
9
namespace Extensions {
10
namespace Common {
11
namespace Aws {
12

            
13
absl::Status SignerBaseImpl::sign(Http::RequestMessage& message, bool sign_body,
14
55
                                  const absl::string_view override_region) {
15

            
16
55
  const auto content_hash = createContentHash(message, sign_body);
17
55
  auto& headers = message.headers();
18
55
  return sign(headers, content_hash, override_region);
19
55
}
20

            
21
absl::Status SignerBaseImpl::signEmptyPayload(Http::RequestHeaderMap& headers,
22
5
                                              const absl::string_view override_region) {
23
5
  headers.setReference(SignatureHeaders::get().ContentSha256,
24
5
                       SignatureConstants::HashedEmptyString);
25
5
  return sign(headers, std::string(SignatureConstants::HashedEmptyString), override_region);
26
5
}
27

            
28
absl::Status SignerBaseImpl::signUnsignedPayload(Http::RequestHeaderMap& headers,
29
21
                                                 const absl::string_view override_region) {
30
21
  headers.setReference(SignatureHeaders::get().ContentSha256, SignatureConstants::UnsignedPayload);
31
21
  return sign(headers, std::string(SignatureConstants::UnsignedPayload), override_region);
32
21
}
33

            
34
37
bool SignerBaseImpl::addCallbackIfCredentialsPending(CredentialsPendingCallback&& cb) {
35
37
  return credentials_provider_chain_->addCallbackIfChainCredentialsPending(std::move(cb));
36
37
}
37

            
38
// Region support utilities for sigv4a
39
void SignerBaseImpl::addRegionHeader(
40
    ABSL_ATTRIBUTE_UNUSED Http::RequestHeaderMap& headers,
41
83
    ABSL_ATTRIBUTE_UNUSED const absl::string_view override_region) const {}
42
void SignerBaseImpl::addRegionQueryParam(
43
    ABSL_ATTRIBUTE_UNUSED Envoy::Http::Utility::QueryParamsMulti& query_params,
44
10
    ABSL_ATTRIBUTE_UNUSED const absl::string_view override_region) const {}
45

            
46
12
std::string SignerBaseImpl::getRegion() const { return region_; }
47

            
48
absl::Status SignerBaseImpl::sign(Http::RequestHeaderMap& headers, const std::string& content_hash,
49
85
                                  const absl::string_view override_region) {
50

            
51
85
  if (!query_string_ && !content_hash.empty()) {
52
68
    headers.setReferenceKey(SignatureHeaders::get().ContentSha256, content_hash);
53
68
  }
54

            
55
85
  const auto credentials = credentials_provider_chain_->chainGetCredentials();
56

            
57
85
  if (!credentials.hasCredentials()) {
58
    // Empty or "anonymous" credentials are a valid use-case for non-production environments.
59
    // This behavior matches what the AWS SDK would do.
60
4
    ENVOY_LOG(debug, "Sign exiting early - no credentials found");
61
4
    return absl::OkStatus();
62
4
  }
63

            
64
81
  if (headers.Method() == nullptr) {
65
2
    return absl::Status{absl::StatusCode::kInvalidArgument, "Message is missing :method header"};
66
2
  }
67
79
  if (headers.Path() == nullptr) {
68
2
    return absl::Status{absl::StatusCode::kInvalidArgument, "Message is missing :path header"};
69
2
  }
70

            
71
77
  const auto long_date = long_date_formatter_.now(time_source_);
72
77
  const auto short_date = short_date_formatter_.now(time_source_);
73

            
74
77
  if (!query_string_) {
75
60
    addRequiredHeaders(headers, long_date, credentials.sessionToken(), override_region);
76
60
  }
77

            
78
77
  const auto canonical_headers =
79
77
      Utility::canonicalizeHeaders(headers, excluded_header_matchers_, included_header_matchers_);
80

            
81
  // Phase 1: Create a canonical request
82
77
  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
77
  auto query_params =
87
77
      Envoy::Http::Utility::QueryParamsMulti::parseQueryString(headers.getPathValue());
88
77
  if (query_string_) {
89
17
    addRegionQueryParam(query_params, override_region);
90
17
    createQueryParams(
91
17
        query_params,
92
17
        createAuthorizationCredential(credentials.accessKeyId().value(), credential_scope),
93
17
        long_date, credentials.sessionToken(), canonical_headers, expiration_time_);
94

            
95
17
    headers.setPath(query_params.replaceQueryString(headers.Path()->value()));
96
17
  }
97

            
98
77
  auto canonical_request = Utility::createCanonicalRequest(
99
77
      headers.Method()->value().getStringView(), headers.Path()->value().getStringView(),
100
77
      canonical_headers,
101
77
      content_hash.empty() ? SignatureConstants::HashedEmptyString : content_hash,
102
77
      Utility::shouldNormalizeUriPath(service_name_), Utility::useDoubleUriEncode(service_name_));
103
77
  ENVOY_LOG(debug, "Canonical request:\n{}", canonical_request);
104

            
105
  // Phase 2: Create a string to sign
106
77
  const auto string_to_sign = createStringToSign(canonical_request, long_date, credential_scope);
107
77
  ENVOY_LOG(debug, "String to sign:\n{}", string_to_sign);
108

            
109
  // Phase 3: Create a signature
110
77
  const auto signature =
111
77
      createSignature(credentials.accessKeyId().value(), credentials.secretAccessKey().value(),
112
77
                      short_date, string_to_sign, override_region);
113
  // Phase 4: Sign request
114
77
  if (query_string_) {
115
    // Append signature to existing query string
116
17
    query_params.add(SignatureQueryParameterValues::AmzSignature, signature);
117
17
    headers.setPath(query_params.replaceQueryString(Http::HeaderString(headers.getPathValue())));
118
    // Sanitize logged query string
119
17
    query_params.overwrite(SignatureQueryParameterValues::AmzSignature, "*****");
120
17
    if (query_params.getFirstValue(SignatureQueryParameterValues::AmzSecurityToken)) {
121
6
      query_params.overwrite(SignatureQueryParameterValues::AmzSecurityToken, "*****");
122
6
    }
123
17
    auto sanitised_query_string =
124
17
        query_params.replaceQueryString(Http::HeaderString(headers.getPathValue()));
125
17
    ENVOY_LOG(debug, "Query string signing - New path (sanitised): {}", sanitised_query_string);
126

            
127
65
  } else {
128
60
    const auto authorization_header = createAuthorizationHeader(
129
60
        credentials.accessKeyId().value(), credential_scope, canonical_headers, signature);
130

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

            
133
    // Sanitize logged authorization header
134
60
    std::vector<std::string> sanitised_header =
135
60
        absl::StrSplit(authorization_header, absl::ByString("Signature="));
136
60
    ENVOY_LOG(debug, "Header signing - Authorization header (sanitised): {}Signature=*****",
137
60
              sanitised_header[0]);
138
60
  }
139
77
  return absl::OkStatus();
140
79
}
141

            
142
void SignerBaseImpl::addRequiredHeaders(Http::RequestHeaderMap& headers,
143
                                        const std::string long_date,
144
                                        const absl::optional<std::string> session_token,
145
134
                                        const absl::string_view override_region) {
146
  // Explicitly remove Authorization and security token header if present
147
134
  headers.remove(Http::CustomHeaders::get().Authorization);
148
134
  headers.remove(SignatureHeaders::get().SecurityToken);
149

            
150
134
  if (session_token.has_value() && !session_token.value().empty()) {
151
36
    headers.setCopy(SignatureHeaders::get().SecurityToken, session_token.value());
152
36
  }
153

            
154
134
  headers.setCopy(SignatureHeaders::get().Date, long_date);
155
134
  addRegionHeader(headers, override_region);
156
134
}
157

            
158
55
std::string SignerBaseImpl::createContentHash(Http::RequestMessage& message, bool sign_body) const {
159
55
  if (!sign_body) {
160
21
    return std::string(SignatureConstants::HashedEmptyString);
161
21
  }
162
34
  auto& crypto_util = Envoy::Common::Crypto::UtilitySingleton::get();
163
34
  const auto content_hash = message.body().length() > 0
164
34
                                ? Hex::encode(crypto_util.getSha256Digest(message.body()))
165
34
                                : std::string(SignatureConstants::HashedEmptyString);
166
34
  return content_hash;
167
55
}
168

            
169
std::string
170
SignerBaseImpl::createAuthorizationCredential(absl::string_view access_key_id,
171
151
                                              absl::string_view credential_scope) const {
172
151
  return fmt::format(SignatureConstants::AuthorizationCredentialFormat, access_key_id,
173
151
                     credential_scope);
174
151
}
175

            
176
void SignerBaseImpl::createQueryParams(Envoy::Http::Utility::QueryParamsMulti& query_params,
177
                                       const absl::string_view credential,
178
                                       const absl::string_view long_date,
179
                                       const absl::optional<std::string> session_token,
180
                                       const std::map<std::string, std::string>& signed_headers,
181
91
                                       const uint16_t expiration_time) const {
182
  // X-Amz-Algorithm
183
91
  query_params.add(SignatureQueryParameterValues::AmzAlgorithm, getAlgorithmString());
184
  // X-Amz-Date
185
91
  query_params.add(SignatureQueryParameterValues::AmzDate, long_date);
186
  // X-Amz-Expires
187
91
  query_params.add(SignatureQueryParameterValues::AmzExpires, std::to_string(expiration_time));
188

            
189
  // These three parameters can contain characters that require URL encoding
190
91
  if (session_token.has_value()) {
191
    // X-Amz-Security-Token
192
10
    query_params.add(SignatureQueryParameterValues::AmzSecurityToken,
193
10
                     Envoy::Http::Utility::PercentEncoding::urlEncode(session_token.value()));
194
10
  }
195
  // X-Amz-Credential
196
91
  query_params.add(SignatureQueryParameterValues::AmzCredential,
197
91
                   Utility::encodeQueryComponentPreservingPlus(credential));
198
  // X-Amz-SignedHeaders
199
91
  query_params.add(SignatureQueryParameterValues::AmzSignedHeaders,
200
91
                   Utility::encodeQueryComponentPreservingPlus(
201
91
                       Utility::joinCanonicalHeaderNames(signed_headers)));
202
91
}
203

            
204
} // namespace Aws
205
} // namespace Common
206
} // namespace Extensions
207
} // namespace Envoy