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

Add SecurityHub central organization configuration support #35752

Merged
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
Show all changes
80 commits
Select commit Hold shift + click to select a range
6d15af7
Add Security Hub organization_configuration support
twelsh-aw Feb 10, 2024
89576dd
Add tests and fixes
twelsh-aw Feb 10, 2024
77ff562
Implement delete and cleanup misleading comments
twelsh-aw Feb 10, 2024
44549af
Merge branch 'hashicorp:main' into issues-34651_securityhub_central_c…
twelsh-aw Feb 12, 2024
4786a7c
Cleanups for organization_configuration
twelsh-aw Feb 18, 2024
6c5ee7b
Add resource for configuration_policy
twelsh-aw Feb 20, 2024
10981bb
Add more acceptance tests
twelsh-aw Feb 20, 2024
c1e3041
Fix TestAccSecurityHub_serial
twelsh-aw Feb 20, 2024
4536b8e
Merge branch 'hashicorp:main' into issues-34651_securityhub_central_c…
twelsh-aw Feb 20, 2024
115d731
Add configuration policy association resource
twelsh-aw Feb 24, 2024
8dd49f6
Add configurable timeout settings
twelsh-aw Feb 25, 2024
859186f
Merge branch 'hashicorp:main' into issues-34651_securityhub_central_c…
twelsh-aw Feb 25, 2024
5a2bcd1
Shorten association timeout
twelsh-aw Feb 26, 2024
cef74d5
Add documentation
twelsh-aw Mar 3, 2024
d425b76
Merge branch 'hashicorp:main' into issues-34651_securityhub_central_c…
twelsh-aw Mar 3, 2024
9330634
Update error check configuration after merges
twelsh-aw Mar 3, 2024
a0515f5
Remove security hub account resource in central config test setup
twelsh-aw Mar 4, 2024
fa30e8e
Speed up configuration_policy_association_test.go
twelsh-aw Mar 4, 2024
c0029c8
Merge branch 'main' into HEAD
ewbankkit Mar 6, 2024
d163ce9
Add CHANGELOG entry.
ewbankkit Mar 6, 2024
07dd6de
Fix semgrep 'ci.semgrep.aws.pointer-conversion-ResourceData-SetId'.
ewbankkit Mar 6, 2024
80e8be5
Fix semgrep 'ci.typed-enum-conversion'.
ewbankkit Mar 6, 2024
34bd792
Fix semgrep 'ci.calling-SetId-in-resource-delete'.
ewbankkit Mar 6, 2024
15c350f
Fix semgrep 'ci.typed-enum-conversion'.
ewbankkit Mar 6, 2024
421026b
Merge branch 'hashicorp:main' into issues-34651_securityhub_central_c…
twelsh-aw Mar 6, 2024
4caecc5
Fix semgrep 'ci.caps2-in-var-name'.
ewbankkit Mar 6, 2024
2b797e3
Merge commit '4caecc5f4b5c599b600543fc49497fb7b316a021' into HEAD
ewbankkit Mar 6, 2024
8429657
Lint fixes
twelsh-aw Mar 6, 2024
1368531
Fix semgrep 'ci.securityhub-in-func-name'.
ewbankkit Mar 6, 2024
7d5dbdf
Merge commit '13685314dfe015809ec7f23d5eb7d274dda2b6c0' into HEAD
ewbankkit Mar 6, 2024
fd1b9e5
Run 'make fmt'.
ewbankkit Mar 6, 2024
5a517f7
Fix tfproviderdocs 'missing title section'.
ewbankkit Mar 6, 2024
93109ea
Fix terrafmt errors in acceptance test configurations.
ewbankkit Mar 6, 2024
97c99d6
Fix semgrep 'ci.securityhub-in-func-name'.
ewbankkit Mar 6, 2024
0647f25
Fix terrafmt errors in acceptance test configurations.
ewbankkit Mar 6, 2024
6a8b98b
Add missing attribute reference in docs
twelsh-aw Mar 7, 2024
669e4af
Move functions around.
ewbankkit Mar 7, 2024
6be25d6
Cosmetics.
ewbankkit Mar 7, 2024
1a82250
Cosmetics.
ewbankkit Mar 7, 2024
818cbf0
Cosmetics.
ewbankkit Mar 7, 2024
67f0a32
Fix error handling.
ewbankkit Mar 7, 2024
d4a2099
r/aws_securityhub_configuration_policy: Rename some attributes.
ewbankkit Mar 7, 2024
971dc50
Add 'findConfigurationPolicyByID'.
ewbankkit Mar 7, 2024
3f1f706
r/aws_securityhub_configuration_policy: Tidy up acceptance tests.
ewbankkit Mar 7, 2024
aae9c28
Add 'testAccConfigurationPolicy_disappears'.
ewbankkit Mar 7, 2024
80d0479
r/aws_securityhub_configuration_policy_association: Tidy up.
ewbankkit Mar 7, 2024
2de1102
r/aws_securityhub_configuration_policy_association: Tidy up acceptanc…
ewbankkit Mar 7, 2024
ca01f97
r/aws_securityhub_organization_configuration: Tidy up.
ewbankkit Mar 7, 2024
97f6a33
Merge branch 'main' into HEAD
ewbankkit Mar 8, 2024
c616b93
r/aws_securityhub_organization_configuration: Add 'statusOrganization…
ewbankkit Mar 8, 2024
cb5af4c
r/aws_securityhub_organization_configuration: Tidy up acceptance tests.
ewbankkit Mar 8, 2024
3c60106
r/aws_securityhub_account: Reduce visibility.
ewbankkit Mar 8, 2024
a160d8e
r/aws_securityhub_action_target: Reduce visibility.
ewbankkit Mar 8, 2024
9165cee
r/aws_securityhub_finding_aggregator: Reduce visibility.
ewbankkit Mar 8, 2024
c385a79
r/aws_securityhub_insight: Reduce visibility.
ewbankkit Mar 8, 2024
494d599
r/aws_securityhub_invite_accepter: Reduce visibility.
ewbankkit Mar 8, 2024
28ec78d
r/aws_securityhub_member: Reduce visibility.
ewbankkit Mar 8, 2024
7ef7998
r/aws_securityhub_standards_subscription: Reduce visibility.
ewbankkit Mar 8, 2024
6987e53
Fix golangci-lint 'unparam'.
ewbankkit Mar 8, 2024
919e7ef
Consolidate 'TestAccSecurityHub_centralConfiguration' into 'TestAccSe…
ewbankkit Mar 8, 2024
20c4c99
r/aws_securityhub_configuration_policy: Fix typos.
ewbankkit Mar 8, 2024
40980ce
r/aws_securityhub_organization_configuration: Only run waiters if 'co…
ewbankkit Mar 8, 2024
db3b85e
Add 'testAccCheckOrganizationConfigurationDestroy'.
ewbankkit Mar 8, 2024
2e606a4
r/aws_securityhub_organization_configuration: Add CHANGELOG entry for…
ewbankkit Mar 8, 2024
abf31cb
testAccOrganizationConfiguration_centralConfiguration: Remove 'acctes…
ewbankkit Mar 8, 2024
5bfe14e
Add 'acctest.PreCheckOrganizationManagementAccountWithProvider' and '…
ewbankkit Mar 8, 2024
fe4b269
Add 'acctest.ProviderFunc'.
ewbankkit Mar 8, 2024
109eb81
testAccOrganizationConfiguration_centralConfiguration: Call 'acctest.…
ewbankkit Mar 8, 2024
9779c21
r/aws_securityhub_organization_configuration: Retry on errors like 'D…
ewbankkit Mar 8, 2024
835b8a5
Add 'testAccConfigurationPolicyAssociation_disappears'.
ewbankkit Mar 8, 2024
75eac52
r/aws_securityhub_configuration_policy: Better error handling.
ewbankkit Mar 8, 2024
ea9f200
Acceptane test output:
ewbankkit Mar 8, 2024
c9593fe
r/aws_securityhub_configuration_policy: Handle 'ResourceNotFoundExcep…
ewbankkit Mar 8, 2024
502d829
Acceptance test output:
ewbankkit Mar 8, 2024
a119cb5
r/aws_securityhub_configuration_policy_association: Tidy up acceptanc…
ewbankkit Mar 8, 2024
7402052
r/aws_securityhub_configuration_policy_association: Better error hand…
ewbankkit Mar 8, 2024
a0dc5c2
r/aws_securityhub_configuration_policy_association: Handle 'ResourceN…
ewbankkit Mar 8, 2024
65ffc0b
r/aws_securityhub_configuration_policy_association: Random names in a…
ewbankkit Mar 8, 2024
38abadb
Acceptance test output:
ewbankkit Mar 8, 2024
8f3a4b5
Fix golangci-lint 'whitespace'.
ewbankkit Mar 8, 2024
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
118 changes: 105 additions & 13 deletions internal/service/securityhub/organization_configuration.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ package securityhub

