| 1 | /* |
| 2 | * Copyright 2010-2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. |
| 3 | * |
| 4 | * Licensed under the Apache License, Version 2.0 (the "License"). |
| 5 | * You may not use this file except in compliance with the License. |
| 6 | * A copy of the License is located at |
| 7 | * |
| 8 | * http://aws.amazon.com/apache2.0 |
| 9 | * |
| 10 | * or in the "license" file accompanying this file. This file is distributed |
| 11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either |
| 12 | * express or implied. See the License for the specific language governing |
| 13 | * permissions and limitations under the License. |
| 14 | */ |
| 15 | |
| 16 | |
| 17 | #include <aws/core/auth/STSCredentialsProvider.h> |
| 18 | #include <aws/core/config/AWSProfileConfigLoader.h> |
| 19 | #include <aws/core/platform/Environment.h> |
| 20 | #include <aws/core/platform/FileSystem.h> |
| 21 | #include <aws/core/utils/logging/LogMacros.h> |
| 22 | #include <aws/core/utils/StringUtils.h> |
| 23 | #include <aws/core/utils/FileSystemUtils.h> |
| 24 | #include <aws/core/client/SpecifiedRetryableErrorsRetryStrategy.h> |
| 25 | #include <aws/core/utils/StringUtils.h> |
| 26 | #include <aws/core/utils/UUID.h> |
| 27 | #include <cstdlib> |
| 28 | #include <fstream> |
| 29 | #include <string.h> |
| 30 | #include <climits> |
| 31 | |
| 32 | |
| 33 | using namespace Aws::Utils; |
| 34 | using namespace Aws::Utils::Logging; |
| 35 | using namespace Aws::Auth; |
| 36 | using namespace Aws::Internal; |
| 37 | using namespace Aws::FileSystem; |
| 38 | using namespace Aws::Client; |
| 39 | using Aws::Utils::Threading::ReaderLockGuard; |
| 40 | using Aws::Utils::Threading::WriterLockGuard; |
| 41 | |
| 42 | static const char STS_ASSUME_ROLE_WEB_IDENTITY_LOG_TAG[] = "STSAssumeRoleWithWebIdentityCredentialsProvider" ; |
| 43 | STSAssumeRoleWebIdentityCredentialsProvider::STSAssumeRoleWebIdentityCredentialsProvider() : |
| 44 | m_initialized(false) |
| 45 | { |
| 46 | // check environment variables |
| 47 | Aws::String tmpRegion = Aws::Environment::GetEnv("AWS_DEFAULT_REGION" ); |
| 48 | m_roleArn = Aws::Environment::GetEnv("AWS_ROLE_ARN" ); |
| 49 | m_tokenFile = Aws::Environment::GetEnv("AWS_WEB_IDENTITY_TOKEN_FILE" ); |
| 50 | m_sessionName = Aws::Environment::GetEnv("AWS_ROLE_SESSION_NAME" ); |
| 51 | |
| 52 | // check profile_config if either m_roleArn or m_tokenFile is not loaded from environment variable |
| 53 | // region source is not enforced, but we need it to construct sts endpoint, if we can't find from environment, we should check if it's set in config file. |
| 54 | if (m_roleArn.empty() || m_tokenFile.empty() || tmpRegion.empty()) |
| 55 | { |
| 56 | auto profile = Aws::Config::GetCachedConfigProfile(Aws::Auth::GetConfigProfileName()); |
| 57 | if (tmpRegion.empty()) |
| 58 | { |
| 59 | tmpRegion = profile.GetRegion(); |
| 60 | } |
| 61 | // If either of these two were not found from environment, use whatever found for all three in config file |
| 62 | if (m_roleArn.empty() || m_tokenFile.empty()) |
| 63 | { |
| 64 | m_roleArn = profile.GetRoleArn(); |
| 65 | m_tokenFile = profile.GetValue("web_identity_token_file" ); |
| 66 | m_sessionName = profile.GetValue("role_session_name" ); |
| 67 | } |
| 68 | } |
| 69 | |
| 70 | if (m_tokenFile.empty()) |
| 71 | { |
| 72 | AWS_LOGSTREAM_WARN(STS_ASSUME_ROLE_WEB_IDENTITY_LOG_TAG, "Token file must be specified to use STS AssumeRole web identity creds provider." ); |
| 73 | return; // No need to do further constructing |
| 74 | } |
| 75 | else |
| 76 | { |
| 77 | AWS_LOGSTREAM_DEBUG(STS_ASSUME_ROLE_WEB_IDENTITY_LOG_TAG, "Resolved token_file from profile_config or environment variable to be " << m_tokenFile); |
| 78 | } |
| 79 | |
| 80 | if (m_roleArn.empty()) |
| 81 | { |
| 82 | AWS_LOGSTREAM_WARN(STS_ASSUME_ROLE_WEB_IDENTITY_LOG_TAG, "RoleArn must be specified to use STS AssumeRole web identity creds provider." ); |
| 83 | return; // No need to do further constructing |
| 84 | } |
| 85 | else |
| 86 | { |
| 87 | AWS_LOGSTREAM_DEBUG(STS_ASSUME_ROLE_WEB_IDENTITY_LOG_TAG, "Resolved role_arn from profile_config or environment variable to be " << m_roleArn); |
| 88 | } |
| 89 | |
| 90 | if (tmpRegion.empty()) |
| 91 | { |
| 92 | tmpRegion = Aws::Region::US_EAST_1; |
| 93 | } |
| 94 | else |
| 95 | { |
| 96 | AWS_LOGSTREAM_DEBUG(STS_ASSUME_ROLE_WEB_IDENTITY_LOG_TAG, "Resolved region from profile_config or environment variable to be " << tmpRegion); |
| 97 | } |
| 98 | |
| 99 | if (m_sessionName.empty()) |
| 100 | { |
| 101 | m_sessionName = Aws::Utils::UUID::RandomUUID(); |
| 102 | } |
| 103 | else |
| 104 | { |
| 105 | AWS_LOGSTREAM_DEBUG(STS_ASSUME_ROLE_WEB_IDENTITY_LOG_TAG, "Resolved session_name from profile_config or environment variable to be " << m_sessionName); |
| 106 | } |
| 107 | |
| 108 | Aws::Client::ClientConfiguration config; |
| 109 | config.scheme = Aws::Http::Scheme::HTTPS; |
| 110 | config.region = tmpRegion; |
| 111 | |
| 112 | Aws::Vector<Aws::String> retryableErrors; |
| 113 | retryableErrors.push_back("IDPCommunicationError" ); |
| 114 | retryableErrors.push_back("InvalidIdentityToken" ); |
| 115 | |
| 116 | config.retryStrategy = Aws::MakeShared<SpecifiedRetryableErrorsRetryStrategy>(STS_ASSUME_ROLE_WEB_IDENTITY_LOG_TAG, retryableErrors, 3/*maxRetries*/); |
| 117 | |
| 118 | m_client = Aws::MakeUnique<Aws::Internal::STSCredentialsClient>(STS_ASSUME_ROLE_WEB_IDENTITY_LOG_TAG, config); |
| 119 | m_initialized = true; |
| 120 | AWS_LOGSTREAM_INFO(STS_ASSUME_ROLE_WEB_IDENTITY_LOG_TAG, "Creating STS AssumeRole with web identity creds provider." ); |
| 121 | } |
| 122 | |
| 123 | AWSCredentials STSAssumeRoleWebIdentityCredentialsProvider::GetAWSCredentials() |
| 124 | { |
| 125 | // A valid client means required information like role arn and token file were constructed correctly. |
| 126 | // We can use this provider to load creds, otherwise, we can just return empty creds. |
| 127 | if (!m_initialized) |
| 128 | { |
| 129 | return Aws::Auth::AWSCredentials(); |
| 130 | } |
| 131 | RefreshIfExpired(); |
| 132 | ReaderLockGuard guard(m_reloadLock); |
| 133 | return m_credentials; |
| 134 | } |
| 135 | |
| 136 | void STSAssumeRoleWebIdentityCredentialsProvider::Reload() |
| 137 | { |
| 138 | AWS_LOGSTREAM_INFO(STS_ASSUME_ROLE_WEB_IDENTITY_LOG_TAG, "Credentials have expired, attempting to renew from STS." ); |
| 139 | |
| 140 | Aws::IFStream tokenFile(m_tokenFile.c_str()); |
| 141 | if(tokenFile) |
| 142 | { |
| 143 | Aws::String token((std::istreambuf_iterator<char>(tokenFile)), std::istreambuf_iterator<char>()); |
| 144 | m_token = token; |
| 145 | } |
| 146 | else |
| 147 | { |
| 148 | AWS_LOGSTREAM_ERROR(STS_ASSUME_ROLE_WEB_IDENTITY_LOG_TAG, "Can't open token file: " << m_tokenFile); |
| 149 | return; |
| 150 | } |
| 151 | STSCredentialsClient::STSAssumeRoleWithWebIdentityRequest request {m_sessionName, m_roleArn, m_token}; |
| 152 | |
| 153 | auto result = m_client->GetAssumeRoleWithWebIdentityCredentials(request); |
| 154 | m_credentials = result.creds; |
| 155 | } |
| 156 | |
| 157 | void STSAssumeRoleWebIdentityCredentialsProvider::RefreshIfExpired() |
| 158 | { |
| 159 | ReaderLockGuard guard(m_reloadLock); |
| 160 | if (!m_credentials.IsExpiredOrEmpty()) |
| 161 | { |
| 162 | return; |
| 163 | } |
| 164 | |
| 165 | guard.UpgradeToWriterLock(); |
| 166 | if (!m_credentials.IsExpiredOrEmpty()) // double-checked lock to avoid refreshing twice |
| 167 | { |
| 168 | return; |
| 169 | } |
| 170 | |
| 171 | Reload(); |
| 172 | } |
| 173 | |