Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ci: use go-libddwaf/v3 in smoke tests #2671

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/smoke-tests.yml
Expand Up @@ -8,7 +8,7 @@ on:
required: true
type: string
go-libddwaf-ref:
description: A git ref to update github.com/DataDog/go-libddwaf/v2 to. No-op if empty.
description: A git ref to update github.com/DataDog/go-libddwaf/v3 to. No-op if empty.
required: false
type: string
push:
Expand Down Expand Up @@ -57,7 +57,7 @@ jobs:
- name: Install requested go-libddwaf version
if: github.event_name == 'workflow_call' && inputs.go-libddwaf-ref != ''
run: |-
go get -u -t github.com/DataDog/go-libddwaf/v2@${{ inputs.go-libddwaf-ref }}
go get -u -t github.com/DataDog/go-libddwaf/v3@${{ inputs.go-libddwaf-ref }}
go mod tidy
- name: Compile dd-trace-go
run: go build $PACKAGES
Expand Down
81 changes: 81 additions & 0 deletions internal/appsec/actions/actions.go
@@ -0,0 +1,81 @@
package actions

import (
"encoding/json"
"strconv"
)

type (
ActionEntry[T any] struct {
ID string `json:"id"`
Type string `json:"type"`
Parameters T `json:"parameters"`
}

BlockActionParams struct {
GRPCStatusCode *int `json:"grpc_status_code,omitempty"`
StatusCode int `json:"status_code"`
Type string `json:"type,omitempty"`
}
RedirectActionParams struct {
Location string `json:"location,omitempty"`
StatusCode int `json:"status_code"`
}
)

