-
Notifications
You must be signed in to change notification settings - Fork 541
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
DLP-655 Adds support for DLP profiles
- Loading branch information
Eduardo Gomes
committed
Oct 26, 2022
1 parent
dce40a5
commit 42b532e
Showing
7 changed files
with
438 additions
and
4 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_dlp_profile: adds support for DLP profiles | ||
``` |
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
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,219 @@ | ||
package provider | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"github.com/cloudflare/cloudflare-go" | ||
"strings" | ||
|
||
"github.com/MakeNowJust/heredoc/v2" | ||
"github.com/hashicorp/terraform-plugin-log/tflog" | ||
"github.com/hashicorp/terraform-plugin-sdk/v2/diag" | ||
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" | ||
) | ||
|
||
func resourceCloudflareDLPProfile() *schema.Resource { | ||
return &schema.Resource{ | ||
Schema: resourceCloudflareDLPProfileSchema(), | ||
CreateContext: resourceCloudflareDLPProfileCreate, | ||
ReadContext: resourceCloudflareDLPProfileRead, | ||
UpdateContext: resourceCloudflareDLPProfileUpdate, | ||
DeleteContext: resourceCloudflareDLPProfileDelete, | ||
Importer: &schema.ResourceImporter{ | ||
StateContext: resourceCloudflareDLPProfileImport, | ||
}, | ||
Description: heredoc.Doc(` | ||
Provides a Cloudflare DLP Profile resource. Data Loss Prevention profiles | ||
are a set of entries that can be matched in HTTP bodies or files. | ||
They are referenced in Zero Trust Gateway rules. | ||
`), | ||
} | ||
} | ||
|
||
func dlpPatternToSchema(pattern cloudflare.DLPPattern) map[string]interface{} { | ||
schema := make(map[string]interface{}) | ||
if pattern.Regex != "" { | ||
schema["regex"] = pattern.Regex | ||
} | ||
if pattern.Validation != "" { | ||
schema["validation"] = pattern.Validation | ||
} | ||
return schema | ||
} | ||
|
||
func dlpPatternToAPI(pattern map[string]interface{}) cloudflare.DLPPattern { | ||
entryPattern := cloudflare.DLPPattern{ | ||
Regex: pattern["regex"].(string), | ||
} | ||
if validation, ok := pattern["validation"].(string); ok { | ||
entryPattern.Validation = validation | ||
} | ||
return entryPattern | ||
} | ||
|
||
func dlpEntryToSchema(entry cloudflare.DLPEntry) map[string]interface{} { | ||
entrySchema := make(map[string]interface{}) | ||
if entry.ID != "" { | ||
entrySchema["id"] = entry.ID | ||
} | ||
if entry.Name != "" { | ||
entrySchema["name"] = entry.Name | ||
} | ||
entrySchema["enabled"] = entry.Enabled != nil && *entry.Enabled == true | ||
if entry.Pattern != nil { | ||
entrySchema["pattern"] = schema.NewSet(schema.HashResource(&schema.Resource{ | ||
Schema: resourceCloudflareDLPPatternSchema(), | ||
}), []interface{}{dlpPatternToSchema(*entry.Pattern)}) | ||
} | ||
return entrySchema | ||
} | ||
|
||
func dlpEntryToAPI(entryType string, entryMap map[string]interface{}) cloudflare.DLPEntry { | ||
apiEntry := cloudflare.DLPEntry{ | ||
Name: entryMap["name"].(string), | ||
} | ||
if entryID, ok := entryMap["id"].(string); ok { | ||
apiEntry.ID = entryID | ||
} | ||
if patterns, ok := entryMap["pattern"].(*schema.Set); ok && patterns.Len() != 0 { | ||
newPattern := dlpPatternToAPI(patterns.List()[0].(map[string]interface{})) | ||
apiEntry.Pattern = &newPattern | ||
} | ||
enabled := entryMap["enabled"] == true | ||
apiEntry.Enabled = &enabled | ||
apiEntry.Type = entryType | ||
return apiEntry | ||
} | ||
|
||
func resourceCloudflareDLPProfileRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { | ||
client := meta.(*cloudflare.API) | ||
|
||
identifier := cloudflare.AccountIdentifier(d.Get("account_id").(string)) | ||
dlpProfile, err := client.GetDLPProfile(ctx, identifier, d.Id()) | ||
if err != nil { | ||
tflog.Info(ctx, fmt.Sprintf("DLP Profile %s no longer exists", d.Id())) | ||
d.SetId("") | ||
return nil | ||
} | ||
|
||
d.Set("name", dlpProfile.Name) | ||
d.Set("type", dlpProfile.Type) | ||
if dlpProfile.Description != "" { | ||
d.Set("description", dlpProfile.Description) | ||
} | ||
entries := make([]interface{}, 0, len(dlpProfile.Entries)) | ||
for _, entry := range dlpProfile.Entries { | ||
entries = append(entries, dlpEntryToSchema(entry)) | ||
} | ||
d.Set("entry", schema.NewSet(schema.HashResource(&schema.Resource{ | ||
Schema: resourceCloudflareDLPEntrySchema(), | ||
}), entries)) | ||
|
||
return nil | ||
} | ||
|
||
func resourceCloudflareDLPProfileCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { | ||
client := meta.(*cloudflare.API) | ||
identifier := cloudflare.AccountIdentifier(d.Get("account_id").(string)) | ||
|
||
newDLPProfile := cloudflare.DLPProfile{ | ||
Name: d.Get("name").(string), | ||
Type: d.Get("type").(string), | ||
Description: d.Get("description").(string), | ||
} | ||
|
||
if newDLPProfile.Type == DLPProfileTypePredefined { | ||
return diag.FromErr(fmt.Errorf("predefined DLP profiles cannot be created and must be imported")) | ||
} | ||
|
||
if entries, ok := d.GetOk("entry"); ok { | ||
for _, entry := range entries.(*schema.Set).List() { | ||
newDLPProfile.Entries = append(newDLPProfile.Entries, dlpEntryToAPI(newDLPProfile.Type, entry.(map[string]interface{}))) | ||
} | ||
} | ||
|
||
dlpProfiles, err := client.CreateDLPProfiles(ctx, identifier, cloudflare.CreateDLPProfilesParams{ | ||
Profiles: []cloudflare.DLPProfile{newDLPProfile}, | ||
Type: newDLPProfile.Type, | ||
}) | ||
if err != nil { | ||
return diag.FromErr(fmt.Errorf("error creating DLP Profile for name %s: %w", newDLPProfile.Name, err)) | ||
} | ||
if len(dlpProfiles) == 0 { | ||
return diag.FromErr(fmt.Errorf("error creating DLP Profile for name %s: no profile in response", newDLPProfile.Name)) | ||
} | ||
|
||
d.SetId(dlpProfiles[0].ID) | ||
return resourceCloudflareDLPProfileRead(ctx, d, meta) | ||
} | ||
|
||
func resourceCloudflareDLPProfileUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { | ||
client := meta.(*cloudflare.API) | ||
|
||
updatedDLPProfile := cloudflare.DLPProfile{ | ||
ID: d.Id(), | ||
Name: d.Get("name").(string), | ||
Type: d.Get("type").(string), | ||
} | ||
updatedDLPProfile.Description, _ = d.Get("description").(string) | ||
if entries, ok := d.GetOk("entry"); ok { | ||
for _, entry := range entries.(*schema.Set).List() { | ||
updatedDLPProfile.Entries = append(updatedDLPProfile.Entries, dlpEntryToAPI(updatedDLPProfile.Type, entry.(map[string]interface{}))) | ||
} | ||
} | ||
|
||
tflog.Debug(ctx, fmt.Sprintf("Updating Cloudflare DLP Profile from struct: %+v", updatedDLPProfile)) | ||
|
||
identifier := cloudflare.AccountIdentifier(d.Get("account_id").(string)) | ||
dlpProfile, err := client.UpdateDLPProfile(ctx, identifier, cloudflare.UpdateDLPProfileParams{ | ||
ProfileID: updatedDLPProfile.ID, | ||
Profile: updatedDLPProfile, | ||
Type: updatedDLPProfile.Type, | ||
}) | ||
if err != nil { | ||
return diag.FromErr(fmt.Errorf("error updating DLP profile for ID %q: %w", d.Id(), err)) | ||
} | ||
if dlpProfile.ID == "" { | ||
return diag.FromErr(fmt.Errorf("failed to find DLP Profile ID in update response; resource was empty")) | ||
} | ||
|
||
return resourceCloudflareDLPProfileRead(ctx, d, meta) | ||
} | ||
|
||
func resourceCloudflareDLPProfileDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { | ||
client := meta.(*cloudflare.API) | ||
tflog.Debug(ctx, fmt.Sprintf("Deleting Cloudflare DLP Profile using ID: %s", d.Id())) | ||
|
||
profileType, _ := d.Get("type").(string) | ||
if profileType != DLPProfileTypeCustom { | ||
return diag.FromErr(fmt.Errorf("error deleting DLP Profile: can only delete custom profiles")) | ||
} | ||
identifier := cloudflare.AccountIdentifier(d.Get("account_id").(string)) | ||
if err := client.DeleteDLPProfile(ctx, identifier, d.Id()); err != nil { | ||
return diag.FromErr(fmt.Errorf("error deleting DLP Profile for ID %q: %w", d.Id(), err)) | ||
} | ||
|
||
resourceCloudflareDLPProfileRead(ctx, d, meta) | ||
return nil | ||
} | ||
|
||
func resourceCloudflareDLPProfileImport(ctx context.Context, d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { | ||
attributes := strings.SplitN(d.Id(), "/", 3) | ||
if len(attributes) != 3 { | ||
return nil, fmt.Errorf( | ||
"invalid id (%q) specified, should be in format %q", | ||
d.Id(), | ||
"account/accountID/dlpProfileID", | ||
) | ||
} | ||
identifierType, identifierID, dlpProfileID := attributes[0], attributes[1], attributes[2] | ||
|
||
tflog.Debug(ctx, fmt.Sprintf("Importing Cloudflare DLP Profile: %s %q, ID %q", identifierType, identifierID, dlpProfileID)) | ||
|
||
//lintignore:R001 | ||
d.Set(fmt.Sprintf("%s_id", identifierType), identifierID) | ||
d.SetId(dlpProfileID) | ||
|
||
resourceCloudflareDLPProfileRead(ctx, d, meta) | ||
return []*schema.ResourceData{d}, nil | ||
} |
113 changes: 113 additions & 0 deletions
113
internal/provider/resource_cloudflare_dlp_profile_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,113 @@ | ||
package provider | ||
|
||
import ( | ||
"fmt" | ||
"testing" | ||
|
||
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" | ||
) | ||
|
||
func TestAccCloudflareDLPProfile_Custom(t *testing.T) { | ||
rnd := generateRandomResourceName() | ||
name := fmt.Sprintf("cloudflare_dlp_profile.%s", rnd) | ||
|
||
resource.Test(t, resource.TestCase{ | ||
PreCheck: func() { | ||
testAccPreCheckAccount(t) | ||
}, | ||
ProviderFactories: providerFactories, | ||
Steps: []resource.TestStep{ | ||
{ | ||
Config: testAccCloudflareDLPProfileConfigCustom(accountID, rnd, "custom profile"), | ||
Check: resource.ComposeTestCheckFunc( | ||
resource.TestCheckResourceAttr(name, "account_id", accountID), | ||
resource.TestCheckResourceAttr(name, "name", rnd), | ||
resource.TestCheckResourceAttr(name, "description", "custom profile"), | ||
resource.TestCheckResourceAttr(name, "type", "custom"), | ||
resource.TestCheckResourceAttr(name, "entry.0.name", fmt.Sprintf("%s_entry1", rnd)), | ||
resource.TestCheckResourceAttr(name, "entry.0.enabled", "true"), | ||
resource.TestCheckResourceAttr(name, "entry.0.pattern.0.regex", "^4[0-9]"), | ||
resource.TestCheckResourceAttr(name, "entry.0.pattern.0.validation", "luhn"), | ||
), | ||
}, | ||
}, | ||
}) | ||
} | ||
|
||
func TestAccCloudflareDLPProfile_Custom_MultipleEntries(t *testing.T) { | ||
rnd := generateRandomResourceName() | ||
name := fmt.Sprintf("cloudflare_dlp_profile.%s", rnd) | ||
|
||
resource.Test(t, resource.TestCase{ | ||
PreCheck: func() { | ||
testAccPreCheckAccount(t) | ||
}, | ||
ProviderFactories: providerFactories, | ||
Steps: []resource.TestStep{ | ||
{ | ||
Config: testAccCloudflareDLPProfileConfigCustomMultipleEntries(accountID, rnd, "custom profile 2"), | ||
Check: resource.ComposeTestCheckFunc( | ||
resource.TestCheckResourceAttr(name, "account_id", accountID), | ||
resource.TestCheckResourceAttr(name, "name", rnd), | ||
resource.TestCheckResourceAttr(name, "description", "custom profile 2"), | ||
resource.TestCheckResourceAttr(name, "type", "custom"), | ||
resource.TestCheckResourceAttr(name, "entry.0.name", fmt.Sprintf("%s_entry1", rnd)), | ||
resource.TestCheckResourceAttr(name, "entry.0.enabled", "true"), | ||
resource.TestCheckResourceAttr(name, "entry.0.pattern.0.regex", "^4[0-9]"), | ||
resource.TestCheckResourceAttr(name, "entry.0.pattern.0.validation", "luhn"), | ||
resource.TestCheckResourceAttr(name, "entry.1.name", fmt.Sprintf("%s_entry2", rnd)), | ||
resource.TestCheckResourceAttr(name, "entry.1.enabled", "true"), | ||
resource.TestCheckResourceAttr(name, "entry.1.pattern.0.regex", "^3[0-9]"), | ||
resource.TestCheckResourceAttr(name, "entry.1.pattern.0.validation", "luhn"), | ||
), | ||
}, | ||
}, | ||
}) | ||
} | ||
|
||
func testAccCloudflareDLPProfileConfigCustom(accountID, rnd, description string) string { | ||
return fmt.Sprintf(` | ||
resource "cloudflare_dlp_profile" "%[1]s" { | ||
account_id = "%[3]s" | ||
name = "%[1]s" | ||
description = "%[2]s" | ||
type = "custom" | ||
entry { | ||
name = "%[1]s_entry1" | ||
enabled = true | ||
pattern { | ||
regex = "^4[0-9]" | ||
validation = "luhn" | ||
} | ||
} | ||
} | ||
`, rnd, description, accountID) | ||
} | ||
|
||
func testAccCloudflareDLPProfileConfigCustomMultipleEntries(accountID, rnd, description string) string { | ||
return fmt.Sprintf(` | ||
resource "cloudflare_dlp_profile" "%[1]s" { | ||
account_id = "%[3]s" | ||
name = "%[1]s" | ||
description = "%[2]s" | ||
type = "custom" | ||
entry { | ||
name = "%[1]s_entry1" | ||
enabled = true | ||
pattern { | ||
regex = "^4[0-9]" | ||
validation = "luhn" | ||
} | ||
} | ||
entry { | ||
name = "%[1]s_entry2" | ||
enabled = true | ||
pattern { | ||
regex = "^3[0-9]" | ||
validation = "luhn" | ||
} | ||
} | ||
} | ||
`, rnd, description, accountID) | ||
} |
Oops, something went wrong.