H.1. ldap2pg#

H.1. ldap2pg

H.1. ldap2pg #

Postgres is able to check password of an existing role using the LDAP protocol out of the box. ldap2pg automates the creation, update and removal of PostgreSQL roles and users from an entreprise directory.

Managing roles is close to managing privileges as you expect roles to have proper default privileges. ldap2pg can grant and revoke privileges too.

H.1.1. About ldap2pg #

Version: 6.2

GitHub

H.1.2. Features #

  • Reads settings from an expressive YAML config file.

  • Creates, alters and drops PostgreSQL roles from LDAP searches.

  • Creates static roles from YAML to complete LDAP entries.

  • Manages role parents (alias groups).

  • Grants or revokes privileges statically or from LDAP entries.

  • Dry run, check mode.

  • Logs LDAP searches as ldapsearch(1) commands.

  • Logs every SQL statements.

ldap2pg is licensed under PostgreSQL license.

ldap2pg requires a configuration file called ldap2pg.yaml. Project ships a tested ldap2pg.yml as a starting point.

ldap2pg is reported to work with OpenLDAP, FreeIPA, Oracle Internet Directory and Microsoft Active Directory.

H.1.3. Installation #

H.1.3.1. Requirements #

ldap2pg is released as a Go binary with no requirements.

On runtime, ldap2pg requires an unprivileged role with CREATEDB and CREATEROLE options or a superuser access. ldap2pg does not require to run on the same host as the synchronized PostgreSQL cluster.

With 2MiB of RAM and one vCPU, ldap2pg can synchronize several thousands of roles in seconds, depending on PostgreSQL instance and LDAP directory response time.

H.1.3.2. Installing binary #

  • Download binary for your target system and architecture from ldap2pg release page.

  • Move the binary to /usr/local/bin.

  • Ensure it’s executable with chmod 0755 /usr/local/bin/ldap2pg.

  • Test installation with ldap2pg --version.

H.1.3.3. Using yum/dnf #

On RHEL and compatible clone, Dalibo Labs YUM repository offer RPM package for ldap2pg.

For using Dalibo Labs packaging:

H.1.4. Command Line Interface #

ldap2pg tries to be friendly regarding configuration and consistent with psql, OpenLDAP utils and 12 factors apps. ldap2pg reads its configuration from several sources, in the following order, first prevail:

  1. command line arguments.

  2. environment variables.

  3. configuration file.

  4. ldaprc, ldap.conf, etc.

The --help switch shows regular online documentation for CLI arguments. As of version 5.7, this looks like:

$ ldap2pg --help
usage: ldap2pg [OPTIONS] [dbname]

      --check             Check mode: exits with 1 if Postgres instance is unsynchronized.
      --color             Force color output.
  -c, --config string     Path to YAML configuration file. Use - for stdin.
  -?, --help              Show this help message and exit. (default true)
  -q, --quiet count       Decrease log verbosity.
  -R, --real              Real mode. Apply changes to Postgres instance.
  -P, --skip-privileges   Turn off privilege synchronisation.
  -v, --verbose count     Increase log verbosity.
  -V, --version           Show version and exit. (default true)

Optional argument dbname is alternatively the database name or a conninfo string or an URI.
See man psql(1) for more information.

By default, ldap2pg runs in dry mode.
ldap2pg requires a configuration file to describe LDAP searches and mappings.
See https://ldap2pg.readthedocs.io/en/latest/ for further details.

Arguments can be defined multiple times. On conflict, the last argument is used.

H.1.4.1. Environment variables #

ldap2pg has no CLI switch to configure Postgres connection. However, ldap2pg supports libpq PG* env vars.

See psql(1) for details on libpq env vars.

The same goes for LDAP, ldap2pg supports standard LDAP* env vars and ldaprc files. See ldap.conf(5) for further details on how to configure. ldap2pg accepts one extra variable: LDAPPASSWORD.

ldap2pg loads .env file in the lda2pg.yml’s parent directory if exists.

Use true or false for boolean values in environment. e.g. LDAP2PG_SKIPPRIVILEGES=true.

Tip

Test Postgres connexion using psql(1) and LDAP using ldapwhoami(1), ldap2pg will be okay and it will be easier to debug the setup and the configuration later.

H.1.4.2. Logging setup #

ldap2pg have several levels of logging:

  • ERROR: error details. When this happend, ldap2pg will crash.

  • WARNING: ldap2pg warns about choices you should be aware of.

  • CHANGE: only changes applied to Postgres cluster. (aka Magnus Hagander level).

  • INFO (default): tells what ldap2pg is doing, especially before long task.

  • DEBUG: everything, including raw SQL queries and LDAP searches and introspection details.

The --quiet and --verbose switches respectively decrease and increase verbosity.

You can select the highest level of verbosity with LDAP2PG_VERBOSITY envvar. For example:

$ LDAP2PG_VERBOSITY=DEBUG ldap2pg
12:23:45 INFO   Starting ldap2pg                                 version=v6.0-alpha5 runtime=go1.21.0 commit=<none>
12:23:45 WARN   Running a prerelease! Use at your own risks!
12:23:45 DEBUG  Searching configuration file in standard locations.
12:23:45 DEBUG  Found configuration file.                        path=./ldap2pg.yml
$

ldap2pg output varies whether it’s running with a TTY or not. If standard error is a TTY, logging is colored and tweaked for human reading. Otherwise, logging format is pure logfmt, for machine processing. You can force human-readable output by using --color CLI switch.

H.1.5. ldap2pg.yml file reference #

ldap2pg requires a YAML configuration file usually named ldap2pg.yml and put in working directory. Everything can be configured from the YAML file: Postgres inspect queries, LDAP searches, privileges and synchronization map.

Warning

ldap2pg requires a config file where the synchronization map is described.

H.1.5.1. File Location #

ldap2pg searches for configuration file in the following order :

  1. ldap2pg.yml in current working directory.

  2. ~/.config/ldap2pg.yml.

  3. /etc/ldap2pg.yml.

  4. /etc/ldap2pg/ldap2pg.yml.

If LDAP2PG_CONFIG or --config is set, ldap2pg skips searching the standard file locations. You can specify - to read configuration from standard input. This is helpful to feed ldap2pg with dynamic configuration.

H.1.5.2. File Structure #

ldap2pg.yml is split in several sections :

  • postgres : setup Postgres connexion and inspection queries.

  • privileges : the definition of privileges profiles.

  • rules : the list of LDAP searches and associated mapping to roles and grants.

The project provides a simple well commented ldap2pg.yml, tested on CI. If you don’t know how to begin, it is a good starting point.

Note

If you have trouble finding the right configuration for your needs, feel free to file an issue to get help.

