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

Support for parsing both config and credentials files #2939

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
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
113 changes: 30 additions & 83 deletions src/Credentials/CredentialProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -96,19 +96,13 @@ public static function defaultProvider(array $config = [])
) {
$defaultChain['sso'] = self::sso(
$profileName,
self::getHomeDir() . '/.aws/config',
null,
$config
);
$defaultChain['process_credentials'] = self::process();
$defaultChain['ini'] = self::ini();
$defaultChain['process_config'] = self::process(
'profile ' . $profileName,
self::getHomeDir() . '/.aws/config'
);
$defaultChain['ini_config'] = self::ini(
'profile '. $profileName,
self::getHomeDir() . '/.aws/config'
);
$defaultChain['process_config'] = self::process($profileName);
$defaultChain['ini_config'] = self::ini($profileName);
}

if (self::shouldUseEcs()) {
Expand Down Expand Up @@ -325,21 +319,14 @@ public static function sso($ssoProfileName = 'default',
$filename = null,
$config = []
) {
$filename = $filename ?: (self::getHomeDir() . '/.aws/config');

return function () use ($ssoProfileName, $filename, $config) {
if (!@is_readable($filename)) {
return self::reject("Cannot read credentials from $filename");
}
$profiles = self::loadProfiles($filename);

if (isset($profiles[$ssoProfileName])) {
$ssoProfile = $profiles[$ssoProfileName];
} elseif (isset($profiles['profile ' . $ssoProfileName])) {
$ssoProfileName = 'profile ' . $ssoProfileName;
$ssoProfile = $profiles[$ssoProfileName];
} else {
return self::reject("Profile {$ssoProfileName} does not exist in {$filename}.");
return self::reject("Profile {$ssoProfileName} does not exist in config or credentials files.");
}

if (!empty($ssoProfile['sso_session'])) {
Expand Down Expand Up @@ -455,8 +442,7 @@ public static function assumeRoleWithWebIdentityCredentialProvider(array $config
*
* @param string|null $profile Profile to use. If not specified will use
* the "default" profile in "~/.aws/credentials".
* @param string|null $filename If provided, uses a custom filename rather
* than looking in the home directory.
* @param string|null $filename If provided, also load from a custom config file.
* @param array|null $config If provided, may contain the following:
* preferStaticCredentials: If true, prefer static
* credentials to role_arn if both are present
Expand All @@ -469,7 +455,6 @@ public static function assumeRoleWithWebIdentityCredentialProvider(array $config
*/
public static function ini($profile = null, $filename = null, array $config = [])
{
$filename = self::getFileName($filename);
$profile = $profile ?: (getenv(self::ENV_PROFILE) ?: 'default');

return function () use ($profile, $filename, $config) {
Expand All @@ -481,15 +466,9 @@ public static function ini($profile = null, $filename = null, array $config = []
: false;
$stsClient = isset($config['stsClient']) ? $config['stsClient'] : null;

if (!@is_readable($filename)) {
return self::reject("Cannot read credentials from $filename");
}
$data = self::loadProfiles($filename);
if ($data === false) {
return self::reject("Invalid credentials file: $filename");
}
if (!isset($data[$profile])) {
return self::reject("'$profile' not found in credentials file");
return self::reject("'$profile' not found in config or credentials files");
}

/*
Expand Down Expand Up @@ -526,8 +505,8 @@ public static function ini($profile = null, $filename = null, array $config = []
if (!isset($data[$profile]['aws_access_key_id'])
|| !isset($data[$profile]['aws_secret_access_key'])
) {
return self::reject("No credentials present in INI profile "
. "'$profile' ($filename)");
return self::reject("No credentials present in config or credentials files with profile "
. "'$profile'");
}

if (empty($data[$profile]['aws_session_token'])) {
Expand All @@ -553,30 +532,22 @@ public static function ini($profile = null, $filename = null, array $config = []
*
* @param string|null $profile Profile to use. If not specified will use
* the "default" profile in "~/.aws/credentials".
* @param string|null $filename If provided, uses a custom filename rather
* than looking in the home directory.
* @param string|null $filename If provided, also load from a custom config file.
*
* @return callable
*/
public static function process($profile = null, $filename = null)
{
$filename = self::getFileName($filename);
$profile = $profile ?: (getenv(self::ENV_PROFILE) ?: 'default');

return function () use ($profile, $filename) {
if (!@is_readable($filename)) {
return self::reject("Cannot read process credentials from $filename");
}
$data = \Aws\parse_ini_file($filename, true, INI_SCANNER_RAW);
if ($data === false) {
return self::reject("Invalid credentials file: $filename");
}
$data = self::loadProfiles($filename);
if (!isset($data[$profile])) {
return self::reject("'$profile' not found in credentials file");
return self::reject("'$profile' not found in config or credentials files");
}
if (!isset($data[$profile]['credential_process'])) {
return self::reject("No credential_process present in INI profile "
. "'$profile' ($filename)");
return self::reject("No credential_process present in config or credentials files with profile "
. "'$profile'");
}

$credentialProcess = $data[$profile]['credential_process'];
Expand Down Expand Up @@ -672,7 +643,7 @@ private static function loadRoleProfile(
if (empty($roleArn)) {
return self::reject(
"A role_arn must be provided with credential_source in " .
"file {$filename} under profile {$profileName} "
"config or credentials files under profile {$profileName} "
);
}
}
Expand Down Expand Up @@ -733,33 +704,16 @@ private static function getHomeDir()
*/
private static function loadProfiles($filename)
{
$profileData = \Aws\parse_ini_file($filename, true, INI_SCANNER_RAW);

// If loading .aws/credentials, also load .aws/config when AWS_SDK_LOAD_NONDEFAULT_CONFIG is set
if ($filename === self::getHomeDir() . '/.aws/credentials'
&& getenv('AWS_SDK_LOAD_NONDEFAULT_CONFIG')
) {
$configFilename = self::getHomeDir() . '/.aws/config';
$configProfileData = \Aws\parse_ini_file($configFilename, true, INI_SCANNER_RAW);
foreach ($configProfileData as $name => $profile) {
// standardize config profile names
$name = str_replace('profile ', '', $name);
if (!isset($profileData[$name])) {
$profileData[$name] = $profile;
}
}
}

return $profileData;
return self::loadDefaultProfiles($filename);
}

/**
* Gets profiles from ~/.aws/credentials and ~/.aws/config ini files
*/
private static function loadDefaultProfiles() {
private static function loadDefaultProfiles($extraFilename = null) {
$profiles = [];
$credFile = self::getHomeDir() . '/.aws/credentials';
$configFile = self::getHomeDir() . '/.aws/config';
$credFile = getenv('AWS_SHARED_CREDENTIALS_FILE') ?: (self::getHomeDir() . '/.aws/credentials');
$configFile = getenv('AWS_CONFIG_FILE') ?: (self::getHomeDir() . '/.aws/config');
if (file_exists($credFile)) {
$profiles = \Aws\parse_ini_file($credFile, true, INI_SCANNER_RAW);
}
Expand All @@ -769,18 +723,24 @@ private static function loadDefaultProfiles() {
foreach ($configProfileData as $name => $profile) {
// standardize config profile names
$name = str_replace('profile ', '', $name);
if (!isset($profiles[$name])) {
$profiles[$name] = $profile;
}
$profiles[$name] = array_merge($profiles[$name] ?? [], $profile);
}
}

if (!is_null($extraFilename) && $extraFilename != $credFile && $extraFilename != $configFile) {
$extraFileData = \Aws\parse_ini_file($extraFilename, true, INI_SCANNER_RAW);
foreach ($extraFileData as $name => $profile) {
// standardize config profile names
$name = str_replace('profile ', '', $name);
$profiles[$name] = array_merge($profiles[$name] ?? [], $profile);
}
}
return $profiles;
}

public static function getCredentialsFromSource(
$profileName = '',
$filename = '',
$filename = null,
$config = []
) {
$data = self::loadProfiles($filename);
Expand Down Expand Up @@ -826,19 +786,6 @@ private static function reject($msg)
return new Promise\RejectedPromise(new CredentialsException($msg));
}

/**
* @param $filename
* @return string
*/
private static function getFileName($filename)
{
if (!isset($filename)) {
$filename = getenv(self::ENV_SHARED_CREDENTIALS_FILE) ?:
(self::getHomeDir() . '/.aws/credentials');
}
return $filename;
}

/**
* @return boolean
*/
Expand Down Expand Up @@ -866,7 +813,7 @@ private static function getSsoCredentials($profiles, $ssoProfileName, $filename,
$sessionName = $ssoProfile['sso_session'];
if (empty($profiles['sso-session ' . $sessionName])) {
return self::reject(
"Could not find sso-session {$sessionName} in {$filename}"
"Could not find sso-session {$sessionName} in config or credentials files"
);
}
$ssoSession = $profiles['sso-session ' . $ssoProfile['sso_session']];
Expand Down Expand Up @@ -918,7 +865,7 @@ private static function getSsoCredentialsLegacy($profiles, $ssoProfileName, $fil
|| empty($ssoProfile['sso_role_name'])
) {
return self::reject(
"Profile {$ssoProfileName} in {$filename} must contain the following keys: "
"Profile {$ssoProfileName} in config or credential files must contain the following keys: "
. "sso_start_url, sso_region, sso_account_id, and sso_role_name."
);
}
Expand Down
37 changes: 24 additions & 13 deletions src/Token/ParsesIniTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,34 @@
trait ParsesIniTrait
{
/**
* Gets profiles from specified $filename, or default ini files.
* Gets profiles from specified $extraFilename, or default ini files.
*/
private static function loadProfiles($filename)
{
$profileData = \Aws\parse_ini_file($filename, true, INI_SCANNER_RAW);
$configFilename = self::getHomeDir() . '/.aws/config';
if (is_readable($configFilename)) {
$configProfiles = \Aws\parse_ini_file($configFilename, true, INI_SCANNER_RAW);
$profileData = array_merge($configProfiles, $profileData);
private static function loadProfiles($extraFilename = null) {
$profiles = [];
$credFile = getenv('AWS_SHARED_CREDENTIALS_FILE') ?: (self::getHomeDir() . '/.aws/credentials');
$configFile = getenv('AWS_CONFIG_FILE') ?: (self::getHomeDir() . '/.aws/config');
if (file_exists($credFile)) {
$profiles = \Aws\parse_ini_file($credFile, true, INI_SCANNER_RAW);
}
foreach ($profileData as $name => $profile) {
// standardize config profile names
$name = str_replace('profile ', '', $name);
$profileData[$name] = $profile;

if (file_exists($configFile)) {
$configProfileData = \Aws\parse_ini_file($configFile, true, INI_SCANNER_RAW);
foreach ($configProfileData as $name => $profile) {
// standardize config profile names
$name = str_replace('profile ', '', $name);
$profiles[$name] = array_merge($profiles[$name] ?? [], $profile);
}
}

return $profileData;
if (!is_null($extraFilename) && $extraFilename != $credFile && $extraFilename != $configFile) {
$extraFileData = \Aws\parse_ini_file($extraFilename, true, INI_SCANNER_RAW);
foreach ($extraFileData as $name => $profile) {
// standardize config profile names
$name = str_replace('profile ', '', $name);
$profiles[$name] = array_merge($profiles[$name] ?? [], $profile);
}
}
return $profiles;
}

/**
Expand Down
34 changes: 6 additions & 28 deletions src/Token/SsoTokenProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ class SsoTokenProvider implements RefreshableTokenProviderInterface
/**
* Constructs a new SsoTokenProvider object, which will fetch a token from an authenticated SSO profile
* @param string $profileName The name of the profile that contains the sso_session key
* @param string|null $configFilePath Name of the config file to sso profile from
* @param string|null $configFilePath If provided, also load from a custom config file.
* @param SSOOIDCClient|null $ssoOidcClient The sso client for generating a new token
*/
public function __construct(
Expand All @@ -40,7 +40,7 @@ public function __construct(
SSOOIDCClient $ssoOidcClient = null
) {
$this->profileName = $this->resolveProfileName($profileName);
$this->configFilePath = $this->resolveConfigFile($configFilePath);
$this->configFilePath = $configFilePath;
$this->ssoOidcClient = $ssoOidcClient;
}

Expand All @@ -63,24 +63,6 @@ private function resolveProfileName($argProfileName): string
}
}

/**
* This method resolves the config file from where the profiles
* are going to be loaded from. If $argFileName is not empty then,
* it takes precedence over the default config file location.
*
* @param string|null $argConfigFilePath The config path provided as argument.
*
* @return string
*/
private function resolveConfigFile($argConfigFilePath): string
{
if (empty($argConfigFilePath)) {
return self::getHomeDir() . '/.aws/config';
} else{
return $argConfigFilePath;
}
}

/**
* Loads cached sso credentials.
*
Expand All @@ -89,19 +71,15 @@ private function resolveConfigFile($argConfigFilePath): string
public function __invoke()
{
return Promise\Coroutine::of(function () {
if (empty($this->configFilePath) || !is_readable($this->configFilePath)) {
throw new TokenException("Cannot read profiles from {$this->configFilePath}");
}

$profiles = self::loadProfiles($this->configFilePath);
if (!isset($profiles[$this->profileName])) {
throw new TokenException("Profile `{$this->profileName}` does not exist in {$this->configFilePath}.");
throw new TokenException("Profile `{$this->profileName}` does not exist in config or credentials files.");
}

$profile = $profiles[$this->profileName];
if (empty($profile['sso_session'])) {
throw new TokenException(
"Profile `{$this->profileName}` in {$this->configFilePath} must contain an sso_session."
"Profile `{$this->profileName}` in config or credentials files must contain an sso_session."
);
}

Expand All @@ -110,15 +88,15 @@ public function __invoke()
$profileSsoSession = 'sso-session ' . $ssoSessionName;
if (empty($profiles[$profileSsoSession])) {
throw new TokenException(
"Sso session `{$ssoSessionName}` does not exist in {$this->configFilePath}"
"Sso session `{$ssoSessionName}` does not exist in config or credentials files"
);
}

$sessionProfileData = $profiles[$profileSsoSession];
foreach (['sso_start_url', 'sso_region'] as $requiredProp) {
if (empty($sessionProfileData[$requiredProp])) {
throw new TokenException(
"Sso session `{$ssoSessionName}` in {$this->configFilePath} is missing the required property `{$requiredProp}`"
"Sso session `{$ssoSessionName}` in config or credentials files is missing the required property `{$requiredProp}`"
);
}
}
Expand Down
4 changes: 2 additions & 2 deletions src/Token/TokenProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -254,13 +254,13 @@ private static function reject($msg)
* Token provider that creates a token from cached sso credentials
*
* @param string $profileName the name of the ini profile name
* @param string $filename the location of the ini file
* @param string|null $filename If provided, also load from a custom config file.
* @param array $config configuration options
*
* @return SsoTokenProvider
* @see Aws\Token\SsoTokenProvider for $config details.
*/
public static function sso($profileName, $filename, $config = [])
public static function sso($profileName, $filename = null, $config = [])
{
$ssoClient = isset($config['ssoClient']) ? $config['ssoClient'] : null;

Expand Down
Loading