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

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

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

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

            
15
namespace {
16
// IAM Roles Anywhere credential strings
17
constexpr absl::string_view CREDENTIAL_SET = "credentialSet";
18
constexpr absl::string_view CREDENTIALS_LOWER = "credentials";
19
constexpr absl::string_view ACCESS_KEY_ID_LOWER = "accessKeyId";
20
constexpr absl::string_view SECRET_ACCESS_KEY_LOWER = "secretAccessKey";
21
constexpr absl::string_view EXPIRATION_LOWER = "expiration";
22
constexpr absl::string_view SESSION_TOKEN_LOWER = "sessionToken";
23
} // namespace
24

            
25
IAMRolesAnywhereCredentialsProvider::IAMRolesAnywhereCredentialsProvider(
26
    Server::Configuration::ServerFactoryContext& context, AwsClusterManagerPtr aws_cluster_manager,
27
    absl::string_view cluster_name, CreateMetadataFetcherCb create_metadata_fetcher_cb,
28
    absl::string_view region, MetadataFetcher::MetadataReceiver::RefreshState refresh_state,
29
    std::chrono::seconds initialization_timer,
30
    std::unique_ptr<Extensions::Common::Aws::IAMRolesAnywhereSigV4Signer> roles_anywhere_signer,
31
    const envoy::extensions::common::aws::v3::IAMRolesAnywhereCredentialProvider
32
        iam_roles_anywhere_config)
33

            
34
21
    : MetadataCredentialsProviderBase(context, aws_cluster_manager, cluster_name,
35
21
                                      create_metadata_fetcher_cb, refresh_state,
36
21
                                      initialization_timer),
37
21
      role_arn_(iam_roles_anywhere_config.role_arn()),
38
21
      role_session_name_(iam_roles_anywhere_config.role_session_name()),
39
21
      profile_arn_(iam_roles_anywhere_config.profile_arn()),
40
21
      trust_anchor_arn_(iam_roles_anywhere_config.trust_anchor_arn()), region_(region),
41
21
      session_duration_(PROTOBUF_GET_SECONDS_OR_DEFAULT(
42
21
          iam_roles_anywhere_config, session_duration,
43
21
          Extensions::Common::Aws::IAMRolesAnywhereSignatureConstants::DefaultExpiration)),
44
21
      roles_anywhere_signer_(std::move(roles_anywhere_signer)) {}
