Skip to content

Commit

Permalink
Support deletion of KMS Key Aliases - CORE-292 (#383)
Browse files Browse the repository at this point in the history
* Add support for deletion of KMS Key Aliases
  • Loading branch information
arsci authored Dec 14, 2022
1 parent 3ffdc9f commit 9694b5f
Show file tree
Hide file tree
Showing 5 changed files with 74 additions and 21 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ The currently supported functionality includes:
- Inspecting and deleting all CloudWatch Dashboards in an AWS account
- Inspecting and deleting all OpenSearch Domains in an AWS account
- Inspecting and deleting all IAM OpenID Connect Providers
- Inspecting and deleting all Customer managed keys from Key Management Service in an AWS account
- Inspecting and deleting all Customer managed keys (and associated key aliases) from Key Management Service in an AWS account
- Inspecting and deleting all CloudWatch Log Groups in an AWS Account
- Inspecting and deleting all GuardDuty Detectors in an AWS Account
- Inspecting and deleting all Macie member accounts in an AWS account - as long as those accounts were created by Invitation - and not via AWS Organizations
Expand Down
4 changes: 3 additions & 1 deletion aws/aws.go
Original file line number Diff line number Diff line change
Expand Up @@ -861,7 +861,7 @@ func GetAllResources(targetRegions []string, excludeAfter time.Time, resourceTyp
// KMS Customer managed keys
customerKeys := KmsCustomerKeys{}
if IsNukeable(customerKeys.ResourceName(), resourceTypes) {
keys, err := getAllKmsUserKeys(cloudNukeSession, customerKeys.MaxBatchSize(), excludeAfter, configObj)
keys, aliases, err := getAllKmsUserKeys(cloudNukeSession, customerKeys.MaxBatchSize(), excludeAfter, configObj)
if err != nil {
ge := report.GeneralError{
Error: err,
Expand All @@ -871,7 +871,9 @@ func GetAllResources(targetRegions []string, excludeAfter time.Time, resourceTyp
report.RecordError(ge)
}
if len(keys) > 0 {
customerKeys.KeyAliases = aliases
customerKeys.KeyIds = awsgo.StringValueSlice(keys)

resourcesInRegion.Resources = append(resourcesInRegion.Resources, customerKeys)
}

Expand Down
34 changes: 29 additions & 5 deletions aws/kms_customer_key.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,13 @@ import (
"github.com/hashicorp/go-multierror"
)

func getAllKmsUserKeys(session *session.Session, batchSize int, excludeAfter time.Time, configObj config.Config) ([]*string, error) {
func getAllKmsUserKeys(session *session.Session, batchSize int, excludeAfter time.Time, configObj config.Config) ([]*string, map[string][]string, error) {
svc := kms.New(session)
// collect all aliases for each key
keyAliases, err := listKeyAliases(svc, batchSize)
if err != nil {
return nil, errors.WithStackTrace(err)
return nil, nil, errors.WithStackTrace(err)
}

// checking in parallel if keys can be considered for removal
var wg sync.WaitGroup
wg.Add(len(keyAliases))
Expand All @@ -36,6 +35,8 @@ func getAllKmsUserKeys(session *session.Session, batchSize int, excludeAfter tim
wg.Wait()

var kmsIds []*string
aliases := map[string][]string{}

for _, channel := range resultsChan {
result := <-channel
if result.Error != nil {
Expand All @@ -44,10 +45,11 @@ func getAllKmsUserKeys(session *session.Session, batchSize int, excludeAfter tim
continue
}
if result.KeyId != "" {
aliases[result.KeyId] = keyAliases[result.KeyId]
kmsIds = append(kmsIds, &result.KeyId)
}
}
return kmsIds, nil
return kmsIds, aliases, nil
}

// KmsCheckIncludeResult - structure used results of evaluation: not null KeyId - key should be included
Expand Down Expand Up @@ -141,7 +143,7 @@ func listKeyAliases(svc *kms.KMS, batchSize int) (map[string][]string, error) {
return aliases, nil
}

func nukeAllCustomerManagedKmsKeys(session *session.Session, keyIds []*string) error {
func nukeAllCustomerManagedKmsKeys(session *session.Session, keyIds []*string, keyAliases map[string][]string) error {
region := aws.StringValue(session.Config.Region)
if len(keyIds) == 0 {
logging.Logger.Debugf("No Customer Keys to nuke in region %s", region)
Expand All @@ -161,6 +163,13 @@ func nukeAllCustomerManagedKmsKeys(session *session.Session, keyIds []*string) e
}
wg.Wait()

wgAlias := new(sync.WaitGroup)
wgAlias.Add(len(keyAliases))
for _, aliases := range keyAliases {
go deleteAliases(wgAlias, svc, aliases)
}
wgAlias.Wait()

// collect errors from each channel
var allErrs *multierror.Error
for _, errChan := range errChans {
Expand All @@ -172,6 +181,21 @@ func nukeAllCustomerManagedKmsKeys(session *session.Session, keyIds []*string) e
return errors.WithStackTrace(allErrs.ErrorOrNil())
}

func deleteAliases(wg *sync.WaitGroup, svc *kms.KMS, aliases []string) {
defer wg.Done()

for _, aliasName := range aliases {
input := &kms.DeleteAliasInput{AliasName: &aliasName}
_, err := svc.DeleteAlias(input)

if err != nil {
logging.Logger.Errorf("[Failed] Failed deleting alias: %s", aliasName)
} else {
logging.Logger.Debugf("Deleted alias %s", aliasName)
}
}
}

func requestKeyDeletion(wg *sync.WaitGroup, errChan chan error, svc *kms.KMS, key *string) {
defer wg.Done()
input := &kms.ScheduleKeyDeletionInput{KeyId: key, PendingWindowInDays: aws.Int64(int64(kmsRemovalWindow))}
Expand Down
48 changes: 37 additions & 11 deletions aws/kms_customer_key_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,21 +27,23 @@ func TestListKmsUserKeys(t *testing.T) {

aliasName := "cloud-nuke-test-" + util.UniqueID()
keyAlias := fmt.Sprintf("alias/%s", aliasName)
createdKeyId := createKmsCustomerManagedKey(t, session, keyAlias, err)
createdKeyId := createKmsCustomerManagedKey(t, session, keyAlias)

// test if listing of keys will return new key
keys, err := getAllKmsUserKeys(session, KmsCustomerKeys{}.MaxBatchSize(), time.Now(), config.Config{})
// test if listing of keys will return new key and alias
keys, aliases, err := getAllKmsUserKeys(session, KmsCustomerKeys{}.MaxBatchSize(), time.Now(), config.Config{})
require.NoError(t, err)
assert.Contains(t, aws.StringValueSlice(keys), createdKeyId)
assert.Contains(t, aliases[createdKeyId], keyAlias)

// test if time shift works
olderThan := time.Now().Add(-1 * time.Hour)
keys, err = getAllKmsUserKeys(session, KmsCustomerKeys{}.MaxBatchSize(), olderThan, config.Config{})
keys, aliases, err = getAllKmsUserKeys(session, KmsCustomerKeys{}.MaxBatchSize(), olderThan, config.Config{})
require.NoError(t, err)
assert.NotContains(t, aws.StringValueSlice(keys), createdKeyId)
assert.NotContains(t, aliases[createdKeyId], keyAlias)

// test if matching by regexp works
keys, err = getAllKmsUserKeys(session, KmsCustomerKeys{}.MaxBatchSize(), time.Now(), config.Config{
keys, aliases, err = getAllKmsUserKeys(session, KmsCustomerKeys{}.MaxBatchSize(), time.Now(), config.Config{
KMSCustomerKeys: config.ResourceType{
IncludeRule: config.FilterRule{
NamesRegExp: []config.Expression{
Expand All @@ -52,10 +54,11 @@ func TestListKmsUserKeys(t *testing.T) {
})
require.NoError(t, err)
assert.Contains(t, aws.StringValueSlice(keys), createdKeyId)
assert.Contains(t, aliases[createdKeyId], keyAlias)
assert.Equal(t, 1, len(keys))

// test if exclusion by regexp works
keys, err = getAllKmsUserKeys(session, KmsCustomerKeys{}.MaxBatchSize(), time.Now(), config.Config{
keys, aliases, err = getAllKmsUserKeys(session, KmsCustomerKeys{}.MaxBatchSize(), time.Now(), config.Config{
KMSCustomerKeys: config.ResourceType{
ExcludeRule: config.FilterRule{
NamesRegExp: []config.Expression{
Expand All @@ -66,6 +69,7 @@ func TestListKmsUserKeys(t *testing.T) {
})
require.NoError(t, err)
assert.NotContains(t, aws.StringValueSlice(keys), createdKeyId)
assert.NotContains(t, aliases[createdKeyId], keyAlias)
}

func TestRemoveKmsUserKeys(t *testing.T) {
Expand All @@ -78,18 +82,26 @@ func TestRemoveKmsUserKeys(t *testing.T) {
require.NoError(t, err)

keyAlias := "alias/cloud-nuke-test-" + util.UniqueID()
createdKeyId := createKmsCustomerManagedKey(t, session, keyAlias, err)
createdKeyId := createKmsCustomerManagedKey(t, session, keyAlias)

err = nukeAllCustomerManagedKmsKeys(session, []*string{&createdKeyId})
err = nukeAllCustomerManagedKmsKeys(session, []*string{&createdKeyId}, map[string][]string{"keyid": {keyAlias}})
require.NoError(t, err)

// test if key is not included for removal second time
keys, err := getAllKmsUserKeys(session, KmsCustomerKeys{}.MaxBatchSize(), time.Now(), config.Config{})
// test if key is not included for removal second time, after being marked for deletion
keys, aliases, err := getAllKmsUserKeys(session, KmsCustomerKeys{}.MaxBatchSize(), time.Now(), config.Config{})

require.NoError(t, err)
assert.NotContains(t, aws.StringValueSlice(keys), createdKeyId)
assert.NotContains(t, aliases[createdKeyId], keyAlias)

// test that all aliases were deleted from the key, even if the key was successfully marked for deletion
listedAliases, err := listAliasesForKey(session, &createdKeyId)

require.NoError(t, err)
assert.Empty(t, listedAliases)
}

func createKmsCustomerManagedKey(t *testing.T, session *session.Session, alias string, err error) string {
func createKmsCustomerManagedKey(t *testing.T, session *session.Session, alias string) string {
svc := kms.New(session)
input := &kms.CreateKeyInput{}
result, err := svc.CreateKey(input)
Expand All @@ -102,3 +114,17 @@ func createKmsCustomerManagedKey(t *testing.T, session *session.Session, alias s

return createdKeyId
}

func listAliasesForKey(session *session.Session, keyId *string) ([]string, error) {
svc := kms.New(session)
input := &kms.ListAliasesInput{KeyId: keyId}
result, err := svc.ListAliases(input)

aliases := make([]string, 0)

for _, alias := range result.Aliases {
aliases = append(aliases, *alias.TargetKeyId)
}

return aliases, err
}
7 changes: 4 additions & 3 deletions aws/kms_customer_key_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,16 @@ import (
const kmsRemovalWindow = 7

type KmsCustomerKeys struct {
KeyIds []string
KeyIds []string
KeyAliases map[string][]string
}

// ResourceName - the simple name of the aws resource
func (c KmsCustomerKeys) ResourceName() string {
return "kmscustomerkeys"
}

// ResourceIdentifiers - The IAM UserNames
// ResourceIdentifiers - The KMS Key IDs
func (c KmsCustomerKeys) ResourceIdentifiers() []string {
return c.KeyIds
}
Expand All @@ -31,7 +32,7 @@ func (r KmsCustomerKeys) MaxBatchSize() int {

// Nuke - remove all customer managed keys
func (c KmsCustomerKeys) Nuke(session *session.Session, keyIds []string) error {
if err := nukeAllCustomerManagedKmsKeys(session, awsgo.StringSlice(keyIds)); err != nil {
if err := nukeAllCustomerManagedKmsKeys(session, awsgo.StringSlice(keyIds), c.KeyAliases); err != nil {
return errors.WithStackTrace(err)
}

Expand Down

0 comments on commit 9694b5f

Please sign in to comment.