From 5f0bbdee9ea9c8b2695161675f835aaf0b2e1e2b Mon Sep 17 00:00:00 2001 From: Pedro Juarez Date: Tue, 23 May 2023 15:15:12 -0700 Subject: [PATCH] Add KES v2 config and toolbox helps (#1602) * add KES config v2 and tooltips * self document how we identiy the `arch` from KES image tag * Verify expected KES cofig Model on tests * add assertions to verify the expected error result --- api/encryption-handlers.go | 797 ++++++++++--- api/encryption-handlers_test.go | 277 ++++- api/tenants_helper_test.go | 96 +- go.mod | 7 +- go.sum | 7 +- kubectl-minio/go.mod | 2 +- kubectl-minio/go.sum | 4 +- pkg/kes/kes.go | 55 +- .../AddTenant/Steps/Encryption/AWSKMSAdd.tsx | 6 + .../Steps/Encryption/AzureKMSAdd.tsx | 4 + .../AddTenant/Steps/Encryption/GCPKMSAdd.tsx | 6 + .../Steps/Encryption/GemaltoKMSAdd.tsx | 3 + .../Steps/Encryption/VaultKMSAdd.tsx | 7 + .../TenantDetails/TenantEncryption.tsx | 1009 ++++++++++------- 14 files changed, 1636 insertions(+), 644 deletions(-) diff --git a/api/encryption-handlers.go b/api/encryption-handlers.go index c01a01f3a53..e3dc63507a0 100644 --- a/api/encryption-handlers.go +++ b/api/encryption-handlers.go @@ -24,7 +24,9 @@ import ( "encoding/json" "errors" "fmt" + "regexp" "strconv" + "strings" "time" "github.com/go-openapi/runtime/middleware" @@ -33,11 +35,40 @@ import ( "github.com/minio/operator/models" miniov2 "github.com/minio/operator/pkg/apis/minio.min.io/v2" "github.com/minio/operator/pkg/kes" + "golang.org/x/mod/semver" "gopkg.in/yaml.v2" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) +const ( + // miniov2 will mount the mTLSCertificates in the following paths + // therefore we set these values in the KES yaml kesConfiguration + mTLSClientCrtPath = "/tmp/kes/client.crt" + mTLSClientKeyPath = "/tmp/kes/client.key" + mTLSClientCaPath = "/tmp/kes/ca.crt" + // if encryption is enabled and encryption is configured to use Vault + defaultPing = 10 // default ping + // imageTagWithArchRegex is a regular expression to identify if a KES tag + // includes the arch as suffix, ie: 2023-05-02T22-48-10Z-arm64 + kesImageTagWithArchRegexPattern = `(\d{4}-\d{2}-\d{2}T\d{2}-\d{2}-\d{2}Z)(-.*)` +) + +const ( + // KesConfigVersion1 identifier v1 + KesConfigVersion1 = "v1" + // KesConfigVersion2 identifier v2 + KesConfigVersion2 = "v2" +) + +// KesConfigVersionsMap is a map of kes config version types +var KesConfigVersionsMap = map[string]interface{}{ + KesConfigVersion1: kes.ServerConfigV1{}, + KesConfigVersion2: kes.ServerConfigV2{}, +} + +type configVersion func(clientCrtIdentity string, encryptionCfg *models.EncryptionConfiguration, mTLSCertificates map[string][]byte) ([]byte, error) + func registerEncryptionHandlers(api *operations.OperatorAPI) { // Get Tenant Encryption Configuration api.OperatorAPITenantEncryptionInfoHandler = operator_api.TenantEncryptionInfoHandlerFunc(func(params operator_api.TenantEncryptionInfoParams, session *models.Principal) middleware.Responder { @@ -212,17 +243,17 @@ func tenantUpdateEncryption(ctx context.Context, operatorClient OperatorClientI, // update KES identities in kes-configuration.yaml secret kesConfigurationSecretName := fmt.Sprintf("%s-kes-configuration", secretName) kesClientCertSecretName := fmt.Sprintf("%s-kes-client-cert", secretName) - kesConfigurationSecret, kesClientCertSecret, err := createOrReplaceKesConfigurationSecrets(ctx, clientSet, namespace, body, kesConfigurationSecretName, kesClientCertSecretName, tenantName) - if err != nil { - return err - } - tenant.Spec.KES.Configuration = kesConfigurationSecret - tenant.Spec.KES.ClientCertSecret = kesClientCertSecret image := params.Body.Image if image == "" { image = miniov2.DefaultKESImage } tenant.Spec.KES.Image = image + kesConfigurationSecret, kesClientCertSecret, err := createOrReplaceKesConfigurationSecrets(ctx, clientSet, namespace, body, kesConfigurationSecretName, kesClientCertSecretName, tenantName, tenant.Spec.KES.Image) + if err != nil { + return err + } + tenant.Spec.KES.Configuration = kesConfigurationSecret + tenant.Spec.KES.ClientCertSecret = kesClientCertSecret i, err := strconv.ParseInt(params.Body.Replicas, 10, 32) if err != nil { return err @@ -280,133 +311,286 @@ func tenantEncryptionInfo(ctx context.Context, operatorClient OperatorClientI, c return nil, err } if rawConfiguration, ok := configSecret.Data["server-config.yaml"]; ok { - kesConfiguration := &kes.ServerConfig{} - // return raw configuration in case the user wants to edit KES configuration manually - encryptConfig.Raw = string(rawConfiguration) - err := yaml.Unmarshal(rawConfiguration, kesConfiguration) + kesConfigVersion, err := getKesConfigVersion(encryptConfig.Image) if err != nil { return nil, err } - if kesConfiguration.Keys.Vault != nil { - vault := kesConfiguration.Keys.Vault - vaultConfig := &models.VaultConfigurationResponse{ - Prefix: vault.Prefix, - Namespace: vault.Namespace, - Engine: vault.EnginePath, - Endpoint: &vault.Endpoint, - } - if vault.Status != nil { - vaultConfig.Status = &models.VaultConfigurationResponseStatus{ - Ping: int64(vault.Status.Ping.Seconds()), - } - } - if vault.AppRole != nil { - vaultConfig.Approle = &models.VaultConfigurationResponseApprole{ - Engine: vault.AppRole.EnginePath, - ID: &vault.AppRole.ID, - Retry: int64(vault.AppRole.Retry.Seconds()), - Secret: &vault.AppRole.Secret, - } - } - if tenant.KESClientCert() { - encryptConfig.KmsMtls = &models.EncryptionConfigurationResponseAO1KmsMtls{} - clientSecretName := tenant.Spec.KES.ClientCertSecret.Name - keyPair, err := clientSet.getSecret(ctx, namespace, clientSecretName, metav1.GetOptions{}) - if err != nil { - return nil, err - } - // Extract client public certificate - if rawCert, ok := keyPair.Data["client.crt"]; ok { - encryptConfig.KmsMtls.Crt, err = parseCertificate(clientSecretName, rawCert) - if err != nil { - return nil, err - } - } - // Extract client ca certificate - if rawCert, ok := keyPair.Data["ca.crt"]; ok { - encryptConfig.KmsMtls.Ca, err = parseCertificate(clientSecretName, rawCert) - if err != nil { - return nil, err - } - } - } - encryptConfig.Vault = vaultConfig - } - if kesConfiguration.Keys.Aws != nil { - awsJSON, err := json.Marshal(kesConfiguration.Keys.Aws) - if err != nil { - return nil, err - } - awsConfig := &models.AwsConfiguration{} - err = json.Unmarshal(awsJSON, awsConfig) + // return raw configuration in case the user wants to edit KES configuration manually + switch kesConfigVersion { + case KesConfigVersion1: + err := getConfigurationResponseFromV1(ctx, clientSet, rawConfiguration, tenant, encryptConfig) if err != nil { return nil, err } - encryptConfig.Aws = awsConfig - } - if kesConfiguration.Keys.Gcp != nil { - gcpJSON, err := json.Marshal(kesConfiguration.Keys.Gcp) + case KesConfigVersion2: + err := getConfigurationResponseFromV2(ctx, clientSet, rawConfiguration, tenant, encryptConfig) if err != nil { return nil, err } - gcpConfig := &models.GcpConfiguration{} - err = json.Unmarshal(gcpJSON, gcpConfig) + default: + err := getConfigurationResponseFromV2(ctx, clientSet, rawConfiguration, tenant, encryptConfig) if err != nil { return nil, err } - encryptConfig.Gcp = gcpConfig } - if kesConfiguration.Keys.Gemalto != nil { - gemalto := kesConfiguration.Keys.Gemalto - gemaltoConfig := &models.GemaltoConfigurationResponse{ - Keysecure: &models.GemaltoConfigurationResponseKeysecure{}, + } + } + return encryptConfig, nil + } + return nil, ErrEncryptionConfigNotFound +} + +// getConfigurationResponseFromV2 hidrates EncryptionConfigurationResponse struct from ServerConfigV1 +func getConfigurationResponseFromV1(ctx context.Context, clientSet K8sClientI, rawConfiguration []byte, tenant *miniov2.Tenant, encryptConfig *models.EncryptionConfigurationResponse) error { + kesConfiguration := &kes.ServerConfigV1{} + encryptConfig.Raw = string(rawConfiguration) + err := yaml.Unmarshal(rawConfiguration, kesConfiguration) + if err != nil { + return err + } + if kesConfiguration.Keys.Vault != nil { + vault := kesConfiguration.Keys.Vault + vaultConfig := &models.VaultConfigurationResponse{ + Prefix: vault.Prefix, + Namespace: vault.Namespace, + Engine: vault.EnginePath, + Endpoint: &vault.Endpoint, + } + if vault.Status != nil { + vaultConfig.Status = &models.VaultConfigurationResponseStatus{ + Ping: int64(vault.Status.Ping.Seconds()), + } + } + if vault.AppRole != nil { + vaultConfig.Approle = &models.VaultConfigurationResponseApprole{ + Engine: vault.AppRole.EnginePath, + ID: &vault.AppRole.ID, + Retry: int64(vault.AppRole.Retry.Seconds()), + Secret: &vault.AppRole.Secret, + } + } + if tenant.KESClientCert() { + encryptConfig.KmsMtls = &models.EncryptionConfigurationResponseAO1KmsMtls{} + clientSecretName := tenant.Spec.KES.ClientCertSecret.Name + keyPair, err := clientSet.getSecret(ctx, tenant.Namespace, clientSecretName, metav1.GetOptions{}) + if err != nil { + return err + } + // Extract client public certificate + if rawCert, ok := keyPair.Data["client.crt"]; ok { + encryptConfig.KmsMtls.Crt, err = parseCertificate(clientSecretName, rawCert) + if err != nil { + return err + } + } + // Extract client ca certificate + if rawCert, ok := keyPair.Data["ca.crt"]; ok { + encryptConfig.KmsMtls.Ca, err = parseCertificate(clientSecretName, rawCert) + if err != nil { + return err + } + } + } + encryptConfig.Vault = vaultConfig + } + if kesConfiguration.Keys.Aws != nil { + awsJSON, err := json.Marshal(kesConfiguration.Keys.Aws) + if err != nil { + return err + } + awsConfig := &models.AwsConfiguration{} + err = json.Unmarshal(awsJSON, awsConfig) + if err != nil { + return err + } + encryptConfig.Aws = awsConfig + } + if kesConfiguration.Keys.Gcp != nil { + gcpJSON, err := json.Marshal(kesConfiguration.Keys.Gcp) + if err != nil { + return err + } + gcpConfig := &models.GcpConfiguration{} + err = json.Unmarshal(gcpJSON, gcpConfig) + if err != nil { + return err + } + encryptConfig.Gcp = gcpConfig + } + if kesConfiguration.Keys.Gemalto != nil { + gemalto := kesConfiguration.Keys.Gemalto + gemaltoConfig := &models.GemaltoConfigurationResponse{ + Keysecure: &models.GemaltoConfigurationResponseKeysecure{}, + } + if gemalto.KeySecure != nil { + gemaltoConfig.Keysecure.Endpoint = &gemalto.KeySecure.Endpoint + if gemalto.KeySecure.Credentials != nil { + gemaltoConfig.Keysecure.Credentials = &models.GemaltoConfigurationResponseKeysecureCredentials{ + Domain: &gemalto.KeySecure.Credentials.Domain, + Retry: int64(gemalto.KeySecure.Credentials.Retry.Seconds()), + Token: &gemalto.KeySecure.Credentials.Token, + } + } + if gemalto.KeySecure.TLS != nil { + if tenant.KESClientCert() { + encryptConfig.KmsMtls = &models.EncryptionConfigurationResponseAO1KmsMtls{} + clientSecretName := tenant.Spec.KES.ClientCertSecret.Name + keyPair, err := clientSet.getSecret(ctx, tenant.Namespace, clientSecretName, metav1.GetOptions{}) + if err != nil { + return err } - if gemalto.KeySecure != nil { - gemaltoConfig.Keysecure.Endpoint = &gemalto.KeySecure.Endpoint - if gemalto.KeySecure.Credentials != nil { - gemaltoConfig.Keysecure.Credentials = &models.GemaltoConfigurationResponseKeysecureCredentials{ - Domain: &gemalto.KeySecure.Credentials.Domain, - Retry: int64(gemalto.KeySecure.Credentials.Retry.Seconds()), - Token: &gemalto.KeySecure.Credentials.Token, - } - } - if gemalto.KeySecure.TLS != nil { - if tenant.KESClientCert() { - encryptConfig.KmsMtls = &models.EncryptionConfigurationResponseAO1KmsMtls{} - clientSecretName := tenant.Spec.KES.ClientCertSecret.Name - keyPair, err := clientSet.getSecret(ctx, namespace, clientSecretName, metav1.GetOptions{}) - if err != nil { - return nil, err - } - // Extract client ca certificate - if rawCert, ok := keyPair.Data["ca.crt"]; ok { - encryptConfig.KmsMtls.Ca, err = parseCertificate(clientSecretName, rawCert) - if err != nil { - return nil, err - } - } - } + // Extract client ca certificate + if rawCert, ok := keyPair.Data["ca.crt"]; ok { + encryptConfig.KmsMtls.Ca, err = parseCertificate(clientSecretName, rawCert) + if err != nil { + return err } } - encryptConfig.Gemalto = gemaltoConfig } - if kesConfiguration.Keys.Azure != nil { - azureJSON, err := json.Marshal(kesConfiguration.Keys.Azure) + } + } + encryptConfig.Gemalto = gemaltoConfig + } + if kesConfiguration.Keys.Azure != nil { + azureJSON, err := json.Marshal(kesConfiguration.Keys.Azure) + if err != nil { + return err + } + azureConfig := &models.AzureConfiguration{} + err = json.Unmarshal(azureJSON, azureConfig) + if err != nil { + return err + } + encryptConfig.Azure = azureConfig + } + + return nil +} + +// getConfigurationResponseFromV2 hidrates EncryptionConfigurationResponse struct from ServerConfigV2 +func getConfigurationResponseFromV2(ctx context.Context, clientSet K8sClientI, rawConfiguration []byte, tenant *miniov2.Tenant, encryptConfig *models.EncryptionConfigurationResponse) error { + kesConfiguration := &kes.ServerConfigV2{} + encryptConfig.Raw = string(rawConfiguration) + err := yaml.Unmarshal(rawConfiguration, kesConfiguration) + if err != nil { + return err + } + if kesConfiguration.Keystore.Vault != nil { + vault := kesConfiguration.Keystore.Vault + vaultConfig := &models.VaultConfigurationResponse{ + Prefix: vault.Prefix, + Namespace: vault.Namespace, + Engine: vault.EnginePath, + Endpoint: &vault.Endpoint, + } + if vault.Status != nil { + vaultConfig.Status = &models.VaultConfigurationResponseStatus{ + Ping: int64(vault.Status.Ping.Seconds()), + } + } + if vault.AppRole != nil { + vaultConfig.Approle = &models.VaultConfigurationResponseApprole{ + Engine: vault.AppRole.EnginePath, + ID: &vault.AppRole.ID, + Retry: int64(vault.AppRole.Retry.Seconds()), + Secret: &vault.AppRole.Secret, + } + } + if tenant.KESClientCert() { + encryptConfig.KmsMtls = &models.EncryptionConfigurationResponseAO1KmsMtls{} + clientSecretName := tenant.Spec.KES.ClientCertSecret.Name + keyPair, err := clientSet.getSecret(ctx, tenant.Namespace, clientSecretName, metav1.GetOptions{}) + if err != nil { + return err + } + // Extract client public certificate + if rawCert, ok := keyPair.Data["client.crt"]; ok { + encryptConfig.KmsMtls.Crt, err = parseCertificate(clientSecretName, rawCert) + if err != nil { + return err + } + } + // Extract client ca certificate + if rawCert, ok := keyPair.Data["ca.crt"]; ok { + encryptConfig.KmsMtls.Ca, err = parseCertificate(clientSecretName, rawCert) + if err != nil { + return err + } + } + } + encryptConfig.Vault = vaultConfig + } + if kesConfiguration.Keystore.Aws != nil { + awsJSON, err := json.Marshal(kesConfiguration.Keystore.Aws) + if err != nil { + return err + } + awsConfig := &models.AwsConfiguration{} + err = json.Unmarshal(awsJSON, awsConfig) + if err != nil { + return err + } + encryptConfig.Aws = awsConfig + } + if kesConfiguration.Keystore.Gcp != nil { + gcpJSON, err := json.Marshal(kesConfiguration.Keystore.Gcp) + if err != nil { + return err + } + gcpConfig := &models.GcpConfiguration{} + err = json.Unmarshal(gcpJSON, gcpConfig) + if err != nil { + return err + } + encryptConfig.Gcp = gcpConfig + } + if kesConfiguration.Keystore.Gemalto != nil { + gemalto := kesConfiguration.Keystore.Gemalto + gemaltoConfig := &models.GemaltoConfigurationResponse{ + Keysecure: &models.GemaltoConfigurationResponseKeysecure{}, + } + if gemalto.KeySecure != nil { + gemaltoConfig.Keysecure.Endpoint = &gemalto.KeySecure.Endpoint + if gemalto.KeySecure.Credentials != nil { + gemaltoConfig.Keysecure.Credentials = &models.GemaltoConfigurationResponseKeysecureCredentials{ + Domain: &gemalto.KeySecure.Credentials.Domain, + Retry: int64(gemalto.KeySecure.Credentials.Retry.Seconds()), + Token: &gemalto.KeySecure.Credentials.Token, + } + } + if gemalto.KeySecure.TLS != nil { + if tenant.KESClientCert() { + encryptConfig.KmsMtls = &models.EncryptionConfigurationResponseAO1KmsMtls{} + clientSecretName := tenant.Spec.KES.ClientCertSecret.Name + keyPair, err := clientSet.getSecret(ctx, tenant.Namespace, clientSecretName, metav1.GetOptions{}) if err != nil { - return nil, err + return err } - azureConfig := &models.AzureConfiguration{} - err = json.Unmarshal(azureJSON, azureConfig) - if err != nil { - return nil, err + // Extract client ca certificate + if rawCert, ok := keyPair.Data["ca.crt"]; ok { + encryptConfig.KmsMtls.Ca, err = parseCertificate(clientSecretName, rawCert) + if err != nil { + return err + } } - encryptConfig.Azure = azureConfig } } } - return encryptConfig, nil + encryptConfig.Gemalto = gemaltoConfig } - return nil, ErrEncryptionConfigNotFound + if kesConfiguration.Keystore.Azure != nil { + azureJSON, err := json.Marshal(kesConfiguration.Keystore.Azure) + if err != nil { + return err + } + azureConfig := &models.AzureConfiguration{} + err = json.Unmarshal(azureJSON, azureConfig) + if err != nil { + return err + } + encryptConfig.Azure = azureConfig + } + return nil } // getKESConfiguration will generate the KES server certificate secrets, the tenant client secrets for mTLS authentication between MinIO and KES and the @@ -449,7 +633,7 @@ func getKESConfiguration(ctx context.Context, clientSet K8sClientI, ns string, e } } // Prepare kesConfiguration for KES - serverConfigSecret, clientCertSecret, err := createOrReplaceKesConfigurationSecrets(ctx, clientSet, ns, encryptionCfg, kesConfigurationSecretName, kesClientCertSecretName, tenantName) + serverConfigSecret, clientCertSecret, err := createOrReplaceKesConfigurationSecrets(ctx, clientSet, ns, encryptionCfg, kesConfigurationSecretName, kesClientCertSecretName, tenantName, kesConfiguration.Image) if err != nil { return nil, err } @@ -460,7 +644,7 @@ func getKESConfiguration(ctx context.Context, clientSet K8sClientI, ns string, e return kesConfiguration, nil } -func createOrReplaceKesConfigurationSecrets(ctx context.Context, clientSet K8sClientI, ns string, encryptionCfg *models.EncryptionConfiguration, kesConfigurationSecretName, kesClientCertSecretName, tenantName string) (*corev1.LocalObjectReference, *miniov2.LocalCertificateReference, error) { +func createOrReplaceKesConfigurationSecrets(ctx context.Context, clientSet K8sClientI, ns string, encryptionCfg *models.EncryptionConfiguration, kesConfigurationSecretName, kesClientCertSecretName, tenantName string, image string) (*corev1.LocalObjectReference, *miniov2.LocalCertificateReference, error) { // if autoCert is enabled then Operator will generate the client certificates, calculate the client cert identity // and pass it to KES via the ${MINIO_KES_IDENTITY} variable clientCrtIdentity := "${MINIO_KES_IDENTITY}" @@ -480,8 +664,99 @@ func createOrReplaceKesConfigurationSecrets(ctx context.Context, clientSet K8sCl h.Write(certificate.RawSubjectPublicKeyInfo) clientCrtIdentity = hex.EncodeToString(h.Sum(nil)) } + + // map to hold mTLSCertificates for KES mTLS against Vault + mTLSCertificates := map[string][]byte{} + + imm := true + // if mTLSCertificates contains elements we create the kubernetes secret + var clientCertSecretReference *miniov2.LocalCertificateReference + var serverRawConfig []byte + var err error + + if encryptionCfg.Raw != "" { + serverRawConfig = []byte(encryptionCfg.Raw) + // verify provided configuration is in valid YAML format + + cv, err := getKesConfigVersion(image) + if err != nil { + return nil, nil, err + } + configType := KesConfigVersionsMap[cv] + err = yaml.Unmarshal(serverRawConfig, &configType) + if err != nil { + return nil, nil, err + } + } else { + + // Identify which method use to generate the KES config YAML + // Based on the KES Image name + kesConfigMethodVersion, err := getKesConfigMethod(image) + if err != nil { + return nil, nil, err + } + // Invoke the resulting kes config method + serverRawConfig, err = kesConfigMethodVersion(clientCrtIdentity, encryptionCfg, mTLSCertificates) + if err != nil { + return nil, nil, err + } + } + + if len(mTLSCertificates) > 0 { + // delete KES client cert secret only if new client certificates are provided + if err := clientSet.deleteSecret(ctx, ns, kesClientCertSecretName, metav1.DeleteOptions{}); err != nil { + // log the errors if any and continue + LogError("deleting secret name %s failed: %v, continuing..", kesClientCertSecretName, err) + } + // Secret to store KES mTLS kesConfiguration to authenticate against a KMS + kesClientCertSecret := corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: kesClientCertSecretName, + Labels: map[string]string{ + miniov2.TenantLabel: tenantName, + }, + }, + Immutable: &imm, + Data: mTLSCertificates, + } + _, err := clientSet.createSecret(ctx, ns, &kesClientCertSecret, metav1.CreateOptions{}) + if err != nil { + return nil, nil, err + } + // kubernetes generic secret + clientCertSecretReference = &miniov2.LocalCertificateReference{ + Name: kesClientCertSecretName, + } + } + + // delete KES configuration secret if exists + if err := clientSet.deleteSecret(ctx, ns, kesConfigurationSecretName, metav1.DeleteOptions{}); err != nil { + // log the errors if any and continue + LogError("deleting secret name %s failed: %v, continuing..", kesConfigurationSecretName, err) + } + + // Secret to store KES server kesConfiguration + kesConfigurationSecret := corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: kesConfigurationSecretName, + Labels: map[string]string{ + miniov2.TenantLabel: tenantName, + }, + }, + Immutable: &imm, + Data: map[string][]byte{ + "server-config.yaml": serverRawConfig, + }, + } + _, err = clientSet.createSecret(ctx, ns, &kesConfigurationSecret, metav1.CreateOptions{}) + return &corev1.LocalObjectReference{ + Name: kesConfigurationSecretName, + }, clientCertSecretReference, err +} + +func createKesConfigV1(clientCrtIdentity string, encryptionCfg *models.EncryptionConfiguration, mTLSCertificates map[string][]byte) ([]byte, error) { // Default kesConfiguration for KES - kesConfig := &kes.ServerConfig{ + kesConfig := &kes.ServerConfigV1{ Addr: "0.0.0.0:7373", Root: "disabled", TLS: kes.TLS{ @@ -512,17 +787,9 @@ func createOrReplaceKesConfigurationSecrets(ctx context.Context, clientSet K8sCl }, Keys: kes.Keys{}, } - // miniov2 will mount the mTLSCertificates in the following paths - // therefore we set these values in the KES yaml kesConfiguration - mTLSClientCrtPath := "/tmp/kes/client.crt" - mTLSClientKeyPath := "/tmp/kes/client.key" - mTLSClientCaPath := "/tmp/kes/ca.crt" - // map to hold mTLSCertificates for KES mTLS against Vault - mTLSCertificates := map[string][]byte{} - // if encryption is enabled and encryption is configured to use Vault switch { case encryptionCfg.Vault != nil: - ping := 10 // default ping + ping := defaultPing if encryptionCfg.Vault.Status != nil { ping = int(encryptionCfg.Vault.Status.Ping) } @@ -546,7 +813,7 @@ func createOrReplaceKesConfigurationSecrets(ctx context.Context, clientSet K8sCl Retry: time.Duration(retry) * time.Second, } } else { - return nil, nil, errors.New("approle credentials missing for kes") + return nil, errors.New("approle credentials missing for kes") } // Vault mTLS kesConfiguration if encryptionCfg.KmsMtls != nil { @@ -555,7 +822,7 @@ func createOrReplaceKesConfigurationSecrets(ctx context.Context, clientSet K8sCl if vaultTLSConfig.Crt != "" { clientCrt, err := base64.StdEncoding.DecodeString(vaultTLSConfig.Crt) if err != nil { - return nil, nil, err + return nil, err } mTLSCertificates["client.crt"] = clientCrt kesConfig.Keys.Vault.TLS.CertPath = mTLSClientCrtPath @@ -563,7 +830,7 @@ func createOrReplaceKesConfigurationSecrets(ctx context.Context, clientSet K8sCl if vaultTLSConfig.Key != "" { clientKey, err := base64.StdEncoding.DecodeString(vaultTLSConfig.Key) if err != nil { - return nil, nil, err + return nil, err } mTLSCertificates["client.key"] = clientKey kesConfig.Keys.Vault.TLS.KeyPath = mTLSClientKeyPath @@ -571,7 +838,7 @@ func createOrReplaceKesConfigurationSecrets(ctx context.Context, clientSet K8sCl if vaultTLSConfig.Ca != "" { caCrt, err := base64.StdEncoding.DecodeString(vaultTLSConfig.Ca) if err != nil { - return nil, nil, err + return nil, err } mTLSCertificates["ca.crt"] = caCrt kesConfig.Keys.Vault.TLS.CAPath = mTLSClientCaPath @@ -609,7 +876,7 @@ func createOrReplaceKesConfigurationSecrets(ctx context.Context, clientSet K8sCl if encryptionCfg.KmsMtls.Ca != "" { caCrt, err := base64.StdEncoding.DecodeString(encryptionCfg.KmsMtls.Ca) if err != nil { - return nil, nil, err + return nil, err } mTLSCertificates["ca.crt"] = caCrt kesConfig.Keys.Gemalto.KeySecure.TLS = &kes.GemaltoTLS{ @@ -661,76 +928,240 @@ func createOrReplaceKesConfigurationSecrets(ctx context.Context, clientSet K8sCl } } } - imm := true - // if mTLSCertificates contains elements we create the kubernetes secret - var clientCertSecretReference *miniov2.LocalCertificateReference - if len(mTLSCertificates) > 0 { - // delete KES client cert secret only if new client certificates are provided - if err := clientSet.deleteSecret(ctx, ns, kesClientCertSecretName, metav1.DeleteOptions{}); err != nil { - // log the errors if any and continue - LogError("deleting secret name %s failed: %v, continuing..", kesClientCertSecretName, err) + return kesConfig.Marshal() +} + +func createKesConfigV2(clientCrtIdentity string, encryptionCfg *models.EncryptionConfiguration, mTLSCertificates map[string][]byte) ([]byte, error) { + kesConfig := &kes.ServerConfigV2{ + Addr: "0.0.0.0:7373", + TLS: kes.TLS{ + KeyPath: "/tmp/kes/server.key", + CertPath: "/tmp/kes/server.crt", + }, + Admin: kes.AdminIdentity{ + Identity: kes.Identity(clientCrtIdentity), + }, + Cache: kes.CacheV2{ + Expiry: &kes.ExpiryV2{ + Any: 5 * time.Minute, + Unused: 20 * time.Second, + }, + }, + Log: kes.Log{ + Error: "on", + Audit: "off", + }, + Keystore: kes.Keys{}, + } + + switch { + case encryptionCfg.Vault != nil: + ping := defaultPing + if encryptionCfg.Vault.Status != nil { + ping = int(encryptionCfg.Vault.Status.Ping) } - // Secret to store KES mTLS kesConfiguration to authenticate against a KMS - kesClientCertSecret := corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: kesClientCertSecretName, - Labels: map[string]string{ - miniov2.TenantLabel: tenantName, - }, + // Initialize Vault Config + kesConfig.Keystore.Vault = &kes.Vault{ + Endpoint: *encryptionCfg.Vault.Endpoint, + EnginePath: encryptionCfg.Vault.Engine, + Namespace: encryptionCfg.Vault.Namespace, + Prefix: encryptionCfg.Vault.Prefix, + Status: &kes.VaultStatus{ + Ping: time.Duration(ping) * time.Second, }, - Immutable: &imm, - Data: mTLSCertificates, } - _, err := clientSet.createSecret(ctx, ns, &kesClientCertSecret, metav1.CreateOptions{}) - if err != nil { - return nil, nil, err + // Vault AppRole credentials + if encryptionCfg.Vault.Approle != nil { + retry := encryptionCfg.Vault.Approle.Retry + kesConfig.Keystore.Vault.AppRole = &kes.AppRole{ + EnginePath: encryptionCfg.Vault.Approle.Engine, + ID: *encryptionCfg.Vault.Approle.ID, + Secret: *encryptionCfg.Vault.Approle.Secret, + Retry: time.Duration(retry) * time.Second, + } + } else { + return nil, errors.New("approle credentials missing for kes") } - // kubernetes generic secret - clientCertSecretReference = &miniov2.LocalCertificateReference{ - Name: kesClientCertSecretName, + // Vault mTLS kesConfiguration + if encryptionCfg.KmsMtls != nil { + vaultTLSConfig := encryptionCfg.KmsMtls + kesConfig.Keystore.Vault.TLS = &kes.VaultTLS{} + if vaultTLSConfig.Crt != "" { + clientCrt, err := base64.StdEncoding.DecodeString(vaultTLSConfig.Crt) + if err != nil { + return nil, err + } + mTLSCertificates["client.crt"] = clientCrt + kesConfig.Keystore.Vault.TLS.CertPath = mTLSClientCrtPath + } + if vaultTLSConfig.Key != "" { + clientKey, err := base64.StdEncoding.DecodeString(vaultTLSConfig.Key) + if err != nil { + return nil, err + } + mTLSCertificates["client.key"] = clientKey + kesConfig.Keystore.Vault.TLS.KeyPath = mTLSClientKeyPath + } + if vaultTLSConfig.Ca != "" { + caCrt, err := base64.StdEncoding.DecodeString(vaultTLSConfig.Ca) + if err != nil { + return nil, err + } + mTLSCertificates["ca.crt"] = caCrt + kesConfig.Keystore.Vault.TLS.CAPath = mTLSClientCaPath + } + } + case encryptionCfg.Aws != nil: + // Initialize AWS + kesConfig.Keystore.Aws = &kes.Aws{ + SecretsManager: &kes.AwsSecretManager{}, + } + // AWS basic kesConfiguration + if encryptionCfg.Aws.Secretsmanager != nil { + kesConfig.Keystore.Aws.SecretsManager.Endpoint = *encryptionCfg.Aws.Secretsmanager.Endpoint + kesConfig.Keystore.Aws.SecretsManager.Region = *encryptionCfg.Aws.Secretsmanager.Region + kesConfig.Keystore.Aws.SecretsManager.KmsKey = encryptionCfg.Aws.Secretsmanager.Kmskey + // AWS credentials + if encryptionCfg.Aws.Secretsmanager.Credentials != nil { + kesConfig.Keystore.Aws.SecretsManager.Login = &kes.AwsSecretManagerLogin{ + AccessKey: *encryptionCfg.Aws.Secretsmanager.Credentials.Accesskey, + SecretKey: *encryptionCfg.Aws.Secretsmanager.Credentials.Secretkey, + SessionToken: encryptionCfg.Aws.Secretsmanager.Credentials.Token, + } + } + } + case encryptionCfg.Gemalto != nil: + // Initialize Gemalto + kesConfig.Keystore.Gemalto = &kes.Gemalto{ + KeySecure: &kes.GemaltoKeySecure{}, + } + // Gemalto Configuration + if encryptionCfg.Gemalto.Keysecure != nil { + kesConfig.Keystore.Gemalto.KeySecure.Endpoint = *encryptionCfg.Gemalto.Keysecure.Endpoint + // Gemalto TLS kesConfiguration + if encryptionCfg.KmsMtls != nil { + if encryptionCfg.KmsMtls.Ca != "" { + caCrt, err := base64.StdEncoding.DecodeString(encryptionCfg.KmsMtls.Ca) + if err != nil { + return nil, err + } + mTLSCertificates["ca.crt"] = caCrt + kesConfig.Keystore.Gemalto.KeySecure.TLS = &kes.GemaltoTLS{ + CAPath: mTLSClientCaPath, + } + } + } + // Gemalto Login + if encryptionCfg.Gemalto.Keysecure.Credentials != nil { + kesConfig.Keystore.Gemalto.KeySecure.Credentials = &kes.GemaltoCredentials{ + Token: *encryptionCfg.Gemalto.Keysecure.Credentials.Token, + Domain: *encryptionCfg.Gemalto.Keysecure.Credentials.Domain, + Retry: 15 * time.Second, + } + } + } + case encryptionCfg.Gcp != nil: + // Initialize GCP + kesConfig.Keystore.Gcp = &kes.Gcp{ + SecretManager: &kes.GcpSecretManager{}, + } + // GCP basic kesConfiguration + if encryptionCfg.Gcp.Secretmanager != nil { + kesConfig.Keystore.Gcp.SecretManager.ProjectID = *encryptionCfg.Gcp.Secretmanager.ProjectID + kesConfig.Keystore.Gcp.SecretManager.Endpoint = encryptionCfg.Gcp.Secretmanager.Endpoint + // GCP credentials + if encryptionCfg.Gcp.Secretmanager.Credentials != nil { + kesConfig.Keystore.Gcp.SecretManager.Credentials = &kes.GcpCredentials{ + ClientEmail: encryptionCfg.Gcp.Secretmanager.Credentials.ClientEmail, + ClientID: encryptionCfg.Gcp.Secretmanager.Credentials.ClientID, + PrivateKeyID: encryptionCfg.Gcp.Secretmanager.Credentials.PrivateKeyID, + PrivateKey: encryptionCfg.Gcp.Secretmanager.Credentials.PrivateKey, + } + } + } + case encryptionCfg.Azure != nil: + // Initialize Azure + kesConfig.Keystore.Azure = &kes.Azure{ + KeyVault: &kes.AzureKeyVault{}, + } + if encryptionCfg.Azure.Keyvault != nil { + kesConfig.Keystore.Azure.KeyVault.Endpoint = *encryptionCfg.Azure.Keyvault.Endpoint + if encryptionCfg.Azure.Keyvault.Credentials != nil { + kesConfig.Keystore.Azure.KeyVault.Credentials = &kes.AzureCredentials{ + TenantID: *encryptionCfg.Azure.Keyvault.Credentials.TenantID, + ClientID: *encryptionCfg.Azure.Keyvault.Credentials.ClientID, + ClientSecret: *encryptionCfg.Azure.Keyvault.Credentials.ClientSecret, + } + } } } + return kesConfig.Marshal() +} - var serverRawConfig []byte - var err error +// getKesConfigMethod identify the config method to use based from the KES image name +func getKesConfigMethod(image string) (configVersion, error) { + version, err := getKesConfigVersion(image) + if err != nil { + return nil, err + } + // switch for future (or previous) versions of KES config + switch version { + case KesConfigVersion1: + return createKesConfigV1, nil + default: + return createKesConfigV2, nil + } +} - if encryptionCfg.Raw != "" { - serverRawConfig = []byte(encryptionCfg.Raw) - // verify provided configuration is in valid YAML format - var configTest kes.ServerConfig - err = yaml.Unmarshal(serverRawConfig, &configTest) - if err != nil { - return nil, nil, err - } +func getKesConfigVersion(image string) (string, error) { + version := KesConfigVersion2 + + imageStrings := strings.Split(image, ":") + var imageTag string + if len(imageStrings) > 1 { + imageTag = imageStrings[1] } else { - // Generate Yaml kesConfiguration for KES - serverRawConfig, err = yaml.Marshal(kesConfig) - if err != nil { - return nil, nil, err + return "", fmt.Errorf("%s not a valid KES release tag", image) + } + + if imageTag == "edge" { + return KesConfigVersion2, nil + } + + if imageTag == "latest" { + return KesConfigVersion2, nil + } + + // When the image tag is semantic version is config v1 + if semver.IsValid(imageTag) { + // Admin is required starting version v0.22.0 + if semver.Compare(imageTag, "v0.22.0") < 0 { + return KesConfigVersion1, nil } + return KesConfigVersion2, nil } - // delete KES configuration secret if exists - if err := clientSet.deleteSecret(ctx, ns, kesConfigurationSecretName, metav1.DeleteOptions{}); err != nil { - // log the errors if any and continue - LogError("deleting secret name %s failed: %v, continuing..", kesConfigurationSecretName, err) + releaseTagNoArch := imageTag + + re := regexp.MustCompile(kesImageTagWithArchRegexPattern) + // if pattern matches, that means we have a tag with arch + if matched := re.Match([]byte(imageTag)); matched { + slicesOfTag := re.FindStringSubmatch(imageTag) + // here we will remove the arch suffix by assigning the first group in the regex + releaseTagNoArch = slicesOfTag[1] } - // Secret to store KES server kesConfiguration - kesConfigurationSecret := corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: kesConfigurationSecretName, - Labels: map[string]string{ - miniov2.TenantLabel: tenantName, - }, - }, - Immutable: &imm, - Data: map[string][]byte{ - "server-config.yaml": serverRawConfig, - }, + // v0.22.0 is the initial image version for Kes config v2, any time format came after and is v2 + _, err := miniov2.ReleaseTagToReleaseTime(releaseTagNoArch) + if err != nil { + // could not parse semversion either, returning error + return "", fmt.Errorf("could not identify KES version from image TAG: %s", releaseTagNoArch) } - _, err = clientSet.createSecret(ctx, ns, &kesConfigurationSecret, metav1.CreateOptions{}) - return &corev1.LocalObjectReference{ - Name: kesConfigurationSecretName, - }, clientCertSecretReference, err + + // Leaving this snippet as comment as this will helpful to compare in future config versions + // kesv2ReleaseTime, _ := miniov2.ReleaseTagToReleaseTime("2023-04-03T16-41-28Z") + // if imageVersionTime.Before(kesv2ReleaseTime) { + // version = kesConfigVersion2 + // } + return version, nil } diff --git a/api/encryption-handlers_test.go b/api/encryption-handlers_test.go index 0b799eaf7a7..e4d48c9ff7f 100644 --- a/api/encryption-handlers_test.go +++ b/api/encryption-handlers_test.go @@ -19,6 +19,7 @@ package api import ( "context" "errors" + "fmt" "net/http" "time" @@ -293,7 +294,7 @@ func (suite *TenantTestSuite) TestTenantEncryptionInfoWithClientCertError() { suite.assert.NotNil(err) } -func (suite *TenantTestSuite) TestTenantEncryptionInfoWithKesClientCertError() { +func (suite *TenantTestSuite) TestTenantEncryptionInfoWithKesClientCertErrorV2() { opClientTenantGetMock = func(ctx context.Context, namespace string, tenantName string, options metav1.GetOptions) (*miniov2.Tenant, error) { return &miniov2.Tenant{ Spec: miniov2.TenantSpec{ @@ -312,7 +313,7 @@ func (suite *TenantTestSuite) TestTenantEncryptionInfoWithKesClientCertError() { if secretName == "mock-kes-config" { return &corev1.Secret{ Data: map[string][]byte{ - "server-config.yaml": suite.getKesYamlMock(false), + "server-config.yaml": suite.getKesV2YamlMock(false), }, }, nil } @@ -329,9 +330,10 @@ func (suite *TenantTestSuite) TestTenantEncryptionInfoWithKesClientCertError() { res, err := tenantEncryptionInfo(context.Background(), suite.opClient, suite.k8sclient, params.Namespace, params) suite.assert.Nil(res) suite.assert.NotNil(err) + suite.assert.Equal(err, errors.New("certificate failed to decode")) } -func (suite *TenantTestSuite) TestTenantEncryptionInfoWithKesClientCACertError() { +func (suite *TenantTestSuite) TestTenantEncryptionInfoWithKesClientCertErrorV1() { opClientTenantGetMock = func(ctx context.Context, namespace string, tenantName string, options metav1.GetOptions) (*miniov2.Tenant, error) { return &miniov2.Tenant{ Spec: miniov2.TenantSpec{ @@ -342,6 +344,7 @@ func (suite *TenantTestSuite) TestTenantEncryptionInfoWithKesClientCACertError() Configuration: &corev1.LocalObjectReference{ Name: "mock-kes-config", }, + Image: "minio/kes:v0.21.1", }, }, }, nil @@ -350,7 +353,86 @@ func (suite *TenantTestSuite) TestTenantEncryptionInfoWithKesClientCACertError() if secretName == "mock-kes-config" { return &corev1.Secret{ Data: map[string][]byte{ - "server-config.yaml": suite.getKesYamlMock(false), + "server-config.yaml": suite.getKesV1YamlMock(false), + }, + }, nil + } + if secretName == "mock-kes-crt" { + return &corev1.Secret{ + Data: map[string][]byte{ + "client.crt": []byte("mock-client-crt"), + }, + }, nil + } + return nil, errors.New("mock-get-error") + } + params, _ := suite.initTenantEncryptionInfoRequest() + res, err := tenantEncryptionInfo(context.Background(), suite.opClient, suite.k8sclient, params.Namespace, params) + suite.assert.Nil(res) + suite.assert.NotNil(err) + suite.assert.Equal(err, errors.New("certificate failed to decode")) +} + +func (suite *TenantTestSuite) TestTenantEncryptionInfoWithKesClientCACertErrorV2() { + opClientTenantGetMock = func(ctx context.Context, namespace string, tenantName string, options metav1.GetOptions) (*miniov2.Tenant, error) { + return &miniov2.Tenant{ + Spec: miniov2.TenantSpec{ + KES: &miniov2.KESConfig{ + ClientCertSecret: &miniov2.LocalCertificateReference{ + Name: "mock-kes-crt", + }, + Configuration: &corev1.LocalObjectReference{ + Name: "mock-kes-config", + }, + }, + }, + }, nil + } + k8sclientGetSecretMock = func(ctx context.Context, namespace, secretName string, opts metav1.GetOptions) (*corev1.Secret, error) { + if secretName == "mock-kes-config" { + return &corev1.Secret{ + Data: map[string][]byte{ + "server-config.yaml": suite.getKesV2YamlMock(false), + }, + }, nil + } + if secretName == "mock-kes-crt" { + return &corev1.Secret{ + Data: map[string][]byte{ + "ca.crt": []byte("mock-client-crt"), + }, + }, nil + } + return nil, errors.New("mock-get-error") + } + params, _ := suite.initTenantEncryptionInfoRequest() + res, err := tenantEncryptionInfo(context.Background(), suite.opClient, suite.k8sclient, params.Namespace, params) + suite.assert.Nil(res) + suite.assert.NotNil(err) + suite.assert.Equal(err, errors.New("certificate failed to decode")) +} + +func (suite *TenantTestSuite) TestTenantEncryptionInfoWithKesClientCACertErrorV1() { + opClientTenantGetMock = func(ctx context.Context, namespace string, tenantName string, options metav1.GetOptions) (*miniov2.Tenant, error) { + return &miniov2.Tenant{ + Spec: miniov2.TenantSpec{ + KES: &miniov2.KESConfig{ + ClientCertSecret: &miniov2.LocalCertificateReference{ + Name: "mock-kes-crt", + }, + Configuration: &corev1.LocalObjectReference{ + Name: "mock-kes-config", + }, + Image: "minio/kes:v0.21.1", + }, + }, + }, nil + } + k8sclientGetSecretMock = func(ctx context.Context, namespace, secretName string, opts metav1.GetOptions) (*corev1.Secret, error) { + if secretName == "mock-kes-config" { + return &corev1.Secret{ + Data: map[string][]byte{ + "server-config.yaml": suite.getKesV1YamlMock(false), }, }, nil } @@ -367,9 +449,10 @@ func (suite *TenantTestSuite) TestTenantEncryptionInfoWithKesClientCACertError() res, err := tenantEncryptionInfo(context.Background(), suite.opClient, suite.k8sclient, params.Namespace, params) suite.assert.Nil(res) suite.assert.NotNil(err) + suite.assert.Equal(err, errors.New("certificate failed to decode")) } -func (suite *TenantTestSuite) TestTenantEncryptionInfoWithGemaltoError() { +func (suite *TenantTestSuite) TestTenantEncryptionInfoWithGemaltoErrorV2() { opClientTenantGetMock = func(ctx context.Context, namespace string, tenantName string, options metav1.GetOptions) (*miniov2.Tenant, error) { return &miniov2.Tenant{ Spec: miniov2.TenantSpec{ @@ -388,7 +471,7 @@ func (suite *TenantTestSuite) TestTenantEncryptionInfoWithGemaltoError() { if secretName == "mock-kes-config" { return &corev1.Secret{ Data: map[string][]byte{ - "server-config.yaml": suite.getKesYamlMock(true), + "server-config.yaml": suite.getKesV2YamlMock(true), }, }, nil } @@ -405,16 +488,21 @@ func (suite *TenantTestSuite) TestTenantEncryptionInfoWithGemaltoError() { res, err := tenantEncryptionInfo(context.Background(), suite.opClient, suite.k8sclient, params.Namespace, params) suite.assert.Nil(res) suite.assert.NotNil(err) + suite.assert.Equal(err, errors.New("certificate failed to decode")) } -func (suite *TenantTestSuite) TestTenantEncryptionInfoWithoutError() { +func (suite *TenantTestSuite) TestTenantEncryptionInfoWithGemaltoErrorV1() { opClientTenantGetMock = func(ctx context.Context, namespace string, tenantName string, options metav1.GetOptions) (*miniov2.Tenant, error) { return &miniov2.Tenant{ Spec: miniov2.TenantSpec{ KES: &miniov2.KESConfig{ + ClientCertSecret: &miniov2.LocalCertificateReference{ + Name: "mock-kes-crt", + }, Configuration: &corev1.LocalObjectReference{ Name: "mock-kes-config", }, + Image: "minio/kes:v0.21.1", }, }, }, nil @@ -423,7 +511,14 @@ func (suite *TenantTestSuite) TestTenantEncryptionInfoWithoutError() { if secretName == "mock-kes-config" { return &corev1.Secret{ Data: map[string][]byte{ - "server-config.yaml": suite.getKesYamlMock(false), + "server-config.yaml": suite.getKesV1YamlMock(true), + }, + }, nil + } + if secretName == "mock-kes-crt" { + return &corev1.Secret{ + Data: map[string][]byte{ + "ca.crt": []byte("mock-client-crt"), }, }, nil } @@ -431,12 +526,133 @@ func (suite *TenantTestSuite) TestTenantEncryptionInfoWithoutError() { } params, _ := suite.initTenantEncryptionInfoRequest() res, err := tenantEncryptionInfo(context.Background(), suite.opClient, suite.k8sclient, params.Namespace, params) + suite.assert.Nil(res) + suite.assert.NotNil(err) + suite.assert.Equal(err, errors.New("certificate failed to decode")) +} + +func (suite *TenantTestSuite) TestTenantEncryptionInfoWithoutErrorv2() { + rawConfig := suite.getKesV2YamlMock(true) + kesImage := miniov2.GetTenantKesImage() + + opClientTenantGetMock = func(ctx context.Context, namespace string, tenantName string, options metav1.GetOptions) (*miniov2.Tenant, error) { + return &miniov2.Tenant{ + Spec: miniov2.TenantSpec{ + KES: &miniov2.KESConfig{ + Configuration: &corev1.LocalObjectReference{ + Name: "mock-kes-config", + }, + }, + }, + }, nil + } + k8sclientGetSecretMock = func(ctx context.Context, namespace, secretName string, opts metav1.GetOptions) (*corev1.Secret, error) { + if secretName == "mock-kes-config" { + return &corev1.Secret{ + Data: map[string][]byte{ + "server-config.yaml": rawConfig, + }, + }, nil + } + return nil, errors.New("mock-get-error") + } + params, _ := suite.initTenantEncryptionInfoRequest() + expectedConfig, err := suite.getExpectedEncriptionConfiguration(context.Background(), rawConfig, true, suite.opClient, params.Tenant, params.Namespace, kesImage) + suite.assert.Nil(err) + res, err := tenantEncryptionInfo(context.Background(), suite.opClient, suite.k8sclient, params.Namespace, params) + suite.assert.Equal(expectedConfig, res) + suite.assert.NotNil(res) + suite.assert.Nil(err) +} + +func (suite *TenantTestSuite) TestTenantEncryptionInfoWithoutErrorv1() { + rawConfig := suite.getKesV1YamlMock(false) + kesImage := "minio/kes:v0.21.1" + + opClientTenantGetMock = func(ctx context.Context, namespace string, tenantName string, options metav1.GetOptions) (*miniov2.Tenant, error) { + return &miniov2.Tenant{ + Spec: miniov2.TenantSpec{ + KES: &miniov2.KESConfig{ + Configuration: &corev1.LocalObjectReference{ + Name: "mock-kes-config", + }, + Image: kesImage, + }, + }, + }, nil + } + k8sclientGetSecretMock = func(ctx context.Context, namespace, secretName string, opts metav1.GetOptions) (*corev1.Secret, error) { + if secretName == "mock-kes-config" { + return &corev1.Secret{ + Data: map[string][]byte{ + "server-config.yaml": rawConfig, + }, + }, nil + } + return nil, errors.New("mock-get-error") + } + params, _ := suite.initTenantEncryptionInfoRequest() + expectedConfig, err := suite.getExpectedEncriptionConfiguration(context.Background(), rawConfig, false, suite.opClient, params.Tenant, params.Namespace, kesImage) + suite.assert.Nil(err) + res, err := tenantEncryptionInfo(context.Background(), suite.opClient, suite.k8sclient, params.Namespace, params) + suite.assert.Equal(expectedConfig, res) suite.assert.NotNil(res) suite.assert.Nil(err) } -func (suite *TenantTestSuite) getKesYamlMock(noVault bool) []byte { - kesConfig := &kes.ServerConfig{ +func (suite *TenantTestSuite) getExpectedEncriptionConfiguration(ctx context.Context, rawConfig []byte, noVault bool, operatorClient OperatorClientI, tenantName string, namespace string, kesImage string) (*models.EncryptionConfigurationResponse, error) { + tenant, err := operatorClient.TenantGet(ctx, namespace, tenantName, metav1.GetOptions{}) + endpoint := "mock-endpoint" + mockid := "mock-id" + mocksecret := "mock-secret" + mockdomain := "mock-domain" + mocktoken := "mock-token" + + if err != nil { + return nil, err + } + response := &models.EncryptionConfigurationResponse{ + Raw: string(rawConfig), + Image: kesImage, + Replicas: fmt.Sprintf("%d", tenant.Spec.KES.Replicas), + Aws: &models.AwsConfiguration{}, + Gcp: &models.GcpConfiguration{}, + Azure: &models.AzureConfiguration{}, + Vault: &models.VaultConfigurationResponse{ + Prefix: "mock-prefix", + Namespace: "mock-namespace", + Engine: "mock-engine-path", + Endpoint: &endpoint, + Status: &models.VaultConfigurationResponseStatus{ + Ping: 5, + }, + Approle: &models.VaultConfigurationResponseApprole{ + Engine: "mock-engine-path", + ID: &mockid, + Retry: 5, + Secret: &mocksecret, + }, + }, + Gemalto: &models.GemaltoConfigurationResponse{ + Keysecure: &models.GemaltoConfigurationResponseKeysecure{ + Endpoint: &endpoint, + Credentials: &models.GemaltoConfigurationResponseKeysecureCredentials{ + Domain: &mockdomain, + Retry: 5, + Token: &mocktoken, + }, + }, + }, + } + if noVault { + response.Vault = nil + } + + return response, nil +} + +func (suite *TenantTestSuite) getKesV1YamlMock(noVault bool) []byte { + kesConfig := &kes.ServerConfigV1{ Keys: kes.Keys{ Vault: &kes.Vault{ Prefix: "mock-prefix", @@ -476,6 +692,47 @@ func (suite *TenantTestSuite) getKesYamlMock(noVault bool) []byte { return kesConfigBytes } +func (suite *TenantTestSuite) getKesV2YamlMock(noVault bool) []byte { + kesConfig := &kes.ServerConfigV2{ + Keystore: kes.Keys{ + Vault: &kes.Vault{ + Prefix: "mock-prefix", + Namespace: "mock-namespace", + EnginePath: "mock-engine-path", + Endpoint: "mock-endpoint", + Status: &kes.VaultStatus{ + Ping: 5 * time.Second, + }, + AppRole: &kes.AppRole{ + EnginePath: "mock-engine-path", + ID: "mock-id", + Retry: 5 * time.Second, + Secret: "mock-secret", + }, + }, + Aws: &kes.Aws{}, + Gcp: &kes.Gcp{}, + Gemalto: &kes.Gemalto{ + KeySecure: &kes.GemaltoKeySecure{ + Endpoint: "mock-endpoint", + Credentials: &kes.GemaltoCredentials{ + Domain: "mock-domain", + Retry: 5 * time.Second, + Token: "mock-token", + }, + TLS: &kes.GemaltoTLS{}, + }, + }, + Azure: &kes.Azure{}, + }, + } + if noVault { + kesConfig.Keystore.Vault = nil + } + kesConfigBytes, _ := yaml.Marshal(kesConfig) + return kesConfigBytes +} + func (suite *TenantTestSuite) initTenantEncryptionInfoRequest() (params operator_api.TenantEncryptionInfoParams, api operations.OperatorAPI) { registerEncryptionHandlers(&api) params.HTTPRequest = &http.Request{} diff --git a/api/tenants_helper_test.go b/api/tenants_helper_test.go index a44edd8a2bc..ca5683e4fd0 100644 --- a/api/tenants_helper_test.go +++ b/api/tenants_helper_test.go @@ -249,6 +249,7 @@ func Test_createOrReplaceKesConfigurationSecrets(t *testing.T) { encryptionCfg *models.EncryptionConfiguration kesConfigurationSecretName string kesClientCertSecretName string + kesImage string tenantName string mockDeleteSecret func(ctx context.Context, namespace string, name string, opts metav1.DeleteOptions) error mockCreateSecret func(ctx context.Context, namespace string, secret *v1.Secret, opts metav1.CreateOptions) (*v1.Secret, error) @@ -272,6 +273,7 @@ func Test_createOrReplaceKesConfigurationSecrets(t *testing.T) { }, }, ns: "default", + kesImage: "minio/kes:v0.22.3", kesConfigurationSecretName: "test-secret", kesClientCertSecretName: "test-client-secret", tenantName: "test", @@ -295,6 +297,7 @@ func Test_createOrReplaceKesConfigurationSecrets(t *testing.T) { }, }, ns: "default", + kesImage: "minio/kes:v0.22.3", kesConfigurationSecretName: "test-secret", kesClientCertSecretName: "test-client-secret", tenantName: "test", @@ -336,6 +339,7 @@ func Test_createOrReplaceKesConfigurationSecrets(t *testing.T) { }, }, ns: "default", + kesImage: "minio/kes:v0.22.3", kesConfigurationSecretName: "test-secret", kesClientCertSecretName: "test-client-secret", tenantName: "test", @@ -359,7 +363,7 @@ func Test_createOrReplaceKesConfigurationSecrets(t *testing.T) { t.Run(tt.name, func(t *testing.T) { k8sClientDeleteSecretMock = tt.args.mockDeleteSecret k8sClientCreateSecretMock = tt.args.mockCreateSecret - got, got1, err := createOrReplaceKesConfigurationSecrets(tt.args.ctx, tt.args.clientSet, tt.args.ns, tt.args.encryptionCfg, tt.args.kesConfigurationSecretName, tt.args.kesClientCertSecretName, tt.args.tenantName) + got, got1, err := createOrReplaceKesConfigurationSecrets(tt.args.ctx, tt.args.clientSet, tt.args.ns, tt.args.encryptionCfg, tt.args.kesConfigurationSecretName, tt.args.kesClientCertSecretName, tt.args.tenantName, tt.args.kesImage) if (err != nil) != tt.wantErr { t.Errorf("createOrReplaceKesConfigurationSecrets() error = %v, wantErr %v", err, tt.wantErr) return @@ -373,3 +377,93 @@ func Test_createOrReplaceKesConfigurationSecrets(t *testing.T) { }) } } + +func Test_GetConfigVersion(t *testing.T) { + type args struct { + kesImage string + } + tests := []struct { + name string + args args + wantversion string + wantErr bool + }{ + { + name: "error unexpected KES config version", + args: args{ + kesImage: "minio/kes:v0.22.0", + }, + wantversion: KesConfigVersion2, + wantErr: false, + }, + { + name: "error unexpected KES config version", + args: args{ + kesImage: "minio/kes:v0.21.0", + }, + wantversion: KesConfigVersion1, + wantErr: false, + }, + { + name: "error unexpected KES config version", + args: args{ + kesImage: "minio/kes:2023-02-15T14-54-37Z", + }, + wantversion: KesConfigVersion2, + wantErr: false, + }, + { + name: "error unexpected KES config version", + args: args{ + kesImage: "minio/kes:2023-04-03T16-41-28Z", + }, + wantversion: KesConfigVersion2, + wantErr: false, + }, + { + name: "error unexpected KES config version", + args: args{ + kesImage: "minio/kes:2023-05-02T22-48-10Z", + }, + wantversion: KesConfigVersion2, + wantErr: false, + }, + { + name: "error unexpected KES config version", + args: args{ + kesImage: "minio/kes:2023-05-02T22-48-10Z-arm64", + }, + wantversion: KesConfigVersion2, + wantErr: false, + }, + { + name: "error unexpected KES config version", + args: args{ + kesImage: "minio/kes:edge", + }, + wantversion: KesConfigVersion2, + wantErr: false, + }, + { + name: "error unexpected KES config version", + args: args{ + kesImage: "minio/kes:latest", + }, + wantversion: KesConfigVersion2, + wantErr: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := getKesConfigVersion(tt.args.kesImage) + if (err != nil) != tt.wantErr { + t.Errorf("getKesConfigVersion() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.wantversion) { + t.Errorf("getKesConfigVersion() got = %v, want %v", got, tt.wantversion) + } + }) + } +} diff --git a/go.mod b/go.mod index 2eefd9c7a99..24af934d3c0 100644 --- a/go.mod +++ b/go.mod @@ -25,7 +25,6 @@ require ( github.com/miekg/dns v1.1.50 github.com/minio/cli v1.24.2 github.com/minio/highwayhash v1.0.2 - github.com/minio/kes v0.22.3 github.com/minio/madmin-go/v2 v2.0.14 github.com/minio/mc v0.0.0-20230221142751-40e51ee9affb github.com/minio/minio-go/v7 v7.0.49 @@ -58,6 +57,11 @@ require ( sigs.k8s.io/structured-merge-diff/v4 v4.2.3 ) +require ( + github.com/minio/kes-go v0.1.0 + golang.org/x/mod v0.10.0 +) + require ( aead.dev/mem v0.2.0 // indirect aead.dev/minisign v0.2.0 // indirect @@ -170,7 +174,6 @@ require ( go.uber.org/atomic v1.10.0 // indirect go.uber.org/multierr v1.9.0 // indirect go.uber.org/zap v1.24.0 // indirect - golang.org/x/mod v0.7.0 // indirect golang.org/x/sync v0.1.0 // indirect golang.org/x/sys v0.5.0 // indirect golang.org/x/term v0.5.0 // indirect diff --git a/go.sum b/go.sum index 3bf7fd218d0..9f5c1f60a2e 100644 --- a/go.sum +++ b/go.sum @@ -644,8 +644,8 @@ github.com/minio/filepath v1.0.0 h1:fvkJu1+6X+ECRA6G3+JJETj4QeAYO9sV43I79H8ubDY= github.com/minio/filepath v1.0.0/go.mod h1:/nRZA2ldl5z6jT9/KQuvZcQlxZIMQoFFQPvEXx9T/Bw= github.com/minio/highwayhash v1.0.2 h1:Aak5U0nElisjDCfPSG79Tgzkn2gl66NxOMspRrKnA/g= github.com/minio/highwayhash v1.0.2/go.mod h1:BQskDq+xkJ12lmlUUi7U0M5Swg3EWR+dLTk+kldvVxY= -github.com/minio/kes v0.22.3 h1:aSPW9uCMVaLax5POxvoQJxCU4MNo/KzMXA7WfmC/lRw= -github.com/minio/kes v0.22.3/go.mod h1:wnhmdwWX2rpurNPKn3yDFImg2wuc7j3e+IU5rVkR9UY= +github.com/minio/kes-go v0.1.0 h1:h201DyOYP5sTqajkxFGxmXz/kPbT8HQNX1uh3Yx2PFc= +github.com/minio/kes-go v0.1.0/go.mod h1:VorHLaIYis9/MxAHAtXN4d8PUMNKhIxTIlvFt0hBOEo= github.com/minio/madmin-go v1.6.6/go.mod h1:ATvkBOLiP3av4D++2v1UEHC/QzsGtgXD5kYvvRYzdKs= github.com/minio/madmin-go/v2 v2.0.14 h1:FJs34UMm1jmDj3rA75tZnZAVRSaeXCL6q0D4Twrwz0M= github.com/minio/madmin-go/v2 v2.0.14/go.mod h1:lFQ1Zzi30StjJtyIpVLhjoxn/uPS+0Wxw4MyuRlNkR0= @@ -978,8 +978,9 @@ golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.7.0 h1:LapD9S96VoQRhi/GrNTqeBJFrUjs5UHCAtTlgwA5oZA= golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.10.0 h1:lFO9qtOdlre5W1jxS3r/4szv2/6iXxScdzjoBMXNhYk= +golang.org/x/mod v0.10.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= diff --git a/kubectl-minio/go.mod b/kubectl-minio/go.mod index 768b4a6e473..f7ba603bd87 100644 --- a/kubectl-minio/go.mod +++ b/kubectl-minio/go.mod @@ -84,7 +84,7 @@ require ( github.com/yusufpapurcu/wmi v1.2.2 // indirect go.starlark.net v0.0.0-20220817180228-f738f5508c12 // indirect golang.org/x/crypto v0.6.0 // indirect - golang.org/x/mod v0.7.0 // indirect + golang.org/x/mod v0.10.0 // indirect golang.org/x/net v0.7.0 // indirect golang.org/x/oauth2 v0.5.0 // indirect golang.org/x/sync v0.1.0 // indirect diff --git a/kubectl-minio/go.sum b/kubectl-minio/go.sum index c749502d4dc..8e9dc1a8586 100644 --- a/kubectl-minio/go.sum +++ b/kubectl-minio/go.sum @@ -21,7 +21,6 @@ github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnht github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -247,8 +246,9 @@ golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.7.0 h1:LapD9S96VoQRhi/GrNTqeBJFrUjs5UHCAtTlgwA5oZA= golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.10.0 h1:lFO9qtOdlre5W1jxS3r/4szv2/6iXxScdzjoBMXNhYk= +golang.org/x/mod v0.10.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= diff --git a/pkg/kes/kes.go b/pkg/kes/kes.go index d957d51a426..2757ee98f72 100644 --- a/pkg/kes/kes.go +++ b/pkg/kes/kes.go @@ -22,12 +22,19 @@ import ( "errors" "time" - "github.com/minio/kes" + "gopkg.in/yaml.v2" + + "github.com/minio/kes-go" ) // Identity of KES to use type Identity = kes.Identity +// AdminIdentity of KES +type AdminIdentity struct { + Identity Identity `yaml:"identity,omitempty" json:"identity,omitempty"` +} + // TLSProxyHeader headers for proxy type TLSProxyHeader struct { ClientCert string `yaml:"cert,omitempty"` @@ -197,8 +204,8 @@ type Keys struct { Azure *Azure `yaml:"azure,omitempty" json:"azure,omitempty"` } -// ServerConfig holds the kes server config -type ServerConfig struct { +// ServerConfigV1 holds the kes server config +type ServerConfigV1 struct { Addr string `yaml:"address,omitempty" json:"address,omitempty"` Root Identity `yaml:"root,omitempty" json:"root,omitempty"` TLS TLS `yaml:"tls,omitempty" json:"tls,omitempty"` @@ -208,6 +215,38 @@ type ServerConfig struct { Keys Keys `yaml:"keys,omitempty" json:"keys,omitempty"` } +// PolicyV2 policy identities for KES Edge after release 2023-04-03T16-41-28Z +type PolicyV2 struct { + Allow []string `yaml:"allow,omitempty" json:"paths,omitempty"` + Deny []string `yaml:"deny,omitempty" json:"deny,omitempty"` + Identities []Identity `yaml:"identities,omitempty" json:"identities,omitempty"` +} + +// ExpiryV2 expiration Starting 2023-04-03T16-41-28Z +type ExpiryV2 struct { + Any time.Duration `yaml:"any,omitempty" json:"any,omitempty"` + Unused time.Duration `yaml:"unused,omitempty" json:"unused,omitempty"` + Offline time.Duration `yaml:"offline,omitempty" json:"offline,omitempty"` +} + +// CacheV2 expiry config Starting 2023-04-03T16-41-28Z +type CacheV2 struct { + Expiry *ExpiryV2 `yaml:"expiry,omitempty" json:"expiry,omitempty"` +} + +// ServerConfigV2 holds the kes server config +// Starting 2023-04-03T16-41-28Z "keys" field changed to "keystore" for Edge +// And Admin is required +type ServerConfigV2 struct { + Admin AdminIdentity `yaml:"admin,omitempty" json:"admin,omitempty"` + Addr string `yaml:"address,omitempty" json:"address,omitempty"` + TLS TLS `yaml:"tls,omitempty" json:"tls,omitempty"` + Policies map[string]PolicyV2 `yaml:"policy,omitempty" json:"policy,omitempty"` + Cache CacheV2 `yaml:"cache,omitempty" json:"cache,omitempty"` + Log Log `yaml:"log,omitempty" json:"log,omitempty"` + Keystore Keys `yaml:"keystore,omitempty" json:"keystore,omitempty"` +} + // ParseCertificate parses a certificate func ParseCertificate(cert []byte) (*x509.Certificate, error) { for { @@ -223,3 +262,13 @@ func ParseCertificate(cert []byte) (*x509.Certificate, error) { } return nil, errors.New("found no (non-CA) certificate in any PEM block") } + +// Marshal ServerConfigV1 +func (c ServerConfigV1) Marshal() ([]byte, error) { + return yaml.Marshal(c) +} + +// Marshal ServerConfigV2 +func (c ServerConfigV2) Marshal() ([]byte, error) { + return yaml.Marshal(c) +} diff --git a/web-app/src/screens/Console/Tenants/AddTenant/Steps/Encryption/AWSKMSAdd.tsx b/web-app/src/screens/Console/Tenants/AddTenant/Steps/Encryption/AWSKMSAdd.tsx index 7cc44ea613e..b69788c4105 100644 --- a/web-app/src/screens/Console/Tenants/AddTenant/Steps/Encryption/AWSKMSAdd.tsx +++ b/web-app/src/screens/Console/Tenants/AddTenant/Steps/Encryption/AWSKMSAdd.tsx @@ -145,6 +145,7 @@ const AWSKMSAdd = () => { cleanValidation("aws_endpoint"); }} label="Endpoint" + tooltip="Endpoint is the AWS SecretsManager endpoint. AWS SecretsManager endpoints have the following schema: secrestmanager[-fips]..amanzonaws.com" value={awsEndpoint} error={validationErrors["aws_endpoint"] || ""} required @@ -159,6 +160,7 @@ const AWSKMSAdd = () => { cleanValidation("aws_region"); }} label="Region" + tooltip="Region is the AWS region the SecretsManager is located" value={awsRegion} error={validationErrors["aws_region"] || ""} required @@ -172,6 +174,7 @@ const AWSKMSAdd = () => { updateField("awsKMSKey", e.target.value); }} label="KMS Key" + tooltip="KMSKey is the AWS-KMS key ID (CMK-ID) used to en/decrypt secrets managed by the SecretsManager. If empty, the default AWS KMS key is used" value={awsKMSKey} /> @@ -187,6 +190,7 @@ const AWSKMSAdd = () => { cleanValidation("aws_accessKey"); }} label="Access Key" + tooltip="AccessKey is the access key for authenticating to AWS" value={awsAccessKey} error={validationErrors["aws_accessKey"] || ""} required @@ -201,6 +205,7 @@ const AWSKMSAdd = () => { cleanValidation("aws_secretKey"); }} label="Secret Key" + tooltip="SecretKey is the secret key for authenticating to AWS" value={awsSecretKey} error={validationErrors["aws_secretKey"] || ""} required @@ -210,6 +215,7 @@ const AWSKMSAdd = () => { ) => { updateField("awsToken", e.target.value); }} diff --git a/web-app/src/screens/Console/Tenants/AddTenant/Steps/Encryption/AzureKMSAdd.tsx b/web-app/src/screens/Console/Tenants/AddTenant/Steps/Encryption/AzureKMSAdd.tsx index 29a28515b6b..1df92193517 100644 --- a/web-app/src/screens/Console/Tenants/AddTenant/Steps/Encryption/AzureKMSAdd.tsx +++ b/web-app/src/screens/Console/Tenants/AddTenant/Steps/Encryption/AzureKMSAdd.tsx @@ -140,6 +140,7 @@ const AzureKMSAdd = () => { cleanValidation("azure_endpoint"); }} label="Endpoint" + tooltip="Endpoint is the Azure KeyVault endpoint" value={azureEndpoint} error={validationErrors["azure_endpoint"] || ""} /> @@ -156,6 +157,7 @@ const AzureKMSAdd = () => { cleanValidation("azure_tenant_id"); }} label="Tenant ID" + tooltip="TenantID is the ID of the Azure KeyVault tenant" value={azureTenantID} error={validationErrors["azure_tenant_id"] || ""} /> @@ -169,6 +171,7 @@ const AzureKMSAdd = () => { cleanValidation("azure_client_id"); }} label="Client ID" + tooltip="ClientID is the ID of the client accessing Azure KeyVault" value={azureClientID} error={validationErrors["azure_client_id"] || ""} /> @@ -182,6 +185,7 @@ const AzureKMSAdd = () => { cleanValidation("azure_client_secret"); }} label="Client Secret" + tooltip="ClientSecret is the client secret accessing the Azure KeyVault" value={azureClientSecret} error={validationErrors["azure_client_secret"] || ""} /> diff --git a/web-app/src/screens/Console/Tenants/AddTenant/Steps/Encryption/GCPKMSAdd.tsx b/web-app/src/screens/Console/Tenants/AddTenant/Steps/Encryption/GCPKMSAdd.tsx index bf1aabc33f6..ced79dc837e 100644 --- a/web-app/src/screens/Console/Tenants/AddTenant/Steps/Encryption/GCPKMSAdd.tsx +++ b/web-app/src/screens/Console/Tenants/AddTenant/Steps/Encryption/GCPKMSAdd.tsx @@ -82,6 +82,7 @@ const GCPKMSAdd = () => { updateField("gcpProjectID", e.target.value); }} label="Project ID" + tooltip="ProjectID is the GCP project ID." value={gcpProjectID} /> @@ -93,6 +94,7 @@ const GCPKMSAdd = () => { updateField("gcpEndpoint", e.target.value); }} label="Endpoint" + tooltip="Endpoint is the GCP project ID. If empty defaults to: secretmanager.googleapis.com:443" value={gcpEndpoint} /> @@ -107,6 +109,7 @@ const GCPKMSAdd = () => { updateField("gcpClientEmail", e.target.value); }} label="Client Email" + tooltip="Is the Client email of the GCP service account used to access the SecretManager" value={gcpClientEmail} /> @@ -118,6 +121,7 @@ const GCPKMSAdd = () => { updateField("gcpClientID", e.target.value); }} label="Client ID" + tooltip="Is the Client ID of the GCP service account used to access the SecretManager" value={gcpClientID} /> @@ -129,6 +133,7 @@ const GCPKMSAdd = () => { updateField("gcpPrivateKeyID", e.target.value); }} label="Private Key ID" + tooltip="Is the private key ID of the GCP service account used to access the SecretManager" value={gcpPrivateKeyID} /> @@ -140,6 +145,7 @@ const GCPKMSAdd = () => { updateField("gcpPrivateKey", e.target.value); }} label="Private Key" + tooltip="Is the private key of the GCP service account used to access the SecretManager" value={gcpPrivateKey} /> diff --git a/web-app/src/screens/Console/Tenants/AddTenant/Steps/Encryption/GemaltoKMSAdd.tsx b/web-app/src/screens/Console/Tenants/AddTenant/Steps/Encryption/GemaltoKMSAdd.tsx index 0be51ef4aef..24b0f1e09e0 100644 --- a/web-app/src/screens/Console/Tenants/AddTenant/Steps/Encryption/GemaltoKMSAdd.tsx +++ b/web-app/src/screens/Console/Tenants/AddTenant/Steps/Encryption/GemaltoKMSAdd.tsx @@ -142,6 +142,7 @@ const GemaltoKMSAdd = () => { cleanValidation("gemalto_endpoint"); }} label="Endpoint" + tooltip="Endpoint is the endpoint to the KeySecure server" value={gemaltoEndpoint} error={validationErrors["gemalto_endpoint"] || ""} required @@ -165,6 +166,7 @@ const GemaltoKMSAdd = () => { cleanValidation("gemalto_token"); }} label="Token" + tooltip="Token is the refresh authentication token to access the KeySecure server" value={gemaltoToken} error={validationErrors["gemalto_token"] || ""} required @@ -179,6 +181,7 @@ const GemaltoKMSAdd = () => { cleanValidation("gemalto_domain"); }} label="Domain" + tooltip="Domain is the isolated namespace within the KeySecure server. If empty, defaults to the top-level / root domain" value={gemaltoDomain} error={validationErrors["gemalto_domain"] || ""} required diff --git a/web-app/src/screens/Console/Tenants/AddTenant/Steps/Encryption/VaultKMSAdd.tsx b/web-app/src/screens/Console/Tenants/AddTenant/Steps/Encryption/VaultKMSAdd.tsx index ef7fae3c2de..4955856accb 100644 --- a/web-app/src/screens/Console/Tenants/AddTenant/Steps/Encryption/VaultKMSAdd.tsx +++ b/web-app/src/screens/Console/Tenants/AddTenant/Steps/Encryption/VaultKMSAdd.tsx @@ -167,6 +167,7 @@ const VaultKMSAdd = () => { cleanValidation("vault_endpoint"); }} label="Endpoint" + tooltip="Endpoint is the Hashicorp Vault endpoint" value={vaultEndpoint} error={validationErrors["vault_endpoint"] || ""} required @@ -181,6 +182,7 @@ const VaultKMSAdd = () => { cleanValidation("vault_engine"); }} label="Engine" + tooltip="Engine is the Hashicorp Vault K/V engine path. If empty, defaults to 'kv'" value={vaultEngine} /> @@ -192,6 +194,7 @@ const VaultKMSAdd = () => { updateField("vaultNamespace", e.target.value); }} label="Namespace" + tooltip="Namespace is an optional Hashicorp Vault namespace. An empty namespace means no particular namespace is used." value={vaultNamespace} /> @@ -203,6 +206,7 @@ const VaultKMSAdd = () => { updateField("vaultPrefix", e.target.value); }} label="Prefix" + tooltip="Prefix is an optional prefix / directory within the K/V engine. If empty, keys will be stored at the K/V engine top level" value={vaultPrefix} /> @@ -218,6 +222,7 @@ const VaultKMSAdd = () => { updateField("vaultAppRoleEngine", e.target.value); }} label="Engine" + tooltip="AppRoleEngine is the AppRole authentication engine path. If empty, defaults to 'approle'" value={vaultAppRoleEngine} /> @@ -230,6 +235,7 @@ const VaultKMSAdd = () => { cleanValidation("vault_id"); }} label="AppRole ID" + tooltip="AppRoleSecret is the AppRole access secret for authenticating to Hashicorp Vault via the AppRole method" value={vaultId} error={validationErrors["vault_id"] || ""} required @@ -244,6 +250,7 @@ const VaultKMSAdd = () => { cleanValidation("vault_secret"); }} label="AppRole Secret" + tooltip="AppRoleSecret is the AppRole access secret for authenticating to Hashicorp Vault via the AppRole method" value={vaultSecret} error={validationErrors["vault_secret"] || ""} required diff --git a/web-app/src/screens/Console/Tenants/TenantDetails/TenantEncryption.tsx b/web-app/src/screens/Console/Tenants/TenantDetails/TenantEncryption.tsx index 91c696e3a3b..2b63657e8c3 100644 --- a/web-app/src/screens/Console/Tenants/TenantDetails/TenantEncryption.tsx +++ b/web-app/src/screens/Console/Tenants/TenantDetails/TenantEncryption.tsx @@ -16,7 +16,7 @@ import { ICertificateInfo, ITenantEncryptionResponse } from "../types"; import { Theme } from "@mui/material/styles"; -import { Button } from "mds"; +import { Button, WarnIcon } from "mds"; import createStyles from "@mui/styles/createStyles"; import withStyles from "@mui/styles/withStyles"; import { @@ -71,6 +71,18 @@ const styles = (theme: Theme) => ...formFieldStyles, ...modalBasic, ...wizardCommon, + warningBlock: { + color: "red", + fontSize: ".85rem", + margin: ".5rem 0 .5rem 0", + display: "flex", + alignItems: "center", + "& svg ": { + marginRight: ".3rem", + height: 16, + width: 16, + }, + }, }); const TenantEncryption = ({ classes }: ITenantEncryption) => { @@ -651,6 +663,14 @@ const TenantEncryption = ({ classes }: ITenantEncryption) => { {encryptionEnabled ? "Data will be encrypted using and external KMS" : "Current encrypted information will not be accessible"} + {encryptionEnabled && ( +
+ + + The content of the KES config secret will be overwritten. + +
+ )} } /> @@ -741,6 +761,7 @@ const TenantEncryption = ({ classes }: ITenantEncryption) => { }) } label="Endpoint" + tooltip="Endpoint is the Hashicorp Vault endpoint" value={vaultConfiguration?.endpoint || ""} error={validationErrors["vault_ping"] || ""} required @@ -757,6 +778,7 @@ const TenantEncryption = ({ classes }: ITenantEncryption) => { }) } label="Engine" + tooltip="Engine is the Hashicorp Vault K/V engine path. If empty, defaults to 'kv'" value={vaultConfiguration?.engine || ""} /> @@ -771,6 +793,7 @@ const TenantEncryption = ({ classes }: ITenantEncryption) => { }) } label="Namespace" + tooltip="Namespace is an optional Hashicorp Vault namespace. An empty namespace means no particular namespace is used." value={vaultConfiguration?.namespace || ""} /> @@ -785,6 +808,7 @@ const TenantEncryption = ({ classes }: ITenantEncryption) => { }) } label="Prefix" + tooltip="Prefix is an optional prefix / directory within the K/V engine. If empty, keys will be stored at the K/V engine top level" value={vaultConfiguration?.prefix || ""} /> @@ -792,124 +816,150 @@ const TenantEncryption = ({ classes }: ITenantEncryption) => { App Role - ) => - setVaultConfiguration({ - ...vaultConfiguration, - approle: { - ...vaultConfiguration?.approle, - engine: e.target.value, - }, - }) - } - label="Engine" - value={vaultConfiguration?.approle?.engine || ""} - /> - - - ) => - setVaultConfiguration({ - ...vaultConfiguration, - approle: { - ...vaultConfiguration?.approle, - id: e.target.value, - }, - }) - } - label="AppRole ID" - value={vaultConfiguration?.approle?.id || ""} - required - error={validationErrors["vault_id"] || ""} - overlayIcon={ - showVaultAppRoleID ? ( - - ) : ( - - ) - } - overlayAction={() => - setShowVaultAppRoleID(!showVaultAppRoleID) - } - /> +
+ + App Role + + + + ) => + setVaultConfiguration({ + ...vaultConfiguration, + approle: { + ...vaultConfiguration?.approle, + engine: e.target.value, + }, + }) + } + label="Engine" + tooltip="AppRoleEngine is the AppRole authentication engine path. If empty, defaults to 'approle'" + value={vaultConfiguration?.approle?.engine || ""} + /> + + + + ) => + setVaultConfiguration({ + ...vaultConfiguration, + approle: { + ...vaultConfiguration?.approle, + id: e.target.value, + }, + }) + } + label="AppRole ID" + tooltip="AppRoleSecret is the AppRole access secret for authenticating to Hashicorp Vault via the AppRole method" + value={vaultConfiguration?.approle?.id || ""} + required + error={validationErrors["vault_id"] || ""} + overlayIcon={ + showVaultAppRoleID ? ( + + ) : ( + + ) + } + overlayAction={() => + setShowVaultAppRoleID(!showVaultAppRoleID) + } + /> + + + + ) => + setVaultConfiguration({ + ...vaultConfiguration, + approle: { + ...vaultConfiguration?.approle, + secret: e.target.value, + }, + }) + } + label="AppRole Secret" + tooltip="AppRoleSecret is the AppRole access secret for authenticating to Hashicorp Vault via the AppRole method" + value={vaultConfiguration?.approle?.secret || ""} + required + error={validationErrors["vault_secret"] || ""} + overlayIcon={ + showVaultAppRoleSecret ? ( + + ) : ( + + ) + } + overlayAction={() => + setShowVaultAppRoleSecret(!showVaultAppRoleSecret) + } + /> + + + + ) => + setVaultConfiguration({ + ...vaultConfiguration, + approle: { + ...vaultConfiguration?.approle, + retry: e.target.value, + }, + }) + } + label="Retry (Seconds)" + error={validationErrors["vault_retry"] || ""} + value={vaultConfiguration?.approle?.retry || ""} + /> + +
- - ) => - setVaultConfiguration({ - ...vaultConfiguration, - approle: { - ...vaultConfiguration?.approle, - secret: e.target.value, - }, - }) - } - label="AppRole Secret" - value={vaultConfiguration?.approle?.secret || ""} - required - error={validationErrors["vault_secret"] || ""} - overlayIcon={ - showVaultAppRoleSecret ? ( - - ) : ( - - ) - } - overlayAction={() => - setShowVaultAppRoleSecret(!showVaultAppRoleSecret) - } - /> - - - ) => - setVaultConfiguration({ - ...vaultConfiguration, - approle: { - ...vaultConfiguration?.approle, - retry: e.target.value, - }, - }) - } - label="Retry (Seconds)" - error={validationErrors["vault_retry"] || ""} - value={vaultConfiguration?.approle?.retry || ""} - /> - - - Status - - - ) => - setVaultConfiguration({ - ...vaultConfiguration, - status: { - ...vaultConfiguration?.status, - ping: e.target.value, - }, - }) - } - label="Ping (Seconds)" - error={validationErrors["vault_ping"] || ""} - value={vaultConfiguration?.status?.ping || ""} - /> + +
+ + Status + + ) => + setVaultConfiguration({ + ...vaultConfiguration, + status: { + ...vaultConfiguration?.status, + ping: e.target.value, + }, + }) + } + label="Ping (Seconds)" + tooltip="controls how often to Vault health status is checked. If not set, defaults to 10s" + error={validationErrors["vault_ping"] || ""} + value={vaultConfiguration?.status?.ping || ""} + /> +
)} @@ -929,84 +979,103 @@ const TenantEncryption = ({ classes }: ITenantEncryption) => { }) } label="Endpoint" + tooltip="Endpoint is the Azure KeyVault endpoint" error={validationErrors["azure_endpoint"] || ""} value={azureConfiguration?.keyvault?.endpoint || ""} />
- Credentials - - - ) => - setAzureConfiguration({ - ...azureConfiguration, - keyvault: { - ...azureConfiguration?.keyvault, - credentials: { - ...azureConfiguration?.keyvault?.credentials, - tenant_id: e.target.value, - }, - }, - }) - } - label="Tenant ID" - value={ - azureConfiguration?.keyvault?.credentials - ?.tenant_id || "" - } - error={validationErrors["azure_tenant_id"] || ""} - /> - - - ) => - setAzureConfiguration({ - ...azureConfiguration, - keyvault: { - ...azureConfiguration?.keyvault, - credentials: { - ...azureConfiguration?.keyvault?.credentials, - client_id: e.target.value, - }, - }, - }) - } - label="Client ID" - value={ - azureConfiguration?.keyvault?.credentials - ?.client_id || "" - } - error={validationErrors["azure_client_id"] || ""} - /> - - - ) => - setAzureConfiguration({ - ...azureConfiguration, - keyvault: { - ...azureConfiguration?.keyvault, - credentials: { - ...azureConfiguration?.keyvault?.credentials, - client_secret: e.target.value, - }, - }, - }) - } - label="Client Secret" - value={ - azureConfiguration?.keyvault?.credentials - ?.client_secret || "" - } - error={validationErrors["azure_client_secret"] || ""} - /> +
+ + Credentials + + + + ) => + setAzureConfiguration({ + ...azureConfiguration, + keyvault: { + ...azureConfiguration?.keyvault, + credentials: { + ...azureConfiguration?.keyvault + ?.credentials, + tenant_id: e.target.value, + }, + }, + }) + } + label="Tenant ID" + tooltip="TenantID is the ID of the Azure KeyVault tenant" + value={ + azureConfiguration?.keyvault?.credentials + ?.tenant_id || "" + } + error={validationErrors["azure_tenant_id"] || ""} + /> + + + + ) => + setAzureConfiguration({ + ...azureConfiguration, + keyvault: { + ...azureConfiguration?.keyvault, + credentials: { + ...azureConfiguration?.keyvault + ?.credentials, + client_id: e.target.value, + }, + }, + }) + } + label="Client ID" + tooltip="ClientID is the ID of the client accessing Azure KeyVault" + value={ + azureConfiguration?.keyvault?.credentials + ?.client_id || "" + } + error={validationErrors["azure_client_id"] || ""} + /> + + + + ) => + setAzureConfiguration({ + ...azureConfiguration, + keyvault: { + ...azureConfiguration?.keyvault, + credentials: { + ...azureConfiguration?.keyvault + ?.credentials, + client_secret: e.target.value, + }, + }, + }) + } + label="Client Secret" + tooltip="ClientSecret is the client secret accessing the Azure KeyVault" + value={ + azureConfiguration?.keyvault?.credentials + ?.client_secret || "" + } + error={ + validationErrors["azure_client_secret"] || "" + } + /> + +
)} @@ -1026,6 +1095,7 @@ const TenantEncryption = ({ classes }: ITenantEncryption) => { }) } label="Project ID" + tooltip="ProjectID is the GCP project ID" value={gcpConfiguration?.secretmanager.project_id || ""} /> @@ -1043,103 +1113,124 @@ const TenantEncryption = ({ classes }: ITenantEncryption) => { }) } label="Endpoint" + tooltip="Endpoint is the GCP project ID. If empty defaults to: secretmanager.googleapis.com:443" value={gcpConfiguration?.secretmanager.endpoint || ""} /> - Credentials - - - ) => - setGCPConfiguration({ - ...gcpConfiguration, - secretmanager: { - ...gcpConfiguration?.secretmanager, - credentials: { - ...gcpConfiguration?.secretmanager.credentials, - client_email: e.target.value, - }, - }, - }) - } - label="Client Email" - value={ - gcpConfiguration?.secretmanager.credentials - ?.client_email || "" - } - /> - - - ) => - setGCPConfiguration({ - ...gcpConfiguration, - secretmanager: { - ...gcpConfiguration?.secretmanager, - credentials: { - ...gcpConfiguration?.secretmanager.credentials, - client_id: e.target.value, - }, - }, - }) - } - label="Client ID" - value={ - gcpConfiguration?.secretmanager.credentials - ?.client_id || "" - } - /> - - - ) => - setGCPConfiguration({ - ...gcpConfiguration, - secretmanager: { - ...gcpConfiguration?.secretmanager, - credentials: { - ...gcpConfiguration?.secretmanager.credentials, - private_key_id: e.target.value, - }, - }, - }) - } - label="Private Key ID" - value={ - gcpConfiguration?.secretmanager.credentials - ?.private_key_id || "" - } - /> - - - ) => - setGCPConfiguration({ - ...gcpConfiguration, - secretmanager: { - ...gcpConfiguration?.secretmanager, - credentials: { - ...gcpConfiguration?.secretmanager.credentials, - private_key: e.target.value, - }, - }, - }) - } - label="Private Key" - value={ - gcpConfiguration?.secretmanager.credentials - ?.private_key || "" - } - /> +
+ + Credentials + + + + ) => + setGCPConfiguration({ + ...gcpConfiguration, + secretmanager: { + ...gcpConfiguration?.secretmanager, + credentials: { + ...gcpConfiguration?.secretmanager + .credentials, + client_email: e.target.value, + }, + }, + }) + } + label="Client Email" + tooltip="Is the Client email of the GCP service account used to access the SecretManager" + value={ + gcpConfiguration?.secretmanager.credentials + ?.client_email || "" + } + /> + + + + ) => + setGCPConfiguration({ + ...gcpConfiguration, + secretmanager: { + ...gcpConfiguration?.secretmanager, + credentials: { + ...gcpConfiguration?.secretmanager + .credentials, + client_id: e.target.value, + }, + }, + }) + } + label="Client ID" + tooltip="Is the Client ID of the GCP service account used to access the SecretManager" + value={ + gcpConfiguration?.secretmanager.credentials + ?.client_id || "" + } + /> + + + + ) => + setGCPConfiguration({ + ...gcpConfiguration, + secretmanager: { + ...gcpConfiguration?.secretmanager, + credentials: { + ...gcpConfiguration?.secretmanager + .credentials, + private_key_id: e.target.value, + }, + }, + }) + } + label="Private Key ID" + tooltip="Is the private key ID of the GCP service account used to access the SecretManager" + value={ + gcpConfiguration?.secretmanager.credentials + ?.private_key_id || "" + } + /> + + + + ) => + setGCPConfiguration({ + ...gcpConfiguration, + secretmanager: { + ...gcpConfiguration?.secretmanager, + credentials: { + ...gcpConfiguration?.secretmanager + .credentials, + private_key: e.target.value, + }, + }, + }) + } + label="Private Key" + tooltip="Is the private key of the GCP service account used to access the SecretManager" + value={ + gcpConfiguration?.secretmanager.credentials + ?.private_key || "" + } + /> + +
)} @@ -1159,6 +1250,7 @@ const TenantEncryption = ({ classes }: ITenantEncryption) => { }) } label="Endpoint" + tooltip="Endpoint is the AWS SecretsManager endpoint. AWS SecretsManager endpoints have the following schema: secrestmanager[-fips]..amanzonaws.com" value={awsConfiguration?.secretsmanager?.endpoint || ""} required error={validationErrors["aws_endpoint"] || ""} @@ -1178,6 +1270,7 @@ const TenantEncryption = ({ classes }: ITenantEncryption) => { }) } label="Region" + tooltip="Region is the AWS region the SecretsManager is located" value={awsConfiguration?.secretsmanager?.region || ""} error={validationErrors["aws_region"] || ""} required @@ -1197,87 +1290,101 @@ const TenantEncryption = ({ classes }: ITenantEncryption) => { }) } label="KMS Key" + tooltip="KMSKey is the AWS-KMS key ID (CMK-ID) used to en/decrypt secrets managed by the SecretsManager. If empty, the default AWS KMS key is used" value={awsConfiguration?.secretsmanager?.kmskey || ""} /> - Credentials - - - ) => - setAWSConfiguration({ - ...awsConfiguration, - secretsmanager: { - ...awsConfiguration?.secretsmanager, - credentials: { - ...awsConfiguration?.secretsmanager - ?.credentials, - accesskey: e.target.value, - }, - }, - }) - } - label="Access Key" - value={ - awsConfiguration?.secretsmanager?.credentials - ?.accesskey || "" - } - error={validationErrors["aws_accessKey"] || ""} - required - /> - - - ) => - setAWSConfiguration({ - ...awsConfiguration, - secretsmanager: { - ...awsConfiguration?.secretsmanager, - credentials: { - ...awsConfiguration?.secretsmanager - ?.credentials, - secretkey: e.target.value, - }, - }, - }) - } - label="Secret Key" - value={ - awsConfiguration?.secretsmanager?.credentials - ?.secretkey || "" - } - error={validationErrors["aws_secretKey"] || ""} - required - /> - - - ) => - setAWSConfiguration({ - ...awsConfiguration, - secretsmanager: { - ...awsConfiguration?.secretsmanager, - credentials: { - ...awsConfiguration?.secretsmanager - ?.credentials, - token: e.target.value, - }, - }, - }) - } - label="Token" - value={ - awsConfiguration?.secretsmanager?.credentials - ?.token || "" - } - /> +
+ + Credentials + + + + ) => + setAWSConfiguration({ + ...awsConfiguration, + secretsmanager: { + ...awsConfiguration?.secretsmanager, + credentials: { + ...awsConfiguration?.secretsmanager + ?.credentials, + accesskey: e.target.value, + }, + }, + }) + } + label="Access Key" + tooltip="AccessKey is the access key for authenticating to AWS" + value={ + awsConfiguration?.secretsmanager?.credentials + ?.accesskey || "" + } + error={validationErrors["aws_accessKey"] || ""} + required + /> + + + + ) => + setAWSConfiguration({ + ...awsConfiguration, + secretsmanager: { + ...awsConfiguration?.secretsmanager, + credentials: { + ...awsConfiguration?.secretsmanager + ?.credentials, + secretkey: e.target.value, + }, + }, + }) + } + label="Secret Key" + tooltip="SecretKey is the secret key for authenticating to AWS" + value={ + awsConfiguration?.secretsmanager?.credentials + ?.secretkey || "" + } + error={validationErrors["aws_secretKey"] || ""} + required + /> + + + + ) => + setAWSConfiguration({ + ...awsConfiguration, + secretsmanager: { + ...awsConfiguration?.secretsmanager, + credentials: { + ...awsConfiguration?.secretsmanager + ?.credentials, + token: e.target.value, + }, + }, + }) + } + label="Token" + tooltip="SessionToken is an optional session token for authenticating to AWS when using STS" + value={ + awsConfiguration?.secretsmanager?.credentials + ?.token || "" + } + /> + +
)} @@ -1297,89 +1404,111 @@ const TenantEncryption = ({ classes }: ITenantEncryption) => { }) } label="Endpoint" + tooltip="Endpoint is the endpoint to the KeySecure server" value={gemaltoConfiguration?.keysecure?.endpoint || ""} error={validationErrors["gemalto_endpoint"] || ""} required /> - - Credentials - - - ) => - setGemaltoConfiguration({ - ...gemaltoConfiguration, - keysecure: { - ...gemaltoConfiguration?.keysecure, - credentials: { - ...gemaltoConfiguration?.keysecure?.credentials, - token: e.target.value, - }, - }, - }) - } - label="Token" - value={ - gemaltoConfiguration?.keysecure?.credentials?.token || - "" - } - error={validationErrors["gemalto_token"] || ""} - required - /> - - - ) => - setGemaltoConfiguration({ - ...gemaltoConfiguration, - keysecure: { - ...gemaltoConfiguration?.keysecure, - credentials: { - ...gemaltoConfiguration?.keysecure?.credentials, - domain: e.target.value, - }, - }, - }) - } - label="Domain" - value={ - gemaltoConfiguration?.keysecure?.credentials - ?.domain || "" - } - error={validationErrors["gemalto_domain"] || ""} - required - /> - - - ) => - setGemaltoConfiguration({ - ...gemaltoConfiguration, - keysecure: { - ...gemaltoConfiguration?.keysecure, - credentials: { - ...gemaltoConfiguration?.keysecure?.credentials, - retry: e.target.value, - }, - }, - }) - } - label="Retry (seconds)" - value={ - gemaltoConfiguration?.keysecure?.credentials?.retry || - "" - } - error={validationErrors["gemalto_retry"] || ""} - /> + +
+ + Credentials + + + + ) => + setGemaltoConfiguration({ + ...gemaltoConfiguration, + keysecure: { + ...gemaltoConfiguration?.keysecure, + credentials: { + ...gemaltoConfiguration?.keysecure + ?.credentials, + token: e.target.value, + }, + }, + }) + } + label="Token" + tooltip="Token is the refresh authentication token to access the KeySecure server" + value={ + gemaltoConfiguration?.keysecure?.credentials + ?.token || "" + } + error={validationErrors["gemalto_token"] || ""} + required + /> + + + + ) => + setGemaltoConfiguration({ + ...gemaltoConfiguration, + keysecure: { + ...gemaltoConfiguration?.keysecure, + credentials: { + ...gemaltoConfiguration?.keysecure + ?.credentials, + domain: e.target.value, + }, + }, + }) + } + label="Domain" + tooltip="Domain is the isolated namespace within the KeySecure server. If empty, defaults to the top-level / root domain" + value={ + gemaltoConfiguration?.keysecure?.credentials + ?.domain || "" + } + error={validationErrors["gemalto_domain"] || ""} + required + /> + + + + ) => + setGemaltoConfiguration({ + ...gemaltoConfiguration, + keysecure: { + ...gemaltoConfiguration?.keysecure, + credentials: { + ...gemaltoConfiguration?.keysecure + ?.credentials, + retry: e.target.value, + }, + }, + }) + } + label="Retry (seconds)" + value={ + gemaltoConfiguration?.keysecure?.credentials + ?.retry || "" + } + error={validationErrors["gemalto_retry"] || ""} + /> + +
)} @@ -1601,6 +1730,7 @@ const TenantEncryption = ({ classes }: ITenantEncryption) => { setImage(e.target.value) } label="Image" + tooltip="KES container image" placeholder="minio/kes:2023-04-18T19-36-09Z" value={image} /> @@ -1615,6 +1745,7 @@ const TenantEncryption = ({ classes }: ITenantEncryption) => { setReplicas(e.target.value) } label="Replicas" + tooltip="Numer of KES pod replicas" value={replicas} required error={validationErrors["replicas"] || ""}