Modifying changesets and explicitly returning a promise for the AWS Creds

This changes modifies the changeset to only include a patch (as per comments above).

I have also changed the flow of retrieving the AWS creds. Previously, they were being returned after they were "resolved".
Now we await and resolve a promise to guarantee that the credentials have been loaded.

Signed-off-by: Nicolas Arnold <nic@roadie.io>
This commit is contained in:
Nicolas Arnold
2021-07-19 17:47:53 +01:00
parent ec98274a7e
commit 76fbdbc322
4 changed files with 68 additions and 30 deletions
+1 -1
View File
@@ -1,5 +1,5 @@
---
'@backstage/plugin-kubernetes-backend': minor
'@backstage/plugin-kubernetes-backend': patch
---
Support assume role on kubernetes api configuration for AWS.
+9 -3
View File
@@ -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';
@@ -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.',
);
});
});
});
@@ -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<Credentials> => {
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<SigningCreds> {
return new Promise<SigningCreds>(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 {