| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396 |
- <?php
- namespace Aws\Credentials;
- use Aws;
- use Aws\CacheInterface;
- use Aws\Exception\CredentialsException;
- use GuzzleHttp\Promise;
- /**
- * Credential providers are functions that accept no arguments and return a
- * promise that is fulfilled with an {@see \Aws\Credentials\CredentialsInterface}
- * or rejected with an {@see \Aws\Exception\CredentialsException}.
- *
- * <code>
- * use Aws\Credentials\CredentialProvider;
- * $provider = CredentialProvider::defaultProvider();
- * // Returns a CredentialsInterface or throws.
- * $creds = $provider()->wait();
- * </code>
- *
- * Credential providers can be composed to create credentials using conditional
- * logic that can create different credentials in different environments. You
- * can compose multiple providers into a single provider using
- * {@see Aws\Credentials\CredentialProvider::chain}. This function accepts
- * providers as variadic arguments and returns a new function that will invoke
- * each provider until a successful set of credentials is returned.
- *
- * <code>
- * // First try an INI file at this location.
- * $a = CredentialProvider::ini(null, '/path/to/file.ini');
- * // Then try an INI file at this location.
- * $b = CredentialProvider::ini(null, '/path/to/other-file.ini');
- * // Then try loading from environment variables.
- * $c = CredentialProvider::env();
- * // Combine the three providers together.
- * $composed = CredentialProvider::chain($a, $b, $c);
- * // Returns a promise that is fulfilled with credentials or throws.
- * $promise = $composed();
- * // Wait on the credentials to resolve.
- * $creds = $promise->wait();
- * </code>
- */
- class CredentialProvider
- {
- const ENV_KEY = 'AWS_ACCESS_KEY_ID';
- const ENV_SECRET = 'AWS_SECRET_ACCESS_KEY';
- const ENV_SESSION = 'AWS_SESSION_TOKEN';
- const ENV_PROFILE = 'AWS_PROFILE';
- /**
- * Create a default credential provider that first checks for environment
- * variables, then checks for the "default" profile in ~/.aws/credentials,
- * then checks for "profile default" profile in ~/.aws/config (which is
- * the default profile of AWS CLI), then tries to make a GET Request to
- * fetch credentials if Ecs environment variable is presented, and finally
- * checks for EC2 instance profile credentials.
- *
- * This provider is automatically wrapped in a memoize function that caches
- * previously provided credentials.
- *
- * @param array $config Optional array of ecs/instance profile credentials
- * provider options.
- *
- * @return callable
- */
- public static function defaultProvider(array $config = [])
- {
- $localCredentialProviders = self::localCredentialProviders();
- $remoteCredentialProviders = self::remoteCredentialProviders($config);
- return self::memoize(
- call_user_func_array(
- 'self::chain',
- array_merge($localCredentialProviders, $remoteCredentialProviders)
- )
- );
- }
- /**
- * Create a credential provider function from a set of static credentials.
- *
- * @param CredentialsInterface $creds
- *
- * @return callable
- */
- public static function fromCredentials(CredentialsInterface $creds)
- {
- $promise = Promise\promise_for($creds);
- return function () use ($promise) {
- return $promise;
- };
- }
- /**
- * Creates an aggregate credentials provider that invokes the provided
- * variadic providers one after the other until a provider returns
- * credentials.
- *
- * @return callable
- */
- public static function chain()
- {
- $links = func_get_args();
- if (empty($links)) {
- throw new \InvalidArgumentException('No providers in chain');
- }
- return function () use ($links) {
- /** @var callable $parent */
- $parent = array_shift($links);
- $promise = $parent();
- while ($next = array_shift($links)) {
- $promise = $promise->otherwise($next);
- }
- return $promise;
- };
- }
- /**
- * Wraps a credential provider and caches previously provided credentials.
- *
- * Ensures that cached credentials are refreshed when they expire.
- *
- * @param callable $provider Credentials provider function to wrap.
- *
- * @return callable
- */
- public static function memoize(callable $provider)
- {
- return function () use ($provider) {
- static $result;
- static $isConstant;
- // Constant credentials will be returned constantly.
- if ($isConstant) {
- return $result;
- }
- // Create the initial promise that will be used as the cached value
- // until it expires.
- if (null === $result) {
- $result = $provider();
- }
- // Return credentials that could expire and refresh when needed.
- return $result
- ->then(function (CredentialsInterface $creds) use ($provider, &$isConstant, &$result) {
- // Determine if these are constant credentials.
- if (!$creds->getExpiration()) {
- $isConstant = true;
- return $creds;
- }
- // Refresh expired credentials.
- if (!$creds->isExpired()) {
- return $creds;
- }
- // Refresh the result and forward the promise.
- return $result = $provider();
- });
- };
- }
- /**
- * Wraps a credential provider and saves provided credentials in an
- * instance of Aws\CacheInterface. Forwards calls when no credentials found
- * in cache and updates cache with the results.
- *
- * Defaults to using a simple file-based cache when none provided.
- *
- * @param callable $provider Credentials provider function to wrap
- * @param CacheInterface $cache Cache to store credentials
- * @param string|null $cacheKey (optional) Cache key to use
- *
- * @return callable
- */
- public static function cache(
- callable $provider,
- CacheInterface $cache,
- $cacheKey = null
- ) {
- $cacheKey = $cacheKey ?: 'aws_cached_credentials';
- return function () use ($provider, $cache, $cacheKey) {
- $found = $cache->get($cacheKey);
- if ($found instanceof CredentialsInterface && !$found->isExpired()) {
- return Promise\promise_for($found);
- }
- return $provider()
- ->then(function (CredentialsInterface $creds) use (
- $cache,
- $cacheKey
- ) {
- $cache->set(
- $cacheKey,
- $creds,
- null === $creds->getExpiration() ?
- 0 : $creds->getExpiration() - time()
- );
- return $creds;
- });
- };
- }
- /**
- * Provider that creates credentials from environment variables
- * AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, and AWS_SESSION_TOKEN.
- *
- * @return callable
- */
- public static function env()
- {
- return function () {
- // Use credentials from environment variables, if available
- $key = getenv(self::ENV_KEY);
- $secret = getenv(self::ENV_SECRET);
- if ($key && $secret) {
- return Promise\promise_for(
- new Credentials($key, $secret, getenv(self::ENV_SESSION) ?: NULL)
- );
- }
- return self::reject('Could not find environment variable '
- . 'credentials in ' . self::ENV_KEY . '/' . self::ENV_SECRET);
- };
- }
- /**
- * Credential provider that creates credentials using instance profile
- * credentials.
- *
- * @param array $config Array of configuration data.
- *
- * @return InstanceProfileProvider
- * @see Aws\Credentials\InstanceProfileProvider for $config details.
- */
- public static function instanceProfile(array $config = [])
- {
- return new InstanceProfileProvider($config);
- }
- /**
- * Credential provider that creates credentials using
- * ecs credentials by a GET request, whose uri is specified
- * by environment variable
- *
- * @param array $config Array of configuration data.
- *
- * @return EcsCredentialProvider
- * @see Aws\Credentials\EcsCredentialProvider for $config details.
- */
- public static function ecsCredentials(array $config = [])
- {
- return new EcsCredentialProvider($config);
- }
- /**
- * Credential provider that creates credentials using assume role
- *
- * @param array $config Array of configuration data
- * @return callable
- * @see Aws\Credentials\AssumeRoleCredentialProvider for $config details.
- */
- public static function assumeRole(array $config=[])
- {
- return new AssumeRoleCredentialProvider($config);
- }
- /**
- * Credentials provider that creates credentials using an ini file stored
- * in the current user's home directory.
- *
- * @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.
- *
- * @return callable
- */
- public static function ini($profile = null, $filename = null)
- {
- $filename = $filename ?: (self::getHomeDir() . '/.aws/credentials');
- $profile = $profile ?: (getenv(self::ENV_PROFILE) ?: 'default');
- return function () use ($profile, $filename) {
- if (!is_readable($filename)) {
- return self::reject("Cannot read credentials from $filename");
- }
- $data = parse_ini_file($filename, true);
- if ($data === false) {
- return self::reject("Invalid credentials file: $filename");
- }
- if (!isset($data[$profile])) {
- return self::reject("'$profile' not found in credentials file");
- }
- 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)");
- }
- if (empty($data[$profile]['aws_session_token'])) {
- $data[$profile]['aws_session_token']
- = isset($data[$profile]['aws_security_token'])
- ? $data[$profile]['aws_security_token']
- : null;
- }
- return Promise\promise_for(
- new Credentials(
- $data[$profile]['aws_access_key_id'],
- $data[$profile]['aws_secret_access_key'],
- $data[$profile]['aws_session_token']
- )
- );
- };
- }
- /**
- * Local credential providers returns a list of local credential providers
- * in following order:
- * - credentials from environment variables
- * - 'default' profile in '.aws/credentials' file
- * - 'profile default' profile in '.aws/config' file
- *
- * @return array
- */
- private static function localCredentialProviders()
- {
- return [
- self::env(),
- self::ini(),
- self::ini('profile default', self::getHomeDir() . '/.aws/config')
- ];
- }
- /**
- * Remote credential providers returns a list of credentials providers
- * for the remote endpoints such as EC2 or ECS Roles.
- *
- * @param array $config Array of configuration data.
- *
- * @return array
- * @see Aws\Credentials\InstanceProfileProvider for $config details.
- * @see Aws\Credentials\EcsCredentialProvider for $config details.
- */
- private static function remoteCredentialProviders(array $config = [])
- {
- if (!empty(getenv(EcsCredentialProvider::ENV_URI))) {
- $providers['ecs'] = self::ecsCredentials($config);
- }
- $providers['instance'] = self::instanceProfile($config);
- if (isset($config['credentials'])
- && $config['credentials'] instanceof CacheInterface
- ) {
- foreach ($providers as $key => $provider) {
- $providers[$key] = self::cache(
- $provider,
- $config['credentials'],
- 'aws_cached_' . $key . '_credentials'
- );
- }
- }
- return $providers;
- }
- /**
- * Gets the environment's HOME directory if available.
- *
- * @return null|string
- */
- private static function getHomeDir()
- {
- // On Linux/Unix-like systems, use the HOME environment variable
- if ($homeDir = getenv('HOME')) {
- return $homeDir;
- }
- // Get the HOMEDRIVE and HOMEPATH values for Windows hosts
- $homeDrive = getenv('HOMEDRIVE');
- $homePath = getenv('HOMEPATH');
- return ($homeDrive && $homePath) ? $homeDrive . $homePath : null;
- }
- private static function reject($msg)
- {
- return new Promise\RejectedPromise(new CredentialsException($msg));
- }
- }
|