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 (
"bytes"
"context"
"fmt"
"io"
"github.com/Azure/azure-sdk-for-go/sdk/storage/azblob"
2022-10-19 02:57:09 -04:00
"github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/blob"
"github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/bloberror"
"github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/container"
2023-01-12 10:22:47 -05:00
"github.com/edgelesssys/constellation/v2/internal/kms/config"
2022-03-22 11:03:15 -04:00
)
2022-03-28 10:49:17 -04:00
type azureBlobAPI interface {
2022-10-19 02:57:09 -04:00
CreateContainer ( context . Context , string , * container . CreateOptions ) ( azblob . CreateContainerResponse , error )
DownloadStream ( context . Context , string , string , * blob . DownloadStreamOptions ) ( azblob . DownloadStreamResponse , error )
UploadStream ( context . Context , string , string , io . Reader , * azblob . UploadStreamOptions ) ( azblob . UploadStreamResponse , error )
2022-03-28 10:49:17 -04:00
}
2022-03-22 11:03:15 -04:00
// AzureStorage is an implementation of the Storage interface, storing keys in the Azure Blob Store.
type AzureStorage struct {
2022-10-19 02:57:09 -04:00
client azureBlobAPI
2022-03-28 10:49:17 -04:00
connectionString string
containerName string
opts * AzureOpts
2022-03-22 11:03:15 -04:00
}
// AzureOpts are additional options to be used when interacting with the Azure API.
type AzureOpts struct {
2022-10-19 02:57:09 -04:00
service * azblob . ClientOptions
download * azblob . DownloadStreamOptions
upload * azblob . UploadStreamOptions
2022-03-22 11:03:15 -04:00
}
// NewAzureStorage initializes a storage client using Azure's Blob Storage: https://azure.microsoft.com/en-us/services/storage/blobs/
//
// A connections string is required to connect to the Storage Account, see https://docs.microsoft.com/en-us/azure/storage/common/storage-configure-connection-string
// If the container does not exists, a new one is created automatically.
// Connect options for the Client, Downloader and Uploader can be configured using opts.
func NewAzureStorage ( ctx context . Context , connectionString , containerName string , opts * AzureOpts ) ( * AzureStorage , error ) {
if opts == nil {
opts = & AzureOpts { }
}
2022-03-28 10:49:17 -04:00
2022-10-19 02:57:09 -04:00
client , err := azblob . NewClientFromConnectionString ( connectionString , opts . service )
if err != nil {
return nil , fmt . Errorf ( "creating storage client from connection string: %w" , err )
}
2022-03-28 10:49:17 -04:00
s := & AzureStorage {
2022-10-19 02:57:09 -04:00
client : client ,
2022-03-28 10:49:17 -04:00
connectionString : connectionString ,
containerName : containerName ,
opts : opts ,
2022-03-22 11:03:15 -04:00
}
// Try to create a new storage container, continue if it already exists
2022-03-28 10:49:17 -04:00
if err := s . createContainerOrContinue ( ctx ) ; err != nil {
return nil , err
2022-03-22 11:03:15 -04:00
}
2022-03-28 10:49:17 -04:00
return s , nil
2022-03-22 11:03:15 -04:00
}
// Get returns a DEK from from Azure Blob Storage by key ID.
func ( s * AzureStorage ) Get ( ctx context . Context , keyID string ) ( [ ] byte , error ) {
2022-10-19 02:57:09 -04:00
res , err := s . client . DownloadStream ( ctx , s . containerName , keyID , s . opts . download )
2022-03-22 11:03:15 -04:00
if err != nil {
2022-10-19 02:57:09 -04:00
if bloberror . HasCode ( err , bloberror . BlobNotFound ) {
2022-03-22 11:03:15 -04:00
return nil , ErrDEKUnset
}
return nil , fmt . Errorf ( "downloading DEK from storage: %w" , err )
}
2022-10-19 02:57:09 -04:00
defer res . Body . Close ( )
return io . ReadAll ( res . Body )
2022-03-22 11:03:15 -04:00
}
// Put saves a DEK to Azure Blob Storage by key ID.
func ( s * AzureStorage ) Put ( ctx context . Context , keyID string , encDEK [ ] byte ) error {
2022-10-19 02:57:09 -04:00
if _ , err := s . client . UploadStream ( ctx , s . containerName , keyID , bytes . NewReader ( encDEK ) , s . opts . upload ) ; err != nil {
2022-03-22 11:03:15 -04:00
return fmt . Errorf ( "uploading DEK to storage: %w" , err )
}
2022-10-19 02:57:09 -04:00
2022-03-22 11:03:15 -04:00
return nil
}
2022-03-28 10:49:17 -04:00
// createContainerOrContinue creates a new storage container if necessary, or continues if it already exists.
func ( s * AzureStorage ) createContainerOrContinue ( ctx context . Context ) error {
2022-10-19 02:57:09 -04:00
_ , err := s . client . CreateContainer ( ctx , s . containerName , & azblob . CreateContainerOptions {
2022-03-28 10:49:17 -04:00
Metadata : config . StorageTags ,
} )
2022-10-19 02:57:09 -04:00
if ( err == nil ) || bloberror . HasCode ( err , bloberror . ContainerAlreadyExists ) {
2022-03-28 10:49:17 -04:00
return nil
}
return fmt . Errorf ( "creating storage container: %w" , err )
2022-03-22 11:03:15 -04:00
}