diff --git a/cmd/admin-bucket-handlers.go b/cmd/admin-bucket-handlers.go index c3d2c7393..c88356dc1 100644 --- a/cmd/admin-bucket-handlers.go +++ b/cmd/admin-bucket-handlers.go @@ -31,7 +31,7 @@ import ( jsoniter "github.com/json-iterator/go" "github.com/klauspost/compress/zip" - "github.com/minio/kes" + "github.com/minio/kes-go" "github.com/minio/madmin-go/v2" "github.com/minio/minio-go/v7/pkg/tags" "github.com/minio/minio/internal/bucket/lifecycle" diff --git a/cmd/admin-handler-utils.go b/cmd/admin-handler-utils.go index fc2c2c78f..706a52463 100644 --- a/cmd/admin-handler-utils.go +++ b/cmd/admin-handler-utils.go @@ -23,7 +23,7 @@ import ( "fmt" "net/http" - "github.com/minio/kes" + "github.com/minio/kes-go" "github.com/minio/madmin-go/v2" "github.com/minio/minio/internal/auth" "github.com/minio/minio/internal/config" diff --git a/cmd/bucket-encryption-handlers.go b/cmd/bucket-encryption-handlers.go index c837d3208..a3a657055 100644 --- a/cmd/bucket-encryption-handlers.go +++ b/cmd/bucket-encryption-handlers.go @@ -25,7 +25,7 @@ import ( "io" "net/http" - "github.com/minio/kes" + "github.com/minio/kes-go" "github.com/minio/madmin-go/v2" "github.com/minio/minio/internal/kms" "github.com/minio/minio/internal/logger" diff --git a/cmd/common-main.go b/cmd/common-main.go index 9ec492f48..b415fc5d9 100644 --- a/cmd/common-main.go +++ b/cmd/common-main.go @@ -51,7 +51,7 @@ import ( consoleCerts "github.com/minio/console/pkg/certs" "github.com/minio/console/restapi" "github.com/minio/console/restapi/operations" - "github.com/minio/kes" + "github.com/minio/kes-go" "github.com/minio/madmin-go/v2" "github.com/minio/minio-go/v7" "github.com/minio/minio-go/v7/pkg/credentials" @@ -795,6 +795,15 @@ func handleKMSConfig() { GlobalKMS = KMS } if env.IsSet(config.EnvKESEndpoint) { + if env.IsSet(config.EnvKESAPIKey) { + if env.IsSet(config.EnvKESClientKey) { + logger.Fatal(errors.New("ambigious KMS configuration"), fmt.Sprintf("The environment contains %q as well as %q", config.EnvKESAPIKey, config.EnvKESClientKey)) + } + if env.IsSet(config.EnvKESClientCert) { + logger.Fatal(errors.New("ambigious KMS configuration"), fmt.Sprintf("The environment contains %q as well as %q", config.EnvKESAPIKey, config.EnvKESClientCert)) + } + } + var endpoints []string for _, endpoint := range strings.Split(env.Get(config.EnvKESEndpoint, ""), ",") { if strings.TrimSpace(endpoint) == "" { @@ -817,62 +826,77 @@ func handleKMSConfig() { logger.Fatal(err, fmt.Sprintf("Unable to load X.509 root CAs for KES from %q", env.Get(config.EnvKESServerCA, globalCertsCADir.Get()))) } - loadX509KeyPair := func(certFile, keyFile string) (tls.Certificate, error) { - // Manually load the certificate and private key into memory. - // We need to check whether the private key is encrypted, and - // if so, decrypt it using the user-provided password. - certBytes, err := os.ReadFile(certFile) + var kmsConf kms.Config + if env.IsSet(config.EnvKESAPIKey) { + key, err := kes.ParseAPIKey(env.Get(config.EnvKESAPIKey, "")) if err != nil { - return tls.Certificate{}, fmt.Errorf("Unable to load KES client certificate as specified by the shell environment: %v", err) + logger.Fatal(err, fmt.Sprintf("Failed to parse KES API key from %q", env.Get(config.EnvKESAPIKey, ""))) } - keyBytes, err := os.ReadFile(keyFile) - if err != nil { - return tls.Certificate{}, fmt.Errorf("Unable to load KES client private key as specified by the shell environment: %v", err) + kmsConf = kms.Config{ + Endpoints: endpoints, + Enclave: env.Get(config.EnvKESEnclave, ""), + DefaultKeyID: env.Get(config.EnvKESKeyName, ""), + APIKey: key, + RootCAs: rootCAs, } - privateKeyPEM, rest := pem.Decode(bytes.TrimSpace(keyBytes)) - if len(rest) != 0 { - return tls.Certificate{}, errors.New("Unable to load KES client private key as specified by the shell environment: private key contains additional data") - } - if x509.IsEncryptedPEMBlock(privateKeyPEM) { - keyBytes, err = x509.DecryptPEMBlock(privateKeyPEM, []byte(env.Get(config.EnvKESClientPassword, ""))) + } else { + loadX509KeyPair := func(certFile, keyFile string) (tls.Certificate, error) { + // Manually load the certificate and private key into memory. + // We need to check whether the private key is encrypted, and + // if so, decrypt it using the user-provided password. + certBytes, err := os.ReadFile(certFile) if err != nil { - return tls.Certificate{}, fmt.Errorf("Unable to decrypt KES client private key as specified by the shell environment: %v", err) + return tls.Certificate{}, fmt.Errorf("Unable to load KES client certificate as specified by the shell environment: %v", err) } - keyBytes = pem.EncodeToMemory(&pem.Block{Type: privateKeyPEM.Type, Bytes: keyBytes}) + keyBytes, err := os.ReadFile(keyFile) + if err != nil { + return tls.Certificate{}, fmt.Errorf("Unable to load KES client private key as specified by the shell environment: %v", err) + } + privateKeyPEM, rest := pem.Decode(bytes.TrimSpace(keyBytes)) + if len(rest) != 0 { + return tls.Certificate{}, errors.New("Unable to load KES client private key as specified by the shell environment: private key contains additional data") + } + if x509.IsEncryptedPEMBlock(privateKeyPEM) { + keyBytes, err = x509.DecryptPEMBlock(privateKeyPEM, []byte(env.Get(config.EnvKESClientPassword, ""))) + if err != nil { + return tls.Certificate{}, fmt.Errorf("Unable to decrypt KES client private key as specified by the shell environment: %v", err) + } + keyBytes = pem.EncodeToMemory(&pem.Block{Type: privateKeyPEM.Type, Bytes: keyBytes}) + } + certificate, err := tls.X509KeyPair(certBytes, keyBytes) + if err != nil { + return tls.Certificate{}, fmt.Errorf("Unable to load KES client certificate as specified by the shell environment: %v", err) + } + return certificate, nil } - certificate, err := tls.X509KeyPair(certBytes, keyBytes) + + reloadCertEvents := make(chan tls.Certificate, 1) + certificate, err := certs.NewCertificate(env.Get(config.EnvKESClientCert, ""), env.Get(config.EnvKESClientKey, ""), loadX509KeyPair) if err != nil { - return tls.Certificate{}, fmt.Errorf("Unable to load KES client certificate as specified by the shell environment: %v", err) + logger.Fatal(err, "Failed to load KES client certificate") + } + certificate.Watch(context.Background(), 15*time.Minute, syscall.SIGHUP) + certificate.Notify(reloadCertEvents) + + kmsConf = kms.Config{ + Endpoints: endpoints, + Enclave: env.Get(config.EnvKESEnclave, ""), + DefaultKeyID: env.Get(config.EnvKESKeyName, ""), + Certificate: certificate, + ReloadCertEvents: reloadCertEvents, + RootCAs: rootCAs, } - return certificate, nil } - reloadCertEvents := make(chan tls.Certificate, 1) - certificate, err := certs.NewCertificate(env.Get(config.EnvKESClientCert, ""), env.Get(config.EnvKESClientKey, ""), loadX509KeyPair) - if err != nil { - logger.Fatal(err, "Failed to load KES client certificate") - } - certificate.Watch(context.Background(), 15*time.Minute, syscall.SIGHUP) - certificate.Notify(reloadCertEvents) - - defaultKeyID := env.Get(config.EnvKESKeyName, "") - KMS, err := kms.NewWithConfig(kms.Config{ - Endpoints: endpoints, - Enclave: env.Get(config.EnvKESEnclave, ""), - DefaultKeyID: defaultKeyID, - Certificate: certificate, - ReloadCertEvents: reloadCertEvents, - RootCAs: rootCAs, - }) + KMS, err := kms.NewWithConfig(kmsConf) if err != nil { logger.Fatal(err, "Unable to initialize a connection to KES as specified by the shell environment") } - // We check that the default key ID exists or try to create it otherwise. // This implicitly checks that we can communicate to KES. We don't treat // a policy error as failure condition since MinIO may not have the permission // to create keys - just to generate/decrypt data encryption keys. - if err = KMS.CreateKey(context.Background(), defaultKeyID); err != nil && !errors.Is(err, kes.ErrKeyExists) && !errors.Is(err, kes.ErrNotAllowed) { + if err = KMS.CreateKey(context.Background(), env.Get(config.EnvKESKeyName, "")); err != nil && !errors.Is(err, kes.ErrKeyExists) && !errors.Is(err, kes.ErrNotAllowed) { logger.Fatal(err, "Unable to initialize a connection to KES as specified by the shell environment") } GlobalKMS = KMS diff --git a/cmd/encryption-v1.go b/cmd/encryption-v1.go index 0e3c928cf..c4045ad18 100644 --- a/cmd/encryption-v1.go +++ b/cmd/encryption-v1.go @@ -34,7 +34,7 @@ import ( "strconv" "strings" - "github.com/minio/kes" + "github.com/minio/kes-go" "github.com/minio/minio/internal/crypto" "github.com/minio/minio/internal/etag" "github.com/minio/minio/internal/fips" diff --git a/cmd/kms-handlers.go b/cmd/kms-handlers.go index 12fa87adc..ba60ca13d 100644 --- a/cmd/kms-handlers.go +++ b/cmd/kms-handlers.go @@ -23,7 +23,7 @@ import ( "net/http" "time" - "github.com/minio/kes" + "github.com/minio/kes-go" "github.com/minio/madmin-go/v2" "github.com/minio/minio/internal/kms" "github.com/minio/minio/internal/logger" diff --git a/cmd/metrics-v2.go b/cmd/metrics-v2.go index 810260bd3..0207358f3 100644 --- a/cmd/metrics-v2.go +++ b/cmd/metrics-v2.go @@ -27,7 +27,7 @@ import ( "sync/atomic" "time" - "github.com/minio/kes" + "github.com/minio/kes-go" "github.com/minio/madmin-go/v2" "github.com/minio/minio/internal/bucket/lifecycle" "github.com/minio/minio/internal/logger" diff --git a/go.mod b/go.mod index d154e4131..84d880054 100644 --- a/go.mod +++ b/go.mod @@ -47,7 +47,7 @@ require ( github.com/minio/csvparser v1.0.0 github.com/minio/dperf v0.4.2 github.com/minio/highwayhash v1.0.2 - github.com/minio/kes v0.22.3 + github.com/minio/kes-go v0.1.0 github.com/minio/madmin-go/v2 v2.0.11 github.com/minio/minio-go/v7 v7.0.47 github.com/minio/mux v1.9.0 diff --git a/go.sum b/go.sum index 43e97ae0c..9aab3e91b 100644 --- a/go.sum +++ b/go.sum @@ -233,8 +233,8 @@ github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de/go.mod h1:DCaWoU github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= +github.com/armon/go-metrics v0.0.0-20190430140413-ec5e00d3c878 h1:EFSB7Zo9Eg91v7MJPVsifUysc/wPdN+NOnVe6bWbdBM= github.com/armon/go-metrics v0.0.0-20190430140413-ec5e00d3c878/go.mod h1:3AMJUQhVx52RsWOnlkpikZr01T/yAVN2gn0861vByNg= -github.com/armon/go-metrics v0.3.9 h1:O2sNqxBdvq8Eq5xmzljcYzAORli6RWCvEym4cJf9m18= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d h1:Byv0BzEl3/e6D5CLfI0j/7hiIEtvGVFPCZ7Ei2oq8iQ= @@ -584,8 +584,8 @@ github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtng github.com/hashicorp/go-hclog v0.9.1/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= github.com/hashicorp/go-hclog v1.1.0 h1:QsGcniKx5/LuX2eYoeL+Np3UKYPNaN7YKpTh29h8rbw= github.com/hashicorp/go-hclog v1.1.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= +github.com/hashicorp/go-immutable-radix v1.0.0 h1:AKDB1HM5PWEA7i4nhcpwOrO2byshxBjXVn/J/3+z5/0= github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= -github.com/hashicorp/go-immutable-radix v1.3.1 h1:DKHmCUm2hRBK510BaiZlwvpD40f8bJFeZnpfm2KLowc= github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= github.com/hashicorp/go-msgpack v0.5.5/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= github.com/hashicorp/go-msgpack v1.1.5 h1:9byZdVjKTe5mce63pRVNP1L7UAmdHOTEMGehn6KvJWs= @@ -772,8 +772,8 @@ github.com/minio/filepath v1.0.0/go.mod h1:/nRZA2ldl5z6jT9/KQuvZcQlxZIMQoFFQPvEX github.com/minio/highwayhash v1.0.1/go.mod h1:BQskDq+xkJ12lmlUUi7U0M5Swg3EWR+dLTk+kldvVxY= 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.11 h1:Ct905UAMJ43EAwKCi8xy5PzWPWyYL5YCQ441E9LYXTA= github.com/minio/madmin-go/v2 v2.0.11/go.mod h1:5aFi/VLWBHC2DEFfGIlUmAeJhaF4ZAjuYpEWZFU14Zw= diff --git a/internal/config/constants.go b/internal/config/constants.go index 1bdf4b8d3..923ff15e0 100644 --- a/internal/config/constants.go +++ b/internal/config/constants.go @@ -69,13 +69,14 @@ const ( EnvKMSSecretKey = "MINIO_KMS_SECRET_KEY" EnvKMSSecretKeyFile = "MINIO_KMS_SECRET_KEY_FILE" - EnvKESEndpoint = "MINIO_KMS_KES_ENDPOINT" - EnvKESEnclave = "MINIO_KMS_KES_ENCLAVE" - EnvKESKeyName = "MINIO_KMS_KES_KEY_NAME" - EnvKESClientKey = "MINIO_KMS_KES_KEY_FILE" - EnvKESClientPassword = "MINIO_KMS_KES_KEY_PASSWORD" - EnvKESClientCert = "MINIO_KMS_KES_CERT_FILE" - EnvKESServerCA = "MINIO_KMS_KES_CAPATH" + EnvKESEndpoint = "MINIO_KMS_KES_ENDPOINT" // One or multiple KES endpoints, separated by ',' + EnvKESEnclave = "MINIO_KMS_KES_ENCLAVE" // Optional "namespace" within a KES cluster - not required for stateless KES + EnvKESKeyName = "MINIO_KMS_KES_KEY_NAME" // The default key name used for IAM data and when no key ID is specified on a bucket + EnvKESAPIKey = "MINIO_KMS_KES_API_KEY" // Access credential for KES - API keys and private key / certificate are mutually exclusive + EnvKESClientKey = "MINIO_KMS_KES_KEY_FILE" // Path to TLS private key for authenticating to KES with mTLS - usually prefer API keys + EnvKESClientPassword = "MINIO_KMS_KES_KEY_PASSWORD" // Optional password to decrypt an encrypt TLS private key + EnvKESClientCert = "MINIO_KMS_KES_CERT_FILE" // Path to TLS certificate for authenticating to KES with mTLS - usually prefer API keys + EnvKESServerCA = "MINIO_KMS_KES_CAPATH" // Path to file/directory containing CA certificates to verify the KES server certificate EnvEndpoints = "MINIO_ENDPOINTS" // legacy EnvWorm = "MINIO_WORM" // legacy diff --git a/internal/kms/identity-manager.go b/internal/kms/identity-manager.go index 0c99bf705..031f0ba77 100644 --- a/internal/kms/identity-manager.go +++ b/internal/kms/identity-manager.go @@ -20,7 +20,7 @@ package kms import ( "context" - "github.com/minio/kes" + "github.com/minio/kes-go" ) // IdentityManager is the generic interface that handles KMS identity operations diff --git a/internal/kms/kes.go b/internal/kms/kes.go index 13c27ad58..81ed1e4c2 100644 --- a/internal/kms/kes.go +++ b/internal/kms/kes.go @@ -26,7 +26,7 @@ import ( "strings" "sync" - "github.com/minio/kes" + "github.com/minio/kes-go" "github.com/minio/pkg/certs" ) @@ -52,6 +52,11 @@ type Config struct { // a cryptographic operation. DefaultKeyID string + // APIKey is an credential provided by env. var. + // to authenticate to a KES server. Either an + // API key or a client certificate must be specified. + APIKey kes.APIKey + // Certificate is the client TLS certificate // to authenticate to KMS via mTLS. Certificate *certs.Certificate @@ -74,12 +79,26 @@ func NewWithConfig(config Config) (KMS, error) { endpoints := make([]string, len(config.Endpoints)) // Copy => avoid being affect by any changes to the original slice copy(endpoints, config.Endpoints) - client := kes.NewClientWithConfig("", &tls.Config{ - MinVersion: tls.VersionTLS12, - Certificates: []tls.Certificate{config.Certificate.Get()}, - RootCAs: config.RootCAs, - ClientSessionCache: tls.NewLRUClientSessionCache(tlsClientSessionCacheSize), - }) + var client *kes.Client + if config.APIKey != nil { + cert, err := kes.GenerateCertificate(config.APIKey) + if err != nil { + return nil, err + } + client = kes.NewClientWithConfig("", &tls.Config{ + MinVersion: tls.VersionTLS12, + Certificates: []tls.Certificate{cert}, + RootCAs: config.RootCAs, + ClientSessionCache: tls.NewLRUClientSessionCache(tlsClientSessionCacheSize), + }) + } else { + client = kes.NewClientWithConfig("", &tls.Config{ + MinVersion: tls.VersionTLS12, + Certificates: []tls.Certificate{config.Certificate.Get()}, + RootCAs: config.RootCAs, + ClientSessionCache: tls.NewLRUClientSessionCache(tlsClientSessionCacheSize), + }) + } client.Endpoints = endpoints var bulkAvailable bool @@ -101,6 +120,9 @@ func NewWithConfig(config Config) (KMS, error) { bulkAvailable: bulkAvailable, } go func() { + if config.Certificate == nil || config.ReloadCertEvents == nil { + return + } for { var prevCertificate tls.Certificate certificate, ok := <-config.ReloadCertEvents diff --git a/internal/kms/key-manager.go b/internal/kms/key-manager.go index b63c5e0c3..f02981bb2 100644 --- a/internal/kms/key-manager.go +++ b/internal/kms/key-manager.go @@ -20,7 +20,7 @@ package kms import ( "context" - "github.com/minio/kes" + "github.com/minio/kes-go" ) // KeyManager is the generic interface that handles KMS key operations diff --git a/internal/kms/kms.go b/internal/kms/kms.go index 954432c9f..a2efd64d6 100644 --- a/internal/kms/kms.go +++ b/internal/kms/kms.go @@ -23,7 +23,7 @@ import ( "encoding/json" jsoniter "github.com/json-iterator/go" - "github.com/minio/kes" + "github.com/minio/kes-go" ) // KMS is the generic interface that abstracts over diff --git a/internal/kms/policy-manager.go b/internal/kms/policy-manager.go index d4c872632..6e2cbca2a 100644 --- a/internal/kms/policy-manager.go +++ b/internal/kms/policy-manager.go @@ -20,7 +20,7 @@ package kms import ( "context" - "github.com/minio/kes" + "github.com/minio/kes-go" ) // PolicyManager is the generic interface that handles KMS policy] operations diff --git a/internal/kms/single-key.go b/internal/kms/single-key.go index 95b6e9829..bfd8aceca 100644 --- a/internal/kms/single-key.go +++ b/internal/kms/single-key.go @@ -33,7 +33,7 @@ import ( "golang.org/x/crypto/chacha20" "golang.org/x/crypto/chacha20poly1305" - "github.com/minio/kes" + "github.com/minio/kes-go" "github.com/minio/minio/internal/hash/sha256" ) diff --git a/internal/kms/status-manager.go b/internal/kms/status-manager.go index 45bc57f26..533eb77a6 100644 --- a/internal/kms/status-manager.go +++ b/internal/kms/status-manager.go @@ -20,7 +20,7 @@ package kms import ( "context" - "github.com/minio/kes" + "github.com/minio/kes-go" ) // StatusManager is the generic interface that handles KMS status operations