Introduzione

Licenza

Gfdl

La presente documentazione è distribuita secondo i termini della GNU Free Documentation License Versione 1.3, 3 novembre 2008.

Ne è garantita assoluta libertà di copia, ridistribuzione e modifica, sia a scopi commerciali che non.

L'autore detiene il credito per il lavoro originale ma nè credito nè responsabilità per le modifiche.

Qualsiasi lavoro derivato deve essere conforme a questa stessa licenza.

Il testo pieno della licenza è a:

https://www.gnu.org/licenses/fdl.txt

Architettura Web

Abbiamo dovuto attendere circa il 1990 prima della vera rivoluzione democratica di Internet: il World Wide Web.

Il Prof. Tim Bernars-Lee del CERN di Ginevra ci ha dato un prodotto costituito da:

  • un server per la distribuzione a richiesta di pagine Web
  • il linguaggio di redazione delle Pagine Web: HTML - Hyper Text Markup Language
  • la possibilità di collegare pagine ad altre pagine, anche su altri server, tramite degli Hyperlink
  • un client per accedere al server, in realtà sviluppato più da altri che non dall'originatore iniziale
  • il protocollo di comunicazione tra il client e il server: HTTP - Hyper Text Transfer Protocol

HTTP si è naturalmente molto evoluto, tecnicamente e socialmente, dagli albori, come tutto il mondo WWW.

HTTP ha una notevole importanza moderna come protocollo di accesso ad un servizio distribuito in rete, con tecnologie come Docker, Kubernetes, e altre simili.

Protocollo HTTP

Il protocollo applicativo Hyper Text Transfer Protocol (HTTP) interscambia messaggi in codice ASCII a 7 bit tra un client e un server del protocollo.

I messaggi sono conformi ad uno standard TCP/IP, definito da una serie di documenti Request For Comments (RFC).

Struttura di un Messaggio

Ogni messaggio interscambiato ha:

  • Testata del Messaggio
  • Linea vuota separatrice
  • Corpo del Messaggio - non sempre presente

Http-msg

Vi sono due tipi di messaggio:

  • Richiesta - dal client al server
  • Responso - dal server al client

Richiesta

La prima linea ha la struttura:

  • Metodo - il tipo di processamento usato
  • Risorsa - l'oggetto su cui il metodo opera
  • HTTP/versione - l'identificativo del protocollo

Le linee seguenti sono headers della richiesta. Possono essere molte linee, una per ciascuno.

Uno header ha la struttura:

  • Nome - singola parola, eventualmente contenente trattini separatori se più parole
  • Due punti
  • Valore - singolo, o più valori separati da virgole

Gli header consentiti sono tanti, parte dello standard. Server individuali possono avere un numero ridotto o esteso degli header accettati.

Responso

La prima linea ha la struttura:

  • Codice di Stato
  • Tipo Evento - corrispondente al codice

Seguono attributi, per la maggior parte informativi.

Gli attributi del responso hanno la stessa struttura sintattica di quelli della richiesta.

Tra alcuni attributi della richiesta e corrispondenti attributi del responso, è implementata una negoziazione di comportamenti, opzioni o features che il server può offrire.

Esempio di responso di successo:

Resp01

Esempio di responso indicante fallimento:

Resp02

Risorsa

Struct-uri

E' l'identificativo di un oggetto su cui il server opera.

Formalmente, una URI (Uniform Resource Identifier) è l'intera stringa emessa dal browser, ed è divisa in due parti:

  • la URL (Uniform Resource Locator), che indica come raggiungere e collegarsi al server
  • la URN (Uniform Resource Name), la stringa che il server riceve nella prima linea della richiesta

Path

Nel caso più comune la parte path di un URN currisponde al percorso di un file al di sotto della directory principale di servizio pagine del server, la DocumentRoot.

La parte path di un URN non necessariamente corrisponde al percorso di un file, ma può essere un alias.

Il server trasforma il path iniziale in una stringa finale, detta route.

Un route specifico invoca uno handler, una funzione specifica che la elabora, o un programma esterno che viene in tal caso invocato ed esegue. Tale funzione o programma riceve come argomenti la richiesta in read-only e il responso in read-write. Legge, interpreta ed usa la richiesta, scrive il responso.

Query

I dati della richiesta possono essere:

  • presenti nel URN della testata, come parte query, se il metodo è GET
  • presenti nel corpo se il metodo è POST

Se i dati sono nella testata (GET) seguono le regole di costruzione stringa dette URL Encoding:

  • = assegna un valore ad una variabile
  • & separa due assegnazioni
  • + rappresenta uno spazio
  • qualsiasi altro carattere è rappresentato come %xx ove xx è il codice esadecimale del carattere nella tabella US ASCII

Se i dati sono nel corpo della richiesta, possono essere in un formato qualsiasi, naturalmente compreso dallo handler.

Un formato di rappresentazione dati molto usato è JSON - JavaScript Object Notation.

Fragment

Non è inviato al server ma processato dal client, in vari modi, spesso ollegati col tipo MIME della richiesta.

Metodi HTTP

MetodoAzione
GETTrasferisce in download nel corpo la risorsa specificata dal server al client
HEADResponso identico a GET nello header, ma senza il dowload dell'oggetto
POSTTrasferisce in upload il contenuto del corpo ad un programma di processamento sul server
PUTTrasferisce in upload il contenuto del corpo alla risorsa specificata, creandola o rimpiazzandola
DELETECancella dal server la risorsa specificata
CONNECTCrea un tunnel tramite un proxy server con una destinazione finale specificata come risorsa
OPTIONSRichiede i metodi disponibili per la risorsa specificata
TRACECompie un echo della richiesta nel responso, per debugging
PATCHCompie una modifica parziale alla risorsa, con le istruzioni contenute nel corpo

Esempio di Metodi HTTP

Richiesta GET

Http-get

Richiesta HEAD

Http-head

Richiesta PUT

Http-put

Molti server non implementano la richiesta PUT: Problemi di Sicurezza

Codici di Stato HTTP

Mantenuti da IANA (Internet Assigned Numbers Authority) sulla base di vari documenti RFC.

La prima cifra del codice indica il tipo di evento verificatosi:

CodiceTipo EventoSpiegazione
1xxInformational ResponseRichiesta ricevuta e il processamento continua
2xxSuccessLa richiesta è stata accettata
3xxRedirectionRichieste ulteriori azioni per completare la richiesta
4xxClient ErrorRa richiesta contiene errori e non viene accettata
5xxServer ErrorLa richiesta è valida ma il server non riesce ad eseguirla per varie ragioni

La lista completa di codici di stato è lunga e qui vengono listati i codici più comuni:

CodiceTipo EventoSpiegazione
200OkRichiesta completata con successo e viene ritornato l'oggetto richiesto
201CreatedUn oggetto è stato creata con successo
202AcceptedRichiesta accettata ma non ancora processata
204No ContentRichiesta completata con successo, ma non è richiesto on oggetto di ritorno
301Moved PermanentlySpecifica una URL a cui la richiesta deve sempre rivolgersi
302Found (Moved Temporarily)Specifica una URL a cui la richiesta deve rivolgersi questa singola volta
400Bad RequestRichiesta mal formata
401UnauthorizedNon è stata fornita autenticazione
403ForbiddenAutenticazione errata o azione proibita dalle policy
404Not FoundLa risorsa richiesta non esiste
405Method Not AllowedMetodo di richiesta errato
406Not AcceptableNessun metodo di negoziazione contenuto è disponibile
407Proxy Authentication RequiredOccorre prima l'autenticazione col proxy
408Request TimeoutTroppo tempo per terminare la richiesta
414URI Too LongLa URL di un GET è troppo lunga ed occorre usare un POST
417Expectation FailedSpecifico tipo dello header Expect non è disponibile
418I'm A TeapotUovo di Pasqua a volte usato invece di 403 Forbidden
429Too Many RequestsEcceduto il limite quando vi è un meccanismo di rate limiting
451Unavailable For Legal ReasonsAccesso negato per motivi legali (da Fahrenheit 451)
500Internal Server ErrorErrore generico del server, non specificato
501Not ImplementedIl server non conosce correntemente come processare la richiesta
502Bad GatewayIl server è un proxy e non riesce a contattare la destinazione
503Service UnavailableIndisponibilità temporanea del servizio
504Gateway TimeoutIl server è un proxy ed è andato in timeout

Vi sono svariati altri codici ufficiali del protocollo.

Vi sono anche codici non ufficiali, usati in specifiche implementazioni del server. Per esempio:

CodiceTipo EventoServerSpiegazione
218This is fineApacheErrore in combinazione col settaggio ProxyErrorOverride
420Method FailureSpringFallimento di un metodo Java invocato
430Shopify Security RejectionShopifyErrore di sicurezza
440Login Time-outMicrosoftSessione scaduta
450Blocked by Windows Parental ControlsMicrosoftBloccato dal controllo parentale
494Request header too largeNginxLa testata della richiesta è troppo grossa
495SSL Certificate ErrorNginxIl certificato cliente è invalido
496SSL Certificate RequiredNginxOccorre un certificato cliente
497HTTP Request Sent to HTTPS PortNginxProtocollo di richiesta errato
509Bandwidth Limit ExceededApacheSuperato il limite di banda

La Specifica CGI

Common Gateway Interface:

  • Il client invia dati al server
    • Dati spesso generati da un FORM
  • Il Server attiva un programma esterno
    • Il programma riceve in input i dati del client
    • Metodi GET e POST
  • Il programma esegue
    • p.es. Query di Database
  • Il programma genera in output una pagina HTML
  • La pagina viene inviata al client

Il Metodo GET

Cgi-get

Il Metodo POST

Cgi-post

Server Side Includes

Comandi inseriti nel testo HTML, scanditi ed eseguiti dal server prima dell’invio pagina:

  • Inclusi in commenti e preceduti da ‘#’
  • Il server deve tipicamente essere abilitato a scandire le pagine contenenti SSI
  • Le pagine contenenti SSI possono avere l'estensione .shtml

Esempi:

Http-ssi

Server e Client per il Web

Servers

Server Dedicati

Questa piccola lista enumera solo servers disponibili con una licenza Open Source, garanzia di qualità e trasparenza.

Apache Web Server

Apache-logo

Scritto in Linguaggio C. Sorgenti disponibili sul sito di Apache e su https://github.com/apache/httpd.

Nato circa nel 1995, storicamente il più usato.

Solido e potente, supporta ogni feature del mondo Web.

Più del 40% dei siti mondiali.

Nginx

Nginx-logo

Pronunciato Engine-ex.

Scritto in Linguaggio C. Sorgenti disponibili, p.es. su https://github.com/nginx/nginx.

Sviluppato dal 2002, originariamente da Igor Sysoev, quindi passato ad una comunità di sviluppatori volontari.

Circa il 30% dei siti mondiali.

Apache Tomcat

Tomcat-logo

Scritto in Java. Codice donato alla Apache dalla Sun Microsystem. Sorgenti disponibili su, p.es. https://github.com/apache/tomcat.

La sua funzione primaria è come contenitore di oggetti servlet dell'universo Java. Funziona benissimo anche come normale server web.

Circa l'1% dei siti mondiali.

Lighttpd

Lighttpd-logo

Pronunciato Lightly.

Scritto in Linguaggio C. Sorgenti della versione 2 disponibili, p.es. su https://github.com/lighttpd/lighttpd2.

Leggero e con meno requisiti di risorse degli altri.

Non così completo come altri servers.

Circa lo 0,1% dei siti mondiali.

NOTA

Il motivo per cui la somma d'uso dei server elencati non si avvicina al 100% è che vi sono anche server non Open Source.

Server Programmati

La maggior parte dei linguaggi di programmazione permette di costruire dei server che gestiscono il protocollo HTTP e altri aspetti del mondo Web.

La complessità, e le corrispondenti prestazioni e possibilità operative variano enormemente.

Una menzione va a Node.js.

Node.js

Nodejs-logo

E' in essenza un linguaggio di programmazione JavaScript lato server.

Consente quindi di redigere interi server web dal comportamento dinamico.

La sua principale limitazione, oltre che usare il JavaScript, è l'azzenza totale di parallelismo: è single-threaded di natura.

Clients

Browsers Grafici

Il primo browser grafico prodotto fu Viola, di Pei-Yuan Wei di Taiwan, e basato sull'innovativo sistema grafico X Window. L'autore lavorava sul concetto di hyperlink, con spunti da Apple Hypercard, e si dice che in realtà sia stato lui ad inventare il concetto di World Wide Web, invece di Tim Berners-Lee.

Marc Andreesen, nuovo impiegato del National Center for Supercomputing Applications (Urbana, Illinois), sviluppò a partire dai primi anni '90 il browser grafico, Mosaic.

Mosaic

Questo rese il nuovo ambiente WWW molto usabile da un utente comune e determinò il rapido successo del web.

Ora non più in uso, Mosaic si è presto evoluto in un nuovo (per allora) prodotto, proprietario ma gratuito, Netscape Navigator.

Netscape

La Netscape ha introdotto per il suo Navigator, e per i server web, il protocollo Secure Socket Layer.

Navigator si è successivamente evoluto e gli è stato dato il nome del progetto originale di Marc Andreesen, Mozilla. Mozilla è diventata un'entità giuridica indipendente, ed ha prodotto il moderno browser Firefox.

Ad oggi di browser grafici ve ne è un numero considerevole, tra cui:

  • Mozilla Firefox
  • Google Chrome
  • Brave
  • Vivaldi
  • Opera
  • Apple Safari
  • Microsoft Edge

Command Line Interface

Comandi da terminale, non grafici, che compiono un collegamenteo al server web col protocollo HTTP.

Il primo browser a caratteri si chiamava letteralmente www, inventato da Tim Berners-Lee stesso, ed era molto primitivo.

Altri notevoli browser a caratteri furono Lynx per i primi Linux, e MacWWW (Samba) per gli antichi Apple MacIntosh.

Vi è oggi una rinascita del CLI, sia per interazioni batch, p.es. comandi inseriti in procedure shell, sia con la diffusione di Docker e Kubernetes che mal supportano ambienti grafici.

Queste utility sono spesso usate nel testing e debugging di API REST basate su server web.

wget

Usato tipicamente per il download in modo batch di file rappresentati da una URL.

Spesso usato come web crawler, per lo scraping (download ricorsivo) di interi siti web.

curl

Un vero è proprio coltellino svizzero, usato non solo per interazioni HTTP, ma praticamente qualsiasi protocollo applicativo TCP/IP basato sull'interscambip di messaggi ASCII.

HTTPie

Comando: http. Numerose opzioni.

Flessibile nell'inviare ogni tipo di richiesta HTTP. Interazione molto amichevole.

Docker

Installazione di Docker

Dockinst

Su Hardware o su VM VirtualBox, requisiti:

  • Linux Ubuntu o simile (p.es. Mint), recente, 64 bit
  • 4 GB RAM
  • 100+ GB HD
  • Connessione a Internet

Ultima Versione dal Repository Ufficiale

Installare dal repository di docker.io.

Versione più recente, e permette la successiva estensione con plugins.

Occorre configurare la locazione del repository ed scaricare il necessario certificato. Questo si compie al meglio, preparando la seguente procedura shell, docker-repo.sh:

vim ~/docker-repo.sh
#! /bin/bash
# Add Docker's official GPG key:
sudo apt-get update
sudo apt-get install ca-certificates curl gnupg
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
sudo chmod a+r /etc/apt/keyrings/docker.gpg
# Add the repository to Apt sources:
echo \
  "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \
  $(. /etc/os-release && echo "$UBUNTU_CODENAME") stable" | \
  sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt-get update
# Install the Docker packages:
sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

Rendere la procedura eseguibile e lanciarla:

chmod +x ~/docker-repo.sh
cd
./docker-repo.sh

Important NOTA

Noi installiamo la release di Docker nel modo appena descritto, prendendola dal repository di docker.io.

Dopo l'installazione

Solo i membri del gruppo docker possono usare l'ambiente.

Configurare l’utente corrente:

sudo usermod -aG docker $USER

Reboot del sistema. In teoria un relogin è sufficiente, ma la VM Ubuntu vuole proprio un reboot

Test:

docker info

Package dal Repository di Release Linux

Alternativa al metodo precedente, In Ubuntu o simili:

sudo apt upgrade
sudo apt install docker.io

Questa versione di Docker così installata può essere non molto recente.

Important NOTA

Noi NON installiamo Docker dal repository di release di Linux.

Docker Compose

Docker Compose è uno strumento per l’orchestrazione di più contenitori sulla stessa macchina host

Installare Docker Compose su Ubuntu:

sudo curl -SL https://github.com/docker/compose/releases/download/v2.23.3/docker-compose-linux-x86_64 -o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose
docker-compose version

Esiste anche una versione pacchettizzata di release, installabile con:

sudo apt install docker-compose

Ma è una versione relativamente vecchia, e sconsigliata.

Important NOTA

Noi non abbiamo bisogno di installare Docker Compose col procedimento sopra descritto. La release di Docker che abbiamo scaricato dal repository di docker.io include già un plugin per Docker Compose.

Nuova Versione di Docker Compose

Il Docker Compose originale è una utility sviluppata da un gruppo indipendente da Docker.io

  • scritta in Python
  • comando con trattino separatore
    • docker-compose comando [opzioni] [argomenti]

Docker.io ha di recente aggiunto la sua versione di Docker Compose scritta in Go e parte del comando docker

  • comando senza trattino separatore
    • docker compose comando [opzioni] [argomenti]
  • La sintassi non è cambiata
  • Usano entrambi il file docker-compose.yml

Installazione del Plugin

Docker Compose è un plugin scaricabile dal repository software di Docker.io.

Se è stata installata l'ultima versione di Docker tramite il repository della docker.io, allora il plugin Docker Compose è già installato.

Altrimenti si può scrivere la procedura shell docker-compose.sh:

#! /bin/bash
# Add Docker's official GPG key:
sudo apt-get update
sudo apt-get install ca-certificates curl gnupg
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
sudo chmod a+r /etc/apt/keyrings/docker.gpg
# Add the repository to Apt sources:
echo \
  "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \
  $(. /etc/os-release && echo "$UBUNTU_CODENAME") stable" | \
  sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt-get update
# Install the Docker Compose plugin:
sudo apt install docker-compose-plugin

Renderla eseguibile e lanciarla. Questo installa il plugin.

Important NOTA

Noi non abbiamo bisogno di installare il plugin per Docker Compose.

Web Server Apache

Il Server Web Apache è presente come software da alcune decine di anni ed è ancora uno dei server web più usati.

Naturalmente si è evoluto nel tempo.

La versione corrente è la 2.4.x.

