#! Copyright 2021 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
  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=pinninpeds,ou=groups,dc=pinniped,dc=dev
  member: cn=olive,ou=users,dc=pinniped,dc=dev
#@ 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: projects.registry.vmware.com/pinniped/test-ldap:latest
          imagePullPolicy: Always
          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
