-
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 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
1 parent
f6c7caf
commit 13ac506
Showing
5 changed files
with
907 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,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 | ||
} |
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,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) | ||
} |
Oops, something went wrong.