internal: refactor storage credentials (#1071)

* Move storage clients to separate packages

* Allow setting of client credentials for AWS S3

* Use managed identity client secret or default credentials for Azure Blob Storage

* Use credentials file to authorize GCS client

---------

Signed-off-by: Daniel Weiße <dw@edgeless.systems>
This commit is contained in:
Daniel Weiße 2023-03-02 15:08:31 +01:00 committed by GitHub
parent 96b4b74a7a
commit 5eb73706f5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
30 changed files with 857 additions and 1130 deletions

View File

@ -20,6 +20,7 @@ import (
"github.com/edgelesssys/constellation/v2/internal/crypto/testvector"
"github.com/edgelesssys/constellation/v2/internal/file"
kmssetup "github.com/edgelesssys/constellation/v2/internal/kms/setup"
"github.com/edgelesssys/constellation/v2/internal/kms/uri"
"github.com/edgelesssys/constellation/v2/internal/logger"
"github.com/edgelesssys/constellation/v2/internal/oid"
"github.com/edgelesssys/constellation/v2/internal/versions/components"
@ -90,7 +91,7 @@ func TestInit(t *testing.T) {
initSecretHash, err := bcrypt.GenerateFromPassword(initSecret, bcrypt.DefaultCost)
require.NoError(t, err)
masterSecret := kmssetup.MasterSecret{Key: []byte("secret"), Salt: []byte("salt")}
masterSecret := uri.MasterSecret{Key: []byte("secret"), Salt: []byte("salt")}
testCases := map[string]struct {
nodeLock *fakeLock
@ -108,14 +109,14 @@ func TestInit(t *testing.T) {
disk: &stubDisk{},
fileHandler: file.NewHandler(afero.NewMemMapFs()),
initSecretHash: initSecretHash,
req: &initproto.InitRequest{InitSecret: initSecret, KmsUri: masterSecret.EncodeToURI(), StorageUri: kmssetup.NoStoreURI},
req: &initproto.InitRequest{InitSecret: initSecret, KmsUri: masterSecret.EncodeToURI(), StorageUri: uri.NoStoreURI},
},
"node locked": {
nodeLock: lockedLock,
initializer: &stubClusterInitializer{},
disk: &stubDisk{},
fileHandler: file.NewHandler(afero.NewMemMapFs()),
req: &initproto.InitRequest{InitSecret: initSecret, KmsUri: masterSecret.EncodeToURI(), StorageUri: kmssetup.NoStoreURI},
req: &initproto.InitRequest{InitSecret: initSecret, KmsUri: masterSecret.EncodeToURI(), StorageUri: uri.NoStoreURI},
initSecretHash: initSecretHash,
wantErr: true,
wantShutdown: true,
@ -125,7 +126,7 @@ func TestInit(t *testing.T) {
initializer: &stubClusterInitializer{},
disk: &stubDisk{openErr: someErr},
fileHandler: file.NewHandler(afero.NewMemMapFs()),
req: &initproto.InitRequest{InitSecret: initSecret, KmsUri: masterSecret.EncodeToURI(), StorageUri: kmssetup.NoStoreURI},
req: &initproto.InitRequest{InitSecret: initSecret, KmsUri: masterSecret.EncodeToURI(), StorageUri: uri.NoStoreURI},
initSecretHash: initSecretHash,
wantErr: true,
},
@ -134,7 +135,7 @@ func TestInit(t *testing.T) {
initializer: &stubClusterInitializer{},
disk: &stubDisk{uuidErr: someErr},
fileHandler: file.NewHandler(afero.NewMemMapFs()),
req: &initproto.InitRequest{InitSecret: initSecret, KmsUri: masterSecret.EncodeToURI(), StorageUri: kmssetup.NoStoreURI},
req: &initproto.InitRequest{InitSecret: initSecret, KmsUri: masterSecret.EncodeToURI(), StorageUri: uri.NoStoreURI},
initSecretHash: initSecretHash,
wantErr: true,
},
@ -143,7 +144,7 @@ func TestInit(t *testing.T) {
initializer: &stubClusterInitializer{},
disk: &stubDisk{updatePassphraseErr: someErr},
fileHandler: file.NewHandler(afero.NewMemMapFs()),
req: &initproto.InitRequest{InitSecret: initSecret, KmsUri: masterSecret.EncodeToURI(), StorageUri: kmssetup.NoStoreURI},
req: &initproto.InitRequest{InitSecret: initSecret, KmsUri: masterSecret.EncodeToURI(), StorageUri: uri.NoStoreURI},
initSecretHash: initSecretHash,
wantErr: true,
},
@ -152,7 +153,7 @@ func TestInit(t *testing.T) {
initializer: &stubClusterInitializer{},
disk: &stubDisk{},
fileHandler: file.NewHandler(afero.NewReadOnlyFs(afero.NewMemMapFs())),
req: &initproto.InitRequest{InitSecret: initSecret, KmsUri: masterSecret.EncodeToURI(), StorageUri: kmssetup.NoStoreURI},
req: &initproto.InitRequest{InitSecret: initSecret, KmsUri: masterSecret.EncodeToURI(), StorageUri: uri.NoStoreURI},
initSecretHash: initSecretHash,
wantErr: true,
},
@ -161,7 +162,7 @@ func TestInit(t *testing.T) {
initializer: &stubClusterInitializer{initClusterErr: someErr},
disk: &stubDisk{},
fileHandler: file.NewHandler(afero.NewMemMapFs()),
req: &initproto.InitRequest{InitSecret: initSecret, KmsUri: masterSecret.EncodeToURI(), StorageUri: kmssetup.NoStoreURI},
req: &initproto.InitRequest{InitSecret: initSecret, KmsUri: masterSecret.EncodeToURI(), StorageUri: uri.NoStoreURI},
initSecretHash: initSecretHash,
wantErr: true,
},
@ -249,9 +250,9 @@ func TestSetupDisk(t *testing.T) {
disk: disk,
}
masterSecret := kmssetup.MasterSecret{Key: tc.masterKey, Salt: tc.salt}
masterSecret := uri.MasterSecret{Key: tc.masterKey, Salt: tc.salt}
cloudKms, err := kmssetup.KMS(context.Background(), kmssetup.NoStoreURI, masterSecret.EncodeToURI())
cloudKms, err := kmssetup.KMS(context.Background(), uri.NoStoreURI, masterSecret.EncodeToURI())
require.NoError(err)
assert.NoError(server.setupDisk(context.Background(), cloudKms))
})

View File

@ -33,7 +33,7 @@ import (
"github.com/edgelesssys/constellation/v2/internal/file"
"github.com/edgelesssys/constellation/v2/internal/grpc/dialer"
grpcRetry "github.com/edgelesssys/constellation/v2/internal/grpc/retry"
kmssetup "github.com/edgelesssys/constellation/v2/internal/kms/setup"
"github.com/edgelesssys/constellation/v2/internal/kms/uri"
"github.com/edgelesssys/constellation/v2/internal/license"
"github.com/edgelesssys/constellation/v2/internal/retry"
"github.com/edgelesssys/constellation/v2/internal/versions"
@ -164,7 +164,7 @@ func (i *initCmd) initialize(cmd *cobra.Command, newDialer func(validator *cloud
MasterSecret: masterSecret.Key,
Salt: masterSecret.Salt,
KmsUri: masterSecret.EncodeToURI(),
StorageUri: kmssetup.NoStoreURI,
StorageUri: uri.NoStoreURI,
KeyEncryptionKeyId: "",
UseExistingKek: false,
CloudServiceAccountUri: serviceAccURI,
@ -351,19 +351,19 @@ type initFlags struct {
}
// readOrGenerateMasterSecret reads a base64 encoded master secret from file or generates a new 32 byte secret.
func (i *initCmd) readOrGenerateMasterSecret(outWriter io.Writer, fileHandler file.Handler, filename string) (kmssetup.MasterSecret, error) {
func (i *initCmd) readOrGenerateMasterSecret(outWriter io.Writer, fileHandler file.Handler, filename string) (uri.MasterSecret, error) {
if filename != "" {
i.log.Debugf("Reading master secret from file %q", filename)
var secret kmssetup.MasterSecret
var secret uri.MasterSecret
if err := fileHandler.ReadJSON(filename, &secret); err != nil {
return kmssetup.MasterSecret{}, err
return uri.MasterSecret{}, err
}
if len(secret.Key) < crypto.MasterSecretLengthMin {
return kmssetup.MasterSecret{}, fmt.Errorf("provided master secret is smaller than the required minimum of %d Bytes", crypto.MasterSecretLengthMin)
return uri.MasterSecret{}, fmt.Errorf("provided master secret is smaller than the required minimum of %d Bytes", crypto.MasterSecretLengthMin)
}
if len(secret.Salt) < crypto.RNGLengthDefault {
return kmssetup.MasterSecret{}, fmt.Errorf("provided salt is smaller than the required minimum of %d Bytes", crypto.RNGLengthDefault)
return uri.MasterSecret{}, fmt.Errorf("provided salt is smaller than the required minimum of %d Bytes", crypto.RNGLengthDefault)
}
return secret, nil
}
@ -372,19 +372,19 @@ func (i *initCmd) readOrGenerateMasterSecret(outWriter io.Writer, fileHandler fi
i.log.Debugf("Generating new master secret")
key, err := crypto.GenerateRandomBytes(crypto.MasterSecretLengthDefault)
if err != nil {
return kmssetup.MasterSecret{}, err
return uri.MasterSecret{}, err
}
salt, err := crypto.GenerateRandomBytes(crypto.RNGLengthDefault)
if err != nil {
return kmssetup.MasterSecret{}, err
return uri.MasterSecret{}, err
}
secret := kmssetup.MasterSecret{
secret := uri.MasterSecret{
Key: key,
Salt: salt,
}
i.log.Debugf("Generated master secret key and salt values")
if err := fileHandler.WriteJSON(constants.MasterSecretFilename, secret, file.OptNone); err != nil {
return kmssetup.MasterSecret{}, err
return uri.MasterSecret{}, err
}
fmt.Fprintf(outWriter, "Your Constellation master secret was successfully written to ./%s\n", constants.MasterSecretFilename)
return secret, nil

View File

@ -18,9 +18,6 @@ import (
"testing"
"time"
kmssetup "github.com/edgelesssys/constellation/v2/internal/kms/setup"
"github.com/edgelesssys/constellation/v2/internal/versions"
"github.com/edgelesssys/constellation/v2/bootstrapper/initproto"
"github.com/edgelesssys/constellation/v2/cli/internal/cloudcmd"
"github.com/edgelesssys/constellation/v2/cli/internal/clusterid"
@ -33,9 +30,11 @@ import (
"github.com/edgelesssys/constellation/v2/internal/grpc/atlscredentials"
"github.com/edgelesssys/constellation/v2/internal/grpc/dialer"
"github.com/edgelesssys/constellation/v2/internal/grpc/testdialer"
"github.com/edgelesssys/constellation/v2/internal/kms/uri"
"github.com/edgelesssys/constellation/v2/internal/license"
"github.com/edgelesssys/constellation/v2/internal/logger"
"github.com/edgelesssys/constellation/v2/internal/oid"
"github.com/edgelesssys/constellation/v2/internal/versions"
"github.com/spf13/afero"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
@ -193,7 +192,7 @@ func TestInitialize(t *testing.T) {
require.NoError(err)
// assert.Contains(out.String(), base64.StdEncoding.EncodeToString([]byte("ownerID")))
assert.Contains(out.String(), hex.EncodeToString([]byte("clusterID")))
var secret kmssetup.MasterSecret
var secret uri.MasterSecret
assert.NoError(fileHandler.ReadJSON(constants.MasterSecretFilename, &secret))
assert.NotEmpty(secret.Key)
assert.NotEmpty(secret.Salt)
@ -288,7 +287,7 @@ func TestReadOrGenerateMasterSecret(t *testing.T) {
createFileFunc: func(handler file.Handler) error {
return handler.WriteJSON(
"someSecret",
kmssetup.MasterSecret{Key: []byte("constellation-master-secret"), Salt: []byte("constellation-32Byte-length-salt")},
uri.MasterSecret{Key: []byte("constellation-master-secret"), Salt: []byte("constellation-32Byte-length-salt")},
file.OptNone,
)
},
@ -319,7 +318,7 @@ func TestReadOrGenerateMasterSecret(t *testing.T) {
createFileFunc: func(handler file.Handler) error {
return handler.WriteJSON(
"shortSecret",
kmssetup.MasterSecret{Key: []byte("constellation-master-secret"), Salt: []byte("short")},
uri.MasterSecret{Key: []byte("constellation-master-secret"), Salt: []byte("short")},
file.OptNone,
)
},
@ -331,7 +330,7 @@ func TestReadOrGenerateMasterSecret(t *testing.T) {
createFileFunc: func(handler file.Handler) error {
return handler.WriteJSON(
"shortSecret",
kmssetup.MasterSecret{Key: []byte("short"), Salt: []byte("constellation-32Byte-length-salt")},
uri.MasterSecret{Key: []byte("short"), Salt: []byte("constellation-32Byte-length-salt")},
file.OptNone,
)
},
@ -377,7 +376,7 @@ func TestReadOrGenerateMasterSecret(t *testing.T) {
tc.filename = strings.Trim(filename[1], "\n")
}
var masterSecret kmssetup.MasterSecret
var masterSecret uri.MasterSecret
require.NoError(fileHandler.ReadJSON(tc.filename, &masterSecret))
assert.Equal(masterSecret.Key, secret.Key)
assert.Equal(masterSecret.Salt, secret.Salt)

View File

@ -24,7 +24,7 @@ import (
"github.com/edgelesssys/constellation/v2/internal/file"
"github.com/edgelesssys/constellation/v2/internal/grpc/dialer"
grpcRetry "github.com/edgelesssys/constellation/v2/internal/grpc/retry"
kmssetup "github.com/edgelesssys/constellation/v2/internal/kms/setup"
"github.com/edgelesssys/constellation/v2/internal/kms/uri"
"github.com/edgelesssys/constellation/v2/internal/retry"
"github.com/spf13/afero"
"github.com/spf13/cobra"
@ -73,7 +73,7 @@ func (r *recoverCmd) recover(
}
r.log.Debugf("Using flags: %+v", flags)
var masterSecret kmssetup.MasterSecret
var masterSecret uri.MasterSecret
r.log.Debugf("Loading master secret file from %s", flags.secretPath)
if err := fileHandler.ReadJSON(flags.secretPath, &masterSecret); err != nil {
return err
@ -102,7 +102,7 @@ func (r *recoverCmd) recover(
r.log.Debugf("Created a new validator")
doer.setDialer(newDialer(validator), flags.endpoint)
r.log.Debugf("Set dialer for endpoint %s", flags.endpoint)
doer.setURIs(masterSecret.EncodeToURI(), kmssetup.NoStoreURI)
doer.setURIs(masterSecret.EncodeToURI(), uri.NoStoreURI)
r.log.Debugf("Set secrets")
if err := r.recoverCall(cmd.Context(), cmd.OutOrStdout(), interval, doer); err != nil {
if grpcRetry.ServiceIsUnavailable(err) {

View File

@ -26,7 +26,7 @@ import (
"github.com/edgelesssys/constellation/v2/internal/grpc/atlscredentials"
"github.com/edgelesssys/constellation/v2/internal/grpc/dialer"
"github.com/edgelesssys/constellation/v2/internal/grpc/testdialer"
kmssetup "github.com/edgelesssys/constellation/v2/internal/kms/setup"
"github.com/edgelesssys/constellation/v2/internal/kms/uri"
"github.com/edgelesssys/constellation/v2/internal/logger"
"github.com/spf13/afero"
"github.com/stretchr/testify/assert"
@ -158,7 +158,7 @@ func TestRecover(t *testing.T) {
require.NoError(fileHandler.WriteJSON(
"constellation-mastersecret.json",
kmssetup.MasterSecret{Key: tc.masterSecret.Secret, Salt: tc.masterSecret.Salt},
uri.MasterSecret{Key: tc.masterSecret.Secret, Salt: tc.masterSecret.Salt},
file.OptNone,
))

13
go.mod
View File

@ -53,11 +53,14 @@ require (
github.com/Azure/go-autorest/autorest/to v0.4.0
github.com/aws/aws-sdk-go-v2 v1.17.5
github.com/aws/aws-sdk-go-v2/config v1.18.15
github.com/aws/aws-sdk-go-v2/credentials v1.13.15
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.23
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.55
github.com/aws/aws-sdk-go-v2/service/cloudfront v1.26.0
github.com/aws/aws-sdk-go-v2/service/cloudwatchlogs v1.20.5
github.com/aws/aws-sdk-go-v2/service/ec2 v1.86.1
github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2 v1.19.5
github.com/aws/aws-sdk-go-v2/service/resourcegroupstaggingapi v1.14.5
github.com/aws/aws-sdk-go-v2/service/s3 v1.30.5
github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.18.6
github.com/aws/smithy-go v1.13.5
@ -72,6 +75,7 @@ require (
github.com/google/go-tpm-tools v0.3.10
github.com/googleapis/gax-go/v2 v2.7.0
github.com/grpc-ecosystem/go-grpc-middleware v1.3.0
github.com/hashicorp/go-kms-wrapping/v2 v2.0.8
github.com/hashicorp/go-kms-wrapping/wrappers/awskms/v2 v2.0.7
github.com/hashicorp/go-kms-wrapping/wrappers/azurekeyvault/v2 v2.0.7
github.com/hashicorp/go-kms-wrapping/wrappers/gcpckms/v2 v2.0.8
@ -113,10 +117,9 @@ require (
k8s.io/kubernetes v1.26.2
k8s.io/mount-utils v0.26.2
k8s.io/utils v0.0.0-20230220204549-a5ecb0141aa5
sigs.k8s.io/yaml v1.3.0
)
require github.com/apparentlymart/go-textseg/v13 v13.0.0 // indirect
require (
cloud.google.com/go v0.107.0 // indirect
cloud.google.com/go/iam v0.8.0 // indirect
@ -144,20 +147,18 @@ require (
github.com/Masterminds/sprig/v3 v3.2.3 // indirect
github.com/Masterminds/squirrel v1.5.3 // indirect
github.com/Microsoft/go-winio v0.6.0 // indirect
github.com/apparentlymart/go-textseg/v13 v13.0.0 // indirect
github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d // indirect
github.com/aws/aws-sdk-go v1.44.209 // indirect
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.10 // indirect
github.com/aws/aws-sdk-go-v2/credentials v1.13.15 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.29 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.23 // indirect
github.com/aws/aws-sdk-go-v2/internal/ini v1.3.30 // indirect
github.com/aws/aws-sdk-go-v2/internal/v4a v1.0.21 // indirect
github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2 v1.19.5
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.11 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.24 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.23 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.13.23 // indirect
github.com/aws/aws-sdk-go-v2/service/resourcegroupstaggingapi v1.14.5
github.com/aws/aws-sdk-go-v2/service/sso v1.12.4 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.4 // indirect
github.com/aws/aws-sdk-go-v2/service/sts v1.18.5 // indirect
@ -224,7 +225,6 @@ require (
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
github.com/hashicorp/go-hclog v1.3.1 // indirect
github.com/hashicorp/go-kms-wrapping/v2 v2.0.8
github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/hashicorp/go-retryablehttp v0.7.2 // indirect
github.com/hashicorp/go-secure-stdlib/awsutil v0.1.6 // indirect
@ -323,5 +323,4 @@ require (
sigs.k8s.io/kustomize/api v0.12.1 // indirect
sigs.k8s.io/kustomize/kyaml v0.13.9 // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect
sigs.k8s.io/yaml v1.3.0
)

View File

@ -54,27 +54,9 @@ require (
)
require (
cloud.google.com/go v0.107.0 // indirect
cloud.google.com/go/compute v1.18.0 // indirect
cloud.google.com/go/compute/metadata v0.2.3 // indirect
cloud.google.com/go/iam v0.8.0 // indirect
cloud.google.com/go/kms v1.8.0 // indirect
cloud.google.com/go/storage v1.28.1 // indirect
github.com/Azure/azure-sdk-for-go v68.0.0+incompatible // indirect
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.3.1 // indirect
github.com/Azure/azure-sdk-for-go/sdk/internal v1.1.2 // indirect
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v0.6.1 // indirect
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
github.com/Azure/go-autorest v14.2.0+incompatible // indirect
github.com/Azure/go-autorest/autorest v0.11.28 // indirect
github.com/Azure/go-autorest/autorest/adal v0.9.21 // indirect
github.com/Azure/go-autorest/autorest/azure/auth v0.5.11 // indirect
github.com/Azure/go-autorest/autorest/azure/cli v0.4.6 // indirect
github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect
github.com/Azure/go-autorest/autorest/to v0.4.0 // indirect
github.com/Azure/go-autorest/autorest/validation v0.3.1 // indirect
github.com/Azure/go-autorest/logger v0.2.1 // indirect
github.com/Azure/go-autorest/tracing v0.6.0 // indirect
github.com/BurntSushi/toml v1.2.1 // indirect
github.com/MakeNowJust/heredoc v1.0.0 // indirect
github.com/Masterminds/goutils v1.1.1 // indirect
@ -86,7 +68,6 @@ require (
github.com/acomagu/bufpipe v1.0.4 // indirect
github.com/apparentlymart/go-textseg/v13 v13.0.0 // indirect
github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d // indirect
github.com/aws/aws-sdk-go v1.44.209 // indirect
github.com/aws/aws-sdk-go-v2 v1.17.5 // indirect
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.10 // indirect
github.com/aws/aws-sdk-go-v2/config v1.18.15 // indirect
@ -119,7 +100,6 @@ require (
github.com/cyberphone/json-canonicalization v0.0.0-20210303052042-6bc126869bf4 // indirect
github.com/cyphar/filepath-securejoin v0.2.3 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/dimchansky/utfbom v1.1.1 // indirect
github.com/docker/cli v20.10.21+incompatible // indirect
github.com/docker/distribution v2.8.1+incompatible // indirect
github.com/docker/docker v20.10.22+incompatible // indirect
@ -153,7 +133,6 @@ require (
github.com/go-playground/validator/v10 v10.11.2 // indirect
github.com/gobwas/glob v0.2.3 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang-jwt/jwt/v4 v4.4.2 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/google/btree v1.1.2 // indirect
@ -178,15 +157,8 @@ require (
github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
github.com/hashicorp/go-hclog v1.3.1 // indirect
github.com/hashicorp/go-kms-wrapping/v2 v2.0.8 // indirect
github.com/hashicorp/go-kms-wrapping/wrappers/awskms/v2 v2.0.7 // indirect
github.com/hashicorp/go-kms-wrapping/wrappers/azurekeyvault/v2 v2.0.7 // indirect
github.com/hashicorp/go-kms-wrapping/wrappers/gcpckms/v2 v2.0.8 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/hashicorp/go-retryablehttp v0.7.2 // indirect
github.com/hashicorp/go-secure-stdlib/awsutil v0.1.6 // indirect
github.com/hashicorp/go-uuid v1.0.3 // indirect
github.com/hashicorp/go-version v1.6.0 // indirect
github.com/hashicorp/hc-install v0.4.0 // indirect
github.com/hashicorp/terraform-exec v0.17.3 // indirect
@ -215,7 +187,6 @@ require (
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // indirect
github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/mitchellh/go-wordwrap v1.0.0 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/mitchellh/reflectwalk v1.0.2 // indirect
@ -281,7 +252,6 @@ require (
golang.org/x/text v0.7.0 // indirect
golang.org/x/time v0.3.0 // indirect
golang.org/x/tools v0.6.0 // indirect
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect
google.golang.org/api v0.110.0 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/genproto v0.0.0-20230209215440-0dfe4f8abfcc // indirect

View File

@ -32,7 +32,6 @@ cloud.google.com/go v0.92.2/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+Y
cloud.google.com/go v0.92.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI=
cloud.google.com/go v0.93.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI=
cloud.google.com/go v0.107.0 h1:qkj22L7bgkl6vIeZDlOY2po43Mx/TIa2Wsa7VR+PEww=
cloud.google.com/go v0.107.0/go.mod h1:wpc2eNrD7hXUTy8EKS10jkxpZBjASrORK7goS+3YX2I=
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
@ -46,10 +45,6 @@ cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2Aawl
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk=
cloud.google.com/go/iam v0.8.0 h1:E2osAkZzxI/+8pZcxVLcDtAQx/u+hZXVryUaYQ5O0Kk=
cloud.google.com/go/iam v0.8.0/go.mod h1:lga0/y3iH6CX7sYqypWJ33hf7kkfXJag67naqGESjkE=
cloud.google.com/go/kms v1.8.0 h1:VrJLOsMRzW7IqTTYn+OYupqF3iKSE060Nrn+PECrYjg=
cloud.google.com/go/kms v1.8.0/go.mod h1:4xFEhYFqvW+4VMELtZyxomGSYtSQKzM178ylFW4jMAg=
cloud.google.com/go/longrunning v0.3.0 h1:NjljC+FYPV3uh5/OwWT6pVU+doBqMg2x/rZlE+CamDs=
cloud.google.com/go/monitoring v0.1.0/go.mod h1:Hpm3XfzJv+UTiXzCG5Ffp0wijzHTC7Cv4eR7o3x/fEE=
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
@ -67,8 +62,6 @@ cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohl
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo=
cloud.google.com/go/storage v1.28.1 h1:F5QDG5ChchaAVQhINh24U99OWHURqrW8OmQcGKXcbgI=
cloud.google.com/go/storage v1.28.1/go.mod h1:Qnisd4CqDdo6BGs2AD5LLnEsmSQ80wQ5ogcBBKhU86Y=
cloud.google.com/go/trace v0.1.0/go.mod h1:wxEwsoeRVPbeSkt7ZC9nWCgmoKQRAoySN7XHW2AmI7g=
code.gitea.io/sdk/gitea v0.11.3/go.mod h1:z3uwDV/b9Ls47NGukYM9XhnHtqPh/J+t40lsUrR6JDY=
contrib.go.opencensus.io/exporter/aws v0.0.0-20181029163544-2befc13012d0/go.mod h1:uu1P0UCM/6RbsMrgPa98ll8ZcHM858i/AD06a9aLRCA=
@ -84,47 +77,11 @@ github.com/Azure/azure-amqp-common-go/v2 v2.1.0/go.mod h1:R8rea+gJRuJR6QxTir/XuE
github.com/Azure/azure-pipeline-go v0.2.1/go.mod h1:UGSo8XybXnIGZ3epmeBw7Jdz+HiUVpqIlpz/HKHylF4=
github.com/Azure/azure-sdk-for-go v29.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
github.com/Azure/azure-sdk-for-go v30.1.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
github.com/Azure/azure-sdk-for-go v68.0.0+incompatible h1:fcYLmCpyNYRnvJbPerq7U0hS+6+I79yEDJBqVNcqUzU=
github.com/Azure/azure-sdk-for-go v68.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.3.1 h1:gVXuXcWd1i4C2Ruxe321aU+IKGaStvGB/S90PUPB/W8=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.3.1/go.mod h1:DffdKW9RFqa5VgmsjUOsS7UE7eiA5iAvYUs63bhKQ0M=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.2.1 h1:T8quHYlUGyb/oqtSTwqlCr1ilJHrDv+ZtpSfo+hm1BU=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.1.2 h1:+5VZ72z0Qan5Bog5C+ZkgSqUbeVUd9wgtHOrIKuc5b8=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.1.2/go.mod h1:eWRD7oawr1Mu1sLCawqVc0CUiF43ia3qQMxLscsKQ9w=
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v0.6.1 h1:YvQv9Mz6T8oR5ypQOL6erY0Z5t71ak1uHV4QFokCOZk=
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v0.6.1/go.mod h1:c6WvOhtmjNUWbLfOG1qxM/q0SPvQNSVJvolm+C52dIU=
github.com/Azure/azure-service-bus-go v0.9.1/go.mod h1:yzBx6/BUGfjfeqbRZny9AQIbIe3AcV9WZbAdpkoXOa0=
github.com/Azure/azure-storage-blob-go v0.8.0/go.mod h1:lPI3aLPpuLTeUwh1sViKXFxwl2B6teiRqI0deQUvsw0=
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8=
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
github.com/Azure/go-autorest v12.0.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs=
github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
github.com/Azure/go-autorest/autorest v0.11.24/go.mod h1:G6kyRlFnTuSbEYkQGawPfsCswgme4iYf6rfSKUDzbCc=
github.com/Azure/go-autorest/autorest v0.11.28 h1:ndAExarwr5Y+GaHE6VCaY1kyS/HwwGGyuimVhWsHOEM=
github.com/Azure/go-autorest/autorest v0.11.28/go.mod h1:MrkzG3Y3AH668QyF9KRk5neJnGgmhQ6krbhR8Q5eMvA=
github.com/Azure/go-autorest/autorest/adal v0.9.18/go.mod h1:XVVeme+LZwABT8K5Lc3hA4nAe8LDBVle26gTrguhhPQ=
github.com/Azure/go-autorest/autorest/adal v0.9.21 h1:jjQnVFXPfekaqb8vIsv2G1lxshoW+oGv4MDlhRtnYZk=
github.com/Azure/go-autorest/autorest/adal v0.9.21/go.mod h1:zua7mBUaCc5YnSLKYgGJR/w5ePdMDA6H56upLsHzA9U=
github.com/Azure/go-autorest/autorest/azure/auth v0.5.11 h1:P6bYXFoao05z5uhOQzbC3Qd8JqF3jUoocoTeIxkp2cA=
github.com/Azure/go-autorest/autorest/azure/auth v0.5.11/go.mod h1:84w/uV8E37feW2NCJ08uT9VBfjfUHpgLVnG2InYD6cg=
github.com/Azure/go-autorest/autorest/azure/cli v0.4.5/go.mod h1:ADQAXrkgm7acgWVUNamOgh8YNrv4p27l3Wc55oVfpzg=
github.com/Azure/go-autorest/autorest/azure/cli v0.4.6 h1:w77/uPk80ZET2F+AfQExZyEWtn+0Rk/uw17m9fv5Ajc=
github.com/Azure/go-autorest/autorest/azure/cli v0.4.6/go.mod h1:piCfgPho7BiIDdEQ1+g4VmKyD5y+p/XtSNqE6Hc4QD0=
github.com/Azure/go-autorest/autorest/date v0.3.0 h1:7gUk1U5M/CQbp9WoqinNzJar+8KY+LPI6wiWrP/myHw=
github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74=
github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k=
github.com/Azure/go-autorest/autorest/mocks v0.4.2 h1:PGN4EDXnuQbojHbU0UWoNvmu9AGVwYHG9/fkDYhtAfw=
github.com/Azure/go-autorest/autorest/mocks v0.4.2/go.mod h1:Vy7OitM9Kei0i1Oj+LvyAWMXJHeKH1MVlzFugfVrmyU=
github.com/Azure/go-autorest/autorest/to v0.4.0 h1:oXVqrxakqqV1UZdSazDOPOLvOIz+XA683u8EctwboHk=
github.com/Azure/go-autorest/autorest/to v0.4.0/go.mod h1:fE8iZBn7LQR7zH/9XU2NcPR4o9jEImooCeWJcYV/zLE=
github.com/Azure/go-autorest/autorest/validation v0.3.1 h1:AgyqjAd94fwNAoTjl/WQXg4VvFeRFpO+UhNyRXqF1ac=
github.com/Azure/go-autorest/autorest/validation v0.3.1/go.mod h1:yhLgjC0Wda5DYXl6JAsWyUe4KVNffhoDhG0zVzUMo3E=
github.com/Azure/go-autorest/logger v0.2.1 h1:IG7i4p/mDa2Ce4TRyAO8IHnVhAVF3RFU+ZtXWSmf4Tg=
github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8=
github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUMfuitfgcfuo=
github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU=
github.com/AzureAD/microsoft-authentication-library-for-go v0.8.1 h1:oPdPEZFSbl7oSPEAIPMPBMUmiL+mqgzBJwM/9qYcwNg=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/toml v1.2.1 h1:9F2/+DoOYIOksmaJFPw1tGFy1eDnIJXg+UHjuD8lTak=
github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
@ -214,10 +171,7 @@ github.com/aws/aws-sdk-go v1.23.20/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpi
github.com/aws/aws-sdk-go v1.25.11/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
github.com/aws/aws-sdk-go v1.25.37/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
github.com/aws/aws-sdk-go v1.30.27/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0=
github.com/aws/aws-sdk-go v1.37.0/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro=
github.com/aws/aws-sdk-go v1.44.209 h1:wZuiaA4eaqYZmoZXqGgNHqVD7y7kUGFvACDGBgowTps=
github.com/aws/aws-sdk-go v1.44.209/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI=
github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g=
github.com/aws/aws-sdk-go-v2 v1.17.5 h1:TzCUW1Nq4H8Xscph5M/skINUitxM5UBAyvm2s7XBzL4=
github.com/aws/aws-sdk-go-v2 v1.17.5/go.mod h1:uzbQtefpm44goOPmdKyAlXSNcwlRgF3ePWVW6EtJvvw=
@ -354,10 +308,7 @@ github.com/devigned/tab v0.1.1/go.mod h1:XG9mPq0dFghrYvoBF3xdRrJzSTX1b7IQrvaL9mz
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
github.com/dimchansky/utfbom v1.1.0/go.mod h1:rO41eb7gLfo8SF1jd9F8HplJm1Fewwi4mQvIirEdv+8=
github.com/dimchansky/utfbom v1.1.1 h1:vV6w1AhK4VMnhBno/TPVCoK9U/LP0PkLCS9tbxHdi/U=
github.com/dimchansky/utfbom v1.1.1/go.mod h1:SxdoEBH5qIqFocHMyGOXVAybYJdr71b1Q/j0mACtrfE=
github.com/distribution/distribution/v3 v3.0.0-20221208165359-362910506bc2 h1:aBfCb7iqHmDEIp6fBvC/hQUddQfg+3qdYjwzaiP9Hnc=
github.com/dnaeon/go-vcr v1.2.0 h1:zHCHvJYTMh1N7xnV7zf1m1GPBF9Ad0Jk/whtQ1663qI=
github.com/docker/cli v20.10.21+incompatible h1:qVkgyYUnOLQ98LtXBrwd/duVqPT2X4SHndOuGsfwyhU=
github.com/docker/cli v20.10.21+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
github.com/docker/distribution v2.8.1+incompatible h1:Q50tZOPR6T/hjNsyc9g8/syEs6bk8XXApsHjKukMl68=
@ -409,7 +360,6 @@ github.com/facebookgo/limitgroup v0.0.0-20150612190941-6abd8d71ec01 h1:IeaD1VDVB
github.com/facebookgo/muster v0.0.0-20150708232844-fd3d7953fd52 h1:a4DFiKFJiDRGFD1qIcqGLX/WlUMD9dyLSLDt+9QZgt8=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
github.com/fatih/color v1.14.1 h1:qfhVLaG5s+nCROl1zJsZRxFeYrHLqWroPOQ8BWiNb4w=
github.com/fatih/color v1.14.1/go.mod h1:2oHN61fhTpgcxD3TSWCgKDiH1+x4OiDVVGH8WlgGZGg=
github.com/felixge/httpsnoop v1.0.3 h1:s/nj+GCswXYzN5v2DpNMuMQYe+0DDwt5WVCU6CWBdXk=
@ -570,10 +520,6 @@ github.com/gogo/protobuf v1.3.0/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXP
github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=
github.com/golang-jwt/jwt/v4 v4.2.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=
github.com/golang-jwt/jwt/v4 v4.4.2 h1:rcc4lwaZgFMCZ5jxF9ABolDcIHdBytAFgqFPbSJQAYs=
github.com/golang-jwt/jwt/v4 v4.4.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
@ -681,12 +627,10 @@ github.com/google/licenseclassifier v0.0.0-20210325184830-bb04aff29e72/go.mod h1
github.com/google/logger v1.1.1 h1:+6Z2geNxc9G+4D4oDO9njjjn2d0wN5d7uOo0vOIW1NQ=
github.com/google/logger v1.1.1/go.mod h1:BkeJZ+1FhQ+/d087r4dzojEg1u2ZX+ZqG1jTUrLM+zQ=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/martian v2.1.1-0.20190517191504-25dcb96d9e51+incompatible h1:xmapqc1AyLoB+ddYT6r04bD9lIjlOqGaREovi0SzFaE=
github.com/google/martian v2.1.1-0.20190517191504-25dcb96d9e51+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
github.com/google/martian/v3 v3.2.1/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk=
github.com/google/martian/v3 v3.3.2 h1:IqNFLAmvJOgVlpdEBiQbDc2EwKW77amAycfTuWKdfvw=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
@ -773,18 +717,8 @@ github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtng
github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=
github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ=
github.com/hashicorp/go-hclog v0.16.2/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=
github.com/hashicorp/go-hclog v1.3.1 h1:vDwF1DFNZhntP4DAjuTpOw3uEgMUpXh1pB5fW9DqHpo=
github.com/hashicorp/go-hclog v1.3.1/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M=
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
github.com/hashicorp/go-kms-wrapping/v2 v2.0.8 h1:9Q2lu1YbbmiAgvYZ7Pr31RdlVonUpX+mmDL7Z7qTA2U=
github.com/hashicorp/go-kms-wrapping/v2 v2.0.8/go.mod h1:qTCjxGig/kjuj3hk1z8pOUrzbse/GxB1tGfbrq8tGJg=
github.com/hashicorp/go-kms-wrapping/wrappers/awskms/v2 v2.0.7 h1:E3eEWpkofgPNrYyYznfS1+drq4/jFcqHQVNcL7WhUCo=
github.com/hashicorp/go-kms-wrapping/wrappers/awskms/v2 v2.0.7/go.mod h1:j5vefRoguQUG7iM4reS/hKIZssU1lZRqNPM5Wow6UnM=
github.com/hashicorp/go-kms-wrapping/wrappers/azurekeyvault/v2 v2.0.7 h1:X27JWuPW6Gmi2l7NMm0pvnp7z7hhtns2TeIOQU93mqI=
github.com/hashicorp/go-kms-wrapping/wrappers/azurekeyvault/v2 v2.0.7/go.mod h1:i7Dt9mDsVUQG/I639jtdQerliaO2SvvPnpYPhZ8CGZ4=
github.com/hashicorp/go-kms-wrapping/wrappers/gcpckms/v2 v2.0.8 h1:16I8OqBEuxZIowwn3jiLvhlx+z+ia4dJc9stvz0yUBU=
github.com/hashicorp/go-kms-wrapping/wrappers/gcpckms/v2 v2.0.8/go.mod h1:6QUMo5BrXAtbzSuZilqmx0A4px2u6PeFK7vfp2WIzeM=
github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
@ -793,14 +727,10 @@ github.com/hashicorp/go-retryablehttp v0.6.4/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER
github.com/hashicorp/go-retryablehttp v0.7.2 h1:AcYqCvkpalPnPF2pn0KamgwamS42TqUDDYFRKq/RAd0=
github.com/hashicorp/go-retryablehttp v0.7.2/go.mod h1:Jy/gPYAdjqffZ/yFGCFV2doI5wjtH1ewM9u8iYVjtX8=
github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
github.com/hashicorp/go-secure-stdlib/awsutil v0.1.6 h1:W9WN8p6moV1fjKLkeqEgkAMu5rauy9QeYDAmIaPuuiA=
github.com/hashicorp/go-secure-stdlib/awsutil v0.1.6/go.mod h1:MpCPSPGLDILGb4JMm94/mMi3YysIqsXzGCzkEZjcjXg=
github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8=
github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/hashicorp/go-version v1.5.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek=
@ -856,7 +786,6 @@ github.com/jhump/protoreflect v1.8.2/go.mod h1:7GcYQDdMU/O/BBrl/cX6PNHpXh6cenjd8
github.com/jhump/protoreflect v1.9.0/go.mod h1:7GcYQDdMU/O/BBrl/cX6PNHpXh6cenjd8pneu5yW7Tg=
github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
github.com/jmespath/go-jmespath v0.3.0/go.mod h1:9QtRXoHjLGCJ5IBSaohpXITPlowMeeYCZ7fLUTSywik=
github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
@ -912,14 +841,12 @@ github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORN
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k=
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 h1:SOEGU9fKiNWd/HOJuq6+3iTQz8KNCLtVX6idSoTLdUw=
github.com/lann/builder v0.0.0-20180802200727-47ae307949d0/go.mod h1:dXGbAdH5GtBTC4WfIxhKZfyBF/HBFgRZSWwZ9g/He9o=
@ -963,9 +890,6 @@ github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaO
github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-ieproxy v0.0.0-20190610004146-91bb50d98149/go.mod h1:31jz6HNzdxOmlERGGEc4v/dMssOfmp2p5bT/okiKFFc=
@ -976,8 +900,6 @@ github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hd
github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ=
github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84=
github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng=
github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
@ -1010,7 +932,6 @@ github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFW
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
github.com/mitchellh/go-wordwrap v1.0.0 h1:6GlHJ/LTGMrIJbwgdqdl2eEH8o+Exx/0m8ir9Gns0u4=
@ -1119,7 +1040,6 @@ github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0
github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
github.com/pjbgf/sha1cd v0.2.3 h1:uKQP/7QOzNtKYH7UTohZLcjF5/55EnTw0jO/Ru4jZwI=
github.com/pjbgf/sha1cd v0.2.3/go.mod h1:HOK9QrgzdHpbc2Kzip0Q1yi3M2MFGPADtR6HjG65m5M=
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 h1:KoWmjvw+nsYOo29YJK9vDA65RGE3NrOnUtO7a+RF9HU=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
@ -1197,7 +1117,6 @@ github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6L
github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.2.2/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
github.com/rogpeppe/go-internal v1.8.1 h1:geMPLpDpQOgVyCg5z5GoRwLHepNdb71NXb67XFkP+Eg=
github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU=
@ -1303,7 +1222,6 @@ github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
@ -1498,10 +1416,8 @@ golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5y
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.0.0-20220826181053-bd7e27e6170d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
golang.org/x/crypto v0.6.0 h1:qfktjS5LUO+fFKeJXZ+ikTRijMmljikvG68fpMMruSc=
@ -1610,7 +1526,6 @@ golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g=
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
@ -1691,7 +1606,6 @@ golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@ -1704,7 +1618,6 @@ golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@ -1745,19 +1658,16 @@ golang.org/x/sys v0.0.0-20210629170331-7dc0b73dc9fb/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220825204002-c680a09ffe64/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@ -1767,7 +1677,6 @@ golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXR
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.0.0-20220722155259-a9ba230a4035/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ=
golang.org/x/term v0.5.0 h1:n2a8QNdAb0sZNpU9R1ALUXBbY+w51fCQDN+7EdxNBsY=
@ -1879,8 +1788,6 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk=
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8=
google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk=
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
google.golang.org/api v0.5.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=

View File

@ -51,7 +51,8 @@ Further, the vault type is chosen to configure whether or not the Key Vault is a
### Google KMS
Providing credentials to your application for Google's Cloud KMS h
Providing credentials to your application for Google's Cloud KMS happens through the usage of service accounts.
A credentials file for the service account is used to authorize the client.
Note that the service account used for authentication requires the following permissions:
@ -74,109 +75,30 @@ Supported are:
Each Plugin requires credentials to authenticate itself to a CSP.
### AWS S3 Bucket
#### AWS S3 Bucket
To use the AWS S3 Bucket plugin, you need to have an existing [AWS account](https://aws.amazon.com/de/premiumsupport/knowledge-center/create-and-activate-aws-account/).
For authentication an access key ID and an access key secret is used.
As a fallback, the client may try to automatically fetch the data from the local AWS directory.
For authentication, you have to pass a config file to the plugin. The AWS config package lets you automatically fetch the data from the local AWS directory.
#### Azure Blob Storage
#### Passing credentials automatically
Authorization for Azure Blob Storage happens through the use of manged identities.
The managed identity requires the following permissions:
You need to store your credentials in your local AWS directory at `$HOME/.aws/`. The AWS config package uses the values from the directory to build a config file, which is used to authenticate the client. The local AWS directory must contain two files:
* `Microsoft.Storage/storageAccounts/blobServices/containers/write` (only if a storage container is not set up in advance)
* `Microsoft.Storage/storageAccounts/blobServices/containers/blobs/write`
* `Microsoft.Storage/storageAccounts/blobServices/containers/blobs/read`
* `Microsoft.Storage/storageAccounts/blobServices/containers/blobs/add/action`
* `credentials`
#### Google Cloud Storage
```bash
[default]
aws_access_key_id = MyAccessKeyId
aws_secret_access_key = MySecretAccessKey
```
Providing credentials to your application for Google's Cloud Storage happens through the usage of service accounts.
A credentials file for the service account is used to authorize the client.
* `config`
Note that the service account requires the following permissions:
```bash
[default]
region = MyRegion
output = json
```
If you have the [AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html) installed, you can
initialise the files with the following command:
```bash
aws configure
```
To create the client:
```Go
cfg, err := config.LoadDefaultConfig(context.TODO())
store, err := storage.NewAWSS3Storage(context.TODO(), "bucketName", cfg, func(*s3.Options) {})
```
### Azure Blob Storage
To use the Azure Blob storage plugin, you need to first [create a storage account](https://docs.microsoft.com/en-us/azure/storage/common/storage-account-create?tabs=azure-portal) or give your application access to an existing storage account.
The plugin uses a connection string created for the storage account to authenticate itself to the Azure API.
The connection string can be found in your storage account in the Azure Portal under the "Access Keys" section or with the following Azure CLI command:
```bash
az storage account show-connection-string -g MyResourceGroup -n MyStorageAccount
```
The client will use the specified Blob container if it already exists, or create it first otherwise.
To create the client:
```Go
connectionString := "DefaultEndpointsProtocol=https;AccountName=<myAccountName>;AccountKey=<myAccountKey>;EndpointSuffix=core.windows.net"
store, err := storage.NewAzureStorage(context.TODO(), connectionString, "myContainer", nil)
```
### Google Cloud Storage
To use the Google Cloud Storage plugin, the Cloud Storage API needs to be enabled in your Google Cloud Account. You can use an existing bucket, create a new bucket yourself, or let the plugin create the bucket on initialization.
When using the Google Cloud APIs, your application will typically [authenticate as a service account](https://cloud.google.com/docs/authentication/production).
You have two options for passing service account credentials to the Storage plugin: (1) Fetching them automatically from the environment or (2) passing them manually in your Go code.
Note that the serivce account requires the following permissions:
* `storage.buckets.create`
* `storage.buckets.create` (only if a bucket is not set up in advance)
* `storage.buckets.get`
* `storage.objects.create`
* `storage.objects.get`
* `storage.objects.update`
#### Finding credentials automatically
If your application is running inside a Google Cloud environment, and you have [attached a service account](https://cloud.google.com/iam/docs/impersonating-service-accounts#attaching-to-resources) to that environment, the Storage Plugin can retrieve credentials for the service account automatically.
If your application is running in an environment with no service account attached, you can manually attach a [service account key](https://cloud.google.com/iam/docs/service-accounts#service_account_keys) to that environment.
After you [created a service account and stored its access key to file](https://cloud.google.com/docs/authentication/production#create_service_account) you need to set the environment variable `GOOGLE_APPLICATION_CREDENTIALS` to the location of that file.
The Storage Plugin will then be able to automatically load the credentials from there:
```bash
export GOOGLE_APPLICATION_CREDENTIALS="/path/to/service-account-file.json"
```
To create the client:
```Go
store, err := storage.NewGoogleCloudStorage(context.TODO(), "myProject", "myBucket", nil)
```
#### Passing credentials manually
You may also explicitly use your service account file in code.
First, create a service account and key the same way as in [finding credentials automatically](#finding-credentials-automatically).
You can then specify the location of the file in your application code.
To create the client:
```Go
credentialFile := "/path/to/service-account-file.json"
opts := option.WithCredentialsFile(credentialFile)
store, err := storage.NewGoogleCloudStorage(context.TODO(), "myProject", "myBucket", nil, opts)
```

View File

@ -17,7 +17,6 @@ package setup
import (
"context"
"encoding/base64"
"fmt"
"net/url"
@ -26,37 +25,12 @@ import (
"github.com/edgelesssys/constellation/v2/internal/kms/kms/azure"
"github.com/edgelesssys/constellation/v2/internal/kms/kms/cluster"
"github.com/edgelesssys/constellation/v2/internal/kms/kms/gcp"
"github.com/edgelesssys/constellation/v2/internal/kms/storage"
"github.com/edgelesssys/constellation/v2/internal/kms/storage/awss3"
"github.com/edgelesssys/constellation/v2/internal/kms/storage/azureblob"
"github.com/edgelesssys/constellation/v2/internal/kms/storage/gcs"
"github.com/edgelesssys/constellation/v2/internal/kms/uri"
)
// Well known endpoints for KMS services.
const (
AWSKMSURI = "kms://aws?keyPolicy=%s&kekID=%s"
AzureKMSURI = "kms://azure?tenantID=%s&clientID=%s&clientSecret=%s&name=%s&type=%s&kekID=%s"
GCPKMSURI = "kms://gcp?project=%s&location=%s&keyRing=%s&protectionLvl=%s&kekID=%s"
ClusterKMSURI = "kms://cluster-kms?key=%s&salt=%s"
AWSS3URI = "storage://aws?bucket=%s"
AzureBlobURI = "storage://azure?container=%s&connectionString=%s"
GCPStorageURI = "storage://gcp?projects=%s&bucket=%s"
NoStoreURI = "storage://no-store"
)
// MasterSecret holds the master key and salt for deriving keys.
type MasterSecret struct {
Key []byte `json:"key"`
Salt []byte `json:"salt"`
}
// EncodeToURI returns an URI encoding the master secret.
func (m *MasterSecret) EncodeToURI() string {
return fmt.Sprintf(
ClusterKMSURI,
base64.URLEncoding.EncodeToString(m.Key),
base64.URLEncoding.EncodeToString(m.Salt),
)
}
// KMSInformation about an existing KMS.
type KMSInformation struct {
KMSURI string
@ -76,41 +50,41 @@ func KMS(ctx context.Context, storageURI, kmsURI string) (kms.CloudKMS, error) {
// getStore creates a key store depending on the given parameters.
func getStore(ctx context.Context, storageURI string) (kms.Storage, error) {
uri, err := url.Parse(storageURI)
url, err := url.Parse(storageURI)
if err != nil {
return nil, err
}
if uri.Scheme != "storage" {
return nil, fmt.Errorf("invalid storage URI: invalid scheme: %s", uri.Scheme)
if url.Scheme != "storage" {
return nil, fmt.Errorf("invalid storage URI: invalid scheme: %s", url.Scheme)
}
switch uri.Host {
switch url.Host {
case "aws":
bucket, err := getAWSS3Config(uri)
cfg, err := uri.DecodeAWSS3ConfigFromURI(storageURI)
if err != nil {
return nil, err
}
return storage.NewAWSS3Storage(ctx, bucket, nil)
return awss3.New(ctx, cfg)
case "azure":
container, connString, err := getAzureBlobConfig(uri)
cfg, err := uri.DecodeAzureBlobConfigFromURI(storageURI)
if err != nil {
return nil, err
}
return storage.NewAzureStorage(ctx, connString, container, nil)
return azureblob.New(ctx, cfg)
case "gcp":
project, bucket, err := getGCPStorageConfig(uri)
cfg, err := uri.DecodeGoogleCloudStorageConfigFromURI(storageURI)
if err != nil {
return nil, err
}
return storage.NewGoogleCloudStorage(ctx, project, bucket, nil)
return gcs.New(ctx, cfg)
case "no-store":
return nil, nil
default:
return nil, fmt.Errorf("unknown storage type: %s", uri.Host)
return nil, fmt.Errorf("unknown storage type: %s", url.Host)
}
}
@ -157,68 +131,3 @@ func getKMS(ctx context.Context, kmsURI string, store kms.Storage) (kms.CloudKMS
return nil, fmt.Errorf("unknown KMS type: %s", url.Host)
}
}
type defaultPolicyProducer struct {
policy string
}
func (p *defaultPolicyProducer) CreateKeyPolicy(keyID string) (string, error) {
return p.policy, nil
}
func getAWSS3Config(uri *url.URL) (string, error) {
r, err := getConfig(uri.Query(), []string{"bucket"})
return r[0], err
}
func getAWSKMSConfig(uri *url.URL) (*defaultPolicyProducer, string, error) {
r, err := getConfig(uri.Query(), []string{"keyPolicy", "kekID"})
if err != nil {
return nil, "", err
}
if len(r) != 2 {
return nil, "", fmt.Errorf("expected 2 KmsURI args, got %d", len(r))
}
kekID, err := base64.URLEncoding.DecodeString(r[1])
if err != nil {
return nil, "", fmt.Errorf("parsing kekID from kmsUri: %w", err)
}
return &defaultPolicyProducer{policy: r[0]}, string(kekID), err
}
func getAzureBlobConfig(uri *url.URL) (string, string, error) {
r, err := getConfig(uri.Query(), []string{"container", "connectionString"})
if err != nil {
return "", "", err
}
return r[0], r[1], nil
}
func getGCPStorageConfig(uri *url.URL) (string, string, error) {
r, err := getConfig(uri.Query(), []string{"project", "bucket"})
return r[0], r[1], err
}
// getConfig parses url query values, returning a map of the requested values.
// Returns an error if a key has no value.
// This function MUST always return a slice of the same length as len(keys).
func getConfig(values url.Values, keys []string) ([]string, error) {
res := make([]string, len(keys))
for idx, key := range keys {
val := values.Get(key)
if val == "" {
return res, fmt.Errorf("missing value for key: %q", key)
}
val, err := url.QueryUnescape(val)
if err != nil {
return res, fmt.Errorf("failed to unescape value for key: %q", key)
}
res[idx] = val
}
return res, nil
}

View File

@ -8,18 +8,13 @@ package setup
import (
"context"
"encoding/base64"
"fmt"
"net/url"
"testing"
"github.com/edgelesssys/constellation/v2/internal/kms/uri"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.uber.org/goleak"
)
const constellationKekID = "Constellation"
func TestMain(m *testing.M) {
goleak.VerifyTestMain(m,
// https://github.com/census-instrumentation/opencensus-go/issues/1262
@ -27,105 +22,6 @@ func TestMain(m *testing.M) {
)
}
func TestGetStore(t *testing.T) {
testCases := map[string]struct {
uri string
wantErr bool
}{
"no store": {
uri: NoStoreURI,
wantErr: false,
},
"aws s3": {
uri: fmt.Sprintf(AWSS3URI, ""),
wantErr: true,
},
"azure blob": {
uri: fmt.Sprintf(AzureBlobURI, "", ""),
wantErr: true,
},
"gcp storage": {
uri: fmt.Sprintf(GCPStorageURI, "", ""),
wantErr: true,
},
"unknown store": {
uri: "storage://unknown",
wantErr: true,
},
"invalid scheme": {
uri: ClusterKMSURI,
wantErr: true,
},
"not a url": {
uri: ":/123",
wantErr: true,
},
}
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
assert := assert.New(t)
_, err := getStore(context.Background(), tc.uri)
if tc.wantErr {
assert.Error(err)
} else {
assert.NoError(err)
}
})
}
}
func TestGetKMS(t *testing.T) {
testCases := map[string]struct {
uri string
wantErr bool
}{
"cluster kms": {
uri: fmt.Sprintf(ClusterKMSURI, base64.URLEncoding.EncodeToString([]byte("key")), base64.URLEncoding.EncodeToString([]byte("salt"))),
wantErr: false,
},
"aws kms": {
uri: fmt.Sprintf(AWSKMSURI, "", ""),
wantErr: true,
},
"azure kms": {
uri: fmt.Sprintf(AzureKMSURI, "", "", "", "", "", ""),
wantErr: true,
},
"gcp kms": {
uri: fmt.Sprintf(GCPKMSURI, "", "", "", "", ""),
wantErr: true,
},
"unknown kms": {
uri: "kms://unknown",
wantErr: true,
},
"invalid scheme": {
uri: NoStoreURI,
wantErr: true,
},
"not a url": {
uri: ":/123",
wantErr: true,
},
}
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
assert := assert.New(t)
kms, err := getKMS(context.Background(), tc.uri, nil)
if tc.wantErr {
assert.Error(err)
} else {
assert.NoError(err)
assert.NotNil(kms)
}
})
}
}
func TestSetUpKMS(t *testing.T) {
assert := assert.New(t)
@ -133,98 +29,8 @@ func TestSetUpKMS(t *testing.T) {
assert.Error(err)
assert.Nil(kms)
masterSecret := MasterSecret{Key: []byte("key"), Salt: []byte("salt")}
masterSecret := uri.MasterSecret{Key: []byte("key"), Salt: []byte("salt")}
kms, err = KMS(context.Background(), "storage://no-store", masterSecret.EncodeToURI())
assert.NoError(err)
assert.NotNil(kms)
}
func TestGetAWSKMSConfig(t *testing.T) {
assert := assert.New(t)
require := require.New(t)
policy := "{keyPolicy: keyPolicy}"
escapedPolicy := url.QueryEscape(policy)
kekID := base64.URLEncoding.EncodeToString([]byte(constellationKekID))
uri, err := url.Parse(fmt.Sprintf(AWSKMSURI, escapedPolicy, kekID))
require.NoError(err)
policyProducer, rKekID, err := getAWSKMSConfig(uri)
require.NoError(err)
keyPolicy, err := policyProducer.CreateKeyPolicy("")
require.NoError(err)
assert.Equal(policy, keyPolicy)
assert.Equal(constellationKekID, rKekID)
}
func TestGetAzureBlobConfig(t *testing.T) {
assert := assert.New(t)
require := require.New(t)
connStr := "DefaultEndpointsProtocol=https;AccountName=test;AccountKey=Q29uc3RlbGxhdGlvbg==;EndpointSuffix=core.windows.net"
escapedConnStr := url.QueryEscape(connStr)
container := "test"
uri, err := url.Parse(fmt.Sprintf(AzureBlobURI, container, escapedConnStr))
require.NoError(err)
rContainer, rConnStr, err := getAzureBlobConfig(uri)
require.NoError(err)
assert.Equal(container, rContainer)
assert.Equal(connStr, rConnStr)
}
func TestGetConfig(t *testing.T) {
const testURI = "test://config?name=test-name&data=test-data&value=test-value"
testCases := map[string]struct {
uri string
keys []string
wantErr bool
}{
"success": {
uri: testURI,
keys: []string{"name", "data", "value"},
wantErr: false,
},
"less keys than capture groups": {
uri: testURI,
keys: []string{"name", "data"},
wantErr: false,
},
"invalid regex": {
uri: testURI,
keys: []string{"name", "data", "test-value"},
wantErr: true,
},
"missing value": {
uri: "test://config?name=test-name&data=test-data&value",
keys: []string{"name", "data", "value"},
wantErr: true,
},
"more keys than expected": {
uri: testURI,
keys: []string{"name", "data", "value", "anotherValue"},
wantErr: true,
},
}
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
assert := assert.New(t)
require := require.New(t)
uri, err := url.Parse(tc.uri)
require.NoError(err)
res, err := getConfig(uri.Query(), tc.keys)
if tc.wantErr {
assert.Error(err)
assert.Len(res, len(tc.keys))
} else {
assert.NoError(err)
require.Len(res, len(tc.keys))
for i := range tc.keys {
assert.NotEmpty(res[i])
}
}
})
}
}

View File

@ -4,7 +4,8 @@ Copyright (c) Edgeless Systems GmbH
SPDX-License-Identifier: AGPL-3.0-only
*/
package storage
// Package awss3 implements a storage backend for the KMS using AWS S3: https://aws.amazon.com/s3/
package awss3
import (
"bytes"
@ -14,9 +15,12 @@ import (
"io"
awsconfig "github.com/aws/aws-sdk-go-v2/config"
"github.com/aws/aws-sdk-go-v2/credentials"
"github.com/aws/aws-sdk-go-v2/service/s3"
"github.com/aws/aws-sdk-go-v2/service/s3/types"
"github.com/edgelesssys/constellation/v2/internal/kms/config"
"github.com/edgelesssys/constellation/v2/internal/kms/storage"
"github.com/edgelesssys/constellation/v2/internal/kms/uri"
)
type awsS3ClientAPI interface {
@ -25,44 +29,47 @@ type awsS3ClientAPI interface {
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 {
// Storage is an implementation of the Storage interface, storing keys in AWS S3 buckets.
type Storage struct {
bucketID string
client awsS3ClientAPI
optFns []func(*s3.Options)
}
// NewAWSS3Storage creates a Storage client for AWS S3: https://aws.amazon.com/s3/
// New creates a Storage client for AWS S3 using the provided config.
//
// 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)
// 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),
)
if err != nil {
return nil, err
return nil, fmt.Errorf("loading AWS S3 client config: %w", err)
}
client := s3.NewFromConfig(cfg, optFns...)
store := &AWSS3Storage{client: client, bucketID: bucketID, optFns: optFns}
client := s3.NewFromConfig(clientCfg)
store := &Storage{client: client, bucketID: cfg.Bucket}
// Try to create new bucket, continue if bucket already exists
if err := store.createBucket(ctx, bucketID, cfg.Region, optFns...); err != nil {
return nil, err
if err := store.createBucket(ctx, cfg.Bucket, cfg.Region); err != nil {
return nil, fmt.Errorf("creating storage bucket: %w", 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) {
func (s *Storage) 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...)
output, err := s.client.GetObject(ctx, getObjectInput)
if err != nil {
var nsk *types.NoSuchKey
if errors.As(err, &nsk) {
return nil, ErrDEKUnset
return nil, storage.ErrDEKUnset
}
return nil, fmt.Errorf("downloading DEK from storage: %w", err)
}
@ -70,27 +77,28 @@ func (s *AWSS3Storage) Get(ctx context.Context, keyID string) ([]byte, error) {
}
// Put saves a DEK to AWS S3 Storage by key ID.
func (s *AWSS3Storage) Put(ctx context.Context, keyID string, data []byte) error {
func (s *Storage) 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 {
if _, err := s.client.PutObject(ctx, putObjectInput); 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 {
func (s *Storage) createBucket(ctx context.Context, bucketID, region string) error {
createBucketInput := &s3.CreateBucketInput{
Bucket: &bucketID,
CreateBucketConfiguration: &types.CreateBucketConfiguration{
LocationConstraint: types.BucketLocationConstraint(region),
},
}
if _, err := s.client.CreateBucket(ctx, createBucketInput, optFns...); err != nil {
if _, err := s.client.CreateBucket(ctx, createBucketInput); err != nil {
var bne *types.BucketAlreadyExists
var baowby *types.BucketAlreadyOwnedByYou
if !(errors.As(err, &bne) || errors.As(err, &baowby)) {

View File

@ -4,7 +4,7 @@ Copyright (c) Edgeless Systems GmbH
SPDX-License-Identifier: AGPL-3.0-only
*/
package storage
package awss3
import (
"bytes"
@ -15,6 +15,7 @@ import (
"github.com/aws/aws-sdk-go-v2/service/s3"
"github.com/aws/aws-sdk-go-v2/service/s3/types"
"github.com/edgelesssys/constellation/v2/internal/kms/storage"
"github.com/stretchr/testify/assert"
)
@ -71,7 +72,7 @@ func TestAWSS3Get(t *testing.T) {
t.Run(name, func(t *testing.T) {
assert := assert.New(t)
store := &AWSS3Storage{
store := &Storage{
client: tc.client,
}
@ -80,9 +81,9 @@ func TestAWSS3Get(t *testing.T) {
assert.Error(err)
if tc.unsetError {
assert.ErrorIs(err, ErrDEKUnset)
assert.ErrorIs(err, storage.ErrDEKUnset)
} else {
assert.False(errors.Is(err, ErrDEKUnset))
assert.False(errors.Is(err, storage.ErrDEKUnset))
}
} else {
@ -111,7 +112,7 @@ func TestAWSS3Put(t *testing.T) {
t.Run(name, func(t *testing.T) {
assert := assert.New(t)
store := &AWSS3Storage{
store := &Storage{
client: tc.client,
}
@ -154,7 +155,7 @@ func TestAWSS3CreateBucket(t *testing.T) {
t.Run(name, func(t *testing.T) {
assert := assert.New(t)
store := &AWSS3Storage{
store := &Storage{
client: tc.client,
}

View File

@ -0,0 +1,104 @@
/*
Copyright (c) Edgeless Systems GmbH
SPDX-License-Identifier: AGPL-3.0-only
*/
// Package azureblob implements a storage backend for the KMS using Azure Blob Storage.
package azureblob
import (
"bytes"
"context"
"fmt"
"io"
"github.com/Azure/azure-sdk-for-go/sdk/azcore"
"github.com/Azure/azure-sdk-for-go/sdk/azidentity"
"github.com/Azure/azure-sdk-for-go/sdk/storage/azblob"
"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"
"github.com/edgelesssys/constellation/v2/internal/kms/config"
"github.com/edgelesssys/constellation/v2/internal/kms/storage"
"github.com/edgelesssys/constellation/v2/internal/kms/uri"
)
type azureBlobAPI interface {
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)
}
// Storage is an implementation of the Storage interface, storing keys in the Azure Blob Store.
type Storage struct {
client azureBlobAPI
container string
}
// New initializes a storage client using Azure's Blob Storage using the provided config.
//
// See the Azure docs for more information: https://azure.microsoft.com/en-us/services/storage/blobs/
func New(ctx context.Context, cfg uri.AzureBlobConfig) (*Storage, error) {
var creds azcore.TokenCredential
creds, err := azidentity.NewClientSecretCredential(cfg.TenantID, cfg.ClientID, cfg.ClientSecret, nil)
if err != nil {
// Fallback: try to load default credentials
creds, err = azidentity.NewDefaultAzureCredential(nil)
if err != nil {
return nil, fmt.Errorf("invalid client-secret credentials. Trying to load default credentials: %w", err)
}
}
client, err := azblob.NewClient(fmt.Sprintf("https://%s.blob.core.windows.net/", cfg.StorageAccount), creds, nil)
if err != nil {
return nil, fmt.Errorf("creating storage client: %w", err)
}
s := &Storage{
client: client,
container: cfg.Container,
}
// Try to create a new storage container, continue if it already exists
if err := s.createContainerOrContinue(ctx); err != nil {
return nil, err
}
return s, nil
}
// Get returns a DEK from from Azure Blob Storage by key ID.
func (s *Storage) Get(ctx context.Context, keyID string) ([]byte, error) {
res, err := s.client.DownloadStream(ctx, s.container, keyID, nil)
if err != nil {
if bloberror.HasCode(err, bloberror.BlobNotFound) {
return nil, storage.ErrDEKUnset
}
return nil, fmt.Errorf("downloading DEK from storage: %w", err)
}
defer res.Body.Close()
return io.ReadAll(res.Body)
}
// Put saves a DEK to Azure Blob Storage by key ID.
func (s *Storage) Put(ctx context.Context, keyID string, encDEK []byte) error {
if _, err := s.client.UploadStream(ctx, s.container, keyID, bytes.NewReader(encDEK), nil); err != nil {
return fmt.Errorf("uploading DEK to storage: %w", err)
}
return nil
}
// createContainerOrContinue creates a new storage container if necessary, or continues if it already exists.
func (s *Storage) createContainerOrContinue(ctx context.Context) error {
_, err := s.client.CreateContainer(ctx, s.container, &azblob.CreateContainerOptions{
Metadata: config.StorageTags,
})
if (err == nil) || bloberror.HasCode(err, bloberror.ContainerAlreadyExists) {
return nil
}
return fmt.Errorf("creating storage container: %w", err)
}

View File

@ -4,7 +4,7 @@ Copyright (c) Edgeless Systems GmbH
SPDX-License-Identifier: AGPL-3.0-only
*/
package storage
package azureblob
import (
"bytes"
@ -18,6 +18,7 @@ import (
"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"
"github.com/edgelesssys/constellation/v2/internal/kms/storage"
"github.com/stretchr/testify/assert"
)
@ -45,11 +46,9 @@ func TestAzureGet(t *testing.T) {
t.Run(name, func(t *testing.T) {
assert := assert.New(t)
client := &AzureStorage{
client: &tc.client,
connectionString: "test",
containerName: "test",
opts: &AzureOpts{},
client := &Storage{
client: &tc.client,
container: "test",
}
out, err := client.Get(context.Background(), "test-key")
@ -57,9 +56,9 @@ func TestAzureGet(t *testing.T) {
assert.Error(err)
if tc.unsetError {
assert.ErrorIs(err, ErrDEKUnset)
assert.ErrorIs(err, storage.ErrDEKUnset)
} else {
assert.False(errors.Is(err, ErrDEKUnset))
assert.False(errors.Is(err, storage.ErrDEKUnset))
}
return
}
@ -89,11 +88,9 @@ func TestAzurePut(t *testing.T) {
testData := []byte{0x1, 0x2, 0x3}
client := &AzureStorage{
client: &tc.client,
connectionString: "test",
containerName: "test",
opts: &AzureOpts{},
client := &Storage{
client: &tc.client,
container: "test",
}
err := client.Put(context.Background(), "test-key", testData)
@ -128,11 +125,9 @@ func TestCreateContainerOrContinue(t *testing.T) {
t.Run(name, func(t *testing.T) {
assert := assert.New(t)
client := &AzureStorage{
client: &tc.client,
connectionString: "test",
containerName: "test",
opts: &AzureOpts{},
client := &Storage{
client: &tc.client,
container: "test",
}
err := client.createContainerOrContinue(context.Background())

View File

@ -1,105 +0,0 @@
/*
Copyright (c) Edgeless Systems GmbH
SPDX-License-Identifier: AGPL-3.0-only
*/
package storage
import (
"bytes"
"context"
"fmt"
"io"
"github.com/Azure/azure-sdk-for-go/sdk/storage/azblob"
"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"
"github.com/edgelesssys/constellation/v2/internal/kms/config"
)
type azureBlobAPI interface {
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)
}
// AzureStorage is an implementation of the Storage interface, storing keys in the Azure Blob Store.
type AzureStorage struct {
client azureBlobAPI
connectionString string
containerName string
opts *AzureOpts
}
// AzureOpts are additional options to be used when interacting with the Azure API.
type AzureOpts struct {
service *azblob.ClientOptions
download *azblob.DownloadStreamOptions
upload *azblob.UploadStreamOptions
}
// 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{}
}
client, err := azblob.NewClientFromConnectionString(connectionString, opts.service)
if err != nil {
return nil, fmt.Errorf("creating storage client from connection string: %w", err)
}
s := &AzureStorage{
client: client,
connectionString: connectionString,
containerName: containerName,
opts: opts,
}
// Try to create a new storage container, continue if it already exists
if err := s.createContainerOrContinue(ctx); err != nil {
return nil, err
}
return s, nil
}
// Get returns a DEK from from Azure Blob Storage by key ID.
func (s *AzureStorage) Get(ctx context.Context, keyID string) ([]byte, error) {
res, err := s.client.DownloadStream(ctx, s.containerName, keyID, s.opts.download)
if err != nil {
if bloberror.HasCode(err, bloberror.BlobNotFound) {
return nil, ErrDEKUnset
}
return nil, fmt.Errorf("downloading DEK from storage: %w", err)
}
defer res.Body.Close()
return io.ReadAll(res.Body)
}
// Put saves a DEK to Azure Blob Storage by key ID.
func (s *AzureStorage) Put(ctx context.Context, keyID string, encDEK []byte) error {
if _, err := s.client.UploadStream(ctx, s.containerName, keyID, bytes.NewReader(encDEK), s.opts.upload); err != nil {
return fmt.Errorf("uploading DEK to storage: %w", err)
}
return nil
}
// createContainerOrContinue creates a new storage container if necessary, or continues if it already exists.
func (s *AzureStorage) createContainerOrContinue(ctx context.Context) error {
_, err := s.client.CreateContainer(ctx, s.containerName, &azblob.CreateContainerOptions{
Metadata: config.StorageTags,
})
if (err == nil) || bloberror.HasCode(err, bloberror.ContainerAlreadyExists) {
return nil
}
return fmt.Errorf("creating storage container: %w", err)
}

View File

@ -1,130 +0,0 @@
/*
Copyright (c) Edgeless Systems GmbH
SPDX-License-Identifier: AGPL-3.0-only
*/
package storage
import (
"context"
"errors"
"io"
"cloud.google.com/go/storage"
"google.golang.org/api/option"
)
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)
}
// GoogleCloudStorage is an implementation of the Storage interface, storing keys in Google Cloud Storage buckets.
type GoogleCloudStorage struct {
newClient func(ctx context.Context, opts ...option.ClientOption) (gcpStorageAPI, error)
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) {
s := &GoogleCloudStorage{
newClient: gcpStorageClientFactory,
projectID: projectID,
bucketName: bucketName,
opts: opts,
}
// Make sure the storage bucket exists, if not create it
if err := s.createContainerOrContinue(ctx, bucketOptions); err != nil {
return nil, err
}
return s, nil
}
// Get returns a DEK from Google Cloud Storage by key ID.
func (s *GoogleCloudStorage) Get(ctx context.Context, keyID string) ([]byte, error) {
client, err := s.newClient(ctx, s.opts...)
if err != nil {
return nil, err
}
defer client.Close()
reader, err := client.NewReader(ctx, s.bucketName, keyID)
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 {
client, err := s.newClient(ctx, s.opts...)
if err != nil {
return err
}
defer client.Close()
writer := client.NewWriter(ctx, s.bucketName, keyID)
defer writer.Close()
_, err = writer.Write(data)
return err
}
func (s *GoogleCloudStorage) createContainerOrContinue(ctx context.Context, bucketOptions *storage.BucketAttrs) error {
client, err := s.newClient(ctx, s.opts...)
if err != nil {
return err
}
defer client.Close()
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
}

View File

@ -1,114 +0,0 @@
//go:build integration
/*
Copyright (c) Edgeless Systems GmbH
SPDX-License-Identifier: AGPL-3.0-only
*/
package storage
import (
"context"
"io"
"os"
"testing"
"time"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/client"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"google.golang.org/api/option"
)
const storageEmulator = "gcr.io/cloud-devrel-public-resources/storage-testbench"
func TestGoogleCloudStorage(t *testing.T) {
assert := assert.New(t)
require := require.New(t)
containerCtx := context.Background()
// Set up the Storage Emulator
t.Log("Creating storage emulator...")
cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
require.NoError(err)
emulator, err := setupEmulator(containerCtx, cli, storageEmulator)
require.NoError(err)
defer func() { _ = cli.ContainerStop(containerCtx, emulator.ID, nil) }()
// Run the actual test
t.Setenv("STORAGE_EMULATOR_HOST", "localhost:9000")
bucketName := "test-bucket"
projectName := "test-project"
t.Log("Running test...")
ctx, cancel := context.WithTimeout(context.Background(), time.Second*50)
defer cancel()
store, err := NewGoogleCloudStorage(ctx, projectName, bucketName, nil, option.WithoutAuthentication())
require.NoError(err)
testDEK1 := []byte("test DEK")
testDEK2 := []byte("more test DEK")
// request unset value
_, err = store.Get(ctx, "test:input")
assert.Error(err)
// test Put method
assert.NoError(store.Put(ctx, "volume01", testDEK1))
assert.NoError(store.Put(ctx, "volume02", testDEK2))
// make sure values have been set
val, err := store.Get(ctx, "volume01")
assert.NoError(err)
assert.Equal(testDEK1, val)
val, err = store.Get(ctx, "volume02")
assert.NoError(err)
assert.Equal(testDEK2, val)
_, err = store.Get(ctx, "invalid:key")
assert.Error(err)
assert.ErrorIs(err, ErrDEKUnset)
}
func setupEmulator(ctx context.Context, cli *client.Client, imageName string) (container.ContainerCreateCreatedBody, error) {
reader, err := cli.ImagePull(ctx, imageName, types.ImagePullOptions{})
if err != nil {
return container.ContainerCreateCreatedBody{}, err
}
if _, err := io.Copy(os.Stdout, reader); err != nil {
return container.ContainerCreateCreatedBody{}, err
}
if err := reader.Close(); err != nil {
return container.ContainerCreateCreatedBody{}, err
}
// the 3 true statements are necessary to attach later to the container log
containerConfig := &container.Config{
Image: storageEmulator,
AttachStdout: true,
AttachStderr: true,
Tty: true,
}
emulator, err := cli.ContainerCreate(ctx, containerConfig, &container.HostConfig{NetworkMode: container.NetworkMode("host"), AutoRemove: true}, nil, nil, "google-cloud-storage-test")
if err != nil {
return emulator, err
}
if err := cli.ContainerStart(ctx, emulator.ID, types.ContainerStartOptions{}); err != nil {
return emulator, err
}
logs, err := cli.ContainerLogs(ctx, emulator.ID, types.ContainerLogsOptions{
ShowStdout: true,
Follow: true,
})
if err != nil {
return emulator, err
}
go func() { _, _ = io.Copy(os.Stdout, logs) }()
return emulator, nil
}

View File

@ -0,0 +1,131 @@
/*
Copyright (c) Edgeless Systems GmbH
SPDX-License-Identifier: AGPL-3.0-only
*/
// Package gcs implements a storage backend for the KMS using Google Cloud Storage (GCS).
package gcs
import (
"context"
"errors"
"io"
gcstorage "cloud.google.com/go/storage"
"github.com/edgelesssys/constellation/v2/internal/kms/storage"
"github.com/edgelesssys/constellation/v2/internal/kms/uri"
"google.golang.org/api/option"
)
type gcpStorageAPI interface {
Attrs(ctx context.Context, bucketName string) (*gcstorage.BucketAttrs, error)
Close() error
CreateBucket(ctx context.Context, bucketName, projectID string, attrs *gcstorage.BucketAttrs) error
NewWriter(ctx context.Context, bucketName, objectName string) io.WriteCloser
NewReader(ctx context.Context, bucketName, objectName string) (io.ReadCloser, error)
}
type wrappedGCPClient struct {
*gcstorage.Client
}
func (c *wrappedGCPClient) Attrs(ctx context.Context, bucketName string) (*gcstorage.BucketAttrs, error) {
return c.Client.Bucket(bucketName).Attrs(ctx)
}
func (c *wrappedGCPClient) CreateBucket(ctx context.Context, bucketName, projectID string, attrs *gcstorage.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)
}
// Storage is an implementation of the Storage interface, storing keys in Google Cloud Storage buckets.
type Storage struct {
newClient func(ctx context.Context) (gcpStorageAPI, error)
bucketName string
}
// New creates a Storage client for Google Cloud Storage using the provided config.
//
// See the Google docs for more information: https://cloud.google.com/storage/docs/
func New(ctx context.Context, cfg uri.GoogleCloudStorageConfig) (*Storage, error) {
s := &Storage{
newClient: newGCPStorageClientFactory(cfg.CredentialsPath),
bucketName: cfg.Bucket,
}
// Make sure the storage bucket exists, if not create it
if err := s.createContainerOrContinue(ctx, cfg.ProjectID); err != nil {
return nil, err
}
return s, nil
}
// Get returns a DEK from Google Cloud Storage by key ID.
func (s *Storage) Get(ctx context.Context, keyID string) ([]byte, error) {
client, err := s.newClient(ctx)
if err != nil {
return nil, err
}
defer client.Close()
reader, err := client.NewReader(ctx, s.bucketName, keyID)
if err != nil {
if errors.Is(err, gcstorage.ErrObjectNotExist) {
return nil, storage.ErrDEKUnset
}
return nil, err
}
defer reader.Close()
return io.ReadAll(reader)
}
// Put saves a DEK to Google Cloud Storage by key ID.
func (s *Storage) Put(ctx context.Context, keyID string, data []byte) error {
client, err := s.newClient(ctx)
if err != nil {
return err
}
defer client.Close()
writer := client.NewWriter(ctx, s.bucketName, keyID)
defer writer.Close()
_, err = writer.Write(data)
return err
}
func (s *Storage) createContainerOrContinue(ctx context.Context, projectID string) error {
client, err := s.newClient(ctx)
if err != nil {
return err
}
defer client.Close()
if _, err := client.Attrs(ctx, s.bucketName); errors.Is(err, gcstorage.ErrBucketNotExist) {
return client.CreateBucket(ctx, s.bucketName, projectID, nil)
} else if err != nil {
return err
}
return nil
}
func newGCPStorageClientFactory(credPath string) func(context.Context) (gcpStorageAPI, error) {
return func(ctx context.Context) (gcpStorageAPI, error) {
client, err := gcstorage.NewClient(ctx, option.WithCredentialsFile(credPath))
if err != nil {
return nil, err
}
return &wrappedGCPClient{client}, nil
}
}

View File

@ -4,7 +4,7 @@ Copyright (c) Edgeless Systems GmbH
SPDX-License-Identifier: AGPL-3.0-only
*/
package storage
package gcs
import (
"bytes"
@ -13,9 +13,9 @@ import (
"io"
"testing"
"cloud.google.com/go/storage"
gcstorage "cloud.google.com/go/storage"
"github.com/edgelesssys/constellation/v2/internal/kms/storage"
"github.com/stretchr/testify/assert"
"google.golang.org/api/option"
)
type stubGCPStorageAPI struct {
@ -28,19 +28,19 @@ type stubGCPStorageAPI struct {
writer *stubWriteCloser
}
func (s *stubGCPStorageAPI) stubClientFactory(ctx context.Context, opts ...option.ClientOption) (gcpStorageAPI, error) {
func (s *stubGCPStorageAPI) stubClientFactory(ctx context.Context) (gcpStorageAPI, error) {
return s, s.newClientErr
}
func (s *stubGCPStorageAPI) Attrs(ctx context.Context, bucketName string) (*storage.BucketAttrs, error) {
return &storage.BucketAttrs{}, s.attrsErr
func (s *stubGCPStorageAPI) Attrs(ctx context.Context, bucketName string) (*gcstorage.BucketAttrs, error) {
return &gcstorage.BucketAttrs{}, s.attrsErr
}
func (s *stubGCPStorageAPI) Close() error {
return nil
}
func (s *stubGCPStorageAPI) CreateBucket(ctx context.Context, bucketName, projectID string, attrs *storage.BucketAttrs) error {
func (s *stubGCPStorageAPI) CreateBucket(ctx context.Context, bucketName, projectID string, attrs *gcstorage.BucketAttrs) error {
s.createBucketCalled = true
return s.createBucketErr
}
@ -88,7 +88,7 @@ func TestGCPGet(t *testing.T) {
wantErr: true,
},
"ErrObjectNotExist error": {
client: &stubGCPStorageAPI{newReaderErr: storage.ErrObjectNotExist},
client: &stubGCPStorageAPI{newReaderErr: gcstorage.ErrObjectNotExist},
unsetError: true,
wantErr: true,
},
@ -98,9 +98,8 @@ func TestGCPGet(t *testing.T) {
t.Run(name, func(t *testing.T) {
assert := assert.New(t)
client := &GoogleCloudStorage{
client := &Storage{
newClient: tc.client.stubClientFactory,
projectID: "test",
bucketName: "test",
}
@ -109,9 +108,9 @@ func TestGCPGet(t *testing.T) {
assert.Error(err)
if tc.unsetError {
assert.ErrorIs(err, ErrDEKUnset)
assert.ErrorIs(err, storage.ErrDEKUnset)
} else {
assert.False(errors.Is(err, ErrDEKUnset))
assert.False(errors.Is(err, storage.ErrDEKUnset))
}
} else {
@ -155,9 +154,8 @@ func TestGCPPut(t *testing.T) {
t.Run(name, func(t *testing.T) {
assert := assert.New(t)
client := &GoogleCloudStorage{
client := &Storage{
newClient: tc.client.stubClientFactory,
projectID: "test",
bucketName: "test",
}
testData := []byte{0x1, 0x2, 0x3}
@ -184,7 +182,7 @@ func TestGCPCreateContainerOrContinue(t *testing.T) {
client: &stubGCPStorageAPI{},
},
"container does not exist": {
client: &stubGCPStorageAPI{attrsErr: storage.ErrBucketNotExist},
client: &stubGCPStorageAPI{attrsErr: gcstorage.ErrBucketNotExist},
createNewBucket: true,
},
"creating client fails": {
@ -197,7 +195,7 @@ func TestGCPCreateContainerOrContinue(t *testing.T) {
},
"CreateBucket fails": {
client: &stubGCPStorageAPI{
attrsErr: storage.ErrBucketNotExist,
attrsErr: gcstorage.ErrBucketNotExist,
createBucketErr: someErr,
},
wantErr: true,
@ -208,13 +206,12 @@ func TestGCPCreateContainerOrContinue(t *testing.T) {
t.Run(name, func(t *testing.T) {
assert := assert.New(t)
client := &GoogleCloudStorage{
client := &Storage{
newClient: tc.client.stubClientFactory,
projectID: "test",
bucketName: "test",
}
err := client.createContainerOrContinue(context.Background(), nil)
err := client.createContainerOrContinue(context.Background(), "project")
if tc.wantErr {
assert.Error(err)
} else {

View File

@ -0,0 +1,44 @@
/*
Copyright (c) Edgeless Systems GmbH
SPDX-License-Identifier: AGPL-3.0-only
*/
// Package memfs implements a storage backend for the KMS that stores keys in memory only.
// This package should be used for testing only.
package memfs
import (
"context"
"github.com/edgelesssys/constellation/v2/internal/kms/storage"
)
// Storage is the standard implementation of the Storage interface, storing keys in memory only.
type Storage struct {
dekPool map[string][]byte
}
// New creates and initializes a new Storage object.
func New() *Storage {
s := &Storage{
dekPool: make(map[string][]byte),
}
return s
}
// Get returns a DEK from Storage by key ID.
func (s *Storage) Get(ctx context.Context, keyID string) ([]byte, error) {
encDEK, ok := s.dekPool[keyID]
if ok {
return encDEK, nil
}
return nil, storage.ErrDEKUnset
}
// Put saves a DEK to Storage by key ID.
func (s *Storage) Put(ctx context.Context, keyID string, encDEK []byte) error {
s.dekPool[keyID] = encDEK
return nil
}

View File

@ -4,12 +4,13 @@ Copyright (c) Edgeless Systems GmbH
SPDX-License-Identifier: AGPL-3.0-only
*/
package storage
package memfs
import (
"context"
"testing"
"github.com/edgelesssys/constellation/v2/internal/kms/storage"
"github.com/stretchr/testify/assert"
"go.uber.org/goleak"
)
@ -24,28 +25,28 @@ func TestMain(m *testing.M) {
func TestMemMapStorage(t *testing.T) {
assert := assert.New(t)
storage := NewMemMapStorage()
store := New()
testDEK1 := []byte("test DEK")
testDEK2 := []byte("more test DEK")
ctx := context.Background()
// request unset value
_, err := storage.Get(ctx, "test:input")
assert.ErrorIs(err, ErrDEKUnset)
_, err := store.Get(ctx, "test:input")
assert.ErrorIs(err, storage.ErrDEKUnset)
// test Put method
assert.NoError(storage.Put(ctx, "volume01", testDEK1))
assert.NoError(storage.Put(ctx, "volume02", testDEK2))
assert.NoError(store.Put(ctx, "volume01", testDEK1))
assert.NoError(store.Put(ctx, "volume02", testDEK2))
// make sure values have been set
val, err := storage.Get(ctx, "volume01")
val, err := store.Get(ctx, "volume01")
assert.NoError(err)
assert.Equal(testDEK1, val)
val, err = storage.Get(ctx, "volume02")
val, err = store.Get(ctx, "volume02")
assert.NoError(err)
assert.Equal(testDEK2, val)
_, err = storage.Get(ctx, "invalid:key")
assert.ErrorIs(err, ErrDEKUnset)
_, err = store.Get(ctx, "invalid:key")
assert.ErrorIs(err, storage.ErrDEKUnset)
}

View File

@ -1,38 +0,0 @@
/*
Copyright (c) Edgeless Systems GmbH
SPDX-License-Identifier: AGPL-3.0-only
*/
package storage
import "context"
// MemMapStorage is the standard implementation of the Storage interface, storing keys in memory only.
type MemMapStorage struct {
dekPool map[string][]byte
}
// NewMemMapStorage creates and initialises a new MemMapStorage object.
func NewMemMapStorage() *MemMapStorage {
s := &MemMapStorage{
dekPool: make(map[string][]byte),
}
return s
}
// Get returns a DEK from MemMapStorage by key ID.
func (s *MemMapStorage) Get(ctx context.Context, keyID string) ([]byte, error) {
encDEK, ok := s.dekPool[keyID]
if ok {
return encDEK, nil
}
return nil, ErrDEKUnset
}
// Put saves a DEK to MemMapStorage by key ID.
func (s *MemMapStorage) Put(ctx context.Context, keyID string, encDEK []byte) error {
s.dekPool[keyID] = encDEK
return nil
}

View File

@ -11,7 +11,6 @@ package test
import (
"context"
"flag"
"strings"
"testing"
"time"
@ -19,9 +18,9 @@ import (
"github.com/aws/aws-sdk-go-v2/service/s3"
"github.com/aws/aws-sdk-go-v2/service/s3/types"
"github.com/edgelesssys/constellation/v2/internal/kms/kms/aws"
"github.com/edgelesssys/constellation/v2/internal/kms/storage"
"github.com/edgelesssys/constellation/v2/internal/kms/storage/awss3"
"github.com/edgelesssys/constellation/v2/internal/kms/storage/memfs"
"github.com/edgelesssys/constellation/v2/internal/kms/uri"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@ -29,53 +28,41 @@ func TestAwsStorage(t *testing.T) {
if !*runAwsStorage {
t.Skip("Skipping AWS storage test")
}
assert := assert.New(t)
if *awsAccessKey == "" || *awsAccessKeyID == "" || *awsBucket == "" || *awsRegion == "" {
flag.Usage()
t.Fatal("Required flags not set: --aws-access-key, --aws-access-key-id, --aws-bucket, --aws-region")
}
require := require.New(t)
ctx, cancel := context.WithTimeout(context.Background(), time.Second*30)
defer cancel()
bucketID := strings.ToLower(addSuffix("test-bucket"))
// create bucket
store, err := storage.NewAWSS3Storage(ctx, bucketID, func(*s3.Options) {})
cfg := uri.AWSS3Config{
Bucket: *awsBucket,
AccessKeyID: *awsAccessKeyID,
AccessKey: *awsAccessKey,
Region: *awsRegion,
}
store, err := awss3.New(ctx, cfg)
require.NoError(err)
testDEK1 := []byte("test DEK")
testDEK2 := []byte("more test DEK")
runStorageTest(t, store)
// request unset value
_, err = store.Get(ctx, "test:input")
assert.Error(err)
// test Put method
assert.NoError(store.Put(ctx, "volume01", testDEK1))
assert.NoError(store.Put(ctx, "volume02", testDEK2))
// make sure values have been set
val, err := store.Get(ctx, "volume01")
assert.NoError(err)
assert.Equal(testDEK1, val)
val, err = store.Get(ctx, "volume02")
assert.NoError(err)
assert.Equal(testDEK2, val)
_, err = store.Get(ctx, "invalid:key")
assert.Error(err)
assert.ErrorIs(err, storage.ErrDEKUnset)
cleanUpBucket(ctx, require, bucketID, func(*s3.Options) {})
cleanUpBucket(ctx, require, *awsBucket, *awsRegion)
}
func cleanUpBucket(ctx context.Context, require *require.Assertions, bucketID string, optFns ...func(*s3.Options)) {
cfg, err := config.LoadDefaultConfig(ctx)
func cleanUpBucket(ctx context.Context, require *require.Assertions, bucketID, awsRegion string) {
cfg, err := config.LoadDefaultConfig(ctx, config.WithRegion(awsRegion))
require.NoError(err)
client := s3.NewFromConfig(cfg, optFns...)
client := s3.NewFromConfig(cfg)
// List all objects of the bucket
listObjectsInput := &s3.ListObjectsV2Input{
Bucket: &bucketID,
}
output, _ := client.ListObjectsV2(ctx, listObjectsInput)
output, err := client.ListObjectsV2(ctx, listObjectsInput)
require.NoError(err)
var objects []string
var i int32
for i = 0; i < output.KeyCount; i++ {
@ -117,7 +104,7 @@ func TestAwsKms(t *testing.T) {
require := require.New(t)
store := storage.NewMemMapStorage()
store := memfs.New()
ctx, cancel := context.WithTimeout(context.Background(), time.Second*30)
defer cancel()

View File

@ -15,9 +15,9 @@ import (
"time"
"github.com/edgelesssys/constellation/v2/internal/kms/kms/azure"
"github.com/edgelesssys/constellation/v2/internal/kms/storage"
"github.com/edgelesssys/constellation/v2/internal/kms/storage/azureblob"
"github.com/edgelesssys/constellation/v2/internal/kms/storage/memfs"
"github.com/edgelesssys/constellation/v2/internal/kms/uri"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@ -25,31 +25,26 @@ func TestAzureStorage(t *testing.T) {
if !*runAzStorage {
t.Skip("Skipping Azure storage test")
}
if *azConnectionString == "" || *azContainer == "" {
if *azStorageAccount == "" || *azContainer == "" || *azClientID == "" || *azClientSecret == "" || *azTenantID == "" {
flag.Usage()
t.Fatal("Required flags not set: --az-connection-string, --az-container")
t.Fatal("Required flags not set: --az-storage-account, --az-container, --az-tenant-id, --az-client-id, --az-client-secret")
}
assert := assert.New(t)
require := require.New(t)
ctx, cancel := context.WithTimeout(context.Background(), time.Second*30)
defer cancel()
store, err := storage.NewAzureStorage(ctx, *azConnectionString, *azContainer, nil)
cfg := uri.AzureBlobConfig{
StorageAccount: *azStorageAccount,
Container: *azContainer,
TenantID: *azTenantID,
ClientID: *azClientID,
ClientSecret: *azClientSecret,
}
store, err := azureblob.New(ctx, cfg)
require.NoError(err)
testData := []byte("Constellation test data")
testName := "constellation-test"
err = store.Put(ctx, testName, testData)
assert.NoError(err)
got, err := store.Get(ctx, testName)
assert.NoError(err)
assert.Equal(testData, got)
_, err = store.Get(ctx, addSuffix("does-not-exist"))
assert.ErrorIs(err, storage.ErrDEKUnset)
runStorageTest(t, store)
}
func TestAzureKeyKMS(t *testing.T) {
@ -63,7 +58,7 @@ func TestAzureKeyKMS(t *testing.T) {
}
require := require.New(t)
store := storage.NewMemMapStorage()
store := memfs.New()
ctx, cancel := context.WithTimeout(context.Background(), time.Second*30)
defer cancel()
@ -92,7 +87,7 @@ func TestAzureKeyHSM(t *testing.T) {
}
require := require.New(t)
store := storage.NewMemMapStorage()
store := memfs.New()
ctx, cancel := context.WithTimeout(context.Background(), time.Second*30)
defer cancel()

View File

@ -15,9 +15,9 @@ import (
"time"
"github.com/edgelesssys/constellation/v2/internal/kms/kms/gcp"
"github.com/edgelesssys/constellation/v2/internal/kms/storage"
"github.com/edgelesssys/constellation/v2/internal/kms/storage/gcs"
"github.com/edgelesssys/constellation/v2/internal/kms/storage/memfs"
"github.com/edgelesssys/constellation/v2/internal/kms/uri"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@ -31,7 +31,7 @@ func TestGCPKMS(t *testing.T) {
}
require := require.New(t)
store := storage.NewMemMapStorage()
store := memfs.New()
ctx, cancel := context.WithTimeout(context.Background(), time.Second*30)
defer cancel()
@ -53,29 +53,22 @@ func TestGcpStorage(t *testing.T) {
if !*runGcpStorage {
t.Skip("Skipping Google Storage test")
}
if *gcpProjectID == "" || *gcpBucket == "" {
if *gcpProjectID == "" || *gcpBucket == "" || *gcpCredentialsPath == "" {
flag.Usage()
t.Fatal("Required flags not set: --gcp-project, --gcp-bucket ")
}
assert := assert.New(t)
require := require.New(t)
ctx, cancel := context.WithTimeout(context.Background(), time.Second*30)
defer cancel()
store, err := storage.NewGoogleCloudStorage(ctx, *gcpProjectID, *gcpBucket, nil)
assert.NoError(err)
testData := []byte("Constellation test data")
testName := "constellation-test"
cfg := uri.GoogleCloudStorageConfig{
CredentialsPath: *gcpCredentialsPath,
ProjectID: *gcpProjectID,
Bucket: *gcpBucket,
}
store, err := gcs.New(ctx, cfg)
require.NoError(err)
err = store.Put(ctx, testName, testData)
assert.NoError(err)
got, err := store.Get(ctx, testName)
assert.NoError(err)
assert.Equal(testData, got)
_, err = store.Get(ctx, addSuffix("does-not-exist"))
assert.ErrorIs(err, storage.ErrDEKUnset)
runStorageTest(t, store)
}

View File

@ -19,6 +19,7 @@ import (
"github.com/edgelesssys/constellation/v2/internal/kms/config"
"github.com/edgelesssys/constellation/v2/internal/kms/kms"
"github.com/edgelesssys/constellation/v2/internal/kms/storage"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@ -29,18 +30,19 @@ var (
runAwsStorage = flag.Bool("aws-storage", false, "set to run AWS S3 Bucket Storage test")
runAwsKms = flag.Bool("aws-kms", false, "set to run AWS KMS test")
awsRegion = flag.String("aws-region", "us-east-1", "Region to use for AWS tests. Required for AWS KMS test.")
awsAccessKeyID = flag.String("aws-access-key-id", "", "ID of the Access key to use for AWS tests. Required for AWS KMS test.")
awsAccessKey = flag.String("aws-access-key", "", "Access key to use for AWS tests. Required for AWS KMS test.")
awsAccessKeyID = flag.String("aws-access-key-id", "", "ID of the Access key to use for AWS tests. Required for AWS KMS and storage test.")
awsAccessKey = flag.String("aws-access-key", "", "Access key to use for AWS tests. Required for AWS KMS and storage test.")
awsBucket = flag.String("aws-bucket", "", "Name of the S3 bucket to use for AWS storage test. Required for AWS storage test.")
azConnectionString = flag.String("az-storage-conn", "", "Connection string for Azure storage account. Required for Azure storage test.")
azContainer = flag.String("az-container", "constellation-test-storage", "Container to save test data to. Required for Azure storage test.")
runAzStorage = flag.Bool("az-storage", false, "set to run Azure Storage test")
runAzKms = flag.Bool("az-kms", false, "set to run Azure KMS test")
runAzHsm = flag.Bool("az-hsm", false, "set to run Azure HSM test")
azVaultName = flag.String("az-vault-name", "", "Name of the Azure Key Vault to use. Required for Azure KMS/HSM test.")
azTenantID = flag.String("az-tenant-id", "", "Tenant ID to use for Azure tests. Required for Azure KMS/HSM test.")
azClientID = flag.String("az-client-id", "", "Client ID to use for Azure tests. Required for Azure KMS/HSM test.")
azClientSecret = flag.String("az-client-secret", "", "Client secret to use for Azure tests. Required for Azure KMS/HSM test.")
runAzStorage = flag.Bool("az-storage", false, "set to run Azure Storage test")
runAzKms = flag.Bool("az-kms", false, "set to run Azure KMS test")
runAzHsm = flag.Bool("az-hsm", false, "set to run Azure HSM test")
azVaultName = flag.String("az-vault-name", "", "Name of the Azure Key Vault to use. Required for Azure KMS/HSM and storage test.")
azTenantID = flag.String("az-tenant-id", "", "Tenant ID to use for Azure tests. Required for Azure KMS/HSM and storage test.")
azClientID = flag.String("az-client-id", "", "Client ID to use for Azure tests. Required for Azure KMS/HSM and storage test.")
azClientSecret = flag.String("az-client-secret", "", "Client secret to use for Azure tests. Required for Azure KMS/HSM and storage test.")
azStorageAccount = flag.String("az-storage-account", "", "Service URL for Azure storage account. Required for Azure storage test.")
azContainer = flag.String("az-container", "constellation-test-storage", "Container to save test data to. Required for Azure storage test.")
runGcpKms = flag.Bool("gcp-kms", false, "set to run Google KMS test")
runGcpStorage = flag.Bool("gcp-storage", false, "set to run Google Storage test")
@ -81,6 +83,27 @@ func runKMSTest(t *testing.T, kms kms.CloudKMS) {
t.Logf("DEK 3: %x\n", res3)
}
func runStorageTest(t *testing.T, store kms.Storage) {
assert := assert.New(t)
require := require.New(t)
testData := []byte("Constellation test data")
testName := "constellation-test"
ctx, cancel := context.WithTimeout(context.Background(), time.Second*30)
defer cancel()
err := store.Put(ctx, testName, testData)
require.NoError(err)
got, err := store.Get(ctx, testName)
require.NoError(err)
assert.Equal(testData, got)
_, err = store.Get(ctx, addSuffix("does-not-exist"))
assert.ErrorIs(err, storage.ErrDEKUnset)
}
func addSuffix(s string) string {
rand := rand.New(rand.NewSource(time.Now().UnixNano()))
letters := []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")

View File

@ -44,19 +44,23 @@ type VaultBaseURL string
// Well known endpoints for KMS services.
const (
awsKMSURI = "kms://aws?region=%s&accessKeyID=%s&acccessKey=%skeyName=%s"
awsKMSURI = "kms://aws?region=%s&accessKeyID=%s&accessKey=%s&keyName=%s"
azureKMSURI = "kms://azure?tenantID=%s&clientID=%s&clientSecret=%s&vaultName=%s&vaultType=%s&keyName=%s"
gcpKMSURI = "kms://gcp?project=%s&location=%s&keyRing=%s&credentialsPath=%s&keyName=%s"
gcpKMSURI = "kms://gcp?projectID=%s&location=%s&keyRing=%s&credentialsPath=%s&keyName=%s"
clusterKMSURI = "kms://cluster-kms?key=%s&salt=%s"
awsS3URI = "storage://aws?bucket=%s"
azureBlobURI = "storage://azure?container=%s&connectionString=%s"
gcpStorageURI = "storage://gcp?projects=%s&bucket=%s"
NoStoreURI = "storage://no-store"
awsS3URI = "storage://aws?bucket=%s&region=%s&accessKeyID=%s&accessKey=%s"
azureBlobURI = "storage://azure?account=%s&container=%s&tenantID=%s&clientID=%s&clientSecret=%s"
gcpStorageURI = "storage://gcp?projectID=%s&bucket=%s&credentialsPath=%s"
// NoStoreURI is a URI that indicates that no storage is used.
// Should only be used with cluster KMS.
NoStoreURI = "storage://no-store"
)
// MasterSecret holds the master key and salt for deriving keys.
type MasterSecret struct {
Key []byte `json:"key"`
// Key is the secret value used in HKDF to derive keys.
Key []byte `json:"key"`
// Salt is the salt used in HKDF to derive keys.
Salt []byte `json:"salt"`
}
@ -100,10 +104,14 @@ func DecodeMasterSecretFromURI(uri string) (MasterSecret, error) {
// AWSConfig is the configuration to authenticate with AWS KMS.
type AWSConfig struct {
KeyName string
Region string
// KeyName is the name of the key in AWS KMS.
KeyName string
// Region is the region of the key in AWS KMS.
Region string
// AccessKeyID is the ID of the access key used for authentication with the AWS API.
AccessKeyID string
AccessKey string
// AccessKey is the secret value used for authentication with the AWS API.
AccessKey string
}
// DecodeAWSConfigFromURI decodes an AWS configuration from a URI.
@ -157,14 +165,84 @@ func (c AWSConfig) EncodeToURI() string {
)
}
// AWSS3Config is the configuration to authenticate with AWS S3 storage bucket.
type AWSS3Config struct {
// Bucket is the name of the S3 storage bucket to use.
Bucket string
// Region is the region storage bucket is located in.
Region string
// AccessKeyID is the ID of the access key used for authentication with the AWS API.
AccessKeyID string
// AccessKey is the secret value used for authentication with the AWS API.
AccessKey string
}
// DecodeAWSS3ConfigFromURI decodes an S3 configuration from a URI.
func DecodeAWSS3ConfigFromURI(uri string) (AWSS3Config, error) {
u, err := url.Parse(uri)
if err != nil {
return AWSS3Config{}, err
}
if u.Scheme != "storage" {
return AWSS3Config{}, fmt.Errorf("invalid scheme: %q", u.Scheme)
}
if u.Host != "aws" {
return AWSS3Config{}, fmt.Errorf("invalid host: %q", u.Host)
}
q := u.Query()
bucket, err := getQueryParameter(q, "bucket")
if err != nil {
return AWSS3Config{}, err
}
region, err := getQueryParameter(q, "region")
if err != nil {
return AWSS3Config{}, err
}
accessKeyID, err := getQueryParameter(q, "accessKeyID")
if err != nil {
return AWSS3Config{}, err
}
accessKey, err := getQueryParameter(q, "accessKey")
if err != nil {
return AWSS3Config{}, err
}
return AWSS3Config{
Bucket: bucket,
Region: region,
AccessKeyID: accessKeyID,
AccessKey: accessKey,
}, nil
}
// EncodeToURI returns a URI encoding the S3 configuration.
func (s AWSS3Config) EncodeToURI() string {
return fmt.Sprintf(
awsS3URI,
url.QueryEscape(s.Bucket),
url.QueryEscape(s.Region),
url.QueryEscape(s.AccessKeyID),
url.QueryEscape(s.AccessKey),
)
}
// AzureConfig is the configuration to authenticate with Azure Key Vault.
type AzureConfig struct {
TenantID string
ClientID string
// TenantID of the Azure Active Directory the Key Vault is located in.
TenantID string
// ClientID is the ID of the managed identity used to authenticate with the Azure API.
ClientID string
// ClientSecret is the secret-value/password of the managed identity used to authenticate with the Azure API.
ClientSecret string
KeyName string
VaultName string
VaultType VaultBaseURL
// KeyName is the name of the key in Azure Key Vault.
KeyName string
// VaultName is the name of the vault.
VaultName string
// VaultType is the type of the vault.
// This defines whether or not the Key Vault is a managed HSM.
VaultType VaultBaseURL
}
// DecodeAzureConfigFromURI decodes an Azure configuration from a URI.
@ -230,14 +308,89 @@ func (a AzureConfig) EncodeToURI() string {
)
}
// AzureBlobConfig is the configuration to authenticate with Azure Blob storage.
type AzureBlobConfig struct {
// StorageAccount is the name of the storage account to use.
StorageAccount string
// Container is the name of the container to use.
Container string
// TenantID of the Azure Active Directory the Key Vault is located in.
TenantID string
// ClientID is the ID of the managed identity used to authenticate with the Azure API.
ClientID string
// ClientSecret is the secret-value/password of the managed identity used to authenticate with the Azure API.
ClientSecret string
}
// DecodeAzureBlobConfigFromURI decodes an Azure Blob configuration from a URI.
func DecodeAzureBlobConfigFromURI(uri string) (AzureBlobConfig, error) {
u, err := url.Parse(uri)
if err != nil {
return AzureBlobConfig{}, err
}
if u.Scheme != "storage" {
return AzureBlobConfig{}, fmt.Errorf("invalid scheme: %q", u.Scheme)
}
if u.Host != "azure" {
return AzureBlobConfig{}, fmt.Errorf("invalid host: %q", u.Host)
}
q := u.Query()
storageAccount, err := getQueryParameter(q, "account")
if err != nil {
return AzureBlobConfig{}, err
}
container, err := getQueryParameter(q, "container")
if err != nil {
return AzureBlobConfig{}, err
}
tenantID, err := getQueryParameter(q, "tenantID")
if err != nil {
return AzureBlobConfig{}, err
}
clientID, err := getQueryParameter(q, "clientID")
if err != nil {
return AzureBlobConfig{}, err
}
clientSecret, err := getQueryParameter(q, "clientSecret")
if err != nil {
return AzureBlobConfig{}, err
}
return AzureBlobConfig{
StorageAccount: storageAccount,
Container: container,
TenantID: tenantID,
ClientID: clientID,
ClientSecret: clientSecret,
}, nil
}
// EncodeToURI returns a URI encoding the Azure Blob configuration.
func (a AzureBlobConfig) EncodeToURI() string {
return fmt.Sprintf(
azureBlobURI,
url.QueryEscape(a.StorageAccount),
url.QueryEscape(a.Container),
url.QueryEscape(a.TenantID),
url.QueryEscape(a.ClientID),
url.QueryEscape(a.ClientSecret),
)
}
// GCPConfig is the configuration to authenticate with GCP KMS.
type GCPConfig struct {
// CredentialsPath is the path to a credentials file of a service account used to authorize against the KMS.
// CredentialsPath is the path to a credentials file of a service account used to authorize against the GCP API.
CredentialsPath string
ProjectID string
Location string
KeyRing string
KeyName string
// ProjectID is the name of the GCP project the KMS is located in.
ProjectID string
// Location is the location of the KMS.
Location string
// KeyRing is the name of the keyring.
KeyRing string
// KeyName is the name of the key in the GCP KMS.
KeyName string
}
// DecodeGCPConfigFromURI decodes a GCP configuration from a URI.
@ -255,7 +408,7 @@ func DecodeGCPConfigFromURI(uri string) (GCPConfig, error) {
}
q := u.Query()
credentials, err := getQueryParameter(q, "credentials")
credentials, err := getQueryParameter(q, "credentialsPath")
if err != nil {
return GCPConfig{}, err
}
@ -297,6 +450,61 @@ func (g GCPConfig) EncodeToURI() string {
)
}
// GoogleCloudStorageConfig is the configuration to authenticate with Google Cloud Storage.
type GoogleCloudStorageConfig struct {
// CredentialsPath is the path to a credentials file of a service account used to authorize against the GCP API.
CredentialsPath string
// ProjectID is the name of the GCP project the storage bucket is located in.
ProjectID string
// Bucket is the name of the bucket to use.
Bucket string
}
// DecodeGoogleCloudStorageConfigFromURI decodes a Google Cloud Storage configuration from a URI.
func DecodeGoogleCloudStorageConfigFromURI(uri string) (GoogleCloudStorageConfig, error) {
u, err := url.Parse(uri)
if err != nil {
return GoogleCloudStorageConfig{}, err
}
if u.Scheme != "storage" {
return GoogleCloudStorageConfig{}, fmt.Errorf("invalid scheme: %q", u.Scheme)
}
if u.Host != "gcp" {
return GoogleCloudStorageConfig{}, fmt.Errorf("invalid host: %q", u.Host)
}
q := u.Query()
credentials, err := getQueryParameter(q, "credentialsPath")
if err != nil {
return GoogleCloudStorageConfig{}, err
}
projectID, err := getQueryParameter(q, "projectID")
if err != nil {
return GoogleCloudStorageConfig{}, err
}
bucket, err := getQueryParameter(q, "bucket")
if err != nil {
return GoogleCloudStorageConfig{}, err
}
return GoogleCloudStorageConfig{
CredentialsPath: credentials,
ProjectID: projectID,
Bucket: bucket,
}, nil
}
// EncodeToURI returns a URI encoding the Google Cloud Storage configuration.
func (g GoogleCloudStorageConfig) EncodeToURI() string {
return fmt.Sprintf(
gcpStorageURI,
url.QueryEscape(g.ProjectID),
url.QueryEscape(g.Bucket),
url.QueryEscape(g.CredentialsPath),
)
}
// getBase64QueryParameter returns the url-base64-decoded value for the given key from the query parameters.
func getBase64QueryParameter(q url.Values, key string) ([]byte, error) {
value, err := getQueryParameter(q, key)

View File

@ -0,0 +1,113 @@
/*
Copyright (c) Edgeless Systems GmbH
SPDX-License-Identifier: AGPL-3.0-only
*/
package uri
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.uber.org/goleak"
)
func TestMain(m *testing.M) {
goleak.VerifyTestMain(m)
}
func TestMasterSecretURI(t *testing.T) {
cfg := MasterSecret{
Key: []byte("key"),
Salt: []byte("salt"),
}
checkURI(t, cfg, DecodeMasterSecretFromURI)
}
func TestAWSURI(t *testing.T) {
cfg := AWSConfig{
KeyName: "key",
Region: "region",
AccessKeyID: "accessKeyID",
AccessKey: "accessKey",
}
checkURI(t, cfg, DecodeAWSConfigFromURI)
}
func TestAWSS3URI(t *testing.T) {
cfg := AWSS3Config{
Bucket: "bucket",
Region: "region",
AccessKeyID: "accessKeyID",
AccessKey: "accessKey",
}
checkURI(t, cfg, DecodeAWSS3ConfigFromURI)
}
func TestAzureURI(t *testing.T) {
cfg := AzureConfig{
KeyName: "key",
TenantID: "tenantID",
ClientID: "clientID",
ClientSecret: "clientSecret",
VaultName: "vaultName",
VaultType: DefaultCloud,
}
checkURI(t, cfg, DecodeAzureConfigFromURI)
}
func TestAzureBlobURI(t *testing.T) {
cfg := AzureBlobConfig{
StorageAccount: "accountName",
Container: "containerName",
TenantID: "tenantID",
ClientID: "clientID",
ClientSecret: "clientSecret",
}
checkURI(t, cfg, DecodeAzureBlobConfigFromURI)
}
func TestGCPURI(t *testing.T) {
cfg := GCPConfig{
KeyName: "key",
ProjectID: "project",
Location: "location",
KeyRing: "keyRing",
CredentialsPath: "/path/to/credentials",
}
checkURI(t, cfg, DecodeGCPConfigFromURI)
}
func TestGoogleCloudStorageURI(t *testing.T) {
cfg := GoogleCloudStorageConfig{
ProjectID: "project",
Bucket: "bucket",
CredentialsPath: "/path/to/credentials",
}
checkURI(t, cfg, DecodeGoogleCloudStorageConfigFromURI)
}
type cfgStruct interface {
EncodeToURI() string
}
func checkURI[T any](t *testing.T, cfg cfgStruct, decodeFunc func(string) (T, error)) {
t.Helper()
require := require.New(t)
assert := assert.New(t)
uri := cfg.EncodeToURI()
decoded, err := decodeFunc(uri)
require.NoError(err, "failed to decode URI to config: %s", uri)
assert.Equal(cfg, decoded, "decoded config does not match original config")
}

View File

@ -18,6 +18,7 @@ import (
"github.com/edgelesssys/constellation/v2/internal/crypto"
"github.com/edgelesssys/constellation/v2/internal/file"
"github.com/edgelesssys/constellation/v2/internal/kms/setup"
"github.com/edgelesssys/constellation/v2/internal/kms/uri"
"github.com/edgelesssys/constellation/v2/internal/logger"
"github.com/edgelesssys/constellation/v2/keyservice/internal/server"
"github.com/spf13/afero"
@ -52,12 +53,12 @@ func main() {
if len(salt) < crypto.RNGLengthDefault {
log.With(zap.Error(errors.New("invalid salt length"))).Fatalf("Expected salt to be %d bytes, but got %d", crypto.RNGLengthDefault, len(salt))
}
masterSecret := setup.MasterSecret{Key: masterKey, Salt: salt}
masterSecret := uri.MasterSecret{Key: masterKey, Salt: salt}
// set up Key Management Service
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Minute)
defer cancel()
conKMS, err := setup.KMS(ctx, setup.NoStoreURI, masterSecret.EncodeToURI())
conKMS, err := setup.KMS(ctx, uri.NoStoreURI, masterSecret.EncodeToURI())
if err != nil {
log.With(zap.Error(err)).Fatalf("Failed to setup KMS")
}