Skip to content

Commit

Permalink
Merge pull request #201 from AirHelp/feat/add_delete_operation
Browse files Browse the repository at this point in the history
Add delete option to allow keys removal
  • Loading branch information
lukaszkowalczyk98 authored Nov 22, 2023
2 parents 131cc7b + 363e043 commit 750d28b
Show file tree
Hide file tree
Showing 13 changed files with 204 additions and 7 deletions.
15 changes: 13 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ Treasury is a very simple tool for managing secrets. It uses Amazon S3 or SSM ([
- [Write file content](#write-file-content)
- [Read secret](#read-secret)
- [List secrets](#list-secrets)
- [Delete secret](#delete-secret)
- [Import secrets](#import-secrets)
- [Export secrets](#export-secrets)
- [Teamplate usage](#teamplate-usage)
Expand Down Expand Up @@ -151,6 +152,14 @@ development/application/app_api_pass
development/application/test
```

### Delete secret
Delete the secret from treasury

```
> treasury delete development/application/secret_key
Key development/application/secret_key has been successfully deleted
```

### Import secrets
Assuming properties file `./secrets.env` with content:
```bash
Expand Down Expand Up @@ -325,7 +334,8 @@ key4: secret4
"Effect": "Allow",
"Action": [
"s3:PutObject*",
"s3:GetObject*"
"s3:GetObject*",
"s3:DeleteObject*"
],
"Resource": [
"arn:aws:s3:::TREASURY_S3_BUCKET_NAME/test/test/*",
Expand Down Expand Up @@ -472,7 +482,8 @@ The following bucket policy denies upload object (s3:PutObject) permission to ev
"Effect": "Allow",
"Action": [
"ssm:GetParameter*",
"ssm:PutParameter"
"ssm:PutParameter",
"ssm:DeleteParameter"
],
"Resource": [
"arn:aws:ssm:eu-west-1:064764542321:parameter/development/application/*"
Expand Down
1 change: 1 addition & 0 deletions backend/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (

type BackendAPI interface {
PutObject(*types.PutObjectInput) error
DeleteObject(*types.DeleteObjectInput) error
GetObject(*types.GetObjectInput) (*types.GetObjectOutput, error)
GetObjects(*types.GetObjectsInput) (*types.GetObjectsOuput, error)
}
8 changes: 8 additions & 0 deletions backend/s3/s3.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,3 +94,11 @@ func (c *Client) GetObjects(object *types.GetObjectsInput) (*types.GetObjectsOup
}
return &types.GetObjectsOuput{Secrets: keyValuePairs}, nil
}
func (c *Client) DeleteObject(object *types.DeleteObjectInput) error {
params := &s3.DeleteObjectInput{
Bucket: aws.String(c.bucket),
Key: aws.String(object.Key),
}
_, err := c.S3Svc.DeleteObject(params)
return err
}
10 changes: 10 additions & 0 deletions backend/ssm/ssm.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,3 +99,13 @@ func unSlash(input string) string {
}
return input
}

func (c *Client) DeleteObject(object *types.DeleteObjectInput) error {
params := &ssm.DeleteParameterInput{
// we decided to use path based keys without `/` at the begining
// so we need to add it here
Name: aws.String("/" + object.Key),
}
_, err := c.svc.DeleteParameter(params)
return err
}
23 changes: 23 additions & 0 deletions client/delete.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package client

import (
"github.com/AirHelp/treasury/types"
"github.com/AirHelp/treasury/utils"
)

// Delete removeds specified secret for given key
func (c *Client) Delete(key string) error {
if err := utils.ValidateInputKey(key); err != nil {
return err
}

secretObject := &types.DeleteObjectInput{
Key: key,
}

if err := c.Backend.DeleteObject(secretObject); err != nil {
return err
}

return nil
}
39 changes: 39 additions & 0 deletions client/delete_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package client_test

import (
"testing"

"github.com/AirHelp/treasury/client"
test "github.com/AirHelp/treasury/test/backend"
)

func TestDelete(t *testing.T) {

dummyClientOptions := &client.Options{
Backend: &test.MockBackendClient{},
S3BucketName: "fake_s3_bucket",
}

treasury, err := client.New(dummyClientOptions)
if err != nil {
t.Error(err)
}

err = treasury.Delete(test.Key9)
if err != nil {
t.Error(err)
}

// Check whether the key was deleted
got, err := treasury.ReadValue(test.Key9)

if err == nil {
t.Errorf("Client.ReadValue() returned nil value for error when there should be one ")
return
}

if got != "" {
t.Errorf("Client.ReadValue() returned non empty string for deleted key")
return
}
}
27 changes: 24 additions & 3 deletions client/template_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ import (
)

const (
templateTestSourceFile = "../test/resources/source.secret.tpl"
templateTestSourceFile = "../test/resources/source.existing_secret.tpl"
templateTestSourceFile2 = "../test/resources/source.not_existing_secret.tpl"
templateTestDestinationFile = "../test/output/destination.secret"
templateTestDestinationParentDir = "../test/output"
)
Expand All @@ -29,8 +30,28 @@ func TestTemplate(t *testing.T) {
"Name": "some_testing_template",
}

if err := treasury.Template(templateTestSourceFile, templateTestDestinationFile, 0, map[string]string{}, envMap); err != nil {
t.Error("Could not generate secret file from template. Error: ", err.Error())
tests := []struct {
file string
wantErr bool
}{
{
file: templateTestSourceFile,
wantErr: false,
},
{
file: templateTestSourceFile2,
wantErr: true,
},
}

for _, tt := range tests {
t.Run(tt.file, func(t *testing.T) {
err := treasury.Template(tt.file, templateTestDestinationFile, 0, map[string]string{}, envMap)
if (err != nil) != tt.wantErr {
t.Errorf("Failed to use treasury template, error = %v, wantErr %v", err, tt.wantErr)
return
}
})
}

_, err = os.Stat(templateTestDestinationParentDir)
Expand Down
59 changes: 59 additions & 0 deletions cmd/delete.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// Copyright © 2016 AirHelp Inc. devops@airhelp.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package cmd

import (
"errors"
"fmt"

"github.com/AirHelp/treasury/client"
"github.com/spf13/cobra"
)

// deleteCmd represents the delete command
var deleteCmd = &cobra.Command{
Use: "delete KEY",
Short: "Remove secrets from Treasury",
Long: `Remove secret with the given key from Treasury.`,
RunE: delete,
}

func init() {
RootCmd.AddCommand(deleteCmd)
deleteCmd.SuggestFor = []string{"remove"}
}

func delete(cmd *cobra.Command, args []string) error {
if len(args) != 1 {
return errors.New("Missing Key to delete.")
}
key := args[0]

treasury, err := client.New(&client.Options{
Region: s3Region,
S3BucketName: treasuryS3,
})
if err != nil {
return err
}

err = treasury.Delete(key)
if err != nil {
return err
}

fmt.Printf("Key %s has been successfully deleted\n", key)
return nil
}
20 changes: 20 additions & 0 deletions test/backend/test.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ const (
Key8 = "test/aircom/NEW_RELIC_LICENSE_KEY"
Key8NoEnv = "aircom/NEW_RELIC_LICENSE_KEY"
ShortKey8 = "NEW_RELIC_LICENSE_KEY"
Key9 = "test/slack/SLACK_TOKEN"
Key9NoEnv = "slack/SLACK_TOKEN"
ShortKey9 = "SLACK_TOKEN"
)

var KeyValueMap = map[string]string{
Expand All @@ -45,6 +48,7 @@ var KeyValueMap = map[string]string{
Key6: "2oui3yrwohsf",
Key7: "weoirgfhdh",
Key8: "sfjsoidhgi340j",
Key9: "sdfjksdfjksdf",
}

// MockBackendClient fake backendAPI
Expand All @@ -60,6 +64,12 @@ func (m *MockBackendClient) PutObject(input *types.PutObjectInput) error {
}

func (m *MockBackendClient) GetObject(input *types.GetObjectInput) (*types.GetObjectOutput, error) {
if _, ok := KeyValueMap[input.Key]; !ok {
return &types.GetObjectOutput{
Value: "",
}, fmt.Errorf("Missing key:%s in KeyValue map", input.Key)
}

return &types.GetObjectOutput{
Value: KeyValueMap[input.Key],
}, nil
Expand All @@ -74,3 +84,13 @@ func (m *MockBackendClient) GetObjects(input *types.GetObjectsInput) (*types.Get
}
return &types.GetObjectsOuput{Secrets: response}, nil
}

func (m *MockBackendClient) DeleteObject(input *types.DeleteObjectInput) error {
if _, ok := KeyValueMap[input.Key]; !ok {
return errors.New(fmt.Sprintf("Missing key:%s in KeyValue map", input.Key))
}

delete(KeyValueMap, input.Key)

return nil
}
2 changes: 2 additions & 0 deletions test/resources/source.existing_secret.tpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
APPLICATION_SECRET_KEY={{ read "test/webapp/user_api_pass" }}
NAME={{ .Name }}
Original file line number Diff line number Diff line change
@@ -1,3 +1,2 @@
APPLICATION_SECRET_KEY={{ read "test/webapp/user_api_pass" }}
APPLICATION_NON_EXISTING_KEY={{ read "test/none/user_api_pass" }}
NAME={{ .Name }}
4 changes: 4 additions & 0 deletions types/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,7 @@ type GetObjectOutput struct {
type GetObjectsOuput struct {
Secrets map[string]string
}

type DeleteObjectInput struct {
Key string
}
2 changes: 1 addition & 1 deletion version/version.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import (
)

// treasury version should be changed here
const version = "v0.11.0"
const version = "v0.12.0"

// This will be filled in by the compiler.
var (
Expand Down

0 comments on commit 750d28b

Please sign in to comment.