backstage / backstage/backstage

๐Ÿ› Bug Report: ambiguously-named clusters cannot be targeted with the proxy endpoint

Open
#15,901 5 comments 0 reactions 0 assignees View on GitHub
area:kubernetes no stale type:bug
Dominant language
TypeScript
Stars
34.4k
Forks
7.6k
Avg merge
8h 57m
Merged PRs (30d)
50

Description

### ๐Ÿ“œ Description

If two clusters located by Backstage have the same name, proxy requests targeting that name will always be routed to the first one -- there's no way to target the second one! You can test this by `curl`ing the proxy endpoint for all namespaces, specifying the ambiguous cluster name, and providing a bearer token that is only valid for the second cluster.

### ๐Ÿ‘ Expected behavior

Well, depends on what you know. If you're only aware of the second cluster you might expect to see namespaces. If you know there are two ambiguously-named clusters, you might expect to see an error about this.

### ๐Ÿ‘Ž Actual Behavior with Screenshots

console prints
```text
{
"kind": "Status",
"apiVersion": "v1",
"metadata": {

},
"status": "Failure",
"message": "Unauthorized",
"reason": "Unauthorized",
"code": 401
}
```
This indicates that only the first cluster got queried -- since the token we used isn't valid for that cluster.

### ๐Ÿ‘Ÿ Reproduction steps

1. Create two kind clusters: `kind create cluster --name kind-1 && kind create cluster --name kind-2`
1. Grab their server URLs:
```bash
export KIND_1_URL=$(kubectl config view -o jsonpath='{.clusters[?(@.name == "kind-kind-1")].cluster.server}')
export KIND_2_URL=$(kubectl config view -o jsonpath='{.clusters[?(@.name == "kind-kind-2")].cluster.server}')
```
1. Get a high-powered bearer token for the second cluster:
```bash
kubectl config use-context kind-kind-2
kubectl create clusterrolebinding default-sa-admin --serviceaccount default:default --clusterrole cluster-admin
export TOKEN=$(kubectl get secret -o go-template='{{(index .items 0).data.token | base64decode}}')
```
1. Set up an app-config to target these clusters, but with both having the same name:
```yaml
kubernetes:
serviceLocatorMethod:
type: 'multiTenant'
clusterLocatorMethods:
- type: config
clusters:
- name: kind
url: ${KIND_1_URL}
skipTLSVerify: true
skipMetricsLookup: true
authProvider: serviceAccount
- name: kind
url: ${KIND_2_URL}
skipTLSVerify: true
skipMetricsLookup: true
authProvider: serviceAccount
```
1. Run the backend: `yarn start-backend`
1. In a separate process, try to fetch namespaces specifying the ambiguous cluster name, using the token for the second cluster:
```bash
curl -H "Authorization: Bearer $TOKEN" \
-H 'X-Kubernetes-Cluster: kind' \
localhost:7007/api/kubernetes/proxy/api/v1/namespaces
```

### ๐Ÿ“ƒ Provide the context for the Bug.

#### Cause
We already know that this bug is caused by the `find()` call here:

https://github.com/backstage/backstage/blob/38c32326456b5dcef0c16b5ff011cf4181a94d7f/plugins/kubernetes-backend/src/service/KubernetesProxy.ts#L121-L123

According to the [MDN docs](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/find):

> The `find()` method returns the first element in the provided array that satisfies the provided testing function

and this is why it is always the _first_ cluster with the given name that gets targeted.

