-
Notifications
You must be signed in to change notification settings - Fork 9.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
r/aws_securityhub: Add aws_securityhub_product_subscription resource #6921
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
package aws | ||
|
||
import ( | ||
"fmt" | ||
"log" | ||
|
||
"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, | ||
}, | ||
}, | ||
} | ||
} | ||
|
||
func resourceAwsSecurityHubProductSubscriptionCreate(d *schema.ResourceData, meta interface{}) error { | ||
conn := meta.(*AWSClient).securityhubconn | ||
log.Printf("[DEBUG] Enabling Security Hub product subscription for product %s", d.Get("product_arn")) | ||
|
||
resp, err := conn.EnableImportFindingsForProduct(&securityhub.EnableImportFindingsForProductInput{ | ||
ProductArn: aws.String(d.Get("product_arn").(string)), | ||
}) | ||
|
||
if err != nil { | ||
return fmt.Errorf("Error enabling Security Hub product subscription for product %s: %s", d.Get("product_arn"), err) | ||
} | ||
|
||
d.SetId(*resp.ProductSubscriptionArn) | ||
|
||
return resourceAwsSecurityHubProductSubscriptionRead(d, meta) | ||
} | ||
|
||
func resourceAwsSecurityHubProductSubscriptionRead(d *schema.ResourceData, meta interface{}) error { | ||
conn := meta.(*AWSClient).securityhubconn | ||
|
||
log.Printf("[DEBUG] Reading Security Hub product subscriptions to find %s", d.Id()) | ||
resp, err := conn.ListEnabledProductsForImport(&securityhub.ListEnabledProductsForImportInput{}) | ||
bflad marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
if err != nil { | ||
return fmt.Errorf("Error reading Security Hub product subscriptions to find %s: %s", d.Id(), err) | ||
} | ||
|
||
productSubscriptions := make([]interface{}, len(resp.ProductSubscriptions)) | ||
bflad marked this conversation as resolved.
Show resolved
Hide resolved
|
||
for i := range resp.ProductSubscriptions { | ||
productSubscriptions[i] = *resp.ProductSubscriptions[i] | ||
} | ||
|
||
if _, contains := sliceContainsString(productSubscriptions, d.Id()); !contains { | ||
log.Printf("[WARN] Security Hub product subscriptions (%s) not found, removing from state", d.Id()) | ||
d.SetId("") | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func resourceAwsSecurityHubProductSubscriptionDelete(d *schema.ResourceData, meta interface{}) error { | ||
conn := meta.(*AWSClient).securityhubconn | ||
log.Printf("[DEBUG] Disabling Security Hub product subscription %s", d.Id()) | ||
|
||
_, err := conn.DisableImportFindingsForProduct(&securityhub.DisableImportFindingsForProductInput{ | ||
ProductSubscriptionArn: aws.String(d.Id()), | ||
}) | ||
|
||
if err != nil { | ||
return fmt.Errorf("Error disabling Security Hub product subscription %s: %s", d.Id(), err) | ||
} | ||
|
||
return nil | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
package aws | ||
|
||
import ( | ||
"fmt" | ||
"testing" | ||
|
||
"github.com/aws/aws-sdk-go/service/securityhub" | ||
"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"), | ||
), | ||
}, | ||
// Import is not supported, since the API to lookup product_arn from a product | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. {
ResourceName: "aws_securityhub_product_subscription.example",
ImportState: true,
ImportStateVerify: true,
} There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I've added an import test step, but I've need to use {
ResourceName: "aws_securityhub_product_subscription.example",
ImportState: true,
ImportStateVerify: true,
ImportStateVerifyIgnore: []string{"product_arn"},
}, |
||
// subscription is currrently private | ||
{ | ||
// 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 | ||
|
||
resp, err := conn.ListEnabledProductsForImport(&securityhub.ListEnabledProductsForImportInput{}) | ||
|
||
if err != nil { | ||
return err | ||
} | ||
|
||
productSubscriptions := make([]interface{}, len(resp.ProductSubscriptions)) | ||
for i := range resp.ProductSubscriptions { | ||
productSubscriptions[i] = *resp.ProductSubscriptions[i] | ||
} | ||
|
||
if _, contains := sliceContainsString(productSubscriptions, rs.Primary.ID); !contains { | ||
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 | ||
} | ||
|
||
resp, err := conn.ListEnabledProductsForImport(&securityhub.ListEnabledProductsForImportInput{}) | ||
|
||
if err != nil { | ||
return err | ||
} | ||
|
||
productSubscriptions := make([]interface{}, len(resp.ProductSubscriptions)) | ||
for i := range resp.ProductSubscriptions { | ||
productSubscriptions[i] = *resp.ProductSubscriptions[i] | ||
} | ||
|
||
if _, contains := sliceContainsString(productSubscriptions, rs.Primary.ID); contains { | ||
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" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just noting here for future posterity and not that we don't like our friends over at AlertLogic, but its a little bit of a bummer that the AWS services are automatically subscribed for acceptance testing purposes since they are effectively free, assumed stable for a long while, and doesn't involve an extra EULA. (I'm a little surprised the API doesn't require accepting the EULA first.) I'll try to switch to using a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎉 7cdbfcf |
||
} | ||
` |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
--- | ||
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: | ||
|
||
* `id` - The ARN of a resource that represents your subscription to the product that generates the findings that you want to import into Security Hub. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. * `id` - 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 via the product ARN, e.g.
```sh
$ terraform import aws_securityhub_product_subscription.example arn:aws:securityhub:us-east-1::product/aws/guardduty
``` There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The ID is the subscription ARN since that's the only thing I can test still exists - I've added an Import section describing it. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is currently missing acceptance testing and documentation. 😅 I think the current comment in the acceptance testing might be outdated since it looks like the
Read
function is implemented just fine below.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I left this in accidentally - but I might have a misunderstanding about the import process. Do resources need to be able to read/populate all attributes solely from the resource ID? The API doesn't have a way to read back the
product_arn
given a product subscription ARN (you can only check a subscription exists). It looks like I might be able to make an import test that works if I specifyImportStateVerifyIgnore: []string{"product_arn"}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah yikes, I was conflating product ARNs with product subscription ARNs. Yeah, for imports we need to set all attributes in the Read function or it'll show them as a difference after import (e.g.
attribute: "" => "configured-value"
).We have a few options since the API doesn't have a way to read it back:
I'd lean towards including both ARNs in the resource ID for ease, operator friendliness after import, and safer than trying to derive it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I thought about option 3 previously and then ran away scared 🤣 (I think it's doable, but the resource would need to know the current account ID and region to build an ARN and it feels brittle). Option 2 sounds good to me - I'll look at it later today.