Lyhyesti

Tavoitteena on sallia dynaamiset DNS-päivitykset zoneihin järjestelmässä jossa on käytössä uudempi BIND (9+ sallii dynaamiset päivitykset) sekä SELinux. Oletetaan että käytössä on Red Hat:in valmistamat nk. "targeted security" SELinux-säännöt (oletuksena päällä). Tässä tekstissä samalla käydään läpi eri työkalut liittyen SELinux-asetuksiin, koska samantyylisia ongelmia tulee vastaan muidenkin palvelujen yhteydessä. Tekstiä voi siis pitää ohjenuorana vastaavien ongelmien ratkonnassa.

Tarvittavat asetukset zone-tiedostoihin

Jotta BIND suostuu vastaanottamaan päivityspyyntöjä, pitää se erikseen sallia per zone-tiedosto. Alla on erikseen tehty ACL (access control list) jossa kerrotaan mistä IP-osotteista päivitykset otetaan vastaan ja sitten ACL:ää on käytetty zonessa. Tämä siis alkutilanne, joka ei tosin toimi SELinux:in kanssa (vielä). Jos käytössäsi ei ole SELinux:ia niin silloin nämä ohjeet soveltuvat vain osittain (voit ohittaa kaikki SELinux:iin liittyvät asiat).

Huomaathan tässä vaiheessa jo että päivitysmekanismi jota tässä rakennetaan ei ole "turvallinen", koska se perustuu pelkästään päivittäjän IP-osoitteeseen (ACL:ien vuoksi). "Oikea" tapa on käyttää DNSSEC:iä, mutta Windows:it eivät valitettavasti osaa sitä. Toisaalta BIND ei valitettavasti osaa Windows:ien käyttämään tunnistusta dynaamisissa päivityksissä.

acl luokkaverkko_acl { 193.64.22.0/23; };

zone "luokkaverkko.net" {
	type master;
	file "luokkaverkko.net.zone";
	allow-update { luokkaverkko_acl; localhost; };
};

[ Named.conf jossa määritelty zone johon voi DDNS on sallittu ]

Huomattavaa ylläolevassa esimerkissä on että "localhost"-niminen ACL on BIND:iin jo sisäänrakennettu. Eli sallitaan päivitykset sekä verkosta 193.64.22.0/23 että osoitteesta 127.0.0.1. Zone-tiedosto itse sijaitsee directory-option osoittaman paikan alla koska file-parametrille annetaan suhteellinen polku. Eli RHEL4:ssä se olisi tiedostossa /var/named/luokkaverkko.net.zone.

Tässä tekstissä tehdään vain nk. "forward"-tyylinen zone, mutta reverse-zonet toteutetaan täysin samoin joten sen takia ne on jätetty tästä pois.

Dynaamisten päivitysten testaaminen

Testaus voidaan suorittaa lähettämällä omalta koneelta tietueiden lisäyspyyntöjä. Pyyntöjä voidaan lähettää nsupdate-nimisellä ohjelmalla,

[root@system ~]# nsupdate
> server 127.0.0.1
> update add foo3.luokkaverkko.net 8000 A 2.3.4.5
> send
> quit

[ Dynaamisen päivityksen testaus nsupdate-työkalulla ]

Yllä on yritetty lisätä foo.luokkaverkko.net-nimelle A-tietue (address). Eli suomeksi, haluttu lisätä IP-osoite nimelle foo. nsupdate on interaktiivinen ohjelma joka lukee syötteestä komentoja. Ohjaalmalla sen syöte jostain tiedostosta voidaan päivitykset ja poistot myös automatisoida (mitä ei tehdä tässä). Se mitä ylläolevasta ei nähdä on että päivätys epäonnistuu (nsupdate valittaisi oikeasti että palvelin ei hyväksynyt päivitystä).

Eli jostain syystä päivitys epäonnistui, vaikka se muuten onkin ihan järkevä päivitysyritys. Tutkikaamme BIND:in lokeja (oletuksena menevät syslog:in kautta /var/log/messages-tiedostoon Red Hat:eillä).

