Roles at GuestGet#
GuestGet is the company; everything else on this host is legacy that will go. This page is the role catalogue: what a role means in every system a person can touch, so that "add a developer" or "hire a support agent" is a decision made once, here, and executed by the machinery in authentik/ (directory, Cloudflare Access, GitLab, PostgreSQL, sv1, mail) and by the platform back-office (admin.guestget.com).
Two rules make the catalogue work at 3 people and at 30:
- Roles are functions, not titles. A CTO is a person in
infra,tech-leadsandmanagement; a title is HR's concern, a role is a set of permissions. A person holds several roles; a role never holds a person's name. - A role's meaning lives here and in code; who holds it is an operation. Changing what
supportcan do is a merge request on this repository or onguestget, reviewed. Giving Ana thesupportrole is two clicks in the directory's own interface (https://id.profuse.ro/if/admin → Directory → Users, then Groups), which thepeoplerole may do without touching git. Every other system reads that: Access from thegroupsclaim at her next login, SSSD and PostgreSQL over LDAP, GitLab within five minutes, mail within fifteen. Nobody grants access inside one of those systems by hand; the quarterly review (access-review.md) is what catches the exception that slips through.
The catalogue#
Groups are slugs: they become the LDAP cn, the Unix group, the value in the groups claim. Every role has an owner, who approves membership and answers for it at the review.
| role (group) | who | owner |
|---|---|---|
infra | operates production: platform / SRE, and the CTO | CTO |
tech-leads | seniors who merge, release and read production data | CTO |
developers | backend, frontend, mobile engineers | CTO |
qa | testers, test automation | CTO |
product | product managers, designers | CEO |
support | L1/L2 support, customer success, implementation | head of customer |
finance | billing, invoices, payouts, accounting | CEO |
sales | account executives, partnerships (affiliate agents) | CEO |
marketing | site, blog, content, campaigns | CEO |
people | HR and administration: onboarding, offboarding, the directory's people | CEO |
management | CEO and whoever needs the whole picture read-only | CEO |
Do not add cto, ceo, senior-*, junior-*. Seniority is a title. If a senior needs more than a mid, that "more" is a role (tech-leads), and the mid gets it the day they are trusted with it.
What each role reaches#
Access has three levels (onboarding.md): identity alone (any device); identity on a managed device (WARP on, posture); identity, device and the private network (SSH, databases).
| system | infra | tech-leads | developers | qa | product | support | finance | sales | marketing | people | management |
|---|---|---|---|---|---|---|---|---|---|---|---|
directory administration (/if/admin) | read + outposts (device) | — | — | — | — | — | — | — | — | add and move people (device) | — |
| Cloudflare account, Zero Trust | member | — | — | — | — | — | — | — | — | — | — |
| sv1 SSH + sudo | yes (network) | — | — | — | — | — | — | — | — | — | — |
| PostgreSQL production | read/write (network) | read-only (network) | — | — | — | — | — | — | — | — | — |
| PostgreSQL staging | read/write | read/write | read-only | read-only | — | — | — | — | — | — | — |
handbook handbook.guestget.com (this page, onboarding, offboarding, the review; the first three also in Romanian at /ro) | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes |
handbook /who-has-what (names against roles) | yes | — | — | — | — | — | — | — | — | yes | yes |
GitLab guestget group (the product) | owner | maintainer | developer | developer | reporter | reporter | — | — | reporter | — | reporter |
GitLab profuse group (this repository: the infrastructure, and this catalogue's source) | owner | maintainer | reporter | reporter | reporter | reporter | — | — | reporter | reporter | reporter |
| Sentry | admin | admin | member | member | member | member | — | — | — | — | — |
| Zabbix, Traefik, mailcow admin | yes (device) | Zabbix read (device) | — | — | — | — | — | — | — | — | — |
| staging app | yes | yes | yes | yes | yes | yes | yes | yes | yes | — | yes |
back-office admin.guestget.com (platform role) | super_admin | super_admin | — | — | product | support | finance | sales | content | — | management |
| affiliate portal admin (agents) | yes | — | — | — | — | view | pay | manage, impersonate | — | — | view |
mail: personal mailbox first@guestget.com | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes |
| mail: shared addresses (see below) | security@ | — | — | — | — | support@, hello@ | billing@ | sales@, partners@, hello@ | press@ | jobs@, hr@ | legal@ |
| adding and moving people (the directory's interface) | — | — | — | — | — | — | — | — | — | yes | — |
Read it by column: hiring a support agent = groups = ["support"] = the handbook, the staging app, the back-office with the support platform role, Sentry as member, GitLab as reporter (to file bugs), a mailbox, and support@ / hello@ in their inbox. Nothing else, and nothing to remember.
Two rows are worth reading twice. The handbook is everyone's: it is this page, the onboarding page and the rest of docs/, published at handbook.guestget.com and gated by nothing but a directory identity, so nobody needs an account anywhere else to read how the company works. Its one restricted page, /who-has-what, is the nominal list — which person holds which role — generated from the directory and open to infra, people and management, the three roles that administer, answer for, and are accountable for membership. GitLab is the other way round: it is where the product is built, and finance and sales no longer hold an account there. They held one for a single reason — the "Start here" tile used to point into the repository, so their first day was a login wall — and publishing the handbook removed the reason.
The handbook is in two languages#
Everything in this repository is written in English, and docs/ro/ is the one exception. The reason is who each is for: a runbook is read by the people who maintain the systems, and this handbook is read by everyone the company hires. The pages a colleague needs in order to join, understand and leave — onboarding.md, this catalogue and offboarding.md — are translated, and served at handbook.guestget.com/ro. Every page carries the link to itself in the other language.
Two pages stay in English, and each says so where its Romanian version would be:
- the quarterly review (
access-review.md) is a procedure a member ofinfraperforms, and every artefact of it — the issue, the merge request, the output of the commands it asks you to read — is English already; - the nominal list (
/who-has-what) is names, usernames and group slugs, which are the same string in any language, and its address is what the Access policy matches. A copy at another address would be the same list of names with none of the gate.
English is the source of truth, and not by convention alone: each Romanian file records the sha256 of the English file it was translated from, and scripts/build-handbook.py fails the build when the two no longer match, naming the file and the command that records the new hash. Editing an English page therefore breaks the pipeline until someone carries the change across — which is the point.
Where each mapping is executed#
| system | mechanism |
|---|---|
| the directory itself | two roles in authentik/rbac.tf: directory-people (create a person, reset their password, move them between roles) for people, directory-operations (read everything, run the outposts) for infra. No group is a superuser; akadmin, which is not a person, is the break-glass account, and every structural change is an OpenTofu apply |
| Access, WARP, SSH, DB gateway | one Access group per role on the groups claim (cloudflare/accounts/profuse/access.tf), one policy per level; Gateway rules per role for the databases (gateway.tf) |
| PostgreSQL roles | pg-directory-sync (sv1, every 5 min): infra rw everywhere; tech-leads ro prod, rw staging; developers and qa ro staging; a demotion is a revoke |
| the handbook | docs/ rendered by scripts/build-handbook.py and published to Cloudflare Pages by the handbook:deploy job on every change to main; one Access application for the site (directory_everyone) and a second one for /who-has-what*, which a more specific path puts ahead of the first. The nominal list is read from the directory's API at build time, and a daily schedule rebuilds it. The Romanian pages are docs/ro/, published under /ro; each one records the hash of its English source and the build fails when they diverge (--refresh-translation-hashes records the new one) |
| GitLab | gitlab-directory-sync (sv1, every 5 min): the account is created before the first login, the highest role sets the access level on guestget and on this repository, a leaver is blocked and dropped from both. finance and sales get no account |
| Sentry | SAML through Access, the Sentry roles on the SaaS application; the role inside Sentry is member by default, admins by hand (listed in the review) |
| Zabbix | SAML through Access on a managed device for infra and tech-leads; the Zabbix role from the groups claim (infra → superadmin, tech-leads → user) |
| sv1 | SSSD simple_allow_groups = infra, sudoers %infra |
a mailbox per active person (mailcow template, first SSO); shared addresses are aliases whose recipients are the role's members, reconciled by mailcow-directory-sync (every 15 min) | |
| back-office | today: is_super_admin + a platform_role column with four agent abilities, password login, no SSO — see the next section |
The back-office is the gap#
admin.guestget.com is where support, finance, sales and product will spend their day, and it is the one system that does not know the directory: staff log in with a password, the platform_role column can only be set from a seeder, and every section except the affiliate agents is super-admin-only. Three changes, in guestget, in this order:
- SSO. Staff log into the back-office through authentik (OIDC; Access already gates the hostname). Password login stays only for the seeded break-glass super-admin. The
groupsclaim is read at every login and setsplatform_role— the column becomes a cache of the directory, never edited by hand. - A platform-role catalogue that matches this page.
config/platform.phpgrows from four agent abilities to sections:organizations.view,organizations.manage,users.view,bookings.view,subscriptions.*,invoices.*,agents.*,content.*(blog, landing, websites),analytics.view,impersonate. Roles:super_admin(infra, tech-leads),support,finance,sales,content(marketing),product,management(read everything, change nothing). The admin app already gates its navigation on abilities; only the map grows. - Impersonation with a reason. Support impersonating a hotel is the most sensitive thing the company does. Today it needs super-admin and leaves no reason. It becomes a
supportability that requires a ticket reference, lasts one hour, is read-only unless the role says otherwise, and is written to the admin audit log with the reason — the same shape the affiliate-agent impersonation already has.
Mail#
One domain, guestget.com. A person has exactly one mailbox, firstname@guestget.com, created by the directory at their first SSO login and switched off the day they leave. Nobody has a second password for mail: clients use app passwords.
Shared addresses are aliases, not mailboxes: support@, hello@, billing@, sales@, partners@, press@, jobs@, hr@, legal@, security@. Their recipients are the members of a role, kept in sync from the directory — joining support puts support@ in your inbox, leaving removes it, and no shared password ever exists. Today support@, billing@, legal@ and app@ are real mailboxes with their own passwords; mailcow-directory-sync reports what each would become and never converts a mailbox on its own — delete the mailbox (after exporting it) and the next run creates the alias. app@ / noreply@ / alerts@ stay mailboxes: they are the platform's own senders and receivers, with app passwords held in the secrets store, not by a person. When support outgrows an inbox, a helpdesk tool takes over support@ — the alias just points somewhere else.
Naming, and the domain#
Usernames are firstname (or firstname.lastname at the first collision), lowercase, chosen once: the same string is the GitLab handle, the PostgreSQL role, the Unix user and the mailbox. The directory's e-mail is the person's @guestget.com address.
The directory, GitLab and the Access team domain still carry the profuse.ro name from the company that built them. They move to id.guestget.com, git.guestget.com and a guestget Zero Trust team as their own migration — DNS, certificates, the OIDC callback URLs, WARP re-enrolment — after the roles above are in place, not before: a rename with three roles is a morning, a rename mid-hiring is a week.
How this grows#
- Today: this catalogue in git, the people in the directory's interface, the quarterly review.
- When someone starts asking for access rather than being given it: authentik's access-request workflow (2026.8) lets a person request a role and its owner approve it in the portal, with the grant recorded there instead of in a conversation.
- When there is an HR system: it feeds the directory over SCIM and even the two clicks go away. The catalogue does not change shape at any size; only who performs the assignment does.