1
0
Fork 0

Add Kubernetes integration tests for EasyHAProxy examples

- Introduced a suite of pytest-based integration tests for Kubernetes using `kind`.
- Automated local installation of dependencies (`kind`, `kubectl`, and `helm`) when missing.
- Implemented fixtures for Kubernetes resource management and TLS secrets.
- Added end-to-end tests for HTTP and HTTPS ingress functionality.
This commit is contained in:
Joao Gilberto Magalhaes 2026-02-11 23:32:44 -05:00
parent 5767e55dea
commit 90b01b1f13
18 changed files with 2978 additions and 24 deletions

View file

@ -31,12 +31,22 @@ Protect APIs and services with JWT authentication without needing application-le
| `algorithm` | JWT signing algorithm | `RS256` |
| `issuer` | Expected JWT issuer (optional, set to `none`/`null` to skip validation) | (optional) |
| `audience` | Expected JWT audience (optional, set to `none`/`null` to skip validation) | (optional) |
| `pubkey_path` | Path to public key file (required if `pubkey` not provided) | (required) |
| `pubkey` | Public key content as base64-encoded string (required if `pubkey_path` not provided) | (optional) |
| `pubkey_path` | Path to public key file (priority 1: explicit file path) | (optional) |
| `pubkey` | Public key content as base64-encoded string (priority 2: inline content) | (optional) |
| `k8s_secret.pubkey` | Kubernetes secret containing public key (priority 3: Kubernetes only - see below) | (optional) |
| `paths` | List of paths that require JWT validation (optional) | (all paths) |
| `only_paths` | If `true`, only specified paths are accessible; if `false`, only specified paths require JWT | `false` |
| `allow_anonymous` | If `true`, allows requests without Authorization header (validates JWT if present) | `false` |
### Public Key Configuration Priority
When multiple public key options are configured, they are evaluated in this order:
1. **`pubkey_path`** - Direct file path (explicit configuration)
2. **`pubkey`** - Base64-encoded key content (inline configuration)
3. **`k8s_secret.pubkey`** - Kubernetes secret (recommended for Kubernetes deployments)
The first configured option is used; others are ignored.
## Path Validation Logic
- **No paths configured:** ALL requests to the domain require JWT validation (default behavior)
@ -120,7 +130,70 @@ services:
# Invalid JWTs are rejected
```
### Kubernetes Annotations
### Kubernetes with Secrets (Recommended)
The recommended way to configure JWT public keys in Kubernetes is using Kubernetes Secrets with the `k8s_secret` pattern:
```yaml
---
# Create a secret with your JWT public key
apiVersion: v1
kind: Secret
metadata:
name: jwt-pubkey-secret
namespace: production
type: Opaque
stringData:
pubkey: |
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA...
-----END PUBLIC KEY-----
---
# Reference it in your ingress
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: api-ingress
namespace: production
annotations:
easyhaproxy.plugins: "jwt_validator"
easyhaproxy.plugin.jwt_validator.algorithm: "RS256"
easyhaproxy.plugin.jwt_validator.issuer: "https://auth.example.com/"
easyhaproxy.plugin.jwt_validator.audience: "https://api.example.com"
# Load public key from Kubernetes secret
easyhaproxy.plugin.jwt_validator.k8s_secret.pubkey: "jwt-pubkey-secret"
easyhaproxy.plugin.jwt_validator.paths: "/api/admin,/api/users"
easyhaproxy.plugin.jwt_validator.only_paths: "false"
spec:
ingressClassName: easyhaproxy
rules:
- host: api.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: api-service
port:
number: 8080
```
**With explicit secret key name:**
```yaml
metadata:
annotations:
# Use custom key name from the secret
easyhaproxy.plugin.jwt_validator.k8s_secret.pubkey: "jwt-pubkey-secret/rsa-public-key"
```
For complete details about the `k8s_secret` pattern, including auto-detect vs explicit key names, troubleshooting, and security considerations, see: [Loading Plugin Configuration from Kubernetes Secrets](../kubernetes.md#loading-plugin-configuration-from-kubernetes-secrets)
### Kubernetes with pubkey_path (Legacy)
You can also mount the public key file using ConfigMaps or volumes:
```yaml
apiVersion: networking.k8s.io/v1
@ -135,11 +208,13 @@ metadata:
easyhaproxy.plugin.jwt_validator.paths: "/api/admin,/api/users"
easyhaproxy.plugin.jwt_validator.only_paths: "false"
spec:
ingressClassName: easyhaproxy
rules:
- host: api.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: api-service
@ -147,6 +222,8 @@ spec:
number: 8080
```
**Note:** This requires mounting the public key file into the EasyHAProxy pod using ConfigMaps or volumes. Using `k8s_secret.pubkey` is recommended as it's simpler and more secure.
### Static YAML Configuration
```yaml

