Come accedere a più macchine in DMZ tramite un solo indirizzo IP e un forward proxy

In ufficio abbiamo un domino Internet locale e varie macchine con alcune applicazioni web che operano in maniera federata, vale a dire che quando ci si collega alla prima si viene rimandati ad un’altra per l’autenticazione e comincia un rimpallo tra le varie macchine (sempre tramite http redirect, cioè tramite il browser sul PC client) finché non si supera sia l’autenticazione che l’autorizzazione. Questa modalità di autenticazione è chiamata federativa o anche «per attestazioni», poiché in una applicazione ci si fida di un server di autenticazione, il quale può essere federato con altri tra i quali è stabilita la fiducia. L’idea è che si arrivi ad autenticare l’utente in maniera automatica, tramite il single sign on, vale a dire che l’utente viene riconosciuto perché ha già fatto il login nel suo sistema operativo e non deve rifarlo per ogni applicazione. Il protocollo utilizzato per scambiare informazioni tra i vari siti implicati è quello SAML, mentre, per fare un po’ di nomi, le tecnologie coinvolte sono la fedlet di Java, la AD FS di Microsoft e altre meno conosciute.

Facciamo l’esempio dell’ufficio con tutte le macchine nel dominio ufficio.local. Avremo quindi dei nomi di macchine fisiche come vm1.ufficio.local o vm2.ufficio.local, e CNAME per le applicazioni, come adfs.ufficio.local e app1.ufficio.local. Le applicazioni sono tutte web, ma utilizzano le porte più disparate, la 443, 8443, la 9653 ed altre ancora, tutte rigorosamente cifrate tramite SSL con certificati emessi dalla ca.ufficio.local.

Il problema è: voglio permettere l’accesso da fuori dell’ufficio protetto dal firewall, senza utilizzare nessuna VPN che dia accesso a tutta la rete interna o che richieda di configurare il firewall in maniera potenzialmente errata.

La soluzione è stata la seguente:

  • configurare il primo firewall per inoltrare tutte le porte delle varie applicazioni web al secondo firewall,

  • configurare il secondo firewall in modo che inoltri tutte le porte delle varie applicazioni ad una sola porta di una macchina proxy sulla quale è configurato pound,

  • configurare pound perché inoltri le varie richieste alla macchina corretta nella LAN.

pound è un proxy HTTP, vale a dire che si mette in ascolto su una porta e, tramite protocollo HTTP, riceve le richieste che poi smista in base a regole sulle intestazioni della richiesta HTTP. pound è in grado di mettersi in ascolto su una porta SSL e di comunicare con il server effettivo, sempre via SSL.

Perché il certificato SSL di pound sia riconosciuto e accettato dai browser per tutti i nomi delle varie macchine, ci sono alcune strade:

  • la prima è avere un certificato per ogni macchina (in questo caso pound usa l’estensione SNI (sigla che sta per Server Name Indication: suggerimento sul nome del server) del protocollo TLS (l’attuale estensione di SSL) per capire quale servername è richiesto dal client),

  • la seconda è avere un certificato che corrisponde a tanti nomi (in questo caso di utilizza l’estensione subjectAlternativeNames del certificato),

  • la terza è avere un certificato che vale per tutto il dominio.

Utilizzo la seconda possibilità e genero un certificato e una richiesta di firma (CSR: Certificate Sign Request) usando lo strumento keytool di java. Nota: questo strumento non crea automaticamente la CSR con tutti i subjectAlternativeNames, ma li si deve specificare sia per la creazione delle chiavi iniziali, sia per la creazione della CSR.

Userò pound.ufficio.local che è il nome della macchina sulla quale ho messo pound, xi12.ufficio.local che è una delle applicazioni, erpln.ufficio.local è la seconda applicazione, adfs.ufficio.local è la terza.

keytool -genkeypair -alias pound -keyalg RSA -keysize 2048 \
-dname "CN=pound.ufficio.local, OU=demo, O=ufficio, L=Torino, C=IT" \
-ext SAN=dns:pound.ufficio.local,dns:xi12.ufficio.local,\
dns:erpln.ufficio.local,dns:adfs.ufficio.local \
-validity 3650 -keypass 'password' -keystore pound.pfx \
-storepass 'password' -storetype PKCS12

