Configure Grafana to use the K8s-internal Authentik instance for
OAuth2 login. Infrastructure Admins group gets Admin role, others Viewer.
Client credentials stored in SOPS-encrypted secret.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The nginx egress policy only allowed traffic to the apps namespace.
Authentik is now in its own namespace, so nginx couldn't proxy to it.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Internal-only test ingress (no Cloudflare proxy) for validating
K8s Authentik before DNS cutover.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The chart ingress still has auth.k.renner.uno from a prior deploy.
Need to let the HelmRelease upgrade remove it first, then add the
standalone test ingress in a follow-up commit.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Revert Cloudflared wildcard route - k.renner.uno is internal only.
Create separate ingress for auth.k.renner.uno without cloudflare-proxied
annotation so only UniFi ExternalDNS picks it up (A record to VIP).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add wildcard Cloudflared route for *.k.renner.uno to test K8s services
before doing DNS cutover from old infrastructure. Add auth.k.renner.uno
as additional host on Authentik ingress.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Only grant access that is currently needed. Database and monitoring
namespace access was added speculatively - removed per least-privilege.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The CNPG operator in the database namespace needs to reach port 8000
on PostgreSQL pods for status extraction. Without this, it cannot
determine cluster health or create replica instances.
Also allow monitoring namespace for Prometheus scraping.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
K8s injects AUTHENTIK_DB_RW_*, AUTHENTIK_SERVER_* env vars because
services are named authentik-*. Authentik parses ALL AUTHENTIK_*
env vars and crashes. Rename DB cluster to ak-pg to avoid collision.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The Authentik config parser fails with 'Attr has no get' when
secrets are passed via nested value fields. Use global.env with
secretKeyRef for PostgreSQL password, secret key, and SMTP password.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
CloudNativePG PostgreSQL (2 replicas), Authentik Helm chart with
2 server + 2 worker replicas. Same secret key as existing deployment.
SOPS secrets, Cloudflared tunnel route, ingress with TLS.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The UniFi ExternalDNS is in infrastructure but references the
HelmRepository from apps/external-dns which deploys later.
Duplicate the HelmRepository definition.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Automates the full cluster bootstrap. Usage: GITEA_TOKEN=xxx ./kubernetes/bootstrap.sh
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Use the official owncloud/ocis-charts Helm chart from Git instead
of custom manifests. The chart handles NATS, service discovery,
collaboration, and all internal wiring correctly.
- FluxCD GitRepository source for the chart
- ONLYOFFICE deployed as a simple Deployment + Service
- Minimal values: domain, ingress, and ONLYOFFICE integration
- Chart manages its own secrets, NATS, and service registry
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
ONLYOFFICE IP filter rejects pod CIDR despite being in the allow
list (nginx proxy changes source IP). Disable IP filter and rely
on Cilium network policies to restrict access to ONLYOFFICE.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
ONLYOFFICE JWT blocks the WOPI discovery endpoint (403). The oCIS
collaboration service doesn't send JWT for discovery requests.
Security model (same as official oCIS example):
- ONLYOFFICE IP filter: only pod CIDR (10.244.0.0/16) allowed
- WOPI tokens signed by oCIS for browser<->collaboration auth
- Cilium network policies restrict pod-to-pod traffic
- External access only via ingress-nginx with CrowdSec
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Shared WOPI JWT secret (SOPS encrypted) for both collaboration
service and ONLYOFFICE. IP filter restricted to pod CIDR.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Only allow WOPI requests from the pod network (10.244.0.0/16).
External requests to ONLYOFFICE are blocked by the IP filter.
JWT remains disabled (same as official oCIS example) — security
is enforced by network policies and IP filtering instead.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Mounting to local.json or local.dist.json breaks ONLYOFFICE startup.
Instead, use a postStart lifecycle hook to patch the generated
local.json after startup to set wopi.host and IP filter.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
local.dist.json gets merged with defaults which overwrites the wopi.host.
Mount directly to local.json which takes final precedence.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Set COLLABORATION_APP_ADDR to internal ONLYOFFICE Service URL for
backend WOPI discovery. Configure ONLYOFFICE local.json with
wopi.host=https://office.renner.uno so the discovery XML response
contains the public URL for browser iframes.
Revert Cilium socketLB hairpin — no longer needed.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Known Cilium issue: pods can't reach L2 LoadBalancer VIPs.
Setting socketLB.hostNamespaceOnly=false enables socket-level
load balancing in pod network namespaces, allowing pods to
access the LB VIP from within the cluster.
Also removes hostAliases workaround and broken CoreDNS config.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Pods can't reach the L2 VIP (10.2.40.80) from inside the cluster
(hairpin issue). Use hostAliases to resolve *.renner.uno hostnames
to the ingress-nginx ClusterIP instead.
Also allow apps→ingress traffic in ingress namespace network policy.
TODO: Replace hardcoded ClusterIP with proper CoreDNS rewrite.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
oCIS registers services in NATS using the pod hostname. In Docker
Compose this was 'ocis'. In K8s it's a random pod name. Setting
hostname: ocis makes the registration consistent.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
OCIS_REVA_GATEWAY was being used as a NATS service lookup key, not
a direct address. The gateway is registered in NATS by the oCIS
server and should be discovered automatically.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
oCIS server binds gateway to 0.0.0.0:9142. Collaboration service
uses OCIS_REVA_GATEWAY for gateway connection. Removed redundant
GATEWAY_GRPC_ADDR from collaboration.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Cluster-internal communication should stay internal, not go through
the external VIP. ONLYOFFICE WOPI discovery uses the internal
Service URL. The browser iframe URL is handled separately by the
WOPI response and ingress routing.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Pods inside the cluster can't reach the L2 LoadBalancer VIP because
Cilium doesn't handle hairpin traffic by default. Enabling socketLB
allows pods to access LB VIPs from within the cluster.
This fixes the collaboration service failing to reach office.renner.uno
(which resolves to the ingress L2 VIP).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Apps need to reach the ingress VIP (for OIDC, WOPI discovery via
public URLs). Allow egress to ingress namespace, DNS, host, and world.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The root cause of the 403 errors: ONLYOFFICE has an IP filter in
local.json that only allows connections from specific hostnames.
Add a permissive filter via ConfigMap.
Also add OCIS_URL to the collaboration service (was in the official
example but missing from our config).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
K8s auto-injects OWNCLOUD_SERVICE_HOST, OWNCLOUD_PORT etc. because
the Service is named 'owncloud'. oCIS parses OWNCLOUD_* env vars
for its own configuration, causing conflicts with NATS registry
and service discovery.
enableServiceLinks: false prevents this injection.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The collaboration service needs the gateway address to register
with the app provider system.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
COLLABORATION_APP_ADDR is used in the browser iframe. Must be the
public URL (office.renner.uno), not the internal K8s Service URL.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
oCIS needs this env var to read the custom CSP config file.
Without it, it uses built-in defaults that block Authentik OIDC.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Config PVC is shared between oCIS and collaboration pods. RBD
(ReadWriteOnce) can't be attached to multiple nodes. CephFS
supports ReadWriteMany.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
ONLYOFFICE WOPI callbacks need resolvable hostnames. Split into:
- owncloud (oCIS server + NATS + gateway)
- ocis-collaboration (WOPI bridge between oCIS and ONLYOFFICE)
- onlyoffice (document server)
Each has its own Service for DNS-based discovery. Communication:
- Collaboration → oCIS via owncloud:9233 (NATS) and owncloud:9142 (gateway)
- Collaboration → ONLYOFFICE via onlyoffice:80 (WOPI discovery)
- ONLYOFFICE → Collaboration via ocis-collaboration:9300 (WOPI callbacks)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Collaboration service tried to reach ONLYOFFICE via the external
URL through the ingress VIP, which is blocked by network policy.
Use localhost:80 since they're in the same pod.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The collaboration service is not part of 'ocis server' — it runs
as a separate process. Add it as a third container in the pod
with proper WOPI config pointing to localhost for same-pod
communication with ONLYOFFICE.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add ONLYOFFICE Document Server as a sidecar container with:
- WOPI integration with oCIS collaboration service
- Separate service and ingress for office.renner.uno
- Cloudflared tunnel route for external access
- JWT disabled (WOPI handles auth)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
ingress-nginx needs API access for leader election, Ingress
watching, and secret/configmap reading. Add kube-apiserver entity.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Init only runs on first boot. Subsequent restarts skip init since
the config is persisted on the PVC.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
ocis init --insecure yes skips the interactive prompt and generates
config without internal TLS (handled by ingress-nginx). The runtime
does NOT use OCIS_INSECURE — all TLS validation is enabled.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Remove shell wrapper, use ocis init directly. Set fsGroup=1000
so PVC mounts are writable by the oCIS user.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Let oCIS generate internal certificates. Answer 'no' to the
insecure prompt during init. TLS termination handled by ingress.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>