v2_metadata_service.go 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241
  1. package metadata
  2. import (
  3. "crypto/hmac"
  4. "crypto/sha256"
  5. "encoding/hex"
  6. "encoding/json"
  7. "errors"
  8. "github.com/docker/docker/api/types"
  9. "github.com/docker/docker/layer"
  10. "github.com/opencontainers/go-digest"
  11. )
  12. // V2MetadataService maps layer IDs to a set of known metadata for
  13. // the layer.
  14. type V2MetadataService interface {
  15. GetMetadata(diffID layer.DiffID) ([]V2Metadata, error)
  16. GetDiffID(dgst digest.Digest) (layer.DiffID, error)
  17. Add(diffID layer.DiffID, metadata V2Metadata) error
  18. TagAndAdd(diffID layer.DiffID, hmacKey []byte, metadata V2Metadata) error
  19. Remove(metadata V2Metadata) error
  20. }
  21. // v2MetadataService implements V2MetadataService
  22. type v2MetadataService struct {
  23. store Store
  24. }
  25. var _ V2MetadataService = &v2MetadataService{}
  26. // V2Metadata contains the digest and source repository information for a layer.
  27. type V2Metadata struct {
  28. Digest digest.Digest
  29. SourceRepository string
  30. // HMAC hashes above attributes with recent authconfig digest used as a key in order to determine matching
  31. // metadata entries accompanied by the same credentials without actually exposing them.
  32. HMAC string
  33. }
  34. // CheckV2MetadataHMAC returns true if the given "meta" is tagged with a hmac hashed by the given "key".
  35. func CheckV2MetadataHMAC(meta *V2Metadata, key []byte) bool {
  36. if len(meta.HMAC) == 0 || len(key) == 0 {
  37. return len(meta.HMAC) == 0 && len(key) == 0
  38. }
  39. mac := hmac.New(sha256.New, key)
  40. mac.Write([]byte(meta.Digest))
  41. mac.Write([]byte(meta.SourceRepository))
  42. expectedMac := mac.Sum(nil)
  43. storedMac, err := hex.DecodeString(meta.HMAC)
  44. if err != nil {
  45. return false
  46. }
  47. return hmac.Equal(storedMac, expectedMac)
  48. }
  49. // ComputeV2MetadataHMAC returns a hmac for the given "meta" hash by the given key.
  50. func ComputeV2MetadataHMAC(key []byte, meta *V2Metadata) string {
  51. if len(key) == 0 || meta == nil {
  52. return ""
  53. }
  54. mac := hmac.New(sha256.New, key)
  55. mac.Write([]byte(meta.Digest))
  56. mac.Write([]byte(meta.SourceRepository))
  57. return hex.EncodeToString(mac.Sum(nil))
  58. }
  59. // ComputeV2MetadataHMACKey returns a key for the given "authConfig" that can be used to hash v2 metadata
  60. // entries.
  61. func ComputeV2MetadataHMACKey(authConfig *types.AuthConfig) ([]byte, error) {
  62. if authConfig == nil {
  63. return nil, nil
  64. }
  65. key := authConfigKeyInput{
  66. Username: authConfig.Username,
  67. Password: authConfig.Password,
  68. Auth: authConfig.Auth,
  69. IdentityToken: authConfig.IdentityToken,
  70. RegistryToken: authConfig.RegistryToken,
  71. }
  72. buf, err := json.Marshal(&key)
  73. if err != nil {
  74. return nil, err
  75. }
  76. return []byte(digest.FromBytes([]byte(buf))), nil
  77. }
  78. // authConfigKeyInput is a reduced AuthConfig structure holding just relevant credential data eligible for
  79. // hmac key creation.
  80. type authConfigKeyInput struct {
  81. Username string `json:"username,omitempty"`
  82. Password string `json:"password,omitempty"`
  83. Auth string `json:"auth,omitempty"`
  84. IdentityToken string `json:"identitytoken,omitempty"`
  85. RegistryToken string `json:"registrytoken,omitempty"`
  86. }
  87. // maxMetadata is the number of metadata entries to keep per layer DiffID.
  88. const maxMetadata = 50
  89. // NewV2MetadataService creates a new diff ID to v2 metadata mapping service.
  90. func NewV2MetadataService(store Store) V2MetadataService {
  91. return &v2MetadataService{
  92. store: store,
  93. }
  94. }
  95. func (serv *v2MetadataService) diffIDNamespace() string {
  96. return "v2metadata-by-diffid"
  97. }
  98. func (serv *v2MetadataService) digestNamespace() string {
  99. return "diffid-by-digest"
  100. }
  101. func (serv *v2MetadataService) diffIDKey(diffID layer.DiffID) string {
  102. return string(digest.Digest(diffID).Algorithm()) + "/" + digest.Digest(diffID).Hex()
  103. }
  104. func (serv *v2MetadataService) digestKey(dgst digest.Digest) string {
  105. return string(dgst.Algorithm()) + "/" + dgst.Hex()
  106. }
  107. // GetMetadata finds the metadata associated with a layer DiffID.
  108. func (serv *v2MetadataService) GetMetadata(diffID layer.DiffID) ([]V2Metadata, error) {
  109. if serv.store == nil {
  110. return nil, errors.New("no metadata storage")
  111. }
  112. jsonBytes, err := serv.store.Get(serv.diffIDNamespace(), serv.diffIDKey(diffID))
  113. if err != nil {
  114. return nil, err
  115. }
  116. var metadata []V2Metadata
  117. if err := json.Unmarshal(jsonBytes, &metadata); err != nil {
  118. return nil, err
  119. }
  120. return metadata, nil
  121. }
  122. // GetDiffID finds a layer DiffID from a digest.
  123. func (serv *v2MetadataService) GetDiffID(dgst digest.Digest) (layer.DiffID, error) {
  124. if serv.store == nil {
  125. return layer.DiffID(""), errors.New("no metadata storage")
  126. }
  127. diffIDBytes, err := serv.store.Get(serv.digestNamespace(), serv.digestKey(dgst))
  128. if err != nil {
  129. return layer.DiffID(""), err
  130. }
  131. return layer.DiffID(diffIDBytes), nil
  132. }
  133. // Add associates metadata with a layer DiffID. If too many metadata entries are
  134. // present, the oldest one is dropped.
  135. func (serv *v2MetadataService) Add(diffID layer.DiffID, metadata V2Metadata) error {
  136. if serv.store == nil {
  137. // Support a service which has no backend storage, in this case
  138. // an add becomes a no-op.
  139. // TODO: implement in memory storage
  140. return nil
  141. }
  142. oldMetadata, err := serv.GetMetadata(diffID)
  143. if err != nil {
  144. oldMetadata = nil
  145. }
  146. newMetadata := make([]V2Metadata, 0, len(oldMetadata)+1)
  147. // Copy all other metadata to new slice
  148. for _, oldMeta := range oldMetadata {
  149. if oldMeta != metadata {
  150. newMetadata = append(newMetadata, oldMeta)
  151. }
  152. }
  153. newMetadata = append(newMetadata, metadata)
  154. if len(newMetadata) > maxMetadata {
  155. newMetadata = newMetadata[len(newMetadata)-maxMetadata:]
  156. }
  157. jsonBytes, err := json.Marshal(newMetadata)
  158. if err != nil {
  159. return err
  160. }
  161. err = serv.store.Set(serv.diffIDNamespace(), serv.diffIDKey(diffID), jsonBytes)
  162. if err != nil {
  163. return err
  164. }
  165. return serv.store.Set(serv.digestNamespace(), serv.digestKey(metadata.Digest), []byte(diffID))
  166. }
  167. // TagAndAdd amends the given "meta" for hmac hashed by the given "hmacKey" and associates it with a layer
  168. // DiffID. If too many metadata entries are present, the oldest one is dropped.
  169. func (serv *v2MetadataService) TagAndAdd(diffID layer.DiffID, hmacKey []byte, meta V2Metadata) error {
  170. meta.HMAC = ComputeV2MetadataHMAC(hmacKey, &meta)
  171. return serv.Add(diffID, meta)
  172. }
  173. // Remove unassociates a metadata entry from a layer DiffID.
  174. func (serv *v2MetadataService) Remove(metadata V2Metadata) error {
  175. if serv.store == nil {
  176. // Support a service which has no backend storage, in this case
  177. // an remove becomes a no-op.
  178. // TODO: implement in memory storage
  179. return nil
  180. }
  181. diffID, err := serv.GetDiffID(metadata.Digest)
  182. if err != nil {
  183. return err
  184. }
  185. oldMetadata, err := serv.GetMetadata(diffID)
  186. if err != nil {
  187. oldMetadata = nil
  188. }
  189. newMetadata := make([]V2Metadata, 0, len(oldMetadata))
  190. // Copy all other metadata to new slice
  191. for _, oldMeta := range oldMetadata {
  192. if oldMeta != metadata {
  193. newMetadata = append(newMetadata, oldMeta)
  194. }
  195. }
  196. if len(newMetadata) == 0 {
  197. return serv.store.Delete(serv.diffIDNamespace(), serv.diffIDKey(diffID))
  198. }
  199. jsonBytes, err := json.Marshal(newMetadata)
  200. if err != nil {
  201. return err
  202. }
  203. return serv.store.Set(serv.diffIDNamespace(), serv.diffIDKey(diffID), jsonBytes)
  204. }