mirror of https://github.com/docker/docs.git
162 lines
5.0 KiB
Go
162 lines
5.0 KiB
Go
package snapshot
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"github.com/Sirupsen/logrus"
|
|
|
|
"github.com/docker/notary/server/storage"
|
|
"github.com/docker/notary/tuf/data"
|
|
"github.com/docker/notary/tuf/signed"
|
|
)
|
|
|
|
// GetOrCreateSnapshotKey either creates a new snapshot key, or returns
|
|
// the existing one. Only the PublicKey is returned. The private part
|
|
// is held by the CryptoService.
|
|
func GetOrCreateSnapshotKey(gun string, store storage.KeyStore, crypto signed.CryptoService, createAlgorithm string) (data.PublicKey, error) {
|
|
keyAlgorithm, public, err := store.GetKey(gun, data.CanonicalSnapshotRole)
|
|
if err == nil {
|
|
return data.NewPublicKey(keyAlgorithm, public), nil
|
|
}
|
|
|
|
if _, ok := err.(*storage.ErrNoKey); ok {
|
|
key, err := crypto.Create("snapshot", createAlgorithm)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
logrus.Debug("Creating new snapshot key for ", gun, ". With algo: ", key.Algorithm())
|
|
err = store.SetKey(gun, data.CanonicalSnapshotRole, key.Algorithm(), key.Public())
|
|
if err == nil {
|
|
return key, nil
|
|
}
|
|
|
|
if _, ok := err.(*storage.ErrKeyExists); ok {
|
|
keyAlgorithm, public, err = store.GetKey(gun, data.CanonicalSnapshotRole)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return data.NewPublicKey(keyAlgorithm, public), nil
|
|
}
|
|
return nil, err
|
|
}
|
|
return nil, err
|
|
}
|
|
|
|
// GetOrCreateSnapshot either returns the exisiting latest snapshot, or uses
|
|
// whatever the most recent snapshot is to generate a new one.
|
|
func GetOrCreateSnapshot(gun string, store storage.MetaStore, cryptoService signed.CryptoService) ([]byte, error) {
|
|
|
|
d, err := store.GetCurrent(gun, "snapshot")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
sn := &data.SignedSnapshot{}
|
|
if d != nil {
|
|
err := json.Unmarshal(d, sn)
|
|
if err != nil {
|
|
logrus.Error("Failed to unmarshal existing snapshot")
|
|
return nil, err
|
|
}
|
|
if !snapshotExpired(sn) && !contentExpired(gun, sn, store) {
|
|
return d, nil
|
|
}
|
|
}
|
|
|
|
sgnd, version, err := createSnapshot(gun, sn, store, cryptoService)
|
|
if err != nil {
|
|
logrus.Error("Failed to create a new snapshot")
|
|
return nil, err
|
|
}
|
|
out, err := json.Marshal(sgnd)
|
|
if err != nil {
|
|
logrus.Error("Failed to marshal new snapshot")
|
|
return nil, err
|
|
}
|
|
err = store.UpdateCurrent(gun, storage.MetaUpdate{Role: "snapshot", Version: version, Data: out})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
// snapshotExpired simply checks if the snapshot is past its expiry time
|
|
func snapshotExpired(sn *data.SignedSnapshot) bool {
|
|
return signed.IsExpired(sn.Signed.Expires)
|
|
}
|
|
|
|
// contentExpired checks to see if any of the roles already in the snapshot
|
|
// have been updated. It will update any roles that have changed as it goes
|
|
// so that we don't have to run through all this again a second time.
|
|
func contentExpired(gun string, sn *data.SignedSnapshot, store storage.MetaStore) bool {
|
|
expired := false
|
|
updatedMeta := make(data.Files)
|
|
for role, meta := range sn.Signed.Meta {
|
|
curr, err := store.GetCurrent(gun, role)
|
|
if err != nil {
|
|
return false
|
|
}
|
|
roleExp, newHash := roleExpired(curr, meta)
|
|
if roleExp {
|
|
updatedMeta[role] = data.FileMeta{
|
|
Length: int64(len(curr)),
|
|
Hashes: data.Hashes{
|
|
"sha256": newHash,
|
|
},
|
|
}
|
|
}
|
|
expired = expired || roleExp
|
|
}
|
|
if expired {
|
|
sn.Signed.Meta = updatedMeta
|
|
}
|
|
return expired
|
|
}
|
|
|
|
// roleExpired checks if the content for a specific role differs from
|
|
// the snapshot
|
|
func roleExpired(roleData []byte, meta data.FileMeta) (bool, []byte) {
|
|
currMeta, err := data.NewFileMeta(bytes.NewReader(roleData), "sha256")
|
|
if err != nil {
|
|
// if we can't generate FileMeta from the current roleData, we should
|
|
// continue to serve the old role if it isn't time expired
|
|
// because we won't be able to generate a new one.
|
|
return false, nil
|
|
}
|
|
hash := currMeta.Hashes["sha256"]
|
|
return !bytes.Equal(hash, meta.Hashes["sha256"]), hash
|
|
}
|
|
|
|
// createSnapshot uses an existing snapshot to create a new one.
|
|
// Important things to be aware of:
|
|
// - It requires that a snapshot already exists. We create snapshots
|
|
// on upload so there should always be an existing snapshot if this
|
|
// gets called.
|
|
// - It doesn't update what roles are present in the snapshot, as those
|
|
// were validated during upload. We also updated the hashes of the
|
|
// already present roles as part of our checks on whether we could
|
|
// serve the previous version of the snapshot
|
|
func createSnapshot(gun string, sn *data.SignedSnapshot, store storage.MetaStore, cryptoService signed.CryptoService) (*data.Signed, int, error) {
|
|
algorithm, public, err := store.GetKey(gun, data.CanonicalSnapshotRole)
|
|
if err != nil {
|
|
// owner of gun must have generated a snapshot key otherwise
|
|
// we won't proceed with generating everything.
|
|
return nil, 0, err
|
|
}
|
|
key := data.NewPublicKey(algorithm, public)
|
|
|
|
// update version and expiry
|
|
sn.Signed.Version = sn.Signed.Version + 1
|
|
sn.Signed.Expires = data.DefaultExpires(data.CanonicalSnapshotRole)
|
|
|
|
out, err := sn.ToSigned()
|
|
if err != nil {
|
|
return nil, 0, err
|
|
}
|
|
err = signed.Sign(cryptoService, out, key)
|
|
if err != nil {
|
|
return nil, 0, err
|
|
}
|
|
return out, sn.Signed.Version, nil
|
|
}
|