Merge branch 'master' of https://github.com/spotify/backstage into lintMod

This commit is contained in:
Debajyoti Halder
2021-01-29 14:05:27 +05:30
244 changed files with 4425 additions and 1286 deletions
+10
View File
@@ -1,5 +1,15 @@
# @backstage/plugin-kubernetes-backend
## 0.2.6
### Patch Changes
- 681111228: Add AWS auth provider for Kubernetes
- Updated dependencies [26a3a6cf0]
- Updated dependencies [664dd08c9]
- Updated dependencies [9dd057662]
- @backstage/backend-common@0.5.1
## 0.2.5
### Patch Changes
+9 -73
View File
@@ -1,83 +1,19 @@
# Kubernetes Backend
WORK IN PROGRESS
This is the backend part of the Kubernetes plugin for Backstage. It is called by and responds to requests from the frontend [`@backstage/plugin-kubernetes`](https://github.com/backstage/backstage/tree/master/plugins/kubernetes) plugin.
This is the backend part of the Kubernetes plugin.
It directly interfaces with the Kubernetes API control plane to obtain information about objects that will then be presented at the front end.
It responds to Kubernetes requests from the frontend.
## Introduction
## Configuration
See our announcement blog post [New Backstage feature: Kubernetes for Service Owners](https://backstage.io/blog/2021/01/12/new-backstage-feature-kubernetes-for-service-owners) to learn more about the motivation behind developing the plugin.
### serviceLocatorMethod
## Setup & Configuration
This configures how to determine which clusters a component is running in.
This plugin must be explicitly added to a Backstage app, along with it's peer frontend plugin.
Currently, the only valid serviceLocatorMethod is:
The plugin requires configuration in the Backstage `app-config.yaml` to connect to a Kubernetes API control plane.
#### multiTenant
In addition, configuration of an entity's `catalog-info.yaml` helps identify which specific Kubernetes object(s) should be presented on a specific entity catalog page.
This configuration assumes that all components run on all the provided clusters.
### clusterLocatorMethods
This is used to determine where to retrieve cluster configuration from.
Currently, the only valid serviceLocatorMethod is:
#### config
This clusterLocatorMethod will read cluster information in from config
Example:
```yaml
kubernetes:
serviceLocatorMethod: 'multiTenant'
clusterLocatorMethods:
- 'config'
clusters:
- url: http://127.0.0.1:9999
name: minikube
serviceAccountToken: <TOKEN FROM STEP 4>
authProvider: 'serviceAccount'
- url: http://127.0.0.2:9999
name: gke-cluster-1
authProvider: 'google'
```
##### clusters
Used by the `config` `clusterLocatorMethods` to construct Kubernetes clients.
###### url
The base url to the Kubernetes control plane. Can be found by using the `Kubernetes master` result from running the `kubectl cluster-info` command.
###### name
A name to represent this cluster, this must be unique within the `clusters` array. Users will see this value in the Service Catalog Kubernetes plugin.
###### authProvider
This determines how the Kubernetes client authenticate with the Kubernetes cluster. Valid values are:
| Value | Description |
| ---------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `serviceAccount` | This will use a Kubernetes [service account](https://kubernetes.io/docs/reference/access-authn-authz/service-accounts-admin/) to access the Kubernetes API. When this is used the `serviceAccountToken` field should also be set. |
| `google` | This will use a user's google auth token from the [google auth plugin](https://backstage.io/docs/auth/) to access the Kubernetes API. |
###### serviceAccount (optional)
The service account token to be used when using the `authProvider`, `serviceAccount`.
## RBAC
The current RBAC permissions required are read-only cluster wide, for the following objects:
- pods
- services
- configmaps
- deployments
- replicasets
- horizontalpodautoscalers
- ingresses
For more information, see the [formal documentation about the Kubernetes feature in Backstage](https://backstage.io/docs/features/kubernetes/overview).
+6 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-kubernetes-backend",
"version": "0.2.5",
"version": "0.2.6",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -31,11 +31,13 @@
"clean": "backstage-cli clean"
},
"dependencies": {
"@backstage/backend-common": "^0.5.0",
"@aws-sdk/credential-provider-node": "^3.3.0",
"@backstage/backend-common": "^0.5.1",
"@backstage/catalog-model": "^0.7.0",
"@backstage/config": "^0.1.2",
"@kubernetes/client-node": "^0.13.2",
"@types/express": "^4.17.6",
"aws4": "^1.11.0",
"compression": "^1.7.4",
"cors": "^2.8.5",
"express": "^4.17.1",
@@ -49,7 +51,8 @@
"yn": "^4.0.0"
},
"devDependencies": {
"@backstage/cli": "^0.4.7",
"@backstage/cli": "^0.5.0",
"@types/aws4": "^1.5.1",
"supertest": "^4.0.2"
},
"files": [
+2 -2
View File
@@ -20,8 +20,8 @@ export interface Config {
clusters: {
url: string;
name: string;
serviceAccountToken: string;
authProvider: 'serviceAccount';
serviceAccountToken: string | undefined;
authProvider: 'aws' | 'google' | 'serviceAccount';
}[];
};
}
@@ -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');
});
});
@@ -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;
}
}
@@ -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',
@@ -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';