IAM appliance - iam-lib-pgconnect
Python library for connecting to PostgreSQL databases in the IAM Appliance without hunting for credentials by hand. Instead of
docker exec -it -u postgres czechidm-db psql -d czechidm
you write, in Python:
import iam_lib_pgconnect
with iam_lib_pgconnect.connect() as db:
print(db.query("SELECT count(*) FROM idm_identity"))
The library on its own:
-
resolves the container’s IP through the Docker SDK,
-
reads the password from a pwfile on the host,
-
opens a psycopg2 connection.
Connections are read-only by default. The common case is reading data, and the
default user is the postgres superuser - so a mistyped UPDATE or DELETE
would otherwise write straight into the IdM database with full rights. To modify
data you opt in explicitly with readonly=False (see below).
Installation
This library is mainly a dependency of other iam-* utlities so you probably won’t need to explicitly install it. You can of course do that, in case you want to do some DIY scripting, though.
dnf install iam-lib-pgconnect
Library dependencies (python3-psycopg2, python3-docker) are pulled in automatically by dnf.
Required privileges
The library reads two privileged resources, so it only works under root or
under an account that has been explicitly granted access to both:
-
Password files on the host at
/data/volumes/{db}-db/secrets/*.pwfile, typically owned byrootwith mode0600. -
The Docker API via the local socket
/var/run/docker.sock, used to resolve the target container’s IP. Reachable byrootor members of thedockergroup.
Running under any other account fails fast: a PermissionError when reading
the pwfile, or a Docker permission error when opening the socket. The library
does not attempt to escalate or fall back.
Quick start
Connection to the czechidm DB as user postgres:
import iam_lib_pgconnect
with iam_lib_pgconnect.connect() as db:
rows = db.query("SELECT id, username FROM idm_identity LIMIT 5")
for row in rows:
print(row["id"], row["username"])
rows is a list of dicts, keys are column names.
How to use the library
Reading data (SELECT)
import iam_lib_pgconnect
with iam_lib_pgconnect.connect() as db:
# No parameters:
rows = db.query("SELECT count(*) AS total FROM idm_identity")
print(rows[0]["total"])
# With parameters (always via %s, never via string interpolation - SQL injection):
rows = db.query(
"SELECT id, username FROM idm_identity WHERE username = %s",
["admin"],
)
print(rows)
Modifying data (INSERT / UPDATE / DELETE)
Connections are read-only by default, so writes require readonly=False:
import iam_lib_pgconnect
with iam_lib_pgconnect.connect(user="czechidm", readonly=False) as db:
affected = db.execute(
"UPDATE idm_identity SET modified = now() WHERE username = %s",
["admin"],
)
print("rows affected:", affected)
# When the `with` block exits without an exception, commit is called automatically.
# If the block raises, a rollback is performed. The connection is always closed.
Without readonly=False, the write fails with psycopg2’s
ReadOnlySqlTransaction error before anything is committed.
Multiple statements in one transaction
Same as a single statement - as long as you stay inside with, no commit happens:
import iam_lib_pgconnect
with iam_lib_pgconnect.connect(user="czechidm", readonly=False) as db:
db.execute("INSERT INTO idm_audit_log (msg) VALUES (%s)", ["start"])
db.execute("UPDATE idm_identity SET disabled = true WHERE id = %s", [user_id])
db.execute("INSERT INTO idm_audit_log (msg) VALUES (%s)", ["done"])
# Commit happens only here, after the block finishes successfully.
If you want to drive commit/rollback manually (e.g. several commits in one
connection), call them yourself and don’t use with:
db = iam_lib_pgconnect.connect(user="czechidm", readonly=False)
try:
db.execute("INSERT ...", [...])
db.commit()
db.execute("UPDATE ...", [...])
db.commit()
finally:
db.close()
A DB instance other than czechidm
The db parameter drives everything (container {db}-db, database name,
pwfile paths):
import iam_lib_pgconnect
with iam_lib_pgconnect.connect(db="bridge") as conn:
rows = conn.query("SELECT current_database()")
print(rows)
with iam_lib_pgconnect.connect(db="bridge", user="bridge") as conn:
rows = conn.query("SELECT current_user")
print(rows)
Combinations of (db, user) that make sense on a typical appliance:
| db | user=postgres | user=<application name> |
|---|---|---|
czechidm |
superuser, full rights |
|
bridge |
superuser, full rights |
|
If the {db}-db container is not running or the pwfile is missing, connect()
raises RuntimeError straight away.
API overview
iam_lib_pgconnect.connect(…)
connect(db='czechidm', user='postgres',
password_provider=None, host=None, port=5432, dbname=None, readonly=True)
| parameter | purpose |
|---|---|
|
instance identifier, e.g. |
|
DB user, e.g. |
|
custom password source; if |
|
DB address; if |
|
DB port. Default |
|
DB name; if |
|
open the session read-only. Default |
Returns IamDbConnection.
IamDbConnection
| method | what it does |
|---|---|
|
SELECT - returns |
|
INSERT / UPDATE / DELETE - returns the number of affected rows. |
|
commits the transaction. |
|
rolls the transaction back. |
|
closes the connection. |
Password sources - outlook
The way passwords are stored may change over time. The library is built for
that: a new source is added as an internal backend and the auto-detection in
connect() picks the one available in the current environment. The call
itself does not change.
| state | backend |
|---|---|
today |
pwfile on the host ( |
planned |
key=value secret storage (e.g. Vault) - once it’s available |