9
votes

The goal

I want to programmatically add an item to a table in my DynamoDB from my Elastic Beanstalk application, using code similar to:

Item item = new Item()
    .withPrimaryKey(UserIdAttributeName, userId)
    .withString(UserNameAttributeName, userName);

table.putItem(item);

The unexpected result

Logs show the following error message, with the [bold parts] being my edits:

User: arn:aws:sts::[iam id?]:assumed-role/aws-elasticbeanstalk-ec2-role/i-[some number] is not authorized to perform: dynamodb:PutItem on resource: arn:aws:dynamodb:us-west-2:[iam id?]:table/PiggyBanks (Service: AmazonDynamoDBv2; Status Code: 400; Error Code: AccessDeniedException; Request ID: [the request id])

I am able to get the table just fine, but things go awry when PutItem is called.

The configuration

I created a new Elastic Beanstalk application. According to the documentation, this automatically assigns the application a new role, called:

aws-elasticbeanstalk-service-role

That same documentation indicates that I can add access to my database as follows:

Add permissions for additional services to the default service role in the IAM console.

So, I found the aws-elasticbeanstalk-service-role role and added to it the managed policy, AmazonDynamoDBFullAccess. This policy looks like the following, with additional actions removed for brevity:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Action": [
        "dynamodb:*",
        [removed for brevity]
        "lambda:DeleteFunction"
      ],
      "Effect": "Allow",
      "Resource": "*"
    }
  ]
}

This certainly looks like it should grant the access I need. And, indeed, the policy simulator verifies this. With the following parameters, the action is allowed:

  • Role: aws-elasticbeanstalk-service-role
  • Service: DynamoDB
  • Action: PutItem
  • Simulation Resource: [Pulled from the above log] arn:aws:dynamodb:us-west-2:[iam id?]:table/PiggyBanks

Update

In answer to the good question by filipebarretto, I instantiate the DynamoDB object as follows:

private static DynamoDB createDynamoDB() {
    AmazonDynamoDBClient client = new AmazonDynamoDBClient();
    client.setRegion(Region.getRegion(Regions.US_WEST_2));

    DynamoDB result = new DynamoDB(client);
    return result;
}

According to this documentation, this should be the way to go about it, because it is using the default credentials provider chain and, in turn, the instance profile credentials,

which exist within the instance metadata associated with the IAM role for the EC2 instance.

[This option] in the default provider chain is available only when running your application on an EC2 instance, but provides the greatest ease of use and best security when working with EC2 instances.

Other things I tried

This related Stack Overflow question had an answer that indicated region might be the issue. I've tried tweaking the region with no additional success.

I have tried forcing the usage of the correct credentials using the following:

    AmazonDynamoDBClient client = new AmazonDynamoDBClient(new InstanceProfileCredentialsProvider());

I have also tried creating an entirely new environment from within Elastic Beanstalk.

In conclusion

By the error in the log, it certainly looks like my Elastic Beanstalk application is assuming the correct role.

And, by the results of the policy simulator, it looks like the role should have permission to do exactly what I want to do.

So...please help!

Thank you!

2
How are you instantiating your DynamoDB object and setting up to use credentials from the ec2 role on your code? - filipebarretto
Updated the question to contain those details. Thank you for the good question! - Dan
I posted as an answer a suggestion to force loading the EC2 credentials according to the default credentials provider chain. See if that works for you =) - filipebarretto
Thank you -- I have already tried to use the following, but yours differs and I'll gladly give it a shot. AmazonDynamoDBClient client = new AmazonDynamoDBClient(new InstanceProfileCredentialsProvider()); I updated the question to reflect this. - Dan
Ok! Let me know if it works out =) - filipebarretto

2 Answers

11
votes

Update the aws-elasticbeanstalk-ec2-role role, instead of the aws-elasticbeanstalk-service-role.

This salient documentation contains the key:

When you create an environment, AWS Elastic Beanstalk prompts you to provide two AWS Identity and Access Management (IAM) roles, a service role and an instance profile. The service role is assumed by Elastic Beanstalk to use other AWS services on your behalf. The instance profile is applied to the instances in your environment and allows them to upload logs to Amazon S3 and perform other tasks that vary depending on the environment type and platform.

In other words, one of these roles (-service-role) is used by the Beanstalk service itself, while the other (-ec2-role) is applied to the actual instance.

It's the latter that pertains to any permissions you need from within your application code.

6
votes

To load your credentials, try:

InstanceProfileCredentialsProvider mInstanceProfileCredentialsProvider = new InstanceProfileCredentialsProvider();
AWSCredentials credentials = mInstanceProfileCredentialsProvider.getCredentials();

AmazonDynamoDBClient client = new AmazonDynamoDBClient(credentials);

or

AmazonDynamoDBClient client = new AmazonDynamoDBClient(new DefaultAWSCredentialsProviderChain());