A cloud-native LDAP server backed by a Consul datastore
Go to file
Alex 1d338a9cc1
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
fix consul version in ci
2024-03-20 17:02:45 +00:00
goldap Patch ASN.1 BER encoding of integers and length + unit tests 2021-09-16 13:51:43 +02:00
ldapserver Encoding errors must be logged 2021-09-16 13:47:09 +02:00
test Disable reconnect in tests + some cosmetic changes 2021-09-16 13:52:46 +02:00
.gitignore use legacy ssha algorithm, new one is incompatible 2023-03-26 09:41:22 +02:00
.woodpecker.yml fix consul version in ci 2024-03-20 17:02:45 +00:00
acl.go Don't do stupid things like use a dn as a pattern 2020-01-26 23:12:00 +01:00
bottin.hcl.example Update bottin version in hcl example 2021-03-09 19:08:59 +01:00
bottin.png Add bottin logo 2020-02-01 16:45:22 +01:00
config.json.example Rebrand to Bottin (with Superboum's benediction) 2020-01-31 22:15:40 +01:00
default.nix cleaned Docker process 2023-03-27 10:23:32 +02:00
flake.lock Make repo a Nix flake 2022-12-01 22:40:13 +01:00
flake.nix Set correct package path 2022-12-01 22:43:06 +01:00
go.mod Improve password hash handling 2022-02-10 20:51:01 +01:00
go.sum Improve password hash handling 2022-02-10 20:51:01 +01:00
gomod2nix.toml Add Nix packaging 2022-07-24 09:47:31 +02:00
LICENSE Add GPLv3 license 2020-01-26 20:22:36 +01:00
main.go Improve password hash handling 2022-02-10 20:51:01 +01:00
Makefile Refactor memberOf management logic 2020-02-13 14:41:49 +01:00
memberof.go Use consul's stale reads by default 2021-03-09 18:24:30 +01:00
read.go Fix wrong handling of multi value attributes 2022-02-14 12:13:31 +01:00
README.md Woodpecker CI 2024-03-20 17:02:45 +00:00
ssha.go use legacy ssha algorithm, new one is incompatible 2023-03-26 09:41:22 +02:00
TODO.md Hopefully, fix most case-sensitivity issues 2020-02-15 12:07:31 +01:00
util.go Refactor & add case normalization logic to putAttributes 2021-03-09 19:00:45 +01:00
write.go Improve password hash handling 2022-02-10 20:51:01 +01:00



Bottin is a LDAP server that uses Consul's key-value store as a storage backend, in order to provide a redundant (high-availability) LDAP server on a Nomad+Consul cluster. It is a reimplementation of superboum's Bottin using the Go programming language.

Use case: Deuxfleurs is a self-hosting collective where we try to build a resilient infrastructure with commodity hardware by having redundancy and auto-reconfiguration (this is why we use the Nomad+Consul stack). We try to use replicated/high availability data stores everywhere we can, and Bottin helps us do this for the LDAP service. We are not managing a high number of users at the moment and scalability is not our highest priority.


  • most LDAP operations implemented (add, modify, delete, compare, search with most basic filters)
  • TLS support with STARTTLS
  • Access control through an ACL (hardcoded in the configuration file)

A Docker image is provided on the Docker hub (built in default.nix). An example for running Bottin on a Nomad cluster can be found in bottin.hcl.example.

Bottin takes a single command line argument, -config <filename>, which is the path to its config file (defaults to ./config.json). The configuration file is a JSON file whose contents is described in the following section.

Guichet is a simple LDAP web administration interface that works well with Bottin.

Bottin is licensed under the terms of the GPLv3.

Building Bottin

Bottin requires go 1.13 or later.

To build Bottin, clone this repository outside of your $GOPATH. Then, run make in the root of the repo.


nix-build -A bin
nix-build -A docker
docker load < $(nix-build -A docker)
docker push dxflrs/bottin:???

Server initialization

When Bottin is launched on an empty database, it creates a special admin entity with the name cn=admin,your_suffix. It will have a randomly generated password that is printed out by the server. Check your logs to retrieve the password.

The admin entity has no powers other than those granted by the ACL rules, so unless you don't want to use it, make sure to keep rules that allow to bind to the admin entity and that allows the admin entity to do everything.

Configuration of Bottin


Bottin supports all of the log levels of logrus. The log level can be specified in the key log_level of the json config file, or in the environment variable BOTTIN_LOG_LEVEL. By default, the log level is set to info.

The LDAP suffix

Bottin only handles LDAP entries under a given path, which is typically of the form dn=sld,dn=tld, where sld.tld is your domain name. Specify this suffix in the suffix key of the json config file.

Connection to the Consul server

By default, Bottin connects to the Consul server on localhost. Change this by specifying the consul_host key in the json config file.

Bind addresses

Insecure port

By default, Bottin listens on all interfaces on port 389 for standard non-TLS connections. Change the value of the bind key in the json config file to change this behaviour (default value: An empty string will disable this port and Bottin will not listen for non-TLS connections.

Secure port

If a TLS configuration is provided (see next section), Bottin also listens on all interfaces on port 636 for TLS connections. Change the value of the bind_secure key in the json config file to change this behaviour (default value: An empty string will disable this port and Bottin will not listen for TLS connections.


Bottin supports TLS connections using either fully secure connections or using the STARTLS functionnality of the LDAP protocol to upgrade from an insecure connection. To use it, specify the following three keys in the json config file:

  • tls_server_name: the host name that clients will use to reach your LDAP server
  • tls_cert_file: path to your TLS certificate (a .pem file)
  • tls_key_file: path to your TLS key (a .pem file)

If a TLS configuration is provided, the STARTTLS mechanism may be used on the insecure port, independently of whether the secure port is enabled or not.

The secure port is disabled and a warning is shown if the bind_secure value is set (non-empty) and no valid TLS configuration is provided.

Access control list

Bottin supports a flexible syntax to specify access rights to items in the database. The ACL is specified as a list of rules. A request will be allowed if there exists a rule that allows it. Otherwise an insufficient permission error will be returned.

The list of ACL rules are specified in the acl key of the json config file, as a list of strings whose structure is defined in the next paragraph.

Rule format

A rule is a string composed of five fields separated by :. The fields are the following:

  1. The name of the user that must be bound (logged in) for the rule to apply. May contain wildcards such as * (see the format used by Go's path.Match). The special name ANONYMOUS applies to clients before they bind to an LDAP entity.
  2. The groups that the user must be a part of, separated by spaces. Wildcards may also be used. If several groups (or wildcard group patterns) are specified, for each pattern the user must be part of a group that matches it.
  3. The action, a subset of bind, read, add, delete, modify, modifyAdd separated by spaces. modifyAdd is a special value that only authorizes modifications that add new values to a given attribute. This can be used to allow users to add other users to a group but not remove users from the group.
  4. The target entity of the action as a pattern that may contain wildcards. The special word SELF is replaced by the entity name of the bound user before trying to match.
  5. The allowed attributes for a read, add or modify operation. This is specified as a list of patterns to include and exclude attributes, separated by spaces. A pattern that starts by ! is an exclude pattern, otherwise it is an include pattern. To read/write an attribute, it has to match at least one include pattern and not match any exclude pattern. Delete operations do not check for any attribute, thus as soon as delete is included in the allowed actions, the right to delete entities is granted.

Rule examples

  • Anybody (before binding) can bind to an entity under ou=users,dc=bottin,dc=eu: ANONYMOUS::bind:*,ou=users,dc=bottin,dc=eu:

  • Anybody (before binding) can bind to the specific admin entity: ANONYMOUS::bind:cn=admin,dc=bottin,dc=eu:

  • Anybody who is logged in can read anything that is not a userpassword attribute: *,dc=bottin,dc=eu::read:*:* !userpassword

  • Anybody can read and modify anything from their own entry: *::read modify:SELF:*

  • The admin can read, add, modify, delete anything: cn=admin,dc=bottin,dc=eu::read add modify delete:*:*

  • Members of the admin group can read, add, modify, delete anything: *:cn=admin,ou=groups,dc=bottin,dc=eu:read add modify delete:*:*