amazon-s3-php-class icon indicating copy to clipboard operation
amazon-s3-php-class copied to clipboard

Support for IAM credentials

Open joshuaspence opened this issue 10 years ago • 2 comments

It would be great if this library supported the use of IAM credentials (as opposed to an explicit access key / secret key pair). I'm not entirely sure how feasible it would be to implement this, although from my experience it would generally involve the following:

  1. Curl http://169.254.169.254/latest/meta-data/iam/security-credentials/ to get the relevant IAM role.
  2. Curl curl http://169.254.169.254/latest/meta-data/iam/security-credentials/<ROLE>/ to get the S3 credentials (access key, secret key and token).
  3. Use the credentials from step 2.

joshuaspence avatar Jun 04 '14 19:06 joshuaspence

I needed to add support for this for a project so here is the patch in case anyone also needs IAM role support.

Patch is under public domain.

diff --git a/components/S3Adapter/S3.php b/components/S3Adapter/S3.php
index 0b1564b..af4d65c 100644
--- a/components/S3Adapter/S3.php
+++ b/components/S3Adapter/S3.php
@@ -127,6 +127,15 @@ class S3
     */
    private static $__timeOffset = 0;

+    /**
+     * Security Token used when accessing from an IAM role
+     *
+     * @var string
+     * @access public
+     * @static
+     */
+    public static $securityToken = null;
+
    /**
     * SSL client key
     *
@@ -180,14 +189,16 @@ class S3
    * @param string $secretKey Secret key
    * @param boolean $useSSL Enable SSL
    * @param string $endpoint Amazon URI
+    * @param string $securityToken For use with IAM roles
    * @return void
    */
-   public function __construct($accessKey = null, $secretKey = null, $useSSL = false, $endpoint = 's3.amazonaws.com')
+   public function __construct($accessKey = null, $secretKey = null, $useSSL = false, $endpoint = 's3.amazonaws.com', $securityToken = null)
    {
        if ($accessKey !== null && $secretKey !== null)
            self::setAuth($accessKey, $secretKey);
        self::$useSSL = $useSSL;
        self::$endpoint = $endpoint;
+        self::$securityToken = $securityToken;
    }


