Integrating Container Security in CI/CD Pipelines: Technical How-to with Jenkins guide
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
Jenkins Installation Procedure
Let’s get Jenkins up and running on your Linux machine.
- Install JDK: Jenkins requires Java to run.
sudo apt-get update sudo apt install openjdk-17-jdk
- 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
- Verify that Jenkins is running by checking its status:
systemctl status jenkins
- Install Docker:
curl -fsSL https://get.docker.com | bash usermod -aG docker jenkins systemctl restart jenkins
- 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.
- 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.
- Install Plugins: On the next page, select “Install suggested plugins” and ensure all default plugins are installed.
- 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:
- Build a simple Docker image.
- Push the image to an intermediate registry (e.g., Docker Hub) for vulnerability scanning with SUSE Security.
- Proceed to push the image to a production registry (e.g., Harbor) only if no vulnerabilities are detected.
- Finally, deploy the application successfully to your Kubernetes cluster.
Procedure
- 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
- 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' } } } } }
- 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.”
- 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.
- Action: Change
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 theJenkinsfile
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 theJenkinsfile
if you use a different ID).
- Action: Change the Harbor URL (
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 theJenkinsfile
if you use a different ID).
- Action: Create a new credential for your Kubernetes cluster’s
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!
Related Articles
Nov 11th, 2024
Send SUSE Security (NeuVector) events to AWS CloudTrail Lake
Jan 24th, 2025