distribution/metadata/v2_metadata_service.go
63099477
 package metadata
 
 import (
0928f3f2
 	"crypto/hmac"
 	"crypto/sha256"
 	"encoding/hex"
63099477
 	"encoding/json"
3d86b0c7
 	"errors"
63099477
 
0928f3f2
 	"github.com/docker/docker/api/types"
63099477
 	"github.com/docker/docker/layer"
7a855799
 	"github.com/opencontainers/go-digest"
63099477
 )
 
 // V2MetadataService maps layer IDs to a set of known metadata for
 // the layer.
d3bd14a4
 type V2MetadataService interface {
 	GetMetadata(diffID layer.DiffID) ([]V2Metadata, error)
 	GetDiffID(dgst digest.Digest) (layer.DiffID, error)
 	Add(diffID layer.DiffID, metadata V2Metadata) error
 	TagAndAdd(diffID layer.DiffID, hmacKey []byte, metadata V2Metadata) error
 	Remove(metadata V2Metadata) error
 }
 
 // v2MetadataService implements V2MetadataService
 type v2MetadataService struct {
63099477
 	store Store
 }
 
d3bd14a4
 var _ V2MetadataService = &v2MetadataService{}
 
63099477
 // V2Metadata contains the digest and source repository information for a layer.
 type V2Metadata struct {
 	Digest           digest.Digest
 	SourceRepository string
0928f3f2
 	// HMAC hashes above attributes with recent authconfig digest used as a key in order to determine matching
 	// metadata entries accompanied by the same credentials without actually exposing them.
 	HMAC string
 }
 
128d07d3
 // CheckV2MetadataHMAC returns true if the given "meta" is tagged with a hmac hashed by the given "key".
0928f3f2
 func CheckV2MetadataHMAC(meta *V2Metadata, key []byte) bool {
 	if len(meta.HMAC) == 0 || len(key) == 0 {
 		return len(meta.HMAC) == 0 && len(key) == 0
 	}
 	mac := hmac.New(sha256.New, key)
 	mac.Write([]byte(meta.Digest))
 	mac.Write([]byte(meta.SourceRepository))
 	expectedMac := mac.Sum(nil)
 
 	storedMac, err := hex.DecodeString(meta.HMAC)
 	if err != nil {
 		return false
 	}
 
 	return hmac.Equal(storedMac, expectedMac)
 }
 
 // ComputeV2MetadataHMAC returns a hmac for the given "meta" hash by the given key.
 func ComputeV2MetadataHMAC(key []byte, meta *V2Metadata) string {
 	if len(key) == 0 || meta == nil {
 		return ""
 	}
 	mac := hmac.New(sha256.New, key)
 	mac.Write([]byte(meta.Digest))
 	mac.Write([]byte(meta.SourceRepository))
 	return hex.EncodeToString(mac.Sum(nil))
 }
 
 // ComputeV2MetadataHMACKey returns a key for the given "authConfig" that can be used to hash v2 metadata
 // entries.
 func ComputeV2MetadataHMACKey(authConfig *types.AuthConfig) ([]byte, error) {
 	if authConfig == nil {
 		return nil, nil
 	}
 	key := authConfigKeyInput{
 		Username:      authConfig.Username,
 		Password:      authConfig.Password,
 		Auth:          authConfig.Auth,
 		IdentityToken: authConfig.IdentityToken,
 		RegistryToken: authConfig.RegistryToken,
 	}
 	buf, err := json.Marshal(&key)
 	if err != nil {
 		return nil, err
 	}
 	return []byte(digest.FromBytes([]byte(buf))), nil
 }
 
 // authConfigKeyInput is a reduced AuthConfig structure holding just relevant credential data eligible for
 // hmac key creation.
 type authConfigKeyInput struct {
 	Username string `json:"username,omitempty"`
 	Password string `json:"password,omitempty"`
 	Auth     string `json:"auth,omitempty"`
 
 	IdentityToken string `json:"identitytoken,omitempty"`
 	RegistryToken string `json:"registrytoken,omitempty"`
63099477
 }
 
 // maxMetadata is the number of metadata entries to keep per layer DiffID.
 const maxMetadata = 50
 
 // NewV2MetadataService creates a new diff ID to v2 metadata mapping service.
d3bd14a4
 func NewV2MetadataService(store Store) V2MetadataService {
 	return &v2MetadataService{
63099477
 		store: store,
 	}
 }
 
d3bd14a4
 func (serv *v2MetadataService) diffIDNamespace() string {
63099477
 	return "v2metadata-by-diffid"
 }
 
d3bd14a4
 func (serv *v2MetadataService) digestNamespace() string {
63099477
 	return "diffid-by-digest"
 }
 
d3bd14a4
 func (serv *v2MetadataService) diffIDKey(diffID layer.DiffID) string {
63099477
 	return string(digest.Digest(diffID).Algorithm()) + "/" + digest.Digest(diffID).Hex()
 }
 
