diff --git a/aws/provider.go b/aws/provider.go index 34e5c292584..e76c0c2f421 100644 --- a/aws/provider.go +++ b/aws/provider.go @@ -820,6 +820,7 @@ func Provider() *schema.Provider { "aws_securityhub_member": resourceAwsSecurityHubMember(), "aws_securityhub_product_subscription": resourceAwsSecurityHubProductSubscription(), "aws_securityhub_standards_subscription": resourceAwsSecurityHubStandardsSubscription(), + "aws_securityhub_action_target": ResourceAwsSecurityHubActionTarget(), "aws_servicecatalog_portfolio": resourceAwsServiceCatalogPortfolio(), "aws_service_discovery_http_namespace": resourceAwsServiceDiscoveryHttpNamespace(), "aws_service_discovery_private_dns_namespace": resourceAwsServiceDiscoveryPrivateDnsNamespace(), diff --git a/aws/resource_aws_securityhub_action_target.go b/aws/resource_aws_securityhub_action_target.go new file mode 100644 index 00000000000..feee7de43ee --- /dev/null +++ b/aws/resource_aws_securityhub_action_target.go @@ -0,0 +1,166 @@ +package aws + +import ( + "fmt" + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/securityhub" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" + "log" + "regexp" + "strings" +) + +func ResourceAwsSecurityHubActionTarget() *schema.Resource { + return &schema.Resource{ + Create: resourceAwsSecurityHubActionTargetCreate, + Read: resourceAwsSecurityHubActionTargetRead, + Update: resourceAwsSecurityHubActionTargetUpdate, + Delete: resourceAwsSecurityHubActionTargetDelete, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + + Schema: map[string]*schema.Schema{ + "identifier": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.All( + validation.StringLenBetween(1, 20), + validation.StringMatch(regexp.MustCompile(`^[a-zA-Z0-9]+$`), "must contain only alphanumeric characters"), + ), + }, + "name": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.All( + validation.StringLenBetween(1, 20), + ), + }, + "description": { + Type: schema.TypeString, + Required: true, + }, + "action_target_arn": { + Type: schema.TypeString, + Computed: true, + }, + }, + } +} + +func resourceAwsSecurityHubActionTargetCreate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).securityhubconn + description := d.Get("description").(string) + name := d.Get("name").(string) + identifier := d.Get("identifier").(string) + + log.Printf("[DEBUG] Creating Security Hub custom action target %s", identifier) + + resp, err := conn.CreateActionTarget(&securityhub.CreateActionTargetInput{ + Description: aws.String(description), + Id: aws.String(identifier), + Name: aws.String(name), + }) + + if err != nil { + return fmt.Errorf("Error creating Security Hub custom action target %s: %s", identifier, err) + } + + d.SetId(*resp.ActionTargetArn) + + return resourceAwsSecurityHubActionTargetRead(d, meta) +} + +func resourceAwsSecurityHubActionTargetParseIdentifier(identifier string) (string, error) { + parts := strings.Split(identifier, "/") + + if len(parts) != 3 { + return "", fmt.Errorf("Expected Security Hub Custom action ARN, received: %s", identifier) + } + + return parts[2], nil +} + +func resourceAwsSecurityHubActionTargetRead(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).securityhubconn + + log.Printf("[DEBUG] Reading Security Hub custom action targets to find %s", d.Id()) + + actionTargetIdentifier, err := resourceAwsSecurityHubActionTargetParseIdentifier(d.Id()) + + if err != nil { + return err + } + + actionTarget, err := resourceAwsSecurityHubActionTargetCheckExists(conn, d.Id()) + + if err != nil { + return fmt.Errorf("Error reading Security Hub custom action targets to find %s: %s", d.Id(), err) + } + + if actionTarget == nil { + log.Printf("[WARN] Security Hub custom action target (%s) not found, removing from state", d.Id()) + d.SetId("") + return nil + } + + d.Set("identifier", actionTargetIdentifier) + d.Set("description", actionTarget.Description) + d.Set("action_target_arn", actionTarget.ActionTargetArn) + d.Set("name", actionTarget.Name) + + return nil +} + +func resourceAwsSecurityHubActionTargetUpdate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).securityhubconn + + input := &securityhub.UpdateActionTargetInput{ + ActionTargetArn: aws.String(d.Id()), + Description: aws.String(d.Get("description").(string)), + Name: aws.String(d.Get("name").(string)), + } + if _, err := conn.UpdateActionTarget(input); err != nil { + return fmt.Errorf("error updating Security Hub Action Target (%s): %w", d.Id(), err) + } + return nil +} + +func resourceAwsSecurityHubActionTargetCheckExists(conn *securityhub.SecurityHub, actionTargetArn string) (*securityhub.ActionTarget, error) { + input := &securityhub.DescribeActionTargetsInput{ + ActionTargetArns: aws.StringSlice([]string{actionTargetArn}), + } + var found *securityhub.ActionTarget = nil + err := conn.DescribeActionTargetsPages(input, func(page *securityhub.DescribeActionTargetsOutput, lastPage bool) bool { + for _, actionTarget := range page.ActionTargets { + if aws.StringValue(actionTarget.ActionTargetArn) == actionTargetArn { + found = actionTarget + return false + } + } + return !lastPage + }) + + if err != nil { + return nil, err + } + + return found, nil +} + +func resourceAwsSecurityHubActionTargetDelete(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).securityhubconn + log.Printf("[DEBUG] Deleting Security Hub custom action target %s", d.Id()) + + _, err := conn.DeleteActionTarget(&securityhub.DeleteActionTargetInput{ + ActionTargetArn: aws.String(d.Get("action_target_arn").(string)), + }) + + if err != nil { + return fmt.Errorf("Error deleting Security Hub custom action target %s: %s", d.Id(), err) + } + + return nil +} diff --git a/aws/resource_aws_securityhub_action_target_test.go b/aws/resource_aws_securityhub_action_target_test.go new file mode 100644 index 00000000000..e8c2ce469a5 --- /dev/null +++ b/aws/resource_aws_securityhub_action_target_test.go @@ -0,0 +1,103 @@ +package aws + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" +) + +func testAccAwsSecurityHubActionTarget_basic(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSSecurityHubAccountDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAwsSecurityHubActionTargetConfig, + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSSecurityHubAccountExists("aws_securityhub_account.example"), + testAccCheckAwsSecurityHubActionTargetExists("aws_securityhub_action_target.example"), + ), + }, + { + ResourceName: "aws_securityhub_action_target.example", + ImportState: true, + ImportStateVerify: true, + }, + { + // Check Destroy - but only target the specific resource (otherwise Security Hub + // will be disabled and the destroy check will fail) + Config: testAccAwsSecurityHubActionTargetConfig_empty, + Check: testAccCheckAwsSecurityHubActionTargetDestroy, + }, + }, + }) +} + +func testAccCheckAwsSecurityHubActionTargetExists(n string) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[n] + if !ok { + return fmt.Errorf("Not found: %s", n) + } + + if rs.Primary.ID == "" { + return fmt.Errorf("No Security Hub custom action ARN is set") + } + + conn := testAccProvider.Meta().(*AWSClient).securityhubconn + + action, err := resourceAwsSecurityHubActionTargetCheckExists(conn, rs.Primary.ID) + + if err != nil { + return err + } + + if action == nil { + return fmt.Errorf("Security Hub custom action %s not found", rs.Primary.ID) + } + + return nil + } +} + +func testAccCheckAwsSecurityHubActionTargetDestroy(s *terraform.State) error { + conn := testAccProvider.Meta().(*AWSClient).securityhubconn + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_securityhub_action_target" { + continue + } + + action, err := resourceAwsSecurityHubActionTargetCheckExists(conn, rs.Primary.ID) + + if err != nil { + return err + } + + if action == nil { + return fmt.Errorf("Security Hub custom action %s still exists", rs.Primary.ID) + } + } + + return nil +} + +const testAccAwsSecurityHubActionTargetConfig_empty = ` +resource "aws_securityhub_account" "example" {} +` + +const testAccAwsSecurityHubActionTargetConfig = ` +resource "aws_securityhub_account" "example" {} + +data "aws_region" "current" {} + +resource "aws_securityhub_action_target" "example" { + depends_on = ["aws_securityhub_account.example"] + name = "Test action" + identifier = "testaction" + description = "This is a test custom action" +} +` diff --git a/aws/resource_aws_securityhub_test.go b/aws/resource_aws_securityhub_test.go index fc0c7555873..42cb4945a9f 100644 --- a/aws/resource_aws_securityhub_test.go +++ b/aws/resource_aws_securityhub_test.go @@ -13,6 +13,9 @@ func TestAccAWSSecurityHub_serial(t *testing.T) { "basic": testAccAWSSecurityHubMember_basic, "invite": testAccAWSSecurityHubMember_invite, }, + "CustomAction": { + "basic": testAccAwsSecurityHubActionTarget_basic, + }, "ProductSubscription": { "basic": testAccAWSSecurityHubProductSubscription_basic, }, diff --git a/website/docs/r/securityhub_action_target.html.markdown b/website/docs/r/securityhub_action_target.html.markdown new file mode 100644 index 00000000000..516d38c353a --- /dev/null +++ b/website/docs/r/securityhub_action_target.html.markdown @@ -0,0 +1,48 @@ +--- +subcategory: "Security Hub" +layout: "aws" +page_title: "AWS: aws_securityhub_action_target" +description: |- + Creates Security Hub custom action. +--- + +# Resource: aws_securityhub_action_target + +Creates Security Hub custom action. + +## Example Usage + +```hcl +resource "aws_securityhub_account" "example" {} + +data "aws_region" "current" {} + +resource "aws_securityhub_action_target" "example" { + depends_on = ["aws_securityhub_account.example"] + name = "Send notification to chat" + identifier = "SendToChat" + description = "This is custom action sends selected findings to chat" +} +``` + +## Argument Reference + +The following arguments are supported: + +* `name` - (Required) The description for the custom action target. +* `identifier` - (Required) The ID for the custom action target. +* `description` - (Required) The name of the custom action target. + +## Attributes Reference + +The following attributes are exported in addition to the arguments listed above: + +* `action_target_arn` - The action target ARN of the Security Hub custom action. + +## Import + +Security Hub custom action can be imported using the action target ARN e.g. + +```sh +$ terraform import aws_securityhub_action_target.example arn:aws:securityhub:eu-west-1:312940875350:action/custom/a +```