Skip to content

Secrets Management for Container Scanning

This page outlines best practices for managing secrets when integrating container scanning into CI/CD pipelines.

Overview

Container scanning often requires access to Kubernetes clusters, container registries, and other sensitive resources. Proper secrets management is critical for:

  • Protecting cluster access credentials
  • Securing container registry authentication
  • Managing service account tokens securely
  • Implementing the principle of least privilege
  • Preventing secrets from being exposed in logs or outputs

Types of Secrets

When integrating container scanning, you'll typically need to manage these types of secrets:

  1. Kubernetes Authentication:
  2. Kubeconfig files
  3. Service account tokens
  4. API server certificates

  5. Container Registry Authentication:

  6. Registry usernames and passwords
  7. Registry access tokens
  8. Docker config.json files

  9. Scanning Credentials:

  10. Profile repository access tokens
  11. Compliance API keys
  12. Report repository credentials

  13. Temporary Credentials:

  14. Short-lived service account tokens
  15. Limited-scope access tokens
  16. Ephemeral certificates

Best Practices

General Security Practices

  • Never store secrets in code repositories
  • Limit secret access to only the jobs and stages that require them
  • Use environment variables to inject secrets into pipelines
  • Implement secret rotation policies
  • Always validate inputs to prevent injection attacks
  • Audit secret usage regularly

CI/CD Platform-Specific Practices

GitHub Actions

  1. Use GitHub Secrets for sensitive data:
1
2
3
4
- name: Setup Kubernetes access
  run: |
    echo "${{ secrets.KUBE_CONFIG }}" | base64 -d > kubeconfig.yaml
    export KUBECONFIG=kubeconfig.yaml
  1. Limit secret exposure with environment isolation:
1
2
3
4
5
jobs:
  scan:
    environment: production
    env:
      SCANNER_TOKEN: ${{ secrets.SCANNER_TOKEN }}
  1. Use OIDC for cloud provider authentication:
1
2
3
4
5
- name: Configure AWS credentials
  uses: aws-actions/configure-aws-credentials@v1
  with:
    role-to-assume: ${{ secrets.AWS_ROLE }}
    aws-region: us-east-1

GitLab CI/CD

  1. Use GitLab CI/CD Variables:
1
2
3
before_script:
  - echo "$KUBE_CONFIG" | base64 -d > kubeconfig.yaml
  - export KUBECONFIG=kubeconfig.yaml
  1. Use masked variables to prevent accidental exposure:
# In GitLab CI/CD settings, mark variables as "Masked"
  1. Limit variable scope by environment:
# In GitLab CI/CD settings, set variables with a specific environment scope
  1. Use CI/CD file variables for per-pipeline secrets:
variables:
  SCANNER_TOKEN: $CI_JOB_TOKEN

Kubernetes Secret Management

  1. Create time-limited service accounts:
# Create short-lived token (Kubernetes 1.22+)
TOKEN=$(kubectl create token scanner-sa --duration=15m)
  1. Create least-privilege RBAC roles:
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: scanner-role
rules:
- apiGroups: [""]
  resources: ["pods"]
  verbs: ["get", "list"]
- apiGroups: [""]
  resources: ["pods/exec"]
  verbs: ["create"]
  resourceNames: ["specific-pod-name"]
  1. Use namespaced resources to limit access scope:
1
2
3
4
5
# Create rolebinding in specific namespace
kubectl create rolebinding scanner-binding \
  --role=scanner-role \
  --serviceaccount=default:scanner-sa \
  -n target-namespace
  1. Include Job/Pipeline IDs in resource names for traceability:
1
2
3
4
5
metadata:
  name: scanner-sa-${CI_JOB_ID}
  annotations:
    ci-job: "${CI_JOB_ID}"
    pipeline: "${CI_PIPELINE_ID}"

Temporary Credentials Workflow

Here's a recommended workflow for managing temporary credentials in scanning pipelines:

  1. Create a temporary service account:
kubectl create serviceaccount scanner-sa-${CI_JOB_ID} -n ${NAMESPACE}
  1. Create a restricted role with specific pod access:
cat <<EOF | kubectl apply -f -
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: scanner-role-${CI_JOB_ID}
  namespace: ${NAMESPACE}
