Securitatea Pipeline-ului DevSecOps: Integrarea Securitatii in CI/CD
DevSecOps reprezinta o schimbare fundamentala in modul in care organizatiile abordeaza securitatea - trecerea de la un control final inainte de deployment la o practica integrata pe tot parcursul ciclului de viata al dezvoltarii software. Pipeline-ul CI/CD este locul unde aceasta integrare prinde forma concreta.
Acest ghid ofera abordari practice pentru construirea pipeline-urilor securizate.
Arhitectura Pipeline-ului Securizat
┌─────────────────────────────────────────────────────────────────────────────┐
│ Secure CI/CD Pipeline │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ Code │ → │ Build │ → │ Test │ → │ Deploy │ → │ Monitor │ │
│ │ Commit │ │ │ │ │ │ │ │ │ │
│ └────┬─────┘ └────┬─────┘ └────┬─────┘ └────┬─────┘ └────┬─────┘ │
│ │ │ │ │ │ │
│ ▼ ▼ ▼ ▼ ▼ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ Pre- │ │ SAST │ │ DAST │ │ Config │ │ Runtime │ │
│ │ commit │ │ SCA │ │ Security │ │ Audit │ │ Security │ │
│ │ Hooks │ │ Secrets │ │ Tests │ │ CSPM │ │ RASP │ │
│ └──────────┘ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
Etapa 1: Securitate Pre-Commit
Configurare Git Hooks
# .pre-commit-config.yaml
repos:
# Detectie secrete
- repo: https://github.com/Yelp/detect-secrets
rev: v1.4.0
hooks:
- id: detect-secrets
args: ['--baseline', '.secrets.baseline']
# Scanare credentiale
- repo: https://github.com/trufflesecurity/trufflehog
rev: v3.63.0
hooks:
- id: trufflehog
args: ['--only-verified']
# Linting de securitate
- repo: https://github.com/PyCQA/bandit
rev: 1.7.5
hooks:
- id: bandit
args: ['-r', 'src/', '-ll']
# Linting Dockerfile
- repo: https://github.com/hadolint/hadolint
rev: v2.12.0
hooks:
- id: hadolint-docker
args: ['--ignore', 'DL3008']
# Securitate Terraform
- repo: https://github.com/antonbabenko/pre-commit-terraform
rev: v1.83.5
hooks:
- id: terraform_tfsec
- id: terraform_checkov
# Linting YAML (pentru manifeste K8s)
- repo: https://github.com/adrienverge/yamllint
rev: v1.32.0
hooks:
- id: yamllint
args: ['-c', '.yamllint.yml']Hook-uri Custom de Securitate
#!/bin/bash
# .git/hooks/pre-commit
echo "Se ruleaza verificarile de securitate..."
# Verificare pattern-uri de secrete hardcodate
SECRET_PATTERNS=(
'password\s*=\s*["\047][^"\047]+'
'api[_-]?key\s*=\s*["\047][^"\047]+'
'secret\s*=\s*["\047][^"\047]+'
'AWS[A-Z0-9]{16,}'
'AKIA[A-Z0-9]{16}'
)
for pattern in "${SECRET_PATTERNS[@]}"; do
if git diff --cached --name-only | xargs grep -lE "$pattern" 2>/dev/null; then
echo "EROARE: Secret potential detectat cu pattern-ul: $pattern"
echo "Te rugam sa elimini secretele inainte de commit"
exit 1
fi
done
# Verificare tipuri de fisiere sensibile
SENSITIVE_EXTENSIONS=(.pem .key .p12 .pfx .env .env.local)
for ext in "${SENSITIVE_EXTENSIONS[@]}"; do
if git diff --cached --name-only | grep -E "\\${ext}$"; then
echo "EROARE: Tip de fisier sensibil detectat: $ext"
echo "Ia in considerare utilizarea unui sistem de management al secretelor"
exit 1
fi
done
echo "Verificarile de securitate pre-commit au trecut"Etapa 2: Securitatea Build-ului
Pipeline Securizat GitHub Actions
# .github/workflows/secure-pipeline.yml
name: Secure CI/CD Pipeline
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
permissions:
contents: read
security-events: write
actions: read
jobs:
security-scan:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0
# SAST - Static Application Security Testing
- name: Run SAST (Semgrep)
uses: returntocorp/semgrep-action@v1
with:
config: >-
p/security-audit
p/secrets
p/owasp-top-ten
generateSarif: true
- name: Upload SAST results
uses: github/codeql-action/upload-sarif@v2
with:
sarif_file: semgrep.sarif
# SCA - Software Composition Analysis
- name: Run SCA (Trivy)
uses: aquasecurity/trivy-action@master
with:
scan-type: 'fs'
scan-ref: '.'
format: 'sarif'
output: 'trivy-sca.sarif'
severity: 'CRITICAL,HIGH'
- name: Upload SCA results
uses: github/codeql-action/upload-sarif@v2
with:
sarif_file: trivy-sca.sarif
# Scanare secrete
- name: Scan for secrets
uses: trufflesecurity/trufflehog@main
with:
path: ./
base: ${{ github.event.repository.default_branch }}
head: HEAD
# Conformitate licente
- name: Check licenses
run: |
npm install -g license-checker
license-checker --failOn 'GPL;AGPL' --summary
build-and-scan:
needs: security-scan
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Build container image
uses: docker/build-push-action@v5
with:
context: .
push: false
load: true
tags: app:${{ github.sha }}
cache-from: type=gha
cache-to: type=gha,mode=max
# Scanare imagine container
- name: Scan container image
uses: aquasecurity/trivy-action@master
with:
image-ref: 'app:${{ github.sha }}'
format: 'sarif'
output: 'trivy-container.sarif'
severity: 'CRITICAL,HIGH'
exit-code: '1'
- name: Upload container scan results
uses: github/codeql-action/upload-sarif@v2
if: always()
with:
sarif_file: trivy-container.sarif
# Generare SBOM
- name: Generate SBOM
uses: anchore/sbom-action@v0
with:
image: app:${{ github.sha }}
format: spdx-json
output-file: sbom.spdx.json
- name: Upload SBOM
uses: actions/upload-artifact@v3
with:
name: sbom
path: sbom.spdx.json
security-tests:
needs: build-and-scan
runs-on: ubuntu-latest
services:
app:
image: app:${{ github.sha }}
ports:
- 8080:8080
steps:
# DAST - Dynamic Application Security Testing
- name: Run DAST (OWASP ZAP)
uses: zaproxy/action-baseline@v0.9.0
with:
target: 'http://localhost:8080'
rules_file_name: '.zap/rules.tsv'
cmd_options: '-a'
- name: Upload DAST results
uses: actions/upload-artifact@v3
with:
name: zap-report
path: report_html.html
# Testare securitate API
- name: Run API security tests
run: |
npm install -g @stoplight/spectral-cli
spectral lint openapi.yaml --ruleset .spectral.yml
deploy-staging:
needs: security-tests
runs-on: ubuntu-latest
environment: staging
steps:
- name: Deploy to staging
run: |
# Comenzi de deployment aici
echo "Deploying to staging..."
# Scanare securitate infrastructura
- name: Scan infrastructure config
uses: bridgecrewio/checkov-action@v12
with:
directory: ./terraform
framework: terraform
output_format: sarif
output_file_path: checkov.sarif
- name: Upload infrastructure scan
uses: github/codeql-action/upload-sarif@v2
with:
sarif_file: checkov.sarifPipeline Securizat GitLab CI
# .gitlab-ci.yml
stages:
- security-scan
- build
- test
- deploy
variables:
DOCKER_DRIVER: overlay2
SECURE_LOG_LEVEL: info
# Template-uri de scanare de securitate
include:
- template: Security/SAST.gitlab-ci.yml
- template: Security/Secret-Detection.gitlab-ci.yml
- template: Security/Dependency-Scanning.gitlab-ci.yml
- template: Security/Container-Scanning.gitlab-ci.yml
- template: Security/License-Scanning.gitlab-ci.yml
# Override job SAST
sast:
stage: security-scan
variables:
SAST_EXCLUDED_PATHS: "spec, test, tests, tmp, vendor"
SEARCH_MAX_DEPTH: 10
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
# Job custom de securitate
security-audit:
stage: security-scan
image: node:18-alpine
script:
- npm audit --audit-level=high
- npm run security:check
allow_failure: false
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
# Scanare container cu Trivy
container-security:
stage: security-scan
image:
name: aquasec/trivy:latest
entrypoint: [""]
script:
- trivy image --exit-code 1 --severity HIGH,CRITICAL $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
rules:
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
# Scanare infrastructura
infra-security:
stage: security-scan
image:
name: bridgecrew/checkov:latest
entrypoint: [""]
script:
- checkov -d ./terraform --framework terraform --output cli --output junitxml --output-file-path ./checkov-results
artifacts:
reports:
junit: checkov-results/results_junitxml.xml
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
changes:
- "terraform/**/*"
# Build cu context de securitate
build:
stage: build
image: docker:24
services:
- docker:24-dind
script:
- docker build
--build-arg BUILD_DATE=$(date -u +'%Y-%m-%dT%H:%M:%SZ')
--build-arg VCS_REF=$CI_COMMIT_SHA
--label "org.opencontainers.image.created=$(date -u +'%Y-%m-%dT%H:%M:%SZ')"
--label "org.opencontainers.image.revision=$CI_COMMIT_SHA"
-t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA .
- docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
rules:
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
# Testare DAST
dast:
stage: test
image: owasp/zap2docker-stable
script:
- zap-baseline.py -t $STAGING_URL -r zap-report.html
artifacts:
paths:
- zap-report.html
rules:
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCHEtapa 3: Integrarea Testarii de Securitate
Configurare SAST (Semgrep)
# .semgrep.yml
rules:
- id: hardcoded-secret
patterns:
- pattern-either:
- pattern: $X = "..."
- pattern: $X = '...'
pattern-regex: (password|secret|api_key|token)\s*=\s*['"][^'"]+['"]
message: Secret hardcodat detectat
languages: [python, javascript, typescript, java]
severity: ERROR
- id: sql-injection
patterns:
- pattern: |
$QUERY = f"SELECT ... {$INPUT} ..."
...
cursor.execute($QUERY)
message: Vulnerabilitate potentiala de SQL injection
languages: [python]
severity: ERROR
- id: unsafe-deserialization
pattern: pickle.loads($X)
message: Deserializare nesigura cu pickle
languages: [python]
severity: ERROR
- id: command-injection
patterns:
- pattern: subprocess.call($CMD, shell=True)
- pattern: os.system($CMD)
message: Potential command injection
languages: [python]
severity: ERROR
# Regula custom pentru securitate AI/LLM
- id: prompt-injection-risk
patterns:
- pattern: |
prompt = f"... {$USER_INPUT} ..."
- pattern: |
prompt = "..." + $USER_INPUT + "..."
message: Input utilizator direct in prompt - risc potential de prompt injection
languages: [python]
severity: WARNINGConfigurare SCA (Dependabot + Renovate)
# .github/dependabot.yml
version: 2
updates:
- package-ecosystem: "npm"
directory: "/"
schedule:
interval: "daily"
open-pull-requests-limit: 10
labels:
- "dependencies"
- "security"
groups:
security-patches:
applies-to: security-updates
patterns:
- "*"
- package-ecosystem: "pip"
directory: "/"
schedule:
interval: "daily"
groups:
security-patches:
applies-to: security-updates
- package-ecosystem: "docker"
directory: "/"
schedule:
interval: "weekly"
- package-ecosystem: "terraform"
directory: "/terraform"
schedule:
interval: "weekly"// renovate.json
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": [
"config:base",
":semanticCommits",
"security:openssf-scorecard"
],
"vulnerabilityAlerts": {
"enabled": true,
"labels": ["security"]
},
"packageRules": [
{
"matchUpdateTypes": ["patch", "minor"],
"matchCurrentVersion": "!/^0/",
"automerge": true,
"automergeType": "pr",
"automergeStrategy": "squash"
},
{
"matchDepTypes": ["devDependencies"],
"automerge": true
},
{
"matchPackagePatterns": ["*"],
"matchUpdateTypes": ["major"],
"labels": ["major-update"]
}
],
"osvVulnerabilityAlerts": true
}Etapa 4: Securitatea Containerelor
Dockerfile Securizat
# Dockerfile cu bune practici de securitate
# Foloseste versiune specifica, nu latest
FROM node:20.10.0-alpine3.18 AS builder
# Creeaza utilizator non-root
RUN addgroup -g 1001 -S appgroup && \
adduser -u 1001 -S appuser -G appgroup
WORKDIR /app
# Copiaza doar fisierele package mai intai pentru cache mai bun
COPY package*.json ./
# Instaleaza dependentele cu audit de securitate
RUN npm ci --only=production && \
npm audit --audit-level=high
# Copiaza codul aplicatiei
COPY --chown=appuser:appgroup . .
# Build aplicatie
RUN npm run build
# Etapa de productie
FROM node:20.10.0-alpine3.18 AS production
# Update-uri de securitate
RUN apk update && \
apk upgrade --no-cache && \
apk add --no-cache dumb-init
# Creeaza utilizator non-root
RUN addgroup -g 1001 -S appgroup && \
adduser -u 1001 -S appuser -G appgroup
WORKDIR /app
# Copiaza aplicatia compilata
COPY --from=builder --chown=appuser:appgroup /app/dist ./dist
COPY --from=builder --chown=appuser:appgroup /app/node_modules ./node_modules
COPY --from=builder --chown=appuser:appgroup /app/package.json ./
# Sterge fisierele inutile
RUN rm -rf /var/cache/apk/* /tmp/* /root/.npm
# Configurari de securitate
ENV NODE_ENV=production
ENV NPM_CONFIG_LOGLEVEL=warn
# Ruleaza ca utilizator non-root
USER appuser
# Health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD node healthcheck.js || exit 1
# Foloseste dumb-init pentru gestionarea corecta a semnalelor
ENTRYPOINT ["dumb-init", "--"]
CMD ["node", "dist/server.js"]
# Etichete
LABEL org.opencontainers.image.source="https://github.com/org/repo"
LABEL org.opencontainers.image.description="Descrierea aplicatiei"
LABEL org.opencontainers.image.licenses="MIT"Politici de Securitate Kubernetes
# k8s/security-policies.yaml
# Pod Security Policy (pentru clustere mai vechi)
apiVersion: policy/v1beta1
kind: PodSecurityPolicy
metadata:
name: restricted
spec:
privileged: false
allowPrivilegeEscalation: false
requiredDropCapabilities:
- ALL
volumes:
- 'configMap'
- 'emptyDir'
- 'projected'
- 'secret'
- 'downwardAPI'
- 'persistentVolumeClaim'
hostNetwork: false
hostIPC: false
hostPID: false
runAsUser:
rule: 'MustRunAsNonRoot'
seLinux:
rule: 'RunAsAny'
fsGroup:
rule: 'RunAsAny'
readOnlyRootFilesystem: true
---
# Politica de Retea
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: app-network-policy
namespace: production
spec:
podSelector:
matchLabels:
app: myapp
policyTypes:
- Ingress
- Egress
ingress:
- from:
- podSelector:
matchLabels:
app: api-gateway
ports:
- protocol: TCP
port: 8080
egress:
- to:
- podSelector:
matchLabels:
app: database
ports:
- protocol: TCP
port: 5432
- to:
- namespaceSelector: {}
podSelector:
matchLabels:
k8s-app: kube-dns
ports:
- protocol: UDP
port: 53
---
# Cote de Resurse
apiVersion: v1
kind: ResourceQuota
metadata:
name: security-quota
namespace: production
spec:
hard:
pods: "20"
requests.cpu: "10"
requests.memory: 20Gi
limits.cpu: "20"
limits.memory: 40GiEtapa 5: Conformitate ca si Cod
Policy as Code (OPA/Rego)
# policies/kubernetes.rego
package kubernetes.admission
# Refuza containerele care ruleaza ca root
deny[msg] {
input.request.kind.kind == "Pod"
container := input.request.object.spec.containers[_]
not container.securityContext.runAsNonRoot
msg := sprintf("Containerul %v nu trebuie sa ruleze ca root", [container.name])
}
# Cere limite de resurse
deny[msg] {
input.request.kind.kind == "Pod"
container := input.request.object.spec.containers[_]
not container.resources.limits
msg := sprintf("Containerul %v trebuie sa aiba limite de resurse", [container.name])
}
# Refuza containerele privilegiate
deny[msg] {
input.request.kind.kind == "Pod"
container := input.request.object.spec.containers[_]
container.securityContext.privileged
msg := sprintf("Containerul %v nu trebuie sa fie privilegiat", [container.name])
}
# Cere registre aprobate
deny[msg] {
input.request.kind.kind == "Pod"
container := input.request.object.spec.containers[_]
not startswith(container.image, "gcr.io/my-project/")
not startswith(container.image, "docker.io/library/")
msg := sprintf("Containerul %v foloseste un registru neaprobat", [container.name])
}
# Cere eticheta de scanare de securitate
deny[msg] {
input.request.kind.kind == "Deployment"
not input.request.object.metadata.labels["security-scanned"]
msg := "Deployment-ul trebuie sa aiba eticheta security-scanned"
}Monitorizare si Alerte
# prometheus-alerts.yml
groups:
- name: security-alerts
rules:
- alert: SecurityScanFailed
expr: security_scan_status{result="failed"} > 0
for: 5m
labels:
severity: critical
annotations:
summary: Scanarea de securitate a esuat
description: "Scanarea de securitate a esuat pentru {{ $labels.project }}"
- alert: HighVulnerabilityDetected
expr: security_vulnerabilities{severity="critical"} > 0
for: 1m
labels:
severity: critical
annotations:
summary: Vulnerabilitate critica detectata
description: "Vulnerabilitate critica in {{ $labels.image }}"
- alert: UnauthorizedDeployment
expr: deployment_without_approval > 0
for: 1m
labels:
severity: warning
annotations:
summary: Deployment fara aprobare de securitate
- alert: SecretExposed
expr: secret_scan_detections > 0
for: 1m
labels:
severity: critical
annotations:
summary: Secret expus in codConcluzie
Un pipeline CI/CD securizat necesita integrarea securitatii in fiecare etapa - de la hook-urile pre-commit pana la monitorizarea in runtime. Cheia este automatizarea: verificari de securitate care ruleaza automat, consistent si fara sa incetineasca dezvoltarea.
Concluzii principale:
- Shift left - Prinde problemele cat mai devreme posibil
- Automatizeaza totul - Verificarile manuale de securitate nu scaleaza
- Esueaza rapid - Blocheaza codul nesigur sa avanseze in pipeline
- Masoara si imbunatateste - Urmareste metricile de securitate in timp
- Echilibreaza securitatea si viteza - Securitatea nu ar trebui sa fie un bottleneck
La DeviDevs, ajutam organizatiile sa construiasca pipeline-uri CI/CD securizate care permit deployment-uri rapide si sigure. Contacteaza-ne pentru a discuta nevoile tale de DevSecOps.
Resurse Conexe
- ML CI/CD: Continuous Integration and Deployment for Machine Learning: Aplica securitatea CI/CD pe pipeline-uri ML
- MLOps Security: Securing Your ML Pipeline: Securitate specifica pipeline-urilor ML
- MLOps Best Practices: Building Production-Ready ML Pipelines: Arhitectura pipeline-urilor de productie
- Common MLOps Mistakes and How to Avoid Them: Inclusiv capcanele deployment-ului manual
- What is MLOps?: Prezentare completa MLOps
Sistemul tau AI e conform cu EU AI Act? Evaluare gratuita de risc - afla in 2 minute →