diff --git a/contrib/helm/README.md b/contrib/helm/README.md index f2659fd01..eda37cd03 100644 --- a/contrib/helm/README.md +++ b/contrib/helm/README.md @@ -40,6 +40,8 @@ export ENCRYPTION_KEY=$(openssl rand -base64 32) export COOKIE_SECRET=$(openssl rand -base64 32) export PASSWORD_PEPPER=$(openssl rand -base64 32) export TRUST_TOKEN_SECRET=$(openssl rand -base64 32) +openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048 \ + -out oauth2_signing_key.pem ``` #### Download remote dependencies @@ -57,6 +59,7 @@ helm install my-probo ./charts/probo \ --set probo.auth.cookieSecret="$COOKIE_SECRET" \ --set probo.auth.passwordPepper="$PASSWORD_PEPPER" \ --set probo.trustAuth.tokenSecret="$TRUST_TOKEN_SECRET" \ + --set-file probo.oauth2.signingKey="./oauth2_signing_key.pem" \ --set postgresql.enabled=true \ --set postgresql.auth.postgresUser="probod" \ --set postgresql.auth.postgresPassword="your-db-password" \ @@ -76,6 +79,7 @@ helm install my-probo ./charts/probo \ --set probo.auth.cookieSecret="$COOKIE_SECRET" \ --set probo.auth.passwordPepper="$PASSWORD_PEPPER" \ --set probo.trustAuth.tokenSecret="$TRUST_TOKEN_SECRET" \ + --set-file probo.oauth2.signingKey="./oauth2_signing_key.pem" \ --set probo.mailer.smtp.password="smtp-password" \ --set postgresql.enabled=true \ --set postgresql.auth.postgresPassword="probod" \ @@ -174,6 +178,7 @@ All deployments require: - `probo.auth.cookieSecret` - For session management - `probo.auth.passwordPepper` - For password hashing - `probo.trustAuth.tokenSecret` - For trust center tokens +- `probo.oauth2.signingKey` - PEM private key used to sign OAuth2 tokens - `postgresql.host` - PostgreSQL server hostname - `postgresql.password` - Database password - `s3.accessKeyId` - S3 access credentials @@ -200,6 +205,7 @@ helm install my-probo ././charts/probo \ --set probo.auth.cookieSecret="$COOKIE_SECRET" \ --set probo.auth.passwordPepper="$PASSWORD_PEPPER" \ --set probo.trustAuth.tokenSecret="$TRUST_TOKEN_SECRET" \ + --set-file probo.oauth2.signingKey="./oauth2_signing_key.pem" \ --set postgresql.host="mydb.abc123.us-east-1.rds.amazonaws.com" \ --set postgresql.password="" \ --set s3.region="us-east-1" \ @@ -225,6 +231,7 @@ helm install my-probo ././charts/probo \ --set probo.auth.cookieSecret="$COOKIE_SECRET" \ --set probo.auth.passwordPepper="$PASSWORD_PEPPER" \ --set probo.trustAuth.tokenSecret="$TRUST_TOKEN_SECRET" \ + --set-file probo.oauth2.signingKey="./oauth2_signing_key.pem" \ --set postgresql.host="10.0.0.5" \ --set postgresql.password="" \ --set s3.endpoint="https://storage.googleapis.com" \ @@ -235,7 +242,8 @@ helm install my-probo ././charts/probo \ ### Azure - PostgreSQL: Azure Database for PostgreSQL -- Storage: Azure Blob Storage (with S3 compatibility) +- Storage: Prefer native S3-compatible backends (Azure Blob via S3 proxies is + currently not officially supported) - Kubernetes: Azure Kubernetes Service (AKS) #### Example @@ -244,12 +252,13 @@ helm install my-probo ././charts/probo \ ```bash # Prerequisites: # - Azure Database for PostgreSQL instance -# - Azure Blob Storage container with S3 compatibility +# - Prefer AWS S3/GCS/Spaces (Azure Blob via S3 proxy is not officially supported) helm install my-probo ././charts/probo \ --set probo.encryptionKey="$ENCRYPTION_KEY" \ --set probo.auth.cookieSecret="$COOKIE_SECRET" \ --set probo.auth.passwordPepper="$PASSWORD_PEPPER" \ --set probo.trustAuth.tokenSecret="$TRUST_TOKEN_SECRET" \ + --set-file probo.oauth2.signingKey="./oauth2_signing_key.pem" \ --set postgresql.host="mydb.postgres.database.azure.com" \ --set postgresql.password="" \ --set s3.endpoint="https://.blob.core.windows.net" \ @@ -259,7 +268,9 @@ helm install my-probo ././charts/probo \ --set s3.usePathStyle=true ``` -> **Note:** Azure Blob Storage requires `s3.usePathStyle=true` to construct URLs correctly (path-style: `https://account.blob.core.windows.net/container/...` instead of virtual-hosted style: `https://bucket.account.blob.core.windows.net/...`). +> **Note:** `s3.usePathStyle=true` is necessary for some S3-compatible +> providers, but it does not address known Azure Blob metadata incompatibilities +> when used behind S3 proxies. ### DigitalOcean - PostgreSQL: Managed PostgreSQL Database @@ -277,6 +288,7 @@ helm install my-probo ././charts/probo \ --set probo.auth.cookieSecret="$COOKIE_SECRET" \ --set probo.auth.passwordPepper="$PASSWORD_PEPPER" \ --set probo.trustAuth.tokenSecret="$TRUST_TOKEN_SECRET" \ + --set-file probo.oauth2.signingKey="./oauth2_signing_key.pem" \ --set postgresql.host="db-postgresql-nyc1-12345.ondigitalocean.com" \ --set postgresql.password="" \ --set s3.region="nyc3" \ @@ -391,6 +403,7 @@ spec: | metrics.serviceMonitor.relabelings | list | `[]` | Relabeling configs for the ServiceMonitor | | probo.baseUrl | string | `"probo.example.com"` | Public hostname where Probo will be accessible | | probo.encryptionKey | string | `""` | **REQUIRED** Base64-encoded encryption key (generate with: openssl rand -base64 32) | +| probo.oauth2.signingKey | string | `""` | **REQUIRED** PEM private key for OAuth2 signing (set with --set-file) | | probo.service.port | int | `8080` | Probo application service port | | probo.metrics.port | int | `8081` | Probo metrics service port | | probo.tracing.enabled | bool | `false` | Enable OpenTelemetry tracing | diff --git a/contrib/helm/charts/probo/README.md b/contrib/helm/charts/probo/README.md index f96981a7c..5af7917ae 100644 --- a/contrib/helm/charts/probo/README.md +++ b/contrib/helm/charts/probo/README.md @@ -10,6 +10,9 @@ This Helm chart deploys Probo - an open-source SOC-2 compliance platform - on Ku - S3 or S3-compatible object storage (AWS S3, GCS, DigitalOcean Spaces, SeaweedFS, etc.) - OpenSSL installed (for generating secrets) +> Azure Blob behind S3 compatibility proxies currently has known metadata +> compatibility issues. Prefer native S3-compatible backends for production. + ## Installing the Chart ### From OCI Registry @@ -21,6 +24,7 @@ helm install probo oci://artifact.probo.inc/probo/probo --version " passwordPepper: "" + oauth2: + signingKey: "" trustAuth: cookieDomain: "example.com" tokenSecret: "" @@ -156,11 +167,20 @@ The following parameters **must** be configured: | `probo.auth.cookieSecret` | Cookie signing secret (32+ bytes) | | `probo.auth.passwordPepper` | Password hashing pepper (32+ bytes) | | `probo.trustAuth.tokenSecret` | Trust token secret (32+ bytes) | +| `probo.oauth2.signingKey` | PEM-encoded OAuth2 private signing key | | `postgresql.host` | PostgreSQL hostname | | `postgresql.password` | PostgreSQL password | | `s3.accessKeyId` | S3 access key ID | | `s3.secretAccessKey` | S3 secret access key | +### Secret Format Quick Reference + +- `probo.encryptionKey`, `probo.auth.cookieSecret`, + `probo.auth.passwordPepper`, `probo.trustAuth.tokenSecret`: + base64-encoded 32-byte secrets (for example: `openssl rand -base64 32`) +- `probo.oauth2.signingKey`: PEM-encoded private key (`--set-file` strongly + recommended) + ### Key Configuration Parameters | Parameter | Description | Default | @@ -169,6 +189,7 @@ The following parameters **must** be configured: | `image.tag` | Probo image tag | Chart appVersion | | `replicaCount` | Number of Probo replicas | `1` | | `probo.baseUrl` | Public baseUrl | `probo.example.com` | +| `probo.oauth2.signingKey` | OAuth2 signing key (PEM private key) | `""` (required) | | `postgresql.host` | PostgreSQL host | `""` (required) | | `postgresql.port` | PostgreSQL port | `5432` | | `postgresql.database` | Database name | `probod` | @@ -178,7 +199,7 @@ The following parameters **must** be configured: | `s3.bucket` | S3 bucket name | `probod` | | `s3.region` | AWS region | `us-east-1` | | `s3.endpoint` | S3 endpoint (for S3-compatible) | `""` | -| `s3.usePathStyle` | Use path-style URLs (required for Azure Blob Storage) | `false` | +| `s3.usePathStyle` | Use path-style URLs for selected S3-compatible backends | `false` | | `chrome.enabled` | Deploy Chrome | `true` | | `chrome.external.addr` | External Chrome (if disabled) | `""` | | `ingress.enabled` | Enable ingress | `false` | @@ -204,9 +225,25 @@ The chart deploys the following: Database migrations run automatically when Probo starts. No manual intervention is required. +For managed PostgreSQL providers (notably PostgreSQL >= 15), ensure the +Probo role can own and modify the `public` schema before first startup: + +```sql +ALTER SCHEMA public OWNER TO probod; +GRANT ALL ON SCHEMA public TO probod; +``` + +Probo migrations also require these extensions to be installable: + +- `citext` +- `pgcrypto` +- `unaccent` +- `pg_stat_statements` + ### TLS/SSL Configuration -For secure PostgreSQL connections, you can provide a CA certificate bundle in two ways: +For secure PostgreSQL connections, you can provide a CA certificate bundle in +two ways: 1. **Inline CA Bundle** (`postgresql.caBundle`): Provide the certificate content directly in values.yaml ```yaml @@ -234,7 +271,8 @@ For secure PostgreSQL connections, you can provide a CA certificate bundle in tw readOnly: true ``` -**Note:** Using `caBundlePath` is recommended for large CA bundles (e.g., system CA bundles) as it avoids environment variable size limitations. +**Note:** Using `caBundlePath` is recommended for large CA bundles (for +example system CA bundles) as it avoids environment variable size limitations. ### Backup @@ -277,6 +315,15 @@ Check the Probo logs for database connection errors. The application will fail t Check the Probo logs for S3 connection errors when uploading files. +### `IP geolocation table is empty` warning + +If startup logs show: + +`IP geolocation table is empty; run geoloc-import to populate it` + +the instance is running correctly, but geolocation data is missing. Populate +it by running the `geoloc-import` command in your operational workflow. + ## Examples ### AWS Deployment diff --git a/contrib/helm/charts/probo/templates/_helpers.tpl b/contrib/helm/charts/probo/templates/_helpers.tpl index dfdfae705..d49288eed 100644 --- a/contrib/helm/charts/probo/templates/_helpers.tpl +++ b/contrib/helm/charts/probo/templates/_helpers.tpl @@ -157,3 +157,34 @@ Chrome DevTools Protocol address {{- .Values.chrome.external.addr | required "chrome.external.addr is required when chrome.enabled=false" }} {{- end }} {{- end }} + +{{/* +Validate a required 32-byte base64 secret. +*/}} +{{- define "probo.requireBase64Key32" -}} +{{- $name := .name -}} +{{- $value := .value | required (printf "%s is required" $name) -}} +{{- if not (regexMatch "^[A-Za-z0-9+/]{43}=$" $value) -}} +{{- fail (printf "%s must be a base64-encoded 32-byte secret (example: openssl rand -base64 32)" $name) -}} +{{- end -}} +{{- $decoded := b64dec $value -}} +{{- if ne (len $decoded) 32 -}} +{{- fail (printf "%s must decode to exactly 32 bytes" $name) -}} +{{- end -}} +{{- $value -}} +{{- end }} + +{{/* +Validate a required PEM private key. +*/}} +{{- define "probo.requirePEMPrivateKey" -}} +{{- $name := .name -}} +{{- $value := .value | required (printf "%s is required" $name) -}} +{{- if not (contains "-----BEGIN" $value) -}} +{{- fail (printf "%s must be a PEM-encoded private key" $name) -}} +{{- end -}} +{{- if not (contains "PRIVATE KEY-----" $value) -}} +{{- fail (printf "%s must be a PEM-encoded private key" $name) -}} +{{- end -}} +{{- $value -}} +{{- end }} diff --git a/contrib/helm/charts/probo/templates/deployment.yaml b/contrib/helm/charts/probo/templates/deployment.yaml index a8fc049b3..1c813ba32 100644 --- a/contrib/helm/charts/probo/templates/deployment.yaml +++ b/contrib/helm/charts/probo/templates/deployment.yaml @@ -11,6 +11,7 @@ spec: selector: matchLabels: {{- include "probo.selectorLabels" . | nindent 6 }} + app.kubernetes.io/component: server template: metadata: annotations: @@ -20,6 +21,7 @@ spec: {{- end }} labels: {{- include "probo.labels" . | nindent 8 }} + app.kubernetes.io/component: server {{- with .Values.podLabels }} {{- toYaml . | nindent 8 }} {{- end }} @@ -75,7 +77,7 @@ spec: value: {{ join "," .Values.probo.cors.allowedOrigins | quote }} # PostgreSQL Database - name: PG_ADDR - value: {{ if .Values.postgresql.enabled }}{{ printf "%s-postgresql-demo:5432" .Release.Name | quote }}{{ else }}{{ .Values.probo.pg.addr | required "probo.pg.addr is required when postgresql.enabled=false" | quote }}{{ end }} + value: {{ printf "%s:%v" (include "probo.postgresql.host" .) (include "probo.postgresql.port" .) | quote }} - name: PG_USERNAME value: {{ include "probo.postgresql.username" . | quote }} - name: PG_PASSWORD @@ -128,6 +130,11 @@ spec: secretKeyRef: name: {{ include "probo.fullname" . }} key: password-pepper + - name: OAUTH2_SERVER_SIGNING_KEY + valueFrom: + secretKeyRef: + name: {{ include "probo.fullname" . }} + key: oauth2-signing-key - name: AUTH_PASSWORD_ITERATIONS value: {{ .Values.probo.auth.passwordIterations | quote }} {{- if .Values.probo.saml.enabled }} @@ -271,6 +278,13 @@ spec: value: {{ .Values.probo.customDomains.acme.email | quote }} - name: ACME_KEY_TYPE value: {{ .Values.probo.customDomains.acme.keyType | quote }} + {{- if .Values.probo.customDomains.acme.accountKey }} + - name: ACME_ACCOUNT_KEY + valueFrom: + secretKeyRef: + name: {{ include "probo.fullname" . }} + key: acme-account-key + {{- end }} {{- end }} # Connectors (Slack OAuth2) {{- range .Values.probo.connectors }} diff --git a/contrib/helm/charts/probo/templates/secret.yaml b/contrib/helm/charts/probo/templates/secret.yaml index 75933ac77..7ac534e94 100644 --- a/contrib/helm/charts/probo/templates/secret.yaml +++ b/contrib/helm/charts/probo/templates/secret.yaml @@ -17,10 +17,14 @@ stringData: s3-secret-key: {{ include "probo.s3.secretAccessKey" . | quote }} # Application secrets - encryption-key: {{ .Values.probo.encryptionKey | required "probo.encryptionKey is required" | quote }} - cookie-secret: {{ .Values.probo.auth.cookieSecret | required "probo.auth.cookieSecret is required" | quote }} - password-pepper: {{ .Values.probo.auth.passwordPepper | required "probo.auth.passwordPepper is required" | quote }} - trust-token-secret: {{ .Values.probo.trustAuth.tokenSecret | required "probo.trustAuth.tokenSecret is required" | quote }} + encryption-key: {{ include "probo.requireBase64Key32" (dict "name" "probo.encryptionKey" "value" .Values.probo.encryptionKey) | quote }} + cookie-secret: {{ include "probo.requireBase64Key32" (dict "name" "probo.auth.cookieSecret" "value" .Values.probo.auth.cookieSecret) | quote }} + password-pepper: {{ include "probo.requireBase64Key32" (dict "name" "probo.auth.passwordPepper" "value" .Values.probo.auth.passwordPepper) | quote }} + oauth2-signing-key: {{ include "probo.requirePEMPrivateKey" (dict "name" "probo.oauth2.signingKey" "value" .Values.probo.oauth2.signingKey) | quote }} + trust-token-secret: {{ include "probo.requireBase64Key32" (dict "name" "probo.trustAuth.tokenSecret" "value" .Values.probo.trustAuth.tokenSecret) | quote }} + {{- if .Values.probo.customDomains.acme.accountKey }} + acme-account-key: {{ include "probo.requirePEMPrivateKey" (dict "name" "probo.customDomains.acme.accountKey" "value" .Values.probo.customDomains.acme.accountKey) | quote }} + {{- end }} # Optional SMTP credentials {{- if .Values.probo.mailer.smtp.user }} diff --git a/contrib/helm/charts/probo/templates/service.yaml b/contrib/helm/charts/probo/templates/service.yaml index db0309fad..03f203ca1 100644 --- a/contrib/helm/charts/probo/templates/service.yaml +++ b/contrib/helm/charts/probo/templates/service.yaml @@ -4,6 +4,7 @@ metadata: name: {{ include "probo.fullname" . }} labels: {{- include "probo.labels" . | nindent 4 }} + app.kubernetes.io/component: server {{- with .Values.service.annotations }} annotations: {{- toYaml . | nindent 4 }} @@ -42,3 +43,4 @@ spec: name: metrics selector: {{- include "probo.selectorLabels" . | nindent 4 }} + app.kubernetes.io/component: server diff --git a/contrib/helm/charts/probo/templates/servicemonitor.yaml b/contrib/helm/charts/probo/templates/servicemonitor.yaml index 49ffdff1f..f84916215 100644 --- a/contrib/helm/charts/probo/templates/servicemonitor.yaml +++ b/contrib/helm/charts/probo/templates/servicemonitor.yaml @@ -12,6 +12,7 @@ spec: selector: matchLabels: {{- include "probo.selectorLabels" . | nindent 6 }} + app.kubernetes.io/component: server endpoints: - port: metrics path: /metrics diff --git a/contrib/helm/charts/probo/values-production.yaml.example b/contrib/helm/charts/probo/values-production.yaml.example index e566d88a9..5978d1b86 100644 --- a/contrib/helm/charts/probo/values-production.yaml.example +++ b/contrib/helm/charts/probo/values-production.yaml.example @@ -126,6 +126,12 @@ probo: # REQUIRED: Generate with: openssl rand -base64 32 passwordPepper: "CHANGE_ME_PASSWORD_PEPPER" + oauth2: + # REQUIRED: PEM-encoded private key (RSA recommended) + # Example: openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048 > oauth2_signing_key.pem + # Then set with --set-file probo.oauth2.signingKey=oauth2_signing_key.pem + signingKey: "CHANGE_ME_OAUTH2_SIGNING_KEY_PEM" + # Trust center authentication trustAuth: cookieName: "TCT" @@ -190,6 +196,8 @@ probo: email: "admin@example.com" keyType: "EC256" insecureTls: false + # Optional persisted account key to avoid creating a new ACME account on restarts + accountKey: "" # External OAuth2 connectors (optional) connectors: @@ -227,7 +235,7 @@ postgresql: maxConnIdleTimeSeconds: 1800 maxConnLifetimeSeconds: 3600 # Uncomment if using TLS with custom CA - # caCertBundle: | + # caBundle: | # -----BEGIN CERTIFICATE----- # ... # -----END CERTIFICATE----- @@ -261,13 +269,8 @@ s3: # accessKeyId: "..." # Spaces access key # secretAccessKey: "..." # Spaces secret - # Azure Blob Storage example (uncomment and adjust): - # region: "eastus" - # bucket: "probo-container" - # endpoint: "https://mystorageaccount.blob.core.windows.net" - # accessKeyId: "mystorageaccount" # Storage account name - # secretAccessKey: "..." # Storage account access key - # usePathStyle: true # Required for Azure Blob Storage + # Azure Blob through S3-compat proxies has known metadata compatibility gaps. + # Prefer AWS S3 / GCS / Spaces for production until native Azure Blob support lands. # Chrome for PDF generation chrome: diff --git a/contrib/helm/charts/probo/values.yaml b/contrib/helm/charts/probo/values.yaml index 890ae9470..55f50489b 100644 --- a/contrib/helm/charts/probo/values.yaml +++ b/contrib/helm/charts/probo/values.yaml @@ -167,6 +167,8 @@ probo: # Public hostname where Probo will be accessible # This should match your ingress host baseUrl: probo.example.com + # REQUIRED: Generate with openssl rand -base64 32 + encryptionKey: "" service: port: 8080 @@ -183,12 +185,12 @@ probo: batchTimeout: 5 exportTimeout: 30 maxQueueSize: 2048 -# pg: -# addr: "probo-postgresql-demo-app:5432" -# username: "postgres" -# password: "probod" -# database: "probod" -# pool-size: 100 + + # OAuth2 signing key used by bootstrap to sign auth tokens + # REQUIRED: PEM-encoded private key (RSA is recommended) + # Generate with: openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048 + oauth2: + signingKey: "" # CORS configuration cors: @@ -208,7 +210,11 @@ probo: invitationTokenValidity: 3600 cookieName: "SSID" cookieDomain: "probo.example.com" + # REQUIRED: Generate with openssl rand -base64 32 + cookieSecret: "" cookieDuration: 24 + # REQUIRED: Generate with openssl rand -base64 32 + passwordPepper: "" passwordIterations: 1000000 # SAML authentication (optional) @@ -232,6 +238,7 @@ probo: tlsHttpServer: 443 # Trust token signing secret (REQUIRED, at least 32 bytes) # Generate with: openssl rand -base64 32 + tokenSecret: "" scope: "trust_center_readonly" tokenType: "trust_center_access" @@ -280,6 +287,9 @@ probo: email: "admin@example.com" keyType: "EC256" insecureTls: false + # Optional persisted ACME account key (PEM-encoded) + # If unset, a new account may be created on each restart + accountKey: "" # External OAuth2 connectors (optional) connectors: [] @@ -349,7 +359,7 @@ s3: endpoint: "" accessKeyId: "" # REQUIRED when seaweedfs.enabled=false: S3 access key secretAccessKey: "" # REQUIRED when seaweedfs.enabled=false: S3 secret key - # Use path-style URLs (required for Azure Blob Storage and some S3-compatible services) + # Use path-style URLs for selected S3-compatible services # When true: https://endpoint/bucket/key # When false (default): https://bucket.endpoint/key usePathStyle: false