diff --git a/docs/configuration/secret-management.md b/docs/configuration/secret-management.md new file mode 100644 index 0000000..50f9f43 --- /dev/null +++ b/docs/configuration/secret-management.md @@ -0,0 +1,187 @@ +# Secret Key Management + +`authpaseto_secret_key` is the root of trust for all `local` tokens. If an attacker +reads this key, they can mint valid tokens and impersonate any subject in your +system until the key is rotated and previously issued tokens are invalidated. + +This guide explains how to: + +1. Generate a cryptographically strong key. +2. Store it in a secure system instead of source code. +3. Retrieve it safely at runtime. +4. Rotate it with minimal downtime. + +## Why this matters + +For `local` PASETO, your secret key is used for authenticated encryption. +Compromise impact is high: + +- **Token forgery:** attackers can create valid access or refresh tokens. +- **Privilege escalation:** forged tokens can carry arbitrary claims. +- **Persistence:** compromise may remain active until keys rotate. + +Treat this key like a database credential with admin scope, or better. + +## Key generation requirements + +Use at least **32 random bytes** from a cryptographically secure RNG. For +storage and transport, encode bytes as URL-safe base64. + +```python +import base64 +import secrets + + +def generate_paseto_secret_key() -> str: + """Return a new URL-safe base64 key for `authpaseto_secret_key`.""" + raw_key = secrets.token_bytes(32) + return base64.urlsafe_b64encode(raw_key).decode("utf-8") +``` + +Avoid: + +- Hardcoded strings such as `"secret"`. +- Password-like values typed by humans. +- Reusing keys across environments. + +Generate unique keys per environment (`dev`, `staging`, `prod`) and per tenant +when you need strict cryptographic isolation. + +## Storage and retrieval options + +### 1) Environment variable (baseline) + +Store only the generated random key value in an environment variable such as +`AUTHPASETO_SECRET_KEY`. + +**Advantages** + +- Easy to adopt. +- Works with containers and most deployment systems. + +**Disadvantages** + +- Exposed to process-level introspection. +- Rotation usually needs restarts. +- Weak controls if your deployment platform lacks secret management. + +### 2) Encrypted file + Fernet envelope encryption + +Store an encrypted key blob on disk and decrypt it at startup with a separate +master key (for example from your orchestrator secret store). + +**Advantages** + +- At-rest encryption for file-based deployments. +- Easy local/offline operation. + +**Disadvantages** + +- You must still protect and rotate the Fernet master key. +- Adds envelope-encryption operational complexity. + +### 3) TPM-backed unsealing (`tpm2-pytss`) + +Seal the PASETO key to TPM state (PCR policy), then unseal only on trusted +hosts and expected boot conditions. + +**Advantages** + +- Hardware-backed protection. +- Strong resistance to key extraction from disk images. +- Can bind decryption to platform integrity. + +**Disadvantages** + +- Platform-specific provisioning and recovery workflows. +- Hardware lifecycle and replacement complexity. +- More operational overhead than software-only options. + +### 4) HashiCorp Vault (transit/kv) + +Keep key material in Vault and retrieve short-lived secrets at startup (or on a +refresh schedule). Prefer AppRole, Kubernetes auth, or cloud IAM auth. + +**Advantages** + +- Centralized access control and auditing. +- Strong policy model and dynamic credentials. +- Good rotation workflows. + +**Disadvantages** + +- Requires high-availability Vault operations. +- New dependency in your auth critical path. +- Misconfigured policies can still leak secrets. + +### 5) Cloud secret managers (AWS/GCP/Azure) + +Use managed secrets services (for example AWS Secrets Manager, GCP Secret +Manager, or Azure Key Vault) with workload identity. + +**Advantages** + +- Managed durability/HA and IAM integration. +- Native rotation tooling and audit logs. + +**Disadvantages** + +- Cloud lock-in. +- Latency/network dependency if fetched at request time. + +### 6) Keycloak (identity provider integration) + +Keycloak is useful for externalizing identity and token issuance, but it is not +a full secret-management system for generic application keys. + +**Advantages** + +- Centralized identity and access model. +- Useful when you already delegate authn/authz to Keycloak. + +**Disadvantages** + +- Not designed as a general-purpose secret vault. +- Limited secret lifecycle controls compared to Vault/KMS products. +- Usually better paired with a dedicated secret manager. + +## Recommended loading pattern + +- Retrieve the secret once during startup. +- Keep it in memory only (avoid writing plaintext to logs/files). +- Fail fast if retrieval fails. +- Cache with explicit refresh policy only if your backend supports safe rotation. + +```python +import os + +from fastapi_paseto import AuthPASETO + + +def load_secret_from_env() -> str: + """Read a required PASETO secret from environment variables.""" + secret = os.getenv("AUTHPASETO_SECRET_KEY") + if not secret: + msg = "AUTHPASETO_SECRET_KEY is not set" + raise RuntimeError(msg) + return secret + + +@AuthPASETO.load_config +def get_config() -> dict[str, str]: + """Provide secure runtime configuration to FastAPI PASETO.""" + return {"authpaseto_secret_key": load_secret_from_env()} +``` + +## Rotation strategy + +Use staged rotation with overlap: + +1. Generate a new key in your secret manager. +2. Deploy services that can validate old tokens while issuing with the new key + (or use short token TTLs and coordinated cutover). +3. Wait for old access/refresh windows to expire. +4. Revoke and remove the previous key. + +For high-security environments, automate periodic key rotation and include +incident-driven emergency rotation playbooks. diff --git a/docs/examples.md b/docs/examples.md index fbc95eb..9c5d4ab 100644 --- a/docs/examples.md +++ b/docs/examples.md @@ -19,5 +19,6 @@ from example file to the relevant guide. | `examples/footer_assertion.py` | Footers and implicit assertions | [Footers and Assertions](advanced-usage/footer-assertion.md) | | `examples/overrides.py` | Route-level transport overrides | [Per-route Overrides](advanced-usage/overrides.md) | | `examples/validation.py` | Issuer, audience, base64, and custom token types | [Validation and Custom Types](advanced-usage/validation.md) | +| `examples/secure_secret_management.py` | Secure key generation and retrieval patterns | [Secret Key Management](configuration/secret-management.md) | | `examples/generate_doc.py` | Manual OpenAPI customization | [Generate Documentation](advanced-usage/generate-docs.md) | | `examples/multiple_files/` | Multi-module application layout | [Bigger Applications](advanced-usage/bigger-app.md) | diff --git a/docs/index.md b/docs/index.md index 01bc4b3..1eb0278 100644 --- a/docs/index.md +++ b/docs/index.md @@ -54,5 +54,6 @@ def get_config(): - Start with [Basic Usage](usage/basic.md) - See [JSON Body Tokens](usage/json.md) for HTTP body transport - See [WebSocket Usage](usage/websocket.md) for header and query auth +- See [Secret Key Management](configuration/secret-management.md) for generation, storage, and rotation guidance - See [Examples](examples.md) for the full example catalog - See [API Documentation](api-doc.md) for the full callable reference diff --git a/examples/secure_secret_management.py b/examples/secure_secret_management.py new file mode 100644 index 0000000..0043978 --- /dev/null +++ b/examples/secure_secret_management.py @@ -0,0 +1,77 @@ +"""Example secret loading patterns for `authpaseto_secret_key`. + +This module demonstrates secure key generation and retrieval options that can be +adapted to environment variables, Fernet-encrypted local files, and TPM-backed +unsealing. +""" + +import base64 +import os +import secrets +from pathlib import Path + +from cryptography.fernet import Fernet + +from fastapi_paseto import AuthPASETO + + +def generate_paseto_secret_key() -> str: + """Generate a URL-safe base64 key for `authpaseto_secret_key`. + + Returns: + Newly generated key encoded as UTF-8 text. + """ + raw_key = secrets.token_bytes(32) + return base64.urlsafe_b64encode(raw_key).decode("utf-8") + + +def load_secret_from_env() -> str: + """Load `AUTHPASETO_SECRET_KEY` and fail fast if it is unset. + + Raises: + RuntimeError: If the environment variable is missing. + + Returns: + Secret key string suitable for `authpaseto_secret_key`. + """ + secret = os.getenv("AUTHPASETO_SECRET_KEY") + if secret: + return secret + msg = "AUTHPASETO_SECRET_KEY is not set" + raise RuntimeError(msg) + + +def decrypt_secret_with_fernet(encrypted_path: Path, fernet_key: str) -> str: + """Decrypt an encrypted secret blob with Fernet. + + Args: + encrypted_path: Path to encrypted key bytes. + fernet_key: Base64-encoded Fernet key from a secure source. + + Returns: + Decrypted secret key text for `authpaseto_secret_key`. + """ + encrypted_bytes = encrypted_path.read_bytes() + decrypted_bytes = Fernet(fernet_key.encode("utf-8")).decrypt(encrypted_bytes) + return decrypted_bytes.decode("utf-8") + + +def load_secret_from_tpm() -> str: + """Placeholder showing where TPM unseal integration should happen. + + Replace this function with your `tpm2-pytss` policy session and unseal flow. + + Returns: + Secret key text loaded from TPM-sealed storage. + """ + msg = "Implement TPM unseal using tpm2-pytss for your platform" + raise NotImplementedError(msg) + + +@AuthPASETO.load_config +def get_config() -> dict[str, str]: + """Return runtime configuration for FastAPI PASETO. + + This example defaults to an environment variable for simplicity. + """ + return {"authpaseto_secret_key": load_secret_from_env()} diff --git a/mkdocs.yml b/mkdocs.yml index c59a7a0..8ffca78 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -1,34 +1,34 @@ site_name: FastAPI PASETO site_description: FastAPI extension that provides PASETO Auth support -theme: - name: material - palette: - primary: black - -repo_name: Raze-Systems/fastapi-paseto -repo_url: https://github.com/Raze-Systems/fastapi-paseto - -markdown_extensions: - - markdown_include.include: - base_path: docs - - toc: - permalink: true - - pymdownx.highlight: - linenums_style: pymdownx.inline - - pymdownx.magiclink: - user: Raze-Systems - repo: fastapi-paseto - repo_url_shorthand: true - - pymdownx.emoji: - emoji_index: !!python/name:pymdownx.emoji.twemoji - - attr_list - - def_list - - admonition - - codehilite - - pymdownx.tabbed - - pymdownx.superfences - - pymdownx.inlinehilite - +theme: + name: material + palette: + primary: black + +repo_name: Raze-Systems/fastapi-paseto +repo_url: https://github.com/Raze-Systems/fastapi-paseto + +markdown_extensions: + - markdown_include.include: + base_path: docs + - toc: + permalink: true + - pymdownx.highlight: + linenums_style: pymdownx.inline + - pymdownx.magiclink: + user: Raze-Systems + repo: fastapi-paseto + repo_url_shorthand: true + - pymdownx.emoji: + emoji_index: !!python/name:pymdownx.emoji.twemoji + - attr_list + - def_list + - admonition + - codehilite + - pymdownx.tabbed + - pymdownx.superfences + - pymdownx.inlinehilite + nav: - About: index.md - Usage: @@ -50,6 +50,7 @@ nav: - Generate Documentation: advanced-usage/generate-docs.md - Configuration Options: - General Options: configuration/general.md + - Secret Key Management: configuration/secret-management.md - HTTP Transport Options: configuration/headers.md - Denylist Options: configuration/denylist.md - Examples: examples.md @@ -57,6 +58,6 @@ nav: - Supply Chain Security: supply-chain-security.md - Development - Contributing: contributing.md - Release Notes: release-notes.md - -extra_css: - - 'css/custom.css' + +extra_css: + - 'css/custom.css'