github.com/rumpl/bof@v23.0.0-rc.2+incompatible/distribution/metadata/v2_metadata_service.go (about)

     1  package metadata // import "github.com/docker/docker/distribution/metadata"
     2  
     3  import (
     4  	"crypto/hmac"
     5  	"crypto/sha256"
     6  	"encoding/hex"
     7  	"encoding/json"
     8  	"errors"
     9  
    10  	"github.com/docker/docker/api/types"
    11  	"github.com/docker/docker/layer"
    12  	"github.com/opencontainers/go-digest"
    13  )
    14  
    15  // V2MetadataService maps layer IDs to a set of known metadata for
    16  // the layer.
    17  type V2MetadataService interface {
    18  	GetMetadata(diffID layer.DiffID) ([]V2Metadata, error)
    19  	GetDiffID(dgst digest.Digest) (layer.DiffID, error)
    20  	Add(diffID layer.DiffID, metadata V2Metadata) error
    21  	TagAndAdd(diffID layer.DiffID, hmacKey []byte, metadata V2Metadata) error
    22  	Remove(metadata V2Metadata) error
    23  }
    24  
    25  // v2MetadataService implements V2MetadataService
    26  type v2MetadataService struct {
    27  	store Store
    28  }
    29  
    30  var _ V2MetadataService = &v2MetadataService{}
    31  
    32  // V2Metadata contains the digest and source repository information for a layer.
    33  type V2Metadata struct {
    34  	Digest           digest.Digest
    35  	SourceRepository string
    36  	// HMAC hashes above attributes with recent authconfig digest used as a key in order to determine matching
    37  	// metadata entries accompanied by the same credentials without actually exposing them.
    38  	HMAC string
    39  }
    40  
    41  // CheckV2MetadataHMAC returns true if the given "meta" is tagged with a hmac hashed by the given "key".
    42  func CheckV2MetadataHMAC(meta *V2Metadata, key []byte) bool {
    43  	if len(meta.HMAC) == 0 || len(key) == 0 {
    44  		return len(meta.HMAC) == 0 && len(key) == 0
    45  	}
    46  	mac := hmac.New(sha256.New, key)
    47  	mac.Write([]byte(meta.Digest))
    48  	mac.Write([]byte(meta.SourceRepository))
    49  	expectedMac := mac.Sum(nil)
    50  
    51  	storedMac, err := hex.DecodeString(meta.HMAC)
    52  	if err != nil {
    53  		return false
    54  	}
    55  
    56  	return hmac.Equal(storedMac, expectedMac)
    57  }
    58  
    59  // ComputeV2MetadataHMAC returns a hmac for the given "meta" hash by the given key.
    60  func ComputeV2MetadataHMAC(key []byte, meta *V2Metadata) string {
    61  	if len(key) == 0 || meta == nil {
    62  		return ""
    63  	}
    64  	mac := hmac.New(sha256.New, key)
    65  	mac.Write([]byte(meta.Digest))
    66  	mac.Write([]byte(meta.SourceRepository))
    67  	return hex.EncodeToString(mac.Sum(nil))
    68  }
    69  
    70  // ComputeV2MetadataHMACKey returns a key for the given "authConfig" that can be used to hash v2 metadata
    71  // entries.
    72  func ComputeV2MetadataHMACKey(authConfig *types.AuthConfig) ([]byte, error) {
    73  	if authConfig == nil {
    74  		return nil, nil
    75  	}
    76  	key := authConfigKeyInput{
    77  		Username:      authConfig.Username,
    78  		Password:      authConfig.Password,
    79  		Auth:          authConfig.Auth,
    80  		IdentityToken: authConfig.IdentityToken,
    81  		RegistryToken: authConfig.RegistryToken,
    82  	}
    83  	buf, err := json.Marshal(&key)
    84  	if err != nil {
    85  		return nil, err
    86  	}
    87  	return []byte(digest.FromBytes(buf)), nil
    88  }
    89  
    90  // authConfigKeyInput is a reduced AuthConfig structure holding just relevant credential data eligible for
    91  // hmac key creation.
    92  type authConfigKeyInput struct {
    93  	Username string `json:"username,omitempty"`
    94  	Password string `json:"password,omitempty"`
    95  	Auth     string `json:"auth,omitempty"`
    96  
    97  	IdentityToken string `json:"identitytoken,omitempty"`
    98  	RegistryToken string `json:"registrytoken,omitempty"`
    99  }
   100  
   101  // maxMetadata is the number of metadata entries to keep per layer DiffID.
   102  const maxMetadata = 50
   103  
   104  // NewV2MetadataService creates a new diff ID to v2 metadata mapping service.
   105  func NewV2MetadataService(store Store) V2MetadataService {
   106  	return &v2MetadataService{
   107  		store: store,
   108  	}
   109  }
   110  
   111  func (serv *v2MetadataService) diffIDNamespace() string {
   112  	return "v2metadata-by-diffid"
   113  }
   114  
   115  func (serv *v2MetadataService) digestNamespace() string {
   116  	return "diffid-by-digest"
   117  }
   118  
   119  func (serv *v2MetadataService) diffIDKey(diffID layer.DiffID) string {
   120  	return string(digest.Digest(diffID).Algorithm()) + "/" + digest.Digest(diffID).Encoded()
   121  }
   122  
   123  func (serv *v2MetadataService) digestKey(dgst digest.Digest) string {
   124  	return string(dgst.Algorithm()) + "/" + dgst.Encoded()
   125  }
   126  
   127  // GetMetadata finds the metadata associated with a layer DiffID.
   128  func (serv *v2MetadataService) GetMetadata(diffID layer.DiffID) ([]V2Metadata, error) {
   129  	if serv.store == nil {
   130  		return nil, errors.New("no metadata storage")
   131  	}
   132  	jsonBytes, err := serv.store.Get(serv.diffIDNamespace(), serv.diffIDKey(diffID))
   133  	if err != nil {
   134  		return nil, err
   135  	}
   136  
   137  	var metadata []V2Metadata
   138  	if err := json.Unmarshal(jsonBytes, &metadata); err != nil {
   139  		return nil, err
   140  	}
   141  
   142  	return metadata, nil
   143  }
   144  
   145  // GetDiffID finds a layer DiffID from a digest.
   146  func (serv *v2MetadataService) GetDiffID(dgst digest.Digest) (layer.DiffID, error) {
   147  	if serv.store == nil {
   148  		return layer.DiffID(""), errors.New("no metadata storage")
   149  	}
   150  	diffIDBytes, err := serv.store.Get(serv.digestNamespace(), serv.digestKey(dgst))
   151  	if err != nil {
   152  		return layer.DiffID(""), err
   153  	}
   154  
   155  	return layer.DiffID(diffIDBytes), nil
   156  }
   157  
   158  // Add associates metadata with a layer DiffID. If too many metadata entries are
   159  // present, the oldest one is dropped.
   160  func (serv *v2MetadataService) Add(diffID layer.DiffID, metadata V2Metadata) error {
   161  	if serv.store == nil {
   162  		// Support a service which has no backend storage, in this case
   163  		// an add becomes a no-op.
   164  		// TODO: implement in memory storage
   165  		return nil
   166  	}
   167  	oldMetadata, err := serv.GetMetadata(diffID)
   168  	if err != nil {
   169  		oldMetadata = nil
   170  	}
   171  	newMetadata := make([]V2Metadata, 0, len(oldMetadata)+1)
   172  
   173  	// Copy all other metadata to new slice
   174  	for _, oldMeta := range oldMetadata {
   175  		if oldMeta != metadata {
   176  			newMetadata = append(newMetadata, oldMeta)
   177  		}
   178  	}
   179  
   180  	newMetadata = append(newMetadata, metadata)
   181  
   182  	if len(newMetadata) > maxMetadata {
   183  		newMetadata = newMetadata[len(newMetadata)-maxMetadata:]
   184  	}
   185  
   186  	jsonBytes, err := json.Marshal(newMetadata)
   187  	if err != nil {
   188  		return err
   189  	}
   190  
   191  	err = serv.store.Set(serv.diffIDNamespace(), serv.diffIDKey(diffID), jsonBytes)
   192  	if err != nil {
   193  		return err
   194  	}
   195  
   196  	return serv.store.Set(serv.digestNamespace(), serv.digestKey(metadata.Digest), []byte(diffID))
   197  }
   198  
   199  // TagAndAdd amends the given "meta" for hmac hashed by the given "hmacKey" and associates it with a layer
   200  // DiffID. If too many metadata entries are present, the oldest one is dropped.
   201  func (serv *v2MetadataService) TagAndAdd(diffID layer.DiffID, hmacKey []byte, meta V2Metadata) error {
   202  	meta.HMAC = ComputeV2MetadataHMAC(hmacKey, &meta)
   203  	return serv.Add(diffID, meta)
   204  }
   205  
   206  // Remove disassociates a metadata entry from a layer DiffID.
   207  func (serv *v2MetadataService) Remove(metadata V2Metadata) error {
   208  	if serv.store == nil {
   209  		// Support a service which has no backend storage, in this case
   210  		// an remove becomes a no-op.
   211  		// TODO: implement in memory storage
   212  		return nil
   213  	}
   214  	diffID, err := serv.GetDiffID(metadata.Digest)
   215  	if err != nil {
   216  		return err
   217  	}
   218  	oldMetadata, err := serv.GetMetadata(diffID)
   219  	if err != nil {
   220  		oldMetadata = nil
   221  	}
   222  	newMetadata := make([]V2Metadata, 0, len(oldMetadata))
   223  
   224  	// Copy all other metadata to new slice
   225  	for _, oldMeta := range oldMetadata {
   226  		if oldMeta != metadata {
   227  			newMetadata = append(newMetadata, oldMeta)
   228  		}
   229  	}
   230  
   231  	if len(newMetadata) == 0 {
   232  		return serv.store.Delete(serv.diffIDNamespace(), serv.diffIDKey(diffID))
   233  	}
   234  
   235  	jsonBytes, err := json.Marshal(newMetadata)
   236  	if err != nil {
   237  		return err
   238  	}
   239  
   240  	return serv.store.Set(serv.diffIDNamespace(), serv.diffIDKey(diffID), jsonBytes)
   241  }