mirror of
				https://gitea.com/Lydanne/buildx.git
				synced 2025-10-31 16:13:45 +08:00 
			
		
		
		
	
		
			
				
	
	
		
			340 lines
		
	
	
		
			8.6 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			340 lines
		
	
	
		
			8.6 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| package data
 | |
| 
 | |
| import (
 | |
| 	"fmt"
 | |
| 	"path"
 | |
| 	"regexp"
 | |
| 	"strings"
 | |
| 
 | |
| 	"github.com/sirupsen/logrus"
 | |
| )
 | |
| 
 | |
| // Canonical base role names
 | |
| var (
 | |
| 	CanonicalRootRole      RoleName = "root"
 | |
| 	CanonicalTargetsRole   RoleName = "targets"
 | |
| 	CanonicalSnapshotRole  RoleName = "snapshot"
 | |
| 	CanonicalTimestampRole RoleName = "timestamp"
 | |
| )
 | |
| 
 | |
| // BaseRoles is an easy to iterate list of the top level
 | |
| // roles.
 | |
| var BaseRoles = []RoleName{
 | |
| 	CanonicalRootRole,
 | |
| 	CanonicalTargetsRole,
 | |
| 	CanonicalSnapshotRole,
 | |
| 	CanonicalTimestampRole,
 | |
| }
 | |
| 
 | |
| // Regex for validating delegation names
 | |
| var delegationRegexp = regexp.MustCompile("^[-a-z0-9_/]+$")
 | |
| 
 | |
| // ErrNoSuchRole indicates the roles doesn't exist
 | |
| type ErrNoSuchRole struct {
 | |
| 	Role RoleName
 | |
| }
 | |
| 
 | |
| func (e ErrNoSuchRole) Error() string {
 | |
| 	return fmt.Sprintf("role does not exist: %s", e.Role)
 | |
| }
 | |
| 
 | |
| // ErrInvalidRole represents an error regarding a role. Typically
 | |
| // something like a role for which sone of the public keys were
 | |
| // not found in the TUF repo.
 | |
| type ErrInvalidRole struct {
 | |
| 	Role   RoleName
 | |
| 	Reason string
 | |
| }
 | |
| 
 | |
| func (e ErrInvalidRole) Error() string {
 | |
| 	if e.Reason != "" {
 | |
| 		return fmt.Sprintf("tuf: invalid role %s. %s", e.Role, e.Reason)
 | |
| 	}
 | |
| 	return fmt.Sprintf("tuf: invalid role %s.", e.Role)
 | |
| }
 | |
| 
 | |
| // ValidRole only determines the name is semantically
 | |
| // correct. For target delegated roles, it does NOT check
 | |
| // the the appropriate parent roles exist.
 | |
| func ValidRole(name RoleName) bool {
 | |
| 	if IsDelegation(name) {
 | |
| 		return true
 | |
| 	}
 | |
| 
 | |
| 	for _, v := range BaseRoles {
 | |
| 		if name == v {
 | |
| 			return true
 | |
| 		}
 | |
| 	}
 | |
| 	return false
 | |
| }
 | |
| 
 | |
| // IsDelegation checks if the role is a delegation or a root role
 | |
| func IsDelegation(role RoleName) bool {
 | |
| 	strRole := role.String()
 | |
| 	targetsBase := CanonicalTargetsRole + "/"
 | |
| 
 | |
| 	whitelistedChars := delegationRegexp.MatchString(strRole)
 | |
| 
 | |
| 	// Limit size of full role string to 255 chars for db column size limit
 | |
| 	correctLength := len(role) < 256
 | |
| 
 | |
| 	// Removes ., .., extra slashes, and trailing slash
 | |
| 	isClean := path.Clean(strRole) == strRole
 | |
| 	return strings.HasPrefix(strRole, targetsBase.String()) &&
 | |
| 		whitelistedChars &&
 | |
| 		correctLength &&
 | |
| 		isClean
 | |
| }
 | |
| 
 | |
| // IsBaseRole checks if the role is a base role
 | |
| func IsBaseRole(role RoleName) bool {
 | |
| 	for _, baseRole := range BaseRoles {
 | |
| 		if role == baseRole {
 | |
| 			return true
 | |
| 		}
 | |
| 	}
 | |
| 	return false
 | |
| }
 | |
| 
 | |
| // IsWildDelegation determines if a role represents a valid wildcard delegation
 | |
| // path, i.e. targets/*, targets/foo/*.
 | |
| // The wildcard may only appear as the final part of the delegation and must
 | |
| // be a whole segment, i.e. targets/foo* is not a valid wildcard delegation.
 | |
| func IsWildDelegation(role RoleName) bool {
 | |
| 	if path.Clean(role.String()) != role.String() {
 | |
| 		return false
 | |
| 	}
 | |
| 	base := role.Parent()
 | |
| 	if !(IsDelegation(base) || base == CanonicalTargetsRole) {
 | |
| 		return false
 | |
| 	}
 | |
| 	return role[len(role)-2:] == "/*"
 | |
| }
 | |
