zoraxy-waf/wallarm_client.go

86 lines
2.5 KiB
Go

package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"log"
"net/http"
"time"
)
// InspectionRequest is sent to the Wallarm node.
type InspectionRequest struct {
Method string `json:"method"`
URL string `json:"url"`
Host string `json:"host"`
RemoteAddr string `json:"remote_addr"`
ContentType string `json:"content_type"`
ContentLength int64 `json:"content_length"`
Headers map[string][]string `json:"headers"`
BodySample []byte `json:"body_sample"` // first N bytes
}
// InspectionResponse is returned by the Wallarm node.
type InspectionResponse struct {
Verdict string `json:"verdict"` // "allow" or "block"
Reason string `json:"reason,omitempty"`
Score int `json:"score,omitempty"`
}
const maxBodySample = 4096 // only send first 4KB to Wallarm
// inspect sends a synchronous inspection request to the Wallarm node.
// Returns nil if allowed, or an error describing the block reason.
func inspect(r *http.Request) (*InspectionResponse, error) {
c := getConfig()
// Sample the first N bytes of the body.
var bodySample []byte
if r.Body != nil {
bodySample = make([]byte, maxBodySample)
n, _ := io.ReadFull(r.Body, bodySample)
bodySample = bodySample[:n]
// Restore body for downstream use.
r.Body = io.NopCloser(io.MultiReader(
bytes.NewReader(bodySample),
r.Body,
))
}
inspReq := InspectionRequest{
Method: r.Method,
URL: r.URL.String(),
Host: r.Host,
RemoteAddr: r.RemoteAddr,
ContentType: r.Header.Get("Content-Type"),
ContentLength: r.ContentLength,
Headers: map[string][]string(r.Header),
BodySample: bodySample,
}
body, err := json.Marshal(inspReq)
if err != nil {
return nil, fmt.Errorf("marshal inspection request: %w", err)
}
timeout := time.Duration(c.TimeoutMs) * time.Millisecond
client := &http.Client{Timeout: timeout}
resp, err := client.Post(c.NodeURL+"/inspect", "application/json", bytes.NewReader(body))
if err != nil {
return nil, fmt.Errorf("wallarm node unreachable: %w", err)
}
defer resp.Body.Close()
var inspResp InspectionResponse
if err := json.NewDecoder(resp.Body).Decode(&inspResp); err != nil {
return nil, fmt.Errorf("decode wallarm response: %w", err)
}
log.Printf("WAF: %s %s → %s (score=%d reason=%s)",
r.Method, r.URL.Path, inspResp.Verdict, inspResp.Score, inspResp.Reason)
return &inspResp, nil
}