Skip to content

Commit

Permalink
Merge pull request #1169 from cloudflare/add-uri-transformation-support
Browse files Browse the repository at this point in the history
resource/cloudflare_ruleset: add support for transform rules
  • Loading branch information
jacobbednarz authored Sep 10, 2021
2 parents 6d8b7cf + 6df6414 commit 9f0de28
Show file tree
Hide file tree
Showing 3 changed files with 419 additions and 4 deletions.
111 changes: 108 additions & 3 deletions cloudflare/resource_cloudflare_ruleset.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,11 +111,13 @@ func resourceCloudflareRuleset() *schema.Resource {
"uri": {
Type: schema.TypeList,
Optional: true,
MaxItems: 1,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"path": {
Type: schema.TypeList,
Optional: true,
MaxItems: 1,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"value": {
Expand All @@ -132,6 +134,7 @@ func resourceCloudflareRuleset() *schema.Resource {
"query": {
Type: schema.TypeList,
Optional: true,
MaxItems: 1,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"value": {
Expand All @@ -152,6 +155,30 @@ func resourceCloudflareRuleset() *schema.Resource {
},
},
},
"headers": {
Type: schema.TypeList,
Optional: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"name": {
Type: schema.TypeString,
Optional: true,
},
"value": {
Type: schema.TypeString,
Optional: true,
},
"expression": {
Type: schema.TypeString,
Optional: true,
},
"operation": {
Type: schema.TypeString,
Optional: true,
},
},
},
},
"increment": {
Type: schema.TypeInt,
Optional: true,
Expand Down Expand Up @@ -387,6 +414,8 @@ func buildStateFromRulesetRules(rules []cloudflare.RulesetRule) interface{} {
var overrides []map[string]interface{}
var idBasedOverrides []map[string]interface{}
var categoryBasedOverrides []map[string]interface{}
var headers []map[string]interface{}
var uri []map[string]interface{}

if !reflect.ValueOf(r.ActionParameters.Overrides).IsNil() {
for _, overrideRule := range r.ActionParameters.Overrides.Rules {
Expand All @@ -413,15 +442,54 @@ func buildStateFromRulesetRules(rules []cloudflare.RulesetRule) interface{} {
})
}

if !reflect.ValueOf(r.ActionParameters.Headers).IsNil() {
for headerName, header := range r.ActionParameters.Headers {
headers = append(headers, map[string]interface{}{
"name": headerName,
"value": header.Value,
"expression": header.Expression,
"operation": header.Operation,
})
}
}

if !reflect.ValueOf(r.ActionParameters.URI).IsNil() {
var query []map[string]interface{}
var path []map[string]interface{}
originValue := false
if r.ActionParameters.URI.Origin {
originValue = true
}

if !reflect.ValueOf(r.ActionParameters.URI.Query).IsNil() {
query = append(query, map[string]interface{}{
"value": r.ActionParameters.URI.Query.Value,
"expression": r.ActionParameters.URI.Query.Expression,
})
}

if !reflect.ValueOf(r.ActionParameters.URI.Path).IsNil() {
path = append(path, map[string]interface{}{
"value": r.ActionParameters.URI.Path.Value,
"expression": r.ActionParameters.URI.Path.Expression,
})
}

uri = append(uri, map[string]interface{}{
"origin": originValue,
"query": query,
"path": path,
})
}

actionParameters = append(actionParameters, map[string]interface{}{
"id": r.ActionParameters.ID,
"increment": r.ActionParameters.Increment,
// "headers": r.ActionParameters.Headers,
"headers": headers,
"overrides": overrides,
"products": r.ActionParameters.Products,
"ruleset": r.ActionParameters.Ruleset,
"uri": r.ActionParameters.URI,
// "version": r.ActionParameters.Version,
"uri": uri,
})

rule["action_parameters"] = actionParameters
Expand Down Expand Up @@ -498,6 +566,43 @@ func buildRulesetRulesFromResource(r interface{}) ([]cloudflare.RulesetRule, err
}
}

case "uri":
for _, uriValue := range pValue.([]interface{}) {
if val, ok := uriValue.(map[string]interface{})["path"]; ok && len(val.([]interface{})) > 0 {
uriPathConfig := val.([]interface{})[0].(map[string]interface{})
rule.ActionParameters.URI = &cloudflare.RulesetRuleActionParametersURI{
Path: &cloudflare.RulesetRuleActionParametersURIPath{
Value: uriPathConfig["value"].(string),
Expression: uriPathConfig["expression"].(string),
},
}
}

if val, ok := uriValue.(map[string]interface{})["query"]; ok && len(val.([]interface{})) > 0 {
uriQueryConfig := val.([]interface{})[0].(map[string]interface{})
rule.ActionParameters.URI = &cloudflare.RulesetRuleActionParametersURI{
Query: &cloudflare.RulesetRuleActionParametersURIQuery{
Value: uriQueryConfig["value"].(string),
Expression: uriQueryConfig["expression"].(string),
},
}
}
}

case "headers":
headers := make(map[string]cloudflare.RulesetRuleActionParametersHTTPHeader)
for _, headerList := range pValue.([]interface{}) {
name := headerList.(map[string]interface{})["name"].(string)

headers[name] = cloudflare.RulesetRuleActionParametersHTTPHeader{
Value: headerList.(map[string]interface{})["value"].(string),
Expression: headerList.(map[string]interface{})["expression"].(string),
Operation: headerList.(map[string]interface{})["operation"].(string),
}
}

rule.ActionParameters.Headers = headers

default:
log.Printf("[DEBUG] unknown key encountered in buildRulesetRulesFromResource for action parameters: %s", pKey)
}
Expand Down
217 changes: 217 additions & 0 deletions cloudflare/resource_cloudflare_ruleset_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -562,6 +562,134 @@ func TestAccCloudflareRuleset_MagicTransitUpdateWithHigherPriority(t *testing.T)
})
}

