diff --git a/contrib/helm/.gitignore b/contrib/helm/.gitignore new file mode 100644 index 000000000..f905e3ad7 --- /dev/null +++ b/contrib/helm/.gitignore @@ -0,0 +1,22 @@ +# General files for the project +pkg/* +*.pyc +bin/* +.project +/.bin +/_test/secrets/*.json + +# OSX leaves these everywhere on SMB shares +._* + +# OSX trash +.DS_Store + +# Files generated by JetBrains IDEs, e.g. IntelliJ IDEA +.idea/ +*.iml + +# Vscode files +.vscode + +charts/probo/charts diff --git a/contrib/helm/README.md b/contrib/helm/README.md new file mode 100644 index 000000000..3db045ef1 --- /dev/null +++ b/contrib/helm/README.md @@ -0,0 +1,474 @@ +# Probo Kubernetes Deployment + +This directory contains the Helm chart for deploying Probo on Kubernetes with external managed services. + +## Quick Links + +- [Helm Chart Documentation](charts/probo/README.md) +- [Values Reference](charts/probo/values.yaml) + +## Prerequisites + +Before deploying Probo, ensure you have: + +1. **Kubernetes Cluster** - Version 1.23+ +2. **Helm** - Version 3.8+ +3. **PostgreSQL Database** - Managed service (AWS RDS, GCP Cloud SQL, Azure Database, etc.) +4. **S3 Storage** - AWS S3 or S3-compatible storage (GCS, DigitalOcean Spaces, MinIO, etc.) + +## Quick Start + +### 1. Generate Secrets + +```bash +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) + +echo "Save these secrets securely!" +``` + +### 2. Install + +## Install + +#### Using Official Chart Repository +> Have Helm 3 [installed](https://helm.sh/docs/intro/install). + +```sh +helm repo add probo https://getprobo.github.io/probo-helm-charts/ +helm install probo probo/probo -n probo --create-namespace --values values.yaml +``` + +To update versions: + +``` +helm repo update probo +helm upgrade probo probo/probo -n probo --values values.yaml +``` + +#### Using Local Chart + +##### Generate Secrets + +```bash +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) + +# Save these somewhere secure! +echo "ENCRYPTION_KEY=$ENCRYPTION_KEY" +echo "COOKIE_SECRET=$COOKIE_SECRET" +echo "PASSWORD_PEPPER=$PASSWORD_PEPPER" +echo "TRUST_TOKEN_SECRET=$TRUST_TOKEN_SECRET" +``` + +##### Install using Chart and set values + +```bash +helm install my-probo ./charts/probo \ + --set probo.hostname="probo.example.com" \ + --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 postgresql.enabled=true \ + --set postgres.auth.postgresUser="probod" \ + --set postgres.auth.postgresPassword="your-db-password" \ + --set postgres.auth.database="probod" \ + --set minio.enabled=true \ + --set s3.bucket="your-bucket-name" \ + --set s3.accessKeyId="your-access-key" \ + --set s3.secretAccessKey="your-secret-key" +``` + +##### Install using Chart and values file + + +```bash +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" \ + -f ./charts/probo/values.yaml +``` + +### 3. Access + +```bash +kubectl port-forward svc/probo 8080:8080 +# Visit http://localhost:8080 +``` + +## Production Deployment + +For production deployments, we recommend: + +1. **Copy the production template:** + ```bash + cp charts/probo/values.yaml charts/probo/values-k8s-production.yaml + ``` + +2. **Edit the configuration:** + - Set your domain name + - Configure external PostgreSQL connection + - Configure S3 storage credentials + - Add SMTP settings for email + - Enable ingress with TLS + - Configure autoscaling + +3. **Install:** + ```bash + helm install probo ./charts/probo -f ./charts/probo/values-k8s-production.yaml + ``` + +## Architecture + +### What Gets Deployed + +- **Probo Application** - Main Go binary serving GraphQL APIs and React frontends +- **Chrome Headless** - For PDF generation (optional, can use external service) +- **LoadBalancer Service** - For external access via TCP on ports 80, 443, and 8080 +- **Ingress** - Alternative to LoadBalancer for HTTP routing (optional) + +### External Dependencies (Required) + +- **PostgreSQL** - Managed database for compliance data +- **S3 Storage** - Object storage for files and documents + +The chart is designed to work with managed cloud services, ensuring reliability and scalability. + +### Deployment Mode: HAProxy Ingress (Default) + +The default configuration uses HAProxy Ingress controller which provides both Layer 4 (TCP passthrough) and Layer 7 (HTTP routing) capabilities. + +**Architecture:** + +``` +Client + ↓ +HAProxy Ingress LoadBalancer + ├─ Port 80 (TCP Layer 4 passthrough) → probo:80 + ├─ Port 443 (TCP Layer 4 passthrough) → probo:443 + └─ HTTP probo.example.com (Layer 7 routing) → probo:8080 +``` + +**How it works:** + +1. **HAProxy Ingress Controller** runs in the release namespace with a LoadBalancer service +2. **TCP ConfigMap** defines Layer 4 TCP passthrough rules: + - Port 80 → Probo service port 80 + - Port 443 → Probo service port 443 +3. **HTTP Ingress** defines Layer 7 HTTP routing: + - Host `probo.example.com` → Probo service port 8080 + +**Benefits:** +- Single LoadBalancer for both TCP and HTTP traffic +- TCP passthrough for ports 80/443 (Probo handles TLS directly) +- HTTP routing for backoffice on port 8080 +- Supports ACME/Let's Encrypt integration + +**Port Configuration:** +- **Port 80** - TCP passthrough to Probo:80 (HTTP service, ACME challenges) +- **Port 443** - TCP passthrough to Probo:443 (HTTPS service with TLS) +- **Port 8080** - HTTP routing to Probo:8080 (Backoffice, via host-based routing) + +## Configuration + +### Required Configuration + +All deployments require: + +- `probo.encryptionKey` - For data encryption at rest +- `probo.auth.cookieSecret` - For session management +- `probo.auth.passwordPepper` - For password hashing +- `probo.trustAuth.tokenSecret` - For trust center tokens +- `postgresql.host` - PostgreSQL server hostname +- `postgresql.password` - Database password +- `s3.accessKeyId` - S3 access credentials +- `s3.secretAccessKey` - S3 secret key + +See [values.yaml](charts/probo/values.yaml) for all available options. + + +## Cloud Provider Examples + +### AWS +- PostgreSQL: Amazon RDS for PostgreSQL +- Storage: Amazon S3 +- Kubernetes: Amazon EKS + +#### Exemple +```bash +# Prerequisites: +# - Amazon RDS PostgreSQL instance +# - S3 bucket created + +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 postgresql.host="mydb.abc123.us-east-1.rds.amazonaws.com" \ + --set postgresql.password="" \ + --set s3.region="us-east-1" \ + --set s3.bucket="my-probo-bucket" \ + --set s3.accessKeyId="" \ + --set s3.secretAccessKey="" +``` + +### GCP +- PostgreSQL: Cloud SQL for PostgreSQL +- Storage: Cloud Storage with S3 compatibility +- Kubernetes: Google Kubernetes Engine (GKE) + +#### Exemple + +```bash +# Prerequisites: +# - Cloud SQL PostgreSQL instance +# - Cloud Storage bucket with HMAC keys + +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 postgresql.host="10.0.0.5" \ + --set postgresql.password="" \ + --set s3.endpoint="https://storage.googleapis.com" \ + --set s3.bucket="my-probo-bucket" \ + --set s3.accessKeyId="" \ + --set s3.secretAccessKey="" +``` + +### Azure +- PostgreSQL: Azure Database for PostgreSQL +- Storage: Azure Blob Storage (with S3 compatibility) +- Kubernetes: Azure Kubernetes Service (AKS) + +#### Exemple + + +```bash +# Prerequisites: +# - Azure Database for PostgreSQL instance +# - Azure Blob Storage container with S3 compatibility +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 postgresql.host="mydb.postgres.database.azure.com" \ + --set postgresql.password="" \ + --set s3.endpoint="https://.blob.core.windows.net" \ + --set s3.bucket="my-probo-bucket" \ + --set s3.accessKeyId="" \ + --set s3.secretAccessKey="" +``` + +### DigitalOcean +- PostgreSQL: Managed PostgreSQL Database +- Storage: DigitalOcean Spaces +- Kubernetes: DigitalOcean Kubernetes (DOKS) + +#### Exemple +```bash +# Prerequisites: +# - Managed PostgreSQL Database +# - Spaces bucket + +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 postgresql.host="db-postgresql-nyc1-12345.ondigitalocean.com" \ + --set postgresql.password="" \ + --set s3.region="nyc3" \ + --set s3.endpoint="https://nyc3.digitaloceanspaces.com" \ + --set s3.bucket="my-probo-bucket" \ + --set s3.accessKeyId="" \ + --set s3.secretAccessKey="" +``` + +### Using External Secrets Operator + +Example with AWS Secrets Manager: + +```yaml +apiVersion: external-secrets.io/v1beta1 +kind: ExternalSecret +metadata: + name: probo-secrets +spec: + secretStoreRef: + name: aws-secrets-manager + kind: SecretStore + target: + name: probo + data: + - secretKey: encryption-key + remoteRef: + key: probo/encryption-key + - secretKey: db-password + remoteRef: + key: probo/db-password +``` + +## Full Values + +| Key | Type | Default | Description | +|-----------------------------------------------| ------- |----------------------------------------------------|-----------------------------------------------------------------------------------------------------| +| replicaCount | int | `1` | Number of Probo application replicas | +| image.repository | string | `"ghcr.io/getprobo/probo"` | Container image repository | +| image.pullPolicy | string | `"IfNotPresent"` | Image pull policy | +| image.tag | string | `"latest"` | Overrides the image tag whose default is the chart appVersion | +| imagePullSecrets | list | `[]` | Image pull secrets for private registries | +| nameOverride | string | `""` | Override the name of the chart | +| fullnameOverride | string | `""` | Override the fullname of the chart | +| serviceAccount.create | bool | `true` | Specifies whether a service account should be created | +| serviceAccount.automount | bool | `true` | Automatically mount a ServiceAccount's API credentials | +| serviceAccount.annotations | object | `{}` | Annotations to add to the service account | +| serviceAccount.name | string | `""` | The name of the service account to use | +| podAnnotations | object | `{}` | Annotations to add to pods | +| podLabels | object | `{}` | Labels to add to pods | +| podSecurityContext.runAsUser | int | `0` | User ID to run the container as (0 = root, change to 1000 for production) | +| podSecurityContext.runAsGroup | int | `0` | Group ID to run the container as (0 = root, change to 1000 for production) | +| securityContext.runAsUser | int | `0` | User ID for the security context | +| securityContext.runAsGroup | int | `0` | Group ID for the security context | +| securityContext.privileged | bool | `true` | Enable privileged mode for the container (set to false for production) | +| securityContext.capabilities.drop | list | `["ALL"]` | Linux capabilities to drop from the container | +| securityContext.readOnlyRootFilesystem | bool | `false` | Mount root filesystem as read-only (/data directory requires write access) | +| service.type | string | `"ClusterIP"` | Kubernetes service type | +| service.port | int | `8080` | Service port | +| service.httpPort | int | `80` | HTTP port for TCP passthrough | +| service.httpsPort | int | `443` | HTTPS port for TCP passthrough | +| service.annotations | object | `{}` | Annotations for the service | +| haproxy-ingress.enabled | bool | `true` | Enable included HAProxy Ingress controller | +| haproxy-ingress.controller.ingressClass | string | `"haproxy"` | Ingress class name for the controller | +| haproxy-ingress.controller.ingressClassResource.enabled | bool | `true` | Enable IngressClass resource creation | +| haproxy-ingress.controller.ingressClassResource.name | string | `"haproxy"` | Name of the IngressClass resource | +| haproxy-ingress.controller.ingressClassResource.default | bool | `false` | Set as default IngressClass | +| haproxy-ingress.controller.service.type | string | `"LoadBalancer"` | HAProxy Ingress controller service type | +| haproxy-ingress.controller.extraArgs | list | `["--tcp-services-configmap=$(POD_NAMESPACE)/haproxy-tcp-services"]` | Extra arguments for HAProxy controller | +| haproxy-ingress.controller.config.ssl-redirect | string | `"false"` | Disable automatic SSL redirect | +| ingress.enabled | bool | `true` | Enable ingress resource | +| ingress.className | string | `"haproxy"` | Ingress class name | +| ingress.annotations | object | `{"kubernetes.io/ingress.class": "haproxy"}` | Annotations for the ingress resource | +| ingress.hosts[0].host | string | `"probo.example.com"` | Ingress hostname | +| ingress.hosts[0].paths[0].path | string | `"/"` | Path to match | +| ingress.hosts[0].paths[0].pathType | string | `"Prefix"` | Path matching type | +| ingress.tls | list | `[]` | TLS configuration for ingress | +| resources.limits.cpu | string | `"2000m"` | CPU limit for the container | +| resources.limits.memory | string | `"2Gi"` | Memory limit for the container | +| resources.requests.cpu | string | `"500m"` | CPU request for the container | +| resources.requests.memory | string | `"512Mi"` | Memory request for the container | +| livenessProbe.httpGet.path | string | `"/"` | Path for the liveness probe | +| livenessProbe.httpGet.port | string | `"http"` | Port for the liveness probe | +| livenessProbe.initialDelaySeconds | int | `30` | Initial delay for liveness probe | +| livenessProbe.periodSeconds | int | `10` | Period for liveness probe | +| livenessProbe.timeoutSeconds | int | `5` | Timeout for liveness probe | +| livenessProbe.failureThreshold | int | `6` | Failure threshold for liveness probe | +| readinessProbe.httpGet.path | string | `"/"` | Path for the readiness probe | +| readinessProbe.httpGet.port | string | `"http"` | Port for the readiness probe | +| readinessProbe.initialDelaySeconds | int | `10` | Initial delay for readiness probe | +| readinessProbe.periodSeconds | int | `5` | Period for readiness probe | +| readinessProbe.timeoutSeconds | int | `3` | Timeout for readiness probe | +| readinessProbe.failureThreshold | int | `3` | Failure threshold for readiness probe | +| autoscaling.enabled | bool | `false` | Enable horizontal pod autoscaling | +| autoscaling.minReplicas | int | `1` | Minimum number of replicas | +| autoscaling.maxReplicas | int | `10` | Maximum number of replicas | +| autoscaling.targetCPUUtilizationPercentage | int | `80` | Target CPU utilization percentage | +| volumes | list | `[]` | Additional volumes on the output Deployment definition | +| volumeMounts | list | `[]` | Additional volumeMounts on the output Deployment definition | +| nodeSelector | object | `{}` | Node selector for pod assignment | +| tolerations | list | `[]` | Tolerations for pod assignment | +| affinity | object | `{}` | Affinity rules for pod assignment | +| persistence.enabled | bool | `false` | Enable persistent volume for data storage | +| persistence.storageClass | string | `""` | Storage class for the persistent volume | +| persistence.accessMode | string | `"ReadWriteOnce"` | Access mode for the persistent volume | +| persistence.size | string | `"10Gi"` | Size of the persistent volume | +| persistence.annotations | object | `{}` | Annotations for the persistent volume claim | +| metrics.serviceMonitor.enabled | bool | `false` | Enable Prometheus Operator ServiceMonitor | +| metrics.serviceMonitor.interval | string | `"30s"` | Scrape interval for metrics | +| metrics.serviceMonitor.scrapeTimeout | string | `"10s"` | Scrape timeout for metrics | +| metrics.serviceMonitor.labels | object | `{}` | Labels for the ServiceMonitor | +| metrics.serviceMonitor.relabelings | list | `[]` | Relabeling configs for the ServiceMonitor | +| probo.hostname | 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.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 | +| probo.tracing.addr | string | `""` | OTLP gRPC endpoint (e.g., tempo:4317) | +| probo.tracing.maxBatchSize | int | `512` | Maximum batch size for trace exports | +| probo.tracing.batchTimeout | int | `5` | Batch timeout in seconds | +| probo.tracing.exportTimeout | int | `30` | Export timeout in seconds | +| probo.tracing.maxQueueSize | int | `2048` | Maximum queue size for traces | +| probo.cors.allowedOrigins | list | `["https://probo.example.com", "http://..."]` | CORS allowed origins | +| probo.extraHeaderFields | object | `{}` | Extra HTTP headers to add to responses | +| probo.auth.disableSignup | bool | `false` | Disable user signup | +| probo.auth.invitationTokenValidity | int | `3600` | Invitation token validity in seconds | +| probo.auth.cookieName | string | `"SSID"` | Authentication cookie name | +| probo.auth.cookieDomain | string | `"probo.example.com"` | Authentication cookie domain | +| probo.auth.cookieSecret | string | `""` | **REQUIRED** Cookie signing secret (at least 32 bytes, generate with: openssl rand -base64 32) | +| probo.auth.cookieDuration | int | `24` | Cookie duration in hours | +| probo.auth.passwordPepper | string | `""` | **REQUIRED** Password hashing pepper (at least 32 bytes, generate with: openssl rand -base64 32) | +| probo.auth.passwordIterations | int | `1000000` | Password hashing iterations | +| probo.trustAuth.cookieName | string | `"TCT"` | Trust center cookie name | +| probo.trustAuth.cookieDomain | string | `"probo.example.com"` | Trust center cookie domain | +| probo.trustAuth.cookieDuration | int | `24` | Trust center cookie duration in hours | +| probo.trustAuth.tokenDuration | int | `168` | Trust token duration in hours | +| probo.trustAuth.reportUrlDuration | int | `15` | Report URL duration in minutes | +| probo.trustAuth.tokenSecret | string | `""` | **REQUIRED** Trust token signing secret (at least 32 bytes, generate with: openssl rand -base64 32) | +| probo.trustAuth.scope | string | `"trust_center_readonly"` | Trust token scope | +| probo.trustAuth.tokenType | string | `"trust_center_access"` | Trust token type | +| probo.mailer.senderName | string | `"Probo"` | Email sender name | +| probo.mailer.senderEmail | string | `"no-reply@notification.getprobo.com"` | Email sender address | +| probo.mailer.smtp.addr | string | `"sandbox.smtp.mailtrap.io:2525"` | SMTP server address | +| probo.mailer.smtp.user | string | `"2d1b1d0e8b3d0b"` | SMTP username | +| probo.mailer.smtp.password | string | `"25a8eb11e75e8d"` | SMTP password | +| probo.mailer.smtp.tlsRequired | bool | `true` | Require TLS for SMTP connection | +| probo.openai.apiKey | string | `""` | OpenAI API key for AI features (optional) | +| probo.openai.temperature | float | `0.1` | OpenAI temperature setting | +| probo.openai.modelName | string | `"gpt-4o"` | OpenAI model name | +| probo.customDomains.enabled | bool | `false` | Enable custom domains feature | +| probo.customDomains.renewalInterval | int | `3600` | Certificate renewal interval in seconds | +| probo.customDomains.provisionInterval | int | `30` | Domain provision interval in seconds | +| probo.customDomains.cnameTarget | string | `"probo.example.com"` | CNAME target for custom domains | +| probo.customDomains.acme.directory | string | `"https://acme-v02.api.letsencrypt.org/directory"` | ACME directory URL | +| probo.customDomains.acme.email | string | `"admin@example.com"` | ACME registration email | +| probo.customDomains.acme.keyType | string | `"EC256"` | ACME key type | +| probo.customDomains.acme.insecureTls | bool | `false` | Allow insecure TLS for ACME | +| probo.connectors | list | `[]` | External OAuth2 connectors configuration | +| postgresql.enabled | bool | `true` | Enable included PostgreSQL container for demo purposes using CloudNativePG | +| postgresql.resources.limits.memory | string | `"2Gi"` | PostgreSQL memory limit | +| postgresql.resources.limits.ephemeral-storage | string | `"5Gi"` | PostgreSQL ephemeral storage limit | +| postgresql.auth.postgresUser | string | `"probod"` | PostgreSQL username | +| postgresql.auth.postgresPassword | string | `"probod"` | PostgreSQL password | +| postgresql.auth.database | string | `"probod"` | PostgreSQL database name | +| s3.region | string | `"us-east-1"` | S3 region | +| s3.bucket | string | `"probod"` | S3 bucket name | +| s3.endpoint | string | `""` | S3 endpoint (leave empty for AWS S3, set for S3-compatible storage) | +| s3.accessKeyId | string | `""` | **REQUIRED** (when minio.enabled=false) S3 access key | +| s3.secretAccessKey | string | `""` | **REQUIRED** (when minio.enabled=false) S3 secret key | +| minio.enabled | bool | `true` | Enable included MinIO for demo purposes (NOT for production) | +| minio.mode | string | `"standalone"` | MinIO mode | +| minio.persistence.enabled | bool | `false` | Enable MinIO persistence | +| minio.resources.requests.memory | string | `"128Mi"` | MinIO memory request | +| minio.primary.enabled | bool | `true` | Enable MinIO primary node | +| minio.auth.rootUser | string | `"probod"` | MinIO root username | +| minio.auth.rootPassword | string | `"probod"` | MinIO root password | +| chrome.enabled | bool | `true` | Deploy Chrome headless in the cluster for PDF generation | +| chrome.replicaCount | int | `1` | Number of Chrome replicas | +| chrome.image.repository | string | `"chromedp/headless-shell"` | Chrome container image repository | +| chrome.image.tag | string | `"140.0.7259.2"` | Chrome image tag | +| chrome.image.pullPolicy | string | `"IfNotPresent"` | Chrome image pull policy | +| chrome.resources.limits.cpu | string | `"1000m"` | Chrome CPU limit | +| chrome.resources.limits.memory | string | `"1Gi"` | Chrome memory limit | +| chrome.resources.requests.cpu | string | `"100m"` | Chrome CPU request | +| chrome.resources.requests.memory | string | `"256Mi"` | Chrome memory request | +| chrome.external.addr | string | `""` | External Chrome address (used when chrome.enabled=false) | diff --git a/contrib/helm/charts/probo/.helmignore b/contrib/helm/charts/probo/.helmignore new file mode 100644 index 000000000..0e8a0eb36 --- /dev/null +++ b/contrib/helm/charts/probo/.helmignore @@ -0,0 +1,23 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*.orig +*~ +# Various IDEs +.project +.idea/ +*.tmproj +.vscode/ diff --git a/contrib/helm/charts/probo/CONFIG_NOTE.md b/contrib/helm/charts/probo/CONFIG_NOTE.md new file mode 100644 index 000000000..11f5bd712 --- /dev/null +++ b/contrib/helm/charts/probo/CONFIG_NOTE.md @@ -0,0 +1,87 @@ +# Configuration Note + +## Environment Variable Substitution + +The Helm chart templates use a `-from-env` suffix pattern in the configuration file (e.g., `encryption-key-from-env: "ENCRYPTION_KEY"`). This pattern assumes the Probo application supports reading secrets from environment variables instead of directly from the config file. + +### Current Implementation + +The chart currently: +1. Stores sensitive values in a Kubernetes Secret +2. Mounts them as environment variables in the pod +3. References them in the config with `-from-env` suffix + +### If Probo Doesn't Support `-from-env` Pattern + +If the current Probo application doesn't support the `-from-env` pattern, you have three options: + +#### Option 1: Implement in Probo (Recommended) + +Update the Probo configuration parser to recognize `-from-env` suffixes and read from environment variables. This is the most secure approach. + +Example in Go: +```go +// In config loading code +if strings.HasSuffix(key, "-from-env") { + envVar := value.(string) + actualValue := os.Getenv(envVar) + // Use actualValue instead of value +} +``` + +#### Option 2: Use initContainer with envsubst + +Modify the deployment to use an init container that substitutes environment variables: + +```yaml +initContainers: + - name: config-init + image: alpine:3.19 + command: + - sh + - -c + - | + apk add --no-cache gettext + envsubst < /config-template/config.yaml > /config/config.yaml + env: + # All environment variables from secrets + - name: ENCRYPTION_KEY + valueFrom: ... + volumeMounts: + - name: config-template + mountPath: /config-template + - name: config + mountPath: /config +``` + +Then update the ConfigMap to use `${ENCRYPTION_KEY}` instead of the `-from-env` pattern. + +#### Option 3: Mount Everything as Secret + +Create a complete config.yaml as a Secret (not ConfigMap) with actual values: + +```yaml +apiVersion: v1 +kind: Secret +metadata: + name: probo-config +stringData: + config.yaml: | + probod: + encryption-key: {{ .Values.probo.encryptionKey }} + # ... all config with actual values +``` + +This is simpler but less secure as the entire config is stored as a Secret. + +## Recommendation + +We recommend implementing Option 1 in the Probo codebase as it: +- Follows Kubernetes best practices +- Keeps secrets separate from configuration +- Works well with secret management tools (Vault, External Secrets, etc.) +- Supports secret rotation without config changes + +## Current Status + +The Helm chart is built with Option 1 in mind. If the Probo application doesn't yet support `-from-env`, please implement it or use one of the alternative approaches above. diff --git a/contrib/helm/charts/probo/Chart.lock b/contrib/helm/charts/probo/Chart.lock new file mode 100644 index 000000000..f14358e8d --- /dev/null +++ b/contrib/helm/charts/probo/Chart.lock @@ -0,0 +1,9 @@ +dependencies: +- name: minio + repository: https://charts.min.io/ + version: 5.4.0 +- name: kubernetes-ingress + repository: https://haproxytech.github.io/helm-charts + version: 1.45.1 +digest: sha256:86861ee988df3673c6306ff42719f09d12ce22e2d48a0db757ad4365d204bd85 +generated: "2025-10-31T10:17:36.108938+01:00" diff --git a/contrib/helm/charts/probo/Chart.yaml b/contrib/helm/charts/probo/Chart.yaml new file mode 100644 index 000000000..fac1c847a --- /dev/null +++ b/contrib/helm/charts/probo/Chart.yaml @@ -0,0 +1,29 @@ +apiVersion: v2 +name: probo +description: A Helm chart for deploying Probo - Open Source Compliance Platform +type: application +version: 0.1.0 +appVersion: "0.74.7" +keywords: + - compliance + - soc2 + - security + - governance +home: https://getprobo.com +sources: + - https://github.com/getprobo/probo +maintainers: + - name: Probo Team + email: hello@getprobo.com +icon: https://github.com/getprobo/probo/blob/main/.github/cover_v3.png + +dependencies: + - name: minio + condition: minio.enabled + repository: https://charts.min.io/ + version: 5.4.0 # Latest stable version + - name: kubernetes-ingress + alias: haproxy-ingress + condition: haproxy-ingress.enabled + version: 1.45.1 + repository: https://haproxytech.github.io/helm-charts diff --git a/contrib/helm/charts/probo/README.md b/contrib/helm/charts/probo/README.md new file mode 100644 index 000000000..27b9a7dff --- /dev/null +++ b/contrib/helm/charts/probo/README.md @@ -0,0 +1,267 @@ +# Probo Helm Chart + +This Helm chart deploys Probo - an open-source SOC-2 compliance platform - on Kubernetes. + +## Prerequisites + +- Kubernetes 1.23+ +- Helm 3.8+ +- External PostgreSQL database (AWS RDS, GCP Cloud SQL, Azure Database, etc.) +- S3 or S3-compatible object storage (AWS S3, GCS, DigitalOcean Spaces, MinIO, etc.) + +## Installing the Chart + +### Generate Required Secrets + +```bash +# Generate required secrets +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) + +echo "Save these secrets securely!" +``` + +### Install + +```bash +helm install probo . \ + --set probo.hostname="probo.example.com" \ + --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 postgresql.host="postgres.example.com" \ + --set postgresql.password="" \ + --set s3.bucket="probo-production" \ + --set s3.accessKeyId="" \ + --set s3.secretAccessKey="" +``` + +### Production Deployment + +For production, create a `values-production.yaml` file: + +```yaml +# values-production.yaml +image: + repository: ghcr.io/getprobo/probo + tag: "0.74.7" + +replicaCount: 3 + +ingress: + enabled: true + className: "nginx" + annotations: + cert-manager.io/cluster-issuer: "letsencrypt-prod" + hosts: + - host: probo.example.com + paths: + - path: / + pathType: Prefix + tls: + - secretName: probo-tls + hosts: + - probo.example.com + +probo: + hostname: "probo.example.com" + encryptionKey: "" + cors: + allowedOrigins: + - "https://probo.example.com" + auth: + cookieDomain: "example.com" + cookieSecret: "" + passwordPepper: "" + trustAuth: + cookieDomain: "example.com" + tokenSecret: "" + mailer: + senderEmail: "noreply@example.com" + smtp: + addr: "smtp.sendgrid.net:587" + user: "apikey" + password: "" + tlsRequired: true + +postgresql: + host: "postgres.example.com" + password: "" + +s3: + region: "us-east-1" + bucket: "probo-production" + accessKeyId: "" + secretAccessKey: "" + +autoscaling: + enabled: true + minReplicas: 3 + maxReplicas: 10 +``` + +Install with: + +```bash +helm install probo . -f values-production.yaml +``` + +## Configuration + +### Required Configuration + +The following parameters **must** be configured: + +| Parameter | Description | +|-----------|-------------| +| `probo.encryptionKey` | Base64-encoded encryption key (32+ bytes) | +| `probo.auth.cookieSecret` | Cookie signing secret (32+ bytes) | +| `probo.auth.passwordPepper` | Password hashing pepper (32+ bytes) | +| `probo.trustAuth.tokenSecret` | Trust token secret (32+ bytes) | +| `postgresql.host` | PostgreSQL hostname | +| `postgresql.password` | PostgreSQL password | +| `s3.accessKeyId` | S3 access key ID | +| `s3.secretAccessKey` | S3 secret access key | + +### Key Configuration Parameters + +| Parameter | Description | Default | +|-----------|-------------|---------| +| `image.repository` | Probo image repository | `ghcr.io/getprobo/probo` | +| `image.tag` | Probo image tag | Chart appVersion | +| `replicaCount` | Number of Probo replicas | `1` | +| `probo.hostname` | Public hostname | `probo.example.com` | +| `postgresql.host` | PostgreSQL host | `""` (required) | +| `postgresql.port` | PostgreSQL port | `5432` | +| `postgresql.database` | Database name | `probod` | +| `postgresql.username` | Database user | `probod` | +| `s3.bucket` | S3 bucket name | `probod` | +| `s3.region` | AWS region | `us-east-1` | +| `s3.endpoint` | S3 endpoint (for S3-compatible) | `""` | +| `chrome.enabled` | Deploy Chrome | `true` | +| `chrome.external.addr` | External Chrome (if disabled) | `""` | +| `ingress.enabled` | Enable ingress | `false` | + +See [values.yaml](values.yaml) for all available options. + +## Components + +The chart deploys the following: + +- **Probo Application**: Main Go binary serving GraphQL APIs and SPAs +- **Chrome Headless**: For PDF generation (optional, can use external) +- **Ingress**: For external access with TLS (optional) + +### External Dependencies (Required) + +- **PostgreSQL**: Managed database service +- **S3 Storage**: Object storage for files and documents + +## Database Management + +### Migrations + +Database migrations run automatically when Probo starts. No manual intervention is required. + +### Backup + +Use your PostgreSQL provider's backup solution (e.g., AWS RDS automated backups, GCP Cloud SQL backups). + +## Upgrading + +```bash +helm upgrade probo . -f values-production.yaml +``` + +## Uninstalling + +```bash +helm uninstall probo +``` + +**Note:** This does not delete your external PostgreSQL database or S3 bucket. + +## Troubleshooting + +### View Logs + +```bash +kubectl logs -f deployment/probo +``` + +### Check Configuration + +```bash +kubectl describe configmap probo +kubectl get secret probo -o yaml +``` + +### Test Database Connection + +Check the Probo logs for database connection errors. The application will fail to start if it cannot connect to PostgreSQL. + +### Test S3 Connection + +Check the Probo logs for S3 connection errors when uploading files. + +## Examples + +### AWS Deployment + +```yaml +postgresql: + host: "mydb.abc123.us-east-1.rds.amazonaws.com" + password: "" + +s3: + region: "us-east-1" + bucket: "my-probo-bucket" + accessKeyId: "" + secretAccessKey: "" + # Leave endpoint empty for AWS S3 +``` + +### GCP Deployment + +```yaml +postgresql: + host: "10.0.0.5" # Cloud SQL private IP + password: "" + +s3: + region: "us-east1" + bucket: "my-probo-bucket" + endpoint: "https://storage.googleapis.com" + accessKeyId: "" + secretAccessKey: "" +``` + +### DigitalOcean Deployment + +```yaml +postgresql: + host: "db-postgresql-nyc1-12345.ondigitalocean.com" + password: "" + +s3: + region: "nyc3" + bucket: "my-probo-bucket" + endpoint: "https://nyc3.digitaloceanspaces.com" + accessKeyId: "" + secretAccessKey: "" + +``` + +### Using External Chrome Service + +By default, Chrome is deployed in the cluster. To use an external Chrome service: + +```yaml +chrome: + enabled: false + external: + addr: "chrome.browserless.io:3000" +``` diff --git a/contrib/helm/charts/probo/templates/NOTES.txt b/contrib/helm/charts/probo/templates/NOTES.txt new file mode 100644 index 000000000..ebfe2becc --- /dev/null +++ b/contrib/helm/charts/probo/templates/NOTES.txt @@ -0,0 +1,84 @@ +🎉 Probo has been successfully deployed! + +📋 Release Information: + Name: {{ .Release.Name }} + Namespace: {{ .Release.Namespace }} + Version: {{ .Chart.AppVersion }} + +🌐 Accessing Probo: + +{{- if .Values.ingress.enabled }} +{{- range $host := .Values.ingress.hosts }} + Application: {{ if $.Values.ingress.tls }}https{{ else }}http{{ end }}://{{ $host.host }} +{{- end }} +{{- else if contains "NodePort" .Values.service.type }} + Get the application URL by running: + export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "probo.fullname" . }}) + export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}") + echo "Application: http://$NODE_IP:$NODE_PORT" +{{- else if contains "LoadBalancer" .Values.service.type }} + NOTE: It may take a few minutes for the LoadBalancer IP to be available. + You can watch the status by running: + kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include "probo.fullname" . }} + + Once available, get the application URL by running: + export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "probo.fullname" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}") + echo "Application: http://$SERVICE_IP:{{ .Values.service.port }}" +{{- else if contains "ClusterIP" .Values.service.type }} + Application is available only within the cluster. + + To access it from your local machine, run: + kubectl --namespace {{ .Release.Namespace }} port-forward svc/{{ include "probo.fullname" . }} 8080:{{ .Values.service.port }} + + Then visit: http://localhost:8080 +{{- end }} + +📊 Monitoring: + Metrics endpoint: http://{{ include "probo.fullname" . }}:{{ .Values.probo.metrics.port }}/metrics + + To access metrics from your local machine, run: + kubectl --namespace {{ .Release.Namespace }} port-forward svc/{{ include "probo.fullname" . }} 8081:{{ .Values.probo.metrics.port }} + +🔍 View Logs: + kubectl --namespace {{ .Release.Namespace }} logs -f deployment/{{ include "probo.fullname" . }} + +🗄️ Database: + Using external PostgreSQL at: {{ .Values.postgresql.host }}:{{ .Values.postgresql.port }} + Database: {{ .Values.postgresql.database }} + +💾 Object Storage: + Using S3 storage + Bucket: {{ .Values.s3.bucket }} + Region: {{ .Values.s3.region }} + {{- if .Values.s3.endpoint }} + Endpoint: {{ .Values.s3.endpoint }} + {{- end }} + +⚠️ Important Security Notes: + +{{- if not .Values.ingress.tls }} + ⚠️ TLS is not enabled! Enable it for production by configuring ingress.tls +{{- end }} + +📚 Next Steps: + +1. Update the hostname in values.yaml to match your domain: + probo.hostname: "{{ .Values.probo.hostname }}" + +2. Configure email delivery: + Update probo.mailer.smtp settings with your SMTP server details. + +{{- if not .Values.probo.openai.apiKey }} +3. (Optional) Configure OpenAI integration for AI-powered features: + Set probo.openai.apiKey in values.yaml +{{- end }} + +4. Review and update CORS allowed origins: + probo.cors.allowedOrigins + +📖 Documentation: + - GitHub: https://github.com/getprobo/probo + - Discord: https://discord.gg/8qfdJYfvpY + - Website: https://getprobo.com + +✨ Thank you for using Probo! diff --git a/contrib/helm/charts/probo/templates/_helpers.tpl b/contrib/helm/charts/probo/templates/_helpers.tpl new file mode 100644 index 000000000..f23ad5e9d --- /dev/null +++ b/contrib/helm/charts/probo/templates/_helpers.tpl @@ -0,0 +1,159 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "probo.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Create a default fully qualified app name. +*/}} +{{- define "probo.fullname" -}} +{{- if .Values.fullnameOverride }} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- $name := default .Chart.Name .Values.nameOverride }} +{{- if contains $name .Release.Name }} +{{- .Release.Name | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} +{{- end }} +{{- end }} +{{- end }} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "probo.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "probo.labels" -}} +helm.sh/chart: {{ include "probo.chart" . }} +{{ include "probo.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "probo.selectorLabels" -}} +app.kubernetes.io/name: {{ include "probo.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + +{{/* +Create the name of the service account to use +*/}} +{{- define "probo.serviceAccountName" -}} +{{- if .Values.serviceAccount.create }} +{{- default (include "probo.fullname" .) .Values.serviceAccount.name }} +{{- else }} +{{- default "default" .Values.serviceAccount.name }} +{{- end }} +{{- end }} + +{{/* +PostgreSQL hostname +*/}} +{{- define "probo.postgresql.host" -}} +{{- if .Values.postgresql.enabled }} +{{- printf "%s-postgresql-demo" .Release.Name }} +{{- else }} +{{- .Values.postgresql.host | required "postgresql.host is required when postgresql.enabled=false" }} +{{- end }} +{{- end }} + +{{/* +PostgreSQL port +*/}} +{{- define "probo.postgresql.port" -}} +{{- if .Values.postgresql.enabled }} +{{- 5432 }} +{{- else }} +{{- .Values.postgresql.port | default 5432 }} +{{- end }} +{{- end }} + +{{/* +PostgreSQL database name +*/}} +{{- define "probo.postgresql.database" -}} +{{- if .Values.postgresql.enabled }} +{{- .Values.postgresql.auth.database | default "probod" }} +{{- else }} +{{- .Values.postgresql.database | default "probod" }} +{{- end }} +{{- end }} + +{{/* +PostgreSQL username +*/}} +{{- define "probo.postgresql.username" -}} +{{- if .Values.postgresql.enabled }} +{{- .Values.postgresql.auth.postgresUser | default "probod" }} +{{- else }} +{{- .Values.postgresql.username | default "probod" }} +{{- end }} +{{- end }} + +{{/* +PostgreSQL password (from subchart or external config) +*/}} +{{- define "probo.postgresql.password" -}} +{{- if .Values.postgresql.enabled }} +{{- .Values.postgresql.auth.postgresPassword | required "postgresql.auth.postgresPassword is required when postgresql.enabled=true" }} +{{- else }} +{{- .Values.postgresql.password | required "postgresql.password is required when postgresql.enabled=false" }} +{{- end }} +{{- end }} + +{{/* +S3 endpoint +*/}} +{{- define "probo.s3.endpoint" -}} +{{- if .Values.minio.enabled }} +{{- printf "http://%s-minio:9000" .Release.Name }} +{{- else }} +{{- .Values.s3.endpoint }} +{{- end }} +{{- end }} + +{{/* +S3 access key +*/}} +{{- define "probo.s3.accessKeyId" -}} +{{- if .Values.minio.enabled }} +{{- .Values.minio.auth.rootUser | required "minio.auth.rootUser is required when minio.enabled=true" }} +{{- else }} +{{- .Values.s3.accessKeyId | required "s3.accessKeyId is required when minio.enabled=false" }} +{{- end }} +{{- end }} + +{{/* +S3 secret key +*/}} +{{- define "probo.s3.secretAccessKey" -}} +{{- if .Values.minio.enabled }} +{{- .Values.minio.auth.rootPassword | required "minio.auth.rootPassword is required when minio.enabled=true" }} +{{- else }} +{{- .Values.s3.secretAccessKey | required "s3.secretAccessKey is required when minio.enabled=false" }} +{{- end }} +{{- end }} + +{{/* +Chrome DevTools Protocol address +*/}} +{{- define "probo.chrome.addr" -}} +{{- if .Values.chrome.enabled }} +{{- printf "%s-chrome:9222" (include "probo.fullname" .) }} +{{- else }} +{{- .Values.chrome.external.addr | required "chrome.external.addr is required when chrome.enabled=false" }} +{{- end }} +{{- end }} diff --git a/contrib/helm/charts/probo/templates/chrome.yaml b/contrib/helm/charts/probo/templates/chrome.yaml new file mode 100644 index 000000000..e3937968d --- /dev/null +++ b/contrib/helm/charts/probo/templates/chrome.yaml @@ -0,0 +1,77 @@ +{{- if .Values.chrome.enabled }} +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "probo.fullname" . }}-chrome + labels: + {{- include "probo.labels" . | nindent 4 }} + app.kubernetes.io/component: chrome +spec: + replicas: {{ .Values.chrome.replicaCount }} + selector: + matchLabels: + {{- include "probo.selectorLabels" . | nindent 6 }} + app.kubernetes.io/component: chrome + template: + metadata: + labels: + {{- include "probo.selectorLabels" . | nindent 8 }} + app.kubernetes.io/component: chrome + spec: + containers: + - name: chrome + image: "{{ .Values.chrome.image.repository }}:{{ .Values.chrome.image.tag }}" + imagePullPolicy: {{ .Values.chrome.image.pullPolicy }} + args: + - --headless + - --disable-gpu + - --disable-dev-shm-usage + - --hide-scrollbars + - --mute-audio + - --no-default-browser-check + - --no-first-run + - --disable-background-networking + - --disable-background-timer-throttling + - --disable-extensions + ports: + - name: devtools + containerPort: 9222 + protocol: TCP + livenessProbe: + httpGet: + path: /json/version + port: devtools + initialDelaySeconds: 10 + periodSeconds: 10 + readinessProbe: + httpGet: + path: /json/version + port: devtools + initialDelaySeconds: 5 + periodSeconds: 5 + resources: + {{- toYaml .Values.chrome.resources | nindent 12 }} + securityContext: + capabilities: + add: + - SYS_ADMIN +--- +apiVersion: v1 +kind: Service +metadata: + name: {{ include "probo.fullname" . }}-chrome + labels: + {{- include "probo.labels" . | nindent 4 }} + app.kubernetes.io/component: chrome +spec: + type: ClusterIP + ports: + - port: 9222 + targetPort: devtools + protocol: TCP + name: devtools + selector: + {{- include "probo.selectorLabels" . | nindent 4 }} + app.kubernetes.io/component: chrome +{{- end }} diff --git a/contrib/helm/charts/probo/templates/configmap.yaml b/contrib/helm/charts/probo/templates/configmap.yaml new file mode 100644 index 000000000..47c6ef827 --- /dev/null +++ b/contrib/helm/charts/probo/templates/configmap.yaml @@ -0,0 +1,119 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ include "probo.fullname" . }} + labels: + {{- include "probo.labels" . | nindent 4 }} +data: + config.yml: | + unit: + metrics: + addr: "0.0.0.0:{{ .Values.probo.metrics.port }}" + {{- if .Values.probo.tracing.enabled }} + tracing: + addr: {{ .Values.probo.tracing.addr | quote }} + max-batch-size: {{ .Values.probo.tracing.maxBatchSize }} + batch-timeout: {{ .Values.probo.tracing.batchTimeout }} + export-timeout: {{ .Values.probo.tracing.exportTimeout }} + max-queue-size: {{ .Values.probo.tracing.maxQueueSize }} + {{- end }} + + probod: + hostname: {{ .Values.probo.hostname | quote }} + encryption-key: {{ .Values.probo.encryptionKey | quote }} + chrome-dp-addr: {{ include "probo.chrome.addr" . | quote }} + + api: + addr: "0.0.0.0:{{ .Values.service.port }}" + cors: + allowed-origins: + {{- toYaml .Values.probo.cors.allowedOrigins | nindent 12 }} + {{- if .Values.probo.extraHeaderFields }} + extra-header-fields: + {{- toYaml .Values.probo.extraHeaderFields | nindent 10 }} + {{- end }} + + pg: + addr: "{{ include "probo.postgresql.host" . }}:{{ include "probo.postgresql.port" . }}" + username: {{ include "probo.postgresql.username" . | quote }} + password: {{ include "probo.postgresql.password" . | quote }} + database: {{ include "probo.postgresql.database" . | quote }} + pool-size: {{ .Values.postgresql.poolSize }} + {{- if .Values.postgresql.caCertBundle }} + ca-cert-bundle: | + {{- .Values.postgresql.caCertBundle | nindent 10 }} + {{- end }} + + auth: + disable-signup: {{ .Values.probo.auth.disableSignup }} + invitation-confirmation-token-validity: {{ .Values.probo.auth.invitationTokenValidity }} + cookie: + name: {{ .Values.probo.auth.cookieName | quote }} + domain: {{ .Values.probo.auth.cookieDomain | quote }} + secret: {{ .Values.probo.auth.cookieSecret | quote }} + duration: {{ .Values.probo.auth.cookieDuration }} + password: + pepper: {{ .Values.probo.auth.passwordPepper | quote }} + iterations: {{ .Values.probo.auth.passwordIterations }} + + trust-auth: + cookie-name: {{ .Values.probo.trustAuth.cookieName | quote }} + cookie-domain: {{ .Values.probo.trustAuth.cookieDomain | quote }} + cookie-duration: {{ .Values.probo.trustAuth.cookieDuration }} + token-duration: {{ .Values.probo.trustAuth.tokenDuration }} + report-url-duration: {{ .Values.probo.trustAuth.reportUrlDuration }} + {{- if .Values.probo.trustAuth.ports }} + ports: + http-challenge: {{ .Values.probo.trustAuth.ports.httpChallenge }} + tls-http-server: {{ .Values.probo.trustAuth.ports.tlsHttpServer }} + {{- end }} + token-secret: {{ .Values.probo.trustAuth.tokenSecret | quote }} + scope: {{ .Values.probo.trustAuth.scope | quote }} + token-type: {{ .Values.probo.trustAuth.tokenType | quote }} + + aws: + region: {{ .Values.s3.region | quote }} + bucket: {{ .Values.s3.bucket | quote }} + access-key-id: {{ .Values.s3.accessKey | quote }} + secret-access-key: {{ .Values.s3.secretKey | quote }} + {{- $s3Endpoint := include "probo.s3.endpoint" . }} + {{- if $s3Endpoint }} + endpoint: {{ $s3Endpoint | quote }} + {{- end }} + + mailer: + sender-name: {{ .Values.probo.mailer.senderName | quote }} + sender-email: {{ .Values.probo.mailer.senderEmail | quote }} + smtp: + addr: {{ .Values.probo.mailer.smtp.addr | quote }} + {{- if .Values.probo.mailer.smtp.user }} + user: {{ .Values.probo.mailer.smtp.user | quote }} + {{- end }} + {{- if .Values.probo.mailer.smtp.password }} + password: {{ .Values.probo.mailer.smtp.password | quote }} + {{- end }} + tls-required: {{ .Values.probo.mailer.smtp.tlsRequired }} + + {{- if .Values.probo.openai.apiKey }} + openai: + api-key-from-env: "OPENAI_API_KEY" + temperature: {{ .Values.probo.openai.temperature }} + model-name: {{ .Values.probo.openai.modelName | quote }} + {{- end }} + + {{- if .Values.probo.customDomains.enabled }} + custom-domains: + renewal-interval: {{ .Values.probo.customDomains.renewalInterval }} + provision-interval: {{ .Values.probo.customDomains.provisionInterval }} + cname-target: {{ .Values.probo.customDomains.cnameTarget | quote }} + acme: + directory: {{ .Values.probo.customDomains.acme.directory | quote }} + email: {{ .Values.probo.customDomains.acme.email | quote }} + key-type: {{ .Values.probo.customDomains.acme.keyType | quote }} + insecure-tls: {{ .Values.probo.customDomains.acme.insecureTls }} + {{- end }} + + {{- if .Values.probo.connectors }} + connectors: + {{- toYaml .Values.probo.connectors | nindent 8 }} + {{- end }} diff --git a/contrib/helm/charts/probo/templates/deployment.yaml b/contrib/helm/charts/probo/templates/deployment.yaml new file mode 100644 index 000000000..36d9cf39d --- /dev/null +++ b/contrib/helm/charts/probo/templates/deployment.yaml @@ -0,0 +1,162 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "probo.fullname" . }} + labels: + {{- include "probo.labels" . | nindent 4 }} +spec: + {{- if not .Values.autoscaling.enabled }} + replicas: {{ .Values.replicaCount }} + {{- end }} + selector: + matchLabels: + {{- include "probo.selectorLabels" . | nindent 6 }} + template: + metadata: + annotations: + checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }} + checksum/secret: {{ include (print $.Template.BasePath "/secret.yaml") . | sha256sum }} + {{- with .Values.podAnnotations }} + {{- toYaml . | nindent 8 }} + {{- end }} + labels: + {{- include "probo.labels" . | nindent 8 }} + {{- with .Values.podLabels }} + {{- toYaml . | nindent 8 }} + {{- end }} + spec: + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + serviceAccountName: {{ include "probo.serviceAccountName" . }} + securityContext: + {{- toYaml .Values.podSecurityContext | nindent 8 }} + containers: + - name: {{ .Chart.Name }} + securityContext: + {{- toYaml .Values.securityContext | nindent 12 }} + image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" + imagePullPolicy: {{ .Values.image.pullPolicy }} + command: ["/bin/sh", "-c"] # replaces ENTRYPOINT + args: ["echo $DB_PASSWORD && cat /etc/probod/config.yml && exec /usr/local/bin/probod -cfg-file /etc/probod/config.yml"] + +# args: +# - "-cfg-file" +# - "/etc/probod/config.yml" + env: + # Database credentials + - name: DB_PASSWORD + valueFrom: + secretKeyRef: + name: {{ include "probo.fullname" . }} + key: db-password + # S3 credentials + - name: S3_ACCESS_KEY + valueFrom: + secretKeyRef: + name: {{ include "probo.fullname" . }} + key: s3-access-key + - name: S3_SECRET_KEY + valueFrom: + secretKeyRef: + name: {{ include "probo.fullname" . }} + key: s3-secret-key + # Application secrets + - name: ENCRYPTION_KEY + valueFrom: + secretKeyRef: + name: {{ include "probo.fullname" . }} + key: encryption-key + - name: COOKIE_SECRET + valueFrom: + secretKeyRef: + name: {{ include "probo.fullname" . }} + key: cookie-secret + - name: PASSWORD_PEPPER + valueFrom: + secretKeyRef: + name: {{ include "probo.fullname" . }} + key: password-pepper + - name: TRUST_TOKEN_SECRET + valueFrom: + secretKeyRef: + name: {{ include "probo.fullname" . }} + key: trust-token-secret + # Optional SMTP credentials + {{- if .Values.probo.mailer.smtp.user }} + - name: SMTP_USER + valueFrom: + secretKeyRef: + name: {{ include "probo.fullname" . }} + key: smtp-user + {{- end }} + {{- if .Values.probo.mailer.smtp.password }} + - name: SMTP_PASSWORD + valueFrom: + secretKeyRef: + name: {{ include "probo.fullname" . }} + key: smtp-password + {{- end }} + # Optional OpenAI API key + {{- if .Values.probo.openai.apiKey }} + - name: OPENAI_API_KEY + valueFrom: + secretKeyRef: + name: {{ include "probo.fullname" . }} + key: openai-api-key + {{- end }} + ports: + - name: http + containerPort: {{ .Values.service.httpPort }} + protocol: TCP + - name: https + containerPort: {{ .Values.service.httpsPort }} + protocol: TCP + - name: backoffice + containerPort: {{ .Values.service.port }} + protocol: TCP + - name: metrics + containerPort: {{ .Values.probo.metrics.port }} + protocol: TCP + livenessProbe: + {{- toYaml .Values.livenessProbe | nindent 12 }} + readinessProbe: + {{- toYaml .Values.readinessProbe | nindent 12 }} + resources: + {{- toYaml .Values.resources | nindent 12 }} + volumeMounts: + - name: config + mountPath: /etc/probod + readOnly: false + - name: data + mountPath: /data + {{- with .Values.volumeMounts }} + {{- toYaml . | nindent 12 }} + {{- end }} + volumes: + - name: config + configMap: + name: {{ include "probo.fullname" . }} + - name: data + {{- if .Values.persistence.enabled }} + persistentVolumeClaim: + claimName: {{ .Values.persistence.existingClaim | default (include "probo.fullname" .) }} + {{- else }} + emptyDir: {} + {{- end }} + {{- with .Values.volumes }} + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} diff --git a/contrib/helm/charts/probo/templates/haproxy-tcp-configmap.yaml b/contrib/helm/charts/probo/templates/haproxy-tcp-configmap.yaml new file mode 100644 index 000000000..a8772356f --- /dev/null +++ b/contrib/helm/charts/probo/templates/haproxy-tcp-configmap.yaml @@ -0,0 +1,16 @@ +{{- if index .Values "haproxy-ingress" "enabled" -}} +apiVersion: v1 +kind: ConfigMap +metadata: + name: haproxy-tcp-services + namespace: {{ .Release.Namespace }} + labels: + {{- include "probo.labels" . | nindent 4 }} +data: + # TCP passthrough for port 80 (HTTP) + # Format: "": "/:" + "80": "{{ .Release.Namespace }}/{{ include "probo.fullname" . }}:{{ .Values.service.httpPort }}" + + # TCP passthrough for port 443 (HTTPS) + "443": "{{ .Release.Namespace }}/{{ include "probo.fullname" . }}:{{ .Values.service.httpsPort }}" +{{- end }} diff --git a/contrib/helm/charts/probo/templates/hpa.yaml b/contrib/helm/charts/probo/templates/hpa.yaml new file mode 100644 index 000000000..fd58bc75b --- /dev/null +++ b/contrib/helm/charts/probo/templates/hpa.yaml @@ -0,0 +1,32 @@ +{{- if .Values.autoscaling.enabled }} +apiVersion: autoscaling/v2 +kind: HorizontalPodAutoscaler +metadata: + name: {{ include "probo.fullname" . }} + labels: + {{- include "probo.labels" . | nindent 4 }} +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: {{ include "probo.fullname" . }} + minReplicas: {{ .Values.autoscaling.minReplicas }} + maxReplicas: {{ .Values.autoscaling.maxReplicas }} + metrics: + {{- if .Values.autoscaling.targetCPUUtilizationPercentage }} + - type: Resource + resource: + name: cpu + target: + type: Utilization + averageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }} + {{- end }} + {{- if .Values.autoscaling.targetMemoryUtilizationPercentage }} + - type: Resource + resource: + name: memory + target: + type: Utilization + averageUtilization: {{ .Values.autoscaling.targetMemoryUtilizationPercentage }} + {{- end }} +{{- end }} diff --git a/contrib/helm/charts/probo/templates/ingress.yaml b/contrib/helm/charts/probo/templates/ingress.yaml new file mode 100644 index 000000000..c9e992410 --- /dev/null +++ b/contrib/helm/charts/probo/templates/ingress.yaml @@ -0,0 +1,41 @@ +{{- if .Values.ingress.enabled -}} +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: {{ include "probo.fullname" . }}-http + labels: + {{- include "probo.labels" . | nindent 4 }} + annotations: + {{- with .Values.ingress.annotations }} + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + {{- if .Values.ingress.className }} + ingressClassName: {{ .Values.ingress.className }} + {{- end }} + {{- if .Values.ingress.tls }} + tls: + {{- range .Values.ingress.tls }} + - hosts: + {{- range .hosts }} + - {{ . | quote }} + {{- end }} + secretName: {{ .secretName }} + {{- end }} + {{- end }} + rules: + {{- range .Values.ingress.hosts }} + - host: {{ .host | quote }} + http: + paths: + {{- range .paths }} + - path: {{ .path }} + pathType: {{ .pathType }} + backend: + service: + name: {{ include "probo.fullname" $ }} + port: + number: {{ $.Values.service.port }} + {{- end }} + {{- end }} +{{- end }} diff --git a/contrib/helm/charts/probo/templates/postgresql-demo-app.yml b/contrib/helm/charts/probo/templates/postgresql-demo-app.yml new file mode 100644 index 000000000..09b5a3022 --- /dev/null +++ b/contrib/helm/charts/probo/templates/postgresql-demo-app.yml @@ -0,0 +1,81 @@ +{{- if .Values.postgresql.enabled }} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "probo.fullname" . }}-postgresql-demo +spec: + replicas: 1 + selector: + matchLabels: + app: {{ include "probo.fullname" . }}-postgresql-demo + template: + metadata: + labels: + app: {{ include "probo.fullname" . }}-postgresql-demo + spec: + containers: + - name: postgres + image: postgres:18 + env: + - name: POSTGRES_USER + value: {{ .Values.postgresql.auth.postgresUser }} + - name: POSTGRES_PASSWORD + value: {{ .Values.postgresql.auth.postgresPassword }} + - name: POSTGRES_DB + value: {{ .Values.postgresql.auth.database }} + ports: + - containerPort: 5432 + volumeMounts: + - name: postgres-init + mountPath: /docker-entrypoint-initdb.d + readOnly: true + resources: + {{- toYaml .Values.postgresql.resources | nindent 12 }} + volumes: + - name: postgres-init + configMap: + name: {{ include "probo.fullname" . }}-postgres-init + defaultMode: 0755 +--- +apiVersion: v1 +kind: Service +metadata: + name: {{ .Release.Name }}-postgresql-demo +spec: + selector: + app: {{ include "probo.fullname" . }}-postgresql-demo + ports: + - port: 5432 + targetPort: 5432 +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ include "probo.fullname" . }}-postgres-init + labels: + {{- include "probo.labels" . | nindent 4 }} +data: + 01_probod.sh: | + #!/bin/sh + + set -eu + + psql -v ON_ERROR_STOP=1 -U $POSTGRES_USER <<-EOF + ALTER USER probod WITH SUPERUSER; + ALTER USER probod PASSWORD 'probod'; + GRANT ALL PRIVILEGES ON DATABASE probod TO probod; + CREATE DATABASE probod_test; + GRANT ALL PRIVILEGES ON DATABASE probod_test TO probod; + EOF + + psql -v ON_ERROR_STOP=1 -U $POSTGRES_USER -d probod <<-EOF + ALTER SCHEMA public OWNER TO probod; + GRANT ALL ON SCHEMA public TO probod; + EOF + + psql -v ON_ERROR_STOP=1 -U $POSTGRES_USER -d probod_test <<-EOF + ALTER SCHEMA public OWNER TO probod; + GRANT ALL ON SCHEMA public TO probod; + EOF +--- +{{- end -}} diff --git a/contrib/helm/charts/probo/templates/pvc.yaml b/contrib/helm/charts/probo/templates/pvc.yaml new file mode 100644 index 000000000..7633fd14e --- /dev/null +++ b/contrib/helm/charts/probo/templates/pvc.yaml @@ -0,0 +1,25 @@ +{{- if and .Values.persistence.enabled (not .Values.persistence.existingClaim) }} +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: {{ include "probo.fullname" . }} + labels: + {{- include "probo.labels" . | nindent 4 }} + {{- with .Values.persistence.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + accessModes: + - {{ .Values.persistence.accessMode | quote }} + resources: + requests: + storage: {{ .Values.persistence.size | quote }} + {{- if .Values.persistence.storageClass }} + {{- if (eq "-" .Values.persistence.storageClass) }} + storageClassName: "" + {{- else }} + storageClassName: {{ .Values.persistence.storageClass | quote }} + {{- end }} + {{- end }} +{{- end }} diff --git a/contrib/helm/charts/probo/templates/secret.yaml b/contrib/helm/charts/probo/templates/secret.yaml new file mode 100644 index 000000000..65386280c --- /dev/null +++ b/contrib/helm/charts/probo/templates/secret.yaml @@ -0,0 +1,33 @@ +apiVersion: v1 +kind: Secret +metadata: + name: {{ include "probo.fullname" . }} + labels: + {{- include "probo.labels" . | nindent 4 }} +type: Opaque +stringData: + # Database credentials + db-password: {{ include "probo.postgresql.password" . | quote }} + + # S3 credentials + s3-access-key: {{ include "probo.s3.accessKeyId" . | quote }} + 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 }} + + # Optional SMTP credentials + {{- if .Values.probo.mailer.smtp.user }} + smtp-user: {{ .Values.probo.mailer.smtp.user | quote }} + {{- end }} + {{- if .Values.probo.mailer.smtp.password }} + smtp-password: {{ .Values.probo.mailer.smtp.password | quote }} + {{- end }} + + # Optional OpenAI API key + {{- if .Values.probo.openai.apiKey }} + openai-api-key: {{ .Values.probo.openai.apiKey | quote }} + {{- end }} diff --git a/contrib/helm/charts/probo/templates/service.yaml b/contrib/helm/charts/probo/templates/service.yaml new file mode 100644 index 000000000..db0309fad --- /dev/null +++ b/contrib/helm/charts/probo/templates/service.yaml @@ -0,0 +1,44 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ include "probo.fullname" . }} + labels: + {{- include "probo.labels" . | nindent 4 }} + {{- with .Values.service.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + type: {{ .Values.service.type }} + {{- if and .Values.service.clusterIP (eq .Values.service.type "ClusterIP") }} + clusterIP: {{ .Values.service.clusterIP }} + {{- end }} + {{- if .Values.service.loadBalancerIP }} + loadBalancerIP: {{ .Values.service.loadBalancerIP }} + {{- end }} + {{- if .Values.service.loadBalancerSourceRanges }} + loadBalancerSourceRanges: + {{- toYaml .Values.service.loadBalancerSourceRanges | nindent 4 }} + {{- end }} + ports: + - port: {{ .Values.service.httpPort }} + targetPort: http + protocol: TCP + name: http + - port: {{ .Values.service.httpsPort }} + targetPort: https + protocol: TCP + name: https + - port: {{ .Values.service.port }} + targetPort: backoffice + protocol: TCP + name: backoffice + {{- if and (eq .Values.service.type "NodePort") .Values.service.nodePort }} + nodePort: {{ .Values.service.nodePort }} + {{- end }} + - port: {{ .Values.probo.metrics.port }} + targetPort: metrics + protocol: TCP + name: metrics + selector: + {{- include "probo.selectorLabels" . | nindent 4 }} diff --git a/contrib/helm/charts/probo/templates/serviceaccount.yaml b/contrib/helm/charts/probo/templates/serviceaccount.yaml new file mode 100644 index 000000000..768ded582 --- /dev/null +++ b/contrib/helm/charts/probo/templates/serviceaccount.yaml @@ -0,0 +1,13 @@ +{{- if .Values.serviceAccount.create -}} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "probo.serviceAccountName" . }} + labels: + {{- include "probo.labels" . | nindent 4 }} + {{- with .Values.serviceAccount.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +automountServiceAccountToken: {{ .Values.serviceAccount.automount }} +{{- end }} diff --git a/contrib/helm/charts/probo/templates/servicemonitor.yaml b/contrib/helm/charts/probo/templates/servicemonitor.yaml new file mode 100644 index 000000000..49ffdff1f --- /dev/null +++ b/contrib/helm/charts/probo/templates/servicemonitor.yaml @@ -0,0 +1,24 @@ +{{- if .Values.metrics.serviceMonitor.enabled }} +apiVersion: monitoring.coreos.com/v1 +kind: ServiceMonitor +metadata: + name: {{ include "probo.fullname" . }} + labels: + {{- include "probo.labels" . | nindent 4 }} + {{- with .Values.metrics.serviceMonitor.labels }} + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + selector: + matchLabels: + {{- include "probo.selectorLabels" . | nindent 6 }} + endpoints: + - port: metrics + path: /metrics + interval: {{ .Values.metrics.serviceMonitor.interval }} + scrapeTimeout: {{ .Values.metrics.serviceMonitor.scrapeTimeout }} + {{- with .Values.metrics.serviceMonitor.relabelings }} + relabelings: + {{- toYaml . | nindent 8 }} + {{- end }} +{{- end }} diff --git a/contrib/helm/charts/probo/values-production.yaml.example b/contrib/helm/charts/probo/values-production.yaml.example new file mode 100644 index 000000000..d90768f2e --- /dev/null +++ b/contrib/helm/charts/probo/values-production.yaml.example @@ -0,0 +1,311 @@ +# Production values for probo. +# This file provides a template for production deployment. +# Copy this file to values-production.yaml and customize it for your environment. + +# Use specific version tag +image: + repository: ghcr.io/getprobo/probo + tag: "0.74.7" + pullPolicy: IfNotPresent + +# Scale for high availability +replicaCount: 3 + +# Production security context (non-root user) +podSecurityContext: + runAsNonRoot: true + runAsUser: 1000 # probo user (UID defined in Dockerfile) + runAsGroup: 1000 + fsGroup: 1000 + +securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + readOnlyRootFilesystem: false # /data directory requires write access + runAsUser: 1000 + runAsGroup: 1000 + +# Service configuration +service: + type: ClusterIP + port: 8080 + httpPort: 80 + httpsPort: 443 + annotations: {} + +# Production resource limits +resources: + limits: + cpu: 2000m + memory: 4Gi + requests: + cpu: 1000m + memory: 2Gi + +# Enable horizontal pod autoscaling +autoscaling: + enabled: true + minReplicas: 3 + maxReplicas: 10 + targetCPUUtilizationPercentage: 70 + targetMemoryUtilizationPercentage: 80 + +# HAProxy Ingress Controller configuration +haproxy-ingress: + enabled: true + controller: + ingressClass: haproxy + ingressClassResource: + enabled: true + name: haproxy + default: false + name: controller + service: + type: LoadBalancer + annotations: + # Cloud provider annotations for LoadBalancer + # AWS example: + # service.beta.kubernetes.io/aws-load-balancer-type: "nlb" + # GCP example: + # cloud.google.com/load-balancer-type: "External" + extraArgs: + - --tcp-services-configmap=$(POD_NAMESPACE)/haproxy-tcp-services + config: + ssl-redirect: "false" # Probo handles SSL internally + +# Configure ingress with TLS +ingress: + enabled: true + className: "haproxy" + annotations: + kubernetes.io/ingress.class: haproxy + cert-manager.io/cluster-issuer: "letsencrypt-prod" + hosts: + - host: probo.example.com + paths: + - path: / + pathType: Prefix + tls: + - secretName: probo-tls + hosts: + - probo.example.com + +# Probo application configuration +probo: + # Must match your ingress hostname + hostname: "probo.example.com" + + # REQUIRED: Generate with: openssl rand -base64 32 + encryptionKey: "CHANGE_ME_ENCRYPTION_KEY" + + service: + port: 8080 + + metrics: + port: 8081 + + # CORS configuration - add all domains that will access the API + cors: + allowedOrigins: + - "https://probo.example.com" + + # Extra HTTP headers to add to responses + extraHeaderFields: {} + # X-Custom-Header: "custom-value" + + # Authentication + auth: + # Set to true to disable public signup + disableSignup: false + # Cookie domain should be your root domain + cookieDomain: "example.com" + # REQUIRED: Generate with: openssl rand -base64 32 + cookieSecret: "CHANGE_ME_COOKIE_SECRET" + # REQUIRED: Generate with: openssl rand -base64 32 + passwordPepper: "CHANGE_ME_PASSWORD_PEPPER" + + # Trust center authentication + trustAuth: + cookieName: "TCT" + cookieDomain: "example.com" + cookieDuration: 24 + tokenDuration: 168 + reportUrlDuration: 15 + ports: + httpChallenge: 80 + tlsHttpServer: 443 + # REQUIRED: Generate with: openssl rand -base64 32 + tokenSecret: "CHANGE_ME_TRUST_TOKEN_SECRET" + scope: "trust_center_readonly" + tokenType: "trust_center_access" + + # Email configuration + mailer: + senderName: "Probo" + senderEmail: "noreply@example.com" + smtp: + # Use your SMTP provider (e.g., SendGrid, Mailgun, AWS SES) + addr: "smtp.sendgrid.net:587" + user: "apikey" + password: "CHANGE_ME_SMTP_PASSWORD" + tlsRequired: true + + # OpenAI integration for AI features (optional) + openai: + apiKey: "CHANGE_ME_OPENAI_API_KEY" + temperature: 0.1 + modelName: "gpt-4o" + + # OpenTelemetry tracing (optional) + tracing: + enabled: true + addr: "tempo.monitoring:4317" + maxBatchSize: 1000 + batchTimeout: 10 + exportTimeout: 30 + maxQueueSize: 10000 + + # Custom domains with Let's Encrypt (optional) + customDomains: + enabled: true + renewalInterval: 86400 # 24 hours + provisionInterval: 300 # 5 minutes + cnameTarget: "custom.example.com" + acme: + directory: "https://acme-v02.api.letsencrypt.org/directory" + email: "admin@example.com" + keyType: "EC256" + insecureTls: false + + # External OAuth2 connectors (optional) + connectors: + - name: "github" + type: "oauth2" + config: + client-id: "CHANGE_ME_GITHUB_CLIENT_ID" + client-secret: "CHANGE_ME_GITHUB_CLIENT_SECRET" + redirect-uri: "https://probo.example.com/api/console/v1/connectors/complete" + auth-url: "https://github.com/login/oauth/authorize" + token-url: "https://github.com/login/oauth/access_token" + +# PostgreSQL configuration (REQUIRED) +# For production: Use external managed PostgreSQL (AWS RDS, GCP Cloud SQL, etc.) +# Disable the included PostgreSQL chart +postgresql: + # Disable the included PostgreSQL for production + enabled: false + + # External PostgreSQL connection (configure these when enabled=false) + # Examples: + # - AWS RDS: mydb.abc123.us-east-1.rds.amazonaws.com + # - GCP Cloud SQL: 10.0.0.5 (private IP) + # - Azure Database: myserver.postgres.database.azure.com + # - DigitalOcean: db-postgresql-nyc1-12345.ondigitalocean.com + host: "CHANGE_ME_POSTGRES_HOST" + port: 5432 + database: "probod" + username: "probod" + password: "CHANGE_ME_DB_PASSWORD" + poolSize: 200 + # Uncomment if using TLS with custom CA + # caCertBundle: | + # -----BEGIN CERTIFICATE----- + # ... + # -----END CERTIFICATE----- + +# MinIO configuration +# Disable included MinIO for production - use external S3 instead +minio: + enabled: false + +# S3 storage configuration (REQUIRED) +s3: + # AWS S3 example: + region: "us-east-1" + bucket: "probo-production" + # Leave endpoint empty for AWS S3 + endpoint: "" + accessKeyId: "CHANGE_ME_AWS_ACCESS_KEY_ID" + secretAccessKey: "CHANGE_ME_AWS_SECRET_ACCESS_KEY" + + # GCP Cloud Storage example (uncomment and adjust): + # region: "us-east1" + # bucket: "probo-production" + # endpoint: "https://storage.googleapis.com" + # accessKeyId: "GOOG1E..." # HMAC access key + # secretAccessKey: "..." # HMAC secret + + # DigitalOcean Spaces example (uncomment and adjust): + # region: "nyc3" + # bucket: "probo-production" + # endpoint: "https://nyc3.digitaloceanspaces.com" + # accessKeyId: "..." # Spaces access key + # secretAccessKey: "..." # Spaces secret + +# Chrome for PDF generation +chrome: + enabled: true + # Scale for concurrent PDF generation + replicaCount: 3 + resources: + limits: + cpu: 1000m + memory: 2Gi + requests: + cpu: 500m + memory: 1Gi + + # To use external Chrome service instead, set enabled=false and configure: + # external: + # addr: "chrome.browserless.io:3000" + +# Persistent data volume (optional) +persistence: + enabled: true + # Use existing PVC or let Helm create one + # existingClaim: "probo-data-pvc" + storageClass: "" # Use default storage class or specify one + accessMode: ReadWriteOnce + size: 50Gi + annotations: {} + +# Enable Prometheus monitoring +metrics: + serviceMonitor: + enabled: true + interval: 30s + scrapeTimeout: 10s + labels: + prometheus: kube-prometheus + +# Pod placement (optional) +nodeSelector: {} + # node-role.kubernetes.io/worker: "true" + +tolerations: [] + +# Anti-affinity for high availability +affinity: + podAntiAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - weight: 100 + podAffinityTerm: + labelSelector: + matchExpressions: + - key: app.kubernetes.io/name + operator: In + values: + - probo + topologyKey: kubernetes.io/hostname + +# Service account annotations (for workload identity, IRSA, etc.) +serviceAccount: + create: true + annotations: {} + # AWS EKS example (uncomment and adjust): + # eks.amazonaws.com/role-arn: arn:aws:iam::ACCOUNT_ID:role/probo-role + + # GCP GKE example (uncomment and adjust): + # iam.gke.io/gcp-service-account: probo@PROJECT_ID.iam.gserviceaccount.com diff --git a/contrib/helm/charts/probo/values.yaml b/contrib/helm/charts/probo/values.yaml new file mode 100644 index 000000000..cc93ed397 --- /dev/null +++ b/contrib/helm/charts/probo/values.yaml @@ -0,0 +1,352 @@ +# Default values for probo. +# This is a YAML-formatted file. +# Declare variables to be passed into your templates. + +replicaCount: 1 + +image: + repository: ghcr.io/getprobo/probo + pullPolicy: IfNotPresent + # Overrides the image tag whose default is the chart appVersion. + tag: "latest" + +imagePullSecrets: [] +nameOverride: "" +fullnameOverride: "" + +serviceAccount: + # Specifies whether a service account should be created + create: true + # Automatically mount a ServiceAccount's API credentials? + automount: true + # Annotations to add to the service account + annotations: {} + # The name of the service account to use. + # If not set and create is true, a name is generated using the fullname template + name: "" + +podAnnotations: {} +podLabels: {} + +podSecurityContext: + runAsUser: 0 + runAsGroup: 0 +# runAsNonRoot: false +# runAsUser: 1000 # probo user (UID defined in Dockerfile) +# fsGroup: 1000 # probo group + +securityContext: + runAsUser: 0 + runAsGroup: 0 + privileged: true +# allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + readOnlyRootFilesystem: false # /data directory requires write access for file uploads + +service: + type: ClusterIP + port: 8080 + httpPort: 80 + httpsPort: 443 + # nodePort: 30080 + # clusterIP: "" + # loadBalancerIP: "" + # loadBalancerSourceRanges: [] + annotations: {} + +haproxy-ingress: + enabled: true + controller: + ingressClass: haproxy # This tells the controller to create an IngressClass + ingressClassResource: + enabled: true # Enable IngressClass resource creation + name: haproxy + default: false + name: controller + service: + type: LoadBalancer + annotations: {} + extraArgs: + - --tcp-services-configmap=$(POD_NAMESPACE)/haproxy-tcp-services + config: + # HAProxy Ingress global config + ssl-redirect: "false" + +ingress: + enabled: true + className: "haproxy" + annotations: + kubernetes.io/ingress.class: haproxy + # cert-manager.io/cluster-issuer: letsencrypt-prod + hosts: + - host: probo.example.com + paths: + - path: / + pathType: Prefix + backend: + service: + name: probo + port: + number: 8080 + tls: [] + # - secretName: probo-tls + # hosts: + # - probo.example.com + +resources: + limits: + cpu: 2000m + memory: 2Gi + requests: + cpu: 500m + memory: 512Mi + +livenessProbe: + httpGet: + path: / + port: backoffice + initialDelaySeconds: 30 + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 6 + +readinessProbe: + httpGet: + path: / + port: backoffice + initialDelaySeconds: 10 + periodSeconds: 5 + timeoutSeconds: 3 + failureThreshold: 3 + +autoscaling: + enabled: false + minReplicas: 1 + maxReplicas: 10 + targetCPUUtilizationPercentage: 80 + # targetMemoryUtilizationPercentage: 80 + +# Additional volumes on the output Deployment definition. +volumes: [] +# - name: foo +# secret: +# secretName: mysecret +# optional: false + +# Additional volumeMounts on the output Deployment definition. +volumeMounts: [] +# - name: foo +# mountPath: "/etc/foo" +# readOnly: true + +nodeSelector: {} + +tolerations: [] + +affinity: {} + +# Persistent data volume +persistence: + enabled: false + # existingClaim: "" + storageClass: "" + accessMode: ReadWriteOnce + size: 10Gi + annotations: {} + +# Prometheus Operator ServiceMonitor +metrics: + serviceMonitor: + enabled: false + interval: 30s + scrapeTimeout: 10s + labels: {} + relabelings: [] + +# Probo application configuration +probo: + # Public hostname where Probo will be accessible + # This should match your ingress host + hostname: probo.example.com + + # Base64-encoded encryption key (REQUIRED) + # Generate with: openssl rand -base64 32 + encryptionKey: "" + + service: + port: 8080 + + metrics: + port: 8081 + + # OpenTelemetry tracing configuration + tracing: + enabled: false + # OTLP gRPC endpoint (e.g., tempo:4317) + addr: "" + maxBatchSize: 512 + batchTimeout: 5 + exportTimeout: 30 + maxQueueSize: 2048 +# pg: +# addr: "probo-postgresql-demo-app:5432" +# username: "postgres" +# password: "probod" +# database: "probod" +# pool-size: 100 + + # CORS configuration + cors: + allowedOrigins: + - "https://probo.example.com" + - "http://probo.example.com" + + # Extra HTTP headers to add to responses + extraHeaderFields: {} + + # Authentication configuration + auth: + disableSignup: false + invitationTokenValidity: 3600 + cookieName: "SSID" + cookieDomain: "probo.example.com" + # Cookie signing secret (REQUIRED, at least 32 bytes) + # Generate with: openssl rand -base64 32 + cookieSecret: "" + cookieDuration: 24 + # Password hashing pepper (REQUIRED, at least 32 bytes) + # Generate with: openssl rand -base64 32 + passwordPepper: "" + passwordIterations: 1000000 + + # Trust center authentication + trustAuth: + cookieName: "TCT" + cookieDomain: "probo.example.com" + cookieDuration: 24 + tokenDuration: 168 + reportUrlDuration: 15 + ports: + httpChallenge: 80 + 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" + + # Email configuration + mailer: + senderName: "Probo" + senderEmail: "no-reply@notification.getprobo.com" + smtp: + addr: "sandbox.smtp.mailtrap.io:2525" + user: "2d1b1d0e8b3d0b" + password: "25a8eb11e75e8d" + tlsRequired: true +# smtp: +# addr: "localhost:1025" +# user: "" +# password: "" +# tlsRequired: false + + # OpenAI integration (optional) + openai: + apiKey: "" + temperature: 0.1 + modelName: "gpt-4o" + + # Custom domains configuration (optional) + customDomains: + enabled: false + renewalInterval: 3600 + provisionInterval: 30 + cnameTarget: "probo.example.com" + acme: + directory: "https://acme-v02.api.letsencrypt.org/directory" + email: "admin@example.com" + keyType: "EC256" + insecureTls: false + + # External OAuth2 connectors (optional) + connectors: [] + # - name: "github" + # type: "oauth2" + # config: + # client-id: "github-client-id" + # client-secret: "github-client-secret" + # redirect-uri: "https://probo.example.com/api/console/v1/connectors/complete" + # auth-url: "https://github.com/login/oauth/authorize" + # token-url: "https://github.com/login/oauth/access_token" + +# PostgreSQL configuration +# For production: Use external managed PostgreSQL (AWS RDS, GCP Cloud SQL, etc.) +# For testing: Enable the included PostgreSQL chart +postgresql: + # -- enabled included Postgres container for demo purposes only using cloudnative-pg + enabled: true + resources: + limits: + memory: "2Gi" + ephemeral-storage: "5Gi" + auth: + postgresUser: probod + postgresPassword: probod + database: probod + +# S3 storage configuration +# For production: Use external S3 (AWS S3, GCS, etc.) +# For testing: Enable the included MinIO chart +s3: + # External S3 configuration (used when minio.enabled=false) + region: "us-east-1" + bucket: "probod" + # For AWS S3, leave endpoint empty + # For S3-compatible storage (MinIO, DigitalOcean Spaces, etc.), set the endpoint + endpoint: "" + accessKeyId: "" # REQUIRED when minio.enabled=false: S3 access key + secretAccessKey: "" # REQUIRED when minio.enabled=false: S3 secret key + +# MinIO configuration +# Enable included MinIO for testing/development (NOT for production) +minio: + # -- enabled included Minio operator for s3 resource demo purposes + enabled: true + mode: standalone + persistence: + enabled: false + resources: + requests: + memory: 128Mi + primary: + enabled: true + auth: + rootUser: probod + rootPassword: probod + +# Headless Chrome for PDF generation +chrome: + # Deploy Chrome in the cluster + enabled: true + + replicaCount: 1 + + image: + repository: chromedp/headless-shell + tag: "140.0.7259.2" + pullPolicy: IfNotPresent + + resources: + limits: + cpu: 1000m + memory: 1Gi + requests: + cpu: 100m + memory: 256Mi + + # External Chrome configuration + # Used when chrome.enabled=false + external: + addr: ""