Advanced Kubernetes 3 | LDAP

1. LDAP

(1) LDAP Introduction

LDAP is a protocol initially used to manage telephone directories through telecommunication companies. It’s operating on the application layer of OSI. The default port for LDAP is port 389.

(2) LDAP Data Interchange Format (LDIF)

LDIF is the standard plain text data files used for LDAP directory content and update requests. There are several fields used in LDIF,

  • dn: unique distinguished name for each entry

  • dc: domain component, for example www.mydomain.com would be written as DC=www,DC=mydomain,DC=com

  • ou: organizational unit, used to specifiy a user as part of one group

  • cn: common name referring to individual objects

(3) Example: LDAP Pod in Minikube

Let’s continue with the minikube cluster we have built in the last article.

First, let’s create a new namespace called ldap,

$ kubectl create ns ldap

Then, make a directory named ldif and download the LDIF plain text files from github,

$ mkdir ldif
$ cd ldif
$ curl -LO https://raw.githubusercontent.com/Sadamingh/minikube-with-dex/main/ldap/ldif/0-ous.ldif
$ curl -LO https://raw.githubusercontent.com/Sadamingh/minikube-with-dex/main/ldap/ldif/1-users.ldif
$ curl -LO https://raw.githubusercontent.com/Sadamingh/minikube-with-dex/main/ldap/ldif/2-groups.ldif

Go back to the last directory after this,

$ cd ..

Create a secert and a configmap for the deployment under this nameapce,

$ kubectl create secret generic openldap \
    --namespace ldap \
    --from-literal=adminpassword=adminpassword
    
$ kubectl create configmap ldap \
    --namespace ldap \
    --from-file=ldif

Finally, curl the YAML file ldap.yaml from github and then apply it to create the service and deployment.

$ curl -LO https://raw.githubusercontent.com/Sadamingh/minikube-with-dex/main/ldap/ldap.yaml
$ kubectl apply --namespace ldap -f ldap.yaml

Now we have an ldap pod running for further exploration.

$ kubectl get pods -A | grep ldap
ldap           openldap-6d86d655c5-5bkvc                 1/1     Running   0          73m

(4) Interact with LDAP

Now we can access the LDAP pod through,

$ kubectl exec --stdin --tty -n ldap $LDAP_POD -- /bin/sh
#

We can check the LDIF entries through ldapsearch but for now because we have no user or group imported, we will receive No such object (32) errors,

# ldapsearch -x -D "cn=admin,dc=example,dc=org" -w adminpassword -b "ou=groups,dc=example,dc=org"
No such object (32)
# ldapsearch -x -D "cn=admin,dc=example,dc=org" -w adminpassword -b "ou=people,dc=example,dc=org"
No such object (32)

From ldap.yaml and config map ldap, we have mounted local path ldap/ldif to /ldifs. Therefore, now we are able to access the LDIF files,

# ls -lrt /ldifs
lrwxrwxrwx 1 root root 20 Mar 15 03:31 2-groups.ldif -> ..data/2-groups.ldif
lrwxrwxrwx 1 root root 19 Mar 15 03:31 1-users.ldif -> ..data/1-users.ldif
lrwxrwxrwx 1 root root 17 Mar 15 03:31 0-ous.ldif -> ..data/0-ous.ldif

These LDIF files can be mapped to the following tree structure,

β”œβ”€dc=example,dc=org    # example.org
β”‚  β”œβ”€ou=people
β”‚  β”‚  β”œβ”€cn=admin1
β”‚  β”‚  β”œβ”€cn=admin2
β”‚  β”‚  β”œβ”€cn=developer1
β”‚  β”‚  └─cn=developer2
β”‚  └─ou=groups
β”‚     β”œβ”€cn=admins
β”‚     β”‚  β”œβ”€cn=admin1
β”‚     β”‚  └─cn=admin2
β”‚     └─cn=developers
β”‚        β”œβ”€cn=developer1
β”‚        └─cn=developer2

Then, we can add them to the LDAP server at localhost:389 by using command ldapadd,

