diff --git a/.changeset/tidy-kings-cough.md b/.changeset/tidy-kings-cough.md new file mode 100644 index 0000000000..e908572c8b --- /dev/null +++ b/.changeset/tidy-kings-cough.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-kubernetes-backend': patch +--- + +The name of the header used to specify a cluster to the proxy endpoint is now visible in the API reference. diff --git a/docs/features/kubernetes/proxy.md b/docs/features/kubernetes/proxy.md new file mode 100644 index 0000000000..a98cdfd16b --- /dev/null +++ b/docs/features/kubernetes/proxy.md @@ -0,0 +1,90 @@ +--- +id: proxy +title: Kubernetes Backend Proxy Endpoint +sidebar_label: Proxy +description: Interacting with the Kubernetes API in Backstage plugins +--- + +[Contributors](../../overview/glossary#backstage-user-profiles) wanting to +create developer portal experiences based on data from Kubernetes (e.g. for +interacting with [Custom +Resources](https://kubernetes.io/docs/concepts/extend-kubernetes/api-extension/custom-resources/) +beyond the default behaviors of the existing Kubernetes plugin) can leverage the +Kubernetes backend plugin's proxy endpoint to allow them to make arbitrary +requests to the [REST +API](https://kubernetes.io/docs/reference/using-api/api-concepts/). + +Here is a snippet fetching namespaces from a cluster configured with the +`google` [auth provider](./configuration#clustersauthprovider): + +```typescript +import { + discoveryApiRef, + googleAuthApiRef, + useApi, +} from '@backstage/core-plugin-api'; + +const CLUSTER_NAME = ''; // use a known cluster name + +// get a bearer token from Google +const googleAuthApi = useApi(googleAuthApiRef); +const token = await googleAuthApi.getAccessToken( + 'https://www.googleapis.com/auth/cloud-platform', +); + +const discoveryApi = useApi(discoveryApiRef); +const kubernetesBaseUrl = await discoveryApi.getBaseUrl('kubernetes'); +const kubernetesProxyEndpoint = `${kubernetesBaseUrl}/proxy`; + +// fetch namespaces +await fetch(`${kubernetesProxyEndpoint}/api/v1/namespaces`, { + method: 'GET', + headers: { + 'X-Kubernetes-Cluster': CLUSTER_NAME, + Authorization: `Bearer ${token}`, + }, +}); +``` + +## How it works + +The proxy will interpret the +[`X-Kubernetes-Cluster` +header](../../reference/plugin-kubernetes-backend.header_kubernetes_cluster) +as the name of the cluster to target. This name will be compared to each cluster +returned by all the configured [cluster +locators](./kubernetes/configuration#clusterlocatormethods) +-- the first cluster whose [`name` field](./configuration#clustersname) matches +the value in the header will be targeted. + +Then the request will be forwarded verbatim (but with the endpoint's base URL +prefix stripped) to the cluster. + +## Authentication + +Until some security and permission decisions are made (see [this +conversation](https://github.com/backstage/backstage/pull/13026/files#r1029376939) +for context), contributors consuming the proxy endpoint in their plugin code are +responsible for negotiating their own bearer token out-of-band. This requires +knowing some auth details about the cluster being contacted -- in practice, only +clusters with [client side auth +providers](./authentication#client-side-providers) can reasonably be reached. + +The proxy has no provisions for mTLS, so it cannot be used to connect to +clusters using the [x509 Client +Certs](https://kubernetes.io/docs/reference/access-authn-authz/authentication/#x509-client-certs) +authentication strategy. [Bearer +tokens](https://kubernetes.io/docs/reference/access-authn-authz/authentication/#putting-a-bearer-token-in-a-request) +will be forwarded as-is. + +## Other known limitations + +The proxy as it was released in [Backstage +1.9](https://github.com/backstage/backstage/blob/master/docs/releases/v1.9.0-changelog.md#patch-changes-15) +has a few known bugs: + +- [#15901](https://github.com/backstage/backstage/issues/15901) - it cannot + reliably target clusters who share the same name with another located cluster. +- [#16018](https://github.com/backstage/backstage/issues/16018) - it does not + forward request bodies, making it incapable of supporting `POST`, `PUT` or + `PATCH` verbs in practice. diff --git a/microsite/sidebars.json b/microsite/sidebars.json index f051d3c70b..6baeb20e8b 100644 --- a/microsite/sidebars.json +++ b/microsite/sidebars.json @@ -87,7 +87,8 @@ "features/kubernetes/installation", "features/kubernetes/configuration", "features/kubernetes/authentication", - "features/kubernetes/troubleshooting" + "features/kubernetes/troubleshooting", + "features/kubernetes/proxy" ] }, { diff --git a/plugins/kubernetes-backend/api-report.md b/plugins/kubernetes-backend/api-report.md index 4ec043a8f9..006f980c56 100644 --- a/plugins/kubernetes-backend/api-report.md +++ b/plugins/kubernetes-backend/api-report.md @@ -141,7 +141,7 @@ export class GoogleServiceAccountAuthTranslator ): Promise; } -// @alpha +// @public export const HEADER_KUBERNETES_CLUSTER: string; // @alpha (undocumented) diff --git a/plugins/kubernetes-backend/src/service/KubernetesProxy.ts b/plugins/kubernetes-backend/src/service/KubernetesProxy.ts index 9d88d2ab84..cb36c647ac 100644 --- a/plugins/kubernetes-backend/src/service/KubernetesProxy.ts +++ b/plugins/kubernetes-backend/src/service/KubernetesProxy.ts @@ -32,7 +32,7 @@ export const APPLICATION_JSON: string = 'application/json'; /** * The header that is used to specify the cluster name. * - * @alpha + * @public */ export const HEADER_KUBERNETES_CLUSTER: string = 'X-Kubernetes-Cluster';