E' allineata con i seguenti componenti ausiliari:

  • TLS (Transport Layer Security) 1.3
  • OpenSSL 3.1.1

Ci sono per Linux due versioni di Apache, nella stessa release:

  • versione Debian
    • installata su Ubuntu, Mint, ecc.
    • la struttura della configurazione è distribuita in più directory e file
    • supporta più host virtuali contemporaneamente (più servers)
  • versione Unix generico
    • usata in docker Alpine Linux, ecc.
    • la struttura della documentazione è molto più piatta
    • è intesa per un solo server

Entrambe le versioni sono funzionalmente equivalenti.

Dal punto di vista amministrativo, gli stessi parametri di configurazione sono spesso in files diversi.

Moduli

Apache ha una parte core e svariati moduli opzionali, che vengono caricati nello spazio di indirizzamento del programma e aggiungono funzionalità.

Sono moduli statici, la variazione di configurazione dei moduli richiede un restart del programma.

Altri programmi modulari, p.es. il Kernel Linux, usano moduli dinamici o pluggable modules che possono essere caricati o scaricati dallo spazio di indirizzamento a runtime, senza interrompere il programma.

In generale l'intera configurazione di Apache è statica, viene letta allo start del programma. La variazione di configurazione richiede quasi sempre un restart del server Apache.

Piattaforme

Apache è disponibile per Windows, Mac, Linux ed altri sistemi operativi. Esamineremo qui il caso Libux.

Lo Apache Web Server moderno, viene in due varianti maggiori:

  • versione Debian/Ubuntu - installata di default su piattaforme hardware e macchine virtuali
    • disponibile per tutte le distribuzioni Linux
    • completa e complessa
    • installata con apt install apache2
    • gestita come servizio standard di systemctl, service o init
  • versione Unix - preferita dalle immagini Docker
    • ridotta a pochi componenti indispensabili
    • eventualmente estendibile con pacchetti aggiuntivi
    • controllata con comandi manuali, httpd o apachectl

Vista l'importabza del mondo Docker negli applicativi distribuiti moderni, è necessario considerare entrambe le versioni.

Hanno metodi e comandi di amministrazione diversi, anche se la filosofia di base è la stessa.

Le versioni Docker possono mancare di alcuni features più complessi della versione HW/VM.

La differenza si nota nell'organizzazione della directory di configurazione.

Configurazione Debian/Ubuntu

Installata da gestione pacchetti di sistema.

Configurazione in: /etc/apache2.

Struttura:

.
├── apache2.conf
├── conf-available
│   ├── ...
│   └── serve-cgi-bin.conf
├── conf-enabled
│   ├── ...
│   └── serve-cgi-bin.conf -> ../conf-available/serve-cgi-bin.conf
├── envvars
├── magic
├── mods-available
│   ├── ...
│   └── xml2enc.load
├── mods-enabled
│   ├── ...
│   └── status.load -> ../mods-available/status.load
├── ports.conf
├── sites-available
│   ├── 000-default.conf
│   └── default-ssl.conf
└── sites-enabled
    └── 000-default.conf -> ../sites-available/000-default.conf

Controllato con apache2ctl, ma restart da systemctl o altro gestore di servizi.

Gestione dei moduli con comandi di abilitazione e disabilitazione.

Configurazione Docker Alpine

Immagine: httpd:2.4-alpine.

Configurazione in: /usr/local/apache2.

Struttura:

.
|-- bin
|   |-- ab
|   |-- apachectl
|   |-- apxs
|   |-- checkgid
|   |-- dbmmanage
|   |-- envvars
|   |-- envvars-std
|   |-- fcgistarter
|   |-- htcacheclean
|   |-- htdbm
|   |-- htdigest
|   |-- htpasswd
|   |-- httpd
|   |-- httxt2dbm
|   |-- logresolve
|   |-- rotatelogs
|   `-- suexec
|-- build
|-- cgi-bin
|-- conf
|   |-- extra
|   |   |-- httpd-autoindex.conf
|   |   |-- httpd-dav.conf
|   |   |-- httpd-default.conf
|   |   |-- httpd-info.conf
|   |   |-- httpd-languages.conf
|   |   |-- httpd-manual.conf
|   |   |-- httpd-mpm.conf
|   |   |-- httpd-multilang-errordoc.conf
|   |   |-- httpd-ssl.conf
|   |   |-- httpd-userdir.conf
|   |   |-- httpd-vhosts.conf
|   |   `-- proxy-html.conf
|   |-- httpd.conf
|   |-- magic
|   |-- mime.types
|   `-- original
|-- error
|-- htdocs
|-- icons
|-- include
|-- logs
`-- modules

File di configurazione principale: /usr/local/apache2/conf/httpd.conf.

Si aggiungono configurazioni di virtual hosts, proxy, ecc. ponendole in: /usr/local/apache2/conf/extra.

Controllato con: apachectl.

Configurazione Docker Ubuntu

Immagine: httpd:2.4.

Più pesante dell'immagine Alpine. Mancano numerose utilities come ip, ping, vi, ecc. - ma sono installabili.

Configurazione in /usr/local/apache2.

Struttura uguale a quella di Docker Alpine.

Controllato con: apachectl.

Configurazione Manuale su Docker Alpine

Lanciare un contenitore Alpine e collegarsi:

docker run -d --name alp alpine sleep 1000000
docker exec -ti alp sh

Installare menualmente Apache di base:

apk add apache2 apache2-utils

Immagine molto leggera.

Configurazione in: /etc/apache2.

Struttura:

.
├── conf.d
│   ├── default.conf
│   ├── info.conf
│   ├── languages.conf
│   ├── mpm.conf
│   └── userdir.conf
├── httpd.conf
├── magic
└── mime.types

Si possono aggiungere pacchetti che ne estendono le funzionalità.

File di configurazione principale: /etc/apache2/httpd.conf

Si aggiungono configurazioni di virtual hosts, proxy, ecc. ponendole in: /etc/apache2/conf.d.

I moduli si gestiscono a mano commentandoli o scommentandoli nel file di configurazione principale.

Controllato con httpd, incluso i restart.

Installazione

Installazione su Linux Ubuntu o Linux Mint

Aggiornamento delle cache del software:

sudo apt update

Installazione di Apache:

sudo apt install apache2

L'installazione automaticamente abilita il servizio al prossimo reboot e lo fa partire immediatamente. Controllare lo stato del servizio con:

sudo systemctl status apache2
● apache2.service - The Apache HTTP Server
     Loaded: loaded (/lib/systemd/system/apache2.service; enabled; vendor preset: enabled)
     Active: active (running) since Wed 2024-03-06 14:54:49 CET; 57s ago
       Docs: https://httpd.apache.org/docs/2.4/
   Main PID: 2825 (apache2)
      Tasks: 55 (limit: 5137)
     Memory: 4.9M
        CPU: 58ms
     CGroup: /system.slice/apache2.service
             ├─2825 /usr/sbin/apache2 -k start
             ├─2829 /usr/sbin/apache2 -k start
             └─2830 /usr/sbin/apache2 -k start

Mar 06 14:54:49 apache systemd[1]: Starting The Apache HTTP Server...
Mar 06 14:54:49 apache apachectl[2824]: AH00558: apache2: Could not reliably determine the server's>
Mar 06 14:54:49 apache systemd[1]: Started The Apache HTTP Server.

Il servizio è di stile nowait: vengono lanciati un numero di processi figli, pronti a ricevere e gestire connessioni da client.

Il numero di processi figli varia dinamicamente col numero statistico di richieste da un minimo a un massimo, entrambi configurabili.

Controllo di Apache

Controllo tramite systemd

In un Linux completo il servizio è gestito da systemd, col comando systemctl:

  • sudo systemctl start apache2 - lancia il servizio
  • sudo systemctl stop apache2 - ferma il servizio
  • sudo systemctl enable apache2 - abilita il servizio, cioè lo lancia automaticamente al prossimo boot
  • sudo systemctl disable apache2- disabilita il servizio, cioè NON lo lancia automaticamente al prossimo boot
  • sudo systemctl status apache2 - da un rapporto sullo stato del servizio

Controllo tramite apache2 o apache2ctl

In un sistema ove systemd è assente, il server apache si può controllare col comando apache2.

La sintassi si ottiene chiedendo aiuto:

apache2 -h
Usage: apache2 [-D name] [-d directory] [-f file]
               [-C "directive"] [-c "directive"]
               [-k start|restart|graceful|graceful-stop|stop]
               [-v] [-V] [-h] [-l] [-L] [-t] [-T] [-S] [-X]

Si può usare questo comando per avere la versione di Apache:

apache2 -v
Server version: Apache/2.4.52 (Ubuntu)
Server built:   2023-10-26T13:44:44

Esiste anche il comando apache2ctl, che ha le stesse funzionalità di controllo di apache2.

Il comando apache2ctl, a differenza di apache2, legge le variabili d'ambiente di Apache. In molti casi è da preferirsi.

Verifichiamo l'accessibilità della pagina principale del sito, aprendo un browser a localhost.

La pagina è complessa e informativa.


Installazione su Docker Alpine Linux

Lanciamo un contenitore Alpine:

docker run -d --name alp -p 8888:80 alpine sleep 1000000

Mappiamo la porta 80 del contenitore alla porta 8888 dello host per evitare collisioni con l'installazione precedente di Apache sullo host.

Colleghiamoci al contenitore:

docker exec -ti alp sh

Installiamo Apache:

apk add apache2 apache2-utils

Il comando di controllo è httpd, con la stessa sintassi di apache2 sulla versione Ubuntu.

Questo comando di controllo è indispensabile, poichè il contenitore Alpine non ha systemd.

Lanciamo Apache:

httpd -k start

Da un warning sistemabile in seguito con la variazione di configurazione. Anche la versione Ubuntu da lo stesso warning, ma non è comparso a video, bensi inserito in un log appropriato.

Controlliamo la partenza:

ps wax
PID   USER     TIME  COMMAND
    1 root      0:00 sleep 1000000
   22 root      0:00 httpd -k start
   23 apache    0:00 httpd -k start
   24 apache    0:00 httpd -k start
   25 apache    0:00 httpd -k start
   26 apache    0:00 httpd -k start
   27 apache    0:00 httpd -k start
   29 apache    0:00 httpd -k start
   50 root      0:00 sh
   55 root      0:00 ps wax

Controlliamo la versione:

httpd -v
Server version: Apache/2.4.58 (Unix)
Server built:   Nov 24 2023 15:41:14

Accediamo alla pagina principale con curl:

apk add curl
curl localhost
<html><body><h1>It works!</h1></body></html>

E' molto più semplificata.

Usciamo dal contenitore:

exit

Possiamo accedere dallo host alla pagina web principale offerta dal contenitore Alpine, aprendo un browser alla URL localhost:8888, oppure col comando:

curl localhost:8888

Configurazione Principale

Configurazione della Versione Debian/Ubuntu

La directory di configurazione è /etc/apache2.

La struttura di configurazione è:

/etc/apache2/
├── apache2.conf
├── conf-available
│   └── *.conf
├── conf-enabled
│   └── serve-cgi-bin.conf -> ../conf-available/*.conf
├── envvars
├── magic
├── mods-available
│   └── *.load
├── mods-enabled
│   ├── *.conf -> ../mods-available/*.conf
│   └── *.load -> ../mods-available/*.load
├── ports.conf
├── sites-available
│   └── *.conf
└── sites-enabled
    └── *.conf -> ../sites-available/*.conf

Elementi di Configurazione

File Principale

Il file di configurazione principale è /etc/apache2/apache2.conf.

vim /etc/apache2/apache2.conf

Originariamente questo file di configurazione era molto lungo, ora è stato accorciato e segmenti di configurazione si trovano in sottodirectories di /etc/apache2.

envvars

Prima di leggere il file di configurazione principale, Apache crea delle variabili d'ambiente dal file /etc/apache2/envvars.

vim /etc/apache2/envvars

Non è necessario editare quest'ultimo file a meno di configurazioni diverse dal default.

magic

Contiene l'associazione tra i tipi MIME e i magic numbers di Unix.

Un magic number è l'etichetta composta da alcuni bytes che si trova all'inizio di un file e che ne dichiara il tipo. Unix non usa estensioni del nome, ma magic numbers.

Il tipo MIME (Multimedia Internet Mail Extensions) è il metodo standard Internet per dichiarare un tipo di file.

ports

La specifica delle porte usate dal server.

mods

Lista di tutti i moduli di Apache:

  • mods-available/*.load - moduli disponibili
  • mods-enabled/*.load - moduli abilitati, comando di caricamento del modulo
  • mods-enabled/*.conf - eventuale configurazione di un modulo abilitato

In mods-enabled i files sono dei link simbolici ai corrispondenti files di mods-available.

sites

Siti virtuali configurati:

  • sites-available/*.conf - configurazione di un sito virtuale disponibile
  • sites-enabled/*.conf - configurazione di un sito virtuale abilitato

In sites-enabled i files sono dei link simbolici ai corrispondenti files di sites-available.

Inizialmente vi sono due configurazioni disponibili:

  • 000-default.conf - sito di base HTTP senza SSL
  • default-ssl.conf - configurazione di un sito HTTPS con SSL

Solo il sito di base HTTP è abilitato. HTTPS richiede la fornitura di un certificato.

conf

Segmenti di configurazione.

  • conf-available/*.conf - configurazioni che estendono la capacità del server di base
  • conf-enabled/*.conf - configurazioni abilitate

In conf-enabled i files sono dei link simbolici ai corrispondenti files di conf-available.

Tutte le configurazioni extra disponibili sono abilitate.

Configurazione della Versione Unix/Alpine

Una struttura di configurazione molto più semplice:

/etc/apache2
├── conf.d
│   ├── default.conf
│   ├── info.conf
│   ├── languages.conf
│   ├── mpm.conf
│   └── userdir.conf
├── httpd.conf
├── magic
└── mime.types

Elementi

magic della versione Unix

Associa i tipi MIME ai magic numbers standard di Unix.

mime.types

Associa i tipi MIME alle estensioni dei nomi dei file. Questa versione può essere usata anche su sistemi operativi, come Windows, che non usano magic numbers.

File Principale della versione Unix

E' httpd.conf e contiene molte più informazioni qui aggregate che non il corrispondente file principale dalla versione Ubuntu.

Per esempio qui vi è la lista dei moduli.

conf.d

Directory con un numero limitato di file di configurazione di estensioni, che vengono inclusi in httpd.conf:

  • default.conf - parametri globali del server
  • info.conf - parametri per il route alias /status
  • languages.conf - supporto a lingue oltre l'americano e set di caratteri oltre ASCII
  • mpm.conf - gestione del pool dei servers, o del numero di processi figli generati
  • userdir.conf - settaggi che consentono a sottodirectories di utenti di essere pubblicate dal server web oltre alla DocumentRoot principale

Note

La configurazione della versione Unix/Alpine di Apache è di più semplice comprensione, più tradizionale, e più adattabile a sistemi operativi diversi.

Parametri di Configurazione di Base

Sono qui descritti solo alcuni parametri, i più importanti. I files di configurazione sono peraltro ben commentati, e leggendoli si può comprendere il significato di tutti i parametri.

Questa esposizione segue la struttura della versione Unix. L'equivalente versione Debian mantiene i parametri in altri files. Per scoprire la loro esatta locazione si può compiere una ricerca con grep, per esempio:

cd /etc/apache2
grep -r ServerRoot .

File httpd.conf:

  • ServerRoot /var/www - directory di base di tutti i files gestiti, p.es. anche i logs
  • DocumentRoot "/var/www/localhost/htdocs" - directory di base per le pagine statiche
  • Listen 80 - ascolta sulla porta 80
  • ServerName www.example.com:80 - nome ufficiale del server. Se manca, viene preso allo start l'indirizzo IP del server, e prodotto un warning
  • ServerAdmin you@example.com - posta elettronica dell'amministratore
  • ServerSignature On - aggiunge nel responso uno header con la versione del server e nome del virtual host. Possibilità: On | Off | EMail. Con EMail aggiunge anche un mailto: all'amministratore
  • ServerTokens OS - quantità di Headers di identificazione da usare. Possibilità: Full | OS | Minor | Minimal | Major | Prod. OS: solo concernenti il sistema operativo.

File conf.d/default.conf:

  • Timeout 60 - per il completamento di una richiesta, in secondi
  • KeepAlive On - se i client possono inviare più richieste nella stessa sessione
  • MaxKeepAliveRequests 100 - numero massimo di richieste per sessione
  • KeepAliveTimeout 5 - intervallo in secondi tra richieste successive nella stessa sessione
  • UseCanonicalName Off - se identificarsi con il Fully Qualified Domain Name (FQDN) del DNS [On] o con il ServerName [Off]
  • HostnameLookups Off - se risolvere a DNS l'indirizzo IP dei client delle richieste
  • AccessFileName .htaccess - nome del file di override dei permessi di accesso per la directory corrente

User Directory

Una piattaforma Unix tradizionalmente configura parecchi utenti che vi possono compiere login.

Questo da la possibilità che tali utenti pubblichino le proprie pagine web, sotto la propria directory $HOME/public_html.

Se il nome del server è www.example.com e il nome dell'utente è paperino, la URL per accedere alle pagine web di quell'utente è www.sample.com/~paperino. Questo da il suo file index.html.

Non serve a niente se il server web è in un contenitore Docker.

Configurata nel file conf.d/userdir.conf:

<IfModule userdir_module>

UserDir public_html

<Directory "/home/*/public_html">
    AllowOverride FileInfo AuthConfig Limit Indexes
    Options MultiViews Indexes SymLinksIfOwnerMatch IncludesNoExec
    Require method GET POST OPTIONS
</Directory>

</IfModule>

Lingue ed Encoding

Lingue

Si può cambiare la lingua in cui una pagina HTML è scritta con il tag principale:

<html lang="it">

o in uno span:

<p> French " <span lang="fr"> Bonjour </span> " </p>

o anche tramite uso opportuno di codice JavaScript.

Apache permette di ospitare varianti di un documento in più lingue, aggiungendo un suffisso, p.es. summary.html.fr, summary.html.it, ...

Se non vi è tale suffisso vale la lingua di default.

La selezione della lingua è spesso offerta da una voce di menù del browser. Viene compiuta una negoziazione con opportuni header nella richiesta e nel responso.

Il file conf.d/languages.conf descrive le lingue accettabili al server.

Encoding

Una simile negoziazione avviene per lo encoding ovvero il metodo usato per rappresentare caratteri visivi in sequenze di byte.

I caratteri visivi venivano anche chiamati glifi (glyphs). Lo standard moderno Linux usa lo encoding UTF-8 con numero variabile di bytes. Con vocabolo preso dal Linguaggio Go, il carattere visivo o glifo si chiama runa, ed è diverso dal byte o bytes che lo rappresentano.