func TestAccCloudflareRuleset_TransformationRuleURIPath(t *testing.T) {
// Temporarily unset CLOUDFLARE_API_TOKEN if it is set as the WAF
// service does not yet support the API tokens and it results in
// misleading state error messages.
if os.Getenv("CLOUDFLARE_API_TOKEN") != "" {
defer func(apiToken string) {
os.Setenv("CLOUDFLARE_API_TOKEN", apiToken)
}(os.Getenv("CLOUDFLARE_API_TOKEN"))
os.Setenv("CLOUDFLARE_API_TOKEN", "")
}

t.Parallel()
rnd := generateRandomResourceName()
zoneID := os.Getenv("CLOUDFLARE_ZONE_ID")
zoneName := os.Getenv("CLOUDFLARE_DOMAIN")
resourceName := "cloudflare_ruleset." + rnd

resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
Steps: []resource.TestStep{
{
Config: testAccCheckCloudflareRulesetTransformationRuleURIPath(rnd, "transform rule for URI path", zoneID, zoneName),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr(resourceName, "name", "transform rule for URI path"),
resource.TestCheckResourceAttr(resourceName, "description", rnd+" ruleset description"),
resource.TestCheckResourceAttr(resourceName, "kind", "zone"),
resource.TestCheckResourceAttr(resourceName, "phase", "http_request_transform"),

resource.TestCheckResourceAttr(resourceName, "rules.#", "1"),
resource.TestCheckResourceAttr(resourceName, "rules.0.action", "rewrite"),
resource.TestCheckResourceAttr(resourceName, "rules.0.action_parameters.0.uri.#", "1"),
resource.TestCheckResourceAttr(resourceName, "rules.0.action_parameters.0.uri.0.path.0.value", "/static-rewrite"),
),
},
},
})
}

func TestAccCloudflareRuleset_TransformationRuleURIQuery(t *testing.T) {
// Temporarily unset CLOUDFLARE_API_TOKEN if it is set as the WAF
// service does not yet support the API tokens and it results in
// misleading state error messages.
if os.Getenv("CLOUDFLARE_API_TOKEN") != "" {
defer func(apiToken string) {
os.Setenv("CLOUDFLARE_API_TOKEN", apiToken)
}(os.Getenv("CLOUDFLARE_API_TOKEN"))
os.Setenv("CLOUDFLARE_API_TOKEN", "")
}

t.Parallel()
rnd := generateRandomResourceName()
zoneID := os.Getenv("CLOUDFLARE_ZONE_ID")
zoneName := os.Getenv("CLOUDFLARE_DOMAIN")
resourceName := "cloudflare_ruleset." + rnd

resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
Steps: []resource.TestStep{
{
Config: testAccCheckCloudflareRulesetTransformationRuleURIQuery(rnd, "transform rule for URI query", zoneID, zoneName),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr(resourceName, "name", "transform rule for URI query"),
resource.TestCheckResourceAttr(resourceName, "description", rnd+" ruleset description"),
resource.TestCheckResourceAttr(resourceName, "kind", "zone"),
resource.TestCheckResourceAttr(resourceName, "phase", "http_request_transform"),

resource.TestCheckResourceAttr(resourceName, "rules.#", "1"),
resource.TestCheckResourceAttr(resourceName, "rules.0.action", "rewrite"),
resource.TestCheckResourceAttr(resourceName, "rules.0.action_parameters.0.uri.#", "1"),
resource.TestCheckResourceAttr(resourceName, "rules.0.action_parameters.0.uri.0.query.0.value", "a=b"),
),
},
},
})
}

