added bind 9 zone file creation code
This commit is contained in:
parent
6f0b6b8816
commit
b2d6393bf4
|
@ -4,11 +4,14 @@ import Propellor
|
||||||
import Propellor.Property.File
|
import Propellor.Property.File
|
||||||
import qualified Propellor.Property.Apt as Apt
|
import qualified Propellor.Property.Apt as Apt
|
||||||
import qualified Propellor.Property.Service as Service
|
import qualified Propellor.Property.Service as Service
|
||||||
|
import Utility.Applicative
|
||||||
|
|
||||||
|
import Data.List
|
||||||
|
|
||||||
namedconf :: FilePath
|
namedconf :: FilePath
|
||||||
namedconf = "/etc/bind/named.conf.local"
|
namedconf = "/etc/bind/named.conf.local"
|
||||||
|
|
||||||
data Zone = Zone
|
data NamedConf = NamedConf
|
||||||
{ zdomain :: Domain
|
{ zdomain :: Domain
|
||||||
, ztype :: Type
|
, ztype :: Type
|
||||||
, zfile :: FilePath
|
, zfile :: FilePath
|
||||||
|
@ -16,7 +19,7 @@ data Zone = Zone
|
||||||
, zconfiglines :: [String]
|
, zconfiglines :: [String]
|
||||||
}
|
}
|
||||||
|
|
||||||
zoneDesc :: Zone -> String
|
zoneDesc :: NamedConf -> String
|
||||||
zoneDesc z = zdomain z ++ " (" ++ show (ztype z) ++ ")"
|
zoneDesc z = zdomain z ++ " (" ++ show (ztype z) ++ ")"
|
||||||
|
|
||||||
type IPAddr = String
|
type IPAddr = String
|
||||||
|
@ -26,8 +29,8 @@ type Domain = String
|
||||||
data Type = Master | Secondary
|
data Type = Master | Secondary
|
||||||
deriving (Show, Eq)
|
deriving (Show, Eq)
|
||||||
|
|
||||||
secondary :: Domain -> [IPAddr] -> Zone
|
secondary :: Domain -> [IPAddr] -> NamedConf
|
||||||
secondary domain masters = Zone
|
secondary domain masters = NamedConf
|
||||||
{ zdomain = domain
|
{ zdomain = domain
|
||||||
, ztype = Secondary
|
, ztype = Secondary
|
||||||
, zfile = "db." ++ domain
|
, zfile = "db." ++ domain
|
||||||
|
@ -35,7 +38,7 @@ secondary domain masters = Zone
|
||||||
, zconfiglines = ["allow-transfer { }"]
|
, zconfiglines = ["allow-transfer { }"]
|
||||||
}
|
}
|
||||||
|
|
||||||
zoneStanza :: Zone -> [Line]
|
zoneStanza :: NamedConf -> [Line]
|
||||||
zoneStanza z =
|
zoneStanza z =
|
||||||
[ "// automatically generated by propellor"
|
[ "// automatically generated by propellor"
|
||||||
, "zone \"" ++ zdomain z ++ "\" {"
|
, "zone \"" ++ zdomain z ++ "\" {"
|
||||||
|
@ -56,8 +59,160 @@ zoneStanza z =
|
||||||
|
|
||||||
-- | Rewrites the whole named.conf.local file to serve the specificed
|
-- | Rewrites the whole named.conf.local file to serve the specificed
|
||||||
-- zones.
|
-- zones.
|
||||||
zones :: [Zone] -> Property
|
zones :: [NamedConf] -> Property
|
||||||
zones zs = hasContent namedconf (concatMap zoneStanza zs)
|
zones zs = hasContent namedconf (concatMap zoneStanza zs)
|
||||||
`describe` ("dns server for zones: " ++ unwords (map zoneDesc zs))
|
`describe` ("dns server for zones: " ++ unwords (map zoneDesc zs))
|
||||||
`requires` Apt.serviceInstalledRunning "bind9"
|
`requires` Apt.serviceInstalledRunning "bind9"
|
||||||
`onChange` Service.reloaded "bind9"
|
`onChange` Service.reloaded "bind9"
|
||||||
|
|
||||||
|
-- | Represents a bind 9 zone file.
|
||||||
|
data Zone = Zone SOA [(HostName, Record)]
|
||||||
|
|
||||||
|
-- | Every domain has a SOA record, which is big and complicated.
|
||||||
|
data SOA = SOA
|
||||||
|
{ sRoot :: BindDomain
|
||||||
|
, sSerial :: SerialNumber
|
||||||
|
-- ^ The most important parameter is the serial number,
|
||||||
|
-- which must increase after each change.
|
||||||
|
, sRefresh :: Integer
|
||||||
|
, sRetry :: Integer
|
||||||
|
, sExpire :: Integer
|
||||||
|
, sTTL :: Integer
|
||||||
|
, sRecord :: [Record]
|
||||||
|
-- ^ Records for the root of the domain. Typically NS, A, TXT
|
||||||
|
}
|
||||||
|
|
||||||
|
-- | Types of DNS records.
|
||||||
|
--
|
||||||
|
-- This is not a complete list, more can be added.
|
||||||
|
data Record
|
||||||
|
= A Ipv4
|
||||||
|
| AAAA Ipv6
|
||||||
|
| CNAME BindDomain
|
||||||
|
| MX Int BindDomain
|
||||||
|
| NS BindDomain
|
||||||
|
| TXT String
|
||||||
|
|
||||||
|
type Ipv4 = String
|
||||||
|
type Ipv6 = String
|
||||||
|
|
||||||
|
type SerialNumber = Integer
|
||||||
|
|
||||||
|
-- | Domains in the zone file must end with a period if they are absolute.
|
||||||
|
--
|
||||||
|
-- Let's use a type to keep absolute domains straight from relative
|
||||||
|
-- domains.
|
||||||
|
--
|
||||||
|
-- The SOADomain refers to the root SOA record.
|
||||||
|
data BindDomain = RelDomain Domain | AbsDomain Domain | SOADomain
|
||||||
|
|
||||||
|
dValue :: BindDomain -> String
|
||||||
|
dValue (RelDomain d) = d
|
||||||
|
dValue (AbsDomain d) = d ++ "."
|
||||||
|
dValue (SOADomain) = "@"
|
||||||
|
|
||||||
|
rField :: Record -> String
|
||||||
|
rField (A _) = "A"
|
||||||
|
rField (AAAA _) = "AAAA"
|
||||||
|
rField (CNAME _) = "CNAME"
|
||||||
|
rField (MX _ _) = "MX"
|
||||||
|
rField (NS _) = "NS"
|
||||||
|
rField (TXT _) = "TXT"
|
||||||
|
|
||||||
|
rValue :: Record -> String
|
||||||
|
rValue (A addr) = addr
|
||||||
|
rValue (AAAA addr) = addr
|
||||||
|
rValue (CNAME d) = dValue d
|
||||||
|
rValue (MX pri d) = show pri ++ " " ++ dValue d
|
||||||
|
rValue (NS d) = dValue d
|
||||||
|
rValue (TXT s) = [q] ++ filter (/= q) s ++ [q]
|
||||||
|
where
|
||||||
|
q = '\"'
|
||||||
|
|
||||||
|
-- | Adjusts the serial number of the zone to
|
||||||
|
--
|
||||||
|
-- * Always be larger than the passed SerialNumber
|
||||||
|
-- * Always be larger than the serial number in the Zone record.
|
||||||
|
nextSerial :: Zone -> SerialNumber -> Zone
|
||||||
|
nextSerial (Zone soa l) oldserial = Zone soa' l
|
||||||
|
where
|
||||||
|
soa' = soa { sSerial = succ $ max (sSerial soa) oldserial }
|
||||||
|
|
||||||
|
-- | Write a Zone out to a to a file.
|
||||||
|
--
|
||||||
|
-- The serial number that is written to the file comes from larger of the
|
||||||
|
-- Zone's SOA serial number, and the last serial number used in the file.
|
||||||
|
-- This ensures that serial number always increases, while also letting
|
||||||
|
-- a Zone contain an existing serial number, which may be quite large.
|
||||||
|
--
|
||||||
|
-- TODO: This increases the serial number when propellor is running on the
|
||||||
|
-- same host and generating its zone there, but what if the DNS host is
|
||||||
|
-- changed? We'd then want to remember the actual serial number and
|
||||||
|
-- propigate it to the new DNS host.
|
||||||
|
writeZoneFile :: Zone -> FilePath -> IO ()
|
||||||
|
writeZoneFile z f = do
|
||||||
|
oldserial <- nextZoneFileSerialNumber f
|
||||||
|
let z'@(Zone soa' _) = nextSerial z oldserial
|
||||||
|
writeFile f (genZoneFile z')
|
||||||
|
writeFile (zoneSerialFile f) (show $ sSerial soa')
|
||||||
|
|
||||||
|
-- | Next to the zone file, is a ".serial" file, which contains
|
||||||
|
-- the SOA Serial number of that zone. This saves the bother of parsing
|
||||||
|
-- this horrible format.
|
||||||
|
zoneSerialFile :: FilePath -> FilePath
|
||||||
|
zoneSerialFile f = f ++ ".serial"
|
||||||
|
|
||||||
|
nextZoneFileSerialNumber :: FilePath -> IO SerialNumber
|
||||||
|
nextZoneFileSerialNumber = maybe 1 (+1) <$$> readZoneSerialFile
|
||||||
|
|
||||||
|
readZoneSerialFile :: FilePath -> IO (Maybe SerialNumber)
|
||||||
|
readZoneSerialFile f = catchDefaultIO Nothing $
|
||||||
|
readish <$> readFile (zoneSerialFile f)
|
||||||
|
|
||||||
|
-- | Generating a zone file.
|
||||||
|
genZoneFile :: Zone -> String
|
||||||
|
genZoneFile (Zone soa rs) = unlines $
|
||||||
|
header : genSOA soa : map genr rs
|
||||||
|
where
|
||||||
|
header = com "BIND zone file. Generated by propellor, do not edit."
|
||||||
|
|
||||||
|
genr (d, r) = genRecord (Just d, r)
|
||||||
|
|
||||||
|
genRecord :: (Maybe Domain, Record) -> String
|
||||||
|
genRecord (mdomain, record) = intercalate "\t"
|
||||||
|
[ dname
|
||||||
|
, "IN"
|
||||||
|
, rField record
|
||||||
|
, rValue record
|
||||||
|
]
|
||||||
|
where
|
||||||
|
dname = fromMaybe "" mdomain
|
||||||
|
|
||||||
|
genSOA :: SOA -> String
|
||||||
|
genSOA soa = unlines $
|
||||||
|
header : map genRecord (zip (repeat Nothing) (sRecord soa))
|
||||||
|
where
|
||||||
|
header = unlines
|
||||||
|
-- @ IN SOA root. root (
|
||||||
|
[ intercalate "\t"
|
||||||
|
[ dValue SOADomain
|
||||||
|
, "IN"
|
||||||
|
, "SOA"
|
||||||
|
, dValue (sRoot soa)
|
||||||
|
, "root"
|
||||||
|
, "("
|
||||||
|
]
|
||||||
|
, headerline sSerial "Serial"
|
||||||
|
, headerline sRefresh "Refresh"
|
||||||
|
, headerline sRetry "Retry"
|
||||||
|
, headerline sExpire "Expire"
|
||||||
|
, headerline sTTL "Default TTL"
|
||||||
|
, inheader ")"
|
||||||
|
]
|
||||||
|
headerline r comment = inheader $ show (r soa) ++ "\t\t" ++ com comment
|
||||||
|
inheader l = "\t\t\t" ++ l
|
||||||
|
|
||||||
|
-- | Comment line in a zone file.
|
||||||
|
com :: String -> String
|
||||||
|
com s = "; " ++ s
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue