propellor

propellor config for hosts.
git clone git://git.ricketyspace.net/propellor.git
Log | Files | Refs | LICENSE

Unbound.hs (5257B)


      1 -- | Maintainer: FĂ©lix Sipma <felix+propellor@gueux.org>
      2 --
      3 -- Properties for the Unbound caching DNS server
      4 
      5 module Propellor.Property.Unbound
      6 	( installed
      7 	, restarted
      8 	, reloaded
      9 	, UnboundSection
     10 	, UnboundZone
     11 	, UnboundHost
     12 	, UnboundSetting
     13 	, UnboundValue
     14 	, UnboundKey
     15 	, ConfSection
     16 	, ZoneType
     17 	, cachingDnsServer
     18 	) where
     19 
     20 import Propellor.Base
     21 import Propellor.Property.File
     22 import qualified Propellor.Property.Apt as Apt
     23 import qualified Propellor.Property.Service as Service
     24 
     25 import Data.List (find)
     26 
     27 
     28 type ConfSection = String
     29 
     30 type UnboundSetting = (UnboundKey, UnboundValue)
     31 
     32 type UnboundSection = (ConfSection, [UnboundSetting])
     33 
     34 type UnboundZone = (BindDomain, ZoneType)
     35 
     36 type UnboundHost = (BindDomain, Record)
     37 
     38 type UnboundKey = String
     39 
     40 type UnboundValue = String
     41 
     42 type ZoneType = String
     43 
     44 installed :: Property DebianLike
     45 installed = Apt.installed ["unbound"]
     46 
     47 restarted :: Property DebianLike
     48 restarted = Service.restarted "unbound"
     49 
     50 reloaded :: Property DebianLike
     51 reloaded = Service.reloaded "unbound"
     52 
     53 dValue :: BindDomain -> String
     54 dValue (RelDomain d) = d
     55 dValue (AbsDomain d) = d ++ "."
     56 dValue (RootDomain) = "@"
     57 
     58 sectionHeader :: ConfSection -> String
     59 sectionHeader header = header ++ ":"
     60 
     61 config :: FilePath
     62 config = "/etc/unbound/unbound.conf.d/propellor.conf"
     63 
     64 -- | Provided a [UnboundSection], a [UnboundZone] and a [UnboundHost],
     65 -- cachingDnsServer ensure unbound is configured accordingly.
     66 --
     67 -- Be carefull with CNAMEs, unbound is not a primary DNS server, so it will
     68 -- resolve these by itself. For a locally served zone, you probably want A/AAAA
     69 -- records instead.
     70 --
     71 -- Example property:
     72 --
     73 -- > cachingDnsServer
     74 -- >      [ ("remote-control", [("control-enable", "no")]
     75 -- >      , ("server",
     76 -- >      	[ ("interface", "0.0.0.0")
     77 -- >      	, ("access-control", "192.168.1.0/24 allow")
     78 -- >      	, ("do-tcp", "no")
     79 -- >      	])
     80 -- >      [ (AbsDomain "example.com", "transparent")
     81 -- >      , (AbsDomain $ reverseIP $ IPv4 "192.168.1", "static")
     82 -- >      ]
     83 -- >      [ (AbsDomain "example.com", Address $ IPv4 "192.168.1.2")
     84 -- >      , (AbsDomain "myhost.example.com", Address $ IPv4 "192.168.1.2")
     85 -- >      , (AbsDomain "myrouter.example.com", Address $ IPv4 "192.168.1.1")
     86 -- >      , (AbsDomain "www.example.com", Address $ IPv4 "192.168.1.2")
     87 -- >      , (AbsDomain "example.com", MX 10 "mail.example.com")
     88 -- >      , (AbsDomain "mylaptop.example.com", Address $ IPv4 "192.168.1.2")
     89 -- >      -- ^ connected via ethernet
     90 -- >      , (AbsDomain "mywifi.example.com", Address $ IPv4 "192.168.2.1")
     91 -- >      , (AbsDomain "mylaptop.example.com", Address $ IPv4 "192.168.2.2")
     92 -- >      -- ^ connected via wifi, use round robin
     93 -- >      , (AbsDomain "myhost.example.com", PTR $ reverseIP $ IPv4 "192.168.1.2")
     94 -- >      , (AbsDomain "myrouter.example.com", PTR $ reverseIP $ IPv4 "192.168.1.1")
     95 -- >      , (AbsDomain "mylaptop.example.com", PTR $ reverseIP $ IPv4 "192.168.1.2")
     96 -- >      ]
     97 cachingDnsServer :: [UnboundSection] -> [UnboundZone] -> [UnboundHost] -> Property DebianLike
     98 cachingDnsServer sections zones hosts =
     99 	config `hasContent` (comment : otherSections ++ serverSection)
    100 	`onChange` restarted
    101   where
    102 	comment = "# deployed with propellor, do not modify"
    103 	serverSection = genSection (fromMaybe ("server", []) $ find ((== "server") . fst) sections)
    104 		++ map genZone zones
    105 		++ map (uncurry genRecord') hosts
    106 	otherSections = foldr ((++) . genSection) [] $ filter ((/= "server") . fst) sections
    107 
    108 genSection :: UnboundSection -> [Line]
    109 genSection (section, settings) = sectionHeader section : map genSetting settings
    110 
    111 genSetting :: UnboundSetting -> Line
    112 genSetting (key, value) = "    " ++ key ++ ": " ++ value
    113 
    114 genZone :: UnboundZone -> Line
    115 genZone (dom, zt) = "    local-zone: \"" ++ dValue dom ++ "\" " ++ zt
    116 
    117 genRecord' :: BindDomain -> Record -> Line
    118 genRecord' dom r = "    local-data: \"" ++ fromMaybe "" (genRecord dom r) ++ "\""
    119 
    120 genRecord :: BindDomain -> Record -> Maybe String
    121 genRecord dom (Address addr) = Just $ genAddressNoTtl dom addr
    122 genRecord dom (MX priority dest) = Just $ unwords
    123 	[ dValue dom
    124 	, "MX"
    125 	, val priority
    126 	, dValue dest
    127 	]
    128 genRecord dom (PTR revip) = Just $ unwords
    129 	[ revip ++ "."
    130 	, "PTR"
    131 	, dValue dom
    132 	]
    133 genRecord dom (CNAME dest) = Just $ unwords
    134 	[ dValue dom
    135 	, "CNAME"
    136 	, dValue dest
    137 	]
    138 genRecord dom (NS serv) = Just $ unwords
    139 	[ dValue dom
    140 	, "NS"
    141 	, dValue serv
    142 	]
    143 genRecord dom (TXT txt) = Just $ unwords
    144 	[ dValue dom
    145 	, "TXT"
    146 	, txt
    147 	]
    148 genRecord dom (SRV priority weight port target) = Just $ unwords
    149 	[ dValue dom
    150 	, "SRV"
    151 	, val priority
    152 	, val weight
    153 	, val port
    154 	, dValue target
    155 	]
    156 genRecord dom (SSHFP algo hash fingerprint) = Just $ unwords
    157 	[ dValue dom
    158 	, "SSHFP"
    159 	, val algo
    160 	, val hash
    161 	, fingerprint
    162 	]
    163 genRecord _ (INCLUDE _) = Nothing
    164 
    165 genAddressNoTtl :: BindDomain -> IPAddr -> String
    166 genAddressNoTtl dom = genAddress dom Nothing
    167 
    168 genAddress :: BindDomain -> Maybe Int -> IPAddr -> String
    169 genAddress dom ttl addr = case addr of
    170 	IPv4 _ -> genAddress' "A" dom ttl addr
    171 	IPv6 _ -> genAddress' "AAAA" dom ttl addr
    172 
    173 genAddress' :: String -> BindDomain -> Maybe Int -> IPAddr -> String
    174 genAddress' recordtype dom ttl addr = unwords $
    175 	[ dValue dom ]
    176 	++ maybe [] (\ttl' -> [val ttl']) ttl ++
    177 	[ "IN"
    178 	, recordtype
    179 	, val addr
    180 	]