# LDAP Gateway

# Preamble

The EE server supports esaqa's LDAP Gateway, a secure way to expose LDAP servers. In addition the LDAP server provides cryptographic that applications can use to decrypt data on login. The advantages are:

  • Servers and applications authenticate eachother cryptographically.
  • The whole transmission between application and gateway is encrypted by SSL and esaqa's transport encryption.
  • User secrets will be encrypted by secrets provided by the gateway
  • Encryption secrets are not stored in the server's settings.yaml.
  • Each user has one unique key that is not shared across users
  • Encryption keys for user secrets are calculated based on LDAP parameters and not stored in the gateway itself.
  • No modification to the structure of the LDAP directory is necessary.
  • Multiple LDAP gateways can be configured for HA purposes to allow Active / Passive failover.

WARNING

All keys are protected by the LDAP gateway. If the settings.yaml of the gateway is lost, all stored data in Psono becomes inaccessible.

We assume that:

  • you have a server with docker installed
  • you use https://ldapgateway.example.com to host your ldapgateway
  • you have a valid certificate for ldapgateway.example.com in /etc/ssl/ with fullchain_ldapgateway.pem and privkey_ldapgateway.pem
  • An A-Record for ldapgateway.example.com exists, pointing to your server's ip address

# Installation with Docker

  1. Create a settings.yaml in e.g. /opt/docker/ldapgateway/ with the following content

    # generate the following six parameters with the following command
    # docker run --rm -ti esaqa/ldap-gateway:latest python3 ./ldapgw/manage.py generateserverkeys
    SECRET_KEY: 'SOME SUPER SECRET KEY THAT SHOULD BE RANDOM AND 32 OR MORE DIGITS LONG'
    DB_SECRET: 'SOME SUPER SECRET DB SECRET THAT SHOULD BE RANDOM AND 32 OR MORE DIGITS LONG'
    EMAIL_SECRET_SALT: '$2b$12$XUG.sKxC2jmkUvWQjg53.e'
    PRIVATE_KEY: '02...0b'
    PUBLIC_KEY: '02...0b'
    
    # Switch DEBUG to false if you go into production
    DEBUG: False
    
    # Adjust this according to Django Documentation
    # https://docs.djangoproject.com/en/2.2/ref/settings/
    ALLOWED_HOSTS: ['*']
    
    # Cache enabled without belows Redis may lead to unexpected behaviour
    # Cache with Redis
    # By default you should use something different than database 0 or 1, e.g. 13
    # (default max is 16, can be configured in redis.conf) possible URLS are:
    #    redis://[:password]@localhost:6379/0
    #    rediss://[:password]@localhost:6379/0
    #    unix://[:password]@/path/to/socket.sock?db=0
    # CACHE_ENABLE: False
    # CACHE_REDIS: False
    # CACHE_REDIS_LOCATION: 'redis://127.0.0.1:6379/13'
    
    # The LDAP configuration
    # The syntax / feature follows the same approach as Psono server so please check the documentation
    # for alternative configurations / installation details
    # e.g. https://doc.psono.com/admin/configuration/ldap-ad.html
    LDAP: [
    	{
    		'LDAP_URL': 'ldap://dc01.example.com:389',
    		'LDAP_DOMAIN': 'example.com',
    		'LDAP_REQUIRED_GROUP': [],
    		'LDAP_BIND_DN': 'CN=LDAPPsono,OU=UsersTech,OU=example.com,DC=example,DC=com',
    		'LDAP_BIND_PASS': 'thePasswordForTheUserAbove',
    		'LDAP_SEARCH_USER_DN': 'OU=Users,OU=example.com,DC=example,DC=com',
    		'LDAP_SEARCH_GROUP_DN': 'DC=example,DC=com',
    		'LDAP_ATTR_USERNAME': 'sAMAccountName',
    		'LDAP_ATTR_EMAIL': 'mail',
    	},
    ]
    
  2. Test your integration

    To test the settings you can use the testldap command like shown below

    docker run --rm \
      -v /opt/docker/ldapgateway/settings.yaml:/root/.ldapgw/settings.yaml \
      -ti esaqa/ldap-gateway:latest python3 ./ldapgw/manage.py testldap username@something.com thePassWord
    
  3. Create Client

    You can create a client on the command line with the following command.

    docker run --rm \
      -v /opt/docker/ldapgateway/settings.yaml:/root/.ldapgw/settings.yaml \
      -ti esaqa/ldap-gateway:latest python3 ./ldapgw/manage.py generateclient
    

    The output will look similar to this:

    # Add this content into your settings.yml of the ldap gateway
    
    CLIENTS: [
        {
            'CLIENT_ID': '6e983a0d-6a60-4d9f-9595-12416aec74da',
            'CLIENT_PUBLIC_KEY': 'f0...61',
            'CLIENT_VERIFY_KEY': 'bf...25',
        },
    ]
    
    
    # And these are the parameters for your application
    
    CLIENT_ID: '6e983a0d-6a60-4d9f-9595-12416aec74da'
    CLIENT_PRIVATE_KEY: '76...ee'
    SERVER_PUBLIC_KEY: '02...0b'
    
  4. Add CLIENTS to settings.yaml

    Add the CLIENTS-section of the last command to the settings.yaml of the gateway. Take note of the last three parameters (CLIENT_ID, CLIENT_PRIVATE_KEY, SEVER_PUBLIC_KEY) as you will need them later for the configuration of the Psono EE Server.

  5. Run the gateway container and expose the server port

    docker run --name ldap-gateway \
        -v /opt/docker/ldapgateway/settings.yaml:/root/.ldapgw/settings.yaml \
        -d --restart=unless-stopped -p 10109:80 esaqa/ldap-gateway:latest
    

    This will start the LDAP gateway on port 10109. If you open now http://your-ip:10109/info/ you should see something like this:

    {"info":"{\"version\": \"....}
    

    If you don't, please make sure no firewall is blocking your request.

# Reverse Proxy with Nginx

  1. Install Nginx

    sudo apt-get install nginx
    
  2. Create nginx config

    Create ldapgateway.example.com.conf in /etc/nginx/sites-available with the following content:

    server {
        listen 80;
        server_name ldapgateway.example.com;
        return 301 https://$host$request_uri;
    }
    
    server {
        listen 443 ssl http2;
        server_name ldapgateway.example.com;
    
        ssl_protocols TLSv1.2;
        ssl_prefer_server_ciphers on;
        ssl_session_cache shared:SSL:10m;
        ssl_session_tickets off;
        ssl_stapling on;
        ssl_stapling_verify on;
        ssl_session_timeout 1d;
        resolver 8.8.8.8 8.8.4.4 valid=300s;
        resolver_timeout 5s;
        ssl_ciphers 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256';
    
        # Comment this in if you know what you are doing
        # add_header Strict-Transport-Security "max-age=63072000; includeSubdomains; preload";
    
        add_header Referrer-Policy same-origin;
        add_header X-Frame-Options DENY;
        add_header X-Content-Type-Options nosniff;
        add_header X-XSS-Protection "1; mode=block";
    
        # If you have the fileserver too, then you have to add your fileserver URL e.g. https://fs01.example.com as connect-src too:
        add_header Content-Security-Policy "default-src 'none';  manifest-src 'self'; connect-src 'self'; font-src 'self'; img-src 'self'; script-src 'self'; style-src 'self'; object-src 'self'";
    
        ssl_certificate /etc/ssl/fullchain_ldapgateway.pem;
        ssl_certificate_key /etc/ssl/privkey_ldapgateway.pem;
    
        client_max_body_size 256m;
    
        gzip on;
        gzip_disable "msie6";
    
        gzip_vary on;
        gzip_proxied any;
        gzip_comp_level 6;
        gzip_buffers 16 8k;
        gzip_http_version 1.1;
        gzip_min_length 256;
        gzip_types text/plain text/css application/json application/x-javascript application/javascript text/xml application/xml application/xml+rss text/javascript application/vnd.ms-fontobject application/x-font-ttf font/opentype image/svg+xml image/x-icon;
    
        root /var/www/html;
        
        location / {
    		proxy_set_header        Host $host;
            proxy_set_header        X-Real-IP $remote_addr;
            proxy_set_header        X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header        X-Forwarded-Proto $scheme;
    
            add_header Last-Modified $date_gmt;
            add_header Pragma "no-cache";
            add_header Cache-Control "private, max-age=0, no-cache, no-store";
            if_modified_since off;
            expires off;
            etag off;
    
            proxy_pass          http://localhost:10109;
        }
    }
    
  3. Enable nginx config

    ln -s /etc/nginx/sites-available/ldapgateway.example.com.conf /etc/nginx/sites-enabled/
    
  4. Test nginx config

    sudo nginx -t
    
  5. Restart nginx

    sudo service nginx restart
    

    If you open https://ldapgateway.example.com/info/ you should see the following:

    {"info":"{\"version\": \"....}
    

# Configuration of Psono EE Server

  1. Modify the settings.yaml of the Psono EE server

    During the installation of Psono EE server you created a settings.yaml. Add these last three parameters (CLIENT_ID, CLIENT_PRIVATE_KEY, SEVER_PUBLIC_KEY) as LDAPGATEWAY as shown below:

    LDAPGATEWAY: [
        {
            'CLIENT_ID': '8b02a601-d590-458d-b47c-45042b5a50b7',
            'CLIENT_PRIVATE_KEY': '59...da',
            'SERVER_PUBLIC_KEY': '02...0b',
            'SERVER_URL': 'https://ldapgateway.example.com',
        },
    ]
    

    Replace https://ldapgateway.example.com with the URL that you you used before for the reverse proxy configuration.

  2. (optional) LDAP Gateway exclusive secrets

    At the moment the server would still use the DB_SECRET to encrypt all user / group secrets. So you can switch out the LDAP config for the gateway and visa versa at any point. From a security standpoint you should use the secrets provided by the gateway. To do that you have to add the following line to your Psono's server settings.yaml

    LDAPGATEWAY_EXCLUSIVE_SECRETS: True
    

    This will instruct Psono to use the secrets of the LDAP gateway to encrypt all user and group secrets.

    WARNING

    This process is irreversible. The server cannot decrypt any secrets without the help of the LDAP gateway.

    WARNING

    This setting is exclusive and cannot be used in combination with directly connected LDAP servers.

  3. Test your integration

    To test the settings you can use the testldapgateway command like shown below

    docker run --rm \
      -v /opt/docker/psono/settings.yaml:/root/.psono_server/settings.yaml \
      -ti psono/psono-combo-enterprise:latest python3 ./psono/manage.py testldapgateway username@something.com thePassWord
    
  4. Finished

    Whenever a user logs in with LDAP, the server will now contact the LDAP gateway to verify the provided credentials.

# Note: Installation behind Firewall

If you have put your installation behind a firewall, you have to whitelist some ports / adjust some settings, that all features work:

  • Incoming TCP connections (usually Port 443) from servers to gateways
  • Outgoing TCP / UDP 123 connection to time.google.com: The gateway requires a synced time for various reasons (Throttling, Replay protection, ...) Therefore it has a healthcheck, to compare the local time to a time server (by default time.google.com). You can specify your own timeserver in the settings.yaml with the TIME_SERVER parameter.
  • Outgoing TCP connection (usually 636) to your LDAP server