@@ -364,7 +375,7 @@ class S3
    */
    public static function listBuckets($detailed = false)
    {
-       $rest = new S3Request('GET', '', '', self::$endpoint);
+       $rest = new S3Request('GET', '', '', self::$endpoint, self::$securityToken);
        $rest = $rest->getResponse();
        if ($rest->error === false && $rest->code !== 200)
            $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
@@ -410,7 +421,7 @@ class S3
    */
    public static function getBucket($bucket, $prefix = null, $marker = null, $maxKeys = null, $delimiter = null, $returnCommonPrefixes = false)
    {
-       $rest = new S3Request('GET', $bucket, '', self::$endpoint);
+       $rest = new S3Request('GET', $bucket, '', self::$endpoint, self::$securityToken);
        if ($maxKeys == 0) $maxKeys = null;
        if ($prefix !== null && $prefix !== '') $rest->setParameter('prefix', $prefix);
        if ($marker !== null && $marker !== '') $rest->setParameter('marker', $marker);
@@ -455,7 +466,7 @@ class S3
        if ($maxKeys == null && $nextMarker !== null && (string)$response->body->IsTruncated == 'true')
        do
        {
-           $rest = new S3Request('GET', $bucket, '', self::$endpoint);
+           $rest = new S3Request('GET', $bucket, '', self::$endpoint, self::$securityToken);
            if ($prefix !== null && $prefix !== '') $rest->setParameter('prefix', $prefix);
            $rest->setParameter('marker', $nextMarker);
            if ($delimiter !== null && $delimiter !== '') $rest->setParameter('delimiter', $delimiter);
@@ -497,7 +508,7 @@ class S3
    */
    public static function putBucket($bucket, $acl = self::ACL_PRIVATE, $location = false)
    {
-       $rest = new S3Request('PUT', $bucket, '', self::$endpoint);
+       $rest = new S3Request('PUT', $bucket, '', self::$endpoint, self::$securityToken);
        $rest->setAmzHeader('x-amz-acl', $acl);

        if ($location !== false)
@@ -533,7 +544,7 @@ class S3
    */
    public static function deleteBucket($bucket)
    {
-       $rest = new S3Request('DELETE', $bucket, '', self::$endpoint);
+       $rest = new S3Request('DELETE', $bucket, '', self::$endpoint, self::$securityToken);
        $rest = $rest->getResponse();
        if ($rest->error === false && $rest->code !== 204)
            $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
@@ -615,7 +626,7 @@ class S3
    public static function putObject($input, $bucket, $uri, $acl = self::ACL_PRIVATE, $metaHeaders = array(), $requestHeaders = array(), $storageClass = self::STORAGE_CLASS_STANDARD, $serverSideEncryption = self::SSE_NONE)
    {
        if ($input === false) return false;
-       $rest = new S3Request('PUT', $bucket, $uri, self::$endpoint);
+       $rest = new S3Request('PUT', $bucket, $uri, self::$endpoint, self::$securityToken);

        if (!is_array($input)) $input = array(
            'data' => $input, 'size' => strlen($input),
@@ -731,7 +742,7 @@ class S3
    */
    public static function getObject($bucket, $uri, $saveTo = false)
    {
-       $rest = new S3Request('GET', $bucket, $uri, self::$endpoint);
+       $rest = new S3Request('GET', $bucket, $uri, self::$endpoint, self::$securityToken);
        if ($saveTo !== false)
        {
            if (is_resource($saveTo))
@@ -766,7 +777,7 @@ class S3
    */
    public static function getObjectInfo($bucket, $uri, $returnInfo = true)
    {
-       $rest = new S3Request('HEAD', $bucket, $uri, self::$endpoint);
+       $rest = new S3Request('HEAD', $bucket, $uri, self::$endpoint, self::$securityToken);
        $rest = $rest->getResponse();
        if ($rest->error === false && ($rest->code !== 200 && $rest->code !== 404))
            $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
@@ -795,7 +806,7 @@ class S3
    */
    public static function copyObject($srcBucket, $srcUri, $bucket, $uri, $acl = self::ACL_PRIVATE, $metaHeaders = array(), $requestHeaders = array(), $storageClass = self::STORAGE_CLASS_STANDARD)
    {
-       $rest = new S3Request('PUT', $bucket, $uri, self::$endpoint);
+       $rest = new S3Request('PUT', $bucket, $uri, self::$endpoint, self::$securityToken);
        $rest->setHeader('Content-Length', 0);
        foreach ($requestHeaders as $h => $v) $rest->setHeader($h, $v);
        foreach ($metaHeaders as $h => $v) $rest->setAmzHeader('x-amz-meta-'.$h, $v);
@@ -831,7 +842,7 @@ class S3
    */
    public static function setBucketRedirect($bucket = NULL, $location = NULL)
    {
-       $rest = new S3Request('PUT', $bucket, '', self::$endpoint);
+       $rest = new S3Request('PUT', $bucket, '', self::$endpoint, self::$securityToken);

        if( empty($bucket) || empty($location) ) {
            self::__triggerError("S3::setBucketRedirect({$bucket}, {$location}): Empty parameter.", __FILE__, __LINE__);
@@ -908,7 +919,7 @@ class S3
        }
        $dom->appendChild($bucketLoggingStatus);

-       $rest = new S3Request('PUT', $bucket, '', self::$endpoint);
+       $rest = new S3Request('PUT', $bucket, '', self::$endpoint, self::$securityToken);
        $rest->setParameter('logging', null);
        $rest->data = $dom->saveXML();
        $rest->size = strlen($rest->data);
@@ -937,7 +948,7 @@ class S3
    */
    public static function getBucketLogging($bucket)
    {
-       $rest = new S3Request('GET', $bucket, '', self::$endpoint);
+       $rest = new S3Request('GET', $bucket, '', self::$endpoint, self::$securityToken);
        $rest->setParameter('logging', null);
        $rest = $rest->getResponse();
        if ($rest->error === false && $rest->code !== 200)
@@ -976,7 +987,7 @@ class S3
    */
    public static function getBucketLocation($bucket)
    {
-       $rest = new S3Request('GET', $bucket, '', self::$endpoint);
+       $rest = new S3Request('GET', $bucket, '', self::$endpoint, self::$securityToken);
        $rest->setParameter('location', null);
        $rest = $rest->getResponse();
        if ($rest->error === false && $rest->code !== 200)
@@ -1040,7 +1051,7 @@ class S3
        $accessControlPolicy->appendChild($accessControlList);
        $dom->appendChild($accessControlPolicy);

-       $rest = new S3Request('PUT', $bucket, $uri, self::$endpoint);
+       $rest = new S3Request('PUT', $bucket, $uri, self::$endpoint, self::$securityToken);
        $rest->setParameter('acl', null);
        $rest->data = $dom->saveXML();
        $rest->size = strlen($rest->data);
@@ -1067,7 +1078,7 @@ class S3
    */
    public static function getAccessControlPolicy($bucket, $uri = '')
    {
-       $rest = new S3Request('GET', $bucket, $uri, self::$endpoint);
+       $rest = new S3Request('GET', $bucket, $uri, self::$endpoint, self::$securityToken);
        $rest->setParameter('acl', null);
        $rest = $rest->getResponse();
        if ($rest->error === false && $rest->code !== 200)
@@ -1128,7 +1139,7 @@ class S3
    */
    public static function deleteObject($bucket, $uri)
    {
-       $rest = new S3Request('DELETE', $bucket, $uri, self::$endpoint);
+       $rest = new S3Request('DELETE', $bucket, $uri, self::$endpoint, self::$securityToken);
        $rest = $rest->getResponse();
        if ($rest->error === false && $rest->code !== 204)
            $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
@@ -1291,7 +1302,7 @@ class S3
        $useSSL = self::$useSSL;

        self::$useSSL = true; // CloudFront requires SSL
-       $rest = new S3Request('POST', '', '2010-11-01/distribution', 'cloudfront.amazonaws.com');
+       $rest = new S3Request('POST', '', '2010-11-01/distribution', 'cloudfront.amazonaws.com', self::$securityToken);
        $rest->data = self::__getCloudFrontDistributionConfigXML(
            $bucket.'.s3.amazonaws.com',
            $enabled,
@@ -1339,7 +1350,7 @@ class S3
        $useSSL = self::$useSSL;

        self::$useSSL = true; // CloudFront requires SSL
-       $rest = new S3Request('GET', '', '2010-11-01/distribution/'.$distributionId, 'cloudfront.amazonaws.com');
+       $rest = new S3Request('GET', '', '2010-11-01/distribution/'.$distributionId, 'cloudfront.amazonaws.com', self::$securityToken);
        $rest = self::__getCloudFrontResponse($rest);

        self::$useSSL = $useSSL;
@@ -1381,7 +1392,7 @@ class S3
        $useSSL = self::$useSSL;

        self::$useSSL = true; // CloudFront requires SSL
-       $rest = new S3Request('PUT', '', '2010-11-01/distribution/'.$dist['id'].'/config', 'cloudfront.amazonaws.com');
+       $rest = new S3Request('PUT', '', '2010-11-01/distribution/'.$dist['id'].'/config', 'cloudfront.amazonaws.com', self::$securityToken);
        $rest->data = self::__getCloudFrontDistributionConfigXML(
            $dist['origin'],
            $dist['enabled'],
@@ -1433,7 +1444,7 @@ class S3
        $useSSL = self::$useSSL;

        self::$useSSL = true; // CloudFront requires SSL
-       $rest = new S3Request('DELETE', '', '2008-06-30/distribution/'.$dist['id'], 'cloudfront.amazonaws.com');
+       $rest = new S3Request('DELETE', '', '2008-06-30/distribution/'.$dist['id'], 'cloudfront.amazonaws.com', self::$securityToken);
        $rest->setHeader('If-Match', $dist['hash']);
        $rest = self::__getCloudFrontResponse($rest);

@@ -1467,7 +1478,7 @@ class S3

        $useSSL = self::$useSSL;
        self::$useSSL = true; // CloudFront requires SSL
-       $rest = new S3Request('GET', '', '2010-11-01/distribution', 'cloudfront.amazonaws.com');
+       $rest = new S3Request('GET', '', '2010-11-01/distribution', 'cloudfront.amazonaws.com', self::$securityToken);
        $rest = self::__getCloudFrontResponse($rest);
        self::$useSSL = $useSSL;

@@ -1511,7 +1522,7 @@ class S3
        }

        self::$useSSL = true; // CloudFront requires SSL
-       $rest = new S3Request('GET', '', '2010-11-01/origin-access-identity/cloudfront', 'cloudfront.amazonaws.com');
+       $rest = new S3Request('GET', '', '2010-11-01/origin-access-identity/cloudfront', 'cloudfront.amazonaws.com', self::$securityToken);
        $rest = self::__getCloudFrontResponse($rest);
        $useSSL = self::$useSSL;

@@ -1556,7 +1567,7 @@ class S3

        $useSSL = self::$useSSL;
        self::$useSSL = true; // CloudFront requires SSL
-       $rest = new S3Request('POST', '', '2010-08-01/distribution/'.$distributionId.'/invalidation', 'cloudfront.amazonaws.com');
+       $rest = new S3Request('POST', '', '2010-08-01/distribution/'.$distributionId.'/invalidation', 'cloudfront.amazonaws.com', self::$securityToken);
        $rest->data = self::__getCloudFrontInvalidationBatchXML($paths, (string)microtime(true));
        $rest->size = strlen($rest->data);
        $rest = self::__getCloudFrontResponse($rest);
@@ -1623,7 +1634,7 @@ class S3

        $useSSL = self::$useSSL;
        self::$useSSL = true; // CloudFront requires SSL
-       $rest = new S3Request('GET', '', '2010-11-01/distribution/'.$distributionId.'/invalidation', 'cloudfront.amazonaws.com');
+       $rest = new S3Request('GET', '', '2010-11-01/distribution/'.$distributionId.'/invalidation', 'cloudfront.amazonaws.com', self::$securityToken);
        $rest = self::__getCloudFrontResponse($rest);
        self::$useSSL = $useSSL;

@@ -1998,9 +2009,10 @@ final class S3Request
    * @param string $bucket Bucket name
    * @param string $uri Object URI
    * @param string $endpoint AWS endpoint URI
+    * @param string $securityToken When using IAM roles
    * @return mixed
    */
-   function __construct($verb, $bucket = '', $uri = '', $endpoint = 's3.amazonaws.com')
+   function __construct($verb, $bucket = '', $uri = '', $endpoint = 's3.amazonaws.com', $securityToken=null)
    {

        $this->endpoint = $endpoint;
@@ -2035,6 +2047,10 @@ final class S3Request
            $this->resource = $this->uri;
        }

+        if ($securityToken !== null)
+        {
+            $this->amzHeaders['x-amz-security-token'] = $securityToken;
+        }

        $this->headers['Date'] = gmdate('D, d M Y H:i:s T');
        $this->response = new STDClass;

andrewfenn avatar Aug 19 '14 08:08 andrewfenn

@andrewfenn Thanks! That simple patch was very helpful for something I'm working on.

jstanden avatar Feb 20 '16 08:02 jstanden