Skip to content
Snippets Groups Projects
Commit 92ba0fcc authored by Quentin Duchemin's avatar Quentin Duchemin
Browse files

Merge branch 'pica-openldap-dev' into 'master'

Pica openldap dev

See merge request !42
parents 00670398 87b94103
No related branches found
No related tags found
1 merge request!42Pica openldap dev
Showing
with 1039 additions and 0 deletions
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
pip-wheel-metadata/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
.hypothesis/
.pytest_cache/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
.python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don’t work, or not
# install all needed dependencies.
#Pipfile.lock
# celery beat schedule file
celerybeat-schedule
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
FROM alpine:3.9
WORKDIR /usr/src/app
COPY requirements.txt ./
RUN apk add --no-cache python3 && \
python3 -m ensurepip && \
rm -r /usr/lib/python*/ensurepip && \
pip3 install --upgrade pip setuptools -r requirements.txt && \
rm -r /root/.cache
# ACME Copy certs
Ce service permet l'exploitation des certificats TLS générés par Traefik
par d'autres serveurs pour leur propres besoins de chiffrement de connexions.
Traefik a la capacité d'automatiser la demande et le renouvellement de certificats
TLS auprès de services en ligne tels que [Let's Encrypt](https://letsencrypt.org)
pour assurer le chiffrement des connexions HTTPS aux sites web dont il a la charge.
L'idée est de déléguer la gestion de certificats utilisés par d'autres services dans
d'autres conteneurs à Traefik, ce qui présente les avantages suivants :
* Les certificats générés sont signés par une autorité de certification publique dont les
certificats sont déjà présents sur la plupart des systèmes, ce qui facilite la
vérification de l'authenticité des certificats par rapport à des certificats auto-signés (pas
de certificats de signature à déployer)
* A leur expiration, les certificats sont automatiquement renouvelés, ce qui élimine au tâche d'administration
et garantit la disponibilité continue des services
\ No newline at end of file
""" Traefik Certificate extraction and update module
Provides tools to extract TLS certificates from Treafik acme.json files.
Classes
-------
CertUpdater:
This class handles acme files decoding and stores or updates the resulting
certificates in separate folders.
"""
import os
import errno
import json
from base64 import b64decode
class CertUpdater:
"""Decodes acme files and extact the resulting certificates if needed.
This class keeps flat certificates for specified domain names
in sync with the contents of an acme.json file. The certificates are stored in separated
subdirectories named after the main domain name they are valid for.
If the certificates specify x509 SAN records, only the main CN is used (a single
directory is created, regardless of the number of alternate names.)
Attributes
----------
acme_file : str
Name of the acme.json file containing the certificates
names : list
List of domain names to keep updated
certs_root_path : str
Directory where the certificates will be stored. The certificates are
stored in subdirectories of this directory
Methods
-------
add(name)
Adds a domain name to the updater
remove(name)
Removes a domain name from the updater
update()
Decodes the acme.json file and updates the certicates that need to be updated.
"""
def __init__(self, acme_file, certs_root_path):
"""
Parameters
----------
acme_file : str
path of the acme file containing the certificates
certs_root_path : str
root directory where certificates will be extracted.
They are extracted in subdirectories named after the CN stored in
the certificate.
"""
self.acme_file = acme_file
self.names = []
self.certs_root_path = certs_root_path + '/'
def add(self, name):
"""Adds a domain name to the updater.
Parameters
----------
name : str
domain name to add
"""
self.names.append(name)
# Create directories etc if they do not exist
try:
os.makedirs(self.certs_root_path + name + '/')
except OSError as error:
if error.errno != errno.EEXIST:
raise
def remove(self, name):
"""Removes a domain name from the updater.
Parameters
----------
name : str
domain name to remove
"""
try:
self.names.remove(name)
except ValueError:
pass
def update(self):
"""Decodes the acme.json file and updates the certicates that need to be updated.
Returns
-------
list
a list of domain names that have been updated. An empty list if no certificate
has been updated.
"""
# Open and decode the acme file
try:
with open(self.acme_file) as acme:
data = json.loads(acme.read())
except FileNotFoundError:
print('ACME file {0} not found when trying to decode.'.format(self.acme_file))
return []
except json.JSONDecodeError:
print('File {0} does not look like an ACME file.'.format(self.acme_file))
return []
# Get the acme file version
try:
acme_ver = 2 if 'acme-v02' in data['Account']['Registration']['uri'] else 1
except TypeError:
if 'DomainsCertificate' in data:
acme_ver = 1
else:
acme_ver = 2
# Get the certificates
if acme_ver == 1:
certs = data['DomainsCertificate']['Certs']
elif acme_ver == 2:
certs = data['Certificates']
# Iterate over certificates. If a certificate has been updated,
# add its name to the updated_names.
updated_names = []
for c in certs:
if acme_ver == 1:
name = c['Certificate']['Domain']
key = c['Certificate']['PrivateKey']
fullchain = c['Certificate']['Certificate']
elif acme_ver == 2:
name = c['Domain']['Main']
key = c['Key']
fullchain = c['Certificate']
if name in self.names:
key = b64decode(key).decode('utf-8')
fullchain = b64decode(fullchain).decode('utf-8')
chain_start = fullchain.find('-----BEGIN CERTIFICATE-----', 1)
cert = fullchain[0:chain_start]
chain = fullchain[chain_start:]
print('Updating certificates for {0}'.format(name))
if self._needs_updating(name, fullchain):
path = self.certs_root_path + name + '/'
with open(path + 'privkey.pem', 'w') as f:
f.write(key)
with open(path + 'cert.pem', 'w') as f:
f.write(cert)
with open(path + 'chain.pem', 'w') as f:
f.write(chain)
with open(path + 'fullchain.pem', 'w') as f:
f.write(fullchain)
print('Certificates updated')
updated_names.append(name)
else:
print('Cetificates are already up-to-date')
return updated_names
def _needs_updating(self, name, fullchain):
"""Checks if a certificate has changed
Parameters
----------
name : str
Name of the directory containing the certificates
fullchain : str
Full certificates extracted from the acme.json file
Returns
-------
bool
True if the contents of the current certificates are
different from the fullchain. False otherwise.
"""
path = self.certs_root_path + name + '/fullchain.pem'
try:
with open(path, 'r') as f:
return f.read() != fullchain
except FileNotFoundError:
return True
\ No newline at end of file
import docker
class DockerAction:
def __init__(self, docker_client, container_id):
self.client = docker_client
self.container_id = container_id
pass
def exec(self, id):
c = self.client.container.get(self.container_id)
if c.status == 'running':
self.action(c)
def action(self, container):
pass
class RestartDockerAction(DockerAction):
def __init__(self, docker_client, container_id):
super().__init__(docker_client, container_id)
def action(self, container):
try:
container.restart()
except docker.errors.APIError:
print('Unable to restart container {0}({1})'.format(container.name,
container.id))
class KillDockerAction(DockerAction):
def __init__(self, docker_client, container_id, signal='SIGKILL'):
super().__init__(docker_client, container_id)
self.signal = signal
def action(self, container):
try:
container.kill(self.signal)
except docker.errors.APIError:
print('Unable to kill container {0}({1}) with signal {2}'
.format(container.name, self.container_id, self.signal))
\ No newline at end of file
"""Docker event monitoring
Provides a monitor for docker events that add/remove services when
they occur according to the configuration provided in the labels of these
services.
Classes
-------
DockerMonitor
A monitor for docker events.
"""
import docker
import threading
import requests
from service_manager import Service
from docker_actions import RestartDockerAction
from docker_actions import KillDockerAction
class DockerMonitor(threading.Thread):
"""Implements a docker events monitoring thread.
This class implements a thread that listen to the docker socket to intercept
events on specific containers to launch actions on these when start and stop events occur.
Containers that should be monitored are identified by labels.
"""
def __init__(self, services_mgr):
self.docker_client = docker.from_env()
self.events = None
self.services_mgr = services_mgr
super().__init__()
def run(self):
print('Starting monitor')
# Identify all running containers and get dependent services
# at startup
try:
containers = self.docker_client.containers.list()
for c in containers:
if c.status == 'running':
self._add_service(c)
# Then, wait for docker events and identify new dependent services
# or services that exit the system. Do an update each time a new
# service is added / restarted etc. or a service is changed.
self.events = self.docker_client.events(decode = True)
for event in self.events:
if 'status' not in event:
continue
try:
c = self.docker_client.containers.get(event['id'])
if event['status'] == 'stop':
self._remove_service(c)
elif event['status'] == 'start':
self._add_service(c)
except docker.errors.NotFound:
pass
except docker.errors.APIError as error:
print('Docker error while looking up container {0}: {1} '.format(event.id, error.strerror))
except requests.exceptions.ConnectionError as error:
print('Connection to docker socket refused.')
print('No more events to handle: stopping monitor')
def stop(self):
if self.events is not None:
self.events.close()
self.join()
def _get_host_from_traefik_rule(self, container):
if 'traefik.frontend.rule' in container.labels:
try:
return container.labels['traefik.frontend.rule'].split('Host:')[1].split(',')[0].strip()
except IndexError:
return ''
def _get_action_from_label(self, container):
if 'acme_copy_certs.action' not in container.labels:
return None
try:
action = container.label('acme_copy_certs.action')
if action[0].strip() == 'kill':
if len(action) == 1:
return KillDockerAction(self.docker_client, container.id, 'SIGHUP')
else:
return KillDockerAction(self.docker_client, container.id, action[1].strip())
elif action[0].strip() == 'restart':
return RestartDockerAction(self.docker_client, container.id)
else:
return None
except IndexError:
print('Invalid action')
return None
def _add_service(self, container):
if 'acme_copy_certs.enable' in container.labels:
if container.labels['acme_copy_certs.enable'] == 'true':
host = self._get_host_from_traefik_rule(container)
elif container.labels['acme_copy_certs.enable'] == 'false':
pass
else:
print('Invalid acme_copy_certs.enable value for {0}'
.format(container.name))
if host:
print('Handling container {0}({1})'.format(container.name, container.id))
s = Service(container.id, host, self._get_action_from_label(container))
self.services_mgr.add(s)
else:
print('Not handling container {0}({1})'.format(container.name, container.id))
def _remove_service(self, container):
self.services_mgr.remove(container.id)
\ No newline at end of file
import os
from watchdog.observers import Observer
from watchdog.events import PatternMatchingEventHandler
class FileWatcher:
def __init__(self, file_path, handler):
self.observer = Observer()
abs_path, file_name = os.path.split(os.path.abspath(file_path))
self.observed_path = abs_path
self.observed_file = file_name
self.handler = handler
self.event_handler = PatternMatchingEventHandler(
patterns=['*/' + file_name],
ignore_patterns=[],
ignore_directories=True)
self.event_handler.on_any_event = self._on_any_event
self.observer.schedule(self.event_handler,
self.observed_path,
recursive=False)
def _on_any_event(self, event):
self.handler()
def start(self):
print('Starting observing file {0} in directory {1}'.format(self.observed_file, self.observed_path))
self.observer.start()
def stop(self):
self.observer.stop()
self.observer.join()
\ No newline at end of file
"""Handling of docker services
This module provides a ServicesManager class implementing a collection of
services whose certificates are to be updated by the ssytem.
Classes
-------
Service
A class representing a single service
ServiceManager
A class handling a collection of services
"""
from threading import Lock
class Service:
"""
A service wrapper This object represents a docker service
Attributes
----------
id : int
The id of the container running the service
host : str
The hostname associated with the service used for CN in TLS certs
action : DockerAction
An action to perform on the service when the run_action method is
invoked.
Methods
-------
run_action()
Runs the action bound to this service
"""
def __init__(self, id, host, action):
"""
Parameters
----------
id : int
The id of the container running the service
host : str
The hostname associated with the service used for CN in TLS certs
action : DockerAction
An action to perform on the service when the run_action method is invoked.
"""
self.id = id
self.host = host
self.action = action
def run_action(self):
"""Executes the action on the container associated to the service."""
if self.action is not None:
self.action.exec(self.id)
class ServicesManager:
"""
A ServicesManager handles a collection of services and forwards certicate update
requests to a CertUpdater. If a service certificate is updated (either after adding a
service or after an explicit update request), the docker action associated
with the service is executed.
Attribures
----------
services_by_id : dict
A dictionnary of the managed services identified by their container id.
services_by_host : dict
A dictionnary of the managed services identified by the CN of their
certificates.
updater : CertUpdater object
The certificates updater in charge of handling the certificates for the managed
services
Methods
-------
add(service)
Adds a new service to the collection
remove(id)
Removes a service from the collection based on its container id
update()
Checks if any certificate for a managed service needs to be updated. If
so, performs the docker action associated with the service on its
container
"""
def __init__(self, updater):
"""
Parameters
----------
updater : CertUpdater
The CertUpdater used to handle certificates for the services
"""
self.services_by_id = {}
self.services_by_host = {}
self.updater = updater
self.lock = Lock()
def add(self, service):
""" Adds a service to the collection and enables handling of its
certificates
"""
with self.lock:
self.services_by_id[service.id] = service
if service.host not in self.services_by_host:
self.services_by_host[service.host] = [service]
else:
self.services_by_host[service.host].append(service)
self.updater.add(service.host)
self.update()
def remove(self, id):
""" Removes a service by its id from the collection and disables handling
of its certificates
"""
with self.lock:
if id in self.services_by_id:
service = self.services_by_id[id]
host = service.host
self.services_by_host[host].remove(service)
del self.services_by_id[id]
if len(self.services_by_host[host]) == 0:
del self.services_by_host[host]
self.updater.remove(host)
def update(self):
""" Updates the certificates of the services if needed. If a certificate
has changed, exectute the associated docker action on the service container.
"""
with self.lock:
for h in self.updater.update():
for s in self.services_by_host[h]:
s.run_action()
\ No newline at end of file
docker
watchdog
#!/usr/bin/env python3
import sys, os
sys.path.append(os.path.dirname(os.path.abspath(__file__)) + '/../app')
import time
import cert_updater
import file_watcher
import docker_monitor
import service_manager
if __name__ == "__main__":
u = cert_updater.CertUpdater('./acme.json', '.')
sm = service_manager.ServicesManager(u)
f = file_watcher.FileWatcher('./acme.json', sm.update)
dm = docker_monitor.DockerMonitor(sm)
f.start()
dm.start()
try:
while True:
time.sleep(1)
except KeyboardInterrupt:
dm.stop()
f.stop()
FROM osixia/openldap:1.4.0
LABEL maintainer="quentinduchemin@tuta.io,bonnest@utc.fr"
ADD bootstrap /container/service/slapd/assets/config/bootstrap
ADD environment /container/environment/01-custom
CMD [ "--copy-service" ]
# Serveur LDAP
<!-- TOC depthFrom:2 depthTo:6 withLinks:1 updateOnSave:1 orderedList:0 -->
- [Démarrage](#dmarrage)
- [Configuration](#configuration)
- [Schémas additionnels](#schmas-additionnels)
- [Entrée initiales de l'annuaire](#entre-initiales-de-lannuaire)
- [Configuration par défaut](#configuration-par-dfaut)
- [Certificats](#certificats)
- [Secrets](#secrets)
- [Plus de logs](#plus-de-logs)
- [Mise à jour de l'image](#mise-jour-de-limage)
- [Démarrage du conteneur](#dmarrage-du-conteneur)
- [Test de l'image sur un client](#test-de-limage-sur-un-client)
<!-- /TOC -->
Cette image est basée sur [osixia/openldap](https://github.com/osixia/docker-openldap). Elle est spécialisée pour les besoins de l'infrastructure de Picasoft.
Cette image **doit être lancée** aux côtés de [TLS Certs Monitor](../pica-tls-certs-monitor) pour fonctionner.
## Démarrage
Copier `pica-openldap.secrets.example` en `pica-openldap.secrets`, puis lancer :
```bash
docker-compose up -d && docker-compose logs -f
```
Notez que l'ensemble de la configuration pour préparer l'instance n'est utilisée que lors du premier lancement : elle n'aura aucun effet sur les instances existantes.
## Configuration
Par rapport à la configuration de base, cette version :
* Spécifie l'organisation Picasoft (variables d'environnement dans le Docker Compose),
* Configure un utilisateur `readonly` appellé `nss`,
* Active et force la connexion TLS au serveur LDAP,
* Ajoute les schémas additionnels (permettant d'utiliser `host` et `authorizedService`, par exemple),
* Crée la structure du base du LDAP (Group, People, Service),
* Ajoute des entrées d'exemple pour les comptes.
La configuration par défaut de l'image est dans les répertoires `bootstrap`, `environment` et `secrets`.
La table suivante donne le rôle de ces répertoires :
| Répertoire | Contenu | À quoi ça sert |
|------------|---------|----------------|
| [bootstrap/schema](./bootstap/schema) | Schémas additionnels (format ldif) | Schémas ajoutés automatiquement à l'initialisation de la configuration |
| [bootstrap/ldif](./bootstrap/ldif) | Entrées initiales de l'annuaire (format ldif) | Entrées automatiquement ajoutées à l'initialisation de l'annuaire |
| [environment](./environment) | Configuration par défaut | Définition des valeurs par défaut des variables d'environnement utilisables dans le Docker Compose |
| [secrets](./secrets) | Mots de passe | Mots de passe utilisés pour les trois utilisateurs par défaut (admin, config et nss)
Le contenu de ces répertoire est copié dans l'image à la construction. Cette copie
est supprimée par défaut une fois l'image configurée, après la première exécution du
conteneur.
### Schémas additionnels
Le fichier [ldapns.schema](./bootstrap/schema/ldapns.schema) permet le support des attributs `host` et `authorizedServices`.
### Entrée initiales de l'annuaire
Le fichier [init.ldif](./bootstrap/ldfi/init.ldif) crée la structure de base de l'annuaire :
* Une OU (Organizational Unit) `People`, pour les comptes POSIX personnels,
* Une OU `Groups`, pour les groupes POSIX,
* Une OU `Services`, pour les comptes POSIX "virtuels", destinés aux services comme Mattermost.
Il crée aussi des entrées `example` donnant un exemple pour chacun de ces types.
### Configuration par défaut
* Le fichier [pica.startup.yaml](./environment/pica.startup.yaml) contient toutes les
valeurs par défaut des paramètres de configuration. Ces valeurs sont utilisées à la
création de le configuration du serveur ldap **à la première exécution du conteneur**. Le fichier est supprimé ensuite.
* Le fichier [pica.yaml](./environment/pica.yaml) contient les paramètres de
de configuration utilisés à chaque démarrage du conteneur. Pour l'instant il est utilisé pour définir le niveau de messages de débogage de `slapd` et limiter le nombre
de descripteurs de fichiers utilisés (1024). On peut l'augmenter si on estime qu'il y aura plus de connexions simultanées sur le serveur.
### Certificats
La configuration par défaut spécifie les fichiers suivants :
| Fichier | Rôle |
|---------|------|
| cert.pem | Certificat serveur |
| chain.pem | Certificat CA |
| privkey.pem | Clé privée serveur |
### Secrets
Le repertoire [secrets](./secrets) doit contenir le fichier `pica-openldap.secrets`
avec les mots de passe en clair qui seront utilisés pour l'administrateur de
l'annuaire, l'accès à la configuration et l'accès en lecture seule. Un modèle est
fourni dans le fichier [pica-ldap.secrets.examples](./secrets/
pica-ldap.secrets.example).
| Variable | Usage |
|----------|-------|
| LDAP_ADMIN_PASSWORD | Mot de passe de l'adminsitrateur de l'annuaire (cn=admin,dc=picasoft,dc=net)|
| LDAP_CONFIG_PASSWORD | Mot de passe de configuration (cn=admin,cn=config) |
| LDAP_READONLY_USER_PASSWORD | Mot de passe de l'utilisateur *read only* (cn=nss,dc=picasoft,dc=net) |
> Attention : les mots de passe apparaîtront **en clair** dans l'environnement du conteneur.
### Plus de logs
Pour obtenir plus de logs, il suffit d'ajouter la ligne suivante au service `ldap-host` dans le fichier
[docker-compose.yml](./docker-compose.yml) :
```yaml
command: --loglevel debug
```
## Mise à jour de l'image
Il suffit de modifier la version de osixia/openldap dans le [Dockerfile](./Dockerfile) :
```Dockerfile
FROM osixia/openldap:XXX
```
Ne pas oublier de mettre à jour la version dans le [docker-compose.yml](./docker-compose.yml) :
```
image: registry.picasoft.net/pica-openldap:XXX
```
## Test de l'image sur un client
* Accès à la configuration (utiliser le mot de passe défini par `LDAP_CONFIG_PASSWORD`)
```bash
ldapsearch -Z -W -x -D cn=admin,cn=config -b cn=config
```
* Accès à l'annuaire (utiliser le mot de passe défini par `LDAP_ADMIN_PASSWORD`)
```bash
ldapsearch -Z -W -x -D cn=admin,dc=picasoft,dc=net -b dc=picasoft,dc=net
```
Ces deux commandes doivent fonctionner **dans le conteneur**. Si ce n'est pas le cas, il y
a une erreur de configuration.
dn: ou=Services,{{ LDAP_BASE_DN }}
objectClass: top
objectClass: organizationalUnit
ou: Services
description: Comptes LDAP pour les services, utiles par exemple pour envoyer des mails en leur nom
dn: ou=People,{{ LDAP_BASE_DN }}
objectClass: top
objectClass: organizationalUnit
ou: People
description: Comptes LDAP pour les humains, permettant de se connecter sur les machines par SSH
dn: ou=Groups,{{ LDAP_BASE_DN }}
objectClass: top
objectClass: organizationalUnit
ou: Groups
description: Espace pour gérer les groupes (POSIX), permettant de configurer les droits des humains
dn: cn=tech,ou=Groups,{{ LDAP_BASE_DN }}
gidNumber: 500
cn: tech
objectClass: posixGroup
objectClass: top
description: Groupe primaire. Les personnes dans ce groupe ont accès à toutes les commandes Docker sur les machines autorisées.
dn: cn=admin,ou=Groups,{{ LDAP_BASE_DN }}
gidNumber: 501
cn: admin
objectClass: posixGroup
objectClass: top
description: Groupe primaire. Les personnes dans ce groupe ont un accès root sur les machines autorisées.
dn: cn=member,ou=Groups,{{ LDAP_BASE_DN }}
gidNumber: 502
objectClass: posixGroup
objectClass: top
cn: representant
description: Groupe secondaire. Les personnes dans ce groupes peuvent accéder aux services restreints aux membres (exemple : Cloud...)
memberUid:
dn: cn=representant,ou=Groups,{{ LDAP_BASE_DN }}
gidNumber: 503
objectClass: posixGroup
objectClass: top
cn: representant
description: Groupe secondaire. Les personnes dans ce groupes sont les représentants de l'association (ou par transfert), il est utile pour les informations privées, d'ordre administratif... (exemple : Wiki)
memberUid:
dn: cn=example,ou=People,{{ LDAP_BASE_DN }}
gidNumber: -1
cn: example
objectClass: inetOrgPerson
objectClass: top
objectClass: posixAccount
objectClass: shadowAccount
objectClass: ldapPublicKey
objectClass: hostObject
objectClass: authorizedServiceObject
loginShell: /bin/bash
uidNumber:
givenName: Prénom
sn: Nom
uid: example
sshPublicKey:
homeDirectory: /home/users/example
userPassword::
authorizedService: example
host: example
shadowExpire:
description: Ce type de compte est réservé aux utilisateurs physiques, pouvant avoir un accès aux machines.
dn: cn=example,ou=Services,{{ LDAP_BASE_DN }}
objectClass: organizationalRole
objectClass: top
objectClass: simpleSecurityObject
objectClass: posixAccount
objectClass: authorizedServiceObject
homeDirectory: /dev/null
uidNumber:
gidNumber:
uid: example
cn: example
authorizedService:
userPassword::
description: Ce type de compte est réservé aux services, ne peuvent pas se connecter aux machines.
# $Id$
# LDAP Name Service Additional Schema
# http://www.iana.org/assignments/gssapi-service-names
attributetype ( 1.3.6.1.4.1.5322.17.2.1 NAME 'authorizedService'
DESC 'IANA GSS-API authorized service name'
EQUALITY caseIgnoreMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{256} )
objectclass ( 1.3.6.1.4.1.5322.17.1.1 NAME 'authorizedServiceObject'
DESC 'Auxiliary object class for adding authorizedService attribute'
SUP top
AUXILIARY
MAY authorizedService )
objectclass ( 1.3.6.1.4.1.5322.17.1.2 NAME 'hostObject'
DESC 'Auxiliary object class for adding host attribute'
SUP top
AUXILIARY
MAY host )
version: "3.7"
volumes:
ldap_db:
name: ldap_db
ldap_config:
name: ldap_config
services:
ldap:
image: registry.picasoft.net/pica-openldap:1.4.0
build: .
container_name: ldap
ports:
- "636:636"
env_file:
- ./secrets/pica-openldap.secrets
labels:
traefik.frontend.rule: "Host:ldap.picasoft.net"
traefik.enable: true
tls-certs-monitor.enable: true
tls-certs-monitor.action: "restart"
volumes:
- ldap_db:/var/lib/ldap
- ldap_config:/etc/ldap/slapd.d
- /DATA/docker/certs/ldap.picasoft.net:/container/service/slapd/assets/certs
restart: unless-stopped
# See this page to know what variables can be used : https://github.com/osixia/docker-openldap#defaultstartupyaml
# Here we only let the modified default variables
# Required and used for new ldap server only
LDAP_ORGANISATION: Picasoft
LDAP_DOMAIN: picasoft.net
LDAP_BASE_DN: #if empty automatically set from LDAP_DOMAIN
LDAP_READONLY_USER: true
LDAP_READONLY_USER_USERNAME: nss
# TLS
LDAP_TLS: true
LDAP_TLS_CRT_FILENAME: cert.pem
LDAP_TLS_KEY_FILENAME: privkey.pem
LDAP_TLS_CA_CRT_FILENAME: chain.pem
# Note 25/04 : This sets ssf to 128. Maybe it should set minssf to 128 instead to
# reject any non-encryption connexion on port 389. Testing required.
# (Indeed, when this variable is true, this file is executed :
# https://github.com/osixia/docker-openldap/blob/stable/image/service/slapd/assets/config/tls/tls-enforce-enable.ldif)
LDAP_TLS_ENFORCE: true
LDAP_TLS_VERIFY_CLIENT: never
HOSTNAME: ldap.picasoft.net
# This is the default image configuration file
# These values will persists in container environment.
# All environment variables used after the container first start
# must be defined here.
# more information : https://github.com/osixia/docker-light-baseimage
# General container configuration
# see table 5.1 in http://www.openldap.org/doc/admin24/slapdconf2.html for the available log levels.
LDAP_LOG_LEVEL: 0
# Ulimit
LDAP_NOFILE: 1024
LDAP_ADMIN_PASSWORD=admin
LDAP_CONFIG_PASSWORD=config
LDAP_READONLY_USER_PASSWORD=nss
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment