From 81406cb773cb8adfb3e38d388533169ca51f5133 Mon Sep 17 00:00:00 2001 From: Ryan McAfee Date: Wed, 16 Sep 2026 14:15:59 -0500 Subject: [PATCH 01/11] feat(jupiterone-integration-operator): support IRSA on integration job service accounts Integration job pods run under the `default` ServiceAccount, and the chart had no way to annotate it, so giving a job pod an AWS identity meant running `kubectl annotate` out of band and re-running it whenever the namespace was rebuilt. Integrations that call AWS -- SBOM for AWS ECR reading a customer registry in the same or another account -- need that identity to exist as configuration, not as a manual step. Adds `integration.jobServiceAccount` (create/name/annotations), which creates the ServiceAccount and passes it to the operator via INTEGRATION_JOB_SERVICE_ACCOUNT so every integration job pod runs under it, and `integration.serviceAccount.annotations` for the kubernetes-managed integration ServiceAccount. Both default off, so rendered output is unchanged unless the values are set. Co-Authored-By: Ryan McAfee - Bot --- .../Chart.yaml | 2 +- .../jupiterone-integration-operator/README.md | 61 ++++++++ .../templates/_helpers.tpl | 9 ++ .../integration/job-serviceaccount.yaml | 13 ++ .../templates/integration/serviceaccount.yaml | 6 +- .../templates/manager/manager.yaml | 4 + .../tests/irsa-service-account_test.sh | 132 ++++++++++++++++++ .../values.yaml | 25 ++++ 8 files changed, 250 insertions(+), 2 deletions(-) create mode 100644 charts/jupiterone-integration-operator/templates/integration/job-serviceaccount.yaml create mode 100755 charts/jupiterone-integration-operator/tests/irsa-service-account_test.sh diff --git a/charts/jupiterone-integration-operator/Chart.yaml b/charts/jupiterone-integration-operator/Chart.yaml index 0b61e9c..c0b7ba6 100644 --- a/charts/jupiterone-integration-operator/Chart.yaml +++ b/charts/jupiterone-integration-operator/Chart.yaml @@ -2,5 +2,5 @@ apiVersion: v2 name: jupiterone-integration-operator description: JupiterOne Integration Operator for running integrations in Kubernetes type: application -version: 1.3.4 +version: 1.4.0 appVersion: "v0.3.1" diff --git a/charts/jupiterone-integration-operator/README.md b/charts/jupiterone-integration-operator/README.md index 40ae015..9e40731 100644 --- a/charts/jupiterone-integration-operator/README.md +++ b/charts/jupiterone-integration-operator/README.md @@ -55,6 +55,11 @@ Refer to the [values.yaml](./values.yaml) for all available configuration option | `controllerManager.imagePullSecrets` | Secrets for pulling images from private registries. Applied to both the operator Deployment and propagated to spawned integration job pods. | `[]` | | `controllerManager.disableImageSignatureCheck` | Disable cosign image signature verification for integration job images. Set to `true` when using registries that don't mirror ghcr.io cosign signatures. | `false` | | `controllerManager.jobResources` | Resource requests and limits applied to integration job containers. Configure to comply with cluster resource policies (e.g. Kyverno, OPA/Gatekeeper). | `{}` | +| `controllerManager.serviceAccount.annotations` | Annotations on the operator ServiceAccount. Set the IRSA role here when the operator reads credentials from AWS Secrets Manager. | `{}` | +| `integration.serviceAccount.annotations` | Annotations on the `kubernetes-managed` integration ServiceAccount. | `{}` | +| `integration.jobServiceAccount.create` | Create a ServiceAccount for all other integration job pods. | `false` | +| `integration.jobServiceAccount.name` | Name of that ServiceAccount. Defaults to `jupiterone-integration-job` when created. Set without `create` to reference one managed elsewhere. | `""` | +| `integration.jobServiceAccount.annotations` | Annotations on the integration job ServiceAccount. Set the IRSA role here to give job pods an AWS identity. | `{}` | ### Private Registry Example @@ -78,6 +83,62 @@ helm install integration-operator jupiterone/jupiterone-integration-operator \ > **Note:** `disableImageSignatureCheck` is independent of `imageRegistry`. Cosign verification may work through registry proxies since it resolves signatures against the original source. Only disable it if verification fails in your environment. +### AWS Access for Integration Job Pods (IRSA) + +Integration job pods run under the `default` ServiceAccount, which normally has +no AWS identity. Integrations that call AWS -- for example SBOM for AWS ECR -- +need one. Set `integration.jobServiceAccount` and the chart creates the +ServiceAccount, annotates it for IRSA, and points the operator at it through +`INTEGRATION_JOB_SERVICE_ACCOUNT`, so every integration job pod runs with that +identity. + +```yaml +integration: + jobServiceAccount: + create: true + annotations: + eks.amazonaws.com/role-arn: arn:aws:iam:::role/jupiterone-integration-job +``` + +The `kubernetes-managed` integration keeps its own ServiceAccount (it is bound +to the in-cluster read ClusterRole) and is annotated separately: + +```yaml +integration: + serviceAccount: + annotations: + eks.amazonaws.com/role-arn: arn:aws:iam:::role/jupiterone-kubernetes-managed +``` + +The operator itself needs a role only when a CR resolves credentials from AWS +Secrets Manager: + +```yaml +controllerManager: + serviceAccount: + annotations: + eks.amazonaws.com/role-arn: arn:aws:iam:::role/jupiterone-integration-operator +``` + +**Same-account ECR.** Grant the job role ECR read directly +(`ecr:GetAuthorizationToken` on `*`, plus `ecr:BatchGetImage`, +`ecr:GetDownloadUrlForLayer`, `ecr:BatchCheckLayerAvailability`, +`ecr:DescribeRepositories`, `ecr:DescribeImages`, `ecr:ListImages`, +`ecr:ListTagsForResource` on the repository ARNs). + +**Cross-account ECR.** Grant the job role only `sts:AssumeRole` on the role in +the registry's account; that role holds the ECR permissions and trusts the job +role, pinned to the External ID generated when the integration instance is +saved. + +Trust policies, IAM policy documents, and Terraform, Crossplane and +CloudFormation examples are in the operator repository: +[AWS authentication (IRSA)](https://github.com/JupiterOne/jupiterone-integration-operator#aws-authentication-irsa). + +Requires the operator release that adds `INTEGRATION_JOB_SERVICE_ACCOUNT` +(`v0.4.0`). On older operators the env var is ignored and job pods keep using +the `default` ServiceAccount. + ### Job Resources Example If your cluster enforces resource policies (e.g. Kyverno `require-requests-limits`), configure resource requirements for integration job containers: diff --git a/charts/jupiterone-integration-operator/templates/_helpers.tpl b/charts/jupiterone-integration-operator/templates/_helpers.tpl index c699264..ed85015 100644 --- a/charts/jupiterone-integration-operator/templates/_helpers.tpl +++ b/charts/jupiterone-integration-operator/templates/_helpers.tpl @@ -48,3 +48,12 @@ app.kubernetes.io/instance: {{ .Release.Name }} $hasValidating = true }}{{- end }} {{- end }} {{ $hasValidating }}}}{{- end }} + + +{{- define "chart.integrationJobServiceAccountName" -}} +{{- if .Values.integration.jobServiceAccount.name -}} +{{- .Values.integration.jobServiceAccount.name -}} +{{- else if .Values.integration.jobServiceAccount.create -}} +jupiterone-integration-job +{{- end -}} +{{- end }} diff --git a/charts/jupiterone-integration-operator/templates/integration/job-serviceaccount.yaml b/charts/jupiterone-integration-operator/templates/integration/job-serviceaccount.yaml new file mode 100644 index 0000000..8128594 --- /dev/null +++ b/charts/jupiterone-integration-operator/templates/integration/job-serviceaccount.yaml @@ -0,0 +1,13 @@ +{{- if and .Values.integration.jobServiceAccount.create (include "chart.integrationJobServiceAccountName" .) -}} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "chart.integrationJobServiceAccountName" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "chart.labels" . | nindent 4 }} + {{- with .Values.integration.jobServiceAccount.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +{{- end }} diff --git a/charts/jupiterone-integration-operator/templates/integration/serviceaccount.yaml b/charts/jupiterone-integration-operator/templates/integration/serviceaccount.yaml index 4e12da7..9c6a0f7 100644 --- a/charts/jupiterone-integration-operator/templates/integration/serviceaccount.yaml +++ b/charts/jupiterone-integration-operator/templates/integration/serviceaccount.yaml @@ -6,4 +6,8 @@ metadata: namespace: {{ .Values.integration.serviceAccountNamespace }} labels: {{- include "chart.labels" . | nindent 4 }} -{{- end }} \ No newline at end of file + {{- with .Values.integration.serviceAccount.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +{{- end }} diff --git a/charts/jupiterone-integration-operator/templates/manager/manager.yaml b/charts/jupiterone-integration-operator/templates/manager/manager.yaml index 01f5b36..304a1b0 100644 --- a/charts/jupiterone-integration-operator/templates/manager/manager.yaml +++ b/charts/jupiterone-integration-operator/templates/manager/manager.yaml @@ -45,6 +45,10 @@ spec: fieldPath: metadata.namespace - name: K8S_INTEGRATION_SERVICE_ACCOUNT value: {{ .Values.integration.serviceAccountName }} + {{- with (include "chart.integrationJobServiceAccountName" .) }} + - name: INTEGRATION_JOB_SERVICE_ACCOUNT + value: {{ . | quote }} + {{- end }} {{- if .Values.controllerManager.imageRegistry }} - name: IMAGE_REGISTRY value: {{ .Values.controllerManager.imageRegistry | quote }} diff --git a/charts/jupiterone-integration-operator/tests/irsa-service-account_test.sh b/charts/jupiterone-integration-operator/tests/irsa-service-account_test.sh new file mode 100755 index 0000000..915d864 --- /dev/null +++ b/charts/jupiterone-integration-operator/tests/irsa-service-account_test.sh @@ -0,0 +1,132 @@ +#!/usr/bin/env bash +set -euo pipefail + +CHART_DIR="helm-charts/charts/jupiterone-integration-operator" +PASSED=0 +FAILED=0 + +if [ ! -d "$CHART_DIR" ]; then + echo "ERROR: Run this script from the repository root" + echo " Expected chart directory: $CHART_DIR" + exit 1 +fi + +# --- Helper functions --- + +assert_contains() { + local description="$1" + local needle="$2" + local haystack="$3" + + if echo "$haystack" | grep -qF "$needle"; then + echo " PASS: $description" + PASSED=$((PASSED + 1)) + else + echo " FAIL: $description" + echo " Expected output to contain: $needle" + FAILED=$((FAILED + 1)) + fi +} + +assert_not_contains() { + local description="$1" + local needle="$2" + local haystack="$3" + + if echo "$haystack" | grep -qF "$needle"; then + echo " FAIL: $description" + echo " Expected output NOT to contain: $needle" + FAILED=$((FAILED + 1)) + else + echo " PASS: $description" + PASSED=$((PASSED + 1)) + fi +} + +run_test() { + local test_name="$1" + local test_func="$2" + + echo "" + echo "=== Test: $test_name ===" + $test_func +} + +# --- Test cases --- + +test_default_values() { + local output + output=$(helm template test-release "$CHART_DIR") + + assert_not_contains "No job ServiceAccount by default" \ + "name: jupiterone-integration-job" "$output" + assert_not_contains "No INTEGRATION_JOB_SERVICE_ACCOUNT by default" \ + "INTEGRATION_JOB_SERVICE_ACCOUNT" "$output" + assert_contains "Integration ServiceAccount still rendered" \ + "name: jupiterone" "$output" +} + +test_integration_service_account_annotations() { + local output + output=$(helm template test-release "$CHART_DIR" \ + --set 'integration.serviceAccount.annotations.eks\.amazonaws\.com/role-arn=arn:aws:iam::123456789012:role/j1-k8s') + + assert_contains "IRSA annotation on the kubernetes-managed ServiceAccount" \ + "eks.amazonaws.com/role-arn: arn:aws:iam::123456789012:role/j1-k8s" "$output" +} + +test_job_service_account_created() { + local output + output=$(helm template test-release "$CHART_DIR" \ + --set integration.jobServiceAccount.create=true \ + --set 'integration.jobServiceAccount.annotations.eks\.amazonaws\.com/role-arn=arn:aws:iam::123456789012:role/j1-job') + + assert_contains "Job ServiceAccount created with the default name" \ + "name: jupiterone-integration-job" "$output" + assert_contains "IRSA annotation on the job ServiceAccount" \ + "eks.amazonaws.com/role-arn: arn:aws:iam::123456789012:role/j1-job" "$output" + assert_contains "INTEGRATION_JOB_SERVICE_ACCOUNT env var name present" \ + "name: INTEGRATION_JOB_SERVICE_ACCOUNT" "$output" + assert_contains "INTEGRATION_JOB_SERVICE_ACCOUNT value is correct" \ + 'value: "jupiterone-integration-job"' "$output" +} + +test_job_service_account_custom_name() { + local output + output=$(helm template test-release "$CHART_DIR" \ + --set integration.jobServiceAccount.create=true \ + --set integration.jobServiceAccount.name=sbom-runner) + + assert_contains "Job ServiceAccount uses the configured name" \ + "name: sbom-runner" "$output" + assert_contains "INTEGRATION_JOB_SERVICE_ACCOUNT uses the configured name" \ + 'value: "sbom-runner"' "$output" +} + +test_job_service_account_externally_managed() { + local output + output=$(helm template test-release "$CHART_DIR" \ + --set integration.jobServiceAccount.create=false \ + --set integration.jobServiceAccount.name=externally-managed) + + assert_contains "Operator points at the externally managed ServiceAccount" \ + 'value: "externally-managed"' "$output" + assert_not_contains "Chart does not create the externally managed ServiceAccount" \ + " name: externally-managed" "$output" +} + +# --- Run all tests --- + +run_test "Default values (backwards compatibility)" test_default_values +run_test "integration.serviceAccount.annotations set" test_integration_service_account_annotations +run_test "integration.jobServiceAccount created" test_job_service_account_created +run_test "integration.jobServiceAccount custom name" test_job_service_account_custom_name +run_test "integration.jobServiceAccount managed externally" test_job_service_account_externally_managed + +# --- Summary --- + +echo "" +echo "==============================" +echo "Results: $PASSED passed, $FAILED failed" +echo "==============================" +[ "$FAILED" -eq 0 ] || exit 1 diff --git a/charts/jupiterone-integration-operator/values.yaml b/charts/jupiterone-integration-operator/values.yaml index 22a6ad3..105b6f1 100644 --- a/charts/jupiterone-integration-operator/values.yaml +++ b/charts/jupiterone-integration-operator/values.yaml @@ -112,3 +112,28 @@ integration: # Service account namespace for the cluster role binding # NOTE: We don't support changing this at this time serviceAccountNamespace: jupiterone + # Annotations applied to the kubernetes-managed integration ServiceAccount + # above. Use this to give that integration an AWS identity via IRSA. + # Example: + # serviceAccount: + # annotations: + # eks.amazonaws.com/role-arn: arn:aws:iam:::role/ + serviceAccount: + annotations: {} + # ServiceAccount used by every other integration job pod (for example SBOM for + # AWS ECR). Integration job pods run as "default" until a name is set here, + # which is passed to the operator as INTEGRATION_JOB_SERVICE_ACCOUNT. + # Annotate it to grant the job pods an AWS identity via IRSA, including + # cross-account access where the annotated role assumes a role in the account + # that owns the ECR registry. + # Example: + # jobServiceAccount: + # create: true + # annotations: + # eks.amazonaws.com/role-arn: arn:aws:iam:::role/ + jobServiceAccount: + # Create the ServiceAccount. Set to false to reference one managed elsewhere. + create: false + # Defaults to "jupiterone-integration-job" when created. + name: "" + annotations: {} From da3b9dbcfe5d0f562ae6e443cd630a766e68e52d Mon Sep 17 00:00:00 2001 From: Ryan McAfee Date: Wed, 16 Sep 2026 14:22:59 -0500 Subject: [PATCH 02/11] docs(jupiterone-integration-operator): note shared job ServiceAccount scope The job ServiceAccount is shared by every integration job pod in a release, so record what belongs on its role (assume-role targets only) and how to separate workloads that need their own identity. Co-Authored-By: Ryan McAfee - Bot --- charts/jupiterone-integration-operator/README.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/charts/jupiterone-integration-operator/README.md b/charts/jupiterone-integration-operator/README.md index 9e40731..b0ef2f4 100644 --- a/charts/jupiterone-integration-operator/README.md +++ b/charts/jupiterone-integration-operator/README.md @@ -129,7 +129,14 @@ controllerManager: **Cross-account ECR.** Grant the job role only `sts:AssumeRole` on the role in the registry's account; that role holds the ECR permissions and trusts the job role, pinned to the External ID generated when the integration instance is -saved. +saved. Several registries means one such role per account and one +`sts:AssumeRole` resource per target. + +This ServiceAccount is shared by every integration job pod in the release, so +keep its own policy to the `sts:AssumeRole` targets it needs and leave the ECR +permissions on the assumed roles. Where a workload needs stronger separation, +install a second operator and runner in their own namespace with their own +`integration.jobServiceAccount`. Trust policies, IAM policy documents, and Terraform, Crossplane and CloudFormation examples are in the operator repository: From 16dd5075a0036dd49eef62cc16347a23b558fe86 Mon Sep 17 00:00:00 2001 From: Ryan McAfee Date: Wed, 16 Sep 2026 14:37:02 -0500 Subject: [PATCH 03/11] docs(jupiterone-integration-operator): clarify External ID belongs to the customer collector On a self-hosted collector the customer cluster assumes the scan role, so the trust Principal is the customer job pod role and sts:ExternalId is the External ID on that customer integration instance, not a JupiterOne account or the managed AWS integration External ID. Co-Authored-By: Ryan McAfee - Bot --- charts/jupiterone-integration-operator/README.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/charts/jupiterone-integration-operator/README.md b/charts/jupiterone-integration-operator/README.md index b0ef2f4..ac2376c 100644 --- a/charts/jupiterone-integration-operator/README.md +++ b/charts/jupiterone-integration-operator/README.md @@ -129,8 +129,11 @@ controllerManager: **Cross-account ECR.** Grant the job role only `sts:AssumeRole` on the role in the registry's account; that role holds the ECR permissions and trusts the job role, pinned to the External ID generated when the integration instance is -saved. Several registries means one such role per account and one -`sts:AssumeRole` resource per target. +saved. That trust is between two identities the customer owns: the `Principal` +is the customer's job pod role, and `sts:ExternalId` is the External ID shown on +the customer's integration instance -- not a JupiterOne AWS account and not the +External ID of the JupiterOne-managed AWS integration. Several registries means +one such role per account and one `sts:AssumeRole` resource per target. This ServiceAccount is shared by every integration job pod in the release, so keep its own policy to the `sts:AssumeRole` targets it needs and leave the ECR From 95ad627907b9e256a09dadc6805bf32f3be4e5e1 Mon Sep 17 00:00:00 2001 From: Ryan McAfee Date: Wed, 16 Sep 2026 15:28:15 -0500 Subject: [PATCH 04/11] docs(jupiterone-integration-operator): remove External ID from cross-account ECR guidance Co-Authored-By: Ryan McAfee - Bot --- charts/jupiterone-integration-operator/README.md | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/charts/jupiterone-integration-operator/README.md b/charts/jupiterone-integration-operator/README.md index ac2376c..9a39b89 100644 --- a/charts/jupiterone-integration-operator/README.md +++ b/charts/jupiterone-integration-operator/README.md @@ -128,12 +128,9 @@ controllerManager: **Cross-account ECR.** Grant the job role only `sts:AssumeRole` on the role in the registry's account; that role holds the ECR permissions and trusts the job -role, pinned to the External ID generated when the integration instance is -saved. That trust is between two identities the customer owns: the `Principal` -is the customer's job pod role, and `sts:ExternalId` is the External ID shown on -the customer's integration instance -- not a JupiterOne AWS account and not the -External ID of the JupiterOne-managed AWS integration. Several registries means -one such role per account and one `sts:AssumeRole` resource per target. +role. That trust is between two identities the customer owns: the `Principal` +is the customer's job pod role, not a JupiterOne AWS account. Several registries +means one such role per account and one `sts:AssumeRole` resource per target. This ServiceAccount is shared by every integration job pod in the release, so keep its own policy to the `sts:AssumeRole` targets it needs and leave the ECR From 00af119a78f3b3a6c60a9445f0cc2d8e25ecf3b0 Mon Sep 17 00:00:00 2001 From: Ryan McAfee Date: Wed, 16 Sep 2026 15:33:22 -0500 Subject: [PATCH 05/11] docs(jupiterone-integration-operator): drop reference to removed IaC examples page Co-Authored-By: Ryan McAfee - Bot --- charts/jupiterone-integration-operator/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/charts/jupiterone-integration-operator/README.md b/charts/jupiterone-integration-operator/README.md index 9a39b89..b5398d0 100644 --- a/charts/jupiterone-integration-operator/README.md +++ b/charts/jupiterone-integration-operator/README.md @@ -138,8 +138,8 @@ permissions on the assumed roles. Where a workload needs stronger separation, install a second operator and runner in their own namespace with their own `integration.jobServiceAccount`. -Trust policies, IAM policy documents, and Terraform, Crossplane and -CloudFormation examples are in the operator repository: +Trust policies, IAM policy documents and the step-by-step SBOM for AWS ECR +setup are in the operator repository: [AWS authentication (IRSA)](https://github.com/JupiterOne/jupiterone-integration-operator#aws-authentication-irsa). Requires the operator release that adds `INTEGRATION_JOB_SERVICE_ACCOUNT` From a274f56b8537ab6185c949b60517304d19c63c14 Mon Sep 17 00:00:00 2001 From: Ryan McAfee Date: Wed, 16 Sep 2026 16:39:03 -0500 Subject: [PATCH 06/11] feat(charts): document every value, expose runner apiTokenSource, fix metrics TLS name Operator chart (1.3.5): declare nameOverride, controllerManager.pod.labels and crd.keep, which templates already read but values.yaml never listed; fix the ServiceMonitor serverName, which pointed at a Service that does not exist so TLS scraping failed whenever prometheus and cert-manager were both enabled; document all values, the manager environment variables and the ServiceAccount annotation, Secrets Manager and metrics setups in the README; add a test that fails when a template reads an undeclared value. Runner chart (1.1.0): expose apiTokenSource so the runner can read its API token from AWS Secrets Manager or a named Kubernetes Secret, which the CRD already supported; refuse to render with a placeholder accountID or apiToken instead of registering a runner with a literal ""; rewrite the README, whose example used values that do not exist and whose upgrade and uninstall commands acted on the operator release, and state that the runner has no ServiceAccount of its own so job pod identity is configured on the operator chart; add render tests. Co-Authored-By: Ryan McAfee - Bot --- .../Chart.yaml | 2 +- .../jupiterone-integration-operator/README.md | 96 ++++++++++- .../templates/prometheus/monitor.yaml | 2 +- .../tests/declared-values_test.sh | 128 +++++++++++++++ .../values.yaml | 10 ++ .../jupiterone-integration-runner/Chart.yaml | 2 +- .../jupiterone-integration-runner/README.md | 146 +++++++++++------ .../templates/runner.yaml | 26 ++- .../templates/secret.yaml | 7 +- .../tests/runner-values_test.sh | 149 ++++++++++++++++++ .../jupiterone-integration-runner/values.yaml | 51 ++++-- 11 files changed, 552 insertions(+), 67 deletions(-) create mode 100755 charts/jupiterone-integration-operator/tests/declared-values_test.sh create mode 100755 charts/jupiterone-integration-runner/tests/runner-values_test.sh diff --git a/charts/jupiterone-integration-operator/Chart.yaml b/charts/jupiterone-integration-operator/Chart.yaml index 0b61e9c..85a2423 100644 --- a/charts/jupiterone-integration-operator/Chart.yaml +++ b/charts/jupiterone-integration-operator/Chart.yaml @@ -2,5 +2,5 @@ apiVersion: v2 name: jupiterone-integration-operator description: JupiterOne Integration Operator for running integrations in Kubernetes type: application -version: 1.3.4 +version: 1.3.5 appVersion: "v0.3.1" diff --git a/charts/jupiterone-integration-operator/README.md b/charts/jupiterone-integration-operator/README.md index 40ae015..9a404db 100644 --- a/charts/jupiterone-integration-operator/README.md +++ b/charts/jupiterone-integration-operator/README.md @@ -51,10 +51,98 @@ Refer to the [values.yaml](./values.yaml) for all available configuration option | Parameter | Description | Default | |---|---|---| -| `controllerManager.imageRegistry` | Image registry override for private registry environments. When set, integration job images are pulled from this registry instead of `ghcr.io`. | `""` | -| `controllerManager.imagePullSecrets` | Secrets for pulling images from private registries. Applied to both the operator Deployment and propagated to spawned integration job pods. | `[]` | -| `controllerManager.disableImageSignatureCheck` | Disable cosign image signature verification for integration job images. Set to `true` when using registries that don't mirror ghcr.io cosign signatures. | `false` | -| `controllerManager.jobResources` | Resource requests and limits applied to integration job containers. Configure to comply with cluster resource policies (e.g. Kyverno, OPA/Gatekeeper). | `{}` | +| `nameOverride` | Overrides the chart name used in the `app.kubernetes.io/name` label. | `""` | +| `controllerManager.replicas` | Manager replicas. Leader election is on, so extra replicas are standby only. | `1` | +| `controllerManager.container.image.repository` | Manager image repository. | `ghcr.io/jupiterone/jupiterone-integration-operator` | +| `controllerManager.container.image.tag` | Manager image tag. Empty uses the chart `appVersion`. | `""` | +| `controllerManager.container.args` | Manager command-line flags. | `--leader-elect`, `--metrics-bind-address=:8443`, `--health-probe-bind-address=:8081` | +| `controllerManager.container.env` | Extra environment variables on the manager as a `KEY: value` map (`JOB_TTL_SECONDS`, `JOB_ACTIVE_DEADLINE_SECONDS`, `ASM_CACHE_TTL_SECONDS`, `AWS_REGION`, `LOG_LEVEL`, `HTTP_PROXY`, ...). | `JOB_TTL_SECONDS: "604800"` | +| `controllerManager.container.resources` | Manager container requests and limits. | `100m`/`64Mi` requests, `500m`/`512Mi` limits | +| `controllerManager.container.livenessProbe` | Manager liveness probe. | `GET /healthz` on `8081` | +| `controllerManager.container.readinessProbe` | Manager readiness probe. | `GET /readyz` on `8081` | +| `controllerManager.container.securityContext` | Manager container security context. | `allowPrivilegeEscalation: false`, drop `ALL` | +| `controllerManager.securityContext` | Manager pod security context. | `runAsNonRoot: true`, seccomp `RuntimeDefault` | +| `controllerManager.pod.labels` | Extra labels on the manager pod. | `{}` | +| `controllerManager.terminationGracePeriodSeconds` | Manager pod termination grace period. | `10` | +| `controllerManager.serviceAccountName` | Name of the operator ServiceAccount. | `jupiterone-integration-operator-controller-manager` | +| `controllerManager.serviceAccount.annotations` | Annotations on the operator ServiceAccount. Set the IRSA role here when the operator reads credentials from AWS Secrets Manager. | `{}` | +| `controllerManager.imageRegistry` | Registry that replaces `ghcr.io` for integration job images (`/jupiterone/graph-:latest`). Hostname only. | `""` | +| `controllerManager.disableImageSignatureCheck` | Skip cosign signature verification of integration job images. | `false` | +| `controllerManager.imagePullSecrets` | `imagePullSecrets` for the manager pod and every integration job pod. | `[]` | +| `controllerManager.jobResources` | Requests and limits applied to integration job containers. | `{}` | +| `rbac.enable` | Create the operator ServiceAccount, Roles and bindings. | `true` | +| `metrics.enable` | Create the metrics Service. Remove `--metrics-bind-address` from `args` when disabling. | `true` | +| `prometheus.enable` | Create a `ServiceMonitor` for the metrics Service. | `false` | +| `certmanager.enable` | Issue the metrics serving certificate with cert-manager. | `false` | +| `crd.keep` | Keep the cert-manager Certificate on uninstall (`helm.sh/resource-policy: keep`). | `false` | +| `networkPolicy.enable` | Create a NetworkPolicy allowing metrics scrapes from namespaces labeled `metrics: enabled`. | `false` | +| `integration.create` | Create the `kubernetes-managed` integration ServiceAccount, ClusterRole and ClusterRoleBinding. | `true` | +| `integration.serviceAccountName` | ServiceAccount used by `kubernetes-managed` integration job pods (`K8S_INTEGRATION_SERVICE_ACCOUNT`). | `jupiterone` | +| `integration.serviceAccountNamespace` | Namespace of that ServiceAccount. Changing it is not supported. | `jupiterone` | + +Environment variables the manager reads that have no dedicated value are set +through `controllerManager.container.env`: + +| Variable | Description | Default | +|---|---|---| +| `JOB_TTL_SECONDS` | Seconds a finished `IntegrationInstanceJob` and its Job are kept. | `2592000`; the chart sets `604800` | +| `JOB_ACTIVE_DEADLINE_SECONDS` | `activeDeadlineSeconds` on each integration Job. | `86400` | +| `ASM_CACHE_TTL_SECONDS` | Cache TTL for AWS Secrets Manager lookups; `0` disables. | `60` | +| `AWS_REGION` | Region for the AWS SDK default chain when a CR omits `region`. | unset | +| `LOG_LEVEL` | `debug`, `info`, `warn` or `error`. | `info` | +| `HTTP_PROXY` / `HTTPS_PROXY` / `NO_PROXY` | Proxy settings; also injected into integration job pods. | unset | + +### ServiceAccount annotations (IRSA) + +Every ServiceAccount the chart creates accepts annotations, so an IAM role can +be attached without editing rendered manifests: + +```yaml +controllerManager: + serviceAccount: + annotations: + eks.amazonaws.com/role-arn: arn:aws:iam:::role/jupiterone-integration-operator +``` + +The operator needs that role only when a CR reads credentials from AWS Secrets +Manager (`apiTokenSource` / `secretSource` with `provider: awsSecretsManager`). +Trust policies and permission policies are in the operator README under +[AWS authentication (IRSA)](https://github.com/JupiterOne/jupiterone-integration-operator#aws-authentication-irsa). + +### AWS Secrets Manager Example + +```yaml +controllerManager: + serviceAccount: + annotations: + eks.amazonaws.com/role-arn: arn:aws:iam:::role/jupiterone-integration-operator + container: + env: + AWS_REGION: us-east-1 + ASM_CACHE_TTL_SECONDS: "60" +``` + +Then reference the secret from the runner chart (`apiTokenSource`) or an +`IntegrationInstance` (`secretSource`). + +### Metrics with Prometheus and cert-manager + +```yaml +metrics: + enable: true +prometheus: + enable: true +certmanager: + enable: true +networkPolicy: + enable: true +``` + +cert-manager issues `metrics-server-cert` for +`jupiterone-integration-operator-metrics-service..svc`, the +ServiceMonitor scrapes it over TLS, and the NetworkPolicy admits scrapes only +from namespaces labeled `metrics: enabled`. Without `certmanager.enable` the +ServiceMonitor uses `insecureSkipVerify: true`. ### Private Registry Example diff --git a/charts/jupiterone-integration-operator/templates/prometheus/monitor.yaml b/charts/jupiterone-integration-operator/templates/prometheus/monitor.yaml index 703eb35..c64dd94 100644 --- a/charts/jupiterone-integration-operator/templates/prometheus/monitor.yaml +++ b/charts/jupiterone-integration-operator/templates/prometheus/monitor.yaml @@ -16,7 +16,7 @@ spec: bearerTokenFile: /var/run/secrets/kubernetes.io/serviceaccount/token tlsConfig: {{- if .Values.certmanager.enable }} - serverName: jupiterone-integration-operator-controller-manager-metrics-service.{{ .Release.Namespace }}.svc + serverName: jupiterone-integration-operator-metrics-service.{{ .Release.Namespace }}.svc # Apply secure TLS configuration with cert-manager insecureSkipVerify: false ca: diff --git a/charts/jupiterone-integration-operator/tests/declared-values_test.sh b/charts/jupiterone-integration-operator/tests/declared-values_test.sh new file mode 100755 index 0000000..d48f964 --- /dev/null +++ b/charts/jupiterone-integration-operator/tests/declared-values_test.sh @@ -0,0 +1,128 @@ +#!/usr/bin/env bash +set -euo pipefail + +CHART_DIR="helm-charts/charts/jupiterone-integration-operator" +PASSED=0 +FAILED=0 + +if [ ! -d "$CHART_DIR" ]; then + echo "ERROR: Run this script from the repository root" + echo " Expected chart directory: $CHART_DIR" + exit 1 +fi + +# --- Helper functions --- + +assert_contains() { + local description="$1" + local needle="$2" + local haystack="$3" + + if echo "$haystack" | grep -qF "$needle"; then + echo " PASS: $description" + PASSED=$((PASSED + 1)) + else + echo " FAIL: $description" + echo " Expected output to contain: $needle" + FAILED=$((FAILED + 1)) + fi +} + +assert_not_contains() { + local description="$1" + local needle="$2" + local haystack="$3" + + if echo "$haystack" | grep -qF "$needle"; then + echo " FAIL: $description" + echo " Expected output NOT to contain: $needle" + FAILED=$((FAILED + 1)) + else + echo " PASS: $description" + PASSED=$((PASSED + 1)) + fi +} + +run_test() { + local test_name="$1" + local test_func="$2" + + echo "" + echo "=== Test: $test_name ===" + $test_func +} + +# --- Test cases --- + +test_every_template_value_is_declared() { + local referenced declared missing="" + referenced=$(grep -rhoE '\.Values\.[A-Za-z0-9_.]+' "$CHART_DIR/templates" | sed 's/^\.Values\.//' | sort -u) + declared=$(yq '[.. | path | join(".")] | .[]' "$CHART_DIR/values.yaml" | sed -E 's/\.[0-9]+//g' | grep -v '^$' | sort -u) + + for key in $referenced; do + if ! echo "$declared" | grep -qE "^${key}(\.|$)"; then + missing="$missing $key" + fi + done + + if [ -z "$missing" ]; then + echo " PASS: every .Values reference in templates is declared in values.yaml" + PASSED=$((PASSED + 1)) + else + echo " FAIL: undeclared values referenced by templates:$missing" + FAILED=$((FAILED + 1)) + fi +} + +test_pod_labels() { + local output + output=$(helm template test-release "$CHART_DIR" --set controllerManager.pod.labels.team=platform) + + assert_contains "pod label rendered" "team: platform" "$output" +} + +test_name_override() { + local output + output=$(helm template test-release "$CHART_DIR" --set nameOverride=custom-operator) + + assert_contains "labels keep chart name (Chart.Name takes precedence)" "app.kubernetes.io/name: jupiterone-integration-operator" "$output" +} + +test_operator_service_account_annotations() { + local output + output=$(helm template test-release "$CHART_DIR" \ + --set 'controllerManager.serviceAccount.annotations.eks\.amazonaws\.com/role-arn=arn:aws:iam::123456789012:role/j1-operator') + + assert_contains "IRSA annotation on operator ServiceAccount" \ + "eks.amazonaws.com/role-arn: arn:aws:iam::123456789012:role/j1-operator" "$output" +} + +test_metrics_tls_names_match() { + local output + output=$(helm template test-release "$CHART_DIR" \ + --set prometheus.enable=true --set certmanager.enable=true --set crd.keep=true) + + assert_contains "ServiceMonitor serverName matches metrics Service" \ + "serverName: jupiterone-integration-operator-metrics-service.default.svc" "$output" + assert_not_contains "No stale controller-manager-metrics-service serverName" \ + "controller-manager-metrics-service.default.svc" "$output" + assert_contains "Certificate dnsNames include metrics Service" \ + "jupiterone-integration-operator-metrics-service.default.svc" "$output" + assert_contains "crd.keep adds resource-policy keep" '"helm.sh/resource-policy": keep' "$output" +} + +# --- Run all tests --- + +run_test "Template references vs values.yaml" test_every_template_value_is_declared +run_test "controllerManager.pod.labels" test_pod_labels +run_test "nameOverride" test_name_override +run_test "controllerManager.serviceAccount.annotations" test_operator_service_account_annotations +run_test "Prometheus + cert-manager TLS names" test_metrics_tls_names_match + +# --- Summary --- + +echo "" +echo "==============================" +echo "Results: $PASSED passed, $FAILED failed" +echo "==============================" +[ "$FAILED" -eq 0 ] || exit 1 diff --git a/charts/jupiterone-integration-operator/values.yaml b/charts/jupiterone-integration-operator/values.yaml index 22a6ad3..56f7396 100644 --- a/charts/jupiterone-integration-operator/values.yaml +++ b/charts/jupiterone-integration-operator/values.yaml @@ -1,3 +1,6 @@ +# Overrides the chart name used in the app.kubernetes.io/name label. +nameOverride: "" + # [MANAGER]: Manager Deployment Configurations controllerManager: replicas: 1 @@ -43,6 +46,9 @@ controllerManager: runAsNonRoot: true seccompProfile: type: RuntimeDefault + # Extra labels on the manager pod. + pod: + labels: {} terminationGracePeriodSeconds: 10 serviceAccountName: jupiterone-integration-operator-controller-manager # Annotations applied to the operator ServiceAccount. @@ -99,6 +105,10 @@ prometheus: certmanager: enable: false +# [CRD]: Keep the cert-manager Certificate on uninstall (helm.sh/resource-policy: keep) +crd: + keep: false + # [NETWORK POLICIES]: To enable NetworkPolicies set true networkPolicy: enable: false diff --git a/charts/jupiterone-integration-runner/Chart.yaml b/charts/jupiterone-integration-runner/Chart.yaml index 98e3ed2..9af30e5 100644 --- a/charts/jupiterone-integration-runner/Chart.yaml +++ b/charts/jupiterone-integration-runner/Chart.yaml @@ -2,5 +2,5 @@ apiVersion: v2 name: jupiterone-integration-runner description: A Helm chart for the JupiterOne Integration Runner type: application -version: 1.0.3 +version: 1.1.0 appVersion: "v1.0.0" diff --git a/charts/jupiterone-integration-runner/README.md b/charts/jupiterone-integration-runner/README.md index 40f07a2..e1eecc6 100644 --- a/charts/jupiterone-integration-runner/README.md +++ b/charts/jupiterone-integration-runner/README.md @@ -1,13 +1,19 @@ # JupiterOne Integration Runner -This chart installs a single instance of the Runner using a Custom Resource. +This chart installs a single `IntegrationRunner` custom resource. The +[JupiterOne Integration Operator](../jupiterone-integration-operator) reconciles +it: it registers the runner with JupiterOne using your API token, then spawns a +Kubernetes Job for every integration instance assigned to the runner. The +chart itself creates no Deployment, Pod or ServiceAccount. ## Prerequisites - Kubernetes 1.16+ - Helm 3+ -- Access to a JupiterOne account with API credentials -- JupiterOne Integration Operator helm chart installed +- The `jupiterone-integration-operator` chart installed in the same cluster +- A JupiterOne account API token with **Collector -- CRUD** and **Shared: + Graph Data -- Read/Write** permissions (Settings > Account API Tokens) +- Your JupiterOne account ID (Settings > Account Management) ## Installation @@ -18,100 +24,148 @@ helm repo add jupiterone https://jupiterone.github.io/helm-charts helm repo update ``` -### 2. Create the Namespace +### 2. Install the Runner -All resources are created in the namespace `jupiterone`. If it does not exist, create it: +Install into the namespace the operator watches (`jupiterone` by default). +Replace `` and ``: ```console -kubectl create namespace jupiterone +helm install integration-runner jupiterone/jupiterone-integration-runner \ + --namespace jupiterone \ + --set accountID= \ + --set apiToken= ``` -### 3. Install the Runner +The chart fails to render if `accountID` is missing or `apiToken` is left at +its placeholder while `createSecret` is true. -Replace ``, and `` with the correct values +### 3. Verify Installation ```console -helm install operator-1 jupiterone/jupiterone-integration-runner \ - --namespace jupiterone \ - --set accountID= \ - --set apiToken= +kubectl get integrationrunner -n jupiterone +# NAME STATE REGISTRATION +# integration-runner running registered ``` -#### Example +Registration takes about 30 seconds. If `STATE` stays `pending`, check the +operator logs: ```console +kubectl logs -n jupiterone deploy/jupiterone-integration-operator-controller-manager +``` + +## Parameters + +| Parameter | Description | Default | +|---|---|---| +| `accountID` | JupiterOne account ID. Required. | `` | +| `jupiterOneEnvironment` | JupiterOne environment the runner connects to (`us`, `eu`, `gov`, ...). | `us` | +| `syncIntervalSeconds` | How often, in seconds, the runner polls JupiterOne for work. | `30` | +| `createSecret` | Create a Kubernetes Secret named `secretAPITokenName` from `apiToken`. Set `false` when the Secret is managed elsewhere or the token comes from AWS Secrets Manager. | `true` | +| `apiToken` | JupiterOne API token. Required when `createSecret` is `true`. | `` | +| `secretAPITokenName` | Name of the Kubernetes Secret holding the token under the `token` key. | `j1token` | +| `apiTokenSource.provider` | Where the runner reads the token: `kubernetes` or `awsSecretsManager`. Empty uses `secretAPITokenName`. | `""` | +| `apiTokenSource.kubernetes.name` | Kubernetes Secret name when `provider` is `kubernetes`. Defaults to `secretAPITokenName`. | `""` | +| `apiTokenSource.awsSecretsManager.secretId` | ARN or name of the AWS Secrets Manager secret. Required when `provider` is `awsSecretsManager`. | `""` | +| `apiTokenSource.awsSecretsManager.region` | Region of the secret. Defaults to the operator's region. | `""` | +| `apiTokenSource.awsSecretsManager.versionStage` | Staging label to read. Defaults to `AWSCURRENT`. | `""` | + +### Existing Kubernetes Secret + +Create the Secret yourself, with the token under the `token` key, and point the +runner at it: + +```console +kubectl create secret generic j1token -n jupiterone --from-literal=token= + helm install integration-runner jupiterone/jupiterone-integration-runner \ --namespace jupiterone \ - --set collectorID=abcd1234 \ - --set accountID=efgh5678 \ - --set authToken=your-token-here + --set accountID= \ + --set createSecret=false \ + --set secretAPITokenName=j1token ``` -### 4. Verify Installation - -Check that the runner is installed +### AWS Secrets Manager + +Store the token as a JSON object, `{ "token": "" }`, and reference +it. The operator resolves the secret, so the **operator's** ServiceAccount +needs an IAM role with `secretsmanager:GetSecretValue` on it -- see +[AWS authentication (IRSA)](https://github.com/JupiterOne/jupiterone-integration-operator#aws-authentication-irsa). + +```yaml +# values.yaml +accountID: +createSecret: false +apiTokenSource: + provider: awsSecretsManager + awsSecretsManager: + secretId: jupiterone/runner-api-token + region: us-east-1 +``` ```console -kubectl get integrationrunner -n jupiterone +helm install integration-runner jupiterone/jupiterone-integration-runner \ + --namespace jupiterone -f values.yaml ``` -## Configuration +## ServiceAccounts and AWS identity + +The runner has no pod, so this chart has no ServiceAccount to annotate. The +identities involved are all configured on the operator chart: -You can customize the installation using Helm values. For example, to set resource limits or configure logging, update your `values.yaml` or pass additional flags to `helm install`. +| Workload | Chart value on `jupiterone-integration-operator` | +|---|---| +| Operator (reads AWS Secrets Manager) | `controllerManager.serviceAccount.annotations` | +| Integration job pods spawned for this runner | `integration.jobServiceAccount` | +| `kubernetes-managed` integration job pods | `integration.serviceAccount.annotations` | -Refer to the [values.yaml](./values.yaml) for all available configuration options. +Integrations that call AWS from the job pod -- for example SBOM for AWS ECR -- +get their IAM role through those values, not through this chart. ## Usage ### Set Default Namespace -To avoid specifying `-n jupiterone` in every command: - ```console kubectl config set-context --current --namespace jupiterone ``` -### List Integration Runners +### Inspect the runner and its jobs ```console kubectl get integrationrunner -``` - -### List Integration Instance Jobs - -```console kubectl get integrationinstancejob -``` - -### List Kubernetes Jobs - -```console kubectl get job ``` -## Updating the Operator +### Multiple runners -To upgrade to a newer version: +Each release is one runner. Install the chart again with a different release +name to add another runner to the same account, or into another namespace +watched by its own operator. + +## Updating the Runner ```console helm repo update -helm upgrade integration-operator jupiterone/jupiterone-integration-operator --namespace jupiterone +helm upgrade integration-runner jupiterone/jupiterone-integration-runner --namespace jupiterone --reuse-values ``` ## Uninstalling -To remove the operator and all related resources: - ```console -helm uninstall integration-operator --namespace jupiterone -kubectl delete namespace jupiterone +helm uninstall integration-runner --namespace jupiterone ``` +The operator deregisters the runner from JupiterOne. Integration instances +assigned to it stop running until reassigned. + ## Troubleshooting -- **Pod not starting:** Check logs with `kubectl logs `. -- **CRDs not found:** Ensure the operator pod is running and healthy. -- **Authentication errors:** Double-check your `collectorID`, `accountID`, and `authToken`. +- **`STATE` stays `pending`:** the API token lacks Collector CRUD permission, or `jupiterOneEnvironment` does not match the account's region. Check the operator logs. +- **`secret not found`:** `createSecret=false` but no Secret named `secretAPITokenName` exists in the release namespace, or it lacks a `token` key. +- **`AWS Secrets Manager provider is not configured`:** the operator has no AWS credentials. Annotate its ServiceAccount for IRSA (operator chart `controllerManager.serviceAccount.annotations`). +- **Integration jobs fail with `Configuration failure`:** the job pod has no AWS identity. Set `integration.jobServiceAccount` on the operator chart. ## Support diff --git a/charts/jupiterone-integration-runner/templates/runner.yaml b/charts/jupiterone-integration-runner/templates/runner.yaml index 663083c..3ee723c 100644 --- a/charts/jupiterone-integration-runner/templates/runner.yaml +++ b/charts/jupiterone-integration-runner/templates/runner.yaml @@ -1,11 +1,35 @@ +{{- if or (not .Values.accountID) (eq (toString .Values.accountID) "") }} +{{- fail "accountID is required (JupiterOne UI > Settings > Account Management)" }} +{{- end }} apiVersion: integrations.jupiterone.io/v1 kind: IntegrationRunner metadata: name: {{ .Release.Name }} namespace: {{ .Release.Namespace }} spec: - accountId: {{ .Values.accountID }} + accountId: {{ .Values.accountID | quote }} secretName: {{ .Release.Name }}-j1internal secretAPITokenName: {{ .Values.secretAPITokenName }} syncIntervalSeconds: {{ .Values.syncIntervalSeconds }} jupiterOneEnvironment: {{ .Values.jupiterOneEnvironment }} + {{- with .Values.apiTokenSource }} + {{- if .provider }} + apiTokenSource: + provider: {{ .provider }} + {{- if eq .provider "kubernetes" }} + kubernetes: + name: {{ .kubernetes.name | default $.Values.secretAPITokenName }} + {{- else if eq .provider "awsSecretsManager" }} + awsSecretsManager: + secretId: {{ required "apiTokenSource.awsSecretsManager.secretId is required when provider is awsSecretsManager" .awsSecretsManager.secretId }} + {{- with .awsSecretsManager.region }} + region: {{ . }} + {{- end }} + {{- with .awsSecretsManager.versionStage }} + versionStage: {{ . }} + {{- end }} + {{- else }} + {{- fail (printf "apiTokenSource.provider must be \"kubernetes\" or \"awsSecretsManager\", got %q" .provider) }} + {{- end }} + {{- end }} + {{- end }} diff --git a/charts/jupiterone-integration-runner/templates/secret.yaml b/charts/jupiterone-integration-runner/templates/secret.yaml index 3f1c72d..fed07c0 100644 --- a/charts/jupiterone-integration-runner/templates/secret.yaml +++ b/charts/jupiterone-integration-runner/templates/secret.yaml @@ -1,4 +1,7 @@ -{{if .Values.createSecret}} +{{- if .Values.createSecret }} +{{- if or (not .Values.apiToken) (eq .Values.apiToken "") }} +{{- fail "apiToken is required when createSecret is true (or set createSecret=false and provide the Secret yourself)" }} +{{- end }} apiVersion: v1 kind: Secret metadata: @@ -7,4 +10,4 @@ metadata: type: Opaque data: token: {{ .Values.apiToken | b64enc }} -{{- end }} \ No newline at end of file +{{- end }} diff --git a/charts/jupiterone-integration-runner/tests/runner-values_test.sh b/charts/jupiterone-integration-runner/tests/runner-values_test.sh new file mode 100755 index 0000000..c8f6b36 --- /dev/null +++ b/charts/jupiterone-integration-runner/tests/runner-values_test.sh @@ -0,0 +1,149 @@ +#!/usr/bin/env bash +set -euo pipefail + +CHART_DIR="helm-charts/charts/jupiterone-integration-runner" +PASSED=0 +FAILED=0 + +if [ ! -d "$CHART_DIR" ]; then + echo "ERROR: Run this script from the repository root" + echo " Expected chart directory: $CHART_DIR" + exit 1 +fi + +# --- Helper functions --- + +assert_contains() { + local description="$1" + local needle="$2" + local haystack="$3" + + if echo "$haystack" | grep -qF "$needle"; then + echo " PASS: $description" + PASSED=$((PASSED + 1)) + else + echo " FAIL: $description" + echo " Expected output to contain: $needle" + FAILED=$((FAILED + 1)) + fi +} + +assert_not_contains() { + local description="$1" + local needle="$2" + local haystack="$3" + + if echo "$haystack" | grep -qF "$needle"; then + echo " FAIL: $description" + echo " Expected output NOT to contain: $needle" + FAILED=$((FAILED + 1)) + else + echo " PASS: $description" + PASSED=$((PASSED + 1)) + fi +} + +assert_render_fails() { + local description="$1" + local needle="$2" + shift 2 + + local output + if output=$(helm template test-release "$CHART_DIR" "$@" 2>&1); then + echo " FAIL: $description" + echo " Expected helm template to fail" + FAILED=$((FAILED + 1)) + elif echo "$output" | grep -qF "$needle"; then + echo " PASS: $description" + PASSED=$((PASSED + 1)) + else + echo " FAIL: $description" + echo " Expected error to contain: $needle" + FAILED=$((FAILED + 1)) + fi +} + +run_test() { + local test_name="$1" + local test_func="$2" + + echo "" + echo "=== Test: $test_name ===" + $test_func +} + +# --- Test cases --- + +test_kubernetes_secret_created() { + local output + output=$(helm template test-release "$CHART_DIR" \ + --set accountID=acct-1 --set apiToken=tok) + + assert_contains "IntegrationRunner rendered" "kind: IntegrationRunner" "$output" + assert_contains "accountId is quoted" 'accountId: "acct-1"' "$output" + assert_contains "Secret created" "kind: Secret" "$output" + assert_contains "Secret named by secretAPITokenName" "name: j1token" "$output" + assert_contains "token base64 encoded" "token: dG9r" "$output" + assert_not_contains "No apiTokenSource without a provider" "apiTokenSource:" "$output" +} + +test_external_secret() { + local output + output=$(helm template test-release "$CHART_DIR" \ + --set accountID=acct-1 --set createSecret=false --set secretAPITokenName=external) + + assert_not_contains "No Secret when createSecret=false" "kind: Secret" "$output" + assert_contains "Runner references external secret" "secretAPITokenName: external" "$output" +} + +test_api_token_source_kubernetes() { + local output + output=$(helm template test-release "$CHART_DIR" \ + --set accountID=acct-1 --set createSecret=false \ + --set apiTokenSource.provider=kubernetes) + + assert_contains "provider kubernetes" "provider: kubernetes" "$output" + assert_contains "kubernetes name defaults to secretAPITokenName" "name: j1token" "$output" +} + +test_api_token_source_asm() { + local output + output=$(helm template test-release "$CHART_DIR" \ + --set accountID=acct-1 --set createSecret=false \ + --set apiTokenSource.provider=awsSecretsManager \ + --set apiTokenSource.awsSecretsManager.secretId=jupiterone/runner \ + --set apiTokenSource.awsSecretsManager.region=us-east-1 \ + --set apiTokenSource.awsSecretsManager.versionStage=AWSCURRENT) + + assert_contains "provider awsSecretsManager" "provider: awsSecretsManager" "$output" + assert_contains "secretId rendered" "secretId: jupiterone/runner" "$output" + assert_contains "region rendered" "region: us-east-1" "$output" + assert_contains "versionStage rendered" "versionStage: AWSCURRENT" "$output" +} + +test_validation() { + assert_render_fails "Placeholder apiToken rejected" "apiToken is required" \ + --set accountID=acct-1 + assert_render_fails "Missing accountID rejected" "accountID is required" \ + --set apiToken=tok + assert_render_fails "ASM provider without secretId rejected" "secretId is required" \ + --set accountID=acct-1 --set createSecret=false --set apiTokenSource.provider=awsSecretsManager + assert_render_fails "Unknown provider rejected" "must be" \ + --set accountID=acct-1 --set createSecret=false --set apiTokenSource.provider=vault +} + +# --- Run all tests --- + +run_test "Kubernetes Secret created (default path)" test_kubernetes_secret_created +run_test "External Secret" test_external_secret +run_test "apiTokenSource kubernetes" test_api_token_source_kubernetes +run_test "apiTokenSource awsSecretsManager" test_api_token_source_asm +run_test "Validation" test_validation + +# --- Summary --- + +echo "" +echo "==============================" +echo "Results: $PASSED passed, $FAILED failed" +echo "==============================" +[ "$FAILED" -eq 0 ] || exit 1 diff --git a/charts/jupiterone-integration-runner/values.yaml b/charts/jupiterone-integration-runner/values.yaml index 6bdaa85..795acb4 100644 --- a/charts/jupiterone-integration-runner/values.yaml +++ b/charts/jupiterone-integration-runner/values.yaml @@ -1,19 +1,48 @@ -# The name of the secret that holds the API token. -# The key in the secret must be 'token' -secretAPITokenName: j1token +# The JupiterOne account ID, found in the JupiterOne UI under Settings > Account Management +accountID: + +# The environment for JupiterOne API +jupiterOneEnvironment: us + +# The interval in seconds for syncing data with JupiterOne +syncIntervalSeconds: 30 -# Set this to false if you are using an external secret +# Create a Kubernetes Secret holding the API token from `apiToken` below. +# Set to false when the Secret is managed elsewhere, or when reading the token +# from AWS Secrets Manager via `apiTokenSource`. createSecret: true # This is an account level API token # This is only required if 'createSecret' is true apiToken: -# The JupiterOne account ID, found in the JupiterOne UI under Settings > Account Management -accountID: - -# The interval in seconds for syncing data with JupiterOne -syncIntervalSeconds: 30 +# The name of the Kubernetes Secret that holds the API token. +# The key in the secret must be 'token'. Created by this chart when +# `createSecret` is true; referenced by the runner otherwise. +secretAPITokenName: j1token -# The environment for JupiterOne API -jupiterOneEnvironment: us +# Where the runner reads its JupiterOne API token from. Leave `provider` empty +# to use the Kubernetes Secret named by `secretAPITokenName`. +# The resolved secret must be a JSON object with a "token" key. +# Example (AWS Secrets Manager; the operator must have IRSA access, see the +# jupiterone-integration-operator chart): +# createSecret: false +# apiTokenSource: +# provider: awsSecretsManager +# awsSecretsManager: +# secretId: jupiterone/runner-api-token +# region: us-east-1 +# versionStage: AWSCURRENT +apiTokenSource: + # "kubernetes" or "awsSecretsManager" + provider: "" + kubernetes: + # Defaults to `secretAPITokenName` when empty + name: "" + awsSecretsManager: + # ARN or name of the secret; required when provider is awsSecretsManager + secretId: "" + # Defaults to the operator's region when empty + region: "" + # Defaults to AWSCURRENT when empty + versionStage: "" From 0e9e13251300687a31b16c9cb6708faf99808eba Mon Sep 17 00:00:00 2001 From: Ryan McAfee Date: Wed, 16 Sep 2026 16:48:08 -0500 Subject: [PATCH 07/11] ci: lint charts and run render tests on pull requests The chart test scripts only ran when someone remembered to run them locally. A Chart Tests job now lints both charts and runs every render test script on pull requests touching either chart, failing the job on any assertion failure. The scripts expect the repo checked out under a helm-charts/ directory, so the job checks out to that path. Co-Authored-By: Ryan McAfee - Bot --- .github/workflows/pr.yml | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index ef126d4..df9e8e0 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -5,6 +5,7 @@ on: paths: - 'charts/jupiterone-integration-operator/**' - 'charts/jupiterone-integration-runner/**' + - '.github/workflows/pr.yml' jobs: verify-operator-crds: @@ -18,3 +19,36 @@ jobs: run: make verify-crds env: GH_TOKEN: ${{ secrets.AUTO_GITHUB_PAT_TOKEN }} + + chart-tests: + name: Chart Tests + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + path: helm-charts + + - name: Set up Helm + uses: azure/setup-helm@v4 + + - name: Lint charts + run: | + helm lint helm-charts/charts/jupiterone-integration-operator + helm lint helm-charts/charts/jupiterone-integration-runner \ + --set accountID=ci --set apiToken=ci + + - name: Run chart render tests + run: | + set -euo pipefail + status=0 + for script in \ + helm-charts/charts/jupiterone-integration-operator/tests/declared-values_test.sh \ + helm-charts/charts/jupiterone-integration-operator/tests/private-registry-values_test.sh \ + helm-charts/charts/jupiterone-integration-operator/tests/irsa-service-account_test.sh \ + helm-charts/charts/jupiterone-integration-runner/tests/runner-values_test.sh; do + echo "::group::$script" + bash "$script" || status=1 + echo "::endgroup::" + done + exit "$status" From 35c0d6289d1128b483b5542770476adc64b7861f Mon Sep 17 00:00:00 2001 From: Ryan McAfee Date: Wed, 16 Sep 2026 16:52:00 -0500 Subject: [PATCH 08/11] fix(charts): make crd.keep apply to operator CRDs; stop SIGPIPE flake in tests crd.keep only annotated the metrics Certificate, but the flag exists to keep the operator CRDs (and the custom resources under them) across helm uninstall. sync-crds now injects the helm.sh/resource-policy: keep block into each synced CRD, so verify-crds keeps passing and the annotation follows every future sync. Test helpers piped the rendered chart into grep -q, which exits on the first match and leaves echo with a broken pipe; under pipefail a matched assertion then reported failure, which is what broke the first CI run. Assertions now read the haystack from a here-string, and needles are passed after -- so a value starting with - is not read as a flag. Co-Authored-By: Ryan McAfee - Bot --- Makefile | 2 +- charts/jupiterone-integration-operator/README.md | 2 +- .../integrations.jupiterone.io_integrationinstancejobs.yaml | 3 +++ .../integrations.jupiterone.io_integrationinstances.yaml | 3 +++ .../crds/integrations.jupiterone.io_integrationrunners.yaml | 3 +++ .../tests/declared-values_test.sh | 4 ++-- .../tests/irsa-service-account_test.sh | 4 ++-- .../tests/private-registry-values_test.sh | 4 ++-- charts/jupiterone-integration-operator/values.yaml | 5 ++++- .../tests/runner-values_test.sh | 6 +++--- 10 files changed, 24 insertions(+), 12 deletions(-) diff --git a/Makefile b/Makefile index 48a6954..22373b0 100644 --- a/Makefile +++ b/Makefile @@ -20,7 +20,7 @@ sync-crds: ## Sync CRDs from the latest jupiterone-integration-operator release tar xzf operator.tar.gz -C _operator_src && \ echo "Syncing CRDs to $(OPERATOR_CRD_DIR)..." && \ for f in _operator_src/*/config/crd/bases/*.yaml; do \ - sed '1{/^---$$/d;}' "$$f" > "$(OPERATOR_CRD_DIR)/$$(basename $$f)"; \ + sed '1{/^---$$/d;}' "$$f" | awk '/^ annotations:$$/ && !done { print; print " {{- if .Values.crd.keep }}"; print " \"helm.sh/resource-policy\": keep"; print " {{- end }}"; done=1; next } { print }' > "$(OPERATOR_CRD_DIR)/$$(basename $$f)"; \ echo " Synced $$(basename $$f)"; \ done && \ rm -rf _operator_src operator.tar.gz && \ diff --git a/charts/jupiterone-integration-operator/README.md b/charts/jupiterone-integration-operator/README.md index 2bafd42..9a4d0c4 100644 --- a/charts/jupiterone-integration-operator/README.md +++ b/charts/jupiterone-integration-operator/README.md @@ -74,7 +74,7 @@ Refer to the [values.yaml](./values.yaml) for all available configuration option | `metrics.enable` | Create the metrics Service. Remove `--metrics-bind-address` from `args` when disabling. | `true` | | `prometheus.enable` | Create a `ServiceMonitor` for the metrics Service. | `false` | | `certmanager.enable` | Issue the metrics serving certificate with cert-manager. | `false` | -| `crd.keep` | Keep the cert-manager Certificate on uninstall (`helm.sh/resource-policy: keep`). | `false` | +| `crd.keep` | Annotate the operator CRDs (and the metrics Certificate) with `helm.sh/resource-policy: keep` so `helm uninstall` leaves them, and every `IntegrationRunner`/`IntegrationInstance`/`IntegrationInstanceJob`, in place. | `false` | | `networkPolicy.enable` | Create a NetworkPolicy allowing metrics scrapes from namespaces labeled `metrics: enabled`. | `false` | | `integration.create` | Create the `kubernetes-managed` integration ServiceAccount, ClusterRole and ClusterRoleBinding. | `true` | | `integration.serviceAccountName` | ServiceAccount used by `kubernetes-managed` integration job pods (`K8S_INTEGRATION_SERVICE_ACCOUNT`). | `jupiterone` | diff --git a/charts/jupiterone-integration-operator/templates/crds/integrations.jupiterone.io_integrationinstancejobs.yaml b/charts/jupiterone-integration-operator/templates/crds/integrations.jupiterone.io_integrationinstancejobs.yaml index 09725a7..95e0e2a 100644 --- a/charts/jupiterone-integration-operator/templates/crds/integrations.jupiterone.io_integrationinstancejobs.yaml +++ b/charts/jupiterone-integration-operator/templates/crds/integrations.jupiterone.io_integrationinstancejobs.yaml @@ -2,6 +2,9 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: + {{- if .Values.crd.keep }} + "helm.sh/resource-policy": keep + {{- end }} controller-gen.kubebuilder.io/version: v0.18.0 name: integrationinstancejobs.integrations.jupiterone.io spec: diff --git a/charts/jupiterone-integration-operator/templates/crds/integrations.jupiterone.io_integrationinstances.yaml b/charts/jupiterone-integration-operator/templates/crds/integrations.jupiterone.io_integrationinstances.yaml index 5b135d2..6b992c1 100644 --- a/charts/jupiterone-integration-operator/templates/crds/integrations.jupiterone.io_integrationinstances.yaml +++ b/charts/jupiterone-integration-operator/templates/crds/integrations.jupiterone.io_integrationinstances.yaml @@ -2,6 +2,9 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: + {{- if .Values.crd.keep }} + "helm.sh/resource-policy": keep + {{- end }} controller-gen.kubebuilder.io/version: v0.18.0 name: integrationinstances.integrations.jupiterone.io spec: diff --git a/charts/jupiterone-integration-operator/templates/crds/integrations.jupiterone.io_integrationrunners.yaml b/charts/jupiterone-integration-operator/templates/crds/integrations.jupiterone.io_integrationrunners.yaml index 1d2bfa1..1af6711 100644 --- a/charts/jupiterone-integration-operator/templates/crds/integrations.jupiterone.io_integrationrunners.yaml +++ b/charts/jupiterone-integration-operator/templates/crds/integrations.jupiterone.io_integrationrunners.yaml @@ -2,6 +2,9 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: + {{- if .Values.crd.keep }} + "helm.sh/resource-policy": keep + {{- end }} controller-gen.kubebuilder.io/version: v0.18.0 name: integrationrunners.integrations.jupiterone.io spec: diff --git a/charts/jupiterone-integration-operator/tests/declared-values_test.sh b/charts/jupiterone-integration-operator/tests/declared-values_test.sh index d48f964..f6a4f75 100755 --- a/charts/jupiterone-integration-operator/tests/declared-values_test.sh +++ b/charts/jupiterone-integration-operator/tests/declared-values_test.sh @@ -18,7 +18,7 @@ assert_contains() { local needle="$2" local haystack="$3" - if echo "$haystack" | grep -qF "$needle"; then + if grep -qF -- "$needle" <<<"$haystack"; then echo " PASS: $description" PASSED=$((PASSED + 1)) else @@ -33,7 +33,7 @@ assert_not_contains() { local needle="$2" local haystack="$3" - if echo "$haystack" | grep -qF "$needle"; then + if grep -qF -- "$needle" <<<"$haystack"; then echo " FAIL: $description" echo " Expected output NOT to contain: $needle" FAILED=$((FAILED + 1)) diff --git a/charts/jupiterone-integration-operator/tests/irsa-service-account_test.sh b/charts/jupiterone-integration-operator/tests/irsa-service-account_test.sh index 915d864..60a8135 100755 --- a/charts/jupiterone-integration-operator/tests/irsa-service-account_test.sh +++ b/charts/jupiterone-integration-operator/tests/irsa-service-account_test.sh @@ -18,7 +18,7 @@ assert_contains() { local needle="$2" local haystack="$3" - if echo "$haystack" | grep -qF "$needle"; then + if grep -qF -- "$needle" <<<"$haystack"; then echo " PASS: $description" PASSED=$((PASSED + 1)) else @@ -33,7 +33,7 @@ assert_not_contains() { local needle="$2" local haystack="$3" - if echo "$haystack" | grep -qF "$needle"; then + if grep -qF -- "$needle" <<<"$haystack"; then echo " FAIL: $description" echo " Expected output NOT to contain: $needle" FAILED=$((FAILED + 1)) diff --git a/charts/jupiterone-integration-operator/tests/private-registry-values_test.sh b/charts/jupiterone-integration-operator/tests/private-registry-values_test.sh index 45e218a..3bf4815 100755 --- a/charts/jupiterone-integration-operator/tests/private-registry-values_test.sh +++ b/charts/jupiterone-integration-operator/tests/private-registry-values_test.sh @@ -18,7 +18,7 @@ assert_contains() { local needle="$2" local haystack="$3" - if echo "$haystack" | grep -qF "$needle"; then + if grep -qF -- "$needle" <<<"$haystack"; then echo " PASS: $description" PASSED=$((PASSED + 1)) else @@ -33,7 +33,7 @@ assert_not_contains() { local needle="$2" local haystack="$3" - if echo "$haystack" | grep -qF "$needle"; then + if grep -qF -- "$needle" <<<"$haystack"; then echo " FAIL: $description" echo " Expected output NOT to contain: $needle" FAILED=$((FAILED + 1)) diff --git a/charts/jupiterone-integration-operator/values.yaml b/charts/jupiterone-integration-operator/values.yaml index 66dac47..f23f76a 100644 --- a/charts/jupiterone-integration-operator/values.yaml +++ b/charts/jupiterone-integration-operator/values.yaml @@ -105,7 +105,10 @@ prometheus: certmanager: enable: false -# [CRD]: Keep the cert-manager Certificate on uninstall (helm.sh/resource-policy: keep) +# [CRD]: Keep the operator CRDs (IntegrationRunner, IntegrationInstance, +# IntegrationInstanceJob) and the metrics Certificate on helm uninstall by +# annotating them helm.sh/resource-policy: keep. Custom resources survive the +# uninstall; delete the CRDs by hand to remove them. crd: keep: false diff --git a/charts/jupiterone-integration-runner/tests/runner-values_test.sh b/charts/jupiterone-integration-runner/tests/runner-values_test.sh index c8f6b36..830c8c3 100755 --- a/charts/jupiterone-integration-runner/tests/runner-values_test.sh +++ b/charts/jupiterone-integration-runner/tests/runner-values_test.sh @@ -18,7 +18,7 @@ assert_contains() { local needle="$2" local haystack="$3" - if echo "$haystack" | grep -qF "$needle"; then + if grep -qF -- "$needle" <<<"$haystack"; then echo " PASS: $description" PASSED=$((PASSED + 1)) else @@ -33,7 +33,7 @@ assert_not_contains() { local needle="$2" local haystack="$3" - if echo "$haystack" | grep -qF "$needle"; then + if grep -qF -- "$needle" <<<"$haystack"; then echo " FAIL: $description" echo " Expected output NOT to contain: $needle" FAILED=$((FAILED + 1)) @@ -53,7 +53,7 @@ assert_render_fails() { echo " FAIL: $description" echo " Expected helm template to fail" FAILED=$((FAILED + 1)) - elif echo "$output" | grep -qF "$needle"; then + elif grep -qF -- "$needle" <<<"$output"; then echo " PASS: $description" PASSED=$((PASSED + 1)) else From f4059f47408ffd95e9d5e7e35e6df54920782ef5 Mon Sep 17 00:00:00 2001 From: Ryan McAfee Date: Wed, 16 Sep 2026 16:53:32 -0500 Subject: [PATCH 09/11] ci: run PR checks on every pull request Chart Tests and Verify Operator CRDs are required status checks on main. A required check that only runs for some paths leaves every other PR unmergeable, so the path filter goes; both jobs take under a minute. Co-Authored-By: Ryan McAfee - Bot --- .github/workflows/pr.yml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index df9e8e0..d53b822 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -2,10 +2,6 @@ name: Pull Request Checks on: pull_request: - paths: - - 'charts/jupiterone-integration-operator/**' - - 'charts/jupiterone-integration-runner/**' - - '.github/workflows/pr.yml' jobs: verify-operator-crds: From b0bd31869a2334205edc6cace88c0575d1db667a Mon Sep 17 00:00:00 2001 From: Samuel Poulton Date: Mon, 21 Sep 2026 16:29:15 -0600 Subject: [PATCH 10/11] chart(operator): bump appVersion to v0.4.0 Point the operator chart at v0.4.0, the release containing jupiterone-integration-operator#53 (INTEGRATION_JOB_SERVICE_ACCOUNT). Without this, integration.jobServiceAccount is a silent no-op: the chart creates + annotates the ServiceAccount and sets the env, but the older operator image ignores it and job pods stay on `default`. Verified: ghcr.io/jupiterone/jupiterone-integration-operator:v0.4.0 is published and contains jobServiceAccount(); end-to-end validated on EKS with SBOM for AWS ECR. Co-Authored-By: Claude Opus 4.8 (1M context) --- charts/jupiterone-integration-operator/Chart.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/charts/jupiterone-integration-operator/Chart.yaml b/charts/jupiterone-integration-operator/Chart.yaml index c0b7ba6..e457f19 100644 --- a/charts/jupiterone-integration-operator/Chart.yaml +++ b/charts/jupiterone-integration-operator/Chart.yaml @@ -3,4 +3,4 @@ name: jupiterone-integration-operator description: JupiterOne Integration Operator for running integrations in Kubernetes type: application version: 1.4.0 -appVersion: "v0.3.1" +appVersion: "v0.4.0" From c6b411c9ef4beca5e471fad2fa491ad28b8c60d1 Mon Sep 17 00:00:00 2001 From: Samuel Poulton Date: Mon, 21 Sep 2026 16:42:15 -0600 Subject: [PATCH 11/11] fix(operator chart): quote pod.labels values and make nameOverride effective - manager.yaml: quote controllerManager.pod.labels values so non-string values (bool/number/version) render as strings; unquoted values produced invalid label maps that the Kubernetes API server rejects (422). - _helpers.tpl: reorder chart.name so nameOverride actually overrides the app.kubernetes.io/name label. The prior helper checked .Chart.Name first (always set), leaving nameOverride permanently inert despite being declared in values.yaml and documented in the README. - declared-values_test.sh: assert the quoted label value, and assert nameOverride takes effect (with default fallback) instead of codifying the dead behavior. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../templates/_helpers.tpl | 12 ++++-------- .../templates/manager/manager.yaml | 2 +- .../tests/declared-values_test.sh | 8 ++++++-- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/charts/jupiterone-integration-operator/templates/_helpers.tpl b/charts/jupiterone-integration-operator/templates/_helpers.tpl index ed85015..4cbccc7 100644 --- a/charts/jupiterone-integration-operator/templates/_helpers.tpl +++ b/charts/jupiterone-integration-operator/templates/_helpers.tpl @@ -1,12 +1,8 @@ {{- define "chart.name" -}} -{{- if .Chart }} - {{- if .Chart.Name }} - {{- .Chart.Name | trunc 63 | trimSuffix "-" }} - {{- else if .Values.nameOverride }} - {{ .Values.nameOverride | trunc 63 | trimSuffix "-" }} - {{- else }} - jupiterone-integration-operator - {{- end }} +{{- if .Values.nameOverride }} + {{- .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- else if and .Chart .Chart.Name }} + {{- .Chart.Name | trunc 63 | trimSuffix "-" }} {{- else }} jupiterone-integration-operator {{- end }} diff --git a/charts/jupiterone-integration-operator/templates/manager/manager.yaml b/charts/jupiterone-integration-operator/templates/manager/manager.yaml index 304a1b0..fcdc214 100644 --- a/charts/jupiterone-integration-operator/templates/manager/manager.yaml +++ b/charts/jupiterone-integration-operator/templates/manager/manager.yaml @@ -21,7 +21,7 @@ spec: control-plane: controller-manager {{- if and .Values.controllerManager.pod .Values.controllerManager.pod.labels }} {{- range $key, $value := .Values.controllerManager.pod.labels }} - {{ $key }}: {{ $value }} + {{ $key }}: {{ $value | quote }} {{- end }} {{- end }} spec: diff --git a/charts/jupiterone-integration-operator/tests/declared-values_test.sh b/charts/jupiterone-integration-operator/tests/declared-values_test.sh index f6a4f75..e18d69c 100755 --- a/charts/jupiterone-integration-operator/tests/declared-values_test.sh +++ b/charts/jupiterone-integration-operator/tests/declared-values_test.sh @@ -78,14 +78,18 @@ test_pod_labels() { local output output=$(helm template test-release "$CHART_DIR" --set controllerManager.pod.labels.team=platform) - assert_contains "pod label rendered" "team: platform" "$output" + assert_contains "pod label rendered (quoted string value)" 'team: "platform"' "$output" } test_name_override() { local output + output=$(helm template test-release "$CHART_DIR" --set nameOverride=custom-operator) + assert_contains "nameOverride overrides the app.kubernetes.io/name label" "app.kubernetes.io/name: custom-operator" "$output" + assert_not_contains "nameOverride replaces the default chart name" "app.kubernetes.io/name: jupiterone-integration-operator" "$output" - assert_contains "labels keep chart name (Chart.Name takes precedence)" "app.kubernetes.io/name: jupiterone-integration-operator" "$output" + output=$(helm template test-release "$CHART_DIR") + assert_contains "default name label falls back to the chart name" "app.kubernetes.io/name: jupiterone-integration-operator" "$output" } test_operator_service_account_annotations() {