-
Notifications
You must be signed in to change notification settings - Fork 202
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Implement secrets pkg: backend and filedriver
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
1 parent
1e40eff
commit e41ce1e
Showing
6 changed files
with
903 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
|
||
} |
Oops, something went wrong.