diff --git a/aws/provider.go b/aws/provider.go index 9f22db7ccfe..26bc4ac339f 100644 --- a/aws/provider.go +++ b/aws/provider.go @@ -626,6 +626,7 @@ func Provider() terraform.ResourceProvider { "aws_default_security_group": resourceAwsDefaultSecurityGroup(), "aws_security_group_rule": resourceAwsSecurityGroupRule(), "aws_securityhub_account": resourceAwsSecurityHubAccount(), + "aws_securityhub_product_subscription": resourceAwsSecurityHubProductSubscription(), "aws_securityhub_standards_subscription": resourceAwsSecurityHubStandardsSubscription(), "aws_servicecatalog_portfolio": resourceAwsServiceCatalogPortfolio(), "aws_service_discovery_private_dns_namespace": resourceAwsServiceDiscoveryPrivateDnsNamespace(), diff --git a/aws/resource_aws_securityhub_product_subscription.go b/aws/resource_aws_securityhub_product_subscription.go new file mode 100644 index 00000000000..dece3fc8b86 --- /dev/null +++ b/aws/resource_aws_securityhub_product_subscription.go @@ -0,0 +1,134 @@ +package aws + +import ( + "fmt" + "log" + "strings" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/securityhub" + "github.com/hashicorp/terraform/helper/schema" +) + +func resourceAwsSecurityHubProductSubscription() *schema.Resource { + return &schema.Resource{ + Create: resourceAwsSecurityHubProductSubscriptionCreate, + Read: resourceAwsSecurityHubProductSubscriptionRead, + Delete: resourceAwsSecurityHubProductSubscriptionDelete, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + + Schema: map[string]*schema.Schema{ + "product_arn": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validateArn, + }, + "arn": { + Type: schema.TypeString, + Computed: true, + }, + }, + } +} + +func resourceAwsSecurityHubProductSubscriptionCreate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).securityhubconn + productArn := d.Get("product_arn").(string) + + log.Printf("[DEBUG] Enabling Security Hub product subscription for product %s", productArn) + + resp, err := conn.EnableImportFindingsForProduct(&securityhub.EnableImportFindingsForProductInput{ + ProductArn: aws.String(productArn), + }) + + if err != nil { + return fmt.Errorf("Error enabling Security Hub product subscription for product %s: %s", productArn, err) + } + + d.SetId(fmt.Sprintf("%s,%s", productArn, *resp.ProductSubscriptionArn)) + + return resourceAwsSecurityHubProductSubscriptionRead(d, meta) +} + +func resourceAwsSecurityHubProductSubscriptionRead(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).securityhubconn + + productArn, productSubscriptionArn, err := resourceAwsSecurityHubProductSubscriptionParseId(d.Id()) + + if err != nil { + return err + } + + log.Printf("[DEBUG] Reading Security Hub product subscriptions to find %s", d.Id()) + + exists, err := resourceAwsSecurityHubProductSubscriptionCheckExists(conn, productSubscriptionArn) + + if err != nil { + return fmt.Errorf("Error reading Security Hub product subscriptions to find %s: %s", d.Id(), err) + } + + if !exists { + log.Printf("[WARN] Security Hub product subscriptions (%s) not found, removing from state", d.Id()) + d.SetId("") + } + + d.Set("product_arn", productArn) + d.Set("arn", productSubscriptionArn) + + return nil +} + +func resourceAwsSecurityHubProductSubscriptionCheckExists(conn *securityhub.SecurityHub, productSubscriptionArn string) (bool, error) { + input := &securityhub.ListEnabledProductsForImportInput{} + exists := false + + err := conn.ListEnabledProductsForImportPages(input, func(page *securityhub.ListEnabledProductsForImportOutput, lastPage bool) bool { + for _, readProductSubscriptionArn := range page.ProductSubscriptions { + if aws.StringValue(readProductSubscriptionArn) == productSubscriptionArn { + exists = true + return false + } + } + return !lastPage + }) + + if err != nil { + return false, err + } + + return exists, nil +} + +func resourceAwsSecurityHubProductSubscriptionParseId(id string) (string, string, error) { + parts := strings.SplitN(id, ",", 2) + + if len(parts) != 2 { + return "", "", fmt.Errorf("Expected Security Hub product subscription ID in format , - received: %s", id) + } + + return parts[0], parts[1], nil +} + +func resourceAwsSecurityHubProductSubscriptionDelete(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).securityhubconn + log.Printf("[DEBUG] Disabling Security Hub product subscription %s", d.Id()) + + _, productSubscriptionArn, err := resourceAwsSecurityHubProductSubscriptionParseId(d.Id()) + + if err != nil { + return err + } + + _, err = conn.DisableImportFindingsForProduct(&securityhub.DisableImportFindingsForProductInput{ + ProductSubscriptionArn: aws.String(productSubscriptionArn), + }) + + if err != nil { + return fmt.Errorf("Error disabling Security Hub product subscription %s: %s", d.Id(), err) + } + + return nil +} diff --git a/aws/resource_aws_securityhub_product_subscription_test.go b/aws/resource_aws_securityhub_product_subscription_test.go new file mode 100644 index 00000000000..76119826a87 --- /dev/null +++ b/aws/resource_aws_securityhub_product_subscription_test.go @@ -0,0 +1,108 @@ +package aws + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" +) + +func testAccAWSSecurityHubProductSubscription_basic(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSSecurityHubAccountDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSSecurityHubProductSubscriptionConfig_basic, + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSSecurityHubProductSubscriptionExists("aws_securityhub_product_subscription.example"), + ), + }, + { + ResourceName: "aws_securityhub_product_subscription.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: testAccAWSSecurityHubProductSubscriptionConfig_empty, + Check: testAccCheckAWSSecurityHubProductSubscriptionDestroy, + }, + }, + }) +} + +func testAccCheckAWSSecurityHubProductSubscriptionExists(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) + } + + conn := testAccProvider.Meta().(*AWSClient).securityhubconn + + _, productSubscriptionArn, err := resourceAwsSecurityHubProductSubscriptionParseId(rs.Primary.ID) + + if err != nil { + return err + } + + exists, err := resourceAwsSecurityHubProductSubscriptionCheckExists(conn, productSubscriptionArn) + + if err != nil { + return err + } + + if !exists { + return fmt.Errorf("Security Hub product subscription %s not found", rs.Primary.ID) + } + + return nil + } +} + +func testAccCheckAWSSecurityHubProductSubscriptionDestroy(s *terraform.State) error { + conn := testAccProvider.Meta().(*AWSClient).securityhubconn + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_securityhub_product_subscription" { + continue + } + + _, productSubscriptionArn, err := resourceAwsSecurityHubProductSubscriptionParseId(rs.Primary.ID) + + if err != nil { + return err + } + + exists, err := resourceAwsSecurityHubProductSubscriptionCheckExists(conn, productSubscriptionArn) + + if err != nil { + return err + } + + if exists { + return fmt.Errorf("Security Hub product subscription %s still exists", rs.Primary.ID) + } + } + + return nil +} + +const testAccAWSSecurityHubProductSubscriptionConfig_empty = ` +resource "aws_securityhub_account" "example" {} +` + +const testAccAWSSecurityHubProductSubscriptionConfig_basic = ` +resource "aws_securityhub_account" "example" {} + +data "aws_region" "current" {} + +resource "aws_securityhub_product_subscription" "example" { + depends_on = ["aws_securityhub_account.example"] + product_arn = "arn:aws:securityhub:${data.aws_region.current.name}:733251395267:product/alertlogic/althreatmanagement" +} +` diff --git a/aws/resource_aws_securityhub_test.go b/aws/resource_aws_securityhub_test.go index f451ed263cd..a00828248a5 100644 --- a/aws/resource_aws_securityhub_test.go +++ b/aws/resource_aws_securityhub_test.go @@ -9,6 +9,9 @@ func TestAccAWSSecurityHub(t *testing.T) { "Account": { "basic": testAccAWSSecurityHubAccount_basic, }, + "ProductSubscription": { + "basic": testAccAWSSecurityHubProductSubscription_basic, + }, "StandardsSubscription": { "basic": testAccAWSSecurityHubStandardsSubscription_basic, }, diff --git a/website/aws.erb b/website/aws.erb index 9e1bc4deb90..0c681e2ac1b 100644 --- a/website/aws.erb +++ b/website/aws.erb @@ -2190,6 +2190,10 @@ aws_securityhub_account + > + aws_securityhub_product_subscription + + > aws_securityhub_standards_subscription diff --git a/website/docs/r/securityhub_product_subscription.markdown b/website/docs/r/securityhub_product_subscription.markdown new file mode 100644 index 00000000000..319e5fd6f49 --- /dev/null +++ b/website/docs/r/securityhub_product_subscription.markdown @@ -0,0 +1,78 @@ +--- +layout: "aws" +page_title: "AWS: aws_securityhub_product_subscription" +sidebar_current: "docs-aws-resource-securityhub-product-subscription" +description: |- + Subscribes to a Security Hub product. +--- + +# aws_securityhub_product_subscription + +Subscribes to a Security Hub product. + +## Example Usage + +```hcl +resource "aws_securityhub_account" "example" {} + +data "aws_region" "current" {} + +resource "aws_securityhub_product_subscription" "example" { + depends_on = ["aws_securityhub_account.example"] + product_arn = "arn:aws:securityhub:${data.aws_region.current.name}:733251395267:product/alertlogic/althreatmanagement" +} +``` + +## Argument Reference + +The following arguments are supported: + +* `product_arn` - (Required) The ARN of the product that generates findings that you want to import into Security Hub - see below. + +Currently available products (remember to replace `${var.region}` as appropriate): + +* `arn:aws:securityhub:${var.region}::product/aws/guardduty` +* `arn:aws:securityhub:${var.region}::product/aws/inspector` +* `arn:aws:securityhub:${var.region}::product/aws/macie` +* `arn:aws:securityhub:${var.region}:733251395267:product/alertlogic/althreatmanagement` +* `arn:aws:securityhub:${var.region}:679703615338:product/armordefense/armoranywhere` +* `arn:aws:securityhub:${var.region}:151784055945:product/barracuda/cloudsecurityguardian` +* `arn:aws:securityhub:${var.region}:758245563457:product/checkpoint/cloudguard-iaas` +* `arn:aws:securityhub:${var.region}:634729597623:product/checkpoint/dome9-arc` +* `arn:aws:securityhub:${var.region}:517716713836:product/crowdstrike/crowdstrike-falcon` +* `arn:aws:securityhub:${var.region}:749430749651:product/cyberark/cyberark-pta` +* `arn:aws:securityhub:${var.region}:250871914685:product/f5networks/f5-advanced-waf` +* `arn:aws:securityhub:${var.region}:123073262904:product/fortinet/fortigate` +* `arn:aws:securityhub:${var.region}:324264561773:product/guardicore/aws-infection-monkey` +* `arn:aws:securityhub:${var.region}:324264561773:product/guardicore/guardicore` +* `arn:aws:securityhub:${var.region}:949680696695:product/ibm/qradar-siem` +* `arn:aws:securityhub:${var.region}:955745153808:product/imperva/imperva-attack-analytics` +* `arn:aws:securityhub:${var.region}:297986523463:product/mcafee-skyhigh/mcafee-mvision-cloud-aws` +* `arn:aws:securityhub:${var.region}:188619942792:product/paloaltonetworks/redlock` +* `arn:aws:securityhub:${var.region}:122442690527:product/paloaltonetworks/vm-series` +* `arn:aws:securityhub:${var.region}:805950163170:product/qualys/qualys-pc` +* `arn:aws:securityhub:${var.region}:805950163170:product/qualys/qualys-vm` +* `arn:aws:securityhub:${var.region}:336818582268:product/rapid7/insightvm` +* `arn:aws:securityhub:${var.region}:062897671886:product/sophos/sophos-server-protection` +* `arn:aws:securityhub:${var.region}:112543817624:product/splunk/splunk-enterprise` +* `arn:aws:securityhub:${var.region}:112543817624:product/splunk/splunk-phantom` +* `arn:aws:securityhub:${var.region}:956882708938:product/sumologicinc/sumologic-mda` +* `arn:aws:securityhub:${var.region}:754237914691:product/symantec-corp/symantec-cwp` +* `arn:aws:securityhub:${var.region}:422820575223:product/tenable/tenable-io` +* `arn:aws:securityhub:${var.region}:679593333241:product/trend-micro/deep-security` +* `arn:aws:securityhub:${var.region}:453761072151:product/turbot/turbot` +* `arn:aws:securityhub:${var.region}:496947949261:product/twistlock/twistlock-enterprise` + +## Attributes Reference + +The following attributes are exported in addition to the arguments listed above: + +* `arn` - The ARN of a resource that represents your subscription to the product that generates the findings that you want to import into Security Hub. + +## Import + +Security Hub product subscriptions can be imported in the form `product_arn,arn`, e.g. + +```sh +$ terraform import aws_securityhub_product_subscription.example arn:aws:securityhub:eu-west-1:733251395267:product/alertlogic/althreatmanagement,arn:aws:securityhub:eu-west-1:123456789012:product-subscription/alertlogic/althreatmanagement +```