Documentation

Credential Management

Security best practices for managing credentials, tokens, and service accounts in evidence collection.

Principles

1. Least Privilege

Grant minimum permissions required:

  • ✅ Read-only scopes only
  • ✅ Specific resource access
  • ✅ Time-limited credentials (when possible)
  • ❌ Never grant write permissions
  • ❌ Never use admin/root credentials

Example (GitHub):

# ✅ GOOD: Read-only scopes
scopes:
  - repo:read
  - read:org

# ❌ BAD: Write access
scopes:
  - repo  # Includes write access
  - admin:org  # Full admin access

2. Never Hardcode Credentials

Always use environment variables:

✅ GOOD:

sources:
  github:
    token_env: GITHUB_TOKEN
  google_workspace:
    credentials_env: GOOGLE_APPLICATION_CREDENTIALS

❌ BAD:

sources:
  github:
    token: ghp_hardcoded_token_abc123  # Never do this!

3. Rotate Regularly

Rotation schedule:

  • Production credentials: Every 90 days
  • Development credentials: Every 180 days
  • After security incident: Immediately
  • After team member departure: Within 24 hours

4. Separate by Environment

Use different credentials per environment:

  • Development
  • Staging
  • Production

Never reuse credentials across environments.


GitHub Token Management

Creating Tokens

Step 1: Generate Personal Access Token

  1. Go to GitHub → Settings → Developer settings → Personal access tokens
  2. Click "Generate new token (classic)"
  3. Name: evidence-collection-production
  4. Expiration: 90 days (with calendar reminder)
  5. Select scopes:
    • public_repo (for public repos)
    • repo → Select only if private repos needed
    • read:org
  6. Click "Generate token"
  7. Copy immediately (shown only once)

Token format:

ghp_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

Storing Tokens

Local development:

# Add to shell profile
echo 'export GITHUB_TOKEN=ghp_your_token_here' >> ~/.zshrc
source ~/.zshrc

# Verify
echo $GITHUB_TOKEN

CI/CD (GitHub Actions):

name: Evidence Collection
on:
  schedule:
    - cron: '0 0 1 * *'

jobs:
  collect:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Collect Evidence
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: evidence collect

Add secret in GitHub:

  1. Repository → Settings → Secrets and variables → Actions
  2. Click "New repository secret"
  3. Name: GITHUB_TOKEN
  4. Value: ghp_your_token_here
  5. Click "Add secret"

Rotating Tokens

Rotation procedure:

  1. Create new token (as above)
  2. Update environment variables:
    export GITHUB_TOKEN=ghp_new_token_here
  3. Test collection:
    evidence collect
  4. Update CI/CD secrets
  5. Wait 7 days (grace period)
  6. Revoke old token:
    • GitHub → Settings → Personal access tokens
    • Find old token
    • Click "Delete"

Calendar reminders:

  • Set reminder 7 days before expiration
  • Set reminder on rotation day
  • Document rotation in team calendar

AWS Credential Management

IAM User for Evidence Collection

Create dedicated IAM user:

# AWS CLI
aws iam create-user --user-name evidence-collector

# Attach read-only policy
aws iam put-user-policy \
  --user-name evidence-collector \
  --policy-name EvidenceCollectionPolicy \
  --policy-document file://evidence-policy.json

Policy (evidence-policy.json):

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "EvidenceCollection",
      "Effect": "Allow",
      "Action": [
        "iam:GetAccountPasswordPolicy",
        "cloudtrail:DescribeTrails",
        "cloudtrail:GetTrailStatus",
        "logs:DescribeLogGroups"
      ],
      "Resource": "*"
    }
  ]
}

Access Key Management

Create access key:

aws iam create-access-key --user-name evidence-collector

Output:

{
  "AccessKey": {
    "UserName": "evidence-collector",
    "AccessKeyId": "AKIA...",
    "SecretAccessKey": "...",
    "Status": "Active",
    "CreateDate": "2026-01-09T12:00:00Z"
  }
}

Store securely:

# Local development
export AWS_ACCESS_KEY_ID=AKIA...
export AWS_SECRET_ACCESS_KEY=...
export AWS_REGION=us-east-1

# Add to shell profile
echo 'export AWS_ACCESS_KEY_ID=AKIA...' >> ~/.zshrc
echo 'export AWS_SECRET_ACCESS_KEY=...' >> ~/.zshrc
echo 'export AWS_REGION=us-east-1' >> ~/.zshrc

Rotating Access Keys

AWS supports 2 active keys per user - use for zero-downtime rotation:

Step 1: Create second key

aws iam create-access-key --user-name evidence-collector

Step 2: Update environment variables

export AWS_ACCESS_KEY_ID=AKIA_new_key
export AWS_SECRET_ACCESS_KEY=new_secret

Step 3: Test collection

evidence collect

Step 4: Deactivate old key

