Tu pipeline CI/CD puede tener controles de seguridad herméticos —commits firmados, dependencias fijadas, escaneos SAST, firma de imágenes de contenedor—, pero nada de eso importa si el propio proceso de despliegue es débil. El despliegue es el punto crítico donde la seguridad del pipeline se encuentra con la seguridad de producción. Un workflow de despliegue comprometido puede eludir todos los controles previos que has construido, empujando código malicioso directamente al entorno del que dependen tus clientes.
Esta guía cubre cómo construir workflows de despliegue seguros de principio a fin: elegir el modelo de despliegue adecuado, aplicar puertas y aprobaciones, verificar los artefactos en el momento del despliegue, desplegar los cambios de forma progresiva y mantener un rastro de auditoría completo desde el commit hasta producción.
Modelos de despliegue: basado en push frente a basado en pull (GitOps)
La primera decisión arquitectónica que define tu postura de seguridad de despliegue es si usas un modelo basado en push o basado en pull.
Despliegues basados en push (dirigidos por CI)
En un modelo tradicional basado en push, el pipeline CI/CD construye el artefacto y luego lo empuja directamente al entorno de destino. GitHub Actions despliega en Kubernetes mediante kubectl apply, o un job de GitLab CI ejecuta helm upgrade contra un clúster. El propio pipeline posee las credenciales del entorno de producción.
Este modelo es sencillo, pero conlleva un riesgo inherente: el runner de CI tiene acceso de escritura directo a producción. Si un atacante compromete el pipeline —a través de una dependencia envenenada, un pull request malicioso o un secreto robado—, hereda ese acceso a producción de inmediato.
Despliegues basados en pull (GitOps)
En un modelo basado en pull o GitOps, un controlador dedicado que se ejecuta dentro del entorno de destino —como Flux o ArgoCD— vigila un repositorio de Git en busca de cambios en el estado deseado. Cuando se hace commit de un nuevo manifiesto (normalmente por parte del pipeline de CI al actualizar un tag de imagen), el controlador extrae el cambio y reconcilia el clúster para que coincida.
La ventaja de seguridad es significativa. El pipeline de CI nunca necesita credenciales directas del clúster de producción. La superficie de ataque se reduce porque el agente de despliegue vive dentro del clúster y solo extrae de una fuente conocida. La detección de deriva viene incorporada: si alguien modifica manualmente un recurso, el controlador lo revierte para que coincida con Git.
Recomendación: para las cargas de trabajo de producción, prefiere un modelo GitOps basado en pull. Reserva los despliegues basados en push para los entornos de desarrollo y staging, donde la velocidad importa más que un control de acceso estricto. Incluso en configuraciones basadas en push, aplica el principio de mínimo privilegio con rigor a las credenciales de despliegue.
Puertas de despliegue: aprobaciones manuales y entornos protegidos
Los pipelines automatizados son rápidos, pero desplegar en producción no debería ocurrir sin verificación humana en el caso de los cambios de alto impacto. Las puertas de despliegue introducen puntos de control que exigen una aprobación explícita antes de que una release continúe.
Environments de GitHub y revisores obligatorios
GitHub Actions admite Environments con reglas de protección. Puedes exigir que uno o más revisores aprueben un despliegue antes de que se ejecute el job. Esto se configura en los ajustes del repositorio y se aplica a nivel de plataforma: el código del pipeline no puede eludirlo.
# .github/workflows/deploy.yml
jobs:
deploy-production:
runs-on: ubuntu-latest
environment:
name: production
url: https://app.example.com
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Verify artifact signature
run: |
cosign verify \
--key cosign.pub \
ghcr.io/myorg/myapp:${{ github.sha }}
- name: Deploy to production
run: |
helm upgrade --install myapp ./chart \
--set image.tag=${{ github.sha }} \
--namespace production
Con el entorno production configurado para exigir revisores, este job se pausará y esperará la aprobación antes de ejecutar cualquier paso. El aprobador ve exactamente qué commit y qué ejecución de workflow dispararon el despliegue.
Entornos protegidos de GitLab
GitLab ofrece entornos protegidos que restringen qué usuarios o grupos pueden disparar despliegues. Combinado con jobs manuales, esto crea un workflow de aprobación robusto.
# .gitlab-ci.yml
deploy_production:
stage: deploy
environment:
name: production
url: https://app.example.com
rules:
- if: $CI_COMMIT_BRANCH == "main"
when: manual
script:
- cosign verify --key cosign.pub $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
- helm upgrade --install myapp ./chart
--set image.tag=$CI_COMMIT_SHA
--namespace production
resource_group: production
La directiva when: manual requiere que un usuario haga clic en «Play» en la interfaz de GitLab. El resource_group garantiza que solo se ejecute un despliegue a la vez, previniendo condiciones de carrera.
Aprobaciones basadas en Slack y ChatOps
Para los equipos que viven en Slack, integrar los workflows de aprobación con el chat proporciona visibilidad y tiempos de respuesta rápidos. Herramientas como Opsgenie, PagerDuty o bots de Slack personalizados pueden publicar una solicitud de despliegue en un canal y esperar a que un usuario autorizado la apruebe mediante un botón o una reacción. El requisito clave es que el mecanismo de aprobación sea auditable y no pueda falsificarse: usa tokens verificados de la app de Slack y registra cada decisión de aprobación.
Verificación de artefactos en el momento del despliegue
Firmar los artefactos durante la fase de build es solo la mitad de la ecuación. Debes verificar esas firmas en el momento del despliegue. De lo contrario, un atacante que obtenga acceso a tu registro puede sustituir una imagen firmada por una maliciosa sin firmar o vuelta a firmar.
Verificación con Cosign antes del despliegue
Añade un paso de verificación explícito en tu pipeline de despliegue que se ejecute antes de cualquier comando de despliegue. Si la verificación falla, el pipeline debe detenerse de inmediato.
# Verify the image signature before deploying
cosign verify \
--certificate-identity "https://github.com/myorg/myapp/.github/workflows/build.yml@refs/heads/main" \
--certificate-oidc-issuer "https://token.actions.githubusercontent.com" \
ghcr.io/myorg/myapp@sha256:abc123...
# Verify SLSA provenance
cosign verify-attestation \
--type slsaprovenance \
--certificate-identity "https://github.com/slsa-framework/slsa-github-generator/.github/workflows/generator_container_slsa3.yml@refs/tags/v1.9.0" \
--certificate-oidc-issuer "https://token.actions.githubusercontent.com" \
ghcr.io/myorg/myapp@sha256:abc123...
Admission controllers: Kyverno y Sigstore Policy Controller
La verificación a nivel de pipeline es buena, pero puede eludirse si alguien despliega directamente en el clúster usando kubectl. Los admission controllers aplican la verificación a nivel del servidor de la API de Kubernetes: ninguna imagen sin firmar puede entrar en el clúster, independientemente de cómo se haya enviado.
Kyverno es un motor de políticas nativo de Kubernetes que puede verificar las firmas y attestations de las imágenes como parte de su webhook de admisión:
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: require-signed-images
spec:
validationFailureAction: Enforce
background: false
rules:
- name: verify-signature
match:
any:
- resources:
kinds:
- Pod
verifyImages:
- imageReferences:
- "ghcr.io/myorg/*"
attestors:
- entries:
- keyless:
subject: "https://github.com/myorg/*"
issuer: "https://token.actions.githubusercontent.com"
attestations:
- type: https://slsa.dev/provenance/v1
conditions:
- all:
- key: "{{ builder.id }}"
operator: Equals
value: "https://github.com/slsa-framework/slsa-github-generator/.github/workflows/generator_container_slsa3.yml@refs/tags/v1.9.0"
El Sigstore Policy Controller (antes cosigned) proporciona una funcionalidad similar y es mantenido por el proyecto Sigstore. Se integra estrechamente con los workflows de firma sin claves y es una opción sólida si tu organización se ha estandarizado en el ecosistema Sigstore.
La combinación de la verificación a nivel de pipeline y el control de admisión a nivel de clúster crea defensa en profundidad: incluso si se elude una capa, la otra atrapa los artefactos no autorizados.
Despliegues progresivos: canary, blue-green y feature flags
Desplegar una nueva versión al 100 % del tráfico de forma instantánea es un riesgo de seguridad y de fiabilidad. Las estrategias de despliegue progresivo te permiten detectar problemas —incluidos los de seguridad— antes de que afecten a todos los usuarios.
Despliegues canary
Un despliegue canary enruta un pequeño porcentaje del tráfico (por ejemplo, el 5 %) a la nueva versión mientras la mayoría sigue accediendo a la release estable. Si métricas como las tasas de error, la latencia o las señales de seguridad (conexiones salientes inesperadas, escaladas de privilegios elevadas) se degradan, el canary se revierte automáticamente.
Herramientas como Flagger (para Kubernetes), AWS App Mesh e Istio automatizan el análisis de canary. Flagger, por ejemplo, puede configurarse para monitorizar métricas personalizadas de Prometheus y promover o revertir automáticamente:
apiVersion: flagger.app/v1beta1
kind: Canary
metadata:
name: myapp
namespace: production
spec:
targetRef:
apiVersion: apps/v1
kind: Deployment
name: myapp
progressDeadlineSeconds: 600
service:
port: 8080
analysis:
interval: 1m
threshold: 5
maxWeight: 50
stepWeight: 10
metrics:
- name: request-success-rate
thresholdRange:
min: 99
interval: 1m
- name: request-duration
thresholdRange:
max: 500
interval: 1m
Despliegues blue-green
Los despliegues blue-green mantienen dos entornos idénticos. El entorno «blue» ejecuta la versión actual; el «green» ejecuta la nueva. El tráfico se conmuta de una sola vez (normalmente mediante un balanceador de carga o un cambio de DNS) después de que el entorno green pase las comprobaciones de salud y la validación de seguridad. Si algo va mal, volver a blue es instantáneo.
El beneficio de seguridad es una ruta de rollback limpia y predecible. No hay un estado parcial sobre el que razonar, y la versión anterior permanece plenamente operativa durante todo el despliegue.
Feature flags como controles de seguridad
Las feature flags desacoplan el despliegue de la publicación. El código se despliega en producción pero permanece inactivo detrás de una flag. Esto da a los equipos de seguridad un interruptor de emergencia: si una funcionalidad recién publicada introduce una vulnerabilidad o se comporta de forma inesperada, puede desactivarse al instante sin un rollback completo. Herramientas como LaunchDarkly, Unleash y OpenFeature proporcionan una gestión centralizada de flags con logs de auditoría de quién activó qué y cuándo.
Estrategias de rollback
Todo plan de despliegue debe incluir un plan de rollback. Cuando las cosas van mal —y lo harán—, la velocidad y la fiabilidad de tu rollback determinan directamente el radio de impacto.
Rollback automatizado ante fallo de comprobación de salud
Kubernetes admite de forma nativa el rollback a través de su controlador de deployment. Si los nuevos pods fallan las readiness o liveness probes, el rollout se detiene y puede revertirse automáticamente:
# Check rollout status and rollback if needed
kubectl rollout status deployment/myapp --namespace production --timeout=300s
if [ $? -ne 0 ]; then
echo "Rollout failed, initiating rollback"
kubectl rollout undo deployment/myapp --namespace production
exit 1
fi
En un modelo GitOps, el rollback significa revertir el commit de Git que introdujo el cambio. El controlador detecta la reversión y reconcilia el clúster de vuelta al estado anterior. Esto preserva el rastro de auditoría completo en Git.
Despliegues inmutables
Los despliegues inmutables tratan cada release como una instancia nueva y desechable. En lugar de actualizar los contenedores in situ, despliegas un conjunto de recursos completamente nuevo y desmantelas los antiguos. Esto elimina la deriva de configuración y garantiza que lo que se probó sea exactamente lo que se ejecuta en producción. Combinados con digests de imagen (en lugar de tags mutables como latest), los despliegues inmutables garantizan la reproducibilidad binaria.
Separación de las identidades de build y de deploy
Una de las mejoras de seguridad más impactantes que puedes hacer es garantizar que la identidad usada para construir los artefactos sea distinta de la identidad usada para desplegarlos. Esto limita el radio de impacto de un compromiso en cualquiera de las dos fases.
Credenciales distintas
El pipeline de build debería tener credenciales para hacer push de imágenes a un registro y firmarlas, pero ningún acceso a la infraestructura de producción. El pipeline de despliegue (o el controlador de GitOps) debería tener credenciales para descargar imágenes y aplicar manifiestos, pero ningún acceso a los repositorios de código fuente ni a las claves de firma.
En la práctica, esto significa usar cuentas de servicio, roles de IAM o claims de OIDC separados para cada fase. En AWS, el rol de build podría tener permisos para el push a ECR y la firma con KMS, mientras que el rol de deploy tiene permisos para EKS y Secrets Manager pero no para el push a ECR.
Runners distintos
Lleva la separación más lejos ejecutando los jobs de build y de deploy en runners físicamente distintos. Los jobs de build se ejecutan en runners efímeros y de propósito general. Los jobs de deploy se ejecutan en runners dedicados y fortalecidos que se sitúan dentro de una frontera de red más cercana al entorno de producción. Esto impide que un runner de build comprometido pivote hacia producción.
Para un tratamiento más profundo de la separación de identidades y los principios de mínimo privilegio en CI/CD, consulta nuestra guía sobre Separación de funciones y mínimo privilegio en pipelines CI/CD.
Congelaciones de despliegue y ventanas de cambio
No todo momento es un buen momento para desplegar. Las congelaciones de despliegue —periodos durante los cuales se prohíben los cambios en producción— reducen el riesgo durante eventos de alto tráfico, festivos, transiciones de guardia o respuesta activa a incidentes.
Implementa las congelaciones a nivel de plataforma, no solo como un acuerdo de equipo. Los Environments de GitHub admiten deployment branch policies y wait timers. GitLab permite deploy freezes configurados mediante la interfaz o la API con programaciones de estilo cron. Para los workflows basados en Kubernetes, puedes hacer cumplir las congelaciones con una política de OPA/Gatekeeper o Kyverno que rechace los despliegues durante ventanas de tiempo específicas.
# Kyverno policy to enforce deployment freeze
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: deployment-freeze
spec:
validationFailureAction: Enforce
background: false
rules:
- name: block-deployments-during-freeze
match:
any:
- resources:
kinds:
- Deployment
namespaces:
- production
preconditions:
all:
- key: "{{ time_now() }}"
operator: GreaterThan
value: "2026-03-27T00:00:00Z" # Freeze start
- key: "{{ time_now() }}"
operator: LessThan
value: "2026-03-30T00:00:00Z" # Freeze end
validate:
message: "Production deployments are frozen until March 30. Contact platform-team for emergency exceptions."
deny: {}
Documenta un proceso de excepción para los parches de seguridad de emergencia que necesiten desplegarse durante una congelación, incluyendo quién puede autorizar la excepción y cómo se registra.
Rastro de auditoría: vincular los despliegues con commits, aprobadores y ejecuciones del pipeline
Un workflow de despliegue seguro produce un rastro de auditoría completo y a prueba de manipulaciones. Para cada despliegue de producción, deberías poder responder: ¿qué se desplegó? ¿quién lo aprobó? ¿qué pipeline lo construyó? ¿A qué commit se remonta?
Logs de auditoría a nivel de plataforma
AWS CloudTrail registra las llamadas a la API de EKS, ECS y Lambda, incluidos quién inició el despliegue y desde qué origen. Los Audit Logs de GCP proporcionan una cobertura similar para GKE y Cloud Run. Asegúrate de que estos logs se envíen a un almacén de logs inmutable y centralizado (como un bucket de S3 dedicado con object lock o un SIEM) donde no puedan ser manipulados por un atacante que haya comprometido el entorno de despliegue.
Trazabilidad a nivel de pipeline
Anota los recursos de Kubernetes con metadatos de despliegue para poder trazar desde un pod en ejecución hasta el origen exacto:
# Include in your Helm chart or Kustomize overlay
metadata:
labels:
app.kubernetes.io/version: "{{ .Values.image.tag }}"
annotations:
deploy.example.com/commit-sha: "{{ .Values.commitSha }}"
deploy.example.com/pipeline-url: "{{ .Values.pipelineUrl }}"
deploy.example.com/approved-by: "{{ .Values.approvedBy }}"
deploy.example.com/deployed-at: "{{ now | date \"2006-01-02T15:04:05Z\" }}"
En GitHub Actions, pasa estos valores a través del workflow de despliegue:
- name: Deploy with traceability
run: |
helm upgrade --install myapp ./chart \
--set image.tag=${{ github.sha }} \
--set commitSha=${{ github.sha }} \
--set pipelineUrl="https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}" \
--set approvedBy="${{ github.actor }}" \
--namespace production
Monitorización posterior al despliegue
El despliegue no termina cuando la nueva versión está en ejecución. La monitorización posterior al despliegue cierra el bucle de retroalimentación y detecta los problemas que las comprobaciones previas al despliegue pasaron por alto.
Detección de anomalías
Establece métricas de referencia para el comportamiento normal de la aplicación: tasas de peticiones, tasas de error, percentiles de latencia, uso de CPU/memoria y patrones de conexión de red. Después de cada despliegue, compara las métricas actuales con la referencia. Herramientas como Prometheus + Alertmanager, Datadog y Grafana Alerting pueden disparar alertas cuando las métricas posteriores al despliegue se desvían más allá de los umbrales.
Desde una perspectiva de seguridad, presta especial atención a las conexiones de red salientes inesperadas, los nuevos procesos generados dentro de los contenedores, las llamadas al sistema elevadas y los aumentos repentinos de los fallos de autenticación. Estos pueden indicar que un artefacto comprometido superó el pipeline.
Métricas DORA para la seguridad
Las cuatro métricas DORA —frecuencia de despliegue, lead time de los cambios, tasa de fallos de cambio y tiempo medio de recuperación— suelen usarse para medir el rendimiento de DevOps. Son igual de valiosas para la seguridad:
- La frecuencia de despliegue indica con qué frecuencia puedes enviar parches de seguridad. Una mayor frecuencia significa una remediación más rápida.
- El lead time de los cambios mide la rapidez con la que una corrección de seguridad pasa del commit a producción. Los lead times largos suponen ventanas de exposición prolongadas.
- La tasa de fallos de cambio rastrea con qué frecuencia los despliegues causan incidentes. Una tasa alta sugiere pruebas o verificación inadecuadas, una preocupación de seguridad.
- El tiempo medio de recuperación (MTTR) mide la rapidez con la que puedes revertir o remediar un despliegue defectuoso. Un MTTR bajo limita el radio de impacto de cualquier incidente, incluida una brecha de seguridad.
Haz un seguimiento de estas métricas por entorno y correlaciónalas con los eventos de seguridad. Si tu tasa de fallos de cambio se dispara tras adoptar un nuevo patrón de despliegue, investiga antes de que se convierta en una responsabilidad de seguridad.
Poniéndolo todo junto: un pipeline de despliegue seguro completo
Este es un workflow completo de GitHub Actions que incorpora las prácticas tratadas anteriormente: verificación de artefactos, aprobaciones basadas en entornos, trazabilidad del despliegue y rollback automatizado:
# .github/workflows/secure-deploy.yml
name: Secure Deployment
on:
workflow_run:
workflows: ["Build and Sign"]
types: [completed]
branches: [main]
jobs:
verify-and-deploy:
runs-on: ubuntu-latest
if: ${{ github.event.workflow_run.conclusion == 'success' }}
environment:
name: production
url: https://app.example.com
permissions:
id-token: write
contents: read
steps:
- name: Checkout manifests
uses: actions/checkout@v4
- name: Install cosign
uses: sigstore/cosign-installer@v3
- name: Verify image signature (keyless)
run: |
IMAGE="ghcr.io/myorg/myapp@${{ github.event.workflow_run.head_sha }}"
cosign verify \
--certificate-identity "https://github.com/myorg/myapp/.github/workflows/build.yml@refs/heads/main" \
--certificate-oidc-issuer "https://token.actions.githubusercontent.com" \
"$IMAGE"
- name: Verify SLSA provenance
run: |
IMAGE="ghcr.io/myorg/myapp@${{ github.event.workflow_run.head_sha }}"
cosign verify-attestation \
--type slsaprovenance \
--certificate-identity "https://github.com/slsa-framework/slsa-github-generator/.github/workflows/generator_container_slsa3.yml@refs/tags/v1.9.0" \
--certificate-oidc-issuer "https://token.actions.githubusercontent.com" \
"$IMAGE"
- name: Configure AWS credentials (deploy role)
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::123456789012:role/deploy-production
aws-region: us-east-1
- name: Deploy to EKS
run: |
aws eks update-kubeconfig --name production-cluster
helm upgrade --install myapp ./chart \
--set image.tag=${{ github.event.workflow_run.head_sha }} \
--set commitSha=${{ github.event.workflow_run.head_sha }} \
--set pipelineUrl="https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}" \
--set approvedBy="${{ github.actor }}" \
--namespace production \
--wait --timeout 300s
- name: Verify rollout
run: |
kubectl rollout status deployment/myapp \
--namespace production --timeout=300s
- name: Rollback on failure
if: failure()
run: |
echo "Deployment failed — initiating rollback"
kubectl rollout undo deployment/myapp --namespace production
echo "::error::Deployment rolled back due to failure"
Y el pipeline equivalente de GitLab CI con controles similares:
# .gitlab-ci.yml
stages:
- verify
- deploy
- validate
verify_artifact:
stage: verify
image: bitnami/cosign:latest
script:
- cosign verify
--certificate-identity "https://gitlab.com/myorg/myapp//.gitlab-ci.yml@refs/heads/main"
--certificate-oidc-issuer "https://gitlab.com"
$CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
rules:
- if: $CI_COMMIT_BRANCH == "main"
deploy_production:
stage: deploy
environment:
name: production
url: https://app.example.com
resource_group: production
needs: [verify_artifact]
rules:
- if: $CI_COMMIT_BRANCH == "main"
when: manual
script:
- aws eks update-kubeconfig --name production-cluster
- helm upgrade --install myapp ./chart
--set image.tag=$CI_COMMIT_SHA
--set commitSha=$CI_COMMIT_SHA
--set pipelineUrl=$CI_PIPELINE_URL
--set approvedBy=$GITLAB_USER_LOGIN
--namespace production
--wait --timeout 300s
validate_deployment:
stage: validate
needs: [deploy_production]
script:
- kubectl rollout status deployment/myapp --namespace production --timeout=300s
after_script:
- |
if [ "$CI_JOB_STATUS" == "failed" ]; then
echo "Rolling back deployment"
kubectl rollout undo deployment/myapp --namespace production
fi
rules:
- if: $CI_COMMIT_BRANCH == "main"
Resumen y guías relacionadas
Los workflows de despliegue seguros requieren defensa en profundidad en cada fase: elegir el modelo de despliegue adecuado, aplicar puertas y aprobaciones, verificar los artefactos en la frontera del clúster, desplegar los cambios de forma progresiva, mantener rutas de rollback limpias, separar las identidades de build y de deploy, respetar las ventanas de cambio y registrarlo todo. Ningún control por sí solo es suficiente. La combinación de la verificación a nivel de pipeline, la aplicación mediante admission controller, los despliegues progresivos y el registro de auditoría integral crea un proceso de despliegue que es a la vez rápido y seguro.
Continúa desarrollando tu conocimiento sobre CI/CD seguro con estas guías relacionadas:
- Separación de funciones y mínimo privilegio en pipelines CI/CD — Análisis en profundidad de la separación de identidades, las credenciales con alcance limitado y el principio de mínimo privilegio en todo tu pipeline.
- Patrones defensivos y mitigaciones para los ataques a pipelines CI/CD — Contramedidas prácticas para los vectores de ataque más habituales dirigidos a los sistemas CI/CD.