Architect’s Guide: Native GitLab Secret Management
- Mark Kendall
- Jan 15
- 3 min read
Building your CI/CD pipelines without external cloud services like AWS Secrets Manager is a perfectly valid "Keep It Simple" strategy, provided you use GitLab’s native security features correctly.
Here is a developer-focused guide on why and how to handle secrets effectively within GitLab.
Architect’s Guide: Native GitLab Secret Management
As a senior developer, your goal is to prevent "Credential Leakage" while maintaining a low-friction developer experience. If we aren't using an external vault, we must leverage GitLab's "Three Pillars of Secret Safety."
1. The Three Pillars of GitLab Secrets
When you add a variable in Settings > CI/CD > Variables, you must understand these three toggles:
* Protected: This variable is only injected into pipelines running on Protected Branches (like main or production).
* Why: It prevents a developer from creating a "hack-test" branch and writing echo $PROD_DB_PASSWORD to see the value in the logs.
* Masked: GitLab will intercept the variable value in the job logs and replace it with [FILTERED].
* Why: Accidents happen. If a script fails and dumps its environment, your secrets won't be visible to everyone with "Reporter" access.
* Expanded (Off): For secrets containing symbols like $, turn off "Expand variable reference."
* Why: It prevents GitLab from trying to parse your password as another variable name.
2. Use "File" Type Variables (The "Pro" Move)
Instead of the standard "Variable" type, use the File type for sensitive config files (like Kubernetes kubeconfig, SSH private keys, or JSON service accounts).
* The Concept: GitLab doesn't put the content in the environment variable. It saves the content to a temporary file on the runner disk and sets the variable value to the path of that file.
* The Benefit: Most "log-dumping" commands (like env) only show the file path (e.g., /builds/org/repo.tmp/MY_SECRET), not the actual secret.
3. Best Practices for Your Base Classes
Since the DevOps team isn't following your spec, you should bake these "enforcement" rules into your Shared Base Classes.
A. Never Hardcode in .gitlab-ci.yml
Even "non-secret" environment variables should live in the UI or a separate config. If it's in the YAML, it’s version-controlled and visible to everyone.
B. Use Scoped Variables
GitLab allows you to scope variables to specific Environments.
* Create a DB_PASSWORD for scope: production and another for scope: staging.
* The runner will only inject the correct one based on the environment keyword in your job.
C. Implement a "Secret Sanity Check"
In your base class before_script, add a check to ensure required secrets exist before starting the build. This provides clear error messages instead of cryptic "Connection Failed" errors later.
# Shared Base Class Template
.base-deploy:
before_script:
- |
if [ -z "$DEPLOY_TOKEN" ]; then
echo "CRITICAL ERROR: DEPLOY_TOKEN is missing. Ensure it is set in GitLab CI Variables."
exit 1
fi
4. Summary: The Developer "Checklist"
| Action | Standard Env Var | GitLab "File" Var |
|---|---|---|
| Simple API Keys | ✅ Use Masked + Protected | ❌ Overkill |
| SSH Private Keys | ❌ Never (Newlines break it) | ✅ Use File Type |
| JSON Credentials | ❌ Hard to parse | ✅ Use File Type |
| DB Passwords | ✅ Use Masked + Protected | ❌ Not needed |
Immediate Recommendation:
If the DevOps team's pipelines aren't "up to spec," it’s likely because they aren't using Protected Environments. By moving your secrets to Group-level variables and restricting them to the production environment, you force the pipelines to respect your security boundaries regardless of how they are written.
Comments