Skip to content

feat(server): support more than one sandbox ServiceAccount per gateway #2806

Description

@bjw123

Description

The sandbox ServiceAccount is fixed gateway-wide. [openshell.drivers.kubernetes] service_account_name is a single value that serves two purposes at once:

  1. the driver stamps it onto every sandbox pod as serviceAccountName
    if !params.service_account_name.is_empty() {
  2. bootstrap auth requires the presented principal to match it exactly —
    if sa_name != expected_service_account {

So every sandbox on a gateway necessarily shares one Kubernetes identity. This asks for that to be configurable as a set, in the same spirit as the namespace-level validation added in #2656.

Context

The ServiceAccount is the unit of cloud IAM federation. On EKS the IAM trust policy conditions on the token subject:

StringLike  <oidc>:sub = "system:serviceaccount:<namespace>:<service-account>"

GCP Workload Identity and Azure Workload Identity bind the same way. So "one SA per gateway" means "one cloud identity per gateway": sandboxes doing different work, with different blast radii, cannot be granted different cloud permissions. Granting the union to the shared SA gives every sandbox the most-privileged set.

Notably the SA is the only pod-level field with no per-sandbox override. platform_config on the SandboxTemplate already carries runtime_class_name, node_selector, tolerations, annotations and host_users, including a documented per-sandbox-overrides-cluster-default precedent:

// Per-sandbox platform_config.host_users overrides the cluster-wide default.

Workarounds today, both unattractive:

  1. Share one SA and grant it the union of permissions — every sandbox gets every permission.
  2. Run a gateway per identity — preserves isolation, but duplicates the gateway, its database and its forwarder topology for what is a single config value.

Proposed Direction

Two complementary parts; either is useful, together they close the case:

  1. Accept a configured set of sandbox service accounts in bootstrap auth. A single value stays the default and keeps current behaviour.
  2. Allow the pod's ServiceAccount to be selected per sandbox — e.g. platform_config.service_account_name, constrained to the set from (1), defaulting to the existing single value.

They are separable. (2) without (1) cannot authenticate, so (1) is the prerequisite. (1) alone is already useful for deployments where something other than the gateway sets the pod's ServiceAccount — a mutating admission policy, or an external controller that owns the sandbox pods — which is the situation we hit: our pods are given a different SA out-of-band, and bootstrap then fails with

K8s TokenReview principal is not the configured sandbox service account

Whichever way (1) is implemented, it should be additive rather than a widening of service_account_name, because of consumer (1) in the Description: turning that field into a list or a delimited string would render invalid pods. The pod default and the accepted-for-auth set need to be distinct settings.

This overlaps #2023, which proposes making bootstrap config explicit and independent of the selected compute driver, and whose first Definition-of-Done item is defining the ownership model for the bootstrap namespace and service-account settings. That seems like the natural place to decide where an accepted set lives; happy to follow that issue's outcome rather than pre-empt it.

Security properties are unchanged: tokens are still verified through TokenReview, still required to be pod-bound (pod_name / pod_uid extras), and IssueSandboxToken still requires a K8sServiceAccount-sourced sandbox principal. This widens which identities an operator may enrol, not how they are verified — the accepted set stays operator-configured and closed.

Definition of Done

  • Bootstrap auth accepts a configured set of sandbox service accounts; a single value remains the default and is unchanged in behaviour.
  • The pod-default service account and the accepted-for-auth set are distinct settings, so the driver keeps stamping exactly one name onto pods.
  • Rejection logging identifies the presented principal and that it was not in the accepted set.
  • Tests: single configured SA (accept + reject), multiple configured SAs (each accepted, non-member rejected).
  • Helm chart can express the set, with the existing single-value setting still supported.
  • Gateway configuration documentation updated.

Environment

Observed on gateway 0.0.85; code references above are against main at 8d67250.

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:computearea:gatewayGateway server and control-plane workstate:acceptedA maintainer decided OpenShell should pursue this issuetest:e2e-kubernetesRequires Kubernetes end-to-end coverage

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions