Introduzione
Licenza
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
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:
Esempio di responso indicante fallimento:
Risorsa
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
ovexx
è 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
Metodo | Azione |
---|---|
GET | Trasferisce in download nel corpo la risorsa specificata dal server al client |
HEAD | Responso identico a GET nello header, ma senza il dowload dell'oggetto |
POST | Trasferisce in upload il contenuto del corpo ad un programma di processamento sul server |
PUT | Trasferisce in upload il contenuto del corpo alla risorsa specificata, creandola o rimpiazzandola |
DELETE | Cancella dal server la risorsa specificata |
CONNECT | Crea un tunnel tramite un proxy server con una destinazione finale specificata come risorsa |
OPTIONS | Richiede i metodi disponibili per la risorsa specificata |
TRACE | Compie un echo della richiesta nel responso, per debugging |
PATCH | Compie una modifica parziale alla risorsa, con le istruzioni contenute nel corpo |
Esempio di Metodi HTTP
Richiesta GET
Richiesta HEAD
Richiesta 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:
Codice | Tipo Evento | Spiegazione |
---|---|---|
1xx | Informational Response | Richiesta ricevuta e il processamento continua |
2xx | Success | La richiesta è stata accettata |
3xx | Redirection | Richieste ulteriori azioni per completare la richiesta |
4xx | Client Error | Ra richiesta contiene errori e non viene accettata |
5xx | Server Error | La 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:
Codice | Tipo Evento | Spiegazione |
---|---|---|
200 | Ok | Richiesta completata con successo e viene ritornato l'oggetto richiesto |
201 | Created | Un oggetto è stato creata con successo |
202 | Accepted | Richiesta accettata ma non ancora processata |
204 | No Content | Richiesta completata con successo, ma non è richiesto on oggetto di ritorno |
301 | Moved Permanently | Specifica una URL a cui la richiesta deve sempre rivolgersi |
302 | Found (Moved Temporarily) | Specifica una URL a cui la richiesta deve rivolgersi questa singola volta |
400 | Bad Request | Richiesta mal formata |
401 | Unauthorized | Non è stata fornita autenticazione |
403 | Forbidden | Autenticazione errata o azione proibita dalle policy |
404 | Not Found | La risorsa richiesta non esiste |
405 | Method Not Allowed | Metodo di richiesta errato |
406 | Not Acceptable | Nessun metodo di negoziazione contenuto è disponibile |
407 | Proxy Authentication Required | Occorre prima l'autenticazione col proxy |
408 | Request Timeout | Troppo tempo per terminare la richiesta |
414 | URI Too Long | La URL di un GET è troppo lunga ed occorre usare un POST |
417 | Expectation Failed | Specifico tipo dello header Expect non è disponibile |
418 | I'm A Teapot | Uovo di Pasqua a volte usato invece di 403 Forbidden |
429 | Too Many Requests | Ecceduto il limite quando vi è un meccanismo di rate limiting |
451 | Unavailable For Legal Reasons | Accesso negato per motivi legali (da Fahrenheit 451) |
500 | Internal Server Error | Errore generico del server, non specificato |
501 | Not Implemented | Il server non conosce correntemente come processare la richiesta |
502 | Bad Gateway | Il server è un proxy e non riesce a contattare la destinazione |
503 | Service Unavailable | Indisponibilità temporanea del servizio |
504 | Gateway Timeout | Il 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:
Codice | Tipo Evento | Server | Spiegazione |
---|---|---|---|
218 | This is fine | Apache | Errore in combinazione col settaggio ProxyErrorOverride |
420 | Method Failure | Spring | Fallimento di un metodo Java invocato |
430 | Shopify Security Rejection | Shopify | Errore di sicurezza |
440 | Login Time-out | Microsoft | Sessione scaduta |
450 | Blocked by Windows Parental Controls | Microsoft | Bloccato dal controllo parentale |
494 | Request header too large | Nginx | La testata della richiesta è troppo grossa |
495 | SSL Certificate Error | Nginx | Il certificato cliente è invalido |
496 | SSL Certificate Required | Nginx | Occorre un certificato cliente |
497 | HTTP Request Sent to HTTPS Port | Nginx | Protocollo di richiesta errato |
509 | Bandwidth Limit Exceeded | Apache | Superato 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
Il Metodo 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:
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
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
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
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
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
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.
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.
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
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
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.
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.
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.
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
oapachectl
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 serviziosudo systemctl stop apache2
- ferma il serviziosudo systemctl enable apache2
- abilita il servizio, cioè lo lancia automaticamente al prossimo bootsudo systemctl disable apache2
- disabilita il servizio, cioè NON lo lancia automaticamente al prossimo bootsudo 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 disponibilimods-enabled/*.load
- moduli abilitati, comando di caricamento del modulomods-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 disponibilesites-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 SSLdefault-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 baseconf-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 serverinfo.conf
- parametri per il route alias/status
languages.conf
- supporto a lingue oltre l'americano e set di caratteri oltre ASCIImpm.conf
- gestione del pool dei servers, o del numero di processi figli generatiuserdir.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 logsDocumentRoot "/var/www/localhost/htdocs"
- directory di base per le pagine staticheListen 80
- ascolta sulla porta 80ServerName www.example.com:80
- nome ufficiale del server. Se manca, viene preso allo start l'indirizzo IP del server, e prodotto un warningServerAdmin you@example.com
- posta elettronica dell'amministratoreServerSignature On
- aggiunge nel responso uno header con la versione del server e nome del virtual host. Possibilità:On | Off | EMail
. ConEMail
aggiunge anche unmailto:
all'amministratoreServerTokens 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 secondiKeepAlive On
- se i client possono inviare più richieste nella stessa sessioneMaxKeepAliveRequests 100
- numero massimo di richieste per sessioneKeepAliveTimeout 5
- intervallo in secondi tra richieste successive nella stessa sessioneUseCanonicalName Off
- se identificarsi con il Fully Qualified Domain Name (FQDN) del DNS [On] o con ilServerName
[Off]HostnameLookups Off
- se risolvere a DNS l'indirizzo IP dei client delle richiesteAccessFileName .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 inizialiMinSpareServers
- numero minimo dei processiMaxSpareServers
- numero massimo dei processiMaxRequestWorkers
- numero massimo di richieste per processoMaxConnectionsPerChild
- 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'accessoAllowOverride 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'accessoOptions Indexes FollowSymLinks
- gli indici di directory sono abilitati, e i link simbolici vengono seguitiAllowOverride 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 autenticazioneFileInfo
- informazioni sui fileIndexes
- indici: ilGET
di una directory ritorna l'indice della directoryLimit
- lista di metodi HTTP consentiti nella directoryOptions=opzioni
- lista delle opzioni, separate da virgolaall
- tuttonone
- niente
Options
Le opzioni possibili sono:
Indexes
- indici della directoryFollowSymLinks
- seguire tutti i link simbolici (default in assenza di altri)SymLinksIfOwnerMatch
- seguire un link solo se dello stesso utente del file riferitoIncludes
- usare i Server Side Includes (SSI). Richiede il modulomod_include
IncludesNOEXEC
- usare gli SSI eccettoexec
ExecCGI
- eseguire programmi configurati col Common Gateway Interface (CGI). Richiede il modulomod_cgi
MultiViews
- negoziazione di contenuto. Richiede il modulomod_negotiations
All
- tutte le opzioni tranneMultiViews
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 tuttiRequire all denied
- negato a tuttiRequire valid-user
- permesso ad utenti che si sono autenticati con uno schema di accesso, definito precedentemente in configurazioneRequire host starshell.sh
- qualsiasi compclientuter appartenente al dominioRequire not host gov
- clients non appartenenti al dominioRequire ip 192.168.27
- clients con indirizzo IP in una certa retaRequire not ip 192.168.27.11
- clients non col determinati indirizzo IPRequire 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
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.
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, sottoServerRoot
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
- possibilità, in ordine crescente di severità:
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ù cortocombined
- con più informazionicombinedio
- 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
Segnalino | Elemento |
---|---|
%h | indirizzo IP del client |
%l | un trattino (- ) |
%u | nome utente, o trattino se manca |
%t | timestamp |
%r | richiesta |
%>s | stato del responso |
%b | dimensione dell'oggetto ritornato |
%I | bytes ricevuti |
%O | bytes inviati |
%{Referer}i | referer - indirizzo da cui la richiesta è provenuta tramite un link |
%{User-Agent}i | informazioni 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 direttoErrorDocument 404 /missing.html
- pagina statica localeErrorDocument 404 "/cgi-bin/missing_handler.pl"
- programma CGI di gestioneErrorDocument 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 codificaAddType application/x-compress .Z
- aggiunta del tipo MIME corrispondente alla codificaAddType 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
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, requisitomod_proxy_http
: gestisce i protocolli HTTP&HTTPSmod_ssl
: supporta i protocolli SSL v3 e TLS v1.x
Esercizio su Debian e Ubuntu
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 serverServerAlias
: nomi aggiuntivi accettati dal serverServerAdmin
: posta elettronica dell'amministratoreErrorLog
: file degli erroriCustomLog
: log delle richieste dei client al serverProxyPass
: 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 adoff
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
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
Cifrario Perfetto
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.
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
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.
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
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
, ...
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.
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.
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).
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
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.
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.
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
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
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
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:
E' necessaria anche la chiave privata del certificatore. per decrittare il certificato:
La chiave privata è di solito precaricata sul SW dell'utente.
Certificati Speciali
Certificate Chain:
Self-signed Certificate (Root CA):
Uso del Certificato
Esempio della situazione con SSL/TLS.
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.
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
-
Un Soggetto, p.es. un Web Server (chi lo amministra) genera una coppia di chiavi, pubblica e privata.
-
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.
-
La CA possiede un Autocertificato (SSC - Self-Signed Certificate), di cui ci si fida a priori, ed una Infrastruttura di Certificazione.
-
La CA verifica i dati di identità della CR.
-
La CA genera un Certificato Soggetto (SC - Subject Certificate) e lo invia al Soggetto richiedente.
-
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 categoriaHIGH
solo algoritmi ad almeno 256 bit. Così il vecchio defaultmd5
non va bene, mentresha256
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 autenticazioneHIGH
- hash con almeno 256 bit, p.es. SNA256MEDIUM
- hash con almeno 128 bir, p.es. SHA1LOW
- 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.
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 nginx
opzioni
.
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 MPMhttp{...}
- relativi al protocollo HTTPserver{... }
- 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. Usareroot
è pericoloso.worker_processes auto
- il mumero di workers. Si può sostituireauto
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 errorepid
- 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 estensionidefault_type application/octet-stream
- tipo MIME di defaultlog:format
- nome e struttura di un formato di logaccess_log
- locazione del file di log degli accessi e formato usatosendfile on
- usa la system callsendfile
del kernel per l'invio di filessendfile_max_chunk
- dimensione massima di ogni segmento inviato consendfile
#tcp_nopush on
- disabilitazione del flag TCPPSH
, che implementa lo Interactive Transfer a bassa latenzakeepalive_timeout 65
- intervallo massimo tra le richieste#gzip on
- compressione ``gzip` dei responsissl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3
- protocolli SSL/TLS accettati
Sezione server
listen 80
- porta d'ascoltoserver_name localhost
- nome del serverlocation / {...}
- parametri di un path, qui il percorso radiceroot /usr/share/nginx/html
- equivalente della DocumentRoot di Apacheindex index.html index.htm
- nomi dei file HTML di defaulttry_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:
Image | Description |
---|---|
unit:1.32.0-minimal | No language modules; based on the debian:bullseye-slim image. |
unit:1.32.0-go1.21 | Single-language; based on the golang:1.21 image. |
unit:1.32.0-jsc11 | Single-language; based on the eclipse-temurin:11-jdk image. |
unit:1.32.0-node20 | Single-language; based on the node:20 image. |
unit:1.32.0-perl5.38 | Single-language; based on the perl:5.38 image. |
unit:1.32.0-php8.2 | Single-language; based on the php:8.2-cli image. |
unit:1.32.0-python3.11 | Single-language; based on the python:3.11 image. |
unit:1.32.0-ruby3.2 | Single-language; based on the ruby:3.2 image. |
unit:1.32.0-wasm | Single-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.
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.
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 daunit.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.