Tizen_4.0 base
[platform/upstream/docker-engine.git] / distribution / metadata / v2_metadata_service.go
1 package 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([]byte(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).Hex()
121 }
122
123 func (serv *v2MetadataService) digestKey(dgst digest.Digest) string {
124         return string(dgst.Algorithm()) + "/" + dgst.Hex()
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 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
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 }