use aws credentials provider

Signed-off-by: Brian Fletcher <brian@roadie.io>
This commit is contained in:
Brian Fletcher
2023-03-27 09:15:52 +01:00
parent 6636e74a88
commit 192d0173a0
7 changed files with 76 additions and 34 deletions
@@ -14,6 +14,7 @@
* limitations under the License.
*/
import { AwsIamKubernetesAuthTranslator } from './AwsIamKubernetesAuthTranslator';
import { ConfigReader } from '@backstage/config';
let presign = jest.fn(async () => ({
hostname: 'https://example.com',
@@ -21,65 +22,79 @@ let presign = jest.fn(async () => ({
path: '/asdf',
}));
const fromEnv = jest.fn(() => {
return {
AccessId: 'asdf',
};
});
const fromTemporaryCredentials = jest.fn();
const credsManager = {
getCredentialProvider: async () => ({
sdkCredentialProvider: {
AccessKeyId: 'asdf',
},
}),
};
jest.mock('@backstage/integration-aws-node', () => ({
DefaultAwsCredentialsManager: {
fromConfig: () => credsManager,
},
}));
const config = new ConfigReader({});
jest.mock('@aws-sdk/signature-v4', () => ({
SignatureV4: jest.fn().mockImplementation(() => ({
presign,
})),
}));
const fromTemporaryCredentials = jest.fn();
jest.mock('@aws-sdk/credential-providers', () => ({
fromEnv: () => fromEnv(),
fromTemporaryCredentials: (opts: any) => fromTemporaryCredentials(opts),
fromTemporaryCredentials: (opts: any) => {
console.log(`got: ${JSON.stringify(opts)}`);
return fromTemporaryCredentials(opts);
},
}));
describe('AwsIamKubernetesAuthTranslator tests', () => {
beforeEach(() => {});
it('returns a signed url for AWS credentials without assume role', async () => {
const authTranslator = new AwsIamKubernetesAuthTranslator();
const authTranslator = new AwsIamKubernetesAuthTranslator({ config });
const authPromise = authTranslator.decorateClusterDetailsWithAuth({
name: 'test-cluster',
url: '',
authProvider: 'aws',
});
expect(fromEnv).toHaveBeenCalledWith();
expect((await authPromise).serviceAccountToken).toEqual(
'k8s-aws-v1.aHR0cHM6Ly9odHRwczovL2V4YW1wbGUuY29tL2FzZGY_',
);
});
it('returns a signed url for AWS credentials with assume role', async () => {
const authTranslator = new AwsIamKubernetesAuthTranslator();
const authTranslator = new AwsIamKubernetesAuthTranslator({ config });
const authPromise = authTranslator.decorateClusterDetailsWithAuth({
assumeRole: 'asdf',
assumeRole: 'SomeRole',
name: 'test-cluster',
url: '',
authProvider: 'aws',
});
expect((await authPromise).serviceAccountToken).toEqual(
'k8s-aws-v1.aHR0cHM6Ly9odHRwczovL2V4YW1wbGUuY29tL2FzZGY_',
);
expect(fromTemporaryCredentials).toHaveBeenCalledWith({
clientConfig: {
region: 'us-east-1',
},
masterCredentials: fromEnv(),
masterCredentials: {
AccessKeyId: 'asdf',
},
params: {
ExternalId: undefined,
RoleArn: 'asdf',
RoleArn: 'SomeRole',
},
});
expect((await authPromise).serviceAccountToken).toEqual(
'k8s-aws-v1.aHR0cHM6Ly9odHRwczovL2V4YW1wbGUuY29tL2FzZGY_',
);
});
it('returns a signed url for AWS credentials and passes the external id', async () => {
const authTranslator = new AwsIamKubernetesAuthTranslator();
const authTranslator = new AwsIamKubernetesAuthTranslator({ config });
const authPromise = authTranslator.decorateClusterDetailsWithAuth({
assumeRole: 'SomeRole',
@@ -88,19 +103,21 @@ describe('AwsIamKubernetesAuthTranslator tests', () => {
url: '',
authProvider: 'aws',
});
expect((await authPromise).serviceAccountToken).toEqual(
'k8s-aws-v1.aHR0cHM6Ly9odHRwczovL2V4YW1wbGUuY29tL2FzZGY_',
);
expect(fromTemporaryCredentials).toHaveBeenCalledWith({
clientConfig: {
region: 'us-east-1',
},
masterCredentials: fromEnv(),
masterCredentials: {
AccessKeyId: 'asdf',
},
params: {
ExternalId: 'external-id',
RoleArn: 'SomeRole',
},
});
expect((await authPromise).serviceAccountToken).toEqual(
'k8s-aws-v1.aHR0cHM6Ly9odHRwczovL2V4YW1wbGUuY29tL2FzZGY_',
);
});
describe('When the credentials is failing', () => {
@@ -110,7 +127,7 @@ describe('AwsIamKubernetesAuthTranslator tests', () => {
});
});
it('throws the right error', async () => {
const authTranslator = new AwsIamKubernetesAuthTranslator();
const authTranslator = new AwsIamKubernetesAuthTranslator({ config });
await expect(
authTranslator.decorateClusterDetailsWithAuth({
name: 'test-cluster',
@@ -15,12 +15,14 @@
*/
import { AWSClusterDetails } from '../types/types';
import { KubernetesAuthTranslator } from './types';
import {
fromEnv,
fromTemporaryCredentials,
} from '@aws-sdk/credential-providers';
import { fromTemporaryCredentials } from '@aws-sdk/credential-providers';
import { SignatureV4 } from '@aws-sdk/signature-v4';
import { Sha256 } from '@aws-crypto/sha256-js';
import {
AwsCredentialsManager,
DefaultAwsCredentialsManager,
} from '@backstage/integration-aws-node';
import { Config } from '@backstage/config';
/**
*
@@ -41,13 +43,21 @@ const defaultRegion = 'us-east-1';
export class AwsIamKubernetesAuthTranslator
implements KubernetesAuthTranslator
{
private readonly credsManager: AwsCredentialsManager;
constructor(opts: { config: Config }) {
this.credsManager = DefaultAwsCredentialsManager.fromConfig(opts.config);
}
private async getBearerToken(
clusterName: string,
assumeRole?: string,
externalId?: string,
): Promise<string> {
console.log(`here ${clusterName}, ${assumeRole} ${externalId}`);
const region = process.env.AWS_REGION ?? defaultRegion;
let credentials = fromEnv();
let credentials = (await this.credsManager.getCredentialProvider())
.sdkCredentialProvider;
if (assumeRole) {
credentials = fromTemporaryCredentials({
masterCredentials: credentials,
@@ -21,39 +21,44 @@ import { NoopKubernetesAuthTranslator } from './NoopKubernetesAuthTranslator';
import { AwsIamKubernetesAuthTranslator } from './AwsIamKubernetesAuthTranslator';
import { OidcKubernetesAuthTranslator } from './OidcKubernetesAuthTranslator';
import { getVoidLogger } from '@backstage/backend-common';
import { ConfigReader } from '@backstage/config';
const logger = getVoidLogger();
const config = new ConfigReader({});
describe('getKubernetesAuthTranslatorInstance', () => {
const sut = KubernetesAuthTranslatorGenerator;
it('can return an auth translator for google auth', () => {
const authTranslator: KubernetesAuthTranslator =
sut.getKubernetesAuthTranslatorInstance('google', { logger });
sut.getKubernetesAuthTranslatorInstance('google', { logger, config });
expect(authTranslator instanceof GoogleKubernetesAuthTranslator).toBe(true);
});
it('can return an auth translator for aws auth', () => {
const authTranslator: KubernetesAuthTranslator =
sut.getKubernetesAuthTranslatorInstance('aws', { logger });
sut.getKubernetesAuthTranslatorInstance('aws', { logger, config });
expect(authTranslator instanceof AwsIamKubernetesAuthTranslator).toBe(true);
});
it('can return an auth translator for serviceAccount auth', () => {
const authTranslator: KubernetesAuthTranslator =
sut.getKubernetesAuthTranslatorInstance('serviceAccount', { logger });
sut.getKubernetesAuthTranslatorInstance('serviceAccount', {
logger,
config,
});
expect(authTranslator instanceof NoopKubernetesAuthTranslator).toBe(true);
});
it('can return an auth translator for oidc auth', () => {
const authTranslator: KubernetesAuthTranslator =
sut.getKubernetesAuthTranslatorInstance('oidc', { logger });
sut.getKubernetesAuthTranslatorInstance('oidc', { logger, config });
expect(authTranslator instanceof OidcKubernetesAuthTranslator).toBe(true);
});
it('throws an error when asked for an auth translator for an unsupported auth type', () => {
expect(() =>
sut.getKubernetesAuthTranslatorInstance('linode', { logger }),
sut.getKubernetesAuthTranslatorInstance('linode', { logger, config }),
).toThrow(
'authProvider "linode" has no KubernetesAuthTranslator associated with it',
);
@@ -22,6 +22,7 @@ import { AwsIamKubernetesAuthTranslator } from './AwsIamKubernetesAuthTranslator
import { GoogleServiceAccountAuthTranslator } from './GoogleServiceAccountAuthProvider';
import { AzureIdentityKubernetesAuthTranslator } from './AzureIdentityKubernetesAuthTranslator';
import { OidcKubernetesAuthTranslator } from './OidcKubernetesAuthTranslator';
import { Config } from '@backstage/config';
/**
*
@@ -32,6 +33,7 @@ export class KubernetesAuthTranslatorGenerator {
authProvider: string,
options: {
logger: Logger;
config: Config;
},
): KubernetesAuthTranslator {
switch (authProvider) {
@@ -39,7 +41,7 @@ export class KubernetesAuthTranslatorGenerator {
return new GoogleKubernetesAuthTranslator();
}
case 'aws': {
return new AwsIamKubernetesAuthTranslator();
return new AwsIamKubernetesAuthTranslator(options);
}
case 'azure': {
return new AzureIdentityKubernetesAuthTranslator(options.logger);
@@ -121,6 +121,7 @@ export class KubernetesBuilder {
const objectsProvider = this.getObjectsProvider({
logger,
fetcher,
config,
serviceLocator,
customResources,
objectTypesToFetch: this.getObjectTypesToFetch(),
@@ -48,6 +48,7 @@ import {
CurrentResourceUsage,
PodStatus,
} from '@kubernetes/client-node';
import { Config } from '@backstage/config';
const isRejected = (
input: PromiseSettledResult<unknown>,
@@ -196,14 +197,17 @@ export class KubernetesFanOutHandler {
private readonly customResources: CustomResource[];
private readonly objectTypesToFetch: Set<ObjectToFetch>;
private readonly authTranslators: Record<string, KubernetesAuthTranslator>;
private readonly config: Config;
constructor({
config,
logger,
fetcher,
serviceLocator,
customResources,
objectTypesToFetch = DEFAULT_OBJECTS,
}: KubernetesFanOutHandlerOptions) {
this.config = config;
this.logger = logger;
this.fetcher = fetcher;
this.serviceLocator = serviceLocator;
@@ -404,6 +408,7 @@ export class KubernetesFanOutHandler {
provider,
{
logger: this.logger,
config: this.config,
},
);
return this.authTranslators[provider];
@@ -25,6 +25,7 @@ import type {
KubernetesRequestBody,
ObjectsByEntityResponse,
} from '@backstage/plugin-kubernetes-common';
import { Config } from '@backstage/config';
/**
*
@@ -240,6 +241,7 @@ export interface AWSClusterDetails extends ClusterDetails {
*/
export interface KubernetesObjectsProviderOptions {
logger: Logger;
config: Config;
fetcher: KubernetesFetcher;
serviceLocator: KubernetesServiceLocator;
customResources: CustomResource[];