keytool -certreq -alias pound -file pound.csr \
-ext SAN=dns:pound.ufficio.local,dns:xi12.ufficio.local,\
dns:erpln.ufficio.local,dns:adfs.ufficio.local \
-keypass 'password' -keystore pound.pfx -storepass 'password' \
-storetype PKCS12

A questo punto prendo il file pound.csr, lo porto alla mia autorità di certificazione, la quale restituisce il certificato pound.cer.

Poiché pound vuole un solo file con la chiave privata e con il certificato, tutto in formato PEM, estraggo la chiave privata da file PFX iniziale e le tolgo la password di protezione, poi creo il file completo per pound:

openssl pkcs12 -in pound.pfx -nocerts -out pound.pw.key # sempre con password
openssl rsa -in pound.pw.key -out pound.nopw.key # senza password
cat pound.cer pound.nopw.key > ssl.pem
rm pound.pfx pound.csr pound.cer pound.nopw.key pound.pw.key
sudo mv ssl.pem /etc/pound/ssl.pem
sudo chown root:root /etc/pound/ssl.pem
sudo chmod go-rwx /etc/pound/ssl.pem

Infine creo il file di configurazione per pound, /etc/pound/pound.cfg:

ListenHTTPS
  Address 192.168.245.99
  Port 8442
  Cert "/etc/pound/ssl.pem"
  LogLevel 5

  ## allow PUT and DELETE also (by default only GET, POST and HEAD)?:
  xHTTP 1

  Service
      IgnoreCase 1
      URL ".*"
      HeadRequire "Host:.*xi12.ufficio.local:9543.*"
      BackEnd
        Address xi12.ufficio.local
        Port 9543
        HTTPS
      End
  End

  Service
      IgnoreCase 1
      URL "^/(bundles/|Scripts/|infor|api/|template/|Content/|CollaborationUI/|webresource/|user/|IONAPIUI/).*"
      HeadRequire "Host:.*xi12.ufficio.local.*"
      BackEnd
         Address xi12.ufficio.local
         Port 8443
         HTTPS
      End
  End

  Service
      IgnoreCase 1
      URL "^/lnui/.*"
      HeadRequire "Host:.*erpln.ufficio.local.*"
      BackEnd
        Address erpln.ufficio.local
        Port 8443
        HTTPS
      End
  End

  Service
      IgnoreCase 1
      URL "^/adfs.*"
      HeadRequire "Host:.*adfs.ufficio.local.*"
      BackEnd
        Address adfs.ufficio.local
        Port 443
        HTTPS
      End
  End
End

Ora si può avviare pound.

Come punto finale, per potersi collegare dall’esterno, si deve inserire nel proprio file /etc/hosts una riga con l’ip pubblico dell’ufficio (quello associato al firewall esterno) e tutti i nomi delle macchine e applicazioni alle quali si deve accedere.

Una volta fatta la prova, si vede che questa configurazione non funziona. Il problema che ho riscontrato è che il server adfs.ufficio.local utilizza a sua volta la SNI, ma pound non imposta l’estensione servername di openssl e quindi la SNI non funziona. A questo punto ho cercato la soluzione sul web e ho trovato che il problema è già stato sollevato da un’altra persona, alla quale è stata fornita una patch non ancora inserita nel codice di pound. La patch però funziona: difatti l’ho scaricata, poi ho fatto le modifiche suggerite nella pagina in questione, ho ricompilato il pacchetto Debian e l’ho installato e provato. Funziona. La patch è in questo thread.

Using functions that return result set in SELECT part of an SQL query in PostgreSQL

[Pagina in italiano]
Lately I had to write a query that transform each record of a table into a serie of records. Specifically, from a table with two colums (first is a key, second is a list of car plates) I had to extract a result set with two colums: the key and a single plate. For all record that had more than one plate, I was asked to duplicate the record in order to have a single car plate as second column.

In other words:

postgres=# create temporary table t (key varchar primary key, plates varchar);
postgres=# insert into t values ('000000','AA888BB CC777DD GG333JJ'), ('111111','ZZ888KK');
INSERT 0 2
postgres=# select * from t;
  key   |         plates
