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 Managed Headers resource #1688

Merged
3 changes: 3 additions & 0 deletions .changelog/1688.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:new-resource
cloudflare_managed_headers
```
71 changes: 71 additions & 0 deletions docs/resources/managed_headers.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
---
page_title: "cloudflare_managed_headers Resource - Cloudflare"
subcategory: ""
description: |-
The Cloudflare Managed Headers https://developers.cloudflare.com/rules/transform/managed-transforms/
allows you to add or remove some predefined headers to one's requests or origin responses.
---

# cloudflare_managed_headers (Resource)

The [Cloudflare Managed Headers](https://developers.cloudflare.com/rules/transform/managed-transforms/)
allows you to add or remove some predefined headers to one's requests or origin responses.

~> You can configure Managed Headers using the dashboard (https://api.cloudflare.com/#managed-headers-api-properties)
Terraform will override your configuration if it exists.

## Example Usage

```terraform
# Enable security headers using Managed Meaders
resource "cloudflare_managed_headers" "example" {
zone_id = "cb029e245cfdd66dc8d2e570d5dd3322"

managed_request_headers {
id = "add_true_client_ip_headers"
enabled = true
}

managed_response_headers {
id = "remove_x-powered-by_header"
enabled = true
}
}
```

<!-- schema generated by tfplugindocs -->
## Schema

### Required

- `zone_id` (String) The zone identifier to target for the resource.

### Optional

- `managed_request_headers` (Block Set) The list of managed request headers. (see [below for nested schema](#nestedblock--managed_request_headers))
- `managed_response_headers` (Block Set) The list of managed response headers. (see [below for nested schema](#nestedblock--managed_response_headers))

### Read-Only

- `id` (String) The ID of this resource.

<a id="nestedblock--managed_request_headers"></a>
### Nested Schema for `managed_request_headers`

Required:

- `enabled` (Boolean) Whether the headers rule is active.
- `id` (String) Unique headers rule identifier.


<a id="nestedblock--managed_response_headers"></a>
### Nested Schema for `managed_response_headers`

Required:

- `enabled` (Boolean) Whether the headers rule is active.
- `id` (String) Unique headers rule identifier.

## Import

Import is not supported for this resource.
12 changes: 6 additions & 6 deletions docs/resources/ruleset.md
Original file line number Diff line number Diff line change
Expand Up @@ -274,7 +274,7 @@ resource "cloudflare_ruleset" "custom_fields_logging_example" {

- `kind` (String) Type of Ruleset to create. Available values: `custom`, `managed`, `root`, `schema`, `zone`.
- `name` (String) Name of the ruleset.
- `phase` (String) Point in the request/response lifecycle where the ruleset will be created. Available values: `ddos_l4`, `ddos_l7`, `http_log_custom_fields`, `http_request_firewall_custom`, `http_request_firewall_managed`, `http_request_late_transform`, `http_request_main`, `http_request_sanitize`, `http_request_transform`, `http_request_origin`, `http_response_firewall_managed`, `http_response_headers_transform`, `magic_transit`, `http_ratelimit`, `http_request_sbfm`.
- `phase` (String) Point in the request/response lifecycle where the ruleset will be created. Available values: `ddos_l4`, `ddos_l7`, `http_log_custom_fields`, `http_request_cache_settings`, `http_request_firewall_custom`, `http_request_firewall_managed`, `http_request_late_transform`, `http_request_late_transform_managed`, `http_request_main`, `http_request_origin`, `http_request_redirect`, `http_request_sanitize`, `http_request_transform`, `http_response_firewall_managed`, `http_response_headers_transform`, `magic_transit`, `http_ratelimit`, `http_request_sbfm`.

### Optional

Expand All @@ -297,7 +297,7 @@ Required:

Optional:

- `action` (String) Action to perform in the ruleset rule. Available values: `block`, `challenge`, `ddos_dynamic`, `execute`, `force_connection_close`, `js_challenge`, `managed_challenge`, `log`, `log_custom_field`, `rewrite`, `score`, `skip`, `route`.
- `action` (String) Action to perform in the ruleset rule. Available values: `block`, `challenge`, `ddos_dynamic`, `execute`, `force_connection_close`, `js_challenge`, `log`, `log_custom_field`, `managed_challenge`, `redirect`, `rewrite`, `route`, `score`, `set_cache_settings`, `skip`.
- `action_parameters` (Block List, Max: 1) List of parameters that configure the behavior of the ruleset rule action. (see [below for nested schema](#nestedblock--rules--action_parameters))
- `description` (String) Brief summary of the ruleset rule and its intended use.
- `enabled` (Boolean) Whether the rule is active.
Expand All @@ -324,7 +324,7 @@ Optional:
- `matched_data` (Block List, Max: 1) List of properties to configure WAF payload logging. (see [below for nested schema](#nestedblock--rules--action_parameters--matched_data))
- `origin` (Block List, Max: 1) List of properties to change request origin. (see [below for nested schema](#nestedblock--rules--action_parameters--origin))
- `overrides` (Block List, Max: 1) List of override configurations to apply to the ruleset. (see [below for nested schema](#nestedblock--rules--action_parameters--overrides))
- `phases` (Set of String) Point in the request/response lifecycle where the ruleset will be created. Available values: `ddos_l4`, `ddos_l7`, `http_log_custom_fields`, `http_request_firewall_custom`, `http_request_firewall_managed`, `http_request_late_transform`, `http_request_main`, `http_request_sanitize`, `http_request_transform`, `http_request_origin`, `http_response_firewall_managed`, `http_response_headers_transform`, `magic_transit`, `http_ratelimit`, `http_request_sbfm`.
- `phases` (Set of String) Point in the request/response lifecycle where the ruleset will be created. Available values: `ddos_l4`, `ddos_l7`, `http_log_custom_fields`, `http_request_cache_settings`, `http_request_firewall_custom`, `http_request_firewall_managed`, `http_request_late_transform`, `http_request_late_transform_managed`, `http_request_main`, `http_request_origin`, `http_request_redirect`, `http_request_sanitize`, `http_request_transform`, `http_response_firewall_managed`, `http_response_headers_transform`, `magic_transit`, `http_ratelimit`, `http_request_sbfm`.
- `products` (Set of String) Products to target with the actions. Available values: `bic`, `hot`, `ratelimit`, `securityLevel`, `uablock`, `waf`, `zonelockdown`.
- `request_fields` (Set of String) List of request headers to include as part of custom fields logging, in lowercase.
- `response` (Block List) List of parameters that configure the response given to end users. (see [below for nested schema](#nestedblock--rules--action_parameters--response))
Expand Down Expand Up @@ -368,7 +368,7 @@ Optional:

Optional:

- `action` (String) Action to perform in the rule-level override. Available values: `block`, `challenge`, `ddos_dynamic`, `execute`, `force_connection_close`, `js_challenge`, `managed_challenge`, `log`, `log_custom_field`, `rewrite`, `score`, `skip`, `route`.
- `action` (String) Action to perform in the rule-level override. Available values: `block`, `challenge`, `ddos_dynamic`, `execute`, `force_connection_close`, `js_challenge`, `log`, `log_custom_field`, `managed_challenge`, `redirect`, `rewrite`, `route`, `score`, `set_cache_settings`, `skip`.
- `categories` (Block List) List of tag-based overrides. (see [below for nested schema](#nestedblock--rules--action_parameters--overrides--categories))
- `enabled` (Boolean, Deprecated) Defines if the current ruleset-level override enables or disables the ruleset.
- `rules` (Block List) List of rule-based overrides. (see [below for nested schema](#nestedblock--rules--action_parameters--overrides--rules))
Expand All @@ -379,7 +379,7 @@ Optional:

Optional:

- `action` (String) Action to perform in the tag-level override. Available values: `block`, `challenge`, `ddos_dynamic`, `execute`, `force_connection_close`, `js_challenge`, `managed_challenge`, `log`, `log_custom_field`, `rewrite`, `score`, `skip`, `route`.
- `action` (String) Action to perform in the tag-level override. Available values: `block`, `challenge`, `ddos_dynamic`, `execute`, `force_connection_close`, `js_challenge`, `log`, `log_custom_field`, `managed_challenge`, `redirect`, `rewrite`, `route`, `score`, `set_cache_settings`, `skip`.
- `category` (String) Tag name to apply the ruleset rule override to.
- `enabled` (Boolean, Deprecated) Defines if the current tag-level override enables or disables the ruleset rules with the specified tag.
- `status` (String) Defines if the current tag-level override enables or disables the ruleset rules with the specified tag. Available values: `enabled`, `disabled`. Defaults to `""`.
Expand All @@ -390,7 +390,7 @@ Optional:

Optional:

- `action` (String) Action to perform in the rule-level override. Available values: `block`, `challenge`, `ddos_dynamic`, `execute`, `force_connection_close`, `js_challenge`, `managed_challenge`, `log`, `log_custom_field`, `rewrite`, `score`, `skip`, `route`.
- `action` (String) Action to perform in the rule-level override. Available values: `block`, `challenge`, `ddos_dynamic`, `execute`, `force_connection_close`, `js_challenge`, `log`, `log_custom_field`, `managed_challenge`, `redirect`, `rewrite`, `route`, `score`, `set_cache_settings`, `skip`.
- `enabled` (Boolean, Deprecated) Defines if the current rule-level override enables or disables the rule.
- `id` (String) Rule ID to apply the override to.
- `score_threshold` (Number) Anomaly score threshold to apply in the ruleset rule override. Only applicable to modsecurity-based rulesets.
Expand Down
14 changes: 14 additions & 0 deletions examples/resources/managed_headers/resource.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Enable security headers using Managed Meaders
resource "cloudflare_managed_headers" "add_security_headers_example" {
zone_id = "cb029e245cfdd66dc8d2e570d5dd3322"

managed_request_headers {
id = "add_true_client_ip_headers"
enabled = true
}

managed_response_headers {
id = "remove_x-powered-by_header"
enabled = true
}
}
1 change: 1 addition & 0 deletions internal/provider/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,7 @@ func New(version string) func() *schema.Provider {
"cloudflare_logpush_job": resourceCloudflareLogpushJob(),
"cloudflare_logpush_ownership_challenge": resourceCloudflareLogpushOwnershipChallenge(),
"cloudflare_magic_firewall_ruleset": resourceCloudflareMagicFirewallRuleset(),
"cloudflare_managed_headers": resourceCloudflareManagedHeaders(),
"cloudflare_notification_policy_webhooks": resourceCloudflareNotificationPolicyWebhooks(),
"cloudflare_notification_policy": resourceCloudflareNotificationPolicy(),
"cloudflare_origin_ca_certificate": resourceCloudflareOriginCACertificate(),
Expand Down
183 changes: 183 additions & 0 deletions internal/provider/resource_cloudflare_managed_headers.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
package provider

import (
"context"
"errors"
"fmt"

cloudflare "github.com/cloudflare/cloudflare-go"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
)

func resourceCloudflareManagedHeaders() *schema.Resource {
return &schema.Resource{
Schema: resourceCloudflareManagedHeadersSchema(),
CreateContext: resourceCloudflareManagedHeadersCreate,
ReadContext: resourceCloudflareManagedHeadersRead,
UpdateContext: resourceCloudflareManagedHeadersUpdate,
DeleteContext: resourceCloudflareManagedHeadersDelete,
SchemaVersion: 0,
Description: `
The [Cloudflare Managed Headers](https://developers.cloudflare.com/rules/transform/managed-transforms/)
allows you to add or remove some predefined headers to one's requests or origin responses.`,
}
}

func resourceCloudflareManagedHeadersCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
zoneID := d.Get("zone_id").(string)
d.SetId(zoneID)
return resourceCloudflareManagedHeadersUpdate(ctx, d, meta)
}

func resourceCloudflareManagedHeadersRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
client := meta.(*cloudflare.API)
zoneID := d.Get("zone_id").(string)
headers, err := client.ListZoneManagedHeaders(ctx, cloudflare.ListManagedHeadersParams{
ZoneID: zoneID,
})
if err != nil {
return diag.FromErr(fmt.Errorf("error reading managed headers: %w", err))
}

// Filter out headers that are not enabled. This will eventually move into
// the API endpoint or the SDK.
var enabledRequestHeaders []cloudflare.ManagedHeader
var enabledResponseHeaders []cloudflare.ManagedHeader

for _, header := range headers.ManagedRequestHeaders {
if header.Enabled {
enabledRequestHeaders = append(enabledRequestHeaders, header)
}
}

for _, header := range headers.ManagedResponseHeaders {
if header.Enabled {
enabledResponseHeaders = append(enabledResponseHeaders, header)
}
}

if err := d.Set("managed_request_headers", buildResourceFromManagedHeaders(enabledRequestHeaders)); err != nil {
return diag.FromErr(err)
}
if err := d.Set("managed_response_headers", buildResourceFromManagedHeaders(enabledResponseHeaders)); err != nil {
return diag.FromErr(err)
}
return nil
}

