Ldap + Identity Server Series ー Part I ー OpenLdap on docker container

Ldap + Identity Server Series ー Part I ー OpenLdap on docker container

2018-06-18 13 By Nordes

Objectives

This is a multi-part tutorial playground in order to build, for dev or for some of your needs, an integration of:

  • [Part I*] OpenLdap (Or active directory) using Contoso Users
  • [Part II] IdentityServer 4 with OpenLdap (Built in Dotnet Core 2.x)
    • Option of using Redis to persist user cache (tokens)
  • [Part III] Docker to run the previous two options all together (If not already explained in previous 2)
  • [Part IV] A small back-end with protected Api’s
    • One in Dotnet Core 2 using the WebApi format
    • One in NodeJS
  • [Part V] A small front-end to call the Api’s
    • One in Dotnet Core 2 using the WebApp MVC format
    • One in NodeJS

Requirements for this part

  • Docker (Windows, Linux or Mac)

Let’s begin [Part I]

In this part, if you are using Active Directory, it will be easy to already find a VM having the Contoso users already within Active Directory. However, if you are using OpenLdap, you won’t find anything. For development purpose it’s nice to have an already populated Ldap database. For some reason, I will stay with the default schema from OpenLdap. The schema differ from Active Directory.

In this part we will cover:

  • How to create an empty OpenLdap Server
  • How to start the Server
  • How to convert a csv file to ldif format
  • How to import within OpenLdap Server
  • How to see the data (tool)

How to create an empty OpenLdap Server

You have the choice here of reading all the documentation to install step-by-step or speed up by using a Docker image. For a production environment I would strongly suggest to read all the documentation regarding OpenLdap. It will helps to avoid security holes and also to build properly your Ldap tree.

Let’s pull the best image I’ve found so far for OpenLdap (osixia/openldap)

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
$ docker pull osixia/openldap:latest
$ docker pull osixia/openldap:latest
$ docker pull osixia/openldap:latest

The download should be pretty quick since the image size is not so big. At the time of writing this, the image size on the disk was about `222 MB`. The Github site from that image help you to configure it and also indicate how to have persistance and so much more. Let’s do a quick `Contoso` configuration:

Create an OpenLdap environment file

This file will basically set the Base DC and Admin/Config/Read-Only & {user} + {password}.

  • File: ./ldap.env
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
LDAP_ORGANISATION=Contoso Ltd.
LDAP_DOMAIN=contoso.com
LDAP_ADMIN_PASSWORD=P@ss1W0Rd!
LDAP_CONFIG_PASSWORD=P@ss1W0Rd!
LDAP_READONLY_USER=true
LDAP_READONLY_USER_USERNAME=ldap-ro
LDAP_READONLY_USER_PASSWORD=P@ss1W0Rd!
LDAP_ORGANISATION=Contoso Ltd. LDAP_DOMAIN=contoso.com LDAP_ADMIN_PASSWORD=P@ss1W0Rd! LDAP_CONFIG_PASSWORD=P@ss1W0Rd! LDAP_READONLY_USER=true LDAP_READONLY_USER_USERNAME=ldap-ro LDAP_READONLY_USER_PASSWORD=P@ss1W0Rd!
LDAP_ORGANISATION=Contoso Ltd.
LDAP_DOMAIN=contoso.com
LDAP_ADMIN_PASSWORD=P@ss1W0Rd!
LDAP_CONFIG_PASSWORD=P@ss1W0Rd!

LDAP_READONLY_USER=true
LDAP_READONLY_USER_USERNAME=ldap-ro
LDAP_READONLY_USER_PASSWORD=P@ss1W0Rd!
  • Create a folder to put shared files within Docker and OpenLdap
    • ./ldap/

Create a docker compose file

The

docker-compose
docker-compose  file will be useful to start/restart or reconfigure the image. I prefer the docker-compose version to the all-in-one command line ( 
docker run ...
docker run ... ). It is still up to you! 😉

File 

