Skip to content

Instantly share code, notes, and snippets.

@ffalor
Last active May 9, 2024 00:49
Show Gist options
  • Save ffalor/3f13621b0d1a4def8b14d513cc0675e9 to your computer and use it in GitHub Desktop.
Save ffalor/3f13621b0d1a4def8b14d513cc0675e9 to your computer and use it in GitHub Desktop.
package prevention_policy
import (
"context"
"fmt"
"time"
"github.com/crowdstrike/gofalcon/falcon/client"
"github.com/crowdstrike/gofalcon/falcon/client/prevention_policies"
"github.com/crowdstrike/gofalcon/falcon/models"
"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
"github.com/hashicorp/terraform-plugin-framework/path"
"github.com/hashicorp/terraform-plugin-framework/resource"
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/booldefault"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
"github.com/hashicorp/terraform-plugin-framework/types"
)
// Ensure the implementation satisfies the expected interfaces.
var (
_ resource.Resource = &preventionPolicyResource{}
_ resource.ResourceWithConfigure = &preventionPolicyResource{}
_ resource.ResourceWithImportState = &preventionPolicyResource{}
// _ resource.ResourceWithValidateConfig = &preventionPolicyResource{}
)
// NewPreventionPolicyResource is a helper function to simplify the provider implementation.
func NewPreventionPolicyResource() resource.Resource {
return &preventionPolicyResource{}
}
// preventionPolicyResource is the resource implementation.
type preventionPolicyResource struct {
client *client.CrowdStrikeAPISpecification
}
var toggleAttribute = schema.BoolAttribute{
Optional: true,
Computed: true,
Description: "Whether to enable the setting.",
Default: booldefault.StaticBool(false),
}
var mlSliderLevels = []string{"DISABLED", "CAUTIOUS", "MODERATE", "AGGRESSIVE", "EXTRA_AGGRESSIVE"}
var mlSliderDetectionAttribute = schema.StringAttribute{
Required: true,
Description: "Machine learning level for detection.",
Validators: []validator.String{
stringvalidator.OneOf(mlSliderLevels...),
},
}
var mlSliderPreventionAttribute = schema.StringAttribute{
Required: true,
Description: "Machine learning level for prevention.",
Validators: []validator.String{
stringvalidator.OneOf(mlSliderLevels...),
},
}
var mlSliderAttribute = schema.SingleNestedAttribute{
Optional: true,
Computed: true,
Description: "ml slider setting.",
Attributes: map[string]schema.Attribute{
"detection": mlSliderDetectionAttribute,
"prevention": mlSliderPreventionAttribute,
},
}
type preventionPolicyResourceModel struct {
ID types.String `tfsdk:"id"`
Enabled types.Bool `tfsdk:"enabled"`
Name types.String `tfsdk:"name"`
Description types.String `tfsdk:"description"`
PlatformName types.String `tfsdk:"platform_name"`
LastUpdated types.String `tfsdk:"last_updated"`
Windows windowsPolicy `tfsdk:"windows"`
}
type toggle struct {
Enabled types.Bool `tfsdk:"enabled" json:"enabled"`
}
type mlSlider struct {
Detection types.String `tfsdk:"detection"`
Prevention types.String `tfsdk:"prevention"`
}
type windowsPolicy struct {
AdditionalUserModeData toggle `tfsdk:"additional_user_mode_data"`
CloudAntiMalwareForMicrosoftOfficeFiles mlSlider `tfsdk:"cloud_anti_malware_microsoft_office_files"`
}
// Configure adds the provider configured client to the resource.
func (r *preventionPolicyResource) Configure(
ctx context.Context,
req resource.ConfigureRequest,
resp *resource.ConfigureResponse,
) {
if req.ProviderData == nil {
return
}
client, ok := req.ProviderData.(*client.CrowdStrikeAPISpecification)
if !ok {
resp.Diagnostics.AddError(
"Unexpected Resource Configure Type",
fmt.Sprintf(
"Expected *client.CrowdStrikeAPISpecification, got: %T. Please report this issue to the provider developers.",
req.ProviderData,
),
)
return
}
r.client = client
}
// Metadata returns the resource type name.
func (r *preventionPolicyResource) Metadata(
_ context.Context,
req resource.MetadataRequest,
resp *resource.MetadataResponse,
) {
resp.TypeName = req.ProviderTypeName + "_prevention_policy"
}
// Schema defines the schema for the resource.
func (r *preventionPolicyResource) Schema(
_ context.Context,
_ resource.SchemaRequest,
resp *resource.SchemaResponse,
) {
resp.Schema = schema.Schema{
Attributes: map[string]schema.Attribute{
"id": schema.StringAttribute{
Computed: true,
Description: "Identifier for the prevention policy.",
PlanModifiers: []planmodifier.String{
stringplanmodifier.UseStateForUnknown(),
},
},
"last_updated": schema.StringAttribute{
Computed: true,
Description: "Timestamp of the last Terraform update of the resource.",
},
"name": schema.StringAttribute{
Required: true,
Description: "Name of the prevention policy.",
},
"enabled": schema.BoolAttribute{
Optional: true,
Computed: true,
Description: "Enable the prevention policy.",
Default: booldefault.StaticBool(true),
},
"description": schema.StringAttribute{
Optional: true,
Description: "Description of the prevention policy.",
},
// todo: make this case insensitive
"platform_name": schema.StringAttribute{
Required: true,
Description: "Platform for the sensor update policy to manage. (Windows, Mac, Linux)",
Validators: []validator.String{
stringvalidator.OneOf("Windows", "Linux", "Mac"),
},
},
"windows": schema.SingleNestedAttribute{
Optional: true,
Description: "Block to configure a windows prevention policy.",
Attributes: map[string]schema.Attribute{
"additional_user_mode_data": toggleAttribute,
"cloud_anti_malware_microsoft_office_files": mlSliderAttribute,
},
},
},
}
}
type apiToggle struct {
Enabled bool `json:"enabled"`
}
type apiMlSlider struct {
Detection string `json:"detection"`
Prevention string `json:"prevention"`
}
// Create creates the resource and sets the initial Terraform state.
func (r *preventionPolicyResource) Create(
ctx context.Context,
req resource.CreateRequest,
resp *resource.CreateResponse,
) {
var plan preventionPolicyResourceModel
diags := req.Plan.Get(ctx, &plan)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
windowsPreventionSettings := generateWindowsPreventionSettings(ctx, plan)
createParams := prevention_policies.CreatePreventionPoliciesParams{
Context: ctx,
Body: &models.PreventionCreatePoliciesReqV1{
Resources: []*models.PreventionCreatePolicyReqV1{
{
Name: plan.Name.ValueStringPointer(),
Description: plan.Description.ValueString(),
PlatformName: plan.PlatformName.ValueStringPointer(),
Settings: windowsPreventionSettings,
},
},
},
}
res, err := r.client.PreventionPolicies.CreatePreventionPolicies(&createParams)
// todo: if we should handle scope and timeout errors instead of giving a vague error
if err != nil {
resp.Diagnostics.AddError(
"Error creating prevention policy",
"Could not create prevention policy, unexpected error: "+err.Error(),
)
return
}
preventionPolicy := res.Payload.Resources[0]
plan.ID = types.StringValue(*preventionPolicy.ID)
plan.Description = types.StringValue(*preventionPolicy.Description)
plan.PlatformName = types.StringValue(*preventionPolicy.PlatformName)
plan.Name = types.StringValue(*preventionPolicy.Name)
plan.Enabled = types.BoolValue(*preventionPolicy.Enabled)
plan.LastUpdated = types.StringValue(time.Now().Format(time.RFC850))
diags = resp.State.Set(ctx, plan)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
}
// Read refreshes the Terraform state with the latest data.
func (r *preventionPolicyResource) Read(
ctx context.Context,
req resource.ReadRequest,
resp *resource.ReadResponse,
) {
var state preventionPolicyResourceModel
diags := req.State.Get(ctx, &state)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
// Set refreshed state
diags = resp.State.Set(ctx, &state)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
}
// Update updates the resource and sets the updated Terraform state on success.
func (r *preventionPolicyResource) Update(
ctx context.Context,
req resource.UpdateRequest,
resp *resource.UpdateResponse,
) {
// Retrieve values from plan
var plan preventionPolicyResourceModel
diags := req.Plan.Get(ctx, &plan)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
diags = resp.State.Set(ctx, plan)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
}
// Delete deletes the resource and removes the Terraform state on success.
func (r *preventionPolicyResource) Delete(
ctx context.Context,
req resource.DeleteRequest,
resp *resource.DeleteResponse,
) {
var state preventionPolicyResourceModel
diags := req.State.Get(ctx, &state)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
}
// ImportState implements the logic to support resource imports.
func (r *preventionPolicyResource) ImportState(
ctx context.Context,
req resource.ImportStateRequest,
resp *resource.ImportStateResponse,
) {
// Retrieve import ID and save to id attribute
resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp)
}
// generateWindowsPreventionSettings maps plan prevention settings to api params for create and update.
func generateWindowsPreventionSettings(
ctx context.Context,
config preventionPolicyResourceModel,
) []*models.PreventionSettingReqV1 {
preventionSettings := []*models.PreventionSettingReqV1{}
s1 := "CloudAntiMalwareForMicrosoftOfficeFiles"
s2 := "AdditionalUserModeData"
preventionSettings = append(
preventionSettings,
&models.PreventionSettingReqV1{
ID: &s1,
Value: apiToggle{Enabled: config.Windows.AdditionalUserModeData.Enabled.ValueBool()},
},
)
preventionSettings = append(
preventionSettings,
&models.PreventionSettingReqV1{
ID: &s2,
Value: apiMlSlider{
Prevention: config.Windows.CloudAntiMalwareForMicrosoftOfficeFiles.Prevention.ValueString(),
Detection: config.Windows.CloudAntiMalwareForMicrosoftOfficeFiles.Detection.ValueString(),
},
},
)
return preventionSettings
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment