Archivi categoria: Open Source

Inerente a software a codice aperto (open source)

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.

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.

Metti che sbagli a configurare il tuo apache e crei un «open proxy»

Lo so, la documentazione va letta bene e più volte, ma capita di avere fretta oppure che la documentazione sia vaga. Così mi è successo di configurare male un server apache che, dopo due anni, è stato usato come «open proxy», cioè come proxy senza nessun controllo. Questo vuol dire che chiunque, impostando come proxy l’ip del mio server, poteva accedere ad internet senza rivelare il proprio indirizzo ip.

L’errore che avevo commesso era il seguente: avevo abilitato il modulo proxy perché apache inoltrasse alcune richieste ad altri siti, nascondendoli ai client. In questo modo su un solo IP davo accesso a varie applicazioni gestite su diverse macchine virtuali. Apache riceveva la richiesta http://www.miodominio.tld/sitoA e la inoltrava all’apache sulla macchina virtuale A, se riceveva la richiesta http://www.miodominio.tld/sitoB la inoltrava all’apache sulla macchina virtuale B, eccetera.

La mia configurazione, una volta attivato il modulo proxy, era la seguente:

<IfModule mod_proxy.c>
   ProxyRequests On
   <Location "/sitoA">
      ProxyPass http://192.168.74.40/
   </Location>
   <Location "/sitoB">
      ProxyPass http://192.168.74.41:8080/
   </Location>
</IfModule>

L’errore sta nel fatto che ProxyRequests non serve ad attivare questo tipo di richieste (apache configurato come reverse proxy), ma quelle del normale proxy (configurato come forward proxy).

Evidentemente ci sono persone che scoprono quando un server web è configurato male e ne approfittano, sicché ieri pomeriggio mi è arrivato l’allarme automatico che indicava sia l’eccessivo utilizzo della banda, sia il carico anomalo in apache. Dopo aver capito cosa era successo, ed essermi documentato meglio, ho impostato il parametro ProxyRequests a Off e la cosa sarebbe stata risolta, ma…

… ma controllando i log di apache continuavo a vedere decine di richieste riferite a siti non miei, alle quali apache rispondeva 200 (HTTP OK). Come ad esempio queste due:

222.186.15.212 - - [02/Mar/2016:06:38:12 +0100] "GET http://909888.com/ HTTP/1.1" 200 272 "http://909888.com/" "Mozilla/5.0+(compatible;+Baiduspider/2.0;++http://www.baidu.com/search/spider.html)"
104.223.72.222 - - [02/Mar/2016:06:38:09 +0100] "GET http://www.heshijiuxian.net/ HTTP/1.1" 200 272 "http://www.baidu.com" "Mozilla/5.0 (compatible; Googlebot/2.1; http://www.google.com/bot.html)"

Proseguendo nella mia indagine sul server apache ho capito il problema. Il mio server gestisce sullo stesso IP siti diversi, quindi è accessibile sia come http://www.miodominio.tld/ che come http://www.altromiodominio.tld/. Per far questo ho dovuto abilitare il NameVirtualHost di apache che in pratica dice ad apache: ascolta su un IP, e quando ti arriva una richiesta, estrai l’intestazione chiamata «host» e in base a quella usa la configurazione del sito corrispondente (cercandola tra i vari VirtualHost configurati).

Non è che sia scritto proprio in caratteri cubitali, ma la documentazione dice anche, tra le righe, che se l’host non viene trovato né tra i ServerName dei vari VirtualHost e nessure tra i ServerAlias, allora viene usato il VirtualHost collegato all’hostname della macchina. Quindi tutte le richieste che si riferiscono a host non gestiti (come quelle che trovavo nei log) restituivano in realtà la pagina del sito principale di questa macchina.

Per risolvere il problema, ho creato un nuovo VirtualHost con un ServerAlias tale da farlo utilizzare per tutti questi siti. In quel VirtualHost viene restituito errore di autorizzazione, come ad esempio:

5.79.83.31 - - [02/Mar/2016:11:10:48 +0100] "GET http://pornobiz.org:81/test_url1/image.php HTTP/1.0" 403 486 "-" "Opera/9.50 (Windows NT 5.1; U; en)"
5.79.83.31 - - [02/Mar/2016:11:10:48 +0100] "CONNECT 74.125.133.138:443 HTTP/1.1" 400 0 "-" "-"

Il VirtualHost è fatto così:

<VirtualHost indirizzo-ipv4:80 [indirizzo-ipv6]:80>
  ServerName www.example.com
  ServerAlias *
  DocumentRoot /var/www/
  <Directory />
    Options FollowSymLinks
    AllowOverride None
  </Directory>
  <Directory /var/www/>
    Options Indexes FollowSymLinks MultiViews
    AllowOverride None
    Order allow,deny
    deny from all
  </Directory>
  ErrorLog /var/log/apache2/error-www.example.com.log
  LogLevel notice
  CustomLog /var/log/apache2/access-www.example.com.log combined
  ServerSignature On
</VirtualHost>

Notare che ServerAlias non contiene un hostname, ma un pattern che corrisponde a tutti gli hostname possibili, così da utilizzare questo VirtualHost per qualsiasi URL.
Ovviamente, per non fare ricadere in questo caso anche gli hostname che vengono invece gestiti dagli altri VirtualHost configurati in precedenza, è stato necessario chiamare il file di questo con un nome che fosse alfabeticamente ultimo, cioè zzz.conf. In questo modo, quando apache cerca il VirtualHost da usare, esamina tutti i file in /etc/apache2/sites-enabled nell’ordine alfabetico, e prende quest’ultimo solo se l’host della richiesta non corrisponde ad uno di quelli gestiti dagli altri VirtualHost.