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

            
3
#include "envoy/extensions/common/aws/v3/credential_provider.pb.h"
4

            
5
#include "source/common/common/logger.h"
6
#include "source/common/http/message_impl.h"
7
#include "source/common/http/utility.h"
8
#include "source/common/json/json_loader.h"
9
#include "source/extensions/common/aws/aws_cluster_manager.h"
10
#include "source/extensions/common/aws/credentials_provider.h"
11
#include "source/extensions/common/aws/metadata_fetcher.h"
12
#include "source/extensions/common/aws/signers/sigv4_signer_impl.h"
13
#include "source/extensions/common/aws/utility.h"
14

            
15
namespace Envoy {
16
namespace Extensions {
17
namespace Common {
18
namespace Aws {
19
using std::chrono::seconds;
20

            
21
AssumeRoleCredentialsProvider::AssumeRoleCredentialsProvider(
22
    Server::Configuration::ServerFactoryContext& context, AwsClusterManagerPtr aws_cluster_manager,
23
    absl::string_view cluster_name, CreateMetadataFetcherCb create_metadata_fetcher_cb,
24
    absl::string_view region, MetadataFetcher::MetadataReceiver::RefreshState refresh_state,
25
    std::chrono::seconds initialization_timer,
26
    std::unique_ptr<Extensions::Common::Aws::SigV4SignerImpl> assume_role_signer,
27
    envoy::extensions::common::aws::v3::AssumeRoleCredentialProvider assume_role_config)
28

            
29
26
    : MetadataCredentialsProviderBase(context, aws_cluster_manager, cluster_name,
30
26
                                      create_metadata_fetcher_cb, refresh_state,
31
26
                                      initialization_timer),
32
26
      role_arn_(assume_role_config.role_arn()),
33
26
      role_session_name_(assume_role_config.role_session_name()), region_(region),
34
26
      external_id_(assume_role_config.external_id()),
35
26
      assume_role_signer_(std::move(assume_role_signer)) {
36

            
37
26
  if (assume_role_config.has_session_duration()) {
38
1
    session_duration_ = DurationUtil::durationToSeconds(assume_role_config.session_duration());
39
1
  }
40
26
}
41

            
42
19
void AssumeRoleCredentialsProvider::onMetadataSuccess(const std::string&& body) {
43
19
  ENVOY_LOG(debug, "AWS STS AssumeRole fetch success, calling callback func");
44
19
  on_async_fetch_cb_(std::move(body));
45
19
}
46

            
47
4
void AssumeRoleCredentialsProvider::onMetadataError(Failure reason) {
48
4
  stats_->credential_refreshes_failed_.inc();
49
4
  ENVOY_LOG(error, "AWS STS AssumeRole fetch failure: {}",
50
4
            metadata_fetcher_->failureToString(reason));
51
4
  credentialsRetrievalError();
52
4
}
53

            
54
24
void AssumeRoleCredentialsProvider::refresh() {
55
  // We can have assume role credentials pending at this point, as the signers credential provider
56
  // chain is potentially async
57
24
  if (assume_role_signer_->addCallbackIfCredentialsPending(CancelWrapper::cancelWrapped(
58
24
          [this]() { continueRefresh(); }, &cancel_refresh_callback_)) == false) {
59
    // We're not pending credentials, so sign immediately
60
23
    return continueRefresh();
61
23
  } else {
62
    // Leave and let our callback handle the rest of the processing
63
1
    return;
64
1
  }
65
24
}
66

            
67
23
void AssumeRoleCredentialsProvider::continueRefresh() {
68
23
  const auto uri = aws_cluster_manager_->getUriFromClusterName(cluster_name_);
69
23
  ENVOY_LOG(debug, "Getting AWS credentials from STS at URI: {}", uri.value());
70

            
71
23
  Http::RequestMessageImpl message;
72
23
  message.headers().setScheme(Http::Headers::get().SchemeValues.Https);
73
23
  message.headers().setMethod(Http::Headers::get().MethodValues.Get);
74
23
  message.headers().setHost(Http::Utility::parseAuthority(uri.value()).host_);
75
23
  std::string path =
76
23
      fmt::format("/?Version=2011-06-15&Action=AssumeRole&RoleArn={}&RoleSessionName={}",
77
23
                  Envoy::Http::Utility::PercentEncoding::encode(role_arn_),
78
23
                  Envoy::Http::Utility::PercentEncoding::encode(role_session_name_));
79
23
  if (session_duration_) {
80
1
    path += fmt::format("&DurationSeconds={}", session_duration_.value());
81
1
  }
82

            
83
23
  if (!external_id_.empty()) {
84
1
    path +=
85
1
        fmt::format("&ExternalId={}", Envoy::Http::Utility::PercentEncoding::encode(external_id_));
86
1
  }
87

            
88
23
  message.headers().setPath(path);
89
  // Use the Accept header to ensure that AssumeRoleResponse is returned as JSON.
90
23
  message.headers().setReference(Http::CustomHeaders::get().Accept,
91
23
                                 Http::Headers::get().ContentTypeValues.Json);
92

            
93
  // No code path exists that can cause signing to fail, as signing only fails if path or method is
94
  // unset.
95
23
  auto status = assume_role_signer_->sign(message, true, region_);
96

            
97
  // Using Http async client to fetch the AWS credentials.
98
23
  if (!metadata_fetcher_) {
99
22
    metadata_fetcher_ = create_metadata_fetcher_cb_(context_.clusterManager(), clusterName());
100
22
  } else {
101
1
    metadata_fetcher_->cancel(); // Cancel if there is any inflight request.
102
1
  }
103
23
  on_async_fetch_cb_ = [this](const std::string&& arg) {
104
19
    return this->extractCredentials(std::move(arg));
105
19
  };
106

            
107
  // mark credentials as pending while async completes
108
23
  credentials_pending_.store(true);
109

            
110
23
  metadata_fetcher_->fetch(message, Tracing::NullSpan::instance(), *this);
111
23
}
112

            
113
void AssumeRoleCredentialsProvider::extractCredentials(
114
19
    const std::string&& credential_document_value) {
115

            
116
19
  absl::StatusOr<Json::ObjectSharedPtr> document_json_or_error;
117
19
  document_json_or_error = Json::Factory::loadFromString(credential_document_value);
118
19
  if (!document_json_or_error.ok()) {
119
1
    ENVOY_LOG(error, "Could not parse AWS credentials document from STS: {}",
120
1
              document_json_or_error.status().message());
121
1
    credentialsRetrievalError();
122
1
    return;
123
1
  }
124

            
125
18
  absl::StatusOr<Json::ObjectSharedPtr> root_node =
126
18
      document_json_or_error.value()->getObject(ASSUMEROLE_RESPONSE_ELEMENT);
127
18
  if (!root_node.ok()) {
128
1
    ENVOY_LOG(error, "AWS STS credentials document is empty");
129
1
    credentialsRetrievalError();
130
1
    return;
131
1
  }
132
17
  absl::StatusOr<Json::ObjectSharedPtr> result_node =
133
17
      root_node.value()->getObject(ASSUMEROLE_RESULT_ELEMENT);
134
17
  if (!result_node.ok()) {
135
4
    ENVOY_LOG(error, "AWS STS returned an unexpected result");
136
4
    credentialsRetrievalError();
137
4
    return;
138
4
  }
139
13
  absl::StatusOr<Json::ObjectSharedPtr> credentials = result_node.value()->getObject(CREDENTIALS);
140
13
  if (!credentials.ok()) {
141
1
    ENVOY_LOG(error, "AWS STS credentials document does not contain any credentials");
142
1
    credentialsRetrievalError();
143
1
    return;
144
1
  }
145

            
146
12
  const auto access_key_id =
147
12
      Utility::getStringFromJsonOrDefault(credentials.value(), ACCESS_KEY_ID, "");
148
12
  const auto secret_access_key =
149
12
      Utility::getStringFromJsonOrDefault(credentials.value(), SECRET_ACCESS_KEY, "");
150
12
  const auto session_token =
151
12
      Utility::getStringFromJsonOrDefault(credentials.value(), SESSION_TOKEN, "");
152

            
153
  // Mandatory response fields
154
12
  if (access_key_id.empty() || secret_access_key.empty() || session_token.empty()) {
155
1
    ENVOY_LOG(error, "Bad format, could not parse AWS credentials document from STS");
156
1
    credentialsRetrievalError();
157
1
    return;
158
1
  }
159

            
160
11
  ENVOY_LOG(debug, "Received the following AWS credentials from STS: {}={}, {}={}, {}={}",
161
11
            AWS_ACCESS_KEY_ID, access_key_id, AWS_SECRET_ACCESS_KEY,
162
11
            secret_access_key.empty() ? "" : "*****", AWS_SESSION_TOKEN,
163
11
            session_token.empty() ? "" : "*****");
164
11
  setCredentialsToAllThreads(
165
11
      std::make_unique<Credentials>(access_key_id, secret_access_key, session_token));
166
11
  stats_->credential_refreshes_succeeded_.inc();
167

            
168
11
  ENVOY_LOG(debug, "Metadata receiver {} moving to Ready state", cluster_name_);
169
11
  refresh_state_ = MetadataFetcher::MetadataReceiver::RefreshState::Ready;
170
  // Set receiver state in statistics
171
11
  stats_->metadata_refresh_state_.set(uint64_t(refresh_state_));
172

            
173
11
  const auto expiration = Utility::getIntegerFromJsonOrDefault(credentials.value(), EXPIRATION, 0);
174

            
175
11
  if (expiration != 0) {
176
2
    expiration_time_ =
177
2
        std::chrono::time_point<std::chrono::system_clock>(std::chrono::seconds(expiration));
178
2
    ENVOY_LOG(debug, "AWS STS credentials expiration time (unix timestamp): {}", expiration);
179
9
  } else {
180
    // We don't have a valid expiration time from the json response
181
9
    expiration_time_.reset();
182
9
  }
183

            
184
11
  last_updated_ = context_.api().timeSource().systemTime();
185
11
  handleFetchDone();
186
11
}
187

            
188
} // namespace Aws
189
} // namespace Common
190
} // namespace Extensions
191
} // namespace Envoy