Jenkins is an open source automation server used to accelerate the software delivery process and has become the de facto DevOps engine, specially for its scripted Jenkinsfile pipelines committed to source control. To accomplish its function, Jenkins needs to interface with some external systems, such as GitHub or, the reason for this article, AWS.
One way to grant Jenkins access to AWS is to run it on an EC2 instance with an attached IAM role. This approach has some pros:
- It’s very easy to setup (you can even find it in the AWS Marketplace)
- No access keys are used, so no keys will ever be exposed
But also some cons:
- Can’t run Jenkins out of AWS
- The attached IAM role must have all the permissions that all the jobs will ever need (how many Jenkins servers end up with PowerUserAccess?)
- All actions / events performed from Jenkins will have the same user name in CloudTrail, regardless of who executed the job
If the cons beat the pros in your case, you can adopt another strategy.
Step 1: Create IAM users and roles
Create one IAM user for each Jenkins user that needs to run AWS-related jobs. The IAM user name should be easily guessed from Jenkins user name (if it can be the same, the better) and only have the following policy attached (more on this later):
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": "sts:AssumeRole", "Resource": "*" } ] }
Create IAM roles (the number and permissions attached to them will depend on your needs: admin, developer, sre, read-only, …) and edit trust relationship in order to allow previously generated users to assume these roles.
Step 2: Generate Access keys
Create AWS access keys for each user and store them in the Jenkins server using the AWS Credentials plugin. Make sure you set an ID to these credentials that can be easily guessed from the user name (as before, if it can be the same, the better).
From this point, we solved all cons stated above:
- Jenkins server can run anywhere
- Jenkins process will assume the role with only the necessary permissions to run the job
- CloudTrail will show the name of the user that run the job
But, let’s face it, Jenkins is far from the most secure tool in the world. So:
- A successful attack could compromise our AWS credentials
- A legitimate user could escalate his privileges (using the credentials from an admin user)
How can we solve this?
Step 3: MFA to the rescue
AWS STS provides two API operations that let users pass MFA information: GetSessionToken and, what we need, AssumeRole.
Assign a MFA device to each user and attach the following policy to each role (those created in step 1):
{ "Version": "2012-10-17", "Statement": [ { "Sid": "ForceMFA", "Effect": "Allow", "Principal": {"AWS": "arn:aws:iam:::root"}, "Action": "sts:AssumeRole", "Condition": { "StringEquals": { "aws:username": [ ] }, "Bool": { "aws:MultiFactorAuthPresent": true } } } ] }
This policy forces the use of MFA to assume the role. Thereby, as credentials can only be used to assume a role (do you remember the only policy attached to the users?), even if they are compromised, an attacker could do little without access to the MFA device.
I’ll explain how to make use of this approach in a Jenkins pipeline in the next post.
👉I hope you’ve enjoyed this post and I encourage you to check our blog for other posts that you might find helpful, such as What is the cloud?
Do not hesitate to contact us if you would like us to help you on your projects.