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 access2. 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
- Go to GitHub → Settings → Developer settings → Personal access tokens
- Click "Generate new token (classic)"
- Name:
evidence-collection-production - Expiration: 90 days (with calendar reminder)
- Select scopes:
- ✅
public_repo(for public repos) - ✅
repo→ Select only if private repos needed - ✅
read:org
- ✅
- Click "Generate token"
- Copy immediately (shown only once)
Token format:
ghp_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxStoring Tokens
Local development:
# Add to shell profile
echo 'export GITHUB_TOKEN=ghp_your_token_here' >> ~/.zshrc
source ~/.zshrc
# Verify
echo $GITHUB_TOKENCI/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 collectAdd secret in GitHub:
- Repository → Settings → Secrets and variables → Actions
- Click "New repository secret"
- Name:
GITHUB_TOKEN - Value:
ghp_your_token_here - Click "Add secret"
Rotating Tokens
Rotation procedure:
- Create new token (as above)
- Update environment variables:
export GITHUB_TOKEN=ghp_new_token_here - Test collection:
evidence collect - Update CI/CD secrets
- Wait 7 days (grace period)
- 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.jsonPolicy (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-collectorOutput:
{
"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' >> ~/.zshrcRotating 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-collectorStep 2: Update environment variables
export AWS_ACCESS_KEY_ID=AKIA_new_key
export AWS_SECRET_ACCESS_KEY=new_secretStep 3: Test collection
evidence collectStep 4: Deactivate old key
aws iam update-access-key \
--user-name evidence-collector \
--access-key-id AKIA_old_key \
--status InactiveStep 5: Wait 7 days, then delete
aws iam delete-access-key \
--user-name evidence-collector \
--access-key-id AKIA_old_keyUsing Assume Role
More secure than static credentials:
Setup:
- Create IAM role with evidence collection policy
- Configure trust relationship
- 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-1Benefits:
- 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
- Go to Google Cloud Console
- Select project (or create new)
- IAM & Admin → Service Accounts
- Click "Create Service Account"
- Name:
evidence-collector - Description: "Read-only evidence collection for SOC 2 compliance"
- Click "Create and Continue"
- Skip role assignment (not needed for domain-wide delegation)
- Click "Done"
Generating Key
Step 2: Create JSON key
- Click on service account
- Go to "Keys" tab
- Click "Add Key" → "Create new key"
- Select "JSON"
- Click "Create"
- 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 filesEnvironment variable:
export GOOGLE_APPLICATION_CREDENTIALS=/path/to/service-account.jsonRotating Service Account Keys
Google allows up to 10 keys per service account:
Step 1: Create new key
- Go to service account → Keys
- Add Key → Create new key → JSON
- Download new key
Step 2: Update environment variable
export GOOGLE_APPLICATION_CREDENTIALS=/path/to/new-service-account.jsonStep 3: Test collection
evidence collectStep 4: Delete old key
- Go to service account → Keys
- Find old key (by key ID)
- Click ⋮ → Delete
- 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.jsonRetrieve 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 collectAWS 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.jsonRetrieve 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 collect1Password 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 collectCI/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.jsonGitLab 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.jsonSecurity 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:
.envfiles 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 immediatelyPrevention:
# .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_tokenSolution:
# ✅ 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
- GitHub Connector - GitHub token requirements
- AWS Connector - AWS IAM setup
- Google Workspace Connector - Service account setup
- Signing Keys - Ed25519 key management