L'antico ASCII usa 7 bit per carattere. Le codifiche ISO-8859-x usano 8 bit per carattere; i codici 129-255 sono variabili e permettono in alternativa di rappresentare rune in cirillico, greco, ecc.

Lo standard cinese, introdotto dalla IBM, si chiama Big 5. Il Giappone ha una serie di standard JIS (Japanese Industry Standard). Unicode prometteva di uniformare la situazione ma ha infatti introdotto molte varianti.

Il server Apache supporta molti encodings, i browser solitamente permettono di selezionare quello visualizzato.

Una pagina HTML dichiara lo encoding nella testata, per esempio:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
...

oppure

<!DOCTYPE html>
<html lang="en">
<head>
<meta http-equiv="Content-Type" 
      content="text/html; charset=utf-8">
...

Multi Process Management

Apache usa il metodo no wait di gestione del servizio. Le richieste non sono gestite direttamente dal processo principale, che solo riceve le richieste sul listen port 80.

All'arrivo di una richiesta il server principale la passa ad un processo figlio, nello stesso Cgroup, che apre una porta effimera e prosegue la connessione col client.

La gestione dei processi figli si chiama Multi Process Management (MPM), ed è congigurata nel file conf.d/mpm.conf.

Vi sono più tipi di MPM:

  • prefork MPM
  • worker MPM
  • event MPM
  • NetWare MPM
  • OS/2 MPM
  • WinNT MPM

Il funzionamento dei vari MPM dipende dalle strutture architettoniche del kernel del sistema operativo ospitante.

Quale MPM sia selezionato dipende dall'abilitazione di un modulo anzichè un altro.

Apache su Linux usa prefork MPM nella versione Unix e event MPM nella versione Debian.

Il segmento di controllo di prefork MPM è:

<IfModule mpm_prefork_module>
    StartServers             5
    MinSpareServers          5
    MaxSpareServers         10
    MaxRequestWorkers      250
    MaxConnectionsPerChild   0
</IfModule>
  • StartServers - processi iniziali
  • MinSpareServers - numero minimo dei processi
  • MaxSpareServers - numero massimo dei processi
  • MaxRequestWorkers - numero massimo di richieste per processo
  • MaxConnectionsPerChild - numero massimo di connessioni per processo [0: terminare la connessione e il processo dopo il servizio al client]

Moduli

Lista dei moduli

Versione Debian:

apache2ctl -M

Versione Unix:

httpd -M

La lista da i moduli correntemente caricati nello spazio di indirizzamento del programma.

Vi sono tre tipi di moduli:

  • Static - linkati staticamente nel programma principale
  • Shared - librerie condivise (shared objects) linkati allo start del programma
  • Dynamic - che permettono il caricamento a runtime. Apache non usa questo tipo di moduli.

Abilitazione e Disabilitazione Moduli

Versione Debian

Abilitare un modulo, esempio:

sudo a2enmod rewrite

Disabilitare un modulo, esempio:

sudo a2dismod status

I comandi rispettivamente aggiungono o tolgono un link simbolico nella sottodirectory mods-enabled alla sottodirectory mods-available.

Basta, p.es., rewrite e non mod_rewrite.

Occorre restartare il servizio:

sudo systemctl restart apache2

Versione Unix

Editare il file di configurazione principale:

vi /etc/apache2/httpd.conf

Non c'è sudo nel contenitore alpine. Siamo già root.

Trovare nel file il modulo desiderato.

Togliere o porre il cancelletto (#) a inizio riga per abilitare o disabilitare il modulo.

Restartare Apache:

httpd -k restart

Installazione Moduli

In versione Debian

Debian/Ubuntu possiede un numero elevato di moduli Apache aggiuntivi, nella distribuzione di release.

Per listarli:

sudo apt update
sudo apt search libapache2-mod-

Possono poi venir installati col normale comando sudo apt install ....

In Versione Alpine/Docker

Alpine possiede un numero più limitato di moduli di Apache, sviluppati e contribuiti da terzi.

Listare i moduli disponibili:

apk update
apk search apache | grep mod

Vengono poi installati col comando di installazione pacchetti di Alpine: apk add ....

A volte un ente contribuisce un suo modulo e lo rende disponibile nel suo repository come package. Per averlo dobbiamo aggiungere il repository e la chiave pubblica alla configurazione.

Per esempio:

wget -q https://apk.signalsciences.net/sigsci_apk.pub ; mv sigsci_apk.pub /etc/apk/keys/
echo https://apk.signalsciences.net/3.19/main | tee -a /etc/apk/repositories && apk update

Configurazione Condizionale

La presenza o meno di moduli determina se venga attivato o meno un parametro di configurazione, che ha un modulo come prerequisito, o venga caricato un modulo dipendente.

Per esempio, caricamento di un modulo dipendente:

<IfModule !mpm_prefork_module>
  LoadModule cgid_module modules/mod_cgid.so
</IfModule>
<IfModule mpm_prefork_module>
  LoadModule cgi_module modules/mod_cgi.so
</IfModule>

Esempio di configurazioni dipendenti:

<IfModule unixd_module>

User apache
Group apache

</IfModule>

Quest'ultimo serve per lanciare Apache coi permessi di un utente e di un gruppo particolari, invece di root.

Altro esempio:

<IfModule dir_module>
    DirectoryIndex index.html
</IfModule>

Quando il Path è una directory, viene mostrato questo file in essa contenuta.

Altro esempio:

<IfModule alias_module>
    # Redirect permanent /foo http://www.example.com/bar
    # Alias /webpath /full/filesystem/path
    ScriptAlias /cgi-bin/ "/var/www/localhost/cgi-bin/"
</IfModule>

Directories, Files e Locazioni

Accesso a Directories

Ogni directory coinvolta nelle operazioni del server deve avere uno schema di permessi di accesso.

Lo schema di accesso della directory principale, per esempio, è:

<Directory />
    AllowOverride none
    Require all denied
</Directory>

Dalla directory radice in giù:

  • Require all denied - a tutti è negato l'accesso
  • AllowOverride none - non sono consentite deroghe

Lo schema di accesso alla directory è:

<Directory "/var/www/localhost/htdocs">
    Options Indexes FollowSymLinks
    AllowOverride None
    Require all granted
</Directory>

ove:

  • Require all granted - a tutti è permesso l'accesso
  • Options Indexes FollowSymLinks - gli indici di directory sono abilitati, e i link simbolici vengono seguiti
  • AllowOverride none - non sono consentite deroghe

Una directory più specifica vince su una directory più generica. I parametri di /var/www/localhost/htdocs vincono su quelli di /.

Lo schema di accesso alla directory ove vengono posti programmi invocabili con il Common Gateway Interface è:

<Directory "/var/www/localhost/cgi-bin">
    AllowOverride None
    Options None
    Require all granted
</Directory>

Override

Singole directories possono contenere configurazioni di accesso specifiche, in estensione o sostituzione (Override) di quelle del file principale di configurazione. Il file principale deve però permetterlo, e dichiarare quali overrides sono concessi.

Tali overrides sono nel file .htaccess della directory configurata.

AllowOverride può contenere uno o più dei seguenti argomenti separati da spazio:

  • AuthConfig - configurazioni di autenticazione
  • FileInfo - informazioni sui file
  • Indexes - indici: il GET di una directory ritorna l'indice della directory
  • Limit - lista di metodi HTTP consentiti nella directory
  • Options=opzioni - lista delle opzioni, separate da virgola
  • all - tutto
  • none - niente

Options

Le opzioni possibili sono:

  • Indexes - indici della directory
  • FollowSymLinks - seguire tutti i link simbolici (default in assenza di altri)
  • SymLinksIfOwnerMatch - seguire un link solo se dello stesso utente del file riferito
  • Includes - usare i Server Side Includes (SSI). Richiede il modulo mod_include
  • IncludesNOEXEC - usare gli SSI eccetto exec
  • ExecCGI - eseguire programmi configurati col Common Gateway Interface (CGI). Richiede il modulo mod_cgi
  • MultiViews - negoziazione di contenuto. Richiede il modulo mod_negotiations
  • All - tutte le opzioni tranne MultiViews

Require

Direttiva che implementa le autorizzazioni di accesso ad una risorsa. Richiede i moduli mod_authz_core e mod_authz_host.

La sintassi è flessibile. Alcuni esempi sono:

  • Require all granted - permesso a tutti
  • Require all denied - negato a tutti
  • Require valid-user - permesso ad utenti che si sono autenticati con uno schema di accesso, definito precedentemente in configurazione
  • Require host starshell.sh - qualsiasi compclientuter appartenente al dominio
  • Require not host gov - clients non appartenenti al dominio
  • Require ip 192.168.27 - clients con indirizzo IP in una certa reta
  • Require not ip 192.168.27.11 - clients non col determinati indirizzo IP
  • Require expr %{HTTP_USER_AGENT} != 'BadBot' - espressione che testa un campo header della richiesta

Limitazioni di accesso basate sugli header, indirizzi IP o nomi di dominio non sono sicure. La richiesta può essere contraffatta. L'intero pacchetto IP e payload può essere un forgery, costruito con tecniche e strumenti di packet crafting.

Apache possiede una ricca sintassi di espressioni, con variabili, operatori, costrutti di controllo.

Accesso a Files

Oltre che schemi di accesso per directories, vi sono quelli per protezione di files.

Per esempio:

<Files ".ht*">
    Require all denied
</Files>

Impediscono che i file .htaccess e .htpasswd vengano visti dagli utenti.

Questi sono files usati nel limitare l'accesso a directories solo a utenti che si devono autenticare con username e password.

Il file .htaccess è posto nella directory che si protegge e contiene un override di settaggi di configurazione.

Il file .htpasswd può essere posto nella stessa directory, e contiene nomi utenti e le corrispondenti password in formato hash. Di solito si preferisce porlo fuori da DocumentRoot, in tal modo proteggendolo da accessi automaticamente.

Accesso Autenticato

Directory ad Accesso Limitato

Questo esempio segue i nomi files della versione Unix di Apache, ma per la versione Debian è completamente equivalente.

Nella DocumentRoot creare una directory restricted e una pagina HTML di prova:

cd /var/www/localhost/htdocs
mkdir restricted
cd restricted
vi index.html
<html>
 <head>
  <title>Club Privato</title>
 </head>
 <body bgcolor="white">
  <h1>Ciao</h1>
 </body>
</html>

Autenticazione Basic

Scrivere il file di limitazione accessi nella directory riservata:

vi .htaccess
AuthType        Basic
AuthName        "Restricted Access"
AuthUserFile    /etc/apache2/users

<Limit GET>
require valid-user
</Limit>

Modificare i permessi del file di configurazione globale /etc/apache2/httpd.conf

Nella sezione di protezione della directory corrispondente al DocumentRoot, da:

AllowOverride None

a

AllowOverride AuthConfig Limit

Generare il file dei permessi:

cd /etc/apache2
htpasswd -mc users pippo
Password:pluto
Repeat password:pluto

Restartare il server:

httpd -k restart

Restricted

Analisi del Traffico

Chiudere il browser poi riaprirlo, poichè usa delle cache.

Installare l'utility tcpdump su Alpine:

apk add tcpdump

Aprire la cattura di traffico con dump ASCII con:

tcpdump -A

Aprire il browser e connettersi a localhost:8888/restricted, dando l'utente pippo e la password pluto.

Controllare il traffico catturato da tcpdump. Troveremo:

GET /restricted/ HTTP/1.1
Host: 192.168.101.12
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:121.0) Gecko/20100101 Firefox/121.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Connection: keep-alive
Upgrade-Insecure-Requests: 1
If-Modified-Since: Sat, 02 Mar 2024 20:13:15 GMT
If-None-Match: "70-612b31f6ae088"
Authorization: Basic cGlwcG86cGx1dG8=

La coppia nome:password viene inviata nella testata della richiesta http come stringa non crittografata, ma solo trasformata con l'algoritmo Base64.

Uno sniffer può intercettare la richiesta http e uno hacker la può decodificare, p.es.:

echo "cGlwcG86cGx1dG8K=" | base64 -d
pippo:pluto

Autenticazione Digest

E' un metodo challenge-response.

Non occorre inviare la password, basta dimostrare di saperla.

Chresp

Scrivere il file di limitazione accessi nella directory riservata:

cd /var/www/localhost/htdocs/restricted
vi .htaccess
AuthType                Digest
AuthName                "club"
AuthDigestDomain        /restricted/ http://localhost/restricted/

AuthDigestProvider file
AuthUserFile /etc/apache2/secure   
 
<Limit GET>
require valid-user
</Limit>

Generare il file dei permessi:

cd /etc/apache2
htdigest -c secure club tizio
Adding password for user tizio in realm club.
New password:caio
Repeat password:caio

Nel file /etc/apache2/httpd.conf:

Togliere il commento alla linea

LoadModule auth_digest_module modules/mod_auth_digest.so

Restart del server corrente:

httpd -k restart

La cattura traffico è completamente differente:

GET /restricted/ HTTP/1.1
Host: 192.168.101.12
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:121.0) Gecko/20100101 Firefox/121.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Connection: keep-alive
Upgrade-Insecure-Requests: 1
Authorization: Digest username="tizio", realm="club", nonce="FBRN4bMSBgA=d74359083de5b800e832d25773dcbc555768d016", uri="/restricted/", algorithm=MD5, response="3a4fce5ee9163dd376b803e9cb4f8bbe", qop=auth, nc=00000001, cnonce="0e32602512c548ec"

La password non compare, nemmeno trascodificata.

Se vi sono errori, verificare il file di log /var/www/logs/error.log.

Errori e MIME

Errori

Log degli Errori

File e Livello di Logging

Parametri:

  • ErrorLog logs/error.log - file di log, sotto ServerRoot
  • LogLevel warn - sono loggati gli errori uguali o superiori a questo livello di severità
    • possibilità, in ordine crescente di severità: debug, info, notice, warn, error, crit, alert, emerg

Formato dei Log

Ogni evento di log è una singola linea. Quali siano gli elementi su tale linea dipende dal formato del log.

Sono definiti tre formati:

  • common - tradizionale, più corto
  • combined - con più informazioni
  • combinedio - con anche informazioni di input e output
<IfModule log_config_module>
    LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combined
    LogFormat "%h %l %u %t \"%r\" %>s %b" common

    <IfModule logio_module>
      LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\" %I %O" combinedio
    </IfModule>
    #CustomLog logs/access.log common
    CustomLog logs/access.log combined
</IfModule>

Gli elementi del log sono rappresentati da segnalini

SegnalinoElemento
%hindirizzo IP del client
%lun trattino (-)
%unome utente, o trattino se manca
%ttimestamp
%rrichiesta
%>sstato del responso
%bdimensione dell'oggetto ritornato
%Ibytes ricevuti
%Obytes inviati
%{Referer}ireferer - indirizzo da cui la richiesta è provenuta tramite un link
%{User-Agent}iinformazioni sul browser

Esempio di linea log combinedio:

81.95.52.96 - - [12/Oct/2012:11:25:58 +0100] "GET /docs HTTP/1.1" 200 3623 
"http://webarch.net/docs/hosting-logs" "Mozilla/5.0 (X11; Linux i686; rv:15.0) 
Gecko/20100101 Firefox/15.0.1" 1224 6793

Errori Customizzabili

Esempi:

  • ErrorDocument 500 "The server made a boo boo." - testo diretto
  • ErrorDocument 404 /missing.html - pagina statica locale
  • ErrorDocument 404 "/cgi-bin/missing_handler.pl" - programma CGI di gestione
  • ErrorDocument 402 http://www.example.com/subscription_info.html - pagina remota

Gestione MIME

Il Multimedia Internet Message Extensions identifica files non US ASCII.

Ognuno di tali files ha una possibile codifica, handler di gestione, filtro di output.

Il seguente è un estratto del segmento di configurazione:

<IfModule mime_module>
    TypesConfig /etc/apache2/mime.types
    #AddEncoding x-compress .Z
    #AddEncoding x-gzip .gz .tgz
    AddType application/x-compress .Z
    AddType application/x-gzip .gz .tgz
    #AddHandler cgi-script .cgi
    #AddHandler type-map var
    #AddType text/html .shtml
    #AddOutputFilter INCLUDES .shtml
</IfModule>

Ove:

  • TypesConfig /etc/apache2/mime.types - file di definizione dei tipi MIME
  • #AddEncoding x-compress .Z - aggiunta di una codifica
  • AddType application/x-compress .Z - aggiunta del tipo MIME corrispondente alla codifica
  • AddType application/x-gzip .gz .tgz - aggiunta di un tipo MIME
  • #AddHandler cgi-script .cgi - aggiunta di uno handler: tutti i files con estensione .cgi, nella directory appropriata definita altrove, sono programmi con interfaccia CGI
  • #AddHandler type-map var - aggiunta di una mappa (opzione negoziabile)
  • #AddType text/html .shtml - l'estensione .shtml identifica un file HTML
  • #AddOutputFilter INCLUDES .shtml - l'estensione .shtml invoca il filtro Server Side Includes

Host Virtuali

Versione Debian e Ubuntu

Struttura di Directories

Creazione delle directory:

sudo mkdir -p /var/www/your_domain_1/public_html
sudo mkdir -p /var/www/your_domain_2/public_html

Permessi alle directory create:

sudo chown -R $USER:$USER /var/www/your_domain_1/public_html
sudo chown -R $USER:$USER /var/www/your_domain_2/public_html
sudo chmod -R 755 /var/www

Pagine di Default

Creazione di pagine di default per i due siti:

vim /var/www/your_domain_1/public_html/index.html
<html>
  <head>
    <title>Welcome to your_domain_1!</title>
  </head>
  <body>
    <h1>Success! The your_domain_1 virtual host is working!</h1>
  </body>
</html>
vim /var/www/your_domain_2/public_html/index.html
<html>
  <head>
    <title>Welcome to your_domain_2!</title>
  </head>
  <body> <h1>Success! The your_domain_2 virtual host is working!</h1>
  </body>
</html>

Configurazione degli Host Virtuali

Primo Virtual Server

Copiare il file di configurazione di default:

sudo cp /etc/apache2/sites-available/000-default.conf \
  /etc/apache2/sites-available/your_domain_1.conf

Modificare il nuovo file:

sudo vim /etc/apache2/sites-available/your_domain_1.conf

Modificare le direttive esistenti:

<VirtualHost *:80>
  ...
    ServerAdmin webmaster@localhost
    DocumentRoot /var/www/html
   ...
    ErrorLog ${APACHE_LOG_DIR}/error.log
    CustomLog ${APACHE_LOG_DIR}/access.log combined
