Skip to content

Commit 23157f7

Browse files
authored
Add WebIdentity credential provider (#232)
1 parent 6606f3e commit 23157f7

13 files changed

+781
-80
lines changed

AbstractApi.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
use AsyncAws\Core\Credentials\CredentialProvider;
1111
use AsyncAws\Core\Credentials\IniFileProvider;
1212
use AsyncAws\Core\Credentials\InstanceProvider;
13+
use AsyncAws\Core\Credentials\WebIdentityProvider;
1314
use AsyncAws\Core\Exception\InvalidArgument;
1415
use AsyncAws\Core\Signer\Request;
1516
use AsyncAws\Core\Signer\Signer;
@@ -69,6 +70,7 @@ public function __construct($configuration = [], ?CredentialProvider $credential
6970
$this->configuration = $configuration;
7071
$this->credentialProvider = $credentialProvider ?? new CacheProvider(new ChainProvider([
7172
new ConfigurationProvider(),
73+
new WebIdentityProvider($this->logger),
7274
new IniFileProvider($this->logger),
7375
new InstanceProvider($this->httpClient, $this->logger),
7476
]));

AwsClientFactory.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
use AsyncAws\Core\Credentials\CredentialProvider;
1212
use AsyncAws\Core\Credentials\IniFileProvider;
1313
use AsyncAws\Core\Credentials\InstanceProvider;
14+
use AsyncAws\Core\Credentials\WebIdentityProvider;
1415
use AsyncAws\Core\Exception\InvalidArgument;
1516
use AsyncAws\Core\Exception\MissingDependency;
1617
use AsyncAws\Core\Sts\StsClient;
@@ -72,6 +73,7 @@ public function __construct($configuration = [], ?CredentialProvider $credential
7273
$this->configuration = $configuration;
7374
$this->credentialProvider = $credentialProvider ?? new CacheProvider(new ChainProvider([
7475
new ConfigurationProvider(),
76+
new WebIdentityProvider($this->logger),
7577
new IniFileProvider($this->logger),
7678
new InstanceProvider($this->httpClient, $this->logger),
7779
]));

Configuration.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@ class Configuration
2121
public const OPTION_SHARED_CREDENTIALS_FILE = 'sharedCredentialsFile';
2222
public const OPTION_SHARED_CONFIG_FILE = 'sharedConfigFile';
2323
public const OPTION_ENDPOINT = 'endpoint';
24+
public const OPTION_ROLE_ARN = 'roleArn';
25+
public const OPTION_WEB_IDENTITY_TOKEN_FILE = 'webIdentityTokenFile';
26+
public const OPTION_ROLE_SESSION_NAME = 'roleSessionName';
2427

2528
private const AVAILABLE_OPTIONS = [
2629
self::OPTION_REGION => true,
@@ -31,6 +34,9 @@ class Configuration
3134
self::OPTION_SHARED_CREDENTIALS_FILE => true,
3235
self::OPTION_SHARED_CONFIG_FILE => true,
3336
self::OPTION_ENDPOINT => true,
37+
self::OPTION_ROLE_ARN => true,
38+
self::OPTION_WEB_IDENTITY_TOKEN_FILE => true,
39+
self::OPTION_ROLE_SESSION_NAME => true,
3440
];
3541

3642
private const FALLBACK_OPTIONS = [
@@ -41,6 +47,9 @@ class Configuration
4147
self::OPTION_SESSION_TOKEN => 'AWS_SESSION_TOKEN',
4248
self::OPTION_SHARED_CREDENTIALS_FILE => 'AWS_SHARED_CREDENTIALS_FILE',
4349
self::OPTION_SHARED_CONFIG_FILE => 'AWS_CONFIG_FILE',
50+
self::OPTION_ROLE_ARN => 'AWS_ROLE_ARN',
51+
self::OPTION_WEB_IDENTITY_TOKEN_FILE => 'AWS_WEB_IDENTITY_TOKEN_FILE',
52+
self::OPTION_ROLE_SESSION_NAME => 'AWS_ROLE_SESSION_NAME',
4453
];
4554

4655
private const DEFAULT_OPTIONS = [

Credentials/IniFileLoader.php

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace AsyncAws\Core\Credentials;
6+
7+
use Psr\Log\LoggerInterface;
8+
use Psr\Log\NullLogger;
9+
10+
/**
11+
* Load and parse AWS iniFile.
12+
*
13+
* @author Jérémy Derussé <[email protected]>
14+
*/
15+
class IniFileLoader
16+
{
17+
public const KEY_REGION = 'region';
18+
public const KEY_ACCESS_KEY_ID = 'aws_access_key_id';
19+
public const KEY_SECRET_ACCESS_KEY = 'aws_secret_access_key';
20+
public const KEY_SESSION_TOKEN = 'aws_session_token';
21+
public const KEY_ROLE_ARN = 'role_arn';
22+
public const KEY_ROLE_SESSION_NAME = 'role_session_name';
23+
public const KEY_SOURCE_PROFILE = 'source_profile';
24+
public const KEY_WEB_IDENTITY_TOKEN_FILE = 'web_identity_token_file';
25+
26+
private $logger;
27+
28+
public function __construct(?LoggerInterface $logger = null)
29+
{
30+
$this->logger = $logger ?? new NullLogger();
31+
}
32+
33+
/**
34+
* @param string[] $filepaths
35+
*
36+
* @return string[][]
37+
*/
38+
public function loadProfiles(array $filepaths): array
39+
{
40+
$profilesData = [];
41+
$homeDir = null;
42+
foreach ($filepaths as $filepath) {
43+
if ('' === $filepath) {
44+
continue;
45+
}
46+
if ('~' === $filepath[0]) {
47+
$homeDir = $homeDir ?? $this->getHomeDir();
48+
$filepath = $homeDir . \substr($filepath, 1);
49+
}
50+
if (!\is_readable($filepath)) {
51+
continue;
52+
}
53+
54+
foreach ($this->parseIniFile($filepath) as $name => $profile) {
55+
$name = \preg_replace('/^profile /', '', $name);
56+
if (!isset($profilesData[$name])) {
57+
$profilesData[$name] = \array_map('trim', $profile);
58+
}
59+
}
60+
}
61+
62+
return $profilesData;
63+
}
64+
65+
private function getHomeDir(): string
66+
{
67+
// On Linux/Unix-like systems, use the HOME environment variable
68+
if (false !== $homeDir = \getenv('HOME')) {
69+
return $homeDir;
70+
}
71+
72+
// Get the HOMEDRIVE and HOMEPATH values for Windows hosts
73+
$homeDrive = \getenv('HOMEDRIVE');
74+
$homePath = \getenv('HOMEPATH');
75+
76+
return ($homeDrive && $homePath) ? $homeDrive . $homePath : '/';
77+
}
78+
79+
private function parseIniFile(string $filepath): array
80+
{
81+
if (false === $data = \parse_ini_string(
82+
\preg_replace('/^#/m', ';', \file_get_contents($filepath)),
83+
true,
84+
\INI_SCANNER_RAW
85+
)) {
86+
$this->logger->warning('The ini file {path} is invalid.', ['path' => $filepath]);
87+
88+
return [];
89+
}
90+
91+
return $data;
92+
}
93+
}

Credentials/IniFileProvider.php

Lines changed: 14 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -18,26 +18,19 @@
1818
*/
1919
class IniFileProvider implements CredentialProvider
2020
{
21-
private const KEY_ACCESS_KEY_ID = 'aws_access_key_id';
22-
private const KEY_SECRET_ACCESS_KEY = 'aws_secret_access_key';
23-
private const KEY_SESSION_TOKEN = 'aws_session_token';
24-
private const KEY_ROLE_ARN = 'role_arn';
25-
private const KEY_ROLE_SESSION_NAME = 'role_session_name';
26-
private const KEY_SOURCE_PROFILE = 'source_profile';
27-
28-
/**
29-
* @var LoggerInterface
30-
*/
21+
private $iniFileLoader;
22+
3123
private $logger;
3224

33-
public function __construct(?LoggerInterface $logger = null)
25+
public function __construct(?LoggerInterface $logger = null, ?IniFileLoader $iniFileLoader = null)
3426
{
3527
$this->logger = $logger ?? new NullLogger();
28+
$this->iniFileLoader = $iniFileLoader ?? new IniFileLoader($this->logger);
3629
}
3730

3831
public function getCredentials(Configuration $configuration): ?Credentials
3932
{
40-
$profilesData = $this->loadProfiles([
33+
$profilesData = $this->iniFileLoader->loadProfiles([
4134
$configuration->get(Configuration::OPTION_SHARED_CREDENTIALS_FILE),
4235
$configuration->get(Configuration::OPTION_SHARED_CONFIG_FILE),
4336
]);
@@ -67,15 +60,15 @@ private function getCredentialsFromProfile(array $profilesData, string $profile,
6760
}
6861

6962
$profileData = $profilesData[$profile];
70-
if (isset($profileData[self::KEY_ACCESS_KEY_ID], $profileData[self::KEY_ACCESS_KEY_ID])) {
63+
if (isset($profileData[IniFileLoader::KEY_ACCESS_KEY_ID], $profileData[IniFileLoader::KEY_ACCESS_KEY_ID])) {
7164
return new Credentials(
72-
$profileData[self::KEY_ACCESS_KEY_ID],
73-
$profileData[self::KEY_SECRET_ACCESS_KEY],
74-
$profileData[self::KEY_SESSION_TOKEN] ?? null
65+
$profileData[IniFileLoader::KEY_ACCESS_KEY_ID],
66+
$profileData[IniFileLoader::KEY_SECRET_ACCESS_KEY],
67+
$profileData[IniFileLoader::KEY_SESSION_TOKEN] ?? null
7568
);
7669
}
7770

78-
if (isset($profileData[self::KEY_ROLE_ARN])) {
71+
if (isset($profileData[IniFileLoader::KEY_ROLE_ARN])) {
7972
return $this->getCredentialsFromRole($profilesData, $profileData, $profile, $circularCollector);
8073
}
8174

@@ -86,9 +79,9 @@ private function getCredentialsFromProfile(array $profilesData, string $profile,
8679

8780
private function getCredentialsFromRole(array $profilesData, array $profileData, string $profile, array $circularCollector = []): ?Credentials
8881
{
89-
$roleArn = (string) ($profileData[self::KEY_ROLE_ARN] ?? '');
90-
$roleSessionName = (string) ($profileData[self::KEY_ROLE_SESSION_NAME] ?? \uniqid('async-aws-', true));
91-
if (null === $sourceProfileName = $profileData[self::KEY_SOURCE_PROFILE] ?? null) {
82+
$roleArn = (string) ($profileData[IniFileLoader::KEY_ROLE_ARN] ?? '');
83+
$roleSessionName = (string) ($profileData[IniFileLoader::KEY_ROLE_SESSION_NAME] ?? \uniqid('async-aws-', true));
84+
if (null === $sourceProfileName = $profileData[IniFileLoader::KEY_SOURCE_PROFILE] ?? null) {
9285
$this->logger->warning('The source profile is not defined in Role "{profile}".', ['profile' => $profile]);
9386

9487
return null;
@@ -102,8 +95,7 @@ private function getCredentialsFromRole(array $profilesData, array $profileData,
10295
return null;
10396
}
10497

105-
$stsClient = new StsClient(isset($profilesData[$sourceProfileName]['region']) ? ['region' => $profilesData[$sourceProfileName]['region']] : [], $sourceCredentials);
106-
98+
$stsClient = new StsClient(isset($profilesData[$sourceProfileName][IniFileLoader::KEY_REGION]) ? ['region' => $profilesData[$sourceProfileName][IniFileLoader::KEY_REGION]] : [], $sourceCredentials);
10799
$result = $stsClient->assumeRole([
108100
'RoleArn' => $roleArn,
109101
'RoleSessionName' => $roleSessionName,
@@ -126,60 +118,4 @@ private function getCredentialsFromRole(array $profilesData, array $profileData,
126118
$credentials->getExpiration()
127119
);
128120
}
129-
130-
private function getHomeDir(): string
131-
{
132-
// On Linux/Unix-like systems, use the HOME environment variable
133-
if (false !== $homeDir = \getenv('HOME')) {
134-
return $homeDir;
135-
}
136-
137-
// Get the HOMEDRIVE and HOMEPATH values for Windows hosts
138-
$homeDrive = \getenv('HOMEDRIVE');
139-
$homePath = \getenv('HOMEPATH');
140-
141-
return ($homeDrive && $homePath) ? $homeDrive . $homePath : '/';
142-
}
143-
144-
private function loadProfiles(array $filepaths): array
145-
{
146-
$profilesData = [];
147-
$homeDir = null;
148-
foreach ($filepaths as $filepath) {
149-
if ('' === $filepath) {
150-
continue;
151-
}
152-
if ('~' === $filepath[0]) {
153-
$homeDir = $homeDir ?? $this->getHomeDir();
154-
$filepath = $homeDir . \substr($filepath, 1);
155-
}
156-
if (!\is_readable($filepath)) {
157-
continue;
158-
}
159-
160-
foreach ($this->parseIniFile($filepath) as $name => $profile) {
161-
$name = \preg_replace('/^profile /', '', $name);
162-
if (!isset($profilesData[$name])) {
163-
$profilesData[$name] = \array_map('trim', $profile);
164-
}
165-
}
166-
}
167-
168-
return $profilesData;
169-
}
170-
171-
private function parseIniFile(string $filepath): array
172-
{
173-
if (false === $data = \parse_ini_string(
174-
\preg_replace('/^#/m', ';', \file_get_contents($filepath)),
175-
true,
176-
\INI_SCANNER_RAW
177-
)) {
178-
$this->logger->warning('The ini file {path} is invalid.', ['path' => $filepath]);
179-
180-
return [];
181-
}
182-
183-
return $data;
184-
}
185121
}

0 commit comments

Comments
 (0)