From 95eaa3d55f230b687644daecd24c08579d5226ed Mon Sep 17 00:00:00 2001 From: Jacob Bednarz Date: Mon, 5 Jul 2021 15:10:52 +1000 Subject: [PATCH] rulesets: add more WAF coverage Updates rulesets to extend into the WAF specific actions and action parameters. --- rulesets.go | 129 ++++++++++++++++++++++++++++------------------- rulesets_test.go | 89 +++++++++++++++++++++++++++++++- 2 files changed, 164 insertions(+), 54 deletions(-) diff --git a/rulesets.go b/rulesets.go index a9396f600bd..f00ff235b63 100644 --- a/rulesets.go +++ b/rulesets.go @@ -11,59 +11,41 @@ import ( ) const ( - // RulesetKindRoot is definition for an account level ruleset. - RulesetKindRoot RulesetKind = "root" - - // RulesetKindCustom is the user defined rulesets. - RulesetKindCustom RulesetKind = "custom" - - // RulesetKindManaged denotes Cloudflare managed rulesets. + RulesetKindCustom RulesetKind = "custom" RulesetKindManaged RulesetKind = "managed" + RulesetKindRoot RulesetKind = "root" + RulesetKindSchema RulesetKind = "schema" + RulesetKindZone RulesetKind = "zone" - // RulesetKindSchema denotes a schema ruleset. - RulesetKindSchema RulesetKind = "schema" - - // RulesetKindZone expresses a zone level ruleset. - RulesetKindZone RulesetKind = "zone" - - // RulesetPhaseDDoSL7 phase runs during DDoS mitigation stage. - RulesetPhaseDDoSL7 RulesetPhase = "ddos_l7" - - // RulesetPhaseMagicTransit phase is invoked when traffic is routed via Magic - // Transit. - RulesetPhaseMagicTransit RulesetPhase = "magic_transit" - - // RulesetPhaseHTTPRequestMain runs in the primary part of the HTTP request. - RulesetPhaseHTTPRequestMain RulesetPhase = "http_request_main" - - // RulesetPhaseHTTPRequestFirewallCustom runs on custom firewall rulesets. - RulesetPhaseHTTPRequestFirewallCustom RulesetPhase = "http_request_firewall_custom" - - // RulesetPhaseHTTPRequestFirewallManaged runs for Cloudflare managed rulesets. + RulesetPhaseDDoSL7 RulesetPhase = "ddos_l7" + RulesetPhaseHTTPRequestFirewallCustom RulesetPhase = "http_request_firewall_custom" RulesetPhaseHTTPRequestFirewallManaged RulesetPhase = "http_request_firewall_managed" - - // RulesetPhaseHTTPRequestTransform is performed at the HTTP request - // transformation phase. - RulesetPhaseHTTPRequestTransform RulesetPhase = "http_request_transform" - - // RulesetPhaseHTTPRequestSanitize is run during the HTTP request sanitisation - // phase. - RulesetPhaseHTTPRequestSanitize RulesetPhase = "http_request_sanitize" - - // RulesetRuleActionSkip represents the "skip" action. - RulesetRuleActionSkip RulesetRuleAction = "skip" - - // RulesetRuleActionBlock represents the "block" action. - RulesetRuleActionBlock RulesetRuleAction = "block" - - // RulesetRuleActionJSChallenge represents the "js_challenge" action. - RulesetRuleActionJSChallenge RulesetRuleAction = "js_challenge" - - // RulesetRuleActionChallenge represents the "challenge" action. - RulesetRuleActionChallenge RulesetRuleAction = "challenge" - - // RulesetRuleActionLog represents the "log" action. - RulesetRuleActionLog RulesetRuleAction = "log" + RulesetPhaseHTTPRequestMain RulesetPhase = "http_request_main" + RulesetPhaseHTTPRequestSanitize RulesetPhase = "http_request_sanitize" + RulesetPhaseHTTPRequestTransform RulesetPhase = "http_request_transform" + RulesetPhaseMagicTransit RulesetPhase = "magic_transit" + + RulesetRuleActionBlock RulesetRuleAction = "block" + RulesetRuleActionChallenge RulesetRuleAction = "challenge" + RulesetRuleActionDDoSDynamic RulesetRuleAction = "ddos_dynamic" + RulesetRuleActionExecute RulesetRuleAction = "execute" + RulesetRuleActionForceConnectionClose RulesetRuleAction = "force_connection_close" + RulesetRuleActionJSChallenge RulesetRuleAction = "js_challenge" + RulesetRuleActionLog RulesetRuleAction = "log" + RulesetRuleActionRewrite RulesetRuleAction = "rewrite" + RulesetRuleActionScore RulesetRuleAction = "score" + RulesetRuleActionSkip RulesetRuleAction = "skip" + + RulesetActionParameterProductBIC RulesetActionParameterProduct = "bic" + RulesetActionParameterProductHOT RulesetActionParameterProduct = "hot" + RulesetActionParameterProductRateLimit RulesetActionParameterProduct = "ratelimit" + RulesetActionParameterProductSecurityLevel RulesetActionParameterProduct = "securityLevel" + RulesetActionParameterProductUABlock RulesetActionParameterProduct = "uablock" + RulesetActionParameterProductWAF RulesetActionParameterProduct = "waf" + RulesetActionParameterProductZoneLockdown RulesetActionParameterProduct = "zonelockdown" + + RulesetRuleActionParametersHTTPHeaderOperationRemove RulesetRuleActionParametersHTTPHeaderOperation = "remove" + RulesetRuleActionParametersHTTPHeaderOperationSet RulesetRuleActionParametersHTTPHeaderOperation = "set" ) // RulesetRuleAction defines a custom type that is used to express allowed @@ -74,15 +56,21 @@ type RulesetRuleAction string type RulesetKind string // RulesetPhase is the custom type for defining at what point the ruleset will -// be applied and limited to expected values. +// be applied in the request pipeline. type RulesetPhase string +type RulesetActionParameterProduct string + +// RulesetRuleActionParametersHTTPHeaderOperation defines available options for +// HTTP header operations in actions. +type RulesetRuleActionParametersHTTPHeaderOperation string + // Ruleset contains the structure of a Ruleset. type Ruleset struct { ID string `json:"id,omitempty"` Name string `json:"name"` Description string `json:"description"` - Kind string `json:"kind"` + Kind RulesetKind `json:"kind"` Version string `json:"version,omitempty"` LastUpdated *time.Time `json:"last_updated,omitempty"` Phase RulesetPhase `json:"phase"` @@ -92,7 +80,40 @@ type Ruleset struct { // RulesetRuleActionParameters specifies the action parameters for a Ruleset // rule. type RulesetRuleActionParameters struct { - Ruleset string `json:"ruleset,omitempty"` + ID string `json:"id,omitempty"` + Ruleset string `json:"ruleset,omitempty"` + Increment int `json:"increment,omitempty"` + URI RulesetRuleActionParametersURI `json:"uri,omitempty"` + Headers map[string]RulesetRuleActionParametersHTTPHeader `json:"headers,omitempty"` + Products []RulesetActionParameterProduct `json:"products,omitempty"` +} + +// RulesetRuleActionParametersURI holds the URI struct for an action parameter. +type RulesetRuleActionParametersURI struct { + Path RulesetRuleActionParametersURIPath `json:"path,omitempty"` + Query RulesetRuleActionParametersURIQuery `json:"query,omitempty"` + Origin bool `json:"origin,omitempty"` +} + +// RulesetRuleActionParametersURIPath holds the path specific portion of a URI +// action parameter. +type RulesetRuleActionParametersURIPath struct { + Expression string `json:"expression,omitempty"` +} + +// RulesetRuleActionParametersURIQuery holds the query specific portion of a URI +// action parameter. +type RulesetRuleActionParametersURIQuery struct { + Value string `json:"value,omitempty"` + Expression string `json:"expression,omitempty"` +} + +// RulesetRuleActionParametersHTTPHeader is the definition for define action +// parameters that involve HTTP headers. +type RulesetRuleActionParametersHTTPHeader struct { + Operation string `json:"operation,omitempty"` + Value string `json:"value,omitempty"` + Expression string `json:"expression,omitempty"` } // RulesetRule contains information about a single Ruleset Rule. @@ -106,6 +127,8 @@ type RulesetRule struct { LastUpdated *time.Time `json:"last_updated,omitempty"` Ref string `json:"ref,omitempty"` Enabled bool `json:"enabled"` + Categories []string `json:"categories,omitempty"` + ScoreThreshold int `json:"score_threshold,omitempty"` } // UpdateRulesetRequest is the representation of a Ruleset update. diff --git a/rulesets_test.go b/rulesets_test.go index d0b2118f4f9..291b0b30738 100644 --- a/rulesets_test.go +++ b/rulesets_test.go @@ -63,7 +63,7 @@ func TestListRulesets(t *testing.T) { } } -func TestGetRuleset(t *testing.T) { +func TestGetRuleset_MagicTransit(t *testing.T) { setup() defer teardown() @@ -112,6 +112,93 @@ func TestGetRuleset(t *testing.T) { } } +func TestGetRuleset_WAF(t *testing.T) { + setup() + defer teardown() + + handler := func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, http.MethodGet, r.Method, "Expected method 'GET', got %s", r.Method) + w.Header().Set("content-type", "application/json") + fmt.Fprint(w, `{ + "result": { + "id": "70339d97bdb34195bbf054b1ebe81f76", + "name": "Cloudflare Normalization Ruleset", + "description": "Created by the Cloudflare security team, this ruleset provides normalization on the URL path", + "kind": "managed", + "version": "1", + "rules": [ + { + "id": "78723a9e0c7c4c6dbec5684cb766231d", + "version": "1", + "action": "rewrite", + "action_parameters": { + "uri": { + "path": { + "expression": "normalize_url_path(raw.http.request.uri.path)" + }, + "origin": false + } + }, + "description": "Normalization on the URL path, without propagating it to the origin", + "last_updated": "2020-12-18T09:28:09.655749Z", + "ref": "272936dc447b41fe976255ff6b768ec0", + "enabled": true + } + ], + "last_updated": "2020-12-18T09:28:09.655749Z", + "phase": "http_request_sanitize" + }, + "success": true, + "errors": [], + "messages": [] + }`) + } + + mux.HandleFunc("/accounts/"+testAccountID+"/rulesets/b232b534beea4e00a21dcbb7a8a545e9", handler) + mux.HandleFunc("/zones/"+testZoneID+"/rulesets/b232b534beea4e00a21dcbb7a8a545e9", handler) + + lastUpdated, _ := time.Parse(time.RFC3339, "2020-12-18T09:28:09.655749Z") + + rules := []RulesetRule{{ + ID: "78723a9e0c7c4c6dbec5684cb766231d", + Version: "1", + Action: RulesetRuleActionRewrite, + ActionParameters: &RulesetRuleActionParameters{ + URI: RulesetRuleActionParametersURI{ + Path: RulesetRuleActionParametersURIPath{ + Expression: "normalize_url_path(raw.http.request.uri.path)", + }, + Origin: false, + }, + }, + Description: "Normalization on the URL path, without propagating it to the origin", + LastUpdated: &lastUpdated, + Ref: "272936dc447b41fe976255ff6b768ec0", + Enabled: true, + }} + + want := Ruleset{ + ID: "70339d97bdb34195bbf054b1ebe81f76", + Name: "Cloudflare Normalization Ruleset", + Description: "Created by the Cloudflare security team, this ruleset provides normalization on the URL path", + Kind: RulesetKindManaged, + Version: "1", + LastUpdated: &lastUpdated, + Phase: RulesetPhaseHTTPRequestSanitize, + Rules: rules, + } + + zoneActual, err := client.GetZoneRuleset(context.Background(), testZoneID, "b232b534beea4e00a21dcbb7a8a545e9") + if assert.NoError(t, err) { + assert.Equal(t, want, zoneActual) + } + + accountActual, err := client.GetAccountRuleset(context.Background(), testAccountID, "b232b534beea4e00a21dcbb7a8a545e9") + if assert.NoError(t, err) { + assert.Equal(t, want, accountActual) + } +} + func TestCreateRuleset(t *testing.T) { setup() defer teardown()