| 
 | |
| // BaseRole is an internal representation of a root/targets/snapshot/timestamp role, with its public keys included
 | |
| type BaseRole struct {
 | |
| 	Keys      map[string]PublicKey
 | |
| 	Name      RoleName
 | |
| 	Threshold int
 | |
| }
 | |
| 
 | |
| // NewBaseRole creates a new BaseRole object with the provided parameters
 | |
| func NewBaseRole(name RoleName, threshold int, keys ...PublicKey) BaseRole {
 | |
| 	r := BaseRole{
 | |
| 		Name:      name,
 | |
| 		Threshold: threshold,
 | |
| 		Keys:      make(map[string]PublicKey),
 | |
| 	}
 | |
| 	for _, k := range keys {
 | |
| 		r.Keys[k.ID()] = k
 | |
| 	}
 | |
| 	return r
 | |
| }
 | |
| 
 | |
| // ListKeys retrieves the public keys valid for this role
 | |
| func (b BaseRole) ListKeys() KeyList {
 | |
| 	return listKeys(b.Keys)
 | |
| }
 | |
| 
 | |
| // ListKeyIDs retrieves the list of key IDs valid for this role
 | |
| func (b BaseRole) ListKeyIDs() []string {
 | |
| 	return listKeyIDs(b.Keys)
 | |
| }
 | |
| 
 | |
| // Equals returns whether this BaseRole equals another BaseRole
 | |
