Skip to content

Commit

Permalink
Merge pull request #260 from ekristen/kms-tweaks
Browse files Browse the repository at this point in the history
feat(kms): continue processing keys if error encountered
  • Loading branch information
ekristen authored Aug 27, 2024
2 parents e6ff5b2 + 186439e commit dec0c45
Show file tree
Hide file tree
Showing 10 changed files with 3,471 additions and 153 deletions.
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,10 @@ require (
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/stevenle/topsort v0.2.0 // indirect
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect
golang.org/x/mod v0.17.0 // indirect
golang.org/x/sync v0.8.0 // indirect
golang.org/x/sys v0.20.0 // indirect
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
)
4 changes: 4 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,8 @@ go.uber.org/ratelimit v0.3.1/go.mod h1:6euWsTB6U/Nb3X++xEUXA8ciPJvr19Q/0h1+oDcJh
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
Expand Down Expand Up @@ -97,6 +99,8 @@ golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
Expand Down
2,951 changes: 2,951 additions & 0 deletions mocks/mock_kmsiface/mock.go

Large diffs are not rendered by default.

43 changes: 23 additions & 20 deletions resources/kms-aliases.go → resources/kms-alias.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@ package resources

import (
"context"

"fmt"
"strings"

"github.com/aws/aws-sdk-go/service/kms"
"github.com/aws/aws-sdk-go/service/kms/kmsiface"

"github.com/ekristen/libnuke/pkg/registry"
"github.com/ekristen/libnuke/pkg/resource"
Expand All @@ -25,19 +25,26 @@ func init() {
})
}

type KMSAliasLister struct{}
type KMSAliasLister struct {
mockSvc kmsiface.KMSAPI
}

