From be2df3519dd900338ba690021173cf6e801ce6f9 Mon Sep 17 00:00:00 2001 From: Benoit Pourre Date: Mon, 2 Nov 2026 05:42:49 +0100 Subject: [PATCH 1/7] feat: update valkey version to 0.12.1 and code-server image to 4.144.0 Signed-off-by: Benoit Pourre --- charts/codx/Chart.lock | 6 +++--- charts/codx/Chart.yaml | 2 +- charts/codx/values.yaml | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/charts/codx/Chart.lock b/charts/codx/Chart.lock index 874d100..f88646f 100644 --- a/charts/codx/Chart.lock +++ b/charts/codx/Chart.lock @@ -4,6 +4,6 @@ dependencies: version: 2.41.0 - name: valkey repository: oci://ghcr.io/valkey-io/valkey-helm - version: 0.12.0 -digest: sha256:bdfd5df0224e07bb230f7efdd218dbe959a02dbc9c7f3fad52b19295cfb559d4 -generated: "2026-10-13T15:21:42.428651669+02:00" + version: 0.12.1 +digest: sha256:12e757ddc2f8f1122d79ac7b077d577ba8a7ff328912c54d63a6b34b79d0a48a +generated: "2026-10-28T17:43:33.664164823+01:00" diff --git a/charts/codx/Chart.yaml b/charts/codx/Chart.yaml index 5ab9696..0f49016 100644 --- a/charts/codx/Chart.yaml +++ b/charts/codx/Chart.yaml @@ -28,7 +28,7 @@ dependencies: repository: oci://ghcr.io/valkey-io/valkey-helm tags: - valkey - version: 0.12.0 + version: 0.12.1 annotations: category: DeveloperTools artifacthub.io/images: | diff --git a/charts/codx/values.yaml b/charts/codx/values.yaml index c230cf2..a254329 100644 --- a/charts/codx/values.yaml +++ b/charts/codx/values.yaml @@ -994,7 +994,7 @@ profiles: runAsGroup: 1000 fsGroup: 1000 codeServer: - image: docker.io/captnbp/code-server:4.141.0 + image: docker.io/captnbp/code-server:4.144.0 securityContext: {} # allowPrivilegeEscalation: false From 700a9a6b25b59b866a70fdf8c567181fedf3cfc9 Mon Sep 17 00:00:00 2001 From: Benoit Pourre Date: Mon, 2 Nov 2026 09:07:38 +0100 Subject: [PATCH 2/7] feat: Migrate from Ingress to IngressRoute Signed-off-by: Benoit Pourre --- .github/workflows/ci.yml | 2 +- Dockerfile | 2 +- charts/codx/README.md | 32 ++- charts/codx/templates/_helpers.tpl | 10 - charts/codx/templates/ingress.yaml | 88 -------- charts/codx/templates/ingressroute.yaml | 89 ++++++++ charts/codx/templates/serverstransport.yaml | 2 +- charts/codx/templates/service.yaml | 4 - charts/codx/templates/traefik-mtls.yaml | 2 +- charts/codx/values.yaml | 147 +++++-------- docs/authentication.md | 37 ++-- go.mod | 104 ++++----- go.sum | 229 ++++++++++---------- 13 files changed, 348 insertions(+), 400 deletions(-) delete mode 100644 charts/codx/templates/ingress.yaml create mode 100644 charts/codx/templates/ingressroute.yaml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f1d793b..eecb69f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -142,7 +142,7 @@ jobs: --set oidc.clientSecret=secret \ --set oidc.redirectUrl=https://codx.example.com/auth/callback \ --set redis.host=valkey:6379 \ - --set ingress.host=codx.example.com \ + --set ingress.hostname=codx.example.com \ > /dev/null - name: Helm package diff --git a/Dockerfile b/Dockerfile index 1ba5a37..93c4965 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,7 +1,7 @@ # syntax=docker/dockerfile:1 # -- Build stage -- -FROM golang:1.27-alpine AS builder +FROM golang:1.27.3-alpine AS builder WORKDIR /src diff --git a/charts/codx/README.md b/charts/codx/README.md index 62afa70..86d1688 100644 --- a/charts/codx/README.md +++ b/charts/codx/README.md @@ -16,7 +16,7 @@ graph TB subgraph K8s["Kubernetes Cluster"] subgraph IngressNS["traefik Namespace"] - Traefik[Traefik Ingress
TLS termination] + Traefik[Traefik IngressRoute
TLS termination] end subgraph MonitoringNS["kube-prometheus-stack Namespace
Optional"] @@ -112,6 +112,7 @@ $ helm install my-release oci://registry-1.docker.io/captnbp/codx - Helm 3.2.0+ - PV provisioner support in the underlying infrastructure - [cert-manager](https://cert-manager.io/) +- [Traefik](https://traefik.io/) 3.6+ with the Kubernetes CRD provider (IngressRoute, Middleware, TLSOption, ServersTransport) ## Installing the Chart @@ -235,22 +236,19 @@ $ helm delete --purge my-release | `service.extraHeadlessPorts` | Extra ports to expose on codx headless service | `[]` | | `service.ipFamilyPolicy` | Controller Service ipFamilyPolicy (optional, cloud specific) | `PreferDualStack` | | `service.ipFamilies` | Controller Service ipFamilies (optional, cloud specific) | `["IPv6","IPv4"]` | -| `ingress.enabled` | Enable ingress record generation for codx | `true` | -| `ingress.pathType` | Ingress path type | `ImplementationSpecific` | -| `ingress.apiVersion` | Force Ingress API version (automatically detected if not set) | `""` | -| `ingress.hostname` | Default host for the ingress record | `codx.local` | -| `ingress.ingressClassName` | IngressClass that will be be used to implement the Ingress (Kubernetes 1.18+) | `traefik` | -| `ingress.ingressControllerType` | ingressControllerType that will be be used to implement the Ingress specific annotations (Ex. nginx or traefik) | `traefik` | -| `ingress.path` | Default path for the ingress record | `/` | -| `ingress.annotations` | Additional annotations for the Ingress resource. To enable certificate autogeneration, place here your cert-manager annotations. | `{}` | -| `ingress.extraMiddlewares` | Extra Traefik middlewares appended to the traefik.ingress.kubernetes.io/router.middlewares annotation, after any user-defined middlewares and the chart passTLSClientCert middleware (when traefikMtls.enabled). Entries are middleware references; the "@kubernetescrd" provider suffix is added when missing. | `[]` | -| `ingress.tls` | Enable TLS configuration for the host defined at `ingress.hostname` parameter | `false` | -| `ingress.selfSigned` | Create a TLS secret for this ingress record using self-signed certificates generated by Helm | `false` | -| `ingress.extraHosts` | An array with additional hostname(s) to be covered with the ingress record | `[]` | -| `ingress.extraPaths` | An array with additional arbitrary paths that may need to be added to the ingress under the main host | `[]` | -| `ingress.extraTls` | TLS configuration for additional hostname(s) to be covered with this ingress record | `[]` | -| `ingress.secrets` | Custom TLS certificates as secrets | `[]` | -| `ingress.extraRules` | Additional rules to be covered with this ingress record | `[]` | +| `ingress.enabled` | Enable IngressRoute record generation for codx (requires the Traefik Kubernetes CRD provider) | `true` | +| `ingress.apiVersion` | Force IngressRoute API version: traefik.io/v1alpha1 (Traefik v3, default) or traefik.containo.us/v1alpha1 (Traefik v2) | `traefik.io/v1alpha1` | +| `ingress.hostname` | Default host for the IngressRoute record | `codx.local` | +| `ingress.path` | Default path for the IngressRoute record | `/` | +| `ingress.entryPoints` | Traefik entryPoints the IngressRoute binds to (e.g. websecure). Empty binds to all entry points | `[]` | +| `ingress.annotations` | Additional annotations for the IngressRoute resource | `{}` | +| `ingress.middlewares` | Traefik Middlewares attached to the IngressRoute routes, before the chart passTLSClientCert middleware (when traefikMtls.enabled). A namespace defaults to the release namespace; cross-namespace references require the Traefik allowCrossNamespace option | `[]` | +| `ingress.tls.enabled` | Enable TLS on the IngressRoute (frontend TLS between Traefik and the clients) | `false` | +| `ingress.tls.secretName` | Name of the Secret holding the TLS certificate (in the release namespace). Optional: when empty, no secretName is set on the IngressRoute (use certResolver or a TLSStore instead) | `""` | +| `ingress.tls.certResolver` | Traefik certificate resolver (ACME) used to generate the certificate, e.g. letsencrypt | `""` | +| `ingress.tls.options.name` | Name of the TLSOption applied to the route. Defaults to the chart client-mTLS TLSOption when traefikMtls.enabled | `""` | +| `ingress.tls.options.namespace` | Namespace of the TLSOption. Defaults to the release namespace | `""` | +| `ingress.extraHosts` | An array with additional hostname(s) to be covered with the IngressRoute record | `[]` | ### RBAC parameter diff --git a/charts/codx/templates/_helpers.tpl b/charts/codx/templates/_helpers.tpl index bbd7350..1e89526 100644 --- a/charts/codx/templates/_helpers.tpl +++ b/charts/codx/templates/_helpers.tpl @@ -25,16 +25,6 @@ Create the name of the service account to use {{- end -}} {{- end -}} -{{/* -Return true if cert-manager required annotations for TLS signed certificates are set in the Ingress annotations -Ref: https://cert-manager.io/docs/usage/ingress/#supported-annotations -*/}} -{{- define "codx.ingress.certManagerRequest" -}} -{{ if or (hasKey . "cert-manager.io/cluster-issuer") (hasKey . "cert-manager.io/issuer") }} - {{- true -}} -{{- end -}} -{{- end -}} - {{/* Return true if a TLS credentials secret object should be created */}} diff --git a/charts/codx/templates/ingress.yaml b/charts/codx/templates/ingress.yaml deleted file mode 100644 index 69c25b8..0000000 --- a/charts/codx/templates/ingress.yaml +++ /dev/null @@ -1,88 +0,0 @@ -{{- if .Values.ingress.enabled }} -{{- $mtlsMiddleware := and .Values.traefikMtls.enabled (eq .Values.ingress.ingressControllerType "traefik") }} -apiVersion: {{ include "common.capabilities.ingress.apiVersion" . }} -kind: Ingress -metadata: - name: {{ template "common.names.fullname" . }} - namespace: {{ include "common.names.namespace" . | quote }} - labels: {{- include "common.labels.standard" ( dict "customLabels" .Values.commonLabels "context" $ ) | nindent 4 }} - app.kubernetes.io/component: codx - {{- $ann := dict }} - {{- if or .Values.ingress.annotations .Values.commonAnnotations }} - {{- $ann = include "common.tplvalues.merge" ( dict "values" ( list .Values.ingress.annotations .Values.commonAnnotations ) "context" . ) | fromYaml }} - {{- end }} - {{- $mwKey := "traefik.ingress.kubernetes.io/router.middlewares" }} - {{- if and (eq .Values.ingress.ingressControllerType "traefik") (or $mtlsMiddleware .Values.ingress.extraMiddlewares) }} - {{- $mwList := list }} - {{- if index $ann $mwKey }} - {{- $mwList = append $mwList (index $ann $mwKey) }} - {{- end }} - {{- if $mtlsMiddleware }} - {{- $mwList = append $mwList (printf "%s-%s@kubernetescrd" (include "common.names.namespace" .) (include "codx.traefikMtls.middlewareName" .)) }} - {{- end }} - {{- range .Values.ingress.extraMiddlewares }} - {{- $ref := . }} - {{- if not (contains "@" $ref) }} - {{- $ref = printf "%s@kubernetescrd" $ref }} - {{- end }} - {{- $mwList = append $mwList $ref }} - {{- end }} - {{- $_ := set $ann $mwKey (join "," $mwList) }} - {{- end }} - {{- if $mtlsMiddleware }} - {{- if not (index $ann "traefik.ingress.kubernetes.io/router.tls.options") }} - {{- $_ := set $ann "traefik.ingress.kubernetes.io/router.tls.options" (printf "%s-%s@kubernetescrd" (include "common.names.namespace" .) (include "codx.traefikMtls.tlsOptionName" .)) }} - {{- end }} - {{- end }} - {{- if $ann }} - annotations: {{- include "common.tplvalues.render" ( dict "value" $ann "context" $) | nindent 4 }} - {{- else }} - annotations: {} - {{- end }} -spec: - {{- if .Values.ingress.ingressClassName }} - ingressClassName: {{ .Values.ingress.ingressClassName | quote }} - {{- end }} - rules: - {{- if .Values.ingress.hostname }} - - host: {{ .Values.ingress.hostname }} - http: - paths: - {{- if .Values.ingress.extraPaths }} - {{- toYaml .Values.ingress.extraPaths | nindent 10 }} - {{- end }} - - path: {{ .Values.ingress.path }} - pathType: {{ .Values.ingress.pathType }} - {{- if .Values.tls.enabled }} - backend: {{- include "common.ingress.backend" (dict "serviceName" (include "common.names.fullname" .) "servicePort" "https" "context" $) | nindent 14 }} - {{- else }} - backend: {{- include "common.ingress.backend" (dict "serviceName" (include "common.names.fullname" .) "servicePort" "http" "context" $) | nindent 14 }} - {{- end }} - {{- end }} - {{- range .Values.ingress.extraHosts }} - - host: {{ .name | quote }} - http: - paths: - - path: {{ default "/" .path }} - pathType: {{ default "ImplementationSpecific" .pathType }} - {{- if .Values.tls.enabled }} - backend: {{- include "common.ingress.backend" (dict "serviceName" (include "common.names.fullname" $) "servicePort" "https" "context" $) | nindent 14 }} - {{- else }} - backend: {{- include "common.ingress.backend" (dict "serviceName" (include "common.names.fullname" .) "servicePort" "http" "context" $) | nindent 14 }} - {{- end }} - {{- end }} - {{- if .Values.ingress.extraRules }} - {{- include "common.tplvalues.render" (dict "value" .Values.ingress.extraRules "context" $) | nindent 4 }} - {{- end }} - {{- if or (and .Values.ingress.tls (or (include "common.ingress.certManagerRequest" ( dict "annotations" .Values.ingress.annotations )) .Values.ingress.selfSigned)) .Values.ingress.extraTls }} - tls: - {{- if and .Values.ingress.tls (or (include "common.ingress.certManagerRequest" ( dict "annotations" .Values.ingress.annotations )) .Values.ingress.selfSigned) }} - - hosts: - - {{ .Values.ingress.hostname | quote }} - secretName: {{ printf "%s-tls" .Values.ingress.hostname }} - {{- end }} - {{- if .Values.ingress.extraTls }} - {{- include "common.tplvalues.render" (dict "value" .Values.ingress.extraTls "context" $) | nindent 4 }} - {{- end }} - {{- end }} -{{- end }} \ No newline at end of file diff --git a/charts/codx/templates/ingressroute.yaml b/charts/codx/templates/ingressroute.yaml new file mode 100644 index 0000000..1216940 --- /dev/null +++ b/charts/codx/templates/ingressroute.yaml @@ -0,0 +1,89 @@ +{{/* +Traefik IngressRoute exposing the CodX service. Requires the Traefik +Kubernetes CRD provider (IngressRoute, Middleware, TLSOption). + +The backend service only exposes the HTTPS port; when the chart-internal +TLS is enabled the route targets it with the chart ServersTransport +(, created in serverstransport.yaml) so Traefik trusts the +self-signed CodX certificate. +*/}} +{{- if .Values.ingress.enabled }} +{{- $fullname := include "common.names.fullname" . }} +{{- $mtlsMiddleware := .Values.traefikMtls.enabled }} +{{- $hosts := list }} +{{- if .Values.ingress.hostname }} +{{- $hosts = append $hosts (dict "name" .Values.ingress.hostname "path" .Values.ingress.path) }} +{{- end }} +{{- range .Values.ingress.extraHosts }} +{{- $hosts = append $hosts (dict "name" .name "path" (default "/" .path)) }} +{{- end }} +apiVersion: {{ .Values.ingress.apiVersion }} +kind: IngressRoute +metadata: + name: {{ $fullname }} + namespace: {{ include "common.names.namespace" . | quote }} + labels: {{- include "common.labels.standard" ( dict "customLabels" .Values.commonLabels "context" $ ) | nindent 4 }} + app.kubernetes.io/component: codx + {{- if or .Values.ingress.annotations .Values.commonAnnotations }} + annotations: {{- include "common.tplvalues.merge" ( dict "values" ( list .Values.ingress.annotations .Values.commonAnnotations ) "context" . ) | nindent 4 }} + {{- end }} +spec: + {{- with .Values.ingress.entryPoints }} + entryPoints: {{- toYaml . | nindent 4 }} + {{- end }} + routes: + {{- range $hosts }} + - match: {{ printf "Host(`%s`) && PathPrefix(`%s`)" .name .path | quote }} + kind: Rule + {{- if or $.Values.ingress.middlewares $mtlsMiddleware }} + middlewares: + {{- range $.Values.ingress.middlewares }} + - name: {{ .name }} + {{- if .namespace }} + namespace: {{ .namespace | quote }} + {{- end }} + {{- end }} + {{- if $mtlsMiddleware }} + - name: {{ include "codx.traefikMtls.middlewareName" $ }} + {{- end }} + {{- end }} + services: + - name: {{ $fullname }} + port: {{ $.Values.containerPorts.https }} + {{- if $.Values.tls.enabled }} + scheme: https + serversTransport: {{ $fullname }} + {{- end }} + {{- end }} + {{- if .Values.ingress.tls.enabled }} + tls: + {{- if .Values.ingress.tls.secretName }} + secretName: {{ .Values.ingress.tls.secretName | quote }} + {{- end }} + {{- if .Values.ingress.tls.certResolver }} + certResolver: {{ .Values.ingress.tls.certResolver | quote }} + {{- end }} + {{- $tlsOptionName := .Values.ingress.tls.options.name }} + {{- if and $mtlsMiddleware (not $tlsOptionName) }} + {{- $tlsOptionName = include "codx.traefikMtls.tlsOptionName" . }} + {{- end }} + {{- if $tlsOptionName }} + options: + name: {{ $tlsOptionName }} + {{- if .Values.ingress.tls.options.namespace }} + namespace: {{ .Values.ingress.tls.options.namespace | quote }} + {{- end }} + {{- end }} + {{- $domains := list }} + {{- range $hosts }} + {{- $domains = append $domains .name }} + {{- end }} + {{- if $domains }} + domains: + - main: {{ index $domains 0 | quote }} + {{- if gt (len $domains) 1 }} + sans: {{- rest $domains | toYaml | nindent 10 }} + {{- end }} + {{- end }} + {{- end }} +{{- end }} diff --git a/charts/codx/templates/serverstransport.yaml b/charts/codx/templates/serverstransport.yaml index d2faed6..d707e73 100644 --- a/charts/codx/templates/serverstransport.yaml +++ b/charts/codx/templates/serverstransport.yaml @@ -1,4 +1,4 @@ -{{- if and (eq .Values.ingress.ingressControllerType "traefik") .Values.tls.enabled }} +{{- if .Values.tls.enabled }} {{- $releaseNamespace := .Release.Namespace }} {{- $clusterDomain := .Values.clusterDomain }} {{- $serviceName := include "common.names.fullname" . }} diff --git a/charts/codx/templates/service.yaml b/charts/codx/templates/service.yaml index 31e6de7..4f76c95 100644 --- a/charts/codx/templates/service.yaml +++ b/charts/codx/templates/service.yaml @@ -9,10 +9,6 @@ metadata: {{- if or .Values.service.annotations .Values.commonAnnotations }} {{- $annotations := include "common.tplvalues.merge" ( dict "values" ( list .Values.service.annotations .Values.commonAnnotations ) "context" . ) }}{{- include "common.tplvalues.render" ( dict "value" $annotations "context" $) | nindent 4 }} {{- end }} - {{- if and (eq .Values.ingress.ingressControllerType "traefik") .Values.tls.enabled }} - traefik.ingress.kubernetes.io/service.serversscheme: https - traefik.ingress.kubernetes.io/service.serverstransport: "{{ .Release.Namespace }}-{{ template "common.names.fullname" . }}@kubernetescrd" - {{- end }} spec: type: {{ .Values.service.type }} {{- if and .Values.service.clusterIP (eq .Values.service.type "ClusterIP") }} diff --git a/charts/codx/templates/traefik-mtls.yaml b/charts/codx/templates/traefik-mtls.yaml index 351d671..0ab90b8 100644 --- a/charts/codx/templates/traefik-mtls.yaml +++ b/charts/codx/templates/traefik-mtls.yaml @@ -9,7 +9,7 @@ Traefik objects for the mTLS admin fallback authentication: Requires the Traefik CRDs (traefik.io/v1alpha1 Middleware and TLSOption). All objects are created in the release namespace, the same namespace as the -Ingress, so Traefik resolves the plain-name references (name@kubernetescrd). +IngressRoute, so Traefik resolves the plain-name references. */}} {{- if .Values.traefikMtls.enabled }} {{- $fullname := include "common.names.fullname" . }} diff --git a/charts/codx/values.yaml b/charts/codx/values.yaml index a254329..bbbdf75 100644 --- a/charts/codx/values.yaml +++ b/charts/codx/values.yaml @@ -315,118 +315,75 @@ service: ## codx ingress parameters -## ref: http://kubernetes.io/docs/concepts/services-networking/ingress/ +## ref: https://doc.traefik.io/traefik/providers/kubernetes-crd/ +## The chart exposes CodX with a Traefik IngressRoute, which requires the +## Traefik Kubernetes CRD provider (IngressRoute, Middleware, TLSOption). +## Set enabled=false to expose CodX with your own objects instead (e.g. a +## standard Ingress for another controller). ## ingress: - ## @param ingress.enabled Enable ingress record generation for codx + ## @param ingress.enabled Enable IngressRoute record generation for codx ## enabled: true - ## @param ingress.pathType Ingress path type + ## @param ingress.apiVersion Force IngressRoute API version: traefik.io/v1alpha1 (Traefik v3, default) or traefik.containo.us/v1alpha1 (Traefik v2) ## - pathType: ImplementationSpecific - ## @param ingress.apiVersion Force Ingress API version (automatically detected if not set) - ## - apiVersion: "" - ## @param ingress.hostname Default host for the ingress record + apiVersion: traefik.io/v1alpha1 + ## @param ingress.hostname Default host for the IngressRoute record ## hostname: codx.local - ## @param ingress.ingressClassName IngressClass that will be be used to implement the Ingress (Kubernetes 1.18+) - ## This is supported in Kubernetes 1.18+ and required if you have more than one IngressClass marked as the default for your cluster . - ## ref: https://kubernetes.io/blog/2020/04/02/improvements-to-the-ingress-api-in-kubernetes-1.18/ - ## - ingressClassName: "traefik" - ## @param ingress.ingressControllerType ingressControllerType that will be be used to implement the Ingress specific annotations (Ex. nginx or traefik) - ingressControllerType: "traefik" - ## @param ingress.path Default path for the ingress record - ## NOTE: You may need to set this to '/*' in order to use this with ALB ingress controllers + ## @param ingress.path Default path for the IngressRoute record ## path: / - ## @param ingress.annotations Additional annotations for the Ingress resource. To enable certificate autogeneration, place here your cert-manager annotations. - ## Use this parameter to set the required annotations for cert-manager, see - ## ref: https://cert-manager.io/docs/usage/ingress/#supported-annotations + ## @param ingress.entryPoints [array] Traefik entryPoints the IngressRoute binds to (e.g. websecure). Empty binds to all entry points. + ## e.g: + ## entryPoints: + ## - websecure + ## + entryPoints: [] + ## @param ingress.annotations Additional annotations for the IngressRoute resource ## e.g: ## annotations: - ## kubernetes.io/ingress.class: nginx - ## cert-manager.io/cluster-issuer: cluster-issuer-name + ## external-dns.alpha.kubernetes.io/hostname: codx.example.com ## annotations: {} - ## @param ingress.extraMiddlewares [array] Extra Traefik middlewares appended to the traefik.ingress.kubernetes.io/router.middlewares annotation, after any user-defined middlewares and the chart passTLSClientCert middleware (when traefikMtls.enabled). Entries are middleware references; the "@kubernetescrd" provider suffix is added when missing. + ## @param ingress.middlewares [array] Traefik Middlewares attached to the IngressRoute routes, before the chart passTLSClientCert middleware (when traefikMtls.enabled). A namespace defaults to the release namespace; cross-namespace references require the Traefik allowCrossNamespace option. ## e.g: - ## extraMiddlewares: - ## - my-middleware - ## - traefik-my-other-middleware@kubernetescrd - ## - extraMiddlewares: [] - ## @param ingress.tls Enable TLS configuration for the host defined at `ingress.hostname` parameter - ## TLS certificates will be retrieved from a TLS secret with name: `{{- printf "%s-tls" .Values.ingress.hostname }}` - ## You can: - ## - Use the `ingress.secrets` parameter to create this TLS secret - ## - Rely on cert-manager to create it by setting the corresponding annotations - ## - Rely on Helm to create self-signed certificates by setting `ingress.selfSigned=true` + ## middlewares: + ## - name: my-middleware + ## - name: redirects-to-https + ## namespace: traefik ## - tls: false - ## @param ingress.selfSigned Create a TLS secret for this ingress record using self-signed certificates generated by Helm + middlewares: [] + ## Frontend TLS of the IngressRoute (between Traefik and the clients). The + ## backend TLS to the CodX service is controlled by the `tls` section. ## - selfSigned: false - ## @param ingress.extraHosts An array with additional hostname(s) to be covered with the ingress record + tls: + ## @param ingress.tls.enabled Enable TLS on the IngressRoute + ## + enabled: false + ## @param ingress.tls.secretName Name of the Secret holding the TLS certificate (in the release namespace). Optional: when empty, no secretName is set on the IngressRoute (use certResolver or a TLSStore instead). + ## + secretName: "" + ## @param ingress.tls.certResolver Traefik certificate resolver (ACME) used to generate the certificate, e.g. letsencrypt + ## + certResolver: "" + ## TLSOption applied to the route. Defaults to the chart client-mTLS + ## TLSOption when traefikMtls.enabled. + ## + options: + ## @param ingress.tls.options.name Name of the TLSOption applied to the route + ## + name: "" + ## @param ingress.tls.options.namespace Namespace of the TLSOption. Defaults to the release namespace. + ## + namespace: "" + ## @param ingress.extraHosts An array with additional hostname(s) to be covered with the IngressRoute record ## e.g: ## extraHosts: ## - name: codx.local ## path: / ## extraHosts: [] - ## @param ingress.extraPaths An array with additional arbitrary paths that may need to be added to the ingress under the main host - ## e.g: - ## extraPaths: - ## - path: /* - ## backend: - ## serviceName: ssl-redirect - ## servicePort: use-annotation - ## - extraPaths: [] - ## @param ingress.extraTls TLS configuration for additional hostname(s) to be covered with this ingress record - ## ref: https://kubernetes.io/docs/concepts/services-networking/ingress/#tls - ## e.g: - ## extraTls: - ## - hosts: - ## - codx.local - ## secretName: codx.local-tls - ## - extraTls: [] - ## @param ingress.secrets Custom TLS certificates as secrets - ## NOTE: 'key' and 'certificate' are expected in PEM format - ## NOTE: 'name' should line up with a 'secretName' set further up - ## If it is not set and you're using cert-manager, this is unneeded, as it will create a secret for you with valid certificates - ## If it is not set and you're NOT using cert-manager either, self-signed certificates will be created valid for 365 days - ## It is also possible to create and manage the certificates outside of this helm chart - ## Please see README.md for more information - ## e.g: - ## secrets: - ## - name: codx.local-tls - ## key: |- - ## -----BEGIN RSA PRIVATE KEY----- - ## ... - ## -----END RSA PRIVATE KEY----- - ## certificate: |- - ## -----BEGIN CERTIFICATE----- - ## ... - ## -----END CERTIFICATE----- - ## - secrets: [] - ## @param ingress.extraRules Additional rules to be covered with this ingress record - ## ref: https://kubernetes.io/docs/concepts/services-networking/ingress/#ingress-rules - ## e.g: - ## extraRules: - ## - host: example.local - ## http: - ## path: / - ## backend: - ## service: - ## name: example-svc - ## port: - ## name: http - ## - extraRules: [] ## @section RBAC parameter ## Specifies whether a ServiceAccount should be created @@ -787,11 +744,11 @@ authFallback: ## route, and a passTLSClientCert Middleware forwarding the certificate ## information to CodX in the X-Forwarded-Tls-Client-Cert-Info header. ## Requires the Traefik CRDs (Middleware, TLSOption) and the ingress to be -## managed by Traefik (ingress.ingressControllerType=traefik). All objects are -## created in the release namespace. When enabled, the chart also sets the -## traefik.ingress.kubernetes.io/router.tls.options annotation on the Ingress -## (unless already set) and appends the Middleware to -## traefik.ingress.kubernetes.io/router.middlewares. +## exposed with the chart IngressRoute (ingress.enabled, Traefik CRD +## provider). All objects are created in the release namespace. When enabled, +## the chart also appends the Middleware to the IngressRoute routes (after +## ingress.middlewares) and applies the TLSOption on the IngressRoute +## (ingress.tls.options, requires ingress.tls.enabled) unless already set. ## @param traefikMtls.enabled Create the client CA Secret, TLSOption and passTLSClientCert Middleware. ## @param traefikMtls.apiVersion API version of the Traefik CRDs: traefik.io/v1alpha1 (Traefik v3, default) or traefik.containo.us/v1alpha1 (Traefik v2). ## @param traefikMtls.existingCaSecret Existing secret with the client CA certificate (key ca.crt), e.g. the chart internal CA "-ca-crt". When empty, a Secret is created from traefikMtls.caCertificates. diff --git a/docs/authentication.md b/docs/authentication.md index 81648a1..821268f 100644 --- a/docs/authentication.md +++ b/docs/authentication.md @@ -237,24 +237,25 @@ traefikMtls: | `TLSOption` | `-client-mtls` | Requests and validates the client certificate on the ingress route (`spec.clientAuth`). | | `Middleware` | `-passtlsclientcert` | Forwards the certificate CN to CodX in `X-Forwarded-Tls-Client-Cert-Info` (`passTLSClientCert`, `pem: false`). | -When `traefikMtls.enabled` is true and `ingress.ingressControllerType` is -`traefik`, the chart also wires the ingress annotations automatically: - -- appends `-passtlsclientcert-@kubernetescrd` to - `traefik.ingress.kubernetes.io/router.middlewares` (your existing list is - preserved); -- sets `traefik.ingress.kubernetes.io/router.tls.options` to - `-client-mtls@kubernetescrd`, unless you set it yourself. - -Extra middlewares can be added through the `ingress.extraMiddlewares` -list: they are appended to `router.middlewares` after any user-defined -middlewares (from `ingress.annotations`) and the chart -`passTLSClientCert` middleware. Entries are middleware references; the -`@kubernetescrd` provider suffix is added when missing. - -Both objects are created in the same namespace as the Ingress, so Traefik -resolves the plain-name references. When enabled, the render fails with an -explicit error if neither `existingCaSecret` nor `caCertificates` is set. +When `traefikMtls.enabled` is true, the chart also wires the IngressRoute +automatically: + +- appends the `-passtlsclientcert` Middleware to the route + middlewares, after the middlewares from `ingress.middlewares`; +- applies the `-client-mtls` TLSOption on the IngressRoute + (`ingress.tls.options`, requires `ingress.tls.enabled`), unless you set it + yourself. + +Middlewares can be added through the `ingress.middlewares` list: each entry +is a middleware `name` with an optional `namespace` (defaults to the +release namespace; cross-namespace references require the Traefik +`allowCrossNamespace` option). They run before the chart +`passTLSClientCert` middleware. + +Both objects are created in the same namespace as the IngressRoute, so +Traefik resolves the plain-name references. When enabled, the render fails +with an explicit error if neither `existingCaSecret` nor `caCertificates` +is set. ### Choosing the client CA diff --git a/go.mod b/go.mod index 34ef09e..948f12b 100644 --- a/go.mod +++ b/go.mod @@ -1,26 +1,26 @@ module github.com/captnbp/CodX -go 1.27.1 +go 1.27.3 require ( - github.com/cert-manager/cert-manager v1.21.2 + github.com/cert-manager/cert-manager v1.21.3 github.com/coreos/go-oidc/v3 v3.21.0 - github.com/go-jose/go-jose/v4 v4.1.4 + github.com/go-jose/go-jose/v4 v4.1.7 github.com/go-logr/logr v1.4.4 github.com/go-logr/zerologr v1.2.3 github.com/google/go-cmp v0.7.0 - github.com/prometheus/client_golang v1.24.1 - github.com/redis/go-redis/v9 v9.22.0 + github.com/prometheus/client_golang v1.25.0 + github.com/redis/go-redis/v9 v9.23.0 github.com/rs/zerolog v1.35.1 - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.71.0 - go.opentelemetry.io/otel v1.46.0 - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.46.0 - go.opentelemetry.io/otel/sdk v1.46.0 + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.72.0 + go.opentelemetry.io/otel v1.47.0 + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.47.0 + go.opentelemetry.io/otel/sdk v1.47.0 golang.org/x/oauth2 v0.37.0 gopkg.in/yaml.v3 v3.0.1 - k8s.io/api v0.37.0 - k8s.io/apimachinery v0.37.0 - k8s.io/client-go v0.37.0 + k8s.io/api v0.37.2 + k8s.io/apimachinery v0.37.2 + k8s.io/client-go v0.37.2 ) require ( @@ -30,61 +30,63 @@ require ( github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/emicklei/go-restful/v3 v3.13.0 // indirect github.com/felixge/httpsnoop v1.1.0 // indirect - github.com/fxamacker/cbor/v2 v2.9.1 // indirect + github.com/fxamacker/cbor/v2 v2.10.0 // indirect github.com/go-logr/stdr v1.2.2 // indirect - github.com/go-openapi/jsonpointer v1.0.0 // indirect - github.com/go-openapi/jsonreference v1.0.0 // indirect - github.com/go-openapi/swag v0.28.0 // indirect - github.com/go-openapi/swag/cmdutils v0.28.0 // indirect - github.com/go-openapi/swag/conv v0.28.0 // indirect - github.com/go-openapi/swag/fileutils v0.28.0 // indirect - github.com/go-openapi/swag/jsonutils v0.28.0 // indirect - github.com/go-openapi/swag/loading v0.28.0 // indirect - github.com/go-openapi/swag/mangling v0.28.0 // indirect - github.com/go-openapi/swag/netutils v0.28.0 // indirect - github.com/go-openapi/swag/pools v0.28.0 // indirect - github.com/go-openapi/swag/stringutils v0.28.0 // indirect - github.com/go-openapi/swag/typeutils v0.28.0 // indirect - github.com/go-openapi/swag/yamlutils v0.28.0 // indirect + github.com/go-openapi/jsonpointer v1.0.2 // indirect + github.com/go-openapi/jsonreference v1.0.3 // indirect + github.com/go-openapi/swag v0.29.2 // indirect + github.com/go-openapi/swag/cmdutils v0.29.2 // indirect + github.com/go-openapi/swag/conv v0.29.2 // indirect + github.com/go-openapi/swag/fileutils v0.29.2 // indirect + github.com/go-openapi/swag/jsonutils v0.29.2 // indirect + github.com/go-openapi/swag/loading v0.29.2 // indirect + github.com/go-openapi/swag/mangling v0.29.2 // indirect + github.com/go-openapi/swag/netutils v0.29.2 // indirect + github.com/go-openapi/swag/pools v0.29.2 // indirect + github.com/go-openapi/swag/stringutils v0.29.2 // indirect + github.com/go-openapi/swag/typeutils v0.29.2 // indirect + github.com/go-openapi/swag/yamlutils v0.29.2 // indirect github.com/google/gnostic-models v0.7.1 // indirect github.com/google/uuid v1.6.0 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.30.0 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.33.0 // indirect github.com/json-iterator/go v1.1.12 // indirect - github.com/mattn/go-colorable v0.1.14 // indirect - github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mattn/go-colorable v0.1.16 // indirect + github.com/mattn/go-isatty v0.0.24 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/pandatix/go-abnf v0.5.5 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect - github.com/prometheus/client_model v0.6.2 // indirect - github.com/prometheus/common v0.70.1 // indirect - github.com/prometheus/procfs v0.21.1 // indirect + github.com/prometheus/client_model v0.6.3 // indirect + github.com/prometheus/common v0.73.1 // indirect + github.com/prometheus/procfs v0.23.0 // indirect github.com/x448/float16 v0.8.4 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.46.0 // indirect - go.opentelemetry.io/otel/metric v1.46.0 // indirect - go.opentelemetry.io/otel/trace v1.46.0 // indirect - go.opentelemetry.io/proto/otlp v1.11.0 // indirect - go.uber.org/atomic v1.11.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.47.0 // indirect + go.opentelemetry.io/otel/log v1.47.0 // indirect + go.opentelemetry.io/otel/metric v1.47.0 // indirect + go.opentelemetry.io/otel/trace v1.47.0 // indirect + go.opentelemetry.io/proto/otlp v1.11.1 // indirect + go.uber.org/atomic v1.12.0 // indirect go.yaml.in/yaml/v2 v2.4.4 // indirect - go.yaml.in/yaml/v3 v3.0.5 // indirect - golang.org/x/net v0.60.0 // indirect - golang.org/x/sys v0.48.0 // indirect - golang.org/x/term v0.46.0 // indirect - golang.org/x/text v0.42.0 // indirect - golang.org/x/time v0.15.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688 // indirect - google.golang.org/grpc v1.83.2 // indirect + go.yaml.in/yaml/v3 v3.0.6 // indirect + golang.org/x/net v0.62.0 // indirect + golang.org/x/sys v0.49.0 // indirect + golang.org/x/term v0.47.0 // indirect + golang.org/x/text v0.43.0 // indirect + golang.org/x/time v0.16.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20261026165753-2ffab32bce7e // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20261026165753-2ffab32bce7e // indirect + google.golang.org/grpc v1.85.0 // indirect google.golang.org/protobuf v1.36.12 // indirect gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect - k8s.io/apiextensions-apiserver v0.37.0 // indirect + k8s.io/apiextensions-apiserver v0.37.2 // indirect k8s.io/klog/v2 v2.140.0 // indirect k8s.io/kube-openapi v0.0.0-20260721132016-d427ff9ee9ad // indirect - k8s.io/utils v0.0.0-20260626114624-be93311217bd // indirect - sigs.k8s.io/gateway-api v1.6.0 // indirect - sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect + k8s.io/utils v0.0.0-20261023123148-ca04ba58ecab // indirect + sigs.k8s.io/gateway-api v1.7.0 // indirect + sigs.k8s.io/json v0.0.0-20260909141634-11ed52e25bc5 // indirect sigs.k8s.io/randfill v1.0.0 // indirect sigs.k8s.io/structured-merge-diff/v6 v6.4.2 // indirect sigs.k8s.io/yaml v1.6.0 // indirect diff --git a/go.sum b/go.sum index 92e1f61..a2b0193 100644 --- a/go.sum +++ b/go.sum @@ -6,8 +6,8 @@ github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA= github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0= github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM= github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw= -github.com/cert-manager/cert-manager v1.21.2 h1:UWgoYM+KLNtRlqmoWOKjKBOBUPVHeepQ6/ac3ERJW68= -github.com/cert-manager/cert-manager v1.21.2/go.mod h1:cUXqp2Ki46IovJB+XpkfaFNzitQDJjyfjrCAsl5Jnns= +github.com/cert-manager/cert-manager v1.21.3 h1:s5xNJPfWSiBWLAxMmXw8runfcXMVVWM4kcMpZ1OODbY= +github.com/cert-manager/cert-manager v1.21.3/go.mod h1:Y0beWPQTPpBNIdxwDX2EAL24ONep1H4jGOmSKPvoa0I= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/coreos/go-oidc/v3 v3.21.0 h1:wZo4Q9Pum8dYEj0eMUPrqR+kvuGkeUplbLpNCkBqoWM= @@ -20,10 +20,10 @@ github.com/emicklei/go-restful/v3 v3.13.0 h1:C4Bl2xDndpU6nJ4bc1jXd+uTmYPVUwkD6bF github.com/emicklei/go-restful/v3 v3.13.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/felixge/httpsnoop v1.1.0 h1:3YtUj32ZZkqZtt3sZZsClsymw/QDuVfpNhoA31zeORc= github.com/felixge/httpsnoop v1.1.0/go.mod h1:Zqxgdd+1Rkcz8euOqdr7lqgCRJztwr5hp9vDSi5UZCE= -github.com/fxamacker/cbor/v2 v2.9.1 h1:2rWm8B193Ll4VdjsJY28jxs70IdDsHRWgQYAI80+rMQ= -github.com/fxamacker/cbor/v2 v2.9.1/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= -github.com/go-jose/go-jose/v4 v4.1.4 h1:moDMcTHmvE6Groj34emNPLs/qtYXRVcd6S7NHbHz3kA= -github.com/go-jose/go-jose/v4 v4.1.4/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08= +github.com/fxamacker/cbor/v2 v2.10.0 h1:HVF3T5vYwLqO81ebPirWEPNv2SMrUMD3HLnKgEr6uWE= +github.com/fxamacker/cbor/v2 v2.10.0/go.mod h1:uSwlToFuT9BZAKRxxVm3qQC8DR4tlWl7D6pf20iRYH4= +github.com/go-jose/go-jose/v4 v4.1.7 h1:Nac/98uT5LPkfbiFuYa8ktf62a2VxwQ4xbq1GK8pV5E= +github.com/go-jose/go-jose/v4 v4.1.7/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.4 h1:tG4xh9yMsRCAiodLVTxyrkzSZ9+o0L1Kg/+cPVcbP/8= github.com/go-logr/logr v1.4.4/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= @@ -31,40 +31,40 @@ github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-logr/zerologr v1.2.3 h1:up5N9vcH9Xck3jJkXzgyOxozT14R47IyDODz8LM1KSs= github.com/go-logr/zerologr v1.2.3/go.mod h1:BxwGo7y5zgSHYR1BjbnHPyF/5ZjVKfKxAZANVu6E8Ho= -github.com/go-openapi/jsonpointer v1.0.0 h1:kR9tHqY0CtZaOPVFm622dPVNhrvYpwr4uCxgL3h1H8s= -github.com/go-openapi/jsonpointer v1.0.0/go.mod h1:Z3rw7dWu1p9IgitXCFamSlA5lmDiklEB6vkaxcNZW5Y= -github.com/go-openapi/jsonreference v1.0.0 h1:jlmTr6torcd1YgDQvSfNmRtKzYDO4FGBkrAdlAVWnpY= -github.com/go-openapi/jsonreference v1.0.0/go.mod h1:jtwdyGbJk0Xhe5Y+rwtglQP6Sb1WZST4rT32LWB+sv0= -github.com/go-openapi/swag v0.28.0 h1:xkgbOSKj6DZziNpyqRRAOt3GJGtgjgsd2RoyT30VWuw= -github.com/go-openapi/swag v0.28.0/go.mod h1:4qYnT3Cqr1p1VknOdPo70evN4rgQnAg6jwApHyxSGIg= -github.com/go-openapi/swag/cmdutils v0.28.0 h1:7TOeNtkYru1SG8Y34tDh9WBbLsMqGnptuxWiHREPZ4Q= -github.com/go-openapi/swag/cmdutils v0.28.0/go.mod h1:Sm1MVFMkF6guJJ+pQqHnQA3N0j9qALV3NxzDSv6bETM= -github.com/go-openapi/swag/conv v0.28.0 h1:GtqqbyFe7vR5Y7ehxG9W6/OvrSFdf1OLeTGp40TqxH8= -github.com/go-openapi/swag/conv v0.28.0/go.mod h1:mbUE+mzctnhxi864m0Q07SpN8OowD9JhxmxuYvZZD/k= -github.com/go-openapi/swag/fileutils v0.28.0 h1:Z04XWQD7R8Eq+7GnOrjovBxPPmZzsS4gt2H2GPGIViU= -github.com/go-openapi/swag/fileutils v0.28.0/go.mod h1:VvJFZLTZS0AI854gEQz5tk7dBESdLjiNUMSZ/th2ry8= -github.com/go-openapi/swag/jsonutils v0.28.0 h1:YIch6FwO7RXzeAnbO8Tu7dWBZeUEH+4nA0HXltVTnv4= -github.com/go-openapi/swag/jsonutils v0.28.0/go.mod h1:CYM3WlTUcagR2ZoHdz54di/cbBqt82tuxuXgAjxw+mg= -github.com/go-openapi/swag/jsonutils/fixtures_test v0.28.0 h1:qV+VVUAx5Oro8WjVWpZeql7YReTKhT4smR4zhcOQZr0= -github.com/go-openapi/swag/jsonutils/fixtures_test v0.28.0/go.mod h1:mofwUWx70wvskwESqRJ//k/9kURmCgyJl5m5Ppoh5kY= -github.com/go-openapi/swag/loading v0.28.0 h1:td8QZdZC9MIYGGSnSPKShKiK22I2tU5UQvuUhIBPRLU= -github.com/go-openapi/swag/loading v0.28.0/go.mod h1:rXB0QiQX5mMveXEA7ouM4KiiM9jVJe4K6BVbwhD1M4k= -github.com/go-openapi/swag/mangling v0.28.0 h1:pH8eyeNO9SLYsTMWJrurnNfKmDa28XrlA+HePVD53VM= -github.com/go-openapi/swag/mangling v0.28.0/go.mod h1:jtBE2+V+3pILxOR7Vgce+Cwp6A2PgZbvVqfNntbVs0w= -github.com/go-openapi/swag/netutils v0.28.0 h1:YXN6TALEi2pzts8/8GNm6T61HTAZsieukGZidap989k= -github.com/go-openapi/swag/netutils v0.28.0/go.mod h1:J+WYyFMLtvtCGqa6jLv+YNUmIKI3ZRQRrvfNDMoQoEQ= -github.com/go-openapi/swag/pools v0.28.0 h1:HPMZWSAfce3rdVTFcjFiCIBtDg9h4x2QlRrHipwhxeU= -github.com/go-openapi/swag/pools v0.28.0/go.mod h1:kVQefhSK5RWuRe7BXsL8htgBPAMpN7HDGpGEknqugeE= -github.com/go-openapi/swag/stringutils v0.28.0 h1:ixsc9iYgDPubHL/8nSkbnryEHpD2VRlBMLKpQyPXcDU= -github.com/go-openapi/swag/stringutils v0.28.0/go.mod h1:lzRN95CxXmA03XcDWHLOb6nOMcxCqR5rGY0lOgsfRoM= -github.com/go-openapi/swag/typeutils v0.28.0 h1:nRBKSBXjDgf01VDPB3fWeD9nQuhCOVeIYAkUx2tbkyY= -github.com/go-openapi/swag/typeutils v0.28.0/go.mod h1:Srm0xFNRZ1Y+vCxJclo5qzx8aj+1pAKda/YfFPrG0dQ= -github.com/go-openapi/swag/yamlutils v0.28.0 h1:TV3JXH6DS46KUroDtMLAYHGkdWf5VDq3wVWFirmzROY= -github.com/go-openapi/swag/yamlutils v0.28.0/go.mod h1:x0q/yndZHEgk9Rx3DyDqzFUmHy55KTvIZldvF2dTJXs= -github.com/go-openapi/testify/enable/yaml/v2 v2.6.0 h1:gGHwAJ0R/5jU8BEGDbfRNR3hL68dAVi84WuOApp29B0= -github.com/go-openapi/testify/enable/yaml/v2 v2.6.0/go.mod h1:tY+St1SGq4NFl0QIqdTY4aEdbChAHxhyB77XQi9iJCo= -github.com/go-openapi/testify/v2 v2.6.0 h1:5PKH2HE7YJ/LuRPQGvSxBRlFXNQhSetBLlGAgUEu3ug= -github.com/go-openapi/testify/v2 v2.6.0/go.mod h1:SgsVHtfooshd0tublTtJ50FPKhujf47YRqauXXOUxfw= +github.com/go-openapi/jsonpointer v1.0.2 h1:NbW7xT2b+l7x+JefiPGDsZWsrLk1ogrz4AHK1TTzMt8= +github.com/go-openapi/jsonpointer v1.0.2/go.mod h1:N0rI4MbNKD7xAFTiHtB+5Nhrg2j/wwYg7m7Fmb4/jk8= +github.com/go-openapi/jsonreference v1.0.3 h1:5rDbq+JsjCv6/nUDDJt83nLZoADI4w03uvHXer+4aYQ= +github.com/go-openapi/jsonreference v1.0.3/go.mod h1:OJSlMXHysgH272D9z2tiy2MGe5A9vQLHaCPb6KbYdE4= +github.com/go-openapi/swag v0.29.2 h1:9n8frkcsuQRA0INU31VbxNxt1zBaHxrHYGEf5BUjFi4= +github.com/go-openapi/swag v0.29.2/go.mod h1:RkCiX1gCVXWgLOavDXE3ALJrQ/VO2ziiIzOUs3z10xo= +github.com/go-openapi/swag/cmdutils v0.29.2 h1:cXEzX/nWCODon251f+1HgL75/R4XXML5IZxgLCWMoro= +github.com/go-openapi/swag/cmdutils v0.29.2/go.mod h1:XziaSVzYqkDvpWXzASfWajw2YvJrLscq610O2XiyqJw= +github.com/go-openapi/swag/conv v0.29.2 h1:8c9shoB8l0QRSR6ymq1llHdBWDqpIgJfjTGHx3Vuhm0= +github.com/go-openapi/swag/conv v0.29.2/go.mod h1:AZS0YigTNf8qNtD6WqJz/N4RUNmpr76SyjFGkq4+p6Q= +github.com/go-openapi/swag/fileutils v0.29.2 h1:mdUL+Vw5ah1fO1AvFCoHeIyv7YZsCz2MN3KNqd/lLVI= +github.com/go-openapi/swag/fileutils v0.29.2/go.mod h1:7QKmmodjAebaq61lGVJa8ldLdGeMF4DSgYv8tXOGGo0= +github.com/go-openapi/swag/jsonutils v0.29.2 h1:uZNSD2/rJDYAfvsLYkykTzXuyxM3QpDKV9jhRHrMu6k= +github.com/go-openapi/swag/jsonutils v0.29.2/go.mod h1:ONTdNvj3Y+IXRzfa17E+Rgt2ns/qiSOYjIo2K7v2yDs= +github.com/go-openapi/swag/jsonutils/fixtures_test v0.29.2 h1:w+Fd6EBOMGlC74GYGHqCLGyb3Bpams8TtNrgM/SG4jo= +github.com/go-openapi/swag/jsonutils/fixtures_test v0.29.2/go.mod h1:HC2egPORFzbj/gf05Zrfgz8Mgplx3oW7hgGi7pjZVhM= +github.com/go-openapi/swag/loading v0.29.2 h1:QU1ry24e6r6Shoxe380Nx0X5iKkpRO+hJffsyJ2dVlY= +github.com/go-openapi/swag/loading v0.29.2/go.mod h1:DqilDjuiJKETecsS4TRopWG1acUDsfqN39ZmQhPu6uI= +github.com/go-openapi/swag/mangling v0.29.2 h1:ltdo3K0tTP4P6zWhVVWDU9D37/YYBuMJRdZxiS2Dtog= +github.com/go-openapi/swag/mangling v0.29.2/go.mod h1:RjDi4TnItAenS3cgCXSpIl1kKbjG7il6pnmQVl/8bp8= +github.com/go-openapi/swag/netutils v0.29.2 h1:dBli6jyUa93sDS/XYWShJzf4+nnVfFKfvY9SLUaUzB8= +github.com/go-openapi/swag/netutils v0.29.2/go.mod h1:1pM6Xg/Um8E1eps3umWl8Hme3ByWXOtHsvFC0CULHUE= +github.com/go-openapi/swag/pools v0.29.2 h1:PlGoDRF8WtSyXkedZZkpW3f9wDhFf3u8KtaBcmgmh8c= +github.com/go-openapi/swag/pools v0.29.2/go.mod h1:V3lhxVT4qDYRqc2MRSSbLsTYIYV/2HlHafhyEkmzLIc= +github.com/go-openapi/swag/stringutils v0.29.2 h1:lcnBxwAaysT3bMUVBfn9V/PkfVqkurpqufKU6rTUMGk= +github.com/go-openapi/swag/stringutils v0.29.2/go.mod h1:i9cdh7sGaa0nm3CqIvH5IM5h2QClk1wns2ktKeILvRI= +github.com/go-openapi/swag/typeutils v0.29.2 h1:O7aVvkTs3pXwikgtrPLenigEMdQFgONKRooQA9pgf2I= +github.com/go-openapi/swag/typeutils v0.29.2/go.mod h1:7+GDG+uz9Ke+eVt4rClZg7wklSDp8hT/mKNh/pC26TE= +github.com/go-openapi/swag/yamlutils v0.29.2 h1:IFKFFeDnuIwzfsuWRQU+rI8iL3g4XyAYjV/RlnlxPLM= +github.com/go-openapi/swag/yamlutils v0.29.2/go.mod h1:7MGqtcrK73sxQ4ceiyIf8qiXS/s09JLMdR5esK5euYQ= +github.com/go-openapi/testify/enable/yaml/v2 v2.7.0 h1:wPW6YRgx3+SID1yUy/Xwa17L8kFEaEKod2VRbJDZNUs= +github.com/go-openapi/testify/enable/yaml/v2 v2.7.0/go.mod h1:mI1M88etYbc3PhgHsWQK2kwvNwW5aGFqMPbmib+SGIs= +github.com/go-openapi/testify/v2 v2.8.0 h1:19QDx5b57p8KjO2E4sgs/woL4Akp+FGLRlVm2xn0Rho= +github.com/go-openapi/testify/v2 v2.8.0/go.mod h1:4gUN8jC+RqOE2qjW/vDmfyTvl5BCht4qL7oca0odrak= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/gnostic-models v0.7.1 h1:SisTfuFKJSKM5CPZkffwi6coztzzeYUhc3v4yxLWH8c= @@ -74,24 +74,24 @@ github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.30.0 h1:/Tnpcb2E0Pz/tN9s3bfEY2Q8ePCEX9iuS+cneUwncnw= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.30.0/go.mod h1:zOBXOsUaBSjKgmH4OGzV1esUpR3oUSCPYVd2cUBjKYY= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.33.0 h1:5T5Ns3kMpWr+z3s5ZA0e7ljVyWNVNxK76sHPAxi5D1k= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.33.0/go.mod h1:oWNYw09cH8wl8fFgC5hKlBLxR4XfKy2KQpP8MJjeCcc= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= -github.com/klauspost/compress v1.19.1 h1:VsB4HPswih7mmZ8WleSFQ75c/Ui1M4trX5oAsJnhSlk= -github.com/klauspost/compress v1.19.1/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ= -github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE= -github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= +github.com/klauspost/compress v1.20.1 h1:T7kKElXUMXrUJ2E9QhQhxFtcK5rPyLdsGZvdbLMPdiQ= +github.com/klauspost/compress v1.20.1/go.mod h1:LUdAzn7YLVvxLpc7y3V1m40wESHTgc1422pwwBSKYuI= +github.com/klauspost/cpuid/v2 v2.4.0 h1:S6Hrbc7+ywsr0r+RLapfGBHfyefhCTwEh3A0tV913Dw= +github.com/klauspost/cpuid/v2 v2.4.0/go.mod h1:19jmZ9mjzoF//ddRSUsv0zfBTJWh3QJh9FNxZTMrGxU= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= -github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= -github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= -github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= -github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-colorable v0.1.16 h1:+2YjeAe7xP1OQbZW1jERPWWCDLlNqFNCvjiqGiAt84A= +github.com/mattn/go-colorable v0.1.16/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= +github.com/mattn/go-isatty v0.0.24 h1:tGZZoVgT/KiqK1c8ocVLeDS8BSWMRd47J3Lbz7vsReI= +github.com/mattn/go-isatty v0.0.24/go.mod h1:nMCL3Zebbrt45jsMDgnfIwz6ydEQApk5oEI3HqDio6A= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -100,19 +100,21 @@ github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFd github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/pandatix/go-abnf v0.5.5 h1:EXex5KkRQx4Dm6GnpAcQnIMBs9KiK0uf8p/wq+4t9PM= +github.com/pandatix/go-abnf v0.5.5/go.mod h1:AE0KD3/VNoCjGzc1gqetRKshbI4SYErXQ/8kPenR5mc= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prometheus/client_golang v1.24.1 h1:JnJkREXzWxUdCuPFpIWZiPispT9xVV59uiuyR2bPlnU= -github.com/prometheus/client_golang v1.24.1/go.mod h1:F+oSRECHg4sse5ucfYpYDeIv/hu68Zo0uoHKetWnzcE= -github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= -github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= -github.com/prometheus/common v0.70.1 h1:1HvjP4D5oL3t8RsPlwxA9onvvStjtIHYE5XuuwOi/PY= -github.com/prometheus/common v0.70.1/go.mod h1:VdFUQDMZK3VLkurFUVhia6uys/0suUp86TJz5qbJRhc= -github.com/prometheus/procfs v0.21.1 h1:GljZCt+zSTS+NZq88cyQ1LjZ+RCHp3uVuabBWA5+OJI= -github.com/prometheus/procfs v0.21.1/go.mod h1:aB55Cww9pdSJVHk0hUf0inxWyyjPogFIjmHKYgMKmtY= -github.com/redis/go-redis/v9 v9.22.0 h1:laDvpYXTJtZLloinw1fA5Kqd6HAEH2XKxOkG/PDq2F0= -github.com/redis/go-redis/v9 v9.22.0/go.mod h1:y2g0Wj8rQvuK0ELM+oxSudcLtC09JScs98I/X9gRWY4= +github.com/prometheus/client_golang v1.25.0 h1:cDtW1TcFsMi1RJc8khwCKB4VhzwV04aKqbYtNOXuiK0= +github.com/prometheus/client_golang v1.25.0/go.mod h1:j2X/4/kaQHLPnmyEU8zjmkndo1T317VpCIQEml8LTKk= +github.com/prometheus/client_model v0.6.3 h1:O0jaTVAYNxTHYInEPFJt5I3+sN8zqBtVMPTB1qyxiEo= +github.com/prometheus/client_model v0.6.3/go.mod h1:gpN5P9S7Rr6Yr92PiQ+Ixvhf6JZEkF1dnxsYL2aPBEM= +github.com/prometheus/common v0.73.1 h1:SZWh/j4AvhCdhX8jvYSnxvPxhWlC9QZkjJ+tOrEr7kU= +github.com/prometheus/common v0.73.1/go.mod h1:I9aA/Ukz9s1olOobh8KtCkU7B1mQPQsB9UvQPsUQ1Dk= +github.com/prometheus/procfs v0.23.0 h1:ad+S6FTkzwaMgacKLM5h6bNKExS+DMH0YiUwXItH/o4= +github.com/prometheus/procfs v0.23.0/go.mod h1:3AVXO0FqLafyYFU/V/IQRZlC8Rgqz8Dm/x6pKLK+RIk= +github.com/redis/go-redis/v9 v9.23.0 h1:/TTVdJa8BRNsybwwGGnmiYktHNlUXtbOQxlWpHBPL3s= +github.com/redis/go-redis/v9 v9.23.0/go.mod h1:EWP2UUk+XDaI1s5nQB5mablZN6vccXlmBQTyfnF87D0= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/rs/zerolog v1.35.1 h1:m7xQeoiLIiV0BCEY4Hs+j2NG4Gp2o2KPKmhnnLiazKI= @@ -131,53 +133,54 @@ github.com/zeebo/xxh3 v1.1.0 h1:s7DLGDK45Dyfg7++yxI0khrfwq9661w9EN78eP/UZVs= github.com/zeebo/xxh3 v1.1.0/go.mod h1:IisAie1LELR4xhVinxWS5+zf1lA4p0MW4T+w+W07F5s= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.71.0 h1:3g7B90UzBltIDKq1/5mrTGxTnOFDV0ICOhLoxiZ8jlg= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.71.0/go.mod h1:Ef8SuTh59BT7+ofpDxN9z+yOlc4t2GjLmKDgYNJL/NU= -go.opentelemetry.io/otel v1.46.0 h1:FHt5/CDyVxi/8IM1CH7VE/rRgq3kLHa2mSTVMO8AWyc= -go.opentelemetry.io/otel v1.46.0/go.mod h1:Gj3SEScelsNC45tp4nSxRYlS+f5iez7W8XPMCt905kE= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.46.0 h1:OFnwLJr+pF3iHrlGSzbxyuo6/6HyBlnlN1CWEJmBVcw= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.46.0/go.mod h1:716wFneO0ov19A2beH5hjfh9AK5z/VWNAtDijp1Y0/g= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.46.0 h1:KrC1YrQeSt46ITMWAbgQx1M1eV1/1TKzttrBzymPmss= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.46.0/go.mod h1:zDSEzoEqsOrgBeGvH66KRgxh90VonFyJqBHA0Pk3+rM= -go.opentelemetry.io/otel/metric v1.46.0 h1:yBnkXvgV7AXFILZc5K6IZe/CBFF3OS7BJ8ov6/lj0K8= -go.opentelemetry.io/otel/metric v1.46.0/go.mod h1:iPmdWqifKUdzziPkvvzIJXITl56fQx2mGM/DHLB3/2o= -go.opentelemetry.io/otel/sdk v1.46.0 h1:h5CNQQjEbuQXY/JfZtgt3i7HVFV3aHPO2OAwO2eTYPI= -go.opentelemetry.io/otel/sdk v1.46.0/go.mod h1:GAERFXFt5SYCEB+YiKUbMBeza6UaDH7GmGOZEfh2gSM= -go.opentelemetry.io/otel/sdk/metric v1.46.0 h1:0piZ26EG4RBfebb2jhDH6ERCYHoVWduc3kLgPCwSnSE= -go.opentelemetry.io/otel/sdk/metric v1.46.0/go.mod h1:I1PbKrdVc8Qu8HYVDNtqVIwLwjNrhsV/uFuxfwg8mO4= -go.opentelemetry.io/otel/trace v1.46.0 h1:OULy7ccdJnZtJ0UDYFOIGaCmiWzJ8Vi2G/Rsu60qs1c= -go.opentelemetry.io/otel/trace v1.46.0/go.mod h1:J7GAXweO77XSFkB/rmAqk9D6ihszhFjLU+d9WuUxDLI= -go.opentelemetry.io/proto/otlp v1.11.0 h1:5rrYs0Ykyj50sdU/JU0x8etU+LubXWb+gED6TbEdMIk= -go.opentelemetry.io/proto/otlp v1.11.0/go.mod h1:SmVizdCOAm3XBtG1g1NnOdhW6jtddT72hLMhv8VwA8E= -go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= -go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.72.0 h1:LxwW/9ctSCv+QkE/cLR7M91ZIkXNMqJtEMi1vCw9U8s= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.72.0/go.mod h1:tOsftB4SslBwwErVEPaenU2RpThXWPIU8DoJHEC4dyw= +go.opentelemetry.io/otel v1.47.0 h1:j7ALJ/zgkS7Z6aeJW09p8VC9804bC+PpeTfCD4XPnOM= +go.opentelemetry.io/otel v1.47.0/go.mod h1:8wS9O2qfXrYrzp6hIF/HOYJJf/wIhFPhR2xLuP+iXQU= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.47.0 h1:julhjPeUH/q/7hinbSdDdqt5h7Zw9YWmRlWRhI0jd54= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.47.0/go.mod h1:Ao2mz688LH/tFf0yMAenidq6k2YNSx6SIY2q6jDACck= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.47.0 h1:aXZXsZ012wOgVkqaiQv+Z/d8V0xKjmg70F6m4CE94lc= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.47.0/go.mod h1:ziu1gUIJhAaxM3EV1e3Ralk5AAyL1UOYz3lJJ4VSI38= +go.opentelemetry.io/otel/log v1.47.0 h1:cOTS1CcLbSQeZKanGJ+0JpF/+t4PELi3O3bbl2lqCcI= +go.opentelemetry.io/otel/log v1.47.0/go.mod h1:9byitSQ5pLC6PpqwGXjqdMKya6ZTswHRZh2vvXT33nw= +go.opentelemetry.io/otel/metric v1.47.0 h1:4PptaldXx3Eat1XjMZ68pPJEs5wrhlemctZE9a3UdWY= +go.opentelemetry.io/otel/metric v1.47.0/go.mod h1:ADGSXxRrXM6bjbvLo535EstVFlPpPYZm4LBKixjDHwU= +go.opentelemetry.io/otel/sdk v1.47.0 h1:zWXEr4j2lFefG87TU6Yg8a7ngfohIKFZHKp0Hf5hC6I= +go.opentelemetry.io/otel/sdk v1.47.0/go.mod h1:VUc24kiOeoGsxG8G9ULx3fWKvB7jMhnGE8Oi607lgR0= +go.opentelemetry.io/otel/sdk/metric v1.47.0 h1:lfISg2j93VT6yqdk9OfUaZmw/GfcZqCCV3jdXtsPnKw= +go.opentelemetry.io/otel/sdk/metric v1.47.0/go.mod h1:ypLp+mW1Nt2x+Szt3b5/i1syodyts49lMOwxpDI3VGw= +go.opentelemetry.io/otel/trace v1.47.0 h1:JOjX/Oci8K94QHddo+bbfya/Ai/nf6/dt9ZfrFNWSrM= +go.opentelemetry.io/otel/trace v1.47.0/go.mod h1:jNaSLa2PZEYFG6fRjJABAu+bw4FS08uDmPg28lTghu0= +go.opentelemetry.io/proto/otlp v1.11.1 h1:SCrPqH9NE5CmBrOqpjNCiT0X5mGwMyGERXdogfR7Yjw= +go.opentelemetry.io/proto/otlp v1.11.1/go.mod h1:wq8W4aSf6bOy8RZyESOeQFr9Pu7wydWX/TY4sgvrTPg= +go.uber.org/atomic v1.12.0 h1:BvcXdFKuviU4fTL/f+SxdQ5qJX/Jix8pAkgdUcb3XOE= +go.uber.org/atomic v1.12.0/go.mod h1:I6c4cg+6HCxRjfjSsYtApoFILnpc0CGUdGkXVqbYVNk= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.yaml.in/yaml/v2 v2.4.4 h1:tuyd0P+2Ont/d6e2rl3be67goVK4R6deVxCUX5vyPaQ= go.yaml.in/yaml/v2 v2.4.4/go.mod h1:gMZqIpDtDqOfM0uNfy0SkpRhvUryYH0Z6wdMYcacYXQ= -go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= -go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= -golang.org/x/net v0.60.0 h1:79p50tfZlm0J9YfoDsSi639qSXNGVwEzOPLCxM2FsYU= -golang.org/x/net v0.60.0/go.mod h1:2DA/G1UfVbCpQPeWTmMPGY7Cs2PkBkwu743bVX5PIVg= +go.yaml.in/yaml/v3 v3.0.6 h1:zcDfvjXEqTQmpjniUBLc91xYJMB2c4QOFxEF/LCEckM= +go.yaml.in/yaml/v3 v3.0.6/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= +golang.org/x/net v0.62.0 h1:18ELalfM5+XNRwzM2nYqXsZQ9ByS1uhj2O/U7+5jqRY= +golang.org/x/net v0.62.0/go.mod h1:WgDCOTH8iwtB66T4/GfI94AHo/hhdb7Hah9NDseQfiI= golang.org/x/oauth2 v0.37.0 h1:JUlcxA8oAtauLfiH8FX2/FkAWHAdi0QtGCGc+hofE98= golang.org/x/oauth2 v0.37.0/go.mod h1:IxwZNxUULJmpBFf9K/9NTMSIfZZuvuTy1gGxhigP/58= -golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.48.0 h1:bbX/i/6MgT9BVLM9RT1thmxL04yeTAhbEz4SyadbXoo= -golang.org/x/sys v0.48.0/go.mod h1:hNLxWAXmnKAxqDtdwIYC4bM9oQPEecfsnNMuSxOs3og= -golang.org/x/term v0.46.0 h1:3+OXuTbaKDgwk8jTi3aSLHRlmWqHEUDUtxnbFigO4YE= -golang.org/x/term v0.46.0/go.mod h1:+K02xbkittuwc0Am4abfA3Fc+XRGXkvBXNO88NCXPoc= -golang.org/x/text v0.42.0 h1:JbOZXgfeCPU9gacVtYliJqOhD+zhrEqK4LfdpmlUZqI= -golang.org/x/text v0.42.0/go.mod h1:ojzP1Z+2QtioaF8DTtO8K5q7JWVVYwZKenzujK0Zd0E= -golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U= -golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno= +golang.org/x/sys v0.49.0 h1:XbzkgYJHdqh/8m2Uu0W/dQv8nktxx4BFHp1M0gROTXA= +golang.org/x/sys v0.49.0/go.mod h1:hNLxWAXmnKAxqDtdwIYC4bM9oQPEecfsnNMuSxOs3og= +golang.org/x/term v0.47.0 h1:cpyOwQkr2Nzxe2p6KnrTG7Ei2wtzjsuSjhpXXepZ/BM= +golang.org/x/term v0.47.0/go.mod h1:efAfAuG6RKABvg1ArGebRkvX6LXul14SM67o2e4mUCY= +golang.org/x/text v0.43.0 h1:1QivrlhwAsnMOcqgOdl9my7s9OyZl8O5/UGUTJVXoWc= +golang.org/x/text v0.43.0/go.mod h1:hbSIYA/amXcRXTFSZqh8tWHEoqRvvbKPJ2uz/KU+a+s= +golang.org/x/time v0.16.0 h1:vMb6ptszcQMkcwiRTAuNNU50gom6++Q/6gY2hDM6VDE= +golang.org/x/time v0.16.0/go.mod h1:rVKOqvZeKvrDKTQiAHJ7wmwP0RzleSphoEA9RcdLA0s= gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4= gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E= -google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688 h1:ax2KzoSRIZU/M0cIxri3pKxy99vniH1PVxWC6si/eZI= -google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688/go.mod h1:1RJ9BQGyNdZwkGc1eTqkErfRZ6RJyYPHZo73BZ1vQqI= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688 h1:cYNAzI2sUwhmCcoj9TxvihSrqsxt6uIkj3rDRhSDmW4= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688/go.mod h1:DjtHYE8FKJLivXcBEjGwndXfIC23G0VpXiXKqG179uA= -google.golang.org/grpc v1.83.2 h1:EManeRomTObA0BU7I8vXgg/78uE5MJ9M8B39EX2WscU= -google.golang.org/grpc v1.83.2/go.mod h1:YPI1hK3kDked6iHvgX3tR0y+nX/qpMFKhPgFsokw1S8= +google.golang.org/genproto/googleapis/api v0.0.0-20261026165753-2ffab32bce7e h1:e789sLZjeTdc02ZCQGTSzHOu0fR73+dwPQVKUp67Z88= +google.golang.org/genproto/googleapis/api v0.0.0-20261026165753-2ffab32bce7e/go.mod h1:vHV3wJM9WZyfV7GyB+OQGq5dWVrFwCDxapPK4WlRD88= +google.golang.org/genproto/googleapis/rpc v0.0.0-20261026165753-2ffab32bce7e h1:w3fy/G8OrJHYexn+u5y7hFyc8YRRIr7DCevtIOwAcGY= +google.golang.org/genproto/googleapis/rpc v0.0.0-20261026165753-2ffab32bce7e/go.mod h1:OaIUM3+LpYcK2GXM4FTmhWoIq371Owdr+Cc7/BsYHHc= +google.golang.org/grpc v1.85.0 h1:BPBCfuVRQ1qvBhKDOq9uvQllMvO59rKsYhXpp7Dd8vw= +google.golang.org/grpc v1.85.0/go.mod h1:0rQrOD58yjal/0EaVbGBANindixsCZNQPDV3VKtaKuQ= google.golang.org/protobuf v1.36.12 h1:pJOKDDOyeXErUroCihFAd5LQuwXBSpVnKGrj5o/fwxc= google.golang.org/protobuf v1.36.12/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -189,24 +192,24 @@ gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -k8s.io/api v0.37.0 h1:Z//Vj9N7RA/yS2sDmxyeo7h+RR4zbUrd2vrd3Z0TbB4= -k8s.io/api v0.37.0/go.mod h1:LKXgcJWMc+f4OLbP5SFR8rulEg07zZhpi/zMULiBImk= -k8s.io/apiextensions-apiserver v0.37.0 h1:zRMQ3+/LIE5oZ0tVvXwYHC+dIkSP5cjNWju7AZU1LOI= -k8s.io/apiextensions-apiserver v0.37.0/go.mod h1:HU0PfSBwchHL5iDau6jjt9zU6ryWkDDlaVUiq91NK80= -k8s.io/apimachinery v0.37.0 h1:Np2AbDtf8x6RDHiD8T9LbKJ9gaegeVNa8yNm5FuGKm0= -k8s.io/apimachinery v0.37.0/go.mod h1:RN3nhprFSCxOi5Selxd7oMTXOe/c+ZbcE7Im+TS2zkE= -k8s.io/client-go v0.37.0 h1:nsN31fy8wBySuZ+QRnKmrjRSQLOG2rvoGN0tKd12zhQ= -k8s.io/client-go v0.37.0/go.mod h1:FcGqw+Ll/gNQiq+nPGY1Oyt9y7SgDh1d3MW3RFDEbn0= +k8s.io/api v0.37.2 h1:PnxqnUFy93jr2GcpWDkKOm8wfy25kT9wa572MuQS8oc= +k8s.io/api v0.37.2/go.mod h1:+NVRLft99kx+Jg9v34GmHw867Dapim89TSyCsn2hK+M= +k8s.io/apiextensions-apiserver v0.37.2 h1:myaUiAlm1NRLYdpAvWYTqdgeiCepFupJiRFVVueMUYw= +k8s.io/apiextensions-apiserver v0.37.2/go.mod h1:BFl1aPC89HsgFgYI+3xpFtckvkAeenDTZP84njA9yw4= +k8s.io/apimachinery v0.37.2 h1:G4t9RGoPDbQML38hz/kQQiltttgd3egg2ODCR8aIc2o= +k8s.io/apimachinery v0.37.2/go.mod h1:qaAvSNvgU2x07wRM95MSHSJTPQ/ob/LeNAGbeDgqQJg= +k8s.io/client-go v0.37.2 h1:sgFtyRZukGkOrsu+QgS7LYnbFFJ5ut3MchdBNduD//E= +k8s.io/client-go v0.37.2/go.mod h1:bo3LFC8YbCtKFPX21Yj8puMsoJLAv/+SvuOODma0s54= k8s.io/klog/v2 v2.140.0 h1:Tf+J3AH7xnUzZyVVXhTgGhEKnFqye14aadWv7bzXdzc= k8s.io/klog/v2 v2.140.0/go.mod h1:o+/RWfJ6PwpnFn7OyAG3QnO47BFsymfEfrz6XyYSSp0= k8s.io/kube-openapi v0.0.0-20260721132016-d427ff9ee9ad h1:oXImqH8mQNk7PmvzKhmN3ddJoY6OnyM225MXwGHPm0A= k8s.io/kube-openapi v0.0.0-20260721132016-d427ff9ee9ad/go.mod h1:0/mqHCVhlumdJ3BhCfnjSZQE037nAhNodh1/hK0T8/I= -k8s.io/utils v0.0.0-20260626114624-be93311217bd h1:Ea7fgQ5we8Y9T0OX5o0dAHzQOBRI07D/dEYRaB9ZZEs= -k8s.io/utils v0.0.0-20260626114624-be93311217bd/go.mod h1:xDxuJ0whA3d0I4mf/C4ppKHxXynQ+fxnkmQH0vTHnuk= -sigs.k8s.io/gateway-api v1.6.0 h1:735YBRj5NXFrOGX0GoSjwzUIzbz8kiEOfADsqHFmHgE= -sigs.k8s.io/gateway-api v1.6.0/go.mod h1:FVfx3t389ybeXOqvDghLbdvJdSCfI/PReqCUI3lu3mY= -sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg= -sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= +k8s.io/utils v0.0.0-20261023123148-ca04ba58ecab h1:pjQrNLaF//ytZtWwCAHtW+pqwSKjtWDqzA8+TCX37RI= +k8s.io/utils v0.0.0-20261023123148-ca04ba58ecab/go.mod h1:DdQ0BJxlQtuMlKDqdg7X5IMMQCXIk2b3d1/4M9OhUYo= +sigs.k8s.io/gateway-api v1.7.0 h1:duVnm7nO6oXfMMUWfcDoBph47CYR/ME3NGxccOVglaY= +sigs.k8s.io/gateway-api v1.7.0/go.mod h1:ihME72ink97/HnDMFNpNf9bH/mAcdTAVw0GR35iiZvA= +sigs.k8s.io/json v0.0.0-20260909141634-11ed52e25bc5 h1:k9jo7ED/fsfOcz3L1P20P5tjTNtsdQNwMOG2aKzHrl8= +sigs.k8s.io/json v0.0.0-20260909141634-11ed52e25bc5/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= sigs.k8s.io/structured-merge-diff/v6 v6.4.2 h1:qdOxHwrl2Kaag1aQEarlYcOA9vSyGCp3CIki3aW8c4Q= From 31fb0e3797d007a338fc6f3e45f68d8d3b992a45 Mon Sep 17 00:00:00 2001 From: Benoit Pourre Date: Mon, 2 Nov 2026 11:17:10 +0100 Subject: [PATCH 3/7] feat: update gosec version to 2.30.0 in CI and documentation Signed-off-by: Benoit Pourre --- .github/workflows/ci.yml | 2 +- AGENTS.md | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index eecb69f..af4c718 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -88,7 +88,7 @@ jobs: cache: true - name: Install gosec - run: go install github.com/securego/gosec/v2/cmd/gosec@v2.29.0 + run: go install github.com/securego/gosec/v2/cmd/gosec@v2.30.0 # Note: do not use -quiet here: it suppresses the report output # entirely, including the -out SARIF file (the file is then never diff --git a/AGENTS.md b/AGENTS.md index df996a4..c9e67b0 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -58,7 +58,7 @@ gofmt -l internal/ cmd/ api/ # empty output = clean ### SAST (gosec) - security-sensitive or Go changes ```sh -go install github.com/securego/gosec/v2/cmd/gosec@v2.29.0 # version pinned in CI +go install github.com/securego/gosec/v2/cmd/gosec@v2.30.0 # version pinned in CI "$(go env GOPATH)/bin/gosec" -fmt=sarif -out=results.sarif ./... # exit 0 = clean ``` @@ -117,7 +117,7 @@ A change is only done when these pass - they mirror the local commands above: - **go**: `go vet` + `go test -race` with coverage - **lint**: golangci-lint -- **security**: gosec v2.29.0, SARIF uploaded to code scanning (needs +- **security**: gosec v2.30.0, SARIF uploaded to code scanning (needs `security-events: write` + `actions: read`) - **helm**: dependency update, lint, template render check, package, OCI push + cosign sign From 74255f6a4533ff972e103c71c7dc656168d0660e Mon Sep 17 00:00:00 2001 From: Benoit Pourre Date: Mon, 2 Nov 2026 14:46:28 +0100 Subject: [PATCH 4/7] feat: add release tagging step to Helm chart packaging in CI Signed-off-by: Benoit Pourre --- .github/workflows/ci.yml | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index af4c718..2a72b3c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -120,6 +120,21 @@ jobs: - name: Set up Helm uses: azure/setup-helm@v5 + # On a tag push, bake the tag into the chart before it is packaged and + # pushed: appVersion and codx image tag take the git tag, and the chart + # patch version is bumped (version: X.Y.Z -> X.Y.Z+1). Branch and PR + # builds package the chart as committed. + - name: Stamp release tag into chart + if: startsWith(github.ref, 'refs/tags/') + run: | + tag="${GITHUB_REF_NAME}" + sed -i -E "s|^appVersion: \".*\"$|appVersion: \"${tag}\"|" charts/codx/Chart.yaml + awk -F. -v OFS=. '/^version:/ { $(NF) = $(NF) + 1 } { print }' \ + charts/codx/Chart.yaml > charts/codx/.Chart.yaml.tmp + mv charts/codx/.Chart.yaml.tmp charts/codx/Chart.yaml + sed -i -E "0,/^ tag: .*/s// tag: \"${tag}\"/" charts/codx/values.yaml + echo "appVersion and image.tag set to ${tag}, chart version: $(sed -n 's/^version: //p' charts/codx/Chart.yaml)" + - name: Log in to GitHub Container Registry if: github.event_name != 'pull_request' uses: docker/login-action@v4 From f3e1d09482ab38e6507cf99fa1d92194eac6f63e Mon Sep 17 00:00:00 2001 From: Benoit Pourre Date: Mon, 2 Nov 2026 20:46:30 +0100 Subject: [PATCH 5/7] feat: enhance IngressRoute support with cert-manager integration and update documentation Signed-off-by: Benoit Pourre --- AGENTS.md | 2 + charts/codx/README.md | 66 +++--- charts/codx/templates/_helpers.tpl | 8 + .../codx/templates/ingress-certificate.yaml | 43 ++++ charts/codx/templates/ingressroute.yaml | 8 +- charts/codx/values.yaml | 36 ++++ docs/README.md | 3 + docs/authentication.md | 44 ++-- docs/inactivity.md | 27 ++- docs/ingress.md | 192 ++++++++++++++++++ docs/workspace-creation.md | 34 ++-- 11 files changed, 372 insertions(+), 91 deletions(-) create mode 100644 charts/codx/templates/ingress-certificate.yaml create mode 100644 docs/ingress.md diff --git a/AGENTS.md b/AGENTS.md index c9e67b0..f810b81 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -26,6 +26,8 @@ for the ground rules and [docs/](docs/) for the feature documentation. ## Language and style - **English everywhere**: code, comments, commit messages, docs, chart values. +- Diagrams in Markdown docs are Mermaid (` ```mermaid ` fenced blocks), not + ASCII art. - `gofmt` clean; comments explain *why*, not *what*; minimal diffs that match the surrounding style. - Conventional commits (`feat:`, `fix:`, `docs:`, `chore:`), as used in the diff --git a/charts/codx/README.md b/charts/codx/README.md index 86d1688..defe506 100644 --- a/charts/codx/README.md +++ b/charts/codx/README.md @@ -220,35 +220,41 @@ $ helm delete --purge my-release ### Exposure parameters -| Name | Description | Value | -| ---------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------ | -| `service.type` | Kubernetes service type | `ClusterIP` | -| `service.ports.https` | codx service HTTPS port | `8443` | -| `service.nodePorts` | Specify the nodePort values for the LoadBalancer and NodePort service types. | `{}` | -| `service.sessionAffinity` | Control where client requests go, to the same pod or round-robin | `None` | -| `service.sessionAffinityConfig` | Additional settings for the sessionAffinity | `{}` | -| `service.clusterIP` | codx service clusterIP IP | `""` | -| `service.loadBalancerIP` | loadBalancerIP for the CodX service (optional, cloud specific) | `""` | -| `service.loadBalancerSourceRanges` | Address that are allowed when service is LoadBalancer | `[]` | -| `service.externalTrafficPolicy` | Enable client source IP preservation | `Cluster` | -| `service.annotations` | Additional custom annotations for codx service | `{}` | -| `service.extraPorts` | Extra port to expose on codx service | `[]` | -| `service.extraHeadlessPorts` | Extra ports to expose on codx headless service | `[]` | -| `service.ipFamilyPolicy` | Controller Service ipFamilyPolicy (optional, cloud specific) | `PreferDualStack` | -| `service.ipFamilies` | Controller Service ipFamilies (optional, cloud specific) | `["IPv6","IPv4"]` | -| `ingress.enabled` | Enable IngressRoute record generation for codx (requires the Traefik Kubernetes CRD provider) | `true` | -| `ingress.apiVersion` | Force IngressRoute API version: traefik.io/v1alpha1 (Traefik v3, default) or traefik.containo.us/v1alpha1 (Traefik v2) | `traefik.io/v1alpha1` | -| `ingress.hostname` | Default host for the IngressRoute record | `codx.local` | -| `ingress.path` | Default path for the IngressRoute record | `/` | -| `ingress.entryPoints` | Traefik entryPoints the IngressRoute binds to (e.g. websecure). Empty binds to all entry points | `[]` | -| `ingress.annotations` | Additional annotations for the IngressRoute resource | `{}` | -| `ingress.middlewares` | Traefik Middlewares attached to the IngressRoute routes, before the chart passTLSClientCert middleware (when traefikMtls.enabled). A namespace defaults to the release namespace; cross-namespace references require the Traefik allowCrossNamespace option | `[]` | -| `ingress.tls.enabled` | Enable TLS on the IngressRoute (frontend TLS between Traefik and the clients) | `false` | -| `ingress.tls.secretName` | Name of the Secret holding the TLS certificate (in the release namespace). Optional: when empty, no secretName is set on the IngressRoute (use certResolver or a TLSStore instead) | `""` | -| `ingress.tls.certResolver` | Traefik certificate resolver (ACME) used to generate the certificate, e.g. letsencrypt | `""` | -| `ingress.tls.options.name` | Name of the TLSOption applied to the route. Defaults to the chart client-mTLS TLSOption when traefikMtls.enabled | `""` | -| `ingress.tls.options.namespace` | Namespace of the TLSOption. Defaults to the release namespace | `""` | -| `ingress.extraHosts` | An array with additional hostname(s) to be covered with the IngressRoute record | `[]` | +| Name | Description | Value | +| ------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | --------------------- | +| `service.type` | Kubernetes service type | `ClusterIP` | +| `service.ports.https` | codx service HTTPS port | `8443` | +| `service.nodePorts` | Specify the nodePort values for the LoadBalancer and NodePort service types. | `{}` | +| `service.sessionAffinity` | Control where client requests go, to the same pod or round-robin | `None` | +| `service.sessionAffinityConfig` | Additional settings for the sessionAffinity | `{}` | +| `service.clusterIP` | codx service clusterIP IP | `""` | +| `service.loadBalancerIP` | loadBalancerIP for the CodX service (optional, cloud specific) | `""` | +| `service.loadBalancerSourceRanges` | Address that are allowed when service is LoadBalancer | `[]` | +| `service.externalTrafficPolicy` | Enable client source IP preservation | `Cluster` | +| `service.annotations` | Additional custom annotations for codx service | `{}` | +| `service.extraPorts` | Extra port to expose on codx service | `[]` | +| `service.extraHeadlessPorts` | Extra ports to expose on codx headless service | `[]` | +| `service.ipFamilyPolicy` | Controller Service ipFamilyPolicy (optional, cloud specific) | `PreferDualStack` | +| `service.ipFamilies` | Controller Service ipFamilies (optional, cloud specific) | `["IPv6","IPv4"]` | +| `ingress.enabled` | Enable IngressRoute record generation for codx | `true` | +| `ingress.apiVersion` | Force IngressRoute API version: traefik.io/v1alpha1 (Traefik v3, default) or traefik.containo.us/v1alpha1 (Traefik v2) | `traefik.io/v1alpha1` | +| `ingress.hostname` | Default host for the IngressRoute record | `codx.local` | +| `ingress.path` | Default path for the IngressRoute record | `/` | +| `ingress.entryPoints` | Traefik entryPoints the IngressRoute binds to (e.g. websecure). Empty binds to all entry points. | `[]` | +| `ingress.annotations` | Additional annotations for the IngressRoute resource | `{}` | +| `ingress.middlewares` | Traefik Middlewares attached to the IngressRoute routes, before the chart passTLSClientCert middleware (when traefikMtls.enabled). A namespace defaults to the release namespace; cross-namespace references require the Traefik allowCrossNamespace option. | `[]` | +| `ingress.tls.enabled` | Enable TLS on the IngressRoute | `false` | +| `ingress.tls.secretName` | Name of the Secret holding the TLS certificate (in the release namespace). Optional: when empty, no secretName is set on the IngressRoute (use certResolver or a TLSStore instead). | `""` | +| `ingress.tls.certResolver` | Traefik certificate resolver (ACME) used to generate the certificate, e.g. letsencrypt | `""` | +| `ingress.tls.options.name` | Name of the TLSOption applied to the route | `""` | +| `ingress.tls.options.namespace` | Namespace of the TLSOption. Defaults to the release namespace. | `""` | +| `ingress.extraHosts` | An array with additional hostname(s) to be covered with the IngressRoute record | `[]` | +| `ingress.certManager.enabled` | Create a cert-manager Certificate for the IngressRoute frontend TLS (implies ingress.tls.enabled) | `false` | +| `ingress.certManager.issuerRef.name` | Name of the cert-manager Issuer/ClusterIssuer issuing the ingress certificate (required when enabled) | `""` | +| `ingress.certManager.issuerRef.kind` | Kind of the issuer resource (Issuer or ClusterIssuer) | `ClusterIssuer` | +| `ingress.certManager.issuerRef.group` | Group of the issuer resource | `cert-manager.io` | +| `ingress.certManager.labels` | Additional labels for the Certificate (in addition to commonLabels) | `{}` | +| `ingress.certManager.annotations` | Additional annotations for the Certificate (in addition to commonAnnotations) | `{}` | ### RBAC parameter @@ -454,7 +460,7 @@ $ helm delete --purge my-release | `profiles.default.podSpec.securityContext.runAsUser` | User ID of the code-server container. | `1000` | | `profiles.default.podSpec.securityContext.runAsGroup` | Group ID of the code-server container. | `1000` | | `profiles.default.podSpec.securityContext.fsGroup` | Group ID of the volumes mounted by the code-server container. | `1000` | -| `profiles.default.podSpec.codeServer.image` | code-server image of the default profile. | `docker.io/captnbp/code-server:4.141.0` | +| `profiles.default.podSpec.codeServer.image` | code-server image of the default profile. | `docker.io/captnbp/code-server:4.144.0` | | `profiles.default.podSpec.codeServer.resources.limits.cpu` | CPU limit of the code-server container. | `2` | | `profiles.default.podSpec.codeServer.resources.limits.memory` | Memory limit of the code-server container. | `4Gi` | | `profiles.default.podSpec.codeServer.resources.requests.cpu` | CPU request of the code-server container. | `250m` | diff --git a/charts/codx/templates/_helpers.tpl b/charts/codx/templates/_helpers.tpl index 1e89526..e35644f 100644 --- a/charts/codx/templates/_helpers.tpl +++ b/charts/codx/templates/_helpers.tpl @@ -46,6 +46,14 @@ Return the TLS secret name {{- end -}} {{- end -}} +{{/* +Ingress frontend TLS Secret name. Defaults to -ingress-tls, the +Secret the chart Certificate (ingress.certManager.enabled) issues into. +*/}} +{{- define "codx.ingressTlsSecretName" -}} +{{- .Values.ingress.tls.secretName | default (printf "%s-ingress-tls" (include "common.names.fullname" .)) -}} +{{- end -}} + {{/* Instance name: defaults to release name, used in workspace object names. */}} diff --git a/charts/codx/templates/ingress-certificate.yaml b/charts/codx/templates/ingress-certificate.yaml new file mode 100644 index 0000000..5973f14 --- /dev/null +++ b/charts/codx/templates/ingress-certificate.yaml @@ -0,0 +1,43 @@ +{{/* +cert-manager Certificate for the IngressRoute frontend TLS (between Traefik +and the clients). Independent from the chart internal TLS (tls.enabled), +which covers the service certificate, not the public hostname. + +Issued for ingress.hostname (commonName) and every ingress.extraHosts name, +stored in the Secret the IngressRoute references as tls.secretName (defaults +to -ingress-tls). +*/}} +{{- if and .Values.ingress.enabled .Values.ingress.certManager.enabled }} +{{- $fullname := include "common.names.fullname" . }} +{{- $hostname := required "ingress.hostname is required when ingress.certManager.enabled is true" .Values.ingress.hostname }} +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + name: {{ $fullname }}-ingress + namespace: {{ include "common.names.namespace" . | quote }} + labels: {{- include "common.labels.standard" . | nindent 4 }} + app.kubernetes.io/component: codx + {{- if .Values.commonLabels }} + {{- include "common.tplvalues.render" ( dict "value" .Values.commonLabels "context" $ ) | nindent 4 }} + {{- end }} + {{- if .Values.ingress.certManager.labels }} + {{- include "common.tplvalues.render" ( dict "value" .Values.ingress.certManager.labels "context" $ ) | nindent 4 }} + {{- end }} + {{- if or .Values.ingress.certManager.annotations .Values.commonAnnotations }} + annotations: {{- include "common.tplvalues.merge" ( dict "values" ( list .Values.ingress.certManager.annotations .Values.commonAnnotations ) "context" $ ) | nindent 4 }} + {{- end }} +spec: + secretName: {{ include "codx.ingressTlsSecretName" . }} + commonName: {{ $hostname | quote }} + dnsNames: + - {{ $hostname | quote }} + {{- range .Values.ingress.extraHosts }} + - {{ .name | quote }} + {{- end }} + usages: + - server auth + issuerRef: + name: {{ required "ingress.certManager.issuerRef.name is required when ingress.certManager.enabled is true" .Values.ingress.certManager.issuerRef.name }} + kind: {{ .Values.ingress.certManager.issuerRef.kind | quote }} + group: {{ .Values.ingress.certManager.issuerRef.group | quote }} +{{- end }} diff --git a/charts/codx/templates/ingressroute.yaml b/charts/codx/templates/ingressroute.yaml index 1216940..3387de8 100644 --- a/charts/codx/templates/ingressroute.yaml +++ b/charts/codx/templates/ingressroute.yaml @@ -55,10 +55,12 @@ spec: serversTransport: {{ $fullname }} {{- end }} {{- end }} - {{- if .Values.ingress.tls.enabled }} + {{/* ingress.certManager.enabled implies the frontend TLS: the route serves + the Secret the chart Certificate issues into. */}} + {{- if or .Values.ingress.tls.enabled .Values.ingress.certManager.enabled }} tls: - {{- if .Values.ingress.tls.secretName }} - secretName: {{ .Values.ingress.tls.secretName | quote }} + {{- if or .Values.ingress.tls.secretName .Values.ingress.certManager.enabled }} + secretName: {{ include "codx.ingressTlsSecretName" . | quote }} {{- end }} {{- if .Values.ingress.tls.certResolver }} certResolver: {{ .Values.ingress.tls.certResolver | quote }} diff --git a/charts/codx/values.yaml b/charts/codx/values.yaml index bbbdf75..46eb042 100644 --- a/charts/codx/values.yaml +++ b/charts/codx/values.yaml @@ -384,6 +384,42 @@ ingress: ## path: / ## extraHosts: [] + ## cert-manager Certificate for the IngressRoute frontend TLS. When + ## enabled, the chart issues a certificate for ingress.hostname + ## (commonName) and every ingress.extraHosts name into the + ## -ingress-tls Secret, and the IngressRoute frontend TLS is + ## enabled with that Secret as tls.secretName (an explicit + ## ingress.tls.secretName wins). The Certificate metadata carries + ## commonLabels/commonAnnotations in addition to the dedicated ones below. + ## + certManager: + ## @param ingress.certManager.enabled Create a cert-manager Certificate for the IngressRoute frontend TLS (implies ingress.tls.enabled) + ## + enabled: false + ## cert-manager issuer issuing the ingress certificate (e.g. letsencrypt). + ## + issuerRef: + ## @param ingress.certManager.issuerRef.name Name of the cert-manager Issuer/ClusterIssuer issuing the ingress certificate (required when enabled) + ## + name: "" + ## @param ingress.certManager.issuerRef.kind Kind of the issuer resource (Issuer or ClusterIssuer) + ## + kind: "ClusterIssuer" + ## @param ingress.certManager.issuerRef.group Group of the issuer resource + ## + group: "cert-manager.io" + ## @param ingress.certManager.labels Additional labels for the Certificate (in addition to commonLabels) + ## e.g: + ## labels: + ## cert-manager.io/cluster-issuer: letsencrypt + ## + labels: {} + ## @param ingress.certManager.annotations Additional annotations for the Certificate (in addition to commonAnnotations) + ## e.g: + ## annotations: + ## cert-manager.io/issue-priority: "1" + ## + annotations: {} ## @section RBAC parameter ## Specifies whether a ServiceAccount should be created diff --git a/docs/README.md b/docs/README.md index 7531ba3..48fd6e8 100644 --- a/docs/README.md +++ b/docs/README.md @@ -3,6 +3,9 @@ - [Authentication](authentication.md) - OIDC login flow, sessions, and the mTLS client-certificate fallback for admins (Traefik TLSOption, passTLSClientCert Middleware, client CA management). +- [Ingress exposure](ingress.md) - Traefik IngressRoute, frontend TLS + (existing Secret, Traefik ACME resolver or cert-manager certificate), + backend ServersTransport and using your own ingress objects. - [Workspace creation](workspace-creation.md) - per-user Kubernetes objects (Service, PVC, Certificate, Pod), naming, TLS model and workspace lifecycle. diff --git a/docs/authentication.md b/docs/authentication.md index 821268f..ce7288f 100644 --- a/docs/authentication.md +++ b/docs/authentication.md @@ -8,32 +8,26 @@ mTLS fallback, and the security model. ## Overview +```mermaid +flowchart LR + Browser["Browser
(no client cert)"] + Admin["Admin browser
(client cert)"] + Traefik["Traefik ingress
TLSOption (optional)
passTLSClientCert Middleware"] + CodX["CodX server
/auth/login, /auth/callback
session middleware"] + OIDC["OIDC provider
(e.g. Forgejo)"] + Store[("Redis/Valkey
session store")] + + Browser --> Traefik + Admin -->|mTLS fallback: admins| Traefik + Traefik --> CodX + CodX -->|OIDC flow| OIDC + CodX --> Store ``` - +-----------------------+ +---------------------+ - Browser | Traefik ingress | | OIDC provider | - (no client cert) | TLSOption (optional) | | (e.g. Forgejo) | - -------------| |--------| | - | | passTLSClientCert | +---------------------+ - | | Middleware | - | +-----------+-----------+ - | | - | +-----------v-----------+ - +----------->| CodX server | - | /auth/login | OIDC flow - | /auth/callback | - | session middleware | mTLS fallback (admins) - +-----------+-----------+ - | - +-----------v-----------+ - | Redis/Valkey | - | session store | - +-----------------------+ - - Admin browser Traefik validates the client certificate against the - (client cert) TLSOption CA, forwards the Common Name in the - X-Forwarded-Tls-Client-Cert-Info header, and CodX grants - an admin session if the CN is in the allow list. -``` + +On the mTLS fallback, Traefik validates the client certificate against the +TLSOption CA, forwards the Common Name in the +`X-Forwarded-Tls-Client-Cert-Info` header, and CodX grants an admin session +if the CN is in the allow list. Both mechanisms produce the same artifact: a session stored in Redis/Valkey and referenced by the `codx-session` cookie. diff --git a/docs/inactivity.md b/docs/inactivity.md index 1512ad6..6fddc77 100644 --- a/docs/inactivity.md +++ b/docs/inactivity.md @@ -8,21 +8,20 @@ how to configure the delays. ## Overview +```mermaid +flowchart LR + CodX["CodX inactivity loops
(activity source, watcher, reconciler)"] + API["Kubernetes API"] + Envoy["workspace Envoy sidecar
(admin :9901)"] + Pod["workspace Pod"] + + CodX -->|lists pods| API + CodX -->|poll /stats| Envoy + CodX -->|stop: delete| Pod ``` -+--------------+ lists pods +------------------------+ -| CodX |--------------->| Kubernetes API | -| | +------------------------+ -| inactivity | poll /stats +------------------------+ -| loops |--------------->| workspace Envoy | -| | | sidecar (admin :9901) | -| - activity | +------------------------+ -| source | -| - watcher | stop +------------------------+ -| - reconciler|--------------->| workspace Pod | -+--------------+ (delete) +------------------------+ - Service, PVC and Certificate are kept: the workspace - is restarted later with the user's data intact. -``` + +Only the Pod is deleted: the Service, PVC and Certificate are kept, so the +workspace is restarted later with the user's data intact. Three loops run inside the CodX server, all ticking at the same `inactivity.checkInterval` (default `60s`): diff --git a/docs/ingress.md b/docs/ingress.md new file mode 100644 index 0000000..ef3a1a5 --- /dev/null +++ b/docs/ingress.md @@ -0,0 +1,192 @@ +# Ingress exposure (Traefik IngressRoute) + +This document describes how the chart exposes the CodX server at the edge: +the Traefik IngressRoute it creates, the frontend TLS options (including +the cert-manager certificate), and the backend TLS to the CodX service. + +The chart does not create a standard Kubernetes Ingress: CodX is exposed +through a Traefik IngressRoute, which requires the Traefik Kubernetes CRD +provider (IngressRoute, Middleware, TLSOption). With another ingress +controller, set `ingress.enabled=false` and create your own objects (see +[Using your own ingress objects](#using-your-own-ingress-objects)). + +## Overview + +```mermaid +flowchart LR + Client["Client (browser)
https://codx.example.com"] + Route["IngressRoute <fullname>
match: Host(`codx.example`)
&& PathPrefix(`/`)
frontend TLS: Secret <fullname>-ingress-tls
TLSOption <fullname>-client-mtls (optional)
Middleware <fullname>-passtlsclientcert (optional)"] + Service["Service <fullname>:8443
HTTPS only"] + + Client -->|frontend TLS| Route + Route -->|"scheme: https
serversTransport: <fullname>"| Service + + style Client fill:#FF6B6B,stroke:#fff,stroke-width:2px,color:#fff + style Route fill:#00D9FF,stroke:#fff,stroke-width:2px,color:#000 + style Service fill:#326CE5,stroke:#fff,stroke-width:2px,color:#fff +``` + +The backend Service only exposes the HTTPS port (`containerPorts.https`, +default 8443): the CodX server serves HTTPS and nothing else (TLS is +mandatory in the server). Traefik therefore dials the backend with +`scheme: https` and the chart ServersTransport, which trusts the +chart-internal CA. + +## Created objects + +| Object | Name | Created when | +|--------|------|--------------| +| IngressRoute | `` | `ingress.enabled` (default) | +| ServersTransport | `` | `tls.enabled` (default) | +| Certificate (cert-manager) | `-ingress` | `ingress.certManager.enabled` | +| TLS Secret (issued by cert-manager) | `-ingress-tls` | by cert-manager, from the Certificate above | + +All objects are created in the release namespace. + +## Hosts and routes + +One route rule is generated per host: + +- `ingress.hostname` with `ingress.path` (default `/`), +- every entry of `ingress.extraHosts` (`name`, `path` defaulting to `/`). + +Each rule matches `Host(``) && PathPrefix(``)` and targets the +`` Service on the HTTPS port. When `ingress.hostname` is empty, +only the `extraHosts` routes are generated. + +The IngressRoute carries `ingress.annotations` (merged with +`commonAnnotations`) and the standard chart labels plus `commonLabels`. +Traefik entryPoints are selected with `ingress.entryPoints` (e.g. +`websecure`); when empty, the route binds to all entry points. + +## Middlewares + +Middlewares are attached to every route, in order: + +1. the ones from `ingress.middlewares` (each entry is `name` + optional + `namespace`; a namespace defaults to the release namespace, and + cross-namespace references require the Traefik `allowCrossNamespace` + option), +2. the chart `passTLSClientCert` Middleware when `traefikMtls.enabled`. + +The mTLS middleware, its TLSOption and the client CA Secret are documented +in [Authentication](authentication.md). + +## Frontend TLS + +The `tls` block of the IngressRoute is rendered when `ingress.tls.enabled` +is true or when `ingress.certManager.enabled` is true (the cert-manager +certificate implies the frontend TLS - without it, the issued Secret would +never be served). + +| Key | Role | +|-----|------| +| `ingress.tls.secretName` | Secret holding the certificate Traefik presents to the clients. Defaults to `-ingress-tls` when `ingress.certManager.enabled` (see below); when empty and no certificate is requested, no `secretName` is set on the route. | +| `ingress.tls.certResolver` | Alternative to `secretName`: Traefik ACME resolver (e.g. `letsencrypt`) issuing the certificate on the fly. | +| `ingress.tls.options.name` / `.namespace` | TLSOption applied to the route (e.g. to request client certificates). Defaults to the chart client-mTLS TLSOption when `traefikMtls.enabled` and nothing is set. | + +The route also carries the TLS domains, so Traefik answers for every host: +`main` is the first host (`ingress.hostname` or the first `extraHosts` +entry), the other hosts are listed as `sans`. + +### cert-manager certificate (`ingress.certManager`) + +Instead of an existing Secret or a Traefik ACME resolver, the chart can +request the certificate from cert-manager. With +`ingress.certManager.enabled=true` the chart creates: + +| Property | Value | +|----------|-------| +| Certificate name | `-ingress` | +| Secret | `ingress.tls.secretName` or `-ingress-tls` | +| commonName | `ingress.hostname` | +| dnsNames | `ingress.hostname` and every `ingress.extraHosts` name | +| usages | `server auth` | +| issuerRef | `ingress.certManager.issuerRef` (`name` required, `kind` default `ClusterIssuer`, `group` default `cert-manager.io`) | +| labels / annotations | standard chart labels plus `commonLabels` and `ingress.certManager.labels`; `ingress.certManager.annotations` merged with `commonAnnotations` | + +Example: + +```yaml +ingress: + hostname: codx.example.com + extraHosts: + - name: www.codx.example.com + tls: + enabled: true + certManager: + enabled: true + issuerRef: + name: letsencrypt + kind: ClusterIssuer +``` + +Notes: + +- `ingress.hostname` and `ingress.certManager.issuerRef.name` are required + when the certificate is enabled: `helm template` fails with an explicit + error message otherwise. +- An explicit `ingress.tls.secretName` wins over the + `-ingress-tls` default for both the Certificate and the + IngressRoute. +- The ingress certificate is independent from the chart internal TLS + (`tls.enabled`), which covers the CodX *service* certificate - not the + public hostname. +- The Certificate requires cert-manager to be installed in the cluster. + +## Backend TLS (ServersTransport) + +The CodX server presents the certificate issued by the chart internal CA +(`-server-tls`). For Traefik to trust it when dialing +`https://:8443`, the chart creates the ServersTransport +`` whenever `tls.enabled` is true: + +- `rootCAs`: the `ca.crt` of the `-server-tls` Secret, +- `serverName`: the Service FQDN `..svc.`, + matching the certificate DNS names. + +Since the server speaks HTTPS only, keep `tls.enabled` (the default) when +using the chart IngressRoute: with it disabled the route dials the backend +as plain HTTP and the connection fails. + +## Traefik v2 and v3 + +The CRD API version differs between Traefik major versions: + +| Traefik | Setting | Value | +|---------|---------|-------| +| v3 (default) | `ingress.apiVersion` and `traefikMtls.apiVersion` | `traefik.io/v1alpha1` | +| v2 | `ingress.apiVersion` and `traefikMtls.apiVersion` | `traefik.containo.us/v1alpha1` | + +## Using your own ingress objects + +Set `ingress.enabled=false` to skip the IngressRoute and expose CodX with +your own objects (e.g. a standard Ingress for another controller, or an +IngressRoute you manage). Requirements to keep in mind: + +- route to the Service `` on the HTTPS port `containerPorts.https` + (8443) over HTTPS, trusting the chart CA (or replicate the ServersTransport + settings), +- allow the ingress controller traffic in the NetworkPolicy + (`networkPolicy.codx.ingress.fromIngressController`, enabled by default), +- the mTLS fallback objects (`traefikMtls`) still need to be wired to your + route by hand: the chart only attaches them to its own IngressRoute. + +## Troubleshooting + +| Symptom | Likely cause and check | +|---------|------------------------| +| 404 on the hostname | Route/host mismatch: check the rendered match rules (`helm template`, look for `Host(\`...\`)`), the `ingress.entryPoints` list and that Traefik serves that entry point. | +| Traefik serves the wrong / default certificate | No `secretName` on the route: enable `ingress.tls.enabled` or `ingress.certManager.enabled`, or check the Secret exists in the release namespace. | +| Certificate stays `Not Ready` | cert-manager cannot issue: `kubectl describe certificate -ingress` (issuerRef name/kind, ACME challenge of the `issuerRef` issuer). | +| 502 / connection reset to the backend | Backend dial failure: keep `tls.enabled=true` (the server only speaks HTTPS) and check the ServersTransport `` exists; check the NetworkPolicy allows the ingress controller. | +| Client certificates ignored | The client-mTLS TLSOption is not applied: it requires the frontend TLS and defaults to the chart TLSOption only when `traefikMtls.enabled` - see [Authentication](authentication.md). | +| CRDs rejected by the API server | Wrong CRD API version for your Traefik: set `ingress.apiVersion` (and `traefikMtls.apiVersion`) to `traefik.containo.us/v1alpha1` on Traefik v2. | + +## Related documents + +- [Authentication](authentication.md) - the mTLS fallback objects + (TLSOption, passTLSClientCert Middleware, client CA Secret). +- [Workspace creation](workspace-creation.md) - the full TLS model, from + the browser to the workspace. +- [Documentation index](README.md) diff --git a/docs/workspace-creation.md b/docs/workspace-creation.md index c4413bf..9817de7 100644 --- a/docs/workspace-creation.md +++ b/docs/workspace-creation.md @@ -6,25 +6,21 @@ naming, the TLS model, and the lifecycle of a workspace. ## Overview -``` - User CodX server Kubernetes - ---- ---------- ---------- - login ------> OIDC session (username -> slug) - pick a profile - Start ------> /api/workspace/start?profile= (SSE stream) - | EnsureWorkspace (idempotent): - | 1. Service - - | 2. PVC - - | 3. cert-manager Certificate - | 4. Pod - - | Wait for the certificate to be Ready - | Wait for the pod to be Ready - <------ ready redirect to /user// - | - browser <-----> CodX proxy /user//... --mTLS--> Envoy sidecar - (TLS :9443) - --> code-server - (localhost:8080) +```mermaid +sequenceDiagram + participant U as User + participant C as CodX server + participant K as Kubernetes + + U->>C: login (OIDC session, username mapped to slug) + U->>C: pick a profile and Start (SSE stream) + C->>K: EnsureWorkspace (idempotent) + Note over C,K: 1. Service {instance}-{slug}
2. PVC {instance}-{slug}
3. cert-manager Certificate
4. Pod {instance}-{slug} + C->>K: Wait for the certificate to be Ready + C->>K: Wait for the pod to be Ready + C->>U: ready, redirect to /user/{slug}/ + U->>C: browser traffic /user/{slug}/... + Note over C: mTLS proxy to the Envoy sidecar (TLS :9443),
which forwards to code-server (localhost:8080) ``` The browser never talks to the workspace pod directly: all workspace traffic From b35658bc4c6ce5c15e31bd3ca7f173b74cde6ce6 Mon Sep 17 00:00:00 2001 From: Benoit Pourre Date: Mon, 2 Nov 2026 21:01:41 +0100 Subject: [PATCH 6/7] feat: add Mermaid diagram syntax validation to documentation workflow Signed-off-by: Benoit Pourre --- .github/workflows/ci.yml | 14 +++++++ AGENTS.md | 12 ++++++ Makefile | 11 +++++ hack/check_mermaid.mjs | 88 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 125 insertions(+) create mode 100644 hack/check_mermaid.mjs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2a72b3c..daacce1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -105,6 +105,20 @@ jobs: with: sarif_file: results.sarif + # -- Markdown docs: Mermaid diagram syntax -- + # Uses the Node.js preinstalled on the runner (the check needs >= 20.11). + docs: + name: Docs (Mermaid) + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - name: Checkout + uses: actions/checkout@v7 + + - name: Check Mermaid diagrams + run: make check-mermaid + # -- Helm lint & package -- helm: name: Helm lint diff --git a/AGENTS.md b/AGENTS.md index f810b81..5894af0 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -108,6 +108,17 @@ make check-values-docs - When the CRD (`charts/codx/crds/`) changes, regenerate the docs with `make docs-crd`. +### Docs (Markdown changes) + +```sh +make check-mermaid +``` + +- Validates the syntax of every ` ```mermaid ` block in the Markdown files + with the real Mermaid parser (`hack/check_mermaid.mjs`, Node + jsdom, no + browser). Diagrams in docs are Mermaid, never ASCII art. The node_modules + are installed into a throwaway directory, not the repository. + ### Workflows (changes under `.github/`) Validate the YAML parses (`python3 -c "import yaml; yaml.safe_load(open(...))"`) @@ -126,6 +137,7 @@ A change is only done when these pass - they mirror the local commands above: - **docker**: amd64 build -> Trivy CVE gate (HIGH/CRITICAL, ignore-unfixed) -> CycloneDX SBOM (artifact + cosign attest) -> multi-arch build/push -> cosign sign. The image is scanned **before** it is pushed. +- **docs**: Mermaid diagram syntax check (`make check-mermaid`). Pull requests are reviewed by the code owners (`.github/CODEOWNERS`). diff --git a/Makefile b/Makefile index f25016d..b287664 100644 --- a/Makefile +++ b/Makefile @@ -52,6 +52,17 @@ docs-crd: check-values-docs: python3 hack/check_values_docs.py charts/codx/values.yaml +# Validate the syntax of the mermaid diagrams in the Markdown files with +# the real Mermaid parser (syntax only, no rendering). The node_modules are +# installed into a throwaway directory so the repository stays free of +# JavaScript dependencies. Requires node and npm on PATH. +MERMAID_CHECK_DIR ?= $(shell mktemp -d) + +.PHONY: check-mermaid +check-mermaid: + npm --prefix $(MERMAID_CHECK_DIR) install --no-audit --no-fund --silent mermaid@11 jsdom >/dev/null + NODE_PATH=$(MERMAID_CHECK_DIR)/node_modules node hack/check_mermaid.mjs + # Scan the Go modules (go.mod/go.sum) for known CVEs, like the CI does on # the Docker image. Requires trivy on PATH (https://trivy.dev). # Fails on HIGH/CRITICAL findings with a fix available. diff --git a/hack/check_mermaid.mjs b/hack/check_mermaid.mjs new file mode 100644 index 0000000..14ca348 --- /dev/null +++ b/hack/check_mermaid.mjs @@ -0,0 +1,88 @@ +#!/usr/bin/env node +/* +Validate the syntax of every mermaid fenced block in the Markdown files of +the repository with the real Mermaid parser (mermaid.parse) - syntax only, +no rendering, so no browser or Chromium is needed. jsdom provides the DOM +globals the Mermaid bundle expects at import time. + +Usage: + make check-mermaid (installs mermaid + jsdom in a temp dir) + node hack/check_mermaid.mjs [FILE...] (mermaid and jsdom must already be + resolvable, e.g. via NODE_PATH) + +Requires Node.js >= 20.11 (import.meta.dirname). +Exit code 0 when every block parses, 1 otherwise. +*/ + +import { createRequire } from 'node:module'; +import { readdirSync, readFileSync, statSync } from 'node:fs'; +import { join, relative } from 'node:path'; +import { pathToFileURL } from 'node:url'; + +const MERMAID_BLOCK = /```mermaid\n(.*?)```/gs; +const EXCLUDED_DIRS = new Set(['.git', 'node_modules']); + +function markdownFiles(dir) { + const files = []; + for (const entry of readdirSync(dir)) { + const full = join(dir, entry); + if (statSync(full).isDirectory()) { + if (EXCLUDED_DIRS.has(entry)) continue; + files.push(...markdownFiles(full)); + } else if (entry.endsWith('.md')) { + files.push(full); + } + } + return files; +} + +async function main() { + const root = join(import.meta.dirname, '..'); + const targets = process.argv.length > 2 + ? process.argv.slice(2) + : markdownFiles(root); + const blocks = []; + for (const file of targets) { + const text = readFileSync(file, 'utf8'); + const display = file.startsWith(root + '/') ? relative(root, file) : file; + for (const match of text.matchAll(MERMAID_BLOCK)) { + const line = text.slice(0, match.index).split('\n').length; + blocks.push({ file: display, line, diagram: match[1] }); + } + } + if (blocks.length === 0) { + console.log('No mermaid blocks found.'); + return; + } + + // Mermaid's UMD bundle reads DOM globals at import time: set them up + // before requiring it. The probe path keeps the resolution anchored to + // this script while NODE_PATH supplies the actual packages. + const require = createRequire(pathToFileURL(join(import.meta.dirname, 'node_modules/.probe'))); + const { JSDOM } = require('jsdom'); + const dom = new JSDOM(''); + globalThis.window = dom.window; + globalThis.document = dom.window.document; + Object.defineProperty(globalThis, 'navigator', { + value: dom.window.navigator, + configurable: true, + }); + const mermaid = require('mermaid').default; + mermaid.initialize({ startOnLoad: false }); + + let failures = 0; + for (const { file, line, diagram } of blocks) { + try { + await mermaid.parse(diagram, { suppressErrors: false }); + console.log(`${file}:${line}: OK`); + } catch (error) { + failures++; + const message = String(error.message ?? error).split('\n').slice(0, 2).join(' | '); + console.error(`${file}:${line}: INVALID - ${message}`); + } + } + console.log(`${blocks.length - failures}/${blocks.length} mermaid block(s) valid.`); + if (failures > 0) process.exit(1); +} + +await main(); From d607ca483d70b9fc6627e8958b833b54965da529 Mon Sep 17 00:00:00 2001 From: Benoit Pourre Date: Mon, 2 Nov 2026 21:33:02 +0100 Subject: [PATCH 7/7] fix: change MERMAID_CHECK_DIR assignment to use := for correct directory handling Signed-off-by: Benoit Pourre --- Makefile | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index b287664..60d963f 100644 --- a/Makefile +++ b/Makefile @@ -56,7 +56,9 @@ check-values-docs: # the real Mermaid parser (syntax only, no rendering). The node_modules are # installed into a throwaway directory so the repository stays free of # JavaScript dependencies. Requires node and npm on PATH. -MERMAID_CHECK_DIR ?= $(shell mktemp -d) +# ":=" expands mktemp once: a lazy "?=" would run it per reference and the +# install and the NODE_PATH would point at two different directories. +MERMAID_CHECK_DIR := $(shell mktemp -d) .PHONY: check-mermaid check-mermaid: