2022-09-05 03:06:08 -04:00
|
|
|
/*
|
|
|
|
Copyright (c) Edgeless Systems GmbH
|
|
|
|
|
|
|
|
SPDX-License-Identifier: AGPL-3.0-only
|
|
|
|
*/
|
|
|
|
|
2023-03-02 09:08:31 -05:00
|
|
|
// Package awss3 implements a storage backend for the KMS using AWS S3: https://aws.amazon.com/s3/
|
|
|
|
package awss3
|
2022-03-22 11:03:15 -04:00
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
|
|
|
"context"
|
|
|
|
"errors"
|
|
|
|
"fmt"
|
|
|
|
"io"
|
|
|
|
|
|
|
|
awsconfig "github.com/aws/aws-sdk-go-v2/config"
|
2023-03-02 09:08:31 -05:00
|
|
|
"github.com/aws/aws-sdk-go-v2/credentials"
|
2022-03-22 11:03:15 -04:00
|
|
|
"github.com/aws/aws-sdk-go-v2/service/s3"
|
|
|
|
"github.com/aws/aws-sdk-go-v2/service/s3/types"
|
2023-01-12 10:22:47 -05:00
|
|
|
"github.com/edgelesssys/constellation/v2/internal/kms/config"
|
2023-03-02 09:08:31 -05:00
|
|
|
"github.com/edgelesssys/constellation/v2/internal/kms/storage"
|
|
|
|
"github.com/edgelesssys/constellation/v2/internal/kms/uri"
|
2022-03-22 11:03:15 -04:00
|
|
|
)
|
|
|
|
|
2022-03-25 06:55:09 -04:00
|
|
|
type awsS3ClientAPI interface {
|
|
|
|
GetObject(ctx context.Context, params *s3.GetObjectInput, optFns ...func(*s3.Options)) (*s3.GetObjectOutput, error)
|
|
|
|
PutObject(ctx context.Context, params *s3.PutObjectInput, optFns ...func(*s3.Options)) (*s3.PutObjectOutput, error)
|
|
|
|
CreateBucket(ctx context.Context, params *s3.CreateBucketInput, optFns ...func(*s3.Options)) (*s3.CreateBucketOutput, error)
|
|
|
|
}
|
|
|
|
|
2023-03-02 09:08:31 -05:00
|
|
|
// Storage is an implementation of the Storage interface, storing keys in AWS S3 buckets.
|
|
|
|
type Storage struct {
|
2022-03-22 11:03:15 -04:00
|
|
|
bucketID string
|
2022-03-25 06:55:09 -04:00
|
|
|
client awsS3ClientAPI
|
2022-03-22 11:03:15 -04:00
|
|
|
}
|
|
|
|
|
2023-03-02 09:08:31 -05:00
|
|
|
// New creates a Storage client for AWS S3 using the provided config.
|
2022-03-22 11:03:15 -04:00
|
|
|
//
|
2023-03-02 09:08:31 -05:00
|
|
|
// See the AWS docs for more information: https://aws.amazon.com/s3/
|
|
|
|
func New(ctx context.Context, cfg uri.AWSS3Config) (*Storage, error) {
|
|
|
|
clientCfg, err := awsconfig.LoadDefaultConfig(
|
|
|
|
ctx,
|
|
|
|
awsconfig.WithCredentialsProvider(credentials.NewStaticCredentialsProvider(cfg.AccessKeyID, cfg.AccessKey, "")),
|
|
|
|
awsconfig.WithRegion(cfg.Region),
|
|
|
|
)
|
2022-03-22 11:03:15 -04:00
|
|
|
if err != nil {
|
2023-03-02 09:08:31 -05:00
|
|
|
return nil, fmt.Errorf("loading AWS S3 client config: %w", err)
|
2022-03-22 11:03:15 -04:00
|
|
|
}
|
|
|
|
|
2023-03-02 09:08:31 -05:00
|
|
|
client := s3.NewFromConfig(clientCfg)
|
|
|
|
|
|
|
|
store := &Storage{client: client, bucketID: cfg.Bucket}
|
2022-03-25 06:55:09 -04:00
|
|
|
|
2022-03-22 11:03:15 -04:00
|
|
|
// Try to create new bucket, continue if bucket already exists
|
2023-03-02 09:08:31 -05:00
|
|
|
if err := store.createBucket(ctx, cfg.Bucket, cfg.Region); err != nil {
|
|
|
|
return nil, fmt.Errorf("creating storage bucket: %w", err)
|
2022-03-22 11:03:15 -04:00
|
|
|
}
|
2022-03-25 06:55:09 -04:00
|
|
|
return store, nil
|
2022-03-22 11:03:15 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
// Get returns a DEK from from AWS S3 Storage by key ID.
|
2023-03-02 09:08:31 -05:00
|
|
|
func (s *Storage) Get(ctx context.Context, keyID string) ([]byte, error) {
|
2022-03-22 11:03:15 -04:00
|
|
|
getObjectInput := &s3.GetObjectInput{
|
|
|
|
Bucket: &s.bucketID,
|
|
|
|
Key: &keyID,
|
|
|
|
}
|
2023-03-02 09:08:31 -05:00
|
|
|
output, err := s.client.GetObject(ctx, getObjectInput)
|
2022-03-22 11:03:15 -04:00
|
|
|
if err != nil {
|
|
|
|
var nsk *types.NoSuchKey
|
|
|
|
if errors.As(err, &nsk) {
|
2023-03-02 09:08:31 -05:00
|
|
|
return nil, storage.ErrDEKUnset
|
2022-03-22 11:03:15 -04:00
|
|
|
}
|
|
|
|
return nil, fmt.Errorf("downloading DEK from storage: %w", err)
|
|
|
|
}
|
|
|
|
return io.ReadAll(output.Body)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Put saves a DEK to AWS S3 Storage by key ID.
|
2023-03-02 09:08:31 -05:00
|
|
|
func (s *Storage) Put(ctx context.Context, keyID string, data []byte) error {
|
2022-03-22 11:03:15 -04:00
|
|
|
putObjectInput := &s3.PutObjectInput{
|
|
|
|
Bucket: &s.bucketID,
|
|
|
|
Key: &keyID,
|
|
|
|
Body: bytes.NewReader(data),
|
|
|
|
Tagging: &config.AWSS3Tag,
|
|
|
|
}
|
2023-03-02 09:08:31 -05:00
|
|
|
if _, err := s.client.PutObject(ctx, putObjectInput); err != nil {
|
2022-03-22 11:03:15 -04:00
|
|
|
return fmt.Errorf("uploading DEK to storage: %w", err)
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
2022-03-25 06:55:09 -04:00
|
|
|
|
2023-03-02 09:08:31 -05:00
|
|
|
func (s *Storage) createBucket(ctx context.Context, bucketID, region string) error {
|
2022-03-25 06:55:09 -04:00
|
|
|
createBucketInput := &s3.CreateBucketInput{
|
|
|
|
Bucket: &bucketID,
|
|
|
|
CreateBucketConfiguration: &types.CreateBucketConfiguration{
|
|
|
|
LocationConstraint: types.BucketLocationConstraint(region),
|
|
|
|
},
|
|
|
|
}
|
2023-03-02 09:08:31 -05:00
|
|
|
|
|
|
|
if _, err := s.client.CreateBucket(ctx, createBucketInput); err != nil {
|
2022-03-25 06:55:09 -04:00
|
|
|
var bne *types.BucketAlreadyExists
|
|
|
|
var baowby *types.BucketAlreadyOwnedByYou
|
|
|
|
if !(errors.As(err, &bne) || errors.As(err, &baowby)) {
|
|
|
|
return fmt.Errorf("creating storage container: %w", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|