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

Add support for create/update individual ruleset rules #1744

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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .changelog/1744.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:enhancement
rulesets: Add support for create/update calls for individual ruleset rules
```
50 changes: 50 additions & 0 deletions rulesets.go
Original file line number Diff line number Diff line change
Expand Up @@ -733,6 +733,14 @@ type UpdateRulesetParams struct {
Rules []RulesetRule `json:"rules"`
}

type CreateRulesetRuleParams struct {
RulesetRule
}

type UpdateRulesetRuleParams struct {
RulesetRule
}

type UpdateEntrypointRulesetParams struct {
Phase string `json:"-"`
Description string `json:"description,omitempty"`
Expand Down Expand Up @@ -842,6 +850,48 @@ func (api *API) UpdateRuleset(ctx context.Context, rc *ResourceContainer, params
return result.Result, nil
}

// CreateRulesetRule creates a new ruleset rule.
//
// API reference: https://developers.cloudflare.com/api/operations/createAccountRulesetRule
// API reference: https://developers.cloudflare.com/api/operations/createZoneRulesetRule
func (api *API) CreateRulesetRule(ctx context.Context, rc *ResourceContainer, rulesetID string, params CreateRulesetRuleParams) (Ruleset, error) {
uri := fmt.Sprintf("/%s/%s/rulesets/%s/rules", rc.Level, rc.Identifier, rulesetID)
res, err := api.makeRequestContext(ctx, http.MethodPost, uri, params)
if err != nil {
return Ruleset{}, err
}

result := CreateRulesetResponse{}
if err := json.Unmarshal(res, &result); err != nil {
return Ruleset{}, fmt.Errorf("%s: %w", errUnmarshalError, err)
}

return result.Result, nil
}

// UpdateRulesetRule updates a ruleset rule based on the ruleset and rule ID.
//
// API reference: https://developers.cloudflare.com/api/operations/updateAccountRulesetRule
// API reference: https://developers.cloudflare.com/api/operations/updateZoneRulesetRule
func (api *API) UpdateRulesetRule(ctx context.Context, rc *ResourceContainer, rulesetID string, params UpdateRulesetRuleParams) (Ruleset, error) {
if params.ID == "" {
return Ruleset{}, ErrMissingResourceIdentifier
}

uri := fmt.Sprintf("/%s/%s/rulesets/%s/rules/%s", rc.Level, rc.Identifier, rulesetID, params.ID)
res, err := api.makeRequestContext(ctx, http.MethodPatch, uri, params)
if err != nil {
return Ruleset{}, err
}

result := UpdateRulesetResponse{}
if err := json.Unmarshal(res, &result); err != nil {
return Ruleset{}, fmt.Errorf("%s: %w", errUnmarshalError, err)
}

return result.Result, nil
}

// GetEntrypointRuleset returns an entry point ruleset base on the phase.
//
// API reference: https://developers.cloudflare.com/api/operations/getAccountEntrypointRuleset
Expand Down
164 changes: 164 additions & 0 deletions rulesets_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -864,3 +864,167 @@ func TestUpdateRuleset(t *testing.T) {
assert.Equal(t, want, accountActual)
}
}

func TestUpdateRulesetRule(t *testing.T) {
setup()
defer teardown()

handler := func(w http.ResponseWriter, r *http.Request) {
assert.Equal(t, http.MethodPatch, r.Method, "Expected method 'PUT', got %s", r.Method)
w.Header().Set("content-type", "application/json")
fmt.Fprint(w, `{
"result": {
"id": "2c0fc9fa937b11eaa1b71c4d701ab86e",
"name": "ruleset1",
"description": "Test Firewall Ruleset Update",
"kind": "root",
"version": "1",
"last_updated": "2020-12-02T20:24:07.776073Z",
"phase": "magic_transit",
"rules": [
{
"id": "62449e2e0de149619edb35e59c10d802",
"version": "1",
"action": "skip",
"action_parameters":{
"ruleset":"current"
},
"expression": "udp.dstport in { 32768..65535 }",
"description": "Allow UDP Ephemeral Ports",
"last_updated": "2020-12-02T20:24:07.776073Z",
"ref": "72449e2e0de149619edb35e59c10d801",
"enabled": true
}
]
},
"success": true,
"errors": [],
"messages": []
}`)
}

mux.HandleFunc("/accounts/"+testAccountID+"/rulesets/2c0fc9fa937b11eaa1b71c4d701ab86e/rules/62449e2e0de149619edb35e59c10d802", handler)
mux.HandleFunc("/zones/"+testZoneID+"/rulesets/2c0fc9fa937b11eaa1b71c4d701ab86e/rules/62449e2e0de149619edb35e59c10d802", handler)

lastUpdated, _ := time.Parse(time.RFC3339, "2020-12-02T20:24:07.776073Z")

rule := RulesetRule{
ID: "62449e2e0de149619edb35e59c10d802",
Version: StringPtr("1"),
Action: string(RulesetRuleActionSkip),
ActionParameters: &RulesetRuleActionParameters{
Ruleset: "current",
},
Expression: "udp.dstport in { 32768..65535 }",
Description: "Allow UDP Ephemeral Ports",
LastUpdated: &lastUpdated,
Ref: "72449e2e0de149619edb35e59c10d801",
Enabled: BoolPtr(true),
}

updated := UpdateRulesetRuleParams{
rule,
}

want := Ruleset{
ID: "2c0fc9fa937b11eaa1b71c4d701ab86e",
Name: "ruleset1",
Description: "Test Firewall Ruleset Update",
Kind: "root",
Version: StringPtr("1"),
LastUpdated: &lastUpdated,
Phase: string(RulesetPhaseMagicTransit),
Rules: []RulesetRule{rule},
}

zoneActual, err := client.UpdateRulesetRule(context.Background(), ZoneIdentifier(testZoneID), "2c0fc9fa937b11eaa1b71c4d701ab86e", updated)
if assert.NoError(t, err) {
assert.Equal(t, want, zoneActual)
}

accountActual, err := client.UpdateRulesetRule(context.Background(), AccountIdentifier(testAccountID), "2c0fc9fa937b11eaa1b71c4d701ab86e", updated)
if assert.NoError(t, err) {
assert.Equal(t, want, accountActual)
}
}

func TestCreateRulesetRule(t *testing.T) {
setup()
defer teardown()

handler := func(w http.ResponseWriter, r *http.Request) {
assert.Equal(t, http.MethodPost, r.Method, "Expected method 'POST', got %s", r.Method)
w.Header().Set("content-type", "application/json")
fmt.Fprint(w, `{
"result": {
"id": "2c0fc9fa937b11eaa1b71c4d701ab86e",
"name": "my example ruleset",
"description": "Test magic transit ruleset",
"kind": "root",
"version": "1",
"last_updated": "2020-12-02T20:24:07.776073Z",
"phase": "magic_transit",
"rules": [
{
"id": "62449e2e0de149619edb35e59c10d801",
"version": "1",
"action": "skip",
"action_parameters":{
"ruleset":"current"
},
"expression": "tcp.dstport in { 32768..65535 }",
"description": "Allow TCP Ephemeral Ports",
"last_updated": "2020-12-02T20:24:07.776073Z",
"ref": "72449e2e0de149619edb35e59c10d801",
"enabled": true
}
]
},
"success": true,
"errors": [],
"messages": []
}`)
}

mux.HandleFunc("/accounts/"+testAccountID+"/rulesets/2c0fc9fa937b11eaa1b71c4d701ab86e/rules", handler)
mux.HandleFunc("/zones/"+testZoneID+"/rulesets/2c0fc9fa937b11eaa1b71c4d701ab86e/rules", handler)

lastUpdated, _ := time.Parse(time.RFC3339, "2020-12-02T20:24:07.776073Z")

rule := RulesetRule{
ID: "62449e2e0de149619edb35e59c10d801",
Version: StringPtr("1"),
Action: string(RulesetRuleActionSkip),
ActionParameters: &RulesetRuleActionParameters{
Ruleset: "current",
},
Expression: "tcp.dstport in { 32768..65535 }",
Description: "Allow TCP Ephemeral Ports",
LastUpdated: &lastUpdated,
Ref: "72449e2e0de149619edb35e59c10d801",
Enabled: BoolPtr(true),
}

newRule := CreateRulesetRuleParams{rule}

want := Ruleset{
ID: "2c0fc9fa937b11eaa1b71c4d701ab86e",
Name: "my example ruleset",
Description: "Test magic transit ruleset",
Kind: "root",
Version: StringPtr("1"),
LastUpdated: &lastUpdated,
Phase: string(RulesetPhaseMagicTransit),
Rules: []RulesetRule{rule},
}

zoneActual, err := client.CreateRulesetRule(context.Background(), ZoneIdentifier(testZoneID), "2c0fc9fa937b11eaa1b71c4d701ab86e", newRule)
if assert.NoError(t, err) {
assert.Equal(t, want, zoneActual)
}

accountActual, err := client.CreateRulesetRule(context.Background(), AccountIdentifier(testAccountID), "2c0fc9fa937b11eaa1b71c4d701ab86e", newRule)
if assert.NoError(t, err) {
assert.Equal(t, want, accountActual)
}
}