This chart deploys the Fortify Elastic Service Fabric (ESF) 26.2 control plane: the Scanner Job Controller (SJC) and the SJC operator. It expects external PostgreSQL and S3-compatible communal storage and exposes the SJC API over HTTPS.
The repository production guide currently documents these tested Kubernetes versions:
kubectl access to the target cluster, with permission
to create secrets, services, deployments, RBAC resources, and CRDs in
the target namespaceoci://registry-1.docker.io/fortifydocker/helm-esf and the
image registry used by sjc, sjc-init, and
sjc-operatorThe examples below use the shell variable ESF_NAMESPACE
for the target Kubernetes namespace. Set this variable once before
running any command and all examples will use it automatically.
The Fortify ESF chart installs the SJC deployment, the SJC operator, the custom metrics APIService, and the ProcessQueue CRD.
sjc.databaseAuth.hostsjc.csAuth.rootUrisjc-svcoci://registry-1.docker.io/fortifydocker/helm-esfReplace <chart-version> with the published chart
version you want to deploy from Docker Hub OCI.
helm show chart oci://registry-1.docker.io/fortifydocker/helm-esf --version <chart-version>If your environment requires authenticated Docker Hub pulls or you
need to avoid anonymous pull limits, run
helm registry login registry-1.docker.io before the
commands below.
Set ESF_NAMESPACE to the Kubernetes namespace where you
want to deploy. Replace default with your target namespace
if you are not deploying to the default namespace. All subsequent
kubectl and helm commands use this
variable.
export ESF_NAMESPACE=defaultCreate the required secrets in the $ESF_NAMESPACE
namespace using the names below. See instructions below for generating
the TLS material and API token, and for the expected secret
contents.
| Purpose | Secret name | Required | Keys | Configurable |
|---|---|---|---|---|
| SJC API TLS | fortify-sjc-secrets |
Always | ca.crt, tls.crt, tls.key |
No. The chart hardcodes this name. |
| SJC API bearer token | esf-sjc-credentials by default |
global.sjcApiAuthMode=k8s-secret (default) |
token |
Yes, via global.sjcApiAuthCred. |
| Database init credentials (user with admin privileges) | esf-db-init-credentials by default |
Always | username, password |
Yes, via sjc.databaseAuth.initAuthSecret. |
| Database app credentials | esf-db-credentials by default |
Optional to precreate | username, password |
Yes, via sjc.databaseAuth.authSecret. |
| S3 credentials | esf-s3-credentials by default |
Always | accessKeyId, secretAccessKey,
sessionToken |
Yes, via sjc.csAuth.authCred. |
| Communal storage certificate | esf-cs-cert by default |
sjc.csAuth.certificateSecret.enabled=true for self
signed certificates |
tls.crt or the configured key |
Yes, via sjc.csAuth.certificateSecret.*. |
The chart always mounts the SJC API certificate from the secret
fortify-sjc-secrets. The certificate must be valid for the
service name sjc-svc in the $ESF_NAMESPACE
namespace.
Generate a local CA and sign an SJC certificate:
openssl genrsa -out ca.key 4096
openssl req -x509 -new -nodes -key ca.key -sha256 -days 365 \
-out ca.crt \
-subj "/C=US/ST=State/L=City/O=Organization/CN=${ESF_NAMESPACE}-local-ca"
openssl genrsa -out sjc.key 2048
cat > sjc-cert.conf <<EOF
[req]
distinguished_name = req_distinguished_name
req_extensions = v3_req
prompt = no
[req_distinguished_name]
C = US
ST = State
L = City
O = Organization
CN = sjc-svc.${ESF_NAMESPACE}.svc.cluster.local
[v3_req]
subjectAltName = @alt_names
[alt_names]
DNS.1 = sjc-svc
DNS.2 = sjc-svc.${ESF_NAMESPACE}
DNS.3 = sjc-svc.${ESF_NAMESPACE}.svc
DNS.4 = sjc-svc.${ESF_NAMESPACE}.svc.cluster.local
DNS.5 = localhost
IP.1 = 127.0.0.1
EOF
openssl req -new -key sjc.key -out sjc.csr -config sjc-cert.conf
openssl x509 -req -in sjc.csr -CA ca.crt -CAkey ca.key -CAcreateserial \
-out sjc.crt -days 365 -sha256 -extensions v3_req -extfile sjc-cert.confCreate the Kubernetes secret:
kubectl create secret generic fortify-sjc-secrets \
--namespace "$ESF_NAMESPACE" \
--from-file=ca.crt=ca.crt \
--from-file=tls.crt=sjc.crt \
--from-file=tls.key=sjc.keyFor production CA-signed certificates, use the same SAN set for the target namespace deployment.
API_TOKEN=$(openssl rand -hex 32)
kubectl create secret generic esf-sjc-credentials \
--namespace "$ESF_NAMESPACE" \
--from-literal=token="$API_TOKEN"If you use a different secret name, set
global.sjcApiAuthCred to match it.
The sjc-init container uses the init secret to create
the SJC schema, apply migrations, and create or rotate the
application-level database secret.
kubectl create secret generic esf-db-init-credentials \
--namespace "$ESF_NAMESPACE" \
--from-literal=username='<db-admin-user>' \
--from-literal=password='<db-admin-password>'The application secret referenced by
sjc.databaseAuth.authSecret defaults to
esf-db-credentials. You can precreate it if required by
your database policy:
kubectl create secret generic esf-db-credentials \
--namespace "$ESF_NAMESPACE" \
--from-literal=username='sjc_user' \
--from-literal=password='<app-db-password>'If the application secret is missing or invalid,
sjc-init creates or rotates it during startup.
This deployment supports only
sjc.csAuth.authMode=k8s-secret. Create the secret
referenced by sjc.csAuth.authCred, and keep
sjc.csAuth.useSSL=true.
kubectl create secret generic esf-s3-credentials \
--namespace "$ESF_NAMESPACE" \
--from-literal=accessKeyId='<access-key-id>' \
--from-literal=secretAccessKey='<secret-access-key>' \
--from-literal=sessionToken=''If the communal storage endpoint uses a self-signed or private CA
certificate, add that certificate to a separate secret and enable
sjc.csAuth.certificateSecret.
kubectl create secret generic esf-cs-cert \
--namespace "$ESF_NAMESPACE" \
--from-file=tls.crt='<path-to-storage-ca-or-server-cert>'Create a minimal override file and update the hostnames, paths, and authentication mode for your environment.
The rootUri value must follow the format
s3://<endpoint>/<bucket>, where the S3 endpoint
host comes first and the bucket name is the first path segment after the
host. The example below uses the AWS regional endpoint for
us-west-2; replace it with your actual S3-compatible
endpoint, bucket name, and path prefix.
global:
sjcApiAuthMode: k8s-secret
sjcApiAuthCred: esf-sjc-credentials
sjc:
databaseAuth:
initAuthSecret: esf-db-init-credentials
authSecret: esf-db-credentials
host: postgres.example.internal
database: sjc
sslMode: require
csAuth:
authMode: k8s-secret
authCred: esf-s3-credentials
rootUri: s3://s3.us-west-2.amazonaws.com/my-esf-bucket
region: us-west-2
useSSL: true
skipTLSVerify: false
# certificateSecret:
# enabled: true
# name: esf-cs-cert
# key: tls.crtSet sjc.csAuth.authCred to the name of the communal
storage credentials secret you created and keep
sjc.csAuth.useSSL=true.
The supported configuration reference is documented in the Values section below.
helm upgrade --install fortify-esf oci://registry-1.docker.io/fortifydocker/helm-esf \
--version <chart-version> \
--namespace "$ESF_NAMESPACE" \
--create-namespace \
--values values-production.yamlCheck the Helm release and workloads:
helm status fortify-esf --namespace "$ESF_NAMESPACE"
kubectl get pods -n "$ESF_NAMESPACE"
kubectl get svc sjc-svc -n "$ESF_NAMESPACE"
kubectl get crd processqueues.esf.fortify.com
kubectl get apiservice v1beta2.custom.metrics.k8s.ioCheck logs for the controller and operator:
kubectl logs deployment/sjc -c sjc -n "$ESF_NAMESPACE" --tail=50
kubectl logs deployment/sjc-operator-controller-manager -n "$ESF_NAMESPACE" --tail=50Verify that the HTTPS endpoint responds:
kubectl port-forward svc/sjc-svc 8443:31031 -n "$ESF_NAMESPACE"
curl -k https://localhost:8443/healthThe /health endpoint does not require a bearer
token.
helm upgrade.Before running any upgrade command, ensure ESF_NAMESPACE
is set to the namespace of your existing release:
ESF_NAMESPACE=default # replace with your actual release namespacehelm get values fortify-esf --namespace "$ESF_NAMESPACE" -o yaml > current-values.yaml
helm show values oci://registry-1.docker.io/fortifydocker/helm-esf --version <chart-version> > new-values.yamlReview new-values.yaml from the OCI chart and merge any
new keys into your environment-specific override file.
helm upgrade fortify-esf oci://registry-1.docker.io/fortifydocker/helm-esf \
--version <chart-version> \
--namespace "$ESF_NAMESPACE" \
--values values-production.yamlhelm status fortify-esf --namespace "$ESF_NAMESPACE"
kubectl get pods -n "$ESF_NAMESPACE"
kubectl get crd processqueues.esf.fortify.com
kubectl logs deployment/sjc -c sjc -n "$ESF_NAMESPACE" --tail=50
kubectl logs deployment/sjc-operator-controller-manager -n "$ESF_NAMESPACE" --tail=50fortify-sjc-secrets is missing or
malformedThe main deployment hardcodes the secret name
fortify-sjc-secrets. Verify the secret exists in the
release namespace and contains ca.crt,
tls.crt, and tls.key.
kubectl get secret fortify-sjc-secrets -n "$ESF_NAMESPACE" -o yamlIf global.sjcApiAuthMode is k8s-secret,
verify that the bearer token secret named by
global.sjcApiAuthCred exists and that your client is
sending Authorization: Bearer <token>.
sjc-init
fails or the app DB secret rotates unexpectedlyThe init container must be able to read the init secret and create or
update the application secret named by
sjc.databaseAuth.authSecret. Confirm that the init
credentials have sufficient database permissions and that the Kubernetes
secret is writable by the pod's service account.
Make sure sjc.csAuth.authMode is
k8s-secret, sjc.csAuth.authCred points to the
secret you created, and that secret contains accessKeyId,
secretAccessKey, and sessionToken.
If the storage endpoint uses a self-signed or private CA certificate,
create the certificate secret and enable
sjc.csAuth.certificateSecret.enabled=true. Do not set
sjc.csAuth.skipTLSVerify=true in production unless you
fully understand the trust implications.
Verify that fortify-esf-operator.crd.enable=true and
fortify-esf-operator.rbac.enable=true, then check the
operator deployment logs.
kubectl get crd processqueues.esf.fortify.com
kubectl logs deployment/sjc-operator-controller-manager -n "$ESF_NAMESPACE" --tail=50The processqueues.esf.fortify.com CRD is cluster-scoped (not
namespaced), and a stale copy from the previous installation in another
namespace would be still present in the cluster if
fortify-esf-operator.crd.keep in the values set to true. It
has Helm annotations pointing to <previous_namespace>/fortify-esf,
so when installing the ESF chart in new namespace, Helm refuses to take
ownership of the CRD — "resource already exists and is not managed by
Helm." You can patch CRD annotations to point to the new release:
kubectl annotate crd processqueues.esf.fortify.com \
meta.helm.sh/release-name=<esf release name> \
meta.helm.sh/release-namespace=<new namespace> \
--overwrite
kubectl label crd processqueues.esf.fortify.com \
app.kubernetes.io/managed-by=Helm --overwrite| Key | Type | Default | Description |
|---|---|---|---|
| customResources | list | [] |
Extra Kubernetes resources to deploy alongside the chart. Each entry is rendered verbatim. |
| fortify-esf-operator.certmanager.enable | bool | false |
|
| fortify-esf-operator.controllerManager.container.args[0] | string | "--leader-elect" |
|
| fortify-esf-operator.controllerManager.container.args[1] | string | "--metrics-bind-address=:8443" |
|
| fortify-esf-operator.controllerManager.container.args[2] | string | "--health-probe-bind-address=:8081" |
|
| fortify-esf-operator.controllerManager.container.image.repository | string | "fortifydocker/esf-sjc-operator" |
|
| fortify-esf-operator.controllerManager.container.imagePullPolicy | string | "Always" |
|
| fortify-esf-operator.controllerManager.container.livenessProbe.httpGet.path | string | "/healthz" |
|
| fortify-esf-operator.controllerManager.container.livenessProbe.httpGet.port | int | 8081 |
|
| fortify-esf-operator.controllerManager.container.livenessProbe.initialDelaySeconds | int | 15 |
|
| fortify-esf-operator.controllerManager.container.livenessProbe.periodSeconds | int | 20 |
|
| fortify-esf-operator.controllerManager.container.readinessProbe.httpGet.path | string | "/readyz" |
|
| fortify-esf-operator.controllerManager.container.readinessProbe.httpGet.port | int | 8081 |
|
| fortify-esf-operator.controllerManager.container.readinessProbe.initialDelaySeconds | int | 5 |
|
| fortify-esf-operator.controllerManager.container.readinessProbe.periodSeconds | int | 10 |
|
| fortify-esf-operator.controllerManager.container.resources.limits.cpu | string | "500m" |
|
| fortify-esf-operator.controllerManager.container.resources.limits.memory | string | "128Mi" |
|
| fortify-esf-operator.controllerManager.container.resources.requests.cpu | string | "10m" |
|
| fortify-esf-operator.controllerManager.container.resources.requests.memory | string | "64Mi" |
|
| fortify-esf-operator.controllerManager.container.securityContext.allowPrivilegeEscalation | bool | false |
|
| fortify-esf-operator.controllerManager.container.securityContext.capabilities.drop[0] | string | "ALL" |
|
| fortify-esf-operator.controllerManager.nodeSelector | object | {} |
Node selector for the operator pods. Applied verbatim. |
| fortify-esf-operator.controllerManager.replicas | int | 1 |
|
| fortify-esf-operator.controllerManager.securityContext.runAsNonRoot | bool | true |
|
| fortify-esf-operator.controllerManager.securityContext.seccompProfile.type | string | "RuntimeDefault" |
|
| fortify-esf-operator.controllerManager.serviceAccountName | string | "sjc-operator-controller-manager" |
|
| fortify-esf-operator.controllerManager.terminationGracePeriodSeconds | int | 10 |
|
| fortify-esf-operator.controllerManager.tolerations | list | [] |
Tolerations for the operator pods. Applied verbatim. |
| fortify-esf-operator.crd.enable | bool | true |
|
| fortify-esf-operator.crd.keep | bool | true |
|
| fortify-esf-operator.metrics.enable | bool | true |
|
| fortify-esf-operator.networkPolicy.enable | bool | false |
|
| fortify-esf-operator.prometheus.enable | bool | false |
|
| fortify-esf-operator.rbac.enable | bool | true |
|
| global.imageTag | string | "26.2.1" |
|
| global.sjcApiAuthCred | string | "esf-sjc-credentials" |
|
| global.sjcApiAuthMode | string | "k8s-secret" |
|
| sjc.additionalEnvironmentVariables | list | [] |
Additional environment variables injected into the SJC container. Each entry is a standard Kubernetes env var object (name, value, valueFrom, etc.). |
| sjc.affinity | object | {} |
Affinity rules for the SJC pods. Applied verbatim. |
| sjc.containerSecurityContext | object | {"allowPrivilegeEscalation":false,"capabilities":{"drop":["ALL"]}} |
Container-level security context for the SJC container |
| sjc.csAuth.authCred | string | "" |
The credentials to use for authenticating to the communal storage. Required if authMode is "password" or "k8s-secret". For "password" the value is "username:password", for "k8s-secret" the value is the secret name storing the "accessKeyId", "secretAccessKey", and "sessionToken" keys. Default is "". |
| sjc.csAuth.authMode | string | "k8s-secret" |
The authentication mode to use for connecting to the communal storage. Supported values are: "k8s-secret", "password". |
| sjc.csAuth.certificateSecret | object | {"enabled":false,"key":"tls.crt","name":"esf-cs-cert"} |
The Kubernetes Secret containing the TLS certificate for the communal storage. Required if communal storage (i.e. minio) is configured to use a self-signed certificate. Default is disabled. |
| sjc.csAuth.certificateSecret.enabled | bool | false |
Whether the sjc.csAuth.certificateSecret is enabled. Default is false. |
| sjc.csAuth.certificateSecret.key | string | "tls.crt" |
The key within the Kubernetes Secret containing the TLS certificate for the communal storage. Default is "tls.crt". |
| sjc.csAuth.certificateSecret.name | string | "esf-cs-cert" |
The name of the Kubernetes Secret containing the TLS certificate for the communal storage. Default is "esf-cs-cert". |
| sjc.csAuth.region | string | "us-west-2" |
The AWS region where the communal storage is located. Required if S3 storage is used. Default is "us-west-2". |
| sjc.csAuth.rootUri | string | "s3://s3.us-west-2.amazonaws.com/993455010077-us-west-2-fod-esf-scan-job-controller" |
The root URI for the communal storage. Default is "s3://my-bucket/path/to/storage". |
| sjc.csAuth.skipTLSVerify | bool | false |
Whether to skip TLS verification when connecting to the communal storage. Default is false. Not recommended for production use. |
| sjc.csAuth.useSSL | bool | true |
Whether to use SSL when connecting to the communal storage. Default is true. |
| sjc.databaseAuth.authSecret | string | "esf-db-credentials" |
The name of the Kubernetes Secret containing the database credentials (username and password) in app mode (defaults to "esf-db-credentials") |
| sjc.databaseAuth.database | string | "sjc" |
The name of the database. Default is "sjc". |
| sjc.databaseAuth.host | string | "postgres-svc" |
The hostname of the database server. Default is "postgres-svc". |
| sjc.databaseAuth.initAuthSecret | string | "esf-db-init-credentials" |
The name of the Kubernetes Secret containing the database credentials (username and password) in admin mode (defaults to "esf-db-init-credentials") |
| sjc.databaseAuth.sslMode | string | "require" |
The SSL mode to use for the database connection. Default is "require". |
| sjc.image.imagePullPolicy | string | "Always" |
The image pull policy to use for the SJC deployment container. Default is "Always". |
| sjc.image.name | string | "fortifydocker/esf-sjc" |
The container image to use for the SJC deployment. |
| sjc.imagePullSecrets | list | [] |
Image pull secrets for the SJC pods. Each entry is an object with a
name key. |
| sjc.initContainerSecurityContext | object | {"allowPrivilegeEscalation":false,"capabilities":{"drop":["ALL"]}} |
Container-level security context for the SJC init container |
| sjc.initImage.imagePullPolicy | string | "Always" |
The image pull policy to use for the SJC init container. Default is "Always". |
| sjc.initImage.name | string | "fortifydocker/esf-sjc-init" |
The container image to use for the SJC init container. |
| sjc.initResources.limits.memory | string | "512Mi" |
The CPU resource limit for the SJC init container. Default is not set. |
| sjc.initResources.requests | object | {"cpu":"50m","memory":"128Mi"} |
The resource requests and limits for the SJC init container. Default is: |
| sjc.initResources.requests.cpu | string | "50m" |
The CPU resource request for the SJC init container. Default is "50m". |
| sjc.initResources.requests.memory | string | "128Mi" |
The memory resource request for the SJC init container. Default is "128Mi". |
| sjc.livenessProbe.failureThreshold | int | 3 |
|
| sjc.livenessProbe.initialDelaySeconds | int | 30 |
|
| sjc.livenessProbe.periodSeconds | int | 10 |
|
| sjc.livenessProbe.successThreshold | int | 1 |
|
| sjc.livenessProbe.timeoutSeconds | int | 5 |
|
| sjc.nodeSelector | object | {} |
Node selector for the SJC pods. Applied verbatim. |
| sjc.podDisruptionBudget | object | { minAvailable: 1 } |
PodDisruptionBudget spec applied verbatim. Set to {} to disable. |
| sjc.podSecurityContext | object | {"runAsNonRoot":true,"seccompProfile":{"type":"RuntimeDefault"}} |
Pod-level security context for the SJC deployment |
| sjc.priorityClassName | string | "" |
PriorityClassName for the SJC pods. |
| sjc.readinessProbe.failureThreshold | int | 3 |
|
| sjc.readinessProbe.initialDelaySeconds | int | 10 |
|
| sjc.readinessProbe.periodSeconds | int | 5 |
|
| sjc.readinessProbe.successThreshold | int | 1 |
|
| sjc.readinessProbe.timeoutSeconds | int | 3 |
|
| sjc.replicaCount | int | 1 |
|
| sjc.resources.limits | object | {"memory":"1Gi"} |
The resource limits for the SJC deployment container. Default is: |
| sjc.resources.limits.memory | string | "1Gi" |
The CPU resource limit for the SJC deployment container. Default is not set. |
| sjc.resources.requests | object | {"cpu":"100m","memory":"256Mi"} |
The resource requests and limits for the SJC deployment container. Default is: |
| sjc.resources.requests.cpu | string | "100m" |
The CPU resource request for the SJC deployment container. Default is "100m". |
| sjc.resources.requests.memory | string | "256Mi" |
The memory resource request for the SJC deployment container. Default is "256Mi". |
| sjc.service.type | object | "ClusterIP" |
Service IP and port configuration for the SJC API |
| sjc.serviceAccount.annotations | object | {} |
Annotations to add to the ServiceAccount (e.g. for IAM role binding). |
| sjc.serviceAccount.automountServiceAccountToken | bool | true |
Whether to automount the service account token into pods. SJC needs the K8s API, so default is true. |
| sjc.serviceAccount.create | bool | true |
Whether to create the ServiceAccount. Default is true. |
| sjc.serviceAccount.name | string | "fortify.esf-service-account" |
The name of the ServiceAccount. Default is "fortify.esf-service-account". |
| sjc.tolerations | list | [] |
Tolerations for the SJC pods. Applied verbatim. |
| sjc.topologySpreadConstraints | list | [] |
Topology spread constraints for the SJC pods. Applied verbatim. |