Skip to content

Kubernetes operator: protect workloads by annotation

The Shield360 operator applies zero-code setup to Kubernetes workloads. It works like the OpenTelemetry Operator’s auto-instrumentation.

When a pod is created, a mutating admission webhook checks whether the pod or its namespace opted in. If it did, the webhook:

  1. adds an init container that copies the SDK into a shared volume,
  2. mounts that volume read-only into the app container, and
  3. sets the language’s start-up variables (NODE_OPTIONS, PYTHONPATH, DOTNET_STARTUP_HOOKS or JAVA_TOOL_OPTIONS).

Your images don’t change. The operator runs unprivileged and is separate from the node-level Shield360 controller.

The operator, its SDK init images and its Helm chart are published on GitHub Container Registry. The chart’s default images are the published ones for its version, so the install needs no image settings.

ArtifactName
Helm chartoci://ghcr.io/thinkfleetai/charts/shield360-operator
Operatorghcr.io/thinkfleetai/shield360-operator
SDK init imagesghcr.io/thinkfleetai/shield360-autoinstrumentation-nodejs, -python, -dotnet, -java
System logs agentghcr.io/thinkfleetai/shield360-system-logs-agent

Images are multi-arch (linux/amd64, linux/arm64) and signed with cosign. The published Python init image is built for Python 3.12 on glibc. If your apps run another Python version or an Alpine (musl) image, ask support@shield360.ai for a matching init image, and set it with sdkImages.python or the instrumentation’s spec.python.image.

Install the chart
Terminal window
helm install shield360-operator oci://ghcr.io/thinkfleetai/charts/shield360-operator \
--version 0.3.0 \
--namespace shield360-system --create-namespace

To see the published versions, run helm show chart oci://ghcr.io/thinkfleetai/charts/shield360-operator. The webhook needs TLS. The chart generates a certificate by default. Set certManager.enabled=true to use cert-manager instead.

If your cluster pulls through a mirror registry, set image.repository and sdkImageDefaults.repository (for example mirror.example.com/thinkfleetai/shield360-autoinstrumentation; the chart adds -<language>:<version>), or a full reference per language in sdkImages.<language>.

Add the API key and an instrumentation

In each app namespace, create a Secret with your environment’s API key (Settings → Developer → API keys) under the key api-key, then a Shield360Instrumentation:

apiVersion: shield360.ai/v1alpha1
kind: Shield360Instrumentation
metadata:
name: default
namespace: orders
spec:
endpoint: https://app.shield360.ai
apiKey:
secretName: shield360
key: api-key
environment: production
rasp:
mode: monitor
configPublicKey: '<JWK from the app’s Runtime protection settings>'
iast:
enabled: true
sampleRate: "0.1"

The API key is always read from the Secret. The resource has no field that can hold it.

Opt workloads in

Annotate the pod template, or the whole namespace:

spec:
template:
metadata:
annotations:
shield360.ai/inject-nodejs: "true"

Existing pods aren’t changed. Restart them (kubectl rollout restart) to apply it.

The chart can also create the Shield360Instrumentation and opt every pod in a namespace in, without any annotations. Create the API key Secret in each namespace first, then install:

Terminal window
kubectl -n orders create secret generic shield360 --from-literal=api-key=<key>
helm install shield360 oci://ghcr.io/thinkfleetai/charts/shield360-operator \
--version 0.3.0 \
--namespace shield360-system --create-namespace \
--set shield360.endpoint=https://app.shield360.ai \
--set apiKey.existingSecret=shield360 \
--set injectNamespaces.orders=nodejs
ValueMeaning
shield360.endpointYour Shield360 URL
apiKey.existingSecret, apiKey.keyThe Secret, in each listed namespace, that holds the API key (key api-key by default)
injectNamespaces.<namespace>Comma-separated languages: nodejs, python, dotnet, java, or sdk
shield360.environmentOptional deployment.environment, such as production
shield360.raspModeOptional runtime protection default: off, monitor or block

The chart never takes the API key itself, so the key isn’t in rendered manifests or in the Helm release. A namespace or pod annotation still wins over injectNamespaces, so one pod opts out with shield360.ai/inject-nodejs: "false". Restart the workloads (kubectl -n orders rollout restart deployment) to instrument running pods.

To send the cluster’s logs too, add --set systemLogs.enabled=true (see System logs). The agent image defaults to the published one. It reuses shield360.endpoint and apiKey.existingSecret, with the Secret in the release namespace.

AnnotationValue
shield360.ai/inject-nodejs, inject-python, inject-dotnet, inject-java"true" (the namespace’s only instrumentation), the instrumentation’s name, or "false"
shield360.ai/inject-sdkAdds only the configuration, for apps that already ship the SDK
shield360.ai/container-namesWhich containers to change, when a pod has several

Annotations work on a pod or a namespace, and the pod’s annotation wins, which is how one pod opts out of a namespace-wide setting. The instrumentation must be in the pod’s namespace.