ldap2pg

ldap2pg — automate the creation, update, and removal of Postgres Pro roles and users from an enterprise directory

Synopsis

ldap2pg [option...] [dbname]

Description #

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

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

ldap2pg is provided with Postgres Pro Standard as a separate pre-built package ldap2pg (for the detailed installation instructions, see Chapter 16).

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

ldap2pg requires a configuration file called ldap2pg.yaml.

Features #

  • Reads settings from an expressive YAML config file.

  • Creates, alters and drops Postgres Pro 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.

Requirements #

ldap2pg is released as a single binary with no dependencies.

At run time, 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 Postgres Pro Standard cluster.

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

Command-Line Reference #

ldap2pg tries to be friendly regarding configuration and consistent with psql, OpenLDAP utils and The Twelve-Factor App. 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 command-line usage is as follows:

ldap2pg [OPTIONS] [dbname]

Where options are as follows:

--check

Check mode: exits with 1 if Postgres Pro instance is unsynchronized.

--color

Force color output.

-c string
--config string

Path to YAML configuration file. Use - for stdin.

--directory string

Path to directory containing configuration files.

--help

Show help message and exit (default true).

--ldappassword-file string

Path to LDAP password file.

--quiet count

Decrease log verbosity.

--real

Real mode. Apply changes to Postgres Pro instance.

-skip-privileges

Turn off privilege synchronization.

--verbose count

Increase log verbosity.

--version

Show version and exit (default true).

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

By default, ldap2pg runs in dry mode. ldap2pg requires a configuration file to describe LDAP searches and mappings.

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

Environment Variables #

ldap2pg has no command-line interface switch to configure Postgres Pro connection. However, ldap2pg supports libpq PG* environment variables.

See psql(1) for details on libpq environment variables.

The same goes for LDAP, ldap2pg supports standard LDAP* environment variables and .ldaprc files. See ldap.conf(5) man page for further details on how to configure. ldap2pg accepts two extra variables: LDAPPASSWORD and LDAPPASSWORD_FILE.

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 Pro connection using psql(1) and LDAP using ldapwhoami(1) man pages, ldap2pg will be okay and it will be easier to debug the setup and the configuration later.

Logging Setup #

ldap2pg has 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 Pro> cluster.

  • 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 command-line switches respectively decrease and increase verbosity.

You can select the highest level of verbosity with LDAP2PG_VERBOSITY environment variable. 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 is 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 command-line interface switch.

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 Pro inspect queries, LDAP searches, privileges and synchronization map.

Warning

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

Here are the contents of a tested ldap2pg.yml, which can be used as a starting point:

#
#
#       L D A P 2 P G   S A M P L E   C O N F I G U R A T I O N
#
#
# This is a starting point configuration file for ldap2pg.yml. Including static
# roles, groups, privilege and LDAP search.
#
# This configuration assumes the following principles:
#
# - All LDAP users are grouped in `ldap_roles` group.
# - Read privileges are granted to `readers` group.
# - Write privileges are granted to `writers` group.
# - DDL privileges are granted to `owners` group.
# - We have one or more databases with public and maybe a schema.
# - Grants are not specific to a schema. Once you're writer in a database, you
#   are writer to all schemas in it.
#
# The LDAP directory content is described in test/fixtures/openldap-data.ldif
#
# Adapt to your needs!
#
#
# File format version. Allows ldap2pg to check whether the file is supported.
#
version: 6

#
#       1.   P O S T G R E S   I N S P E C T I O N
#
#  See the section called “Inspecting Postgres Pro Cluster”.
#
postgres:
  roles_blacklist_query: [nominal, postgres, pg_*]
  databases_query: [nominal]

#
#       2.   P R I V I L E G E S   D E F I N I T I O N
#
# See the section called “Managing Privileges”. Privileges wrapped
# in double underscores are built-in privilege profiles. See
# the section called “Built-in Privileges” for a documentation of
# each of them.
#