#### Background
In October 2022, in my big ["Let's just do single-cluster" comment](https://github.com/backstage/backstage/issues/12231#issuecomment-1290727412), I suggested

> At the moment there is no guarantee within the kubernetes subsystem that clusters have unique names (let's file an issue about this), so if this procedure results in multiple clusters, fail the response.

Unfortunately we didn't implement that requirement, and I'm just now filing part of the relevant issue! The rest of the issue should take the form of an RFC about unique identifiers for Kubernetes clusters generally.

### ๐Ÿ–ฅ๏ธ Your Environment

If you really wanna know, I'm running MacOS Ventura 13.1 on a 2019 16-inch Macbook Pro, and I just completed the reproduction steps with the code on master at the time of writing:

```console
% yarn backstage-cli info
OS: Darwin 22.2.0 - darwin/x64
node: v16.19.0
yarn: 3.2.3
cli: 0.22.1 (local)
backstage: N/A

Dependencies:
@backstage/app-defaults 0.0.0-use.local
@backstage/backend-app-api 0.0.0-use.local
@backstage/backend-common 0.0.0-use.local
@backstage/backend-defaults 0.0.0-use.local
@backstage/backend-plugin-api 0.0.0-use.local
@backstage/backend-tasks 0.0.0-use.local
@backstage/backend-test-utils 0.0.0-use.local
@backstage/catalog-client 0.0.0-use.local
@backstage/catalog-model 0.0.0-use.local
@backstage/cli-common 0.0.0-use.local
@backstage/cli 0.0.0-use.local
@backstage/codemods 0.0.0-use.local
@backstage/config-loader 0.0.0-use.local
@backstage/config 0.0.0-use.local
@backstage/core-app-api 0.0.0-use.local
@backstage/core-components 0.0.0-use.local
@backstage/core-plugin-api 0.0.0-use.local
@backstage/create-app 0.0.0-use.local
@backstage/dev-utils 0.0.0-use.local
@backstage/errors 0.0.0-use.local
@backstage/integration-aws-node 0.0.0-use.local
@backstage/integration-react 0.0.0-use.local
@backstage/integration 0.0.0-use.local
@backstage/plugin-adr-backend 0.0.0-use.local
@backstage/plugin-adr-common 0.0.0-use.local
@backstage/plugin-adr 0.0.0-use.local
@backstage/plugin-airbrake-backend 0.0.0-use.local
@backstage/plugin-airbrake 0.0.0-use.local
@backstage/plugin-allure 0.0.0-use.local
@backstage/plugin-analytics-module-ga 0.0.0-use.local
@backstage/plugin-apache-airflow 0.0.0-use.local
@backstage/plugin-api-docs-module-protoc-gen-doc 0.0.0-use.local
@backstage/plugin-api-docs 0.0.0-use.local
@backstage/plugin-apollo-explorer 0.0.0-use.local
@backstage/plugin-app-backend 0.0.0-use.local
@backstage/plugin-auth-backend 0.0.0-use.local
@backstage/plugin-auth-node 0.0.0-use.local
@backstage/plugin-azure-devops-backend 0.0.0-use.local
@backstage/plugin-azure-devops-common 0.0.0-use.local
@backstage/plugin-azure-devops 0.0.0-use.local
@backstage/plugin-azure-sites-backend 0.0.0-use.local
@backstage/plugin-azure-sites-common 0.0.0-use.local
@backstage/plugin-azure-sites 0.0.0-use.local
@backstage/plugin-badges-backend 0.0.0-use.local
@backstage/plugin-badges 0.0.0-use.local
@backstage/plugin-bazaar-backend 0.0.0-use.local
@backstage/plugin-bazaar 0.0.0-use.local
@backstage/plugin-bitbucket-cloud-common 0.0.0-use.local
@backstage/plugin-bitrise 0.0.0-use.local
@backstage/plugin-catalog-backend-module-aws 0.0.0-use.local
@backstage/plugin-catalog-backend-module-azure 0.0.0-use.local
@backstage/plugin-catalog-backend-module-bitbucket-cloud 0.0.0-use.local
@backstage/plugin-catalog-backend-module-bitbucket-server 0.0.0-use.local
@backstage/plugin-catalog-backend-module-bitbucket 0.0.0-use.local
@backstage/plugin-catalog-backend-module-gerrit 0.0.0-use.local
@backstage/plugin-catalog-backend-module-github 0.0.0-use.local
@backstage/plugin-catalog-backend-module-gitlab 0.0.0-use.local
@backstage/plugin-catalog-backend-module-incremental-ingestion 0.0.0-use.local
@backstage/plugin-catalog-backend-module-ldap 0.0.0-use.local
@backstage/plugin-catalog-backend-module-msgraph 0.0.0-use.local
@backstage/plugin-catalog-backend-module-openapi 0.0.0-use.local
@backstage/plugin-catalog-backend 0.0.0-use.local
@backstage/plugin-catalog-common 0.0.0-use.local
@backstage/plugin-catalog-graph 0.0.0-use.local
@backstage/plugin-catalog-graphql 0.0.0-use.local
@backstage/plugin-catalog-import 0.0.0-use.local
@backstage/plugin-catalog-node 0.0.0-use.local
@backstage/plugin-catalog-react 0.0.0-use.local
@backstage/plugin-catalog 0.0.0-use.local
@backstage/plugin-cicd-statistics-module-gitlab 0.0.0-use.local
@backstage/plugin-cicd-statistics 0.0.0-use.local
@backstage/plugin-circleci 0.0.0-use.local
@backstage/plugin-cloudbuild 0.0.0-use.local
@backstage/plugin-code-climate 0.0.0-use.local
@backstage/plugin-code-coverage-backend 0.0.0-use.local
@backstage/plugin-code-coverage 0.0.0-use.local
@backstage/plugin-codescene 0.0.0-use.local
@backstage/plugin-config-schema 0.0.0-use.local
@backstage/plugin-cost-insights-common 0.0.0-use.local
@backstage/plugin-cost-insights 0.0.0-use.local
@backstage/plugin-dynatrace 0.0.0-use.local
@backstage/plugin-events-backend-module-aws-sqs 0.0.0-use.local
@backstage/plugin-events-backend-module-azure 0.0.0-use.local
@backstage/plugin-events-backend-module-bitbucket-cloud 0.0.0-use.local
@backstage/plugin-events-backend-module-gerrit 0.0.0-use.local
@backstage/plugin-events-backend-module-github 0.0.0-use.local
@backstage/plugin-events-backend-module-gitlab 0.0.0-use.local
@backstage/plugin-events-backend-test-utils 0.0.0-use.local
@backstage/plugin-events-backend 0.0.0-use.local
@backstage/plugin-events-node 0.0.0-use.local
@backstage/plugin-explore-backend 0.0.0-use.local
@backstage/plugin-explore-common 0.0.0-use.local
@backstage/plugin-explore-react 0.0.0-use.local
@backstage/plugin-explore 0.0.0-use.local
@backstage/plugin-firehydrant 0.0.0-use.local
@backstage/plugin-fossa 0.0.0-use.local
@backstage/plugin-gcalendar 0.0.0-use.local
@backstage/plugin-gcp-projects 0.0.0-use.local
@backstage/plugin-git-release-manager 0.0.0-use.local
@backstage/plugin-github-actions 0.0.0-use.local
@backstage/plugin-github-deployments 0.0.0-use.local
@backstage/plugin-github-issues 0.0.0-use.local
@backstage/plugin-github-pull-requests-board 0.0.0-use.local
@backstage/plugin-gitops-profiles 0.0.0-use.local
@backstage/plugin-gocd 0.0.0-use.local
@backstage/plugin-graphiql 0.0.0-use.local
@backstage/plugin-graphql-backend 0.0.0-use.local
@backstage/plugin-home 0.0.0-use.local
@backstage/plugin-ilert 0.0.0-use.local
@backstage/plugin-jenkins-backend 0.0.0-use.local
@backstage/plugin-jenkins-common 0.0.0-use.local
@backstage/plugin-jenkins 0.0.0-use.local
@backstage/plugin-kafka-backend 0.0.0-use.local
@backstage/plugin-kafka 0.0.0-use.local
@backstage/plugin-kubernetes-backend 0.0.0-use.local
@backstage/plugin-kubernetes-common 0.0.0-use.local
@backstage/plugin-kubernetes 0.0.0-use.local
@backstage/plugin-lighthouse 0.0.0-use.local
@backstage/plugin-newrelic-dashboard 0.0.0-use.local
@backstage/plugin-newrelic 0.0.0-use.local
@backstage/plugin-org-react 0.0.0-use.local
@backstage/plugin-org 0.0.0-use.local
@backstage/plugin-pagerduty 0.0.0-use.local
@backstage/plugin-periskop-backend 0.0.0-use.local
@backstage/plugin-periskop 0.0.0-use.local
@backstage/plugin-permission-backend 0.0.0-use.local
@backstage/plugin-permission-common 0.0.0-use.local
@backstage/plugin-permission-node 0.0.0-use.local
@backstage/plugin-permission-react 0.0.0-use.local
@backstage/plugin-playlist-backend 0.0.0-use.local
@backstage/plugin-playlist-common 0.0.0-use.local
@backstage/plugin-playlist 0.0.0-use.local
@backstage/plugin-proxy-backend 0.0.0-use.local
@backstage/plugin-rollbar-backend 0.0.0-use.local
@backstage/plugin-rollbar 0.0.0-use.local
@backstage/plugin-scaffolder-backend-module-cookiecutter 0.0.0-use.local
@backstage/plugin-scaffolder-backend-module-rails 0.0.0-use.local
@backstage/plugin-scaffolder-backend-module-sentry 0.0.0-use.local
@backstage/plugin-scaffolder-backend-module-yeoman 0.0.0-use.local
@backstage/plugin-scaffolder-backend 0.0.0-use.local
@backstage/plugin-scaffolder-common 0.0.0-use.local
@backstage/plugin-scaffolder-node 0.0.0-use.local
@backstage/plugin-scaffolder-react 0.0.0-use.local
@backstage/plugin-scaffolder 0.0.0-use.local
@backstage/plugin-search-backend-module-elasticsearch 0.0.0-use.local
@backstage/plugin-search-backend-module-pg 0.0.0-use.local
@backstage/plugin-search-backend-node 0.0.0-use.local
@backstage/plugin-search-backend 0.0.0-use.local
@backstage/plugin-search-common 0.0.0-use.local
@backstage/plugin-search-react 0.0.0-use.local
@backstage/plugin-search 0.0.0-use.local
@backstage/plugin-sentry 0.0.0-use.local
@backstage/plugin-shortcuts 0.0.0-use.local
@backstage/plugin-sonarqube-backend 0.0.0-use.local
@backstage/plugin-sonarqube-react 0.0.0-use.local
@backstage/plugin-sonarqube 0.0.0-use.local
@backstage/plugin-splunk-on-call 0.0.0-use.local
@backstage/plugin-stack-overflow-backend 0.0.0-use.local
@backstage/plugin-stack-overflow 0.0.0-use.local
@backstage/plugin-tech-insights-backend-module-jsonfc 0.0.0-use.local
@backstage/plugin-tech-insights-backend 0.0.0-use.local
@backstage/plugin-tech-insights-common 0.0.0-use.local
@backstage/plugin-tech-insights-node 0.0.0-use.local
@backstage/plugin-tech-insights 0.0.0-use.local
@backstage/plugin-tech-radar 0.0.0-use.local
@backstage/plugin-techdocs-addons-test-utils 0.0.0-use.local
@backstage/plugin-techdocs-backend 0.0.0-use.local
@backstage/plugin-techdocs-module-addons-contrib 0.0.0-use.local
@backstage/plugin-techdocs-node 0.0.0-use.local
@backstage/plugin-techdocs-react 0.0.0-use.local
@backstage/plugin-techdocs 0.0.0-use.local
@backstage/plugin-todo-backend 0.0.0-use.local
@backstage/plugin-todo 0.0.0-use.local
@backstage/plugin-user-settings-backend 0.0.0-use.local
@backstage/plugin-user-settings 0.0.0-use.local
@backstage/plugin-vault-backend 0.0.0-use.local
@backstage/plugin-vault 0.0.0-use.local
@backstage/plugin-xcmetrics 0.0.0-use.local
@backstage/release-manifests 0.0.0-use.local
@backstage/repo-tools 0.0.0-use.local
@backstage/test-utils 0.0.0-use.local
@backstage/theme 0.0.0-use.local
@backstage/types 0.0.0-use.local
@backstage/version-bridge 0.0.0-use.local
```

### ๐Ÿ‘€ Have you spent some time to check if this bug has been raised before?

- [X] I checked and didn't find similar issue

### ๐Ÿข Have you read the Code of Conduct?

- [X] I have read the [Code of Conduct](https://github.com/backstage/backstage/blob/master/CODE_OF_CONDUCT.md)

### Are you willing to submit PR?

Yes I am willing to submit a PR!

Contributor guide

Open the contributing guide

Research direction

Start in plugins/kubernetes-backend/src/service/KubernetesProxy.ts at the find() call linked in the issue, then reproduce the proxy request with two same-named kind clusters and the second cluster's bearer token. Done means the proxy no longer silently targets the first matching cluster and instead handles the ambiguous name according to the agreed behavior.

Written by the indexing model from the issue text.

Assessment

Tech stack
kubernetes, typescript
Domain
api, backend, infrastructure
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
45/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.