./docker-compose.yml
./docker-compose.yml

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
ldap:
container_name: contosoOpenLdap
image: osixia/openldap:latest
ports:
- "389:389"
- "636:636"
env_file:
- ./ldap.env
volumes:
- ./ldap:/data/ldif
ldap: container_name: contosoOpenLdap image: osixia/openldap:latest ports: - "389:389" - "636:636" env_file: - ./ldap.env volumes: - ./ldap:/data/ldif
ldap:
   container_name: contosoOpenLdap
   image: osixia/openldap:latest
   ports: 
     - "389:389"
     - "636:636"
   env_file:
     - ./ldap.env
   volumes:
     - ./ldap:/data/ldif

How to start the Server

Within the directory where you have the 

docker-compose.yml
docker-compose.yml file, you should be able to use 
docker-compose
docker-compose executable (if in windows) or if you are on Linux you might have to also install that package.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
$ docker-compose start
$ docker-compose start
$ docker-compose start
  • This will be starting the server. If you prefer to see all the log within the screen, you can instead use 
    docker-compose up
    docker-compose up command.

How to convert a 
csv
csv file to 
ldif
ldif format

First of, what is a 

ldif
ldif file? Easy reading on Wikipedia. Basically, it is a Ldap Data Interchange Format. It gives the possibility to add, remove, modify (by properties if we want) or even delete using a specific text format. The format in itself is not complex, the issue you will encounter is that the attributes might not be defined (OpenLdap VS Active Directory). By example the
sAMAccount
sAMAccount exists in Active Directory and in OpenLdap it might be the 
uid
uid attribute, depending on your organization.

I have created a simple project named csv2ldif (far from perfect) that can quickly convert a simple 

CSV
CSV format into a 
ldif
ldif format with a default password of 
P@ss1W0Rd!
P@ss1W0Rd! (Yes again, let’s keep it simple). If you want to change the default password, please do, it’s not so much work. You can also contribute to the repository. It will generate basically 2 files. The first one will be about creating the user groups. The second one is about creating the user and then assigning them to the group. In that project you can also find the CSV file regarding the Contoso Users (with also the pre-generated demo in the example folder).

Regarding the LDIF generation, you can find good information on the LdapWiki.

Small peek (Raw csv)

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
dn,objectClass,uid,homeDirectory,group,givenName,sn,displayName,cn,mail,manager,telephoneNumber,title
cn=Dan Jump,inetOrgPerson;person;organizationalPerson;posixAccount;top,danj,/home/danj,Executive,Dan,Jump,Dan Jump,Dan Jump,danj@contoso.com,,(425) 555-0179,CEO
cn=Adam Barr,inetOrgPerson;person;organizationalPerson;posixAccount;top,adamb,/home/adamb,Operations,Adam,Barr,Adam Barr,Adam Barr,adamb@contoso.com,cn=Dan Jump,(206) 555-5472,General Manager of Professional Services
dn,objectClass,uid,homeDirectory,group,givenName,sn,displayName,cn,mail,manager,telephoneNumber,title cn=Dan Jump,inetOrgPerson;person;organizationalPerson;posixAccount;top,danj,/home/danj,Executive,Dan,Jump,Dan Jump,Dan Jump,danj@contoso.com,,(425) 555-0179,CEO cn=Adam Barr,inetOrgPerson;person;organizationalPerson;posixAccount;top,adamb,/home/adamb,Operations,Adam,Barr,Adam Barr,Adam Barr,adamb@contoso.com,cn=Dan Jump,(206) 555-5472,General Manager of Professional Services
dn,objectClass,uid,homeDirectory,group,givenName,sn,displayName,cn,mail,manager,telephoneNumber,title
cn=Dan Jump,inetOrgPerson;person;organizationalPerson;posixAccount;top,danj,/home/danj,Executive,Dan,Jump,Dan Jump,Dan Jump,danj@contoso.com,,(425) 555-0179,CEO
cn=Adam Barr,inetOrgPerson;person;organizationalPerson;posixAccount;top,adamb,/home/adamb,Operations,Adam,Barr,Adam Barr,Adam Barr,adamb@contoso.com,cn=Dan Jump,(206) 555-5472,General Manager of Professional Services

Note that the Base OU for the 

groups
groups and 
users
users are not generated. Usually you already have this in your tree. In our case we have something empty.

Create the 

groups
groups and 
users
users 
OU using a LDIF file

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
dn: ou=users, dc=contoso,dc=com
ou: users
description: All people in organisation
objectclass: organizationalunit
dn: ou=groups, dc=contoso,dc=com
ou: groups
description: All Groups in organisation
objectclass: organizationalunit
dn: ou=users, dc=contoso,dc=com ou: users description: All people in organisation objectclass: organizationalunit dn: ou=groups, dc=contoso,dc=com ou: groups description: All Groups in organisation objectclass: organizationalunit
dn: ou=users, dc=contoso,dc=com
ou: users
description: All people in organisation
objectclass: organizationalunit

dn: ou=groups, dc=contoso,dc=com
ou: groups
description: All Groups in organisation
objectclass: organizationalunit

Small peek (Generated groups LDIF)

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
dn: cn=Executive,ou=groups,dc=contoso,dc=com
objectClass: groupOfUniqueNames
cn: Executive
uniqueMember: cn=Executive,ou=groups,dc=contoso,dc=com
description: Executive
dn: cn=Operations,ou=groups,dc=contoso,dc=com
objectClass: groupOfUniqueNames
cn: Operations
uniqueMember: cn=Operations,ou=groups,dc=contoso,dc=com
description: Operations
dn: cn=Executive,ou=groups,dc=contoso,dc=com objectClass: groupOfUniqueNames cn: Executive uniqueMember: cn=Executive,ou=groups,dc=contoso,dc=com description: Executive dn: cn=Operations,ou=groups,dc=contoso,dc=com objectClass: groupOfUniqueNames cn: Operations uniqueMember: cn=Operations,ou=groups,dc=contoso,dc=com description: Operations
dn: cn=Executive,ou=groups,dc=contoso,dc=com
objectClass: groupOfUniqueNames
cn: Executive
uniqueMember: cn=Executive,ou=groups,dc=contoso,dc=com
description: Executive

dn: cn=Operations,ou=groups,dc=contoso,dc=com
objectClass: groupOfUniqueNames
cn: Operations
uniqueMember: cn=Operations,ou=groups,dc=contoso,dc=com
description: Operations

Small peek (Generated users LDIF)

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
dn: cn=Dan Jump,ou=users,dc=contoso,dc=com
objectClass: inetOrgPerson
objectClass: person
objectClass: organizationalPerson
objectClass: posixAccount
objectClass: top
uid: danj
uidNumber: 70000
gidNumber: 70001
homeDirectory: /home/danj
givenName: Dan
sn: Jump
displayName: Dan Jump
cn: Dan Jump
mail: danj@contoso.com
telephoneNumber: (425) 555-0179
title: CEO
userPassword: {SHA}RKkNn7+KoG94IN3x/B2jnm/4DS0=
dn: cn=Executive,ou=groups,dc=contoso,dc=com
changetype: modify
add: uniqueMember
uniqueMember: cn=Dan Jump,ou=users,dc=contoso,dc=com
dn: cn=Adam Barr,ou=users,dc=contoso,dc=com
objectClass: inetOrgPerson
objectClass: person
objectClass: organizationalPerson
objectClass: posixAccount
objectClass: top
uid: adamb
uidNumber: 70002
gidNumber: 70003
homeDirectory: /home/adamb
givenName: Adam
sn: Barr
displayName: Adam Barr
cn: Adam Barr
mail: adamb@contoso.com
manager: cn=Dan Jump,ou=users,dc=contoso,dc=com
telephoneNumber: (206) 555-5472
title: General Manager of Professional Services
userPassword: {SHA}RKkNn7+KoG94IN3x/B2jnm/4DS0=
dn: cn=Operations,ou=groups,dc=contoso,dc=com
changetype: modify
add: uniqueMember
uniqueMember: cn=Adam Barr,ou=users,dc=contoso,dc=com
dn: cn=Dan Jump,ou=users,dc=contoso,dc=com objectClass: inetOrgPerson objectClass: person objectClass: organizationalPerson objectClass: posixAccount objectClass: top uid: danj uidNumber: 70000 gidNumber: 70001 homeDirectory: /home/danj givenName: Dan sn: Jump displayName: Dan Jump cn: Dan Jump mail: danj@contoso.com telephoneNumber: (425) 555-0179 title: CEO userPassword: {SHA}RKkNn7+KoG94IN3x/B2jnm/4DS0= dn: cn=Executive,ou=groups,dc=contoso,dc=com changetype: modify add: uniqueMember uniqueMember: cn=Dan Jump,ou=users,dc=contoso,dc=com dn: cn=Adam Barr,ou=users,dc=contoso,dc=com objectClass: inetOrgPerson objectClass: person objectClass: organizationalPerson objectClass: posixAccount objectClass: top uid: adamb uidNumber: 70002 gidNumber: 70003 homeDirectory: /home/adamb givenName: Adam sn: Barr displayName: Adam Barr cn: Adam Barr mail: adamb@contoso.com manager: cn=Dan Jump,ou=users,dc=contoso,dc=com telephoneNumber: (206) 555-5472 title: General Manager of Professional Services userPassword: {SHA}RKkNn7+KoG94IN3x/B2jnm/4DS0= dn: cn=Operations,ou=groups,dc=contoso,dc=com changetype: modify add: uniqueMember uniqueMember: cn=Adam Barr,ou=users,dc=contoso,dc=com
dn: cn=Dan Jump,ou=users,dc=contoso,dc=com
objectClass: inetOrgPerson
objectClass: person
objectClass: organizationalPerson
objectClass: posixAccount
objectClass: top
uid: danj
uidNumber: 70000
gidNumber: 70001
homeDirectory: /home/danj
givenName: Dan
sn: Jump
displayName: Dan Jump
cn: Dan Jump
mail: danj@contoso.com
telephoneNumber: (425) 555-0179
title: CEO
userPassword: {SHA}RKkNn7+KoG94IN3x/B2jnm/4DS0=

