feat(cyberchef): Gateway-API as an alternative to the Ingress (pilot)
Validate Helm Charts / Detect changed charts (pull_request) Successful in 4s
Validate Helm Charts / Resource quantities (pull_request) Successful in 14s
Validate Helm Charts / HTTPRoute match (pull_request) Successful in 13s
Validate Helm Charts / Build cyberchef (pull_request) Successful in 6s
Validate Helm Charts / Trivy config cyberchef (pull_request) Successful in 18s
Validate Helm Charts / Build template-chart (pull_request) Successful in 7s
Validate Helm Charts / Trivy config template-chart (pull_request) Successful in 17s
Install-test / Install-test cyberchef (pull_request) Successful in 1m0s
Validate Helm Charts / Install-test (pull_request) Successful in 1m0s
Validate Helm Charts / Selector match (pull_request) Successful in 12s
Validate Helm Charts / Detect changed charts (pull_request) Successful in 4s
Validate Helm Charts / Resource quantities (pull_request) Successful in 14s
Validate Helm Charts / HTTPRoute match (pull_request) Successful in 13s
Validate Helm Charts / Build cyberchef (pull_request) Successful in 6s
Validate Helm Charts / Trivy config cyberchef (pull_request) Successful in 18s
Validate Helm Charts / Build template-chart (pull_request) Successful in 7s
Validate Helm Charts / Trivy config template-chart (pull_request) Successful in 17s
Install-test / Install-test cyberchef (pull_request) Successful in 1m0s
Validate Helm Charts / Install-test (pull_request) Successful in 1m0s
Validate Helm Charts / Selector match (pull_request) Successful in 12s
The Gateway-API pilot of Gitea #114: one chart, the convention, the CI that holds it. The remaining 20 charts follow in their own PRs. The form, additive and off by default: httpRoute: enabled: false # counterpart to ingress.enabled parentRefs: [] # the Gateway - a platform object hostnames: [] # empty = [ingress.domain] Only HTTPRoute with parentRefs is rendered: no Gateway, no gatewayClassName, no Certificate, and no implementation-specific policy object. That subset is identical on nginx-gateway-fabric, Cilium, Envoy Gateway and Istio, which is what lets the implementation be chosen later without touching the charts again. Class and certificate move to the platform. Four guards, all against silent failures: - mutually exclusive with ingress.enabled, with a render abort naming BOTH value paths. A migration runs over a second hostname, not in parallel. - parentRefs mandatory in Gateway mode, render abort on the empty list: a route without a parent is created, is schema-valid, and routes nothing. - hostnames empty means [ingress.domain], so the hostname has one source. - the mode is a VALUE, not auto-detection: .Capabilities.APIVersions is unreliable at template time and a missing CRD fails at install time. ingress.clusterIssuer was already only read inside the Ingress template, so the "conditionally required" half of #114 needed no change in this chart - checked rather than assumed, and it has to be re-checked per chart in the sweep. CI, both halves, because neither covers the other: - the install-test installs cyberchef in GATEWAY mode against the REAL Gateway-API CRDs (standard channel v1.6.3, pinned and Renovate-tracked, applied --server-side). A schema-less stub would accept a malformed route and prove only that something was created. The Ingress path of this one chart is no longer installed; it is a built-in type that never routed in k3d anyway, and helm lint renders this chart's defaults - Ingress mode - on every PR. - ci/run-httproute-match-test.sh, over every chart on every PR: every backendRefs entry must name a Service, on a port, that the SAME render creates, and parentRefs must not be empty. The CRDs accept both of those mistakes, so the schema cannot catch them, and the only report would be a route status no controller writes in CI. What stays unprovable, and is written down instead of implied: that traffic flows. No Gateway controller runs in k3d. Minor: additive with unchanged defaults, proven by an empty helm template diff against origin/main for the default values. Part of #114
This commit is contained in:
18 files changed
+1225
-37
No files matched your search
@@ -5,7 +5,7 @@ icon: https://raw.githubusercontent.com/gchq/CyberChef/master/src/web/static/ima
|
||||
|
||||
type: application
|
||||
|
||||
version: 9.0.2+up11.4.0
|
||||
version: 9.1.0+up11.4.0
|
||||
appVersion: "11.4.0"
|
||||
|
||||
maintainers:
|
||||
|
||||
@@ -1,9 +1,14 @@
|
||||
{{- $replicas := include "cyberchef.replicas" (dict "scaleDown" .Values.scaleDown "replicas" .Values.cyberchef.replicas) -}}
|
||||
{{- $httpRoute := .Values.httpRoute | default dict -}}
|
||||
{{- $hostnames := $httpRoute.hostnames | default list -}}
|
||||
{{- if not $hostnames -}}
|
||||
{{- $hostnames = list .Values.ingress.domain -}}
|
||||
{{- end -}}
|
||||
CyberChef {{ .Chart.AppVersion }} — release {{ .Release.Name }} in namespace {{ .Release.Namespace }}.
|
||||
|
||||
{{- if .Values.ingress.enabled }}
|
||||
|
||||
ACCESS
|
||||
ACCESS — INGRESS MODE (ingress.enabled: true, httpRoute.enabled: false)
|
||||
https://{{ .Values.ingress.domain }}
|
||||
|
||||
The certificate is requested by cert-manager through the "{{ .Values.ingress.clusterIssuer }}"
|
||||
@@ -13,31 +18,83 @@ ACCESS
|
||||
|
||||
This chart has no secrets and no volumes — the two ingress values above are
|
||||
everything an installation needs.
|
||||
{{- else if $httpRoute.enabled }}
|
||||
|
||||
ACCESS — GATEWAY MODE (httpRoute.enabled: true, ingress.enabled: false)
|
||||
{{- range $hostnames }}
|
||||
https://{{ . }}
|
||||
{{- end }}
|
||||
|
||||
This release renders an HTTPRoute ({{ .Release.Name }}-{{ .Chart.Name }}-httproute) and NOTHING else
|
||||
of the routing path — no Gateway, no class, no Certificate. Three things
|
||||
therefore belong to the cluster and not to this chart, and if any of them is
|
||||
missing the hostname above simply does not answer:
|
||||
|
||||
1. The Gateway-API CRDs. Without them this install would have failed, so
|
||||
they are present — nothing to check.
|
||||
2. The Gateway named in httpRoute.parentRefs
|
||||
{{- range $httpRoute.parentRefs }}
|
||||
{{ .name }}{{ if .namespace }} in namespace {{ .namespace }}{{ end }}{{ if .sectionName }}, listener {{ .sectionName }}{{ end }}
|
||||
{{- end }}
|
||||
must exist AND its listener must allow routes from this namespace
|
||||
({{ .Release.Namespace }}) in allowedRoutes. A rejected attachment is
|
||||
visible only in the route's status:
|
||||
|
||||
kubectl -n {{ .Release.Namespace }} get httproute {{ .Release.Name }}-{{ .Chart.Name }}-httproute -o yaml
|
||||
|
||||
Read status.parents[].conditions — "Accepted: True" and
|
||||
"ResolvedRefs: True" is what a working attachment looks like. An empty
|
||||
status means no controller has looked at the route at all.
|
||||
3. The TLS certificate for the hostname, on the Gateway's listener.
|
||||
ingress.clusterIssuer is NOT used in this mode and is not required —
|
||||
the chart renders no Certificate here, so there is no
|
||||
tls-{{ .Release.Name }}-{{ .Chart.Name }}-ingress secret and nothing for cert-manager to do
|
||||
on behalf of this release.
|
||||
|
||||
ingress.annotations is also inert in this mode: the Gateway API has no
|
||||
annotation channel for controller behaviour. See the note below on who can
|
||||
use this instance.
|
||||
{{- 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:
|
||||
ACCESS — NEITHER INGRESS NOR HTTPROUTE
|
||||
ingress.enabled and httpRoute.enabled are both false, so this release has no
|
||||
Ingress, no HTTPRoute 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.
|
||||
Then browse to http://localhost:8080. To publish it instead, set either
|
||||
ingress.enabled=true with ingress.domain and ingress.clusterIssuer, or
|
||||
httpRoute.enabled=true with ingress.domain and httpRoute.parentRefs.
|
||||
{{- 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 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
|
||||
Ingress stay in place, so the certificate is not re-issued.{{ else if $httpRoute.enabled }} and
|
||||
HTTPRoute stay in place, so the route keeps pointing at a Service with no
|
||||
endpoints — the Gateway answers with an error of its own rather than with
|
||||
nothing.{{ else }} stays in
|
||||
place.{{ end }}
|
||||
{{ end }}
|
||||
ANYONE WHO REACHES THE URL CAN USE IT
|
||||
{{- if $httpRoute.enabled }}
|
||||
CyberChef has no login, and this chart adds none. In GATEWAY MODE there is no
|
||||
place in this chart to put one either: the Gateway API has no annotation
|
||||
channel, so the ingress-nginx external-auth or basic-auth annotations that
|
||||
cover this in Ingress mode have NO counterpart here, and the chart renders no
|
||||
implementation-specific policy object on purpose (it would bind the chart to
|
||||
one Gateway implementation). Protection 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 and not here.
|
||||
{{- else }}
|
||||
CyberChef has no login, and this chart adds none. If it must not be open to
|
||||
everyone, put authentication in front of it via ingress.annotations — the
|
||||
chart merges them into the Ingress, e.g. ingress-nginx external auth or
|
||||
basic auth annotations.
|
||||
basic auth annotations. Note that this escape hatch exists in Ingress mode
|
||||
only: httpRoute.enabled has no annotation channel at all.
|
||||
{{- end }}
|
||||
|
||||
NOTHING TO BACK UP, NOTHING TO MIGRATE
|
||||
CyberChef is a static web app: recipes and inputs are processed in the
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
{{/*
|
||||
The Gateway-API alternative to cyberchef.ingress.yaml (Gitea #114).
|
||||
|
||||
WHAT THIS RENDERS, AND WHAT IT DELIBERATELY DOES NOT
|
||||
|
||||
Only an HTTPRoute with parentRefs. No Gateway, no gatewayClassName, no
|
||||
Certificate, and no implementation-specific policy object (nginx-gateway-
|
||||
fabric's ClientSettingsPolicy, Envoy Gateway's ClientTrafficPolicy or
|
||||
whatever the next one calls it). That subset is the part which looks IDENTICAL
|
||||
on nginx-gateway-fabric, Cilium, Envoy Gateway and Istio - and keeping to it is
|
||||
what lets the cluster's implementation be chosen, or changed, without touching
|
||||
this chart again. A policy object here would bind the chart to one
|
||||
implementation and to a CRD whose presence it cannot check.
|
||||
|
||||
So the class and the certificate move from this chart to the PLATFORM: they sit
|
||||
on the Gateway and its listeners, which belong in cluster-config, not here.
|
||||
ingress.clusterIssuer is therefore meaningless in this mode - the chart renders
|
||||
no Certificate at all - and it is not required either, because both reads of it
|
||||
live inside cyberchef.ingress.yaml, behind ingress.enabled.
|
||||
|
||||
WHY A VALUE AND NOT AUTO-DETECTION
|
||||
|
||||
.Capabilities.APIVersions is unreliable at template time (it answers from the
|
||||
cluster helm happens to be pointed at, and not at all for `helm template`),
|
||||
while an HTTPRoute without the Gateway-API CRDs fails at INSTALL time. A chart
|
||||
that guesses would therefore sometimes render an object the cluster rejects and
|
||||
sometimes silently render nothing. So the mode is a value.
|
||||
*/}}
|
||||
{{- /*
|
||||
Null-safe like every other block this chart dereferences (#99): an override
|
||||
written without children - "httpRoute:" with the keys below it deleted - is
|
||||
YAML null, and `.Values.httpRoute.enabled` would abort the render with a nil
|
||||
pointer. An empty block means "chart defaults apply", and all three defaults
|
||||
here are empty or false, so the whole block collapses to "no HTTPRoute".
|
||||
*/ -}}
|
||||
{{- $httpRoute := .Values.httpRoute | default dict -}}
|
||||
{{- if and $httpRoute.enabled .Values.ingress.enabled -}}
|
||||
{{- fail "ingress.enabled and httpRoute.enabled are both true - set exactly one of them. The Ingress and the HTTPRoute would publish the same Service under the same hostname over two different data paths, which is already a conflict at DNS level: one name, two addresses, and which one a client reaches is luck. A migration runs over a SECOND hostname (set httpRoute.hostnames explicitly while the Ingress keeps ingress.domain), never over parallel operation of both." -}}
|
||||
{{- end -}}
|
||||
{{- if $httpRoute.enabled -}}
|
||||
{{- $parentRefs := $httpRoute.parentRefs | default list -}}
|
||||
{{- if not $parentRefs -}}
|
||||
{{- fail "httpRoute.enabled is true but httpRoute.parentRefs is empty - an HTTPRoute without a parent attaches to no Gateway. It is created, it is schema-valid, and it routes NOTHING, with no event and no condition that says so; the only symptom is a hostname that does not answer. Name the cluster's Gateway, e.g. httpRoute.parentRefs: [{name: public, namespace: gateway}]." -}}
|
||||
{{- end -}}
|
||||
{{- /*
|
||||
One source of truth for the hostname: an empty httpRoute.hostnames means
|
||||
[ingress.domain], so the two cannot drift apart. ingress.domain is read here
|
||||
even though no Ingress is rendered in this mode - the value names the public
|
||||
hostname of this release, which is what both modes need; it is not an Ingress
|
||||
switch. Setting httpRoute.hostnames explicitly is what a migration over a
|
||||
second hostname needs, and it then wins over ingress.domain.
|
||||
*/ -}}
|
||||
{{- $hostnames := $httpRoute.hostnames | default list -}}
|
||||
{{- if not $hostnames -}}
|
||||
{{- $hostnames = list (required "A Domain/Host must be provided: set ingress.domain (used as the HTTPRoute's single hostname when httpRoute.hostnames is empty) or set httpRoute.hostnames explicitly" .Values.ingress.domain) -}}
|
||||
{{- end -}}
|
||||
apiVersion: gateway.networking.k8s.io/v1
|
||||
kind: HTTPRoute
|
||||
metadata:
|
||||
name: {{ .Release.Name }}-{{ .Chart.Name }}-httproute
|
||||
spec:
|
||||
parentRefs:
|
||||
{{- toYaml $parentRefs | nindent 4 }}
|
||||
hostnames:
|
||||
{{- toYaml $hostnames | nindent 4 }}
|
||||
rules:
|
||||
# The Ingress counterpart is `path: / , pathType: Prefix` on the one
|
||||
# backend Service - CyberChef is a static bundle served at the root and has
|
||||
# no second path. The port is the Service's port NUMBER and not its name:
|
||||
# backendRefs takes a PortNumber, so unlike the Ingress this reference
|
||||
# cannot be made by name and the 80 below has to track
|
||||
# cyberchef.service.yaml by hand. ci/run-httproute-match-test.sh is what
|
||||
# holds that pair - it fails when a backendRef names a Service, or a port,
|
||||
# the same render does not create.
|
||||
- matches:
|
||||
- path:
|
||||
type: PathPrefix
|
||||
value: /
|
||||
backendRefs:
|
||||
- name: {{ .Release.Name }}-{{ .Chart.Name }}-service
|
||||
port: 80
|
||||
{{- end }}
|
||||
@@ -57,13 +57,42 @@
|
||||
"type": ["string", "null"]
|
||||
},
|
||||
"annotations": {
|
||||
"description": "Extra Ingress annotations, merged with the cert-manager annotation the chart always sets. Free-form keys by nature.",
|
||||
"description": "Extra Ingress annotations, merged with the cert-manager annotation the chart always sets. Free-form keys by nature. No counterpart in Gateway mode: the Gateway API has no annotation channel, so these are gone when httpRoute.enabled is used instead.",
|
||||
"type": "object"
|
||||
}
|
||||
}
|
||||
},
|
||||
"httpRoute": {
|
||||
"description": "Gateway-API alternative to the Ingress (Gitea #114). Mutually exclusive with ingress.enabled - the render aborts when both are true. The chart renders only an HTTPRoute with parentRefs; class and certificate sit on the Gateway and belong to the platform. The Gateway-API CRDs must be present in the cluster, because the chart cannot detect them.",
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"enabled": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"parentRefs": {
|
||||
"description": "The Gateway(s) the route attaches to. Required while httpRoute.enabled is true, enforced by required()-equivalent in the template (an empty list aborts the render) - a JSON Schema \"required\" cannot do it, because the key ships as an empty list and is therefore present. Entries are the Gateway API's ParentReference and are passed through unchanged.",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/parentRef"
|
||||
}
|
||||
},
|
||||
"hostnames": {
|
||||
"description": "Empty means [ingress.domain], so the hostname has a single source of truth.",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"definitions": {
|
||||
"parentRef": {
|
||||
"description": "A Gateway API ParentReference, passed verbatim into HTTPRoute.spec.parentRefs. Deliberately left OPEN (no additionalProperties: false), for the same reason probes and security contexts are: Kubernetes owns this schema, the Gateway API adds fields to it across releases, and a closed list here would reject a field only upstream knows about. What the chart can assert is that \"name\" is there - it is required by the API and a reference without it attaches to nothing, which is the silent failure this chart aborts the render over.",
|
||||
"type": "object",
|
||||
"required": ["name"]
|
||||
},
|
||||
"resources": {
|
||||
"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"],
|
||||
|
||||
+50
-3
@@ -68,13 +68,60 @@ 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).
|
||||
# Set to false to omit the Ingress entirely (the required clusterIssuer value
|
||||
# is then not needed either, because it is only read inside the Ingress
|
||||
# template). Mutually exclusive with httpRoute.enabled below: with both true
|
||||
# the render aborts, naming both values.
|
||||
enabled: true
|
||||
clusterIssuer: null
|
||||
# The public hostname of this release. Read by the Ingress AND - when
|
||||
# httpRoute.hostnames is empty - by the HTTPRoute, so it stays the single
|
||||
# source of truth for the hostname in both modes.
|
||||
domain: null
|
||||
className: nginx
|
||||
# Extra annotations for the Ingress; merged with the cert-manager
|
||||
# cluster-issuer annotation the chart always sets.
|
||||
#
|
||||
# THESE HAVE NO COUNTERPART IN GATEWAY MODE. The Gateway API has no
|
||||
# annotation channel for controller behaviour, so everything configured here
|
||||
# - external auth in front of an app that has no login of its own, for
|
||||
# instance - is simply gone when httpRoute.enabled is used instead. It is
|
||||
# then the Gateway's business, or that of an implementation-specific policy
|
||||
# object this chart deliberately does not render.
|
||||
annotations: {}
|
||||
|
||||
# Gateway-API alternative to the Ingress above (Gitea #114). Off by default, so
|
||||
# an existing release is unaffected; exactly one of ingress.enabled and
|
||||
# httpRoute.enabled may be true.
|
||||
#
|
||||
# The chart renders ONLY an HTTPRoute with parentRefs - no Gateway, no
|
||||
# gatewayClassName, no Certificate and no implementation-specific policy
|
||||
# object. Class and certificate belong to the Gateway and therefore to the
|
||||
# platform, which is what keeps this chart independent of whether the cluster
|
||||
# runs nginx-gateway-fabric, Cilium, Envoy Gateway or Istio.
|
||||
#
|
||||
# The Gateway-API CRDs must be installed in the cluster: the chart does not and
|
||||
# cannot detect them (.Capabilities.APIVersions is unreliable at template
|
||||
# time), so with httpRoute.enabled: true and no CRDs the INSTALL fails.
|
||||
httpRoute:
|
||||
enabled: false
|
||||
# The Gateway(s) this route attaches to - a platform object, usually in
|
||||
# another namespace, which must allow this namespace in its listener's
|
||||
# allowedRoutes. One entry per parent, the fields are the Gateway API's
|
||||
# ParentReference (name, and optionally namespace, sectionName, port, group,
|
||||
# kind), passed through unchanged:
|
||||
#
|
||||
# parentRefs:
|
||||
# - name: public
|
||||
# namespace: gateway
|
||||
# sectionName: https
|
||||
#
|
||||
# REQUIRED while httpRoute.enabled is true - the render aborts on an empty
|
||||
# list, because a route without a parent is created, is valid and routes
|
||||
# nothing, with no event to say so.
|
||||
parentRefs: []
|
||||
# Empty means [ingress.domain], so the hostname has one source of truth. Set
|
||||
# explicitly only to publish under a different or an additional name - which
|
||||
# is how a migration from the Ingress runs: the HTTPRoute takes a second
|
||||
# hostname while the Ingress keeps ingress.domain.
|
||||
hostnames: []
|
||||
Reference in new issue
Block a user