func buildResourceFromManagedHeaders(headers []cloudflare.ManagedHeader) interface{} {
headersState := []map[string]interface{}{}
for _, header := range headers {
headersState = append(headersState, map[string]interface{}{
"id": header.ID,
"enabled": header.Enabled,
})
}

return headersState
}

func resourceCloudflareManagedHeadersUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
client := meta.(*cloudflare.API)
zoneID := d.Get("zone_id").(string)

mh, err := buildManagedHeadersFromResource(d)
if err != nil {
return diag.FromErr(fmt.Errorf("error building managed headers from resource: %w", err))
}
if _, err := client.UpdateZoneManagedHeaders(ctx, cloudflare.UpdateManagedHeadersParams{
ManagedHeaders: mh,
ZoneID: zoneID,
}); err != nil {
return diag.FromErr(fmt.Errorf("error updating managed headers: %w", err))
}
return resourceCloudflareManagedHeadersRead(ctx, d, meta)
}

// receives the resource config and builds a managed headers struct.
func buildManagedHeadersFromResource(d *schema.ResourceData) (cloudflare.ManagedHeaders, error) {
requestHeaders, ok := d.Get("managed_request_headers").(*schema.Set)
if !ok {
return cloudflare.ManagedHeaders{}, errors.New("unable to create interface array type assertion")
}
reqHeaders, err := buildManagedHeadersListFromResource(requestHeaders)
if err != nil {
return cloudflare.ManagedHeaders{}, err
}

responseHeaders, ok := d.Get("managed_response_headers").(*schema.Set)
if !ok {
return cloudflare.ManagedHeaders{}, errors.New("unable to create interface array type assertion")
}
respHeaders, err := buildManagedHeadersListFromResource(responseHeaders)
if err != nil {
return cloudflare.ManagedHeaders{}, err
}

return cloudflare.ManagedHeaders{
ManagedRequestHeaders: reqHeaders,
ManagedResponseHeaders: respHeaders,
}, nil
}