dn: cn=Executive,ou=groups,dc=contoso,dc=com
changetype: modify
add: uniqueMember
uniqueMember: cn=Dan Jump,ou=users,dc=contoso,dc=com

dn: cn=Adam Barr,ou=users,dc=contoso,dc=com
objectClass: inetOrgPerson
objectClass: person
objectClass: organizationalPerson
objectClass: posixAccount
objectClass: top
uid: adamb
uidNumber: 70002
gidNumber: 70003
homeDirectory: /home/adamb
givenName: Adam
sn: Barr
displayName: Adam Barr
cn: Adam Barr
mail: adamb@contoso.com
manager: cn=Dan Jump,ou=users,dc=contoso,dc=com
telephoneNumber: (206) 555-5472
title: General Manager of Professional Services
userPassword: {SHA}RKkNn7+KoG94IN3x/B2jnm/4DS0=

dn: cn=Operations,ou=groups,dc=contoso,dc=com
changetype: modify
add: uniqueMember
uniqueMember: cn=Adam Barr,ou=users,dc=contoso,dc=com

If you want the entire data don’t forget to just go on https://github.com/Nordes/Csv2Ldif/tree/master/example. There’s the 00, 01, 02 sample file where it have the entire Contoso setup.

How to import within OpenLdap Server

Right now, we have an empty OpenLdap Server. What we want is a server with some users and groups in order for us to play with them within an application or during our Devs.

Download the following files (if not already) and save them within the 