--------+-------------------------
 000000 | AA888BB CC777DD GG333JJ
 111111 | ZZ888KK

what I wanted was:

  key   |         plate
--------+-------------------------
 000000 | AA888BB
 000000 | CC777DD
 000000 | GG333JJ
 111111 | ZZ888KK

The solution I found is:

postgres=# select key,
    unnest(regexp_split_to_array(plates, E'\\s+')) AS plate from t;
  key   |  plate
--------+---------
 000000 | AA888BB
 000000 | CC777DD
 000000 | GG333JJ
 111111 | ZZ888KK

What does this query? First, it convert a list (plates) into an array using a space separator, and second, it convert the arrary into a relation of possibly many records.

But, I didn’t like this solution, and moreover, I did not even undertand it. So, a few questions arose:

  1. how is it possible that postgresql allow me to specify a function tha return a set in the SELECT part instead of the FROM part? When I studied SQL, I learned that FROM is for specifying all my data sources (relations), and SELECT for specifying what to display and eventually how to format them.
  2. how does postgresql choose creating a cartesian product multiplying a first element (a single value “key”) and a second one (a relation “plate”)?
  3. how postgresql define this second relation that is not a fixed one since it depends on a filed taken from the current record? I.e., for each “key” there is a specific relation “plate”. Furthermore, if this is really a cartesia product, addin a new unnest would create 4×3 records. Let’s try:
    postgres=# select key,
        unnest(regexp_split_to_array(plates, E'\\s+')) AS plate1,
        unnest(regexp_split_to_array(plates, E'\\s+')) AS plate2 from t;
      key   | plate1  | plate2
    --------+---------+---------
     000000 | AA888BB | AA888BB
     000000 | CC777DD | CC777DD
     000000 | GG333JJ | GG333JJ
     111111 | ZZ888KK | ZZ888KK
  4. why this has not lead to a new cartesian product? May this be related to IMMUTABLE functions like, probably, the unnest one? (I think to remember that IMMUTABLE functions are functions that do not chage result when you call it using the same arguments. In this case postgresql would avoid to call them many time and directly use the result. But why it does not do a new product?) Let’s test is differently, with another array:
    postgres=# select key,
        unnest(regexp_split_to_array(plates, E'\\s+')) AS plate1,
        unnest('{1,2}'::int[]) AS array2 from t;
      key   | plate1  | array2
    --------+---------+--------
     000000 | AA888BB |      1
     000000 | CC777DD |      2
     000000 | GG333JJ |      1
     000000 | AA888BB |      2
     000000 | CC777DD |      1
     000000 | GG333JJ |      2
     111111 | ZZ888KK |      1
     111111 | ZZ888KK |      2

    here, a cartesian product is made.

So, without much understanding of that is going on, I contacted a couple of mailing lists until I got an answer by Tom Lane (here).

The answer contains a few points:

  1. the best way to wite this query is to use the LATERAL subqueries, moving the function that create the arry from the SELECT to the FROM part, and to replace the two functions unnest+regexp_split_to_array with a single function regexp_split_to_table:
    select key, targa
        from t,
        lateral regexp_split_to_table(plates, E'\\s+') as plate;
  2. utilizing functions that return a result set in SELECT part, is a hangover from Berkeley QUEL. This part of the code is not something Lane is happy. Moreover he notes that with postgresql 10, the code that manage these functions that returns more rows has been rewritten in order to better isolate it from other code (see the link in his email)
  3. the cartesian product is not made for every function called. The final number of records is base on the least common multiple of all periods of the functions results. This explain why using two function that produce 3 record, at the end produce a 3 records results, while using a function that produce 3 records and a function that produce 2 record, will end in a 6 records results.

Usare delle funzioni che restituiscono result set nella parte SELECT di costrutti SQL in PostgreSQL

[English page]
Di recente ho dovuto scrivere una query che trasformasse ogni record di una tabella, in una serie di record. In particolare, da una tabella che ha due colonne (la prima è la chiave, la seconda è un elenco di targhe) ho dovuto estrarre un result set che avesse due colonne: la chiave e una sola targa. Per quei record che hanno più targhe, si doveva arrivare a duplicare il record in modo da avere la coppia chiave/targa per ciascuna delle targhe nell’elenco relativo.

