Skip to content

Latest commit

 

History

History
568 lines (433 loc) · 14.2 KB

File metadata and controls

568 lines (433 loc) · 14.2 KB

Setting Up Signing Keys

Production key management and certificate authorities

Audience: Infrastructure Engineers, System Administrators Prerequisites: Completed Quick Start Guide Time: ~30 minutes

What You'll Learn

By the end of this tutorial, you will:

  • Understand key management best practices
  • Generate production-grade signing keys
  • Manage keys across multiple environments
  • Work with certificate authorities (CAs)
  • Implement key rotation strategies
  • Secure keys in production systems

Key Management Overview

Development vs. Production Keys

Aspect Development Production
Key Type RSA 2048-bit RSA 4096-bit or ECDSA P-384
Certificate Self-signed CA-signed
Storage Developer machine Secure key store
Permissions 0600 (user only) 0400 (read-only)
Lifetime 1 year 2-5 years
Rotation Manual Automated
Backup Optional Required

Step 1: Choose Key Algorithm

pytest-jux supports two cryptographic algorithms:

RSA (Recommended for Most Cases)

jux-keygen --output prod_key.pem \
           --type rsa \
           --bits 4096 \
           --cert \
           --subject "CN=pytest-jux-prod,O=YourOrg,C=US" \
           --days-valid 730

Pros:

  • ✅ Widely supported
  • ✅ Well-understood security properties
  • ✅ Compatible with older systems

Cons:

  • ❌ Larger signatures (~512 bytes)
  • ❌ Slower signing (~50ms per report)

Use for: General-purpose production deployments

ECDSA (For Performance-Critical Cases)

jux-keygen --output prod_key.pem \
           --type ecdsa \
           --curve P-384 \
           --cert \
           --subject "CN=pytest-jux-prod,O=YourOrg,C=US" \
           --days-valid 730

Pros:

  • ✅ Smaller signatures (~128 bytes)
  • ✅ Faster signing (~20ms per report)
  • ✅ Modern cryptography

Cons:

  • ❌ Less widely supported
  • ❌ May not work with older verification tools

Use for: High-volume testing, performance-critical pipelines

Step 2: Generate Production Keys

For Single Environment

# Create key directory
mkdir -p ~/.jux/production
chmod 700 ~/.jux/production

# Generate production key with certificate
jux-keygen \
  --output ~/.jux/production/signing_key.pem \
  --type rsa \
  --bits 4096 \
  --cert \
  --subject "CN=pytest-jux-prod,O=YourCompany,OU=Engineering,C=US" \
  --days-valid 730

# Verify permissions
ls -la ~/.jux/production/
# -rw-------  1 user  staff  3272 Oct 17 14:30 signing_key.pem
# -rw-r--r--  1 user  staff  1234 Oct 17 14:30 signing_key.crt

For Multiple Environments

Generate separate keys for each environment:

# Development
jux-keygen --output ~/.jux/dev/signing_key.pem \
           --type rsa --bits 2048 --cert \
           --subject "CN=pytest-jux-dev,O=YourCompany,OU=Engineering" \
           --days-valid 365

# Staging
jux-keygen --output ~/.jux/staging/signing_key.pem \
           --type rsa --bits 4096 --cert \
           --subject "CN=pytest-jux-staging,O=YourCompany,OU=Engineering" \
           --days-valid 730

# Production
jux-keygen --output ~/.jux/production/signing_key.pem \
           --type rsa --bits 4096 --cert \
           --subject "CN=pytest-jux-prod,O=YourCompany,OU=Engineering" \
           --days-valid 730

Why separate keys?

  • Limits blast radius if one key is compromised
  • Enables environment-specific trust policies
  • Simplifies key rotation per environment

Step 3: Certificate Authority Integration

Self-signed certificates work for testing, but production should use CA-signed certificates.

Option A: Internal Certificate Authority

If your organization has an internal CA:

# 1. Generate Certificate Signing Request (CSR)
openssl req -new -key ~/.jux/production/signing_key.pem \
  -out pytest-jux-prod.csr \
  -subj "/CN=pytest-jux-prod/O=YourCompany/OU=Engineering/C=US"

# 2. Submit CSR to your CA
# (Process varies by organization)

# 3. Receive signed certificate
# Save as ~/.jux/production/signing_key.crt

# 4. Verify certificate chain
openssl verify -CAfile /path/to/ca-bundle.crt \
  ~/.jux/production/signing_key.crt

Option B: Public Certificate Authority

For public infrastructure (less common for test signing):

# Use Let's Encrypt, DigiCert, etc.
# Follow CA-specific enrollment process

# Note: pytest-jux signatures are typically for internal use,
# so public CAs are rarely necessary

Option C: Self-Signed (Development Only)

# Already generated by jux-keygen --cert
# Fine for development, NOT for production

Step 4: Distribute Public Certificates

Verifiers need the public certificate (NOT the private key).

Extract Public Certificate

# Certificate is already in signing_key.crt
cp ~/.jux/production/signing_key.crt /path/to/distribution/

