diff options
20 files changed, 110 insertions, 213 deletions
diff --git a/lib/modules/openldap b/lib/modules/openldap index 0f0bc9a..1e84c32 100644 --- a/lib/modules/openldap +++ b/lib/modules/openldap @@ -21,40 +21,41 @@ from ldap.filter import filter_format from ldap.dn import dn2str,explode_dn,str2dn from ldap.modlist import addModlist from ldif import LDIFParser from functools import partial import re, pwd import tempfile, atexit # Dirty hack to check equality between the targetted LDIF and that # currently in the directory. The value of some configuration (olc*) # attributes is automatically indexed when added; for those we'll add # explicit indices to what we find in the LDIF. indexedAttributes = frozenset([ 'olcAttributeTypes', 'olcObjectClasses', 'olcAccess', 'olcSyncrepl', 'olcOverlay', 'olcLimits', 'olcAuthzRegexp', + 'olcDbConfig', ]) # Another hack. Configuration entries sometimes pollutes the DNs with # indices, thus it's not possible to directly use them as base. # Instead, we use their parent as a pase, and search for the *unique* # match with the same ObjectClass and the matching extra attributes. # ('%s' in the attribute value is replaced with the value of the source # entry.) indexedDN = { 'olcSchemaConfig': [('cn', '{*}%s')], 'olcHdbConfig': [('olcDbDirectory', '%s' )], 'olcOverlayConfig': [('olcOverlay', '%s' )], } # Allow for flexible ACLs for user using SASL's EXTERNAL mechanism. # "username=postfix,cn=peercred,cn=external,cn=auth" is replaced by # "gidNumber=106+uidNumber=102,cn=peercred,cn=external,cn=auth" where # 102 is postfix's UID and 106 its primary GID. # (Regular expressions are not allowed.) @@ -74,68 +75,40 @@ def acl_sasl_ext(m): , pwd_dict[u].pw_gid , pwd_dict[u].pw_uid , m.group('end') ) # Run the given callback on each DN seen. If its return value is not # None, update the changed variable. class LDIFCallback(LDIFParser): def __init__(self, module, input, callback): LDIFParser.__init__(self,input) self.callback = callback self.changed = False def handle(self,dn,entry): b = self.callback(dn,entry) if b is not None: self.changed |= b -# Run slapcat(8) on the given suffix or DB number (suffix takes -# precedence) with an optional filter. (This is useful for offline -# searches, or one needs to bypass ACLs.) Returns an open pipe to the -# subprocess. -def slapcat(filter=None, suffix=None, idx=0): - cmd = [ os.path.join(os.sep, 'usr', 'sbin', 'slapcat') ] - - if filter is not None: - cmd.extend([ '-a', filter ]) - - if suffix is not None: - if type(suffix) is not str: - suffix = dn2str(suffix) - cmd.extend([ '-b', suffix ]) - else: - cmd.append( '-n%d' % idx ) - - return subprocess.Popen( cmd, stdout=subprocess.PIPE - , stderr=open(os.devnull, 'wb') ) - - -# Start / stop / whatever a service. -def service(name, state): - cmd = [ os.path.join(os.sep, 'usr', 'sbin', 'service'), name, state ] - subprocess.check_call( cmd, stdout=open(os.devnull, 'wb') - , stderr=subprocess.STDOUT ) - - # Check if the given dn is already present in the directory. # Returns None if doesn't exist, and give the dn,entry otherwise def flexibleSearch(module, l, dn, entry): idxClasses = set(entry['objectClass']).intersection(indexedDN.keys()) if not idxClasses: base = dn scope = ldap.SCOPE_BASE f = 'objectClass=*' else: # Search on the parent instead, and try to use a precise filter dn = str2dn(dn) h,t,_ = dn.pop(0)[0] base = dn2str(dn) scope = ldap.SCOPE_ONELEVEL f = [] for c in idxClasses: f.append ( filter_format('objectClass=%s', [c]) ) for a,v in indexedDN[c]: if a == h: v2 = t @@ -237,164 +210,101 @@ def loadModule(module, l, name): if not r: changed = True if module.check_mode: module.exit_json(changed=changed, msg="add module %s" % name) l.modify_s( 'cn=module{0},cn=config' , [(ldap.MOD_ADD, 'olcModuleLoad', name)] ) return changed # Find the database associated with a given attribute (eg, # olcDbDirectory or olcSuffix). def getDN_DB(module, l, a, v, attrlist=['']): f = filter_format( '(&(objectClass=olcDatabaseConfig)('+a+'=%s))', [v] ) return l.search_s( 'cn=config' , ldap.SCOPE_ONELEVEL , filterstr = f , attrlist = attrlist ) -# Clear the given DB directory and delete the associated database. Fail -# if non empty, unless all existing DNS are in skipdns. -def wontRemove(module, skipdns, d, _): - if d not in skipdns: - module.fail_json(msg="won't remove '%s'" % d) -def removeDB(module, dbdir, skipdn=None): - changed = False - if not os.path.exists(dbdir): - return False - - l = ldap.initialize( 'ldapi://' ) - l.sasl_interactive_bind_s('', ldap.sasl.external()) - r = getDN_DB( module, l, 'olcDbDirectory', dbdir, attrlist=['olcSuffix'] ) - l.unbind_s() - - if len(r) > 1: - module.fail_json(msg="Multiple results found! This is a bug. Please report.") - elif r: - dn,entry = r.pop() - suffix = entry['olcSuffix'][0] - - skipdns = [suffix] - if skipdn is not None: - skipdns.extend([ "%s,%s" % (s,suffix) for s in skipdn ]) - # here we need to use slapcat not search_s, because we may - # not have read access on the database (even though we're - # root!). - p = slapcat( suffix=suffix ) - parser = LDIFCallback( module, p.stdout - , partial(wontRemove,module,skipdns) ) - parser.parse() - - changed = True - if module.check_mode: - module.exit_json(changed=changed, msg="remove dir %s" % dbdir) - - # slapd doesn't support database deletion, so we need to turn it - # off and remove it from slapd.d manually. - service( 'slapd', 'stop' ) - path = [ os.sep, 'etc', 'ldap', 'slapd.d' ] - ldif = explode_dn(dn)[::-1] - ldif[-1] += ".ldif" - path.extend( ldif ) - os.unlink( os.path.join(*path) ) - - # delete all children in path, but not the path directory itself. - for file in os.listdir(dbdir): - os.unlink( os.path.join(dbdir, file) ) - service( 'slapd', 'start' ) - return changed - - # Convert a *.schema file into *.ldif format. The algorithm can be found # in /etc/ldap/schema/openldap.ldif . def slapd_to_ldif(src, name): s = open( src, 'r' ) d = tempfile.NamedTemporaryFile(delete=False) atexit.register(lambda: os.unlink( d.name )) d.write('dn: cn=%s,cn=schema,cn=config\n' % name) d.write('objectClass: olcSchemaConfig\n') re1 = re.compile( r'^objectIdentifier\s(.*)', re.I ) re2 = re.compile( r'^objectClass\s(.*)', re.I ) re3 = re.compile( r'^attributeType\s(.*)', re.I ) reSp = re.compile( r'^\s+' ) for line in s.readlines(): if line == '\n': line = '#\n' m1 = re1.match(line) m2 = re2.match(line) m3 = re3.match(line) if m1 is not None: line = 'olcObjectIdentifier: %s' % m1.group(1) elif m2 is not None: line = 'olcObjectClasses: %s' % m2.group(1) elif m3 is not None: line = 'olcAttributeTypes: %s' % m3.group(1) d.write( reSp.sub(line, ' ') ) s.close() d.close() return d.name def main(): module = AnsibleModule( argument_spec = dict( - dbdirectory = dict( default=None ), - ignoredn = dict( default=None ), - state = dict( default="present", choices=["absent", "present"]), + state = dict( default="present", choices=["absent","present"]), target = dict( default=None ), module = dict( default=None ), suffix = dict( default=None ), format = dict( default="ldif", choices=["ldif","slapd.conf"] ), name = dict( default=None ), local = dict( default="no", choices=["no","file","template"] ), ), supports_check_mode=True ) params = module.params state = params['state'] - dbdirectory = params['dbdirectory'] - ignoredn = params['ignoredn'] target = params['target'] mod = params['module'] suffix = params['suffix'] form = params['format'] name = params['name'] - if ignoredn is not None: - ignoredn = ignoredn.split(':') - changed = False try: if state == "absent": - if dbdirectory is not None: - changed = removeDB(module,dbdirectory,skipdn=ignoredn) - # TODO: might be useful to be able remove DNs - else: - module.fail_json(msg="missing dbdirectory") + module.fail_json(msg="OpenLDAP's ansible: unsupported feature") elif state == "present": if form == 'slapd.conf': if name is None: module.fail_json(msg="missing name") target = slapd_to_ldif(target, name) if target is None and mod is None: module.fail_json(msg="missing target or module") # bind only once per LDIF file for performance l = ldap.initialize( 'ldapi://' ) l.sasl_interactive_bind_s('', ldap.sasl.external()) if mod is None: callback = partial(processEntry,module,l) else: changed |= loadModule (module, l, '%s.la' % mod) if target is None and suffix is None: l.unbind_s() module.exit_json(changed=changed) diff --git a/roles/IMAP/files/etc/dovecot/dovecot-ldap-userdb.conf.ext b/roles/IMAP/files/etc/dovecot/dovecot-ldap-userdb.conf.ext index 6c39bf6..c455c07 100644 --- a/roles/IMAP/files/etc/dovecot/dovecot-ldap-userdb.conf.ext +++ b/roles/IMAP/files/etc/dovecot/dovecot-ldap-userdb.conf.ext @@ -36,41 +36,41 @@ sasl_mech = EXTERNAL #tls_cipher_suite = # TLS cert/key is used only if LDAP server requires a client certificate. #tls_cert_file = #tls_key_file = # Valid values: never, hard, demand, allow, try #tls_require_cert = # Use the given ldaprc path. #ldaprc_path = # LDAP library debug level as specified by LDAP_DEBUG_* in ldap_log.h. # -1 = everything. You may need to recompile OpenLDAP with debugging enabled # to get enough output. #debug_level = 0 # LDAP protocol version to use. Likely 2 or 3. ldap_version = 3 # LDAP base. %variables can be used here. # For example: dc=mail, dc=example, dc=org -base = fvl=%n,fvd=%d,ou=virtual,o=mailHosting,dc=fripost,dc=org +base = fvl=%n,fvd=%d,ou=virtual,dc=fripost,dc=org # Dereference: never, searching, finding, always deref = never # Search scope: base, onelevel, subtree scope = base # User attributes are given in LDAP-name=dovecot-internal-name list. The # internal names are: # uid - System UID # gid - System GID # home - Home directory # mail - Mail location # # There are also other special fields which can be returned, see # http://wiki2.dovecot.org/UserDatabase/ExtraFields user_attrs = =mail_plugins=antispam # Filter for user lookup. Some variables can be used (see # http://wiki2.dovecot.org/Variables for full list): diff --git a/roles/IMAP/files/etc/dovecot/dovecot-ldap.conf.ext b/roles/IMAP/files/etc/dovecot/dovecot-ldap.conf.ext index 77edba8..1ffa73d 100644 --- a/roles/IMAP/files/etc/dovecot/dovecot-ldap.conf.ext +++ b/roles/IMAP/files/etc/dovecot/dovecot-ldap.conf.ext @@ -63,48 +63,48 @@ uris = ldapi:// # logging into LDAP server using the username and password given by client. # The pass_filter is used to find the DN for the user. Note that the pass_attrs # is still used, only the password field is ignored in it. Before doing any # search, the binding is switched back to the default DN. auth_bind = yes # If authentication binding is used, you can save one LDAP request per login # if users' DN can be specified with a common template. The template can use # the standard %variables (see user_filter). Note that you can't # use any pass_attrs if you use this setting. # # If you use this setting, it's a good idea to use a different # dovecot-ldap.conf.ext for userdb (it can even be a symlink, just as long as # the filename is different in userdb's args). That way one connection is used # only for LDAP binds and another connection is used for user lookups. # Otherwise the binding is changed to the default DN before each user lookup. # # For example: # auth_bind_userdn = cn=%u,ou=people,o=org # -auth_bind_userdn = fvl=%n,fvd=%d,ou=virtual,o=mailHosting,dc=fripost,dc=org +auth_bind_userdn = fvl=%n,fvd=%d,ou=virtual,dc=fripost,dc=org # LDAP protocol version to use. Likely 2 or 3. ldap_version = 3 # LDAP base. %variables can be used here. # For example: dc=mail, dc=example, dc=org -base = fvl=%n,fvd=%d,ou=virtual,o=mailHosting,dc=fripost,dc=org +base = fvl=%n,fvd=%d,ou=virtual,dc=fripost,dc=org # Dereference: never, searching, finding, always deref = never # Search scope: base, onelevel, subtree scope = base # User attributes are given in LDAP-name=dovecot-internal-name list. The # internal names are: # uid - System UID # gid - System GID # home - Home directory # mail - Mail location # # There are also other special fields which can be returned, see # http://wiki2.dovecot.org/UserDatabase/ExtraFields user_attrs = # Filter for user lookup. Some variables can be used (see # http://wiki2.dovecot.org/Variables for full list): diff --git a/roles/IMAP/files/etc/postfix/virtual/mailbox.cf b/roles/IMAP/files/etc/postfix/virtual/mailbox.cf index 009dd98..e69343b 100644 --- a/roles/IMAP/files/etc/postfix/virtual/mailbox.cf +++ b/roles/IMAP/files/etc/postfix/virtual/mailbox.cf @@ -1,9 +1,9 @@ server_host = ldapi://%2Fprivate%2Fldapi/ version = 3 -search_base = fvl=%u,fvd=%d,ou=virtual,o=mailHosting,dc=fripost,dc=org +search_base = fvl=%u,fvd=%d,ou=virtual,dc=fripost,dc=org domain = static:all scope = base bind = none query_filter = (&(objectClass=FripostVirtualUser)(fvl=%u)) result_attribute = fvl result_format = OK diff --git a/roles/IMAP/files/etc/postfix/virtual/transport_content_filter.cf b/roles/IMAP/files/etc/postfix/virtual/transport_content_filter.cf index b082f69..642b722 100644 --- a/roles/IMAP/files/etc/postfix/virtual/transport_content_filter.cf +++ b/roles/IMAP/files/etc/postfix/virtual/transport_content_filter.cf @@ -1,9 +1,9 @@ server_host = ldapi://%2Fprivate%2Fldapi/ version = 3 -search_base = fvl=%u,fvd=%d,ou=virtual,o=mailHosting,dc=fripost,dc=org +search_base = fvl=%u,fvd=%d,ou=virtual,dc=fripost,dc=org domain = static:all scope = base bind = none query_filter = (&(objectClass=FripostVirtualUser)(objectClass=AmavisAccount)(fvl=%u)) result_attribute = fvl result_format = amavisfeed:[127.0.0.1]:10041 diff --git a/roles/LDAP-provider/tasks/main.yml b/roles/LDAP-provider/tasks/main.yml index 48cc8d2..d221486 100644 --- a/roles/LDAP-provider/tasks/main.yml +++ b/roles/LDAP-provider/tasks/main.yml @@ -1,23 +1,23 @@ - name: Load and configure the syncprov overlay openldap: module=syncprov state=present - suffix=o=mailHosting,dc=fripost,dc=org + suffix=dc=fripost,dc=org target=etc/ldap/syncprov.ldif local=file - name: Enable the EXTERNAL SASL mechanism lineinfile: dest=/usr/lib/sasl2/slapd.conf regexp='^mech_list'':' line=mech_list':'' EXTERNAL' owner=root group=root mode=0644 - name: Copy the SyncRepls's client certificates assemble: src=certs/ldap remote_src=no dest=/etc/ldap/ssl/clients.pem owner=root group=root mode=0644 tags: - genkey # TODO: authz constraint diff --git a/roles/MX/files/usr/local/sbin/reserved-alias.pl b/roles/MX/files/usr/local/sbin/reserved-alias.pl index 603d773..517e51b 100755 --- a/roles/MX/files/usr/local/sbin/reserved-alias.pl +++ b/roles/MX/files/usr/local/sbin/reserved-alias.pl @@ -50,41 +50,41 @@ my @recipients = grep { $_ and $orig ne $_ } # add localparts to domain map { my $x = $_; if ($x =~ /^\@/) { $x = (defined $local and $local ne '') ? $local.$x : undef; } $x } @ARGV; # Die if we can't deliver to site admins die "Error: Aborted delivery to '$orig' in attempt to break an alias expansion loop.\n" unless @recipients; if (defined $domain) { # Look for the domain owner or postmaster my $ldap = Net::LDAPI->new(); $ldap->bind( sasl => Authen::SASL->new(mechanism => 'EXTERNAL') ) or die "Error: Couldn't bind"; my @attrs = ( 'fripostPostmaster', 'fripostOwner' ); my $mesg = $ldap->search( base => 'fvd='.escape_dn_value($domain).',' - .'ou=virtual,o=mailHosting,dc=fripost,dc=org' + .'ou=virtual,dc=fripost,dc=org' , scope => 'base' , deref => 'never' , filter => '(&(objectClass=FripostVirtualDomain)' .'(fvd='.escape_filter_value($domain).')'. ')' , attrs => \@attrs ); if ($mesg->code) { warn "Warning: ".$mesg->error; } elsif ($mesg->count != 1) { # Note: this may happen for "$mydestination", but these mails # are unlikely. We'll get a harmless warning at worst. warn "Warning: Something weird happened when looking up domain '".$domain. "'. Check your ACL."; } else { my $entry = $mesg->pop_entry() // die "Error: Cannot pop entry."; foreach (@attrs) { my $v = $entry->get_value($_, asref => 1) or next; diff --git a/roles/MX/templates/etc/postfix/virtual/alias.cf.j2 b/roles/MX/templates/etc/postfix/virtual/alias.cf.j2 index c7d2f0a..2e80d45 100644 --- a/roles/MX/templates/etc/postfix/virtual/alias.cf.j2 +++ b/roles/MX/templates/etc/postfix/virtual/alias.cf.j2 @@ -1,8 +1,8 @@ server_host = ldapi://%2Fprivate%2Fldapi/ version = 3 -search_base = fvl=%u,fvd=%d,ou=virtual,o=mailHosting,dc=fripost,dc=org +search_base = fvl=%u,fvd=%d,ou=virtual,dc=fripost,dc=org domain = static:all scope = base bind = none query_filter = (&(objectClass=FripostVirtualAlias)(fvl=%u)) result_attribute = fripostMaildrop diff --git a/roles/MX/templates/etc/postfix/virtual/alias_domains.cf.j2 b/roles/MX/templates/etc/postfix/virtual/alias_domains.cf.j2 index dec8bce..bdfa802 100644 --- a/roles/MX/templates/etc/postfix/virtual/alias_domains.cf.j2 +++ b/roles/MX/templates/etc/postfix/virtual/alias_domains.cf.j2 @@ -1,9 +1,9 @@ server_host = ldapi://%2Fprivate%2Fldapi/ version = 3 -search_base = fvd=%d,ou=virtual,o=mailHosting,dc=fripost,dc=org +search_base = fvd=%d,ou=virtual,dc=fripost,dc=org domain = static:all scope = base bind = none query_filter = (&(objectClass=FripostVirtualAliasDomain)(fvd=%d)) result_attribute = fripostMaildrop result_format = %U@%s diff --git a/roles/MX/templates/etc/postfix/virtual/catchall.cf.j2 b/roles/MX/templates/etc/postfix/virtual/catchall.cf.j2 index 8ac40fd..398e530 100644 --- a/roles/MX/templates/etc/postfix/virtual/catchall.cf.j2 +++ b/roles/MX/templates/etc/postfix/virtual/catchall.cf.j2 @@ -1,8 +1,8 @@ server_host = ldapi://%2Fprivate%2Fldapi/ version = 3 -search_base = fvd=%d,ou=virtual,o=mailHosting,dc=fripost,dc=org +search_base = fvd=%d,ou=virtual,dc=fripost,dc=org domain = static:all scope = base bind = none query_filter = (&(objectClass=FripostVirtualDomain)(!(objectClass=FripostVirtualAliasDomain))(fvd=%d)(fripostOptionalMaildrop=*)) result_attribute = fripostOptionalMaildrop diff --git a/roles/MX/templates/etc/postfix/virtual/list.cf.j2 b/roles/MX/templates/etc/postfix/virtual/list.cf.j2 index 5988159..4020b42 100644 --- a/roles/MX/templates/etc/postfix/virtual/list.cf.j2 +++ b/roles/MX/templates/etc/postfix/virtual/list.cf.j2 @@ -1,11 +1,11 @@ server_host = ldapi://%2Fprivate%2Fldapi/ version = 3 -search_base = fvl=%u,fvd=%d,ou=virtual,o=mailHosting,dc=fripost,dc=org +search_base = fvl=%u,fvd=%d,ou=virtual,dc=fripost,dc=org domain = static:all scope = base bind = none query_filter = (&(objectClass=FripostVirtualList)(fvl=%u)) result_attribute = fvl # Use a dedicated "virtual" domain to decongestion potential bottlenecks # on trivial_rewrite(8) due to slow LDAP lookups in tranport_maps. result_format = %D/%U@lists.fripost.org diff --git a/roles/MX/templates/etc/postfix/virtual/mailbox.cf.j2 b/roles/MX/templates/etc/postfix/virtual/mailbox.cf.j2 index a108c0d..118e17a 100644 --- a/roles/MX/templates/etc/postfix/virtual/mailbox.cf.j2 +++ b/roles/MX/templates/etc/postfix/virtual/mailbox.cf.j2 @@ -1,11 +1,11 @@ server_host = ldapi://%2Fprivate%2Fldapi/ version = 3 -search_base = fvl=%u,fvd=%d,ou=virtual,o=mailHosting,dc=fripost,dc=org +search_base = fvl=%u,fvd=%d,ou=virtual,dc=fripost,dc=org domain = static:all scope = base bind = none query_filter = (&(objectClass=FripostVirtualUser)(fvl=%u)) result_attribute = fvl # Use a dedicated "virtual" domain to decongestion potential bottlenecks # on trivial_rewrite(8) due to slow LDAP lookups in tranport_maps. result_format = %D/%U@mda.fripost.org diff --git a/roles/MX/templates/etc/postfix/virtual/mailbox_domains.cf.j2 b/roles/MX/templates/etc/postfix/virtual/mailbox_domains.cf.j2 index 74304a4..43b7f3a 100644 --- a/roles/MX/templates/etc/postfix/virtual/mailbox_domains.cf.j2 +++ b/roles/MX/templates/etc/postfix/virtual/mailbox_domains.cf.j2 @@ -1,8 +1,8 @@ server_host = ldapi://%2Fprivate%2Fldapi/ version = 3 -search_base = fvd=%s,ou=virtual,o=mailHosting,dc=fripost,dc=org +search_base = fvd=%s,ou=virtual,dc=fripost,dc=org scope = base bind = none query_filter = (&(objectClass=FripostVirtualDomain)(fvd=%s)) result_attribute = fvd result_format = OK diff --git a/roles/amavis/templates/etc/amavis/conf.d/50-user.j2 b/roles/amavis/templates/etc/amavis/conf.d/50-user.j2 index 200ce90..3595331 100644 --- a/roles/amavis/templates/etc/amavis/conf.d/50-user.j2 +++ b/roles/amavis/templates/etc/amavis/conf.d/50-user.j2 @@ -62,41 +62,41 @@ $signed_header_fields{received} = 0; # A couple of common banned rules one might can refer by their name %banned_rules = ( 'NO-MS-EXEC'=> new_RE( qr/^\.exe-ms$/ ), 'PASSALL' => new_RE( [qr/^/ => 0] ), 'ALLOW_EXE' => new_RE( qr/.\.(vbs|pif|scr|bat)$/i, [qr/^\.exe$/ => 0] ), 'ALLOW_VBS' => new_RE( [qr/.\.vbs$/ => 0] ), ); {% if 'MDA' in group_names %} $enable_ldap = 1; # Load Net::LDAP $default_ldap = { hostname => 'ldapi://', sasl => 1, sasl_mech => 'EXTERNAL', deref => 'never', timeout => 5, scope => 'one', - base => 'fvd=%d,ou=virtual,o=mailHosting,dc=fripost,dc=org', + base => 'fvd=%d,ou=virtual,dc=fripost,dc=org', # XXX: ideally we would use %u in the base and the query_filter, but # it's not supported as of amavis 2.7 (see the 'lookup_ldap' # subroutine in /usr/sbin/amavisd-new) query_filter => '(&(objectClass=amavisAccount)(ObjectClass=FripostVirtualUser)(fvl=%m))' }; {% endif %} # http://www.ijs.si/software/amavisd/amavisd-new-docs.html#pbanks-ex $protocol = 'LMTP'; $inet_socket_port = []; {% if 'out' in group_names %} push @$inet_socket_port, 10040; $interface_policy{'10040'} = 'OUTGOING'; {% endif %} {% if 'MDA' in group_names %} push @$inet_socket_port, 10041; $interface_policy{'10041'} = 'INCOMING'; diff --git a/roles/common-LDAP/files/etc/ldap/schema/fripost.ldif b/roles/common-LDAP/files/etc/ldap/schema/fripost.ldif index 54f3037..a26f249 100644 --- a/roles/common-LDAP/files/etc/ldap/schema/fripost.ldif +++ b/roles/common-LDAP/files/etc/ldap/schema/fripost.ldif @@ -3,51 +3,51 @@ # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. # Load this file with # # ldapadd -Y EXTERNAL -H ldapi:/// -f fripost.ldif # # It will load the schema. To perform modifications, the easiest way is to # -# * Save the database: slapcat -b 'o=mailHosting,dc=fripost,dc=dev' > /tmp/db.ldif +# * Save the database: slapcat -b 'dc=fripost,dc=org' > /tmp/db.ldif # * Save the configuration: slapcat -n0 > /tmp/config.ldif # * Backup slap.d: cp -a /etc/ldap/slapd.d/ /tmp/slap.d_back # * Edit the schema in /tmp/config.ldif # * Load the new config: mkdir -m 0700 /tmp/slapd.d_new && slapadd -F /tmp/slapd.d_new -n0 -l /tmp/config.ldif # * Stop slapd: /etc/init.d/slapd stop # * Load the new config: rm -rf /etc/ldap/slapd.d/ && mv /tmp/slapd.d_new /etc/ldap/slapd.d && chown -R openldap:openldap /etc/ldap/slapd.d -# * Create indexes: sudo -u openldap slapindex -b 'o=mailHosting,dc=fripost,dc=dev' +# * Create indexes: sudo -u openldap slapindex -b 'dc=fripost,dc=org' # * Start slapd: /etc/init.d/slapd start # If it fails, remove the existing database and see what's wrong -# rm -rf /var/lib/ldap/dev/* && sudo -u openldap slapadd -b 'o=mailHosting,dc=fripost,dc=org' -l /tmp/db.ldif +# rm -rf /var/lib/ldap/dev/* && sudo -u openldap slapadd -b 'dc=fripost,dc=org' -l /tmp/db.ldif # # # /!\ WARN: All modification to the ACL should be reflected to the test # /!\ suite as well! # # # References: # - http://courier.svn.sourceforge.net/svnroot/courier/trunk/courier-authlib/authldap.schema # - http://www.qmail-ldap.org/wiki/index.php/Qmail.schema # - http://www.wanderingbarque.com/howtos/mailserver/mailserver.html # 1.3.6.1.4.1.40011 Fripost's OID # 1.3.6.1.4.1.40011.1 # 1.3.6.1.4.1.40011.1.2 fripost LDAP Elements # 1.3.6.1.4.1.40011.1.2.1 AttributeTypes # 1.3.6.1.4.1.40011.1.2.2 ObjectClasses # 1.3.6.1.4.1.40011.1.2.3 Syntax Definitions # This schema depends on: diff --git a/roles/common-LDAP/files/var/lib/ldap/fripost/DB_CONFIG b/roles/common-LDAP/files/var/lib/ldap/DB_CONFIG index c7072dc..07738c2 100644 --- a/roles/common-LDAP/files/var/lib/ldap/fripost/DB_CONFIG +++ b/roles/common-LDAP/files/var/lib/ldap/DB_CONFIG @@ -1,20 +1,20 @@ # It may be a good idea to modify this file, depending on the output of # -# db_stat -mh /var/lib/ldap/fripost | head -16 +# db_stat -mh /var/lib/ldap | head -16 # # (For optimal performance, the Requested pages found in the cache # should be above 95%, and the dirty/clean pages forced from the cache # should be 0.) # # and # -# db_stat -ch /var/lib/ldap/fripost | head -16 +# db_stat -ch /var/lib/ldap | head -16 # # (For optimal performance, usage should be within 85% of the configured # values.) # -set_cachesize 0 5242880 1 -# 5MB cachesize, allow defragmentation +# 5MB cachesize +set_cachesize 0 5242880 0 set_lk_max_objects 1500 set_lk_max_locks 1500 set_lk_max_lockers 1500 diff --git a/roles/common-LDAP/tasks/main.yml b/roles/common-LDAP/tasks/main.yml index 43c6bfb..3b8b36c 100644 --- a/roles/common-LDAP/tasks/main.yml +++ b/roles/common-LDAP/tasks/main.yml @@ -1,88 +1,69 @@ # XXX If #742056 gets fixed, we should preseed slapd to use peercreds as # RootDN once the fix enters stable. - name: Install OpenLDAP apt: pkg={{ item }} with_items: - slapd - ldap-utils - ldapvi - db-util - python-ldap - name: Configure slapd template: src=etc/default/slapd.j2 dest=/etc/default/slapd owner=root group=root mode=0644 register: r1 notify: - Restart slapd -# Upon install slapd create and populate a database under /var/lib/ldap. -# We clear it up and create a children directory to get finer-grain -# control. -- name: Clear empty /var/lib/ldap - # Don't remove the database (and fail) if it contains something else - # than its suffix or cn=admin,... - openldap: dbdirectory=/var/lib/ldap ignoredn=cn=admin - state=absent - -- name: Create directory /var/lib/ldap/fripost - file: path=/var/lib/ldap/fripost - state=directory +- name: Copy DB_CONFIG + copy: src=var/lib/ldap/DB_CONFIG + dest=/var/lib/ldap/DB_CONFIG owner=openldap group=openldap - mode=0700 - -- name: Copy /var/lib/ldap/fripost/DB_CONFIG - copy: src=var/lib/ldap/fripost/DB_CONFIG - dest=/var/lib/ldap/fripost/DB_CONFIG - owner=openldap group=openldap - mode=0600 - register: r2 - notify: - # Not sure if required - - Restart slapd + mode=0644 - name: Create directory /etc/ldap/ssl file: path=/etc/ldap/ssl state=directory owner=root group=root mode=0755 tags: - genkey - name: Generate a private key and a X.509 certificate for slapd # XXX: GnuTLS (libgnutls26 2.12.20-8+deb7u2, found in Wheezy) doesn't # support ECDSA; and slapd doesn't seem to support DHE (!?) so # we're stuck with "plain RSA" Key-Exchange. Also, there is a bug with # SHA-512. command: genkeypair.sh x509 --pubkey=/etc/ldap/ssl/{{ item.name }}.pem --privkey=/etc/ldap/ssl/{{ item.name }}.key --ou=LDAP {{ item.ou }} --cn={{ item.name }} --usage=digitalSignature,keyEncipherment -t rsa -b 4096 -h sha256 --chown="root:openldap" --chmod=0640 - register: r3 - changed_when: r3.rc == 0 - failed_when: r3.rc > 1 + register: r2 + changed_when: r2.rc == 0 + failed_when: r2.rc > 1 with_items: - { group: 'LDAP-provider', name: ldap.fripost.org, ou: } - { group: 'MX', name: mx, ou: --ou=SyncRepl } - { group: 'lists', name: lists, ou: --ou=SyncRepl } when: "item.group in group_names" tags: - genkey - name: Fetch slapd's X.509 certificate # Ensure we don't fetch private data sudo: False fetch: src=/etc/ldap/ssl/{{ item.name }}.pem dest=certs/ldap/ fail_on_missing=yes flat=yes with_items: - { group: 'LDAP-provider', name: ldap.fripost.org } - { group: 'MX', name: mx } - { group: 'lists', name: lists } when: "item.group in group_names" @@ -106,29 +87,30 @@ # It'd certainly be nicer if we didn't have to deploy amavis' schema # everywhere, but we need the 'objectClass' in our replicates, hence # they need to be aware of the 'amavisAccount' class. with_items: - fripost.ldif - amavis.schema tags: - amavis - name: Load amavis' schema openldap: target=/etc/ldap/schema/amavis.schema state=present format=slapd.conf name=amavis tags: - ldap - name: Load Fripost' schema openldap: target=/etc/ldap/schema/fripost.ldif state=present tags: - ldap +# We assume a clean (=stock) cn=config - name: Configure the LDAP database openldap: target=etc/ldap/database.ldif.j2 local=template state=present - name: Start slapd service: name=slapd state=started - when: not (r1.changed or r2.changed or r3.changed) + when: not (r1.changed or r2.changed) - meta: flush_handlers diff --git a/roles/common-LDAP/templates/etc/ldap/database.ldif.j2 b/roles/common-LDAP/templates/etc/ldap/database.ldif.j2 index 308bece..f633692 100644 --- a/roles/common-LDAP/templates/etc/ldap/database.ldif.j2 +++ b/roles/common-LDAP/templates/etc/ldap/database.ldif.j2 @@ -17,163 +17,168 @@ # There are a couple of XXX in this file, due to Postfix not supporting # SASL binds in Wheezy. dn: cn=config objectClass: olcGlobal cn: config olcArgsFile: /var/run/slapd/slapd.args olcPidFile: /var/run/slapd/slapd.pid olcLogLevel: none olcToolThreads: 1 {% if ansible_processor_vcpus > 4 %} olcThreads: {{ 2 * ansible_processor_vcpus }} {% else %} olcThreads: 8 {% endif %} {% if 'LDAP-provider' in group_names %} olcTLSCertificateFile: /etc/ldap/ssl/ldap.fripost.org.pem olcTLSCertificateKeyFile: /etc/ldap/ssl/ldap.fripost.org.key olcTLSCACertificateFile: /etc/ldap/ssl/clients.pem olcTLSVerifyClient: allow -olcAuthzRegexp: "^cn=([^,]+),ou=SyncRepl,ou=LDAP,ou=SSLcerts,o=Fripost$" - "cn=$1,ou=replicates,o=mailHosting,dc=fripost,dc=org" +olcAuthzRegexp: "^(cn=[^,]+,ou=syncRepl),ou=LDAP,ou=SSLcerts,o=Fripost$" + "$1,dc=fripost,dc=org" olcSaslSecProps: minssf=128,noanonymous,noplain,nodict # XXX We would like to say 'PFS' here, but Wheezy'z GnuTLS (libgnutls26 # 2.12.20-8+deb7u2) is too old :-( (Also, DHE/ECDHE are not supported.) olcTLSCipherSuite: SECURE128:!CIPHER-ALL:+AES-128-CBC:+AES-256-CBC:!MD5 {% endif %} olcLocalSSF: 128 # /!\ This is not portable! But we only use glibc's crypt(3), which # supports (salted, streched) SHA512 olcPasswordHash: {CRYPT} olcPasswordCryptSaltFormat: $6$%s dn: olcDatabase=hdb,cn=config objectClass: olcDatabaseConfig objectClass: olcHdbConfig -olcDbDirectory: /var/lib/ldap/fripost -olcSuffix: o=mailHosting,dc=fripost,dc=org +olcDbDirectory: /var/lib/ldap +olcSuffix: dc=fripost,dc=org {% if 'LDAP-provider' not in group_names and ('MX' in group_names or 'lists' in group_names) %} olcReadOnly: TRUE {% endif %} {% if 'LDAP-provider' in group_names %} olcLastMod: TRUE olcDbCheckpoint: 512 15 {% else %} olcLastMod: FALSE {% endif %} +# See DB_CONFIG +olcDbConfig: set_cachesize 0 5242880 0 +olcDbConfig: set_lk_max_objects 1500 +olcDbConfig: set_lk_max_locks 1500 +olcDbConfig: set_lk_max_lockers 1500 # The root user has all rights on the whole database (when SASL-binding # on a UNIX socket). olcRootDN: gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth # Ensure that all DIT access is made according to the LDAPv3 protocol, # and must use 1/ authentication, and 2/ SASL or TLS. (Local clients # should use ldapi:// and SASL/EXERNAL, while remote clients should use # TLS.) # XXX: olcRequires: none LDAPv3 authc strong olcRequires: none LDAPv3 authc olcSecurity: simple_bind=128 ssf=128 update_ssf=128 # # ######################################################################## # Performance considerations # # To reindex an existing database, you have to # * Stop slapd sudo service slapd stop -# * Reindex su openldap -c "slapindex -b 'o=mailHosting,dc=fripost,dc=org'" +# * Reindex su openldap -c "slapindex -b 'dc=fripost,dc=org'" # * Restart slapd sudo service slapd start # olcDbIndex: objectClass eq # Let us make Postfix's life easier. {% if 'MX' in group_names or 'MDA' in group_names %} olcDbIndex: fripostIsStatusActive,fvd,fvl eq {% endif %} {% if 'MX' in group_names %} olcDbIndex: fripostOptionalMaildrop pres {% endif %} {% if ('LDAP-provider' not in group_names and ('MX' in group_names or 'lists' in group_names)) or 'LDAP-provider' in group_names and (groups.MX | difference([inventory_hostname]) or groups.lists | difference([inventory_hostname])) %} # SyncProv/SyncRepl specific indexing. olcDbIndex: entryCSN,entryUUID eq {% endif%} # # # References # - https://wiki.zimbra.com/wiki/OpenLDAP_Performance_Tuning_5.0 # - http://www.openldap.org/doc/admin24/tuning.html # - http://www.openldap.org/faq/data/cache/42.html # - http://www.openldap.org/faq/data/cache/136.html # - http://www.zytrax.com/books/ldap/apa/indeces.html # # ######################################################################## # Sync Replication # # References: # - http://www.openldap.org/doc/admin24/replication.html#Syncrepl # - http://www.zytrax.com/books/ldap/ch7/#ol-syncrepl-rap # {% if 'LDAP-provider' in group_names %} {% if groups.MX | difference([inventory_hostname]) %} -olcLimits: dn.exact="cn=mx,ou=replicates,o=mailHosting,dc=fripost,dc=org" +olcLimits: dn.exact="cn=mX,ou=syncRepl,dc=fripost,dc=org" time.soft=unlimited time.hard=unlimited size.soft=unlimited size.hard=unlimited {% endif %} {% if groups.lists | difference([inventory_hostname]) %} -olcLimits: dn.exact="cn=lists,ou=replicates,o=mailHosting,dc=fripost,dc=org" +olcLimits: dn.exact="cn=lists,ou=syncRepl,dc=fripost,dc=org" time.soft=unlimited time.hard=unlimited size.soft=unlimited size.hard=unlimited {% endif %} {% endif %} {% if 'MX' in group_names and 'LDAP-provider' not in group_names %} # Test it: # LDAPSASL_MECH=external LDAPTLS_CACERT=/etc/ldap/ssl/ldap.fripost.org.pem LDAPTLS_CERT=/etc/ldap/ssl/mx.pem LDAPTLS_KEY=/etc/ldap/ssl/mx.key sudo -u openldap ldapwhoami -H ldaps://ldap.fripost.org/ -# LDAPSASL_MECH=external LDAPTLS_CACERT=/etc/ldap/ssl/ldap.fripost.org.pem LDAPTLS_CERT=/etc/ldap/ssl/mx.pem LDAPTLS_KEY=/etc/ldap/ssl/mx.key sudo -u openldap ldapsearch -H ldaps://ldap.fripost.org/ -b ou=virtual,o=mailHosting,dc=fripost,dc=org +# LDAPSASL_MECH=external LDAPTLS_CACERT=/etc/ldap/ssl/ldap.fripost.org.pem LDAPTLS_CERT=/etc/ldap/ssl/mx.pem LDAPTLS_KEY=/etc/ldap/ssl/mx.key sudo -u openldap ldapsearch -H ldaps://ldap.fripost.org/ -b ou=virtual,dc=fripost,dc=org olcSyncrepl: rid=000 provider=ldaps://ldap.fripost.org type=refreshAndPersist retry="10 30 300 +" - searchbase="ou=virtual,o=mailHosting,dc=fripost,dc=org" + searchbase="ou=virtual,dc=fripost,dc=org" attrs=objectClass,fvd,fvl,fripostMaildrop,fripostOptionalMaildrop,fripostPostmaster,fripostOwner scope=sub sizelimit=unlimited schemachecking=off bindmethod=sasl saslmech=external tls_cert=/etc/ldap/ssl/mx.pem tls_key=/etc/ldap/ssl/mx.key tls_cacert=/etc/ldap/ssl/ldap.fripost.org.pem tls_reqcert=hard {% endif %} {% if 'lists' in group_names and 'LDAP-provider' not in group_names %} olcSyncrepl: rid=001 provider=ldaps://ldap.fripost.org type=refreshAndPersist retry="10 30 300 +" - searchbase="ou=virtual,o=mailHosting,dc=fripost,dc=org" + searchbase="ou=virtual,dc=fripost,dc=org" attrs=objectClass,fvd,fvl,fripostListManager,fripostOwner scope=sub sizelimit=unlimited schemachecking=off bindmethod=sasl saslmech=external tls_cert=/etc/ldap/ssl/lists.pem tls_key=/etc/ldap/ssl/lists.key tls_cacert=/etc/ldap/ssl/ldap.fripost.org.pem tls_reqcert=hard {% endif %} # # ######################################################################## # Access control # /!\ WARN: All modification to the ACL should be reflected to the test # /!\ suite as well! olcAddContentAcl: TRUE # # Overview: @@ -200,324 +205,324 @@ olcAddContentAcl: TRUE # - http://www.openldap.org/faq/data/cache/1140.html # - http://www.openldap.org/faq/data/cache/1133.html # - man 5 slapd.access # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # User Passwords: # # * Anonymous users are allowed to simple bind through TLS-protected # connections. # * Anonymous users are allowed to simple bind when connecting to a # local ldapi:// socket (when using auth_binds, Dovecot delegates # authentication to the LDAP server). # * Authenticated users are allowed to change (ie replace) their # password through TLS-protected connections, but read access is not # granted. # * Domain postmasters are allowed to change (ie replace) their users' # password through TLS-protected connections, but read access is not # granted. # * The same goes for general admins. # * The same goes for local admins. -olcAccess: to dn.regex="^fvl=[^,]+,(fvd=[^,]+,ou=virtual,o=mailHosting,dc=fripost,dc=org)$" +olcAccess: to dn.regex="^fvl=[^,]+,(fvd=[^,]+,ou=virtual,dc=fripost,dc=org)$" filter=(objectClass=FripostVirtualUser) attrs=userPassword by realanonymous tls_ssf=128 =xd by realanonymous sockurl.regex="^ldapi://" =xd by realself tls_ssf=128 =w by group/FripostVirtualDomain/fripostPostmaster.expand="$1" tls_ssf=128 =w - by dn.onelevel="ou=admins,o=mailHosting,dc=fripost,dc=org" tls_ssf=128 =w + by dn.onelevel="ou=admins,dc=fripost,dc=org" tls_ssf=128 =w by dn.exact="username=guilhem,cn=peercred,cn=external,cn=auth" sockurl.regex="^ldapi://" =w # # XXX # * Anonymous users are allowed to simple bind as Postfix, but only when # using a local ldapi:// listener from one of the Postfix instance # (which should be accessible by the 'postfix' UNIX user only). -olcAccess: to dn.exact="cn=postfix,ou=services,o=mailHosting,dc=fripost,dc=org" +olcAccess: to dn.exact="cn=postfix,ou=services,dc=fripost,dc=org" attrs=userPassword by realanonymous sockurl.regex="^ldapi://%2Fvar%2Fspool%2Fpostfix-[-[:alnum:]]+%2Fprivate%2F" =xd # # TODO: are there other services which need to be able to simple bind? # # * Catch-all: no one else may access the passwords (including for # simple bind). -olcAccess: to dn.subtree="o=mailHosting,dc=fripost,dc=org" +olcAccess: to dn.subtree="dc=fripost,dc=org" attrs=userPassword by * =0 # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # Base # # * The SyncRepl replicates may use the base as a searchBase, when using # a TLS-protected connection. # * So can Dovecot on the MDA (for the iterate filter), when # SASL-binding using the EXTERNAL mechanism and connecting to a local # ldapi:// socket. -olcAccess: to dn.exact="ou=virtual,o=mailHosting,dc=fripost,dc=org" +olcAccess: to dn.exact="ou=virtual,dc=fripost,dc=org" attrs=entry,objectClass filter=(objectClass=FripostVirtual) {% if 'LDAP-provider' in group_names -%} {% if groups.MX | difference([inventory_hostname]) -%} - by dn.exact="cn=mx,ou=replicates,o=mailHosting,dc=fripost,dc=org" tls_ssf=128 =rsd + by dn.exact="cn=mX,ou=syncRepl,dc=fripost,dc=org" tls_ssf=128 =rsd {% endif -%} {% if groups.lists | difference([inventory_hostname]) -%} - by dn.exact="cn=lists,ou=replicates,o=mailHosting,dc=fripost,dc=org" tls_ssf=128 =rsd + by dn.exact="cn=lists,ou=syncRepl,dc=fripost,dc=org" tls_ssf=128 =rsd {% endif -%} {% endif -%} {% if 'MDA' in group_names -%} - by dn.exact="username=dovecot,cn=peercred,cn=external,cn=auth" sockurl.regex="^ldapi://" =sd + by dn.exact="username=dovecot,cn=peercred,cn=external,cn=auth" sockurl.regex="^ldapi://" =sd {% endif -%} - by users =0 break + by users =0 break # # * Only SyncRepl replicates may access operational attributes in the # subtree, when using a TLS-protected connection. -olcAccess: to dn.subtree="ou=virtual,o=mailHosting,dc=fripost,dc=org" +olcAccess: to dn.subtree="ou=virtual,dc=fripost,dc=org" attrs=structuralObjectClass,createTimestamp,creatorsName,entryDN,entryUUID,modifiersName,modifyTimestamp,hasSubordinates,subschemaSubentry {% if 'LDAP-provider' in group_names -%} {% if groups.MX | difference([inventory_hostname]) -%} - by dn.exact="cn=mx,ou=replicates,o=mailHosting,dc=fripost,dc=org" tls_ssf=128 =rsd + by dn.exact="cn=mX,ou=syncRepl,dc=fripost,dc=org" tls_ssf=128 =rsd {% endif -%} {% if groups.lists | difference([inventory_hostname]) -%} - by dn.exact="cn=lists,ou=replicates,o=mailHosting,dc=fripost,dc=org" tls_ssf=128 =rsd + by dn.exact="cn=lists,ou=syncRepl,dc=fripost,dc=org" tls_ssf=128 =rsd {% endif -%} {% endif -%} - by * =0 + by * =0 # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # Domain entries # # * The SyncRepl replicates have read access to the entry itself, when # using a TLS-protected connection. # * So has Postfix, when connecting a local ldapi:// socket from the # 'private' directory in one of the non-default instance's chroot. # * So has Dovecot on the MDA (for the iterate filter), when # SASL-binding using the EXTERNAL mechanism and connecting to a local # ldapi:// socket. # * Amavis may use the entry as searchBase (required to look for the # per-user preferences) but doesn't have read access to the entry. # * The 'nobody' UNIX user has read access on the MX:es, when using # SASL-binding using the EXTERNAL mechanism and connecting to a local # ldapi:// socket. This is required for the 'reserved-alias.pl' # script. -olcAccess: to dn.regex="^fvd=[^,]+,ou=virtual,o=mailHosting,dc=fripost,dc=org$" +olcAccess: to dn.regex="^fvd=[^,]+,ou=virtual,dc=fripost,dc=org$" attrs=entry,objectClass,fvd filter=(&(objectClass=FripostVirtualDomain)(!(objectClass=FripostPendingEntry))) {% if 'LDAP-provider' in group_names -%} {% if groups.MX | difference([inventory_hostname]) -%} - by dn.exact="cn=mx,ou=replicates,o=mailHosting,dc=fripost,dc=org" tls_ssf=128 =rsd + by dn.exact="cn=mX,ou=syncRepl,dc=fripost,dc=org" tls_ssf=128 =rsd {% endif -%} {% if groups.lists | difference([inventory_hostname]) -%} - by dn.exact="cn=lists,ou=replicates,o=mailHosting,dc=fripost,dc=org" tls_ssf=128 =rsd + by dn.exact="cn=lists,ou=syncRepl,dc=fripost,dc=org" tls_ssf=128 =rsd {% endif -%} {% endif -%} - by dn.exact="cn=postfix,ou=services,o=mailHosting,dc=fripost,dc=org" sockurl.regex="^ldapi://%2Fvar%2Fspool%2Fpostfix-[-[:alnum:]]+%2Fprivate%2F" =rsd + by dn.exact="cn=postfix,ou=services,dc=fripost,dc=org" sockurl.regex="^ldapi://%2Fvar%2Fspool%2Fpostfix-[-[:alnum:]]+%2Fprivate%2F" =rsd {% if 'MDA' in group_names -%} - by dn.exact="username=dovecot,cn=peercred,cn=external,cn=auth" sockurl.regex="^ldapi://" =rsd - by dn.exact="username=amavis,cn=peercred,cn=external,cn=auth" sockurl.regex="^ldapi://" =sd + by dn.exact="username=dovecot,cn=peercred,cn=external,cn=auth" sockurl.regex="^ldapi://" =rsd + by dn.exact="username=amavis,cn=peercred,cn=external,cn=auth" sockurl.regex="^ldapi://" =sd {% endif -%} {% if 'MX' in group_names -%} - by dn.exact="username=nobody,cn=peercred,cn=external,cn=auth" sockurl.regex="^ldapi://" =rsd + by dn.exact="username=nobody,cn=peercred,cn=external,cn=auth" sockurl.regex="^ldapi://" =rsd {% endif -%} - by users =0 break + by users =0 break # # * The SyncRepl MX replicates can check whether a virtual domain is # active, and read the destination address for catch-alls, when using # a TLS-protected connection. # * So can Postfix on the MX:es, when connecting a local ldapi:// socket # from the 'private' directory in one of the non-default instance's # chroot. {% if 'MX' in group_names or ('LDAP-provider' in group_names and groups.MX | difference([inventory_hostname])) %} -olcAccess: to dn.regex="^fvd=[^,]+,ou=virtual,o=mailHosting,dc=fripost,dc=org$" +olcAccess: to dn.regex="^fvd=[^,]+,ou=virtual,dc=fripost,dc=org$" attrs=fripostIsStatusActive,fripostOptionalMaildrop filter=(&(objectClass=FripostVirtualDomain)(!(objectClass=FripostPendingEntry))) {% if 'LDAP-provider' in group_names and groups.MX | difference([inventory_hostname]) -%} - by dn.exact="cn=mx,ou=replicates,o=mailHosting,dc=fripost,dc=org" tls_ssf=128 =rsd + by dn.exact="cn=mX,ou=syncRepl,dc=fripost,dc=org" tls_ssf=128 =rsd {% endif -%} {% if 'MX' in group_names -%} - by dn.exact="cn=postfix,ou=services,o=mailHosting,dc=fripost,dc=org" sockurl.regex="^ldapi://%2Fvar%2Fspool%2Fpostfix-[-[:alnum:]]+%2Fprivate%2F" =rsd + by dn.exact="cn=postfix,ou=services,dc=fripost,dc=org" sockurl.regex="^ldapi://%2Fvar%2Fspool%2Fpostfix-[-[:alnum:]]+%2Fprivate%2F" =rsd {% endif -%} - by users =0 break + by users =0 break {% endif %} # # * The 'nobody' UNIX user can list the domain owners and postmasters on # the MX:es, when SASL-binding using the EXTERNAL mechanism and # connecting to a local ldapi:// socket. This is required for the # 'reserved-alias.pl' script. {% if 'MX' in group_names %} -olcAccess: to dn.regex="^fvd=[^,]+,ou=virtual,o=mailHosting,dc=fripost,dc=org$" +olcAccess: to dn.regex="^fvd=[^,]+,ou=virtual,dc=fripost,dc=org$" attrs=fripostOwner,fripostPostmaster filter=(&(objectClass=FripostVirtualDomain)(!(objectClass=FripostPendingEntry))) by dn.exact="username=nobody,cn=peercred,cn=external,cn=auth" sockurl.regex="^ldapi://" =rsd - by users =0 break + by users =0 break {% endif %} # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # Alias domain entries # # * The SyncRepl MX replicates have read access to the entry itself and # the destination domain it aliases to, when using a TLS-protected # connection. # * So has Postfix on the MX:es, when connecting a local ldapi:// socket # from the 'private' directory in one of the non-default instance's # chroot. {% if 'MX' in group_names or ('LDAP-provider' in group_names and groups.MX | difference([inventory_hostname])) %} -olcAccess: to dn.regex="^fvd=[^,]+,ou=virtual,o=mailHosting,dc=fripost,dc=org$" +olcAccess: to dn.regex="^fvd=[^,]+,ou=virtual,dc=fripost,dc=org$" attrs=entry,fripostMaildrop filter=(&(objectClass=FripostVirtualAliasDomain)(!(objectClass=FripostPendingEntry))) {% if 'LDAP-provider' in group_names and groups.MX | difference([inventory_hostname]) -%} - by dn.exact="cn=mx,ou=replicates,o=mailHosting,dc=fripost,dc=org" tls_ssf=128 =rsd + by dn.exact="cn=mX,ou=syncRepl,dc=fripost,dc=org" tls_ssf=128 =rsd {% endif -%} {% if 'MX' in group_names -%} - by dn.exact="cn=postfix,ou=services,o=mailHosting,dc=fripost,dc=org" sockurl.regex="^ldapi://%2Fvar%2Fspool%2Fpostfix-[-[:alnum:]]+%2Fprivate%2F" =rsd + by dn.exact="cn=postfix,ou=services,dc=fripost,dc=org" sockurl.regex="^ldapi://%2Fvar%2Fspool%2Fpostfix-[-[:alnum:]]+%2Fprivate%2F" =rsd {% endif -%} - by users =0 break + by users =0 break {% endif %} # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # User entries # # * The SyncRepl replicates have read access to the entry itself, when # using a TLS-protected connection. # * So has Postfix, when connecting a local ldapi:// socket from the # 'private' directory in one of the non-default instance's chroot. # * So has Dovecot on the MDA (for the iterate filter), when # SASL-binding using the EXTERNAL mechanism and connecting to a local # ldapi:// socket. # * So has Amavis on the MDA, when SASL-binding using the EXTERNAL # mechanism and connecting to a local ldapi:// socket. -olcAccess: to dn.regex="^fvl=[^,]+,fvd=[^,]+,ou=virtual,o=mailHosting,dc=fripost,dc=org$" +olcAccess: to dn.regex="^fvl=[^,]+,fvd=[^,]+,ou=virtual,dc=fripost,dc=org$" attrs=entry,objectClass,fvl filter=(objectClass=FripostVirtualUser) {% if 'LDAP-provider' in group_names and groups.MX | difference([inventory_hostname]) -%} - by dn.exact="cn=mx,ou=replicates,o=mailHosting,dc=fripost,dc=org" tls_ssf=128 =rsd + by dn.exact="cn=mX,ou=syncRepl,dc=fripost,dc=org" tls_ssf=128 =rsd {% endif -%} - by dn.exact="cn=postfix,ou=services,o=mailHosting,dc=fripost,dc=org" sockurl.regex="^ldapi://%2Fvar%2Fspool%2Fpostfix-[-[:alnum:]]+%2Fprivate%2F" =rsd + by dn.exact="cn=postfix,ou=services,dc=fripost,dc=org" sockurl.regex="^ldapi://%2Fvar%2Fspool%2Fpostfix-[-[:alnum:]]+%2Fprivate%2F" =rsd {% if 'MDA' in group_names -%} - by dn.exact="username=dovecot,cn=peercred,cn=external,cn=auth" sockurl.regex="^ldapi://" =rsd - by dn.exact="username=amavis,cn=peercred,cn=external,cn=auth" sockurl.regex="^ldapi://" =rsd + by dn.exact="username=dovecot,cn=peercred,cn=external,cn=auth" sockurl.regex="^ldapi://" =rsd + by dn.exact="username=amavis,cn=peercred,cn=external,cn=auth" sockurl.regex="^ldapi://" =rsd {% endif -%} - by users =0 break + by users =0 break # # * The SyncRepl MX replicates can check whether a virtual user is # active, when using a TLS-protected connection. # * So can Postfix on the MX:es, when connecting a local ldapi:// socket # from the 'private' directory in one of the non-default instance's # chroot. {% if 'MX' in group_names or ('LDAP-provider' in group_names and groups.MX | difference([inventory_hostname])) %} -olcAccess: to dn.regex="^fvl=[^,]+,fvd=[^,]+,ou=virtual,o=mailHosting,dc=fripost,dc=org$" +olcAccess: to dn.regex="^fvl=[^,]+,fvd=[^,]+,ou=virtual,dc=fripost,dc=org$" attrs=fripostIsStatusActive filter=(objectClass=FripostVirtualUser) {% if 'LDAP-provider' in group_names and groups.MX | difference([inventory_hostname]) -%} - by dn.exact="cn=mx,ou=replicates,o=mailHosting,dc=fripost,dc=org" tls_ssf=128 =rsd + by dn.exact="cn=mX,ou=syncRepl,dc=fripost,dc=org" tls_ssf=128 =rsd {% endif -%} {% if 'MX' in group_names -%} - by dn.exact="cn=postfix,ou=services,o=mailHosting,dc=fripost,dc=org" sockurl.regex="^ldapi://%2Fvar%2Fspool%2Fpostfix-[-[:alnum:]]+%2Fprivate%2F" =rsd + by dn.exact="cn=postfix,ou=services,dc=fripost,dc=org" sockurl.regex="^ldapi://%2Fvar%2Fspool%2Fpostfix-[-[:alnum:]]+%2Fprivate%2F" =rsd {% endif -%} - by users =0 break + by users =0 break {% endif %} {% if 'MDA' in group_names %} # # * Amavis can look for per-user configuration options, when # SASL-binding using the EXTERNAL mechanism and connecting to a local # ldapi:// socket. # TODO: we need a fripostUseContentFilter here # filter=(&(objectClass=FripostVirtualUser)(objectClass=AmavisAccount)(fripostIsStatusActive=TRUE)(fripostUseContentFilter=TRUE)) # TODO: only allow it to read the configuration options users are allowed # to set and modify. -olcAccess: to dn.regex="^fvl=[^,]+,fvd=[^,]+,ou=virtual,o=mailHosting,dc=fripost,dc=org$" +olcAccess: to dn.regex="^fvl=[^,]+,fvd=[^,]+,ou=virtual,dc=fripost,dc=org$" attrs=@AmavisAccount filter=(&(objectClass=FripostVirtualUser)(objectClass=AmavisAccount)(fripostIsStatusActive=TRUE)) by dn.exact="username=amavis,cn=peercred,cn=external,cn=auth" sockurl.regex="^ldapi://" =rsd by users =0 break # # * Dovecot can look for user quotas, when SASL-binding using the # EXTERNAL mechanism and connecting to a local ldapi:// socket. -olcAccess: to dn.regex="^fvl=[^,]+,fvd=[^,]+,ou=virtual,o=mailHosting,dc=fripost,dc=org$" +olcAccess: to dn.regex="^fvl=[^,]+,fvd=[^,]+,ou=virtual,dc=fripost,dc=org$" attrs=fripostUserQuota filter=(objectClass=FripostVirtualUser) by dn.exact="username=dovecot,cn=peercred,cn=external,cn=auth" sockurl.regex="^ldapi://" =rsd by users =0 break {% endif %} # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # Alias entries # # * The SyncRepl MX replicates can read the entry itelf, whether it # is active, and the address(es) it aliases to, when using a # TLS-protected connection. # * So can Postfix on the MX:es, when connecting a local ldapi:// socket # from the 'private' directory in one of the non-default instance's # chroot. {% if 'MX' in group_names or ('LDAP-provider' in group_names and groups.MX | difference([inventory_hostname])) %} -olcAccess: to dn.regex="^fvl=[^,]+,fvd=[^,]+,ou=virtual,o=mailHosting,dc=fripost,dc=org$" +olcAccess: to dn.regex="^fvl=[^,]+,fvd=[^,]+,ou=virtual,dc=fripost,dc=org$" attrs=entry,objectClass,fvl,fripostMaildrop,fripostIsStatusActive filter=(objectClass=FripostVirtualAlias) {% if 'LDAP-provider' in group_names and groups.MX | difference([inventory_hostname]) -%} - by dn.exact="cn=mx,ou=replicates,o=mailHosting,dc=fripost,dc=org" tls_ssf=128 =rsd + by dn.exact="cn=mX,ou=syncRepl,dc=fripost,dc=org" tls_ssf=128 =rsd {% endif -%} {% if 'MX' in group_names -%} - by dn.exact="cn=postfix,ou=services,o=mailHosting,dc=fripost,dc=org" sockurl.regex="^ldapi://%2Fvar%2Fspool%2Fpostfix-[-[:alnum:]]+%2Fprivate%2F" =rsd + by dn.exact="cn=postfix,ou=services,dc=fripost,dc=org" sockurl.regex="^ldapi://%2Fvar%2Fspool%2Fpostfix-[-[:alnum:]]+%2Fprivate%2F" =rsd {% endif -%} - by users =0 break + by users =0 break {% endif %} # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # List entries # # * The SyncRepl replicates can read the entry itelf and the list manager, when # using a TLS-protected connection. # * So can Postfix on the MX:es and lists managers, when connecting a local # ldapi:// socket from the 'private' directory in one of the non-default # instance's chroot. # XXX: where does sympa enter the picture? we really don't want to reintroduce listcomands... {% if 'MX' in group_names or 'lists' in group_names or ('LDAP-provider' in group_names and (groups.lists | difference([inventory_hostname]) or groups.MX | difference([inventory_hostname]))) %} -olcAccess: to dn.regex="^fvl=[^,]+,fvd=[^,]+,ou=virtual,o=mailHosting,dc=fripost,dc=org$" +olcAccess: to dn.regex="^fvl=[^,]+,fvd=[^,]+,ou=virtual,dc=fripost,dc=org$" attrs=entry,objectClass,fvl,fripostListManager filter=(&(objectClass=FripostVirtualList)(!(objectClass=FripostPendingEntry))) {% if 'LDAP-provider' in group_names -%} {% if groups.MX | difference([inventory_hostname]) -%} - by dn.exact="cn=mx,ou=replicates,o=mailHosting,dc=fripost,dc=org" tls_ssf=128 =rsd + by dn.exact="cn=mX,ou=syncRepl,dc=fripost,dc=org" tls_ssf=128 =rsd {% endif -%} {% if groups.lists | difference([inventory_hostname]) -%} - by dn.exact="cn=lists,ou=replicates,o=mailHosting,dc=fripost,dc=org" tls_ssf=128 =rsd + by dn.exact="cn=lists,ou=syncRepl,dc=fripost,dc=org" tls_ssf=128 =rsd {% endif -%} {% endif -%} {% if 'MX' in group_names or 'lists' in group_names -%} - by dn.exact="cn=postfix,ou=services,o=mailHosting,dc=fripost,dc=org" sockurl.regex="^ldapi://%2Fvar%2Fspool%2Fpostfix-[-[:alnum:]]+%2Fprivate%2F" =rsd + by dn.exact="cn=postfix,ou=services,dc=fripost,dc=org" sockurl.regex="^ldapi://%2Fvar%2Fspool%2Fpostfix-[-[:alnum:]]+%2Fprivate%2F" =rsd {% endif -%} - by users =0 break + by users =0 break {% endif %} # # * The SyncRepl MX replicates can check whether a virtual list is # active when using a TLS-protected connection. # * So can Postfix on the MX:es, when connecting a local ldapi:// socket # from the 'private' directory in one of the non-default instance's # chroot. {% if 'MX' in group_names or ('LDAP-provider' in group_names and groups.MX | difference([inventory_hostname])) %} -olcAccess: to dn.regex="^fvl=[^,]+,fvd=[^,]+,ou=virtual,o=mailHosting,dc=fripost,dc=org$" +olcAccess: to dn.regex="^fvl=[^,]+,fvd=[^,]+,ou=virtual,dc=fripost,dc=org$" attrs=fripostIsStatusActive filter=(&(objectClass=FripostVirtualList)(!(objectClass=FripostPendingEntry))) {% if 'LDAP-provider' in group_names and groups.MX | difference([inventory_hostname]) -%} - by dn.exact="cn=mx,ou=replicates,o=mailHosting,dc=fripost,dc=org" tls_ssf=128 =rsd + by dn.exact="cn=mX,ou=syncRepl,dc=fripost,dc=org" tls_ssf=128 =rsd {% endif -%} {% if 'MX' in group_names -%} - by dn.exact="cn=postfix,ou=services,o=mailHosting,dc=fripost,dc=org" sockurl.regex="^ldapi://%2Fvar%2Fspool%2Fpostfix-[-[:alnum:]]+%2Fprivate%2F" =rsd + by dn.exact="cn=postfix,ou=services,dc=fripost,dc=org" sockurl.regex="^ldapi://%2Fvar%2Fspool%2Fpostfix-[-[:alnum:]]+%2Fprivate%2F" =rsd {% endif -%} - by users =0 break + by users =0 break {% endif %} {% if 'LDAP-provider' in group_names %} # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # TODO: allow users to edit their entry, etc # {% endif %} # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # Catch-all # # * Catch all the breaks above. # * Deny any access to everyone else. -olcAccess: to dn.subtree="o=mailHosting,dc=fripost,dc=org" - by dn.children="ou=virtual,o=mailHosting,dc=fripost,dc=org" +0 - by * =0 +olcAccess: to dn.subtree="dc=fripost,dc=org" + by dn.children="ou=virtual,dc=fripost,dc=org" +0 + by * =0 # vim: set filetype=ldif : diff --git a/roles/lists/files/etc/postfix/virtual/transport_list.cf b/roles/lists/files/etc/postfix/virtual/transport_list.cf index f85c4f8..384b832 100644 --- a/roles/lists/files/etc/postfix/virtual/transport_list.cf +++ b/roles/lists/files/etc/postfix/virtual/transport_list.cf @@ -1,8 +1,8 @@ server_host = ldapi://%2Fprivate%2Fldapi/ version = 3 -search_base = fvl=%u,fvd=%d,ou=virtual,o=mailHosting,dc=fripost,dc=org +search_base = fvl=%u,fvd=%d,ou=virtual,dc=fripost,dc=org domain = static:all scope = base bind = none query_filter = (&(objectClass=FripostVirtualList)(fvl=%u)) result_attribute = fripostListManager diff --git a/roles/webmail/templates/usr/share/roundcube/plugins/password/config.inc.php.j2 b/roles/webmail/templates/usr/share/roundcube/plugins/password/config.inc.php.j2 index a661909..06f1556 100644 --- a/roles/webmail/templates/usr/share/roundcube/plugins/password/config.inc.php.j2 +++ b/roles/webmail/templates/usr/share/roundcube/plugins/password/config.inc.php.j2 @@ -28,68 +28,68 @@ $rcmail_config['password_log'] = false; // You can provide one or several hosts in an array in which case the hosts are tried from left to right. // Exemple: array('ldap1.exemple.com', 'ldap2.exemple.com'); // Default: 'localhost' $rcmail_config['password_ldap_host'] = 'ldap.fripost.org'; // LDAP server port to connect to // Default: '389' $rcmail_config['password_ldap_port'] = '389'; // TLS is started after connecting // Using TLS for password modification is recommanded. // Default: false $rcmail_config['password_ldap_starttls'] = false; // LDAP version // Default: '3' $rcmail_config['password_ldap_version'] = '3'; // LDAP base name (root directory) // Exemple: 'dc=exemple,dc=com' -$rcmail_config['password_ldap_basedn'] = 'ou=virtual,o=mailHosting,dc=fripost,dc=org'; +$rcmail_config['password_ldap_basedn'] = 'ou=virtual,dc=fripost,dc=org'; // LDAP connection method // There is two connection method for changing a user's LDAP password. // 'user': use user credential (recommanded, require password_confirm_current=true) // 'admin': use admin credential (this mode require password_ldap_adminDN and password_ldap_adminPW) // Default: 'user' $rcmail_config['password_ldap_method'] = 'user'; // LDAP Admin DN // Used only in admin connection mode // Default: null $rcmail_config['password_ldap_adminDN'] = null; // LDAP Admin Password // Used only in admin connection mode // Default: null $rcmail_config['password_ldap_adminPW'] = null; // LDAP user DN mask // The user's DN is mandatory and as we only have his login, // we need to re-create his DN using a mask // '%login' will be replaced by the current roundcube user's login // '%name' will be replaced by the current roundcube user's name part // '%domain' will be replaced by the current roundcube user's domain part // '%dc' will be replaced by domain name hierarchal string e.g. "dc=test,dc=domain,dc=com" // Exemple: 'uid=%login,ou=people,dc=exemple,dc=com' -$rcmail_config['password_ldap_userDN_mask'] = 'fvl=%name,fvd=%domain,ou=virtual,o=mailHosting,dc=fripost,dc=org'; +$rcmail_config['password_ldap_userDN_mask'] = 'fvl=%name,fvd=%domain,ou=virtual,dc=fripost,dc=org'; // LDAP search DN // The DN roundcube should bind with to find out user's DN // based on his login. Note that you should comment out the default // password_ldap_userDN_mask setting for this to take effect. // Use this if you cannot specify a general template for user DN with // password_ldap_userDN_mask. You need to perform a search based on // users login to find his DN instead. A common reason might be that // your users are placed under different ou's like engineering or // sales which cannot be derived from their login only. $rcmail_config['password_ldap_searchDN'] = null; // LDAP search password // If password_ldap_searchDN is set, the password to use for // binding to search for user's DN. Note that you should comment out the default // password_ldap_userDN_mask setting for this to take effect. // Warning: Be sure to set approperiate permissions on this file so this password // is only accesible to roundcube and don't forget to restrict roundcube's access to // your directory as much as possible using ACLs. Should this password be compromised // you want to minimize the damage. |