H.1.5.2.1. About YAML #

YAML is a super-set of JSON. A JSON document is a valid YAML document. YAML is a very permissive format where indentation is meaningful.

In ldap2pg.yaml file, you will likely use wildcard for glob pattern and curly brace for LDAP attribute injection. Take care of protecting these characters with quotes.

H.1.5.3. Postgres Parameters #

The postgres section defines custom SQL queries for Postgres inspection.

The postgres section contains several *_query parameters. These parameters can be either a string containing an SQL query or a YAML list to return a static list of values, skipping execution of a query on PostgreSQL cluster.

H.1.5.3.1. databases_query #

The SQL query to list databases names in the cluster. By default, ldap2pg searches databases it cans connect to and it can reassign objects to its owner. ldap2pg loops databases to reassign objects before dropping a role. ldap2pg manages privilege on each database.

postgres:
  databases_query: "SELECT datname FROM pg_catalog.pg_databases;"
  # OR
  databases_query: [mydb]

Note

Configuring a _query parameter with a YAML list skip querying the cluster for inspection and forces ldap2pg to use a static value.

H.1.5.3.2. fallback_owner #

Name of the role accepting ownership of database of dropped role.

Before dropping a role, ldap2pg reassigns objects and purges ACL. ldap2pg starts by reassigning database owner by the targetted user. The new owner of the database is the fallback owner. Other objects are reassigned to each database owner.

H.1.5.3.3. managed_roles_query #

The SQL query to list the name of managed roles.

ldap2pg restricts role deletion and privilege edition to managed roles. Usualy, this query returns children of a dedicated group like ldap_roles. By default, ldap2pg manages all roles it has access to.

public is a special builtin role in Postgres. If managed_roles_query returns public role in the list, ldap2pg will manage privileges on public. By default, ldap2pg manages public privileges.

The following example tells ldap2pg to manage public role, ldap_roles and any members of ldap_roles:

postgres:
  managed_roles_query: |
    VALUES
      ('public'),
      ('ldap_roles')

    UNION

    SELECT DISTINCT role.rolname
    FROM pg_roles AS role
    JOIN pg_auth_members AS ms ON ms.member = role.oid
    JOIN pg_roles AS parent
      ON parent.rolname = 'ldap_roles' AND parent.oid = ms.roleid
    ORDER BY 1;
H.1.5.3.4. roles_blacklist_query #

The SQL query returning name and glob pattern to blacklist role from management. ldap2pg won’t touch anything on these roles. Default value is [postgres, pg_*]. ldap2pg blacklist self user.

postgres:
  roles_blacklist_query:
  - postgres
  - "pg_*"
  - "rds_*"

Warning

Beware that *foo is a YAML reference. You must quote pattern beginning with *.

H.1.5.3.5. schemas_query #

The SQL query returning the name of managed schemas in a database. ldap2pg executes this query on each databases returned by databases_query, only if ldap2pg manages privileges. ldap2pg loops on objects in theses schemas when inspecting GRANTs in the cluster.

postgres:
  schemas_query: |
    SELECT nspname FROM pg_catalog.pg_namespace

H.1.5.4. PostgreSQL Privileges Section #

The privileges top level section is a mapping defining privilege profiles, referenced later in Synchronisation map’s grant rule. A privilege profile is a list of either a reference to a privilege type in a Postgres ACL or other profile. A privilege profile may include another profile, recursively. See Managing Privileges for details.

privileges:
  reading:
  - default: global
    type: SELECT
    on: TABLES

  writing:
  - reading
  - default: global
    type: SELECT
    on: TABLES

A privilege profile whose name starts with _ is inactive unless included in an active profile.

H.1.5.4.1. default #

Defines the scope of default privileges. Can be undefined or either global or schema. global scope references default privileges for any schemas, including future schemas. schema scope references default privileges on specific schemas. Target schema is defined by grant rule.

privileges:
  reading:
  - default: global
    type: SELECT
    on: TABLES
H.1.5.4.2. type #

Type of privilege as described in Section 5.7 e.g. SELECT, REFERENCES, USAGE, etc.

privileges:
  reading:
  - type: USAGE
    on: SCHEMAS
H.1.5.4.3. on #

Target ACL of privilege type. e.g. TABLES, SEQUENCES, SCHEMAS, etc. Note the special cases ALL TABLES, ALL SEQUENCES, etc. See Managing Privileges documentation for details.

privileges:
  reading:
  - type: SELECT
    on: ALL TABLES

H.1.5.5. Synchronisation rules #

The top level rules section is a YAML list. This is the only mandatory parameter in ldap2pg.yaml. Each item of rules is called a mapping. A mapping is a YAML dict with any of role or grant subsection. A mapping can optionnaly have a description field and a ldapsearch section.

rules:
- description: "Define DBA roles"
  ldapsearch:
    base: ...
  roles:
  - name: "{cn}"
    options: LOGIN SUPERUSER

The ldapsearch subsection is optional. You can define roles and grants without querying a directory.

H.1.5.5.1. description #

A free string used for logging. This parameter does not accepts mustache parameter injection.

H.1.5.5.2. ldapsearch #

This directive defines LDAP search parameters. It is named after the ldapsearch CLI utility shipped by OpenLDAP project. It’s behaviour should be mostly the same.

Note

This documentation refers LDAP query as search while the word query is reserved for SQL query.

ldapsearch directives allows and requires LDAP attributes injection in role and grant rules using curly braces. See Searching directory for details.

H.1.5.5.2.1. base, scope and filter #

These parameters have the same meaning, definition and default as searchbase, scope and filter arguments of ldapsearch CLI utility.

rules:
- ldapsearch:
    base: ou=people,dc=acme,dc=tld
    scope: sub
    filter: >
      (&
         (member=*)
         (cn=group_*)
      )
H.1.5.5.2.2. joins #

Customizes LDAP sub-searches. The joins section is a dictionary with attribute name as key and LDAP search parameters as value. LDAP search parameters are the same as for top LDAP search.

rules:
- ldapsearch:
    joins:
      member:
        filter: ...
        scope: ...
  role:
  - name: "{member.sAMAccountName}"

The search base of sub-search is the value of the referencing attribute, e.g. each value of member. You can’t customize the base attribute of sub-search. Likewise, ldap2pg infers attributes of sub-searches from role and grant rules. You can have only a single sub-search per top-level search. You can’t do sub-sub-search.

See Searching directory for details.

Note

Executing a sub-search for each entry of a result set can be very heavy. You may optimize the query by using special LDAP search filter like memberOf. Refer to your LDAP directory administrator and documentation for details.

H.1.5.5.3. role #

