#! Copyright 2021-2023 the Pinniped contributors. All Rights Reserved. #! SPDX-License-Identifier: Apache-2.0 #@ load("@ytt:data", "data") #@ load("@ytt:sha256", "sha256") #@ load("@ytt:yaml", "yaml") #@ def ldapLIDIF(): #@yaml/text-templated-strings ldap.ldif: | # ** CAUTION: Blank lines separate entries in the LDIF format! Do not remove them! *** # Here's a good explanation of LDIF: # https://www.digitalocean.com/community/tutorials/how-to-use-ldif-files-to-make-changes-to-an-openldap-system # pinniped.dev (organization, root) dn: dc=pinniped,dc=dev objectClass: dcObject objectClass: organization dc: pinniped o: example # users, pinniped.dev (organization unit) dn: ou=users,dc=pinniped,dc=dev objectClass: organizationalUnit ou: users # groups, pinniped.dev (organization unit) dn: ou=groups,dc=pinniped,dc=dev objectClass: organizationalUnit ou: groups # beach-groups, groups, pinniped.dev (organization unit) dn: ou=beach-groups,ou=groups,dc=pinniped,dc=dev objectClass: organizationalUnit ou: beach-groups # pinny, users, pinniped.dev (user) dn: cn=pinny,ou=users,dc=pinniped,dc=dev objectClass: inetOrgPerson objectClass: posixAccount objectClass: shadowAccount cn: pinny sn: Seal givenName: Pinny the 🦭 mail: pinny.ldap@example.com userPassword: (@= data.values.pinny_ldap_password @) uid: pinny uidNumber: 1000 gidNumber: 1000 homeDirectory: /home/pinny loginShell: /bin/bash gecos: pinny-the-seal # wally, users, pinniped.dev (user without password) dn: cn=wally,ou=users,dc=pinniped,dc=dev objectClass: inetOrgPerson objectClass: posixAccount objectClass: shadowAccount cn: wally sn: Walrus givenName: Wally mail: wally.ldap@example.com mail: wally.alternate@example.com uid: wally uidNumber: 1001 gidNumber: 1001 homeDirectory: /home/wally loginShell: /bin/bash gecos: wally-the-walrus # olive, users, pinniped.dev (user without password) dn: cn=olive,ou=users,dc=pinniped,dc=dev objectClass: inetOrgPerson objectClass: posixAccount objectClass: shadowAccount cn: olive sn: Boston Terrier givenName: Olive mail: olive.ldap@example.com uid: olive uidNumber: 1002 gidNumber: 1002 homeDirectory: /home/olive loginShell: /bin/bash gecos: olive-the-dog # ball-game-players, beach-groups, groups, pinniped.dev (group of users) dn: cn=ball-game-players,ou=beach-groups,ou=groups,dc=pinniped,dc=dev cn: ball-game-players objectClass: groupOfNames member: cn=pinny,ou=users,dc=pinniped,dc=dev member: cn=olive,ou=users,dc=pinniped,dc=dev # seals, groups, pinniped.dev (group of users) dn: cn=seals,ou=groups,dc=pinniped,dc=dev cn: seals objectClass: groupOfNames member: cn=pinny,ou=users,dc=pinniped,dc=dev # walruses, groups, pinniped.dev (group of users) dn: cn=walruses,ou=groups,dc=pinniped,dc=dev cn: walruses objectClass: groupOfNames member: cn=wally,ou=users,dc=pinniped,dc=dev # pinnipeds, users, pinniped.dev (group of groups) dn: cn=pinnipeds,ou=groups,dc=pinniped,dc=dev cn: pinnipeds objectClass: groupOfNames member: cn=seals,ou=groups,dc=pinniped,dc=dev member: cn=walruses,ou=groups,dc=pinniped,dc=dev # mammals, groups, pinniped.dev (group of both groups and users) dn: cn=mammals,ou=groups,dc=pinniped,dc=dev cn: mammals objectClass: groupOfNames member: cn=pinnipeds,ou=groups,dc=pinniped,dc=dev member: cn=olive,ou=users,dc=pinniped,dc=dev # ball-game-players group again, but this time defined as a posixGroup dn: cn=ball-game-players-posix,ou=groups,dc=pinniped,dc=dev objectClass: posixGroup objectClass: top cn: ball-game-players-posix gidNumber: 1001 memberUid: pinny memberUid: olive # seals group again, but this time defined as a posixGroup dn: cn=seals-posix,ou=groups,dc=pinniped,dc=dev objectClass: posixGroup objectClass: top cn: seals-posix gidNumber: 1002 memberUid: pinny # walruses group again, but this time defined as a posixGroup dn: cn=walruses-posix,ou=groups,dc=pinniped,dc=dev objectClass: posixGroup objectClass: top cn: walruses-posix gidNumber: 1002 memberUid: wally #@ end --- apiVersion: v1 kind: Secret metadata: name: ldap-ldif-files namespace: tools type: Opaque stringData: #@ ldapLIDIF() --- apiVersion: v1 kind: Secret metadata: name: ldap-server-config-before-ldif-files namespace: tools type: Opaque stringData: server-config.ldif: | # Load the memberof module. dn: cn=module,cn=config cn: module objectClass: olcModuleList objectClass: top olcModulePath: /opt/bitnami/openldap/lib/openldap olcModuleLoad: memberof dn: olcOverlay={0}memberof,olcDatabase={2}hdb,cn=config objectClass: olcConfig objectClass: olcMemberOf objectClass: olcOverlayConfig objectClass: top olcOverlay: memberof olcMemberOfDangling: ignore olcMemberOfRefInt: TRUE olcMemberOfGroupOC: groupOfNames olcMemberOfMemberAD: member # Load the refint module. dn: cn=module,cn=config cn: module objectclass: olcModuleList objectclass: top olcmodulepath: /opt/bitnami/openldap/lib/openldap olcmoduleload: refint dn: olcOverlay={1}refint,olcDatabase={2}hdb,cn=config objectClass: olcConfig objectClass: olcOverlayConfig objectClass: olcRefintConfig objectClass: top olcOverlay: {1}refint olcRefintAttribute: memberof member manager owner --- apiVersion: v1 kind: Secret metadata: name: ldap-server-config-after-ldif-files namespace: tools type: Opaque stringData: server-config.ldif: | # Reject any further connections that do not use TLS or StartTLS dn: olcDatabase={2}hdb,cn=config changetype: modify add: olcSecurity olcSecurity: tls=1 --- apiVersion: apps/v1 kind: Deployment metadata: name: ldap namespace: tools labels: app: ldap spec: replicas: 1 selector: matchLabels: app: ldap template: metadata: labels: app: ldap annotations: #! Cause the pod to get recreated whenever the LDIF file changes. ldifConfigHash: #@ sha256.sum(yaml.encode(ldapLIDIF())) spec: containers: - name: ldap #! Use our own fork of docker.io/bitnami/openldap for now, because we added the #! LDAP_SERVER_CONFIG_BEFORE_CUSTOM_LDIF_DIR and LDAP_SERVER_CONFIG_AFTER_CUSTOM_LDIF_DIR options. #! See https://github.com/pinniped-ci-bot/bitnami-docker-openldap/tree/pinniped image: #@ data.values.ldap_image imagePullPolicy: IfNotPresent ports: - name: ldap containerPort: 1389 - name: ldaps containerPort: 1636 resources: requests: cpu: "100m" #! one-tenth of one CPU memory: "64Mi" limits: #! Do not limit CPU because it was causing issues running integration tests on AKS where openldap became very slow. memory: "64Mi" readinessProbe: tcpSocket: port: ldap initialDelaySeconds: 2 timeoutSeconds: 90 periodSeconds: 2 failureThreshold: 9 env: #! Example ldapsearch commands that can be run from within the container based on these env vars. #! These will print the whole LDAP tree starting at our root. #! Using StartTLS (-ZZ) on the ldap port... #! LDAPTLS_CACERT=/var/certs/ca.pem ldapsearch -x -ZZ -H 'ldap://ldap.tools.svc.cluster.local' -D 'cn=admin,dc=pinniped,dc=dev' -w password -b 'dc=pinniped,dc=dev' #! Using ldaps... #! LDAPTLS_CACERT=/var/certs/ca.pem ldapsearch -x -H 'ldaps://ldap.tools.svc.cluster.local' -D 'cn=admin,dc=pinniped,dc=dev' -w password -b 'dc=pinniped,dc=dev' #! Note that the memberOf attribute is special and not returned by default. It must be specified as one of attributes to return in the search, e.g.: #! LDAPTLS_CACERT=/var/certs/ca.pem ldapsearch -x -H 'ldaps://ldap.tools.svc.cluster.local' -D 'cn=admin,dc=pinniped,dc=dev' -w password -b 'dc=pinniped,dc=dev' cn uidNumber mail member memberOf #! This should fail and report "TLS confidentiality required" because we require TLS and this does not use TLS or StartTLS... #! ldapsearch -x -H 'ldap://ldap.tools.svc.cluster.local' -D 'cn=admin,dc=pinniped,dc=dev' -w password -b 'dc=pinniped,dc=dev' - name: BITNAMI_DEBUG value: "true" - name: LDAP_ADMIN_USERNAME value: "admin" - name: LDAP_ADMIN_PASSWORD value: "password" #! ok to hardcode: the LDAP server will not be available from outside the cluster - name: LDAP_ENABLE_TLS value: "yes" - name: LDAP_TLS_CERT_FILE value: "/var/certs/ldap.pem" - name: LDAP_TLS_KEY_FILE value: "/var/certs/ldap-key.pem" - name: LDAP_TLS_CA_FILE value: "/var/certs/ca.pem" #! Note that the custom LDIF file is only read at pod start-up time. - name: LDAP_CUSTOM_LDIF_DIR value: "/var/ldifs" - name: LDAP_SERVER_CONFIG_BEFORE_CUSTOM_LDIF_DIR value: "/var/server-config-before-ldifs" - name: LDAP_SERVER_CONFIG_AFTER_CUSTOM_LDIF_DIR value: "/var/server-config-after-ldifs" #! Seems like LDAP_ROOT is still required when using LDAP_CUSTOM_LDIF_DIR because it effects the admin user. #! Presumably this needs to match the root that we create in the LDIF file. - name: LDAP_ROOT value: "dc=pinniped,dc=dev" volumeMounts: - name: certs mountPath: /var/certs readOnly: true - name: ldifs mountPath: /var/ldifs readOnly: true - name: server-config-before-ldifs mountPath: /var/server-config-before-ldifs readOnly: true - name: server-config-after-ldifs mountPath: /var/server-config-after-ldifs readOnly: true volumes: - name: certs secret: secretName: certs - name: ldifs secret: secretName: ldap-ldif-files - name: server-config-before-ldifs secret: secretName: ldap-server-config-before-ldif-files - name: server-config-after-ldifs secret: secretName: ldap-server-config-after-ldif-files --- apiVersion: v1 kind: Service metadata: name: ldap namespace: tools labels: app: ldap spec: type: ClusterIP selector: app: ldap ports: - protocol: TCP port: 389 targetPort: 1389 name: ldap - protocol: TCP port: 636 targetPort: 1636 name: ldaps --- apiVersion: v1 kind: Service metadata: name: ldaps namespace: tools labels: app: ldap spec: type: ClusterIP selector: app: ldap ports: - protocol: TCP port: 636 targetPort: 1636 name: ldaps --- apiVersion: v1 kind: Service metadata: name: ldapstarttls namespace: tools labels: app: ldap spec: type: ClusterIP selector: app: ldap ports: - protocol: TCP port: 389 targetPort: 1389 name: ldap