{"id":644,"date":"2026-01-31T12:15:30","date_gmt":"2026-01-31T11:15:30","guid":{"rendered":"https:\/\/secure-pipelines.com\/?p=644"},"modified":"2026-07-08T22:45:38","modified_gmt":"2026-07-08T21:45:38","slug":"secure-deployment-workflows-ci-cd-pipeline-production","status":"publish","type":"post","link":"https:\/\/secure-pipelines.com\/es\/ci-cd-security\/secure-deployment-workflows-ci-cd-pipeline-production\/","title":{"rendered":"Workflows de Despliegue Seguros: Del Pipeline CI\/CD a Producci\u00f3n"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">Tu pipeline CI\/CD puede tener controles de seguridad herm\u00e9ticos \u2014commits firmados, dependencias fijadas, escaneos SAST, firma de im\u00e1genes de contenedor\u2014, pero nada de eso importa si el propio proceso de despliegue es d\u00e9bil. El despliegue es el punto cr\u00edtico donde la seguridad del pipeline se encuentra con la seguridad de producci\u00f3n. Un workflow de despliegue comprometido puede eludir todos los controles previos que has construido, empujando c\u00f3digo malicioso directamente al entorno del que dependen tus clientes.<\/p>\n\n<p class=\"wp-block-paragraph\">Esta gu\u00eda cubre c\u00f3mo 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\u00eda completo desde el commit hasta producci\u00f3n.<\/p>\n\n<h2 class=\"wp-block-heading\">Modelos de despliegue: basado en push frente a basado en pull (GitOps)<\/h2>\n\n<p class=\"wp-block-paragraph\">La primera decisi\u00f3n arquitect\u00f3nica que define tu postura de seguridad de despliegue es si usas un modelo basado en push o basado en pull.<\/p>\n\n<h3 class=\"wp-block-heading\">Despliegues basados en push (dirigidos por CI)<\/h3>\n\n<p class=\"wp-block-paragraph\">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 <code>kubectl apply<\/code>, o un job de GitLab CI ejecuta <code>helm upgrade<\/code> contra un cl\u00faster. El propio pipeline posee las credenciales del entorno de producci\u00f3n.<\/p>\n\n<p class=\"wp-block-paragraph\">Este modelo es sencillo, pero conlleva un riesgo inherente: el runner de CI tiene acceso de escritura directo a producci\u00f3n. Si un atacante compromete el pipeline \u2014a trav\u00e9s de una dependencia envenenada, un pull request malicioso o un secreto robado\u2014, hereda ese acceso a producci\u00f3n de inmediato.<\/p>\n\n<h3 class=\"wp-block-heading\">Despliegues basados en pull (GitOps)<\/h3>\n\n<p class=\"wp-block-paragraph\">En un modelo basado en pull o GitOps, un controlador dedicado que se ejecuta dentro del entorno de destino \u2014como <strong>Flux<\/strong> o <strong>ArgoCD<\/strong>\u2014 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\u00faster para que coincida.<\/p>\n\n<p class=\"wp-block-paragraph\">La ventaja de seguridad es significativa. El pipeline de CI nunca necesita credenciales directas del cl\u00faster de producci\u00f3n. La superficie de ataque se reduce porque el agente de despliegue vive dentro del cl\u00faster y solo extrae de una fuente conocida. La detecci\u00f3n de deriva viene incorporada: si alguien modifica manualmente un recurso, el controlador lo revierte para que coincida con Git.<\/p>\n\n<p class=\"wp-block-paragraph\"><strong>Recomendaci\u00f3n:<\/strong> para las cargas de trabajo de producci\u00f3n, 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\u00e1s que un control de acceso estricto. Incluso en configuraciones basadas en push, aplica el principio de m\u00ednimo privilegio con rigor a las credenciales de despliegue.<\/p>\n\n<h2 class=\"wp-block-heading\">Puertas de despliegue: aprobaciones manuales y entornos protegidos<\/h2>\n\n<p class=\"wp-block-paragraph\">Los pipelines automatizados son r\u00e1pidos, pero desplegar en producci\u00f3n no deber\u00eda ocurrir sin verificaci\u00f3n humana en el caso de los cambios de alto impacto. Las puertas de despliegue introducen puntos de control que exigen una aprobaci\u00f3n expl\u00edcita antes de que una release contin\u00fae.<\/p>\n\n<h3 class=\"wp-block-heading\">Environments de GitHub y revisores obligatorios<\/h3>\n\n<p class=\"wp-block-paragraph\">GitHub Actions admite <strong>Environments<\/strong> con reglas de protecci\u00f3n. Puedes exigir que uno o m\u00e1s 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\u00f3digo del pipeline no puede eludirlo.<\/p>\n\n<pre><code># .github\/workflows\/deploy.yml\njobs:\n  deploy-production:\n    runs-on: ubuntu-latest\n    environment:\n      name: production\n      url: https:\/\/app.example.com\n    steps:\n      - name: Checkout\n        uses: actions\/checkout@v4\n\n      - name: Verify artifact signature\n        run: |\n          cosign verify \\\n            --key cosign.pub \\\n            ghcr.io\/myorg\/myapp:${{ github.sha }}\n\n      - name: Deploy to production\n        run: |\n          helm upgrade --install myapp .\/chart \\\n            --set image.tag=${{ github.sha }} \\\n            --namespace production\n<\/code><\/pre>\n\n<p class=\"wp-block-paragraph\">Con el entorno <code>production<\/code> configurado para exigir revisores, este job se pausar\u00e1 y esperar\u00e1 la aprobaci\u00f3n antes de ejecutar cualquier paso. El aprobador ve exactamente qu\u00e9 commit y qu\u00e9 ejecuci\u00f3n de workflow dispararon el despliegue.<\/p>\n\n<h3 class=\"wp-block-heading\">Entornos protegidos de GitLab<\/h3>\n\n<p class=\"wp-block-paragraph\">GitLab ofrece <strong>entornos protegidos<\/strong> que restringen qu\u00e9 usuarios o grupos pueden disparar despliegues. Combinado con jobs manuales, esto crea un workflow de aprobaci\u00f3n robusto.<\/p>\n\n<pre><code># .gitlab-ci.yml\ndeploy_production:\n  stage: deploy\n  environment:\n    name: production\n    url: https:\/\/app.example.com\n  rules:\n    - if: $CI_COMMIT_BRANCH == \"main\"\n      when: manual\n  script:\n    - cosign verify --key cosign.pub $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA\n    - helm upgrade --install myapp .\/chart\n        --set image.tag=$CI_COMMIT_SHA\n        --namespace production\n  resource_group: production\n<\/code><\/pre>\n\n<p class=\"wp-block-paragraph\">La directiva <code>when: manual<\/code> requiere que un usuario haga clic en \u00abPlay\u00bb en la interfaz de GitLab. El <code>resource_group<\/code> garantiza que solo se ejecute un despliegue a la vez, previniendo condiciones de carrera.<\/p>\n\n<h3 class=\"wp-block-heading\">Aprobaciones basadas en Slack y ChatOps<\/h3>\n\n<p class=\"wp-block-paragraph\">Para los equipos que viven en Slack, integrar los workflows de aprobaci\u00f3n con el chat proporciona visibilidad y tiempos de respuesta r\u00e1pidos. Herramientas como <strong>Opsgenie<\/strong>, <strong>PagerDuty<\/strong> 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\u00f3n o una reacci\u00f3n. El requisito clave es que el mecanismo de aprobaci\u00f3n sea auditable y no pueda falsificarse: usa tokens verificados de la app de Slack y registra cada decisi\u00f3n de aprobaci\u00f3n.<\/p>\n\n<h2 class=\"wp-block-heading\">Verificaci\u00f3n de artefactos en el momento del despliegue<\/h2>\n\n<p class=\"wp-block-paragraph\">Firmar los artefactos durante la fase de build es solo la mitad de la ecuaci\u00f3n. Debes <strong>verificar<\/strong> 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.<\/p>\n\n<h3 class=\"wp-block-heading\">Verificaci\u00f3n con Cosign antes del despliegue<\/h3>\n\n<p class=\"wp-block-paragraph\">A\u00f1ade un paso de verificaci\u00f3n expl\u00edcito en tu pipeline de despliegue que se ejecute antes de cualquier comando de despliegue. Si la verificaci\u00f3n falla, el pipeline debe detenerse de inmediato.<\/p>\n\n<pre><code># Verify the image signature before deploying\ncosign verify \\\n  --certificate-identity \"https:\/\/github.com\/myorg\/myapp\/.github\/workflows\/build.yml@refs\/heads\/main\" \\\n  --certificate-oidc-issuer \"https:\/\/token.actions.githubusercontent.com\" \\\n  ghcr.io\/myorg\/myapp@sha256:abc123...\n\n# Verify SLSA provenance\ncosign verify-attestation \\\n  --type slsaprovenance \\\n  --certificate-identity \"https:\/\/github.com\/slsa-framework\/slsa-github-generator\/.github\/workflows\/generator_container_slsa3.yml@refs\/tags\/v1.9.0\" \\\n  --certificate-oidc-issuer \"https:\/\/token.actions.githubusercontent.com\" \\\n  ghcr.io\/myorg\/myapp@sha256:abc123...\n<\/code><\/pre>\n\n<h3 class=\"wp-block-heading\">Admission controllers: Kyverno y Sigstore Policy Controller<\/h3>\n\n<p class=\"wp-block-paragraph\">La verificaci\u00f3n a nivel de pipeline es buena, pero puede eludirse si alguien despliega directamente en el cl\u00faster usando <code>kubectl<\/code>. Los <strong>admission controllers<\/strong> aplican la verificaci\u00f3n a nivel del servidor de la API de Kubernetes: ninguna imagen sin firmar puede entrar en el cl\u00faster, independientemente de c\u00f3mo se haya enviado.<\/p>\n\n<p class=\"wp-block-paragraph\"><strong>Kyverno<\/strong> es un motor de pol\u00edticas nativo de Kubernetes que puede verificar las firmas y attestations de las im\u00e1genes como parte de su webhook de admisi\u00f3n:<\/p>\n\n<pre><code>apiVersion: kyverno.io\/v1\nkind: ClusterPolicy\nmetadata:\n  name: require-signed-images\nspec:\n  validationFailureAction: Enforce\n  background: false\n  rules:\n    - name: verify-signature\n      match:\n        any:\n          - resources:\n              kinds:\n                - Pod\n      verifyImages:\n        - imageReferences:\n            - \"ghcr.io\/myorg\/*\"\n          attestors:\n            - entries:\n                - keyless:\n                    subject: \"https:\/\/github.com\/myorg\/*\"\n                    issuer: \"https:\/\/token.actions.githubusercontent.com\"\n          attestations:\n            - type: https:\/\/slsa.dev\/provenance\/v1\n              conditions:\n                - all:\n                    - key: \"{{ builder.id }}\"\n                      operator: Equals\n                      value: \"https:\/\/github.com\/slsa-framework\/slsa-github-generator\/.github\/workflows\/generator_container_slsa3.yml@refs\/tags\/v1.9.0\"\n<\/code><\/pre>\n\n<p class=\"wp-block-paragraph\">El <strong>Sigstore Policy Controller<\/strong> (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\u00f3n s\u00f3lida si tu organizaci\u00f3n se ha estandarizado en el ecosistema Sigstore.<\/p>\n\n<p class=\"wp-block-paragraph\">La combinaci\u00f3n de la verificaci\u00f3n a nivel de pipeline y el control de admisi\u00f3n a nivel de cl\u00faster crea defensa en profundidad: incluso si se elude una capa, la otra atrapa los artefactos no autorizados.<\/p>\n\n<h2 class=\"wp-block-heading\">Despliegues progresivos: canary, blue-green y feature flags<\/h2>\n\n<p class=\"wp-block-paragraph\">Desplegar una nueva versi\u00f3n al 100 % del tr\u00e1fico de forma instant\u00e1nea es un riesgo de seguridad y de fiabilidad. Las estrategias de despliegue progresivo te permiten detectar problemas \u2014incluidos los de seguridad\u2014 antes de que afecten a todos los usuarios.<\/p>\n\n<h3 class=\"wp-block-heading\">Despliegues canary<\/h3>\n\n<p class=\"wp-block-paragraph\">Un despliegue canary enruta un peque\u00f1o porcentaje del tr\u00e1fico (por ejemplo, el 5 %) a la nueva versi\u00f3n mientras la mayor\u00eda sigue accediendo a la release estable. Si m\u00e9tricas como las tasas de error, la latencia o las se\u00f1ales de seguridad (conexiones salientes inesperadas, escaladas de privilegios elevadas) se degradan, el canary se revierte autom\u00e1ticamente.<\/p>\n\n<p class=\"wp-block-paragraph\">Herramientas como <strong>Flagger<\/strong> (para Kubernetes), <strong>AWS App Mesh<\/strong> e <strong>Istio<\/strong> automatizan el an\u00e1lisis de canary. Flagger, por ejemplo, puede configurarse para monitorizar m\u00e9tricas personalizadas de Prometheus y promover o revertir autom\u00e1ticamente:<\/p>\n\n<pre><code>apiVersion: flagger.app\/v1beta1\nkind: Canary\nmetadata:\n  name: myapp\n  namespace: production\nspec:\n  targetRef:\n    apiVersion: apps\/v1\n    kind: Deployment\n    name: myapp\n  progressDeadlineSeconds: 600\n  service:\n    port: 8080\n  analysis:\n    interval: 1m\n    threshold: 5\n    maxWeight: 50\n    stepWeight: 10\n    metrics:\n      - name: request-success-rate\n        thresholdRange:\n          min: 99\n        interval: 1m\n      - name: request-duration\n        thresholdRange:\n          max: 500\n        interval: 1m\n<\/code><\/pre>\n\n<h3 class=\"wp-block-heading\">Despliegues blue-green<\/h3>\n\n<p class=\"wp-block-paragraph\">Los despliegues blue-green mantienen dos entornos id\u00e9nticos. El entorno \u00abblue\u00bb ejecuta la versi\u00f3n actual; el \u00abgreen\u00bb ejecuta la nueva. El tr\u00e1fico se conmuta de una sola vez (normalmente mediante un balanceador de carga o un cambio de DNS) despu\u00e9s de que el entorno green pase las comprobaciones de salud y la validaci\u00f3n de seguridad. Si algo va mal, volver a blue es instant\u00e1neo.<\/p>\n\n<p class=\"wp-block-paragraph\">El beneficio de seguridad es una ruta de rollback limpia y predecible. No hay un estado parcial sobre el que razonar, y la versi\u00f3n anterior permanece plenamente operativa durante todo el despliegue.<\/p>\n\n<h3 class=\"wp-block-heading\">Feature flags como controles de seguridad<\/h3>\n\n<p class=\"wp-block-paragraph\">Las feature flags desacoplan el despliegue de la publicaci\u00f3n. El c\u00f3digo se despliega en producci\u00f3n pero permanece inactivo detr\u00e1s de una flag. Esto da a los equipos de seguridad un interruptor de emergencia: si una funcionalidad reci\u00e9n publicada introduce una vulnerabilidad o se comporta de forma inesperada, puede desactivarse al instante sin un rollback completo. Herramientas como <strong>LaunchDarkly<\/strong>, <strong>Unleash<\/strong> y <strong>OpenFeature<\/strong> proporcionan una gesti\u00f3n centralizada de flags con logs de auditor\u00eda de qui\u00e9n activ\u00f3 qu\u00e9 y cu\u00e1ndo.<\/p>\n\n<h2 class=\"wp-block-heading\">Estrategias de rollback<\/h2>\n\n<p class=\"wp-block-paragraph\">Todo plan de despliegue debe incluir un plan de rollback. Cuando las cosas van mal \u2014y lo har\u00e1n\u2014, la velocidad y la fiabilidad de tu rollback determinan directamente el radio de impacto.<\/p>\n\n<h3 class=\"wp-block-heading\">Rollback automatizado ante fallo de comprobaci\u00f3n de salud<\/h3>\n\n<p class=\"wp-block-paragraph\">Kubernetes admite de forma nativa el rollback a trav\u00e9s de su controlador de deployment. Si los nuevos pods fallan las readiness o liveness probes, el rollout se detiene y puede revertirse autom\u00e1ticamente:<\/p>\n\n<pre><code># Check rollout status and rollback if needed\nkubectl rollout status deployment\/myapp --namespace production --timeout=300s\nif [ $? -ne 0 ]; then\n  echo \"Rollout failed, initiating rollback\"\n  kubectl rollout undo deployment\/myapp --namespace production\n  exit 1\nfi\n<\/code><\/pre>\n\n<p class=\"wp-block-paragraph\">En un modelo GitOps, el rollback significa revertir el commit de Git que introdujo el cambio. El controlador detecta la reversi\u00f3n y reconcilia el cl\u00faster de vuelta al estado anterior. Esto preserva el rastro de auditor\u00eda completo en Git.<\/p>\n\n<h3 class=\"wp-block-heading\">Despliegues inmutables<\/h3>\n\n<p class=\"wp-block-paragraph\">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\u00f3n y garantiza que lo que se prob\u00f3 sea exactamente lo que se ejecuta en producci\u00f3n. Combinados con digests de imagen (en lugar de tags mutables como <code>latest<\/code>), los despliegues inmutables garantizan la reproducibilidad binaria.<\/p>\n\n<h2 class=\"wp-block-heading\">Separaci\u00f3n de las identidades de build y de deploy<\/h2>\n\n<p class=\"wp-block-paragraph\">Una de las mejoras de seguridad m\u00e1s 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.<\/p>\n\n<h3 class=\"wp-block-heading\">Credenciales distintas<\/h3>\n\n<p class=\"wp-block-paragraph\">El pipeline de build deber\u00eda tener credenciales para hacer push de im\u00e1genes a un registro y firmarlas, pero ning\u00fan acceso a la infraestructura de producci\u00f3n. El pipeline de despliegue (o el controlador de GitOps) deber\u00eda tener credenciales para descargar im\u00e1genes y aplicar manifiestos, pero ning\u00fan acceso a los repositorios de c\u00f3digo fuente ni a las claves de firma.<\/p>\n\n<p class=\"wp-block-paragraph\">En la pr\u00e1ctica, esto significa usar cuentas de servicio, roles de IAM o claims de OIDC separados para cada fase. En AWS, el rol de build podr\u00eda 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.<\/p>\n\n<h3 class=\"wp-block-heading\">Runners distintos<\/h3>\n\n<p class=\"wp-block-paragraph\">Lleva la separaci\u00f3n m\u00e1s lejos ejecutando los jobs de build y de deploy en runners f\u00edsicamente distintos. Los jobs de build se ejecutan en runners ef\u00edmeros y de prop\u00f3sito general. Los jobs de deploy se ejecutan en runners dedicados y fortalecidos que se sit\u00faan dentro de una frontera de red m\u00e1s cercana al entorno de producci\u00f3n. Esto impide que un runner de build comprometido pivote hacia producci\u00f3n.<\/p>\n\n<p class=\"wp-block-paragraph\">Para un tratamiento m\u00e1s profundo de la separaci\u00f3n de identidades y los principios de m\u00ednimo privilegio en CI\/CD, consulta nuestra gu\u00eda sobre <a href=\"https:\/\/secure-pipelines.com\/es\/ci-cd-security\/separation-of-duties-least-privilege-ci-cd-pipelines\/\">Separaci\u00f3n de funciones y m\u00ednimo privilegio en pipelines CI\/CD<\/a>.<\/p>\n\n<h2 class=\"wp-block-heading\">Congelaciones de despliegue y ventanas de cambio<\/h2>\n\n<p class=\"wp-block-paragraph\">No todo momento es un buen momento para desplegar. Las congelaciones de despliegue \u2014periodos durante los cuales se proh\u00edben los cambios en producci\u00f3n\u2014 reducen el riesgo durante eventos de alto tr\u00e1fico, festivos, transiciones de guardia o respuesta activa a incidentes.<\/p>\n\n<p class=\"wp-block-paragraph\">Implementa las congelaciones a nivel de plataforma, no solo como un acuerdo de equipo. Los Environments de GitHub admiten <strong>deployment branch policies<\/strong> y <strong>wait timers<\/strong>. GitLab permite <strong>deploy freezes<\/strong> 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\u00edtica de OPA\/Gatekeeper o Kyverno que rechace los despliegues durante ventanas de tiempo espec\u00edficas.<\/p>\n\n<pre><code># Kyverno policy to enforce deployment freeze\napiVersion: kyverno.io\/v1\nkind: ClusterPolicy\nmetadata:\n  name: deployment-freeze\nspec:\n  validationFailureAction: Enforce\n  background: false\n  rules:\n    - name: block-deployments-during-freeze\n      match:\n        any:\n          - resources:\n              kinds:\n                - Deployment\n              namespaces:\n                - production\n      preconditions:\n        all:\n          - key: \"{{ time_now() }}\"\n            operator: GreaterThan\n            value: \"2026-03-27T00:00:00Z\"  # Freeze start\n          - key: \"{{ time_now() }}\"\n            operator: LessThan\n            value: \"2026-03-30T00:00:00Z\"  # Freeze end\n      validate:\n        message: \"Production deployments are frozen until March 30. Contact platform-team for emergency exceptions.\"\n        deny: {}\n<\/code><\/pre>\n\n<p class=\"wp-block-paragraph\">Documenta un proceso de excepci\u00f3n para los parches de seguridad de emergencia que necesiten desplegarse durante una congelaci\u00f3n, incluyendo qui\u00e9n puede autorizar la excepci\u00f3n y c\u00f3mo se registra.<\/p>\n\n<h2 class=\"wp-block-heading\">Rastro de auditor\u00eda: vincular los despliegues con commits, aprobadores y ejecuciones del pipeline<\/h2>\n\n<p class=\"wp-block-paragraph\">Un workflow de despliegue seguro produce un rastro de auditor\u00eda completo y a prueba de manipulaciones. Para cada despliegue de producci\u00f3n, deber\u00edas poder responder: \u00bf<em>qu\u00e9<\/em> se despleg\u00f3? \u00bf<em>qui\u00e9n<\/em> lo aprob\u00f3? \u00bf<em>qu\u00e9<\/em> pipeline lo construy\u00f3? \u00bfA <em>qu\u00e9<\/em> commit se remonta?<\/p>\n\n<h3 class=\"wp-block-heading\">Logs de auditor\u00eda a nivel de plataforma<\/h3>\n\n<p class=\"wp-block-paragraph\"><strong>AWS CloudTrail<\/strong> registra las llamadas a la API de EKS, ECS y Lambda, incluidos qui\u00e9n inici\u00f3 el despliegue y desde qu\u00e9 origen. <strong>Los Audit Logs de GCP<\/strong> proporcionan una cobertura similar para GKE y Cloud Run. Aseg\u00farate de que estos logs se env\u00eden a un almac\u00e9n 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.<\/p>\n\n<h3 class=\"wp-block-heading\">Trazabilidad a nivel de pipeline<\/h3>\n\n<p class=\"wp-block-paragraph\">Anota los recursos de Kubernetes con metadatos de despliegue para poder trazar desde un pod en ejecuci\u00f3n hasta el origen exacto:<\/p>\n\n<pre><code># Include in your Helm chart or Kustomize overlay\nmetadata:\n  labels:\n    app.kubernetes.io\/version: \"{{ .Values.image.tag }}\"\n  annotations:\n    deploy.example.com\/commit-sha: \"{{ .Values.commitSha }}\"\n    deploy.example.com\/pipeline-url: \"{{ .Values.pipelineUrl }}\"\n    deploy.example.com\/approved-by: \"{{ .Values.approvedBy }}\"\n    deploy.example.com\/deployed-at: \"{{ now | date \\\"2006-01-02T15:04:05Z\\\" }}\"\n<\/code><\/pre>\n\n<p class=\"wp-block-paragraph\">En GitHub Actions, pasa estos valores a trav\u00e9s del workflow de despliegue:<\/p>\n\n<pre><code>- name: Deploy with traceability\n  run: |\n    helm upgrade --install myapp .\/chart \\\n      --set image.tag=${{ github.sha }} \\\n      --set commitSha=${{ github.sha }} \\\n      --set pipelineUrl=\"https:\/\/github.com\/${{ github.repository }}\/actions\/runs\/${{ github.run_id }}\" \\\n      --set approvedBy=\"${{ github.actor }}\" \\\n      --namespace production\n<\/code><\/pre>\n\n<h2 class=\"wp-block-heading\">Monitorizaci\u00f3n posterior al despliegue<\/h2>\n\n<p class=\"wp-block-paragraph\">El despliegue no termina cuando la nueva versi\u00f3n est\u00e1 en ejecuci\u00f3n. La monitorizaci\u00f3n posterior al despliegue cierra el bucle de retroalimentaci\u00f3n y detecta los problemas que las comprobaciones previas al despliegue pasaron por alto.<\/p>\n\n<h3 class=\"wp-block-heading\">Detecci\u00f3n de anomal\u00edas<\/h3>\n\n<p class=\"wp-block-paragraph\">Establece m\u00e9tricas de referencia para el comportamiento normal de la aplicaci\u00f3n: tasas de peticiones, tasas de error, percentiles de latencia, uso de CPU\/memoria y patrones de conexi\u00f3n de red. Despu\u00e9s de cada despliegue, compara las m\u00e9tricas actuales con la referencia. Herramientas como <strong>Prometheus + Alertmanager<\/strong>, <strong>Datadog<\/strong> y <strong>Grafana Alerting<\/strong> pueden disparar alertas cuando las m\u00e9tricas posteriores al despliegue se desv\u00edan m\u00e1s all\u00e1 de los umbrales.<\/p>\n\n<p class=\"wp-block-paragraph\">Desde una perspectiva de seguridad, presta especial atenci\u00f3n 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\u00f3n. Estos pueden indicar que un artefacto comprometido super\u00f3 el pipeline.<\/p>\n\n<h3 class=\"wp-block-heading\">M\u00e9tricas DORA para la seguridad<\/h3>\n\n<p class=\"wp-block-paragraph\">Las cuatro m\u00e9tricas DORA \u2014frecuencia de despliegue, lead time de los cambios, tasa de fallos de cambio y tiempo medio de recuperaci\u00f3n\u2014 suelen usarse para medir el rendimiento de DevOps. Son igual de valiosas para la seguridad:<\/p>\n\n<ul class=\"wp-block-list\">\n<li><strong>La frecuencia de despliegue<\/strong> indica con qu\u00e9 frecuencia puedes enviar parches de seguridad. Una mayor frecuencia significa una remediaci\u00f3n m\u00e1s r\u00e1pida.<\/li>\n<li><strong>El lead time de los cambios<\/strong> mide la rapidez con la que una correcci\u00f3n de seguridad pasa del commit a producci\u00f3n. Los lead times largos suponen ventanas de exposici\u00f3n prolongadas.<\/li>\n<li><strong>La tasa de fallos de cambio<\/strong> rastrea con qu\u00e9 frecuencia los despliegues causan incidentes. Una tasa alta sugiere pruebas o verificaci\u00f3n inadecuadas, una preocupaci\u00f3n de seguridad.<\/li>\n<li><strong>El tiempo medio de recuperaci\u00f3n (MTTR)<\/strong> 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.<\/li>\n<\/ul>\n\n<p class=\"wp-block-paragraph\">Haz un seguimiento de estas m\u00e9tricas por entorno y correlaci\u00f3nalas con los eventos de seguridad. Si tu tasa de fallos de cambio se dispara tras adoptar un nuevo patr\u00f3n de despliegue, investiga antes de que se convierta en una responsabilidad de seguridad.<\/p>\n\n<h2 class=\"wp-block-heading\">Poni\u00e9ndolo todo junto: un pipeline de despliegue seguro completo<\/h2>\n\n<p class=\"wp-block-paragraph\">Este es un workflow completo de GitHub Actions que incorpora las pr\u00e1cticas tratadas anteriormente: verificaci\u00f3n de artefactos, aprobaciones basadas en entornos, trazabilidad del despliegue y rollback automatizado:<\/p>\n\n<pre><code># .github\/workflows\/secure-deploy.yml\nname: Secure Deployment\n\non:\n  workflow_run:\n    workflows: [\"Build and Sign\"]\n    types: [completed]\n    branches: [main]\n\njobs:\n  verify-and-deploy:\n    runs-on: ubuntu-latest\n    if: ${{ github.event.workflow_run.conclusion == 'success' }}\n    environment:\n      name: production\n      url: https:\/\/app.example.com\n    permissions:\n      id-token: write\n      contents: read\n    steps:\n      - name: Checkout manifests\n        uses: actions\/checkout@v4\n\n      - name: Install cosign\n        uses: sigstore\/cosign-installer@v3\n\n      - name: Verify image signature (keyless)\n        run: |\n          IMAGE=\"ghcr.io\/myorg\/myapp@${{ github.event.workflow_run.head_sha }}\"\n          cosign verify \\\n            --certificate-identity \"https:\/\/github.com\/myorg\/myapp\/.github\/workflows\/build.yml@refs\/heads\/main\" \\\n            --certificate-oidc-issuer \"https:\/\/token.actions.githubusercontent.com\" \\\n            \"$IMAGE\"\n\n      - name: Verify SLSA provenance\n        run: |\n          IMAGE=\"ghcr.io\/myorg\/myapp@${{ github.event.workflow_run.head_sha }}\"\n          cosign verify-attestation \\\n            --type slsaprovenance \\\n            --certificate-identity \"https:\/\/github.com\/slsa-framework\/slsa-github-generator\/.github\/workflows\/generator_container_slsa3.yml@refs\/tags\/v1.9.0\" \\\n            --certificate-oidc-issuer \"https:\/\/token.actions.githubusercontent.com\" \\\n            \"$IMAGE\"\n\n      - name: Configure AWS credentials (deploy role)\n        uses: aws-actions\/configure-aws-credentials@v4\n        with:\n          role-to-assume: arn:aws:iam::123456789012:role\/deploy-production\n          aws-region: us-east-1\n\n      - name: Deploy to EKS\n        run: |\n          aws eks update-kubeconfig --name production-cluster\n          helm upgrade --install myapp .\/chart \\\n            --set image.tag=${{ github.event.workflow_run.head_sha }} \\\n            --set commitSha=${{ github.event.workflow_run.head_sha }} \\\n            --set pipelineUrl=\"https:\/\/github.com\/${{ github.repository }}\/actions\/runs\/${{ github.run_id }}\" \\\n            --set approvedBy=\"${{ github.actor }}\" \\\n            --namespace production \\\n            --wait --timeout 300s\n\n      - name: Verify rollout\n        run: |\n          kubectl rollout status deployment\/myapp \\\n            --namespace production --timeout=300s\n\n      - name: Rollback on failure\n        if: failure()\n        run: |\n          echo \"Deployment failed \u2014 initiating rollback\"\n          kubectl rollout undo deployment\/myapp --namespace production\n          echo \"::error::Deployment rolled back due to failure\"\n<\/code><\/pre>\n\n<p class=\"wp-block-paragraph\">Y el pipeline equivalente de GitLab CI con controles similares:<\/p>\n\n<pre><code># .gitlab-ci.yml\nstages:\n  - verify\n  - deploy\n  - validate\n\nverify_artifact:\n  stage: verify\n  image: bitnami\/cosign:latest\n  script:\n    - cosign verify\n        --certificate-identity \"https:\/\/gitlab.com\/myorg\/myapp\/\/.gitlab-ci.yml@refs\/heads\/main\"\n        --certificate-oidc-issuer \"https:\/\/gitlab.com\"\n        $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA\n  rules:\n    - if: $CI_COMMIT_BRANCH == \"main\"\n\ndeploy_production:\n  stage: deploy\n  environment:\n    name: production\n    url: https:\/\/app.example.com\n  resource_group: production\n  needs: [verify_artifact]\n  rules:\n    - if: $CI_COMMIT_BRANCH == \"main\"\n      when: manual\n  script:\n    - aws eks update-kubeconfig --name production-cluster\n    - helm upgrade --install myapp .\/chart\n        --set image.tag=$CI_COMMIT_SHA\n        --set commitSha=$CI_COMMIT_SHA\n        --set pipelineUrl=$CI_PIPELINE_URL\n        --set approvedBy=$GITLAB_USER_LOGIN\n        --namespace production\n        --wait --timeout 300s\n\nvalidate_deployment:\n  stage: validate\n  needs: [deploy_production]\n  script:\n    - kubectl rollout status deployment\/myapp --namespace production --timeout=300s\n  after_script:\n    - |\n      if [ \"$CI_JOB_STATUS\" == \"failed\" ]; then\n        echo \"Rolling back deployment\"\n        kubectl rollout undo deployment\/myapp --namespace production\n      fi\n  rules:\n    - if: $CI_COMMIT_BRANCH == \"main\"\n<\/code><\/pre>\n\n<h2 class=\"wp-block-heading\">Resumen y gu\u00edas relacionadas<\/h2>\n\n<p class=\"wp-block-paragraph\">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\u00faster, 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\u00fan control por s\u00ed solo es suficiente. La combinaci\u00f3n de la verificaci\u00f3n a nivel de pipeline, la aplicaci\u00f3n mediante admission controller, los despliegues progresivos y el registro de auditor\u00eda integral crea un proceso de despliegue que es a la vez r\u00e1pido y seguro.<\/p>\n\n<p class=\"wp-block-paragraph\">Contin\u00faa desarrollando tu conocimiento sobre CI\/CD seguro con estas gu\u00edas relacionadas:<\/p>\n\n<ul class=\"wp-block-list\">\n<li><a href=\"https:\/\/secure-pipelines.com\/es\/ci-cd-security\/separation-of-duties-least-privilege-ci-cd-pipelines\/\">Separaci\u00f3n de funciones y m\u00ednimo privilegio en pipelines CI\/CD<\/a> \u2014 An\u00e1lisis en profundidad de la separaci\u00f3n de identidades, las credenciales con alcance limitado y el principio de m\u00ednimo privilegio en todo tu pipeline.<\/li>\n<li><a href=\"https:\/\/secure-pipelines.com\/es\/ci-cd-security\/defensive-patterns-mitigations-ci-cd-pipeline-attacks\/\">Patrones defensivos y mitigaciones para los ataques a pipelines CI\/CD<\/a> \u2014 Contramedidas pr\u00e1cticas para los vectores de ataque m\u00e1s habituales dirigidos a los sistemas CI\/CD.<\/li>\n<\/ul>\n","protected":false},"excerpt":{"rendered":"<p>Tu pipeline CI\/CD puede tener controles de seguridad herm\u00e9ticos \u2014commits firmados, dependencias fijadas, escaneos SAST, firma de im\u00e1genes de contenedor\u2014, pero nada de eso importa si el propio proceso de despliegue es d\u00e9bil. El despliegue es el punto cr\u00edtico donde la seguridad del pipeline se encuentra con la seguridad de producci\u00f3n. Un workflow de despliegue &#8230; <a title=\"Workflows de Despliegue Seguros: Del Pipeline CI\/CD a Producci\u00f3n\" class=\"read-more\" href=\"https:\/\/secure-pipelines.com\/es\/ci-cd-security\/secure-deployment-workflows-ci-cd-pipeline-production\/\" aria-label=\"Leer m\u00e1s sobre Workflows de Despliegue Seguros: Del Pipeline CI\/CD a Producci\u00f3n\">Leer m\u00e1s<\/a><\/p>\n","protected":false},"author":1,"featured_media":1562,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[55,58],"tags":[],"post_folder":[],"class_list":["post-644","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-ci-cd-security","category-pipeline-hardening"],"_links":{"self":[{"href":"https:\/\/secure-pipelines.com\/es\/wp-json\/wp\/v2\/posts\/644","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/secure-pipelines.com\/es\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/secure-pipelines.com\/es\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/secure-pipelines.com\/es\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/secure-pipelines.com\/es\/wp-json\/wp\/v2\/comments?post=644"}],"version-history":[{"count":7,"href":"https:\/\/secure-pipelines.com\/es\/wp-json\/wp\/v2\/posts\/644\/revisions"}],"predecessor-version":[{"id":1595,"href":"https:\/\/secure-pipelines.com\/es\/wp-json\/wp\/v2\/posts\/644\/revisions\/1595"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/secure-pipelines.com\/es\/wp-json\/wp\/v2\/media\/1562"}],"wp:attachment":[{"href":"https:\/\/secure-pipelines.com\/es\/wp-json\/wp\/v2\/media?parent=644"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/secure-pipelines.com\/es\/wp-json\/wp\/v2\/categories?post=644"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/secure-pipelines.com\/es\/wp-json\/wp\/v2\/tags?post=644"},{"taxonomy":"post_folder","embeddable":true,"href":"https:\/\/secure-pipelines.com\/es\/wp-json\/wp\/v2\/post_folder?post=644"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}