constellation/keyservice/internal/storage/awss3storage.go
Otto Bittner 90b88e1cf9 kms: rename kms to keyservice
In the light of extending our eKMS support it will be helpful
to have a tighter use of the word "KMS".
KMS should refer to the actual component that manages keys.
The keyservice, also called KMS in the constellation code,
does not manage keys itself. It talks to a KMS backend,
which in turn does the actual key management.
2023-01-16 11:56:34 +01:00

102 lines
3.1 KiB
Go

/*
Copyright (c) Edgeless Systems GmbH
SPDX-License-Identifier: AGPL-3.0-only
*/
package storage
import (
"bytes"
"context"
"errors"
"fmt"
"io"
awsconfig "github.com/aws/aws-sdk-go-v2/config"
"github.com/aws/aws-sdk-go-v2/service/s3"
"github.com/aws/aws-sdk-go-v2/service/s3/types"
"github.com/edgelesssys/constellation/v2/keyservice/internal/config"
)
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)
}
// AWSS3Storage is an implementation of the Storage interface, storing keys in AWS S3 buckets.
type AWSS3Storage struct {
bucketID string
client awsS3ClientAPI
optFns []func(*s3.Options)
}
// NewAWSS3Storage creates a Storage client for AWS S3: https://aws.amazon.com/s3/
//
// You need to provide credentials to authenticate to AWS using the cfg parameter.
func NewAWSS3Storage(ctx context.Context, bucketID string, optFns ...func(*s3.Options)) (*AWSS3Storage, error) {
// Create S3 client
cfg, err := awsconfig.LoadDefaultConfig(ctx)
if err != nil {
return nil, err
}
client := s3.NewFromConfig(cfg, optFns...)
store := &AWSS3Storage{client: client, bucketID: bucketID, optFns: optFns}
// Try to create new bucket, continue if bucket already exists
if err := store.createBucket(ctx, bucketID, cfg.Region, optFns...); err != nil {
return nil, err
}
return store, nil
}
// Get returns a DEK from from AWS S3 Storage by key ID.
func (s *AWSS3Storage) Get(ctx context.Context, keyID string) ([]byte, error) {
getObjectInput := &s3.GetObjectInput{
Bucket: &s.bucketID,
Key: &keyID,
}
output, err := s.client.GetObject(ctx, getObjectInput, s.optFns...)
if err != nil {
var nsk *types.NoSuchKey
if errors.As(err, &nsk) {
return nil, ErrDEKUnset
}
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.
func (s *AWSS3Storage) Put(ctx context.Context, keyID string, data []byte) error {
putObjectInput := &s3.PutObjectInput{
Bucket: &s.bucketID,
Key: &keyID,
Body: bytes.NewReader(data),
Tagging: &config.AWSS3Tag,
}
if _, err := s.client.PutObject(ctx, putObjectInput, s.optFns...); err != nil {
return fmt.Errorf("uploading DEK to storage: %w", err)
}
return nil
}
func (s *AWSS3Storage) createBucket(ctx context.Context, bucketID, region string, optFns ...func(*s3.Options)) error {
createBucketInput := &s3.CreateBucketInput{
Bucket: &bucketID,
CreateBucketConfiguration: &types.CreateBucketConfiguration{
LocationConstraint: types.BucketLocationConstraint(region),
},
}
if _, err := s.client.CreateBucket(ctx, createBucketInput, optFns...); err != nil {
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
}