mirror of https://github.com/docker/docs.git
231 lines
8.0 KiB
Go
231 lines
8.0 KiB
Go
package handlers
|
|
|
|
import (
|
|
"fmt"
|
|
"path"
|
|
"sort"
|
|
|
|
"github.com/Sirupsen/logrus"
|
|
|
|
"github.com/docker/go/canonical/json"
|
|
"github.com/docker/notary/server/storage"
|
|
"github.com/docker/notary/trustpinning"
|
|
"github.com/docker/notary/tuf"
|
|
"github.com/docker/notary/tuf/data"
|
|
"github.com/docker/notary/tuf/signed"
|
|
"github.com/docker/notary/tuf/utils"
|
|
"github.com/docker/notary/tuf/validation"
|
|
)
|
|
|
|
// validateUpload checks that the updates being pushed
|
|
// are semantically correct and the signatures are correct
|
|
// A list of possibly modified updates are returned if all
|
|
// validation was successful. This allows the snapshot to be
|
|
// created and added if snapshotting has been delegated to the
|
|
// server
|
|
func validateUpdate(cs signed.CryptoService, gun string, updates []storage.MetaUpdate, store storage.MetaStore) ([]storage.MetaUpdate, error) {
|
|
|
|
// some delegated targets role may be invalid based on other updates
|
|
// that have been made by other clients. We'll rebuild the slice of
|
|
// updates with only the things we should actually update
|
|
updatesToApply := make([]storage.MetaUpdate, 0, len(updates))
|
|
|
|
roles := make(map[string]storage.MetaUpdate)
|
|
for _, v := range updates {
|
|
roles[v.Role] = v
|
|
}
|
|
|
|
builder := tuf.NewRepoBuilder(gun, cs, trustpinning.TrustPinConfig{})
|
|
if err := loadFromStore(gun, data.CanonicalRootRole, builder, store); err != nil {
|
|
if _, ok := err.(storage.ErrNotFound); !ok {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
if rootUpdate, ok := roles[data.CanonicalRootRole]; ok {
|
|
builder = builder.BootstrapNewBuilder()
|
|
if err := builder.Load(data.CanonicalRootRole, rootUpdate.Data, 1, false); err != nil {
|
|
return nil, validation.ErrBadRoot{Msg: err.Error()}
|
|
}
|
|
|
|
logrus.Debug("Successfully validated root")
|
|
updatesToApply = append(updatesToApply, rootUpdate)
|
|
} else if !builder.IsLoaded(data.CanonicalRootRole) {
|
|
return nil, validation.ErrValidation{Msg: "no pre-existing root and no root provided in update."}
|
|
}
|
|
|
|
targetsToUpdate, err := loadAndValidateTargets(gun, builder, roles, store)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
updatesToApply = append(updatesToApply, targetsToUpdate...)
|
|
// there's no need to load files from the database if no targets etc...
|
|
// were uploaded because that means they haven't been updated and
|
|
// the snapshot will already contain the correct hashes and sizes for
|
|
// those targets (incl. delegated targets)
|
|
logrus.Debug("Successfully validated targets")
|
|
|
|
// At this point, root and targets must have been loaded into the repo
|
|
if snapshotUpdate, ok := roles[data.CanonicalSnapshotRole]; ok {
|
|
if err := builder.Load(data.CanonicalSnapshotRole, snapshotUpdate.Data, 1, false); err != nil {
|
|
return nil, validation.ErrBadSnapshot{Msg: err.Error()}
|
|
}
|
|
logrus.Debug("Successfully validated snapshot")
|
|
updatesToApply = append(updatesToApply, roles[data.CanonicalSnapshotRole])
|
|
} else {
|
|
// Check:
|
|
// - we have a snapshot key
|
|
// - it matches a snapshot key signed into the root.json
|
|
// Then:
|
|
// - generate a new snapshot
|
|
// - add it to the updates
|
|
update, err := generateSnapshot(gun, builder, store)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
updatesToApply = append(updatesToApply, *update)
|
|
}
|
|
|
|
// generate a timestamp immediately
|
|
update, err := generateTimestamp(gun, builder, store)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return append(updatesToApply, *update), nil
|
|
}
|
|
|
|
func loadAndValidateTargets(gun string, builder tuf.RepoBuilder, roles map[string]storage.MetaUpdate, store storage.MetaStore) ([]storage.MetaUpdate, error) {
|
|
targetsRoles := make(utils.RoleList, 0)
|
|
for role := range roles {
|
|
if role == data.CanonicalTargetsRole || data.IsDelegation(role) {
|
|
targetsRoles = append(targetsRoles, role)
|
|
}
|
|
}
|
|
|
|
// N.B. RoleList sorts paths with fewer segments first.
|
|
// By sorting, we'll always process shallower targets updates before deeper
|
|
// ones (i.e. we'll load and validate targets before targets/foo). This
|
|
// helps ensure we only load from storage when necessary in a cleaner way.
|
|
sort.Sort(targetsRoles)
|
|
|
|
updatesToApply := make([]storage.MetaUpdate, 0, len(targetsRoles))
|
|
for _, roleName := range targetsRoles {
|
|
// don't load parent if current role is "targets",
|
|
// we must load all ancestor roles, starting from `targets` and working down,
|
|
// for delegations to validate the full parent chain
|
|
var parentsToLoad []string
|
|
ancestorRole := roleName
|
|
for ancestorRole != data.CanonicalTargetsRole {
|
|
ancestorRole = path.Dir(ancestorRole)
|
|
if !builder.IsLoaded(ancestorRole) {
|
|
parentsToLoad = append(parentsToLoad, ancestorRole)
|
|
}
|
|
}
|
|
for i := len(parentsToLoad) - 1; i >= 0; i-- {
|
|
if err := loadFromStore(gun, parentsToLoad[i], builder, store); err != nil {
|
|
// if the parent doesn't exist, just keep going - loading the role will eventually fail
|
|
// due to it being an invalid role
|
|
if _, ok := err.(storage.ErrNotFound); !ok {
|
|
return nil, err
|
|
}
|
|
}
|
|
}
|
|
|
|
if err := builder.Load(roleName, roles[roleName].Data, 1, false); err != nil {
|
|
logrus.Error("ErrBadTargets: ", err.Error())
|
|
return nil, validation.ErrBadTargets{Msg: err.Error()}
|
|
}
|
|
updatesToApply = append(updatesToApply, roles[roleName])
|
|
}
|
|
|
|
return updatesToApply, nil
|
|
}
|
|
|
|
// generateSnapshot generates a new snapshot from the previous one in the store - this assumes all
|
|
// the other roles except timestamp have already been set on the repo, and will set the generated
|
|
// snapshot on the repo as well
|
|
func generateSnapshot(gun string, builder tuf.RepoBuilder, store storage.MetaStore) (*storage.MetaUpdate, error) {
|
|
var prev *data.SignedSnapshot
|
|
_, currentJSON, err := store.GetCurrent(gun, data.CanonicalSnapshotRole)
|
|
if err == nil {
|
|
prev = new(data.SignedSnapshot)
|
|
if err = json.Unmarshal(currentJSON, prev); err != nil {
|
|
logrus.Error("Failed to unmarshal existing snapshot for GUN ", gun)
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
if _, ok := err.(storage.ErrNotFound); !ok && err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
meta, ver, err := builder.GenerateSnapshot(prev)
|
|
|
|
switch err.(type) {
|
|
case nil:
|
|
return &storage.MetaUpdate{
|
|
Role: data.CanonicalSnapshotRole,
|
|
Version: ver,
|
|
Data: meta,
|
|
}, nil
|
|
case signed.ErrInsufficientSignatures, signed.ErrNoKeys, signed.ErrRoleThreshold:
|
|
// If we cannot sign the snapshot, then we don't have keys for the snapshot,
|
|
// and the client should have submitted a snapshot
|
|
return nil, validation.ErrBadHierarchy{
|
|
Missing: data.CanonicalSnapshotRole,
|
|
Msg: "no snapshot was included in update and server does not hold current snapshot key for repository"}
|
|
default:
|
|
return nil, validation.ErrValidation{Msg: err.Error()}
|
|
}
|
|
}
|
|
|
|
// generateTimestamp generates a new timestamp from the previous one in the store - this assumes all
|
|
// the other roles have already been set on the repo, and will set the generated timestamp on the repo as well
|
|
func generateTimestamp(gun string, builder tuf.RepoBuilder, store storage.MetaStore) (*storage.MetaUpdate, error) {
|
|
var prev *data.SignedTimestamp
|
|
_, currentJSON, err := store.GetCurrent(gun, data.CanonicalTimestampRole)
|
|
|
|
switch err.(type) {
|
|
case nil:
|
|
prev = new(data.SignedTimestamp)
|
|
if err := json.Unmarshal(currentJSON, prev); err != nil {
|
|
logrus.Error("Failed to unmarshal existing timestamp for GUN ", gun)
|
|
return nil, err
|
|
}
|
|
case storage.ErrNotFound:
|
|
break // this is the first timestamp ever for the repo
|
|
default:
|
|
return nil, err
|
|
}
|
|
|
|
meta, ver, err := builder.GenerateTimestamp(prev)
|
|
|
|
switch err.(type) {
|
|
case nil:
|
|
return &storage.MetaUpdate{
|
|
Role: data.CanonicalTimestampRole,
|
|
Version: ver,
|
|
Data: meta,
|
|
}, nil
|
|
case signed.ErrInsufficientSignatures, signed.ErrNoKeys:
|
|
// If we cannot sign the timestamp, then we don't have keys for the timestamp,
|
|
// and the client screwed up their root
|
|
return nil, validation.ErrBadRoot{
|
|
Msg: fmt.Sprintf("no timestamp keys exist on the server"),
|
|
}
|
|
default:
|
|
return nil, validation.ErrValidation{Msg: err.Error()}
|
|
}
|
|
}
|
|
|
|
func loadFromStore(gun, roleName string, builder tuf.RepoBuilder, store storage.MetaStore) error {
|
|
_, metaJSON, err := store.GetCurrent(gun, roleName)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if err := builder.Load(roleName, metaJSON, 1, true); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|