privileges:
  # Define `ro` privilege group with read-only grants
  ro:
  - __connect__
  - __select_on_tables__
  - __select_on_sequences__
  - __usage_on_schemas__
  - __usage_on_types__

  # `rw` privilege group lists write-only grants
  rw:
  - __temporary__
  - __all_on_tables__
  - __all_on_sequences__

  # `ddl` privilege group lists DDL only grants.
  ddl:
  - __create_on_schemas__


#
#       3.   S Y N C H R O N I S A T I O N   M A P
#
# This list contains rules to declare roles and grants. Each role or grant rule
# can be templated with attributes from LDAP entries returned by a search
# query.
#
# Any role found in cluster and not generated by rules will be dropped. Any
# grant found in cluster and not generated by rules will be revoked.
#

rules:
- description: "Setup static roles and grants."
  roles:
  - names:
    - readers
    options: NOLOGIN
  - name: writers
    # Grant reading to writers
    parent: readers
    options: NOLOGIN
  - name: owners
    # Grant read/write to owners
    parent: writers
    options: NOLOGIN

  grant:
  - privilege: ro
    role: readers
    # Scope to a single schema
    schemas: nominal
  - privilege: rw
    role: writers
  - privilege: ddl
    role: owners

- description: "Search LDAP to create readers, writers and owners."
  ldapsearch:
    base: cn=users,dc=bridoulou,dc=fr
    filter: "
    (|
      (cn=owners)
      (cn=readers)
      (cn=writers)
    )
    "
  role:
    name: '{member.cn}'
    options: LOGIN
    parent: "{cn}"

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.

File Structure #

ldap2pg.yml is split in several sections:

  • postgres: setup Postgres Pro> connection and inspection queries.

  • ldap: configuration for LDAP client.

  • privileges: the definition of privilege profiles.

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

If you don't know how to begin, a simple tested and well commented ldap2pg.yml is a good starting point.

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. See this YAML cheatsheet for some example.

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

rules:
  - role: {cn}  # It's an invalid YAML dict.
  - role: "{cn}"  # It's a string with LDAP attribute injection

postgres Section #

The postgres section defines custom SQL queries for Postgres Pro 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 Postgres Pro cluster.

databases_query #

The SQL query to list databases names in the cluster. By default, ldap2pg searches databases it can 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 skips querying the cluster for inspection and forces ldap2pg to use a static value.

fallback_owner #

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

