Skip to main content

Control catalog

One row per control objective: the principle it serves (security/principles.md), the mechanism that implements it, and the query that produces its evidence. This is the system description an ISO 27001 Statement of Applicability and a SOC 2 report map to. A row changes only when the code does; design.md §3.0 holds the finer-grained list of mechanisms.

An evidence query is what an auditor runs, or is shown, to pull the records of an arbitrary week.

#Control objectivePrincipleMechanismEvidence query
K1Changes reach a cluster only through a reviewed, CI-checked pull request9Rulesets on main and develop of gentian-os, gentian-ui and gentian-apps: pull request, thread resolution, required status checks, no bypass actors; release tags immutable; the organization requires a second factor of every member and defaults members to read; CODEOWNERS in each repository; third-party Actions pinned by commitgh api repos/gentian-org/<repo>/rulesets; gh api orgs/gentian-org (two_factor_requirement_enabled, default_repository_permission); gh pr list --state merged with each PR's checks
K2Every change to declared state names the person and the relation that allowed it7, 9The director commits to the deployments repository with a Gentian-Authz: trailer and an OpenPGP signature; Argo CD verifies through AppProject.spec.sourceIntegritygit log --show-signature on the deployments repository; argocd app get showing the signature policy
K3Images are built, signed and attested by CI9Immutable image tags published by digest; every image signed keyless with the building workflow's identity and carrying an SBOM and a provenance attestation; the signature is written in cosign's legacy form (the sha256-<digest>.sig tag), which is the form Kyverno 1.13 reads; Kyverno checks the signature on ghcr.io/gentian-org/* in tenant namespaces in Audit mode, so a failure is reported and not refused; profiles pin bundles by digestcosign verify --certificate-oidc-issuer https://token.actions.githubusercontent.com --certificate-identity-regexp 'https://github.com/gentian-org/' <image>; kubectl get policyreport -A; kubectl get pods -o jsonpath='{..imageID}'
K4Third-party code is scanned and known vulnerabilities are reported and updated9CodeQL default setup; Dependabot alerts, security updates and weekly grouped version updates for every ecosystem, against develop, the default branch; Actions and base images pinned by commit or digest; builds and tests install from the lockfiles (uv sync --locked, npm ci); govulncheck, pip-audit, npm audit and Trivy in CI, reporting without failing the rungh api repos/gentian-org/<repo>/code-scanning/alerts; gh api repos/gentian-org/<repo>/dependabot/alerts with fixed_at; the audit jobs' logs
K5Images are scanned before release9Trivy on every published image, high and critical, in the publish jobs, reporting without blockingThe Scan the image step of each publish run; gh run view <id> --log
K6Identity is a verified claim set from a known issuer1Keycloak realm per tenant and a kernel realm; RS256 verification against the issuer's keys in every service; the gateway's OIDC session in front of every authMode: oidc routeRealm export; kubectl get securitypolicy -A
K7Every request to a protected route is decided by policy, and the decision fails closed2, 3OpenFGA model (authz/model/v1/model.fga) with CI tests; Check in bouncer, director, custodian, usher and registrar; failOpen: false at the gatewayCI run of the authorization policy tests; the model's tests
K8Administrators enroll a second factor1A sign-in flow in every realm requires a second factor of anyone in a group marked gentianRequiresMFA, and enrolls them if they have noneRealm export: browserFlow and the group attribute; Keycloak: the account's credentials of type otp
K9Privileged credentials are few and kept in the vault, and external ones are rotated5, 8OpenBao holds kernel credentials; the custodian rotates external ones; automated callers sign in to Keycloak as gentian-operator; one OpenFGA key per kernel serviceOpenBao audit of the paths; rotation dates
K10Break-glass access is time-bound and recorded5, 7breakGlass role holder on the Cluster claim, with a reason and a required expiry, dropped by the operator at that timeThe claim's commits; break-glass audit records from the director
K11Changes to a person's identity and rights are recorded with who asked and what allowed it7Registrar record, 400-day retention (internal/registrar/record); Keycloak user and admin events on in tenant realmsselect * from actions where at between … on the registrar database; Keycloak events API
K12Who holds privileged access is reviewed on an interval and the review is recorded5kubectl gentian platform-roles review: the members of the admin and breakGlass groups and the role holders the Cluster claim names, confirmed by a person who may audit the cluster; the claim keeps spec.platformAccess.accessReview (who, when, the next date, a quarter by default) and the director answers reviewDue once it has passed; a tenant's own administrators are the tenant's to reviewgit log -p -- clusters/<cluster>/kernel/claims/cluster.yaml on the deployments repository, each review commit listing what was reviewed; kubectl gentian platform-roles holders for whether one is due
K13Tenants are isolated from each other and from the kernel6Namespace per tenant with default-deny ingress and egress; Kyverno pod-security policies in Enforce; LimitRange; per-tenant OpenBao store and policy; ingress NetworkPolicies on the kernel namespaces, on by default (KERNEL_NETWORK_POLICIES unset means on), generated into kernel/security/network-policies/kubectl get networkpolicy -n <tenant>; kubectl get networkpolicy -n kernel-control; kubectl get clusterpolicy; bao policy read <tenant>
K14Secrets are held in a vault and reach a workload only through its tenant's store8OpenBao with transit auto-unseal; External Secrets Operator with one store, role and policy per tenantkubectl get externalsecret -A; OpenBao policies
K15Secrets are encrypted at rest8Pre-flight reads the API server's encryption flag where it is visible and otherwise the Cluster claim's controlPlane.secretsAtRest with its evidence; scripts/tools/rewrite-secrets.sh re-encrypts Secrets written before the provider was switched onThe claim's controlPlane block and the commit that set it; the pre-flight line; on a self-hosted control plane, etcdctl get on one Secret key showing the provider prefix
K16Data in transit is encrypted6cert-manager with Let's Encrypt at the edge; internal CA from OpenBao PKI; kernel Postgres clients require TLS; Argo CD serves TLS checked by the Gateway; MariaDB offers TLSCertificate inventory; openssl s_client against each public host
K17Tenant data is backed up, encrypted and restorable8BackupPolicy, TenantExport, TenantRestore: age-encrypted bundles, signed manifests, retention tierskubectl get tenantexport -A; the record of a restore
K18Security-relevant events are written and forwarded to a retaining destination7One JSON envelope per decision and change from the bouncer and the director, the registrar's identity record, Keycloak's user and admin events as JSON with a thirty-day expiry, and the gateway's JSON access log, joined by the request id (audit-events.md); the forwarder ships them to the destination the Cluster claim names (spec.logForwarding); retention and immutability are the destination's; the Kubernetes API audit log is the provider's, since the platform does not own the control plane and configures no audit policy of its ownThe destination's retention and lock settings and its signed manifests; one request id followed through the bouncer's, the director's and the gateway's records
K19Availability and capacity are monitored and alerted on—spec.monitoring.enabled installs kube-prometheus-stack with the platform's rules (audit-events.md §5)Alertmanager's alert history; the rules in kernel-observability
K20Deleting a tenant removes its data and leaves a record8App purge, tenant retire and delete with purge, mailbox archive or deleteDirector change log entry for the deletion
K22A vulnerability can be reported privately and is answered within a stated time—SECURITY.md in each repository; GitHub private vulnerability reporting enabledgh api repos/gentian-org/<repo>/private-vulnerability-reporting
K23The documentation describes the controls as they are—This catalog; design.md §3.0; the principlesReviewed at each release
K24The platform's own interfaces verify the person's token themselves and refuse to run open1, 3The desktop's and the administration console's backends verify RS256 against the issuer's keys with audience and expiry tests; the ComponentProfiles the platform ships inject issuer and audience from the vault, set ENVIRONMENT: production and expose every route with authMode: oidc, so the gateway's session and the bouncer (failOpen: false) stand in front of each backend and the tenant NetworkPolicy admits only the gateway; a backend refuses to start with AUTH_DISABLED in production, so the anonymous path exists only in a local environmentkubectl get componentprofile desktop admin-console -o yaml (the environment and the exposures); the backend's startup log; the tests in gentian-ui that assert the anonymous path needs the opt-out