Skip to content

Commit 988c84f

Browse files
committed
The following:
— Bump ekg-forward. — Apply DDoS protection middleware to the timeseries server. — Read the timeseries query string from request body rather than request query param.
1 parent 40c1d48 commit 988c84f

5 files changed

Lines changed: 92 additions & 21 deletions

File tree

cabal.project

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ if impl (ghc >= 9.12)
8181
source-repository-package
8282
type: git
8383
location: https://github.com/input-output-hk/ekg-forward.git
84-
tag: d06caa8f53d945b9d252c9c7a719983767926140
85-
--sha256: sha256-sQj2B5a9OwgLAIVoyGxSFww7aJ81Vf0DVUkYJepA4yo=
84+
tag: c72c9a29045431df7484b665bed33c12ea71d0ac
85+
--sha256: sha256-b87qt8RMI4gNPF8QTrRjfS5KK2/JhbxUp5ijscn2Vf8=
8686
subdir:
8787
.

cardano-tracer/cardano-tracer.cabal

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,8 @@ library
202202
, trace-forward ^>= 2.4.0
203203
, trace-resources ^>= 0.2.4
204204
, wai ^>= 3.2
205+
, wai-extra
206+
, wai-rate-limit
205207
, warp ^>= 3.4
206208
, warp-tls
207209
, yaml

cardano-tracer/src/Cardano/Tracer/Acceptors/Client.hs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ import Data.Word (Word32)
4343
import qualified Network.Mux as Mux
4444
import qualified Network.Socket as Socket
4545
import qualified System.Metrics.Configuration as EKGF
46-
import System.Metrics.Network.Acceptor (acceptEKGMetricsInit)
46+
import System.Metrics.Network.Acceptor (acceptMetricsInit)
4747

4848
import qualified Trace.Forward.Configuration.DataPoint as DPF
4949
import qualified Trace.Forward.Configuration.TraceObject as TF
@@ -194,7 +194,7 @@ runEKGAcceptorInit
194194
respoinderCtx
195195
LBS.ByteString IO () Void
196196
runEKGAcceptorInit tracerEnv ekgConfig errorHandler =
197-
acceptEKGMetricsInit
197+
acceptMetricsInit
198198
ekgConfig
199199
(prepareMetricsStores tracerEnv . micConnectionId)
200200
(store tracerEnv . connIdToNodeId . micConnectionId)

cardano-tracer/src/Cardano/Tracer/Acceptors/Server.hs

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,6 @@ module Cardano.Tracer.Acceptors.Server
55
( runAcceptorsServer
66
) where
77

