4
cyberchef
Justin Klein edited this page 2026-10-14 14:57:00 +00:00
This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

cyberchef

Deploys CyberChef, the "Cyber Swiss Army Knife", as a stateless Deployment behind an Ingress or a Gateway-API HTTPRoute. Everything the app does happens in the visitor's browser; the pod only serves static files. This is the smallest chart in the repo and the shortest path to seeing every convention of the chart style guide in one place.

Image ghcr.io/gchq/cyberchef
Versions not listed on purpose — read the current pair off cyberchef/Chart.yaml, see Chart index
Workloads Deployment (1 replica)
Storage none — one emptyDir on /tmp for nginx
Routing two mutually exclusive modes. Ingress (default): ingress.domain + ingress.clusterIssuer required while ingress.enabled. Gateway API: httpRoute.enabled with httpRoute.parentRefs — see Routing modes
External dependencies none
Secrets none
Install-test ci/cyberchef/ (test values only, no fixtures) — installed in Gateway mode, see CI

Prerequisites

In Ingress mode (the default):

  • cert-manager with the ClusterIssuer named in ingress.clusterIssuer, and DNS for ingress.domain pointing at the ingress controller. With ingress.enabled: false neither is needed.
  • Nothing else. No database, no vault item, no volume — the two ingress values are the whole input of an installation.

In Gateway mode (httpRoute.enabled: true):

  • The Gateway-API CRDs, standard channel. The chart does not and cannot detect them, so without them the install fails with no matches for kind HTTPRoute.
  • A Gateway whose listener accepts routes from this namespace (allowedRoutes), named in httpRoute.parentRefs. The chart renders no Gateway.
  • The certificate on that Gateway's listener. ingress.clusterIssuer is unused and unnecessary here — the chart renders no Certificate in this mode.
  • ingress.domain is still needed (it is the hostname, not an Ingress switch) unless httpRoute.hostnames is set explicitly.

Installation

The registry URL and its credentials live in 1Password (item jlab-registry-access: username, password, charts-url) and never in a repository or a shell history file.

helm repo add jlab "$CHARTS_URL" --username "$REG_USER" --password "$REG_PASS"
helm repo update

helm install cyberchef jlab/cyberchef \
  --namespace cyberchef --create-namespace \
  -f my-values.yaml

Minimal my-values.yaml:

ingress:
  domain: cyberchef.example.com
  clusterIssuer: letsencrypt

The same installation in Gateway mode:

ingress:
  enabled: false
  domain: cyberchef.example.com
httpRoute:
  enabled: true
  parentRefs:
    - name: public
      namespace: gateway
      sectionName: https

helm install prints the chart's NOTES.txt, which renders the same information against the values actually set. Read it — it is the per-release version of this page.

Configurable values

Global

Key Type Default Description
scaleDown bool false Renders every workload with replicas: 0 without uninstalling. Takes precedence over cyberchef.replicas. Service and Ingress stay, so the certificate is not re-issued.
global object unset Helm's conventional cross-chart values. Unread by this chart; kept open in the schema so an umbrella install cannot break on it.

cyberchef

Key Type Default Description
cyberchef.replicas int 1 Replica count; 0 is allowed. Nothing in this chart is stateful, so raising it is safe.
cyberchef.securityContext.pod object non-root 101:101, seccompProfile: RuntimeDefault Passed verbatim into the pod spec. The seccomp profile sits here on purpose: it covers every container including init containers.
cyberchef.securityContext.container object readOnlyRootFilesystem: true, runAsNonRoot: true, all caps dropped, no privilege escalation Passed verbatim into the container spec.
cyberchef.livenessProbe object GET / on port http, period 10s, timeout 5s, 3 failures Port addressed by name, so a rebuilt default needs no further context.
cyberchef.readinessProbe object GET / on port http, period 10s, timeout 5s, 3 failures
cyberchef.startupProbe object GET / on port http, period 5s, timeout 5s, 30 failures (= 150s) Gates liveness and readiness.
cyberchef.env list [] Extra environment variables. Rendered through tpl; value, secretKeyRef and configMapKeyRef forms. Additive, not overriding — see Environment variables. Note it changes nothing about the app.
cyberchef.resources.limitFactor number 3 Memory and ephemeral-storage limits are computed as factor × request.
cyberchef.resources.requests object cpu: 0.1, memory: 512Mi, ephemeralStorage: 10Mi
cyberchef.resources.limits object unset (computed: memory: 1536Mi, ephemeral-storage: 30Mi) An explicit limit always wins. A CPU limit is only rendered when set here.

