diff --git a/sysdig/data_source_sysdig_secure_aws_ml_policy.go b/sysdig/data_source_sysdig_secure_aws_ml_policy.go new file mode 100644 index 00000000..3b013a33 --- /dev/null +++ b/sysdig/data_source_sysdig_secure_aws_ml_policy.go @@ -0,0 +1,104 @@ +package sysdig + +import ( + "context" + "time" + + v2 "github.com/draios/terraform-provider-sysdig/sysdig/internal/client/v2" + "github.com/hashicorp/terraform-plugin-log/tflog" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func dataSourceSysdigSecureAWSMLPolicy() *schema.Resource { + timeout := 5 * time.Minute + + return &schema.Resource{ + ReadContext: dataSourceSysdigSecureAWSMLPolicyRead, + + Timeouts: &schema.ResourceTimeout{ + Read: schema.DefaultTimeout(timeout), + }, + + Schema: createAWSMLPolicyDataSourceSchema(), + } +} + +func dataSourceSysdigSecureAWSMLPolicyRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + return awsMLPolicyDataSourceRead(ctx, d, meta, "custom policy", isCustomCompositePolicy) +} + +func createAWSMLPolicyDataSourceSchema() map[string]*schema.Schema { + return map[string]*schema.Schema{ + // IMPORTANT: Type is implicit: It's automatically added upon conversion to JSON + "type": { + Type: schema.TypeString, + Computed: true, + }, + "name": NameSchema(), + "description": DescriptionComputedSchema(), + "enabled": EnabledComputedSchema(), + "severity": SeverityComputedSchema(), + "scope": ScopeComputedSchema(), + "version": VersionSchema(), + "notification_channels": NotificationChannelsComputedSchema(), + "runbook": RunbookComputedSchema(), + "rule": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "id": ReadOnlyIntSchema(), + "name": ReadOnlyStringSchema(), + "description": DescriptionComputedSchema(), + "tags": TagsSchema(), + "version": VersionSchema(), + "anomalous_console_login": MLRuleThresholdAndSeverityComputedSchema(), + }, + }, + }, + } +} + +func awsMLPolicyDataSourceRead(ctx context.Context, d *schema.ResourceData, meta interface{}, resourceName string, validationFunc func(v2.PolicyRulesComposite) bool) diag.Diagnostics { + client, err := getSecureCompositePolicyClient(meta.(SysdigClients)) + if err != nil { + return diag.FromErr(err) + } + + policyName := d.Get("name").(string) + policyType := policyTypeAWSML + + policies, _, err := client.FilterCompositePoliciesByNameAndType(ctx, policyType, policyName) + if err != nil { + return diag.FromErr(err) + } + + var policy v2.PolicyRulesComposite + for _, existingPolicy := range policies { + tflog.Debug(ctx, "Filtered policies", map[string]interface{}{"name": existingPolicy.Policy.Name}) + + if existingPolicy.Policy.Name == policyName && existingPolicy.Policy.Type == policyType { + if !validationFunc(existingPolicy) { + return diag.Errorf("policy is not a %s", resourceName) + } + policy = existingPolicy + break + } + } + + if policy.Policy == nil { + return diag.Errorf("unable to find policy %s", resourceName) + } + + if policy.Policy.ID == 0 { + return diag.Errorf("unable to find %s", resourceName) + } + + err = awsMLPolicyToResourceData(&policy, d) + if err != nil { + return diag.FromErr(err) + } + + return nil +} diff --git a/sysdig/data_source_sysdig_secure_aws_ml_policy_test.go b/sysdig/data_source_sysdig_secure_aws_ml_policy_test.go new file mode 100644 index 00000000..5966c757 --- /dev/null +++ b/sysdig/data_source_sysdig_secure_aws_ml_policy_test.go @@ -0,0 +1,64 @@ +//go:build tf_acc_sysdig_secure || tf_acc_policies || tf_acc_onprem_secure + +package sysdig_test + +import ( + "fmt" + "os" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + + "github.com/draios/terraform-provider-sysdig/sysdig" +) + +func TestAccAWSMLPolicyDataSource(t *testing.T) { + rText := acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + if v := os.Getenv("SYSDIG_SECURE_API_TOKEN"); v == "" { + t.Fatal("SYSDIG_SECURE_API_TOKEN must be set for acceptance tests") + } + }, + ProviderFactories: map[string]func() (*schema.Provider, error){ + "sysdig": func() (*schema.Provider, error) { + return sysdig.Provider(), nil + }, + }, + Steps: []resource.TestStep{ + { + Config: awsAWSMLPolicyDataSource(rText), + }, + }, + }) +} + +func awsAWSMLPolicyDataSource(name string) string { + return fmt.Sprintf(` +resource "sysdig_secure_aws_ml_policy" "policy_1" { + name = "Test AWS ML Policy %s" + description = "Test AWS ML Policy Description %s" + enabled = true + severity = 4 + + rule { + description = "Test AWS ML Rule Description" + + anomalous_console_login { + enabled = true + threshold = 2 + severity = 1 + } + } + +} + +data "sysdig_secure_aws_ml_policy" "policy_2" { + name = sysdig_secure_aws_ml_policy.policy_1.name + depends_on = [sysdig_secure_aws_ml_policy.policy_1] +} +`, name, name) +} diff --git a/sysdig/data_source_sysdig_secure_drift_policy.go b/sysdig/data_source_sysdig_secure_drift_policy.go new file mode 100644 index 00000000..82614f21 --- /dev/null +++ b/sysdig/data_source_sysdig_secure_drift_policy.go @@ -0,0 +1,117 @@ +package sysdig + +import ( + "context" + "time" + + v2 "github.com/draios/terraform-provider-sysdig/sysdig/internal/client/v2" + "github.com/hashicorp/terraform-plugin-log/tflog" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func dataSourceSysdigSecureDriftPolicy() *schema.Resource { + timeout := 5 * time.Minute + + return &schema.Resource{ + ReadContext: dataSourceSysdigSecureDriftPolicyRead, + + Timeouts: &schema.ResourceTimeout{ + Read: schema.DefaultTimeout(timeout), + }, + + Schema: createDriftPolicyDataSourceSchema(), + } +} + +func dataSourceSysdigSecureDriftPolicyRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + return driftPolicyDataSourceRead(ctx, d, meta, "custom policy", isCustomCompositePolicy) +} + +func createDriftPolicyDataSourceSchema() map[string]*schema.Schema { + return map[string]*schema.Schema{ + // IMPORTANT: Type is implicit: It's automatically added upon conversion to JSON + "type": { + Type: schema.TypeString, + Computed: true, + }, + "name": NameSchema(), + "description": DescriptionComputedSchema(), + "enabled": EnabledComputedSchema(), + "severity": SeverityComputedSchema(), + "scope": ScopeComputedSchema(), + "version": VersionSchema(), + "notification_channels": NotificationChannelsComputedSchema(), + "runbook": RunbookComputedSchema(), + "rule": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "id": ReadOnlyIntSchema(), + "name": ReadOnlyStringSchema(), + "description": DescriptionComputedSchema(), + "tags": TagsSchema(), + "version": VersionSchema(), + "enabled": BoolComputedSchema(), + "exceptions": ExceptionsComputedSchema(), + "prohibited_binaries": ExceptionsComputedSchema(), + }, + }, + }, + "actions": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "prevent_drift": PreventActionComputedSchema(), + "container": ContainerActionComputedSchema(), + "capture": CaptureActionComputedSchema(), + }, + }, + }, + } +} + +func driftPolicyDataSourceRead(ctx context.Context, d *schema.ResourceData, meta interface{}, resourceName string, validationFunc func(v2.PolicyRulesComposite) bool) diag.Diagnostics { + client, err := getSecureCompositePolicyClient(meta.(SysdigClients)) + if err != nil { + return diag.FromErr(err) + } + + policyName := d.Get("name").(string) + policyType := policyTypeDrift + + policies, _, err := client.FilterCompositePoliciesByNameAndType(ctx, policyType, policyName) + if err != nil { + return diag.FromErr(err) + } + + var policy v2.PolicyRulesComposite + for _, existingPolicy := range policies { + tflog.Debug(ctx, "Filtered policies", map[string]interface{}{"name": existingPolicy.Policy.Name}) + + if existingPolicy.Policy.Name == policyName && existingPolicy.Policy.Type == policyType { + if !validationFunc(existingPolicy) { + return diag.Errorf("policy is not a %s", resourceName) + } + policy = existingPolicy + break + } + } + + if policy.Policy == nil { + return diag.Errorf("unable to find policy %s", resourceName) + } + + if policy.Policy.ID == 0 { + return diag.Errorf("unable to find %s", resourceName) + } + + err = driftPolicyToResourceData(&policy, d) + if err != nil { + return diag.FromErr(err) + } + + return nil +} diff --git a/sysdig/data_source_sysdig_secure_drift_policy_test.go b/sysdig/data_source_sysdig_secure_drift_policy_test.go new file mode 100644 index 00000000..30d040a0 --- /dev/null +++ b/sysdig/data_source_sysdig_secure_drift_policy_test.go @@ -0,0 +1,70 @@ +//go:build tf_acc_sysdig_secure || tf_acc_policies || tf_acc_onprem_secure + +package sysdig_test + +import ( + "fmt" + "os" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + + "github.com/draios/terraform-provider-sysdig/sysdig" +) + +func TestAccDriftPolicyDataSource(t *testing.T) { + rText := acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + if v := os.Getenv("SYSDIG_SECURE_API_TOKEN"); v == "" { + t.Fatal("SYSDIG_SECURE_API_TOKEN must be set for acceptance tests") + } + }, + ProviderFactories: map[string]func() (*schema.Provider, error){ + "sysdig": func() (*schema.Provider, error) { + return sysdig.Provider(), nil + }, + }, + Steps: []resource.TestStep{ + { + Config: driftPolicyDataSource(rText), + }, + }, + }) +} + +func driftPolicyDataSource(name string) string { + return fmt.Sprintf(` +resource "sysdig_secure_drift_policy" "policy_1" { + name = "Test Drift Policy %s" + description = "Test Drift Policy Description %s" + enabled = true + severity = 4 + + rule { + description = "Test Drift Rule Description" + enabled = true + + exceptions { + items = ["/usr/bin/sh"] + } + prohibited_binaries { + items = ["/usr/bin/curl"] + } + } + + actions { + prevent_drift = true + } + +} + +data "sysdig_secure_drift_policy" "policy_2" { + name = sysdig_secure_drift_policy.policy_1.name + depends_on = [sysdig_secure_drift_policy.policy_1] +} +`, name, name) +} diff --git a/sysdig/data_source_sysdig_secure_malware_policy.go b/sysdig/data_source_sysdig_secure_malware_policy.go new file mode 100644 index 00000000..8877d684 --- /dev/null +++ b/sysdig/data_source_sysdig_secure_malware_policy.go @@ -0,0 +1,121 @@ +package sysdig + +import ( + "context" + "time" + + v2 "github.com/draios/terraform-provider-sysdig/sysdig/internal/client/v2" + "github.com/hashicorp/terraform-plugin-log/tflog" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func dataSourceSysdigSecureMalwarePolicy() *schema.Resource { + timeout := 5 * time.Minute + + return &schema.Resource{ + ReadContext: dataSourceSysdigSecureMalwarePolicyRead, + + Timeouts: &schema.ResourceTimeout{ + Read: schema.DefaultTimeout(timeout), + }, + + Schema: createMalwarePolicyDataSourceSchema(), + } +} + +func dataSourceSysdigSecureMalwarePolicyRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + return malwarePolicyDataSourceRead(ctx, d, meta, "custom policy", isCustomCompositePolicy) +} + +func isCustomCompositePolicy(policy v2.PolicyRulesComposite) bool { + return !policy.Policy.IsDefault && policy.Policy.TemplateId == 0 +} + +func createMalwarePolicyDataSourceSchema() map[string]*schema.Schema { + return map[string]*schema.Schema{ + // IMPORTANT: Type is implicit: It's automatically added upon conversion to JSON + "type": { + Type: schema.TypeString, + Computed: true, + }, + "name": NameSchema(), + "description": DescriptionComputedSchema(), + "enabled": EnabledComputedSchema(), + "severity": SeverityComputedSchema(), + "scope": ScopeComputedSchema(), + "version": VersionSchema(), + "notification_channels": NotificationChannelsComputedSchema(), + "runbook": RunbookComputedSchema(), + "rule": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "id": ReadOnlyIntSchema(), + "name": ReadOnlyStringSchema(), + "description": DescriptionComputedSchema(), + "tags": TagsSchema(), + "version": VersionSchema(), + "use_managed_hashes": BoolComputedSchema(), + "additional_hashes": HashesComputedSchema(), + "ignore_hashes": HashesComputedSchema(), + }, + }, + }, + "actions": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "prevent_malware": PreventActionComputedSchema(), + "container": ContainerActionComputedSchema(), + "capture": CaptureActionComputedSchema(), + }, + }, + }, + } +} + +func malwarePolicyDataSourceRead(ctx context.Context, d *schema.ResourceData, meta interface{}, resourceName string, validationFunc func(v2.PolicyRulesComposite) bool) diag.Diagnostics { + client, err := getSecureCompositePolicyClient(meta.(SysdigClients)) + if err != nil { + return diag.FromErr(err) + } + + policyName := d.Get("name").(string) + policyType := policyTypeMalware + + policies, _, err := client.FilterCompositePoliciesByNameAndType(ctx, policyType, policyName) + if err != nil { + return diag.FromErr(err) + } + + var policy v2.PolicyRulesComposite + for _, existingPolicy := range policies { + tflog.Debug(ctx, "Filtered policies", map[string]interface{}{"name": existingPolicy.Policy.Name}) + + if existingPolicy.Policy.Name == policyName && existingPolicy.Policy.Type == policyType { + if !validationFunc(existingPolicy) { + return diag.Errorf("policy is not a %s", resourceName) + } + policy = existingPolicy + break + } + } + + if policy.Policy == nil { + return diag.Errorf("unable to find policy %s", resourceName) + } + + if policy.Policy.ID == 0 { + return diag.Errorf("unable to find %s", resourceName) + } + + err = malwarePolicyToResourceData(&policy, d) + if err != nil { + return diag.FromErr(err) + } + + return nil +} diff --git a/sysdig/data_source_sysdig_secure_malware_policy_test.go b/sysdig/data_source_sysdig_secure_malware_policy_test.go new file mode 100644 index 00000000..5e818b24 --- /dev/null +++ b/sysdig/data_source_sysdig_secure_malware_policy_test.go @@ -0,0 +1,71 @@ +//go:build tf_acc_sysdig_secure || tf_acc_policies || tf_acc_onprem_secure + +package sysdig_test + +import ( + "fmt" + "os" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + + "github.com/draios/terraform-provider-sysdig/sysdig" +) + +func TestAccMalwarePolicyDataSource(t *testing.T) { + rText := acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + if v := os.Getenv("SYSDIG_SECURE_API_TOKEN"); v == "" { + t.Fatal("SYSDIG_SECURE_API_TOKEN must be set for acceptance tests") + } + }, + ProviderFactories: map[string]func() (*schema.Provider, error){ + "sysdig": func() (*schema.Provider, error) { + return sysdig.Provider(), nil + }, + }, + Steps: []resource.TestStep{ + { + Config: malwarePolicyDataSource(rText), + }, + }, + }) +} + +func malwarePolicyDataSource(name string) string { + return fmt.Sprintf(` +resource "sysdig_secure_malware_policy" "policy_1" { + name = "Test Malware Policy %s" + description = "Test Malware Policy Description %s" + enabled = true + severity = 4 + + rule { + description = "Test Malware Rule Description" + + use_managed_hashes = true + + additional_hashes { + hash = "304ef4cdda3463b24bf53f9cdd69ad3ecdab0842e7e70e2f3cfbb9f14e1c4ae6" + } + + ignore_hashes { + hash = "6ac3c336e4094835293a3fed8a4b5fedde1b5e2626d9838fed50693bba00af0e" + } + } + + actions { + prevent_malware = true + } + +} + +data "sysdig_secure_malware_policy" "policy_2" { + name = sysdig_secure_malware_policy.policy_1.name +} +`, name, name) +} diff --git a/sysdig/data_source_sysdig_secure_ml_policy.go b/sysdig/data_source_sysdig_secure_ml_policy.go new file mode 100644 index 00000000..2c452154 --- /dev/null +++ b/sysdig/data_source_sysdig_secure_ml_policy.go @@ -0,0 +1,104 @@ +package sysdig + +import ( + "context" + "time" + + v2 "github.com/draios/terraform-provider-sysdig/sysdig/internal/client/v2" + "github.com/hashicorp/terraform-plugin-log/tflog" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func dataSourceSysdigSecureMLPolicy() *schema.Resource { + timeout := 5 * time.Minute + + return &schema.Resource{ + ReadContext: dataSourceSysdigSecureMLPolicyRead, + + Timeouts: &schema.ResourceTimeout{ + Read: schema.DefaultTimeout(timeout), + }, + + Schema: createMLPolicyDataSourceSchema(), + } +} + +func dataSourceSysdigSecureMLPolicyRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + return mlPolicyDataSourceRead(ctx, d, meta, "custom policy", isCustomCompositePolicy) +} + +func createMLPolicyDataSourceSchema() map[string]*schema.Schema { + return map[string]*schema.Schema{ + // IMPORTANT: Type is implicit: It's automatically added upon conversion to JSON + "type": { + Type: schema.TypeString, + Computed: true, + }, + "name": NameSchema(), + "description": DescriptionComputedSchema(), + "enabled": EnabledComputedSchema(), + "severity": SeverityComputedSchema(), + "scope": ScopeComputedSchema(), + "version": VersionSchema(), + "notification_channels": NotificationChannelsComputedSchema(), + "runbook": RunbookComputedSchema(), + "rule": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "id": ReadOnlyIntSchema(), + "name": ReadOnlyStringSchema(), + "description": DescriptionComputedSchema(), + "tags": TagsSchema(), + "version": VersionSchema(), + "cryptomining_trigger": MLRuleThresholdAndSeverityComputedSchema(), + }, + }, + }, + } +} + +func mlPolicyDataSourceRead(ctx context.Context, d *schema.ResourceData, meta interface{}, resourceName string, validationFunc func(v2.PolicyRulesComposite) bool) diag.Diagnostics { + client, err := getSecureCompositePolicyClient(meta.(SysdigClients)) + if err != nil { + return diag.FromErr(err) + } + + policyName := d.Get("name").(string) + policyType := policyTypeML + + policies, _, err := client.FilterCompositePoliciesByNameAndType(ctx, policyType, policyName) + if err != nil { + return diag.FromErr(err) + } + + var policy v2.PolicyRulesComposite + for _, existingPolicy := range policies { + tflog.Debug(ctx, "Filtered policies", map[string]interface{}{"name": existingPolicy.Policy.Name}) + + if existingPolicy.Policy.Name == policyName && existingPolicy.Policy.Type == policyType { + if !validationFunc(existingPolicy) { + return diag.Errorf("policy is not a %s", resourceName) + } + policy = existingPolicy + break + } + } + + if policy.Policy == nil { + return diag.Errorf("unable to find policy %s", resourceName) + } + + if policy.Policy.ID == 0 { + return diag.Errorf("unable to find %s", resourceName) + } + + err = mlPolicyToResourceData(&policy, d) + if err != nil { + return diag.FromErr(err) + } + + return nil +} diff --git a/sysdig/data_source_sysdig_secure_ml_policy_test.go b/sysdig/data_source_sysdig_secure_ml_policy_test.go new file mode 100644 index 00000000..613e1e16 --- /dev/null +++ b/sysdig/data_source_sysdig_secure_ml_policy_test.go @@ -0,0 +1,64 @@ +//go:build tf_acc_sysdig_secure || tf_acc_policies || tf_acc_onprem_secure + +package sysdig_test + +import ( + "fmt" + "os" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + + "github.com/draios/terraform-provider-sysdig/sysdig" +) + +func TestAccMLPolicyDataSource(t *testing.T) { + rText := acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + if v := os.Getenv("SYSDIG_SECURE_API_TOKEN"); v == "" { + t.Fatal("SYSDIG_SECURE_API_TOKEN must be set for acceptance tests") + } + }, + ProviderFactories: map[string]func() (*schema.Provider, error){ + "sysdig": func() (*schema.Provider, error) { + return sysdig.Provider(), nil + }, + }, + Steps: []resource.TestStep{ + { + Config: mlPolicyDataSource(rText), + }, + }, + }) +} + +func mlPolicyDataSource(name string) string { + return fmt.Sprintf(` +resource "sysdig_secure_ml_policy" "policy_1" { + name = "Test ML Policy %s" + description = "Test ML Policy Description %s" + enabled = true + severity = 4 + + rule { + description = "Test ML Rule Description" + + cryptomining_trigger { + enabled = true + threshold = 1 + severity = 1 + } + } + +} + +data "sysdig_secure_ml_policy" "policy_2" { + name = sysdig_secure_ml_policy.policy_1.name + depends_on = [sysdig_secure_ml_policy.policy_1] +} +`, name, name) +} diff --git a/sysdig/internal/client/v2/composite_policies.go b/sysdig/internal/client/v2/composite_policies.go new file mode 100644 index 00000000..993da8b9 --- /dev/null +++ b/sysdig/internal/client/v2/composite_policies.go @@ -0,0 +1,250 @@ +package v2 + +import ( + "context" + "errors" + "fmt" + "net/http" + "net/url" + "strings" +) + +const ( + skipPolicyV2MsgFlag = "skipPolicyV2Msg" + CreateCompositePolicyPath = "%s/api/v2/policies/batch?%s=%t" + DeleteCompositePolicyPath = "%s/api/v2/policies/batch/%d?%s=%t" + UpdateCompositePolicyPath = "%s/api/v2/policies/batch/%d" + + GetCompositePolicyPath = "%s/api/v2/policies/%d" // TODO: Add skip query param + GetCompositePoliciesPath = "%s/api/v2/policies?%s" // TODO: Implement pagination otherwise up to getPoliciesLimit number of policies will be returned + + GetCompositePolicyRulesPath = "%s/api/policies/v3/rules/groups?%s" + getPoliciesLimit = 1000 // TODO: What is a good limit? +) + +type CompositePolicyInterface interface { + Base + CreateCompositePolicy(ctx context.Context, policy PolicyRulesComposite) (PolicyRulesComposite, error) + DeleteCompositePolicy(ctx context.Context, policyID int) error + UpdateCompositePolicy(ctx context.Context, policy PolicyRulesComposite) (PolicyRulesComposite, error) + GetCompositePolicyByID(ctx context.Context, policyID int) (PolicyRulesComposite, int, error) + FilterCompositePoliciesByNameAndType(ctx context.Context, policyType string, policyName string) ([]PolicyRulesComposite, int, error) +} + +func (client *Client) CreateCompositePolicy(ctx context.Context, policy PolicyRulesComposite) (PolicyRulesComposite, error) { + payload, err := Marshal(policy) + if err != nil { + return PolicyRulesComposite{}, err + } + + response, err := client.requester.Request(ctx, http.MethodPost, client.CreateCompositePolicyURL(), payload) + if err != nil { + return PolicyRulesComposite{}, err + } + defer response.Body.Close() + + if response.StatusCode != http.StatusOK { + return PolicyRulesComposite{}, client.ErrorFromResponse(response) + } + + return Unmarshal[PolicyRulesComposite](response.Body) +} + +func (client *Client) UpdateCompositePolicy(ctx context.Context, policy PolicyRulesComposite) (PolicyRulesComposite, error) { + payload, err := Marshal(policy) + if err != nil { + return PolicyRulesComposite{}, err + } + + response, err := client.requester.Request(ctx, http.MethodPut, client.UpdateCompositePolicyURL(policy.Policy.ID), payload) + if err != nil { + return PolicyRulesComposite{}, err + } + defer response.Body.Close() + + if response.StatusCode != http.StatusOK { + return PolicyRulesComposite{}, client.ErrorFromResponse(response) + } + + return Unmarshal[PolicyRulesComposite](response.Body) +} + +func (client *Client) DeleteCompositePolicy(ctx context.Context, policyID int) error { + response, err := client.requester.Request(ctx, http.MethodDelete, client.DeleteCompositePolicyURL(policyID), nil) + if err != nil { + return err + } + defer response.Body.Close() + + if response.StatusCode != http.StatusNoContent && response.StatusCode != http.StatusOK { + return client.ErrorFromResponse(response) + } + + return err +} + +func (client *Client) GetCompositePolicyByID(ctx context.Context, policyID int) (PolicyRulesComposite, int, error) { + response, err := client.requester.Request(ctx, http.MethodGet, client.GetCompositePolicyURL(policyID), nil) + if err != nil { + return PolicyRulesComposite{}, 0, err + } + defer response.Body.Close() + + if response.StatusCode != http.StatusOK { + return PolicyRulesComposite{}, response.StatusCode, client.ErrorFromResponse(response) + } + + policy, err := Unmarshal[Policy](response.Body) + if err != nil { + return PolicyRulesComposite{}, 0, err + } + + names := []string{} + for _, rule := range policy.Rules { + names = append(names, rule.Name) + } + + rules, _, err := client.GetCompositePolicyRulesByName(ctx, names) + if err != nil { + return PolicyRulesComposite{}, 0, err + } + + return PolicyRulesComposite{ + Policy: &policy, + Rules: rules, + }, http.StatusOK, nil +} + +func (client *Client) GetCompositePolicyRulesByName(ctx context.Context, names []string) ([]*RuntimePolicyRule, int, error) { + if len(names) == 0 { + return nil, http.StatusOK, errors.New("Please provide at least one rule name") + } + + response, err := client.requester.Request(ctx, http.MethodGet, client.GetCompositePolicyRulesURL(names), nil) + if err != nil { + return []*RuntimePolicyRule{}, 0, err + } + defer response.Body.Close() + + if response.StatusCode != http.StatusOK { + return []*RuntimePolicyRule{}, response.StatusCode, client.ErrorFromResponse(response) + } + + unmarshalled, err := Unmarshal[[][]*RuntimePolicyRule](response.Body) + if err != nil { + return []*RuntimePolicyRule{}, 0, err + } + + rules := []*RuntimePolicyRule{} + for _, arr := range unmarshalled { + rules = append(rules, arr...) + } + + if len(rules) == 0 { + return nil, http.StatusOK, errors.New("Rules not found") + } + + return rules, http.StatusOK, nil +} + +// This method is used in a data source to retrieve a policy by name and type. +// We must retrieve and iterate over all policies, as there is no endpoint to get a policy by name. +func (client *Client) FilterCompositePoliciesByNameAndType(ctx context.Context, policyType string, policyName string) ([]PolicyRulesComposite, int, error) { + // TODO: Implement pagination in order to get all policies? + q := GetPoliciesQueryParams{policyType, policyName, getPoliciesLimit} + response, err := client.requester.Request(ctx, http.MethodGet, client.GetCompositePoliciesURL(q), nil) + if err != nil { + return []PolicyRulesComposite{}, 0, err + } + defer response.Body.Close() + + if response.StatusCode != http.StatusOK { + return []PolicyRulesComposite{}, response.StatusCode, client.ErrorFromResponse(response) + } + + policies, err := Unmarshal[[]Policy](response.Body) // TODO + if err != nil { + return []PolicyRulesComposite{}, 0, err + } + + if len(policies) == 0 { + return []PolicyRulesComposite{}, 0, fmt.Errorf("Policy was not found: %s %s", policyType, policyName) + } + + compositePoliciesByPolicyID := map[int]PolicyRulesComposite{} + policyIDByRuleName := map[string]int{} + names := []string{} + for _, policy := range policies { + x := policy + compositePoliciesByPolicyID[x.ID] = PolicyRulesComposite{ + Policy: &x, + Rules: []*RuntimePolicyRule{}, + } + + for _, rule := range x.Rules { + policyIDByRuleName[rule.Name] = x.ID + names = append(names, rule.Name) + } + } + + rules, _, err := client.GetCompositePolicyRulesByName(ctx, names) + if err != nil { + return []PolicyRulesComposite{}, 0, err + } + + if len(rules) != len(names) { + return []PolicyRulesComposite{}, 0, fmt.Errorf("Some rules were not found: %d != %d", len(rules), len(names)) + } + + for _, rule := range rules { + policyID := policyIDByRuleName[rule.Name] + p := compositePoliciesByPolicyID[policyID] + p.Rules = append(p.Rules, rule) + compositePoliciesByPolicyID[policyID] = p + } + + policiesFull := []PolicyRulesComposite{} + for _, policy := range compositePoliciesByPolicyID { + policiesFull = append(policiesFull, policy) + } + + return policiesFull, http.StatusOK, nil +} + +func (client *Client) CreateCompositePolicyURL() string { + return fmt.Sprintf(CreateCompositePolicyPath, client.config.url, skipPolicyV2MsgFlag, client.config.secureSkipPolicyV2Msg) +} + +func (client *Client) DeleteCompositePolicyURL(policyID int) string { + return fmt.Sprintf(DeleteCompositePolicyPath, client.config.url, policyID, skipPolicyV2MsgFlag, client.config.secureSkipPolicyV2Msg) +} + +func (client *Client) UpdateCompositePolicyURL(policyID int) string { + return fmt.Sprintf(UpdateCompositePolicyPath, client.config.url, policyID) +} + +func (client *Client) GetCompositePolicyURL(policyID int) string { + return fmt.Sprintf(GetCompositePolicyPath, client.config.url, policyID) +} + +func (client *Client) GetCompositePoliciesURL(queryParams GetPoliciesQueryParams) string { + return fmt.Sprintf(GetCompositePoliciesPath, client.config.url, queryParams.Encode()) +} + +func (client *Client) GetCompositePolicyRulesURL(names []string) string { + items := []string{} + for _, name := range names { + items = append(items, fmt.Sprintf("names=%s", name)) + } + return fmt.Sprintf(GetCompositePolicyRulesPath, client.config.url, strings.Join(items[:], "&")) +} + +type GetPoliciesQueryParams struct { + PolicyType string + Filter string + Limit int +} + +func (q *GetPoliciesQueryParams) Encode() string { + return fmt.Sprintf("policyType=%s&filter=%s&limit=%d", q.PolicyType, url.QueryEscape(q.Filter), q.Limit) +} diff --git a/sysdig/internal/client/v2/model.go b/sysdig/internal/client/v2/model.go index dbed0ffb..7eea2291 100644 --- a/sysdig/internal/client/v2/model.go +++ b/sysdig/internal/client/v2/model.go @@ -1,6 +1,9 @@ package v2 import ( + "encoding/json" + "errors" + cloudauth "github.com/draios/terraform-provider-sysdig/sysdig/internal/client/v2/cloudauth/go" ) @@ -250,11 +253,198 @@ type Policy struct { Version int `json:"version,omitempty"` NotificationChannelIds []int `json:"notificationChannelIds"` Type string `json:"type"` + Origin string `json:"origin"` Runbook string `json:"runbook"` TemplateId int `json:"templateId"` TemplateVersion string `json:"templateVersion"` } +type PolicyRulesComposite struct { + Policy *Policy `json:"policy"` + Rules []*RuntimePolicyRule `json:"rules"` +} + +type ( + FlexInt int + RuntimePolicyObjectOrigin string // NOTE: This is an int in model_rules.go#L247 + ElementType string +) + +type RuntimePolicyRuleDetails interface { + GetRuleType() ElementType +} + +type RuntimePolicyRule struct { + Id *FlexInt `json:"id"` + Name string `json:"name"` + Origin *RuntimePolicyObjectOrigin `json:"origin"` + VersionId string `json:"versionId"` + Filename string `json:"filename"` + Description string `json:"description"` + Details RuntimePolicyRuleDetails `json:"details"` + Tags []string `json:"tags"` + Version *int `json:"version"` + CreatedOn int64 `json:"createdOn"` + ModifiedOn int64 `json:"modifiedOn"` +} + +// TODO: This should be exported into a common package +// Copied from: https://github.com/draios/secure-backend/blob/main/policies/model/model_rules.go#L676C1-L779C2 +func (r *RuntimePolicyRule) UnmarshalJSON(b []byte) error { + type findType struct { + RuleType string `json:"ruleType"` + } + findDetails := struct { + FindType findType `json:"details"` + Id *FlexInt `json:"id"` + Name string `json:"name"` + Origin *RuntimePolicyObjectOrigin `json:"origin"` + VersionId string `json:"versionId"` + Filename string `json:"filename"` + Description string `json:"description"` + Tags []string `json:"tags"` + Version *int `json:"version"` + CreatedOn int64 `json:"createdOn"` + ModifiedOn int64 `json:"modifiedOn"` + }{} + + err := json.Unmarshal(b, &findDetails) + if err != nil { + return err + } + + var d RuntimePolicyRuleDetails + switch findDetails.FindType.RuleType { + // case "FALCO": + // d = &FalcoRuleDetails{} + // case "CONTAINER": + // d = &ContainerImageRuleDetails{} + // case "FILESYSTEM": + // d = &FilesystemRuleDetails{} + // case "NETWORK": + // d = &NetworkRuleDetails{ + // AllInbound: true, + // AllOutbound: true, + // } + // case "PROCESS": + // d = &ProcessRuleDetails{} + // case "SYSCALL": + // d = &SyscallRuleDetails{} + case "DRIFT": + d = &DriftRuleDetails{} + case "MACHINE_LEARNING": + d = &MLRuleDetails{} + case "AWS_MACHINE_LEARNING": + d = &AWSMLRuleDetails{} + case "MALWARE": + d = &MalwareRuleDetails{} + // case "OKTA_MACHINE_LEARNING": + // d = &OktaMLRuleDetails{} + default: + return errors.New("The field details has an unknown ruleType: " + findDetails.FindType.RuleType) + } + + getRawDetails := struct { + RawDetails json.RawMessage `json:"details"` + }{} + err = json.Unmarshal(b, &getRawDetails) + if err != nil { + return err + } + + err = json.Unmarshal(getRawDetails.RawDetails, d) + if err != nil { + return err + } + + if findDetails.FindType.RuleType == "DRIFT" { + d1 := &DriftRuleDetails{} + err = json.Unmarshal(getRawDetails.RawDetails, d1) + if err != nil { + return err + } + if d1.Exceptions != nil && d1.ProhibitedBinaries != nil { + d = d1 + } + } + + var findDetailsIdPtr *FlexInt + if findDetails.Id != nil { + findDetailsId := FlexInt(*findDetails.Id) + findDetailsIdPtr = &findDetailsId + } + + r.Id = findDetailsIdPtr + r.Name = findDetails.Name + r.Origin = findDetails.Origin + r.VersionId = findDetails.VersionId + r.Filename = findDetails.Filename + r.Description = findDetails.Description + r.Tags = findDetails.Tags + r.Version = findDetails.Version + r.Details = d + r.CreatedOn = findDetails.CreatedOn + r.ModifiedOn = findDetails.ModifiedOn + + return nil +} + +type MLRuleThresholdAndSeverity struct { + Enabled bool `json:"enabled" yaml:"enabled"` + Threshold float64 `json:"threshold" yaml:"threshold"` + Severity float64 `json:"severity" yaml:"severity"` +} + +type MLRuleDetails struct { + RuleType ElementType `json:"ruleType" yaml:"ruleType"` + AnomalyDetectionTrigger *MLRuleThresholdAndSeverity `json:"anomalyDetectionTrigger" yaml:"anomalyDetectionTrigger"` + CryptominingTrigger *MLRuleThresholdAndSeverity `json:"cryptominingTrigger" yaml:"cryptominingTrigger"` + Details `json:"-"` +} + +func (p MLRuleDetails) GetRuleType() ElementType { + return p.RuleType +} + +type MalwareRuleDetails struct { + RuleType ElementType `json:"ruleType"` + UseManagedHashes bool `json:"useManagedHashes"` + AdditionalHashes map[string][]string `json:"additionalHashes"` + IgnoreHashes map[string][]string `json:"ignoreHashes"` + Details `json:"-"` +} + +func (p MalwareRuleDetails) GetRuleType() ElementType { + return p.RuleType +} + +type RuntimePolicyRuleList struct { + Items []string `json:"items"` + MatchItems bool `json:"matchItems"` +} + +type DriftRuleDetails struct { + RuleType ElementType `json:"ruleType"` + Exceptions *RuntimePolicyRuleList `json:"exceptionList"` + ProhibitedBinaries *RuntimePolicyRuleList `json:"prohibitedBinaries"` + Mode string `json:"mode"` + Details `json:"-"` +} + +func (p DriftRuleDetails) GetRuleType() ElementType { + return p.RuleType +} + +type AWSMLRuleDetails struct { + RuleType ElementType `json:"ruleType" yaml:"ruleType"` + AnomalousConsoleLogin *MLRuleThresholdAndSeverity `json:"anomalousConsoleLogin" yaml:"anomalousConsoleLogin"` + Details `json:"-"` +} + +func (p AWSMLRuleDetails) GetRuleType() ElementType { + return p.RuleType +} + type PolicyRule struct { Name string `json:"ruleName"` Enabled bool `json:"enabled"` @@ -262,15 +452,16 @@ type PolicyRule struct { // Did not add support storageId because FE does not support it yet type Action struct { - AfterEventNs int `json:"afterEventNs,omitempty"` - BeforeEventNs int `json:"beforeEventNs,omitempty"` - Name string `json:"name,omitempty"` - Filter string `json:"filter,omitempty"` - StorageType string `json:"storageType,omitempty"` - BucketName string `json:"bucketName,omitempty"` - Folder string `json:"folder,omitempty"` - IsLimitedToContainer bool `json:"isLimitedToContainer"` - Type string `json:"type"` + AfterEventNs int `json:"afterEventNs,omitempty"` + BeforeEventNs int `json:"beforeEventNs,omitempty"` + Name string `json:"name,omitempty"` + Filter string `json:"filter,omitempty"` + StorageType string `json:"storageType,omitempty"` + BucketName string `json:"bucketName,omitempty"` + Folder string `json:"folder,omitempty"` + IsLimitedToContainer bool `json:"isLimitedToContainer"` + Type string `json:"type"` + Msg *string `json:"msg,omitempty"` } type List struct { diff --git a/sysdig/internal/client/v2/sysdig.go b/sysdig/internal/client/v2/sysdig.go index d72dabfc..6cb88450 100644 --- a/sysdig/internal/client/v2/sysdig.go +++ b/sysdig/internal/client/v2/sysdig.go @@ -35,6 +35,7 @@ type SysdigSecure interface { SysdigCommon SecureCommon PolicyInterface + CompositePolicyInterface RuleInterface ListInterface MacroInterface diff --git a/sysdig/provider.go b/sysdig/provider.go index f4fc6f1e..ed80e24a 100644 --- a/sysdig/provider.go +++ b/sysdig/provider.go @@ -121,7 +121,11 @@ func (p *SysdigProvider) Provider() *schema.Provider { "sysdig_custom_role": resourceSysdigCustomRole(), "sysdig_team_service_account": resourceSysdigTeamServiceAccount(), + "sysdig_secure_aws_ml_policy": resourceSysdigSecureAWSMLPolicy(), "sysdig_secure_custom_policy": resourceSysdigSecureCustomPolicy(), + "sysdig_secure_drift_policy": resourceSysdigSecureDriftPolicy(), + "sysdig_secure_malware_policy": resourceSysdigSecureMalwarePolicy(), + "sysdig_secure_ml_policy": resourceSysdigSecureMLPolicy(), "sysdig_secure_managed_policy": resourceSysdigSecureManagedPolicy(), "sysdig_secure_managed_ruleset": resourceSysdigSecureManagedRuleset(), "sysdig_secure_policy": resourceSysdigSecurePolicy(), @@ -198,7 +202,11 @@ func (p *SysdigProvider) Provider() *schema.Provider { "sysdig_secure_notification_channel_msteams": dataSourceSysdigSecureNotificationChannelMSTeams(), "sysdig_secure_notification_channel_prometheus_alert_manager": dataSourceSysdigSecureNotificationChannelPrometheusAlertManager(), "sysdig_secure_notification_channel_team_email": dataSourceSysdigSecureNotificationChannelTeamEmail(), + "sysdig_secure_aws_ml_policy": dataSourceSysdigSecureAWSMLPolicy(), "sysdig_secure_custom_policy": dataSourceSysdigSecureCustomPolicy(), + "sysdig_secure_drift_policy": dataSourceSysdigSecureDriftPolicy(), + "sysdig_secure_malware_policy": dataSourceSysdigSecureMalwarePolicy(), + "sysdig_secure_ml_policy": dataSourceSysdigSecureMLPolicy(), "sysdig_secure_managed_policy": dataSourceSysdigSecureManagedPolicy(), "sysdig_secure_managed_ruleset": dataSourceSysdigSecureManagedRuleset(), "sysdig_secure_rule_container": dataSourceSysdigSecureRuleContainer(), diff --git a/sysdig/resource_sysdig_secure_aws_ml_policy.go b/sysdig/resource_sysdig_secure_aws_ml_policy.go new file mode 100644 index 00000000..00c58152 --- /dev/null +++ b/sysdig/resource_sysdig_secure_aws_ml_policy.go @@ -0,0 +1,218 @@ +package sysdig + +import ( + "context" + "errors" + "net/http" + "strconv" + "time" + + v2 "github.com/draios/terraform-provider-sysdig/sysdig/internal/client/v2" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" +) + +func resourceSysdigSecureAWSMLPolicy() *schema.Resource { + timeout := 5 * time.Minute + + return &schema.Resource{ + CreateContext: resourceSysdigAWSMLPolicyCreate, + ReadContext: resourceSysdigAWSMLPolicyRead, + UpdateContext: resourceSysdigAWSMLPolicyUpdate, + DeleteContext: resourceSysdigAWSMLPolicyDelete, + Importer: &schema.ResourceImporter{ + StateContext: resourceSysdigSecureAWSMLPolicyImportState, + }, + + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(timeout), + Delete: schema.DefaultTimeout(timeout), + Update: schema.DefaultTimeout(timeout), + Read: schema.DefaultTimeout(timeout), + }, + // IMPORTANT: composite.Policy.Rules and composite.Policy.RuleNames are read-only attributes. + // They're not used as a source for rule updates, so it's okay to drop those attributes in TF. + // To update the rules, composite.Rules values are used instead. + // https://github.com/draios/secure-backend/blob/main/policies/api/handler_policies.go#L1120 + Schema: map[string]*schema.Schema{ + // IMPORTANT: Type is implicit: It's automatically added upon conversion to JSON + "type": { + Type: schema.TypeString, + Optional: true, + Default: policyTypeAWSML, + ValidateDiagFunc: validateDiagFunc(validation.StringInSlice([]string{policyTypeAWSML}, false)), + }, + "name": NameSchema(), + "description": DescriptionSchema(), + "enabled": EnabledSchema(), + "severity": SeveritySchema(), + "scope": ScopeSchema(), + "version": VersionSchema(), + "notification_channels": NotificationChannelsSchema(), + "runbook": RunbookSchema(), + "rule": { + Type: schema.TypeList, + Required: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "id": ReadOnlyIntSchema(), + "name": ReadOnlyStringSchema(), + // Do not allow switching off individual rules + // "enabled": EnabledSchema(), + "description": DescriptionSchema(), + "tags": TagsSchema(), + "version": VersionSchema(), + "anomalous_console_login": MLRuleThresholdAndSeveritySchema(), + }, + }, + }, + }, // Schema end + } +} + +func awsMLPolicyFromResourceData(d *schema.ResourceData) (v2.PolicyRulesComposite, error) { + policy := &v2.PolicyRulesComposite{ + Policy: &v2.Policy{}, + Rules: []*v2.RuntimePolicyRule{}, + } + err := awsMLPolicyReducer(policy, d) + if err != nil { + return *policy, err + } + + return *policy, nil +} + +func awsMLPolicyToResourceData(policy *v2.PolicyRulesComposite, d *schema.ResourceData) error { + return awsMLTFResourceReducer(d, *policy) +} + +func resourceSysdigAWSMLPolicyCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + sysdigClients := meta.(SysdigClients) + client, err := getSecureCompositePolicyClient(sysdigClients) + if err != nil { + return diag.FromErr(err) + } + + policy, err := awsMLPolicyFromResourceData(d) + if err != nil { + return diag.FromErr(err) + } + + policy, err = client.CreateCompositePolicy(ctx, policy) + if err != nil { + return diag.FromErr(err) + } + + err = awsMLPolicyToResourceData(&policy, d) + if err != nil { + return diag.FromErr(err) + } + + sysdigClients.AddCleanupHook(sendPoliciesToAgents) + + return nil +} + +func resourceSysdigAWSMLPolicyUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + sysdigClients := meta.(SysdigClients) + client, err := getSecureCompositePolicyClient(sysdigClients) + if err != nil { + return diag.FromErr(err) + } + + policy, err := awsMLPolicyFromResourceData(d) + if err != nil { + return diag.FromErr(err) + } + + _, err = client.UpdateCompositePolicy(ctx, policy) + if err != nil { + return diag.FromErr(err) + } + sysdigClients.AddCleanupHook(sendPoliciesToAgents) + + return nil +} + +func resourceSysdigAWSMLPolicyRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + client, err := getSecureCompositePolicyClient(meta.(SysdigClients)) + if err != nil { + return diag.FromErr(err) + } + + id, _ := strconv.Atoi(d.Id()) + policy, statusCode, err := client.GetCompositePolicyByID(ctx, id) + if err != nil { + if statusCode == http.StatusNotFound { + d.SetId("") + } else { + return diag.FromErr(err) + } + } + + err = awsMLPolicyToResourceData(&policy, d) + if err != nil { + return diag.FromErr(err) + } + + return nil +} + +func resourceSysdigAWSMLPolicyDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + sysdigClients := meta.(SysdigClients) + client, err := getSecureCompositePolicyClient(sysdigClients) + if err != nil { + return diag.FromErr(err) + } + + policy, err := awsMLPolicyFromResourceData(d) + if err != nil { + return diag.FromErr(err) + } + + if policy.Policy.ID == 0 { + return diag.FromErr(errors.New("Policy ID is missing")) + } + + err = client.DeleteCompositePolicy(ctx, policy.Policy.ID) + if err != nil { + return diag.FromErr(err) + } + sysdigClients.AddCleanupHook(sendPoliciesToAgents) + + return nil +} + +func resourceSysdigSecureAWSMLPolicyImportState(ctx context.Context, d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { + client, err := getSecureCompositePolicyClient(meta.(SysdigClients)) + if err != nil { + return nil, err + } + + policy, err := awsMLPolicyFromResourceData(d) + if err != nil { + return nil, err + } + + if policy.Policy.ID == 0 { + return nil, errors.New("Policy ID is missing") + } + + policy, _, err = client.GetCompositePolicyByID(ctx, policy.Policy.ID) + if err != nil { + return nil, err + } + + if policy.Policy.IsDefault || policy.Policy.TemplateId != 0 { + return nil, errors.New("unable to import policy that is not a custom policy") + } + + err = awsMLPolicyToResourceData(&policy, d) + if err != nil { + return nil, err + } + + return []*schema.ResourceData{d}, nil +} diff --git a/sysdig/resource_sysdig_secure_aws_ml_policy_test.go b/sysdig/resource_sysdig_secure_aws_ml_policy_test.go new file mode 100644 index 00000000..e54274f4 --- /dev/null +++ b/sysdig/resource_sysdig_secure_aws_ml_policy_test.go @@ -0,0 +1,84 @@ +//go:build tf_acc_sysdig_secure || tf_acc_policies || tf_acc_onprem_secure + +package sysdig_test + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + + "github.com/draios/terraform-provider-sysdig/sysdig" +) + +func TestAccAWSMLPolicy(t *testing.T) { + rText := func() string { return acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum) } + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: preCheckAnyEnv(t, SysdigSecureApiTokenEnv), + ProviderFactories: map[string]func() (*schema.Provider, error){ + "sysdig": func() (*schema.Provider, error) { + return sysdig.Provider(), nil + }, + }, + Steps: []resource.TestStep{ + { + Config: awsMLPolicyWithName(rText()), + }, + { + Config: awsMLPolicyWithoutNotificationChannel(rText()), + }, + }, + }) +} + +func awsMLPolicyWithName(name string) string { + return fmt.Sprintf(` +%s + +resource "sysdig_secure_aws_ml_policy" "sample" { + name = "Test AWS ML Policy %s" + description = "Test AWS ML Policy Description" + enabled = true + severity = 4 + + rule { + description = "Test AWS ML Rule Description" + + anomalous_console_login { + enabled = true + threshold = 2 + severity = 1 + } + } + + notification_channels = [sysdig_secure_notification_channel_email.sample_email.id] +} + +`, secureNotificationChannelEmailWithName(name), name) +} + +func awsMLPolicyWithoutNotificationChannel(name string) string { + return fmt.Sprintf(` +resource "sysdig_secure_aws_ml_policy" "sample" { + name = "Test AWS ML Policy %s" + description = "Test AWS ML Policy Description" + enabled = true + severity = 4 + + rule { + description = "Test AWS ML Rule Description" + + anomalous_console_login { + enabled = true + threshold = 2 + severity = 1 + } + } + +} + +`, name) +} diff --git a/sysdig/resource_sysdig_secure_drift_policy.go b/sysdig/resource_sysdig_secure_drift_policy.go new file mode 100644 index 00000000..c1d4c679 --- /dev/null +++ b/sysdig/resource_sysdig_secure_drift_policy.go @@ -0,0 +1,229 @@ +package sysdig + +import ( + "context" + "errors" + "net/http" + "strconv" + "time" + + v2 "github.com/draios/terraform-provider-sysdig/sysdig/internal/client/v2" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" +) + +func resourceSysdigSecureDriftPolicy() *schema.Resource { + timeout := 5 * time.Minute + + return &schema.Resource{ + CreateContext: resourceSysdigDriftPolicyCreate, + ReadContext: resourceSysdigDriftPolicyRead, + UpdateContext: resourceSysdigDriftPolicyUpdate, + DeleteContext: resourceSysdigDriftPolicyDelete, + Importer: &schema.ResourceImporter{ + StateContext: resourceSysdigSecureDriftPolicyImportState, + }, + + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(timeout), + Delete: schema.DefaultTimeout(timeout), + Update: schema.DefaultTimeout(timeout), + Read: schema.DefaultTimeout(timeout), + }, + // IMPORTANT: composite.Policy.Rules and composite.Policy.RuleNames are read-only attributes. + // They're not used as a source for rule updates, so it's okay to drop those attributes in TF. + // To update the rules, composite.Rules values are used instead. + // https://github.com/draios/secure-backend/blob/main/policies/api/handler_policies.go#L1120 + Schema: map[string]*schema.Schema{ + // IMPORTANT: Type is implicit: It's automatically added upon conversion to JSON + "type": { + Type: schema.TypeString, + Optional: true, + Default: "drift", + ValidateDiagFunc: validateDiagFunc(validation.StringInSlice([]string{"drift"}, false)), + }, + "name": NameSchema(), + "description": DescriptionSchema(), + "enabled": EnabledSchema(), + "severity": SeveritySchema(), + "scope": ScopeSchema(), + "version": VersionSchema(), + "notification_channels": NotificationChannelsSchema(), + "runbook": RunbookSchema(), + "rule": { + Type: schema.TypeList, + Required: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "id": ReadOnlyIntSchema(), + "name": ReadOnlyStringSchema(), + "description": DescriptionSchema(), + "tags": TagsSchema(), + "version": VersionSchema(), + "enabled": BoolSchema(), // Enable maps to mode rule attribute + "exceptions": ExceptionsSchema(), + "prohibited_binaries": ExceptionsSchema(), + }, + }, + }, + "actions": { + Type: schema.TypeList, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "prevent_drift": PreventActionSchema(), + "container": ContainerActionSchema(), + "capture": CaptureActionSchema(), + }, + }, + }, + }, // Schema end + } +} + +func driftPolicyFromResourceData(d *schema.ResourceData) (v2.PolicyRulesComposite, error) { + policy := &v2.PolicyRulesComposite{ + Policy: &v2.Policy{}, + Rules: []*v2.RuntimePolicyRule{}, + } + err := driftPolicyReducer(policy, d) + if err != nil { + return *policy, err + } + + return *policy, nil +} + +// TODO: Rename +func driftPolicyToResourceData(policy *v2.PolicyRulesComposite, d *schema.ResourceData) error { + return driftTFResourceReducer(d, *policy) +} + +func resourceSysdigDriftPolicyCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + sysdigClients := meta.(SysdigClients) + client, err := getSecureCompositePolicyClient(sysdigClients) + if err != nil { + return diag.FromErr(err) + } + + policy, err := driftPolicyFromResourceData(d) + if err != nil { + return diag.FromErr(err) + } + + policy, err = client.CreateCompositePolicy(ctx, policy) + if err != nil { + return diag.FromErr(err) + } + sysdigClients.AddCleanupHook(sendPoliciesToAgents) + + err = driftPolicyToResourceData(&policy, d) + if err != nil { + return diag.FromErr(err) + } + + return nil +} + +func resourceSysdigDriftPolicyUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + sysdigClients := meta.(SysdigClients) + client, err := getSecureCompositePolicyClient(sysdigClients) + if err != nil { + return diag.FromErr(err) + } + + policy, err := driftPolicyFromResourceData(d) + if err != nil { + return diag.FromErr(err) + } + + _, err = client.UpdateCompositePolicy(ctx, policy) + if err != nil { + return diag.FromErr(err) + } + sysdigClients.AddCleanupHook(sendPoliciesToAgents) + + return nil +} + +func resourceSysdigDriftPolicyRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + client, err := getSecureCompositePolicyClient(meta.(SysdigClients)) + if err != nil { + return diag.FromErr(err) + } + + id, _ := strconv.Atoi(d.Id()) + policy, statusCode, err := client.GetCompositePolicyByID(ctx, id) + if err != nil { + if statusCode == http.StatusNotFound { + d.SetId("") + } else { + return diag.FromErr(err) + } + } + + err = driftPolicyToResourceData(&policy, d) + if err != nil { + return diag.FromErr(err) + } + + return nil +} + +func resourceSysdigDriftPolicyDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + sysdigClients := meta.(SysdigClients) + client, err := getSecureCompositePolicyClient(sysdigClients) + if err != nil { + return diag.FromErr(err) + } + + policy, err := driftPolicyFromResourceData(d) + if err != nil { + return diag.FromErr(err) + } + + if policy.Policy.ID == 0 { + return diag.FromErr(errors.New("Policy ID is missing")) + } + + err = client.DeleteCompositePolicy(ctx, policy.Policy.ID) + if err != nil { + return diag.FromErr(err) + } + sysdigClients.AddCleanupHook(sendPoliciesToAgents) + + return nil +} + +func resourceSysdigSecureDriftPolicyImportState(ctx context.Context, d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { + client, err := getSecureCompositePolicyClient(meta.(SysdigClients)) + if err != nil { + return nil, err + } + + policy, err := driftPolicyFromResourceData(d) + if err != nil { + return nil, err + } + + if policy.Policy.ID == 0 { + return nil, errors.New("Policy ID is missing") + } + + policy, _, err = client.GetCompositePolicyByID(ctx, policy.Policy.ID) + if err != nil { + return nil, err + } + + if policy.Policy.IsDefault || policy.Policy.TemplateId != 0 { + return nil, errors.New("unable to import policy that is not a custom policy") + } + + err = driftPolicyToResourceData(&policy, d) + if err != nil { + return nil, err + } + + return []*schema.ResourceData{d}, nil +} diff --git a/sysdig/resource_sysdig_secure_drift_policy_test.go b/sysdig/resource_sysdig_secure_drift_policy_test.go new file mode 100644 index 00000000..c9f45e96 --- /dev/null +++ b/sysdig/resource_sysdig_secure_drift_policy_test.go @@ -0,0 +1,173 @@ +//go:build tf_acc_sysdig_secure || tf_acc_policies || tf_acc_onprem_secure + +package sysdig_test + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + + "github.com/draios/terraform-provider-sysdig/sysdig" +) + +func TestAccDriftPolicy(t *testing.T) { + rText := func() string { return acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum) } + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: preCheckAnyEnv(t, SysdigSecureApiTokenEnv), + ProviderFactories: map[string]func() (*schema.Provider, error){ + "sysdig": func() (*schema.Provider, error) { + return sysdig.Provider(), nil + }, + }, + Steps: []resource.TestStep{ + { + Config: driftPolicyWithName(rText()), + }, + { + Config: driftPolicyWithAllActions(rText()), + }, + { + Config: driftPolicyWithoutActions(rText()), + }, + { + Config: driftPolicyWithoutNotificationChannel(rText()), + }, + }, + }) +} + +func driftPolicyWithName(name string) string { + return fmt.Sprintf(` +%s + +resource "sysdig_secure_drift_policy" "sample" { + name = "Test Drift Policy %s" + description = "Test Drift Policy Description" + enabled = true + severity = 4 + + rule { + description = "Test Drift Rule Description" + + enabled = true + + exceptions { + items = ["/usr/bin/sh"] + } + prohibited_binaries { + items = ["/usr/bin/curl"] + } + } + + actions { + prevent_drift = true + } + + notification_channels = [sysdig_secure_notification_channel_email.sample_email.id] +} + +`, secureNotificationChannelEmailWithName(name), name) +} + +func driftPolicyWithAllActions(name string) string { + return fmt.Sprintf(` +%s + +resource "sysdig_secure_drift_policy" "sample" { + name = "Test Drift Policy %s" + description = "Test Drift Policy Description" + enabled = true + severity = 4 + + rule { + description = "Test Drift Rule Description" + + enabled = true + + exceptions { + items = ["/usr/bin/sh"] + } + prohibited_binaries { + items = ["/usr/bin/curl"] + } + } + + actions { + prevent_drift = true + container = "stop" + capture { + seconds_before_event = 5 + seconds_after_event = 10 + name = "testcapture" + } + } + + notification_channels = [sysdig_secure_notification_channel_email.sample_email.id] +} + +`, secureNotificationChannelEmailWithName(name), name) +} + +func driftPolicyWithoutActions(name string) string { + return fmt.Sprintf(` +%s + +resource "sysdig_secure_drift_policy" "sample" { + name = "Test Drift Policy %s" + description = "Test Drift Policy Description" + enabled = true + severity = 4 + + rule { + description = "Test Drift Rule Description" + + enabled = true + + exceptions { + items = ["/usr/bin/sh"] + } + prohibited_binaries { + items = ["/usr/bin/curl"] + } + } + + actions {} + + notification_channels = [sysdig_secure_notification_channel_email.sample_email.id] +} + +`, secureNotificationChannelEmailWithName(name), name) +} + +func driftPolicyWithoutNotificationChannel(name string) string { + return fmt.Sprintf(` +resource "sysdig_secure_drift_policy" "sample" { + name = "Test Drift Policy %s" + description = "Test Drift Policy Description" + enabled = true + severity = 4 + + rule { + description = "Test Drift Rule Description" + + enabled = true + + exceptions { + items = ["/usr/bin/sh"] + } + prohibited_binaries { + items = ["/usr/bin/curl"] + } + } + + actions { + prevent_drift = true + } +} + +`, name) +} diff --git a/sysdig/resource_sysdig_secure_malware_policy.go b/sysdig/resource_sysdig_secure_malware_policy.go new file mode 100644 index 00000000..c5b29b4e --- /dev/null +++ b/sysdig/resource_sysdig_secure_malware_policy.go @@ -0,0 +1,235 @@ +package sysdig + +import ( + "context" + "errors" + "net/http" + "strconv" + "time" + + v2 "github.com/draios/terraform-provider-sysdig/sysdig/internal/client/v2" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" +) + +func resourceSysdigSecureMalwarePolicy() *schema.Resource { + timeout := 5 * time.Minute + + return &schema.Resource{ + CreateContext: resourceSysdigMalwarePolicyCreate, + ReadContext: resourceSysdigMalwarePolicyRead, + UpdateContext: resourceSysdigMalwarePolicyUpdate, + DeleteContext: resourceSysdigMalwarePolicyDelete, + Importer: &schema.ResourceImporter{ + StateContext: resourceSysdigSecureMalwarePolicyImportState, + }, + + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(timeout), + Delete: schema.DefaultTimeout(timeout), + Update: schema.DefaultTimeout(timeout), + Read: schema.DefaultTimeout(timeout), + }, + // IMPORTANT: composite.Policy.Rules and composite.Policy.RuleNames are read-only attributes. + // They're not used as a source for rule updates, so it's okay to drop those attributes in TF. + // To update the rules, composite.Rules values are used instead. + // https://github.com/draios/secure-backend/blob/main/policies/api/handler_policies.go#L1120 + Schema: map[string]*schema.Schema{ + // IMPORTANT: Type is implicit: It's automatically added upon conversion to JSON + "type": { + Type: schema.TypeString, + Optional: true, + Default: "malware", + ValidateDiagFunc: validateDiagFunc(validation.StringInSlice([]string{"malware"}, false)), + }, + "name": NameSchema(), + "description": DescriptionSchema(), + "enabled": EnabledSchema(), + "severity": SeveritySchema(), + "scope": ScopeSchema(), + "version": VersionSchema(), + "notification_channels": NotificationChannelsSchema(), + "runbook": RunbookSchema(), + "rule": { + Type: schema.TypeList, + Required: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "id": ReadOnlyIntSchema(), + "name": ReadOnlyStringSchema(), + // Do not allow switching off individual rules + // "enabled": EnabledSchema(), + "description": DescriptionSchema(), + "tags": TagsSchema(), + "version": VersionSchema(), + "use_managed_hashes": BoolSchema(), + "additional_hashes": HashesSchema(), + "ignore_hashes": HashesSchema(), + }, + }, + }, + "actions": { + Type: schema.TypeList, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "prevent_malware": PreventActionSchema(), + "container": ContainerActionSchema(), + "capture": CaptureActionSchema(), + }, + }, + }, + }, // Schema end + } +} + +func getSecureCompositePolicyClient(c SysdigClients) (v2.CompositePolicyInterface, error) { + return c.sysdigSecureClientV2() +} + +func malwarePolicyFromResourceData(d *schema.ResourceData) (v2.PolicyRulesComposite, error) { + policy := &v2.PolicyRulesComposite{ + Policy: &v2.Policy{}, + Rules: []*v2.RuntimePolicyRule{}, + } + err := malwarePolicyReducer(policy, d) + if err != nil { + return *policy, err + } + + return *policy, nil +} + +// TODO: Rename +func malwarePolicyToResourceData(policy *v2.PolicyRulesComposite, d *schema.ResourceData) error { + return malwareTFResourceReducer(d, *policy) +} + +func resourceSysdigMalwarePolicyCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + sysdigClients := meta.(SysdigClients) + client, err := getSecureCompositePolicyClient(sysdigClients) + if err != nil { + return diag.FromErr(err) + } + + policy, err := malwarePolicyFromResourceData(d) + if err != nil { + return diag.FromErr(err) + } + + policy, err = client.CreateCompositePolicy(ctx, policy) + if err != nil { + return diag.FromErr(err) + } + sysdigClients.AddCleanupHook(sendPoliciesToAgents) + + err = malwarePolicyToResourceData(&policy, d) + if err != nil { + return diag.FromErr(err) + } + + return nil +} + +func resourceSysdigMalwarePolicyUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + sysdigClients := meta.(SysdigClients) + client, err := getSecureCompositePolicyClient(sysdigClients) + if err != nil { + return diag.FromErr(err) + } + + policy, err := malwarePolicyFromResourceData(d) + if err != nil { + return diag.FromErr(err) + } + + _, err = client.UpdateCompositePolicy(ctx, policy) + if err != nil { + return diag.FromErr(err) + } + sysdigClients.AddCleanupHook(sendPoliciesToAgents) + + return nil +} + +func resourceSysdigMalwarePolicyRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + client, err := getSecureCompositePolicyClient(meta.(SysdigClients)) + if err != nil { + return diag.FromErr(err) + } + + id, _ := strconv.Atoi(d.Id()) + policy, statusCode, err := client.GetCompositePolicyByID(ctx, id) + if err != nil { + if statusCode == http.StatusNotFound { + d.SetId("") + } else { + return diag.FromErr(err) + } + } + + err = malwarePolicyToResourceData(&policy, d) + if err != nil { + return diag.FromErr(err) + } + + return nil +} + +func resourceSysdigMalwarePolicyDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + sysdigClients := meta.(SysdigClients) + client, err := getSecureCompositePolicyClient(sysdigClients) + if err != nil { + return diag.FromErr(err) + } + + policy, err := malwarePolicyFromResourceData(d) + if err != nil { + return diag.FromErr(err) + } + + if policy.Policy.ID == 0 { + return diag.FromErr(errors.New("Policy ID is missing")) + } + + err = client.DeleteCompositePolicy(ctx, policy.Policy.ID) + if err != nil { + return diag.FromErr(err) + } + sysdigClients.AddCleanupHook(sendPoliciesToAgents) + + return nil +} + +func resourceSysdigSecureMalwarePolicyImportState(ctx context.Context, d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { + client, err := getSecureCompositePolicyClient(meta.(SysdigClients)) + if err != nil { + return nil, err + } + + policy, err := malwarePolicyFromResourceData(d) + if err != nil { + return nil, err + } + + if policy.Policy.ID == 0 { + return nil, errors.New("Policy ID is missing") + } + + policy, _, err = client.GetCompositePolicyByID(ctx, policy.Policy.ID) + if err != nil { + return nil, err + } + + if policy.Policy.IsDefault || policy.Policy.TemplateId != 0 { + return nil, errors.New("unable to import policy that is not a custom policy") + } + + err = malwarePolicyToResourceData(&policy, d) + if err != nil { + return nil, err + } + + return []*schema.ResourceData{d}, nil +} diff --git a/sysdig/resource_sysdig_secure_malware_policy_test.go b/sysdig/resource_sysdig_secure_malware_policy_test.go new file mode 100644 index 00000000..5421fed9 --- /dev/null +++ b/sysdig/resource_sysdig_secure_malware_policy_test.go @@ -0,0 +1,242 @@ +//go:build tf_acc_sysdig_secure || tf_acc_policies || tf_acc_onprem_secure + +package sysdig_test + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + + "github.com/draios/terraform-provider-sysdig/sysdig" +) + +func TestAccMalwarePolicy(t *testing.T) { + rText := func() string { return acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum) } + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: preCheckAnyEnv(t, SysdigSecureApiTokenEnv), + ProviderFactories: map[string]func() (*schema.Provider, error){ + "sysdig": func() (*schema.Provider, error) { + return sysdig.Provider(), nil + }, + }, + Steps: []resource.TestStep{ + { + Config: malwarePolicyWithName(rText()), + }, + { + Config: malwarePolicyWithoutAnyHashes(rText()), + }, + { + Config: malwarePolicyWithoutAdditionalHashes(rText()), + }, + { + Config: malwarePolicyWithoutIgnoreHashes(rText()), + }, + { + Config: malwarePolicyWithAllActions(rText()), + }, + { + Config: malwarePolicyWithoutNotificationChannel(rText()), + }, + }, + }) +} + +func malwarePolicyWithName(name string) string { + return fmt.Sprintf(` +%s + +resource "sysdig_secure_malware_policy" "sample" { + name = "Test Malware Policy %s" + description = "Test Malware Policy Description" + enabled = true + severity = 4 + + rule { + description = "Test Malware Rule Description" + + use_managed_hashes = true + + additional_hashes { + hash = "304ef4cdda3463b24bf53f9cdd69ad3ecdab0842e7e70e2f3cfbb9f14e1c4ae6" + } + + ignore_hashes { + hash = "6ac3c336e4094835293a3fed8a4b5fedde1b5e2626d9838fed50693bba00af0e" + } + } + + actions { + prevent_malware = true + container = "stop" + } + + notification_channels = [sysdig_secure_notification_channel_email.sample_email.id] +} + +`, secureNotificationChannelEmailWithName(name), name) +} + +func malwarePolicyWithoutAdditionalHashes(name string) string { + return fmt.Sprintf(` +%s + +resource "sysdig_secure_malware_policy" "sample" { + name = "Test Malware Policy %s" + description = "Test Malware Policy Description" + enabled = true + severity = 4 + + rule { + description = "Test Malware Rule Description" + + use_managed_hashes = true + + ignore_hashes { + hash = "6ac3c336e4094835293a3fed8a4b5fedde1b5e2626d9838fed50693bba00af0e" + } + } + + actions { + prevent_malware = true + container = "stop" + } + + notification_channels = [sysdig_secure_notification_channel_email.sample_email.id] +} + +`, secureNotificationChannelEmailWithName(name), name) +} + +func malwarePolicyWithoutIgnoreHashes(name string) string { + return fmt.Sprintf(` +%s + +resource "sysdig_secure_malware_policy" "sample" { + name = "Test Malware Policy %s" + description = "Test Malware Policy Description" + enabled = true + severity = 4 + + rule { + description = "Test Malware Rule Description" + + use_managed_hashes = true + + additional_hashes { + hash = "304ef4cdda3463b24bf53f9cdd69ad3ecdab0842e7e70e2f3cfbb9f14e1c4ae6" + } + } + + actions { + prevent_malware = true + container = "stop" + } + + notification_channels = [sysdig_secure_notification_channel_email.sample_email.id] +} + +`, secureNotificationChannelEmailWithName(name), name) +} + +func malwarePolicyWithoutAnyHashes(name string) string { + return fmt.Sprintf(` +%s + +resource "sysdig_secure_malware_policy" "sample" { + name = "Test Malware Policy %s" + description = "Test Malware Policy Description" + enabled = true + severity = 4 + + rule { + description = "Test Malware Rule Description" + use_managed_hashes = true + } + + actions { + prevent_malware = true + } + + notification_channels = [sysdig_secure_notification_channel_email.sample_email.id] +} + +`, secureNotificationChannelEmailWithName(name), name) +} + +func malwarePolicyWithAllActions(name string) string { + return fmt.Sprintf(` +%s + +resource "sysdig_secure_malware_policy" "sample" { + name = "Test Malware Policy %s" + description = "Test Malware Policy Description" + enabled = true + severity = 4 + + rule { + description = "Test Malware Rule Description" + + use_managed_hashes = true + + additional_hashes { + hash = "304ef4cdda3463b24bf53f9cdd69ad3ecdab0842e7e70e2f3cfbb9f14e1c4ae6" + } + + ignore_hashes { + hash = "6ac3c336e4094835293a3fed8a4b5fedde1b5e2626d9838fed50693bba00af0e" + } + } + + actions { + prevent_malware = true + container = "stop" + capture { + seconds_before_event = 5 + seconds_after_event = 10 + name = "testcapture" + } + + } + + notification_channels = [sysdig_secure_notification_channel_email.sample_email.id] +} + +`, secureNotificationChannelEmailWithName(name), name) +} + +func malwarePolicyWithoutNotificationChannel(name string) string { + return fmt.Sprintf(` +resource "sysdig_secure_malware_policy" "sample" { + name = "Test Malware Policy %s" + description = "Test Malware Policy Description" + enabled = true + severity = 4 + + rule { + description = "Test Malware Rule Description" + + use_managed_hashes = true + + additional_hashes { + hash = "304ef4cdda3463b24bf53f9cdd69ad3ecdab0842e7e70e2f3cfbb9f14e1c4ae6" + } + + ignore_hashes { + hash = "6ac3c336e4094835293a3fed8a4b5fedde1b5e2626d9838fed50693bba00af0e" + } + } + + actions { + prevent_malware = true + container = "stop" + } + +} + +`, name) +} diff --git a/sysdig/resource_sysdig_secure_ml_policy.go b/sysdig/resource_sysdig_secure_ml_policy.go new file mode 100644 index 00000000..c4008db3 --- /dev/null +++ b/sysdig/resource_sysdig_secure_ml_policy.go @@ -0,0 +1,218 @@ +package sysdig + +import ( + "context" + "errors" + "net/http" + "strconv" + "time" + + v2 "github.com/draios/terraform-provider-sysdig/sysdig/internal/client/v2" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" +) + +func resourceSysdigSecureMLPolicy() *schema.Resource { + timeout := 5 * time.Minute + + return &schema.Resource{ + CreateContext: resourceSysdigMLPolicyCreate, + ReadContext: resourceSysdigMLPolicyRead, + UpdateContext: resourceSysdigMLPolicyUpdate, + DeleteContext: resourceSysdigMLPolicyDelete, + Importer: &schema.ResourceImporter{ + StateContext: resourceSysdigSecureMLPolicyImportState, + }, + + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(timeout), + Delete: schema.DefaultTimeout(timeout), + Update: schema.DefaultTimeout(timeout), + Read: schema.DefaultTimeout(timeout), + }, + // IMPORTANT: composite.Policy.Rules and composite.Policy.RuleNames are read-only attributes. + // They're not used as a source for rule updates, so it's okay to drop those attributes in TF. + // To update the rules, composite.Rules values are used instead. + // https://github.com/draios/secure-backend/blob/main/policies/api/handler_policies.go#L1120 + Schema: map[string]*schema.Schema{ + // IMPORTANT: Type is implicit: It's automatically added upon conversion to JSON + "type": { + Type: schema.TypeString, + Optional: true, + Default: policyTypeML, + ValidateDiagFunc: validateDiagFunc(validation.StringInSlice([]string{policyTypeML}, false)), + }, + "name": NameSchema(), + "description": DescriptionSchema(), + "enabled": EnabledSchema(), + "severity": SeveritySchema(), + "scope": ScopeSchema(), + "version": VersionSchema(), + "notification_channels": NotificationChannelsSchema(), + "runbook": RunbookSchema(), + "rule": { + Type: schema.TypeList, + Required: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "id": ReadOnlyIntSchema(), + "name": ReadOnlyStringSchema(), + // Do not allow switching off individual rules + // "enabled": EnabledSchema(), + "description": DescriptionSchema(), + "tags": TagsSchema(), + "version": VersionSchema(), + "cryptomining_trigger": MLRuleThresholdAndSeveritySchema(), + }, + }, + }, + }, // Schema end + } +} + +func mlPolicyFromResourceData(d *schema.ResourceData) (v2.PolicyRulesComposite, error) { + policy := &v2.PolicyRulesComposite{ + Policy: &v2.Policy{}, + Rules: []*v2.RuntimePolicyRule{}, + } + err := mlPolicyReducer(policy, d) + if err != nil { + return *policy, err + } + + return *policy, nil +} + +func mlPolicyToResourceData(policy *v2.PolicyRulesComposite, d *schema.ResourceData) error { + return mlTFResourceReducer(d, *policy) +} + +func resourceSysdigMLPolicyCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + sysdigClients := meta.(SysdigClients) + client, err := getSecureCompositePolicyClient(sysdigClients) + if err != nil { + return diag.FromErr(err) + } + + policy, err := mlPolicyFromResourceData(d) + if err != nil { + return diag.FromErr(err) + } + + policy, err = client.CreateCompositePolicy(ctx, policy) + if err != nil { + return diag.FromErr(err) + } + + err = mlPolicyToResourceData(&policy, d) + if err != nil { + return diag.FromErr(err) + } + + sysdigClients.AddCleanupHook(sendPoliciesToAgents) + + return nil +} + +func resourceSysdigMLPolicyUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + sysdigClients := meta.(SysdigClients) + client, err := getSecureCompositePolicyClient(sysdigClients) + if err != nil { + return diag.FromErr(err) + } + + policy, err := mlPolicyFromResourceData(d) + if err != nil { + return diag.FromErr(err) + } + + _, err = client.UpdateCompositePolicy(ctx, policy) + if err != nil { + return diag.FromErr(err) + } + sysdigClients.AddCleanupHook(sendPoliciesToAgents) + + return nil +} + +func resourceSysdigMLPolicyRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + client, err := getSecureCompositePolicyClient(meta.(SysdigClients)) + if err != nil { + return diag.FromErr(err) + } + + id, _ := strconv.Atoi(d.Id()) + policy, statusCode, err := client.GetCompositePolicyByID(ctx, id) + if err != nil { + if statusCode == http.StatusNotFound { + d.SetId("") + } else { + return diag.FromErr(err) + } + } + + err = mlPolicyToResourceData(&policy, d) + if err != nil { + return diag.FromErr(err) + } + + return nil +} + +func resourceSysdigMLPolicyDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + sysdigClients := meta.(SysdigClients) + client, err := getSecureCompositePolicyClient(sysdigClients) + if err != nil { + return diag.FromErr(err) + } + + policy, err := mlPolicyFromResourceData(d) + if err != nil { + return diag.FromErr(err) + } + + if policy.Policy.ID == 0 { + return diag.FromErr(errors.New("Policy ID is missing")) + } + + err = client.DeleteCompositePolicy(ctx, policy.Policy.ID) + if err != nil { + return diag.FromErr(err) + } + sysdigClients.AddCleanupHook(sendPoliciesToAgents) + + return nil +} + +func resourceSysdigSecureMLPolicyImportState(ctx context.Context, d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { + client, err := getSecureCompositePolicyClient(meta.(SysdigClients)) + if err != nil { + return nil, err + } + + policy, err := mlPolicyFromResourceData(d) + if err != nil { + return nil, err + } + + if policy.Policy.ID == 0 { + return nil, errors.New("Policy ID is missing") + } + + policy, _, err = client.GetCompositePolicyByID(ctx, policy.Policy.ID) + if err != nil { + return nil, err + } + + if policy.Policy.IsDefault || policy.Policy.TemplateId != 0 { + return nil, errors.New("unable to import policy that is not a custom policy") + } + + err = mlPolicyToResourceData(&policy, d) + if err != nil { + return nil, err + } + + return []*schema.ResourceData{d}, nil +} diff --git a/sysdig/resource_sysdig_secure_ml_policy_test.go b/sysdig/resource_sysdig_secure_ml_policy_test.go new file mode 100644 index 00000000..e2e680df --- /dev/null +++ b/sysdig/resource_sysdig_secure_ml_policy_test.go @@ -0,0 +1,84 @@ +//go:build tf_acc_sysdig_secure || tf_acc_policies || tf_acc_onprem_secure + +package sysdig_test + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + + "github.com/draios/terraform-provider-sysdig/sysdig" +) + +func TestAccMLPolicy(t *testing.T) { + rText := func() string { return acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum) } + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: preCheckAnyEnv(t, SysdigSecureApiTokenEnv), + ProviderFactories: map[string]func() (*schema.Provider, error){ + "sysdig": func() (*schema.Provider, error) { + return sysdig.Provider(), nil + }, + }, + Steps: []resource.TestStep{ + { + Config: mlPolicyWithName(rText()), + }, + { + Config: mlPolicyWithoutNotificationChannel(rText()), + }, + }, + }) +} + +func mlPolicyWithName(name string) string { + return fmt.Sprintf(` +%s + +resource "sysdig_secure_ml_policy" "sample" { + name = "Test ML Policy %s" + description = "Test ML Policy Description" + enabled = true + severity = 4 + + rule { + description = "Test ML Rule Description" + + cryptomining_trigger { + enabled = true + threshold = 2 + severity = 1 + } + } + + notification_channels = [sysdig_secure_notification_channel_email.sample_email.id] +} + +`, secureNotificationChannelEmailWithName(name), name) +} + +func mlPolicyWithoutNotificationChannel(name string) string { + return fmt.Sprintf(` +resource "sysdig_secure_ml_policy" "sample" { + name = "Test ML Policy %s" + description = "Test ML Policy Description" + enabled = true + severity = 4 + + rule { + description = "Test ML Rule Description" + + cryptomining_trigger { + enabled = true + threshold = 2 + severity = 1 + } + } + +} + +`, name) +} diff --git a/sysdig/resource_sysdig_secure_policy.go b/sysdig/resource_sysdig_secure_policy.go index 1bf50eea..7e876f95 100644 --- a/sysdig/resource_sysdig_secure_policy.go +++ b/sysdig/resource_sysdig_secure_policy.go @@ -18,100 +18,21 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" ) -var validatePolicyType = validation.StringInSlice([]string{"falco", "list_matching", "k8s_audit", "aws_cloudtrail", "gcp_auditlog", "azure_platformlogs", "awscloudtrail", "okta", "github"}, false) - -// Creates the common policy schema that is shared between policy resources -func createPolicySchema(original map[string]*schema.Schema) map[string]*schema.Schema { - policySchema := map[string]*schema.Schema{ - "name": { - Type: schema.TypeString, - Required: true, - }, - "enabled": { - Type: schema.TypeBool, - Optional: true, - Default: true, - }, - "scope": { - Type: schema.TypeString, - Optional: true, - Default: "", - }, - "version": { - Type: schema.TypeInt, - Computed: true, - }, - "notification_channels": { - Type: schema.TypeSet, - Optional: true, - Elem: &schema.Schema{ - Type: schema.TypeInt, - }, - }, - "runbook": { - Type: schema.TypeString, - Optional: true, - }, - "actions": policyActionBlockSchema, - } - - for k, v := range original { - policySchema[k] = v - } - - return policySchema -} - -var policyActionBlockSchema = &schema.Schema{ - Type: schema.TypeList, - Optional: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "container": { - Type: schema.TypeString, - Optional: true, - ValidateFunc: validation.StringInSlice([]string{"stop", "pause", "kill"}, false), - }, - "capture": { - Type: schema.TypeList, - Optional: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "seconds_after_event": { - Type: schema.TypeInt, - Required: true, - ValidateDiagFunc: validateDiagFunc(validation.IntAtLeast(0)), - }, - "seconds_before_event": { - Type: schema.TypeInt, - Required: true, - ValidateDiagFunc: validateDiagFunc(validation.IntAtLeast(0)), - }, - "name": { - Type: schema.TypeString, - Required: true, - }, - "filter": { - Type: schema.TypeString, - Optional: true, - Default: "", - }, - "bucket_name": { - Type: schema.TypeString, - Optional: true, - Default: "", - }, - "folder": { - Type: schema.TypeString, - Optional: true, - Default: "/", - }, - }, - }, - }, - }, - }, -} +var validatePolicyType = validation.StringInSlice([]string{ + "falco", + "list_matching", + "k8s_audit", + "aws_cloudtrail", + "awscloudtrail", + "gcp_auditlog", + "azure_platformlogs", + "okta", + "github", + "malware", + "drift", + "aws_machine_learning", + "machine_learning", +}, false) func resourceSysdigSecurePolicy() *schema.Resource { timeout := 5 * time.Minute @@ -283,6 +204,16 @@ func addActionsToPolicy(d *schema.ResourceData, policy *v2.Policy) { return } + preventDriftAction, ok := d.GetOk("actions.0.prevent_drift") + if ok && preventDriftAction.(bool) { + policy.Actions = append(policy.Actions, v2.Action{Type: "POLICY_ACTION_PREVENT_DRIFT"}) + } + + preventMalwareAction, ok := d.GetOk("actions.0.prevent_malware") + if ok && preventMalwareAction.(bool) { + policy.Actions = append(policy.Actions, v2.Action{Type: "POLICY_ACTION_PREVENT_MALWARE"}) + } + containerAction := d.Get("actions.0.container").(string) if containerAction != "" { containerAction = strings.ToUpper("POLICY_ACTION_" + containerAction) diff --git a/sysdig/schema.go b/sysdig/schema.go new file mode 100644 index 00000000..fece683b --- /dev/null +++ b/sysdig/schema.go @@ -0,0 +1,471 @@ +package sysdig + +import ( + // "github.com/hashicorp/terraform-plugin-log/tflog" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" +) + +func ReadOnlyIntSchema() *schema.Schema { + return &schema.Schema{ + Type: schema.TypeInt, + Required: false, // Read-only value + Optional: false, // Read-only value + Computed: true, + } +} + +func ReadOnlyStringSchema() *schema.Schema { + return &schema.Schema{ + Type: schema.TypeString, + Required: false, // Read-only value + Optional: false, // Read-only value + Computed: true, + } +} + +func BoolSchema() *schema.Schema { + return &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Default: false, + } +} + +func BoolComputedSchema() *schema.Schema { + return &schema.Schema{ + Type: schema.TypeBool, + Computed: true, + } +} + +// Can be omitted for composite policies +func RuleNamesSchema() *schema.Schema { + return &schema.Schema{ + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + } +} + +// Can be omitted for Composite policies +func RulesSchema() *schema.Schema { + return &schema.Schema{ + Type: schema.TypeList, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + }, + "enabled": { + Type: schema.TypeBool, + Optional: true, + Default: true, + }, + }, + }, + } +} + +func ScopeSchema() *schema.Schema { + return &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Default: "", + } +} + +func ScopeComputedSchema() *schema.Schema { + return &schema.Schema{ + Type: schema.TypeString, + Computed: true, + } +} + +func RunbookSchema() *schema.Schema { + return &schema.Schema{ + Type: schema.TypeString, + Optional: true, + } +} + +func RunbookComputedSchema() *schema.Schema { + return &schema.Schema{ + Type: schema.TypeString, + Computed: true, + } +} + +func VersionSchema() *schema.Schema { + return &schema.Schema{ + Type: schema.TypeInt, + Computed: true, + } +} + +func NameSchema() *schema.Schema { + return &schema.Schema{ + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringIsNotEmpty, + } +} + +func DescriptionSchema() *schema.Schema { + return &schema.Schema{ + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringIsNotEmpty, + } +} + +func DescriptionComputedSchema() *schema.Schema { + return &schema.Schema{ + Type: schema.TypeString, + Computed: true, + } +} + +func NotificationChannelsSchema() *schema.Schema { + return &schema.Schema{ + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Schema{ + Type: schema.TypeInt, + }, + } +} + +func NotificationChannelsComputedSchema() *schema.Schema { + return &schema.Schema{ + Type: schema.TypeSet, + Computed: true, + Elem: &schema.Schema{ + Type: schema.TypeInt, + }, + } +} + +func EnabledSchema() *schema.Schema { + return &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Default: true, + } +} + +func EnabledComputedSchema() *schema.Schema { + return &schema.Schema{ + Type: schema.TypeBool, + Computed: true, + } +} + +func SeveritySchema() *schema.Schema { + return &schema.Schema{ + Type: schema.TypeInt, + Default: 4, + Optional: true, + ValidateDiagFunc: validateDiagFunc(validation.IntBetween(0, 7)), + } +} + +func SeverityComputedSchema() *schema.Schema { + return &schema.Schema{ + Type: schema.TypeInt, + Computed: true, + } +} + +func PreventActionSchema() *schema.Schema { + return &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Default: false, + } +} + +func PreventActionComputedSchema() *schema.Schema { + return &schema.Schema{ + Type: schema.TypeBool, + Computed: true, + } +} + +func ContainerActionSchema() *schema.Schema { + return &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringInSlice([]string{"stop", "pause", "kill"}, false), + } +} + +func ContainerActionComputedSchema() *schema.Schema { + return &schema.Schema{ + Type: schema.TypeString, + Computed: true, + } +} + +func CaptureActionSchema() *schema.Schema { + return &schema.Schema{ + Type: schema.TypeList, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "seconds_after_event": { + Type: schema.TypeInt, + Required: true, + ValidateDiagFunc: validateDiagFunc(validation.IntAtLeast(0)), + }, + "seconds_before_event": { + Type: schema.TypeInt, + Required: true, + ValidateDiagFunc: validateDiagFunc(validation.IntAtLeast(0)), + }, + "name": { + Type: schema.TypeString, + Required: true, + }, + "filter": { + Type: schema.TypeString, + Optional: true, + Default: "", + }, + "bucket_name": { + Type: schema.TypeString, + Optional: true, + Default: "", + }, + "folder": { + Type: schema.TypeString, + Optional: true, + Default: "/", + }, + }, + }, + } +} + +func CaptureActionComputedSchema() *schema.Schema { + return &schema.Schema{ + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "seconds_after_event": { + Type: schema.TypeInt, + Computed: true, + }, + "seconds_before_event": { + Type: schema.TypeInt, + Computed: true, + }, + "name": { + Type: schema.TypeString, + Computed: true, + }, + "filter": { + Type: schema.TypeString, + Computed: true, + }, + "bucket_name": { + Type: schema.TypeString, + Computed: true, + }, + "folder": { + Type: schema.TypeString, + Computed: true, + }, + }, + }, + } +} + +func HashesSchema() *schema.Schema { + return &schema.Schema{ + Type: schema.TypeList, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "hash": { + Type: schema.TypeString, + Required: true, + }, + }, + }, + } +} + +func HashesComputedSchema() *schema.Schema { + return &schema.Schema{ + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "hash": { + Type: schema.TypeString, + Computed: true, + }, + }, + }, + } +} + +// Tags are always set automatically by Sysdig +func TagsSchema() *schema.Schema { + return &schema.Schema{ + Type: schema.TypeSet, + Computed: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + } +} + +func ExceptionsSchema() *schema.Schema { + return &schema.Schema{ + Type: schema.TypeList, + MaxItems: 1, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "items": { + Type: schema.TypeSet, + Required: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "match_items": { + Type: schema.TypeBool, + // The match_items attribute is controlled by the mode attribute value. + // Follow the UI - it doesn't update match_items attribute. + Computed: true, + }, + }, + }, + } +} + +func ExceptionsComputedSchema() *schema.Schema { + return &schema.Schema{ + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "items": { + Type: schema.TypeSet, + Computed: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "match_items": { + Type: schema.TypeBool, + Computed: true, + }, + }, + }, + } +} + +func MLRuleThresholdAndSeveritySchema() *schema.Schema { + return &schema.Schema{ + Type: schema.TypeList, + MaxItems: 1, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "enabled": { + Type: schema.TypeBool, + Optional: true, + Default: true, + }, + "threshold": { + Type: schema.TypeInt, + Required: true, + }, + "severity": { + Type: schema.TypeInt, + Optional: true, + }, + }, + }, + } +} + +func MLRuleThresholdAndSeverityComputedSchema() *schema.Schema { + return &schema.Schema{ + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "enabled": { + Type: schema.TypeBool, + Computed: true, + }, + "threshold": { + Type: schema.TypeInt, + Computed: true, + }, + "severity": { + Type: schema.TypeInt, + Computed: true, + }, + }, + }, + } +} + +// Creates the common policy schema that is shared between policy resources +func createPolicySchema(original map[string]*schema.Schema) map[string]*schema.Schema { + policySchema := map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + }, + "enabled": { + Type: schema.TypeBool, + Optional: true, + Default: true, + }, + "scope": { + Type: schema.TypeString, + Optional: true, + Default: "", + }, + "version": { + Type: schema.TypeInt, + Computed: true, + }, + "notification_channels": { + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Schema{ + Type: schema.TypeInt, + }, + }, + "runbook": { + Type: schema.TypeString, + Optional: true, + }, + "actions": { + Type: schema.TypeList, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "container": ContainerActionSchema(), + "capture": CaptureActionSchema(), + }, + }, + }, + } + + for k, v := range original { + policySchema[k] = v + } + + return policySchema +} diff --git a/sysdig/tfresource.go b/sysdig/tfresource.go new file mode 100644 index 00000000..77b122b8 --- /dev/null +++ b/sysdig/tfresource.go @@ -0,0 +1,594 @@ +package sysdig + +import ( + "errors" + "strconv" + "strings" + + v2 "github.com/draios/terraform-provider-sysdig/sysdig/internal/client/v2" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +const ( + policyTypeMalware = "malware" + policyTypeDrift = "drift" + policyTypeML = "machine_learning" + policyTypeAWSML = "aws_machine_learning" + + preventMalwareKey = "prevent_malware" + preventDriftKey = "prevent_drift" + + defaultMalwareTag = "malware" + defaultDriftTag = "drift" + defaultMLTag = "machine_learning" +) + +type Target interface { + *schema.ResourceData | *v2.PolicyRulesComposite +} + +type Source interface { + // copylocks: Do not pass lock by value: + // schema.ResourceData contains sync.Once contains sync.Mutex (govet) + *schema.ResourceData | v2.PolicyRulesComposite +} + +func Reducer[T Target, S Source](reducers ...func(T, S) error) func(T, S) error { + return func(target T, source S) error { + return Reduce(target, source, reducers...) + } +} + +func Reduce[T Target, S Source](target T, source S, reducers ...func(T, S) error) error { + for _, f := range reducers { + if f != nil { + err := f(target, source) + if err != nil { + return err + } + } + } + return nil +} + +func schemaSetToList(values interface{}) []string { + v := values.(*schema.Set).List() + + x := make([]string, len(v)) + for i := range v { + x[i] = v[i].(string) + } + return x +} + +func toIntPtr(value interface{}) *int { + ptr := new(int) + v, ok := value.(int) + if ok { + *ptr = v + } + return ptr +} + +func setTFResourceBaseAttrs(d *schema.ResourceData, policy v2.PolicyRulesComposite) error { + d.SetId(strconv.Itoa(policy.Policy.ID)) + + _ = d.Set("version", policy.Policy.Version) + + _ = d.Set("name", policy.Policy.Name) + _ = d.Set("description", policy.Policy.Description) + _ = d.Set("severity", policy.Policy.Severity) + _ = d.Set("enabled", policy.Policy.Enabled) + _ = d.Set("scope", policy.Policy.Scope) + _ = d.Set("runbook", policy.Policy.Runbook) + + _ = d.Set("notification_channels", policy.Policy.NotificationChannelIds) + + return nil +} + +func setTFResourcePolicyType(policyType string) func(d *schema.ResourceData, policy v2.PolicyRulesComposite) error { + return func(d *schema.ResourceData, policy v2.PolicyRulesComposite) error { + if policy.Policy.Type != "" { + _ = d.Set("type", policy.Policy.Type) + } else { + _ = d.Set("type", policyType) + } + + return nil + } +} + +func setTFResourcePolicyRulesMalware(d *schema.ResourceData, policy v2.PolicyRulesComposite) error { + if len(policy.Rules) == 0 { + return errors.New("The policy must have at least one rule attached to it") + } + + rules := []map[string]interface{}{} + for _, rule := range policy.Rules { + additionalHashes := []map[string]interface{}{} + for k, _ := range rule.Details.(*v2.MalwareRuleDetails).AdditionalHashes { + additionalHashes = append(additionalHashes, map[string]interface{}{ + "hash": k, + }) + } + + ignoreHashes := []map[string]interface{}{} + for k, _ := range rule.Details.(*v2.MalwareRuleDetails).IgnoreHashes { + ignoreHashes = append(ignoreHashes, map[string]interface{}{ + "hash": k, + }) + } + + rules = append(rules, map[string]interface{}{ + "id": rule.Id, + "name": rule.Name, + "description": rule.Description, + "version": rule.Version, + "tags": rule.Tags, + "use_managed_hashes": rule.Details.(*v2.MalwareRuleDetails).UseManagedHashes, + "additional_hashes": additionalHashes, + "ignore_hashes": ignoreHashes, + }) + } + + _ = d.Set("rule", rules) + + return nil +} + +func setTFResourcePolicyRulesDrift(d *schema.ResourceData, policy v2.PolicyRulesComposite) error { + if len(policy.Rules) == 0 { + return errors.New("The policy must have at least one rule attached to it") + } + + rules := []map[string]interface{}{} + for _, rule := range policy.Rules { + // Only a single block of exceptions and prohibited binaries is allowed + exceptions := []map[string]interface{}{{ + "items": rule.Details.(*v2.DriftRuleDetails).Exceptions.Items, + "match_items": rule.Details.(*v2.DriftRuleDetails).Exceptions.MatchItems, + }} + + prohibitedBinaries := []map[string]interface{}{{ + "items": rule.Details.(*v2.DriftRuleDetails).ProhibitedBinaries.Items, + "match_items": rule.Details.(*v2.DriftRuleDetails).ProhibitedBinaries.MatchItems, + }} + + mode := rule.Details.(*v2.DriftRuleDetails).Mode + enabled := true + if mode == "disabled" { + enabled = false + } + + rules = append(rules, map[string]interface{}{ + "id": rule.Id, + "name": rule.Name, + "description": rule.Description, + "version": rule.Version, + "tags": rule.Tags, + "enabled": enabled, + "exceptions": exceptions, + "prohibited_binaries": prohibitedBinaries, + }) + } + + _ = d.Set("rule", rules) + + return nil +} + +func setTFResourcePolicyRulesML(d *schema.ResourceData, policy v2.PolicyRulesComposite) error { + if len(policy.Rules) == 0 { + return errors.New("The policy must have at least one rule attached to it") + } + + rules := []map[string]interface{}{} + for _, rule := range policy.Rules { + // Only a single block of anomaly detection trigger and cryptomining trigger is allowed + // anomalyDetectionTrigger := []map[string]interface{}{{ + // "enabled": rule.Details.(*v2.MLRuleDetails).AnomalyDetectionTrigger.Enabled, + // "threshold": rule.Details.(*v2.MLRuleDetails).AnomalyDetectionTrigger.Threshold, + // "severity": rule.Details.(*v2.MLRuleDetails).AnomalyDetectionTrigger.Severity, + // }} + + cryptominingTrigger := []map[string]interface{}{{ + "enabled": rule.Details.(*v2.MLRuleDetails).CryptominingTrigger.Enabled, + "threshold": rule.Details.(*v2.MLRuleDetails).CryptominingTrigger.Threshold, + "severity": rule.Details.(*v2.MLRuleDetails).CryptominingTrigger.Severity, + }} + + rules = append(rules, map[string]interface{}{ + "id": rule.Id, + "name": rule.Name, + "description": rule.Description, + "version": rule.Version, + "tags": rule.Tags, + "cryptomining_trigger": cryptominingTrigger, + }) + } + + _ = d.Set("rule", rules) + + return nil +} + +func setTFResourcePolicyRulesAWSML(d *schema.ResourceData, policy v2.PolicyRulesComposite) error { + if len(policy.Rules) == 0 { + return errors.New("The policy must have at least one rule attached to it") + } + + rules := []map[string]interface{}{} + for _, rule := range policy.Rules { + anomalousConsoleLogin := []map[string]interface{}{{ + "enabled": rule.Details.(*v2.AWSMLRuleDetails).AnomalousConsoleLogin.Enabled, + "threshold": rule.Details.(*v2.AWSMLRuleDetails).AnomalousConsoleLogin.Threshold, + "severity": rule.Details.(*v2.AWSMLRuleDetails).AnomalousConsoleLogin.Severity, + }} + + rules = append(rules, map[string]interface{}{ + "id": rule.Id, + "name": rule.Name, + "description": rule.Description, + "version": rule.Version, + "tags": rule.Tags, + "anomalous_console_login": anomalousConsoleLogin, + }) + } + + _ = d.Set("rule", rules) + + return nil +} + +// TODO: Split this func into smaller composable functions +func setTFResourcePolicyActions(key string) func(d *schema.ResourceData, policy v2.PolicyRulesComposite) error { + return func(d *schema.ResourceData, policy v2.PolicyRulesComposite) error { + actions := []map[string]interface{}{{}} + prevent := false + for _, action := range policy.Policy.Actions { + if action.Type == "POLICY_ACTION_PREVENT_MALWARE" || action.Type == "POLICY_ACTION_PREVENT_DRIFT" { + actions[0][key] = true + prevent = true + } else if action.Type == "POLICY_ACTION_PAUSE" || action.Type == "POLICY_ACTION_STOP" || action.Type == "POLICY_ACTION_KILL" { // TODO: Refactor + action := strings.Replace(action.Type, "POLICY_ACTION_", "", 1) + actions[0]["container"] = strings.ToLower(action) + } else { + actions[0]["capture"] = []map[string]interface{}{{ + "seconds_after_event": action.AfterEventNs / 1000000000, + "seconds_before_event": action.BeforeEventNs / 1000000000, + "name": action.Name, + "filter": action.Filter, + "bucket_name": action.BucketName, + "folder": action.Folder, + }} + } + } + + // If prevent_malware was updated from true to false, ensure TF resource knows that + if !prevent { + actions[0][key] = false + } + + currentContainerAction := d.Get("actions.0.container").(string) + currentCaptureAction := d.Get("actions.0.capture").([]interface{}) + // If the policy retrieved from service has no actions and the current state is default values, + // then do not set the "actions" key as it may cause terraform to think there has been a state change + if len(policy.Policy.Actions) > 0 || currentContainerAction != "" || len(currentCaptureAction) > 0 { + _ = d.Set("actions", actions) + } + + return nil + } +} + +var malwareTFResourceReducer = Reducer( + setTFResourceBaseAttrs, + setTFResourcePolicyType(policyTypeMalware), + setTFResourcePolicyActions(preventMalwareKey), + setTFResourcePolicyRulesMalware, +) + +var driftTFResourceReducer = Reducer( + setTFResourceBaseAttrs, + setTFResourcePolicyType(policyTypeDrift), + setTFResourcePolicyActions(preventDriftKey), + setTFResourcePolicyRulesDrift, +) + +var mlTFResourceReducer = Reducer( + setTFResourceBaseAttrs, + setTFResourcePolicyType(policyTypeML), + setTFResourcePolicyRulesML, +) + +var awsMLTFResourceReducer = Reducer( + setTFResourceBaseAttrs, + setTFResourcePolicyType(policyTypeAWSML), + setTFResourcePolicyRulesAWSML, +) + +func setPolicyBaseAttrs(policyType string) func(policy *v2.PolicyRulesComposite, d *schema.ResourceData) error { + return func(policy *v2.PolicyRulesComposite, d *schema.ResourceData) error { + id, err := strconv.Atoi(d.Id()) + if err == nil && id != 0 { + policy.Policy.ID = id + } + + v := d.Get("version").(int) + if v != 0 { + // Version can only be provided when updating existing policies + policy.Policy.Version = v + } + + policy.Policy.Type = policyType + + policy.Policy.Name = d.Get("name").(string) + policy.Policy.Enabled = d.Get("enabled").(bool) + + policy.Policy.Description = d.Get("description").(string) + policy.Policy.Severity = d.Get("severity").(int) + + policy.Policy.Runbook = d.Get("runbook").(string) + policy.Policy.Scope = d.Get("scope").(string) + + policy.Policy.NotificationChannelIds = []int{} + notificationChannelIdSet := d.Get("notification_channels").(*schema.Set) + for _, id := range notificationChannelIdSet.List() { + policy.Policy.NotificationChannelIds = append(policy.Policy.NotificationChannelIds, id.(int)) + } + + return nil + } +} + +func setPolicyActions(policy *v2.PolicyRulesComposite, d *schema.ResourceData) error { + addActionsToPolicy(d, policy.Policy) + return nil +} + +func setPolicyRulesMalware(policy *v2.PolicyRulesComposite, d *schema.ResourceData) error { + policy.Policy.Rules = []*v2.PolicyRule{} + policy.Rules = []*v2.RuntimePolicyRule{} + if _, ok := d.GetOk("rule"); ok { + // TODO: Iterate over a list of rules instead of hard-coding the index values + // TODO: Should we assume that only a single Malware rule can be attached to a policy? + + additionalHashes := map[string][]string{} + if items, ok := d.GetOk("rule.0.additional_hashes"); ok { // TODO: Do not hardcode the indexes + for _, item := range items.([]interface{}) { + item := item.(map[string]interface{}) + k := item["hash"].(string) + additionalHashes[k] = []string{} + } + } + + // TODO: Extract into a function + ignoreHashes := map[string][]string{} + if items, ok := d.GetOk("rule.0.ignore_hashes"); ok { // TODO: Do not hardcode the indexes + for _, item := range items.([]interface{}) { + item := item.(map[string]interface{}) + k := item["hash"].(string) + ignoreHashes[k] = []string{} + } + } + + tags := schemaSetToList(d.Get("rule.0.tags")) + // Set default tags as field tags must not be null + if len(tags) == 0 { + tags = []string{defaultMalwareTag} + } + + rule := &v2.RuntimePolicyRule{ + // TODO: Do not hardcode the indexes + Name: d.Get("rule.0.name").(string), + Description: d.Get("rule.0.description").(string), + Tags: tags, + Details: v2.MalwareRuleDetails{ + RuleType: v2.ElementType("MALWARE"), // TODO: Use const + UseManagedHashes: d.Get("rule.0.use_managed_hashes").(bool), + AdditionalHashes: additionalHashes, + IgnoreHashes: ignoreHashes, + }, + } + + id := v2.FlexInt(d.Get("rule.0.id").(int)) + if int(id) != 0 { + rule.Id = &id + } + + v := toIntPtr(d.Get("rule.0.version")) + if *v != 0 { + // Version can only be provided when updating existing rules + rule.Version = v + } + + policy.Rules = append(policy.Rules, rule) + } + return nil +} + +func setPolicyRulesDrift(policy *v2.PolicyRulesComposite, d *schema.ResourceData) error { + policy.Policy.Rules = []*v2.PolicyRule{} + policy.Rules = []*v2.RuntimePolicyRule{} + if _, ok := d.GetOk("rule"); ok { + // TODO: Iterate over a list of rules instead of hard-coding the index values + // TODO: Should we assume that only a single Malware rule can be attached to a policy? + + exceptions := &v2.RuntimePolicyRuleList{} + if _, ok := d.GetOk("rule.0.exceptions"); ok { // TODO: Do not hardcode the indexes + exceptions.Items = schemaSetToList(d.Get("rule.0.exceptions.0.items")) + exceptions.MatchItems = d.Get("rule.0.exceptions.0.match_items").(bool) + } + + // TODO: Extract into a function + prohibitedBinaries := &v2.RuntimePolicyRuleList{} + if _, ok := d.GetOk("rule.0.prohibited_binaries"); ok { // TODO: Do not hardcode the indexes + prohibitedBinaries.Items = schemaSetToList(d.Get("rule.0.prohibited_binaries.0.items")) + prohibitedBinaries.MatchItems = d.Get("rule.0.prohibited_binaries.0.match_items").(bool) + } + + tags := schemaSetToList(d.Get("rule.0.tags")) + // Set default tags as field tags must not be null + if len(tags) == 0 { + tags = []string{defaultDriftTag} + } + + enabled := d.Get("rule.0.enabled").(bool) + mode := "enabled" + if !enabled { + mode = "disabled" + } + + rule := &v2.RuntimePolicyRule{ + // TODO: Do not hardcode the indexes + Name: d.Get("rule.0.name").(string), + Description: d.Get("rule.0.description").(string), + Tags: tags, + Details: v2.DriftRuleDetails{ + RuleType: v2.ElementType("DRIFT"), // TODO: Use const + Mode: mode, + Exceptions: exceptions, + ProhibitedBinaries: prohibitedBinaries, + }, + } + + id := v2.FlexInt(d.Get("rule.0.id").(int)) + if int(id) != 0 { + rule.Id = &id + } + + v := toIntPtr(d.Get("rule.0.version")) + if *v != 0 { + // Version can only be provided when updating existing rules + rule.Version = v + } + + policy.Rules = append(policy.Rules, rule) + } + return nil +} + +func setPolicyRulesML(policy *v2.PolicyRulesComposite, d *schema.ResourceData) error { + policy.Policy.Rules = []*v2.PolicyRule{} + policy.Rules = []*v2.RuntimePolicyRule{} + if _, ok := d.GetOk("rule"); ok { + // TODO: Iterate over a list of rules instead of hard-coding the index values + // TODO: Should we assume that only a single Malware rule can be attached to a policy? + + // TODO: Extract into a function + cryptominingTrigger := &v2.MLRuleThresholdAndSeverity{} + if _, ok := d.GetOk("rule.0.cryptomining_trigger"); ok { // TODO: Do not hardcode the indexes + cryptominingTrigger.Enabled = d.Get("rule.0.cryptomining_trigger.0.enabled").(bool) + cryptominingTrigger.Threshold = float64(d.Get("rule.0.cryptomining_trigger.0.threshold").(int)) + cryptominingTrigger.Severity = float64(d.Get("rule.0.cryptomining_trigger.0.severity").(int)) + } + anomalyDetectionTrigger := &v2.MLRuleThresholdAndSeverity{} + + tags := schemaSetToList(d.Get("rule.0.tags")) + // Set default tags as field tags must not be null + if len(tags) == 0 { + tags = []string{defaultMLTag} + } + + rule := &v2.RuntimePolicyRule{ + // TODO: Do not hardcode the indexes + Name: d.Get("rule.0.name").(string), + Description: d.Get("rule.0.description").(string), + // IMPORTANT: In order to update an ML policy, + // correct version number must be provided + Tags: tags, + Details: v2.MLRuleDetails{ + RuleType: v2.ElementType("MACHINE_LEARNING"), // TODO: Use const + CryptominingTrigger: cryptominingTrigger, + AnomalyDetectionTrigger: anomalyDetectionTrigger, + }, + } + + id := v2.FlexInt(d.Get("rule.0.id").(int)) + if int(id) != 0 { + rule.Id = &id + } + + v := toIntPtr(d.Get("rule.0.version")) + if *v != 0 { + // Version can only be provided when updating existing rules + rule.Version = v + } + + policy.Rules = append(policy.Rules, rule) + } + return nil +} + +func setPolicyRulesAWSML(policy *v2.PolicyRulesComposite, d *schema.ResourceData) error { + policy.Policy.Rules = []*v2.PolicyRule{} + policy.Rules = []*v2.RuntimePolicyRule{} + if _, ok := d.GetOk("rule"); ok { + // TODO: Iterate over a list of rules instead of hard-coding the index values + // TODO: Should we assume that only a single Malware rule can be attached to a policy? + + anomalousConsoleLogin := &v2.MLRuleThresholdAndSeverity{} + if _, ok := d.GetOk("rule.0.anomalous_console_login"); ok { // TODO: Do not hardcode the indexes + anomalousConsoleLogin.Enabled = d.Get("rule.0.anomalous_console_login.0.enabled").(bool) + anomalousConsoleLogin.Threshold = float64(d.Get("rule.0.anomalous_console_login.0.threshold").(int)) + anomalousConsoleLogin.Severity = float64(d.Get("rule.0.anomalous_console_login.0.severity").(int)) + } + + tags := schemaSetToList(d.Get("rule.0.tags")) + // Set default tags as field tags must not be null + if len(tags) == 0 { + tags = []string{defaultMLTag} + } + + rule := &v2.RuntimePolicyRule{ + // TODO: Do not hardcode the indexes + Name: d.Get("rule.0.name").(string), + Description: d.Get("rule.0.description").(string), + // IMPORTANT: In order to update an ML policy, + // correct version number must be provided + Tags: tags, + Details: v2.AWSMLRuleDetails{ + RuleType: v2.ElementType("AWS_MACHINE_LEARNING"), // TODO: Use const + AnomalousConsoleLogin: anomalousConsoleLogin, + }, + } + + id := v2.FlexInt(d.Get("rule.0.id").(int)) + if int(id) != 0 { + rule.Id = &id + } + + v := toIntPtr(d.Get("rule.0.version")) + if *v != 0 { + // Version can only be provided when updating existing rules + rule.Version = v + } + + policy.Rules = append(policy.Rules, rule) + } + return nil +} + +var malwarePolicyReducer = Reducer( + setPolicyBaseAttrs(policyTypeMalware), + setPolicyActions, + setPolicyRulesMalware, +) + +var driftPolicyReducer = Reducer( + setPolicyBaseAttrs(policyTypeDrift), + setPolicyActions, + setPolicyRulesDrift, +) + +var mlPolicyReducer = Reducer( + setPolicyBaseAttrs(policyTypeML), + setPolicyRulesML, +) + +var awsMLPolicyReducer = Reducer( + setPolicyBaseAttrs(policyTypeAWSML), + setPolicyRulesAWSML, +) diff --git a/website/docs/d/secure_aws_ml_policy.md b/website/docs/d/secure_aws_ml_policy.md new file mode 100644 index 00000000..649418e4 --- /dev/null +++ b/website/docs/d/secure_aws_ml_policy.md @@ -0,0 +1,55 @@ +--- +subcategory: "Sysdig Secure" +layout: "sysdig" +page_title: "Sysdig: sysdig_secure_aws_ml_policy" +description: |- + Retrieves a Sysdig Secure ML Policy. +--- + +# Data Source: sysdig_secure_aws_ml_policy + +Retrieves the information of an existing Sysdig Secure ML Policy. + +-> **Note:** Sysdig Terraform Provider is under rapid development at this point. If you experience any issue or discrepancy while using it, please make sure you have the latest version. If the issue persists, or you have a Feature Request to support an additional set of resources, please open a [new issue](https://github.com/sysdiglabs/terraform-provider-sysdig/issues/new) in the GitHub repository. + +## Example Usage + +```terraform +data "sysdig_secure_aws_ml_policy" "policy" { + name = "ML Policy 1" +} +``` + +## Argument Reference + +* `name` - (Required) The name of the Secure managed policy. + +## Attributes Reference + +In addition to all arguments above, the following attributes are exported: + +* `id` - The id for the managed policy. + +* `description` - The description for the managed policy. + +* `severity` - The severity of Secure policy. The accepted values + are: 0, 1, 2, 3 (High), 4, 5 (Medium), 6 (Low) and 7 (Info). + +* `enabled` - Whether the policy is enabled or not. + +* `runbook` - Customer provided url that provides a runbook for a given policy. + +* `scope` - The application scope for the policy. + +* `notification_channels` - IDs of the notification channels to send alerts to + when the policy is fired. + +### `rule` block + +The rule block is required and supports: + +* `description` - (Required) Rule description. +* `anomalous_console_login` - (Required) This attribute allows you to activate anomaly detection for console logins and adjust its settings. + * `threshold` - (Required) Trigger at or above confidence level. + * `severity` - (Optional) The severity associated with the rule. + diff --git a/website/docs/d/secure_drift_policy.md b/website/docs/d/secure_drift_policy.md new file mode 100644 index 00000000..04aa81d6 --- /dev/null +++ b/website/docs/d/secure_drift_policy.md @@ -0,0 +1,80 @@ +--- +subcategory: "Sysdig Secure" +layout: "sysdig" +page_title: "Sysdig: sysdig_secure_drift_policy" +description: |- + Retrieves a Sysdig Secure Drift Policy. +--- + +# Data Source: sysdig_secure_drift_policy + +Retrieves the information of an existing Sysdig Secure Drift Policy. + +-> **Note:** Sysdig Terraform Provider is under rapid development at this point. If you experience any issue or discrepancy while using it, please make sure you have the latest version. If the issue persists, or you have a Feature Request to support an additional set of resources, please open a [new issue](https://github.com/sysdiglabs/terraform-provider-sysdig/issues/new) in the GitHub repository. + +## Example Usage + +```terraform +data "sysdig_secure_drift_policy" "policy" { + name = "Drift Policy 1" +} +``` + +## Argument Reference + +* `name` - (Required) The name of the Secure managed policy. + +## Attributes Reference + +In addition to all arguments above, the following attributes are exported: + +* `id` - The id for the managed policy. + +* `description` - The description for the managed policy. + +* `severity` - The severity of Secure policy. The accepted values + are: 0, 1, 2, 3 (High), 4, 5 (Medium), 6 (Low) and 7 (Info). + +* `enabled` - Whether the policy is enabled or not. + +* `runbook` - Customer provided url that provides a runbook for a given policy. + +* `scope` - The application scope for the policy. + +* `notification_channels` - IDs of the notification channels to send alerts to + when the policy is fired. + +### Actions block + +The actions block is optional and supports: + +* `prevent_drift` - (Optional) Prevent the execution of drifted binaries and specified prohibited binaries. + +* `container` - (Optional) The action applied to container when this Policy is + triggered. Can be *stop*, *pause* or *kill*. If this is not specified, + no action will be applied at the container level. + +* `capture` - (Optional) Captures with Sysdig the stream of system calls: + * `seconds_before_event` - (Required) Captures the system calls during the + amount of seconds before the policy was triggered. + * `seconds_after_event` - (Required) Captures the system calls for the amount + of seconds after the policy was triggered. + * `name` - (Required) The name of the capture file + * `filter` - (Optional) Additional filter to apply to the capture. For example: `proc.name=cat` + * `bucket_name` - (Optional) Custom bucket to store capture in, + bucket should be onboarded in Integrations > S3 Capture Storage. Default is to use Sysdig Secure Storage + * `folder` - (Optional) Name of folder to store capture inside the bucket. + By default we will store the capture file at the root of the bucket + +### `rule` block + +The rule block is required and supports: + +* `description` - (Required) The description of the drift rule. +* `enabled` - (Required) Toggle to dynamically detect execution of drifted binaries. A drifted binary is any binary that was not part of the original image of the container. It is typically downloaded or compiled into a running container. +* `exceptions` - (Optional) Specify comma separated list of exceptions. + * `items` - (Required) Specify comma separated list of exceptions, e.g. `/usr/bin/rm, /usr/bin/curl`. +* `prohibited_binaries` - (Optional) A prohibited binary can be a known harmful binary or one that facilitates discovery of your environment. + * `items` - (Required) Specify comma separated list of prohibited binaries, e.g. `/usr/bin/rm, /usr/bin/curl`. + + diff --git a/website/docs/d/secure_malware_policy.md b/website/docs/d/secure_malware_policy.md new file mode 100644 index 00000000..fb06e473 --- /dev/null +++ b/website/docs/d/secure_malware_policy.md @@ -0,0 +1,79 @@ +--- +subcategory: "Sysdig Secure" +layout: "sysdig" +page_title: "Sysdig: sysdig_secure_malware_policy" +description: |- + Retrieves a Sysdig Secure Malware Policy. +--- + +# Data Source: sysdig_secure_malware_policy + +Retrieves the information of an existing Sysdig Secure Malware Policy. + +-> **Note:** Sysdig Terraform Provider is under rapid development at this point. If you experience any issue or discrepancy while using it, please make sure you have the latest version. If the issue persists, or you have a Feature Request to support an additional set of resources, please open a [new issue](https://github.com/sysdiglabs/terraform-provider-sysdig/issues/new) in the GitHub repository. + +## Example Usage + +```terraform +data "sysdig_secure_malware_policy" "example" { + name = "Sysdig Runtime Threat Detection" +} +``` + +## Argument Reference + +* `name` - (Required) The name of the Secure managed policy. + +## Attributes Reference + +In addition to all arguments above, the following attributes are exported: + +* `id` - The id for the managed policy. + +* `description` - The description for the managed policy. + +* `severity` - The severity of Secure policy. The accepted values + are: 0, 1, 2, 3 (High), 4, 5 (Medium), 6 (Low) and 7 (Info). + +* `enabled` - Whether the policy is enabled or not. + +* `runbook` - Customer provided url that provides a runbook for a given policy. + +* `scope` - The application scope for the policy. + +* `notification_channels` - IDs of the notification channels to send alerts to + when the policy is fired. + +### Actions block + +The actions block is optional and supports: + +* `prevent_malware` - (Optional) Prevent the execution of detected malware and binaries with known hashes. + +* `container` - (Optional) The action applied to container when this Policy is + triggered. Can be *stop*, *pause* or *kill*. If this is not specified, + no action will be applied at the container level. + +* `capture` - (Optional) Captures with Sysdig the stream of system calls: + * `seconds_before_event` - (Required) Captures the system calls during the + amount of seconds before the policy was triggered. + * `seconds_after_event` - (Required) Captures the system calls for the amount + of seconds after the policy was triggered. + * `name` - (Required) The name of the capture file + * `filter` - (Optional) Additional filter to apply to the capture. For example: `proc.name=cat` + * `bucket_name` - (Optional) Custom bucket to store capture in, + bucket should be onboarded in Integrations > S3 Capture Storage. Default is to use Sysdig Secure Storage + * `folder` - (Optional) Name of folder to store capture inside the bucket. + By default we will store the capture file at the root of the bucket + +### `rule` block + +The rule block is required and supports: + +* `description` - (Required) The description of the malware rule. +* `use_managed_hashes` - (Required) Should Sysdig's managed hashes be used? The possible values are `true` or `false`. +* `additional_hashes` - (Optional) The block contains a single hash that should be matched. + * `hash` - (Required) The hash value that should be matched. +* `ignore_hashes` - (Optional) The block contains a single hash that should be matched. + * `hash` - (Required) The hash value that should be matched. + diff --git a/website/docs/d/secure_ml_policy.md b/website/docs/d/secure_ml_policy.md new file mode 100644 index 00000000..0b2bc5b1 --- /dev/null +++ b/website/docs/d/secure_ml_policy.md @@ -0,0 +1,56 @@ +--- +subcategory: "Sysdig Secure" +layout: "sysdig" +page_title: "Sysdig: sysdig_secure_ml_policy" +description: |- + Retrieves a Sysdig Secure ML Policy. +--- + +# Data Source: sysdig_secure_ml_policy + +Retrieves the information of an existing Sysdig Secure ML Policy. + +-> **Note:** Sysdig Terraform Provider is under rapid development at this point. If you experience any issue or discrepancy while using it, please make sure you have the latest version. If the issue persists, or you have a Feature Request to support an additional set of resources, please open a [new issue](https://github.com/sysdiglabs/terraform-provider-sysdig/issues/new) in the GitHub repository. + +## Example Usage + +```terraform +data "sysdig_secure_ml_policy" "policy" { + name = "ML Policy 1" +} +``` + +## Argument Reference + +* `name` - (Required) The name of the Secure managed policy. + +## Attributes Reference + +In addition to all arguments above, the following attributes are exported: + +* `id` - The id for the managed policy. + +* `description` - The description for the managed policy. + +* `severity` - The severity of Secure policy. The accepted values + are: 0, 1, 2, 3 (High), 4, 5 (Medium), 6 (Low) and 7 (Info). + +* `enabled` - Whether the policy is enabled or not. + +* `runbook` - Customer provided url that provides a runbook for a given policy. + +* `scope` - The application scope for the policy. + +* `notification_channels` - IDs of the notification channels to send alerts to + when the policy is fired. + +### `rule` block + +The rule block is required and supports: + +* `description` - (Required) Rule description. +* `cryptomining_trigger` - (Required) Cryptomining detection: Detect unusual activity in the Activity Audit based on the set confidence level. + * `threshold` - (Required) Trigger at or above confidence level. + * `severity` - (Optional) Severity level associated with this rule. + + diff --git a/website/docs/r/secure_aws_ml_policy.md b/website/docs/r/secure_aws_ml_policy.md new file mode 100644 index 00000000..70421eff --- /dev/null +++ b/website/docs/r/secure_aws_ml_policy.md @@ -0,0 +1,67 @@ +--- +subcategory: "Sysdig Secure" +layout: "sysdig" +page_title: "Sysdig: sysdig_secure_aws_ml_policy" +description: |- + Retrieves a Sysdig Secure ML Policy. +--- + +# Resource: sysdig_secure_aws_ml_policy + +Retrieves the information of an existing Sysdig Secure ML Policy. + +-> **Note:** Sysdig Terraform Provider is under rapid development at this point. If you experience any issue or discrepancy while using it, please make sure you have the latest version. If the issue persists, or you have a Feature Request to support an additional set of resources, please open a [new issue](https://github.com/sysdiglabs/terraform-provider-sysdig/issues/new) in the GitHub repository. + +## Example Usage + +```terraform +resource "sysdig_secure_aws_ml_policy" "policy" { + name = "Test ML Policy 1" + description = "Test ML Policy Description" + enabled = true + severity = 4 + + rule { + description = "Test ML Rule Description" + + anomalous_console_login { + enabled = true + threshold = 1 + severity = 1 + } +} +``` + +## Argument Reference + +* `name` - (Required) The name of the Secure managed policy. + +## Attributes Reference + +In addition to all arguments above, the following attributes are exported: + +* `id` - The id for the managed policy. + +* `description` - The description for the managed policy. + +* `severity` - The severity of Secure policy. The accepted values + are: 0, 1, 2, 3 (High), 4, 5 (Medium), 6 (Low) and 7 (Info). + +* `enabled` - Whether the policy is enabled or not. + +* `runbook` - Customer provided url that provides a runbook for a given policy. + +* `scope` - The application scope for the policy. + +* `notification_channels` - IDs of the notification channels to send alerts to + when the policy is fired. + +### `rule` block + +The rule block is required and supports: + +* `description` - (Required) Rule description. +* `anomalous_console_login` - (Required) This attribute allows you to activate anomaly detection for console logins and adjust its settings. + * `threshold` - (Required) Trigger at or above confidence level. + * `severity` - (Optional) The severity associated with the rule. + diff --git a/website/docs/r/secure_drift_policy.md b/website/docs/r/secure_drift_policy.md new file mode 100644 index 00000000..e7d2d148 --- /dev/null +++ b/website/docs/r/secure_drift_policy.md @@ -0,0 +1,121 @@ +--- +subcategory: "Sysdig Secure" +layout: "sysdig" +page_title: "Sysdig: sysdig_secure_drift_policy" +description: |- + Retrieves a Sysdig Secure Drift Policy. +--- + +# Resource: sysdig_secure_drift_policy + +Retrieves the information of an existing Sysdig Secure Drift Policy. + +-> **Note:** Sysdig Terraform Provider is under rapid development at this point. If you experience any issue or discrepancy while using it, please make sure you have the latest version. If the issue persists, or you have a Feature Request to support an additional set of resources, please open a [new issue](https://github.com/sysdiglabs/terraform-provider-sysdig/issues/new) in the GitHub repository. + +## Example Usage + +```terraform +data "sysdig_secure_notification_channel" "email_notification_channel" { + name = "Test Email Channel" +} + +resource "sysdig_secure_drift_policy" "policy" { + name = "Drift Policy 1" + description = "" + severity = 4 + enabled = true + runbook = "https://runbook.com" + + // Scope selection + scope = "container.id != \"\"" + + // Rule selection + rule { + description = "Test Drift Rule Description" + enabled = true + + exceptions { + items = ["/usr/bin/curl"] + match_items = false + } + + prohibited_binaries { + items = ["/usr/bin/sh"] + match_items = true + } + + } + + actions { + prevent_drift = true + container = "stop" + + capture { + seconds_before_event = 5 + seconds_after_event = 10 + } + } + + notification_channels = [data.sysdig_secure_notification_channel.email_notification_channel.id] +} +``` + +## Argument Reference + +* `name` - (Required) The name of the Secure managed policy. + +## Attributes Reference + +In addition to all arguments above, the following attributes are exported: + +* `id` - The id for the managed policy. + +* `description` - The description for the managed policy. + +* `severity` - The severity of Secure policy. The accepted values + are: 0, 1, 2, 3 (High), 4, 5 (Medium), 6 (Low) and 7 (Info). + +* `enabled` - Whether the policy is enabled or not. + +* `runbook` - Customer provided url that provides a runbook for a given policy. + +* `scope` - The application scope for the policy. + +* `notification_channels` - IDs of the notification channels to send alerts to + when the policy is fired. + +### Actions block + +The actions block is optional and supports: + +* `prevent_drift` - (Optional) Prevent the execution of drifted binaries and specified prohibited binaries. + +* `container` - (Optional) The action applied to container when this Policy is + triggered. Can be *stop*, *pause* or *kill*. If this is not specified, + no action will be applied at the container level. + +* `capture` - (Optional) Captures with Sysdig the stream of system calls: + * `seconds_before_event` - (Required) Captures the system calls during the + amount of seconds before the policy was triggered. + * `seconds_after_event` - (Required) Captures the system calls for the amount + of seconds after the policy was triggered. + * `name` - (Required) The name of the capture file + * `filter` - (Optional) Additional filter to apply to the capture. For example: `proc.name=cat` + * `bucket_name` - (Optional) Custom bucket to store capture in, + bucket should be onboarded in Integrations > S3 Capture Storage. Default is to use Sysdig Secure Storage + * `folder` - (Optional) Name of folder to store capture inside the bucket. + By default we will store the capture file at the root of the bucket + +### `rule` block + +The rule block is required and supports: + +* `description` - (Required) The description of the drift rule. +* `enabled` - (Required) Toggle to dynamically detect execution of drifted binaries. A drifted binary is any binary that was not part of the original image of the container. It is typically downloaded or compiled into a running container. +* `exceptions` - (Optional) Specify comma separated list of exceptions. + * `items` - (Required) Specify comma separated list of exceptions, e.g. `/usr/bin/rm, /usr/bin/curl`. +* `prohibited_binaries` - (Optional) A prohibited binary can be a known harmful binary or one that facilitates discovery of your environment. + * `items` - (Required) Specify comma separated list of prohibited binaries, e.g. `/usr/bin/rm, /usr/bin/curl`. + + + diff --git a/website/docs/r/secure_malware_policy.md b/website/docs/r/secure_malware_policy.md new file mode 100644 index 00000000..24473c2b --- /dev/null +++ b/website/docs/r/secure_malware_policy.md @@ -0,0 +1,115 @@ +--- +subcategory: "Sysdig Secure" +layout: "sysdig" +page_title: "Sysdig: sysdig_secure_malware_policy" +description: |- + Retrieves a Sysdig Secure Malware Policy. +--- + +# Resource: sysdig_secure_malware_policy + +Retrieves the information of an existing Sysdig Secure Malware Policy. + +-> **Note:** Sysdig Terraform Provider is under rapid development at this point. If you experience any issue or discrepancy while using it, please make sure you have the latest version. If the issue persists, or you have a Feature Request to support an additional set of resources, please open a [new issue](https://github.com/sysdiglabs/terraform-provider-sysdig/issues/new) in the GitHub repository. + +## Example Usage + +```terraform +data "sysdig_secure_notification_channel" "email_notification_channel" { + name = "Test Email Channel" +} + +resource "sysdig_secure_malware_policy" "prevent_malware" { + name = "Write apt database" + description = "an attempt to write to the dpkg database by any non-dpkg related program" + severity = 4 + enabled = true + runbook = "https://runbook.com" + + // Scope selection + scope = "container.id != \"\"" + + // Rule selection + rule { + description = "Test Malware Rule Description" + use_managed_hashes = true + + additional_hashes { + hash = "304ef4cdda3463b24bf53f9cdd69ad3ecdab0842e7e70e2f3cfbb9f14e1c4ae6" + } + + ignore_hashes { + hash = "6ac3c336e4094835293a3fed8a4b5fedde1b5e2626d9838fed50693bba00af0e" + } + } + + actions { + prevent_malware = true + container = "stop" + + capture { + seconds_before_event = 5 + seconds_after_event = 10 + } + } + + notification_channels = [data.sysdig_secure_notification_channel.email_notification_channel.id] +} +``` + +## Argument Reference + +* `name` - (Required) The name of the Secure managed policy. + +## Attributes Reference + +In addition to all arguments above, the following attributes are exported: + +* `id` - The id for the managed policy. + +* `description` - The description for the managed policy. + +* `severity` - The severity of Secure policy. The accepted values + are: 0, 1, 2, 3 (High), 4, 5 (Medium), 6 (Low) and 7 (Info). + +* `enabled` - Whether the policy is enabled or not. + +* `runbook` - Customer provided url that provides a runbook for a given policy. + +* `scope` - The application scope for the policy. + +* `notification_channels` - IDs of the notification channels to send alerts to + when the policy is fired. + +### Actions block + +The actions block is optional and supports: + +* `prevent_malware` - (Optional) Prevent the execution of detected malware and binaries with known hashes. + +* `container` - (Optional) The action applied to container when this Policy is + triggered. Can be *stop*, *pause* or *kill*. If this is not specified, + no action will be applied at the container level. + +* `capture` - (Optional) Captures with Sysdig the stream of system calls: + * `seconds_before_event` - (Required) Captures the system calls during the + amount of seconds before the policy was triggered. + * `seconds_after_event` - (Required) Captures the system calls for the amount + of seconds after the policy was triggered. + * `name` - (Required) The name of the capture file + * `filter` - (Optional) Additional filter to apply to the capture. For example: `proc.name=cat` + * `bucket_name` - (Optional) Custom bucket to store capture in, + bucket should be onboarded in Integrations > S3 Capture Storage. Default is to use Sysdig Secure Storage + * `folder` - (Optional) Name of folder to store capture inside the bucket. + By default we will store the capture file at the root of the bucket + +### `rule` block + +The rule block is required and supports: + +* `description` - (Required) The description of the malware rule. +* `use_managed_hashes` - (Required) Should Sysdig's managed hashes be used? The possible values are `true` or `false`. +* `additional_hashes` - (Optional) The block contains a single hash that should be matched. + * `hash` - (Required) The hash value that should be matched. +* `ignore_hashes` - (Optional) The block contains a single hash that should be matched. + * `hash` - (Required) The hash value that should be matched. diff --git a/website/docs/r/secure_ml_policy.md b/website/docs/r/secure_ml_policy.md new file mode 100644 index 00000000..c01febcc --- /dev/null +++ b/website/docs/r/secure_ml_policy.md @@ -0,0 +1,69 @@ +--- +subcategory: "Sysdig Secure" +layout: "sysdig" +page_title: "Sysdig: sysdig_secure_ml_policy" +description: |- + Retrieves a Sysdig Secure ML Policy. +--- + +# Resource: sysdig_secure_ml_policy + +Retrieves the information of an existing Sysdig Secure ML Policy. + +-> **Note:** Sysdig Terraform Provider is under rapid development at this point. If you experience any issue or discrepancy while using it, please make sure you have the latest version. If the issue persists, or you have a Feature Request to support an additional set of resources, please open a [new issue](https://github.com/sysdiglabs/terraform-provider-sysdig/issues/new) in the GitHub repository. + +## Example Usage + +```terraform +resource "sysdig_secure_ml_policy" "policy" { + name = "Test ML Policy 1" + description = "Test ML Policy Description" + enabled = true + severity = 4 + + rule { + description = "Test ML Rule Description" + + cryptomining_trigger { + enabled = true + threshold = 1 + severity = 1 + } +} +``` + +## Argument Reference + +* `name` - (Required) The name of the Secure managed policy. + +## Attributes Reference + +In addition to all arguments above, the following attributes are exported: + +* `id` - The id for the managed policy. + +* `description` - The description for the managed policy. + +* `severity` - The severity of Secure policy. The accepted values + are: 0, 1, 2, 3 (High), 4, 5 (Medium), 6 (Low) and 7 (Info). + +* `enabled` - Whether the policy is enabled or not. + +* `runbook` - Customer provided url that provides a runbook for a given policy. + +* `scope` - The application scope for the policy. + +* `notification_channels` - IDs of the notification channels to send alerts to + when the policy is fired. + +### `rule` block + +The rule block is required and supports: + +* `description` - (Required) Rule description. +* `cryptomining_trigger` - (Required) Cryptomining detection: Detect unusual activity in the Activity Audit based on the set confidence level. + * `threshold` - (Required) Trigger at or above confidence level. + * `severity` - (Optional) Severity level associated with this rule. + + +