Skip to content

Commit 4f51d8c

Browse files
authored
Release/0.9.2 (#132)
* Add metrics to API mode * Add env var support to the pkg apifw * Bump up Go ver and some dependencies
1 parent 645cd26 commit 4f51d8c

File tree

36 files changed

+546
-145
lines changed

36 files changed

+546
-145
lines changed

.github/workflows/binaries.yml

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ jobs:
5151
needs:
5252
- draft-release
5353
env:
54-
X_GO_DISTRIBUTION: "https://go.dev/dl/go1.23.8.linux-amd64.tar.gz"
54+
X_GO_DISTRIBUTION: "https://go.dev/dl/go1.23.10.linux-amd64.tar.gz"
5555
APIFIREWALL_NAMESPACE: "github.com/wallarm/api-firewall"
5656
strategy:
5757
matrix:
@@ -162,7 +162,7 @@ jobs:
162162
needs:
163163
- draft-release
164164
env:
165-
X_GO_VERSION: "1.23.8"
165+
X_GO_VERSION: "1.23.10"
166166
APIFIREWALL_NAMESPACE: "github.com/wallarm/api-firewall"
167167
strategy:
168168
matrix:
@@ -272,19 +272,19 @@ jobs:
272272
include:
273273
- arch: armv6
274274
distro: bookworm
275-
go_distribution: https://go.dev/dl/go1.23.8.linux-armv6l.tar.gz
275+
go_distribution: https://go.dev/dl/go1.23.10.linux-armv6l.tar.gz
276276
artifact: armv6-libc
277277
- arch: aarch64
278278
distro: bookworm
279-
go_distribution: https://go.dev/dl/go1.23.8.linux-arm64.tar.gz
279+
go_distribution: https://go.dev/dl/go1.23.10.linux-arm64.tar.gz
280280
artifact: arm64-libc
281281
- arch: armv6
282282
distro: alpine_latest
283-
go_distribution: https://go.dev/dl/go1.23.8.linux-armv6l.tar.gz
283+
go_distribution: https://go.dev/dl/go1.23.10.linux-armv6l.tar.gz
284284
artifact: armv6-musl
285285
- arch: aarch64
286286
distro: alpine_latest
287-
go_distribution: https://go.dev/dl/go1.23.8.linux-arm64.tar.gz
287+
go_distribution: https://go.dev/dl/go1.23.10.linux-arm64.tar.gz
288288
artifact: arm64-musl
289289
steps:
290290
- uses: actions/checkout@v4

Makefile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
VERSION := 0.9.1
1+
VERSION := 0.9.2
22
NAMESPACE := github.com/wallarm/api-firewall
33

44
.DEFAULT_GOAL := build
@@ -43,7 +43,7 @@ stop_k6_tests:
4343

4444
run_k6_tests: stop_k6_tests
4545
@docker compose -f resources/test/docker-compose-api-mode.yml up --build --detach --force-recreate
46-
docker run --rm -i --network host grafana/k6 run --vus 100 --iterations 1200 -v - <resources/test/specification/script.js || true
46+
docker run --rm -i --network host grafana/k6 run -v - <resources/test/specification/script.js || true
4747
$(MAKE) stop_k6_tests
4848

4949
.PHONY: lint tidy test fmt build genmocks vulncheck run_k6_tests stop_k6_tests

cmd/api-firewall/internal/handlers/api/app.go

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,14 @@ import (
99
"strings"
1010
"sync"
1111
"syscall"
12+
"time"
1213

1314
"github.com/google/uuid"
1415
"github.com/rs/zerolog"
1516
"github.com/savsgio/gotils/strconv"
1617
"github.com/valyala/fasthttp"
1718

19+
"github.com/wallarm/api-firewall/internal/platform/metrics"
1820
"github.com/wallarm/api-firewall/internal/platform/router"
1921
"github.com/wallarm/api-firewall/internal/platform/storage"
2022
"github.com/wallarm/api-firewall/internal/platform/web"
@@ -32,6 +34,7 @@ var (
3234
type App struct {
3335
Routers map[int]*router.Mux
3436
Log zerolog.Logger
37+
Metrics metrics.Metrics
3538
passOPTIONS bool
3639
maxErrorsInResponse int
3740
shutdown chan os.Signal
@@ -41,7 +44,7 @@ type App struct {
4144
}
4245

4346
// NewApp creates an App value that handle a set of routes for the set of application.
44-
func NewApp(lock *sync.RWMutex, passOPTIONS bool, maxErrorsInResponse int, storedSpecs storage.DBOpenAPILoader, shutdown chan os.Signal, logger zerolog.Logger, mw ...web.Middleware) *App {
47+
func NewApp(lock *sync.RWMutex, passOPTIONS bool, maxErrorsInResponse int, storedSpecs storage.DBOpenAPILoader, shutdown chan os.Signal, logger zerolog.Logger, pMetrics metrics.Metrics, mw ...web.Middleware) *App {
4548

4649
schemaIDs := storedSpecs.SchemaIDs()
4750

@@ -56,6 +59,7 @@ func NewApp(lock *sync.RWMutex, passOPTIONS bool, maxErrorsInResponse int, store
5659
shutdown: shutdown,
5760
mw: mw,
5861
Log: logger,
62+
Metrics: pMetrics,
5963
storedSpecs: storedSpecs,
6064
lock: lock,
6165
passOPTIONS: passOPTIONS,
@@ -138,6 +142,9 @@ func (a *App) APIModeMainHandler(ctx *fasthttp.RequestCtx) {
138142
// handle panic
139143
defer func() {
140144
if r := recover(); r != nil {
145+
146+
a.Metrics.IncErrorTypeCounter("request processing error", 0)
147+
141148
a.Log.Error().Msgf("panic: %v", r)
142149

143150
// Log the Go stack trace for this panic'd goroutine.
@@ -149,10 +156,15 @@ func (a *App) APIModeMainHandler(ctx *fasthttp.RequestCtx) {
149156
// Add request ID
150157
ctx.SetUserValue(web.RequestID, uuid.NewString())
151158

159+
// Request handling start time
160+
start := time.Now()
161+
152162
schemaIDs, notFoundSchemaIDs, err := getWallarmSchemaID(ctx, a.storedSpecs)
153163
if err != nil {
154164
defer web.LogRequestResponseAtTraceLevel(ctx, a.Log)
155165

166+
a.Metrics.IncErrorTypeCounter("schema not found", 0)
167+
156168
a.Log.Error().
157169
Err(err).
158170
Bytes("host", ctx.Request.Header.Host()).
@@ -161,6 +173,8 @@ func (a *App) APIModeMainHandler(ctx *fasthttp.RequestCtx) {
161173
Interface("request_id", ctx.UserValue(web.RequestID)).
162174
Msg("error while getting schema ID")
163175

176+
a.Metrics.IncHTTPRequestTotalCountOnly(0, fasthttp.StatusInternalServerError)
177+
164178
if err := web.RespondError(ctx, fasthttp.StatusInternalServerError, ""); err != nil {
165179
a.Log.Error().
166180
Err(err).
@@ -256,6 +270,8 @@ func (a *App) APIModeMainHandler(ctx *fasthttp.RequestCtx) {
256270
continue
257271
}
258272

273+
a.Metrics.IncErrorTypeCounter("request processing error", schemaIDs[i])
274+
259275
// Didn't receive the response code. It means that the router respond to the request because it was not valid.
260276
// The API Firewall should respond by 500 status code in this case.
261277
ctx.Response.Header.Reset()
@@ -274,6 +290,8 @@ func (a *App) APIModeMainHandler(ctx *fasthttp.RequestCtx) {
274290

275291
// Add schema IDs that were not found in the DB to the response
276292
for i := 0; i < len(notFoundSchemaIDs); i++ {
293+
a.Metrics.IncErrorTypeCounter("schema not found", notFoundSchemaIDs[i])
294+
a.Metrics.IncHTTPRequestTotalCountOnly(notFoundSchemaIDs[i], fasthttp.StatusOK)
277295
responseSummary = append(responseSummary, &validator.ValidationResponseSummary{
278296
SchemaID: &notFoundSchemaIDs[i],
279297
StatusCode: &statusInternalError,
@@ -288,6 +306,11 @@ func (a *App) APIModeMainHandler(ctx *fasthttp.RequestCtx) {
288306
ctx.Request.Header.SetMethod(fasthttp.MethodGet)
289307
}
290308

309+
// save http request count for each schema ID
310+
for _, schemaID := range schemaIDs {
311+
a.Metrics.IncHTTPRequestStat(start, schemaID, fasthttp.StatusOK)
312+
}
313+
291314
// limit amount of errors to reduce the total size of the response
292315
limitedResponseErrors := validator.SampleSlice(responseErrors, a.maxErrorsInResponse)
293316

cmd/api-firewall/internal/handlers/api/handler.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010

1111
"github.com/wallarm/api-firewall/internal/config"
1212
"github.com/wallarm/api-firewall/internal/platform/loader"
13+
"github.com/wallarm/api-firewall/internal/platform/metrics"
1314
apiMode "github.com/wallarm/api-firewall/internal/platform/validator"
1415
"github.com/wallarm/api-firewall/internal/platform/web"
1516
"github.com/wallarm/api-firewall/pkg/APIMode/validator"
@@ -21,6 +22,7 @@ type RequestValidator struct {
2122
Log zerolog.Logger
2223
Cfg *config.APIMode
2324
ParserPool *fastjson.ParserPool
25+
Metrics metrics.Metrics
2426
SchemaID int
2527
}
2628

@@ -55,7 +57,7 @@ func (s *RequestValidator) Handler(ctx *fasthttp.RequestCtx) error {
5557
return nil
5658
}
5759

58-
validationErrors, err := apiMode.APIModeValidateRequest(ctx, s.ParserPool, s.CustomRoute, s.Cfg.UnknownParametersDetection)
60+
validationErrors, err := apiMode.APIModeValidateRequest(ctx, s.Metrics, s.SchemaID, s.ParserPool, s.CustomRoute, s.Cfg.UnknownParametersDetection)
5961
if err != nil {
6062
s.Log.Error().
6163
Err(err).

cmd/api-firewall/internal/handlers/api/routes.go

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
package api
22

33
import (
4-
"github.com/rs/zerolog"
54
"net/url"
65
"os"
76
"runtime/debug"
87
"sync"
98

109
"github.com/corazawaf/coraza/v3"
10+
"github.com/rs/zerolog"
1111
"github.com/rs/zerolog/log"
1212
"github.com/valyala/fasthttp"
1313
"github.com/valyala/fastjson"
@@ -16,11 +16,12 @@ import (
1616
"github.com/wallarm/api-firewall/internal/mid"
1717
"github.com/wallarm/api-firewall/internal/platform/allowiplist"
1818
"github.com/wallarm/api-firewall/internal/platform/loader"
19+
"github.com/wallarm/api-firewall/internal/platform/metrics"
1920
"github.com/wallarm/api-firewall/internal/platform/storage"
2021
"github.com/wallarm/api-firewall/internal/platform/web"
2122
)
2223

23-
func Handlers(lock *sync.RWMutex, cfg *config.APIMode, shutdown chan os.Signal, logger zerolog.Logger, storedSpecs storage.DBOpenAPILoader, AllowedIPCache *allowiplist.AllowedIPsType, waf coraza.WAF) fasthttp.RequestHandler {
24+
func Handlers(lock *sync.RWMutex, cfg *config.APIMode, shutdown chan os.Signal, logger zerolog.Logger, metrics metrics.Metrics, storedSpecs storage.DBOpenAPILoader, AllowedIPCache *allowiplist.AllowedIPsType, waf coraza.WAF) fasthttp.RequestHandler {
2425

2526
// handle panic
2627
defer func() {
@@ -52,7 +53,7 @@ func Handlers(lock *sync.RWMutex, cfg *config.APIMode, shutdown chan os.Signal,
5253
}
5354

5455
// Construct the App which holds all routes as well as common Middleware.
55-
apps := NewApp(lock, cfg.PassOptionsRequests, cfg.MaxErrorsInResponse, storedSpecs, shutdown, logger, mid.IPAllowlist(&ipAllowlistOptions), mid.WAFModSecurity(&modSecOptions), mid.Logger(logger), mid.MIMETypeIdentifier(logger), mid.Errors(logger), mid.Panics(logger))
56+
apps := NewApp(lock, cfg.PassOptionsRequests, cfg.MaxErrorsInResponse, storedSpecs, shutdown, logger, metrics, mid.IPAllowlist(&ipAllowlistOptions), mid.WAFModSecurity(&modSecOptions), mid.Logger(logger), mid.MIMETypeIdentifier(logger), mid.Errors(logger), mid.Panics(logger))
5657

5758
for _, schemaID := range schemaIDs {
5859

@@ -89,6 +90,7 @@ func Handlers(lock *sync.RWMutex, cfg *config.APIMode, shutdown chan os.Signal,
8990
ParserPool: &parserPool,
9091
OpenAPIRouter: newSwagRouter,
9192
SchemaID: schemaID,
93+
Metrics: metrics,
9294
}
9395
updRoutePathEsc, err := url.JoinPath(serverURL.Path, newSwagRouter.Routes[i].Path)
9496
if err != nil {

cmd/api-firewall/internal/handlers/api/run.go

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import (
1515

1616
"github.com/wallarm/api-firewall/internal/config"
1717
"github.com/wallarm/api-firewall/internal/platform/allowiplist"
18+
"github.com/wallarm/api-firewall/internal/platform/metrics"
1819
"github.com/wallarm/api-firewall/internal/platform/storage"
1920
"github.com/wallarm/api-firewall/internal/version"
2021
)
@@ -105,10 +106,34 @@ func Run(logger zerolog.Logger) error {
105106

106107
zeroLogger := &config.ZerologAdapter{Logger: logger}
107108

109+
// =========================================================================
110+
// Init Metrics
111+
112+
// make a channel to listen for errors coming from the metrics listener. Use a
113+
// buffered channel so the goroutine can exit if we don't collect this error.
114+
metricsErrors := make(chan error, 1)
115+
116+
options := metrics.Options{
117+
EndpointName: cfg.Metrics.EndpointName,
118+
Host: cfg.Metrics.Host,
119+
ReadTimeout: cfg.Metrics.ReadTimeout,
120+
WriteTimeout: cfg.Metrics.WriteTimeout,
121+
}
122+
123+
metricsController := metrics.NewPrometheusMetrics(cfg.Metrics.Enabled)
124+
125+
if cfg.Metrics.Enabled {
126+
go func() {
127+
// Start the service listening for requests.
128+
logger.Info().Msgf("Prometheus metrics: API listening on %s/%s", options.Host, options.EndpointName)
129+
metricsErrors <- metricsController.StartService(&logger, &options)
130+
}()
131+
}
132+
108133
// =========================================================================
109134
// Init Handlers
110135

111-
requestHandlers := Handlers(&dbLock, &cfg, shutdown, logger, specStorage, allowedIPCache, waf)
136+
requestHandlers := Handlers(&dbLock, &cfg, shutdown, logger, metricsController, specStorage, allowedIPCache, waf)
112137

113138
// =========================================================================
114139
// Start Health API Service
@@ -184,6 +209,9 @@ func Run(logger zerolog.Logger) error {
184209
Bytes("method", ctx.Request.Header.Method()).
185210
Msg("request processing error")
186211

212+
metricsController.IncHTTPRequestTotalCountOnly(0, fasthttp.StatusInternalServerError)
213+
metricsController.IncErrorTypeCounter("request processing error", 0)
214+
187215
ctx.Error("", fasthttp.StatusInternalServerError)
188216
},
189217
Logger: zeroLogger,
@@ -195,7 +223,7 @@ func Run(logger zerolog.Logger) error {
195223

196224
updSpecErrors := make(chan error, 1)
197225

198-
updOpenAPISpec := NewHandlerUpdater(&dbLock, logger, specStorage, &cfg, &api, shutdown, &healthData, allowedIPCache, waf)
226+
updOpenAPISpec := NewHandlerUpdater(&dbLock, logger, metricsController, specStorage, &cfg, &api, shutdown, &healthData, allowedIPCache, waf)
199227

200228
// disable updater if SpecificationUpdatePeriod == 0
201229
if cfg.SpecificationUpdatePeriod.Seconds() > 0 {
@@ -228,6 +256,9 @@ func Run(logger zerolog.Logger) error {
228256
case err := <-updSpecErrors:
229257
return errors.Wrap(err, "regular updater error")
230258

259+
case err := <-metricsErrors:
260+
return errors.Wrap(err, "metrics error")
261+
231262
case sig := <-shutdown:
232263
logger.Info().Msgf("%s: %v: Start shutdown", logPrefix, sig)
233264

cmd/api-firewall/internal/handlers/api/updater.go

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package api
22

33
import (
4+
"github.com/wallarm/api-firewall/internal/platform/metrics"
45
"os"
56
"runtime/debug"
67
"sync"
@@ -35,10 +36,11 @@ type Specification struct {
3536
health *Health
3637
lock *sync.RWMutex
3738
allowedIPCache *allowiplist.AllowedIPsType
39+
metrics metrics.Metrics
3840
}
3941

4042
// NewHandlerUpdater function defines configuration updater controller
41-
func NewHandlerUpdater(lock *sync.RWMutex, logger zerolog.Logger, sqlLiteStorage storage.DBOpenAPILoader, cfg *config.APIMode, api *fasthttp.Server, shutdown chan os.Signal, health *Health, allowedIPCache *allowiplist.AllowedIPsType, waf coraza.WAF) updater.Updater {
43+
func NewHandlerUpdater(lock *sync.RWMutex, logger zerolog.Logger, metrics metrics.Metrics, sqlLiteStorage storage.DBOpenAPILoader, cfg *config.APIMode, api *fasthttp.Server, shutdown chan os.Signal, health *Health, allowedIPCache *allowiplist.AllowedIPsType, waf coraza.WAF) updater.Updater {
4244
return &Specification{
4345
logger: logger,
4446
waf: waf,
@@ -51,6 +53,7 @@ func NewHandlerUpdater(lock *sync.RWMutex, logger zerolog.Logger, sqlLiteStorage
5153
health: health,
5254
lock: lock,
5355
allowedIPCache: allowedIPCache,
56+
metrics: metrics,
5457
}
5558
}
5659

@@ -100,7 +103,7 @@ func (s *Specification) Run() {
100103

101104
s.lock.Lock()
102105
s.sqlLiteStorage = newSpecDB
103-
s.api.Handler = Handlers(s.lock, s.cfg, s.shutdown, s.logger, s.sqlLiteStorage, s.allowedIPCache, s.waf)
106+
s.api.Handler = Handlers(s.lock, s.cfg, s.shutdown, s.logger, s.metrics, s.sqlLiteStorage, s.allowedIPCache, s.waf)
104107
s.health.OpenAPIDB = s.sqlLiteStorage
105108
if err := s.sqlLiteStorage.AfterLoad(s.cfg.PathToSpecDB); err != nil {
106109
s.logger.Error().Err(err).Msgf("%s: error in after specification loading function", logPrefix)

cmd/api-firewall/tests/main_api_mode_bench_test.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313
"github.com/valyala/fasthttp"
1414

1515
handlersAPI "github.com/wallarm/api-firewall/cmd/api-firewall/internal/handlers/api"
16+
"github.com/wallarm/api-firewall/internal/platform/metrics"
1617
"github.com/wallarm/api-firewall/internal/platform/storage"
1718
"github.com/wallarm/api-firewall/internal/platform/web"
1819
)
@@ -35,7 +36,7 @@ func BenchmarkAPIModeBasic(b *testing.B) {
3536
shutdown := make(chan os.Signal, 1)
3637
signal.Notify(shutdown, os.Interrupt, syscall.SIGTERM)
3738

38-
handler := handlersAPI.Handlers(&lock, &cfg, shutdown, logger, specStorage, nil, nil)
39+
handler := handlersAPI.Handlers(&lock, &cfg, shutdown, logger, metrics.NewPrometheusMetrics(false), specStorage, nil, nil)
3940

4041
p, err := json.Marshal(map[string]any{
4142
"firstname": "test",

0 commit comments

Comments
 (0)