Salta el contingut

DevSecOps: Seguretat en el Cicle de Vida del Desenvolupament

Introducció

DevSecOps ("Development + Security + Operations") integra la seguretat en cada etapa del cicle de vida del programari, en lloc de tractar-la com un pas final o una barrera per al llançament. La filosofia és "shift left": detectar i corregir vulnerabilitats el més aviat possible, quan el cost és menor.

flowchart LR
    subgraph PLAN[Planificació]
        TR[Threat Modeling\nSTRIDE]
        REQS[Security\nRequirements]
    end
    subgraph CODE[Codi]
        SAST[SAST\nSonarQube\nSemgrep]
        SECRETS[Secret\nScanning\nGitLeaks]
        REVIEW[Code\nReview]
    end
    subgraph BUILD[Build]
        SCA[SCA\nDependency Check\nSnyk]
        CONTAINER[Container\nScan\nTrivy]
    end
    subgraph TEST[Test]
        DAST[DAST\nOWASP ZAP]
        PENTEST[Pentest\nManual]
    end
    subgraph DEPLOY[Desplegament]
        IaC[IaC Scan\nCheckov]
        SIGN[Signing\nCosign]
    end
    subgraph RUN[Producció]
        SIEM[SIEM\nMonitoratge]
        RASP[RASP\nRuntime\nProtection]
    end

    PLAN --> CODE --> BUILD --> TEST --> DEPLOY --> RUN --> PLAN

    style PLAN fill:#E3F2FD
    style CODE fill:#E8F5E9
    style BUILD fill:#FFF8E1
    style TEST fill:#FBE9E7
    style DEPLOY fill:#F3E5F5
    style RUN fill:#E8EAF6

Pipeline CI/CD Segur

Estructura d'un pipeline GitLab CI

# .gitlab-ci.yml - Pipeline DevSecOps complet

stages:
  - sast
  - build
  - scan
  - test
  - deploy

variables:
  DOCKER_IMAGE: "$CI_REGISTRY_IMAGE:$CI_COMMIT_SHA"

# ==========================================
# ETAPA 1: Anàlisi estàtica de codi (SAST)
# ==========================================
semgrep-sast:
  stage: sast
  image: returntocorp/semgrep
  script:
    - semgrep --config=p/owasp-top-ten --config=p/python . --json > semgrep-report.json
    - semgrep --config=p/owasp-top-ten --config=p/python . --error
  artifacts:
    reports:
      sast: semgrep-report.json
  allow_failure: false  # Atura el pipeline si hi ha vulnerabilitats crítiques

# Detecció de secrets en el codi
gitleaks-scan:
  stage: sast
  image: zricethezav/gitleaks:latest
  script:
    - gitleaks detect --source . --report-path=gitleaks-report.json
  artifacts:
    paths:
      - gitleaks-report.json
  allow_failure: false

# ==========================================
# ETAPA 2: Build de la imatge Docker
# ==========================================
docker-build:
  stage: build
  image: docker:24
  services:
    - docker:24-dind
  script:
    - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
    - docker build -t $DOCKER_IMAGE .
    - docker push $DOCKER_IMAGE

# ==========================================
# ETAPA 3: Escaneig de la imatge
# ==========================================
trivy-scan:
  stage: scan
  image: aquasec/trivy:latest
  script:
    - trivy image --exit-code 1 --severity HIGH,CRITICAL $DOCKER_IMAGE
    - trivy image --format json --output trivy-report.json $DOCKER_IMAGE
  artifacts:
    paths:
      - trivy-report.json

# Escaneig de dependències
dependency-check:
  stage: scan
  image: owasp/dependency-check
  script:
    - /usr/share/dependency-check/bin/dependency-check.sh
      --project "$CI_PROJECT_NAME"
      --scan .
      --format JSON
      --out dependency-check-report.json
      --failOnCVSS 7
  artifacts:
    paths:
      - dependency-check-report.json

# ==========================================
# ETAPA 4: Tests dinàmics (DAST)
# ==========================================
zap-dast:
  stage: test
  image: zaproxy/zap-stable:latest
  variables:
    TARGET_URL: "http://staging.empresa.cat"
  script:
    - zap-baseline.py -t $TARGET_URL -J zap-report.json -r zap-report.html
  artifacts:
    paths:
      - zap-report.html
      - zap-report.json
  allow_failure: true  # DAST pot tenir falsos positius en entorn staging

