Por Qué Importa la Seguridad en GitLab CI
Los pipelines de GitLab CI/CD son potentes — pero con ese poder viene el riesgo. Una variable mal configurada puede filtrar secretos. Un runner sin alcance definido puede ejecutar código malicioso. Un entorno sin protección puede permitir que un desarrollador junior haga un despliegue directo a producción. Este cheat sheet te ofrece YAML listo para copiar y pegar para cada control crítico de seguridad en GitLab CI, desde variables protegidas hasta la federación OIDC.
Guarda esta página en tus favoritos. Úsala como referencia cada vez que configures un nuevo proyecto o audites uno existente.
1. Variables Protegidas, Enmascaradas y Ocultas
Las variables de GitLab CI/CD controlan cómo fluyen los secretos en tus pipelines. Configurar esto incorrectamente es la causa número uno de fugas de credenciales en CI/CD. Cada valor sensible debe estar protegido (disponible solo en ramas/tags protegidos), enmascarado (oculto en los logs del job) y, donde sea compatible, oculto (invisible en la interfaz después de la creación).
Configuración de Variables a través de la API
# Crear una variable protegida + enmascarada a través de la API de GitLab
curl --request POST \
--header "PRIVATE-TOKEN: $GITLAB_TOKEN" \
"https://gitlab.example.com/api/v4/projects/$PROJECT_ID/variables" \
--form "key=AWS_SECRET_ACCESS_KEY" \
--form "value=$MY_SECRET" \
--form "protected=true" \
--form "masked=true" \
--form "variable_type=env_var"
Uso de Variables en .gitlab-ci.yml
variables:
# Las variables a nivel de grupo o proyecto se inyectan automáticamente.
# Las variables de tipo archivo se escriben en una ruta temporal.
DEPLOY_TOKEN:
description: "Token para despliegue en producción"
value: "" # Intencionalmente vacío — configurar en CI/CD Settings
deploy_production:
stage: deploy
script:
- echo "Desplegando con token enmascarado..."
- ./deploy.sh --token "$DEPLOY_TOKEN"
rules:
- if: $CI_COMMIT_BRANCH == "main"
environment:
name: production
Reglas clave:
- Nunca escribas secretos directamente en
.gitlab-ci.yml— utiliza siempre la configuración de variables CI/CD. - Establece
protected=truepara que los secretos solo estén disponibles en ramas protegidas. - Establece
masked=truepara que los valores se oculten en los logs del job. - Usa variables a nivel de grupo para secretos compartidos entre proyectos (por ejemplo, credenciales de la nube).
2. Tipos de Runners y Alcance
Los runners ejecutan tus jobs de CI/CD. Si cualquier runner puede tomar cualquier job, un merge request malicioso podría exfiltrar secretos de un runner de producción. Un alcance adecuado es esencial.
Registro de Runner con Tags y Protección
# Registrar un runner con alcance solo para ramas protegidas
gitlab-runner register \
--non-interactive \
--url "https://gitlab.example.com" \
--token "$RUNNER_REG_TOKEN" \
--executor docker \
--docker-image alpine:latest \
--tag-list "production,protected" \
--access-level ref_protected
Asignación de Jobs a Runners Específicos
# .gitlab-ci.yml — asegurar que los jobs de producción solo se ejecuten en runners protegidos
deploy_production:
stage: deploy
tags:
- production
- protected
script:
- kubectl apply -f k8s/production/
rules:
- if: $CI_COMMIT_BRANCH == "main"
environment:
name: production
# Los jobs de desarrollo usan un runner separado y sin privilegios
test:
stage: test
tags:
- shared
- development
script:
- pytest tests/
Mejores prácticas:
- Usa
--access-level ref_protectedpara restringir los runners a ramas y tags protegidos. - Usa runners específicos del proyecto para cargas de trabajo sensibles — nunca compartas runners de producción entre proyectos no relacionados.
- Prefiere runners efímeros (ejecutores Docker, Kubernetes) para que el entorno se destruya después de cada job.
- Desactiva los shared runners en proyectos que manejan despliegues sensibles.
3. Entornos Protegidos con Aprobaciones
Los entornos protegidos añaden una puerta humana antes de los despliegues. Esta es tu última línea de defensa contra cambios no autorizados en producción.
Configuración del Entorno en .gitlab-ci.yml
# .gitlab-ci.yml — despliegue con protección de entorno
deploy_staging:
stage: deploy
script:
- ./deploy.sh staging
environment:
name: staging
url: https://staging.example.com
rules:
- if: $CI_COMMIT_BRANCH == "main"
deploy_production:
stage: deploy
script:
- ./deploy.sh production
environment:
name: production
url: https://example.com
deployment_tier: production
rules:
- if: $CI_COMMIT_BRANCH == "main"
when: manual
allow_failure: false
Configuración de Reglas de Aprobación a través de la API
# Proteger el entorno de producción con aprobaciones requeridas
curl --request POST \
--header "PRIVATE-TOKEN: $GITLAB_TOKEN" \
"https://gitlab.example.com/api/v4/projects/$PROJECT_ID/protected_environments" \
--data '{"name": "production", "deploy_access_levels": [{"group_id": 9899826}], "required_approval_count": 2, "approval_rules": [{"group_id": 9899826, "required_approvals": 2}]}'
Configura esto en Settings > CI/CD > Protected Environments en la interfaz de GitLab. Requiere al menos dos aprobaciones para despliegues a producción. Restringe el acceso de despliegue a grupos o usuarios específicos — nunca a “All maintainers.”
4. Alcance de CI_JOB_TOKEN
CI_JOB_TOKEN es un token automático que GitLab inyecta en cada job. Por defecto, puede acceder a otros proyectos en tu grupo — un riesgo serio de movimiento lateral. Desde GitLab 16.0, deberías restringir su alcance.
Restricción del Acceso del Token
# Verificar el alcance actual de CI_JOB_TOKEN
curl --header "PRIVATE-TOKEN: $GITLAB_TOKEN" \
"https://gitlab.example.com/api/v4/projects/$PROJECT_ID/job_token_scope"
# Limitar CI_JOB_TOKEN para que solo acceda a proyectos específicos
curl --request PATCH \
--header "PRIVATE-TOKEN: $GITLAB_TOKEN" \
"https://gitlab.example.com/api/v4/projects/$PROJECT_ID/job_token_scope" \
--data '{"enabled": true}'
# Añadir un proyecto a la lista de permitidos
curl --request POST \
--header "PRIVATE-TOKEN: $GITLAB_TOKEN" \
"https://gitlab.example.com/api/v4/projects/$PROJECT_ID/job_token_scope/allowlist" \
--data '{"target_project_id": 12345}'
Uso Seguro de CI_JOB_TOKEN en Pipelines
# .gitlab-ci.yml — uso de CI_JOB_TOKEN para triggers entre proyectos
trigger_deploy:
stage: deploy
trigger:
project: my-group/deploy-project
branch: main
strategy: depend
# CI_JOB_TOKEN se usa automáticamente para el trigger.
# El proyecto destino debe incluir este proyecto en su lista de permitidos.
Reglas clave: Habilita el límite de alcance del CI/CD job token en cada proyecto. Solo incluye en la lista de permitidos los proyectos específicos que genuinamente necesitan acceso entre proyectos. Audita las listas de permitidos trimestralmente.
5. OIDC id_tokens para AWS y GCP
La federación OIDC elimina por completo las credenciales de nube de larga duración en tus variables de CI/CD. GitLab emite un JWT de corta duración, y tu proveedor de nube lo intercambia por credenciales temporales. Este es el estándar de oro para la autenticación en la nube en pipelines.
Federación OIDC con AWS
# .gitlab-ci.yml — autenticación OIDC con AWS
deploy_aws:
stage: deploy
image: amazon/aws-cli:latest
id_tokens:
GITLAB_OIDC_TOKEN:
aud: https://gitlab.example.com
variables:
ROLE_ARN: arn:aws:iam::123456789012:role/gitlab-ci-deploy
script:
- >
export $(printf "AWS_ACCESS_KEY_ID=%s AWS_SECRET_ACCESS_KEY=%s AWS_SESSION_TOKEN=%s"
$(aws sts assume-role-with-web-identity
--role-arn $ROLE_ARN
--role-session-name "GitLabCI-${CI_PROJECT_ID}-${CI_PIPELINE_ID}"
--web-identity-token "$GITLAB_OIDC_TOKEN"
--duration-seconds 3600
--query 'Credentials.[AccessKeyId,SecretAccessKey,SessionToken]'
--output text))
- aws s3 sync ./build s3://my-production-bucket/
rules:
- if: $CI_COMMIT_BRANCH == "main"
environment:
name: production
Federación de Identidades de Carga de Trabajo con GCP
# .gitlab-ci.yml — autenticación OIDC con GCP
deploy_gcp:
stage: deploy
image: google/cloud-sdk:latest
id_tokens:
GITLAB_OIDC_TOKEN:
aud: https://gitlab.example.com
script:
- echo "$GITLAB_OIDC_TOKEN" > /tmp/gitlab_token.txt
- >
gcloud iam workload-identity-pools create-cred-config
projects/$GCP_PROJECT_NUMBER/locations/global/workloadIdentityPools/$POOL_ID/providers/$PROVIDER_ID
--service-account="$GCP_SERVICE_ACCOUNT"
--output-file=/tmp/gcp_creds.json
--credential-source-file=/tmp/gitlab_token.txt
- export GOOGLE_APPLICATION_CREDENTIALS=/tmp/gcp_creds.json
- gcloud config set project $GCP_PROJECT_ID
- gcloud run deploy my-service --image gcr.io/$GCP_PROJECT_ID/my-app:$CI_COMMIT_SHA
rules:
- if: $CI_COMMIT_BRANCH == "main"
environment:
name: production
En el lado de la nube, configura la política de confianza para validar claims como project_path, ref y ref_protected de modo que solo proyectos y ramas específicos puedan asumir el rol.
6. Seguridad de Pipelines de Merge Request
Los pipelines de merge request se ejecutan sobre código que aún no ha sido revisado. Trátalos como no confiables. Nunca expongas secretos de producción a los pipelines de MR.
# .gitlab-ci.yml — reglas separadas para pipelines de MR vs. pipelines de rama
test:
stage: test
script:
- pytest tests/
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
- if: $CI_COMMIT_BRANCH == "main"
deploy_production:
stage: deploy
script:
- ./deploy.sh production
rules:
# NUNCA ejecutar en pipelines de merge request
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
when: never
- if: $CI_COMMIT_BRANCH == "main"
environment:
name: production
Controles críticos:
- Usa
rules:para asegurar que los jobs de despliegue nunca se ejecuten en pipelines de tipomerge_request_event. - Requiere que el pipeline sea exitoso antes de hacer merge (Settings > Merge Requests).
- Habilita “Pipelines must succeed” y “All discussions must be resolved.”
- Considera habilitar merged results pipelines para probar el estado post-merge.
7. Plantilla de Detección de Secretos
El escáner integrado de detección de secretos de GitLab captura credenciales accidentalmente comprometidas antes de que lleguen a la rama por defecto.
# .gitlab-ci.yml — incluir la plantilla de detección de secretos
include:
- template: Jobs/Secret-Detection.gitlab-ci.yml
# Sobrescribir para que el pipeline falle si se encuentran secretos
secret_detection:
variables:
SECRET_DETECTION_HISTORIC_SCAN: "true" # Escanear el historial completo de git
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
allow_failure: false # Bloquear el pipeline al detectar secretos
Para un escaneo más completo, añade también las plantillas de SAST y dependency scanning:
include:
- template: Jobs/Secret-Detection.gitlab-ci.yml
- template: Jobs/SAST.gitlab-ci.yml
- template: Jobs/Dependency-Scanning.gitlab-ci.yml
Revisa los hallazgos en el Security Dashboard (disponible en GitLab Ultimate) o analiza los artefactos JSON en los tiers inferiores.
8. Push Rules
Las push rules aplican políticas a nivel de Git — antes de que el código entre siquiera en un pipeline. Úsalas para prevenir que se suban secretos, imponer estándares en los mensajes de commit y restringir tipos de archivos.
# Configurar push rules a través de la API
curl --request PUT \
--header "PRIVATE-TOKEN: $GITLAB_TOKEN" \
"https://gitlab.example.com/api/v4/projects/$PROJECT_ID/push_rule" \
--data '{"deny_delete_tag": true, "prevent_secrets": true, "commit_message_regex": "^(feat|fix|chore|docs|refactor|test|ci)\\(.*\\):.*", "max_file_size": 50, "member_check": true, "reject_unsigned_commits": true}'
Push rules recomendadas:
prevent_secrets: true— Rechaza pushes que contengan archivos que parezcan secretos (claves, tokens, certificados).reject_unsigned_commits: true— Requiere commits firmados con GPG (GitLab Premium+).commit_message_regex— Impone mensajes de commit convencionales para un registro de auditoría limpio.max_file_size— Previene la subida accidental de binarios grandes.member_check: true— Rechaza commits de personas que no son miembros del proyecto.
9. Timeouts de Jobs e interruptible
Los jobs desbocados desperdician recursos y pueden ser explotados para criptominería. Establece timeouts explícitos y marca los jobs no críticos como interruptible para que se cancelen cuando un nuevo pipeline comience en la misma rama.
# .gitlab-ci.yml — timeouts e interruptible
default:
timeout: 30m # Timeout global por defecto para todos los jobs
interruptible: true # Cancelar jobs en ejecución cuando se hace push de un nuevo commit
retry:
max: 1
when:
- runner_system_failure
- stuck_or_timeout_failure
test:
stage: test
timeout: 15m
interruptible: true
script:
- pytest tests/ --timeout=600
deploy_production:
stage: deploy
timeout: 20m
interruptible: false # NUNCA cancelar un despliegue a producción en ejecución
script:
- ./deploy.sh production
rules:
- if: $CI_COMMIT_BRANCH == "main"
when: manual
environment:
name: production
Directrices:
- Establece un timeout a nivel de proyecto en Settings > CI/CD > General Pipelines (recomendado: 60 minutos máximo).
- Establece timeouts a nivel de job más ajustados que el valor por defecto del proyecto.
- Marca los jobs de test y lint como
interruptible: truepara ahorrar capacidad de runners. - Marca los jobs de despliegue como
interruptible: falsepara prevenir despliegues parciales. - Usa
retrysolo para fallos transitorios de infraestructura — nunca para fallos de tests.
Tabla de Referencia Rápida
| Control | Dónde Configurar | Tier Mínimo |
|---|---|---|
| Variables Protegidas/Enmascaradas | Settings > CI/CD > Variables | Free |
| Alcance de Runners | Settings > CI/CD > Runners | Free |
| Entornos Protegidos | Settings > CI/CD > Protected Environments | Premium |
| Alcance de CI_JOB_TOKEN | Settings > CI/CD > Token Access | Free |
| OIDC id_tokens | .gitlab-ci.yml |
Free |
| Detección de Secretos | include: template |
Free (Ultimate para dashboard) |
| Push Rules | Settings > Repository > Push Rules | Premium |
| Timeouts de Jobs | Settings > CI/CD + .gitlab-ci.yml |
Free |
Lecturas Adicionales y Laboratorios
Continúa fortaleciendo tus pipelines de GitLab CI/CD con estos recursos relacionados:
- Guía de Seguridad de GitLab CI/CD — Un recorrido completo por cada configuración de seguridad en GitLab CI/CD.
- Mejores Prácticas de Gestión de Secretos en CI/CD — Profundización en integración con vault, rotación y secretos con privilegios mínimos.
- Autenticación OIDC en Pipelines de CI/CD — Laboratorio paso a paso para configurar OIDC con AWS, GCP y Azure.
- Checklist de Seguridad de Pipelines CI/CD — La lista de verificación completa que cubre GitHub Actions, GitLab CI y Jenkins.
- Documentación Oficial de GitLab CI/CD — La referencia autoritativa para todas las funcionalidades de GitLab CI/CD.
La seguridad no es una configuración de una sola vez — es una práctica continua. Revisa la configuración de seguridad de tus pipelines trimestralmente, rota las credenciales, audita el acceso de los runners y mantén tu instancia de GitLab actualizada. Este cheat sheet te da los bloques de construcción. Ahora ve y asegura tus pipelines.