Skip to content

Commit

Permalink
Merge pull request #2672 from cwlowder/cwlowder/add-bot-management-re…
Browse files Browse the repository at this point in the history
…source
  • Loading branch information
jacobbednarz authored Aug 16, 2023
2 parents 4291cbd + 0f45ad9 commit 2770081
Show file tree
Hide file tree
Showing 8 changed files with 412 additions and 0 deletions.
3 changes: 3 additions & 0 deletions .changelog/2672.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:new-resource
introduce bot management resource
```
63 changes: 63 additions & 0 deletions docs/resources/bot_management.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
---
page_title: "cloudflare_bot_management Resource - Cloudflare"
subcategory: ""
description: |-
Provides a resource to configure Bot Management.
Specifically, this resource can be used to manage:
Bot Fight ModeSuper Bot Fight ModeBot Management for Enterprise
---

# cloudflare_bot_management (Resource)

Provides a resource to configure Bot Management.

Specifically, this resource can be used to manage:

- **Bot Fight Mode**
- **Super Bot Fight Mode**
- **Bot Management for Enterprise**

## Example Usage

```terraform
resource "cloudflare_bot_management" "example" {
zone_id = "0da42c8d2132a9ddaf714f9e7c920711"
enable_js = true
sbfm_definitely_automated = "block"
sbfm_likely_automated = "managed_challenge"
sbfm_verified_bots = "allow"
sbfm_static_resource_protection = false
optimize_wordpress = true
}
```
<!-- schema generated by tfplugindocs -->
## Schema

### Required

- `zone_id` (String) The zone identifier to target for the resource. **Modifying this attribute will force creation of a new resource.**

### Optional

- `auto_update_model` (Boolean) Automatically update to the newest bot detection models created by Cloudflare as they are released. [Learn more.](https://developers.cloudflare.com/bots/reference/machine-learning-models#model-versions-and-release-notes).
- `enable_js` (Boolean) Use lightweight, invisible JavaScript detections to improve Bot Management. [Learn more about JavaScript Detections](https://developers.cloudflare.com/bots/reference/javascript-detections/).
- `fight_mode` (Boolean) Whether to enable Bot Fight Mode.
- `optimize_wordpress` (Boolean) Whether to optimize Super Bot Fight Mode protections for Wordpress.
- `sbfm_definitely_automated` (String) Super Bot Fight Mode (SBFM) action to take on definitely automated requests.
- `sbfm_likely_automated` (String) Super Bot Fight Mode (SBFM) action to take on likely automated requests.
- `sbfm_static_resource_protection` (Boolean) Super Bot Fight Mode (SBFM) to enable static resource protection. Enable if static resources on your application need bot protection. Note: Static resource protection can also result in legitimate traffic being blocked.
- `sbfm_verified_bots` (String) Super Bot Fight Mode (SBFM) action to take on verified bots requests.
- `suppress_session_score` (Boolean) Whether to disable tracking the highest bot score for a session in the Bot Management cookie.

### Read-Only

- `id` (String) The ID of this resource.
- `using_latest_model` (Boolean) A read-only field that indicates whether the zone currently is running the latest ML model.

## Import

Import is supported using the following syntax:

```shell
$ terraform import cloudflare_bot_management.example <zone_id>
```
1 change: 1 addition & 0 deletions examples/resources/cloudflare_bot_management/import.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
$ terraform import cloudflare_bot_management.example <zone_id>
9 changes: 9 additions & 0 deletions examples/resources/cloudflare_bot_management/resource.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
resource "cloudflare_bot_management" "example" {
zone_id = "0da42c8d2132a9ddaf714f9e7c920711"
enable_js = true
sbfm_definitely_automated = "block"
sbfm_likely_automated = "managed_challenge"
sbfm_verified_bots = "allow"
sbfm_static_resource_protection = false
optimize_wordpress = true
}
1 change: 1 addition & 0 deletions internal/sdkv2provider/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,7 @@ func New(version string) func() *schema.Provider {
"cloudflare_spectrum_application": resourceCloudflareSpectrumApplication(),
"cloudflare_split_tunnel": resourceCloudflareSplitTunnel(),
"cloudflare_static_route": resourceCloudflareStaticRoute(),
"cloudflare_bot_management": resourceCloudflareBotManagement(),
"cloudflare_teams_account": resourceCloudflareTeamsAccount(),
"cloudflare_teams_list": resourceCloudflareTeamsList(),
"cloudflare_teams_location": resourceCloudflareTeamsLocation(),
Expand Down
155 changes: 155 additions & 0 deletions internal/sdkv2provider/resource_cloudflare_bot_management.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
package sdkv2provider

import (
"context"
"fmt"

"github.com/MakeNowJust/heredoc/v2"
"github.com/cloudflare/cloudflare-go"
"github.com/cloudflare/terraform-provider-cloudflare/internal/consts"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/pkg/errors"
)

func resourceCloudflareBotManagement() *schema.Resource {
return &schema.Resource{
Schema: resourceCloudflareBotManagementSchema(),
CreateContext: resourceCloudflareBotManagementCreate,
ReadContext: resourceCloudflareBotManagementRead,
UpdateContext: resourceCloudflareBotManagementUpdate,
DeleteContext: resourceCloudflareBotManagementDelete,
Importer: &schema.ResourceImporter{
StateContext: resourceCloudflareBotManagementImport,
},
Description: heredoc.Doc(`Provides a resource to configure Bot Management.
Specifically, this resource can be used to manage:
- **Bot Fight Mode**
- **Super Bot Fight Mode**
- **Bot Management for Enterprise**
`),
}
}

func resourceCloudflareBotManagementCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
d.SetId(d.Get(consts.ZoneIDSchemaKey).(string))

return resourceCloudflareBotManagementUpdate(ctx, d, meta)
}

func resourceCloudflareBotManagementRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
client := meta.(*cloudflare.API)
bm, err := client.GetBotManagement(ctx, cloudflare.ZoneIdentifier(d.Id()))
if err != nil {
return diag.FromErr(fmt.Errorf("failed to fetch bot management configuration: %w", err))
}

if bm.EnableJS != nil {
d.Set("enable_js", bm.EnableJS)
}

if bm.FightMode != nil {
d.Set("fight_mode", bm.FightMode)
}

if bm.SBFMDefinitelyAutomated != nil {
d.Set("sbfm_definitely_automated", bm.SBFMDefinitelyAutomated)
}

if bm.SBFMLikelyAutomated != nil {
d.Set("sbfm_likely_automated", bm.SBFMLikelyAutomated)
}

if bm.SBFMVerifiedBots != nil {
d.Set("sbfm_verified_bots", bm.SBFMVerifiedBots)
}

if bm.SBFMStaticResourceProtection != nil {
d.Set("sbfm_static_resource_protection", bm.SBFMStaticResourceProtection)
}

if bm.OptimizeWordpress != nil {
d.Set("optimize_wordpress", bm.OptimizeWordpress)
}

if bm.SuppressSessionScore != nil {
d.Set("suppress_session_score", bm.SuppressSessionScore)
}

if bm.AutoUpdateModel != nil {
d.Set("auto_update_model", bm.AutoUpdateModel)
}

if bm.UsingLatestModel != nil {
d.Set("using_latest_model", bm.UsingLatestModel)
}

return nil
}

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

params := buildBotManagementParams(d)

_, err := client.UpdateBotManagement(ctx, cloudflare.ZoneIdentifier(zoneID), params)
if err != nil {
return diag.FromErr(errors.Wrap(err, "failed to update bot management configuration"))
}

return resourceCloudflareBotManagementRead(ctx, d, meta)
}

// Deletion of bot management configuration is not something we support, we will
// use a dummy handler for now.
func resourceCloudflareBotManagementDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
return nil
}

func resourceCloudflareBotManagementImport(ctx context.Context, d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) {
d.SetId(d.Id())
d.Set(consts.ZoneIDSchemaKey, d.Id())

resourceCloudflareBotManagementRead(ctx, d, meta)

return []*schema.ResourceData{d}, nil
}

func buildBotManagementParams(d *schema.ResourceData) cloudflare.UpdateBotManagementParams {
bm := cloudflare.UpdateBotManagementParams{}

if val, exists := d.GetOkExists("enable_js"); exists {
bm.EnableJS = cloudflare.BoolPtr(val.(bool))
}
if val, exists := d.GetOkExists("fight_mode"); exists {
bm.EnableJS = cloudflare.BoolPtr(val.(bool))
}

if val, exists := d.GetOkExists("sbfm_definitely_automated"); exists {
bm.SBFMDefinitelyAutomated = cloudflare.StringPtr(val.(string))
}
if val, exists := d.GetOkExists("sbfm_likely_automated"); exists {
bm.SBFMLikelyAutomated = cloudflare.StringPtr(val.(string))
}
if val, exists := d.GetOkExists("sbfm_verified_bots"); exists {
bm.SBFMVerifiedBots = cloudflare.StringPtr(val.(string))
}
if val, exists := d.GetOkExists("sbfm_static_resource_protection"); exists {
bm.SBFMStaticResourceProtection = cloudflare.BoolPtr(val.(bool))
}
if val, exists := d.GetOkExists("optimize_wordpress"); exists {
bm.OptimizeWordpress = cloudflare.BoolPtr(val.(bool))
}

if val, exists := d.GetOkExists("suppress_session_score"); exists {
bm.SuppressSessionScore = cloudflare.BoolPtr(val.(bool))
}
if val, exists := d.GetOkExists("auto_update_model"); exists {
bm.AutoUpdateModel = cloudflare.BoolPtr(val.(bool))
}

return bm
}
113 changes: 113 additions & 0 deletions internal/sdkv2provider/resource_cloudflare_bot_management_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
package sdkv2provider

import (
"fmt"
"os"
"regexp"
"testing"

"github.com/cloudflare/cloudflare-go"
"github.com/cloudflare/terraform-provider-cloudflare/internal/consts"
"github.com/hashicorp/terraform-plugin-testing/helper/resource"
)

func TestAccCloudflareBotManagement_SBFM(t *testing.T) {
rnd := generateRandomResourceName()
resourceID := "cloudflare_bot_management." + rnd
zoneID := os.Getenv("CLOUDFLARE_ZONE_ID")

sbfmConfig := cloudflare.BotManagement{
EnableJS: cloudflare.BoolPtr(true),
SBFMDefinitelyAutomated: cloudflare.StringPtr("managed_challenge"),
SBFMLikelyAutomated: cloudflare.StringPtr("block"),
SBFMVerifiedBots: cloudflare.StringPtr("allow"),
SBFMStaticResourceProtection: cloudflare.BoolPtr(false),
OptimizeWordpress: cloudflare.BoolPtr(true),
}

resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
ProviderFactories: providerFactories,
Steps: []resource.TestStep{
{
Config: testCloudflareBotManagementSBFM(rnd, zoneID, sbfmConfig),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr(resourceID, consts.ZoneIDSchemaKey, zoneID),
resource.TestCheckResourceAttr(resourceID, "enable_js", "true"),
resource.TestCheckResourceAttr(resourceID, "sbfm_definitely_automated", "managed_challenge"),
resource.TestCheckResourceAttr(resourceID, "sbfm_likely_automated", "block"),
resource.TestCheckResourceAttr(resourceID, "sbfm_verified_bots", "allow"),
resource.TestCheckResourceAttr(resourceID, "sbfm_static_resource_protection", "false"),
resource.TestCheckResourceAttr(resourceID, "optimize_wordpress", "true"),
),
},
{
ResourceName: resourceID,
ImportState: true,
ImportStateVerify: true,
},
},
})
}

func TestAccCloudflareBotManagement_Unentitled(t *testing.T) {
rnd := generateRandomResourceName()
resourceID := "cloudflare_bot_management." + rnd
zoneID := os.Getenv("CLOUDFLARE_ZONE_ID")

bmEntConfig := cloudflare.BotManagement{
EnableJS: cloudflare.BoolPtr(true),
SuppressSessionScore: cloudflare.BoolPtr(false),
AutoUpdateModel: cloudflare.BoolPtr(false),
}

resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
ProviderFactories: providerFactories,
Steps: []resource.TestStep{
{
Config: testCloudflareBotManagementEntSubscription(rnd, zoneID, bmEntConfig),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr(resourceID, consts.ZoneIDSchemaKey, zoneID),
resource.TestCheckResourceAttr(resourceID, "enable_js", "true"),
resource.TestCheckResourceAttr(resourceID, "suppress_session_score", "false"),
resource.TestCheckResourceAttr(resourceID, "auto_update_model", "false"),
),
ExpectError: regexp.MustCompile("zone not entitled to disable"),
},
},
})
}

func testCloudflareBotManagementSBFM(resourceName, rnd string, bm cloudflare.BotManagement) string {
return fmt.Sprintf(`
resource "cloudflare_bot_management" "%[1]s" {
zone_id = "%[2]s"
enable_js = "%[3]t"
sbfm_definitely_automated = "%[4]s"
sbfm_likely_automated = "%[5]s"
sbfm_verified_bots = "%[6]s"
sbfm_static_resource_protection = "%[7]t"
optimize_wordpress = "%[8]t"
}
`, resourceName, rnd,
*bm.EnableJS, *bm.SBFMDefinitelyAutomated,
*bm.SBFMLikelyAutomated, *bm.SBFMVerifiedBots,
*bm.SBFMStaticResourceProtection, *bm.OptimizeWordpress)
}

func testCloudflareBotManagementEntSubscription(resourceName, rnd string, bm cloudflare.BotManagement) string {
return fmt.Sprintf(`
resource "cloudflare_bot_management" "%[1]s" {
zone_id = "%[2]s"
enable_js = "%[3]t"
suppress_session_score = "%[4]t"
auto_update_model = "%[5]t"
}
`, resourceName, rnd,
*bm.EnableJS, *bm.SuppressSessionScore, *bm.AutoUpdateModel)
}
Loading

0 comments on commit 2770081

Please sign in to comment.