rules:
- apiGroups: [""]
  resources: ["pods"]
  verbs: ["get", "list"]
- apiGroups: [""]
  resources: ["pods/exec"]
  verbs: ["create"]
  resourceNames: ["${POD_NAME}"]
EOF
  1. Bind the role to the service account:
1
2
3
4
kubectl create rolebinding scanner-binding-${CI_JOB_ID} \
  --role=scanner-role-${CI_JOB_ID} \
  --serviceaccount=${NAMESPACE}:scanner-sa-${CI_JOB_ID} \
  -n ${NAMESPACE}
  1. Generate a short-lived token:
TOKEN=$(kubectl create token scanner-sa-${CI_JOB_ID} -n ${NAMESPACE} --duration=15m)
  1. Use the token for scanning:
# Create scanner kubeconfig
cat > scanner-kubeconfig.yaml <<EOF
apiVersion: v1
kind: Config
clusters:
- cluster:
    server: ${KUBE_SERVER}
    certificate-authority-data: ${KUBE_CA_DATA}
  name: k8s-cluster
contexts:
- context:
    cluster: k8s-cluster
    user: scanner-user
    namespace: ${NAMESPACE}
  name: scanner-context
current-context: scanner-context
users:
- name: scanner-user
  user:
    token: ${TOKEN}
EOF
  1. Clean up after scanning:
1
2
3
kubectl delete rolebinding scanner-binding-${CI_JOB_ID} -n ${NAMESPACE}
kubectl delete role scanner-role-${CI_JOB_ID} -n ${NAMESPACE}
kubectl delete serviceaccount scanner-sa-${CI_JOB_ID} -n ${NAMESPACE}

Managing Secrets in Container Registry Authentication

When scanning containers that require pulling from private registries:

  1. Use docker config secrets:
1
2
3
4
5
6
7
apiVersion: v1
kind: Secret
metadata:
  name: registry-credentials
type: kubernetes.io/dockerconfigjson
data:
  .dockerconfigjson: <base64-encoded-docker-config>
  1. Reference secrets in pod definition:
1
2
3
spec:
  imagePullSecrets:
  - name: registry-credentials
  1. Securely inject credentials in CI/CD:
before_script:
  - echo "$REGISTRY_PASSWORD" | docker login -u "$REGISTRY_USERNAME" --password-stdin $REGISTRY_URL

Sanitizing Output and Avoiding Secret Leakage

To prevent accidental exposure of secrets in logs and reports:

  1. Sanitize scan output:
# Replace sensitive data in outputs
sed -i 's/sensitive-data-pattern/[REDACTED]/g' scan-results.json
  1. Use log filters in CI/CD pipelines:
1
2
3
4
# GitLab CI/CD filter pattern
variables:
  SECURE_LOG_LEVEL: info
  CI_DEBUG_TRACE: "false"
  1. Set token variables as secrets in your CI/CD system:
  2. GitHub Actions: ${{ secrets.TOKEN_NAME }}
  3. GitLab CI/CD: Masked variables

  4. Avoid using debug output for workflows with secrets:

1
2
3
# Only enable debug on non-sensitive stages
debug:
  if: github.event.inputs.enable_debug == 'true'
  1. Use credential helpers when possible:
  2. AWS: aws-cli credential helper
  3. GCP: gcloud credential helper
  4. Azure: az credential helper

Automated Secret Rotation

For long-running scanning pipelines or environments, implement automated secret rotation:

  1. Create periodic jobs to rotate credentials:
1
2
3
4
5
6
7
# GitLab CI/CD schedule for rotating credentials
secret-rotation:
  stage: maintenance
  rules:
    - if: $CI_PIPELINE_SOURCE == "schedule"
  script:
    - ./rotate-credentials.sh
  1. Store rotation timestamps:
# Record when credentials were last rotated
echo "Last rotated: $(date)" > rotation-timestamp.txt
  1. Implement graceful credential handover:
1
2
3
4
# Create new credentials before invalidating old ones
NEW_TOKEN=$(create-new-token)
update-token-consumers "$NEW_TOKEN"
invalidate-old-token