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 }