Merge pull request #102 from jk-cluster/feat/cyberchef-ingress-gate
cyberchef: Ingress-Gate + Null-Absicherung + Schema-Typen (Welle 1, #93/#99/#68)
This commit is contained in:
7 files changed
+195
-19
No files matched your search
@@ -5,7 +5,7 @@ icon: https://raw.githubusercontent.com/gchq/CyberChef/master/src/web/static/ima
|
||||
|
||||
type: application
|
||||
|
||||
version: 7.1.0+up11.4.0
|
||||
version: 8.0.0+up11.4.0
|
||||
appVersion: "11.4.0"
|
||||
|
||||
maintainers:
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
{{- $replicas := include "cyberchef.replicas" (dict "scaleDown" .Values.scaleDown "replicas" .Values.cyberchef.replicas) -}}
|
||||
CyberChef {{ .Chart.AppVersion }} — release {{ .Release.Name }} in namespace {{ .Release.Namespace }}.
|
||||
|
||||
{{- if .Values.ingress.enabled }}
|
||||
|
||||
ACCESS
|
||||
https://{{ .Values.ingress.domain }}
|
||||
|
||||
@@ -11,11 +13,25 @@ ACCESS
|
||||
|
||||
This chart has no secrets and no volumes — the two ingress values above are
|
||||
everything an installation needs.
|
||||
{{- else }}
|
||||
|
||||
ACCESS — NO INGRESS
|
||||
ingress.enabled is false, so this release has no Ingress and no certificate;
|
||||
ingress.domain and ingress.clusterIssuer are not needed. The app is only
|
||||
reachable inside the cluster, as
|
||||
{{ .Release.Name }}-{{ .Chart.Name }}-service:80. To open it locally:
|
||||
|
||||
kubectl -n {{ .Release.Namespace }} port-forward svc/{{ .Release.Name }}-{{ .Chart.Name }}-service 8080:80
|
||||
|
||||
Then browse to http://localhost:8080. Set ingress.enabled=true together with
|
||||
ingress.domain and ingress.clusterIssuer to publish it instead.
|
||||
{{- end }}
|
||||
{{ if eq $replicas "0" }}
|
||||
SCALED TO ZERO
|
||||
{{ if .Values.scaleDown }}scaleDown: true{{ else }}cyberchef.replicas: 0{{ end }} — the deployment runs with replicas: 0, so the URL
|
||||
above answers 503 until you set it back and upgrade again. Service and
|
||||
Ingress stay in place, so the certificate is not re-issued.
|
||||
{{ if .Values.scaleDown }}scaleDown: true{{ else }}cyberchef.replicas: 0{{ end }} — the deployment runs with replicas: 0, so the endpoint
|
||||
above does not answer until you set it back and upgrade again. Service{{ if .Values.ingress.enabled }} and
|
||||
Ingress stay in place, so the certificate is not re-issued.{{ else }} stays in
|
||||
place.{{ end }}
|
||||
{{ end }}
|
||||
ANYONE WHO REACHES THE URL CAN USE IT
|
||||
CyberChef has no login, and this chart adds none. If it must not be open to
|
||||
|
||||
@@ -114,3 +114,156 @@ template-chart is the reference implementation.
|
||||
1
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
|
||||
{{/*
|
||||
Merge a user-supplied values sub-tree onto a dict of chart defaults so that a
|
||||
null / empty override falls back to the defaults instead of erasing them.
|
||||
|
||||
Input: dict "defaults" <dict> "given" <dict|nil> "path" <string, optional
|
||||
values path used in error messages>. Output: YAML of the merged dict (consume
|
||||
it with fromYaml).
|
||||
|
||||
Why this exists (issue #99): Helm merges values maps recursively, but an
|
||||
override key written without children — e.g. a leftover
|
||||
|
||||
securityContext:
|
||||
|
||||
after deleting its last remaining sub-key — is YAML null and replaces the
|
||||
whole default sub-tree. The container then renders with "securityContext:
|
||||
null": no readOnlyRootFilesystem, no dropped capabilities, no
|
||||
allowPrivilegeEscalation: false. The workload starts and reports healthy while
|
||||
being unhardened, so nothing points at the mistake. The same shape erases a
|
||||
probe (it disappears) or the resources block (no requests, no computed
|
||||
limits). This helper defines the opposite semantics: an empty block means
|
||||
"chart defaults apply".
|
||||
|
||||
Rules:
|
||||
- a nil "given" is treated as an empty dict (all defaults apply).
|
||||
- a key whose override value is null keeps the default (nulls never delete).
|
||||
- a key present in both as a map is merged recursively, so a partially
|
||||
filled block only overrides the keys it actually sets.
|
||||
- a scalar override for a key the defaults define as a map is rejected with
|
||||
a message naming the values path, instead of failing later with a template
|
||||
field error.
|
||||
- any other set value wins as given, including an explicit false or 0 —
|
||||
Helm's "default" and sprig's "mergeOverwrite" would both swallow those
|
||||
zero values, hence the explicit kindIs "invalid" nil check.
|
||||
- keys only present in the override are passed through unchanged.
|
||||
|
||||
NOTE: like the helpers above, this is duplicated into every chart's
|
||||
templates/_helpers.tpl with the chart's name as define prefix, because charts
|
||||
in this repo deliberately have no dependencies.
|
||||
*/}}
|
||||
{{- define "cyberchef.defaultedDict" -}}
|
||||
{{- $defaults := .defaults | default dict -}}
|
||||
{{- $given := .given -}}
|
||||
{{- $path := .path | default "value" -}}
|
||||
{{- if kindIs "invalid" $given -}}
|
||||
{{- $given = dict -}}
|
||||
{{- end -}}
|
||||
{{- if not (kindIs "map" $given) -}}
|
||||
{{- fail (printf "%s must be a mapping or null, got %s (%v)" $path (kindOf $given) $given) -}}
|
||||
{{- end -}}
|
||||
{{- $out := deepCopy $defaults -}}
|
||||
{{- range $key, $value := $given -}}
|
||||
{{- if not (kindIs "invalid" $value) -}}
|
||||
{{- $default := index $defaults $key -}}
|
||||
{{- if and (kindIs "map" $value) (kindIs "map" $default) -}}
|
||||
{{- $_ := set $out $key (fromYaml (include "cyberchef.defaultedDict" (dict "defaults" $default "given" $value "path" (printf "%s.%s" $path $key)))) -}}
|
||||
{{- else if kindIs "map" $default -}}
|
||||
{{- fail (printf "%s.%s must be a mapping or null, got %s (%v)" $path $key (kindOf $value) $value) -}}
|
||||
{{- else -}}
|
||||
{{- $_ := set $out $key $value -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
{{- toYaml $out -}}
|
||||
{{- end -}}
|
||||
|
||||
{{/*
|
||||
Pick one sub-block out of a values block that may itself be null.
|
||||
|
||||
Input: dict "block" <dict|nil> "key" <string> "path" <string>.
|
||||
Returns YAML of dict "value" <the sub-block, may be nil>, after rejecting a
|
||||
non-mapping block with a message that names the values path. Needed because
|
||||
".Values.cyberchef.securityContext" can itself be erased.
|
||||
*/}}
|
||||
{{- define "cyberchef.subBlock" -}}
|
||||
{{- $block := .block -}}
|
||||
{{- if kindIs "invalid" $block -}}
|
||||
{{- $block = dict -}}
|
||||
{{- end -}}
|
||||
{{- if not (kindIs "map" $block) -}}
|
||||
{{- fail (printf "%s must be a mapping or null, got %s (%v)" .path (kindOf $block) $block) -}}
|
||||
{{- end -}}
|
||||
{{- toYaml (dict "value" (index $block .key)) -}}
|
||||
{{- end -}}
|
||||
|
||||
{{/*
|
||||
The effective security contexts, probes and resources (issue #99).
|
||||
|
||||
The defaults below MUST stay in sync with the corresponding blocks in
|
||||
values.yaml, which stays the documented, user-facing place for them; this copy
|
||||
is only the fallback for the case where an override has erased the block.
|
||||
|
||||
The probes address the container port by its name ("http"), so a rebuilt
|
||||
default probe needs no further context.
|
||||
*/}}
|
||||
{{- define "cyberchef.podSecurityContext" -}}
|
||||
{{- $given := (fromYaml (include "cyberchef.subBlock" (dict "block" . "key" "pod" "path" "cyberchef.securityContext"))).value -}}
|
||||
{{- $defaults := dict
|
||||
"runAsNonRoot" true
|
||||
"runAsUser" 101
|
||||
"runAsGroup" 101 -}}
|
||||
{{- include "cyberchef.defaultedDict" (dict "defaults" $defaults "given" $given "path" "cyberchef.securityContext.pod") -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- define "cyberchef.containerSecurityContext" -}}
|
||||
{{- $given := (fromYaml (include "cyberchef.subBlock" (dict "block" . "key" "container" "path" "cyberchef.securityContext"))).value -}}
|
||||
{{- $defaults := dict
|
||||
"allowPrivilegeEscalation" false
|
||||
"readOnlyRootFilesystem" true
|
||||
"runAsNonRoot" true
|
||||
"capabilities" (dict "drop" (list "ALL")) -}}
|
||||
{{- include "cyberchef.defaultedDict" (dict "defaults" $defaults "given" $given "path" "cyberchef.securityContext.container") -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- define "cyberchef.livenessProbe" -}}
|
||||
{{- $defaults := dict
|
||||
"httpGet" (dict "path" "/" "port" "http")
|
||||
"periodSeconds" 10
|
||||
"timeoutSeconds" 5
|
||||
"failureThreshold" 3 -}}
|
||||
{{- include "cyberchef.defaultedDict" (dict "defaults" $defaults "given" . "path" "cyberchef.livenessProbe") -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- define "cyberchef.readinessProbe" -}}
|
||||
{{- $defaults := dict
|
||||
"httpGet" (dict "path" "/" "port" "http")
|
||||
"periodSeconds" 10
|
||||
"timeoutSeconds" 5
|
||||
"failureThreshold" 3 -}}
|
||||
{{- include "cyberchef.defaultedDict" (dict "defaults" $defaults "given" . "path" "cyberchef.readinessProbe") -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- define "cyberchef.startupProbe" -}}
|
||||
{{- $defaults := dict
|
||||
"httpGet" (dict "path" "/" "port" "http")
|
||||
"periodSeconds" 5
|
||||
"timeoutSeconds" 5
|
||||
"failureThreshold" 30 -}}
|
||||
{{- include "cyberchef.defaultedDict" (dict "defaults" $defaults "given" . "path" "cyberchef.startupProbe") -}}
|
||||
{{- end -}}
|
||||
|
||||
{{/*
|
||||
The effective resources block, fed into the resources helper above so that an
|
||||
erased "resources:" still yields the chart's requests and computed limits
|
||||
instead of a container without any resource accounting.
|
||||
*/}}
|
||||
{{- define "cyberchef.effectiveResources" -}}
|
||||
{{- $defaults := dict
|
||||
"limitFactor" 3
|
||||
"requests" (dict "cpu" 0.1 "memory" "512Mi" "ephemeralStorage" "10Mi") -}}
|
||||
{{- $merged := fromYaml (include "cyberchef.defaultedDict" (dict "defaults" $defaults "given" . "path" "cyberchef.resources")) -}}
|
||||
{{- include "cyberchef.resources" $merged -}}
|
||||
{{- end -}}
|
||||
@@ -17,7 +17,7 @@ spec:
|
||||
spec:
|
||||
automountServiceAccountToken: false
|
||||
securityContext:
|
||||
{{- toYaml .Values.cyberchef.securityContext.pod | nindent 8 }}
|
||||
{{- include "cyberchef.podSecurityContext" .Values.cyberchef.securityContext | nindent 8 }}
|
||||
containers:
|
||||
- name: {{ .Release.Name }}-{{ .Chart.Name }}-deployment
|
||||
image: "ghcr.io/gchq/cyberchef:{{ .Chart.AppVersion }}"
|
||||
@@ -46,7 +46,7 @@ spec:
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
securityContext:
|
||||
{{- toYaml .Values.cyberchef.securityContext.container | nindent 12 }}
|
||||
{{- include "cyberchef.containerSecurityContext" .Values.cyberchef.securityContext | nindent 12 }}
|
||||
volumeMounts:
|
||||
# nginx-unprivileged keeps its pid file and temp/cache dirs under
|
||||
# /tmp, which needs to be writable with a read-only root filesystem.
|
||||
@@ -57,13 +57,13 @@ spec:
|
||||
protocol: TCP
|
||||
name: http
|
||||
livenessProbe:
|
||||
{{- toYaml .Values.cyberchef.livenessProbe | nindent 12 }}
|
||||
{{- include "cyberchef.livenessProbe" .Values.cyberchef.livenessProbe | nindent 12 }}
|
||||
readinessProbe:
|
||||
{{- toYaml .Values.cyberchef.readinessProbe | nindent 12 }}
|
||||
{{- include "cyberchef.readinessProbe" .Values.cyberchef.readinessProbe | nindent 12 }}
|
||||
startupProbe:
|
||||
{{- toYaml .Values.cyberchef.startupProbe | nindent 12 }}
|
||||
{{- include "cyberchef.startupProbe" .Values.cyberchef.startupProbe | nindent 12 }}
|
||||
resources:
|
||||
{{- include "cyberchef.resources" .Values.cyberchef.resources | nindent 12 }}
|
||||
{{- include "cyberchef.effectiveResources" .Values.cyberchef.resources | nindent 12 }}
|
||||
volumes:
|
||||
- name: tmp
|
||||
emptyDir: {}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
{{- if .Values.ingress.enabled }}
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
@@ -24,3 +25,4 @@ spec:
|
||||
- hosts:
|
||||
- {{ required "A Domain/Host must be provided" .Values.ingress.domain }}
|
||||
secretName: tls-{{ .Release.Name }}-{{ .Chart.Name }}-ingress
|
||||
{{- end }}
|
||||
@@ -65,7 +65,8 @@
|
||||
},
|
||||
"definitions": {
|
||||
"resources": {
|
||||
"type": "object",
|
||||
"description": "May be null / empty: the chart then falls back to its default requests and computed limits (issue #99) instead of rendering a container without resource accounting.",
|
||||
"type": ["object", "null"],
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"limitFactor": {
|
||||
@@ -81,7 +82,7 @@
|
||||
}
|
||||
},
|
||||
"resourceList": {
|
||||
"type": "object",
|
||||
"type": ["object", "null"],
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"cpu": {
|
||||
@@ -140,22 +141,23 @@
|
||||
}
|
||||
},
|
||||
"securityContext": {
|
||||
"type": "object",
|
||||
"description": "May be null / empty at any level: the chart then falls back to its hardening defaults (issue #99) instead of rendering an unhardened workload.",
|
||||
"type": ["object", "null"],
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"pod": {
|
||||
"description": "Rendered verbatim as the pod's securityContext; kept open because the chart does not interpret it.",
|
||||
"type": "object"
|
||||
"description": "Merged onto the chart's pod hardening defaults and rendered as the pod's securityContext; kept open because the chart does not interpret the individual fields.",
|
||||
"type": ["object", "null"]
|
||||
},
|
||||
"container": {
|
||||
"description": "Rendered verbatim as the container's securityContext; kept open because the chart does not interpret it.",
|
||||
"type": "object"
|
||||
"description": "Merged onto the chart's container hardening defaults and rendered as the container's securityContext; kept open because the chart does not interpret the individual fields.",
|
||||
"type": ["object", "null"]
|
||||
}
|
||||
}
|
||||
},
|
||||
"probe": {
|
||||
"description": "Rendered verbatim as a container probe; kept open because the chart does not interpret it.",
|
||||
"type": "object"
|
||||
"description": "Merged onto the chart's probe defaults and rendered as a container probe; kept open because the chart does not interpret the individual fields. May be null / empty, which means the defaults apply (issue #99) rather than the probe disappearing.",
|
||||
"type": ["object", "null"]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -59,6 +59,9 @@ cyberchef:
|
||||
ephemeralStorage: 10Mi
|
||||
|
||||
ingress:
|
||||
# Set to false to omit the Ingress entirely (the required domain and
|
||||
# clusterIssuer values are then not needed either, because they are only
|
||||
# read inside the Ingress template).
|
||||
enabled: true
|
||||
clusterIssuer: null
|
||||
domain: null
|
||||
|
||||
Reference in new issue
Block a user