From e604e03831309b09d4315ccf4e3932cf15b35bb6 Mon Sep 17 00:00:00 2001 From: Morten Olsen Date: Mon, 19 Jan 2026 04:57:59 +0100 Subject: [PATCH] secret templatte documentation --- AGENTS.md | 389 +---------------------------- apps/common/README.md | 50 ++++ apps/common/templates/_helpers.tpl | 4 +- 3 files changed, 55 insertions(+), 388 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index e63b127..b990561 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -58,6 +58,7 @@ mkdir -p apps/charts/my-app/templates ``` #### Chart.yaml + ```yaml apiVersion: v2 version: 1.0.0 @@ -65,6 +66,7 @@ name: my-app ``` #### values.yaml + ```yaml image: repository: docker.io/org/my-app @@ -73,390 +75,5 @@ image: subdomain: my-app ``` -### 2. Core Templates +See ./apps/common/README.md for guide on writing charts -#### Deployment Template -Create `templates/deployment.yaml`: -```yaml -apiVersion: apps/v1 -kind: Deployment -metadata: - name: "{{ .Release.Name }}" -spec: - strategy: - type: Recreate - replicas: 1 - revisionHistoryLimit: 0 - selector: - matchLabels: - app: "{{ .Release.Name }}" - template: - metadata: - labels: - app: "{{ .Release.Name }}" - spec: - containers: - - name: "{{ .Release.Name }}" - image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" - imagePullPolicy: "{{ .Values.image.pullPolicy }}" - ports: - - name: http - containerPort: 8080 - protocol: TCP - livenessProbe: - tcpSocket: - port: http - readinessProbe: - tcpSocket: - port: http - volumeMounts: - - mountPath: /data - name: data - volumes: - - name: data - persistentVolumeClaim: - claimName: "{{ .Release.Name }}-data" -``` - -#### Service Template -Create `templates/service.yaml`: -```yaml -apiVersion: v1 -kind: Service -metadata: - name: "{{ .Release.Name }}" -spec: - ports: - - port: 80 - targetPort: http - protocol: TCP - name: http - selector: - app: "{{ .Release.Name }}" -``` - -#### Persistent Volume Claim -Create `templates/pvc.yaml`: -```yaml -apiVersion: v1 -kind: PersistentVolumeClaim -metadata: - name: "{{ .Release.Name }}-data" -spec: - accessModes: - - ReadWriteOnce - resources: - requests: - storage: 10Gi -``` - -## Custom Resource Definitions (CRDs) - -This project uses several custom resources that are managed by operators in the cluster: - -### 1. OIDC Client (OpenID Connect Authentication) - -The `OidcClient` resource automatically provisions OAuth2/OIDC clients with your identity provider. - -Create `templates/client.yaml`: -```yaml -apiVersion: homelab.mortenolsen.pro/v1 -kind: OidcClient -metadata: - name: '{{ .Release.Name }}' -spec: - environment: '{{ .Values.globals.environment }}' - redirectUris: - - path: /oauth/oidc/callback - subdomain: '{{ .Values.subdomain }}' - matchingMode: strict -``` - -**What it does:** -- Creates an OIDC client in your identity provider (e.g., Authentik) -- Generates a Kubernetes secret named `{{ .Release.Name }}-client` containing: - - `clientId`: The OAuth client ID - - `clientSecret`: The OAuth client secret - - `configuration`: The OIDC provider URL - -**Using in deployment:** -```yaml -env: - - name: OAUTH_CLIENT_ID - valueFrom: - secretKeyRef: - name: "{{ .Release.Name }}-client" - key: clientId - - name: OAUTH_CLIENT_SECRET - valueFrom: - secretKeyRef: - name: "{{ .Release.Name }}-client" - key: clientSecret - - name: OPENID_PROVIDER_URL - valueFrom: - secretKeyRef: - name: "{{ .Release.Name }}-client" - key: configuration -``` - -### 2. PostgreSQL Database - -The `PostgresDatabase` resource automatically provisions PostgreSQL databases. - -**New Version (Recommended):** - -Create `templates/database.yaml`: -```yaml -{{ include "common.database" . }} -``` - -Add to `values.yaml`: -```yaml -database: - enabled: true -``` - -**Legacy Version (Deprecated):** - -The legacy `homelab.mortenolsen.pro/v1` API version is deprecated. For new charts, use the common library template which uses the new `postgres.homelab.mortenolsen.pro/v1` API version. - -**What it does:** -- Creates a PostgreSQL database with the same name as your release -- Creates a user with appropriate permissions -- Generates a Kubernetes secret named `{{ .Release.Name }}-connection` containing: - - `url`: Complete PostgreSQL connection URL - - `host`: Database hostname - - `port`: Database port - - `database`: Database name - - `username`: Database username - - `password`: Database password - -**Using in deployment:** -```yaml -env: - - name: DATABASE_URL - valueFrom: - secretKeyRef: - name: "{{ .Release.Name }}-connection" - key: url -``` - -**Note:** The secret name changed from `{release}-pg-connection` (legacy) to `{release}-connection` (new version). The common library template handles this automatically. - -### 3. Secret Generation - -The `GenerateSecret` resource creates secure random secrets. - -Create `templates/secret.yaml`: -```yaml -apiVersion: homelab.mortenolsen.pro/v1 -kind: GenerateSecret -metadata: - name: "{{ .Release.Name }}-secrets" -spec: - fields: - - name: encryptionkey - encoding: hex # Options: hex, base64, alphanumeric - length: 64 # Length in bytes (before encoding) - - name: apitoken - encoding: base64 - length: 32 -``` - -**What it does:** -- Generates cryptographically secure random values -- Creates a Kubernetes secret with the specified fields -- Supports different encoding formats for different use cases - -**Using in deployment:** -```yaml -env: - - name: ENCRYPTION_KEY - valueFrom: - secretKeyRef: - name: "{{ .Release.Name }}-secrets" - key: encryptionkey -``` - -### 4. External HTTP Service - -The `ExternalHttpService` resource configures ingress routing for your application. - -Create `templates/external-http-service.yaml`: -```yaml -apiVersion: homelab.mortenolsen.pro/v1 -kind: ExternalHttpService -metadata: - name: '{{ .Release.Name }}' -spec: - environment: '{{ .Values.globals.environment }}' - subdomain: '{{ .Values.subdomain }}' - destination: - host: '{{ .Release.Name }}.{{ .Release.Namespace }}.svc.cluster.local' - port: - number: 80 -``` - -**What it does:** -- Creates ingress routes for your application -- Configures subdomain routing (e.g., `myapp.yourdomain.com`) -- Handles TLS termination automatically -- Integrates with your service mesh (if applicable) - -## Best Practices - -### 1. Naming Conventions -- Use `{{ .Release.Name }}` consistently for all resource names -- Suffix resource names appropriately: `-data`, `-secrets`, `-client`, `-database` - -### 2. Container Configuration -- Always specify health checks (liveness and readiness probes) -- Use named ports (e.g., `http`, `grpc`) instead of port numbers -- Set `revisionHistoryLimit: 0` to prevent accumulation of old ReplicaSets - -### 3. Environment Variables -- Never hardcode secrets in values.yaml -- Use secretKeyRef to reference generated secrets -- Group related environment variables together - -### 4. Persistent Storage -- Always use PVCs for stateful data -- Consider storage requirements carefully (start with reasonable defaults) -- Mount data at standard paths for the application - -### 5. OIDC Integration -- Set `ENABLE_SIGNUP: "false"` if using OIDC -- Enable OIDC signup with `ENABLE_OAUTH_SIGNUP: "true"` -- Configure email merging if needed with `OAUTH_MERGE_ACCOUNTS_BY_EMAIL` - -### 6. Database Usage -- Only include database.yaml if the app needs PostgreSQL -- Applications should support DATABASE_URL environment variable -- Consider connection pooling settings for production - -## Disabling Applications - -To temporarily disable an application, rename its directory with `.disabled` suffix: -```bash -mv apps/charts/my-app apps/charts/my-app.disabled -``` - -The ArgoCD ApplicationSet will automatically exclude directories matching `*.disabled`. - -## Testing Your Chart - -1. **Lint your chart:** - ```bash - helm lint apps/charts/my-app - ``` - -2. **Render templates locally:** - ```bash - helm template my-app apps/charts/my-app - ``` - -3. **Dry run installation:** - ```bash - helm install my-app apps/charts/my-app --dry-run --debug - ``` - -## Deployment Workflow - -**IMPORTANT:** There is no test environment. When creating or modifying applications: - -1. **Make changes directly to the files** - The agent will write changes to the actual chart files -2. **User deploys the changes** - After changes are made, the user must deploy them to the cluster -3. **Debug with kubectl** - If issues arise after deployment, agents can use kubectl to: - - Check pod status and logs - - Inspect generated resources - - Verify secret creation - - Troubleshoot configuration issues - -**Note:** Agents cannot deploy applications themselves. They can only: -- Create and modify chart files -- Use kubectl to investigate deployment issues -- Provide debugging assistance and recommendations - -## Common Patterns - -### Application with OIDC + Database -For apps requiring both authentication and database: -- Include `client.yaml` for OIDC -- Include `database.yaml` for PostgreSQL -- Reference both secrets in deployment - -### Stateless Applications -For simple stateless apps: -- Omit `pvc.yaml` -- Remove volume mounts from deployment -- Consider using `Deployment` scaling if appropriate - -### Background Services -For services without web interface: -- Omit `external-http-service.yaml` -- Omit `client.yaml` (no OIDC needed) -- Focus on service discovery within cluster - -## Troubleshooting - -### Secret Not Found -If secrets are not being created: -1. Check that the CRD controller is running -2. Verify the `environment` value matches your setup -3. Check controller logs for provisioning errors - -### OIDC Issues -1. Verify redirect URIs match exactly -2. Check that the identity provider is accessible -3. Ensure the client secret is being properly mounted - -### Database Connection -1. Verify the database operator is running -2. Check network policies between namespaces -3. Ensure the database server has capacity - -## Global Values - -Applications can access global values through `{{ .Values.globals }}`: -- `environment`: The deployment environment (e.g., "production", "staging") -- Additional values can be added at the root chart level - -## Maintenance - -### Updating Images -1. Update the tag in `values.yaml`: - ```yaml - tag: v1.0.0 # Use semantic version tags only - ``` -2. **Note:** Do not include SHA digests in tags. Immutable digests are automatically added later by Renovate - -### Renovate Integration -The project uses Renovate for automated dependency updates. Configure in `renovate.json5` to: -- Auto-update container images -- Create pull requests for updates -- Group related updates - -### Backup Considerations -For applications with persistent data: -1. Consider implementing backup CronJobs -2. Use volume snapshots if available -3. Export data regularly for critical applications - -## Contributing - -When adding new applications: -1. Follow the existing patterns and conventions -2. Document any special requirements in the chart's README -3. Consider security implications of all configurations -4. Update this document if introducing new patterns - -## Maintaining This Document - -**IMPORTANT:** When making changes to the project structure, patterns, or custom resources: -- Keep this AGENTS.md file up to date with any changes -- Document new CRDs or custom resources as they are added -- Update examples if the patterns change -- Add new sections for significant new features or patterns -- Ensure all code examples remain accurate and tested - -This document serves as the primary reference for creating and maintaining applications in this project. Keeping it current ensures consistency and helps onboard new contributors. \ No newline at end of file diff --git a/apps/common/README.md b/apps/common/README.md index 6327b0a..b117f9c 100644 --- a/apps/common/README.md +++ b/apps/common/README.md @@ -525,6 +525,56 @@ externalSecrets: The secret will contain a key named `mySecretKey` (not `my-password-generator`). +## Secret-based Configurations + +When an application requires a configuration file (like `config.yaml` or `.env`) that contains sensitive data, you should generate a `Secret` using External Secrets templating instead of a `ConfigMap`. This keeps the sensitive data encrypted in ETCD while allowing you to merge static and dynamic values. + +### 1. Create an External Secret with a Template +Create a custom template in your chart (e.g., `templates/external-config.yaml`): + +```yaml +apiVersion: external-secrets.io/v1beta1 +kind: ExternalSecret +metadata: + name: {{ include "common.fullname" . }}-config +spec: + refreshInterval: 1h + secretStoreRef: + name: vault-backend # Replace with your SecretStore name + kind: SecretStore + target: + name: {{ include "common.fullname" . }}-config + template: + engineVersion: v2 + data: + config.yaml: | + server: + port: 8080 + database: + user: {{ "{{" }} .db_user | toString {{ "}}" }} + password: {{ "{{" }} .db_pass | toString {{ "}}" }} + data: + - secretKey: db_user + remoteRef: + key: database/credentials + property: username + - secretKey: db_pass + remoteRef: + key: database/credentials + property: password +``` + +### 2. Mount the Secret in values.yaml +Reference the generated secret in your `volumes` configuration using the `{release}` placeholder: + +```yaml +volumes: + - name: config + mountPath: /app/config.yaml + subPath: config.yaml + secret: "{release}-config" +``` + ## Placeholders Use placeholders in `values.yaml` for dynamic values that are resolved at template time: diff --git a/apps/common/templates/_helpers.tpl b/apps/common/templates/_helpers.tpl index 24ea597..13feeba 100644 --- a/apps/common/templates/_helpers.tpl +++ b/apps/common/templates/_helpers.tpl @@ -214,10 +214,10 @@ Standard volumes {{- end }} {{- else if .configMap }} configMap: - name: {{ .configMap }} + name: {{ .configMap | replace "{release}" $.Release.Name | replace "{namespace}" $.Release.Namespace | replace "{fullname}" (include "common.fullname" $) }} {{- else if .secret }} secret: - secretName: {{ .secret }} + secretName: {{ .secret | replace "{release}" $.Release.Name | replace "{namespace}" $.Release.Namespace | replace "{fullname}" (include "common.fullname" $) }} {{- end }} {{- end }} {{- end }}