Defines a rule to describe one or more roles wanted in the target Postgres cluster. This includes name, options, config, comment and membership. Plural form roles is valid. The value can be either a single role rule or a list of role rules.

rules:
- role:
    name: dba
    options: SUPERUSER LOGIN
- roles:
  - name: group0
    options: NOLOGIN
  - name: group1
    options: NOLOGIN
H.1.5.5.3.1. comment #

Defines the SQL comment of a role. Default value is Managed by ldap2pg. Accepts LDAP attribute injection.

In case of LDAP attributes injection, you must take care of how many combination will be generated. If the template generates a single comment, ldap2pg will copy the comment for each role generated by the role rule. If the template generates multiple comments, ldap2pg associates name and comment. If there is more or less comments generated than name generated, ldap2pg fails.

The following example defines a static comment shared by all generated roles:

rules:
- roles:
    names:
    - alice
    - bob
    comment: "Static roles from YAML."

The following example generates a single comment from LDAP entry distinguised name, copied for all generated roles:

rules:
- ldapsearch:
    ...
  role:
    name: "{cn}"
    comment: "Generated from LDAP entry {dn}."

The following example generate a unique comment for each roles generated:

rules:
- ldapsearch:
    ...
  role:
    name: "{member.cn}"
    comment: "Generated from LDAP entry {member}."

Tip

If a role is defined multiple times, parents are merged. Other fields are kept as declared by the first definition of the role.

H.1.5.5.3.2. name #

Name of the role wanted in the cluster. The value can be either a single string or a list of strings. Plural form names is valid. You can inject LDAP attributes in name using curly braces. When multiple names are defined, a new role is defined for each name, each with the same attributes such as options and parents. comment parameter has a special handling, see above.

rules:
- roles:
    name: "my-role-name"

When injecting LDAP attribute in name, each value of the LDAP attribute of each LDAP entry will define a new role. When multiple LDAP attributes are defined in the format, all combination of attributes are generated.

ldap2pg protects role name with double quotes in the target Postgres cluster. Capitalization is preserved, spaces are allowed (even if it’s a really bad idea).

ldap2pg applies roles_blacklist_query on this parameter.

H.1.5.5.3.3. options #

Defines PostgreSQL role options. Maybe an SQL-like string or a YAML dictionary. Valid options are BYPASSRLS, CONNECTION LIMIT, LOGIN, CREATEDB, CREATEROLE, INHERIT, REPLICATION and SUPERUSER. Available options varies following the version of the target PostgreSQL cluster and the privilege of ldap2pg user.

- roles:
  - name: my-dba
    options: LOGIN SUPERUSER
  - name: my-group
    options:
      LOGIN: no
      INHERIT: yes
H.1.5.5.3.4. config #

Defines PostgreSQL configuration parameters that will be set for the role. Must be a YAML dictionary. Available configuration parameters varies following the version of the target PostgreSQL cluster. Some parameters requires superuser privileges to be set. ldap2pg will fails if it does not have privilege to set a config parameter.

- roles:
  - name: my-db-writer
    config:
      log_statement: mod
      log_min_duration_sample: 100

Setting config to null (the default) will disable the feature for the role. If config is a dict, ldap2pg will drop parameter set in cluster but not defined in ldap2pg YAML. To reset all parameters, set config to an empty dict like below.

- roles:
  - name: reset-my-configuration
    config: {}

Note that LDAP attributes are not expanded in config values.

H.1.5.5.3.5. parent #

Name of a parent role. A list of names is accepted. The plural form parents is valid too. Parent role is granted with GRANT ROLE parent TO role;. parent parameter accepts LDAP attributes injection using curly braces. ldap2pg applies roles_blacklist_query on this parameter. Reference parent can be local roles not managed by ldap2pg.

rules:
- role:
    name: myrole
    parent: myparent
H.1.5.5.3.6. before_create #

SQL snippet to execute before role creation. before_create accepts LDAP attributes injection using curly braces. You are responsible to escape attribute with either .identifier() or .string().

rules:
- ldapsearch: ...
  role:
    name: "{cn}"
    before_create: "INSERT INTO log VALUES ({cn.string()})"
H.1.5.5.3.7. after_create #

SQL snippet to execute after role creation. after_create accepts LDAP attributes injection using curly braces. You are responsible to escape attribute with either .identifier() or .string().

rules:
- ldapsearch: ...
  role:
    name: "{sAMAccountName}"
    after_create: "CREATE SCHEMA {sAMAccountName.identifier()} AUTHORIZATION {sAMAccountName.identifier()}"
H.1.5.5.4. grant #

Defines a grant of a privilege to a role with corresponding parameters. Can be a mapping or a list of mapping. Plural form grants is valid too.

rules:
- grant:
    privilege: reader
    databases: __all__
    schema: public
    role: myrole
H.1.5.5.4.1. database #

Scope the grant to one or more databases. May be a list of names. Plural form databases is valid. Special value __all__ expands to all managed databases as returned by databases_query. Defaults to __all__. Grants found in other databases will be revoked. Accepts LDAP attributes injection using curly braces.

This parameter is ignored for instance-wide privileges (e.g. on LANGUAGE).

H.1.5.5.4.2. privilege #

Name of a privilege, within the privileges defined in privileges YAML section. May be a list of names. Plural form privileges is valid. Required, there is not default value. Accepts LDAP attribute injection using curly braces.

H.1.5.5.4.3. role #

Name of the target role of the grant (granted role or grantee). Must be listed by managed_roles_query. May be a list of names. Plural form roles is valid. Accepts LDAP attribute injection using curly braces. ldap2pg applies roles_blacklist_query on this parameter.

H.1.5.5.4.4. schema #

Name of a schema, whithin the schemas returned by schemas_query. Special value __all__ means all managed schemas in the databases. May be a list of names. Plural form schemas is valid. Accepts LDAP attribute injection using curly braces.

This parameter is ignored for privileges on DATABASE and other instance-wide or database-wide privileges.

H.1.5.5.4.5. owner #

Name of role to configure default privileges for. Special value __auto__ fallbacks to managed roles having CREATE privilege on the target schema. May be a list of names. Plural form owners is valid. Accepts LDAP attribute injection using curly braces.

H.1.6. Inspecting Postgres cluster #

ldap2pg follows the explicit create / implicit drop and explicit grant / implicit revoke pattern. Thus properly inspecting cluster for what you want to drop/revoke is very crucial to succeed in synchronization.

ldap2pg inspects databases, schemas, roles, owners and grants with SQL queries. You can customize all these queries in the postgres YAML section with parameters ending with _query. See ldap2pg.yaml reference for details.

H.1.6.1. What databases to synchronize ? #