./ldap/
./ldap/ folder you’ve created earlier:

  1. 00-startup.ldif
    • Create the
      users
      users and 
      groups
      groups OU (Organisation Unit) (ou=…,dc=contoso,dc=com)
  2. 01-output-groups.ldif
    • Create all the 
      groups
      groups CN within the  
      groups
      groups OU (cn=…,ou=groups,dc=contoso,dc=com)
  3. [02-output-users.ldif](https://github.com/Nordes/Csv2Ldif/blob/master/example/02-output-users.ldif)
    • Create all the 
      users
      users CN within the 
      users
      users OU (cn=…,ou=users,dc=contoso,dc=com)
    • Attach all the 
      users
      users to their pre-defined
      group
      group. In our case it’s a 1-1 match.

Import

It’s now time to launch the command line tool (if possible where you have your ldif file).

Our action parameter will be using the following:

  • -f: Import the file launching the 
    ldapmodify
    ldapmodify command within the container
  • -d: Use the administrator role to import
  • -w: The administrator password (if not set, but with the -w option, it will request the password). In case the password is not set, it will fail.
  • -c: Continue on failure (we never know, but at least it will give the logs)
  • : The other one are trivial.

The output result when launching the command are describing what’s happening. By example

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
...
adding new entry "cn=Josh Edwards,ou=users,dc=contoso,dc=com"
modifying entry "cn=Sales,ou=groups,dc=contoso,dc=com"
...
... adding new entry "cn=Josh Edwards,ou=users,dc=contoso,dc=com" modifying entry "cn=Sales,ou=groups,dc=contoso,dc=com" ...
...
adding new entry "cn=Josh Edwards,ou=users,dc=contoso,dc=com"
modifying entry "cn=Sales,ou=groups,dc=contoso,dc=com"
...

If you are too lazy to type the commands, you can copy the batch file located at the same emplacement as the 3 previous downloads

Organisation Units

PowerShell uses the back tick ` for multiline, however on linux it will be \ character.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
$ docker exec contosoOpenLdap ldapmodify \
-a -x -h localhost -p 389 \
-D "cn=admin,dc=contoso,dc=com" \
-f /data/ldif/00-startup.ldif \
-w P@ss1W0Rd! \
-c
$ docker exec contosoOpenLdap ldapmodify \ -a -x -h localhost -p 389 \ -D "cn=admin,dc=contoso,dc=com" \ -f /data/ldif/00-startup.ldif \ -w P@ss1W0Rd! \ -c
$ docker exec contosoOpenLdap ldapmodify \
-a -x -h localhost -p 389 \
-D "cn=admin,dc=contoso,dc=com" \
-f /data/ldif/00-startup.ldif \
-w P@ss1W0Rd! \
-c

Groups

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
$ docker exec contosoOpenLdap ldapmodify \
-a -x -h localhost -p 389 \
-D "cn=admin,dc=contoso,dc=com" \
-f /data/ldif/01-output-groups.ldif \
-w P@ss1W0Rd! \
-c
$ docker exec contosoOpenLdap ldapmodify \ -a -x -h localhost -p 389 \ -D "cn=admin,dc=contoso,dc=com" \ -f /data/ldif/01-output-groups.ldif \ -w P@ss1W0Rd! \ -c
$ docker exec contosoOpenLdap ldapmodify \
-a -x -h localhost -p 389 \
-D "cn=admin,dc=contoso,dc=com" \
-f /data/ldif/01-output-groups.ldif \
-w P@ss1W0Rd! \
-c

Users and membership

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
$ docker exec contosoOpenLdap ldapmodify \
-a -x -h localhost -p 389 \
-D "cn=admin,dc=contoso,dc=com" \
-f /data/ldif/02-output-users.ldif \
-w P@ss1W0Rd! \
-c
$ docker exec contosoOpenLdap ldapmodify \ -a -x -h localhost -p 389 \ -D "cn=admin,dc=contoso,dc=com" \ -f /data/ldif/02-output-users.ldif \ -w P@ss1W0Rd! \ -c
$ docker exec contosoOpenLdap ldapmodify \
-a -x -h localhost -p 389 \
-D "cn=admin,dc=contoso,dc=com" \
-f /data/ldif/02-output-users.ldif \
-w P@ss1W0Rd! \
-c

How to see the data (tool)

You have some choice here, but since I am not a big fan of doing the query all by hand and do the request through the OpenLdap Docker instance, I prefer to use LdapAdmin. It gives a UI which is easy to play with.

When creating a connection you will need to set the following:

What Value Desc.
Connection Name OpenLdap Contoso Test Name of the connection (can be what you want)
Host localhost Since we’re running thing locally, localhost is good
port 389 Default port (unsecured). The secured is supposed to be also available.
base dc=contoso,dc=com Base DC when opening the connection
Auth type Simple Authentication Option under the base
Username cn=admin,dc=contoso,dc=com User to connect with (let’s use Admin, but you can also use the ldap-ro or other user)
Password P@ss1W0Rd! Admin password in this case

Look After Import

ldap-Imported

ldap-Imported

 

The end! See you soon for the next part.