Installazione

La distribuzione dei servizi della piattaforma avviene mediante immagini docker, non ci sono requisiti particolari in merito all'orchestratore utilizzato (docker swarm, kubernetes, nomad, mesos).

Installazione ambiente di Test / Sviluppo

Per una installazione di test su singolo nodo si consiglia di usare il service compose presente nei due repository principali:

Installazione ambiente di Produzione

Per un ambiente di produzione è indispensabile predisporre un ambiente con adeguati livelli di ridondanza e dimensionamento dei singoli servizi in base alla propria infrastruttura e ai propri requisiti in termini di affidabilità e livelli di servizio da garantire.

Non c'e' una ricetta valida a priori per dimensionare il sistema, si consiglia di partire da una configurazione con 2 repliche di ogni servizio stateless e un dimensionamento molto largo (es: 1GB di ram, 1 vCPU) e poi monitorare il proprio sistema per stabilire i requisiti di ogni singolo container: si potrà verificare facilmente che molti microservizi richiedono pochi Mb di ram.

Elenco dei servizi necessari

Ogni servizio si basa su una immagine docker specifica e necessita di adeguate configurazioni. Le configurazioni e i valori di default delle stesse sono documentate nei singoli repository dei microservizi.

Al fine di dare una corretta configurazione dei vari servizi vengono indicate inoltre:

  1. la dipendenza dalle persistenze usate dalla piattaforma

  2. la necessità di pubblicare un endpoint per quel servizio pubblicamente raggiungibile

Sito istituzionale

ServizioDescrizioneDocker ImagePubblico

cms-core

Servizio principale del CMS, consente l'accesso della radazione per la gestione dei contenuti e fornisce l'interfaccia di navigazione per i cittadini

PostgreSQLRedisFiles storage

solr

Motore di ricerca in cui il cms indicizza i contenuti per rendere performante la ricerca sul sito

Files storage

varnish

Http cache usata davanti al CMS per ridurre il carico sul CMS stesso.

Servizi digitali e Area Personale

ServizioDescrizioneDocker ImagePubblicoPersistenza

app-web

Servizio principale della piattaforma, gestisce pratiche, appuntamenti, offre interfacce di configurazione per gli amministratori e di gestione operativa per i funzionari degli enti. E' inoltre l'interfaccia principale anche per l'esperienza utente.

PostgreSQLFile StorageRedis

app-worker

Esecuzione delle operazioni asincrone del Core (creazione PDF, invio di messaggi, webhooks, ...)

PostgreSQLFile Storage

app-manager

Esecuzione delle migrazioni database

PostgreSQL

app-varnish

Http cache per scaricare l'app-web

form-server

Server delle form (Form.IO)

MongoDB

form-varnish

Http cache per scaricare il form-server

form-builder

Applicativo per la gestione dei subform condivisi da tutti i moduli form.io

payment- dispatcher-v1

Genera, se necessario, un evento Versione 1 di pagamento da un evento di una pratica

Kafka

payment- dispatcher-v2

Genera, se necessario, un evento Versione 2 di pagamento da un evento di una pratica

Kafka

payment-updater

Ascolta sul topic di kafka dei pagamenti e aggiorna lo stato delle pratiche di conseguenza

Kafka

gotenberg

API interna per la creazione di file PDF

ksqldb

Ascolta su tutti i topic di kafka ed espone via API dati aggregati sugli eventi della piattaforma

Kafka

kafka-core-api

Espone un endpoint http che il core usa per inviare eventi sui topic di kafka

KafkaFile Storage

payments poller

Legge da Ksql i pagamenti pendenti e per ognuno di essi chiama il proxy che lo gestisce per aggiornare lo stato del pagamento

XXX-payment proxy

Servizio per l'integrazione con l'intermediario di pagamento XXX (vedi Integrazioni per la lista completa)

KafkaFile Storage

YYY-protocol- proxy

Servizio per l'integrazione con il sistema di protocollo YYY (vedi Integrazioni per la lista completa)

File StorageKafka

registry-api

API per l'app Django con cui abbiamo integrato una decina di protocolli

PostgreSQL

registry-scheduler

Scheduler per gli errori registrati durante un tentativo di protocollazione

PostgreSQLKafka

analytics-services-aggregator

Servizio per il calcolo dei KPI sui Servizi

KafkaClickHouse

analytics-first-availability-aggregator

Servizio per il calcolo dei KPI sui Calendari

KafkaClickHouse

analytics-bookings-aggregator

Servizio per il calcolo dei KPI sui Calendari

KafkaClickHouse

analytics-charts-exporter

API per la generazione dei grafici degli Analytics

ClickHouse

satisfy-hasura

API per il servizio di Customer Satisfaction

PostgreSQL

satisfy-api

API per la raccolta di ratings e questionari di Customer Satisfaction

Kafka

satisfy-ratings-aggregator

Servizio per il calcolo dei KPI della Customer Satisfaction

Kafka

pdnd-connector

Servizio che gestisce l'interazione con la pdnd e gli enti erogatori degli e-service utilizzati dalla piattaforma

File Storage

Esempio di un file di deployment per orchestratore Docker Swarm:

File di esempio
version: "3.8"
# vim: autoindent tabstop=2 shiftwidth=2 expandtab softtabstop=2 filetype=yaml fileencoding=utf-8

networks:
  oc-prod-public:             # usata per pubblicare servizi
    external: true
  oc-prod-internal:           # usata per la comunicazione tra i componenti dello stack
    external: true

x-deploy: &deploy-snippet
      endpoint_mode: dnsrr
      placement:
        max_replicas_per_node: 2
        constraints: [ node.labels.apps == true ]
      replicas: 1
      update_config:
        parallelism: 1
        order: start-first
        delay: 30s
        failure_action: rollback
        monitor: 10s
      rollback_config:
        parallelism: 1
        delay: 10s
        failure_action: pause         # default: pause or continue
        monitor: 10s                  # default: 5s
        order: start-first
      restart_policy:
        condition: any
        delay: 15s                    # default: 5s, between attempts
        max_attempts: 50
        window: 20s                   # time to decide if the restart has been sucessfull
      resources:
        limits:
          cpus: '0.2'
          memory: 512M
        reservations:
          memory: 32M


x-sdc-env: &sdc-env
    ENV: prod
    APP_ENV: prod
    DB_DRIVER: pdo_pgsql
    DB_HOST: 'postgres'
    DB_PORT: 5432
    DB_NAME: oc
    DB_USER: oc
    DB_VERSION: 14
    DB_PASSWORD: 'oc'
    MAILER_URL: 'smtp://mailserver:1025'
    SECRET: change_me
    DEFAULT_FROM_EMAIL_ADDRESS: no-reply@opencity.it
    EZ_PASSWORD: change_me
    OCSDC_SCHEME: https
    OCSDC_HOST: app.localtest.me
    LOGS_PATH: php://stderr
    ENABLE_MIGRATIONS: 'false'
    ENABLE_INSTANCE_CONFIG: 'false'
    # accesso in scrittura da PHP
    FORMSERVER_PRIVATE_URL: http://form-server:8000
    # accesso con cache varnish
    FORMSERVER_PUBLIC_URL: http://form.localtest.me
    # accesso readonly per admin
    FORMSERVER_ADMIN_URL: http://forms-readonly.localtest.me
    WKHTMLTOPDF_SERVICE: http://gotenberg:3000
    PHP_FPM_USER: wodby
    PHP_FPM_GROUP: wodby
    PHP_APCU_ENABLED: 1
    PHP_OPCACHE_ENABLE: 1
    PHP_FPM_CLEAR_ENV: 'no'
    PHP_FPM_PM_MAX_CHILDREN: 50
    PHP_FPM_PM_MAX_REQUESTS: 2000
    PHP_FPM_PM_MAX_SPARE_SERVERS: 8
    PHP_FPM_PM_MIN_SPARE_SERVERS: 2
    PHP_FPM_REQUEST_SLOWLOG_TIMEOUT: 4000
    PHP_FPM_PM_START_SERVERS: 5
    PHP_DISPLAY_ERRORS: 'off'
    PHP_DISPLAY_STARTUP_ERRORS: 'off'
    PHP_ERROR_REPORTING: 'E_ERROR'
    PHP_DATE_TIMEZONE: 'Europe/Rome'
    PHP_SESSION_SAVE_HANDLER: redis
    PHP_SESSION_SAVE_PATH: 'tcp://redis:6379'
    PHP_SESSION_GC_MAXLIFETIME: 2880
    RECAPTCHA_KEY: recaptcha_key
    RECAPTCHA_SECRET: recaptcha_secret
    EWZ_RECAPTCHA_SITE_KEY: recaptcha_key
    EWZ_RECAPTCHA_SECRET: recaptcha_secret
    FEATURE_NEW_OUTDATED_BROWSER: 'true'
    FEATURE_APPLICATION_DETAIL: 'true'
    FEATURE_CALENDAR_TYPE: 'true'
    #FEATURE_DUE_AMOUNT: 'true'
    #FEATURE_ANALYTICS: 'true'
    TOKEN_TTL: 864000 # 10gg
    CACHE_MAX_AGE: 3600
    KAFKA_URL: vector
    KSQLDB_URL: ksqldb-server:8088
    SKIP_CACHE_WARMUP: 'false'
    TRUSTED_PROXIES: '10.0.0.0/8,172.16.0.0/12,192.168.0.0/16'
    DEFAULT_CACHE_REDIS_PROVIDER: 'redis://redis:6379'
    DEFAULT_CACHE_LIFETIME: 3600
    DEFAULT_CACHE_PREFIX_SEED: v-2.27.0
    REGISTRY_API_URL: https://registry.qa.stanzadelcittadino.it
    REGISTRY_API_KEY: change_me
    API_USER_PASSWORD: change_me
    # In assenza di una vera autenticazione basata su SPID si può simulare
    # un login di un utente con le seguenti variabili d'ambiente:
    shibb_pat_attribute_codicefiscale: CLNVTR76P01G822Q
    shibb_pat_attribute_cognome: Coliandro
    shibb_pat_attribute_nome: Vittorino
    shibb_pat_attribute_sesso: M
    shibb_pat_attribute_emailaddress: info@comune.bugliano.pi.it
    shibb_pat_attribute_datanascita: 1/9/1976
    shibb_pat_attribute_luogonascita: Ponsacco
    shibb_pat_attribute_provincianascita: PI
    shibb_pat_attribute_telefono: 003912378945
    shibb_pat_attribute_cellulare: 333 444 666 99
    shibb_pat_attribute_indirizzoresidenza: 'Via Gramsci, 1'
    shibb_pat_attribute_capresidenza: 56056
    shibb_pat_attribute_cittaresidenza: Bugliano
    shibb_pat_attribute_provinciaresidenza: PI
    shibb_pat_attribute_statoresidenza: Italia
    shibb_pat_attribute_spidcode: 123456789
    shibb_pat_attribute_x509certificate_issuerdn: FAKE_issuerdn
    shibb_pat_attribute_x509certificate_subjectdn: FAKE_subjectdn
    shibb_pat_attribute_x509certificate_base64: "DQpSZXN1bHQgZ29lcyBoZXJlLi4uDQpCYXNlNjQNCg0KQmFzZTY0IGlzIGEgZ2VuZXJpYyB0ZXJtIGZvciBhIG51bWJlciBvZiBzaW1pbGFyIGVuY29kaW5nIHNjaGVtZXMgdGhhdCBlbmNvZGUgYmluYXJ5IGRhdGEgYnkgdHJlYXRpbmcgaXQgbnVtZXJpY2FsbHkgYW5kIHRyYW5zbGF0aW5nIGl0IGludG8gYSBiYXNlIDY0IHJlcHJlc2VudGF0aW9uLiBUaGUgQmFzZTY0IHRlcm0gb3JpZ2luYXRlcyBmcm9tIGEgc3BlY2lmaWMgTUlNRSBjb250ZW50IHRyYW5zZmVyIGVuY29kaW5nLg=="
    shibb_Shib-Session-ID: abc123abc123abc123abc123abc123abc123abc123
    shibb_Shib-Session-Index: abc123abc123abc123abc123abc123abc123abc123
    shibb_Shib-Authentication-Instant: 2000-01-01T00-00Z

x-registry-env: &registry-env
  DJANGO_DATABASE: postgres:5432:oc_registry:oc:oc
  DJANGO_SECRET_KEY: change_me
  DJANGO_SETTINGS_MODULE: application_registry.settings_production
  KAFKA_BOOTSTRAP_SERVERS: kafka:9092
  SENTRY_DSN:

configs:
  sf-config-instances:
    name: oc-instances-v1
    file: ./config/app/instances.yml
  vector-api:
    name: occ-kafka-http-v1
    file: ./config/kafka/vector.toml

