From 747a1546665e32cecf164b3e643c40952aaec93e Mon Sep 17 00:00:00 2001 From: Wesley Charles Blake Date: Sat, 18 Apr 2026 13:48:42 +0200 Subject: [PATCH 1/4] chore(spectre): adds podSecurityContext and securityContext and adds digest pinning --- charts/spectre/Chart.yaml | 2 +- charts/spectre/README.md | 13 ++++++-- charts/spectre/templates/deployment.yaml | 9 ++++-- charts/spectre/values.yaml | 39 +++++++++++++++++++----- 4 files changed, 50 insertions(+), 13 deletions(-) diff --git a/charts/spectre/Chart.yaml b/charts/spectre/Chart.yaml index 61fb4072..c8345035 100644 --- a/charts/spectre/Chart.yaml +++ b/charts/spectre/Chart.yaml @@ -22,7 +22,7 @@ type: application # This is the chart version. This version number should be incremented each time you make changes # to the chart and its templates, including the app version. # Versions are expected to follow Semantic Versioning (https://semver.org/) -version: 0.3.5 +version: 0.4.0 # This is the version number of the application being deployed. This version number should be # incremented each time you make changes to the application. Versions are not expected to diff --git a/charts/spectre/README.md b/charts/spectre/README.md index 9829c50c..2ace1e6f 100644 --- a/charts/spectre/README.md +++ b/charts/spectre/README.md @@ -1,6 +1,6 @@ # spectre -![Version: 0.3.4](https://img.shields.io/badge/Version-0.3.4-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 0.68.3](https://img.shields.io/badge/AppVersion-0.68.3-informational?style=flat-square) +![Version: 0.4.0](https://img.shields.io/badge/Version-0.4.0-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 0.68.3](https://img.shields.io/badge/AppVersion-0.68.3-informational?style=flat-square) A Helm chart for deploying Chronicle Spectre Relay on Kubernetes @@ -25,10 +25,12 @@ A Helm chart for deploying Chronicle Spectre Relay on Kubernetes | chainType | string | `nil` | can be one of mainnet: `eth`, `arb`, `opt`, `gno`, `scr`, `zkevm`, `mantle`, or testnets: `gor`, `sep`, `ogor`, `mango`, `testnet-zkEVM-mango`, `mantle-testnet`, `scr-sepolia` | | configsUrl | string | `""` | config url for the app, if provided, will override the default config. can be an ipfs url or a http url | | entryPointOverride | list | `[]` | | +| env.normal | object | `{}` | | | environment | string | `"stage"` | CFG_ENVIRONMENT can be one of `stage` or `prod` | | ethConfig | object | `{}` | | | extraObjects | list | `[]` | Extra K8s manifests to deploy | | fullnameOverride | string | `"spectre"` | | +| image.digest | string | `"sha256:c87e4d6944941c09019102da22bbd458649f54488627b2acbff21a51112215af"` | | | image.pullPolicy | string | `"IfNotPresent"` | | | image.repository | string | `"ghcr.io/chronicleprotocol/spectre"` | | | image.tag | string | `""` | | @@ -58,11 +60,16 @@ A Helm chart for deploying Chronicle Spectre Relay on Kubernetes | readinessProbe.probeSpec.timeoutSeconds | int | `5` | | | replicaCount | int | `1` | | | resources | object | `{}` | | +| revisionHistoryLimit | int | `3` | | | rpcUrl | string | `nil` | rpcUrl for the target chain | -| securityContext | object | `{}` | | +| securityContext.allowPrivilegeEscalation | bool | `false` | | +| securityContext.capabilities.drop[0] | string | `"ALL"` | | +| securityContext.readOnlyRootFilesystem | bool | `true` | | | service.annotations | object | `{}` | | | service.ports.listen.port | int | `8080` | | | service.ports.listen.protocol | string | `"TCP"` | | +| service.ports.metrics.port | int | `9090` | | +| service.ports.metrics.protocol | string | `"TCP"` | | | service.type | string | `"ClusterIP"` | | | serviceAccount.annotations | object | `{}` | | | serviceAccount.create | bool | `true` | | @@ -71,6 +78,7 @@ A Helm chart for deploying Chronicle Spectre Relay on Kubernetes | serviceMonitor.enabled | bool | `false` | If true, a ServiceMonitor CRD is created for a prometheus operator https://github.com/coreos/prometheus-operator | | serviceMonitor.interval | string | `"60s"` | ServiceMonitor scrape interval | | serviceMonitor.labels | object | `{}` | Additional ServiceMonitor labels | +| serviceMonitor.metricRelabelings | list | `[]` | ServiceMonitor metricRelabelings | | serviceMonitor.namespace | string | `nil` | Alternative namespace for ServiceMonitor | | serviceMonitor.path | string | `"/metrics"` | Path to scrape | | serviceMonitor.port | string | `"metrics"` | port to scrape | @@ -79,6 +87,7 @@ A Helm chart for deploying Chronicle Spectre Relay on Kubernetes | serviceMonitor.scrapeTimeout | string | `"60s"` | ServiceMonitor scrape timeout | | serviceMonitor.tlsConfig | object | `{}` | ServiceMonitor TLS configuration | | tolerations | list | `[]` | | +| topologySpreadConstraints | list | `[]` | | | txType | string | `nil` | can be one of `legacy`or `eip1559` | ---------------------------------------------- diff --git a/charts/spectre/templates/deployment.yaml b/charts/spectre/templates/deployment.yaml index 3f68bfcc..7d68a1a6 100644 --- a/charts/spectre/templates/deployment.yaml +++ b/charts/spectre/templates/deployment.yaml @@ -8,6 +8,7 @@ spec: {{- if not .Values.autoscaling.enabled }} replicas: {{ .Values.replicaCount }} {{- end }} + revisionHistoryLimit: {{ .Values.revisionHistoryLimit | default 3 }} selector: matchLabels: {{- include "spectre.selectorLabels" . | nindent 6 }} @@ -31,7 +32,7 @@ spec: - name: {{ .Chart.Name }} securityContext: {{- toYaml .Values.securityContext | nindent 12 }} - image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" + image: "{{ .Values.image.repository }}{{ if .Values.image.digest }}@{{ .Values.image.digest }}{{ else }}:{{ .Values.image.tag | default .Chart.AppVersion }}{{ end }}" {{- if .Values.entryPointOverride }} command: {{- range .Values.entryPointOverride }} @@ -83,7 +84,7 @@ spec: - name: CFG_CHAIN_TX_TYPE value: "{{ .Values.txType }}" - name: CFG_LOG_LEVEL - value: "{{ .Values.logLevel | default "debug" }}" + value: "{{ .Values.logLevel | default "info" }}" - name: CFG_LOG_FORMAT value: "{{ .Values.logFormat | default "text" }}" {{- if .Values.ethConfig }} @@ -133,3 +134,7 @@ spec: tolerations: {{- toYaml . | nindent 8 }} {{- end }} + {{- with .Values.topologySpreadConstraints }} + topologySpreadConstraints: + {{- toYaml . | nindent 8 }} + {{- end }} diff --git a/charts/spectre/values.yaml b/charts/spectre/values.yaml index 0f162b52..75f76435 100644 --- a/charts/spectre/values.yaml +++ b/charts/spectre/values.yaml @@ -9,6 +9,8 @@ image: pullPolicy: IfNotPresent # Overrides the image tag whose default is the chart appVersion. tag: "" + # When set, pins the image by digest (e.g. "sha256:abc..."). Takes precedence over tag. + digest: "sha256:c87e4d6944941c09019102da22bbd458649f54488627b2acbff21a51112215af" # -- CFG_ENVIRONMENT can be one of `stage` or `prod` environment: stage @@ -88,16 +90,29 @@ serviceAccount: podAnnotations: {} +# Pod-level security context. Left unset by default because the upstream spectre +# image currently runs as root (no USER in Dockerfile, distroless/base without +# :nonroot tag). Once the image is rebuilt as non-root, enable the block below. podSecurityContext: {} - # fsGroup: 2000 - -securityContext: {} - # capabilities: - # drop: - # - ALL - # readOnlyRootFilesystem: true # runAsNonRoot: true - # runAsUser: 1000 + # runAsUser: 65532 + # runAsGroup: 65532 + # fsGroup: 65532 + # seccompProfile: + # type: RuntimeDefault + +# Container-level security context. Safe defaults: spectre does not write to +# the filesystem, so readOnlyRootFilesystem is enabled without needing a +# writable emptyDir. runAsNonRoot lives on podSecurityContext and is disabled +# pending an upstream image rebuild with a non-root USER. +securityContext: + allowPrivilegeEscalation: false + readOnlyRootFilesystem: true + capabilities: + drop: + - ALL + +revisionHistoryLimit: 3 service: type: ClusterIP @@ -163,6 +178,14 @@ tolerations: [] affinity: {} +topologySpreadConstraints: [] + # - maxSkew: 1 + # topologyKey: kubernetes.io/hostname + # whenUnsatisfiable: ScheduleAnyway + # labelSelector: + # matchLabels: + # app.kubernetes.io/name: spectre + serviceMonitor: # -- If true, a ServiceMonitor CRD is created for a prometheus operator # https://github.com/coreos/prometheus-operator From c92935b962e2510d47313a520132ab93ea4cb3a4 Mon Sep 17 00:00:00 2001 From: Jinsoo Heo Date: Sat, 18 Apr 2026 21:43:52 +0900 Subject: [PATCH 2/4] fix(spectre): mount tmp for read-only root fs --- charts/spectre/templates/deployment.yaml | 16 ++++++++++++++-- charts/spectre/values.yaml | 8 ++++---- 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/charts/spectre/templates/deployment.yaml b/charts/spectre/templates/deployment.yaml index 7d68a1a6..eaa99342 100644 --- a/charts/spectre/templates/deployment.yaml +++ b/charts/spectre/templates/deployment.yaml @@ -107,19 +107,31 @@ spec: {{- include "helpers.list-env-variables" . | indent 12 }} resources: {{- toYaml .Values.resources | nindent 12 }} - {{- if .Values.inlineConfig }} + {{- if or .Values.inlineConfig .Values.securityContext.readOnlyRootFilesystem }} volumeMounts: + {{- if .Values.securityContext.readOnlyRootFilesystem }} + - name: tmp + mountPath: /tmp + {{- end }} + {{- if .Values.inlineConfig }} - name: inline-config mountPath: /config.hcl subPath: config.hcl readOnly: true + {{- end }} {{- end }} - {{- if .Values.inlineConfig }} + {{- if or .Values.inlineConfig .Values.securityContext.readOnlyRootFilesystem }} volumes: + {{- if .Values.securityContext.readOnlyRootFilesystem }} + - name: tmp + emptyDir: {} + {{- end }} + {{- if .Values.inlineConfig }} - name: inline-config configMap: name: inline-config + {{- end }} {{- end }} {{- with .Values.nodeSelector }} diff --git a/charts/spectre/values.yaml b/charts/spectre/values.yaml index 75f76435..67b64af4 100644 --- a/charts/spectre/values.yaml +++ b/charts/spectre/values.yaml @@ -101,10 +101,10 @@ podSecurityContext: {} # seccompProfile: # type: RuntimeDefault -# Container-level security context. Safe defaults: spectre does not write to -# the filesystem, so readOnlyRootFilesystem is enabled without needing a -# writable emptyDir. runAsNonRoot lives on podSecurityContext and is disabled -# pending an upstream image rebuild with a non-root USER. +# Container-level security context. The chart mounts an emptyDir at /tmp when +# readOnlyRootFilesystem is enabled because spectre creates /tmp/chronicle at +# startup. runAsNonRoot lives on podSecurityContext and is disabled pending an +# upstream image rebuild with a non-root USER. securityContext: allowPrivilegeEscalation: false readOnlyRootFilesystem: true From 0a9b15e545bb1ebaa31faf246b4fe9ac7a467cb4 Mon Sep 17 00:00:00 2001 From: Jinsoo Heo Date: Sat, 18 Apr 2026 21:46:35 +0900 Subject: [PATCH 3/4] fix(spectre): accept relay startup logs at info level --- charts/spectre/templates/tests/test-logs.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/charts/spectre/templates/tests/test-logs.yaml b/charts/spectre/templates/tests/test-logs.yaml index 9747054b..eb68b44c 100644 --- a/charts/spectre/templates/tests/test-logs.yaml +++ b/charts/spectre/templates/tests/test-logs.yaml @@ -16,5 +16,5 @@ spec: - -c - | echo 'Running tests'; - kubectl logs --namespace {{ .Release.Namespace }} $(kubectl get pods --namespace {{ .Release.Namespace }} -l 'app.kubernetes.io/instance={{ .Release.Name }},app.kubernetes.io/name={{ include "spectre.name" . }}' -o jsonpath='{.items[0].metadata.name}') | grep 'msg=Starting tag=RELAY' + kubectl logs --namespace {{ .Release.Namespace }} $(kubectl get pods --namespace {{ .Release.Namespace }} -l 'app.kubernetes.io/instance={{ .Release.Name }},app.kubernetes.io/name={{ include "spectre.name" . }}' -o jsonpath='{.items[0].metadata.name}') | grep -E 'msg=(Starting|"Relay started") tag=RELAY' restartPolicy: Never From 6e1254800284b182044196cfe7e9e77b24572189 Mon Sep 17 00:00:00 2001 From: Jinsoo Heo Date: Sat, 18 Apr 2026 21:52:04 +0900 Subject: [PATCH 4/4] fix(spectre): restore chart overrides --- charts/spectre/templates/deployment.yaml | 4 ++-- charts/spectre/values.yaml | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/charts/spectre/templates/deployment.yaml b/charts/spectre/templates/deployment.yaml index eaa99342..13522f12 100644 --- a/charts/spectre/templates/deployment.yaml +++ b/charts/spectre/templates/deployment.yaml @@ -8,7 +8,7 @@ spec: {{- if not .Values.autoscaling.enabled }} replicas: {{ .Values.replicaCount }} {{- end }} - revisionHistoryLimit: {{ .Values.revisionHistoryLimit | default 3 }} + revisionHistoryLimit: {{ .Values.revisionHistoryLimit }} selector: matchLabels: {{- include "spectre.selectorLabels" . | nindent 6 }} @@ -32,7 +32,7 @@ spec: - name: {{ .Chart.Name }} securityContext: {{- toYaml .Values.securityContext | nindent 12 }} - image: "{{ .Values.image.repository }}{{ if .Values.image.digest }}@{{ .Values.image.digest }}{{ else }}:{{ .Values.image.tag | default .Chart.AppVersion }}{{ end }}" + image: "{{ .Values.image.repository }}{{ if .Values.image.tag }}:{{ .Values.image.tag }}{{ else if .Values.image.digest }}@{{ .Values.image.digest }}{{ else }}:{{ .Chart.AppVersion }}{{ end }}" {{- if .Values.entryPointOverride }} command: {{- range .Values.entryPointOverride }} diff --git a/charts/spectre/values.yaml b/charts/spectre/values.yaml index 67b64af4..1cb3c484 100644 --- a/charts/spectre/values.yaml +++ b/charts/spectre/values.yaml @@ -9,7 +9,8 @@ image: pullPolicy: IfNotPresent # Overrides the image tag whose default is the chart appVersion. tag: "" - # When set, pins the image by digest (e.g. "sha256:abc..."). Takes precedence over tag. + # When set, pins the image by digest (e.g. "sha256:abc...") while tag remains + # empty. Set image.tag to override the default pinned digest. digest: "sha256:c87e4d6944941c09019102da22bbd458649f54488627b2acbff21a51112215af" # -- CFG_ENVIRONMENT can be one of `stage` or `prod`