Skip to content

Commit 717d639

Browse files
authored
feat: login credential provider (#3211)
1 parent 17af7a1 commit 717d639

19 files changed

+4092
-344
lines changed

CHANGELOG.md

Lines changed: 261 additions & 257 deletions
Large diffs are not rendered by default.

src/ClientResolver.php

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,8 @@ class ClientResolver
6161
__CLASS__,
6262
'_resolve_from_env_ini'
6363
];
64+
private const ANONYMOUS_SIGNATURE = 'anonymous';
65+
private const DPOP_SIGNATURE = 'dpop';
6466

6567
/** @var array Map of types to a corresponding function */
6668
private static $typeMap = [
@@ -668,7 +670,10 @@ public static function _apply_credentials($value, array &$args)
668670
$args['credentials'] = CredentialProvider::fromCredentials(
669671
new Credentials('', '')
670672
);
671-
$args['config']['signature_version'] = 'anonymous';
673+
if ($args['config']['signature_version'] !== self::DPOP_SIGNATURE) {
674+
$args['config']['signature_version'] = self::ANONYMOUS_SIGNATURE;
675+
}
676+
672677
$args['config']['configured_signature_version'] = true;
673678
} elseif ($value instanceof CacheInterface) {
674679
$args['credentials'] = CredentialProvider::defaultProvider($args);

src/Credentials/CredentialProvider.php

Lines changed: 88 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -43,15 +43,16 @@
4343
*/
4444
class CredentialProvider
4545
{
46-
const ENV_ARN = 'AWS_ROLE_ARN';
47-
const ENV_KEY = 'AWS_ACCESS_KEY_ID';
48-
const ENV_PROFILE = 'AWS_PROFILE';
49-
const ENV_ROLE_SESSION_NAME = 'AWS_ROLE_SESSION_NAME';
50-
const ENV_SECRET = 'AWS_SECRET_ACCESS_KEY';
51-
const ENV_ACCOUNT_ID = 'AWS_ACCOUNT_ID';
52-
const ENV_SESSION = 'AWS_SESSION_TOKEN';
53-
const ENV_TOKEN_FILE = 'AWS_WEB_IDENTITY_TOKEN_FILE';
54-
const ENV_SHARED_CREDENTIALS_FILE = 'AWS_SHARED_CREDENTIALS_FILE';
46+
public const ENV_ARN = 'AWS_ROLE_ARN';
47+
public const ENV_KEY = 'AWS_ACCESS_KEY_ID';
48+
public const ENV_PROFILE = 'AWS_PROFILE';
49+
public const ENV_ROLE_SESSION_NAME = 'AWS_ROLE_SESSION_NAME';
50+
public const ENV_SECRET = 'AWS_SECRET_ACCESS_KEY';
51+
public const ENV_ACCOUNT_ID = 'AWS_ACCOUNT_ID';
52+
public const ENV_SESSION = 'AWS_SESSION_TOKEN';
53+
public const ENV_TOKEN_FILE = 'AWS_WEB_IDENTITY_TOKEN_FILE';
54+
public const ENV_SHARED_CREDENTIALS_FILE = 'AWS_SHARED_CREDENTIALS_FILE';
55+
public const ENV_CONFIG_FILE = 'AWS_CONFIG_FILE';
5556
public const ENV_REGION = 'AWS_REGION';
5657
public const FALLBACK_REGION = 'us-east-1';
5758
public const REFRESH_WINDOW = 60;
@@ -82,6 +83,7 @@ public static function defaultProvider(array $config = [])
8283
$cacheable = [
8384
'web_identity',
8485
'sso',
86+
'login',
8587
'process_credentials',
8688
'process_config',
8789
'ecs',
@@ -94,24 +96,24 @@ public static function defaultProvider(array $config = [])
9496
'env' => self::env(),
9597
'web_identity' => self::assumeRoleWithWebIdentityCredentialProvider($config),
9698
];
97-
if (
98-
!isset($config['use_aws_shared_config_files'])
99+
if (!isset($config['use_aws_shared_config_files'])
99100
|| $config['use_aws_shared_config_files'] !== false
100101
) {
101102
$defaultChain['sso'] = self::sso(
102103
$profileName,
103-
self::getHomeDir() . '/.aws/config',
104+
self::getConfigFileName(),
104105
$config
105106
);
107+
$defaultChain['login'] = self::login($profileName, $config);
106108
$defaultChain['process_credentials'] = self::process();
107109
$defaultChain['ini'] = self::ini(null, null, $config);
108110
$defaultChain['process_config'] = self::process(
109111
'profile ' . $profileName,
110-
self::getHomeDir() . '/.aws/config'
112+
self::getConfigFileName()
111113
);
112114
$defaultChain['ini_config'] = self::ini(
113115
'profile '. $profileName,
114-
self::getHomeDir() . '/.aws/config'
116+
self::getConfigFileName()
115117
);
116118
}
117119

@@ -339,11 +341,12 @@ public static function instanceProfile(array $config = [])
339341
*
340342
* @return callable
341343
*/
342-
public static function sso($ssoProfileName = 'default',
343-
$filename = null,
344-
$config = []
344+
public static function sso(
345+
$ssoProfileName = 'default',
346+
$filename = null,
347+
$config = []
345348
) {
346-
$filename = $filename ?: (self::getHomeDir() . '/.aws/config');
349+
$filename = $filename ?? self::getConfigFileName();
347350

348351
return function () use ($ssoProfileName, $filename, $config) {
349352
if (!@is_readable($filename)) {
@@ -489,17 +492,13 @@ public static function assumeRoleWithWebIdentityCredentialProvider(array $config
489492
*/
490493
public static function ini($profile = null, $filename = null, array $config = [])
491494
{
492-
$filename = self::getFileName($filename);
495+
$filename = self::getCredentialsFileName($filename);
493496
$profile = $profile ?: (getenv(self::ENV_PROFILE) ?: 'default');
494497

495498
return function () use ($profile, $filename, $config) {
496-
$preferStaticCredentials = isset($config['preferStaticCredentials'])
497-
? $config['preferStaticCredentials']
498-
: false;
499-
$disableAssumeRole = isset($config['disableAssumeRole'])
500-
? $config['disableAssumeRole']
501-
: false;
502-
$stsClient = isset($config['stsClient']) ? $config['stsClient'] : null;
499+
$preferStaticCredentials = $config['preferStaticCredentials'] ?? false;
500+
$disableAssumeRole = $config['disableAssumeRole'] ?? false;
501+
$stsClient = $config['stsClient'] ?? null;
503502

504503
if (!@is_readable($filename)) {
505504
return self::reject("Cannot read credentials from $filename");
@@ -583,7 +582,7 @@ public static function ini($profile = null, $filename = null, array $config = []
583582
*/
584583
public static function process($profile = null, $filename = null)
585584
{
586-
$filename = self::getFileName($filename);
585+
$filename = self::getCredentialsFileName($filename);
587586
$profile = $profile ?: (getenv(self::ENV_PROFILE) ?: 'default');
588587

589588
return function () use ($profile, $filename) {
@@ -659,6 +658,41 @@ public static function process($profile = null, $filename = null)
659658
};
660659
}
661660

661+
/**
662+
* Login credential provider for AWS local development using console credentials
663+
*
664+
* @param string|null $profileName profile containing your console login session information
665+
* @param array $config region used for refresh requests.
666+
* pass `'region' => <your_region>` to configure a region,
667+
* otherwise, provider construction falls back to AWS_REGION,
668+
* then the profile specified for `login`
669+
*
670+
* @return callable
671+
*/
672+
public static function login(
673+
?string $profileName = null,
674+
array $config = [],
675+
): callable
676+
{
677+
$resolvedProfile = $profileName ?? getenv(self::ENV_PROFILE) ?: 'default';
678+
679+
return static function () use ($resolvedProfile, $config) {
680+
try {
681+
$provider = new LoginCredentialProvider(
682+
$resolvedProfile,
683+
$config['region'] ?? null
684+
);
685+
} catch (\Exception $e) {
686+
return self::reject(
687+
"Failed to initialize login credential provider for profile '{$resolvedProfile}': "
688+
. $e->getMessage()
689+
);
690+
}
691+
692+
return $provider();
693+
};
694+
}
695+
662696
/**
663697
* Assumes role for profile that includes role_arn
664698
*
@@ -672,13 +706,11 @@ private static function loadRoleProfile(
672706
$config = []
673707
) {
674708
$roleProfile = $profiles[$profileName];
675-
$roleArn = isset($roleProfile['role_arn']) ? $roleProfile['role_arn'] : '';
676-
$roleSessionName = isset($roleProfile['role_session_name'])
677-
? $roleProfile['role_session_name']
678-
: 'aws-sdk-php-' . round(microtime(true) * 1000);
709+
$roleArn = $roleProfile['role_arn'] ?? '';
710+
$roleSessionName = $roleProfile['role_session_name']
711+
?? 'aws-sdk-php-' . round(microtime(true) * 1000);
679712

680-
if (
681-
empty($roleProfile['source_profile'])
713+
if (empty($roleProfile['source_profile'])
682714
== empty($roleProfile['credential_source'])
683715
) {
684716
return self::reject("Either source_profile or credential_source must be set " .
@@ -748,7 +780,7 @@ private static function loadRoleProfile(
748780
*
749781
* @return null|string
750782
*/
751-
private static function getHomeDir()
783+
public static function getHomeDir()
752784
{
753785
// On Linux/Unix-like systems, use the HOME environment variable
754786
if ($homeDir = getenv('HOME')) {
@@ -765,15 +797,15 @@ private static function getHomeDir()
765797
/**
766798
* Gets profiles from specified $filename, or default ini files.
767799
*/
768-
private static function loadProfiles($filename)
800+
public static function loadProfiles($filename)
769801
{
770802
$profileData = \Aws\parse_ini_file($filename, true, INI_SCANNER_RAW);
771803

772804
// If loading .aws/credentials, also load .aws/config when AWS_SDK_LOAD_NONDEFAULT_CONFIG is set
773805
if ($filename === self::getHomeDir() . '/.aws/credentials'
774806
&& getenv('AWS_SDK_LOAD_NONDEFAULT_CONFIG')
775807
) {
776-
$configFilename = self::getHomeDir() . '/.aws/config';
808+
$configFilename = self::getConfigFileName();
777809
$configProfileData = \Aws\parse_ini_file($configFilename, true, INI_SCANNER_RAW);
778810
foreach ($configProfileData as $name => $profile) {
779811
// standardize config profile names
@@ -790,7 +822,8 @@ private static function loadProfiles($filename)
790822
/**
791823
* Gets profiles from ~/.aws/credentials and ~/.aws/config ini files
792824
*/
793-
private static function loadDefaultProfiles() {
825+
private static function loadDefaultProfiles()
826+
{
794827
$profiles = [];
795828
$credFile = self::getHomeDir() . '/.aws/credentials';
796829
$configFile = self::getHomeDir() . '/.aws/config';
@@ -861,15 +894,32 @@ private static function reject($msg)
861894
}
862895

863896
/**
897+
* Locates shared configuration file by first checking for AWS_CONFIG,
898+
* then falling back to the default location. Returns the path of the
899+
* resolved configuration file.
900+
*
901+
* @return string
902+
*/
903+
public static function getConfigFileName(): string
904+
{
905+
return getenv(self::ENV_CONFIG_FILE) ?: self::getHomeDir() . '/.aws/config';
906+
}
907+
908+
/**
909+
* Locates credentials file by first checking for AWS_SHARED_CREDENTIALS_FILE,
910+
* then falling back to the default location. Returns the path of the
911+
* resolved credentials file.
912+
*
864913
* @param $filename
865914
* @return string
866915
*/
867-
private static function getFileName($filename)
916+
public static function getCredentialsFileName($filename): string
868917
{
869918
if (!isset($filename)) {
870919
$filename = getenv(self::ENV_SHARED_CREDENTIALS_FILE) ?:
871920
(self::getHomeDir() . '/.aws/credentials');
872921
}
922+
873923
return $filename;
874924
}
875925

src/Credentials/CredentialSources.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,4 +19,5 @@ final class CredentialSources
1919
const PROFILE_SSO = 'profile_sso';
2020
const PROFILE_SSO_LEGACY = 'profile_sso_legacy';
2121
const PROFILE_PROCESS = 'profile_process';
22+
const PROFILE_LOGIN = 'profile_login';
2223
}

0 commit comments

Comments
 (0)