From 6322019dac6eb601470b696679f165e3488a759f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Zhou=F0=9F=92=AF?= Date: Fri, 15 Nov 2024 15:33:34 -0500 Subject: [PATCH] supporting cws multi-policy in terraform --- ...a_source_datadog_csm_threats_agent_rule.go | 22 +- .../data_source_datadog_csm_threats_policy.go | 109 ++++++++ datadog/fwprovider/framework_provider.go | 3 + ...dog_csm_threats_multi_policy_agent_rule.go | 248 ++++++++++++++++++ .../resource_datadog_csm_threats_policy.go | 245 +++++++++++++++++ ...m_threats_multi_policy_agent_rules_test.go | 124 +++++++++ ...ource_datadog_csm_threats_policies_test.go | 107 ++++++++ datadog/tests/provider_test.go | 4 + ...sm_threats_multi_policy_agent_rule_test.go | 133 ++++++++++ ...esource_datadog_csm_threats_policy_test.go | 121 +++++++++ docs/data-sources/csm_threats_agent_rules.md | 4 + docs/data-sources/csm_threats_policies.md | 33 +++ .../csm_threats_multi_policy_agent_rule.md | 31 +++ docs/resources/csm_threats_policy.md | 30 +++ 14 files changed, 1209 insertions(+), 5 deletions(-) create mode 100644 datadog/fwprovider/data_source_datadog_csm_threats_policy.go create mode 100644 datadog/fwprovider/resource_datadog_csm_threats_multi_policy_agent_rule.go create mode 100644 datadog/fwprovider/resource_datadog_csm_threats_policy.go create mode 100644 datadog/tests/data_source_datadog_csm_threats_multi_policy_agent_rules_test.go create mode 100644 datadog/tests/data_source_datadog_csm_threats_policies_test.go create mode 100644 datadog/tests/resource_datadog_csm_threats_multi_policy_agent_rule_test.go create mode 100644 datadog/tests/resource_datadog_csm_threats_policy_test.go create mode 100644 docs/data-sources/csm_threats_policies.md create mode 100644 docs/resources/csm_threats_multi_policy_agent_rule.md create mode 100644 docs/resources/csm_threats_policy.md diff --git a/datadog/fwprovider/data_source_datadog_csm_threats_agent_rule.go b/datadog/fwprovider/data_source_datadog_csm_threats_agent_rule.go index d6e160a1a..bbdc98ace 100644 --- a/datadog/fwprovider/data_source_datadog_csm_threats_agent_rule.go +++ b/datadog/fwprovider/data_source_datadog_csm_threats_agent_rule.go @@ -25,6 +25,7 @@ type csmThreatsAgentRulesDataSource struct { } type csmThreatsAgentRulesDataSourceModel struct { + PolicyId types.String `tfsdk:"policy_id"` Id types.String `tfsdk:"id"` AgentRulesIds types.List `tfsdk:"agent_rules_ids"` AgentRules []csmThreatsAgentRuleModel `tfsdk:"agent_rules"` @@ -51,7 +52,12 @@ func (r *csmThreatsAgentRulesDataSource) Read(ctx context.Context, request datas return } - res, _, err := r.api.ListCSMThreatsAgentRules(r.auth) + policyId := state.PolicyId.ValueStringPointer() + params := datadogV2.NewListCSMThreatsAgentRulesOptionalParameters() + if !state.PolicyId.IsNull() && !state.PolicyId.IsUnknown() { + params.WithPolicyId(*policyId) + } + res, _, err := r.api.ListCSMThreatsAgentRules(r.auth, *params) if err != nil { response.Diagnostics.Append(utils.FrameworkErrorDiag(err, "error while fetching agent rules")) return @@ -75,7 +81,7 @@ func (r *csmThreatsAgentRulesDataSource) Read(ctx context.Context, request datas } stateId := strings.Join(agentRuleIds, "--") - state.Id = types.StringValue(computeAgentRulesDataSourceID(&stateId)) + state.Id = types.StringValue(computeDataSourceID(&stateId)) tfAgentRuleIds, diags := types.ListValueFrom(ctx, types.StringType, agentRuleIds) response.Diagnostics.Append(diags...) state.AgentRulesIds = tfAgentRuleIds @@ -84,11 +90,11 @@ func (r *csmThreatsAgentRulesDataSource) Read(ctx context.Context, request datas response.Diagnostics.Append(response.State.Set(ctx, &state)...) } -func computeAgentRulesDataSourceID(agentruleIds *string) string { +func computeDataSourceID(ids *string) string { // Key for hashing var b strings.Builder - if agentruleIds != nil { - b.WriteString(*agentruleIds) + if ids != nil { + b.WriteString(*ids) } keyStr := b.String() h := sha256.New() @@ -101,6 +107,12 @@ func (*csmThreatsAgentRulesDataSource) Schema(_ context.Context, _ datasource.Sc response.Schema = schema.Schema{ Description: "Use this data source to retrieve information about existing Agent rules.", Attributes: map[string]schema.Attribute{ + // Input + "policy_id": schema.StringAttribute{ + Description: "Listing only the rules in the policy with this field as the ID", + Optional: true, + }, + // Output "id": utils.ResourceIDAttribute(), "agent_rules_ids": schema.ListAttribute{ Computed: true, diff --git a/datadog/fwprovider/data_source_datadog_csm_threats_policy.go b/datadog/fwprovider/data_source_datadog_csm_threats_policy.go new file mode 100644 index 000000000..c8a3455b0 --- /dev/null +++ b/datadog/fwprovider/data_source_datadog_csm_threats_policy.go @@ -0,0 +1,109 @@ +package fwprovider + +import ( + "context" + "strings" + + "github.com/DataDog/datadog-api-client-go/v2/api/datadogV2" + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/datasource" + "github.com/hashicorp/terraform-plugin-framework/datasource/schema" + "github.com/hashicorp/terraform-plugin-framework/types" + + "github.com/terraform-providers/terraform-provider-datadog/datadog/internal/utils" +) + +var ( + _ datasource.DataSourceWithConfigure = &csmThreatsPoliciesDataSource{} +) + +type csmThreatsPoliciesDataSource struct { + api *datadogV2.CSMThreatsApi + auth context.Context +} + +type csmThreatsPoliciesDataSourceModel struct { + Id types.String `tfsdk:"id"` + PolicyIds types.List `tfsdk:"policy_ids"` + Policies []csmThreatsPolicyModel `tfsdk:"policies"` +} + +func NewCSMThreatsPoliciesDataSource() datasource.DataSource { + return &csmThreatsPoliciesDataSource{} +} + +func (r *csmThreatsPoliciesDataSource) Configure(_ context.Context, request datasource.ConfigureRequest, _ *datasource.ConfigureResponse) { + providerData := request.ProviderData.(*FrameworkProvider) + r.api = providerData.DatadogApiInstances.GetCSMThreatsApiV2() + r.auth = providerData.Auth +} + +func (*csmThreatsPoliciesDataSource) Metadata(_ context.Context, _ datasource.MetadataRequest, response *datasource.MetadataResponse) { + response.TypeName = "csm_threats_policies" +} + +func (r *csmThreatsPoliciesDataSource) Read(ctx context.Context, request datasource.ReadRequest, response *datasource.ReadResponse) { + var state csmThreatsPoliciesDataSourceModel + response.Diagnostics.Append(request.Config.Get(ctx, &state)...) + if response.Diagnostics.HasError() { + return + } + + res, _, err := r.api.ListCSMThreatsAgentPolicies(r.auth) + if err != nil { + response.Diagnostics.Append(utils.FrameworkErrorDiag(err, "error while fetching agent rules")) + return + } + + data := res.GetData() + policyIds := make([]string, len(data)) + policies := make([]csmThreatsPolicyModel, len(data)) + + for idx, policy := range res.GetData() { + var policyModel csmThreatsPolicyModel + policyModel.Id = types.StringValue(policy.GetId()) + attributes := policy.Attributes + policyModel.Name = types.StringValue(attributes.GetName()) + policyModel.Description = types.StringValue(attributes.GetDescription()) + policyModel.Enabled = types.BoolValue(attributes.GetEnabled()) + policyModel.Tags, _ = types.SetValueFrom(ctx, types.StringType, attributes.GetHostTags()) + policyIds[idx] = policy.GetId() + policies[idx] = policyModel + } + + stateId := strings.Join(policyIds, "--") + state.Id = types.StringValue(computeDataSourceID(&stateId)) + tfAgentRuleIds, diags := types.ListValueFrom(ctx, types.StringType, policyIds) + response.Diagnostics.Append(diags...) + state.PolicyIds = tfAgentRuleIds + state.Policies = policies + + response.Diagnostics.Append(response.State.Set(ctx, &state)...) +} + +func (*csmThreatsPoliciesDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, response *datasource.SchemaResponse) { + response.Schema = schema.Schema{ + Description: "Use this data source to retrieve information about existing policies.", + Attributes: map[string]schema.Attribute{ + "id": utils.ResourceIDAttribute(), + "policy_ids": schema.ListAttribute{ + Computed: true, + Description: "List of IDs for the policies.", + ElementType: types.StringType, + }, + "policies": schema.ListAttribute{ + Computed: true, + Description: "List of policies", + ElementType: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "id": types.StringType, + "tags": types.SetType{ElemType: types.StringType}, + "name": types.StringType, + "description": types.StringType, + "enabled": types.BoolType, + }, + }, + }, + }, + } +} diff --git a/datadog/fwprovider/framework_provider.go b/datadog/fwprovider/framework_provider.go index e6bc7cd90..be2baa9b8 100644 --- a/datadog/fwprovider/framework_provider.go +++ b/datadog/fwprovider/framework_provider.go @@ -66,6 +66,8 @@ var Resources = []func() resource.Resource{ NewWebhookResource, NewWebhookCustomVariableResource, NewLogsCustomDestinationResource, + NewCSMThreatsPolicyResource, + NewCSMThreatsMultiPolicyAgentRuleResource, } var Datasources = []func() datasource.DataSource{ @@ -86,6 +88,7 @@ var Datasources = []func() datasource.DataSource{ NewDatadogRoleUsersDataSource, NewSecurityMonitoringSuppressionDataSource, NewCSMThreatsAgentRulesDataSource, + NewCSMThreatsPoliciesDataSource, } // FrameworkProvider struct diff --git a/datadog/fwprovider/resource_datadog_csm_threats_multi_policy_agent_rule.go b/datadog/fwprovider/resource_datadog_csm_threats_multi_policy_agent_rule.go new file mode 100644 index 000000000..c0f06e3da --- /dev/null +++ b/datadog/fwprovider/resource_datadog_csm_threats_multi_policy_agent_rule.go @@ -0,0 +1,248 @@ +package fwprovider + +import ( + "context" + "strings" + + "github.com/DataDog/datadog-api-client-go/v2/api/datadogV2" + "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/planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/types" + + "github.com/terraform-providers/terraform-provider-datadog/datadog/internal/utils" +) + +type csmThreatsMultiPolicyAgentRuleResource struct { + api *datadogV2.CSMThreatsApi + auth context.Context +} + +type csmThreatsMultiPolicyAgentRuleModel struct { + Id types.String `tfsdk:"id"` + PolicyId types.String `tfsdk:"policy_id"` + Name types.String `tfsdk:"name"` + Description types.String `tfsdk:"description"` + Enabled types.Bool `tfsdk:"enabled"` + Expression types.String `tfsdk:"expression"` +} + +func NewCSMThreatsMultiPolicyAgentRuleResource() resource.Resource { + return &csmThreatsMultiPolicyAgentRuleResource{} +} + +func (r *csmThreatsMultiPolicyAgentRuleResource) Metadata(_ context.Context, request resource.MetadataRequest, response *resource.MetadataResponse) { + response.TypeName = "csm_threats_multi_policy_agent_rule" +} + +func (r *csmThreatsMultiPolicyAgentRuleResource) Configure(_ context.Context, request resource.ConfigureRequest, response *resource.ConfigureResponse) { + providerData := request.ProviderData.(*FrameworkProvider) + r.api = providerData.DatadogApiInstances.GetCSMThreatsApiV2() + r.auth = providerData.Auth +} + +func (r *csmThreatsMultiPolicyAgentRuleResource) Schema(_ context.Context, _ resource.SchemaRequest, response *resource.SchemaResponse) { + response.Schema = schema.Schema{ + Description: "Provides a Datadog CSM Threats Agent Rule API resource.", + Attributes: map[string]schema.Attribute{ + "id": utils.ResourceIDAttribute(), + "policy_id": schema.StringAttribute{ + Required: true, + Description: "The ID of the agent policy in which the rule is saved", + }, + "name": schema.StringAttribute{ + Required: true, + Description: "The name of the Agent rule.", + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + "description": schema.StringAttribute{ + Optional: true, + Description: "A description for the Agent rule.", + Computed: true, + }, + "enabled": schema.BoolAttribute{ + Required: true, + Description: "Indicates Whether the Agent rule is enabled.", + }, + "expression": schema.StringAttribute{ + Required: true, + Description: "The SECL expression of the Agent rule", + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + }, + } +} + +func (r *csmThreatsMultiPolicyAgentRuleResource) ImportState(ctx context.Context, request resource.ImportStateRequest, response *resource.ImportStateResponse) { + result := strings.SplitN(request.ID, ":", 2) + if len(result) != 2 { + response.Diagnostics.AddError("error retrieving policy_id or rule_id from given ID", "") + return + } + + response.Diagnostics.Append(response.State.SetAttribute(ctx, path.Root("policy_id"), result[0])...) + response.Diagnostics.Append(response.State.SetAttribute(ctx, path.Root("id"), result[1])...) +} + +func (r *csmThreatsMultiPolicyAgentRuleResource) Create(ctx context.Context, request resource.CreateRequest, response *resource.CreateResponse) { + var state csmThreatsMultiPolicyAgentRuleModel + response.Diagnostics.Append(request.Plan.Get(ctx, &state)...) + if response.Diagnostics.HasError() { + return + } + + csmThreatsMutex.Lock() + defer csmThreatsMutex.Unlock() + + agentRulePayload, err := r.buildCreateCSMThreatsAgentRulePayload(&state) + if err != nil { + response.Diagnostics.AddError("error while parsing resource", err.Error()) + } + + res, _, err := r.api.CreateCSMThreatsAgentRule(r.auth, *agentRulePayload) + if err != nil { + response.Diagnostics.Append(utils.FrameworkErrorDiag(err, "error creating agent rule")) + return + } + if err := utils.CheckForUnparsed(response); err != nil { + response.Diagnostics.Append(utils.FrameworkErrorDiag(err, "response contains unparsed object")) + return + } + + r.updateStateFromResponse(ctx, &state, &res) + response.Diagnostics.Append(response.State.Set(ctx, &state)...) +} + +func (r *csmThreatsMultiPolicyAgentRuleResource) Read(ctx context.Context, request resource.ReadRequest, response *resource.ReadResponse) { + var state csmThreatsMultiPolicyAgentRuleModel + response.Diagnostics.Append(request.State.Get(ctx, &state)...) + if response.Diagnostics.HasError() { + return + } + + agentRuleId := state.Id.ValueString() + policyId := state.PolicyId.ValueString() + res, httpResponse, err := r.api.GetCSMThreatsAgentRule(r.auth, agentRuleId, *datadogV2.NewGetCSMThreatsAgentRuleOptionalParameters().WithPolicyId(policyId)) + if err != nil { + if httpResponse != nil && httpResponse.StatusCode == 404 { + response.State.RemoveResource(ctx) + return + } + response.Diagnostics.Append(utils.FrameworkErrorDiag(err, "error fetching agent rule")) + return + } + if err := utils.CheckForUnparsed(response); err != nil { + response.Diagnostics.Append(utils.FrameworkErrorDiag(err, "response contains unparsed object")) + return + } + + r.updateStateFromResponse(ctx, &state, &res) + response.Diagnostics.Append(response.State.Set(ctx, &state)...) +} + +func (r *csmThreatsMultiPolicyAgentRuleResource) Update(ctx context.Context, request resource.UpdateRequest, response *resource.UpdateResponse) { + var state csmThreatsMultiPolicyAgentRuleModel + response.Diagnostics.Append(request.Plan.Get(ctx, &state)...) + if response.Diagnostics.HasError() { + return + } + + csmThreatsMutex.Lock() + defer csmThreatsMutex.Unlock() + + agentRulePayload, err := r.buildUpdateCSMThreatsAgentRulePayload(&state) + if err != nil { + response.Diagnostics.AddError("error while parsing resource", err.Error()) + } + + res, _, err := r.api.UpdateCSMThreatsAgentRule(r.auth, state.Id.ValueString(), *agentRulePayload) + if err != nil { + response.Diagnostics.Append(utils.FrameworkErrorDiag(err, "error updating agent rule")) + return + } + if err := utils.CheckForUnparsed(response); err != nil { + response.Diagnostics.Append(utils.FrameworkErrorDiag(err, "response contains unparsed object")) + return + } + + r.updateStateFromResponse(ctx, &state, &res) + response.Diagnostics.Append(response.State.Set(ctx, &state)...) +} + +func (r *csmThreatsMultiPolicyAgentRuleResource) Delete(ctx context.Context, request resource.DeleteRequest, response *resource.DeleteResponse) { + var state csmThreatsMultiPolicyAgentRuleModel + response.Diagnostics.Append(request.State.Get(ctx, &state)...) + if response.Diagnostics.HasError() { + return + } + + csmThreatsMutex.Lock() + defer csmThreatsMutex.Unlock() + + id := state.Id.ValueString() + policyId := state.PolicyId.ValueString() + httpResp, err := r.api.DeleteCSMThreatsAgentRule(r.auth, id, *datadogV2.NewDeleteCSMThreatsAgentRuleOptionalParameters().WithPolicyId(policyId)) + if err != nil { + if httpResp != nil && httpResp.StatusCode == 404 { + return + } + response.Diagnostics.Append(utils.FrameworkErrorDiag(err, "error deleting agent rule")) + return + } +} + +func (r *csmThreatsMultiPolicyAgentRuleResource) buildCreateCSMThreatsAgentRulePayload(state *csmThreatsMultiPolicyAgentRuleModel) (*datadogV2.CloudWorkloadSecurityAgentRuleCreateRequest, error) { + _, policyId, name, description, enabled, expression := r.extractAgentRuleAttributesFromResource(state) + + attributes := datadogV2.CloudWorkloadSecurityAgentRuleCreateAttributes{} + attributes.Expression = expression + attributes.Name = name + attributes.Description = description + attributes.Enabled = &enabled + attributes.PolicyId = &policyId + + data := datadogV2.NewCloudWorkloadSecurityAgentRuleCreateData(attributes, datadogV2.CLOUDWORKLOADSECURITYAGENTRULETYPE_AGENT_RULE) + return datadogV2.NewCloudWorkloadSecurityAgentRuleCreateRequest(*data), nil +} + +func (r *csmThreatsMultiPolicyAgentRuleResource) buildUpdateCSMThreatsAgentRulePayload(state *csmThreatsMultiPolicyAgentRuleModel) (*datadogV2.CloudWorkloadSecurityAgentRuleUpdateRequest, error) { + agentRuleId, policyId, _, description, enabled, _ := r.extractAgentRuleAttributesFromResource(state) + + attributes := datadogV2.CloudWorkloadSecurityAgentRuleUpdateAttributes{} + attributes.Description = description + attributes.Enabled = &enabled + attributes.PolicyId = &policyId + + data := datadogV2.NewCloudWorkloadSecurityAgentRuleUpdateData(attributes, datadogV2.CLOUDWORKLOADSECURITYAGENTRULETYPE_AGENT_RULE) + data.Id = &agentRuleId + return datadogV2.NewCloudWorkloadSecurityAgentRuleUpdateRequest(*data), nil +} + +func (r *csmThreatsMultiPolicyAgentRuleResource) extractAgentRuleAttributesFromResource(state *csmThreatsMultiPolicyAgentRuleModel) (string, string, string, *string, bool, string) { + // Mandatory fields + id := state.Id.ValueString() + policyId := state.PolicyId.ValueString() + name := state.Name.ValueString() + enabled := state.Enabled.ValueBool() + expression := state.Expression.ValueString() + description := state.Description.ValueStringPointer() + + return id, policyId, name, description, enabled, expression +} + +func (r *csmThreatsMultiPolicyAgentRuleResource) updateStateFromResponse(ctx context.Context, state *csmThreatsMultiPolicyAgentRuleModel, res *datadogV2.CloudWorkloadSecurityAgentRuleResponse) { + state.Id = types.StringValue(res.Data.GetId()) + + attributes := res.Data.Attributes + + state.Name = types.StringValue(attributes.GetName()) + state.Description = types.StringValue(attributes.GetDescription()) + state.Enabled = types.BoolValue(attributes.GetEnabled()) + state.Expression = types.StringValue(attributes.GetExpression()) +} diff --git a/datadog/fwprovider/resource_datadog_csm_threats_policy.go b/datadog/fwprovider/resource_datadog_csm_threats_policy.go new file mode 100644 index 000000000..256974725 --- /dev/null +++ b/datadog/fwprovider/resource_datadog_csm_threats_policy.go @@ -0,0 +1,245 @@ +package fwprovider + +import ( + "context" + "fmt" + + "github.com/DataDog/datadog-api-client-go/v2/api/datadogV2" + "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/types" + + "github.com/terraform-providers/terraform-provider-datadog/datadog/internal/utils" +) + +type csmThreatsPolicyModel struct { + Id types.String `tfsdk:"id"` + Tags types.Set `tfsdk:"tags"` + Name types.String `tfsdk:"name"` + Description types.String `tfsdk:"description"` + Enabled types.Bool `tfsdk:"enabled"` +} + +type csmThreatsPolicyResource struct { + api *datadogV2.CSMThreatsApi + auth context.Context +} + +func NewCSMThreatsPolicyResource() resource.Resource { + return &csmThreatsPolicyResource{} +} + +func (r *csmThreatsPolicyResource) Metadata(_ context.Context, request resource.MetadataRequest, response *resource.MetadataResponse) { + response.TypeName = "csm_threats_policy" +} + +func (r *csmThreatsPolicyResource) Configure(_ context.Context, request resource.ConfigureRequest, response *resource.ConfigureResponse) { + providerData := request.ProviderData.(*FrameworkProvider) + r.api = providerData.DatadogApiInstances.GetCSMThreatsApiV2() + r.auth = providerData.Auth +} + +func (r *csmThreatsPolicyResource) Schema(_ context.Context, _ resource.SchemaRequest, response *resource.SchemaResponse) { + response.Schema = schema.Schema{ + Description: "Provides a Datadog CSM Threats policy API resource.", + Attributes: map[string]schema.Attribute{ + "id": utils.ResourceIDAttribute(), + "name": schema.StringAttribute{ + Required: true, + Description: "The name of the policy.", + }, + "description": schema.StringAttribute{ + Optional: true, + Description: "A description for the policy.", + Computed: true, + }, + "enabled": schema.BoolAttribute{ + Optional: true, + Default: booldefault.StaticBool(false), + Description: "Indicates whether the policy is enabled.", + Computed: true, + }, + "tags": schema.SetAttribute{ + Optional: true, + Description: "Host tags that define where the policy is deployed.", + ElementType: types.StringType, + Computed: true, + }, + }, + } +} + +func (r *csmThreatsPolicyResource) ImportState(ctx context.Context, request resource.ImportStateRequest, response *resource.ImportStateResponse) { + resource.ImportStatePassthroughID(ctx, path.Root("id"), request, response) +} + +func (r *csmThreatsPolicyResource) Create(ctx context.Context, request resource.CreateRequest, response *resource.CreateResponse) { + var state csmThreatsPolicyModel + response.Diagnostics.Append(request.Plan.Get(ctx, &state)...) + if response.Diagnostics.HasError() { + return + } + + csmThreatsMutex.Lock() + defer csmThreatsMutex.Unlock() + + policyPayload, err := r.buildCreateCSMThreatsPolicyPayload(&state) + if err != nil { + response.Diagnostics.AddError("error while parsing resource", err.Error()) + } + + res, _, err := r.api.CreateCSMThreatsAgentPolicy(r.auth, *policyPayload) + if err != nil { + response.Diagnostics.Append(utils.FrameworkErrorDiag(err, "error creating policy")) + return + } + if err := utils.CheckForUnparsed(response); err != nil { + response.Diagnostics.Append(utils.FrameworkErrorDiag(err, "response contains unparsed object")) + return + } + + r.updateStateFromResponse(ctx, &state, &res) + response.Diagnostics.Append(response.State.Set(ctx, &state)...) +} + +func (r *csmThreatsPolicyResource) Read(ctx context.Context, request resource.ReadRequest, response *resource.ReadResponse) { + var state csmThreatsPolicyModel + response.Diagnostics.Append(request.State.Get(ctx, &state)...) + if response.Diagnostics.HasError() { + return + } + + policyId := state.Id.ValueString() + res, httpResponse, err := r.api.GetCSMThreatsAgentPolicy(r.auth, policyId) + if err != nil { + if httpResponse != nil && httpResponse.StatusCode == 404 { + response.State.RemoveResource(ctx) + return + } + response.Diagnostics.Append(utils.FrameworkErrorDiag(err, "error fetching agent policy")) + return + } + if err := utils.CheckForUnparsed(response); err != nil { + response.Diagnostics.Append(utils.FrameworkErrorDiag(err, "response contains unparsed object")) + return + } + + r.updateStateFromResponse(ctx, &state, &res) + response.Diagnostics.Append(response.State.Set(ctx, &state)...) +} + +func (r *csmThreatsPolicyResource) Update(ctx context.Context, request resource.UpdateRequest, response *resource.UpdateResponse) { + var state csmThreatsPolicyModel + response.Diagnostics.Append(request.Plan.Get(ctx, &state)...) + if response.Diagnostics.HasError() { + return + } + + csmThreatsMutex.Lock() + defer csmThreatsMutex.Unlock() + + policyPayload, err := r.buildUpdateCSMThreatsPolicyPayload(&state) + if err != nil { + response.Diagnostics.AddError("error while parsing resource", err.Error()) + } + + res, _, err := r.api.UpdateCSMThreatsAgentPolicy(r.auth, state.Id.ValueString(), *policyPayload) + if err != nil { + response.Diagnostics.Append(utils.FrameworkErrorDiag(err, "error updating agent rule")) + return + } + if err := utils.CheckForUnparsed(response); err != nil { + response.Diagnostics.Append(utils.FrameworkErrorDiag(err, "response contains unparsed object")) + return + } + + r.updateStateFromResponse(ctx, &state, &res) + response.Diagnostics.Append(response.State.Set(ctx, &state)...) +} + +func (r *csmThreatsPolicyResource) Delete(ctx context.Context, request resource.DeleteRequest, response *resource.DeleteResponse) { + var state csmThreatsPolicyModel + response.Diagnostics.Append(request.State.Get(ctx, &state)...) + if response.Diagnostics.HasError() { + return + } + + csmThreatsMutex.Lock() + defer csmThreatsMutex.Unlock() + + id := state.Id.ValueString() + + httpResp, err := r.api.DeleteCSMThreatsAgentPolicy(r.auth, id) + if err != nil { + if httpResp != nil && httpResp.StatusCode == 404 { + return + } + response.Diagnostics.Append(utils.FrameworkErrorDiag(err, "error deleting agent rule")) + return + } +} + +func (r *csmThreatsPolicyResource) buildCreateCSMThreatsPolicyPayload(state *csmThreatsPolicyModel) (*datadogV2.CloudWorkloadSecurityAgentPolicyCreateRequest, error) { + _, name, description, enabled, tags, err := r.extractPolicyAttributesFromResource(state) + if err != nil { + return nil, err + } + + attributes := datadogV2.CloudWorkloadSecurityAgentPolicyCreateAttributes{} + attributes.Name = name + attributes.Description = description + attributes.Enabled = enabled + attributes.HostTags = tags + + data := datadogV2.NewCloudWorkloadSecurityAgentPolicyCreateData(attributes, datadogV2.CLOUDWORKLOADSECURITYAGENTPOLICYTYPE_POLICY) + return datadogV2.NewCloudWorkloadSecurityAgentPolicyCreateRequest(*data), nil +} + +func (r *csmThreatsPolicyResource) buildUpdateCSMThreatsPolicyPayload(state *csmThreatsPolicyModel) (*datadogV2.CloudWorkloadSecurityAgentPolicyUpdateRequest, error) { + policyId, name, description, enabled, tags, err := r.extractPolicyAttributesFromResource(state) + if err != nil { + return nil, err + } + attributes := datadogV2.CloudWorkloadSecurityAgentPolicyUpdateAttributes{} + attributes.Name = &name + attributes.Description = description + attributes.Enabled = enabled + attributes.HostTags = tags + + data := datadogV2.NewCloudWorkloadSecurityAgentPolicyUpdateData(attributes, datadogV2.CLOUDWORKLOADSECURITYAGENTPOLICYTYPE_POLICY) + data.Id = &policyId + return datadogV2.NewCloudWorkloadSecurityAgentPolicyUpdateRequest(*data), nil +} + +func (r *csmThreatsPolicyResource) extractPolicyAttributesFromResource(state *csmThreatsPolicyModel) (string, string, *string, *bool, []string, error) { + // Mandatory fields + id := state.Id.ValueString() + name := state.Name.ValueString() + enabled := state.Enabled.ValueBoolPointer() + description := state.Description.ValueStringPointer() + var tags []string + if !state.Tags.IsNull() && !state.Tags.IsUnknown() { + for _, tag := range state.Tags.Elements() { + tagStr, ok := tag.(types.String) + if !ok { + return "", "", nil, nil, nil, fmt.Errorf("expected item to be of type types.String, got %T", tag) + } + tags = append(tags, tagStr.ValueString()) + } + } + + return id, name, description, enabled, tags, nil +} + +func (r *csmThreatsPolicyResource) updateStateFromResponse(ctx context.Context, state *csmThreatsPolicyModel, res *datadogV2.CloudWorkloadSecurityAgentPolicyResponse) { + state.Id = types.StringValue(res.Data.GetId()) + + attributes := res.Data.Attributes + + state.Name = types.StringValue(attributes.GetName()) + state.Description = types.StringValue(attributes.GetDescription()) + state.Enabled = types.BoolValue(attributes.GetEnabled()) + state.Tags, _ = types.SetValueFrom(ctx, types.StringType, attributes.GetHostTags()) +} diff --git a/datadog/tests/data_source_datadog_csm_threats_multi_policy_agent_rules_test.go b/datadog/tests/data_source_datadog_csm_threats_multi_policy_agent_rules_test.go new file mode 100644 index 000000000..f47344f0f --- /dev/null +++ b/datadog/tests/data_source_datadog_csm_threats_multi_policy_agent_rules_test.go @@ -0,0 +1,124 @@ +package test + +import ( + "context" + "fmt" + "github.com/DataDog/datadog-api-client-go/v2/api/datadogV2" + "strconv" + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/terraform" + + "github.com/terraform-providers/terraform-provider-datadog/datadog/fwprovider" +) + +func TestAccCSMThreatsMultiPolicyAgentRuleDataSource(t *testing.T) { + ctx, providers, accProviders := testAccFrameworkMuxProviders(context.Background(), t) + + policyName := uniqueAgentRuleName(ctx) + policyConfig := fmt.Sprintf(` + resource "datadog_csm_threats_policy" "policy_for_test" { + name = "%s" + enabled = true + description = "im a policy" + tags = ["host_name:test_host"] + } + `, policyName) + agentRuleName := uniqueAgentRuleName(ctx) + agentRuleConfig := fmt.Sprintf(` + %s + resource "datadog_csm_threats_multi_policy_agent_rule" "agent_rule_for_data_source_test" { + name = "%s" + policy_id = datadog_csm_threats_policy.policy_for_test.id + enabled = true + description = "im a rule" + expression = "open.file.name == \"etc/shadow/password\"" + } + `, policyConfig, agentRuleName) + dataSourceName := "data.datadog_csm_threats_agent_rules.my_data_source" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProtoV5ProviderFactories: accProviders, + CheckDestroy: testAccCheckCSMThreatsAgentRuleDestroy(providers.frameworkProvider), + Steps: []resource.TestStep{ + { + // Create an agent rule to have at least one + Config: agentRuleConfig, + Check: testAccCheckCSMThreatsMultiPolicyAgentRuleExists(providers.frameworkProvider, "datadog_csm_threats_multi_policy_agent_rule.agent_rule_for_data_source_test"), + }, + { + Config: fmt.Sprintf(` + %s + data "datadog_csm_threats_agent_rules" "my_data_source" { + policy_id = datadog_csm_threats_policy.policy_for_test.id + } + `, agentRuleConfig), + Check: checkCSMThreatsMultiPolicyAgentRulesDataSourceContent(providers.frameworkProvider, dataSourceName, agentRuleName), + }, + }, + }) +} + +func checkCSMThreatsMultiPolicyAgentRulesDataSourceContent(accProvider *fwprovider.FrameworkProvider, dataSourceName string, agentRuleName string) resource.TestCheckFunc { + return func(state *terraform.State) error { + res, ok := state.RootModule().Resources[dataSourceName] + if !ok { + return fmt.Errorf("resource missing from state: %s", dataSourceName) + } + + auth := accProvider.Auth + apiInstances := accProvider.DatadogApiInstances + + policyId := res.Primary.Attributes["policy_id"] + allAgentRulesResponse, _, err := apiInstances.GetCSMThreatsApiV2().ListCSMThreatsAgentRules(auth, *datadogV2.NewListCSMThreatsAgentRulesOptionalParameters().WithPolicyId(policyId)) + if err != nil { + return err + } + + // Check the agentRule we created is in the API response + agentRuleId := "" + ruleName := "" + for _, rule := range allAgentRulesResponse.GetData() { + if rule.Attributes.GetName() == agentRuleName { + agentRuleId = rule.GetId() + ruleName = rule.Attributes.GetName() + break + } + } + if agentRuleId == "" { + return fmt.Errorf("agent rule with name '%s' not found in API responses", agentRuleName) + } + + // Check that the data_source fetched is correct + resourceAttributes := res.Primary.Attributes + agentRulesIdsCount, err := strconv.Atoi(resourceAttributes["agent_rules_ids.#"]) + if err != nil { + return err + } + agentRulesCount, err := strconv.Atoi(resourceAttributes["agent_rules.#"]) + if err != nil { + return err + } + if agentRulesCount != agentRulesIdsCount { + return fmt.Errorf("the data source contains %d agent rules IDs but %d agent rules", agentRulesIdsCount, agentRulesCount) + } + + // Find in which position is the agent rule we created, and check its values + idx := 0 + for idx < agentRulesIdsCount && resourceAttributes[fmt.Sprintf("agent_rules_ids.%d", idx)] != agentRuleId { + idx++ + } + if idx == len(resourceAttributes) { + return fmt.Errorf("agent rule with ID '%s' not found in data source", agentRuleId) + } + + return resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(dataSourceName, fmt.Sprintf("agent_rules.%d.name", idx), ruleName), + resource.TestCheckResourceAttr(dataSourceName, fmt.Sprintf("agent_rules.%d.enabled", idx), "true"), + resource.TestCheckResourceAttr(dataSourceName, fmt.Sprintf("agent_rules.%d.description", idx), "im a rule"), + resource.TestCheckResourceAttr(dataSourceName, fmt.Sprintf("agent_rules.%d.expression", idx), "open.file.name == \"etc/shadow/password\""), + )(state) + } +} diff --git a/datadog/tests/data_source_datadog_csm_threats_policies_test.go b/datadog/tests/data_source_datadog_csm_threats_policies_test.go new file mode 100644 index 000000000..15a9f322d --- /dev/null +++ b/datadog/tests/data_source_datadog_csm_threats_policies_test.go @@ -0,0 +1,107 @@ +package test + +import ( + "context" + "fmt" + "strconv" + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/terraform" + + "github.com/terraform-providers/terraform-provider-datadog/datadog/fwprovider" +) + +func TestAccCSMThreatsPoliciesDataSource(t *testing.T) { + ctx, providers, accProviders := testAccFrameworkMuxProviders(context.Background(), t) + + policyName := uniqueAgentRuleName(ctx) + dataSourceName := "data.datadog_csm_threats_policies.my_data_source" + policyConfig := fmt.Sprintf(` + resource "datadog_csm_threats_policy" "policy_for_data_source_test" { + name = "%s" + enabled = true + description = "im a policy" + tags = ["host_name:test_host"] + } + `, policyName) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProtoV5ProviderFactories: accProviders, + CheckDestroy: testAccCheckCSMThreatsPolicyDestroy(providers.frameworkProvider), + Steps: []resource.TestStep{ + { + // Create a policy to have at least one + Config: policyConfig, + Check: testAccCheckCSMThreatsPolicyExists(providers.frameworkProvider, "datadog_csm_threats_policy.policy_for_data_source_test"), + }, + { + Config: fmt.Sprintf(` + %s + data "datadog_csm_threats_policies" "my_data_source" {} + `, policyConfig), + Check: checkCSMThreatsPoliciesDataSourceContent(providers.frameworkProvider, dataSourceName, policyName), + }, + }, + }) +} + +func checkCSMThreatsPoliciesDataSourceContent(accProvider *fwprovider.FrameworkProvider, dataSourceName string, policyName string) resource.TestCheckFunc { + return func(state *terraform.State) error { + res, ok := state.RootModule().Resources[dataSourceName] + if !ok { + return fmt.Errorf("resource missing from state: %s", dataSourceName) + } + + auth := accProvider.Auth + apiInstances := accProvider.DatadogApiInstances + + allPoliciesResponse, _, err := apiInstances.GetCSMThreatsApiV2().ListCSMThreatsAgentPolicies(auth) + if err != nil { + return err + } + + // Check the policy we created is in the API response + resPolicyId := "" + for _, policy := range allPoliciesResponse.GetData() { + if policy.Attributes.GetName() == policyName { + resPolicyId = policy.GetId() + break + } + } + if resPolicyId == "" { + return fmt.Errorf("policy with name '%s' not found in API responses", policyName) + } + + // Check that the data_source fetched is correct + resourceAttributes := res.Primary.Attributes + policyIdsCount, err := strconv.Atoi(resourceAttributes["policy_ids.#"]) + if err != nil { + return err + } + policiesCount, err := strconv.Atoi(resourceAttributes["policies.#"]) + if err != nil { + return err + } + if policiesCount != policyIdsCount { + return fmt.Errorf("the data source contains %d policy IDs but %d policies", policyIdsCount, policiesCount) + } + + // Find in which position is the policy we created, and check its values + idx := 0 + for idx < policyIdsCount && resourceAttributes[fmt.Sprintf("policy_ids.%d", idx)] != resPolicyId { + idx++ + } + if idx == len(resourceAttributes) { + return fmt.Errorf("policy with ID '%s' not found in data source", resPolicyId) + } + + return resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(dataSourceName, fmt.Sprintf("policies.%d.name", idx), policyName), + resource.TestCheckResourceAttr(dataSourceName, fmt.Sprintf("policies.%d.enabled", idx), "true"), + resource.TestCheckResourceAttr(dataSourceName, fmt.Sprintf("policies.%d.tags.0", idx), "host_name:test_host"), + resource.TestCheckResourceAttr(dataSourceName, fmt.Sprintf("policies.%d.description", idx), "im a policy"), + )(state) + } +} diff --git a/datadog/tests/provider_test.go b/datadog/tests/provider_test.go index 5b1c8984f..ab04ea402 100644 --- a/datadog/tests/provider_test.go +++ b/datadog/tests/provider_test.go @@ -57,6 +57,8 @@ var testFiles2EndpointTags = map[string]string{ "tests/data_source_datadog_application_key_test": "application_keys", "tests/data_source_datadog_cloud_workload_security_agent_rules_test": "cloud-workload-security", "tests/data_source_datadog_csm_threats_agent_rules_test": "cloud-workload-security", + "tests/data_source_datadog_csm_threats_multi_policy_agent_rules_test": "cloud-workload-security", + "tests/data_source_datadog_csm_threats_policies_test": "cloud-workload-security", "tests/data_source_datadog_dashboard_list_test": "dashboard-lists", "tests/data_source_datadog_dashboard_test": "dashboard", "tests/data_source_datadog_hosts_test": "hosts", @@ -108,6 +110,8 @@ var testFiles2EndpointTags = map[string]string{ "tests/resource_datadog_cloud_configuration_rule_test": "security-monitoring", "tests/resource_datadog_cloud_workload_security_agent_rule_test": "cloud_workload_security", "tests/resource_datadog_csm_threats_agent_rule_test": "cloud-workload-security", + "tests/resource_datadog_csm_threats_multi_policy_agent_rule_test": "cloud-workload-security", + "tests/resource_datadog_csm_threats_policy_test": "cloud-workload-security", "tests/resource_datadog_dashboard_alert_graph_test": "dashboards", "tests/resource_datadog_dashboard_alert_value_test": "dashboards", "tests/resource_datadog_dashboard_change_test": "dashboards", diff --git a/datadog/tests/resource_datadog_csm_threats_multi_policy_agent_rule_test.go b/datadog/tests/resource_datadog_csm_threats_multi_policy_agent_rule_test.go new file mode 100644 index 000000000..c1d77f16c --- /dev/null +++ b/datadog/tests/resource_datadog_csm_threats_multi_policy_agent_rule_test.go @@ -0,0 +1,133 @@ +package test + +import ( + "context" + "errors" + "fmt" + "github.com/DataDog/datadog-api-client-go/v2/api/datadogV2" + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/terraform" + + "github.com/terraform-providers/terraform-provider-datadog/datadog/fwprovider" +) + +// Create an agent rule and update its description +func TestAccCSMThreatsMultiPolicyAgentRule_CreateAndUpdate(t *testing.T) { + ctx, providers, accProviders := testAccFrameworkMuxProviders(context.Background(), t) + + agentRuleName := uniqueAgentRuleName(ctx) + resourceName := "datadog_csm_threats_multi_policy_agent_rule.agent_rule_test" + + policyName := uniqueAgentRuleName(ctx) + policyConfig := fmt.Sprintf(` + resource "datadog_csm_threats_policy" "policy_for_test" { + name = "%s" + enabled = true + description = "im a policy" + tags = ["host_name:test_host"] + } + `, policyName) + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProtoV5ProviderFactories: accProviders, + CheckDestroy: testAccCheckCSMThreatsMultiPolicyAgentRuleDestroy(providers.frameworkProvider), + Steps: []resource.TestStep{ + { + // Create a policy to have at least one + Config: policyConfig, + Check: testAccCheckCSMThreatsPolicyExists(providers.frameworkProvider, "datadog_csm_threats_policy.policy_for_test"), + }, + { + Config: fmt.Sprintf(` + %s + resource "datadog_csm_threats_multi_policy_agent_rule" "agent_rule_test" { + name = "%s" + policy_id = datadog_csm_threats_policy.policy_for_test.id + enabled = true + description = "im a rule" + expression = "open.file.name == \"etc/shadow/password\"" + } + `, policyConfig, agentRuleName), + Check: resource.ComposeTestCheckFunc( + testAccCheckCSMThreatsMultiPolicyAgentRuleExists(providers.frameworkProvider, resourceName), + checkCSMThreatsAgentRuleContent( + resourceName, + agentRuleName, + "im a rule", + "open.file.name == \"etc/shadow/password\"", + ), + ), + }, + // Update description + { + Config: fmt.Sprintf(` + %s + resource "datadog_csm_threats_multi_policy_agent_rule" "agent_rule_test" { + name = "%s" + policy_id = datadog_csm_threats_policy.policy_for_test.id + enabled = true + description = "updated agent rule for terraform provider test" + expression = "open.file.name == \"etc/shadow/password\"" + } + `, policyConfig, agentRuleName), + Check: resource.ComposeTestCheckFunc( + testAccCheckCSMThreatsMultiPolicyAgentRuleExists(providers.frameworkProvider, resourceName), + checkCSMThreatsAgentRuleContent( + resourceName, + agentRuleName, + "updated agent rule for terraform provider test", + "open.file.name == \"etc/shadow/password\"", + ), + ), + }, + }, + }) +} + +func testAccCheckCSMThreatsMultiPolicyAgentRuleExists(accProvider *fwprovider.FrameworkProvider, resourceName string) resource.TestCheckFunc { + return func(s *terraform.State) error { + resource, ok := s.RootModule().Resources[resourceName] + if !ok { + return fmt.Errorf("resource '%s' not found in the state %s", resourceName, s.RootModule().Resources) + } + + if resource.Type != "datadog_csm_threats_multi_policy_agent_rule" { + return fmt.Errorf("resource %s is not of type datadog_csm_threats_multi_policy_agent_rule, found %s instead", resourceName, resource.Type) + } + + auth := accProvider.Auth + apiInstances := accProvider.DatadogApiInstances + + policyId := resource.Primary.Attributes["policy_id"] + _, _, err := apiInstances.GetCSMThreatsApiV2().GetCSMThreatsAgentRule(auth, resource.Primary.ID, *datadogV2.NewGetCSMThreatsAgentRuleOptionalParameters().WithPolicyId(policyId)) + if err != nil { + return fmt.Errorf("received an error retrieving agent rule: %s", err) + } + + return nil + } +} + +func testAccCheckCSMThreatsMultiPolicyAgentRuleDestroy(accProvider *fwprovider.FrameworkProvider) resource.TestCheckFunc { + return func(s *terraform.State) error { + auth := accProvider.Auth + apiInstances := accProvider.DatadogApiInstances + + for _, resource := range s.RootModule().Resources { + if resource.Type == "datadog_csm_threats_multi_policy_agent_rule" { + policyId := resource.Primary.Attributes["policy_id"] + _, httpResponse, err := apiInstances.GetCSMThreatsApiV2().GetCSMThreatsAgentRule(auth, resource.Primary.ID, *datadogV2.NewGetCSMThreatsAgentRuleOptionalParameters().WithPolicyId(policyId)) + if err == nil { + return errors.New("agent rule still exists") + } + if httpResponse == nil || httpResponse.StatusCode != 404 { + return fmt.Errorf("received an error while getting the agent rule: %s", err) + } + } + } + + return nil + } +} diff --git a/datadog/tests/resource_datadog_csm_threats_policy_test.go b/datadog/tests/resource_datadog_csm_threats_policy_test.go new file mode 100644 index 000000000..4e9099c01 --- /dev/null +++ b/datadog/tests/resource_datadog_csm_threats_policy_test.go @@ -0,0 +1,121 @@ +package test + +import ( + "context" + "errors" + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/terraform" + + "github.com/terraform-providers/terraform-provider-datadog/datadog/fwprovider" +) + +// Create an agent policy and update its description +func TestAccCSMThreatsPolicy_CreateAndUpdate(t *testing.T) { + ctx, providers, accProviders := testAccFrameworkMuxProviders(context.Background(), t) + + policyName := uniqueAgentRuleName(ctx) + resourceName := "datadog_csm_threats_policy.policy_test" + tags := []string{"host_name:test_host"} + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProtoV5ProviderFactories: accProviders, + CheckDestroy: testAccCheckCSMThreatsPolicyDestroy(providers.frameworkProvider), + Steps: []resource.TestStep{ + { + Config: fmt.Sprintf(` + resource "datadog_csm_threats_policy" "policy_test" { + name = "%s" + enabled = true + description = "im a policy" + tags = ["host_name:test_host"] + } + `, policyName), + Check: resource.ComposeTestCheckFunc( + testAccCheckCSMThreatsPolicyExists(providers.frameworkProvider, "datadog_csm_threats_policy.policy_test"), + checkCSMThreatsPolicyContent( + resourceName, + policyName, + "im a policy", + tags, + ), + ), + }, + // Update description + { + Config: fmt.Sprintf(` + resource "datadog_csm_threats_policy" "policy_test" { + name = "%s" + enabled = true + description = "updated policy for terraform provider test" + tags = ["host_name:test_host"] + } + `, policyName), + Check: resource.ComposeTestCheckFunc( + testAccCheckCSMThreatsPolicyExists(providers.frameworkProvider, resourceName), + checkCSMThreatsPolicyContent( + resourceName, + policyName, + "updated policy for terraform provider test", + tags, + ), + ), + }, + }, + }) +} + +func checkCSMThreatsPolicyContent(resourceName string, name string, description string, tags []string) resource.TestCheckFunc { + return resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "name", name), + resource.TestCheckResourceAttr(resourceName, "description", description), + resource.TestCheckResourceAttr(resourceName, "enabled", "true"), + resource.TestCheckResourceAttr(resourceName, "tags.0", tags[0]), + ) +} + +func testAccCheckCSMThreatsPolicyExists(accProvider *fwprovider.FrameworkProvider, resourceName string) resource.TestCheckFunc { + return func(s *terraform.State) error { + resource, ok := s.RootModule().Resources[resourceName] + if !ok { + return fmt.Errorf("resource '%s' not found in the state %s", resourceName, s.RootModule().Resources) + } + + if resource.Type != "datadog_csm_threats_policy" { + return fmt.Errorf("resource %s is not of type datadog_csm_threats_policy, found %s instead", resourceName, resource.Type) + } + + auth := accProvider.Auth + apiInstances := accProvider.DatadogApiInstances + + _, _, err := apiInstances.GetCSMThreatsApiV2().GetCSMThreatsAgentPolicy(auth, resource.Primary.ID) + if err != nil { + return fmt.Errorf("received an error retrieving policy: %s", err) + } + + return nil + } +} + +func testAccCheckCSMThreatsPolicyDestroy(accProvider *fwprovider.FrameworkProvider) resource.TestCheckFunc { + return func(s *terraform.State) error { + auth := accProvider.Auth + apiInstances := accProvider.DatadogApiInstances + + for _, resource := range s.RootModule().Resources { + if resource.Type == "datadog_csm_threats_policy" { + _, httpResponse, err := apiInstances.GetCSMThreatsApiV2().GetCSMThreatsAgentPolicy(auth, resource.Primary.ID) + if err == nil { + return errors.New("policy still exists") + } + if httpResponse == nil || httpResponse.StatusCode != 404 { + return fmt.Errorf("received an error while getting the policy: %s", err) + } + } + } + + return nil + } +} diff --git a/docs/data-sources/csm_threats_agent_rules.md b/docs/data-sources/csm_threats_agent_rules.md index 6e6e7a0d1..6f5ea6e33 100644 --- a/docs/data-sources/csm_threats_agent_rules.md +++ b/docs/data-sources/csm_threats_agent_rules.md @@ -15,6 +15,10 @@ Use this data source to retrieve information about existing Agent rules. ## Schema +### Optional + +- `policy_id` (String) Listing only the rules in the policy with this field as the ID + ### Read-Only - `agent_rules` (List of Object) List of Agent rules (see [below for nested schema](#nestedatt--agent_rules)) diff --git a/docs/data-sources/csm_threats_policies.md b/docs/data-sources/csm_threats_policies.md new file mode 100644 index 000000000..46ae79787 --- /dev/null +++ b/docs/data-sources/csm_threats_policies.md @@ -0,0 +1,33 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "datadog_csm_threats_policies Data Source - terraform-provider-datadog" +subcategory: "" +description: |- + Use this data source to retrieve information about existing policies. +--- + +# datadog_csm_threats_policies (Data Source) + +Use this data source to retrieve information about existing policies. + + + + +## Schema + +### Read-Only + +- `id` (String) The ID of this resource. +- `policies` (List of Object) List of policies (see [below for nested schema](#nestedatt--policies)) +- `policy_ids` (List of String) List of IDs for the policies. + + +### Nested Schema for `policies` + +Read-Only: + +- `description` (String) +- `enabled` (Boolean) +- `id` (String) +- `name` (String) +- `tags` (Set of String) diff --git a/docs/resources/csm_threats_multi_policy_agent_rule.md b/docs/resources/csm_threats_multi_policy_agent_rule.md new file mode 100644 index 000000000..84d0a5771 --- /dev/null +++ b/docs/resources/csm_threats_multi_policy_agent_rule.md @@ -0,0 +1,31 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "datadog_csm_threats_multi_policy_agent_rule Resource - terraform-provider-datadog" +subcategory: "" +description: |- + Provides a Datadog CSM Threats Agent Rule API resource. +--- + +# datadog_csm_threats_multi_policy_agent_rule (Resource) + +Provides a Datadog CSM Threats Agent Rule API resource. + + + + +## Schema + +### Required + +- `enabled` (Boolean) Indicates Whether the Agent rule is enabled. +- `expression` (String) The SECL expression of the Agent rule +- `name` (String) The name of the Agent rule. +- `policy_id` (String) The ID of the agent policy in which the rule is saved + +### Optional + +- `description` (String) A description for the Agent rule. + +### Read-Only + +- `id` (String) The ID of this resource. diff --git a/docs/resources/csm_threats_policy.md b/docs/resources/csm_threats_policy.md new file mode 100644 index 000000000..ac2c43b58 --- /dev/null +++ b/docs/resources/csm_threats_policy.md @@ -0,0 +1,30 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "datadog_csm_threats_policy Resource - terraform-provider-datadog" +subcategory: "" +description: |- + Provides a Datadog CSM Threats policy API resource. +--- + +# datadog_csm_threats_policy (Resource) + +Provides a Datadog CSM Threats policy API resource. + + + + +## Schema + +### Required + +- `name` (String) The name of the policy. + +### Optional + +- `description` (String) A description for the policy. +- `enabled` (Boolean) Indicates whether the policy is enabled. Defaults to `false`. +- `tags` (Set of String) Host tags that define where the policy is deployed. + +### Read-Only + +- `id` (String) The ID of this resource.