Phase 4: SSO scaffolding, RBAC enforcement, tenant-scoped dashboards, audit logging, K8s deployment

RBAC (api/internal/authz) is live on /query and /dashboards, backed by a
new enterprise/ module (session issuance, audit logging, RBAC storage,
OIDC/SAML protocol wiring) that core never imports -- only calls over
HTTP. Found and fixed a real cross-tenant vulnerability in dashboards
(no tenant_id filtering at all) while writing the threat model doc.

Two things are explicitly NOT done, documented rather than hidden:
tenant isolation for log data itself (/query still shares one ClickHouse
connection and Tantivy index across every tenant -- RBAC controls who
can query, not what a query can see), and human SSO login (protocol
wiring exists, no HTTP handler calls it yet). See
docs/security/threat-model.md and docs/phase-4-runbook.md.

Also adds deploy/ (Go Operator + Helm chart, validated offline only --
no cluster was reachable in this environment).
This commit is contained in:
2026-08-13 22:16:59 -07:00
parent 9435115ab7
commit 3eb0f4c589
116 changed files with 8589 additions and 126 deletions
+55
View File
@@ -0,0 +1,55 @@
{{/*
Standard labels applied to every resource this chart renders.
*/}}
{{- define "sentry.labels" -}}
app.kubernetes.io/part-of: sentry
app.kubernetes.io/managed-by: {{ .Release.Service }}
helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version }}
{{- end -}}
{{/*
Per-component selector labels -- usage:
{{ include "sentry.selectorLabels" (list $ "api") }}
A plain string arg (the old shape this started with) can't reach
$.Release from inside the defined template -- `include`'s argument
becomes the template's entire root context, so a bare "api" string
leaves no way to get back to the chart root. A two-element list carries
both.
*/}}
{{- define "sentry.selectorLabels" -}}
{{- $root := index . 0 -}}
{{- $name := index . 1 -}}
app.kubernetes.io/name: sentry-{{ $name }}
app.kubernetes.io/instance: {{ $root.Release.Name }}
{{- end -}}
{{/*
An initContainer that busy-waits for a TCP host:port to accept
connections -- usage: {{ include "sentry.waitForTCP" (list "name-suffix" "host" "port") }}
This approximates docker-compose.yml's `depends_on: condition:
service_healthy` (waits for the dependency's process to be reachable),
but NOT `condition: service_completed_successfully` (waits for a
one-shot Job, like clickhouse-migrate, to have actually finished). That
second guarantee doesn't have a lightweight equivalent here without
giving every app pod's ServiceAccount RBAC to read Job status, which is
a lot of privilege for a startup-ordering nicety -- see
deploy/helm/sentry/README.md's "Startup ordering" section. The gap it
leaves (a pod starts before its migration Job has finished) is covered
by the app's own crash-and-restart-on-connect/schema failure: every Go
service here already os.Exit(1)s on a failed Postgres/ClickHouse ping at
startup (see e.g. api/cmd/api/main.go), so Kubernetes' restart policy
naturally retries until the schema is ready. Documented as a real,
accepted tradeoff, not implied to be a hard ordering guarantee.
*/}}
{{- define "sentry.waitForTCP" -}}
{{- $name := index . 0 -}}
{{- $host := index . 1 -}}
{{- $port := index . 2 -}}
- name: wait-for-{{ $name }}
image: busybox:1.36
command:
- sh
- -c
- until nc -z -w2 {{ $host }} {{ $port }}; do echo "waiting for {{ $host }}:{{ $port }}"; sleep 2; done
{{- end -}}
@@ -0,0 +1,81 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ .Release.Name }}-alerting
labels:
{{- include "sentry.labels" . | nindent 4 }}
{{- include "sentry.selectorLabels" (list $ "alerting") | nindent 4 }}
spec:
# See values.yaml's comment: replicas is not a real knob here yet.
replicas: {{ .Values.alerting.replicas }}
selector:
matchLabels:
{{- include "sentry.selectorLabels" (list $ "alerting") | nindent 6 }}
template:
metadata:
labels:
{{- include "sentry.selectorLabels" (list $ "alerting") | nindent 8 }}
spec:
initContainers:
{{- include "sentry.waitForTCP" (list "postgres" (printf "%s-postgres" .Release.Name) "5432") | nindent 8 }}
{{- include "sentry.waitForTCP" (list "api" (printf "%s-api" .Release.Name) "8080") | nindent 8 }}
containers:
- name: alerting
image: "{{ .Values.alerting.image.repository }}:{{ .Values.alerting.image.tag }}"
imagePullPolicy: {{ .Values.global.imagePullPolicy }}
env:
- name: POSTGRES_ADDR
value: "{{ .Release.Name }}-postgres:5432"
- name: POSTGRES_DATABASE
value: sentry_metadata
- name: POSTGRES_USERNAME
value: sentry
- name: POSTGRES_PASSWORD
valueFrom:
secretKeyRef:
name: {{ .Release.Name }}-postgres
key: password
- name: API_QUERY_URL
value: "http://{{ .Release.Name }}-api:8080"
{{- if .Values.enterprise.enabled }}
# RoleService credential for POST /query, once api's
# ENTERPRISE_AUTH_URL enforcement is on -- see
# /docs/phase-4-isolation-design.md's alerting<->api gap and
# alerting/internal/queryclient's doc comment. NOT generated
# by this chart: mint one with
# `enterprise-auth -mint-service-token=alerting` (see
# enterprise/README.md) and supply it via
# --set-string alerting.apiServiceToken=... or a values
# override backed by a Secret you manage -- a chart
# generating its own long-lived service credential and
# storing it in the same release's values would defeat the
# point of it being a distinct, revocable credential.
{{- if .Values.alerting.apiServiceToken }}
- name: API_SERVICE_TOKEN
value: {{ .Values.alerting.apiServiceToken | quote }}
{{- end }}
{{- end }}
ports:
- name: http
containerPort: 8081
readinessProbe:
exec:
command: ["/alerting", "-healthcheck"]
initialDelaySeconds: 5
periodSeconds: 5
resources:
{{- toYaml .Values.alerting.resources | nindent 12 }}
---
apiVersion: v1
kind: Service
metadata:
name: {{ .Release.Name }}-alerting
labels:
{{- include "sentry.labels" . | nindent 4 }}
{{- include "sentry.selectorLabels" (list $ "alerting") | nindent 4 }}
spec:
selector:
{{- include "sentry.selectorLabels" (list $ "alerting") | nindent 4 }}
ports:
- name: http
port: 8081
+79
View File
@@ -0,0 +1,79 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ .Release.Name }}-api
labels:
{{- include "sentry.labels" . | nindent 4 }}
{{- include "sentry.selectorLabels" (list $ "api") | nindent 4 }}
spec:
replicas: {{ .Values.api.replicas }}
selector:
matchLabels:
{{- include "sentry.selectorLabels" (list $ "api") | nindent 6 }}
template:
metadata:
labels:
{{- include "sentry.selectorLabels" (list $ "api") | nindent 8 }}
spec:
initContainers:
{{- include "sentry.waitForTCP" (list "clickhouse" (printf "%s-clickhouse" .Release.Name) "9000") | nindent 8 }}
{{- include "sentry.waitForTCP" (list "postgres" (printf "%s-postgres" .Release.Name) "5432") | nindent 8 }}
{{- include "sentry.waitForTCP" (list "search" (printf "%s-search" .Release.Name) "50052") | nindent 8 }}
containers:
- name: api
image: "{{ .Values.api.image.repository }}:{{ .Values.api.image.tag }}"
imagePullPolicy: {{ .Values.global.imagePullPolicy }}
env:
- name: CLICKHOUSE_ADDR
value: "{{ .Release.Name }}-clickhouse:9000"
- name: CLICKHOUSE_PASSWORD
valueFrom:
secretKeyRef:
name: {{ .Release.Name }}-clickhouse
key: password
- name: SEARCH_GRPC_ADDR
value: "{{ .Release.Name }}-search:50052"
- name: POSTGRES_ADDR
value: "{{ .Release.Name }}-postgres:5432"
- name: POSTGRES_DATABASE
value: sentry_metadata
- name: POSTGRES_USERNAME
value: sentry
- name: POSTGRES_PASSWORD
valueFrom:
secretKeyRef:
name: {{ .Release.Name }}-postgres
key: password
{{- if .Values.enterprise.enabled }}
# Turns on authz.RequireRole*/RequireRoleOrService enforcement
# on /query and /dashboards -- see api/internal/authz and
# /docs/phase-4-rbac-design.md. Off (unset) when
# enterprise.enabled is false, matching every nil-authorizer
# no-op default in this codebase.
- name: ENTERPRISE_AUTH_URL
value: "http://{{ .Release.Name }}-enterprise-auth:8082"
{{- end }}
ports:
- name: http
containerPort: 8080
readinessProbe:
exec:
command: ["/api", "-healthcheck"]
initialDelaySeconds: 5
periodSeconds: 5
resources:
{{- toYaml .Values.api.resources | nindent 12 }}
---
apiVersion: v1
kind: Service
metadata:
name: {{ .Release.Name }}-api
labels:
{{- include "sentry.labels" . | nindent 4 }}
{{- include "sentry.selectorLabels" (list $ "api") | nindent 4 }}
spec:
selector:
{{- include "sentry.selectorLabels" (list $ "api") | nindent 4 }}
ports:
- name: http
port: 8080
@@ -0,0 +1,103 @@
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: {{ .Release.Name }}-clickhouse
labels:
{{- include "sentry.labels" . | nindent 4 }}
{{- include "sentry.selectorLabels" (list $ "clickhouse") | nindent 4 }}
spec:
serviceName: {{ .Release.Name }}-clickhouse
replicas: 1
selector:
matchLabels:
{{- include "sentry.selectorLabels" (list $ "clickhouse") | nindent 6 }}
template:
metadata:
labels:
{{- include "sentry.selectorLabels" (list $ "clickhouse") | nindent 8 }}
spec:
containers:
- name: clickhouse
image: "{{ .Values.clickhouse.image.repository }}:{{ .Values.clickhouse.image.tag }}"
imagePullPolicy: {{ .Values.global.imagePullPolicy }}
env:
# Required to avoid the official image's network lockdown of
# the implicit `default` user -- see values.yaml's comment on
# this password and docker-compose.yml's original.
- name: CLICKHOUSE_PASSWORD
valueFrom:
secretKeyRef:
name: {{ .Release.Name }}-clickhouse
key: password
ports:
- name: http
containerPort: 8123
- name: native
containerPort: 9000
volumeMounts:
- name: data
mountPath: /var/lib/clickhouse
readinessProbe:
httpGet:
path: /ping
port: http
initialDelaySeconds: 5
periodSeconds: 5
resources:
{{- toYaml .Values.clickhouse.resources | nindent 12 }}
volumeClaimTemplates:
- metadata:
name: data
spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: {{ .Values.clickhouse.persistence.size }}
---
apiVersion: v1
kind: Service
metadata:
name: {{ .Release.Name }}-clickhouse
labels:
{{- include "sentry.labels" . | nindent 4 }}
{{- include "sentry.selectorLabels" (list $ "clickhouse") | nindent 4 }}
spec:
clusterIP: None
selector:
{{- include "sentry.selectorLabels" (list $ "clickhouse") | nindent 4 }}
ports:
- name: http
port: 8123
- name: native
port: 9000
---
# One-shot: applies /storage/migrations/*.sql -- same image
# storage/Dockerfile builds for docker-compose.yml's clickhouse-migrate
# service. Plain Job, not a Helm hook -- see redpanda.yaml's comment and
# deploy/helm/sentry/README.md's "Startup ordering" section.
apiVersion: batch/v1
kind: Job
metadata:
name: {{ .Release.Name }}-clickhouse-migrate
labels:
{{- include "sentry.labels" . | nindent 4 }}
spec:
backoffLimit: 6
template:
metadata:
labels:
{{- include "sentry.selectorLabels" (list $ "clickhouse-migrate") | nindent 8 }}
spec:
restartPolicy: OnFailure
containers:
- name: clickhouse-migrate
image: "{{ .Values.clickhouse.migrateImage.repository }}:{{ .Values.clickhouse.migrateImage.tag }}"
imagePullPolicy: {{ .Values.global.imagePullPolicy }}
env:
- name: CLICKHOUSE_HTTP
value: "http://{{ .Release.Name }}-clickhouse:8123"
- name: CLICKHOUSE_PASSWORD
valueFrom:
secretKeyRef:
name: {{ .Release.Name }}-clickhouse
key: password
@@ -0,0 +1,74 @@
{{- if .Values.enterprise.enabled }}
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ .Release.Name }}-enterprise-auth
labels:
{{- include "sentry.labels" . | nindent 4 }}
{{- include "sentry.selectorLabels" (list $ "enterprise-auth") | nindent 4 }}
spec:
replicas: {{ .Values.enterprise.replicas }}
selector:
matchLabels:
{{- include "sentry.selectorLabels" (list $ "enterprise-auth") | nindent 6 }}
template:
metadata:
labels:
{{- include "sentry.selectorLabels" (list $ "enterprise-auth") | nindent 8 }}
spec:
containers:
- name: enterprise-auth
image: "{{ .Values.enterprise.image.repository }}:{{ .Values.enterprise.image.tag }}"
imagePullPolicy: {{ .Values.global.imagePullPolicy }}
env:
- name: ENTERPRISE_SESSION_SIGNING_KEY
valueFrom:
secretKeyRef:
name: {{ .Release.Name }}-enterprise-auth
key: sessionSigningKey
{{- if .Values.enterprise.oidc.issuerURL }}
- name: OIDC_ISSUER_URL
value: {{ .Values.enterprise.oidc.issuerURL | quote }}
- name: OIDC_CLIENT_ID
value: {{ .Values.enterprise.oidc.clientID | quote }}
- name: OIDC_CLIENT_SECRET
valueFrom:
secretKeyRef:
name: {{ .Release.Name }}-enterprise-auth
key: oidcClientSecret
- name: OIDC_REDIRECT_URL
value: {{ .Values.enterprise.oidc.redirectURL | quote }}
{{- end }}
{{- if .Values.enterprise.saml.idpMetadataURL }}
- name: SAML_ENTITY_ID
value: {{ .Values.enterprise.saml.entityID | quote }}
- name: SAML_ACS_URL
value: {{ .Values.enterprise.saml.acsURL | quote }}
- name: SAML_IDP_METADATA_URL
value: {{ .Values.enterprise.saml.idpMetadataURL | quote }}
{{- end }}
ports:
- name: http
containerPort: 8082
readinessProbe:
exec:
command: ["/enterprise-auth", "-healthcheck"]
initialDelaySeconds: 5
periodSeconds: 5
resources:
{{- toYaml .Values.enterprise.resources | nindent 12 }}
---
apiVersion: v1
kind: Service
metadata:
name: {{ .Release.Name }}-enterprise-auth
labels:
{{- include "sentry.labels" . | nindent 4 }}
{{- include "sentry.selectorLabels" (list $ "enterprise-auth") | nindent 4 }}
spec:
selector:
{{- include "sentry.selectorLabels" (list $ "enterprise-auth") | nindent 4 }}
ports:
- name: http
port: 8082
{{- end }}
+65
View File
@@ -0,0 +1,65 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ .Release.Name }}-ingest
labels:
{{- include "sentry.labels" . | nindent 4 }}
{{- include "sentry.selectorLabels" (list $ "ingest") | nindent 4 }}
spec:
replicas: {{ .Values.ingest.replicas }}
selector:
matchLabels:
{{- include "sentry.selectorLabels" (list $ "ingest") | nindent 6 }}
template:
metadata:
labels:
{{- include "sentry.selectorLabels" (list $ "ingest") | nindent 8 }}
spec:
initContainers:
{{- include "sentry.waitForTCP" (list "redpanda" (printf "%s-redpanda" .Release.Name) "9092") | nindent 8 }}
{{- include "sentry.waitForTCP" (list "clickhouse" (printf "%s-clickhouse" .Release.Name) "9000") | nindent 8 }}
containers:
- name: ingest
image: "{{ .Values.ingest.image.repository }}:{{ .Values.ingest.image.tag }}"
imagePullPolicy: {{ .Values.global.imagePullPolicy }}
env:
- name: REDPANDA_BROKERS
value: "{{ .Release.Name }}-redpanda:9092"
- name: CLICKHOUSE_ADDR
value: "{{ .Release.Name }}-clickhouse:9000"
- name: CLICKHOUSE_PASSWORD
valueFrom:
secretKeyRef:
name: {{ .Release.Name }}-clickhouse
key: password
ports:
- name: grpc
containerPort: 4317
{{- if .Values.ingest.tlsSecretName }}
volumeMounts:
- name: tls
mountPath: /etc/sentry-ingest
readOnly: true
{{- end }}
resources:
{{- toYaml .Values.ingest.resources | nindent 12 }}
{{- if .Values.ingest.tlsSecretName }}
volumes:
- name: tls
secret:
secretName: {{ .Values.ingest.tlsSecretName }}
{{- end }}
---
apiVersion: v1
kind: Service
metadata:
name: {{ .Release.Name }}-ingest
labels:
{{- include "sentry.labels" . | nindent 4 }}
{{- include "sentry.selectorLabels" (list $ "ingest") | nindent 4 }}
spec:
selector:
{{- include "sentry.selectorLabels" (list $ "ingest") | nindent 4 }}
ports:
- name: grpc
port: 4317
+110
View File
@@ -0,0 +1,110 @@
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: {{ .Release.Name }}-postgres
labels:
{{- include "sentry.labels" . | nindent 4 }}
{{- include "sentry.selectorLabels" (list $ "postgres") | nindent 4 }}
spec:
serviceName: {{ .Release.Name }}-postgres
replicas: 1
selector:
matchLabels:
{{- include "sentry.selectorLabels" (list $ "postgres") | nindent 6 }}
template:
metadata:
labels:
{{- include "sentry.selectorLabels" (list $ "postgres") | nindent 8 }}
spec:
containers:
- name: postgres
image: "{{ .Values.postgres.image.repository }}:{{ .Values.postgres.image.tag }}"
imagePullPolicy: {{ .Values.global.imagePullPolicy }}
env:
- name: POSTGRES_DB
value: sentry_metadata
- name: POSTGRES_USER
value: sentry
- name: POSTGRES_PASSWORD
valueFrom:
secretKeyRef:
name: {{ .Release.Name }}-postgres
key: password
ports:
- name: postgres
containerPort: 5432
volumeMounts:
- name: data
mountPath: /var/lib/postgresql/data
readinessProbe:
exec:
command: ["pg_isready", "-U", "sentry", "-d", "sentry_metadata"]
initialDelaySeconds: 5
periodSeconds: 5
resources:
{{- toYaml .Values.postgres.resources | nindent 12 }}
volumeClaimTemplates:
- metadata:
name: data
spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: {{ .Values.postgres.persistence.size }}
---
apiVersion: v1
kind: Service
metadata:
name: {{ .Release.Name }}-postgres
labels:
{{- include "sentry.labels" . | nindent 4 }}
{{- include "sentry.selectorLabels" (list $ "postgres") | nindent 4 }}
spec:
clusterIP: None
selector:
{{- include "sentry.selectorLabels" (list $ "postgres") | nindent 4 }}
ports:
- name: postgres
port: 5432
---
# One-shot: applies /metadata/migrations/*.sql (including Phase 4's
# tenants/users/tenant_memberships/audit_log schema) -- same image
# metadata/Dockerfile builds for docker-compose.yml's metadata-migrate
# service. Plain Job, not a Helm hook -- see redpanda.yaml's comment.
apiVersion: batch/v1
kind: Job
metadata:
name: {{ .Release.Name }}-metadata-migrate
labels:
{{- include "sentry.labels" . | nindent 4 }}
spec:
backoffLimit: 6
template:
metadata:
labels:
{{- include "sentry.selectorLabels" (list $ "metadata-migrate") | nindent 8 }}
spec:
restartPolicy: OnFailure
containers:
- name: metadata-migrate
image: "{{ .Values.postgres.migrateImage.repository }}:{{ .Values.postgres.migrateImage.tag }}"
imagePullPolicy: {{ .Values.global.imagePullPolicy }}
env:
- name: POSTGRES_HOST
value: "{{ .Release.Name }}-postgres"
- name: POSTGRES_PORT
value: "5432"
- name: POSTGRES_USER
value: sentry
- name: POSTGRES_PASSWORD
valueFrom:
secretKeyRef:
name: {{ .Release.Name }}-postgres
key: password
- name: POSTGRES_DATABASE
value: sentry_metadata
- name: AUDIT_WRITER_PASSWORD
valueFrom:
secretKeyRef:
name: {{ .Release.Name }}-postgres
key: auditWriterPassword
+108
View File
@@ -0,0 +1,108 @@
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: {{ .Release.Name }}-redpanda
labels:
{{- include "sentry.labels" . | nindent 4 }}
{{- include "sentry.selectorLabels" (list $ "redpanda") | nindent 4 }}
spec:
serviceName: {{ .Release.Name }}-redpanda
replicas: 1
selector:
matchLabels:
{{- include "sentry.selectorLabels" (list $ "redpanda") | nindent 6 }}
template:
metadata:
labels:
{{- include "sentry.selectorLabels" (list $ "redpanda") | nindent 8 }}
spec:
containers:
- name: redpanda
image: "{{ .Values.redpanda.image.repository }}:{{ .Values.redpanda.image.tag }}"
imagePullPolicy: {{ .Values.global.imagePullPolicy }}
args:
- redpanda
- start
- --smp=1
- --memory=1G
- --reserve-memory=0M
- --overprovisioned
- --node-id=0
- --check=false
- --kafka-addr=PLAINTEXT://0.0.0.0:9092
- --advertise-kafka-addr=PLAINTEXT://{{ .Release.Name }}-redpanda:9092
ports:
- name: kafka
containerPort: 9092
- name: admin
containerPort: 9644
volumeMounts:
- name: data
mountPath: /var/lib/redpanda/data
readinessProbe:
exec:
command: ["rpk", "cluster", "health", "--exit-when-healthy"]
initialDelaySeconds: 5
periodSeconds: 5
resources:
{{- toYaml .Values.redpanda.resources | nindent 12 }}
volumeClaimTemplates:
- metadata:
name: data
spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: {{ .Values.redpanda.persistence.size }}
---
apiVersion: v1
kind: Service
metadata:
name: {{ .Release.Name }}-redpanda
labels:
{{- include "sentry.labels" . | nindent 4 }}
{{- include "sentry.selectorLabels" (list $ "redpanda") | nindent 4 }}
spec:
clusterIP: None
selector:
{{- include "sentry.selectorLabels" (list $ "redpanda") | nindent 4 }}
ports:
- name: kafka
port: 9092
- name: admin
port: 9644
---
# One-shot: creates the sentry.logs.raw topic. Same image
# transport/Dockerfile builds for docker-compose.yml's redpanda-provision
# service. Deliberately a plain Job, not a Helm hook -- see
# deploy/helm/sentry/README.md's "Startup ordering" section for why
# (StatefulSets-as-hooks breaks helm upgrade/uninstall's ownership
# tracking of stateful resources). backoffLimit gives it room to retry
# until redpanda's StatefulSet is actually ready; ingest/search's own
# crash-and-restart-on-connect-failure covers the rest of the ordering,
# same as every dependency in this chart.
apiVersion: batch/v1
kind: Job
metadata:
name: {{ .Release.Name }}-redpanda-provision
labels:
{{- include "sentry.labels" . | nindent 4 }}
spec:
backoffLimit: 6
template:
metadata:
labels:
{{- include "sentry.selectorLabels" (list $ "redpanda-provision") | nindent 8 }}
spec:
restartPolicy: OnFailure
containers:
- name: redpanda-provision
image: "{{ .Values.redpanda.provisionImage.repository }}:{{ .Values.redpanda.provisionImage.tag }}"
imagePullPolicy: {{ .Values.global.imagePullPolicy }}
env:
- name: REDPANDA_BROKERS
value: "{{ .Release.Name }}-redpanda:9092"
- name: REDPANDA_ADMIN_HOSTS
value: "{{ .Release.Name }}-redpanda:9644"
- name: REDPANDA_TOPIC_PARTITIONS
value: "6"
+71
View File
@@ -0,0 +1,71 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ .Release.Name }}-search
labels:
{{- include "sentry.labels" . | nindent 4 }}
{{- include "sentry.selectorLabels" (list $ "search") | nindent 4 }}
spec:
# See values.yaml's comment: replicas is not a real knob here yet.
replicas: {{ .Values.search.replicas }}
strategy:
type: Recreate # single PVC below (ReadWriteOnce) -- avoid two pods racing to mount it during a rollout
selector:
matchLabels:
{{- include "sentry.selectorLabels" (list $ "search") | nindent 6 }}
template:
metadata:
labels:
{{- include "sentry.selectorLabels" (list $ "search") | nindent 8 }}
spec:
initContainers:
{{- include "sentry.waitForTCP" (list "redpanda" (printf "%s-redpanda" .Release.Name) "9092") | nindent 8 }}
containers:
- name: search
image: "{{ .Values.search.image.repository }}:{{ .Values.search.image.tag }}"
imagePullPolicy: {{ .Values.global.imagePullPolicy }}
env:
- name: REDPANDA_BROKERS
value: "{{ .Release.Name }}-redpanda:9092"
- name: REDPANDA_TOPIC_PARTITIONS
value: "6"
- name: RUST_LOG
value: "info"
ports:
- name: grpc
containerPort: 50052
volumeMounts:
- name: index-data
mountPath: /var/lib/sentry-search
resources:
{{- toYaml .Values.search.resources | nindent 12 }}
volumes:
- name: index-data
persistentVolumeClaim:
claimName: {{ .Release.Name }}-search-index
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: {{ .Release.Name }}-search-index
labels:
{{- include "sentry.labels" . | nindent 4 }}
spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: {{ .Values.search.persistence.size }}
---
apiVersion: v1
kind: Service
metadata:
name: {{ .Release.Name }}-search
labels:
{{- include "sentry.labels" . | nindent 4 }}
{{- include "sentry.selectorLabels" (list $ "search") | nindent 4 }}
spec:
selector:
{{- include "sentry.selectorLabels" (list $ "search") | nindent 4 }}
ports:
- name: grpc
port: 50052
+81
View File
@@ -0,0 +1,81 @@
{{/*
Shared control-plane secrets -- the cluster-wide passwords
docker-compose.yml hardcodes as "sentry-dev-only"/etc (see its
clickhouse/metadata-postgres/metadata-migrate comments) become real
generated-or-supplied Secrets here. Each follows the same pattern: a
values override wins if set, otherwise a value is generated once and
kept stable across `helm upgrade` via `lookup` (so upgrades don't
silently rotate a live credential out from under a running Deployment --
same "never rotate a live credential without coordinating the
consumer-side change" reasoning as
deploy/operator/internal/controller/tenant_controller.go's
reconcileSecret). `lookup` returns nothing under `helm template`
(no live cluster) -- expected; see deploy/README.md's verification
section for what that means for this file specifically.
*/}}
{{- define "sentry.stableSecretValue" -}}
{{- $ns := index . 0 -}}
{{- $name := index . 1 -}}
{{- $key := index . 2 -}}
{{- $override := index . 3 -}}
{{- $existing := lookup "v1" "Secret" $ns $name -}}
{{- if $override -}}
{{ $override }}
{{- else if $existing -}}
{{ index $existing.data $key | b64dec }}
{{- else -}}
{{ randAlphaNum 40 }}
{{- end -}}
{{- end -}}
---
apiVersion: v1
kind: Secret
metadata:
name: {{ .Release.Name }}-clickhouse
labels:
{{- include "sentry.labels" . | nindent 4 }}
type: Opaque
stringData:
# The official clickhouse-server image locks down *network* access
# entirely for the implicit `default` user unless this is genuinely
# non-empty -- see docker-compose.yml's clickhouse service comment.
# Not a substitute for task 2's per-tenant credentials (still unbuilt
# -- see deploy/operator's Tenant controller); this is the shared
# admin/migration credential only.
password: {{ include "sentry.stableSecretValue" (list .Release.Namespace (printf "%s-clickhouse" .Release.Name) "password" .Values.clickhouse.password) }}
---
apiVersion: v1
kind: Secret
metadata:
name: {{ .Release.Name }}-postgres
labels:
{{- include "sentry.labels" . | nindent 4 }}
type: Opaque
stringData:
password: {{ include "sentry.stableSecretValue" (list .Release.Namespace (printf "%s-postgres" .Release.Name) "password" .Values.postgres.password) }}
# Restricted audit_writer Postgres role (Phase 4 task 4) -- INSERT+SELECT
# only, via its own pool, never the shared role above. See
# /docs/phase-4-isolation-design.md's audit-logging section and
# metadata/README.md.
auditWriterPassword: {{ include "sentry.stableSecretValue" (list .Release.Namespace (printf "%s-postgres" .Release.Name) "auditWriterPassword" .Values.postgres.auditWriterPassword) }}
{{- if .Values.enterprise.enabled }}
---
apiVersion: v1
kind: Secret
metadata:
name: {{ .Release.Name }}-enterprise-auth
labels:
{{- include "sentry.labels" . | nindent 4 }}
type: Opaque
stringData:
# Must be >= 32 bytes -- see enterprise/internal/config.Load and
# enterprise/internal/session.MinSigningKeyBytes. Rotating this
# invalidates every outstanding session/service token -- same
# "don't rotate a live credential silently" reasoning as above,
# which is why it's kept stable via the lookup above rather than
# regenerated on every `helm upgrade`.
sessionSigningKey: {{ include "sentry.stableSecretValue" (list .Release.Namespace (printf "%s-enterprise-auth" .Release.Name) "sessionSigningKey" .Values.enterprise.sessionSigningKey) }}
{{- if .Values.enterprise.oidc.clientSecret }}
oidcClientSecret: {{ .Values.enterprise.oidc.clientSecret | quote }}
{{- end }}
{{- end }}
@@ -0,0 +1,92 @@
{{- if and .Values.enterprise.enabled .Values.tenantOperator.enabled }}
apiVersion: v1
kind: ServiceAccount
metadata:
name: {{ .Release.Name }}-tenant-operator
labels:
{{- include "sentry.labels" . | nindent 4 }}
---
# ClusterRole, not Role: Tenant is cluster-scoped-CRD-but-namespaced-object
# (see crds/sentry.io_tenants.yaml's scope: Namespaced), and this chart
# doesn't assume it's the only namespace the operator might one day watch
# -- narrowed to exactly the two resource types
# deploy/operator/internal/controller/tenant_controller.go's
# +kubebuilder:rbac markers name (tenants, tenants/status, secrets), not
# a wildcard grant.
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: {{ .Release.Name }}-tenant-operator
labels:
{{- include "sentry.labels" . | nindent 4 }}
rules:
- apiGroups: ["sentry.io"]
resources: ["tenants"]
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
- apiGroups: ["sentry.io"]
resources: ["tenants/status"]
verbs: ["get", "update", "patch"]
- apiGroups: [""]
resources: ["secrets"]
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: {{ .Release.Name }}-tenant-operator
labels:
{{- include "sentry.labels" . | nindent 4 }}
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: {{ .Release.Name }}-tenant-operator
subjects:
- kind: ServiceAccount
name: {{ .Release.Name }}-tenant-operator
namespace: {{ .Release.Namespace }}
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ .Release.Name }}-tenant-operator
labels:
{{- include "sentry.labels" . | nindent 4 }}
{{- include "sentry.selectorLabels" (list $ "tenant-operator") | nindent 4 }}
spec:
# One replica -- see deploy/operator/cmd/tenant-operator/main.go's
# comment: no leader election yet, a second replica could
# double-generate a Secret.
replicas: 1
selector:
matchLabels:
{{- include "sentry.selectorLabels" (list $ "tenant-operator") | nindent 6 }}
template:
metadata:
labels:
{{- include "sentry.selectorLabels" (list $ "tenant-operator") | nindent 8 }}
spec:
serviceAccountName: {{ .Release.Name }}-tenant-operator
containers:
- name: tenant-operator
image: "{{ .Values.tenantOperator.image.repository }}:{{ .Values.tenantOperator.image.tag }}"
imagePullPolicy: {{ .Values.global.imagePullPolicy }}
ports:
- name: metrics
containerPort: 8080
- name: probes
containerPort: 8081
readinessProbe:
httpGet:
path: /readyz
port: probes
initialDelaySeconds: 5
periodSeconds: 5
livenessProbe:
httpGet:
path: /healthz
port: probes
initialDelaySeconds: 10
periodSeconds: 10
resources:
{{- toYaml .Values.tenantOperator.resources | nindent 12 }}
{{- end }}
+14
View File
@@ -0,0 +1,14 @@
{{- if .Values.enterprise.enabled }}
{{- range .Values.tenants }}
---
apiVersion: sentry.io/v1alpha1
kind: Tenant
metadata:
name: {{ .name }}
labels:
{{- include "sentry.labels" $ | nindent 4 }}
spec:
displayName: {{ .displayName | default .name | quote }}
suspended: {{ .suspended | default false }}
{{- end }}
{{- end }}
+43
View File
@@ -0,0 +1,43 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ .Release.Name }}-web
labels:
{{- include "sentry.labels" . | nindent 4 }}
{{- include "sentry.selectorLabels" (list $ "web") | nindent 4 }}
spec:
replicas: {{ .Values.web.replicas }}
selector:
matchLabels:
{{- include "sentry.selectorLabels" (list $ "web") | nindent 6 }}
template:
metadata:
labels:
{{- include "sentry.selectorLabels" (list $ "web") | nindent 8 }}
spec:
containers:
- name: web
# No env vars here -- see values.yaml's web.builtWith* comment:
# this is a static build, its API base URLs are baked into the
# image, not configurable at the Deployment level.
image: "{{ .Values.web.image.repository }}:{{ .Values.web.image.tag }}"
imagePullPolicy: {{ .Values.global.imagePullPolicy }}
ports:
- name: http
containerPort: 3000
resources:
{{- toYaml .Values.web.resources | nindent 12 }}
---
apiVersion: v1
kind: Service
metadata:
name: {{ .Release.Name }}-web
labels:
{{- include "sentry.labels" . | nindent 4 }}
{{- include "sentry.selectorLabels" (list $ "web") | nindent 4 }}
spec:
selector:
{{- include "sentry.selectorLabels" (list $ "web") | nindent 4 }}
ports:
- name: http
port: 3000