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

            
3
#include "source/common/http/message_impl.h"
4
#include "source/common/http/utility.h"
5
#include "source/common/json/json_loader.h"
6
#include "source/extensions/common/aws/utility.h"
7

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

            
13
WebIdentityCredentialsProvider::WebIdentityCredentialsProvider(
14
    Server::Configuration::ServerFactoryContext& context, AwsClusterManagerPtr aws_cluster_manager,
15
    absl::string_view cluster_name, CreateMetadataFetcherCb create_metadata_fetcher_cb,
16
    MetadataFetcher::MetadataReceiver::RefreshState refresh_state,
17
    std::chrono::seconds initialization_timer,
18
    const envoy::extensions::common::aws::v3::AssumeRoleWithWebIdentityCredentialProvider&
19
        web_identity_config)
20
52
    : MetadataCredentialsProviderBase(context, aws_cluster_manager, cluster_name,
21
52
                                      create_metadata_fetcher_cb, refresh_state,
22
52
                                      initialization_timer),
23
52
      role_arn_(web_identity_config.role_arn()),
24
52
      role_session_name_(web_identity_config.role_session_name()) {
25

            
26
52
  auto provider_or_error_ = Config::DataSource::DataSourceProvider<std::string>::create(
27
52
      web_identity_config.web_identity_token_data_source(), context.mainThreadDispatcher(),
28
52
      context.threadLocal(), context.api(), false,
29
53
      [](absl::string_view data) { return std::make_shared<std::string>(data); }, 4096);
30
52
  if (provider_or_error_.ok()) {
31
26
    web_identity_data_source_provider_ = std::move(provider_or_error_.value());
32
52
  } else {
33
26
    ENVOY_LOG(info, "Invalid web identity data source");
34
26
    web_identity_data_source_provider_.reset();
35
26
  }
36
52
}
37

            
38
48
void WebIdentityCredentialsProvider::refresh() {
39

            
40
48
  absl::string_view web_identity_data;
41

            
42
  // If we're unable to read from the configured data source, exit early.
43
48
  if (!web_identity_data_source_provider_.has_value() ||
44
48
      web_identity_data_source_provider_.value()->data() == nullptr) {
45
25
    return;
46
25
  }
47

            
48
23
  ENVOY_LOG(debug, "Getting AWS web identity credentials from STS: {}",
49
23
            aws_cluster_manager_->getUriFromClusterName(cluster_name_).value());
50
23
  web_identity_data = *web_identity_data_source_provider_.value()->data();
51

            
52
23
  Http::RequestMessageImpl message;
53
23
  message.headers().setScheme(Http::Headers::get().SchemeValues.Https);
54
23
  message.headers().setMethod(Http::Headers::get().MethodValues.Get);
55
23
  auto statusOr = aws_cluster_manager_->getUriFromClusterName(cluster_name_);
56
23
  message.headers().setHost(Http::Utility::parseAuthority(statusOr.value()).host_);
57
23
  message.headers().setPath(
58
23
      fmt::format("/?Action=AssumeRoleWithWebIdentity"
59
23
                  "&Version=2011-06-15"
60
23
                  "&RoleSessionName={}"
61
23
                  "&RoleArn={}"
62
23
                  "&WebIdentityToken={}",
63
23
                  Envoy::Http::Utility::PercentEncoding::encode(role_session_name_),
64
23
                  Envoy::Http::Utility::PercentEncoding::encode(role_arn_),
65
23
                  Envoy::Http::Utility::PercentEncoding::encode(web_identity_data)));
66
  // Use the Accept header to ensure that AssumeRoleWithWebIdentityResponse is returned as JSON.
67
23
  message.headers().setReference(Http::CustomHeaders::get().Accept,
68
23
                                 Http::Headers::get().ContentTypeValues.Json);
69

            
70
  // Using Http async client to fetch the AWS credentials.
71
23
  if (!metadata_fetcher_) {
72
21
    metadata_fetcher_ = create_metadata_fetcher_cb_(context_.clusterManager(), clusterName());
73
21
  } else {
74
2
    metadata_fetcher_->cancel(); // Cancel if there is any inflight request.
75
2
  }
76

            
77
23
  on_async_fetch_cb_ = [this](const std::string&& arg) {
78
17
    return this->extractCredentials(std::move(arg));
79
17
  };
80

            
81
  // mark credentials as pending while async completes
82
23
  credentials_pending_.store(true);
83

            
84
23
  metadata_fetcher_->fetch(message, Tracing::NullSpan::instance(), *this);
85
23
}
86

            
87
void WebIdentityCredentialsProvider::extractCredentials(
88
17
    const std::string&& credential_document_value) {
89

            
90
17
  absl::StatusOr<Json::ObjectSharedPtr> document_json_or_error;
91
17
  document_json_or_error = Json::Factory::loadFromString(credential_document_value);
92
17
  if (!document_json_or_error.ok()) {
93
1
    ENVOY_LOG(error, "Could not parse AWS credentials document from STS: {}",
94
1
              document_json_or_error.status().message());
95
1
    credentialsRetrievalError();
96
1
    return;
97
1
  }
98

            
99
16
  absl::StatusOr<Json::ObjectSharedPtr> root_node =
100
16
      document_json_or_error.value()->getObject(WEB_IDENTITY_RESPONSE_ELEMENT);
101
16
  if (!root_node.ok()) {
102
1
    ENVOY_LOG(error, "AWS STS credentials document is empty");
103
1
    credentialsRetrievalError();
104
1
    return;
105
1
  }
106
15
  absl::StatusOr<Json::ObjectSharedPtr> result_node =
107
15
      root_node.value()->getObject(WEB_IDENTITY_RESULT_ELEMENT);
108
15
  if (!result_node.ok()) {
109
3
    ENVOY_LOG(error, "AWS STS returned an unexpected result");
110
3
    credentialsRetrievalError();
111
3
    return;
112
3
  }
113
12
  absl::StatusOr<Json::ObjectSharedPtr> credentials = result_node.value()->getObject(CREDENTIALS);
114
12
  if (!credentials.ok()) {
115
1
    ENVOY_LOG(error, "AWS STS credentials document does not contain any credentials");
116
1
    credentialsRetrievalError();
117
1
    return;
118
1
  }
119

            
120
11
  const auto access_key_id =
121
11
      Utility::getStringFromJsonOrDefault(credentials.value(), ACCESS_KEY_ID, "");
122
11
  const auto secret_access_key =
123
11
      Utility::getStringFromJsonOrDefault(credentials.value(), SECRET_ACCESS_KEY, "");
124
11
  const auto session_token =
125
11
      Utility::getStringFromJsonOrDefault(credentials.value(), SESSION_TOKEN, "");
126

            
127
  // Mandatory response fields
128
11
  if (access_key_id.empty() || secret_access_key.empty() || session_token.empty()) {
129
1
    ENVOY_LOG(error, "Bad format, could not parse AWS credentials document from STS");
130
1
    credentialsRetrievalError();
131
1
    return;
132
1
  }
133

            
134
10
  ENVOY_LOG(debug, "Received the following AWS credentials from STS: {}={}, {}={}, {}={}",
135
10
            AWS_ACCESS_KEY_ID, access_key_id, AWS_SECRET_ACCESS_KEY,
136
10
            secret_access_key.empty() ? "" : "*****", AWS_SESSION_TOKEN,
137
10
            session_token.empty() ? "" : "*****");
138
10
  setCredentialsToAllThreads(
139
10
      std::make_unique<Credentials>(access_key_id, secret_access_key, session_token));
140
10
  stats_->credential_refreshes_succeeded_.inc();
141

            
142
10
  ENVOY_LOG(debug, "Metadata receiver {} moving to Ready state", cluster_name_);
143
10
  refresh_state_ = MetadataFetcher::MetadataReceiver::RefreshState::Ready;
144
  // Set receiver state in statistics
145
10
  stats_->metadata_refresh_state_.set(uint64_t(refresh_state_));
146

            
147
10
  const auto expiration = Utility::getIntegerFromJsonOrDefault(credentials.value(), EXPIRATION, 0);
148

            
149
10
  if (expiration != 0) {
150
8
    expiration_time_ =
151
8
        std::chrono::time_point<std::chrono::system_clock>(std::chrono::seconds(expiration));
152
8
    ENVOY_LOG(debug, "AWS STS credentials expiration time (unix timestamp): {}", expiration);
153
8
  } else {
154
    // We don't have a valid expiration time from the json response
155
2
    expiration_time_.reset();
156
2
  }
157

            
158
10
  last_updated_ = context_.api().timeSource().systemTime();
159
10
  handleFetchDone();
160
10
}
161

            
162
17
void WebIdentityCredentialsProvider::onMetadataSuccess(const std::string&& body) {
163
17
  ENVOY_LOG(debug, "AWS metadata fetch from STS success, calling callback func");
164
17
  on_async_fetch_cb_(std::move(body));
165
17
}
166

            
167
4
void WebIdentityCredentialsProvider::onMetadataError(Failure reason) {
168
4
  ENVOY_LOG(error, "AWS metadata fetch failure: {}", metadata_fetcher_->failureToString(reason));
169
4
  credentialsRetrievalError();
170
4
}
171

            
172
} // namespace Aws
173
} // namespace Common
174
} // namespace Extensions
175
} // namespace Envoy