Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions cardano-tracer/cardano-tracer.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ library
Cardano.Tracer.Handlers.Logs.TraceObjects
Cardano.Tracer.Handlers.Logs.Utils

Cardano.Tracer.Handlers.Metrics.DDoSProtectionMiddleware
Cardano.Tracer.Handlers.Metrics.Monitoring
Cardano.Tracer.Handlers.Metrics.Prometheus
Cardano.Tracer.Handlers.Metrics.Servers
Expand Down Expand Up @@ -202,6 +203,8 @@ library
, trace-forward ^>= 2.4.0
, trace-resources ^>= 0.2.4
, wai ^>= 3.2
, wai-extra
, wai-rate-limit
, warp ^>= 3.4
, warp-tls
, yaml
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
{-# LANGUAGE NumericUnderscores #-}
{-# LANGUAGE OverloadedRecordDot #-}
module Cardano.Tracer.Handlers.Metrics.DDoSProtectionMiddleware(DDoSProtectionMiddlewareConfig(..), mkDDoSProtectionMiddleware) where
import Control.Concurrent (forkIO)
import Control.Concurrent.Extra (threadDelay)
import Control.Concurrent.STM (atomically, modifyTVar', newTVarIO, readTVar, readTVarIO,
writeTVar)
import Control.Monad (void)
import Network.Wai
import Network.Wai.Middleware.RequestSizeLimit
import Network.Wai.Middleware.Timeout
import Network.Wai.RateLimit
import Network.Wai.RateLimit.Backend (Backend (MkBackend))
import Network.Wai.RateLimit.Strategy

data DDoSProtectionMiddlewareConfig = DDoSProtectionMiddlewareConfig {
requestBodySizeLimitKB :: Word,
requestRateWindowSec :: Word,
requestRateLimitSec :: Word,
responseTimeLimitSec :: Word
}

-- | Simple request rate limiter backend that limits the rate of
-- requests based on the total number of requests.
totalRequestRateLimiterBackend :: IO (Backend ())
totalRequestRateLimiterBackend = do
usage <- newTVarIO (0 :: Integer)

let
backendGetUsage :: () -> IO Integer
backendGetUsage _ = readTVarIO usage

backendIncAndGetUsage :: () -> Integer -> IO Integer
backendIncAndGetUsage _ k = atomically $ modifyTVar' usage (+ k) >> readTVar usage

backendExpireIn :: () -> Integer -> IO ()
backendExpireIn _ s = void $ forkIO $ do
threadDelay (fromIntegral (s * 1_000_000))
atomically $ writeTVar usage 0

pure $ MkBackend backendGetUsage backendIncAndGetUsage backendExpireIn

mkDDoSProtectionMiddleware :: DDoSProtectionMiddlewareConfig -> IO Middleware
mkDDoSProtectionMiddleware cfg = totalRequestRateLimiterBackend >>= \backend ->
pure $
-- request body size limiter
requestSizeLimitMiddleware
(setMaxLengthForRequest (const (pure (Just (fromIntegral cfg.requestBodySizeLimitKB * 1024))))
defaultRequestSizeLimitSettings)
.
-- request rate limiter (fixed window)
rateLimiting (fixedWindow backend
(fromIntegral cfg.requestRateWindowSec)
(fromIntegral cfg.requestRateLimitSec)
(const (pure ()))
)
.
-- response time limiter
timeout (fromIntegral cfg.responseTimeLimitSec)
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE ScopedTypeVariables #-}

module Cardano.Tracer.Handlers.Metrics.Monitoring
Expand All @@ -9,6 +9,7 @@ module Cardano.Tracer.Handlers.Metrics.Monitoring

import Cardano.Tracer.Configuration
import Cardano.Tracer.Environment
import Cardano.Tracer.Handlers.Metrics.DDoSProtectionMiddleware
import Cardano.Tracer.Handlers.Metrics.Utils
import Cardano.Tracer.MetaTrace
import Cardano.Tracer.Types
Expand All @@ -22,11 +23,19 @@ import qualified Data.Text as T
import Network.HTTP.Types
import Network.Wai
import Network.Wai.Handler.Warp (Settings, defaultSettings, runSettings)
import Network.Wai.Handler.WarpTLS (runTLS, tlsSettingsChain, TLSSettings)
import Network.Wai.Handler.WarpTLS (TLSSettings, runTLS, tlsSettingsChain)
import qualified System.Metrics as EKG
import System.Remote.Monitoring.Wai
import System.Time.Extra (sleep)

ddosProtectionMiddlewareConfig :: DDoSProtectionMiddlewareConfig
ddosProtectionMiddlewareConfig = DDoSProtectionMiddlewareConfig {
requestBodySizeLimitKB = 2 * 1024,
requestRateWindowSec = 60,
requestRateLimitSec = 120,
responseTimeLimitSec = 5
}

-- | 'ekg' package allows to run only one EKG server, to display only one web page
-- for particular EKG.Store. Since 'cardano-tracer' can be connected to any number
-- of nodes, we display their list on the first web page (the first 'Endpoint')
Expand All @@ -43,7 +52,7 @@ runMonitoringServer
-> IO RouteDictionary
-> IO ()
runMonitoringServer tracerEnv endpoint computeRoutes_autoUpdate = do
let TracerEnv
let TracerEnv
{ teConfig = TracerConfig
{ tlsCertificate }
, teTracer
Expand All @@ -57,6 +66,8 @@ runMonitoringServer tracerEnv endpoint computeRoutes_autoUpdate = do
}
dummyStore <- EKG.newStore

middleware <- mkDDoSProtectionMiddleware ddosProtectionMiddlewareConfig

let
settings :: Settings
settings = setEndpoint endpoint defaultSettings
Expand All @@ -66,7 +77,7 @@ runMonitoringServer tracerEnv endpoint computeRoutes_autoUpdate = do
tlsSettingsChain certificateFile (fromMaybe [] certificateChain) certificateKeyFile

application :: Application
application = renderEkg dummyStore computeRoutes_autoUpdate
application = middleware $ renderEkg dummyStore computeRoutes_autoUpdate

run :: IO ()
run | Just True <- epForceSSL endpoint
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE ViewPatterns #-}

Expand All @@ -11,6 +11,7 @@ module Cardano.Tracer.Handlers.Metrics.Prometheus
import Cardano.Logging.Prometheus.Exposition (renderExpositionFromSampleWith)
import Cardano.Tracer.Configuration
import Cardano.Tracer.Environment
import Cardano.Tracer.Handlers.Metrics.DDoSProtectionMiddleware
import Cardano.Tracer.Handlers.Metrics.Utils
import Cardano.Tracer.MetaTrace

Expand All @@ -30,10 +31,18 @@ import qualified Data.Text.Lazy.Encoding as TL
import Network.HTTP.Types
import Network.Wai
import Network.Wai.Handler.Warp (Settings, defaultSettings, runSettings)
import Network.Wai.Handler.WarpTLS (runTLS, tlsSettingsChain, TLSSettings)
import Network.Wai.Handler.WarpTLS (TLSSettings, runTLS, tlsSettingsChain)
import System.Metrics as EKG (Store, sampleAll)
import System.Time.Extra (sleep)

ddosProtectionMiddlewareConfig :: DDoSProtectionMiddlewareConfig
ddosProtectionMiddlewareConfig = DDoSProtectionMiddlewareConfig {
requestBodySizeLimitKB = 2 * 1024,
requestRateWindowSec = 60,
requestRateLimitSec = 120,
responseTimeLimitSec = 5
}

-- | Runs a simple HTTP server that listens on @endpoint@.
--
-- At the root, it lists the connected nodes, either as HTML or JSON, depending
Expand Down Expand Up @@ -103,6 +112,9 @@ runPrometheusServer tracerEnv endpoint computeRoutes_autoUpdate = do
traceWith teTracer TracerStartedPrometheus
{ ttPrometheusEndpoint = endpoint
}

middleware <- mkDDoSProtectionMiddleware ddosProtectionMiddlewareConfig

let
settings :: Settings
settings = setEndpoint endpoint defaultSettings
Expand All @@ -112,7 +124,7 @@ runPrometheusServer tracerEnv endpoint computeRoutes_autoUpdate = do
tlsSettingsChain certificateFile (fromMaybe [] certificateChain) certificateKeyFile

application :: Application
application = renderPrometheus computeRoutes_autoUpdate noSuffix teMetricsHelp promLabels
application = middleware $ renderPrometheus computeRoutes_autoUpdate noSuffix teMetricsHelp promLabels

run :: IO ()
run | Just True <- epForceSSL endpoint
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import Cardano.Timeseries.Interface (ExecutionError (..))
import Cardano.Tracer.Acceptors.Utils (getTimeMs)
import Cardano.Tracer.Configuration (Certificate (..), Endpoint, TracerConfig (..),
epForceSSL, setEndpoint)
import Cardano.Tracer.Handlers.Metrics.DDoSProtectionMiddleware
import Cardano.Tracer.Handlers.Metrics.Utils (contentHdrUtf8Text)
import Cardano.Tracer.MetaTrace
import Cardano.Tracer.Timeseries
Expand All @@ -25,6 +26,14 @@ import Network.Wai.Handler.Warp hiding (run)
import Network.Wai.Handler.WarpTLS
import System.Time.Extra (sleep)

ddosProtectionMiddlewareConfig :: DDoSProtectionMiddlewareConfig
ddosProtectionMiddlewareConfig = DDoSProtectionMiddlewareConfig {
requestBodySizeLimitKB = 2 * 1024,
requestRateWindowSec = 60,
requestRateLimitSec = 30,
responseTimeLimitSec = 5
}

-- | GET timeseries/query
parseTimeseriesQuery :: Request -> Maybe ()
parseTimeseriesQuery request = do
Expand Down Expand Up @@ -57,6 +66,7 @@ runTimeseriesServer tr tracerConfig endpoint handle = do
{ ttTimeseriesEndpoint = endpoint
}

middleware <- mkDDoSProtectionMiddleware ddosProtectionMiddlewareConfig

let
settings :: Settings
Expand All @@ -67,7 +77,7 @@ runTimeseriesServer tr tracerConfig endpoint handle = do
tlsSettingsChain certificateFile (fromMaybe [] certificateChain) certificateKeyFile

application :: Application
application = timeseriesApp handle
application = middleware $ timeseriesApp handle

run :: IO ()
run | Just True <- epForceSSL endpoint , Just cert <- tlsCertificate tracerConfig
Expand Down
Loading