databases_query returns the flat list of databases to manage. The databases_query must return the default database as defined in PGDATABASE. When dropping roles, ldap2pg loops the databases list to reassign objects and clean GRANTs of to be dropped role. This databases list also narrows the scope of GRANTs inspection. ldap2pg will revoke GRANTs only on these databases. See ldap2pg.yaml reference for details.

postgres:
  databases_query: |
    SELECT datname
    FROM pg_catalog.pg_database
    WHERE datallowconn IS TRUE;

H.1.6.2. Synchronize a subset of roles #

By default, ldap2pg manages all roles from Postgres it has powers on, minus the default blacklist. If you want ldap2pg to synchronsize only a subset of roles, you need to customize inspection query in postgres:managed_roles_query. The following query excludes superusers from synchronization.

postgres:
  managed_roles_query: |
    SELECT 'public'
    UNION
    SELECT rolname
    FROM pg_catalog.pg_roles
    WHERE rolsuper IS FALSE
    ORDER BY 1;

ldap2pg will only drop, revoke, grant on roles returned by this query.

A common case for this query is to return only members of a group like ldap_roles. This way, ldap2pg is scoped to a subset of roles in the cluster.

The public role does not exists in the system catalog. Thus if you want ldap2pg to manage public privileges, you must include explicitly public in the set of managed roles. This is the default. Of course, even if public is managed, ldap2pg won’t drop or alter it if it’s not in the directory.

A safety net to completely ignore some roles is roles_blacklist_query.

postgres:
  roles_blacklist_query: [postgres, pg_*]  # This is the default.

Note

A pattern starting with a * must be quoted. Else you'll end up with a YAML error like found undefined alias.

H.1.6.3. Inspecting Schemas #

For schema-wide privileges, ldap2pg needs to known managed schemas for each database. This is the purpose of schemas_query.

H.1.6.4. Configuring owners default privileges #

To configure default privileges, use the default keyword when referencing a privilege:

privileges:
  reading:
  - default: global
    type: SELECT
    on: TABLES

Then grant it using grant rule:

rules:
- grant:
  - privilege: reading
    role: readers
    schema: public
    owner: ownerrole

You can use __auto__ as owner. For each schema, ldap2pg will configure every managed role having CREATE privilege on schema.

rules:
- grant:
  - privilege: reading
    role: readers
    schema: public
    owner __auto__

ldap2pg configures default privileges last, after all effective privileges. Thus CREATE on schema is granted before ldap2pg inspects creators on schemas.

H.1.6.5. Static Queries #

You can replace all queries with a static list in YAML. This list will be used as if returned by Postgres. That’s very handy to freeze a value like databases or schemas.

postgres:
  databases_query: [postgres]
  schemas_query: [public]

H.1.7. Managing roles #

ldap2pg synchronizes Postgres roles in three steps:

  1. Loop rules and generate wanted roles list from role rules.

  2. Inspect Postgres for existing roles, their options and their membership.

  3. Compare the two roles sets and apply to the Postgres cluster using CREATE, DROP and ALTER.

Each role entry in rules is a rule to generate zero or more roles with the corresponding parameters. A role rule is like a template. role rules allows to deduplicate membership and options by setting a list of names.

You can mix static rules and dynamic rules in the same file.

H.1.7.1. Running unprivileged #

ldap2pg is designed to run unprivileged. Synchronization user needs CREATEROLE option to manage other unprivileged roles. CREATEDB options allows synchronization user to managed database owners.

ldap2pg user must have createrole_self_grant set to inherit,set to properly handle groups.

CREATE ROLE ldap2pg LOGIN CREATEDB CREATEROLE;
ALTER ROLE ldap2pg SET createrole_self_grant TO 'inherit,set;

Running unprivileged before Postgres 16 is actually flawed. You’d better just run ldap2pg with superuser privileges, you wont feel falsly secured.

H.1.7.2. Ignoring roles #

ldap2pg totally ignores roles matching one of the glob pattern defined in roles_blacklist_query:

postgres:
  # This is the default value.
  roles_blacklist_query: [postgres, pg_*]

The role blacklist is also applied to grants. ldap2pg will never apply GRANT or REVOKE on a role matching one of the blacklist patterns.

ldap2pg blacklists its running user.

H.1.7.3. Membership #

ldap2pg manages parents of roles. ldap2pg applies roles_blacklist_query to parents. However, ldap2pg grants unmanaged parents. This way, you can create a group manually and manages its members using ldap2pg.

H.1.8. Querying Directory with LDAP #

ldap2pg reads LDAP searches in rules steps in the ldapsearch entry.

A LDAP search is not mandatory. ldap2pg can create roles defined statically from YAML. Each LDAP search is executed once and only once. There is neither loop nor deduplication of LDAP searches.

Tip

ldap2pg logs LDAP searches as ldapsearch commands. Enable verbose messages to see them. You can debug a failing search by copy-pasting the command in your shell and update parameters. Once you are okay, translate back the right parameters in the YAML.

H.1.8.1. Configuring Directory Access #

ldap2pg reads directory configuration from ldaprc file and LDAP* environment variables. Known LDAP options are:

  • BASE

  • BINDDN

  • PASSWORD

  • REFERRALS

  • SASL_AUTHCID

  • SASL_AUTHZID

  • SASL_MECH

  • TIMEOUT

  • TLS_REQCERT

  • NETWORK_TIMEOUT

  • URI

See ldap.conf(5) for the meaning and format of each options.

H.1.8.2. Injecting LDAP attributes #

Several parameters accepts LDAP attribute injection using curly braces. To do this, wraps attribute name with curly braces like {cn} or {sAMAccountName}. ldap2pg expands to each value of the attribute for each entries of the search.

If the parameter has multiple LDAP attributes, ldap2pg expands to all combination of attributes for each entries.

Given the following LDAP entries:

dn: uid=dimitri,cn=Users,dc=bridoulou,dc=fr
objectClass: inetOrgPerson
uid: dimitri
sn: Dimitri
cn: dimitri
mail: dimitri@bridoulou.fr
company: external

dn: cn=domitille,cn=Users,dc=bridoulou,dc=fr
objectClass: inetOrgPerson
objectClass: organizationalPerson
objectClass: person
objectClass: top
cn: domitille
sn: Domitille
company: acme
company: external

The format {company}_{cn} with the above LDAP entries generates the following strings:

  • acme_domitille

  • external_domitille

  • external_dimitri

The pseudo attribute dn is always available and references the Distinguished Name of the original LDAP entry.

H.1.8.2.2. LDAP Attribute Case #

When injecting an LDAP attribute with curly braces, you can control the case of the value using .lower() or .upper() methods.

