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,12 @@
|
||||
apiVersion: v2
|
||||
name: sentry
|
||||
description: >-
|
||||
Sentry: Kubernetes-native distributed log aggregation. Phase 4 adds
|
||||
multi-tenant-aware deployment (per-tenant ClickHouse credential Secrets
|
||||
via the tenant-operator, optional enterprise-auth) on top of the same
|
||||
single-cluster topology Phases 0-3 ran under docker-compose -- see
|
||||
deploy/README.md for what "multi-tenant-aware" does and doesn't mean
|
||||
here.
|
||||
type: application
|
||||
version: 0.4.0
|
||||
appVersion: "phase-4"
|
||||
@@ -0,0 +1,85 @@
|
||||
# deploy/helm/sentry
|
||||
|
||||
A Helm chart covering every `docker-compose.yml` service (Redpanda,
|
||||
ClickHouse, Postgres, ingest, search, api, alerting, web) plus, when
|
||||
`enterprise.enabled: true`: enterprise-auth, the `deploy/operator`
|
||||
tenant-operator, and `Tenant` CRs from `values.tenants`. See
|
||||
`/deploy/README.md` for what "multi-tenant-aware" does and doesn't mean
|
||||
at this layer, and its verification-status section before trusting this
|
||||
against a real cluster.
|
||||
|
||||
This chart never builds images -- push every image its `values.yaml`
|
||||
references to a registry the cluster can pull from first, same division
|
||||
of labor as `docker compose build` vs. `docker compose up`.
|
||||
|
||||
## Startup ordering
|
||||
|
||||
`docker-compose.yml` uses `depends_on: condition: service_healthy` /
|
||||
`service_completed_successfully` to sequence startup (e.g. `api` waits
|
||||
for `clickhouse-migrate` to actually finish, not just for `clickhouse` to
|
||||
be reachable). This chart approximates that more loosely:
|
||||
|
||||
- Migration Jobs (`clickhouse-migrate`, `metadata-migrate`,
|
||||
`redpanda-provision`) are plain `Job` resources (not Helm hooks --
|
||||
making the StatefulSets they depend on into hooks too, to get
|
||||
ordering, would break `helm upgrade`/`helm uninstall`'s normal
|
||||
ownership tracking of stateful resources, a worse tradeoff), with
|
||||
`backoffLimit: 6` so they retry a few times if their dependency isn't
|
||||
up yet.
|
||||
- App Deployments get an `initContainer` that busy-waits for their
|
||||
dependency's **TCP port**, not for a specific Job's completion (see
|
||||
`templates/_helpers.tpl`'s `sentry.waitForTCP`) -- this covers "is
|
||||
ClickHouse/Postgres/Redpanda up" but not "has the migration Job
|
||||
actually finished."
|
||||
- The gap that leaves (a pod starts before its migration has completed)
|
||||
is covered by every Go service here already calling `os.Exit(1)` on a
|
||||
failed startup DB ping (see e.g. `api/cmd/api/main.go`) --
|
||||
Kubernetes' pod restart policy retries with backoff until the schema
|
||||
is ready. This is a real, working, but *looser* guarantee than
|
||||
docker-compose's explicit ordering -- documented here rather than
|
||||
implied to be equivalent.
|
||||
|
||||
## Trying the two-tenant example
|
||||
|
||||
```sh
|
||||
# Quote each --set value -- zsh globs an unquoted tenants[0] as a
|
||||
# pattern and fails with "no matches found."
|
||||
helm install sentry . --include-crds \
|
||||
--set enterprise.enabled=true \
|
||||
--set tenantOperator.enabled=true \
|
||||
--set 'tenants[0].name=acme' --set 'tenants[0].displayName=Acme Corp' \
|
||||
--set 'tenants[1].name=globex' --set 'tenants[1].displayName=Globex Corporation'
|
||||
|
||||
kubectl get tenants
|
||||
kubectl get secret sentry-tenant-acme-clickhouse sentry-tenant-globex-clickhouse
|
||||
```
|
||||
|
||||
This proves the K8s-side half of Phase 4's "two tenants... with their
|
||||
own users, roles, dashboards" exit criteria (`/CLAUDE.md`) -- a real
|
||||
per-tenant credential Secret exists for each. It does **not** by itself
|
||||
give either tenant a working login, dashboard, or ClickHouse database:
|
||||
those need the OIDC/SAML login handlers, `internal/tenantprovision`, and
|
||||
`internal/rbacstore` wiring the Phase 4 task 5 summary names as deferred.
|
||||
|
||||
## `web`'s image needs rebuilding per environment
|
||||
|
||||
`web` is a static SvelteKit build (`adapter-static`) -- its three API
|
||||
base URLs (`VITE_API_BASE_URL`/`VITE_ALERTING_API_BASE_URL`/
|
||||
`VITE_ENTERPRISE_AUTH_BASE_URL`) are baked in at **image build time**
|
||||
(`web/Dockerfile`'s build args), not read from the container's
|
||||
environment at runtime. `values.yaml`'s `web.builtWithApiBaseURL` etc.
|
||||
document what the image you point `web.image` at needs to have been
|
||||
built with (an Ingress hostname, a LoadBalancer IP, etc.) -- this chart
|
||||
has no Ingress resources and can't itself act on those values; rebuild
|
||||
`web`'s image with the right build args for wherever this release is
|
||||
actually reachable from a browser before pointing real users at it.
|
||||
|
||||
## Validating without a cluster
|
||||
|
||||
```sh
|
||||
helm lint .
|
||||
helm template sentry . --include-crds > /tmp/rendered.yaml
|
||||
```
|
||||
|
||||
See `/deploy/README.md`'s verification section for what was actually
|
||||
checked this way (and what wasn't -- no live cluster was available).
|
||||
@@ -0,0 +1,93 @@
|
||||
# Hand-written, not `controller-gen crd` output -- see
|
||||
# api/v1alpha1/groupversion_info.go's doc comment. Kept in sync with
|
||||
# api/v1alpha1/tenant_types.go by hand; api/v1alpha1/api_test.go's
|
||||
# round-trip tests catch a Go/YAML drift in the *shape* of the types,
|
||||
# but not a drift in this file's field descriptions/validation rules --
|
||||
# review both together when either changes.
|
||||
apiVersion: apiextensions.k8s.io/v1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
name: tenants.sentry.io
|
||||
spec:
|
||||
group: sentry.io
|
||||
names:
|
||||
kind: Tenant
|
||||
listKind: TenantList
|
||||
plural: tenants
|
||||
singular: tenant
|
||||
scope: Namespaced
|
||||
versions:
|
||||
- name: v1alpha1
|
||||
served: true
|
||||
storage: true
|
||||
subresources:
|
||||
status: {}
|
||||
additionalPrinterColumns:
|
||||
- name: Phase
|
||||
type: string
|
||||
jsonPath: .status.phase
|
||||
- name: Age
|
||||
type: date
|
||||
jsonPath: .metadata.creationTimestamp
|
||||
schema:
|
||||
openAPIV3Schema:
|
||||
type: object
|
||||
description: >-
|
||||
Tenant is the K8s-native representation of one Sentry tenant's
|
||||
deployment-topology state -- see
|
||||
deploy/operator/internal/controller/tenant_controller.go's doc
|
||||
comment for what the controller does and does not manage.
|
||||
properties:
|
||||
apiVersion:
|
||||
type: string
|
||||
kind:
|
||||
type: string
|
||||
metadata:
|
||||
type: object
|
||||
spec:
|
||||
type: object
|
||||
required: [displayName]
|
||||
properties:
|
||||
displayName:
|
||||
type: string
|
||||
description: Human-readable only -- the object's own metadata.name is the stable identifier.
|
||||
suspended:
|
||||
type: boolean
|
||||
description: Admin-facing lever for the Suspended phase.
|
||||
default: false
|
||||
status:
|
||||
type: object
|
||||
properties:
|
||||
phase:
|
||||
type: string
|
||||
enum: [Provisioning, Active, Suspended, Deprovisioning]
|
||||
clickHouseDatabaseName:
|
||||
type: string
|
||||
clickHouseSecretRef:
|
||||
type: string
|
||||
tantivyIndexPath:
|
||||
type: string
|
||||
observedGeneration:
|
||||
type: integer
|
||||
format: int64
|
||||
conditions:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
required: [type, status]
|
||||
properties:
|
||||
type:
|
||||
type: string
|
||||
status:
|
||||
type: string
|
||||
enum: ["True", "False", "Unknown"]
|
||||
reason:
|
||||
type: string
|
||||
message:
|
||||
type: string
|
||||
observedGeneration:
|
||||
type: integer
|
||||
format: int64
|
||||
lastTransitionTime:
|
||||
type: string
|
||||
format: date-time
|
||||
@@ -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
|
||||
@@ -0,0 +1,180 @@
|
||||
# Default values for the sentry chart. See deploy/helm/sentry/README.md
|
||||
# for the multi-tenant-specific values (enterprise.*, tenants) and what
|
||||
# "multi-tenant-aware" does and doesn't mean at this layer.
|
||||
#
|
||||
# Image repositories default to locally-built tags matching each
|
||||
# service's docker-compose.yml container_name, minus the "sentry-"
|
||||
# container_name prefix duplication -- push these to a registry this
|
||||
# cluster can actually pull from before installing; this chart never
|
||||
# builds images itself (same division of labor as docker-compose.yml:
|
||||
# `docker compose build` vs. `docker compose up`).
|
||||
|
||||
global:
|
||||
imagePullPolicy: IfNotPresent
|
||||
|
||||
redpanda:
|
||||
image:
|
||||
repository: docker.redpanda.com/redpandadata/redpanda
|
||||
tag: v24.2.7
|
||||
persistence:
|
||||
size: 10Gi
|
||||
resources: {}
|
||||
# Built from ./transport (docker-compose.yml's redpanda-provision
|
||||
# service) -- the one-shot topic-creation Job below.
|
||||
provisionImage:
|
||||
repository: sentry-redpanda-provision
|
||||
tag: latest
|
||||
|
||||
clickhouse:
|
||||
image:
|
||||
repository: clickhouse/clickhouse-server
|
||||
tag: "24.8"
|
||||
persistence:
|
||||
size: 20Gi
|
||||
resources: {}
|
||||
# Leave empty to auto-generate and persist across upgrades -- see
|
||||
# templates/secrets.yaml's stableSecretValue helper.
|
||||
password: ""
|
||||
# Built from ./storage (docker-compose.yml's clickhouse-migrate
|
||||
# service) -- the one-shot schema-migration Job.
|
||||
migrateImage:
|
||||
repository: sentry-clickhouse-migrate
|
||||
tag: latest
|
||||
|
||||
postgres:
|
||||
image:
|
||||
repository: postgres
|
||||
tag: 16-alpine
|
||||
persistence:
|
||||
size: 10Gi
|
||||
resources: {}
|
||||
password: ""
|
||||
auditWriterPassword: ""
|
||||
# Built from ./metadata (docker-compose.yml's metadata-migrate
|
||||
# service) -- the one-shot schema-migration Job.
|
||||
migrateImage:
|
||||
repository: sentry-metadata-migrate
|
||||
tag: latest
|
||||
|
||||
ingest:
|
||||
image:
|
||||
repository: sentry-ingest
|
||||
tag: latest
|
||||
replicas: 1
|
||||
resources: {}
|
||||
# mTLS server cert/key/CA -- see hack/dev-certs/generate.sh for the
|
||||
# dev equivalent of what this Secret must contain
|
||||
# (server.pem/server-key.pem/ca.pem) in a real deployment. Unlike
|
||||
# docker-compose.yml's bind-mounted ./hack/dev-certs/out, a cluster
|
||||
# deployment supplies this as a real Secret -- named here, not
|
||||
# generated by this chart (cert issuance is out of scope, same
|
||||
# "boring, well-understood" preference as everywhere else in this
|
||||
# repo -- use cert-manager or an equivalent, don't hand-roll it here).
|
||||
tlsSecretName: ""
|
||||
|
||||
search:
|
||||
image:
|
||||
repository: sentry-search
|
||||
tag: latest
|
||||
# Pinned to 1: search consumes the same Redpanda topic ingest's
|
||||
# consumer does with its own offset tracking (see search/README.md).
|
||||
# A second replica would form a second, independent consumer instance
|
||||
# against the same partitions with no coordination -- correctness,
|
||||
# not just resource waste, is the reason this isn't a `replicas` knob
|
||||
# yet. Matches CLAUDE.md's Phase 4 non-goal: "no general multi-cluster
|
||||
# orchestration."
|
||||
replicas: 1
|
||||
resources: {}
|
||||
persistence:
|
||||
size: 20Gi
|
||||
|
||||
api:
|
||||
image:
|
||||
repository: sentry-api
|
||||
tag: latest
|
||||
replicas: 2
|
||||
resources: {}
|
||||
|
||||
alerting:
|
||||
image:
|
||||
repository: sentry-alerting
|
||||
tag: latest
|
||||
# Pinned to 1 for the same reason as search: rulestore.ClaimDueRules
|
||||
# has no leader-election/partitioning story for multiple evaluator
|
||||
# replicas yet -- two would both try to claim and evaluate the same
|
||||
# due rules. Named explicitly rather than silently defaulted, since
|
||||
# it's the kind of knob someone reasonably expects to just work.
|
||||
replicas: 1
|
||||
resources: {}
|
||||
# See templates/alerting.yaml's comment -- only meaningful when
|
||||
# enterprise.enabled is true. Empty by default.
|
||||
apiServiceToken: ""
|
||||
|
||||
web:
|
||||
image:
|
||||
repository: sentry-web
|
||||
tag: latest
|
||||
replicas: 2
|
||||
resources: {}
|
||||
# NOT wired to any Deployment env var -- web is a static SvelteKit
|
||||
# build (adapter-static, see web/package.json), and VITE_API_BASE_URL/
|
||||
# VITE_ALERTING_API_BASE_URL/VITE_ENTERPRISE_AUTH_BASE_URL are baked in
|
||||
# at *image build time* (docker-compose.yml's web.build.args), not
|
||||
# read at container runtime. Deploying this chart into a real cluster
|
||||
# means rebuilding the web image with these three build args pointed
|
||||
# at wherever api/alerting/enterprise-auth are actually reachable from
|
||||
# a browser (an Ingress host, a LoadBalancer IP, etc.) -- this section
|
||||
# exists to document that requirement, not because the chart can act
|
||||
# on it.
|
||||
builtWithApiBaseURL: "http://localhost:8080"
|
||||
builtWithAlertingApiBaseURL: "http://localhost:8081"
|
||||
builtWithEnterpriseAuthBaseURL: "http://localhost:8082"
|
||||
|
||||
# enterprise-auth (commercial license) + the tenant-operator that
|
||||
# reconciles the Tenant CRD -- both off by default, matching
|
||||
# docker-compose.yml's own "included, not wired into enforcement by
|
||||
# default" stance (see its enterprise-auth service comment) and
|
||||
# enterprise/README.md's "Status" section on what's built vs. deferred.
|
||||
enterprise:
|
||||
enabled: false
|
||||
image:
|
||||
repository: sentry-enterprise-auth
|
||||
tag: latest
|
||||
replicas: 1
|
||||
resources: {}
|
||||
# Leave empty to auto-generate (>= 32 bytes) and persist across
|
||||
# upgrades -- see templates/secrets.yaml.
|
||||
sessionSigningKey: ""
|
||||
oidc:
|
||||
issuerURL: ""
|
||||
clientID: ""
|
||||
clientSecret: ""
|
||||
redirectURL: ""
|
||||
saml:
|
||||
entityID: ""
|
||||
acsURL: ""
|
||||
idpMetadataURL: ""
|
||||
|
||||
# Installs deploy/operator (the Tenant CRD controller) alongside this
|
||||
# chart. Only meaningful when enterprise.enabled is also true --
|
||||
# gated on that, not a separate flag, since a Tenant CR with no
|
||||
# enterprise-auth deployed to consume its Secret has nothing to do.
|
||||
tenantOperator:
|
||||
enabled: false
|
||||
image:
|
||||
repository: sentry-tenant-operator
|
||||
tag: latest
|
||||
resources: {}
|
||||
|
||||
# One entry per tenant to provision -- rendered as Tenant CRs
|
||||
# (templates/tenants.yaml), reconciled by the tenant-operator into a
|
||||
# per-tenant ClickHouse credential Secret. See
|
||||
# deploy/operator/internal/controller/tenant_controller.go's doc comment
|
||||
# for exactly what that does and doesn't set up. Empty by default; a
|
||||
# real two-tenant deployment (Phase 4's exit criteria) sets e.g.:
|
||||
# tenants:
|
||||
# - name: acme
|
||||
# displayName: "Acme Corp"
|
||||
# - name: globex
|
||||
# displayName: "Globex Corporation"
|
||||
tenants: []
|
||||
Reference in New Issue
Block a user