10 "github.com/docker/docker/api/types"
11 "github.com/docker/docker/layer"
12 "github.com/opencontainers/go-digest"
15 // V2MetadataService maps layer IDs to a set of known metadata for
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
25 // v2MetadataService implements V2MetadataService
26 type v2MetadataService struct {
30 var _ V2MetadataService = &v2MetadataService{}
32 // V2Metadata contains the digest and source repository information for a layer.
33 type V2Metadata struct {
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.
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
46 mac := hmac.New(sha256.New, key)
47 mac.Write([]byte(meta.Digest))
48 mac.Write([]byte(meta.SourceRepository))
49 expectedMac := mac.Sum(nil)
51 storedMac, err := hex.DecodeString(meta.HMAC)
56 return hmac.Equal(storedMac, expectedMac)
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 {
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))
70 // ComputeV2MetadataHMACKey returns a key for the given "authConfig" that can be used to hash v2 metadata
72 func ComputeV2MetadataHMACKey(authConfig *types.AuthConfig) ([]byte, error) {
73 if authConfig == nil {
76 key := authConfigKeyInput{
77 Username: authConfig.Username,
78 Password: authConfig.Password,
79 Auth: authConfig.Auth,
80 IdentityToken: authConfig.IdentityToken,
81 RegistryToken: authConfig.RegistryToken,
83 buf, err := json.Marshal(&key)
87 return []byte(digest.FromBytes([]byte(buf))), nil
90 // authConfigKeyInput is a reduced AuthConfig structure holding just relevant credential data eligible for
92 type authConfigKeyInput struct {
93 Username string `json:"username,omitempty"`
94 Password string `json:"password,omitempty"`
95 Auth string `json:"auth,omitempty"`
97 IdentityToken string `json:"identitytoken,omitempty"`
98 RegistryToken string `json:"registrytoken,omitempty"`
101 // maxMetadata is the number of metadata entries to keep per layer DiffID.
102 const maxMetadata = 50
104 // NewV2MetadataService creates a new diff ID to v2 metadata mapping service.
105 func NewV2MetadataService(store Store) V2MetadataService {
106 return &v2MetadataService{
111 func (serv *v2MetadataService) diffIDNamespace() string {
112 return "v2metadata-by-diffid"
115 func (serv *v2MetadataService) digestNamespace() string {
116 return "diffid-by-digest"
119 func (serv *v2MetadataService) diffIDKey(diffID layer.DiffID) string {
120 return string(digest.Digest(diffID).Algorithm()) + "/" + digest.Digest(diffID).Hex()
123 func (serv *v2MetadataService) digestKey(dgst digest.Digest) string {
124 return string(dgst.Algorithm()) + "/" + dgst.Hex()
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")
132 jsonBytes, err := serv.store.Get(serv.diffIDNamespace(), serv.diffIDKey(diffID))
137 var metadata []V2Metadata
138 if err := json.Unmarshal(jsonBytes, &metadata); err != nil {
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")
150 diffIDBytes, err := serv.store.Get(serv.digestNamespace(), serv.digestKey(dgst))
152 return layer.DiffID(""), err
155 return layer.DiffID(diffIDBytes), nil
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
167 oldMetadata, err := serv.GetMetadata(diffID)
171 newMetadata := make([]V2Metadata, 0, len(oldMetadata)+1)
173 // Copy all other metadata to new slice
174 for _, oldMeta := range oldMetadata {
175 if oldMeta != metadata {
176 newMetadata = append(newMetadata, oldMeta)
180 newMetadata = append(newMetadata, metadata)
182 if len(newMetadata) > maxMetadata {
183 newMetadata = newMetadata[len(newMetadata)-maxMetadata:]
186 jsonBytes, err := json.Marshal(newMetadata)
191 err = serv.store.Set(serv.diffIDNamespace(), serv.diffIDKey(diffID), jsonBytes)
196 return serv.store.Set(serv.digestNamespace(), serv.digestKey(metadata.Digest), []byte(diffID))
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)
206 // Remove unassociates 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
214 diffID, err := serv.GetDiffID(metadata.Digest)
218 oldMetadata, err := serv.GetMetadata(diffID)
222 newMetadata := make([]V2Metadata, 0, len(oldMetadata))
224 // Copy all other metadata to new slice
225 for _, oldMeta := range oldMetadata {
226 if oldMeta != metadata {
227 newMetadata = append(newMetadata, oldMeta)
231 if len(newMetadata) == 0 {
232 return serv.store.Delete(serv.diffIDNamespace(), serv.diffIDKey(diffID))
235 jsonBytes, err := json.Marshal(newMetadata)
240 return serv.store.Set(serv.diffIDNamespace(), serv.diffIDKey(diffID), jsonBytes)