diff --git a/.env.example b/.env.example index 37a4b02..8006cdd 100644 --- a/.env.example +++ b/.env.example @@ -48,13 +48,13 @@ LOCALSTACK_AWS_SESSION_TOKEN= # ----------------------------------------------------------------------------- -# Frontend SPA statico locale (S3 LocalStack + CloudFront locale) +# Frontend SPA statico locale (S3 LocalStack + CDN/edge locale) # ----------------------------------------------------------------------------- # Queste variabili riguardano SOLO il serving della SPA Angular. # Non usarle per i documenti e non sostituirle con bucket S3 reali. # I documenti restano governati da POC_DOCUMENT_DISK/AWS_REAL_* più sotto. FRONTEND_STATIC_BUCKET=poc-frontend-static-local -FRONTEND_CLOUDFRONT_LOCAL_URL=https://localhost:8443 +EDGE_CDN_LOCAL_URL=https://localhost:8443 # ----------------------------------------------------------------------------- diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7b5ec63..97f0541 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -256,7 +256,7 @@ jobs: - name: Apply LocalStack infrastructure run: docker compose --profile tools run --rm terraform init && docker compose --profile tools run --rm terraform apply -auto-approve - # Il percorso di serving default e' Traefik -> frontend-cloudfront -> S3 + # Il percorso di serving default e' Traefik -> edge-cdn -> S3 # LocalStack: lo smoke e gli audit a11y devono colpire la SPA realmente # caricata nel bucket, non un bucket vuoto. Si riusa la stessa logica di # make setup (build + sync con cache-control differenziato). @@ -310,7 +310,7 @@ jobs: echo "$spa_headers" | grep -i '^x-content-type-options: nosniff' echo "$spa_headers" | grep -i '^referrer-policy: strict-origin-when-cross-origin' - # La SPA Angular deve essere servita da S3 LocalStack via frontend-cloudfront, + # La SPA Angular deve essere servita da S3 LocalStack via edge-cdn, # sia sulla root sia su un deep link (fallback SPA verso index.html) curl --silent --fail --insecure https://localhost:8443/ | grep -q 'TLS + routing per hostname] - FC[frontend-cloudfront
CloudFront locale: SPA da S3 + proxy /api] + FC[edge-cdn
emulatore CDN locale: SPA da S3 + proxy /api] N[Nginx
proxy /api + fastcgi] end subgraph backend[rete backend] @@ -100,7 +100,7 @@ flowchart LR AL -->|docker logs| LK ``` -Confini di responsabilità: Traefik termina TLS e applica auth alle dashboard; il CloudFront locale (`frontend-cloudfront`) serve la SPA da S3 LocalStack e inoltra `/api/`, `/health` e `/ready` a Nginx, che resta il proxy applicativo verso PHP-FPM (oltre a poter servire la SPA in modalità standard dall'immagine); Laravel gestisce validazione, identità, persistenza e orchestrazione; Step Functions (LocalStack) detiene lo stato del workflow; il worker esegue i task e risponde con i task token; il collector è l'unico punto di raccolta telemetria. +Confini di responsabilità: Traefik termina TLS e applica auth alle dashboard; l'emulatore CDN locale (`edge-cdn`, un secondo Nginx) serve la SPA da S3 LocalStack e inoltra `/api/`, `/health` e `/ready` all'Nginx applicativo, che resta il proxy verso PHP-FPM (oltre a poter servire la SPA in modalità standard dall'immagine); Laravel gestisce validazione, identità, persistenza e orchestrazione; Step Functions (LocalStack) detiene lo stato del workflow; il worker esegue i task e risponde con i task token; il collector è l'unico punto di raccolta telemetria. --- @@ -152,16 +152,16 @@ Confini di responsabilità: Traefik termina TLS e applica auth alle dashboard; i ### Nginx 1.27 (static + fastcgi) **Dove**: `docker/nginx/Dockerfile` (multi-stage: build SPA con node:22 → runtime `nginx:1.27-alpine`, `USER nginx`), `docker/nginx/default.conf`. -**Ruolo**: nel flusso default è il proxy applicativo — inoltra `/api/` e gli endpoint di sistema a PHP-FPM e applica i security header (X-Frame-Options, X-Content-Type-Options, Referrer-Policy, Content-Security-Policy). L'immagine include anche la SPA Angular buildata e può servirla in modalità standard con fallback `try_files ... /index.html`, percorso alternativo al CloudFront locale (`frontend-cloudfront`), che invece è l'origine default della SPA da S3 LocalStack. +**Ruolo**: nel flusso default è il proxy applicativo — inoltra `/api/` e gli endpoint di sistema a PHP-FPM e applica i security header (X-Frame-Options, X-Content-Type-Options, Referrer-Policy, Content-Security-Policy). L'immagine include anche la SPA Angular buildata e può servirla in modalità standard con fallback `try_files ... /index.html`, percorso alternativo all'emulatore CDN locale (`edge-cdn`), che invece è l'origine default della SPA da S3 LocalStack. **Dettaglio rilevante**: `/internal/metrics` non è servito dal listener edge `:8080` usato da Traefik; ritorna 404 dall'esterno. Lo scrape passa dal listener interno `nginx:8081/internal/metrics`, raggiungibile solo sulle reti Docker interne. La CI verifica sia il 404 esterno sia la raggiungibilità interna. **Gap**: la CSP è locale e mirata alla SPA attuale; eventuali nuovi asset remoti o embedding esterni richiedono aggiornamento esplicito della policy. -### CloudFront locale (frontend-cloudfront) +### Emulatore CDN locale (edge-cdn) -**Dove**: `docker/cloudfront/default.conf.template` (config Nginx parametrizzata via `envsubst`), servizio `frontend-cloudfront` in `docker-compose.yml` (immagine `nginx:1.29-alpine`), target `make frontend-s3-local-deploy`/`frontend-s3-local-upload`. -**Ruolo**: emula in locale il layer CDN/edge che in produzione sarebbe Amazon CloudFront. È l'**origine di default della SPA**: serve gli asset statici Angular leggendoli da S3 LocalStack (`/` → bucket `index.html`; le altre richieste sono proxate al bucket con fallback deep-link `@spa_index` su `index.html`, intercettando 403/404), e fa da **reverse proxy** verso Nginx per `/api/`, `/health`, `/ready`. È il primo hop dopo Traefik (`Traefik → frontend-cloudfront → {S3 LocalStack | Nginx}`). +**Dove**: `docker/edge-cdn/default.conf.template` (config Nginx parametrizzata via `envsubst`), servizio `edge-cdn` in `docker-compose.yml` (immagine `nginx:1.29-alpine`), target `make frontend-s3-local-deploy`/`frontend-s3-local-upload`. +**Ruolo**: emula in locale il layer CDN/edge che in produzione sarebbe Amazon CloudFront. È l'**origine di default della SPA**: serve gli asset statici Angular leggendoli da S3 LocalStack (`/` → bucket `index.html`; le altre richieste sono proxate al bucket con fallback deep-link `@spa_index` su `index.html`, intercettando 403/404), e fa da **reverse proxy** verso Nginx per `/api/`, `/health`, `/ready`. È il primo hop dopo Traefik (`Traefik → edge-cdn → {S3 LocalStack | Nginx}`). **Security header**: applica a livello server gli header edge (CSP `frame-ancestors 'none'`, X-Frame-Options `SAMEORIGIN`, X-Content-Type-Options, Referrer-Policy) sugli asset statici. Sulle risposte `/api/` non sovrascrive gli header dell'origine: un `add_header` dedicato nella `location /api/` interrompe l'ereditarietà nginx, così resta valida la CSP differenziata dell'app (in particolare `frame-ancestors 'self'` per l'anteprima PDF in iframe, che altrimenti verrebbe bloccata dalla doppia CSP). -**Motivazione**: validare il percorso reale build → upload su object storage → distribuzione edge senza dipendere da CloudFront vero; tiene il serving statico della SPA disaccoppiato dall'immagine applicativa. +**Motivazione**: validare il percorso reale build → upload su object storage → distribuzione edge senza dipendere da CloudFront vero; tiene il serving statico della SPA (e ogni riferimento a LocalStack) fuori dall'immagine Nginx applicativa, che è un artefatto di produzione. In produzione il ruolo sarebbe ricoperto da AWS CloudFront. **Valutazione**: riproduce in modo fedele il flusso S3-origin + edge proxy e la separazione asset/API. Gap: non copre OAC, invalidation della cache, signed URL né la propagazione edge reali (limiti dichiarati, §11); convive con la versione 1.27 dell'Nginx applicativo (due immagini Nginx distinte con ruoli diversi). ### AWS Bedrock (LLM) @@ -222,7 +222,7 @@ Confini di responsabilità: Traefik termina TLS e applica auth alle dashboard; i ### CI — GitHub Actions **Dove**: `.github/workflows/ci.yml` (3 job), `mirror-images.yml`, `scripts/ci/`. -**Pipeline**: `backend` (Composer validate, Pint, Larastan, Pest), `frontend` (lint OpenAPI, generazione client + check committed, ESLint, typecheck, Jest, build Angular, `npm audit --audit-level=high`), `stack` (mirroring/caching immagini, `terraform fmt/validate`, validazione config osservabilità, build immagini dev+prod, **Trivy** `--exit-code 1 --severity HIGH,CRITICAL` su app e nginx, avvio stack completo con LocalStack+Terraform apply, build e upload della SPA Angular su S3 LocalStack, smoke HTTPS via Traefik — SPA servita dal CloudFront locale con fallback deep-link, check 401 sulle dashboard — audit axe e pa11y contro lo stack reale, push condizionale delle immagini su GHCR solo per i non-PR). Concurrency con cancel-in-progress. +**Pipeline**: `backend` (Composer validate, Pint, Larastan, Pest), `frontend` (lint OpenAPI, generazione client + check committed, ESLint, typecheck, Jest, build Angular, `npm audit --audit-level=high`), `stack` (mirroring/caching immagini, `terraform fmt/validate`, validazione config osservabilità, build immagini dev+prod, **Trivy** `--exit-code 1 --severity HIGH,CRITICAL` su app e nginx, avvio stack completo con LocalStack+Terraform apply, build e upload della SPA Angular su S3 LocalStack, smoke HTTPS via Traefik — SPA servita dall'emulatore CDN locale con fallback deep-link, check 401 sulle dashboard — audit axe e pa11y contro lo stack reale, push condizionale delle immagini su GHCR solo per i non-PR). Concurrency con cancel-in-progress. **Valutazione**: copertura larga e realistica (lo smoke testa il sistema integrato, non i mock); il quality gate include sicurezza (Trivy, npm audit) e accessibilità. Gap: niente coverage report; i job non pubblicano artefatti di debug in caso di fallimento smoke. --- @@ -368,7 +368,7 @@ Coperte in §5 (Bedrock, Textract) e §6. Punti trasversali: Coperto in §5; valutazione sintetica: - **Solido**: data layer uniforme (servizi Angular + client generato), feedback espliciti (loading con `aria-live`, error, empty), SSE per progress reale dell'elaborazione (`DocumentWorkflowService` con EventSource), design token centralizzati con dark mode, test Jest mirati e audit a11y automatizzati in CI. -- **Limiti**: il CloudFront locale valida il flusso build → S3 locale → distribuzione CDN-like, ma non copre OAC/invalidation/propagazione edge reali; la copertura test frontend resta minima e va ampliata sui componenti visuali e sui flussi SSE end-to-end. +- **Limiti**: l'emulatore CDN locale (Nginx) valida il flusso build → S3 locale → distribuzione edge, ma non copre OAC/invalidation/propagazione edge reali (in produzione: AWS CloudFront); la copertura test frontend resta minima e va ampliata sui componenti visuali e sui flussi SSE end-to-end. --- diff --git a/docs/architecture-decisions/0008-angular-frontend-static-serving.md b/docs/architecture-decisions/0008-angular-frontend-static-serving.md index 6cd7508..f520abe 100644 --- a/docs/architecture-decisions/0008-angular-frontend-static-serving.md +++ b/docs/architecture-decisions/0008-angular-frontend-static-serving.md @@ -23,10 +23,20 @@ vive in uno store Angular a signal; SSE, upload multipart, preview PDF, dark mod loading states restano espliciti. Terraform LocalStack provisiona anche un bucket S3 dedicato agli asset Angular. Il percorso -default locale passa da Traefik al servizio Docker `frontend-cloudfront`, che emula il ruolo di -CloudFront davanti al bucket S3 LocalStack e inoltra `/api/`, `/health` e `/ready` a -Nginx/Laravel. Il deploy locale carica `apps/frontend/dist` nel bucket con cache-control -differenziato: `index.html` no-cache, bundle hashati immutable, altri asset con cache breve. +default locale passa da Traefik al servizio Docker `edge-cdn`: un **secondo Nginx** +che **emula in locale il ruolo di una CDN/edge** davanti al bucket S3 LocalStack — serve gli +asset statici e inoltra `/api/`, `/health` e `/ready` all'Nginx applicativo/Laravel. Non emula +Amazon CloudFront né la sua semantica; ne riproduce solo il *ruolo* di distribuzione edge. In +produzione quel ruolo sarebbe ricoperto da AWS CloudFront (vedi *Evoluzione*). Il deploy locale +carica `apps/frontend/dist` nel bucket con cache-control differenziato: `index.html` no-cache, +bundle hashati immutable, altri asset con cache breve. + +Il serving CDN locale è un container Nginx **separato** dall'Nginx applicativo, per due motivi: +(1) **sostanziale** — l'Nginx applicativo è un'immagine di produzione (buildata, scansionata da +Trivy, pubblicata su GHCR) e non deve contenere riferimenti a LocalStack; isolare il serving da +S3 emulato in un container scaffolding solo-locale mantiene pulito l'artefatto di produzione; +(2) **di forma** — riflette la topologia reale CDN → origin, dove l'edge è distinto dall'origin +applicativo. ![Frontend SPA e contratto API](../architecture/diagrams/03_frontend_spa_contratto_api.drawio.png) @@ -36,18 +46,29 @@ differenziato: `index.html` no-cache, bundle hashati immutable, altri asset con - Il frontend applicativo resta su Angular, Angular Router, HttpClient, RxJS e signal store. - La build statica Angular non dipende dal dev server. -- Traefik e `frontend-cloudfront` sono il percorso integrato default per demo end-to-end. -- S3 LocalStack + CloudFront locale valida il pattern build → bucket → CDN-like distribution, - non la semantica completa di CloudFront reale. +- Traefik e `edge-cdn` (emulatore CDN locale, Nginx) sono il percorso integrato + default per demo end-to-end. +- S3 LocalStack + emulatore CDN locale (Nginx) valida il pattern build → bucket → distribuzione + edge, **non** la semantica di Amazon CloudFront (OAC, invalidation, signed URL, edge propagation). - I documenti possono usare `POC_DOCUMENT_DISK=real_s3` e `AWS_REAL_*` per Textract reale senza toccare `FRONTEND_STATIC_BUCKET`, che resta dedicato alla SPA locale. +## Evoluzione + +Il target di produzione è sostituire l'emulatore CDN locale con **Amazon CloudFront** davanti a +un bucket S3 **privato** con Origin Access Control (OAC), invalidation di `index.html` al deploy +e response headers policy gestite. L'Nginx `edge-cdn` resta solo lo stand-in locale +per le demo offline, dato che l'immagine LocalStack Community non espone l'API CloudFront. + ## Alternatives considered -- **Mantenere solo Nginx**: semplice, ma non valida il pattern static-hosting richiesto. -- **Usare l'API CloudFront LocalStack Terraform**: scartato perché l'immagine LocalStack locale - risponde `501` per CloudFront; il default resta comunque S3 locale + CloudFront locale tramite - servizio Docker. +- **Servire la SPA da S3 con il solo Nginx applicativo** (un container): scartato perché + imporrebbe un `proxy_pass` verso LocalStack dentro l'immagine di produzione, accoppiandola + all'ambiente locale; il container CDN separato evita questo. +- **Servire la SPA dal filesystem dell'Nginx applicativo**: possibile (l'immagine include già la + build), ma non eserciterebbe il percorso via object storage S3. +- **Usare l'API CloudFront di LocalStack via Terraform**: scartato perché l'immagine LocalStack + Community risponde `501` per CloudFront; il ruolo edge resta emulato dal servizio Docker Nginx. - **Client API scritto a mano**: scartato per evitare deriva dal contratto OpenAPI. ## Implementation evidence @@ -56,8 +77,8 @@ differenziato: `index.html` no-cache, bundle hashati immutable, altri asset con - `apps/frontend/orval.config.ts` con `client: "angular"`. - `docker/nginx/Dockerfile` builda Angular con `node:22-bookworm-slim`. - `infra/localstack/main.tf` risorsa `aws_s3_bucket.frontend_static`. -- `docker/cloudfront/default.conf.template` e servizio Compose `frontend-cloudfront`. -- `Makefile` target `frontend-s3-local-*`, `frontend-cloudfront-local-url` e +- `docker/edge-cdn/default.conf.template` e servizio Compose `edge-cdn`. +- `Makefile` target `frontend-s3-local-*`, `edge-cdn-local-url` e `frontend-serving-local-test`. ## Related documents diff --git a/docs/architecture-decisions/README.md b/docs/architecture-decisions/README.md index 06f4aff..df1ff6e 100644 --- a/docs/architecture-decisions/README.md +++ b/docs/architecture-decisions/README.md @@ -38,7 +38,7 @@ ADR che referenzia il precedente. La numerazione è progressiva e a quattro cifr | [0005](0005-no-automatic-fallbacks.md) | Nessun fallback automatico dei servizi AI: stato `failed` esplicito | Accepted, implemented | | [0006](0006-observability-and-audit.md) | Osservabilità (OTel/Prometheus) e audit trail append-only | Accepted, implemented baseline | | [0007](0007-authn-authz-boundary.md) | Confine authn/authz: IdP simulato, RBAC/ABAC server-side | Accepted, implemented baseline | -| [0008](0008-angular-frontend-static-serving.md) | Frontend Angular e serving statico S3 locale/CloudFront locale | Accepted, implemented | +| [0008](0008-angular-frontend-static-serving.md) | Frontend Angular e serving statico S3 locale + emulatore CDN locale (Nginx) | Accepted, implemented | ## Aggiungere un ADR diff --git a/docs/architecture/aws-well-architected-mapping.md b/docs/architecture/aws-well-architected-mapping.md index cb10bf7..716b374 100644 --- a/docs/architecture/aws-well-architected-mapping.md +++ b/docs/architecture/aws-well-architected-mapping.md @@ -13,7 +13,7 @@ per il perimetro funzionale [`../poc-scope.md`](../poc-scope.md). | Operational excellence | Ciclo di vita Docker-first, target Make per setup/verifica, provisioning AWS-like via Terraform, job di migrazione al release, endpoint `/health` e `/ready`, OTel Collector, regole Prometheus con runbook, CI a tre job. | `Makefile`, `docker-compose.yml`, `infra/localstack/*.tf`, `routes/web.php` (`/health`, `/ready`), `docker/prometheus/rules/`, `docs/runbooks/`, `.github/workflows/ci.yml` | Nessun SLO/error budget formalizzato, nessun processo di on-call o di incident management, nessuna policy di approvazione del rilascio. | Definire SLO posseduti, dashboard di capacity, processo di incident e approvazioni di rilascio. | | Security | Nessuna UI di amministrazione runtime, configurazione separata tra SSM Parameter Store e Secrets Manager, middleware di identità strutturato, controlli RBAC/ABAC con tenant scoping, audit append-only, route legacy bloccate, header di sicurezza e CSP in nginx, nessuna credenziale IAM statica nella CI ordinaria, gate Trivy HIGH/CRITICAL. | `app/Copilot/Support/RuntimeConfigurationLoader.php`, `app/Http/Middleware/`, `app/Copilot/Audit/`, `docker/nginx/default.conf`, `.github/workflows/ci.yml` (Trivy) | Identità simulata via header trusted/config locale (nessun IdP), segreti di default committati come fallback compose, niente AV/CDR sull'upload. | Sostituire l'identità locale con un confine IdP aziendale, collegare un ruolo IAM con privilegio minimo, aggiungere scansione AV/CDR e SBOM gate. | | Reliability | Orchestrazione Step Functions **end-to-end** (LocalStack) con retry/catch dichiarativi, timeout e heartbeat per task, coda SQS con DLQ (redrive `maxReceiveCount` 3), stati `failed` espliciti, **nessun fallback automatico** dei servizi AI (ADR 0005), tabella di workflow idempotente, readiness check reale. | `infra/localstack/state-machines/document-pipeline.asl.json`, `infra/localstack/main.tf` (DLQ, redrive), `app/Console/Commands/ConsumeWorkflowTasks.php`, `app/Copilot/Workflow/Services/`, migrazione `document_workflow_tasks` (`task_token_hash` unique) | Una sola replica del worker in compose (design già concorrenza-safe), nessun backup/PITR dei dati, procedura di redrive dalla DLQ non automatizzata. | ≥2 repliche del worker, RDS/PITR o backup schedulati con restore testato, runbook di redrive DLQ e retry budget espliciti. | -| Performance efficiency | SPA servita staticamente da S3 LocalStack via CloudFront locale (cache-control differenziato: `index.html` no-cache, bundle hashati immutable; Nginx come fallback statico e proxy `/api`), PHP-FPM con OPcache, pipeline documentale asincrona fuori dal ciclo HTTP, istogramma di latenza HTTP, dimensione massima del body limitata. | `docker/cloudfront/`, `docker/nginx/`, `docker/php/`, `app/Copilot/Observability/MetricsRecorder.php`, `routes/api.php` (throttle) | Upload gestito in-process anziché con finalizzazione presigned S3; nessun target di capacity derivato dal traffico osservato. | Spostare gli upload pesanti su finalizzazione presigned S3 e fissare target di capacity dai dati osservati. | +| Performance efficiency | SPA servita staticamente da S3 LocalStack via emulatore CDN locale (Nginx; in produzione AWS CloudFront) con cache-control differenziato (`index.html` no-cache, bundle hashati immutable; Nginx applicativo come fallback statico e proxy `/api`), PHP-FPM con OPcache, pipeline documentale asincrona fuori dal ciclo HTTP, istogramma di latenza HTTP, dimensione massima del body limitata. | `docker/edge-cdn/`, `docker/nginx/`, `docker/php/`, `app/Copilot/Observability/MetricsRecorder.php`, `routes/api.php` (throttle) | Upload gestito in-process anziché con finalizzazione presigned S3; nessun target di capacity derivato dal traffico osservato. | Spostare gli upload pesanti su finalizzazione presigned S3 e fissare target di capacity dai dati osservati. | | Cost optimization | Lo smoke su AWS reale è opzionale/manuale, le risorse AWS-like girano in LocalStack, Prometheus è locale. | `.github/workflows/aws-smoke.yml`, `docker-compose.yml` (LocalStack), `infra/localstack/` | Nessun tag di allocazione costi, nessun budget, nessuna lifecycle policy sugli oggetti S3. | Aggiungere tag di cost allocation, budget, lifecycle policy sugli oggetti e schedulazioni limitate dello smoke reale. | | Sustainability | Tooling dockerizzato che evita drift dell'host, build frontend multi-stage, teardown locale via Compose/Terraform, esecuzione su cloud reale minimizzata. | `docker/nginx/Dockerfile` (multi-stage), `docker-compose.yml`, `infra/localstack/` | Nessun budget sulla dimensione delle immagini, nessuna retention esplicita per log, metriche e documenti. | Definire budget di dimensione immagini e policy di retention per log, metriche e documenti. | diff --git a/docs/architecture/capitolato-traceability.md b/docs/architecture/capitolato-traceability.md index 2d3d672..d01334b 100644 --- a/docs/architecture/capitolato-traceability.md +++ b/docs/architecture/capitolato-traceability.md @@ -343,10 +343,10 @@ ne fornisce l'emulazione locale ripetibile (LocalStack), nello spirito di: ## 16. Hardening di rete e perimetro (Traefik/Nginx, IAM a minimo privilegio) -**Scelta nella PoC:** TLS edge via Traefik, CloudFront locale (`frontend-cloudfront`) che serve -la SPA statica da S3 LocalStack e Nginx come proxy applicativo con superfici non +**Scelta nella PoC:** TLS edge via Traefik, emulatore CDN locale (`edge-cdn`, Nginx) che serve +la SPA statica da S3 LocalStack e Nginx applicativo come proxy con superfici non API bloccate, IAM role granulari sulle risorse LocalStack -([`docker-compose.yml`](../../docker-compose.yml): `traefik`, `frontend-cloudfront`, `nginx`; +([`docker-compose.yml`](../../docker-compose.yml): `traefik`, `edge-cdn`, `nginx`; [`infra/localstack/main.tf`](../../infra/localstack/main.tf): `aws_iam_role`). Cifrare il traffico all'edge, ridurre la superficie esposta e applicare il minimo privilegio sono misure di igiene di base che riducono il rischio a prescindere dall'ambiente. @@ -404,7 +404,7 @@ nella libertà tecnologica della sezione «Vincoli». - **Stack di osservabilità open source (OpenTelemetry Collector, Prometheus, Tempo, Grafana, Loki, Alertmanager):** adottato come equivalente locale, vendor-neutral, di CloudWatch/X-Ray, che il Capitolato indica come telemetria target. -- **Edge/runtime locale (Traefik, CloudFront locale, Nginx):** equivalenti locali di CloudFront/ALB, che il +- **Edge/runtime locale (Traefik, emulatore CDN locale Nginx, Nginx applicativo):** equivalenti locali di CloudFront/ALB, che il Capitolato nomina nella loro forma AWS gestita. - **Librerie di manipolazione PDF (`setasign/fpdf`, `setasign/fpdi`) per lo split documentale:** dettaglio implementativo non coperto dal Capitolato. diff --git a/docs/architecture/diagrams/04_edge_runtime_backend_api.drawio b/docs/architecture/diagrams/04_edge_runtime_backend_api.drawio index 8b80a38..ba8fefc 100644 --- a/docs/architecture/diagrams/04_edge_runtime_backend_api.drawio +++ b/docs/architecture/diagrams/04_edge_runtime_backend_api.drawio @@ -112,7 +112,7 @@ - + diff --git a/docs/architecture/diagrams/04_edge_runtime_backend_api.drawio.png b/docs/architecture/diagrams/04_edge_runtime_backend_api.drawio.png index b4d2c8f..a8f1bc1 100644 Binary files a/docs/architecture/diagrams/04_edge_runtime_backend_api.drawio.png and b/docs/architecture/diagrams/04_edge_runtime_backend_api.drawio.png differ diff --git a/docs/architecture/diagrams/04_edge_runtime_backend_api.drawio.svg b/docs/architecture/diagrams/04_edge_runtime_backend_api.drawio.svg index 95354fa..0665c93 100644 --- a/docs/architecture/diagrams/04_edge_runtime_backend_api.drawio.svg +++ b/docs/architecture/diagrams/04_edge_runtime_backend_api.drawio.svg @@ -1,3 +1,3 @@ -Laravel API
Browser
client dell'operatore HR
Traefik
reverse proxy, terminazione TLS
Nginx
proxy /api e FastCGI
CSP
Security headers
CSP · X-Frame-Options
Angular SPA
UI operativa
PHP-FPM
runtime PHP 8.4 (FastCGI)
M
/internal/metrics
listener interno
OBS
Osservabilità
dettaglio slide 7
Routes /api/v1
Controllers
Middleware
identity · authorize · throttle
Service layer
CMP
Composer dependencies
autoloader ottimizzato
Pint
stile del codice PHP
PHPStan / Larastan
analisi statica · estensione Laravel
HTTPS
routing
origin
FastCGI
exec
expose :8081
internal metrics
gate qualità
gate qualità
CloudFront locale
SPA da S3 + proxy /api
S3 SPA (LocalStack)
bucket asset statici
/api
Text is not SVG - cannot display
\ No newline at end of file +Laravel API
Browser
client dell'operatore HR
Traefik
reverse proxy, terminazione TLS
Nginx
proxy /api e FastCGI
CSP
Security headers
CSP · X-Frame-Options
Angular SPA
UI operativa
PHP-FPM
runtime PHP 8.4 (FastCGI)
M
/internal/metrics
listener interno
OBS
Osservabilità
dettaglio slide 7
Routes /api/v1
Controllers
Middleware
identity · authorize · throttle
Service layer
CMP
Composer dependencies
autoloader ottimizzato
Pint
stile del codice PHP
PHPStan / Larastan
analisi statica · estensione Laravel
HTTPS
routing
origin
FastCGI
exec
expose :8081
internal metrics
gate qualità
gate qualità
Emulatore CDN (nginx)
SPA da S3 + proxy /api
S3 SPA (LocalStack)
bucket asset statici
/api
Text is not SVG - cannot display
\ No newline at end of file diff --git a/docs/architecture/diagrams/final-architecture.drawio b/docs/architecture/diagrams/final-architecture.drawio index ce37838..c4791cf 100644 --- a/docs/architecture/diagrams/final-architecture.drawio +++ b/docs/architecture/diagrams/final-architecture.drawio @@ -61,7 +61,7 @@ - + diff --git a/docs/architecture/diagrams/final-architecture.drawio.png b/docs/architecture/diagrams/final-architecture.drawio.png index dcce3cc..599c03f 100644 Binary files a/docs/architecture/diagrams/final-architecture.drawio.png and b/docs/architecture/diagrams/final-architecture.drawio.png differ diff --git a/docs/architecture/diagrams/final-architecture.drawio.svg b/docs/architecture/diagrams/final-architecture.drawio.svg index 13f472c..c5e6818 100644 --- a/docs/architecture/diagrams/final-architecture.drawio.svg +++ b/docs/architecture/diagrams/final-architecture.drawio.svg @@ -1,3 +1,3 @@ -
FRONTEND - SPA (build/test)
CLIENT / EDGE
APPLICAZIONE
CONFIG - LocalStack
DATI
INFRA locale
ORCHESTRAZIONE - LocalStack
AWS reale
OSSERVABILITA'
CI / QUALITÀ
TypeScript
tipizzazione statica della SPA
Angular CLI
build frontend Angular
Angular SPA
UI operativa
Jest
test unitari frontend
Orval
genera client API tipizzato
OpenAPI
contratto API (specifica)
Browser
client dell'operatore HR
Traefik
reverse proxy, terminazione TLS
Nginx
proxy /api e FastCGI
CloudFront (LocalStack)
SPA da S3 + proxy /api
S3 SPA (LocalStack)
bucket asset statici
PHP-FPM
runtime PHP 8.4 (FastCGI)
Laravel API
API JSON: validazione, tenant, audit
Secrets Manager
segreti applicativi (emulato)
SSM Param Store
configurazione runtime
PostgreSQL
documenti, estratti, audit, stato task
Redis
cache, sessioni, rate limiting
Docker Compose
orchestra i container locali
LocalStack
emula i servizi AWS in locale
Terraform
provisioning infrastruttura (IaC)
Step Functions
orchestrazione con task token
SQS
coda dei task asincroni
Workflow Worker
consuma SQS ed esegue i task
DLQ
coda dei messaggi falliti
Bedrock
generazione, split, estrazione
Textract
OCR dei documenti
S3
storage documenti/sottodocumenti
KMS
cifratura dei dati at-rest
Alloy
raccolta log dei container
OTel Collector
ingest OTLP -> metriche e trace
Prometheus
metriche e regole di alert
Tempo
trace distribuite
Loki
aggregazione dei log
Grafana
dashboard e correlazione
Alertmanager
instradamento degli alert
GitHub Actions
orchestratore della pipeline CI
Trivy
scan vulnerabilita immagini
npm audit
audit dipendenze frontend
Redocly
lint del contratto OpenAPI
ESLint
lint del codice SPA
axe
test accessibilita (a11y)
pa11y
test accessibilita (a11y)
Pint
stile del codice PHP
Pest
test del backend
terraform validate
validazione dell'IaC
HTTPS
SPA + /api
/api /health /ready
SPA da S3
FastCGI
read/write
cache/sessioni
leggi config
leggi segreti
startExecution
task
consuma
callback (task token)
salva 
risultati
AI / 
LLM
OCR
documenti/
sottodocumenti
dopo i retry
compila 
(tsc)
bundle
genera
client tipizzato
testa
upload bundle
contratto
OTLP
OTLP
metriche
trace
log
query
query
query
alert
cifratura
LEGENDA - FLUSSI
sincrono (HTTP / API)
workflow asincrono
errore / DLQ
telemetria (OTLP / scrape)
provisioning / infra / CI
build frontend
encryption
execution role
IAM
execution role SFN · least-priv
push/pull
 base image
GHCR
registry immagini · push / pull
PHPStan (Larastan)
analisi statica · estensione Laravel
Docker Hub
mirror base images
Text is not SVG - cannot display
\ No newline at end of file +
FRONTEND - SPA (build/test)
CLIENT / EDGE
APPLICAZIONE
CONFIG - LocalStack
DATI
INFRA locale
ORCHESTRAZIONE - LocalStack
AWS reale
OSSERVABILITA'
CI / QUALITÀ
TypeScript
tipizzazione statica della SPA
Angular CLI
build frontend Angular
Angular SPA
UI operativa
Jest
test unitari frontend
Orval
genera client API tipizzato
OpenAPI
contratto API (specifica)
Browser
client dell'operatore HR
Traefik
reverse proxy, terminazione TLS
Nginx
proxy /api e FastCGI
Emulatore CDN (nginx)
SPA da S3 + proxy /api
S3 SPA (LocalStack)
bucket asset statici
PHP-FPM
runtime PHP 8.4 (FastCGI)
Laravel API
API JSON: validazione, tenant, audit
Secrets Manager
segreti applicativi (emulato)
SSM Param Store
configurazione runtime
PostgreSQL
documenti, estratti, audit, stato task
Redis
cache, sessioni, rate limiting
Docker Compose
orchestra i container locali
LocalStack
emula i servizi AWS in locale
Terraform
provisioning infrastruttura (IaC)
Step Functions
orchestrazione con task token
SQS
coda dei task asincroni
Workflow Worker
consuma SQS ed esegue i task
DLQ
coda dei messaggi falliti
Bedrock
generazione, split, estrazione
Textract
OCR dei documenti
S3
storage documenti/sottodocumenti
KMS
cifratura dei dati at-rest
Alloy
raccolta log dei container
OTel Collector
ingest OTLP -> metriche e trace
Prometheus
metriche e regole di alert
Tempo
trace distribuite
Loki
aggregazione dei log
Grafana
dashboard e correlazione
Alertmanager
instradamento degli alert
GitHub Actions
orchestratore della pipeline CI
Trivy
scan vulnerabilita immagini
npm audit
audit dipendenze frontend
Redocly
lint del contratto OpenAPI
ESLint
lint del codice SPA
axe
test accessibilita (a11y)
pa11y
test accessibilita (a11y)
Pint
stile del codice PHP
Pest
test del backend
terraform validate
validazione dell'IaC
HTTPS
SPA + /api
/api /health /ready
SPA da S3
FastCGI
read/write
cache/sessioni
leggi config
leggi segreti
startExecution
task
consuma
callback (task token)
salva 
risultati
AI / 
LLM
OCR
documenti/
sottodocumenti
dopo i retry
compila 
(tsc)
bundle
genera
client tipizzato
testa
upload bundle
contratto
OTLP
OTLP
metriche
trace
log
query
query
query
alert
cifratura
LEGENDA - FLUSSI
sincrono (HTTP / API)
workflow asincrono
errore / DLQ
telemetria (OTLP / scrape)
provisioning / infra / CI
build frontend
encryption
execution role
IAM
execution role SFN · least-priv
push/pull
 base image
GHCR
registry immagini · push / pull
PHPStan (Larastan)
analisi statica · estensione Laravel
Docker Hub
mirror base images
Text is not SVG - cannot display
\ No newline at end of file diff --git a/docs/architecture/final-architecture.md b/docs/architecture/final-architecture.md index 6f10601..bba4a51 100644 --- a/docs/architecture/final-architecture.md +++ b/docs/architecture/final-architecture.md @@ -25,7 +25,7 @@ export PNG/SVG vanno rigenerati da draw.io dopo ogni modifica (`drawio -x -f png | Livello | Componente implementato | Ruolo | | --- | --- | --- | | Frontend | SPA Angular/TypeScript in `apps/frontend` | Upload, stato di elaborazione, revisione, flussi di anteprima/eliminazione. | -| Edge | Traefik (TLS), CloudFront locale e Nginx | HTTPS locale, serving della SPA da S3 LocalStack, proxy API, blocco delle superfici `/admin`. | +| Edge | Traefik (TLS), emulatore CDN locale (Nginx) e Nginx applicativo | HTTPS locale, serving della SPA da S3 LocalStack, proxy API, blocco delle superfici `/admin`. | | API | API JSON Laravel in `app/Http` | Validazione, controlli di tenant, audit event, avvio del workflow. | | Workflow | Step Functions e SQS (LocalStack) | Orchestrazione production-like con callback task token, end-to-end. | | Worker | `php artisan poc:workflow:consume` | Ricezione SQS, esecuzione dei task, `SendTaskSuccess`/`SendTaskFailure`, `SendTaskHeartbeat`. | @@ -44,12 +44,19 @@ Step Functions, SQS/DLQ, S3, EventBridge, SSM Parameter Store e Secrets Manager. parla con i servizi AWS, reali o emulati, **senza cambiare codice**: cambiano solo endpoint e credenziali. -La SPA Angular usa come default locale il percorso `Traefik -> frontend-cloudfront -> S3 +La SPA Angular usa come default locale il percorso `Traefik -> edge-cdn -> S3 LocalStack`: `make frontend-s3-local-deploy` carica `apps/frontend/dist` nel bucket `FRONTEND_STATIC_BUCKET`, poi `https://localhost:8443` serve gli asset da quel bucket. Il -CloudFront locale inoltra `/api/`, `/health` e `/ready` a Nginx/Laravel. Nginx resta il proxy -applicativo e il percorso interno di compatibilità, ma non è più l'origine primaria della SPA -nel flusso default. La simulazione non sostituisce una validazione CloudFront reale su AWS. +servizio `edge-cdn` è un **secondo Nginx** che emula in locale il **ruolo di una +CDN/edge** (non Amazon CloudFront): serve gli asset statici e inoltra `/api/`, `/health` e +`/ready` all'Nginx applicativo/Laravel. È un container separato dall'Nginx applicativo di +proposito — quest'ultimo è un'immagine di produzione (buildata, scansionata, pubblicata) e non +deve conoscere LocalStack, mentre il serving da S3 emulato resta confinato in uno scaffolding +solo-locale; la separazione riflette anche la topologia reale CDN → origin. L'Nginx applicativo +resta il proxy verso PHP-FPM e il percorso interno di compatibilità (include la build SPA), ma +non è l'origine primaria della SPA nel flusso default. L'emulazione non sostituisce una CDN +reale: in produzione il ruolo sarebbe ricoperto da AWS CloudFront (bucket privato + OAC, +invalidation, edge propagation). ### Dettaglio edge/runtime/API @@ -77,7 +84,7 @@ credenziali e configurazione esplicite: - `BEDROCK_MODEL_ID` - `TEXTRACT_ENABLED=true` -Le variabili `FRONTEND_STATIC_BUCKET` e `FRONTEND_CLOUDFRONT_LOCAL_URL` sono locali e dedicate +Le variabili `FRONTEND_STATIC_BUCKET` e `EDGE_CDN_LOCAL_URL` sono locali e dedicate alla SPA: non devono puntare a bucket reali e non governano il caricamento documenti. I test e la CI standard non chiamano S3, Textract o Bedrock reali. diff --git a/docs/runbooks/ci-cd.md b/docs/runbooks/ci-cd.md index 0b2aeac..bba7df7 100644 --- a/docs/runbooks/ci-cd.md +++ b/docs/runbooks/ci-cd.md @@ -7,7 +7,7 @@ Ordinary CI runs without enterprise IAM credentials. It is a single pipeline, - **backend** — builds the app image and runs the PHP checks: Composer manifest validation, Pint (format), Larastan/PHPStan (static analysis) and Pest (tests). - **frontend** — runs the Angular SPA suite on the Node tool image: OpenAPI contract lint, generated client drift check, ESLint, typecheck, Jest tests, production build, and a production-only `npm audit` at HIGH. -- **stack** — static infrastructure/observability checks (Terraform `fmt`/`init`/`validate`, OTel Collector and Prometheus config), production image build, Trivy scan (`vuln,secret,config` at HIGH/CRITICAL), LocalStack Terraform apply, Angular SPA build and upload to the LocalStack S3 bucket, HTTPS smoke of the served stack (SPA served via the CloudFront emulator with deep-link fallback, `/api`/`/health`/`/ready`, blocked surfaces, observability dashboards behind basic auth), accessibility (axe/Pa11y plus an enforced-CSP smoke), and conditional publish of the two custom images (`poc-app`, `poc-nginx`) to GHCR. +- **stack** — static infrastructure/observability checks (Terraform `fmt`/`init`/`validate`, OTel Collector and Prometheus config), production image build, Trivy scan (`vuln,secret,config` at HIGH/CRITICAL), LocalStack Terraform apply, Angular SPA build and upload to the LocalStack S3 bucket, HTTPS smoke of the served stack (SPA served via the local CDN emulator — a separate Nginx — with deep-link fallback, `/api`/`/health`/`/ready`, blocked surfaces, observability dashboards behind basic auth), accessibility (axe/Pa11y plus an enforced-CSP smoke), and conditional publish of the two custom images (`poc-app`, `poc-nginx`) to GHCR. Supporting workflows: diff --git a/docs/security/iam-permissions-matrix.md b/docs/security/iam-permissions-matrix.md index 22ea056..808a315 100644 --- a/docs/security/iam-permissions-matrix.md +++ b/docs/security/iam-permissions-matrix.md @@ -9,7 +9,7 @@ realmente; servono come riferimento per il passaggio ad AWS reale. | API Laravel | `s3:PutObject` | Bucket/prefix documenti reale | Salvare il documento caricato per OCR/AI | Smoke AWS reale / futura prod | Preferire prefix scoped per tenant/ambiente. | | API Laravel | `s3:GetObject` | Bucket/prefix documenti reale | Leggere il file originale come input Bedrock | Smoke AWS reale / futura prod | Necessario quando l'elaborazione legge dal disco S3. | | Deploy SPA (CI / `make frontend-s3-local-upload`) | `s3:PutObject` / `s3:DeleteObject` / `s3:ListBucket` | Bucket frontend statico (`FRONTEND_STATIC_BUCKET`) | Caricare/sincronizzare la build Angular (`apps/frontend/dist`) | LocalStack / futura prod | Bucket dedicato alla SPA, separato dai documenti. In prod aggiungere `cloudfront:CreateInvalidation` per invalidare `index.html` dopo il deploy. | -| Serving SPA (CloudFront → S3) | `s3:GetObject` | Oggetti del bucket frontend statico | Servire gli asset della SPA Angular | LocalStack / futura prod | In LocalStack il bucket è **public-read** (`Principal: "*"`) solo per l'emulazione del CloudFront locale. In prod: bucket **privato** con CloudFront **OAC** (principal `cloudfront.amazonaws.com`, condizione `AWS:SourceArn` sulla distribuzione), **mai** `Principal: "*"`. | +| Serving SPA (CDN/edge → S3) | `s3:GetObject` | Oggetti del bucket frontend statico | Servire gli asset della SPA Angular | LocalStack / futura prod | In LocalStack il bucket è **public-read** (`Principal: "*"`) solo per l'emulatore CDN locale (Nginx). In prod: bucket **privato** con CloudFront **OAC** (principal `cloudfront.amazonaws.com`, condizione `AWS:SourceArn` sulla distribuzione), **mai** `Principal: "*"`. | | API Laravel | `states:StartExecution` | State machine della pipeline documentale | Avviare il workflow documentale | LocalStack / futura prod | In LocalStack usa ruolo/risorse create da Terraform. | | Worker queue | `sqs:ReceiveMessage` | Coda dei task documentali | Consumare i task con callback token | LocalStack / futura prod | Solo coda sorgente. | | Worker queue | `sqs:DeleteMessage` | Coda dei task documentali | Confermare il task completato/segnalato | LocalStack / futura prod | Eliminare solo dopo la decisione di callback. | diff --git a/infra/localstack/README.md b/infra/localstack/README.md index b7aa6f8..4cbb2da 100644 --- a/infra/localstack/README.md +++ b/infra/localstack/README.md @@ -29,21 +29,24 @@ Resources modelled here: - SES local sender identity. Compose starts LocalStack and application processes. Terraform creates AWS-like resources. The -default frontend path is still S3 local + CloudFront local: Terraform owns the LocalStack S3 -bucket, while the `frontend-cloudfront` Docker service fronts that bucket and proxies API calls -to Nginx. This avoids mixing frontend static assets with the optional real S3 bucket used by +default frontend path is S3 local + a local CDN emulator: Terraform owns the LocalStack S3 +bucket, while the `edge-cdn` Docker service — a second Nginx that emulates the role of +a CDN/edge (not Amazon CloudFront) — fronts that bucket and proxies API calls to the application +Nginx. It is a separate container on purpose: the application Nginx is a production image and must +not reference LocalStack, so the emulated S3 serving stays confined to a local-only scaffold. This +also avoids mixing frontend static assets with the optional real S3 bucket used by documents/Textract. Frontend static serving flow: ```bash make frontend-s3-local-deploy -make frontend-cloudfront-local-url +make edge-cdn-local-url make frontend-serving-local-test ``` -The CloudFront emulator is local and Docker-based because the LocalStack image used by this PoC -does not expose the CloudFront API in the default local license. It validates the local -build-to-bucket-to-CDN flow, but it does not replace testing real CloudFront behavior such as TLS -certificates, edge propagation, invalidations, OAC/OAI, response headers policies or AWS IAM -enforcement. +The CDN emulator is local and Docker-based (a plain Nginx) because the LocalStack image used by +this PoC does not expose the CloudFront API in the default local license. It validates the local +build-to-bucket-to-edge flow, but it does not replace a real CDN: in production the role would be +filled by AWS CloudFront (TLS certificates, edge propagation, invalidations, OAC/OAI, response +headers policies, AWS IAM enforcement). diff --git a/infra/localstack/outputs.tf b/infra/localstack/outputs.tf index 8581c5e..d899f62 100644 --- a/infra/localstack/outputs.tf +++ b/infra/localstack/outputs.tf @@ -18,8 +18,8 @@ output "frontend_s3_website_endpoint" { value = "http://${aws_s3_bucket.frontend_static.bucket}.s3-website.localhost.localstack.cloud:4566" } -output "frontend_cloudfront_local_url" { - value = var.frontend_cloudfront_local_url +output "edge_cdn_local_url" { + value = var.edge_cdn_local_url } output "event_bus_name" { diff --git a/infra/localstack/variables.tf b/infra/localstack/variables.tf index 22c6efb..e25d3a5 100644 --- a/infra/localstack/variables.tf +++ b/infra/localstack/variables.tf @@ -40,8 +40,8 @@ variable "frontend_static_bucket" { default = "poc-frontend-static-local" } -variable "frontend_cloudfront_local_url" { - description = "Local URL of the Docker CloudFront emulator that fronts the LocalStack S3 frontend bucket." +variable "edge_cdn_local_url" { + description = "Local URL of the Docker CDN/edge emulator (Nginx) that fronts the LocalStack S3 frontend bucket." type = string default = "https://localhost:8443" }