123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241 |
- package metadata
- import (
- "crypto/hmac"
- "crypto/sha256"
- "encoding/hex"
- "encoding/json"
- "errors"
- "github.com/docker/docker/api/types"
- "github.com/docker/docker/layer"
- "github.com/opencontainers/go-digest"
- )
- // V2MetadataService maps layer IDs to a set of known metadata for
- // the layer.
- 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 {
- store Store
- }
- var _ V2MetadataService = &v2MetadataService{}
- // V2Metadata contains the digest and source repository information for a layer.
- type V2Metadata struct {
- Digest digest.Digest
- SourceRepository string
- // 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
- }
- // CheckV2MetadataHMAC returns true if the given "meta" is tagged with a hmac hashed by the given "key".
- 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"`
- }
- // 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.
- func NewV2MetadataService(store Store) V2MetadataService {
- return &v2MetadataService{
- store: store,
- }
- }
- func (serv *v2MetadataService) diffIDNamespace() string {
- return "v2metadata-by-diffid"
- }
- func (serv *v2MetadataService) digestNamespace() string {
- return "diffid-by-digest"
- }
- func (serv *v2MetadataService) diffIDKey(diffID layer.DiffID) string {
- return string(digest.Digest(diffID).Algorithm()) + "/" + digest.Digest(diffID).Hex()
- }
- func (serv *v2MetadataService) digestKey(dgst digest.Digest) string {
- return string(dgst.Algorithm()) + "/" + dgst.Hex()
- }
- // GetMetadata finds the metadata associated with a layer DiffID.
- func (serv *v2MetadataService) GetMetadata(diffID layer.DiffID) ([]V2Metadata, error) {
- if serv.store == nil {
- return nil, errors.New("no metadata storage")
- }
- 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.
- func (serv *v2MetadataService) GetDiffID(dgst digest.Digest) (layer.DiffID, error) {
- if serv.store == nil {
- return layer.DiffID(""), errors.New("no metadata storage")
- }
- 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.
- func (serv *v2MetadataService) Add(diffID layer.DiffID, metadata V2Metadata) error {
- 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
- }
- 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))
- }
- // 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.
- func (serv *v2MetadataService) TagAndAdd(diffID layer.DiffID, hmacKey []byte, meta V2Metadata) error {
- meta.HMAC = ComputeV2MetadataHMAC(hmacKey, &meta)
- return serv.Add(diffID, meta)
- }
- // Remove unassociates a metadata entry from a layer DiffID.
- func (serv *v2MetadataService) Remove(metadata V2Metadata) error {
- 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
- }
- 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)
- }
|