summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--cokiki.hs36
-rw-r--r--lib/Kiki.hs78
-rw-r--r--lib/ProcessUtils.hs2
3 files changed, 90 insertions, 26 deletions
diff --git a/cokiki.hs b/cokiki.hs
index 54cc4ba..b6d94a0 100644
--- a/cokiki.hs
+++ b/cokiki.hs
@@ -20,6 +20,7 @@ import System.IO
20import System.Posix.User 20import System.Posix.User
21import CommandLine 21import CommandLine
22import Data.OpenPGP (SymmetricAlgorithm(Unencrypted)) 22import Data.OpenPGP (SymmetricAlgorithm(Unencrypted))
23import qualified Hosts
23 24
24usage = unlines 25usage = unlines
25 [ "cokiki <command> [--chroot=ROOTDIR]" 26 [ "cokiki <command> [--chroot=ROOTDIR]"
@@ -42,17 +43,20 @@ usage = unlines
42 , "COMMANDS" 43 , "COMMANDS"
43 , "" 44 , ""
44 , " ssh-client Modify system ssh configuration to respect" 45 , " ssh-client Modify system ssh configuration to respect"
45 , " /var/cache/kiki/ssh_known_hosts." 46 , " /var/cache/kiki/config/ssh_known_hosts."
46 , "" 47 , ""
47 , " ssh-server Modify system ssh configuration to use the" 48 , " ssh-server Modify system ssh configuration to use the"
48 , " kiki-managed host key at" 49 , " kiki-managed host key at"
49 , " /var/cache/kiki/ssh_host_rsa_key." 50 , " /var/cache/kiki/config/ssh_host_rsa_key."
50 , "" 51 , ""
51 , " strongswan Modify /etc/ipsec.conf to include settings from" 52 , " strongswan Modify /etc/ipsec.conf to include settings from"
52 , " /var/cache/kiki/ipsec.conf." 53 , " /var/cache/kiki/config/ipsec.conf."
53 , "" 54 , ""
54 , " tor Modify /etc/tor/torrc to configure a tor hidden" 55 , " tor Modify /etc/tor/torrc to configure a tor hidden"
55 , " service for email (smtp), ssh, and http ports." 56 , " service for email (smtp), ssh, and http ports."
57 , ""
58 , " hosts Merge hostnames from /var/cache/kiki/config/hosts"
59 , " into system file /etc/hosts."
56 ] 60 ]
57 61
58main = do 62main = do
@@ -63,6 +67,7 @@ main = do
63 ["ssh-server"] -> Just $ sshServer uid <$> Kiki.ㄧchroot <*> Kiki.ㄧhomedir 67 ["ssh-server"] -> Just $ sshServer uid <$> Kiki.ㄧchroot <*> Kiki.ㄧhomedir
64 ["strongswan"] -> Just $ strongswan uid <$> Kiki.ㄧchroot <*> Kiki.ㄧhomedir 68 ["strongswan"] -> Just $ strongswan uid <$> Kiki.ㄧchroot <*> Kiki.ㄧhomedir
65 ["tor"] -> Just $ configureTor uid <$> Kiki.ㄧchroot <*> Kiki.ㄧhomedir 69 ["tor"] -> Just $ configureTor uid <$> Kiki.ㄧchroot <*> Kiki.ㄧhomedir
70 ["hosts"] -> Just $ configureHosts uid <$> Kiki.ㄧchroot <*> Kiki.ㄧhomedir
66 _ -> Nothing 71 _ -> Nothing
67 spec = uncurry fancy Kiki.kikiOptions "" 72 spec = uncurry fancy Kiki.kikiOptions ""
68 errorQuit msg = do 73 errorQuit msg = do
@@ -78,6 +83,7 @@ main = do
78maybeReadFile :: FilePath -> IO (Maybe L.ByteString) 83maybeReadFile :: FilePath -> IO (Maybe L.ByteString)
79maybeReadFile path = do 84maybeReadFile path = do
80 doesFileExist path >>= bool (return Nothing) (Just <$> L.readFile path) 85 doesFileExist path >>= bool (return Nothing) (Just <$> L.readFile path)
86
81myWriteFile f bs = do 87myWriteFile f bs = do
82 createDirectoryIfMissing True (takeDirectory f) 88 createDirectoryIfMissing True (takeDirectory f)
83 hPutStrLn stderr $ "Writing "++f 89 hPutStrLn stderr $ "Writing "++f
@@ -133,17 +139,29 @@ sshServer uid root cmn = whenRoot uid root cmn $ do
133 Kiki.importAndRefresh root cmn Unencrypted 139 Kiki.importAndRefresh root cmn Unencrypted
134 140
135strongswan uid root cmn = whenRoot uid root cmn $ do 141strongswan uid root cmn = whenRoot uid root cmn $ do
142 -- (1) /etc/ipsec.conf <-- 'include /var/cache/kiki/config/ipsec.conf'
136 -- Parsing as if ssh config, that's not right, but good enough for now. 143 -- Parsing as if ssh config, that's not right, but good enough for now.
137 ipsecconf <- parseSshConfig . fromMaybe "" <$> maybeReadFile (root "/etc/ipsec.conf") 144 ipsecconf <- parseSshConfig . fromMaybe "" <$> maybeReadFile (root "/etc/ipsec.conf")
138 let p:gs = groupBy (\_ d -> not $ sshIsDirective "include" d) $ ["#"]:ipsecconf 145 let p:gs = groupBy (\_ d -> not $ sshIsDirective "include" d) $ ["#"]:ipsecconf
139 got = filter (\(d:ds) -> elem "/var/cache/kiki/config/ipsec.conf" d) gs 146 got = filter (\(d:ds) -> elem "/var/cache/kiki/config/ipsec.conf" d) gs
140 case got of 147 case got of
141 _:_ -> do hPutStrLn stderr "ipsec already configured." 148 _:_ -> do hPutStrLn stderr "ipsec.conf already configured."
142 [] -> do let ipsecconf' = drop 1 $ ipsecconf ++ [stmt] 149 [] -> do let ipsecconf' = drop 1 $ ipsecconf ++ [stmt]
143 stmt = ["include", " ", "/var/cache/kiki/config/ipsec.conf"] 150 stmt = ["include", " ", "/var/cache/kiki/config/ipsec.conf"]
144 hPutStrLn stderr "adding include directive" 151 hPutStrLn stderr "adding include directive"
145 myWriteFile (root "/etc/ipsec.conf") $ unparseSshConfig ipsecconf' 152 myWriteFile (root "/etc/ipsec.conf") $ unparseSshConfig ipsecconf'
146 -- etc/ipsec.conf <-- 'include /var/cache/kiki/ipsec.conf' 153
154 -- (2) /etc/ipsec.secrets/ <- include /var/cache/kiki/config/ipsec.secrets
155 -- Parsing as if ssh config, that's not right, but good enough for now.
156 ipsecconf <- parseSshConfig . fromMaybe "" <$> maybeReadFile (root "/etc/ipsec.secrets")
157 let p:gs = groupBy (\_ d -> not $ sshIsDirective "include" d) $ ["#"]:ipsecconf
158 got = filter (\(d:ds) -> elem "/var/cache/kiki/config/ipsec.secrets" d) gs
159 case got of
160 _:_ -> do hPutStrLn stderr "ipsec.secrets already configured."
161 [] -> do let ipsecconf' = drop 1 $ ipsecconf ++ [stmt]
162 stmt = ["include", " ", "/var/cache/kiki/config/ipsec.secrets"]
163 hPutStrLn stderr "adding include directive"
164 myWriteFile (root "/etc/ipsec.secrets") $ unparseSshConfig ipsecconf'
147 Kiki.importAndRefresh root cmn Unencrypted 165 Kiki.importAndRefresh root cmn Unencrypted
148 166
149configureTor uid root cmn = whenRoot uid root cmn $ do 167configureTor uid root cmn = whenRoot uid root cmn $ do
@@ -201,6 +219,14 @@ configureTor uid root cmn = whenRoot uid root cmn $ do
201 Kiki.importAndRefresh root cmn Unencrypted 219 Kiki.importAndRefresh root cmn Unencrypted
202 return () 220 return ()
203 221
222configureHosts uid root cmn = whenRoot uid root cmn $ do
223 Kiki.importAndRefresh root cmn Unencrypted
224 hosts <- Hosts.decode . fromMaybe "" <$> maybeReadFile (root "/etc/hosts")
225 kikihosts <- Hosts.decode . fromMaybe "" <$> maybeReadFile (root "/var/cache/kiki/config/hosts")
226 let hosts' = hosts `Hosts.plus` kikihosts
227 case Hosts.diff hosts hosts' of
228 [] -> hPutStrLn stderr "kiki hosts are already merged."
229 _ -> myWriteFile (root "/etc/hosts") $ Hosts.encode hosts'
204 230
205parseSshConfig :: L.ByteString -> [[L.ByteString]] 231parseSshConfig :: L.ByteString -> [[L.ByteString]]
206parseSshConfig bs = map tokenize $ L8.lines bs 232parseSshConfig bs = map tokenize $ L8.lines bs
diff --git a/lib/Kiki.hs b/lib/Kiki.hs
index d5b3457..468394f 100644
--- a/lib/Kiki.hs
+++ b/lib/Kiki.hs
@@ -19,7 +19,7 @@ import Data.OpenPGP
19import Data.OpenPGP.Util 19import Data.OpenPGP.Util
20import Data.Ord 20import Data.Ord
21import System.Directory 21import System.Directory
22import System.FilePath.Posix 22import System.FilePath.Posix as FilePath
23import System.IO 23import System.IO
24import System.IO.Temp 24import System.IO.Temp
25import System.IO.Error 25import System.IO.Error
@@ -38,6 +38,8 @@ import qualified Data.ByteString.Lazy as L
38import qualified Data.ByteString.Lazy.Char8 as Char8 38import qualified Data.ByteString.Lazy.Char8 as Char8
39import qualified Data.Map.Strict as Map 39import qualified Data.Map.Strict as Map
40import qualified SSHKey as SSH 40import qualified SSHKey as SSH
41import Network.Socket -- (SockAddr)
42import ProcessUtils
41 43
42import GnuPGAgent (Query(..)) 44import GnuPGAgent (Query(..))
43import CommandLine 45import CommandLine
@@ -323,7 +325,7 @@ refreshCache rt rootdir = do
323 timeout = -1 -- TODO: set milisecond timeout on dotlock 325 timeout = -1 -- TODO: set milisecond timeout on dotlock
324 createDirectoryIfMissing True cachedir 326 createDirectoryIfMissing True cachedir
325 tmpdir <- createTempDirectory cachedir ("transaction." ++ takeBaseName destdir) 327 tmpdir <- createTempDirectory cachedir ("transaction." ++ takeBaseName destdir)
326 createSymbolicLink tmpdir (tmpdir ++ ".link") 328 createSymbolicLink (makeRelative cachedir tmpdir) (tmpdir ++ ".link")
327 lock <- dotlock_create destdir 0 329 lock <- dotlock_create destdir 0
328 T.mapM (flip dotlock_take timeout) lock 330 T.mapM (flip dotlock_take timeout) lock
329 let mkpath pth = tmpdir </> unslash (makeRelative destdir pth) 331 let mkpath pth = tmpdir </> unslash (makeRelative destdir pth)
@@ -336,7 +338,7 @@ refreshCache rt rootdir = do
336 -- otherwise call readyReadBeforeWrite on them. 338 -- otherwise call readyReadBeforeWrite on them.
337 rename (tmpdir ++ ".link") destdir 339 rename (tmpdir ++ ".link") destdir
338 er <- T.mapM dotlock_release lock 340 er <- T.mapM dotlock_release lock
339 void $ T.mapM removeDirectoryRecursive oldcommit 341 void $ T.mapM removeDirectoryRecursive (FilePath.combine cachedir <$> oldcommit)
340 -- Present transaction is Write only (or Write-Before-Read) which is fine. 342 -- Present transaction is Write only (or Write-Before-Read) which is fine.
341 -- If ever Read-Before-Write is required, uncomment and use: 343 -- If ever Read-Before-Write is required, uncomment and use:
342 -- let readyReadBeforeWrite pth = do 344 -- let readyReadBeforeWrite pth = do
@@ -367,6 +369,10 @@ refreshCache rt rootdir = do
367 wr f bs 369 wr f bs
368 write = write' writeFile 370 write = write' writeFile
369 writeL = write' L.writeFile 371 writeL = write' L.writeFile
372 writeL077 f bs = do
373 old_umask <- setFileCreationMask 0o077
374 writeL f bs
375 setFileCreationMask old_umask
370 376
371 let names = do wk <- rtWorkingKey rt 377 let names = do wk <- rtWorkingKey rt
372 -- XXX unnecessary signature check 378 -- XXX unnecessary signature check
@@ -436,6 +442,11 @@ refreshCache rt rootdir = do
436 (mkpath "ipsec.d/private/" ++ Char8.unpack oname++".pem") 442 (mkpath "ipsec.d/private/" ++ Char8.unpack oname++".pem")
437 "missing ipsec key?" 443 "missing ipsec key?"
438 444
445 -- TODO: probably we should add multiple entries for the case that there
446 -- are multiple secret master-keys each with distinct tor and ipsec keys.
447 writeL077 (mkpath "ipsec.secrets")
448 $ ": RSA /var/cache/kiki/config/ipsec.d/private/" <> oname <> ".pem"
449
439 writeSecret "ssh-client" 450 writeSecret "ssh-client"
440 (mkpath "root/.ssh/id_rsa") 451 (mkpath "root/.ssh/id_rsa")
441 "missing ssh-client key?" 452 "missing ssh-client key?"
@@ -457,32 +468,57 @@ refreshCache rt rootdir = do
457 either warn (write $ mkpath "ipsec.d/certs/" ++ Char8.unpack oname++".pem") 468 either warn (write $ mkpath "ipsec.d/certs/" ++ Char8.unpack oname++".pem")
458 $ show_pem' "ipsec" grip (rtKeyDB rt) pemFromPacket 469 $ show_pem' "ipsec" grip (rtKeyDB rt) pemFromPacket
459 470
460 let cs = filter notme (Map.elems $ rtKeyDB rt) 471 let onionkeys = mapMaybe namedContact $ Map.elems $ rtKeyDB rt
472 cs = filter (\(_,_,kd) -> notme kd) onionkeys
461 kk = keykey (fromJust $ rtWorkingKey rt) 473 kk = keykey (fromJust $ rtWorkingKey rt)
462 notme kd = keykey (keyPacket kd) /= kk 474 notme kd = keykey (keyPacket kd) /= kk
463 475
464 installConctact :: KeyData -> IO Char8.ByteString 476 namedContact kd = do
465 installConctact kd = do
466 -- The getHostnames command requires a valid cross-signed tor key 477 -- The getHostnames command requires a valid cross-signed tor key
467 -- for each onion name returned in (_,(ns,_)). 478 -- for each onion name returned in (_,(ns,_)).
468 let (addr,(ns,_)) = getHostnames kd 479 let (addr,(ns,_)) = getHostnames kd
469 contactname = fmap Char8.unpack $ listToMaybe ns -- only first onion name. 480 fmap (\n -> (n,addr, kd)) $ listToMaybe ns -- only first onion name.
470 flip (maybe $ return Char8.empty) contactname $ \contactname -> do 481
482 installConctact :: (L.ByteString, SockAddr, KeyData) -> IO Char8.ByteString
483 installConctact (contactname,addr,kd) = do
471 484
472 let cpath = interp (Map.singleton "onion" contactname) "ipsec.d/certs/%(onion).pem" 485 let cpath = interp (Map.singleton "onion" (Char8.unpack contactname)) "ipsec.d/certs/%(onion).pem"
473 their_master = packet $ keyMappedPacket kd 486 their_master = packet $ keyMappedPacket kd
474 -- We find all cross-certified ipsec keys for the given cross-certified onion name. 487 -- We find all cross-certified ipsec keys for the given cross-certified onion name.
475 ipsecs :: [Packet] 488 ipsecs :: [Packet]
476 ipsecs = sortOn (Down . timestamp) 489 ipsecs = sortOn (Down . timestamp)
477 $ getSubkeys CrossSigned their_master (keySubKeys kd) "ipsec" 490 $ getSubkeys CrossSigned their_master (keySubKeys kd) "ipsec"
478 ++ getSubkeys CrossSigned their_master (keySubKeys kd) "strongswan" 491 -- ++ getSubkeys CrossSigned their_master (keySubKeys kd) "strongswan"
492 sshs :: [Packet]
493 sshs = sortOn (Down . timestamp)
494 $ getSubkeys CrossSigned their_master (keySubKeys kd) "ssh-server"
479 bss <- forM (take 1 ipsecs) $ \k -> do 495 bss <- forM (take 1 ipsecs) $ \k -> do
480 let warn' x = warn x >> return Char8.empty 496 let warn' x = warn x >> return Char8.empty
481 flip (either warn') (pemFromPacket k :: Either String String) $ \pem -> do 497 flip (either warn') (pemFromPacket k :: Either String String) $ \pem -> do
482 write (mkpath cpath) pem 498 write (mkpath cpath) pem
483 return $ strongswanForContact addr contactname 499 case take 1 sshs of
500 [sshkey] -> do
501 (_,(sout,serr)) <- runExternal "(f=$(mktemp); cat > \"$f\"; ssh-keygen -l -f \"$f\" | (read _ hash _; echo -n $hash.ssh.cryptonomic.net) | tr -d ':')"
502 (Just $ sshblobFromPacket sshkey)
503 -- ssh-keygen -l -f /dev/stdin
504 -- putStrLn $ "wtf="++show(sout,serr, sshblobFromPacket sshkey)
505 return $ strongswanForContact addr contactname (Char8.fromChunks [sout])
506 [] -> error "fuck."
484 return $ Char8.concat bss 507 return $ Char8.concat bss
485 508
509 known_hosts = L.concat $ map getssh onionkeys
510
511 getssh (contactname,addr,kd) = do
512 let their_master = packet $ keyMappedPacket kd
513 sshs :: [Packet]
514 sshs = sortOn (Down . timestamp)
515 $ getSubkeys CrossSigned their_master (keySubKeys kd) "ssh-server"
516 blobs = mapMaybe sshblobFromPacketL sshs
517 taggedblobs = map (\b -> contactname <> " " <> b) blobs
518 Char8.unlines taggedblobs
519
520 writeL (mkpath "ssh_known_hosts") known_hosts
521
486 cons <- mapM installConctact cs 522 cons <- mapM installConctact cs
487 writeL (mkpath "ipsec.conf") . Char8.unlines 523 writeL (mkpath "ipsec.conf") . Char8.unlines
488 $ [ "conn %default" 524 $ [ "conn %default"
@@ -497,7 +533,7 @@ refreshCache rt rootdir = do
497 , " leftsubnet=" <> Char8.pack (showA wkaddr) <> "/128" 533 , " leftsubnet=" <> Char8.pack (showA wkaddr) <> "/128"
498 , " leftauth=pubkey" 534 , " leftauth=pubkey"
499 , " leftid=" <> Char8.pack (showA wkaddr) 535 , " leftid=" <> Char8.pack (showA wkaddr)
500 , " leftrsasigkey=" <> oname 536 , " leftrsasigkey=" <> oname <> ".pem"
501 , " leftikeport=4500" 537 , " leftikeport=4500"
502 , " rightikeport=4500" 538 , " rightikeport=4500"
503 , " right=%any" 539 , " right=%any"
@@ -508,13 +544,13 @@ refreshCache rt rootdir = do
508 ] ++ filter (not . Char8.null) cons 544 ] ++ filter (not . Char8.null) cons
509 commit 545 commit
510 546
511strongswanForContact addr oname = Char8.unlines 547strongswanForContact addr oname rightip = Char8.unlines
512 [ "conn " <> p oname 548 [ "conn " <> oname
513 , " right=%" <> p oname <> ".ipv4" 549 , " right=lan." <> rightip
514 , " rightsubnet=" <> p (showA addr) <> "/128" 550 , " rightsubnet=" <> p (showA addr) <> "/128"
515 , " rightauth=pubkey" 551 , " rightauth=pubkey"
516 , " rightid=" <> p (showA addr) 552 , " rightid=" <> p (showA addr)
517 , " rightrsasigkey=" <> p (oname) <> ".pem" 553 , " rightrsasigkey=" <> oname <> ".pem"
518 ] 554 ]
519 where p = Char8.pack 555 where p = Char8.pack
520 556
@@ -578,11 +614,11 @@ interp vars raw = es >>= interp1
578 where (key,rest) = break (==')') str 614 where (key,rest) = break (==')') str
579 interp1 plain = plain 615 interp1 plain = plain
580 616
581sshblobFromPacket k = blob 617sshblobFromPacket k = Char8.unpack $ fromJust $ sshblobFromPacketL k
582 where 618
583 Just (RSAKey (MPI n) (MPI e)) = rsaKeyFromPacket k 619sshblobFromPacketL k = do
584 bs = SSH.keyblob (n,e) 620 RSAKey (MPI n) (MPI e) <- rsaKeyFromPacket k
585 blob = Char8.unpack bs 621 return $ SSH.keyblob (n,e)
586 622
587{- 623{-
588replaceSshServerKeys root cmn = do 624replaceSshServerKeys root cmn = do
diff --git a/lib/ProcessUtils.hs b/lib/ProcessUtils.hs
index b89edb9..1a9cc04 100644
--- a/lib/ProcessUtils.hs
+++ b/lib/ProcessUtils.hs
@@ -126,10 +126,12 @@ readProcessWithErrorH cmd args stdin erH = do
126runExternal :: String -> Maybe String -> IO (Int,(S.ByteString,S.ByteString)) 126runExternal :: String -> Maybe String -> IO (Int,(S.ByteString,S.ByteString))
127runExternal cmd input = do 127runExternal cmd input = do
128 cwd <- getCurrentDirectory 128 cwd <- getCurrentDirectory
129 {-
129 putStr $ takeFileName cwd ++ "> " ++ cmd 130 putStr $ takeFileName cwd ++ "> " ++ cmd
130 ++ case input of 131 ++ case input of
131 Nothing -> "\n" 132 Nothing -> "\n"
132 Just s -> " <<EOF\n" ++ s ++ "EOF\n" 133 Just s -> " <<EOF\n" ++ s ++ "EOF\n"
134 -}
133 let p = (shell cmd) { std_in = maybe Inherit (const CreatePipe) input 135 let p = (shell cmd) { std_in = maybe Inherit (const CreatePipe) input
134 , std_out = CreatePipe 136 , std_out = CreatePipe
135 , std_err = CreatePipe } 137 , std_err = CreatePipe }