summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorJoey Hess2015-01-04 12:44:05 -0400
committerJoey Hess2015-01-04 12:44:05 -0400
commit8172f243d73f0c7f7231d03c3561c3da5a7ad0d4 (patch)
treee260aa5e95469157d42a7cfa0a88cd11e236ad5b /src
parenta1f61e09b8d72ef4d4ee8490b9b228dafd8b3d1d (diff)
DNS WIP
Diffstat (limited to 'src')
-rw-r--r--src/Propellor/Info.hs1
-rw-r--r--src/Propellor/PrivData/Paths.hs2
-rw-r--r--src/Propellor/Property.hs3
-rw-r--r--src/Propellor/Property/Dns.hs50
-rw-r--r--src/Propellor/Property/DnsSec.hs69
-rw-r--r--src/Propellor/Types/Dns.hs1
6 files changed, 109 insertions, 17 deletions
diff --git a/src/Propellor/Info.hs b/src/Propellor/Info.hs
index 0437f8ec..3af3fc15 100644
--- a/src/Propellor/Info.hs
+++ b/src/Propellor/Info.hs
@@ -64,6 +64,7 @@ addDNS r = pureInfoProperty (rdesc r) $ mempty { _dns = S.singleton r }
rdesc (NS d) = unwords ["NS", ddesc d]
rdesc (TXT s) = unwords ["TXT", s]
rdesc (SRV x y z d) = unwords ["SRV", show x, show y, show z, ddesc d]
+ rdesc (INCLUDE f) = unwords ["$INCLUDE", f]
ddesc (AbsDomain domain) = domain
ddesc (RelDomain domain) = domain
diff --git a/src/Propellor/PrivData/Paths.hs b/src/Propellor/PrivData/Paths.hs
index 9f791b76..3d0d8a58 100644
--- a/src/Propellor/PrivData/Paths.hs
+++ b/src/Propellor/PrivData/Paths.hs
@@ -3,7 +3,7 @@ module Propellor.PrivData.Paths where
import System.FilePath
privDataDir :: FilePath
-privDataDir = "privdata.joey"
+privDataDir = "privdata"
privDataFile :: FilePath
privDataFile = privDataDir </> "privdata.gpg"
diff --git a/src/Propellor/Property.hs b/src/Propellor/Property.hs
index 37fd90d6..c0878fb6 100644
--- a/src/Propellor/Property.hs
+++ b/src/Propellor/Property.hs
@@ -26,8 +26,7 @@ propertyList :: Desc -> [Property] -> Property
propertyList desc ps = Property desc (ensureProperties ps) (combineInfos ps)
-- | Combines a list of properties, resulting in one property that
--- ensures each in turn. Does not stop on failure; does propigate
--- overall success/failure.
+-- ensures each in turn. Stops if a property fails.
combineProperties :: Desc -> [Property] -> Property
combineProperties desc ps = Property desc (go ps NoChange) (combineInfos ps)
where
diff --git a/src/Propellor/Property/Dns.hs b/src/Propellor/Property/Dns.hs
index 20530f26..89180963 100644
--- a/src/Propellor/Property/Dns.hs
+++ b/src/Propellor/Property/Dns.hs
@@ -56,18 +56,18 @@ import Data.List
primary :: [Host] -> Domain -> SOA -> [(BindDomain, Record)] -> RevertableProperty
primary hosts domain soa rs = RevertableProperty setup cleanup
where
- setup = withwarnings (check needupdate baseprop)
- `requires` servingZones
+ setup = setupPrimary hosts domain soa rs
+ `onChange` Service.reloaded "bind9"
+ cleanup = cleanupPrimary domain
`onChange` Service.reloaded "bind9"
- cleanup = check (doesFileExist zonefile) $
- property ("removed dns primary for " ++ domain)
- (makeChange $ removeZoneFile zonefile)
- `requires` namedConfWritten
- `onChange` Service.reloaded "bind9"
+setupPrimary :: [Host] -> Domain -> SOA -> [(BindDomain, Record)] -> Property
+setupPrimary hosts domain soa rs = withwarnings (check needupdate baseprop)
+ `requires` servingZones
+ where
(partialzone, zonewarnings) = genZone hosts domain soa
zone = partialzone { zHosts = zHosts partialzone ++ rs }
- zonefile = "/etc/bind/propellor/db." ++ domain
+ zonefile = zoneFile domain
baseprop = Property ("dns primary for " ++ domain)
(makeChange $ writeZoneFile zone zonefile)
(addNamedConf conf)
@@ -100,6 +100,18 @@ primary hosts domain soa rs = RevertableProperty setup cleanup
z = zone { zSOA = (zSOA zone) { sSerial = oldserial } }
in z /= oldzone || oldserial < sSerial (zSOA zone)
+
+cleanupPrimary :: Domain -> Property
+cleanupPrimary domain = check (doesFileExist zonefile) $
+ property ("removed dns primary for " ++ domain)
+ (makeChange $ removeZoneFile zonefile)
+ `requires` namedConfWritten
+ where
+ zonefile = zoneFile domain
+
+zoneFile :: Domain -> FilePath
+zoneFile domain = "/etc/bind/propellor/db." ++ domain
+
-- | Primary dns server for a domain, secured with DNSSEC.
--
-- This is like `primary`, except the resulting zone
@@ -121,8 +133,24 @@ primary hosts domain soa rs = RevertableProperty setup cleanup
signedPrimary :: Recurrance -> [Host] -> Domain -> SOA -> [(BindDomain, Record)] -> RevertableProperty
signedPrimary recurrance hosts domain soa rs = RevertableProperty setup cleanup
where
- setup = undefined
- cleanup = undefined
+ -- TODO put signed zone file in named.conf.
+ -- TODO enable dnssec options.
+ -- dnssec-enable yes; dnssec-validation yes; dnssec-lookaside auto;
+ -- TODO if keys change, resign zone file.
+ -- TODO write to entirely different files than does primary,
+ -- so that primary can be reverted and signedPrimary enabled,
+ -- or vice-versa, without conflicts.
+ setup = setupPrimary hosts domain soa rs'
+ `onChange` toProp (zoneSigned domain (zoneFile domain))
+ `onChange` Service.reloaded "bind9"
+
+ cleanup = cleanupPrimary domain
+ `onChange` toProp (revert (zoneSigned domain (zoneFile domain)))
+ `onChange` Service.reloaded "bind9"
+
+ -- Include the public keys into the zone file.
+ rs' = include PubKSK : include PubZSK : rs
+ include k = (RootDomain, INCLUDE (keyFn domain k))
-- | Secondary dns server for a domain.
--
@@ -243,6 +271,7 @@ rField (MX _ _) = "MX"
rField (NS _) = "NS"
rField (TXT _) = "TXT"
rField (SRV _ _ _ _) = "SRV"
+rField (INCLUDE _) = "$INCLUDE"
rValue :: Record -> String
rValue (Address (IPv4 addr)) = addr
@@ -256,6 +285,7 @@ rValue (SRV priority weight port target) = unwords
, show port
, dValue target
]
+rValue (INCLUDE f) = f
rValue (TXT s) = [q] ++ filter (/= q) s ++ [q]
where
q = '"'
diff --git a/src/Propellor/Property/DnsSec.hs b/src/Propellor/Property/DnsSec.hs
index 55a447a1..201cc003 100644
--- a/src/Propellor/Property/DnsSec.hs
+++ b/src/Propellor/Property/DnsSec.hs
@@ -1,7 +1,7 @@
module Propellor.Property.DnsSec where
import Propellor
-import Propellor.Property.File
+import qualified Propellor.Property.File as File
-- | Puts the DNSSEC key files in place from PrivData.
--
@@ -14,10 +14,13 @@ keysInstalled domain = RevertableProperty setup cleanup
map installkey keys
cleanup = propertyList "DNSSEC keys removed" $
- map (notPresent . keyFn domain) keys
+ map (File.notPresent . keyFn domain) keys
- installkey k = (if isPublic k then hasPrivContentExposedFrom else hasPrivContentFrom)
- (keysrc k) (keyFn domain k) (Context domain)
+ installkey k = writer (keysrc k) (keyFn domain k) (Context domain)
+ where
+ writer
+ | isPublic k = File.hasPrivContentExposedFrom
+ | otherwise = File.hasPrivContentFrom
keys = [ PubZSK, PrivZSK, PubKSK, PrivKSK ]
@@ -30,6 +33,64 @@ keysInstalled domain = RevertableProperty setup cleanup
else "dnssec-keygen -f KSK -a RSASHA256 -b 4096 -n ZONE " ++ domain
]
+-- | Uses dnssec-signzone to sign a domain's zone file.
+--
+-- signedPrimary uses this, so this property does not normally need to be
+-- used directly.
+zoneSigned :: Domain -> FilePath -> RevertableProperty
+zoneSigned domain zonefile = RevertableProperty setup cleanup
+ where
+ setup = check needupdate (forceZoneSigned domain zonefile)
+ `requires` toProp (keysInstalled domain)
+
+ cleanup = combineProperties ("removed signed zone for " ++ domain)
+ [ File.notPresent signedzonefile
+ , File.notPresent dssetfile
+ , toProp (revert (keysInstalled domain))
+ ]
+
+ signedzonefile = dir </> domain ++ ".signed"
+ dssetfile = dir </> "-" ++ domain ++ "."
+ dir = takeDirectory zonefile
+
+ -- Need to update the signed zone if the zone file
+ -- has a newer timestamp.
+ needupdate = do
+ v <- catchMaybeIO $ getModificationTime signedzonefile
+ case v of
+ Nothing -> return True
+ Just t1 -> do
+ t2 <- getModificationTime zonefile
+ return (t2 >= t1)
+
+forceZoneSigned :: Domain -> FilePath -> Property
+forceZoneSigned domain zonefile = property ("zone signed for " ++ domain) $ liftIO $ do
+ salt <- take 16 <$> saltSha1
+ let p = proc "dnssec-signzone"
+ [ "-A"
+ , "-3", salt
+ , "-N", "keep"
+ , "-o", domain
+ , zonefile
+ -- the ordering of these key files does not matter
+ , keyFn domain PubZSK
+ , keyFn domain PubKSK
+ ]
+ -- Run in the same directory as the zonefile, so it will
+ -- write the dsset file there.
+ (_, _, _, h) <- createProcess $
+ p { cwd = Just (takeDirectory zonefile) }
+ ifM (checkSuccessProcess h)
+ ( return MadeChange
+ , return FailedChange
+ )
+
+saltSha1 :: IO String
+saltSha1 = readProcess "sh"
+ [ "-c"
+ , "head -c 1024 /dev/urandom | sha1sum | cut -d ' ' -f 1"
+ ]
+
-- | The file used for a given key.
keyFn :: Domain -> DnsSecKey -> FilePath
keyFn domain k = "/etc/bind/propellor" </>
diff --git a/src/Propellor/Types/Dns.hs b/src/Propellor/Types/Dns.hs
index 5e9666d8..2fbf51e5 100644
--- a/src/Propellor/Types/Dns.hs
+++ b/src/Propellor/Types/Dns.hs
@@ -62,6 +62,7 @@ data Record
| NS BindDomain
| TXT String
| SRV Word16 Word16 Word16 BindDomain
+ | INCLUDE FilePath
deriving (Read, Show, Eq, Ord)
getIPAddr :: Record -> Maybe IPAddr