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 operations on that secret data, most namely 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 Dec 7, 2020
1 parent 1e40eff commit e41ce1e
Show file tree
Hide file tree
Showing 6 changed files with 903 additions and 0 deletions.
164 changes: 164 additions & 0 deletions pkg/secrets/filedriver/filedriver.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
package filedriver

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

"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 assocaited 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")

// fileDriver is the filedriver object
type FileDriver struct {
// filePath is the path to the secretsfile
secretsDataFilePath string
// drivertype is the string representation of a filedriver
drivertype string
// lockfile is the filedriver lockfile
lockfile lockfile.Locker
// secretData is an in-memory rep of secrets data
secretData map[string][]byte
// lastModified is the time when the database was last modified in memory
lastModified time.Time
}

//NewFileDriver creates a new file driver
func NewFileDriver(dirPath string) (*FileDriver, error) {
fileDriver := new(FileDriver)
fileDriver.secretsDataFilePath = filepath.Join(dirPath, secretsDataFile)
fileDriver.drivertype = "file"
lockfile, err := lockfile.GetLockfile(filepath.Join(dirPath, "secretsdata.lock"))
if err != nil {
return nil, err
}
fileDriver.lockfile = lockfile
fileDriver.secretData = make(map[string][]byte)

return fileDriver, nil
}

// DriverType retuns the string represntation of the filedriver
func (d *FileDriver) DriverType() string {
return d.drivertype
}

// List returns all secret id's
func (d *FileDriver) List() ([]string, error) {
d.lockfile.Lock()
defer d.lockfile.Unlock()
err := d.loadData()
if err != nil {
return nil, err
}
var allID []string
for k := range d.secretData {
allID = append(allID, k)
}

return allID, err
}

// Lookup returns the bytes associated with a secret id
func (d *FileDriver) Lookup(id string) ([]byte, error) {

d.lockfile.Lock()
defer d.lockfile.Unlock()

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

}

// Store stores the bytes associated with an id
func (d *FileDriver) Store(id string, data []byte) error {
d.lockfile.Lock()
defer d.lockfile.Unlock()

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

}

// Delete deletes a secret's data associated with an id
func (d *FileDriver) Delete(id string) error {
d.lockfile.Lock()
defer d.lockfile.Unlock()
err := d.loadData()
if err != nil {
return err
}
if _, ok := d.secretData[id]; ok {
delete(d.secretData, id)
} else {
return errors.Wrap(ErrNoSecretData, id)
}
marshalled, err := json.MarshalIndent(d.secretData, "", " ")
if err != nil {
return err
}
err = ioutil.WriteFile(d.secretsDataFilePath, marshalled, 0644)
if err != nil {
return err
}
return nil
}

// load loads the secret data into memory if it has been modified
func (d *FileDriver) loadData() error {
fileInfo, err := os.Stat(d.secretsDataFilePath)
if err != nil {
if os.IsNotExist(err) {
return nil
} else {
return err
}
}

if !d.lastModified.Equal(fileInfo.ModTime()) {
file, err := os.Open(d.secretsDataFilePath)
defer file.Close()

byteValue, err := ioutil.ReadAll(file)
if err != nil {
return err
}
json.Unmarshal([]byte(byteValue), &d.secretData)
d.lastModified = fileInfo.ModTime()
}
return nil

}
94 changes: 94 additions & 0 deletions pkg/secrets/filedriver/filedriver_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
package filedriver

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

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

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

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

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

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

}

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

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

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

}

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

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

}

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

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

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

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

}

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

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

data, err := tstdriver.List()
assert.NoError(t, err)
assert.Len(t, data, 2)

}
Loading

0 comments on commit e41ce1e

Please sign in to comment.