2023-05-23 09:09:14 -04:00
|
|
|
/*
|
|
|
|
Copyright (c) Edgeless Systems GmbH
|
|
|
|
|
|
|
|
SPDX-License-Identifier: AGPL-3.0-only
|
|
|
|
*/
|
|
|
|
|
|
|
|
// package measurementsuploader is used to upload measurements (v2) JSON files (and signatures) to S3.
|
|
|
|
package measurementsuploader
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"encoding/json"
|
|
|
|
"fmt"
|
|
|
|
"io"
|
2024-02-08 09:20:01 -05:00
|
|
|
"log/slog"
|
2023-05-23 09:09:14 -04:00
|
|
|
"net/url"
|
2023-08-31 04:31:45 -04:00
|
|
|
"time"
|
2023-05-23 09:09:14 -04:00
|
|
|
|
|
|
|
s3manager "github.com/aws/aws-sdk-go-v2/feature/s3/manager"
|
|
|
|
"github.com/aws/aws-sdk-go-v2/service/s3"
|
|
|
|
s3types "github.com/aws/aws-sdk-go-v2/service/s3/types"
|
2023-06-07 10:16:32 -04:00
|
|
|
"github.com/edgelesssys/constellation/v2/internal/api/versionsapi"
|
2023-05-23 09:09:14 -04:00
|
|
|
"github.com/edgelesssys/constellation/v2/internal/attestation/measurements"
|
|
|
|
"github.com/edgelesssys/constellation/v2/internal/constants"
|
2023-06-02 05:20:01 -04:00
|
|
|
"github.com/edgelesssys/constellation/v2/internal/staticupload"
|
2023-05-23 09:09:14 -04:00
|
|
|
)
|
|
|
|
|
|
|
|
// Uploader uploads image info to S3.
|
|
|
|
type Uploader struct {
|
2023-06-02 05:20:01 -04:00
|
|
|
uploadClient uploadClient
|
|
|
|
uploadClientClose func(ctx context.Context) error
|
2023-05-23 09:09:14 -04:00
|
|
|
// bucket is the name of the S3 bucket to use.
|
|
|
|
bucket string
|
|
|
|
|
2024-02-08 09:20:01 -05:00
|
|
|
log *slog.Logger
|
2023-05-23 09:09:14 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
// New creates a new Uploader.
|
2024-02-08 09:20:01 -05:00
|
|
|
func New(ctx context.Context, region, bucket, distributionID string, log *slog.Logger) (*Uploader, CloseFunc, error) {
|
2023-06-02 05:20:01 -04:00
|
|
|
staticUploadClient, staticUploadClientClose, err := staticupload.New(ctx, staticupload.Config{
|
2023-08-31 04:31:45 -04:00
|
|
|
Region: region,
|
|
|
|
Bucket: bucket,
|
|
|
|
DistributionID: distributionID,
|
|
|
|
CacheInvalidationStrategy: staticupload.CacheInvalidateBatchOnFlush,
|
|
|
|
CacheInvalidationWaitTimeout: 10 * time.Minute,
|
|
|
|
}, log)
|
2023-05-23 09:09:14 -04:00
|
|
|
if err != nil {
|
2023-06-02 05:20:01 -04:00
|
|
|
return nil, nil, err
|
2023-05-23 09:09:14 -04:00
|
|
|
}
|
|
|
|
|
2023-06-02 05:20:01 -04:00
|
|
|
uploader := &Uploader{
|
|
|
|
uploadClient: staticUploadClient,
|
|
|
|
uploadClientClose: staticUploadClientClose,
|
|
|
|
bucket: bucket,
|
|
|
|
log: log,
|
|
|
|
}
|
|
|
|
uploaderClose := func(ctx context.Context) error {
|
|
|
|
return uploader.Close(ctx)
|
|
|
|
}
|
|
|
|
return uploader, uploaderClose, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Close closes the uploader.
|
|
|
|
// It invalidates the CDN cache for all uploaded files.
|
|
|
|
func (a *Uploader) Close(ctx context.Context) error {
|
|
|
|
if a.uploadClientClose == nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
return a.uploadClientClose(ctx)
|
2023-05-23 09:09:14 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
// Upload uploads the measurements v2 JSON file and its signature to S3.
|
|
|
|
func (a *Uploader) Upload(ctx context.Context, rawMeasurement, signature io.ReadSeeker) (string, string, error) {
|
|
|
|
// parse the measurements to get the ref, stream, and version
|
|
|
|
var measurements measurements.ImageMeasurementsV2
|
|
|
|
if err := json.NewDecoder(rawMeasurement).Decode(&measurements); err != nil {
|
|
|
|
return "", "", err
|
|
|
|
}
|
|
|
|
if _, err := rawMeasurement.Seek(0, io.SeekStart); err != nil {
|
|
|
|
return "", "", err
|
|
|
|
}
|
|
|
|
|
2023-08-01 10:48:13 -04:00
|
|
|
ver, err := versionsapi.NewVersion(measurements.Ref, measurements.Stream, measurements.Version, versionsapi.VersionKindImage)
|
|
|
|
if err != nil {
|
|
|
|
return "", "", fmt.Errorf("creating version: %w", err)
|
2023-05-23 09:09:14 -04:00
|
|
|
}
|
2024-01-09 13:37:56 -05:00
|
|
|
key, err := url.JoinPath(ver.ArtifactPath(versionsapi.APIV2), ver.Kind().String(), constants.CDNMeasurementsFile)
|
2023-05-23 09:09:14 -04:00
|
|
|
if err != nil {
|
|
|
|
return "", "", err
|
|
|
|
}
|
2024-01-09 13:37:56 -05:00
|
|
|
sigKey, err := url.JoinPath(ver.ArtifactPath(versionsapi.APIV2), ver.Kind().String(), constants.CDNMeasurementsSignature)
|
2023-05-23 09:09:14 -04:00
|
|
|
if err != nil {
|
|
|
|
return "", "", err
|
|
|
|
}
|
2024-04-03 09:49:03 -04:00
|
|
|
a.log.Debug(fmt.Sprintf("Archiving image measurements to s3://%s/%s and s3://%s/%s", a.bucket, key, a.bucket, sigKey))
|
2023-05-23 09:09:14 -04:00
|
|
|
if _, err = a.uploadClient.Upload(ctx, &s3.PutObjectInput{
|
|
|
|
Bucket: &a.bucket,
|
|
|
|
Key: &key,
|
|
|
|
Body: rawMeasurement,
|
|
|
|
ChecksumAlgorithm: s3types.ChecksumAlgorithmSha256,
|
|
|
|
}); err != nil {
|
|
|
|
return "", "", fmt.Errorf("uploading measurements: %w", err)
|
|
|
|
}
|
|
|
|
if _, err = a.uploadClient.Upload(ctx, &s3.PutObjectInput{
|
|
|
|
Bucket: &a.bucket,
|
|
|
|
Key: &sigKey,
|
|
|
|
Body: signature,
|
|
|
|
ChecksumAlgorithm: s3types.ChecksumAlgorithmSha256,
|
|
|
|
}); err != nil {
|
|
|
|
return "", "", fmt.Errorf("uploading measurements signature: %w", err)
|
|
|
|
}
|
|
|
|
return constants.CDNRepositoryURL + "/" + key, constants.CDNRepositoryURL + "/" + sigKey, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
type uploadClient interface {
|
|
|
|
Upload(ctx context.Context, input *s3.PutObjectInput, opts ...func(*s3manager.Uploader)) (*s3manager.UploadOutput, error)
|
|
|
|
}
|
2023-06-02 05:20:01 -04:00
|
|
|
|
|
|
|
// CloseFunc is a function that closes the client.
|
|
|
|
type CloseFunc func(ctx context.Context) error
|