</VirtualHost>

Aggiungere le direttive ServerName, ServerAlias e DocumentRoot:

<VirtualHost *:80>
  ...
    ServerAdmin admin@your_domain_1
    ServerName your_domain_1
    ServerAlias www.your_domain_1
    DocumentRoot /var/www/your_domain_1/public_html
    ...
    ErrorLog ${APACHE_LOG_DIR}/error.log
    CustomLog ${APACHE_LOG_DIR}/access.log combined
    ...
</VirtualHost>

Secondo Virual Server

Copiare il file di configurazione di del primo server virtuale:

sudo cp /etc/apache2/sites-available/your_domain_1.conf \
  /etc/apache2/sites-available/your_domain_2.conf

Editare il file generato in modo equivalente a quello del primo sito:

sudo vim /etc/apache2/sites-available/your_domain_2.conf
<VirtualHost *:80>
  ...
    ServerAdmin admin@your_domain_2
    ServerName your_domain_2
    ServerAlias www.your_domain_2
    DocumentRoot /var/www/your_domain_2/public_html
    ...
    ErrorLog ${APACHE_LOG_DIR}/error.log
    CustomLog ${APACHE_LOG_DIR}/access.log combined
    ...
</VirtualHost>

Abilitazione degli Host Viruali

sudo a2ensite your_domain_1.conf
sudo a2ensite your_domain_2.conf

Disabilitazione del server di default:

sudo a2dissite 000-default.conf

Test della configurazione:

sudo apache2ctl configtest

Modifica di /etc/hosts

Per la risoluzione nomi indirizzi, aggiungere i nomi dei nostri domini:

sudo vim /etc/hosts
127.0.0.1       localhost
127.0.2.1       your_domain_1
127.0.2.2       your_domain_2

Restart del Server

sudo systemctl restart apache2

Test

Con un browser o con curl, accedere a http://your_domain_1 e http://your_domain_2

Pulizia

Al termine rimettere a posto la configurazione iniziale:

sudo a2ensite 000-default.conf
sudo a2dissite your_domain_1.conf
sudo a2dissite your_domain_2.conf
sudo systemctl restart apache2

Versione Unix in Docker Alpine

Lanciare un contenitore Alpine e collegarsi:

docker run -d --name alp --privileged -p 8888:80 alpine sleep 1000000
docker exec -ti alp sh

Installare Apache:

apk add apache2
apk add curl

Non è necessaria l'abilitazione di alcun modulo.

Il file di configurazione principale, /etc/apache2/httpd.conf, include tutti i file *.conf dalla sottodirectory conf.d.

Andiamo in questa sottodirectory:

cd /etc/apache2/conf.d

Prepariamo due file di configurazione di due host virtuali che chiamiamo domain1 e domain2.

Il primo:

vi domain1.conf
<VirtualHost domain1:80>
    ServerAdmin webmaster@domain1.com
    DocumentRoot "/var/www/localhost/domain1/htdocs"
    <Directory "/var/www/localhost/domain1/htdocs">
      Options Indexes FollowSymLinks
      AllowOverride None
      Require all granted
    </Directory>
    ServerName domain1.com
    ServerAlias domain1.com
    ErrorLog "logs/domain1-error_log"
    CustomLog "logs/domain1-access_log" common
</VirtualHost>

Il secondo:

vi domain2.conf
<VirtualHost domain2:80>
    ServerAdmin webmaster@domain2.com
    DocumentRoot "/var/www/localhost/domain2/htdocs"
    <Directory "/var/www/localhost/domain2/htdocs">
      Options Indexes FollowSymLinks
      AllowOverride None
      Require all granted
    </Directory>
    ServerName domain2.com
    ServerAlias domain2.com
    ErrorLog "logs/domain2-error_log"
    CustomLog "logs/domain2-access_log" common
</VirtualHost>

Ciascuno ha i propri identificativi, e una sua directory DocumentRoot, con accesso regolato dalla direttiva <Directory>.

I log sono nella directory comune dei log di Apache.

Creiamo le directory DocumentRoot:

mkdir -p /var/www/localhost/domain1/htdocs
mkdir -p /var/www/localhost/domain2/htdocs

Creiamo dei files index.html in ciascuna di queste directory:

vi /var/www/localhost/domain1/htdocs/index.html
<h1>Hello from domain 1</h1>
vi /var/www/localhost/domain2/htdocs/index.html
<h1>Hello from domain 2</h1>

Occorre che i nomi dei domini siano risolti. Modifichiamo /etc/hosts:

vi /etc/hosts

Inserendo le linee:

127.0.1.1       domain1
127.0.1.2       domain2

Start del server:

httpd -k start

Test di raggiungibilità dall'interno:

curl domain1
curl domain2

Raggiungibilità dall'Esterno

Configurazione Interna

Scopriamo l'indirizzo IP del contenitore:

ip a | grep eth0 | grep inet
inet 172.17.0.2/16 brd 172.17.255.255 scope global eth0

Aggiungiamo altri due indirizzi:

ip addr add 10.0.0.1 dev eth0
ip addr add 10.0.0.2 dev eth0

Modifichiamo /etc/hosts sostituendo le linee di configurazione:

10.0.0.1      domain1
10.0.0.2      domain2

Configurazione Esterna

Apriamo un'altra finestra di terminale sulla macchina host.

Aggiungiamo la risoluzione di domain1 e domain2 al file /etc/hosts:

sudo vim /etc/hosts
10.0.0.1      domain1
10.0.0.2      domain2

Modifichiamo il routing:

sudo ip route add 10.0.0.0/8 via 172.17.0.2

Forward Proxy

Proprietà di un Forward Proxy

Gestisce le richieste cliente di accedere a risorse in internet. E' posizionato tra i client e l'internet, e inoltra le richieste a server esterni.

Il server di destinazione vede arrivare la richiesta dal forward proxy e non sa del vero client che l'ha originata.

Può includere filtraggio di contenuto, controlli di accesso e anonimizzazione delle richieste, nascondendo l'indirizzo IP del client.

Posizionato al confine della rete intranet del client.

Esercizio con Versione Debian e Ubuntu

Fwproxy

Creare la directory di esercizio:

mkdir 01net2-ccli-cproxy-cweb
cd 01net2-ccli-cproxy-cweb

NOTA

L'esercizio è scaricabile come file tar 01net2-ccli-cproxy-cweb.tar

Preparazione dello scaffolding:

mkdir ccli cproxy cweb
touch ccli/Dockerfile cproxy/Dockerfile docker-compose.yml
tree
.
├── ccli
│   └── Dockerfile
├── cproxy
│   └── Dockerfile
├── cweb
└── docker-compose.yml

Client

Il client ccli è un'immagine Alpine abbastanza semplice.

vim ccli/Dockerfile
FROM alpine:3.7
MAINTAINER John Smith <john@stormforce.ac>

RUN apk --update add --no-cache openssh tcpdump curl

CMD ["/bin/sleep","1000000"]

Proxy

Il proxy cproxy è un'immagine Ubuntu con la versione Debian/Ubuntu di Apache installata, più qualche altra utility.

vim cproxy/Dockerfile
FROM ubuntu

RUN apt update
RUN apt install -y apache2
RUN apt install -y vim curl wget iproute2 iputils-ping tcpdump

EXPOSE 80 443
CMD ["/bin/sleep","1000000"]

L'immagine Ubuntu di base è così scarna che non ha il comando ping e nemmeno il comando ip. Così, a scopo di debugging, vengono aggiunti i pacchetti iproute2, iputils-ping e tcpdump.

La configurazione del proxy viene mappata in un volume esterno, per la persistenza dei dati.

E' necessario creare a mano tale volume:

docker volume create conf

Web Server

Il web server cweb è un'immagine Alpine con la versione Unix di Apache, disponibile su Docker Hub.

docker-compose.yml

Il docker-compose.yml è:

vim docker-compose.yml
version: '3.6'

services:
  one:
    build: ccli
    image: ccli
    container_name: one
    hostname: one
    cap_add:
      - ALL
    networks:
      net1:
        ipv4_address: 192.168.101.11
  two:
    build: cproxy
    image: cproxy
    container_name: two
    hostname: two
    cap_add:
      - ALL
    volumes:
      - conf:/etc/apache2
    networks:
      net1:
        ipv4_address: 192.168.101.10
      net2:
        ipv4_address: 192.168.102.10
  three:
    image: httpd:2.4-alpine
    container_name: three
    hostname: three
    cap_add:
      - ALL
    networks:
      net2:
        ipv4_address: 192.168.102.12
networks:
  net1:
    name: net1
    ipam:
      driver: default
      config:
        - subnet: 192.168.101.0/24
  net2:
    name: net2
    ipam:
      driver: default
      config:
        - subnet: 192.168.102.0/24
volumes:
  conf:
    external: true

Lancio del progetto:

docker compose up -d

Controllo dei contenitori generati:

docker ps

Verifica Iniziale

Vogliamo verificare che il contenitore one (client) non riesce a connettersi col contenitore three (web server). Tra l'altro sono su due reti diverse e non è configurato il routing.

Connettersi a one:

docker exec -ti one sh

Provare la connessione:

curl http://192.168.102.12

Il comando si blocca: non è raggiungibile. Interrompere con Control-C

Uscire:

exit

Configurazione del Proxy

In una finestra di terminale collegarsi al contenitore two:

docker exec -ti two bash

Lanciare Apache:

apache2ctl -k start

Controllare che i processi siano attivi:

ps wax

Abilitare i moduli proxy, proxy_http, e proxy_connect:

a2enmod proxy proxy_http proxy_connect

Editare il file del modulo di proxy abilitato, scommentando le linee del proxy:

vim /etc/apache2/mods-enabled/proxy.conf
.....
        ProxyRequests On
        <Proxy *>
           AddDefaultCharset off
           Require all denied
           Require local
        </Proxy>
.....

Creare un nuovo Virtual Hosts:

vim /etc/apache2/sites-available/forward-proxy.conf
<VirtualHost *:8080>
  ProxyRequests On
  ProxyVia On
  <Proxy "*">
  Require ip 192.168
  </Proxy>
  ErrorLog ${APACHE_LOG_DIR}/error_forward_proxy.log
  CustomLog ${APACHE_LOG_DIR}/access_forward_proxy.log combined
</VirtualHost>

Modificare il file di configurazione delle porte, per aggiungere l'ascolto alla porta 8080:

vim /etc/apache2/ports.conf
Listen 80
Listen 8080

<IfModule ssl_module>
        Listen 443
</IfModule>

<IfModule mod_gnutls.c>
        Listen 443
</IfModule>

Abilitare il sito virtuale:

a2ensite forward-proxy.conf

Restartare il server:

apache2ctl -k restart

Uscire dal container:

exit

Test del Proxy

Connettersi a one:

docker exec -ti one sh

Occorre configurare la variabile d'ambiente che setta il proxy:

export http_proxy=http://192.168.101.10:8080

Provare la connessione:

curl http://192.168.102.12
<html><body><h1>It works!</h1></body></html>

Prova di Persistenza Dati

Fermare e ristartare il progetto:

docker compose down
docker compose up -d

La configurazione precedente è rimasta perchè mantenuta nel volume conf.

Attivare il server proxy:

docker exec two apache2ctl -k start

Attenzione a non dare l'opzione -ti. Questa crea un terminale di collegamento, fa partire il server, poi il comando termina, il terminale muore e il processo apache2 rimane senza terminale di controllo e si trasforma in un processo zombie. L'unico modo di uscirne è di ristartare il progetto.

Il client ha lo stesso comportamento di prima.

Connettersi a one:

docker exec -ti one sh

Provare la connessione:

export http_proxy=http://192.168.101.10:8080
curl http://192.168.102.12

Esercizio con Versione Unix su Alpine Docker

Progetto

Nella stessa locazione dell'esercizio precedente, creare una nuova directory:

mkdir cproxyalp

Il Dockerfile è:

vim cproxyalp/Dockerfile
FROM alpine

RUN apk add apache2 apache2-proxy apache2-proxy-html curl

EXPOSE 80 443
CMD ["/bin/sleep","1000000"]

Il docker-compose.yml è:

vim docker-compose.yml
version: '3.6'

services:
  one:
    build: ccli
    image: ccli
    container_name: one
    hostname: one
    cap_add:
      - ALL
    networks:
      net1:
        ipv4_address: 192.168.101.11
  two:
    build: cproxyalp
    image: cproxyalp
    container_name: two
    hostname: two
    cap_add:
      - ALL
    networks:
      net1:
        ipv4_address: 192.168.101.10
      net2:
        ipv4_address: 192.168.102.10
  three:
    image: httpd:2.4-alpine
    container_name: three
    hostname: three
    cap_add:
      - ALL
    networks:
      net2:
        ipv4_address: 192.168.102.12
networks:
  net1:
    name: net1
    ipam:
      driver: default
      config:
        - subnet: 192.168.101.0/24
  net2:
    name: net2
    ipam:
      driver: default
      config:
        - subnet: 192.168.102.0/24

Partenza del progetto:

docker compose up -d

Configurazione

Connessione al proxy:

docker exec -ti two sh

Configurazione:

cd /etc/apache2
vi conf.d/forward-proxy.conf
<VirtualHost *:8080>
  ProxyRequests On
  ProxyVia On
  <Proxy "*">
  Require ip 192.168
  </Proxy>
  ErrorLog logs/error_forward_proxy.log
  CustomLog logs/access_forward_proxy.log combined
</VirtualHost>

Modificare il file di configurazione principale:

vi httpd.conf

Nella sezione Listen, aggiungere la linea:

Listen 8080

Far partire il server:

httpd -k start

Uscire:

exit

Test

Connettersi a one:

docker exec -ti one sh

Configurare il client proxy:

export http_proxy=http://192.168.101.10:8080

Provare la connessione:

curl http://192.168.102.12
<html><body><h1>It works!</h1></body></html>

Naturalmente questa configurazione non resiste ad un docker compose down. Se desideriamo la persistenza, allora dobbiamo creare un apposito volume docker e mapparlo alla directory di configurazione.

Reverse Proxy

Proprietà di un Reverse Proxy

Gestisce le richieste di un cliente esterno che accede a risorse nella intranet di un'organizzazione. Compie il forward delle richieste del client all'appropriato backend server.

Usato per load balancing, terminazione di SSL, cache e controlli di sicurezza.

I backend servers vedono la richiesta provenire dal reverse proxy come se questo fosse il client.

Posizionato al confine di ingresso dell'intranet di un'organizzazione, gestisce e controlla le richieste in ingresso.

Casi d'Uso di un Reverse Proxy

Con moduli del proxy server stesso:

  • Load Balancing: bilanciamento del carico dei backend server.
  • SSL Termination: possono gestire la crittografazione e decrittografazione con SSL/TLS per conto dei backend server.
  • Caching: accelerazione dei tempi di responso al client e diminuzione del carico dei backend server.

Con software aggiuntivo installato a bordo del proxy:

  • Sicurezza: possono filtrare il contenuto in provenienza dall'internet, e bloccare attacchi DDoS.
  • Web Application Firewall (WAF): possono proteggere la sicurezza dell'applicativo dei backend server, impedendo p.es. SQL Injection e Cross Site Scripting (XSS).
  • Conversione di Protocollo: possono tradurre richieste da un protocollo ad un altro.
  • Compressione ed Ottimizzazione: possono effettuare compressione ed ottimizzazionne dei responsi verso il client, riducendo i tempi di trasferimento.

Moduli Requisiti

  • mod_proxy: modulo primario, requisito
  • mod_proxy_http: gestisce i protocolli HTTP&HTTPS
  • mod_ssl: supporta i protocolli SSL v3 e TLS v1.x

Esercizio su Debian e Ubuntu

Revproxy

Preparazione

Creare la directory di esercizio:

mkdir -p 02net1-ccli-net2-cproxy-2cweb
cd 02net1-ccli-net2-cproxy-2cweb

Preparare lo scaffolding:

mkdir ccli cproxy cweb
touch ccli/Dockerfile cproxy/Dockerfile docker-compose.yml

Verificare l'albero:

tree
.
├── ccli
│   └── Dockerfile
├── cproxy
│   └── Dockerfile
├── cweb
└── docker-compose.yml

Preparare volumi per la persistenza dati:

docker volume create confrev
docker volume create three-html
docker volume create four-html
docker volume ls

Client

Il client ccli è un'immagine Alpine abbastanza semplice.

vim ccli/Dockerfile
FROM alpine:3.7
MAINTAINER John Smith <john@stormforce.ac>

RUN apk --update add --no-cache openssh tcpdump curl

CMD ["/bin/sleep","1000000"]

Proxy

Il proxy cproxy è un'immagine Ubuntu con la versione Debian/Ubuntu di Apache installata, più qualche altra utility.

vim cproxy/Dockerfile
FROM ubuntu

RUN apt update
RUN apt install -y apache2
RUN apt install -y vim curl wget iproute2 iputils-ping tcpdump

EXPOSE 80 443
CMD ["/bin/sleep","1000000"]

Web Server

Il web server cweb è un'immagine Alpine con la versione Unix di Apache, disponibile su Docker Hub.

docker-compose.yml

Il docker-compose.yml è:

vim docker-compose.yml
version: '3.6'

services:
  one:
    build: ccli
    image: ccli
    container_name: one
    hostname: one
    cap_add:
      - ALL
    networks:
      net1:
        ipv4_address: 192.168.101.11
  two:
    build: cproxy
    image: cproxy
    container_name: two
    hostname: two
    cap_add:
      - ALL
    volumes:
      - confrev:/etc/apache2
    networks:
      net1:
        ipv4_address: 192.168.101.10
      net2:
        ipv4_address: 192.168.102.10
  three:
    image: httpd:2.4-alpine
    container_name: three
    hostname: three
    cap_add:
      - ALL
    volumes:
      - three-html:/usr/local/apache2/htdocs/
    networks:
      net2:
        ipv4_address: 192.168.102.12
  four:
    image: httpd:2.4-alpine
    container_name: four
    hostname: four
    cap_add:
      - ALL
    volumes:
      - four-html:/usr/local/apache2/htdocs/
    networks:
      net2:
        ipv4_address: 192.168.102.13
networks:
  net1:
    name: net1
    ipam:
      driver: default
      config:
        - subnet: 192.168.101.0/24
  net2:
    name: net2
    ipam:
      driver: default
      config:
        - subnet: 192.168.102.0/24
volumes:
  confrev:
    external: true
  three-html:
    external: true
  four-html:
    external: true

Lancio del progetto:

docker compose up -d

Controllo dei contenitori generati:

docker ps

Configurazione

Collegarsi a two ed abilitare il servizio web se non lo è già:

