summaryrefslogtreecommitdiff
path: root/src/Propellor/Property/DnsSec.hs
blob: aa58dc6096c560fa8b167b2fd172527d27cde606 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
module Propellor.Property.DnsSec where

import Propellor.Base
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 (HasInfo + UnixLike) UnixLike
keysInstalled domain = setup <!> cleanup
  where
	setup = propertyList "DNSSEC keys installed" $ toProps $
		map installkey keys

	cleanup = propertyList "DNSSEC keys removed" $ toProps $
		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 (HasInfo + UnixLike) UnixLike
zoneSigned domain zonefile = setup <!> cleanup
  where
	setup :: Property (HasInfo + UnixLike)
	setup = check needupdate (forceZoneSigned domain zonefile)
		`requires` keysInstalled domain
	
	cleanup :: Property UnixLike
	cleanup = File.notPresent (signedZoneFile zonefile)
		`before` File.notPresent dssetfile
		`before` 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 UnixLike
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 propagate 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"