From 51450cc6139ea7cbe8dbba538e652359a9b9c160 Mon Sep 17 00:00:00 2001 From: Ashley Cui Date: Tue, 17 Nov 2020 09:09:22 -0500 Subject: [PATCH] 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 --- pkg/secrets/filedriver/filedriver.go | 152 +++++++++++++ pkg/secrets/filedriver/filedriver_test.go | 89 ++++++++ pkg/secrets/secrets.go | 262 ++++++++++++++++++++++ pkg/secrets/secrets_test.go | 184 +++++++++++++++ pkg/secrets/secretsdb.go | 192 ++++++++++++++++ 5 files changed, 879 insertions(+) create mode 100644 pkg/secrets/filedriver/filedriver.go create mode 100644 pkg/secrets/filedriver/filedriver_test.go create mode 100644 pkg/secrets/secrets.go create mode 100644 pkg/secrets/secrets_test.go create mode 100644 pkg/secrets/secretsdb.go diff --git a/pkg/secrets/filedriver/filedriver.go b/pkg/secrets/filedriver/filedriver.go new file mode 100644 index 000000000..f5a33ae2e --- /dev/null +++ b/pkg/secrets/filedriver/filedriver.go @@ -0,0 +1,152 @@ +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 +func NewDriver(dirPath string) (*Driver, error) { + fileDriver := new(Driver) + fileDriver.secretsDataFilePath = filepath.Join(dirPath, secretsDataFile) + lock, err := lockfile.GetLockfile(filepath.Join(dirPath, "secretsdata.lock")) + if err != nil { + return nil, err + } + fileDriver.lockfile = lock + + return fileDriver, nil +} + +// List returns all secret id's +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 +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, 0644) + if err != nil { + return err + } + return nil +} + +// Delete deletes a secret's data associated with an id +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, 0644) + 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 +} diff --git a/pkg/secrets/filedriver/filedriver_test.go b/pkg/secrets/filedriver/filedriver_test.go new file mode 100644 index 000000000..b6de7b451 --- /dev/null +++ b/pkg/secrets/filedriver/filedriver_test.go @@ -0,0 +1,89 @@ +package filedriver + +import ( + "io/ioutil" + "os" + "testing" + + "github.com/stretchr/testify/assert" +) + +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() + 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) +} diff --git a/pkg/secrets/secrets.go b/pkg/secrets/secrets.go new file mode 100644 index 000000000..47d8a0c44 --- /dev/null +++ b/pkg/secrets/secrets.go @@ -0,0 +1,262 @@ +package secrets + +import ( + "os" + "path/filepath" + "time" + + "github.com/containers/common/pkg/secrets/filedriver" + "github.com/containers/storage/pkg/lockfile" + "github.com/containers/storage/pkg/stringid" + "github.com/pkg/errors" +) + +// ErrInvalidPath indicates that the secrets path is invalid +var ErrInvalidPath = errors.New("invalid secrets path") + +// ErrNoSuchSecret indicates that the the secret does not exist +var ErrNoSuchSecret = errors.New("no such secret") + +// ErrSecretNameInUse indicates that the secret name is already in use +var ErrSecretNameInUse = errors.New("secret name in use") + +// ErrInvalidSecretName indicates that the secret name is invalid +var ErrInvalidSecretName = errors.New("invalid secret name") + +// ErrInvalidDriver indicates that the driver type is invalid +var ErrInvalidDriver = errors.New("invalid driver") + +// ErrInvalidDriverOpts indicates that a driver option is invalid +var ErrInvalidDriverOpts = errors.New("need Path for filedriver") + +// ErrInvalidDriverOpts indicates that a secret is ambiguous +var ErrAmbiguous = errors.New("secret is ambiguous") + +// secretsFile is the name of the file that the secrets database will be stored in +var secretsFile = "secrets.json" + +// SecretsManager holds information on handling secrets +type SecretsManager struct { + // secretsPath is the path to the db file where secrets are stored + secretsDBPath string + // lockfile is the locker for the secrets file + lockfile lockfile.Locker + // db is an in-memory cache of the database of secrets + db *db +} + +// Secret defines a secret +type Secret struct { + // Name is the name of the secret + Name string `json:"name"` + // ID is the unique secret ID + ID string `json:"id"` + // Metadata stores other metadata on the secret + Metadata map[string]string `json:"metadata,omitempty"` + // CreatedAt is when the secret was created + CreatedAt time.Time `json:"createdat"` + // Driver is the driver used to store secret data + Driver string `json:"driver"` + // DriverOptions is other metadata needed to use the driver + DriverOptions map[string]string `json:"driveroptions"` +} + +// SecretsDriver interfaces with the secrets data store. +// The driver stores the actual bytes of secret data, as opposed to +// the secret metadata. +// Currently only the unencrypted filedriver and an in-memory driver +// used for testing are implemented. +type SecretsDriver interface { + // List lists all secret ids in the secrets data store + List() ([]string, error) + // Lookup gets the secret's data bytes + Lookup(id string) ([]byte, error) + // Store stores the secret's data bytes + Store(id string, data []byte) error + // Delete deletes a secret's data from the driver + Delete(id string) error +} + +// NewManager creates a new secrets manager +func NewManager(dirPath string) (*SecretsManager, error) { + manager := new(SecretsManager) + + if !filepath.IsAbs(dirPath) { + return nil, errors.Wrapf(ErrInvalidPath, "path must be absolute: %s", dirPath) + } + if _, err := os.Stat(dirPath); os.IsNotExist(err) { + os.MkdirAll(dirPath, 0660) + } + + lock, err := lockfile.GetLockfile(filepath.Join(dirPath, "secrets.lock")) + if err != nil { + return nil, err + } + manager.lockfile = lock + manager.secretsDBPath = filepath.Join(dirPath, secretsFile) + manager.db = new(db) + manager.db.Secrets = make(map[string]Secret) + manager.db.NameToID = make(map[string]string) + manager.db.IDToName = make(map[string]string) + return manager, nil +} + +// Store creates a secret and stores it, given a name. +// It stores secret metadata as well as the secret payload +func (s *SecretsManager) Store(name string, data []byte, driverType string, driverOpts map[string]string) (string, error) { + err := validateSecretName(name) + if err != nil { + return "", err + } + + s.lockfile.Lock() + defer s.lockfile.Unlock() + + exist, err := s.secretExists(name) + if err != nil { + return "", err + } + if exist { + return "", errors.Wrapf(ErrSecretNameInUse, name) + } + + secr := new(Secret) + secr.Name = name + + for { + newID := stringid.GenerateNonCryptoID() + + // docker has 25 character secret ids + newID = newID[0:25] + _, err := s.lookupSecret(newID) + if err != nil { + if errors.Cause(err) == ErrNoSuchSecret { + secr.ID = newID + break + } else { + return "", err + } + } + } + + secr.Driver = driverType + secr.Metadata = make(map[string]string) + secr.CreatedAt = time.Now() + secr.DriverOptions = driverOpts + + driver, err := getDriver(driverType, driverOpts) + if err != nil { + return "", err + } + err = driver.Store(secr.ID, data) + + if err != nil { + return "", errors.Wrapf(err, "error creating secret %s", name) + } + + err = s.store(secr) + if err != nil { + return "", errors.Wrapf(err, "error creating secret %s", name) + } + + return secr.ID, nil +} + +// Delete removes a secret +// It removes secret metadata as well as the secret data +func (s *SecretsManager) Delete(nameOrID string) (string, error) { + err := validateSecretName(nameOrID) + if err != nil { + return "", err + } + + s.lockfile.Lock() + defer s.lockfile.Unlock() + + secret, err := s.lookupSecret(nameOrID) + if err != nil { + return "", err + } + secretID := secret.ID + + driver, err := getDriver(secret.Driver, secret.DriverOptions) + if err != nil { + return "", err + } + + err = driver.Delete(secretID) + if err != nil { + return "", errors.Wrapf(err, "error deleting secret %s", nameOrID) + } + + err = s.delete(secretID) + if err != nil { + return "", errors.Wrapf(err, "error deleting secret %s", nameOrID) + } + return secretID, nil +} + +// Lookup gives a secret's metadata +func (s *SecretsManager) Lookup(nameOrID string) (*Secret, error) { + s.lockfile.Lock() + defer s.lockfile.Unlock() + + return s.lookupSecret(nameOrID) +} + +// List lists all secrets +func (s *SecretsManager) List() ([]Secret, error) { + s.lockfile.Lock() + defer s.lockfile.Unlock() + + secrets, err := s.lookupAll() + if err != nil { + return nil, err + } + var ls []Secret + for _, v := range secrets { + ls = append(ls, v) + + } + return ls, nil +} + +// LookupSecretData returns secret data in bytes +func (s *SecretsManager) LookupSecretData(nameOrID string) ([]byte, error) { + s.lockfile.Lock() + defer s.lockfile.Unlock() + + secret, err := s.lookupSecret(nameOrID) + if err != nil { + return nil, err + } + driver, err := getDriver(secret.Driver, secret.DriverOptions) + if err != nil { + return nil, err + } + data, err := driver.Lookup(secret.ID) + if err != nil { + return nil, err + } + return data, nil +} + +// validateSecretName checks if the secret name is valid +func validateSecretName(name string) error { + if name == "" { + return errors.Wrap(ErrInvalidSecretName, name) + } + return nil +} + +// getDriver creates a new driver +func getDriver(name string, opts map[string]string) (SecretsDriver, error) { + if name == "file" { + if path, ok := opts["path"]; ok { + return filedriver.NewDriver(path) + } else { + return nil, errors.Wrap(ErrInvalidDriverOpts, "need path for filedriver") + } + } + return nil, ErrInvalidDriver +} diff --git a/pkg/secrets/secrets_test.go b/pkg/secrets/secrets_test.go new file mode 100644 index 000000000..f9b7c8ab5 --- /dev/null +++ b/pkg/secrets/secrets_test.go @@ -0,0 +1,184 @@ +package secrets + +import ( + "bytes" + "io/ioutil" + "os" + "testing" + + "github.com/stretchr/testify/assert" +) + +var drivertype = "file" + +var opts map[string]string + +func setup() (*SecretsManager, string, error) { + testpath, err := ioutil.TempDir("", "secretsdata") + if err != nil { + return nil, "", err + } + manager, err := NewManager(testpath) + opts = map[string]string{"path": testpath} + return manager, testpath, err +} + +func cleanup(testpath string) { + os.RemoveAll(testpath) +} + +func TestAddSecretAndLookupData(t *testing.T) { + manager, testpath, err := setup() + assert.NoError(t, err) + defer cleanup(testpath) + + _, err = manager.Store("mysecret", []byte("mydata"), drivertype, opts) + assert.NoError(t, err) + + _, err = manager.lookupSecret("mysecret") + assert.NoError(t, err) + + data, err := manager.LookupSecretData("mysecret") + assert.NoError(t, err) + if !bytes.Equal(data, []byte("mydata")) { + t.Errorf("error: secret data not equal") + } +} + +func TestAddMultipleSecrets(t *testing.T) { + manager, testpath, err := setup() + assert.NoError(t, err) + defer cleanup(testpath) + + id, err := manager.Store("mysecret", []byte("mydata"), drivertype, opts) + assert.NoError(t, err) + + id2, err := manager.Store("mysecret2", []byte("mydata2"), drivertype, opts) + assert.NoError(t, err) + + secrets, err := manager.List() + assert.NoError(t, err) + assert.Len(t, secrets, 2) + + _, err = manager.lookupSecret("mysecret") + assert.NoError(t, err) + + _, err = manager.lookupSecret("mysecret2") + assert.NoError(t, err) + + data, err := manager.LookupSecretData(id) + assert.NoError(t, err) + if !bytes.Equal(data, []byte("mydata")) { + t.Errorf("error: secret data not equal") + } + + data2, err := manager.LookupSecretData(id2) + assert.NoError(t, err) + if !bytes.Equal(data2, []byte("mydata2")) { + t.Errorf("error: secret data not equal") + } +} + +func TestAddSecretDupName(t *testing.T) { + manager, testpath, err := setup() + assert.NoError(t, err) + defer cleanup(testpath) + + _, err = manager.Store("mysecret", []byte("mydata"), drivertype, opts) + assert.NoError(t, err) + + _, err = manager.Store("mysecret", []byte("mydata"), drivertype, opts) + assert.Error(t, err) +} + +func TestRemoveSecret(t *testing.T) { + manager, testpath, err := setup() + assert.NoError(t, err) + defer cleanup(testpath) + + _, err = manager.Store("mysecret", []byte("mydata"), drivertype, opts) + assert.NoError(t, err) + + _, err = manager.lookupSecret("mysecret") + assert.NoError(t, err) + + _, err = manager.Delete("mysecret") + assert.NoError(t, err) + + _, err = manager.lookupSecret("mysecret") + assert.Error(t, err) + + _, err = manager.LookupSecretData("mysecret") + assert.Error(t, err) +} + +func TestRemoveSecretNoExist(t *testing.T) { + manager, testpath, err := setup() + assert.NoError(t, err) + defer cleanup(testpath) + + _, err = manager.Delete("mysecret") + assert.Error(t, err) +} + +func TestLookupAllSecrets(t *testing.T) { + manager, testpath, err := setup() + assert.NoError(t, err) + defer cleanup(testpath) + + id, err := manager.Store("mysecret", []byte("mydata"), drivertype, opts) + assert.NoError(t, err) + + // inspect using secret name + lookup, err := manager.Lookup("mysecret") + assert.NoError(t, err) + assert.Equal(t, lookup.ID, id) +} + +func TestInspectSecretId(t *testing.T) { + manager, testpath, err := setup() + assert.NoError(t, err) + defer cleanup(testpath) + + id, err := manager.Store("mysecret", []byte("mydata"), drivertype, opts) + assert.NoError(t, err) + + _, err = manager.lookupSecret("mysecret") + assert.NoError(t, err) + + // inspect using secret id + lookup, err := manager.Lookup(id) + assert.NoError(t, err) + assert.Equal(t, lookup.ID, id) + + // inspect using id prefix + short := id[0:5] + lookupshort, err := manager.Lookup(short) + assert.NoError(t, err) + assert.Equal(t, lookupshort.ID, id) + +} + +func TestInspectSecretBogus(t *testing.T) { + manager, testpath, err := setup() + assert.NoError(t, err) + defer cleanup(testpath) + + _, err = manager.Lookup("bogus") + assert.Error(t, err) +} + +func TestSecretList(t *testing.T) { + manager, testpath, err := setup() + assert.NoError(t, err) + defer cleanup(testpath) + + _, err = manager.Store("mysecret", []byte("mydata"), drivertype, opts) + assert.NoError(t, err) + _, err = manager.Store("mysecret2", []byte("mydata2"), drivertype, opts) + assert.NoError(t, err) + + allSecrets, err := manager.List() + assert.NoError(t, err) + assert.Len(t, allSecrets, 2) +} diff --git a/pkg/secrets/secretsdb.go b/pkg/secrets/secretsdb.go new file mode 100644 index 000000000..06a4699b7 --- /dev/null +++ b/pkg/secrets/secretsdb.go @@ -0,0 +1,192 @@ +package secrets + +import ( + "encoding/json" + "io/ioutil" + "os" + "strings" + "time" + + "github.com/pkg/errors" +) + +type db struct { + // Secrets maps a secret id to secret metadata + Secrets map[string]Secret `json:"secrets"` + // NameToID maps a secret name to a secret id + NameToID map[string]string `json:"nametoid"` + // IDToName maps a secret id to a secret name + IDToName map[string]string `json:"idtoname"` + // lastModified is the time when the database was last modified on the file system + lastModified time.Time +} + +// loadDB loads database data into the in-memory cache if it has been modified +func (s *SecretsManager) loadDB() error { + // check if the db file exists + fileInfo, err := os.Stat(s.secretsDBPath) + if err != nil { + if !os.IsExist(err) { + // if the file doesn't exist, then there's no reason to update the db cache + // the db cache will show no entries anyway + // the file will be created later on a store() + return nil + } else { + return err + } + } + + // we check if the file has been modified after the last time it was loaded into the cache + // if the file has been modified, then we know that our cache is not up-to-date, so we load + // the db into the cache + if !s.db.lastModified.Equal(fileInfo.ModTime()) { + file, err := os.Open(s.secretsDBPath) + if err != nil { + return err + } + defer file.Close() + if err != nil { + return err + } + + byteValue, err := ioutil.ReadAll(file) + if err != nil { + return err + } + unmarshalled := new(db) + if err := json.Unmarshal(byteValue, unmarshalled); err != nil { + return err + } + s.db = unmarshalled + s.db.lastModified = fileInfo.ModTime() + } + return nil +} + +// getNameAndID takes a secret's name or id and returns both its +// identifying name and id +func (s *SecretsManager) getNameAndID(nameOrID string) (name, id string, err error) { + err = s.loadDB() + if err != nil { + return "", "", err + } + if id, ok := s.db.NameToID[nameOrID]; ok { + name := nameOrID + return name, id, nil + } + + if name, ok := s.db.IDToName[nameOrID]; ok { + id := nameOrID + return name, id, nil + } + + // ID prefix may have been given, iterate through all ID's + exists := false + var foundID, foundName string + for id, name := range s.db.IDToName { + if exists { + return "", "", errors.Wrapf(ErrAmbiguous, "more than one result secret with prefix %s", nameOrID) + } + if strings.HasPrefix(id, nameOrID) { + exists = true + foundID = id + foundName = name + } + } + if exists { + return foundName, foundID, nil + } + + return "", "", errors.Wrapf(ErrNoSuchSecret, "No secret with name or id %s", nameOrID) +} + +// secretExists checks if the secret exists +func (s *SecretsManager) secretExists(nameOrID string) (bool, error) { + _, _, err := s.getNameAndID(nameOrID) + if err != nil { + if errors.Cause(err) == ErrNoSuchSecret { + return false, nil + } + return false, err + } + return true, nil +} + +// lookupAll gets all secrets stored +func (s *SecretsManager) lookupAll() (map[string]Secret, error) { + err := s.loadDB() + if err != nil { + return nil, err + } + return s.db.Secrets, nil +} + +// lookupSecret returns a secret with the given name or id +func (s *SecretsManager) lookupSecret(nameOrID string) (*Secret, error) { + err := s.loadDB() + if err != nil { + return nil, err + } + _, id, err := s.getNameAndID(nameOrID) + if err != nil { + return nil, err + } + allSecrets, err := s.lookupAll() + if err != nil { + return nil, err + } + if secret, ok := allSecrets[id]; ok { + return &secret, nil + } + + return nil, ErrNoSuchSecret +} + +// Store creates a new secret in the secrets database +// it deals with only storing metadata, not data payload +func (s *SecretsManager) store(entry *Secret) error { + err := s.loadDB() + if err != nil { + return err + } + + s.db.Secrets[entry.ID] = *entry + s.db.NameToID[entry.Name] = entry.ID + s.db.IDToName[entry.ID] = entry.Name + + marshalled, err := json.MarshalIndent(s.db, "", " ") + if err != nil { + return err + } + err = ioutil.WriteFile(s.secretsDBPath, marshalled, 0644) + if err != nil { + return err + } + + return nil +} + +// delete deletes a secret from the secrets database +// it deals with only deleting metadata, not data payload +func (s *SecretsManager) delete(nameOrID string) error { + name, id, err := s.getNameAndID(nameOrID) + if err != nil { + return err + } + err = s.loadDB() + if err != nil { + return err + } + delete(s.db.Secrets, id) + delete(s.db.NameToID, name) + delete(s.db.IDToName, id) + marshalled, err := json.MarshalIndent(s.db, "", " ") + if err != nil { + return err + } + err = ioutil.WriteFile(s.secretsDBPath, marshalled, 0644) + if err != nil { + return err + } + return nil +}