Users, groups and roles
Local and LDAP sign-in, the four roles and where they come from, the read-only viewer, field-level permissions, sessions, and what is not supported yet.
Sign-in
Two providers work today: local accounts with a password, and LDAP / Active Directory. Both can be on at once; the login form offers whichever are enabled. Providers are declared in the chart's users/*.yaml, mounted at /etc/itops/users and read at every start. The Auth Providers page in the UI shows them and is read-only: adding or changing a provider is a values change and a redeploy.
Local accounts
Enabled by default (users/local.yaml, local.enabled: true). The first start seeds admin / Password123! as a superadmin, only if the local provider is enabled and only if no admin exists. Change that password at first login.
Password policy for local accounts, from users/local.yaml: at least 12 characters, upper and lower case, a digit, symbol optional, the last five passwords remembered. local.allowRegistration is false; set it to true for self-service sign-up.
Password reset is a one-time link, never a password in an email or on screen. resetPassword issues the link; setPasswordWithToken consumes it.
LDAP and Active Directory
# values.yaml
env:
ITOPS_FEATURES_LDAP_AUTH: "true"
auth:
ldap:
bindPasswordSecret: { name: itops-ldap, key: bindPassword } # an existing Secret
# users/ldap.yaml
ldap:
enabled: true
name: corp-ad
displayName: "Corporate Active Directory"
isDefault: true
jitProvisioning: true
host: ldap.corp.example.com
port: 636
useTLS: true # ldaps; or startTLS: true on 389
bindDn: cn=itops,ou=service,dc=corp,dc=example,dc=com
bindPasswordSecret: { name: itops-ldap, key: bindPassword }
baseDn: dc=corp,dc=example,dc=com
userFilter: "(sAMAccountName=%s)"
groupFilter: "(member=%s)"
userAttrs: { username: sAMAccountName, email: mail, displayName: cn, firstName: givenName, lastName: sn }
groupMappings: []
Users are created on their first successful bind (jitProvisioning). The bind password comes from your own Secret, mounted at /etc/itops/auth/ldap-secret. The bindPassword key in the YAML exists for development and is not what a production install should use.
Group auto-join. An LDAP group whose cn equals an ITOps group name joins the user to that group at login. The built-in admin groups are prefixed itops- on purpose, so a company-wide cn=admins never promotes anyone by accident. An explicit entry in auth.ldap.groupMappings[] wins over name matching. syncLDAPGroups re-reads memberships on demand.
The chart's network policy allows LDAP egress to a same-namespace pod labelled app=ldap on 389 and 636 (networkPolicy.allowLdapEgress). A directory outside the namespace needs its own egress rule.
Not supported yet
Entra ID, Google Workspace, Okta, Auth0, Keycloak, OneLogin, JumpCloud, AWS IAM Identity Center, ADFS, GitHub, GitLab, generic SAML and generic OIDC appear in the provider catalogue and can be configured, but signing in through them returns "not yet implemented". MFA is a feature flag that, if enabled, refuses every password login rather than pretend. The placeholders users/m365.yaml and users/aws.yaml in the chart are not wired to anything.
Roles
A person's role is derived from group membership at every request. It is a ceiling, not a floor, and every check fails closed.
| Role | Granted by membership in | May |
|---|---|---|
| admin | itops-admins, itops-trust-admins (also the legacy admins, infra-admins) |
everything: users, groups, licence, deletes, permission rules |
| operator | infra-admins, security-team, compliance, pki-operators |
day-to-day work: services, tickets, SLA definitions, exclusion windows, tickets on behalf of others |
| requester | any signed-in user in none of the above | raise and follow their own tickets, read what their field permissions allow |
| viewer | itops-viewers |
read everything, change nothing |
The viewer check runs last, so it never demotes an admin who also happens to be a viewer. Every mutation that changes the world requires at least operator; deleting a user, a service or a node requires admin; the REST attachment upload requires a user session, not the operator key. The public demo's demo account is a viewer, which is how it can be handed to strangers.
Roles apply to the API, not only the UI. A viewer's deleteService fails with FORBIDDEN whether it comes from a button or from curl.
Groups
Groups are rows in the database, hierarchical by name, managed on the Users page (admin) or upserted from the chart's groups/*.yaml at core start. Four ticketing groups ship by default (incident-responders, change-implementers, problem-investigators, provisioners); the role groups above are created as needed. Group names may take the path form, prod/eu-central-1/db-admins, which the ticketing lookup walks from the most specific level up.
Users are not in YAML by design: passwords, sessions and login history are runtime data. Create people in the UI (createUser takes groupIds), or let them arrive through LDAP.
Field-level permissions
Beyond the four roles, individual fields can be HIDDEN, READ, WRITE or ADMIN per group. The plugins register the fields that matter: on a ticket title, description, priority, status, assignee; on an SLA definition definition_name, target_value, actual_value, warning_threshold, critical_threshold, change_reason. Rules carry a priority and optional conditions: only the user's own records, only certain statuses or priorities, only within a time window (hours, days, timezone). The same mechanism covers screens and UI elements (screenPermissions, myUIPermissions) and environment-level access (checkEnvironmentAccess).
Managed on the Permissions page or with addPermissionRule, updatePermissionRule, deletePermissionRule, updateFieldDefinition. The UI reads myFieldPermissions and myUIPermissions at login and hides or locks accordingly; the API enforces the same rules on every read and write.
Sessions
Sign-in returns a 15-minute access token and a 7-day refresh token (HS256, ITOPS_JWT_SECRET). mySessions lists a user's sessions with IP address, user agent, creation, expiry and last activity; revokeSession and revokeAllSessions end them; extendSession pushes the idle timeout, which defaults to 8 hours.
Rate limiting and headers
Per-IP token-bucket rate limiting (10 requests per second, burst 20) is forced on in production. The client address is the TCP peer unless ITOPS_SECURITY_TRUST_PROXY is set, in which case X-Real-IP is used. Read the ingress warning before trusting the proxy. Production also forces the security headers and HSTS.
Audit
Every ticket keeps its status, assignment and field history. Every failed API-key check lands in the SLA event log as auth_failed. User and group changes are logged by the core. There is no separate immutable audit store in this release.