diff --git a/kubernetes/apps/authentik/blueprints-configmap.yaml b/kubernetes/apps/authentik/blueprints-configmap.yaml index c26f8f6d..21973996 100644 --- a/kubernetes/apps/authentik/blueprints-configmap.yaml +++ b/kubernetes/apps/authentik/blueprints-configmap.yaml @@ -228,6 +228,84 @@ data: # keeps a single session store for the gate's flow. providers: [] + # Synology DSM (nas-1) LDAP integration. DSM's Directory Client + # binds to the LDAP Outpost via the standalone LoadBalancer Service + # defined in ldap-outpost-lb.yaml (lb-pool: bgp → 10.2.99.x advertised + # to UDM over BGP, reachable from the mgmt LAN). Access is gated by + # PolicyBindings on the `synology` Application — only members of + # `Synology Users` or `Synology Admins` may bind. The provider shares + # the base DN (`DC=authentik,DC=renner,DC=uno`) and authorization + # flow (`ldap-authentication-flow`) with the existing LDAP Unifi / + # LDAP Jellyfin providers; the outpost dispatches based on which + # provider an Application policy admits the bind for. UID/GID start + # 2000/4000 matches the homelab convention so synthetic POSIX IDs + # stay consistent across all consumers (relevant for SMB ACL + # ownership on nas-1's shares). + - id: synology-users-group + model: authentik_core.group + identifiers: + name: Synology Users + state: present + attrs: + is_superuser: false + + - id: synology-admins-group + model: authentik_core.group + identifiers: + name: Synology Admins + state: present + attrs: + is_superuser: false + + - id: ldap-synology-provider + model: authentik_providers_ldap.ldapprovider + identifiers: + name: LDAP Synology + state: present + attrs: + authorization_flow: !Find [authentik_flows.flow, [slug, ldap-authentication-flow]] + invalidation_flow: !Find [authentik_flows.flow, [slug, default-invalidation-flow]] + base_dn: DC=authentik,DC=renner,DC=uno + bind_mode: direct + search_mode: direct + uid_start_number: 2000 + gid_start_number: 4000 + mfa_support: false + + - id: synology-application + model: authentik_core.application + identifiers: + slug: synology + state: present + attrs: + name: Synology DSM (nas-1) + slug: synology + provider: !KeyOf ldap-synology-provider + meta_launch_url: https://nas-1.renner.uno + meta_description: "Directory authentication for Synology DSM on nas-1" + + - model: authentik_policies.policybinding + identifiers: + target: !KeyOf synology-application + group: !KeyOf synology-users-group + order: 0 + state: present + attrs: + enabled: true + negate: false + failure_result: false + + - model: authentik_policies.policybinding + identifiers: + target: !KeyOf synology-application + group: !KeyOf synology-admins-group + order: 1 + state: present + attrs: + enabled: true + negate: false + failure_result: false + # `Local Radius` carried `kubernetes_namespace: default` in its # config from the Proxmox-era data, which the chart Role has no # permissions for. Pin spawned outposts at `authentik` so the @@ -257,6 +335,7 @@ data: providers: - !Find [authentik_providers_ldap.ldapprovider, [name, "LDAP Unifi"]] - !Find [authentik_providers_ldap.ldapprovider, [name, "LDAP Jellyfin"]] + - !KeyOf ldap-synology-provider config: log_level: info authentik_host: "http://authentik-server.authentik.svc.cluster.local" diff --git a/kubernetes/apps/authentik/kustomization.yaml b/kubernetes/apps/authentik/kustomization.yaml index 4a9efcf7..5f95a105 100644 --- a/kubernetes/apps/authentik/kustomization.yaml +++ b/kubernetes/apps/authentik/kustomization.yaml @@ -7,3 +7,5 @@ resources: - paperless-gate-ingress.yaml - paperless-gate-egress.yaml - outpost-servicemonitors.yaml + - ldap-outpost-lb.yaml + - ldap-outpost-cnp.yaml diff --git a/kubernetes/apps/authentik/ldap-outpost-cnp.yaml b/kubernetes/apps/authentik/ldap-outpost-cnp.yaml new file mode 100644 index 00000000..b9cd5f8a --- /dev/null +++ b/kubernetes/apps/authentik/ldap-outpost-cnp.yaml @@ -0,0 +1,33 @@ +--- +# Allow mgmt-LAN clients (notably nas-1 at 10.2.40.50 / fd40::50) to +# bind to the LDAP outpost via the LoadBalancer Service in +# ldap-outpost-lb.yaml. The namespace's default-deny-ingress CNP only +# admits traffic from named namespaces + host/remote-node entities; +# external LAN clients land in Cilium's `world` identity which the +# default policy blocks. This policy additively allows :389/TCP from +# the mgmt-LAN CIDRs to the LDAP outpost pods only — does not widen +# anything else in the namespace. +# +# Source CIDR is the mgmt LAN (10.2.40.0/24 + fd40::/64) where nas-1 +# and other LAN clients live. We deliberately do NOT open this to +# `world` — the BGP LB pool is technically reachable from any peer +# but LDAP binds carry plaintext credentials and must stay on the +# trusted segment until LDAPS is wired. +apiVersion: cilium.io/v2 +kind: CiliumNetworkPolicy +metadata: + name: ldap-outpost-lan-ingress + namespace: authentik +spec: + endpointSelector: + matchLabels: + app.kubernetes.io/name: authentik-outpost-ldap + goauthentik.io/outpost-name: ldap-outpost + ingress: + - fromCIDR: + - 10.2.40.0/24 + - fd40::/64 + toPorts: + - ports: + - port: "3389" + protocol: TCP diff --git a/kubernetes/apps/authentik/ldap-outpost-lb.yaml b/kubernetes/apps/authentik/ldap-outpost-lb.yaml new file mode 100644 index 00000000..02b15bc3 --- /dev/null +++ b/kubernetes/apps/authentik/ldap-outpost-lb.yaml @@ -0,0 +1,57 @@ +--- +# Standalone LoadBalancer Service exposing the LDAP outpost pods on the +# BGP-advertised LB pool (10.2.99.x / fd99::x). Reachable from the mgmt +# LAN (10.2.40.0/24 / fd40::/64) via UDM-learned BGP routes — the LB IPs +# live on no device VLAN. +# +# Why standalone instead of mutating the operator-owned +# `ak-outpost-ldap-outpost` Service: the Authentik operator periodically +# reconciles its own Service from the LDAPOutpost record, which would +# stomp on any in-place edits to type/labels. Same workaround as the +# RADIUS outpost (standalone headless Service for upstream metrics-port +# bug). Selector targets the operator-managed Deployment pods directly +# via the labels Authentik stamps on them. +# +# Port mapping: container listens on 3389/TCP (LDAP) and 6636/TCP +# (LDAPS) internally; we only publish 389 here because LDAPS would +# require pinning a stable cert on the LDAPProvider — currently the +# outpost self-signs at startup, which DSM's Directory Client cannot +# trust across restarts. The CNP in ldap-outpost-cnp.yaml restricts the +# source CIDR to the mgmt LAN so the cleartext bind stays on a trusted +# segment. LDAPS is a follow-up once a stable cert is wired. +apiVersion: v1 +kind: Service +metadata: + name: ldap-outpost-lb + namespace: authentik + labels: + lb-pool: bgp + annotations: + # Pinned to .88 / fd99::88 (next free in the .80-.89 infra range + # alongside traefik .86, ha .87, syslog .84, syncthing .85). Pinning + # matters for LDAP because DSM's Directory Client caches DNS very + # aggressively — a Cilium IPAM re-allocation after a churn event + # would silently break bind until DSM's cache TTL expires. + lbipam.cilium.io/ips: "10.2.99.88,fd99::88" + # LAN-side DNS only. external-dns-unifi (sources: ingress, service, + # traefik-proxy) publishes this record to the UDM split-horizon + # zone. The Cloudflare external-dns instance only watches `ingress`, + # so this Service produces no public record — LDAP stays + # LAN-internal, which matches the threat model of the cleartext + # bind on 389. + external-dns.alpha.kubernetes.io/hostname: ldap.renner.uno +spec: + type: LoadBalancer + externalTrafficPolicy: Local + ipFamilyPolicy: PreferDualStack + ipFamilies: + - IPv4 + - IPv6 + selector: + app.kubernetes.io/name: authentik-outpost-ldap + goauthentik.io/outpost-name: ldap-outpost + ports: + - name: ldap + port: 389 + targetPort: 3389 + protocol: TCP