diff --git a/helm/README.md b/helm/README.md index 38c615215..b4cec75c0 100644 --- a/helm/README.md +++ b/helm/README.md @@ -277,7 +277,8 @@ uer. `ClusterIssuer` or `Issuer`. | | gatewayApi.listeners.tls.name | string | `tls` | Gateway API TLS passthrough listener name. | | gatewayApi.listeners.tls.port | int | `443` | Gateway API TLS passthrough listener port. | | gatewayApi.listeners.tls.backendPort | int | `null` | Backend service port that terminates TLS; defaults to the console port. | -| gatewayApi.hostname | string | Hostname to access RustFS via gateway api. | +| gatewayApi.hostname | string | `example.rustfs.com` | Console hostname for Gateway API. | +| gatewayApi.endpointHostname | string | `""` | Optional separate S3 endpoint hostname; empty disables the S3 HTTPRoute. | | gatewayApi.secretName | string | Secret tls to via RustFS using HTTPS. | | gatewayApi.existingGateway.name | string | `""` | The existing gateway name, instead of creating a new one. | | gatewayApi.existingGateway.namespace | string | `""` | The namespace of the existing gateway, if not the local namespace. | @@ -449,7 +450,21 @@ NAME HOSTNAMES AGE rustfs-route ["example.rustfs.com"] 172m ``` -Then, via RustFS instance via `https://example.rustfs.com` or `http://example.rustfs.com`. +Access the console at `https://example.rustfs.com`; HTTP redirects to HTTPS by default. HTTPRoutes reference the chart's Kubernetes Service directly and do not require the TraefikService CRD or Traefik's Kubernetes CRD provider. The former TraefikService cookie stickiness is no longer configured by this chart. + +To expose the S3 endpoint alongside the console, set a different `gatewayApi.endpointHostname`: + +```yaml +gatewayApi: + enabled: true + hostname: console.example.com + endpointHostname: s3.example.com + existingGateway: + name: shared-gateway + namespace: gateway-system +``` + +The console route uses `service.console.port` (9001 by default), and the optional S3 route uses `service.endpoint.port` (9000 by default). Both attach to the configured HTTPS listener and preserve request paths. Configure DNS and the Gateway's TLS certificate for both hostnames. When using a Gateway in another namespace, its listener must allow routes from the RustFS namespace. Leaving `endpointHostname` empty preserves the console-only behavior. For end-to-end encryption, set `gatewayApi.listeners.tls.enabled` to `true`. The chart then adds a `TLS` listener with `tls.mode: Passthrough` to the `Gateway` and generates a `TLSRoute` that forwards the encrypted stream to the RustFS service, where TLS is terminated on the backend side. Note that backend TLS termination must be configured on RustFS itself (for example `RUSTFS_TLS_PATH` pointing to server certificates), and the installed Gateway API CRDs must include `TLSRoute`. diff --git a/helm/rustfs/templates/gateway-api/httproute.yml b/helm/rustfs/templates/gateway-api/httproute.yml index 77dc71be0..3f3e2f505 100644 --- a/helm/rustfs/templates/gateway-api/httproute.yml +++ b/helm/rustfs/templates/gateway-api/httproute.yml @@ -1,42 +1,47 @@ {{- if .Values.gatewayApi.enabled }} +{{- $routes := list (dict "name" "route" "hostname" .Values.gatewayApi.hostname "port" .Values.service.console.port) }} +{{- with .Values.gatewayApi.endpointHostname }} +{{- if eq . $.Values.gatewayApi.hostname }} +{{- fail "gatewayApi.endpointHostname must differ from gatewayApi.hostname" }} +{{- end }} +{{- $routes = append $routes (dict "name" "endpoint-route" "hostname" . "port" $.Values.service.endpoint.port) }} +{{- end }} +{{- range $routes }} +{{- if eq .name "endpoint-route" }} +--- +{{- end }} apiVersion: gateway.networking.k8s.io/v1 kind: HTTPRoute metadata: - name: {{ include "rustfs.fullname" . }}-route - namespace: {{ .Release.Namespace }} + name: {{ include "rustfs.fullname" $ }}-{{ .name }} + namespace: {{ $.Release.Namespace }} annotations: - {{- if eq .Values.gatewayApi.gatewayClass "contour" }} + {{- if and (eq $.Values.gatewayApi.gatewayClass "contour") (eq .name "route") }} projectcontour.io/upstream-hash-policy: "cookie" {{- end }} spec: parentRefs: - {{- if .Values.gatewayApi.existingGateway.name }} - - name: {{ .Values.gatewayApi.existingGateway.name }} - {{- if .Values.gatewayApi.existingGateway.namespace }} - namespace: {{ .Values.gatewayApi.existingGateway.namespace }} + {{- if $.Values.gatewayApi.existingGateway.name }} + - name: {{ $.Values.gatewayApi.existingGateway.name }} + {{- if $.Values.gatewayApi.existingGateway.namespace }} + namespace: {{ $.Values.gatewayApi.existingGateway.namespace }} {{- end }} - sectionName: {{ .Values.gatewayApi.listeners.https.name | default "websecure"}} + sectionName: {{ $.Values.gatewayApi.listeners.https.name | default "websecure"}} {{- else }} - name: {{ include "rustfs.fullname" $ }}-gateway - sectionName: {{ .Values.gatewayApi.listeners.https.name | default "websecure"}} + sectionName: {{ $.Values.gatewayApi.listeners.https.name | default "websecure"}} {{- end }} hostnames: - - {{ .Values.gatewayApi.hostname }} + - {{ .hostname | quote }} rules: - matches: - path: type: PathPrefix value: / backendRefs: - {{- if eq .Values.gatewayApi.gatewayClass "traefik" }} - - name: {{ include "rustfs.fullname" . }}-sticky-svc - port: {{ .Values.service.console.port }} - kind: TraefikService - group: traefik.io - {{- else }} - name: {{ include "rustfs.fullname" $ }}-svc - port: {{ .Values.service.console.port }} - {{- end }} + port: {{ .port }} +{{- end }} {{- if .Values.gatewayApi.httpToHttpsRedirect }} --- apiVersion: gateway.networking.k8s.io/v1 diff --git a/helm/rustfs/templates/gateway-api/traefik-service.yaml b/helm/rustfs/templates/gateway-api/traefik-service.yaml deleted file mode 100644 index e1c539a3a..000000000 --- a/helm/rustfs/templates/gateway-api/traefik-service.yaml +++ /dev/null @@ -1,20 +0,0 @@ -{{- if and .Values.gatewayApi.enabled (eq .Values.gatewayApi.gatewayClass "traefik") }} -apiVersion: traefik.io/v1alpha1 -kind: TraefikService -metadata: - name: {{ include "rustfs.fullname" . }}-sticky-svc - namespace: {{ .Release.Namespace }} -spec: - weighted: - services: - - name: {{ include "rustfs.fullname" . }}-svc - namespace: {{ .Release.Namespace }} - port: {{ .Values.service.console.port }} - weight: 1 - sticky: - cookie: - name: rustfs - httpOnly: true - secure: {{ .Values.ingress.tls.enabled | default false }} - sameSite: {{ if .Values.ingress.tls.enabled }}none{{ else }}lax{{ end }} -{{- end }} diff --git a/helm/rustfs/values.yaml b/helm/rustfs/values.yaml index 91a9e410b..88d01e341 100644 --- a/helm/rustfs/values.yaml +++ b/helm/rustfs/values.yaml @@ -381,7 +381,8 @@ gatewayApi: port: 443 # Service port that terminates TLS on the backend; defaults to the console port. backendPort: null - hostname: example.rustfs.com + hostname: example.rustfs.com # Console hostname. + endpointHostname: "" # Optional separate hostname for the S3 endpoint. httpToHttpsRedirect: true existingGateway: name: "" diff --git a/scripts/test_helm_templates.sh b/scripts/test_helm_templates.sh index 05e4456be..1b21e68cb 100755 --- a/scripts/test_helm_templates.sh +++ b/scripts/test_helm_templates.sh @@ -73,6 +73,84 @@ render_server_cert() { ' } +# Gateway routes must resolve to the rendered Kubernetes Service without Traefik CRDs. +for gateway_class in traefik contour istio; do + for distributed in false true; do + standalone=true + if [[ "$distributed" == true ]]; then standalone=false; fi + gateway_output=$(render_chart \ + --set "mode.distributed.enabled=$distributed" \ + --set "mode.standalone.enabled=$standalone" \ + --set gatewayApi.enabled=true \ + --set "gatewayApi.gatewayClass=$gateway_class" \ + --set fullnameOverride=gateway-test \ + --set service.console.port=19001 \ + --set service.endpoint.port=19000 \ + --set gatewayApi.hostname=console.example.com \ + --set gatewayApi.endpointHostname=s3.example.com \ + --set gatewayApi.existingGateway.name=shared-gateway \ + --set gatewayApi.existingGateway.namespace=gateway-system \ + --set gatewayApi.listeners.https.name=https) + yq eval -e 'select(.kind == "Service" and .metadata.name == "gateway-test-svc") | + .metadata.namespace == "rustfs" and + .spec.ports[0].port == 19000 and .spec.ports[1].port == 19001' - <<<"$gateway_output" >/dev/null + for route in route endpoint-route; do + expected_port=19001 + expected_host=console.example.com + if [[ "$route" == endpoint-route ]]; then + expected_port=19000 + expected_host=s3.example.com + fi + ROUTE_NAME="gateway-test-$route" EXPECTED_PORT="$expected_port" EXPECTED_HOST="$expected_host" \ + yq eval -e 'select(.kind == "HTTPRoute" and .metadata.name == strenv(ROUTE_NAME)) | + .metadata.namespace == "rustfs" and + .spec.hostnames[0] == strenv(EXPECTED_HOST) and + .spec.parentRefs[0].name == "shared-gateway" and + .spec.parentRefs[0].namespace == "gateway-system" and + .spec.parentRefs[0].sectionName == "https" and + .spec.rules[0].matches[0].path.type == "PathPrefix" and + .spec.rules[0].matches[0].path.value == "/" and + (.spec.rules[0].backendRefs | length) == 1 and + .spec.rules[0].backendRefs[0].name == "gateway-test-svc" and + .spec.rules[0].backendRefs[0].port == env(EXPECTED_PORT) and + (.spec.rules[0].backendRefs[0].kind // "Service") == "Service" and + (.spec.rules[0].backendRefs[0].group // "") == ""' - <<<"$gateway_output" >/dev/null + done + if grep -q 'kind: TraefikService' <<<"$gateway_output"; then + echo "Gateway API must not require a TraefikService CRD" >&2 + exit 1 + fi + done +done + +gateway_default=$(render_chart --set gatewayApi.enabled=true) +yq eval -e 'select(.kind == "HTTPRoute" and .metadata.name == "rustfs-route") | + .spec.rules[0].backendRefs[0].name == "rustfs-svc" and + .spec.rules[0].backendRefs[0].port == 9001 and + .spec.parentRefs[0].name == "rustfs-gateway" and + .spec.parentRefs[0].sectionName == "websecure"' - <<<"$gateway_default" >/dev/null +if grep -q 'name: rustfs-endpoint-route' <<<"$gateway_default"; then + echo "The S3 route must be opt-in" >&2 + exit 1 +fi + +gateway_no_redirect=$(render_chart --set gatewayApi.enabled=true --set gatewayApi.endpointHostname=s3.example.com --set gatewayApi.httpToHttpsRedirect=false) +if grep -q 'type: RequestRedirect' <<<"$gateway_no_redirect"; then + echo "Disabling HTTPS redirects must suppress the redirect route" >&2 + exit 1 +fi + +if render_chart --set gatewayApi.enabled=true --set gatewayApi.endpointHostname=example.rustfs.com >/dev/null 2>&1; then + echo "Console and S3 routes must not share a hostname" >&2 + exit 1 +fi + +gateway_disabled=$(render_chart --set gatewayApi.endpointHostname=s3.example.com) +if grep -q 'kind: HTTPRoute' <<<"$gateway_disabled"; then + echo "Disabling Gateway API must suppress all HTTP routes" >&2 + exit 1 +fi + recreate_output=$(render_standalone_deployment --set mode.standalone.strategy.type=Recreate) grep -q "type: Recreate" <<<"$recreate_output" if grep -q "rollingUpdate:" <<<"$recreate_output"; then