Secrets: masked, protected and file variables

GitLab CI/CD · Practical · 10 min

Deploy keys and API tokens must never live in the repository, and must not be readable from every branch.

Credentials go in Settings, CI/CD, Variables at project, group or instance level, never in .gitlab-ci.yml. Each variable has options:

  • Masked: the value is replaced with [MASKED] in job logs. Values must meet length and character requirements to be maskable. Masked and hidden variables also can't be revealed in the UI after saving.
  • Protected: only exposed to pipelines on protected branches or tags. A developer pushing a feature branch can't read production credentials, because a job on that branch simply doesn't receive them.
  • Environment scope: only exposed to jobs deploying to a matching environment, such as production.
  • Type: File: GitLab writes the value to a temporary file and the variable contains the path. Ideal for kubeconfigs, certificates and service-account JSON.

The job token CI_JOB_TOKEN is generated per job and lets the job access the package registry, container registry and some APIs of permitted projects; it expires when the job ends. Control which other projects accept it with the token access allowlist.

Masking is a safety net, not a guarantee: base64-encoding or printing part of a secret bypasses it. And anyone who can push to a protected branch can exfiltrate protected variables, so protect branches carefully.

Example

deploy:
  image: $CI_REGISTRY/acme/tools/kubectl:1.33   # your own tools image
  rules:
    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH   # protected branch
  environment: production
  script:
    # KUBECONFIG is a protected, File-type variable: its value is a path
    - kubectl --kubeconfig "$KUBECONFIG" get nodes
    - ./notify.sh --token "$SLACK_TOKEN"   # masked: logs show [MASKED]

publish-package:
  script:
    - 'curl --header "JOB-TOKEN: $CI_JOB_TOKEN" --upload-file pkg.tgz
       "$CI_API_V4_URL/projects/$CI_PROJECT_ID/packages/generic/pkg/1.0.0/pkg.tgz"'

Remember

  • Secrets live in CI/CD settings, not YAML
  • Protected variables reach only protected branches and tags
  • File-type variables hold a path to a temporary file

Watch out for

  • Leaving a deploy secret unprotected so any branch's pipeline can read it.

Check yourself

  1. A protected variable is defined. A developer pushes to feature/x (unprotected). What does their job see?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: docs.gitlab.com/ci/variables/