docker exec -ti two bash
apache2ctl -k start
ps wax

Moduli

Abilitare i moduli requisiti:

a2enmod proxy 
a2enmod proxy_http
a2enmod ssl

Verificare:

apache2ctl -M

Disabilitare il sito di default

a2dissite 000-default.conf

Creare un nuovo sito virtuale

cd /etc/apache2/sites-available
vim example.com.conf
<VirtualHost *:80>
    ServerName site1.com
    ServerAlias www.site1.com 
    ServerAdmin postmaster@site1.com
    ErrorLog ${APACHE_LOG_DIR}/error.log
    CustomLog ${APACHE_LOG_DIR}/access.log combined
    ProxyPass / http://192.168.102.12:80/
    ProxyPassReverse / http://192.168.102.12:80/
    ProxyRequests Off
</VirtualHost>
  • ServerName: nome ufficiale del server
  • ServerAlias: nomi aggiuntivi accettati dal server
  • ServerAdmin: posta elettronica dell'amministratore
  • ErrorLog: file degli errori
  • CustomLog: log delle richieste dei client al server
  • ProxyPass: mappa i server remoti nello spazio locale e definisce gli indirizzi target per la redirezione del traffico. E' il parametro che definisce il server corrente come reverse proxy.
  • ProxyPassReverse: gestisce i responsi dei backend servers, compiendo una riscrittura dei campi e headers del responso con le proprie informazioni. Questo impedisce che i backend server vengano esposti all'internet.
  • ProxyRequests: impedisce cge il server venga usato come forward proxy. Dovrebbe tipicamente essere settato ad off quando si usa ProxyPass.

Abilitare il Virtual Host

a2ensite example.com.conf

Restart

Restartare il server.

apachectl -k restart
ps wax

Uscire:

exit

Verifica

Vogliamo creare pagine web distintive per i downstream servers.

Collegarsi a three:

docker exec -ti three sh

Modificare il file principale di DocumentRoot:

vi /usr/local/apache2/htdocs/index.html
<html><body><h1>It works! I am three</h1></body></html>
exit

Simili cambiamenti su four:

docker exec -ti four sh

Modificare il file principale di DocumentRoot:

vi /usr/local/apache2/htdocs/index.html
<html><body><h1>It works! I am four</h1></body></html>
exit

Collegarsi a one:

docker exec -ti one sh

Collegamento a two:

curl 192.168.101.10
<html><body><h1>It works! I am three</h1></body></html>

Configurazione su Docker Alpine

Progetto 2

Aggiungere al progetto la directory cproxyalp:

mkdir cproxyalp

Il Dockerfile è:

vim cproxyalp/Dockerfile
FROM alpine

RUN apk add apache2 apache2-proxy apache2-proxy-html curl

EXPOSE 80 443
CMD ["/bin/sleep","1000000"]

Il docker-compose.yml è:

version: '3.6'

services:
  one:
    build: ccli
    image: ccli
    container_name: one
    hostname: one
    cap_add:
      - ALL
    networks:
      net1:
        ipv4_address: 192.168.101.11
  two:
    build: cproxyalp
    image: cproxyalp
    container_name: two
    hostname: two
    cap_add:
      - ALL
    networks:
      net1:
        ipv4_address: 192.168.101.10
      net2:
        ipv4_address: 192.168.102.10
  three:
    image: httpd:2.4-alpine
    container_name: three
    hostname: three
    cap_add:
      - ALL
    volumes:
      - three-html:/usr/local/apache2/htdocs/
    networks:
      net2:
        ipv4_address: 192.168.102.12
  four:
    image: httpd:2.4-alpine
    container_name: four
    hostname: four
    cap_add:
      - ALL
    volumes:
      - four-html:/usr/local/apache2/htdocs/
    networks:
      net2:
        ipv4_address: 192.168.102.13
networks:
  net1:
    name: net1
    ipam:
      driver: default
      config:
        - subnet: 192.168.101.0/24
  net2:
    name: net2
    ipam:
      driver: default
      config:
        - subnet: 192.168.102.0/24
volumes:
  three-html:
    external: true
  four-html:
    external: true

Partenza del progetto:

docker compose up -d

Configurazione 2

Connessione al proxy:

docker exec -ti two sh

Configurazione:

cd /etc/apache2
vi conf.d/reverse-proxy.conf
<VirtualHost *:80>
    ServerName site1.com
    ServerAlias www.site1.com 
    ServerAdmin postmaster@site1.com
    ErrorLog logs/error.log
    CustomLog logs/access.log combined
    ProxyPass / http://192.168.102.12:80/
    ProxyPassReverse / http://192.168.102.12:80/
    ProxyRequests Off
</VirtualHost>

Disabilitare il forward proxy se esiste:

cd /etc/apache2/conf.d
mv forward-proxy.conf forward-proxy.conf.old 2> /dev/null

Verificare la configurazione:

httpd -t

Startare il server:

httpd -k start

Utilities e Performance

Performance

httperf

Programma per Ubuntu, per il test delle performance di un sito web.

Installazione dalla release:

sudo apt update
sudo apt install httperf

Esempi d'uso:

httperf --server 127.0.0.1 --port 80 --uri /index.html --rate 300 --num-conn 30000 --num-call 1 --timeout 5

Interrompere con Ctrl-C.

autobench

E' un wrapper intorno ad httperf, che ha come prerequisito.

E' fornito in codice sorgente, ma è stato compilato e reso disponibile per questo corso come file tar autobench.tar

Compiere il dowload copiando l'indirizzo del link, poi:

wget indirizzo_link

Dopo il download, estrarlo nella directory /usr/local/bin:

sudo tar xvf autobench.tar -C /usr/local/bin

Copiare il file di configurazione:

cp /usr/local/bin/autobench.conf ~/.autobench.conf

Uso:

autobench --single_host --host1 127.0.0.1  --uri1 /index.html \
  --quiet --low_rate 20 --high_rate 200 --rate_step 20 \
  --num_call 10 --num_conn 100 --timeout 5 --file results.tsv

loadem

wget https://github.com/pjoe/loadem/releases/download/0.2.11/loadem-0.2.11-linux-amd64.tar.gz
tar xvf loadem-0.2.11-linux-amd64.tar.gz
sudo cp loadem /usr/local/bin

Uso:

loadem http://localhost 200

Interrompere con Ctrl-C.

Server Sicuro

Crittografia e Certificati

Crittografia Classica

Enigma

La crittografia ha una storia millenaria.

Sicurezza della Crittografazione

Incondizionatamente Sicura

  • Il testo cifrato non contiene abbastanza informazioni per determinare univocamente il testo in chiaro corrispondente, per qualsiasi quantità di testo in chiaro
    • Solo One-Time-Pads
  • Normalmente i testi lunghi crittografati con la stessa chiave aumentano la probabilità di successo di crittanalisi

Computazionalmente Sicura

  • Il costo della decrittografazione è superiore al valore dell’informazione crittografata
  • Il tempo richiesto per la decrittografazione supera la vita utile dell’informazione

Principio di Kerckhoffs

Legge di base della crittografia

il faut qu’il puisse sans inconvénient tomber entre les mains de l’ennemi” - Auguste Kerckhoffs (1835-1903), Fiammingo (La cryptographie militaire, 1883)

the enemy knows the system being used” - Shannon, 1949

Assumere che il nemico conosca gli algoritmi usati,

Contrario di: Security through Obscurity

Attacchi di Crittanalisi

Cryptoattack

Cifrario Perfetto

Onetime

Cifrario perfetto se M e C sono indipendenti:

Prob (M=M’) = Prob (M=M’ | C=C’)

Lunghezza chiave > Lunghezza testo in chiaro

Modern Cryptography

Digitale e basata su due aspetti:

Algoritmi:

  • Procedure matemetiche per la crittazione e decrittazione di dati
  • Implementati come programmi, routines, funzioni

Tutti i maggiori algoritmi sono ben noti.

E' considerato sicuro un algoritmo noto se ha una implementazione Open Source. E' considerato insicuro un algoritmo proprietario e privato.

Chiavi:

  • Sequenze di bit usati nella crittazione e decrittazione
  • Configurate manualmente, note solo ai partecipanti

La sicurezza sta nelle chiavi.

Modern

Diffusione e Confusione

Diffusione:

  • Dissipa la struttura statistica del testo in chiaro
  • Il testo cifrato non ha strutture statistiche a corto raggio
  • Cifra a blocchi binaria: permutazione + funzione (reversibile)
  • Al cambiare di un bit del messaggio in chiaro cambiano in media il 50% dei bit del messaggio crittato

Confusione:

  • Complica al massimo la relazione tra la statistica del testo cifrato e il valore della chiave di cifratura
  • Al cambiare di un bit della chiave cambiano in media il 50% dei bit del messaggio crittato

Cifra di Feistel

Esempio di Rete a Sostituzione-Permutazione - (Substitution-Permutation Network SPN - Shannon).

Fondamento dei principali algoritmi moderni di crittografazione.

  • Un blocco di testo in chiaro è diviso in due metà: Destra e Sinistra
  • Vi sono più Sottochiavi, una per ciclo, derivate dalla Chiave primaria e tutte differenti
  • N cicli, in ciascuno dei quali si ha:
    • una Sostituzione sulla metà Sinistra:
    • una Funzione di Ciclo sulla metà Destra, parametrizzata dalla Sottochiave di ciclo
  • un OR esclusivo del risultante con la metà Sinistra
  • una Permutazione: interscambio delle metà Destra e Sinistra

Tutti i maggiori algoritmi di crittografazione moderni sono derivati dai concetti di Cifra di Feistel

Feistel

Gestione Chiavi

Chiavi Singole

Definizione: Chiave = Stringa di caratteri = Sequenza di Bit

  • La stessa chiave K crittografa e decrittografa il messaggio
  • Il messaggio non cambia di lunghezza da Chiaro a Crittografato
  • L'algoritmo di crittografazione è relativamente veloce

La chiave deve transitare da mittente a destinazione. Il canale per le chiavi è diverso dal canale per i messaggi:

  • più sicuro
  • più costoso (es. Umano)

Non ne vale la pena per singoli messaggi.

Singlekey

Proprietà delle Chiavi

La crittografazione è un’algebra lineare.

Sia M:messaggio in chiaro, C:messaggio crittografato, E(K)[]:funzione di crittografazione con chiave K, D(K)[]:funzione di decrittografazione con chiave K

Si ha:

C=E(K)[M]    M=D(K)[C]    M=D(K)[E(K)[M]]

In un’algebra lineare la rappresentazione può usare funzioni lineari, matrici od operatori lineari.

Usando operatori lineari e confondendo crittografazione e decrittografazione, rappresentate con la sola chiave K (le sottochiavi sono invertite):

C=KM   M=KC   M=KKM

cioè KK=Identità

Interscambio Messaggi

Key01

Chiavi Doppie

Da una stringa casuale S un algoritmo genera due chiavi particolari dette:

  • Chiave pubblica U
  • Chiave priv*ata P

Algoritmi: RSA, Diffie-Hellmann, ...

Key02

Il messaggio è crittografato con una delle chiavi e può venir decrittografato solo con l’altra.

La chiave Pubblica viene inviata ad un Deposito Chiavi da cui tutti possono prelevarla oppure configurata nel software di comunicazione.

La chiave Privata viene mantenuta segreta e non circola mai in rete.

Doublekey

Confidenzialità del Messaggio

Spesso è desiderata la segretezza (riservatezza, confidenzialità) del messaggio.

Il messaggio viene crittografato dal mittente con la chiave pubblica del destinatario.

Solo chi è in possesso della chiave privata corrispondente, cioè il destinatario, può decrittografare il messaggio.

Ke703

Assicurazione di Provenienza

Non sempre si desidera che il messaggio sia confidenziale, può essere necessario invece assicurarsi dell’identità del mittente.

In tal caso il mittente crittografa il messaggio in partenza con la propria chiave privata.

Tutti lo possono decrittografare avendo così l’assicurazione formale che solo il mittente l’abbia potuto inviare.

Confidenzialità ed Assicurazione di Provenienza possono ottenersi entrambi indipendentemente e simultaneamente.

Necessità di un Programma di gestione, p.es. PGP (Pretty Good Privacy).

Key04

In pratica l’uso delle chiavi doppie è troppo lento (10000 volte più delle chiavi singole);

  • OK per commutazione di messaggio
  • NO per commutazione di pacchetto

Ma: la lunghezza delle chiavi singole è ca. 128 bit

Quindi:

  • Usare le chiavi doppie per interscambiare una chiave singola in modo sicuro
  • Poi usare la chiave singola per crittografare il messaggio

Concetto di Chiave di Sessione

Chiave di Sessione

Sessionkey

The sender initially encrypts a single key (session key) and sends it to the receiver The rest of the communication session is encrypted with that single key

Message Authentication Code

Message Authentication Code (MAC): checksum crittografico allegato al messaggio.

Lo schema di checksum non deve necessariamente essere reversibile.

Mac01

Funzione di Hash

  • Input: stream di bit di qualsiasi lunghezza
  • Output: stream di bit di lunghezza fissa
    • anche detto: Segnatura
  • Collisione: due stream di input diversi che danno la stessa segnatura - computazionalmente impossibile

Proprietà:

  • Funzione non invertibile
    • Tipicamente usa iterazione di passi con compressione dei risultati intermedi
  • Attacchi di forza bruta: dipendono dalla lunghezza della segnatura
  • Alternativamente metodi crittanalitici complessi
    • Reperire collisioni nei passi intermedi

Firma Elettronica

Mittente:

  • Calcolare una funzione di hash del messaggio
  • Crittarla con chiave privata e appenderla al messaggio

Ricevente:

  • Estrarre la funzione di hash e decrittarla con chiave pubblica del mittente
  • Applicare la funzione di hash al messaggio
  • Se coincidono la firma è verificata

Non repudiabilità dei messaggi firmati.

Firma01

Definizioni Italiane

Definizioni giuridiche un po' imprecise dal punto di vista tecnico:

  • Firma elettronica (FE)
    • Metodo per identificare l'autore di un'operazione informatica o di un documento informatico
  • Firma elettronica avanzata (FEA)
    • FE che consente il controllo esclusivo del mezzo di firma da parte del firmatario, e permette di verificare l'integrità del documento firmato
  • Firma elettronica qualificata (FEQ)
    • FEA basata su un certificato qualificato e realizzata mediante un dispositivo sicuro per la creazione della firma
  • Firma digitale (FD)
    • FEA basata su un certificato qualificato e realizzata con la tecnica della crittografia asimmetrica

Firma02

Non esistono al momento tecnologie di firme qualificate che non siano digitali.

Il dispositivo di firma della FEQ è opzionale, poichè FEQ ed FD producono i medesimi effetti giuridici.

Dispositivo di Firma

Firma02

Apparato elettronico in grado di generare firme digitali e di fornire autenticazione on line tramite certificato.

Formato tipico: Card simile a carta di credito.

  • Anche su chiavi USB
    • Ospita un chip avanzato crittografico
    • Necessario l'apposito lettore e software
  • Conserva a bordo la chiave privata dell'utente
  • Obbligatorio per Firma Qualificata ma non per Firma Digitale
  • Non è clonabile

Un'altra soluzione moderna è di ospitare la chiave privata a bordo di uno Smartphone.

Il tentativo di accesso ai dati mediante apparati elettronici di violazione provoca di solito la perdita dei dati contenuti.

  • Accesso tramite PIN
  • Numero massimo di tentativi - blocco card o programma
  • Sbloccare con PUK (personal unlock code)

NOTA

Il fallimento di accesso, avaria o perdita del dispositivo di firma rende inverificabili tutti i documenti firmati con tale dispositivo

Certificati

Identità e Fiducia

In un Deposito Chiavi una chiave necessita di un identificativo univoco del possessore:

  • Indirizzo di posta elettronica

Problemi:

  • Come si può essere certi che la chiave appartiene alla persona indicata?
  • Come si può essere certi dell'identità di un corrispondente nel mondo informatico?
  • Come ci si può fidare della verità delle asserzioni di un documento informatico?

Soluzione:

  • Trasferimento della fiducia dal soggetto finale ad altri:
    • Più enti arzialmente fidati: raccomandazioni
    • Singolo ente completamente fidato: certificazioni

Verifica: Scenario Possibile

Cert01

In Pratica

Il deposito chiavi non esiste:

  • Qualche eccezione: MIT Key Server
  • Sarebbero troppi, problemi di sicurezza e di privacy
  • Soprattutto problemi politici (affrontabili tecnicamente)
    • es: Planetary Citizen Registry (1971)

L'utente vuole potersi fidare in assenza dell'ente di verifica o di connessione.

  • Necessaria la chiave pubblica del soggetto, non modificata

Struttura di un certificato soggetto:

Cert02

E' necessaria anche la chiave privata del certificatore. per decrittare il certificato:

Cert03

La chiave privata è di solito precaricata sul SW dell'utente.

Certificati Speciali

Certificate Chain:

Cchain

Self-signed Certificate (Root CA):

Cself

Uso del Certificato

Esempio della situazione con SSL/TLS.

Cert04

Standard X509

Standard della ITU-T sui Certificati.

  • Basato su crittografia a chiave doppia e firma elettronica
  • Definisce i tracciati record dei Certificati
    • 3 versioni principali
  • Prevede Liste di Revoca dei Certificati

Imposta unastruttura gerarchica degli enti di certificazione

  • Certifying Authority (CA)
  • Public Key Infrastructure (PKI)

Public Key Infrastructure

Per PKI s’intende l’infrastruttura necessaria per consentire un uso appropriato ed efficiente di una serie di funzionalità legate alla crittografia a chiave pubblica quali la firma digitale, l’autenticazione, la cifratura/decifratura e la marcatura temporale dei documenti.

Pki01

PKI è una infrastruttura per la gestione di chiavi pubbliche e non una infrastruttura pubblica per la gestione chiavi.

Configurazione di Certificati

OpenSSL

E' un toolkit Open Source robusto, sicuro e completo per la gestione di protocolli Transport Layer Security (TLS) e Secure Sockets Layer (SSL).

E' anche una libreria generica con molte utility crittografiche.

openssl
OpenSSL> help
Standard commands
asn1parse         ca                ciphers           cms
crl               crl2pkcs7         dgst              dhparam
dsa               dsaparam          ec                ecparam
enc               engine            errstr            gendsa
genpkey           genrsa            help              list
nseq              ocsp              passwd            pkcs12
pkcs7             pkcs8             pkey              pkeyparam
pkeyutl           prime             rand              rehash
........
openssl comando opzioni argomenti