Before dropping a role, ldap2pg reassigns objects and purges the access control list (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.

managed_roles_query #

The SQL query to list the name of managed roles.

ldap2pg restricts role deletion and privilege edition to managed roles. Usually, 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 built-in role in Postgres Pro. 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;
roles_blacklist_query #

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

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

Warning

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

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

ldap Section #

The ldap section customizes LDAP client behaviour. Configure connection using ldap.conf and LDAP* environment variables.

known_rdns #

List of attributes known to be part of the distinguished name (DN).

ldap2pg skips sub-search for attributes in this list. e.g., {member.cn} won't trigger a sub-search on all members if cn is in known_rdns. Default value is [n, l, st, o, ou, c, street, dc, uid]. Add a value to fasten synchronization. Remove a value if an attribute is not part of the DN.

ldap:
  known_rdns: [cn, uid]

privileges Section #

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

privileges:
  reading:
  - on: GLOBAL DEFAULT
    type: SELECT
    object: TABLES
  writing:
  - reading
  - on: GLOBAL
    type: SELECT
    object: TABLES

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

object #

Defines the target object for object-grained ACL.

Actually useful only for GLOBAL DEFAULT and SCHEMA DEFAULT ACL where the object is the target object class like TABLES, SEQUENCES, etc. grant rule defines target schema for SCHEMA DEFAULT.

privileges:
  reading:
  - type: SELECT
    on: GLOBAL DEFAULT
    object: TABLES
type #

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

The value can be either a single string or a list of strings. Plural form types is valid. When multiple types are defined, a new privilege is defined for each type, each with the same attributes such as on.

privileges:
  reading:
  - type: USAGE
    on: SCHEMAS
  
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

rules Section #

The top-level rules section defines synchronisation rules. The section contains 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 dictionary 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.

description #

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

ldapsearch #

This directive defines LDAP search parameters. It is named after the ldapsearch command-line interface utility shipped by OpenLDAP project. Its behaviour should be mostly the same.

Note

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

ldapsearch directives allow and require LDAP attributes injection in role and grant rules using curly braces. See Querying Directory with LDAP for details.

base, scope and filter

These parameters have the same meaning, definition and default as base, scope and filter arguments of ldapsearch command-line interface utility.

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

Customizes LDAP sub-search. 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. Actually, a single sub-search is supported.

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 second-level sub-search.

See Querying Directory with LDAP 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.

role #

Defines a rule to describe one or more roles wanted in the target Postgres Pro 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
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 combinations 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 distinguished 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.

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 comment.

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 Pro cluster. Capitalization is preserved, spaces are allowed (even if it's a really bad idea).

ldap2pg applies roles_blacklist_query on this parameter.

options #

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

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

Defines Postgres Pro configuration parameters that will be set for the role. Must be a YAML dictionary. Available configuration parameters vary following the version of the target Postgres Pro cluster. Some parameters require superuser privileges to be set. ldap2pg will fail 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.

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. Parent reference can be local roles not managed by ldap2pg.

rules:
- role:
    name: myrole
    parent: myparent
before_create #

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

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

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

rules:
- ldapsearch: ...
  role:
    name: "{sAMAccountName}"
    after_create: "CREATE SCHEMA {sAMAccountName.identifier()} AUTHORIZATION {sAMAccountName.identifier()}"
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
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).

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.

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.

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.

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.

acls Section #

Defines Postgres Pro ACLs. An ACL is set of queries to list GRANTs in the cluster and to manage them by granting or revoking item in the list. ACL is scoped to instance or database. References ACL in privileges profiles. Writing a custom ACL is tricky, ensure you understand both PostgreSQL and ldap2pg before.

acls

The acls top level section is a mapping defining ACLs. All fields are mandatory.

acls:
  PROCEDURE:
    scope: database
    inspect: |
      WITH ...
    grant: GRANT ...
    revoke: REVOKE ...
scope #

Scope of the ACL. Can be instance or database.

inspect #

SQL query to list GRANTs in the cluster. The signature of the query depends on the scope. See custom ACLs for details.

grant #

SQL query to grant a privilege. The query accepts templating using angle brackets. Some parameters are injected as keyword, i.e., as raw SQL. Other parameters are quoted as identifiers.

Available parameters:

  • <acl> name of the ACL. Raw SQL.

  • <database> name of database to grant on. Quoted identifier.

  • <grantee> name of role to grant on. Quoted identifier.

  • <object> name of object to grant on. Quoted identifier.

  • <owner> name of role to grant to. Quoted identifier.

  • <privilege> type of privilege. Raw SQL.

  • <schema> name of schema to grant on. Quoted identifier.

revoke #

SQL query to revoke a privilege. Like grant, the query accepts templating using angle brackets. Accepts same parameters as grant. Having different paramenter between GRANT and REVOKE leads to unexpected behaviour.

Inspecting Postgres Pro 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 the section called “postgres Section” for details.

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 the role to be dropped. This databases list also narrows the scope of GRANTs inspection. ldap2pg will revoke GRANTs only on these databases. See the section called “postgres Section” for details.

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

Synchronize a Subset of Roles #

By default, ldap2pg manages all roles from Postgres Pro, 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 exist 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.

Inspecting Schemas #

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

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.

Static Queries #

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

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

Managing Roles #

ldap2pg synchronizes Postgres Pro roles in three steps:

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

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

  3. Compare the two roles sets and apply to the Postgres Pro 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 allow you to deduplicate membership and options by setting a list of names.

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

Running Unprivileged #

ldap2pg is designed to run unprivileged. Synchronization user needs CREATEROLE option to manage other unprivileged roles. CREATEDB options allow synchronization user to manage 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 PostgreSQL 16 is actually flawed. You'd better just run ldap2pg with superuser privileges, you won't feel falsly secured.

Ignoring Roles #

ldap2pg totally ignores roles matching one of the global 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.

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 manage its members using ldap2pg.

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.

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) man page for the meaning and format of each options.

