zotregistry.dev/zot@v1.4.4-0.20240314164342-eec277e14d20/pkg/extensions/imagetrust/image_trust.go (about)

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