aws iam update-access-key \
  --user-name evidence-collector \
  --access-key-id AKIA_old_key \
  --status Inactive

Step 5: Wait 7 days, then delete

aws iam delete-access-key \
  --user-name evidence-collector \
  --access-key-id AKIA_old_key

Using Assume Role

More secure than static credentials:

Setup:

  1. Create IAM role with evidence collection policy
  2. Configure trust relationship
  3. Use temporary credentials

Trust policy:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "AWS": "arn:aws:iam::SOURCE_ACCOUNT:user/evidence-collector"
      },
      "Action": "sts:AssumeRole",
      "Condition": {
        "StringEquals": {
          "sts:ExternalId": "evidence-sdk-production-2026"
        }
      }
    }
  ]
}

Configuration:

sources:
  aws:
    mode: assume_role
    role_arn: arn:aws:iam::123456789012:role/evidence-collector
    external_id: evidence-sdk-production-2026
    region: us-east-1

Benefits:

  • Temporary credentials (expire after 1 hour)
  • External ID prevents "confused deputy" attacks
  • Better audit trail
  • Cross-account access support

Google Workspace Service Accounts

Creating Service Account

Step 1: Create in Google Cloud Console

  1. Go to Google Cloud Console
  2. Select project (or create new)
  3. IAM & Admin → Service Accounts
  4. Click "Create Service Account"
  5. Name: evidence-collector
  6. Description: "Read-only evidence collection for SOC 2 compliance"
  7. Click "Create and Continue"
  8. Skip role assignment (not needed for domain-wide delegation)
  9. Click "Done"

Generating Key

Step 2: Create JSON key

  1. Click on service account
  2. Go to "Keys" tab
  3. Click "Add Key" → "Create new key"
  4. Select "JSON"
  5. Click "Create"
  6. Download and save securely

Key file format:

{
  "type": "service_account",
  "project_id": "evidence-project",
  "private_key_id": "abc123...",
  "private_key": "-----BEGIN PRIVATE KEY-----\n...",
  "client_email": "evidence-collector@evidence-project.iam.gserviceaccount.com",
  "client_id": "123456789..."
}

Securing Service Account Keys

File permissions:

# Set restrictive permissions
chmod 600 /path/to/service-account.json
chown $USER:$USER /path/to/service-account.json

# Verify
ls -l /path/to/service-account.json
# Should show: -rw------- 1 user user ...

Storage location:

# Development
~/.evidence/google-service-account.json

# Production
/etc/evidence/keys/google-service-account.json

# CI/CD - use secrets management, not files

Environment variable:

export GOOGLE_APPLICATION_CREDENTIALS=/path/to/service-account.json

Rotating Service Account Keys

Google allows up to 10 keys per service account:

Step 1: Create new key

  1. Go to service account → Keys
  2. Add Key → Create new key → JSON
  3. Download new key

Step 2: Update environment variable

export GOOGLE_APPLICATION_CREDENTIALS=/path/to/new-service-account.json

Step 3: Test collection

evidence collect

Step 4: Delete old key

  1. Go to service account → Keys
  2. Find old key (by key ID)
  3. Click ⋮ → Delete
  4. Confirm deletion

Best practice: Rotate annually, document in team calendar


Secrets Management Systems

HashiCorp Vault

Store credentials in Vault:

# Write GitHub token
vault kv put secret/evidence/github token=ghp_your_token_here

# Write AWS credentials
vault kv put secret/evidence/aws \
  access_key_id=AKIA... \
  secret_access_key=...

# Write Google service account
vault kv put secret/evidence/google \
  service_account=@/path/to/service-account.json

Retrieve at collection time:

# Shell script
export GITHUB_TOKEN=$(vault kv get -field=token secret/evidence/github)
export AWS_ACCESS_KEY_ID=$(vault kv get -field=access_key_id secret/evidence/aws)
export AWS_SECRET_ACCESS_KEY=$(vault kv get -field=secret_access_key secret/evidence/aws)

evidence collect

AWS Secrets Manager

Store credentials:

# GitHub token
aws secretsmanager create-secret \
  --name evidence/github-token \
  --secret-string ghp_your_token_here

# Google service account
aws secretsmanager create-secret \
  --name evidence/google-service-account \
  --secret-string file:///path/to/service-account.json

Retrieve in CI/CD:

# GitHub Actions with AWS Secrets Manager
- name: Retrieve secrets
  run: |
    export GITHUB_TOKEN=$(aws secretsmanager get-secret-value \
      --secret-id evidence/github-token \
      --query SecretString \
      --output text)

    aws secretsmanager get-secret-value \
      --secret-id evidence/google-service-account \
      --query SecretString \
      --output text > /tmp/google-sa.json

    export GOOGLE_APPLICATION_CREDENTIALS=/tmp/google-sa.json

- name: Collect evidence
  run: evidence collect

1Password CLI

Store credentials:

