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

allow crowdstrike_sensor_update_policy to assign host groups #4

Merged
merged 1 commit into from
Apr 30, 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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -90,5 +90,6 @@ jobs:
TF_ACC: "1"
FALCON_CLIENT_ID: ${{ secrets.FALCON_CLIENT_ID }}
FALCON_CLIENT_SECRET: ${{ secrets.FALCON_CLIENT_SECRET }}
HOST_GROUP_ID: ${{ secrets.HOST_GROUP_ID }}
run: go test -v -cover ./internal/provider/

2 changes: 2 additions & 0 deletions docs/resources/sensor_update_policy.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ resource "crowdstrike_sensor_update_policy" "test" {
platform_name = "Windows"
build = "18110"
uninstall_protection = false
# host_groups = ["host_group_id"]
}

output "sensor_policy" {
Expand All @@ -53,6 +54,7 @@ output "sensor_policy" {

- `description` (String) Sensor Update Policy description
- `enabled` (Boolean) Enable the Sensor Update Policy
- `host_groups` (List of String) Host Group ids to attach to the policy
- `uninstall_protection` (Boolean) Enable uninstall protection

### Read-Only
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ resource "crowdstrike_sensor_update_policy" "test" {
platform_name = "Windows"
build = "18110"
uninstall_protection = false
# host_groups = ["host_group_id"]
}

output "sensor_policy" {
Expand Down
1 change: 1 addition & 0 deletions internal/provider/host_group_resource_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ func TestAccHostGroupResource(t *testing.T) {
rName := acctest.RandomWithPrefix("tf-acceptance-test")
resource.Test(t, resource.TestCase{
ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
PreCheck: func() { testAccPreCheck(t) },
Steps: []resource.TestStep{
// Create and Read testing
{
Expand Down
12 changes: 12 additions & 0 deletions internal/provider/provider_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package provider

import (
"os"
"testing"

"github.com/hashicorp/terraform-plugin-framework/providerserver"
"github.com/hashicorp/terraform-plugin-go/tfprotov6"
)
Expand All @@ -18,3 +21,12 @@ provider "crowdstrike" {}
var testAccProtoV6ProviderFactories = map[string]func() (tfprotov6.ProviderServer, error){
"crowdstrike": providerserver.NewProtocol6WithError(New("test")()),
}

func testAccPreCheck(t *testing.T) {
requiredEnvVars := []string{"FALCON_CLIENT_ID", "FALCON_CLIENT_SECRET", "HOST_GROUP_ID"}
for _, envVar := range requiredEnvVars {
if v := os.Getenv(envVar); v == "" {
t.Fatalf("%s must be set for acceptance tests", envVar)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
func TestAccSensorUpdatePolicyBuildsDataSource(t *testing.T) {
resource.Test(t, resource.TestCase{
ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
PreCheck: func() { testAccPreCheck(t) },
Steps: []resource.TestStep{
// Read testing
{
Expand Down
195 changes: 189 additions & 6 deletions internal/provider/sensor_update_policy_resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,14 @@ package provider
import (
"context"
"fmt"
"strings"
"time"

"github.com/crowdstrike/gofalcon/falcon/client"
"github.com/crowdstrike/gofalcon/falcon/client/sensor_update_policies"
"github.com/crowdstrike/gofalcon/falcon/models"
"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
"github.com/hashicorp/terraform-plugin-framework/diag"
"github.com/hashicorp/terraform-plugin-framework/path"
"github.com/hashicorp/terraform-plugin-framework/resource"
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
Expand All @@ -26,6 +28,17 @@ var (
_ resource.ResourceWithImportState = &sensorUpdatePolicyResource{}
)

type hostGroupAction int

const (
removeHostGroup hostGroupAction = iota
addHostGroup
)

func (h hostGroupAction) String() string {
return [...]string{"remove-host-group", "add-host-group"}[h]
}

// NewSensorUpdatePolicyResource is a helper function to simplify the provider implementation.
func NewSensorUpdatePolicyResource() resource.Resource {
return &sensorUpdatePolicyResource{}
Expand All @@ -46,6 +59,7 @@ type sensorUpdatePolicyResourceModel struct {
PlatformName types.String `tfsdk:"platform_name"`
UninstallProtection types.Bool `tfsdk:"uninstall_protection"`
LastUpdated types.String `tfsdk:"last_updated"`
HostGroups types.List `tfsdk:"host_groups"`
}

// Configure adds the provider configured client to the resource.
Expand Down Expand Up @@ -133,6 +147,11 @@ func (r *sensorUpdatePolicyResource) Schema(
Description: "Enable uninstall protection",
Default: booldefault.StaticBool(false),
},
"host_groups": schema.ListAttribute{
Optional: true,
ElementType: types.StringType,
Description: "Host Group ids to attach to the policy",
},
},
}
}
Expand All @@ -151,16 +170,13 @@ func (r *sensorUpdatePolicyResource) Create(
return
}

policyName := plan.Name.ValueString()
platformName := plan.PlatformName.ValueString()

policyParams := sensor_update_policies.CreateSensorUpdatePoliciesV2Params{
Context: ctx,
Body: &models.SensorUpdateCreatePoliciesReqV2{
Resources: []*models.SensorUpdateCreatePolicyReqV2{
{
Name: &policyName,
PlatformName: &platformName,
Name: plan.Name.ValueStringPointer(),
PlatformName: plan.PlatformName.ValueStringPointer(),
Description: plan.Description.ValueString(),
Settings: &models.SensorUpdateSettingsReqV2{
Build: plan.Build.ValueString(),
Expand All @@ -176,7 +192,6 @@ func (r *sensorUpdatePolicyResource) Create(
} else {
uninstallProtection = "DISABLED"
}

policyParams.Body.Resources[0].Settings.UninstallProtection = uninstallProtection

policy, err := r.client.SensorUpdatePolicies.CreateSensorUpdatePoliciesV2(&policyParams)
Expand Down Expand Up @@ -211,6 +226,24 @@ func (r *sensorUpdatePolicyResource) Create(
plan.Enabled = types.BoolValue(*actionResp.Payload.Resources[0].Enabled)
}

if len(plan.HostGroups.Elements()) != 0 {
var hostGroupIDs []string
resp.Diagnostics.Append(plan.HostGroups.ElementsAs(ctx, &hostGroupIDs, false)...)
if resp.Diagnostics.HasError() {
return
}

err = r.updateHostGroups(ctx, addHostGroup, hostGroupIDs, plan.ID.ValueString())

if err != nil {
resp.Diagnostics.AddError(
"Error assinging host group to policy",
"Could not assign host group to policy, unexpected error: "+err.Error(),
)
return
}
}

diags = resp.State.Set(ctx, plan)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
Expand Down Expand Up @@ -254,12 +287,26 @@ func (r *sensorUpdatePolicyResource) Read(
state.Build = types.StringValue(*policyResource.Settings.Build)
state.PlatformName = types.StringValue(*policyResource.PlatformName)
state.Enabled = types.BoolValue(*policyResource.Enabled)

if *policyResource.Settings.UninstallProtection == "ENABLED" {
state.UninstallProtection = types.BoolValue(true)
} else {
state.UninstallProtection = types.BoolValue(false)
}

var hostGroups []string
for _, hostGroup := range policyResource.Groups {
hostGroups = append(hostGroups, *hostGroup.ID)
}

hostGroupIDs, diags := types.ListValueFrom(ctx, types.StringType, hostGroups)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}

state.HostGroups = hostGroupIDs

// Set refreshed state
diags = resp.State.Set(ctx, &state)
resp.Diagnostics.Append(diags...)
Expand All @@ -281,6 +328,51 @@ func (r *sensorUpdatePolicyResource) Update(
if resp.Diagnostics.HasError() {
return
}
// Retrieve values from state
var state sensorUpdatePolicyResourceModel
diags = req.State.Get(ctx, &state)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}

hostGroupsToAdd, hostGroupsToRemove, diags := r.getHostGroupsToModify(ctx, plan, state)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}

if len(hostGroupsToAdd) != 0 {
err := r.updateHostGroups(ctx, addHostGroup, hostGroupsToAdd, plan.ID.ValueString())
if err != nil {
resp.Diagnostics.AddError(
"Error updating CrowdStrike sensor update policy",
fmt.Sprintf(
"Could not add host groups: (%s) to policy with id: %s \n\n %s",
strings.Join(hostGroupsToAdd, ", "),
plan.ID.ValueString(),
err.Error(),
),
)
return
}
}

if len(hostGroupsToRemove) != 0 {
err := r.updateHostGroups(ctx, removeHostGroup, hostGroupsToRemove, plan.ID.ValueString())
if err != nil {
resp.Diagnostics.AddError(
"Error updating CrowdStrike sensor update policy",
fmt.Sprintf(
"Could not remove host groups: (%s) to policy with id: %s \n\n %s",
strings.Join(hostGroupsToAdd, ", "),
plan.ID.ValueString(),
err.Error(),
),
)
return
}
}

policyParams := sensor_update_policies.UpdateSensorUpdatePoliciesV2Params{
Context: ctx,
Expand Down Expand Up @@ -345,6 +437,19 @@ func (r *sensorUpdatePolicyResource) Update(

plan.Enabled = types.BoolValue(*actionResp.Payload.Resources[0].Enabled)

var hostGroups []string
for _, hostGroup := range policyResource.Groups {
hostGroups = append(hostGroups, *hostGroup.ID)
}

hostGroupIDs, diags := types.ListValueFrom(ctx, types.StringType, hostGroups)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}

plan.HostGroups = hostGroupIDs

diags = resp.State.Set(ctx, plan)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
Expand Down Expand Up @@ -430,3 +535,81 @@ func (r *sensorUpdatePolicyResource) updatePolicyEnabledState(

return *res, err
}

// updateHostGroups will remove or add a slice of host groups
// to a slice of sensor update policies.
func (r *sensorUpdatePolicyResource) updateHostGroups(
ctx context.Context,
action hostGroupAction,
hostGroupIDs []string,
policyID string,
) error {
var actionParams []*models.MsaspecActionParameter
name := "group_id"

for _, hostGroup := range hostGroupIDs {
actionParam := &models.MsaspecActionParameter{
Name: &name,
Value: &hostGroup,
}

actionParams = append(actionParams, actionParam)
}

_, err := r.client.SensorUpdatePolicies.PerformSensorUpdatePoliciesAction(
&sensor_update_policies.PerformSensorUpdatePoliciesActionParams{
Context: ctx,
ActionName: action.String(),
Body: &models.MsaEntityActionRequestV2{
ActionParameters: actionParams,
Ids: []string{policyID},
},
},
)

return err
}

// getHostGroupsToModify takes in the planned state and current state and returns
// a list of host group ids to remove and add.
func (r *sensorUpdatePolicyResource) getHostGroupsToModify(
ctx context.Context,
plan, state sensorUpdatePolicyResourceModel,
) (hostGroupsToAdd []string, hostGroupsToRemove []string, diags diag.Diagnostics) {
var planHostGroupIDs, stateHostGroupIds []string
planMap := make(map[string]bool)
stateMap := make(map[string]bool)

d := plan.HostGroups.ElementsAs(ctx, &planHostGroupIDs, false)
diags.Append(d...)
if diags.HasError() {
return
}
d = state.HostGroups.ElementsAs(ctx, &stateHostGroupIds, false)
diags.Append(d...)
if diags.HasError() {
return
}

for _, id := range planHostGroupIDs {
planMap[id] = true
}

for _, id := range stateHostGroupIds {
stateMap[id] = true
}

for _, id := range planHostGroupIDs {
if !stateMap[id] {
hostGroupsToAdd = append(hostGroupsToAdd, id)
}
}

for _, id := range stateHostGroupIds {
if !planMap[id] {
hostGroupsToRemove = append(hostGroupsToRemove, id)
}
}

return
}
Loading
Loading