commit inicial
This commit is contained in:
commit
978e630342
6
.gitignore
vendored
Normal file
6
.gitignore
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
*~
|
||||
\#*
|
||||
.#*
|
||||
*.pyc
|
||||
*.bak
|
||||
__pycache__
|
146
README.md
Normal file
146
README.md
Normal file
@ -0,0 +1,146 @@
|
||||
# Rol openldap-docker
|
||||
|
||||
Este rol configurar un servicio OpenLDAP dentro de un container
|
||||
Docker. Permite un esquema de replicación delta-syncrepl [deltasync].
|
||||
|
||||
El rol efectúa a grandes rasgos las siguientes tareas:
|
||||
|
||||
1. _Configuración general_: setea clave del administrador
|
||||
`cn=admin,cn=config` y carga esquemas.
|
||||
|
||||
2. _Configuración de provider delta-syncrepl_: configura los módulos
|
||||
`accesslog` y `syncprov` en el provider de replicación.
|
||||
|
||||
3. _Configuración de dominios_: Hace un _loop_ sobre cada dominio
|
||||
especificado en `openldap_domains`, configurando entradas de las
|
||||
base de datos en `cn=config` y clientes de replicación del dominio.
|
||||
|
||||
La siguientes consideraciones deben ser tenidas en cuenta al usar este rol:
|
||||
|
||||
* Se deben especificar *primero* los dominios más generales
|
||||
(`ejemplo.com.ar`) y luego los más específicos
|
||||
(`sub.ejemplo.com.ar`).
|
||||
|
||||
* La base `cn=accesslog` del provider requiere que se especifique un
|
||||
usuario administrador en la variable `openldap_accesslog_admin_dn`.
|
||||
Este usuario *debe* pertenecer a alguno de los dominios
|
||||
configurados en el servidor.
|
||||
|
||||
* Se debe setear una clave para el usuario administrador
|
||||
`cn=admin,cn=config` en la variable `openldap_admin_password`.
|
||||
|
||||
## Configuración del rol
|
||||
|
||||
Las variables a continuación determinan el comportamiento del rol.
|
||||
El valor por defecto se muestra entre paréntesis.
|
||||
|
||||
### Configuraciones generales
|
||||
|
||||
* `openldap_schemas`: Schemas a cargar. Cada schema personalizado se
|
||||
debe agregar el LDIF correspondiente en
|
||||
`files/schemas/NOMBRE.ldif` dentro del rol. Valor por defecto:
|
||||
|
||||
```
|
||||
- core
|
||||
- cosine
|
||||
- nis
|
||||
- inetorgperson
|
||||
- misc
|
||||
```
|
||||
|
||||
* `openldap_create_dit_entries` (`yes`): Especifica si se debe crear
|
||||
el DIT: dominios, usuarios administradores, OUs, para cada
|
||||
dominio.
|
||||
|
||||
### Replicación
|
||||
|
||||
A no ser que se seteen las variables a continuación, el rol no
|
||||
configura replicación alguna.
|
||||
|
||||
* `openldap_provider` (`no`):
|
||||
En un esquema de replicación, el host es provider.
|
||||
|
||||
* `openldap_consumer` (`no`):
|
||||
En un esquema de replicación, el host es consumer.
|
||||
|
||||
* `openldap_replicator_base` (`dc=example,dc=com`):
|
||||
DN base a partir del cual se replica.
|
||||
|
||||
* `openldap_accesslog_dir` (`/var/lib/ldap/accesslog`):
|
||||
Directorio donde crear la base `cn=accesslog`.
|
||||
|
||||
### Credenciales
|
||||
|
||||
* `openldap_admin_password` (`password`):
|
||||
Clave del usuario administrador `cn=admin,cn=config`.
|
||||
|
||||
* `openldap_accesslog_admin_dn` (`cn=admin,dc=example,dc=com`):
|
||||
DN del usuario administrador para la base `cn=accesslog`.
|
||||
Se debe setear en el _provider_.
|
||||
|
||||
* `openldap_replicator_dn` (`cn=replicator,dc=example,dc=com`):
|
||||
DN del usuario usado para replicación.
|
||||
Se debe setear en el _provider_ y en el _consumer_.
|
||||
|
||||
* `openldap_replicator_password` (`password`):
|
||||
Clave del usuario usado para replicación.
|
||||
Se debe setear en el _provider_ y en el _consumer_.
|
||||
|
||||
### Valores por defecto para los dominios
|
||||
|
||||
* `openldap_default_db_access`: Lista con permisos de acceso.
|
||||
Valor por defecto:
|
||||
```
|
||||
- {0}to attrs=userPassword by self write by anonymous auth by * none"
|
||||
- {1}to attrs=shadowLastChange by self write by * read"
|
||||
- {2}to * by * read"
|
||||
```
|
||||
|
||||
* `openldap_default_db_index`: Lista con índices del dominio.
|
||||
Valor por defecto:
|
||||
```
|
||||
- cn,uid eq
|
||||
- member,memberUid eq
|
||||
- objectClass eq
|
||||
- uidNumber,gidNumber eq
|
||||
```
|
||||
|
||||
* `openldap_default_db_limits`: Límites de acceso al dominio.
|
||||
Valor por defecto: `[]` (lista vacía)
|
||||
|
||||
* `openldap_default_domain_ous`: OUs a crear dentro del dominio.
|
||||
Valor por defecto:
|
||||
```
|
||||
- Alias
|
||||
- Group
|
||||
- People
|
||||
```
|
||||
|
||||
### Configuración de dominios
|
||||
|
||||
La configuración de los dominios se setea en la variable
|
||||
`openldap_domains` (`[]`). Esta variable es una _lista de
|
||||
diccionarios_ en la que cada item representa un dominio.
|
||||
|
||||
Cada dominio (diccionario) tiene las siguientes claves:
|
||||
|
||||
* `name`: Obligatorio. Nombre del dominio en notación `mi.dominio.com`.
|
||||
Para este ejemplo se crea la organizacion `dc=mi,dc=ejemplo,dc=com`.
|
||||
* `admincn` (`admin`): cn del administrador, cuyo DN vendrá dado por
|
||||
`cn=admin,dc=mi,dc=ejemplo,dc=com`.
|
||||
* `adminpw` (`password`): clave del administrador del dominio.
|
||||
Se recomienda cambiar este valor, o setear la variable `adminpw_id`.
|
||||
* `adminpw_id`: ID de la clave del administrador en Rattic.
|
||||
Sobreescribe el valor de `adminpw` con la clave obtenida de Rattic.
|
||||
* `access`: lista con permisos de acceso.
|
||||
Por defecto toma el valor de la variable `openldap_default_db_access`.
|
||||
* `ou`: unidades organizacionales del dominio.
|
||||
Por defecto toma el valor de la variable `openldap_default_domain_ous`.
|
||||
|
||||
|
||||
* Si bien se crea un usuario admin para los subdominios, éstos por
|
||||
defecto no tienen permiso alguno.
|
||||
|
||||
## Referencias
|
||||
|
||||
[deltasync]: https://openldap.org/doc/admin24/replication.html#Delta-syncrepl
|
486
action_plugins/ldap.py
Normal file
486
action_plugins/ldap.py
Normal file
@ -0,0 +1,486 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
|
||||
from ansible.plugins.action import ActionBase
|
||||
from ansible.errors import AnsibleError
|
||||
from ansible.utils.vars import merge_hash
|
||||
from ansible.module_utils._text import to_text
|
||||
from ansible.module_utils.parsing.convert_bool import boolean
|
||||
import sys
|
||||
import re
|
||||
import base64
|
||||
import io
|
||||
try:
|
||||
import ldap
|
||||
import ldif
|
||||
HAS_LDAP = True
|
||||
|
||||
class MyLDIF(ldif.LDIFParser):
|
||||
'''Clase auxiliar para extraer información de LDIF'''
|
||||
def __init__(self,input):
|
||||
ldif.LDIFParser.__init__(self,input)
|
||||
self._entries = []
|
||||
def handle(self,dn,entry):
|
||||
self._entries.append((dn,entry))
|
||||
def entries(self):
|
||||
return self._entries
|
||||
|
||||
except ImportError:
|
||||
HAS_LDAP = False
|
||||
|
||||
class LDAPException(Exception):
|
||||
'''Excepción genérica de este módulo'''
|
||||
pass
|
||||
|
||||
# ---- funciones para chequeo de claves, sacado de
|
||||
# http://www.openldap.org/faq/data/cache/347.html
|
||||
|
||||
import os
|
||||
import hashlib
|
||||
from base64 import encodestring as b64encode
|
||||
from base64 import decodestring as b64decode
|
||||
|
||||
def makeSecret(password):
|
||||
salt = os.urandom(4)
|
||||
h = hashlib.sha1(password)
|
||||
h.update(salt)
|
||||
return "{SSHA}" + b64encode(h.digest() + salt)[:-1]
|
||||
|
||||
def checkPassword(challenge_password, password):
|
||||
try:
|
||||
challenge_bytes = b64decode(challenge_password[6:])
|
||||
digest = challenge_bytes[:20]
|
||||
salt = challenge_bytes[20:]
|
||||
hr = hashlib.sha1(password)
|
||||
hr.update(salt)
|
||||
return digest == hr.digest()
|
||||
except:
|
||||
pass
|
||||
return False
|
||||
|
||||
|
||||
class ActionModule(ActionBase):
|
||||
|
||||
# Nombre de los argumentos
|
||||
ARG_DN = 'dn'
|
||||
ARG_OBJCLS = 'objectClass'
|
||||
ARG_ATTRS = 'attributes'
|
||||
ARG_FILTER = 'filter'
|
||||
ARG_LDIF = 'from_ldif'
|
||||
ARG_REMOTE = 'remote_src'
|
||||
ARG_RDN = 'dn_relative'
|
||||
ARG_CURLY = 'dn_curly_hack'
|
||||
ARG_BINDDN = 'bind_dn'
|
||||
ARG_BINDPW = 'bind_pw'
|
||||
ARG_SRVURI = 'server_uri'
|
||||
ARG_TLS = 'start_tls'
|
||||
ARG_VALCRT = 'validate_certs'
|
||||
ARG_STYPE = 'search_type'
|
||||
ARG_STATE = 'state'
|
||||
|
||||
# Atributos que representan claves
|
||||
ATTR_PASSWORD = ('olcRootPW', 'userPassword')
|
||||
|
||||
# Atributos que no se pueden eliminar
|
||||
ATTR_FRAGILE = ('olcModuleLoad','olcAttributeTypes')
|
||||
|
||||
# Atributos RDN, no se pueden modificar
|
||||
ATTR_RDN = ()
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(ActionModule, self).__init__(*args,**kwargs)
|
||||
self.result= dict(failed=False,changed=False,action_history=[])
|
||||
self.dn = None
|
||||
self.attrs = dict()
|
||||
self.entries = []
|
||||
|
||||
|
||||
def _read_args(self):
|
||||
'''
|
||||
Leer argumentos y efectuar búsqueda inicial del DN
|
||||
'''
|
||||
|
||||
# Leer DN y parametros de busqueda
|
||||
self.req_dn = self._task.args.get(self.ARG_DN, None)
|
||||
self.dn_relative = self._task.args.get(self.ARG_RDN, False)
|
||||
self.search_filter = self._task.args.get(self.ARG_FILTER, '(objectClass=*)')
|
||||
self.search_type = self._task.args.get(self.ARG_STYPE, 'sub')
|
||||
self.state = self._task.args.get(self.ARG_STATE, 'present')
|
||||
self.oc = self._task.args.get(self.ARG_OBJCLS, None)
|
||||
self.req_attrs = self._task.args.get(self.ARG_ATTRS, {})
|
||||
|
||||
if self._task.args.get(self.ARG_LDIF, []):
|
||||
'''Leer LDIF pasado como argumento y usarlo para configurar entrada'''
|
||||
self._load_from_ldif()
|
||||
|
||||
if self.dn_relative:
|
||||
'''Setear base de busqueda al parent de req_dn'''
|
||||
reqdn = ldap.dn.str2dn(self.req_dn)
|
||||
rdnkey = reqdn[0][0][0]
|
||||
rdnval = reqdn[0][0][1]
|
||||
if boolean(self._task.args.get(self.ARG_CURLY,False), strict=False):
|
||||
rdnval = '{*}' + rdnval
|
||||
|
||||
self.search_filter = '(&{}({}={}))'.format(
|
||||
self.search_filter, rdnkey, rdnval)
|
||||
self.search_base = ldap.dn.dn2str(reqdn[1:])
|
||||
else:
|
||||
self.search_base = self.req_dn
|
||||
|
||||
# Validar params obligatorios
|
||||
if not self.req_dn:
|
||||
raise LDAPException("'{}' argument is mandatory, got value: {}".format(self.ARG_DN,self.req_dn))
|
||||
if not self.oc:
|
||||
raise LDAPException("'{}' argument is mandatory".format(self.ARG_OBJCLS))
|
||||
|
||||
# parametros de conexion al servidor
|
||||
self.bdn = self._task.args.get(self.ARG_BINDDN, None)
|
||||
self.bpw = self._task.args.get(self.ARG_BINDPW, None)
|
||||
self.uri = self._task.args.get(self.ARG_SRVURI, 'ldapi:///')
|
||||
self.tls = self._task.args.get(self.ARG_TLS, False)
|
||||
self.valcert = self._task.args.get(self.ARG_VALCRT, True)
|
||||
|
||||
# Armar comando ldapsearch
|
||||
self.ldapsearch_cmd = 'ldapsearch -LLL -H {}'.format(self.uri)
|
||||
if self.bdn and self.bpw:
|
||||
self.ldapsearch_cmd = '{} -D {} -w "{}"'.format(
|
||||
self.ldapsearch_cmd, self.bdn, self.bpw)
|
||||
else:
|
||||
self.ldapsearch_cmd = "{} -Y EXTERNAL".format(
|
||||
self.ldapsearch_cmd)
|
||||
if not self.valcert:
|
||||
self.ldapsearch_cmd = 'LDAPTLS_REQCERT=never {}'.format(self.ldapsearch_cmd)
|
||||
if self.tls:
|
||||
self.ldapsearch_cmd = '{} -ZZ'.format(self.ldapsearch_cmd)
|
||||
|
||||
# Armar parametros para módulos ldap_entry, ldap_attr
|
||||
self.ldap_params = {
|
||||
'bind_dn': self.bdn,
|
||||
'bind_pw': self.bpw,
|
||||
'server_uri': self.uri,
|
||||
'start_tls': self.tls,
|
||||
'validate_certs': self.valcert
|
||||
}
|
||||
|
||||
|
||||
def _load_from_ldif(self):
|
||||
'''Configurar argumentos a partir de archivo LDIF'''
|
||||
|
||||
# copiado en buena parte de https://github.com/ansible/ansible/blob/devel/lib/ansible/plugins/action/unarchive.py
|
||||
source = self._task.args.get(self.ARG_LDIF)
|
||||
remote_src = boolean(self._task.args.get(self.ARG_REMOTE, False),
|
||||
strict=False)
|
||||
ldif_content = io.StringIO()
|
||||
|
||||
# el ldif es local
|
||||
if not remote_src:
|
||||
'''encontrar nombre real del archivo'''
|
||||
try:
|
||||
source = self._loader.get_real_file(self._find_needle('files',source))
|
||||
except AnsibleError as e:
|
||||
raise AnsibleActionFail(to_text(e))
|
||||
'''leer archivo'''
|
||||
with open(source,'r') as f:
|
||||
ldif_content.write(unicode(f.read()))
|
||||
|
||||
# el ldif es remoto
|
||||
else:
|
||||
try:
|
||||
slurp = self._execute_module(module_name='slurp',module_args=dict(src=source))
|
||||
if slurp.get('failed',False):
|
||||
raise AnsibleError('Failed reading remote file!')
|
||||
if slurp.get('encoding') == 'base64':
|
||||
ldif_content.write(unicode(b64decode(slurp['content'])))
|
||||
else:
|
||||
raise AnsibleError('Unknown encoding returned from slurp, this is a bug.')
|
||||
except AnsibleError as e:
|
||||
raise AnsibleActionFail('Error reading remote file: {}'.format(e.message))
|
||||
|
||||
ldif_content.write(u'\n\n')
|
||||
ldif_content.seek(0)
|
||||
parser = MyLDIF(ldif_content)
|
||||
parser.parse()
|
||||
|
||||
# Sólo se soporta UNA entrada por archivo LDIF
|
||||
entry = parser.entries()[0]
|
||||
self.result['entry'] = entry
|
||||
self.req_dn = entry[0]
|
||||
self.req_attrs = entry[1].copy()
|
||||
del self.req_attrs['objectClass']
|
||||
self.oc = entry[1].get('objectClass',None)
|
||||
# self.result['req_attr'] = self.req_attrs
|
||||
|
||||
|
||||
def _ldapsearch(self, args):
|
||||
'''Ejecutar comando ldapsearch y devolver el resultado'''
|
||||
command = "{} {}".format(self.ldapsearch_cmd,args)
|
||||
result = {}
|
||||
failed = False
|
||||
try:
|
||||
result = self._execute_module(
|
||||
module_name='command',
|
||||
module_args=dict(_raw_params=command))
|
||||
failed = False if result['rc'] in (0, 32) else result.get('failed',False)
|
||||
except Exception as e:
|
||||
raise LDAPException("Failed command: {}, message: {}".format(
|
||||
command, e.message))
|
||||
if failed:
|
||||
raise LDAPException("Failed command: {}, message: {}".format(
|
||||
command, result['stderr']))
|
||||
result['failed'] = failed
|
||||
return result
|
||||
|
||||
|
||||
def _find_dn(self):
|
||||
'''
|
||||
Encontrar DN que matchea rdnkey=rdnval dentro del DN self.parent,
|
||||
aplicando el filtro de búsqueda en self.sfilter.
|
||||
'''
|
||||
|
||||
self.search_type = 'base'
|
||||
if self.dn_relative:
|
||||
self.search_type = 'one'
|
||||
|
||||
# Ejecutar comando
|
||||
entries = self.search()
|
||||
|
||||
# DN encontrado
|
||||
founddn = None
|
||||
if len(entries):
|
||||
founddn = entries[0][0]
|
||||
|
||||
# registrar resultados y salir
|
||||
self.result['action_history'].append({
|
||||
'function': 'finddn',
|
||||
'outcome': ('found' if founddn else 'not found'),
|
||||
})
|
||||
self.dn = founddn
|
||||
|
||||
|
||||
def search(self):
|
||||
'''
|
||||
Efectuar búsqueda, retornar entrada(s) encontradas
|
||||
'''
|
||||
|
||||
qfilter = '(objectClass=*)'
|
||||
# armar el filtro de búsqueda agregando los filtros adicionales
|
||||
if isinstance(self.search_filter, basestring):
|
||||
qfilter = self.search_filter
|
||||
else:
|
||||
for f in self.search_filter:
|
||||
qfilter = '(&{}{})'.format(qfilter,f)
|
||||
|
||||
# efectuar búsqueda mediante ldapsearch
|
||||
cmd_args = '-s {} -b "{}" "{}"'.format(self.search_type, self.search_base, qfilter)
|
||||
result = self._ldapsearch(cmd_args)
|
||||
|
||||
entries = []
|
||||
if result['rc'] in (0,32):
|
||||
# procesar la salida LDIF
|
||||
ldif_out = io.StringIO(result['stdout']+'\n\n')
|
||||
parser = MyLDIF(ldif_out)
|
||||
parser.parse()
|
||||
entries = parser.entries()
|
||||
self.result['action_history'].append({
|
||||
'function': 'search',
|
||||
'outcome': ('found' if len(entries) else 'no results found'),
|
||||
'arguments': cmd_args
|
||||
})
|
||||
|
||||
else:
|
||||
self.result['action_history'].append({
|
||||
'function': 'search',
|
||||
'outcome': 'failed',
|
||||
'arguments': cmd_args
|
||||
|
||||
})
|
||||
return entries
|
||||
|
||||
|
||||
def add_entry(self):
|
||||
'''
|
||||
Agregar entrada al directorio llamando al módulo ldap_entry.
|
||||
Los datos se obtienen de los argumentos pasados al módulo.
|
||||
'''
|
||||
|
||||
attrs = dict(self.req_attrs)
|
||||
for attr in attrs.keys():
|
||||
if attr in self.ATTR_PASSWORD:
|
||||
attrs[attr] = makeSecret(attrs[attr])
|
||||
|
||||
result = {}
|
||||
failed = False
|
||||
changed = False
|
||||
args = dict( dn=self.req_dn,
|
||||
objectClass=self.oc,
|
||||
attributes=attrs,
|
||||
params = self.ldap_params
|
||||
)
|
||||
try:
|
||||
# llamar a ldap_entry con argumentos dn, objectClass, attributes
|
||||
result = self._execute_module(
|
||||
module_name='ldap_entry',
|
||||
module_args=args
|
||||
)
|
||||
failed = result.get('failed',False)
|
||||
changed = result.get('changed',False)
|
||||
except Exception as e:
|
||||
raise LDAPException("Error invoking ldap_entry: {}: {}".format(
|
||||
type(e).__name__,to_text(e)))
|
||||
if failed:
|
||||
raise LDAPException("Error invoking ldap_entry, args: {}, message: {}".format(
|
||||
args, result))
|
||||
|
||||
# registrar resultados
|
||||
if changed:
|
||||
self.result['changed'] = True
|
||||
self.result['action_history'].append({
|
||||
'function': 'add_entry',
|
||||
'outcome': 'failed' if failed else 'success',
|
||||
'result': result
|
||||
})
|
||||
|
||||
# actualizar self.dn con el DN de la entrada recién creada
|
||||
self._find_dn()
|
||||
|
||||
|
||||
def findattr(self):
|
||||
'''
|
||||
Leer atributos de la entrada con ldapsearch.
|
||||
'''
|
||||
|
||||
# efectuar búsqueda
|
||||
cmd_args = '-s base -b "{}"'.format(self.dn)
|
||||
result = self._ldapsearch(cmd_args)
|
||||
|
||||
# Leer LDIF resultante
|
||||
ldif_out = io.StringIO(result['stdout'])
|
||||
parser = MyLDIF(ldif_out)
|
||||
parser.parse()
|
||||
entries = parser.entries()
|
||||
|
||||
# validar que el resultado se una única entrada
|
||||
if len(entries) != 1:
|
||||
raise LDAPException("Unable to read attributes for DN={}, cmd={}, entries={}".format(self.dn,cmd,entries))
|
||||
if entries[0][0] != self.dn:
|
||||
raise LDAPException("Unexpected DN={} while reading entry attributes, expected {}".format(
|
||||
entries[0][0], self.dn))
|
||||
|
||||
# registrar invocación y guardar self.attrs
|
||||
self.result['action_history'].append({
|
||||
'function': 'findattr',
|
||||
'outcome': 'success',
|
||||
'result': result
|
||||
})
|
||||
self.attrs = entries[0][1]
|
||||
|
||||
|
||||
def updateattr(self, attribute, value):
|
||||
'''Verificar/actualizar valor para un atributo'''
|
||||
|
||||
if attribute in self.ATTR_PASSWORD:
|
||||
if not isinstance(value, basestring):
|
||||
value = value[0]
|
||||
if checkPassword(self.attrs.get(attribute,[None])[0], value):
|
||||
# si la clave matchea, salir
|
||||
self.result['action_history'].append({
|
||||
'function': 'updateattr',
|
||||
'arguments': [attribute, 'hidden password'],
|
||||
'outcome': 'password not changed'
|
||||
})
|
||||
return
|
||||
else:
|
||||
# setear el valor al hash salteado
|
||||
value = [makeSecret(value)]
|
||||
|
||||
# convertir el valor a una lista
|
||||
if isinstance(value, basestring):
|
||||
value = [value]
|
||||
|
||||
changed = False
|
||||
failed = False
|
||||
result = None
|
||||
args = {
|
||||
'dn': self.dn,
|
||||
'name': attribute,
|
||||
'values': value,
|
||||
'state': 'present' if attribute in self.ATTR_FRAGILE else 'exact',
|
||||
'params': self.ldap_params
|
||||
}
|
||||
if set(value) != set(self.attrs.get(attribute,[])):
|
||||
# actualizar valor llamando a ldap_attr
|
||||
try:
|
||||
result = self._execute_module(
|
||||
module_name='ldap_attr',
|
||||
module_args=args
|
||||
)
|
||||
changed = result.get('changed',False)
|
||||
failed = result.get('failed',False)
|
||||
except Exception as e:
|
||||
raise LDAPException("Error invoking ldap_attr: {}: {}, result = {}".format(
|
||||
type(e).__name__,to_text(e),result))
|
||||
|
||||
# guardar resultados
|
||||
if changed:
|
||||
self.result['changed'] = True
|
||||
if failed:
|
||||
self.result['failed'] = True
|
||||
self.result['action_history'].append({
|
||||
'function': 'updateattr',
|
||||
'arguments': [attribute, value],
|
||||
'outcome': 'failed' if failed else ('changed' if changed else 'unchanged'),
|
||||
'result': result
|
||||
})
|
||||
|
||||
|
||||
def run(self, tmp=None, task_vars={}):
|
||||
'''Función principal, punto de entrada al plugin'''
|
||||
|
||||
# Llamar a la función run de la clase madre
|
||||
result = super(ActionModule, self).run(tmp, task_vars)
|
||||
|
||||
# Verificar si debo salir o fallar
|
||||
if result.get('skipped', False) or result.get('failed', False):
|
||||
return result
|
||||
|
||||
# Asegurar que la importación LDAP no falló, salir con gracia
|
||||
if not HAS_LDAP:
|
||||
return merge_hash( self.result, {
|
||||
'failed': True,
|
||||
'msg': "Missing required 'ldap' module (pip install python-ldap)."
|
||||
})
|
||||
|
||||
try:
|
||||
# leer argumentos
|
||||
self._read_args()
|
||||
|
||||
# si solo se requiere buscar, efectuar busqueda y salir
|
||||
if self.state == 'search':
|
||||
self.result.update(dict(entries=self.search()))
|
||||
return self.result
|
||||
|
||||
# busqueda inicial del DN
|
||||
self._find_dn()
|
||||
|
||||
if self.dn is None:
|
||||
# el DN no existe, crear la entrada
|
||||
self.add_entry()
|
||||
|
||||
else:
|
||||
# el DN existe, verificar atributos
|
||||
attrs = self.findattr()
|
||||
|
||||
# verificar/actualizar cada atributo
|
||||
for attr in self.req_attrs.keys():
|
||||
self.updateattr(attr, self.req_attrs[attr])
|
||||
|
||||
except LDAPException as e:
|
||||
return merge_hash( self.result, {
|
||||
'failed': True,
|
||||
'msg': "Invalid arguments: {}".format(e.message)
|
||||
})
|
||||
|
||||
self.result['dn'] = self.dn
|
||||
self.result['attrs'] = self.attrs
|
||||
return self.result
|
94
defaults/main.yml
Normal file
94
defaults/main.yml
Normal file
@ -0,0 +1,94 @@
|
||||
---
|
||||
# Indica si el host es provider en un esquema de replicación
|
||||
openldap_provider: no
|
||||
|
||||
# Nombre del provider para un host consumer
|
||||
openldap_provider_host: null
|
||||
|
||||
# Indica si el host es esclavo en un esquema de replicación
|
||||
openldap_consumer: no
|
||||
|
||||
# Clave del usuario cn=admin,cn=config
|
||||
openldap_admin_password: password
|
||||
|
||||
# DN del administrador de la base cn=accesslog (provider)
|
||||
openldap_backup_dir: "/srv/backups/ldap"
|
||||
openldap_backup_keep: 200
|
||||
|
||||
# DN del administrador de la base cn=accesslog (provider)
|
||||
openldap_accesslog_admin_dn: cn=admin,dc=example,dc=com
|
||||
|
||||
# DN, clave y search base del replicador
|
||||
openldap_replicator_dn: cn=replicator,dc=example,dc=com
|
||||
openldap_replicator_password: password
|
||||
openldap_replicator_base: dc=example,dc=com
|
||||
|
||||
# port in docker HOST to bind ldap service
|
||||
openldap_bind_port: 10389
|
||||
openldap_bind_host: "localhost"
|
||||
|
||||
# docker image name
|
||||
openldap_image_name: "i-openldap"
|
||||
|
||||
# docker container name
|
||||
openldap_container_name: "c-openldap"
|
||||
|
||||
# docker volume names
|
||||
openldap_volume_config: "ldap_config"
|
||||
openldap_volume_data: "ldap_data"
|
||||
openldap_volume_backup: "ldap_backup"
|
||||
|
||||
# permisos de acceso por defecto
|
||||
openldap_default_db_access:
|
||||
- "{0}to attrs=userPassword by self write by anonymous auth by * none"
|
||||
- "{1}to attrs=shadowLastChange by self write by * read"
|
||||
- "{2}to * by * read"
|
||||
|
||||
# indices por defecto
|
||||
openldap_default_db_index:
|
||||
- "cn,uid eq"
|
||||
- "member,memberUid eq"
|
||||
- "objectClass eq"
|
||||
- "uidNumber,gidNumber eq"
|
||||
|
||||
# limites por defecto: ninguno
|
||||
openldap_default_db_limits: []
|
||||
|
||||
# OUs creadas por defecto dentro de cada dominio
|
||||
openldap_default_domain_ous:
|
||||
- Alias
|
||||
- Group
|
||||
- People
|
||||
|
||||
# Dominios a configurar:
|
||||
# Cada dominio se especifica en un diccionario con las siguientes claves:
|
||||
# - name: nombre del dominio (ejemplo.com) (obligatorio)
|
||||
# a partir de este nombre se crea la organizacion dc=ejemplo,dc=com
|
||||
# - admincn: nombre cn del administrador (defecto=admin)
|
||||
# este cn deriva en un DN cn=admin,dc=ejemplo,dc=com
|
||||
# - adminpw: clave del administrador de este dominio (defecto=password)
|
||||
# SE RECOMIENDA CAMBIAR ESTE VALOR, O SETEAR ID RATTIC
|
||||
# - access: lista con permisos de acceso. por defecto es la lista definida
|
||||
# en la variable openldap_default_db_access
|
||||
# - ou: unidades organizacionales del dominio, defecto openldap_default_domain_ous
|
||||
openldap_domains: []
|
||||
# - name: unl.edu.ar
|
||||
# - name: rectorado.unl.edu.ar
|
||||
# - name: servicios.unl.edu.ar
|
||||
|
||||
# Directorio donde crear la base cn=accesslog
|
||||
openldap_accesslog_dir: "/var/lib/ldap/accesslog"
|
||||
|
||||
# esquemas a cargar
|
||||
openldap_schemas:
|
||||
- core
|
||||
- cosine
|
||||
- nis
|
||||
- inetorgperson
|
||||
- misc
|
||||
|
||||
# crear entradas en el DIT? (dominios, administradores, OUs)
|
||||
openldap_create_dit_entries: yes
|
||||
|
||||
# habilitar modulo memberof?
|
||||
openldap_enable_memberof: yes
|
40
files/Dockerfile
Normal file
40
files/Dockerfile
Normal file
@ -0,0 +1,40 @@
|
||||
FROM debian:stable-slim
|
||||
|
||||
MAINTAINER Mauro Torrez <contact@mau.ro>
|
||||
|
||||
ENV OPENLDAP_ROOT_PASSWORD="root"
|
||||
|
||||
# space-separated list of schemas
|
||||
ENV OPENLDAP_SCHEMAS="misc"
|
||||
|
||||
ENV OPENLDAP_BACKUP_MIN="0"
|
||||
ENV OPENLDAP_BACKUP_HOUR="1"
|
||||
ENV OPENLDAP_BACKUP_DOM="*"
|
||||
ENV OPENLDAP_BACKUP_MON="*"
|
||||
ENV OPENLDAP_BACKUP_DOW="*"
|
||||
# TODO configurar Cron de backup
|
||||
|
||||
RUN apt-get update && \
|
||||
DEBIAN_FRONTEND=noninteractive apt-get install -y \
|
||||
slapd \
|
||||
ldap-utils && \
|
||||
apt-get clean && \
|
||||
rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
|
||||
|
||||
COPY entrypoint.sh /entrypoint.sh
|
||||
|
||||
# TODO backup and restore
|
||||
# ADD ldap_backup.sh /usr/local/sbin/ldap_backup.sh
|
||||
# ADD ldap_restore.sh /usr/local/sbin/ldap_restore.sh
|
||||
|
||||
# add my_custom_schema: install by setting OPENLDAP_SCHEMAS=my_custom_schema
|
||||
# COPY my_custom_schema.ldif /etc/ldap/schema/my_custom_schema.ldif
|
||||
|
||||
EXPOSE 389
|
||||
|
||||
VOLUME ["/etc/ldap/slapd.d", "/var/lib/ldap", "/var/backups/ldap"]
|
||||
|
||||
ENTRYPOINT ["/bin/bash", "/entrypoint.sh"]
|
||||
# log level info:
|
||||
|
||||
CMD ["slapd", "-d", "32768", "-u", "openldap", "-g", "openldap"]
|
85
files/entrypoint.sh
Executable file
85
files/entrypoint.sh
Executable file
@ -0,0 +1,85 @@
|
||||
#!/bin/bash
|
||||
msg(){ ${VERBOSE:-true} && echo ${@} ; }
|
||||
assert(){ [[ $? -eq 0 ]] || { [[ -n ${1} ]] && msg ${@} ; exit 1 ; } }
|
||||
|
||||
# from https://github.com/dinkel/docker-openldap/blob/master/entrypoint.sh:
|
||||
# When not limiting the open file descritors limit, the memory consumption of
|
||||
# slapd is absurdly high. See https://github.com/docker/docker/issues/8231
|
||||
ulimit -n 8192
|
||||
|
||||
msg "I: running slapd for initial setup..."
|
||||
slapd -u openldap -g openldap -h ldapi:///
|
||||
assert "E: openldap died unexpectedly!"
|
||||
|
||||
PIDFILE=$(ldapsearch -LLL -Y EXTERNAL -H ldapi:/// -b "cn=config" -s base \
|
||||
"" olcPidFile | grep olcPidFile | awk "{print $2}")
|
||||
msg "I: slapd running with PID ${PIDFILE}"
|
||||
|
||||
[[ -n "${OPENLDAP_ADMIN_PASSWORD}" ]]
|
||||
assert "E: please set non-empty password in OPENLDAP_ADMIN_PASSWORD and retry."
|
||||
|
||||
HASHED_PW=$(slappasswd -h {SSHA} -s "${OPENLDAP_ADMIN_PASSWORD}")
|
||||
[[ -n "${HASHED_PW}" ]]
|
||||
assert "E: password hash unexpectedly empty!"
|
||||
|
||||
msg "I: Setting administrator password..."
|
||||
ldapmodify -Y EXTERNAL -H ldapi:/// <<EOF
|
||||
dn: olcDatabase={0}config,cn=config
|
||||
changetype: modify
|
||||
replace: olcRootPW
|
||||
olcRootPW: ${HASHED_PW}
|
||||
|
||||
EOF
|
||||
assert "FATAL: failure setting administrator password!"
|
||||
|
||||
# find current schemas
|
||||
eval "declare -A LOADED_SCHEMAS=( $(ldapsearch -LLL -Y EXTERNAL -H ldapi:/// \
|
||||
-b "cn=schema,cn=config" -s one cn \
|
||||
| sed -n 's/^cn:.*[{].*[}]\(.*\)$/[\1]=loaded/p') )"
|
||||
msg "I: currently loaded schemas: ${!LOADED_SCHEMAS[@]}"
|
||||
|
||||
# load schemas
|
||||
# built-in: core, cosine, nis, inetorgperson
|
||||
# available: collective, corba, duaconf, dyngroup, java, misc, nis, openldap, pmi, ppolicy
|
||||
for schema in ${OPENLDAP_SCHEMAS}
|
||||
do
|
||||
[[ -z "${LOADED_SCHEMAS[$schema]}" ]] || continue;
|
||||
msg "I: loading schema ${schema}..."
|
||||
[[ -f /etc/ldap/schema/${schema}.ldif ]]
|
||||
assert "E: schema /etc/ldap/schema/${schema}.ldif not found!"
|
||||
ldapadd -Y EXTERNAL -H ldapi:/// -f /etc/ldap/schema/${schema}.ldif
|
||||
assert "E: failure loading schema ${schema}!"
|
||||
done
|
||||
|
||||
# enable memberof module
|
||||
if ${OPENLDAP_ENABLE_MEMBEROF}
|
||||
then
|
||||
msg "I: enabling memberof module ..."
|
||||
ldapmodify -LLL -Y EXTERNAL -H ldapi:/// <<EOF
|
||||
dn: cn=module{0},cn=config
|
||||
changetype: modify
|
||||
add: olcModuleLoad
|
||||
olcModuleLoad: memberof
|
||||
|
||||
EOF
|
||||
RES=$?
|
||||
[[ $RES -eq 0 ]] || [[ $RES -eq 20 ]]
|
||||
assert "E: failed loading memberof module (${RES})"
|
||||
msg "I: module memberof enabled (${RES})"
|
||||
unset RES
|
||||
fi
|
||||
|
||||
# kill slapd after initial setup
|
||||
msg "I: killing initial server..."
|
||||
kill -INT $(cat ${PIDFILE})
|
||||
|
||||
# unset sensitive variables
|
||||
unset OPENLDAP_ROOT_PASSWORD
|
||||
unset HASHED_PW
|
||||
unset LOADED_SCHEMAS
|
||||
unset PIDFILE
|
||||
|
||||
# run Dockerfile CMD
|
||||
msg "I: running CMD $@"
|
||||
set -e
|
||||
exec "$@"
|
304
tasks/domain.yml
Normal file
304
tasks/domain.yml
Normal file
@ -0,0 +1,304 @@
|
||||
---
|
||||
# Configuración de un dominio en el DIT.
|
||||
# Se distinguen 3 partes en este playbook:
|
||||
# 1) Verificar si el dominio es parte de otro ya existente.
|
||||
# 2) Si el dominio especificado NO es subdominio de otro, se agrega una entrada
|
||||
# correspondiente en cn=config.
|
||||
# 3) Se agrega el usuario admin para el dominio y las OUs
|
||||
# respectivas, por defecto: People, Group, Alias
|
||||
|
||||
# parte 1: chequear si el dominio es subdominio de otro -----------------------
|
||||
|
||||
- name: "(aux) separar DN en componentes"
|
||||
set_fact:
|
||||
# componentes del dominio
|
||||
dcs: "{{ domain.name.split('.') }}"
|
||||
# dominio convertido a DN
|
||||
ddn: "{{ domain.name.split('.')|map('regex_replace','^','dc=')|join(',') }}"
|
||||
|
||||
- name: "Buscar entradas en cn=config para {{ domain.name }} y superiores"
|
||||
ldap:
|
||||
state: "search"
|
||||
dn: "cn=config"
|
||||
objectClass: "olcDatabaseConfig"
|
||||
filter: "(olcSuffix=*)"
|
||||
bind_dn: "cn=admin,cn=config"
|
||||
bind_pw: "{{ openldap_admin_password }}"
|
||||
server_uri: "ldap://localhost:{{ openldap_bind_port }}"
|
||||
register: "sfxsearch"
|
||||
|
||||
- name: "(aux) matchear resultados de búsqueda"
|
||||
set_fact:
|
||||
sfxmatches: "[ {% for i in range(dcs|length -1, -1, -1) %}\
|
||||
{% set sdn = dcs[i:]|map('regex_replace','^','dc=')|join(',') %}\
|
||||
{% for e in sfxsearch.entries if e[1]['olcSuffix'][0] == sdn %}\
|
||||
{{ { 'dn': sdn, 'name': dcs[i:]|join('.'), 'configdn': e[0] } }},\
|
||||
{% endfor %}\
|
||||
{% endfor %}\
|
||||
]"
|
||||
|
||||
- name: "(aux) setear dominio superior y credenciales"
|
||||
set_fact:
|
||||
superd: "{{ sfxmatches | first | default({'name': domain.name, 'dn': ddn}) }}"
|
||||
# DN del admin y clave
|
||||
admindn: "cn={{ domain.admincn | default('admin') }},{{ ddn }}"
|
||||
adminpw: "{{ domain.adminpw | default('password') }}"
|
||||
|
||||
- name: "(aux) mergear detalles de dominio superior"
|
||||
set_fact:
|
||||
super: "{{ openldap_domains | selectattr('name', 'equalto', superd.name) | \
|
||||
first | combine(superd) }}"
|
||||
|
||||
# parte 2: agregar entrada en cn=config ---------------------------------------
|
||||
|
||||
- name: "Entrada en cn=config para {{ domain.name }}"
|
||||
# Cuando el dominio no es hijo de otro ya existente, crear entrada en cn=config
|
||||
when: "super.dn == ddn"
|
||||
block:
|
||||
|
||||
- name: "(aux) setear propiedades"
|
||||
set_fact:
|
||||
olcDbIndex: "{{ domain.index | default(openldap_default_db_index) }}"
|
||||
olcAccess: "{{ domain.access | default(openldap_default_db_access) }}"
|
||||
backup_domains: "{{ backup_domains | default([]) | union([ddn]) }}"
|
||||
|
||||
- name: "(aux) validar que entryUUID esté en olcDbIndex (necesario para replicar)"
|
||||
when:
|
||||
- "openldap_provider == True"
|
||||
- "olcDbIndex | map('regex_search','entryUUID.* eq$') | reject('equalto',[]) | list | length == 0"
|
||||
set_fact:
|
||||
olcDbIndex: "{{ olcDbIndex | union(['entryUUID eq']) }}"
|
||||
|
||||
- name: "(aux) propiedad olcAccess (provider)"
|
||||
when:
|
||||
- "openldap_provider == True"
|
||||
- "domain.access_provider is defined"
|
||||
set_fact:
|
||||
olcAccess: "{{ domain.access_provider }}"
|
||||
|
||||
- name: "(aux) propiedad olcAccess (consumer)"
|
||||
when:
|
||||
- "openldap_consumer == True"
|
||||
- "domain.access_consumer is defined"
|
||||
set_fact:
|
||||
olcAccess: "{{ domain.access_consumer }}"
|
||||
|
||||
- name: "Directorio para el dominio"
|
||||
command: >-
|
||||
docker exec -u openldap:openldap {{ openldap_container_name }}
|
||||
mkdir "/var/lib/ldap/{{ ddn }}"
|
||||
register: ret
|
||||
failed_when: no
|
||||
changed_when: "ret.rc == 0"
|
||||
|
||||
- name: "Entrada en cn=config para {{ ddn }}"
|
||||
register: "entry_add"
|
||||
ldap:
|
||||
dn: "olcDatabase=mdb,cn=config"
|
||||
dn_relative: yes
|
||||
filter: "(olcSuffix=\"{{ ddn }}\")"
|
||||
objectClass:
|
||||
- "olcDatabaseConfig"
|
||||
- "olcMdbConfig"
|
||||
attributes:
|
||||
olcDbMaxSize: "1073741824"
|
||||
olcSuffix: "{{ ddn }}"
|
||||
olcDbDirectory: "/var/lib/ldap/{{ ddn }}"
|
||||
olcRootDN: "{{ admindn }}"
|
||||
olcRootPW: "{{ adminpw }}"
|
||||
olcAccess: "{{ olcAccess }}"
|
||||
olcDbCheckpoint: "512 30"
|
||||
olcLastMod: "TRUE"
|
||||
olcDbIndex: "{{ olcDbIndex }}"
|
||||
olcLimits: "{{ domain.limits | default(openldap_default_db_limits) }}"
|
||||
bind_dn: "cn=admin,cn=config"
|
||||
bind_pw: "{{ openldap_admin_password }}"
|
||||
server_uri: "ldap://localhost:{{ openldap_bind_port }}"
|
||||
|
||||
- name: "Overlay memberof para {{ ddn }}"
|
||||
when: "openldap_enable_memberof == True"
|
||||
ldap:
|
||||
dn: "olcOverlay=memberof,{{ entry_add.dn }}"
|
||||
dn_relative: yes
|
||||
objectClass:
|
||||
- "olcOverlayConfig"
|
||||
- "olcConfig"
|
||||
- "olcMemberOf"
|
||||
attributes:
|
||||
olcMemberOfDangling: "ignore"
|
||||
olcMemberOfRefInt: "FALSE"
|
||||
olcMemberOfGroupOC: "groupOfNames"
|
||||
olcMemberOfMemberAD: "member"
|
||||
olcMemberOfMemberOfAD: "memberOf"
|
||||
bind_dn: "cn=admin,cn=config"
|
||||
bind_pw: "{{ openldap_admin_password }}"
|
||||
server_uri: "ldap://localhost:{{ openldap_bind_port }}"
|
||||
|
||||
- name: "Overlay syncprov para {{ ddn }}"
|
||||
when: "openldap_provider == True"
|
||||
ldap:
|
||||
dn: "olcOverlay=syncprov,{{ entry_add.dn }}"
|
||||
dn_relative: yes
|
||||
objectClass:
|
||||
- "olcOverlayConfig"
|
||||
- "olcSyncProvConfig"
|
||||
attributes:
|
||||
olcSpNoPresent: "TRUE"
|
||||
bind_dn: "cn=admin,cn=config"
|
||||
bind_pw: "{{ openldap_admin_password }}"
|
||||
server_uri: "ldap://localhost:{{ openldap_bind_port }}"
|
||||
|
||||
- name: "Overlay accesslog para {{ ddn }}"
|
||||
when: "openldap_provider == True"
|
||||
ldap:
|
||||
dn: "olcOverlay=accesslog,{{ entry_add.dn }}"
|
||||
dn_relative: yes
|
||||
objectClass:
|
||||
- "olcOverlayConfig"
|
||||
- "olcAccessLogConfig"
|
||||
attributes:
|
||||
olcAccessLogDB: "cn=accesslog"
|
||||
olcAccessLogOps: "writes"
|
||||
olcAccessLogPurge: "07+00:00 01+00:00"
|
||||
olcAccessLogSuccess: "TRUE"
|
||||
bind_dn: "cn=admin,cn=config"
|
||||
bind_pw: "{{ openldap_admin_password }}"
|
||||
server_uri: "ldap://localhost:{{ openldap_bind_port }}"
|
||||
|
||||
# EL ORDEN EN LAS SIGUIENTES 2 es importante!!!
|
||||
# solo se soporta UN provider
|
||||
- name: "Configurar cliente de replicacion (1: entrada olcSyncRepl)"
|
||||
when:
|
||||
- "openldap_consumer == True"
|
||||
- "openldap_provider_host | bool == True"
|
||||
ldap_attr:
|
||||
dn: "{{ entry_add.dn }}"
|
||||
name: "olcSyncRepl"
|
||||
state: "exact"
|
||||
values: >-
|
||||
{0}rid=3
|
||||
provider=ldap://{{ openldap_provider_host }}
|
||||
bindmethod=simple
|
||||
binddn="{{ openldap_replicator_dn }}"
|
||||
credentials="{{ openldap_replicator_password }}"
|
||||
searchbase="{{ openldap_replicator_base }}"
|
||||
logbase="cn=accesslog"
|
||||
logfilter="(&(objectClass=auditWriteObject)(reqResult=0))"
|
||||
schemachecking=on
|
||||
type=refreshAndPersist
|
||||
retry="60 10 300 +"
|
||||
interval=00:00:01:00 syncdata=accesslog
|
||||
bind_dn: "cn=admin,cn=config"
|
||||
bind_pw: "{{ openldap_admin_password }}"
|
||||
server_uri: "ldap://localhost:{{ openldap_bind_port }}"
|
||||
|
||||
- name: "Configurar cliente de replicacion (2: entrada olcUpdateRef)"
|
||||
when:
|
||||
- "openldap_consumer == True"
|
||||
- "openldap_provider_host | bool == True"
|
||||
ldap_attr:
|
||||
dn: "{{ entry_add.dn }}"
|
||||
name: "olcUpdateRef"
|
||||
state: "exact"
|
||||
values: "ldap://{{ openldap_provider_host }}"
|
||||
bind_dn: "cn=admin,cn=config"
|
||||
bind_pw: "{{ openldap_admin_password }}"
|
||||
server_uri: "ldap://localhost:{{ openldap_bind_port }}"
|
||||
|
||||
- name: "Desconfigurar cliente de replicacion (2: entrada olcUpdateRef)"
|
||||
when: "openldap_consumer == False"
|
||||
ldap_attr:
|
||||
dn: "{{ entry_add.dn }}"
|
||||
name: "olcUpdateRef"
|
||||
state: "exact"
|
||||
values: []
|
||||
bind_dn: "cn=admin,cn=config"
|
||||
bind_pw: "{{ openldap_admin_password }}"
|
||||
server_uri: "ldap://localhost:{{ openldap_bind_port }}"
|
||||
|
||||
- name: "Desconfigurar cliente de replicacion (1: entrada olcSyncRepl)"
|
||||
when: "openldap_consumer == False"
|
||||
ldap_attr:
|
||||
dn: "{{ entry_add.dn }}"
|
||||
name: "olcSyncRepl"
|
||||
state: "exact"
|
||||
values: []
|
||||
bind_dn: "cn=admin,cn=config"
|
||||
bind_pw: "{{ openldap_admin_password }}"
|
||||
server_uri: "ldap://localhost:{{ openldap_bind_port }}"
|
||||
|
||||
# fin del bloque -------------------------------
|
||||
|
||||
# parte 3: agregar entrada en el DIT y crear usuario admin, replicator, OUs ---------------
|
||||
|
||||
- when:
|
||||
- "openldap_create_dit_entries == True"
|
||||
- "openldap_consumer == False"
|
||||
block:
|
||||
|
||||
- name: "(aux) credenciales para modificar entradas en {{ super.dn }}"
|
||||
set_fact:
|
||||
rootdn: "cn={{ super.admincn | default('admin') }},{{ super.dn }}"
|
||||
rootpw: "{{ super.adminpw | default(openldap_admmin_password|default('password')) }}"
|
||||
|
||||
- name: "Entrada para organización {{ ddn }}"
|
||||
ldap:
|
||||
dn: "{{ ddn }}"
|
||||
objectClass:
|
||||
- "dcObject"
|
||||
- "organization"
|
||||
- "top"
|
||||
attributes:
|
||||
o: "{{ domain.name }}"
|
||||
bind_dn: "{{ rootdn }}"
|
||||
bind_pw: "{{ rootpw }}"
|
||||
server_uri: "ldap://localhost:{{ openldap_bind_port }}"
|
||||
|
||||
- name: "Usuario admin para {{ ddn }}"
|
||||
ldap:
|
||||
dn: "{{ admindn }}"
|
||||
objectClass:
|
||||
- "organizationalRole"
|
||||
- "simpleSecurityObject"
|
||||
attributes:
|
||||
description: "LDAP Administrator role for domain {{ domain.name }}"
|
||||
userPassword: "{{ adminpw }}"
|
||||
bind_dn: "{{ rootdn }}"
|
||||
bind_pw: "{{ rootpw }}"
|
||||
server_uri: "ldap://localhost:{{ openldap_bind_port }}"
|
||||
|
||||
- name: "Usuario de replicacion para {{ ddn }}"
|
||||
when:
|
||||
- "super.dn == ddn"
|
||||
- "ddn in openldap_replicator_dn"
|
||||
ldap:
|
||||
dn: "{{ openldap_replicator_dn }}"
|
||||
objectClass:
|
||||
- "organizationalRole"
|
||||
- "simpleSecurityObject"
|
||||
attributes:
|
||||
description: "LDAP Replication role for domain {{ domain.name }}"
|
||||
userPassword: "{{ openldap_replicator_password }}"
|
||||
bind_dn: "{{ rootdn }}"
|
||||
bind_pw: "{{ rootpw }}"
|
||||
server_uri: "ldap://localhost:{{ openldap_bind_port }}"
|
||||
|
||||
- name: "OUs para {{ ddn }}"
|
||||
ldap:
|
||||
dn: "ou={{ item }},{{ ddn }}"
|
||||
objectClass:
|
||||
- "organizationalUnit"
|
||||
- "top"
|
||||
attributes:
|
||||
ou: "{{ item }}"
|
||||
bind_dn: "{{ rootdn }}"
|
||||
bind_pw: "{{ rootpw }}"
|
||||
server_uri: "ldap://localhost:{{ openldap_bind_port }}"
|
||||
loop: "{{ domain.ou | default(openldap_default_domain_ous) }}"
|
||||
|
||||
- set_fact:
|
||||
ldap_base_dn: "{{ lookup('vars', 'ldap_base_dn', default=ddn) }}"
|
||||
ldap_admin_dn: "{{ lookup('vars', 'ldap_admin_dn', default=rootdn) }}"
|
||||
ldap_admin_password: "{{ lookup('vars', 'ldap_admin_password', default=rootpw) }}"
|
||||
|
||||
# fin del bloque -------------------------------
|
52
tasks/main.yml
Normal file
52
tasks/main.yml
Normal file
@ -0,0 +1,52 @@
|
||||
---
|
||||
# Playbook for setting up a Docker container with openLDAP.
|
||||
# A port binding to the Docker host is required for setting
|
||||
# up domains and replication.
|
||||
|
||||
- name: "Create directory for building image"
|
||||
file:
|
||||
path: "/tmp/build.openldap-image"
|
||||
state: "directory"
|
||||
|
||||
- name: "Copy required files"
|
||||
copy:
|
||||
src: "{{ item }}"
|
||||
dest: "/tmp/build.openldap-image/"
|
||||
loop:
|
||||
- "Dockerfile"
|
||||
- "entrypoint.sh"
|
||||
|
||||
- name: "Build openldap image"
|
||||
docker_image:
|
||||
path: "/tmp/build.openldap-image"
|
||||
name: "{{ openldap_image_name }}"
|
||||
|
||||
- name: "Start openldap container"
|
||||
docker_container:
|
||||
image: "{{ openldap_image_name }}"
|
||||
name: "{{ openldap_container_name }}"
|
||||
volumes:
|
||||
- "{{ openldap_volume_config }}:/etc/ldap"
|
||||
- "{{ openldap_volume_data }}:/var/lib/ldap"
|
||||
- "{{ openldap_volume_backup }}:/var/backups/ldap"
|
||||
env:
|
||||
OPENLDAP_ADMIN_PASSWORD: "{{ openldap_admin_password }}"
|
||||
OPENLDAP_SCHEMAS: "{{ openldap_schemas | join (' ') }}"
|
||||
OPENLDAP_ENABLE_MEMBEROF: "{{ 'true' if openldap_enable_module_memberof else 'false' }}"
|
||||
|
||||
networks:
|
||||
- name: "{{ docker_network_name }}"
|
||||
ports:
|
||||
- "{{ openldap_bind_host }}:{{ openldap_bind_port }}:389"
|
||||
|
||||
- include_tasks: "provider.yml"
|
||||
when: "openldap_provider == True"
|
||||
|
||||
- include_tasks: "domain.yml"
|
||||
loop: "{{ openldap_domains }}"
|
||||
loop_control:
|
||||
loop_var: "domain"
|
||||
|
||||
- set_fact:
|
||||
ldap_uri: "{{ lookup( 'vars', 'ldap_uri',
|
||||
default='ldap://'+openldap_container_name+':389') }}"
|
82
tasks/provider.yml
Normal file
82
tasks/provider.yml
Normal file
@ -0,0 +1,82 @@
|
||||
---
|
||||
# En este archivo se configura el provider en un esquema
|
||||
# de replicación delta-syncrepl.
|
||||
# Ver https://openldap.org/doc/admin24/replication.html#Delta-syncrepl
|
||||
|
||||
- name: "Habilitar módulos syncprov y accesslog"
|
||||
ldap_attr:
|
||||
dn: "cn=module{0},cn=config"
|
||||
name: "olcModuleLoad"
|
||||
values: >-
|
||||
[ {% if openldap_enable_memberof %}
|
||||
"{2}syncprov", "{3}accesslog" {% else %}
|
||||
"{1}syncprov", "{2}accesslog" {% endif %} ]
|
||||
bind_dn: "cn=admin,cn=config"
|
||||
bind_pw: "{{ openldap_admin_password }}"
|
||||
server_uri: "ldap://localhost:{{ openldap_bind_port }}"
|
||||
|
||||
- name: "Crear directorio para db accesslog"
|
||||
file:
|
||||
path: "{{ openldap_accesslog_dir }}"
|
||||
state: "directory"
|
||||
owner: "openldap"
|
||||
group: "openldap"
|
||||
|
||||
- name: "Configurar base cn=accesslog"
|
||||
ldap:
|
||||
dn: "olcDatabase=mdb,cn=config"
|
||||
dn_relative: yes
|
||||
filter: "(olcSuffix=cn=accesslog)"
|
||||
objectClass:
|
||||
- "olcDatabaseConfig"
|
||||
- "olcMdbConfig"
|
||||
attributes:
|
||||
olcRootDN: "{{ openldap_accesslog_admin_dn }}"
|
||||
olcDbMaxSize: "8589934592"
|
||||
olcSuffix: "cn=accesslog"
|
||||
olcDbDirectory: "{{ openldap_accesslog_dir }}"
|
||||
olcAccess:
|
||||
- "{0}to * by dn=\"{{ openldap_replicator_dn }}\" read"
|
||||
olcLimits:
|
||||
- >-
|
||||
{0}dn.exact="{{ openldap_replicator_dn }}"
|
||||
time.soft=unlimited
|
||||
time.hard=unlimited
|
||||
size.soft=unlimited
|
||||
size.hard=unlimited
|
||||
- >-
|
||||
{1}dn.exact="{{ openldap_accesslog_admin_dn }}"
|
||||
time.soft=unlimited
|
||||
time.hard=unlimited
|
||||
size.soft=unlimited
|
||||
size.hard=unlimited
|
||||
bind_dn: "cn=admin,cn=config"
|
||||
bind_pw: "{{ openldap_admin_password }}"
|
||||
server_uri: "ldap://localhost:{{ openldap_bind_port }}"
|
||||
register: accesslog_entry
|
||||
|
||||
- name: "Configurar base olcOverlay=syncprov,{{ accesslog_entry.dn }}"
|
||||
ldap:
|
||||
dn: "olcOverlay=syncprov,{{ accesslog_entry.dn }}"
|
||||
dn_relative: yes
|
||||
objectClass:
|
||||
- "olcOverlayConfig"
|
||||
- "olcSyncProvConfig"
|
||||
attributes:
|
||||
olcSpNoPresent: "TRUE"
|
||||
olcSpReloadHint: "TRUE"
|
||||
bind_dn: "cn=admin,cn=config"
|
||||
bind_pw: "{{ openldap_admin_password }}"
|
||||
server_uri: "ldap://localhost:{{ openldap_bind_port }}"
|
||||
|
||||
# Esto se debe agregar luego del overlay, por eso va aparte
|
||||
- name: "Configurar propiedad olcDbIndex de {{ accesslog_entry.dn }}"
|
||||
ldap_attr:
|
||||
dn: "{{ accesslog_entry.dn }}"
|
||||
name: "olcDbIndex"
|
||||
values:
|
||||
- "default eq"
|
||||
- "entryCSN,objectClass,reqEnd,reqResult,reqStart"
|
||||
bind_dn: "cn=admin,cn=config"
|
||||
bind_pw: "{{ openldap_admin_password }}"
|
||||
server_uri: "ldap://localhost:{{ openldap_bind_port }}"
|
Loading…
x
Reference in New Issue
Block a user