{"id":697,"date":"2026-03-02T23:09:46","date_gmt":"2026-03-02T22:09:46","guid":{"rendered":"https:\/\/secure-pipelines.com\/ci-cd-security\/lab-securing-gitlab-ci-pipelines-protected-variables-runners-environments-2\/"},"modified":"2026-03-25T05:16:21","modified_gmt":"2026-03-25T04:16:21","slug":"lab-securing-gitlab-ci-pipelines-protected-variables-runners-environments-2","status":"publish","type":"post","link":"https:\/\/secure-pipelines.com\/es\/ci-cd-security\/lab-securing-gitlab-ci-pipelines-protected-variables-runners-environments-2\/","title":{"rendered":"Lab: Asegurando Pipelines de GitLab CI \u2014 Variables Protegidas, Runners y Entornos"},"content":{"rendered":"<h2>Descripci\u00f3n General<\/h2>\n<p>GitLab CI es la segunda plataforma de CI\/CD m\u00e1s utilizada en la industria, impulsando millones de pipelines en organizaciones de todos los tama\u00f1os. Su estrecha integraci\u00f3n con el control de c\u00f3digo fuente la hace excepcionalmente conveniente \u2014 pero esa misma integraci\u00f3n crea una amplia superficie de ataque si los pipelines no se endurecen deliberadamente.<\/p>\n<p>En este laboratorio pr\u00e1ctico recorrer\u00e1s seis ejercicios que aseguran progresivamente un pipeline de GitLab CI. Comenzar\u00e1s con una configuraci\u00f3n intencionalmente insegura donde cada variable es visible para cada rama, los runners compartidos manejan todos los trabajos y no hay puertas de entorno. Al final tendr\u00e1s un pipeline que aplica <strong>acceso de variables con privilegio m\u00ednimo<\/strong>, <strong>runners con alcance definido<\/strong>, <strong>entornos protegidos con aprobaciones de despliegue<\/strong>, <strong>acceso restringido de CI_JOB_TOKEN<\/strong>, <strong>pipelines seguros de merge request<\/strong> y <strong>controles adicionales de endurecimiento<\/strong> incluyendo detecci\u00f3n de secretos.<\/p>\n<p>Cada comando, fragmento YAML y ruta de interfaz en este laboratorio est\u00e1 basado en GitLab 16.x \/ 17.x y funciona en el nivel gratuito de GitLab.com.<\/p>\n<h2>Requisitos Previos<\/h2>\n<ul>\n<li>Una <strong>cuenta de GitLab<\/strong> \u2014 el nivel gratuito en <a href=\"https:\/\/gitlab.com\" target=\"_blank\" rel=\"noopener\">gitlab.com<\/a> es suficiente para cada ejercicio.<\/li>\n<li>Un <strong>proyecto de prueba<\/strong> que contenga una aplicaci\u00f3n simple (incluso un solo archivo <code>index.html<\/code> ser\u00e1 suficiente) y un archivo <code>.gitlab-ci.yml<\/code> en la ra\u00edz del repositorio.<\/li>\n<li>Familiaridad b\u00e1sica con la <strong>sintaxis de GitLab CI<\/strong>: stages, jobs, scripts y rules.<\/li>\n<li>(Opcional) Una m\u00e1quina Linux o macOS si planeas registrar tu propio GitLab Runner en el Ejercicio 2.<\/li>\n<\/ul>\n<h2>Configuraci\u00f3n del Entorno<\/h2>\n<h3>Paso 1 \u2014 Crear un Nuevo Proyecto en GitLab<\/h3>\n<ol>\n<li>Navega a <strong>GitLab > New Project > Create blank project<\/strong>.<\/li>\n<li>N\u00f3mbralo <code>secure-pipeline-lab<\/code>, establece la visibilidad como <strong>Private<\/strong> e inicializa con un README.<\/li>\n<li>En <strong>Settings > Repository > Protected branches<\/strong>, confirma que <code>main<\/code> est\u00e1 listada como rama protegida (esto es el valor predeterminado).<\/li>\n<\/ol>\n<h3>Paso 2 \u2014 Agregar una Aplicaci\u00f3n Simple<\/h3>\n<p>Crea <code>index.html<\/code> en la ra\u00edz del repositorio:<\/p>\n<pre><code>&lt;!DOCTYPE html&gt;\n&lt;html lang=\"en\"&gt;\n&lt;head&gt;&lt;meta charset=\"UTF-8\"&gt;&lt;title&gt;Secure Pipeline Lab&lt;\/title&gt;&lt;\/head&gt;\n&lt;body&gt;&lt;h1&gt;Hello, GitLab CI!&lt;\/h1&gt;&lt;\/body&gt;\n&lt;\/html&gt;<\/code><\/pre>\n<h3>Paso 3 \u2014 Crear el Pipeline Inicial (Inseguro)<\/h3>\n<p>Agrega el siguiente <code>.gitlab-ci.yml<\/code>. Este es <em>deliberadamente inseguro<\/em> \u2014 es el punto de partida que endureceremos a lo largo del laboratorio:<\/p>\n<pre><code># .gitlab-ci.yml \u2014 Punto de partida INSEGURO\nstages:\n  - build\n  - test\n  - deploy\n\nbuild-job:\n  stage: build\n  script:\n    - echo \"Building the application...\"\n    - echo \"DB_PASSWORD is $DB_PASSWORD\"   # \u00a1Variable impresa en los logs!\n\ntest-job:\n  stage: test\n  script:\n    - echo \"Running tests...\"\n    - echo \"API_KEY is $API_KEY\"            # \u00a1Variable impresa en los logs!\n\ndeploy-job:\n  stage: deploy\n  script:\n    - echo \"Deploying to production...\"\n    - echo \"DEPLOY_TOKEN is $DEPLOY_TOKEN\" # \u00a1Variable impresa en los logs!\n<\/code><\/pre>\n<p>Este pipeline tiene varios problemas:<\/p>\n<ul>\n<li>Todas las variables de CI\/CD est\u00e1n disponibles para <strong>cada rama<\/strong>, incluyendo ramas creadas por colaboradores externos.<\/li>\n<li>Las variables se imprimen directamente en los logs de los trabajos \u2014 cualquier persona con acceso a los logs puede leerlas.<\/li>\n<li>Los trabajos se ejecutan en <strong>runners compartidos<\/strong> sin garant\u00edas de aislamiento.<\/li>\n<li>No hay <strong>puertas de entorno<\/strong> \u2014 el trabajo de despliegue se ejecuta autom\u00e1ticamente en cada push.<\/li>\n<\/ul>\n<p>Haz commit de este archivo en <code>main<\/code> y verifica que el pipeline se ejecuta. Ahora corrijamos cada uno de estos problemas.<\/p>\n<h2>Ejercicio 1: Variables Protegidas y Enmascaradas<\/h2>\n<p>Las variables de CI\/CD de GitLab soportan tres indicadores de protecci\u00f3n que reducen dr\u00e1sticamente el radio de explosi\u00f3n de una rama comprometida o un fork.<\/p>\n<h3>Comprendiendo los Tres Indicadores<\/h3>\n<table>\n<thead>\n<tr>\n<th>Indicador<\/th>\n<th>Efecto<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td><strong>Protected<\/strong><\/td>\n<td>La variable <em>solo<\/em> se inyecta en pipelines que se ejecutan en <strong>ramas o tags protegidos<\/strong>. Un pipeline activado desde una rama de funcionalidad o un fork nunca ver\u00e1 el valor.<\/td>\n<\/tr>\n<tr>\n<td><strong>Masked<\/strong><\/td>\n<td>GitLab redacta el valor de la variable en los logs de los trabajos. Si un script imprime accidentalmente el valor, el log muestra <code>[MASKED]<\/code> en su lugar.<\/td>\n<\/tr>\n<tr>\n<td><strong>Hidden<\/strong> (GitLab 17+)<\/td>\n<td>El valor de la variable no puede revelarse en la interfaz despu\u00e9s de su creaci\u00f3n \u2014 ni siquiera por los maintainers del proyecto. \u00datil para secretos gestionados por un equipo de plataforma que los desarrolladores nunca deber\u00edan ver en texto plano.<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<h3>Paso 1 \u2014 Crear Variables<\/h3>\n<ol>\n<li>Ve a <strong>Settings > CI\/CD > Variables > Expand > Add variable<\/strong>.<\/li>\n<li>Crea las siguientes variables:<\/li>\n<\/ol>\n<table>\n<thead>\n<tr>\n<th>Clave<\/th>\n<th>Valor (ejemplo)<\/th>\n<th>Protected<\/th>\n<th>Masked<\/th>\n<th>Hidden<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td><code>DEPLOY_TOKEN<\/code><\/td>\n<td><code>glpat-xxxxxxxxxxxxxxxxxxxx<\/code><\/td>\n<td>S\u00ed<\/td>\n<td>S\u00ed<\/td>\n<td>No<\/td>\n<\/tr>\n<tr>\n<td><code>DB_PASSWORD<\/code><\/td>\n<td><code>S3cur3P@ssw0rd!2024<\/code><\/td>\n<td>S\u00ed<\/td>\n<td>S\u00ed<\/td>\n<td>S\u00ed<\/td>\n<\/tr>\n<tr>\n<td><code>API_KEY<\/code><\/td>\n<td><code>sk-test-abc123def456<\/code><\/td>\n<td>No<\/td>\n<td>S\u00ed<\/td>\n<td>No<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<h3>Paso 2 \u2014 Actualizar el Pipeline<\/h3>\n<pre><code># .gitlab-ci.yml \u2014 Ejercicio 1\nstages:\n  - build\n  - test\n  - deploy\n\nbuild-job:\n  stage: build\n  script:\n    - echo \"Building the application...\"\n    - echo \"API_KEY value length = ${#API_KEY}\"  # Seguro: imprime la longitud, no el valor\n\ntest-job:\n  stage: test\n  script:\n    - echo \"Running tests...\"\n    # Intentando imprimir una variable enmascarada:\n    - echo \"DB_PASSWORD is $DB_PASSWORD\"\n    # La salida mostrar\u00e1: DB_PASSWORD is [MASKED]\n\ndeploy-job:\n  stage: deploy\n  script:\n    - echo \"Deploying with DEPLOY_TOKEN...\"\n    - echo \"Token is $DEPLOY_TOKEN\"\n    # En main (protegida): Token is [MASKED]\n    # En rama de funcionalidad: Token is <vac\u00edo \u2014 variable no inyectada>\n  rules:\n    - if: $CI_COMMIT_BRANCH == \"main\"\n<\/code><\/pre>\n<h3>Paso 3 \u2014 Verificar el Comportamiento de Protecci\u00f3n<\/h3>\n<ol>\n<li><strong>Push en <code>main<\/code><\/strong> \u2014 el trabajo de despliegue se ejecuta y <code>DEPLOY_TOKEN<\/code> se inyecta (el log muestra <code>[MASKED]<\/code>).<\/li>\n<li><strong>Crea una rama<\/strong> <code>feature\/test-vars<\/code>, haz push de un commit \u2014 el trabajo de despliegue no se ejecuta (las rules lo restringen a <code>main<\/code>). Incluso si modificas las rules para permitirlo, <code>DEPLOY_TOKEN<\/code> y <code>DB_PASSWORD<\/code> est\u00e1n <strong>vac\u00edos<\/strong> porque la rama no est\u00e1 protegida.<\/li>\n<li><code>API_KEY<\/code>, que est\u00e1 enmascarada pero <em>no<\/em> protegida, est\u00e1 disponible en ambas ramas \u2014 su valor se redacta en los logs.<\/li>\n<\/ol>\n<p><strong>Lecci\u00f3n clave:<\/strong> Siempre marca las credenciales de despliegue como <strong>Protected<\/strong> y <strong>Masked<\/strong>. Usa <strong>Hidden<\/strong> para secretos que los desarrolladores nunca deber\u00edan recuperar de la interfaz.<\/p>\n<h2>Ejercicio 2: Seguridad y Alcance de Runners<\/h2>\n<p>Los runners son los motores de c\u00f3mputo que ejecutan tus trabajos de CI. Elegir el tipo correcto de runner \u2014 y definir su alcance correctamente \u2014 es una de las decisiones de seguridad m\u00e1s impactantes que puedes tomar.<\/p>\n<h3>Tipos de Runner<\/h3>\n<table>\n<thead>\n<tr>\n<th>Tipo<\/th>\n<th>Alcance<\/th>\n<th>Postura de Seguridad<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td><strong>Instance (compartido)<\/strong><\/td>\n<td>Disponible para cada proyecto en la instancia de GitLab<\/td>\n<td>Multi-tenant. Los trabajos de otros proyectos pueden ejecutarse en la misma m\u00e1quina. Riesgo de fuga de datos a trav\u00e9s del sistema de archivos compartido, socket de Docker o capas en cach\u00e9.<\/td>\n<\/tr>\n<tr>\n<td><strong>Group<\/strong><\/td>\n<td>Disponible para cada proyecto en un grupo espec\u00edfico<\/td>\n<td>Mejor aislamiento que los runners de instancia, pero a\u00fan compartido entre proyectos dentro del grupo.<\/td>\n<\/tr>\n<tr>\n<td><strong>Project<\/strong><\/td>\n<td>Disponible para un solo proyecto<\/td>\n<td>Mejor aislamiento. T\u00fa controlas la m\u00e1quina, la configuraci\u00f3n de Docker y el acceso a la red.<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<h3>Paso 1 \u2014 Registrar un Runner Espec\u00edfico del Proyecto<\/h3>\n<p>En una m\u00e1quina que controles (una VM, un servidor disponible o incluso un host Docker local), instala GitLab Runner y reg\u00edstralo:<\/p>\n<pre><code># Instalar GitLab Runner (Linux amd64)\nsudo curl -L --output \/usr\/local\/bin\/gitlab-runner \\\n  https:\/\/gitlab-runner-downloads.s3.amazonaws.com\/latest\/binaries\/gitlab-runner-linux-amd64\nsudo chmod +x \/usr\/local\/bin\/gitlab-runner\nsudo gitlab-runner install --user=gitlab-runner --working-directory=\/home\/gitlab-runner\nsudo gitlab-runner start\n\n# Registrar el runner\n# Encuentra tu token de registro: Settings > CI\/CD > Runners > Expand > New project runner\nsudo gitlab-runner register \\\n  --non-interactive \\\n  --url https:\/\/gitlab.com\/ \\\n  --token \"$RUNNER_TOKEN\" \\\n  --executor docker \\\n  --docker-image alpine:latest \\\n  --description \"secure-deploy-runner\" \\\n  --tag-list \"secure-deploy\" \\\n  --access-level ref_protected\n<\/code><\/pre>\n<p>El indicador cr\u00edtico es <code>--access-level ref_protected<\/code>. Esto le dice a GitLab que el runner <strong>solo aceptar\u00e1 trabajos de ramas o tags protegidos<\/strong>. Un pipeline activado por una rama de funcionalidad o un merge request de fork nunca se programar\u00e1 en este runner.<\/p>\n<h3>Paso 2 \u2014 Deshabilitar Runners Compartidos para Trabajos Sensibles<\/h3>\n<p>Ve a <strong>Settings > CI\/CD > Runners<\/strong> y cambia <strong>Enable shared runners for this project<\/strong> a desactivado \u2014 o d\u00e9jalos activados para stages no sensibles y usa tags para dirigir los trabajos sensibles a tu runner de proyecto.<\/p>\n<h3>Paso 3 \u2014 Actualizar el Pipeline con Selecci\u00f3n de Runner Basada en Tags<\/h3>\n<pre><code># .gitlab-ci.yml \u2014 Ejercicio 2\nstages:\n  - build\n  - test\n  - deploy\n\nbuild-job:\n  stage: build\n  # Se ejecuta en cualquier runner disponible (compartido est\u00e1 bien para builds)\n  script:\n    - echo \"Building the application...\"\n\ntest-job:\n  stage: test\n  script:\n    - echo \"Running tests...\"\n\ndeploy-job:\n  stage: deploy\n  tags:\n    - secure-deploy            # Solo se ejecuta en runner(s) con este tag\n  script:\n    - echo \"Deploying with DEPLOY_TOKEN...\"\n    - |\n      curl --fail --silent --header \"PRIVATE-TOKEN: $DEPLOY_TOKEN\" \\\n        https:\/\/gitlab.com\/api\/v4\/projects\/$CI_PROJECT_ID\/releases\n  rules:\n    - if: $CI_COMMIT_BRANCH == \"main\"\n<\/code><\/pre>\n<p>Dado que el runner <code>secure-deploy<\/code> est\u00e1 registrado con acceso <code>ref_protected<\/code>, este trabajo de despliegue solo se ejecutar\u00e1 en el runner espec\u00edfico del proyecto <strong>y<\/strong> solo cuando el pipeline se origine desde una referencia protegida.<\/p>\n<h2>Ejercicio 3: Entornos Protegidos y Aprobaciones de Despliegue<\/h2>\n<p>Incluso con variables protegidas y runners con alcance definido, es posible que desees una puerta humana antes de que el c\u00f3digo llegue a producci\u00f3n. Los <strong>entornos protegidos<\/strong> de GitLab proporcionan exactamente eso.<\/p>\n<h3>Paso 1 \u2014 Crear Entornos<\/h3>\n<ol>\n<li>Navega a <strong>Operate > Environments > New environment<\/strong>.<\/li>\n<li>Crea dos entornos: <code>staging<\/code> y <code>production<\/code>.<\/li>\n<\/ol>\n<h3>Paso 2 \u2014 Proteger el Entorno de Producci\u00f3n<\/h3>\n<ol>\n<li>Ve a <strong>Settings > CI\/CD > Protected environments<\/strong> (disponible en Premium\/Ultimate, o en el nivel gratuito auto-gestionado).<\/li>\n<li>Selecciona <code>production<\/code>.<\/li>\n<li>En <strong>Allowed to deploy<\/strong>, restringe a <code>Maintainers<\/code> (o un usuario espec\u00edfico).<\/li>\n<li>En <strong>Required approvals<\/strong>, establece en <strong>1<\/strong> (o m\u00e1s, dependiendo de tu pol\u00edtica).<\/li>\n<li>Agrega el\/los aprobador(es) designado(s).<\/li>\n<\/ol>\n<h3>Paso 3 \u2014 Actualizar el Pipeline con Definiciones de Entorno<\/h3>\n<pre><code># .gitlab-ci.yml \u2014 Ejercicio 3\nstages:\n  - build\n  - test\n  - deploy\n\nbuild-job:\n  stage: build\n  script:\n    - echo \"Building the application...\"\n\ntest-job:\n  stage: test\n  script:\n    - echo \"Running tests...\"\n\ndeploy-staging:\n  stage: deploy\n  environment:\n    name: staging\n    url: https:\/\/staging.example.com\n  script:\n    - echo \"Deploying to staging...\"\n  rules:\n    - if: $CI_COMMIT_BRANCH == \"main\"\n\ndeploy-production:\n  stage: deploy\n  tags:\n    - secure-deploy\n  environment:\n    name: production\n    url: https:\/\/prod.example.com\n  script:\n    - echo \"Deploying to production...\"\n  rules:\n    - if: $CI_COMMIT_BRANCH == \"main\"\n      when: manual           # Requiere un clic humano\n  allow_failure: false        # El pipeline permanece bloqueado hasta ser aprobado\n<\/code><\/pre>\n<h3>C\u00f3mo Funciona la Aprobaci\u00f3n<\/h3>\n<ol>\n<li>Un push a <code>main<\/code> activa el pipeline.<\/li>\n<li><code>deploy-staging<\/code> se ejecuta autom\u00e1ticamente.<\/li>\n<li><code>deploy-production<\/code> muestra un bot\u00f3n <strong>Play<\/strong> en la interfaz del pipeline.<\/li>\n<li>Hacer clic en <strong>Play<\/strong> no ejecuta inmediatamente el trabajo \u2014 GitLab verifica las reglas de protecci\u00f3n del entorno y presenta un <strong>di\u00e1logo de aprobaci\u00f3n<\/strong> al\/los aprobador(es) designado(s).<\/li>\n<li>Solo despu\u00e9s de que se alcance el n\u00famero requerido de aprobaciones, el trabajo comienza.<\/li>\n<\/ol>\n<p>Esta puerta de dos capas \u2014 <code>when: manual<\/code> m\u00e1s aprobaci\u00f3n de entorno \u2014 asegura que ninguna persona individual pueda enviar c\u00f3digo directamente a producci\u00f3n sin revisi\u00f3n.<\/p>\n<h2>Ejercicio 4: Alcance de CI_JOB_TOKEN<\/h2>\n<p>Cada trabajo de GitLab CI recibe un token autom\u00e1tico en la variable <code>CI_JOB_TOKEN<\/code>. Este token autentica solicitudes de API y Git <em>como el proyecto del pipeline<\/em>. Por defecto, su alcance es peligrosamente amplio.<\/p>\n<h3>El Riesgo<\/h3>\n<p>Sin restricciones, un trabajo en el Proyecto A puede usar <code>CI_JOB_TOKEN<\/code> para clonar o llamar a la API de <em>cualquier otro proyecto<\/em> en el mismo grupo (o instancia, dependiendo de la configuraci\u00f3n). Si un colaborador malicioso inyecta un script en un trabajo de CI, puede exfiltrar c\u00f3digo de repositorios no relacionados.<\/p>\n<h3>Paso 1 \u2014 Restringir el Alcance del Token<\/h3>\n<ol>\n<li>Ve a <strong>Settings > CI\/CD > Token Access<\/strong>.<\/li>\n<li>Cambia <strong>Limit access to this project<\/strong> a <strong>Enabled<\/strong>.<\/li>\n<li>En <strong>Allow CI job tokens from the following projects to access this project<\/strong>, agrega solo los proyectos que genuinamente necesitan acceso (un modelo de lista de permitidos).<\/li>\n<li>En <strong>Limit CI_JOB_TOKEN access to the following projects<\/strong> (saliente), agrega solo los proyectos que tu pipeline necesita alcanzar.<\/li>\n<\/ol>\n<h3>Paso 2 \u2014 Probar el Acceso<\/h3>\n<pre><code># .gitlab-ci.yml \u2014 Ejercicio 4\nstages:\n  - test\n\ntest-token-allowed:\n  stage: test\n  script:\n    - echo \"Cloning an allowed project...\"\n    - git clone https:\/\/gitlab-ci-token:${CI_JOB_TOKEN}@gitlab.com\/my-group\/allowed-project.git\n    - echo \"Success \u2014 access permitted\"\n\ntest-token-denied:\n  stage: test\n  script:\n    - echo \"Cloning a non-allowed project...\"\n    - git clone https:\/\/gitlab-ci-token:${CI_JOB_TOKEN}@gitlab.com\/my-group\/restricted-project.git\n    # Salida esperada: remote: HTTP Basic: Access denied\n    # fatal: Authentication failed \u2014 403 Forbidden\n  allow_failure: true\n<\/code><\/pre>\n<h3>Paso 3 \u2014 Verificar<\/h3>\n<ol>\n<li>Ejecuta el pipeline. <code>test-token-allowed<\/code> tiene \u00e9xito y clona el proyecto permitido.<\/li>\n<li><code>test-token-denied<\/code> falla con <strong>403 Forbidden<\/strong> porque <code>restricted-project<\/code> no est\u00e1 en la lista de permitidos.<\/li>\n<\/ol>\n<p><strong>Lecci\u00f3n clave:<\/strong> Siempre restringe <code>CI_JOB_TOKEN<\/code> al conjunto m\u00e1s peque\u00f1o de proyectos que tu pipeline realmente necesita. Trata el alcance predeterminado \u00ababierto\u00bb como una mala configuraci\u00f3n.<\/p>\n<h2>Ejercicio 5: Seguridad de Pipelines de Merge Request<\/h2>\n<p>Los pipelines de merge request (MR) se ejecutan cuando un colaborador abre o actualiza un merge request. Son esenciales para la calidad del c\u00f3digo \u2014 pero tambi\u00e9n pueden ser un vector de ataque si no se configuran cuidadosamente.<\/p>\n<h3>El Riesgo<\/h3>\n<p>Cuando un colaborador externo hace fork de tu proyecto y abre un MR, GitLab puede ejecutar un pipeline en ese MR. Si el pipeline tiene acceso a variables protegidas o runners privilegiados, el c\u00f3digo del colaborador podr\u00eda exfiltrar secretos.<\/p>\n<h3>Paso 1 \u2014 Configurar Reglas de Pipeline de MR<\/h3>\n<pre><code># .gitlab-ci.yml \u2014 Ejercicio 5\nstages:\n  - validate\n  - build\n  - deploy\n\n# --- Trabajos seguros para ejecutar en pipelines de MR (no se necesitan secretos) ---\nlint:\n  stage: validate\n  script:\n    - echo \"Linting code...\"\n  rules:\n    - if: $CI_PIPELINE_SOURCE == \"merge_request_event\"\n    - if: $CI_COMMIT_BRANCH == \"main\"\n\nunit-tests:\n  stage: validate\n  script:\n    - echo \"Running unit tests...\"\n  rules:\n    - if: $CI_PIPELINE_SOURCE == \"merge_request_event\"\n    - if: $CI_COMMIT_BRANCH == \"main\"\n\n# --- Trabajos que requieren secretos \u2014 nunca ejecutar en pipelines de MR ---\nbuild-image:\n  stage: build\n  script:\n    - echo \"Building and pushing Docker image...\"\n    - echo \"Using REGISTRY_TOKEN = $REGISTRY_TOKEN\"  # Protected + Masked\n  rules:\n    - if: $CI_COMMIT_BRANCH == \"main\"\n\ndeploy-production:\n  stage: deploy\n  tags:\n    - secure-deploy\n  environment:\n    name: production\n    url: https:\/\/prod.example.com\n  script:\n    - echo \"Deploying to production...\"\n  rules:\n    - if: $CI_COMMIT_BRANCH == \"main\"\n      when: manual\n<\/code><\/pre>\n<h3>C\u00f3mo GitLab Maneja los Pipelines de MR desde Forks<\/h3>\n<ul>\n<li>Los pipelines activados por <code>merge_request_event<\/code> desde un <strong>fork<\/strong> se ejecutan autom\u00e1ticamente con <strong>permisos limitados<\/strong>.<\/li>\n<li>Las variables protegidas <strong>nunca<\/strong> se inyectan en pipelines de MR desde forks.<\/li>\n<li><code>CI_JOB_TOKEN<\/code> en pipelines de fork tiene un alcance reducido \u2014 solo puede acceder al proyecto fuente (fork), no al destino.<\/li>\n<\/ul>\n<p>Al separar tus trabajos en \u00abseguros para MR\u00bb (lint, test) y \u00abrequieren secretos\u00bb (build, deploy), aseguras que los colaboradores puedan validar su c\u00f3digo sin exponer credenciales.<\/p>\n<h3>Mejores Pr\u00e1cticas para Pipelines de MR<\/h3>\n<ul>\n<li>Nunca uses <code>only\/except<\/code> \u2014 prefiere <code>rules:<\/code> para claridad y correcci\u00f3n.<\/li>\n<li>Controla los trabajos dependientes de secretos con <code>if: $CI_COMMIT_BRANCH == \"main\"<\/code> (u otra referencia protegida).<\/li>\n<li>Considera habilitar <strong>Pipelines must succeed<\/strong> en <strong>Settings > Merge requests<\/strong> para requerir que el pipeline de MR pase antes de fusionar.<\/li>\n<li>Habilita <strong>Merged results pipelines<\/strong> para probar el <em>resultado de la fusi\u00f3n<\/em> en lugar de solo la rama fuente \u2014 esto detecta problemas de integraci\u00f3n m\u00e1s temprano.<\/li>\n<\/ul>\n<h2>Ejercicio 6: Endurecimiento Adicional<\/h2>\n<p>Con variables, runners, entornos, tokens y pipelines de MR asegurados, varios controles adicionales llevan tu pipeline a una postura de seguridad de nivel producci\u00f3n.<\/p>\n<h3>Tiempos de Espera de Trabajos<\/h3>\n<p>Los trabajos sin l\u00edmite pueden ser abusados para criptominer\u00eda o usados para mantener acceso persistente. Establece tiempos de espera expl\u00edcitos:<\/p>\n<pre><code>deploy-production:\n  stage: deploy\n  timeout: 10 minutes\n  script:\n    - echo \"Deploying...\"\n<\/code><\/pre>\n<h3>Pipelines Interrumpibles<\/h3>\n<p>Prev\u00e9n el desperdicio de recursos y limita la ventana para trabajos maliciosos de larga duraci\u00f3n marcando los trabajos no cr\u00edticos como interrumpibles:<\/p>\n<pre><code>lint:\n  stage: validate\n  interruptible: true     # Se cancela autom\u00e1ticamente si un pipeline m\u00e1s nuevo comienza\n  script:\n    - echo \"Linting...\"\n<\/code><\/pre>\n<h3>Push Rules (Restringir la Creaci\u00f3n de Pipelines)<\/h3>\n<p>En <strong>Settings > Repository > Push rules<\/strong>, puedes:<\/p>\n<ul>\n<li><strong>Rechazar commits sin firmar<\/strong> \u2014 asegura que cada commit est\u00e9 firmado con GPG.<\/li>\n<li><strong>Restringir nombres de ramas<\/strong> \u2014 aplicar una convenci\u00f3n de nombres (por ejemplo, <code>feature\/*<\/code>, <code>bugfix\/*<\/code>).<\/li>\n<li><strong>Prevenir el push de secretos<\/strong> \u2014 la regla de push integrada de GitLab puede bloquear archivos que coincidan con patrones comunes de secretos.<\/li>\n<\/ul>\n<h3>Detecci\u00f3n de Secretos con GitLab SAST<\/h3>\n<p>Agrega la plantilla integrada de Detecci\u00f3n de Secretos de GitLab para capturar credenciales accidentalmente comprometidas:<\/p>\n<pre><code>include:\n  - template: Security\/Secret-Detection.gitlab-ci.yml\n<\/code><\/pre>\n<p>Esto agrega un trabajo <code>secret_detection<\/code> que escanea cada commit en busca de API keys, tokens, contrase\u00f1as y otros patrones de secretos. Los resultados aparecen en la pesta\u00f1a <strong>Security<\/strong> de los merge requests.<\/p>\n<h2>El Pipeline Endurecido Final<\/h2>\n<p>A continuaci\u00f3n se muestra el <code>.gitlab-ci.yml<\/code> completo combinando cada control de seguridad de este laboratorio. Cada l\u00ednea relevante para la seguridad est\u00e1 comentada.<\/p>\n<pre><code># .gitlab-ci.yml \u2014 Pipeline de GitLab CI Completamente Endurecido\n\n# Incluir el esc\u00e1ner de detecci\u00f3n de secretos integrado de GitLab\ninclude:\n  - template: Security\/Secret-Detection.gitlab-ci.yml  # Escanea secretos filtrados\n\nstages:\n  - validate\n  - build\n  - deploy\n\n# --- Configuraci\u00f3n predeterminada aplicada a todos los trabajos ---\ndefault:\n  timeout: 10 minutes        # Prevenir trabajos fuera de control\/abusados\n\n# --- Seguros para pipelines de merge request (no se requieren secretos) ---\nlint:\n  stage: validate\n  interruptible: true        # Cancelar si un pipeline m\u00e1s nuevo comienza\n  script:\n    - echo \"Linting source code...\"\n  rules:\n    - if: $CI_PIPELINE_SOURCE == \"merge_request_event\"  # Ejecutar en MRs\n    - if: $CI_COMMIT_BRANCH == \"main\"                    # Ejecutar en main\n\nunit-tests:\n  stage: validate\n  interruptible: true\n  script:\n    - echo \"Running unit tests...\"\n    - echo \"API_KEY length = ${#API_KEY}\"  # Seguro: imprime solo la longitud\n  rules:\n    - if: $CI_PIPELINE_SOURCE == \"merge_request_event\"\n    - if: $CI_COMMIT_BRANCH == \"main\"\n\n# --- Requiere secretos \u2014 solo se ejecuta en rama protegida ---\nbuild-image:\n  stage: build\n  script:\n    - echo \"Building Docker image...\"\n    - echo \"Authenticating to registry...\"  # Usa REGISTRY_TOKEN (Protected + Masked)\n  rules:\n    - if: $CI_COMMIT_BRANCH == \"main\"       # Solo en rama protegida\n\n# --- Despliegue a staging \u2014 autom\u00e1tico en main ---\ndeploy-staging:\n  stage: deploy\n  environment:\n    name: staging                            # Entorno rastreado\n    url: https:\/\/staging.example.com\n  script:\n    - echo \"Deploying to staging...\"\n  rules:\n    - if: $CI_COMMIT_BRANCH == \"main\"\n\n# --- Despliegue a producci\u00f3n \u2014 manual + aprobaci\u00f3n requerida ---\ndeploy-production:\n  stage: deploy\n  tags:\n    - secure-deploy                          # Se ejecuta solo en runner espec\u00edfico del proyecto\n  environment:\n    name: production                         # Entorno protegido con aprobaciones\n    url: https:\/\/prod.example.com\n  script:\n    - echo \"Deploying to production...\"\n    - |\n      curl --fail --silent \\\n        --header \"PRIVATE-TOKEN: $DEPLOY_TOKEN\" \\   # Variable Protected + Masked\n        --request POST \\\n        \"https:\/\/gitlab.com\/api\/v4\/projects\/$CI_PROJECT_ID\/deployments\"\n  rules:\n    - if: $CI_COMMIT_BRANCH == \"main\"\n      when: manual                           # Requiere activaci\u00f3n humana\n  allow_failure: false                       # El pipeline se bloquea hasta ser aprobado\n  timeout: 5 minutes                         # Tiempo de espera m\u00e1s estricto para despliegues\n<\/code><\/pre>\n<h2>Limpieza<\/h2>\n<p>Despu\u00e9s de completar el laboratorio, limpia los recursos de prueba:<\/p>\n<ol>\n<li><strong>Eliminar o archivar el proyecto de prueba:<\/strong> Ve a <strong>Settings > General > Advanced > Delete project<\/strong>.<\/li>\n<li><strong>Eliminar variables de CI\/CD:<\/strong> Si planeas mantener el proyecto, ve a <strong>Settings > CI\/CD > Variables<\/strong> y elimina las variables de prueba (<code>DEPLOY_TOKEN<\/code>, <code>DB_PASSWORD<\/code>, <code>API_KEY<\/code>).<\/li>\n<li><strong>Desregistrar el runner de prueba:<\/strong><\/li>\n<\/ol>\n<pre><code># Listar runners registrados\nsudo gitlab-runner list\n\n# Desregistrar el runner de prueba\nsudo gitlab-runner unregister --name \"secure-deploy-runner\"\n\n# Opcionalmente eliminar GitLab Runner por completo\nsudo gitlab-runner stop\nsudo gitlab-runner uninstall\nsudo rm \/usr\/local\/bin\/gitlab-runner\n<\/code><\/pre>\n<h2>Conclusiones Clave<\/h2>\n<ul>\n<li><strong>Protege y enmascara cada variable de secreto.<\/strong> Las variables protegidas solo se inyectan en ramas protegidas, y el enmascaramiento previene la exposici\u00f3n accidental en logs. Usa el indicador Hidden para secretos que nunca deber\u00edan ser legibles en la interfaz.<\/li>\n<li><strong>Define el alcance de los runners al nivel m\u00ednimo de confianza requerido.<\/strong> Usa runners espec\u00edficos del proyecto con acceso <code>ref_protected<\/code> para trabajos de despliegue. Reserva los runners compartidos para pasos no sensibles de build y test.<\/li>\n<li><strong>Controla los despliegues a producci\u00f3n con protecci\u00f3n de entorno y aprobaciones.<\/strong> Combinar <code>when: manual<\/code> con un entorno protegido y aprobadores requeridos asegura que ninguna persona individual pueda enviar a producci\u00f3n sin verificaci\u00f3n.<\/li>\n<li><strong>Restringe CI_JOB_TOKEN a una lista de permitidos expl\u00edcita.<\/strong> El alcance predeterminado es demasiado amplio. Limita tanto el acceso entrante como el saliente solo a los proyectos que tu pipeline realmente necesita.<\/li>\n<li><strong>Separa los trabajos de pipeline de MR de los trabajos de despliegue.<\/strong> Los trabajos de lint y test son seguros para pipelines de merge request; los trabajos de build y deploy que necesitan secretos solo deber\u00edan ejecutarse en ramas protegidas.<\/li>\n<li><strong>A\u00f1ade controles adicionales en capas: tiempos de espera, trabajos interrumpibles, push rules y detecci\u00f3n de secretos.<\/strong> Cada capa aborda un vector de ataque diferente y juntas crean defensa en profundidad.<\/li>\n<\/ul>\n<h2>Pr\u00f3ximos Pasos<\/h2>\n<p>Contin\u00faa construyendo tu conocimiento de seguridad CI\/CD con estas gu\u00edas relacionadas:<\/p>\n<ul>\n<li><a href=\"\/es\/ci-cd-security\/ci-cd-execution-models-trust-assumptions-security-guide\/\">Modelos de Ejecuci\u00f3n de CI\/CD y Supuestos de Confianza<\/a> \u2014 Comprende las implicaciones de seguridad de diferentes arquitecturas de CI\/CD y d\u00f3nde se encuentran los l\u00edmites de confianza.<\/li>\n<li><a href=\"\/es\/ci-cd-security\/separation-of-duties-least-privilege-ci-cd-pipelines\/\">Separaci\u00f3n de Funciones y Privilegio M\u00ednimo en Pipelines de CI\/CD<\/a> \u2014 Aprende c\u00f3mo dise\u00f1ar pipelines donde ning\u00fan rol o token individual tenga m\u00e1s acceso del necesario.<\/li>\n<\/ul>\n","protected":false},"excerpt":{"rendered":"<p>Descripci\u00f3n General GitLab CI es la segunda plataforma de CI\/CD m\u00e1s utilizada en la industria, impulsando millones de pipelines en organizaciones de todos los tama\u00f1os. Su estrecha integraci\u00f3n con el control de c\u00f3digo fuente la hace excepcionalmente conveniente \u2014 pero esa misma integraci\u00f3n crea una amplia superficie de ataque si los pipelines no se endurecen &#8230; <a title=\"Lab: Asegurando Pipelines de GitLab CI \u2014 Variables Protegidas, Runners y Entornos\" class=\"read-more\" href=\"https:\/\/secure-pipelines.com\/es\/ci-cd-security\/lab-securing-gitlab-ci-pipelines-protected-variables-runners-environments-2\/\" aria-label=\"Leer m\u00e1s sobre Lab: Asegurando Pipelines de GitLab CI \u2014 Variables Protegidas, Runners y Entornos\">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,57],"tags":[],"post_folder":[],"class_list":["post-697","post","type-post","status-publish","format-standard","hentry","category-ci-cd-security","category-gitlab-ci"],"_links":{"self":[{"href":"https:\/\/secure-pipelines.com\/es\/wp-json\/wp\/v2\/posts\/697","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=697"}],"version-history":[{"count":0,"href":"https:\/\/secure-pipelines.com\/es\/wp-json\/wp\/v2\/posts\/697\/revisions"}],"wp:attachment":[{"href":"https:\/\/secure-pipelines.com\/es\/wp-json\/wp\/v2\/media?parent=697"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/secure-pipelines.com\/es\/wp-json\/wp\/v2\/categories?post=697"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/secure-pipelines.com\/es\/wp-json\/wp\/v2\/tags?post=697"},{"taxonomy":"post_folder","embeddable":true,"href":"https:\/\/secure-pipelines.com\/es\/wp-json\/wp\/v2\/post_folder?post=697"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}