Securing Jenkins access to AWS (part II)

Recommended setup

If you followed the steps in my previous post, you have:

  • IAM users than can only assume a role
  • AWS access keys for those users stored in Jenkins (using AWS Credentials plugin)
  • An MFA device assigned to each user
  • A condition that forces MFA when assuming roles

Example pipeline

With all this, we can now run a pipeline that takes advantage of this settings. Something like:

def getAWSUser() {
  wrap([$class: "BuildUser"]) {
    return env.BUILD_USER_ID
  }
}

def getCredentialsId() {
  wrap([$class: "BuildUser"]) {
    return env.BUILD_USER_ID
  }
}

def assumeRole(String credentials, String userName,
  String accountId = "YOUR_AWS_ACCOUNT_ID", String role = "role_to_be_assumed") {
  def String trustedAccount = "YOUR_AWS_ACCOUNT_ID"

  def mfa = input(
    message: "Enter MFA Token",
    parameters: [[$class: 'StringParameterDefinition', name: 'mfa', trim: true]]
  )

  withCredentials([[
    $class: 'AmazonWebServicesCredentialsBinding',
    credentialsId: "${credentials}",
    accessKeyVariable: 'AWS_ACCESS_KEY_ID',
    secretKeyVariable: 'AWS_SECRET_ACCESS_KEY'
  ]]) {
    return sh(script: """
      aws sts assume-role \
        --role-arn arn:aws:iam::${accountId}:role/${role} \
        --serial-number arn:aws:iam::${trustedAccount}:mfa/${userName} \
        --query 'Credentials' \
        --token-code ${mfa} \
        --role-session-name ${userName}
    """, returnStdout: true)
  }
}

pipeline {
  agent any

  stages {
    stage('Setup') {
      steps {
        script {
          AWSUser = getAWSUser()
          credentialsId = getCredentialsId()
        }
      }
    }

    stage('Get credentials') {
      steps {
        script {
          jsonCreds = assumeRole("${credentialsId}", "${AWSUser}")
          creds = readJSON text: "${jsonCreds}"
        }
      }
    }

    stage('Use credentials') {
      steps {
        withEnv([
            "AWS_ACCESS_KEY_ID=${creds.AccessKeyId}",
            "AWS_SECRET_ACCESS_KEY=${creds.SecretAccessKey}",
            "AWS_SESSION_TOKEN=${creds.SessionToken}"
          ]) {
            sh """
              aws sts get-caller-identity
            """
        }
      }
    }
  }
}

When running the pipeline, Jenkins will ask you to enter your MFA code in order to assume the desired role:

And after that, use those credentials to perform whatever AWS-related stuff:

Key points

  • If you followed my advice, your IAM user name and credentials ID will be the same as your Jenkins user name. Otherwise, you’ll have to modify getAWSUser and/or getCredentialsId functions accordingly.
  • This example assumes a privileged role in the same account as the IAM user, but the setup can be used to assume a role in another account. Just pass the destination account ID as a parameter to the assumeRole function call.

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.

See you on the next post!

3 Responses

  1. Hi,
    Thank you for script first of all.
    I have changed in the script account id and assumed role.
    After typing mfa , getting this issue: ERROR: Could not find credentials entry with ID ‘anonymous’
    Could you please advice?

    1. I have solved first issue: Now this one is appears: hudson.remoting.ProxyException: net.sf.json.JSONException: Invalid JSON String

  2. Without more information, it looks like

    readJSON text: “${jsonCreds}”

    is trying to parse a non-json response. Can you take a look at the result of the “aws sts assume-role” command in your environment?

Leave a Reply

Your email address will not be published. Required fields are marked *