InstanceProfileProvider.php 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102
  1. <?php
  2. namespace Aws\Credentials;
  3. use Aws\Exception\CredentialsException;
  4. use GuzzleHttp\Promise;
  5. use GuzzleHttp\Psr7\Request;
  6. use GuzzleHttp\Promise\PromiseInterface;
  7. use Psr\Http\Message\ResponseInterface;
  8. /**
  9. * Credential provider that provides credentials from the EC2 metadata server.
  10. */
  11. class InstanceProfileProvider
  12. {
  13. const SERVER_URI = 'http://169.254.169.254/latest/';
  14. const CRED_PATH = 'meta-data/iam/security-credentials/';
  15. /** @var string */
  16. private $profile;
  17. /** @var callable */
  18. private $client;
  19. /**
  20. * The constructor accepts the following options:
  21. *
  22. * - timeout: Connection timeout, in seconds.
  23. * - profile: Optional EC2 profile name, if known.
  24. *
  25. * @param array $config Configuration options.
  26. */
  27. public function __construct(array $config = [])
  28. {
  29. $this->timeout = isset($config['timeout']) ? $config['timeout'] : 1.0;
  30. $this->profile = isset($config['profile']) ? $config['profile'] : null;
  31. $this->client = isset($config['client'])
  32. ? $config['client'] // internal use only
  33. : \Aws\default_http_handler();
  34. }
  35. /**
  36. * Loads instance profile credentials.
  37. *
  38. * @return PromiseInterface
  39. */
  40. public function __invoke()
  41. {
  42. return Promise\coroutine(function () {
  43. if (!$this->profile) {
  44. $this->profile = (yield $this->request(self::CRED_PATH));
  45. }
  46. $json = (yield $this->request(self::CRED_PATH . $this->profile));
  47. $result = $this->decodeResult($json);
  48. yield new Credentials(
  49. $result['AccessKeyId'],
  50. $result['SecretAccessKey'],
  51. $result['Token'],
  52. strtotime($result['Expiration'])
  53. );
  54. });
  55. }
  56. /**
  57. * @param string $url
  58. * @return PromiseInterface Returns a promise that is fulfilled with the
  59. * body of the response as a string.
  60. */
  61. private function request($url)
  62. {
  63. $fn = $this->client;
  64. $request = new Request('GET', self::SERVER_URI . $url);
  65. return $fn($request, ['timeout' => $this->timeout])
  66. ->then(function (ResponseInterface $response) {
  67. return (string) $response->getBody();
  68. })->otherwise(function (array $reason) {
  69. $reason = $reason['exception'];
  70. $msg = $reason->getMessage();
  71. throw new CredentialsException(
  72. $this->createErrorMessage($msg, 0, $reason)
  73. );
  74. });
  75. }
  76. private function createErrorMessage($previous)
  77. {
  78. return "Error retrieving credentials from the instance profile "
  79. . "metadata server. ({$previous})";
  80. }
  81. private function decodeResult($response)
  82. {
  83. $result = json_decode($response, true);
  84. if ($result['Code'] !== 'Success') {
  85. throw new CredentialsException('Unexpected instance profile '
  86. . 'response code: ' . $result['Code']);
  87. }
  88. return $result;
  89. }
  90. }