As part of my new role as the Head of Security, I am responsible for maintaining the credentials and AWS security.

Today I will walk you through one of the best practices for AWS security called the "Key rotation". According to this practice, you need to rotate your keys at least every 90 days, and more often if you can, so that in case of a data leak the credential would already be expired.

As much as you can you are supposed to use Roles on your AWS instances and reduce the usage of API keys as much as possible. Nevertheless, at Nuxeo, some Jenkins are outside AWS and it is still useful to be able to launch some AWS APIs from your laptop from time to time.

Before enforcing any security policy at Nuxeo, I like to put myself in everyone’s shoes and try to make things easy for everyone. So for the above scenario, I decided to find a solution for both Shell and Jenkins key rotation.

The key rotation by itself is a simple process. It includes the following steps:

  1. Create a new AWS key
  2. Store the new AWS key
  3. Remove the old AWS key

AWS CLI

It is always nice to be able to check some Object ACL on your computer when someone asks you to help him/her understand what the issue is with his/her S3 Object

aws --profile=nuxeo s3api get-object-acl --bucket MyBucket --key MyObject

It would be very cool if I could rotate the access key using just one command like:

aws --profile=nuxeo configure rotate-keys

This command, unfortunately, does not exist yet. The Pull request has been done here and awaiting the merge (maybe if you all add a +1, it will speed up the merge process :) )

While I am waiting, I created Yet Another AWS utils: yaws

It is available on pip

pip install yaws

It allows you to do the key rotation by typing:

yaws --profile=nuxeo rotate-keys

And just like that the keys have been updated! You can continue to use your AWS and just need to add a crontab entry.

0 0 * * * yaws --profile=nuxeo rotate-keys

There you go - you are now rotating your keys every hour without even noticing it.

Jenkins

Jenkins usually needs to do some operation on the cloud, such as push an artifact to an S3 bucket or launch an Ansible script.

There is a plugin aws-credentials-plugin that handles the storage of keys for you and also does the assumeRole operation if you specify a roleArn.

So you can have basically one AccessKey that is mapped to several AWS Jenkins credentials with different roles.

But we still need to rotate those AccessKeys, and update all the credentials that use these accessKeys.

For that we can use a Groovy script as follows:

import com.cloudbees.jenkins.plugins.awscredentials.AWSCredentialsImpl;
import com.cloudbees.plugins.credentials.domains.*;

node {
    // Load all the AWSCredentials from Jenkins
    def creds = com.cloudbees.plugins.credentials.CredentialsProvider.lookupCredentials(
        com.cloudbees.jenkins.plugins.awscredentials.AWSCredentialsImpl.class,
        jenkins.model.Jenkins.instance
    )
    // Create a map of AccessKey to Credentials attached to this accessKeys
    def rotate = [:]
    // Building the map
    creds.each {
        if (!rotate[it.accessKey]) {
            rotate[it.accessKey] = []
        }
        rotate[it.accessKey].add(it)
    }
    // For each accessKey
    rotate.each {
       // Rotate the access key
        accessKey = it.value[0].accessKey
        secret = it.value[0].secretKey.getPlainText()
        withEnv(["AWS_ACCESS_KEY_ID=${accessKey}","AWS_SECRET_ACCESS_KEY=${secret}"]) {
            newKeyRaw = sh(returnStdout: true, script: "${aws_bin} iam create-access-key").trim()
            newKey = readJSON(text: newKeyRaw)
            sh(script: "${aws_bin} iam delete-access-key --access-key-id=${accessKey}")
            println("Rotate key ${accessKey} to ${newKey.AccessKey.AccessKeyId}")
        }
        // Now that the accessKey has been rotated on AWS, we need to update all credentials using it
        def credentials_store = jenkins.model.Jenkins.instance.getExtensionList(
            'com.cloudbees.plugins.credentials.SystemCredentialsProvider'
            )[0].getStore()
        it.value.each {
            cred = it
            domain = null
            // Search domain for this credentials : not optimal
            credentials_store.getDomains().each {
                curDomain = it
                credentials_store.getCredentials(it).each {
                    if (it == cred) {
                        domain = curDomain
                    }
                }
            }
            if (curDomain) {
                newIt = new AWSCredentialsImpl(it.scope, it.id, newKey.AccessKey.AccessKeyId, newKey.AccessKey.SecretAccessKey, it.description, it.iamRoleArn, it.iamMfaSerialNumber)
                credentials_store.updateCredentials(curDomain, it, newIt)
            } else {
                println("WARNING: key ${it.id} (${it.AccessKey} failed to be update with ${newKey.AccessKey.AccessKeyId}")
            }
        }
    }
}

The script requires you to be an administrator of Jenkins and not to run it in the Groovy sandbox.

Now all you need to do is create a scheduled job on Jenkins and you are all set!

HashiCorp Vault

Besides what we discussed today, there are other approaches, such as HashiCorp Vault, that can generate throwable credentials for you. It will probably be my subject for a later blog.