From 5eb73706f54cf00b8ef7bdb20387682f2cb7d8dd Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Daniel=20Wei=C3=9Fe?=
 <66256922+daniel-weisse@users.noreply.github.com>
Date: Thu, 2 Mar 2023 15:08:31 +0100
Subject: [PATCH] internal: refactor storage credentials (#1071)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

* 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>
---
 .../internal/initserver/initserver_test.go    |  21 +-
 cli/internal/cmd/init.go                      |  22 +-
 cli/internal/cmd/init_test.go                 |  15 +-
 cli/internal/cmd/recover.go                   |   6 +-
 cli/internal/cmd/recover_test.go              |   4 +-
 go.mod                                        |  13 +-
 hack/go.mod                                   |  30 ---
 hack/go.sum                                   |  93 -------
 internal/kms/README.md                        | 112 ++------
 internal/kms/setup/setup.go                   | 119 +--------
 internal/kms/setup/setup_test.go              | 198 +-------------
 .../{awss3storage.go => awss3/awss3.go}       |  50 ++--
 .../awss3_test.go}                            |  13 +-
 internal/kms/storage/azureblob/azureblob.go   | 104 ++++++++
 .../azureblob_test.go}                        |  31 +--
 internal/kms/storage/azurestorage.go          | 105 --------
 internal/kms/storage/gcloudstorage.go         | 130 ---------
 .../storage/gcloudstorage_integration_test.go | 114 --------
 internal/kms/storage/gcs/gcs.go               | 131 +++++++++
 .../gcs_test.go}                              |  35 ++-
 internal/kms/storage/memfs/memfs.go           |  44 +++
 .../memfs_test.go}                            |  21 +-
 internal/kms/storage/memfsstorage.go          |  38 ---
 internal/kms/test/aws_test.go                 |  55 ++--
 internal/kms/test/azure_test.go               |  37 ++-
 internal/kms/test/gcp_test.go                 |  33 +--
 internal/kms/test/integration_test.go         |  45 +++-
 internal/kms/uri/uri.go                       | 250 ++++++++++++++++--
 internal/kms/uri/uri_test.go                  | 113 ++++++++
 keyservice/cmd/main.go                        |   5 +-
 30 files changed, 857 insertions(+), 1130 deletions(-)
 rename internal/kms/storage/{awss3storage.go => awss3/awss3.go} (54%)
 rename internal/kms/storage/{awss3storage_test.go => awss3/awss3_test.go} (94%)
 create mode 100644 internal/kms/storage/azureblob/azureblob.go
 rename internal/kms/storage/{azurestorage_test.go => azureblob/azureblob_test.go} (86%)
 delete mode 100644 internal/kms/storage/azurestorage.go
 delete mode 100644 internal/kms/storage/gcloudstorage.go
 delete mode 100644 internal/kms/storage/gcloudstorage_integration_test.go
 create mode 100644 internal/kms/storage/gcs/gcs.go
 rename internal/kms/storage/{gcloudstorage_test.go => gcs/gcs_test.go} (85%)
 create mode 100644 internal/kms/storage/memfs/memfs.go
 rename internal/kms/storage/{memfsstroage_test.go => memfs/memfs_test.go} (62%)
 delete mode 100644 internal/kms/storage/memfsstorage.go
 create mode 100644 internal/kms/uri/uri_test.go

diff --git a/bootstrapper/internal/initserver/initserver_test.go b/bootstrapper/internal/initserver/initserver_test.go
index 2f5b3398c..4347ef9bc 100644
--- a/bootstrapper/internal/initserver/initserver_test.go
+++ b/bootstrapper/internal/initserver/initserver_test.go
@@ -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))
 		})