View file

@ -245,6 +245,219 @@ env:
For more information on plugin types and available plugins, see the [Using Plugins](plugins.md) guide.
## Loading Plugin Configuration from Kubernetes Secrets
EasyHAProxy supports loading sensitive plugin configuration values directly from Kubernetes Secrets using the `k8s_secret` pattern. This is a **generic, plugin-agnostic feature** that works with any plugin.
### Why Use Kubernetes Secrets?
- **Security**: Keep sensitive data (API keys, passwords, certificates) out of annotations
- **Best practices**: Follows Kubernetes conventions for managing sensitive data
- **Simplicity**: No need to mount volumes or ConfigMaps for secret data
- **Encryption**: Secrets are encrypted at rest in etcd
### Annotation Format
```yaml
# Auto-detect key (tries common variations):
easyhaproxy.plugin.{plugin_name}.k8s_secret.{config_key}: "secret_name"
# Explicit key (no variations):
easyhaproxy.plugin.{plugin_name}.k8s_secret.{config_key}: "secret_name/key_name"
```
### How It Works
1. **You create** a Kubernetes Secret with your sensitive data
2. **You reference** the secret in your ingress annotation using the `k8s_secret` pattern
3. **EasyHAProxy reads** the secret from the same namespace as the ingress
4. **EasyHAProxy transforms** the annotation to inject the secret value
5. **The plugin receives** the value as if it was provided directly in the annotation
**Example transformation:**
```yaml
# Input annotation:
easyhaproxy.plugin.jwt_validator.k8s_secret.pubkey: "my-jwt-secret"
# EasyHAProxy reads the secret and transforms to:
easyhaproxy.plugin.jwt_validator.pubkey: "<base64-encoded-content>"
```
### Auto-Detect vs Explicit Key
#### Auto-Detect Key Format
When you use `"secret_name"` (without `/`), EasyHAProxy tries to find the key automatically:
```yaml
easyhaproxy.plugin.jwt_validator.k8s_secret.pubkey: "my-jwt-secret"
```
EasyHAProxy will try these keys in order:
1. Exact match: `pubkey`
2. Common variations based on the config key name
**Auto-detect key variations:**
| Config Key | Tries (in order) |
|------------|---------------------------------------|
| `pubkey` | `pubkey`, `public-key`, `jwt.pub`, `tls.crt` |
| `password` | `password`, `pass`, `pwd` |
| `api_key` | `api_key`, `apikey`, `api-key`, `key` |
#### Explicit Key Format
When you use `"secret_name/key_name"` (with `/`), EasyHAProxy only tries the exact key name:
```yaml
easyhaproxy.plugin.jwt_validator.k8s_secret.pubkey: "my-jwt-secret/rsa-public-key"
```
EasyHAProxy will **only** try: `rsa-public-key` (no variations)
**Use explicit key when:**
- Your secret uses a non-standard key name
- You want to be explicit and avoid ambiguity
- Multiple keys exist in the secret
### Complete Example
```yaml
---
# 1. Create a secret with your JWT public key
apiVersion: v1
kind: Secret
metadata:
name: jwt-pubkey-secret
namespace: production
type: Opaque
stringData:
pubkey: |
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA...
-----END PUBLIC KEY-----
---
# 2. Reference it in your ingress
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: api-ingress
namespace: production
annotations:
easyhaproxy.plugins: "jwt_validator"
easyhaproxy.plugin.jwt_validator.algorithm: "RS256"
easyhaproxy.plugin.jwt_validator.issuer: "https://auth.example.com/"
easyhaproxy.plugin.jwt_validator.audience: "https://api.example.com"
# Load pubkey from Kubernetes secret (auto-detect key)
easyhaproxy.plugin.jwt_validator.k8s_secret.pubkey: "jwt-pubkey-secret"
spec:
ingressClassName: easyhaproxy
rules:
- host: api.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: api-service
port:
number: 8080
```
### Example with Explicit Key Name
```yaml
---
apiVersion: v1
kind: Secret
metadata:
name: app-credentials
namespace: production
type: Opaque
stringData:
# Custom key name
rsa-public-key: |
-----BEGIN PUBLIC KEY-----
...
-----END PUBLIC KEY-----
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: api-ingress
namespace: production
annotations:
easyhaproxy.plugins: "jwt_validator"
# Use explicit key name after the slash
easyhaproxy.plugin.jwt_validator.k8s_secret.pubkey: "app-credentials/rsa-public-key"
spec:
ingressClassName: easyhaproxy
# ... rest of configuration
```
### Using with Any Plugin
The `k8s_secret` pattern works with **any plugin configuration**:
```yaml
# JWT Validator - load public key
easyhaproxy.plugin.jwt_validator.k8s_secret.pubkey: "jwt-secret"
# Hypothetical API auth plugin - load API key
easyhaproxy.plugin.api_auth.k8s_secret.api_key: "api-credentials/key"
# Hypothetical basic auth plugin - load password
easyhaproxy.plugin.basic_auth.k8s_secret.password: "auth-secret/pwd"
```
### Priority Order
When multiple configuration methods are used, this is the priority (highest to lowest):
1. **Explicit annotation** (e.g., `easyhaproxy.plugin.jwt_validator.pubkey: "value"`)
2. **k8s_secret annotation** (e.g., `easyhaproxy.plugin.jwt_validator.k8s_secret.pubkey: "secret"`)
Explicit annotations always take precedence over `k8s_secret` annotations.
### Troubleshooting
**Secret not found:**
```
WARNING: Ingress production/api-ingress - Failed to process k8s_secret annotation
'easyhaproxy.plugin.jwt_validator.k8s_secret.pubkey' with value 'jwt-secret': ...
```
- Verify the secret exists: `kubectl get secret jwt-secret -n production`
- Check the secret is in the same namespace as the ingress
**Key not found in secret:**
```
WARNING: Ingress production/api-ingress - Secret 'jwt-secret' found but no matching
key (tried: pubkey, public-key, jwt.pub, tls.crt)
```
- List secret keys: `kubectl get secret jwt-secret -n production -o jsonpath='{.data}'`
- Use explicit key format: `"jwt-secret/actual-key-name"`
**Check EasyHAProxy logs:**
```bash
kubectl logs -n easyhaproxy -l app=easyhaproxy --tail=100
```
Look for:
- `INFO: Loaded 'pubkey' from secret 'jwt-secret'` (success)
- `WARNING: Secret 'xyz' found but no matching key` (key not found)
### Security Considerations
- Secrets are read from the **same namespace** as the ingress (no cross-namespace access)
- EasyHAProxy needs RBAC permissions to read secrets (included in default deployment)
- Secrets are encrypted at rest in etcd
- Secret values are base64-encoded by Kubernetes automatically
- Use Kubernetes RBAC to control which service accounts can read which secrets
## Certbot / ACME / Letsencrypt
It is necessary to add the annotation `easyhaproxy.certbot` to the ingress configuration: