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 }