- ldapsearch: ...
  role: "{cn.lower()}"

H.1.9. Managing Privileges #

Managing privileges is tricky. ldap2pg tries to make this simpler and safer.

H.1.9.1. Basics #

The base design of ldap2pg is ambitious. Instead of revoke-everything-regrant design, ldap2pg uses inspect-modify design. The process is the same as for roles synchronization, including the three following steps:

  1. Loop rules and generate wanted grants set.

  2. Inspect Postgres cluster for granted privileges.

  3. Compare the two sets of grants and update the Postgres cluster using GRANT, REVOKE.

ldap2pg synchronizes privileges one at a time, database by database. ldap2pg synchronizes default privileges last.

By default, ldap2pg does not manage any privileges. To enable privilege management, you must define at least one active privilege profile in privileges section. The simplest way is to reuse [builtin privilege profiless] shipped with ldap2pg in an active group of privileges.

H.1.9.2. Defining a Privilege Profile #

A privilege profile is a list of references to either a privilege type on an ACL or another profile. ldap2pg ships several predefined ACL like DATABASE, LANGUAGE, etc. A privilege type is USAGE, CONNECT and so on as describe in as documented in documentation Section 5.7. See privileges YAML section documentation for details on privilege profile format.

ldap2pg loads referenced ACL by inspecting PostgreSQL cluster with carefully crafted queries. An unreferenced ACL is ignored. Inspected grants are supposed to revokation unless explicitly wanted by a grant rule.

Warning

If it’s not granted, revoke it!

Once a privilege is enabled, ldap2pg revokes all grants found in Postgres instance and not required by a grant rule in rules.

H.1.9.3. Extended Intance inspection #

When managing privileges, ldap2pg has deeper inspection of Postgres instance. ldap2pg inspects schemas after roles synchronization and before synchronizing privileges. ldap2pg inspects objects owner after privileges synchronization and before synchronizing default privileges. An object owner is a role having CREATE privilege on a schema.

H.1.9.4. Granting Privilege Profile #

Inspecting privileges may consume a lot of resources on PostgreSQL instance. Revoking privileges is known to be slow in PostgreSQL. The best practice is to grant privileges to a group role and let user inherit privileges. With ldap2pg, you can define static groups in YAML and inherit them when creating roles from directory.

Use grant rule to grant a privilege profile to one or more roles. When granting privileges, you must define the grantee. You may scope the grant to one or more databases, one or more schemas. If the privilege profile includes default privileges, you may define the owners on which to configure default privileges.

By default, a grant applies to all managed databases as returned by databases_query, to all schema of each database as returned by schemas_query.

H.1.9.5. Example #

The following example defines three privileges profile. The rules defines three groups and grant the corresponding privilege profile:

privileges:
  reading:
  - __connect__
  - __usage_on_schemas__
  - __select_on_tables__

  writing:
  - reading  # include reading privileges
  - __insert_on_tables__
  - __update_on_tables__

  owning:
  - writing
  - __create_on_schemas__
  - __truncate_on_tables__

rules:
- role:
  - names:
    - readers
    - writers
    - owners
    options: NOLOGIN
- grant:
  - privilege: reading
    role: readers
  - privilege: writing
    role: writers
  - privilege: owning
    role: owners

Another way of including reading profile in writing is to writers group to inherit readers group.

H.1.9.6. Managing public Privileges #

PostgreSQL has a pseudo-role called public. It’s a wildcard roles meaning every users. All roles in PostgreSQL implicitly inherits from this public role. Granting a privilege to public role grants to every role now and in the future.

PostgreSQL also as the public schema. The public schema is a real schema available in all databases.

PostgreSQL has some built-in privileges for public role. Especially for the public schema. For example, public has CONNECT on all databases by default. This means that you only rely on pg_hba.conf to configure access to databases, which requires administrative access to the cluster and a pg_reload_conf() call.

By default, ldap2pg includes public role in managed roles. Predefined ACL knows how to inspect built-in privileges granted to public. If you want to preserve public role, rewrite managed_roles_query to not include public.

H.1.9.7. Managing Default Privileges #

If you grant SELECT privileges on all tables in a schema to a role, this wont apply to new tables created afterward. Instead of reexecuting ldap2pg after the creation of every objects, PostgreSQL provides a way to define default privileges for future objects.

PostgreSQL attaches default privileges to the creator role. When the role creates an object, PostgreSQL apply the corresponding default privileges to the new object. e.g. ALTER DEFAULT PRIVILEGES FOR ROLE bob GRANT SELECT ON TABLES TO alice; ensures every new tables bob creates will be selectable by alice:

If ldap2pg creates and drops creator roles, you want ldap2pg to properly configure default privileges on these roles. If you wonder whether to manage privileges with ldap2pg, you should at least manage default privileges along creator.

ldap2pg inspects the creators from PostgreSQL, per schemas, not LDAP directory. A creator is a role with LOGIN option and CREATE privilege on a schema. You can manually set the target owner of a grant to any managed roles.

ldap2pg does not configure privileges on __all__ schemas. You are supposed to use global scope instead. If you want to grant/revoke default privilege per schema, you must reference schema default.

The following example configures default privileges for alice to allow bob to SELECT on future tables created by alice.

privileges:
  reading:
  - default: global
    type: SELECT
    on: TABLES
  owning:
  - type: CREATE
    on: SCHEMAS

rules:
- roles:
    names:
    - alice
    - bob
    options: LOGIN
- grant:
    privilege: owning
    role: alice
- grant:
    privilege: reading
    role: bob

PostgreSQL has hard-wire global default privileges. If a role does not have global default privileges configured, PostgreSQL assume some defaults. By default, PostgreSQL just grant privileges on owner. You can see them once you modify the default privileges. PostgreSQL will copy the hard-wired values along your granted privileges.

If you don’t explicitly re-grant these privileges in ldap2pg.yml, ldap2pg will revoke these hard-wired privileges. Actually, an owner of table don’t need to be granted SELECT on its own tables. Thus, the hard-wired defaults are useless. You can let ldap2pg purge these useless defaults.

H.1.10. Builtins Privileges #

ldap2pg provides some builtin ACL and predefined privilege profiles for recurrent usage. There is no warranty on these privileges. You have to check privileges configuration on your databases just like you should do with your own code.

H.1.10.1. Using Predefined Privilege Profiles #

A privilege profile is a list of reference to a privilege type in an ACL. In ldap2pg, an ACL is a set of query to inspect, grant and revoke privilege on a class of objects. The inspect query expands aclitem PostgreSQL type to list all grants from system catalog. Privilege profile can include another profile.