An erased block (cyberchef.securityContext: with no children, a null probe, a null resources) falls back to the chart's own defaults instead of disappearing — style guide §12.2.

ingress

Key Type Default Description
ingress.enabled bool true false omits the Ingress entirely. Mutually exclusive with httpRoute.enabled — with both true the render aborts naming both values. Nothing outside the routing templates reads clusterIssuer, so it becomes unnecessary with the gate; domain is also read by the HTTPRoute when httpRoute.hostnames is empty.
ingress.domain string null Required while ingress.enabled is true. Ingress host and TLS SAN.
ingress.clusterIssuer string null Required while ingress.enabled is true. cert-manager ClusterIssuer.
ingress.className string nginx Ingress class.
ingress.annotations object {} Merged with the cert-manager annotation the chart always sets. The place to bolt authentication on — see below. No counterpart in Gateway mode: the Gateway API has no annotation channel, so everything set here is gone in that mode.

TLS is stored in tls-<release>-cyberchef-ingress.

httpRoute

The Gateway-API alternative, off by default. Added with the Gateway-API pilot (Gitea #114) — this is the first chart in the repo to carry it, and the form is now binding for all of them (style guide §4).

Key Type Default Description
httpRoute.enabled bool false Renders <release>-cyberchef-httproute instead of the Ingress. Mutually exclusive with ingress.enabled.
httpRoute.parentRefs list [] The Gateway(s) the route attaches to — Gateway API ParentReference entries (name, and optionally namespace, sectionName, port, group, kind), passed through unchanged. Required while httpRoute.enabled is true: the render aborts on an empty list. The schema requires name on every entry and is otherwise left open, because Kubernetes owns that schema.
httpRoute.hostnames list [] Empty means [ingress.domain], so the hostname has a single source of truth. Set it explicitly only to publish under a different or an additional name — which is how a migration off the Ingress runs.

What the chart renders in this mode is only the HTTPRoute: one rule, PathPrefix: /, one backendRefs entry pointing at <release>-cyberchef-service on port 80.

networkPolicy

Inbound 8080 only — the single port this pod declares. Outbound DNS and nothing else: CyberChef's recipes run in the visitor's browser, so the pod has no reason to open an outbound connection at all, which makes this the tightest egress rule in the repository.

Value Type Default Meaning
networkPolicy.enabled bool true Renders the policy. Only a boundary where the CNI enforces one — unenforced, the API server accepts it and nothing is filtered, so everything reads as configured.
networkPolicy.allowedNamespaces list [] Namespaces whose pods may reach the restricted ports, additionally to the release's own. Matched on the kubernetes.io/metadata.name label Kubernetes sets itself — no labelling work, and no typo protection either: a name that does not exist matches nothing and is silently ineffective. Namespace-granular, so an entry opens the port to every pod in it.
networkPolicy.probeSources list [] CIDRs allowed to reach the probed port. Empty means open, not blocked — the rule is then rendered with no from at all. Probes come from the IP of the worker node running the pod, which the chart cannot know; a list that misses it drops every probe and the pod never becomes Ready.
networkPolicy.ingressController object kube-system / app.kubernetes.io/name: rke2-traefik The controller allowed to reach the routed port once probeSources restricts it. The two keys render as one from entry, i.e. an AND — which is why this is not a line in allowedNamespaces, where it would open the port to every pod in kube-system. Default measured on the cluster, not assumed.
networkPolicy.egress.extraPorts list [] Additional outbound TCP ports, to any destination. The escape hatch from the egress cap, and the only alternative to switching the policy off.

Routing modes

The two modes are exclusive by design, and three of the four rules exist because of a failure that reports nothing:

Rule What it prevents
ingress.enabled and httpRoute.enabled cannot both be true — the render aborts naming both value paths two data paths onto one Service under one hostname. That is a conflict at DNS level already: one name, two addresses, and which one a client reaches is luck. A migration runs over a second hostname (httpRoute.hostnames set explicitly while the Ingress keeps ingress.domain), never in parallel.
httpRoute.parentRefs must not be empty — the render aborts a route without a parent attaches to no Gateway. The object is created, it is schema-valid, no controller rejects it, and it routes nothing: no event, no condition, and the only symptom is a hostname that does not answer.
the mode is a value, never auto-detected .Capabilities.APIVersions is unreliable at template time, and an HTTPRoute without the CRDs fails at install time. A chart that guessed would sometimes render an object the cluster rejects and sometimes silently render nothing.
empty hostnames means [ingress.domain] two values that have to agree drift apart.

What moves to the platform, and what is simply gone. In Gateway mode the chart renders no Gateway, no gatewayClassName, no Certificate and — deliberately — no implementation-specific policy object (ClientSettingsPolicy, ClientTrafficPolicy and their equivalents). Only HTTPRoute with parentRefs looks identical on nginx-gateway-fabric, Cilium, Envoy Gateway and Istio, and keeping to that subset is what lets the cluster's implementation be chosen, or replaced, without touching the chart again. Class and certificate therefore belong to the Gateway and its listeners. ingress.annotations has no counterpart at all — relevant for this chart in particular, see the next section.

Checking an attachment. A refused attachment is visible only in the route's own status, which is written by the implementation:

kubectl -n <namespace> get httproute <release>-cyberchef-httproute -o yaml

Accepted: True and ResolvedRefs: True under status.parents[] is a working attachment. An empty status means no controller has looked at the route at all — usually a parentRefs pointing at a Gateway that does not exist, or a listener whose allowedRoutes does not admit this namespace.

Operating notes

Anyone who reaches the URL can use it

CyberChef has no login and this chart adds none. In Ingress mode the escape hatch is ingress.annotations (ingress-nginx external auth or basic auth), or leaving ingress.enabled: false and reaching it with a port-forward:

kubectl -n <namespace> port-forward svc/<release>-cyberchef-service 8080:80

In Gateway mode that escape hatch does not exist in the chart, and this is the sharpest consequence of the routing switch for this particular chart. The Gateway API has no annotation channel, so the external-auth annotations have no counterpart, and the chart renders no implementation-specific policy object on purpose. Protection then has to come from the Gateway itself: a listener that is not publicly reachable, or that implementation's own auth filter/policy, configured next to the Gateway. The NOTES.txt of a Gateway-mode release says so, because "put auth in front of it via ingress.annotations" is advice that silently stops applying.

cyberchef.env reaches nginx, not CyberChef

The served bundle is static JavaScript: recipes and inputs are processed in the visitor's browser and never reach the server. Environment variables land in the nginx process that serves the files and are read by nothing else. The NOTES.txt says so explicitly whenever the list is non-empty, because the setting looks like it should work.

Unprivileged nginx, and why /tmp is a volume

Since app version 11.0.0 the upstream image is built on nginxinc/nginx-unprivileged: nginx runs as its own nginx user (uid 101) and listens on 8080, so no sysctl or capability is needed to run fully non-root. The Service maps 80 → the named container port http. nginx keeps its pid file and temp/cache directories under /tmp, which is mounted as an emptyDir because the root filesystem is read-only.

Scaled to zero

With scaleDown: true (or cyberchef.replicas: 0) the Deployment runs at zero and the URL stops answering, while Service and Ingress remain — the certificate is not re-issued when you scale back up. In Gateway mode the Service and the HTTPRoute remain in the same way, so the route keeps pointing at a Service with no endpoints and the Gateway answers with an error of its own rather than with nothing.

The probed port is open inside the cluster by default

networkPolicy.probeSources is empty out of the box, so the rule for the port the probes address is rendered with no from selector at all — any pod the CNI routes to can reach it. Not from the internet (the port carries no NodePort), but this is not pod-level isolation, and reading the object as if it were is the mistake to avoid.

It is that way on purpose. The kubelet's liveness, readiness and startup probes originate from the IP of the worker node running the pod — an address no chart can know and must not guess. A CIDR that excludes the real node blocks every probe: the pod never becomes Ready, the rollout times out, and the symptom reads as a broken application rather than as a policy. Shipping open is the honest default; narrowing it means listing the worker node IPs yourself.

Note the companion trap: the moment probeSources is non-empty the rule is restricted, so everything else that needs the port has to be in the same rule. The chart adds the release's own namespace, ingressController and allowedNamespaces for exactly that reason.

The policy is only a boundary where the CNI enforces one

The API server accepts a NetworkPolicy whatever the cluster does with it. Where the CNI does not enforce policies, everything looks configured and nothing is filtered — which is worse than no policy, because it invites the assumption that something is being filtered. Verify by connecting from an unrelated namespace rather than by reading it off the manifest.

Measured for this cluster: the CNI is Canal (Flannel for the network, Calico for policy), 6/6, and it does enforce. Measured for CI: k3s brings its own policy controller and enforces too — but it exempts the kubelet's probe traffic, so a blocked probe path does not turn the install-test red. What CI does catch is anything the application itself needs.

Upgrades and backup

There is nothing to back up and nothing to migrate. The pod holds no state, so an app-version bump is a plain image swap and helm rollback carries no data risk. Renovate follows ghcr.io/gchq/cyberchef through this chart's customManagers block with default docker versioning — no suffix, no override — see Renovate.

CI

This chart's install-test runs in Gateway mode, and that is a deliberate trade (Gitea #114). The install-test installs exactly one configuration per chart and the two modes are exclusive, so one of them had to be chosen.

ci/cyberchef/test-values.yaml therefore sets ingress.enabled: false, httpRoute.enabled: true with a parentRefs entry, and keeps ingress.domain as the hostname. No fixtures.yaml exists — there is nothing to fake.

What is given up: the Ingress of this chart is no longer installed anywhere. That costs little. An Ingress is a built-in GA type the API server has validated for a decade, the CI cluster's own ingress controller is traefik while the chart asks for class nginx, so nothing ever routed through it there either — the install only proved "the API server accepted an Ingress", and that keeps being proved by twenty other charts plus helm lint --strict, which renders this chart's defaults (Ingress mode) on every PR.

What is gained: a real HTTPRoute applied against the real Gateway-API CRDs, fetched at a pinned version by ci/gateway-api-crds.sh and applied --server-side. A schema-less stub CRD would have accepted a malformed route and the test would have proved only that something was created. See CI/CD for the CRD fixture and for the httproute-match render test that holds the half the CRDs cannot.

Not proved, and it cannot be: that traffic flows. No Gateway controller runs in the CI cluster, so nothing routes. What is proved is that the object is schema-valid and the pod becomes Ready.

Because it installs fast and depends on nothing, cyberchef is also the chart the README's measured install-test runtimes are taken against.

See also

  • Chart style guide — the rules this chart implements, in their plainest form
  • Versioning — what a bump of 9.0.1+up11.4.0 means
  • CI/CD — the httproute-match render test and the Gateway-API CRD fixture
  • Chart index