-
Notifications
You must be signed in to change notification settings - Fork 630
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Denis Davydov
committed
Jun 9, 2022
1 parent
9648b75
commit 4d807de
Showing
7 changed files
with
360 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
```release-note:enhancement | ||
resource/cloudflare_managed_headers: Add support of Managed Headers API. | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
# 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_request_headers { | ||
id = "add_visitor_location_headers" | ||
enabled = false | ||
} | ||
|
||
managed_response_headers { | ||
id = "add_security_headers" | ||
enabled = false | ||
} | ||
|
||
managed_response_headers { | ||
id = "remove_x-powered-by_header" | ||
enabled = true | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
173 changes: 173 additions & 0 deletions
173
internal/provider/resource_cloudflare_managed_headers.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,173 @@ | ||
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, | ||
Importer: &schema.ResourceImporter{ | ||
StateContext: resourceCloudflareManagedHeadersImport, | ||
}, | ||
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. | ||
~> **NOTE:** You can configure Managed Headers using the dashboard (https://api.cloudflare.com/#managed-headers-api-properties) | ||
Terraform will override yours configuration if it exists.`, | ||
} | ||
} | ||
|
||
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)) | ||
} | ||
|
||
if err := d.Set("managed_request_headers", buildResourceFromManagedHeaders(headers.ManagedRequestHeaders)); err != nil { | ||
return diag.FromErr(err) | ||
} | ||
if err := d.Set("managed_response_headers", buildResourceFromManagedHeaders(headers.ManagedResponseHeaders)); 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").([]interface{}) | ||
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").([]interface{}) | ||
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 []interface{}) ([]cloudflare.ManagedHeader, error) { | ||
headers := make([]cloudflare.ManagedHeader, 0, len(resource)) | ||
for _, header := range resource { | ||
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") | ||
} | ||
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 | ||
} | ||
|
||
func resourceCloudflareManagedHeadersImport(ctx context.Context, d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { | ||
return nil, errors.New("Import is not yet supported for Managed Headers") | ||
} |
107 changes: 107 additions & 0 deletions
107
internal/provider/resource_cloudflare_managed_headers_test.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,107 @@ | ||
package provider | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"os" | ||
"testing" | ||
|
||
cloudflare "github.com/cloudflare/cloudflare-go" | ||
"github.com/hashicorp/terraform-plugin-log/tflog" | ||
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" | ||
"github.com/pkg/errors" | ||
) | ||
|
||
func init() { | ||
resource.AddTestSweepers("cloudflare_managed_headers", &resource.Sweeper{ | ||
Name: "cloudflare_managed_headers", | ||
F: testSweepCloudflareManagedHeaders, | ||
}) | ||
} | ||
|
||
func testSweepCloudflareManagedHeaders(r string) error { | ||
ctx := context.Background() | ||
client, clientErr := sharedClient() | ||
if clientErr != nil { | ||
tflog.Error(ctx, fmt.Sprintf("Failed to create Cloudflare client: %s", clientErr)) | ||
} | ||
|
||
zone := os.Getenv("CLOUDFLARE_ZONE_ID") | ||
if zone == "" { | ||
return errors.New("CLOUDFLARE_ZONE_ID must be set") | ||
} | ||
|
||
managedHeaders, err := client.ListZoneManagedHeaders(context.Background(), cloudflare.ListManagedHeadersParams{ | ||
ZoneID: zoneID, | ||
}) | ||
if err != nil { | ||
tflog.Error(ctx, fmt.Sprintf("Failed to fetch Cloudflare Zone Managed Headers: %s", err)) | ||
} | ||
|
||
requestHeaders := make([]cloudflare.ManagedHeader, 0, len(managedHeaders.ManagedRequestHeaders)) | ||
for _, h := range managedHeaders.ManagedRequestHeaders { | ||
tflog.Info(ctx, fmt.Sprintf("Disabling Cloudflare Zone Managed Header ID: %s", h.ID)) | ||
h.Enabled = false | ||
requestHeaders = append(requestHeaders, h) | ||
} | ||
responseHeaders := make([]cloudflare.ManagedHeader, 0, len(managedHeaders.ManagedResponseHeaders)) | ||
for _, h := range managedHeaders.ManagedResponseHeaders { | ||
tflog.Info(ctx, fmt.Sprintf("Disabling Cloudflare Zone Managed Header ID: %s", h.ID)) | ||
h.Enabled = false | ||
responseHeaders = append(responseHeaders, h) | ||
} | ||
|
||
_, err = client.UpdateZoneManagedHeaders(context.Background(), cloudflare.UpdateManagedHeadersParams{ | ||
ManagedHeaders: cloudflare.ManagedHeaders{ | ||
ManagedRequestHeaders: requestHeaders, | ||
ManagedResponseHeaders: responseHeaders, | ||
}, | ||
ZoneID: zoneID, | ||
}) | ||
if err != nil { | ||
tflog.Error(ctx, fmt.Sprintf("Failed to disable Cloudflare Zone Managed Headers: %s", err)) | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func TestZoneCloudflareManagedHeaders(t *testing.T) { | ||
t.Parallel() | ||
|
||
rnd := generateRandomResourceName() | ||
zoneID := os.Getenv("CLOUDFLARE_ZONE_ID") | ||
resourceName := "cloudflare_managed_headers." + rnd | ||
resource.Test(t, resource.TestCase{ | ||
PreCheck: func() { testAccPreCheck(t) }, | ||
ProviderFactories: providerFactories, | ||
Steps: []resource.TestStep{ | ||
{ | ||
Config: testAccCheckCloudflareManagedHeaders(rnd, zoneID), | ||
Check: resource.ComposeTestCheckFunc( | ||
resource.TestCheckResourceAttr(resourceName, "zone_id", zoneID), | ||
resource.TestCheckResourceAttr(resourceName, "managed_request_headers.#", "1"), | ||
resource.TestCheckResourceAttr(resourceName, "managed_request_headers.0.id", "add_true_client_ip_headers"), | ||
resource.TestCheckResourceAttr(resourceName, "managed_request_headers.0.enabled", "true"), | ||
resource.TestCheckResourceAttr(resourceName, "managed_response_headers.#", "1"), | ||
resource.TestCheckResourceAttr(resourceName, "managed_response_headers.0.id", "add_security_headers"), | ||
resource.TestCheckResourceAttr(resourceName, "managed_response_headers.0.enabled", "true"), | ||
), | ||
}, | ||
}, | ||
}) | ||
} | ||
|
||
func testAccCheckCloudflareManagedHeaders(rnd, zoneID string) string { | ||
return fmt.Sprintf(` | ||
resource "cloudflare_managed_headers" "%[1]s" { | ||
zone_id = "%[2]s" | ||
managed_request_headers { | ||
id = "add_true_client_ip_headers" | ||
enabled = true | ||
} | ||
managed_response_headers { | ||
id = "add_security_headers" | ||
enabled = true | ||
} | ||
}`, rnd, zoneID) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
package provider | ||
|
||
import "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" | ||
|
||
func resourceCloudflareManagedHeadersSchema() map[string]*schema.Schema { | ||
return map[string]*schema.Schema{ | ||
"zone_id": { | ||
Description: "The zone identifier to target for the resource.", | ||
Type: schema.TypeString, | ||
Optional: false, | ||
}, | ||
"managed_request_headers": { | ||
Description: "The list of managed request headers", | ||
Type: schema.TypeList, | ||
Optional: true, | ||
Elem: &schema.Resource{ | ||
Schema: map[string]*schema.Schema{ | ||
"id": { | ||
Type: schema.TypeString, | ||
Computed: true, | ||
Description: "Unique headers rule identifier.", | ||
}, | ||
"enabled": { | ||
Type: schema.TypeBool, | ||
Optional: true, | ||
Description: "Whether the headers rule is active.", | ||
}, | ||
}, | ||
}, | ||
}, | ||
"managed_response_headers": { | ||
Description: "The list of managed response headers", | ||
Type: schema.TypeList, | ||
Optional: true, | ||
Elem: &schema.Resource{ | ||
Schema: map[string]*schema.Schema{ | ||
"id": { | ||
Type: schema.TypeString, | ||
Computed: true, | ||
Description: "Unique headers rule identifier.", | ||
}, | ||
"enabled": { | ||
Type: schema.TypeBool, | ||
Optional: true, | ||
Description: "Whether the headers rule is active.", | ||
}, | ||
}, | ||
}, | ||
}, | ||
} | ||
} |