git.iko.soy/typeable/octopod
on master help sign in
add TLS and authorization via certificate
commitfa3ff7d927f9177e06a40e8c0dc6a867f6ff1ab7 authorAndrei Orlov <aaorlov1@gmail.com> dateMon 30 Mar 2020, 18:32 (7 years ago) parentc21c5fd
4 files changed+153−38
default.nix+24−18 dm.cabal+32 src/DMC.hs+41−17 src/DMS.hs+56−3
default.nix+24−18
@@ -1,9 +1,10 @@
1−{ mkDerivation, aeson, base, bytestring, chronos, deriving-aeson
2−, directory, fast-logger, filepath, generic-lens, http-api-data
3−, http-client, lens, mtl, network-uri, optparse-generic
1+{ mkDerivation, aeson, base, bytestring, chronos, connection
2+, data-default-class, deriving-aeson, directory, fast-logger
3+, filepath, generic-lens, http-api-data, http-client
4+, http-client-tls, lens, mtl, network-uri, optparse-generic
45, postgresql-simple, resource-pool, servant, servant-client
5−, servant-server, stdenv, temporary, text, transformers
6−, typed-process, warp
6+, servant-server, stdenv, temporary, text, tls, transformers
7+, typed-process, warp, warp-tls, x509, x509-store, x509-validation
78}:
89mkDerivation {
910 pname = "dm";
@@ -12,23 +13,28 @@ mkDerivation {
1213 isLibrary = true;
1314 isExecutable = true;
1415 libraryHaskellDepends = [
15− aeson base bytestring chronos deriving-aeson directory fast-logger
16− filepath generic-lens http-api-data http-client lens mtl
17− network-uri optparse-generic postgresql-simple resource-pool
18− servant servant-client servant-server temporary text transformers
19− typed-process warp
16+ aeson base bytestring chronos connection data-default-class
17+ deriving-aeson directory fast-logger filepath generic-lens
18+ http-api-data http-client http-client-tls lens mtl network-uri
19+ optparse-generic postgresql-simple resource-pool servant
20+ servant-client servant-server temporary text tls transformers
21+ typed-process warp warp-tls x509 x509-store x509-validation
2022 ];
2123 executableHaskellDepends = [
22− aeson base bytestring chronos directory fast-logger filepath
23− http-client mtl network-uri optparse-generic postgresql-simple
24− resource-pool servant servant-client servant-server temporary text
25− transformers typed-process warp
24+ aeson base bytestring chronos connection data-default-class
25+ directory fast-logger filepath http-client http-client-tls mtl
26+ network-uri optparse-generic postgresql-simple resource-pool
27+ servant servant-client servant-server temporary text tls
28+ transformers typed-process warp warp-tls x509 x509-store
29+ x509-validation
2630 ];
2731 testHaskellDepends = [
28− aeson base bytestring chronos directory fast-logger filepath
29− http-client mtl network-uri optparse-generic postgresql-simple
30− resource-pool servant servant-client servant-server temporary text
31− transformers typed-process warp
32+ aeson base bytestring chronos connection data-default-class
33+ directory fast-logger filepath http-client http-client-tls mtl
34+ network-uri optparse-generic postgresql-simple resource-pool
35+ servant servant-client servant-server temporary text tls
36+ transformers typed-process warp warp-tls x509 x509-store
37+ x509-validation
3238 ];
3339 homepage = "https://github.com/https://github.com/Aviora/dm#readme";
3440 license = stdenv.lib.licenses.bsd3;
dm.cabal+32
@@ -50,6 +50,8 @@ library
5050 , base >=4.7 && <5
5151 , bytestring
5252 , chronos
53+ , connection
54+ , data-default-class
5355 , deriving-aeson
5456 , directory
5557 , fast-logger
@@ -57,6 +59,7 @@ library
5759 , generic-lens
5860 , http-api-data
5961 , http-client
62+ , http-client-tls
6063 , lens
6164 , mtl
6265 , network-uri
@@ -68,9 +71,14 @@ library
6871 , servant-server
6972 , temporary
7073 , text
74+ , tls
7175 , transformers
7276 , typed-process
7377 , warp
78+ , warp-tls
79+ , x509
80+ , x509-store
81+ , x509-validation
7482 default-language: Haskell2010
7583
7684executable dmc-exe
@@ -87,11 +95,14 @@ executable dmc-exe
8795 , base >=4.7 && <5
8896 , bytestring
8997 , chronos
98+ , connection
99+ , data-default-class
90100 , directory
91101 , dm
92102 , fast-logger
93103 , filepath
94104 , http-client
105+ , http-client-tls
95106 , mtl
96107 , network-uri
97108 , optparse-generic
@@ -102,9 +113,14 @@ executable dmc-exe
102113 , servant-server
103114 , temporary
104115 , text
116+ , tls
105117 , transformers
106118 , typed-process
107119 , warp
120+ , warp-tls
121+ , x509
122+ , x509-store
123+ , x509-validation
108124 default-language: Haskell2010
109125
110126executable dms-exe
@@ -121,10 +137,13 @@ executable dms-exe
121137 , base >=4.7 && <5
122138 , bytestring
123139 , chronos
140+ , connection
141+ , data-default-class
124142 , directory
125143 , dm
126144 , fast-logger
127145 , http-client
146+ , http-client-tls
128147 , mtl
129148 , network-uri
130149 , optparse-generic
@@ -135,9 +154,14 @@ executable dms-exe
135154 , servant-server
136155 , temporary
137156 , text
157+ , tls
138158 , transformers
139159 , typed-process
140160 , warp
161+ , warp-tls
162+ , x509
163+ , x509-store
164+ , x509-validation
141165 default-language: Haskell2010
142166
143167test-suite dm-test
@@ -153,11 +177,14 @@ test-suite dm-test
153177 , base >=4.7 && <5
154178 , bytestring
155179 , chronos
180+ , connection
181+ , data-default-class
156182 , directory
157183 , dm
158184 , fast-logger
159185 , filepath
160186 , http-client
187+ , http-client-tls
161188 , mtl
162189 , network-uri
163190 , optparse-generic
@@ -168,7 +195,12 @@ test-suite dm-test
168195 , servant-server
169196 , temporary
170197 , text
198+ , tls
171199 , transformers
172200 , typed-process
173201 , warp
202+ , warp-tls
203+ , x509
204+ , x509-store
205+ , x509-validation
174206 default-language: Haskell2010
src/DMC.hs+41−17
@@ -4,6 +4,7 @@ import Chronos
44import Control.Lens hiding (List)
55import Control.Monad
66import Data.Coerce
7+import Data.Default.Class (def)
78import Data.Foldable
89import Data.Generics.Product
910import Data.Maybe (fromMaybe)
@@ -11,9 +12,13 @@ import Data.Proxy
1112import Data.Text as T
1213import Data.Text.IO as T
1314import Data.Text.Lens
15+import Network.Connection
1416import Network.HTTP.Client
15− ( defaultManagerSettings, managerResponseTimeout, newManager
17+ ( managerResponseTimeout, newManager
1618 , responseTimeoutMicro)
19+import Network.HTTP.Client.TLS
20+import Network.TLS
21+import Network.TLS.Extra.Cipher
1722import Network.URI
1823import Options.Generic
1924import Prelude as P
@@ -33,12 +38,12 @@ import Types
3338-- | CLI options
3439-- FIXME: add options documentation
3540data Args
36− = Create { name :: Text, tag :: Text, envs :: [Text] }
37− | List
38− | Edit { name :: Text }
39− | Destroy { name :: Text }
40− | Update { name :: Text, tag :: Text }
41− | Info { name :: Text }
41+ = Create { name :: Text, tag :: Text, envs :: [Text], tlsCert :: Maybe Text, tlsKey :: Maybe Text }
42+ | List { tlsCert :: Maybe Text, tlsKey :: Maybe Text }
43+ | Edit { name :: Text, tlsCert :: Maybe Text, tlsKey :: Maybe Text }
44+ | Destroy { name :: Text, tlsCert :: Maybe Text, tlsKey :: Maybe Text }
45+ | Update { name :: Text, tag :: Text, tlsCert :: Maybe Text, tlsKey :: Maybe Text }
46+ | Info { name :: Text, tlsCert :: Maybe Text, tlsKey :: Maybe Text }
4247 deriving stock (Show, Generic)
4348
4449instance ParseRecord Args where
@@ -49,22 +54,41 @@ instance ParseRecord Args where
4954runDMC :: IO ()
5055runDMC = do
5156 args <- getRecord "DMC"
52− manager <- newManager defaultManagerSettings
53− { managerResponseTimeout =
54− responseTimeoutMicro $ 20 * 60 * 10 ^ (6 :: Int) }
57+ let cert = maybe "cert.pem" unpack $ tlsCert args
58+ key = maybe "key.pem" unpack $ tlsKey args
59+ manager <- makeManager cert key
5560 env <- getBaseUrl
5661 let clientEnv = mkClientEnv manager env
5762 case args of
58− Create n t e -> do
63+ Create n t e _ _ -> do
5964 envPairs <- parseEnvs e
6065 handleCreate clientEnv $ Deployment (coerce n) (coerce t) envPairs
61− List -> handleList clientEnv
62− Edit tName -> handleEdit clientEnv $ DeploymentName tName
63− Destroy tName -> handleDestroy clientEnv $ DeploymentName tName
64− Update tName tTag -> handleUpdate clientEnv
66+ List _ _ -> handleList clientEnv
67+ Edit tName _ _ -> handleEdit clientEnv $ DeploymentName tName
68+ Destroy tName _ _ -> handleDestroy clientEnv $ DeploymentName tName
69+ Update tName tTag _ _ -> handleUpdate clientEnv
6570 (DeploymentName tName)
6671 (DeploymentTag tTag)
67− Info tName -> handleInfo clientEnv $ DeploymentName tName
72+ Info tName _ _ -> handleInfo clientEnv $ DeploymentName tName
73+ where
74+ makeManager cert key = do
75+ creds <- either error Just `fmap` credentialLoadX509 cert key
76+ let hooks = def
77+ { onCertificateRequest = \_ -> return creds
78+ , onServerCertificate = \_ _ _ _ -> return []
79+ }
80+ clientParams = (defaultParamsClient dmsHostName "")
81+ { clientHooks = hooks
82+ , clientSupported = def { supportedCiphers = ciphersuite_default }
83+ }
84+ tlsSettings = TLSSettings clientParams
85+ tlsManagerSettings' = (mkManagerSettings tlsSettings Nothing)
86+ { managerResponseTimeout =
87+ responseTimeoutMicro $ 20 * 60 * 10 ^ (6 :: Int) }
88+ in newManager tlsManagerSettings'
89+
90+dmsHostName :: String
91+dmsHostName = "dm.kube.thebestagent.pro"
6892
6993getBaseUrl :: IO BaseUrl
7094getBaseUrl = do
@@ -73,7 +97,7 @@ getBaseUrl = do
7397 let
7498 -- FIXME: unhardcode
7599 defaultBaseUrl :: BaseUrl
76− defaultBaseUrl = BaseUrl Http "dm.kube.thebestagent.pro" 80 ""
100+ defaultBaseUrl = BaseUrl Https dmsHostName 443 ""
77101 parseUrl :: String -> BaseUrl
78102 parseUrl = fromMaybe defaultBaseUrl . parseUrl'
79103 where
src/DMS.hs+56−3
@@ -7,14 +7,23 @@ import Control.Monad
77import Control.Monad.IO.Class
88import Control.Monad.Trans.Reader (ReaderT, ask, runReaderT)
99import Data.ByteString (ByteString)
10+import qualified Data.ByteString.Char8 as BSC
1011import Data.Coerce
12+import Data.Default.Class (def)
1113import Data.Int (Int64)
1214import Data.Maybe
1315import Data.Pool
1416import Data.Text (lines, pack, unpack, unwords)
1517import Data.Traversable
18+import Data.X509 (ExtKeyUsagePurpose(..), HashALG(HashSHA256))
19+import Data.X509.CertificateStore (readCertificateStore)
20+import Data.X509.Validation
21+ (ValidationChecks(..), ValidationHooks(..), checkLeafV3, validate)
1622import Database.PostgreSQL.Simple
23+import Network.TLS
24+ (CertificateRejectReason(..), CertificateUsage(..), onClientCertificate)
1725import Network.Wai.Handler.Warp
26+import Network.Wai.Handler.WarpTLS
1827import Options.Generic
1928import Prelude hiding (lines, unlines, unwords, log)
2029import Servant
@@ -24,14 +33,21 @@ import System.IO.Temp
2433import System.Log.FastLogger
2534import System.Process.Typed
2635
36+
2737import API
2838import DMS.Git
2939import DMS.Kubernetes
3040import Types
3141
3242
33−data Args
34− = Args { port :: Int, db :: ByteString, dbPoolSize :: Int }
43+data Args = Args
44+ { port :: Int
45+ , db :: ByteString
46+ , dbPoolSize :: Int
47+ , tlsCert :: ByteString
48+ , tlsKey :: ByteString
49+ , tlsStore :: ByteString
50+ }
3551 deriving (Generic, Show)
3652
3753instance ParseRecord Args where
@@ -69,7 +85,44 @@ runDMS = do
6985 kubectlBin <- kubectlPath ?! "kubectl not found"
7086 pgPool <- initConnectionPool (db args) (dbPoolSize args)
7187 let app' = app $ AppState pgPool logger' helmBin b2bHelmBin gitBin kubectlBin
72− run (port args) app'
88+ let cert = tlsCert args
89+ key = tlsKey args
90+ store = BSC.unpack $ tlsStore args
91+ tlsOpts' = tlsOpts cert key store
92+ in runTLS tlsOpts' warpOpts app'
93+
94+ where tlsOpts cert key storePath
95+ = (initTLSSettings cert key)
96+ { tlsWantClientCert = True
97+ , onInsecure = DenyInsecure "This server only accepts secure HTTPS connections."
98+ , tlsServerHooks = def { onClientCertificate =
99+ fmap certificateUsageFromValidations . (validateCertificate storePath)}
100+ }
101+ initTLSSettings cert key = tlsSettings (BSC.unpack cert) (BSC.unpack key)
102+ warpOpts = setPort serverPort defaultSettings
103+ certificateUsageFromValidations = maybe CertificateUsageAccept
104+ (CertificateUsageReject . CertificateRejectOther)
105+ serviceID = ("localhost", BSC.pack $ show serverPort)
106+ validationHooks = def
107+ { hookValidateName = \_ _ -> [] }
108+ validationChecks = def
109+ { checkStrictOrdering = True
110+ , checkLeafKeyPurpose = [KeyUsagePurpose_ClientAuth]
111+ , checkLeafV3 = False
112+ }
113+ validateCertificate storePath cert = do
114+ mstore <- readCertificateStore storePath
115+ maybe
116+ (pure $ Just "Cannot init a store")
117+ (fmap fromX509FailedReasons . (\store -> validate HashSHA256
118+ validationHooks validationChecks store def serviceID cert))
119+ mstore
120+ fromX509FailedReasons reasons = case reasons of
121+ [] -> Nothing
122+ _ -> Just (show reasons)
123+
124+serverPort :: Int
125+serverPort = 4443
73126
74127initConnectionPool :: ByteString -> Int -> IO PgPool
75128initConnectionPool dbConnStr =