import (
"context"
"fmt"
"log"
"time"

"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/securityhub"
Expand Down Expand Up @@ -43,6 +45,24 @@ func ResourceOrganizationConfiguration() *schema.Resource {
Computed: true,
ValidateDiagFunc: enum.Validate[types.AutoEnableStandards](),
},
"organization_configuration": {
Type: schema.TypeList,
Optional: true,
MaxItems: 1,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"configuration_type": {
Type: schema.TypeString,
Required: true,
ValidateDiagFunc: enum.Validate[types.OrganizationConfigurationConfigurationType](),
},
"status": {
Type: schema.TypeString,
Computed: true,
},
},
},
},
},
}
}
Expand All @@ -59,6 +79,10 @@ func resourceOrganizationConfigurationUpdate(ctx context.Context, d *schema.Reso
input.AutoEnableStandards = types.AutoEnableStandards(v.(string))
}

if v, ok := d.GetOk("organization_configuration"); ok && len(v.([]interface{})) > 0 && v.([]interface{})[0] != nil {
input.OrganizationConfiguration = expandOrganizationConfiguration(v.([]interface{})[0].(map[string]interface{}))
}

_, err := conn.UpdateOrganizationConfiguration(ctx, input)

if err != nil {
Expand All @@ -76,7 +100,7 @@ func resourceOrganizationConfigurationRead(ctx context.Context, d *schema.Resour
var diags diag.Diagnostics
conn := meta.(*conns.AWSClient).SecurityHubClient(ctx)

output, err := FindOrganizationConfiguration(ctx, conn)
output, err := WaitOrganizationConfigurationEnabled(ctx, conn, d.Timeout(schema.TimeoutDefault))

if !d.IsNewResource() && tfresource.NotFound(err) {
log.Printf("[WARN] Security Hub Organization Configuration %s not found, removing from state", d.Id())
Expand All @@ -91,28 +115,96 @@ func resourceOrganizationConfigurationRead(ctx context.Context, d *schema.Resour
d.Set("auto_enable", output.AutoEnable)
d.Set("auto_enable_standards", output.AutoEnableStandards)

if err := d.Set("organization_configuration", []interface{}{flattenOrganizationConfiguration(output.OrganizationConfiguration)}); err != nil {
return sdkdiag.AppendErrorf(diags, "setting organization_configuration: %s", err)
}

return diags
}

func FindOrganizationConfiguration(ctx context.Context, conn *securityhub.Client) (*securityhub.DescribeOrganizationConfigurationOutput, error) {
input := &securityhub.DescribeOrganizationConfigurationInput{}
func findOrganizationConfiguration(ctx context.Context, conn *securityhub.Client) retry.StateRefreshFunc {
return func() (interface{}, string, error) {
input := &securityhub.DescribeOrganizationConfigurationInput{}
output, err := conn.DescribeOrganizationConfiguration(ctx, input)

output, err := conn.DescribeOrganizationConfiguration(ctx, input)
if tfawserr.ErrCodeEquals(err, errCodeResourceNotFoundException) || tfawserr.ErrMessageContains(err, errCodeInvalidAccessException, "not subscribed to AWS Security Hub") {
return nil, "", &retry.NotFoundError{
LastError: err,
LastRequest: input,
}
}

if tfawserr.ErrCodeEquals(err, errCodeResourceNotFoundException) || tfawserr.ErrMessageContains(err, errCodeInvalidAccessException, "not subscribed to AWS Security Hub") {
return nil, &retry.NotFoundError{
LastError: err,
LastRequest: input,
if err != nil {
return nil, "", err
}

if output == nil || output.OrganizationConfiguration == nil {
return nil, "", tfresource.NewEmptyResultError(input)
}

switch output.OrganizationConfiguration.Status {
case types.OrganizationConfigurationStatusPending:
return nil, "", nil
case types.OrganizationConfigurationStatusEnabled, "":
return output, string(output.OrganizationConfiguration.Status), nil
default:
var statusErr error
if msg := output.OrganizationConfiguration.StatusMessage; msg != nil && len(*msg) > 0 {
statusErr = fmt.Errorf("StatusMessage: %s", *msg)
}
return nil, "", &retry.UnexpectedStateError{
LastError: statusErr,
State: string(output.OrganizationConfiguration.Status),
ExpectedState: []string{
string(types.OrganizationConfigurationStatusEnabled),
string(types.OrganizationConfigurationStatusPending),
},
}
}
}
}

if err != nil {
return nil, err
func WaitOrganizationConfigurationEnabled(ctx context.Context, conn *securityhub.Client, timeout time.Duration) (*securityhub.DescribeOrganizationConfigurationOutput, error) {
stateConf := &retry.StateChangeConf{
Pending: enum.Slice(types.OrganizationConfigurationStatusPending),
Target: append(enum.Slice(types.OrganizationConfigurationStatusEnabled), ""),
Refresh: findOrganizationConfiguration(ctx, conn),
Timeout: timeout,
NotFoundChecks: 20,
ContinuousTargetOccurence: 2,
}

outputRaw, err := stateConf.WaitForStateContext(ctx)
if out, ok := outputRaw.(*securityhub.DescribeOrganizationConfigurationOutput); ok {
return out, err
}

return nil, err
}

func expandOrganizationConfiguration(tfMap map[string]interface{}) *types.OrganizationConfiguration {
if tfMap == nil {
return nil
}

apiObject := &types.OrganizationConfiguration{}

if v, ok := tfMap["configuration_type"].(string); ok && len(v) > 0 {
apiObject.ConfigurationType = types.OrganizationConfigurationConfigurationType(v)
}

return apiObject
}

func flattenOrganizationConfiguration(apiObject *types.OrganizationConfiguration) map[string]interface{} {
if apiObject == nil {
return nil
}

if output == nil || output.OrganizationConfiguration == nil {
return nil, tfresource.NewEmptyResultError(input)
tfMap := map[string]interface{}{
"configuration_type": apiObject.ConfigurationType,
"status": apiObject.Status,
}

return output, nil
return tfMap
}
117 changes: 116 additions & 1 deletion internal/service/securityhub/organization_configuration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"context"
"fmt"
"testing"
"time"

"github.com/hashicorp/terraform-plugin-testing/helper/resource"
"github.com/hashicorp/terraform-plugin-testing/terraform"
Expand All @@ -32,6 +33,7 @@ func testAccOrganizationConfiguration_basic(t *testing.T) {
testAccOrganizationConfigurationExists(ctx, resourceName),
resource.TestCheckResourceAttr(resourceName, "auto_enable", "true"),
resource.TestCheckResourceAttr(resourceName, "auto_enable_standards", "DEFAULT"),
resource.TestCheckResourceAttr(resourceName, "organization_configuration.#", "0"),
),
},
{
Expand All @@ -45,6 +47,7 @@ func testAccOrganizationConfiguration_basic(t *testing.T) {
testAccOrganizationConfigurationExists(ctx, resourceName),
resource.TestCheckResourceAttr(resourceName, "auto_enable", "false"),
resource.TestCheckResourceAttr(resourceName, "auto_enable_standards", "DEFAULT"),
resource.TestCheckResourceAttr(resourceName, "organization_configuration.#", "0"),
),
},
},
Expand All @@ -67,6 +70,7 @@ func testAccOrganizationConfiguration_autoEnableStandards(t *testing.T) {
testAccOrganizationConfigurationExists(ctx, resourceName),
resource.TestCheckResourceAttr(resourceName, "auto_enable", "true"),
resource.TestCheckResourceAttr(resourceName, "auto_enable_standards", "DEFAULT"),
resource.TestCheckResourceAttr(resourceName, "organization_configuration.#", "0"),
),
},
{
Expand All @@ -80,6 +84,87 @@ func testAccOrganizationConfiguration_autoEnableStandards(t *testing.T) {
testAccOrganizationConfigurationExists(ctx, resourceName),
resource.TestCheckResourceAttr(resourceName, "auto_enable", "true"),
resource.TestCheckResourceAttr(resourceName, "auto_enable_standards", "NONE"),
resource.TestCheckResourceAttr(resourceName, "organization_configuration.#", "0"),
),
},
},
})
}

// CENTRAL configuration has unique set of constraints vs other SecurityHub organization_config:
//
// 1. Must be done from a *member* delegated admin account:
// "Central configuration couldn't be enabled because the organization management account is designated as the delegated Security Hub administrator account."
// "Designate a different account as the delegated administrator, and retry."
//
// To allow for this the following is a multi-account test:
// The primary provider is expected to be a member account and alternate provider a management account.
//
// 2. Dependencies on DelegatedAdmin and FindingAggregators invert (!) after central config is created.
// "Finding Aggregator must be created to enable Central Configuration"
// "You must [...] disable central configuration in order to remove or change your aggregation Region."
// "You must [...] disable central configuration in order to remove or change the delegated Security Hub administrator"
//
// Due to this API behaviour, this resource isn't very terraform friendly.
func TestAccOrganizationConfiguration_centralConfiguration(t *testing.T) {
ctx := acctest.Context(t)
resourceName := "aws_securityhub_organization_configuration.test"
resource.Test(t, resource.TestCase{
PreCheck: func() {
acctest.PreCheck(ctx, t)
acctest.PreCheckAlternateAccount(t)
acctest.PreCheckAlternateRegionIs(t, acctest.Region())
acctest.PreCheckOrganizationMemberAccount(ctx, t)
},
ErrorCheck: acctest.ErrorCheck(t, names.SecurityHubEndpointID),
ProtoV5ProviderFactories: acctest.ProtoV5FactoriesAlternate(ctx, t),
CheckDestroy: acctest.CheckDestroyNoop,
Steps: []resource.TestStep{
{
// Start with LOCAL e.g default behaviour
// This allows us to create an finding_aggregator in test without breaking dependency flow on destroy
Config: testAccOrganizationConfigurationConfig_centralConfiguration(true, "DEFAULT", "LOCAL"),
Check: resource.ComposeTestCheckFunc(
testAccOrganizationConfigurationExists(ctx, resourceName),
resource.TestCheckResourceAttr(resourceName, "auto_enable", "true"),
resource.TestCheckResourceAttr(resourceName, "auto_enable_standards", "DEFAULT"),
resource.TestCheckResourceAttr(resourceName, "organization_configuration.#", "1"),
resource.TestCheckResourceAttr(resourceName, "organization_configuration.0.configuration_type", "LOCAL"),
resource.TestCheckResourceAttr(resourceName, "organization_configuration.0.status", "ENABLED"),
),
},
{
ResourceName: resourceName,
ImportState: true,
ImportStateVerify: true,
},
// Enable CENTRAL configuration
{
Config: testAccOrganizationConfigurationConfig_centralConfiguration(false, "NONE", "CENTRAL"),
Check: resource.ComposeTestCheckFunc(
testAccOrganizationConfigurationExists(ctx, resourceName),
resource.TestCheckResourceAttr(resourceName, "auto_enable", "false"),
resource.TestCheckResourceAttr(resourceName, "auto_enable_standards", "NONE"),
resource.TestCheckResourceAttr(resourceName, "organization_configuration.#", "1"),
resource.TestCheckResourceAttr(resourceName, "organization_configuration.0.configuration_type", "CENTRAL"),
resource.TestCheckResourceAttr(resourceName, "organization_configuration.0.status", "ENABLED"),
),
},
{
ResourceName: resourceName,
ImportState: true,
ImportStateVerify: true,
},
// Go back to LOCAL; this allows us to destroy the delegated admin with the given dependency flow that is necessary for creates.
{
Config: testAccOrganizationConfigurationConfig_centralConfiguration(true, "DEFAULT", "LOCAL"),
Check: resource.ComposeTestCheckFunc(
testAccOrganizationConfigurationExists(ctx, resourceName),
resource.TestCheckResourceAttr(resourceName, "auto_enable", "true"),
resource.TestCheckResourceAttr(resourceName, "auto_enable_standards", "DEFAULT"),
resource.TestCheckResourceAttr(resourceName, "organization_configuration.#", "1"),
resource.TestCheckResourceAttr(resourceName, "organization_configuration.0.configuration_type", "LOCAL"),
resource.TestCheckResourceAttr(resourceName, "organization_configuration.0.status", "ENABLED"),
),
},
},
Expand All @@ -95,7 +180,7 @@ func testAccOrganizationConfigurationExists(ctx context.Context, n string) resou

conn := acctest.Provider.Meta().(*conns.AWSClient).SecurityHubClient(ctx)

_, err := tfsecurityhub.FindOrganizationConfiguration(ctx, conn)
_, err := tfsecurityhub.WaitOrganizationConfigurationEnabled(ctx, conn, 2*time.Minute)

return err
}
Expand Down Expand Up @@ -133,3 +218,33 @@ resource "aws_securityhub_organization_configuration" "test" {
}
`, autoEnableStandards))
}

func testAccOrganizationConfigurationConfig_centralConfiguration(autoEnable bool, autoEnableStandards, configType string) string {
return acctest.ConfigCompose(
acctest.ConfigAlternateAccountProvider(),
fmt.Sprintf(`
data "aws_caller_identity" "member" {}

resource "aws_securityhub_organization_admin_account" "test" {
admin_account_id = data.aws_caller_identity.member.account_id

provider = awsalternate
}

resource "aws_securityhub_finding_aggregator" "test" {
linking_mode = "ALL_REGIONS"

depends_on = [aws_securityhub_organization_admin_account.test]
}

resource "aws_securityhub_organization_configuration" "test" {
auto_enable = %[1]t
auto_enable_standards = %[2]q
organization_configuration {
configuration_type = %[3]q
}

depends_on = [aws_securityhub_organization_admin_account.test]
}
`, autoEnable, autoEnableStandards, configType))
}
5 changes: 3 additions & 2 deletions internal/service/securityhub/securityhub_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,9 @@ func TestAccSecurityHub_serial(t *testing.T) {
"MultiRegion": testAccOrganizationAdminAccount_MultiRegion,
},
"OrganizationConfiguration": {
"basic": testAccOrganizationConfiguration_basic,
"AutoEnableStandards": testAccOrganizationConfiguration_autoEnableStandards,
"basic": testAccOrganizationConfiguration_basic,
"AutoEnableStandards": testAccOrganizationConfiguration_autoEnableStandards,
"CentralConfiguration": TestAccOrganizationConfiguration_centralConfiguration,
},
"ProductSubscription": {
"basic": testAccProductSubscription_basic,
Expand Down
Loading