github.com/vieux/docker@v0.6.3-0.20161004191708-e097c2a938c7/distribution/metadata/v2_metadata_service.go (about)

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