diff --git a/docs/features/kubernetes/proxy.md b/docs/features/kubernetes/proxy.md index 08d80d3e30..b53dfeb29d 100644 --- a/docs/features/kubernetes/proxy.md +++ b/docs/features/kubernetes/proxy.md @@ -22,8 +22,8 @@ import { discoveryApiRef, googleAuthApiRef, useApi, + identityApiRef, } from '@backstage/core-plugin-api'; -import { getBearerTokenFromAuthorizationHeader } from '@backstage/plugin-auth-node'; const CLUSTER_NAME = ''; // use a known cluster name @@ -33,10 +33,9 @@ const token = await googleAuthApi.getAccessToken( 'https://www.googleapis.com/auth/cloud-platform', ); -// get a bearer token from backstage `/auth` -const userToken = getBearerTokenFromAuthorizationHeader( - req.header('authorization'), -); +// get a backstage ID token +const identityApi = useApi(identityApiRef); +const { token: userToken } = await identityApi.getCredentials(); const discoveryApi = useApi(discoveryApiRef); const kubernetesBaseUrl = await discoveryApi.getBaseUrl('kubernetes'); @@ -56,18 +55,19 @@ await fetch(`${kubernetesProxyEndpoint}/api/v1/namespaces`, { ## How it works The proxy will interpret the -[`Backstage-Kubernetes-Cluster` -header](https://backstage.io/docs/reference/plugin-kubernetes-backend.header_kubernetes_cluster) -as the name of the cluster to target. This name will be compared to each cluster +[`Backstage-Kubernetes-Cluster`](https://backstage.io/docs/reference/plugin-kubernetes-backend.header_kubernetes_cluster) +header as the name of the cluster to target. This name will be compared to each cluster returned by all the configured [cluster locators](https://backstage.io/docs/features/kubernetes/configuration#clusterlocatormethods) -- the first cluster whose [`name` field](https://backstage.io/docs/features/kubernetes/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. +Then the request will be forwarded to the cluster. -The proxy will also interpret the `Backstage-Kubernetes-Authorization` header as the `Authorization` header to use when forwarding a request to a target cluster. +Overall, the only changes to each request are: + +- the endpoint's base URL prefix is stripped. +- the `Backstage-Kubernetes-Authorization` header becomes the `Authorization` header that is used when forwarding the request. ## Authentication @@ -82,9 +82,59 @@ providers](https://backstage.io/docs/features/kubernetes/authentication#client-s 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. +authentication strategy.\ +The current `/proxy` Implementation expects a +[Bearer +token](https://kubernetes.io/docs/reference/access-authn-authz/authentication/#putting-a-bearer-token-in-a-request) +to be provided as a `Backstage-Kubernetes-Authorization` header for a target cluster. This token will be used as the `Authorization` header when forwarding a request to a target cluster. + +## How to disable the proxy endpoint via PermissionPolicy + +The kubernetes plugin can disable the use of the `proxy` endpoint by leveraging the permission framework. This integration allows admins to use well defined PermissionPolicies to restrict the use of the endpoint all together. The `proxy` endpoint can return 403 errors even if it has a valid ID token attached that a cluster would authorize thus allowing integrators the confidence that Backstage is not accessing kubernetes clusters on behalf of undesired parties. + +This feature assumes your backstage instance has enabled the [permissions framework](https://backstage.io/docs/permissions/getting-started) + +A sample policy like: + +[packages/backend/src/plugins/permissions.ts](https://github.com/backstage/backstage/blob/master/packages/backend/src/plugins/permission.ts) + +```typescript +import { BackstageIdentityResponse } from '@backstage/plugin-auth-node'; +import { + AuthorizeResult, + PolicyDecision, +} from '@backstage/plugin-permission-common'; +import { + PermissionPolicy, + PolicyQuery, +} from '@backstage/plugin-permission-node'; + +class KubernetesDenyAllProxyEndpointPolicy implements PermissionPolicy { + async handle( + request: PolicyQuery, + user?: BackstageIdentityResponse, + ): Promise { + if (request.permission.name === 'kubernetes.proxy') { + return { + result: AuthorizeResult.DENY, + }; + } + return { result: AuthorizeResult.ALLOW }; + } +} +``` + +would leverage the permission framework to return the following response: + +```json +{ + "error": { + "name": "NotAllowedError" + } +} +``` + +even if a valid ID token was attached that a cluster would authorize. ## Other known limitations