Skip to content

Commit 3ee7ccc

Browse files
authored
Release/0.9.1 (#130)
* Update dependencies * Add APIFW_API_MODE_MAX_ERRORS_IN_RESPONSE param to limit the response size * Fix related_fields_details value
1 parent 53b7420 commit 3ee7ccc

File tree

27 files changed

+466
-93
lines changed

27 files changed

+466
-93
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.7.linux-amd64.tar.gz"
54+
X_GO_DISTRIBUTION: "https://go.dev/dl/go1.23.8.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.7"
165+
X_GO_VERSION: "1.23.8"
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.7.linux-armv6l.tar.gz
275+
go_distribution: https://go.dev/dl/go1.23.8.linux-armv6l.tar.gz
276276
artifact: armv6-libc
277277
- arch: aarch64
278278
distro: bookworm
279-
go_distribution: https://go.dev/dl/go1.23.7.linux-arm64.tar.gz
279+
go_distribution: https://go.dev/dl/go1.23.8.linux-arm64.tar.gz
280280
artifact: arm64-libc
281281
- arch: armv6
282282
distro: alpine_latest
283-
go_distribution: https://go.dev/dl/go1.23.7.linux-armv6l.tar.gz
283+
go_distribution: https://go.dev/dl/go1.23.8.linux-armv6l.tar.gz
284284
artifact: armv6-musl
285285
- arch: aarch64
286286
distro: alpine_latest
287-
go_distribution: https://go.dev/dl/go1.23.7.linux-arm64.tar.gz
287+
go_distribution: https://go.dev/dl/go1.23.8.linux-arm64.tar.gz
288288
artifact: arm64-musl
289289
steps:
290290
- uses: actions/checkout@v4

.github/workflows/trivy.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ jobs:
2020
security-events: write # for github/codeql-action/upload-sarif to upload SARIF results
2121
actions: read # only required for a private repository by github/codeql-action/upload-sarif to get the Action run status
2222
name: Build
23-
runs-on: "ubuntu-20.04"
23+
runs-on: "ubuntu-22.04"
2424
steps:
2525
- name: Checkout code
2626
uses: actions/checkout@v4

Makefile

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

44
.DEFAULT_GOAL := build

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

Lines changed: 21 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -30,17 +30,18 @@ var (
3030
// object for each of our http handlers. Feel free to add any configuration
3131
// data/logic on this App struct
3232
type App struct {
33-
Routers map[int]*router.Mux
34-
Log zerolog.Logger
35-
passOPTIONS bool
36-
shutdown chan os.Signal
37-
mw []web.Middleware
38-
storedSpecs storage.DBOpenAPILoader
39-
lock *sync.RWMutex
33+
Routers map[int]*router.Mux
34+
Log zerolog.Logger
35+
passOPTIONS bool
36+
maxErrorsInResponse int
37+
shutdown chan os.Signal
38+
mw []web.Middleware
39+
storedSpecs storage.DBOpenAPILoader
40+
lock *sync.RWMutex
4041
}
4142

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

4546
schemaIDs := storedSpecs.SchemaIDs()
4647

@@ -51,13 +52,14 @@ func NewApp(lock *sync.RWMutex, passOPTIONS bool, storedSpecs storage.DBOpenAPIL
5152
}
5253

5354
app := App{
54-
Routers: routers,
55-
shutdown: shutdown,
56-
mw: mw,
57-
Log: logger,
58-
storedSpecs: storedSpecs,
59-
lock: lock,
60-
passOPTIONS: passOPTIONS,
55+
Routers: routers,
56+
shutdown: shutdown,
57+
mw: mw,
58+
Log: logger,
59+
storedSpecs: storedSpecs,
60+
lock: lock,
61+
passOPTIONS: passOPTIONS,
62+
maxErrorsInResponse: maxErrorsInResponse,
6163
}
6264

6365
return &app
@@ -286,7 +288,10 @@ func (a *App) APIModeMainHandler(ctx *fasthttp.RequestCtx) {
286288
ctx.Request.Header.SetMethod(fasthttp.MethodGet)
287289
}
288290

289-
if err := web.Respond(ctx, validator.ValidationResponse{Summary: responseSummary, Errors: responseErrors}, fasthttp.StatusOK); err != nil {
291+
// limit amount of errors to reduce the total size of the response
292+
limitedResponseErrors := validator.SampleSlice(responseErrors, a.maxErrorsInResponse)
293+
294+
if err := web.Respond(ctx, validator.ValidationResponse{Summary: responseSummary, Errors: limitedResponseErrors}, fasthttp.StatusOK); err != nil {
290295
a.Log.Error().
291296
Err(err).
292297
Bytes("host", ctx.Request.Header.Host()).

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ func Handlers(lock *sync.RWMutex, cfg *config.APIMode, shutdown chan os.Signal,
5252
}
5353

5454
// Construct the App which holds all routes as well as common Middleware.
55-
apps := NewApp(lock, cfg.PassOptionsRequests, storedSpecs, shutdown, logger, mid.IPAllowlist(&ipAllowlistOptions), mid.WAFModSecurity(&modSecOptions), mid.Logger(logger), mid.MIMETypeIdentifier(logger), mid.Errors(logger), mid.Panics(logger))
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))
5656

5757
for _, schemaID := range schemaIDs {
5858

cmd/api-firewall/tests/main_api_mode_test.go

Lines changed: 106 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"net/url"
1212
"os"
1313
"os/signal"
14+
"slices"
1415
"strings"
1516
"sync"
1617
"syscall"
@@ -22,7 +23,6 @@ import (
2223
"github.com/google/uuid"
2324
"github.com/rs/zerolog"
2425
"github.com/valyala/fasthttp"
25-
"golang.org/x/exp/slices"
2626

2727
handlersAPI "github.com/wallarm/api-firewall/cmd/api-firewall/internal/handlers/api"
2828
"github.com/wallarm/api-firewall/internal/config"
@@ -613,6 +613,9 @@ func TestAPIModeBasic(t *testing.T) {
613613
// check conflicts in the Path
614614
t.Run("testConflictsInThePath", apifwTests.testConflictsInThePath)
615615
t.Run("testObjectInQuery", apifwTests.testObjectInQuery)
616+
617+
// check limited response (maxErrorsInResponse param)
618+
t.Run("testAPIModeMissedMultipleReqParamsLimitedResponse", apifwTests.testAPIModeMissedMultipleReqParamsLimitedResponse)
616619
}
617620

618621
func createForm(form map[string]string) (string, io.Reader, error) {
@@ -2926,3 +2929,105 @@ func (s *APIModeServiceTests) testObjectInQuery(t *testing.T) {
29262929
// check response status code and response body
29272930
checkResponseForbiddenStatusCode(t, &reqCtx, DefaultSchemaID, []string{validator.ErrCodeRequiredQueryParameterMissed})
29282931
}
2932+
2933+
func (s *APIModeServiceTests) testAPIModeMissedMultipleReqParamsLimitedResponse(t *testing.T) {
2934+
2935+
updatedCfg := config.APIMode{
2936+
APIFWInit: config.APIFWInit{Mode: web.APIMode},
2937+
SpecificationUpdatePeriod: 2 * time.Second,
2938+
UnknownParametersDetection: true,
2939+
PassOptionsRequests: false,
2940+
MaxErrorsInResponse: 1,
2941+
}
2942+
2943+
handler := handlersAPI.Handlers(s.lock, &updatedCfg, s.shutdown, s.logger, s.dbSpec, nil, nil)
2944+
2945+
p, err := json.Marshal(map[string]any{
2946+
"firstname": "test",
2947+
"lastname": "test",
2948+
"job": "test",
2949+
"email": "[email protected]",
2950+
"url": "http://wallarm.com",
2951+
})
2952+
2953+
if err != nil {
2954+
t.Fatal(err)
2955+
}
2956+
2957+
req := fasthttp.AcquireRequest()
2958+
req.SetRequestURI("/test/signup")
2959+
req.Header.SetMethod("POST")
2960+
req.SetBodyStream(bytes.NewReader(p), -1)
2961+
req.Header.SetContentType("application/json")
2962+
req.Header.Add(web.XWallarmSchemaIDHeader, fmt.Sprintf("%d", DefaultSchemaID))
2963+
2964+
reqCtx := fasthttp.RequestCtx{
2965+
Request: *req,
2966+
}
2967+
2968+
handler(&reqCtx)
2969+
2970+
t.Logf("Name of the test: %s; request method: %s; request uri: %s; request body: %s", t.Name(), string(reqCtx.Request.Header.Method()), string(reqCtx.Request.RequestURI()), string(reqCtx.Request.Body()))
2971+
t.Logf("Name of the test: %s; status code: %d; response body: %s", t.Name(), reqCtx.Response.StatusCode(), string(reqCtx.Response.Body()))
2972+
2973+
// check response status code and response body
2974+
checkResponseOkStatusCode(t, &reqCtx, DefaultSchemaID)
2975+
2976+
// Repeat request with invalid email
2977+
reqInvalidEmail, err := json.Marshal(map[string]any{
2978+
"email": "[email protected]",
2979+
})
2980+
2981+
if err != nil {
2982+
t.Fatal(err)
2983+
}
2984+
2985+
req.SetBodyStream(bytes.NewReader(reqInvalidEmail), -1)
2986+
2987+
missedParams := map[string]any{
2988+
"firstname": struct{}{},
2989+
"lastname": struct{}{},
2990+
}
2991+
2992+
reqCtx = fasthttp.RequestCtx{
2993+
Request: *req,
2994+
}
2995+
2996+
handler(&reqCtx)
2997+
2998+
if reqCtx.Response.StatusCode() != 200 {
2999+
t.Errorf("Incorrect response status code. Expected: 200 and got %d",
3000+
reqCtx.Response.StatusCode())
3001+
}
3002+
3003+
apifwResponse := validator.ValidationResponse{}
3004+
if err := json.Unmarshal(reqCtx.Response.Body(), &apifwResponse); err != nil {
3005+
t.Errorf("Error while JSON response parsing: %v", err)
3006+
}
3007+
3008+
if len(apifwResponse.Errors) != 1 {
3009+
t.Errorf("wrong number of errors. Expected: 1. Got: %d", len(apifwResponse.Errors))
3010+
}
3011+
3012+
for _, apifwErr := range apifwResponse.Errors {
3013+
3014+
if apifwErr.Code != validator.ErrCodeRequiredBodyParameterMissed {
3015+
t.Errorf("Incorrect error code. Expected: %s and got %s",
3016+
validator.ErrCodeRequiredBodyParameterMissed, apifwErr.Code)
3017+
}
3018+
3019+
if len(apifwErr.Fields) != 1 {
3020+
t.Errorf("wrong number of related fields. Expected: 1. Got: %d", len(apifwErr.Fields))
3021+
}
3022+
3023+
if _, ok := missedParams[apifwErr.Fields[0]]; !ok {
3024+
t.Errorf("Invalid missed field. Expected: firstname or lastname but got %s",
3025+
apifwErr.Fields[0])
3026+
}
3027+
3028+
}
3029+
3030+
t.Logf("Name of the test: %s; request method: %s; request uri: %s; request body: %s", t.Name(), string(reqCtx.Request.Header.Method()), string(reqCtx.Request.RequestURI()), string(reqCtx.Request.Body()))
3031+
t.Logf("Name of the test: %s; status code: %d; response body: %s", t.Name(), reqCtx.Response.StatusCode(), string(reqCtx.Response.Body()))
3032+
3033+
}
0 Bytes
Binary file not shown.

demo/docker-compose/OWASP_CoreRuleSet/docker-compose.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ version: "3.8"
22
services:
33
api-firewall:
44
container_name: api-firewall
5-
image: wallarm/api-firewall:v0.9.0
5+
image: wallarm/api-firewall:v0.9.1
66
restart: on-failure
77
environment:
88
APIFW_URL: "http://0.0.0.0:8080"

demo/docker-compose/docker-compose-api-mode.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ version: '3.8'
22
services:
33
api-firewall:
44
container_name: api-firewall
5-
image: wallarm/api-firewall:v0.9.0
5+
image: wallarm/api-firewall:v0.9.1
66
restart: on-failure
77
environment:
88
APIFW_MODE: "api"

demo/docker-compose/docker-compose-graphql-mode.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ version: '3.8'
22
services:
33
api-firewall:
44
container_name: api-firewall
5-
image: wallarm/api-firewall:v0.9.0
5+
image: wallarm/api-firewall:v0.9.1
66
restart: on-failure
77
environment:
88
APIFW_MODE: "graphql"

0 commit comments

Comments
 (0)