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

            
3
#include "envoy/server/factory_context.h"
4

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

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

            
14
InstanceProfileCredentialsProvider::InstanceProfileCredentialsProvider(
15
    Server::Configuration::ServerFactoryContext& context, AwsClusterManagerPtr aws_cluster_manager,
16
    CreateMetadataFetcherCb create_metadata_fetcher_cb,
17
    MetadataFetcher::MetadataReceiver::RefreshState refresh_state,
18
    std::chrono::seconds initialization_timer, absl::string_view cluster_name)
19
79
    : MetadataCredentialsProviderBase(context, aws_cluster_manager, cluster_name,
20
79
                                      create_metadata_fetcher_cb, refresh_state,
21
79
                                      initialization_timer) {}
22

            
23
26
void InstanceProfileCredentialsProvider::refresh() {
24

            
25
26
  ENVOY_LOG(debug, "Getting AWS credentials from the EC2MetadataService");
26

            
27
  // First request for a session TOKEN so that we can call EC2MetadataService securely.
28
  // https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/configuring-instance-metadata-service.html
29
26
  Http::RequestMessageImpl token_req_message;
30
26
  token_req_message.headers().setScheme(Http::Headers::get().SchemeValues.Http);
31
26
  token_req_message.headers().setMethod(Http::Headers::get().MethodValues.Put);
32
26
  token_req_message.headers().setHost(EC2_METADATA_HOST);
33
26
  token_req_message.headers().setPath(EC2_IMDS_TOKEN_RESOURCE);
34
26
  token_req_message.headers().setCopy(Http::LowerCaseString(EC2_IMDS_TOKEN_TTL_HEADER),
35
26
                                      EC2_IMDS_TOKEN_TTL_DEFAULT_VALUE);
36

            
37
  // Using Http async client to fetch the AWS credentials where we first get the token.
38
26
  if (!metadata_fetcher_) {
39
23
    metadata_fetcher_ = create_metadata_fetcher_cb_(context_.clusterManager(), clusterName());
40
23
  } else {
41
3
    metadata_fetcher_->cancel(); // Cancel if there is any inflight request.
42
3
  }
43
26
  on_async_fetch_cb_ = [this](const std::string&& arg) {
44
25
    return this->fetchInstanceRoleAsync(std::move(arg));
45
25
  };
46
26
  continue_on_async_fetch_failure_ = true;
47
26
  continue_on_async_fetch_failure_reason_ = "Token fetch failed, falling back to IMDSv1";
48

            
49
  // mark credentials as pending while async completes
50
26
  credentials_pending_.store(true);
51

            
52
26
  metadata_fetcher_->fetch(token_req_message, Tracing::NullSpan::instance(), *this);
53
26
}
54

            
55
25
void InstanceProfileCredentialsProvider::fetchInstanceRoleAsync(const std::string&& token_string) {
56
  // Discover the Role of this instance.
57
25
  Http::RequestMessageImpl message;
58
25
  message.headers().setScheme(Http::Headers::get().SchemeValues.Http);
59
25
  message.headers().setMethod(Http::Headers::get().MethodValues.Get);
60
25
  message.headers().setHost(EC2_METADATA_HOST);
61
25
  message.headers().setPath(SECURITY_CREDENTIALS_PATH);
62
25
  if (!token_string.empty()) {
63
9
    message.headers().setCopy(Http::LowerCaseString(EC2_IMDS_TOKEN_HEADER),
64
9
                              StringUtil::trim(token_string));
65
9
  }
66

            
67
  // Using Http async client to fetch the Instance Role.
68
25
  metadata_fetcher_->cancel(); // Cancel if there is any inflight request.
69
25
  on_async_fetch_cb_ = [this, token_string = std::move(token_string)](const std::string&& arg) {
70
15
    return this->fetchCredentialFromInstanceRoleAsync(std::move(arg), std::move(token_string));
71
15
  };
72

            
73
  // mark credentials as pending while async completes
74
25
  credentials_pending_.store(true);
75

            
76
25
  metadata_fetcher_->fetch(message, Tracing::NullSpan::instance(), *this);
77
25
}
78

            
79
void InstanceProfileCredentialsProvider::fetchCredentialFromInstanceRoleAsync(
80
15
    const std::string&& instance_role, const std::string&& token_string) {
81

            
82
15
  if (instance_role.empty()) {
83
3
    ENVOY_LOG(error, "No roles found to fetch AWS credentials from the EC2MetadataService");
84
3
    credentialsRetrievalError();
85
3
    return;
86
3
  }
87
12
  const auto instance_role_list = StringUtil::splitToken(StringUtil::trim(instance_role), "\n");
88
12
  if (instance_role_list.empty()) {
89
2
    ENVOY_LOG(error, "No roles found to fetch AWS credentials from the EC2MetadataService");
90
2
    credentialsRetrievalError();
91
2
    return;
92
2
  }
93
10
  ENVOY_LOG(debug, "AWS credentials list:\n{}", instance_role);
94

            
95
  // Only one Role can be associated with an instance:
96
  // https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/iam-roles-for-amazon-ec2.html
97
10
  const auto credential_path =
98
10
      std::string(SECURITY_CREDENTIALS_PATH) + "/" +
99
10
      std::string(instance_role_list[0].data(), instance_role_list[0].size());
100
10
  ENVOY_LOG(debug, "AWS credentials path: {}", credential_path);
101

            
102
10
  Http::RequestMessageImpl message;
103
10
  message.headers().setScheme(Http::Headers::get().SchemeValues.Http);
104
10
  message.headers().setMethod(Http::Headers::get().MethodValues.Get);
105
10
  message.headers().setHost(EC2_METADATA_HOST);
106
10
  message.headers().setPath(credential_path);
107
10
  if (!token_string.empty()) {
108
5
    message.headers().setCopy(Http::LowerCaseString(EC2_IMDS_TOKEN_HEADER),
109
5
                              StringUtil::trim(token_string));
110
5
  }
111

            
112
  // Using Http async client to fetch and parse the AWS credentials.
113
10
  metadata_fetcher_->cancel(); // Cancel if there is any inflight request.
114
10
  on_async_fetch_cb_ = [this](const std::string&& arg) {
115
8
    return this->extractCredentialsAsync(std::move(arg));
116
8
  };
117

            
118
  // mark credentials as pending while async completes
119
10
  credentials_pending_.store(true);
120

            
121
10
  metadata_fetcher_->fetch(message, Tracing::NullSpan::instance(), *this);
122
10
}
123

            
124
void InstanceProfileCredentialsProvider::extractCredentialsAsync(
125
8
    const std::string&& credential_document_value) {
126
8
  if (credential_document_value.empty()) {
127
2
    ENVOY_LOG(error, "Empty AWS credentials document");
128
2
    credentialsRetrievalError();
129
2
    return;
130
2
  }
131

            
132
6
  absl::StatusOr<Json::ObjectSharedPtr> document_json_or_error;
133
6
  document_json_or_error = Json::Factory::loadFromString(credential_document_value);
134
6
  if (!document_json_or_error.ok()) {
135
2
    ENVOY_LOG(error, "Could not parse AWS credentials document: {}",
136
2
              document_json_or_error.status().message());
137
2
    credentialsRetrievalError();
138
2
    return;
139
2
  }
140

            
141
4
  const auto access_key_id =
142
4
      Utility::getStringFromJsonOrDefault(document_json_or_error.value(), ACCESS_KEY_ID, "");
143
4
  const auto secret_access_key =
144
4
      Utility::getStringFromJsonOrDefault(document_json_or_error.value(), SECRET_ACCESS_KEY, "");
145
4
  const auto session_token =
146
4
      Utility::getStringFromJsonOrDefault(document_json_or_error.value(), TOKEN, "");
147

            
148
4
  ENVOY_LOG(debug,
149
4
            "Obtained following AWS credentials from the EC2MetadataService: {}={}, {}={}, {}={}",
150
4
            AWS_ACCESS_KEY_ID, access_key_id, AWS_SECRET_ACCESS_KEY,
151
4
            secret_access_key.empty() ? "" : "*****", AWS_SESSION_TOKEN,
152
4
            session_token.empty() ? "" : "*****");
153

            
154
4
  last_updated_ = context_.api().timeSource().systemTime();
155
4
  setCredentialsToAllThreads(
156
4
      std::make_unique<Credentials>(access_key_id, secret_access_key, session_token));
157
4
  stats_->credential_refreshes_succeeded_.inc();
158
4
  ENVOY_LOG(debug, "Metadata receiver moving to Ready state");
159
4
  refresh_state_ = MetadataFetcher::MetadataReceiver::RefreshState::Ready;
160
  // Set receiver state in statistics
161
4
  stats_->metadata_refresh_state_.set(uint64_t(refresh_state_));
162
4
  handleFetchDone();
163
4
}
164

            
165
27
void InstanceProfileCredentialsProvider::onMetadataSuccess(const std::string&& body) {
166
27
  ENVOY_LOG(debug, "AWS Instance metadata fetch success, calling callback func");
167
27
  on_async_fetch_cb_(std::move(body));
168
27
}
169

            
170
29
void InstanceProfileCredentialsProvider::onMetadataError(Failure reason) {
171
  // Credential retrieval failed, so set blank (anonymous) credentials
172
29
  credentialsRetrievalError();
173
29
  if (continue_on_async_fetch_failure_) {
174
21
    ENVOY_LOG(warn, "{}. Reason: {}", continue_on_async_fetch_failure_reason_,
175
21
              metadata_fetcher_->failureToString(reason));
176
21
    continue_on_async_fetch_failure_ = false;
177
21
    continue_on_async_fetch_failure_reason_ = "";
178
21
    on_async_fetch_cb_(std::move(""));
179
21
  } else {
180
8
    ENVOY_LOG(error, "AWS Instance metadata fetch failure: {}",
181
8
              metadata_fetcher_->failureToString(reason));
182
8
    handleFetchDone();
183
8
  }
184
29
}
185

            
186
} // namespace Aws
187
} // namespace Common
188
} // namespace Extensions
189
} // namespace Envoy