elb-openapi ServiceAccount → cluster-admin (BLAST submit RBAC fix)¶
Motivation¶
BLAST jobs submitted through the OpenAPI execution plane (POST /v1/jobs,
served by the elb-openapi pods on AKS) failed at runtime. The dashboard
showed a FAILED blastn core_nt badge, and the job status carried:
Root cause (confirmed via live pod logs)¶
elastic-blast submit runs inside the elb-openapi pod as the
elb-openapi-sa ServiceAccount. The pod's /app/elastic-blast.log showed:
kubectl --context=incluster apply -f .../templates/elb-janitor-rbac.yaml
Error from server (Forbidden): clusterrolebindings.rbac.authorization.k8s.io
"elb-janitor-rbac" is forbidden: User "system:serviceaccount:default:elb-openapi-sa"
cannot get resource "clusterrolebindings" in API group "rbac.authorization.k8s.io"
at the cluster scope
elastic-blast submit applies a broad set of cluster-scoped objects on every
submit:
- a janitor
ClusterRoleBinding(elb-janitor-rbac.yaml) that binds the default ServiceAccount to the built-incluster-adminClusterRole, - a
create-workspaceDaemonSet inkube-system, - PersistentVolumes + a StorageClass,
- the per-batch BLAST Jobs.
The shipped elb-openapi-role ClusterRole only granted
nodes/pods/configmaps/services (core), batch/jobs, and apps/deployments
(read-only). Every openapi-driven core_nt submit therefore marched through a
cascade of 403s — clusterrolebindings forbidden → serviceaccounts
forbidden → daemonsets forbidden — and never created any BLAST Jobs, so the
watchdog marked the job failed. Only terminal/CLI submissions (which carry the
cluster-admin kubeconfig) had ever succeeded.
Scoping below cluster-admin is also security theater here: to apply the
janitor binding the SA must hold bind/escalate on the cluster-admin
ClusterRole, which already lets it grant itself cluster-admin at will. The
elb-openapi pod is internal-only (private LoadBalancer, no public ingress)
and is the trusted BLAST control plane that runs elastic-blast submit, so a
direct cluster-admin binding is both the honest representation of its
privilege level and the only configuration that keeps pace with
elastic-blast's evolving manifest set without whack-a-mole RBAC patches.
User-facing change¶
OpenAPI-driven BLAST submits against partitioned databases (e.g. core_nt)
now run to completion instead of failing at submit. No UI change.
API / IaC diff summary¶
api/tasks/openapi/manifests.pybuild_manifests: removed the narrow customelb-openapi-roleClusterRole (and dropped it from the manifest document list); repointed theelb-openapi-bindingClusterRoleBindingroleRefto the built-incluster-adminClusterRole.api/tests/test_openapi_task.py: replaced the (interim) custom-role assertion withtest_build_manifests_grants_janitor_rbac_permissions, which asserts the binding targetscluster-admin, bindselb-openapi-sa, and that the redundantelb-openapi-roleClusterRole is no longer emitted.
Live remediation (applied to elb-cluster-02)¶
The running cluster was unblocked immediately without a full redeploy:
kubectl delete clusterrolebinding elb-openapi-binding # roleRef is immutable
kubectl apply -f - # recreated binding → cluster-admin
kubectl delete clusterrole elb-openapi-role # orphaned by the change
The next api.tasks.openapi.deploy.deploy_openapi_service run emits the same
cluster-admin binding, so the live state and the manifest builder agree.
Validation evidence¶
- The previously-failing janitor apply now succeeds from inside the pod:
kubectl --context=incluster apply -f .../elb-janitor-rbac.yaml→clusterrolebinding.rbac.authorization.k8s.io/elb-janitor-rbac unchanged. - End-to-end submit of a
blastn/core_ntjob through the pod'sPOST /v1/jobstransitioneddispatching → submitting → running → **completed**(job092eb3103fdb), confirming BLAST Jobs are created and finish. uv run pytest -q api/tests/test_openapi_task.py api/tests/test_openapi_deploy_contract.py api/tests/test_smoke.py→ 98 passed.uv run ruff check api/tasks/openapi/manifests.py api/tests/test_openapi_task.py→ clean.
Out of scope (sibling repo)¶
A separate docker-openapi (sibling elastic-blast-azure) default surfaced
once RBAC was fixed: Mode B submits to a partitioned DB without an explicit
blast_options.outfmt fall back to -outfmt 7, which Partitioned BLAST
rejects at merge ("7 is not supported for merge"). The dashboard's own UI
submit already sends outfmt 5, so the UI flow is unaffected; the raw OpenAPI
Model B example that omits outfmt should set outfmt to 5 or 6. Fixing
the sibling default requires rebuilding the elb-openapi image and is tracked
separately.