Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: account id endpoint support #2976

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changes/nextrelease/feat_account_id_endpoint_support.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
[
{
"type": "feature",
"category": "Endpoint",
"description": "Endpoint resolution based on a account id."
}
]
21 changes: 17 additions & 4 deletions src/AwsClient.php
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,16 @@ public static function getArguments()
* client-side parameter validation.
* - version: (string, required) The version of the webservice to
* utilize (e.g., 2006-03-01).
* - account_id_endpoint_mode: (string, default(preferred)) this option
* decides whether credentials should resolve an accountId value,
* which is going to be used as part of the endpoint resolution.
* The valid values for this option are:
* - preferred: when this value is set then, a warning is logged when
* accountId is empty in the resolved identity.
* - required: when this value is set then, an exception is thrown when
* accountId is empty in the resolved identity.
* - disabled: when this value is set then, the validation for if accountId
* was resolved or not, is ignored.
* - ua_append: (string, array) To pass custom user agent parameters.
* - app_id: (string) an optional application specific identifier that can be set.
* When set it will be appended to the User-Agent header of every request
Expand Down Expand Up @@ -245,7 +255,7 @@ public function __construct(array $args)
$this->region = $config['region'] ?? null;
$this->signingRegionSet = $config['sigv4a_signing_region_set'] ?? null;
$this->config = $config['config'];
$this->setClientBuiltIns($args);
$this->setClientBuiltIns($args, $config);
$this->clientContextParams = $this->setClientContextParams($args);
$this->defaultRequestOptions = $config['http'];
$this->endpointProvider = $config['endpoint_provider'];
Expand Down Expand Up @@ -578,7 +588,8 @@ private function addEndpointV2Middleware()
EndpointV2Middleware::wrap(
$this->endpointProvider,
$this->getApi(),
$endpointArgs
$endpointArgs,
$this->credentialProvider
),
'endpoint-resolution'
);
Expand Down Expand Up @@ -608,10 +619,10 @@ private function setClientContextParams($args)
/**
* Retrieves and sets default values used for endpoint resolution.
*/
private function setClientBuiltIns($args)
private function setClientBuiltIns($args, $resolvedConfig)
{
$builtIns = [];
$config = $this->getConfig();
$config = $resolvedConfig['config'];
$service = $args['service'];

$builtIns['SDK::Endpoint'] = null;
Expand All @@ -632,6 +643,8 @@ private function setClientBuiltIns($args)
$builtIns['AWS::S3::ForcePathStyle'] = $config['use_path_style_endpoint'];
$builtIns['AWS::S3::DisableMultiRegionAccessPoints'] = $config['disable_multiregion_access_points'];
}
$builtIns['AWS::Auth::AccountIdEndpointMode'] = $resolvedConfig['account_id_endpoint_mode'];

$this->clientBuiltIns += $builtIns;
}

Expand Down
33 changes: 32 additions & 1 deletion src/ClientResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,13 @@ class ClientResolver
'doc' => 'Set to false to disable checking for shared aws config files usually located in \'~/.aws/config\' and \'~/.aws/credentials\'. This will be ignored if you set the \'profile\' setting.',
'default' => true,
],
'account_id_endpoint_mode' => [
'type' => 'value',
'valid' => ['string'],
'doc' => 'Decides whether account_id must a be a required resolved credentials property. If this configuration is set to disabled, then account_id is not required. If set to preferred a warning will be logged when account_id is not resolved, and when set to required an exception will be thrown if account_id is not resolved.',
'default' => [__CLASS__, '_default_account_id_endpoint_mode'],
'fn' => [__CLASS__, '_apply_account_id_endpoint_mode']
],
'sigv4a_signing_region_set' => [
'type' => 'value',
'valid' => ['array', 'string'],
Expand Down Expand Up @@ -625,7 +632,8 @@ public static function _apply_credentials($value, array &$args)
$value['key'],
$value['secret'],
$value['token'] ?? null,
$value['expires'] ?? null
$value['expires'] ?? null,
$value['accountId'] ?? null
)
);
} elseif ($value === false) {
Expand Down Expand Up @@ -1095,6 +1103,29 @@ public static function _apply_idempotency_auto_fill(
}
}

public static function _default_account_id_endpoint_mode($args)
{
return ConfigurationResolver::resolve(
'account_id_endpoint_mode',
'preferred',
'string',
$args
);
}

public static function _apply_account_id_endpoint_mode($value, array &$args)
{
static $accountIdEndpointModes = ['disabled', 'required', 'preferred'];
if (!in_array($value, $accountIdEndpointModes)) {
throw new IAE(
"The value provided for the config account_id_endpoint_mode is invalid."
."Valid values are: " . implode(", ", $accountIdEndpointModes)
);
}

$args['account_id_endpoint_mode'] = $value;
}

public static function _default_endpoint_provider(array $args)
{
$service = $args['api'] ?? null;
Expand Down
34 changes: 28 additions & 6 deletions src/Credentials/CredentialProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ class CredentialProvider
const ENV_PROFILE = 'AWS_PROFILE';
const ENV_ROLE_SESSION_NAME = 'AWS_ROLE_SESSION_NAME';
const ENV_SECRET = 'AWS_SECRET_ACCESS_KEY';
const ENV_ACCOUNT_ID = 'AWS_ACCOUNT_ID';
const ENV_SESSION = 'AWS_SESSION_TOKEN';
const ENV_TOKEN_FILE = 'AWS_WEB_IDENTITY_TOKEN_FILE';
const ENV_SHARED_CREDENTIALS_FILE = 'AWS_SHARED_CREDENTIALS_FILE';
Expand Down Expand Up @@ -291,9 +292,18 @@ public static function env()
// Use credentials from environment variables, if available
$key = getenv(self::ENV_KEY);
$secret = getenv(self::ENV_SECRET);
$accountId = getenv(self::ENV_ACCOUNT_ID) ?: null;
$token = getenv(self::ENV_SESSION) ?: null;

if ($key && $secret) {
return Promise\Create::promiseFor(
new Credentials($key, $secret, getenv(self::ENV_SESSION) ?: NULL)
new Credentials(
$key,
$secret,
$token,
null,
$accountId
)
);
}

Expand Down Expand Up @@ -541,7 +551,9 @@ public static function ini($profile = null, $filename = null, array $config = []
new Credentials(
$data[$profile]['aws_access_key_id'],
$data[$profile]['aws_secret_access_key'],
$data[$profile]['aws_session_token']
$data[$profile]['aws_session_token'],
null,
!empty($data[$profile]['aws_account_id']) ? $data[$profile]['aws_account_id'] : null
)
);
};
Expand Down Expand Up @@ -616,12 +628,20 @@ public static function process($profile = null, $filename = null)
$processData['SessionToken'] = null;
}

$accountId = null;
if (!empty($processData['AccountId'])) {
$accountId = $processData['AccountId'];
} elseif (!empty($data[$profile]['aws_account_id'])) {
$accountId = $data[$profile]['aws_account_id'];
}

return Promise\Create::promiseFor(
new Credentials(
$processData['AccessKeyId'],
$processData['SecretAccessKey'],
$processData['SessionToken'],
$expires
$expires,
$accountId
)
);
};
Expand Down Expand Up @@ -704,8 +724,8 @@ private static function loadRoleProfile(
'RoleArn' => $roleArn,
'RoleSessionName' => $roleSessionName
]);

$credentials = $stsClient->createCredentials($result);

return Promise\Create::promiseFor($credentials);
}

Expand Down Expand Up @@ -897,7 +917,8 @@ private static function getSsoCredentials($profiles, $ssoProfileName, $filename,
$ssoCredentials['accessKeyId'],
$ssoCredentials['secretAccessKey'],
$ssoCredentials['sessionToken'],
$expiration
$expiration,
$ssoProfile['sso_account_id']
)
);
}
Expand Down Expand Up @@ -956,7 +977,8 @@ private static function getSsoCredentialsLegacy($profiles, $ssoProfileName, $fil
$ssoCredentials['accessKeyId'],
$ssoCredentials['secretAccessKey'],
$ssoCredentials['sessionToken'],
$expiration
$expiration,
$ssoProfile['sso_account_id']
)
);
}
Expand Down
16 changes: 13 additions & 3 deletions src/Credentials/Credentials.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ class Credentials extends AwsCredentialIdentity implements
private $secret;
private $token;
private $expires;
private $accountId;

/**
* Constructs a new BasicAWSCredentials object, with the specified AWS
Expand All @@ -25,12 +26,13 @@ class Credentials extends AwsCredentialIdentity implements
* @param string $token Security token to use
* @param int $expires UNIX timestamp for when credentials expire
*/
public function __construct($key, $secret, $token = null, $expires = null)
public function __construct($key, $secret, $token = null, $expires = null, $accountId = null)
{
$this->key = trim((string) $key);
$this->secret = trim((string) $secret);
$this->token = $token;
$this->expires = $expires;
$this->accountId = $accountId;
}

public static function __set_state(array $state)
Expand All @@ -39,7 +41,8 @@ public static function __set_state(array $state)
$state['key'],
$state['secret'],
$state['token'],
$state['expires']
$state['expires'],
$state['accountId']
);
}

Expand Down Expand Up @@ -68,13 +71,19 @@ public function isExpired()
return $this->expires !== null && time() >= $this->expires;
}

public function getAccountId()
{
return $this->accountId;
}

public function toArray()
{
return [
'key' => $this->key,
'secret' => $this->secret,
'token' => $this->token,
'expires' => $this->expires
'expires' => $this->expires,
'accountId' => $this->accountId
];
}

Expand All @@ -101,6 +110,7 @@ public function __unserialize($data)
$this->secret = $data['secret'];
$this->token = $data['token'];
$this->expires = $data['expires'];
$this->accountId = $data['accountId'];
}

/**
Expand Down
8 changes: 2 additions & 6 deletions src/Credentials/EcsCredentialProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -69,18 +69,13 @@ public function __construct(array $config = [])
public function __invoke()
{
$this->attempts = 0;

$uri = $this->getEcsUri();

if ($this->isCompatibleUri($uri)) {
return Promise\Coroutine::of(function () {
$client = $this->client;
$request = new Request('GET', $this->getEcsUri());

$headers = $this->getHeadersForAuthToken();

$credentials = null;

while ($credentials === null) {
$credentials = (yield $client(
$request,
Expand All @@ -95,7 +90,8 @@ public function __invoke()
$result['AccessKeyId'],
$result['SecretAccessKey'],
$result['Token'],
strtotime($result['Expiration'])
strtotime($result['Expiration']),
$result['AccountId'] ?? null
);
})->otherwise(function ($reason) {
$reason = is_array($reason) ? $reason['exception'] : $reason;
Expand Down
3 changes: 2 additions & 1 deletion src/Credentials/InstanceProfileProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,8 @@ public function __invoke($previousCredentials = null)
$result['AccessKeyId'],
$result['SecretAccessKey'],
$result['Token'],
strtotime($result['Expiration'])
strtotime($result['Expiration']),
$result['AccountId'] ?? null
);
}

Expand Down
Loading
Loading