7a85579917
The `digest` data type, used throughout docker for image verification and identity, has been broken out into `opencontainers/go-digest`. This PR updates the dependencies and moves uses over to the new type. Signed-off-by: Stephen J Day <stephen.day@docker.com>
241 lines
6.9 KiB
Go
241 lines
6.9 KiB
Go
package metadata
|
|
|
|
import (
|
|
"crypto/hmac"
|
|
"crypto/sha256"
|
|
"encoding/hex"
|
|
"encoding/json"
|
|
"errors"
|
|
|
|
"github.com/docker/docker/api/types"
|
|
"github.com/docker/docker/layer"
|
|
"github.com/opencontainers/go-digest"
|
|
)
|
|
|
|
// V2MetadataService maps layer IDs to a set of known metadata for
|
|
// the layer.
|
|
type V2MetadataService interface {
|
|
GetMetadata(diffID layer.DiffID) ([]V2Metadata, error)
|
|
GetDiffID(dgst digest.Digest) (layer.DiffID, error)
|
|
Add(diffID layer.DiffID, metadata V2Metadata) error
|
|
TagAndAdd(diffID layer.DiffID, hmacKey []byte, metadata V2Metadata) error
|
|
Remove(metadata V2Metadata) error
|
|
}
|
|
|
|
// v2MetadataService implements V2MetadataService
|
|
type v2MetadataService struct {
|
|
store Store
|
|
}
|
|
|
|
var _ V2MetadataService = &v2MetadataService{}
|
|
|
|
// V2Metadata contains the digest and source repository information for a layer.
|
|
type V2Metadata struct {
|
|
Digest digest.Digest
|
|
SourceRepository string
|
|
// HMAC hashes above attributes with recent authconfig digest used as a key in order to determine matching
|
|
// metadata entries accompanied by the same credentials without actually exposing them.
|
|
HMAC string
|
|
}
|
|
|
|
// CheckV2MetadataHMAC returns true if the given "meta" is tagged with a hmac hashed by the given "key".
|
|
func CheckV2MetadataHMAC(meta *V2Metadata, key []byte) bool {
|
|
if len(meta.HMAC) == 0 || len(key) == 0 {
|
|
return len(meta.HMAC) == 0 && len(key) == 0
|
|
}
|
|
mac := hmac.New(sha256.New, key)
|
|
mac.Write([]byte(meta.Digest))
|
|
mac.Write([]byte(meta.SourceRepository))
|
|
expectedMac := mac.Sum(nil)
|
|
|
|
storedMac, err := hex.DecodeString(meta.HMAC)
|
|
if err != nil {
|
|
return false
|
|
}
|
|
|
|
return hmac.Equal(storedMac, expectedMac)
|
|
}
|
|
|
|
// ComputeV2MetadataHMAC returns a hmac for the given "meta" hash by the given key.
|
|
func ComputeV2MetadataHMAC(key []byte, meta *V2Metadata) string {
|
|
if len(key) == 0 || meta == nil {
|
|
return ""
|
|
}
|
|
mac := hmac.New(sha256.New, key)
|
|
mac.Write([]byte(meta.Digest))
|
|
mac.Write([]byte(meta.SourceRepository))
|
|
return hex.EncodeToString(mac.Sum(nil))
|
|
}
|
|
|
|
// ComputeV2MetadataHMACKey returns a key for the given "authConfig" that can be used to hash v2 metadata
|
|
// entries.
|
|
func ComputeV2MetadataHMACKey(authConfig *types.AuthConfig) ([]byte, error) {
|
|
if authConfig == nil {
|
|
return nil, nil
|
|
}
|
|
key := authConfigKeyInput{
|
|
Username: authConfig.Username,
|
|
Password: authConfig.Password,
|
|
Auth: authConfig.Auth,
|
|
IdentityToken: authConfig.IdentityToken,
|
|
RegistryToken: authConfig.RegistryToken,
|
|
}
|
|
buf, err := json.Marshal(&key)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return []byte(digest.FromBytes([]byte(buf))), nil
|
|
}
|
|
|
|
// authConfigKeyInput is a reduced AuthConfig structure holding just relevant credential data eligible for
|
|
// hmac key creation.
|
|
type authConfigKeyInput struct {
|
|
Username string `json:"username,omitempty"`
|
|
Password string `json:"password,omitempty"`
|
|
Auth string `json:"auth,omitempty"`
|
|
|
|
IdentityToken string `json:"identitytoken,omitempty"`
|
|
RegistryToken string `json:"registrytoken,omitempty"`
|
|
}
|
|
|
|
// maxMetadata is the number of metadata entries to keep per layer DiffID.
|
|
const maxMetadata = 50
|
|
|
|
// NewV2MetadataService creates a new diff ID to v2 metadata mapping service.
|
|
func NewV2MetadataService(store Store) V2MetadataService {
|
|
return &v2MetadataService{
|
|
store: store,
|
|
}
|
|
}
|
|
|
|
func (serv *v2MetadataService) diffIDNamespace() string {
|
|
return "v2metadata-by-diffid"
|
|
}
|
|
|
|
func (serv *v2MetadataService) digestNamespace() string {
|
|
return "diffid-by-digest"
|
|
}
|
|
|
|
func (serv *v2MetadataService) diffIDKey(diffID layer.DiffID) string {
|
|
return string(digest.Digest(diffID).Algorithm()) + "/" + digest.Digest(diffID).Hex()
|
|
}
|
|
|
|
func (serv *v2MetadataService) digestKey(dgst digest.Digest) string {
|
|
return string(dgst.Algorithm()) + "/" + dgst.Hex()
|
|
}
|
|
|
|
// GetMetadata finds the metadata associated with a layer DiffID.
|
|
func (serv *v2MetadataService) GetMetadata(diffID layer.DiffID) ([]V2Metadata, error) {
|
|
if serv.store == nil {
|
|
return nil, errors.New("no metadata storage")
|
|
}
|
|
jsonBytes, err := serv.store.Get(serv.diffIDNamespace(), serv.diffIDKey(diffID))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var metadata []V2Metadata
|
|
if err := json.Unmarshal(jsonBytes, &metadata); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return metadata, nil
|
|
}
|
|
|
|
// GetDiffID finds a layer DiffID from a digest.
|
|
func (serv *v2MetadataService) GetDiffID(dgst digest.Digest) (layer.DiffID, error) {
|
|
if serv.store == nil {
|
|
return layer.DiffID(""), errors.New("no metadata storage")
|
|
}
|
|
diffIDBytes, err := serv.store.Get(serv.digestNamespace(), serv.digestKey(dgst))
|
|
if err != nil {
|
|
return layer.DiffID(""), err
|
|
}
|
|
|
|
return layer.DiffID(diffIDBytes), nil
|
|
}
|
|
|
|
// Add associates metadata with a layer DiffID. If too many metadata entries are
|
|
// present, the oldest one is dropped.
|
|
func (serv *v2MetadataService) Add(diffID layer.DiffID, metadata V2Metadata) error {
|
|
if serv.store == nil {
|
|
// Support a service which has no backend storage, in this case
|
|
// an add becomes a no-op.
|
|
// TODO: implement in memory storage
|
|
return nil
|
|
}
|
|
oldMetadata, err := serv.GetMetadata(diffID)
|
|
if err != nil {
|
|
oldMetadata = nil
|
|
}
|
|
newMetadata := make([]V2Metadata, 0, len(oldMetadata)+1)
|
|
|
|
// Copy all other metadata to new slice
|
|
for _, oldMeta := range oldMetadata {
|
|
if oldMeta != metadata {
|
|
newMetadata = append(newMetadata, oldMeta)
|
|
}
|
|
}
|
|
|
|
newMetadata = append(newMetadata, metadata)
|
|
|
|
if len(newMetadata) > maxMetadata {
|
|
newMetadata = newMetadata[len(newMetadata)-maxMetadata:]
|
|
}
|
|
|
|
jsonBytes, err := json.Marshal(newMetadata)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = serv.store.Set(serv.diffIDNamespace(), serv.diffIDKey(diffID), jsonBytes)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return serv.store.Set(serv.digestNamespace(), serv.digestKey(metadata.Digest), []byte(diffID))
|
|
}
|
|
|
|
// TagAndAdd amends the given "meta" for hmac hashed by the given "hmacKey" and associates it with a layer
|
|
// DiffID. If too many metadata entries are present, the oldest one is dropped.
|
|
func (serv *v2MetadataService) TagAndAdd(diffID layer.DiffID, hmacKey []byte, meta V2Metadata) error {
|
|
meta.HMAC = ComputeV2MetadataHMAC(hmacKey, &meta)
|
|
return serv.Add(diffID, meta)
|
|
}
|
|
|
|
// Remove unassociates a metadata entry from a layer DiffID.
|
|
func (serv *v2MetadataService) Remove(metadata V2Metadata) error {
|
|
if serv.store == nil {
|
|
// Support a service which has no backend storage, in this case
|
|
// an remove becomes a no-op.
|
|
// TODO: implement in memory storage
|
|
return nil
|
|
}
|
|
diffID, err := serv.GetDiffID(metadata.Digest)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
oldMetadata, err := serv.GetMetadata(diffID)
|
|
if err != nil {
|
|
oldMetadata = nil
|
|
}
|
|
newMetadata := make([]V2Metadata, 0, len(oldMetadata))
|
|
|
|
// Copy all other metadata to new slice
|
|
for _, oldMeta := range oldMetadata {
|
|
if oldMeta != metadata {
|
|
newMetadata = append(newMetadata, oldMeta)
|
|
}
|
|
}
|
|
|
|
if len(newMetadata) == 0 {
|
|
return serv.store.Delete(serv.diffIDNamespace(), serv.diffIDKey(diffID))
|
|
}
|
|
|
|
jsonBytes, err := json.Marshal(newMetadata)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return serv.store.Set(serv.diffIDNamespace(), serv.diffIDKey(diffID), jsonBytes)
|
|
}
|