diff options
Diffstat (limited to 'vendor/github.com')
12 files changed, 688 insertions, 18 deletions
diff --git a/vendor/github.com/containers/buildah/CHANGELOG.md b/vendor/github.com/containers/buildah/CHANGELOG.md index 0ad3069ce..ccf46b324 100644 --- a/vendor/github.com/containers/buildah/CHANGELOG.md +++ b/vendor/github.com/containers/buildah/CHANGELOG.md @@ -2,6 +2,14 @@ # Changelog +## v1.19.4 (2021-02-06) + run: fix check for host pid namespace + bump containernetworking/cni library to v0.8.1 - fix for CVE-2021-20206 + Finish plumbing for buildah bud --manifest + buildah manifest add localimage should work + Fix build arg check + --iidfile: print hash prefix + ## v1.19.3 (2021-01-28) [ci:docs] Fix man page for buildah push Vendor in containers/image v5.10.1 diff --git a/vendor/github.com/containers/buildah/buildah.go b/vendor/github.com/containers/buildah/buildah.go index 4fbc475c2..7065e00e4 100644 --- a/vendor/github.com/containers/buildah/buildah.go +++ b/vendor/github.com/containers/buildah/buildah.go @@ -28,7 +28,7 @@ const ( Package = "buildah" // Version for the Package. Bump version in contrib/rpm/buildah.spec // too. - Version = "1.19.3" + Version = "1.19.4" // The value we use to identify what type of information, currently a // serialized Builder structure, we are using as per-container state. // This should only be changed when we make incompatible changes to diff --git a/vendor/github.com/containers/buildah/changelog.txt b/vendor/github.com/containers/buildah/changelog.txt index db2faf71a..4a0f81b04 100644 --- a/vendor/github.com/containers/buildah/changelog.txt +++ b/vendor/github.com/containers/buildah/changelog.txt @@ -1,3 +1,11 @@ +- Changelog for v1.19.4 (2021-02-06) + * run: fix check for host pid namespace + * bump containernetworking/cni library to v0.8.1 - fix for CVE-2021-20206 + * Finish plumbing for buildah bud --manifest + * buildah manifest add localimage should work + * Fix build arg check + * --iidfile: print hash prefix + - Changelog for v1.19.3 (2021-01-28) * [ci:docs] Fix man page for buildah push * Vendor in containers/image v5.10.1 diff --git a/vendor/github.com/containers/buildah/commit.go b/vendor/github.com/containers/buildah/commit.go index 9c6831601..f588c8043 100644 --- a/vendor/github.com/containers/buildah/commit.go +++ b/vendor/github.com/containers/buildah/commit.go @@ -224,7 +224,7 @@ func checkRegistrySourcesAllows(forWhat string, dest types.ImageReference) (inse return false, nil } -func (b *Builder) addManifest(ctx context.Context, manifestName string, imageSpec string) error { +func (b *Builder) addManifest(ctx context.Context, manifestName string, imageSpec string) (string, error) { var create bool systemContext := &types.SystemContext{} var list manifests.List @@ -235,13 +235,13 @@ func (b *Builder) addManifest(ctx context.Context, manifestName string, imageSpe } else { _, list, err = manifests.LoadFromImage(b.store, listImage.ID) if err != nil { - return err + return "", err } } names, err := util.ExpandNames([]string{manifestName}, "", systemContext, b.store) if err != nil { - return errors.Wrapf(err, "error encountered while expanding image name %q", manifestName) + return "", errors.Wrapf(err, "error encountered while expanding image name %q", manifestName) } ref, err := alltransports.ParseImageName(imageSpec) @@ -249,13 +249,13 @@ func (b *Builder) addManifest(ctx context.Context, manifestName string, imageSpe if ref, err = alltransports.ParseImageName(util.DefaultTransport + imageSpec); err != nil { // check if the local image exists if ref, _, err = util.FindImage(b.store, "", systemContext, imageSpec); err != nil { - return err + return "", err } } } if _, err = list.Add(ctx, systemContext, ref, true); err != nil { - return err + return "", err } var imageID string if create { @@ -263,10 +263,7 @@ func (b *Builder) addManifest(ctx context.Context, manifestName string, imageSpe } else { imageID, err = list.SaveToImage(b.store, listImage.ID, nil, "") } - if err == nil { - fmt.Printf("%s\n", imageID) - } - return err + return imageID, err } // Commit writes the contents of the container, along with its updated @@ -469,7 +466,7 @@ func (b *Builder) Commit(ctx context.Context, dest types.ImageReference, options dest = dest2 } if options.IIDFile != "" { - if err = ioutil.WriteFile(options.IIDFile, []byte(img.ID), 0644); err != nil { + if err = ioutil.WriteFile(options.IIDFile, []byte("sha256:"+img.ID), 0644); err != nil { return imgID, nil, "", err } } @@ -489,9 +486,12 @@ func (b *Builder) Commit(ctx context.Context, dest types.ImageReference, options } if options.Manifest != "" { - if err := b.addManifest(ctx, options.Manifest, imgID); err != nil { + manifestID, err := b.addManifest(ctx, options.Manifest, imgID) + if err != nil { return imgID, nil, "", err } + logrus.Debugf("added imgID %s to manifestID %s", imgID, manifestID) + } return imgID, ref, manifestDigest, nil } diff --git a/vendor/github.com/containers/buildah/go.mod b/vendor/github.com/containers/buildah/go.mod index cccf42895..17469ad12 100644 --- a/vendor/github.com/containers/buildah/go.mod +++ b/vendor/github.com/containers/buildah/go.mod @@ -4,7 +4,7 @@ go 1.12 require ( github.com/containerd/containerd v1.4.1 // indirect - github.com/containernetworking/cni v0.7.2-0.20190904153231-83439463f784 + github.com/containernetworking/cni v0.8.1 github.com/containers/common v0.33.1 github.com/containers/image/v5 v5.10.1 github.com/containers/ocicrypt v1.0.3 diff --git a/vendor/github.com/containers/buildah/go.sum b/vendor/github.com/containers/buildah/go.sum index bf796c496..cab904fcf 100644 --- a/vendor/github.com/containers/buildah/go.sum +++ b/vendor/github.com/containers/buildah/go.sum @@ -76,8 +76,8 @@ github.com/containerd/fifo v0.0.0-20190226154929-a9fb20d87448/go.mod h1:ODA38xgv github.com/containerd/go-runc v0.0.0-20180907222934-5a6d9f37cfa3/go.mod h1:IV7qH3hrUgRmyYrtgEeGWJfWbgcHL9CSRruz2Vqcph0= github.com/containerd/ttrpc v0.0.0-20190828154514-0e0f228740de/go.mod h1:PvCDdDGpgqzQIzDW1TphrGLssLDZp2GuS+X5DkEJB8o= github.com/containerd/typeurl v0.0.0-20180627222232-a93fcdb778cd/go.mod h1:Cm3kwCdlkCfMSHURc+r6fwoGH6/F1hH3S4sg0rLFWPc= -github.com/containernetworking/cni v0.7.2-0.20190904153231-83439463f784 h1:rqUVLD8I859xRgUx/WMC3v7QAFqbLKZbs+0kqYboRJc= -github.com/containernetworking/cni v0.7.2-0.20190904153231-83439463f784/go.mod h1:LGwApLUm2FpoOfxTDEeq8T9ipbpZ61X79hmU3w8FmsY= +github.com/containernetworking/cni v0.8.1 h1:7zpDnQ3T3s4ucOuJ/ZCLrYBxzkg0AELFfII3Epo9TmI= +github.com/containernetworking/cni v0.8.1/go.mod h1:LGwApLUm2FpoOfxTDEeq8T9ipbpZ61X79hmU3w8FmsY= github.com/containers/common v0.33.1 h1:XpDiq8Cta8+u1s4kpYSEWdB140ZmqgyIXfWkLqKx3z0= github.com/containers/common v0.33.1/go.mod h1:mjDo/NKeweL/onaspLhZ38WnHXaYmrELHclIdvSnYpY= github.com/containers/image/v5 v5.9.0 h1:dRmUtcluQcmasNo3DpnRoZjfU0rOu1qZeL6wlDJr10Q= diff --git a/vendor/github.com/containers/buildah/imagebuildah/executor.go b/vendor/github.com/containers/buildah/imagebuildah/executor.go index a72e24eea..74ed9a42b 100644 --- a/vendor/github.com/containers/buildah/imagebuildah/executor.go +++ b/vendor/github.com/containers/buildah/imagebuildah/executor.go @@ -115,6 +115,7 @@ type Executor struct { imageInfoLock sync.Mutex imageInfoCache map[string]imageTypeAndHistoryAndDiffIDs fromOverride string + manifest string } type imageTypeAndHistoryAndDiffIDs struct { @@ -231,6 +232,7 @@ func NewExecutor(store storage.Store, options BuildOptions, mainNode *parser.Nod logRusage: options.LogRusage, imageInfoCache: make(map[string]imageTypeAndHistoryAndDiffIDs), fromOverride: options.From, + manifest: options.Manifest, } if exec.err == nil { exec.err = os.Stderr @@ -679,7 +681,7 @@ func (b *Executor) Build(ctx context.Context, stages imagebuilder.Stages) (image } logrus.Debugf("printing final image id %q", imageID) if b.iidfile != "" { - if err = ioutil.WriteFile(b.iidfile, []byte(imageID), 0644); err != nil { + if err = ioutil.WriteFile(b.iidfile, []byte("sha256:"+imageID), 0644); err != nil { return imageID, ref, errors.Wrapf(err, "failed to write image ID to file %q", b.iidfile) } } else { diff --git a/vendor/github.com/containers/buildah/imagebuildah/stage_executor.go b/vendor/github.com/containers/buildah/imagebuildah/stage_executor.go index 9c15785bc..13631108e 100644 --- a/vendor/github.com/containers/buildah/imagebuildah/stage_executor.go +++ b/vendor/github.com/containers/buildah/imagebuildah/stage_executor.go @@ -838,7 +838,7 @@ func (s *StageExecutor) Execute(ctx context.Context, base string) (imgID string, // we need to call ib.Run() to correctly put the args together before // determining if a cached layer with the same build args already exists // and that is done in the if block below. - if checkForLayers && s.builder.Args == nil { + if checkForLayers && len(s.builder.Args) == 0 { cacheID, err = s.intermediateImageExists(ctx, node, addedContentSummary, s.stepRequiresLayer(step)) if err != nil { return "", nil, errors.Wrap(err, "error checking if cached image exists from a previous build") @@ -1276,6 +1276,7 @@ func (s *StageExecutor) commit(ctx context.Context, createdBy string, emptyLayer MaxRetries: s.executor.maxPullPushRetries, RetryDelay: s.executor.retryPullPushDelay, HistoryTimestamp: s.executor.timestamp, + Manifest: s.executor.manifest, } imgID, _, manifestDigest, err := s.builder.Commit(ctx, imageRef, options) if err != nil { diff --git a/vendor/github.com/containers/buildah/run_linux.go b/vendor/github.com/containers/buildah/run_linux.go index 66c856884..8c7c1bbc0 100644 --- a/vendor/github.com/containers/buildah/run_linux.go +++ b/vendor/github.com/containers/buildah/run_linux.go @@ -2210,7 +2210,7 @@ func checkAndOverrideIsolationOptions(isolation Isolation, options *RunOptions) case IsolationOCI: pidns := options.NamespaceOptions.Find(string(specs.PIDNamespace)) userns := options.NamespaceOptions.Find(string(specs.UserNamespace)) - if (pidns == nil || pidns.Host) && (userns != nil && !userns.Host) { + if (pidns != nil && pidns.Host) && (userns != nil && !userns.Host) { return errors.Errorf("not allowed to mix host PID namespace with container user namespace") } } diff --git a/vendor/github.com/containers/common/pkg/secrets/filedriver/filedriver.go b/vendor/github.com/containers/common/pkg/secrets/filedriver/filedriver.go new file mode 100644 index 000000000..37edc16be --- /dev/null +++ b/vendor/github.com/containers/common/pkg/secrets/filedriver/filedriver.go @@ -0,0 +1,158 @@ +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) + // the lockfile functions requre that the rootPath dir is executable + if err := os.MkdirAll(rootPath, 0700); err != nil { + return nil, err + } + + 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 +} diff --git a/vendor/github.com/containers/common/pkg/secrets/secrets.go b/vendor/github.com/containers/common/pkg/secrets/secrets.go new file mode 100644 index 000000000..5e0fb3e9d --- /dev/null +++ b/vendor/github.com/containers/common/pkg/secrets/secrets.go @@ -0,0 +1,282 @@ +package secrets + +import ( + "os" + "path/filepath" + "regexp" + "strings" + "time" + + "github.com/containers/common/pkg/secrets/filedriver" + "github.com/containers/storage/pkg/lockfile" + "github.com/containers/storage/pkg/stringid" + "github.com/pkg/errors" +) + +// maxSecretSize is the max size for secret data - 512kB +const maxSecretSize = 512000 + +// secretIDLength is the character length of a secret ID - 25 +const secretIDLength = 25 + +// errInvalidPath indicates that the secrets path is invalid +var errInvalidPath = errors.New("invalid secrets path") + +// errNoSuchSecret indicates that 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") + +// errInvalidDriverOpt indicates that a driver option is invalid +var errInvalidDriverOpt = errors.New("invalid driver option") + +// errAmbiguous indicates that a secret is ambiguous +var errAmbiguous = errors.New("secret is ambiguous") + +// errDataSize indicates that the secret data is too large or too small +var errDataSize = errors.New("secret data must be larger than 0 and less than 512000 bytes") + +// secretsFile is the name of the file that the secrets database will be stored in +var secretsFile = "secrets.json" + +// secretNameRegexp matches valid secret names +// Allowed: 64 [a-zA-Z0-9-_.] characters, and the start and end character must be [a-zA-Z0-9] +var secretNameRegexp = regexp.MustCompile(`^[a-zA-Z0-9][a-zA-Z0-9_.-]*$`) + +// 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 is 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 +// rootPath is the directory where the secrets data file resides +func NewManager(rootPath string) (*SecretsManager, error) { + manager := new(SecretsManager) + + if !filepath.IsAbs(rootPath) { + return nil, errors.Wrapf(errInvalidPath, "path must be absolute: %s", rootPath) + } + // the lockfile functions requre that the rootPath dir is executable + if err := os.MkdirAll(rootPath, 0700); err != nil { + return nil, err + } + + lock, err := lockfile.GetLockfile(filepath.Join(rootPath, "secrets.lock")) + if err != nil { + return nil, err + } + manager.lockfile = lock + manager.secretsDBPath = filepath.Join(rootPath, 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 takes a name, creates a secret and stores the secret metadata and the secret payload. +// It returns a generated ID that is associated with the secret. +// The max size for secret data is 512kB. +func (s *SecretsManager) Store(name string, data []byte, driverType string, driverOpts map[string]string) (string, error) { + err := validateSecretName(name) + if err != nil { + return "", err + } + + if !(len(data) > 0 && len(data) < maxSecretSize) { + return "", errDataSize + } + + s.lockfile.Lock() + defer s.lockfile.Unlock() + + exist, err := s.exactSecretExists(name) + if err != nil { + return "", err + } + if exist { + return "", errors.Wrapf(errSecretNameInUse, name) + } + + secr := new(Secret) + secr.Name = name + + for { + newID := stringid.GenerateNonCryptoID() + // GenerateNonCryptoID() gives 64 characters, so we truncate to correct length + newID = newID[0:secretIDLength] + _, 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 all secret metadata and secret data associated with the specified secret. +// Delete takes a name, ID, or partial ID. +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 given its name, ID, or partial ID. +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 metadata as well as secret data in bytes. +// The secret data can be looked up using its name, ID, or partial ID. +func (s *SecretsManager) LookupSecretData(nameOrID string) (*Secret, []byte, error) { + s.lockfile.Lock() + defer s.lockfile.Unlock() + + secret, err := s.lookupSecret(nameOrID) + if err != nil { + return nil, nil, err + } + driver, err := getDriver(secret.Driver, secret.DriverOptions) + if err != nil { + return nil, nil, err + } + data, err := driver.Lookup(secret.ID) + if err != nil { + return nil, nil, err + } + return secret, data, nil +} + +// validateSecretName checks if the secret name is valid. +func validateSecretName(name string) error { + if !secretNameRegexp.MatchString(name) || len(name) > 64 || strings.HasSuffix(name, "-") || strings.HasSuffix(name, ".") { + return errors.Wrapf(errInvalidSecretName, "only 64 [a-zA-Z0-9-_.] characters allowed, and the start and end character must be [a-zA-Z0-9]: %s", 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(errInvalidDriverOpt, "need path for filedriver") + } + } + return nil, errInvalidDriver +} diff --git a/vendor/github.com/containers/common/pkg/secrets/secretsdb.go b/vendor/github.com/containers/common/pkg/secrets/secretsdb.go new file mode 100644 index 000000000..22db97c12 --- /dev/null +++ b/vendor/github.com/containers/common/pkg/secrets/secretsdb.go @@ -0,0 +1,211 @@ +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()) { + return nil + } + + 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, ID, or partial ID, and returns both its name and full ID. +func (s *SecretsManager) getNameAndID(nameOrID string) (name, id string, err error) { + name, id, err = s.getExactNameAndID(nameOrID) + if err == nil { + return name, id, nil + } else if errors.Cause(err) != errNoSuchSecret { + return "", "", err + } + + // ID prefix may have been given, iterate through all IDs. + // ID and partial ID has a max lenth of 25, so we return if its greater than that. + if len(nameOrID) > secretIDLength { + return "", "", errors.Wrapf(errNoSuchSecret, "no secret with name or id %q", nameOrID) + } + exists := false + var foundID, foundName string + for id, name := range s.db.IDToName { + if strings.HasPrefix(id, nameOrID) { + if exists { + return "", "", errors.Wrapf(errAmbiguous, "more than one result secret with prefix %s", nameOrID) + } + exists = true + foundID = id + foundName = name + } + } + + if exists { + return foundName, foundID, nil + } + return "", "", errors.Wrapf(errNoSuchSecret, "no secret with name or id %q", nameOrID) +} + +// getExactNameAndID takes a secret's name or ID and returns both its name and full ID. +func (s *SecretsManager) getExactNameAndID(nameOrID string) (name, id string, err error) { + err = s.loadDB() + if err != nil { + return "", "", err + } + if name, ok := s.db.IDToName[nameOrID]; ok { + id := nameOrID + return name, id, nil + } + + if id, ok := s.db.NameToID[nameOrID]; ok { + name := nameOrID + return name, id, nil + } + + return "", "", errors.Wrapf(errNoSuchSecret, "no secret with name or id %q", nameOrID) +} + +// exactSecretExists checks if the secret exists, given a name or ID +// Does not match partial name or IDs +func (s *SecretsManager) exactSecretExists(nameOrID string) (bool, error) { + _, _, err := s.getExactNameAndID(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, ID, or partial 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, errors.Wrapf(errNoSuchSecret, "no secret with name or id %q", nameOrID) +} + +// 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, 0600) + if err != nil { + return err + } + + return nil +} + +// delete deletes a secret from the secrets database, given a name, ID, or partial ID. +// 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, 0600) + if err != nil { + return err + } + return nil +} |