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

DLP-1714: added ContextAwareness support for DLP Profiles #3158

Merged
merged 1 commit into from Mar 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 3 additions & 0 deletions .changelog/3158.txt
@@ -0,0 +1,3 @@
```release-note:enhancement
resource/cloudflare_dlp_profile: Added support for `context_awareness` field to profiles
```
18 changes: 18 additions & 0 deletions docs/resources/dlp_profile.md
Expand Up @@ -90,6 +90,7 @@ resource "cloudflare_dlp_profile" "example_custom" {

### Optional

- `context_awareness` (Block List, Max: 1) Scan the context of predefined entries to only return matches surrounded by keywords. (see [below for nested schema](#nestedblock--context_awareness))
- `description` (String) Brief summary of the profile and its intended use.

### Read-Only
Expand Down Expand Up @@ -120,6 +121,23 @@ Optional:

- `validation` (String) The validation algorithm to apply with this pattern.



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

Required:

- `enabled` (Boolean) Scan the context of predefined entries to only return matches surrounded by keywords.
- `skip` (Block List, Min: 1, Max: 1) Content types to exclude from context analysis and return all matches. (see [below for nested schema](#nestedblock--context_awareness--skip))

<a id="nestedblock--context_awareness--skip"></a>
### Nested Schema for `context_awareness.skip`

Required:

- `files` (Boolean) Return all matches, regardless of context analysis result, if the data is a file.

## Import

Import is supported using the following syntax:
Expand Down
44 changes: 44 additions & 0 deletions internal/sdkv2provider/resource_cloudflare_dlp_profile.go
Expand Up @@ -71,6 +71,38 @@ func dlpEntryToSchema(entry cloudflare.DLPEntry) map[string]interface{} {
return entrySchema
}

func dlpContextAwarenessSkipToAPI(skipSchema map[string]interface{}) cloudflare.DLPContextAwarenessSkip {
files := skipSchema["files"].(bool)
skip := cloudflare.DLPContextAwarenessSkip{
Files: &files,
}
return skip
}

func dlpContextAwarenessToAPI(contextSchema map[string]interface{}) cloudflare.DLPContextAwareness {
enabled := contextSchema["enabled"].(bool)
skip_items := contextSchema["skip"].([]interface{})
skip_item := skip_items[0].(map[string]interface{})
context := cloudflare.DLPContextAwareness{
Enabled: &enabled,
Skip: dlpContextAwarenessSkipToAPI(skip_item),
}
return context
}

func dlpContextAwarenessSkipToSchema(skip cloudflare.DLPContextAwarenessSkip) map[string]interface{} {
skipSchema := make(map[string]interface{})
skipSchema["files"] = skip.Files
return skipSchema
}

func dlpContextAwarenessToSchema(context cloudflare.DLPContextAwareness) map[string]interface{} {
contextSchema := make(map[string]interface{})
contextSchema["enabled"] = *context.Enabled
contextSchema["skip"] = []interface{}{dlpContextAwarenessSkipToSchema(context.Skip)}
return contextSchema
}

func dlpEntryToAPI(entryType string, entryMap map[string]interface{}) cloudflare.DLPEntry {
apiEntry := cloudflare.DLPEntry{
Name: entryMap["name"].(string),
Expand Down Expand Up @@ -109,6 +141,9 @@ func resourceCloudflareDLPProfileRead(ctx context.Context, d *schema.ResourceDat
d.Set("description", dlpProfile.Description)
}
d.Set("allowed_match_count", dlpProfile.AllowedMatchCount)
if dlpProfile.ContextAwareness != nil {
d.Set("context_awareness", []interface{}{dlpContextAwarenessToSchema(*dlpProfile.ContextAwareness)})
}
entries := make([]interface{}, 0, len(dlpProfile.Entries))
for _, entry := range dlpProfile.Entries {
entries = append(entries, dlpEntryToSchema(entry))
Expand All @@ -129,6 +164,11 @@ func resourceCloudflareDLPProfileCreate(ctx context.Context, d *schema.ResourceD
AllowedMatchCount: d.Get("allowed_match_count").(int),
}

if contextAwarenessSchema, ok := d.GetOk("context_awareness.0"); ok {
contextAwareness := dlpContextAwarenessToAPI(contextAwarenessSchema.(map[string]interface{}))
newDLPProfile.ContextAwareness = &contextAwareness
}

if newDLPProfile.Type == DLPProfileTypePredefined {
return diag.FromErr(fmt.Errorf("predefined DLP profiles cannot be created and must be imported"))
}
Expand Down Expand Up @@ -164,6 +204,10 @@ func resourceCloudflareDLPProfileUpdate(ctx context.Context, d *schema.ResourceD
AllowedMatchCount: d.Get("allowed_match_count").(int),
}
updatedDLPProfile.Description, _ = d.Get("description").(string)
if contextAwarenessSchema, ok := d.GetOk("context_awareness.0"); ok {
contextAwareness := dlpContextAwarenessToAPI(contextAwarenessSchema.(map[string]interface{}))
updatedDLPProfile.ContextAwareness = &contextAwareness
}
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{})))
Expand Down
66 changes: 66 additions & 0 deletions internal/sdkv2provider/resource_cloudflare_dlp_profile_test.go
Expand Up @@ -4,6 +4,8 @@ import (
"fmt"
"testing"

"github.com/cloudflare/cloudflare-go"

"github.com/cloudflare/terraform-provider-cloudflare/internal/consts"
"github.com/hashicorp/terraform-plugin-testing/helper/resource"
"github.com/hashicorp/terraform-plugin-testing/plancheck"
Expand Down Expand Up @@ -105,6 +107,44 @@ func TestAccCloudflareDLPProfile_CustomWithAllowedMatchCount(t *testing.T) {
})
}

func TestAccCloudflareDLPProfile_ContextAwareness(t *testing.T) {
rnd := generateRandomResourceName()
name := fmt.Sprintf("cloudflare_dlp_profile.%s", rnd)

enabled := true
files := true

resource.Test(t, resource.TestCase{
PreCheck: func() {
testAccPreCheckAccount(t)
},
ProviderFactories: providerFactories,
Steps: []resource.TestStep{
{
Config: testAccCloudflareDLPProfileConfigWithContextAwareness(accountID, rnd, "custom profile", cloudflare.DLPContextAwareness{
Enabled: &enabled,
Skip: cloudflare.DLPContextAwarenessSkip{
Files: &files,
},
}),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr(name, consts.AccountIDSchemaKey, accountID),
resource.TestCheckResourceAttr(name, "name", rnd),
resource.TestCheckResourceAttr(name, "description", "custom profile"),
resource.TestCheckResourceAttr(name, "type", "custom"),
resource.TestCheckResourceAttr(name, "allowed_match_count", "0"),
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, "context_awareness.0.enabled", "true"),
resource.TestCheckResourceAttr(name, "context_awareness.0.skip.0.files", "true"),
),
},
},
})
}

func testAccCloudflareDLPProfileConfigCustom(accountID, rnd, description string) string {
return fmt.Sprintf(`
resource "cloudflare_dlp_profile" "%[1]s" {
Expand Down Expand Up @@ -172,3 +212,29 @@ resource "cloudflare_dlp_profile" "%[1]s" {
}
`, rnd, description, accountID, allowedMatchCount)
}

func testAccCloudflareDLPProfileConfigWithContextAwareness(accountID, rnd, description string, contextAwareness cloudflare.DLPContextAwareness) string {
return fmt.Sprintf(`
resource "cloudflare_dlp_profile" "%[2]s" {
account_id = "%[1]s"
name = "%[2]s"
description = "%[3]s"
allowed_match_count = 0
type = "custom"
entry {
name = "%[2]s_entry1"
enabled = true
pattern {
regex = "^4[0-9]"
validation = "luhn"
}
}
context_awareness {
enabled = %[4]t
skip {
files = %[5]t
}
}
}
`, accountID, rnd, description, *contextAwareness.Enabled, *contextAwareness.Skip.Files)
}
35 changes: 35 additions & 0 deletions internal/sdkv2provider/schema_cloudflare_dlp_profile.go
Expand Up @@ -66,6 +66,31 @@ func hashResourceCloudflareDLPEntry(i interface{}) int {
return schema.HashString(v["name"])
}

func resourceCloudflareDLPContextAwarenessSchema() map[string]*schema.Schema {
return map[string]*schema.Schema{
"enabled": {
Type: schema.TypeBool,
Required: true,
Description: "Scan the context of predefined entries to only return matches surrounded by keywords.",
},
"skip": {
Type: schema.TypeList,
Description: "Content types to exclude from context analysis and return all matches.",
Required: true,
MaxItems: 1,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"files": {
Type: schema.TypeBool,
Required: true,
Description: "Return all matches, regardless of context analysis result, if the data is a file.",
},
},
},
},
}
}

func resourceCloudflareDLPProfileSchema() map[string]*schema.Schema {
return map[string]*schema.Schema{
consts.AccountIDSchemaKey: {
Expand Down Expand Up @@ -107,5 +132,15 @@ func resourceCloudflareDLPProfileSchema() map[string]*schema.Schema {
Required: true,
ValidateFunc: validation.IntBetween(0, 1000),
},
"context_awareness": {
Type: schema.TypeList,
Description: "Scan the context of predefined entries to only return matches surrounded by keywords.",
Computed: true,
Optional: true,
MaxItems: 1,
Elem: &schema.Resource{
Schema: resourceCloudflareDLPContextAwarenessSchema(),
},
},
}
}