services:

  # Symfony-core, esposto solo su rete interna
  app:
    image: registry.gitlab.com/opencity-labs/area-personale/core/app:2.27.0
    stop_grace_period: 45s
    configs:
      - source: sf-config-instances
        target: /var/www/html/config/instances_prod.yml
    networks:
      - oc-prod-internal
    environment:
      <<: *sdc-env
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:80/health-check"]
      interval: 1m30s
      timeout: 10s
      retries: 6
      start_period: 5s
    deploy:
      <<: *deploy-snippet
      endpoint_mode: vip
      replicas: 1
      resources:
        limits:
          cpus: '1'
          memory: 2048M
        reservations:
          memory: 1024M
      labels:
        traefik.enable: 'false'

  # Frontend del symfony-core
  varnish:
    image: registry.gitlab.com/opencontent/varnish:1.2
    stop_grace_period: 45s
    networks:
      - oc-prod-public
      - oc-prod-internal
    healthcheck:
      disable: true
    deploy:
      <<: *deploy-snippet
      endpoint_mode: dnsrr
      replicas: 1
      resources:
        limits:
          cpus: '1'
          memory: 2048M
        reservations:
          memory: 1024M
      labels:
        traefik.enable: 'true'
        traefik.http.services.varnish.loadbalancer.server.port: 6081
        # disabilitato altrimenti quando fallisce backend non sfrutto grace
        #traefik.http.services.varnish.loadbalancer.healthcheck.path: /health-check

        # Security headers
        traefik.http.middlewares.security.headers.frameDeny: 'true'
        traefik.http.middlewares.security.headers.customFrameOptionsValue: 'SAMEORIGIN'
        traefik.http.middlewares.security.headers.stsSeconds: 31536000
        traefik.http.middlewares.security.headers.stsIncludeSubdomains: 'true'
        traefik.http.middlewares.security.headers.forceStsHeader: 'true'
        #traefik.http.middlewares.security.headers.contentSecurityPolicy: "default-src 'self' data:; img-src 'self' data: blob: c.bing.com www.googletagmanager.com *.openstreetmap.org c.clarity.ms cartodb-basemaps-b.global.ssl.fastly.net cartodb-basemaps-c.global.ssl.fastly.net cartodb-basemaps-a.global.ssl.fastly.nett; style-src 'self' 'unsafe-inline' static.opencityitalia.it satisfy.opencontent.it widget.freshworks.com stackpath.bootstrapcdn.com unpkg.com cdn.datatables.net printjs-4de6.kxcdn.com cdnjs.cloudflare.com fonts.googleapis.com; connect-src 'self' s3.eu-west-1.amazonaws.com api.opencityitalia.it qa-genova-id.boat.opencontent.io dev-genova-id.boat.opencontent.io mappe.genova.opencityitalia.it api.opencontent.it api.qa.stanzadelcittadino.it registry.qa.stanzadelcittadino.it satisfy.boat.opencontent.io static.opencityitalia.it www.giscom.cloud nominatim.openstreetmap.org a.clarity.ms d.clarity.ms k.clarity.ms i.clarity.ms h.clarity.ms widget.freshworks.com fiscalcode.opencontent.it form-qa.stanzadelcittadino.it form-readonly-qa.stanzadelcittadino.it cdn.datatables.net satisfy.opencityitalia.it satisfy.opencontent.it satisfy.hasura.app sdc-analytics-dev-charts-exporter.boat.opencontent.io efil-proxy-qa.boat.opencontent.io pmpay-proxy-qa.boat.opencontent.io iris-proxy-qa.boat.opencontent.io mypay-proxy-qa.boat.opencontent.io www.openstreetmap.org flyimg.opencontent.it; script-src 'self' 'unsafe-inline' 'unsafe-eval' static.opencityitalia.it cdn.announcekit.app widget.freshworks.com www.clarity.ms form-qa.stanzadelcittadino.it cdnjs.cloudflare.com cdn.form.io unpkg.com code.jquery.com www.googletagmanager.com cdn.datatables.net printjs-4de6.kxcdn.com satisfy.opencityitalia.it satisfy.opencontent.it www.recaptcha.net www.gstatic.com; object-src 'none'; font-src 'self' data: satisfy.opencontent.it stackpath.bootstrapcdn.com cdnjs.cloudflare.com unpkg.com fonts.gstatic.com; worker-src 'self'; report-uri https://csp-collector.opencontent.it/csp?env=production&mode=enforce&app=sdc-qa; child-src 'self' www.youtube-nocookie.com announcekit.app"
        traefik.http.middlewares.security.headers.contentTypeNosniff: 'true'
        traefik.http.middlewares.security.headers.permissionsPolicy: "geolocation=(self), camera=(self), microphone=(self)"
        traefik.http.middlewares.security.headers.referrerPolicy: 'strict-origin-when-cross-origin'
        traefik.http.middlewares.security.headers.browserXssFilter: 'true'

        # Cors generali
        traefik.http.routers.https.rule: 'Method(`GET`, `POST`, `PUT`, `DELETE`, `PATCH`, `HEAD`, `OPTIONS`, `PURGE`) && Host(`app.localtest.me`)'
        traefik.http.routers.https.tls: 'true'
        traefik.http.routers.https.tls.certresolver: 'myhttpchallenge'
        traefik.http.routers.https.middlewares: 'cors, general-rl, security'

        # Cors per condivisione autenticazione
        traefik.http.routers.origin-https.rule: 'Method(`GET`, `PUT`, `DELETE`, `PATCH`, `HEAD`, `PURGE`) && Host(`app.localtest.me`)  && PathPrefix(`/lang/api/session-auth`)'
        traefik.http.routers.origin-https.tls: 'true'
        traefik.http.routers.origin-https.priority: 10001
        traefik.http.routers.origin-https.tls.certresolver: 'myhttpchallenge'
        traefik.http.routers.origin-https.middlewares: 'cors-origin, general-rl, security'

        traefik.http.middlewares.cors.headers.accesscontrolallowcredentials: 'true'
        traefik.http.middlewares.cors.headers.accesscontrolallowheaders: 'Authorization,Origin,Content-Type,Accept,access-control-allow-headers,access-control-allow-methods,access-control-allow-origin,access-control-allow-credentials,Referer,X-Hasura-Role,X-Hasura-Admin-Secret,X-Hasura-Allowed-Roles,X-Hasura-Default-Role,X-Hasura-User-Id,X-Hasura-Org-Id,X-Locale'
        traefik.http.middlewares.cors.headers.accesscontrolallowmethods: 'GET,POST,HEAD,PUT,DELETE,PATCH,OPTIONS'
        traefik.http.middlewares.cors.headers.accesscontrolalloworiginlist: '*'
        traefik.http.middlewares.cors.headers.accesscontrolmaxage: 100
        traefik.http.middlewares.cors.headers.addvaryheader: 'true'
        
        traefik.http.middlewares.cors-origin.headers.accesscontrolallowcredentials: 'true'
        traefik.http.middlewares.cors-origin.headers.accesscontrolallowheaders: 'Authorization,Origin,Content-Type,Accept,access-control-allow-headers,access-control-allow-methods,access-control-allow-origin,access-control-allow-credentials,Referer,X-Hasura-Role,X-Hasura-Admin-Secret,X-Hasura-Allowed-Roles,X-Hasura-Default-Role,X-Hasura-User-Id,X-Hasura-Org-Id,X-Locale'
        traefik.http.middlewares.cors-origin.headers.accesscontrolallowmethods: 'GET,POST,HEAD,PUT,DELETE,PATCH,OPTIONS'
        traefik.http.middlewares.cors-origin.headers.accesscontrolmaxage: 100
        traefik.http.middlewares.cors-origin.headers.addvaryheader: 'true'

    environment:
      # https://github.com/wodby/varnish#environment-variables
      VARNISH_BACKEND_GRACE  : '2880m'
      VARNISH_BACKEND_HOST: 'app'
      VARNISH_BACKEND_PORT: '80'
      VARNISH_BACKEND_PROBE: '/health-check'
      VARNISH_KEEP_ALL_PARAMS: 1
      VARNISH_CACHE_STATIC_FILES: 1
      ##VARNISH_MOBILE_SEPARATE_CASH: 1
      #VARNISH_BIG_FILES_SIZE: 10485760
      VARNISHD_MEMORY_SIZE: '1024m'
      VARNISH_BACKEND_BETWEEN_BYTES_TIMEOUT: 30s
      VARNISH_BACKEND_FIRST_BYTE_TIMEOUT: 30s
      VARNISH_STRIP_COOKIES: 'cookies_consent|_[_a-z]+|wooTracker|VCKEY-[a-zA-Z0-9-_]+'
      VARNISHD_PARAM_WORKSPACE_BACKEND: '256k'
      VARNISHD_PARAM_WORKSPACE_CLIENT: '256k'
      VARNISH_SECRET: 'change_me'

  # Manager symfony, non è esposto su rete pubblica esegue migrazioni e script all'avvio
  manager:
    image: registry.gitlab.com/opencity-labs/area-personale/core/app:2.27.0
    stop_grace_period: 45s
    configs:
      - source: sf-config-instances
        target: /var/www/html/config/instances_prod.yml
    networks:
      - oc-prod-internal
    environment:
      <<: *sdc-env
      DB_USER: oc   # Inserire username utente manager se configurato
      DB_PASSWORD: 'oc' # Inserire password utente manager se configurato
      ENABLE_MIGRATIONS: 'true'
    deploy:
      <<: *deploy-snippet
      endpoint_mode: vip
      replicas: 1
      resources:
        limits:
          cpus: '1'
          memory: 1024M
        reservations:
          memory: 512M

  # Ci sono 3 valori da tenere sempre uguali: WORKER_NUM, replicas e swam_cronjob_replicas
  worker:
    image: registry.gitlab.com/opencity-labs/area-personale/core/app:2.27.0
    networks:
      - oc-prod-internal
    stop_signal: SIGQUIT
    stop_grace_period: 20m
    command: [ "./bin/worker-daemon.sh" ]
    configs:
      - source: sf-config-instances
        target: /var/www/html/config/instances_prod.yml
    volumes:
      - ./config/worker/worker-daemon.sh:/var/www/html/bin/worker-daemon.sh:ro
    healthcheck:
      disable: true
    environment:
      <<: *sdc-env
      SWARM_SERVICE: "worker"
      SWARM_STACK: "oc-prod"
      #DEBUG: 1
      WORKER_ID: 1
      WORKER_NUM: 1
      MAX_EXECUTIONS: 100
      MAX_ACTIONS: 50
    deploy:
      <<: *deploy-snippet
      endpoint_mode: dnsrr
      replicas: 1
      update_config:
        order: stop-first
      restart_policy:
        condition: none
      resources:
        limits:
          cpus: '0.5'
          memory: 2048M
        reservations:
          memory: 512M
      labels:
        swarm.cronjob.enable: "true"
        swarm.cronjob.schedule: "*/3 * * * *"
        swarm.cronjob.skip-running: "true"
        swarm.cronjob.replicas: 1

  # Espone delle api rest per la creazione di eventi su kafka
  vector:
    image: timberio/vector:0.18.1-debian
    configs:
      - source: vector-api
        target: /etc/vector/vector.toml
    healthcheck:
      test: [ "CMD", "curl", "-f", "http://localhost:80/health" ]
      interval: 1m30s
      timeout: 10s
      retries: 6
      start_period: 5s
    environment:
      VECTOR_LOG: debug
    networks:
      - oc-prod-internal
    deploy:
      <<: *deploy-snippet
      labels:
        traefik.enable: 'false'

  # Microservizio per la creazione di pdf
  gotenberg:
    image: gotenberg/gotenberg:7.9.2
    networks:
      - oc-prod-internal
      - oc-prod-public
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
      interval: 1m30s
      timeout: 10s
      retries: 6
      start_period: 5s
    deploy:
      <<: *deploy-snippet
      labels:
        prometheus.disable: 'true'
        traefik.enable: 'true'
        traefik.http.services.gotenberg.loadbalancer.server.port: 3000
        traefik.http.routers.gotenberg.rule: 'Host(`gotenberg.localtest.me`)'
        traefik.http.routers.gotenberg.tls: 'true'
        traefik.http.routers.gotenberg.tls.certresolver: 'myhttpchallenge'
        traefik.http.routers.gotenberg.middlewares: 'goodheaders'

  # Strumento per testare la posta elettronica
  mailhog:
    image: mailhog/mailhog
    networks:
      - oc-prod-internal
    deploy:
      <<: *deploy-snippet
      labels:
        traefik.enable: 'true'
        traefik.http.services.mailhog.loadbalancer.server.port: 8025
        traefik.http.routers.mailhog.rule: Host(`mail.localtest.me`)
        traefik.http.routers.mailhog.tls: 'true'
        traefik.http.routers.mailhog.middlewares: goodheaders
        traefik.http.routers.mailhog.tls.certresolver: 'myhttpchallenge'

  ##############################################################################
  # FORMS
  ##############################################################################

  # Frontend per il formserver
  form-varnish:
    image: registry.gitlab.com/opencontent/varnish:1.2
    stop_grace_period: 45s
    networks:
      - oc-prod-internal
      - oc-prod-public
    deploy:
      <<: *deploy-snippet
      replicas: 1
      resources:
        limits:
          cpus: '0.5'
          memory: 512M
        reservations:
          cpus: '0.1'
          memory: 128M
      labels:
        prometheus.enable: 'true'
        prometheus.port: 9131
        traefik.enable: 'true'
        traefik.http.services.formvarnish.loadbalancer.server.port: 6081
        traefik.http.services.formvarnish.loadbalancer.healthcheck.path: /.vchealthz
        traefik.http.routers.formvarnish.rule: 'Method(`GET`, `HEAD`, `OPTIONS`) && Host(`form.localtest.me`)'
        traefik.http.routers.formvarnish.tls: 'true'
        traefik.http.routers.formvarnish.tls.certresolver: 'myhttpchallenge'
        traefik.http.routers.formvarnish.middlewares: 'cors'


    environment:
      # https://github.com/wodby/varnish#environment-variables
      VARNISH_BACKEND_HOST: 'form-server'
      VARNISH_BACKEND_PORT: '8000'
      VARNISH_BACKEND_GRACE: '2880m'
      VARNISH_BACKEND_PROBE: '/form/5d7aa15b18fecd734051ae7f?varnish-health'
      VARNISH_CACHE_STATIC_FILES: 1
      VARNISHD_MEMORY_SIZE: '256m'
      VARNISH_BACKEND_BETWEEN_BYTES_TIMEOUT: 10s
      VARNISH_BACKEND_FIRST_BYTE_TIMEOUT: 30s
      VARNISH_KEEP_ALL_PARAMS: 1
      VARNISH_STRIP_COOKIES: 'cookie_consent|_[_a-z]+|wooTracker|VCKEY-[a-zA-Z0-9-_]+|_ga|_gid|_gat[a-zA-Z0-9-_]+'
      VARNISH_SECRET: 'nvXq3CkK'
      VARNISHD_PARAM_WORKSPACE_BACKEND: '256k'
      VARNISHD_PARAM_WORKSPACE_CLIENT: '256k'

  # Espone api rest per il crud dei form
  form-server:
    image: registry.gitlab.com/opencity-labs/area-personale/form-server:1.3.0
    networks:
      - oc-prod-public
      - oc-prod-internal
    environment:
      DB_URL: mongodb://mongo:27017/formmanager
      MAX_AGE: 5
      S_MAX_AGE: 60
    #healthcheck:
    #  test: [ "CMD", "wget", "--spider", "http://localhost:8000/form/5d7aa15b18fecd734051ae7f" ]
    #  start_period: 30s
    #  timeout: 1s
    #  retries: 1
    #  interval: 1m
    deploy:
      <<: *deploy-snippet
      labels:
        traefik.enable: 'true'
        traefik.http.services.formserver.loadbalancer.server.port: 8000

        # Espongo il formserver aperto in scrittura con oc-auth, sotto /api per il form-builder
        traefik.http.routers.formserver.rule: 'Method(`GET`, `HEAD`, `OPTIONS`, `POST`, `PUT`, `PATCH`, `DELETE`) && Host(`form-builder.localtest.me`) && PathPrefix(`/api/`)'
        traefik.http.routers.formserver.tls: 'true'
        traefik.http.routers.formserver.tls.certresolver: 'myhttpchallenge'
        traefik.http.middlewares.strip-forms-api.stripprefix.prefixes: '/api/'
        traefik.http.routers.formserver.middlewares: 'strip-forms-api'

        # Espongo anche readonly per gli admin da backend (per vedere subito aggiornamenti)
        traefik.http.routers.formserver-ro.rule: 'Method(`GET`, `POST`, `HEAD`, `OPTIONS`) && Host(`form-readonly.localtest.me`)'
        traefik.http.routers.formserver-ro.tls: 'true'
        traefik.http.routers.formserver-ro.tls.certresolver: 'myhttpchallenge'
        traefik.http.routers.formserver-ro.middlewares: 'oc-auth, cors'

  # Microservizio per la gestione dei componenti comuni dei form (nested form)
  form-builder:
    image: registry.gitlab.com/opencity-labs/area-personale/formbuilderjs:0-5-2
    healthcheck:
      test: [ "NONE" ]
    networks:
      - oc-prod-public
    deploy:
      <<: *deploy-snippet
      labels:
        traefik.enable: 'true'
        traefik.http.services.formbuilder.loadbalancer.server.port: 80

        traefik.http.routers.formbuilder.rule: 'Method(`GET`, `HEAD`, `OPTIONS`) && Host(`form-builder.localtest.me`)'
        traefik.http.routers.formbuilder.tls: 'true'
        traefik.http.routers.formbuilder.tls.certresolver: 'myhttpchallenge'
        traefik.http.routers.formbuilder.middlewares: 'oc-auth'

  ##############################################################################
  # FORMS
  ##############################################################################

  # Interroga la tabella PAYMENTS_STATUS  su ksqldb e richiede info ai proxy sui pagamenti pendenti
  payments-poller:
    image: registry.gitlab.com/opencity-labs/area-personale/payments-poller:1.0.2
    networks:
      - oc-prod-internal
    environment:
      KSQLDB_SERVER: http://ksqldb-server:8088
      LOOP_TIME: 900
      LOG_LEVEL: DEBUG
      HEALTHCHECK_ID: 88f0d2a5-0326-46a1-9093-42e83c3017c8
      PYTHONDEBUG: 1
    deploy:
      <<: *deploy-snippet
      resources:
        reservations:
          memory: 1G
        limits:
          cpus: '0.5'
          memory: 4G
    healthcheck:
      test: [ NONE ]

  # Legge gli eventi dal topic applications e crea un evento nel topic payments se necessario
  payment-dispatcher:
    image: registry.gitlab.com/opencity-labs/area-personale/payment-dispatcher:1.2.1
    networks:
      - oc-prod-internal
    environment:
      KAFKA_TOPIC_APPLICATIONS: applications
      KAFKA_TOPIC_PAYMENTS: payments
      KAFKA_BOOTSTRAP_SERVERS: kafka:9092
      KAFKA_CONSUMER_GROUP_PREFIX: payment-dispatcher
      PROMETHEUS_JOB_NAME: payment_dispatcher
      APP_ID: payment-dispatcher:1.2.1
      KSQLDB_SERVER: http://ksqldb-server:8088
      LOG_LEVEL: INFO
    healthcheck:
      test: [ NONE ]
    deploy:
      <<: *deploy-snippet
      resources:
        limits:
          cpus: '0.5'
          memory: 1G

  # Gateway proxy di iris (ambiente di test)
  iris-proxy:
    image: registry.gitlab.com/opencity-labs/area-personale/iris-payment-proxy:1.3.3
    stop_grace_period: 5m
    networks:
      - oc-prod-public
    environment:
      CANCEL_PAYMENT: "true"
      KAFKA_TOPIC_NAME: payments
      KAFKA_BOOTSTRAP_SERVERS: kafka:9092
      KAFKA_GROUP_ID: iris_payment_proxy
      IRIS_OTF_PAYMENT_URL: https://apistage.regione.toscana.it/C01/ComunicazionePosizioniDebitorieOTF/v3/IdpAllineamentoPendenzeEnteOTF
      IRIS_IUV_URL: https://iristest.rete.toscana.it/IdpBillerNdpServices/GenerazioneIUVService
      IRIS_OUTCOME_URL: https://apistage.regione.toscana.it/C01/InvioNotificaPagamentoEsito/v3/IdpInformativaPagamento.Esito
      IRIS_VERIFY_URL: https://apistage.regione.toscana.it/C01/VerificaStatoPagamento/v3/IdpVerificaStatoPagamento
      IRIS_NOTICE_URL: https://iristest.rete.toscana.it/IdpBillerNdpServices/GenerazioneAvvisiService
      EXTERNAL_API_URL: "https://iris-proxy.localtest.me"
      INTERNAL_API_URL: "http://iris-proxy.localtest.me"
      BASE_PATH_EVENT: "sdc-payments/iris-proxy/payments/"
      BASE_PATH_CONFIG: "sdc-payments/iris-proxy/tenants/"
      # STORAGE_TYPE="LOCAL|MINIO|S3"
      STORAGE_TYPE: "LOCAL"
      STORAGE_BUCKET_NAME: "payments"
      APP_ID: "iris-proxy:1.3.3"
    deploy:
      <<: *deploy-snippet
      labels:
        traefik.enable: 'true'
        traefik.http.services.iris-proxy-qa.loadbalancer.server.port: 8000

        traefik.http.routers.iris-proxy.entrypoints: 'websecure, web'
        traefik.http.routers.iris-proxy.rule: 'Host(`iris-proxy.localtest.me`) && PathPrefix(`/{op:(notice|online-payment|receipt|landing)}/{id:[^/]+}`, `/{op:(docs|redoc|openapi.json|tenants|services|notify-payment)}`, `/{op:(tenants|services)}/({id:[^/]+}|schema)`) && METHOD(`GET`, `POST`, `PUT` ,`PATCH`, `DELETE`, `HEAD`, `OPTIONS`)'
        traefik.http.routers.iris-proxy.tls: 'true'
        traefik.http.routers.iris-proxy.tls.certresolver: 'myhttpchallenge'

        traefik.http.middlewares.payment-proxy-qa-cors.headers.accesscontrolallowcredentials: 'true'
        traefik.http.middlewares.payment-proxy-qa-cors.headers.accesscontrolallowheaders: 'Authorization,Origin,Content-Type,Accept,access-control-allow-headers,access-control-allow-methods,access-control-allow-origin,access-control-allow-credentials,Referer'
        traefik.http.middlewares.payment-proxy-qa-cors.headers.accesscontrolallowmethods: 'GET,POST,HEAD,PUT,DELETE,PATCH,OPTIONS'
        # asterisco perché ognuno di essi è acceduto da tutti i domini custom che avranno gli enti
        traefik.http.middlewares.payment-proxy-qa-cors.headers.accesscontrolalloworiginlist: '*'
        traefik.http.middlewares.payment-proxy-qa-cors.headers.accesscontrolmaxage: 100
        traefik.http.middlewares.payment-proxy-qa-cors.headers.addvaryheader: 'true'

        traefik.http.routers.iris-proxy.middlewares: 'goodheaders, payment-proxy-qa-cors'
    volumes:
      - ./var/tenants:/app/payments/sdc-payments/iris-proxy/tenants
      - ./var/payments:/app/payments/sdc-payments/iris-proxy/payments

  # Aggiorna lo stato delle pratiche dopo che si sono verificati dei pagamenti
  krun-process-payments:
    image: registry.gitlab.com/opencity-labs/area-personale/core/app:2.27.0
    configs:
      - source: sf-config-instances
        target: /var/www/html/config/instances_prod.yml
    entrypoint: /bin/krun
    stop_grace_period: 300s
    healthcheck:
      disable: true
    volumes:
      - ./config/krun/process-payment:/bin/process-payment
      - ./config/krun/krun:/bin/krun
    networks:
      - oc-prod-internal
    environment:
      <<: *sdc-env
      KAFKA_TOPIC: "payments"
      KAFKA_SERVER: "kafka:9092"
      KAFKA_CONSUMER_GROUP: "oc-prod-krun-process-payments"
      COMMAND: "/bin/process-payment"
      #DEBUG: 1
      VERBOSE: 1
    deploy:
      <<: *deploy-snippet
      resources:
        limits:
          cpus: '0.2'
          memory: 1024M
      update_config:
        parallelism: 3
        order: stop-first


  ##############################################################################
  # REGISTRY
  ##############################################################################

  # Espone le api per la configurazione dei vari sistemi di protocollazione
  registry-rest-api:
    image: registry.gitlab.com/opencity-labs/area-personale/stanzadelcittadino-application-registry:1.10.2
    networks:
      - oc-prod-public
      - oc-prod-internal
    environment:
      <<: *registry-env
      DJANGO_LOG_LEVEL: INFO
      REGISTRY_API_KEY: change_me
    deploy:
      <<: *deploy-snippet
      labels:
        traefik.enable: "true"
        traefik.http.services.registry.loadbalancer.server.port: 8000
        traefik.http.routers.registry.rule: "Host(`registry.localtest.me`)"
        traefik.http.routers.registry.tls: "true"
        traefik.http.routers.registry.tls.certresolver: "myhttpchallenge"

        traefik.http.middlewares.registry-qa-home-redir.redirectregex.regex: 'registry.localtest.me/$$'
        traefik.http.middlewares.registry-qa-home-redir.redirectregex.replacement: "registry.localtest.me/admin"

        traefik.http.middlewares.registry-redirect-tenants.replacepathregex.regex: '^/(d3|datagraph|dedagroup|halley|halley-cloud|hypersic|infor|insiel|maggioli|pitre|tinn)/v1/tenants/(.*)'
        traefik.http.middlewares.registry-redirect-tenants.replacepathregex.replacement: '/api/v1/tenants/'

        traefik.http.middlewares.registry-redirect-services.replacepathregex.regex: '^/(d3|datagraph|dedagroup|halley|halley-cloud|hypersic|infor|insiel|maggioli|pitre|tinn)/v1/services/'
        traefik.http.middlewares.registry-redirect-services.replacepathregex.replacement: '/api/v1/services/$$1/'
        traefik.http.middlewares.registry-redirect-service.replacepathregex.regex: '^/(d3|datagraph|dedagroup|halley|halley-cloud|hypersic|infor|insiel|maggioli|pitre|tinn)/v1/services/(.+)'
        traefik.http.middlewares.registry-redirect-service.replacepathregex.replacement: '/api/v1/services/$$1/$$2/'

        traefik.http.middlewares.registry-redirect-service-schema.replacepathregex.regex: '^/(d3|datagraph|dedagroup|halley|halley-cloud|hypersic|infor|insiel|maggioli|pitre|tinn)/v1/schema'
        traefik.http.middlewares.registry-redirect-service-schema.replacepathregex.replacement: '/api/v1/services-schema/$$1'

        traefik.http.middlewares.registry-cors.headers.accesscontrolallowcredentials: 'true'
        traefik.http.middlewares.registry-cors.headers.accesscontrolallowheaders: '*'
        traefik.http.middlewares.registry-cors.headers.accesscontrolallowmethods: 'GET,POST,HEAD,PUT,OPTIONS'
        traefik.http.middlewares.registry-cors.headers.accesscontrolalloworiginlist: '*'
        traefik.http.middlewares.registry-cors.headers.accesscontrolmaxage: 100
        traefik.http.middlewares.registry-cors.headers.addvaryheader: 'true'

        traefik.http.routers.registry.middlewares: "goodheaders, registry-qa-home-redir, registry-redirect-tenants, registry-redirect-services, registry-redirect-service, registry-redirect-service-schema, registry-cors"

        prometheus.enable: "true"
        prometheus.port: 8000

  # Gestisce il sistema di retry della protocollazione
  registry-retry-scheduler:
    image: registry.gitlab.com/opencity-labs/area-personale/stanzadelcittadino-application-registry:1.10.2
    command: /app/venv/bin/python3.9 manage.py run_retry_scheduler
    networks:
      - oc-prod-internal
    environment:
      <<: *registry-env
      ENABLE_SENTRY_EVENT_FILTER: "true"
    deploy:
      <<: *deploy-snippet
      
      
  ##############################################################################
  # PDND
  ##############################################################################

  pdnd-connector:
    image: registry.gitlab.com/opencity-labs/area-personale/pdnd-connector:x.x.x
    networks:
      - oc-prod-internal
    deploy:
      <<: *deploy-snippet
    environment:
      ENVIRONMENT: app_environment
      SENTRY_TOKEN: sentry DSN
      HTTP_EXTERNAL_BASEPATH: https://api.qa.stanzadelcittadino.it/pdnd
      SDC_PUBLIC_KEY_ENDPOINT: https://core/.well-known/jwks.json
      CACHE_EXPIRATION: 5m
      CACHE_EVICTION: 10m
      STORAGE_TYPE: s3
      STORAGE_BUCKET: s3_bucket
      STORAGE_BASE_PATH: pdnd
      STORAGE_LOCAL_PATH: /data/pdnd
      STORAGE_S3_KEY: S3_key
      STORAGE_S3_SECRET: s3_secret
      STORAGE_S3_REGION: eu-west-1
      STORAGE_S3_ENDPOINT:  https://s3.eu-west-1.amazonaws.com
      STORAGE_S3_SSL: "false"
    labels:
      prometheus.enable: 'true'
      prometheus.port: 8000
      traefik.enable: 'true'
      traefik.docker.network: 'oc-prod-internal'
      traefik.http.services.pdnd-connector.loadbalancer.server.port: 8000
      traefik.http.routers.pdnd-connector.entrypoints: 'websecure, web'
      traefik.http.routers.pdnd-connector.rule: 'Host(`api.qa.stanzadelcittadino.it`) && PathPrefix(`/pdnd/{op:(status|metrics|docs|anpr/accertamento-residenza|anpr/stato-famiglia|tenants|keys|tenants/configs|tenants/clients|e-services)}`, `/pdnd/{op:(status|metrics|docs|anpr/accertamento-residenza|anpr/stato-famiglia|tenants|keys|tenants/configs|tenants/clients|e-services)}/({id:[^/]+})`) && METHOD(`GET`, `POST`, `PUT` ,`PATCH`, `DELETE`, `HEAD`, `OPTIONS`)'
      traefik.http.routers.pdnd-connector.tls: 'true'
      traefik.http.middlewares.pdnd-connector-stripper.stripprefix.prefixes: '/pdnd/'
      traefik.http.routers.pdnd-connector.tls.certresolver: 'myhttpchallenge'
      traefik.http.routers.pdnd-connector.middlewares: 'goodheaders, cors-free-all, pdnd-connector-stripper'