func TestAccCloudflareRuleset_TransformationRuleHeaders(t *testing.T) {
// Temporarily unset CLOUDFLARE_API_TOKEN if it is set as the WAF
// service does not yet support the API tokens and it results in
// misleading state error messages.
if os.Getenv("CLOUDFLARE_API_TOKEN") != "" {
defer func(apiToken string) {
os.Setenv("CLOUDFLARE_API_TOKEN", apiToken)
}(os.Getenv("CLOUDFLARE_API_TOKEN"))
os.Setenv("CLOUDFLARE_API_TOKEN", "")
}

t.Parallel()
rnd := generateRandomResourceName()
zoneID := os.Getenv("CLOUDFLARE_ZONE_ID")
zoneName := os.Getenv("CLOUDFLARE_DOMAIN")
resourceName := "cloudflare_ruleset." + rnd

resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
Steps: []resource.TestStep{
{
Config: testAccCheckCloudflareRulesetTransformationRuleHeaders(rnd, "transform rule for headers", zoneID, zoneName),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr(resourceName, "name", "transform rule for headers"),
resource.TestCheckResourceAttr(resourceName, "description", rnd+" ruleset description"),
resource.TestCheckResourceAttr(resourceName, "kind", "zone"),
resource.TestCheckResourceAttr(resourceName, "phase", "http_request_late_transform"),

resource.TestCheckResourceAttr(resourceName, "rules.#", "1"),
resource.TestCheckResourceAttr(resourceName, "rules.0.action", "rewrite"),

resource.TestCheckResourceAttr(resourceName, "rules.0.action_parameters.0.headers.#", "3"),

resource.TestCheckResourceAttr(resourceName, "rules.0.action_parameters.0.headers.0.name", "example1"),
resource.TestCheckResourceAttr(resourceName, "rules.0.action_parameters.0.headers.0.value", "my-http-header-value1"),
resource.TestCheckResourceAttr(resourceName, "rules.0.action_parameters.0.headers.0.operation", "set"),

resource.TestCheckResourceAttr(resourceName, "rules.0.action_parameters.0.headers.1.name", "example2"),
resource.TestCheckResourceAttr(resourceName, "rules.0.action_parameters.0.headers.1.operation", "set"),
resource.TestCheckResourceAttr(resourceName, "rules.0.action_parameters.0.headers.1.expression", "cf.zone.name"),

resource.TestCheckResourceAttr(resourceName, "rules.0.action_parameters.0.headers.2.name", "example3"),
resource.TestCheckResourceAttr(resourceName, "rules.0.action_parameters.0.headers.2.operation", "remove"),
),
},
},
})
}

func testAccCheckCloudflareRulesetMagicTransitSingle(rnd, name, accountID string) string {
return fmt.Sprintf(`
resource "cloudflare_ruleset" "%[1]s" {
Expand Down Expand Up @@ -977,3 +1105,92 @@ func testAccCheckCloudflareRulesetManagedWAFWithIDBasedOverrides(rnd, name, zone
}
}`, rnd, name, zoneID, zoneName)
}

func testAccCheckCloudflareRulesetTransformationRuleURIPath(rnd, name, zoneID, zoneName string) string {
return fmt.Sprintf(`
resource "cloudflare_ruleset" "%[1]s" {
zone_id = "%[3]s"
name = "%[2]s"
description = "%[1]s ruleset description"
kind = "zone"
phase = "http_request_transform"
rules {
action = "rewrite"
action_parameters {
uri {
path {
value = "/static-rewrite"
}
}
}
expression = "(http.host eq \"%[4]s\")"
description = "URI transformation path example"
enabled = false
}
}`, rnd, name, zoneID, zoneName)
}

func testAccCheckCloudflareRulesetTransformationRuleURIQuery(rnd, name, zoneID, zoneName string) string {
return fmt.Sprintf(`
resource "cloudflare_ruleset" "%[1]s" {
zone_id = "%[3]s"
name = "%[2]s"
description = "%[1]s ruleset description"
kind = "zone"
phase = "http_request_transform"
rules {
action = "rewrite"
action_parameters {
uri {
query {
value = "a=b"
}
}
}
expression = "(http.host eq \"%[4]s\")"
description = "URI transformation query example"
enabled = false
}
}`, rnd, name, zoneID, zoneName)
}

func testAccCheckCloudflareRulesetTransformationRuleHeaders(rnd, name, zoneID, zoneName string) string {
return fmt.Sprintf(`
resource "cloudflare_ruleset" "%[1]s" {
zone_id = "%[3]s"
name = "%[2]s"
description = "%[1]s ruleset description"
kind = "zone"
phase = "http_request_late_transform"
rules {
action = "rewrite"
action_parameters {
headers {
name = "example1"
operation = "set"
value = "my-http-header-value1"
}
headers {
name = "example2"
operation = "set"
expression = "cf.zone.name"
}
headers {
name = "example3"
operation = "remove"
}
}
expression = "true"
description = "example header transformation rule"
enabled = false
}
}`, rnd, name, zoneID, zoneName)
}
Loading

0 comments on commit 9f0de28

Please sign in to comment.