45

            
46
6
void IAMRolesAnywhereCredentialsProvider::onMetadataSuccess(const std::string&& body) {
47
6
  ENVOY_LOG(debug, "AWS IAM Roles Anywhere fetch success, calling callback func");
48
6
  on_async_fetch_cb_(std::move(body));
49
6
}
50

            
51
11
void IAMRolesAnywhereCredentialsProvider::onMetadataError(Failure reason) {
52
11
  stats_->credential_refreshes_failed_.inc();
53
11
  ENVOY_LOG(error, "AWS IAM Roles Anywhere fetch failure: {}",
54
11
            metadata_fetcher_->failureToString(reason));
55
11
  credentialsRetrievalError();
56
11
}
57

            
58
20
void IAMRolesAnywhereCredentialsProvider::refresh() {
59

            
60
20
  const auto uri = aws_cluster_manager_->getUriFromClusterName(cluster_name_);
61
20
  if (!uri.ok()) {
62
1
    ENVOY_LOG(error, "AWS Cluster Manager Unable to find cluster {}", cluster_name_);
63
1
    credentialsRetrievalError();
64
1
    return;
65
1
  }
66

            
67
19
  ENVOY_LOG(debug, "Getting AWS credentials from the rolesanywhere service at URI: {}",
68
19
            uri.value());
69

            
70
19
  Http::RequestMessageImpl message;
71
19
  message.headers().setScheme(Http::Headers::get().SchemeValues.Https);
72
19
  message.headers().setMethod(Http::Headers::get().MethodValues.Post);
73
19
  message.headers().setHost(Http::Utility::parseAuthority(uri.value()).host_);
74
19
  message.headers().setPath("/sessions");
75
19
  message.headers().setContentType("application/json");
76

            
77
19
  auto json_message = Protobuf::Struct();
78
19
  auto& fields = *json_message.mutable_fields();
79
19
  fields["profileArn"].set_string_value(profile_arn_);
80
19
  fields["roleArn"].set_string_value(role_arn_);
81
19
  fields["trustAnchorArn"].set_string_value(trust_anchor_arn_);
82

            
83
19
  if (session_duration_.has_value()) {
84
19
    fields["durationSeconds"].set_number_value(session_duration_.value());
85
19
  }
86
19
  if (!role_session_name_.empty()) {
87
16
    fields["roleSessionName"].set_string_value(role_session_name_);
88
16
  }
89

            
90
19
  auto body_data = Json::Factory::loadFromProtobufStruct(json_message);
91

            
92
19
  message.body().add(body_data->asJsonString());
93
19
  ENVOY_LOG(debug, "IAM Roles Anywhere /sessions payload: {}", body_data->asJsonString());
94

            
95
19
  const auto status = roles_anywhere_signer_->sign(message, true, region_);
96
19
  if (!status.ok()) {
97
1
    ENVOY_LOG(debug, status.message());
98
1
    credentialsRetrievalError();
99
1
    return;
100
1
  }
101

            
102
  // Using Http async client to fetch the AWS credentials.
103
18
  if (!metadata_fetcher_) {
104
16
    metadata_fetcher_ = create_metadata_fetcher_cb_(context_.clusterManager(), clusterName());
105
16
  } else {
106
2
    metadata_fetcher_->cancel(); // Cancel if there is any inflight request.
107
2
  }
108
18
  on_async_fetch_cb_ = [this](const std::string&& arg) {
109
6
    return this->extractCredentials(std::move(arg));
110
6
  };
111

            
112
  // mark credentials as pending while async completes
113
18
  credentials_pending_.store(true);
114

            
115
18
  metadata_fetcher_->fetch(message, Tracing::NullSpan::instance(), *this);
116
18
}
117

            
118
void IAMRolesAnywhereCredentialsProvider::extractCredentials(
119
6
    const std::string&& credential_document_value) {
120
6
  absl::StatusOr<Json::ObjectSharedPtr> document_json_or_error;
121

            
122
6
  document_json_or_error = Json::Factory::loadFromString(credential_document_value);
123
6
  if (!document_json_or_error.ok()) {
124
1
    ENVOY_LOG(error, "Could not parse AWS credentials document from rolesanywhere service: {}",
125
1
              document_json_or_error.status().message());
126
1
    credentialsRetrievalError();
127
1
    return;
128
1
  }
129

            
130
5
  auto credentialset_object_or_error =
131
5
      document_json_or_error.value()->getObjectArray(std::string(CREDENTIAL_SET), false);
132
5
  if (!credentialset_object_or_error.ok()) {
133
1
    ENVOY_LOG(error, "Could not parse AWS credentials document from rolesanywhere service: {}",
134
1
              credentialset_object_or_error.status().message());
135
1
    credentialsRetrievalError();
136
1
    return;
137
1
  }
138

            
139
  // We only consider the first credential returned in a CredentialSet
140
4
  auto credential_object_or_error =
141
4
      credentialset_object_or_error.value()[0]->getObject(std::string(CREDENTIALS_LOWER));
142
4
  if (!credential_object_or_error.ok()) {
143
1
    ENVOY_LOG(error, "Could not parse AWS credentials document from rolesanywhere service: {}",
144
1
              credential_object_or_error.status().message());
145
1
    credentialsRetrievalError();
146
1
    return;
147
1
  }
148

            
149
3
  const auto access_key_id = Utility::getStringFromJsonOrDefault(
150
3
      credential_object_or_error.value(), std::string(ACCESS_KEY_ID_LOWER), "");
151
3
  const auto secret_access_key = Utility::getStringFromJsonOrDefault(
152
3
      credential_object_or_error.value(), std::string(SECRET_ACCESS_KEY_LOWER), "");
153
3
  const auto session_token = Utility::getStringFromJsonOrDefault(
154
3
      credential_object_or_error.value(), std::string(SESSION_TOKEN_LOWER), "");
155

            
156
3
  ENVOY_LOG(debug,
157
3
            "Found following AWS credentials from rolesanywhere service: {}={}, {}={}, {}={}",
158
3
            ACCESS_KEY_ID_LOWER, access_key_id, SECRET_ACCESS_KEY_LOWER,
159
3
            secret_access_key.empty() ? "" : "*****", SESSION_TOKEN_LOWER,
160
3
            session_token.empty() ? "" : "*****");
161

            
162
3
  const auto expiration_str = Utility::getStringFromJsonOrDefault(
163
3
      credential_object_or_error.value(), std::string(EXPIRATION_LOWER), "");
164

            
165
3
  if (!expiration_str.empty()) {
166
3
    absl::Time expiration_time;
167
3
    if (absl::ParseTime(EXPIRATION_FORMAT, expiration_str, &expiration_time, nullptr)) {
168
3
      ENVOY_LOG(debug, "Rolesanywhere role AWS credentials expiration time: {}", expiration_str);
169
3
      expiration_time_ = absl::ToChronoTime(expiration_time);
170
3
    }
171
3
  }
172

            
173
3
  last_updated_ = context_.api().timeSource().systemTime();
174
3
  setCredentialsToAllThreads(
175
3
      std::make_unique<Credentials>(access_key_id, secret_access_key, session_token));
176
3
  stats_->credential_refreshes_succeeded_.inc();
177
3
  ENVOY_LOG(debug, "Metadata receiver {} moving to Ready state", cluster_name_);
178
3
  refresh_state_ = MetadataFetcher::MetadataReceiver::RefreshState::Ready;
179
  // Set receiver state in statistics
180
3
  stats_->metadata_refresh_state_.set(uint64_t(refresh_state_));
181
3
  handleFetchDone();
182
3
}
183

            
184
} // namespace Aws
185
} // namespace Common
186
} // namespace Extensions
187
} // namespace Envoy