Credentials Best Practices
Your Amazon Web Services account is (we hope!) pretty important to you. Whether you’re running mission-critical applications that need to be protected from malicious interlopers, or you simply want to ensure that only the people you specify can bill resources to your AWS account, it is vital that you keep your account and its associated AWS resources secure.
The client libraries in the AWS SDK for Java almost universally require you to specify a set of AWS security credentials to use when making service requests. These credentials prove that your application has permission to perform any requested actions, keeping your account safe from attackers who do not know these credentials.
This blog post is the first in a series in which we will discuss best practices around managing and securing your security credentials and explore features of the AWS SDK for Java that help you do so easily. In this post, I’ll quickly lay out an easy way to securely use credentials with the AWS SDK for Java from applications running on Amazon EC2. In subsequent posts, we’ll dive a bit deeper into some of the principles that make this approach secure and discuss some of the more advanced scenarios enabled by the SDK.
Identity and Access Management
Credentials for programmatic access to the AWS APIs come in the form of an Access Key ID (or “access key”) and a Secret Access Key (or “secret key”). Similar to the familiar concepts of a username and password, the access key identifies who is making the call, while the secret key proves that the caller is actually who they say they are.
Identity — who your callers are — and access management — what those callers are allowed to do — for your account are managed through the appropriately-named AWS Identity and Access Management (IAM) service. This service lets you define “users” (representing either actual human users or autonomous software applications), configure policies granting different permissions to individual users or groups of users, and manage sets of credentials that can be used to authenticate as a particular user.
Configuring Credentials Using the AWS SDK for Java
Alright, let’s see some code! In the AWS SDK for Java, the client objects you use to interact with individual services each get credentials to sign their requests using an implementation of the AWSCredentialsProvider interface. When the client makes a request, it internally calls the
getCredentials()method on its
AWSCredentialsProviderinstance to retrieve an appropriate set of credentials to use to sign the request. The SDK provides a number of different implementations of this interface, which attempt to retrieve credentials from various different places.
To set the credentials provider used by a client instance, just pass it to the constructor:
AWSCredentialsProvider provider = ...; // This client will authenticate using credentials from the given provider. AmazonDynamoDBClient client = new AmazonDynamoDBClient(provider);
IAM Roles for Amazon EC2
If your application runs on Amazon EC2 instances, a really great way to get a set of credentials to use is via IAM Roles for Amazon EC2. A “role” is an IAM concept similar to a user, but without permanent credentials associated with it. Instead, a user or application can be given permission to “assume” a role, retrieving a temporary set of credentials that allow it to perform actions that the role’s policy allows.
When launching an EC2 instance, you can choose to associate it with an IAM role. Any application running on that EC2 instance is then allowed to assume the associated role. Amazon EC2 handles all the legwork of securely authenticating instances to the IAM service to assume the role and periodically refreshing the retrieved role credentials, keeping your application super-secure with almost no work on your part.
Using credentials for a role associated with your EC2 Instance from the AWS SDK for Java is super easy — just use the
AWSCredentialsProvider provider = new InstanceProfileCredentialsProvider(); AmazonDynamoDBClient client = new AmazonDynamoDBClient(provider);
In fact, if you use one of the client constructors that do not take an
AWSCredentialsProvider, the client will also use an
InstanceProfileCredentialsProvider(after first checking for overrides specified via an environment variable or system properties).
Associating an EC2 Instance with a Role
It’s really easy to create a role and associate it with your EC2 instances via the AWS Management Console (there’s a nice walkthrough here, but if you want to do so programmatically that’s no sweat either. First, we’ll create a new role. We configure it to allow Amazon EC2 to assume the role on our instances’ behalf, and give the role permission to access a particular S3 bucket (for the sake of example). This probably only needs to be done once when initially setting up your application, after which we can keep reusing the same role for any new EC2 instances we launch.
// Requires credentials for an administrative user. AmazonIdentityManagement iam = new AmazonIdentityManagementClient(...); iam.createRole(new CreateRoleRequest() .withRoleName("test-role") .withAssumeRolePolicyDocument(new Policy() .withStatements(new Statement(Effect.Allow) .withActions(STSActions.AssumeRole) .withPrincipals(new Principal(Services.AmazonEC2))) .toJson())); iam.putRolePolicy(new PutRolePolicyRequest() .withRoleName("test-role") .withPolicyName("allow-s3-read") .withPolicyDocument(new Policy() .withStatements(new Statement(Effect.Allow) .withActions(S3Actions.GetObject) .withResources(new S3ObjectResource("top-secret-bucket", "*"))) .toJson()));
Next, we create an instance profile and add the role to it. An instance profile allows you to associate multiple roles with a particular instance, which is useful in some advanced scenarios. For now, we’ll just add a single role. Like creating a role, this probably only needs to be done once when initially configuring your application.
iam.createInstanceProfile(new CreateInstanceProfileRequest() .withInstanceProfileName("test-instance-profile")); iam.addRoleToInstanceProfile(new AddRoleToInstanceProfileRequest() .withInstanceProfileName("test-instance-profile") .withRoleName("test-role"));
Once our role and instance profile are set up, we can launch new Amazon EC2 instances associated with our newly-created instance profile, making credentials available to any applications running on the instance via
// Requires credentials for a user (or role) with permission to launch EC2 // instances AND pass roles. See http://docs.aws.amazon.com/IAM/latest/UserGuide/ // role-usecase-ec2app.html#role-usecase-ec2app-permissions for an example. AmazonEC2 ec2 = new AmazonEC2Client(...); ec2.runInstances(new RunInstancesRequest() .withInstanceType(InstanceType.T1Micro) .withImageId("ami-d03ea1e0") // 64-bit Amazon Linux AMI 2013.09 .withIamInstanceProfile(new IamInstanceProfileSpecification() .withName("test-instance-profile")) .withMinCount(1) .withMaxCount(1));
Instance profiles can also be associated with EC2 instances created indirectly via Auto Scaling, AWS Elastic Beanstalk, AWS CloudFormation, and AWS OpsWorks!
That’s all for now! Stay tuned till next time, when we’ll talk in a bit more depth about the principles behind some of the important things that IAM roles for EC2 Instances takes care of for you under the covers, and discuss ways to apply those same principles to securely handle credentials for applications that aren’t running on EC2 instances.
Already using roles for EC2? Let us know what you think in the comments!