Skip to content

Commit 3092173

Browse files
committed
document admin privileges, password handling, and non-superuser limits
1 parent 062610a commit 3092173

3 files changed

Lines changed: 65 additions & 8 deletions

File tree

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@ AboutBits PostgreSQL Operator is a Kubernetes operator that helps you manage Pos
1111

1212
> **Note:** Kubernetes 1.29+ is required due to the use of CRD CEL validations (GA in 1.29, Beta in 1.25).
1313
14+
The admin role used by the operator does not need to be a superuser.
15+
Managed services such as AWS RDS, Amazon Aurora, Google Cloud SQL, and Azure Database for PostgreSQL are supported.
16+
See [Admin privileges](docs/cluster-connection.md#admin-privileges).
17+
1418
## Architecture
1519

1620
```

docs/cluster-connection.md

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,6 @@ Use this option when the credentials should be mounted as a file inside the oper
3535
|--------|----------|-----------------------------------------------------------------------------------------|----------|
3636
| `path` | `string` | The absolute path inside the operator Pod to the file containing the admin credentials. | Yes |
3737

38-
3938
#### File format
4039

4140
The file must contain JSON with the following fields:
@@ -66,6 +65,21 @@ The value of `adminSecretFileRef.path` is the `mountPath` plus the name of the f
6665

6766
See [Using a file reference](#using-a-file-reference-adminsecretfileref) in the examples for a complete setup with each volume source.
6867

68+
## Admin privileges
69+
70+
The admin role does not need to be a superuser. This makes the operator usable with managed services such as AWS RDS, Amazon Aurora, Google Cloud SQL, or Azure Database for PostgreSQL, where no superuser is available.
71+
72+
| Custom Resource | Required privilege of the admin role |
73+
|---------------------------------------|-----------------------------------------------------------------|
74+
| `ClusterConnection` | `LOGIN` |
75+
| `Role` | `CREATEROLE` |
76+
| `Database` | `CREATEDB` |
77+
| `Schema`, `Grant`, `DefaultPrivilege` | Ownership of, or the matching privileges on, the target objects |
78+
79+
The master user of the managed services above has `LOGIN`, `CREATEDB`, and `CREATEROLE`. See [Role](role.md#non-superuser-admins) for the limits that apply to a non-superuser admin.
80+
81+
The operator reads role state from the public view `pg_roles`. It does not read `pg_authid` or `pg_shadow`, which these services deny.
82+
6983
## Examples
7084

7185
### Using a Kubernetes Secret (`adminSecretRef`)

docs/role.md

Lines changed: 46 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,14 @@ The `Role` Custom Resource Definition (CRD) manages PostgreSQL roles (users).
44

55
## Spec
66

7-
| Field | Type | Description | Required | Mutable |
8-
|---------------------|---------------|-------------------------------------------------------------------------------------|----------|---------|
9-
| `clusterRef` | `ResourceRef` | Reference to the `ClusterConnection` to use. | Yes | Yes |
10-
| `name` | `string` | The name of the role to create in the database. | Yes | No |
11-
| `comment` | `string` | A comment to add to the role. | No | Yes |
12-
| `passwordSecretRef` | `ResourceRef` | Reference to a secret containing the password for the role to make it a LOGIN role. | No | Yes |
13-
| `flags` | `RoleFlags` | Flags and attributes for the role. | No | Yes |
7+
| Field | Type | Description | Required | Mutable |
8+
|----------------------|---------------|-------------------------------------------------------------------------------------|----------|---------|
9+
| `clusterRef` | `ResourceRef` | Reference to the `ClusterConnection` to use. | Yes | Yes |
10+
| `name` | `string` | The name of the role to create in the database. | Yes | No |
11+
| `comment` | `string` | A comment to add to the role. | No | Yes |
12+
| `passwordSecretRef` | `ResourceRef` | Reference to a secret containing the password for the role to make it a LOGIN role. | No | Yes |
13+
| `passwordEncryption` | `string` | How the password is sent to PostgreSQL: `scram-sha-256` (default) or `server`. | No | Yes |
14+
| `flags` | `RoleFlags` | Flags and attributes for the role. | No | Yes |
1415

1516
### ResourceRef (`clusterRef` and `passwordSecretRef`)
1617

@@ -45,6 +46,44 @@ The operator uses the presence of the `passwordSecretRef` field to determine if
4546
- **Login Role (User)**: If `passwordSecretRef` is specified, the role is created with the `LOGIN` attribute. It uses the password from the referenced secret.
4647
- **No-Login Role (Group)**: If `passwordSecretRef` is omitted, the role is created with the `NOLOGIN` attribute. This is useful for creating roles that serve as groups for permissions.
4748

49+
### Password handling
50+
51+
The operator does not read the password hash from `pg_authid`.
52+
That catalog is readable by superusers only, and managed PostgreSQL services of cloud providers, such as AWS RDS, Google Cloud SQL, or Azure Database for PostgreSQL, revoke it from every role, including the master user.
53+
54+
Instead, the operator stores a keyed fingerprint of the password it applied last in `status.passwordFingerprint`.
55+
On each reconcile it compares the referenced Secret against that fingerprint. When they differ, the operator runs `ALTER ROLE ... PASSWORD`.
56+
57+
The fingerprint is an `HMAC-SHA256`. Its key is random and private to the operator.
58+
The operator generates the key once and stores it in a Secret named `postgresql-operator-password-fingerprint-key` in its own namespace. A reader of the `Role` status learns nothing about the password without that key.
59+
The Secret name is set by the configuration property `postgresql-operator.password-fingerprint.secret-name`, for example through the environment variable `POSTGRESQL_OPERATOR_PASSWORD_FINGERPRINT_SECRET_NAME`.
60+
61+
**Consequences:**
62+
63+
- The Secret is the source of truth. A password change made directly in PostgreSQL is not detected.
64+
- If the key Secret is lost, the operator generates a new key and re-applies every `Role` password once.
65+
- After the upgrade to the version that introduced the fingerprint, every existing `Role` gets one password update, because its status has no fingerprint yet.
66+
67+
#### `passwordEncryption`
68+
69+
| Value | Behavior |
70+
|-----------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
71+
| `scram-sha-256` | **Default**. The operator computes the `SCRAM-SHA-256` verifier itself and sends only the verifier. The cleartext password never reaches the server, its statement log, or extensions such as `pgaudit`. |
72+
| `server` | The operator sends the cleartext password. The server hashes it according to its `password_encryption` setting. Use this for clients that only support MD5 authentication. |
73+
74+
If the Secret already contains an `MD5` or `SCRAM-SHA-256` verifier, the operator forwards it unchanged in both modes.
75+
76+
**Note:**
77+
A pre-hashed password bypasses server-side password policies. The `credcheck` extension rejects it unless `credcheck.encrypted_password_allowed` is on. For example a Cloud SQL password policy does not apply to hashed passwords. Set `passwordEncryption: server` when such a policy must apply.
78+
79+
### Non-superuser admins
80+
81+
The admin role of the `ClusterConnection` does not need to be a superuser. `CREATEROLE` is sufficient for `Role` resources.
82+
See [ClusterConnection](cluster-connection.md#admin-privileges) for the full list of privileges. The following limits apply when the admin is not a superuser:
83+
84+
- The flags `superuser`, `replication`, and `bypassrls` cannot be set. PostgreSQL rejects them, and the `Role` status shows the error.
85+
- On PostgreSQL 16 and later, the admin can only alter roles on which it holds `ADMIN OPTION`. Roles created by the operator qualify. Roles created by another user do not, unless that user grants the admin `ADMIN OPTION`.
86+
4887
### Example
4988

5089
```yaml

0 commit comments

Comments
 (0)