fix(dev): proxy local API and restrict frontend upstream
This commit is contained in:
@@ -3,13 +3,10 @@ set -eu
|
|||||||
|
|
||||||
THT_FRONTEND_API_UPSTREAM=${THT_FRONTEND_API_UPSTREAM:-http://core:8787}
|
THT_FRONTEND_API_UPSTREAM=${THT_FRONTEND_API_UPSTREAM:-http://core:8787}
|
||||||
THT_FRONTEND_API_UPSTREAM=${THT_FRONTEND_API_UPSTREAM%/}
|
THT_FRONTEND_API_UPSTREAM=${THT_FRONTEND_API_UPSTREAM%/}
|
||||||
case "$THT_FRONTEND_API_UPSTREAM" in
|
if ! /usr/local/bin/validate-frontend-api-upstream "$THT_FRONTEND_API_UPSTREAM"; then
|
||||||
http://*|https://*) ;;
|
echo "Invalid THT_FRONTEND_API_UPSTREAM: expected internal http://core:8787" >&2
|
||||||
*)
|
exit 2
|
||||||
echo "Invalid THT_FRONTEND_API_UPSTREAM: use an internal http(s) upstream" >&2
|
fi
|
||||||
exit 2
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
export THT_FRONTEND_API_UPSTREAM
|
export THT_FRONTEND_API_UPSTREAM
|
||||||
|
|
||||||
if ! envsubst '${THT_FRONTEND_API_UPSTREAM}' \
|
if ! envsubst '${THT_FRONTEND_API_UPSTREAM}' \
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ FROM nginxinc/nginx-unprivileged:1.27-alpine AS runtime
|
|||||||
COPY --from=build /src/dist /usr/share/nginx/html
|
COPY --from=build /src/dist /usr/share/nginx/html
|
||||||
COPY docker/nginx.conf.template /etc/nginx/templates/default.conf.template
|
COPY docker/nginx.conf.template /etc/nginx/templates/default.conf.template
|
||||||
COPY --chmod=755 docker/frontend-entrypoint.sh /usr/local/bin/frontend-entrypoint
|
COPY --chmod=755 docker/frontend-entrypoint.sh /usr/local/bin/frontend-entrypoint
|
||||||
|
COPY --chmod=755 docker/validate-frontend-api-upstream.sh /usr/local/bin/validate-frontend-api-upstream
|
||||||
ENTRYPOINT ["/usr/local/bin/frontend-entrypoint"]
|
ENTRYPOINT ["/usr/local/bin/frontend-entrypoint"]
|
||||||
CMD ["nginx", "-g", "daemon off;"]
|
CMD ["nginx", "-g", "daemon off;"]
|
||||||
EXPOSE 8080
|
EXPOSE 8080
|
||||||
|
|||||||
@@ -31,4 +31,27 @@ if rg -n 'BACKEND_BASE_URL|VITE_BACKEND_URL' docker/frontend-entrypoint.sh docke
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
upstream_validator=docker/validate-frontend-api-upstream.sh
|
||||||
|
for upstream in http://core:8787 http://core:8787/; do
|
||||||
|
if ! "$upstream_validator" "$upstream"; then
|
||||||
|
echo "frontend upstream validator rejected $upstream" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
for upstream in \
|
||||||
|
https://core:8787 \
|
||||||
|
http://core:8080 \
|
||||||
|
http://core:8787/api \
|
||||||
|
http://user:pass@core:8787 \
|
||||||
|
'http://core:8787?next=evil' \
|
||||||
|
'http://core:8787#fragment' \
|
||||||
|
'http://core:8787 injected' \
|
||||||
|
'http://core:8787;proxy_pass http://evil'; do
|
||||||
|
if "$upstream_validator" "$upstream" >/dev/null 2>&1; then
|
||||||
|
echo "frontend upstream validator accepted unsafe upstream: $upstream" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
echo "frontend same-origin proxy policy: ok"
|
echo "frontend same-origin proxy policy: ok"
|
||||||
|
|||||||
Executable
+7
@@ -0,0 +1,7 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
case "${1-}" in
|
||||||
|
http://core:8787|http://core:8787/) exit 0 ;;
|
||||||
|
*) exit 2 ;;
|
||||||
|
esac
|
||||||
@@ -4,6 +4,7 @@ import path from "path";
|
|||||||
|
|
||||||
export default defineConfig(() => {
|
export default defineConfig(() => {
|
||||||
const embedBase = process.env.VITE_BASE; // "/datamart-builder/assets/" in embedded; undefined = standalone
|
const embedBase = process.env.VITE_BASE; // "/datamart-builder/assets/" in embedded; undefined = standalone
|
||||||
|
const apiUpstream = process.env.THT_FRONTEND_API_UPSTREAM ?? "http://localhost:8787";
|
||||||
return {
|
return {
|
||||||
plugins: [react()],
|
plugins: [react()],
|
||||||
// base: prefisso pubblico degli asset. Default "/" (standalone).
|
// base: prefisso pubblico degli asset. Default "/" (standalone).
|
||||||
@@ -11,6 +12,15 @@ export default defineConfig(() => {
|
|||||||
// /datamart-builder/assets/ → frontend-root mappa 1:1 (niente /assets/assets/).
|
// /datamart-builder/assets/ → frontend-root mappa 1:1 (niente /assets/assets/).
|
||||||
base: embedBase ?? "/",
|
base: embedBase ?? "/",
|
||||||
build: { manifest: true, outDir: "dist", assetsDir: embedBase ? "" : "assets" },
|
build: { manifest: true, outDir: "dist", assetsDir: embedBase ? "" : "assets" },
|
||||||
|
server: {
|
||||||
|
proxy: {
|
||||||
|
"/api": {
|
||||||
|
target: apiUpstream,
|
||||||
|
changeOrigin: true,
|
||||||
|
rewrite: (path) => path.replace(/^\/api(?=\/|$)/, ""),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
resolve: { alias: { "@": path.resolve(__dirname, "./src") } },
|
resolve: { alias: { "@": path.resolve(__dirname, "./src") } },
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -55,10 +55,10 @@ trap cleanup EXIT INT TERM
|
|||||||
) &
|
) &
|
||||||
pids+=($!)
|
pids+=($!)
|
||||||
|
|
||||||
# Frontend: punta al backend reale.
|
# Frontend: il browser usa sempre /api; Vite lo inoltra al backend locale.
|
||||||
(
|
(
|
||||||
cd "$FRONTEND"
|
cd "$FRONTEND"
|
||||||
VITE_BACKEND_URL="http://localhost:$BACKEND_PORT" \
|
THT_FRONTEND_API_UPSTREAM="http://localhost:$BACKEND_PORT" \
|
||||||
npm run dev -- --port "$FRONTEND_PORT"
|
npm run dev -- --port "$FRONTEND_PORT"
|
||||||
) &
|
) &
|
||||||
pids+=($!)
|
pids+=($!)
|
||||||
|
|||||||
Executable
+67
@@ -0,0 +1,67 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd -P)"
|
||||||
|
TMPDIR_TEST="$(mktemp -d)"
|
||||||
|
PIDS=()
|
||||||
|
|
||||||
|
cleanup() {
|
||||||
|
for pid in "${PIDS[@]}"; do kill "$pid" 2>/dev/null || true; done
|
||||||
|
rm -rf "$TMPDIR_TEST"
|
||||||
|
}
|
||||||
|
trap cleanup EXIT INT TERM
|
||||||
|
|
||||||
|
free_port() {
|
||||||
|
node -e 'const server = require("node:net").createServer(); server.listen(0, "127.0.0.1", () => { console.log(server.address().port); server.close(); });'
|
||||||
|
}
|
||||||
|
|
||||||
|
backend_port_file="$TMPDIR_TEST/backend-port"
|
||||||
|
node -e '
|
||||||
|
const Fastify = require(process.argv[1]);
|
||||||
|
const fs = require("node:fs");
|
||||||
|
const app = Fastify();
|
||||||
|
app.get("/health", async (request) => ({ backend: "fastify", path: request.raw.url }));
|
||||||
|
app.listen({ port: 0, host: "127.0.0.1" }).then((address) => {
|
||||||
|
fs.writeFileSync(process.argv[2], String(new URL(address).port));
|
||||||
|
});
|
||||||
|
' "$ROOT/backend/node_modules/fastify" "$backend_port_file" >"$TMPDIR_TEST/backend.log" 2>&1 &
|
||||||
|
PIDS+=("$!")
|
||||||
|
|
||||||
|
for _ in {1..50}; do
|
||||||
|
[ -s "$backend_port_file" ] && break
|
||||||
|
sleep 0.1
|
||||||
|
done
|
||||||
|
[ -s "$backend_port_file" ] || { cat "$TMPDIR_TEST/backend.log" >&2; exit 1; }
|
||||||
|
backend_port="$(<"$backend_port_file")"
|
||||||
|
frontend_port="$(free_port)"
|
||||||
|
|
||||||
|
THT_FRONTEND_API_UPSTREAM="http://127.0.0.1:$backend_port" \
|
||||||
|
npm --prefix "$ROOT/frontend" run dev -- --host 127.0.0.1 --port "$frontend_port" \
|
||||||
|
>"$TMPDIR_TEST/vite.log" 2>&1 &
|
||||||
|
PIDS+=("$!")
|
||||||
|
|
||||||
|
response=""
|
||||||
|
for _ in {1..50}; do
|
||||||
|
if response="$(curl -fsS "http://127.0.0.1:$frontend_port/api/health" 2>/dev/null)"; then
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
sleep 0.1
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ "$response" != '{"backend":"fastify","path":"/health"}' ]; then
|
||||||
|
cat "$TMPDIR_TEST/vite.log" >&2
|
||||||
|
printf 'expected Vite /api/health to reach Fastify /health, got: %s\n' "$response" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! rg -Fq 'THT_FRONTEND_API_UPSTREAM="http://localhost:$BACKEND_PORT"' "$ROOT/scripts/run-stack.sh"; then
|
||||||
|
echo "run-stack.sh must configure the Vite internal upstream from BACKEND_PORT" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if rg -q 'VITE_BACKEND_URL' "$ROOT/scripts/run-stack.sh"; then
|
||||||
|
echo "run-stack.sh must keep the browser base on /api" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "local browser /api routes to Fastify through the Vite proxy: ok"
|
||||||
Reference in New Issue
Block a user