# Create vault
op vault create Evidence

# Store GitHub token
op item create \
  --category=password \
  --title="GitHub Evidence Token" \
  --vault=Evidence \
  password=ghp_your_token_here

# Store AWS credentials
op item create \
  --category=login \
  --title="AWS Evidence Collector" \
  --vault=Evidence \
  username=AKIA... \
  password=...

Retrieve for collection:

# Load credentials into environment
eval $(op signin)

export GITHUB_TOKEN=$(op item get "GitHub Evidence Token" --fields password)
export AWS_ACCESS_KEY_ID=$(op item get "AWS Evidence Collector" --fields username)
export AWS_SECRET_ACCESS_KEY=$(op item get "AWS Evidence Collector" --fields password)

evidence collect

CI/CD Best Practices

GitHub Actions Secrets

Organization-level secrets:

  • Use for shared credentials across repositories
  • Settings → Organization → Secrets and variables → Actions

Repository-level secrets:

  • Use for repository-specific credentials
  • Settings → Secrets and variables → Actions

Environment-specific secrets:

  • Use for production vs staging credentials
  • Settings → Environments → Create environment → Add secrets

Example workflow:

name: Evidence Collection
on:
  schedule:
    - cron: '0 0 1 * *'

jobs:
  collect-production:
    runs-on: ubuntu-latest
    environment: production
    steps:
      - uses: actions/checkout@v4

      - name: Collect Evidence
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
          AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          GOOGLE_APPLICATION_CREDENTIALS: /tmp/google-sa.json
        run: |
          echo '${{ secrets.GOOGLE_SERVICE_ACCOUNT_JSON }}' > /tmp/google-sa.json
          chmod 600 /tmp/google-sa.json
          evidence collect
          rm /tmp/google-sa.json

GitLab CI Variables

Project variables:

  • Settings → CI/CD → Variables

Group variables:

  • Group → Settings → CI/CD → Variables

Protected variables:

  • Only available in protected branches/tags
  • Use for production credentials

Masked variables:

  • Values hidden in job logs
  • Always enable for credentials

Example:

collect-evidence:
  stage: collect
  only:
    - schedules
  variables:
    GIT_STRATEGY: clone
  script:
    - echo "$GOOGLE_SERVICE_ACCOUNT_JSON" > /tmp/google-sa.json
    - export GOOGLE_APPLICATION_CREDENTIALS=/tmp/google-sa.json
    - evidence collect
  after_script:
    - rm -f /tmp/google-sa.json

Security Checklist

Before Production

  • All credentials use least privilege principle
  • No credentials hardcoded in config files
  • All credentials stored in environment variables or secrets manager
  • Credential rotation schedule documented
  • Calendar reminders set for rotation
  • Team has access to credential vault
  • Backup access (break-glass) documented
  • All service accounts have descriptive names
  • Service account usage is monitored
  • Credentials are environment-specific (dev/staging/prod)

Monthly Review

  • Review active credentials
  • Verify no unnecessary permissions
  • Check for unused credentials (revoke)
  • Review access logs for anomalies
  • Verify team members still need access
  • Update credential inventory documentation

After Security Incident

  • Rotate all affected credentials immediately
  • Review access logs for misuse
  • Document incident and response
  • Update security procedures if needed
  • Notify compliance team
  • Schedule security audit

Common Pitfalls

❌ Committing Credentials to Git

Never commit:

  • .env files with credentials
  • Service account JSON files
  • SSH private keys
  • API tokens in config files

If committed:

# Remove from history (use carefully!)
git filter-branch --force --index-filter \
  'git rm --cached --ignore-unmatch .env' \
  --prune-empty --tag-name-filter cat -- --all

# Force push (only if safe to do so)
git push origin --force --all

# Rotate all exposed credentials immediately

Prevention:

# .gitignore
.env
.env.*
*.pem
*.key
*-key.json
service-account.json

❌ Sharing Credentials Between Environments

Problem:

# ❌ BAD: Same token for dev and prod
export GITHUB_TOKEN=ghp_shared_token

Solution:

# ✅ GOOD: Separate tokens
# Development
export GITHUB_TOKEN=ghp_dev_token

# Production
export GITHUB_TOKEN=ghp_prod_token

❌ Using Root/Admin Credentials

Problem:

  • AWS root account credentials
  • GitHub organization owner token
  • Google Workspace super admin service account

Solution:

  • Create dedicated user/service account
  • Grant only required permissions
  • Disable/delete when not in use

❌ No Rotation Plan

Problem:

  • Credentials never rotated
  • No expiration dates
  • No calendar reminders

Solution:

Credential Rotation Calendar:
- GitHub tokens: Every 90 days (1st of Jan, Apr, Jul, Oct)
- AWS access keys: Every 90 days (15th of Jan, Apr, Jul, Oct)
- Google service accounts: Every 365 days (January 1st)

See Also