Builtin privilege profile starts and ends with __. ldap2pg disables privilege profile starting with _. Thus you have to include builtin privileges profile in another profile to enable them. If two profiles reference the same privilege, ldap2pg will inspect it once.

privileges:
  ro:
  - __connect__
  - __usage_on_schemas__
  - __select_on_tables__

  rw:
  - ro
  - __insert__
  - __update_on_tables__

  ddl:
  - rw
  - __all_on_schemas__
  - __all_on_tables__

rules:
- grant:
    privilege: ddl
    database: mydb
    role: admins

Builtin profile’s name follows the following loose convention:

  • ..._on_all_tables__ references ALL TABLES IN SCHEMA ACL. Likewise for sequences and functions.

  • __default_...__ references both global and schema-wide default privileges.

  • __..._on_tables__ groups __..._on_all_tables__ and __default_..._on_tables__.

  • Group starting with __all_on_...__ is equivalent to ALL PRIVILEGES in SQL. However, each privilege will be granted individually.

  • A privilege specific to one object type does not have _on_<type> suffix. E.g. __delete_on_tables__ is aliased to __delete__.

This page does not document the SQL standard and the meaning of each SQL privileges. You will find the documentation of SQL privileges in GRANT and ALTER DEFAULT PRIVILEGES.

H.1.10.2. ACL Reference #

Here is the list of builtin ACL.

For effective privileges:

  • DATABASE: privilege on database like CONNECT, CREATE, etc.

  • SCHEMA: manage USAGE and CREATE on schema.

  • LANGUAGE: manage USAGE on procedural languages.

  • ALL FUNCTIONS IN SCHEMA: manage EXECUTE on all functions per schema.

  • ALL SEQUENCES IN SCHEMA: like above but for sequences.

  • ALL TABLES IN SCHEMA: like above but for tables and views.

ALL ... IN SCHEMA ACL inspects whether a privilege is granted to only a subset of objects. This is a partial grant. A partial grant is either revoked if unwanted or regranted if expected.

ACL for default privileges:

  • SEQUENCES

  • FUNCTIONS

  • TABLES

Theses ACL must be referenced with global set to either schema or global.

You can reference these ACL using privileges:on parameter in YAML. Like this:

privileges:
  myprofile:
  - type: SELECT
    on: ALL TABLES IN SCHEMA

You cannot (yet) configure custom ACL.

H.1.10.3. Profiles Reference #

H.1.10.3.1. Profile __all_on_functions__ #
H.1.10.3.2. Profile __all_on_schemas__ #
H.1.10.3.3. Profile __all_on_sequences__ #
H.1.10.3.5. Profile __delete_on_tables__ #
H.1.10.3.6. Profile __execute_on_functions__ #
H.1.10.3.7. Profile __insert_on_tables__ #
H.1.10.3.8. Profile __references_on_tables__ #
H.1.10.3.9. Profile __select_on_sequences__ #
H.1.10.3.10. Profile __select_on_tables__ #
H.1.10.3.11. Profile __trigger_on_tables__ #
H.1.10.3.12. Profile __truncate_on_tables__ #
H.1.10.3.13. Profile __update_on_sequences__ #
H.1.10.3.14. Profile __update_on_tables__ #
H.1.10.3.15. Profile __usage_on_sequences__ #

H.1.10.4. Privileges Reference #

Here is the list of predefined privileges:

Name Manages
__connect__ CONNECT ON DATABASE
__create_on_schemas__ CREATE ON SCHEMA
__delete_on_all_tables__ DELETE ON ALL TABLES IN SCHEMA
__execute_on_all_functions__ EXECUTE ON ALL FUNCTIONS IN SCHEMA
__insert_on_all_tables__ INSERT ON ALL TABLES IN SCHEMA
__references_on_all_tables__ REFERENCES ON ALL TABLES IN SCHEMA
__select_on_all_sequences__ SELECT ON ALL SEQUENCES IN SCHEMA
__select_on_all_tables__ SELECT ON ALL TABLES IN SCHEMA
__temporary__ TEMPORARY ON DATABASE
__trigger_on_all_tables__ TRIGGER ON ALL TABLES IN SCHEMA
__truncate_on_all_tables__ TRUNCATE ON ALL TABLES IN SCHEMA
__update_on_all_sequences__ UPDATE ON ALL SEQUENCES IN SCHEMA
__update_on_all_tables__ UPDATE ON ALL TABLES IN SCHEMA
__usage_on_all_sequences__ USAGE ON ALL SEQUENCES IN SCHEMA
__usage_on_schemas__ USAGE ON SCHEMA

H.1.10.5. Default Privileges Reference #

Here is the list of predefined default privileges. Default privilege profile references both global and schema defaults.

Name Manages
__default_delete_on_tables__ DELETE ON TABLES
__default_execute_on_functions__ EXECUTE ON FUNCTIONS
__default_insert_on_tables__ INSERT ON TABLES
__default_references_on_tables__ REFERENCES ON TABLES
__default_select_on_sequences__ SELECT ON SEQUENCES
__default_select_on_tables__ SELECT ON TABLES
__default_trigger_on_tables__ TRIGGER ON TABLES
__default_truncate_on_tables__ TRUNCATE ON TABLES
__default_update_on_sequences__ UPDATE ON SEQUENCES
__default_update_on_tables__ UPDATE ON TABLES
__default_usage_on_sequences__ USAGE ON SEQUENCES

H.1.11. Cookbook #

Here in this cookbook, you’ll find some recipes for various use case of ldap2pg.

If you struggle to find a way to setup ldap2pg for your needs, please file an issue so that we can update Cookbook with new recipes! Your contribution is welcome!

H.1.11.1. Configure pg_hba.conf with LDAP #

ldap2pg does NOT configure PostgreSQL for you. You should carefully read Section 20.10 for this point. Having PostgreSQL properly configured before writing ldap2pg.yaml is a good start. Here is the steps to setup PostgreSQL with LDAP in the best order:

  • Write the LDAP search and test it with ldapsearch(1). This way, you can also check how you connect to your LDAP directory.

  • In PostgreSQL cluster, manually create a single role having its password in LDAP directory.

  • Edit pg_hba.conf following Section 20.10 until you can effectively login with the single role and the password from LDAP.

Once you have LDAP authentication configured in PostgreSQL cluster, you can move to automate role creation from the LDAP directory using ldap2pg:

  • Write a simple ldap2pg.yaml with only one LDAP search just to setup ldap2pg connection parameters for PostgreSQL and LDAP connection. ldap2pg always run in dry mode by default, so you can safely loop ldap2pg execution until you get it right.

  • Then, complete ldap2pg.yaml to fit your needs following Section H.1.4. Run ldap2pg for real and check that ldap2pg maintain your single test role, and that you can still connect to the cluster with it.

  • Finally, you must decide when and how you want to trigger synchronization: a regular cron tab ? An ansible task ? Manually ? Other ? Ensure ldap2pg execution is frequent, on purpose and notified.