func (l *KMSAliasLister) List(_ context.Context, o interface{}) ([]resource.Resource, error) {
opts := o.(*nuke.ListerOpts)
resources := make([]resource.Resource, 0)

svc := kms.New(opts.Session)
var svc kmsiface.KMSAPI
if l.mockSvc != nil {
svc = l.mockSvc
} else {
svc = kms.New(opts.Session)
}

resources := make([]resource.Resource, 0)
err := svc.ListAliasesPages(nil, func(page *kms.ListAliasesOutput, lastPage bool) bool {
for _, alias := range page.Aliases {
resources = append(resources, &KMSAlias{
svc: svc,
name: alias.AliasName,
Name: alias.AliasName,
})
}
return true
Expand All @@ -50,32 +57,28 @@ func (l *KMSAliasLister) List(_ context.Context, o interface{}) ([]resource.Reso
}

type KMSAlias struct {
svc *kms.KMS
name *string
svc kmsiface.KMSAPI
Name *string
}

func (e *KMSAlias) Filter() error {
if strings.HasPrefix(*e.name, "alias/aws/") {
func (r *KMSAlias) Filter() error {
if strings.HasPrefix(*r.Name, "alias/aws/") {
return fmt.Errorf("cannot delete AWS alias")
}
return nil
}

func (e *KMSAlias) Remove(_ context.Context) error {
_, err := e.svc.DeleteAlias(&kms.DeleteAliasInput{
AliasName: e.name,
func (r *KMSAlias) Remove(_ context.Context) error {
_, err := r.svc.DeleteAlias(&kms.DeleteAliasInput{
AliasName: r.Name,
})
return err
}

func (e *KMSAlias) String() string {
return *e.name
func (r *KMSAlias) String() string {
return *r.Name
}

func (e *KMSAlias) Properties() types.Properties {
properties := types.NewProperties()
properties.
Set("Name", e.name)

return properties
func (r *KMSAlias) Properties() types.Properties {
return types.NewPropertiesFromStruct(r)
}
125 changes: 125 additions & 0 deletions resources/kms-alias_mock_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
package resources

import (
"context"
"testing"

"github.com/golang/mock/gomock"
"github.com/gotidy/ptr"
"github.com/stretchr/testify/assert"

"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/awserr"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/kms"

"github.com/ekristen/aws-nuke/v3/mocks/mock_kmsiface"
"github.com/ekristen/aws-nuke/v3/pkg/nuke"
)

func Test_Mock_KMSAlias_List(t *testing.T) {
a := assert.New(t)
ctrl := gomock.NewController(t)
defer ctrl.Finish()

mockKMS := mock_kmsiface.NewMockKMSAPI(ctrl)

mockKMS.EXPECT().ListAliasesPages(gomock.Any(), gomock.Any()).DoAndReturn(
func(input *kms.ListAliasesInput, fn func(*kms.ListAliasesOutput, bool) bool) error {
fn(&kms.ListAliasesOutput{
Aliases: []*kms.AliasListEntry{
{AliasName: aws.String("alias/test-alias-1")},
{AliasName: aws.String("alias/test-alias-2")},
},
}, true)
return nil
},
)

lister := KMSAliasLister{
mockSvc: mockKMS,
}

resources, err := lister.List(context.TODO(), &nuke.ListerOpts{
Region: &nuke.Region{
Name: "us-east-2",
},
Session: session.Must(session.NewSession()),
})
a.NoError(err)
a.Len(resources, 2)
}

func Test_Mock_KMSAlias_List_Error(t *testing.T) {
a := assert.New(t)
ctrl := gomock.NewController(t)
defer ctrl.Finish()

mockKMS := mock_kmsiface.NewMockKMSAPI(ctrl)

mockKMS.EXPECT().
ListAliasesPages(gomock.Any(), gomock.Any()).
Return(awserr.New("BadRequest", "400 Bad Request", nil))

lister := KMSAliasLister{
mockSvc: mockKMS,
}

resources, err := lister.List(context.TODO(), &nuke.ListerOpts{
Region: &nuke.Region{
Name: "us-east-2",
},
Session: session.Must(session.NewSession()),
})
a.Error(err)
a.Nil(resources)
a.EqualError(err, "BadRequest: 400 Bad Request")
}

func Test_KMSAlias_Filter(t *testing.T) {
a := assert.New(t)

alias := &KMSAlias{
Name: ptr.String("alias/aws/test-alias"),
}

err := alias.Filter()
a.Error(err)
a.EqualError(err, "cannot delete AWS alias")

alias.Name = ptr.String("alias/custom/test-alias")
err = alias.Filter()
a.NoError(err)
}

func Test_KMSAlias_Properties(t *testing.T) {
a := assert.New(t)

alias := &KMSAlias{
Name: ptr.String("alias/custom/test-alias"),
}

a.Equal("alias/custom/test-alias", alias.String())
a.Equal("alias/custom/test-alias", alias.Properties().Get("Name"))
}

func Test_Mock_KMSAlias_Remove(t *testing.T) {
a := assert.New(t)
ctrl := gomock.NewController(t)
defer ctrl.Finish()

mockKMS := mock_kmsiface.NewMockKMSAPI(ctrl)

// Mock the DeleteAlias method
mockKMS.EXPECT().DeleteAlias(&kms.DeleteAliasInput{
AliasName: ptr.String("alias/test-alias-1"),
}).Return(&kms.DeleteAliasOutput{}, nil)

alias := &KMSAlias{
svc: mockKMS,
Name: ptr.String("alias/test-alias-1"),
}

err := alias.Remove(context.TODO())
a.NoError(err)
}
137 changes: 137 additions & 0 deletions resources/kms-key.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
package resources

import (
"context"
"errors"
"fmt"

"github.com/gotidy/ptr"
"github.com/sirupsen/logrus"

"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/awserr"
"github.com/aws/aws-sdk-go/service/kms"
"github.com/aws/aws-sdk-go/service/kms/kmsiface"

"github.com/ekristen/libnuke/pkg/registry"
"github.com/ekristen/libnuke/pkg/resource"
"github.com/ekristen/libnuke/pkg/types"

"github.com/ekristen/aws-nuke/v3/pkg/nuke"
)

const KMSKeyResource = "KMSKey"

func init() {
registry.Register(&registry.Registration{
Name: KMSKeyResource,
Scope: nuke.Account,
Lister: &KMSKeyLister{},
DependsOn: []string{
KMSAliasResource,
},
})
}

type KMSKeyLister struct {
mockSvc kmsiface.KMSAPI
}

func (l *KMSKeyLister) List(_ context.Context, o interface{}) ([]resource.Resource, error) {
opts := o.(*nuke.ListerOpts)
resources := make([]resource.Resource, 0)

var svc kmsiface.KMSAPI
if l.mockSvc != nil {
svc = l.mockSvc
} else {
svc = kms.New(opts.Session)
}

inaccessibleKeys := false

if err := svc.ListKeysPages(nil, func(keysOut *kms.ListKeysOutput, lastPage bool) bool {
for _, key := range keysOut.Keys {
resp, err := svc.DescribeKey(&kms.DescribeKeyInput{
KeyId: key.KeyId,
})
if err != nil {
var awsError awserr.Error
if errors.As(err, &awsError) {
if awsError.Code() == "AccessDeniedException" {
inaccessibleKeys = true
logrus.WithError(err).Debug("unable to describe key")
continue
}
}

logrus.WithError(err).Error("unable to describe key")
continue
}

kmsKey := &KMSKey{
svc: svc,
ID: resp.KeyMetadata.KeyId,
State: resp.KeyMetadata.KeyState,
Manager: resp.KeyMetadata.KeyManager,
}

tags, err := svc.ListResourceTags(&kms.ListResourceTagsInput{
KeyId: key.KeyId,
})
if err != nil {
logrus.WithError(err).Error("unable to list tags")
} else {
kmsKey.Tags = tags.Tags
}

resources = append(resources, kmsKey)
}

return !lastPage
}); err != nil {
return nil, err
}

if inaccessibleKeys {
logrus.Warn("one or more KMS keys were inaccessible, debug logging will contain more information")
}

return resources, nil
}

type KMSKey struct {
svc kmsiface.KMSAPI
ID *string
State *string
Manager *string
Tags []*kms.Tag
}

func (r *KMSKey) Filter() error {
if ptr.ToString(r.State) == kms.KeyStatePendingDeletion {
return fmt.Errorf("is already in PendingDeletion state")
}

if ptr.ToString(r.Manager) == kms.KeyManagerTypeAws {
return fmt.Errorf("cannot delete AWS managed key")
}

return nil
}

func (r *KMSKey) Remove(_ context.Context) error {
_, err := r.svc.ScheduleKeyDeletion(&kms.ScheduleKeyDeletionInput{
KeyId: r.ID,
PendingWindowInDays: aws.Int64(7),
})
return err
}

func (r *KMSKey) String() string {
return *r.ID
}

func (r *KMSKey) Properties() types.Properties {
return types.NewPropertiesFromStruct(r)
}
Loading

0 comments on commit dec0c45

Please sign in to comment.