diff --git a/.changeset/blue-feet-poke.md b/.changeset/blue-feet-poke.md index 3be886c687..1a9e32a11a 100644 --- a/.changeset/blue-feet-poke.md +++ b/.changeset/blue-feet-poke.md @@ -1,5 +1,5 @@ --- -'@backstage/plugin-kubernetes-backend': minor +'@backstage/plugin-kubernetes-backend': patch --- Support assume role on kubernetes api configuration for AWS. diff --git a/plugins/kubernetes-backend/api-report.md b/plugins/kubernetes-backend/api-report.md index 0de29bd2f5..9f35fda79f 100644 --- a/plugins/kubernetes-backend/api-report.md +++ b/plugins/kubernetes-backend/api-report.md @@ -10,7 +10,7 @@ import { KubernetesFetchError } from '@backstage/plugin-kubernetes-common'; import { KubernetesRequestBody } from '@backstage/plugin-kubernetes-common'; import { Logger as Logger_2 } from 'winston'; -// Warning: (ae-missing-release-tag) "ClusterDetails" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// Warning: (ae-missing-release-tag) "AWSClusterDetails" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) export interface AWSClusterDetails extends ClusterDetails { @@ -18,6 +18,8 @@ export interface AWSClusterDetails extends ClusterDetails { assumeRole?: string; } +// Warning: (ae-missing-release-tag) "ClusterDetails" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// // @public (undocumented) export interface ClusterDetails { // (undocumented) @@ -59,11 +61,13 @@ export interface FetchResponseWrapper { responses: FetchResponse[]; } -// Warning: (ae-missing-release-tag) "KubernetesClustersSupplier" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// Warning: (ae-missing-release-tag) "GKEClusterDetails" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) export interface GKEClusterDetails extends ClusterDetails {} +// Warning: (ae-missing-release-tag) "KubernetesClustersSupplier" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// // @public (undocumented) export interface KubernetesClustersSupplier { // (undocumented) @@ -142,11 +146,13 @@ export interface RouterOptions { logger: Logger_2; } -// Warning: (ae-missing-release-tag) "ServiceLocatorMethod" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// Warning: (ae-missing-release-tag) "ServiceAccountClusterDetails" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) export interface ServiceAccountClusterDetails extends ClusterDetails {} +// Warning: (ae-missing-release-tag) "ServiceLocatorMethod" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// // @public (undocumented) export type ServiceLocatorMethod = 'multiTenant' | 'http'; diff --git a/plugins/kubernetes-backend/src/kubernetes-auth-translator/AwsIamKubernetesAuthTranslator.test.ts b/plugins/kubernetes-backend/src/kubernetes-auth-translator/AwsIamKubernetesAuthTranslator.test.ts index d5179fc07a..9060ce9239 100644 --- a/plugins/kubernetes-backend/src/kubernetes-auth-translator/AwsIamKubernetesAuthTranslator.test.ts +++ b/plugins/kubernetes-backend/src/kubernetes-auth-translator/AwsIamKubernetesAuthTranslator.test.ts @@ -19,16 +19,23 @@ import { AwsIamKubernetesAuthTranslator } from './AwsIamKubernetesAuthTranslator import { get, def } from 'bdd-lazy-var'; describe('AwsIamKubernetesAuthTranslator tests', () => { - let valid: boolean = true; let role: any = undefined; - let response: any = { + const credentials: any = { + accessKeyId: 'bloop', + secretAccessKey: 'omg-so-secret', + sessionToken: 'token', + }; + + let assumeResponse: any = { Credentials: { - AccessKeyId: 'bloop', - SecretAccessKey: 'omg-so-secret', - SessionToken: 'token', + AccessKeyId: credentials.accessKeyId, + SecretAccessKey: credentials.secretAccessKey, + SessionToken: credentials.sessionToken, }, }; + let credentialsResponse: any = new AWS.Credentials(credentials); + AWSMock.setSDKInstance(AWS); beforeEach(() => { @@ -41,13 +48,15 @@ describe('AwsIamKubernetesAuthTranslator tests', () => { def('subject', () => { AWSMock.mock('STS', 'assumeRole', (_params: any, callback: Function) => { - callback(null, response); + callback(null, assumeResponse); }); const authTranslator = new AwsIamKubernetesAuthTranslator(); + jest - .spyOn(authTranslator, 'validCredentials') - .mockImplementation(() => valid); + .spyOn(authTranslator, 'awsGetCredentials') + .mockImplementation(async () => credentialsResponse); + return authTranslator.decorateClusterDetailsWithAuth({ assumeRole: role, name: 'test-cluster', @@ -86,16 +95,25 @@ describe('AwsIamKubernetesAuthTranslator tests', () => { describe('When the role is invalid', () => { it('returns the original AWS credentials', async () => { - response = undefined; - + assumeResponse = undefined; await expect(get('subject')).rejects.toThrow(/Unable to assume role:/); }); }); }); - it('throws when unable to get aws credentials', async () => { - valid = false; - AWS.config.credentials = undefined; - await expect(get('subject')).rejects.toThrow('No AWS credentials found'); + describe('When no creds are returned from AWS', () => { + it('throws unable to get aws credentials', async () => { + credentialsResponse = new Error(); + await expect(get('subject')).rejects.toThrow('No AWS credentials found.'); + }); + }); + + describe('When invalid creds are returned from AWS', () => { + it('throws credentials are invalid to get aws credentials', async () => { + credentialsResponse = new AWS.Credentials(credentialsResponse); + await expect(get('subject')).rejects.toThrow( + 'Invalid AWS credentials found.', + ); + }); }); }); diff --git a/plugins/kubernetes-backend/src/kubernetes-auth-translator/AwsIamKubernetesAuthTranslator.ts b/plugins/kubernetes-backend/src/kubernetes-auth-translator/AwsIamKubernetesAuthTranslator.ts index e9004818a2..c15022d504 100644 --- a/plugins/kubernetes-backend/src/kubernetes-auth-translator/AwsIamKubernetesAuthTranslator.ts +++ b/plugins/kubernetes-backend/src/kubernetes-auth-translator/AwsIamKubernetesAuthTranslator.ts @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import AWS from 'aws-sdk'; +import AWS, { Credentials } from 'aws-sdk'; import { sign } from 'aws4'; import { AWSClusterDetails } from '../types/types'; import { KubernetesAuthTranslator } from './types'; @@ -38,29 +38,43 @@ type SigningCreds = { export class AwsIamKubernetesAuthTranslator implements KubernetesAuthTranslator { validCredentials(creds: SigningCreds): boolean { - if (!creds.accessKeyId || !creds.secretAccessKey || !creds.sessionToken) { + if ( + !creds?.accessKeyId || + !creds?.secretAccessKey || + !creds?.sessionToken + ) { return false; } return true; } + awsGetCredentials = async (): Promise => { + return new Promise((resolve, reject) => { + AWS.config.getCredentials(err => { + if (err) { + return reject(err); + } + + return resolve(AWS.config.credentials as Credentials); + }); + }); + }; + async getCredentials(assumeRole: string | undefined): Promise { return new Promise(async (resolve, reject) => { - await AWS.config.getCredentials(err => { - if (err) { - console.error('Unable to load aws config.'); - reject(err); - } - }); + const awsCreds = await this.awsGetCredentials(); + + if (!(awsCreds instanceof Credentials)) + return reject(Error('No AWS credentials found.')); let creds: SigningCreds = { - accessKeyId: AWS.config.credentials?.accessKeyId, - secretAccessKey: AWS.config.credentials?.secretAccessKey, - sessionToken: AWS.config.credentials?.sessionToken, + accessKeyId: awsCreds.accessKeyId, + secretAccessKey: awsCreds.secretAccessKey, + sessionToken: awsCreds.sessionToken, }; if (!this.validCredentials(creds)) - return reject(Error('No AWS credentials found.')); + return reject(Error('Invalid AWS credentials found.')); if (!assumeRole) return resolve(creds); try {