Skip to content
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_member resource #6975

Merged
merged 3 commits into from
Mar 17, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions aws/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -721,6 +721,7 @@ func Provider() terraform.ResourceProvider {
"aws_default_security_group": resourceAwsDefaultSecurityGroup(),
"aws_security_group_rule": resourceAwsSecurityGroupRule(),
"aws_securityhub_account": resourceAwsSecurityHubAccount(),
"aws_securityhub_member": resourceAwsSecurityHubMember(),
"aws_securityhub_product_subscription": resourceAwsSecurityHubProductSubscription(),
"aws_securityhub_standards_subscription": resourceAwsSecurityHubStandardsSubscription(),
"aws_servicecatalog_portfolio": resourceAwsServiceCatalogPortfolio(),
Expand Down
158 changes: 158 additions & 0 deletions aws/resource_aws_securityhub_member.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
package aws

import (
"fmt"
"log"

"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/securityhub"
"github.com/hashicorp/terraform-plugin-sdk/helper/schema"
)

const (
SecurityHubMemberStatusCreated = "Created"
SecurityHubMemberStatusInvited = "Invited"
SecurityHubMemberStatusAssociated = "Associated"
SecurityHubMemberStatusResigned = "Resigned"
SecurityHubMemberStatusRemoved = "Removed"
)

func resourceAwsSecurityHubMember() *schema.Resource {
return &schema.Resource{
Create: resourceAwsSecurityHubMemberCreate,
Read: resourceAwsSecurityHubMemberRead,
Delete: resourceAwsSecurityHubMemberDelete,
Importer: &schema.ResourceImporter{
State: schema.ImportStatePassthrough,
},

Schema: map[string]*schema.Schema{
"account_id": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
ValidateFunc: validateAwsAccountId,
},
"email": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"invite": {
Type: schema.TypeBool,
Optional: true,
ForceNew: true,
},
"master_id": {
Type: schema.TypeString,
Computed: true,
},
"member_status": {
Type: schema.TypeString,
Computed: true,
},
},
}
}

func resourceAwsSecurityHubMemberCreate(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*AWSClient).securityhubconn
log.Printf("[DEBUG] Creating Security Hub member %s", d.Get("account_id").(string))

resp, err := conn.CreateMembers(&securityhub.CreateMembersInput{
AccountDetails: []*securityhub.AccountDetails{
{
AccountId: aws.String(d.Get("account_id").(string)),
Email: aws.String(d.Get("email").(string)),
},
},
})

if err != nil {
return fmt.Errorf("Error creating Security Hub member %s: %s", d.Get("account_id").(string), err)
}

if len(resp.UnprocessedAccounts) > 0 {
return fmt.Errorf("Error creating Security Hub member %s: UnprocessedAccounts is not empty", d.Get("account_id").(string))
}

d.SetId(d.Get("account_id").(string))

if d.Get("invite").(bool) {
log.Printf("[INFO] Inviting Security Hub member %s", d.Id())
iresp, err := conn.InviteMembers(&securityhub.InviteMembersInput{
AccountIds: []*string{aws.String(d.Get("account_id").(string))},
})

if err != nil {
return fmt.Errorf("Error inviting Security Hub member %s: %s", d.Id(), err)
}

if len(iresp.UnprocessedAccounts) > 0 {
return fmt.Errorf("Error inviting Security Hub member %s: UnprocessedAccounts is not empty", d.Id())
}
}

return resourceAwsSecurityHubMemberRead(d, meta)
}

func resourceAwsSecurityHubMemberRead(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*AWSClient).securityhubconn

log.Printf("[DEBUG] Reading Security Hub member %s", d.Id())
resp, err := conn.GetMembers(&securityhub.GetMembersInput{
AccountIds: []*string{aws.String(d.Id())},
})

if err != nil {
if isAWSErr(err, securityhub.ErrCodeResourceNotFoundException, "") {
log.Printf("[WARN] Security Hub member (%s) not found, removing from state", d.Id())
d.SetId("")
return nil
}
return err
}

if len(resp.Members) == 0 {
log.Printf("[WARN] Security Hub member (%s) not found, removing from state", d.Id())
d.SetId("")
return nil
}

member := resp.Members[0]

d.Set("account_id", member.AccountId)
d.Set("email", member.Email)
d.Set("master_id", member.MasterId)

status := aws.StringValue(member.MemberStatus)
d.Set("member_status", status)

invited := status == SecurityHubMemberStatusInvited || status == SecurityHubMemberStatusAssociated || status == SecurityHubMemberStatusResigned
d.Set("invite", invited)

return nil
}

func resourceAwsSecurityHubMemberDelete(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*AWSClient).securityhubconn
log.Printf("[DEBUG] Deleting Security Hub member: %s", d.Id())

resp, err := conn.DeleteMembers(&securityhub.DeleteMembersInput{
AccountIds: []*string{aws.String(d.Id())},
})

if err != nil {
if isAWSErr(err, securityhub.ErrCodeResourceNotFoundException, "") {
log.Printf("[WARN] Security Hub member (%s) not found", d.Id())
return nil
}
return fmt.Errorf("Error deleting Security Hub member %s: %s", d.Id(), err)
}

if len(resp.UnprocessedAccounts) > 0 {
return fmt.Errorf("Error deleting Security Hub member %s: UnprocessedAccounts is not empty", d.Get("account_id").(string))
}

return nil
}
142 changes: 142 additions & 0 deletions aws/resource_aws_securityhub_member_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
package aws