named[19731]: client 127.0.0.1#32805: updating zone 'luokkaverkko.net/IN': adding an RR
named[19731]: journal file luokkaverkko.net.zone.jnl does not exist, creating it
named[19731]: luokkaverkko.net.zone.jnl: create: permission denied
named[19731]: client 127.0.0.1#32805: updating zone 'luokkaverkko.net/IN': error:
  journal open failed: unexpected error

[ BIND ei onnistu tiedostonluonnissa ]

Eli lokista voi lukea sen että päivityspyyntö on kyllä saatu. BIND on myös kohdentanut oikean zonen mihin päivitys kohdistuu. Kolmas rivi tähdentää varsinaisen ongelman, josta alla lisää.

Dynaamisten päivitysten toteutus BIND:issä

Koska zone-tiedostot voivat sisältää suhteellisen monipuolistakin syntaksia ja kommenttikenttiä, ei BIND kirjoita zone tiedostoa uusiksi jokaisen päivityksen jälkeen. Tiedostojen tulkinta sekä uusiksikirjoitus olisi vain liian raskasta vähänkin isommassa ympäristössä. Tämän vuoksi BIND säilyttää zoneen kohdistuvat muutokset erillisessä tiedostossa. Tiedosto on binäärimuotoinen jotta BIND voi nopeasti tulkita sen sisältöä, mutta tämä tarkoittaa myös että muutoksia on vaikea nähdä "käsin". Ongelma kuitenkin ylempänä on nyt siinä että BIND päivityspyynnön saatuaan (ja hyväksyttyään) yrittää päivittää zoneen liittyvää kirjanpitotiedostoa ("journal file"). Koska /var/named/luokkaverkko.net.zone.jnl-nimistä tiedostoa ei ole olemassa, BIND yrittää luoda sellaisen ja siihen BIND-prosessilla ei ole oikeuksia. BIND-prosessi on käynnissä named UID:lla ja ilmeisesti hakemiston suojaukset estävät kirjoituksen sillä UID:lla.

BIND:ille ei valitettavasti voi erikseen säätää mihin se kirjanpitotiedostot kirjottaisi, vaan se yrittää aina automaagisesti käyttää zonen tiedostonimeä jatkettuna .jnl:llä.

Tarkistetaan seuraavaksi josko oikeasti oikeuksissa olisi toivomisen varaa. Samalla kokeilla vippaskonstia joka yleensä auttaa vastaaviin tilanteisiin, eli luodaan valmiiksi puuttuva tiedosto tyhjänä ja samalla korjataan sitten oikeudet siten että BIND oikeasti voi tiedostoja muuttaa. Myös zone-tiedoston oikeudet pitää korjata jotta BIND voi kirjoittaa kirjanpitotiedoston sisällön takaisin oikeaan zoneen.

[root@system ~]# cd /var/named
[root@system named]# ls -l
drwxrwx--- named named 4096 Apr  4  2003 data
-rw-r--r-- named named  198 Apr  4  2003 localdomain.zone
-rw-r--r-- named named  195 Apr  4  2003 localhost.zone
-rw-r--r-- root  root   240 Mar  1 16:04 luokkaverkko.net.zone
-rw-r--r-- named named  415 Apr  4  2003 named.broadcast
-rw-r--r-- named named 2518 Apr  4  2003 named.ca
-rw-r--r-- named named  432 Apr  4  2003 named.ip6.local
-rw-r--r-- named named  433 Apr  4  2003 named.local
-rw-r--r-- named named  416 Apr  4  2003 named.zero
drwxrwx--- named named 4096 Apr  4  2003 slaves
[root@system named]# touch luokkaverkko.net.zone.jnl
[root@system named]# chown named.named luokkaverkko*
[root@system named]# ls -la luokkaverkko*
-rw-r--r-- named named  240 Mar  1 16:04 luokkaverkko.net.zone
-rw-r--r-- named named    0 Mar  1 16:12 luokkaverkko.net.zone.jnl

[ Oikeuksien tarkistus ja korjaus ]

Tämän jälkeen käynnistetään BIND uudestaan (service restart named) ja yritetään päivitystä uudelleen. Sen jälkeen katsotaan lokia (päivitys ei onnistu nimittäin vieläkään):

