Matrix-controlled Kubernetes deployment toggle operator
Project description
openclaw-k8s-toggle-operator
Matrix-controlled Kubernetes deployment toggle operator. Connects to a Matrix homeserver with E2E encryption and listens for chat commands to scale a K8s deployment between 0 and 1 replicas.
Bot Commands
Send these as plain text messages in a Matrix room with the bot (encrypted or unencrypted):
| Command | Action |
|---|---|
start / on |
Scale deployment to 1 replica |
stop / off |
Scale deployment to 0 replicas |
status |
Show deployment replica counts |
help |
Show available commands |
Only users listed in ALLOWED_USERS can send commands. The bot auto-accepts
room invitations from allowed users.
Architecture
- Runs as a single-replica Deployment in a dedicated namespace
- Uses the Kubernetes Python client with in-cluster config to patch deployment scale
- Connects to Matrix via minimatrix (wraps matrix-nio with E2E encryption, session reuse, and device trust)
- Session reuse — persists Matrix access token and device ID across restarts (no re-login or key loss)
- Auto-join — accepts pending room invites on startup
- TOFU device trust — automatically trusts all devices of allowed users
- Multiple auth methods — password, SSO, or JWT via Keycloak (ROPC + JWKS)
- Crypto store must be on a persistent volume or the bot loses decryption keys on restart
- Auto-reconnect loop with exponential backoff (max 20 retries)
Configuration
| Variable | Required | Default |
|---|---|---|
MATRIX_HOMESERVER |
no | http://synapse.matrix.svc.cluster.local:8008 |
MATRIX_USER |
yes | — |
MATRIX_PASSWORD |
yes | — |
ALLOWED_USERS |
yes | — (comma-separated full Matrix user IDs) |
DEPLOYMENT_NAME |
no | clawdbot |
DEPLOYMENT_NAMESPACE |
no | clawdbot |
CRYPTO_STORE_PATH |
no | /data/crypto_store |
ECHO_MODE |
no | true (echo user messages with lobster emoji before processing) |
LOGURU_LEVEL |
no | DEBUG |
JWT Authentication
When AUTH_METHOD=jwt is set, the operator authenticates via Keycloak ROPC (Resource Owner Password Credentials) grant instead of direct Matrix password login. This enables centralized identity management through Keycloak while allowing non-interactive bot authentication.
See HOWTO_MATRIX_KEYCLOAK_OAUTH.md for a step-by-step setup guide covering Keycloak client creation, Synapse configuration, and verification.
Authentication Flow
sequenceDiagram
participant Bot as Bot/Service
participant KC as Keycloak
participant Syn as Synapse
Bot->>KC: 1. ROPC grant (username + password)
KC-->>Bot: 2. JWT access token
Bot->>Syn: 3. Matrix login (JWT)
Syn->>KC: 4. Validate JWT (JWKS or introspection)
KC-->>Syn: JWT valid
Syn-->>Bot: 5. Matrix access token
Supported Login Types
| Login Type | Synapse Config | Validation Method | Use Case |
|---|---|---|---|
com.famedly.login.token.oauth (default) |
synapse-token-authenticator oauth: |
JWKS endpoint | Recommended — automatic key rotation |
com.famedly.login.token |
synapse-token-authenticator jwt: |
Symmetric secret (HS512) | Internal services with shared secret |
org.matrix.login.jwt |
Native jwt_config: |
Public key (RS256) | Simple setup, manual key management |
Environment Variables
| Variable | Required | Default |
|---|---|---|
AUTH_METHOD |
no | password |
KEYCLOAK_URL |
yes (if jwt) | — |
KEYCLOAK_REALM |
yes (if jwt) | — |
KEYCLOAK_CLIENT_ID |
yes (if jwt) | — |
KEYCLOAK_CLIENT_SECRET |
no | "" (empty for public clients) |
JWT_LOGIN_TYPE |
no | com.famedly.login.token.oauth |
Complete Setup Guide
This section describes the full setup for JWT authentication with Keycloak and Synapse.
Prerequisites
- Keycloak instance (any recent version)
- Synapse homeserver
- Admin access to both Keycloak and Synapse
Step 1: Keycloak Realm Setup
Create a dedicated realm for Matrix (or use an existing one). A dedicated realm simplifies user management since any user in the realm can authenticate to Matrix.
Via Keycloak Admin Console:
- Navigate to Realm Settings → Create Realm
- Set realm name (e.g.,
matrix) - Enable the realm
Recommended realm settings:
registrationAllowed: false— disable self-registration (admin creates users)loginWithEmailAllowed: false— use usernames, not emails (Matrix localparts)registrationEmailAsUsername: false— important for Matrix username compatibility
Step 2: Create OAuth Client for Bot Authentication
Create a confidential client with ROPC (Direct Access Grants) enabled. This client is used by bots and services to obtain JWT tokens via username/password.
Via Keycloak Admin Console:
- Navigate to Clients → Create Client
- Set Client ID (e.g.,
synapse-oauth) - Configure:
- Client authentication: ON (confidential client)
- Authorization: OFF
- Authentication flow: Enable only Direct access grants (ROPC)
- Standard flow: OFF (not needed for bots)
- Service accounts: OFF (not needed)
Key settings summary:
| Setting | Value | Reason |
|---|---|---|
publicClient |
false |
Confidential client with secret |
clientAuthenticatorType |
client-secret |
Use client secret for auth |
directAccessGrantsEnabled |
true |
Required — enables ROPC grant |
standardFlowEnabled |
false |
No browser redirects needed |
serviceAccountsEnabled |
false |
Not using service account |
After creation:
- Go to Credentials tab
- Copy the Client secret — you'll need this for the bot configuration
Step 3: Create Bot User in Keycloak
Create a user account for the bot in your Keycloak realm.
Via Keycloak Admin Console:
- Navigate to Users → Add user
- Set username (e.g.,
clawdbot-operator) — this becomes the Matrix localpart - Set email (optional)
- Enable the user
- Go to Credentials tab → Set password
- Set a password and disable "Temporary"
Important: The Keycloak username must match the desired Matrix localpart. Synapse extracts the username from the preferred_username JWT claim.
Step 4: Install synapse-token-authenticator
The synapse-token-authenticator module adds additional login types to Synapse for JWT/OIDC authentication.
Installation:
pip install synapse-token-authenticator
For Docker deployments, add to your container startup:
pip install --no-cache-dir synapse-token-authenticator
Or create a custom Synapse image with the module pre-installed.
Step 5: Configure Synapse
Add the synapse-token-authenticator module to your homeserver.yaml. The configuration depends on which login type(s) you want to support.
Option A: OAuth with JWKS Validation (Recommended)
Login type: com.famedly.login.token.oauth
This is the recommended configuration. Synapse validates JWT signatures against Keycloak's JWKS endpoint, enabling automatic key rotation.
modules:
- module: synapse_token_authenticator.TokenAuthenticator
config:
oauth:
jwt_validation:
# Keycloak JWKS endpoint for signature validation
jwks_endpoint: "https://keycloak.example.com/realms/<realm>/protocol/openid-connect/certs"
# JWT claim containing the Matrix localpart
localpart_path: "preferred_username"
# Require token expiry (recommended)
require_expiry: true
# Validator type: "exist" just checks the user exists
validator:
type: exist
# Auto-register users on first login
registration_enabled: true
Advantages:
- Automatic key rotation — no manual key updates when Keycloak rotates keys
- Standard OIDC/OAuth2 flow
- Works with any OIDC provider
Option B: JWT with Symmetric Secret
Login type: com.famedly.login.token
Uses a shared symmetric secret (HS512) for JWT validation. Simpler but requires secure secret distribution.
modules:
- module: synapse_token_authenticator.TokenAuthenticator
config:
jwt:
# Shared secret (must match token signing key)
secret: "your-256-bit-secret-here"
algorithm: HS512
# Don't auto-register users
allow_registration: false
# Require token expiry
require_expiry: true
Use case: Internal services where you control both token generation and validation.
Option C: Native Synapse JWT (No Module Required)
Login type: org.matrix.login.jwt
Uses Synapse's built-in JWT support with the realm's RSA public key.
Get the realm public key:
curl -s "https://keycloak.example.com/realms/<realm>" | jq -r '.public_key'
Add to homeserver.yaml:
jwt_config:
enabled: true
secret: |
-----BEGIN PUBLIC KEY-----
<paste the public key here>
-----END PUBLIC KEY-----
algorithm: "RS256"
subject_claim: "preferred_username"
issuer: "https://keycloak.example.com/realms/<realm>"
Disadvantages:
- Manual key management — must update
homeserver.yamlwhen Keycloak rotates keys - No automatic key rotation
Combined Configuration (All Methods)
You can enable multiple authentication methods simultaneously:
modules:
- module: synapse_token_authenticator.TokenAuthenticator
config:
# Option A: OAuth/JWKS (com.famedly.login.token.oauth)
oauth:
jwt_validation:
jwks_endpoint: "https://keycloak.example.com/realms/<realm>/protocol/openid-connect/certs"
localpart_path: "preferred_username"
require_expiry: true
validator:
type: exist
registration_enabled: true
# Option B: Symmetric JWT (com.famedly.login.token)
jwt:
secret: "your-256-bit-secret-here"
algorithm: HS512
allow_registration: false
require_expiry: true
# Option C: OIDC Token Introspection (com.famedly.login.token.oidc)
# For real-time token validation (slower but supports revocation)
oidc:
issuer: "https://keycloak.example.com/realms/<realm>"
client_id: "synapse"
client_secret: "your-oidc-client-secret"
allow_registration: true
Step 6: Restart Synapse
After updating homeserver.yaml, restart Synapse to load the new configuration.
Step 7: Test the Setup
Use the included test script to verify the JWT authentication flow:
KEYCLOAK_URL=https://keycloak.example.com \
KEYCLOAK_REALM=matrix \
KEYCLOAK_CLIENT_ID=synapse-oauth \
KEYCLOAK_CLIENT_SECRET=your-client-secret \
MATRIX_USER=clawdbot-operator \
MATRIX_PASSWORD=your-password \
MATRIX_HOMESERVER=https://matrix.example.com \
ALLOWED_USERS=@admin:matrix.example.com \
./test-jwt-login.sh
The script performs:
- Step 1: Obtains JWT from Keycloak via ROPC grant
- Step 2: Decodes and displays JWT claims (for debugging)
- Step 3: Tests Matrix login via the configured login type
Test different login types:
# Test OAuth/JWKS (default)
./test-jwt-login.sh
# Test symmetric JWT
./test-jwt-login.sh --login-type com.famedly.login.token
# Test native Synapse JWT
./test-jwt-login.sh --login-type org.matrix.login.jwt
# Test password auth (skip Keycloak)
./test-jwt-login.sh --auth-method password
test-jwt-login.sh Reference
Command-line options:
| Option | Description | Default |
|---|---|---|
-t, --login-type TYPE |
JWT login type | com.famedly.login.token.oauth |
-m, --auth-method TYPE |
Auth method (password or jwt) |
jwt |
-s, --skip-decode |
Skip JWT decoding step | false |
-v, --verbose |
Show detailed output including full responses | false |
-h, --help |
Show help message | — |
Exit codes:
| Code | Meaning |
|---|---|
| 0 | All tests passed |
| 1 | Configuration error (missing environment variables) |
| 2 | Keycloak token request failed |
| 3 | JWT decode failed |
| 4 | Matrix login failed |
Notes:
- The script auto-detects the local
.venvdirectory for runningconnectortest - For password auth (
-m password), Keycloak variables are not required - Use
-v(verbose) to see full HTTP responses when debugging failures
Comparison: Login Types
| Feature | com.famedly.login.token.oauth |
com.famedly.login.token |
org.matrix.login.jwt |
|---|---|---|---|
| Key management | Automatic (JWKS) | Manual (shared secret) | Manual (public key) |
| Key rotation | Automatic | Manual secret update | Manual config update |
| Algorithm | RS256 (asymmetric) | HS512 (symmetric) | RS256 (asymmetric) |
| Module required | Yes | Yes | No |
| Token revocation | Via introspection mode | No | No |
| Setup complexity | Medium | Low | Low |
| Recommended for | Production | Internal services | Simple setups |
Troubleshooting
Common Issues
"Invalid username or password" from Synapse:
- Verify the Keycloak user exists and password is correct
- Check that
preferred_usernamein JWT matches an existing Matrix user (orregistration_enabled: true) - Verify JWKS endpoint is accessible from Synapse
"ROPC grant failed" from Keycloak:
- Ensure
directAccessGrantsEnabled: trueon the client - Verify client secret is correct
- Check user is enabled in Keycloak
"JWT signature validation failed":
- For JWKS: verify the endpoint URL is correct and accessible
- For native JWT: ensure public key is correctly formatted with PEM headers
- Check
issuermatches the JWTissclaim
Useful Keycloak Endpoints
| Endpoint | Purpose |
|---|---|
/realms/<realm>/.well-known/openid-configuration |
OIDC discovery document |
/realms/<realm>/protocol/openid-connect/certs |
JWKS endpoint (public keys) |
/realms/<realm>/protocol/openid-connect/token |
Token endpoint (ROPC) |
/realms/<realm> |
Realm info (includes public key) |
Kubernetes Deployment
RBAC
The operator requires a ServiceAccount with a Role scoped to the target namespace:
apiVersion: v1
kind: ServiceAccount
metadata:
name: openclaw-toggle-operator
namespace: clawdbot
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: openclaw-toggle-operator
namespace: clawdbot
rules:
- apiGroups: ["apps"]
resources: ["deployments", "deployments/scale"]
verbs: ["get", "patch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: openclaw-toggle-operator
namespace: clawdbot
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: openclaw-toggle-operator
subjects:
- kind: ServiceAccount
name: openclaw-toggle-operator
namespace: clawdbot
Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: openclaw-toggle-operator
namespace: clawdbot
spec:
replicas: 1
selector:
matchLabels:
app: openclaw-toggle-operator
template:
metadata:
labels:
app: openclaw-toggle-operator
spec:
serviceAccountName: openclaw-toggle-operator
containers:
- name: operator
image: xomoxcc/openclaw-k8s-toggle-operator:latest
env:
- name: MATRIX_USER
value: "clawdbot-operator"
- name: MATRIX_PASSWORD
valueFrom:
secretKeyRef:
name: openclaw-toggle-operator
key: matrix-password
- name: ALLOWED_USERS
value: "@henning:matrix.example.com,@openclaw:matrix.example.com"
# - name: MATRIX_HOMESERVER
# value: "http://synapse.matrix.svc.cluster.local:8008" # default
# - name: DEPLOYMENT_NAME
# value: "clawdbot" # default
# - name: DEPLOYMENT_NAMESPACE
# value: "clawdbot" # default
# - name: CRYPTO_STORE_PATH
# value: "/data/crypto_store" # default
# - name: ECHO_MODE
# value: "true" # default
volumeMounts:
- name: crypto-store
mountPath: /data/crypto_store
resources:
requests:
cpu: 50m
memory: 64Mi
limits:
cpu: 500m
memory: 128Mi
volumes:
- name: crypto-store
persistentVolumeClaim:
claimName: openclaw-toggle-operator-crypto
Installation
From PyPI
pip install openclaw-k8s-toggle-operator
From source
git clone https://github.com/vroomfondel/openclaw-k8s-toggle-operator.git
cd openclaw-k8s-toggle-operator
make venv
source .venv/bin/activate
pip install .
Docker
docker build -t openclaw-k8s-toggle-operator .
Or via Makefile:
make docker
Multi-arch build script
build-container-multiarch.sh builds and pushes multi-arch images (amd64 + arm64).
./build-container-multiarch.sh # login + full multi-arch build & push
./build-container-multiarch.sh onlylocal # login + local-only build (no push)
./build-container-multiarch.sh login # Docker Hub login only
Usage
# Run directly
openclaw-k8s-toggle-operator
# Or via Python module
python -m openclaw_k8s_toggle_operator
Connectivity test
Test Matrix homeserver connectivity without starting the full operator (useful as a pre-flight check or container readiness probe):
# Via console script (after pip install)
openclaw-k8s-toggle-operator-conntest
# Via Python module
python -m openclaw_k8s_toggle_operator conntest
Exits 0 on successful login, 1 on failure. Only tests Matrix — does not require in-cluster K8s access.
Scripts
repo_scripts/blurimage.py — OCR-based screenshot redaction
Blurs sensitive text in terminal/K9s screenshots using Tesseract OCR. Designed for redacting secrets, usernames, session IDs, device IDs, and other sensitive information before sharing screenshots.
Features:
- Multi-pass OCR with three preprocessing strategies (weighted grayscale, max-channel, blue-channel) to handle colored terminal text on dark backgrounds
- OTSU thresholding for clean black/white separation instead of naive inversion
- Upscaling (default 2x) for better recognition of small monospaced fonts
- Two-level matching: word-level (single tokens) and line-level (multi-word patterns), with targeted blurring that only redacts the matched words, not entire lines
- Literal and regex patterns:
--blurfor case-insensitive literal phrases,--blur-regexfor case-sensitive regex patterns
Dependencies: tesseract-ocr (system), pytesseract + opencv-python (auto-installed)
Usage:
# Blur usernames, domain, client secret, session IDs, and device IDs
python repo_scripts/blurimage.py \
--blur matrixadmin henning elasticc.io \
--blur-regex "rVFe\S+" "session id \S+" "[A-Z]{8,}" \
screenshot.png
# Debug mode — show what Tesseract detects
python repo_scripts/blurimage.py --debug --blur myuser screenshot.png
# Skip inversion for light-background images
python repo_scripts/blurimage.py --no-invert --blur myuser screenshot.png
# Higher upscaling for very small text (slower)
python repo_scripts/blurimage.py --scale 3 --blur myuser screenshot.png
Pattern types:
| Argument | Case sensitivity | Description |
|---|---|---|
--blur |
case-insensitive | Literal phrases, auto-escaped for regex safety |
--blur-regex |
case-sensitive | Raw regex patterns (add (?i) in pattern for case-insensitive) |
| (hardcoded) | case-insensitive | PXL*, *.png, *.jpg, *.mp4, *.json filenames |
See the module docstring in repo_scripts/blurimage.py for a detailed explanation of the preprocessing pipeline, multi-pass OCR strategy, and two-level matching approach.
Development
Makefile targets
| Target | Description |
|---|---|
make venv |
Create virtualenv and install all dependencies |
make tests |
Run pytest |
make lint |
Format code with black (line length 120) |
make isort |
Sort imports with isort |
make tcheck |
Static type checking with mypy |
make commit-checks |
Run pre-commit hooks on all files |
make prepare |
Run tests + commit-checks |
make pypibuild |
Build sdist + wheel with hatch |
make pypipush |
Publish to PyPI with hatch |
make docker |
Build Docker image |
License
This project is licensed under the LGPL where applicable/possible — see LICENSE.md. Some files/parts may use other licenses: MIT | GPL | LGPL. Always check per‑file headers/comments.
Authors
- Repo owner (primary author)
- Additional attributions are noted inline in code comments
Acknowledgments
- Inspirations and snippets are referenced in code comments where appropriate.
⚠️ Note
This is a development/experimental project. For production use, review security settings, customize configurations, and test thoroughly in your environment. Provided "as is" without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose and noninfringement. In no event shall the authors or copyright holders be liable for any claim, damages or other liability, whether in an action of contract, tort or otherwise, arising from, out of or in connection with the software or the use or other dealings in the software. Use at your own risk.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file openclaw_k8s_toggle_operator-0.0.19.tar.gz.
File metadata
- Download URL: openclaw_k8s_toggle_operator-0.0.19.tar.gz
- Upload date:
- Size: 18.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: Hatch/1.16.3 cpython/3.14.3 HTTPX/0.28.1
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ca57f6c9383c051d78ba9148d300c837461bf2e573807f75844a985351a0600b
|
|
| MD5 |
f34c6503a1132865b6f5121dce01294a
|
|
| BLAKE2b-256 |
4cf10f6e5d2d4b7efa53a52d4eca962cf4a2ea436f3c35a66f08fbd7e12a5667
|
File details
Details for the file openclaw_k8s_toggle_operator-0.0.19-py3-none-any.whl.
File metadata
- Download URL: openclaw_k8s_toggle_operator-0.0.19-py3-none-any.whl
- Upload date:
- Size: 19.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: Hatch/1.16.3 cpython/3.14.3 HTTPX/0.28.1
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
3a077aff56ac8027e8183910bbe818bb28dca28fd4f921023a4315fb84b6ced1
|
|
| MD5 |
c13aa46ecf339ac8f86473abea2c1aef
|
|
| BLAKE2b-256 |
9d00d57e131e96eca099df5c32fd81aec220a7e94a43b9c589574406cc9f4d6d
|