Hive Hive
Sign in

feat(server,kura): authorize tenant-scoped cache via OAuth introspection

GitHub issue · Closed

Metadata
Source
tuist/tuist #10935
Updated
Jun 24, 2026
Domains
Kura
Details

Resolves N/A

This PR adds tenant-scoped cache authorization for Tuist-managed Kura deployments so Tuist can support account-scoped binaries.

Final behavior:

  • Kura HTTP cache routes accept tenant_id with an optional namespace_id. When namespace_id is omitted, the request is tenant-scoped and Kura stores it under its internal empty-namespace key.
  • Tuist adds first-class account:cache:read and account:cache:write scopes and derives explicit cache_grants for users, account tokens, and project tokens.
  • Tuist-issued JWTs now include cache_grants, and the OAuth metadata document advertises POST /oauth2/introspect, which returns the same grant model for Kura when local JWT claims are not enough.
  • Kura uses a dedicated OAuth introspection client instead of reusing the CLI/app OAuth client. The CLI/app client no longer advertises the introspect grant.
  • The Tuist Kura hook authorizes the hot path from JWT cache_grants, falls back to OAuth introspection when needed, and keeps /api/cache/access only as a compatibility fallback for existing project-scoped traffic while introspection is being rolled out.
  • Tenant-scoped HTTP traffic skips project-scoped analytics webhooks, while namespace-scoped traffic keeps the existing webhook behavior.

Important

Kura authenticates to /oauth2/introspect with a dedicated OAuth client ID and secret because introspection reveals whether arbitrary bearer tokens are active and which cache grants they carry. The bearer token still authenticates the user, account token, or project token being checked. The client credential authenticates Kura itself as the trusted resource server allowed to perform that check. Operationally, the production Helm overlay syncs TUIST_KURA_INTROSPECTION_CLIENT_ID and TUIST_KURA_INTROSPECTION_CLIENT_SECRET from the kura-introspection-oauth-client 1Password item into the server ExternalSecret. The production and regional Kura overlays merge KURA_EXTENSION_TUIST_INTROSPECT_CLIENT_SECRET from the same item into kura-shared-secrets, while the server injects KURA_EXTENSION_TUIST_INTROSPECT_CLIENT_ID into each Kura instance. Without that secret, Kura keeps using /api/cache/access for project-scoped compatibility traffic, but account-scoped binaries require introspection.

User and developer impact:

  • Tuist can now support binaries that are scoped only to the account instead of forcing a project namespace.
  • Users and account tokens can upload and download binaries scoped only to the tenant.
  • Account tokens need account:cache:read or account:cache:write for tenant-scoped cache access.
  • Project tokens remain project-scoped and do not gain tenant-wide cache access.
  • Existing project-scoped cache traffic keeps working during rollout because Kura can still fall back to /api/cache/access until introspection is configured everywhere.

How to test locally

  • cd server && mise x elixir@1.19.5 erlang@28.4.3 -- mix format
  • cd kura && mise x rust@1.94.1 -- cargo fmt
  • cd server && mise x elixir@1.19.5 erlang@28.4.3 -- env MIX_ENV=test mix test test/tuist/authentication_test.exs test/tuist/oauth/token_generator_test.exs test/tuist/kura/provisioner/kubernetes_controller_test.exs test/tuist_web/controllers/well_known_controller_test.exs test/tuist_web/controllers/oauth/introspect_controller_test.exs
  • cd server && mise x elixir@1.19.5 erlang@28.4.3 -- env MIX_ENV=test mix test test/tuist_web/controllers/api/cache_controller_test.exs test/tuist_web/controllers/webhooks/cache_controller_test.exs test/tuist_web/controllers/webhooks/gradle_cache_controller_test.exs
  • cd server && mix test test/tuist/oauth/clients_test.exs test/tuist/kura/provisioner/kubernetes_controller_test.exs test/tuist_web/controllers/oauth/introspect_controller_test.exs
  • helm template tuist infra/helm/tuist -n tuist -f infra/helm/tuist/values-managed-common.yaml -f infra/helm/tuist/values-managed-production.yaml --set server.image.tag=sha-test --set kuraController.image.tag=sha-test --set kuraRuntime.image.tag=sha-test --set clickhouse.external.url=http://clickhouse.example.com
  • helm template tuist infra/helm/tuist -n tuist-kura-controller -f infra/helm/tuist/values-managed-kura-region.yaml --set kuraController.image.tag=sha-test
  • helm lint infra/helm/tuist -f infra/helm/tuist/values-managed-common.yaml -f infra/helm/tuist/values-managed-production.yaml --set server.image.tag=sha-test --set kuraController.image.tag=sha-test --set kuraRuntime.image.tag=sha-test --set clickhouse.external.url=http://clickhouse.example.com
  • helm lint infra/helm/tuist -f infra/helm/tuist/values-managed-kura-region.yaml --set kuraController.image.tag=sha-test
  • cd kura && mise exec -- cargo test tuist_hook -- --nocapture
  • cd kura && mise exec -- cargo test tenant_only_xcode_routes_work_through_router -- --nocapture
  • cd kura && mise exec -- cargo test tenant_only_xcode_routes_skip_project_scoped_analytics_events -- --nocapture
  • cd kura && mise exec -- cargo test extension_context_omits_namespace_for_tenant_scoped_requests -- --nocapture
  • cd kura && mise exec -- cargo test clean_namespace_removes_existing_tenant_scoped_artifacts -- --nocapture
  • cd kura && mise exec -- cargo test scripts_can_read_environment_variables -- --nocapture
  • cd kura && mise exec -- cargo clippy --all-targets -- -D warnings
  • Attempted: cd kura && env KURA_E2E_SKIP_BUILD=1 mise x shellspec@0.28.1 -- shellspec spec/e2e/tenant_scope_spec.sh Blocked locally because the Docker daemon stopped responding to docker compose and docker info during the e2e setup path.
Comments
TA
tuist-atlas[bot] Jun 3, 2026

The tenant-scoped cache authorization via OAuth introspection is now available in kura@0.6.0. Update to get these improvements.