feature: add aws iam auth translator for kubernetes
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
---
|
||||
'@backstage/plugin-kubernetes': patch
|
||||
'@backstage/plugin-kubernetes-backend': patch
|
||||
---
|
||||
|
||||
Add AWS auth provider for Kubernetes
|
||||
@@ -31,11 +31,14 @@
|
||||
"clean": "backstage-cli clean"
|
||||
},
|
||||
"dependencies": {
|
||||
"@aws-sdk/credential-provider-node": "^3.3.0",
|
||||
"@backstage/backend-common": "^0.4.3",
|
||||
"@backstage/catalog-model": "^0.6.1",
|
||||
"@backstage/config": "^0.1.2",
|
||||
"@kubernetes/client-node": "^0.13.2",
|
||||
"@types/aws4": "^1.5.1",
|
||||
"@types/express": "^4.17.6",
|
||||
"aws4": "^1.11.0",
|
||||
"compression": "^1.7.4",
|
||||
"cors": "^2.8.5",
|
||||
"express": "^4.17.1",
|
||||
|
||||
+2
-2
@@ -20,8 +20,8 @@ export interface Config {
|
||||
clusters: {
|
||||
url: string;
|
||||
name: string;
|
||||
serviceAccountToken: string;
|
||||
authProvider: 'serviceAccount';
|
||||
serviceAccountToken: string | undefined;
|
||||
authProvider: 'aws' | 'google' | 'serviceAccount';
|
||||
}[];
|
||||
};
|
||||
}
|
||||
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* Copyright 2020 Spotify AB
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
const mockCredentialProvider = jest.fn();
|
||||
jest.mock('@aws-sdk/credential-provider-node', () => {
|
||||
return {
|
||||
defaultProvider: () => mockCredentialProvider,
|
||||
};
|
||||
});
|
||||
|
||||
import { AwsIamKubernetesAuthTranslator } from './AwsIamKubernetesAuthTranslator';
|
||||
|
||||
describe('AwsIamKubernetesAuthTranslator tests', () => {
|
||||
beforeEach(() => {
|
||||
jest.resetAllMocks();
|
||||
});
|
||||
it('returns a signed url for aws credentials', async () => {
|
||||
const authTranslator = new AwsIamKubernetesAuthTranslator();
|
||||
|
||||
mockCredentialProvider.mockImplementation(async () => {
|
||||
// These credentials are not real.
|
||||
// Pulled from example in docs: https://docs.aws.amazon.com/general/latest/gr/aws-sec-cred-types.html
|
||||
return {
|
||||
accessKeyId: 'AKIAIOSFODNN7EXAMPLE',
|
||||
secretKeyId: 'wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY',
|
||||
};
|
||||
});
|
||||
|
||||
const clusterDetails = await authTranslator.decorateClusterDetailsWithAuth({
|
||||
name: 'test-cluster',
|
||||
url: '',
|
||||
authProvider: 'aws',
|
||||
});
|
||||
expect(clusterDetails.serviceAccountToken).toBeDefined();
|
||||
});
|
||||
|
||||
it('throws when unable to get aws credentials', async () => {
|
||||
const authTranslator = new AwsIamKubernetesAuthTranslator();
|
||||
|
||||
mockCredentialProvider.mockImplementation(async () => {
|
||||
throw new Error('not implemented');
|
||||
});
|
||||
|
||||
const promise = authTranslator.decorateClusterDetailsWithAuth({
|
||||
name: 'test-cluster',
|
||||
url: '',
|
||||
authProvider: 'aws',
|
||||
});
|
||||
await expect(promise).rejects.toThrow('not implemented');
|
||||
});
|
||||
});
|
||||
+73
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* Copyright 2020 Spotify AB
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { defaultProvider } from '@aws-sdk/credential-provider-node';
|
||||
import { sign } from 'aws4';
|
||||
import { KubernetesAuthTranslator } from './types';
|
||||
import { ClusterDetails } from '..';
|
||||
|
||||
const base64 = (str: string) =>
|
||||
Buffer.from(str.toString(), 'binary').toString('base64');
|
||||
const prepend = (prep: string) => (str: string) => prep + str;
|
||||
const replace = (search: string | RegExp, substitution: string) => (
|
||||
str: string,
|
||||
) => str.replace(search, substitution);
|
||||
const pipe = (fns: ReadonlyArray<any>) => (thing: string): string =>
|
||||
fns.reduce((val, fn) => fn(val), thing);
|
||||
const removePadding = replace(/=+$/, '');
|
||||
const makeUrlSafe = pipe([replace('+', '-'), replace('/', '_')]);
|
||||
|
||||
export class AwsIamKubernetesAuthTranslator
|
||||
implements KubernetesAuthTranslator {
|
||||
async getBearerToken(clusterName: string): Promise<string> {
|
||||
const credentialProvider = defaultProvider();
|
||||
const credentials = await credentialProvider();
|
||||
const request = {
|
||||
host: `sts.amazonaws.com`,
|
||||
path: `/?Action=GetCallerIdentity&Version=2011-06-15&X-Amz-Expires=60`,
|
||||
headers: {
|
||||
'x-k8s-aws-id': clusterName,
|
||||
},
|
||||
signQuery: true,
|
||||
};
|
||||
const signedRequest = sign(request, {
|
||||
accessKeyId: credentials.accessKeyId,
|
||||
secretAccessKey: credentials.secretAccessKey,
|
||||
sessionToken: credentials.sessionToken,
|
||||
});
|
||||
|
||||
return pipe([
|
||||
(signed: any) => `https://${signed.host}${signed.path}`,
|
||||
base64,
|
||||
removePadding,
|
||||
makeUrlSafe,
|
||||
prepend('k8s-aws-v1.'),
|
||||
])(signedRequest);
|
||||
}
|
||||
|
||||
async decorateClusterDetailsWithAuth(
|
||||
clusterDetails: ClusterDetails,
|
||||
): Promise<ClusterDetails> {
|
||||
const clusterDetailsWithAuthToken: ClusterDetails = Object.assign(
|
||||
{},
|
||||
clusterDetails,
|
||||
);
|
||||
|
||||
clusterDetailsWithAuthToken.serviceAccountToken = await this.getBearerToken(
|
||||
clusterDetails.name,
|
||||
);
|
||||
return clusterDetailsWithAuthToken;
|
||||
}
|
||||
}
|
||||
+8
@@ -18,6 +18,7 @@ import { KubernetesAuthTranslator } from './types';
|
||||
import { GoogleKubernetesAuthTranslator } from './GoogleKubernetesAuthTranslator';
|
||||
import { KubernetesAuthTranslatorGenerator } from './KubernetesAuthTranslatorGenerator';
|
||||
import { ServiceAccountKubernetesAuthTranslator } from './ServiceAccountKubernetesAuthTranslator';
|
||||
import { AwsIamKubernetesAuthTranslator } from './AwsIamKubernetesAuthTranslator';
|
||||
|
||||
describe('getKubernetesAuthTranslatorInstance', () => {
|
||||
const sut = KubernetesAuthTranslatorGenerator;
|
||||
@@ -29,6 +30,13 @@ describe('getKubernetesAuthTranslatorInstance', () => {
|
||||
expect(authTranslator instanceof GoogleKubernetesAuthTranslator).toBe(true);
|
||||
});
|
||||
|
||||
it('can return an auth translator for aws auth', () => {
|
||||
const authTranslator: KubernetesAuthTranslator = sut.getKubernetesAuthTranslatorInstance(
|
||||
'aws',
|
||||
);
|
||||
expect(authTranslator instanceof AwsIamKubernetesAuthTranslator).toBe(true);
|
||||
});
|
||||
|
||||
it('can return an auth translator for serviceAccount auth', () => {
|
||||
const authTranslator: KubernetesAuthTranslator = sut.getKubernetesAuthTranslatorInstance(
|
||||
'serviceAccount',
|
||||
|
||||
+4
@@ -17,6 +17,7 @@
|
||||
import { KubernetesAuthTranslator } from './types';
|
||||
import { GoogleKubernetesAuthTranslator } from './GoogleKubernetesAuthTranslator';
|
||||
import { ServiceAccountKubernetesAuthTranslator } from './ServiceAccountKubernetesAuthTranslator';
|
||||
import { AwsIamKubernetesAuthTranslator } from './AwsIamKubernetesAuthTranslator';
|
||||
|
||||
export class KubernetesAuthTranslatorGenerator {
|
||||
static getKubernetesAuthTranslatorInstance(
|
||||
@@ -26,6 +27,9 @@ export class KubernetesAuthTranslatorGenerator {
|
||||
case 'google': {
|
||||
return new GoogleKubernetesAuthTranslator();
|
||||
}
|
||||
case 'aws': {
|
||||
return new AwsIamKubernetesAuthTranslator();
|
||||
}
|
||||
case 'serviceAccount': {
|
||||
return new ServiceAccountKubernetesAuthTranslator();
|
||||
}
|
||||
|
||||
@@ -148,4 +148,4 @@ export interface KubernetesFetchError {
|
||||
|
||||
export type ServiceLocatorMethod = 'multiTenant' | 'http'; // TODO implement http
|
||||
export type ClusterLocatorMethod = 'config';
|
||||
export type AuthProviderType = 'google' | 'serviceAccount';
|
||||
export type AuthProviderType = 'google' | 'serviceAccount' | 'aws';
|
||||
|
||||
Vendored
+2
-2
@@ -35,11 +35,11 @@ export interface Config {
|
||||
/**
|
||||
* @visibility secret
|
||||
*/
|
||||
serviceAccountToken: string;
|
||||
serviceAccountToken: string | undefined;
|
||||
/**
|
||||
* @visibility frontend
|
||||
*/
|
||||
authProvider: 'serviceAccount';
|
||||
authProvider: 'aws' | 'google' | 'serviceAccount';
|
||||
}[];
|
||||
};
|
||||
}
|
||||
|
||||
@@ -263,6 +263,15 @@
|
||||
"@aws-sdk/property-provider" "3.1.0"
|
||||
tslib "^1.8.0"
|
||||
|
||||
"@aws-sdk/credential-provider-env@3.3.0":
|
||||
version "3.3.0"
|
||||
resolved "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.3.0.tgz#7930e504a7a79ab98a9fd34befc5c84b8c4df679"
|
||||
integrity sha512-kyqZMlGdH/05IhuXLBUXtj5+hhRfYiHFcJLc3ts/uiwCixswVHPAYHgyWm9ajFkmWtpz6ih+0LoYryhPbYu01A==
|
||||
dependencies:
|
||||
"@aws-sdk/property-provider" "3.3.0"
|
||||
"@aws-sdk/types" "3.1.0"
|
||||
tslib "^1.8.0"
|
||||
|
||||
"@aws-sdk/credential-provider-imds@3.1.0":
|
||||
version "3.1.0"
|
||||
resolved "https://registry.npmjs.org/@aws-sdk/credential-provider-imds/-/credential-provider-imds-3.1.0.tgz#33d48753dc00bddce79d2aa8076a7cb5bf8562df"
|
||||
@@ -271,6 +280,15 @@
|
||||
"@aws-sdk/property-provider" "3.1.0"
|
||||
tslib "^1.8.0"
|
||||
|
||||
"@aws-sdk/credential-provider-imds@3.3.0":
|
||||
version "3.3.0"
|
||||
resolved "https://registry.npmjs.org/@aws-sdk/credential-provider-imds/-/credential-provider-imds-3.3.0.tgz#ff0cf5489853c16d23fc99d7bae425587e836c40"
|
||||
integrity sha512-Cx0YMnO/ScGQVDns006bLbqOxNURGN2Xm21bCY0l0ZUJCdJ2va1/9q1rljDyw2KvdzZNQVRQII3uUgj/Oq/K+g==
|
||||
dependencies:
|
||||
"@aws-sdk/property-provider" "3.3.0"
|
||||
"@aws-sdk/types" "3.1.0"
|
||||
tslib "^1.8.0"
|
||||
|
||||
"@aws-sdk/credential-provider-ini@3.1.0":
|
||||
version "3.1.0"
|
||||
resolved "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.1.0.tgz#1a6cf9ab9fa1450d4472b9e371099b0c0283349b"
|
||||
@@ -280,6 +298,16 @@
|
||||
"@aws-sdk/shared-ini-file-loader" "3.1.0"
|
||||
tslib "^1.8.0"
|
||||
|
||||
"@aws-sdk/credential-provider-ini@3.3.0":
|
||||
version "3.3.0"
|
||||
resolved "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.3.0.tgz#55fe8f391b72d30e650ba8bc680e82bbeacbbfe5"
|
||||
integrity sha512-zawNFJoasXiaV5n0H3/KNOi7mAZ7mHpG1+nBEkoWhZ31lIUM9+heGPcxKCbf/pMQjiOebUqL1OpWe4uSWxIVMw==
|
||||
dependencies:
|
||||
"@aws-sdk/property-provider" "3.3.0"
|
||||
"@aws-sdk/shared-ini-file-loader" "3.1.0"
|
||||
"@aws-sdk/types" "3.1.0"
|
||||
tslib "^1.8.0"
|
||||
|
||||
"@aws-sdk/credential-provider-node@3.1.0":
|
||||
version "3.1.0"
|
||||
resolved "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.1.0.tgz#89bc8803752a3e580c6f2410306c7edad6be7fa2"
|
||||
@@ -292,6 +320,19 @@
|
||||
"@aws-sdk/property-provider" "3.1.0"
|
||||
tslib "^1.8.0"
|
||||
|
||||
"@aws-sdk/credential-provider-node@^3.3.0":
|
||||
version "3.3.0"
|
||||
resolved "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.3.0.tgz#5c97323fa7b23590070d06aa7b1be8d93b2bf4be"
|
||||
integrity sha512-PPBNzPq8fHk9dEQTTE4iJi6ZWtmo057Lc+I8Rlzmvz6NthK9iKiU819tfaxVBb6ZR7bLP0BuDiCi4G1lD+rQnQ==
|
||||
dependencies:
|
||||
"@aws-sdk/credential-provider-env" "3.3.0"
|
||||
"@aws-sdk/credential-provider-imds" "3.3.0"
|
||||
"@aws-sdk/credential-provider-ini" "3.3.0"
|
||||
"@aws-sdk/credential-provider-process" "3.3.0"
|
||||
"@aws-sdk/property-provider" "3.3.0"
|
||||
"@aws-sdk/types" "3.1.0"
|
||||
tslib "^1.8.0"
|
||||
|
||||
"@aws-sdk/credential-provider-process@3.1.0":
|
||||
version "3.1.0"
|
||||
resolved "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.1.0.tgz#ff817b29a9760c463b77be3ce49375eaeb753ef3"
|
||||
@@ -302,6 +343,17 @@
|
||||
"@aws-sdk/shared-ini-file-loader" "3.1.0"
|
||||
tslib "^1.8.0"
|
||||
|
||||
"@aws-sdk/credential-provider-process@3.3.0":
|
||||
version "3.3.0"
|
||||
resolved "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.3.0.tgz#9de0984bd6dd0f5e40cff3672d7dd19e8cd43074"
|
||||
integrity sha512-7oOF1j6ydUq43P3SsasiIpbxMKCmT0C+XwggHTGiVxNtX+QZiH1vdMf8otA7puLEey0iY5wTAIEcZhC6HenojA==
|
||||
dependencies:
|
||||
"@aws-sdk/credential-provider-ini" "3.3.0"
|
||||
"@aws-sdk/property-provider" "3.3.0"
|
||||
"@aws-sdk/shared-ini-file-loader" "3.1.0"
|
||||
"@aws-sdk/types" "3.1.0"
|
||||
tslib "^1.8.0"
|
||||
|
||||
"@aws-sdk/eventstream-marshaller@3.1.0":
|
||||
version "3.1.0"
|
||||
resolved "https://registry.npmjs.org/@aws-sdk/eventstream-marshaller/-/eventstream-marshaller-3.1.0.tgz#65a217e37abcaa162276ccb1d4487d42431d1534"
|
||||
@@ -634,6 +686,14 @@
|
||||
dependencies:
|
||||
tslib "^1.8.0"
|
||||
|
||||
"@aws-sdk/property-provider@3.3.0":
|
||||
version "3.3.0"
|
||||
resolved "https://registry.npmjs.org/@aws-sdk/property-provider/-/property-provider-3.3.0.tgz#49979cb1a3e5562d51807c7403c5fd48cb9f2cdc"
|
||||
integrity sha512-JTyjtXVNhFczL9IfgwXD55F6DqXL50PhfZxFW92t5dDj5VtWpOL74BbuxHQxHBgnQv1FKLr6N9cr7gfXWexDug==
|
||||
dependencies:
|
||||
"@aws-sdk/types" "3.1.0"
|
||||
tslib "^1.8.0"
|
||||
|
||||
"@aws-sdk/protocol-http@3.1.0":
|
||||
version "3.1.0"
|
||||
resolved "https://registry.npmjs.org/@aws-sdk/protocol-http/-/protocol-http-3.1.0.tgz#7da0ebcf02a40a8300f3bd52f9206f25fdf1ca7f"
|
||||
@@ -6147,6 +6207,11 @@
|
||||
resolved "https://registry.npmjs.org/@types/aria-query/-/aria-query-4.2.0.tgz#14264692a9d6e2fa4db3df5e56e94b5e25647ac0"
|
||||
integrity sha512-iIgQNzCm0v7QMhhe4Jjn9uRh+I6GoPmt03CbEtwx3ao8/EfoQcmgtqH4vQ5Db/lxiIGaWDv6nwvunuh0RyX0+A==
|
||||
|
||||
"@types/aws4@^1.5.1":
|
||||
version "1.5.1"
|
||||
resolved "https://registry.npmjs.org/@types/aws4/-/aws4-1.5.1.tgz#361fadab198a030ab398269183ae3fa86e958ed9"
|
||||
integrity sha1-Nh+tqxmKAwqzmCaRg64/qG6Vjtk=
|
||||
|
||||
"@types/babel__core@^7.0.0", "@types/babel__core@^7.1.7":
|
||||
version "7.1.9"
|
||||
resolved "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.9.tgz#77e59d438522a6fb898fa43dc3455c6e72f3963d"
|
||||
@@ -8580,6 +8645,11 @@ aws-sign2@~0.7.0:
|
||||
resolved "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8"
|
||||
integrity sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=
|
||||
|
||||
aws4@^1.11.0:
|
||||
version "1.11.0"
|
||||
resolved "https://registry.npmjs.org/aws4/-/aws4-1.11.0.tgz#d61f46d83b2519250e2784daf5b09479a8b41c59"
|
||||
integrity sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==
|
||||
|
||||
aws4@^1.8.0:
|
||||
version "1.9.1"
|
||||
resolved "https://registry.npmjs.org/aws4/-/aws4-1.9.1.tgz#7e33d8f7d449b3f673cd72deb9abdc552dbe528e"
|
||||
|
||||
Reference in New Issue
Block a user