Claude
Skills
Sign in
Back

platxa-secrets-management

Included with Lifetime
$97 forever

Fernet encryption, Kubernetes secrets, and secure token patterns for Platxa services. Covers Python cryptography, K8s Secret objects, key rotation, and constant-time token verification.

Cloud & DevOps

What this skill does


# Platxa Secrets Management

Guide for secrets management patterns across Platxa services.

## Overview

| Component | Encryption | Storage | Pattern |
|-----------|------------|---------|---------|
| **Credentials** | Fernet (AES-128-CBC) | Odoo DB (encrypted) | Encrypt before store |
| **K8s Secrets** | Base64 + etcd encryption | K8s API | stringData/data fields |
| **Tokens** | N/A | Memory/Header | HMAC verification |
| **JWT Keys** | Fernet (RSA private) | DB | Rotate with grace period |

## Core Principles

1. **Defense in Depth**: Encrypt at rest, in transit, and minimize plaintext in memory
2. **Least Privilege**: Namespace-scoped secrets, RBAC for access control
3. **Key Rotation**: Regular rotation with grace periods for validation
4. **Audit Trail**: Log secret operations (never log values, only key names)
5. **No Plaintext Storage**: Never store secrets unencrypted in code, configs, or logs

## Fernet Encryption

### Key Generation

```python
from cryptography.fernet import Fernet

# Generate a new key (32 bytes, url-safe base64)
key = Fernet.generate_key()  # b'...' (44 chars base64)

# Store key securely (environment, config parameter, vault)
# NEVER hardcode in source code
```

### Encryption/Decryption

```python
from cryptography.fernet import Fernet

def encrypt_value(value: str, key: bytes) -> str:
    """Encrypt a string value."""
    f = Fernet(key)
    encrypted = f.encrypt(value.encode())
    return encrypted.decode()  # Store as string

def decrypt_value(encrypted: str, key: bytes) -> str:
    """Decrypt a string value."""
    f = Fernet(key)
    decrypted = f.decrypt(encrypted.encode())
    return decrypted.decode()
```

### Key Retrieval Pattern

```python
import base64
from odoo.tools import config

def _get_encryption_key(self) -> bytes:
    """Get encryption key from config with fallback."""
    # Priority 1: Odoo config file
    key = config.get('instance_manager_encryption_key')

    # Priority 2: System parameter (database)
    if not key:
        key = self.env['ir.config_parameter'].sudo().get_param(
            'instance_manager.encryption_key', ''
        )

    # Priority 3: Development fallback (WARNING: insecure)
    if not key:
        _logger.warning("No encryption key configured. Using default (INSECURE!)")
        key = 'developmentonlykey32byteslong!!'

    # Ensure proper Fernet format
    if not key.endswith('='):  # Not base64 encoded
        key = base64.urlsafe_b64encode(key[:32].ljust(32).encode()).decode()

    return key.encode()
```

## Kubernetes Secrets

### Create Secret with stringData

```python
from kubernetes import client

def create_secret(namespace: str, name: str, data: dict) -> None:
    """Create K8s secret using stringData (auto base64)."""
    core_v1 = client.CoreV1Api()

    secret = client.V1Secret(
        metadata=client.V1ObjectMeta(
            name=name,
            namespace=namespace,
            labels={'app.kubernetes.io/managed-by': 'platxa'}
        ),
        string_data=data,  # Plain text, K8s encodes automatically
        type='Opaque'
    )

    core_v1.create_namespaced_secret(namespace, secret)
```

### Create Secret with Manual Base64

```python
import base64

def create_secret_manual(namespace: str, name: str, data: dict) -> None:
    """Create K8s secret with manual base64 encoding."""
    core_v1 = client.CoreV1Api()

    # Manually encode each value
    encoded_data = {
        k: base64.b64encode(v.encode()).decode()
        for k, v in data.items()
    }

    secret = client.V1Secret(
        metadata=client.V1ObjectMeta(name=name, namespace=namespace),
        data=encoded_data,  # Pre-encoded base64
        type='Opaque'
    )

    core_v1.create_namespaced_secret(namespace, secret)
```

### Read Secret