Altrimenti detto:

postgres=# create temporary table t (chiave varchar primary key, targhe varchar);
postgres=# insert into t values ('000000','AA888BB CC777DD GG333JJ'), ('111111','ZZ888KK');
INSERT 0 2
postgres=# select * from t;
 chiave |         targhe
--------+-------------------------
 000000 | AA888BB CC777DD GG333JJ
 111111 | ZZ888KK

quello che volevo era:

 chiave |         targa
--------+-------------------------
 000000 | AA888BB
 000000 | CC777DD
 000000 | GG333JJ
 111111 | ZZ888KK

la soluzione che avevo trovato era la seguente:

postgres=# select chiave,
    unnest(regexp_split_to_array(targhe, E'\\s+')) AS targa from t;
 chiave |  targa
--------+---------
 000000 | AA888BB
 000000 | CC777DD
 000000 | GG333JJ
 111111 | ZZ888KK

Cosa fa la query che ho scritto? Primo, converte in array la colonna targhe usando il separatore spazio, secondo converte l’array in una relazione di vari record.

Ma questa soluzione non mi piaceva, e neppure la capivo granché. Mi sono quindi fatto alcune domande:

  1. come mai posso mettere nella parte SELECT (e non nella FROM) una relazione? Difatti quando studi l’SQL impari che le relazioni (tabelle, viste, eccetera) vanno nella parte FROM che si usa proprio per dire da dove prendere i dati, mentre nella parte SELECT inserisci cosa visualizzare.
  2. come decide, postgresql, di fare un prodotto cartesiano tra il primo elemento (singolo campo “chiave”) e il secondo (relazione “targa”)?
  3. come fa postgresql a definire questa seconda relazione che non è costante, ma dipende dal primo campo del record corrente? Vale a dire che per ogni “chiave” c’è una relazione “targa” diversa. E poi, se veramente viene fatto il prodotto cartesiano, dovrei poter aggiungere un secondo unnest e produrre 4×3 record. Proviamo:
    postgres=# select chiave,
    unnest(regexp_split_to_array(targhe, E'\\s+')) AS targa1,
    unnest(regexp_split_to_array(targhe, E'\\s+')) AS targa2 from t;
     chiave | targa1  | targa2
    --------+---------+---------
     000000 | AA888BB | AA888BB
     000000 | CC777DD | CC777DD
     000000 | GG333JJ | GG333JJ
     111111 | ZZ888KK | ZZ888KK
  4. come mai non ha fatto un ulteriore prodotto? Può avere a che fare con il concetto di funzioni IMMUTABLE alla quale forse unnest appartiene? (Mi pare di ricordare che si chiamino IMMUTABLE le funzioni che a fronte dello stesso input, danno lo stesso output. In questo caso postgresql potrebbe non richiamare nuovamente la unnest perché tanto conosce già il risultato. Ma perché non fa un ulteriore prodotto?)controprova, faccio l’unnest con un altro array:
    postgres=# select chiave,
        unnest(regexp_split_to_array(targhe, E'\\s+')) AS targa1,
        unnest('{1,2}'::int[]) AS array2 from t;
     chiave | targa1  | array2
    --------+---------+--------
     000000 | AA888BB |      1
     000000 | CC777DD |      2
     000000 | GG333JJ |      1
     000000 | AA888BB |      2
     000000 | CC777DD |      1
     000000 | GG333JJ |      2
     111111 | ZZ888KK |      1
     111111 | ZZ888KK |      2

    qui il prodotto l’ha fatto.

Con tanta confusione in testa, ho contattato alcune mailing list fino ad ottenere la risposta chiarificatrice di Tom Lane (qui).

