2022-09-05 03:06:08 -04:00
|
|
|
/*
|
|
|
|
Copyright (c) Edgeless Systems GmbH
|
|
|
|
|
|
|
|
SPDX-License-Identifier: AGPL-3.0-only
|
|
|
|
*/
|
|
|
|
|
2022-03-22 11:03:15 -04:00
|
|
|
package storage
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"errors"
|
|
|
|
"io"
|
|
|
|
|
|
|
|
"cloud.google.com/go/storage"
|
|
|
|
"google.golang.org/api/option"
|
|
|
|
)
|
|
|
|
|
2022-03-25 06:55:49 -04:00
|
|
|
type gcpStorageAPI interface {
|
|
|
|
Attrs(ctx context.Context, bucketName string) (*storage.BucketAttrs, error)
|
|
|
|
Close() error
|
|
|
|
CreateBucket(ctx context.Context, bucketName, projectID string, attrs *storage.BucketAttrs) error
|
|
|
|
NewWriter(ctx context.Context, bucketName, objectName string) io.WriteCloser
|
|
|
|
NewReader(ctx context.Context, bucketName, objectName string) (io.ReadCloser, error)
|
|
|
|
}
|
|
|
|
|
|
|
|
type wrappedGCPClient struct {
|
|
|
|
*storage.Client
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *wrappedGCPClient) Attrs(ctx context.Context, bucketName string) (*storage.BucketAttrs, error) {
|
|
|
|
return c.Client.Bucket(bucketName).Attrs(ctx)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *wrappedGCPClient) CreateBucket(ctx context.Context, bucketName, projectID string, attrs *storage.BucketAttrs) error {
|
|
|
|
return c.Client.Bucket(bucketName).Create(ctx, projectID, attrs)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *wrappedGCPClient) NewWriter(ctx context.Context, bucketName, objectName string) io.WriteCloser {
|
|
|
|
return c.Client.Bucket(bucketName).Object(objectName).NewWriter(ctx)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *wrappedGCPClient) NewReader(ctx context.Context, bucketName, objectName string) (io.ReadCloser, error) {
|
|
|
|
return c.Client.Bucket(bucketName).Object(objectName).NewReader(ctx)
|
|
|
|
}
|
|
|
|
|
2022-03-22 11:03:15 -04:00
|
|
|
// GoogleCloudStorage is an implementation of the Storage interface, storing keys in Google Cloud Storage buckets.
|
|
|
|
type GoogleCloudStorage struct {
|
2022-03-25 06:55:49 -04:00
|
|
|
newClient func(ctx context.Context, opts ...option.ClientOption) (gcpStorageAPI, error)
|
2022-03-22 11:03:15 -04:00
|
|
|
projectID string
|
|
|
|
bucketName string
|
|
|
|
opts []option.ClientOption
|
|
|
|
}
|
|
|
|
|
|
|
|
// NewGoogleCloudStorage creates a Storage client for Google Cloud Storage: https://cloud.google.com/storage/docs/
|
|
|
|
//
|
|
|
|
// The parameter bucketOptions is optional, if not present default options will be created.
|
|
|
|
func NewGoogleCloudStorage(ctx context.Context, projectID, bucketName string, bucketOptions *storage.BucketAttrs, opts ...option.ClientOption) (*GoogleCloudStorage, error) {
|
2022-03-25 06:55:49 -04:00
|
|
|
s := &GoogleCloudStorage{
|
|
|
|
newClient: gcpStorageClientFactory,
|
2022-03-22 11:03:15 -04:00
|
|
|
projectID: projectID,
|
|
|
|
bucketName: bucketName,
|
|
|
|
opts: opts,
|
|
|
|
}
|
|
|
|
|
|
|
|
// Make sure the storage bucket exists, if not create it
|
2022-03-25 06:55:49 -04:00
|
|
|
if err := s.createContainerOrContinue(ctx, bucketOptions); err != nil {
|
2022-03-22 11:03:15 -04:00
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2022-03-25 06:55:49 -04:00
|
|
|
return s, nil
|
2022-03-22 11:03:15 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
// Get returns a DEK from Google Cloud Storage by key ID.
|
|
|
|
func (s *GoogleCloudStorage) Get(ctx context.Context, keyID string) ([]byte, error) {
|
2022-03-25 06:55:49 -04:00
|
|
|
client, err := s.newClient(ctx, s.opts...)
|
2022-03-22 11:03:15 -04:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
defer client.Close()
|
|
|
|
|
2022-03-25 06:55:49 -04:00
|
|
|
reader, err := client.NewReader(ctx, s.bucketName, keyID)
|
2022-03-22 11:03:15 -04:00
|
|
|
if err != nil {
|
|
|
|
if errors.Is(err, storage.ErrObjectNotExist) {
|
|
|
|
return nil, ErrDEKUnset
|
|
|
|
}
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
defer reader.Close()
|
|
|
|
|
|
|
|
return io.ReadAll(reader)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Put saves a DEK to Google Cloud Storage by key ID.
|
|
|
|
func (s *GoogleCloudStorage) Put(ctx context.Context, keyID string, data []byte) error {
|
2022-03-25 06:55:49 -04:00
|
|
|
client, err := s.newClient(ctx, s.opts...)
|
2022-03-22 11:03:15 -04:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
defer client.Close()
|
|
|
|
|
2022-03-25 06:55:49 -04:00
|
|
|
writer := client.NewWriter(ctx, s.bucketName, keyID)
|
|
|
|
defer writer.Close()
|
|
|
|
|
|
|
|
_, err = writer.Write(data)
|
|
|
|
return err
|
|
|
|
}
|
2022-03-22 11:03:15 -04:00
|
|
|
|
2022-03-25 06:55:49 -04:00
|
|
|
func (s *GoogleCloudStorage) createContainerOrContinue(ctx context.Context, bucketOptions *storage.BucketAttrs) error {
|
|
|
|
client, err := s.newClient(ctx, s.opts...)
|
|
|
|
if err != nil {
|
2022-03-22 11:03:15 -04:00
|
|
|
return err
|
|
|
|
}
|
2022-03-25 06:55:49 -04:00
|
|
|
defer client.Close()
|
2022-03-22 11:03:15 -04:00
|
|
|
|
2022-03-25 06:55:49 -04:00
|
|
|
if _, err := client.Attrs(ctx, s.bucketName); errors.Is(err, storage.ErrBucketNotExist) {
|
|
|
|
return client.CreateBucket(ctx, s.bucketName, s.projectID, bucketOptions)
|
|
|
|
} else if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func gcpStorageClientFactory(ctx context.Context, opts ...option.ClientOption) (gcpStorageAPI, error) {
|
|
|
|
client, err := storage.NewClient(ctx, opts...)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return &wrappedGCPClient{client}, nil
|
2022-03-22 11:03:15 -04:00
|
|
|
}
|