8-
import "contra-tracer" Control.Tracer (nullTracer)
9-
108
import Cardano.Logging (TraceObject)
119
import qualified Cardano.Logging.Types as Net
1210
import Cardano.Tracer.Acceptors.Utils
@@ -25,25 +23,25 @@ import Ouroboros.Network.Mux (MiniProtocol (..), MiniProtocolLimits (.
2523
miniProtocolNum, miniProtocolRun)
2624
import Ouroboros.Network.Protocol.Handshake (Handshake, HandshakeArguments (..))
2725
import qualified Ouroboros.Network.Protocol.Handshake as Handshake
26+
import qualified Ouroboros.Network.Server.Simple as Server
2827
import Ouroboros.Network.Snocket (LocalAddress, LocalSocket, Snocket,
2928
localAddressFromPath, localSnocket, makeLocalBearer, makeSocketBearer,
3029
socketSnocket)
31-
import Ouroboros.Network.Socket (ConnectionId (..),
32-
SomeResponderApplication (..))
33-
import qualified Ouroboros.Network.Server.Simple as Server
30+
import Ouroboros.Network.Socket (ConnectionId (..), SomeResponderApplication (..))
3431

3532
import Codec.CBOR.Term (Term)
3633
import Control.Concurrent.Async (wait)
34+
import "contra-tracer" Control.Tracer (nullTracer)
3735
import qualified Data.ByteString.Lazy as LBS
36+
import Data.Functor (void)
3837
import Data.List.NonEmpty (NonEmpty ((:|)))
3938
import qualified Data.Text as Text
40-
import Data.Functor (void)
4139
import Data.Void (Void)
4240
import Data.Word (Word32)
4341
import qualified Network.Mux as Mux
4442
import qualified Network.Socket as Socket
4543
import qualified System.Metrics.Configuration as EKGF
46-
import System.Metrics.Network.Acceptor (acceptEKGMetricsResp)
44+
import System.Metrics.Network.Acceptor (acceptMetricsResp)
4745

4846
import qualified Trace.Forward.Configuration.DataPoint as DPF
4947
import qualified Trace.Forward.Configuration.TraceObject as TF
@@ -180,7 +178,7 @@ runEKGAcceptor
180178
-> (ConnectionId addr -> IO ())
181179
-> RunMiniProtocol 'Mux.ResponderMode initiatorCtx (ResponderContext addr) LBS.ByteString IO Void ()
182180
runEKGAcceptor tracerEnv ekgConfig errorHandler =
183-
acceptEKGMetricsResp
181+
acceptMetricsResp
184182
ekgConfig
185183
(prepareMetricsStores tracerEnv . rcConnectionId)
186184
(store tracerEnv . connIdToNodeId . rcConnectionId)

cardano-tracer/src/Cardano/Tracer/Handlers/Metrics/TimeseriesServer.hs

Lines changed: 80 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{-# LANGUAGE LambdaCase #-}
2-
{-# LANGUAGE NamedFieldPuns #-}
2+
{-# LANGUAGE NumericUnderscores #-}
33
{-# LANGUAGE OverloadedRecordDot #-}
44
{-# LANGUAGE OverloadedStrings #-}
55
{-# LANGUAGE RecordWildCards #-}
@@ -15,27 +15,79 @@ import Cardano.Tracer.Handlers.Metrics.Utils (contentHdrUtf8Text)
1515
import Cardano.Tracer.MetaTrace
1616
import Cardano.Tracer.Timeseries
1717

18+
import Control.Concurrent (threadDelay)
19+
import Control.Concurrent.Extra (forkIO)
20+
import Control.Concurrent.STM (atomically)
21+
import Control.Concurrent.STM.TVar (modifyTVar', newTVarIO, readTVar, readTVarIO,
22+
writeTVar)
1823
import Control.Monad (guard)
1924
import qualified Data.ByteString.Lazy as BL
25+
import Data.Functor (void)
2026
import Data.Maybe (fromMaybe)
21-
import Data.Text (Text)
27+
import Data.Text.Encoding (decodeUtf8Lenient)
2228
import qualified Data.Text.Encoding as T
2329
import Network.HTTP.Types
2430
import Network.Wai
2531
import Network.Wai.Handler.Warp hiding (run)
2632
import Network.Wai.Handler.WarpTLS
33+
import Network.Wai.Middleware.RequestSizeLimit (defaultRequestSizeLimitSettings,
34+
requestSizeLimitMiddleware, setMaxLengthForRequest)
35+
import Network.Wai.Middleware.Timeout (timeout)
36+
import Network.Wai.RateLimit
37+
import Network.Wai.RateLimit.Backend (Backend (..))
38+
import Network.Wai.RateLimit.Strategy
2739
import System.Time.Extra (sleep)
2840

29-
parseTimeseriesQuery :: Request -> Maybe Text
41+
-- COMMENT: (@russoul) make the options below configurable?
42+
-- COMMENT: (@russoul) move the rate limiter backend to its own module?
43+
-- COMMENT: (@russoul) make the limiters applicable to every server we have?
44+
45+
-- | Maximum request body size (KB).
46+
requestBodySizeLimiterKB :: Word
47+
requestBodySizeLimiterKB = 2 * 1024
48+
49+
-- | Fixed time window of the request limiter (sec).
50+
requestRateLimiterWindowSec :: Word
51+
requestRateLimiterWindowSec = 60
52+
53+
-- | Maximum number of requests in every window.
54+
requestRateLimiterLimitSec :: Word
55+
requestRateLimiterLimitSec = 30
56+
57+
-- | Maximum duration of response generation per every request (sec).
58+
responseTimeoutSec :: Word
59+
responseTimeoutSec = 5
60+
61+
-- | Simple request rate limiter backend that limits the rate of
62+
-- requests based on the total number of requests.
63+
totalRequestRateLimiterBackend :: IO (Backend ())
64+
totalRequestRateLimiterBackend = do
65+
usage <- newTVarIO (0 :: Integer)
66+
67+
let
68+
backendGetUsage :: () -> IO Integer
69+
backendGetUsage _ = readTVarIO usage
70+
71+
backendIncAndGetUsage :: () -> Integer -> IO Integer
72+
backendIncAndGetUsage _ k = atomically $ modifyTVar' usage (+ k) >> readTVar usage
73+
74+
backendExpireIn :: () -> Integer -> IO ()
75+
backendExpireIn _ s = void $ forkIO $ do
76+
threadDelay (fromIntegral (s * 1_000_000))
77+
atomically $ writeTVar usage 0
78+
79+
pure $ MkBackend backendGetUsage backendIncAndGetUsage backendExpireIn
80+
81+
-- | GET timeseries/query
82+
parseTimeseriesQuery :: Request -> Maybe ()
3083
parseTimeseriesQuery request = do
3184
guard (request.pathInfo == ["timeseries", "query"])
32-
case queryToQueryText request.queryString of
33-
[("query", Just str)] -> pure str
34-
_ -> Nothing
85+
guard (request.requestMethod == methodPost)
3586

36-
-- | timeseries/query?query=...
3787
timeseriesApp :: TimeseriesHandle -> Application
38-
timeseriesApp handle (parseTimeseriesQuery -> Just query) send = do
88+
timeseriesApp handle request@(parseTimeseriesQuery -> Just ()) send = do
89+
bs <- consumeRequestBodyStrict request
90+
let query = decodeUtf8Lenient (BL.toStrict bs)
3991
now <- getTimeMs
4092
execute handle (fromIntegral now) query >>= \case
4193
Left err -> send $
@@ -58,6 +110,8 @@ runTimeseriesServer tr tracerConfig endpoint handle = do
58110
{ ttTimeseriesEndpoint = endpoint
59111
}
60112

113+
requestRateLimiterBackend <- totalRequestRateLimiterBackend
114+
61115
let
62116
settings :: Settings
63117
settings = setEndpoint endpoint defaultSettings
@@ -67,7 +121,24 @@ runTimeseriesServer tr tracerConfig endpoint handle = do
67121
tlsSettingsChain certificateFile (fromMaybe [] certificateChain) certificateKeyFile
68122

69123
application :: Application
70-
application = timeseriesApp handle
124+
application =
125+
-- request body size limiter
126+
requestSizeLimitMiddleware
127+
(setMaxLengthForRequest (const (pure (Just (fromIntegral requestBodySizeLimiterKB * 1024))))
128+
defaultRequestSizeLimitSettings)
129+
.
130+
-- request rate limiter (fixed window)
131+
rateLimiting (fixedWindow requestRateLimiterBackend
132+
(fromIntegral requestRateLimiterWindowSec)
133+
(fromIntegral requestRateLimiterLimitSec)
134+
(const (pure ()))
135+
)
136+
.
137+
-- response time limiter
138+
timeout (fromIntegral responseTimeoutSec)
139+
$
140+
timeseriesApp handle
141+
71142

72143
run :: IO ()
73144
run | Just True <- epForceSSL endpoint , Just cert <- tlsCertificate tracerConfig

0 commit comments

Comments
 (0)