From 21d8c0fd13cfb775c1c5ddd05e01feea07d81674 Mon Sep 17 00:00:00 2001 From: Andreas Auernhammer Date: Wed, 12 Dec 2018 07:50:29 +0100 Subject: [PATCH] refactor vault configuration and add master-key KMS (#6488) This refactors the vault configuration by moving the vault-related environment variables to `environment.go` (Other ENV should follow in the future to have a central place for adding / handling ENV instead of magic constants and handling across different files) Further this commit adds master-key SSE-S3 support. The operator can specify a SSE-S3 master key using `MINIO_SSE_MASTER_KEY` which will be used as master key to derive and encrypt per-object keys for SSE-S3 requests. This commit is also a pre-condition for SSE-S3 auto-encyption support. Fixes #6329 --- cmd/common-main.go | 15 --- cmd/config-current.go | 12 +- cmd/crypto/vault.go | 277 ++++++++++++++++-------------------------- cmd/environment.go | 166 +++++++++++++++++++++++++ cmd/globals.go | 2 - docs/kms/README.md | 63 +++++++--- 6 files changed, 324 insertions(+), 211 deletions(-) create mode 100644 cmd/environment.go diff --git a/cmd/common-main.go b/cmd/common-main.go index 041579440..723546148 100644 --- a/cmd/common-main.go +++ b/cmd/common-main.go @@ -30,7 +30,6 @@ import ( dns2 "github.com/miekg/dns" "github.com/minio/cli" "github.com/minio/minio-go/pkg/set" - "github.com/minio/minio/cmd/crypto" "github.com/minio/minio/cmd/logger" "github.com/minio/minio/cmd/logger/target/console" "github.com/minio/minio/cmd/logger/target/http" @@ -322,20 +321,6 @@ func handleCommonEnvVars() { globalWORMEnabled = bool(wormFlag) } - kmsConf, err := crypto.NewVaultConfig() - if err != nil { - logger.Fatal(err, "Unable to initialize hashicorp vault") - } - if kmsConf.Vault.Endpoint != "" { - kms, err := crypto.NewVault(kmsConf) - if err != nil { - logger.Fatal(err, "Unable to initialize KMS") - } - globalKMS = kms - globalKMSKeyID = kmsConf.Vault.Key.Name - globalKMSConfig = kmsConf - } - if compress := os.Getenv("MINIO_COMPRESS"); compress != "" { globalIsCompressionEnabled = strings.EqualFold(compress, "true") } diff --git a/cmd/config-current.go b/cmd/config-current.go index da018d433..312c1421e 100644 --- a/cmd/config-current.go +++ b/cmd/config-current.go @@ -271,8 +271,8 @@ func (s *serverConfig) loadFromEnvs() { s.SetCacheConfig(globalCacheDrives, globalCacheExcludes, globalCacheExpiry, globalCacheMaxUse) } - if globalKMS != nil { - s.KMS = globalKMSConfig + if err := Environment.LookupKMSConfig(s.KMS); err != nil { + logger.FatalIf(err, "Unable to setup the KMS") } if globalIsEnvCompression { @@ -534,12 +534,8 @@ func (s *serverConfig) loadToCachedConfigs() { globalCacheExpiry = cacheConf.Expiry globalCacheMaxUse = cacheConf.MaxUse } - if globalKMS == nil { - globalKMSConfig = s.KMS - if kms, err := crypto.NewVault(globalKMSConfig); err == nil { - globalKMS = kms - globalKMSKeyID = globalKMSConfig.Vault.Key.Name - } + if err := Environment.LookupKMSConfig(s.KMS); err != nil { + logger.FatalIf(err, "Unable to setup the KMS") } if !globalIsCompressionEnabled { diff --git a/cmd/crypto/vault.go b/cmd/crypto/vault.go index a5c796f59..43b705bc9 100644 --- a/cmd/crypto/vault.go +++ b/cmd/crypto/vault.go @@ -19,200 +19,133 @@ import ( "encoding/base64" "errors" "fmt" - "os" - "strconv" "strings" "time" vault "github.com/hashicorp/vault/api" ) -const ( - // vaultEndpointEnv Vault endpoint environment variable - vaultEndpointEnv = "MINIO_SSE_VAULT_ENDPOINT" - // vaultAuthTypeEnv type of vault auth to be used - vaultAuthTypeEnv = "MINIO_SSE_VAULT_AUTH_TYPE" - // vaultAppRoleIDEnv Vault AppRole ID environment variable - vaultAppRoleIDEnv = "MINIO_SSE_VAULT_APPROLE_ID" - // vaultAppSecretIDEnv Vault AppRole Secret environment variable - vaultAppSecretIDEnv = "MINIO_SSE_VAULT_APPROLE_SECRET" - // vaultKeyVersionEnv Vault Key Version environment variable - vaultKeyVersionEnv = "MINIO_SSE_VAULT_KEY_VERSION" - // vaultKeyNameEnv Vault Encryption Key Name environment variable - vaultKeyNameEnv = "MINIO_SSE_VAULT_KEY_NAME" - - // vaultCAPath is the path to a directory of PEM-encoded CA - // cert files to verify the Vault server SSL certificate. - vaultCAPath = "MINIO_SSE_VAULT_CAPATH" -) - var ( //ErrKMSAuthLogin is raised when there is a failure authenticating to KMS ErrKMSAuthLogin = errors.New("Vault service did not return auth info") ) -type vaultService struct { - config *VaultConfig - client *vault.Client - leaseDuration time.Duration -} - -// return transit secret engine's path for generate data key operation -func (v *vaultService) genDataKeyEndpoint(key string) string { - return "/transit/datakey/plaintext/" + key -} - -// return transit secret engine's path for decrypt operation -func (v *vaultService) decryptEndpoint(key string) string { - return "/transit/decrypt/" + key -} - -// VaultKey represents vault encryption key-id name & version +// VaultKey represents vault encryption key-ring. type VaultKey struct { - Name string `json:"name"` - Version int `json:"version"` + Name string `json:"name"` // The name of the encryption key-ring + Version int `json:"version"` // The key version } -// VaultAuth represents vault auth type to use. For now, AppRole is the only supported -// auth type. +// VaultAuth represents vault authentication type. +// Currently the only supported authentication type is AppRole. type VaultAuth struct { - Type string `json:"type"` - AppRole VaultAppRole `json:"approle"` + Type string `json:"type"` // The authentication type + AppRole VaultAppRole `json:"approle"` // The AppRole authentication credentials } -// VaultAppRole represents vault approle credentials +// VaultAppRole represents vault AppRole authentication credentials type VaultAppRole struct { - ID string `json:"id"` - Secret string `json:"secret"` + ID string `json:"id"` // The AppRole access ID + Secret string `json:"secret"` // The AppRole secret } -// VaultConfig holds config required to start vault service +// VaultConfig represents vault configuration. type VaultConfig struct { - Endpoint string `json:"endpoint"` - Auth VaultAuth `json:"auth"` - Key VaultKey `json:"key-id"` + Endpoint string `json:"endpoint"` // The vault API endpoint as URL + CAPath string `json:"-"` // The path to PEM-encoded certificate files used for mTLS. Currently not used in config file. + Auth VaultAuth `json:"auth"` // The vault authentication configuration + Key VaultKey `json:"key-id"` // The named key used for key-generation / decryption. + Namespace string `json:"-"` // The vault namespace of enterprise vault instances } -// validate whether all required env variables needed to start vault service have -// been set -func validateVaultConfig(c *VaultConfig) error { - if c.Endpoint == "" { - return fmt.Errorf("Missing hashicorp vault endpoint - %s is empty", vaultEndpointEnv) - } - if strings.ToLower(c.Auth.Type) != "approle" { - return fmt.Errorf("Unsupported hashicorp vault auth type - %s", vaultAuthTypeEnv) - } - if c.Auth.AppRole.ID == "" { - return fmt.Errorf("Missing hashicorp vault AppRole ID - %s is empty", vaultAppRoleIDEnv) - } - if c.Auth.AppRole.Secret == "" { - return fmt.Errorf("Missing hashicorp vault AppSecret ID - %s is empty", vaultAppSecretIDEnv) - } - if c.Key.Name == "" { - return fmt.Errorf("Invalid value set in environment variable %s", vaultKeyNameEnv) - } - if c.Key.Version < 0 { - return fmt.Errorf("Invalid value set in environment variable %s", vaultKeyVersionEnv) - } - return nil +// vaultService represents a connection to a vault KMS. +type vaultService struct { + config *VaultConfig + client *vault.Client + leaseDuration time.Duration } -// authenticate to vault with app role id and app role secret, and get a client access token, lease duration -func getVaultAccessToken(client *vault.Client, appRoleID, appSecret string) (token string, duration int, err error) { - data := map[string]interface{}{ - "role_id": appRoleID, - "secret_id": appSecret, - } - resp, e := client.Logical().Write("auth/approle/login", data) - if e != nil { - return token, duration, e - } - if resp.Auth == nil { - return token, duration, ErrKMSAuthLogin - } - return resp.Auth.ClientToken, resp.Auth.LeaseDuration, nil +var _ KMS = (*vaultService)(nil) // compiler check that *vaultService implements KMS + +// empty/default vault configuration used to check whether a particular is empty. +var emptyVaultConfig = VaultConfig{} + +// IsEmpty returns true if the vault config struct is an +// empty configuration. +func (v *VaultConfig) IsEmpty() bool { return *v == emptyVaultConfig } + +// Verify returns a nil error if the vault configuration +// is valid. A valid configuration is either empty or +// contains valid non-default values. +func (v *VaultConfig) Verify() (err error) { + if v.IsEmpty() { + return // an empty configuration is valid + } + switch { + case v.Endpoint == "": + err = errors.New("crypto: missing hashicorp vault endpoint") + case strings.ToLower(v.Auth.Type) != "approle": + err = fmt.Errorf("crypto: invalid hashicorp vault authentication type: %s is not supported", v.Auth.Type) + case v.Auth.AppRole.ID == "": + err = errors.New("crypto: missing hashicorp vault AppRole ID") + case v.Auth.AppRole.Secret == "": + err = errors.New("crypto: missing hashicorp vault AppSecret ID") + case v.Key.Name == "": + err = errors.New("crypto: missing hashicorp vault key name") + case v.Key.Version < 0: + err = errors.New("crypto: invalid hashicorp vault key version: The key version must not be negative") + } + return } -// NewVaultConfig sets KMSConfig from environment -// variables and performs validations. -func NewVaultConfig() (KMSConfig, error) { - kc := KMSConfig{} - endpoint := os.Getenv(vaultEndpointEnv) - roleID := os.Getenv(vaultAppRoleIDEnv) - roleSecret := os.Getenv(vaultAppSecretIDEnv) - keyName := os.Getenv(vaultKeyNameEnv) - keyVersion := 0 - authType := "approle" - if versionStr := os.Getenv(vaultKeyVersionEnv); versionStr != "" { - version, err := strconv.Atoi(versionStr) - if err != nil { - return kc, fmt.Errorf("Unable to parse %s value (`%s`)", vaultKeyVersionEnv, versionStr) - } - keyVersion = version - } - // return if none of the vault env variables are configured - if (endpoint == "") && (roleID == "") && (roleSecret == "") && (keyName == "") && (keyVersion == 0) { - return kc, nil +// NewVault initializes Hashicorp Vault KMS by authenticating +// to Vault with the credentials in config and gets a client +// token for future api calls. +func NewVault(config VaultConfig) (KMS, error) { + if config.IsEmpty() { + return nil, errors.New("crypto: the hashicorp vault configuration must not be empty") } - c := VaultConfig{ - Endpoint: endpoint, - Auth: VaultAuth{ - Type: authType, - AppRole: VaultAppRole{ - ID: roleID, - Secret: roleSecret, - }, - }, - Key: VaultKey{ - Version: keyVersion, - Name: keyName, - }, - } - if err := validateVaultConfig(&c); err != nil { - return kc, err + if err := config.Verify(); err != nil { + return nil, err } - kc.Vault = c - return kc, nil -} -// NewVault initializes Hashicorp Vault KMS by -// authenticating to Vault with the credentials in KMSConfig, -// and gets a client token for future api calls. -func NewVault(kmsConf KMSConfig) (KMS, error) { - config := kmsConf.Vault - vconfig := &vault.Config{ - Address: config.Endpoint, - } - if err := vconfig.ConfigureTLS(&vault.TLSConfig{ - CAPath: os.Getenv(vaultCAPath), - }); err != nil { + vaultCfg := vault.Config{Address: config.Endpoint} + if err := vaultCfg.ConfigureTLS(&vault.TLSConfig{CAPath: config.CAPath}); err != nil { return nil, err } - c, err := vault.NewClient(vconfig) + client, err := vault.NewClient(&vaultCfg) if err != nil { return nil, err } - - if ns, ok := os.LookupEnv("MINIO_SSE_VAULT_NAMESPACE"); ok { - c.SetNamespace(ns) + if config.Namespace != "" { + client.SetNamespace(config.Namespace) } - accessToken, leaseDuration, err := getVaultAccessToken(c, config.Auth.AppRole.ID, config.Auth.AppRole.Secret) + payload := map[string]interface{}{ + "role_id": config.Auth.AppRole.ID, + "secret_id": config.Auth.AppRole.Secret, + } + resp, err := client.Logical().Write("auth/approle/login", payload) if err != nil { return nil, err } - // authenticate and get the access token - c.SetToken(accessToken) - v := vaultService{client: c, config: &config, leaseDuration: time.Duration(leaseDuration)} - v.renewToken(c) - return &v, nil + if resp.Auth == nil { + return nil, ErrKMSAuthLogin + } + + client.SetToken(resp.Auth.ClientToken) + v := &vaultService{client: client, config: &config, leaseDuration: time.Duration(resp.Auth.LeaseDuration)} + v.renewToken() + return v, nil } -func (v *vaultService) renewToken(c *vault.Client) { +// renewToken starts a new go-routine which renews +// the vault authentication token periodically. +func (v *vaultService) renewToken() { retryDelay := 1 * time.Minute go func() { for { - s, err := c.Auth().Token().RenewSelf(int(v.leaseDuration)) + s, err := v.client.Auth().Token().RenewSelf(int(v.leaseDuration)) if err != nil { time.Sleep(retryDelay) continue @@ -223,48 +156,54 @@ func (v *vaultService) renewToken(c *vault.Client) { }() } -// Generates a random plain text key, sealed plain text key from -// Vault. It returns the plaintext key and sealed plaintext key on success +// GenerateKey returns a new plaintext key, generated by the KMS, +// and a sealed version of this plaintext key encrypted using the +// named key referenced by keyID. It also binds the generated key +// cryptographically to the provided context. func (v *vaultService) GenerateKey(keyID string, ctx Context) (key [32]byte, sealedKey []byte, err error) { - contextStream := new(bytes.Buffer) - ctx.WriteTo(contextStream) + var contextStream bytes.Buffer + ctx.WriteTo(&contextStream) payload := map[string]interface{}{ "context": base64.StdEncoding.EncodeToString(contextStream.Bytes()), } - s, err1 := v.client.Logical().Write(v.genDataKeyEndpoint(keyID), payload) - - if err1 != nil { - return key, sealedKey, err1 + s, err := v.client.Logical().Write(fmt.Sprintf("/transit/datakey/plaintext/%s", keyID), payload) + if err != nil { + return key, sealedKey, err } sealKey := s.Data["ciphertext"].(string) plainKey, err := base64.StdEncoding.DecodeString(s.Data["plaintext"].(string)) if err != nil { - return key, sealedKey, err1 + return key, sealedKey, err } copy(key[:], []byte(plainKey)) return key, []byte(sealKey), nil } -// unsealKMSKey unseals the sealedKey using the Vault master key -// referenced by the keyID. The plain text key is returned on success. +// UnsealKey returns the decrypted sealedKey as plaintext key. +// Therefore it sends the sealedKey to the KMS which decrypts +// it using the named key referenced by keyID and responses with +// the plaintext key. +// +// The context must be same context as the one provided while +// generating the plaintext key / sealedKey. func (v *vaultService) UnsealKey(keyID string, sealedKey []byte, ctx Context) (key [32]byte, err error) { - contextStream := new(bytes.Buffer) - ctx.WriteTo(contextStream) + var contextStream bytes.Buffer + ctx.WriteTo(&contextStream) + payload := map[string]interface{}{ "ciphertext": string(sealedKey), "context": base64.StdEncoding.EncodeToString(contextStream.Bytes()), } - s, err1 := v.client.Logical().Write(v.decryptEndpoint(keyID), payload) - if err1 != nil { - return key, err1 + s, err := v.client.Logical().Write(fmt.Sprintf("/transit/decrypt/%s", keyID), payload) + if err != nil { + return key, err } base64Key := s.Data["plaintext"].(string) - plainKey, err1 := base64.StdEncoding.DecodeString(base64Key) - if err1 != nil { - return key, err1 + plainKey, err := base64.StdEncoding.DecodeString(base64Key) + if err != nil { + return key, err } copy(key[:], []byte(plainKey)) - return key, nil } diff --git a/cmd/environment.go b/cmd/environment.go new file mode 100644 index 000000000..e41abdcd6 --- /dev/null +++ b/cmd/environment.go @@ -0,0 +1,166 @@ +// Minio Cloud Storage, (C) 2016, 2017, 2018 Minio, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package cmd + +import ( + "encoding/hex" + "errors" + "fmt" + "os" + "strconv" + "strings" + + "github.com/minio/minio/cmd/crypto" +) + +const ( + // EnvKMSMasterKey is the environment variable used to specify + // a KMS master key used to protect SSE-S3 per-object keys. + // Valid values must be of the from: "KEY_ID:32_BYTE_HEX_VALUE". + EnvKMSMasterKey = "MINIO_SSE_MASTER_KEY" +) + +const ( + // EnvVaultEndpoint is the environment variable used to specify + // the vault HTTPS endpoint. + EnvVaultEndpoint = "MINIO_SSE_VAULT_ENDPOINT" + + // EnvVaultAuthType is the environment variable used to specify + // the authentication type for vault. + EnvVaultAuthType = "MINIO_SSE_VAULT_AUTH_TYPE" + + // EnvVaultAppRoleID is the environment variable used to specify + // the vault AppRole ID. + EnvVaultAppRoleID = "MINIO_SSE_VAULT_APPROLE_ID" + + // EnvVaultAppSecretID is the environment variable used to specify + // the vault AppRole secret corresponding to the AppRole ID. + EnvVaultAppSecretID = "MINIO_SSE_VAULT_APPROLE_SECRET" + + // EnvVaultKeyVersion is the environment variable used to specify + // the vault key version. + EnvVaultKeyVersion = "MINIO_SSE_VAULT_KEY_VERSION" + + // EnvVaultKeyName is the environment variable used to specify + // the vault named key-ring. In the S3 context it's referred as + // customer master key ID (CMK-ID). + EnvVaultKeyName = "MINIO_SSE_VAULT_KEY_NAME" + + // EnvVaultCAPath is the environment variable used to specify the + // path to a directory of PEM-encoded CA cert files. These CA cert + // files are used to authenticate Minio to Vault over mTLS. + EnvVaultCAPath = "MINIO_SSE_VAULT_CAPATH" + + // EnvVaultNamespace is the environment variable used to specify + // vault namespace. The vault namespace is used if the enterprise + // version of Hashicorp Vault is used. + EnvVaultNamespace = "MINIO_SSE_VAULT_NAMESPACE" +) + +// Environment provides functions for accessing environment +// variables. +var Environment = environment{} + +type environment struct{} + +// Get retrieves the value of the environment variable named +// by the key. If the variable is present in the environment the +// value (which may be empty) is returned. Otherwise it returns +// the specified default value. +func (environment) Get(key, defaultValue string) string { + if v, ok := os.LookupEnv(key); ok { + return v + } + return defaultValue +} + +// Lookup retrieves the value of the environment variable named +// by the key. If the variable is present in the environment the +// value (which may be empty) is returned and the boolean is true. +// Otherwise the returned value will be empty and the boolean will +// be false. +func (environment) Lookup(key string) (string, bool) { return os.LookupEnv(key) } + +// LookupKMSConfig extracts the KMS configuration provided by environment +// variables and merge them with the provided KMS configuration. The +// merging follows the following rules: +// +// 1. A valid value provided as environment variable is higher prioritized +// than the provided configuration and overwrites the value from the +// configuration file. +// +// 2. A value specified as environment variable never changes the configuration +// file. So it is never made a persistent setting. +// +// It sets the global KMS configuration according to the merged configuration +// on success. +func (env environment) LookupKMSConfig(config crypto.KMSConfig) (err error) { + // Lookup Hashicorp-Vault configuration & overwrite config entry if ENV var is present + config.Vault.Endpoint = env.Get(EnvVaultEndpoint, config.Vault.Endpoint) + config.Vault.CAPath = env.Get(EnvVaultCAPath, config.Vault.CAPath) + config.Vault.Auth.Type = env.Get(EnvVaultAuthType, config.Vault.Auth.Type) + config.Vault.Auth.AppRole.ID = env.Get(EnvVaultAppRoleID, config.Vault.Auth.AppRole.ID) + config.Vault.Auth.AppRole.Secret = env.Get(EnvVaultAppSecretID, config.Vault.Auth.AppRole.Secret) + config.Vault.Key.Name = env.Get(EnvVaultKeyName, config.Vault.Key.Name) + config.Vault.Namespace = env.Get(EnvVaultNamespace, config.Vault.Namespace) + keyVersion := env.Get(EnvVaultKeyVersion, strconv.Itoa(config.Vault.Key.Version)) + config.Vault.Key.Version, err = strconv.Atoi(keyVersion) + if err != nil { + return fmt.Errorf("Invalid ENV variable: Unable to parse %s value (`%s`)", EnvVaultKeyVersion, keyVersion) + } + if err = config.Vault.Verify(); err != nil { + return err + } + + // Lookup KMS master keys - only available through ENV. + if masterKey, ok := env.Lookup(EnvKMSMasterKey); ok { + if !config.Vault.IsEmpty() { // Vault and KMS master key provided + return errors.New("Ambiguous KMS configuration: vault configuration and a master key are provided at the same time") + } + globalKMSKeyID, globalKMS, err = parseKMSMasterKey(masterKey) + if err != nil { + return err + } + } + if !config.Vault.IsEmpty() { + globalKMS, err = crypto.NewVault(config.Vault) + if err != nil { + return err + } + globalKMSKeyID = config.Vault.Key.Name + } + return nil +} + +// parseKMSMasterKey parses the value of the environment variable +// `EnvKMSMasterKey` and returns a key-ID and a master-key KMS on success. +func parseKMSMasterKey(envArg string) (string, crypto.KMS, error) { + values := strings.SplitN(envArg, ":", 2) + if len(values) != 2 { + return "", nil, fmt.Errorf("Invalid KMS master key: %s does not contain a ':'", envArg) + } + var ( + keyID = values[0] + hexKey = values[1] + ) + if len(hexKey) != 64 { // 2 hex bytes = 1 byte + return "", nil, fmt.Errorf("Invalid KMS master key: %s not a 32 bytes long HEX value", hexKey) + } + var masterKey [32]byte + if _, err := hex.Decode(masterKey[:], []byte(hexKey)); err != nil { + return "", nil, fmt.Errorf("Invalid KMS master key: %s not a 32 bytes long HEX value", hexKey) + } + return keyID, crypto.NewKMS(masterKey), nil +} diff --git a/cmd/globals.go b/cmd/globals.go index aa7d2330d..de9b0d057 100644 --- a/cmd/globals.go +++ b/cmd/globals.go @@ -230,8 +230,6 @@ var ( globalKMSKeyID string // Allocated KMS globalKMS crypto.KMS - // KMS config - globalKMSConfig crypto.KMSConfig // Is compression include extensions/content-types set. globalIsEnvCompression bool diff --git a/docs/kms/README.md b/docs/kms/README.md index f5a09114d..bf5414c92 100644 --- a/docs/kms/README.md +++ b/docs/kms/README.md @@ -1,26 +1,49 @@ # KMS Quickstart Guide [![Slack](https://slack.minio.io/slack?type=svg)](https://slack.minio.io) -KMS feature allows you to use Vault to generate and manage encryption keys for use by the minio server to encrypt objects. This document explains how to configure Minio with Vault as KMS. +Minio uses a key-management-system (KMS) to support SSE-S3. If a client requests SSE-S3 or auto-encryption +is enabled the Minio server encrypts each object with an unique object key which is protected by a master key +managed by the KMS. Usually many/all object keys are protected by a single master key. + +Minio supports two different KMS concepts: + - External KMS: + Minio can be configured to use an external KMS i.e. [Hashicorp Vault](https://www.vaultproject.io/). + An external KMS decouples Minio as storage system from key-management. An external KMS can + be managed by a dedicated security team and allows to grant/deny access to (certain) objects + by en/disabling the corresponding master keys on demand. + However, an external KMS causes configuration and management overhead. + - Direct KMS master keys: + Minio can also be configured to directly use a master key specified by the ENV. variable `MINIO_SSE_MASTER_KEY`. + Direct master keys are useful if the storage backend is not on the same machine as the Minio server - e.g. + if network drives or Minio gateway is used - and an external KMS would cause too much management overhead. + Note: If the Minio server machine is ever compromised, then the master key must also be + treated as compromised. ## Get started ### 1. Prerequisites Install Minio - [Minio Quickstart Guide](https://docs.minio.io/docs/minio-quickstart-guide). -### 2. Configure Vault -Vault as Key Management System requires following to be configured in Vault +### 2. Setup a KMS -- [transit](https://www.vaultproject.io/docs/secrets/transit/index.html) backend configured with a named encryption key-ring -- [AppRole](https://www.vaultproject.io/docs/auth/approle.html) based authentication with read/update policy for transit backend. In particular, read and update policy are required for the [Generate Data Key](https://www.vaultproject.io/api/secret/transit/index.html#generate-data-key) endpoint and [Decrypt Data](https://www.vaultproject.io/api/secret/transit/index.html#decrypt-data) endpoint. +Either use Hashicorp Vault as external KMS or specify a master key directly depending on your use case. + +#### 2.1 Setup Hashicorp Vault Here is a sample quick start for configuring vault with a transit backend and Approle with correct policy -#### 2.1 Start Vault server in dev mode + +Minio requires the following Vault setup: +- The [transit backend](https://www.vaultproject.io/api/secret/transit/index.html) configured with a named encryption key-ring +- [AppRole](https://www.vaultproject.io/docs/auth/approle.html) based authentication with read/update policy for transit backend. In particular, read and update policy are required for the [Generate Data Key](https://www.vaultproject.io/api/secret/transit/index.html#generate-data-key) endpoint and [Decrypt Data](https://www.vaultproject.io/api/secret/transit/index.html#decrypt-data) endpoint. + +**2.1.1 Start Vault server in dev mode** + In dev mode, Vault server runs in-memory and starts unsealed. Note that running Vault in dev mode is insecure and any data stored in the Vault is lost upon restart. ``` vault server -dev ``` -#### 2.2 Set up vault transit backend and create an app role +**2.1.2 Set up vault transit backend and create an app role** + ``` cat > vaultpolicy.hcl <