{"id":619,"date":"2026-02-05T21:30:47","date_gmt":"2026-02-05T20:30:47","guid":{"rendered":"https:\/\/secure-pipelines.com\/?p=619"},"modified":"2026-03-24T18:44:58","modified_gmt":"2026-03-24T17:44:58","slug":"secrets-management-ci-cd-pipelines-patterns-vault","status":"publish","type":"post","link":"https:\/\/secure-pipelines.com\/es\/ci-cd-security\/secrets-management-ci-cd-pipelines-patterns-vault\/","title":{"rendered":"Gesti\u00f3n de Secretos en Pipelines CI\/CD: Patrones, Anti-Patrones e Integraci\u00f3n con Vault"},"content":{"rendered":"<h2>Introducci\u00f3n: Por Qu\u00e9 los Secretos Son la Causa #1 de Compromiso en CI\/CD<\/h2>\n<p>Si examina la causa ra\u00edz de casi todas las grandes brechas de CI\/CD en los \u00faltimos a\u00f1os \u2014 desde el ataque a la cadena de suministro de Codecov hasta el incidente de seguridad de CircleCI \u2014 encontrar\u00e1 al mismo culpable: secretos comprometidos. Claves de API, credenciales de nube, contrase\u00f1as de bases de datos, certificados de firma \u2014 estas son las llaves maestras que los atacantes persiguen, y los pipelines de CI\/CD son donde concentran sus esfuerzos.<\/p>\n<p>La raz\u00f3n es estructural. Los pipelines existen en una posici\u00f3n singularmente peligrosa: <strong>deben<\/strong> tener acceso a credenciales de producci\u00f3n para desplegar software, pero son inherentemente ef\u00edmeros, multi-tenant y est\u00e1n expuestos a c\u00f3digo no confiable. Cada pull request, cada actualizaci\u00f3n de dependencia, cada push de un contribuidor desencadena la ejecuci\u00f3n del pipeline \u2014 y cada ejecuci\u00f3n es un vector potencial para la exfiltraci\u00f3n de secretos.<\/p>\n<p>El desaf\u00edo no es simplemente \u00abno pongas secretos en el c\u00f3digo.\u00bb Es mucho m\u00e1s profundo que eso. \u00bfC\u00f3mo se le da a un entorno de c\u00f3mputo ef\u00edmero y desechable acceso a sus credenciales m\u00e1s sensibles sin que esas credenciales se filtren en logs, artefactos, jobs posteriores o en manos de actores maliciosos? Esa es la pregunta que responde esta gu\u00eda.<\/p>\n<p>Cubriremos c\u00f3mo se exponen los secretos, c\u00f3mo inyectarlos de forma segura, c\u00f3mo integrar HashiCorp Vault y la federaci\u00f3n de identidad nativa de la nube, y qu\u00e9 anti-patrones evitar. Esta es una gu\u00eda para profesionales \u2014 espere YAML real, comandos CLI reales y decisiones arquitect\u00f3nicas reales.<\/p>\n<h2>C\u00f3mo Se Exponen los Secretos en CI\/CD<\/h2>\n<p>Antes de discutir soluciones, necesitamos comprender el panorama de amenazas. Los secretos se filtran de los pipelines a trav\u00e9s de varios vectores bien documentados.<\/p>\n<h3>Secretos Codificados en Configs de Pipeline e IaC<\/h3>\n<p>El vector de fuga m\u00e1s b\u00e1sico \u2014 y a\u00fan perturbadoramente com\u00fan \u2014 son las credenciales codificadas directamente en archivos de configuraci\u00f3n de pipeline o plantillas de Infrastructure as Code. Un desarrollador probando un despliegue podr\u00eda insertar una clave de acceso de AWS en un archivo <code>.github\/workflows\/deploy.yml<\/code> o un archivo Terraform <code>main.tf<\/code>, hacer commit y olvidarse. Incluso si se elimina en un commit posterior, el secreto vive para siempre en el historial de Git.<\/p>\n<pre><code># NUNCA HAGA ESTO \u2014 credenciales codificadas en un archivo de workflow\njobs:\n  deploy:\n    runs-on: ubuntu-latest\n    env:\n      AWS_ACCESS_KEY_ID: AKIAIOSFODNN7EXAMPLE\n      AWS_SECRET_ACCESS_KEY: wJalrXUtnFEMI\/K7MDENG\/bPxRfiCYEXAMPLEKEY\n    steps:\n      - run: aws s3 sync .\/build s3:\/\/my-bucket<\/code><\/pre>\n<h3>Secretos en Variables de Entorno Impresos en Logs<\/h3>\n<p>Las plataformas de CI t\u00edpicamente inyectan secretos como variables de entorno. El problema surge cuando los pasos del pipeline imprimen inadvertidamente esas variables a stdout. Un comando <code>env<\/code> descuidado, un <code>printenv<\/code> de depuraci\u00f3n, o una herramienta verbosa que vuelca su configuraci\u00f3n puede exponer secretos en los logs de compilaci\u00f3n que a menudo se retienen durante d\u00edas o semanas y son accesibles para todos los miembros del proyecto.<\/p>\n<pre><code># Peligroso: esto imprime TODAS las variables de entorno, incluyendo secretos\n- run: printenv | sort\n\n# Tambi\u00e9n peligroso: flags verbosos en herramientas que vuelcan la configuraci\u00f3n\n- run: terraform plan -debug<\/code><\/pre>\n<h3>Secretos Persistidos en Artefactos de Compilaci\u00f3n o Capas de Contenedores<\/h3>\n<p>Un secreto inyectado durante un build de Docker podr\u00eda persistir en una capa intermedia incluso despu\u00e9s de ser eliminado en una instrucci\u00f3n <code>RUN<\/code> posterior. De manera similar, los artefactos de compilaci\u00f3n \u2014 JARs, ZIPs, binarios compilados \u2014 podr\u00edan incrustar archivos de configuraci\u00f3n que contienen credenciales presentes en el momento de la compilaci\u00f3n.<\/p>\n<pre><code># MAL: El secreto persiste en la capa creada por la instrucci\u00f3n COPY\nCOPY .env \/app\/.env\nRUN \/app\/setup.sh\nRUN rm \/app\/.env   # Demasiado tarde \u2014 todav\u00eda est\u00e1 en una capa anterior<\/code><\/pre>\n<h3>Secretos Accesibles a Workflows de PR No Confiables<\/h3>\n<p>Este es uno de los vectores m\u00e1s peligrosos, particularmente en proyectos de c\u00f3digo abierto. GitHub Actions, por ejemplo, no proporciona secretos a workflows desencadenados por <code>pull_request<\/code> desde forks \u2014 por dise\u00f1o. Sin embargo, el evento <code>pull_request_target<\/code> <em>s\u00ed<\/em> tiene acceso a secretos, y si el workflow hace checkout y ejecuta el c\u00f3digo del autor del PR, crea un camino directo para la exfiltraci\u00f3n de secretos.<\/p>\n<h3>Alcances de Secretos Excesivamente Amplios<\/h3>\n<p>Muchas organizaciones configuran secretos a nivel de organizaci\u00f3n o grupo cuando deber\u00edan estar limitados a repositorios individuales o entornos. Un secreto a nivel de organizaci\u00f3n en GitHub Actions est\u00e1 disponible para <em>cada repositorio<\/em> en esa organizaci\u00f3n. Si cualquiera de esos repositorios es comprometido \u2014 o simplemente tiene un workflow mal configurado \u2014 todos los secretos a nivel de organizaci\u00f3n est\u00e1n en riesgo.<\/p>\n<h2>Patrones de Inyecci\u00f3n de Secretos<\/h2>\n<p>Ahora que entendemos c\u00f3mo se filtran los secretos, examinemos c\u00f3mo introducirlos en los pipelines de forma segura.<\/p>\n<h3>Secretos Nativos de la Plataforma<\/h3>\n<p>Cada plataforma principal de CI\/CD proporciona un mecanismo integrado de gesti\u00f3n de secretos. GitHub Actions tiene secretos de repositorio, entorno y organizaci\u00f3n. GitLab CI tiene variables CI\/CD a nivel de proyecto y grupo con enmascaramiento y protecci\u00f3n opcionales. Estos son el punto de partida m\u00e1s sencillo.<\/p>\n<pre><code># GitHub Actions: referenciando un secreto de repositorio\njobs:\n  deploy:\n    runs-on: ubuntu-latest\n    steps:\n      - name: Deploy to production\n        env:\n          API_KEY: ${{ secrets.PRODUCTION_API_KEY }}\n        run: .\/deploy.sh<\/code><\/pre>\n<pre><code># GitLab CI: usando una variable enmascarada y protegida\ndeploy:\n  stage: deploy\n  script:\n    - echo \"Deploying with masked credentials\"\n    - .\/deploy.sh\n  variables:\n    API_KEY: $PRODUCTION_API_KEY\n  only:\n    - main<\/code><\/pre>\n<p>Los secretos nativos de la plataforma son adecuados para muchos casos de uso, pero tienen limitaciones significativas: no hay generaci\u00f3n din\u00e1mica, registro de auditor\u00eda limitado, rotaci\u00f3n manual y no hay gesti\u00f3n centralizada a trav\u00e9s de m\u00faltiples plataformas.<\/p>\n<h3>Gestores Externos de Secretos<\/h3>\n<p>Para organizaciones con requisitos de seguridad maduros, los gestores externos de secretos \u2014 HashiCorp Vault, AWS Secrets Manager, GCP Secret Manager, Azure Key Vault \u2014 proporcionan control centralizado, registro de auditor\u00eda, generaci\u00f3n din\u00e1mica de secretos, rotaci\u00f3n autom\u00e1tica y pol\u00edticas de acceso granulares. Profundizaremos en la integraci\u00f3n con Vault en la siguiente secci\u00f3n.<\/p>\n<h3>Inyecci\u00f3n Just-in-Time vs Secretos Precargados<\/h3>\n<p>Los secretos precargados se configuran una vez y se ponen a disposici\u00f3n de todas las ejecuciones del pipeline. As\u00ed es como funcionan la mayor\u00eda de los secretos nativos de la plataforma. La inyecci\u00f3n just-in-time (JIT) recupera los secretos en el momento en que se necesitan, a menudo con TTLs cortos. La inyecci\u00f3n JIT es superior porque reduce la ventana de exposici\u00f3n, permite credenciales din\u00e1micas y proporciona registros de auditor\u00eda por ejecuci\u00f3n.<\/p>\n<pre><code># Inyecci\u00f3n JIT: obtener el secreto solo cuando se necesita\n- name: Get database credentials\n  run: |\n    DB_CREDS=$(vault kv get -format=json secret\/data\/myapp\/db)\n    export DB_USER=$(echo $DB_CREDS | jq -r '.data.data.username')\n    export DB_PASS=$(echo $DB_CREDS | jq -r '.data.data.password')\n    .\/run-migrations.sh<\/code><\/pre>\n<h3>Secretos Enmascarados vs Cifrados<\/h3>\n<p>Un concepto err\u00f3neo com\u00fan: \u00abenmascarado\u00bb no significa \u00abseguro.\u00bb Cuando GitHub Actions enmascara un secreto, realiza reemplazo de cadenas en la salida del log. Si el valor del secreto es corto (por ejemplo, un token de 4 caracteres), el enmascaramiento podr\u00eda no activarse. Si el secreto est\u00e1 codificado en base64 o transformado de alguna manera, el valor transformado <em>no<\/em> ser\u00e1 enmascarado. El enmascaramiento es una conveniencia, no un l\u00edmite de seguridad. Los secretos cifrados en reposo (que todas las plataformas principales proporcionan) protegen contra el compromiso del almacenamiento del lado de la plataforma, pero no hacen nada para prevenir la exfiltraci\u00f3n en tiempo de ejecuci\u00f3n.<\/p>\n<h2>Integraci\u00f3n de HashiCorp Vault con CI\/CD<\/h2>\n<p>HashiCorp Vault es el gestor externo de secretos m\u00e1s ampliamente adoptado para pipelines de CI\/CD. Soporta m\u00faltiples m\u00e9todos de autenticaci\u00f3n adecuados para sistemas automatizados, generaci\u00f3n din\u00e1mica de secretos y pol\u00edticas granulares. A continuaci\u00f3n se explica c\u00f3mo integrarlo con las dos plataformas de CI\/CD m\u00e1s comunes.<\/p>\n<h3>Vault AppRole Auth para CI Runners<\/h3>\n<p>AppRole es el m\u00e9todo de autenticaci\u00f3n orientado a m\u00e1quinas de Vault. Utiliza un Role ID (como un nombre de usuario) y un Secret ID (como una contrase\u00f1a) para autenticarse. El Secret ID puede configurarse para un solo uso y con un TTL, lo que lo hace adecuado para CI runners.<\/p>\n<pre><code># Habilitar el m\u00e9todo de autenticaci\u00f3n AppRole\nvault auth enable approle\n\n# Crear una pol\u00edtica para CI\nvault policy write ci-deploy - &lt;&lt;EOF\npath \"secret\/data\/myapp\/*\" {\n  capabilities = [\"read\"]\n}\npath \"database\/creds\/myapp-role\" {\n  capabilities = [\"read\"]\n}\nEOF\n\n# Crear un AppRole con la pol\u00edtica de CI\nvault write auth\/approle\/role\/ci-deploy \\\n  token_policies=\"ci-deploy\" \\\n  token_ttl=15m \\\n  token_max_ttl=30m \\\n  secret_id_ttl=10m \\\n  secret_id_num_uses=1\n\n# Recuperar el Role ID (almacenar en la plataforma CI como variable no sensible)\nvault read auth\/approle\/role\/ci-deploy\/role-id\n\n# Generar un Secret ID de un solo uso (almacenar en la plataforma CI como secreto)\nvault write -f auth\/approle\/role\/ci-deploy\/secret-id<\/code><\/pre>\n<h3>Vault JWT\/OIDC Auth con GitHub Actions<\/h3>\n<p>El enfoque moderno y preferido para GitHub Actions es la autenticaci\u00f3n JWT\/OIDC. GitHub Actions puede emitir un token OIDC para cada ejecuci\u00f3n de workflow, y Vault puede validar ese token para autenticar el pipeline \u2014 eliminando la necesidad de almacenar cualquier credencial de Vault en GitHub.<\/p>\n<pre><code># Configurar Vault JWT auth para GitHub Actions\nvault auth enable jwt\n\nvault write auth\/jwt\/config \\\n  bound_issuer=\"https:\/\/token.actions.githubusercontent.com\" \\\n  oidc_discovery_url=\"https:\/\/token.actions.githubusercontent.com\"\n\n# Crear un rol que se vincule a un repositorio y rama espec\u00edficos\nvault write auth\/jwt\/role\/github-deploy \\\n  role_type=\"jwt\" \\\n  bound_audiences=\"https:\/\/github.com\/my-org\" \\\n  bound_claims_type=\"glob\" \\\n  bound_claims='{\"sub\": \"repo:my-org\/my-repo:ref:refs\/heads\/main\"}' \\\n  user_claim=\"repository_owner\" \\\n  token_policies=\"ci-deploy\" \\\n  token_ttl=\"10m\"<\/code><\/pre>\n<p>Luego en su workflow de GitHub Actions, use la acci\u00f3n <code>hashicorp\/vault-action<\/code>:<\/p>\n<pre><code>jobs:\n  deploy:\n    runs-on: ubuntu-latest\n    permissions:\n      id-token: write\n      contents: read\n    steps:\n      - name: Import secrets from Vault\n        uses: hashicorp\/vault-action@v3\n        with:\n          url: https:\/\/vault.mycompany.com\n          method: jwt\n          role: github-deploy\n          jwtGithubAudience: https:\/\/github.com\/my-org\n          secrets: |\n            secret\/data\/myapp\/db username | DB_USER ;\n            secret\/data\/myapp\/db password | DB_PASS\n\n      - name: Run deployment\n        run: |\n          echo \"Deploying with fetched credentials\"\n          .\/deploy.sh<\/code><\/pre>\n<h3>Vault JWT Auth con GitLab CI<\/h3>\n<p>GitLab CI tiene soporte nativo para la integraci\u00f3n con Vault usando <code>id_tokens<\/code>. GitLab puede generar un JWT que Vault valida, similar al enfoque de GitHub Actions.<\/p>\n<pre><code># Configurar Vault para GitLab JWT auth\nvault auth enable -path=gitlab jwt\n\nvault write auth\/gitlab\/config \\\n  bound_issuer=\"https:\/\/gitlab.com\" \\\n  jwks_url=\"https:\/\/gitlab.com\/-\/jwks\" \\\n  supported_algs=\"RS256\"\n\nvault write auth\/gitlab\/role\/gitlab-deploy \\\n  role_type=\"jwt\" \\\n  bound_claims='{\"project_id\": \"12345\", \"ref_protected\": \"true\"}' \\\n  user_claim=\"user_email\" \\\n  token_policies=\"ci-deploy\" \\\n  token_ttl=\"10m\"<\/code><\/pre>\n<p>Y en su <code>.gitlab-ci.yml<\/code>:<\/p>\n<pre><code>deploy:\n  stage: deploy\n  id_tokens:\n    VAULT_ID_TOKEN:\n      aud: https:\/\/vault.mycompany.com\n  secrets:\n    DB_USER:\n      vault: myapp\/db\/username@secret\n      token: $VAULT_ID_TOKEN\n    DB_PASS:\n      vault: myapp\/db\/password@secret\n      token: $VAULT_ID_TOKEN\n  script:\n    - .\/deploy.sh<\/code><\/pre>\n<h3>Secretos Din\u00e1micos<\/h3>\n<p>Una de las caracter\u00edsticas m\u00e1s poderosas de Vault es la generaci\u00f3n din\u00e1mica de secretos. En lugar de almacenar contrase\u00f1as de bases de datos est\u00e1ticas, Vault puede generar credenciales de corta duraci\u00f3n bajo demanda. Cuando el pipeline finaliza, las credenciales expiran autom\u00e1ticamente.<\/p>\n<pre><code># Habilitar el motor de secretos de base de datos\nvault secrets enable database\n\n# Configurar una conexi\u00f3n PostgreSQL\nvault write database\/config\/myapp-db \\\n  plugin_name=postgresql-database-plugin \\\n  connection_url=\"postgresql:\/\/{{username}}:{{password}}@db.mycompany.com:5432\/myapp\" \\\n  allowed_roles=\"myapp-role\" \\\n  username=\"vault_admin\" \\\n  password=\"vault_admin_password\"\n\n# Crear un rol que genere credenciales con un TTL de 1 hora\nvault write database\/roles\/myapp-role \\\n  db_name=myapp-db \\\n  creation_statements=\"CREATE ROLE \\\"{{name}}\\\" WITH LOGIN PASSWORD '{{password}}' VALID UNTIL '{{expiration}}'; GRANT SELECT, INSERT, UPDATE ON ALL TABLES IN SCHEMA public TO \\\"{{name}}\\\";\" \\\n  default_ttl=\"1h\" \\\n  max_ttl=\"2h\"\n\n# En su pipeline, obtenga credenciales din\u00e1micas\n# vault read database\/creds\/myapp-role\n# Devuelve un par \u00fanico de usuario\/contrase\u00f1a v\u00e1lido por 1 hora<\/code><\/pre>\n<p>Los secretos din\u00e1micos eliminan por completo el problema de la rotaci\u00f3n de credenciales. Cada ejecuci\u00f3n de pipeline obtiene sus propias credenciales \u00fanicas, y las credenciales comprometidas expiran autom\u00e1ticamente.<\/p>\n<h2>Credenciales de Corta Duraci\u00f3n e Identidad de Carga de Trabajo<\/h2>\n<p>El avance m\u00e1s significativo en la gesti\u00f3n de secretos de CI\/CD en los \u00faltimos a\u00f1os es la federaci\u00f3n de identidad de carga de trabajo (workload identity federation) \u2014 la capacidad de una plataforma de CI\/CD para autenticarse directamente con un proveedor de nube usando su propia identidad, sin ninguna credencial almacenada.<\/p>\n<h3>GitHub Actions OIDC con AWS<\/h3>\n<p>GitHub Actions puede asumir un rol de AWS IAM directamente usando federaci\u00f3n OIDC. No se almacenan claves de acceso de AWS en ning\u00fan lugar.<\/p>\n<pre><code># Primero, crear un proveedor de identidad OIDC en AWS (v\u00eda Terraform)\nresource \"aws_iam_openid_connect_provider\" \"github\" {\n  url             = \"https:\/\/token.actions.githubusercontent.com\"\n  client_id_list  = [\"sts.amazonaws.com\"]\n  thumbprint_list = [\"6938fd4d98bab03faadb97b34396831e3780aea1\"]\n}\n\n# Crear un rol IAM que GitHub Actions pueda asumir\nresource \"aws_iam_role\" \"github_actions\" {\n  name = \"github-actions-deploy\"\n\n  assume_role_policy = jsonencode({\n    Version = \"2012-10-17\"\n    Statement = [{\n      Effect = \"Allow\"\n      Principal = {\n        Federated = aws_iam_openid_connect_provider.github.arn\n      }\n      Action = \"sts:AssumeRoleWithWebIdentity\"\n      Condition = {\n        StringEquals = {\n          \"token.actions.githubusercontent.com:aud\" = \"sts.amazonaws.com\"\n        }\n        StringLike = {\n          \"token.actions.githubusercontent.com:sub\" = \"repo:my-org\/my-repo:ref:refs\/heads\/main\"\n        }\n      }\n    }]\n  })\n}<\/code><\/pre>\n<pre><code># Workflow de GitHub Actions usando OIDC\njobs:\n  deploy:\n    runs-on: ubuntu-latest\n    permissions:\n      id-token: write\n      contents: read\n    steps:\n      - uses: aws-actions\/configure-aws-credentials@v4\n        with:\n          role-to-assume: arn:aws:iam::123456789012:role\/github-actions-deploy\n          aws-region: us-east-1\n          role-duration-seconds: 900   # 15 minutos\n\n      - name: Deploy\n        run: aws s3 sync .\/build s3:\/\/my-bucket<\/code><\/pre>\n<h3>GitHub Actions OIDC con GCP<\/h3>\n<p>Google Cloud soporta el mismo patr\u00f3n a trav\u00e9s de Workload Identity Federation.<\/p>\n<pre><code># Crear un Workload Identity Pool y Provider (gcloud CLI)\ngcloud iam workload-identity-pools create \"github-pool\" \\\n  --project=\"my-project\" \\\n  --location=\"global\" \\\n  --display-name=\"GitHub Actions Pool\"\n\ngcloud iam workload-identity-pools providers create-oidc \"github-provider\" \\\n  --project=\"my-project\" \\\n  --location=\"global\" \\\n  --workload-identity-pool=\"github-pool\" \\\n  --display-name=\"GitHub Provider\" \\\n  --attribute-mapping=\"google.subject=assertion.sub,attribute.repository=assertion.repository\" \\\n  --attribute-condition=\"assertion.repository_owner == 'my-org'\" \\\n  --issuer-uri=\"https:\/\/token.actions.githubusercontent.com\"\n\n# Conceder a la Workload Identity la capacidad de suplantar una cuenta de servicio\ngcloud iam service-accounts add-iam-policy-binding \\\n  deploy-sa@my-project.iam.gserviceaccount.com \\\n  --role=\"roles\/iam.workloadIdentityUser\" \\\n  --member=\"principalSet:\/\/iam.googleapis.com\/projects\/123456\/locations\/global\/workloadIdentityPools\/github-pool\/attribute.repository\/my-org\/my-repo\"<\/code><\/pre>\n<pre><code># Workflow de GitHub Actions para GCP\njobs:\n  deploy:\n    runs-on: ubuntu-latest\n    permissions:\n      id-token: write\n      contents: read\n    steps:\n      - uses: google-github-actions\/auth@v2\n        with:\n          workload_identity_provider: projects\/123456\/locations\/global\/workloadIdentityPools\/github-pool\/providers\/github-provider\n          service_account: deploy-sa@my-project.iam.gserviceaccount.com\n\n      - name: Deploy to Cloud Run\n        run: gcloud run deploy my-service --image=gcr.io\/my-project\/my-app:latest<\/code><\/pre>\n<h3>Federaci\u00f3n OIDC en GitLab CI<\/h3>\n<p>GitLab CI soporta el mismo patr\u00f3n de federaci\u00f3n OIDC con AWS, GCP y Azure. La configuraci\u00f3n es similar \u2014 se configura el proveedor de nube para confiar en el emisor OIDC de GitLab y se vincula el acceso a IDs de proyecto, ramas o entornos espec\u00edficos.<\/p>\n<pre><code># GitLab CI con AWS OIDC\nassume_role:\n  stage: deploy\n  id_tokens:\n    AWS_OIDC_TOKEN:\n      aud: https:\/\/sts.amazonaws.com\n  script:\n    - &gt;\n      STS_CREDS=$(aws sts assume-role-with-web-identity\n      --role-arn arn:aws:iam::123456789012:role\/gitlab-deploy\n      --role-session-name \"gitlab-ci-${CI_PIPELINE_ID}\"\n      --web-identity-token \"${AWS_OIDC_TOKEN}\"\n      --duration-seconds 900)\n    - export AWS_ACCESS_KEY_ID=$(echo $STS_CREDS | jq -r '.Credentials.AccessKeyId')\n    - export AWS_SECRET_ACCESS_KEY=$(echo $STS_CREDS | jq -r '.Credentials.SecretAccessKey')\n    - export AWS_SESSION_TOKEN=$(echo $STS_CREDS | jq -r '.Credentials.SessionToken')\n    - aws s3 sync .\/build s3:\/\/my-bucket<\/code><\/pre>\n<h3>Por Qu\u00e9 las Credenciales de Corta Duraci\u00f3n Son Superiores<\/h3>\n<p>Las ventajas de las credenciales federadas de corta duraci\u00f3n sobre los secretos est\u00e1ticos de larga duraci\u00f3n son sustanciales:<\/p>\n<ul>\n<li><strong>No hay secretos que robar.<\/strong> No hay credenciales almacenadas que exfiltrar. El pipeline se autentica con un JWT firmado que es v\u00e1lido solo para esa ejecuci\u00f3n espec\u00edfica.<\/li>\n<li><strong>No se necesita rotaci\u00f3n.<\/strong> Las credenciales se generan por ejecuci\u00f3n y expiran autom\u00e1ticamente. No hay nada que rotar.<\/li>\n<li><strong>Alcance granular.<\/strong> El acceso puede restringirse a repositorios, ramas, entornos e incluso jobs espec\u00edficos de workflow.<\/li>\n<li><strong>Registro de auditor\u00eda completo.<\/strong> Los logs del proveedor de nube muestran exactamente qu\u00e9 ejecuci\u00f3n de pipeline accedi\u00f3 a qu\u00e9 recursos, vinculado al claim OIDC.<\/li>\n<li><strong>Reducci\u00f3n del radio de impacto.<\/strong> Incluso si una credencial es de alguna manera exfiltrada, expira en minutos, no en meses.<\/li>\n<\/ul>\n<h2>Anti-Patrones a Evitar<\/h2>\n<p>Saber qu\u00e9 <em>no<\/em> hacer es tan importante como conocer los patrones correctos. Estos anti-patrones se observan regularmente en entornos de producci\u00f3n.<\/p>\n<h3>Uso de Personal Access Tokens en CI<\/h3>\n<p>Los personal access tokens (PATs) vinculados a cuentas individuales de desarrolladores son uno de los patrones m\u00e1s comunes y m\u00e1s peligrosos. Cuando un desarrollador deja la organizaci\u00f3n, su PAT puede seguir funcionando. Los PATs t\u00edpicamente tienen permisos amplios \u2014 mucho m\u00e1s de lo que el pipeline necesita. Si se exfiltran, el atacante obtiene acceso a todo lo que ese desarrollador pod\u00eda acceder.<\/p>\n<p><strong>En su lugar:<\/strong> Use cuentas de m\u00e1quina con tokens de alcance limitado, o mejor a\u00fan, use tokens de instalaci\u00f3n de GitHub App o federaci\u00f3n OIDC.<\/p>\n<h3>Compartir Secretos Entre Entornos<\/h3>\n<p>Usar la misma contrase\u00f1a de base de datos para desarrollo, staging y producci\u00f3n \u2014 o la misma clave de API para todos los entornos \u2014 significa que un compromiso de su entorno menos seguro (generalmente dev) le da a los atacantes acceso a producci\u00f3n. La separaci\u00f3n de entornos no tiene sentido si las credenciales son las mismas.<\/p>\n<p><strong>En su lugar:<\/strong> Use secretos con alcance por entorno. En GitHub Actions, configure entornos de despliegue con sus propios almacenes de secretos. En GitLab, use variables protegidas con alcance a entornos espec\u00edficos.<\/p>\n<h3>No Rotar Secretos Despu\u00e9s de una Exposici\u00f3n<\/h3>\n<p>Cuando un secreto se registra accidentalmente en logs, se hace commit a un repositorio o se expone en un artefacto de compilaci\u00f3n, muchos equipos simplemente eliminan el log o borran el commit sin rotar la credencial. Esto es inadecuado. Debe asumir que el secreto ha sido observado y rotarlo inmediatamente.<\/p>\n<p><strong>En su lugar:<\/strong> Trate cualquier exposici\u00f3n como un compromiso. Rote inmediatamente. Automatice la rotaci\u00f3n donde sea posible. Use secretos din\u00e1micos para hacer que el problema sea irrelevante.<\/p>\n<h3>Confiar en pull_request_target con Secretos<\/h3>\n<p>El evento <code>pull_request_target<\/code> en GitHub Actions se ejecuta en el contexto de la rama base, lo que significa que tiene acceso a secretos. Esto est\u00e1 destinado a operaciones seguras como etiquetar PRs. Sin embargo, si su workflow hace checkout del head ref del PR y ejecuta ese c\u00f3digo, le ha dado a un contribuidor externo acceso completo a sus secretos.<\/p>\n<pre><code># PELIGROSO: Esto le da al autor del PR acceso a todos los secretos del repositorio\non: pull_request_target\njobs:\n  build:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions\/checkout@v4\n        with:\n          ref: ${{ github.event.pull_request.head.sha }}  # Haciendo checkout de c\u00f3digo no confiable!\n      - run: make test  # Ejecutando c\u00f3digo no confiable con acceso a secretos!<\/code><\/pre>\n<p><strong>En su lugar:<\/strong> Nunca haga checkout ni ejecute c\u00f3digo de PR en un workflow <code>pull_request_target<\/code>. Si necesita ejecutar pruebas en c\u00f3digo de PR con secretos, use un enfoque de dos workflows: ejecute c\u00f3digo no confiable en un workflow <code>pull_request<\/code> (sin secretos), luego use un trigger <code>workflow_run<\/code> separado para operaciones confiables.<\/p>\n<h2>Defensa en Profundidad: Un Enfoque por Capas<\/h2>\n<p>Ning\u00fan control \u00fanico es suficiente. La gesti\u00f3n efectiva de secretos requiere m\u00faltiples capas de defensa superpuestas.<\/p>\n<h3>Escaneo de Secretos<\/h3>\n<p>Implemente escaneo en tres etapas:<\/p>\n<ul>\n<li><strong>Pre-commit:<\/strong> Use herramientas como <code>gitleaks<\/code> o <code>detect-secrets<\/code> como hooks de pre-commit para evitar que los secretos ingresen al repositorio.<\/li>\n<li><strong>En el pipeline:<\/strong> Ejecute escaneo de secretos como un paso de CI en cada pull request. Herramientas como <code>trufflehog<\/code> pueden escanear diffs, historial de commits e incluso archivos binarios.<\/li>\n<li><strong>Post-commit:<\/strong> Habilite el escaneo de secretos integrado de GitHub o la detecci\u00f3n de secretos de GitLab para escanear continuamente todo el contenido del repositorio y alertar sobre hallazgos.<\/li>\n<\/ul>\n<pre><code># Hook pre-commit con gitleaks\n# .pre-commit-config.yaml\nrepos:\n  - repo: https:\/\/github.com\/gitleaks\/gitleaks\n    rev: v8.21.2\n    hooks:\n      - id: gitleaks<\/code><\/pre>\n<pre><code># Escaneo en pipeline con trufflehog\n- name: Scan for secrets\n  run: |\n    docker run --rm -v \"$PWD:\/repo\" trufflesecurity\/trufflehog:latest \\\n      git file:\/\/\/repo --only-verified --fail<\/code><\/pre>\n<h3>Registro de Auditor\u00eda para Acceso a Secretos<\/h3>\n<p>Cada acceso a un secreto debe ser registrado. Vault proporciona logs de auditor\u00eda detallados por defecto. Los gestores de secretos de proveedores de nube (AWS Secrets Manager, GCP Secret Manager) se integran con CloudTrail y Cloud Audit Logs respectivamente. Para secretos nativos de la plataforma, habilite las funciones de registro de auditor\u00eda disponibles en GitHub Enterprise o GitLab Ultimate.<\/p>\n<pre><code># Habilitar registro de auditor\u00eda de Vault\nvault audit enable file file_path=\/var\/log\/vault\/audit.log\n\n# Cada acceso genera una entrada de log como:\n# {\"type\": \"response\", \"auth\": {\"token_type\": \"service\", \"policies\": [\"ci-deploy\"]},\n#  \"request\": {\"path\": \"secret\/data\/myapp\/db\", \"operation\": \"read\"}, ...}<\/code><\/pre>\n<h3>Alcance de Privilegios M\u00ednimos<\/h3>\n<p>Aplique el principio de privilegios m\u00ednimos de forma agresiva:<\/p>\n<ul>\n<li>Limite los secretos al repositorio espec\u00edfico que los necesita, no a la organizaci\u00f3n.<\/li>\n<li>Use secretos a nivel de entorno para que las credenciales de producci\u00f3n solo est\u00e9n disponibles para workflows que despliegan a producci\u00f3n.<\/li>\n<li>Configure protecci\u00f3n de ramas para que solo los workflows que se ejecutan en ramas protegidas puedan acceder a secretos de producci\u00f3n.<\/li>\n<li>En Vault, escriba pol\u00edticas que concedan acceso a la ruta m\u00e1s estrecha posible con capacidades de solo lectura.<\/li>\n<\/ul>\n<pre><code># Pol\u00edtica de Vault: acceso m\u00ednimo para el CI de un microservicio espec\u00edfico\npath \"secret\/data\/payments-service\/production\" {\n  capabilities = [\"read\"]\n}\n\n# Denegar acceso a todo lo dem\u00e1s por defecto (comportamiento predeterminado de Vault)\n# Sin comodines, sin rutas amplias<\/code><\/pre>\n<h3>Rotaci\u00f3n Automatizada<\/h3>\n<p>Los secretos est\u00e1ticos deben rotarse de forma regular y de inmediato despu\u00e9s de cualquier sospecha de exposici\u00f3n. Automatice este proceso:<\/p>\n<ul>\n<li>Use los secretos din\u00e1micos de Vault para eliminar la necesidad de rotaci\u00f3n por completo.<\/li>\n<li>Para secretos que deben ser est\u00e1ticos (por ejemplo, claves de API de terceros), use la rotaci\u00f3n integrada de AWS Secrets Manager con funciones Lambda o soluciones nativas de la nube similares.<\/li>\n<li>Implemente alertas para secretos que no se han rotado dentro de su tiempo de vida esperado.<\/li>\n<\/ul>\n<pre><code># AWS Secrets Manager: configurar rotaci\u00f3n autom\u00e1tica\naws secretsmanager rotate-secret \\\n  --secret-id myapp\/api-key \\\n  --rotation-lambda-arn arn:aws:lambda:us-east-1:123456789012:function:rotate-api-key \\\n  --rotation-rules '{\"ScheduleExpression\": \"rate(30 days)\"}'<\/code><\/pre>\n<h2>Conclusi\u00f3n: La Gesti\u00f3n de Secretos Es Continua<\/h2>\n<p>La gesti\u00f3n de secretos no es una casilla que marcar durante la configuraci\u00f3n inicial del pipeline. Es una disciplina continua que debe evolucionar a medida que su infraestructura crece, a medida que surgen nuevas t\u00e9cnicas de ataque y a medida que su equipo cambia. Los patrones descritos en esta gu\u00eda \u2014 federaci\u00f3n OIDC, secretos din\u00e1micos, inyecci\u00f3n just-in-time, alcance de privilegios m\u00ednimos y escaneo por capas \u2014 representan el estado actual del arte, pero requieren atenci\u00f3n continua.<\/p>\n<p>Comience auditando sus pipelines actuales. Identifique cada credencial almacenada. Para cada una, pregunte: \u00bfpuede ser reemplazada por una credencial de corta duraci\u00f3n o federaci\u00f3n de identidad de carga de trabajo? \u00bfSe puede reducir este alcance? \u00bfSe est\u00e1 registrando este secreto en alg\u00fan lugar? \u00bfExiste un registro de auditor\u00eda para cada acceso?<\/p>\n<p>Las organizaciones que sufren brechas de CI\/CD no son las que nunca almacenaron un secreto \u2014 eso es imposible. Son las que trataron la gesti\u00f3n de secretos como una tarea de configuraci\u00f3n \u00fanica en lugar de una pr\u00e1ctica de seguridad viva. Construya la automatizaci\u00f3n, aplique las pol\u00edticas, monitoree los logs de acceso e itere. Sus pipelines ser\u00e1n significativamente m\u00e1s dif\u00edciles de comprometer como resultado.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Introducci\u00f3n: Por Qu\u00e9 los Secretos Son la Causa #1 de Compromiso en CI\/CD Si examina la causa ra\u00edz de casi todas las grandes brechas de CI\/CD en los \u00faltimos a\u00f1os \u2014 desde el ataque a la cadena de suministro de Codecov hasta el incidente de seguridad de CircleCI \u2014 encontrar\u00e1 al mismo culpable: secretos comprometidos. &#8230; <a title=\"Gesti\u00f3n de Secretos en Pipelines CI\/CD: Patrones, Anti-Patrones e Integraci\u00f3n con Vault\" class=\"read-more\" href=\"https:\/\/secure-pipelines.com\/es\/ci-cd-security\/secrets-management-ci-cd-pipelines-patterns-vault\/\" aria-label=\"Leer m\u00e1s sobre Gesti\u00f3n de Secretos en Pipelines CI\/CD: Patrones, Anti-Patrones e Integraci\u00f3n con Vault\">Leer m\u00e1s<\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[55,58],"tags":[],"post_folder":[],"class_list":["post-619","post","type-post","status-publish","format-standard","hentry","category-ci-cd-security","category-pipeline-hardening"],"_links":{"self":[{"href":"https:\/\/secure-pipelines.com\/es\/wp-json\/wp\/v2\/posts\/619","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=619"}],"version-history":[{"count":1,"href":"https:\/\/secure-pipelines.com\/es\/wp-json\/wp\/v2\/posts\/619\/revisions"}],"predecessor-version":[{"id":628,"href":"https:\/\/secure-pipelines.com\/es\/wp-json\/wp\/v2\/posts\/619\/revisions\/628"}],"wp:attachment":[{"href":"https:\/\/secure-pipelines.com\/es\/wp-json\/wp\/v2\/media?parent=619"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/secure-pipelines.com\/es\/wp-json\/wp\/v2\/categories?post=619"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/secure-pipelines.com\/es\/wp-json\/wp\/v2\/tags?post=619"},{"taxonomy":"post_folder","embeddable":true,"href":"https:\/\/secure-pipelines.com\/es\/wp-json\/wp\/v2\/post_folder?post=619"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}