diff --git a/README.md b/README.md index 2163884b..b37ad6a9 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,7 @@ $ cd terraform-provider-sysdig $ make build ``` -If you're a rookie, check [Official Terraform Provider development guides](https://developer.hashicorp.com/terraform/plugin/frameworkO) +If you're a rookie, check [Official Terraform Provider development guides](https://developer.hashicorp.com/terraform/plugin/framework) ### Creating new resource / data sources diff --git a/sysdig/internal/client/v2/ip_filtering_settings.go b/sysdig/internal/client/v2/ip_filtering_settings.go new file mode 100644 index 00000000..6a2aabd8 --- /dev/null +++ b/sysdig/internal/client/v2/ip_filtering_settings.go @@ -0,0 +1,65 @@ +package v2 + +import ( + "context" + "fmt" + "net/http" +) + +const ( + IPFiltersSettingsPath = "%s/platform/v1/ip-filters-settings" +) + +type IPFilteringSettingsInterface interface { + Base + GetIPFilteringSettings(ctx context.Context) (*IPFiltersSettings, error) + UpdateIPFilteringSettings(ctx context.Context, ipFiltersSettings *IPFiltersSettings) (*IPFiltersSettings, error) +} + +func (client *Client) GetIPFilteringSettings(ctx context.Context) (*IPFiltersSettings, error) { + response, err := client.requester.Request(ctx, http.MethodGet, client.GetIPFiltersSettingsURL(), nil) + if err != nil { + return nil, err + } + defer response.Body.Close() + + if response.StatusCode != http.StatusOK { + err = client.ErrorFromResponse(response) + return nil, err + } + + ipFiltersSettings, err := Unmarshal[IPFiltersSettings](response.Body) + if err != nil { + return nil, err + } + + return &ipFiltersSettings, nil +} + +func (client *Client) UpdateIPFilteringSettings(ctx context.Context, ipFiltersSettings *IPFiltersSettings) (*IPFiltersSettings, error) { + payload, err := Marshal(ipFiltersSettings) + if err != nil { + return nil, err + } + + response, err := client.requester.Request(ctx, http.MethodPut, client.GetIPFiltersSettingsURL(), payload) + if err != nil { + return nil, err + } + defer response.Body.Close() + + if response.StatusCode != http.StatusOK { + return nil, client.ErrorFromResponse(response) + } + + updated, err := Unmarshal[IPFiltersSettings](response.Body) + if err != nil { + return nil, err + } + + return &updated, nil +} + +func (client *Client) GetIPFiltersSettingsURL() string { + return fmt.Sprintf(IPFiltersSettingsPath, client.config.url) +} diff --git a/sysdig/internal/client/v2/ip_filters.go b/sysdig/internal/client/v2/ip_filters.go new file mode 100644 index 00000000..d1d20775 --- /dev/null +++ b/sysdig/internal/client/v2/ip_filters.go @@ -0,0 +1,114 @@ +package v2 + +import ( + "context" + "errors" + "fmt" + "net/http" +) + +var IPFilterNotFound = errors.New("IP filter not found") + +const ( + IPFiltersPath = "%s/platform/v1/ip-filters" + IPFilterPath = "%s/platform/v1/ip-filters/%d" +) + +type IPFiltersInterface interface { + Base + GetIPFilterById(ctx context.Context, id int) (*IPFilter, error) + CreateIPFilter(ctx context.Context, ipFilter *IPFilter) (*IPFilter, error) + UpdateIPFilter(ctx context.Context, ipFilter *IPFilter, id int) (*IPFilter, error) + DeleteIPFilter(ctx context.Context, id int) error +} + +func (client *Client) GetIPFilterById(ctx context.Context, id int) (*IPFilter, error) { + response, err := client.requester.Request(ctx, http.MethodGet, client.GetIPFilterURL(id), nil) + if err != nil { + return nil, err + } + defer response.Body.Close() + + if response.StatusCode != http.StatusOK { + err = client.ErrorFromResponse(response) + return nil, err + } + + ipFilter, err := Unmarshal[IPFilter](response.Body) + if err != nil { + return nil, err + } + + return &ipFilter, nil +} + +func (client *Client) CreateIPFilter(ctx context.Context, ipFilter *IPFilter) (*IPFilter, error) { + payload, err := Marshal(ipFilter) + if err != nil { + return nil, err + } + + response, err := client.requester.Request(ctx, http.MethodPost, client.GetIPFiltersURL(), payload) + if err != nil { + return nil, err + } + defer response.Body.Close() + + if response.StatusCode != http.StatusCreated { + return nil, client.ErrorFromResponse(response) + } + + created, err := Unmarshal[IPFilter](response.Body) + if err != nil { + return nil, err + } + + return &created, nil + +} + +func (client *Client) UpdateIPFilter(ctx context.Context, ipFilter *IPFilter, id int) (*IPFilter, error) { + payload, err := Marshal(ipFilter) + if err != nil { + return nil, err + } + + response, err := client.requester.Request(ctx, http.MethodPut, client.GetIPFilterURL(id), payload) + if err != nil { + return nil, err + } + defer response.Body.Close() + + if response.StatusCode != http.StatusOK { + return nil, client.ErrorFromResponse(response) + } + + updated, err := Unmarshal[IPFilter](response.Body) + if err != nil { + return nil, err + } + + return &updated, nil +} + +func (client *Client) DeleteIPFilter(ctx context.Context, id int) error { + response, err := client.requester.Request(ctx, http.MethodDelete, client.GetIPFilterURL(id), nil) + if err != nil { + return err + } + defer response.Body.Close() + + if response.StatusCode != http.StatusNoContent && response.StatusCode != http.StatusNotFound { + return client.ErrorFromResponse(response) + } + + return nil +} + +func (client *Client) GetIPFilterURL(id int) string { + return fmt.Sprintf(IPFilterPath, client.config.url, id) +} + +func (client *Client) GetIPFiltersURL() string { + return fmt.Sprintf(IPFiltersPath, client.config.url) +} diff --git a/sysdig/internal/client/v2/model.go b/sysdig/internal/client/v2/model.go index 6c3cccce..6339c980 100644 --- a/sysdig/internal/client/v2/model.go +++ b/sysdig/internal/client/v2/model.go @@ -174,6 +174,18 @@ type GroupMappingConfig struct { DifferentTeamSameRoleStrategy string `json:"differentRolesSameTeamStrategy"` } +type IPFilter struct { + ID int `json:"id,omitempty"` + IPRange string `json:"ipRange"` + Note string `json:"note,omitempty"` + Enabled bool `json:"isEnabled"` + LastUpdated string `json:"lastUpdated,omitempty"` +} + +type IPFiltersSettings struct { + IPFilteringEnabled bool `json:"isFilteringEnabled"` +} + type alertWrapper struct { Alert Alert `json:"alert"` } diff --git a/sysdig/internal/client/v2/sysdig.go b/sysdig/internal/client/v2/sysdig.go index dbf88ea4..ab419b48 100644 --- a/sysdig/internal/client/v2/sysdig.go +++ b/sysdig/internal/client/v2/sysdig.go @@ -23,6 +23,8 @@ type SysdigCommon interface { CustomRoleInterface CustomRolePermissionInterface TeamServiceAccountInterface + IPFiltersInterface + IPFilteringSettingsInterface } type SysdigMonitor interface { diff --git a/sysdig/provider.go b/sysdig/provider.go index 3fff7e7c..3b2fa2a0 100644 --- a/sysdig/provider.go +++ b/sysdig/provider.go @@ -115,12 +115,14 @@ func (p *SysdigProvider) Provider() *schema.Provider { }, }, ResourcesMap: map[string]*schema.Resource{ - "sysdig_user": resourceSysdigUser(), - "sysdig_group_mapping": resourceSysdigGroupMapping(), - "sysdig_group_mapping_config": resourceSysdigGroupMappingConfig(), - "sysdig_custom_role": resourceSysdigCustomRole(), - "sysdig_team_service_account": resourceSysdigTeamServiceAccount(), - "sysdig_agent_access_key": resourceSysdigAgentAccessKey(), + "sysdig_user": resourceSysdigUser(), + "sysdig_group_mapping": resourceSysdigGroupMapping(), + "sysdig_group_mapping_config": resourceSysdigGroupMappingConfig(), + "sysdig_custom_role": resourceSysdigCustomRole(), + "sysdig_team_service_account": resourceSysdigTeamServiceAccount(), + "sysdig_agent_access_key": resourceSysdigAgentAccessKey(), + "sysdig_ip_filter": resourceSysdigIPFilter(), + "sysdig_ip_filtering_settings": resourceSysdigIPFilteringSettings(), "sysdig_secure_aws_ml_policy": resourceSysdigSecureAWSMLPolicy(), "sysdig_secure_custom_policy": resourceSysdigSecureCustomPolicy(), diff --git a/sysdig/resource_sysdig_ip_filter.go b/sysdig/resource_sysdig_ip_filter.go new file mode 100644 index 00000000..4a9cd910 --- /dev/null +++ b/sysdig/resource_sysdig_ip_filter.go @@ -0,0 +1,155 @@ +package sysdig + +import ( + "context" + 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" + "strconv" +) + +func resourceSysdigIPFilter() *schema.Resource { + return &schema.Resource{ + ReadContext: resourceSysdigIPFilterRead, + CreateContext: resourceSysdigIPFilterCreate, + UpdateContext: resourceSysdigIPFilterUpdate, + DeleteContext: resourceSysdigIPFilterDelete, + Schema: map[string]*schema.Schema{ + "ip_range": { + Type: schema.TypeString, + Required: true, + }, + "note": { + Type: schema.TypeString, + Optional: true, + }, + "enabled": { + Type: schema.TypeBool, + Required: true, + }, + }, + } +} + +func resourceSysdigIPFilterRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + client, err := m.(SysdigClients).sysdigCommonClientV2() + if err != nil { + return diag.FromErr(err) + } + + id, err := strconv.Atoi(d.Id()) + if err != nil { + return diag.FromErr(err) + } + + ipFilter, err := client.GetIPFilterById(ctx, id) + if err != nil { + if err == v2.IPFilterNotFound { + d.SetId("") + return nil + } + return diag.FromErr(err) + } + + err = ipFilterToResourceData(ipFilter, d) + if err != nil { + return diag.FromErr(err) + } + + return nil +} + +func resourceSysdigIPFilterCreate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + client, err := m.(SysdigClients).sysdigCommonClientV2() + if err != nil { + return diag.FromErr(err) + } + + ipFilter, err := ipFilterFromResourceData(d) + if err != nil { + return diag.FromErr(err) + } + + createdIPFilter, err := client.CreateIPFilter(ctx, ipFilter) + if err != nil { + return diag.FromErr(err) + } + + d.SetId(strconv.Itoa(createdIPFilter.ID)) + + resourceSysdigIPFilterRead(ctx, d, m) + + return nil +} + +func resourceSysdigIPFilterUpdate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + client, err := m.(SysdigClients).sysdigCommonClientV2() + if err != nil { + return diag.FromErr(err) + } + + ipFilter, err := ipFilterFromResourceData(d) + if err != nil { + return diag.FromErr(err) + } + + id, err := strconv.Atoi(d.Id()) + if err != nil { + return diag.FromErr(err) + + } + + ipFilter.ID = id + _, err = client.UpdateIPFilter(ctx, ipFilter, id) + if err != nil { + return diag.FromErr(err) + } + + resourceSysdigIPFilterRead(ctx, d, m) + + return nil +} + +func resourceSysdigIPFilterDelete(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + client, err := m.(SysdigClients).sysdigCommonClientV2() + if err != nil { + return diag.FromErr(err) + } + + id, err := strconv.Atoi(d.Id()) + if err != nil { + return diag.FromErr(err) + } + + err = client.DeleteIPFilter(ctx, id) + if err != nil { + return diag.FromErr(err) + } + + return nil +} + +func ipFilterToResourceData(ipFilter *v2.IPFilter, d *schema.ResourceData) error { + err := d.Set("ip_range", ipFilter.IPRange) + if err != nil { + return err + } + err = d.Set("note", ipFilter.Note) + if err != nil { + return err + } + err = d.Set("enabled", ipFilter.Enabled) + if err != nil { + return err + } + + return nil +} + +func ipFilterFromResourceData(d *schema.ResourceData) (*v2.IPFilter, error) { + return &v2.IPFilter{ + IPRange: d.Get("ip_range").(string), + Note: d.Get("note").(string), + Enabled: d.Get("enabled").(bool), + }, nil +} diff --git a/sysdig/resource_sysdig_ip_filter_test.go b/sysdig/resource_sysdig_ip_filter_test.go new file mode 100644 index 00000000..2acec57d --- /dev/null +++ b/sysdig/resource_sysdig_ip_filter_test.go @@ -0,0 +1,53 @@ +//go:build tf_acc_sysdig_monitor || tf_acc_sysdig_secure || tf_acc_sysdig_common + +package sysdig_test + +import ( + "fmt" + "github.com/draios/terraform-provider-sysdig/sysdig" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func TestAccSysdigIpFilter_fullLifecycle(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: preCheckAnyEnv(t, SysdigMonitorApiTokenEnv), + ProviderFactories: map[string]func() (*schema.Provider, error){ + "sysdig": func() (*schema.Provider, error) { + return sysdig.Provider(), nil + }, + }, + Steps: []resource.TestStep{ + { + // Create resource + Config: createIPFilter("192.168.1.0/24", "Initial note", true), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("sysdig_ip_filter.test", "ip_range", "192.168.1.0/24"), + resource.TestCheckResourceAttr("sysdig_ip_filter.test", "note", "Initial note"), + resource.TestCheckResourceAttr("sysdig_ip_filter.test", "enabled", "true"), + ), + }, + { + // Update resource + Config: createIPFilter("192.168.2.0/24", "Updated note", false), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("sysdig_ip_filter.test", "ip_range", "192.168.2.0/24"), + resource.TestCheckResourceAttr("sysdig_ip_filter.test", "note", "Updated note"), + resource.TestCheckResourceAttr("sysdig_ip_filter.test", "enabled", "false"), + ), + }, + }, + }) +} + +func createIPFilter(ipRange, note string, enabled bool) string { + return fmt.Sprintf(` +resource "sysdig_ip_filter" "test" { + ip_range = "%s" + note = "%s" + enabled = %t +} +`, ipRange, note, enabled) +} diff --git a/sysdig/resource_sysdig_ip_filtering_settings.go b/sysdig/resource_sysdig_ip_filtering_settings.go new file mode 100644 index 00000000..2ae25037 --- /dev/null +++ b/sysdig/resource_sysdig_ip_filtering_settings.go @@ -0,0 +1,93 @@ +package sysdig + +import ( + "context" + 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" +) + +func resourceSysdigIPFilteringSettings() *schema.Resource { + return &schema.Resource{ + ReadContext: resourceSysdigIPFilteringSettingsRead, + CreateContext: resourceSysdigIPFilteringSettingsCreate, + UpdateContext: resourceSysdigIPFilteringSettingsUpdate, + DeleteContext: resourceSysdigIPFilteringSettingsDelete, + Schema: map[string]*schema.Schema{ + "ip_filtering_enabled": { + Type: schema.TypeBool, + Required: true, + }, + }, + } +} + +func resourceSysdigIPFilteringSettingsRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + client, err := m.(SysdigClients).sysdigCommonClientV2() + if err != nil { + return diag.FromErr(err) + } + + ipFilteringSettings, err := client.GetIPFilteringSettings(ctx) + if err != nil { + return diag.FromErr(err) + } + + err = ipFilteringSettingsToResourceData(ipFilteringSettings, d) + if err != nil { + return diag.FromErr(err) + } + + return nil +} + +func resourceSysdigIPFilteringSettingsCreate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + + d.SetId("ip_filtering_settings_id") // It's singleton resource so we use a fixed ID + + return updateIPFilteringSettings(ctx, d, m) +} + +func resourceSysdigIPFilteringSettingsUpdate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + return updateIPFilteringSettings(ctx, d, m) +} + +func resourceSysdigIPFilteringSettingsDelete(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + return nil +} + +func updateIPFilteringSettings(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + client, err := m.(SysdigClients).sysdigCommonClientV2() + if err != nil { + return diag.FromErr(err) + } + + ipFiltersSettings, err := ipFilteringSettingsFromResourceData(d) + if err != nil { + return diag.FromErr(err) + } + + _, err = client.UpdateIPFilteringSettings(ctx, ipFiltersSettings) + if err != nil { + return diag.FromErr(err) + } + + resourceSysdigIPFilteringSettingsRead(ctx, d, m) + + return nil +} + +func ipFilteringSettingsToResourceData(ipFiltersSettings *v2.IPFiltersSettings, d *schema.ResourceData) error { + err := d.Set("ip_filtering_enabled", ipFiltersSettings.IPFilteringEnabled) + if err != nil { + return err + } + + return nil +} + +func ipFilteringSettingsFromResourceData(d *schema.ResourceData) (*v2.IPFiltersSettings, error) { + return &v2.IPFiltersSettings{ + IPFilteringEnabled: d.Get("ip_filtering_enabled").(bool), + }, nil +} diff --git a/sysdig/resource_sysdig_ip_filtering_settings_test.go b/sysdig/resource_sysdig_ip_filtering_settings_test.go new file mode 100644 index 00000000..f2f52a0e --- /dev/null +++ b/sysdig/resource_sysdig_ip_filtering_settings_test.go @@ -0,0 +1,40 @@ +//go:build tf_acc_sysdig_monitor || tf_acc_sysdig_secure || tf_acc_sysdig_common + +package sysdig_test + +import ( + "fmt" + "github.com/draios/terraform-provider-sysdig/sysdig" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func TestAccSysdigIpFilteringSettings_fullLifecycle(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: preCheckAnyEnv(t, SysdigMonitorApiTokenEnv), + ProviderFactories: map[string]func() (*schema.Provider, error){ + "sysdig": func() (*schema.Provider, error) { + return sysdig.Provider(), nil + }, + }, + Steps: []resource.TestStep{ + { + // Create resource + Config: createIPFilteringSettings(false), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("sysdig_ip_filtering_settings.test", "ip_filtering_enabled", "false"), + ), + }, + }, + }) +} + +func createIPFilteringSettings(ipFilteringEnabled bool) string { + return fmt.Sprintf(` +resource "sysdig_ip_filtering_settings" "test" { + ip_filtering_enabled = %t +} +`, ipFilteringEnabled) +} diff --git a/website/docs/r/ip_filter.md b/website/docs/r/ip_filter.md new file mode 100644 index 00000000..0ef37238 --- /dev/null +++ b/website/docs/r/ip_filter.md @@ -0,0 +1,46 @@ +--- +subcategory: "Sysdig Platform" +layout: "sysdig" +page_title: "Sysdig: sysdig_allowed_ip_range" +description: |- + Creates allowed IP range in Sysdig which can be used to restrict access to the Sysdig platform. +--- + +# Resource: sysdig_ip_filter + +Configures IP address/range which can be used to restrict access to the Sysdig platform. +The feature is activated by setting `ip_filtering_enabled` to `true` in the `sysdig_ip_filtering_settings` resource. At least one IP range must be defined in the `sysdig_ip_filter` before enabling the feature. + +-> **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_ip_filter" "example" { + ip_range = "192.168.100.0/24" + note = "Office IP range" + enabled = true +} + +``` +This example creates a filter for IP range 192.168.100.0/24, with a note indicating it's for an office IP range, and it's enabled. + + +## Argument Reference + +* `ip_range` - (Required) The IP range to allow access to the Sysdig platform. Must be in CIDR notation. +* `enabled` - (Required) Specifies whether the IP range is enabled. +* `note` - (Optional) A note describing the allowed IP range. + +## Attributes Reference + +In addition to all arguments above, the following attributes are exported: +* `id` - The ID of the allowed IP range. + +## Import + +Sysdig IP filter can be imported using the ID, e.g. + +``` +$ terraform import sysdig_ip_filter.example 12345 +``` diff --git a/website/docs/r/ip_filtering_settings.md b/website/docs/r/ip_filtering_settings.md new file mode 100644 index 00000000..c1d9d4a1 --- /dev/null +++ b/website/docs/r/ip_filtering_settings.md @@ -0,0 +1,48 @@ +--- +subcategory: "Sysdig Platform" +layout: "sysdig" +page_title: "Sysdig: sysdig_ip_filtering_settings" +description: |- + Creates a IP filters settings in Sysdig. +--- + +# Resource: sysdig_ip_filtering_settings + +Configures settings for IP filters (`sysdig_ip_filter` resource) which can be used to restrict access to the Sysdig platform. +Currently, there is only one setting available: `ip_filtering_enabled` which enables or disables the IP filtering feature. To enable the feature, at least one IP range must be defined in the `sysdig_ip_filter` resource. + +> **Warning** +> This resource is global and is allowed to have only one instance per customer. +> Please verify that all IP ranges are created before enabling the feature. Failure to include your IP range will block your access to Sysdig until you connect from an approved IP range. + + +The `sysdig_ip_filtering_settings` behaves differently from normal resources, in that Terraform does not destroy this resource. +On resource destruction, Terraform performs no actions in Sysdig. + +-> **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_ip_filtering_settings" "example" { + ip_filtering_enabled = true +} + +``` +This example enables the IP filtering feature. + +## Argument Reference + +* `ip_filtering_enabled` - (Required) Specifies whether the IP filtering feature is enabled. + +## Attributes Reference + +No additional attributes are exported. + +## Import + +Sysdig IP filters settings can be imported, e.g. + +``` +$ terraform import sysdig_ip_filtering_settings.example ip_filtering_settings_id +```