Injecting LDAP Attributes #

Several parameters accept LDAP attribute injection using curly braces. To do this, wrap 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 combinations 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 (DN) of the original LDAP entry.

LDAP Attribute Case #

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

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

Managing Privileges #

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

Basics #

The base design of ldap2pg is as follows. 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 Pro cluster for granted privileges.

  3. Compare the two sets of grants and update the Postgres Pro cluster using grant, revoke.

ldap2pg represents privileges with these core objects:

  • Privilege: An action on an object. e.g., CONNECT ON DATABASE.

  • grant: a privilege granted to a role on an object.

  • ACL: a list of grants.

  • profile: a list of privileges.

  • rule: a template to generate wanted grants.

ldap2pg.yml holds profiles and grant rule. ldap2pg synchronizes ACL 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 profile in privileges section. The simplest way is to reuse built-in privilege profiles shipped with ldap2pg in an active custom profile.

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 described in Section 5.8. See privileges YAML section documentation for details on privilege profile format.

ldap2pg loads referenced ACL by inspecting Postgres Pro cluster with carefully crafted queries. ldap2pg inspects only ACL referenced in at least one profile. Inspected grants are supposed to revocation unless explicitly wanted by a grant rule.

Warning

If it's not granted, revoke it!

Once an ACL is inspected, ldap2pg revokes all grants found in Postgres Pro instance and not required by a grant rule in rules.

Extended Intance Inspection #

When managing privileges, ldap2pg has deeper inspection of Postgres Pro 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 the schema.

Granting Privilege Profile #

Inspecting millions of privileges may consume a lot of resources on Postgres Pro instance. Revoking privileges is known to be slow in Postgres Pro. 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 schemas of each database as returned by schemas_query.

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.

Managing Public Privileges #

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

Postgres Pro also has the public schema. The public schema is a real schema available in all databases.

Postgres Pro 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.

Managing Default Privileges #

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

Postgres Pro attaches default privileges to the creator role. When the role creates an object, Postgres Pro applies 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 table 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 Postgres Pro, 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 DEFAULT ACL instead. If you want to grant/revoke default privilege per schema, you must reference SCHEMA DEFAULT ACL.

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

privileges:
  reading:
  - type: SELECT
    on: SCHEMA DEFAULT
    object: TABLES
  owning:
  - type: CREATE
    on: SCHEMAS

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

Postgres Pro has hard-wired global default privileges. If a role does not have global default privileges configured, Postgres Pro assumes some defaults. By default, Postgres Pro just grants privileges on owner. You can see them once you modify the default privileges. Postgres Pro will copy the hard-wired values along with 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.

Built-in Privileges #

ldap2pg provides some built-in ACLs 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.

Using Predefined Privilege Profiles #

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

Built-in privilege profile starts and ends with __. ldap2pg disables privilege profile starting with _. Thus you have to include built-in 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__

  ddl:
  - rw
  - __all_on_schemas__

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

Built-in profile's name follows the loose convention below:

  • ..._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 an alias of __delete__.

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

ACL Reference #

Here is the list of built-in ACLs.

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.

GLOBAL DEFAULT

Manage default privileges on database.

SCHEMA DEFAULT

Manage default privileges per schema.

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 re-granted if expected.

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

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

Default privileges reference a privilege type and a class of objects. ldap2pg inspects default privileges for the following object classes:

  • SEQUENCES

  • FUNCTIONS

  • TABLES

You must reference object class in privilege profile using object parameter in YAML.

You cannot configure custom ACL (yet).

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 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

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

