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

WDAPI-645 add a device posture integration #1340

Merged
merged 1 commit into from
Jan 5, 2022
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions cloudflare/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,7 @@ func Provider() *schema.Provider {
"cloudflare_custom_pages": resourceCloudflareCustomPages(),
"cloudflare_custom_ssl": resourceCloudflareCustomSsl(),
"cloudflare_device_posture_rule": resourceCloudflareDevicePostureRule(),
"cloudflare_device_posture_integration": resourceCloudflareDevicePostureIntegration(),
"cloudflare_filter": resourceCloudflareFilter(),
"cloudflare_firewall_rule": resourceCloudflareFirewallRule(),
"cloudflare_healthcheck": resourceCloudflareHealthcheck(),
Expand Down
185 changes: 185 additions & 0 deletions cloudflare/resource_cloudflare_device_posture_integration.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
package cloudflare

import (
"context"
"fmt"
"log"
"strings"

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

const ws1 = "workspace_one"

func resourceCloudflareDevicePostureIntegration() *schema.Resource {
return &schema.Resource{
Schema: resourceCloudflareDevicePostureIntegrationSchema(),
Create: resourceCloudflareDevicePostureIntegrationCreate,
Read: resourceCloudflareDevicePostureIntegrationRead,
Update: resourceCloudflareDevicePostureIntegrationUpdate,
Delete: resourceCloudflareDevicePostureIntegrationDelete,
Importer: &schema.ResourceImporter{
State: resourceCloudflareDevicePostureIntegrationImport,
},
}
}

func resourceCloudflareDevicePostureIntegrationCreate(d *schema.ResourceData, meta interface{}) error {
client := meta.(*cloudflare.API)
accountID := d.Get("account_id").(string)

newDevicePostureIntegration := cloudflare.DevicePostureIntegration{
Name: d.Get("name").(string),
Type: d.Get("type").(string),
Interval: d.Get("interval").(string),
}

err := setDevicePostureIntegrationConfig(&newDevicePostureIntegration, d)
if err != nil {
return fmt.Errorf("error creating Device Posture integration with provided config: %s", err)
}
fmt.Printf("[DEBUG] Creating Cloudflare Device Posture Integration from struct: %+v\n", newDevicePostureIntegration)

// The API does not return the client_secret so it must be stored in the state func on resource create.
savedSecret := newDevicePostureIntegration.Config.ClientSecret

newDevicePostureIntegration, err = client.CreateDevicePostureIntegration(context.Background(), accountID, newDevicePostureIntegration)
if err != nil {
return fmt.Errorf("error creating Device Posture Rule for account %q: %s %+v", accountID, err, newDevicePostureIntegration)
}

d.SetId(newDevicePostureIntegration.IntegrationID)

return devicePostureIntegrationReadHelper(d, meta, savedSecret)
}

func resourceCloudflareDevicePostureIntegrationRead(d *schema.ResourceData, meta interface{}) error {
// Client secret is always read from the local state.
secret, _ := d.Get("config.0.client_secret").(string)
return devicePostureIntegrationReadHelper(d, meta, secret)
}

func devicePostureIntegrationReadHelper(d *schema.ResourceData, meta interface{}, secret string) error {
client := meta.(*cloudflare.API)
accountID := d.Get("account_id").(string)

devicePostureIntegration, err := client.DevicePostureIntegration(context.Background(), accountID, d.Id())
if err != nil {
if strings.Contains(err.Error(), "HTTP status 404") {
log.Printf("[INFO] Device posture integration %s no longer exists", d.Id())
d.SetId("")
return nil
}
return fmt.Errorf("error finding device posture integration %q: %s", d.Id(), err)
}

devicePostureIntegration.Config.ClientSecret = secret
d.Set("name", devicePostureIntegration.Name)
d.Set("type", devicePostureIntegration.Type)
d.Set("interval", devicePostureIntegration.Interval)
d.Set("config", convertIntegrationConfigToSchema(devicePostureIntegration.Config))

return nil
}

func resourceCloudflareDevicePostureIntegrationUpdate(d *schema.ResourceData, meta interface{}) error {
client := meta.(*cloudflare.API)
accountID := d.Get("account_id").(string)

updatedDevicePostureIntegration := cloudflare.DevicePostureIntegration{
IntegrationID: d.Id(),
Name: d.Get("name").(string),
Type: d.Get("type").(string),
Interval: d.Get("interval").(string),
}

err := setDevicePostureIntegrationConfig(&updatedDevicePostureIntegration, d)
if err != nil {
return fmt.Errorf("error creating Device Posture Rule with provided match input: %s", err)
}

log.Printf("[DEBUG] Updating Cloudflare device posture integration from struct: %+v", updatedDevicePostureIntegration)

devicePostureIntegration, err := client.UpdateDevicePostureIntegration(context.Background(), accountID, updatedDevicePostureIntegration)
if err != nil {
return fmt.Errorf("error updating device posture integration for account %q: %s", accountID, err)
}

if devicePostureIntegration.IntegrationID == "" {
return fmt.Errorf("failed to find device posture integration_id in update response; resource was empty")
}

return resourceCloudflareDevicePostureIntegrationRead(d, meta)
}

func resourceCloudflareDevicePostureIntegrationDelete(d *schema.ResourceData, meta interface{}) error {
client := meta.(*cloudflare.API)
appID := d.Id()
accountID := d.Get("account_id").(string)

log.Printf("[DEBUG] Deleting Cloudflare device posture integration using ID: %s", appID)

err := client.DeleteDevicePostureIntegration(context.Background(), accountID, appID)
if err != nil {
return fmt.Errorf("error deleting Device Posture Rule for account %q: %s", accountID, err)
}

resourceCloudflareDevicePostureIntegrationRead(d, meta)

return nil
}

func resourceCloudflareDevicePostureIntegrationImport(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) {
attributes := strings.SplitN(d.Id(), "/", 2)

if len(attributes) != 2 {
return nil, fmt.Errorf("invalid id (\"%s\") specified, should be in format \"accountID/devicePostureIntegrationID\"", d.Id())
}

accountID, devicePostureIntegrationID := attributes[0], attributes[1]

log.Printf("[DEBUG] Importing Cloudflare device posture integration: id %s for account %s", devicePostureIntegrationID, accountID)

d.Set("account_id", accountID)
d.SetId(devicePostureIntegrationID)

resourceCloudflareDevicePostureIntegrationRead(d, meta)

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

func setDevicePostureIntegrationConfig(integration *cloudflare.DevicePostureIntegration, d *schema.ResourceData) error {
if _, ok := d.GetOk("config"); ok {
config := cloudflare.DevicePostureIntegrationConfig{}
switch integration.Type {
case ws1:
if config.ClientID, ok = d.Get("config.0.client_id").(string); !ok {
return fmt.Errorf("client_id is a string")
}
if config.ClientSecret, ok = d.Get("config.0.client_secret").(string); !ok {
return fmt.Errorf("client_secret is a string")
}
if config.AuthUrl, ok = d.Get("config.0.auth_url").(string); !ok {
return fmt.Errorf("auth_url is a string")
}
if config.ApiUrl, ok = d.Get("config.0.api_url").(string); !ok {
return fmt.Errorf("api_url is a string")
}
integration.Config = config
default:
return fmt.Errorf("unsupported integration type:%s", integration.Type)
}
}
return nil
}

func convertIntegrationConfigToSchema(input cloudflare.DevicePostureIntegrationConfig) []interface{} {
m := map[string]interface{}{
"client_id": input.ClientID,
"client_secret": input.ClientSecret,
"auth_url": input.AuthUrl,
"api_url": input.ApiUrl,
}
return []interface{}{m}
}
83 changes: 83 additions & 0 deletions cloudflare/resource_cloudflare_device_posture_integration_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package cloudflare

import (
"context"
"fmt"
"os"
"testing"

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

func TestAccCloudflareDevicePostureIntegrationCreate(t *testing.T) {
// Temporarily unset CLOUDFLARE_API_TOKEN if it is set as the Access
// service does not yet support the API tokens and it results in
// misleading state error messages.
if os.Getenv("CLOUDFLARE_API_TOKEN") != "" {
defer func(apiToken string) {
os.Setenv("CLOUDFLARE_API_TOKEN", apiToken)
}(os.Getenv("CLOUDFLARE_API_TOKEN"))
os.Setenv("CLOUDFLARE_API_TOKEN", "")
}

rnd := generateRandomResourceName()
name := fmt.Sprintf("cloudflare_device_posture_integration.%s", rnd)

resource.Test(t, resource.TestCase{
PreCheck: func() {
testAccessAccPreCheck(t)
},
Providers: testAccProviders,
CheckDestroy: testAccCheckCloudflareDevicePostureIntegrationDestroy,
Steps: []resource.TestStep{
{
Config: testAccCloudflareDevicePostureIntegration(rnd, accountID),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr(name, "account_id", accountID),
resource.TestCheckResourceAttr(name, "name", rnd),
resource.TestCheckResourceAttr(name, "type", "workspace_one"),
resource.TestCheckResourceAttr(name, "interval", "24h"),
resource.TestCheckResourceAttr(name, "config.0.auth_url", "https://test.uemauth.vmwservices.com/connect/token"),
resource.TestCheckResourceAttr(name, "config.0.api_url", "https://example.com/api-url"),
resource.TestCheckResourceAttr(name, "config.0.client_id", "client-id"),
),
},
},
})
}

func testAccCloudflareDevicePostureIntegration(rnd, accountID string) string {
return fmt.Sprintf(`
resource "cloudflare_device_posture_integration" "%[1]s" {
account_id = "%[2]s"
name = "%[1]s"
type = "workspace_one"
interval = "24h"
config {
api_url = "https://example.com/api-url"
auth_url = "https://test.uemauth.vmwservices.com/connect/token"
client_id = "client-id"
client_secret = "client-secret"
}
}
`, rnd, accountID)
}

func testAccCheckCloudflareDevicePostureIntegrationDestroy(s *terraform.State) error {
client := testAccProvider.Meta().(*cloudflare.API)

for _, rs := range s.RootModule().Resources {
if rs.Type != "cloudflare_device_posture_integration" {
continue
}

_, err := client.DevicePostureIntegration(context.Background(), rs.Primary.Attributes["account_id"], rs.Primary.ID)
if err == nil {
return fmt.Errorf("Device Posture Integration still exists")
}
}

return nil
}
30 changes: 19 additions & 11 deletions cloudflare/resource_cloudflare_device_posture_rule.go
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,12 @@ func setDevicePostureRuleInput(rule *cloudflare.DevicePostureRule, d *schema.Res
if domain, ok := d.GetOk("input.0.domain"); ok {
input.Domain = domain.(string)
}
if complianceStatus, ok := d.GetOk("input.0.compliance_status"); ok {
input.ComplianceStatus = complianceStatus.(string)
}
if connectionID, ok := d.GetOk("input.0.connection_id"); ok {
input.ConnectionID = connectionID.(string)
}
rule.Input = input
}
}
Expand Down Expand Up @@ -218,17 +224,19 @@ func convertMatchToSchema(matches []cloudflare.DevicePostureRuleMatch) []map[str

func convertInputToSchema(input cloudflare.DevicePostureRuleInput) []map[string]interface{} {
m := map[string]interface{}{
"id": input.ID,
"path": input.Path,
"exists": input.Exists,
"thumbprint": input.Thumbprint,
"sha256": input.Sha256,
"running": input.Running,
"require_all": input.RequireAll,
"enabled": input.Enabled,
"version": input.Version,
"operator": input.Operator,
"domain": input.Domain,
"id": input.ID,
"path": input.Path,
"exists": input.Exists,
"thumbprint": input.Thumbprint,
"sha256": input.Sha256,
"running": input.Running,
"require_all": input.RequireAll,
"enabled": input.Enabled,
"version": input.Version,
"operator": input.Operator,
"domain": input.Domain,
"compliance_status": input.ComplianceStatus,
"connection_id": input.ConnectionID,
}

return []map[string]interface{}{m}
Expand Down
63 changes: 63 additions & 0 deletions cloudflare/schema_cloudflare_device_posture_integration.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package cloudflare

import (
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
)

func resourceCloudflareDevicePostureIntegrationSchema() map[string]*schema.Schema {
return map[string]*schema.Schema{
"account_id": {
Type: schema.TypeString,
Required: true,
},
"id": {
Type: schema.TypeString,
Computed: true,
},
"name": {
Type: schema.TypeString,
Required: true,
},
"type": {
Type: schema.TypeString,
Required: true,
ValidateFunc: validation.StringInSlice([]string{ws1}, false),
},
"identifier": {
Type: schema.TypeString,
Optional: true,
},
"interval": {
Type: schema.TypeString,
Optional: true,
},
"config": {
Type: schema.TypeList,
Optional: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"auth_url": {
Type: schema.TypeString,
Optional: true,
ValidateFunc: validation.IsURLWithHTTPS,
},
"api_url": {
Type: schema.TypeString,
Optional: true,
ValidateFunc: validation.IsURLWithHTTPS,
},
"client_id": {
Type: schema.TypeString,
Optional: true,
},
"client_secret": {
Type: schema.TypeString,
Optional: true,
Sensitive: true,
},
},
},
},
}
}
Loading