Integrating Container Security in CI/CD Pipelines: Technical How-to with Jenkins guide

Share
Share

In today’s fast-paced software development world, Continuous Delivery (CD) is no longer a luxury but a necessity. While containers have revolutionized cloud-native applications, enabling rapid feature delivery and efficient infrastructure, their agility can also inadvertently introduce vulnerabilities into production if security isn’t proactively addressed.

This blog post will guide you through establishing a security-first approach within your Jenkins environment, emphasizing the critical role of SUSE Security in safeguarding your containerized applications. We’ll explore key strategies for integrating security scanning and other essential measures at every development stage, building a practical CI/CD pipeline that prioritizes security from its inception to deployment. You’ll learn how to seamlessly integrate SUSE Security APIs with Jenkins for automated vulnerability scanning, ensuring robust supply chain protection and a truly secure development lifecycle.


Setting Up Your Jenkins Lab

Before we dive into building our pipeline, let’s get our environment ready. We will install Jenkins into a virtual machine, no needs to be running on Kubernetes cluster. Here’s what you’ll need:

Lab Composition

  • Cloud instance or on-premise virtual machine: Recommended specifications are 2 CPUs and 4GB of RAM.
  • Production Registry: We’ll be using Harbor for this.
  • DockerHub Account: For pushing and pulling Docker images.
  • Running Kubernetes Cluster: Where your application will be deployed.

Preview the demo before proceed

https://youtu.be/NGQ6tHZvdbM

 

Jenkins Installation Procedure

Let’s get Jenkins up and running on your Linux machine.

  1. Install JDK: Jenkins requires Java to run.
    sudo apt-get update
    sudo apt install openjdk-17-jdk
    
  2. Install Jenkins:
    sudo wget -O /usr/share/keyrings/jenkins-keyring.asc \
      https://pkg.jenkins.io/debian-stable/jenkins.io-2023.key
    echo deb [signed-by=/usr/share/keyrings/jenkins-keyring.asc] \
      https://pkg.jenkins.io/debian-stable binary/ | sudo tee \
      /etc/apt/sources.list.d/jenkins.list > /dev/null
    sudo apt-get install jenkins
    

     

  3. Verify that Jenkins is running by checking its status:
    systemctl status jenkins
    
  4. Install Docker:
    curl -fsSL https://get.docker.com | bash
    usermod -aG docker jenkins
    systemctl restart jenkins
    

     

  5. Install kubectl:
    sudo apt-get update
    curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl"
    sudo install -o root -g root -m 0755 kubectl /usr/local/bin/kubectl
    

 

Initial Jenkins Configuration

Now that Jenkins is installed, access its web interface by navigating to YOUR_IP:8080 in your browser.

  1. Unlock Jenkins: You’ll need an initial token to unlock Jenkins. Retrieve it by running:
    cat /var/lib/jenkins/secrets/initialAdminPassword
    

    Enter this token in the Jenkins setup wizard and proceed to create your administrator username and password.

  2. Install Plugins: On the next page, select “Install suggested plugins” and ensure all default plugins are installed.
  3. Install Additional Plugins: Go to “Manage Jenkins” > “Manage Plugins” and install the following:
    • Docker Pipeline
    • Docker
    • Kubernetes CLI
    • Neuvector PipelineSelect “Download now and restart after installation.” Jenkins will restart automatically; if not, refresh the page.

With these steps, your Jenkins environment is fully configured and ready for pipeline creation!


Building Your First Jenkins Pipeline

We’ll now create a Jenkins pipeline that demonstrates a common CI/CD workflow: building a Docker image, pushing it to an intermediate registry for vulnerability scanning, and then pushing it to a production registry and deploying it to Kubernetes, but only if no vulnerabilities are found.

 

Pipeline Goal

The example pipeline aims to:

  1. Build a simple Docker image.
  2. Push the image to an intermediate registry (e.g., Docker Hub) for vulnerability scanning with SUSE Security.
  3. Proceed to push the image to a production registry (e.g., Harbor) only if no vulnerabilities are detected.
  4. Finally, deploy the application successfully to your Kubernetes cluster.

 

Procedure

  1. Clone the Example Repository:Start by cloning the example repository to your local machine and then push it to your own repository (e.g., on GitHub or GitLab).
    git clone https://github.com/gbrlins/api-produto.git
    
  2. Create the Jenkinsfile:Inside the cloned repository, you’ll find the Jenkinsfile. This file defines your pipeline’s stages and steps. Here’s the complete example:
    pipeline {
        agent any
    
        stages {
            stage ('Build image locally...') {
                steps {
                    script {
                        dockerapp = docker.build("gabriellins/api-produto:latest", '-f ./src/Dockerfile ./src')
                        //dockerapp = docker.build("gabriellins/api-produto:${env.BUILD_ID}", '-f ./src/Dockerfile ./src')
                    }
                }
            }
    
            stage ('Send image to DEV registry') {
                steps {
                    script {
                        docker.withRegistry('https://registry.hub.docker.com', 'cred-dockerhub') {
                            dockerapp.push('latest')
                            //dockerapp.push("${env.BUILD_ID}")
                        }
                    }
                }
            }
    
            stage ('Scan image in the DEV registry with Neuvector') {
                steps {
                    script {
                        //Analyze vulnerabilities before pushing to the production registry. If any vulnerability is found.
    
                        neuvector nameOfVulnerabilityToExemptFour: '', nameOfVulnerabilityToExemptOne: '', nameOfVulnerabilityToExemptThree: '', nameOfVulnerabilityToExemptTwo: '', nameOfVulnerabilityToFailFour: '', nameOfVulnerabilityToFailOne: '', nameOfVulnerabilityToFailThree: '', nameOfVulnerabilityToFailTwo: '', numberOfHighSeverityToFail: '1', numberOfMediumSeverityToFail: '', registrySelection: 'DockerHub', repository: 'gabriellins/api-produto', scanLayers: true, scanTimeout: 10, tag: 'latest'
                    }
                }
            }
    
            stage ('Send image to production registry') {
                steps {
                    script {
                        docker.withRegistry('https://harbor.geekoworld.com/pipeline-images/', 'cred-harbor') {
                            dockerapp.push('latest')
                            //dockerapp.push("${env.BUILD_ID}")
                        }
                    }
                }
            }
    
            stage ('Deploy app in kubernetes cluster "geeko-dev"') {
                environment {
                    tag_version = "latest"
                    //tag_version = "${env.BUILD_ID}"
                }
                steps {
                    withKubeConfig([credentialsId: 'kubeconfig-geeko-dev']) {
                        sh 'sed -i "s/{{tag}}/$tag_version/g" ./k8s/deployment.yaml'
                        sh 'kubectl apply -f ./k8s/deployment.yaml -n ci-cd'
                    }
                }
            }
        }
    }
    
  3. Create a New Jenkins Job:
    • In Jenkins, click on “New Item” (or “New Job”).
    • Enter a name for your job (e.g., “api-produto-pipeline”) and choose the “Pipeline” option. Click “OK.”
  4. Configure the Pipeline Job:
    • Scroll down to the “Pipeline” section.
    • For “Definition,” select “Pipeline script from SCM.”
    • For “SCM,” choose “Git.”
    • In “Repository URL,” add the URL of your Git repository where you pushed the api-produto project.
    • In “Branch Specifier,” enter */master (or the branch you’re using).
    • Ensure “Script Path” is set to Jenkinsfile.
    • Click “Save.”

 

Addressing Pipeline Errors: Credential Configuration

If you try to run the newly created pipeline now by clicking “Build Now,” you’ll encounter errors. This is because the example Jenkinsfile uses placeholder values and references credentials that don’t yet exist in your Jenkins environment. Let’s fix that.

You need to create/modify the following variables/credentials:

  • dockerapp = docker.build("gabriellins/api-produto:latest", '-f ./src/Dockerfile ./src')
    • Action: Change gabriellins to your Docker Hub username.
  • cred-dockerhub:
    • Action: Go to “Manage Jenkins” > “Manage Credentials” > “Jenkins” (global) > “Add Credentials.”
    • Kind: Select “Username with password.”
    • Enter your Docker Hub username and password.
    • ID: Use cred-dockerhub (or update the Jenkinsfile if you use a different ID).
  • docker.withRegistry('https://harbor.geekoworld.com/pipeline-images/', 'cred-harbor'):
    • Action: Change the Harbor URL (https://harbor.geekoworld.com/pipeline-images/) to your production registry’s URL.
    • Action: Create a new credential for Harbor (or your production registry). Go to “Manage Jenkins” > “Manage Credentials” > “Jenkins” (global) > “Add Credentials.”
    • Kind: Select “Username with password.”
    • Enter your Harbor (or other registry) username and password.
    • ID: Use cred-harbor (or update the Jenkinsfile if you use a different ID).
  • withKubeConfig([credentialsId: 'kubeconfig-geeko-dev']):
    • Action: Create a new credential for your Kubernetes cluster’s kubeconfig file. Go to “Manage Jenkins” > “Manage Credentials” > “Jenkins” (global) > “Add Credentials.”
    • Kind: Select “Secret file.”
    • File: Upload your Kubernetes kubeconfig file.
    • ID: Use kubeconfig-geeko-dev (or update the Jenkinsfile if you use a different ID).

 

Next Steps and Future Enhancements

This example provides a solid foundation for a practical CI/CD pipeline, addressing common needs for developers. Here are some ideas for adapting and enhancing this setup:

  • Explore BCI Images SUSE: Consider using SUSE’s Base Container Images (BCI) for building your application images. These provide a secure and reliable foundation.
  • More Complex Applications: Adapt the pipeline to deploy more sophisticated applications, potentially involving multiple services or dependencies.
  • Blue/Green Deployments with Istio: A fantastic next step is to integrate your pipeline with Istio to implement advanced deployment strategies like Blue/Green deployments, enabling zero-downtime updates and easy rollbacks.

We hope this guide helps you get started with building powerful and efficient CI/CD pipelines using Jenkins!

Share
(Visited 1 times, 1 visits today)
Avatar photo
136 views
Gabriel Lins Gabriel Lins is a Solutions Architect at SUSE in Brazil. In this role, he supports the public sector in achieving digital transformation through the company's portfolio and strategies.