Skip to content

Teams and access

Checkstack’s access model is built from three pieces: identity, roles, and teams. Identity says who is making a request. Roles bundle coarse permissions (“can manage users”, “can read systems”). Teams scope those permissions to specific resources (“you may manage the Payment API system, but not the Identity API”). This page is the operator-level synthesis. For the wire-level contract see the developer-facing Users and teams doc.

Every request to Checkstack carries an identity, which is one of three types:

  • Real user. A human who logged in through one of the configured authentication strategies (credential, SAML, LDAP, GitHub OAuth, …). Carries roles and team memberships.
  • Application. An API key for an external machine client. Also carries roles and team memberships. Configured under the Applications tab.
  • Service. Plugin-to-plugin internal calls. Trusted implicitly and bypasses role and team checks. You will not see service identity in any UI; it is purely internal.

For everyday access decisions, “real user” and “application” behave identically. The split exists so admin surfaces can treat them separately (humans have a lifecycle, API keys have a rotation story).

A role is a named bundle of access rules. An access rule is a flat string key like catalog.systems.read or auth.users.manage that a plugin registers at startup. A role grants any number of access rules; permissions union when a user holds multiple roles.

Checkstack ships with these built-in roles:

RoleWhat it covers
adminEverything. Holds the wildcard * rule. Cannot be deleted.
usersThe default authenticated user. Holds read-level access rules.
anonymousLogged-out users. Used for public default access rules.

You can create additional roles under Infrastructure -> Auth -> Roles. Roles you create are deletable and assignable. The built-in anonymous role is filtered out of the role-assignment UI so admins do not accidentally hand it to a person.

Users cannot modify their own role assignments. The backend enforces this even if a UI somewhere tried; it prevents accidental self-lockout or self-elevation.

Roles answer “what kinds of things may you do?” Teams answer “which resources may you do them to?” A team carries:

  • A list of members, by user or application ID.
  • A list of team managers, who can manage that team’s membership and managers without holding global admin (granting the team access to resources stays an admin action - see the permissions table below).
  • A list of resource grants, each of which says “this team may read or manage resource <resourceType>/<resourceId>”.

A user has access to a resource when at least one of these is true:

  1. They hold a role that includes the relevant global access rule (for example, catalog.systems.read or the wildcard *).
  2. They are a member of a team that has a grant for that specific resource.

In other words: roles let you cut access “wide” (everyone can read everything), teams let you cut access “narrow” (only the Payments team can manage the Payment API system).

Resources you mark team-only opt out of the global path entirely. A team-only system requires team membership; even a user with the global catalog.systems.read rule cannot see it unless they are on a team with a grant for it. Use this for sensitive systems.

Two practical distinctions you will hit:

  • Platform admins (users with the admin role) can do anything. They manage users, roles, teams, and resource access settings across the whole platform.
  • Team managers (users in a team’s team_manager list) can manage that specific team’s membership and managers. They cannot touch other teams, grant the team access to resources, or do platform-wide admin work.

This is the usual delegation pattern: a small number of platform admins, and per-team managers who run their own corner.

Every team-related action maps to an access rule (granted via a role) and, for some actions, to being a manager of the specific team. “Admin” below means holding auth.teams.manage (the admin role has it).

ActionWhat you need
View teams, members, and “who can change this” on a resourceauth.teams.read
Create a teamauth.teams.manage (admin)
Delete a teamauth.teams.manage (admin)
Rename / edit a team’s detailsauth.teams.read and be a manager of that team (or auth.teams.manage)
Add / remove team membersauth.teams.read and be a manager of that team (or auth.teams.manage)
Promote / demote team managersauth.teams.read and be a manager of that team (or auth.teams.manage)
Grant or revoke a team’s access to a resource (the Team access editor, Scope to team)auth.teams.manage (admin)
Make a resource team-only (private)auth.teams.manage
Allow a team to create a resource type (create-capability)auth.teams.manage
Create a resource owned by a teamone of: the global <plugin>.<resource>.manage rule; membership of a team that has a create-capability grant for that type; or (incidents/maintenances) manage access to the target system

Key takeaways:

  • Team managers are about running a team (members + managers), not about handing out resource access. Granting a team access to a system, or the right to create a resource type, is an admin (auth.teams.manage) action.
  • Reading is broad: anyone with auth.teams.read can see teams and what manages a resource. Read of the resources themselves stays global unless the resource is marked team-only.
  • Creating a team-owned resource has three independent paths (global manage, a per-type create grant, or managing the parent system) - a user needs only one of them.

When a plugin needs to know whether the current user can see a particular resource, it does not reimplement team-grant logic. The auth backend exposes two service-to-service endpoints that every other plugin uses:

  • A single-resource access check for fast-path decisions (“can this user see system X?”).
  • A list filter for list paths (“of these system IDs, which ones can this user see?”).

Both take a hasGlobalAccess argument (the caller’s role-based verdict). If global access is granted, the backend returns true. Otherwise it consults team grants in a single query.

This means consistency: every plugin that respects team access uses the same engine. If you grant a team manage access on a system, the catalog UI, the health-check UI, the dependency map, and any third-party plugin that integrates correctly all honour it.

A few patterns that show up often:

  • Open read, locked write. Default: users role has *.read rules; managing anything requires admin. Everyone sees everything; only admins change it.
  • Per-team ownership. Default users have global read. Manage access is granted via teams that own specific systems (Payments team owns Payment API, etc.).
  • Tight read scoping. Some systems are marked team-only. Default users do not see them; only their team does. Useful for compliance-sensitive systems.
Where to goWhat you do there
Configuration -> Auth Settings -> UsersAdd users (credential auth), assign roles, deactivate. (Admin.)
Configuration -> Auth Settings -> RolesCreate and edit roles, manage their access-rule lists. (Admin.)
Configuration -> TeamsA standalone page (gated on auth.teams.read, not part of admin Auth Settings): create teams (admin), manage a team’s members and managers (a team’s own managers can do this), search the directory to add members, and (admin) review/edit/revoke/add the team’s resource grants by name.
Configuration -> Auth Settings -> ApplicationsIssue API keys, assign them roles and team memberships. (Admin.)
Resource detail -> Who can change thisSee who can change a resource (and the people by name), and (admin) grant a team access to it. The same editor appears on systems, health-check configurations, incidents, and maintenances.

There are two complementary places to wire teams to resources, and they share one engine:

  • From the resource (“Who can change this” editor on a system / health-check / incident / maintenance): scope this resource to one or more teams. Best when you are already looking at the resource.
  • From the team (Teams page): review everything a team can touch by name, change a grant’s level, revoke it, or add a new grant by searching for a resource. Best for review, offboarding, or setting a team up across many resources at once.

Create forms also expose an Owning team picker to assign a new resource to a team at creation time.

A few things are intentionally out of scope for the current release and tracked separately:

  • Audit log. Checkstack does not yet ship a built-in audit log of role/team/grant changes.
  • CSV exports. Bulk export of users and teams is not built in; the data is queryable via the API.
  • SLO objective labels. SLO objectives have no human-readable name, so in the Teams grant list they show the owning system id rather than a friendly label.
  • Health-check “who can change this” reach. The read-only access indicator on a health-check configuration currently lives on the configuration detail page, which is gated on healthcheck.configuration.manage. So it is visible to people who can already manage the check, not to a read-only viewer wanting to know who to ask. The system, incident, and maintenance indicators sit on read-accessible pages and do not have this limitation; surfacing the health-check one on a read-level view is a follow-up.