diff --git a/src/getStepStartStates.ts b/src/getStepStartStates.ts index 5ff4634..a2d73ce 100644 --- a/src/getStepStartStates.ts +++ b/src/getStepStartStates.ts @@ -4,18 +4,15 @@ import { StepStartStates, } from '@jupiterone/integration-sdk-core'; import { IntegrationConfig } from './config'; -import { - AuthorizationClient, - ResourceType, - VerbType, -} from './kubernetes/clients/authorization'; import { IntegrationSteps } from './steps/constants'; import { validateInvocation } from './validator'; +import getOrCreateAPIClient from './kubernetes/getOrCreateAPIClient'; +import { Client, ResourceType, VerbType } from './kubernetes/client'; async function getServiceState( resource: ResourceType, verb: VerbType, - client: AuthorizationClient, + client: Client, ) { const serviceAccess = await client.fetchSubjectServiceAccess(resource, verb); @@ -29,7 +26,7 @@ export default async function getStepStartStates( const { instance, logger } = context; const { config } = instance; - const client = new AuthorizationClient(config); + const client = getOrCreateAPIClient(config); try { const [ diff --git a/src/kubernetes/client.ts b/src/kubernetes/client.ts index 1c01f73..403448d 100644 --- a/src/kubernetes/client.ts +++ b/src/kubernetes/client.ts @@ -1,18 +1,56 @@ import * as k8s from '@kubernetes/client-node'; import { IntegrationConfig } from '../config'; +import { IntegrationProviderAuthenticationError } from '@jupiterone/integration-sdk-core'; export interface ClientOptions { config: IntegrationConfig; } +export type ResourceType = + | 'nodes' + | 'services' + | 'deployments' + | 'replicasets' + | 'statefulsets' + | 'daemonsets' + | 'jobs' + | 'cronjobs' + | 'configmaps' + | 'secrets' + | 'pods'; + +export type VerbType = 'list' | 'create'; + export class Client { public kubeConfig: k8s.KubeConfig; public config: IntegrationConfig; protected readonly maxPerPage = 250; + private appsClient: k8s.AppsV1Api; + private authorizationClient: k8s.AuthorizationV1Api; + private batchClient: k8s.BatchV1Api; + private batchV1BetaClient: k8s.BatchV1beta1Api; + private coreClient: k8s.CoreV1Api; + private netwokClient: k8s.NetworkingV1Api; + private policyClient: k8s.PolicyV1beta1Api; + private rbacClient: k8s.RbacAuthorizationV1Api; + constructor(config: IntegrationConfig) { this.config = config; this.kubeConfig = new k8s.KubeConfig(); + + this.authenticate(); + + this.appsClient = this.kubeConfig.makeApiClient(k8s.AppsV1Api); + this.authorizationClient = this.kubeConfig.makeApiClient( + k8s.AuthorizationV1Api, + ); + this.batchClient = this.kubeConfig.makeApiClient(k8s.BatchV1Api); + this.batchV1BetaClient = this.kubeConfig.makeApiClient(k8s.BatchV1beta1Api); + this.coreClient = this.kubeConfig.makeApiClient(k8s.CoreV1Api); + this.netwokClient = this.kubeConfig.makeApiClient(k8s.NetworkingV1Api); + this.policyClient = this.kubeConfig.makeApiClient(k8s.PolicyV1beta1Api); + this.rbacClient = this.kubeConfig.makeApiClient(k8s.RbacAuthorizationV1Api); } async iterateApi( @@ -35,4 +73,523 @@ export class Client { this.kubeConfig.loadFromCluster(); } } + + /** + * Apps + */ + async iterateNamespacedDeployments( + namespace: string, + callback: (data: k8s.V1Deployment) => Promise, + ): Promise { + await this.iterateApi( + async (nextPageToken) => { + return this.appsClient.listNamespacedDeployment( + namespace, + undefined, + undefined, + nextPageToken, + undefined, + undefined, + this.maxPerPage, + ); + }, + async (data: k8s.V1DeploymentList) => { + for (const deployment of data.items || []) { + await callback(deployment); + } + }, + ); + } + + async iterateReplicaSets( + namespace: string, + callback: (data: k8s.V1ReplicaSet) => Promise, + ): Promise { + await this.iterateApi( + async (nextPageToken) => { + return this.appsClient.listNamespacedReplicaSet( + namespace, + undefined, + undefined, + nextPageToken, + undefined, + undefined, + this.maxPerPage, + ); + }, + async (data: k8s.V1ReplicaSetList) => { + for (const replicaSet of data.items || []) { + await callback(replicaSet); + } + }, + ); + } + + async iterateStatefulSets( + namespace: string, + callback: (data: k8s.V1StatefulSet) => Promise, + ): Promise { + await this.iterateApi( + async (nextPageToken) => { + return this.appsClient.listNamespacedStatefulSet( + namespace, + undefined, + undefined, + nextPageToken, + undefined, + undefined, + this.maxPerPage, + ); + }, + async (data: k8s.V1StatefulSetList) => { + for (const statefulSet of data.items || []) { + await callback(statefulSet); + } + }, + ); + } + + async iterateDaemonSets( + namespace: string, + callback: (data: k8s.V1DaemonSet) => Promise, + ): Promise { + await this.iterateApi( + async (nextPageToken) => { + return this.appsClient.listNamespacedDaemonSet( + namespace, + undefined, + undefined, + nextPageToken, + undefined, + undefined, + this.maxPerPage, + ); + }, + async (data: k8s.V1DaemonSetList) => { + for (const daemonSet of data.items || []) { + await callback(daemonSet); + } + }, + ); + } + + /** + * Authorization + */ + async fetchSubjectServiceAccess( + resource: ResourceType, + verb: VerbType, + ): Promise { + const groupAppsServices = ['deployments', 'replicasets']; + const resp = await this.authorizationClient.createSelfSubjectAccessReview({ + kind: 'SelfSubjectAccessReview', + apiVersion: 'authorization.k8s.io/v1', + spec: { + resourceAttributes: { + namespace: 'default', + verb: verb, + group: groupAppsServices.includes(resource) ? 'apps' : undefined, + resource: resource, + }, + }, + }); + + return resp.body; + } + + /** + * Batch + */ + async iterateNamespacedJobs( + namespace: string, + callback: (data: k8s.V1Job) => Promise, + ): Promise { + await this.iterateApi( + async (nextPageToken) => { + return this.batchClient.listNamespacedJob( + namespace, + undefined, + undefined, + nextPageToken, + undefined, + undefined, + this.maxPerPage, + ); + }, + async (data: k8s.V1JobList) => { + for (const job of data.items || []) { + await callback(job); + } + }, + ); + } + + /** + * Batch1beta + */ + async iterateNamespacedCronJobs( + namespace: string, + callback: (data: k8s.V1beta1CronJob) => Promise, + ): Promise { + await this.iterateApi( + async (nextPageToken) => { + return this.batchV1BetaClient.listNamespacedCronJob( + namespace, + undefined, + undefined, + nextPageToken, + undefined, + undefined, + this.maxPerPage, + ); + }, + async (data: k8s.V1beta1CronJobList) => { + for (const cronJob of data.items || []) { + await callback(cronJob); + } + }, + ); + } + + /** + * Core + */ + async verifyAuthentication() { + try { + await this.coreClient.listNamespace(); + } catch (err) { + throw new IntegrationProviderAuthenticationError({ + cause: err, + endpoint: '/apis/apps/v1/namespaces', + status: 400, + statusText: err.message, + }); + } + } + + async iterateNamespaces( + callback: (data: k8s.V1Namespace) => Promise, + ): Promise { + const { accessType, namespace } = this.config; + + if (accessType?.toLowerCase() === 'namespace') { + const resp = await this.coreClient.readNamespace(namespace as string); + await callback(resp.body); + } else { + await this.iterateApi( + async (nextPageToken) => { + return this.coreClient.listNamespace( + undefined, + undefined, + nextPageToken, + undefined, + undefined, + this.maxPerPage, + ); + }, + async (data: k8s.V1NamespaceList) => { + for (const namespace of data.items || []) { + await callback(namespace); + } + }, + ); + } + } + + async iterateNamespacedPods( + namespace: string, + callback: (data: k8s.V1Pod) => Promise, + ): Promise { + await this.iterateApi( + async (nextPageToken) => { + return this.coreClient.listNamespacedPod( + namespace, + undefined, + undefined, + nextPageToken, + undefined, + undefined, + this.maxPerPage, + ); + }, + async (data: k8s.V1PodList) => { + for (const pod of data.items) { + await callback(pod); + } + }, + ); + } + + async iterateNodes( + callback: (data: k8s.V1Node) => Promise, + ): Promise { + await this.iterateApi( + async (nextPageToken) => { + return this.coreClient.listNode( + undefined, + undefined, + nextPageToken, + undefined, + undefined, + this.maxPerPage, + ); + }, + async (data: k8s.V1NodeList) => { + for (const node of data.items) { + await callback(node); + } + }, + ); + } + + async iterateNamespacedServices( + namespace: string, + callback: (data: k8s.V1Service) => Promise, + ): Promise { + await this.iterateApi( + async (nextPageToken) => { + return this.coreClient.listNamespacedService( + namespace, + undefined, + undefined, + nextPageToken, + undefined, + undefined, + this.maxPerPage, + ); + }, + async (data: k8s.V1ServiceList) => { + for (const service of data.items) { + await callback(service); + } + }, + ); + } + + async iterateNamespacedConfigMaps( + namespace: string, + callback: (data: k8s.V1ConfigMap) => Promise, + ): Promise { + await this.iterateApi( + async (nextPageToken) => { + return this.coreClient.listNamespacedConfigMap( + namespace, + undefined, + undefined, + nextPageToken, + undefined, + undefined, + this.maxPerPage, + ); + }, + async (data: k8s.V1ConfigMapList) => { + for (const configMap of data.items || []) { + await callback(configMap); + } + }, + ); + } + + async iterateNamespacedSecrets( + namespace: string, + callback: (data: k8s.V1Secret) => Promise, + ): Promise { + await this.iterateApi( + async (nextPageToken) => { + return this.coreClient.listNamespacedSecret( + namespace, + undefined, + undefined, + nextPageToken, + undefined, + undefined, + this.maxPerPage, + ); + }, + async (data: k8s.V1SecretList) => { + for (const secret of data.items || []) { + await callback(secret); + } + }, + ); + } + + async iterateNamespacedServiceAccounts( + namespace: string, + callback: (data: k8s.V1ServiceAccount) => Promise, + ): Promise { + await this.iterateApi( + async (nextPageToken) => { + return this.coreClient.listNamespacedServiceAccount( + namespace, + undefined, + undefined, + nextPageToken, + undefined, + undefined, + this.maxPerPage, + ); + }, + async (data: k8s.V1ServiceAccountList) => { + for (const item of data.items) { + await callback(item); + } + }, + ); + } + + async iterateUsers( + callback: (data: k8s.V1beta1UserSubject) => Promise, + ): Promise { + for (const user of this.kubeConfig.users || []) { + await callback(user); + } + } + + /** + * Network + */ + async iterateNamespacedNetworkPolicies( + namespace: string, + callback: (data: k8s.V1NetworkPolicy) => Promise, + ): Promise { + await this.iterateApi( + async (nextPageToken) => { + return this.netwokClient.listNamespacedNetworkPolicy( + namespace, + undefined, + undefined, + nextPageToken, + undefined, + undefined, + this.maxPerPage, + ); + }, + async (data: k8s.V1NetworkPolicyList) => { + for (const item of data.items) { + await callback(item); + } + }, + ); + } + + /** + * Policy + */ + async iteratePodSecurityPolicies( + callback: (data: k8s.V1beta1PodSecurityPolicy) => Promise, + ) { + await this.iterateApi( + async (nextPageToken) => { + return this.policyClient.listPodSecurityPolicy( + undefined, + undefined, + nextPageToken, + undefined, + undefined, + this.maxPerPage, + ); + }, + async (data: k8s.V1beta1PodSecurityPolicyList) => { + for (const item of data.items || []) { + await callback(item); + } + }, + ); + } + + /** + * RBAC + */ + async iterateNamespacedRoles( + namespace: string, + callback: (data: k8s.V1Role) => Promise, + ): Promise { + await this.iterateApi( + async (nextPageToken) => { + return this.rbacClient.listNamespacedRole( + namespace, + undefined, + undefined, + nextPageToken, + undefined, + undefined, + this.maxPerPage, + ); + }, + async (data: k8s.V1RoleList) => { + for (const item of data.items || []) { + await callback(item); + } + }, + ); + } + + async iterateClusterRoles( + callback: (data: k8s.V1ClusterRole) => Promise, + ): Promise { + await this.iterateApi( + async (nextPageToken) => { + return this.rbacClient.listClusterRole( + undefined, + undefined, + nextPageToken, + undefined, + undefined, + this.maxPerPage, + ); + }, + async (data: k8s.V1ClusterRoleList) => { + for (const item of data.items || []) { + await callback(item); + } + }, + ); + } + + async iterateNamespacedRoleBindings( + namespace: string, + callback: (data: k8s.V1RoleBinding) => Promise, + ): Promise { + await this.iterateApi( + async (nextPageToken) => { + return this.rbacClient.listNamespacedRoleBinding( + namespace, + undefined, + undefined, + nextPageToken, + undefined, + undefined, + this.maxPerPage, + ); + }, + async (data: k8s.V1RoleBindingList) => { + for (const item of data.items || []) { + await callback(item); + } + }, + ); + } + + async iterateClusterRoleBindings( + callback: (data: k8s.V1ClusterRoleBinding) => Promise, + ): Promise { + await this.iterateApi( + async (nextPageToken) => { + return this.rbacClient.listClusterRoleBinding( + undefined, + undefined, + nextPageToken, + undefined, + undefined, + this.maxPerPage, + ); + }, + async (data: k8s.V1ClusterRoleBindingList) => { + for (const item of data.items || []) { + await callback(item); + } + }, + ); + } } diff --git a/src/kubernetes/clients/apps.ts b/src/kubernetes/clients/apps.ts deleted file mode 100644 index d3ea3b6..0000000 --- a/src/kubernetes/clients/apps.ts +++ /dev/null @@ -1,116 +0,0 @@ -import * as k8s from '@kubernetes/client-node'; -import { - V1DaemonSetList, - V1DeploymentList, - V1ReplicaSetList, - V1StatefulSetList, -} from '@kubernetes/client-node'; -import { IntegrationConfig } from '../../config'; -import { Client } from '../client'; - -export class AppsClient extends Client { - private client: k8s.AppsV1Api; - - constructor(config: IntegrationConfig) { - super(config); - this.authenticate(); - - this.client = this.kubeConfig.makeApiClient(k8s.AppsV1Api); - } - - async iterateNamespacedDeployments( - namespace: string, - callback: (data: k8s.V1Deployment) => Promise, - ): Promise { - await this.iterateApi( - async (nextPageToken) => { - return this.client.listNamespacedDeployment( - namespace, - undefined, - undefined, - nextPageToken, - undefined, - undefined, - this.maxPerPage, - ); - }, - async (data: V1DeploymentList) => { - for (const deployment of data.items || []) { - await callback(deployment); - } - }, - ); - } - - async iterateReplicaSets( - namespace: string, - callback: (data: k8s.V1ReplicaSet) => Promise, - ): Promise { - await this.iterateApi( - async (nextPageToken) => { - return this.client.listNamespacedReplicaSet( - namespace, - undefined, - undefined, - nextPageToken, - undefined, - undefined, - this.maxPerPage, - ); - }, - async (data: V1ReplicaSetList) => { - for (const replicaSet of data.items || []) { - await callback(replicaSet); - } - }, - ); - } - - async iterateStatefulSets( - namespace: string, - callback: (data: k8s.V1StatefulSet) => Promise, - ): Promise { - await this.iterateApi( - async (nextPageToken) => { - return this.client.listNamespacedStatefulSet( - namespace, - undefined, - undefined, - nextPageToken, - undefined, - undefined, - this.maxPerPage, - ); - }, - async (data: V1StatefulSetList) => { - for (const statefulSet of data.items || []) { - await callback(statefulSet); - } - }, - ); - } - - async iterateDaemonSets( - namespace: string, - callback: (data: k8s.V1DaemonSet) => Promise, - ): Promise { - await this.iterateApi( - async (nextPageToken) => { - return this.client.listNamespacedDaemonSet( - namespace, - undefined, - undefined, - nextPageToken, - undefined, - undefined, - this.maxPerPage, - ); - }, - async (data: V1DaemonSetList) => { - for (const daemonSet of data.items || []) { - await callback(daemonSet); - } - }, - ); - } -} diff --git a/src/kubernetes/clients/authorization.ts b/src/kubernetes/clients/authorization.ts deleted file mode 100644 index 535c1a0..0000000 --- a/src/kubernetes/clients/authorization.ts +++ /dev/null @@ -1,49 +0,0 @@ -import * as k8s from '@kubernetes/client-node'; -import { IntegrationConfig } from '../../config'; -import { Client } from '../client'; - -export type ResourceType = - | 'nodes' - | 'services' - | 'deployments' - | 'replicasets' - | 'statefulsets' - | 'daemonsets' - | 'jobs' - | 'cronjobs' - | 'configmaps' - | 'secrets' - | 'pods'; -export type VerbType = 'list' | 'create'; - -export class AuthorizationClient extends Client { - private client: k8s.AuthorizationV1Api; - - constructor(config: IntegrationConfig) { - super(config); - - this.authenticate(); - this.client = this.kubeConfig.makeApiClient(k8s.AuthorizationV1Api); - } - - async fetchSubjectServiceAccess( - resource: ResourceType, - verb: VerbType, - ): Promise { - const groupAppsServices = ['deployments', 'replicasets']; - const resp = await this.client.createSelfSubjectAccessReview({ - kind: 'SelfSubjectAccessReview', - apiVersion: 'authorization.k8s.io/v1', - spec: { - resourceAttributes: { - namespace: 'default', - verb: verb, - group: groupAppsServices.includes(resource) ? 'apps' : undefined, - resource: resource, - }, - }, - }); - - return resp.body; - } -} diff --git a/src/kubernetes/clients/batch.ts b/src/kubernetes/clients/batch.ts deleted file mode 100644 index 6e3eee2..0000000 --- a/src/kubernetes/clients/batch.ts +++ /dev/null @@ -1,39 +0,0 @@ -import * as k8s from '@kubernetes/client-node'; -import { V1JobList } from '@kubernetes/client-node'; -import { IntegrationConfig } from '../../config'; -import { Client } from '../client'; - -export class BatchClient extends Client { - private client: k8s.BatchV1Api; - - constructor(config: IntegrationConfig) { - super(config); - this.authenticate(); - - this.client = this.kubeConfig.makeApiClient(k8s.BatchV1Api); - } - - async iterateNamespacedJobs( - namespace: string, - callback: (data: k8s.V1Job) => Promise, - ): Promise { - await this.iterateApi( - async (nextPageToken) => { - return this.client.listNamespacedJob( - namespace, - undefined, - undefined, - nextPageToken, - undefined, - undefined, - this.maxPerPage, - ); - }, - async (data: V1JobList) => { - for (const job of data.items || []) { - await callback(job); - } - }, - ); - } -} diff --git a/src/kubernetes/clients/batch1beta.ts b/src/kubernetes/clients/batch1beta.ts deleted file mode 100644 index bf3e3df..0000000 --- a/src/kubernetes/clients/batch1beta.ts +++ /dev/null @@ -1,40 +0,0 @@ -import * as k8s from '@kubernetes/client-node'; -import { V1beta1CronJobList } from '@kubernetes/client-node'; -import { IntegrationConfig } from '../../config'; -import { Client } from '../client'; - -export class Batch1BetaClient extends Client { - // Batch V1 beta1 is required for cronjobs - private client: k8s.BatchV1beta1Api; - - constructor(config: IntegrationConfig) { - super(config); - this.authenticate(); - - this.client = this.kubeConfig.makeApiClient(k8s.BatchV1beta1Api); - } - - async iterateNamespacedCronJobs( - namespace: string, - callback: (data: k8s.V1beta1CronJob) => Promise, - ): Promise { - await this.iterateApi( - async (nextPageToken) => { - return this.client.listNamespacedCronJob( - namespace, - undefined, - undefined, - nextPageToken, - undefined, - undefined, - this.maxPerPage, - ); - }, - async (data: V1beta1CronJobList) => { - for (const cronJob of data.items || []) { - await callback(cronJob); - } - }, - ); - } -} diff --git a/src/kubernetes/clients/core.ts b/src/kubernetes/clients/core.ts deleted file mode 100644 index dd3758c..0000000 --- a/src/kubernetes/clients/core.ts +++ /dev/null @@ -1,217 +0,0 @@ -import { IntegrationProviderAuthenticationError } from '@jupiterone/integration-sdk-core'; -import * as k8s from '@kubernetes/client-node'; -import { - V1ConfigMapList, - V1NamespaceList, - V1NodeList, - V1PodList, - V1SecretList, - V1ServiceAccountList, - V1ServiceList, -} from '@kubernetes/client-node'; -import { IntegrationConfig } from '../../config'; -import { Client } from '../client'; - -export class CoreClient extends Client { - private client: k8s.CoreV1Api; - - constructor(config: IntegrationConfig) { - super(config); - - this.authenticate(); - this.client = this.kubeConfig.makeApiClient(k8s.CoreV1Api); - } - - // TODO: to be replaced with most lightweight API call - async verifyAuthentication() { - try { - await this.client.listNamespace(); - } catch (err) { - throw new IntegrationProviderAuthenticationError({ - cause: err, - endpoint: '/apis/apps/v1/namespaces', - status: 400, - statusText: err.message, - }); - } - } - - async iterateNamespaces( - callback: (data: k8s.V1Namespace) => Promise, - ): Promise { - const { accessType, namespace } = this.config; - - if (accessType?.toLowerCase() === 'namespace') { - const resp = await this.client.readNamespace(namespace as string); - await callback(resp.body); - } else { - await this.iterateApi( - async (nextPageToken) => { - return this.client.listNamespace( - undefined, - undefined, - nextPageToken, - undefined, - undefined, - this.maxPerPage, - ); - }, - async (data: V1NamespaceList) => { - for (const namespace of data.items || []) { - await callback(namespace); - } - }, - ); - } - } - - async iterateNamespacedPods( - namespace: string, - callback: (data: k8s.V1Pod) => Promise, - ): Promise { - await this.iterateApi( - async (nextPageToken) => { - return this.client.listNamespacedPod( - namespace, - undefined, - undefined, - nextPageToken, - undefined, - undefined, - this.maxPerPage, - ); - }, - async (data: V1PodList) => { - for (const pod of data.items) { - await callback(pod); - } - }, - ); - } - - async iterateNodes( - callback: (data: k8s.V1Node) => Promise, - ): Promise { - await this.iterateApi( - async (nextPageToken) => { - return this.client.listNode( - undefined, - undefined, - nextPageToken, - undefined, - undefined, - this.maxPerPage, - ); - }, - async (data: V1NodeList) => { - for (const node of data.items) { - await callback(node); - } - }, - ); - } - - async iterateNamespacedServices( - namespace: string, - callback: (data: k8s.V1Service) => Promise, - ): Promise { - await this.iterateApi( - async (nextPageToken) => { - return this.client.listNamespacedService( - namespace, - undefined, - undefined, - nextPageToken, - undefined, - undefined, - this.maxPerPage, - ); - }, - async (data: V1ServiceList) => { - for (const service of data.items) { - await callback(service); - } - }, - ); - } - - async iterateNamespacedConfigMaps( - namespace: string, - callback: (data: k8s.V1ConfigMap) => Promise, - ): Promise { - await this.iterateApi( - async (nextPageToken) => { - return this.client.listNamespacedConfigMap( - namespace, - undefined, - undefined, - nextPageToken, - undefined, - undefined, - this.maxPerPage, - ); - }, - async (data: V1ConfigMapList) => { - for (const configMap of data.items || []) { - await callback(configMap); - } - }, - ); - } - - async iterateNamespacedSecrets( - namespace: string, - callback: (data: k8s.V1Secret) => Promise, - ): Promise { - await this.iterateApi( - async (nextPageToken) => { - return this.client.listNamespacedSecret( - namespace, - undefined, - undefined, - nextPageToken, - undefined, - undefined, - this.maxPerPage, - ); - }, - async (data: V1SecretList) => { - for (const secret of data.items || []) { - await callback(secret); - } - }, - ); - } - - async iterateNamespacedServiceAccounts( - namespace: string, - callback: (data: k8s.V1ServiceAccount) => Promise, - ): Promise { - await this.iterateApi( - async (nextPageToken) => { - return this.client.listNamespacedServiceAccount( - namespace, - undefined, - undefined, - nextPageToken, - undefined, - undefined, - this.maxPerPage, - ); - }, - async (data: V1ServiceAccountList) => { - for (const item of data.items) { - await callback(item); - } - }, - ); - } - - async iterateUsers( - callback: (data: k8s.V1beta1UserSubject) => Promise, - ): Promise { - for (const user of this.kubeConfig.users || []) { - await callback(user); - } - } -} diff --git a/src/kubernetes/clients/network.ts b/src/kubernetes/clients/network.ts deleted file mode 100644 index 4ecb75e..0000000 --- a/src/kubernetes/clients/network.ts +++ /dev/null @@ -1,39 +0,0 @@ -import * as k8s from '@kubernetes/client-node'; -import { V1NetworkPolicyList } from '@kubernetes/client-node'; -import { IntegrationConfig } from '../../config'; -import { Client } from '../client'; - -export class NetworkClient extends Client { - private readonly client: k8s.NetworkingV1Api; - - constructor(config: IntegrationConfig) { - super(config); - - this.authenticate(); - this.client = this.kubeConfig.makeApiClient(k8s.NetworkingV1Api); - } - - async iterateNamespacedNetworkPolicies( - namespace: string, - callback: (data: k8s.V1NetworkPolicy) => Promise, - ): Promise { - await this.iterateApi( - async (nextPageToken) => { - return this.client.listNamespacedNetworkPolicy( - namespace, - undefined, - undefined, - nextPageToken, - undefined, - undefined, - this.maxPerPage, - ); - }, - async (data: V1NetworkPolicyList) => { - for (const item of data.items) { - await callback(item); - } - }, - ); - } -} diff --git a/src/kubernetes/clients/policy.ts b/src/kubernetes/clients/policy.ts deleted file mode 100644 index 5ee3faf..0000000 --- a/src/kubernetes/clients/policy.ts +++ /dev/null @@ -1,37 +0,0 @@ -import * as k8s from '@kubernetes/client-node'; -import { V1beta1PodSecurityPolicyList } from '@kubernetes/client-node'; -import { IntegrationConfig } from '../../config'; -import { Client } from '../client'; - -export class PolicyClient extends Client { - private client: k8s.PolicyV1beta1Api; - - constructor(config: IntegrationConfig) { - super(config); - - this.authenticate(); - this.client = this.kubeConfig.makeApiClient(k8s.PolicyV1beta1Api); - } - - async iteratePodSecurityPolicies( - callback: (data: k8s.V1beta1PodSecurityPolicy) => Promise, - ) { - await this.iterateApi( - async (nextPageToken) => { - return this.client.listPodSecurityPolicy( - undefined, - undefined, - nextPageToken, - undefined, - undefined, - this.maxPerPage, - ); - }, - async (data: V1beta1PodSecurityPolicyList) => { - for (const item of data.items || []) { - await callback(item); - } - }, - ); - } -} diff --git a/src/kubernetes/clients/rbac.ts b/src/kubernetes/clients/rbac.ts deleted file mode 100644 index 2e2a6dc..0000000 --- a/src/kubernetes/clients/rbac.ts +++ /dev/null @@ -1,112 +0,0 @@ -import * as k8s from '@kubernetes/client-node'; -import { - V1ClusterRoleBindingList, - V1ClusterRoleList, - V1RoleBindingList, - V1RoleList, -} from '@kubernetes/client-node'; -import { IntegrationConfig } from '../../config'; -import { Client } from '../client'; - -export class RbacAuthorizationClient extends Client { - private client: k8s.RbacAuthorizationV1Api; - - constructor(config: IntegrationConfig) { - super(config); - - this.authenticate(); - this.client = this.kubeConfig.makeApiClient(k8s.RbacAuthorizationV1Api); - } - - async iterateNamespacedRoles( - namespace: string, - callback: (data: k8s.V1Role) => Promise, - ): Promise { - await this.iterateApi( - async (nextPageToken) => { - return this.client.listNamespacedRole( - namespace, - undefined, - undefined, - nextPageToken, - undefined, - undefined, - this.maxPerPage, - ); - }, - async (data: V1RoleList) => { - for (const item of data.items || []) { - await callback(item); - } - }, - ); - } - - async iterateClusterRoles( - callback: (data: k8s.V1ClusterRole) => Promise, - ): Promise { - await this.iterateApi( - async (nextPageToken) => { - return this.client.listClusterRole( - undefined, - undefined, - nextPageToken, - undefined, - undefined, - this.maxPerPage, - ); - }, - async (data: V1ClusterRoleList) => { - for (const item of data.items || []) { - await callback(item); - } - }, - ); - } - - async iterateNamespacedRoleBindings( - namespace: string, - callback: (data: k8s.V1RoleBinding) => Promise, - ): Promise { - await this.iterateApi( - async (nextPageToken) => { - return this.client.listNamespacedRoleBinding( - namespace, - undefined, - undefined, - nextPageToken, - undefined, - undefined, - this.maxPerPage, - ); - }, - async (data: V1RoleBindingList) => { - for (const item of data.items || []) { - await callback(item); - } - }, - ); - } - - async iterateClusterRoleBindings( - callback: (data: k8s.V1ClusterRoleBinding) => Promise, - ): Promise { - await this.iterateApi( - async (nextPageToken) => { - return this.client.listClusterRoleBinding( - undefined, - undefined, - nextPageToken, - undefined, - undefined, - this.maxPerPage, - ); - }, - async (data: V1ClusterRoleBindingList) => { - for (const item of data.items || []) { - await callback(item); - } - }, - ); - } -} diff --git a/src/kubernetes/getOrCreateAPIClient.ts b/src/kubernetes/getOrCreateAPIClient.ts new file mode 100644 index 0000000..8643da0 --- /dev/null +++ b/src/kubernetes/getOrCreateAPIClient.ts @@ -0,0 +1,13 @@ +import { IntegrationConfig } from '../config'; +import { Client } from './client'; + +let client: Client | undefined; + +export default function getOrCreateAPIClient( + config: IntegrationConfig, +): Client { + if (!client) { + client = new Client(config); + } + return client; +} diff --git a/src/steps/clusters/index.ts b/src/steps/clusters/index.ts index 8ad48c6..0a9f659 100644 --- a/src/steps/clusters/index.ts +++ b/src/steps/clusters/index.ts @@ -9,7 +9,6 @@ import { import { Cluster, V1ConfigMap } from '@kubernetes/client-node'; import fetch from 'node-fetch'; import { IntegrationConfig, IntegrationStepContext } from '../../config'; -import { CoreClient } from '../../kubernetes/clients/core'; import { CLUSTER_ENTITY_DATA_KEY, Entities, @@ -18,13 +17,14 @@ import { Relationships, } from '../constants'; import { createClusterEntity } from './converters'; +import getOrCreateAPIClient from '../../kubernetes/getOrCreateAPIClient'; export async function fetchClusterDetails( context: IntegrationStepContext, ): Promise { const { instance, jobState } = context; - const client = new CoreClient(instance.config); + const client = getOrCreateAPIClient(instance.config); const cluster = client.kubeConfig.getCurrentCluster(); const clusterEntity = createClusterEntity(cluster as Cluster); diff --git a/src/steps/config-maps/index.ts b/src/steps/config-maps/index.ts index a4432b9..c141721 100644 --- a/src/steps/config-maps/index.ts +++ b/src/steps/config-maps/index.ts @@ -4,9 +4,9 @@ import { RelationshipClass, } from '@jupiterone/integration-sdk-core'; import { IntegrationConfig, IntegrationStepContext } from '../../config'; -import { CoreClient } from '../../kubernetes/clients/core'; import { Entities, IntegrationSteps, Relationships } from '../constants'; import { createConfigMapEntity } from './converters'; +import getOrCreateAPIClient from '../../kubernetes/getOrCreateAPIClient'; export async function fetchConfigMaps( context: IntegrationStepContext, @@ -14,7 +14,7 @@ export async function fetchConfigMaps( const { instance, jobState } = context; const { config } = instance; - const client = new CoreClient(config); + const client = getOrCreateAPIClient(config); await jobState.iterateEntities( { diff --git a/src/steps/cron-jobs/index.ts b/src/steps/cron-jobs/index.ts index 601abaa..1f92476 100644 --- a/src/steps/cron-jobs/index.ts +++ b/src/steps/cron-jobs/index.ts @@ -4,9 +4,9 @@ import { RelationshipClass, } from '@jupiterone/integration-sdk-core'; import { IntegrationConfig, IntegrationStepContext } from '../../config'; -import { Batch1BetaClient } from '../../kubernetes/clients/batch1beta'; import { Entities, IntegrationSteps, Relationships } from '../constants'; import { createCronJobEntity } from './converters'; +import getOrCreateAPIClient from '../../kubernetes/getOrCreateAPIClient'; export async function fetchCronJobs( context: IntegrationStepContext, @@ -14,7 +14,7 @@ export async function fetchCronJobs( const { instance, jobState } = context; const { config } = instance; - const client = new Batch1BetaClient(config); + const client = getOrCreateAPIClient(config); await jobState.iterateEntities( { diff --git a/src/steps/daemon-sets/index.ts b/src/steps/daemon-sets/index.ts index bc8c992..c4922ca 100644 --- a/src/steps/daemon-sets/index.ts +++ b/src/steps/daemon-sets/index.ts @@ -4,9 +4,9 @@ import { RelationshipClass, } from '@jupiterone/integration-sdk-core'; import { IntegrationConfig, IntegrationStepContext } from '../../config'; -import { AppsClient } from '../../kubernetes/clients/apps'; import { Entities, IntegrationSteps, Relationships } from '../constants'; import { createDaemonSetEntity } from './converters'; +import getOrCreateAPIClient from '../../kubernetes/getOrCreateAPIClient'; export async function fetchDaemonSets( context: IntegrationStepContext, @@ -14,7 +14,7 @@ export async function fetchDaemonSets( const { instance, jobState } = context; const { config } = instance; - const client = new AppsClient(config); + const client = getOrCreateAPIClient(config); await jobState.iterateEntities( { diff --git a/src/steps/deployments/index.ts b/src/steps/deployments/index.ts index 83466b6..ffedc84 100644 --- a/src/steps/deployments/index.ts +++ b/src/steps/deployments/index.ts @@ -5,7 +5,6 @@ import { JobState, RelationshipClass, } from '@jupiterone/integration-sdk-core'; -import { AppsClient } from '../../kubernetes/clients/apps'; import { IntegrationConfig, IntegrationStepContext } from '../../config'; import { Entities, IntegrationSteps, Relationships } from '../constants'; import { @@ -16,6 +15,7 @@ import { getVolumeKey, } from './converters'; import { V1VolumeMount } from '@kubernetes/client-node'; +import getOrCreateAPIClient from '../../kubernetes/getOrCreateAPIClient'; type BuildContainerSpecVolumeRelationshipsParams = { jobState: JobState; @@ -55,7 +55,7 @@ export async function fetchDeployments( const { instance, jobState } = context; const { config } = instance; - const client = new AppsClient(config); + const client = getOrCreateAPIClient(config); await jobState.iterateEntities( { diff --git a/src/steps/jobs/index.ts b/src/steps/jobs/index.ts index 1d23a9b..7a1f531 100644 --- a/src/steps/jobs/index.ts +++ b/src/steps/jobs/index.ts @@ -4,9 +4,9 @@ import { RelationshipClass, } from '@jupiterone/integration-sdk-core'; import { IntegrationConfig, IntegrationStepContext } from '../../config'; -import { BatchClient } from '../../kubernetes/clients/batch'; import { Entities, IntegrationSteps, Relationships } from '../constants'; import { createJobEntity } from './converters'; +import getOrCreateAPIClient from '../../kubernetes/getOrCreateAPIClient'; export async function fetchJobs( context: IntegrationStepContext, @@ -14,7 +14,7 @@ export async function fetchJobs( const { instance, jobState } = context; const { config } = instance; - const client = new BatchClient(config); + const client = getOrCreateAPIClient(config); await jobState.iterateEntities( { diff --git a/src/steps/namespaces/index.ts b/src/steps/namespaces/index.ts index 37971d5..7241d38 100644 --- a/src/steps/namespaces/index.ts +++ b/src/steps/namespaces/index.ts @@ -1,8 +1,8 @@ import { IntegrationStep } from '@jupiterone/integration-sdk-core'; -import { CoreClient } from '../../kubernetes/clients/core'; import { IntegrationConfig, IntegrationStepContext } from '../../config'; import { Entities, IntegrationSteps } from '../constants'; import { createNamespaceEntity } from './converters'; +import getOrCreateAPIClient from '../../kubernetes/getOrCreateAPIClient'; export async function fetchNamespaces( context: IntegrationStepContext, @@ -10,7 +10,7 @@ export async function fetchNamespaces( const { instance, jobState } = context; const { config } = instance; - const client = new CoreClient(config); + const client = getOrCreateAPIClient(config); await client.iterateNamespaces(async (namespace) => { const namespaceEntity = createNamespaceEntity(namespace); diff --git a/src/steps/nodes/index.ts b/src/steps/nodes/index.ts index 4101237..70ee958 100644 --- a/src/steps/nodes/index.ts +++ b/src/steps/nodes/index.ts @@ -1,9 +1,9 @@ import { IntegrationStep } from '@jupiterone/integration-sdk-core'; -import { CoreClient } from '../../kubernetes/clients/core'; import { IntegrationConfig, IntegrationStepContext } from '../../config'; import { Entities, IntegrationSteps } from '../constants'; import { createNodeEntity } from './converters'; import { cacheNodeNameToUid } from '../../util/jobState'; +import getOrCreateAPIClient from '../../kubernetes/getOrCreateAPIClient'; export async function fetchNodes( context: IntegrationStepContext, @@ -11,7 +11,7 @@ export async function fetchNodes( const { instance, jobState } = context; const { config } = instance; - const client = new CoreClient(config); + const client = getOrCreateAPIClient(config); await client.iterateNodes(async (node) => { const nodeEntity = createNodeEntity(node); diff --git a/src/steps/pods/index.ts b/src/steps/pods/index.ts index 163a7e8..070b09c 100644 --- a/src/steps/pods/index.ts +++ b/src/steps/pods/index.ts @@ -3,7 +3,6 @@ import { IntegrationStep, RelationshipClass, } from '@jupiterone/integration-sdk-core'; -import { CoreClient } from '../../kubernetes/clients/core'; import { IntegrationConfig, IntegrationStepContext } from '../../config'; import { Entities, IntegrationSteps, Relationships } from '../constants'; import { @@ -12,6 +11,7 @@ import { getContainerKey, } from './converters'; import { getNodeUidFromPod } from '../../util/jobState'; +import getOrCreateAPIClient from '../../kubernetes/getOrCreateAPIClient'; export async function fetchPods( context: IntegrationStepContext, @@ -19,7 +19,7 @@ export async function fetchPods( const { instance, jobState, logger } = context; const { config } = instance; - const client = new CoreClient(config); + const client = getOrCreateAPIClient(config); const containerEntityKeys = new Set(); await jobState.iterateEntities( diff --git a/src/steps/policies/index.ts b/src/steps/policies/index.ts index 0613a0f..d4bc97a 100644 --- a/src/steps/policies/index.ts +++ b/src/steps/policies/index.ts @@ -11,12 +11,11 @@ import { IntegrationSteps, Relationships, } from '../constants'; -import { PolicyClient } from '../../kubernetes/clients/policy'; import { createNetworkPolicyEntity, createPodSecurityPolicyEntity, } from './converters'; -import { NetworkClient } from '../../kubernetes/clients/network'; +import getOrCreateAPIClient from '../../kubernetes/getOrCreateAPIClient'; export async function fetchPodSecurityPolicies( context: IntegrationStepContext, @@ -24,7 +23,7 @@ export async function fetchPodSecurityPolicies( const { instance, jobState } = context; const { config } = instance; - const client = new PolicyClient(config); + const client = getOrCreateAPIClient(config); const clusterEntity = (await jobState.getData( CLUSTER_ENTITY_DATA_KEY, @@ -49,7 +48,7 @@ export async function fetchNetworkPolicies( const { instance, jobState } = context; const { config } = instance; - const client = new NetworkClient(config); + const client = getOrCreateAPIClient(config); await jobState.iterateEntities( { diff --git a/src/steps/rbac/index.ts b/src/steps/rbac/index.ts index 0dbae18..58e42a8 100644 --- a/src/steps/rbac/index.ts +++ b/src/steps/rbac/index.ts @@ -11,13 +11,13 @@ import { IntegrationSteps, Relationships, } from '../constants'; -import { RbacAuthorizationClient } from '../../kubernetes/clients/rbac'; import { createClusterRoleBindingEntity, createClusterRoleEntity, createRoleBindingEntity, createRoleEntity, } from './converters'; +import getOrCreateAPIClient from '../../kubernetes/getOrCreateAPIClient'; export async function fetchRoles( context: IntegrationStepContext, @@ -25,7 +25,7 @@ export async function fetchRoles( const { instance, jobState } = context; const { config } = instance; - const client = new RbacAuthorizationClient(config); + const client = getOrCreateAPIClient(config); await jobState.iterateEntities( { _type: Entities.NAMESPACE._type, @@ -60,7 +60,7 @@ export async function fetchClusterRoles( CLUSTER_ENTITY_DATA_KEY, )) as Entity; - const client = new RbacAuthorizationClient(config); + const client = getOrCreateAPIClient(config); await client.iterateClusterRoles(async (clusterRole) => { const clusterRoleEntity = createClusterRoleEntity(clusterRole); await jobState.addEntity(clusterRoleEntity); @@ -84,7 +84,7 @@ export async function fetchRoleBindings( const { instance, jobState } = context; const { config } = instance; - const client = new RbacAuthorizationClient(config); + const client = getOrCreateAPIClient(config); await jobState.iterateEntities( { @@ -120,7 +120,7 @@ export async function fetchClusterRoleBindings( CLUSTER_ENTITY_DATA_KEY, )) as Entity; - const client = new RbacAuthorizationClient(config); + const client = getOrCreateAPIClient(config); await client.iterateClusterRoleBindings(async (clusterRoleBinding) => { const clusterRoleBindingEntity = createClusterRoleBindingEntity( clusterRoleBinding, diff --git a/src/steps/replica-sets/index.ts b/src/steps/replica-sets/index.ts index c3c03f2..3dc4689 100644 --- a/src/steps/replica-sets/index.ts +++ b/src/steps/replica-sets/index.ts @@ -5,8 +5,8 @@ import { } from '@jupiterone/integration-sdk-core'; import { IntegrationConfig, IntegrationStepContext } from '../../config'; import { Entities, IntegrationSteps, Relationships } from '../constants'; -import { AppsClient } from '../../kubernetes/clients/apps'; import { createReplicaSetEntity } from './converters'; +import getOrCreateAPIClient from '../../kubernetes/getOrCreateAPIClient'; export async function fetchReplicaSets( context: IntegrationStepContext, @@ -14,7 +14,7 @@ export async function fetchReplicaSets( const { instance, jobState } = context; const { config } = instance; - const client = new AppsClient(config); + const client = getOrCreateAPIClient(config); await jobState.iterateEntities( { diff --git a/src/steps/secrets/index.ts b/src/steps/secrets/index.ts index 3697695..3a15051 100644 --- a/src/steps/secrets/index.ts +++ b/src/steps/secrets/index.ts @@ -4,9 +4,9 @@ import { RelationshipClass, } from '@jupiterone/integration-sdk-core'; import { IntegrationConfig, IntegrationStepContext } from '../../config'; -import { CoreClient } from '../../kubernetes/clients/core'; import { Entities, IntegrationSteps, Relationships } from '../constants'; import { createSecretEntity } from './converters'; +import getOrCreateAPIClient from '../../kubernetes/getOrCreateAPIClient'; export async function fetchSecrets( context: IntegrationStepContext, @@ -14,7 +14,7 @@ export async function fetchSecrets( const { instance, jobState } = context; const { config } = instance; - const client = new CoreClient(config); + const client = getOrCreateAPIClient(config); await jobState.iterateEntities( { diff --git a/src/steps/services/index.ts b/src/steps/services/index.ts index dba7841..7513eab 100644 --- a/src/steps/services/index.ts +++ b/src/steps/services/index.ts @@ -3,17 +3,17 @@ import { IntegrationStep, RelationshipClass, } from '@jupiterone/integration-sdk-core'; -import { CoreClient } from '../../kubernetes/clients/core'; import { IntegrationConfig, IntegrationStepContext } from '../../config'; import { Entities, IntegrationSteps, Relationships } from '../constants'; import { createServiceEntity } from './converters'; +import getOrCreateAPIClient from '../../kubernetes/getOrCreateAPIClient'; export async function fetchServices( context: IntegrationStepContext, ): Promise { const { instance, jobState } = context; const { config } = instance; - const client = new CoreClient(config); + const client = getOrCreateAPIClient(config); await jobState.iterateEntities( { diff --git a/src/steps/stateful-sets/index.ts b/src/steps/stateful-sets/index.ts index 81708c7..5cdf0e0 100644 --- a/src/steps/stateful-sets/index.ts +++ b/src/steps/stateful-sets/index.ts @@ -4,9 +4,9 @@ import { RelationshipClass, } from '@jupiterone/integration-sdk-core'; import { IntegrationConfig, IntegrationStepContext } from '../../config'; -import { AppsClient } from '../../kubernetes/clients/apps'; import { Entities, IntegrationSteps, Relationships } from '../constants'; import { createStatefulSetEntity } from './converters'; +import getOrCreateAPIClient from '../../kubernetes/getOrCreateAPIClient'; export async function fetchStatefulSets( context: IntegrationStepContext, @@ -14,7 +14,7 @@ export async function fetchStatefulSets( const { instance, jobState } = context; const { config } = instance; - const client = new AppsClient(config); + const client = getOrCreateAPIClient(config); await jobState.iterateEntities( { diff --git a/src/steps/subjects/index.ts b/src/steps/subjects/index.ts index aa55f4a..de8de1b 100644 --- a/src/steps/subjects/index.ts +++ b/src/steps/subjects/index.ts @@ -3,10 +3,10 @@ import { IntegrationStep, RelationshipClass, } from '@jupiterone/integration-sdk-core'; -import { CoreClient } from '../../kubernetes/clients/core'; import { IntegrationConfig, IntegrationStepContext } from '../../config'; import { Entities, IntegrationSteps, Relationships } from '../constants'; import { createServiceAccountEntity, createUserEntity } from './converters'; +import getOrCreateAPIClient from '../../kubernetes/getOrCreateAPIClient'; export async function fetchServiceAccounts( context: IntegrationStepContext, @@ -14,7 +14,7 @@ export async function fetchServiceAccounts( const { instance, jobState } = context; const { config } = instance; - const client = new CoreClient(config); + const client = getOrCreateAPIClient(config); await jobState.iterateEntities( { @@ -47,7 +47,7 @@ export async function fetchUsers( const { instance, jobState } = context; const { config } = instance; - const client = new CoreClient(config); + const client = getOrCreateAPIClient(config); await client.iterateUsers(async (user) => { const userEntity = createUserEntity(user); diff --git a/src/validator.ts b/src/validator.ts index c436bfc..5fdf91e 100644 --- a/src/validator.ts +++ b/src/validator.ts @@ -3,7 +3,7 @@ import { IntegrationValidationError, } from '@jupiterone/integration-sdk-core'; import { IntegrationConfig } from './config'; -import { CoreClient } from './kubernetes/clients/core'; +import getOrCreateAPIClient from './kubernetes/getOrCreateAPIClient'; export async function validateInvocation( context: IntegrationExecutionContext, @@ -23,6 +23,6 @@ export async function validateInvocation( ); } - const coreClient = new CoreClient(config); - await coreClient.verifyAuthentication(); + const client = getOrCreateAPIClient(config); + await client.verifyAuthentication(); }