Install
$ agentstack add skill-bendaamerahmed-backstage-idp-plugin-backstage-kubernetes ✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
Verified badge
Passed review? Show it. Paste this badge into your README, it links to the public security report.
Reliability & compatibility
Declared compatibility
Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.
We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.
How agent discovery & health will work →About
Backstage Kubernetes
Make a service's real cluster workloads — including your own custom resources — appear on its entity page. Four things must line up: the cluster is reachable, Backstage is authorised on it, the entity is annotated, and the object type is requested. A miss on any one shows as an empty tab.
Preconditions
- Backend generation known. New backend system:
backend.add(import('@backstage/plugin-kubernetes-backend')) in packages/backend/src/index.ts. Legacy: a createRouter under packages/backend/src/plugins/, which must be migrated before adding modules (backstage-plugin-migrate). Frontend: NFS consumes the plugin's /alpha export; legacy imports EntityKubernetesContent into the entity page.
- Cluster reachability from the backend, not from your laptop. A cluster
behind a VPN the backend pod cannot reach fails identically to a wrong URL.
- Exact config shapes read from the installed
@backstage/plugin-kubernetes-backend config.schema.json, and exact annotation constants from @backstage/plugin-kubernetes-common. Both move.
- Read access to the cluster's RBAC, because surfacing a CRD needs an explicit
grant and that grant is usually the missing piece.
- Any change to a real cluster's RBAC or service accounts is external mutation:
stop and return a BLOCKED report with the exact Role and ClusterRole rules needed, rather than applying them.
Procedure
- Establish which of the four layers is missing before editing config.
Query the backend directly: POST /api/kubernetes/services/ with a user token. A 200 with an empty array is an annotation or selector problem; a 500 naming a cluster is reachability or auth; a 404 means the plugin is not wired at all.
- Choose a cluster locator method.
configlists clusters inline and is
right for a handful. catalog reads them from Resource entities of type kubernetes-cluster, which scales and keeps cluster inventory in the catalog. gke discovers GKE clusters in a project. localKubectlProxy is development only — it assumes kubectl proxy on the developer's machine and must never reach a deployed config.
- Declare clusters under
kubernetes.clusterLocatorMethods. Per cluster the
fields are url, name, and one of the auth arrangements: a serviceAccountToken, or an authProvider with authMetadata. Optional: title, skipTLSVerify, skipMetricsLookup, caData, caFile, oidcTokenProvider, and a per-cluster customResources. Every credential is ${ENV_VAR} or a $env/$file reference — never a literal.
- Pick
serviceLocatorMethoddeliberately.multiTenantqueries every
cluster for every entity and is the usual default. singleTenant and catalogRelation narrow that. On a large fleet multiTenant is a latency and rate-limit problem before it is a correctness one.
- Annotate the entities.
backstage.io/kubernetes-idmatches objects
carrying the backstage.io/kubernetes-id label with the same value — the label goes on your workloads, in your manifests or Helm chart, and is the half people forget. backstage.io/kubernetes-label-selector replaces that with an arbitrary selector and wins when both are present. Namespace can be narrowed with the namespace annotation. Validate with backstage-catalog.
- Request the object types you want.
kubernetes.objectTypesaccepts
pods, services, configmaps, deployments, limitranges, resourcequotas, replicasets, horizontalpodautoscalers, jobs, cronjobs, ingresses, statefulsets, daemonsets and customresources. Setting it at all replaces the default set — a list that omits pods hides pods, which reads as a broken plugin.
- Declare each CRD you want surfaced in
kubernetes.customResourcesas
{ group, apiVersion, plural }. All three are required, apiVersion is the version alone (not group/version), and plural is the CRD's spec.names.plural — not the Kind. Declare them per cluster instead when only some clusters have the operator installed. See kubernetes-crd-author when the CRD is one you own.
- Grant RBAC for the custom resources. The default guidance grants the core
workload types only; a CRD needs its own apiGroups/resources rule with get, list and watch. This is the most common reason a correctly declared CRD stays invisible. Prepare the ClusterRole and stop for authorization before anything is applied to a real cluster.
- Restrict what reaches the frontend. The plugin returns whole objects, so a
Secret-adjacent CRD or one with credentials in spec exposes them to every user who can see the entity. Decide per CRD whether it should be surfaced at all, and treat that as a data-exposure review, not a config change.
- Wire the frontend for the repo's generation and confirm the tab renders
for an entity you know has workloads, not for an arbitrary one.
Verification
yarn backstage-cli config:check --strictpasses with the new keys.POST /api/kubernetes/services/returns the expected clusters, and for
each the resource types you requested. This is the contract; the UI is a view of it.
- An entity with a deliberately wrong
kubernetes-idreturns an empty array —
proving the selector is doing something rather than everything matching.
- Each declared CRD appears in the response for an entity that has one. If the
response omits it while kubectl get succeeds with the backend's own credentials, the gap is RBAC.
yarn backstage-cli config:print --frontendshows no cluster credential.- The Kubernetes tab renders for a known-good entity in both light and dark
themes; the error and empty states are separate components and only one gets looked at.
Failure modes
- Kubernetes tab is empty, no error. In order: the entity is not annotated;
the workloads do not carry the matching label; the namespace is excluded; the object type was dropped by an objectTypes list. Query the API directly to tell these apart — the UI renders all four identically.
- Custom resource never appears though the CRD is declared. RBAC. The backend
identity can list Deployments and cannot list your CRD, and the plugin reports no error for a type it was refused.
apiVersiongiven asgroup/version. The field takes the version alone
and group is separate. The result is a silent no-match, not a validation error.
pluralset to the Kind. It isspec.names.pluralfrom the CRD,
lowercase, usually the plural noun. kubectl get crd -o jsonpath='{.spec.names.plural}' settles it.
- 401 or 403 from one cluster only. A per-cluster token expired, or its
service account lost a binding. The plugin surfaces this per cluster; read the backend logs rather than the tab.
- Works locally, fails deployed.
localKubectlProxyreached a laptop, or the
in-cluster service account differs from the one your kubeconfig used.
- Every entity shows every workload. A
kubernetes-label-selectorbroad
enough to match everything, or a label applied cluster-wide by a mutating webhook.
- Catalog locator returns nothing. The cluster
Resourceentities are absent
or the wrong type; the locator matches on entity type, so a typo yields zero clusters and an empty tab rather than an error.
- Backend slow or rate-limited after adding clusters.
multiTenantqueries
every cluster for every entity page. Narrow the service locator before adding more clusters.
Do not
- Do not apply RBAC, create service accounts, or rotate cluster credentials.
Prepare the manifests, report them, and stop for authorization.
- Do not put a service account token, kubeconfig or CA blob literally in
app-config.yaml.
- Do not enable
skipTLSVerifyoutside local development, and never leave it in
a config that a deployed environment loads.
- Do not ship
localKubectlProxyin any config a deployed backend reads. - Do not surface a CRD whose objects carry credentials or personal data without
an explicit decision recorded in your report.
- Do not set
objectTypesto add one type without re-listing the types you still
want; it replaces rather than extends.
- Do not guess a CRD's
group,apiVersionorplural. Read them from the
installed CRD, or return BLOCKED naming the cluster you need access to.
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: bendaamerahmed
- Source: bendaamerahmed/backstage-idp-plugin
- License: MIT
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet, be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.