2022-09-05 03:06:08 -04:00
|
|
|
/*
|
|
|
|
Copyright (c) Edgeless Systems GmbH
|
|
|
|
|
|
|
|
SPDX-License-Identifier: AGPL-3.0-only
|
|
|
|
*/
|
|
|
|
|
2023-01-19 09:57:50 -05:00
|
|
|
/*
|
|
|
|
Package setup provides functions to create a KMS and key store from a given URI.
|
|
|
|
|
|
|
|
This package does not provide any functionality to interact with the KMS or key store,
|
|
|
|
but only to create them.
|
|
|
|
|
|
|
|
Adding support for a new KMS or storage backend requires adding a new URI for that backend,
|
|
|
|
and implementing the corresponding get*Config function.
|
|
|
|
*/
|
2022-05-10 06:35:17 -04:00
|
|
|
package setup
|
2022-03-22 11:03:15 -04:00
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
2022-07-29 03:52:47 -04:00
|
|
|
"encoding/base64"
|
2022-03-22 11:03:15 -04:00
|
|
|
"fmt"
|
|
|
|
"net/url"
|
|
|
|
"strconv"
|
|
|
|
|
2022-11-09 05:48:56 -05:00
|
|
|
"cloud.google.com/go/kms/apiv1/kmspb"
|
2023-01-12 10:22:47 -05:00
|
|
|
"github.com/edgelesssys/constellation/v2/internal/kms/kms"
|
|
|
|
"github.com/edgelesssys/constellation/v2/internal/kms/kms/aws"
|
|
|
|
"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"
|
2022-03-22 11:03:15 -04:00
|
|
|
)
|
|
|
|
|
2022-11-09 09:57:54 -05:00
|
|
|
// Well known endpoints for KMS services.
|
2022-03-22 11:03:15 -04:00
|
|
|
const (
|
2023-01-16 05:19:03 -05:00
|
|
|
AWSKMSURI = "kms://aws?keyPolicy=%s&kekID=%s"
|
|
|
|
AzureKMSURI = "kms://azure-kms?name=%s&type=%s&kekID=%s"
|
|
|
|
AzureHSMURI = "kms://azure-hsm?name=%s&kekID=%s"
|
|
|
|
GCPKMSURI = "kms://gcp?project=%s&location=%s&keyRing=%s&protectionLvl=%s&kekID=%s"
|
2023-01-12 10:22:47 -05:00
|
|
|
ClusterKMSURI = "kms://cluster-kms?key=%s&salt=%s"
|
2022-03-22 11:03:15 -04:00
|
|
|
AWSS3URI = "storage://aws?bucket=%s"
|
|
|
|
AzureBlobURI = "storage://azure?container=%s&connectionString=%s"
|
|
|
|
GCPStorageURI = "storage://gcp?projects=%s&bucket=%s"
|
|
|
|
NoStoreURI = "storage://no-store"
|
|
|
|
)
|
|
|
|
|
2023-01-16 05:19:03 -05:00
|
|
|
// 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),
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2022-11-09 09:57:54 -05:00
|
|
|
// KMSInformation about an existing KMS.
|
2022-04-20 09:22:39 -04:00
|
|
|
type KMSInformation struct {
|
2022-07-08 04:59:59 -04:00
|
|
|
KMSURI string
|
|
|
|
StorageURI string
|
2022-04-20 09:22:39 -04:00
|
|
|
KeyEncryptionKeyID string
|
|
|
|
}
|
|
|
|
|
2022-10-05 09:02:46 -04:00
|
|
|
// KMS creates a KMS and key store from the given parameters.
|
|
|
|
func KMS(ctx context.Context, storageURI, kmsURI string) (kms.CloudKMS, error) {
|
2022-03-22 11:03:15 -04:00
|
|
|
store, err := getStore(ctx, storageURI)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return getKMS(ctx, kmsURI, store)
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
if uri.Scheme != "storage" {
|
|
|
|
return nil, fmt.Errorf("invalid storage URI: invalid scheme: %s", uri.Scheme)
|
|
|
|
}
|
|
|
|
|
|
|
|
switch uri.Host {
|
|
|
|
case "aws":
|
|
|
|
bucket, err := getAWSS3Config(uri)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return storage.NewAWSS3Storage(ctx, bucket, nil)
|
|
|
|
|
|
|
|
case "azure":
|
|
|
|
container, connString, err := getAzureBlobConfig(uri)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return storage.NewAzureStorage(ctx, connString, container, nil)
|
|
|
|
|
|
|
|
case "gcp":
|
|
|
|
project, bucket, err := getGCPStorageConfig(uri)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return storage.NewGoogleCloudStorage(ctx, project, bucket, nil)
|
|
|
|
|
|
|
|
case "no-store":
|
|
|
|
return nil, nil
|
|
|
|
|
|
|
|
default:
|
|
|
|
return nil, fmt.Errorf("unknown storage type: %s", uri.Host)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// getKMS creates a KMS client with the given key store and depending on the given parameters.
|
|
|
|
func getKMS(ctx context.Context, kmsURI string, store kms.Storage) (kms.CloudKMS, error) {
|
|
|
|
uri, err := url.Parse(kmsURI)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
if uri.Scheme != "kms" {
|
|
|
|
return nil, fmt.Errorf("invalid KMS URI: invalid scheme: %s", uri.Scheme)
|
|
|
|
}
|
|
|
|
|
|
|
|
switch uri.Host {
|
|
|
|
case "aws":
|
2023-01-16 05:19:03 -05:00
|
|
|
poliyProducer, kekID, err := getAWSKMSConfig(uri)
|
2022-03-22 11:03:15 -04:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2023-01-16 05:19:03 -05:00
|
|
|
return aws.New(ctx, poliyProducer, store, kekID)
|
2022-03-22 11:03:15 -04:00
|
|
|
|
|
|
|
case "azure-kms":
|
2023-01-16 05:19:03 -05:00
|
|
|
vaultName, vaultType, kekID, err := getAzureKMSConfig(uri)
|
2022-03-22 11:03:15 -04:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2023-01-16 05:19:03 -05:00
|
|
|
return azure.New(ctx, vaultName, azure.VaultSuffix(vaultType), store, kekID, nil)
|
2022-03-22 11:03:15 -04:00
|
|
|
|
|
|
|
case "azure-hsm":
|
2023-01-16 05:19:03 -05:00
|
|
|
vaultName, kekID, err := getAzureHSMConfig(uri)
|
2022-03-22 11:03:15 -04:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2023-01-16 05:19:03 -05:00
|
|
|
return azure.NewHSM(ctx, vaultName, store, kekID, nil)
|
2022-03-22 11:03:15 -04:00
|
|
|
|
|
|
|
case "gcp":
|
2023-01-16 05:19:03 -05:00
|
|
|
project, location, keyRing, protectionLvl, kekID, err := getGCPKMSConfig(uri)
|
2022-03-22 11:03:15 -04:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2023-01-16 05:19:03 -05:00
|
|
|
return gcp.New(ctx, project, location, keyRing, store, kmspb.ProtectionLevel(protectionLvl), kekID)
|
2022-03-22 11:03:15 -04:00
|
|
|
|
|
|
|
case "cluster-kms":
|
2023-01-16 05:19:03 -05:00
|
|
|
masterSecret, err := getClusterKMSConfig(uri)
|
2022-07-29 03:52:47 -04:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2023-01-16 05:19:03 -05:00
|
|
|
return cluster.New(masterSecret.Key, masterSecret.Salt)
|
2022-03-22 11:03:15 -04:00
|
|
|
|
|
|
|
default:
|
|
|
|
return nil, fmt.Errorf("unknown KMS type: %s", uri.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
|
|
|
|
}
|
|
|
|
|
2023-01-16 05:19:03 -05:00
|
|
|
func getAWSKMSConfig(uri *url.URL) (*defaultPolicyProducer, string, error) {
|
|
|
|
r, err := getConfig(uri.Query(), []string{"keyPolicy", "kekID"})
|
2022-03-22 11:03:15 -04:00
|
|
|
if err != nil {
|
2023-01-16 05:19:03 -05:00
|
|
|
return nil, "", err
|
2022-03-22 11:03:15 -04:00
|
|
|
}
|
2023-01-16 05:19:03 -05:00
|
|
|
|
|
|
|
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
|
2022-03-22 11:03:15 -04:00
|
|
|
}
|
|
|
|
|
2023-01-16 05:19:03 -05:00
|
|
|
func getAzureKMSConfig(uri *url.URL) (string, string, string, error) {
|
|
|
|
r, err := getConfig(uri.Query(), []string{"name", "type", "kekID"})
|
|
|
|
if err != nil {
|
|
|
|
return "", "", "", fmt.Errorf("getting config: %w", err)
|
|
|
|
}
|
|
|
|
if len(r) != 3 {
|
|
|
|
return "", "", "", fmt.Errorf("expected 3 KmsURI args, got %d", len(r))
|
|
|
|
}
|
|
|
|
|
|
|
|
kekID, err := base64.URLEncoding.DecodeString(r[2])
|
|
|
|
if err != nil {
|
|
|
|
return "", "", "", fmt.Errorf("parsing kekID from kmsUri: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return r[0], r[1], string(kekID), err
|
2022-03-22 11:03:15 -04:00
|
|
|
}
|
|
|
|
|
2023-01-16 05:19:03 -05:00
|
|
|
func getAzureHSMConfig(uri *url.URL) (string, string, error) {
|
|
|
|
r, err := getConfig(uri.Query(), []string{"name", "kekID"})
|
|
|
|
if err != nil {
|
|
|
|
return "", "", fmt.Errorf("getting config: %w", err)
|
|
|
|
}
|
|
|
|
if len(r) != 2 {
|
|
|
|
return "", "", fmt.Errorf("expected 2 KmsURI args, got %d", len(r))
|
|
|
|
}
|
|
|
|
|
|
|
|
kekID, err := base64.URLEncoding.DecodeString(r[1])
|
|
|
|
if err != nil {
|
|
|
|
return "", "", fmt.Errorf("parsing kekID from kmsUri: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return r[0], string(kekID), err
|
2022-03-22 11:03:15 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2023-01-16 05:19:03 -05:00
|
|
|
func getGCPKMSConfig(uri *url.URL) (project string, location string, keyRing string, protectionLvl int32, kekID string, err error) {
|
|
|
|
r, err := getConfig(uri.Query(), []string{"project", "location", "keyRing", "protectionLvl", "kekID"})
|
2022-03-22 11:03:15 -04:00
|
|
|
if err != nil {
|
2023-01-16 05:19:03 -05:00
|
|
|
return "", "", "", 0, "", err
|
2022-03-22 11:03:15 -04:00
|
|
|
}
|
2023-01-16 05:19:03 -05:00
|
|
|
|
|
|
|
if len(r) != 5 {
|
|
|
|
return "", "", "", 0, "", fmt.Errorf("expected 5 KmsURI args, got %d", len(r))
|
|
|
|
}
|
|
|
|
|
|
|
|
kekIDByte, err := base64.URLEncoding.DecodeString(r[4])
|
2022-03-22 11:03:15 -04:00
|
|
|
if err != nil {
|
2023-01-16 05:19:03 -05:00
|
|
|
return "", "", "", 0, "", fmt.Errorf("parsing kekID from kmsUri: %w", err)
|
2022-03-22 11:03:15 -04:00
|
|
|
}
|
2023-01-16 05:19:03 -05:00
|
|
|
|
|
|
|
protectionLvl32, err := strconv.ParseInt(r[3], 10, 32)
|
|
|
|
if err != nil {
|
|
|
|
return "", "", "", 0, "", err
|
|
|
|
}
|
|
|
|
return r[0], r[1], r[2], int32(protectionLvl32), string(kekIDByte), nil
|
2022-03-22 11:03:15 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
func getGCPStorageConfig(uri *url.URL) (string, string, error) {
|
|
|
|
r, err := getConfig(uri.Query(), []string{"project", "bucket"})
|
|
|
|
return r[0], r[1], err
|
|
|
|
}
|
|
|
|
|
2023-01-16 05:19:03 -05:00
|
|
|
func getClusterKMSConfig(uri *url.URL) (MasterSecret, error) {
|
|
|
|
r, err := getConfig(uri.Query(), []string{"key", "salt"})
|
2022-07-29 03:52:47 -04:00
|
|
|
if err != nil {
|
2023-01-16 05:19:03 -05:00
|
|
|
return MasterSecret{}, err
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(r) != 2 {
|
|
|
|
return MasterSecret{}, fmt.Errorf("expected 2 KmsURI args, got %d", len(r))
|
|
|
|
}
|
|
|
|
|
|
|
|
key, err := base64.URLEncoding.DecodeString(r[0])
|
|
|
|
if err != nil {
|
|
|
|
return MasterSecret{}, fmt.Errorf("parsing key from kmsUri: %w", err)
|
2022-07-29 03:52:47 -04:00
|
|
|
}
|
2023-01-16 05:19:03 -05:00
|
|
|
salt, err := base64.URLEncoding.DecodeString(r[1])
|
|
|
|
if err != nil {
|
|
|
|
return MasterSecret{}, fmt.Errorf("parsing salt from kmsUri: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return MasterSecret{Key: key, Salt: salt}, nil
|
2022-07-29 03:52:47 -04:00
|
|
|
}
|
|
|
|
|
2022-03-22 11:03:15 -04:00
|
|
|
// 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 {
|
2022-06-09 10:04:30 -04:00
|
|
|
return res, fmt.Errorf("failed to unescape value for key: %q", key)
|
2022-03-22 11:03:15 -04:00
|
|
|
}
|
|
|
|
res[idx] = val
|
|
|
|
}
|
|
|
|
|
|
|
|
return res, nil
|
|
|
|
}
|