diff --git a/.woodpecker.yml b/.woodpecker.yml index b926171..050c21a 100644 --- a/.woodpecker.yml +++ b/.woodpecker.yml @@ -27,7 +27,6 @@ steps: trivy image --scanners vuln --ignore-unfixed --ignorefile .trivyignore --exit-code 1 --severity CRITICAL $REG/server3:$IMG_TAG trivy image --scanners vuln --ignore-unfixed --ignorefile .trivyignore --exit-code 1 --severity CRITICAL $REG/nginx:$IMG_TAG - - name: staging-deploy image: google/cloud-sdk:latest entrypoint: @@ -38,7 +37,6 @@ steps: mkdir -p ~/.ssh - # ── Inject known-hosts and SSH key ─────────────────────────────── gcloud secrets versions access latest \ --secret=STAGING_KNOWN_HOSTS --project=aptivaai-dev \ | base64 -d > ~/.ssh/known_hosts @@ -51,7 +49,6 @@ steps: echo "🔑 SSH prerequisites installed" - # ── SSH into staging and deploy ────────────────────────────────── ssh -o StrictHostKeyChecking=yes \ -i ~/.ssh/id_ed25519 \ jcoakley@10.128.0.12 \ @@ -109,24 +106,24 @@ steps: DEK_PATH=$(gcloud secrets versions access latest --secret=DEK_PATH_$ENV --project=$PROJECT); \ export DEK_PATH; \ export FROM_SECRETS_MANAGER=true; \ - # ── NEW: sync dev DEK into staging volume (uses Secret Manager) ── \ + \ if gcloud secrets describe WRAPPED_DEK_dev --project=$PROJECT >/dev/null 2>&1; then \ - echo \"🔁 Syncing dev DEK into staging volume\"; \ + echo "🔁 Syncing dev DEK into staging volume"; \ gcloud secrets versions access latest --secret=WRAPPED_DEK_dev --project=$PROJECT > /tmp/dev_dek.enc; \ if [ -s /tmp/dev_dek.enc ]; then \ docker volume ls -q | grep -qx aptiva_dek_staging || docker volume create aptiva_dek_staging >/dev/null; \ - sudo docker run --rm -v aptiva_dek_staging:/v -v /tmp:/host busybox sh -c 'set -e; mkdir -p /v/staging; cp -f /host/dev_dek.enc /v/staging/dek.enc; chown 1000:1000 /v/staging/dek.enc; chmod 400 /v/staging/dek.enc; rm -f /v/staging/dek.fpr; echo -n \"staging dek.enc bytes: \"; wc -c /app/.env.production +const rootPath = path.resolve(__dirname, '..'); +const env = (process.env.NODE_ENV || 'production'); +const envPath = path.resolve(rootPath, `.env.${env}`); if (!process.env.FROM_SECRETS_MANAGER) { - dotenv.config({ path: envPath }); + dotenv.config({ path: envPath, override: false }); } const PORT = process.env.SERVER3_PORT || 5002; -const API_BASE = `http://localhost:${PORT}/api`; +const API_BASE = `http://localhost:${PORT}/api`; /* ─── helper: canonical public origin ─────────────────────────── */ const PUBLIC_BASE = ( - process.env.APTIVA_AI_BASE // ← preferred - || process.env.REACT_APP_API_URL // ← old name, tolerated + process.env.APTIVA_AI_BASE + || process.env.REACT_APP_API_URL || '' -).replace(/\/+$/, ''); // strip trailing “/” +).replace(/\/+$/, ''); -/* allow‑list for redirects to block open‑redirect attacks */ const ALLOWED_REDIRECT_HOSTS = new Set([ new URL(PUBLIC_BASE || 'http://localhost').host ]); function isSafeRedirect(url) { - try { - const u = new URL(url); - return ALLOWED_REDIRECT_HOSTS.has(u.host) && u.protocol === 'https:'; - } catch { return false; } - } - + try { + const u = new URL(url); + return ALLOWED_REDIRECT_HOSTS.has(u.host) && u.protocol === 'https:'; + } catch { return false; } +} const app = express(); const { getDocument } = pkg; -const bt = "`".repeat(3); +const stripe = new Stripe(process.env.STRIPE_SECRET_KEY, { apiVersion: '2024-04-10' }); -const stripe = new Stripe(process.env.STRIPE_SECRET_KEY, { - apiVersion: '2024-04-10', -}); +// ── Use raw pool for canary/db checks (avoid DAO wrapper noise) ── +const db = pool.raw || pool; + +// Bootstrap: unwrap DEK, check DB, verify canary +try { + await initEncryption(); + await db.query('SELECT 1'); + await verifyCanary(db); +} catch (e) { + console.error('FATAL during crypto/DB bootstrap:', e?.message || e); + process.exit(1); +} function fprPathFromEnv() { const p = (process.env.DEK_PATH || '').trim(); @@ -82,8 +84,8 @@ app.get('/livez', (_req, res) => res.type('text').send('OK')); // 2) Readiness: crypto + canary are good app.get('/readyz', async (_req, res) => { try { - await initEncryption(); // load/unlock DEK - await verifyCanary(pool); // DB + decrypt sentinel + await initEncryption(); + await verifyCanary(db); return res.type('text').send('OK'); } catch (e) { console.error('[READYZ]', e.message); @@ -91,17 +93,17 @@ app.get('/readyz', async (_req, res) => { } }); -// 3) Health: detailed JSON (you can curl this to “see everything”) +// 3) Health: detailed JSON you can curl app.get('/healthz', async (_req, res) => { const out = { - service: process.env.npm_package_name || 'server', + service: process.env.npm_package_name || 'server3', version: process.env.IMG_TAG || null, uptime_s: Math.floor(process.uptime()), now: new Date().toISOString(), checks: { - live: { ok: true }, // if we reached here, process is up + live: { ok: true }, crypto: { ok: false, fp: null }, - db: { ok: false, ping_ms: null }, + db: { ok: false, ping_ms: null }, canary: { ok: false } } }; @@ -112,8 +114,7 @@ app.get('/healthz', async (_req, res) => { out.checks.crypto.ok = true; const p = fprPathFromEnv(); if (p) { - try { out.checks.crypto.fp = (await readFile(p, 'utf8')).trim(); } - catch { /* fp optional */ } + try { out.checks.crypto.fp = (await readFile(p, 'utf8')).trim(); } catch {} } } catch (e) { out.checks.crypto.error = e.message; @@ -122,7 +123,7 @@ app.get('/healthz', async (_req, res) => { // DB ping const t0 = Date.now(); try { - await pool.query('SELECT 1'); + await db.query('SELECT 1'); out.checks.db.ok = true; out.checks.db.ping_ms = Date.now() - t0; } catch (e) { @@ -131,7 +132,7 @@ app.get('/healthz', async (_req, res) => { // canary try { - await verifyCanary(pool); + await verifyCanary(db); out.checks.canary.ok = true; } catch (e) { out.checks.canary.error = e.message; @@ -141,6 +142,9 @@ app.get('/healthz', async (_req, res) => { return res.status(ready ? 200 : 503).json(out); }); +// …rest of your routes and app.listen(PORT) + + function internalFetch(req, urlPath, opts = {}) { return fetch(`${API_BASE}${urlPath}`, { ...opts,