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

            
3
#include "source/common/http/headers.h"
4
#include "source/common/http/message_impl.h"
5
#include "source/common/http/utility.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
ContainerCredentialsProvider::ContainerCredentialsProvider(
15
    Server::Configuration::ServerFactoryContext& context, AwsClusterManagerPtr aws_cluster_manager,
16
    CreateMetadataFetcherCb create_metadata_fetcher_cb, absl::string_view credential_uri,
17
    MetadataFetcher::MetadataReceiver::RefreshState refresh_state,
18
    std::chrono::seconds initialization_timer, absl::string_view authorization_token,
19
    absl::string_view cluster_name)
20
19
    : MetadataCredentialsProviderBase(context, aws_cluster_manager, cluster_name,
21
19
                                      create_metadata_fetcher_cb, refresh_state,
22
19
                                      initialization_timer),
23
19
      credential_uri_(credential_uri), authorization_token_(authorization_token) {}
24

            
25
18
void ContainerCredentialsProvider::refresh() {
26

            
27
18
  absl::string_view host, path;
28

            
29
18
  ENVOY_LOG(debug, "Getting AWS credentials from the container role at URI: {}",
30
18
            aws_cluster_manager_->getUriFromClusterName(cluster_name_).value());
31
18
  Http::Utility::extractHostPathFromUri(
32
18
      aws_cluster_manager_->getUriFromClusterName(cluster_name_).value(), host, path);
33

            
34
  // ECS Task role: use const authorization_token set during initialization
35
18
  absl::string_view authorization_header = authorization_token_;
36
18
  absl::StatusOr<std::string> token_or_error;
37

            
38
18
  if (authorization_token_.empty()) {
39
    // EKS Pod Identity token is sourced from AWS_CONTAINER_AUTHORIZATION_TOKEN_FILE
40
5
    if (const auto token_file = std::getenv(AWS_CONTAINER_AUTHORIZATION_TOKEN_FILE)) {
41
1
      token_or_error = context_.api().fileSystem().fileReadToEnd(std::string(token_file));
42
1
      if (token_or_error.ok()) {
43
1
        ENVOY_LOG(debug, "Container authorization token file contents loaded");
44
1
        authorization_header = token_or_error.value();
45
1
      }
46
1
    }
47
5
  }
48

            
49
18
  Http::Utility::extractHostPathFromUri(credential_uri_, host, path);
50

            
51
18
  Http::RequestMessageImpl message;
52
18
  message.headers().setScheme(Http::Headers::get().SchemeValues.Http);
53
18
  message.headers().setMethod(Http::Headers::get().MethodValues.Get);
54
18
  message.headers().setHost(host);
55
18
  message.headers().setPath(path);
56
18
  message.headers().setCopy(Http::CustomHeaders::get().Authorization, authorization_header);
57

            
58
  // Using Http async client to fetch the AWS credentials.
59
18
  if (!metadata_fetcher_) {
60
17
    metadata_fetcher_ = create_metadata_fetcher_cb_(context_.clusterManager(), clusterName());
61
17
  } else {
62
1
    metadata_fetcher_->cancel(); // Cancel if there is any inflight request.
63
1
  }
64
18
  on_async_fetch_cb_ = [this](const std::string&& arg) {
65
6
    return this->extractCredentials(std::move(arg));
66
6
  };
67

            
68
  // mark credentials as pending while async completes
69
18
  credentials_pending_.store(true);
70

            
71
18
  metadata_fetcher_->fetch(message, Tracing::NullSpan::instance(), *this);
72
18
}
73

            
74
void ContainerCredentialsProvider::extractCredentials(
75
6
    const std::string&& credential_document_value) {
76
6
  if (credential_document_value.empty()) {
77
1
    credentialsRetrievalError();
78
1
    return;
79
1
  }
80
5
  absl::StatusOr<Json::ObjectSharedPtr> document_json_or_error;
81

            
82
5
  document_json_or_error = Json::Factory::loadFromString(credential_document_value);
83
5
  if (!document_json_or_error.ok()) {
84
1
    ENVOY_LOG(error, "Could not parse AWS credentials document from the container role: {}",
85
1
              document_json_or_error.status().message());
86
1
    credentialsRetrievalError();
87
1
    return;
88
1
  }
89

            
90
4
  const auto access_key_id =
91
4
      Utility::getStringFromJsonOrDefault(document_json_or_error.value(), ACCESS_KEY_ID, "");
92
4
  const auto secret_access_key =
93
4
      Utility::getStringFromJsonOrDefault(document_json_or_error.value(), SECRET_ACCESS_KEY, "");
94
4
  const auto session_token =
95
4
      Utility::getStringFromJsonOrDefault(document_json_or_error.value(), TOKEN, "");
96

            
97
4
  ENVOY_LOG(debug, "Found following AWS credentials in the container role: {}={}, {}={}, {}={}",
98
4
            ACCESS_KEY_ID, access_key_id, SECRET_ACCESS_KEY,
99
4
            secret_access_key.empty() ? "" : "*****", TOKEN, session_token.empty() ? "" : "*****");
100

            
101
4
  const auto expiration_str =
102
4
      Utility::getStringFromJsonOrDefault(document_json_or_error.value(), CONTAINER_EXPIRATION, "");
103

            
104
4
  if (!expiration_str.empty()) {
105
2
    absl::Time expiration_time;
106
2
    if (absl::ParseTime(EXPIRATION_FORMAT, expiration_str, &expiration_time, nullptr)) {
107
2
      ENVOY_LOG(debug, "Container role AWS credentials expiration time: {}", expiration_str);
108
2
      expiration_time_ = absl::ToChronoTime(expiration_time);
109
2
    }
110
2
  }
111

            
112
4
  last_updated_ = context_.api().timeSource().systemTime();
113
4
  setCredentialsToAllThreads(
114
4
      std::make_unique<Credentials>(access_key_id, secret_access_key, session_token));
115
4
  stats_->credential_refreshes_succeeded_.inc();
116

            
117
4
  ENVOY_LOG(debug, "Metadata receiver {} moving to Ready state", cluster_name_);
118
4
  refresh_state_ = MetadataFetcher::MetadataReceiver::RefreshState::Ready;
119
  // Set receiver state in statistics
120
4
  stats_->metadata_refresh_state_.set(uint64_t(refresh_state_));
121
4
  handleFetchDone();
122
4
}
123

            
124
6
void ContainerCredentialsProvider::onMetadataSuccess(const std::string&& body) {
125
6
  ENVOY_LOG(debug, "AWS Task metadata fetch success, calling callback func");
126
6
  on_async_fetch_cb_(std::move(body));
127
6
}
128

            
129
11
void ContainerCredentialsProvider::onMetadataError(Failure reason) {
130
  // Credential retrieval failed, so set blank (anonymous) credentials
131
11
  ENVOY_LOG(error, "AWS metadata fetch failure: {}", metadata_fetcher_->failureToString(reason));
132
11
  credentialsRetrievalError();
133
11
}
134

            
135
} // namespace Aws
136
} // namespace Common
137
} // namespace Extensions
138
} // namespace Envoy