crypto: allow multiple KES endpoints (#10383)

This commit addresses a maintenance / automation problem when MinIO-KES
is deployed on bare-metal. In orchestrated env. the orchestrator (K8S)
will make sure that `n` KES servers (IPs) are available via the same DNS
name. There it is sufficient to provide just one endpoint.
master
Andreas Auernhammer 4 years ago committed by GitHub
parent ba8a8ad818
commit 18725679c4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 4
      cmd/admin-handlers.go
  2. 30
      cmd/crypto/config.go
  3. 38
      cmd/crypto/kes.go
  4. 12
      cmd/crypto/kms.go
  5. 1
      cmd/crypto/retry.go
  6. 8
      cmd/crypto/vault.go

@ -1582,12 +1582,12 @@ func fetchVaultStatus(cfg config.Config) madmin.Vault {
keyID := GlobalKMS.DefaultKeyID() keyID := GlobalKMS.DefaultKeyID()
kmsInfo := GlobalKMS.Info() kmsInfo := GlobalKMS.Info()
if kmsInfo.Endpoint == "" { if len(kmsInfo.Endpoints) == 0 {
vault.Status = "KMS configured using master key" vault.Status = "KMS configured using master key"
return vault return vault
} }
if err := checkConnection(kmsInfo.Endpoint, 15*time.Second); err != nil { if err := checkConnection(kmsInfo.Endpoints[0], 15*time.Second); err != nil {
vault.Status = "offline" vault.Status = "offline"
} else { } else {
vault.Status = "online" vault.Status = "online"

@ -16,11 +16,14 @@ package crypto
import ( import (
"errors" "errors"
"math/rand"
"net/http" "net/http"
"reflect" "reflect"
"strconv" "strconv"
"strings"
"github.com/minio/minio/cmd/config" "github.com/minio/minio/cmd/config"
"github.com/minio/minio/pkg/ellipses"
"github.com/minio/minio/pkg/env" "github.com/minio/minio/pkg/env"
xnet "github.com/minio/minio/pkg/net" xnet "github.com/minio/minio/pkg/net"
) )
@ -167,7 +170,8 @@ const (
const ( const (
// EnvKMSKesEndpoint is the environment variable used to specify // EnvKMSKesEndpoint is the environment variable used to specify
// the kes server HTTPS endpoint. // one or multiple KES server HTTPS endpoints. The individual
// endpoints should be separated by ','.
EnvKMSKesEndpoint = "MINIO_KMS_KES_ENDPOINT" EnvKMSKesEndpoint = "MINIO_KMS_KES_ENDPOINT"
// EnvKMSKesKeyFile is the environment variable used to specify // EnvKMSKesKeyFile is the environment variable used to specify
@ -216,16 +220,30 @@ func LookupKesConfig(kvs config.KVS) (KesConfig, error) {
kesCfg := KesConfig{} kesCfg := KesConfig{}
endpointStr := env.Get(EnvKMSKesEndpoint, kvs.Get(KMSKesEndpoint)) endpointStr := env.Get(EnvKMSKesEndpoint, kvs.Get(KMSKesEndpoint))
if endpointStr != "" { var endpoints []string
// Lookup kes configuration & overwrite config entry if ENV var is present for _, endpoint := range strings.Split(endpointStr, ",") {
endpoint, err := xnet.ParseHTTPURL(endpointStr) if !ellipses.HasEllipses(endpoint) {
endpoints = append(endpoints, endpoint)
continue
}
pattern, err := ellipses.FindEllipsesPatterns(endpoint)
if err != nil { if err != nil {
return kesCfg, err return kesCfg, err
} }
endpointStr = endpoint.String() for _, p := range pattern {
endpoints = append(endpoints, p.Expand()...)
}
} }
kesCfg.Endpoint = endpointStr randNum := rand.Intn(len(endpoints) + 1) // We add 1 b/c len(endpoints) may be 0: See: rand.Intn docs
kesCfg.Endpoint = make([]string, len(endpoints))
for i, endpoint := range endpoints {
endpoint, err := xnet.ParseHTTPURL(endpoint)
if err != nil {
return kesCfg, err
}
kesCfg.Endpoint[(randNum+i)%len(endpoints)] = endpoint.String()
}
kesCfg.KeyFile = env.Get(EnvKMSKesKeyFile, kvs.Get(KMSKesKeyFile)) kesCfg.KeyFile = env.Get(EnvKMSKesKeyFile, kvs.Get(KMSKesKeyFile))
kesCfg.CertFile = env.Get(EnvKMSKesCertFile, kvs.Get(KMSKesCertFile)) kesCfg.CertFile = env.Get(EnvKMSKesCertFile, kvs.Get(KMSKesCertFile))
kesCfg.CAPath = env.Get(EnvKMSKesCAPath, kvs.Get(KMSKesCAPath)) kesCfg.CAPath = env.Get(EnvKMSKesCAPath, kvs.Get(KMSKesCAPath))

@ -46,8 +46,8 @@ var ErrKESKeyExists = NewKESError(http.StatusBadRequest, "key does already exist
type KesConfig struct { type KesConfig struct {
Enabled bool Enabled bool
// The kes server endpoint. // The KES server endpoints.
Endpoint string Endpoint []string
// The path to the TLS private key used // The path to the TLS private key used
// by MinIO to authenticate to the kes // by MinIO to authenticate to the kes
@ -86,7 +86,7 @@ type KesConfig struct {
// Verify verifies if the kes configuration is correct // Verify verifies if the kes configuration is correct
func (k KesConfig) Verify() (err error) { func (k KesConfig) Verify() (err error) {
switch { switch {
case k.Endpoint == "": case len(k.Endpoint) == 0:
err = Errorf("crypto: missing kes endpoint") err = Errorf("crypto: missing kes endpoint")
case k.CertFile == "": case k.CertFile == "":
err = Errorf("crypto: missing cert file") err = Errorf("crypto: missing cert file")
@ -101,7 +101,7 @@ func (k KesConfig) Verify() (err error) {
type kesService struct { type kesService struct {
client *kesClient client *kesClient
endpoint string endpoints []string
defaultKeyID string defaultKeyID string
} }
@ -141,12 +141,12 @@ func NewKes(cfg KesConfig) (KMS, error) {
return &kesService{ return &kesService{
client: &kesClient{ client: &kesClient{
addr: cfg.Endpoint, endpoints: cfg.Endpoint,
httpClient: http.Client{ httpClient: http.Client{
Transport: cfg.Transport, Transport: cfg.Transport,
}, },
}, },
endpoint: cfg.Endpoint, endpoints: cfg.Endpoint,
defaultKeyID: cfg.DefaultKeyID, defaultKeyID: cfg.DefaultKeyID,
}, nil }, nil
} }
@ -163,9 +163,9 @@ func (kes *kesService) DefaultKeyID() string {
// method. // method.
func (kes *kesService) Info() KMSInfo { func (kes *kesService) Info() KMSInfo {
return KMSInfo{ return KMSInfo{
Endpoint: kes.endpoint, Endpoints: kes.endpoints,
Name: kes.DefaultKeyID(), Name: kes.DefaultKeyID(),
AuthType: "TLS", AuthType: "TLS",
} }
} }
@ -221,7 +221,7 @@ func (kes *kesService) UnsealKey(keyID string, sealedKey []byte, ctx Context) (k
// • GenerateDataKey (API: /v1/key/generate/) // • GenerateDataKey (API: /v1/key/generate/)
// • DecryptDataKey (API: /v1/key/decrypt/) // • DecryptDataKey (API: /v1/key/decrypt/)
type kesClient struct { type kesClient struct {
addr string endpoints []string
httpClient http.Client httpClient http.Client
} }
@ -232,8 +232,8 @@ type kesClient struct {
// application does not have the cryptographic key at // application does not have the cryptographic key at
// any point in time. // any point in time.
func (c *kesClient) CreateKey(name string) error { func (c *kesClient) CreateKey(name string) error {
url := fmt.Sprintf("%s/v1/key/create/%s", c.addr, url.PathEscape(name)) path := fmt.Sprintf("/v1/key/create/%s", url.PathEscape(name))
_, err := c.postRetry(url, nil, 0) // No request body and no response expected _, err := c.postRetry(path, nil, 0) // No request body and no response expected
if err != nil { if err != nil {
return err return err
} }
@ -265,8 +265,8 @@ func (c *kesClient) GenerateDataKey(name string, context []byte) ([]byte, []byte
} }
const limit = 1 << 20 // A plaintext/ciphertext key pair will never be larger than 1 MB const limit = 1 << 20 // A plaintext/ciphertext key pair will never be larger than 1 MB
url := fmt.Sprintf("%s/v1/key/generate/%s", c.addr, url.PathEscape(name)) path := fmt.Sprintf("/v1/key/generate/%s", url.PathEscape(name))
resp, err := c.postRetry(url, bytes.NewReader(body), limit) resp, err := c.postRetry(path, bytes.NewReader(body), limit)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
@ -302,8 +302,8 @@ func (c *kesClient) DecryptDataKey(name string, ciphertext, context []byte) ([]b
} }
const limit = 1 << 20 // A data key will never be larger than 1 MiB const limit = 1 << 20 // A data key will never be larger than 1 MiB
url := fmt.Sprintf("%s/v1/key/decrypt/%s", c.addr, url.PathEscape(name)) path := fmt.Sprintf("/v1/key/decrypt/%s", url.PathEscape(name))
resp, err := c.postRetry(url, bytes.NewReader(body), limit) resp, err := c.postRetry(path, bytes.NewReader(body), limit)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -402,12 +402,14 @@ func (c *kesClient) post(url string, body io.Reader, limit int64) (io.Reader, er
return &respBody, nil return &respBody, nil
} }
func (c *kesClient) postRetry(url string, body io.ReadSeeker, limit int64) (io.Reader, error) { func (c *kesClient) postRetry(path string, body io.ReadSeeker, limit int64) (io.Reader, error) {
retryMax := 1 + len(c.endpoints)
for i := 0; ; i++ { for i := 0; ; i++ {
if body != nil { if body != nil {
body.Seek(0, io.SeekStart) // seek to the beginning of the body. body.Seek(0, io.SeekStart) // seek to the beginning of the body.
} }
response, err := c.post(url, body, limit)
response, err := c.post(c.endpoints[i%len(c.endpoints)]+path, body, limit)
if err == nil { if err == nil {
return response, nil return response, nil
} }

@ -109,9 +109,9 @@ type masterKeyKMS struct {
// KMSInfo contains some describing information about // KMSInfo contains some describing information about
// the KMS. // the KMS.
type KMSInfo struct { type KMSInfo struct {
Endpoint string Endpoints []string
Name string Name string
AuthType string AuthType string
} }
// NewMasterKey returns a basic KMS implementation from a single 256 bit master key. // NewMasterKey returns a basic KMS implementation from a single 256 bit master key.
@ -147,9 +147,9 @@ func (kms *masterKeyKMS) GenerateKey(keyID string, ctx Context) (key [32]byte, s
// KMS is configured directly using master key // KMS is configured directly using master key
func (kms *masterKeyKMS) Info() (info KMSInfo) { func (kms *masterKeyKMS) Info() (info KMSInfo) {
return KMSInfo{ return KMSInfo{
Endpoint: "", Endpoints: []string{},
Name: "", Name: "",
AuthType: "master-key", AuthType: "master-key",
} }
} }

@ -23,7 +23,6 @@ import (
const ( const (
retryWaitMin = 500 * time.Millisecond // minimum retry limit. retryWaitMin = 500 * time.Millisecond // minimum retry limit.
retryWaitMax = 3 * time.Second // 3 secs worth of max retry. retryWaitMax = 3 * time.Second // 3 secs worth of max retry.
retryMax = 2
) )
// LinearJitterBackoff provides the time.Duration for a caller to // LinearJitterBackoff provides the time.Duration for a caller to

@ -199,13 +199,13 @@ func (v *vaultService) DefaultKeyID() string {
} }
// Info returns some information about the Vault, // Info returns some information about the Vault,
// configuration - like the endpoint or authentication // configuration - like the endpoints or authentication
// method. // method.
func (v *vaultService) Info() KMSInfo { func (v *vaultService) Info() KMSInfo {
return KMSInfo{ return KMSInfo{
Endpoint: v.config.Endpoint, Endpoints: []string{v.config.Endpoint},
Name: v.DefaultKeyID(), Name: v.DefaultKeyID(),
AuthType: v.config.Auth.Type, AuthType: v.config.Auth.Type,
} }
} }

Loading…
Cancel
Save