Skip to content

Commit

Permalink
[datadog_csm_threats_agent_rules] Add ressource and datasource for CS…
Browse files Browse the repository at this point in the history
…M Threats agent rule (#2316)

* [CWS-1047] - basic structure

* [CWS-1047] - change name

* [CWS-1047] - resource type name

* [CWS-1047] - fix tests

* [CWS-1047] - regenerate docs

* [CWS-1047] - record cassettes

* [CWS-1047] - record create and update cassette

* [CWS-1047] - nits and renaminbg

* [CWS-1047] - re-run resource tesgt

* [CWS-1047] - re-generate doc

* [CWS-1047] - nits

* [CWS-1047] - run against prod api

* [CWS-1047] - again run it against prod api

* fix random agent rule function

* fix function description

* [CWS-1047] - Docs comments

* [CWS-1047] - adress comments

* [CWS-1047] - rmv default

* [CWS-1049] - Add a default value for descriptionm

* [CWS-1047] - hash the datasource vid

* [CWS-1047] - run gofmt

* remove println

---------

Co-authored-by: Kevin Zou <kevin.zou@datadoghq.com>
  • Loading branch information
Malo10LeGoff and nkzou committed Mar 19, 2024
1 parent 260e448 commit 1387446
Show file tree
Hide file tree
Showing 14 changed files with 1,442 additions and 0 deletions.
125 changes: 125 additions & 0 deletions datadog/fwprovider/data_source_datadog_csm_threats_agent_rule.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
package fwprovider

import (
"context"
"crypto/sha256"
"fmt"
"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 = &csmThreatsAgentRulesDataSource{}
)

type csmThreatsAgentRulesDataSource struct {
api *datadogV2.CloudWorkloadSecurityApi
auth context.Context
}

type csmThreatsAgentRulesDataSourceModel struct {
Id types.String `tfsdk:"id"`
AgentRulesIds types.List `tfsdk:"agent_rules_ids"`
AgentRules []csmThreatsAgentRuleModel `tfsdk:"agent_rules"`
}

func NewCSMThreatsAgentRulesDataSource() datasource.DataSource {
return &csmThreatsAgentRulesDataSource{}
}

func (r *csmThreatsAgentRulesDataSource) Configure(_ context.Context, request datasource.ConfigureRequest, _ *datasource.ConfigureResponse) {
providerData := request.ProviderData.(*FrameworkProvider)
r.api = providerData.DatadogApiInstances.GetCloudWorkloadSecurityApiV2()
r.auth = providerData.Auth
}

func (*csmThreatsAgentRulesDataSource) Metadata(_ context.Context, _ datasource.MetadataRequest, response *datasource.MetadataResponse) {
response.TypeName = "csm_threats_agent_rules"
}

func (r *csmThreatsAgentRulesDataSource) Read(ctx context.Context, request datasource.ReadRequest, response *datasource.ReadResponse) {
var state csmThreatsAgentRulesDataSourceModel
response.Diagnostics.Append(request.Config.Get(ctx, &state)...)
if response.Diagnostics.HasError() {
return
}

res, _, err := r.api.ListCSMThreatsAgentRules(r.auth)
if err != nil {
response.Diagnostics.Append(utils.FrameworkErrorDiag(err, "error while fetching agent rules"))
return
}

data := res.GetData()
agentRuleIds := make([]string, len(data))
agentRules := make([]csmThreatsAgentRuleModel, len(data))

for idx, agentRule := range res.GetData() {
var agentRuleModel csmThreatsAgentRuleModel
agentRuleModel.Id = types.StringValue(agentRule.GetId())
attributes := agentRule.Attributes
agentRuleModel.Name = types.StringValue(attributes.GetName())
agentRuleModel.Description = types.StringValue(attributes.GetDescription())
agentRuleModel.Enabled = types.BoolValue(attributes.GetEnabled())
agentRuleModel.Expression = types.StringValue(*attributes.Expression)

agentRuleIds[idx] = agentRule.GetId()
agentRules[idx] = agentRuleModel
}

stateId := strings.Join(agentRuleIds, "--")
state.Id = types.StringValue(computeAgentRulesDataSourceID(&stateId))
tfAgentRuleIds, diags := types.ListValueFrom(ctx, types.StringType, agentRuleIds)
response.Diagnostics.Append(diags...)
state.AgentRulesIds = tfAgentRuleIds
state.AgentRules = agentRules

response.Diagnostics.Append(response.State.Set(ctx, &state)...)
}

func computeAgentRulesDataSourceID(agentruleIds *string) string {
// Key for hashing
var b strings.Builder
if agentruleIds != nil {
b.WriteString(*agentruleIds)
}
keyStr := b.String()
h := sha256.New()
h.Write([]byte(keyStr))

return fmt.Sprintf("%x", h.Sum(nil))
}

func (*csmThreatsAgentRulesDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, response *datasource.SchemaResponse) {
response.Schema = schema.Schema{
Description: "Use this data source to retrieve information about existing Agent rules.",
Attributes: map[string]schema.Attribute{
"id": utils.ResourceIDAttribute(),
"agent_rules_ids": schema.ListAttribute{
Computed: true,
Description: "List of IDs for the Agent rules.",
ElementType: types.StringType,
},
"agent_rules": schema.ListAttribute{
Computed: true,
Description: "List of Agent rules",
ElementType: types.ObjectType{
AttrTypes: map[string]attr.Type{
"id": types.StringType,
"name": types.StringType,
"description": types.StringType,
"enabled": types.BoolType,
"expression": types.StringType,
},
},
},
},
}
}
2 changes: 2 additions & 0 deletions datadog/fwprovider/framework_provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ var Resources = []func() resource.Resource{
NewTeamPermissionSettingResource,
NewTeamResource,
NewSecurityMonitoringSuppressionResource,
NewCSMThreatsAgentRuleResource,
NewServiceAccountResource,
}

Expand All @@ -78,6 +79,7 @@ var Datasources = []func() datasource.DataSource{
NewSensitiveDataScannerGroupOrderDatasource,
NewDatadogUsersDataSource,
NewSecurityMonitoringSuppressionDataSource,
NewCSMThreatsAgentRulesDataSource,
}

// FrameworkProvider struct
Expand Down
229 changes: 229 additions & 0 deletions datadog/fwprovider/resource_datadog_csm_threats_agent_rule.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,229 @@
package fwprovider

import (
"context"

"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/stringdefault"
"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"
)

var (
_ resource.ResourceWithConfigure = &csmThreatsAgentRuleResource{}
_ resource.ResourceWithImportState = &csmThreatsAgentRuleResource{}
)

type csmThreatsAgentRuleModel struct {
Id types.String `tfsdk:"id"`
Name types.String `tfsdk:"name"`
Description types.String `tfsdk:"description"`
Enabled types.Bool `tfsdk:"enabled"`
Expression types.String `tfsdk:"expression"`
}

type csmThreatsAgentRuleResource struct {
api *datadogV2.CloudWorkloadSecurityApi
auth context.Context
}

func NewCSMThreatsAgentRuleResource() resource.Resource {
return &csmThreatsAgentRuleResource{}
}

func (r *csmThreatsAgentRuleResource) Metadata(_ context.Context, request resource.MetadataRequest, response *resource.MetadataResponse) {
response.TypeName = "csm_threats_agent_rule"
}

func (r *csmThreatsAgentRuleResource) Configure(_ context.Context, request resource.ConfigureRequest, response *resource.ConfigureResponse) {
providerData := request.ProviderData.(*FrameworkProvider)
r.api = providerData.DatadogApiInstances.GetCloudWorkloadSecurityApiV2()
r.auth = providerData.Auth
}

func (r *csmThreatsAgentRuleResource) 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(),
"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.",
Default: stringdefault.StaticString(""),
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 *csmThreatsAgentRuleResource) ImportState(ctx context.Context, request resource.ImportStateRequest, response *resource.ImportStateResponse) {
resource.ImportStatePassthroughID(ctx, path.Root("id"), request, response)
}