Scenario

  1. Un Soggetto, p.es. un Web Server (chi lo amministra) genera una coppia di chiavi, pubblica e privata.

  2. Il Soggetto invia una Richiesta di Certificato (CR - Certificate Request) ad un'Autorità di Certificazione (CA - Certifying Authority). La CR contiene i dati di identità del Soggetto e la sua Chiave Pubblica.

  3. La CA possiede un Autocertificato (SSC - Self-Signed Certificate), di cui ci si fida a priori, ed una Infrastruttura di Certificazione.

  4. La CA verifica i dati di identità della CR.

  5. La CA genera un Certificato Soggetto (SC - Subject Certificate) e lo invia al Soggetto richiedente.

  6. Il Soggetto configura il Web Server con il SC ricevuto.

Prepariamo una directory per l'esercizio:

mkdir -p ~/ex/cert
cd ~/ex/cert

Certificate Request

Creazione di una nuova Chiave Privata e Certificate Signing Request:

openssl req -out geekflare.csr -newkey rsa:2048 -nodes -keyout geekflare.key
Generating a RSA private key
............
writing new private key to 'geekflare.key'
-----
You are about ....
-----
Country Name (2 letter code) [AU]:
State or Province Name (full name) [Some-State]:NSW
Locality Name (eg, city) []:Sydney
Organization Name (eg, company) [Internet Widgits Pty Ltd]:
Organizational Unit Name (eg, section) []:Development
Common Name (e.g. server FQDN or YOUR name) []:geekflare.com
Email Address []:boss@geekflare.com

Please enter the following 'extra' attributes
to be sent with your certificate request
A challenge password []:password
An optional company name []:GeekFlare

Self-Signed Certificate

Creazione di un Self-Signed Certificate per una Root CA:

openssl req -x509 -sha256 -nodes -newkey rsa:2048 -keyout gfselfsigned.key -out gfcert.pem
Generating a RSA private key
..........
writing new private key to 'gfselfsigned.key'
-----
You are about to be asked ...
-----
Country Name (2 letter code) [AU]:US
State or Province Name (full name) [Some-State]:Washington
Locality Name (eg, city) []:Seattle
Organization Name (eg, company) [Internet Widgits Pty Ltd]:Microsoft
Organizational Unit Name (eg, section) []:World Control
Common Name (e.g. server FQDN or YOUR name) []:world.ms.com
Email Address []:galacticboss@ms.com

Configurazione di CA

Creare un file minimo di configurazione per una CA:

vim ca.conf
[ ca ]
default_ca = ca_default
[ ca_default ]
dir = ./ca
certs = $dir
new_certs_dir = $dir/ca.db.certs
database = $dir/ca.db.index
serial = $dir/ca.db.serial
RANDFILE = $dir/ca.db.rand
certificate = $dir/ca.crt
private_key = $dir/ca.key
default_days = 365
default_crl_days = 30
default_md = sha256
preserve = no
policy = generic_policy
[ generic_policy ]
countryName = optional
stateOrProvinceName = optional
localityName = optional
organizationName = optional
organizationalUnitName = optional
commonName = optional
emailAddress = optional

Osservazioni:

  • Questo è un file di configurazione molto ridotto e poco sicuro. Viene usato solo per scopi didattici.
  • Il parametro default_md descrive l'algoritmo di firma del certificato. Apache moderno accetta in categoria HIGH solo algoritmi ad almeno 256 bit. Così il vecchio default md5 non va bene, mentre sha256 va bene.

Infrastruttura CA

Creare la directory di database del CA ed alcune altre directory e file necessari, che manterranno informazioni sui certificati emessi:

mkdir ca
cd ca
mkdir ca.db.certs
touch ca.db.index
echo "1234" > ca.db.serial
cd ..

Copiare il certificato autofirmato (root CA certificate) e chiave privata alla directory CA.

cp gfcert.pem ca/ca.crt
cp gfselfsigned.key ca/ca.key

Generare un Certificato

Ogni comando di gestione del CA deve contenere l'opzione che punta al file di configurazione, p.es. -config ca.conf.

Firmare una Certificate Request e generare un certificato:

openssl ca -config ca.conf -out certificate.pem -infiles geekflare.csr
Using configuration from ca.conf
Check that the request matches the signature
Signature ok
The Subject's Distinguished Name is as follows
countryName           :PRINTABLE:'AU'
stateOrProvinceName   :ASN.1 12:'NSW'
localityName          :ASN.1 12:'Sydney'
organizationName      :ASN.1 12:'Internet Widgits Pty Ltd'
organizationalUnitName:ASN.1 12:'Development'
commonName            :ASN.1 12:'geekflare.com'
emailAddress          :IA5STRING:'boss@heekflare.com'
Certificate is to be certified until Sep  8 09:19:15 2022 GMT (365 days)
Sign the certificate? [y/n]:y


1 out of 1 certificate requests certified, commit? [y/n]y
Write out database with 1 new entries
Data Base Updated

Altre Operazioni

La utility openssl possiede numerose possibilità. Alcune sono:

Verificare il file CSR:

openssl req -noout -text -in geekflare.csr

Da in output il contenuto ASCII della richiesta.

Creare una chiave privata RSA:

openssl genrsa -out private.key 2048

In realtà il comando produce un singolo file ove sono descritte sia la chiave privata che quella pubblica.

Estrarre la chiave pubblica RSA:

openssl rsa -in private.key -pubout > public.pub

Verificare una chiave privata:

openssl rsa -in private.key --check

Verificare la Certificate Signer Authority:

openssl x509 -in certificate.pem -noout -issuer -issuer_hash
issuer=C = US, ST = Washington, L = Seattle, O = Microsoft, 
OU = World Control, CN = world.ms.com, 
emailAddress = galacticboss@ms.com
2f06f056

Controllare il valore hash di un certificato:

openssl x509 -noout -hash -in certificate.pem
3543b6f5

Revocare un certificato:

Esempio:

openssl ca -revoke ca/ca.db.certs/1234.pem -config ca.conf

Una revoca adeguata coinvolgerebbe anche la gestione di un Certificate Revocation List (CRL), che qui non viene compiuta. Comunque pochi browser controllano i CRL.

Semplicemente non installare in Apache dei certificati revocati.

Apache e Certificati

Versione Debian/Ubuntu

Prerequisiti

Abilitare il modulo SSL

sudo a2enmod ssl

Se necessario editare il file mods-enabled/ssl.conf.

Il parametro SSLCipherSuite HIGH:!aNULL descrive gli algoritmi di firma del certificato che sono consentiti:

  • !aNULL - proibiti gli algoritmi senza autenticazione
  • HIGH - hash con almeno 256 bit, p.es. SNA256
  • MEDIUM - hash con almeno 128 bir, p.es. SHA1
  • LOW - hash con meno di 128 bit, p.es- MD5

La lista completa delle possibilità, come pure altri parametri di settaggio crittografici, fi trovano alla pagina di manuale: man ciphers.

Una sicurezza nulla si ottiene settando il parametro a: SSLCipherSuite DEFAULT:@SECLEVEL=0.

Nel nostro caso l'algoritmo di hash che firma il certificato è SHA256, quindi non vi è bisogno di alcun cambiamento.

Abilitare il sito con accesso SSL

sudo a2ensite default-ssl

Occorre fornire:

  • il nome del server
  • la risoluzione nome-indirizzo del server
  • la locazione del certificato e della chiave privata del soggetto

Nome del Server:

Inserire il parametro:

ServerName geekflare.com

Il nome del server deve essere lo stesso del campo CN del certificato fornito, nel nostro caso geekflare.com.

Risoluzione nome-indirizzo:

Editare il file /etc/hosts ed inserire la linea, p.es.:

10.0.2.15       geekflare.com

Locazioni:

Sono i parametri:

SSLCertificateFile      /etc/ssl/certs/certificate.pem
SSLCertificateKeyFile /etc/ssl/private/geekflare.key

Copia dei File

Il certificato del soggetto e la chiave privata del soggetto devono essere copiati nelle apposite directory.

Certificato del Soggetto:

sudo cp ~/ex/certs/certificate.pem /etc/ssl/certs

Chiave Privata del Soggetto:

sudo cp ~/ex/certs/geekflare.key /etc/ssl/private

Anche il certificato della CA che firma deve essere copiato nell'apposita directory:

sodo cp ~/ex/certs/gfcert.pem /etc/ssl/certs

Test della Configurazione

Col comando:

sudo apache2ctl -t

Restart del Server

Col comando:

sudo systemctl restart apache2

Verificare:

sudo systemctl status apache2

Se vi sono errori consultare il log:

sudo less /var/log/apache2/error.log

Test di Funzionamento

Aprire un browser e puntarlo a https://geekflare.com


Versione Unix in Docker già Pronto

Useremo l'immagine httpd:2.4-alpine, già disponibile a Docker Hub.

Lancio del container:

docker run -d --name htalp -v $HOME/ex/certs:/certs \
  -p 8888:80 -p 8443:443 httpd:2.4-alpine sleep 1000000

Vi è un bind mapping tra la nostra directory ~/ex/certs, che contiene i certificati che ci servono, e la directory del contenitore /certs. Quest'ultima non esiste e viene creata.

Per impedire collisioni con porte della macchina host compiamo due port mapping:

  • la porta locale 8888 per HTTP
  • la porta locale 8443 per HTTPS

Ci colleghiamo quindi al contenitore:

docker exec -ti htalp sh

Ci troviamo nella directory /usr/local/apache2.

Abilitazione dei Componenti

Editiamo il file di configurazione principale:

vi conf/httpd.conf

Scommentiamo le linee:

#LoadModule ssl_module modules/mod_ssl.so

#LoadModule socache_shmcb_module modules/mod_socache_shmcb.so

#Include conf/extra/httpd-ssl.conf

Modifichiamo i parametri:

ServerName geekflare.com

ServerAdmin admin@geekflare.com

Configurazione di SSL

Questa è nel file conf/extra/httpd-ssl.conf.

SSLRandomSeed startup file:/dev/urandom 512

SSLRandomSeed connect file:/dev/urandom 512

Non occorrono cambiamenti, ma notiamo i parametri del certificato soggetto:

SSLCertificateFile "/usr/local/apache2/conf/server.crt"

SSLCertificateKeyFile "/usr/local/apache2/conf/server.key"

Occorre il certificato CA. Scommentiamo la linea:

SSLCACertificateFile "/usr/local/apache2/conf/ssl.crt/ca-bundle.crt"

Copia dei File Necessari

cp /certs/certificate.pem conf/server.crt
cp /certs/geekflare.key conf/server.key
mkdir /usr/local/apache2/conf/ssl.crt
cp /certs/gfcert.pem conf/ssl.crt/ca-bundle.crt

Risoluzione Nomi Indirizzi

Verifichiamo l'indirizzo IP col comando:

ip a

Supponiamo di trovare l'indirizzo 172.17.0.2.

Editiamo il file /etc/hosts.

Se già esiste, modifichiamo la linea:

172.17.0.2      c59cc19e7178 geekflare.com

Altrimenti inseriamo la linea:

172.17.0.2      geekflare.com

Test della Nuova Configurazione

Col comando:

httpd -t

Potrebbe dare un warning, la prima volta, di disallineamento tra il campo CN del certificato e il nome del server.

Questo sembra essere un feature di questa versione di Apache e solitamente sparisce se si ripete il comando, una volta che il server è partito.

Attivazione del Server

Se non è ancora partito:

httpd

Se era già partito:

httpd -k restart

E verifichiamo con:

ps wax

Possiamo ora uscire dal contenitore del server:

exit

Test da Browser Esterno

Apriamo un browser e puntiamolo a https://localhost:8443.

Naturalmente compare un grosso WARNING.

Br-warning

Il CA non è configurato nel software del browser e quindi non è riconosciuto.

Accettiamo il rischio e funziona.


Versione Unix in Docker Configurato Manualmente

Run del Contenitore

Lanciamo un contenitore con semplice Alpine:

docker run -d --name htalp -v $HOME/ex/certs:/certs \
  -p 8888:80 -p 8443:443 alpine sleep 1000000

Colleghiamoci al contenitore:

docker exec -ti htalp sh

Installiamo il software:

apk add apache2 apache2-ssl apache2-utils

Andiamo nella directory di configurazione:

cd /etc/apache2

Configurazione di questo SSL

Modifichiamo il ServerName nella configurazione principale:

vi httpd.conf

Modifichiamo la linea:

ServerName geekflare.com:80

Editiamo il file di configurazione di SSL:

vi conf.d/ssl.conf

Modifichiamo i parametri:

ServerName geekflare.com:443

ServerAdmin admin@geekflare.com

Notiamo le linee:

SSLCertificateFile /etc/ssl/apache2/server.pem

SSLCertificateKeyFile /etc/ssl/apache2/server.key

Scommentiamo la linea:

SSLCACertificateFile /etc/ssl/apache2/ssl.crt/ca-bundle.pem

Copia dei Nostri File

cp /certs/certificate.pem /etc/ssl/apache2/server.pem
cp /certs/geekflare.key /etc/ssl/apache2/server.key
mkdir -p /etc/ssl/apache2/ssl.crt
cp /certs/gfcert.pem /etc/ssl/apache2/ssl.crt/ca-bundle.pem

Nostra Risoluzione Nomi Indirizzi

Verifichiamo l'indirizzo IP col comando:

ip a

Supponiamo di trovare l'indirizzo 172.17.0.2.

Editiamo il file /etc/hosts.

Se già esiste, modifichiamo la linea:

172.17.0.2      0cb3b32f54f7 geekflare

Altrimenti inseriamo la linea:

172.17.0.2      geekflare.com

Test della Nostra Configurazione

Col comando:

httpd -t

Lancio del Server

Se non è ancora partito:

httpd -k start

Se era già partito:

httpd -k restart

E verifichiamo con:

ps wax

Possiamo ora uscire dal contenitore:

exit

Test Esterno

Apriamo un browser e puntiamolo a https://localhost:8443.

Naturalmente ci compare il WARNING.

NOTA: Il warning compare solo se in un esercizio precedente NON abbiamo accettato la URL https://localhost:8443 come ecceezione.

Possiamo dal browser rimuovere questa eccezione e riprovare.

Accettiamo il rischio e funziona.

Web Server Nginx

Nginx è un server web open source ad alte prestazioni disponibile per vari sistemi Unix e Unix-like e Windows. Sviluppato inizialmente da Igor Sysoev e rilasciato nel 2004 con licenza BSD, nginx è stato concepito con lo scopo esplicito di essere più performante del web server Apache. La sua architettura ad eventi, infatti, esibisce prestazioni consistenti in presenza di carichi elevati, con maggiore efficienza rispetto al modello multi-threaded adottato da Apache. Nginx fu sviluppato con l'obiettivo di risolvere il famoso problema C10k, ovvero creare un server in grado di gestire più di diecimila client contemporaneamente senza osservare una degradazione delle prestazioni.

Nginx non è soltanto un server web. Esso infatti può agire da reverse proxy, da proxy TCP/UDP, da e-mail proxy e da load balancer. Queste caratteristiche ne hanno favorito una larga diffusione, impiegandolo soprattutto come reverse proxy o load balancer.

Architettura

Il server è composto principalmente da due tipi di processi: Master e Worker. I processi Worker (tipicamente uno per ogni core o CPU presenti nel sistema) gestiscono le connessioni con i client e le relative richieste. Sono processi che non richiedono l'accesso a elementi privilegiati del sistema. Il processo master, come si intuisce dal nome, è responsabile del coordinamento dei worker. Esso esegue alcuni compiti privilegiati quali il binding delle porte, il caricamento dei file di configurazione, ecc.

