Hive
feat(server,kura): authorize tenant-scoped cache via OAuth introspection
GitHub issue · Closed
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_idwith an optionalnamespace_id. Whennamespace_idis omitted, the request is tenant-scoped and Kura stores it under its internal empty-namespace key. - Tuist adds first-class
account:cache:readandaccount:cache:writescopes and derives explicitcache_grantsfor users, account tokens, and project tokens. - Tuist-issued JWTs now include
cache_grants, and the OAuth metadata document advertisesPOST /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
introspectgrant. - The Tuist Kura hook authorizes the hot path from JWT
cache_grants, falls back to OAuth introspection when needed, and keeps/api/cache/accessonly 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:readoraccount:cache:writefor 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/accessuntil introspection is configured everywhere.
How to test locally
cd server && mise x elixir@1.19.5 erlang@28.4.3 -- mix formatcd kura && mise x rust@1.94.1 -- cargo fmtcd 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.exscd 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.exscd 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.exshelm 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.comhelm template tuist infra/helm/tuist -n tuist-kura-controller -f infra/helm/tuist/values-managed-kura-region.yaml --set kuraController.image.tag=sha-testhelm 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.comhelm lint infra/helm/tuist -f infra/helm/tuist/values-managed-kura-region.yaml --set kuraController.image.tag=sha-testcd kura && mise exec -- cargo test tuist_hook -- --nocapturecd kura && mise exec -- cargo test tenant_only_xcode_routes_work_through_router -- --nocapturecd kura && mise exec -- cargo test tenant_only_xcode_routes_skip_project_scoped_analytics_events -- --nocapturecd kura && mise exec -- cargo test extension_context_omits_namespace_for_tenant_scoped_requests -- --nocapturecd kura && mise exec -- cargo test clean_namespace_removes_existing_tenant_scoped_artifacts -- --nocapturecd kura && mise exec -- cargo test scripts_can_read_environment_variables -- --nocapturecd 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.shBlocked locally because the Docker daemon stopped responding todocker composeanddocker infoduring the e2e setup path.