Use full secret paths.
This commit is contained in:
6
.gitignore
vendored
6
.gitignore
vendored
@@ -10,6 +10,6 @@ CLAUDE.md
|
||||
|
||||
|
||||
# Test directory
|
||||
/test/tmp
|
||||
/test/test-cloud/*
|
||||
!/test/test-cloud/README.md
|
||||
test/tmp
|
||||
test/test-cloud/*
|
||||
!test/test-cloud/README.md
|
||||
|
@@ -149,9 +149,58 @@ Apps that rely on PostgreSQL or MySQL databases typically need a database initia
|
||||
|
||||
Examples of apps with db-init jobs: `gitea`, `codimd`, `immich`, `openproject`
|
||||
|
||||
##### Database URL Configuration
|
||||
|
||||
**Important:** When apps require database URLs with embedded credentials, always use a separate `dbUrl` secret instead of trying to construct the URL with environment variable substitution in Kustomize templates.
|
||||
|
||||
❌ **Wrong** (Kustomize cannot process runtime env var substitution):
|
||||
```yaml
|
||||
- name: DB_URL
|
||||
value: "postgresql://user:$(DB_PASSWORD)@host/db"
|
||||
```
|
||||
|
||||
✅ **Correct** (Use a dedicated secret):
|
||||
```yaml
|
||||
- name: DB_URL
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: app-secrets
|
||||
key: apps.appname.dbUrl
|
||||
```
|
||||
|
||||
Add `apps.appname.dbUrl` to the manifest's `requiredSecrets` and the `wild-app-add` script will generate the complete URL with embedded credentials.
|
||||
|
||||
##### Security Context Requirements
|
||||
|
||||
Pods must comply with Pod Security Standards. All pods should include proper security contexts to avoid deployment warnings:
|
||||
|
||||
```yaml
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
securityContext:
|
||||
runAsNonRoot: true
|
||||
runAsUser: 999 # Use appropriate non-root user ID
|
||||
runAsGroup: 999 # Use appropriate group ID
|
||||
seccompProfile:
|
||||
type: RuntimeDefault
|
||||
containers:
|
||||
- name: container-name
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
capabilities:
|
||||
drop:
|
||||
- ALL
|
||||
readOnlyRootFilesystem: false # Set to true when possible
|
||||
```
|
||||
|
||||
For PostgreSQL init jobs, use `runAsUser: 999` (postgres user). For other database types, use the appropriate non-root user ID for that database container.
|
||||
|
||||
#### Secrets
|
||||
|
||||
Secrets are managed in the `secrets.yaml` file in the Wild Cloud home directory. The app's `manifest.yaml` should list any required secrets under `requiredSecrets`. When the app is added, default secret values will be generated and stored in the `secrets.yaml` file. Secrets are always stored and referenced in the `apps.<app-name>.<secret-name>` yaml path. When `wild-app-deploy` is run, a Secret resource will be created in the Kubernetes cluster with the name `<app-name>-secrets`, containing all secrets defined in the manifest's `requiredSecrets` key. These secrets can then be referenced in the app's Kustomize files using a `secretKeyRef`. For example, to mount a secret in an environment variable, you would use:
|
||||
Secrets are managed in the `secrets.yaml` file in the Wild Cloud home directory. The app's `manifest.yaml` should list any required secrets under `requiredSecrets`. When the app is added, default secret values will be generated and stored in the `secrets.yaml` file. Secrets are always stored and referenced in the `apps.<app-name>.<secret-name>` yaml path. When `wild-app-deploy` is run, a Secret resource will be created in the Kubernetes cluster with the name `<app-name>-secrets`, containing all secrets defined in the manifest's `requiredSecrets` key. These secrets can then be referenced in the app's Kustomize files using a `secretKeyRef`.
|
||||
|
||||
**Important:** Always use the full dotted path from the manifest as the secret key, not just the last segment. For example, to mount a secret in an environment variable, you would use:
|
||||
|
||||
```yaml
|
||||
env:
|
||||
@@ -159,9 +208,11 @@ env:
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: immich-secrets
|
||||
key: dbPassword
|
||||
key: apps.immich.dbPassword # Use full dotted path, not just "dbPassword"
|
||||
```
|
||||
|
||||
This approach prevents naming conflicts between apps and makes secret keys more descriptive and consistent with the `secrets.yaml` structure.
|
||||
|
||||
`secrets.yaml` files should not be checked in to a git repository and are ignored by default in Wild Cloud home directories. Checked in kustomize files should only reference secrets, not compile them.
|
||||
|
||||
## App Lifecycle
|
||||
|
@@ -27,7 +27,7 @@ spec:
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: mysql-secrets
|
||||
key: rootPassword
|
||||
key: apps.mysql.rootPassword
|
||||
- name: DB_HOSTNAME
|
||||
value: "{{ .apps.ghost.dbHost }}"
|
||||
- name: DB_PORT
|
||||
@@ -40,5 +40,5 @@ spec:
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: ghost-secrets
|
||||
key: dbPassword
|
||||
key: apps.ghost.dbPassword
|
||||
restartPolicy: OnFailure
|
@@ -39,7 +39,7 @@ spec:
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: ghost-secrets
|
||||
key: dbPassword
|
||||
key: apps.ghost.dbPassword
|
||||
- name: GHOST_HOST
|
||||
value: {{ .apps.ghost.domain }}
|
||||
- name: GHOST_PORT_NUMBER
|
||||
@@ -50,7 +50,7 @@ spec:
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: ghost-secrets
|
||||
key: adminPassword
|
||||
key: apps.ghost.adminPassword
|
||||
- name: GHOST_EMAIL
|
||||
value: {{ .apps.ghost.adminEmail }}
|
||||
- name: GHOST_BLOG_TITLE
|
||||
@@ -75,7 +75,7 @@ spec:
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: ghost-secrets
|
||||
key: smtpPassword
|
||||
key: apps.ghost.smtpPassword
|
||||
- name: GHOST_SMTP_FROM_ADDRESS
|
||||
value: {{ .apps.ghost.smtp.from }}
|
||||
resources:
|
||||
|
@@ -36,7 +36,7 @@ spec:
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: postgres-secrets
|
||||
key: password
|
||||
key: apps.postgres.password
|
||||
- name: DB_HOSTNAME
|
||||
value: "{{ .apps.gitea.dbHost }}"
|
||||
- name: DB_DATABASE_NAME
|
||||
@@ -47,5 +47,5 @@ spec:
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: gitea-secrets
|
||||
key: dbPassword
|
||||
key: apps.gitea.dbPassword
|
||||
restartPolicy: OnFailure
|
@@ -33,27 +33,27 @@ spec:
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: gitea-secrets
|
||||
key: adminPassword
|
||||
key: apps.gitea.adminPassword
|
||||
- name: GITEA__security__SECRET_KEY
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: gitea-secrets
|
||||
key: secretKey
|
||||
key: apps.gitea.secretKey
|
||||
- name: GITEA__security__INTERNAL_TOKEN
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: gitea-secrets
|
||||
key: jwtSecret
|
||||
key: apps.gitea.jwtSecret
|
||||
- name: GITEA__database__PASSWD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: gitea-secrets
|
||||
key: dbPassword
|
||||
key: apps.gitea.dbPassword
|
||||
- name: GITEA__mailer__PASSWD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: gitea-secrets
|
||||
key: smtpPassword
|
||||
key: apps.gitea.smtpPassword
|
||||
ports:
|
||||
- name: ssh
|
||||
containerPort: 2222
|
||||
|
@@ -53,7 +53,7 @@ spec:
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: postgres-secrets
|
||||
key: password
|
||||
key: apps.postgres.password
|
||||
- name: DB_HOSTNAME
|
||||
value: "{{ .apps.immich.dbHostname }}"
|
||||
- name: DB_DATABASE_NAME
|
||||
@@ -64,5 +64,5 @@ spec:
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: immich-secrets
|
||||
key: dbPassword
|
||||
key: apps.immich.dbPassword
|
||||
restartPolicy: OnFailure
|
||||
|
@@ -33,7 +33,7 @@ spec:
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: immich-secrets
|
||||
key: dbPassword
|
||||
key: apps.immich.dbPassword
|
||||
- name: TZ
|
||||
value: "{{ .apps.immich.timezone }}"
|
||||
- name: IMMICH_WORKERS_EXCLUDE
|
||||
|
@@ -36,7 +36,7 @@ spec:
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: immich-secrets
|
||||
key: dbPassword
|
||||
key: apps.immich.dbPassword
|
||||
- name: TZ
|
||||
value: "{{ .apps.immich.timezone }}"
|
||||
- name: IMMICH_WORKERS_EXCLUDE
|
||||
|
@@ -82,14 +82,14 @@ spec:
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: mysql-secrets
|
||||
key: rootPassword
|
||||
key: apps.mysql.rootPassword
|
||||
- name: MYSQL_USER
|
||||
value: {{ .apps.mysql.user }}
|
||||
- name: MYSQL_PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: mysql-secrets
|
||||
key: password
|
||||
key: apps.mysql.password
|
||||
- name: MYSQL_DATABASE
|
||||
value: {{ .apps.mysql.dbName }}
|
||||
- name: MYSQL_PORT
|
||||
|
@@ -36,7 +36,7 @@ spec:
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: postgres-secrets
|
||||
key: password
|
||||
key: apps.postgres.password
|
||||
- name: DB_HOSTNAME
|
||||
value: "{{ .apps.openproject.dbHostname }}"
|
||||
- name: DB_DATABASE_NAME
|
||||
@@ -47,5 +47,5 @@ spec:
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: openproject-secrets
|
||||
key: dbPassword
|
||||
key: apps.openproject.dbPassword
|
||||
restartPolicy: OnFailure
|
@@ -62,12 +62,12 @@ spec:
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: openproject-secrets
|
||||
key: dbPassword
|
||||
key: apps.openproject.dbPassword
|
||||
- name: OPENPROJECT_SEED_ADMIN_USER_PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: openproject-secrets
|
||||
key: adminPassword
|
||||
key: apps.openproject.adminPassword
|
||||
resources:
|
||||
limits:
|
||||
memory: 200Mi
|
||||
@@ -106,12 +106,12 @@ spec:
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: openproject-secrets
|
||||
key: dbPassword
|
||||
key: apps.openproject.dbPassword
|
||||
- name: OPENPROJECT_SEED_ADMIN_USER_PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: openproject-secrets
|
||||
key: adminPassword
|
||||
key: apps.openproject.adminPassword
|
||||
resources:
|
||||
limits:
|
||||
memory: 512Mi
|
||||
|
@@ -84,12 +84,12 @@ spec:
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: openproject-secrets
|
||||
key: dbPassword
|
||||
key: apps.openproject.dbPassword
|
||||
- name: OPENPROJECT_SEED_ADMIN_USER_PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: openproject-secrets
|
||||
key: adminPassword
|
||||
key: apps.openproject.adminPassword
|
||||
args:
|
||||
- /app/docker/prod/wait-for-db
|
||||
resources:
|
||||
@@ -127,12 +127,12 @@ spec:
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: openproject-secrets
|
||||
key: dbPassword
|
||||
key: apps.openproject.dbPassword
|
||||
- name: OPENPROJECT_SEED_ADMIN_USER_PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: openproject-secrets
|
||||
key: adminPassword
|
||||
key: apps.openproject.adminPassword
|
||||
args:
|
||||
- /app/docker/prod/web
|
||||
volumeMounts:
|
||||
|
@@ -84,12 +84,12 @@ spec:
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: openproject-secrets
|
||||
key: dbPassword
|
||||
key: apps.openproject.dbPassword
|
||||
- name: OPENPROJECT_SEED_ADMIN_USER_PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: openproject-secrets
|
||||
key: adminPassword
|
||||
key: apps.openproject.adminPassword
|
||||
args:
|
||||
- bash
|
||||
- /app/docker/prod/wait-for-db
|
||||
@@ -132,7 +132,7 @@ spec:
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: openproject-secrets
|
||||
key: dbPassword
|
||||
key: apps.openproject.dbPassword
|
||||
- name: "OPENPROJECT_GOOD_JOB_QUEUES"
|
||||
value: ""
|
||||
volumeMounts:
|
||||
|
@@ -44,7 +44,7 @@ spec:
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: postgres-secrets
|
||||
key: password
|
||||
key: apps.postgres.password
|
||||
volumeMounts:
|
||||
- name: postgres-data
|
||||
mountPath: /var/lib/postgresql/data
|
||||
|
@@ -73,5 +73,5 @@ spec:
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: postgres-secrets
|
||||
key: password
|
||||
key: apps.postgres.password
|
||||
restartPolicy: Never
|
||||
|
@@ -79,27 +79,22 @@ deploy_secrets() {
|
||||
|
||||
echo "Deploying secrets for app '${app_name}' in namespace '${namespace}'"
|
||||
|
||||
# Create secret data
|
||||
# Gather data for app secret
|
||||
local secret_data=""
|
||||
while IFS= read -r secret_path; do
|
||||
# Get the secret value using full path
|
||||
secret_value=$(yq eval ".${secret_path} // \"\"" "${SECRETS_FILE}")
|
||||
|
||||
# Extract just the key name for the Kubernetes secret (handle dotted paths)
|
||||
secret_key="${secret_path##*.}"
|
||||
|
||||
if [ -n "${secret_value}" ] && [ "${secret_value}" != "null" ]; then
|
||||
if [[ "${secret_value}" == CHANGE_ME_* ]]; then
|
||||
echo "Warning: Secret '${secret_path}' for app '${app_name}' still has dummy value: ${secret_value}"
|
||||
fi
|
||||
secret_data="${secret_data} --from-literal=${secret_key}=${secret_value}"
|
||||
secret_data="${secret_data} --from-literal=${secret_path}=${secret_value}"
|
||||
else
|
||||
echo "Error: Required secret '${secret_path}' not found in ${SECRETS_FILE} for app '${app_name}'"
|
||||
exit 1
|
||||
fi
|
||||
done < <(yq eval '.requiredSecrets[]' "${manifest_file}")
|
||||
|
||||
# Create the secret if we have data
|
||||
# Create/update app secret in cluster
|
||||
if [ -n "${secret_data}" ]; then
|
||||
echo "Creating/updating secret '${app_name}-secrets' in namespace '${namespace}'"
|
||||
if [ "${DRY_RUN:-}" = "--dry-run=client" ]; then
|
||||
@@ -112,9 +107,11 @@ deploy_secrets() {
|
||||
fi
|
||||
}
|
||||
|
||||
# Step 1: Create namespaces first (dependencies and main app)
|
||||
# Step 1: Create namespaces first
|
||||
echo "Creating namespaces..."
|
||||
MANIFEST_FILE="apps/${APP_NAME}/manifest.yaml"
|
||||
|
||||
# Create dependency namespaces.
|
||||
if [ -f "${MANIFEST_FILE}" ]; then
|
||||
if yq eval '.requires' "${MANIFEST_FILE}" | grep -q -v '^null$'; then
|
||||
yq eval '.requires[].name' "${MANIFEST_FILE}" | while read -r required_app; do
|
||||
|
Reference in New Issue
Block a user