# Or extract from private key if needed
openssl rsa -in ~/.jux/production/signing_key.pem \
            -pubout -out public_key.pem

Distribute to Verifiers

# Copy to verification systems
scp ~/.jux/production/signing_key.crt \
    verifier-host:/etc/jux/trusted-certs/

# Or publish to shared location
aws s3 cp ~/.jux/production/signing_key.crt \
    s3://company-certs/jux/prod/signing_key.crt

Important: Never distribute the private key (.pem file)!

Step 5: Secure Key Storage

File System Permissions

# Private key: owner read/write only
chmod 600 ~/.jux/production/signing_key.pem

# Certificate: world-readable (public key)
chmod 644 ~/.jux/production/signing_key.crt

# Directory: owner access only
chmod 700 ~/.jux/production

CI/CD Secrets Management

GitHub Actions:

# Store private key as secret
# Settings → Secrets → JUX_SIGNING_KEY

- name: Run tests with signing
  run: |
    echo "${{ secrets.JUX_SIGNING_KEY }}" > /tmp/signing_key.pem
    chmod 600 /tmp/signing_key.pem
    pytest --junit-xml=report.xml
  env:
    JUX_KEY_PATH: /tmp/signing_key.pem

GitLab CI:

# Settings → CI/CD → Variables → JUX_SIGNING_KEY

test:
  script:
    - echo "$JUX_SIGNING_KEY" > /tmp/signing_key.pem
    - chmod 600 /tmp/signing_key.pem
    - pytest --junit-xml=report.xml
  variables:
    JUX_KEY_PATH: /tmp/signing_key.pem

Jenkins:

// Use Credentials Plugin
withCredentials([file(credentialsId: 'jux-signing-key', variable: 'KEY_FILE')]) {
    sh '''
        chmod 600 $KEY_FILE
        pytest --junit-xml=report.xml
    '''
}

Hardware Security Modules (HSM)

Note: pytest-jux does not currently support HSM integration. This is a future consideration (see CLAUDE.md).

For now, use file-based keys with strict access controls.

Step 6: Key Rotation Strategy

Regular key rotation limits exposure from compromised keys.

Rotation Schedule

Environment Rotation Frequency Overlap Period
Development Annually 30 days
Staging Every 2 years 60 days
Production Every 2-3 years 90 days

Rotation Process

# 1. Generate new key (keep old key active)
jux-keygen --output ~/.jux/production/signing_key_v2.pem \
           --type rsa --bits 4096 --cert \
           --subject "CN=pytest-jux-prod-v2,O=YourCompany" \
           --days-valid 730

# 2. Deploy new key to signing systems
ansible-playbook deploy-signing-key.yml \
  --extra-vars "key_version=v2"

# 3. Overlap period (both keys valid)
# Old key: ~/.jux/production/signing_key.pem (sign)
# New key: ~/.jux/production/signing_key_v2.pem (ready)

# 4. Switch to new key
mv ~/.jux/production/signing_key.pem \
   ~/.jux/production/signing_key_v1_archived.pem
mv ~/.jux/production/signing_key_v2.pem \
   ~/.jux/production/signing_key.pem

# 5. Update verification systems with new certificate
# Deploy signing_key.crt to all verifiers

# 6. After overlap period, decommission old key
# Keep old certificate for historical verification
mv ~/.jux/production/signing_key_v1_archived.pem \
   /secure/archive/signing_key_v1_$(date +%Y%m%d).pem

Automated Rotation

Using Let's Encrypt-style automation:

#!/bin/bash
# rotate-jux-key.sh

CURRENT_KEY=~/.jux/production/signing_key.pem
NEW_KEY=~/.jux/production/signing_key_new.pem

# Generate new key
jux-keygen --output $NEW_KEY --type rsa --bits 4096 --cert \
  --subject "CN=pytest-jux-prod,O=YourCompany" \
  --days-valid 730

# Test new key
pytest --junit-xml=/tmp/test.xml
jux-sign /tmp/test.xml --key $NEW_KEY --output /tmp/test-signed.xml
jux-verify /tmp/test-signed.xml --cert ${NEW_KEY%.pem}.crt

# If test passes, rotate
if [ $? -eq 0 ]; then
  mv $CURRENT_KEY ${CURRENT_KEY%.pem}_$(date +%Y%m%d).pem
  mv $NEW_KEY $CURRENT_KEY
  echo "Key rotated successfully"
else
  echo "Key rotation failed"
  exit 1
fi

Step 7: Key Backup and Recovery

Backup Strategy

# Create encrypted backup
tar czf jux-keys-backup-$(date +%Y%m%d).tar.gz ~/.jux/
gpg --symmetric --cipher-algo AES256 jux-keys-backup-*.tar.gz

# Store in secure location
aws s3 cp jux-keys-backup-*.tar.gz.gpg \
  s3://company-backups/jux-keys/ \
  --storage-class GLACIER