H.1.11.2. Search LDAP Directory #

The first step is to search your LDAP server with ldapsearch(1), the CLI tool from OpenLDAP. Like this:

$ ldapsearch -H ldaps://ldap.ldap2pg.docker -U testsasl -W
Enter LDAP Password:
SASL/DIGEST-MD5 authentication started
SASL username: testsasl
SASL SSF: 128
SASL data security layer installed.
# extended LDIF
#
# LDAPv3
...
# search result
search: 4
result: 0 Success

# numResponses: 16
# numEntries: 15
$

Now save the settings in ldaprc:

LDAPURI ldaps://ldap.ldap2pg.docker
LDAPSASL_AUTHCID testsasl

And in environment: LDAPPASSWORD=secret

Next, update your ldapsearch(1) to properly match role entries in LDAP server:

$ ldapsearch -H ldaps://ldap.ldap2pg.docker -U testsasl -W -b cn=dba,ou=groups,dc=ldap,dc=ldap2pg,dc=docker '' member
...
# dba, groups, ldap.ldap2pg.docker
dn: cn=dba,ou=groups,dc=ldap,dc=ldap2pg,dc=docker
member: cn=Alan,ou=people,dc=ldap,dc=ldap2pg,dc=docker
member: cn=albert,ou=people,dc=ldap,dc=ldap2pg,dc=docker
member: cn=ALICE,ou=people,dc=ldap,dc=ldap2pg,dc=docker

# search result
search: 4
result: 0 Success

...
$

Now translate the query in ldap2pg.yaml and associate a role mapping to produce roles from each values of each entries returned by the LDAP search:

- ldapsearch:
    base: cn=dba,ou=groups,dc=ldap,dc=ldap2pg,dc=docker
  role:
    name: '{member.cn}'
    options: LOGIN SUPERUSER

Test it:

$ ldap2pg
...
Querying LDAP cn=dba,ou=groups,dc=ldap,dc=ldap2pg,dc=docker...
Would create alan.
Would create albert.
Would update options of alice.
...
Comparison complete.
$

Read further on how to control role creation from LDAP entry in Section H.1.5. Once you’re satisfied with the comparison output, go real with --real.

H.1.11.3. Using LDAP High-Availability #

ldap2pg supports LDAP High Availability out of the box just like any openldap client. Use a space separated list of URI to tells all servers.

$ LDAPURI="ldaps://ldap1 ldaps://ldap2" ldap2pg

See [ldap.conf(5)] for further details.

H.1.11.4. Running as non-superuser #

Since Postgres provide a CREATEROLE role option, you can manage roles without superuser privileges. Security-wise, it’s a good idea to manage roles without super privileges.

Warning

Up to Postgres 15, having CREATEROLE is roughly equivalent to being superuser. This because CREATEROLE user can grant themselve almost every privileges. Thus ldap2pg supports running unprivileged against Postgres 16 and later only.

ldap2pg supports this case. However, you must be careful about the limitations. Let’s call the non-super role creating other roles creator.

  • You can’t manage some roles options like SUPERUSER, BYPASSRLS and REPLICATION. Thus you wont be able to detect spurious superusers.

  • Ensure creator can revoke all grants of managed users.

H.1.11.5. Removing All Roles #

If ever you want to clean all roles in a PostgreSQL cluster, ldap2pg could be helpful. You must explicitly define an empty rules.

$ echo '{version: 6, rules: []}' | ldap2pg --config -
...
Empty synchronization map. All roles will be dropped!
...

In this example, default blacklist applies. ldap2pg never drop its connection role.

H.1.11.6. ldap2pg as Docker container #

Already familiar with Docker and willing to save the setup time? You’re at the right place.

To run the container simply use the command:

$ docker run --rm dalibo/ldap2pg --help

The Docker image of ldap2pg use the same configuration options as explained in the Section H.1.4 and Section H.1.5 sections. You can mount the ldap2pg.yml configuration file.

$ docker run --rm -v ${PWD}/ldap2pg.yml:/workspace/ldap2pg.yml dalibo/ldap2pg

You can also export some environmnent variables with the -e option:

$ docker run --rm -v ${PWD}/ldap2pg.yml:/workspace/ldap2pg.yml -e PGDSN=postgres://postgres@localhost:5432/ -e LDAPURI=ldaps://localhost -e LDAPBINDDN=cn=you,dc=entreprise,dc=fr -e LDAPPASSWORD=pasglop dalibo/ldap2pg

Make sure your container can resolve the hostname your pointing to. If you use some internal name resolution be sure to add the –dns= option to your command pointing to your internal DNS server. More info

H.1.12. Hacking #

You are welcome to contribute to ldap2pg with patch to code, documentation or configuration sample ! Here is an extended documentation on how to setup a development environment. Feel free to adapt to your cumfort. Automatic tests on CircleCI will take care of validating regressions.

H.1.12.1. Docker Development Environment #

Project repository ships a docker-compose.yml file to launch an Samba Directory and a PostgreSQL instances.

$ docker compose pull
...
Status: Downloaded newer image for postgres:16-alpine
$ docker compose up -d
Creating network "ldap2pg_default" with the default driver
Creating ldap2pg_postgres_1 ...
Creating ldap2pg_samba_1 ...
Creating ldap2pg_postgres_1
Creating ldap2pg_samba_1 ... done

It’s up to you to define how to access Postgres and LDAP containers from your host: either use DNS resolution or a docker-compose.override.yml to expose port on your host. Provided docker-compose.yml comes with postgres.ldap2pg.docker and samba.ldap2pg.docker dnsdock aliases.

Setup your environment with regular PG* envvars so that psql can just connect to your PostgreSQL instance. Check with a simple psql invocation.

$ export PGHOST=postgres.ldap2pg.docker PGUSER=postgres PGPASSWORD=postgres
$ psql -c 'SELECT version()';

Do the same to setup libldap2 with LDAP* envvars. A ldaprc is provided setting up BINDDN and BASE. ldap2pg supports LDAPPASSWORD to set password from env. Check it with ldapsearch:

$ export LDAPURI=ldaps://samba.ldap2pg.docker LDAPPASSWORD=1Ntegral
$ ldapsearch -vxw $LDAPPASSWORD -s base cn
ldap_initialize( <DEFAULT> )
filter: (objectclass=*)
requesting: cn
# extended LDIF
#
# LDAPv3
# base <cn=users,dc=bridoulou,dc=fr> (default) with scope baseObject
# filter: (objectclass=*)
# requesting: cn
#

