Get in touch

    Back to top

    Integrating AWS CodeCommit with Jenkins on EKS

    In this blog post, we will go through a quick rundown on how to enable AWS CodeCommit integration with Jenkins hosted on an AWS EKS cluster. One key element to highlight is that there are no long lived credentials (such as IAM user API keys) used in this solution. Instead, temporary security credentials are used to obtain relevant permissions by using IAM roles for Kubernetes service accounts. Hence, this solution is more secure and easier to automate since there is no requirement to implement a mechanism for managing API access credentials.

    Design Overview

    The solution uses a helm chart to deploy Jenkins in an AWS EKS cluster that is configured to grant access to a AWS CodeCommit repository. Although you can configure Jenkins to access AWS CodeCommit repositories in other AWS accounts (cross account), for this given example, all resources are deployed in a single AWS account. All Jenkins jobs are run in agent pods which are dynamically created within the EKS cluster. These agents uses a Kubernetes service account that is associated to an IAM role which grants relevant permissions to access the AWS CodeCommit repository. The pod configuration for each dynamic agent is managed via Kubernetes plugin for Jenkins.

    Integrate AWS CodeCommit with Jenkins on EKS HLD

    Setup

    Detailed information of each component used in the solution is described below. Terraform code snippets are also provided to highlight key property values of each resource.

    1. Create an EKS cluster with either EKS managed or self managed nodes. In this example we will be using EKS managed nodes. EKS cluster version must be 1.14 or later which is pre-requisite for Kubernetes plugin for Jenkins. Please note that public access for cluster endpoint should not be enabled for productionised deployments. It is also recommended to provision the cluster control plane and worker nodes in private subnets. Ensure relevant VPC NACL rules are configured to allow traffic communication between cluster and worker nodes if different subnet tiers are used to provision the EKS cluster control plane and worker nodes. For more information regarding productionising AWS EKS cluster deployments, please refer to documentation on security best practices for AWS EKS.
    module "eks" {
      source  = "terraform-aws-modules/eks/aws"
      version = "~> 19.0"
    
      cluster_addons = {
        aws-ebs-csi-driver = {
          most_recent = true
        }
        coredns = {
          most_recent = true
        }
        kube-proxy = {
          most_recent = true
        }
        vpc-cni = {
          most_recent = true
        }
      }
    
      cluster_name    = var.eks_cluster_name
      cluster_version = var.eks_cluster_version
    
      cluster_endpoint_public_access  = true
    
      iam_role_name = "${var.eks_cluster_name}-eks-cluster-${var.aws_region}"
      iam_role_use_name_prefix = false
    
    
      vpc_id                   = data.aws_vpc.eks.id
      subnet_ids               = data.aws_subnets.eks_worker_nodes.ids
      control_plane_subnet_ids = data.aws_subnets.eks_control_plane.ids
    
      # EKS Managed Node Group(s)
      eks_managed_node_group_defaults  = {
        iam_role_additional_policies    = {
          AmazonEBSCSIDriverPolicy = "arn:aws:iam::aws:policy/service-role/AmazonEBSCSIDriverPolicy"
        }
        iam_role_name                   = "${var.eks_cluster_name}-eks-workers-${var.aws_region}"
        iam_role_use_name_prefix        = false
        launch_template_use_name_prefix = false
        use_name_prefix = false
      }
      eks_managed_node_groups = {
        default = {
          name                 = "${var.eks_cluster_name}-eks-node-group-default"
          min_size             = 1
          max_size             = 6
          desired_size         = 3
          instance_types       = ["t3.xlarge"]
          launch_template_name = "${var.eks_cluster_name}-eks-node-group-default"
        }
      }
    
      # aws-auth configmap
      manage_aws_auth_configmap = true
    }
    1. Create a CodeCommit repository which will be used to store sample app code.
    resource "aws_codecommit_repository" "sample_app" {
      repository_name = var.codecommit_repo_name
      description     = "This is the sample app repository"
    }
    1. Create IAM roles for both Jenkins controller and Jenkins agent which has the following properties configured:
      • Grants sufficient IAM permissions to access AWS CodeCommit repo created in step 2. You may add additional IAM permissions to Jenkins agent IAM role to perform other tasks as required.
      • Assume role policy is configured to allow Jenkins controller & Jenkins agent Kubernetes service accounts which are created in specified Kubernetes namespace within EKS cluster.

    You can also refer to AWS documentation to get more information on how to configure a Kubernetes service account to assume an IAM role.

    resource "aws_iam_role" "eks_jenkins" {
      name        = local.jenkins_iam_role_name
      description = "IAM role for EKS Jenkins service account in ${var.aws_region} region."
    
      assume_role_policy = jsonencode({
        Version = "2012-10-17"
        Statement = [
          {
            Action = "sts:AssumeRoleWithWebIdentity"
            Effect = "Allow"
            Principal = {
              Federated = module.eks.oidc_provider_arn
            }
            Condition = {
              StringEquals = {
                "${replace(module.eks.cluster_oidc_issuer_url, "/http[s]?:\\/\\//", "")}:aud" = "sts.amazonaws.com"
                "${replace(module.eks.cluster_oidc_issuer_url, "/http[s]?:\\/\\//", "")}:sub" = "system:serviceaccount:${var.k8_ns_jenkins}:${var.k8_sa_name_jenkins}"
              }
            }
          },
        ]
      })
      force_detach_policies = true
    }
    
    resource "aws_iam_role_policy" "eks_jenkins_access_codecommit" {
      name = "CodeCommitRepoAccess"
      role = aws_iam_role.eks_jenkins.id
      policy = jsonencode({
        Version = "2012-10-17"
        Statement = [
          {
            Action = [
              "codecommit:BatchGet*",
              "codecommit:BatchDescribe*",
              "codecommit:Describe*",
              "codecommit:EvaluatePullRequestApprovalRules",
              "codecommit:Get*",
              "codecommit:List*",
              "codecommit:GitPull"
            ]
            Effect = "Allow"
            Resource = [
              aws_codecommit_repository.sample_app.arn
            ]
          },
        ]
      })
    }
    
    resource "aws_iam_role" "eks_jenkins_agent" {
      name        = local.jenkins_agent_iam_role_name
      description = "IAM role for EKS Jenkins agent service account in ${var.aws_region} region."
    
      assume_role_policy = jsonencode({
        Version = "2012-10-17"
        Statement = [
          {
            Action = "sts:AssumeRoleWithWebIdentity"
            Effect = "Allow"
            Principal = {
              Federated = module.eks.oidc_provider_arn
            }
            Condition = {
              StringEquals = {
                "${replace(module.eks.cluster_oidc_issuer_url, "/http[s]?:\\/\\//", "")}:aud" = "sts.amazonaws.com"
                "${replace(module.eks.cluster_oidc_issuer_url, "/http[s]?:\\/\\//", "")}:sub" = "system:serviceaccount:${var.k8_ns_jenkins}:${var.k8_sa_name_jenkins_agent}"
              }
            }
          },
        ]
      })
      force_detach_policies = true
    }
    
    resource "aws_iam_role_policy" "eks_jenkins_agent_access_codecommit" {
      name = "CodeCommitRepoAccess"
      role = aws_iam_role.eks_jenkins_agent.id
      policy = jsonencode({
        Version = "2012-10-17"
        Statement = [
          {
            Action = [
              "codecommit:BatchGet*",
              "codecommit:BatchDescribe*",
              "codecommit:Describe*",
              "codecommit:EvaluatePullRequestApprovalRules",
              "codecommit:Get*",
              "codecommit:List*",
              "codecommit:GitPull"
            ]
            Effect = "Allow"
            Resource = [
              aws_codecommit_repository.sample_app.arn
            ]
          },
        ]
      })
    }
    1. Deploy Jenkins helm chart to the EKS cluster with the following configuration provided via a values file.
      • Create service accounts for both Jenkins controller and Jenkins agent which has annotations to associate corresponding IAM service roles.
      • Install AWS CodeCommit helper plugin which is used to configure GIT credentials without having to customise ~/gitconfig for usage with the awscli tool. In addition, you can install AWS credentials plugin which can be used to access AWS CodeCommit repos in other AWS accounts by assuming IAM roles in respective accounts.
      • Configure Jenkins Pipeline job which uses CodeCommit helper plugin to clone AWS CodeCommit repo.
    resource "kubernetes_namespace" "jenkins" {
      metadata {
        name = var.k8_ns_jenkins
      }
      depends_on = [
        module.eks,
      ]
    }
    
    resource "helm_release" "jenkins" {
      name       = "jenkins"
      repository = "https://charts.jenkins.io"
      chart      = "jenkins"
      namespace  = var.k8_ns_jenkins
      atomic     = true
      values     = [
        templatefile(
          "${path.module}/templates/helm/jenkins-values.yml.tftpl",
          {
            jenkins_sa_name = var.k8_sa_name_jenkins
            jenkins_agent_sa_name = var.k8_sa_name_jenkins_agent
            jenkins_iam_role_arn = aws_iam_role.eks_jenkins.arn
            jenkins_agent_iam_role_arn = aws_iam_role.eks_jenkins_agent.arn
            pipeline_job_name = var.jenkins_pipeline_job_name
            pipeline_repo_url = aws_codecommit_repository.sample_app.clone_url_http
            pipeline_repo_branch = var.jenkins_pipeline_repo_branch
          }
        )
      ]
      depends_on = [
        kubernetes_namespace.jenkins,
      ]
    }

    The content of Jenkins helm values template is given below:

    ---
    serviceAccount:
      create: true
      name: ${jenkins_sa_name}
      annotations:
        eks.amazonaws.com/role-arn: ${jenkins_iam_role_arn}
    
    serviceAccountAgent:
      create: true
      name: ${jenkins_agent_sa_name}
      annotations:
        eks.amazonaws.com/role-arn: ${jenkins_agent_iam_role_arn}
    
    controller:
      installLatestPlugins: true
      additionalPlugins:
        - pipeline-model-definition
        - generic-webhook-trigger
        - aws-credentials
        - pipeline-aws
        - codecommit-url-helper
        - job-dsl
        - ssh-agent
        - pipeline-utility-steps
        - junit
    
      JCasC:
        enabled: true
        configScripts:
          pipelines: |
            jobs:
              - script: >
                  pipelineJob("${pipeline_job_name}") {
                    definition {
                      cpsScm {
                        scm {
                          git {
                            remote {
                              url("${pipeline_repo_url}")
                            }
                            branches("${pipeline_repo_branch}")
                            extensions {
                              codeCommitURLHelper {
                                credentialId('')
                                repositoryName('')
                              }
                            }
                          }
                        }
                        scriptPath("Jenkinsfile")
                      }
                    }
                  }

    Demo

    Checkout the live demo given below which uses a sample codebase in our GitHub repository to demonstrate integration of AWS CodeCommit with Jenkins on EKS.

    Please reach out if you want to know more about our AWS cloud DevSecOps solutions and best practices.