func (r *csmThreatsAgentRuleResource) Create(ctx context.Context, request resource.CreateRequest, response *resource.CreateResponse) {
var state csmThreatsAgentRuleModel
response.Diagnostics.Append(request.Plan.Get(ctx, &state)...)
if response.Diagnostics.HasError() {
return
}

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 *csmThreatsAgentRuleResource) Read(ctx context.Context, request resource.ReadRequest, response *resource.ReadResponse) {
var state csmThreatsAgentRuleModel
response.Diagnostics.Append(request.State.Get(ctx, &state)...)
if response.Diagnostics.HasError() {
return
}

agentRuleId := state.Id.ValueString()
res, httpResponse, err := r.api.GetCSMThreatsAgentRule(r.auth, agentRuleId)
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 *csmThreatsAgentRuleResource) Update(ctx context.Context, request resource.UpdateRequest, response *resource.UpdateResponse) {
var state csmThreatsAgentRuleModel
response.Diagnostics.Append(request.Plan.Get(ctx, &state)...)
if response.Diagnostics.HasError() {
return
}

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 *csmThreatsAgentRuleResource) Delete(ctx context.Context, request resource.DeleteRequest, response *resource.DeleteResponse) {
var state csmThreatsAgentRuleModel
response.Diagnostics.Append(request.State.Get(ctx, &state)...)
if response.Diagnostics.HasError() {
return
}

id := state.Id.ValueString()

httpResp, err := r.api.DeleteCSMThreatsAgentRule(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 *csmThreatsAgentRuleResource) buildCreateCSMThreatsAgentRulePayload(state *csmThreatsAgentRuleModel) (*datadogV2.CloudWorkloadSecurityAgentRuleCreateRequest, error) {
_, name, description, enabled, expression := r.extractAgentRuleAttributesFromResource(state)

attributes := datadogV2.CloudWorkloadSecurityAgentRuleCreateAttributes{}
attributes.Expression = expression
attributes.Name = name
attributes.Description = description
attributes.Enabled = &enabled

data := datadogV2.NewCloudWorkloadSecurityAgentRuleCreateData(attributes, datadogV2.CLOUDWORKLOADSECURITYAGENTRULETYPE_AGENT_RULE)
return datadogV2.NewCloudWorkloadSecurityAgentRuleCreateRequest(*data), nil
}

func (r *csmThreatsAgentRuleResource) buildUpdateCSMThreatsAgentRulePayload(state *csmThreatsAgentRuleModel) (*datadogV2.CloudWorkloadSecurityAgentRuleUpdateRequest, error) {
agentRuleId, _, description, enabled, _ := r.extractAgentRuleAttributesFromResource(state)

attributes := datadogV2.CloudWorkloadSecurityAgentRuleUpdateAttributes{}
attributes.Description = description
attributes.Enabled = &enabled

data := datadogV2.NewCloudWorkloadSecurityAgentRuleUpdateData(attributes, datadogV2.CLOUDWORKLOADSECURITYAGENTRULETYPE_AGENT_RULE)
data.Id = &agentRuleId
return datadogV2.NewCloudWorkloadSecurityAgentRuleUpdateRequest(*data), nil
}

func (r *csmThreatsAgentRuleResource) extractAgentRuleAttributesFromResource(state *csmThreatsAgentRuleModel) (string, string, *string, bool, string) {
// Mandatory fields
id := state.Id.ValueString()
name := state.Name.ValueString()
enabled := state.Enabled.ValueBool()
expression := state.Expression.ValueString()
description := state.Description.ValueStringPointer()

return id, name, description, enabled, expression
}

func (r *csmThreatsAgentRuleResource) updateStateFromResponse(ctx context.Context, state *csmThreatsAgentRuleModel, 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())
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
2024-03-14T12:54:12.185366-04:00

0 comments on commit 1387446

Please sign in to comment.