# ==========================================
# ETAPA 5: Desplegament segur
# ==========================================
deploy-staging:
  stage: deploy
  script:
    - # Signar la imatge Docker amb cosign
    - cosign sign --key env://COSIGN_PRIVATE_KEY $DOCKER_IMAGE
    - kubectl set image deployment/app app=$DOCKER_IMAGE
  environment:
    name: staging
  only:
    - develop

deploy-production:
  stage: deploy
  script:
    - kubectl set image deployment/app app=$DOCKER_IMAGE
  environment:
    name: production
  only:
    - main
  when: manual  # Desplegament a producció sempre manual

Miniactivitat

En el pipeline anterior, hi ha tres punts on es pot bloquejar el pipeline (allow_failure: false). Per cada un, pensa:

  1. Per quin tipus de problemes és adequat bloquejar el pipeline completament?
  2. Quins casos podrien ser falsos positius i justificarien allow_failure: true?

SAST: Anàlisi Estàtica del Codi

Semgrep

# Instal·lar i executar Semgrep localment
docker run --rm \
  -v $(pwd):/src \
  returntocorp/semgrep \
  semgrep --config=p/owasp-top-ten /src

# Regles específiques per a Python (Django/Flask)
docker run --rm \
  -v $(pwd):/src \
  returntocorp/semgrep \
  semgrep --config=p/django --config=p/flask /src

# Escriure una regla personalitzada
cat > custom-rules.yml << 'EOF'
rules:
  - id: hardcoded-password
    pattern: password = "..."
    message: "Contrasenya hardcodejada detectada"
    languages: [python, javascript]
    severity: ERROR

  - id: sql-injection-format-string
    pattern: |
      $DB.execute("... %s ..." % $INPUT)
    message: "Possible SQL injection via format string"
    languages: [python]
    severity: ERROR
EOF

semgrep --config=custom-rules.yml /src

SonarQube

# docker-compose per a SonarQube local
version: '3'
services:
  sonarqube:
    image: sonarqube:community
    container_name: sonarqube-NOMCOGNOM
    ports:
      - "9000:9000"
    environment:
      SONAR_ES_BOOTSTRAP_CHECKS_DISABLE: "true"
    volumes:
      - sonar_data:/opt/sonarqube/data
      - sonar_logs:/opt/sonarqube/logs

volumes:
  sonar_data:
  sonar_logs:
# Analitzar un projecte Python amb SonarQube
docker run --rm \
  -e SONAR_HOST_URL="http://localhost:9000" \
  -e SONAR_LOGIN="<token>" \
  -v $(pwd):/usr/src \
  sonarsource/sonar-scanner-cli \
  -Dsonar.projectKey=my-project \
  -Dsonar.sources=.

Gestió de Secrets

Un dels errors més comuns: pujar credencials al repositori Git.

GitLeaks - Detecció de secrets

# Escanear el repositori complet (inclòs l'historial Git)
docker run --rm \
  -v $(pwd):/path \
  zricethezav/gitleaks:latest \
  detect --source /path --report-path=/path/leaks-report.json

# Escanear únicament el codi no commitat
docker run --rm \
  -v $(pwd):/path \
  zricethezav/gitleaks:latest \
  protect --staged --source /path

HashiCorp Vault - Gestió centralitzada de secrets

# docker-compose per a Vault en mode dev
services:
  vault:
    image: hashicorp/vault:latest
    container_name: vault-NOMCOGNOM
    ports:
      - "8200:8200"
    environment:
      VAULT_DEV_ROOT_TOKEN_ID: "root-token-dev"
      VAULT_DEV_LISTEN_ADDRESS: "0.0.0.0:8200"
    cap_add:
      - IPC_LOCK
# Guardar secrets en Vault (en lloc de .env o hardcoded)
export VAULT_ADDR="http://localhost:8200"
export VAULT_TOKEN="root-token-dev"

# Guardar credencials de la BBDD
vault kv put secret/myapp/database \
  host="db.empresa.cat" \
  username="app_user" \
  password="s3cr3t_p4ssw0rd"

# Recuperar secrets en el codi
vault kv get -field=password secret/myapp/database
# Recuperar secrets de Vault en Python (exemple)
import hvac

client = hvac.Client(url='http://vault:8200', token=os.environ['VAULT_TOKEN'])
secret = client.secrets.kv.read_secret_version(path='myapp/database')
db_password = secret['data']['data']['password']
# La contrasenya MAI apareix al codi font

