From d3916003396238808d5ea2245382f909ba1846c5 Mon Sep 17 00:00:00 2001 From: yakubuv988-design Date: Mon, 19 Oct 2026 17:02:23 +0100 Subject: [PATCH] fix(k8s,restore,docker): harden pod security contexts, add PDBs, clarify CONTRACT_STATE_FILE, and link upstream issues - Clarify CONTRACT_STATE_FILE (--post-restore-sql) semantics in scripts/restore.sh and reject JSON contract snapshots - Harden frontend pod and container securityContext and enable readOnlyRootFilesystem on backend and frontend deployments - Add PodDisruptionBudgets for backend and frontend in k8s/pdb.yaml - Link upstream SynapsVault/backend and SynapsVault/frontend issues in Dockerfiles Closes #46, Closes #55, Closes #56, Closes #57 --- backend/Dockerfile | 5 +++-- frontend/Dockerfile | 3 ++- k8s/backend-deployment.yaml | 11 +++++++++++ k8s/frontend-deployment.yaml | 23 +++++++++++++++++++++++ k8s/kustomization.yaml | 1 + k8s/pdb.yaml | 23 +++++++++++++++++++++++ scripts/restore.sh | 35 ++++++++++++++++++++++++++--------- 7 files changed, 89 insertions(+), 12 deletions(-) create mode 100644 k8s/pdb.yaml diff --git a/backend/Dockerfile b/backend/Dockerfile index a16aa22..f9fc946 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -10,8 +10,9 @@ ARG NODE_VERSION=22 # The backend's dependency tree has unresolved peer conflicts (e.g. # x402-stellar wants @stellar/stellar-sdk@^12 while the app uses ^15), so npm -# needs --legacy-peer-deps until that is fixed upstream. Override with -# --build-arg NPM_FLAGS= once it is. +# needs --legacy-peer-deps until that is fixed upstream +# (tracked in https://github.com/SynapsVault/backend/issues). Override with +# --build-arg NPM_FLAGS= once the upstream peer dependency issue is resolved. ARG NPM_FLAGS=--legacy-peer-deps # ---------- Stage 1: All dependencies (build + migrate tooling) ---------- diff --git a/frontend/Dockerfile b/frontend/Dockerfile index eac2e4a..4003111 100644 --- a/frontend/Dockerfile +++ b/frontend/Dockerfile @@ -4,7 +4,8 @@ ARG NODE_VERSION=22 # The frontend repo has no lockfile and conflicting peer dependencies, which -# crash npm's resolver; --legacy-peer-deps works around it until fixed upstream. +# crash npm's resolver; --legacy-peer-deps works around it until fixed upstream +# (tracked in https://github.com/SynapsVault/frontend/issues). ARG NPM_FLAGS=--legacy-peer-deps # Stage 1: Build the Vite application diff --git a/k8s/backend-deployment.yaml b/k8s/backend-deployment.yaml index 74c290e..0fc8849 100644 --- a/k8s/backend-deployment.yaml +++ b/k8s/backend-deployment.yaml @@ -37,8 +37,12 @@ spec: name: backend-secret securityContext: allowPrivilegeEscalation: false + readOnlyRootFilesystem: true capabilities: drop: ["ALL"] + volumeMounts: + - name: tmp + mountPath: /tmp resources: requests: cpu: "100m" @@ -61,8 +65,12 @@ spec: name: backend-secret securityContext: allowPrivilegeEscalation: false + readOnlyRootFilesystem: true capabilities: drop: ["ALL"] + volumeMounts: + - name: tmp + mountPath: /tmp livenessProbe: httpGet: path: /health @@ -86,3 +94,6 @@ spec: limits: cpu: "1000m" memory: "512Mi" + volumes: + - name: tmp + emptyDir: {} diff --git a/k8s/frontend-deployment.yaml b/k8s/frontend-deployment.yaml index b0c8886..bd34fc4 100644 --- a/k8s/frontend-deployment.yaml +++ b/k8s/frontend-deployment.yaml @@ -19,6 +19,12 @@ spec: labels: app: frontend spec: + securityContext: + runAsNonRoot: true + runAsUser: 101 + runAsGroup: 101 + seccompProfile: + type: RuntimeDefault containers: - name: frontend image: frontend @@ -29,6 +35,16 @@ spec: protocol: TCP securityContext: allowPrivilegeEscalation: false + readOnlyRootFilesystem: true + capabilities: + drop: ["ALL"] + volumeMounts: + - name: nginx-cache + mountPath: /var/cache/nginx + - name: nginx-run + mountPath: /var/run + - name: tmp + mountPath: /tmp livenessProbe: httpGet: path: / @@ -48,6 +64,13 @@ spec: limits: cpu: "250m" memory: "128Mi" + volumes: + - name: nginx-cache + emptyDir: {} + - name: nginx-run + emptyDir: {} + - name: tmp + emptyDir: {} --- apiVersion: v1 kind: Service diff --git a/k8s/kustomization.yaml b/k8s/kustomization.yaml index b149c3f..1b80dbb 100644 --- a/k8s/kustomization.yaml +++ b/k8s/kustomization.yaml @@ -12,6 +12,7 @@ resources: - backend-deployment.yaml - backend-service.yaml - frontend-deployment.yaml + - pdb.yaml - ingress.yaml labels: diff --git a/k8s/pdb.yaml b/k8s/pdb.yaml new file mode 100644 index 0000000..51ddfda --- /dev/null +++ b/k8s/pdb.yaml @@ -0,0 +1,23 @@ +apiVersion: policy/v1 +kind: PodDisruptionBudget +metadata: + name: backend + labels: + app: backend +spec: + minAvailable: 1 + selector: + matchLabels: + app: backend +--- +apiVersion: policy/v1 +kind: PodDisruptionBudget +metadata: + name: frontend + labels: + app: frontend +spec: + minAvailable: 1 + selector: + matchLabels: + app: frontend diff --git a/scripts/restore.sh b/scripts/restore.sh index 6f2f647..cd008bd 100644 --- a/scripts/restore.sh +++ b/scripts/restore.sh @@ -14,7 +14,11 @@ # from the backup came back, then drop it # --work-dir Working directory for downloads (default: mktemp -d) # --keep-work-dir Do not delete the working directory on exit -# --skip-contract-state Do not re-apply CONTRACT_STATE_FILE after restore +# --post-restore-sql Optional SQL script to run against Postgres after +# restoring (aliases $CONTRACT_STATE_FILE). Note: this +# is a PostgreSQL .sql script, NOT the on-chain JSON +# snapshots under contracts/ in the backup archive. +# --skip-contract-state Do not apply CONTRACT_STATE_FILE / --post-restore-sql # -h, --help Show this help # # Environment (same names as backup.sh; legacy names in parentheses): @@ -27,7 +31,10 @@ # BACKUP_ENCRYPTION_KEY gpg passphrase used by backup.sh # BACKUP_AGE_IDENTITY_FILE age identity file, for age-encrypted backups # DATABASE_URL Default target database connection string -# CONTRACT_STATE_FILE Optional SQL file to apply after restore +# CONTRACT_STATE_FILE Optional post-restore PostgreSQL .sql file +# (e.g. to update contract ID rows in DB; +# for repointing env/ConfigMap contract IDs, +# use scripts/rollback-contracts.cjs instead) # # Exit codes: # 0 success @@ -51,7 +58,7 @@ die() { } usage() { - sed -n '2,41p' "$0" | sed 's/^# \{0,1\}//' + sed -n '2,47p' "$0" | sed 's/^# \{0,1\}//' } # --------------------------------------------------------------------------- @@ -63,6 +70,7 @@ VERIFY_ONLY="false" WORK_DIR="" KEEP_WORK_DIR="false" SKIP_CONTRACT_STATE="false" +POST_RESTORE_SQL_ARG="" while [[ $# -gt 0 ]]; do case "$1" in @@ -89,6 +97,11 @@ while [[ $# -gt 0 ]]; do KEEP_WORK_DIR="true" shift ;; + --post-restore-sql) + [[ $# -ge 2 ]] || die 1 "--post-restore-sql requires a path to a .sql file" + POST_RESTORE_SQL_ARG="$2" + shift 2 + ;; --skip-contract-state) SKIP_CONTRACT_STATE="true" shift @@ -289,16 +302,20 @@ restore_into() { apply_contract_state() { local url="$1" if [[ "$SKIP_CONTRACT_STATE" == "true" ]]; then - log "skipping contract state re-application (--skip-contract-state)" + log "skipping post-restore SQL application (--skip-contract-state)" return 0 fi - if [[ -z "$CONTRACT_STATE_FILE" ]]; then - log "no CONTRACT_STATE_FILE configured; skipping contract state re-application" + local sql_file="${POST_RESTORE_SQL_ARG:-${CONTRACT_STATE_FILE:-}}" + if [[ -z "$sql_file" ]]; then + log "no CONTRACT_STATE_FILE / --post-restore-sql configured; skipping post-restore SQL" return 0 fi - [[ -f "$CONTRACT_STATE_FILE" ]] || die 4 "contract state file not found: $CONTRACT_STATE_FILE" - log "re-applying contract state from $CONTRACT_STATE_FILE" - psql_exec "$url" -f "$CONTRACT_STATE_FILE" || die 4 "failed to re-apply contract state" + [[ -f "$sql_file" ]] || die 4 "post-restore SQL file not found: $sql_file" + if [[ "$sql_file" == *.json ]]; then + die 1 "CONTRACT_STATE_FILE ($sql_file) must be a PostgreSQL .sql script, not a JSON contract snapshot (use scripts/rollback-contracts.cjs to repoint on-chain contract IDs)" + fi + log "applying post-restore SQL from $sql_file" + psql_exec "$url" -f "$sql_file" || die 4 "failed to apply post-restore SQL from $sql_file" } # ---------------------------------------------------------------------------