-
-
Notifications
You must be signed in to change notification settings - Fork 356
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Implement support for Macie member accounts (#323)
- Loading branch information
1 parent
52b3e2e
commit 127dad6
Showing
6 changed files
with
211 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
package aws | ||
|
||
import ( | ||
goerror "errors" | ||
"time" | ||
|
||
"github.com/aws/aws-sdk-go/aws" | ||
"github.com/aws/aws-sdk-go/aws/session" | ||
"github.com/aws/aws-sdk-go/service/macie2" | ||
"github.com/aws/aws-sdk-go/service/sts" | ||
"github.com/gruntwork-io/cloud-nuke/config" | ||
"github.com/gruntwork-io/cloud-nuke/logging" | ||
"github.com/gruntwork-io/go-commons/errors" | ||
) | ||
|
||
func getAllMacieMemberAccounts(session *session.Session, excludeAfter time.Time, configObj config.Config) ([]string, error) { | ||
svc := macie2.New(session) | ||
stssvc := sts.New(session) | ||
|
||
allMacieAccounts := []string{} | ||
output, err := svc.GetAdministratorAccount(&macie2.GetAdministratorAccountInput{}) | ||
if err != nil { | ||
// There are several different errors that AWS may return when you attempt to call Macie operations on an account | ||
// that doesn't yet have Macie enabled. For our purposes, this is fine, as we're only looking for those accounts and | ||
// regions where Macie is enabled. Therefore, we ignore only these expected errors, and return any other error that might occur | ||
var ade *macie2.AccessDeniedException | ||
var rnfe *macie2.ResourceNotFoundException | ||
|
||
switch { | ||
case goerror.As(err, &ade): | ||
logging.Logger.Debugf("Macie AccessDeniedException means macie is not enabled in account, so skipping") | ||
return allMacieAccounts, nil | ||
case goerror.As(err, &rnfe): | ||
logging.Logger.Debugf("Macie ResourceNotFoundException means macie is not enabled in account, so skipping") | ||
return allMacieAccounts, nil | ||
default: | ||
return allMacieAccounts, errors.WithStackTrace(err) | ||
} | ||
} | ||
// If the current account does have an Administrator account relationship, and it is enabled, then we consider this a macie member account | ||
if output.Administrator != nil && output.Administrator.RelationshipStatus != nil { | ||
if aws.StringValue(output.Administrator.RelationshipStatus) == macie2.RelationshipStatusEnabled { | ||
|
||
input := &sts.GetCallerIdentityInput{} | ||
output, err := stssvc.GetCallerIdentity(input) | ||
if err != nil { | ||
return allMacieAccounts, errors.WithStackTrace(err) | ||
} | ||
|
||
currentAccountId := aws.StringValue(output.Account) | ||
|
||
allMacieAccounts = append(allMacieAccounts, currentAccountId) | ||
} | ||
} | ||
|
||
return allMacieAccounts, nil | ||
} | ||
|
||
func nukeAllMacieMemberAccounts(session *session.Session, identifiers []string) error { | ||
svc := macie2.New(session) | ||
region := aws.StringValue(session.Config.Region) | ||
|
||
if len(identifiers) == 0 { | ||
logging.Logger.Infof("No Macie member accounts to nuke in region %s", *session.Config.Region) | ||
return nil | ||
} | ||
|
||
logging.Logger.Infof("Deleting Macie account membership and disabling Macie in %s", region) | ||
|
||
for _, accountId := range identifiers { | ||
_, disassociateErr := svc.DisassociateFromAdministratorAccount(&macie2.DisassociateFromAdministratorAccountInput{}) | ||
|
||
if disassociateErr != nil { | ||
return errors.WithStackTrace(disassociateErr) | ||
} | ||
|
||
_, err := svc.DisableMacie(&macie2.DisableMacieInput{}) | ||
if err != nil { | ||
return errors.WithStackTrace(err) | ||
} | ||
|
||
logging.Logger.Infof("[OK] Macie account association for accountId %s deleted in %s", accountId, region) | ||
} | ||
|
||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
package aws | ||
|
||
import ( | ||
"testing" | ||
"time" | ||
|
||
"github.com/aws/aws-sdk-go/aws" | ||
"github.com/aws/aws-sdk-go/aws/session" | ||
"github.com/aws/aws-sdk-go/service/macie2" | ||
"github.com/gruntwork-io/cloud-nuke/config" | ||
"github.com/gruntwork-io/cloud-nuke/util" | ||
"github.com/stretchr/testify/assert" | ||
"github.com/stretchr/testify/require" | ||
) | ||
|
||
func TestListMacieAccounts(t *testing.T) { | ||
// Currently we hardcode to region us-east-1, because this is where our "standing" test invite exists | ||
region := "us-east-1" | ||
session, err := session.NewSession(&aws.Config{Region: aws.String(region)}) | ||
require.NoError(t, err) | ||
|
||
accountId, err := util.GetCurrentAccountId(session) | ||
require.NoError(t, err) | ||
|
||
acceptTestInvite(t, session) | ||
// Clean up after test by deleting the macie account association | ||
defer nukeAllMacieMemberAccounts(session, []string{accountId}) | ||
|
||
retrievedAccountIds, lookupErr := getAllMacieMemberAccounts(session, time.Now(), config.Config{}) | ||
require.NoError(t, lookupErr) | ||
|
||
assert.Contains(t, retrievedAccountIds, accountId) | ||
} | ||
|
||
// Macie is not very conducive to programmatic testing. In order to make this test work, we maintain a standing invite | ||
// from our phxdevops test account to our nuclear-wasteland account. We can continuously "nuke" our membership because | ||
// Macie supports a member account *that was invited* to remove its own association at any time. Meanwhile, disassociating | ||
// in this manner does not destroy or invalidate the original invitation, which allows us to to continually re-accept it | ||
// from our nuclear-wasteland account (where cloud-nuke tests are run), just so that we can nuke it again | ||
// | ||
// Macie is also regional, so for the purposes of cost-savings and lower admin overhead, we're initially only testing this | ||
// in the one hardcoded region - us-east-1 | ||
// | ||
// The other reason we only test in us-east-1 is to avoid conflict with our Macie test in the CIS service catalog, which uses | ||
// these same two accounts for similar purposes, but in EU regions. | ||
// See: https://github.com/gruntwork-io/terraform-aws-cis-service-catalog/blob/master/test/security/macie_test.go | ||
func acceptTestInvite(t *testing.T, session *session.Session) { | ||
svc := macie2.New(session) | ||
|
||
// Accept the "standing" invite from our other test account to become a Macie member account | ||
// This works because Macie invites don't expire or get deleted when you disassociate your member account following an invitation | ||
acceptInviteInput := &macie2.AcceptInvitationInput{ | ||
AdministratorAccountId: aws.String("353720269506"), // sandbox | ||
InvitationId: aws.String("18c0febb89142640f07ba497b19bac8e"), // "standing" test invite ID | ||
} | ||
|
||
_, acceptInviteErr := svc.AcceptInvitation(acceptInviteInput) | ||
require.NoError(t, acceptInviteErr) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
package aws | ||
|
||
import ( | ||
"github.com/aws/aws-sdk-go/aws/session" | ||
"github.com/gruntwork-io/go-commons/errors" | ||
) | ||
|
||
type MacieMember struct { | ||
AccountIds []string | ||
} | ||
|
||
func (r MacieMember) ResourceName() string { | ||
return "macie-member" | ||
} | ||
|
||
func (r MacieMember) ResourceIdentifiers() []string { | ||
return r.AccountIds | ||
} | ||
|
||
func (r MacieMember) MaxBatchSize() int { | ||
return 10 | ||
} | ||
|
||
func (r MacieMember) Nuke(session *session.Session, identifiers []string) error { | ||
if err := nukeAllMacieMemberAccounts(session, identifiers); err != nil { | ||
return errors.WithStackTrace(err) | ||
} | ||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
package util | ||
|
||
import ( | ||
"github.com/aws/aws-sdk-go/aws" | ||
"github.com/aws/aws-sdk-go/aws/session" | ||
"github.com/aws/aws-sdk-go/service/sts" | ||
"github.com/gruntwork-io/go-commons/errors" | ||
) | ||
|
||
func GetCurrentAccountId(session *session.Session) (string, error) { | ||
stssvc := sts.New(session) | ||
|
||
input := &sts.GetCallerIdentityInput{} | ||
|
||
output, err := stssvc.GetCallerIdentity(input) | ||
if err != nil { | ||
return "", errors.WithStackTrace(err) | ||
} | ||
|
||
return aws.StringValue(output.Account), nil | ||
} |