Login roles
Create and manage Postgres roles through Studio, the API or plain SQL, and what Chelabase checks on the way.
Each branch has its own roles. You can create them three ways, and they all end up in the same list:
- in Studio, on the branch's roles panel;
- through the API:
GETandPOST /v1/databases/{id}/branches/{branch_id}/roles,POST …/roles/{role}/reset-passwordandDELETE …/roles/{role}. A role made this way gets a generated password, which you read with the branch's connection details and therolename. Names match^[a-z_][a-z0-9_]{0,62}$and can't be reserved (postgres,anon,pg_…, and others).owner_level: truegives the role the owner's attributes (CREATEROLE,CREATEDB,BYPASSRLS,REPLICATION) and membership ofneon_superuser; - in SQL, as the owner
postgres, through the proxy.
Projects run their own migrations and setup scripts through Chelabase's proxy. CREATE ROLE … LOGIN PASSWORD, ALTER ROLE … PASSWORD, ALTER ROLE … RENAME, DROP ROLE, GRANT role TO role and CREATE DATABASE all work when run as the owner.
Attributes, groups and memberships through the API
POST …/roles takes these fields besides name and owner_level:
| Field | Default | What it does |
|---|---|---|
login | true | false makes a NOLOGIN role, a group, with no password. |
inherit | true | INHERIT or NOINHERIT. |
connection_limit | -1 | -1 is no limit; lower values are refused with role_attribute_not_allowed. |
valid_until | never | When the password stops working (RFC 3339). |
bypass_rls | false | BYPASSRLS: the role ignores row-level security. |
member_of | none | Roles it becomes a member of. |
Nothing else can be set; any other field is refused as an unknown field (invalid_request). A group role doesn't count toward the login-role cap.
PATCH /v1/databases/{id}/branches/{branch_id}/roles/{role} changes a role: owner_level, login, inherit, connection_limit, valid_until (null clears it), bypass_rls and member_of. Fields you leave out stay as they are (as SQL last left them: see SQL controls every role), and member_of replaces the list of the role's other memberships (memberships in pg_* roles and Chelabase's own groups stay). Turning login on generates a password, which you read with the connection details and role; turning it off drops the password. Turning login on counts toward the login-role cap: at the cap it's refused with role_cap_reached (409). The answer never contains a password.
PATCH reaches only roles made through the API, and roles made in SQL by the owner (the owner administers them). A role another role made in SQL answers not_found, and the owner, the reserved roles and any chelabase_… role answer reserved_role_name.
Memberships
A membership is refused with role_membership_not_allowed when its target is:
- one of Chelabase's own roles:
authenticator,anon,authenticated, anychelabase_…role; neon_superuser(owner-level is the only way in: setowner_level),cloud_admin, anypg_…role or the owner;- the role itself, a role that doesn't exist, or one the owner can't administer;
- a group that is itself a member of one of those, at any depth, since joining it would reach them indirectly.
A connection limit under -1 is refused with role_attribute_not_allowed. Both codes are in the error reference. A schema merge creates and updates roles through this same path, so the same rules apply to it.
What works in SQL
CREATE ROLE <name> LOGIN PASSWORD <secret>: the role can log in through the proxy. The password may be plain or a pre-hashed SCRAM verifier (see the password rules); an MD5 one is refused.ALTER ROLE <name> PASSWORD <secret>: stored and synced to the proxy.ALTER ROLE <name> LOGINandNOLOGIN: take effect at once for roles other than the owner (the owner staysLOGIN: the compute forces it).VALID UNTILis enforced by Postgres at connect time; Chelabase keeps no expiry of its own.ALTER ROLE <name> RENAME TO <new>: the role's login works under its new name. A rename withPASSWORD NULLin the same transaction works.DROP ROLE <name>: removed from the proxy's cache. Dropping a role that a live secret data key uses is refused.GRANT <role> TO <app>: role inheritance is active;SET ROLE appandINHERITwork after creation. Studio shows the derived flags (such as owner-level) within a few seconds.CREATE DATABASE <name>: tracked and shown in Studio.- Roles made in SQL can log in after the first resync once the database is running.
ALTER TABLE … OWNER TO <role>also needsGRANT CREATE ON SCHEMA public TO <role>(Postgres 15 and later). The owner canSET ROLEto any role it creates, right afterCREATE ROLE.- Replaying a
pg_dumpallglobals file: itsALTER ROLE … WITH NOSUPERUSER …lines are refused for the owner, which isn't a superuser (Only roles with the SUPERUSER attribute may change the SUPERUSER attribute). RemoveNOSUPERUSERfrom those lines; the SCRAM password literals are accepted as they are.
Password rules
| Rule | Behavior |
|---|---|
Strength (CHELABASE_PASSWORD_STRENGTH) | Self-hosted: off by default, opt in with on. Platform: forced on. When on, a password needs at least 12 characters and an estimated 60 bits of entropy (character classes times length). |
| MD5 | Refused: MD5 passwords can't be used through Chelabase; set a plain password. A role that already has an MD5 password (from an older dump, say) is listed as unusable through the proxy until its password is reset. |
| Pre-hashed SCRAM | Accepted in self-hosted mode (strength rules don't apply to pre-hashed ones), refused in platform mode. |
PASSWORD NULL | Removes a role's login copy. A group role with no password has no proxy copy. |
PASSWORD '' | As in plain Postgres, this means no password (with a notice), the same as PASSWORD NULL: the role can't log in through the proxy until you set a password again. |
| Unreadable | A rename or a branch copy couldn't reseal the stored password, so it was dropped; reset the password. Studio shows "Stored password can't be read; reset it." A wrong secrets key instead gives a 500 credentials_unavailable when the password is revealed. |
Caps
| Cap | Variable | Default | Range |
|---|---|---|---|
| Login roles per branch (group roles without a password don't count) | CHELABASE_BRANCH_ROLE_CAP | 50 | 0 to 500 |
| Databases per branch | CHELABASE_BRANCH_DATABASE_CAP | 20 | 1 to 1000 |
The system postgres database is listed in Studio and the connection picker, and isn't counted in the database cap. Dropping the branch's main database is refused (the branch's main database "<name>" can't be dropped, also with IF EXISTS or WITH (FORCE)), and so is renaming it.
SQL controls every role
What you change in SQL sticks, for roles made in SQL and roles made in Studio or through the API alike: LOGIN, owner level (the owner's attributes and neon_superuser), INHERIT, CONNECTION LIMIT, VALID UNTIL, BYPASSRLS, the other attributes and memberships (GRANT and REVOKE). They take effect in Postgres at once. Chelabase records them in a resync that starts about 3 seconds after the statement commits (one resync for a burst of statements), so Studio's role list catches up within seconds; the periodic resync stays as the backstop. A later PATCH starts from the role as SQL left it.
Two things stay Chelabase's for a role made in Studio or through the API:
- It exists. A role the compute no longer has (a restore to before you dropped it outside Chelabase's view, say) is made again as it was last recorded. Drop it with
DROP ROLEor the API, and it's gone. - Its password. A password set with
ALTER ROLE … PASSWORDis recorded like any other (and is no longer revealable). One set where Chelabase can't see it is set back to the stored one.
A change made through Studio or the API that the compute didn't confirm (the compute was restarting, say) is retried by the resync until it's applied; until then the stored change is what the next PATCH starts from.
Live sessions
GET /v1/databases/{id}/branches/{branch_id}/roles/{role}/sessions lists the sessions open as a role on the branch's compute right now: count, and for each session its client_addr (null for a connection from inside the compute), application_name and backend_start, oldest first. It only reads: when the compute isn't running, the answer is compute_running: false with no sessions, and the compute isn't started. A client that isn't connected at that moment isn't listed. A name with no role has no sessions. Studio shows it on a schema merge that renames a role.
Resync
- Database DDL guard: after any
CREATE DATABASE,ALTER DATABASE … RENAMEorDROP DATABASE, the resync leaves that branch's databases alone for 10 seconds. A restore or reset to parent clears this guard before the resync runs. - Role repairs: a failing role repair (create, set password, a change not yet applied, or grant admin) is logged and skipped, and other repairs continue. The resync returns an error naming the failed roles once all repairs are done.
When the control plane is down
When Chelabase can't reach the control plane, role and database DDL fails after about 5 seconds with role and database changes are unavailable right now; try again (SQLSTATE 55000). The change doesn't take effect; retry the statement. Other refusals reach the client as their own one-line message, for example the branch owner's password can't be removed; reset it instead.
On a protected branch
On a protected branch with the "guard login roles" switch on, a login role other than the owner can't change the schema unless it is exempted on that branch by name. The member logins of non-Admins are refused either way. See Protected branches.
Passwords across branches
A new branch keeps its parent's role passwords unless the parent is protected. See Branches.