use aws credentials provider
Signed-off-by: Brian Fletcher <brian@roadie.io>
This commit is contained in:
+40
-23
@@ -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
-5
@@ -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,
|
||||
|
||||
+10
-5
@@ -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',
|
||||
);
|
||||
|
||||
+3
-1
@@ -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[];
|
||||
|
||||
Reference in New Issue
Block a user