I processi worker fanno uso di una sofisticata architettura event-driven (basata su kqueue in ambienti BSD e sull'omologa implementazione su Linux, epoll) per gestire in modo concorrente le richieste provenienti dai client in modo non bloccante. Questo approccio, in radicale contrapposizione con quello multi-threaded, favorisce un migliore sfruttamento delle risorse di calcolo a parità di numero di client connessi. Avere un unico processo non bloccante che gestisce più client permette infatti di evitare frequenti cambi di contesto (o context switch) inevitabili in un ambiente fortemente multiprogrammato.

Nginx ha un'architettura modulare, e ciò consente di estendere le funzionalità offerte dal server mediante lo sviluppo di moduli custom. Esistono moduli per il supporto alla compressione (gzip, Brotli ed altri), per l'elaborazione delle immagini servite, per lo streaming audio / video, ecc.

Uso

Viene meno usato per servire pagine statiche e siti web tradizionali. Viene invece usato più per configurare proxy e load balancers.

Essendo un processo stateless (non mantiene memoria di previe richieste) è favorito in ambito Kubernetes come server per i Deployment anzichè gli StatefulSet. Viene inoltre spesso usato come gateway ad un applicativo.

Installazione

Installazione su Ubuntu

E' disponibile come pacchetto di release:

sudo apt update
sudo apt install nginx-full nginx-doc

Viene installato come servizio standard, controllabile con systemctl. E' subito running ed enabled.

La directory di gestione è /etc/nginx. Un albero di questa directory è:

.
├── conf.d
├── fastcgi.conf
├── fastcgi_params
├── koi-utf
├── koi-win
├── mime.types
├── modules-available
├── modules-enabled
│   ...
│   └── 70-mod-stream-geoip2.conf -> /usr/share/nginx/modules-available/mod-stream-geoip2.conf
├── nginx.conf
├── proxy_params
├── scgi_params
├── sites-available
│   └── default
├── sites-enabled
│   └── default -> /etc/nginx/sites-available/default
├── snippets
│   ├── fastcgi-php.conf
│   └── snakeoil.conf
├── uwsgi_params
└── win-utf

Il file di configurazione principale è nginx.conf, che include altri files.

E' possibile creare altre configurazioni xxx.conf in questa directory, con la sintassi giusta, e farle includere da nginx.conf.

Parte della struttura, ad esempio modules e sites, ricorda la versione Debian di Apache.

Installazione in Docker

Le immagini sono disponibili su Docker Hub. Sceglieremo il tag alpine:

docker pull nginx:alpine

Lancio del contenitore, con mapping della directory di configurazione in un volume docker per la persistenza delle modifiche, e della DocumentRoot in un altro volume:

Per renderli volumi permanenti, crearli manualmente:

docker volume create nginx-conf
docker volume create nginx-root
docker run -d --name nginalp -p 8088:80 \
  -v nginx-conf:/etc/nginx \
  -v nginx-root:/usr/share/nginx/html \
  nginx:alpine sleep 1000000

Usiamo il mappaggio alla porta locale 8088 per evitare collisioni con servizi simili sulla macchina host.

Collegamento:

docker exec -ti nginalp sh

Lancio di nginx:

nginx

La directory di configurazione è /etc/nginx. L'albero di configurazione è:

.
├── conf.d
│   └── default.conf
├── fastcgi.conf
├── fastcgi_params
├── mime.types
├── modules -> /usr/lib/nginx/modules
├── nginx.conf
├── scgi_params
└── uwsgi_params

La struttura è più semplice di quella della versione piena di Ubuntu.

Da notare che modules è un link simbolico a /usr/lib/nginx/modules.

La DocumentRoot (in terminologia nginx semplicemente root) è: /usr/share/nginx/html.

I log sono registrati nella directory /var/log/nginx, ma sono link simbolici a stdout e stderr, così finiscono nel log del contenitore Docker.

Si possono aggiungere in conf.d altri file di configurazione, ad esempio:

  • proxy.conf - per l'ambiente di proxy
  • sites.conf - per hosts virtuali

Controllo di Nginx in Docker

Il container ha come processo principale il server di ascolto nginx, più un certo numero di workers.

Non vi è systemd nel contenitore, così non possiamo usare systemctl.

Il controllo avviene con nginxopzioni.

Per avere aiuto:

nginx -h

Per controllare il demone nginx principale l'opzione è -s, con le possibilità:

  • nginx -s stop - ferma immediatamente il demone col segnale TERM, e ferma il contenitore.
  • nginx -s quit - ferma immediatamente il demone col segnale QUIT, e ferma il contenitore.
  • nginx -s reopen - riapre i file di log.
  • nginx -s reload - ricarica la configurazione.

Configurazione

Sintassi

La sintassi dei file di configurazione di Nginx è piuttosto semplice e ricorda quella dei linguaggi C-like.

Le varie opzioni che è possibile specificare nel file di configurazione sono dette direttive. Una direttiva può essere semplice, ovvero contenere un solo valore, o composta, ovvero costituita da più campi.

Una direttiva semplice è costituita da un nome (che identifica l'opzione da configurare), un valore (da assegnare all'opzione relativa) e termina con il carattere punto e virgola (;). Nome e valore sono separati da uno spazio.

Le direttive composte sono dette blocchi. Esse non terminano con il carattere punto e virgola. Sono invece seguite da un blocco di direttive ad esse associate delimitato da parentesi graffe. Nel gergo di Nginx, un blocco contenente delle direttive crea un contesto.

Parametri di Configurazione

I file di configurazione hanno parametri distribuiti in sezioni o contesti, che sono:

  • nessuna sezione - parametri globali, all'inizio della configurazione
  • events{...} - controllo degli MPM, secondo l'architettura event MPM
  • http{...} - relativi al protocollo HTTP
  • server{... } - relativi al server stesso

Ciascun parametro è terminato da punto e virgola (;). Per comprensibilità ogni parametro è sulla sua riga. I commenti sono preceduti da # e vanno a fine riga.

Ispezioniamo i parametri di default.

Parametri Globali

  • user nginx - l'tente coi cui permessi gira il server. Usare root è pericoloso.
  • worker_processes auto - il mumero di workers. Si può sostituire auto con un numero intero, almeno 1. auto alloca un core della CPU al processo master e i rimanenti core ciascuno ad un processo worker.
  • error_log - la locazione del log degli errori e il livello minimo di errore
  • pid - ove viene registrato il PID del processo, per poter inviargli dei segnali

Sezione events

  • worker_connections 1024 - quante connessioni client consecutive un worker può accettare nella stessa sessione

Sezione http

  • include /etc/nginx/mime.types - file con i tipi MIME e le corrispondenti estensioni
  • default_type application/octet-stream - tipo MIME di default
  • log:format - nome e struttura di un formato di log
  • access_log - locazione del file di log degli accessi e formato usato
  • sendfile on - usa la system call sendfile del kernel per l'invio di files
    • sendfile_max_chunk - dimensione massima di ogni segmento inviato con sendfile
  • #tcp_nopush on - disabilitazione del flag TCP PSH, che implementa lo Interactive Transfer a bassa latenza
  • keepalive_timeout 65 - intervallo massimo tra le richieste
  • #gzip on - compressione ``gzip` dei responsi
  • ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3 - protocolli SSL/TLS accettati

Sezione server

  • listen 80 - porta d'ascolto
  • server_name localhost - nome del server
  • location / {...} - parametri di un path, qui il percorso radice
    • root /usr/share/nginx/html - equivalente della DocumentRoot di Apache
    • index index.html index.htm - nomi dei file HTML di default
    • try_files $uri $uri/ =404 - prova prima a servire la richiesta come file, poi come directory, infine da l'errore 404
  • `location /admin {...}
    • alias /var/www/reserved - crea un alias per la location `/admin
  • error_page - pagine di errore customizzate

Esempi di Configurazione

Configurazione minima per Pagine Statiche

user nginx;
worker_processes 1;
error_log nginx_error.log;
http {
    server {
        listen 80;
        location / {
            root /var/www/html;
        }
    }
}

Configurazione Alternativa per Containers

Un utente non privilegiato e logging su standard output ed error:

worker_processes 1;
user nobody nobody;
error_log /dev/stdout;
pid /tmp/nginx.pid;
events {
  worker_connections 1024;
}
http {
    # Set an array of temp and cache files options 
    # that otherwise defaults to
    # restricted locations accessible only to root.
    client_body_temp_path /tmp/client_body;
    fastcgi_temp_path /tmp/fastcgi_temp;
    proxy_temp_path /tmp/proxy_temp;
    scgi_temp_path /tmp/scgi_temp;
    uwsgi_temp_path /tmp/uwsgi_temp;
    # mime types
    include /etc/nginx/mime.types;
    default_type application/octet-stream;

    server {
        listen 8080;
        root /usr/share/nginx/html;
        access_log /dev/stdout;
        error_log /dev/stdout;
    }
}

Forward Proxy

L'esercizio è simile a quello del Forward Proxy di Apache.

Creazione della directory di esercizio:

cd ~/ex
mkdir 03net2-ccli-cproxy-cweb
cd 03net2-ccli-cproxy-cweb

Creazione dello scaffolding:

mkdir ccli cproxy cweb
touch ccli/Dockerfile docker-compose.yml
tree

Il client ccli rimane quello del precedente esercizio:

vim ccli/Dockerfile
FROM alpine:3.7
MAINTAINER John Smith <john@stormforce.ac>

RUN apk --update add --no-cache openssh tcpdump curl

CMD ["/bin/sleep","1000000"]

Il cproxy sarà un'immagine nginx:alpine. Il server web target rimane un'immagine httpd:2.4-alpine, come in precedenza.

Creaiamo un volume docker per la persistenza della configurazione:

docker volume create ngproxy-conf

Il docker-compose.yml è:

vim docker-compose.yml
version: '3.6'

services:
  one:
    build: ccli
    image: ccli
    container_name: one
    hostname: one
    cap_add:
      - ALL
    networks:
      net1:
        ipv4_address: 192.168.101.11
  two:
    image: nginx:alpine
    container_name: two
    hostname: two
    cap_add:
      - ALL
    volumes:
      - ngproxy-conf:/etc/apache2
    networks:
      net1:
        ipv4_address: 192.168.101.10
      net2:
        ipv4_address: 192.168.102.10
  three:
    image: httpd:2.4-alpine
    container_name: three
    hostname: three
    cap_add:
      - ALL
    networks:
      net2:
        ipv4_address: 192.168.102.12
networks:
  net1:
    name: net1
    ipam:
      driver: default
      config:
        - subnet: 192.168.101.0/24
  net2:
    name: net2
    ipam:
      driver: default
      config:
        - subnet: 192.168.102.0/24
volumes:
  ngproxy-conf:
    external: true

Partenza del progetto:

docker compose up -d

Collegamento al server nginx:

docker exec -ti two sh

Nella directory di configurazione:

cd /etc/nginx

Modifichiamo il file di configurazione di default:

vi conf.d/default.conf
server {                                                       
    listen       8080;
    server_name  localhost;

    location / {
        proxy_pass http://$http_host$uri$is_args$args;
    }

}

La direttiva proxy_pass abilita il server come Forward Proxy.

La stringa http://$http_host$uri$is_args$args invia al server downstream la stessa URI che ha ricevuto dal client.

I componenti sono:

  • $http_host - URL della richiesta originale
  • $uri - path della richiesta originale
  • $is_args - query string della richiesta originale
  • $args - altri argomenti della richiesta orinale

Testare la configurazione col comando:

nginx -t

Restartare il server col comando:

nginx -s reload

Uscire:

exit

Connettersi a one e provare se il proxy funziona:

docker exec -ti one sh
export http_proxy=http://192.168.101.10:8080
curl http://192.168.102.12

Reverse Proxy

La configurazione di Nginx come reverse proxy avviene semplicemente specificando una Location con la direttiva proxy_pass. Ad esempio:

location /mia/location/ {
    proxy_pass http://www.example.com/link/;
}

Quando i client richiederanno al server l'indirizzo /mia/location, Nginx effettuerà una richiesta al server www.example.com, richiedendo la risorsa /link e restituendo il risultato della richiesta al client.

L'intera location viene mappata sul server esterno. Ad esempio, se si richiede al server l'indirizzo /mia/location/sub/pagina.html il server Nginx richiederà al server esterno la risorsa /link/sub/pagina.html.

E' possibile specificare un numero di porta:

location /mia/location/ {
    proxy_pass http://www.example.com:3000/link/;
}

E' possibile inserire headers:

location /mia/location/ {
    proxy_set_header Accept-Encoding "";
    proxy_pass http://www.example.com/link/;
}

Si può usare un buffer per le richieste:

location /mia/location/ {
    proxy_buffers 16 4k;
    proxy_buffer_size 2k;
    proxy_pass http://www.example.com/link/;
}

Si può esplicitamente disabilitare il buffering:

location /mia/location/ {
    proxy_buffering off;
    proxy_pass http://www.example.com/link/;
}

Load Balancer

Nginx può essere configurato per svolgere il ruolo di load balancer in modo molto efficiente. In particolare, nginx supporta tre differenti modalità di load balancing:

  • round-robin - Le richieste vengono distribuite in sequenza ciclica, in base all'ordine di arrivo;
  • least-connected - Ogni richiesta è assegnata al server con meno connessioni attive in quel momento;
  • ip-hash - Una funzione di hash è utilizzata come funzione di mappatura tra l'indirizzo IP del client e l'identificativo del server che gestirà la richiesta.

La direttiva upstream viene utilizzata per indicare gli indirizzi degli application server utilizzati dal reverse proxy.

Ad esempio, supponendo di voler gestire le richieste in arrivo su www.miaapp.com attraverso i server srv1.miaapp.com, srv2.miaapp.com, srv3.miaapp.com, nginx andrebbe configurato come segue:

http {
    upstream myapp1 {
        server srv1.miaapp.com;
        server srv2.miaapp.com;
        server srv3.miaapp.com;
    }
    server {
        listen 80;
        location / {
            proxy_pass http://miaapp.com;
        }
    }
}

Quando la modalità di load balancing non è specificata, nginx utilizza round-robin per default.

Per attivare la modalità least-connected è sufficiente aggiungere la direttiva leat_conn nel gruppo upstream. Ad esempio:

...
    upstream myapp1 {
        least_conn;
        server srv1.miaapp.com;
        server srv2.miaapp.com;
        server srv3.miaapp.com;
    }
...

Persistenza delle Sessioni

Sia nella modalità round-robin che in quella least-connected non vi è garanzia della persistenza delle sessioni. Ricordiamo infatti che HTTP è per sua natura stateless, e pertanto la gestione delle sessioni è sempre a carico del server. In uno scenario in cui è presente un load balancer, è possibile che le richieste in arrivo da uno stesso client e che logicamente appartengono ad una stessa sessione siano distribuite su application server differenti e che pertanto non saranno in grado di riconoscere gli ID o i cookie di sessione generati dagli altri.

Occorre configurare il load balancer in modo tale da assicurarsi che le richieste in arrivo da uno stesso client vengano sempre servite dallo stesso application server. Tale schema è implementato in nginx con la modalità ip-hash.

In ip-hash, l'indirizzo IP del client è utilizzato come chiave di hashing per la determinazione dell'application server da selezionare. Per attivare questa modalità è sufficiente specificare la direttiva ip_hash nel gruppo upstream. Ad esempio:

...
    upstream myapp1 {
        ip_hash;
        server srv1.miaapp.com;
        server srv2.miaapp.com;
        server srv3.miaapp.com;
    }
...

Specifica dei Pesi

Nginx permette di attribuire un peso numerico a ciascun application server indicato nella direttiva upstream. Server con un peso maggiore verranno selezionati più frequentemente di quelli con un peso inferiore. Per default, in assenza di una esplicita indicazione, si assume un peso pari ad 1.

Questo permette di distribuire il carico in maniera più consona rispetto alle reali capacitè di elaborazione degli application server.

Si supponga che l'application server srv1.miaapp.com sia in grado di gestire più richieste rispetto ai server srv2 e srv3. In tal caso, si può assegnare ad srv1 un peso più alto per assicurarsi che esso venga selezionato più spesso dal load balancer. Ad esempio, specificando:

http {
    upstream myapp1 {
        server srv1.miaapp.com weight=3;
        server srv2.miaapp.com;
        server srv3.miaapp.com;
    }
    server {
        listen 80;
        location / {
            proxy_pass http://miaapp.com;
        }
    }
}

Resilienza

Nginx include anche un meccanismo di monitoraggio passivo degli application server. Ciò significa che se un application server non risponde correttamente alle richieste, esso verrà marcato come non funzionante ed nginx non lo selezionerà per le richieste successive.

È possibile controllare il funzionamento di questo schema. In particolare, è possibile specificare il numero di tentativi da effettuare prima di considerare l'application server non funzionante tramite la direttiva max_fails. Per default, questo parametro è impostato ad uno, e ciò comporta che un server venga immediatamente riconosciuto come non funzionante al verificarsi del primo errore.

È inoltre possibile configurare nginx affinchè monitori lo stato dei server non funzionanti, effettuando dei controlli periodici. Il tempo che intercorre tra un controllo ed il successivo viene impostato con la direttiva fail_timeout. Per default, l'intervallo di fail_timeout à impostato a 10 secondi.

Esercizio: Nginx Reverse Proxy e Load Balancer

Preparazione

Creare la directory per l'esercizio:

cd ~/ex
mkdir -p 04net1-ccli-net2-cproxy-2cweb
cd 04net1-ccli-net2-cproxy-2cweb

Preparare lo scaffolding:

mkdir ccli cproxynx cweb
touch ccli/dockerfile docker-compose.yml
tree

Il client ccli rimane quello del precedente esercizio:

vim ccli/Dockerfile
FROM alpine:3.7
MAINTAINER John Smith <john@stormforce.ac>

RUN apk --update add --no-cache openssh tcpdump curl

CMD ["/bin/sleep","1000000"]

Il cproxy sarà un'immagine nginx:alpine. Il server web target rimane un'immagine httpd:2.4-alpine, come in precedenza.

Creaiamo un volume docker per la persistenza della configurazione:

docker volume create nginx-conf

Il docker-compose.yml è:

vim docker-compose.yml
version: '3.6'

services:
  one:
    build: ccli
    image: ccli
    container_name: one
    hostname: one
    cap_add:
      - ALL
    networks:
      net1:
        ipv4_address: 192.168.101.11
  two:
    image: nginx:alpine
    container_name: two
    hostname: two
    cap_add:
      - ALL
    volumes:
      - nginx-conf:/etc/nginx
    networks:
      net1:
        ipv4_address: 192.168.101.10
      net2:
        ipv4_address: 192.168.102.10
  three:
    image: httpd:2.4-alpine
    container_name: three
    hostname: three
    cap_add:
      - ALL
    volumes:
      - three-html:/usr/local/apache2/htdocs/
    networks:
      net2:
        ipv4_address: 192.168.102.12
  four:
    image: httpd:2.4-alpine
    container_name: four
    hostname: four
    cap_add:
      - ALL
    volumes:
      - four-html:/usr/local/apache2/htdocs/
    networks:
      net2:
        ipv4_address: 192.168.102.13
networks:
  net1:
    name: net1
    ipam:
      driver: default
      config:
        - subnet: 192.168.101.0/24
  net2:
    name: net2
    ipam:
      driver: default
      config:
        - subnet: 192.168.102.0/24
volumes:
  nginx-conf:
    external: true
  three-html:
    external: true
  four-html:
    external: true

Partenza del progetto:

docker compose up -d

Collegamento al server nginx:

docker exec -ti two sh

Nella directory di configurazione:

cd /etc/nginx

Disabilitiamo il file di configurazione del sito di default semplicemente cambiandogli nome:

mv conf.d/default.conf conf.d/default.conf.old

Modifichiamo il file principale di configurazione:

vi nginx.conf
worker_processes 5;
user nobody nobody;
error_log /dev/stdout;
events {
  worker_connections 1024;
}
http {
    include /etc/nginx/mime.types;
    default_type application/octet-stream;
    upstream bongo {
        #ip_hash;
        server 192.168.102.12;
        server 192.168.102.13;
    }

    server {
        listen 8080;
        root /usr/share/nginx/html;
        access_log /dev/stdout;
        error_log /dev/stdout;
        location / {
                proxy_pass http://bongo/;
        }
    }
}

Testare la configurazione col comando:

nginx -t

Restartare il server col comando:

nginx -s reload

Uscire:

exit

Connettersi a one e provare se il proxy funziona:

docker exec -ti one sh

Ripetere più volte:

curl two:8080

Si vede l'effetto round robin.

Per provare la persistenza di connessione, tornare nella configurazione di two e scommentare il parametro:

        ip_hash;

Riprovando, si nota che il server upstream connesso è sempre lo stesso.

Nginx e Certificati

E' necessario innanzi tutto disporre di un opportuno certificato X.509.

L'attivazione di HTTPS per un blocco server avviene specificando il parametro "ssl" per la direttiva listen. Ad esempio:

...
server {
    listen  443 ssl;
    ...
}

E' necessario specificare il percorso del certificato e della relativa chiave privata.

I percorsi dei file certificato e chiave privata sono specificati dalle direttive ssl_certificate e ssl_certificate_key, anch'esse contenute nel blocco server:

...
server {
    listen                  443 ssl;
    server_name             www.miosito.com;
    ssl_certificate         miosito.com.crt;
    ssl_certificate_key     miosito.com.key;
    ...
}

In alcuni casi è possibile che sia il certificato che la chiave privata vengano distribuiti dalla CA nello stesso file (in genere in formato PEM). In questo caso, è sufficiente specificare lo stesso nome di file per entrambe le direttive:

...
server {
    listen                  443 ssl;
    server_name             www.miosito.com;
    ssl_certificate         miosito.com.crt;
    ssl_certificate_key     miosito.com.crt;
    ...
}

Cifre ed Algoritmi

Per ragioni di sicurezza si può voler configurare il server Nginx per supportare solo alcuni tra i possibili cifrari e protocolli inclusi in TLS/SSL. Si pensi ad esempio al caso in cui una vulnerabilità 0-day venga resa nota. Una possibile contromisura in attesa di una patch potrebbe essere quella di disabilitare i protocolli affetti.

Per specificare i protocolli da utilizzare in HTTPS si ricorre alla direttiva ssl_protocols. Ad esempio, il blocco seguente abilita solo TLS 1.1 e 1.2, disabilitando TLS 1.0 e TLS 1.3 (laddove supportato):

...
server {
    listen                  443 ssl;
    server_name             www.miosito.com;
    ssl_certificate         miosito.com.crt;
    ssl_certificate_key     miosito.com.crt;
    ssl_protocols           TLSv1.1 TLSv1.2;
    ...
}

Analogamente, è possibile specificare gli algoritmi di cifratura da utilizzare con la direttiva ssl_ciphers. Ad esempio:

...
server {
    listen                  443 ssl;
    server_name             www.miosito.com;
    ssl_certificate         miosito.com.crt;
    ssl_certificate_key     miosito.com.crt;
    ssl_protocols           TLSv1.1 TLSv1.2;
    ssl_ciphers             HIGH:!aNULL:!MD5;
    ...
}

Il formato utilizzato dalla direttiva ssl_ciphers è lo stesso utilizzato dalla libreria openssl, dato che essa è alla base dell'implementazione di HTTPS in Nginx. L'elenco dei cifrari supportati può essere mostrato utilizzando il comando di shell "openssl ciphers".

Ottimizzazioni

L'utilizzo di HTTPS richiede maggiori risorse computazionali rispetto ad HTTP, non solo perché il traffico deve essere cifrato prima di essere inviato al client, ma soprattutto per la fase di handshake che come sappiamo, ricorre alla crittografia asimmetrica. Pertanto, una prima semplice ottimizzazione consigliata consiste nell'attivare il keepalive per il proprio server HTTPS. In questo modo, i client cercheranno di riutilizzare le connessioni TCP già instaurate per effettuare richieste successive, invece di aprire una nuova connessione per ogni richiesta. Ciò consente di minimizzare il numero di handshake necessari, riducendo l'impatto sulla CPU del server.

Inoltre, è possibile agire sui parametri che regolano il comportamento della cache delle sessioni di Nginx. La cache delle sessioni contiene i parametri TLS/SSL prodotti dagli ultimi handshake. Grazie ad essa, è possibile velocizzare la fase di handshake riutilizzando i parametri già negoziati in precedenza dagli stessi client, invece di ripetere un handshake completo ogni volta. Nginx permette di regolare sia la dimensione della cache in MB che il timeout (tempo trascorso il quale una sessione viene rimossa dalla cache), utilizzando rispettivamente le direttive ssl_session_cache ed ssl_session_timeout del blocco http.

Ad esempio, per impostare la cache delle sessioni a 10 MB, con un timeout di sessione di 10 minuti, si può configurare il blocco http come segue:

...
http {
    ssl_session_cache   shared:SSL:10m;
    ssl_session_timeout 10m;
    server {
        listen                  443 ssl;
        server_name             www.miosito.com;
        keepalive_timeout       70;
        ssl_certificate         miosito.com.crt;
        ssl_certificate_key     miosito.com.crt;
        ssl_protocols           TLSv1.1 TLSv1.2;
        ssl_ciphers             HIGH:!aNULL:!MD5;
        ...
    }
    ...
}

Entrambi HTTP e HTTPS

Sebbene l'utilizzo di HTTP sia ormai fortemente sconsigliato in favore di una massiccia adozione di HTTPS, vi sono dei casi in cui può essere necessario attivare entrambi i protocolli. A tal scopo, è sufficiente aggiungere una seconda direttiva listen, specificando la porta 80/TCP (HTTP) invece della 443/TCP (HTTPS). Ad esempio:

...
server {
    listen                  80;
    listen                  443 ssl;
    server_name             www.miosito.com;
    ssl_certificate         miosito.com.crt;
    ssl_certificate_key     miosito.com.crt;
    ssl_protocols           TLSv1.1 TLSv1.2;
    ssl_ciphers             HIGH:!aNULL:!MD5;
    ...
}

Certificati e Virtual Host name based

Un problema noto nell'utilizzo di HTTPS con i virtual host name based affligge anche Nginx. Nel caso in cui siano presenti più blocchi server, seppur configurati opportunamente ciascuno con il proprio certificato, si verifica infatti un comportamento inatteso: i client ricevono sempre lo stesso certificato, a prescindere dal sito richiesto, causando il fallimento dell'autenticazione del server.

Ad esempio, si supponga di aver configurato il server nel modo seguente:

...
server {
    listen          443 ssl;
    server_name     www.miosito.com;
    ssl_certificate www.miosito.com.crt;
    ...
}
...
server {
    listen          443 ssl;
    server_name     www.miosito.org;
    ssl_certificate www.miosito.org.crt;
    ...
}

Collegandosi al sito miosito.org, il client riceverà comunque il certificato associato a miosito.com, che chiaramente risulterà invalido in quanto assegnato ad un dominio diverso. Il problema, risiede nel fatto che HTTP gira "sopra" TLS/SSL: affinchè il client possa richiedere un host in particolare (usando l'header HTTP Host, per l'appunto), la connessione TLS/SSL deve essere stabilita. Tuttavia, per stabilire la connessione è necessario effettuare uno handshake e quindi ricorrere ad un certificato. Nginx in questo caso seleziona quello di default, ovvero il primo specificato.

L'approccio più comunemente adottato in passato per la risoluzione del problema consiste nell'utilizzo di virtual host IP-based invece che name based: si assegna ad ogni sito un indirizzo IP diverso. I nomi di dominio associati ai due siti verranno risolti rispettivamente sui due IP, così da superare l'ambiguità nell'individuazione del sito richiesto ancor prima di aver effettuato l'handshake TLS/SSL.

Esistono altre soluzioni, largamente utilizzate specialmente nell'ambito dell'hosting condiviso, laddove l'assegnazione di un indirizzo IP dedicato per ogni sito ospitato potrebbe non essere possibile. La prima consiste nell'utilizzare un certificato con più nomi host nel campo SubjectAltName. Questo permette di utilizzare lo stesso certificato per più host.

La soluzione oggi più adottata consiste nel superare il problema alla radice, ovvero consentendo al client di specificare l'host del sito al quale collegarsi durante lo handshake. Ciò è possibile grazie all'estensione del protocollo TLS nota come SNI, ovvero Server name Indication Extension. La SNI è disponibile in Nginx a partire dalla versione 0.8.21 ed è largamente supportata da browser e librerie HTTP da diversi anni.

Esercizio: SSL con Nginx

Preparazione

Lanciamo un contenitore:

docker run -d --name ngalp -v $HOME/ex/certs:/certs \
  -p 8888:80 -p 8443:443 nginx:alpine sleep 1000000

Colleghiamoci al contenitore:

docker exec -ti ngalp sh

Andiamo nella directory di configurazione:

cd /etc/nginx

Configurazione SSL

Editare il file di configurazione del server:

conf.d/default.conf
server {

    listen                      80;
    listen                      443 ssl;
    server_name                 geekflare.com;
    ssl_certificate             certs/geekflare.pem;
    ssl_certificate_key         certs/geekflare.key;
    ssl_protocols               TLSv1 TLSv1.1 TLSv1.2 TLSv1.3;
    ssl_ciphers                 HIGH:!aNULL:!MD5;

    access_log  /var/log/nginx/host.access.log  main;

    location / {
        root   /usr/share/nginx/html;
        index  index.html index.htm;
    }

}

Copia dei File

mkdir certs
cp /certs/certificate.pem certs/geekflare.pem
cp /certs/geekflare.key certs/geekflare.key

Risoluzione Nome Indirizzo

Modificare il file /etc/hosts, aggiungendo la linea:

172.17.0.2      65ebcf7688ab geekflare.com

Test della Configurazione

Col comando:

nginx -t

Lancio del Server

Se non è già partito:

nginx

Se è già partito:

nginx -s reload

E verificare i processi:

ps wax

Quindi uscire:

exit

Connettività Esterna

Apriamo un browser e puntiamolo a https://localhost:8443.

Naturalmente ci compare il WARNING.

NOTA: Il warning compare solo se in un esercizio precedente NON abbiamo accettato la URL https://localhost:8443 come ecceezione.

Possiamo dal browser rimuovere questa eccezione e riprovare.

Accettiamo il rischio e funziona.

Nginx Unit

Unit è un Application Server universale.

Permette di:

  • servire pagine statiche
  • compiere il run di applicativi scritti in otto linguaggi diversi supportati
  • gestire connessioni SSL/TLS
  • fungere da proxy, reverse proxy e balancer

La gestione di Unit avviene dinamicamente tramite una API REST definita.

Non è necessario il restart del server Unit dopo una variazione di configurazione.

Unit è stato sviluppato dal team di Nginx, ma è un prodotto indipendente.

Installazione

Per quanto Unit sia stato prodotto dal team di Nginx, non occorre installare Nginx per usare Unit.

Installazione su Ubuntu e Simili

NOTA

Questo è soltanto descrittivo. Noi non installeremo Unit direttamente sulla macchina host, ma useremo un'immagine Docker.

Occorre conoscere l'esatta versione della release Ubuntu o della versione Ubuntu da cui la corrente distribuzione è derivata.

Su Ubuntu;

cat /etc/lsb-release

Su, p.es., Mint:

cat /etc/upstream-release/lsb-release 
DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=22.04
DISTRIB_CODENAME=jammy
DISTRIB_DESCRIPTION="Ubuntu Jammy Jellyfish"

Le seguenti istruzioni si riferiscono quindi alla release 22.04 di Ubuntu.

Download della firma dei pacchetti:

sudo curl --output /usr/share/keyrings/nginx-keyring.gpg  \
      https://unit.nginx.org/keys/nginx-keyring.gpg

Editare il file di configurazione repositories:

sudo vim /etc/apt/sources.list.d/unit.list
deb [signed-by=/usr/share/keyrings/nginx-keyring.gpg] https://packages.nginx.org/unit/ubuntu/ jammy unit
deb-src [signed-by=/usr/share/keyrings/nginx-keyring.gpg] https://packages.nginx.org/unit/ubuntu/ jammy unit

Installare Unit:

sudo apt update
sudo apt install unit

Ricercare pacchetti d'uso di Unit con vari linguaggi:

apt search --names-only '^unit-'

Al momento Unit supporta:

  • Go
  • Java (varie versioni)
  • Perl
  • PHP
  • Python 2.7 e 3.10
  • Ruby
  • WASM (Web Assembly)

Non si possono installare tutti i linguaggi supportati da Unit.

Per esempio, l'installazione del modulo Go include l'installazione del linguaggio Go stesso. E' un pacchetto poderoso.

Molto meglio usare immagini Docker preconfigurate con Unit e il supporto ad un linguaggio specifico.

Installazione su Docker

Dato un linguaggio di sviluppo, scarichiamo l'immagine Docker che da quel linguaggio e il supporto a Unit.

Per esempio, per il linguaggio Go:

docker pull unit:1.32.0-go1.21

Sono disponibili le seguenti immagini:

ImageDescription
unit:1.32.0-minimalNo language modules; based on the debian:bullseye-slim image.
unit:1.32.0-go1.21Single-language; based on the golang:1.21 image.
unit:1.32.0-jsc11Single-language; based on the eclipse-temurin:11-jdk image.
unit:1.32.0-node20Single-language; based on the node:20 image.
unit:1.32.0-perl5.38Single-language; based on the perl:5.38 image.
unit:1.32.0-php8.2Single-language; based on the php:8.2-cli image.
unit:1.32.0-python3.11Single-language; based on the python:3.11 image.
unit:1.32.0-ruby3.2Single-language; based on the ruby:3.2 image.
unit:1.32.0-wasmSingle-language; based on the debian:bullseye-slim image.

Partenza del Contenitore Unit

Faremo ora partire il contenitore.

Per dargli un indirizzo IP preciso occorre prima creare una rete Docker con CIDR contenente tale indirizzo:

docker network create --subnet 192.168.100.0/24 unitnet

Il lancio del contenitore è:

docker run -d --name unitgo --net unitnet --ip 192.168.100.10 \
  -v /www:/www -p 8080:8080 -p 80:80 unit:1.32.0-go1.21 \
  unitd --control 192.168.100.10:8080 --no-daemon

Non usiamo il comando di default del contenitore. Creerebbe un socket di controllo Unix, e potremmo accedere al server Unix solo da dentro il contenitore.

Diamo invece come comando del contenitore:

unitd --control 192.168.100.10:8080 --no-daemon

Questo crea il socket di controllo Inet 192.168.100.10:8080, che ci permette di interagire con Unit da un altro contenitore o dalla macchina host.

L'opzione --no-daemon lancia unit in foreground. Se non vi fosse partirebbe in background, non vi sarebbe il processo 1 del contenitore, che terminerebbe subito.

Controllo di Raggiungibilità

Pagina di Default

Apriamo un browser all'indirizzo 192.168.100.10. Se funziona vedremo la pagina di benvenuto.

Unit-welcome

Lo stesso lo otteniamo col comando curl, una utility che useremo sempre nell'interazione con Unit.

curl -X GET 192.168.100.10

Connessione di Controllo

Per avere una lista della configurazione corrente di Unit, ci colleghiamo alla porta di controllo:

curl -X GET 192.168.100.10:8080

L'output è una stringa JSON con la configurazione corrente.

Pagine Statiche

Pagina HTML Statica

Creiamo una directory per gli esercizi:

mkdir ~/unit

Creiamo anche una directory di share, che sarà visibile dal contenitore Unit. Questa directory apparterrà all'utente corrente e avrà permessi di lettura pubblici.

sudo mkdir /www
sudo chown $USER:$USER /www
chmod 755 /www

Creiamo una directory e una pagina HTML statica sulla directory condivisa:

mkdir /www/data
vim /www/data/index.html
<!DOCTYPE html>
<html>
   <head>
      <title>Welcome to NGINX Unit!</title>
      <style>
         body {
         width: 35em;
         margin: 0 auto;
         font-family: Tahoma, Verdana, Arial, sans-serif;
         }
      </style>
   </head>
   <body>
      <h1>Welcome to NGINX Unit!</h1>
      <p>If you see this page, the NGINX Unit web server is successfully
         installed and working.  Further configuration is required.
      </p>
      <p>For online documentation and support, please refer to
         <a href="https://unit.nginx.org/">unit.nginx.org</a>.<br/>
      </p>
      <p><em>Thank you for using NGINX Unit.</em></p>
   </body>
</html>

Mella directory di esercizio, creiamo uno snippet di codice in JSON:

cd ~/unit
vim json.snippet
{
    "listeners": {
        "*:8081": {
            "pass": "routes"
        }
    },

    "routes": [
        {
            "action": {
                "share": "/www/data$uri"
            }
        }
    ]
}

Questo è un segmento di configurazione di Unit.

Viene creato un listener, che ascolta su un certo indirizzo IP e porta. Quando arriva un messaggio a questo listener, lo passa ad una route. La route unica configurata serve una pagina statica condivisa sotto /www/data, aggiungendo al percorso la parte uri, ovvero il path della richiesta.

P.es. se la richiesta è a *:8080/blob.html, viene servita la pagina /blob.html. L'asterisco (*) rappresenta qualsiasi indirizzo IP.

Il default del route / è come di solito /index.html.

Sottomettiamo lo snippet a Unit:

curl -X PUT --data-binary @json.snippet 192.168.100.10:8080/config

Apriamo un browser a 192.168.100.10:8081 e vedremo la pagina.

Static-file

Tutte le pagine web scritte sotto /www/data sono ora visibili sotto 192.168.100.10:8081 senza ulteriori modifiche di configurazione. E' l'equivalente di un sito virtuale.

Web Application Server

Esercizio: Applicativo Go

Codice Sorgente

Creiamo la directory in cui scriveremo il programma sorgente e la directory in cui porremo l'eseguibile:

mkdir -p /www/go/src/hello
mkdir -p /www/app
cd /www/go/src/hello

Scriviamo il programma:

package main

import (
  "fmt"
  "log"
  "net/http"
  "unit.nginx.org/go"
)

func handler(w http.ResponseWriter, r *http.Request) {
  fmt.Fprintln(w, "Hello, 世界")
}

func main() {
  http.HandleFunc("/", handler)
  fmt.Println("Running demo app. Press Ctrl+C to exit...")
  log.Fatal(unit.ListenAndServe(":8888", nil))
}

Da notare:

  • deve importare il pacchetto di libreria unit.nginx.org/go
  • la tradizionale funzione http.ListenAndServe() è sostituita da unit.ListenAndServe()
  • non vi sono altri cambiamenti

Compilazione

Se non già lanciato, compiamo il run del contenitore Unit:

docker run -d --name unitgo --net unitnet --ip 192.168.100.10 \
  -v /www:/www -p 8080:8080 -p 80:80 unit:1.32.0-go1.21 \
  unitd --control 192.168.100.10:8080 --no-daemon

Apriamo una sessione al contenitore Unit:

docker exec -ti unitgo bash

Posizioniamoci nella directory del nostro programma:

cd /www/go/src/hello

Creiamo un modulo di compilazione:

go mod init hello

Importiamo le dipendenze necessarie:

go get unit.nginx.org/go@1.32.0

Compiliamo il programma:

go build -o /www/app/hello .

Attenzione a non omettere il punto finale.

Configurazione Unit

Usciamo dal contenitore ed andiamo nella nostra directory degli esercizi:

exit
cd ~/unit

Prepariamo uno snippet di configurazione:

vim go.snippet
{
  "listeners": {
      "*:8082": {
          "pass": "applications/go"
      }
  },
  "applications": {
      "go": {
          "type": "external",
          "working_directory": "/www/",
          "executable": "/www/app/hello"
      }
  }
}

Esecuzione e Test

Sottoponiamo lo snippet a Unit:

curl -X PUT --data-binary @go.snippet 192.168.100.10:8080/config

Test dell'applicativo:

curl 192.168.100.10:8082
Hello, 世界

Conclusione

Il web server rimane un componente essenziale degli applicativi moderni, specialmente gli applicativi distribuiti.

La tecnologia è ben assestata da parecchie decine d'anni, ma nello stesso tempo in continua evoluzione.

Il futuro vedrà un affermarsi del ruolo di un web server come portale d'accesso a moduli di gestione delle richieste dei client, specialmente strutturati in microservizi e inseriti in container.