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:
@@ -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
|
||||
@@ -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 }}
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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"
|
||||
@@ -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
|
||||
@@ -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 }}
|
||||
@@ -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 }}
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user