Key Management
Contents
Key Management#
This guide is for sys admins managing encryption keys for OpenSPP.
Encryption keys protect your data. Lose the keys, lose the data. This guide covers key generation, storage, rotation, and recovery.
Key Types#
OpenSPP uses multiple keys:
Key |
Purpose |
Rotation |
|---|---|---|
Master Key |
Encrypts other keys (envelope encryption) |
Annually |
PII Key |
Encrypts personal data fields |
Annually |
Index Salt |
HMAC for blind indexes |
Never (breaks search) |
Key Providers#
OpenSPP supports 6 key providers:
Provider |
Use Case |
Security |
Complexity |
|---|---|---|---|
Config |
Development/testing |
Low |
Minimal |
Database |
Small production |
Medium |
Low |
Vault |
Enterprise |
High |
Medium |
AWS KMS |
AWS deployments |
High |
Medium |
GCP KMS |
GCP deployments |
High |
Medium |
Azure KV |
Azure deployments |
High |
Medium |
Selecting a Provider#
Set in odoo.conf:
[encryption]
key_provider = config # or: database, vault, aws_kms, gcp_kms, azure_kv
Config Provider (Development)#
Use for: Development, testing, proof-of-concept
Do not use for: Production with real data
Configuration#
# /etc/odoo/odoo.conf
[encryption]
key_provider = config
# Generate keys with: python3 -c "import secrets; print(secrets.token_urlsafe(32))"
encryption_key_master = <base64_encoded_32_byte_key>
encryption_key_pii = <base64_encoded_32_byte_key>
index_salt_default = <base64_encoded_32_byte_salt>
Generating Keys#
# Generate master key
python3 -c "import secrets; print('encryption_key_master =', secrets.token_urlsafe(32))"
# Generate PII key
python3 -c "import secrets; print('encryption_key_pii =', secrets.token_urlsafe(32))"
# Generate index salt
python3 -c "import secrets; print('index_salt_default =', secrets.token_urlsafe(32))"
# Add to odoo.conf
sudo nano /etc/odoo/odoo.conf
Backing Up Config Keys#
# Extract keys from config
grep "encryption_key\|index_salt" /etc/odoo/odoo.conf > /secure/backup/keys_$(date +%Y%m%d).conf
# Encrypt backup
gpg --symmetric --cipher-algo AES256 /secure/backup/keys_*.conf
# Store encrypted backup securely (off-site)
Database Provider (Standard Production)#
Use for: Small to medium production deployments
How it works: Keys stored in database, encrypted with master key from config
Configuration#
# /etc/odoo/odoo.conf
[encryption]
key_provider = database
encryption_key_master = <base64_key_from_config>
Initial Setup#
odoo-bin shell -d openspp_prod
# Create PII data key
master = env['spp.key.provider.database']._get_master_key()
pii_key = env['spp.encryption.key'].generate_key(
key_id='pii',
purpose='Encrypt PII fields',
algorithm='AES-256-GCM',
)
# Create index salt
index_salt = env['spp.encryption.key'].generate_key(
key_id='index_salt_default',
purpose='Blind index salt',
algorithm='HMAC-SHA256',
)
# Verify keys created
keys = env['spp.encryption.key'].search([])
for key in keys:
print(f"{key.key_id} v{key.version}: {key.purpose}")
Checking Keys#
odoo-bin shell -d openspp_prod
# List all keys
keys = env['spp.encryption.key'].search([])
for key in keys:
status = "CURRENT" if key.is_current else "archived"
print(f"{key.key_id} v{key.version} [{status}]: {key.purpose}")
print(f" Created: {key.created_date}")
print(f" Expires: {key.expires_date or 'never'}")
Via database:
SELECT
key_id,
version,
is_current,
algorithm,
purpose,
created_date,
expires_date
FROM spp_encryption_key
ORDER BY key_id, version DESC;
Vault Provider (Enterprise)#
Use for: Enterprise deployments requiring HSM, audit trails, centralized key management
Requires: HashiCorp Vault with Transit secrets engine
Vault Setup#
# Enable Transit secrets engine
vault secrets enable transit
# Create encryption key
vault write -f transit/keys/openspp-pii \
type=aes256-gcm96
# Create policy
vault policy write openspp-encryption - <<EOF
path "transit/encrypt/openspp-pii" {
capabilities = ["update"]
}
path "transit/decrypt/openspp-pii" {
capabilities = ["update"]
}
path "transit/datakey/plaintext/openspp-pii" {
capabilities = ["update"]
}
EOF
# Create AppRole for OpenSPP
vault auth enable approle
vault write auth/approle/role/openspp \
secret_id_ttl=0 \
token_ttl=1h \
token_max_ttl=4h \
policies="openspp-encryption"
# Get credentials
vault read auth/approle/role/openspp/role-id
vault write -f auth/approle/role/openspp/secret-id
OpenSPP Configuration#
# /etc/odoo/odoo.conf
[encryption]
key_provider = vault
vault_url = https://vault.example.com:8200
vault_auth_method = approle
vault_role_id = <role_id_from_vault>
vault_secret_id = <secret_id_from_vault>
vault_transit_mount = transit
Kubernetes Integration#
For Kubernetes deployments:
[encryption]
key_provider = vault
vault_url = http://vault.vault.svc.cluster.local:8200
vault_auth_method = kubernetes
vault_k8s_role = openspp-prod
vault_transit_mount = transit
Kubernetes service account token is auto-discovered.
Testing Vault Connection#
odoo-bin shell -d openspp_prod
provider = env['spp.key.provider.vault']
try:
key = provider.get_data_key('openspp-pii')
print(f"Vault connection OK, got key: {len(key)} bytes")
except Exception as e:
print(f"Vault connection failed: {e}")
AWS KMS Provider#
Use for: AWS deployments (EC2, RDS, EKS)
Requires: AWS KMS key, IAM permissions
AWS Setup#
# Create KMS key
aws kms create-key \
--description "OpenSPP PII encryption" \
--key-usage ENCRYPT_DECRYPT \
--origin AWS_KMS
# Get key ARN
aws kms describe-key --key-id <key-id> --query 'KeyMetadata.Arn'
# Create alias
aws kms create-alias \
--alias-name alias/openspp-pii \
--target-key-id <key-id>
# Grant permissions to EC2 instance role
aws kms create-grant \
--key-id <key-id> \
--grantee-principal arn:aws:iam::account:role/openspp-instance-role \
--operations Encrypt Decrypt GenerateDataKey
OpenSPP Configuration#
# /etc/odoo/odoo.conf
[encryption]
key_provider = aws_kms
aws_region = us-east-1
aws_kms_key_pii = arn:aws:kms:us-east-1:account:key/key-id
# If not using instance role, provide credentials:
aws_access_key_id = <access_key>
aws_secret_access_key = <secret_key>
Testing AWS KMS#
odoo-bin shell -d openspp_prod
provider = env['spp.key.provider.aws_kms']
try:
key = provider.get_data_key('pii')
print(f"AWS KMS OK, got {len(key)} byte key")
except Exception as e:
print(f"AWS KMS failed: {e}")
GCP KMS Provider#
Use for: GCP deployments (GCE, CloudSQL, GKE)
GCP Setup#
# Create key ring
gcloud kms keyrings create openspp \
--location global
# Create key
gcloud kms keys create pii-encryption \
--keyring openspp \
--location global \
--purpose encryption
# Grant permissions
gcloud kms keys add-iam-policy-binding pii-encryption \
--keyring openspp \
--location global \
--member serviceAccount:openspp@project.iam.gserviceaccount.com \
--role roles/cloudkms.cryptoKeyEncrypterDecrypter
OpenSPP Configuration#
[encryption]
key_provider = gcp_kms
gcp_project_id = your-project-id
gcp_kms_location = global
gcp_kms_keyring = openspp
gcp_kms_key_pii = pii-encryption
# If not using service account:
gcp_credentials_file = /etc/odoo/gcp-credentials.json
Azure Key Vault Provider#
Use for: Azure deployments (VMs, Azure Database)
Azure Setup#
# Create Key Vault
az keyvault create \
--name openspp-vault \
--resource-group openspp-rg \
--location eastus
# Create key
az keyvault key create \
--vault-name openspp-vault \
--name pii-encryption \
--protection software \
--ops encrypt decrypt
# Grant access to managed identity
az keyvault set-policy \
--name openspp-vault \
--object-id <managed-identity-id> \
--key-permissions get unwrapKey wrapKey
OpenSPP Configuration#
[encryption]
key_provider = azure_kv
azure_vault_url = https://openspp-vault.vault.azure.net/
azure_key_name_pii = pii-encryption
# If not using managed identity:
azure_tenant_id = <tenant-id>
azure_client_id = <client-id>
azure_client_secret = <client-secret>
Key Rotation#
Rotate keys annually or after suspected compromise.
Rotation Process#
Create new key version
Encrypt new data with new key
Re-encrypt existing data (background job)
Archive old key version
Database Provider Rotation#
odoo-bin shell -d openspp_prod
# Create new key version
new_version = env['spp.encryption.key'].rotate_key('pii')
print(f"Created key version {new_version}")
# Mark old version as archived
old_keys = env['spp.encryption.key'].search([
('key_id', '=', 'pii'),
('is_current', '=', False),
])
print(f"Archived {len(old_keys)} old versions")
# Re-encrypt data (background job)
wizard = env['spp.key.rotation.wizard'].create({
'key_id': 'pii',
'old_version': 1,
'new_version': new_version,
})
wizard.action_rotate()
Vault Provider Rotation#
# Rotate in Vault
vault write -f transit/keys/openspp-pii/rotate
# Update OpenSPP to use new version (automatic)
# Old ciphertext can still be decrypted with old version
AWS KMS Rotation#
# Enable automatic rotation
aws kms enable-key-rotation --key-id <key-id>
# Check rotation status
aws kms get-key-rotation-status --key-id <key-id>
Key Backup and Recovery#
Config Provider Backup#
# Backup config file
sudo cp /etc/odoo/odoo.conf /secure/backup/odoo.conf.$(date +%Y%m%d)
# Encrypt backup
gpg --symmetric --cipher-algo AES256 /secure/backup/odoo.conf.*
# Store off-site
rsync -av /secure/backup/*.gpg backup-server:/openspp-backups/
Database Provider Backup#
# Export keys (encrypted with master key)
odoo-bin shell -d openspp_prod
keys = env['spp.encryption.key'].search([])
import json
key_export = []
for key in keys:
key_export.append({
'key_id': key.key_id,
'version': key.version,
'encrypted_key': key.encrypted_key.decode('latin-1'),
'algorithm': key.algorithm,
'purpose': key.purpose,
})
with open('/tmp/keys_backup.json', 'w') as f:
json.dump(key_export, f, indent=2)
# Encrypt export
gpg --symmetric --cipher-algo AES256 /tmp/keys_backup.json
rm /tmp/keys_backup.json
# Store master key separately (from odoo.conf)
Vault Provider Backup#
Vault handles backups. Ensure Vault itself is backed up:
# Backup Vault data
vault operator raft snapshot save backup.snap
# Store snapshot securely
gpg --symmetric --cipher-algo AES256 backup.snap
Cloud KMS Backup#
AWS/GCP/Azure KMS keys are automatically backed up by the cloud provider. Document key ARNs/IDs:
# AWS
aws kms describe-key --key-id <key-id> > /secure/backup/aws_kms_info.json
# GCP
gcloud kms keys describe pii-encryption \
--keyring openspp \
--location global > /secure/backup/gcp_kms_info.txt
Key Recovery#
Lost Master Key (Config/Database Provider)#
If master key is lost, encrypted data is unrecoverable.
Recovery options:
Restore from key backup
Restore database from backup before key loss
Lost Cloud KMS Access#
If you lose access to cloud KMS:
Restore IAM permissions
Restore KMS key from cloud provider backup
Contact cloud provider support
Testing Key Recovery#
# 1. Backup current keys
# 2. Remove keys from system
# 3. Restore from backup
# 4. Verify decryption works
odoo-bin shell -d openspp_prod
# Test decryption
reg_id = env['spp.registry.id'].search([], limit=1)
try:
value = reg_id.value # Should decrypt successfully
print("Key recovery successful")
except Exception as e:
print(f"Key recovery failed: {e}")
Key Escrow#
For regulated environments, set up key escrow:
Manual Escrow#
Export keys to encrypted file
Split into key shares (Shamir's Secret Sharing)
Distribute shares to trusted parties
Document recovery procedure
Vault Auto-Unseal#
Use cloud KMS to auto-unseal Vault:
# Configure Vault with AWS KMS auto-unseal
vault operator init \
-recovery-shares=5 \
-recovery-threshold=3
# Recovery keys stored in AWS KMS
# Vault unseals automatically on restart
Security Checklist#
[ ] Keys generated with cryptographically secure RNG
[ ] Master key stored separately from database backups
[ ] Key backups encrypted and stored off-site
[ ] Key rotation schedule documented
[ ] Key recovery procedure tested
[ ] Access to keys restricted (file permissions, IAM)
[ ] Key access audited
[ ] Index salts never rotated (breaks search)
[ ] Old key versions retained for decryption
[ ] Key escrow configured (if required)
Troubleshooting#
Key not found error
# Check key exists
odoo-bin shell -d openspp_prod
env['spp.encryption.key'].search([('key_id', '=', 'pii')])
# Check config has key
grep "encryption_key_pii" /etc/odoo/odoo.conf
Vault connection failed
# Test Vault access
vault login -method=approle \
role_id=$VAULT_ROLE_ID \
secret_id=$VAULT_SECRET_ID
# Test key access
vault write transit/datakey/plaintext/openspp-pii bits=256
AWS KMS access denied
# Check IAM permissions
aws kms describe-key --key-id <key-id>
# Check instance role
curl http://169.254.169.254/latest/meta-data/iam/security-credentials/
Re-encryption job stuck
# Check job status
odoo-bin shell -d openspp_prod
jobs = env['queue.job'].search([('name', 'like', 'rotate_key')])
for job in jobs:
print(f"{job.state}: {job.exc_info}")
openspp.org