Prima di procedere all’avvio dei microservizi si devono compiere alcune operazioni:

Creazione del file delle istanze

Va creato un file instances.yml sull'ambiente che ospiterà la piattaforma, questo file dovrà poi essere condiviso con i servizi che ne hanno bisogno (vedi file di esempio di distribuzione).

Il file è così formato:

instances:
  # dominio dell'sitanza con prefisso
  stanzadelcittadino.localtest.me/comune-di-bugliano:
    # codice meccanografico dell'ente
    codice_meccanografico: c_cbug
    # identificativo dell'istanza
    identifier: comune-di-bugliano
    # lingue disponibili (it, de, en)
    app_locales: it|en
    # tipo di login (da scegliere in base al provider dell'ente)
    login_route: login_pat

Andrà creato un blocco istanza per ogni ente che si vorrà ospitare sulla piattaforma

Creazione dei database

Singole istanze degli enti (Symfony core)

Il tipo di multi-tenancy implementata in OpenCity Area personale è di singolo stack applicativo con database dedicato per tenant.

Per poter effettuare un’installazione dell’infrastruttura abbiamo quindi bisogno di un singolo database per tenant con le seguenti caratteristiche.

Postgres >= 11 con estensione postgis >= 3

Permessi

Per ridurre i rischi dovuti a errori o a compromissione delle credenziali vengono usati due utenti differenti:

  • un utente oc_manager che viene utilizzato per creare i database ed eseguire operazione di struttura (creazione, modifica e cancellazione di tabelle, viste, colonne ecc ecc)

  • un utente oc_user che viene utilizzato dall’applicativo per effettuare operazioni sui dati

Attualmente nella nostra infrastruttura la creazione del database avviene con l'utente oc_manager, a partire da un template preimpostato con i privilegi necessari.

Creazione del template

Come utente postgres si crea il database impostando i privilegi di default che verranno assegnati a tutti gli oggetti creati in seguito.

A postgres verranno dati privilegi ampi, mentre a oc_user verranno dati privilegi minimali:

CREATE DATABASE _oc ENCODING='utf8' CONNECTION LIMIT=50;
ALTER DEFAULT PRIVILEGES FOR USER oc_manager IN SCHEMA "public" GRANT ALL ON TYPES TO postgres WITH GRANT OPTION;
ALTER DEFAULT PRIVILEGES FOR USER oc_manager IN SCHEMA "public" GRANT ALL ON FUNCTIONS TO postgres;
ALTER DEFAULT PRIVILEGES FOR USER oc_manager IN SCHEMA "public" GRANT ALL ON SEQUENCES TO postgres;
ALTER DEFAULT PRIVILEGES FOR USER oc_manager IN SCHEMA "public" GRANT ALL PRIVILEGES ON TABLES TO postgres;
ALTER DEFAULT PRIVILEGES FOR USER oc_manager IN SCHEMA "public" GRANT USAGE ON TYPES TO oc_user;
ALTER DEFAULT PRIVILEGES FOR USER oc_manager IN SCHEMA "public" GRANT EXECUTE ON FUNCTIONS TO oc_user;
ALTER DEFAULT PRIVILEGES FOR USER oc_manager IN SCHEMA "public" GRANT ALL ON SEQUENCES TO oc_user;
ALTER DEFAULT PRIVILEGES FOR USER oc_manager IN SCHEMA "public" GRANT SELECT,INSERT,UPDATE,DELETE ON TABLES TO oc_user;
CREATE EXTENSION IF NOT EXISTS postgis;

In seguito la proprietà del database viene assegnata ad oc_manager

ALTER DATABASE _oc OWNER TO oc_manager;
ALTER SCHEMA public OWNER TO oc_manager;

Per creare database come utente postgres gli viene dato il ruolo oc_manager

GRANT oc_manager TO postgres;

Creazione di un nuovo database

Per creare il database e impostarne subito come owner l'utente corretto, si esegue come utente postgres

CREATE DATABASE oc_firenze TEMPLATE = _oc OWNER='oc_manager' ENCODING='utf8' CONNECTION LIMIT=50;Some code

Sistema di protocollazione (registry)

Il sistema di protocollazione ha invece bisogno di un singolo database con le seguenti caratteristiche:

Postgres >= 11

Creazione di un nuovo utente

CREATE USER registry_user WITH PASSWORD 'password';

Tuning di alcuni parametri di connessione (questi comandi sono opzionali ma suggeriti da django)

ALTER ROLE registry_user SET client_encoding TO 'utf8';
ALTER ROLE registry_user SET default_transaction_isolation TO 'read committed';
ALTER ROLE registry_user SET timezone TO 'UTC';

Creo il database e lo assegno all'utente

CREATE DATABASE oc_registry ENCODING='utf8' CONNECTION LIMIT=50;
GRANT ALL PRIVILEGES ON DATABASE oc_registry TO registry_user;

