diff --git a/rulesets.go b/rulesets.go index 0d3894a36..65be6a5bb 100644 --- a/rulesets.go +++ b/rulesets.go @@ -20,6 +20,7 @@ const ( RulesetPhaseDDoSL4 RulesetPhase = "ddos_l4" RulesetPhaseDDoSL7 RulesetPhase = "ddos_l7" RulesetPhaseHTTPLogCustomFields RulesetPhase = "http_log_custom_fields" + RulesetPhaseHTTPRequestCacheSettings RulesetPhase = "http_request_cache_settings" RulesetPhaseHTTPRequestFirewallCustom RulesetPhase = "http_request_firewall_custom" RulesetPhaseHTTPRequestFirewallManaged RulesetPhase = "http_request_firewall_managed" RulesetPhaseHTTPRequestLateTransform RulesetPhase = "http_request_late_transform" @@ -47,6 +48,7 @@ const ( RulesetRuleActionScore RulesetRuleAction = "score" RulesetRuleActionSkip RulesetRuleAction = "skip" RulesetRuleActionRoute RulesetRuleAction = "route" + RulesetRuleActionSetCacheSettings RulesetRuleAction = "set_cache_settings" RulesetActionParameterProductBIC RulesetActionParameterProduct = "bic" RulesetActionParameterProductHOT RulesetActionParameterProduct = "hot" @@ -79,6 +81,7 @@ func RulesetPhaseValues() []string { string(RulesetPhaseDDoSL4), string(RulesetPhaseDDoSL7), string(RulesetPhaseHTTPLogCustomFields), + string(RulesetPhaseHTTPRequestCacheSettings), string(RulesetPhaseHTTPRequestFirewallCustom), string(RulesetPhaseHTTPRequestFirewallManaged), string(RulesetPhaseHTTPRequestLateTransform), @@ -112,6 +115,7 @@ func RulesetRuleActionValues() []string { string(RulesetRuleActionScore), string(RulesetRuleActionSkip), string(RulesetRuleActionRoute), + string(RulesetRuleActionSetCacheSettings), } } @@ -181,24 +185,125 @@ type RulesetActionParametersLogCustomField struct { // RulesetRuleActionParameters specifies the action parameters for a Ruleset // rule. type RulesetRuleActionParameters struct { - ID string `json:"id,omitempty"` - Ruleset string `json:"ruleset,omitempty"` - Rulesets []string `json:"rulesets,omitempty"` - Rules map[string][]string `json:"rules,omitempty"` - Increment int `json:"increment,omitempty"` - URI *RulesetRuleActionParametersURI `json:"uri,omitempty"` - Headers map[string]RulesetRuleActionParametersHTTPHeader `json:"headers,omitempty"` - Products []string `json:"products,omitempty"` - Phases []string `json:"phases,omitempty"` - Overrides *RulesetRuleActionParametersOverrides `json:"overrides,omitempty"` - MatchedData *RulesetRuleActionParametersMatchedData `json:"matched_data,omitempty"` - Version string `json:"version,omitempty"` - Response *RulesetRuleActionParametersBlockResponse `json:"response,omitempty"` - HostHeader string `json:"host_header,omitempty"` - Origin *RulesetRuleActionParametersOrigin `json:"origin,omitempty"` - RequestFields []RulesetActionParametersLogCustomField `json:"request_fields,omitempty"` - ResponseFields []RulesetActionParametersLogCustomField `json:"response_fields,omitempty"` - CookieFields []RulesetActionParametersLogCustomField `json:"cookie_fields,omitempty"` + ID string `json:"id,omitempty"` + Ruleset string `json:"ruleset,omitempty"` + Rulesets []string `json:"rulesets,omitempty"` + Rules map[string][]string `json:"rules,omitempty"` + Increment int `json:"increment,omitempty"` + URI *RulesetRuleActionParametersURI `json:"uri,omitempty"` + Headers map[string]RulesetRuleActionParametersHTTPHeader `json:"headers,omitempty"` + Products []string `json:"products,omitempty"` + Phases []string `json:"phases,omitempty"` + Overrides *RulesetRuleActionParametersOverrides `json:"overrides,omitempty"` + MatchedData *RulesetRuleActionParametersMatchedData `json:"matched_data,omitempty"` + Version string `json:"version,omitempty"` + Response *RulesetRuleActionParametersBlockResponse `json:"response,omitempty"` + HostHeader string `json:"host_header,omitempty"` + Origin *RulesetRuleActionParametersOrigin `json:"origin,omitempty"` + RequestFields []RulesetActionParametersLogCustomField `json:"request_fields,omitempty"` + ResponseFields []RulesetActionParametersLogCustomField `json:"response_fields,omitempty"` + CookieFields []RulesetActionParametersLogCustomField `json:"cookie_fields,omitempty"` + BypassCache *bool `json:"bypass_cache,omitempty"` + EdgeTTL *RulesetRuleActionParametersEdgeTTL `json:"edge_ttl,omitempty"` + BrowserTTL *RulesetRuleActionParametersBrowserTTL `json:"browser_ttl,omitempty"` + ServeStale *RulesetRuleActionParametersServeStale `json:"serve_stale,omitempty"` + RespectStrongETags *bool `json:"respect_strong_etags,omitempty"` + CacheKey *RulesetRuleActionParametersCacheKey `json:"cache_key,omitempty"` + OriginErrorPagePassthru *bool `json:"origin_error_page_passthru,omitempty"` +} + +type RulesetRuleActionParametersEdgeTTL struct { + Mode string `json:"mode,omitempty"` + Default *uint `json:"default,omitempty"` + StatusCodeTTL []RulesetRuleActionParametersStatusCodeTTL `json:"status_code_ttl,omitempty"` +} + +type RulesetRuleActionParametersStatusCodeTTL struct { + StatusCodeRange *RulesetRuleActionParametersStatusCodeRange `json:"status_code_range,omitempty"` + StatusCodeValue *uint `json:"status_code,omitempty"` + Value *int `json:"value,omitempty"` +} + +type RulesetRuleActionParametersStatusCodeRange struct { + From *uint `json:"from,omitempty"` + To *uint `json:"to,omitempty"` +} + +type RulesetRuleActionParametersBrowserTTL struct { + Mode string `json:"mode"` + Default *uint `json:"default,omitempty"` +} + +type RulesetRuleActionParametersServeStale struct { + DisableStaleWhileUpdating *bool `json:"disable_stale_while_updating,omitempty"` +} + +type RulesetRuleActionParametersCacheKey struct { + CacheByDeviceType *bool `json:"cache_by_device_type,omitempty"` + IgnoreQueryStringsOrder *bool `json:"ignore_query_strings_order,omitempty"` + CacheDeceptionArmor *bool `json:"cache_deception_armor,omitempty"` + CustomKey *RulesetRuleActionParametersCustomKey `json:"custom_key,omitempty"` +} + +type RulesetRuleActionParametersCustomKey struct { + Query *RulesetRuleActionParametersCustomKeyQuery `json:"query_string,omitempty"` + Header *RulesetRuleActionParametersCustomKeyHeader `json:"header,omitempty"` + Cookie *RulesetRuleActionParametersCustomKeyCookie `json:"cookie,omitempty"` + User *RulesetRuleActionParametersCustomKeyUser `json:"user,omitempty"` + Host *RulesetRuleActionParametersCustomKeyHost `json:"host,omitempty"` +} + +type RulesetRuleActionParametersCustomKeyHeader struct { + RulesetRuleActionParametersCustomKeyFields + ExcludeOrigin *bool `json:"exclude_origin,omitempty"` +} + +type RulesetRuleActionParametersCustomKeyCookie RulesetRuleActionParametersCustomKeyFields + +type RulesetRuleActionParametersCustomKeyFields struct { + Include []string `json:"include,omitempty"` + CheckPresence []string `json:"check_presence,omitempty"` +} + +type RulesetRuleActionParametersCustomKeyQuery struct { + Include *RulesetRuleActionParametersCustomKeyList `json:"include,omitempty"` + Exclude *RulesetRuleActionParametersCustomKeyList `json:"exclude,omitempty"` +} + +type RulesetRuleActionParametersCustomKeyList struct { + List []string + All bool +} + +func (s *RulesetRuleActionParametersCustomKeyList) UnmarshalJSON(data []byte) error { + var all string + if err := json.Unmarshal(data, &all); err == nil { + s.All = all == "*" + return nil + } + var list []string + if err := json.Unmarshal(data, &list); err == nil { + s.List = list + } + + return nil +} + +func (s RulesetRuleActionParametersCustomKeyList) MarshalJSON() ([]byte, error) { + if s.All { + return json.Marshal("*") + } + return json.Marshal(s.List) +} + +type RulesetRuleActionParametersCustomKeyUser struct { + DeviceType *bool `json:"device_type,omitempty"` + Geo *bool `json:"geo,omitempty"` + Lang *bool `json:"lang,omitempty"` +} + +type RulesetRuleActionParametersCustomKeyHost struct { + Resolved *bool `json:"resolved,omitempty"` } // RulesetRuleActionParametersBlockResponse holds the BlockResponse struct diff --git a/rulesets_test.go b/rulesets_test.go index a82eb7a0d..498625e57 100644 --- a/rulesets_test.go +++ b/rulesets_test.go @@ -199,6 +199,161 @@ func TestGetRuleset_WAF(t *testing.T) { } } +func TestGetRuleset_SetCacheSettings(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 Cache Rules Ruleset", + "description": "This ruleset provides cache settings modifications", + "kind": "zone", + "version": "1", + "rules": [ + { + "id": "78723a9e0c7c4c6dbec5684cb766231d", + "version": "1", + "action": "set_cache_settings", + "action_parameters": { + "bypass_cache": false, + "edge_ttl":{"mode":"respect_origin","default":60,"status_code_ttl":[{"status_code":200,"value":30},{"status_code_range":{"from":201,"to":300},"value":20}]}, + "browser_ttl":{"mode":"override_origin","default":10}, + "serve_stale":{"disable_stale_while_updating":true}, + "respect_strong_etags":true, + "cache_key":{ + "cache_deception_armor":true, + "ignore_query_strings_order":true, + "custom_key": { + "query_string":{"include":"*"}, + "header":{"include":["habc","hdef"],"check_presence":["hfizz","hbuzz"],"exclude_origin":true}, + "cookie":{"include":["cabc","cdef"],"check_presence":["cfizz","cbuzz"]}, + "user":{ + "device_type":true, + "geo":true, + "lang":true + }, + "host":{ + "resolved":true + } + } + }, + "origin_error_page_passthru":true + }, + "description": "Set all available cache settings in one rule", + "last_updated": "2020-12-18T09:28:09.655749Z", + "ref": "272936dc447b41fe976255ff6b768ec0", + "enabled": true + } + ], + "last_updated": "2020-12-18T09:28:09.655749Z", + "phase": "http_request_cache_settings" + }, + "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: string(RulesetRuleActionSetCacheSettings), + ActionParameters: &RulesetRuleActionParameters{ + BypassCache: BoolPtr(false), + EdgeTTL: &RulesetRuleActionParametersEdgeTTL{ + Mode: "respect_origin", + Default: UintPtr(60), + StatusCodeTTL: []RulesetRuleActionParametersStatusCodeTTL{ + { + StatusCodeValue: UintPtr(200), + Value: IntPtr(30), + }, + { + StatusCodeRange: &RulesetRuleActionParametersStatusCodeRange{ + From: UintPtr(201), + To: UintPtr(300), + }, + Value: IntPtr(20), + }, + }, + }, + BrowserTTL: &RulesetRuleActionParametersBrowserTTL{ + Mode: "override_origin", + Default: UintPtr(10), + }, + ServeStale: &RulesetRuleActionParametersServeStale{ + DisableStaleWhileUpdating: BoolPtr(true), + }, + RespectStrongETags: BoolPtr(true), + CacheKey: &RulesetRuleActionParametersCacheKey{ + IgnoreQueryStringsOrder: BoolPtr(true), + CacheDeceptionArmor: BoolPtr(true), + CustomKey: &RulesetRuleActionParametersCustomKey{ + Query: &RulesetRuleActionParametersCustomKeyQuery{ + Include: &RulesetRuleActionParametersCustomKeyList{ + All: true, + }, + }, + Header: &RulesetRuleActionParametersCustomKeyHeader{ + RulesetRuleActionParametersCustomKeyFields: RulesetRuleActionParametersCustomKeyFields{ + Include: []string{"habc", "hdef"}, + CheckPresence: []string{"hfizz", "hbuzz"}, + }, + ExcludeOrigin: BoolPtr(true), + }, + Cookie: &RulesetRuleActionParametersCustomKeyCookie{ + Include: []string{"cabc", "cdef"}, + CheckPresence: []string{"cfizz", "cbuzz"}, + }, + User: &RulesetRuleActionParametersCustomKeyUser{ + DeviceType: BoolPtr(true), + Geo: BoolPtr(true), + Lang: BoolPtr(true), + }, + Host: &RulesetRuleActionParametersCustomKeyHost{ + Resolved: BoolPtr(true), + }, + }, + }, + OriginErrorPagePassthru: BoolPtr(true), + }, + Description: "Set all available cache settings in one rule", + LastUpdated: &lastUpdated, + Ref: "272936dc447b41fe976255ff6b768ec0", + Enabled: true, + }} + + want := Ruleset{ + ID: "70339d97bdb34195bbf054b1ebe81f76", + Name: "Cloudflare Cache Rules Ruleset", + Description: "This ruleset provides cache settings modifications", + Kind: string(RulesetKindZone), + Version: "1", + LastUpdated: &lastUpdated, + Phase: string(RulesetPhaseHTTPRequestCacheSettings), + 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()