# Bootstrapping lldap using [bootstrap.sh](bootstrap.sh) script

bootstrap.sh allows managing your lldap in a git-ops, declarative way using JSON config files.

The script can:

* create, update users
  * set/update all lldap built-in user attributes
  * add/remove users to/from corresponding groups
  * set/update user avatar from file, link or from gravatar by user email
  * set/update user password
* create groups
* delete redundant users and groups (when `DO_CLEANUP` env var is true)
* maintain the desired state described in JSON config files


![](bootstrap-example-log-1.jpeg)

## Required packages

> The script will automatically install the required packages for alpine and debian-based distributions
> when run by root, or you can install them by yourself.

- curl
- [jq](https://github.com/jqlang/jq)
- [jo](https://github.com/jpmens/jo)

## Environment variables

- `LLDAP_URL` or `LLDAP_URL_FILE` - URL to your lldap instance or path to file that contains URL (**MANDATORY**)
- `LLDAP_ADMIN_USERNAME` or `LLDAP_ADMIN_USERNAME_FILE` - admin username or path to file that contains username (**MANDATORY**)
- `LLDAP_ADMIN_PASSWORD` or `LLDAP_ADMIN_PASSWORD_FILE` - admin password or path to file that contains password (**MANDATORY**)
- `USER_CONFIGS_DIR` (default value: `/user-configs`) - directory where the user JSON configs could be found
- `GROUP_CONFIGS_DIR` (default value: `/group-configs`) - directory where the group JSON configs could be found
- `LLDAP_SET_PASSWORD_PATH` - path to the `lldap_set_password` utility (default value: `/app/lldap_set_password`)
- `DO_CLEANUP` (default value: `false`) - delete groups and users not specified in config files, also remove users from groups that they do not belong to

## Config files

There are two types of config files: [group](#group-config-file-example) and [user](#user-config-file-example) configs.
Each config file can be as one JSON file with nested JSON top-level values as several JSON files.

### Group config file example

Group configs are used to define groups that will be created by the script

Fields description:

* `name`: name of the group (**MANDATORY**)

```json
{
  "name": "group-1"
}
{
  "name": "group-2"
}
```

### User config file example

User config defines all the lldap user structures,
if the non-mandatory field is omitted, the script will clean this field in lldap as well.

Fields description:

* `id`: it's just username (**MANDATORY**)
* `email`: self-explanatory (**MANDATORY**)
* `password`: would be used to set the password using `lldap_set_password` utility
* `displayName`: self-explanatory
* `firstName`: self-explanatory
* `lastName`: self-explanatory
* `avatar_file`: must be a valid path to jpeg file (ignored if `avatar_url` specified)
* `avatar_url`: must be a valid URL to jpeg file (ignored if `gravatar_avatar` specified)
* `gravatar_avatar` (`false` by default): the script will try to get an avatar from [gravatar](https://gravatar.com/) by previously specified `email` (has the highest priority)
* `weserv_avatar` (`false` by default): avatar file from `avatar_url` or `gravatar_avatar` would be converted to jpeg using [wsrv.nl](https://wsrv.nl) (useful when your avatar is png)
* `groups`: an array of groups the user would be a member of (all the groups must be specified in group config files)

```json
{
  "id": "username",
  "email": "username@example.com",
  "password": "changeme",
  "displayName": "Display Name",
  "firstName": "First",
  "lastName": "Last",
  "avatar_file": "/path/to/avatar.jpg",
  "avatar_url": "https://i.imgur.com/nbCxk3z.jpg",
  "gravatar_avatar": "false",
  "weserv_avatar": "false",
  "groups": [
    "group-1",
    "group-2"
  ]
}

```

## Usage example

### Manually

The script can be run manually in the terminal for initial bootstrapping of your lldap instance.
You should make sure that the [required packages](#required-packages) are installed
and the [environment variables](#environment-variables) are configured properly.

```bash
export LLDAP_URL=http://localhost:8080
export LLDAP_ADMIN_USERNAME=admin
export LLDAP_ADMIN_PASSWORD=changeme
export USER_CONFIGS_DIR="$(realpath ./configs/user)"
export GROUP_CONFIGS_DIR="$(realpath ./configs/group)"
export LLDAP_SET_PASSWORD_PATH="$(realpath ./lldap_set_password)"
export DO_CLEANUP=false
./bootstrap.sh
```

### Docker compose

Let's suppose you have the next file structure:

```text
./
├─ docker-compose.yaml
└─ bootstrap
   ├─ bootstrap.sh
   └─ user-configs
   │  ├─ user-1.json
   │  ├─ ...
   │  └─ user-n.json
   └─ group-configs
      ├─ group-1.json
      ├─ ...
      └─ group-n.json
   
```

You should mount `bootstrap` dir to lldap container and set the corresponding `env` variables:

```yaml
version: "3"

services:
  lldap:
    image: lldap/lldap:v0.5.0
    volumes:
      - ./bootstrap:/bootstrap
    ports:
      - "3890:3890" # For LDAP
      - "17170:17170" # For the web front-end
    environment:
      # envs required for lldap
      - LLDAP_LDAP_USER_EMAIL=admin@example.com
      - LLDAP_LDAP_USER_PASS=changeme
      - LLDAP_LDAP_BASE_DN=dc=example,dc=com
      
      # envs required for bootstrap.sh
      - LLDAP_URL=http://localhost:17170
      - LLDAP_ADMIN_USERNAME=admin
      - LLDAP_ADMIN_PASSWORD=changeme # same as LLDAP_LDAP_USER_PASS
      - USER_CONFIGS_DIR=/bootstrap/user-configs
      - GROUP_CONFIGS_DIR=/bootstrap/group-configs
      - DO_CLEANUP=false
```

Then, to bootstrap your lldap just run `docker compose exec lldap /bootstrap/bootstrap.sh`.
If config files were changed, re-run the `bootstrap.sh` with the same command.

### Kubernetes job

```yaml
apiVersion: batch/v1
kind: Job
metadata:
  name: lldap-bootstrap
  # Next annotations are required if the job managed by Argo CD,
  # so Argo CD can relaunch the job on every app sync action
  annotations: 
    argocd.argoproj.io/hook: PostSync
    argocd.argoproj.io/hook-delete-policy: BeforeHookCreation
spec:
  template:
    spec:
      restartPolicy: OnFailure
      containers:
        - name: lldap-bootstrap
          image: lldap/lldap:v0.5.0

          command:
            - /bootstrap/bootstrap.sh

          env:
            - name: LLDAP_URL
              value: "http://lldap:8080"

            - name: LLDAP_ADMIN_USERNAME
              valueFrom: { secretKeyRef: { name: lldap-admin-user, key: username } }

            - name: LLDAP_ADMIN_PASSWORD
              valueFrom: { secretKeyRef: { name: lldap-admin-user, key: password } }

            - name: DO_CLEANUP
              value: "true"

          volumeMounts:
            - name: bootstrap
              mountPath: /bootstrap/bootstrap.sh
              subPath: bootstrap.sh

            - name: user-configs
              mountPath: /user-configs
              readOnly: true

            - name: group-configs
              mountPath: /group-configs
              readOnly: true

      volumes:
        - name: bootstrap
          configMap:
            name: bootstrap
            defaultMode: 0555
            items:
              - key: bootstrap.sh
                path: bootstrap.sh

        - name: user-configs
          projected:
            sources:
              - secret:
                  name: lldap-admin-user
                  items:
                    - key: user-config.json
                      path: admin-config.json
              - secret:
                  name: lldap-password-manager-user
                  items:
                    - key: user-config.json
                      path: password-manager-config.json
              - secret:
                  name: lldap-bootstrap-configs
                  items:
                    - key: user-configs.json
                      path: user-configs.json

        - name: group-configs
          projected:
            sources:
              - secret:
                  name: lldap-bootstrap-configs
                  items:
                    - key: group-configs.json
                      path: group-configs.json
```