Infraestructura com a Codi (IaC) Segura

Checkov - Escaneig de Terraform/Kubernetes/Docker

# Escanejar fitxers Terraform per configuracions insegures
docker run --rm \
  -v $(pwd):/tf \
  bridgecrew/checkov:latest \
  -d /tf --framework terraform

# Escanejar fitxers Kubernetes
docker run --rm \
  -v $(pwd)/k8s:/k8s \
  bridgecrew/checkov:latest \
  -d /k8s --framework kubernetes

# Escanejar Dockerfiles
docker run --rm \
  -v $(pwd):/src \
  bridgecrew/checkov:latest \
  -f /src/Dockerfile --framework dockerfile
# Exemple de Kubernetes deployment INSEGUR
# checkov detectarà aquests problemes:
apiVersion: apps/v1
kind: Deployment
spec:
  template:
    spec:
      containers:
        - name: app
          image: myapp:latest    # ❌ Tag "latest" no és reproducible
          securityContext:
            runAsRoot: true      # ❌ Corrent com a root
            privileged: true     # ❌ Mode privilegiat
          resources: {}          # ❌ Sense límits de recursos

# Versió segura:
      containers:
        - name: app
          image: myapp:1.2.3    # ✅ Tag específic
          securityContext:
            runAsNonRoot: true   # ✅ No root
            runAsUser: 1000
            allowPrivilegeEscalation: false  # ✅ No escalada
            readOnlyRootFilesystem: true     # ✅ FS read-only
          resources:
            limits:
              memory: "512Mi"    # ✅ Límits definits
              cpu: "500m"

Contenidors Segurs en Producció

Signatura d'imatges amb Cosign

# Instal·lar cosign
docker run --rm gcr.io/projectsigstore/cosign:latest version

# Generar parella de claus per a la signatura
cosign generate-key-pair

# Signar una imatge Docker
cosign sign --key cosign.key myregistry/myapp:1.0.0

# Verificar la signatura
cosign verify --key cosign.pub myregistry/myapp:1.0.0

Política d'admissió Kubernetes (Kyverno)

# Política que exigeix imatges signades en producció
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: require-signed-images
spec:
  validationFailureAction: enforce
  rules:
    - name: check-image-signature
      match:
        resources:
          kinds:
            - Pod
      verifyImages:
        - imageReferences:
            - "myregistry/*"
          attestors:
            - entries:
                - keys:
                    publicKeys: |-
                      -----BEGIN PUBLIC KEY-----
                      <cosign public key>
                      -----END PUBLIC KEY-----

Miniactivitat

Configura un pipeline mínim amb GitHub Actions o GitLab CI per a un projecte Python senzill que:

  1. Executi Semgrep en cada git push
  2. Generi un informe de vulnerabilitats com a artefacte
  3. Bloquegi el merge si hi ha vulnerabilitats de severitat HIGH o CRITICAL

Control de Versions i Branching Segur

gitGraph
    commit id: "Initial commit"
    branch develop
    checkout develop
    commit id: "Feature A"
    branch feature/login
    checkout feature/login
    commit id: "Login screen"
    commit id: "SAST passed"
    checkout develop
    merge feature/login id: "Merge after review"
    branch release/1.0
    checkout release/1.0
    commit id: "Version bump"
    commit id: "Final DAST scan"
    checkout main
    merge release/1.0 id: "Release 1.0" tag: "v1.0.0"

Protecció de branques (GitHub/GitLab)

# Regles recomanades per a la branca main/master:
# ✅ Requerir pull request reviews (mínim 1 revisor)
# ✅ Requerir CI/CD superat (SAST + tests)
# ✅ No permetre push directe
# ✅ No permetre force push
# ✅ Requerir historial lineal (no merge commits)
# ✅ No permetre eliminació de la branca

Exercici pràctic

Crea un projecte Python mínim amb una vulnerabilitat intencionada (SQL injection via format string) i:

  1. Configura Semgrep per detectar-la automàticament
  2. Configura un hook de pre-commit per blocar commits amb vulnerabilitats
  3. Simula un pipeline CI/CD (pots usar GitHub Actions free tier) amb les etapes:
  4. SAST (Semgrep)
  5. Build Docker
  6. Scan Trivy
  7. Documenta el procés en un informe devsecops_NOMCOGNOM.md