named[19731]: client 127.0.0.1#32806: updating zone 'luokkaverkko.net/IN': adding an RR
named[19731]: luokkaverkko.net.zone.jnl: open: permission denied
kernel: audit(1141229009.574:3): avc:  denied  { write } for  pid=19733
  comm="named" name="luokkaverkko.net.zone.jnl" dev=dm-0 ino=362477
  scontext=root:system_r:named_t tcontext=root:object_r:named_zone_t tclass=file
named[19731]: client 127.0.0.1#32806: updating zone 'luokkaverkko.net/IN':
  error: journal open failed: unexpected error

[ BIND ei onnistu vieläkään ]

Lokissa näkyy viesti ytimen AVC-logiikalta (SELinux:in oikeuksien testaus komponentti). AVC logiikka estää tällä kertaa write-operaation joka kohdistuu luokkaverkko.net.zone.jnl-tiedostoon ja samalla nähdään mikä on prosessin (named) tietoturvakonteksti (scontext) sekä mikä on tiedoston tietoturvakonteksti (tcontext).

Red Hat näyttää konfiguroineen SELinuxin siten että zone-tiedostojen kirjoitus on estetty named_t-kontekstista, ja tämän vuoksi BIND:in kirjoitusoperaatio päättyykin surullisesti unexpected error-viestiin.

SELinux:in hallintakomentoja

Ennenkuin korjataan BIND:in ongelmat, niin olisi hyvä tietää jotain peruskomentoja SELinux:iin liittyen. Aloitetaan komennolla jolla voidaan tarkistaa onko SELinux päällä ja miten se on aktivoitu.

[root@system named]# cat /etc/redhat-release
Red Hat Enterprise Linux ES release 4 (Nahant Update 2)
[root@system named]# sestatus
SELinux status:		enabled
SELinuxfs mount:	/selinux
Current mode:		enforcing
Mode from config file:	enforcing
Policy version:		18
Policy from config file:targeted

Policy booleans:
allow_ypbind		inactive
dhcpd_disable_trans	inactive
httpd_builtin_scripting	active
httpd_disable_trans	inactive
httpd_enable_cgi	active
httpd_enable_homedirs	active
httpd_ssi_exec		active
httpd_tty_comm		inactive
httpd_unified		active
mysqld_disable_trans	inactive
named_disable_trans	inactive
named_write_master_zonesinactive
nscd_disable_trans	inactive
ntpd_disable_trans	inactive
pegasus_disable_trans	inactive
portmap_disable_trans	inactive
postgresql_disable_transinactive
snmpd_disable_trans	inactive
squid_disable_trans	inactive
syslogd_disable_trans	inactive
use_nfs_home_dirs	inactive
use_samba_home_dirs	inactive
use_syslogng		inactive
winbind_disable_trans	inactive
ypbind_disable_trans	inactive

[ RHEL-version tarkistus sekä SELinux-status ]

Syy miksi RHEL-versio tarkistetaan ensin on se että SELinux:iin liittyvät työkalujen nimet vaihtelevat valitettavasti eri jakeluiden välillä, joten ensin katsotaan että ollaan järjestelmässä jossa nämäkin ohjeet sitten toimivat :-).

sestatus-komennon tulostamat ensimmäiset rivit ovat tärkeimmät meidän kannalta. Ylläolevasta listauksesta nähdään että SELinux on aktiivinen nk. "noudattavassa" tilassa (enforcing). Tämä on RHEL4-asennusten oletus mikäli muuta ei asennuksen aikana valita.

Noudattava tila tarkoittaa sitä että AVC noudattaa SELinux:in sääntöjä eikä anna minkään operaation onnistua mikäli se on estetty. Astetta kevyempi tila on päällä silloin kun noudattaminen otetaan pois päältä (esim setenforce-komennolla, tai system-config-securitylevel-työkalulla). Tällöin AVC tekee samat tarkistukset kuin aiemminkin, mutta ei pysäytä kiellettyjä vaan ainoastaan tekee lokiviestejä. Heikennetty tila on tarkoitettu kolmansien osapuolten toimittamien ohjelmistojen testaukseen jotta nähdään mikäli SELinux-sääntöjä tulee muokata. Huomattavaa tässä vaiheessa on että Red Hat:in konfiguroima SELinux rajoittaa pelkästään verkko-ohjelmistoja. Muut prosessit jatkavat klassisen UNIX-mallin mukaisesti.