```python
def read_secret(namespace: str, name: str) -> dict:
    """Read and decode K8s secret."""
    core_v1 = client.CoreV1Api()

    secret = core_v1.read_namespaced_secret(name, namespace)

    # Decode base64 values
    return {
        k: base64.b64decode(v).decode()
        for k, v in secret.data.items()
    }
```

### TLS Secret (Certificate)

```python
def create_tls_secret(namespace: str, name: str, cert: str, key: str) -> None:
    """Create TLS secret for certificates."""
    core_v1 = client.CoreV1Api()

    secret = client.V1Secret(
        metadata=client.V1ObjectMeta(name=name, namespace=namespace),
        type='kubernetes.io/tls',
        string_data={
            'tls.crt': cert,
            'tls.key': key,
        }
    )

    core_v1.create_namespaced_secret(namespace, secret)
```

## Secure Token Generation

### Password Generation

```python
import secrets
import string

def generate_password(length: int = 32) -> str:
    """Generate cryptographically secure password."""
    alphabet = string.ascii_letters + string.digits
    return ''.join(secrets.choice(alphabet) for _ in range(length))

# Usage
db_password = generate_password(32)
admin_password = generate_password(24)
```

### API Token Generation

```python
import secrets

def generate_token(nbytes: int = 32) -> str:
    """Generate hex token for API authentication."""
    return secrets.token_hex(nbytes)  # 64 char hex string

def generate_urlsafe_token(nbytes: int = 32) -> str:
    """Generate URL-safe token."""
    return secrets.token_urlsafe(nbytes)  # ~43 char base64
```

### DNS Verification Token

```python
def generate_dns_token() -> str:
    """Generate token for DNS verification."""
    return secrets.token_hex(16)  # 32 char hex
```

## Token Verification

### Constant-Time Comparison

```python
import hmac

def verify_token(provided: str, expected: str) -> bool:
    """Verify token using constant-time comparison.

    IMPORTANT: Never use == for token comparison (timing attack).
    """
    return hmac.compare_digest(provided, expected)
```

### Bearer Token Middleware

```python
import hmac
from flask import request

def verify_bearer_auth() -> bool:
    """Verify Bearer token from Authorization header."""
    auth_header = request.headers.get('Authorization', '')

    if not auth_header.startswith('Bearer '):
        return False

    token = auth_header[7:]  # Remove 'Bearer ' prefix
    expected = get_expected_token()  # From secure storage

    if not expected:
        return False  # Secure by default

    return hmac.compare_digest(token, expected)
```

## Key Rotation

### Rotation Strategy

| Phase | Duration | Old Key | New Key |
|-------|----------|---------|---------|
| Generate | Immediate | Active | Created (inactive) |
| Grace Period | 24 hours | Active | Available for decrypt |
| Activate | After grace | Inactive | Active |
| Cleanup | 7 days | Deleted | Active |

### Implementation Pattern

```python
def rotate_encryption_key():
    """Rotate encryption key with grace period."""
    # 1. Generate new key
    new_key = Fernet.generate_key()
    new_key_id = generate_key_id()

    # 2. Store new key (inactive)
    store_key(new_key_id, new_key, is_active=False)

    # 3. Schedule activation (after grace period)
    schedule_key_activation(new_key_id, delay=timedelta(hours=24))

    # 4. Re-encrypt existing data (background job)
    schedule_reencryption(new_key_id)

    return new_key_id
```

## Go Environment Secrets

### Config from Environment

```go
func getEnvString(key, defaultValue string) string {
    if value := os.Getenv(key); value != "" {
        return value
    }
    return defaultValue
}

// Usage - never log default values
apiKey := getEnvString("API_KEY", "")
if apiKey == "" {
    log.Fatal("API_KEY environment variable required")
}
```

### In-Cluster Authentication

```go
// K8s automatically mounts service account token
// at /var/run/secrets/kubernetes.io/serviceaccount/token
config, err := rest.InClusterConfig()
if err != nil {
    // Fallback to kubeconfig for local dev
    config, err = clientcmd.BuildConfigFromFlags("", kubeconfig)
}
```

## Workflow

### Step 1: Generate Encryption Key

```python
from cryptography.fern

Related in Cloud & DevOps