6.2. CFengine

CFengine è utilizzato sia per la distribuzione dei files dal server centrale, sia per alcuni compiti di monitoraggio e checkup del sistema come il controllo dei permessi, la pulizia dei files vecchi e il monitoraggio di quelli nuovi (implementazione di policies).

CFengine offre un insieme omogeneo di funzionalità già fornite da altri programmi, da rsync a netsaint/nagios, e dunque permette molti approcci differenti alla configurazione del sistema. Per la configurazione descritta in questo howto abbiamo scelto di implementare principalmente due modalità:

distribuzione

copia in /etc di interi files, sia comuni a tutti i server, sia specifici per ciascuno di essi. Questo è il principale metodo di distribuzione del contenuto statico, si appoggia ad un protocollo specifico di cfservd, con una sua struttura autonoma di autenticazione, e non ha bisogno quindi di installare chiavi ssh o di abilitare rsh.

editing

modifica di files di configurazione. CFengine possiede delle direttive piuttosto potenti per modificare il contenuto di files di testo: è possibile per esempio commentare linee selezionate in base a regexp, e aggiungere testo a piacere.

modalità che possono essere combinate a piacere, potendo quindi programmare modifiche specifiche per ciascuna macchina su dei files di configurazione distribuiti centralmente. Gli stessi files di configurazione di CFengine sono gestiti in questo modo. Di seguito si vedrà come, seguendo passo per passo degli esempi concreti.

CFengine si configura con dei files contenenti una serie di regole che si applicano a seconda delle "classi" dinamiche attive in quel momento. Le regole sono divise in sezioni, che vengono applicate in ordine, e con sintassi differenti: per esempio ci sono sezioni per controllare la presenza (e il checksum) dei files, per ripulire le directory dai files vecchi, per eseguire delle modifiche a dei files di testo, etc... Sono definite classi per svariati attributi specifici della macchina: il nome, il tipo di sistema operativo, anche il giorno e l'ora.

Per approfondire lo studio della configurazione di CFengine si raccomanda senz'altro la lettura dei seguenti testi:

6.2.1. Struttura della configurazione di CFengine

La configurazione delle varie macchine che compongono la rete viene gestita centralmente, modificando una serie di files in un unico punto (il repository centrale della configurazione). Ciascuna macchina mantiene poi una copia locale della versione più aggiornata di questo repository, in modo da poter sapere "cosa fare" anche in caso di problemi a raggiungere il server principale; questo permette anche di cambiare molto rapidamente la macchina che ha il ruolo di server principale.

Le copie dei files di configurazione sono, in questo esempio, installate sotto /configfiles. Le macchine di test si chiamano test1, test2 e test3.

Se si guarda alla directory /configfiles si vede che è strutturata in questo modo:


        \--+ configfiles
           |--+ cfengine
           |  \--- inputs
           |  \--- ppkeys
           |--+ ring0
           |  |--+ common
           |  |  \--- ... 
           |  |--+ test1
           |  |  \--- ... 
           |  |--+ test2
           |  |  \--- ... 
           |  \--+ test3
           |     \--- ... 
           \--+ ring1
              \--+ common
                 \--- ... 

      

Queste directory (a parte cfengine) contengono files che sono copiati (ricorsivamente se comprendono sottodirectory) nella /etc del sistema: la directory common contiene i files comuni a tutti i server, mentre le directory testN contengono ciascuna files specifici per la macchina dallo stesso nome. Se un file è presente sia nella directory generica che in quella specifica quest'ultima ha la precedenza. La suddivisione tra ring0 e ring1 è dovuta al fatto che tra i due differenti livelli di privilegio sono più le differenze che le somiglianze.

Come i dati di configurazione vengono generati per le varie macchine.

La directory cfengine contiene la configurazione di CFengine ed è gestita separatamente. I files in cfengine/inputs controllano completamente l'operato di CFengine e vanno modificati a mano (sulla copia principale!) per cambiare un po' di parametri specifici dei server. Mediante questi files si possono anche impostare delle policies sui vari sistemi, per automatizzare alcuni compiti di routine per esempio.

6.2.2. Esempio di configurazione di un servizio

Mentre è abbastanza semplice, dato lo schema qua sopra, capire come sia possibile gestire un servizio che ha solamente bisogno di configurazione statica (cioè copiando i suoi files di configurazione direttamente in /configfiles/ringN/common/qualcosa o /configfiles/ringN/HOST/qualcosa), può essere utile mostrare come sia possibile configurare CFengine per gestire un servizio che ha bisogno di una configurazione specifica per ciascun sistema piuttosto complicata.

6.2.2.1. Configurazione di CFengine per Tinc

