Operare manualmente sulla mappatura UID/SID di winbind nel formato tdb2

Per collegare una macchina unix o Linux ad una Windows per l’autenticazione, si può utilizzare il winbind, cioè quella parte di samba che permette di autenticare gli utenti tramite le loro credenziali di dominio. Una volta che l’utente è autenticato, viene creato una utenza corrispettiva a quella Windows anche su Unix. La nuova utenza avrà un proprio UID e un GID generati al volo da winbind, ma quando l’utente tornerà una seconda volta a fare l’accesso a Unix, sarà meglio che trovi gli stessi UID e GID, così da avere gli stessi diritti che aveva al primo accesso. Per far sì che UID e GID siano mantenuti, questi vengono memorizzati da qualche parte e associati al SID (cioè all’UID per Windows).

Nel caso che si debbano avere più macchine unix collegate allo stesso dominio Windows, è il caso di memorizzare queste informazioni in un luogo accessibile a tutte le macchine, come ad esempio il dominio Windows stesso (utilizzando l’estensione SFU), oppure in un LDAP, oppure in alcuni file che possono essere periodicamente copiati dalla macchina unix principale alle secondarie. Invece, se la macchina unix è una sola, allora si possono semplicemente utilizzare dei file sul server unix stesso.

Per dire a winbind quale percorso seguire, vanno impostati i «backend» della mappatura degli identificatori, detta idmap. Nel mio caso ho utilizzato il backend tdb2, cioè la nuova versione del tdb («Trivial DataBase», che in italiano significa «base dati elementare»). Nel file di configurazione di samba, ho scritto:

idmap config *:backend = tdb2
idmap config *:range = 4000-4100

Che vuol dire: per le utenze di qualsiasi dominio Windows, memorizza le associazioni SID/UID e SID/GID tramite il backend tdb2. Inoltre crea gli UID e GID nell’intervallo da 4000 a 4100,  in modo da essere sicuro che non si sovrappongano a UID e GID già presenti sul sistema unix locale. (Nota: l’intervallo per UID e GID locali è definito in /etc/login.defs.)

Per tutti quelli che usano Debian GNU/Linux (state già usando tutti Debian, vero?), tdb2 andrà a creare il suo database nella directory /var/lib/samba e lo chiamerà idmap2.tdb.

Questo database è — appunto — elementare: cioè è capace di inserire solo dati nella forma chiave/valore. Per vedere quali chiavi sono memorizzate, si può usare il comando tdbtool in questo modo:

root@miura:~# tdbtool /var/lib/samba/idmap2.tdb keys
key 9 bytes: GID 4037
key 43 bytes: S-1-5-21-1142429371-1648316-403635728-1131
key 9 bytes: GID 4024
key 9 bytes: UID 4043
key 43 bytes: S-1-5-21-1142429371-1648316-403635728-1136
[...]

come si vede, le chiavi sono a volte un UID a volte un GID, a volte un SID. Non è scritto nella documentazione online, ma l’informazione su quanto sia lunga la chiave è di grande aiuto: la chiave “UID 4043” è lunga 9 byte, ma sono solo 8 caratteri, quindi c’è qualcos’altro. Idem per le altre chiavi.

Per sapere a cosa è associata una certa chiave potremmo usare lo stesso comando, con argomenti diversi. Proviamo:

root@miura:~# tdbtool /var/lib/samba/idmap2.tdb show 'GID 4037'
fetch failed

L’errore è criptico, ma ricordando il messaggio precedente sulla lunghezza della chiave, e con un po’ di fantasia (o leggendo il codice sorgente), si può trovare il comando corretto, con l’aggiunta di un byte 0 alla fine della chiave:

root@miura:~# tdbtool /var/lib/samba/idmap2.tdb show 'GID 4037\0'

key 9 bytes
GID 4037
data 43 bytes
[000] 53 2D 31 2D 35 2D 32 31  2D 31 31 34 32 34 32 39  S-1-5-21 -1142429
[010] 33 37 31 2D 31 36 34 38  33 31 36 2D 34 30 33 36  371-1648 316-4036
[020] 33 35 37 32 38 2D 31 31  38 35 00                 35728-11 85

bene, adesso non ci sono errori: alla chiave GID 4043 è associato il SID S-1-5-21-1142429371-1648316-403635728-1185, e viceversa:

root@miura:~# tdbtool /var/lib/samba/idmap2.tdb show \
'S-1-5-21-1142429371-1648316-403635728-1185\0'

key 43 bytes
S-1-5-21-1142429371-1648316-403635728-1185
data 9 bytes
[000] 47 49 44 20 34 30 33 37  00                       GID 4037

Lo stesso comando permette anche di cancellare un’associazione, ma questa operazione — ora che abbiamo capito che winbind scrive due record per ogni associazione — va fatta per entrambe le chiavi. Il comando in questo caso non fornisce nessun messaggio riguardo l’esito, nel miglior stile unix:

root@miura:~# tdbtool /var/lib/samba/idmap2.tdb delete \
'S-1-5-21-1142429371-1648316-403635728-1185\0'
root@miura:~# tdbtool /var/lib/samba/idmap2.tdb delete 'GID 4037\0'

Chiudo così questo breve excursus sul trivial database, che nel mio caso è il frutto dello studio dovuto ad un problema, su un server, che presentava due utenti con lo stesso UID. Erano difatti due diverse associazioni memorizzate nel tdb di winbind: una era riferita ad una utenza effettivamente presente sul dominio, mentre l’altra era una associazione rimasta memorizzata nonostante l’utenza sul dominio fosse stata cancellata. Ah, già, dimenticavo: winbind non cancella mai le associazioni locali SID/UID.

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.