propellor/src/Propellor/Property/DnsSec.hs

121 lines
3.5 KiB
Haskell

module Propellor.Property.DnsSec where
import Propellor
import qualified Propellor.Property.File as File
-- | Puts the DNSSEC key files in place from PrivData.
--
-- signedPrimary uses this, so this property does not normally need to be
-- used directly.
keysInstalled :: Domain -> RevertableProperty
keysInstalled domain = setup <!> cleanup
where
setup = propertyList "DNSSEC keys installed" $
map installkey keys
cleanup = propertyList "DNSSEC keys removed" $
map (File.notPresent . keyFn domain) keys
installkey k = writer (keysrc k) (keyFn domain k) (Context domain)
where
writer
| isPublic k = File.hasPrivContentExposedFrom
| otherwise = File.hasPrivContentFrom
keys = [ PubZSK, PrivZSK, PubKSK, PrivKSK ]
keysrc k = PrivDataSource (DnsSec k) $ unwords
[ "The file with extension"
, keyExt k
, "created by running:"
, if isZoneSigningKey k
then "dnssec-keygen -a RSASHA256 -b 2048 -n ZONE " ++ domain
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 = setup <!> cleanup
where
setup = check needupdate (forceZoneSigned domain zonefile)
`requires` toProp (keysInstalled domain)
cleanup = File.notPresent (signedZoneFile zonefile)
`before` File.notPresent dssetfile
`before` toProp (revert (keysInstalled domain))
dssetfile = dir </> "-" ++ domain ++ "."
dir = takeDirectory zonefile
-- Need to update the signed zone file if the zone file or
-- any of the keys have a newer timestamp.
needupdate = do
v <- catchMaybeIO $ getModificationTime (signedZoneFile zonefile)
case v of
Nothing -> return True
Just t1 -> anyM (newerthan t1) $
zonefile : map (keyFn domain) [minBound..maxBound]
newerthan t1 f = do
t2 <- getModificationTime f
return (t2 >= t1)
forceZoneSigned :: Domain -> FilePath -> Property NoInfo
forceZoneSigned domain zonefile = property ("zone signed for " ++ domain) $ liftIO $ do
salt <- take 16 <$> saltSha1
let p = proc "dnssec-signzone"
[ "-A"
, "-3", salt
-- The serial number needs to be increased each time the
-- zone is resigned, even if there are no other changes,
-- so that it will propigate to secondaries. So, use the
-- unixtime serial format.
, "-N", "unixtime"
, "-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/dnssec" </> concat
[ "K" ++ domain ++ "."
, if isZoneSigningKey k then "ZSK" else "KSK"
, keyExt k
]
-- | These are the extensions that dnssec-keygen looks for.
keyExt :: DnsSecKey -> String
keyExt k
| isPublic k = ".key"
| otherwise = ".private"
isPublic :: DnsSecKey -> Bool
isPublic k = k `elem` [PubZSK, PubKSK]
isZoneSigningKey :: DnsSecKey -> Bool
isZoneSigningKey k = k `elem` [PubZSK, PrivZSK]
-- | dnssec-signzone makes a .signed file
signedZoneFile :: FilePath -> FilePath
signedZoneFile zonefile = zonefile ++ ".signed"