Koska SELinux:issa tiedostojärjestelmän objekteilla on normaalien ominaisuuksien lisäksi myös SELinux-ominaisuudet, olisi tässä vaiheessa hyvä esitellä komennot joilla näitä voitaisiin listata. Näiden lisäksi komennot id sekä ps osaavat -Z:n. Komennolla newrole voidaan käynnistää uusi komentotulkki tiettyyn kontekstiin.

[root@system named]# ls -Z
drwxrwx--- named named system_u:object_r:named_cache_t data
-rw-r--r-- named named system_u:object_r:named_zone_t  localdomain.zone
-rw-r--r-- named named system_u:object_r:named_zone_t  localhost.zone
-rw-r--r-- named named root:object_r:named_zone_t      luokkaverkko.net.zone
-rw-r--r-- named named root:object_r:named_zone_t      luokkaverkko.net.zone.jnl
-rw-r--r-- named named system_u:object_r:named_zone_t  named.broadcast
-rw-r--r-- named named system_u:object_r:named_conf_t  named.ca
-rw-r--r-- named named system_u:object_r:named_zone_t  named.ip6.local
-rw-r--r-- named named system_u:object_r:named_zone_t  named.local
-rw-r--r-- named named system_u:object_r:named_zone_t  named.zero
drwxrwx--- named named system_u:object_r:named_cache_t slaves
[root@system named]# chcon --reference=data luokkaverkko.net.zone*
[root@system named]# ls -Z luokkaverkko.zone*
-rw-r--r-- named named system_u:object_r:named_cache_t luokkaverkko.net.zone
-rw-r--r-- named named system_u:object_r:named_cache_t luokkaverkko.net.zone.jnl

[ SELinux-attribuuttien listaus ja muutos ]

ls-työkalun optiolla -Z saadaan näkyviin lista klassisista UNIX-oikeuksista, omistajuustiedot sekä SELinux-tietoturvakonteksti tiedostolle. Ylläolevasta listauksesta nähdään että zone- ja kirjanpitotiedostot molemmat ovat named_zone_t-tietoturvakontekstissa alunperin. Kyseinen konteksti kuitenkin on RH:n toimittamassa SELinux:issa tehty siten että BIND voi kyseisiä tiedostoja lukea, muttei niitä muuttaa. Tämän vuoksi ongelmia tulee kun BIND yrittää avata kirjanpitotiedoston kirjoitusta varten (AVC-viesti).

Yllä tilannetta korjataan siten että sekä zone- että kirjanpitotiedoston tietoturvakonteksti muutetaan sopivaksi. Sopiva tässä tarkoittaa named_cache_t-kontekstia joka on tarkoitettu välimuistitiedostoille. Sinänsä korjaus ei ole ihan tyylipuhdas, koska zone-tiedostot eivät ole sama asia kuin välimuisti, mutta kun dynaamiset päivitykset otetaan käyttöön, ne alkavat muistuttamaan osittain välimuistia koska BIND myös kirjoittaa niihin pelkän lukemisen sijaan.

chcon-työkalulle voidaan suoraan antaa haluttu konteksti parametrinä mutta tässä tapauksessa tiedetään että /var/named/data:lla on jo haluttu konteksti jolloin kopioidaan konteksti sieltä. Tämän jälkeen tarkistetaan että konteksti on muuttunut (named_cache_t).

Kokeillaan päivitystä taas tämän jälkeen (BIND:iä ei tarvitse edellisen jälkeen käynnistää uudelleen).

named[19731]: client 127.0.0.1#32807: updating zone 'luokkaverkko.net/IN': adding an RR
named[19731]: client 127.0.0.1#32807: updating zone 'luokkaverkko.net/IN': error:
  journal open failed: no more

[ No ei se kyllä vieläkään toimi ]

