CI / CI (push) Successful in 15m28s
## Problem JWT validation failing with `error decoding response body: expected value at line 6 column 1`. Root cause: `AUTHENTIK_ISSUER` pointed to slug `poimen-memory` which returns 404 on OIDC discovery. Slug was renamed to `poimen` in Authentik. Secondary issue: auth env vars were set via `kubectl set env` (not in git), so every ArgoCD sync reverted them. ## Changes - **k8s/app/config.yaml** — ConfigMap for non-sensitive env (auth mode, rate limits, OpenSearch/Obsidian URLs) - **k8s/app/auth.enc.yaml** — SOPS-encrypted Secret with `AUTHENTIK_ISSUER`, `AUTHENTIK_AUDIENCE`, `JWT_CACHE_TTL_SECS` - **k8s/app/secret-generator.yaml** — KSOPS generator for ArgoCD decryption - **k8s/app/deployment.yaml** — `envFrom` referencing ConfigMap + Secret - **k8s/app/kustomization.yaml** — Added config.yaml + KSOPS generator - **k8s/app/opensearch-deployment.yaml** — Updated JWKS/issuer URLs to `poimen` slug ## Rollout Reloader (`--auto-reload-all=true`) triggers rolling restart when ConfigMap/Secret change. Merge and ArgoCD sync handles everything.Reviewed-on: rock/poimen-memory#40 Co-authored-by: rock <[email protected]>
385 lines
10 KiB
YAML
385 lines
10 KiB
YAML
---
|
|
# OpenSearch Security Config
|
|
apiVersion: v1
|
|
kind: ConfigMap
|
|
metadata:
|
|
name: opensearch-config
|
|
namespace: poimen
|
|
labels:
|
|
app: opensearch
|
|
data:
|
|
opensearch.yml: |
|
|
cluster.name: poimen-memory
|
|
node.name: ${HOSTNAME}
|
|
discovery.seed_hosts: "opensearch-0.opensearch,opensearch-1.opensearch"
|
|
cluster.initial_master_nodes: "opensearch-0,opensearch-1"
|
|
|
|
# Security Plugin Configuration
|
|
plugins:
|
|
security:
|
|
ssl:
|
|
transport:
|
|
pemcert_filepath: certs/node.pem
|
|
pemkey_filepath: certs/node-key.pem
|
|
pemtrustedcas_filepath: certs/root-ca.pem
|
|
enforce_hostname_verification: false
|
|
http:
|
|
enabled: true
|
|
pemcert_filepath: certs/node.pem
|
|
pemkey_filepath: certs/node-key.pem
|
|
pemtrustedcas_filepath: certs/root-ca.pem
|
|
|
|
# JWT Authentication Realm
|
|
authcz:
|
|
admin_dn:
|
|
- CN=admin,OU=admin,O=admin,L=admin,ST=admin,C=admin
|
|
authc:
|
|
realms:
|
|
jwt_realm:
|
|
type: jwt
|
|
order: 1
|
|
http_enabled: true
|
|
transport_enabled: false
|
|
description: "JWT realm for Authentik integration"
|
|
|
|
# Token location and format
|
|
token_name: Authorization
|
|
token_extractor: "Bearer " # Extract token after "Bearer "
|
|
|
|
# JWT signing configuration
|
|
jwt_header: "Authorization"
|
|
jwt_url_parameter: null
|
|
roles_key: "roles"
|
|
subject_key: "sub"
|
|
|
|
# JWKS endpoint from Authentik
|
|
jwks_uri: "https://authentik.riotpiao.com/application/o/poimen/jwks/"
|
|
jwks_refresh_interval_ms: 3600000 # 1 hour
|
|
|
|
# Issuer validation
|
|
issuer: "https://authentik.riotpiao.com/application/o/poimen/"
|
|
audience: null
|
|
|
|
# Claims mapping
|
|
enable_ssl_peer_hostname_verification: false
|
|
skip_jwt_verification: false
|
|
|
|
backends:
|
|
internal_authc_backend:
|
|
type: intern
|
|
|
|
# Role-based access control
|
|
roles_mapping:
|
|
all_access:
|
|
- "*"
|
|
own_index:
|
|
- "?kibana"
|
|
- "?opensearch-dashboards"
|
|
logstash:
|
|
- "logstash"
|
|
|
|
# Index-level permissions
|
|
roles:
|
|
all_access:
|
|
cluster_permissions:
|
|
- "*"
|
|
index_permissions:
|
|
- index_patterns:
|
|
- "*"
|
|
allowed_actions:
|
|
- "*"
|
|
tenant_permissions:
|
|
- tenant_patterns:
|
|
- "*"
|
|
allowed_actions:
|
|
- "*"
|
|
|
|
read_vault:
|
|
cluster_permissions:
|
|
- cluster:monitor/health
|
|
- indices:data/read/search
|
|
index_permissions:
|
|
- index_patterns:
|
|
- "vault-*"
|
|
allowed_actions:
|
|
- "indices:data/read/search"
|
|
- "indices:data/read/get"
|
|
tenant_permissions:
|
|
- tenant_patterns:
|
|
- "global_tenant"
|
|
allowed_actions:
|
|
- "kibana_all_read"
|
|
|
|
write_vault:
|
|
cluster_permissions:
|
|
- cluster:monitor/health
|
|
- indices:data/write/index
|
|
- indices:data/write/update
|
|
index_permissions:
|
|
- index_patterns:
|
|
- "vault-*"
|
|
allowed_actions:
|
|
- "indices:data/write/index"
|
|
- "indices:data/write/update"
|
|
- "indices:data/read/search"
|
|
tenant_permissions:
|
|
- tenant_patterns:
|
|
- "global_tenant"
|
|
allowed_actions:
|
|
- "kibana_all"
|
|
|
|
# Map JWT claims to OpenSearch internal users
|
|
authc_cache_enable: true
|
|
|
|
internal_users.yml: |
|
|
# Internal admin user (for bootstrapping)
|
|
admin:
|
|
hash: "$2y$12$K/SpwjtB.wW8u3/52l.f2OPST9/PgBkqquzi.Oi8KfRMfsKkCq3GO" # admin:admin123
|
|
reserved: true
|
|
backend_roles:
|
|
- "admin"
|
|
- "all_access"
|
|
attributes:
|
|
attribute1: "value1"
|
|
attribute2: "value2"
|
|
attribute3: "value3"
|
|
|
|
roles_mapping.yml: |
|
|
all_access:
|
|
reserved: false
|
|
users:
|
|
- "admin"
|
|
backend_roles:
|
|
- "*"
|
|
hosts:
|
|
- "*"
|
|
|
|
action_groups.yml: |
|
|
# Add standard action groups here
|
|
|
|
---
|
|
# OpenSearch StatefulSet
|
|
apiVersion: apps/v1
|
|
kind: StatefulSet
|
|
metadata:
|
|
name: opensearch
|
|
namespace: poimen
|
|
labels:
|
|
app: opensearch
|
|
spec:
|
|
serviceName: opensearch
|
|
replicas: 2
|
|
selector:
|
|
matchLabels:
|
|
app: opensearch
|
|
template:
|
|
metadata:
|
|
labels:
|
|
app: opensearch
|
|
spec:
|
|
affinity:
|
|
podAntiAffinity:
|
|
preferredDuringSchedulingIgnoredDuringExecution:
|
|
- weight: 100
|
|
podAffinityTerm:
|
|
labelSelector:
|
|
matchExpressions:
|
|
- key: app
|
|
operator: In
|
|
values:
|
|
- opensearch
|
|
topologyKey: kubernetes.io/hostname
|
|
|
|
initContainers:
|
|
- name: fix-permissions
|
|
image: busybox:1.28
|
|
command:
|
|
- sysctl
|
|
- -w
|
|
- vm.max_map_count=262144
|
|
securityContext:
|
|
privileged: true
|
|
|
|
containers:
|
|
- name: opensearch
|
|
image: opensearchproject/opensearch:2.11.0
|
|
|
|
env:
|
|
- name: OPENSEARCH_JAVA_OPTS
|
|
value: "-Xms2g -Xmx2g -XX:+AlwaysPreTouch -XX:+UseG1GC -XX:MaxGCPauseMillis=30"
|
|
- name: OPENSEARCH_INITIAL_ADMIN_PASSWORD
|
|
valueFrom:
|
|
secretKeyRef:
|
|
name: opensearch-secrets
|
|
key: admin-password
|
|
- name: DISABLE_SECURITY_PLUGIN
|
|
value: "false"
|
|
- name: OPENSEARCH_SECURITY_SSL_HTTP_ENABLED
|
|
value: "true"
|
|
|
|
ports:
|
|
- containerPort: 9200
|
|
name: http
|
|
protocol: TCP
|
|
- containerPort: 9300
|
|
name: node-comm
|
|
protocol: TCP
|
|
|
|
resources:
|
|
requests:
|
|
memory: "2Gi"
|
|
cpu: "500m"
|
|
limits:
|
|
memory: "4Gi"
|
|
cpu: "1000m"
|
|
|
|
livenessProbe:
|
|
httpGet:
|
|
path: /_cluster/health
|
|
port: 9200
|
|
scheme: HTTPS
|
|
initialDelaySeconds: 30
|
|
periodSeconds: 10
|
|
timeoutSeconds: 5
|
|
failureThreshold: 3
|
|
|
|
readinessProbe:
|
|
httpGet:
|
|
path: /_cluster/health
|
|
port: 9200
|
|
scheme: HTTPS
|
|
initialDelaySeconds: 10
|
|
periodSeconds: 5
|
|
timeoutSeconds: 5
|
|
failureThreshold: 3
|
|
|
|
volumeMounts:
|
|
- name: data
|
|
mountPath: /usr/share/opensearch/data
|
|
- name: config
|
|
mountPath: /usr/share/opensearch/config/opensearch.yml
|
|
subPath: opensearch.yml
|
|
- name: config
|
|
mountPath: /usr/share/opensearch/plugins/opensearch-security/securityconfig/internal_users.yml
|
|
subPath: internal_users.yml
|
|
- name: config
|
|
mountPath: /usr/share/opensearch/plugins/opensearch-security/securityconfig/roles_mapping.yml
|
|
subPath: roles_mapping.yml
|
|
- name: config
|
|
mountPath: /usr/share/opensearch/plugins/opensearch-security/securityconfig/action_groups.yml
|
|
subPath: action_groups.yml
|
|
|
|
volumes:
|
|
- name: config
|
|
configMap:
|
|
name: opensearch-config
|
|
|
|
volumeClaimTemplates:
|
|
- metadata:
|
|
name: data
|
|
spec:
|
|
accessModes:
|
|
- ReadWriteOnce
|
|
storageClassName: longhorn
|
|
resources:
|
|
requests:
|
|
storage: 30Gi
|
|
|
|
---
|
|
# OpenSearch Service (Headless for StatefulSet)
|
|
apiVersion: v1
|
|
kind: Service
|
|
metadata:
|
|
name: opensearch
|
|
namespace: poimen
|
|
labels:
|
|
app: opensearch
|
|
spec:
|
|
clusterIP: None # Headless service
|
|
selector:
|
|
app: opensearch
|
|
ports:
|
|
- port: 9200
|
|
targetPort: 9200
|
|
protocol: TCP
|
|
name: http
|
|
- port: 9300
|
|
targetPort: 9300
|
|
protocol: TCP
|
|
name: node-comm
|
|
publishNotReadyAddresses: true
|
|
|
|
---
|
|
# OpenSearch Internal Service (for direct access)
|
|
apiVersion: v1
|
|
kind: Service
|
|
metadata:
|
|
name: opensearch-internal
|
|
namespace: poimen
|
|
labels:
|
|
app: opensearch
|
|
spec:
|
|
type: ClusterIP
|
|
selector:
|
|
app: opensearch
|
|
ports:
|
|
- port: 9200
|
|
targetPort: 9200
|
|
protocol: TCP
|
|
name: http
|
|
|
|
---
|
|
# Secret for OpenSearch Admin Password
|
|
apiVersion: v1
|
|
kind: Secret
|
|
metadata:
|
|
name: opensearch-secrets
|
|
namespace: poimen
|
|
type: Opaque
|
|
stringData:
|
|
admin-password: "OpenSearch@Admin123!" # TODO: Change to secure password
|
|
|
|
---
|
|
# NetworkPolicy: Only Memory Service can access OpenSearch
|
|
apiVersion: networking.k8s.io/v1
|
|
kind: NetworkPolicy
|
|
metadata:
|
|
name: opensearch-access
|
|
namespace: poimen
|
|
spec:
|
|
podSelector:
|
|
matchLabels:
|
|
app: opensearch
|
|
policyTypes:
|
|
- Ingress
|
|
ingress:
|
|
- from:
|
|
- podSelector:
|
|
matchLabels:
|
|
app.kubernetes.io/name: poimen-memory
|
|
ports:
|
|
- protocol: TCP
|
|
port: 9200
|
|
|
|
---
|
|
# NetworkPolicy: OpenSearch can communicate internally
|
|
apiVersion: networking.k8s.io/v1
|
|
kind: NetworkPolicy
|
|
metadata:
|
|
name: opensearch-internal-comm
|
|
namespace: poimen
|
|
spec:
|
|
podSelector:
|
|
matchLabels:
|
|
app: opensearch
|
|
policyTypes:
|
|
- Ingress
|
|
ingress:
|
|
- from:
|
|
- podSelector:
|
|
matchLabels:
|
|
app: opensearch
|
|
ports:
|
|
- protocol: TCP
|
|
port: 9300
|