From e394dd97627ac4b61fd2bf6094e4f2e10bb243ec Mon Sep 17 00:00:00 2001 From: devops Date: Wed, 16 Sep 2026 12:57:08 +0000 Subject: [PATCH] feat: Trivy security scan + Vault se secrets + post-deploy verify --- Jenkinsfile | 138 ++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 97 insertions(+), 41 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index b48c90f..3873db0 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -9,22 +9,38 @@ pipeline { environment { REGISTRY = '51.195.4.170:5000' IMAGE_NAME = 'swim/demo-app' - // Elvis chain: params maujood na ho to bhi NPE nahi - IMAGE_TAG = "${(params.ROLLBACK_TAG ?: '').trim() ?: env.BUILD_NUMBER}" - // Jenkins container ke andar workspace /var/jenkins_home/... hai, - // par docker daemon HOST par chalta hai jahan wahi folder - // /opt/devops/data/jenkins/... par hai. -v ke liye HOST ka path chahiye. + VAULT_ADDR = 'http://vault:8200' + // Jenkins container ka workspace /var/jenkins_home/... hai, par docker + // daemon HOST par chalta hai jahan wahi folder /opt/devops/data/jenkins/... + // par hai. `docker run -v` ko HOST ka path chahiye. JENKINS_HOME_CONTAINER = '/var/jenkins_home' JENKINS_HOME_HOST = '/opt/devops/data/jenkins' + TRIVY_SEVERITY = 'HIGH,CRITICAL' } options { timestamps() buildDiscarder(logRotator(numToKeepStr: '20')) - timeout(time: 20, unit: 'MINUTES') + timeout(time: 30, unit: 'MINUTES') + disableConcurrentBuilds() } stages { + + stage('Init') { + steps { + script { + // params ko environment block me mat chhuo - wahan null hone par + // NPE aata hai. Yahan script block me safe hai. + def rb = (params.ROLLBACK_TAG ?: '').trim() + env.IS_ROLLBACK = rb ? 'true' : 'false' + env.IMAGE_TAG = rb ? rb : "${env.BUILD_NUMBER}" + echo "MODE : ${env.IS_ROLLBACK == 'true' ? 'ROLLBACK' : 'NORMAL BUILD'}" + echo "IMAGE_TAG : ${env.IMAGE_TAG}" + } + } + } + stage('Checkout') { steps { checkout scm @@ -32,15 +48,31 @@ pipeline { } } - stage('Test') { - when { expression { !(params.ROLLBACK_TAG ?: '').trim() } } + stage('Secrets from Vault') { steps { sh ''' - # container ka path -> host ka path badlo - HOST_WS=$(echo "$PWD" | sed "s|^${JENKINS_HOME_CONTAINER}|${JENKINS_HOME_HOST}|") - echo "container workspace : $PWD" - echo "host workspace : $HOST_WS" + set +x + VT=$(cat /var/jenkins_secrets/vault_token) + # Vault se registry ka password lo - Jenkins credential se NAHI + curl -sf -H "X-Vault-Token: $VT" \ + "${VAULT_ADDR}/v1/swim/data/registry" \ + | python3 -c " +import sys, json +d = json.load(sys.stdin)['data']['data'] +open('.vault-registry.env','w').write( + 'REG_USER=%s\\nREG_PASS=%s\\n' % (d['username'], d['password'])) +print(' Vault se registry creds mil gaye (user=%s)' % d['username']) +" + chmod 600 .vault-registry.env + ''' + } + } + stage('Test') { + when { expression { env.IS_ROLLBACK != 'true' } } + steps { + sh ''' + HOST_WS=$(echo "$PWD" | sed "s|^${JENKINS_HOME_CONTAINER}|${JENKINS_HOME_HOST}|") docker run --rm -v "$HOST_WS":/src -w /src python:3.12-slim sh -c " pip install --quiet -r requirements-dev.txt && python -m pytest tests/ -v @@ -50,26 +82,44 @@ pipeline { } stage('Build image') { - when { expression { !(params.ROLLBACK_TAG ?: '').trim() } } + when { expression { env.IS_ROLLBACK != 'true' } } steps { sh ''' docker build \ --build-arg APP_VERSION=${IMAGE_TAG} \ -t ${REGISTRY}/${IMAGE_NAME}:${IMAGE_TAG} \ -t ${REGISTRY}/${IMAGE_NAME}:latest . - docker images ${REGISTRY}/${IMAGE_NAME} + ''' + } + } + + stage('Security scan (Trivy)') { + when { expression { env.IS_ROLLBACK != 'true' } } + steps { + sh ''' + # Trivy ko docker socket chahiye taaki local image scan kar sake. + # Cache volume se dobara download nahi hoga. + docker run --rm \ + -v /var/run/docker.sock:/var/run/docker.sock \ + -v trivy-cache:/root/.cache/ \ + aquasec/trivy:0.58.1 image \ + --severity ${TRIVY_SEVERITY} \ + --ignore-unfixed \ + --exit-code 0 \ + --format table \ + ${REGISTRY}/${IMAGE_NAME}:${IMAGE_TAG} ''' } } stage('Smoke test image') { - when { expression { !(params.ROLLBACK_TAG ?: '').trim() } } + when { expression { env.IS_ROLLBACK != 'true' } } steps { sh ''' CID=$(docker run -d -P ${REGISTRY}/${IMAGE_NAME}:${IMAGE_TAG}) sleep 5 docker exec $CID python -c " -import urllib.request, json, sys +import urllib.request, json r = json.loads(urllib.request.urlopen('http://127.0.0.1:8000/version').read()) print('version endpoint:', r) assert r['version'] == '${IMAGE_TAG}', 'version galat!' @@ -80,33 +130,42 @@ assert r['version'] == '${IMAGE_TAG}', 'version galat!' } stage('Push to registry') { - when { expression { !(params.ROLLBACK_TAG ?: '').trim() } } + when { expression { env.IS_ROLLBACK != 'true' } } steps { - withCredentials([usernamePassword(credentialsId: 'registry-creds', - usernameVariable: 'REG_USER', - passwordVariable: 'REG_PASS')]) { - sh ''' - echo "$REG_PASS" | docker login ${REGISTRY} -u "$REG_USER" --password-stdin - docker push ${REGISTRY}/${IMAGE_NAME}:${IMAGE_TAG} - docker push ${REGISTRY}/${IMAGE_NAME}:latest - docker logout ${REGISTRY} - ''' - } + sh ''' + set +x + . ./.vault-registry.env + echo "$REG_PASS" | docker login ${REGISTRY} -u "$REG_USER" --password-stdin + docker push ${REGISTRY}/${IMAGE_NAME}:${IMAGE_TAG} + docker push ${REGISTRY}/${IMAGE_NAME}:latest + docker logout ${REGISTRY} + ''' } } stage('Deploy to VM') { steps { - withCredentials([usernamePassword(credentialsId: 'registry-creds', - usernameVariable: 'REG_USER', - passwordVariable: 'REG_PASS')]) { - sh ''' - export ANSIBLE_HOST_KEY_CHECKING=False - export IMAGE_TAG=${IMAGE_TAG} - ansible-galaxy collection install community.docker --force -q || true - ansible-playbook -i deploy/inventory.ini deploy/deploy.yml - ''' - } + sh ''' + set +x + . ./.vault-registry.env + export REG_USER REG_PASS + export ANSIBLE_HOST_KEY_CHECKING=False + export IMAGE_TAG=${IMAGE_TAG} + ansible-galaxy collection install community.docker --force -q || true + ansible-playbook -i deploy/inventory.ini deploy/deploy.yml + ''' + } + } + + stage('Post-deploy verify') { + steps { + sh ''' + APPVM=$(awk -F= "/ansible_host/{print \\$2}" deploy/inventory.ini | tr -d " ") + GOT=$(curl -sf --max-time 10 http://${APPVM}:8000/version | python3 -c "import sys,json;print(json.load(sys.stdin)['version'])") + echo " expected: ${IMAGE_TAG} | live app: ${GOT}" + [ "$GOT" = "${IMAGE_TAG}" ] || { echo "VERSION MISMATCH - deploy fail"; exit 1; } + echo " DEPLOY VERIFIED" + ''' } } } @@ -114,13 +173,10 @@ assert r['version'] == '${IMAGE_TAG}', 'version galat!' post { always { script { - // Agar pipeline node milne se PEHLE fail ho jaye, to yahan - // node context nahi hota - sh/cleanWs crash kar dete hain. if (env.WORKSPACE) { + sh 'rm -f .vault-registry.env || true' sh 'docker image prune -f --filter "until=24h" || true' cleanWs() - } else { - echo 'Node context nahi mila - cleanup skip' } } }