diff --git a/.changeset/seven-plums-return.md b/.changeset/seven-plums-return.md new file mode 100644 index 0000000000..71016577b1 --- /dev/null +++ b/.changeset/seven-plums-return.md @@ -0,0 +1,14 @@ +--- +'@backstage/plugin-kubernetes-backend': patch +'@backstage/plugin-kubernetes-common': patch +--- + +Clusters configured with the `aws` authentication strategy can now customize the +`x-k8s-aws-id` header value used to generate tokens. This value can be specified +specified via the `kubernetes.io/x-k8s-aws-id` parameter (in +`metadata.annotations` for clusters in the catalog, or the `authMetadata` block +on clusters in the app-config). This is particularly helpful when a Backstage +instance contains multiple AWS clusters with the same name in different regions +-- using this new parameter, the clusters can be given different logical names +to distinguish them but still use the same ID for the purposes of generating +tokens. diff --git a/plugins/kubernetes-backend/src/auth/AwsIamStrategy.test.ts b/plugins/kubernetes-backend/src/auth/AwsIamStrategy.test.ts index 9959e1960f..e6e69cc05d 100644 --- a/plugins/kubernetes-backend/src/auth/AwsIamStrategy.test.ts +++ b/plugins/kubernetes-backend/src/auth/AwsIamStrategy.test.ts @@ -16,6 +16,7 @@ import { ConfigReader } from '@backstage/config'; import { ANNOTATION_KUBERNETES_AWS_ASSUME_ROLE, + ANNOTATION_KUBERNETES_AWS_CLUSTER_ID, ANNOTATION_KUBERNETES_AWS_EXTERNAL_ID, } from '@backstage/plugin-kubernetes-common'; import { AwsIamStrategy } from './AwsIamStrategy'; @@ -56,7 +57,7 @@ jest.mock('@aws-sdk/credential-providers', () => ({ describe('AwsIamStrategy#getCredential', () => { const config = new ConfigReader({}); - it('returns a presigned url for cluster name', async () => { + it('returns a presigned url', async () => { const strategy = new AwsIamStrategy({ config }); const credential = await strategy.getCredential({ @@ -77,7 +78,30 @@ describe('AwsIamStrategy#getCredential', () => { ); }); - it('returns a signed url for AWS credentials with assume role', async () => { + it('returns a presigned url for specified cluster ID', async () => { + const strategy = new AwsIamStrategy({ config }); + + const credential = await strategy.getCredential({ + name: 'cluster-name', + url: '', + authMetadata: { + [ANNOTATION_KUBERNETES_AWS_CLUSTER_ID]: 'other-name', + }, + }); + + expect(credential).toEqual({ + type: 'bearer token', + token: 'k8s-aws-v1.aHR0cHM6Ly9odHRwczovL2V4YW1wbGUuY29tL2FzZGY_', + }); + expect(signer.presign).toHaveBeenCalledWith( + expect.objectContaining({ + headers: expect.objectContaining({ 'x-k8s-aws-id': 'other-name' }), + }), + expect.anything(), + ); + }); + + it('returns a presigned url for AWS credentials with assumed role', async () => { const strategy = new AwsIamStrategy({ config }); const credential = await strategy.getCredential({ @@ -106,7 +130,7 @@ describe('AwsIamStrategy#getCredential', () => { }); }); - it('returns a signed url for AWS credentials and passes the external id', async () => { + it('returns a presigned url for AWS credentials and passes the external id', async () => { const strategy = new AwsIamStrategy({ config }); const credential = await strategy.getCredential({ diff --git a/plugins/kubernetes-backend/src/auth/AwsIamStrategy.ts b/plugins/kubernetes-backend/src/auth/AwsIamStrategy.ts index 942b93615f..4c56f86a30 100644 --- a/plugins/kubernetes-backend/src/auth/AwsIamStrategy.ts +++ b/plugins/kubernetes-backend/src/auth/AwsIamStrategy.ts @@ -23,6 +23,7 @@ import { import { Config } from '@backstage/config'; import { ANNOTATION_KUBERNETES_AWS_ASSUME_ROLE, + ANNOTATION_KUBERNETES_AWS_CLUSTER_ID, ANNOTATION_KUBERNETES_AWS_EXTERNAL_ID, } from '@backstage/plugin-kubernetes-common'; import { @@ -61,7 +62,8 @@ export class AwsIamStrategy implements AuthenticationStrategy { return { type: 'bearer token', token: await this.getBearerToken( - clusterDetails.name, + clusterDetails.authMetadata[ANNOTATION_KUBERNETES_AWS_CLUSTER_ID] ?? + clusterDetails.name, clusterDetails.authMetadata[ANNOTATION_KUBERNETES_AWS_ASSUME_ROLE], clusterDetails.authMetadata[ANNOTATION_KUBERNETES_AWS_EXTERNAL_ID], ), @@ -73,7 +75,7 @@ export class AwsIamStrategy implements AuthenticationStrategy { } private async getBearerToken( - clusterName: string, + clusterId: string, assumeRole?: string, externalId?: string, ): Promise { @@ -105,7 +107,7 @@ export class AwsIamStrategy implements AuthenticationStrategy { { headers: { host: `sts.${region}.amazonaws.com`, - 'x-k8s-aws-id': clusterName, + 'x-k8s-aws-id': clusterId, }, hostname: `sts.${region}.amazonaws.com`, method: 'GET', diff --git a/plugins/kubernetes-common/api-report.md b/plugins/kubernetes-common/api-report.md index abd340f368..e4dbf79f4a 100644 --- a/plugins/kubernetes-common/api-report.md +++ b/plugins/kubernetes-common/api-report.md @@ -39,6 +39,10 @@ export const ANNOTATION_KUBERNETES_AUTH_PROVIDER = export const ANNOTATION_KUBERNETES_AWS_ASSUME_ROLE = 'kubernetes.io/aws-assume-role'; +// @public +export const ANNOTATION_KUBERNETES_AWS_CLUSTER_ID = + 'kubernetes.io/x-k8s-aws-id'; + // @public export const ANNOTATION_KUBERNETES_AWS_EXTERNAL_ID = 'kubernetes.io/aws-external-id'; diff --git a/plugins/kubernetes-common/src/catalog-entity-constants.ts b/plugins/kubernetes-common/src/catalog-entity-constants.ts index e7e5cddc25..996faf670d 100644 --- a/plugins/kubernetes-common/src/catalog-entity-constants.ts +++ b/plugins/kubernetes-common/src/catalog-entity-constants.ts @@ -84,6 +84,7 @@ export const ANNOTATION_KUBERNETES_DASHBOARD_APP = */ export const ANNOTATION_KUBERNETES_DASHBOARD_PARAMETERS = 'kubernetes.io/dashboard-parameters'; + /** * Annotation for specifying the assume role use to authenticate with AWS. * @@ -92,6 +93,14 @@ export const ANNOTATION_KUBERNETES_DASHBOARD_PARAMETERS = export const ANNOTATION_KUBERNETES_AWS_ASSUME_ROLE = 'kubernetes.io/aws-assume-role'; +/** + * Annotation for specifying the AWS ID of a cluster when signing STS tokens + * + * @public + */ +export const ANNOTATION_KUBERNETES_AWS_CLUSTER_ID = + 'kubernetes.io/x-k8s-aws-id'; + /** * Annotation for specifying an external id when communicating with AWS *