func buildManagedHeadersListFromResource(resource *schema.Set) ([]cloudflare.ManagedHeader, error) {
headers := make([]cloudflare.ManagedHeader, 0, len(resource.List()))
for _, header := range resource.List() {
h, ok := header.(map[string]interface{})
if !ok {
return nil, errors.New("unable to create interface map type assertion for managed header")
}
id, ok := h["id"].(string)
if !ok {
return nil, errors.New("unable to create string type assertion for managed header ID")
}
enabled, ok := h["enabled"].(bool)
if !ok {
return nil, errors.New("unable to create bool type assertion for managed header enabled")
}

if enabled {
headers = append(headers, cloudflare.ManagedHeader{
ID: id,
Enabled: enabled,
})
}
}
return headers, nil
}

func resourceCloudflareManagedHeadersDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
client := meta.(*cloudflare.API)
zoneID := d.Get("zone_id").(string)

headers, err := client.ListZoneManagedHeaders(ctx, cloudflare.ListManagedHeadersParams{
ZoneID: zoneID,
})
if err != nil {
return diag.FromErr(fmt.Errorf("error reading managed headers: %w", err))
}

requestHeaders := make([]cloudflare.ManagedHeader, 0, len(headers.ManagedRequestHeaders))
for _, header := range headers.ManagedRequestHeaders {
header.Enabled = false
requestHeaders = append(requestHeaders, header)
}
responseHeaders := make([]cloudflare.ManagedHeader, 0, len(headers.ManagedResponseHeaders))
for _, header := range headers.ManagedResponseHeaders {
header.Enabled = false
responseHeaders = append(responseHeaders, header)
}

if _, err := client.UpdateZoneManagedHeaders(ctx, cloudflare.UpdateManagedHeadersParams{
ManagedHeaders: cloudflare.ManagedHeaders{
ManagedRequestHeaders: requestHeaders,
ManagedResponseHeaders: responseHeaders,
},
ZoneID: zoneID,
}); err != nil {
return diag.FromErr(fmt.Errorf("error deleting managed headers with ID %q: %w", d.Id(), err))
}

return nil
}
Loading