feat: Trivy security scan + Vault se secrets + post-deploy verify
This commit is contained in:
parent
b9a3e06ff2
commit
e394dd9762
138
Jenkinsfile
vendored
138
Jenkinsfile
vendored
@ -9,22 +9,38 @@ pipeline {
|
|||||||
environment {
|
environment {
|
||||||
REGISTRY = '51.195.4.170:5000'
|
REGISTRY = '51.195.4.170:5000'
|
||||||
IMAGE_NAME = 'swim/demo-app'
|
IMAGE_NAME = 'swim/demo-app'
|
||||||
// Elvis chain: params maujood na ho to bhi NPE nahi
|
VAULT_ADDR = 'http://vault:8200'
|
||||||
IMAGE_TAG = "${(params.ROLLBACK_TAG ?: '').trim() ?: env.BUILD_NUMBER}"
|
// Jenkins container ka workspace /var/jenkins_home/... hai, par docker
|
||||||
// Jenkins container ke andar workspace /var/jenkins_home/... hai,
|
// daemon HOST par chalta hai jahan wahi folder /opt/devops/data/jenkins/...
|
||||||
// par docker daemon HOST par chalta hai jahan wahi folder
|
// par hai. `docker run -v` ko HOST ka path chahiye.
|
||||||
// /opt/devops/data/jenkins/... par hai. -v ke liye HOST ka path chahiye.
|
|
||||||
JENKINS_HOME_CONTAINER = '/var/jenkins_home'
|
JENKINS_HOME_CONTAINER = '/var/jenkins_home'
|
||||||
JENKINS_HOME_HOST = '/opt/devops/data/jenkins'
|
JENKINS_HOME_HOST = '/opt/devops/data/jenkins'
|
||||||
|
TRIVY_SEVERITY = 'HIGH,CRITICAL'
|
||||||
}
|
}
|
||||||
|
|
||||||
options {
|
options {
|
||||||
timestamps()
|
timestamps()
|
||||||
buildDiscarder(logRotator(numToKeepStr: '20'))
|
buildDiscarder(logRotator(numToKeepStr: '20'))
|
||||||
timeout(time: 20, unit: 'MINUTES')
|
timeout(time: 30, unit: 'MINUTES')
|
||||||
|
disableConcurrentBuilds()
|
||||||
}
|
}
|
||||||
|
|
||||||
stages {
|
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') {
|
stage('Checkout') {
|
||||||
steps {
|
steps {
|
||||||
checkout scm
|
checkout scm
|
||||||
@ -32,15 +48,31 @@ pipeline {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
stage('Test') {
|
stage('Secrets from Vault') {
|
||||||
when { expression { !(params.ROLLBACK_TAG ?: '').trim() } }
|
|
||||||
steps {
|
steps {
|
||||||
sh '''
|
sh '''
|
||||||
# container ka path -> host ka path badlo
|
set +x
|
||||||
HOST_WS=$(echo "$PWD" | sed "s|^${JENKINS_HOME_CONTAINER}|${JENKINS_HOME_HOST}|")
|
VT=$(cat /var/jenkins_secrets/vault_token)
|
||||||
echo "container workspace : $PWD"
|
# Vault se registry ka password lo - Jenkins credential se NAHI
|
||||||
echo "host workspace : $HOST_WS"
|
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 "
|
docker run --rm -v "$HOST_WS":/src -w /src python:3.12-slim sh -c "
|
||||||
pip install --quiet -r requirements-dev.txt &&
|
pip install --quiet -r requirements-dev.txt &&
|
||||||
python -m pytest tests/ -v
|
python -m pytest tests/ -v
|
||||||
@ -50,26 +82,44 @@ pipeline {
|
|||||||
}
|
}
|
||||||
|
|
||||||
stage('Build image') {
|
stage('Build image') {
|
||||||
when { expression { !(params.ROLLBACK_TAG ?: '').trim() } }
|
when { expression { env.IS_ROLLBACK != 'true' } }
|
||||||
steps {
|
steps {
|
||||||
sh '''
|
sh '''
|
||||||
docker build \
|
docker build \
|
||||||
--build-arg APP_VERSION=${IMAGE_TAG} \
|
--build-arg APP_VERSION=${IMAGE_TAG} \
|
||||||
-t ${REGISTRY}/${IMAGE_NAME}:${IMAGE_TAG} \
|
-t ${REGISTRY}/${IMAGE_NAME}:${IMAGE_TAG} \
|
||||||
-t ${REGISTRY}/${IMAGE_NAME}:latest .
|
-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') {
|
stage('Smoke test image') {
|
||||||
when { expression { !(params.ROLLBACK_TAG ?: '').trim() } }
|
when { expression { env.IS_ROLLBACK != 'true' } }
|
||||||
steps {
|
steps {
|
||||||
sh '''
|
sh '''
|
||||||
CID=$(docker run -d -P ${REGISTRY}/${IMAGE_NAME}:${IMAGE_TAG})
|
CID=$(docker run -d -P ${REGISTRY}/${IMAGE_NAME}:${IMAGE_TAG})
|
||||||
sleep 5
|
sleep 5
|
||||||
docker exec $CID python -c "
|
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())
|
r = json.loads(urllib.request.urlopen('http://127.0.0.1:8000/version').read())
|
||||||
print('version endpoint:', r)
|
print('version endpoint:', r)
|
||||||
assert r['version'] == '${IMAGE_TAG}', 'version galat!'
|
assert r['version'] == '${IMAGE_TAG}', 'version galat!'
|
||||||
@ -80,33 +130,42 @@ assert r['version'] == '${IMAGE_TAG}', 'version galat!'
|
|||||||
}
|
}
|
||||||
|
|
||||||
stage('Push to registry') {
|
stage('Push to registry') {
|
||||||
when { expression { !(params.ROLLBACK_TAG ?: '').trim() } }
|
when { expression { env.IS_ROLLBACK != 'true' } }
|
||||||
steps {
|
steps {
|
||||||
withCredentials([usernamePassword(credentialsId: 'registry-creds',
|
sh '''
|
||||||
usernameVariable: 'REG_USER',
|
set +x
|
||||||
passwordVariable: 'REG_PASS')]) {
|
. ./.vault-registry.env
|
||||||
sh '''
|
echo "$REG_PASS" | docker login ${REGISTRY} -u "$REG_USER" --password-stdin
|
||||||
echo "$REG_PASS" | docker login ${REGISTRY} -u "$REG_USER" --password-stdin
|
docker push ${REGISTRY}/${IMAGE_NAME}:${IMAGE_TAG}
|
||||||
docker push ${REGISTRY}/${IMAGE_NAME}:${IMAGE_TAG}
|
docker push ${REGISTRY}/${IMAGE_NAME}:latest
|
||||||
docker push ${REGISTRY}/${IMAGE_NAME}:latest
|
docker logout ${REGISTRY}
|
||||||
docker logout ${REGISTRY}
|
'''
|
||||||
'''
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
stage('Deploy to VM') {
|
stage('Deploy to VM') {
|
||||||
steps {
|
steps {
|
||||||
withCredentials([usernamePassword(credentialsId: 'registry-creds',
|
sh '''
|
||||||
usernameVariable: 'REG_USER',
|
set +x
|
||||||
passwordVariable: 'REG_PASS')]) {
|
. ./.vault-registry.env
|
||||||
sh '''
|
export REG_USER REG_PASS
|
||||||
export ANSIBLE_HOST_KEY_CHECKING=False
|
export ANSIBLE_HOST_KEY_CHECKING=False
|
||||||
export IMAGE_TAG=${IMAGE_TAG}
|
export IMAGE_TAG=${IMAGE_TAG}
|
||||||
ansible-galaxy collection install community.docker --force -q || true
|
ansible-galaxy collection install community.docker --force -q || true
|
||||||
ansible-playbook -i deploy/inventory.ini deploy/deploy.yml
|
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 {
|
post {
|
||||||
always {
|
always {
|
||||||
script {
|
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) {
|
if (env.WORKSPACE) {
|
||||||
|
sh 'rm -f .vault-registry.env || true'
|
||||||
sh 'docker image prune -f --filter "until=24h" || true'
|
sh 'docker image prune -f --filter "until=24h" || true'
|
||||||
cleanWs()
|
cleanWs()
|
||||||
} else {
|
|
||||||
echo 'Node context nahi mila - cleanup skip'
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user