A stunnel to (Google) LDAP Pod

Sometimes you have an application that needs to speak with an LDAP server. And more specifically to Google’s LDAP service. Google’s LDAP requires certificates to connect to it (which you get from your Workspace console). You can integrate those certificates with your application if possible, or you can run stunnel as a proxy, as per Google’s instructions.

It is not that complex then to expand from those instructions to running a stunnel Pod as a proxy. And it happens that Chainguard maintain a stunnel image that can be of use to us. Thus we can now run an LDAP service which can proxy our queries to our (Google) LDAP service:

apiVersion: v1
kind: ConfigMap
metadata:
  name: stunnel-conf
data:
  stunnel.conf: |
    foreground = yes
    #debug = debug
    output = /dev/stdout
    [ldap]
    client = yes
    accept = 0.0.0.0:1389
    connect = ldap.google.com:636
    cert = /google-ldap/tls.crt
    key = /google-ldap/tls.key
---
apiVersion: v1
kind: Service
metadata:
  name: ldap
spec:
  selector:
    app: ldap
  ports:
    - protocol: TCP
      port: 389
      targetPort: 1389
---
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: ldap
  name: ldap
spec:
  replicas: 1
  selector:
    matchLabels:
      app: ldap
  strategy:
    type: Recreate
  template:
    metadata:
      labels:
        app: ldap
    spec:
      volumes:
      - name: google-ldap
        secret:
          secretName: google-ldap
      - name: stunnel-conf
        configMap:
          name: stunnel-conf
      containers:
      - image: chainguard/stunnel
        command:
        - /usr/bin/stunnel
        - /etc/conf/stunnel.conf
        name: stunnel
        ports:
        - containerPort: 1389
        volumeMounts:
        - name: google-ldap
          mountPath: /google-ldap
        - name: stunnel-conf
          mountPath: /etc/conf/stunnel.conf
          subPath: stunnel.conf

What is not defined in the above YAML is the kubernetes TLS secret that contains the certificate key-pair from Google:

$ kubectl create secret tls google-ldap \
--key ./Google_123456.key \
--cert ./Google_123456.crt

The LDAP service is now accessible from within your Kubernetes cluster as ldap://ldap.default.svc.cluster.local:389 (that is ldap:// and NOT ldaps://). If this is an issue for you, you can make it a sidecar container and thus access the stunnel proxy as ldap://127.0.0.1:389 instead.

OpenVPN, LDAP and group membership

While the need for LDAP integration and OpenVPN seems straightforward, it seems to me that the documentation for the auth-ldap plugin is not very easy to locate and find. Take for example the following auth-ldap.conf configuration file

<LDAP>
URL ldap://ldap.example.com
Timeout 15
</LDAP>
<Authorization>
BaseDN "ou=users,dc=example,dc=com"
SearchFilter "(uid=%u)" # (or choose your own LDAP filter for users)
RequireGroup false
</Authorization>

This is a very handy starter that would allow any user with a working password under the ou=users part of your tree to be granted access. But what if you would want to restrict access based on group membership? According to fragments of documentation scattered at different bits of forums and StackOverflow / ServerFault, you’d need to set RequireGroup true and then use the BaseDN of the group and the memberUid attribute within a <Group> ... </Group> subsection of Authorization. This never worked for me. What worked was changing the Search filter to include group membership:

<LDAP>
URL ldaps//ldap.example.com
Timeout 15
</LDAP>
<Authorization>
BaseDN "ou=users,dc=example,dc=com"
SearchFilter "(&(uid=%u)(memberOf=cn=openvpn,ou=groups,dc=example,dc=com))"
RequireGroup false
</Authorization>

Voila!

I did not come up with this. I found it via random Googling somewhere in SO (I cannot remember and cite that answer anymore).