Kuten ylläolevista lokiriveistä näkyy, AVC-ilmoitus katosi (eli SELinux-puoli on nyt kunnossa), mutta eipäs päivitys toimi vieläkään. BIND:in käyttämät kirjanpitotiedostot eivät koskaan voi olla tyhjiä, vaan niiden alussa lukee kuinka paljon dataa niiden sisällä on. Tässä tapauksessa BIND olettaa että kyseessä on sen oma kirjanpitotiedosto ja yrittää tulkita sitä, mutta tulkinta ei onnistu ja tämän vuoksi BIND antaa virheilmoituksen (joka ei ole hirveän kuvaava).

Ratkaisu

Ongelma siis on seuraava: BIND:in pitää päästä luomaan kirjanpitotiedosto itse, koska me ei sitä voida tehdä sen puolesta. BIND-prosessilla kuitenkaan ei ole kirjoitusoikeuksia /var/named-hakemistoon, joten ratkaisuna voidaan pitää sitä että sijoitetaan päivitettävä zone-tiedosto jonnekin mihin BIND:illä on kirjoitusoikeudet. Olisi myös hyvä että sitten kun BIND luo zoneen liittyvän kirjanpitotiedoston, luotu tiedosto saisi automaattisesti oikean SELinux-kontekstin.

RHEL4:ssä on valmis hakemisto johon BIND saa kirjoittaa. Normaalisti BIND kirjoittaa sinne välimuistinsa joten se on valmiiksi suojattu siten että oikeuksien suhteen ei pitäisi tulla ongelmia. SELinux taas toimii siten että kun uusia tiedostoja luodaan hakemistojen alle niin niihin kopioidaan hakemiston tietoturvakonteksti. Eli tässä tapauksessa zone-tiedostojen sijoitus /var/named/data-hakemistoon on ratkaisu. Huomionarvoista tässä on mv-komennon käyttäytyminen SELinux:in kanssa. Kun mv:llä siirretään tiedostoja paikasta toiseen, tiedostojen SELinux-konteksti siirtyy mukana. Eli mv:hen ei päde yllämainittu "uudet tiedostot perivät hakemiston kontekstin"-sääntö. Meidän tapauksessa tämä ei tule olemaan ongelma koska siirrettävillä tiedostoilla on jo valmiiksi oikea konteksti.

Korjaus vaatii siis sen että zone-tiedosto siirretään ensin, ja sitten /etc/named.conf:ia muutetaan siten että BIND osaa hakea zonen oikeasta paikkaa.

[root@system named]# rm luokkaverkko.net.zone.jnl
[root@system named]# mv luokkaverkko.net.zone* data
[root@system named]# ls -Z data
-rw-r--r--  named    named    root:object_r:named_cache_t      luokkaverkko.net.zone

[ Tiedoston siirto sekä tarkistus ]

Koska BIND ei osaa käsitellä tyhjiä kirjanpitotiedostoja, aiemmin luotu tyhjä kirjnapitotiedosto poistetaan ja luotetaan siihen että BIND saa luotua itse kirjanpitotiedoston kun se sitten saa ensimmäisen päivityspyynnön zoneen.

acl luokkaverkko_acl { 193.64.22.0/23; };

zone "luokkaverkko.net" {
	type master;
	file "data/luokkaverkko.net.zone";
	allow-update { luokkaverkko_acl; localhost; };
};

[ BIND-asetusten korjaus ]

Eli ainoa korjaus BIND:in kannalta on zone-tiedoston sijainti.

Tämän jälkeen yritetään vielä kerran päivitystä:

named[19033]: client 127.0.0.1#32792: updating zone 'luokkaverkko.net/IN': adding an RR
named[19033]: journal file data/luokkaverkko.net.zone.jnl does not exist, creating it
named[19033]: zone luokkaverkko.net/IN: sending notifies (serial 2006030102)

[ Ihmeiden ihme, ei virheitä ]

BIND onnistuu luomaan kirjanpitotiedoston onnistuneesti (koska virheitä ei tule) ja myöskin kertoo vielä meille että se lähettää viestit zonen muutoksesta mahdollisille orja-palvelimille (slave). Tässä vaiheessa kannattaakin huomata että muutokset tehdään aina master-zoneen. Mikäli päivityspyyntö tulee zoneen joka on orja-tilassa, pyyntö hylätään. Tämän vuoksi asiakasohjelmat (kuten nsupdate) selvittävät ensin missä majailee kohdezonen master ja lähettävät päivityspyyntönsä sille.

Joka kerta kun zoneen tulee muutos dynaamisesti, BIND kasvattaa itsenäisesti zonen sarjanumeroa (joka näkyy samalla rivillä "notifies"-viestissä).

Tarkistetaan vielä tehty muutos dig:illä (pieni paranoia on tervettä siinä vaiheessa kun testataan uusia asioita):

[root@system ~]# dig @127.0.0.1 a foo3.luokkaverkko.net.

; <<>> DiG 9.2.4 <<>> @127.0.0.1 a foo3.luokkaverkko.net.
;; global options:  printcmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 18766
;; flags: qr aa rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 1, ADDITIONAL: 0

;; QUESTION SECTION:
;foo3.luokkaverkko.net.         IN      A

;; ANSWER SECTION:
foo3.luokkaverkko.net.  8000    IN      A       2.3.4.5

;; AUTHORITY SECTION:
luokkaverkko.net.       86400   IN      NS      1.2.3.20.

;; Query time: 0 msec
;; SERVER: 127.0.0.1#53(127.0.0.1)
;; WHEN: Wed Mar  1 17:42:01 2006
;; MSG SIZE  rcvd: 77

[ Muutos näkyy kyselijöille ]

Muutos näkyy ulospäin, joten tärkein osuus toimii.

Dynaamisten zone-tiedostojen ylläpito

Koska BIND ei tallenna kaikkia muutoksia suoraan zone-tiedostoihin, tulee zonejen ylläpidossa käsin (ja ohjelmallisestikin) olla erityisen varovainen. Koska jokaisen zonen sisältö nyt muodostuu sekä varsinaisesti zone-tiedostosta että kirjanpitotiedostosta, pitää BIND:ille erikseen kertoa että se ajaa muutokset zonetiedostoon "nyt" ennen muutoksia. Jottei samanaikaisesti voi tulla ulkoapäin lisää muutoksia zoneen, pitää zone "sammuttaa".

Valitettavasti vanhemmissa BIND-versioissa tämä käytännössä tarkoittaa seuraavaa menetelmää:

Mikäli käytössä on 9.3-sarjan BIND, on zonet mahdollista erikseen jäädyttää ja jatkaa (rndc-työkalulla), jolloin koko DNS-palvelua ei tarvitse ajaa operaatioiden ajaksi alas.

Esimerkki zone-tiedostosta johon on kirjanpitotiedosto ajettu useampien muutosten jälkeen:

$ORIGIN .
$TTL 86400	; 1 day
luokkaverkko.net	IN SOA	localhost.luokkaverkko.net. root.luokkaverkko.net. (
				2006030105 ; serial
				10800      ; refresh (3 hours)
				900        ; retry (15 minutes)
				604800     ; expire (1 week)
				86400      ; minimum (1 day)
				)
			NS	1.2.3.20.
			A	1.2.3.4
			A	1.2.3.58
$ORIGIN luokkaverkko.net.
$TTL 700	; 11 minutes 40 seconds
foo			A	1.2.3.7
$TTL 86400	; 1 day
foo2			A	1.2.3.9
$TTL 8000	; 2 hours 13 minutes 20 seconds
foo3			A	2.3.4.5
$TTL 86400	; 1 day
www			A	1.2.3.4
			A	1.2.3.5
			A	1.2.3.6

[ Zone-tiedosto useamman muutoksen jälkeen ]

Linkkejä

Nk. "oikea" ratkaisu jossa käsitellään myös ISC:n DHCPd:n integrointia tekemään dynaamisia päivityksiä: http://ops.ietf.org/dns/dynupd/secure-ddns-howto.html .

Red Hat:in oma SELinux-guide (ihan ok, kertoo myös jotain RH:n käyttämistä säännöistä): http://www.redhat.com/docs/manuals/enterprise/RHEL-4-Manual/selinux-guide/ .

Epilogi

Versiohistoria:

Copyright 2006 Aleksandr Koltsoff (czr(at)iki(piste)fi). Käyttö henkilökohtaiseen opiskeluun sallittu, muusta käytöstä erikseen sovittava.