packages feed

hprox 0.6.0 → 0.6.1

raw patch · 5 files changed

+107/−56 lines, 5 filesdep +data-default-classdep +textdep +unixdep ~http-reverse-proxydep ~quicPVP: major bump suggested

API removals or changes: PVP suggests a major version bump

Dependencies added: data-default-class, text, unix

Dependency ranges changed: http-reverse-proxy, quic

API changes (from Hackage documentation)

+ Network.HProx: [_acme] :: Config -> Maybe ByteString
- Network.HProx: Config :: Maybe String -> Int -> [(String, CertFile)] -> Maybe FilePath -> Maybe String -> [(Maybe ByteString, ByteString, ByteString)] -> Maybe String -> Bool -> Bool -> ByteString -> String -> LogLevel -> Config
+ Network.HProx: Config :: Maybe String -> Int -> [(String, CertFile)] -> Maybe FilePath -> Maybe ByteString -> [(Maybe ByteString, ByteString, ByteString)] -> Maybe String -> Bool -> Bool -> ByteString -> Maybe ByteString -> String -> LogLevel -> Config
- Network.HProx: [_ws] :: Config -> Maybe String
+ Network.HProx: [_ws] :: Config -> Maybe ByteString

Files

Changelog.md view
@@ -1,7 +1,13 @@+## 0.6.1++- multiple certificates and SNI support for HTTP/3+- install signal handler with graceful shutdown on Linux and macOS+- support ACME `http-01` challenge (RFC8555)+ ## 0.6.0  - `--rev` now supports domain matching-- fix `Content-Length` header in HTTP/2 responses.+- fix `Content-Length` header in HTTP/2 responses - passwords are now Argon2 salt-hashed  ## 0.5.4
README.md view
@@ -12,60 +12,61 @@ ### Features  * Basic HTTP proxy functionality.-* Simple password authentication.-* TLS encryption (requires a valid certificate). Supports TLS 1.3 and HTTP/2, also known as SPDY Proxy.-* TLS SNI validation (blocks all clients with invalid domain name).-* Provide PAC file for easy client side configuration (supports Chrome and Firefox).+* [Basic](https://en.wikipedia.org/wiki/Basic_access_authentication) password authentication.+* Enables TLS encryption, requiring a valid certificate. Supports TLS 1.3 and HTTP/2, also known as SPDY Proxy.+* TLS SNI validation (blocks all clients with an invalid domain name).+* Provides a PAC file for seamless client-side configuration, compatible with browsers like Chrome and Firefox. * Websocket redirection (compatible with [v2ray-plugin](https://github.com/shadowsocks/v2ray-plugin)).-* Reverse proxy support (redirect requests to a fallback server).+* Reverse proxy support (redirects requests to a fallback server). * DNS-over-HTTPS (DoH) support. * [naiveproxy](https://github.com/klzgrad/naiveproxy) compatible [padding](https://github.com/klzgrad/naiveproxy/#padding-protocol-an-informal-specification) (HTTP Connect proxy). * HTTP/3 (QUIC) support (`h3` protocol).-* Implemented as a middleware, compatible with any Haskell Web Application built with `wai` interface.-  See [library documents](https://hackage.haskell.org/package/hprox) for details.+* ACME `http-01` challenge as specified by RFC8555, see [acme.sh stateless mode](https://github.com/acmesh-official/acme.sh/wiki/Stateless-Mode).+* Designed as a middleware, ensuring compatibility with any Haskell Web Application built using the `wai` interface.+  Refer to [library documents](https://hackage.haskell.org/package/hprox) for details.  ### Installation -`hprox` should build and work on all unix-like OS with `ghc` support, as well as Windows.+`hprox` is designed to build and function seamlessly on all Unix-like operating systems with GHC support, as well as on Windows environments. -[stack](https://docs.haskellstack.org/en/stable/README/#how-to-install) is recommended to build `hprox`.+[stack](https://docs.haskellstack.org/en/stable/README/#how-to-install) is recommended for building `hprox`.  ```sh stack setup stack install ``` -Alternatively, you also can use the statically linked binary for the [latest release](https://github.com/bjin/hprox/releases).+Alternatively, you have the option to utilize the statically linked binary available in the [latest release](https://github.com/bjin/hprox/releases).  ### Usage -Use `hprox --help` to list options with detailed explanation.+Utilize `hprox --help` to view a comprehensive list of options along with detailed explanations. -* To run `hprox` on port 8080, with simple password authentication:+* To run `hprox` on port 8080 with simple password authentication (passwords will be [Argon2-hashed](https://en.wikipedia.org/wiki/Argon2) after first run):  ```sh echo "user:pass" > userpass.txt hprox -p 8080 -a userpass.txt ``` -* To run `hprox` with TLS encryption on port 443, with certificate of `example.com` obtained with [acme.sh](https://acme.sh/):+* To run `hprox` with TLS encryption on port 443, with a certificate for `example.com` obtained with [acme.sh](https://acme.sh/):  ```sh hprox -p 443 -s example.com:$HOME/.acme.sh/example.com/fullchain.cer:$HOME/.acme.sh/example.com/example.com.key ``` -Browsers can be configured with PAC file URL `https://example.com/.hprox/config.pac`.+Browsers can be configured with the PAC file URL `https://example.com/.hprox/config.pac`. -* To work with `v2ray-plugin`, with fallback page to [ubuntu archive](http://archive.ubuntu.com/):+* For integration with `v2ray-plugin` and a fallback page to the [Ubuntu archive](http://archive.ubuntu.com/):  ```sh v2ray-plugin -server -localPort 8080 -mode websocket -host example.com -remotePort xxxx hprox -p 443 -s example.com:fullchain.pem:privkey.pem --ws 127.0.0.1:8080 --rev archive.ubuntu.com:80 ``` -Clients will be able to connect with plugin option `tls;host=example.com`.+Clients can establish connections using the plugin option `tls;host=example.com`. -* Enable HTTP/3 (QUIC) on UDP port 8443, enable DoH support (redirect to 8.8.8.8), and add `naiveproxy` compatible padding:+* Enable HTTP/3 (QUIC) on UDP port 8443, enable DoH support (redirect to 8.8.8.8), and add naiveproxy compatible padding:  ```sh hprox -p 443 -q 8443 -s example.com:fullchain.pem:privkey.pem -a userpass.txt --naive --doh 8.8.8.8@@ -73,11 +74,6 @@  Then DoH can be accessed at `https://example.com/dns-query`. -### Known Issue--* HTTP/3 currently only works on the first domain as specified by `-s/--tls`, also SNI-  validation is unavailable as well.- ### License -`hprox` is licensed under the Apache license. See LICENSE file for details.+`hprox` is licensed under the Apache license. Refer to the `LICENSE` file for comprehensive details.
hprox.cabal view
@@ -5,7 +5,7 @@ -- see: https://github.com/sol/hpack  name:           hprox-version:        0.6.0+version:        0.6.1 synopsis:       a lightweight HTTP proxy server, and more description:    Please see the README on GitHub at <https://github.com/bjin/hprox#readme> category:       Web@@ -62,15 +62,17 @@     , conduit >=1.3     , conduit-extra >=1.3     , crypton+    , data-default-class     , dns >=4.0     , fast-logger >=3.0     , http-client >=0.5     , http-client-tls >=0.3.4-    , http-reverse-proxy >=0.4.0+    , http-reverse-proxy >=0.6.0.2     , http-types >=0.12     , http2 >=4.0     , optparse-applicative >=0.14     , random >=1.2.1+    , text     , tls >=1.5     , tls-session-manager >=0.0.4     , unordered-containers@@ -83,8 +85,12 @@     cpp-options: -DQUIC_ENABLED     build-depends:         http3 >=0.0.3-      , quic >=0.1.1+      , quic >=0.1.15       , warp-quic+  if !os(windows)+    cpp-options: -DOS_UNIX+    build-depends:+        unix  executable hprox   main-is: Main.hs@@ -107,16 +113,18 @@     , conduit >=1.3     , conduit-extra >=1.3     , crypton+    , data-default-class     , dns >=4.0     , fast-logger >=3.0     , hprox     , http-client >=0.5     , http-client-tls >=0.3.4-    , http-reverse-proxy >=0.4.0+    , http-reverse-proxy >=0.6.0.2     , http-types >=0.12     , http2 >=4.0     , optparse-applicative >=0.14     , random >=1.2.1+    , text     , tls >=1.5     , tls-session-manager >=0.0.4     , unordered-containers@@ -129,7 +137,11 @@     cpp-options: -DQUIC_ENABLED     build-depends:         http3 >=0.0.3-      , quic >=0.1.1+      , quic >=0.1.15       , warp-quic+  if !os(windows)+    cpp-options: -DOS_UNIX+    build-depends:+        unix   if flag(static)     ghc-options: -optl-static
src/Network/HProx.hs view
@@ -19,7 +19,10 @@   ) where  import Data.ByteString.Char8       qualified as BS8+import Data.Default.Class          (def) import Data.HashMap.Strict         qualified as HM+import Data.List+    (elemIndex, elemIndices, find, isSuffixOf, sortOn, (\\)) import Data.Ord                    (Down (..)) import Data.String                 (fromString) import Data.Version                (showVersion)@@ -34,8 +37,9 @@     runSettings, setHost, setLogger, setNoParsePath, setOnException, setPort,     setServerName) import Network.Wai.Handler.WarpTLS-    (OnInsecure (..), WarpTLSException, onInsecure, runTLS, tlsAllowedVersions,-    tlsCiphers, tlsServerHooks, tlsSessionManager, tlsSettings)+    (OnInsecure (..), WarpTLSException, defaultTlsSettings, onInsecure, runTLS,+    tlsAllowedVersions, tlsCiphers, tlsCredentials, tlsServerHooks,+    tlsSessionManager)  import Control.Exception    (Exception (..)) import GHC.IO.Exception     (IOErrorType (..))@@ -50,8 +54,13 @@ import Network.Wai.Handler.WarpQUIC (runQUIC) #endif +#ifdef OS_UNIX+import Network.Wai.Handler.Warp+    (setGracefulShutdownTimeout, setInstallShutdownHandler)+import System.Posix.Signals+#endif+ import Control.Monad-import Data.List import Data.Maybe import Options.Applicative @@ -67,12 +76,13 @@   , _port     :: Int   , _ssl      :: [(String, CertFile)]   , _auth     :: Maybe FilePath-  , _ws       :: Maybe String+  , _ws       :: Maybe BS8.ByteString   , _rev      :: [(Maybe BS8.ByteString, BS8.ByteString, BS8.ByteString)]   , _doh      :: Maybe String   , _hide     :: Bool   , _naive    :: Bool   , _name     :: BS8.ByteString+  , _acme     :: Maybe BS8.ByteString   , _log      :: String   , _loglevel :: LogLevel #ifdef QUIC_ENABLED@@ -82,7 +92,7 @@  -- | Default value of 'Config', same as running @hprox@ without arguments defaultConfig :: Config-defaultConfig = Config Nothing 3000 [] Nothing Nothing [] Nothing False False "hprox" "stdout" INFO+defaultConfig = Config Nothing 3000 [] Nothing Nothing [] Nothing False False "hprox" Nothing "stdout" INFO #ifdef QUIC_ENABLED     Nothing #endif@@ -130,6 +140,7 @@                     <*> hide                     <*> naive                     <*> name+                    <*> acme                     <*> logging                     <*> loglevel #ifdef QUIC_ENABLED@@ -192,6 +203,11 @@        <> showDefault        <> help "Specify the server name for the 'Server' header") +    acme = optional $ strOption+        ( long "acme"+       <> metavar "ACCOUNT_THUMBPRINT"+       <> help "Set the thumbprint for stateless http-01 ACME challenge as specified by RFC8555")+     logging = strOption         ( long "log"        <> metavar "<none|stdout|stderr|file>"@@ -237,21 +253,29 @@     smgr <- SM.newSessionManager SM.defaultConfig      let isSSL = not (null certfiles)-        (primaryHost, primaryCert) = head certfiles-        otherCerts = tail $ zip (map fst certfiles) certs+        allCerts = zip (map fst certfiles) certs      when isSSL $ do         logger INFO $ "read " <> toLogStr (show $ length certs) <> " certificates"-        logger INFO $ "primary domain: " <> toLogStr primaryHost-        logger INFO $ "other domains: " <> toLogStr (unwords $ map fst otherCerts)+        logger INFO $ "domains: " <> toLogStr (unwords $ map fst allCerts)      let settings = setHost (fromString (fromMaybe "*6" _bind)) $                    setPort _port $                    setLogger warpLogger $                    setOnException exceptionHandler $+#ifdef OS_UNIX+                   setGracefulShutdownTimeout (Just 3) $+                   setInstallShutdownHandler shutdownHandler $+#endif                    setNoParsePath True $                    setServerName _name defaultSettings +#ifdef OS_UNIX+        shutdownHandler closeSocket = do+            void $ installHandler sigTERM (CatchOnce $ logger INFO "Received SIGTERM signal, shutting down gracefully" >> closeSocket) Nothing+            void $ installHandler sigINT (CatchOnce $ logger INFO "Received SIGINT signal, shutting down gracefully" >> closeSocket) Nothing+#endif+         exceptionHandler req ex             | _loglevel > DEBUG                                 = return ()             | not (defaultShouldDisplayException ex)            = return ()@@ -273,9 +297,6 @@             | otherwise                           =                 logger TRACE $ "(" <> toLogStr (HT.statusCode status) <> ") " <> logRequest req -        tlsset' = tlsSettings (certfile primaryCert) (keyfile primaryCert)-        hooks = (tlsServerHooks tlsset') { TLS.onServerNameIndication = onSNI }-         -- https://www.ssllabs.com/ssltest         weak_ciphers = [ TLS.cipher_ECDHE_RSA_AES256CBC_SHA384                        , TLS.cipher_ECDHE_RSA_AES256CBC_SHA@@ -285,18 +306,17 @@                        , TLS.cipher_AES256_SHA1                        ] -        tlsset = tlsset'-            { tlsServerHooks     = hooks+        tlsset = defaultTlsSettings+            { tlsServerHooks     = def { TLS.onServerNameIndication = onSNI }+            , tlsCredentials     = Just (TLS.Credentials certs)             , onInsecure         = AllowInsecure             , tlsAllowedVersions = [TLS.TLS13, TLS.TLS12]             , tlsCiphers         = TLS.ciphersuite_strong \\ weak_ciphers             , tlsSessionManager  = Just smgr             } -        onSNI Nothing = fail "SNI: unspecified"-        onSNI (Just host)-          | checkSNI host primaryHost = return mempty-          | otherwise                 = lookupSNI host otherCerts+        onSNI Nothing     = fail "SNI: unspecified"+        onSNI (Just host) = lookupSNI host allCerts          lookupSNI host [] = fail ("SNI: unknown hostname (" ++ show host ++ ")")         lookupSNI host ((p, cert) : cs)@@ -314,9 +334,10 @@         quicset qport = Q.defaultServerConfig             { Q.scAddresses      = [(fromString (fromMaybe "0.0.0.0" _bind), fromIntegral qport)]             , Q.scVersions       = [Q.Version1, Q.Version2]-            , Q.scCredentials    = TLS.Credentials [head certs]+            , Q.scCredentials    = TLS.Credentials certs             , Q.scCiphers        = Q.scCiphers Q.defaultServerConfig \\ weak_ciphers             , Q.scALPN           = Just alpn+            , Q.scTlsHooks       = def { TLS.onServerNameIndication = onSNI }             , Q.scUse0RTT        = True             , Q.scSessionManager = smgr             }@@ -363,8 +384,9 @@     manager <- newTlsManager      let revSorted = sortOn (\(a,b,_) -> Down (isJust a, BS8.length b)) _rev-        pset = ProxySettings pauth (Just _name) (BS8.pack <$> _ws) revSorted _hide (_naive && isSSL) logger+        pset = ProxySettings pauth (Just _name) _ws revSorted _hide (_naive && isSSL) _acme logger         proxy = healthCheckProvider $+                acmeProvider pset $                 (if isSSL then forceSSL pset else id) $                 httpProxy pset manager $                 reverseProxy pset manager fallback
src/Network/HProx/Impl.hs view
@@ -4,6 +4,7 @@  module Network.HProx.Impl   ( ProxySettings (..)+  , acmeProvider   , forceSSL   , healthCheckProvider   , httpConnectProxy@@ -27,6 +28,7 @@ import Data.ByteString.Lazy.Char8 qualified as LBS8 import Data.CaseInsensitive       qualified as CI import Data.Conduit.Network       qualified as CN+import Data.Text.Encoding         qualified as TE import Network.HTTP.Client        qualified as HC import Network.HTTP.ReverseProxy     (ProxyDest (..), SetIpHeader (..), WaiProxyResponse (..),@@ -46,13 +48,14 @@ import Network.HProx.Util  data ProxySettings = ProxySettings-  { proxyAuth     :: Maybe (BS.ByteString -> Bool)-  , passPrompt    :: Maybe BS.ByteString-  , wsRemote      :: Maybe BS.ByteString-  , revRemoteMap  :: [(Maybe BS.ByteString, BS.ByteString, BS.ByteString)]-  , hideProxyAuth :: Bool-  , naivePadding  :: Bool-  , logger        :: Logger+  { proxyAuth      :: Maybe (BS.ByteString -> Bool)+  , passPrompt     :: Maybe BS.ByteString+  , wsRemote       :: Maybe BS.ByteString+  , revRemoteMap   :: [(Maybe BS.ByteString, BS.ByteString, BS.ByteString)]+  , hideProxyAuth  :: Bool+  , naivePadding   :: Bool+  , acmeThumbprint :: Maybe BS.ByteString+  , logger         :: Logger   }  logRequest :: Request -> LogStr@@ -122,6 +125,18 @@     ""   where     prompt = fromMaybe "hprox" passPrompt++acmeProvider :: ProxySettings -> Middleware+acmeProvider ProxySettings{..} app req respond+    | not (isSecure req)+    , Just thumbprint <- acmeThumbprint+    , [".well-known", "acme-challenge", token] <- pathInfo req+        = respond $ responseKnownLength+              HT.status200+              [("Content-Type", "text/plain")] $+              LBS.fromChunks [TE.encodeUtf8 token, ".", thumbprint]+    | otherwise+        = app req respond  pacProvider :: Middleware pacProvider fallback req respond