Si è già introdotto il software Tinc, che implementa le reti private virtuali corrispondenti ai vari anelli basati sul trust. La configurazione di base è stata introdotta nel Capitolo 2.2: VPN>.

E' poi necessario effettuare un po' di modifiche a questi file di configurazione di base, modifiche dipendenti dalla macchina e che quindi è particolarmente comodo assegnare a CFengine.

La configurazione di CFengine che è stata realizzata già si occupa di riprodurre una copia di /configfiles/ringN/common/tinc su tutte le macchine (vedi il commento al file cf.sysconfig più sotto). Per realizzare le modifiche ulteriori sono stati creati dei file in /configfiles/cfengine/inputs di nome cf.tinc.ringN (uno per ciascun anello VPN). Uno di questi è riprodotto qui di seguito, con commenti:


control:

    vpn_ring0_name = ( ring0 )
    vpn_ring0_cf_dir = ( /etc/tinc/$(vpn_ring0_name) )

	

Questa è la sezione "control", dove si impostano variabili globali relative a tutta la configurazione.


    
links:

        $(vpn_ring0_cf_dir)/rsa_key.priv ->! $(vpn_ring0_cf_dir)/hosts/$(host).priv
	

La sezione "links" permette di specificare i link simbolici che vogliamo creare. Qui si dice a CFengine di creare un link alla chiave privata (/etc/tinc/ring0/rsa_key.priv), che per ciascuna macchina punterà ad un file differente.


