github.com/devseccon/trivy@v0.47.1-0.20231123133102-bd902a0bd996/pkg/fanal/cache/s3.go (about)

     1  package cache
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"encoding/json"
     7  	"fmt"
     8  
     9  	"github.com/aws/aws-sdk-go-v2/aws"
    10  	"github.com/aws/aws-sdk-go-v2/feature/s3/manager"
    11  	"github.com/aws/aws-sdk-go-v2/service/s3"
    12  	"github.com/hashicorp/go-multierror"
    13  	"golang.org/x/xerrors"
    14  
    15  	"github.com/devseccon/trivy/pkg/fanal/types"
    16  )
    17  
    18  var _ Cache = &S3Cache{}
    19  
    20  type s3API interface {
    21  	HeadObject(ctx context.Context, params *s3.HeadObjectInput, optFns ...func(*s3.Options)) (*s3.HeadObjectOutput, error)
    22  	PutObject(ctx context.Context, params *s3.PutObjectInput, optFns ...func(*s3.Options)) (*s3.PutObjectOutput, error)
    23  	DeleteBucket(ctx context.Context, params *s3.DeleteBucketInput, optFns ...func(*s3.Options)) (*s3.DeleteBucketOutput, error)
    24  }
    25  
    26  type S3Cache struct {
    27  	s3Client   s3API
    28  	downloader *manager.Downloader
    29  	bucketName string
    30  	prefix     string
    31  }
    32  
    33  func NewS3Cache(bucketName, prefix string, api s3API, downloaderAPI *manager.Downloader) S3Cache {
    34  	return S3Cache{
    35  		s3Client:   api,
    36  		downloader: downloaderAPI,
    37  		bucketName: bucketName,
    38  		prefix:     prefix,
    39  	}
    40  }
    41  
    42  func (c S3Cache) PutArtifact(artifactID string, artifactConfig types.ArtifactInfo) (err error) {
    43  	key := fmt.Sprintf("%s/%s/%s", artifactBucket, c.prefix, artifactID)
    44  	if err := c.put(key, artifactConfig); err != nil {
    45  		return xerrors.Errorf("unable to store artifact information in cache (%s): %w", artifactID, err)
    46  	}
    47  	return nil
    48  }
    49  
    50  func (c S3Cache) DeleteBlobs(blobIDs []string) error {
    51  	var errs error
    52  	for _, blobID := range blobIDs {
    53  		key := fmt.Sprintf("%s/%s/%s", blobBucket, c.prefix, blobID)
    54  		input := &s3.DeleteBucketInput{Bucket: aws.String(key)}
    55  		if _, err := c.s3Client.DeleteBucket(context.TODO(), input); err != nil {
    56  			errs = multierror.Append(errs, err)
    57  		}
    58  	}
    59  	return errs
    60  }
    61  
    62  func (c S3Cache) PutBlob(blobID string, blobInfo types.BlobInfo) error {
    63  	key := fmt.Sprintf("%s/%s/%s", blobBucket, c.prefix, blobID)
    64  	if err := c.put(key, blobInfo); err != nil {
    65  		return xerrors.Errorf("unable to store blob information in cache (%s): %w", blobID, err)
    66  	}
    67  	return nil
    68  }
    69  
    70  func (c S3Cache) put(key string, body interface{}) (err error) {
    71  	b, err := json.Marshal(body)
    72  	if err != nil {
    73  		return err
    74  	}
    75  	params := &s3.PutObjectInput{
    76  		Bucket: aws.String(c.bucketName),
    77  		Key:    aws.String(key),
    78  		Body:   bytes.NewReader(b),
    79  	}
    80  	_, err = c.s3Client.PutObject(context.TODO(), params)
    81  	if err != nil {
    82  		return xerrors.Errorf("unable to put object: %w", err)
    83  	}
    84  	// Index file due S3 caveat read after write consistency
    85  	_, err = c.s3Client.PutObject(context.TODO(), &s3.PutObjectInput{
    86  		Bucket: aws.String(c.bucketName),
    87  		Key:    aws.String(fmt.Sprintf("%s.index", key)),
    88  	})
    89  	if err != nil {
    90  		return xerrors.Errorf("unable to put index object: %w", err)
    91  	}
    92  	return nil
    93  }
    94  
    95  func (c S3Cache) GetBlob(blobID string) (types.BlobInfo, error) {
    96  	var blobInfo types.BlobInfo
    97  	buf := manager.NewWriteAtBuffer([]byte{})
    98  	_, err := c.downloader.Download(context.TODO(), buf, &s3.GetObjectInput{
    99  		Bucket: aws.String(c.bucketName),
   100  		Key:    aws.String(fmt.Sprintf("%s/%s/%s", blobBucket, c.prefix, blobID)),
   101  	})
   102  	if err != nil {
   103  		return types.BlobInfo{}, xerrors.Errorf("failed to get blob from the cache: %w", err)
   104  	}
   105  	err = json.Unmarshal(buf.Bytes(), &blobInfo)
   106  	if err != nil {
   107  		return types.BlobInfo{}, xerrors.Errorf("JSON unmarshal error: %w", err)
   108  	}
   109  	return blobInfo, nil
   110  }
   111  
   112  func (c S3Cache) GetArtifact(artifactID string) (types.ArtifactInfo, error) {
   113  	var info types.ArtifactInfo
   114  	buf := manager.NewWriteAtBuffer([]byte{})
   115  	_, err := c.downloader.Download(context.TODO(), buf, &s3.GetObjectInput{
   116  		Bucket: aws.String(c.bucketName),
   117  		Key:    aws.String(fmt.Sprintf("%s/%s/%s", artifactBucket, c.prefix, artifactID)),
   118  	})
   119  	if err != nil {
   120  		return types.ArtifactInfo{}, xerrors.Errorf("failed to get artifact from the cache: %w", err)
   121  	}
   122  	err = json.Unmarshal(buf.Bytes(), &info)
   123  	if err != nil {
   124  		return types.ArtifactInfo{}, xerrors.Errorf("JSON unmarshal error: %w", err)
   125  	}
   126  	return info, nil
   127  }
   128  
   129  func (c S3Cache) getIndex(key, keyType string) error {
   130  	_, err := c.s3Client.HeadObject(context.TODO(), &s3.HeadObjectInput{
   131  		Key:    aws.String(fmt.Sprintf("%s/%s/%s.index", keyType, c.prefix, key)),
   132  		Bucket: &c.bucketName,
   133  	})
   134  	if err != nil {
   135  		return xerrors.Errorf("failed to get index from the cache: %w", err)
   136  	}
   137  	return nil
   138  }
   139  
   140  func (c S3Cache) MissingBlobs(artifactID string, blobIDs []string) (bool, []string, error) {
   141  	var missingArtifact bool
   142  	var missingBlobIDs []string
   143  	for _, blobID := range blobIDs {
   144  		err := c.getIndex(blobID, blobBucket)
   145  		if err != nil {
   146  			// error means cache missed blob info
   147  			missingBlobIDs = append(missingBlobIDs, blobID)
   148  			continue
   149  		}
   150  		blobInfo, err := c.GetBlob(blobID)
   151  		if err != nil {
   152  			return true, missingBlobIDs, xerrors.Errorf("the blob object (%s) doesn't exist in S3 even though the index file exists: %w", blobID, err)
   153  		}
   154  		if blobInfo.SchemaVersion != types.BlobJSONSchemaVersion {
   155  			missingBlobIDs = append(missingBlobIDs, blobID)
   156  		}
   157  	}
   158  	// get artifact info
   159  	err := c.getIndex(artifactID, artifactBucket)
   160  	// error means cache missed artifact info
   161  	if err != nil {
   162  		return true, missingBlobIDs, nil
   163  	}
   164  	artifactInfo, err := c.GetArtifact(artifactID)
   165  	if err != nil {
   166  		return true, missingBlobIDs, xerrors.Errorf("the artifact object (%s) doesn't exist in S3 even though the index file exists: %w", artifactID, err)
   167  	}
   168  	if artifactInfo.SchemaVersion != types.ArtifactJSONSchemaVersion {
   169  		missingArtifact = true
   170  	}
   171  	return missingArtifact, missingBlobIDs, nil
   172  }
   173  
   174  func (c S3Cache) Close() error {
   175  	return nil
   176  }
   177  
   178  func (c S3Cache) Clear() error {
   179  	return nil
   180  }