import (
"fmt"
"testing"

"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/securityhub"
"github.com/hashicorp/terraform-plugin-sdk/helper/resource"
"github.com/hashicorp/terraform-plugin-sdk/terraform"
)

func testAccAWSSecurityHubMember_basic(t *testing.T) {
var member securityhub.Member
resourceName := "aws_securityhub_member.example"

resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckAWSSecurityHubMemberDestroy,
Steps: []resource.TestStep{
{
Config: testAccAWSSecurityHubMemberConfig_basic("111111111111", "example@example.com"),
Check: resource.ComposeTestCheckFunc(
testAccCheckAWSSecurityHubMemberExists(resourceName, &member),
),
},
{
ResourceName: resourceName,
ImportState: true,
ImportStateVerify: true,
},
},
})
}

func testAccAWSSecurityHubMember_invite(t *testing.T) {
var member securityhub.Member
resourceName := "aws_securityhub_member.example"

resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckAWSSecurityHubMemberDestroy,
Steps: []resource.TestStep{
{
Config: testAccAWSSecurityHubMemberConfig_invite("111111111111", "example@example.com", true),
Check: resource.ComposeTestCheckFunc(
testAccCheckAWSSecurityHubMemberExists(resourceName, &member),
resource.TestCheckResourceAttr(resourceName, "member_status", "Invited"),
resource.TestCheckResourceAttr(resourceName, "invite", "true"),
),
},
{
ResourceName: resourceName,
ImportState: true,
ImportStateVerify: true,
},
},
})
}

func testAccCheckAWSSecurityHubMemberExists(n string, member *securityhub.Member) 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.GetMembers(&securityhub.GetMembersInput{
AccountIds: []*string{aws.String(rs.Primary.ID)},
})

if err != nil {
return err
}

if len(resp.Members) == 0 {
return fmt.Errorf("Security Hub member %s not found", rs.Primary.ID)
}

member = resp.Members[0]

return nil
}
}

func testAccCheckAWSSecurityHubMemberDestroy(s *terraform.State) error {
conn := testAccProvider.Meta().(*AWSClient).securityhubconn

for _, rs := range s.RootModule().Resources {
if rs.Type != "aws_securityhub_member" {
continue
}

resp, err := conn.GetMembers(&securityhub.GetMembersInput{
AccountIds: []*string{aws.String(rs.Primary.ID)},
})

if err != nil {
if isAWSErr(err, securityhub.ErrCodeResourceNotFoundException, "") {
return nil
}
return err
}

if len(resp.Members) != 0 {
return fmt.Errorf("Security Hub member still exists")
}

return nil
}

return nil
}

func testAccAWSSecurityHubMemberConfig_basic(accountId, email string) string {
return fmt.Sprintf(`
resource "aws_securityhub_account" "example" {}

resource "aws_securityhub_member" "example" {
depends_on = ["aws_securityhub_account.example"]
account_id = "%s"
email = "%s"
}
`, accountId, email)
}

func testAccAWSSecurityHubMemberConfig_invite(accountId, email string, invite bool) string {
return fmt.Sprintf(`
resource "aws_securityhub_account" "example" {}

resource "aws_securityhub_member" "example" {
depends_on = ["aws_securityhub_account.example"]
account_id = "%s"
email = "%s"
invite = %t
}
`, accountId, email, invite)
}
4 changes: 4 additions & 0 deletions aws/resource_aws_securityhub_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ func TestAccAWSSecurityHub(t *testing.T) {
"Account": {
"basic": testAccAWSSecurityHubAccount_basic,
},
"Member": {
"basic": testAccAWSSecurityHubMember_basic,
"invite": testAccAWSSecurityHubMember_invite,
},
"ProductSubscription": {
"basic": testAccAWSSecurityHubProductSubscription_basic,
},
Expand Down
3 changes: 3 additions & 0 deletions website/aws.erb
Original file line number Diff line number Diff line change
Expand Up @@ -2613,6 +2613,9 @@
<li>
<a href="/docs/providers/aws/r/securityhub_account.html">aws_securityhub_account</a>
</li>
<li>
<a href="/docs/providers/aws/r/securityhub_member.html">aws_securityhub_member</a>
</li>
<li>
<a href="/docs/providers/aws/r/securityhub_product_subscription.html">aws_securityhub_product_subscription</a>
</li>
Expand Down
48 changes: 48 additions & 0 deletions website/docs/r/securityhub_member.markdown
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
---
subcategory: "Security Hub"
layout: "aws"
page_title: "AWS: aws_securityhub_member"
description: |-
Provides a Security Hub member resource.
---

# Resource: aws_securityhub_member

Provides a Security Hub member resource.

## Example Usage

```hcl
resource "aws_securityhub_account" "example" {}

resource "aws_securityhub_member" "example" {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this include a depends_on argument?

depends_on = ["aws_securityhub_account.example"]
account_id = "123456789012"
email = "example@example.com"
invite = true
}
```

## Argument Reference

The following arguments are supported:

* `account_id` - (Required) The ID of the member AWS account.
* `email` - (Required) The email of the member AWS account.
* `invite` - (Optional) Boolean whether to invite the account to Security Hub as a member. Defaults to `false`.

## Attributes Reference

The following attributes are exported in addition to the arguments listed above:

* `id` - The ID of the member AWS account (matches `account_id`).
* `master_id` - The ID of the master Security Hub AWS account.
* `member_status` - The status of the relationship between the member account and its master account.

## Import

Security Hub members can be imported using their account ID, e.g.

```
$ terraform import aws_securityhub_member.example 123456789012
```