From e4b2f3dd5fe33611a2eb92da8b22cbbe3742ed6e Mon Sep 17 00:00:00 2001 From: Sean Sundberg Date: Fri, 17 Sep 2021 14:38:16 -0500 Subject: [PATCH] Updates sync command - Adds `--tekton` flag to add privileged scc to tekton pipeline service account - Adds SCC kube api to support SCC interactions Signed-off-by: Sean Sundberg --- src/api/kubectl/config-map.ts | 4 +- src/api/kubectl/deployment.ts | 4 +- src/api/kubectl/ingress.ts | 4 +- .../kubernetes-resource-manager.spec.ts | 8 +- .../kubectl/kubernetes-resource-manager.ts | 200 +++++++++++++++++- src/api/kubectl/pod.ts | 4 +- src/api/kubectl/role-binding.ts | 4 +- src/api/kubectl/role.ts | 6 +- src/api/kubectl/route.ts | 4 +- src/api/kubectl/secrets.ts | 4 +- .../kubectl/security-context-contraints.ts | 26 +++ src/api/kubectl/service-account.ts | 4 +- src/api/kubectl/tekton-pipeline-resource.ts | 4 +- src/api/kubectl/tekton-pipeline-run.ts | 4 +- src/api/kubectl/tekton-pipeline.ts | 4 +- src/api/kubectl/tekton-task.ts | 4 +- src/api/kubectl/tekton-trigger-binding.ts | 4 +- .../kubectl/tekton-trigger-event-listener.ts | 4 +- src/api/kubectl/tekton-trigger-template.ts | 4 +- src/commands/namespace.ts | 6 + .../namespace/namespace-options.model.ts | 1 + src/services/namespace/namespace.ts | 29 ++- 22 files changed, 296 insertions(+), 40 deletions(-) create mode 100644 src/api/kubectl/security-context-contraints.ts diff --git a/src/api/kubectl/config-map.ts b/src/api/kubectl/config-map.ts index 73e60391..4918b643 100644 --- a/src/api/kubectl/config-map.ts +++ b/src/api/kubectl/config-map.ts @@ -1,4 +1,4 @@ -import {AbstractKubernetesResourceManager, KubeResource, ListOptions, Props} from './kubernetes-resource-manager'; +import {AbstractKubernetesNamespacedResource, KubeResource, ListOptions, Props} from './kubernetes-resource-manager'; import {BuildContext, Factory, ObjectFactory} from 'typescript-ioc'; import {AsyncKubeClient} from './client'; @@ -15,7 +15,7 @@ const factory: ObjectFactory = (context: BuildContext) => { }; @Factory(factory) -export class KubeConfigMap extends AbstractKubernetesResourceManager { +export class KubeConfigMap extends AbstractKubernetesNamespacedResource { constructor(props: Props) { super(props); } diff --git a/src/api/kubectl/deployment.ts b/src/api/kubectl/deployment.ts index 7bc5ae1e..78a6c02b 100644 --- a/src/api/kubectl/deployment.ts +++ b/src/api/kubectl/deployment.ts @@ -1,6 +1,6 @@ import {BuildContext, Factory, Inject, ObjectFactory} from 'typescript-ioc'; import {AsyncKubeClient} from './client'; -import {AbstractKubernetesResourceManager, KubeResource, Props} from './kubernetes-resource-manager'; +import {AbstractKubernetesNamespacedResource, KubeResource, Props} from './kubernetes-resource-manager'; import {timer} from '../../util/timer'; import {Logger} from '../../util/logger'; @@ -25,7 +25,7 @@ const factory: ObjectFactory = (context: BuildContext) => { }; @Factory(factory) -export class KubeDeployment extends AbstractKubernetesResourceManager { +export class KubeDeployment extends AbstractKubernetesNamespacedResource { @Inject logger: Logger; constructor(props: Props) { diff --git a/src/api/kubectl/ingress.ts b/src/api/kubectl/ingress.ts index ba7a542e..f9eff47f 100644 --- a/src/api/kubectl/ingress.ts +++ b/src/api/kubectl/ingress.ts @@ -2,7 +2,7 @@ import {BuildContext, Factory, ObjectFactory} from 'typescript-ioc'; import * as _ from 'lodash'; import {AsyncKubeClient, KubeClient} from './client'; -import {AbstractKubernetesResourceManager, KubeResource, Props} from './kubernetes-resource-manager'; +import {AbstractKubernetesNamespacedResource, KubeResource, Props} from './kubernetes-resource-manager'; export interface Ingress extends KubeResource { spec: { @@ -36,7 +36,7 @@ const factory: ObjectFactory = (context: BuildContext) => { }; @Factory(factory) -export class KubeIngress extends AbstractKubernetesResourceManager { +export class KubeIngress extends AbstractKubernetesNamespacedResource { constructor(props: Props) { super(props); } diff --git a/src/api/kubectl/kubernetes-resource-manager.spec.ts b/src/api/kubectl/kubernetes-resource-manager.spec.ts index 8164df65..f9efa275 100644 --- a/src/api/kubectl/kubernetes-resource-manager.spec.ts +++ b/src/api/kubectl/kubernetes-resource-manager.spec.ts @@ -1,5 +1,5 @@ import { - AbstractKubernetesResourceManager, + AbstractKubernetesNamespacedResource, KubeBody, KubeResource, KubeResourceList, @@ -30,7 +30,7 @@ export const testV1Provider: ObjectFactory = (context: BuildContext) => { }; @Factory(testV1Provider) -class TestV1KubernetesResource extends AbstractKubernetesResourceManager { +class TestV1KubernetesResource extends AbstractKubernetesNamespacedResource { } @@ -45,7 +45,7 @@ export const testV1Beta1Provider: ObjectFactory = (context: BuildContext) => { }; @Factory(testV1Beta1Provider) -class TestV1Beta1KubernetesResource extends AbstractKubernetesResourceManager { +class TestV1Beta1KubernetesResource extends AbstractKubernetesNamespacedResource { constructor(props: Props) { super(props); } @@ -56,7 +56,7 @@ describe('kubernetes-resource-manager', () => { expect(true).toEqual(true); }); - let classUnderTest: AbstractKubernetesResourceManager; + let classUnderTest: AbstractKubernetesNamespacedResource; let mockClient: KubeClient; beforeEach(() => { mockClient = buildMockKubeClient(); diff --git a/src/api/kubectl/kubernetes-resource-manager.ts b/src/api/kubectl/kubernetes-resource-manager.ts index b50c3d84..c96ae9ca 100644 --- a/src/api/kubectl/kubernetes-resource-manager.ts +++ b/src/api/kubectl/kubernetes-resource-manager.ts @@ -60,7 +60,21 @@ export interface QueryString { pretty?: boolean; } -export abstract class KubernetesResourceManager { +export abstract class KubernetesClusterResource { + abstract list(options?: ListOptions): Promise>; + + abstract createOrUpdate(name: string, body: KubeBody): Promise; + + abstract create(name: string, body: KubeBody): Promise; + + abstract update(name: string, body: KubeBody): Promise; + + abstract exists(name: string): Promise; + + abstract 'get'(name: string): Promise; +} + +export abstract class KubernetesNamespacedResource { abstract list(options?: ListOptions): Promise>; abstract createOrUpdate(name: string, body: KubeBody, namespace?: string): Promise; @@ -117,7 +131,189 @@ async function registerCrdSchema(wrappedClient: AsyncKubeClient, name: string): } } -export class AbstractKubernetesResourceManager implements KubernetesResourceManager { + +export class AbstractKubernetesClusterResource implements KubernetesClusterResource { + public client: AsyncKubeClient; + public group?: string; + version: string; + name: string; + kind: string; + crdPromise: Promise; + + constructor(props: Props) { + this.client = props.client; + this.group = props.group; + this.version = props.version || 'v1'; + this.name = props.name; + this.kind = props.kind; + + if (!this.name || !this.kind) { + throw new Error('kind must be defined'); + } + + if (props.crd) { + this.crdPromise = registerCrdSchema(this.client, `${this.name}.${this.group}`); + } else { + this.crdPromise = Promise.resolve(true); + } + } + + async list(options: ListOptions = {}): Promise> { + + const getOptions: Query = this.buildQuery(options); + + const kubeResource: any = await this.resourceNode(this.group, this.version, this.name); + + if (kubeResource) { + const result: KubeBody> = await kubeResource.get(getOptions); + + const items: T[] = _.get(result, 'body.items', []) + .filter(options.filter || (() => true)) + .map(options.map || (val => val)); + + return items; + } else { + return []; + } + } + + buildQuery(options: ListOptions): Query { + return {qs: options.qs}; + } + + async createOrUpdate(name: string, input: KubeBody): Promise { + + const kubeResource = await this.resourceNode(this.group, this.version, this.name); + + if (kubeResource) { + const processedName = this.processName(name); + + if (await this.exists(processedName)) { + const current: T = await this.get(processedName); + + const processedBody = this.processInputs(processedName, input.body, current); + + return kubeResource(processedName).put(processedBody).then(result => result.body); + } else { + const processedBody = this.processInputs(processedName, input.body); + + return kubeResource.post(processedBody).then(result => result.body); + } + } else { + return {} as any; + } + } + + async create(name: string, input: KubeBody): Promise { + + const kubeResource = await this.resourceNode(this.group, this.version, this.name); + + const processedName = this.processName(name); + const processedBody = this.processInputs(processedName, input.body); + + const result: KubeBody = await kubeResource.post(processedBody); + + return result.body; + } + + async update(name: string, input: KubeBody): Promise { + + const kubeResource = await this.resourceNode(this.group, this.version, this.name); + + const current: T = await this.get(name); + + const processedName = this.processName(name); + const processedBody = this.processInputs(processedName, input.body, current); + + const result: KubeBody = await kubeResource(processedName).put(processedBody); + + return result.body; + } + + async exists(name: string): Promise { + const kubeResource = await this.resourceNode(this.group, this.version, this.name); + + try { + const result = await kubeResource(name).get(); + + if (result) { + return true; + } + } catch (err) { + } + + return false; + } + + async get(name: string): Promise { + const kubeResource = await this.resourceNode(this.group, this.version, this.name); + + const result = await kubeResource(name).get(); + + return _.get(result, 'body'); + } + + async getLabel(name: string, labelName: string): Promise { + const resource = await this.get(name); + + const labels: string[] = Object.keys(resource.metadata.labels || {}) + .filter(name => name === labelName) + .map(name => resource.metadata.labels[name]); + + return labels.length > 0 ? labels[0] : undefined; + } + + async resourceNode(group: string | undefined, version: string, kind: string) { + + await this.crdPromise; + + const versionPath: string[] = !group + ? ['api', version] + : ['apis', group, version]; + + const client: KubeClient = await this.client.get(); + + const versionNode = _.get(client, versionPath); + + if (versionNode) { + return _.get(versionNode, kind); + } + } + + processName(name: string): string { + return name.toLowerCase().replace(new RegExp('_', 'g'), '-'); + } + + processInputs(name: string, input: T, current?: T): KubeBody { + + const processedBody: KubeBody = { + body: Object.assign( + { + kind: this.kind, + apiVersion: this.group ? `${this.group}/${this.version}` : this.version, + }, + current, + input, + { + metadata: Object.assign( + {}, + input.metadata, + { + name, + } as KubeMetadata, + current + ? {resourceVersion: current.metadata.resourceVersion} as KubeMetadata + : {}, + ), + }, + ), + }; + + return processedBody; + } +} + +export class AbstractKubernetesNamespacedResource implements KubernetesNamespacedResource { public client: AsyncKubeClient; public group?: string; version: string; diff --git a/src/api/kubectl/pod.ts b/src/api/kubectl/pod.ts index 7ea117ed..aacacf12 100644 --- a/src/api/kubectl/pod.ts +++ b/src/api/kubectl/pod.ts @@ -1,4 +1,4 @@ -import {AbstractKubernetesResourceManager, KubeResource, Props} from './kubernetes-resource-manager'; +import {AbstractKubernetesNamespacedResource, KubeResource, Props} from './kubernetes-resource-manager'; import {BuildContext, Factory, ObjectFactory} from 'typescript-ioc'; import {AsyncKubeClient} from './client'; @@ -15,7 +15,7 @@ const factory: ObjectFactory = (context: BuildContext) => { }; @Factory(factory) -export class KubePod extends AbstractKubernetesResourceManager { +export class KubePod extends AbstractKubernetesNamespacedResource { constructor(props: Props) { super(props); } diff --git a/src/api/kubectl/role-binding.ts b/src/api/kubectl/role-binding.ts index 96958e8a..ca1b5e81 100644 --- a/src/api/kubectl/role-binding.ts +++ b/src/api/kubectl/role-binding.ts @@ -1,6 +1,6 @@ import {BuildContext, Factory, ObjectFactory} from 'typescript-ioc'; import {AsyncKubeClient} from './client'; -import {AbstractKubernetesResourceManager, KubeResource, Props} from './kubernetes-resource-manager'; +import {AbstractKubernetesNamespacedResource, KubeResource, Props} from './kubernetes-resource-manager'; export interface RoleRef { apiGroup: string; @@ -30,7 +30,7 @@ const factory: ObjectFactory = (context: BuildContext) => { }; @Factory(factory) -export class KubeRoleBinding extends AbstractKubernetesResourceManager { +export class KubeRoleBinding extends AbstractKubernetesNamespacedResource { constructor(props: Props) { super(props); } diff --git a/src/api/kubectl/role.ts b/src/api/kubectl/role.ts index efc9af84..3170e0ea 100644 --- a/src/api/kubectl/role.ts +++ b/src/api/kubectl/role.ts @@ -1,6 +1,6 @@ import {BuildContext, Container, Factory, ObjectFactory} from 'typescript-ioc'; import {AsyncKubeClient} from './client'; -import {AbstractKubernetesResourceManager, KubeResource, Props} from './kubernetes-resource-manager'; +import {AbstractKubernetesNamespacedResource, KubeResource, Props} from './kubernetes-resource-manager'; export interface RoleRule { apiGroups: string[]; @@ -29,7 +29,7 @@ const factory: ObjectFactory = (context: BuildContext) => { }; @Factory(factory) -export class KubeRole extends AbstractKubernetesResourceManager { +export class KubeRole extends AbstractKubernetesNamespacedResource { constructor(props: Props) { super(props); } @@ -89,4 +89,4 @@ function reconcileVerbsInPlace(rule: RoleRule, verbs: string[]): string[] { rule.verbs = rule.verbs.concat(verbs).filter(distinct); return rule.verbs; -} \ No newline at end of file +} diff --git a/src/api/kubectl/route.ts b/src/api/kubectl/route.ts index cddc1812..97ec25bd 100644 --- a/src/api/kubectl/route.ts +++ b/src/api/kubectl/route.ts @@ -2,7 +2,7 @@ import {BuildContext, Factory, ObjectFactory} from 'typescript-ioc'; import * as _ from 'lodash'; import {AsyncOcpClient} from './client'; -import {AbstractKubernetesResourceManager, KubeResource, Props} from './kubernetes-resource-manager'; +import {AbstractKubernetesNamespacedResource, KubeResource, Props} from './kubernetes-resource-manager'; export interface Route extends KubeResource { spec: { @@ -35,7 +35,7 @@ const factory: ObjectFactory = (context: BuildContext) => { }; @Factory(factory) -export class OcpRoute extends AbstractKubernetesResourceManager { +export class OcpRoute extends AbstractKubernetesNamespacedResource { constructor(props: Props) { super(props); } diff --git a/src/api/kubectl/secrets.ts b/src/api/kubectl/secrets.ts index 2cccb3f7..89831ef5 100644 --- a/src/api/kubectl/secrets.ts +++ b/src/api/kubectl/secrets.ts @@ -1,6 +1,6 @@ import {BuildContext, Factory, ObjectFactory} from 'typescript-ioc'; import {AsyncKubeClient} from './client'; -import {AbstractKubernetesResourceManager, KubeResource, ListOptions, Props} from './kubernetes-resource-manager'; +import {AbstractKubernetesNamespacedResource, KubeResource, ListOptions, Props} from './kubernetes-resource-manager'; import {decode as base64decode} from '../../util/base64'; export interface Secret extends KubeResource { @@ -18,7 +18,7 @@ const factory: ObjectFactory = (context: BuildContext) => { }; @Factory(factory) -export class KubeSecret extends AbstractKubernetesResourceManager { +export class KubeSecret extends AbstractKubernetesNamespacedResource { constructor(props: Props) { super(props); } diff --git a/src/api/kubectl/security-context-contraints.ts b/src/api/kubectl/security-context-contraints.ts new file mode 100644 index 00000000..c2b681b7 --- /dev/null +++ b/src/api/kubectl/security-context-contraints.ts @@ -0,0 +1,26 @@ +import {AbstractKubernetesClusterResource, KubeMetadata, KubeResource, Props} from './kubernetes-resource-manager'; +import {BuildContext, Factory, ObjectFactory} from 'typescript-ioc'; +import {AsyncKubeClient} from './client'; + +export interface SecurityContextContraints extends KubeResource { + users: string[]; + groups: string[]; +} + +const factory: ObjectFactory = (context: BuildContext) => { + return new KubeSecurityContextConstraints({ + client: context.resolve(AsyncKubeClient), + group: 'security.openshift.io', + version: 'v1', + name: 'securitycontextconstraints', + kind: 'SecurityContextConstraints', + crd: true, + }) +} + +@Factory(factory) +export class KubeSecurityContextConstraints extends AbstractKubernetesClusterResource { + constructor(props: Props) { + super(props); + } +} diff --git a/src/api/kubectl/service-account.ts b/src/api/kubectl/service-account.ts index 1e3f23dc..6b509634 100644 --- a/src/api/kubectl/service-account.ts +++ b/src/api/kubectl/service-account.ts @@ -1,6 +1,6 @@ import {BuildContext, Container, Factory, ObjectFactory} from 'typescript-ioc'; -import {AbstractKubernetesResourceManager, KubeResource, Props} from './kubernetes-resource-manager'; +import {AbstractKubernetesNamespacedResource, KubeResource, Props} from './kubernetes-resource-manager'; import {AsyncKubeClient} from './client'; export interface ServiceAccount extends KubeResource { @@ -17,7 +17,7 @@ const factory: ObjectFactory = (context: BuildContext) => { }; @Factory(factory) -export class KubeServiceAccount extends AbstractKubernetesResourceManager { +export class KubeServiceAccount extends AbstractKubernetesNamespacedResource { constructor(props: Props) { super(props); } diff --git a/src/api/kubectl/tekton-pipeline-resource.ts b/src/api/kubectl/tekton-pipeline-resource.ts index 5fdff0ec..330cf363 100644 --- a/src/api/kubectl/tekton-pipeline-resource.ts +++ b/src/api/kubectl/tekton-pipeline-resource.ts @@ -1,7 +1,7 @@ import {BuildContext, Factory, ObjectFactory} from 'typescript-ioc'; import {AsyncKubeClient} from './client'; -import {AbstractKubernetesResourceManager, KubeResource, Props} from './kubernetes-resource-manager'; +import {AbstractKubernetesNamespacedResource, KubeResource, Props} from './kubernetes-resource-manager'; export interface TektonPipelineResource extends KubeResource { spec: { @@ -25,7 +25,7 @@ const factory: ObjectFactory = (context: BuildContext) => { }; @Factory(factory) -export class KubeTektonPipelineResource extends AbstractKubernetesResourceManager { +export class KubeTektonPipelineResource extends AbstractKubernetesNamespacedResource { constructor(props: Props) { super(props); } diff --git a/src/api/kubectl/tekton-pipeline-run.ts b/src/api/kubectl/tekton-pipeline-run.ts index 9256bf8c..734ab11e 100644 --- a/src/api/kubectl/tekton-pipeline-run.ts +++ b/src/api/kubectl/tekton-pipeline-run.ts @@ -1,7 +1,7 @@ import {BuildContext, Factory, ObjectFactory} from 'typescript-ioc'; import {AsyncKubeClient} from './client'; -import {AbstractKubernetesResourceManager, KubeMetadata, KubeResource, Props} from './kubernetes-resource-manager'; +import {AbstractKubernetesNamespacedResource, KubeMetadata, KubeResource, Props} from './kubernetes-resource-manager'; export interface TektonPipelineRun extends KubeResource { spec: { @@ -42,7 +42,7 @@ const factory: ObjectFactory = (context: BuildContext) => { }; @Factory(factory) -export class KubeTektonPipelineRun extends AbstractKubernetesResourceManager { +export class KubeTektonPipelineRun extends AbstractKubernetesNamespacedResource { constructor(props: Props) { super(props); } diff --git a/src/api/kubectl/tekton-pipeline.ts b/src/api/kubectl/tekton-pipeline.ts index 020e6b3d..3ae06dd4 100644 --- a/src/api/kubectl/tekton-pipeline.ts +++ b/src/api/kubectl/tekton-pipeline.ts @@ -1,7 +1,7 @@ import {BuildContext, Factory, ObjectFactory} from 'typescript-ioc'; import {AsyncKubeClient} from './client'; -import {AbstractKubernetesResourceManager, KubeResource, Props} from './kubernetes-resource-manager'; +import {AbstractKubernetesNamespacedResource, KubeResource, Props} from './kubernetes-resource-manager'; export interface TektonPipelineParam { type?: 'string' | 'array'; @@ -33,7 +33,7 @@ const factory: ObjectFactory = (context: BuildContext) => { }; @Factory(factory) -export class KubeTektonPipeline extends AbstractKubernetesResourceManager { +export class KubeTektonPipeline extends AbstractKubernetesNamespacedResource { constructor(props: Props) { super(props); } diff --git a/src/api/kubectl/tekton-task.ts b/src/api/kubectl/tekton-task.ts index b1a301e6..b39a6606 100644 --- a/src/api/kubectl/tekton-task.ts +++ b/src/api/kubectl/tekton-task.ts @@ -1,7 +1,7 @@ import {BuildContext, Factory, ObjectFactory} from 'typescript-ioc'; import {AsyncKubeClient} from './client'; -import {AbstractKubernetesResourceManager, KubeResource, Props} from './kubernetes-resource-manager'; +import {AbstractKubernetesNamespacedResource, KubeResource, Props} from './kubernetes-resource-manager'; export interface TektonTask extends KubeResource { spec: { @@ -33,7 +33,7 @@ const factory: ObjectFactory = (context: BuildContext) => { }; @Factory(factory) -export class KubeTektonTask extends AbstractKubernetesResourceManager { +export class KubeTektonTask extends AbstractKubernetesNamespacedResource { constructor(props: Props) { super(props); } diff --git a/src/api/kubectl/tekton-trigger-binding.ts b/src/api/kubectl/tekton-trigger-binding.ts index 83e54fa4..1fbaa518 100644 --- a/src/api/kubectl/tekton-trigger-binding.ts +++ b/src/api/kubectl/tekton-trigger-binding.ts @@ -1,7 +1,7 @@ import {BuildContext, Factory, ObjectFactory} from 'typescript-ioc'; import {AsyncKubeClient} from './client'; -import {AbstractKubernetesResourceManager, KubeResource, Props} from './kubernetes-resource-manager'; +import {AbstractKubernetesNamespacedResource, KubeResource, Props} from './kubernetes-resource-manager'; export interface TriggerBinding extends KubeResource { spec: { @@ -24,7 +24,7 @@ const factory: ObjectFactory = (context: BuildContext) => { }; @Factory(factory) -export class KubeTektonTriggerBinding extends AbstractKubernetesResourceManager { +export class KubeTektonTriggerBinding extends AbstractKubernetesNamespacedResource { constructor(props: Props) { super(props); } diff --git a/src/api/kubectl/tekton-trigger-event-listener.ts b/src/api/kubectl/tekton-trigger-event-listener.ts index a51f3e01..c751f19c 100644 --- a/src/api/kubectl/tekton-trigger-event-listener.ts +++ b/src/api/kubectl/tekton-trigger-event-listener.ts @@ -1,7 +1,7 @@ import {BuildContext, Container, Factory, ObjectFactory} from 'typescript-ioc'; import {AsyncKubeClient} from './client'; -import {AbstractKubernetesResourceManager, KubeResource, Props} from './kubernetes-resource-manager'; +import {AbstractKubernetesNamespacedResource, KubeResource, Props} from './kubernetes-resource-manager'; export interface TriggerBindingRef_v0_4 { name: string; @@ -81,7 +81,7 @@ const factory: ObjectFactory = (context: BuildContext) => { }; @Factory(factory) -export class KubeTektonTriggerEventListener extends AbstractKubernetesResourceManager> { +export class KubeTektonTriggerEventListener extends AbstractKubernetesNamespacedResource> { constructor(props: Props) { super(props); } diff --git a/src/api/kubectl/tekton-trigger-template.ts b/src/api/kubectl/tekton-trigger-template.ts index 1b7e3707..65aaa91e 100644 --- a/src/api/kubectl/tekton-trigger-template.ts +++ b/src/api/kubectl/tekton-trigger-template.ts @@ -2,7 +2,7 @@ import {BuildContext, Factory, ObjectFactory} from 'typescript-ioc'; import {AsyncKubeClient} from './client'; import { - AbstractKubernetesResourceManager, + AbstractKubernetesNamespacedResource, KubeResource, Props, TemplateKubeMetadata @@ -31,7 +31,7 @@ const factory: ObjectFactory = (context: BuildContext) => { }; @Factory(factory) -export class KubeTektonTriggerTemplate extends AbstractKubernetesResourceManager { +export class KubeTektonTriggerTemplate extends AbstractKubernetesNamespacedResource { constructor(props: Props) { super(props); } diff --git a/src/commands/namespace.ts b/src/commands/namespace.ts index b31884df..eb4ec631 100644 --- a/src/commands/namespace.ts +++ b/src/commands/namespace.ts @@ -26,6 +26,12 @@ export const builder = (yargs: Argv) => { default: 'default', type: 'string', }) + .option('tekton', { + alias: 'p', + describe: 'flag indicating the tekton pipeline service account should be given privileged scc', + default: false, + type: 'boolean' + }) .option('verbose', { describe: 'flag to produce more verbose logging', type: 'boolean' diff --git a/src/services/namespace/namespace-options.model.ts b/src/services/namespace/namespace-options.model.ts index 58a4f44d..e4aa5628 100644 --- a/src/services/namespace/namespace-options.model.ts +++ b/src/services/namespace/namespace-options.model.ts @@ -3,4 +3,5 @@ export class NamespaceOptionsModel { namespace: string; templateNamespace: string; serviceAccount: string; + tekton?: boolean; } diff --git a/src/services/namespace/namespace.ts b/src/services/namespace/namespace.ts index 46e8f6f0..08578c82 100644 --- a/src/services/namespace/namespace.ts +++ b/src/services/namespace/namespace.ts @@ -25,6 +25,7 @@ import { } from '../../api/kubectl'; import {ClusterType} from '../../util/cluster-type'; import {ChildProcess} from '../../util/child-process'; +import {KubeSecurityContextConstraints, SecurityContextContraints} from '../../api/kubectl/security-context-contraints'; const noopNotifyStatus: (status: string) => void = () => { }; @@ -56,6 +57,8 @@ export class NamespaceImpl implements Namespace { private childProcess: ChildProcess; @Inject private createServiceAccount: CreateServiceAccount; + @Inject + private sccs: KubeSecurityContextConstraints; async getCurrentProject(defaultValue?: string): Promise { const currentContextResult = await this.childProcess.exec('kubectl config view -o jsonpath=\'{.current-context}\''); @@ -91,7 +94,7 @@ export class NamespaceImpl implements Namespace { return namespace; } - async create({namespace, templateNamespace, serviceAccount}: NamespaceOptionsModel, notifyStatus: (status: string) => void = noopNotifyStatus): Promise { + async create({namespace, templateNamespace, serviceAccount, tekton}: NamespaceOptionsModel, notifyStatus: (status: string) => void = noopNotifyStatus): Promise { const {clusterType, serverUrl} = await this.clusterType.getClusterType(templateNamespace); @@ -109,6 +112,16 @@ export class NamespaceImpl implements Namespace { notifyStatus('Copying Secrets'); await this.copySecrets(namespace, templateNamespace); + if (tekton) { + try { + notifyStatus('Adding privileged SCC to pipeline service account'); + await this.setupTektonPipelineSA(namespace); + } catch (error) { + notifyStatus(' Error adding SCC'); + throw error; + } + } + notifyStatus(`Setting current ${label} to ${namespace}`) await this.setCurrentProject(namespace); @@ -368,6 +381,20 @@ export class NamespaceImpl implements Namespace { async setCurrentProject(namespace: string) { await this.childProcess.exec(`kubectl config set-context --current --namespace=${namespace}`); } + + async setupTektonPipelineSA(namespace: string) { + + const privilegedSCC: SecurityContextContraints = await this.sccs.get('privileged'); + + const users: string[] = privilegedSCC.users; + + const pipelineSA = `system:serviceaccount:${namespace}:pipeline`; + if (!users.includes(pipelineSA)) { + privilegedSCC.users.push(pipelineSA); + + await this.sccs.update(privilegedSCC.metadata.name, {body: privilegedSCC}) + } + } } function any(list: string[], test: (value: string) => boolean): boolean {