Usage Recipes #

In this section, you'll find some recipes for various use cases of ldap2pg.

Configure pg_hba.conf with LDAP #

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

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

  2. In Postgres Pro cluster, manually create a single role having its password in LDAP directory.

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

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

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

  2. Then, complete ldap2pg.yaml to fit your needs following the section called “Command-Line Reference”. Run ldap2pg for real and check that ldap2pg maintain your single test role, and that you can still connect to the cluster with it.

  3. 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.

Search LDAP Directory #

The first step is to search your LDAP server with ldapsearch(1), the command-line interface 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 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 Configuration. Once you're satisfied with the comparison output, go real with --real.

Using LDAP High-Availability #

ldap2pg supports LDAP high availability out of the box just like any OpenLDAP client. Use a space-separated list of URIs to tell all servers.

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

See ldap.conf(5) for further details.

Running as Non-Superuser #

Since Postgres Pro> provides a CREATEROLE role option, you can manage roles without superuser privileges. Security-wise, it's a good idea to manage roles without superuser privileges.

Warning

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

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

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

  • Ensure creator can revoke all grants of managed users.

Removing All Roles #

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

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

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

ldap2pg as Docker Container #

We assume that you are already familiar with Docker and willing to save the setup time.

To run the container simply use the command:

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

The Docker image of ldap2pg uses the same configuration options as explained in the Command-Line Interface and ldap2pg.yml 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 you are 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. You can also find more information here.

Custom ACL #

ldap2pg comes with built-in ACLs for common objects like DATABASE, SCHEMA, TABLE, FUNCTION, etc. Postgres Pro has a lot of other objects like FOREIGN DATA WRAPPER, FOREIGN SERVER, FOREIGN TABLE, TYPE, etc. You may also want to manage custom ACL or something else. Writing a custom ACL should help you get the job done.

Note

Writing a custom ACL is quite advanced. You should be familiar with Postgres Pro ACLs and ldap2pg configuration. Ensure you have read Managing Privileges and ldap2pg.yml File Reference sections, have a good understanding of Postgres Pro ACL documentation and have successfully synchronized privileges with ldap2pg.

Define custom ACL in YAML configuration file.

Use Case #

Say you have a custom enum type myenum in your database.

CREATE TYPE public.myenum AS ENUM ('toto', 'titi', 'tata');

We want to manage privileges on this object, eventually other types, with a custom ACL.

Naming #

Name your ACL after the keyword in GRANT or REVOKE statement. From GRANT USAGE ON TYPE mytype TO myrole, name your ACL TYPE.

acls:
  TYPE:
    ...

Scope #

Postgres Pro defines user types per schema. Since we'll hardcode the schema, we will scope our ACL to database.

acls:
  TYPE:
    scope: database

Grant and Revoke #

Writing GRANT and REVOKE statements is the easiest part. See grant for details on query format. We'll use the object field of grant to store the name of the type. Public schema is hardcoded, as explained above.

acls:
  TYPE:
    scope: database
    grant: GRANT <privilege> ON TYPE public.<object> TO <grantee>;
    revoke: REVOKE <privilege> ON TYPE public.<object> FROM <grantee>;

Inspect #

The inspect query is the most difficult part. You need to master aclitem, aclexplode and acldefault Postgres Pro system built-ins. The signature of the inspect query depends on the scope of the ACL.

For instance scope:

  • type: a string describing privilege type as SQL keyword.

  • object: a string describing object name as SQL identifier.

  • grantee: a string describing role name as SQL identifier.

For database scope:

  • type: a string describing privilege type as SQL keyword.

  • object: a string describing object name as SQL identifier.

  • grantee: a string describing role name as SQL identifier.

  • partial: a boolean indicating if the grant is partial.

partial tells ldap2pg to re-grant ALL ... IN SCHEMA privileges. Since our ACL is handling one object at a time, partial will always be false.

ldap2pg sends a single parameter to inspect query: the effective list of privilege types managed by the configuration. This list is an array of text. ldap2pg expects query to filter other privileges out of the list.