# Users, bridoulou.fr
dn: CN=Users,DC=bridoulou,DC=fr
cn: Users

# search result
search: 2
result: 0 Success

# numResponses: 2
# numEntries: 1
$
H.1.12.1.1. Environement without DNS resolution #

To access Samba Directory and PostgreSQL without dnsdock, exposes containers ports to your host with the following override:

# contents docker-compose.override.yml
version: '3'

services:
  samba:
    ports:
      # HOST:CONTAINER
      - 389:389
      - 636:636

  postgres:
    ports:
      - 5432:5432

Use PGHOST=localhost and LDAPURI=ldaps://localhost.

H.1.12.1.2. Running ldap2pg with Changes #

Now you can run ldap2pg from source and test your changes!

$ go run ./cmd/ldap2pg
09:54:27 INFO   Starting ldap2pg                                 version=v6.0-alpha5 runtime=go1.20.3 commit=<none>
09:54:27 WARN   Running a prerelease! Use at your own risks!
09:54:27 INFO   Using YAML configuration file.                   path=./ldap2pg.yml
...
09:54:27 INFO   Nothing to do.                                   elapsed=78.470278ms mempeak=1.2MiB postgres=0s queries=0 ldap=486.921µs searches=1
$

H.1.12.2. Development Fixtures #

ldap2pg project comes with three cases for testing:

  • nominal: a regular case with:

    • running unprivileged

    • a single database named nominal.

    • 3 groupes : readers, writers and owners

    • roles and privileges synchronized.

  • extra: few corner cases together

    • running as superuser

    • synchronize role configuration

    • do LDAP sub-searches.

  • big: a huge synchronization project

    • multiple databases with a LOT of schemas, tables, views, etc.

    • all privileges synchronized

    • 3 groups per schemas.

    • 1K users in directory.

test/fixtures/ holds fixtures for Samba and PostgreSQL. Default development environment loads nominal and extra fixtures. By default, big case is not loaded. Func tests use nominal and extra fixtures. See below for big case.

test/fixtures/postgres/reset.sh resets PostgreSQL state. You can also use make reset-postgres to recreate PostgreSQL container from scratch.

H.1.12.3. Unit tests #

Unit tests strictly have no I/O. Run unit tests as usual go tests.

$ go test ./...
?       github.com/dalibo/ldap2pg/cmd/ldap2pg   [no test files]
?       github.com/dalibo/ldap2pg/cmd/render-doc        [no test files]
ok      github.com/dalibo/ldap2pg/cmd/mon-dojo  0.002s
ok      github.com/dalibo/ldap2pg/internal      0.003s
ok      github.com/dalibo/ldap2pg/internal/config       0.007s
ok      github.com/dalibo/ldap2pg/internal/inspect      0.005s
ok      github.com/dalibo/ldap2pg/internal/ldap 0.005s
ok      github.com/dalibo/ldap2pg/internal/lists        0.005s
?       github.com/dalibo/ldap2pg/internal/postgres     [no test files]
ok      github.com/dalibo/ldap2pg/internal/perf 0.004s
ok      github.com/dalibo/ldap2pg/internal/privilege    0.003s
?       github.com/dalibo/ldap2pg/internal/role [no test files]
ok      github.com/dalibo/ldap2pg/internal/pyfmt        0.004s
ok      github.com/dalibo/ldap2pg/internal/tree 0.002s
ok      github.com/dalibo/ldap2pg/internal/wanted       0.003s
$

H.1.12.4. Functionnal tests #

test/ directory is a [pytest] project with functionnal tests. Functionnal tests tend to validate ldap2pg in real world : no mocks.

Func tests requires Python 3.6. Create a virtualenv to isolate ldap2pg dev Python dependencies. Install dev dependencies with pip install -Ur test/requirements.txt.

$ pip install -Ur test/requirements.txt
...
Successfully installed iniconfig-2.0.0 packaging-23.1 pluggy-1.3.0 pytest-7.4.2 sh-1.14.1
$

You can run func tests right from you development environment:

$ pip install -Ur test/requirements.txt
...
$ pytest test/
...
ldap2pg: /home/bersace/src/dalibo/ldap2pg/test/ldap2pg.sh
...
test/test_nominal.py::test_re_revoke PASSED                                  [ 90%]
test/test_nominal.py::test_nothing_to_do PASSED                              [100%]

=============================== 11 passed in 14.90s ================================
$

CI executes func tests in CentOS 6 and 7 and RockyLinux 8 and 9.

Tests are written with the great pytest and sh projects. conftest.py provides various specific fixtures. The most important is that Postgres database is reset between each Python module. pytests executes Func tests in definition order. If a test modifies Postgres, the following tests will have this modification kept until the end of the module. This allows to split a big scenario in severals steps without loosing context and CPU cycle.

Two main pytest fixtures are very useful when testing: psql and ldap. These little helpers provide fastpath to frequent inspection of Postgres database on LDAP base with sh.py-style API.

You can execute tests just like in CI with the following commands:

$ goreleaser build --clean --snapshot --single-target
$ COMPOSE_FILE=docker-compose.yml:test/docker-compose.yml docker compose up --exit-code-from=test

H.1.12.5. Big Case #

To stress ldap2pg on big setup, use make big. This will feed directory with a lot of users and groups, several databases with a lot of schemas, etc. Synchronize this setup with:

$ test/genperfconfig.sh | PGDATABASE=big0 go run ./cmd/ldap2pg -c -

H.1.12.6. Documenting #

Building documentation requires Python 3.7. mkdocs is in charge of building the documentation. To edit the doc, install docs/requirements.txt and run mkdocs serve at the toplevel directory. See mkdocs documentation for further information.

$ pip install -r docs/requirements.txt
...
Successfully installed babel-2.12.1 certifi-2023.7.22 charset-normalizer-3.2.0 click-8.1.7 colorama-0.4.6 ghp-import-2.1.0 idna-3.4 jinja2-3.1.2 markdown-3.5 m...

H.1.12.7. Releasing #

  • Update default.pgo

  • Review docs/changelog.md. First title must be # ldap2pg X.Y

  • Generate release commit, tag and changelog with make release. make reads next version from changelog.

  • Once CircleCI has created GitHub release artifacts, publish packages with make publish-packages.

  • Once Docker Hub has published new tag, tag latest image on docker hub with make tag-latest.

H.1.13. Support #

If you need support and you didn’t found it in documentation, just drop a question in a GitHub issue! French accepted. Don’t miss the cookbook for advanced use cases.

H.1.14. Authors #

ldap2pg is a Dalibo Labs project.