12 KiB
Secrets and Configuration Management
This guide covers best practices for managing secrets and configuration in Fission Python functions.
Table of Contents
- Overview
- Kubernetes Secrets vs ConfigMaps
- Secrets in Fission
- Vault Encryption
- Secret Rotation
- Configuration Precedence
- Best Practices
Overview
Sensitive data (passwords, API keys) should never be:
- Committed to Git
- Hardcoded in source code
- Passed as plaintext in deployment files
Instead, use:
- Kubernetes Secrets - For sensitive values
- Kubernetes ConfigMaps - For non-sensitive configuration
- Vault encryption - For encrypting secrets at rest in K8s
Kubernetes Secrets vs ConfigMaps
| Feature | Secrets | ConfigMaps |
|---|---|---|
| Purpose | Sensitive data (passwords, tokens, keys) | Non-sensitive config (endpoints, feature flags) |
| Storage | Base64 encoded (not encrypted by default) | Plain text |
| Mount as | Files in /secrets/ |
Files in /configs/ |
| Access in code | get_secret(key) |
get_config(key) |
| Max size | 1MB total | 1MB total |
| Can be encrypted | Yes, with K8s encryption at rest | Yes |
Rule of thumb:
- Use Secrets for: database passwords, API tokens, encryption keys
- Use ConfigMaps for: service URLs, feature flags, log levels, non-sensitive constants
Secrets in Fission
Defining Secret References in deployment.json
In .fission/deployment.json, declare the secret names your functions expect:
{
"function_common": {
"secrets": ["fission-myproject-env"],
"configmaps": ["fission-myproject-config"]
},
"secrets": {
"fission-myproject-env": {
"literals": [
"PG_HOST=localhost",
"PG_PORT=5432"
]
}
}
}
Important: The literals array here is only documentation. The actual secret values must be created separately in Kubernetes.
Creating Actual Kubernetes Secrets
# Create secret with multiple keys
kubectl create secret generic fission-myproject-env \
--from-literal=PG_HOST=postgres.example.com \
--from-literal=PG_PORT=5432 \
--from-literal=PG_DB=mydb \
--from-literal=PG_USER=myuser \
--from-literal=PG_PASS='my-password'
# In a specific namespace (Fission namespace)
kubectl create secret generic fission-myproject-env \
--namespace fission \
--from-literal=...
# From environment file
kubectl create secret generic fission-myproject-env \
--namespace fission \
--from-env-file=.env
How Secrets Are Mounted
Fission mounts secrets as files in the function pod:
/secrets/{namespace}/{secret-name}/{key}
Example path: /secrets/default/fission-myproject-env/PG_HOST
The helpers.py get_secret() function reads from this path:
def get_secret(key: str, default=None):
namespace = get_current_namespace()
path = f"/secrets/{namespace}/{SECRET_NAME}/{key}"
with open(path, "r") as f:
return f.read()
Note: SECRET_NAME must match the K8s secret name (fission-myproject-env).
Reading Secrets in Code
from helpers import get_secret
# With default fallback
db_host = get_secret("PG_HOST", "localhost")
db_port = int(get_secret("PG_PORT", "5432"))
db_user = get_secret("PG_USER")
db_pass = get_secret("PG_PASS")
# If key missing and no default, returns None
maybe_value = get_secret("OPTIONAL_KEY")
Always provide a default for non-critical configuration to avoid crashes if secret is missing.
ConfigMaps
Same pattern, different mount path: /configs/{namespace}/{configmap-name}/{key}
from helpers import get_config
api_endpoint = get_config("API_ENDPOINT", "http://default.api")
feature_flag = get_config("FEATURE_X_ENABLED", "false")
Create ConfigMap:
kubectl create configmap fission-myproject-config \
--namespace fission \
--from-literal=API_ENDPOINT=https://api.example.com \
--from-literal=FEATURE_X_ENABLED=true
Vault Encryption
To encrypt secrets before storing in K8s:
Generate Encryption Key
# Generate 32-byte (64 hex char) random key
openssl rand -hex 32
# Example output: e24ad6ceed96115520f6e6dc8a0da506ae9a706823d54f30a5b75447ecf477b6
Encrypt a Value
# Encrypt locally
from vault import encrypt_vault
key = "e24ad6ceed96115520f6e6dc8a0da506ae9a706823d54f30a5b75447ecf477b6"
encrypted = encrypt_vault("my-secret-password", key)
print(encrypted)
# Output: vault:v1:base64-encrypted-data
Store Encrypted Value
Create K8s secret with encrypted value:
kubectl create secret generic fission-myproject-env \
--from-literal=PG_PASS='vault:v1:base64...'
Configure decryption in helpers.py
CRYPTO_KEY = "e24ad6ceed96115520f6e6dc8a0da506ae9a706823d54f30a5b75447ecf477b6"
Automatic Decryption
get_secret() and get_config() automatically:
- Read the file content
- Detect if it starts with
vault:v1:(usingis_valid_vault_format()) - Decrypt using
CRYPTO_KEYif encrypted - Return plaintext
No code changes needed - it "just works".
Verification
# Test decryption
kubectl get secret fission-myproject-env -o jsonpath='{.data.PG_PASS}' | base64 -d
# Should show: vault:v1:...
# Exec into pod and manually check
kubectl exec -it <pod-name> -- python3 -c "from helpers import get_secret; print(get_secret('PG_PASS'))"
# Should print decrypted value
Secret Rotation
Rotating a Secret
- Generate new value (new password, new API key)
- Encrypt (if using vault)
- Update K8s secret:
kubectl create secret generic fission-myproject-env \ --dry-run=client \ --from-literal=PG_PASS='new-password' \ -o yaml | kubectl apply -f - - Update actual external system (database, API provider) with new value
- Verify applications work (check logs)
- Remove old value (if rotating from old to new, both may need to coexist temporarily)
Rotating Vault Encryption Key
Warning: Changing CRYPTO_KEY requires re-encrypting all secrets!
- Deploy new code with updated
CRYPTO_KEYtemporarily pointing to new key - Create new K8s secrets with values encrypted under new key (or re-encrypt via script)
- Switch
CRYPTO_KEYback to original (or both keys during transition) - actually this is complex
Recommended: Have two keys during rotation:
CRYPTO_KEYS = [
"old-key-hex...", # Keep for decrypting old secrets
"new-key-hex..." # Use for encrypting new/updated secrets
]
Then update decrypt_vault() to try each key until one works. After all secrets migrated, remove old key.
Configuration Precedence
Fission supports multiple deployment configuration files:
- deployment.json - Base configuration (committed to repo)
- dev-deployment.json - Development overrides (usually not committed)
- local-deployment.json - Local overrides (gitignored)
Override Priority
When using fission deploy --dev, Fission loads:
- Base configuration from
deployment.json - Overlay from
dev-deployment.json
Values in the overlay file replace or extend base values.
Example: Override secret name for dev:
deployment.json:
{
"function_common": {
"secrets": ["fission-myproject-env"]
}
}
dev-deployment.json:
{
"function_common": {
"secrets": ["fission-myproject-dev-env"]
}
}
Now fission deploy --dev uses the dev secret, while fission deploy uses prod secret.
Local Overrides
Create .fission/local-deployment.json for your workstation:
{
"function_common": {
"secrets": ["fission-myproject-local-env"]
}
}
Fission automatically uses this if present (no flag needed). .gitignore typically excludes it.
Best Practices
Do's ✅
- Do use Kubernetes Secrets - Never hardcode credentials
- Do encrypt with vault - Prevents plaintext secrets in K8s
- Do store vault key securely - In K8s sealed secret, external vault (HashiCorp Vault, AWS Secrets Manager), or as a separate K8s secret in restricted namespace
- Do namespace secrets - Use different secrets for dev/staging/prod
- Do rotate secrets regularly - Especially database passwords, API tokens
- Do use ConfigMaps for non-sensitive config - Cleaner separation
- Do provide sensible defaults - In
get_secret()calls - Do validate required secrets - Fail fast at startup:
def init(): pg_host = get_secret("PG_HOST") if not pg_host: raise ValueError("PG_HOST secret is required")
Don'ts ❌
- Don't commit secrets - Even in
deployment.jsonliterals - Don't put plaintext in Git - Use placeholders or remove before commit
- Don't embed vault key in code for production - Use environment-specific override or external secret management
- Don't share vault key publicly - It's a symmetric key - anyone with it can decrypt all secrets
- Don't use same secret across namespaces - Separate environments should have separate credentials
- Don't rely on obscurity - Security through obscurity is not security
Supply Chain Security
For production deployments:
-
Store vault key in sealed secrets (if on K8s):
kubectl create secret generic crypto-key \ --from-literal=key='your-hex-key' # Then use SealedSecrets controller to encrypt in Git -
Use external secrets operator:
apiVersion: external-secrets.io/v1beta1 kind: ExternalSecret metadata: name: db-creds spec: refreshInterval: "1h" secretStoreRef: name: vault-backend kind: SecretStore target: name: fission-myproject-env creationPolicy: Owner data: - secretKey: PG_PASS remoteRef: key: /prod/db/password -
Rotate automatically with cronjobs or external secret manager
Environment Variable Alternative
While the template uses secret files mounted by Fission, you can also use environment variables:
"function_common": {
"environment": {
"LOG_LEVEL": "INFO",
"FEATURE_FLAG": "true"
}
}
Access with os.getenv():
import os
log_level = os.getenv("LOG_LEVEL", "INFO")
However: Environment is less flexible than secrets/configmaps for dynamic updates (requires function restart). Prefer secrets/configmaps for values that may change independently of code deployments.
Troubleshooting
Secret Not Available
# Check secret exists in correct namespace
kubectl get secret fission-myproject-env -n fission
# Check secret keys
kubectl get secret fission-myproject-env -n fission -o jsonpath='{.data}'
# Check pod mount
kubectl exec -it <pod-name> -n fission -- ls -la /secrets/default/
Common issues:
- Secret in wrong namespace (use Fission namespace, usually
fissionor as configured) - Secret name typo in helpers.py
SECRET_NAMEvariable - Secret not mounted due to missing permission (service account restriction)
Vault Decryption Failing
from vault import is_valid_vault_format, decrypt_vault
vault_str = get_secret("PG_PASS")
print(is_valid_vault_format(vault_str)) # Should be True
print(decrypt_vault(vault_str, "wrong-key")) # Raises CryptoError
Check:
CRYPTO_KEYis set correctly inhelpers.py- Key is 64 hex characters (32 bytes)
- Encrypted value format is exactly
vault:v1:base64...
Permission Denied Reading Secret
Pod may lack permission to read secret. Check service account:
# Get function pod's service account
kubectl get pod <pod-name> -n fission -o jsonpath='{.spec.serviceAccountName}'
# Check role bindings
kubectl get rolebinding -n fission
kubectl get clusterrolebinding -n fission
# Add permission if needed (requires cluster admin)
kubectl create clusterrolebinding fission-secret-reader \
--clusterrole=view \
--serviceaccount=fission:default
Further Reading
- Kubernetes Secrets
- Kubernetes ConfigMaps
- Fission Environment and Config
- PyNaCl Documentation
- SealedSecrets - Store encrypted secrets in Git