| func (b BaseRole) Equals(o BaseRole) bool {
 | |
| 	if b.Threshold != o.Threshold || b.Name != o.Name || len(b.Keys) != len(o.Keys) {
 | |
| 		return false
 | |
| 	}
 | |
| 
 | |
| 	for keyID, key := range b.Keys {
 | |
| 		oKey, ok := o.Keys[keyID]
 | |
| 		if !ok || key.ID() != oKey.ID() {
 | |
| 			return false
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return true
 | |
| }
 | |
| 
 | |
| // DelegationRole is an internal representation of a delegation role, with its public keys included
 | |
| type DelegationRole struct {
 | |
| 	BaseRole
 | |
| 	Paths []string
 | |
| }
 | |
| 
 | |
| func listKeys(keyMap map[string]PublicKey) KeyList {
 | |
| 	keys := KeyList{}
 | |
| 	for _, key := range keyMap {
 | |
| 		keys = append(keys, key)
 | |
| 	}
 | |
| 	return keys
 | |
| }
 | |
| 
 | |
| func listKeyIDs(keyMap map[string]PublicKey) []string {
 | |
| 	keyIDs := []string{}
 | |
| 	for id := range keyMap {
 | |
| 		keyIDs = append(keyIDs, id)
 | |
| 	}
 | |
| 	return keyIDs
 | |
| }
 | |
| 
 | |
| // Restrict restricts the paths and path hash prefixes for the passed in delegation role,
 | |
| // returning a copy of the role with validated paths as if it was a direct child
 | |
| func (d DelegationRole) Restrict(child DelegationRole) (DelegationRole, error) {
 | |
| 	if !d.IsParentOf(child) {
 | |
| 		return DelegationRole{}, fmt.Errorf("%s is not a parent of %s", d.Name, child.Name)
 | |
| 	}
 | |
| 	return DelegationRole{
 | |
| 		BaseRole: BaseRole{
 | |
| 			Keys:      child.Keys,
 | |
| 			Name:      child.Name,
 | |
| 			Threshold: child.Threshold,
 | |
| 		},
 | |
| 		Paths: RestrictDelegationPathPrefixes(d.Paths, child.Paths),
 | |
| 	}, nil
 | |
| }
 | |
| 
 | |
| // IsParentOf returns whether the passed in delegation role is the direct child of this role,
 | |
| // determined by delegation name.
 | |
| // Ex: targets/a is a direct parent of targets/a/b, but targets/a is not a direct parent of targets/a/b/c
 | |
| func (d DelegationRole) IsParentOf(child DelegationRole) bool {
 | |
| 	return path.Dir(child.Name.String()) == d.Name.String()
 | |
| }
 | |
| 
 | |
| // CheckPaths checks if a given path is valid for the role
 | |
| func (d DelegationRole) CheckPaths(path string) bool {
 | |
| 	return checkPaths(path, d.Paths)
 | |
| }
 | |
| 
 | |
| func checkPaths(path string, permitted []string) bool {
 | |
| 	for _, p := range permitted {
 | |
| 		if strings.HasPrefix(path, p) {
 | |
| 			return true
 | |
| 		}
 | |
| 	}
 | |
| 	return false
 | |
| }
 | |
| 
 | |
| // RestrictDelegationPathPrefixes returns the list of valid delegationPaths that are prefixed by parentPaths
 | |
| func RestrictDelegationPathPrefixes(parentPaths, delegationPaths []string) []string {
 | |
| 	validPaths := []string{}
 | |
| 	if len(delegationPaths) == 0 {
 | |
| 		return validPaths
 | |
| 	}
 | |
| 
 | |
| 	// Validate each individual delegation path
 | |
| 	for _, delgPath := range delegationPaths {
 | |
| 		isPrefixed := false
 | |
| 		for _, parentPath := range parentPaths {
 | |
| 			if strings.HasPrefix(delgPath, parentPath) {
 | |
| 				isPrefixed = true
 | |
| 				break
 | |
| 			}
 | |
| 		}
 | |
| 		// If the delegation path did not match prefix against any parent path, it is not valid
 | |
| 		if isPrefixed {
 | |
| 			validPaths = append(validPaths, delgPath)
 | |
| 		}
 | |
| 	}
 | |
| 	return validPaths
 | |
| }
 | |
| 
 | |
| // RootRole is a cut down role as it appears in the root.json
 | |
| // Eventually should only be used for immediately before and after serialization/deserialization
 | |
| type RootRole struct {
 | |
| 	KeyIDs    []string `json:"keyids"`
 | |
| 	Threshold int      `json:"threshold"`
 | |
| }
 | |
| 
 | |
| // Role is a more verbose role as they appear in targets delegations
 | |
| // Eventually should only be used for immediately before and after serialization/deserialization
 | |
| type Role struct {
 | |
| 	RootRole
 | |
| 	Name  RoleName `json:"name"`
 | |
| 	Paths []string `json:"paths,omitempty"`
 | |
| }
 | |
| 
 | |
| // NewRole creates a new Role object from the given parameters
 | |
| func NewRole(name RoleName, threshold int, keyIDs, paths []string) (*Role, error) {
 | |
| 	if IsDelegation(name) {
 | |
| 		if len(paths) == 0 {
 | |
| 			logrus.Debugf("role %s with no Paths will never be able to publish content until one or more are added", name)
 | |
| 		}
 | |
| 	}
 | |
| 	if threshold < 1 {
 | |
| 		return nil, ErrInvalidRole{Role: name}
 | |
| 	}
 | |
| 	if !ValidRole(name) {
 | |
| 		return nil, ErrInvalidRole{Role: name}
 | |
| 	}
 | |
| 	return &Role{
 | |
| 		RootRole: RootRole{
 | |
| 			KeyIDs:    keyIDs,
 | |
| 			Threshold: threshold,
 | |
| 		},
 | |
| 		Name:  name,
 | |
| 		Paths: paths,
 | |
| 	}, nil
 | |
| 
 | |
| }
 | |
| 
 | |
| // CheckPaths checks if a given path is valid for the role
 | |
| func (r Role) CheckPaths(path string) bool {
 | |
| 	return checkPaths(path, r.Paths)
 | |
| }
 | |
| 
 | |
| // AddKeys merges the ids into the current list of role key ids
 | |
| func (r *Role) AddKeys(ids []string) {
 | |
| 	r.KeyIDs = mergeStrSlices(r.KeyIDs, ids)
 | |
| }
 | |
| 
 | |
| // AddPaths merges the paths into the current list of role paths
 | |
| func (r *Role) AddPaths(paths []string) error {
 | |
| 	if len(paths) == 0 {
 | |
| 		return nil
 | |
| 	}
 | |
| 	r.Paths = mergeStrSlices(r.Paths, paths)
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| // RemoveKeys removes the ids from the current list of key ids
 | |
| func (r *Role) RemoveKeys(ids []string) {
 | |
| 	r.KeyIDs = subtractStrSlices(r.KeyIDs, ids)
 | |
| }
 | |
| 
 | |
| // RemovePaths removes the paths from the current list of role paths
 | |
| func (r *Role) RemovePaths(paths []string) {
 | |
| 	r.Paths = subtractStrSlices(r.Paths, paths)
 | |
| }
 | |
| 
 | |
| func mergeStrSlices(orig, new []string) []string {
 | |
| 	have := make(map[string]bool)
 | |
| 	for _, e := range orig {
 | |
| 		have[e] = true
 | |
| 	}
 | |
| 	merged := make([]string, len(orig), len(orig)+len(new))
 | |
| 	copy(merged, orig)
 | |
| 	for _, e := range new {
 | |
| 		if !have[e] {
 | |
| 			merged = append(merged, e)
 | |
| 		}
 | |
| 	}
 | |
| 	return merged
 | |
| }
 | |
| 
 | |
| func subtractStrSlices(orig, remove []string) []string {
 | |
| 	kill := make(map[string]bool)
 | |
| 	for _, e := range remove {
 | |
| 		kill[e] = true
 | |
| 	}
 | |
| 	var keep []string
 | |
| 	for _, e := range orig {
 | |
| 		if !kill[e] {
 | |
| 			keep = append(keep, e)
 | |
| 		}
 | |
| 	}
 | |
| 	return keep
 | |
| }
 | 
