Least Privileged Access with Terraform
AWS, like most security-minded organizations, strongly recommends following the security practice of least privileged. In some cases, this can be a simple task. A handful of instances may only need to receive HTTPS traffic from an Elastic Load Balancer (ELB), a Relational Database RDS cluster should only be accepting requests from the application servers, etc.
Least privilege can become more complicated when you add automation into the mix. The inclination is to provide Terraform users with all the permissions for everything because, well, that account needs to be able to build anything and everything, right? Not so fast. Let’s look at a couple of strategies to minimize the blast radius of a disaster.
Many cloud engineers will make use of the administrative privileges granted by the ‘AdministratorAccess’ permissions policies. This policy will be attached to a group or (even worse) a handful of IAM users who have programmatic keys. Over the years I’ve seen countless AWS customers rely on such a group for day-to-day activities. We know it’s wrong, time to stop doing it. Programmatic keys can only be deleted manually. AWS won’t take care of this for you. This is outlined in the Shared Responsibility Model for AWS: the customer oversees security in the cloud. Key rotation is a conversation for another time. When keys are compromised without a rotation schedule, bad actors can use them indefinitely until someone notices and disables the keys. How do we overcome this?
Just like IAM users, roles are identities to which permissions can be attached. The primary difference is that rather than mapping a role directly to a user account, they can instead be assumed by other users for a specific duration (AWS is one hour by default). When a user assumes permissions, they are trading in their permissions (temporarily) for the permissions of the role. This action is performed by the Security Token Service (STS) in AWS. When working in the AWS CLI, a command to assume a role would look like this:
aws sts assume-role –role-arn arn:aws:iam::123:role/terraform_admin –role-session-name terraform_lab –external-id aws-is-cool
The request above provides the Amazon Resource Number (ARN) of a role named “terraform_admin”. The session name “terraform_lab” is a description field. Finally, the external-id “aws-is-cool” is an optional form of additional security. Anyone who wishes to assume this role will need to supply the external-ID. The external-ID is defined upon role creation in AWS.
Once you press enter, you’ll notice that a new set of programmatic keys are generated along with their expiration time. This is great! If these keys are exposed, they are only good for an hour (by default) therefore limiting the hole in the ground if something goes sideways. You now have temporary access to make API calls that your IAM account doesn’t have rights to do. Again, admins will need to know both the name of the role as well as the external-ID. Using this process, IAM users only need one permission. sts:AssumeRole
Now that we’ve discussed STS and how we assume a role, let’s talk about Terraform.
By default, Terraform will use the programmatic keys generated for an IAM account to make API calls. We don’t want to have to manually update variables every hour (or every time we apply Terraform code) so we will login with our IAM credentials first. From here, we can rely on the AWS provider and S3 backend configurations for assuming roles.
The “terraform_admin” role is defined in two places. Why? Terraform is used for more than just AWS services. In this case, provisioning resources is different than saving state. Knowing this, we can create a terraform_state and a terraform_provisioning role. The terraform_state role only needs access to save the state to an S3 bucket (with versioning, of course!). The other role, terraform_provisioning, can be limited to CRUD permissions for the services that are required. (EKS, Lambda, EC2, etc)
So, you end up with something like this instead:
In conclusion, AWS and Terraform offer good features to practice least privilege with Infrastructure as Code. By utilizing these methods, sessions are limited to the lifetime of the token and you avoid providing administrative permissions to individual users. In the event that these temporary credentials are compromised, the short life span ensures that bad actors cannot remain within your tenant.
As a longtime partner of AWS, ivision has the experience and expertise to champion cloud security for your organization. To learn more, check out our cloud solutions.