An open API service indexing awesome lists of open source software.

https://github.com/alan-turing-institute/apricot

An OpenID Connect LDAP proxy
https://github.com/alan-turing-institute/apricot

data-safe-haven hacktoberfest hut23 hut23-1304

Last synced: 5 months ago
JSON representation

An OpenID Connect LDAP proxy

Awesome Lists containing this project

README

        

# Apricot

`Apricot` is a proxy for delegating LDAP requests to an OpenID Connect backend.
The name is a slightly tortured acronym for: LD**A**P **pr**oxy for Open**I**D **Co**nnec**t**.

## Usage

Start the `Apricot` server on port 1389 by running:

```bash
python run.py --client-id "" --client-secret "" --backend "" --port 1389 --domain "" --redis-host ""
```

If you prefer to use Docker, you can edit `docker/docker-compose.yaml` and run:

```bash
docker compose up
```

from the `docker` directory.

### Using Redis [Optional]

You can use a Redis server to store generated `uidNumber` and `gidNumber` values in a more persistent way.
To do this, you will need to provide the `--redis-host` and `--redis-port` arguments to `run.py`.

### Configure background refresh [Optional]

By default Apricot will refresh the LDAP tree whenever it is accessed and it contains data older than 60 seconds.
If it takes a long time to fetch all users and groups, or you want to ensure that each request gets a prompt response, you may want to configure background refresh to have it periodically be refreshed in the background.

This is enabled with the `--background-refresh` flag, which uses the `--refresh-interval` parameter as the interval to refresh the ldap database.

### Using TLS [Optional]

You can set up a TLS listener to communicate with encryption enabled over the configured port.
To enable it you need to configure the tls port ex. `--tls-port=1636`, and provide a path to the pem files for the certificate `--tls-certificate=` and the private key `--tls-private-key=`.

## Outputs

This will create an LDAP tree that looks like this:

```ldif
dn: DC=
objectClass: dcObject

dn: OU=users,DC=
objectClass: organizationalUnit
ou: users

dn: OU=groups,DC=
objectClass: organizationalUnit
ou: groups
```

Each user will have an entry like

```ldif
dn: CN=,OU=users,DC=
objectClass: inetOrgPerson
objectClass: organizationalPerson
objectClass: person
objectClass: posixAccount
objectClass: top

memberOf:
```

Each group will have an entry like

```ldif
dn: CN=,OU=groups,DC=
objectClass: groupOfNames
objectClass: posixGroup
objectClass: top

member:
```

## Primary groups

:exclamation: You can disable the creation of mirrored groups with the `--disable-primary-groups` command line option :exclamation:

Apricot creates an associated group for each user, which acts as its POSIX user primary group.

For example:

```ldif
dn: CN=sherlock.holmes,OU=users,DC=
objectClass: inetOrgPerson
objectClass: organizationalPerson
objectClass: person
objectClass: posixAccount
objectClass: top
...
memberOf: CN=sherlock.holmes,OU=groups,DC=
...
```

will have an associated group

```ldif
dn: CN=sherlock.holmes,OU=groups,DC=
objectClass: groupOfNames
objectClass: posixGroup
objectClass: top
...
member: CN=sherlock.holmes,OU=users,DC=
...
```

## Mirrored groups

:exclamation: You can disable the creation of mirrored groups with the `--disable-mirrored-groups` command line option :exclamation:

Each group of users will have an associated group-of-groups where each user in the group will have its user primary group in the group-of-groups.
Note that these groups-of-groups are **not** `posixGroup`s as POSIX does not allow nested groups.

For example:

```ldif
dn:CN=Detectives,OU=groups,DC=
objectClass: groupOfNames
objectClass: posixGroup
objectClass: top
...
member: CN=sherlock.holmes,OU=users,DC=
...
```

will have an associated group-of-groups

```ldif
dn: CN=Primary user groups for Detectives,OU=groups,DC=
objectClass: groupOfNames
objectClass: top
...
member: CN=sherlock.holmes,OU=groups,DC=
...
```

This allows a user to make a request for "all primary user groups needed by members of group X" without getting a large number of primary user groups for unrelated users. To do this, you will need an LDAP request that looks like:

```ldif
(&(objectClass=posixGroup)(|(CN=Detectives)(memberOf=Primary user groups for Detectives)))
```

which will return:

```ldif
dn:CN=Detectives,OU=groups,DC=
objectClass: groupOfNames
objectClass: posixGroup
objectClass: top
...
member: CN=sherlock.holmes,OU=users,DC=
...

dn: CN=sherlock.holmes,OU=groups,DC=
objectClass: groupOfNames
objectClass: posixGroup
objectClass: top
...
member: CN=sherlock.holmes,OU=users,DC=
...
```

## OpenID Connect

Instructions for specific OpenID Connect backends below.

### Microsoft Entra

You will need to use the following command line arguments:

```bash
--backend MicrosoftEntra \
--entra-tenant-id ""
```

You will need to register an application to interact with `Microsoft Entra`.
Do this as follows:

- Create a new `App Registration` in your `Microsoft Entra`.
- Set the name to whatever you choose (e.g. `apricot`)
- Set access to `Accounts in this organizational directory only`.
- Set `Redirect URI` to `Public client/native (mobile & desktop)` with a value of `urn:ietf:wg:oauth:2.0:oob`
- Under `Certificates & secrets` add a `New client secret`
- Set the description to whatever you choose (e.g. `Apricot Authentication Secret`)
- Set the expiry time to whatever is relevant for your use-case
- You **must** record the value of this secret at **creation time**, as it will not be visible later.
- Under `API permissions`:
- Enable the following permissions:
- `Microsoft Graph` > `User.Read.All` (application)
- `Microsoft Graph` > `GroupMember.Read.All` (application)
- `Microsoft Graph` > `User.Read.All` (delegated)
- Select this and click the `Grant admin consent` button (otherwise each user will need to manually consent)

### Keycloak

You will need to use the following command line arguments:

```bash
--backend Keycloak \
--keycloak-base-url "/" \
--keycloak-domain-attribute "" \
--keycloak-realm ""
```

You will need to register an application to interact with `Keycloak`.
Do this as follows:

- Under the realm option `Client scopes` create a new scope, e.g. `domainScope` with:
- Type: `Default`
- Include in token scope: `true`
- Save
- In the created scope click `Mappers` > `Configure new mapper` and now create either
- `Hardcoded claim`
- => Every user gets the same domain
- name: `domain`
- token claim name: `domain`
- claim value: ``
- `User attribute`
- => Every user has an attribute for the domain
- name: `domain`
- user attribute: ``
- token claim name: `domain`
- Create a new `Client` in your `Keycloak` instance.
- Set the name to whatever you choose (e.g. `apricot`)
- Enable `Client authentication`
- Enable the following authentication flows and disable the rest:
- Direct access grants
- Service account roles
- Under `Credentials` copy `client secret`
- Under `Service account roles`:
- Click on `Assign role` then `Filter by clients`
- Assign the following roles:
- `realm-management` > `view-users`
- `realm-management` > `manage-users`
- `realm-management` > `query-groups`
- `realm-management` > `query-users`
- Under `Client scopes` click `Add client scope` > `domainScope`. Make sure to select type `Default`