diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..1f07f02 --- /dev/null +++ b/.env.example @@ -0,0 +1,3 @@ +jupiteroneAccountId=j1dev +jupiteroneApiKey=some-jupiterone-api-key +jupiteroneIntegrationInstanceId=some-integration-instance-id diff --git a/.gitignore b/.gitignore index 2517627..068f57c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,7 @@ node_modules/ dist/ +.tilt-cache/ +tilt_modules/ .j1-integration/ .env .eslintcache diff --git a/Dockerfile b/Dockerfile index 762f680..4d5aaf0 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,9 +1,12 @@ -FROM node:14-alpine +FROM node:14-bullseye-slim +ARG j1_dev_enabled=false + +ENV JUPITERONE_DEV_ENABLED=$j1_dev_enabled ENV JUPITERONE_INTEGRATION_DIR=/opt/jupiterone/integration -# node-gyp/python3 requirement -RUN apk add g++ make python +RUN apt-get update +RUN apt-get -y install g++ make python COPY package.json yarn.lock LICENSE ${JUPITERONE_INTEGRATION_DIR}/ COPY src/ ${JUPITERONE_INTEGRATION_DIR}/src diff --git a/README.md b/README.md index cb76f74..e98c009 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,10 @@ with JupiterOne in the [integration documentation](docs/jupiterone.md). ### Prerequisites +TODO: Document installing kubernetes through docker for mac or similar setup + +TODO: Document tilt workflow https://docs.tilt.dev/install.html + 1. Install [Node.js](https://nodejs.org/) using the [installer](https://nodejs.org/en/download/) or a version manager such as [nvm](https://github.com/nvm-sh/nvm) or [fnm](https://github.com/Schniz/fnm). @@ -22,7 +26,7 @@ with JupiterOne in the [integration documentation](docs/jupiterone.md). names of these parameters are defined in `src/instanceConfigFields.ts`. When executed in a development environment, values for these parameters are read from Node's `process.env`, loaded from `.env`. That file has been added to - `.gitignore` to avoid commiting credentials. + `.gitignore` to avoid committing credentials. ### Running the integration diff --git a/Tiltfile b/Tiltfile new file mode 100644 index 0000000..195da19 --- /dev/null +++ b/Tiltfile @@ -0,0 +1,8 @@ +os.putenv('TILT_CACHE_DIR', os.path.abspath('./.tilt-cache')) + +load('ext://secret', 'secret_yaml_generic') + +docker_build('graph-kubernetes', '.') + +k8s_yaml(secret_yaml_generic('graph-kubernetes', from_env_file='.env')) +k8s_yaml('tilt.yml') diff --git a/configs/cronjobCluster.yml b/configs/cronjobCluster.yml deleted file mode 100644 index 7f0f21f..0000000 --- a/configs/cronjobCluster.yml +++ /dev/null @@ -1,49 +0,0 @@ -apiVersion: batch/v1 -kind: CronJob -metadata: - name: integration-deployment-cron - labels: - name: jupiterone-integration - app: integration-cron -spec: - schedule: "*/60 * * * *" # Schedule to run every hour - jobTemplate: - spec: - template: - spec: - serviceAccountName: jupiterone-integration-cluster - containers: - - name: jupiterone-integration - image: jupiterone/graph-kubernetes:latest - imagePullPolicy: IfNotPresent - env: - # could use better name, but for now: - # ACCESS_TYPE can either be "namespace" or "cluster" - - name: ACCESS_TYPE - value: "cluster" - # If ACCESS_TYPE === "namespace" - # This needs to contain its name - - name: NAMESPACE - value: "default" - - name: JUPITERONE_ACCOUNT_ID - valueFrom: - secretKeyRef: - name: jupiterone-integration-secret - key: jupiteroneAccountId - - name: JUPITERONE_API_KEY - valueFrom: - secretKeyRef: - name: jupiterone-integration-secret - key: jupiteroneApiKey - - name: INTEGRATION_INSTANCE_ID - valueFrom: - secretKeyRef: - name: jupiterone-integration-secret - key: jupiteroneIntegrationInstanceId - - name: IS_RUNNING_TEST - value: "false" - resources: - limits: - cpu: 1000m - memory: 1024Mi - restartPolicy: Never diff --git a/configs/cronjobNamespace.yml b/configs/cronjobNamespace.yml deleted file mode 100644 index 475baab..0000000 --- a/configs/cronjobNamespace.yml +++ /dev/null @@ -1,49 +0,0 @@ -apiVersion: batch/v1 -kind: CronJob -metadata: - name: integration-deployment-cron - labels: - name: jupiterone-integration - app: integration-cron -spec: - schedule: "*/60 * * * *" # Schedule to run every hour - jobTemplate: - spec: - template: - spec: - serviceAccountName: jupiterone-integration - containers: - - name: jupiterone-integration - image: jupiterone/graph-kubernetes:latest - imagePullPolicy: IfNotPresent - env: - # could use better name, but for now: - # ACCESS_TYPE can either be "namespace" or "cluster" - - name: ACCESS_TYPE - value: "namespace" - # If ACCESS_TYPE === "namespace" - # This needs to contain its name - - name: NAMESPACE - value: "default" - - name: JUPITERONE_ACCOUNT_ID - valueFrom: - secretKeyRef: - name: jupiterone-integration-secret - key: jupiteroneAccountId - - name: JUPITERONE_API_KEY - valueFrom: - secretKeyRef: - name: jupiterone-integration-secret - key: jupiteroneApiKey - - name: INTEGRATION_INSTANCE_ID - valueFrom: - secretKeyRef: - name: jupiterone-integration-secret - key: jupiteroneIntegrationInstanceId - - name: IS_RUNNING_TEST - value: "false" - resources: - limits: - cpu: 1000m - memory: 1024Mi - restartPolicy: Never diff --git a/configs/clusterRole.yml b/configs/k8s/clusterRole.yml similarity index 100% rename from configs/clusterRole.yml rename to configs/k8s/clusterRole.yml diff --git a/configs/clusterRoleBinding.yml b/configs/k8s/clusterRoleBinding.yml similarity index 100% rename from configs/clusterRoleBinding.yml rename to configs/k8s/clusterRoleBinding.yml diff --git a/configs/createSecret.yml b/configs/k8s/createSecret.yml similarity index 100% rename from configs/createSecret.yml rename to configs/k8s/createSecret.yml diff --git a/configs/k8s/cronjobCluster.yml b/configs/k8s/cronjobCluster.yml new file mode 100644 index 0000000..4100f0d --- /dev/null +++ b/configs/k8s/cronjobCluster.yml @@ -0,0 +1,251 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: fluent-bit-agent-conf + labels: + app: fluent-bit + component: fluent-bit-agent-conf +data: + fluent-bit: | + [SERVICE] + Parsers_File ./parsers.conf + Grace 15 + [INPUT] + Name tail + Path /var/log/containers/graph-kubernetes*.log + Exclude_Path /var/log/containers/*otel-collector*.log + Parser cri + Tag kube.* + Mem_Buf_Limit 5MB + [OUTPUT] + Name forward + Host 0.0.0.0 + Port 8006 + Match * + parsers: | + [PARSER] + Name jupiter_one + Format json + Time_Key time + Time_Format %Y-%m-%dT%H:%M:%S.%LZ + Time_Keep On +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: otel-agent-conf + labels: + app: opentelemetry + component: otel-agent-conf +data: + otel-agent-config: | + receivers: + hostmetrics: + collection_interval: 10s + scrapers: + cpu: + load: + memory: + disk: + filesystem: + network: + paging: + processes: + otlp: + protocols: + grpc: + http: + fluentforward: + endpoint: 0.0.0.0:8006 + + processors: + batch: + memory_limiter: + limit_mib: 400 + spike_limit_mib: 100 + check_interval: 5s + + exporters: + logging: + loglevel: debug + sampling_initial: 5 + sampling_thereafter: 200 + + extensions: + memory_ballast: + size_mib: 165 + + service: + pipelines: + logs: + receivers: [fluentforward] + processors: [memory_limiter, batch] + exporters: [logging] + metrics: + receivers: [hostmetrics] + processors: [memory_limiter, batch] + exporters: [logging] + traces: + receivers: [otlp] + processors: [memory_limiter, batch] + exporters: [logging] +--- +apiVersion: batch/v1 +kind: CronJob +metadata: + name: integration-deployment-cron + labels: + name: jupiterone-integration + app: integration-cron +spec: + schedule: "*/60 * * * *" # Schedule to run every hour + jobTemplate: + spec: + template: + spec: + serviceAccountName: graph-kubernetes + backoffLimit: 4 + initContainers: + - name: kubexit + image: karlkfi/kubexit:latest + command: ['cp'] + args: ['/bin/kubexit', '/kubexit/kubexit'] + volumeMounts: + - mountPath: /kubexit + name: kubexit + containers: + - name: graph-kubernetes + image: graph-kubernetes + imagePullPolicy: IfNotPresent + command: ['/kubexit/kubexit', 'yarn'] + args: ['collect'] + env: + - name: KUBEXIT_NAME + value: graph-kubernetes + - name: KUBEXIT_GRAVEYARD + value: /graveyard + - name: KUBEXIT_BIRTH_DEPS + value: fluent-bit,otel-collector + - name: KUBEXIT_POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + - name: KUBEXIT_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + - name: JUPITERONE_DEV + value: 'true' + - name: ACCESS_TYPE + value: 'cluster' + - name: NAMESPACE + value: 'default' + - name: IS_RUNNING_TEST + value: 'false' + - name: JUPITERONE_ACCOUNT_ID + valueFrom: + secretKeyRef: + name: graph-kubernetes + key: jupiteroneAccountId + - name: JUPITERONE_API_KEY + valueFrom: + secretKeyRef: + name: graph-kubernetes + key: jupiteroneApiKey + - name: INTEGRATION_INSTANCE_ID + valueFrom: + secretKeyRef: + name: graph-kubernetes + key: jupiteroneIntegrationInstanceId + volumeMounts: + - mountPath: /graveyard + name: graveyard + - mountPath: /kubexit + name: kubexit + - name: otel-collector + image: otel/opentelemetry-collector-contrib:0.33.0 + imagePullPolicy: IfNotPresent + command: ['/kubexit/kubexit', '/otelcontribcol'] + args: ['--config=/conf/otel-agent-config.yaml'] + env: + - name: KUBEXIT_NAME + value: otel-collector + - name: KUBEXIT_GRAVEYARD + value: /graveyard + - name: KUBEXIT_DEATH_DEPS + value: fluent-bit + - name: KUBEXIT_BIRTH_DEPS + value: fluent-bit + - name: KUBEXIT_POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + - name: KUBEXIT_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + volumeMounts: + - name: otel-agent-config-vol + mountPath: /conf + - mountPath: /graveyard + name: graveyard + - mountPath: /kubexit + name: kubexit + - name: fluent-bit + image: fluent/fluent-bit:1.8 + imagePullPolicy: IfNotPresent + command: ['/kubexit/kubexit', '/fluent-bit/bin/fluent-bit'] + args: ['--config=/fluent-bit/etc/fluent-bit.conf'] + env: + - name: KUBEXIT_NAME + value: fluent-bit + - name: KUBEXIT_GRAVEYARD + value: /graveyard + - name: KUBEXIT_DEATH_DEPS + value: graph-kubernetes + volumeMounts: + - name: log-storage + mountPath: /mnt/log/ + readOnly: true + - name: fluent-bit-agent-config-vol + mountPath: /fluent-bit/etc/ + - mountPath: /graveyard + name: graveyard + - mountPath: /kubexit + name: kubexit + - name: varlog + mountPath: /var/log + readOnly: true + - name: varlibdockercontainers + mountPath: /var/lib/docker/containers + readOnly: true + restartPolicy: Never + terminationGracePeriodSeconds: 10 + volumes: + - name: fluent-bit-agent-config-vol + configMap: + name: fluent-bit-agent-conf + items: + - key: fluent-bit + path: fluent-bit.conf + - key: parsers + path: parsers.conf + - name: otel-agent-config-vol + configMap: + name: otel-agent-conf + items: + - key: otel-agent-config + path: otel-agent-config.yaml + - name: graveyard + emptyDir: + medium: Memory + - name: kubexit + emptyDir: {} + - name: log-storage + emptyDir: {} + - name: varlog + hostPath: + path: /var/log + - name: varlibdockercontainers + hostPath: + path: /var/lib/docker/containers diff --git a/configs/k8s/cronjobNamespace.yml b/configs/k8s/cronjobNamespace.yml new file mode 100644 index 0000000..c51767e --- /dev/null +++ b/configs/k8s/cronjobNamespace.yml @@ -0,0 +1,251 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: fluent-bit-agent-conf + labels: + app: fluent-bit + component: fluent-bit-agent-conf +data: + fluent-bit: | + [SERVICE] + Parsers_File ./parsers.conf + Grace 15 + [INPUT] + Name tail + Path /var/log/containers/graph-kubernetes*.log + Exclude_Path /var/log/containers/*otel-collector*.log + Parser cri + Tag kube.* + Mem_Buf_Limit 5MB + [OUTPUT] + Name forward + Host 0.0.0.0 + Port 8006 + Match * + parsers: | + [PARSER] + Name jupiter_one + Format json + Time_Key time + Time_Format %Y-%m-%dT%H:%M:%S.%LZ + Time_Keep On +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: otel-agent-conf + labels: + app: opentelemetry + component: otel-agent-conf +data: + otel-agent-config: | + receivers: + hostmetrics: + collection_interval: 10s + scrapers: + cpu: + load: + memory: + disk: + filesystem: + network: + paging: + processes: + otlp: + protocols: + grpc: + http: + fluentforward: + endpoint: 0.0.0.0:8006 + + processors: + batch: + memory_limiter: + limit_mib: 400 + spike_limit_mib: 100 + check_interval: 5s + + exporters: + logging: + loglevel: debug + sampling_initial: 5 + sampling_thereafter: 200 + + extensions: + memory_ballast: + size_mib: 165 + + service: + pipelines: + logs: + receivers: [fluentforward] + processors: [memory_limiter, batch] + exporters: [logging] + metrics: + receivers: [hostmetrics] + processors: [memory_limiter, batch] + exporters: [logging] + traces: + receivers: [otlp] + processors: [memory_limiter, batch] + exporters: [logging] +--- +apiVersion: batch/v1 +kind: CronJob +metadata: + name: integration-deployment-cron + labels: + name: jupiterone-integration + app: integration-cron +spec: + schedule: "*/60 * * * *" # Schedule to run every hour + jobTemplate: + spec: + template: + spec: + serviceAccountName: graph-kubernetes + backoffLimit: 4 + initContainers: + - name: kubexit + image: karlkfi/kubexit:latest + command: ['cp'] + args: ['/bin/kubexit', '/kubexit/kubexit'] + volumeMounts: + - mountPath: /kubexit + name: kubexit + containers: + - name: graph-kubernetes + image: graph-kubernetes + imagePullPolicy: IfNotPresent + command: ['/kubexit/kubexit', 'yarn'] + args: ['collect'] + env: + - name: KUBEXIT_NAME + value: graph-kubernetes + - name: KUBEXIT_GRAVEYARD + value: /graveyard + - name: KUBEXIT_BIRTH_DEPS + value: fluent-bit,otel-collector + - name: KUBEXIT_POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + - name: KUBEXIT_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + - name: JUPITERONE_DEV + value: 'true' + - name: ACCESS_TYPE + value: 'namespace' + - name: NAMESPACE + value: 'default' + - name: IS_RUNNING_TEST + value: 'false' + - name: JUPITERONE_ACCOUNT_ID + valueFrom: + secretKeyRef: + name: graph-kubernetes + key: jupiteroneAccountId + - name: JUPITERONE_API_KEY + valueFrom: + secretKeyRef: + name: graph-kubernetes + key: jupiteroneApiKey + - name: INTEGRATION_INSTANCE_ID + valueFrom: + secretKeyRef: + name: graph-kubernetes + key: jupiteroneIntegrationInstanceId + volumeMounts: + - mountPath: /graveyard + name: graveyard + - mountPath: /kubexit + name: kubexit + - name: otel-collector + image: otel/opentelemetry-collector-contrib:0.33.0 + imagePullPolicy: IfNotPresent + command: ['/kubexit/kubexit', '/otelcontribcol'] + args: ['--config=/conf/otel-agent-config.yaml'] + env: + - name: KUBEXIT_NAME + value: otel-collector + - name: KUBEXIT_GRAVEYARD + value: /graveyard + - name: KUBEXIT_DEATH_DEPS + value: fluent-bit + - name: KUBEXIT_BIRTH_DEPS + value: fluent-bit + - name: KUBEXIT_POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + - name: KUBEXIT_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + volumeMounts: + - name: otel-agent-config-vol + mountPath: /conf + - mountPath: /graveyard + name: graveyard + - mountPath: /kubexit + name: kubexit + - name: fluent-bit + image: fluent/fluent-bit:1.8 + imagePullPolicy: IfNotPresent + command: ['/kubexit/kubexit', '/fluent-bit/bin/fluent-bit'] + args: ['--config=/fluent-bit/etc/fluent-bit.conf'] + env: + - name: KUBEXIT_NAME + value: fluent-bit + - name: KUBEXIT_GRAVEYARD + value: /graveyard + - name: KUBEXIT_DEATH_DEPS + value: graph-kubernetes + volumeMounts: + - name: log-storage + mountPath: /mnt/log/ + readOnly: true + - name: fluent-bit-agent-config-vol + mountPath: /fluent-bit/etc/ + - mountPath: /graveyard + name: graveyard + - mountPath: /kubexit + name: kubexit + - name: varlog + mountPath: /var/log + readOnly: true + - name: varlibdockercontainers + mountPath: /var/lib/docker/containers + readOnly: true + restartPolicy: Never + terminationGracePeriodSeconds: 10 + volumes: + - name: fluent-bit-agent-config-vol + configMap: + name: fluent-bit-agent-conf + items: + - key: fluent-bit + path: fluent-bit.conf + - key: parsers + path: parsers.conf + - name: otel-agent-config-vol + configMap: + name: otel-agent-conf + items: + - key: otel-agent-config + path: otel-agent-config.yaml + - name: graveyard + emptyDir: + medium: Memory + - name: kubexit + emptyDir: {} + - name: log-storage + emptyDir: {} + - name: varlog + hostPath: + path: /var/log + - name: varlibdockercontainers + hostPath: + path: /var/lib/docker/containers diff --git a/configs/observability/fluentbit.example.conf b/configs/observability/fluentbit.example.conf new file mode 100644 index 0000000..61497c5 --- /dev/null +++ b/configs/observability/fluentbit.example.conf @@ -0,0 +1,15 @@ +[SERVICE] + Parsers_File ./parsers.conf + Grace 15 +[INPUT] + Name tail + Path /var/log/containers/graph-kubernetes*.log + Exclude_Path /var/log/containers/*otel-collector*.log + Parser cri + Tag kube.* + Mem_Buf_Limit 5MB +[OUTPUT] + Name forward + Host 0.0.0.0 + Port 8006 + Match * diff --git a/configs/observability/fluentbit.parsers.example.conf b/configs/observability/fluentbit.parsers.example.conf new file mode 100644 index 0000000..04e9652 --- /dev/null +++ b/configs/observability/fluentbit.parsers.example.conf @@ -0,0 +1,6 @@ +[PARSER] + Name jupiter_one + Format json + Time_Key time + Time_Format %Y-%m-%dT%H:%M:%S.%LZ + Time_Keep On diff --git a/configs/observability/opentelemetry.collector.example.yml b/configs/observability/opentelemetry.collector.example.yml new file mode 100644 index 0000000..b2e92dc --- /dev/null +++ b/configs/observability/opentelemetry.collector.example.yml @@ -0,0 +1,50 @@ +receivers: + hostmetrics: + collection_interval: 10s + scrapers: + cpu: + load: + memory: + disk: + filesystem: + network: + paging: + processes: + otlp: + protocols: + grpc: + http: + fluentforward: + endpoint: 0.0.0.0:8006 + +processors: + batch: + memory_limiter: + limit_mib: 400 + spike_limit_mib: 100 + check_interval: 5s + +exporters: + logging: + loglevel: debug + sampling_initial: 5 + sampling_thereafter: 200 + +extensions: + memory_ballast: + size_mib: 165 + +service: + pipelines: + logs: + receivers: [fluentforward] + processors: [memory_limiter, batch] + exporters: [logging] + metrics: + receivers: [hostmetrics] + processors: [memory_limiter, batch] + exporters: [logging] + traces: + receivers: [otlp] + processors: [memory_limiter, batch] + exporters: [logging] diff --git a/docs/development.md b/docs/development.md index 8c874d8..1bc8ab9 100644 --- a/docs/development.md +++ b/docs/development.md @@ -1,124 +1,21 @@ # Development -Add details here to give a brief overview of how to work with the provider APIs. -Please reference any SDKs or API docs used to help build the integration here. +## Documentation -## Prerequisites - -Supply details about software or tooling (like maybe Docker or Terraform) that -is needed for development here. - -Please supply references to documentation that details how to install those -dependencies here. - -Tools like Node.js and NPM are already covered in the [README](../README.md) so -don't bother documenting that here. - -## Provider account setup - -### Helm - -The easiest way to install and update the `graph-kubernetes` project is through -the published helm chart. You can find information on how to install our -repository [here](https://github.com/JupiterOne/helm-charts) with specific -information about maintain the graph-kubernetes chart -[here](https://github.com/JupiterOne/helm-charts/tree/main/charts/graph-kubernetes). - -#### Quickstart - -```console -helm repo add jupiterone https://jupiterone.github.io/helm-charts -helm repo update -helm install [RELEASE_NAME] jupiterone/graph-kubernetes --set secrets.jupiteroneAccountId="some-account-id" --set secrets.jupiteroneApiKey="some-api-key" --set secrets.jupiteroneIntegrationInstanceId="some-integration-instance-id" -``` - -### K8S Standard - -#### Authentication - -##### RBAC - -This integration expects a service account with either specific namespace -read-only access or cluster-wide read-only access. - -#### Creating service account with namespace read-only access - -1. Create a new service account - -`kubectl create sa jupiterone-integration` - -2. Assign namespace read-only access - -`kubectl create rolebinding jupiterone-integration-view --clusterrole=view --serviceaccount=default:jupiterone-integration --namespace=default` - -#### Creating service account with cluster-wide read-only access - -1. Create a new service account - -`kubectl create sa jupiterone-integration-cluster` - -2. Assign cluster-wide read-only access +- [Kubernetes Reference Documentation](https://kubernetes.io/docs/reference/) +- [OpenTelemetry Documentation](https://opentelemetry.io/docs/) -`kubectl apply -f clusterRole.yml` - -`kubectl apply -f clusterRoleBinding.yml` - -If using a different service account name or different namespace name, make sure -to use the correct name in both the commands/yml listed above. - -### Secrets - -The integration requires you to store `jupiterone account id`, -`jupiterone api key` and `integration id` as secrets that will be read by the -pod. - -1. Update the `createSecret.yml` with base64 encoded values. -2. `kubectl apply -f createSecret.yml` - -### Deploying - -To deploy the built image as a pod: - -a) To create cronjob deployment for a service account with namespace read-only -access `kubectl apply -f cronjobNamespace.yml` - -b) To create deployment for a service account with entire cluster read-only -access `kubectl apply -f cronjobCluster.yml` - -### Debugging - -```console -# To check if the cronjob has been created -kubectl get cronjob - -# To check if the cronjob has spawned any jobs -kubectl get job - -# To see the logs -kubectl logs --selector job-name=job-name -``` - -### Uninstall - -```console -# Delete the deployment -kubectl delete cronjob - -# Delete the service account -kubectl delete serviceaccount -n - -# Delete the cluster role binding -kubectl delete clusterrolebinding - -# Delete the cluster role binding -kubectl delete clusterole -``` - -### Upgrading - -To upgrade a particular resource (cronjob, secrets, etc) all you need to do is -reapply the yaml: +## Prerequisites -```console -kubectl apply -f resourceFile.yaml -``` +- Kubernetes: The quickest way to get setup with one is through `docker for mac` + or a managed setup through GKE/AKS/EKS. +- [Tilt](https://tilt.dev): This is used to hot reload the integration and make + it easier to test changes incrementally. We can also preview what type of + telemetry we'll be exposing to end users because it mimics the customer setup. + +1. Create a Kubernetes integration. +2. Fill out the `.env` file with the appropriate values. +3. Run `tilt up`. +4. Press `s` for log streaming or `space` to get a browser application to + explore the logs. +5. Verify integration jobs are being kicked off in your Kubernetes integration. diff --git a/docs/jupiterone.md b/docs/jupiterone.md index 71cd4b6..cce12e8 100644 --- a/docs/jupiterone.md +++ b/docs/jupiterone.md @@ -2,25 +2,20 @@ ## Kubernetes + JupiterOne Integration Benefits -TODO: Iterate the benefits of ingesting data from the provider into JupiterOne. -Consider the following examples: - - Visualize Kubernetes resources in the JupiterOne graph. - Monitor changes using JupiterOne alerts. ## How it Works -TODO: Iterate significant activities the integration enables. Consider the -following examples: - -- JupiterOne periodically fetches resources from Kubernetes to update the graph. +- When the docker image runs it will fetches resources from Kubernetes to update + the graph. - Write JupiterOne queries to review and monitor updates to the graph. - Configure alerts to take action when JupiterOne graph changes. ## Requirements - A running Kubernetes cluster. This integration will be deployed as a pod and - interact with Kuberenetes API server. + interact with Kubernetes API server. ## Support @@ -29,13 +24,8 @@ If you need help with this integration, please contact ## Integration Walkthrough -Todo: Some section about finding the k8s integration docker image - ### In JupiterOne -TODO: List specific actions that must be taken in JupiterOne. Many of the -following steps will be reusable; take care to be sure they remain accurate. - 1. From the configuration **Gear Icon**, select **Integrations**. 2. Scroll to the **Kubernetes** integration tile and click it. 3. Click the **Add Configuration** button. @@ -44,10 +34,139 @@ following steps will be reusable; take care to be sure they remain accurate. `tag.AccountName` when **Tag with Account Name** is checked. 5. Enter a **Description** that will further assist your team when identifying the integration instance. -6. Select a **Polling Interval** that you feel is sufficient for your monitoring - needs. You may leave this as `DISABLED` and manually execute the integration. -7. Enter the **Kubernetes API Key** generated for use by JupiterOne. -8. Click **Create Configuration** once all values are provided. +6. Click **Create Configuration** once all values are provided. +7. On the **Configuration Settings** page click **CREATE** next to **Integration + API Keys**. +8. Follow the prompts to create the **Integration API Key**, click **REVEAL**, + make note of the API Key. +9. Below you will need to decide how you install the Kubernetes integration in + your cluster. As part of the installation you will need: + - The **Integration API Key** you just created + - The **Integration Instance Id** (which is listed as ID in the + **Configuration Settings**) + - Your **Account Id** (listed under **Account Management** after clicking the + **Gear Icon**). + +### In Kubernetes Via Helm + +The easiest way to install and update the `graph-kubernetes` project is through +the published helm chart. You can find information on how to install our +repository [here](https://github.com/JupiterOne/helm-charts) with specific +information about maintain the graph-kubernetes chart +[here](https://github.com/JupiterOne/helm-charts/tree/main/charts/graph-kubernetes). + +#### Quickstart + +```console +helm repo add jupiterone https://jupiterone.github.io/helm-charts +helm repo update +helm install [RELEASE_NAME] jupiterone/graph-kubernetes --set secrets.jupiteroneAccountId="some-account-id" --set secrets.jupiteroneApiKey="some-api-key" --set secrets.jupiteroneIntegrationInstanceId="some-integration-instance-id" +``` + +### In Kubernetes Via Standard YAML + +#### Authentication + +##### RBAC + +This integration expects a service account with either specific namespace +read-only access or cluster-wide read-only access. + +#### Creating service account with namespace read-only access + +1. Create a new service account + +`kubectl create sa jupiterone-integration` + +2. Assign namespace read-only access + +`kubectl create rolebinding jupiterone-integration-view --clusterrole=view --serviceaccount=default:jupiterone-integration --namespace=default` + +#### Creating service account with cluster-wide read-only access + +1. Create a new service account + +`kubectl create sa jupiterone-integration-cluster` + +2. Assign cluster-wide read-only access + +`kubectl apply -f clusterRole.yml` + +`kubectl apply -f clusterRoleBinding.yml` + +If using a different service account name or different namespace name, make sure +to use the correct name in both the commands/yaml listed above. + +#### Secrets + +The integration requires you to store `jupiterone account id`, +`jupiterone api key` and `integration id` as secrets that will be read by the +pod. + +1. Update the `createSecret.yml` with base64 encoded values. +2. `kubectl apply -f createSecret.yml` + +#### Deploying + +To deploy the built image as a pod: + +a) To create cronjob deployment for a service account with namespace read-only +access `kubectl apply -f cronjobNamespace.yml` + +b) To create deployment for a service account with entire cluster read-only +access `kubectl apply -f cronjobCluster.yml` + +#### Debugging + +```console +# To check if the cronjob has been created +kubectl get cronjob + +# To check if the cronjob has spawned any jobs +kubectl get job + +# To see the logs +kubectl logs --selector job-name=job-name +``` + +#### Uninstall + +```console +# Delete the deployment +kubectl delete cronjob + +# Delete the service account +kubectl delete serviceaccount -n + +# Delete the cluster role binding +kubectl delete clusterrolebinding + +# Delete the cluster role binding +kubectl delete clusterole +``` + +#### Upgrading + +To upgrade a particular resource (cronjob, secrets, etc) all you need to do is +reapply the yaml: + +```console +kubectl apply -f resourceFile.yaml +``` + +## Advanced Usage + +### Telemetry and Diagnostics + +The Helm charts and vanilla Kubernetes yaml are instrumented with the +[OpenTelemetry Collector](https://opentelemetry.io/docs/collector/getting-started/) +and [FluentBit](https://docs.fluentbit.io/manual/) with FluentBit forwarding +docker logs into the OpenTelemetry Collector. If you'd like to forward the same +telemetry to your own internal systems (CloudWatch, Prometheus, etc) +[configure](https://opentelemetry.io/docs/collector/configuration/) the +collector to point to them and update the manifests. + +For detailed information on installing the Kubernetes install # How to Uninstall @@ -64,7 +183,7 @@ NOTE: ALL OF THE FOLLOWING DOCUMENTATION IS GENERATED USING THE "j1-integration document" COMMAND. DO NOT EDIT BY HAND! PLEASE SEE THE DEVELOPER DOCUMENTATION FOR USAGE INFORMATION: -https://github.com/JupiterOne/sdk/blob/master/docs/integrations/development.md +https://github.com/JupiterOne/sdk/blob/main/docs/integrations/development.md ******************************************************************************** --> diff --git a/tilt.yml b/tilt.yml new file mode 100644 index 0000000..e596db6 --- /dev/null +++ b/tilt.yml @@ -0,0 +1,302 @@ +apiVersion: v1 +kind: List +items: +- apiVersion: v1 + kind: ServiceAccount + metadata: + name: graph-kubernetes +- apiVersion: rbac.authorization.k8s.io/v1 + kind: ClusterRole + metadata: + annotations: + rbac.authorization.kubernetes.io/autoupdate: 'true' + labels: + name: graph-kubernetes + name: graph-kubernetes + namespace: default + rules: + - apiGroups: + - '' + resources: ['*'] + verbs: + - get + - list + - watch + - apiGroups: + - extensions + resources: ['*'] + verbs: + - get + - list + - watch + - apiGroups: + - apps + resources: ['*'] + verbs: + - get + - list + - watch + - apiGroups: + - batch + resources: ['*'] + verbs: + - get + - list + - watch +- apiVersion: rbac.authorization.k8s.io/v1 + kind: ClusterRoleBinding + metadata: + labels: + name: graph-kubernetes + name: graph-kubernetes + subjects: + - kind: ServiceAccount + name: graph-kubernetes + namespace: default + roleRef: + kind: ClusterRole + name: graph-kubernetes + apiGroup: rbac.authorization.k8s.io +- apiVersion: v1 + kind: ConfigMap + metadata: + name: fluent-bit-agent-conf + labels: + app: fluent-bit + component: fluent-bit-agent-conf + data: + fluent-bit: | + [SERVICE] + Parsers_File ./parsers.conf + Grace 15 + [INPUT] + Name tail + Path /var/log/containers/graph-kubernetes*.log + Exclude_Path /var/log/containers/*otel-collector*.log + Parser cri + Tag kube.* + Mem_Buf_Limit 5MB + [OUTPUT] + Name forward + Host 0.0.0.0 + Port 8006 + Match * + parsers: | + [PARSER] + Name jupiter_one + Format json + Time_Key time + Time_Format %Y-%m-%dT%H:%M:%S.%LZ + Time_Keep On +- apiVersion: v1 + kind: ConfigMap + metadata: + name: otel-agent-conf + labels: + app: opentelemetry + component: otel-agent-conf + data: + otel-agent-config: | + receivers: + hostmetrics: + collection_interval: 10s + scrapers: + cpu: + load: + memory: + disk: + filesystem: + network: + paging: + processes: + otlp: + protocols: + grpc: + http: + fluentforward: + endpoint: 0.0.0.0:8006 + + processors: + batch: + memory_limiter: + limit_mib: 400 + spike_limit_mib: 100 + check_interval: 5s + + exporters: + logging: + loglevel: debug + sampling_initial: 5 + sampling_thereafter: 200 + + extensions: + memory_ballast: + size_mib: 165 + + service: + pipelines: + logs: + receivers: [fluentforward] + processors: [memory_limiter, batch] + exporters: [logging] + metrics: + receivers: [hostmetrics] + processors: [memory_limiter, batch] + exporters: [logging] + traces: + receivers: [otlp] + processors: [memory_limiter, batch] + exporters: [logging] +- apiVersion: batch/v1 + kind: Job + metadata: + name: graph-kubernetes + spec: + template: + spec: + serviceAccountName: graph-kubernetes + backoffLimit: 4 + initContainers: + - name: kubexit + image: karlkfi/kubexit:latest + command: ['cp'] + args: ['/bin/kubexit', '/kubexit/kubexit'] + volumeMounts: + - mountPath: /kubexit + name: kubexit + containers: + - name: graph-kubernetes + image: graph-kubernetes + imagePullPolicy: IfNotPresent + command: ['/kubexit/kubexit', 'yarn'] + args: ['collect'] + env: + - name: KUBEXIT_NAME + value: graph-kubernetes + - name: KUBEXIT_GRAVEYARD + value: /graveyard + - name: KUBEXIT_BIRTH_DEPS + value: fluent-bit,otel-collector + - name: KUBEXIT_POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + - name: KUBEXIT_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + - name: JUPITERONE_DEV + value: 'true' + - name: ACCESS_TYPE + value: 'cluster' + - name: NAMESPACE + value: 'default' + - name: IS_RUNNING_TEST + value: 'false' + - name: JUPITERONE_ACCOUNT_ID + valueFrom: + secretKeyRef: + name: graph-kubernetes + key: jupiteroneAccountId + - name: JUPITERONE_API_KEY + valueFrom: + secretKeyRef: + name: graph-kubernetes + key: jupiteroneApiKey + - name: INTEGRATION_INSTANCE_ID + valueFrom: + secretKeyRef: + name: graph-kubernetes + key: jupiteroneIntegrationInstanceId + volumeMounts: + - mountPath: /graveyard + name: graveyard + - mountPath: /kubexit + name: kubexit + - name: otel-collector + image: otel/opentelemetry-collector-contrib:0.33.0 + imagePullPolicy: IfNotPresent + command: ['/kubexit/kubexit', '/otelcontribcol'] + args: ['--config=/conf/otel-agent-config.yaml'] + env: + - name: KUBEXIT_NAME + value: otel-collector + - name: KUBEXIT_GRAVEYARD + value: /graveyard + - name: KUBEXIT_DEATH_DEPS + value: fluent-bit + - name: KUBEXIT_BIRTH_DEPS + value: fluent-bit + - name: KUBEXIT_POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + - name: KUBEXIT_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + volumeMounts: + - name: otel-agent-config-vol + mountPath: /conf + - mountPath: /graveyard + name: graveyard + - mountPath: /kubexit + name: kubexit + - name: fluent-bit + image: fluent/fluent-bit:1.8 + imagePullPolicy: IfNotPresent + command: ['/kubexit/kubexit', '/fluent-bit/bin/fluent-bit'] + args: ['--config=/fluent-bit/etc/fluent-bit.conf'] + env: + - name: KUBEXIT_NAME + value: fluent-bit + - name: KUBEXIT_GRAVEYARD + value: /graveyard + - name: KUBEXIT_DEATH_DEPS + value: graph-kubernetes + volumeMounts: + - name: log-storage + mountPath: /mnt/log/ + readOnly: true + - name: fluent-bit-agent-config-vol + mountPath: /fluent-bit/etc/ + - mountPath: /graveyard + name: graveyard + - mountPath: /kubexit + name: kubexit + - name: varlog + mountPath: /var/log + readOnly: true + - name: varlibdockercontainers + mountPath: /var/lib/docker/containers + readOnly: true + restartPolicy: Never + terminationGracePeriodSeconds: 10 + volumes: + - name: fluent-bit-agent-config-vol + configMap: + name: fluent-bit-agent-conf + items: + - key: fluent-bit + path: fluent-bit.conf + - key: parsers + path: parsers.conf + - name: otel-agent-config-vol + configMap: + name: otel-agent-conf + items: + - key: otel-agent-config + path: otel-agent-config.yaml + - name: graveyard + emptyDir: + medium: Memory + - name: kubexit + emptyDir: {} + - name: log-storage + emptyDir: {} + - name: varlog + hostPath: + path: /var/log + - name: varlibdockercontainers + hostPath: + path: /var/lib/docker/containers