diff --git a/cli/internal/cmd/init.go b/cli/internal/cmd/init.go
index a5b8792b9..c1694b2d0 100644
--- a/cli/internal/cmd/init.go
+++ b/cli/internal/cmd/init.go
@@ -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
diff --git a/cli/internal/cmd/init_test.go b/cli/internal/cmd/init_test.go
index 52811658c..63635ecc4 100644
--- a/cli/internal/cmd/init_test.go
+++ b/cli/internal/cmd/init_test.go
@@ -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)
diff --git a/cli/internal/cmd/recover.go b/cli/internal/cmd/recover.go
index f67df6bc0..8df72cb76 100644
--- a/cli/internal/cmd/recover.go
+++ b/cli/internal/cmd/recover.go
@@ -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) {
diff --git a/cli/internal/cmd/recover_test.go b/cli/internal/cmd/recover_test.go
index 51fd4d400..668eb5826 100644
--- a/cli/internal/cmd/recover_test.go
+++ b/cli/internal/cmd/recover_test.go
@@ -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,
 			))
 
diff --git a/go.mod b/go.mod
index e96e03472..2701ef7b5 100644
--- a/go.mod
+++ b/go.mod
@@ -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
 )
diff --git a/hack/go.mod b/hack/go.mod
index f7ea7cca6..009786dd3 100644
--- a/hack/go.mod
+++ b/hack/go.mod
@@ -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
diff --git a/hack/go.sum b/hack/go.sum
index 5f37afb3a..99999b224 100644
--- a/hack/go.sum
+++ b/hack/go.sum
@@ -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=
diff --git a/internal/kms/README.md b/internal/kms/README.md
index 5ab3a6b5d..f14be0751 100644
--- a/internal/kms/README.md
+++ b/internal/kms/README.md
@@ -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)
-```
diff --git a/internal/kms/setup/setup.go b/internal/kms/setup/setup.go
index 6f584245c..eee089e77 100644
--- a/internal/kms/setup/setup.go
+++ b/internal/kms/setup/setup.go
@@ -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
-}
diff --git a/internal/kms/setup/setup_test.go b/internal/kms/setup/setup_test.go
index 46d0e24b0..a92bbc0c1 100644
--- a/internal/kms/setup/setup_test.go
+++ b/internal/kms/setup/setup_test.go
@@ -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])
-				}
-			}
-		})
-	}
-}
diff --git a/internal/kms/storage/awss3storage.go b/internal/kms/storage/awss3/awss3.go
similarity index 54%
rename from internal/kms/storage/awss3storage.go
rename to internal/kms/storage/awss3/awss3.go
index d7ee53289..ee7d5d9a7 100644
--- a/internal/kms/storage/awss3storage.go
+++ b/internal/kms/storage/awss3/awss3.go
@@ -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)) {
diff --git a/internal/kms/storage/awss3storage_test.go b/internal/kms/storage/awss3/awss3_test.go
similarity index 94%
rename from internal/kms/storage/awss3storage_test.go
rename to internal/kms/storage/awss3/awss3_test.go
index 0397fd1f7..5e3d3bfbb 100644
--- a/internal/kms/storage/awss3storage_test.go
+++ b/internal/kms/storage/awss3/awss3_test.go
@@ -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,
 			}
 
diff --git a/internal/kms/storage/azureblob/azureblob.go b/internal/kms/storage/azureblob/azureblob.go
new file mode 100644
index 000000000..e7e41424e
--- /dev/null
+++ b/internal/kms/storage/azureblob/azureblob.go
@@ -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)
+}
diff --git a/internal/kms/storage/azurestorage_test.go b/internal/kms/storage/azureblob/azureblob_test.go
similarity index 86%
rename from internal/kms/storage/azurestorage_test.go
rename to internal/kms/storage/azureblob/azureblob_test.go
index 5660001a0..93a5f2987 100644
--- a/internal/kms/storage/azurestorage_test.go
+++ b/internal/kms/storage/azureblob/azureblob_test.go
@@ -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())
diff --git a/internal/kms/storage/azurestorage.go b/internal/kms/storage/azurestorage.go
deleted file mode 100644
index 4811b0027..000000000
--- a/internal/kms/storage/azurestorage.go
+++ /dev/null
@@ -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)
-}
diff --git a/internal/kms/storage/gcloudstorage.go b/internal/kms/storage/gcloudstorage.go
deleted file mode 100644
index 557921ad0..000000000
--- a/internal/kms/storage/gcloudstorage.go
+++ /dev/null
@@ -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
-}
diff --git a/internal/kms/storage/gcloudstorage_integration_test.go b/internal/kms/storage/gcloudstorage_integration_test.go
deleted file mode 100644
index 99ce31fad..000000000
--- a/internal/kms/storage/gcloudstorage_integration_test.go
+++ /dev/null
@@ -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
-}
diff --git a/internal/kms/storage/gcs/gcs.go b/internal/kms/storage/gcs/gcs.go
new file mode 100644
index 000000000..ca53bf55f
--- /dev/null
+++ b/internal/kms/storage/gcs/gcs.go
@@ -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
+	}
+}
diff --git a/internal/kms/storage/gcloudstorage_test.go b/internal/kms/storage/gcs/gcs_test.go
similarity index 85%
rename from internal/kms/storage/gcloudstorage_test.go
rename to internal/kms/storage/gcs/gcs_test.go
index e226a35b9..552cee99f 100644
--- a/internal/kms/storage/gcloudstorage_test.go
+++ b/internal/kms/storage/gcs/gcs_test.go
@@ -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 {
diff --git a/internal/kms/storage/memfs/memfs.go b/internal/kms/storage/memfs/memfs.go
new file mode 100644
index 000000000..7484b679e
--- /dev/null
+++ b/internal/kms/storage/memfs/memfs.go
@@ -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
+}
diff --git a/internal/kms/storage/memfsstroage_test.go b/internal/kms/storage/memfs/memfs_test.go
similarity index 62%
rename from internal/kms/storage/memfsstroage_test.go
rename to internal/kms/storage/memfs/memfs_test.go
index e01571856..9fe33362b 100644
--- a/internal/kms/storage/memfsstroage_test.go
+++ b/internal/kms/storage/memfs/memfs_test.go
@@ -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)
 }
diff --git a/internal/kms/storage/memfsstorage.go b/internal/kms/storage/memfsstorage.go
deleted file mode 100644
index eb8e2b992..000000000
--- a/internal/kms/storage/memfsstorage.go
+++ /dev/null
@@ -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
-}
diff --git a/internal/kms/test/aws_test.go b/internal/kms/test/aws_test.go
index 1a7ca929e..184d045a0 100644
--- a/internal/kms/test/aws_test.go
+++ b/internal/kms/test/aws_test.go
@@ -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()
 
diff --git a/internal/kms/test/azure_test.go b/internal/kms/test/azure_test.go
index 6a9c0e945..855b4dd54 100644
--- a/internal/kms/test/azure_test.go
+++ b/internal/kms/test/azure_test.go
@@ -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()
 
diff --git a/internal/kms/test/gcp_test.go b/internal/kms/test/gcp_test.go
index 982a97e17..35162e0f1 100644
--- a/internal/kms/test/gcp_test.go
+++ b/internal/kms/test/gcp_test.go
@@ -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)
 }
diff --git a/internal/kms/test/integration_test.go b/internal/kms/test/integration_test.go
index 529cb8372..bd6dccd80 100644
--- a/internal/kms/test/integration_test.go
+++ b/internal/kms/test/integration_test.go
@@ -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")
diff --git a/internal/kms/uri/uri.go b/internal/kms/uri/uri.go
index ace889fb1..6a3de8887 100644
--- a/internal/kms/uri/uri.go
+++ b/internal/kms/uri/uri.go
@@ -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)
diff --git a/internal/kms/uri/uri_test.go b/internal/kms/uri/uri_test.go
new file mode 100644
index 000000000..3517d2a8c
--- /dev/null
+++ b/internal/kms/uri/uri_test.go
@@ -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")
+}
diff --git a/keyservice/cmd/main.go b/keyservice/cmd/main.go
index da6354866..24c378cb0 100644
--- a/keyservice/cmd/main.go
+++ b/keyservice/cmd/main.go
@@ -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")
 	}