func BlockParamsFromMap(params map[string]any) (BlockActionParams, error) {
type blockActionParams struct {
GRPCStatusCode string `json:"grpc_status_code,omitempty"`
StatusCode string `json:"status_code"`
Type string `json:"type,omitempty"`
}
p := BlockActionParams{
StatusCode: 403,
Type: "auto",
}
var strParams blockActionParams
var err error
data, err := json.Marshal(params)
if err != nil {
return p, err
}
if err := json.Unmarshal(data, &strParams); err != nil {
return p, err
}

p.Type = strParams.Type

if p.StatusCode, err = strconv.Atoi(strParams.StatusCode); err != nil {
return p, err
}
if strParams.GRPCStatusCode == "" {
strParams.GRPCStatusCode = "10"
}

grpcCode, err := strconv.Atoi(strParams.GRPCStatusCode)
if err == nil {
p.GRPCStatusCode = &grpcCode
}
return p, err

}
func RedirectParamsFromMap(params map[string]any) (RedirectActionParams, error) {
type redirectActionParams struct {
Location string `json:"location,omitempty"`
StatusCode string `json:"status_code"`
}
p := RedirectActionParams{}
var strParams redirectActionParams
var err error
data, err := json.Marshal(params)
if err != nil {
return p, err
}
if err := json.Unmarshal(data, &strParams); err != nil {
return p, err
}

p.Location = strParams.Location
p.StatusCode, err = strconv.Atoi(strParams.StatusCode)
return p, err
}
2 changes: 1 addition & 1 deletion internal/appsec/appsec.go
Expand Up @@ -129,7 +129,7 @@ func setActiveAppSec(a *appsec) {
type appsec struct {
cfg *config.Config
limiter *limiter.TokenTicker
wafHandle *wafHandle
wafHandle *waf.Handle
started bool
}

Expand Down
11 changes: 11 additions & 0 deletions internal/appsec/config/config.go
Expand Up @@ -13,6 +13,7 @@ import (

internal "github.com/DataDog/appsec-internal-go/appsec"

"gopkg.in/DataDog/dd-trace-go.v1/internal/appsec/actions"
"gopkg.in/DataDog/dd-trace-go.v1/internal/log"
"gopkg.in/DataDog/dd-trace-go.v1/internal/remoteconfig"
"gopkg.in/DataDog/dd-trace-go.v1/internal/telemetry"
Expand Down Expand Up @@ -109,6 +110,16 @@ func NewConfig() (*Config, error) {
return nil, err
}

// Default blocking action
r.Base.Actions = append(r.Base.Actions, actions.ActionEntry[actions.BlockActionParams]{
ID: "block",
Type: "block_request",
Parameters: actions.BlockActionParams{
Type: "auto",
StatusCode: 403,
},
})

return &Config{
RulesManager: r,
WAFTimeout: internal.WAFTimeoutFromEnv(),
Expand Down
28 changes: 8 additions & 20 deletions internal/appsec/config/rules_manager.go
Expand Up @@ -29,15 +29,15 @@ type (
// RulesFragment can represent a full ruleset or a fragment of it.
RulesFragment struct {
Version string `json:"version,omitempty"`
Metadata interface{} `json:"metadata,omitempty"`
Rules []interface{} `json:"rules,omitempty"`
Overrides []interface{} `json:"rules_override,omitempty"`
Exclusions []interface{} `json:"exclusions,omitempty"`
Metadata any `json:"metadata,omitempty"`
Rules []any `json:"rules,omitempty"`
Overrides []any `json:"rules_override,omitempty"`
Exclusions []any `json:"exclusions,omitempty"`
RulesData []RuleDataEntry `json:"rules_data,omitempty"`
Actions []ActionEntry `json:"actions,omitempty"`
CustomRules []interface{} `json:"custom_rules,omitempty"`
Processors []interface{} `json:"processors,omitempty"`
Scanners []interface{} `json:"scanners,omitempty"`
Actions []any `json:"actions,omitempty"`
CustomRules []any `json:"custom_rules,omitempty"`
Processors []any `json:"processors,omitempty"`
Scanners []any `json:"scanners,omitempty"`
}

// RuleDataEntry represents an entry in the "rules_data" top level field of a rules file
Expand All @@ -46,18 +46,6 @@ type (
RulesData struct {
RulesData []RuleDataEntry `json:"rules_data"`
}

// ActionEntry represents an entry in the "actions" top level field of a rules file
ActionEntry struct {
ID string `json:"id"`
Type string `json:"type"`
Parameters struct {
StatusCode int `json:"status_code"`
GRPCStatusCode *int `json:"grpc_status_code,omitempty"`
Type string `json:"type,omitempty"`
Location string `json:"location,omitempty"`
} `json:"parameters,omitempty"`
}
)

// DefaultRulesFragment returns a RulesFragment created using the default static recommended rules
Expand Down
25 changes: 21 additions & 4 deletions internal/appsec/emitter/sharedsec/actions.go
Expand Up @@ -12,6 +12,7 @@ import (
"os"
"strings"

"gopkg.in/DataDog/dd-trace-go.v1/internal/appsec/actions"
"gopkg.in/DataDog/dd-trace-go.v1/internal/log"
)

Expand Down Expand Up @@ -108,17 +109,33 @@ func newGRPCBlockHandler(status int) GRPCWrapper {
}
}

// NewBlockRequestAction creates an action for the "block" action type
func NewBlockRequestAction(httpStatus, grpcStatus int, template string) *Action {
// NewBlockAction creates an action for the "block_request" action type
func NewBlockAction(params map[string]any) *Action {
p, err := actions.BlockParamsFromMap(params)
if err != nil {
return nil
}
return newBlockRequestAction(p.StatusCode, *p.GRPCStatusCode, p.Type)
}

// NewRedirectAction creates an action for the "redirect_request" action type
func NewRedirectAction(params map[string]any) *Action {
p, err := actions.RedirectParamsFromMap(params)
if err != nil {
return nil
}
return newRedirectRequestAction(p.StatusCode, p.Location)
}

func newBlockRequestAction(httpStatus, grpcStatus int, template string) *Action {
return &Action{
http: NewBlockHandler(httpStatus, template),
grpc: newGRPCBlockHandler(grpcStatus),
blocking: true,
}
}

// NewRedirectRequestAction creates an action for the "redirect" action type
func NewRedirectRequestAction(status int, loc string) *Action {
func newRedirectRequestAction(status int, loc string) *Action {
// Default to 303 if status is out of redirection codes bounds
if status < 300 || status >= 400 {
status = 303
Expand Down
14 changes: 7 additions & 7 deletions internal/appsec/emitter/sharedsec/actions_test.go
Expand Up @@ -17,9 +17,9 @@ import (
func TestNewBlockRequestAction(t *testing.T) {
mux := http.NewServeMux()
srv := httptest.NewServer(mux)
mux.HandleFunc("/json", NewBlockRequestAction(403, 10, "json").HTTP().ServeHTTP)
mux.HandleFunc("/html", NewBlockRequestAction(403, 10, "html").HTTP().ServeHTTP)
mux.HandleFunc("/auto", NewBlockRequestAction(403, 10, "auto").HTTP().ServeHTTP)
mux.HandleFunc("/json", newBlockRequestAction(403, 10, "json").HTTP().ServeHTTP)
mux.HandleFunc("/html", newBlockRequestAction(403, 10, "html").HTTP().ServeHTTP)
mux.HandleFunc("/auto", newBlockRequestAction(403, 10, "auto").HTTP().ServeHTTP)
defer srv.Close()

t.Run("json", func(t *testing.T) {
Expand Down Expand Up @@ -157,10 +157,10 @@ func TestNewBlockRequestAction(t *testing.T) {
func TestNewRedirectRequestAction(t *testing.T) {
mux := http.NewServeMux()
srv := httptest.NewServer(mux)
mux.HandleFunc("/redirect-default-status", NewRedirectRequestAction(100, "/redirected").HTTP().ServeHTTP)
mux.HandleFunc("/redirect-no-location", NewRedirectRequestAction(303, "").HTTP().ServeHTTP)
mux.HandleFunc("/redirect1", NewRedirectRequestAction(http.StatusFound, "/redirect2").HTTP().ServeHTTP)
mux.HandleFunc("/redirect2", NewRedirectRequestAction(http.StatusFound, "/redirected").HTTP().ServeHTTP)
mux.HandleFunc("/redirect-default-status", newRedirectRequestAction(100, "/redirected").HTTP().ServeHTTP)
mux.HandleFunc("/redirect-no-location", newRedirectRequestAction(303, "").HTTP().ServeHTTP)
mux.HandleFunc("/redirect1", newRedirectRequestAction(http.StatusFound, "/redirect2").HTTP().ServeHTTP)
mux.HandleFunc("/redirect2", newRedirectRequestAction(http.StatusFound, "/redirected").HTTP().ServeHTTP)
mux.HandleFunc("/redirected", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK) // Shouldn't matter since we write 302 before arriving here
w.Write([]byte("Redirected"))
Expand Down
3 changes: 1 addition & 2 deletions internal/appsec/listener/graphqlsec/graphql.go
Expand Up @@ -14,7 +14,6 @@ import (
"gopkg.in/DataDog/dd-trace-go.v1/internal/appsec/config"
"gopkg.in/DataDog/dd-trace-go.v1/internal/appsec/dyngo"
"gopkg.in/DataDog/dd-trace-go.v1/internal/appsec/emitter/graphqlsec/types"
"gopkg.in/DataDog/dd-trace-go.v1/internal/appsec/emitter/sharedsec"
"gopkg.in/DataDog/dd-trace-go.v1/internal/appsec/listener"
shared "gopkg.in/DataDog/dd-trace-go.v1/internal/appsec/listener/sharedsec"
"gopkg.in/DataDog/dd-trace-go.v1/internal/appsec/trace"
Expand All @@ -33,7 +32,7 @@ var supportedAddresses = listener.AddressSet{
}

// Install registers the GraphQL WAF Event Listener on the given root operation.
func Install(wafHandle *waf.Handle, _ sharedsec.Actions, cfg *config.Config, lim limiter.Limiter, root dyngo.Operation) {
func Install(wafHandle *waf.Handle, cfg *config.Config, lim limiter.Limiter, root dyngo.Operation) {
if listener := newWafEventListener(wafHandle, cfg, lim); listener != nil {
log.Debug("appsec: registering the GraphQL WAF Event Listener")
dyngo.On(root, listener.onEvent)
Expand Down
13 changes: 6 additions & 7 deletions internal/appsec/listener/grpcsec/grpc.go
Expand Up @@ -44,8 +44,8 @@ var supportedAddresses = listener.AddressSet{
}

// Install registers the gRPC WAF Event Listener on the given root operation.
func Install(wafHandle *waf.Handle, actions sharedsec.Actions, cfg *config.Config, lim limiter.Limiter, root dyngo.Operation) {
if listener := newWafEventListener(wafHandle, actions, cfg, lim); listener != nil {
func Install(wafHandle *waf.Handle, cfg *config.Config, lim limiter.Limiter, root dyngo.Operation) {
if listener := newWafEventListener(wafHandle, cfg, lim); listener != nil {
log.Debug("appsec: registering the gRPC WAF Event Listener")
dyngo.On(root, listener.onEvent)
}
Expand All @@ -61,7 +61,7 @@ type wafEventListener struct {
once sync.Once
}

func newWafEventListener(wafHandle *waf.Handle, actions sharedsec.Actions, cfg *config.Config, limiter limiter.Limiter) *wafEventListener {
func newWafEventListener(wafHandle *waf.Handle, cfg *config.Config, limiter limiter.Limiter) *wafEventListener {
if wafHandle == nil {
log.Debug("appsec: no WAF Handle available, the gRPC WAF Event Listener will not be registered")
return nil
Expand All @@ -76,7 +76,6 @@ func newWafEventListener(wafHandle *waf.Handle, actions sharedsec.Actions, cfg *
return &wafEventListener{
wafHandle: wafHandle,
config: cfg,
actions: actions,
addresses: addresses,
limiter: limiter,
wafDiags: wafHandle.Diagnostics(),
Expand Down Expand Up @@ -114,8 +113,8 @@ func (l *wafEventListener) onEvent(op *types.HandlerOperation, handlerArgs types
}
wafResult := shared.RunWAF(wafCtx, waf.RunAddressData{Persistent: values}, l.config.WAFTimeout)
if wafResult.HasActions() || wafResult.HasEvents() {
for _, id := range wafResult.Actions {
if a, ok := l.actions[id]; ok && a.Blocking() {
for aType, params := range wafResult.Actions {
if a := shared.ActionFromEntry(aType, params); a != nil && a.Blocking() {
code, err := a.GRPC()(map[string][]string{})
dyngo.EmitData(userIDOp, types.NewMonitoringError(err.Error(), code))
}
Expand All @@ -137,7 +136,7 @@ func (l *wafEventListener) onEvent(op *types.HandlerOperation, handlerArgs types

wafResult := shared.RunWAF(wafCtx, waf.RunAddressData{Persistent: values}, l.config.WAFTimeout)
if wafResult.HasActions() || wafResult.HasEvents() {
interrupt := shared.ProcessActions(op, l.actions, wafResult.Actions)
interrupt := shared.ProcessActions(op, wafResult.Actions)
shared.AddSecurityEvents(op, l.limiter, wafResult.Events)
log.Debug("appsec: WAF detected an attack before executing the request")
if interrupt {
Expand Down