0
votes

I'm testing to display images from an s3 bucket using javascript, prior to making this part of an application.

I have an s3 bucket (non-public), named for this post: IMAGE-BUCKET Created an identity role : GET-IMAGE. I have temporarily given full s3 access to GET-IMAGE role. I have CORS defined for the bucket. While testing I have disabled the browser cache.

3 issues:

  • Getting "403 Forbidden" response when images are accessed from the html/script below.
  • If I make a particular image public, that image displays -- an issue with large # of images.
  • If I make the entire bucket public, images do not display

It seems Cognito identiy is not able to access the bucket, or there's an issue in the script below.

Also, setting the bucket public doesn't work either, unless each image is also set public. This bucket will be used privately, so this is only an issue while troubleshooting.

I have attached AmazonS3FullAccess to GET-IMAGE, I also added the following policy:

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "AccessS3BucketIMAGEBUCKET",
            "Action": [
                "s3:GetObject",
                "s3:PutObject",
                "s3:ListBucket"
            ],
            "Effect": "Allow",
            "Resource": "*"
        }
    ]
}

Using html and script from AWS documentation (modified):

<script src="https://sdk.amazonaws.com/js/aws-sdk-2.487.0.js"></script>

<script>
        var albumBucketName = 'IMAGE-BUCKET';
        // Initialize the Amazon Cognito credentials provider for GET-IMAGE:
        AWS.config.region = 'us-east-1'; // Region
        AWS.config.credentials = new AWS.CognitoIdentityCredentials({
            IdentityPoolId: 'us-east-1:43ba4c15-ab2f-8880-93be-xxx',
        });

        // Create a new service object
        var s3 = new AWS.S3({
            apiVersion: '2006-03-01',
            params: { Bucket: albumBucketName }
        });

        // A utility function to create HTML.
        function getHtml(template) {
            return template.join('\n');
        }

          
// Show the photos that exist in an album.
         function viewAlbum(albumName) {
                    var albumPhotosKey = '/';
                    s3.listObjects(function (err, data) {
                        if (err) {
                            return alert('There was an error viewing your album: ' + err.message);
                        }
                        // 'this' references the AWS.Response instance that represents the response
                        var href = this.request.httpRequest.endpoint.href;
                        var bucketUrl = href + albumBucketName + '/';

                        var photos = data.Contents.map(function (photo) {
                            var photoKey = photo.Key;
                            var photoUrl = bucketUrl + encodeURIComponent(photoKey);
                            return getHtml([
                                '<span>',
                                '<div>',
                                '<br/>',
                                '<img style="width:128px;height:128px;" src="' + photoUrl + '"/>',
                                '</div>',
                                '<div>',
                                '<span>',
                                photoKey.replace(albumPhotosKey, ''),
                                '</span>',
                                '</div>',
                                '</span>',
                            ]);
                        });
                        var message = photos.length ?
                            '<p>The following photos are present.</p>' :
                            '<p>There are no photos in this album.</p>';
                        var htmlTemplate = [
                            '<div>',
                            '<button onclick="listAlbums()">',
                            'Back To Albums',
                            '</button>',
                            '</div>',
                            '<h2>',
                            'Album: ' + albumName,
                            '</h2>',
                            message,
                            '<div>',
                            getHtml(photos),
                            '</div>',
                            '<h2>',
                            'End of Album: ' + albumName,
                            '</h2>',
                            '<div>',
                            '<button onclick="listAlbums()">',
                            'Back To Albums',
                            '</button>',
                            '</div>',
                        ]
                        document.getElementById('viewer').innerHTML = getHtml(htmlTemplate);
                        document.getElementsByTagName('img')[0].setAttribute('style', 'display:none;');
                    });
                }


</script>
   
</head>
<body>
    <h1>Photo Album Viewer</h1>
    <div id="viewer" />
    <button onclick="viewAlbum('');">View All Images</button>
</body>
</html>

UPDATE: If I grant public read in S3 Bucket Policy:

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "PublicRead",
            "Effect": "Allow",
            "Principal": "*",
            "Action": "s3:GetObject",
            "Resource": "arn:aws:s3:::IMAGE-BUCKET/*"
        }
    ]
}

It allows to access each image; solving the issue #2 and #3. But this makes the bucket basically public.

If I change the Bucket policy to limit to the Cognito identity, changing the principal as follows, again I am not able to access images via the html/script, getting 403 errors.

"Principal": {
    "AWS": "arn:aws:iam::547299998870:role/Cognito_GET-IMAGEIDUnauth_Role" 
}