d3bd14a4
 func (serv *v2MetadataService) digestKey(dgst digest.Digest) string {
63099477
 	return string(dgst.Algorithm()) + "/" + dgst.Hex()
 }
 
 // GetMetadata finds the metadata associated with a layer DiffID.
d3bd14a4
 func (serv *v2MetadataService) GetMetadata(diffID layer.DiffID) ([]V2Metadata, error) {
3d86b0c7
 	if serv.store == nil {
 		return nil, errors.New("no metadata storage")
 	}
63099477
 	jsonBytes, err := serv.store.Get(serv.diffIDNamespace(), serv.diffIDKey(diffID))
 	if err != nil {
 		return nil, err
 	}
 
 	var metadata []V2Metadata
 	if err := json.Unmarshal(jsonBytes, &metadata); err != nil {
 		return nil, err
 	}
 
 	return metadata, nil
 }
 
 // GetDiffID finds a layer DiffID from a digest.
d3bd14a4
 func (serv *v2MetadataService) GetDiffID(dgst digest.Digest) (layer.DiffID, error) {
3d86b0c7
 	if serv.store == nil {
 		return layer.DiffID(""), errors.New("no metadata storage")
 	}
63099477
 	diffIDBytes, err := serv.store.Get(serv.digestNamespace(), serv.digestKey(dgst))
 	if err != nil {
 		return layer.DiffID(""), err
 	}
 
 	return layer.DiffID(diffIDBytes), nil
 }
 
 // Add associates metadata with a layer DiffID. If too many metadata entries are
 // present, the oldest one is dropped.
d3bd14a4
 func (serv *v2MetadataService) Add(diffID layer.DiffID, metadata V2Metadata) error {
3d86b0c7
 	if serv.store == nil {
 		// Support a service which has no backend storage, in this case
 		// an add becomes a no-op.
 		// TODO: implement in memory storage
 		return nil
 	}
63099477
 	oldMetadata, err := serv.GetMetadata(diffID)
 	if err != nil {
 		oldMetadata = nil
 	}
 	newMetadata := make([]V2Metadata, 0, len(oldMetadata)+1)
 
 	// Copy all other metadata to new slice
 	for _, oldMeta := range oldMetadata {
 		if oldMeta != metadata {
 			newMetadata = append(newMetadata, oldMeta)
 		}
 	}
 
 	newMetadata = append(newMetadata, metadata)
 
 	if len(newMetadata) > maxMetadata {
 		newMetadata = newMetadata[len(newMetadata)-maxMetadata:]
 	}
 
 	jsonBytes, err := json.Marshal(newMetadata)
 	if err != nil {
 		return err
 	}
 
 	err = serv.store.Set(serv.diffIDNamespace(), serv.diffIDKey(diffID), jsonBytes)
 	if err != nil {
 		return err
 	}
 
 	return serv.store.Set(serv.digestNamespace(), serv.digestKey(metadata.Digest), []byte(diffID))
 }
 
0928f3f2
 // TagAndAdd amends the given "meta" for hmac hashed by the given "hmacKey" and associates it with a layer
 // DiffID. If too many metadata entries are present, the oldest one is dropped.
d3bd14a4
 func (serv *v2MetadataService) TagAndAdd(diffID layer.DiffID, hmacKey []byte, meta V2Metadata) error {
0928f3f2
 	meta.HMAC = ComputeV2MetadataHMAC(hmacKey, &meta)
 	return serv.Add(diffID, meta)
 }
 
39bcaee4
 // Remove disassociates a metadata entry from a layer DiffID.
d3bd14a4
 func (serv *v2MetadataService) Remove(metadata V2Metadata) error {
3d86b0c7
 	if serv.store == nil {
 		// Support a service which has no backend storage, in this case
 		// an remove becomes a no-op.
 		// TODO: implement in memory storage
 		return nil
 	}
63099477
 	diffID, err := serv.GetDiffID(metadata.Digest)
 	if err != nil {
 		return err
 	}
 	oldMetadata, err := serv.GetMetadata(diffID)
 	if err != nil {
 		oldMetadata = nil
 	}
 	newMetadata := make([]V2Metadata, 0, len(oldMetadata))
 
 	// Copy all other metadata to new slice
 	for _, oldMeta := range oldMetadata {
 		if oldMeta != metadata {
 			newMetadata = append(newMetadata, oldMeta)
 		}
 	}
 
 	if len(newMetadata) == 0 {
 		return serv.store.Delete(serv.diffIDNamespace(), serv.diffIDKey(diffID))
 	}
 
 	jsonBytes, err := json.Marshal(newMetadata)
 	if err != nil {
 		return err
 	}
 
 	return serv.store.Set(serv.diffIDNamespace(), serv.diffIDKey(diffID), jsonBytes)
 }