ADR 0021: Server databases and directories as host-scoped permissions
Context #
Extensions had a private SQLite file and HTTP, but no way to query a company PostgreSQL or MySQL server or look people up in LDAP and Active Directory. Both reach other machines with credentials, so they need consent, and a database connection is more than a network request: it can run any statement the account allows.
Decision #
- Three new permission kinds, all scoped by
host[:port]:sql,ldapandldap.write. A scope without a port covers every port, a host also covers its subdomains, and an IP address matches only itself. The check runs insidesql.openandldap.connecton the host and port that will be dialled, and again on each LDAP operation. - LDAP reads and writes are separate kinds. A lookup extension should not hold write access, and
ldap.writeis easy to show as its own line in the consent dialog.ldap.writecan connect and modify but cannot search. SQL has one kind: a read-only grant cannot be enforced from the client because the server decides what a statement does, so the account on the server is the real limit and the docs say so. - Drivers:
jackc/pgx/v5(itsdatabase/sqladapter),go-sql-driver/mysqlandgo-ldap/ldap/v3, all pure Go. The script cannot name a driver outside the closed listpostgresandmysql. - The
sqlhandle reuses thesqlitehandle code (query, exec, transaction, close, the row cap, the single pinned connection and the transaction ownership rules), with a per-statement timeout and a placeholder rewriter. Scripts write?or:namefor both servers; the host turns them into$1..for PostgreSQL, skipping quoted text and comments. One shape to learn. - TLS is on by default, certificates are verified against the system roots, and there is no option to skip verification.
tls=Falseis allowed for SQL on a private network. LDAP usesldaps://orstart_tls=True, and a bind password is refused over plainldap://unless the host is loopback. Connection settings are built from fields, never from a script-supplied connection string, and the PostgreSQL driver is told not to read the user's.pgpass. - Passwords are a string or
{"secret": name}, resolved from the extension's own entries insecrets, which then needs thesecretspermission. Error messages have the password scrubbed. - Limits: 8 connections per extension, closed on stop; 10000 rows per query and per LDAP search; timeouts of 1 to 300 seconds.
ldap.check_binduses a second connection so the main one keeps its identity, and refuses an empty password, which servers treat as an anonymous bind. ldap.escape_filterandldap.escape_dnare pure and need no permission.
Consequences #
Extensions can reach company databases and directories after the user approves a named host. A granted sql extension can do whatever its database account can; the consent text says it can change or delete data. The binary grows by the three drivers (see the size in the change that added them). Tests use a fake database/sql driver and an in-process LDAP server speaking real BER over loopback; nothing here was run against a live PostgreSQL, MySQL or directory server.