zotregistry.io/zot@v1.4.4-0.20231124084042-02a8ed785457/pkg/extensions/imagetrust/image_trust.go (about)

     1  //go:build imagetrust
     2  // +build imagetrust
     3  
     4  package imagetrust
     5  
     6  import (
     7  	"context"
     8  	"time"
     9  
    10  	"github.com/aws/aws-sdk-go-v2/aws"
    11  	"github.com/aws/aws-sdk-go-v2/aws/transport/http"
    12  	"github.com/aws/aws-sdk-go-v2/config"
    13  	"github.com/aws/aws-sdk-go-v2/service/secretsmanager"
    14  	"github.com/aws/aws-sdk-go-v2/service/secretsmanager/types"
    15  	aws1 "github.com/aws/aws-sdk-go/aws"
    16  	"github.com/aws/aws-sdk-go/aws/endpoints"
    17  	"github.com/aws/aws-sdk-go/aws/session"
    18  	smanager "github.com/aws/aws-sdk-go/service/secretsmanager"
    19  	"github.com/aws/aws-secretsmanager-caching-go/secretcache"
    20  	smithy "github.com/aws/smithy-go"
    21  	godigest "github.com/opencontainers/go-digest"
    22  	ispec "github.com/opencontainers/image-spec/specs-go/v1"
    23  
    24  	zerr "zotregistry.io/zot/errors"
    25  	zcommon "zotregistry.io/zot/pkg/common"
    26  	"zotregistry.io/zot/pkg/log"
    27  	mTypes "zotregistry.io/zot/pkg/meta/types"
    28  	"zotregistry.io/zot/pkg/scheduler"
    29  )
    30  
    31  const (
    32  	defaultDirPerms  = 0o700
    33  	defaultFilePerms = 0o644
    34  )
    35  
    36  type ImageTrustStore struct {
    37  	CosignStorage   publicKeyStorage
    38  	NotationStorage certificateStorage
    39  }
    40  
    41  type SecretsManagerClient interface {
    42  	CreateSecret(ctx context.Context, params *secretsmanager.CreateSecretInput,
    43  		optFns ...func(*secretsmanager.Options)) (*secretsmanager.CreateSecretOutput, error)
    44  	DeleteSecret(ctx context.Context, params *secretsmanager.DeleteSecretInput,
    45  		optFns ...func(*secretsmanager.Options)) (*secretsmanager.DeleteSecretOutput, error)
    46  	ListSecrets(ctx context.Context, params *secretsmanager.ListSecretsInput,
    47  		optFns ...func(*secretsmanager.Options)) (*secretsmanager.ListSecretsOutput, error)
    48  }
    49  
    50  type SecretsManagerCache interface {
    51  	GetSecretString(secretID string) (string, error)
    52  }
    53  
    54  func NewLocalImageTrustStore(rootDir string) (*ImageTrustStore, error) {
    55  	publicKeyStorage, err := NewPublicKeyLocalStorage(rootDir)
    56  	if err != nil {
    57  		return nil, err
    58  	}
    59  
    60  	certStorage, err := NewCertificateLocalStorage(rootDir)
    61  	if err != nil {
    62  		return nil, err
    63  	}
    64  
    65  	return &ImageTrustStore{
    66  		CosignStorage:   publicKeyStorage,
    67  		NotationStorage: certStorage,
    68  	}, nil
    69  }
    70  
    71  func NewAWSImageTrustStore(region, endpoint string) (*ImageTrustStore, error) {
    72  	secretsManagerClient, err := GetSecretsManagerClient(region, endpoint)
    73  	if err != nil {
    74  		return nil, err
    75  	}
    76  
    77  	secretsManagerCache := GetSecretsManagerRetrieval(region, endpoint)
    78  
    79  	publicKeyStorage := NewPublicKeyAWSStorage(secretsManagerClient, secretsManagerCache)
    80  
    81  	certStorage, err := NewCertificateAWSStorage(secretsManagerClient, secretsManagerCache)
    82  	if err != nil {
    83  		return nil, err
    84  	}
    85  
    86  	return &ImageTrustStore{
    87  		CosignStorage:   publicKeyStorage,
    88  		NotationStorage: certStorage,
    89  	}, nil
    90  }
    91  
    92  func GetSecretsManagerClient(region, endpoint string) (*secretsmanager.Client, error) {
    93  	customResolver := aws.EndpointResolverWithOptionsFunc(
    94  		func(service, region string, options ...interface{}) (aws.Endpoint, error) {
    95  			return aws.Endpoint{
    96  				PartitionID:   "aws",
    97  				URL:           endpoint,
    98  				SigningRegion: region,
    99  			}, nil
   100  		})
   101  
   102  	// Using the SDK's default configuration, loading additional config
   103  	// and credentials values from the environment variables, shared
   104  	// credentials, and shared configuration files
   105  	cfg, err := config.LoadDefaultConfig(context.Background(), config.WithRegion(region),
   106  		config.WithEndpointResolverWithOptions(customResolver))
   107  	if err != nil {
   108  		return nil, err
   109  	}
   110  
   111  	return secretsmanager.NewFromConfig(cfg), nil
   112  }
   113  
   114  func GetSecretsManagerRetrieval(region, endpoint string) *secretcache.Cache {
   115  	endpointFunc := func(service, region string, optFns ...func(*endpoints.Options)) (endpoints.ResolvedEndpoint, error) {
   116  		return endpoints.ResolvedEndpoint{
   117  			PartitionID:   "aws",
   118  			URL:           endpoint,
   119  			SigningRegion: region,
   120  		}, nil
   121  	}
   122  	customResolver := endpoints.ResolverFunc(endpointFunc)
   123  
   124  	cfg := aws1.NewConfig().WithRegion(region).WithEndpointResolver(customResolver)
   125  
   126  	newSession := session.Must(session.NewSession())
   127  
   128  	client := smanager.New(newSession, cfg)
   129  	// Create a custom CacheConfig struct
   130  	config := secretcache.CacheConfig{
   131  		MaxCacheSize: secretcache.DefaultMaxCacheSize,
   132  		VersionStage: secretcache.DefaultVersionStage,
   133  		CacheItemTTL: secretcache.DefaultCacheItemTTL,
   134  	}
   135  
   136  	// Instantiate the cache
   137  	cache, _ := secretcache.New(
   138  		func(c *secretcache.Cache) { c.CacheConfig = config },
   139  		func(c *secretcache.Cache) { c.Client = client },
   140  	)
   141  
   142  	return cache
   143  }
   144  
   145  func IsResourceExistsException(err error) bool {
   146  	if opErr, ok := err.(*smithy.OperationError); ok { //nolint: errorlint
   147  		if resErr, ok := opErr.Err.(*http.ResponseError); ok { //nolint: errorlint
   148  			if _, ok := resErr.Err.(*types.ResourceExistsException); ok { //nolint: errorlint
   149  				return true
   150  			}
   151  		}
   152  
   153  		return false
   154  	}
   155  
   156  	return false
   157  }
   158  
   159  func (imgTrustStore *ImageTrustStore) VerifySignature(
   160  	signatureType string, rawSignature []byte, sigKey string, manifestDigest godigest.Digest, imageMeta mTypes.ImageMeta,
   161  	repo string,
   162  ) (string, time.Time, bool, error) {
   163  	desc := ispec.Descriptor{
   164  		MediaType: imageMeta.MediaType,
   165  		Digest:    imageMeta.Digest,
   166  		Size:      imageMeta.Size,
   167  	}
   168  
   169  	if manifestDigest.String() == "" {
   170  		return "", time.Time{}, false, zerr.ErrBadManifestDigest
   171  	}
   172  
   173  	switch signatureType {
   174  	case zcommon.CosignSignature:
   175  		author, isValid, err := VerifyCosignSignature(imgTrustStore.CosignStorage, repo, manifestDigest, sigKey, rawSignature)
   176  
   177  		return author, time.Time{}, isValid, err
   178  	case zcommon.NotationSignature:
   179  		return VerifyNotationSignature(imgTrustStore.NotationStorage, desc, manifestDigest.String(), rawSignature, sigKey)
   180  	default:
   181  		return "", time.Time{}, false, zerr.ErrInvalidSignatureType
   182  	}
   183  }
   184  
   185  func NewTaskGenerator(metaDB mTypes.MetaDB, log log.Logger) scheduler.TaskGenerator {
   186  	return &sigValidityTaskGenerator{
   187  		repos:     []mTypes.RepoMeta{},
   188  		metaDB:    metaDB,
   189  		repoIndex: -1,
   190  		log:       log,
   191  	}
   192  }
   193  
   194  type sigValidityTaskGenerator struct {
   195  	repos     []mTypes.RepoMeta
   196  	metaDB    mTypes.MetaDB
   197  	repoIndex int
   198  	done      bool
   199  	log       log.Logger
   200  }
   201  
   202  func (gen *sigValidityTaskGenerator) Next() (scheduler.Task, error) {
   203  	if len(gen.repos) == 0 {
   204  		ctx := context.Background()
   205  
   206  		repos, err := gen.metaDB.GetMultipleRepoMeta(ctx, func(repoMeta mTypes.RepoMeta) bool {
   207  			return true
   208  		})
   209  		if err != nil {
   210  			return nil, err
   211  		}
   212  
   213  		gen.repos = repos
   214  	}
   215  
   216  	gen.repoIndex++
   217  
   218  	if gen.repoIndex >= len(gen.repos) {
   219  		gen.done = true
   220  
   221  		gen.log.Info().Msg("finished generating tasks for updating signatures validity")
   222  
   223  		return nil, nil
   224  	}
   225  
   226  	return NewValidityTask(gen.metaDB, gen.repos[gen.repoIndex], gen.log), nil
   227  }
   228  
   229  func (gen *sigValidityTaskGenerator) IsDone() bool {
   230  	return gen.done
   231  }
   232  
   233  func (gen *sigValidityTaskGenerator) IsReady() bool {
   234  	return true
   235  }
   236  
   237  func (gen *sigValidityTaskGenerator) Reset() {
   238  	gen.done = false
   239  	gen.repoIndex = -1
   240  	gen.repos = []mTypes.RepoMeta{}
   241  
   242  	gen.log.Info().Msg("finished resetting task generator for updating signatures validity")
   243  }
   244  
   245  type validityTask struct {
   246  	metaDB mTypes.MetaDB
   247  	repo   mTypes.RepoMeta
   248  	log    log.Logger
   249  }
   250  
   251  func NewValidityTask(metaDB mTypes.MetaDB, repo mTypes.RepoMeta, log log.Logger) *validityTask {
   252  	return &validityTask{metaDB, repo, log}
   253  }
   254  
   255  func (validityT *validityTask) DoWork(ctx context.Context) error {
   256  	validityT.log.Info().Msg("update signatures validity")
   257  
   258  	for signedManifest, sigs := range validityT.repo.Signatures {
   259  		if zcommon.IsContextDone(ctx) {
   260  			return ctx.Err()
   261  		}
   262  
   263  		if len(sigs[zcommon.CosignSignature]) != 0 || len(sigs[zcommon.NotationSignature]) != 0 {
   264  			err := validityT.metaDB.UpdateSignaturesValidity(ctx, validityT.repo.Name, godigest.Digest(signedManifest))
   265  			if err != nil {
   266  				validityT.log.Info().Msg("error while verifying signatures")
   267  
   268  				return err
   269  			}
   270  		}
   271  	}
   272  
   273  	validityT.log.Info().Msg("update signatures validity completed")
   274  
   275  	return nil
   276  }