La risposta prevede vari punti:

  1. la query migliore da fare prevede l’utilizzo del LATERAL e lo spostamento della funzione che restituisce l’array dalla parte SELECT alla parte FROM, nonché la sostituzione delle due chiamate unnest+regexp_split_to_array con una sola regexp_split_to_table:
    select chiave, targa
        from t,
            lateral regexp_split_to_table(targhe, E'\\s+') as targa;
  2. il fatto che si possano mettere delle funzioni che restituiscono più di un valore nella parte SELECT è un dovuto alla derivazione di postgresql dal Berkeley QUEL. Di questa parte di codice Tom non è particolarmente contento. Tra l’altro fa notare che in postgresql 10, il codice per la gestione di queste funzioni che restituiscono più valori, è stato notevolmente cambiato per centralizzarlo e isolarlo dal resto (vedi link nel suo email)
  3. il prodotto cartesiano non viene fatto per ciascuna delle funzioni di questo tipo chiamate. Il numero finale di record viene in realtà prodotto in base alla ricerca di un minimo comune multiplo della cardinalità dei result set delle varie funzioni. Questo spiega perché se ho due funzioni (nel mio caso, uguali) che restituiscono entrambe tre record, il mcm è 3, se ne ho una da 3 una da 2, il mcm è 6.

logrotate e postrotate

Il logrotate è un strumento molto utile che gestisce i file di log delle varie applicazioni. Ogni giorno viene eseguito dal cron e, in base alla configurazione, archivia tutti i log. Tra le cose interessanti c’è la possibilità di eseguire un comando dopo l’archiviazione del log (e prima della sua eventuale compressione). L’operazione che viene eseguita normalmente a fine archiviazione, è quella di avvisare l’applicazione di utilizzare un nuovo file di log.
Ad esempio, nel file /etc/logrotate.d/rsyslog di Debian c’è scritto:

postrotate
invoke-rc.d rsyslog rotate > /dev/null
endscript

oppure, nel file /etc/logrotate.d/apache2 c’è scritto:

postrotate
if /etc/init.d/apache2 status > /dev/null ; then \
/etc/init.d/apache2 reload > /dev/null; \
fi;
endscript

Il manuale, a proposito del postrotate, dice:
The lines between postrotate and endscript (both of which must appear on lines by themselves) are executed (using /bin/sh) after the log file is rotated. These directives may only appear inside a log file definition. Normally, the absolute path to the log file is passed as first argument to the  script. If  sharedscripts is specified, whole pattern is passed to the script. See also prerotate. See sharedscripts and nosharedscripts for error handling.

Il secondo esempio dovrebbe fare scattare un campanello d’allarme riguardo la sintassi da usare nel postrotate: non si tratta di uno script composto da vari comandi, ma di linee che vengono eseguite tramite la shell. Allora, se il comando è uno solo, come nel caso di rsyslog, non c’è problema, ma se si usano varie linee, è necessario concatenarle tramite il backslash a fine riga, e utilizzare sempre il separatore «;» tra i comandi.

Maremontana 2016

Maremontana 2016
In partenza poco dopo il ristoro del 24°km (foto di Walter Nesti)

Quest’anno sto aumentando le distanze e difatti, mentre nell’edizione scorsa ho partecipato al percorso da 23km della Maremontana, nel 2016 ho scelto il 45km. C’era anche il 60km, ma per me sarebbe l’equivalente della morte certa. Meglio di no.

È stata proprio una bella corsa. L’organizzazione si è mostrata all’altezza come sempre: posti medici disseminati sul percorso, sentieri sempre percorribili anche se spesso non ci si sta due affiancati, personale dell’organizzazione disseminato un po’ lungo tutto il percorso, disponibilità di una doccia e di un pasto caldo a fine corsa, controllo del contenuto dello zaino con materiale obbligatorio. E addirittura, qualcuno è partito a controllare il sentiero due ore e mezza prima della partenza, cioè alle 3:30 di notte.

Il mio allenamento non è mai stato sufficiente per correre in maniera competitiva, quindi lo spirito con il quale sono partito è quello del campestre che va a fare una passeggiata, anche se impegnativa. Ovviamente ho fatto i miei controlli medici, ho corso almeno una volta a settimana cercando di coprire distanze lunghe (almeno mezza maratona per ciascuno degli ultimi 4-6 allenamenti), ho fatto un po’ strada di collina e una volta ho corso con la neve inaspettata.

La sera prima della corsa ho cenato abbondantemente (sapete, il menu del ristorante diceva «minimo due porzioni»…) e sono andato a letto puntando la sveglia alle 4:50.

Continua a leggere Maremontana 2016