For TYPE ACL, we will inspect privileges on pg_type system catalog.

acls:
  TYPE:
    scope: database
    grant: GRANT <privilege> ON <acl> public.<object> TO <grantee>;
    revoke: REVOKE <privilege> ON <acl> public.<object> FROM <grantee>;
    inspect: |
      WITH grants AS (
        SELECT typname,
               (aclexplode(COALESCE(typacl, acldefault('T', typowner)))).privilege_type AS priv,
               (aclexplode(COALESCE(typacl, acldefault('T', typowner)))).grantee::regrole::text AS grantee
          FROM pg_catalog.pg_type
        WHERE typnamespace::regnamespace = 'public'::regnamespace
          AND typtype <> 'b'  -- exclude base type.
      )
      SELECT grants.priv AS "privilege",
            grants.typname AS "object",
            CASE grants.grantee WHEN '-' THEN 'public' ELSE grants.grantee END AS grantee,
            FALSE AS partial
        FROM grants
      WHERE "priv" = ANY ($1)
      ORDER BY 2, 3, 1
      ;

As you see, it's not an easy query. This query works on Postgres Pro 17.

Usage #

You can now use your custom ACL in a profile. Reference object in profile, not in grant rule.

privileges:
  custom:
  - type: USAGE
    on: TYPE
    object: myenum

rules:
- roles:
    names:
    - alice
  grant:
    privilege: custom
    role: alice

You must reference all types manually in the profile. Executing ldap2pg should produce changes in your database:

$ ldap2pg
...
16:52:02 CHANGE Would Revoke privileges.                         grant="USAGE ON TYPE myenum TO public" database=db0
16:52:02 CHANGE Would Grant privileges.                          grant="USAGE ON TYPE myenum TO alice" database=db0
16:52:02 INFO   Comparison complete.                             searches=0 roles=1 queries=5 grants=1
16:52:02 INFO   Use --real option to apply changes.
16:52:02 INFO   Done.                                            elapsed=44.345229ms mempeak=1.6MiB ldap=0s inspect=28.992071ms sync=0s

That's it.

Debugging #

If you encounter problem, isolate the issue. Reduce the configuration to your custom ACL. Synchronize a single role, grant to it. Avoid LDAP searches, use only static rules. Synchronize a single database. Enable debug messages with --verbose option.

ldap2pg works database per database then ACL per ACL. The messages for each ACL are as follow:

First line about your ACL has acl=TYPE record attribute.

    17:13:35 DEBUG  Inspecting grants.                               acl=TYPE scope=database database=db0
    

Then you have messages for inspection: query and arguments. For each grant returned, a Found grant. message appears.

17:13:35 DEBUG  Executing SQL query:
WITH grants AS (
...
WHERE "priv" = ANY ($1)
ORDER BY 2, 3, 1
;
 arg=[USAGE]
17:13:35 DEBUG  Found grant in Postgres instance.                grant="USAGE ON TYPE myenum TO public" database=db0

Then, ldap2pg expands grants generated by rule. For each grant generated, a Wants grant. message is printed.

17:13:35 DEBUG  Wants grant.                                     grant="USAGE ON TYPE myenum TO alice" database=db0

Finally, ldap2pg prints changes it would apply.

17:13:35 CHANGE Would Revoke privileges.                         grant="USAGE ON TYPE myenum TO public" database=db0
17:13:35 DEBUG  Would Execute SQL query:
REVOKE USAGE ON TYPE public."myenum" FROM "public";
17:13:35 CHANGE Would Grant privileges.                          grant="USAGE ON TYPE myenum TO alice" database=db0
17:13:35 DEBUG  Would Execute SQL query:
GRANT USAGE ON TYPE public."myenum" TO "alice";

At the end, ldap2pg prints a conclusion message, even if no changes are required.

17:13:35 DEBUG  Privileges synchronized.                         acl=TYPE database=db0