instead of just protected variables: dynamic credentials over static secrets
GitLab protected and masked variables are the standard way to use secrets in a pipeline, but they remain static, long-lived values that can circulate unchanged for months in the worst case. HashiCorp Vault takes a different route through the native GitLab CI integration: credentials are issued only when the pipeline runs, are short-lived, and expire automatically. This article shows how the integration works technically, what it gains over plain protected variables, and when the extra operational effort actually pays off for a Magento project.
Table of Contents
- 1. The fundamental problem with static CI/CD variables
- 2. How GitLab protected and masked variables work, and where their limits lie
- 3. What HashiCorp Vault fundamentally does differently
- 4. The native GitLab Vault integration via JWT/OIDC ID tokens
- 5. Example: dynamic database credentials for the deploy job
- 6. Operations: policies, auth methods, and rotation in everyday use
- 7. Effort versus benefit: when Vault pays off for a Magento project
- 8. Migration strategy: moving from protected variables to Vault step by step
- 9. Practical recommendation: combining Vault and protected variables deliberately instead of playing them off against each other
- 10. Summary
- 11. FAQ
1. The fundamental problem with static CI/CD variables
Every GitLab variable marked as protected or masked is, at its core, a single text value stored in the project or group configuration and loaded into the job environment on every matching pipeline run. Unless that value is manually rotated, it stays exactly the same for months or years, which makes it an attractive target: whoever captures it once, for instance through a compromised runner or overly permissive debug logging, holds a permanently valid credential.
For many use cases, such as a single API key for a non-critical third-party service, this risk is acceptable. But once database credentials, SSH deploy keys for production servers, or Composer auth tokens for commercial Magento extensions are involved, a permanently valid, static secret carries far more weight, because a single leak can potentially be exploited indefinitely until someone manually triggers a rotation.
2. How GitLab protected and masked variables work, and where their limits lie
Protected variables are only available to pipelines running on protected branches or tags, which prevents an arbitrary feature branch from accidentally getting access to production secrets. Masked variables ensure the value is replaced with asterisks in job logs, provided the value meets certain format rules, such as containing no line breaks and having a minimum length.
Both mechanisms, however, only protect the transport path and visibility, not the secret's own lifespan. A database username and password once stored in the project settings remains exactly that one user with exactly that one password, no matter how often the pipeline runs. The GitLab variable itself has no mechanism for expiry, automatic rotation, or logging which specific pipeline run actually used the secret and when.
3. What HashiCorp Vault fundamentally does differently
Vault does not manage secrets as static values but can issue them dynamically at request time through so-called secrets engines. With the database secrets engine, for example, Vault generates a fresh database user with a randomly generated password on every request, along with a set lease time, after which Vault automatically removes that user from the database again, with no manual intervention required.
This fundamentally changes the security model: even if an attacker captures an issued credential, it becomes automatically worthless after a short time. Vault additionally keeps a complete audit log of every issuance and every access, so it becomes possible after the fact to trace exactly which pipeline received which credential and when, which makes a substantial difference for compliance requirements such as PCI DSS or ISO 27001.
4. The native GitLab Vault integration via JWT/OIDC ID tokens
Since GitLab 15.7, JWT-based ID tokens can be generated directly in every job, cryptographically signed and carrying claims such as project, branch, and pipeline ID. Vault can be configured through its JWT/OIDC auth method to verify this token and, based on it, issue a short-lived Vault session for exactly that job, with no statically stored Vault token in GitLab beforehand.
Which Vault policy a job receives is determined through bound claims in Vault, for instance a restriction to a specific GitLab project or a specific protected branch. As a result, a job from a feature branch automatically ends up with fewer permissions than a job running on the protected main branch, without any separate, manually managed credential having to exist for that anywhere.
deploy_production:
stage: deploy
id_tokens:
VAULT_ID_TOKEN:
aud: https://vault.mironsoft.internal
variables:
VAULT_ADDR: https://vault.mironsoft.internal
secrets:
DB_PASSWORD:
vault:
engine:
name: database
path: database
path: creds/magento-readwrite
field: password
token: $VAULT_ID_TOKEN
script:
- bin/magento setup:upgrade --keep-generated
rules:
- if: '$CI_COMMIT_BRANCH == "main"'
5. Example: dynamic database credentials for the deploy job
In the classic approach, a single MySQL user with full privileges is stored permanently as a protected variable and used by every deploy job. With the Vault database secrets engine, Vault instead generates a fresh user on every pipeline run, whose permissions are scoped exactly to what setup:upgrade needs, and whose lease time typically spans a few hours, enough for the deploy job's runtime, but no longer.
The job itself barely needs adjustment for this: instead of reading credentials from a predefined environment variable, the secrets: keyword in the job definition fetches the values directly from Vault and exposes them as an environment variable, so bin/magento setup:upgrade works unchanged, just with a credential that automatically stops working once the lease time expires.
# Configure the Vault database secrets engine for MySQL
vault secrets enable database
vault write database/config/magento-mysql \
plugin_name=mysql-database-plugin \
connection_url="{{username}}:{{password}}@tcp(db.internal:3306)/" \
allowed_roles="magento-readwrite" \
username="vault-admin" \
password="initial-admin-password"
vault write database/roles/magento-readwrite \
db_name=magento-mysql \
creation_statements="CREATE USER '{{name}}'@'%' IDENTIFIED BY '{{password}}'; \
GRANT SELECT, INSERT, UPDATE, DELETE ON magento.* TO '{{name}}'@'%';" \
default_ttl="2h" \
max_ttl="4h"
6. Operations: policies, auth methods, and rotation in everyday use
In day-to-day Vault operations, policies should be defined as granularly as possible, ideally one dedicated policy per pipeline purpose, for instance one for database migrations and a separate one for access to Composer auth tokens, rather than a single, broad policy for all CI jobs. Besides the JWT/OIDC method, Vault also supports AppRole auth, which can make sense for runner environments where no native GitLab JWT is available, such as self-hosted runners in isolated networks.
For static secrets that cannot be issued dynamically for technical reasons, such as API keys for external SaaS services, Vault's KV secrets engine still offers a central, versioned, and audited storage location, which at least substantially improves traceability, even though the value's own lifespan still has to be rotated manually.
7. Effort versus benefit: when Vault pays off for a Magento project
Operating a dedicated Vault cluster, including an unsealing strategy, high availability, and a backup concept, is itself a non-trivial infrastructure project that ties up staff time and ongoing operational effort. For a single small Magento project with few developers and a manageable number of secrets, this effort frequently is not in good proportion to the security gain over carefully managed protected variables with regular manual rotation.
Once multiple teams, multiple Magento instances, or strict compliance requirements such as PCI DSS come into play, however, the calculation shifts noticeably: a central Vault cluster operated jointly for all projects pays for itself quickly, because the operational cost is spread across many users while the security gain from short-lived credentials and a complete audit log stays the same per project.
8. Migration strategy: moving from protected variables to Vault step by step
A sensible starting point is not converting all secrets at once, but beginning with the single most sensitive value, in a Magento project frequently the database credentials for the production deploy job. That one job gets switched over to the Vault integration while every other job continues unchanged with protected variables, which allows experience with the integration to build up without risking the entire deployment at once.
Only once the JWT/OIDC integration has proven itself in production do further secrets follow gradually, such as Composer auth tokens or SSH deploy keys. The table below summarizes the properties of protected variables and Vault respectively, and where the line for a sensible migration typically runs.
9. Practical recommendation: combining Vault and protected variables deliberately instead of playing them off against each other
In practice the decision is rarely a pure either-or. For a single small Magento project without a dedicated operations team, carefully maintained protected and masked variables with a fixed, documented rotation schedule, for instance quarterly, usually remain the more pragmatic choice, because the operational effort of a dedicated Vault cluster is hard to justify. Non-critical values such as an API key for an external image service do not necessarily need to move to Vault just because the tool happens to be technically available.
Once a Vault cluster is already running for other projects within the company, or once several Magento instances exist with separately isolated production and staging databases, it pays off to consistently establish Vault as the single source of truth for all sensitive credentials, rather than switching between both approaches case by case. That consistency spares the team the recurring discussion of which secret deserves which protection mechanism, and makes audits toward customers or certification bodies considerably easier.
| Property | Protected/Masked Variables | HashiCorp Vault | Recommendation |
|---|---|---|---|
| Secret lifespan | Static, until manually rotated | Dynamic, automatic expiry | Vault for sensitive credentials |
| Audit log per access | Not available | Complete, per issuance | Vault under compliance duty |
| Operational effort | Minimal | Dedicated cluster required | Protected variables for small projects |
| Access scoping | Protected branch/tag | Fine-grained policies per job | Vault for multiple teams |
| Setup complexity | A few minutes | Several days to weeks | Gradual migration |
Mironsoft
CI/CD pipelines, zero-downtime deployments and release automation
Deployments that run without downtime and without the nail-biting?
We review existing GitLab pipelines for fragile deployment steps and missing safeguards, then build a release process with zero-downtime deployments, automated checks and a rollback you can actually trust in an emergency.
Pipeline Review
Checking an existing .gitlab-ci.yml for fragility, missing stages and security gaps.
Zero-Downtime Deployment
Building symlink releases, health checks and rollback strategies for Magento stores.
CI/CD Automation
Connecting tests, security scans and deployments into one reliable pipeline.
10. Summary
Vault vs. Protected Variables: The Essentials at a Glance
Core problem
Protected and masked variables are static and long-lived, a single leak can potentially be exploited indefinitely.
What Vault delivers
Dynamically issued, short-lived credentials with automatic expiry and a complete audit log via the secrets engine.
Technical path
GitLab JWT/OIDC ID tokens authenticate jobs against Vault, with no statically stored Vault token needed beforehand.
When it pays off
With multiple teams, multiple Magento instances, or strict compliance requirements, not necessarily for a single small project.