UPDATE: I've been reading online, checking some of the other related posts ... I've it reduced to the basic components, here's the latest configuration. The configuration should be as simple as, giving access to the GET-IMAGE role based on the documentation:

Under IAM Management Console > Roles > GET-IMAGE role (unauthenticated) I added an inline policy:

        {
        "Sid": "VisualEditor2",
        "Effect": "Allow",
        "Action": ["s3:GetObject","s3:ListBucket"],
        "Resource": "arn:aws:s3:::IMAGE-BUCKET/*"
        }

I removed the Bucket policy -- this shouldn't be needed, the GET-IMAGE role already has access. Role trust is already included by default. HTML contains the credential: IdentityPoolId: 'us-east-1:9bfadd6a-xxxx-41d4-xxxx-79ad7347xxa1

Those are the most basic components, nothing else should be needed. However, it does not work. I made 1 of the images public and that image is displayed, other images error with 403 Forbidden.

2
Just to clarify, when setting up your identity pool, did you assign the GET-IMAGE role to Unuathorized Identities? So the policies you show at the top were assigned to the Cognito_GET-IMAGEIDUnauth_Role?lemming
The above policies I added to the s3 Bucket Policy (under permissions tab). I also assigned policies to GET-IMAGE for the unauthorized id . Although the principal arn I mentioned above is valid and accepted by the policy, I think the usage may not be correct. I'm researching without much success so far.can.do
Shouldn't your listObjects() method have a params objects as the first parameter?lemming
it's inline. I found the correct format to specify principal,"Principal": { "Federated": "arn:aws:iam::54729999970:role/Cognito_GET-IMAGEIDUnauth_Role" .. that's not working either. It all works if I set Principal: "*" . Also, the bucket policy doesn't like "Conditions" , I've tried different versions of it.can.do

2 Answers

0
votes

You are not defining the trust policy for your unauthenticated role correctly.

As per this documentation on cognito role trust and permissions, the trust policy for an unauthenticated role can be defined as follows:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "",
      "Effect": "Allow",
      "Principal": {
        "Federated": "cognito-identity.amazonaws.com"
      },
      "Action": "sts:AssumeRoleWithWebIdentity",
      "Condition": {
        "StringEquals": {
          "cognito-identity.amazonaws.com:aud": "YOUR_IDENTITY_POOL_ID"
        },
        "ForAnyValue:StringLike": {
          "cognito-identity.amazonaws.com:amr": "unauthenticated"
        }
      }
    }
  ]
}

When you use AWS.CognitoIdentityCredentials, under the hood your Cognito Identity Pool will first get a web identity id for your user. As you don't provide a login with an authenticated token from an identity provider such as Cognito User Pools, or Facebook, the id is for an unauthorized web identity.

Cognito will then call the security token service's assumeRoleWithWebIdentity method on your behalf in order to get credentials with the permissions that you defined in the unauthenticated role's access policy that will allow the web identity to access the s3 bucket.

This is why the principal in the trust policy needs to be cognito-identity.amazonaws.com. It is to give cognito identity pools the permission to call the sts:AssumeRoleWithWebIdentity method on behalf of the web identity in order to obtain IAM credentials.

The access policy part of the role, which defines what unauthenticated users can actually do, will continue to be as you originally defined it in your post:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "AccessS3BucketIMAGEBUCKET",
      "Action": [
        "s3:GetObject",
        "s3:PutObject",
        "s3:ListBucket"
      ],
      "Effect": "Allow",
      "Resource": "*"
    }
  ]
}

Update

I notice that the last inline policy you have posted for your unauthenticated role won't work for s3.listObjects(). It will return a 403 because it needs a slightly different resource statement to indicate the bucket itself, rather than the buckets content.

You can update your policy as follows:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "s3:GetObject"
      ],
      "Resource": [
        "arn:aws:s3:::IMAGE-BUCKET/*"
      ]
    },
    {
      "Effect": "Allow",
      "Action": [
        "s3:ListBucket"
      ],
      "Resource": [
        "arn:aws:s3:::IMAGE-BUCKET"
      ]
    }
  ]
}
0
votes

I've resolved the s3 access issue, I'm including all the settings and methods I used. To troubleshoot, I started testing with an actual AWS user, then stepped back to the cognito identity. I included notes regarding access by an AWS user for reference. I also abondoned the AWS sample HTML code, and used a simple short function to display output in the console, utilizing getsignedurl function. I'm not familiar with AWS libraries, and finding getsignedurl helped speed up testing and finally resolving the issue.

Used the following sample names throughout: Cognito Role: GET-IMAGE S3 Bucket: IMAGE-BUCKET

I'll go over both Cognito and AWS user access to S3, using HTML for simple demo and testing.

With Cognito:

SETTINGS:

Role: Create a Cognito Identity. For instructions and to create, follow this wizard: https://console.aws.amazon.com/cognito/create/ Take a note of the sample code AWS provides after it's created-- you'll need the pool ID.

Permissions: Add Role level AND S3 level permissions

Role level: IAM > Roles > GET-IMAGE_Unauth_Role

Add (JSON) to both Auth and UnAuth Roles

{
"Version": "2012-10-17",
"Statement": [
    {
        "Sid": "s3Access",
        "Effect": "Allow",
        "Action": [
            "s3:GetObject"
        ],
        "Resource": [
            "arn:aws:s3:::IMAGE-BUCKET/*"
        ]}

S3: IMAGE-BUCKET > Permissions > Bucket Policy:

(JSON)

{
"Version": "2012-10-17",
"Statement": [
    {
        "Sid": "PublicRead",
        "Effect": "Allow",
        "Principal": {
            "AWS": [
                "arn:aws:iam::547999998899:user/anAWSUser",
                "arn:aws:iam::547999998899:role/Cognito_GET-IMAGEIDUnauth_Role",
                "arn:aws:iam::547999998899:role/Cognito_GET-IMAGEIDAuth_Role"
            ]
        },
        "Action": [
            "s3:GetObject"
        ],
        "Resource": [
            "arn:aws:s3:::IMAGE-BUCKET/*"
        ]
    }
]}

** Note: I also added an AWSUser, for credential version, for use in the next section "With Credentials"

CORS:

IMAGE-BUCKET> Permissions > CORS

<?xml version="1.0" encoding="UTF-8"?>
<CORSConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
<CORSRule>
<AllowedOrigin>*</AllowedOrigin>
<AllowedMethod>GET</AllowedMethod>
<AllowedMethod>HEAD</AllowedMethod>
<AllowedHeader>*</AllowedHeader>
</CORSRule>
</CORSConfiguration>

**Note: You can restrict the origin in the AllowedOrigin parameter.

HTML:

<!DOCTYPE html>
<html>
<head>
    <script src="https://sdk.amazonaws.com/js/aws-sdk-2.487.0.js"></script>
    <script>
    //   Replace IMAGE-BUCKET with your bucket name.
    var BucketName = 'IMAGE-BUCKET';

    // Cognito credentials (from Cognito ID creation sample code) 
    AWS.config.region = 'us-east-1'; // Region
    AWS.config.credentials = new AWS.CognitoIdentityCredentials({
         IdentityPoolId: 'us-east-1:9999996a-f099-99d4-b999-79a99999aaa1',

    });

    // Create a new service object
    var s3 = new AWS.S3({
            apiVersion: '2006-03-01',
            params: { Bucket: BucketName }
    });

    // Test Function.  REPLACE Key with test file name from s3
function show1() {
  var UrlExpireSeconds = 180 * 1;
  var params = {
  Bucket: BucketName, 
  Key: "20190815_file_name.jpg",    
  Expires: UrlExpireSeconds
               };
var url = s3.getSignedUrl('getObject', params);
console.log('The URL is', url);
document.getElementById('viewer').innerHTML = 
                                '<span>'+
                                '<div>'+
                                '<br/>'+
                                '<img style="width:128px;height:128px;" src="' +
                                url + '"/>' +
                                '</div>'+
                                '<div>'+
                                '<span>'  	
};

	show1();
  	</script>

</head>
<body>
    <h1>S3 Test Image Display</h1>
    <div id="viewer" />
    <button onclick="show1();">View Image</button>
</body>
</html>

With User Credentials

You can also use credentials to authenticate a user to access s3. In the javascript above, comment out the cognito credentials and use the following instead:

//access key ID, Secret
var cred = new AWS.Credentials('AKXXX283988CCCAA-ACCESS-KEY','kKsCuq7a9WNohmOYY8SApewie77493LgV-SECRET');
AWS.config.credentials=cred;

To get the access key and secret from AWS console: IAM > Users > an-AWSUser > Security-Credentials

Under "Access Keys", click "Create Access Key"

===================

Note that the trust policy for an unauthenticated role is automatically created by AWS when you create a Cognito ID Role; it doesn't have to be defined manually as mentioned before.

Also listbucket and bucket level resource permissions, as in "IMAGE-BUCKET", are not required; Getobject is all that's needed to access a file directly. In my case, I'm accessing images by key, do not need to list bucket contents.

I set both the Role and S3 bucket permissions; I did not test without the role permissions, bucket policy may be sufficient.