wai-middleware-delegate 0.1.4.1 → 0.1.4.2
raw patch · 13 files changed
+626/−325 lines, 13 filesdep +directorydep +filepathdep +mustachedep ~bytestringdep ~crypton-connectiondep ~http-clientPVP: minor bump suggested
API additions: PVP suggests at least a minor version bump
Dependencies added: directory, filepath, mustache, temporary, test-certs, unix
Dependency ranges changed: bytestring, crypton-connection, http-client, http-client-tls, http-types, streaming-commons, text, tmp-proc
API changes (from Hackage documentation)
+ Network.Wai.Middleware.Delegate: defaultSettings :: ProxySettings
Files
- ChangeLog.md +6/−0
- src/Network/Wai/Middleware/Delegate.hs +34/−27
- templates/nginx-test.conf.mustache +42/−0
- test/IntegrationTest.hs +210/−120
- test/Network/Connection/CPP.hs +24/−0
- test/Test/Fetch.hs +2/−3
- test/Test/NginxGateway.hs +231/−0
- test/Test/TestRequests.hs +39/−36
- test/Test/WithExtras.hs +15/−19
- test/certificate.csr +0/−27
- test/certificate.pem +0/−30
- test/key.pem +0/−51
- wai-middleware-delegate.cabal +23/−12
ChangeLog.md view
@@ -2,6 +2,12 @@ `wai-middleware-delegate` uses [PVP Versioning][1]. +## 0.1.4.2 -- 2024-10-28++* Export defaultSettings as an alternative to Default#def++* Add comments 'deprecating' the Default instance of ProxySettings+ ## 0.1.4.1 -- 2024-03-17 * Relax the upper bounds on bytestring to allow bytestring 0.12.1
src/Network/Wai/Middleware/Delegate.hs view
@@ -30,6 +30,7 @@ -- * Configuration , ProxySettings (..)+ , defaultSettings -- * Aliases , RequestPredicate@@ -51,8 +52,7 @@ import qualified Data.ByteString.Lazy.Char8 as LC8 import Data.CaseInsensitive (mk) import Data.Conduit- ( ConduitM- , ConduitT+ ( ConduitT , Flush (..) , Void , await@@ -67,13 +67,11 @@ import Data.Default (Default (..)) import Data.IORef (newIORef, readIORef, writeIORef) import Data.Int (Int64)-import Data.Monoid ((<>)) import Data.Streaming.Network ( ClientSettings , clientSettingsTCP , runTCPClient )-import Data.String (IsString) import Network.HTTP.Client ( BodyReader , GivesPopper@@ -86,11 +84,9 @@ , withResponse ) import Network.HTTP.Types- ( Header- , HeaderName+ ( HeaderName , hContentType , internalServerError500- , status304 , status500 ) import Network.HTTP.Types.Header (hHost)@@ -137,25 +133,36 @@ } +{- | This instance is DEPRECATED and will removed in a later release.+please use 'defaultSettings' instead+-} instance Default ProxySettings where- def =- ProxySettings- { -- defaults to returning internal server error showing the error in the body- proxyOnException = onException- , -- default to 15 seconds- proxyTimeout = 15- , proxyHost = "localhost"- , proxyRedirectCount = 0- }- where- onException :: SomeException -> Wai.Response- onException e =- Wai.responseLBS- internalServerError500- [(hContentType, "text/plain; charset=utf-8")]- $ LC8.fromChunks [C8.pack $ show e]+ -- This is DEPRECATED, please use 'defaultSettings' instead+ def = defaultSettings +{- | A default 'ProxySettings' that makes simplistic assumptions, e.g, that+target host is @localhost@+-}+defaultSettings :: ProxySettings+defaultSettings =+ ProxySettings+ { -- defaults to returning internal server error showing the error in the body+ proxyOnException = onException+ , -- default to 15 seconds+ proxyTimeout = 15+ , proxyHost = "localhost"+ , proxyRedirectCount = 0+ }+ where+ onException :: SomeException -> Wai.Response+ onException e =+ Wai.responseLBS+ internalServerError500+ [(hContentType, "text/plain; charset=utf-8")]+ $ LC8.fromChunks [C8.pack $ show e]++ -- | A Wai Application that acts as a http/https proxy. simpleProxy :: ProxySettings ->@@ -229,10 +236,10 @@ toClientSettings :: Wai.Request -> ClientSettings toClientSettings req = case C8.break (== ':') $ Wai.rawPathInfo req of- (host, "") -> clientSettingsTCP (defaultClientPort req) host- (host, port') -> case C8.readInt $ C8.drop 1 port' of- Just (port, _) -> clientSettingsTCP port host- Nothing -> clientSettingsTCP (defaultClientPort req) host+ (h, "") -> clientSettingsTCP (defaultClientPort req) h+ (h, p') -> case C8.readInt $ C8.drop 1 p' of+ Just (p, _) -> clientSettingsTCP p h+ Nothing -> clientSettingsTCP (defaultClientPort req) h dropUpstreamHeaders :: (HeaderName, b) -> Bool
+ templates/nginx-test.conf.mustache view
@@ -0,0 +1,42 @@+server {+ listen 80;+ listen [::]:80;+ server_name {{common_name}};+ location / {+ rewrite ^ https://$host$request_uri? permanent;+ }+}++server {+ listen 443 ssl;+ listen [::]:443 ssl;+ server_name {{commonName}};+ server_tokens off;+ ssl_certificate /etc/tmp-proc/certs/certificate.pem;+ ssl_certificate_key /etc/tmp-proc/certs/key.pem;+ ssl_buffer_size 8k;+ ssl_protocols TLSv1.2;+ ssl_prefer_server_ciphers on;+ ssl_ciphers ECDH+AESGCM:ECDH+AES256:ECDH+AES128:DH+3DES:!ADH:!AECDH:!MD5;+ ssl_ecdh_curve secp384r1;+ ssl_session_tickets off;+ ssl_stapling on;+ ssl_stapling_verify on;+ resolver 8.8.8.8;++ location / {+ try_files $uri @tmp-proc-target;+ }++ location @tmp-proc-target {+ proxy_pass http://{{targetName}}:{{targetPort}};+ add_header X-Frame-Options "SAMEORIGIN" always;+ add_header X-XSS-Protection "1; mode=block" always;+ add_header X-Content-Type-Options "nosniff" always;+ add_header Referrer-Policy "no-referrer-when-downgrade" always;+ add_header Content-Security-Policy "default-src * data: 'unsafe-eval' 'unsafe-inline'" always;+ }++ root /var/www/html;+ index index.html index.htm index.nginx-debian.html;+}
test/IntegrationTest.hs view
@@ -1,40 +1,46 @@ {-# LANGUAGE DataKinds #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE NamedFieldPuns #-} {-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RankNTypes #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TypeApplications #-} {-# LANGUAGE TypeFamilies #-} module Main where -import Control.Monad (when)+import Control.Monad (forM_, when) import Data.ByteString (ByteString) import qualified Data.ByteString.Char8 as C8-import Data.Default (Default (..)) import Data.Foldable (for_)+import Data.Maybe (isJust) import Data.Proxy (Proxy (..)) import Data.Text (Text) import qualified Data.Text as Text import Data.Text.Encoding (encodeUtf8) import qualified Network.HTTP.Client as HC-import Network.HTTP.Client.TLS (newTlsManager) import Network.HTTP.Types (status500, statusCode) import Network.Wai ( Application , rawPathInfo , responseLBS )-import Network.Wai.Handler.Warp (Port, testWithApplication)+import Network.Wai.Handler.Warp (Port)+import Network.Wai.Handler.WarpTLS (tlsSettings) import Network.Wai.Middleware.Delegate ( ProxySettings (..)+ , defaultSettings , delegateToProxy ) import System.Environment (lookupEnv) import System.IO+import Test.Certs.Temp (certificatePath, keyPath, withCertPathsInTmp') import Test.Fetch (fetch) import Test.Hspec import Test.Hspec.TmpProc ( HList (..)+ , HandlesOf , HostIpAddress , Pinged (..) , Proc (..)@@ -42,102 +48,65 @@ , SvcURI , hAddr , handleOf- , startupAll , tdescribe- , terminateAll , toPinged , (&:)+ , (&:&) ) import qualified Test.Hspec.TmpProc as TmpProc import Test.HttpReply+import Test.NginxGateway (NginxGateway (..), mkBadTlsManager) import Test.TestRequests ( RequestBuilder (..) , buildRequest+ , nil+ , secure , testNotProxiedRequests , testOverRedirectedRequests , testRequests )-import Test.WithExtras- ( defaultTlsSettings- , testWithTLSApplication- ) defaultTestSettings :: ProxySettings-defaultTestSettings = def {proxyHost = "httpbin.org", proxyTimeout = 2}+defaultTestSettings = defaultSettings {proxyHost = "httpbin.org", proxyTimeout = 2} redirectTestSettings :: ProxySettings redirectTestSettings = defaultTestSettings {proxyRedirectCount = 2} -tmpHostSettings :: ByteString -> ProxySettings -> ProxySettings-tmpHostSettings tmpHost settings = settings {proxyHost = tmpHost}+setProxyHost :: ByteString -> ProxySettings -> ProxySettings+setProxyHost proxyHost ps = ps {proxyHost} main :: IO () main = do hSetBuffering stdin NoBuffering hSetBuffering stdout NoBuffering- dumpDebug' <- lookupEnv "DEBUG"- let dumpDebug = maybe False (const True) dumpDebug'- hspec $ tdescribe "when accessing http-bin in docker" $ do- -- TODO: reenable when a secure proxy is added to the docker tests- -- secureProxyTest dumpDebug- -- secureNotProxiedTest dumpDebug- insecureRedirectTest dumpDebug- insecureNotProxiedTest dumpDebug- insecureProxyTest dumpDebug+ dumpDebug <- isJust <$> lookupEnv "DEBUG"+ hspec $ tdescribe "accessing http-bin in docker" $ do+ forM_ insecureRequestSpecs $ flip runRequestSpecsTest dumpDebug+ forM_ secureRequestSpecs $ flip runRequestSpecsTest dumpDebug -defaultTestDelegate :: ProxySettings -> IO Application-defaultTestDelegate s = do+mkSampleApp :: ProxySettings -> IO Application+mkSampleApp s = do -- delegate everything but /status/418 let handleFunnyStatus req = rawPathInfo req /= "/status/418" dummyApp _ respond = respond $ responseLBS status500 [] "I should have been proxied"-- manager <- newTlsManager+ manager <- mkBadTlsManager return $ delegateToProxy s manager handleFunnyStatus dummyApp -toHost :: HttpBinFixture -> ByteString-toHost fixture = encodeUtf8 $ hAddr $ handleOf @"tmp-http-bin" Proxy fixture---fixtureApp :: HttpBinFixture -> IO Application-fixtureApp fixture = defaultTestDelegate $ tmpHostSettings (toHost fixture) defaultTestSettings---redirectApp :: HttpBinFixture -> IO Application-redirectApp fixture = defaultTestDelegate $ tmpHostSettings (toHost fixture) redirectTestSettings---testWithInsecureProxy' :: ((HttpBinFixture, Port) -> IO a) -> IO a-testWithInsecureProxy' = TmpProc.testWithApplication onlyHttpBin fixtureApp---testWithSecureProxy' :: ((HttpBinFixture, Port) -> IO a) -> IO a-testWithSecureProxy' action = do- tls <- defaultTlsSettings- TmpProc.testWithTLSApplication tls onlyHttpBin fixtureApp action---testWithInsecureProxy :: (Port -> IO ()) -> IO ()-testWithInsecureProxy = testWithApplication (defaultTestDelegate defaultTestSettings)---testWithSecureProxy :: (Port -> IO ()) -> IO ()-testWithSecureProxy withPort = do- tls <- defaultTlsSettings- testWithTLSApplication tls (defaultTestDelegate defaultTestSettings) withPort---testWithInsecureRedirects' :: ((HttpBinFixture, Port) -> IO ()) -> IO ()-testWithInsecureRedirects' = TmpProc.testWithApplication onlyHttpBin redirectApp+mkSampleApp' :: (HostOf a) => ProxySettings -> HandlesOf a -> IO Application+mkSampleApp' settings f = mkSampleApp $ setProxyHost (hostOf f) settings -tmpHostBuilder :: HttpBinFixture -> RequestBuilder -> RequestBuilder-tmpHostBuilder fixture builder = builder {rbHost = toHost fixture}+testWithSecureProxy :: ((ReverseProxyFixture, Port) -> IO a) -> IO a+testWithSecureProxy action = withCertPathsInTmp' $ \cp -> do+ let tls = tlsSettings (certificatePath cp) (keyPath cp)+ app = mkSampleApp' defaultTestSettings+ TmpProc.testWithTLSApplication tls nginxAndHttpBin app action onDirectAndProxy :: (HttpReply -> HttpReply -> IO ()) -> Bool -> Int -> RequestBuilder -> IO ()@@ -167,88 +136,209 @@ f direct proxied -insecureNotProxiedTest :: Bool -> Spec-insecureNotProxiedTest debug =- let scheme = "HTTP"- desc = "Proxy on " ++ scheme ++ " should fail"- assertNeq = onDirectAndProxy assertHttpRepliesDiffer debug- in aroundAll testWithInsecureProxy' $ describe desc $ do- for_ testNotProxiedRequests $ \(title, modifier) -> do- let shouldNotMatch (f, p) = assertNeq p $ modifier $ tmpHostBuilder f def- it (scheme ++ " " ++ title) shouldNotMatch+check ::+ (HostOf a) =>+ (HttpReply -> HttpReply -> IO ()) ->+ (RequestBuilder -> RequestBuilder) ->+ Bool ->+ RequestBuilder ->+ (HandlesOf a, Int) ->+ IO ()+check assertReplies modifier debug core (f, p) =+ let+ builder = modifier $ hostBuilder f core+ in+ onDirectAndProxy assertReplies debug p builder -insecureRedirectTest :: Bool -> Spec-insecureRedirectTest debug =- let scheme = "HTTP"- desc = "Proxy over " ++ scheme ++ " with too many redirects differs"- assertNeq = onDirectAndProxy assertHttpRepliesDiffer debug- in aroundAll testWithInsecureRedirects' $ describe desc $ do- for_ testOverRedirectedRequests $ \(title, modifier) -> do- let shouldNotMatch (f, p) = assertNeq p $ modifier $ tmpHostBuilder f def- it (scheme ++ " " ++ title) shouldNotMatch+type RequestSpecs = [(String, RequestBuilder -> RequestBuilder)] -insecureProxyTest :: Bool -> Spec-insecureProxyTest debug =- let scheme = "HTTP"- desc = "Simple " ++ scheme ++ " proxying:"- assertEq = onDirectAndProxy assertHttpRepliesAreEq debug- in aroundAll testWithInsecureProxy' $ describe desc $ do- for_ testRequests $ \(title, modifier) -> do- let shouldMatch (f, p) = assertEq p $ modifier $ tmpHostBuilder f def- it (scheme ++ " " ++ title) shouldMatch+data RequestSpecsTest a = RequestSpecsTest+ { stToDesc :: String -> String+ , stSettings :: ProxySettings+ , stWithApp :: forall b. HList a -> ProxySettings -> ((HandlesOf a, Port) -> IO b) -> IO b+ , stAssertReplies :: HttpReply -> HttpReply -> IO ()+ , stProc :: HList a+ , stScheme :: String+ , stCore :: RequestBuilder+ , stRequestSpecs :: RequestSpecs+ } -secureNotProxiedTest :: Bool -> Spec-secureNotProxiedTest debug =- let scheme = "HTTPS"- desc = "Proxy on " ++ scheme ++ " should fail"- assertNeq = onDirectAndProxy assertHttpRepliesDiffer debug- def' = def {rbSecure = True}- in aroundAll testWithSecureProxy' $ describe desc $ do- for_ testNotProxiedRequests $ \(title, modifier) -> do- let shouldNotMatch (f, p) = assertNeq p $ modifier $ tmpHostBuilder f def'- it (scheme ++ " " ++ title) shouldNotMatch+withSecureApp ::+ (HostOf a, TmpProc.AreProcs a) =>+ HList a ->+ ProxySettings ->+ ((HandlesOf a, Port) -> IO b) ->+ IO b+withSecureApp procs settings action = withCertPathsInTmp' $ \cp -> do+ let tls = tlsSettings (certificatePath cp) (keyPath cp)+ app = mkSampleApp' settings+ TmpProc.testWithTLSApplication tls procs app action -secureProxyTest :: Bool -> Spec-secureProxyTest debug =- let scheme = "HTTPS"- desc = "Simple " ++ scheme ++ " proxying:"- assertEq = onDirectAndProxy assertHttpRepliesAreEq debug- def' = def {rbSecure = True}- in aroundAll testWithSecureProxy' $ describe desc $ do- for_ testRequests $ \(title, modifier) -> do- let shouldMatch (f, p) = assertEq p $ modifier $ tmpHostBuilder f def'- it (scheme ++ " " ++ title) shouldMatch+withInsecureApp ::+ (HostOf a, TmpProc.AreProcs a) =>+ HList a ->+ ProxySettings ->+ ((HandlesOf a, Port) -> IO b) ->+ IO b+withInsecureApp procs settings = TmpProc.testWithApplication procs $ mkSampleApp' settings --- | A data type representing a connection to a HttpBin server.-data HttpBin = HttpBin+runRequestSpecsTest ::+ (TmpProc.AreProcs procs, HostOf procs) =>+ RequestSpecsTest procs ->+ Bool ->+ Spec+runRequestSpecsTest st debug =+ let RequestSpecsTest+ { stToDesc+ , stSettings+ , stAssertReplies+ , stProc+ , stScheme+ , stCore+ , stRequestSpecs+ , stWithApp+ } = st+ desc = stToDesc stScheme+ withApp = stWithApp stProc stSettings+ in aroundAll withApp $ describe desc $ do+ for_ stRequestSpecs $ \(title, modifier) -> do+ it (stScheme ++ " " ++ title) $ check stAssertReplies modifier debug stCore -type HttpBinFixture = HList '[ProcHandle HttpBin]+insecureRequestSpecs :: [RequestSpecsTest '[HttpBin]]+insecureRequestSpecs = [insecureRedirects, insecureNotProxied, insecureProxy] -onlyHttpBin :: HList '[HttpBin]-onlyHttpBin = HttpBin &: HNil+insecureProxy :: RequestSpecsTest '[HttpBin]+insecureProxy =+ RequestSpecsTest+ { stToDesc = \s -> "Simple " ++ s ++ " proxying:"+ , stSettings = defaultTestSettings+ , stAssertReplies = assertHttpRepliesAreEq+ , stProc = onlyHttpBin+ , stScheme = "HTTP"+ , stCore = nil+ , stRequestSpecs = testRequests+ , stWithApp = withInsecureApp+ } -setupHttpBin :: IO HttpBinFixture-setupHttpBin = startupAll onlyHttpBin+insecureRedirects :: RequestSpecsTest '[HttpBin]+insecureRedirects =+ RequestSpecsTest+ { stToDesc = \s -> "Proxy over " ++ s ++ " with too many redirects differs"+ , stSettings = redirectTestSettings+ , stAssertReplies = assertHttpRepliesDiffer+ , stProc = onlyHttpBin+ , stScheme = "HTTP"+ , stCore = nil+ , stRequestSpecs = testOverRedirectedRequests+ , stWithApp = withInsecureApp+ } -withHttpBin :: SpecWith HttpBinFixture -> Spec-withHttpBin = beforeAll setupHttpBin . afterAll terminateAll+insecureNotProxied :: RequestSpecsTest '[HttpBin]+insecureNotProxied =+ RequestSpecsTest+ { stToDesc = \s -> "Proxy on " ++ s ++ " should fail"+ , stSettings = defaultTestSettings+ , stAssertReplies = assertHttpRepliesDiffer+ , stProc = onlyHttpBin+ , stScheme = "HTTP"+ , stCore = nil+ , stRequestSpecs = testNotProxiedRequests+ , stWithApp = withInsecureApp+ } +secureRequestSpecs :: [RequestSpecsTest '[NginxGateway, HttpBin]]+secureRequestSpecs = [secureNotProxied, secureProxy]+++secureNotProxied :: RequestSpecsTest '[NginxGateway, HttpBin]+secureNotProxied =+ RequestSpecsTest+ { stToDesc = \s -> "Proxy on " ++ s ++ " should fail"+ , stSettings = defaultTestSettings+ , stAssertReplies = assertHttpRepliesDiffer+ , stProc = nginxAndHttpBin+ , stScheme = "HTTPS"+ , stCore = sNil+ , stRequestSpecs = testNotProxiedRequests+ , stWithApp = withSecureApp+ }+++secureProxy :: RequestSpecsTest '[NginxGateway, HttpBin]+secureProxy =+ RequestSpecsTest+ { stToDesc = \s -> "Simple " ++ s ++ " proxying:"+ , stSettings = defaultTestSettings+ , stAssertReplies = assertHttpRepliesAreEq+ , stProc = nginxAndHttpBin+ , stScheme = "HTTPS"+ , stCore = sNil+ , stRequestSpecs = testRequests+ , stWithApp = withSecureApp+ }+++sNil :: RequestBuilder+sNil = secure nil+++class HostOf a where+ hostOf :: HandlesOf a -> ByteString+++instance HostOf '[HttpBin] where+ hostOf f = encodeUtf8 $ hAddr $ handleOf @"tmp-http-bin" Proxy f+++instance HostOf '[NginxGateway, HttpBin] where+ hostOf f = encodeUtf8 $ hAddr $ handleOf @"nginx-test" Proxy f+++hostBuilder :: (HostOf a) => HandlesOf a -> RequestBuilder -> RequestBuilder+hostBuilder f builder = builder {rbHost = hostOf f}+++type ReverseProxyFixture = HandlesOf '[NginxGateway, HttpBin]+++aGateway :: NginxGateway+aGateway =+ NginxGateway+ { ngCommonName = "localhost"+ , ngTargetPort = 80+ , ngTargetName = "tmp-http-bin"+ }+++nginxAndHttpBin :: HList '[NginxGateway, HttpBin]+nginxAndHttpBin = aGateway &:& HttpBin+++-- | A data type representing a connection to a HttpBin server.+data HttpBin = HttpBin+++type HttpBinFixture = HandlesOf '[HttpBin]+++onlyHttpBin :: HList '[HttpBin]+onlyHttpBin = HttpBin &: HNil++ -- | Run HttpBin using tmp-proc. instance Proc HttpBin where type Image HttpBin = "kennethreitz/httpbin" type Name HttpBin = "tmp-http-bin"-- uriOf = mkUri' runArgs = [] reset _ = pure ()
+ test/Network/Connection/CPP.hs view
@@ -0,0 +1,24 @@+{-# LANGUAGE CPP #-}++{- |+Module : Network.Connection.CPP+Copyright : (c) 2022 Tim Emiola+Maintainer : Tim Emiola <adetokunbo@emio.la>+SPDX-License-Identifier: BSD3+-}+module Network.Connection.CPP (noCheckSettings) where++import Network.Connection (TLSSettings (..))+++#if MIN_VERSION_crypton_connection(0,4,0)+import Data.Default (def)+#endif++#if MIN_VERSION_crypton_connection(0,4,0)+noCheckSettings :: TLSSettings+noCheckSettings = TLSSettingsSimple True False False def+#else+noCheckSettings :: TLSSettings+noCheckSettings = TLSSettingsSimple True False False+#endif
test/Test/Fetch.hs view
@@ -24,7 +24,7 @@ import qualified Data.Conduit.Binary as CB import Data.Int (Int64) import Data.Maybe (fromMaybe)-import Network.Connection (TLSSettings (..))+import Network.Connection.CPP (noCheckSettings) import Network.HTTP.Client ( BodyReader , Request@@ -39,14 +39,13 @@ , withResponse ) import Network.HTTP.Client.TLS (mkManagerSettings)--- import Network.HTTP.Simple (setRequestManager, withResponse) import Network.HTTP.Types (hContentLength, statusCode) import Test.HttpReply fetch :: Request -> IO HttpReply fetch req = do- let mSettings = mkManagerSettings (TLSSettingsSimple True False False) Nothing+ let mSettings = mkManagerSettings noCheckSettings Nothing mSettings' = managerSetProxy proxyFromRequest mSettings m <- newManager mSettings' withResponse req m getSrc
+ test/Test/NginxGateway.hs view
@@ -0,0 +1,231 @@+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE NamedFieldPuns #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE TypeApplications #-}+{-# LANGUAGE TypeFamilies #-}++{- |+Module : Test.NginxGateway+Copyright : (c) 2022 Tim Emiola+Maintainer : Tim Emiola <adetokunbo@emio.la>+SPDX-License-Identifier: BSD3+-}+module Test.NginxGateway+ ( -- * data types+ NginxGateway (..)+ , NginxPrep (..)++ -- * ping via https+ , pingHttps++ -- * a TLS manager that ignores errors+ , mkBadTlsManager+ )+where++import qualified Data.ByteString.Char8 as C8+import Data.Data (Proxy (..))+import Data.List (find)+import Data.Text (Text)+import qualified Data.Text as Text+import qualified Data.Text.IO as Text+import Network.Connection.CPP (noCheckSettings)+import qualified Network.HTTP.Client as HC+import qualified Network.HTTP.Client.TLS as HC+import Network.HTTP.Types.Header (hHost)+import Network.HTTP.Types.Status (statusCode)+import Paths_wai_middleware_delegate (getDataDir)+import System.Directory (createDirectory, removeDirectoryRecursive)+import System.FilePath ((</>))+import System.IO.Temp (createTempDirectory, getCanonicalTemporaryDirectory)+import System.Posix.ByteString (getEffectiveGroupID, getEffectiveUserID)+import System.Posix.Types (GroupID, UserID)+import System.TmpProc+ ( HostIpAddress+ , Pinged (..)+ , Preparer (..)+ , Proc (..)+ , ProcHandle (..)+ , SlimHandle (..)+ , SvcURI+ , ToRunCmd (..)+ , toPinged+ )+import Test.Certs.Temp (CertPaths (..), defaultConfig, generateAndStore)+import Text.Mustache+ ( ToMustache (..)+ , automaticCompile+ , object+ , substitute+ , (~>)+ )+++-- | Run Nginx as a temporary process.+instance Proc NginxGateway where+ -- use this linuxserver.io nginx as it is setup to allow easy override of+ -- config+ type Image NginxGateway = "lscr.io/linuxserver/nginx"+ type Name NginxGateway = "nginx-test"+ uriOf = httpUri+ runArgs = []+ reset _ = pure ()+ ping = pingHttps+++instance ToRunCmd NginxGateway NginxPrep where+ toRunCmd = toRunCmd'+++instance Preparer NginxGateway NginxPrep where+ prepare = prepare'+ tidy = tidy'+++{- | Configures launch of a container thats uses nginx as a gateway (a.k.a+reverse proxy).+-}+data NginxGateway = NginxGateway+ { ngCommonName :: !Text+ , ngTargetPort :: !Int+ , ngTargetName :: !Text+ }+ deriving (Eq, Show)+++instance ToMustache NginxGateway where+ toMustache nt =+ object+ [ "commonName" ~> ngCommonName nt+ , "targetPort" ~> ngTargetPort nt+ , "targetName" ~> ngTargetName nt+ ]+++-- | Values obtained while preparing to launch the nginx container+data NginxPrep = NginxPrep+ { npUserID :: !UserID+ , npGroupID :: !GroupID+ , npVolumeRoot :: !FilePath+ }+ deriving (Eq, Show)+++instance ToMustache NginxPrep where+ toMustache np =+ object+ [ "targetDir" ~> npVolumeRoot np+ ]+++templateName :: FilePath+templateName = "nginx-test.conf.mustache"+++toConfCertsDirs :: FilePath -> (FilePath, FilePath)+toConfCertsDirs topDir = (topDir </> "conf", topDir </> "certs")+++dockerCertsDir :: FilePath+dockerCertsDir = "/etc/tmp-proc/certs"+++dockerConf :: FilePath+dockerConf = "/data/conf/nginx.conf"+++createWorkingDirs :: IO FilePath+createWorkingDirs = do+ tmpDir <- getCanonicalTemporaryDirectory+ topDir <- createTempDirectory tmpDir "nginx-test"+ let (confDir, certsDir) = toConfCertsDirs topDir+ createDirectory confDir+ createDirectory certsDir+ pure topDir+++tidy' :: NginxGateway -> NginxPrep -> IO ()+tidy' _ np = removeDirectoryRecursive $ npVolumeRoot np+++toRunCmd' :: NginxGateway -> NginxPrep -> [Text]+toRunCmd' _ np =+ -- specify user ID and group ID to fix volume mount permissions+ -- mount volume /etc/tmp-proc/certs as target-dir/certs+ -- mount volume /etc/tmp-proc/nginx as target-dir/nginx+ let (confDir, certsDir) = toConfCertsDirs $ npVolumeRoot np+ confPath = confDir </> "nginx.conf"+ envArg name v =+ [ "-e"+ , name ++ "=" ++ show v+ ]+ volumeArg actualPath hostedPath =+ [ "-v"+ , actualPath ++ ":" ++ hostedPath+ ]+ confArg = volumeArg confPath $ dockerConf ++ ":ro"+ certsArg = volumeArg certsDir dockerCertsDir+ puidArg = envArg "PUID" $ npUserID np+ guidArg = envArg "GUID" $ npGroupID np+ in Text.pack <$> confArg ++ certsArg ++ puidArg ++ guidArg+++-- Prepare+-- expand the template with commonName to target-dir/nginx+-- create certs with commonName to target-dir/certs+-- used fixed cert basenames (certificate.pem and key.pem)+prepare' :: [SlimHandle] -> NginxGateway -> IO NginxPrep+prepare' views nt@NginxGateway {ngTargetName = name} = do+ case find ((== name) . shName) views of+ Nothing -> error $ "could not find host " <> show name+ Just _ -> do+ templateDir <- (</> "templates") <$> getDataDir+ compiled <- automaticCompile [templateDir] templateName+ case compiled of+ Left err -> error $ "the template did not compile:" ++ show err+ Right template -> do+ npVolumeRoot <- createWorkingDirs+ npUserID <- getEffectiveUserID+ npGroupID <- getEffectiveGroupID+ let (confDir, cpDir) = toConfCertsDirs npVolumeRoot+ cp =+ CertPaths+ { cpKey = "key.pem"+ , cpCert = "certificate.pem"+ , cpDir+ }+ np = NginxPrep {npUserID, npGroupID, npVolumeRoot}+ generateAndStore cp defaultConfig+ Text.writeFile (confDir </> "nginx.conf") $ substitute template (nt, np)+ pure np+++-- | Make a uri access the http-bin server.+httpUri :: HostIpAddress -> SvcURI+httpUri ip = "http://" <> C8.pack (Text.unpack ip) <> "/"+++pingHttps :: ProcHandle a -> IO Pinged+pingHttps handle = toPinged @HC.HttpException Proxy $ do+ gotStatus <- httpsGet handle "/status/200"+ if gotStatus == 200 then pure OK else pure NotOK+++-- use TLS settings that disable hostname verification. What's not+-- currently possible is to actually specify the hostname to use for SNI+-- that differs from the connection IP address, that's not supported by+-- http-client-tls+mkBadTlsManager :: IO HC.Manager+mkBadTlsManager =+ HC.newTlsManagerWith $ HC.mkManagerSettings noCheckSettings Nothing+++-- | Determine the status from a secure Get to host localhost.+httpsGet :: ProcHandle a -> Text -> IO Int+httpsGet handle urlPath = do+ let theUri = "https://" <> hAddr handle <> "/" <> Text.dropWhile (== '/') urlPath+ manager <- mkBadTlsManager+ getReq <- HC.parseRequest $ Text.unpack theUri+ let withHost = getReq {HC.requestHeaders = [(hHost, "localhost")]}+ statusCode . HC.responseStatus <$> HC.httpLbs withHost manager
test/Test/TestRequests.hs view
@@ -9,12 +9,13 @@ , testPostRequests , testNotProxiedRequests , testOverRedirectedRequests+ , nil+ , secure ) where import qualified Data.ByteString as BS import qualified Data.ByteString.Char8 as C8-import Data.Default (Default (..)) import Data.Maybe (fromMaybe) import Network.HTTP.Client ( Request@@ -38,18 +39,22 @@ } -instance Default RequestBuilder where- def =- RequestBuilder- { rbMethod = methodGet- , rbSecure = False- , rbPath = "/"- , rbBody = Nothing- , rbHost = "httpbin.org"- , rbPort = Nothing- }+nil :: RequestBuilder+nil =+ RequestBuilder+ { rbMethod = methodGet+ , rbSecure = False+ , rbPath = "/"+ , rbBody = Nothing+ , rbHost = "httpbin.org"+ , rbPort = Nothing+ } +secure :: RequestBuilder -> RequestBuilder+secure x = x {rbSecure = True}++ testRequests :: [(String, RequestBuilder -> RequestBuilder)] testRequests = testGetRequests <> testPostRequests @@ -58,32 +63,31 @@ testGetRequests = [ ( "GET"- , (\builder -> builder {rbPath = "/get"})+ , \builder -> builder {rbPath = "/get"} ) , ( "GET (with a query)"- , (\builder -> builder {rbPath = "/get?a=10&b=whatever"})+ , \builder -> builder {rbPath = "/get?a=10&b=whatever"} ) , ( "GET (multiple redirects)"- , (\builder -> builder {rbPath = "/redirect/3"})+ , \builder -> builder {rbPath = "/redirect/3"} ) , ( "GET (with a body)"- , ( \builder ->- builder- { rbPath = "/get"- , rbBody = Just $ RequestBodyBS "Hello httpbin!"- }- )+ , \builder ->+ builder+ { rbPath = "/get"+ , rbBody = Just $ RequestBodyBS "Hello httpbin!"+ } ) , ( "GET (forbidden resource)"- , (\builder -> builder {rbPath = "/status/403"})+ , \builder -> builder {rbPath = "/status/403"} ) , ( "GET (missing resource)"- , (\builder -> builder {rbPath = "/status/404"})+ , \builder -> builder {rbPath = "/status/404"} ) ] @@ -92,7 +96,7 @@ testNotProxiedRequests = [ ( "GET (funny resource - differs on proxy)"- , (\builder -> builder {rbPath = "/status/418"})+ , \builder -> builder {rbPath = "/status/418"} ) ] @@ -101,7 +105,7 @@ testOverRedirectedRequests = [ ( "GET (multiple redirects)"- , (\builder -> builder {rbPath = "/redirect/3"})+ , \builder -> builder {rbPath = "/redirect/3"} ) ] @@ -110,29 +114,28 @@ testPostRequests = [ ( "POST"- , (\builder -> builder {rbMethod = methodPost, rbPath = "/post"})+ , \builder -> builder {rbMethod = methodPost, rbPath = "/post"} ) , ( "POST (with a query)"- , (\builder -> builder {rbMethod = methodPost, rbPath = "/post?a=10&b=whatever"})+ , \builder -> builder {rbMethod = methodPost, rbPath = "/post?a=10&b=whatever"} ) , ( "POST (with a body)"- , ( \builder ->- builder- { rbPath = "/post"- , rbBody = Just $ RequestBodyBS "Hello httpbin!"- , rbMethod = methodPost- }- )+ , \builder ->+ builder+ { rbPath = "/post"+ , rbBody = Just $ RequestBodyBS "Hello httpbin!"+ , rbMethod = methodPost+ } ) , ( "POST (forbidden resource)"- , (\builder -> builder {rbMethod = methodPost, rbPath = "/status/403"})+ , \builder -> builder {rbMethod = methodPost, rbPath = "/status/403"} ) , ( "POST (missing resource)"- , (\builder -> builder {rbMethod = methodPost, rbPath = "/status/404"})+ , \builder -> builder {rbMethod = methodPost, rbPath = "/status/404"} ) ] @@ -144,7 +147,7 @@ | rbSecure = "https" | otherwise = "http" portStr = maybe "" (\x -> ":" ++ show x) rbPort- url = scheme ++ "://" ++ (C8.unpack rbHost) ++ portStr ++ rbPath+ url = scheme ++ "://" ++ C8.unpack rbHost ++ portStr ++ rbPath req <- parseRequest url return $ req
test/Test/WithExtras.hs view
@@ -1,6 +1,5 @@ module Test.WithExtras- ( defaultTlsSettings- , testWithTLSApplication+ ( testWithTLSApplication , testWithTLSApplicationSettings , withTLSApplication , withTLSApplicationSettings@@ -22,34 +21,31 @@ ) import Network.Wai.Handler.Warp.Internal (settingsBeforeMainLoop) import Network.Wai.Handler.WarpTLS- ( TLSSettings- , runTLSSocket+ ( runTLSSocket , tlsSettings )-import Paths_wai_middleware_delegate (getDataFileName)+import Test.Certs.Temp (certificatePath, keyPath, withCertPathsInTmp') --- | The settings used in the integration tests-defaultTlsSettings :: IO TLSSettings-defaultTlsSettings =- tlsSettings- <$> (getDataFileName "test/certificate.pem")- <*> (getDataFileName "test/key.pem")+runTLSSocket' :: Settings -> Socket -> Application -> IO ()+runTLSSocket' settings sock app = withCertPathsInTmp' $ \cp -> do+ let tls = tlsSettings (certificatePath cp) (keyPath cp)+ runTLSSocket tls settings sock app {- | Runs the given 'Application' on a free port. Passes the port to the given operation and executes it, while the 'Application' is running. Shuts down the server before returning. -}-withTLSApplication :: TLSSettings -> IO Application -> (Port -> IO a) -> IO a+withTLSApplication :: IO Application -> (Port -> IO a) -> IO a withTLSApplication = withTLSApplicationSettings defaultSettings {- | 'withTLSApplication' with given 'Settings'. This will ignore the port value set by 'setPort' in 'Settings'. -}-withTLSApplicationSettings :: Settings -> TLSSettings -> IO Application -> (Port -> IO a) -> IO a-withTLSApplicationSettings settings' tlsSettings' mkApp action = do+withTLSApplicationSettings :: Settings -> IO Application -> (Port -> IO a) -> IO a+withTLSApplicationSettings settings' mkApp action = do app <- mkApp withFreePort $ \(port, sock) -> do started <- mkWaiter@@ -60,19 +56,19 @@ } result <- race- (runTLSSocket tlsSettings' settings sock app)+ (runTLSSocket' settings sock app) (waitFor started >> action port) case result of Left () -> throwIO $ ErrorCall "Unexpected: runSettingsSocket exited" Right x -> return x -testWithTLSApplication :: TLSSettings -> IO Application -> (Port -> IO a) -> IO a+testWithTLSApplication :: IO Application -> (Port -> IO a) -> IO a testWithTLSApplication = testWithTLSApplicationSettings defaultSettings -testWithTLSApplicationSettings :: Settings -> TLSSettings -> IO Application -> (Port -> IO a) -> IO a-testWithTLSApplicationSettings settings tlsSettings' mkApp action = do+testWithTLSApplicationSettings :: Settings -> IO Application -> (Port -> IO a) -> IO a+testWithTLSApplicationSettings settings mkApp action = do callingThread <- myThreadId app <- mkApp let wrappedApp request respond =@@ -81,7 +77,7 @@ (defaultShouldDisplayException e) (throwTo callingThread e) throwIO e- withTLSApplicationSettings settings tlsSettings' (return wrappedApp) action+ withTLSApplicationSettings settings (return wrappedApp) action data Waiter a = Waiter
− test/certificate.csr
@@ -1,27 +0,0 @@------BEGIN CERTIFICATE REQUEST------MIIElTCCAn0CAQAwUDELMAkGA1UEBhMCSlAxDDAKBgNVBAgTA0ZVSzERMA8GA1UE-BxMISXRvc2hpbWExDDAKBgNVBAoTA0RpczESMBAGA1UEAxMJbG9jYWxob3N0MIIC-IjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAyVm9wmE7aMKsQB+ZEhH/p8F+-hiYLSZNLmvCF+oHmCQ5/swiK/M4jY4XFsPXSqow4RND2bHw/YW2ZYK5EDj8VYiyZ-MVRm0oNTzbBH6BNkQhGsqS+5h/8rtFmijatD7E4XQ2n9PW/anGH8dC9TXPm9Z3h9-mjbmw/eG/LcVxl3WZho/ryMWBtFVB27MpWVhBVI0n0AkhkWLntlJKMAj6HJqOwDy-A9tCf2tauAxCFTXslE4zUrKRMRADP2xO+Gu3A1upzTWO9jpLzbr6z7efOrEJda4O-0N7f7tUmOgOvNEOiAKkJDjtOECZ8IATGkkdQb+6a72P5/dcg667IZhMpAJC6bJs9-c6Qkn0bm+kMa336dGhLIFw6WcZSrAsjGCva2XRQZ9TSQIAOmMe9eqah8TxSrffuc-s6+xdJ1kpsa5j8aezBQ7Pm0dXVWQms5KmsgXW5GF3cVW9PFooSOdeMvGhDH5Igt9-YPX3ai/xiOSJw9Hm/NtUK+P1B4O7vTGkmKYszkjm9oYsan50WTvhAYMmg2l1V5d9-LgZTo+o1KOnpLB89xXcYry+WXtDKIXqW/f2tzEl4QmXzNgyMrTFGkSLyX09EmOLY-Wp0qqTSjAYh761/o8iP4T72OHSlE1vExLwQkrze+RnrqEbj1LU4MeX94nwcUvaWZ-amhbwI36QePCcebhGdcCAwEAAaAAMA0GCSqGSIb3DQEBBQUAA4ICAQBVayoYDWsg-oA3x0OaVdfz44El8kc2sTsB96C28vVRVotjkuUqmgakJatpCwFz4pS5HUO9ykJb9-SqopqNF13dX562HOiuP0CtX1SfqlSO6jQ8lQ03neHe7m4rYUyOKqIiEtHNZHBCyR-2GHbGqBxW/7VOk0iF6UWNHUhsQMG+muabIgyJECsXxoWJU6nO34dnjBhiNtsAX5n-84glwKq2eTF+Z3IyoI8UVRBKPcxO6Y8G66zvPo7Sc2LX2lhtRM9crC628a7U7WkF-kK6w1vAANbgh0gBHcqPN1fhqIG7VWdeZKRrt3S8d01wrP6o5GXvtGeIj7SUCUOr2-RzL3nOSNFLDyWZlKgGZsOjZqz3W846Zma6maNWiQAxyzA/w4lP97S5NL0ZgUQ88P-US12y4o3ZJoo5j4SwHSDeGx+jI7ZPttXPxGyaeFQhqPf5KEUPqUIOKy10MCMNvE1-3u/iu/nY+PtlKQ5pW0QWkLS92CQ9Gthhj6bL/iWG+o8+j31f1JuBkArKIX/ORNYZ-/i+Xw4a1ZuUuix79smW0ZM9hxHJvyDeO8duvQKBSYnW5ufieRj7XCCbBl/2UabYZ-nabKHAu4S0qCgG1XSSP+blpFYGV6RMCEqe7xeBpXy3Mg2dP9Trx5K8rxvPeZm5AW-cRqVssVWMITl9jBzDHoeRlPO/zmgqND/nw==------END CERTIFICATE REQUEST-----
− test/certificate.pem
@@ -1,30 +0,0 @@------BEGIN CERTIFICATE------MIIFHDCCAwQCCQCskmvu2cKsdTANBgkqhkiG9w0BAQUFADBQMQswCQYDVQQGEwJK-UDEMMAoGA1UECBMDRlVLMREwDwYDVQQHEwhJdG9zaGltYTEMMAoGA1UEChMDRGlz-MRIwEAYDVQQDEwlsb2NhbGhvc3QwHhcNMTgwODAzMDM1NjA4WhcNMTgwOTAyMDM1-NjA4WjBQMQswCQYDVQQGEwJKUDEMMAoGA1UECBMDRlVLMREwDwYDVQQHEwhJdG9z-aGltYTEMMAoGA1UEChMDRGlzMRIwEAYDVQQDEwlsb2NhbGhvc3QwggIiMA0GCSqG-SIb3DQEBAQUAA4ICDwAwggIKAoICAQDJWb3CYTtowqxAH5kSEf+nwX6GJgtJk0ua-8IX6geYJDn+zCIr8ziNjhcWw9dKqjDhE0PZsfD9hbZlgrkQOPxViLJkxVGbSg1PN-sEfoE2RCEaypL7mH/yu0WaKNq0PsThdDaf09b9qcYfx0L1Nc+b1neH2aNubD94b8-txXGXdZmGj+vIxYG0VUHbsylZWEFUjSfQCSGRYue2UkowCPocmo7APID20J/a1q4-DEIVNeyUTjNSspExEAM/bE74a7cDW6nNNY72OkvNuvrPt586sQl1rg7Q3t/u1SY6-A680Q6IAqQkOO04QJnwgBMaSR1Bv7prvY/n91yDrrshmEykAkLpsmz1zpCSfRub6-Qxrffp0aEsgXDpZxlKsCyMYK9rZdFBn1NJAgA6Yx716pqHxPFKt9+5yzr7F0nWSm-xrmPxp7MFDs+bR1dVZCazkqayBdbkYXdxVb08WihI514y8aEMfkiC31g9fdqL/GI-5InD0eb821Qr4/UHg7u9MaSYpizOSOb2hixqfnRZO+EBgyaDaXVXl30uBlOj6jUo-6eksHz3FdxivL5Ze0Mohepb9/a3MSXhCZfM2DIytMUaRIvJfT0SY4thanSqpNKMB-iHvrX+jyI/hPvY4dKUTW8TEvBCSvN75GeuoRuPUtTgx5f3ifBxS9pZlqaFvAjfpB-48Jx5uEZ1wIDAQABMA0GCSqGSIb3DQEBBQUAA4ICAQAaPy1vsDxmDuJbBTg2rj2N-jyRoA6wAYLrX+uVu3uoWlQNuKLuv0VHDMjAa0K2pGpLT0txRCP/fsxeK7ygCG2T5-rvLr44W4tr7OBw9NBdjk9Z95VSbDHqGcW79Zzi2RBeQtnmwo0psROH6mq9dUOHD9-R4DLnNajg8Hd+jeoPXSo8QZtVi9I6XzjJIZ4BP1HuZ2ol4HUmZ87/kHRvPI0zKmM-DKNuzSpQ4/SXwI+onUMVSdc0KaVtaAexcQDZJQmjEgVFUiee5gkrHlMGV9JvwMUw-QW1E40Pz7MjitUhSBIPm9NXXorSAZJxRdXGaGkH1iYBObVvHLuc8FdoOB8VUF7fy-SkazzOczcLtBB82pyI8HIzOeinySZ3L6GQP40ZFcbC6WaCujEgl2h6G4kYrtcNar-Xz7NJOlqUl6l7hC1sDfT+8PpImFTb3gwLFHY0wVRY7iTdpSGBiM7kOs8RKcSeuwc-3ZOcO7+WpfREwnLLiYulxgBQbOCLXOy6F+6jEUYlsrkO5NmefIfhCUBIl9nhEMN1-30//qcre0uNtSN4b9DdfUnXqkH1RxvrUPQ5nZlVqs/zum6b/Mpns8dFkmXOOVaY4-advpJtviXpf9+acbA3s020bspNaFah4WcIxhOBv+q/be0EL9xvQ9r4V4Ee7oSMNE-t7YrGY8rPiubWMWRZw5UwA==------END CERTIFICATE-----
− test/key.pem
@@ -1,51 +0,0 @@------BEGIN RSA PRIVATE KEY------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------END RSA PRIVATE KEY-----
wai-middleware-delegate.cabal view
@@ -1,6 +1,6 @@ cabal-version: 3.0 name: wai-middleware-delegate-version: 0.1.4.1+version: 0.1.4.2 synopsis: WAI middleware that delegates handling of requests. description: [WAI](http://hackage.haskell.org/package/wai) middleware that intercepts requests@@ -23,8 +23,7 @@ build-type: Simple extra-source-files: ChangeLog.md data-files:- test/*.csr- test/*.pem+ templates/*.mustache source-repository head type: git@@ -35,21 +34,24 @@ hs-source-dirs: src build-depends: , async ^>=2.2.1- , base >=4.10 && <5+ , base >=4.10 && <5 , blaze-builder ^>=0.4.1.0- , bytestring >=0.10.8.2 && <0.11 || >=0.11.3.1 && <0.13.0+ , bytestring >=0.10.8.2 && <0.11 || >=0.11.3 && <0.13 , case-insensitive ^>=1.2.0.11 , conduit ^>=1.3.0.3 , conduit-extra ^>=1.3.0 , data-default ^>=0.7.1.1- , http-client >=0.5.13.1 && <0.8.0.0- , http-types >=0.12.1 && <0.13.0- , streaming-commons >=0.2.1.0 && <0.3.0.0- , text >=1.2.3 && <2.2+ , http-client >=0.5.13 && <0.8+ , http-client-tls >=0.3 && <0.4+ , http-types >=0.12.1 && <0.13+ , streaming-commons >=0.2.1 && <0.3+ , text >=1.2.3 && <2.1.2 , wai ^>=3.2 , wai-conduit ^>=3.0.0.4 default-language: Haskell2010+ ghc-options:+ -Wall -Wincomplete-uni-patterns -Wpartial-fields -fwarn-tabs test-suite integration-test type: exitcode-stdio-1.0@@ -59,8 +61,10 @@ Paths_wai_middleware_delegate Test.Fetch Test.HttpReply+ Test.NginxGateway Test.TestRequests Test.WithExtras+ Network.Connection.CPP hs-source-dirs: test build-depends:@@ -72,24 +76,31 @@ , case-insensitive , conduit , conduit-extra- , crypton-connection >=0.3.1+ , crypton-connection >=0.3.1 && < 0.5 , data-default+ , directory >=1.3 && <1.4+ , filepath >=1.4 && <1.6 , hspec >=2.1 , hspec-tmp-proc , http-client , http-client-tls , http-types+ , mustache >=2.3 && <2.5 , network , random >=1.1 , resourcet+ , temporary >=1.2 && <1.5+ , test-certs >=0.1 && <0.2 , text- , tmp-proc >=0.5.2+ , tmp-proc >=0.7 , vault , wai , wai-conduit , wai-middleware-delegate , warp , warp-tls >=3.4+ , unix >=2.7 && <2.9 default-language: Haskell2010- ghc-options: -Wall -fwarn-tabs -threaded+ ghc-options:+ -Wall -Wincomplete-uni-patterns -Wpartial-fields -fwarn-tabs -threaded