Configurazione dei proxy di pagamento

Per garantire una corretta comunicazione tra l'area personale e i proxy di pagamento è necessaria configurare appropriatamente gli endpoint sul proxy di riferimento.

Configurazione dei CORS

Il requisito di base affinchè questi endpoint siano correttamente funzionanti è che siano correttamente racchiuse all'interno di un middleware che gestisca i CORS in maniera adeguata. In particolare questo middleware dovrà essere configurato come segue:

HeaderValore

Access-Control-Allow-Credentials

true

Access-Control-Allow-Headers

Authorization,Origin,Content-Type,Accept,access-control-allow-headers,access-control-allow-methods,access-control-allow-origin,access-control-allow-credentials,Referer

Access-Control-Allow-Methods

GET,POST,HEAD,PUT,DELETE,PATCH,OPTIONS

Access-Control-Allow-Origin-List

*

Access-Control-Max-Age

100

Add-Vary-Header

true

Di seguito un esempio di configurazione del middleware:

traefik.http.middlewares.cors.headers.accesscontrolallowcredentials: 'true'
traefik.http.middlewares.cors.headers.accesscontrolallowheaders: 'Authorization,Origin,Content-Type,Accept,access-control-allow-headers,access-control-allow-methods,access-control-allow-origin,access-control-allow-credentials,Referer'
traefik.http.middlewares.cors.headers.accesscontrolallowmethods: 'GET,POST,HEAD,PUT,DELETE,PATCH,OPTIONS'
traefik.http.middlewares.cors.headers.accesscontrolalloworiginlist: '*'
traefik.http.middlewares.cors.headers.accesscontrolmaxage: 100
traefik.http.middlewares.cors.headers.addvaryheader: 'true'

