Skip to content

Commit

Permalink
Merge pull request #40 from thingful/delete-entries
Browse files Browse the repository at this point in the history
Delete entries
  • Loading branch information
dfuentes authored Nov 27, 2017
2 parents a6d49c6 + b33853b commit 500da44
Show file tree
Hide file tree
Showing 5 changed files with 116 additions and 5 deletions.
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,16 @@ that is unneeded. Parameter store automatically versions secrets and passing
the `--version/-v` flag to read can print older versions of the secret. Default
version (-1) is the latest secret.


### Deleting
```bash
$ chamber delete service key
```

`delete` provides the ability to remove a secret from chamber permanently,
including the secret's additional metadata. There is no way to recover a
secret once it has been deleted so care should be taken with this command.

### AWS Region

Chamber uses [AWS SDK for Go](https://github.com/aws/aws-sdk-go). To use a
Expand Down
47 changes: 47 additions & 0 deletions cmd/delete.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package cmd

import (
"strings"

"github.com/pkg/errors"
"github.com/segmentio/chamber/store"
"github.com/spf13/cobra"
)

// deleteCmd represents the delete command
var deleteCmd = &cobra.Command{
Use: "delete <service> <key>",
Short: "Delete a secret, including all versions",
RunE: delete,
}

func init() {
RootCmd.AddCommand(deleteCmd)
}

func delete(cmd *cobra.Command, args []string) error {
if len(args) < 2 {
return ErrTooFewArguments
}
if len(args) > 2 {
return ErrTooManyArguments
}

service := strings.ToLower(args[0])
if err := validateService(service); err != nil {
return errors.Wrap(err, "Failed to validate service")
}

key := strings.ToLower(args[1])
if err := validateKey(key); err != nil {
return errors.Wrap(err, "Failed to validate key")
}

secretStore := store.NewSSMStore(numRetries)
secretId := store.SecretId{
Service: service,
Key: key,
}

return secretStore.Delete(secretId)
}
30 changes: 26 additions & 4 deletions store/ssmstore.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,17 @@ const (
// when using paths
var validPathKeyFormat = regexp.MustCompile(`^\/[A-Za-z0-9-_]+\/[A-Za-z0-9-_]+$`)


// validKeyFormat is the format that is expected for key names inside parameter store when
// not using paths
var validKeyFormat = regexp.MustCompile(`^[A-Za-z0-9-_]+\.[A-Za-z0-9-_]+$`)

// ensure SSMStore confirms to Store interface
var _ Store = &SSMStore{}

// SSMStore implements the Store interface for storing secrets in SSM Parameter
// Store
type SSMStore struct {
svc ssmiface.SSMAPI
svc ssmiface.SSMAPI
usePaths bool
}

Expand All @@ -57,7 +59,7 @@ func NewSSMStore(numRetries int) *SSMStore {
}

return &SSMStore{
svc: svc,
svc: svc,
usePaths: usePaths,
}
}
Expand Down Expand Up @@ -115,8 +117,28 @@ func (s *SSMStore) Read(id SecretId, version int) (Secret, error) {
return s.readVersion(id, version)
}

func (s *SSMStore) readVersion(id SecretId, version int) (Secret, error) {
// Delete removes a secret from the parameter store. Note this removes all
// versions of the secret.
func (s *SSMStore) Delete(id SecretId) error {
// first read to ensure parameter present
_, err := s.Read(id, -1)
if err != nil {
return err
}

deleteParameterInput := &ssm.DeleteParameterInput{
Name: aws.String(s.idToName(id)),
}

_, err = s.svc.DeleteParameter(deleteParameterInput)
if err != nil {
return err
}

return nil
}

func (s *SSMStore) readVersion(id SecretId, version int) (Secret, error) {
getParameterHistoryInput := &ssm.GetParameterHistoryInput{
Name: aws.String(s.idToName(id)),
WithDecryption: aws.Bool(true),
Expand Down
33 changes: 32 additions & 1 deletion store/ssmstore_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,17 @@ func (m *mockSSMClient) DescribeParametersPages(i *ssm.DescribeParametersInput,
return nil
}

func (m *mockSSMClient) DeleteParameter(i *ssm.DeleteParameterInput) (*ssm.DeleteParameterOutput, error) {
_, ok := m.parameters[*i.Name]
if !ok {
return &ssm.DeleteParameterOutput{}, errors.New("secret not found")
}

delete(m.parameters, *i.Name)

return &ssm.DeleteParameterOutput{}, nil
}

func paramNameInSlice(name *string, slice []*string) bool {
for _, val := range slice {
if *val == *name {
Expand Down Expand Up @@ -389,7 +400,7 @@ func TestHistory(t *testing.T) {

func NewTestSSMStoreWithPaths(mock ssmiface.SSMAPI) *SSMStore {
return &SSMStore{
svc: mock,
svc: mock,
usePaths: true,
}
}
Expand Down Expand Up @@ -547,6 +558,26 @@ func TestHistoryPaths(t *testing.T) {
})
}

func TestDelete(t *testing.T) {
mock := &mockSSMClient{parameters: map[string]mockParameter{}}
store := NewTestSSMStore(mock)

secretId := SecretId{Service: "test", Key: "key"}
store.Write(secretId, "value")

t.Run("Deleting secret should work", func(t *testing.T) {
err := store.Delete(secretId)
assert.Nil(t, err)
err = store.Delete(secretId)
assert.Equal(t, ErrSecretNotFound, err)
})

t.Run("Deleting missing secret should fail", func(t *testing.T) {
err := store.Delete(SecretId{Service: "test", Key: "nonkey"})
assert.Equal(t, ErrSecretNotFound, err)
})
}

type ByKey []Secret

func (a ByKey) Len() int { return len(a) }
Expand Down
1 change: 1 addition & 0 deletions store/store.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,4 +57,5 @@ type Store interface {
Read(id SecretId, version int) (Secret, error)
List(service string, includeValues bool) ([]Secret, error)
History(id SecretId) ([]ChangeEvent, error)
Delete(id SecretId) error
}

0 comments on commit 500da44

Please sign in to comment.