files:

        $(vpn_ring0_cf_dir)/hosts/*.priv mode=400 o=root action=fixplain
        $(vpn_ring0_cf_dir)/tinc-up mode=755 o=root action=fixall
        $(vpn_ring0_cf_dir)/tinc-down mode=755 o=root action=fixall
	

La sezione "files" comprende vari controlli che possiamo fare sui files. Qui controlliamo che i due files specificati abbiano i permessi necessari per essere eseguiti, e siano di proprietà dell'utente specificato (action=fixall causa la correzione dei permessi se quelli riscontrati effettivamente sono differenti da quelli richiesti). Inoltre le chiavi private non devono essere leggibili da altri utenti a parte root.


editfiles:

        { /etc/tinc/nets.boot
          AutoCreate
          AppendIfNoSuchLine "$(vpn_name)" 
        }
	

Nella sezione "editfiles" si istruisce CFengine a manipolare i files di testo che in genere contengono la configurazione di un sistema. In questo caso il file /etc/tinc/nets.boot, almeno sui sistemi Debian, contiene semplicemente un elenco delle VPN da attivare al boot: il comando AppendIfNoSuchLine serve ad aggiungere a questo file una linea contenente il nome della nostra VPN, nel caso non fosse gia' presente. Se il file non esistesse verrà creato (AutoCreate).

  

        { $(vpn_ring0_cf_dir)/tinc.conf
          CommentLinesContaining "ConnectTo = $(host)"
          DeleteLinesStarting "Name ="
          AppendIfNoSuchLine "Name = $(host)"
        }
	

Le istruzioni per manipolare il testo hanno in genere dei nomi piuttosto espressivi. Questa serie qui serve per essere sicuri che il file di configurazione contenga solamente una linea con il nome della macchina, e che su ciascuna macchina il server VPN non cerchi di connettersi a sé stesso.


        { $(vpn_ring0_cf_dir)/tinc-up
          BeginGroupIfNoSuchLine "# added by cfengine; do not edit"
            Append "# added by cfengine; do not edit"
            Append "/sbin/ifconfig $(dollar)INTERFACE $(vpn_ring0_ip) 
                    netmask 255.255.0.0 up"
          EndGroup
        }
	

Qui infine creiamo il file che attiva l'interfaccia IP corrispondente alla VPN, una volta che la connessione sia stata stabilita (l'ultima Append è una sola linea, spezzata per esigenze tipografiche, attenzione!). L'editing è "protetto", nel senso che il file non verrà editato due volte grazie al controllo sulla presenza del commento.


   !ring0_gw::
        { $(vpn_ring0_cf_dir)/tinc-up
          AppendIfNoSuchLine "/sbin/ip route add 172.17.0.0/16 via 172.16.1.1"
        }
	

Questo segmento aggiunge una route per la VPN ring1 su tutte le macchine tranne quelle che appartengono alla classe dei gateway tra ring0 e ring1 (ring0_gw), che non ne hanno bisogno.

6.2.3. Configurazione attuale di CFengine

I files presenti nella directory /configfiles/cfengine/inputs controllano completamente il comportamento di CFengine. Sono strutturati in maniera modulare, di modo che ciascun file contenga tutte le direttive relative ad un particolare sottosistema (sperando così di fare in modo che le direttive strettamente collegate tra loro siano raccolte nello stesso file).

Abbiamo già intuito come questi files siano divisi in "sezioni" (che specificano tipologie differenti di azione) e siano pilotati da un sistema di "classi" dinamiche (che saranno differenti su ciascuna macchina). Può essere utile a questo punto capire a cosa servono i vari moduli presenti, ed esaminarne la funzionalità:

cfagent.conf

Principale file di configurazione per cfagent. Contiene la definizione di alcune variabili fondamentali (come il dominio e l'indirizzo email dell'amministratore), ed include tutti i file di configurazione successivi (nella sezione import). Un parametro fondamentale contenuto in questo file è la sequenza in cui le varie "sezioni" vanno eseguite:

        actionsequence =
            (
            resolve
            copy
            directories
            files
            links
            editfiles
            shellcommands
            tidy
            disable
            )
	

cf.cfengine

File che si occupa di mantenere la copia locale di /configfiles, e di installare correttamente tutte le chiavi pubbliche conosciute per CFengine.

cf.linux

File con la configurazione specifica di Linux, viene eseguito solo se l'host appartiene alla classe "linux". Non contiene nulla di particolarmente intelligente, solo qualche esempio.

cf.sysconfig

File che implementa la copia della configurazione in /etc prima dalla directory generica (/configfiles/common) e poi da quella specifica (/configfiles/NAME). I files sono copiati dal repository principale mantenendone i permessi. Per capire come è semplice programmare un'azione del genere con cfengine può valer la pena di guardare la parte di questo file che codifica questo compito:


control:

    any::

        rconfigdir = ( "$(configdir)/$(ring)" )
        configpath = ( "$(rconfigdir)/common:$(rconfigdir)/$(host)" )

copy:

    any::

        $(configpath)           dest=/etc/
                                recurse=inf
                                timestamps=preserve
                                backup=timestamp

	    

Che copia tutti i file (per via del recurse=inf) dalle due directory specificate in configpath in /etc, preservandone permessi e timestamp, mantenendo inoltre, nel caso dovesse effettuare qualche modifica, per esempio per rimuovere un file differente trovato localmente, una copia di backup dei file modificati in /var/lib/cfengine2/repository. La linea inform=true permette di notificare gli amministratori ogni volta che CFengine modifica un file.

Inoltre mantiene un database dei checksum (in stile Tripwire) dei file binari di sistema.

cf.tinc.ring0, cf.tinc.ring1

Configurazione di Tinc (vedi il paragrafo 2.2.3).

cf.apache, cf.mysql, cf.postfix, cf.bind, ...

Configurazione degli altri servizi. In relazione a questi un'altra cosa che è utile notare è che per capire se bisogna riavviare un servizio, e dunque rilevare quando vi sia stato un cambiamento effettivo dei files di configurazione che vengono editati, bisogna appoggiarsi ad una directory temporanea, dove copiare il file da /configfiles per poi modificarlo e confrontarlo con quello in /etc: in questo caso se vi è una differenza si può definire una classe che riavvia il servizio.

6.2.4. Aggiornamento automatico

Per configurare il sistema in maniera da permettere l'aggiornamento remoto delle configurazioni (e il trasferimento dei files dalla copia principale a quelle locali) è necessario abilitare cfservd e cfexecd su tutti i server. L'autenticazione degli host avviene mediante un sistema autonomo di chiavi RSA, di cui è necessario verificare la distribuzione iniziale per permettere il bootstrap del sistema (vedi Appendice A>).

Altra cosa da controllare è la configurazione delle access list in /configfile/cfengine/inputs/cfservd.conf.

CFengine funziona secondo un modello pull, per cui sono i client a richiedere al server principale l'aggiornamento della configurazione. cfexecd permette l'esecuzione automatica di cfagent a intervalli di tempo determinati (invece che da crontab). Di default questo aggiornamento avviene ogni ora.

Nel caso in cui si voglia forzare un'aggiornamento istantaneo (push) su tutte le macchine, per esempio dopo una modifica urgente della configurazione, è disponibile il comando cfrun, che richiede a tutte le macchine l'esecuzione immediata di cfagent.

6.2.5. Sicurezza

A margine delle riflessioni presenti nel Tutorial di CFengine, si può notare che CFengine è soggetto ai problemi dovuti all'implementazione di un proprio modello di trust e dunque dipende dal controllo sulla distribuzione delle chiavi. Questo è comunque più o meno il meglio che è possibile avere in ogni caso, volendo mantenere una sufficiente flessibilità.

Sotto un altro aspetto, essendo il comportamento di CFengine in genere non distruttivo e ampiamente monitorabile (con qualche --verbose si ottiene una quantità quasi molesta di informazioni), questo software rappresenta probabilmente uno strumento in grado di aumentare la sicurezza complessiva del sistema, in quanto è in grado di imporre sui server una policy comune e controllata.