Skip to content

Commit

Permalink
Implement secrets pkg: backend and filedriver
Browse files Browse the repository at this point in the history
This is the implementation of the backend of secrets. pkg/secrets takes a secret name and data and does these operations on that secret data:  store, delete, lookup, and list, using a secretsmanager.

The first driver implemented here is a filedriver - where the data is stored unencrypted on disk in a file.

The secrets package can be easily expanded to use more drivers as the package implements an interface to accept different drivers

Signed-off-by: Ashley Cui <[email protected]>
  • Loading branch information
ashley-cui committed Jan 5, 2021
1 parent f6c7caf commit 13ac506
Show file tree
Hide file tree
Showing 5 changed files with 907 additions and 0 deletions.
153 changes: 153 additions & 0 deletions pkg/secrets/filedriver/filedriver.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
package filedriver

import (
"encoding/json"
"io/ioutil"
"os"
"path/filepath"
"sort"

"github.com/containers/storage/pkg/lockfile"
"github.com/pkg/errors"
)

// secretsDataFile is the file where secrets data/payload will be stored
var secretsDataFile = "secretsdata.json"

// ErrNoSecretData indicates that there is not data associated with an id
var ErrNoSecretData = errors.New("no secret data with ID")

// ErrNoSecretData indicates that there is secret data already associated with an id
var ErrSecretIDExists = errors.New("secret data with ID already exists")

// Driver is the filedriver object
type Driver struct {
// secretsDataFilePath is the path to the secretsfile
secretsDataFilePath string
// lockfile is the filedriver lockfile
lockfile lockfile.Locker
}

// NewDriver creates a new file driver.
// rootPath is the directory where the secrets data file resides.
func NewDriver(rootPath string) (*Driver, error) {
fileDriver := new(Driver)
fileDriver.secretsDataFilePath = filepath.Join(rootPath, secretsDataFile)
lock, err := lockfile.GetLockfile(filepath.Join(rootPath, "secretsdata.lock"))
if err != nil {
return nil, err
}
fileDriver.lockfile = lock

return fileDriver, nil
}

// List returns all secret IDs
func (d *Driver) List() ([]string, error) {
d.lockfile.Lock()
defer d.lockfile.Unlock()
secretData, err := d.getAllData()
if err != nil {
return nil, err
}
var allID []string
for k := range secretData {
allID = append(allID, k)
}
sort.Strings(allID)
return allID, err
}

// Lookup returns the bytes associated with a secret ID
func (d *Driver) Lookup(id string) ([]byte, error) {
d.lockfile.Lock()
defer d.lockfile.Unlock()

secretData, err := d.getAllData()
if err != nil {
return nil, err
}
if data, ok := secretData[id]; ok {
return data, nil
}
return nil, errors.Wrapf(ErrNoSecretData, "%s", id)
}

// Store stores the bytes associated with an ID. An error is returned if the ID arleady exists
func (d *Driver) Store(id string, data []byte) error {
d.lockfile.Lock()
defer d.lockfile.Unlock()

secretData, err := d.getAllData()
if err != nil {
return err
}
if _, ok := secretData[id]; ok {
return errors.Wrapf(ErrSecretIDExists, "%s", id)
}
secretData[id] = data
marshalled, err := json.MarshalIndent(secretData, "", " ")
if err != nil {
return err
}
err = ioutil.WriteFile(d.secretsDataFilePath, marshalled, 0600)
if err != nil {
return err
}
return nil
}

// Delete deletes the secret associated with the specified ID. An error is returned if no matching secret is found.
func (d *Driver) Delete(id string) error {
d.lockfile.Lock()
defer d.lockfile.Unlock()
secretData, err := d.getAllData()
if err != nil {
return err
}
if _, ok := secretData[id]; ok {
delete(secretData, id)
} else {
return errors.Wrap(ErrNoSecretData, id)
}
marshalled, err := json.MarshalIndent(secretData, "", " ")
if err != nil {
return err
}
err = ioutil.WriteFile(d.secretsDataFilePath, marshalled, 0600)
if err != nil {
return err
}
return nil
}

// getAllData reads the data file and returns all data
func (d *Driver) getAllData() (map[string][]byte, error) {
// check if the db file exists
_, err := os.Stat(d.secretsDataFilePath)
if err != nil {
if os.IsNotExist(err) {
// the file will be created later on a store()
return make(map[string][]byte), nil
} else {
return nil, err
}
}

file, err := os.Open(d.secretsDataFilePath)
if err != nil {
return nil, err
}
defer file.Close()

byteValue, err := ioutil.ReadAll(file)
if err != nil {
return nil, err
}
secretData := new(map[string][]byte)
err = json.Unmarshal([]byte(byteValue), secretData)
if err != nil {
return nil, err
}
return *secretData, nil
}
89 changes: 89 additions & 0 deletions pkg/secrets/filedriver/filedriver_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
package filedriver

import (
"io/ioutil"
"os"
"testing"

"github.com/stretchr/testify/require"
)

func setup() (*Driver, error) {
tmppath, err := ioutil.TempDir("", "secretsdata")
if err != nil {
return nil, err
}
return NewDriver(tmppath)
}

func TestStoreAndLookupSecretData(t *testing.T) {
tstdriver, err := setup()
require.NoError(t, err)
defer os.Remove(tstdriver.secretsDataFilePath)

err = tstdriver.Store("unique_id", []byte("somedata"))
require.NoError(t, err)

secretData, err := tstdriver.Lookup("unique_id")
require.NoError(t, err)
require.Equal(t, secretData, []byte("somedata"))
}

func TestStoreDupID(t *testing.T) {
tstdriver, err := setup()
require.NoError(t, err)
defer os.Remove(tstdriver.secretsDataFilePath)

err = tstdriver.Store("unique_id", []byte("somedata"))
require.NoError(t, err)

err = tstdriver.Store("unique_id", []byte("somedata"))
require.Error(t, err)
}

func TestLookupBogus(t *testing.T) {
tstdriver, err := setup()
require.NoError(t, err)
defer os.Remove(tstdriver.secretsDataFilePath)

_, err = tstdriver.Lookup("bogus")
require.Error(t, err)
}

func TestDeleteSecretData(t *testing.T) {
tstdriver, err := setup()
require.NoError(t, err)
defer os.Remove(tstdriver.secretsDataFilePath)

err = tstdriver.Store("unique_id", []byte("somedata"))
require.NoError(t, err)
err = tstdriver.Delete("unique_id")
require.NoError(t, err)
data, err := tstdriver.Lookup("unique_id")
require.Error(t, err)
require.Nil(t, data)
}

func TestDeleteSecretDataNotExist(t *testing.T) {
tstdriver, err := setup()
require.NoError(t, err)
defer os.Remove(tstdriver.secretsDataFilePath)

err = tstdriver.Delete("bogus")
require.Error(t, err)
}

func TestList(t *testing.T) {
tstdriver, err := setup()
require.NoError(t, err)
defer os.Remove(tstdriver.secretsDataFilePath)

err = tstdriver.Store("unique_id", []byte("somedata"))
require.NoError(t, err)
err = tstdriver.Store("unique_id2", []byte("moredata"))
require.NoError(t, err)

data, err := tstdriver.List()
require.NoError(t, err)
require.Len(t, data, 2)
}
Loading

0 comments on commit 13ac506

Please sign in to comment.