diff --git a/aws/provider.go b/aws/provider.go index 9efd9061c74..9f22db7ccfe 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_standards_subscription": resourceAwsSecurityHubStandardsSubscription(), "aws_servicecatalog_portfolio": resourceAwsServiceCatalogPortfolio(), "aws_service_discovery_private_dns_namespace": resourceAwsServiceDiscoveryPrivateDnsNamespace(), "aws_service_discovery_public_dns_namespace": resourceAwsServiceDiscoveryPublicDnsNamespace(), diff --git a/aws/resource_aws_securityhub_standards_subscription.go b/aws/resource_aws_securityhub_standards_subscription.go new file mode 100644 index 00000000000..b40bf660a3d --- /dev/null +++ b/aws/resource_aws_securityhub_standards_subscription.go @@ -0,0 +1,93 @@ +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 resourceAwsSecurityHubStandardsSubscription() *schema.Resource { + return &schema.Resource{ + Create: resourceAwsSecurityHubStandardsSubscriptionCreate, + Read: resourceAwsSecurityHubStandardsSubscriptionRead, + Delete: resourceAwsSecurityHubStandardsSubscriptionDelete, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + + Schema: map[string]*schema.Schema{ + "standards_arn": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validateArn, + }, + }, + } +} + +func resourceAwsSecurityHubStandardsSubscriptionCreate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).securityhubconn + log.Printf("[DEBUG] Enabling Security Hub standard %s", d.Get("standards_arn")) + + resp, err := conn.BatchEnableStandards(&securityhub.BatchEnableStandardsInput{ + StandardsSubscriptionRequests: []*securityhub.StandardsSubscriptionRequest{ + { + StandardsArn: aws.String(d.Get("standards_arn").(string)), + }, + }, + }) + + if err != nil { + return fmt.Errorf("Error enabling Security Hub standard: %s", err) + } + + standardsSubscription := resp.StandardsSubscriptions[0] + + d.SetId(*standardsSubscription.StandardsSubscriptionArn) + + return resourceAwsSecurityHubStandardsSubscriptionRead(d, meta) +} + +func resourceAwsSecurityHubStandardsSubscriptionRead(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).securityhubconn + + log.Printf("[DEBUG] Reading Security Hub standard %s", d.Id()) + resp, err := conn.GetEnabledStandards(&securityhub.GetEnabledStandardsInput{ + StandardsSubscriptionArns: []*string{aws.String(d.Id())}, + }) + + if err != nil { + return fmt.Errorf("Error reading Security Hub standard %s: %s", d.Id(), err) + } + + if len(resp.StandardsSubscriptions) == 0 { + log.Printf("[WARN] Security Hub standard (%s) not found, removing from state", d.Id()) + d.SetId("") + return nil + } + + standardsSubscription := resp.StandardsSubscriptions[0] + + d.Set("standards_arn", standardsSubscription.StandardsArn) + + return nil +} + +func resourceAwsSecurityHubStandardsSubscriptionDelete(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).securityhubconn + log.Printf("[DEBUG] Disabling Security Hub standard %s", d.Id()) + + _, err := conn.BatchDisableStandards(&securityhub.BatchDisableStandardsInput{ + StandardsSubscriptionArns: []*string{aws.String(d.Id())}, + }) + + if err != nil { + return fmt.Errorf("Error disabling Security Hub standard %s: %s", d.Id(), err) + } + + return nil +} diff --git a/aws/resource_aws_securityhub_standards_subscription_test.go b/aws/resource_aws_securityhub_standards_subscription_test.go new file mode 100644 index 00000000000..5b1c5fb33cb --- /dev/null +++ b/aws/resource_aws_securityhub_standards_subscription_test.go @@ -0,0 +1,107 @@ +package aws + +import ( + "fmt" + "testing" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/securityhub" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" +) + +func testAccAWSSecurityHubStandardsSubscription_basic(t *testing.T) { + var standardsSubscription *securityhub.StandardsSubscription + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSSecurityHubAccountDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSSecurityHubStandardsSubscriptionConfig_basic, + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSSecurityHubStandardsSubscriptionExists("aws_securityhub_standards_subscription.example", standardsSubscription), + ), + }, + { + ResourceName: "aws_securityhub_standards_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: testAccAWSSecurityHubStandardsSubscriptionConfig_empty, + Check: testAccCheckAWSSecurityHubStandardsSubscriptionDestroy, + }, + }, + }) +} + +func testAccCheckAWSSecurityHubStandardsSubscriptionExists(n string, standardsSubscription *securityhub.StandardsSubscription) 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.GetEnabledStandards(&securityhub.GetEnabledStandardsInput{ + StandardsSubscriptionArns: []*string{aws.String(rs.Primary.ID)}, + }) + + if err != nil { + return err + } + + if len(resp.StandardsSubscriptions) == 0 { + return fmt.Errorf("Security Hub standard %s not found", rs.Primary.ID) + } + + standardsSubscription = resp.StandardsSubscriptions[0] + + return nil + } +} + +func testAccCheckAWSSecurityHubStandardsSubscriptionDestroy(s *terraform.State) error { + conn := testAccProvider.Meta().(*AWSClient).securityhubconn + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_securityhub_standards_subscription" { + continue + } + + resp, err := conn.GetEnabledStandards(&securityhub.GetEnabledStandardsInput{ + StandardsSubscriptionArns: []*string{aws.String(rs.Primary.ID)}, + }) + + if err != nil { + if isAWSErr(err, securityhub.ErrCodeResourceNotFoundException, "") { + continue + } + return err + } + + if len(resp.StandardsSubscriptions) != 0 { + return fmt.Errorf("Security Hub standard %s still exists", rs.Primary.ID) + } + } + + return nil +} + +const testAccAWSSecurityHubStandardsSubscriptionConfig_empty = ` +resource "aws_securityhub_account" "example" {} +` + +const testAccAWSSecurityHubStandardsSubscriptionConfig_basic = ` +resource "aws_securityhub_account" "example" {} + +resource "aws_securityhub_standards_subscription" "example" { + depends_on = ["aws_securityhub_account.example"] + standards_arn = "arn:aws:securityhub:::ruleset/cis-aws-foundations-benchmark/v/1.2.0" +} +` diff --git a/aws/resource_aws_securityhub_test.go b/aws/resource_aws_securityhub_test.go index 6a8b05f1883..f451ed263cd 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, }, + "StandardsSubscription": { + "basic": testAccAWSSecurityHubStandardsSubscription_basic, + }, } for group, m := range testCases { diff --git a/website/aws.erb b/website/aws.erb index c4548b6b529..9e1bc4deb90 100644 --- a/website/aws.erb +++ b/website/aws.erb @@ -2190,6 +2190,10 @@ aws_securityhub_account +