Skip to content

Tenancy

EvalHub uses Kubernetes namespaces as tenant boundaries. Each tenant operates in its own namespace, and access is enforced through Kubernetes RBAC via SubjectAccessReview (SAR) checks.

The TrustyAI Operator controls deployment behaviour via spec.tenancy on the EvalHub custom resource. Two modes are available:

multi (default)single
EvalHub placementControl-plane namespace (e.g. opendatahub)Workload namespace
Tenant discoveryLabel evalhub.trustyai.opendatahub.io/tenant on namespacesNone — serves own namespace only
Cross-namespace RBACOperator provisions job SA + RoleBindings per tenantNot performed
Convenience RolesNot createdevalhub-tenant-admin, evalhub-user, admin binding
InvalidPlacement guardYes — cannot deploy in tenant-labelled NSNo — may deploy anywhere
DatabasePostgreSQL recommendedSQLite acceptable for dev/small teams

The core principle in both modes is: namespace = tenant = MLFlow workspace.

One EvalHub instance in a control-plane namespace serves multiple tenant namespaces.

  1. Authentication — Bearer tokens are validated via the Kubernetes TokenReview API. The token can belong to a ServiceAccount, an OpenShift User, or a member of an OpenShift Group — EvalHub treats all identically at this stage.
  2. Authorisation — Every API request includes an X-Tenant header specifying the target namespace. EvalHub runs a SAR check: “can this principal perform this action in that namespace?”
  3. Data isolation — All database queries are filtered by tenant_id
  4. Job isolation — Evaluation jobs run in the tenant namespace with a scoped ServiceAccount

EvalHub uses an embedded SAR authoriser. The auth config (config/auth.yaml) maps API endpoints to Kubernetes resource attributes:

EndpointResourceVerbNamespace source
POST /api/v1/evaluations/jobsevaluationscreateX-Tenant header
GET /api/v1/evaluations/jobsevaluationsgetX-Tenant header
POST /api/v1/evaluations/jobs/*/eventsstatus-eventscreateX-Tenant header
* /api/v1/evaluations/collectionscollections(from HTTP method)X-Tenant header
* /api/v1/evaluations/providersproviders(from HTTP method)X-Tenant header

For POST /api/v1/evaluations/jobs, two additional SAR checks are performed for MLFlow access (mlflow.kubeflow.org/experiments with create and get verbs).

The flow is identical for all principal types. The only difference is what the TokenReview returns and which subject kind is matched in the RoleBinding.

Note that the job pod always runs as the operator-provisioned job ServiceAccount (e.g. evalhub-opendatahub-job), regardless of whether the submitter was a User, Group member, or a ServiceAccount.

The operator installs these ClusterRoles cluster-wide. Mode-specific Role and RoleBinding details are documented on the setup pages linked below.

ClusterRolePurposeKey permissions
evalhub-auth-reviewer-roleToken and SAR validationtokenreviews, subjectaccessreviews
evalhub-jobs-writerCreate evaluation jobsbatch/jobs (create, delete)
evalhub-job-configManage job configconfigmaps (create, get, update, delete)
evalhub-providers-accessProviders endpoint SARproviders (get)
evalhub-collections-accessCollections endpoint SARcollections (get)
evalhub-mlflow-accessAPI server MLFlow accessexperiments (create, get, list, update, delete)
evalhub-mlflow-jobs-accessJob pod MLFlow accessexperiments (create, get, list)

You can change spec.tenancy on a running EvalHub instance. The operator reconciles the difference automatically:

TransitionOperator behaviour
singlemultiRemoves convenience Roles; provisions cross-namespace RBAC in tenant-labelled namespaces
multisingleCleans up cross-namespace resources; creates convenience Roles in the instance namespace

The SAR check is failing. Verify:

  1. The X-Tenant header matches a namespace where the principal has a RoleBinding
  2. The Role includes the correct resources and verbs
  3. The RoleBinding subjects[].kind matches the principal type (ServiceAccount, User, or Group)
Terminal window
oc auth can-i create evaluations.trustyai.opendatahub.io \
-n team-a \
--as=system:serviceaccount:team-a:team-a-user -v=6

All evaluation API requests require the X-Tenant header, even in single-tenancy mode where the tenant is always the EvalHub instance namespace. Requests without the header are rejected.