Implementing Security Best Practices
1. Using Protected Branches
| Property | Description | Default | Notes |
|---|---|---|---|
| protected | Protects the branch from being deleted or force-pushed | false | Enables branch protection rules |
| developers_can_push | Allows developers to push to the branch | false | Requires approval for pushes |
| maintainers_can_push | Allows maintainers to push to the branch | false | Enables direct pushes for maintainers |
| developers_can_merge | Allows developers to merge into the branch | false | Requires approval for merges |
| maintainers_can_merge | Allows maintainers to merge into the branch | false | Enables direct merges for maintainers |
Example: Protecting a Branch in GitLab CI/CD
branch_protection:
rules:
- if: "($CI_COMMIT_BRANCH == 'main')"
protected: true
developers_can_push: false
maintainers_can_push: true
developers_can_merge: false
maintainers_can_merge: true
Note: Protected branches are essential for enforcing security policies and preventing unauthorized changes to critical code.
2. Configuring Protected Tags
| Property | Value | Description |
|---|---|---|
| protected | true |
Indicates if the tag is protected |
| create_access_level | 40 |
Access level required to create the tag (Maintainer: 40, Developer: 30) |
| push_access_level | 40 |
Access level required to push to the tag (Maintainer: 40, Developer: 30) |
Example: Protecting a Tag in .gitlab-ci.yml
release:
stage: release
script:
- echo "Releasing version $CI_COMMIT_TAG"
only:
- tags
tags:
- release
Note: Protected tags can only be created or modified by users with Maintainer or Owner permissions.
Warning: Ensure that only trusted users have the ability to push to protected tags.
3. Implementing Least Privilege for Runners
| Runner Type | Permissions | Security Considerations |
|---|---|---|
| Shared Runner | read, write to project repo |
High risk if compromised; use DEPRECATED in favor of group/project runners |
| Group Runner | read, write to group projects |
Better isolation; restrict access to specific groups |
| Project Runner | read, write to project repo only |
Best practice for least privilege; highly secure |
Example: Project Runner Configuration
variables:
GIT_STRATEGY: clone
runner:
name: project-runner
tags:
- project-specific
access_level: ref_protected
Note: Runner access levels can be set to
not_protected, ref_protected, or protected. Use protected for sensitive projects.
Warning: Never grant
write permissions to runners in public projects without strict access controls.
| Permission Level | Description | Use Case |
|---|---|---|
| read | Can pull code but not push changes | Build and test environments |
| write | Can pull and push changes to repo | Deployment or release pipelines |
| admin | Full access to repo and CI/CD settings | Highly sensitive or internal tools only |
4. Using Masked Variables (masked: true)
| Property | Value | Description |
|---|---|---|
| masked | true |
When set to true, the variable value is masked in the job logs and UI. |
Example: Masked Variable in GitLab CI/CD
variables:
API_KEY: "secret123"
masked: true
job_name:
script:
- echo "Using API key: $API_KEY"
Note: The
masked property is not a standard GitLab CI/CD variable attribute. Instead, use the masked keyword in the variables section to mask sensitive values in logs.
Warning: Even with masking, sensitive data should not be stored in plain text within CI/CD configuration files. Use GitLab's protected variables or external secret management tools.
5. Rotating CI/CD Tokens Regularly
| Token Type | Rotation Frequency | Notes |
|---|---|---|
| Runner Registration Token | Every 90 days |
Used for registering new runners. Rotate to prevent unauthorized access. |
| Job Token | Every 30 days |
Used for job execution and access to artifacts. Rotate to reduce exposure. |
| Personal Access Token (PAT) | Every 90 days |
Used for API access and project operations. Rotate to maintain security. |
| Deploy Token | Every 180 days |
Used for deployment to environments. Rotate to limit access scope. |
Example: Rotating Job Token in CI/CD Pipeline
stages:
- build
- test
- deploy
variables:
GITLAB_TOKEN: ${{CI_JOB_TOKEN}}
job_build:
stage: build
script:
- echo "Building project..."
- curl --header "JOB-TOKEN: $GITLAB_TOKEN" https://gitlab.example.com/api/v4/projects/12345678
job_deploy:
stage: deploy
script:
- echo "Deploying to production..."
- curl --header "JOB-TOKEN: $GITLAB_TOKEN" https://gitlab.example.com/api/v4/projects/12345678/deployments
Note: Ensure that all pipelines using the token are updated to use the new token after rotation.
Warning: Tokens that are not rotated may lead to unauthorized access if compromised.
6. Implementing Secrets Management
| Property | Description | Example |
|---|---|---|
| CI_REGISTRY_PASSWORD | Registry password for Docker image pushing | CI_REGISTRY_PASSWORD=$CI_REGISTRY_USER |
| GITLAB_TOKEN | Personal access token for GitLab API access | GITLAB_TOKEN=$CI_JOB_TOKEN |
| AWS_ACCESS_KEY_ID | Access key for AWS services | AWS_ACCESS_KEY_ID=$AWS_ACCESS_KEY_ID |
| DATABASE_PASSWORD | Password for database connections | DATABASE_PASSWORD=$DB_PASS |
Example: Using GitLab CI Variables
variables:
DATABASE_PASSWORD: $DB_PASS
GITLAB_TOKEN: $CI_JOB_TOKEN
script:
- echo "Using database password: $DATABASE_PASSWORD"
- curl --header "PRIVATE-TOKEN: $GITLAB_TOKEN" https://gitlab.example.com/api/v4/projects
Note: Never expose secrets in logs or console output. Use masked variables where possible.
Warning: Avoid hardcoding secrets in .gitlab-ci.yml files. Use GitLab's UI or API to set variables securely.
| Secret Type | Storage Method | Security Level | Best Practice |
|---|---|---|---|
CI_REGISTRY_PASSWORD |
GitLab CI Variables | High | Use masked variables to prevent exposure in logs |
GITLAB_TOKEN |
GitLab CI Variables | High | Limit scope to specific project or group permissions |
AWS_ACCESS_KEY_ID |
GitLab CI Variables | Medium | Rotate keys regularly and use least privilege IAM roles |
DATABASE_PASSWORD |
GitLab CI Variables | High | Store in encrypted format and restrict access to authorized users only |
7. Using Signed Commits Verification
| Feature | Description | Configuration |
|---|---|---|
| commit signing | Ensures commits are cryptographically signed by the author | Enabled via GPG key setup in GitLab CI/CD |
| commit verification | Verifies that commits were signed with a valid key | Configurable in project settings or pipeline rules |
Example: Enabling commit signing in GitLab CI
variables:
GIT_AUTHOR_NAME: "CI Pipeline"
GIT_AUTHOR_EMAIL: "ci@example.com"
script:
- git config --global user.name "$GIT_AUTHOR_NAME"
- git config --global user.email "$GIT_AUTHOR_EMAIL"
- git commit --signoff -m "Signed commit from CI"
Note: Commit signing requires GPG key setup in GitLab for verification to work properly.
Verification Process
- GitLab verifies GPG signature on each commit
- Requires valid GPG key associated with user
- Can be enforced via branch protection rules
8. Configuring RBAC for Runners
| Permission | Description | Resource | Effect |
|---|---|---|---|
| Runner: Read | Allows reading runner information and status | runner |
View runners in project |
| Runner: Manage | Allows managing runners including enabling/disabling and assigning to jobs | runner |
Enable/disable runners, assign to jobs |
| Runner: Admin | Allows full control over runners including deletion and configuration | runner |
Delete runners, modify configurations |
Example: Setting up RBAC for runners in GitLab
gitlab-runner register \
--url "https://gitlab.example.com/" \
--registration-token "PROJECT_REGISTRATION_TOKEN" \
--executor "shell" \
--name "secure-runner" \
--tag-list "secure,production" \
--run-untagged="false" \
--locked="true"
Note: RBAC policies should be configured at the project or group level to restrict runner access based on roles.
9. Implementing Audit Logging
| Aspect | Description | Configuration |
|---|---|---|
| audit_log_enabled | Enables audit logging for GitLab CI/CD jobs | true or false |
| audit_log_level | Log level for audit events | info, warn, error |
| audit_log_output | Destination for audit logs | file, syslog, http |
Example: Enabling Audit Logging in GitLab CI/CD
variables:
GITLAB_AUDIT_LOG_ENABLED: "true"
GITLAB_AUDIT_LOG_LEVEL: "info"
GITLAB_AUDIT_LOG_OUTPUT: "file"
script:
- echo "Audit logging enabled"
Note: Audit logs capture actions like job creation, pipeline triggers, and environment changes. Ensure logs are stored securely and rotated regularly.
Warning: Enabling audit logging can increase disk I/O and storage usage. Monitor system performance after enabling.
10. Using Security Policies
| Policy Type | Description | Implementation | Notes |
|---|---|---|---|
| Code Review Policy | Enforces mandatory code reviews before merging to main branches | |
Ensures peer review for all changes |
| Vulnerability Scanning | Automatically scans code for known vulnerabilities | |
Integrates with GitLab's built-in SAST scanner |
| Dependency Scanning | Scans dependencies for security issues | |
Scans for vulnerabilities in deployed applications |
Example: Enforcing Code Review Policy
workflow:
rules:
- if: $CI_MERGE_REQUEST_ID
changes:
- src/**
actions:
- require_code_review
Note: Code review policies can be enforced at the project or group level using GitLab's built-in policy management features.