Skip to content

Commit

Permalink
adds support for sso credentials
Browse files Browse the repository at this point in the history
  • Loading branch information
smoench committed Jul 27, 2023
1 parent f6e6c13 commit 8972136
Show file tree
Hide file tree
Showing 39 changed files with 1,049 additions and 0 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ See full documentation on [https://async-aws.com](https://async-aws.com).
| [async-aws/sns](https://github.com/async-aws/sns) | [![Latest Stable Version](https://poser.pugx.org/async-aws/sns/v/stable)](https://packagist.org/packages/async-aws/sns) [![Total Downloads](https://poser.pugx.org/async-aws/sns/downloads)](https://packagist.org/packages/async-aws/sns) | [![](https://github.com/async-aws/sns/workflows/BC%20Check/badge.svg?branch=master)](https://github.com/async-aws/sns/actions) | [![](https://async-aws-pr.github.io/commits-since-release-counter/sns.svg)](https://github.com/async-aws/sns/releases) |
| [async-aws/sqs](https://github.com/async-aws/sqs) | [![Latest Stable Version](https://poser.pugx.org/async-aws/sqs/v/stable)](https://packagist.org/packages/async-aws/sqs) [![Total Downloads](https://poser.pugx.org/async-aws/sqs/downloads)](https://packagist.org/packages/async-aws/sqs) | [![](https://github.com/async-aws/sqs/workflows/BC%20Check/badge.svg?branch=master)](https://github.com/async-aws/sqs/actions) | [![](https://async-aws-pr.github.io/commits-since-release-counter/sqs.svg)](https://github.com/async-aws/sqs/releases) |
| [async-aws/ssm](https://github.com/async-aws/ssm) | [![Latest Stable Version](https://poser.pugx.org/async-aws/ssm/v/stable)](https://packagist.org/packages/async-aws/ssm) [![Total Downloads](https://poser.pugx.org/async-aws/ssm/downloads)](https://packagist.org/packages/async-aws/ssm) | [![](https://github.com/async-aws/ssm/workflows/BC%20Check/badge.svg?branch=master)](https://github.com/async-aws/ssm/actions) | [![](https://async-aws-pr.github.io/commits-since-release-counter/ssm.svg)](https://github.com/async-aws/ssm/releases) |
| [async-aws/sso](https://github.com/async-aws/sso) | [![Latest Stable Version](https://poser.pugx.org/async-aws/sso/v/stable)](https://packagist.org/packages/async-aws/sso) [![Total Downloads](https://poser.pugx.org/async-aws/sso/downloads)](https://packagist.org/packages/async-aws/sso) | [![](https://github.com/async-aws/sso/workflows/BC%20Check/badge.svg?branch=master)](https://github.com/async-aws/sso/actions) | [![](https://async-aws-pr.github.io/commits-since-release-counter/sso.svg)](https://github.com/async-aws/sso/releases) |
| [async-aws/step-functions](https://github.com/async-aws/step-functions) | [![Latest Stable Version](https://poser.pugx.org/async-aws/step-functions/v/stable)](https://packagist.org/packages/async-aws/step-functions) [![Total Downloads](https://poser.pugx.org/async-aws/step-functions/downloads)](https://packagist.org/packages/async-aws/step-functions) | [![](https://github.com/async-aws/step-functions/workflows/BC%20Check/badge.svg?branch=master)](https://github.com/async-aws/step-functions/actions) | [![](https://async-aws-pr.github.io/commits-since-release-counter/step-functions.svg)](https://github.com/async-aws/step-functions/releases) |
| [async-aws/timestream-query](https://github.com/async-aws/timestream-query) | [![Latest Stable Version](https://poser.pugx.org/async-aws/timestream-query/v/stable)](https://packagist.org/packages/async-aws/timestream-query) [![Total Downloads](https://poser.pugx.org/async-aws/timestream-query/downloads)](https://packagist.org/packages/async-aws/timestream-query) | [![](https://github.com/async-aws/timestream-query/workflows/BC%20Check/badge.svg?branch=master)](https://github.com/async-aws/timestream-query/actions) | [![](https://async-aws-pr.github.io/commits-since-release-counter/timestream-query.svg)](https://github.com/async-aws/timestream-query/releases) |
| [async-aws/timestream-write](https://github.com/async-aws/timestream-write) | [![Latest Stable Version](https://poser.pugx.org/async-aws/timestream-write/v/stable)](https://packagist.org/packages/async-aws/timestream-write) [![Total Downloads](https://poser.pugx.org/async-aws/timestream-write/downloads)](https://packagist.org/packages/async-aws/timestream-write) | [![](https://github.com/async-aws/timestream-write/workflows/BC%20Check/badge.svg?branch=master)](https://github.com/async-aws/timestream-write/actions) | [![](https://async-aws-pr.github.io/commits-since-release-counter/timestream-write.svg)](https://github.com/async-aws/timestream-write/releases) |
Expand Down
2 changes: 2 additions & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@
"AsyncAws\\Sns\\": "src/Service/Sns/src",
"AsyncAws\\Sqs\\": "src/Service/Sqs/src",
"AsyncAws\\Ssm\\": "src/Service/Ssm/src",
"AsyncAws\\Sso\\": "src/Service/Sso/src",
"AsyncAws\\StepFunctions\\": "src/Service/StepFunctions/src",
"AsyncAws\\Symfony\\Bundle\\": "src/Integration/Symfony/Bundle/src",
"AsyncAws\\TimestreamQuery\\": "src/Service/TimestreamQuery/src",
Expand Down Expand Up @@ -147,6 +148,7 @@
"AsyncAws\\Sns\\Tests\\": "src/Service/Sns/tests",
"AsyncAws\\Sqs\\Tests\\": "src/Service/Sqs/tests",
"AsyncAws\\Ssm\\Tests\\": "src/Service/Ssm/tests",
"AsyncAws\\Sso\\Tests\\": "src/Service/Sso/tests",
"AsyncAws\\StepFunctions\\Tests\\": "src/Service/StepFunctions/tests",
"AsyncAws\\Symfony\\Bundle\\Tests\\": "src/Integration/Symfony/Bundle/tests",
"AsyncAws\\Test\\": "tests",
Expand Down
3 changes: 3 additions & 0 deletions couscous.yml
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,9 @@ menu:
ssm:
text: SSM
url: /clients/ssm.html
sso:
text: SSO
url: /clients/sso.html
sts:
text: STS
url: /clients/sts.html
Expand Down
1 change: 1 addition & 0 deletions docs/clients/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,7 @@ for more information.
| [SNS](./sns.md) | [async-aws/sns](https://packagist.org/packages/async-aws/sns) |
| [SQS](./sqs.md) | [async-aws/sqs](https://packagist.org/packages/async-aws/sqs) |
| [SSM](./ssm.md) | [async-aws/ssm](https://packagist.org/packages/async-aws/ssm) |
| [SSO](./sso.md) | [async-aws/sso](https://packagist.org/packages/async-aws/sso) |
| [STS](./sts.md) | [async-aws/core](https://packagist.org/packages/async-aws/core) |
| [StepFunctions](./step-functions.md) | [async-aws/step-functions](https://packagist.org/packages/async-aws/step-functions) |
| [TimestreamQuery](./timestream-query.md) | [async-aws/timestream-query](https://packagist.org/packages/async-aws/timestream-query) |
Expand Down
29 changes: 29 additions & 0 deletions docs/clients/sso.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
---
layout: client
category: clients
name: SSO
package: async-aws/sso
fqcn: AsyncAws\Sso\SsoClient
---

## Usage

### Retrieve role credentials

```php
use AsyncAws\Sso\Input\GetRoleCredentialsRequest;
use AsyncAws\Sso\SsoClient;

$client = new SsoClient();

$result = $client->getRoleCredentials(new GetRoleCredentialsRequest([
'roleName' => 'YourRoleName',
'accountId' => 'YourAccountId',
'accessToken' => 'YourAccessToken',
]));

echo 'AccessKeyId:' . $result->getRoleCredentials()->getAccessKeyId().PHP_EOL;
echo 'Expiration:' . $result->getRoleCredentials()->getExpiration().PHP_EOL;
echo 'SecretAccessKey:' . $result->getRoleCredentials()->getSecretAccessKey().PHP_EOL;
echo 'SessionToken:' . $result->getRoleCredentials()->getSessionToken();
```
9 changes: 9 additions & 0 deletions manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -713,6 +713,15 @@
"PutParameter"
]
},
"Sso": {
"source": "https://raw.githubusercontent.com/aws/aws-sdk-php/${LATEST}/src/data/sso/2019-06-10/api-2.json",
"documentation": "https://raw.githubusercontent.com/aws/aws-sdk-php/${LATEST}/src/data/sso/2019-06-10/docs-2.json",
"pagination": "https://raw.githubusercontent.com/aws/aws-sdk-php/${LATEST}/src/data/sso/2019-06-10/paginators-1.json",
"api-reference": "https://docs.aws.amazon.com/singlesignon/latest/PortalAPIReference",
"methods": [
"GetRoleCredentials"
]
},
"Sts": {
"source": "https://raw.githubusercontent.com/aws/aws-sdk-php/${LATEST}/src/data/sts/2011-06-15/api-2.json",
"documentation": "https://raw.githubusercontent.com/aws/aws-sdk-php/${LATEST}/src/data/sts/2011-06-15/docs-2.json",
Expand Down
1 change: 1 addition & 0 deletions src/Core/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
- Support for LocationService
- Support for hostPrefix in requests
- AWS api-change: API updates for the AWS Security Token Service
- Support for SSO credentials

## 1.19.0

Expand Down
10 changes: 10 additions & 0 deletions src/Core/src/AwsClientFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
use AsyncAws\Sns\SnsClient;
use AsyncAws\Sqs\SqsClient;
use AsyncAws\Ssm\SsmClient;
use AsyncAws\Sso\SsoClient;
use AsyncAws\StepFunctions\StepFunctionsClient;
use AsyncAws\TimestreamQuery\TimestreamQueryClient;
use AsyncAws\TimestreamWrite\TimestreamWriteClient;
Expand Down Expand Up @@ -519,6 +520,15 @@ public function ssm(): SsmClient
return $this->serviceCache[__METHOD__];
}

public function sso(): SsoClient
{
if (!isset($this->serviceCache[__METHOD__])) {
$this->serviceCache[__METHOD__] = new SsoClient($this->configuration, $this->credentialProvider, $this->httpClient, $this->logger);
}

return $this->serviceCache[__METHOD__];
}

public function sts(): StsClient
{
if (!isset($this->serviceCache[__METHOD__])) {
Expand Down
4 changes: 4 additions & 0 deletions src/Core/src/Credentials/IniFileLoader.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ final class IniFileLoader
public const KEY_ROLE_SESSION_NAME = 'role_session_name';
public const KEY_SOURCE_PROFILE = 'source_profile';
public const KEY_WEB_IDENTITY_TOKEN_FILE = 'web_identity_token_file';
public const KEY_SSO_START_URL = 'sso_start_url';
public const KEY_SSO_REGION = 'sso_region';
public const KEY_SSO_ACCOUNT_ID = 'sso_account_id';
public const KEY_SSO_ROLE_NAME = 'sso_role_name';

/**
* @var LoggerInterface
Expand Down
75 changes: 75 additions & 0 deletions src/Core/src/Credentials/IniFileProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use AsyncAws\Core\Configuration;
use AsyncAws\Core\Exception\RuntimeException;
use AsyncAws\Core\Sts\StsClient;
use AsyncAws\Sso\SsoClient;
use Psr\Log\LoggerInterface;
use Psr\Log\NullLogger;
use Symfony\Contracts\HttpClient\HttpClientInterface;
Expand Down Expand Up @@ -92,6 +93,16 @@ private function getCredentialsFromProfile(array $profilesData, string $profile,
return $this->getCredentialsFromRole($profilesData, $profileData, $profile, $circularCollector);
}

if (isset($profileData[IniFileLoader::KEY_SSO_START_URL])) {
if (class_exists(SsoClient::class)) {
return $this->getCredentialsFromLegacySso($profileData, $profile);
}

$this->logger->warning('The profile "{profile}" contains SSO (legacy) config but the "async-aws/sso" package is not installed. Try running "composer require async-aws/sso".', ['profile' => $profile]);

return null;
}

$this->logger->info('No credentials found for profile "{profile}".', ['profile' => $profile]);

return null;
Expand Down Expand Up @@ -146,4 +157,68 @@ private function getCredentialsFromRole(array $profilesData, array $profileData,
Credentials::adjustExpireDate($credentials->getExpiration(), $this->getDateFromResult($result))
);
}

/**
* @param array<string, string> $profileData
*/
private function getCredentialsFromLegacySso(array $profileData, string $profile): ?Credentials
{
if (!isset(
$profileData[IniFileLoader::KEY_SSO_START_URL],
$profileData[IniFileLoader::KEY_SSO_REGION],
$profileData[IniFileLoader::KEY_SSO_ACCOUNT_ID],
$profileData[IniFileLoader::KEY_SSO_ROLE_NAME]
)) {
$this->logger->warning('Profile "{profile}" does not contains require legacy SSO config.', ['profile' => $profile]);

return null;
}

$ssoCacheFileLoader = new SsoCacheFileLoader($this->logger);
$tokenData = $ssoCacheFileLoader->loadSsoCacheFile($profileData[IniFileLoader::KEY_SSO_START_URL]);

if ([] === $tokenData) {
return null;
}

$ssoClient = new SsoClient(
['region' => $profileData[IniFileLoader::KEY_SSO_REGION]],
new NullProvider(), // no credentials required as we provide an access token via the role credentials request
$this->httpClient
);
$result = $ssoClient->getRoleCredentials([
'accessToken' => $tokenData[SsoCacheFileLoader::KEY_ACCESS_TOKEN],
'accountId' => $profileData[IniFileLoader::KEY_SSO_ACCOUNT_ID],
'roleName' => $profileData[IniFileLoader::KEY_SSO_ROLE_NAME],
]);

try {
if (null === $credentials = $result->getRoleCredentials()) {
throw new RuntimeException('The RoleCredentials response does not contains credentials');
}
if (null === $accessKeyId = $credentials->getAccessKeyId()) {
throw new RuntimeException('The RoleCredentials response does not contain an accessKeyId');
}
if (null === $secretAccessKey = $credentials->getSecretAccessKey()) {
throw new RuntimeException('The RoleCredentials response does not contain a secretAccessKey');
}
if (null === $sessionToken = $credentials->getSessionToken()) {
throw new RuntimeException('The RoleCredentials response does not contain a sessionToken');
}
if (null === $expiration = $credentials->getExpiration()) {
throw new RuntimeException('The RoleCredentials response does not contain an expiration');
}
} catch (\Exception $e) {
$this->logger->warning('Failed to get credentials from role credentials in profile "{profile}: {exception}".', ['profile' => $profile, 'exception' => $e]);

return null;
}

return new Credentials(
$accessKeyId,
$secretAccessKey,
$sessionToken,
(new \DateTimeImmutable())->setTimestamp($expiration)
);
}
}
79 changes: 79 additions & 0 deletions src/Core/src/Credentials/SsoCacheFileLoader.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
<?php

declare(strict_types=1);

namespace AsyncAws\Core\Credentials;

use AsyncAws\Core\EnvVar;
use Psr\Log\LoggerInterface;
use Psr\Log\NullLogger;

/**
* Load and parse AWS SSO cache file.
*/
final class SsoCacheFileLoader
{
public const KEY_ACCESS_TOKEN = 'accessToken';
public const KEY_EXPIRES_AT = 'expiresAt';

/**
* @var LoggerInterface
*/
private $logger;

public function __construct(?LoggerInterface $logger = null)
{
$this->logger = $logger ?? new NullLogger();
}

/**
* @return array<string, string>
*/
public function loadSsoCacheFile(string $ssoStartUrl): array
{
$filepath = sprintf('%s/.aws/sso/cache/%s.json', $this->getHomeDir(), sha1($ssoStartUrl));

if (!@is_readable($filepath)) {
$this->logger->warning('The sso cache file {path} is not readable.', ['path' => $filepath]);

return [];
}

$tokenData = json_decode(file_get_contents($filepath), true);
if (!isset($tokenData[self::KEY_ACCESS_TOKEN], $tokenData[self::KEY_EXPIRES_AT])) {
$this->logger->warning('Token file at {path} must contain an accessToken and an expiresAt.', ['path' => $filepath]);

return [];
}

try {
$expiration = (new \DateTimeImmutable($tokenData[self::KEY_EXPIRES_AT]));
} catch (\Exception $e) {
$this->logger->warning('Cached SSO credentials returned an invalid expiresAt value.');

return [];
}

if ($expiration < new \DateTimeImmutable()) {
$this->logger->warning('Cached SSO credentials returned an invalid expiresAt value.');

return [];
}

return $tokenData;
}

private function getHomeDir(): string
{
// On Linux/Unix-like systems, use the HOME environment variable
if (null !== $homeDir = EnvVar::get('HOME')) {
return $homeDir;
}

// Get the HOMEDRIVE and HOMEPATH values for Windows hosts
$homeDrive = EnvVar::get('HOMEDRIVE');
$homePath = EnvVar::get('HOMEPATH');

return ($homeDrive && $homePath) ? $homeDrive . $homePath : '/';
}
}
1 change: 1 addition & 0 deletions src/Integration/Symfony/Bundle/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
### Added

- Support for LocationService
- Support for SSO

## 1.11.0

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,10 @@ public static function getAllServices(): array
'class' => \AsyncAws\Ssm\SsmClient::class,
'package' => 'async-aws/ssm',
],
'sso' => [
'class' => \AsyncAws\Sso\SsoClient::class,
'package' => 'async-aws/sso',
],
'sts' => [
'class' => \AsyncAws\Core\Sts\StsClient::class,
'package' => 'async-aws/core',
Expand Down
5 changes: 5 additions & 0 deletions src/Service/Sso/.gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
/.github export-ignore
/tests export-ignore
/.gitignore export-ignore
/Makefile export-ignore
/phpunit.xml.dist export-ignore
3 changes: 3 additions & 0 deletions src/Service/Sso/.github/FUNDING.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# These are supported funding model platforms

github: [nyholm, jderusse]
2 changes: 2 additions & 0 deletions src/Service/Sso/.github/workflows/.editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[*.yml]
indent_size = 2
Loading

0 comments on commit 8972136

Please sign in to comment.