shared library par shift: secret scan, IaC scan, SBOM, cosign signing sab included
All checks were successful
Tests / Test passed: 3
All checks were successful
Tests / Test passed: 3
This commit is contained in:
parent
7e4b66556e
commit
686724ea5a
274
Jenkinsfile
vendored
274
Jenkinsfile
vendored
@ -1,269 +1,7 @@
|
||||
pipeline {
|
||||
// Koi default agent nahi. Har stage khud batati hai kahan chalegi -
|
||||
// CI builder VM par, CD controller par. Isse build ko production ki
|
||||
// chaabi kabhi nahi milti.
|
||||
agent none
|
||||
// Poori pipeline devops-platform library me hai. Yahan sirf module ka naam.
|
||||
// Naya module banaoge to bas yehi 3 lines copy karni hain.
|
||||
@Library('devops-platform') _
|
||||
|
||||
parameters {
|
||||
string(name: 'ROLLBACK_TAG', defaultValue: '',
|
||||
description: 'Khaali = normal build. Purana build number (jaise 2) = seedha us image par rollback, bina rebuild ke')
|
||||
}
|
||||
|
||||
environment {
|
||||
REGISTRY = 'ns31240276.ip-51-195-4.eu:5000'
|
||||
IMAGE_NAME = 'swim/demo-app'
|
||||
// Sab jagah se ek hi naam. Controller isse proxy par bhejta hai
|
||||
// (extra_hosts), builder VM bridge IP par (/etc/hosts). TLS cert
|
||||
// bhi isi naam ka hai, isliye andar ke DNS naam use nahi karte.
|
||||
VAULT_ADDR = 'https://ns31240276.ip-51-195-4.eu:8444'
|
||||
SONAR_URL = 'https://ns31240276.ip-51-195-4.eu:8446'
|
||||
HOST_BRIDGE_IP = '192.168.124.1'
|
||||
TRIVY_SEVERITY = 'HIGH,CRITICAL'
|
||||
}
|
||||
|
||||
// Trigger ko YAHAN declare karna zaroori hai. Sirf config.xml me hone par
|
||||
// DeclarativeJobPropertyTrackerAction NPE deta hai.
|
||||
triggers {
|
||||
pollSCM('')
|
||||
}
|
||||
|
||||
options {
|
||||
timestamps()
|
||||
buildDiscarder(logRotator(numToKeepStr: '20'))
|
||||
timeout(time: 40, unit: 'MINUTES')
|
||||
disableConcurrentBuilds()
|
||||
}
|
||||
|
||||
stages {
|
||||
|
||||
// ============ CI: sab kuch builder VM par ============
|
||||
// Yahan docker daemon builder VM ka hai. Build escape hui to attacker ko
|
||||
// sirf ye VM milti hai - na secrets, na Vault, na production ki key.
|
||||
stage('CI (builder-vm-01)') {
|
||||
agent { label 'builder' }
|
||||
|
||||
stages {
|
||||
stage('Init') {
|
||||
steps {
|
||||
script {
|
||||
// params ko environment block me mat chhuo - wahan null hone par NPE.
|
||||
def rb = (params.ROLLBACK_TAG ?: '').trim()
|
||||
env.IS_ROLLBACK = rb ? 'true' : 'false'
|
||||
env.IMAGE_TAG = rb ? rb : "${env.BUILD_NUMBER}"
|
||||
echo "NODE : ${env.NODE_NAME}"
|
||||
echo "MODE : ${env.IS_ROLLBACK == 'true' ? 'ROLLBACK' : 'NORMAL BUILD'}"
|
||||
echo "IMAGE_TAG : ${env.IMAGE_TAG}"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stage('Checkout') {
|
||||
steps {
|
||||
checkout scm
|
||||
sh 'echo "commit: $(git rev-parse --short HEAD)"'
|
||||
}
|
||||
}
|
||||
|
||||
stage('Secrets from Vault') {
|
||||
steps {
|
||||
// Token ab file se nahi, Jenkins credential se aata hai aur
|
||||
// log me masked rehta hai. Ye token sirf swim/registry padh
|
||||
// sakta hai - baaki Vault iske liye band hai.
|
||||
withCredentials([string(credentialsId: 'vault-builder-token', variable: 'VAULT_TOKEN')]) {
|
||||
sh '''
|
||||
set +x
|
||||
curl -sf -H "X-Vault-Token: $VAULT_TOKEN" \
|
||||
"${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 '''
|
||||
docker run --rm -v "$PWD":/src -w /src python:3.12-slim sh -c "
|
||||
pip install --quiet -r requirements-dev.txt &&
|
||||
python -m pytest tests/ -v
|
||||
"
|
||||
'''
|
||||
}
|
||||
}
|
||||
|
||||
stage('Code quality (SonarQube)') {
|
||||
when { expression { env.IS_ROLLBACK != 'true' } }
|
||||
steps {
|
||||
withCredentials([string(credentialsId: 'sonar-token', variable: 'SONAR_TOKEN')]) {
|
||||
sh '''
|
||||
set +x
|
||||
docker run --rm \
|
||||
--add-host "ns31240276.ip-51-195-4.eu:${HOST_BRIDGE_IP}" \
|
||||
-e SONAR_HOST_URL="${SONAR_URL}" \
|
||||
-e SONAR_TOKEN="$SONAR_TOKEN" \
|
||||
-v "$PWD":/usr/src \
|
||||
sonarsource/sonar-scanner-cli:11 \
|
||||
-Dsonar.projectKey=swim-demo-app
|
||||
'''
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stage('Build image') {
|
||||
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 .
|
||||
'''
|
||||
}
|
||||
}
|
||||
|
||||
stage('Security scan (Trivy)') {
|
||||
when { expression { env.IS_ROLLBACK != 'true' } }
|
||||
steps {
|
||||
sh '''
|
||||
# GATE hai, report nahi: HIGH/CRITICAL mila to build yahin fail hogi.
|
||||
# Jo CVE business ne accept kiye hain wo .trivyignore me hain.
|
||||
docker run --rm \
|
||||
-v /var/run/docker.sock:/var/run/docker.sock \
|
||||
-v trivy-cache:/root/.cache/ \
|
||||
-v "$PWD/.trivyignore":/.trivyignore:ro \
|
||||
aquasec/trivy:0.58.1 image \
|
||||
--severity ${TRIVY_SEVERITY} \
|
||||
--ignore-unfixed \
|
||||
--ignorefile /.trivyignore \
|
||||
--exit-code 1 \
|
||||
--format table \
|
||||
${REGISTRY}/${IMAGE_NAME}:${IMAGE_TAG}
|
||||
'''
|
||||
}
|
||||
}
|
||||
|
||||
stage('Smoke test image') {
|
||||
when { expression { env.IS_ROLLBACK != 'true' } }
|
||||
steps {
|
||||
sh '''
|
||||
CID=$(docker run -d -P ${REGISTRY}/${IMAGE_NAME}:${IMAGE_TAG})
|
||||
trap "docker rm -f $CID >/dev/null 2>&1" EXIT
|
||||
sleep 5
|
||||
docker exec $CID python -c "
|
||||
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!'
|
||||
"
|
||||
'''
|
||||
}
|
||||
}
|
||||
|
||||
stage('Push to registry') {
|
||||
when { expression { env.IS_ROLLBACK != 'true' } }
|
||||
steps {
|
||||
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}
|
||||
'''
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
post {
|
||||
always {
|
||||
sh 'rm -f .vault-registry.env || true'
|
||||
sh 'docker image prune -f --filter "until=24h" || true'
|
||||
cleanWs()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ============ CD: controller par ============
|
||||
// Deploy key sirf yahan aati hai. Builder VM ise kabhi nahi dekhti.
|
||||
stage('Deploy STAGING') {
|
||||
agent { label 'built-in' }
|
||||
steps {
|
||||
checkout scm
|
||||
withCredentials([
|
||||
sshUserPrivateKey(credentialsId: 'deploy-key', keyFileVariable: 'DEPLOY_KEY', usernameVariable: 'DEPLOY_USER'),
|
||||
usernamePassword(credentialsId: 'registry-creds', usernameVariable: 'REG_USER', passwordVariable: 'REG_PASS')
|
||||
]) {
|
||||
sh '''
|
||||
set +x
|
||||
export ANSIBLE_HOST_KEY_CHECKING=False
|
||||
ansible-galaxy collection install community.docker --force -q || true
|
||||
ansible-playbook -i deploy/inventory.ini deploy/deploy.yml \
|
||||
-u "$DEPLOY_USER" --private-key "$DEPLOY_KEY" \
|
||||
-e target=staging
|
||||
'''
|
||||
}
|
||||
sh '''
|
||||
SVM=$(grep "^staging-vm" deploy/inventory.ini | sed "s/.*ansible_host=//" | tr -d " ")
|
||||
GOT=$(curl -sf --max-time 10 http://${SVM}:8000/version | python3 -c "import sys,json;print(json.load(sys.stdin)['version'])")
|
||||
echo " staging expected: ${IMAGE_TAG} | live: ${GOT}"
|
||||
[ "$GOT" = "${IMAGE_TAG}" ] || { echo "STAGING MISMATCH"; exit 1; }
|
||||
echo " STAGING VERIFIED"
|
||||
'''
|
||||
}
|
||||
}
|
||||
|
||||
stage('Production approval') {
|
||||
// agent none = intezaar ke 15 minute me koi executor block nahi hota
|
||||
agent none
|
||||
steps {
|
||||
script {
|
||||
timeout(time: 15, unit: 'MINUTES') {
|
||||
input(message: "Staging par ${env.IMAGE_TAG} verified hai. PRODUCTION par deploy karein?",
|
||||
ok: 'Deploy to Production')
|
||||
}
|
||||
echo "Production deploy approve ho gaya"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stage('Deploy PRODUCTION') {
|
||||
agent { label 'built-in' }
|
||||
steps {
|
||||
checkout scm
|
||||
withCredentials([
|
||||
sshUserPrivateKey(credentialsId: 'deploy-key', keyFileVariable: 'DEPLOY_KEY', usernameVariable: 'DEPLOY_USER'),
|
||||
usernamePassword(credentialsId: 'registry-creds', usernameVariable: 'REG_USER', passwordVariable: 'REG_PASS')
|
||||
]) {
|
||||
sh '''
|
||||
set +x
|
||||
export ANSIBLE_HOST_KEY_CHECKING=False
|
||||
ansible-playbook -i deploy/inventory.ini deploy/deploy.yml \
|
||||
-u "$DEPLOY_USER" --private-key "$DEPLOY_KEY" \
|
||||
-e target=production
|
||||
'''
|
||||
}
|
||||
sh '''
|
||||
PVM=$(grep "^app-vm" deploy/inventory.ini | sed "s/.*ansible_host=//" | tr -d " ")
|
||||
GOT=$(curl -sf --max-time 10 http://${PVM}:8000/version | python3 -c "import sys,json;print(json.load(sys.stdin)['version'])")
|
||||
echo " production expected: ${IMAGE_TAG} | live: ${GOT}"
|
||||
[ "$GOT" = "${IMAGE_TAG}" ] || { echo "PROD MISMATCH"; exit 1; }
|
||||
echo " PRODUCTION VERIFIED"
|
||||
'''
|
||||
}
|
||||
post { always { cleanWs() } }
|
||||
}
|
||||
}
|
||||
|
||||
post {
|
||||
success { echo "BUILD ${env.BUILD_NUMBER} SAFAL -> ${env.REGISTRY}/${env.IMAGE_NAME}:${env.IMAGE_TAG}" }
|
||||
failure { echo "BUILD ${env.BUILD_NUMBER} FAIL" }
|
||||
}
|
||||
}
|
||||
devopsPipeline(
|
||||
module: 'demo-app'
|
||||
)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user