# Clean up local backup
rm jux-keys-backup-*.tar.gz jux-keys-backup-*.tar.gz.gpg

Recovery Process

# Retrieve backup
aws s3 cp s3://company-backups/jux-keys/jux-keys-backup-20251017.tar.gz.gpg .

# Decrypt
gpg --decrypt jux-keys-backup-20251017.tar.gz.gpg > jux-keys-backup.tar.gz

# Extract
tar xzf jux-keys-backup.tar.gz

# Restore keys
cp -r .jux/ ~/.jux/
chmod 700 ~/.jux/
chmod 600 ~/.jux/**/*.pem

Step 8: Multi-Environment Configuration

Environment-Specific Keys

Development:

# ~/.config/jux/config
[jux]
key_path = ~/.jux/dev/signing_key.pem
cert_path = ~/.jux/dev/signing_key.crt

Staging:

# Environment variables (CI/CD)
export JUX_KEY_PATH=/etc/jux/staging/signing_key.pem
export JUX_CERT_PATH=/etc/jux/staging/signing_key.crt

Production:

# Environment variables (CI/CD)
export JUX_KEY_PATH=/etc/jux/production/signing_key.pem
export JUX_CERT_PATH=/etc/jux/production/signing_key.crt

Key Distribution with Ansible

# deploy-signing-key.yml
---
- name: Deploy pytest-jux signing keys
  hosts: ci_runners
  vars:
    key_source: "{{ lookup('env', 'JUX_KEY_VAULT_PATH') }}"
    key_dest: /etc/jux/{{ environment }}/signing_key.pem
    cert_dest: /etc/jux/{{ environment }}/signing_key.crt

  tasks:
    - name: Create key directory
      file:
        path: /etc/jux/{{ environment }}
        state: directory
        mode: '0700'
        owner: jenkins
        group: jenkins

    - name: Copy private key
      copy:
        src: "{{ key_source }}/signing_key.pem"
        dest: "{{ key_dest }}"
        mode: '0600'
        owner: jenkins
        group: jenkins

    - name: Copy certificate
      copy:
        src: "{{ key_source }}/signing_key.crt"
        dest: "{{ cert_dest }}"
        mode: '0644'
        owner: jenkins
        group: jenkins

Security Best Practices

✅ DO

  • Use strong keys: RSA 4096-bit or ECDSA P-384 minimum for production
  • Restrict permissions: 0600 for private keys, 0700 for directories
  • Rotate regularly: Every 2-3 years for production keys
  • Backup encrypted: Use GPG or similar for backups
  • Separate environments: Different keys for dev/staging/prod
  • Audit access: Log who accesses private keys
  • Use CI/CD secrets: Never commit keys to git

❌ DON'T

  • Commit to git: Never check in private keys
  • Share keys: Each environment should have separate keys
  • Use weak keys: RSA 1024 is too weak
  • Skip backups: Lost keys = can't verify old reports
  • Use self-signed in prod: Get CA-signed certificates
  • Ignore permissions: World-readable private keys are compromised
  • Forget rotation: Old keys increase risk over time

Troubleshooting

"Key file not found"

Problem:

ERROR: Key file not found: ~/.jux/signing_key.pem

Solution:

# Verify key exists
ls -la ~/.jux/signing_key.pem

# Check JUX_KEY_PATH environment variable
echo $JUX_KEY_PATH

# Verify configuration
jux-config dump | grep key_path

"Invalid private key format"

Problem:

ERROR: Invalid private key format

Solution:

# Verify key is PEM format
head -1 ~/.jux/signing_key.pem
# Should show: -----BEGIN PRIVATE KEY----- or -----BEGIN RSA PRIVATE KEY-----

# Convert if needed (rarely necessary)
openssl rsa -in old_key.pem -out new_key.pem

"Permission denied"

Problem:

ERROR: Permission denied reading key file

Solution:

# Fix permissions
chmod 600 ~/.jux/signing_key.pem
chown $USER ~/.jux/signing_key.pem

# Verify
ls -la ~/.jux/signing_key.pem

"Certificate verification failed"

Problem:

ERROR: Certificate does not match key

Solution:

# Verify certificate matches key
openssl x509 -in signing_key.crt -noout -modulus > cert_modulus
openssl rsa -in signing_key.pem -noout -modulus > key_modulus
diff cert_modulus key_modulus

# If different, regenerate certificate
jux-keygen --output signing_key.pem --cert --force

Next Steps

Now that you have production keys set up:

Related Documentation

Key Management Checklist

Use this checklist for production key deployment:

  • Generated production-grade keys (RSA 4096 or ECDSA P-384)
  • Set secure file permissions (0600 for keys, 0700 for directories)
  • Obtained CA-signed certificates (not self-signed)
  • Distributed public certificates to verifiers
  • Configured CI/CD secrets management
  • Implemented key rotation schedule
  • Created encrypted backups
  • Documented key locations and procedures
  • Tested key rotation process
  • Audited key access controls
  • Separated keys per environment
  • Verified keys not committed to git