# ldapadd -x -D "cn=admin,dc=example,dc=org" -w adminpassword -H ldap://localhost:389 -f /ldifs/0-ous.ldif
adding new entry "ou=people,dc=example,dc=org"
adding new entry "ou=groups,dc=example,dc=org"
# ldapadd -x -D "cn=admin,dc=example,dc=org" -w adminpassword -H ldap://localhost:389 -f /ldifs/1-users.ldif
adding new entry "cn=admin1,ou=people,dc=example,dc=org"
adding new entry "cn=admin2,ou=people,dc=example,dc=org"
adding new entry "cn=developer1,ou=people,dc=example,dc=org"
adding new entry "cn=developer2,ou=people,dc=example,dc=org"
# ldapadd -x -D "cn=admin,dc=example,dc=org" -w adminpassword -H ldap://localhost:389 -f /ldifs/2-groups.ldif
adding new entry "cn=admins,ou=groups,dc=example,dc=org"
adding new entry "cn=developers,ou=groups,dc=example,dc=org"

After the LDIF is added to the LDAP server, we can access the data through ldapsearch. Note that cn=admin,dc=example,dc=org means the default admin account for the LDAP server which has access to all the objects in the server.

# ldapsearch -x -D "cn=admin,dc=example,dc=org" -w adminpassword -b "ou=groups,dc=example,dc=org"
...
# numResponses: 4
# numEntries: 3
# ldapsearch -x -D "cn=admin,dc=example,dc=org" -w adminpassword -b "ou=people,dc=example,dc=org"
...
# numResponses: 6
# numEntries: 5

(5) Password Hashing

There’s one remaining problem. In the LDIF file 1-users.ldif, userPassword is defined through a hash value {SSHA}RRN6AM9u0tpTEOn6oBcIt9X3BbFPKVk5 according to the code. You can check it out through this link.

This value is generated by slappasswd command with,

# slappasswd -h {SSHA} -s secret

And here the original password for admin1 is actually secret. People don’t store the original password secret directly in LDIF files because someone can easily retrieve the password and it’s not secure.

Now, let’s try to use slappasswd using SSHA (using SHA-1 algorithm) to generate the hash value for secret secret. We will have something like,

# slappasswd -h {SSHA} -s secret  
{SSHA}wH8Y5PJH9h31HoWBj9ZafqB43pyslcjS

Here the hash code is very different from the one we have in the 1-users.ldif. This is because slappasswd utility uses a random salt by default to make the hash more secure and the server can know both of the hash values means secret.

(6) Salted Hash

Let’s see how salted hash works.

The idea of salted hash is to add some random bits to the password each time before hash so that the hash value will become more randomlized. We will put this salt after the hash value as an appendix for the program to verify.

Suppose we use secret as our password. Then after using command slappasswd, we get a hash value as follows,

{SSHA}RRN6AM9u0tpTEOn6oBcIt9X3BbFPKVk5

This hash value is encoded with base64. So let’s first decode it, and then we od command to change it to hexed bin values,

$ echo -n RRN6AM9u0tpTEOn6oBcIt9X3BbFPKVk5 | base64 -d | od -A n -t x1
45  13  7a  00  cf  6e  d2  da  53  10  e9  fa  a0  17  08  b7  d5  f7  05  b1  4f  29  59  39 

The value above has two parts. The first 20 bytes are the actual hash value and the last 4 bytes is the salt we used for extra security.

When user enters password as secret, we will append the salt after the secret as secret\x4f\x29\x59\x39. Then the same encryption process will generate the hash value for us. It should be exactly the same as the one we stored in the LDAP server to pass the authentication.

$ slappasswd -h {SHA} -s $(printf 'secret\x4f\x29\x59\x39') | sed 's/{SHA}//' | base64 -d | od -A n -t x1
45  13  7a  00  cf  6e  d2  da  53  10  e9  fa  a0  17  08  b7  d5  f7  05  b1  

Because the salt is randomly added, we will have different hash values even for the same password. This is useful when the passwords are simple, and the attackers can not guess the password through the hash value.