Endpoint interni (non necessita di CORS)

L'unico endpoint interno in questo caso è quello per richiedere l'aggiornamento dello stato di un pagamento. Per garantire quindi che questo venga chiamato solo ed esclusivamente dal microservizio di polling, è necessario che questo venga configurato come endpoint raggiungibile solo internamente alla rete di docker. Di seguito un esempio:

 networks:
 backplane-sdc:
   external: true
 internal:
 traefik-sdc:
   external: true
 swarm-backplane:
   external: true

...
 networks:
   - traefik-sdc
   - backplane-sdc
 deploy:
   <<: *deploy-snippet
   labels:
     traefik.enable: 'true'
     traefik.docker.network: 'traefik-sdc'
     traefik.http.routers.silfi-proxy-qa-internal.entrypoints: 'backplane'
     traefik.http.routers.silfi-proxy-qa-internal.rule: 'Host(`silfi-proxy-qa.boat-backplane.opencontent.io`) && PathPrefix(`/payment-proxy/silfi/update/{id:[^/]+}`) && METHOD(`GET`, `HEAD`, `OPTIONS`)'

Endpoint pubblici

Gli endpoint pubblici sono:

  • Pagamento online (/online-payment/{payment_id})

  • Download dell'avviso cartaceo (/offline-payment/{payment_id})

  • Download della ricevuta telematica (/receipt/{payment_id})

  • Ritorno alla area personale (detta anche landing url) (/landing/{payment_id})

  • Form schema per la configurazione del tenant (/tenants/schema)

  • Form schema per la configurazione del servizio (/services/schema)

  • Documentazione Swagger delle API (/docs, facoltativo)

  • Status (/status, facoltativo)

  • Metriche di monitoraggio (/metrics, facoltativo)

Di seguito un esempio di configurazione degli endpoint

traefik.http.routers.silfi-proxy-qa.entrypoints: 'web, websecure'
traefik.http.routers.silfi-proxy-qa.rule: 'Host(`api.qa.stanzadelcittadino.it`) && PathPrefix(`/payment-proxy/silfi/{op:(notice|online-payment|receipt|landing)}/{id:[^/]+}`, `/payment-proxy/silfi/{op:(docs|status|metrics)}`, `/payment-proxy/silfi/{op:(tenants|services)}/schema`) && METHOD(`GET`, `POST`, `PUT` ,`PATCH`, `DELETE`, `HEAD`, `OPTIONS`)'
traefik.http.routers.silfi-proxy-qa.tls: 'true'
traefik.http.routers.silfi-proxy-qa.tls.certresolver: 'mydnschallenge'
traefik.http.routers.silfi-proxy-qa.middlewares: 'goodheaders, cors'

Endpoint protetti

Gli endpoint protetti da autenticazione sono:

  • Inserimento, recupero, modifica e cancellazione della configurazione del tenant (/tenants/{id})

  • Inserimento, recupero, modifica e cancellazione della configurazione del servizio (/services/{id})

Di seguito un esempio di configurazione degli endpoint:

# Quando pronti aggiungere il middleware 
# sdc-qa-jwt-decode
traefik.http.routers.silfi-proxy-qa-protected.entrypoints: 'web, websecure'
traefik.http.routers.silfi-proxy-qa-protected.rule: 'Host(`api.qa.stanzadelcittadino.it`) && PathPrefix(`/payment-proxy/silfi/{op:(tenants|services)}`, `/payment-proxy/silfi/{op:(tenants|services)}/{id:[^/]+}`) && METHOD(`GET`, `POST`, `PUT` ,`PATCH`, `DELETE`,>
traefik.http.routers.silfi-proxy-qa-protected.tls: 'true'
traefik.http.routers.silfi-proxy-qa-protected.tls.certresolver: 'mydnschallenge'
# MIDDLEWARE ABILITATO PER CONTROLLO JWT
# traefik.http.routers.silfi-page-proxy-qa-protected.middlewares: 'sdc-qa-jwt-decode, goodheaders, cors'
traefik.http.routers.silfi-proxy-qa-protected.middlewares: 'goodheaders, cors'

Last updated

Logo

Documentazione Opencity Italia