mirror of
				https://github.com/minio/minio.git
				synced 2025-10-29 15:55:00 -04:00 
			
		
		
		
	kms: encrypt IAM/config data with the KMS (#12041)
This commit changes the config/IAM encryption process. Instead of encrypting config data (users, policies etc.) with the root credentials MinIO now encrypts this data with a KMS - if configured. Therefore, this PR moves the MinIO-KMS configuration (via env. variables) to a "top-level" configuration. The KMS configuration cannot be stored in the config file since it is used to decrypt the config file in the first place. As a consequence, this commit also removes support for Hashicorp Vault - which has been deprecated anyway. Signed-off-by: Andreas Auernhammer <aead@mail.de>
This commit is contained in:
		
							parent
							
								
									e05e14309c
								
							
						
					
					
						commit
						3455f786fa
					
				| @ -17,8 +17,7 @@ ENV MINIO_ACCESS_KEY_FILE=access_key \ | ||||
|     MINIO_SECRET_KEY_FILE=secret_key \ | ||||
|     MINIO_ROOT_USER_FILE=access_key \ | ||||
|     MINIO_ROOT_PASSWORD_FILE=secret_key \ | ||||
|     MINIO_KMS_MASTER_KEY_FILE=kms_master_key \ | ||||
|     MINIO_SSE_MASTER_KEY_FILE=sse_master_key \ | ||||
|     MINIO_KMS_SECRET_KEY_FILE=kms_master_key \ | ||||
|     MINIO_UPDATE_MINISIGN_PUBKEY="RWTx5Zr1tiHQLwG9keckT0c45M3AGeHD6IvimQHpyRywVWGbP1aVSGav" | ||||
| 
 | ||||
| EXPOSE 9000 | ||||
|  | ||||
| @ -19,8 +19,7 @@ ENV MINIO_ACCESS_KEY_FILE=access_key \ | ||||
|     MINIO_SECRET_KEY_FILE=secret_key \ | ||||
|     MINIO_ROOT_USER_FILE=access_key \ | ||||
|     MINIO_ROOT_PASSWORD_FILE=secret_key \ | ||||
|     MINIO_KMS_MASTER_KEY_FILE=kms_master_key \ | ||||
|     MINIO_SSE_MASTER_KEY_FILE=sse_master_key \ | ||||
|     MINIO_KMS_SECRET_KEY_FILE=kms_master_key \ | ||||
|     MINIO_UPDATE_MINISIGN_PUBKEY="RWTx5Zr1tiHQLwG9keckT0c45M3AGeHD6IvimQHpyRywVWGbP1aVSGav" | ||||
| 
 | ||||
| EXPOSE 9000 | ||||
|  | ||||
| @ -12,8 +12,7 @@ ENV MINIO_UPDATE=off \ | ||||
|     MINIO_SECRET_KEY_FILE=secret_key \ | ||||
|     MINIO_ROOT_USER_FILE=access_key \ | ||||
|     MINIO_ROOT_PASSWORD_FILE=secret_key \ | ||||
|     MINIO_KMS_MASTER_KEY_FILE=kms_master_key \ | ||||
|     MINIO_SSE_MASTER_KEY_FILE=sse_master_key | ||||
|     MINIO_KMS_SECRET_KEY_FILE=kms_master_key | ||||
| 
 | ||||
| RUN microdnf update --nodocs | ||||
| RUN microdnf install curl ca-certificates shadow-utils util-linux --nodocs | ||||
|  | ||||
| @ -16,8 +16,7 @@ ENV MINIO_ACCESS_KEY_FILE=access_key \ | ||||
|     MINIO_SECRET_KEY_FILE=secret_key \ | ||||
|     MINIO_ROOT_USER_FILE=access_key \ | ||||
|     MINIO_ROOT_PASSWORD_FILE=secret_key \ | ||||
|     MINIO_KMS_MASTER_KEY_FILE=kms_master_key \ | ||||
|     MINIO_SSE_MASTER_KEY_FILE=sse_master_key \ | ||||
|     MINIO_KMS_SECRET_KEY_FILE=kms_master_key \ | ||||
|     MINIO_UPDATE_MINISIGN_PUBKEY="RWTx5Zr1tiHQLwG9keckT0c45M3AGeHD6IvimQHpyRywVWGbP1aVSGav" | ||||
| 
 | ||||
| COPY dockerscripts/verify-minio.sh /usr/bin/verify-minio.sh | ||||
|  | ||||
| @ -33,7 +33,6 @@ import ( | ||||
| 	"github.com/minio/minio/cmd/config/identity/openid" | ||||
| 	"github.com/minio/minio/cmd/config/policy/opa" | ||||
| 	"github.com/minio/minio/cmd/config/storageclass" | ||||
| 	"github.com/minio/minio/cmd/crypto" | ||||
| 	"github.com/minio/minio/cmd/logger" | ||||
| 	"github.com/minio/minio/pkg/auth" | ||||
| 	iampolicy "github.com/minio/minio/pkg/iam/policy" | ||||
| @ -154,11 +153,6 @@ func (a adminAPIHandlers) SetConfigKVHandler(w http.ResponseWriter, r *http.Requ | ||||
| 		return | ||||
| 	} | ||||
| 
 | ||||
| 	// Make sure to write backend is encrypted | ||||
| 	if globalConfigEncrypted { | ||||
| 		saveConfig(GlobalContext, objectAPI, backendEncryptedFile, backendEncryptedMigrationComplete) | ||||
| 	} | ||||
| 
 | ||||
| 	if dynamic { | ||||
| 		// Apply dynamic values. | ||||
| 		if err := applyDynamicConfig(GlobalContext, objectAPI, cfg); err != nil { | ||||
| @ -411,11 +405,6 @@ func (a adminAPIHandlers) SetConfigHandler(w http.ResponseWriter, r *http.Reques | ||||
| 		return | ||||
| 	} | ||||
| 
 | ||||
| 	// Make sure to write backend is encrypted | ||||
| 	if globalConfigEncrypted { | ||||
| 		saveConfig(GlobalContext, objectAPI, backendEncryptedFile, backendEncryptedMigrationComplete) | ||||
| 	} | ||||
| 
 | ||||
| 	writeSuccessResponseHeadersOnly(w) | ||||
| } | ||||
| 
 | ||||
| @ -454,10 +443,6 @@ func (a adminAPIHandlers) GetConfigHandler(w http.ResponseWriter, r *http.Reques | ||||
| 				off = !cache.Enabled(kv) | ||||
| 			case config.StorageClassSubSys: | ||||
| 				off = !storageclass.Enabled(kv) | ||||
| 			case config.KmsVaultSubSys: | ||||
| 				off = !crypto.EnabledVault(kv) | ||||
| 			case config.KmsKesSubSys: | ||||
| 				off = !crypto.EnabledKes(kv) | ||||
| 			case config.PolicyOPASubSys: | ||||
| 				off = !opa.Enabled(kv) | ||||
| 			case config.IdentityOpenIDSubSys: | ||||
|  | ||||
| @ -1756,32 +1756,32 @@ func fetchKMSStatus() madmin.KMS { | ||||
| 	} | ||||
| 	if len(stat.Endpoints) == 0 { | ||||
| 		kmsStat.Status = stat.Name | ||||
| 		return kmsStat | ||||
| 	} | ||||
| 	if err := checkConnection(stat.Endpoints[0], 15*time.Second); err != nil { | ||||
| 		kmsStat.Status = string(madmin.ItemOffline) | ||||
| 		return kmsStat | ||||
| 	} | ||||
| 	kmsStat.Status = string(madmin.ItemOnline) | ||||
| 
 | ||||
| 	kmsContext := kms.Context{"MinIO admin API": "ServerInfoHandler"} // Context for a test key operation | ||||
| 	// 1. Generate a new key using the KMS. | ||||
| 	key, err := GlobalKMS.GenerateKey("", kmsContext) | ||||
| 	if err != nil { | ||||
| 		kmsStat.Encrypt = fmt.Sprintf("Encryption failed: %v", err) | ||||
| 	} else { | ||||
| 		if err := checkConnection(stat.Endpoints[0], 15*time.Second); err != nil { | ||||
| 			kmsStat.Status = string(madmin.ItemOffline) | ||||
| 		} else { | ||||
| 			kmsStat.Status = string(madmin.ItemOnline) | ||||
| 		kmsStat.Encrypt = "success" | ||||
| 	} | ||||
| 
 | ||||
| 			kmsContext := kms.Context{"MinIO admin API": "ServerInfoHandler"} // Context for a test key operation | ||||
| 			// 1. Generate a new key using the KMS. | ||||
| 			key, err := GlobalKMS.GenerateKey("", kmsContext) | ||||
| 			if err != nil { | ||||
| 				kmsStat.Encrypt = fmt.Sprintf("Encryption failed: %v", err) | ||||
| 			} else { | ||||
| 				kmsStat.Encrypt = "success" | ||||
| 			} | ||||
| 
 | ||||
| 			// 2. Verify that we can indeed decrypt the (encrypted) key | ||||
| 			decryptedKey, err := GlobalKMS.DecryptKey(key.KeyID, key.Ciphertext, kmsContext) | ||||
| 			switch { | ||||
| 			case err != nil: | ||||
| 				kmsStat.Decrypt = fmt.Sprintf("Decryption failed: %v", err) | ||||
| 			case subtle.ConstantTimeCompare(key.Plaintext, decryptedKey) != 1: | ||||
| 				kmsStat.Decrypt = "Decryption failed: decrypted key does not match generated key" | ||||
| 			default: | ||||
| 				kmsStat.Decrypt = "success" | ||||
| 			} | ||||
| 		} | ||||
| 	// 2. Verify that we can indeed decrypt the (encrypted) key | ||||
| 	decryptedKey, err := GlobalKMS.DecryptKey(key.KeyID, key.Ciphertext, kmsContext) | ||||
| 	switch { | ||||
| 	case err != nil: | ||||
| 		kmsStat.Decrypt = fmt.Sprintf("Decryption failed: %v", err) | ||||
| 	case subtle.ConstantTimeCompare(key.Plaintext, decryptedKey) != 1: | ||||
| 		kmsStat.Decrypt = "Decryption failed: decrypted key does not match generated key" | ||||
| 	default: | ||||
| 		kmsStat.Decrypt = "success" | ||||
| 	} | ||||
| 	return kmsStat | ||||
| } | ||||
|  | ||||
| @ -204,7 +204,6 @@ const ( | ||||
| 	ErrInvalidSSECustomerParameters | ||||
| 	ErrIncompatibleEncryptionMethod | ||||
| 	ErrKMSNotConfigured | ||||
| 	ErrKMSAuthFailure | ||||
| 
 | ||||
| 	ErrNoAccessKey | ||||
| 	ErrInvalidToken | ||||
| @ -1079,11 +1078,6 @@ var errorCodes = errorCodeMap{ | ||||
| 		Description:    "Server side encryption specified but KMS is not configured", | ||||
| 		HTTPStatusCode: http.StatusBadRequest, | ||||
| 	}, | ||||
| 	ErrKMSAuthFailure: { | ||||
| 		Code:           "InvalidArgument", | ||||
| 		Description:    "Server side encryption specified but KMS authorization failed", | ||||
| 		HTTPStatusCode: http.StatusBadRequest, | ||||
| 	}, | ||||
| 	ErrNoAccessKey: { | ||||
| 		Code:           "AccessDenied", | ||||
| 		Description:    "No AWSAccessKey was presented", | ||||
| @ -1837,8 +1831,6 @@ func toAPIErrorCode(ctx context.Context, err error) (apiErr APIErrorCode) { | ||||
| 		apiErr = ErrIncompatibleEncryptionMethod | ||||
| 	case errKMSNotConfigured: | ||||
| 		apiErr = ErrKMSNotConfigured | ||||
| 	case crypto.ErrKMSAuthLogin: | ||||
| 		apiErr = ErrKMSAuthFailure | ||||
| 	case context.Canceled, context.DeadlineExceeded: | ||||
| 		apiErr = ErrOperationTimedOut | ||||
| 	case errDiskNotFound: | ||||
|  | ||||
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							| @ -18,8 +18,10 @@ package cmd | ||||
| 
 | ||||
| import ( | ||||
| 	"context" | ||||
| 	"crypto/tls" | ||||
| 	"crypto/x509" | ||||
| 	"encoding/gob" | ||||
| 	"encoding/hex" | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"math/rand" | ||||
| @ -37,6 +39,7 @@ import ( | ||||
| 	"github.com/minio/cli" | ||||
| 	"github.com/minio/minio-go/v7/pkg/set" | ||||
| 	"github.com/minio/minio/cmd/config" | ||||
| 	"github.com/minio/minio/cmd/crypto" | ||||
| 	xhttp "github.com/minio/minio/cmd/http" | ||||
| 	"github.com/minio/minio/cmd/logger" | ||||
| 	"github.com/minio/minio/pkg/auth" | ||||
| @ -44,6 +47,7 @@ import ( | ||||
| 	"github.com/minio/minio/pkg/console" | ||||
| 	"github.com/minio/minio/pkg/env" | ||||
| 	"github.com/minio/minio/pkg/handlers" | ||||
| 	"github.com/minio/minio/pkg/kms" | ||||
| ) | ||||
| 
 | ||||
| // serverDebugLog will enable debug printing | ||||
| @ -316,7 +320,6 @@ func handleCommonEnvVars() { | ||||
| 				"Unable to validate credentials inherited from the shell environment") | ||||
| 		} | ||||
| 		globalActiveCred = cred | ||||
| 		globalConfigEncrypted = true | ||||
| 	} | ||||
| 
 | ||||
| 	if env.IsSet(config.EnvRootUser) || env.IsSet(config.EnvRootPassword) { | ||||
| @ -326,29 +329,58 @@ func handleCommonEnvVars() { | ||||
| 				"Unable to validate credentials inherited from the shell environment") | ||||
| 		} | ||||
| 		globalActiveCred = cred | ||||
| 		globalConfigEncrypted = true | ||||
| 	} | ||||
| 
 | ||||
| 	if env.IsSet(config.EnvAccessKeyOld) && env.IsSet(config.EnvSecretKeyOld) { | ||||
| 		oldCred, err := auth.CreateCredentials(env.Get(config.EnvAccessKeyOld, ""), env.Get(config.EnvSecretKeyOld, "")) | ||||
| 		if err != nil { | ||||
| 			logger.Fatal(config.ErrInvalidCredentials(err), | ||||
| 				"Unable to validate the old credentials inherited from the shell environment") | ||||
| 		} | ||||
| 		globalOldCred = oldCred | ||||
| 		os.Unsetenv(config.EnvAccessKeyOld) | ||||
| 		os.Unsetenv(config.EnvSecretKeyOld) | ||||
| 	if env.IsSet(config.EnvKMSSecretKey) && env.IsSet(config.EnvKESEndpoint) { | ||||
| 		logger.Fatal(errors.New("ambigious KMS configuration"), fmt.Sprintf("The environment contains %q as well as %q", config.EnvKMSSecretKey, config.EnvKESEndpoint)) | ||||
| 	} | ||||
| 
 | ||||
| 	if env.IsSet(config.EnvRootUserOld) && env.IsSet(config.EnvRootPasswordOld) { | ||||
| 		oldCred, err := auth.CreateCredentials(env.Get(config.EnvRootUserOld, ""), env.Get(config.EnvRootPasswordOld, "")) | ||||
| 	switch { | ||||
| 	case env.IsSet(config.EnvKMSSecretKey) && env.IsSet(config.EnvKESEndpoint): | ||||
| 		logger.Fatal(errors.New("ambigious KMS configuration"), fmt.Sprintf("The environment contains %q as well as %q", config.EnvKMSSecretKey, config.EnvKESEndpoint)) | ||||
| 	case env.IsSet(config.EnvKMSMasterKey) && env.IsSet(config.EnvKESEndpoint): | ||||
| 		logger.Fatal(errors.New("ambigious KMS configuration"), fmt.Sprintf("The environment contains %q as well as %q", config.EnvKMSMasterKey, config.EnvKESEndpoint)) | ||||
| 	} | ||||
| 	if env.IsSet(config.EnvKMSSecretKey) { | ||||
| 		KMS, err := kms.Parse(env.Get(config.EnvKMSSecretKey, "")) | ||||
| 		if err != nil { | ||||
| 			logger.Fatal(config.ErrInvalidCredentials(err), | ||||
| 				"Unable to validate the old credentials inherited from the shell environment") | ||||
| 			logger.Fatal(err, "Unable to parse the KMS secret key inherited from the shell environment") | ||||
| 		} | ||||
| 		globalOldCred = oldCred | ||||
| 		os.Unsetenv(config.EnvRootUserOld) | ||||
| 		os.Unsetenv(config.EnvRootPasswordOld) | ||||
| 		GlobalKMS = KMS | ||||
| 	} else if env.IsSet(config.EnvKMSMasterKey) { | ||||
| 		logger.LogIf(GlobalContext, errors.New("legacy KMS configuration"), fmt.Sprintf("The environment variable %q is deprecated and will be removed in the future", config.EnvKMSMasterKey)) | ||||
| 
 | ||||
| 		v := strings.SplitN(env.Get(config.EnvKMSMasterKey, ""), ":", 2) | ||||
| 		if len(v) != 2 { | ||||
| 			logger.Fatal(errors.New("invalid "+config.EnvKMSMasterKey), "Unable to parse the KMS secret key inherited from the shell environment") | ||||
| 		} | ||||
| 		secretKey, err := hex.DecodeString(v[1]) | ||||
| 		if err != nil { | ||||
| 			logger.Fatal(err, "Unable to parse the KMS secret key inherited from the shell environment") | ||||
| 		} | ||||
| 		KMS, err := kms.New(v[0], secretKey) | ||||
| 		if err != nil { | ||||
| 			logger.Fatal(err, "Unable to parse the KMS secret key inherited from the shell environment") | ||||
| 		} | ||||
| 		GlobalKMS = KMS | ||||
| 	} | ||||
| 	if env.IsSet(config.EnvKESEndpoint) { | ||||
| 		kesEndpoints, err := crypto.ParseKESEndpoints(env.Get(config.EnvKESEndpoint, "")) | ||||
| 		if err != nil { | ||||
| 			logger.Fatal(err, "Unable to parse the KES endpoints inherited from the shell environment") | ||||
| 		} | ||||
| 		KMS, err := crypto.NewKes(crypto.KesConfig{ | ||||
| 			Enabled:      true, | ||||
| 			Endpoint:     kesEndpoints, | ||||
| 			DefaultKeyID: env.Get(config.EnvKESKeyName, ""), | ||||
| 			CertFile:     env.Get(config.EnvKESClientCert, ""), | ||||
| 			KeyFile:      env.Get(config.EnvKESClientKey, ""), | ||||
| 			CAPath:       env.Get(config.EnvKESServerCA, globalCertsCADir.Get()), | ||||
| 			Transport:    newCustomHTTPTransportWithHTTP2(&tls.Config{RootCAs: globalRootCAs}, defaultDialTimeout)(), | ||||
| 		}) | ||||
| 		if err != nil { | ||||
| 			logger.Fatal(err, "Unable to initialize a connection to KES as specified by the shell environment") | ||||
| 		} | ||||
| 		GlobalKMS = KMS | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
|  | ||||
| @ -18,7 +18,7 @@ package cmd | ||||
| 
 | ||||
| import ( | ||||
| 	"context" | ||||
| 	"crypto/tls" | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"strings" | ||||
| 	"sync" | ||||
| @ -41,6 +41,7 @@ import ( | ||||
| 	"github.com/minio/minio/cmd/logger" | ||||
| 	"github.com/minio/minio/cmd/logger/target/http" | ||||
| 	"github.com/minio/minio/pkg/env" | ||||
| 	"github.com/minio/minio/pkg/kms" | ||||
| 	"github.com/minio/minio/pkg/madmin" | ||||
| ) | ||||
| 
 | ||||
| @ -55,8 +56,6 @@ func initHelp() { | ||||
| 		config.RegionSubSys:         config.DefaultRegionKVS, | ||||
| 		config.APISubSys:            api.DefaultKVS, | ||||
| 		config.CredentialsSubSys:    config.DefaultCredentialKVS, | ||||
| 		config.KmsVaultSubSys:       crypto.DefaultVaultKVS, | ||||
| 		config.KmsKesSubSys:         crypto.DefaultKesKVS, | ||||
| 		config.LoggerWebhookSubSys:  logger.DefaultKVS, | ||||
| 		config.AuditWebhookSubSys:   logger.DefaultAuditKVS, | ||||
| 		config.HealSubSys:           heal.DefaultKVS, | ||||
| @ -204,8 +203,6 @@ func initHelp() { | ||||
| 		config.IdentityOpenIDSubSys: openid.Help, | ||||
| 		config.IdentityLDAPSubSys:   xldap.Help, | ||||
| 		config.PolicyOPASubSys:      opa.Help, | ||||
| 		config.KmsVaultSubSys:       crypto.HelpVault, | ||||
| 		config.KmsKesSubSys:         crypto.HelpKes, | ||||
| 		config.LoggerWebhookSubSys:  logger.Help, | ||||
| 		config.AuditWebhookSubSys:   logger.HelpAudit, | ||||
| 		config.NotifyAMQPSubSys:     notify.HelpAMQP, | ||||
| @ -295,27 +292,6 @@ func validateConfig(s config.Config, setDriveCounts []int) error { | ||||
| 			etcdClnt.Close() | ||||
| 		} | ||||
| 	} | ||||
| 	{ | ||||
| 		kmsCfg, err := crypto.LookupConfig(s, globalCertsCADir.Get(), newCustomHTTPTransportWithHTTP2( | ||||
| 			&tls.Config{ | ||||
| 				RootCAs: globalRootCAs, | ||||
| 			}, defaultDialTimeout)()) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 
 | ||||
| 		// Set env to enable master key validation. | ||||
| 		// this is needed only for KMS. | ||||
| 		env.SetEnvOn() | ||||
| 
 | ||||
| 		if _, err = crypto.NewKMS(kmsCfg); err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 
 | ||||
| 		// Disable merging env values for the rest. | ||||
| 		env.SetEnvOff() | ||||
| 	} | ||||
| 
 | ||||
| 	if _, err := openid.LookupConfig(s[config.IdentityOpenIDSubSys][config.Default], | ||||
| 		NewGatewayHTTPTransport(), xhttp.DrainBody); err != nil { | ||||
| 		return err | ||||
| @ -467,33 +443,17 @@ func lookupConfigs(s config.Config, setDriveCounts []int) { | ||||
| 	} | ||||
| 
 | ||||
| 	if globalCacheConfig.Enabled { | ||||
| 		if cacheEncKey := env.Get(cache.EnvCacheEncryptionMasterKey, ""); cacheEncKey != "" { | ||||
| 			globalCacheKMS, err = crypto.ParseMasterKey(cacheEncKey) | ||||
| 		if cacheEncKey := env.Get(cache.EnvCacheEncryptionKey, ""); cacheEncKey != "" { | ||||
| 			globalCacheKMS, err = kms.Parse(cacheEncKey) | ||||
| 			if err != nil { | ||||
| 				logger.LogIf(ctx, fmt.Errorf("Unable to setup encryption cache: %w", err)) | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	kmsCfg, err := crypto.LookupConfig(s, globalCertsCADir.Get(), newCustomHTTPTransportWithHTTP2( | ||||
| 		&tls.Config{ | ||||
| 			RootCAs: globalRootCAs, | ||||
| 		}, defaultDialTimeout)()) | ||||
| 	if err != nil { | ||||
| 		logger.LogIf(ctx, fmt.Errorf("Unable to setup KMS config: %w", err)) | ||||
| 	} | ||||
| 
 | ||||
| 	GlobalKMS, err = crypto.NewKMS(kmsCfg) | ||||
| 	if err != nil { | ||||
| 		logger.LogIf(ctx, fmt.Errorf("Unable to setup KMS with current KMS config: %w", err)) | ||||
| 	} | ||||
| 	globalAutoEncryption = kmsCfg.AutoEncryption // Enable auto-encryption if enabled | ||||
| 
 | ||||
| 	if kmsCfg.Vault.Enabled { | ||||
| 		const deprecationWarning = `Native Hashicorp Vault support is deprecated and will be removed on 2021-10-01. Please migrate to KES + Hashicorp Vault: https://github.com/minio/kes/wiki/Hashicorp-Vault-Keystore | ||||
| Note that native Hashicorp Vault and KES + Hashicorp Vault are not compatible. | ||||
| If you need help to migrate smoothly visit: https://min.io/pricing` | ||||
| 		logger.LogIf(ctx, fmt.Errorf(deprecationWarning)) | ||||
| 	globalAutoEncryption = crypto.LookupAutoEncryption() // Enable auto-encryption if enabled | ||||
| 	if globalAutoEncryption && GlobalKMS == nil { | ||||
| 		logger.Fatal(errors.New("no KMS configured"), "MINIO_KMS_AUTO_ENCRYPTION requires a valid KMS configuration") | ||||
| 	} | ||||
| 
 | ||||
| 	globalOpenIDConfig, err = openid.LookupConfig(s[config.IdentityOpenIDSubSys][config.Default], | ||||
|  | ||||
| @ -20,57 +20,32 @@ import ( | ||||
| 	"bytes" | ||||
| 	"context" | ||||
| 	"fmt" | ||||
| 	"path" | ||||
| 	"time" | ||||
| 	"unicode/utf8" | ||||
| 
 | ||||
| 	"github.com/minio/minio/cmd/config" | ||||
| 	"github.com/minio/minio/cmd/logger" | ||||
| 	"github.com/minio/minio/pkg/auth" | ||||
| 	"github.com/minio/minio/pkg/kms" | ||||
| 	"github.com/minio/minio/pkg/madmin" | ||||
| 	etcd "go.etcd.io/etcd/clientv3" | ||||
| ) | ||||
| 
 | ||||
| func handleEncryptedConfigBackend(objAPI ObjectLayer) error { | ||||
| 
 | ||||
| 	encrypted, err := checkBackendEncrypted(objAPI) | ||||
| 	if err != nil { | ||||
| 		return fmt.Errorf("Unable to encrypt config %w", err) | ||||
| 	} | ||||
| 
 | ||||
| 	if encrypted { | ||||
| 		// backend is encrypted, but credentials are not specified | ||||
| 		// we shall fail right here. if not proceed forward. | ||||
| 		if !globalConfigEncrypted || !globalActiveCred.IsValid() { | ||||
| 			return config.ErrMissingCredentialsBackendEncrypted(nil) | ||||
| 		} | ||||
| 	} else { | ||||
| 		// backend is not yet encrypted, check if encryption of | ||||
| 		// backend is requested if not return nil and proceed | ||||
| 		// forward. | ||||
| 		if !globalConfigEncrypted { | ||||
| 			return nil | ||||
| 		} | ||||
| 		if !globalActiveCred.IsValid() { | ||||
| 			return config.ErrMissingCredentialsBackendEncrypted(nil) | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	// Migrate IAM configuration | ||||
| 	if err = migrateConfigPrefixToEncrypted(objAPI, globalOldCred, encrypted); err != nil { | ||||
| 	if err = migrateConfigPrefixToEncrypted(objAPI, encrypted); err != nil { | ||||
| 		return fmt.Errorf("Unable to migrate all config at .minio.sys/config/: %w", err) | ||||
| 	} | ||||
| 
 | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| const ( | ||||
| 	backendEncryptedFile = "backend-encrypted" | ||||
| ) | ||||
| const backendEncryptedFile = "backend-encrypted" | ||||
| 
 | ||||
| var ( | ||||
| 	backendEncryptedMigrationIncomplete = []byte("incomplete") | ||||
| 	backendEncryptedMigrationComplete   = []byte("encrypted") | ||||
| ) | ||||
| var backendEncryptedMigrationComplete = []byte("encrypted") | ||||
| 
 | ||||
| func checkBackendEtcdEncrypted(ctx context.Context, client *etcd.Client) (bool, error) { | ||||
| 	data, err := readKeyEtcd(ctx, client, backendEncryptedFile) | ||||
| @ -112,38 +87,15 @@ func migrateIAMConfigsEtcdToEncrypted(ctx context.Context, client *etcd.Client) | ||||
| 	} | ||||
| 
 | ||||
| 	if encrypted { | ||||
| 		// backend is encrypted, but credentials are not specified | ||||
| 		// we shall fail right here. if not proceed forward. | ||||
| 		if !globalConfigEncrypted || !globalActiveCred.IsValid() { | ||||
| 			return config.ErrMissingCredentialsBackendEncrypted(nil) | ||||
| 		if GlobalKMS != nil { | ||||
| 			stat, err := GlobalKMS.Stat() | ||||
| 			if err != nil { | ||||
| 				return err | ||||
| 			} | ||||
| 			logger.Info("Attempting to re-encrypt config, IAM users and policies on MinIO with %q (%s)", stat.DefaultKey, stat.Name) | ||||
| 		} else { | ||||
| 			logger.Info("Attempting to migrate encrypted config, IAM users and policies on MinIO to a plaintext format. To encrypt all MinIO config data a KMS is needed") | ||||
| 		} | ||||
| 	} else { | ||||
| 		// backend is not yet encrypted, check if encryption of | ||||
| 		// backend is requested if not return nil and proceed | ||||
| 		// forward. | ||||
| 		if !globalConfigEncrypted { | ||||
| 			return nil | ||||
| 		} | ||||
| 		if !globalActiveCred.IsValid() { | ||||
| 			return errInvalidArgument | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	if encrypted { | ||||
| 		// No key rotation requested, and backend is | ||||
| 		// already encrypted. We proceed without migration. | ||||
| 		if !globalOldCred.IsValid() { | ||||
| 			return nil | ||||
| 		} | ||||
| 
 | ||||
| 		// No real reason to rotate if old and new creds are same. | ||||
| 		if globalOldCred.Equal(globalActiveCred) { | ||||
| 			return nil | ||||
| 		} | ||||
| 
 | ||||
| 		logger.Info("Attempting rotation of encrypted IAM users and policies on etcd with newly supplied credentials") | ||||
| 	} else { | ||||
| 		logger.Info("Attempting encryption of all IAM users and policies on etcd") | ||||
| 	} | ||||
| 
 | ||||
| 	listCtx, cancel := context.WithTimeout(ctx, 1*time.Minute) | ||||
| @ -154,134 +106,89 @@ func migrateIAMConfigsEtcdToEncrypted(ctx context.Context, client *etcd.Client) | ||||
| 		return err | ||||
| 	} | ||||
| 
 | ||||
| 	if err = saveKeyEtcd(ctx, client, backendEncryptedFile, backendEncryptedMigrationIncomplete); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 
 | ||||
| 	for _, kv := range r.Kvs { | ||||
| 		var ( | ||||
| 			cdata    []byte | ||||
| 			cencdata []byte | ||||
| 		) | ||||
| 		cdata, err = readKeyEtcd(ctx, client, string(kv.Key)) | ||||
| 		if err != nil { | ||||
| 			switch err { | ||||
| 			case errConfigNotFound: | ||||
| 				// Perhaps not present or someone deleted it. | ||||
| 				continue | ||||
| 			} | ||||
| 			return err | ||||
| 		data, err := readKeyEtcd(ctx, client, string(kv.Key)) | ||||
| 		if err == errConfigNotFound { // Perhaps not present or someone deleted it. | ||||
| 			continue | ||||
| 		} | ||||
| 
 | ||||
| 		var data []byte | ||||
| 		// Is rotating of creds requested? | ||||
| 		if globalOldCred.IsValid() { | ||||
| 			data, err = decryptData(cdata, globalOldCred, globalActiveCred) | ||||
| 			if err != nil { | ||||
| 				if err == madmin.ErrMaliciousData { | ||||
| 					return config.ErrInvalidRotatingCredentialsBackendEncrypted(nil) | ||||
| 				} | ||||
| 				return err | ||||
| 			} | ||||
| 		} else { | ||||
| 			data = cdata | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 
 | ||||
| 		if !utf8.Valid(data) { | ||||
| 			_, err = decryptData(data, globalActiveCred) | ||||
| 			if err == nil { | ||||
| 				// Config is already encrypted with right keys | ||||
| 				continue | ||||
| 			data, err = decryptData(data, globalActiveCred) | ||||
| 			if err != nil { | ||||
| 				return fmt.Errorf("Decrypting config failed %w, possibly credentials are incorrect", err) | ||||
| 			} | ||||
| 			return fmt.Errorf("Decrypting config failed %w, possibly credentials are incorrect", err) | ||||
| 		} | ||||
| 
 | ||||
| 		cencdata, err = madmin.EncryptData(globalActiveCred.String(), data) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		if GlobalKMS != nil { | ||||
| 			data, err = config.EncryptBytes(GlobalKMS, data, kms.Context{ | ||||
| 				minioMetaBucket: string(kv.Key), | ||||
| 			}) | ||||
| 			if err != nil { | ||||
| 				return err | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		if err = saveKeyEtcd(ctx, client, string(kv.Key), cencdata); err != nil { | ||||
| 		if err = saveKeyEtcd(ctx, client, string(kv.Key), data); err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	if encrypted && globalActiveCred.IsValid() && globalOldCred.IsValid() { | ||||
| 		logger.Info("Rotation complete, please make sure to unset MINIO_ROOT_USER_OLD and MINIO_ROOT_PASSWORD_OLD envs") | ||||
| 	if encrypted { | ||||
| 		if GlobalKMS != nil { | ||||
| 			logger.Info("Migration of encrypted config data completed. All config data is now encrypted with the KMS") | ||||
| 		} else { | ||||
| 			logger.Info("Migration of encrypted config data completed. All config data is now stored in plaintext") | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	return saveKeyEtcd(ctx, client, backendEncryptedFile, backendEncryptedMigrationComplete) | ||||
| 	return deleteKeyEtcd(ctx, client, backendEncryptedFile) | ||||
| } | ||||
| 
 | ||||
| func migrateConfigPrefixToEncrypted(objAPI ObjectLayer, activeCredOld auth.Credentials, encrypted bool) error { | ||||
| 	if encrypted { | ||||
| 		// No key rotation requested, and backend is | ||||
| 		// already encrypted. We proceed without migration. | ||||
| 		if !activeCredOld.IsValid() { | ||||
| 			return nil | ||||
| 		} | ||||
| 
 | ||||
| 		// No real reason to rotate if old and new creds are same. | ||||
| 		if activeCredOld.Equal(globalActiveCred) { | ||||
| 			return nil | ||||
| 		} | ||||
| 		logger.Info("Attempting rotation of encrypted config, IAM users and policies on MinIO with newly supplied credentials") | ||||
| 	} else { | ||||
| 		logger.Info("Attempting encryption of all config, IAM users and policies on MinIO backend") | ||||
| func migrateConfigPrefixToEncrypted(objAPI ObjectLayer, encrypted bool) error { | ||||
| 	if !encrypted { | ||||
| 		return nil | ||||
| 	} | ||||
| 
 | ||||
| 	err := saveConfig(GlobalContext, objAPI, backendEncryptedFile, backendEncryptedMigrationIncomplete) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	if encrypted { | ||||
| 		if GlobalKMS != nil { | ||||
| 			stat, err := GlobalKMS.Stat() | ||||
| 			if err != nil { | ||||
| 				return err | ||||
| 			} | ||||
| 			logger.Info("Attempting to re-encrypt config, IAM users and policies on MinIO with %q (%s)", stat.DefaultKey, stat.Name) | ||||
| 		} else { | ||||
| 			logger.Info("Attempting to migrate encrypted config, IAM users and policies on MinIO to a plaintext format. To encrypt all MinIO config data a KMS is needed") | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	var marker string | ||||
| 	for { | ||||
| 		res, err := objAPI.ListObjects(GlobalContext, minioMetaBucket, | ||||
| 			minioConfigPrefix, marker, "", maxObjectList) | ||||
| 		res, err := objAPI.ListObjects(GlobalContext, minioMetaBucket, minioConfigPrefix, marker, "", maxObjectList) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 		for _, obj := range res.Objects { | ||||
| 			var ( | ||||
| 				cdata    []byte | ||||
| 				cencdata []byte | ||||
| 			) | ||||
| 
 | ||||
| 			cdata, err = readConfig(GlobalContext, objAPI, obj.Name) | ||||
| 			data, err := readConfig(GlobalContext, objAPI, obj.Name) | ||||
| 			if err != nil { | ||||
| 				return err | ||||
| 			} | ||||
| 
 | ||||
| 			var data []byte | ||||
| 			// Is rotating of creds requested? | ||||
| 			if activeCredOld.IsValid() { | ||||
| 				data, err = decryptData(cdata, activeCredOld, globalActiveCred) | ||||
| 				if err != nil { | ||||
| 					if err == madmin.ErrMaliciousData { | ||||
| 						return config.ErrInvalidRotatingCredentialsBackendEncrypted(nil) | ||||
| 					} | ||||
| 					return err | ||||
| 				} | ||||
| 			} else { | ||||
| 				data = cdata | ||||
| 			} | ||||
| 
 | ||||
| 			if !utf8.Valid(data) { | ||||
| 				_, err = decryptData(data, globalActiveCred) | ||||
| 				if err == nil { | ||||
| 					// Config is already encrypted with right keys | ||||
| 					continue | ||||
| 				data, err = decryptData(data, globalActiveCred) | ||||
| 				if err != nil { | ||||
| 					return fmt.Errorf("Decrypting config failed %w, possibly credentials are incorrect", err) | ||||
| 				} | ||||
| 				return fmt.Errorf("Decrypting config failed %w, possibly credentials are incorrect", err) | ||||
| 			} | ||||
| 
 | ||||
| 			cencdata, err = madmin.EncryptData(globalActiveCred.String(), data) | ||||
| 			if err != nil { | ||||
| 				return err | ||||
| 			if GlobalKMS != nil { | ||||
| 				data, err = config.EncryptBytes(GlobalKMS, data, kms.Context{ | ||||
| 					obj.Bucket: path.Join(obj.Bucket, obj.Name), | ||||
| 				}) | ||||
| 				if err != nil { | ||||
| 					return err | ||||
| 				} | ||||
| 			} | ||||
| 
 | ||||
| 			if err = saveConfig(GlobalContext, objAPI, obj.Name, cencdata); err != nil { | ||||
| 			if err = saveConfig(GlobalContext, objAPI, obj.Name, data); err != nil { | ||||
| 				return err | ||||
| 			} | ||||
| 		} | ||||
| @ -289,13 +196,14 @@ func migrateConfigPrefixToEncrypted(objAPI ObjectLayer, activeCredOld auth.Crede | ||||
| 		if !res.IsTruncated { | ||||
| 			break | ||||
| 		} | ||||
| 
 | ||||
| 		marker = res.NextMarker | ||||
| 	} | ||||
| 
 | ||||
| 	if encrypted && globalActiveCred.IsValid() && activeCredOld.IsValid() { | ||||
| 		logger.Info("Rotation complete, please make sure to unset MINIO_ROOT_USER_OLD and MINIO_ROOT_PASSWORD_OLD envs") | ||||
| 	if encrypted { | ||||
| 		if GlobalKMS != nil { | ||||
| 			logger.Info("Migration of encrypted config data completed. All config data is now encrypted with the KMS") | ||||
| 		} else { | ||||
| 			logger.Info("Migration of encrypted config data completed. All config data is now stored in plaintext") | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	return saveConfig(GlobalContext, objAPI, backendEncryptedFile, backendEncryptedMigrationComplete) | ||||
| 	return deleteConfig(GlobalContext, globalObjectAPI, backendEncryptedFile) | ||||
| } | ||||
|  | ||||
| @ -34,11 +34,11 @@ import ( | ||||
| 	"github.com/minio/minio/cmd/config/notify" | ||||
| 	"github.com/minio/minio/cmd/config/policy/opa" | ||||
| 	"github.com/minio/minio/cmd/config/storageclass" | ||||
| 	"github.com/minio/minio/cmd/crypto" | ||||
| 	"github.com/minio/minio/cmd/logger" | ||||
| 	"github.com/minio/minio/pkg/auth" | ||||
| 	"github.com/minio/minio/pkg/event" | ||||
| 	"github.com/minio/minio/pkg/event/target" | ||||
| 	"github.com/minio/minio/pkg/kms" | ||||
| 	"github.com/minio/minio/pkg/madmin" | ||||
| 	xnet "github.com/minio/minio/pkg/net" | ||||
| 	"github.com/minio/minio/pkg/quick" | ||||
| @ -2412,7 +2412,6 @@ func migrateV27ToV28() error { | ||||
| 	} | ||||
| 
 | ||||
| 	srvConfig.Version = "28" | ||||
| 	srvConfig.KMS = crypto.KMSConfig{} | ||||
| 	if err = quick.SaveConfig(srvConfig, configFile, globalEtcdClient); err != nil { | ||||
| 		return fmt.Errorf("Failed to migrate config from ‘27’ to ‘28’. %w", err) | ||||
| 	} | ||||
| @ -2507,13 +2506,28 @@ func checkConfigVersion(objAPI ObjectLayer, configFile string, version string) ( | ||||
| 		return false, nil, err | ||||
| 	} | ||||
| 
 | ||||
| 	if globalConfigEncrypted && !utf8.Valid(data) { | ||||
| 		data, err = madmin.DecryptData(globalActiveCred.String(), bytes.NewReader(data)) | ||||
| 		if err != nil { | ||||
| 			if err == madmin.ErrMaliciousData { | ||||
| 				return false, nil, config.ErrInvalidCredentialsBackendEncrypted(nil) | ||||
| 	if !utf8.Valid(data) { | ||||
| 		if GlobalKMS != nil { | ||||
| 			data, err = config.DecryptBytes(GlobalKMS, data, kms.Context{ | ||||
| 				minioMetaBucket: path.Join(minioMetaBucket, configFile), | ||||
| 			}) | ||||
| 			if err != nil { | ||||
| 				data, err = madmin.DecryptData(globalActiveCred.String(), bytes.NewReader(data)) | ||||
| 				if err != nil { | ||||
| 					if err == madmin.ErrMaliciousData { | ||||
| 						return false, nil, config.ErrInvalidCredentialsBackendEncrypted(nil) | ||||
| 					} | ||||
| 					return false, nil, err | ||||
| 				} | ||||
| 			} | ||||
| 		} else { | ||||
| 			data, err = madmin.DecryptData(globalActiveCred.String(), bytes.NewReader(data)) | ||||
| 			if err != nil { | ||||
| 				if err == madmin.ErrMaliciousData { | ||||
| 					return false, nil, config.ErrInvalidCredentialsBackendEncrypted(nil) | ||||
| 				} | ||||
| 				return false, nil, err | ||||
| 			} | ||||
| 			return false, nil, err | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| @ -2546,8 +2560,6 @@ func migrateV27ToV28MinioSys(objAPI ObjectLayer) error { | ||||
| 	} | ||||
| 
 | ||||
| 	cfg.Version = "28" | ||||
| 	cfg.KMS = crypto.KMSConfig{} | ||||
| 
 | ||||
| 	if err = saveServerConfig(GlobalContext, objAPI, cfg); err != nil { | ||||
| 		return fmt.Errorf("Failed to migrate config from ‘27’ to ‘28’. %w", err) | ||||
| 	} | ||||
| @ -2739,7 +2751,6 @@ func migrateMinioSysConfigToKV(objAPI ObjectLayer) error { | ||||
| 		logger.SetLoggerHTTPAudit(newCfg, k, auditArgs) | ||||
| 	} | ||||
| 
 | ||||
| 	crypto.SetKMSConfig(newCfg, cfg.KMS) | ||||
| 	xldap.SetIdentityLDAP(newCfg, cfg.LDAPServerConfig) | ||||
| 	openid.SetIdentityOpenID(newCfg, cfg.OpenID) | ||||
| 	opa.SetPolicyOPAConfig(newCfg, cfg.Policy.OPA) | ||||
|  | ||||
| @ -27,7 +27,6 @@ import ( | ||||
| 	"github.com/minio/minio/cmd/config/notify" | ||||
| 	"github.com/minio/minio/cmd/config/policy/opa" | ||||
| 	"github.com/minio/minio/cmd/config/storageclass" | ||||
| 	"github.com/minio/minio/cmd/crypto" | ||||
| 	"github.com/minio/minio/cmd/logger" | ||||
| 	"github.com/minio/minio/pkg/auth" | ||||
| 	"github.com/minio/minio/pkg/event/target" | ||||
| @ -713,9 +712,6 @@ type serverConfigV28 struct { | ||||
| 	// Cache configuration | ||||
| 	Cache cache.Config `json:"cache"` | ||||
| 
 | ||||
| 	// KMS configuration | ||||
| 	KMS crypto.KMSConfig `json:"kms"` | ||||
| 
 | ||||
| 	// Notification queue configuration. | ||||
| 	Notify notifierV3 `json:"notify"` | ||||
| 
 | ||||
| @ -742,9 +738,6 @@ type serverConfigV30 struct { | ||||
| 	// Cache configuration | ||||
| 	Cache cache.Config `json:"cache"` | ||||
| 
 | ||||
| 	// KMS configuration | ||||
| 	KMS crypto.KMSConfig `json:"kms"` | ||||
| 
 | ||||
| 	// Notification queue configuration. | ||||
| 	Notify notifierV3 `json:"notify"` | ||||
| 
 | ||||
| @ -770,9 +763,6 @@ type serverConfigV31 struct { | ||||
| 	// Cache configuration | ||||
| 	Cache cache.Config `json:"cache"` | ||||
| 
 | ||||
| 	// KMS configuration | ||||
| 	KMS crypto.KMSConfig `json:"kms"` | ||||
| 
 | ||||
| 	// Notification queue configuration. | ||||
| 	Notify notifierV3 `json:"notify"` | ||||
| 
 | ||||
| @ -809,9 +799,6 @@ type serverConfigV32 struct { | ||||
| 	// Cache configuration | ||||
| 	Cache cache.Config `json:"cache"` | ||||
| 
 | ||||
| 	// KMS configuration | ||||
| 	KMS crypto.KMSConfig `json:"kms"` | ||||
| 
 | ||||
| 	// Notification queue configuration. | ||||
| 	Notify notify.Config `json:"notify"` | ||||
| 
 | ||||
| @ -850,9 +837,6 @@ type serverConfigV33 struct { | ||||
| 	// Cache configuration | ||||
| 	Cache cache.Config `json:"cache"` | ||||
| 
 | ||||
| 	// KMS configuration | ||||
| 	KMS crypto.KMSConfig `json:"kms"` | ||||
| 
 | ||||
| 	// Notification queue configuration. | ||||
| 	Notify notify.Config `json:"notify"` | ||||
| 
 | ||||
|  | ||||
| @ -17,7 +17,6 @@ | ||||
| package cmd | ||||
| 
 | ||||
| import ( | ||||
| 	"bytes" | ||||
| 	"context" | ||||
| 	"encoding/json" | ||||
| 	"path" | ||||
| @ -27,6 +26,7 @@ import ( | ||||
| 
 | ||||
| 	jsoniter "github.com/json-iterator/go" | ||||
| 	"github.com/minio/minio/cmd/config" | ||||
| 	"github.com/minio/minio/pkg/kms" | ||||
| 	"github.com/minio/minio/pkg/madmin" | ||||
| ) | ||||
| 
 | ||||
| @ -64,8 +64,10 @@ func listServerConfigHistory(ctx context.Context, objAPI ObjectLayer, withData b | ||||
| 				if err != nil { | ||||
| 					return nil, err | ||||
| 				} | ||||
| 				if globalConfigEncrypted && !utf8.Valid(data) { | ||||
| 					data, err = madmin.DecryptData(globalActiveCred.String(), bytes.NewReader(data)) | ||||
| 				if GlobalKMS != nil { | ||||
| 					data, err = config.DecryptBytes(GlobalKMS, data, kms.Context{ | ||||
| 						obj.Bucket: path.Join(obj.Bucket, obj.Name), | ||||
| 					}) | ||||
| 					if err != nil { | ||||
| 						return nil, err | ||||
| 					} | ||||
| @ -103,10 +105,11 @@ func readServerConfigHistory(ctx context.Context, objAPI ObjectLayer, uuidKV str | ||||
| 		return nil, err | ||||
| 	} | ||||
| 
 | ||||
| 	if globalConfigEncrypted && !utf8.Valid(data) { | ||||
| 		data, err = madmin.DecryptData(globalActiveCred.String(), bytes.NewReader(data)) | ||||
| 	if GlobalKMS != nil { | ||||
| 		data, err = config.DecryptBytes(GlobalKMS, data, kms.Context{ | ||||
| 			minioMetaBucket: path.Join(minioMetaBucket, historyFile), | ||||
| 		}) | ||||
| 	} | ||||
| 
 | ||||
| 	return data, err | ||||
| } | ||||
| 
 | ||||
| @ -114,39 +117,39 @@ func saveServerConfigHistory(ctx context.Context, objAPI ObjectLayer, kv []byte) | ||||
| 	uuidKV := mustGetUUID() + kvPrefix | ||||
| 	historyFile := pathJoin(minioConfigHistoryPrefix, uuidKV) | ||||
| 
 | ||||
| 	var err error | ||||
| 	if globalConfigEncrypted { | ||||
| 		kv, err = madmin.EncryptData(globalActiveCred.String(), kv) | ||||
| 	if GlobalKMS != nil { | ||||
| 		var err error | ||||
| 		kv, err = config.EncryptBytes(GlobalKMS, kv, kms.Context{ | ||||
| 			minioMetaBucket: path.Join(minioMetaBucket, historyFile), | ||||
| 		}) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	// Save the new config KV settings into the history path. | ||||
| 	return saveConfig(ctx, objAPI, historyFile, kv) | ||||
| } | ||||
| 
 | ||||
| func saveServerConfig(ctx context.Context, objAPI ObjectLayer, config interface{}) error { | ||||
| 	data, err := json.Marshal(config) | ||||
| func saveServerConfig(ctx context.Context, objAPI ObjectLayer, cfg interface{}) error { | ||||
| 	data, err := json.Marshal(cfg) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 
 | ||||
| 	if globalConfigEncrypted { | ||||
| 		data, err = madmin.EncryptData(globalActiveCred.String(), data) | ||||
| 	var configFile = path.Join(minioConfigPrefix, minioConfigFile) | ||||
| 	if GlobalKMS != nil { | ||||
| 		data, err = config.EncryptBytes(GlobalKMS, data, kms.Context{ | ||||
| 			minioMetaBucket: path.Join(minioMetaBucket, configFile), | ||||
| 		}) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	configFile := path.Join(minioConfigPrefix, minioConfigFile) | ||||
| 	// Save the new config in the std config path | ||||
| 	return saveConfig(ctx, objAPI, configFile, data) | ||||
| } | ||||
| 
 | ||||
| func readServerConfig(ctx context.Context, objAPI ObjectLayer) (config.Config, error) { | ||||
| 	configFile := path.Join(minioConfigPrefix, minioConfigFile) | ||||
| 	configData, err := readConfig(ctx, objAPI, configFile) | ||||
| 	data, err := readConfig(ctx, objAPI, configFile) | ||||
| 	if err != nil { | ||||
| 		// Config not found for some reason, allow things to continue | ||||
| 		// by initializing a new fresh config in safe mode. | ||||
| @ -156,19 +159,18 @@ func readServerConfig(ctx context.Context, objAPI ObjectLayer) (config.Config, e | ||||
| 		return nil, err | ||||
| 	} | ||||
| 
 | ||||
| 	if globalConfigEncrypted && !utf8.Valid(configData) { | ||||
| 		configData, err = madmin.DecryptData(globalActiveCred.String(), bytes.NewReader(configData)) | ||||
| 	if GlobalKMS != nil && !utf8.Valid(data) { | ||||
| 		data, err = config.DecryptBytes(GlobalKMS, data, kms.Context{ | ||||
| 			minioMetaBucket: path.Join(minioMetaBucket, configFile), | ||||
| 		}) | ||||
| 		if err != nil { | ||||
| 			if err == madmin.ErrMaliciousData { | ||||
| 				return nil, config.ErrInvalidCredentialsBackendEncrypted(nil) | ||||
| 			} | ||||
| 			return nil, err | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	var srvCfg = config.New() | ||||
| 	var json = jsoniter.ConfigCompatibleWithStandardLibrary | ||||
| 	if err = json.Unmarshal(configData, &srvCfg); err != nil { | ||||
| 	if err = json.Unmarshal(data, &srvCfg); err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 
 | ||||
|  | ||||
							
								
								
									
										2
									
								
								cmd/config/cache/lookup.go
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								cmd/config/cache/lookup.go
									
									
									
									
										vendored
									
									
								
							| @ -48,7 +48,7 @@ const ( | ||||
| 	EnvCacheRange         = "MINIO_CACHE_RANGE" | ||||
| 	EnvCacheCommit        = "MINIO_CACHE_COMMIT" | ||||
| 
 | ||||
| 	EnvCacheEncryptionMasterKey = "MINIO_CACHE_ENCRYPTION_MASTER_KEY" | ||||
| 	EnvCacheEncryptionKey = "MINIO_CACHE_ENCRYPTION_SECRET_KEY" | ||||
| 
 | ||||
| 	DefaultExpiry        = "90" | ||||
| 	DefaultQuota         = "80" | ||||
|  | ||||
| @ -23,24 +23,29 @@ const ( | ||||
| 
 | ||||
| // Top level common ENVs | ||||
| const ( | ||||
| 	EnvAccessKey       = "MINIO_ACCESS_KEY" | ||||
| 	EnvSecretKey       = "MINIO_SECRET_KEY" | ||||
| 	EnvRootUser        = "MINIO_ROOT_USER" | ||||
| 	EnvRootPassword    = "MINIO_ROOT_PASSWORD" | ||||
| 	EnvAccessKeyOld    = "MINIO_ACCESS_KEY_OLD" | ||||
| 	EnvSecretKeyOld    = "MINIO_SECRET_KEY_OLD" | ||||
| 	EnvRootUserOld     = "MINIO_ROOT_USER_OLD" | ||||
| 	EnvRootPasswordOld = "MINIO_ROOT_PASSWORD_OLD" | ||||
| 	EnvBrowser         = "MINIO_BROWSER" | ||||
| 	EnvDomain          = "MINIO_DOMAIN" | ||||
| 	EnvRegionName      = "MINIO_REGION_NAME" | ||||
| 	EnvPublicIPs       = "MINIO_PUBLIC_IPS" | ||||
| 	EnvFSOSync         = "MINIO_FS_OSYNC" | ||||
| 	EnvArgs            = "MINIO_ARGS" | ||||
| 	EnvDNSWebhook      = "MINIO_DNS_WEBHOOK_ENDPOINT" | ||||
| 	EnvAccessKey    = "MINIO_ACCESS_KEY" | ||||
| 	EnvSecretKey    = "MINIO_SECRET_KEY" | ||||
| 	EnvRootUser     = "MINIO_ROOT_USER" | ||||
| 	EnvRootPassword = "MINIO_ROOT_PASSWORD" | ||||
| 
 | ||||
| 	EnvBrowser    = "MINIO_BROWSER" | ||||
| 	EnvDomain     = "MINIO_DOMAIN" | ||||
| 	EnvRegionName = "MINIO_REGION_NAME" | ||||
| 	EnvPublicIPs  = "MINIO_PUBLIC_IPS" | ||||
| 	EnvFSOSync    = "MINIO_FS_OSYNC" | ||||
| 	EnvArgs       = "MINIO_ARGS" | ||||
| 	EnvDNSWebhook = "MINIO_DNS_WEBHOOK_ENDPOINT" | ||||
| 
 | ||||
| 	EnvUpdate = "MINIO_UPDATE" | ||||
| 
 | ||||
| 	EnvKMSMasterKey  = "MINIO_KMS_MASTER_KEY" // legacy | ||||
| 	EnvKMSSecretKey  = "MINIO_KMS_SECRET_KEY" | ||||
| 	EnvKESEndpoint   = "MINIO_KMS_KES_ENDPOINT" | ||||
| 	EnvKESKeyName    = "MINIO_KMS_KES_KEY_NAME" | ||||
| 	EnvKESClientKey  = "MINIO_KMS_KES_KEY_FILE" | ||||
| 	EnvKESClientCert = "MINIO_KMS_KES_CERT_FILE" | ||||
| 	EnvKESServerCA   = "MINIO_KMS_KES_CAPATH" | ||||
| 
 | ||||
| 	EnvEndpoints = "MINIO_ENDPOINTS" // legacy | ||||
| 	EnvWorm      = "MINIO_WORM"      // legacy | ||||
| 	EnvRegion    = "MINIO_REGION"    // legacy | ||||
|  | ||||
| @ -28,6 +28,30 @@ import ( | ||||
| 	"github.com/secure-io/sio-go/sioutil" | ||||
| ) | ||||
| 
 | ||||
| // EncryptBytes encrypts the plaintext with a key managed by KMS. | ||||
| // The context is bound to the returned ciphertext. | ||||
| // | ||||
| // The same context must be provided when decrypting the | ||||
| // ciphertext. | ||||
| func EncryptBytes(KMS kms.KMS, plaintext []byte, context kms.Context) ([]byte, error) { | ||||
| 	ciphertext, err := Encrypt(KMS, bytes.NewReader(plaintext), context) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	return io.ReadAll(ciphertext) | ||||
| } | ||||
| 
 | ||||
| // DecryptBytes decrypts the ciphertext using a key managed by the KMS. | ||||
| // The same context that have been used during encryption must be | ||||
| // provided. | ||||
| func DecryptBytes(KMS kms.KMS, ciphertext []byte, context kms.Context) ([]byte, error) { | ||||
| 	plaintext, err := Decrypt(KMS, bytes.NewReader(ciphertext), context) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	return io.ReadAll(plaintext) | ||||
| } | ||||
| 
 | ||||
| // Encrypt encrypts the plaintext with a key managed by KMS. | ||||
| // The context is bound to the returned ciphertext. | ||||
| // | ||||
|  | ||||
| @ -15,12 +15,7 @@ | ||||
| package crypto | ||||
| 
 | ||||
| import ( | ||||
| 	"errors" | ||||
| 	"math/rand" | ||||
| 	"net/http" | ||||
| 	"os" | ||||
| 	"reflect" | ||||
| 	"strconv" | ||||
| 	"strings" | ||||
| 
 | ||||
| 	"github.com/minio/minio/cmd/config" | ||||
| @ -29,101 +24,7 @@ import ( | ||||
| 	xnet "github.com/minio/minio/pkg/net" | ||||
| ) | ||||
| 
 | ||||
| // KMSConfig has the KMS config for hashicorp vault | ||||
| type KMSConfig struct { | ||||
| 	AutoEncryption bool        `json:"-"` | ||||
| 	Vault          VaultConfig `json:"vault"` | ||||
| 	Kes            KesConfig   `json:"kes"` | ||||
| } | ||||
| 
 | ||||
| // KMS Vault constants. | ||||
| const ( | ||||
| 	KMSVaultEndpoint      = "endpoint" | ||||
| 	KMSVaultCAPath        = "capath" | ||||
| 	KMSVaultKeyName       = "key_name" | ||||
| 	KMSVaultKeyVersion    = "key_version" | ||||
| 	KMSVaultNamespace     = "namespace" | ||||
| 	KMSVaultAuthType      = "auth_type" | ||||
| 	KMSVaultAppRoleID     = "auth_approle_id" | ||||
| 	KMSVaultAppRoleSecret = "auth_approle_secret" | ||||
| ) | ||||
| 
 | ||||
| // KMS kes constants. | ||||
| const ( | ||||
| 	KMSKesEndpoint = "endpoint" | ||||
| 	KMSKesKeyFile  = "key_file" | ||||
| 	KMSKesCertFile = "cert_file" | ||||
| 	KMSKesCAPath   = "capath" | ||||
| 	KMSKesKeyName  = "key_name" | ||||
| ) | ||||
| 
 | ||||
| // DefaultKVS - default KV crypto config | ||||
| var ( | ||||
| 	DefaultVaultKVS = config.KVS{ | ||||
| 		config.KV{ | ||||
| 			Key:   KMSVaultEndpoint, | ||||
| 			Value: "", | ||||
| 		}, | ||||
| 		config.KV{ | ||||
| 			Key:   KMSVaultKeyName, | ||||
| 			Value: "", | ||||
| 		}, | ||||
| 		config.KV{ | ||||
| 			Key:   KMSVaultAuthType, | ||||
| 			Value: "approle", | ||||
| 		}, | ||||
| 		config.KV{ | ||||
| 			Key:   KMSVaultAppRoleID, | ||||
| 			Value: "", | ||||
| 		}, | ||||
| 		config.KV{ | ||||
| 			Key:   KMSVaultAppRoleSecret, | ||||
| 			Value: "", | ||||
| 		}, | ||||
| 		config.KV{ | ||||
| 			Key:   KMSVaultCAPath, | ||||
| 			Value: "", | ||||
| 		}, | ||||
| 		config.KV{ | ||||
| 			Key:   KMSVaultKeyVersion, | ||||
| 			Value: "", | ||||
| 		}, | ||||
| 		config.KV{ | ||||
| 			Key:   KMSVaultNamespace, | ||||
| 			Value: "", | ||||
| 		}, | ||||
| 	} | ||||
| 
 | ||||
| 	DefaultKesKVS = config.KVS{ | ||||
| 		config.KV{ | ||||
| 			Key:   KMSKesEndpoint, | ||||
| 			Value: "", | ||||
| 		}, | ||||
| 		config.KV{ | ||||
| 			Key:   KMSKesKeyName, | ||||
| 			Value: "", | ||||
| 		}, | ||||
| 		config.KV{ | ||||
| 			Key:   KMSKesCertFile, | ||||
| 			Value: "", | ||||
| 		}, | ||||
| 		config.KV{ | ||||
| 			Key:   KMSKesKeyFile, | ||||
| 			Value: "", | ||||
| 		}, | ||||
| 		config.KV{ | ||||
| 			Key:   KMSKesCAPath, | ||||
| 			Value: "", | ||||
| 		}, | ||||
| 	} | ||||
| ) | ||||
| 
 | ||||
| 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_KMS_MASTER_KEY" | ||||
| 
 | ||||
| 	// EnvKMSAutoEncryption is the environment variable used to en/disable | ||||
| 	// SSE-S3 auto-encryption. SSE-S3 auto-encryption, if enabled, | ||||
| 	// requires a valid KMS configuration and turns any non-SSE-C | ||||
| @ -132,301 +33,49 @@ const ( | ||||
| 	EnvKMSAutoEncryption = "MINIO_KMS_AUTO_ENCRYPTION" | ||||
| ) | ||||
| 
 | ||||
| const ( | ||||
| 	// EnvKMSVaultEndpoint is the environment variable used to specify | ||||
| 	// the vault HTTPS endpoint. | ||||
| 	EnvKMSVaultEndpoint = "MINIO_KMS_VAULT_ENDPOINT" | ||||
| 
 | ||||
| 	// EnvKMSVaultAuthType is the environment variable used to specify | ||||
| 	// the authentication type for vault. | ||||
| 	EnvKMSVaultAuthType = "MINIO_KMS_VAULT_AUTH_TYPE" | ||||
| 
 | ||||
| 	// EnvKMSVaultAppRoleID is the environment variable used to specify | ||||
| 	// the vault AppRole ID. | ||||
| 	EnvKMSVaultAppRoleID = "MINIO_KMS_VAULT_APPROLE_ID" | ||||
| 
 | ||||
| 	// EnvKMSVaultAppSecretID is the environment variable used to specify | ||||
| 	// the vault AppRole secret corresponding to the AppRole ID. | ||||
| 	EnvKMSVaultAppSecretID = "MINIO_KMS_VAULT_APPROLE_SECRET" | ||||
| 
 | ||||
| 	// EnvKMSVaultKeyVersion is the environment variable used to specify | ||||
| 	// the vault key version. | ||||
| 	EnvKMSVaultKeyVersion = "MINIO_KMS_VAULT_KEY_VERSION" | ||||
| 
 | ||||
| 	// EnvKMSVaultKeyName 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). | ||||
| 	EnvKMSVaultKeyName = "MINIO_KMS_VAULT_KEY_NAME" | ||||
| 
 | ||||
| 	// EnvKMSVaultCAPath 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. | ||||
| 	EnvKMSVaultCAPath = "MINIO_KMS_VAULT_CAPATH" | ||||
| 
 | ||||
| 	// EnvKMSVaultNamespace is the environment variable used to specify | ||||
| 	// vault namespace. The vault namespace is used if the enterprise | ||||
| 	// version of Hashicorp Vault is used. | ||||
| 	EnvKMSVaultNamespace = "MINIO_KMS_VAULT_NAMESPACE" | ||||
| ) | ||||
| 
 | ||||
| const ( | ||||
| 	// EnvKMSKesEndpoint is the environment variable used to specify | ||||
| 	// one or multiple KES server HTTPS endpoints. The individual | ||||
| 	// endpoints should be separated by ','. | ||||
| 	EnvKMSKesEndpoint = "MINIO_KMS_KES_ENDPOINT" | ||||
| 
 | ||||
| 	// EnvKMSKesKeyFile is the environment variable used to specify | ||||
| 	// the TLS private key used by MinIO to authenticate to the kes | ||||
| 	// server HTTPS via mTLS. | ||||
| 	EnvKMSKesKeyFile = "MINIO_KMS_KES_KEY_FILE" | ||||
| 
 | ||||
| 	// EnvKMSKesCertFile is the environment variable used to specify | ||||
| 	// the TLS certificate used by MinIO to authenticate to the kes | ||||
| 	// server HTTPS via mTLS. | ||||
| 	EnvKMSKesCertFile = "MINIO_KMS_KES_CERT_FILE" | ||||
| 
 | ||||
| 	// EnvKMSKesCAPath is the environment variable used to specify | ||||
| 	// the TLS root certificates used by MinIO to verify the certificate | ||||
| 	// presented by to the kes server when establishing a TLS connection. | ||||
| 	EnvKMSKesCAPath = "MINIO_KMS_KES_CA_PATH" | ||||
| 
 | ||||
| 	// EnvKMSKesKeyName is the environment variable used to specify | ||||
| 	// the (default) key at the kes server. In the S3 context it's | ||||
| 	// referred as customer master key ID (CMK-ID). | ||||
| 	EnvKMSKesKeyName = "MINIO_KMS_KES_KEY_NAME" | ||||
| ) | ||||
| 
 | ||||
| var defaultVaultCfg = VaultConfig{ | ||||
| 	Auth: VaultAuth{ | ||||
| 		Type: "approle", | ||||
| 	}, | ||||
| } | ||||
| 
 | ||||
| var defaultKesCfg = KesConfig{} | ||||
| 
 | ||||
| // EnabledVault returns true if HashiCorp Vault is enabled. | ||||
| func EnabledVault(kvs config.KVS) bool { | ||||
| 	endpoint := kvs.Get(KMSVaultEndpoint) | ||||
| 	return endpoint != "" | ||||
| } | ||||
| 
 | ||||
| // EnabledKes returns true if kes as KMS is enabled. | ||||
| func EnabledKes(kvs config.KVS) bool { | ||||
| 	endpoint := kvs.Get(KMSKesEndpoint) | ||||
| 	return endpoint != "" | ||||
| } | ||||
| 
 | ||||
| // LookupKesConfig lookup kes server configuration. | ||||
| func LookupKesConfig(kvs config.KVS) (KesConfig, error) { | ||||
| 	kesCfg := KesConfig{} | ||||
| 
 | ||||
| 	endpointStr := env.Get(EnvKMSKesEndpoint, kvs.Get(KMSKesEndpoint)) | ||||
| 	var endpoints []string | ||||
| // ParseKESEndpoints parses the given endpoint string and | ||||
| // returns a list of valid endpoint URLs. The order of the | ||||
| // returned endpoints is randomized. | ||||
| func ParseKESEndpoints(endpointStr string) ([]string, error) { | ||||
| 	var rawEndpoints []string | ||||
| 	for _, endpoint := range strings.Split(endpointStr, ",") { | ||||
| 		if strings.TrimSpace(endpoint) == "" { | ||||
| 			continue | ||||
| 		} | ||||
| 		if !ellipses.HasEllipses(endpoint) { | ||||
| 			endpoints = append(endpoints, endpoint) | ||||
| 			rawEndpoints = append(rawEndpoints, endpoint) | ||||
| 			continue | ||||
| 		} | ||||
| 		pattern, err := ellipses.FindEllipsesPatterns(endpoint) | ||||
| 		if err != nil { | ||||
| 			return kesCfg, err | ||||
| 			return nil, Errorf("Invalid KES endpoint %q: %v", endpointStr, err) | ||||
| 		} | ||||
| 		for _, p := range pattern { | ||||
| 			endpoints = append(endpoints, p.Expand()...) | ||||
| 			rawEndpoints = append(rawEndpoints, p.Expand()...) | ||||
| 		} | ||||
| 	} | ||||
| 	if len(endpoints) == 0 { | ||||
| 		return kesCfg, nil | ||||
| 	if len(rawEndpoints) == 0 { | ||||
| 		return nil, Errorf("Invalid KES endpoint %q", 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 { | ||||
| 	var ( | ||||
| 		randNum   = rand.Intn(len(rawEndpoints)) | ||||
| 		endpoints = make([]string, len(rawEndpoints)) | ||||
| 	) | ||||
| 	for i, endpoint := range rawEndpoints { | ||||
| 		endpoint, err := xnet.ParseHTTPURL(endpoint) | ||||
| 		if err != nil { | ||||
| 			return kesCfg, err | ||||
| 			return nil, Errorf("Invalid KES endpoint %q: %v", endpointStr, err) | ||||
| 		} | ||||
| 		kesCfg.Endpoint[(randNum+i)%len(endpoints)] = endpoint.String() | ||||
| 		endpoints[(randNum+i)%len(rawEndpoints)] = endpoint.String() | ||||
| 	} | ||||
| 	kesCfg.KeyFile = env.Get(EnvKMSKesKeyFile, kvs.Get(KMSKesKeyFile)) | ||||
| 	kesCfg.CertFile = env.Get(EnvKMSKesCertFile, kvs.Get(KMSKesCertFile)) | ||||
| 	kesCfg.CAPath = env.Get(EnvKMSKesCAPath, kvs.Get(KMSKesCAPath)) | ||||
| 	kesCfg.DefaultKeyID = env.Get(EnvKMSKesKeyName, kvs.Get(KMSKesKeyName)) | ||||
| 
 | ||||
| 	if reflect.DeepEqual(kesCfg, defaultKesCfg) { | ||||
| 		return kesCfg, nil | ||||
| 	} | ||||
| 
 | ||||
| 	// Verify all the proper settings. | ||||
| 	if err := kesCfg.Verify(); err != nil { | ||||
| 		return kesCfg, err | ||||
| 	} | ||||
| 	kesCfg.Enabled = true | ||||
| 	return kesCfg, nil | ||||
| 	return endpoints, nil | ||||
| } | ||||
| 
 | ||||
| func lookupAutoEncryption() (bool, error) { | ||||
| 	autoBool, err := config.ParseBool(env.Get(EnvAutoEncryptionLegacy, config.EnableOff)) | ||||
| 	if err != nil { | ||||
| 		return false, err | ||||
| 	} | ||||
| 	if !autoBool { | ||||
| 		autoBool, err = config.ParseBool(env.Get(EnvKMSAutoEncryption, config.EnableOff)) | ||||
| 		if err != nil { | ||||
| 			return false, err | ||||
| 		} | ||||
| 	} | ||||
| 	return autoBool, nil | ||||
| } | ||||
| 
 | ||||
| // LookupConfig lookup vault or kes config, returns KMSConfig | ||||
| // to configure KMS object for object encryption | ||||
| func LookupConfig(c config.Config, defaultRootCAsDir string, transport *http.Transport) (KMSConfig, error) { | ||||
| 	vcfg, err := LookupVaultConfig(c[config.KmsVaultSubSys][config.Default]) | ||||
| 	if err != nil { | ||||
| 		return KMSConfig{}, err | ||||
| 	} | ||||
| 	kesCfg, err := LookupKesConfig(c[config.KmsKesSubSys][config.Default]) | ||||
| 	if err != nil { | ||||
| 		return KMSConfig{}, err | ||||
| 	} | ||||
| 	kesCfg.Transport = transport | ||||
| 	if kesCfg.Enabled && kesCfg.CAPath == "" { | ||||
| 		kesCfg.CAPath = defaultRootCAsDir | ||||
| 	} | ||||
| 	autoEncrypt, err := lookupAutoEncryption() | ||||
| 	if err != nil { | ||||
| 		return KMSConfig{}, err | ||||
| 	} | ||||
| 	kmsCfg := KMSConfig{ | ||||
| 		AutoEncryption: autoEncrypt, | ||||
| 		Vault:          vcfg, | ||||
| 		Kes:            kesCfg, | ||||
| 	} | ||||
| 	return kmsCfg, nil | ||||
| } | ||||
| 
 | ||||
| // LookupVaultConfig 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 succes. | ||||
| func LookupVaultConfig(kvs config.KVS) (VaultConfig, error) { | ||||
| 	if err := config.CheckValidKeys(config.KmsVaultSubSys, kvs, DefaultVaultKVS); err != nil { | ||||
| 		return VaultConfig{}, err | ||||
| 	} | ||||
| 
 | ||||
| 	vcfg, err := lookupConfigLegacy(kvs) | ||||
| 	if err != nil { | ||||
| 		return vcfg, err | ||||
| 	} | ||||
| 
 | ||||
| 	if vcfg.Enabled { | ||||
| 		return vcfg, nil | ||||
| 	} | ||||
| 
 | ||||
| 	vcfg = VaultConfig{ | ||||
| 		Auth: VaultAuth{ | ||||
| 			Type: "approle", | ||||
| 		}, | ||||
| 	} | ||||
| 
 | ||||
| 	endpointStr := env.Get(EnvKMSVaultEndpoint, kvs.Get(KMSVaultEndpoint)) | ||||
| 	if endpointStr != "" { | ||||
| 		// Lookup Hashicorp-Vault configuration & overwrite config entry if ENV var is present | ||||
| 		endpoint, err := xnet.ParseHTTPURL(endpointStr) | ||||
| 		if err != nil { | ||||
| 			return vcfg, err | ||||
| 		} | ||||
| 		endpointStr = endpoint.String() | ||||
| 	} | ||||
| 
 | ||||
| 	vcfg.Endpoint = endpointStr | ||||
| 	vcfg.CAPath = env.Get(EnvKMSVaultCAPath, kvs.Get(KMSVaultCAPath)) | ||||
| 	vcfg.Auth.Type = env.Get(EnvKMSVaultAuthType, kvs.Get(KMSVaultAuthType)) | ||||
| 	if vcfg.Auth.Type == "" { | ||||
| 		vcfg.Auth.Type = "approle" | ||||
| 	} | ||||
| 
 | ||||
| 	vcfg.Auth.AppRole.ID = env.Get(EnvKMSVaultAppRoleID, kvs.Get(KMSVaultAppRoleID)) | ||||
| 	vcfg.Auth.AppRole.Secret = env.Get(EnvKMSVaultAppSecretID, kvs.Get(KMSVaultAppRoleSecret)) | ||||
| 	vcfg.Key.Name = env.Get(EnvKMSVaultKeyName, kvs.Get(KMSVaultKeyName)) | ||||
| 	vcfg.Namespace = env.Get(EnvKMSVaultNamespace, kvs.Get(KMSVaultNamespace)) | ||||
| 	if keyVersion := env.Get(EnvKMSVaultKeyVersion, kvs.Get(KMSVaultKeyVersion)); keyVersion != "" { | ||||
| 		vcfg.Key.Version, err = strconv.Atoi(keyVersion) | ||||
| 		if err != nil { | ||||
| 			return vcfg, Errorf("Unable to parse VaultKeyVersion value (`%s`)", keyVersion) | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	if reflect.DeepEqual(vcfg, defaultVaultCfg) { | ||||
| 		return vcfg, nil | ||||
| 	} | ||||
| 
 | ||||
| 	// Verify all the proper settings. | ||||
| 	if err = vcfg.Verify(); err != nil { | ||||
| 		return vcfg, err | ||||
| 	} | ||||
| 
 | ||||
| 	vcfg.Enabled = true | ||||
| 	return vcfg, nil | ||||
| } | ||||
| 
 | ||||
| // NewKMS - initialize a new KMS. | ||||
| func NewKMS(cfg KMSConfig) (kms KMS, err error) { | ||||
| 	// Lookup KMS master kes - only available through ENV. | ||||
| 	if masterKeyLegacy := env.Get(EnvKMSMasterKeyLegacy, ""); len(masterKeyLegacy) != 0 { | ||||
| 		if cfg.Vault.Enabled { // Vault and KMS master key provided | ||||
| 			return kms, errors.New("Ambiguous KMS configuration: vault configuration and a master key are provided at the same time") | ||||
| 		} | ||||
| 		if cfg.Kes.Enabled { | ||||
| 			return kms, errors.New("Ambiguous KMS configuration: kes configuration and a master key are provided at the same time") | ||||
| 		} | ||||
| 		kms, err = ParseMasterKey(masterKeyLegacy) | ||||
| 		if err != nil { | ||||
| 			return kms, err | ||||
| 		} | ||||
| 	} else if masterKey := env.Get(EnvKMSMasterKey, ""); len(masterKey) != 0 { | ||||
| 		if cfg.Vault.Enabled { // Vault and KMS master key provided | ||||
| 			return kms, errors.New("Ambiguous KMS configuration: vault configuration and a master key are provided at the same time") | ||||
| 		} | ||||
| 		if cfg.Kes.Enabled { | ||||
| 			return kms, errors.New("Ambiguous KMS configuration: kes configuration and a master key are provided at the same time") | ||||
| 		} | ||||
| 		kms, err = ParseMasterKey(masterKey) | ||||
| 		if err != nil { | ||||
| 			return kms, err | ||||
| 		} | ||||
| 	} else if cfg.Vault.Enabled && cfg.Kes.Enabled { | ||||
| 		return kms, errors.New("Ambiguous KMS configuration: vault configuration and kes configuration are provided at the same time") | ||||
| 	} else if cfg.Vault.Enabled { | ||||
| 		if v, ok := os.LookupEnv("MINIO_KMS_VAULT_DEPRECATION"); !ok || v != "off" { // TODO(aead): Remove once Vault support has been removed | ||||
| 			return kms, errors.New("Hashicorp Vault is deprecated and will be removed Oct. 2021. To temporarily enable Hashicorp Vault support, set MINIO_KMS_VAULT_DEPRECATION=off") | ||||
| 		} | ||||
| 		kms, err = NewVault(cfg.Vault) | ||||
| 		if err != nil { | ||||
| 			return kms, err | ||||
| 		} | ||||
| 	} else if cfg.Kes.Enabled { | ||||
| 		kms, err = NewKes(cfg.Kes) | ||||
| 		if err != nil { | ||||
| 			return kms, err | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	if cfg.AutoEncryption && kms == nil { | ||||
| 		return kms, errors.New("Invalid KMS configuration: auto-encryption is enabled but no valid KMS configuration is present") | ||||
| 	} | ||||
| 	return kms, nil | ||||
| // LookupAutoEncryption returns true if and only if | ||||
| // the MINIO_KMS_AUTO_ENCRYPTION env. variable is | ||||
| // set to "on". | ||||
| func LookupAutoEncryption() bool { | ||||
| 	auto, _ := config.ParseBool(env.Get(EnvKMSAutoEncryption, config.EnableOff)) | ||||
| 	return auto | ||||
| } | ||||
|  | ||||
| @ -15,95 +15,3 @@ | ||||
|  */ | ||||
| 
 | ||||
| package crypto | ||||
| 
 | ||||
| import "github.com/minio/minio/cmd/config" | ||||
| 
 | ||||
| // Help template for KMS vault | ||||
| var ( | ||||
| 	HelpVault = config.HelpKVS{ | ||||
| 		config.HelpKV{ | ||||
| 			Key:         KMSVaultEndpoint, | ||||
| 			Description: `API endpoint e.g. "http://vault-endpoint-ip:8200"`, | ||||
| 			Type:        "url", | ||||
| 		}, | ||||
| 		config.HelpKV{ | ||||
| 			Key:         KMSVaultKeyName, | ||||
| 			Description: `unique transit key name - e.g. "my-minio-key"`, | ||||
| 			Type:        "string", | ||||
| 		}, | ||||
| 		config.HelpKV{ | ||||
| 			Key:         KMSVaultAuthType, | ||||
| 			Description: `supported auth type(s) ["approle"], defaults to "approle"`, | ||||
| 			Type:        "string", | ||||
| 		}, | ||||
| 		config.HelpKV{ | ||||
| 			Key:         KMSVaultAppRoleID, | ||||
| 			Description: `unique role ID for approle`, | ||||
| 			Type:        "string", | ||||
| 		}, | ||||
| 		config.HelpKV{ | ||||
| 			Key:         KMSVaultAppRoleSecret, | ||||
| 			Description: `unique secret ID for approle`, | ||||
| 			Type:        "string", | ||||
| 		}, | ||||
| 		config.HelpKV{ | ||||
| 			Key:         KMSVaultNamespace, | ||||
| 			Description: `optional KMS namespace e.g. "customer1"`, | ||||
| 			Optional:    true, | ||||
| 			Type:        "string", | ||||
| 		}, | ||||
| 		config.HelpKV{ | ||||
| 			Key:         KMSVaultKeyVersion, | ||||
| 			Description: `optional key version number`, | ||||
| 			Optional:    true, | ||||
| 			Type:        "number", | ||||
| 		}, | ||||
| 		config.HelpKV{ | ||||
| 			Key:         KMSVaultCAPath, | ||||
| 			Description: `optional path to PEM-encoded CA certs e.g. "/home/user/custom-certs"`, | ||||
| 			Optional:    true, | ||||
| 			Type:        "path", | ||||
| 		}, | ||||
| 		config.HelpKV{ | ||||
| 			Key:         config.Comment, | ||||
| 			Description: config.DefaultComment, | ||||
| 			Optional:    true, | ||||
| 			Type:        "sentence", | ||||
| 		}, | ||||
| 	} | ||||
| 
 | ||||
| 	HelpKes = config.HelpKVS{ | ||||
| 		config.HelpKV{ | ||||
| 			Key:         KMSKesEndpoint, | ||||
| 			Description: `API endpoint - e.g. "https://kes-endpoint:7373"`, | ||||
| 			Type:        "url", | ||||
| 		}, | ||||
| 		config.HelpKV{ | ||||
| 			Key:         KMSKesKeyName, | ||||
| 			Description: `unique key name - e.g. "my-minio-key"`, | ||||
| 			Type:        "string", | ||||
| 		}, | ||||
| 		config.HelpKV{ | ||||
| 			Key:         KMSKesCertFile, | ||||
| 			Description: `path to client certificate for TLS auth - e.g. /etc/keys/public.crt`, | ||||
| 			Type:        "path", | ||||
| 		}, | ||||
| 		config.HelpKV{ | ||||
| 			Key:         KMSKesKeyFile, | ||||
| 			Description: `path to client private key for TLS auth - e.g. /etc/keys/private.key`, | ||||
| 			Type:        "path", | ||||
| 		}, | ||||
| 		config.HelpKV{ | ||||
| 			Key:         KMSKesCAPath, | ||||
| 			Description: `path to PEM-encoded cert(s) to verify kes server cert - e.g. /etc/keys/CAs`, | ||||
| 			Optional:    true, | ||||
| 			Type:        "path", | ||||
| 		}, | ||||
| 		config.HelpKV{ | ||||
| 			Key:         config.Comment, | ||||
| 			Description: config.DefaultComment, | ||||
| 			Optional:    true, | ||||
| 			Type:        "sentence", | ||||
| 		}, | ||||
| 	} | ||||
| ) | ||||
|  | ||||
| @ -41,6 +41,9 @@ func GenerateKey(extKey []byte, random io.Reader) (key ObjectKey) { | ||||
| 	if random == nil { | ||||
| 		random = rand.Reader | ||||
| 	} | ||||
| 	if len(extKey) != 32 { // safety check | ||||
| 		logger.CriticalIf(context.Background(), errors.New("crypto: invalid key length")) | ||||
| 	} | ||||
| 	var nonce [32]byte | ||||
| 	if _, err := io.ReadFull(random, nonce[:]); err != nil { | ||||
| 		logger.CriticalIf(context.Background(), errOutOfEntropy) | ||||
| @ -77,6 +80,9 @@ type SealedKey struct { | ||||
| // key is also cryptographically bound to the object's path (bucket/object) and the | ||||
| // domain (SSE-C or SSE-S3). | ||||
| func (key ObjectKey) Seal(extKey []byte, iv [32]byte, domain, bucket, object string) SealedKey { | ||||
| 	if len(extKey) != 32 { | ||||
| 		logger.CriticalIf(context.Background(), errors.New("crypto: invalid key length")) | ||||
| 	} | ||||
| 	var ( | ||||
| 		sealingKey   [32]byte | ||||
| 		encryptedKey bytes.Buffer | ||||
|  | ||||
| @ -15,17 +15,7 @@ | ||||
| package crypto | ||||
| 
 | ||||
| import ( | ||||
| 	"bytes" | ||||
| 	"context" | ||||
| 	"crypto/hmac" | ||||
| 	"crypto/rand" | ||||
| 	"crypto/sha256" | ||||
| 	"errors" | ||||
| 	"io" | ||||
| 
 | ||||
| 	"github.com/minio/minio/cmd/logger" | ||||
| 	"github.com/minio/minio/pkg/kms" | ||||
| 	"github.com/minio/sio" | ||||
| ) | ||||
| 
 | ||||
| // Context is a list of key-value pairs cryptographically | ||||
| @ -37,73 +27,3 @@ type Context = kms.Context | ||||
| // data key generation and unsealing of KMS-generated | ||||
| // data keys. | ||||
| type KMS = kms.KMS | ||||
| 
 | ||||
| type masterKeyKMS struct { | ||||
| 	keyID     string | ||||
| 	masterKey [32]byte | ||||
| } | ||||
| 
 | ||||
| // NewMasterKey returns a basic KMS implementation from a single 256 bit master key. | ||||
| // | ||||
| // The KMS accepts any keyID but binds the keyID and context cryptographically | ||||
| // to the generated keys. | ||||
| func NewMasterKey(keyID string, key [32]byte) KMS { return &masterKeyKMS{keyID: keyID, masterKey: key} } | ||||
| 
 | ||||
| func (m *masterKeyKMS) Stat() (kms.Status, error) { | ||||
| 	return kms.Status{ | ||||
| 		Name:       "MasterKey", | ||||
| 		DefaultKey: m.keyID, | ||||
| 	}, nil | ||||
| } | ||||
| 
 | ||||
| func (m *masterKeyKMS) CreateKey(keyID string) error { | ||||
| 	return errors.New("crypto: creating keys is not supported by a static master key") | ||||
| } | ||||
| 
 | ||||
| func (m *masterKeyKMS) GenerateKey(keyID string, ctx Context) (kms.DEK, error) { | ||||
| 	if keyID == "" { | ||||
| 		keyID = m.keyID | ||||
| 	} | ||||
| 
 | ||||
| 	var key [32]byte | ||||
| 	if _, err := io.ReadFull(rand.Reader, key[:]); err != nil { | ||||
| 		logger.CriticalIf(context.Background(), errOutOfEntropy) | ||||
| 	} | ||||
| 
 | ||||
| 	var ( | ||||
| 		buffer     bytes.Buffer | ||||
| 		derivedKey = m.deriveKey(keyID, ctx) | ||||
| 	) | ||||
| 	if n, err := sio.Encrypt(&buffer, bytes.NewReader(key[:]), sio.Config{Key: derivedKey[:]}); err != nil || n != 64 { | ||||
| 		logger.CriticalIf(context.Background(), errors.New("KMS: unable to encrypt data key")) | ||||
| 	} | ||||
| 	return kms.DEK{ | ||||
| 		KeyID:      m.keyID, | ||||
| 		Plaintext:  key[:], | ||||
| 		Ciphertext: buffer.Bytes(), | ||||
| 	}, nil | ||||
| } | ||||
| 
 | ||||
| func (m *masterKeyKMS) DecryptKey(keyID string, sealedKey []byte, ctx Context) ([]byte, error) { | ||||
| 	var derivedKey = m.deriveKey(keyID, ctx) | ||||
| 
 | ||||
| 	var key [32]byte | ||||
| 	out, err := sio.DecryptBuffer(key[:0], sealedKey, sio.Config{Key: derivedKey[:]}) | ||||
| 	if err != nil || len(out) != 32 { | ||||
| 		return nil, err // TODO(aead): upgrade sio to use sio.Error | ||||
| 	} | ||||
| 	return key[:], nil | ||||
| } | ||||
| 
 | ||||
| func (m *masterKeyKMS) deriveKey(keyID string, context Context) (key [32]byte) { | ||||
| 	if context == nil { | ||||
| 		context = Context{} | ||||
| 	} | ||||
| 	ctxBytes, _ := context.MarshalText() | ||||
| 
 | ||||
| 	mac := hmac.New(sha256.New, m.masterKey[:]) | ||||
| 	mac.Write([]byte(keyID)) | ||||
| 	mac.Write(ctxBytes) | ||||
| 	mac.Sum(key[:0]) | ||||
| 	return key | ||||
| } | ||||
|  | ||||
| @ -1,104 +0,0 @@ | ||||
| // MinIO Cloud Storage, (C) 2015, 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 crypto | ||||
| 
 | ||||
| import ( | ||||
| 	"bytes" | ||||
| 	"fmt" | ||||
| 	"path" | ||||
| 	"testing" | ||||
| ) | ||||
| 
 | ||||
| var masterKeyKMSTests = []struct { | ||||
| 	GenKeyID, UnsealKeyID     string | ||||
| 	GenContext, UnsealContext Context | ||||
| 
 | ||||
| 	ShouldFail bool | ||||
| }{ | ||||
| 	{GenKeyID: "", UnsealKeyID: "", GenContext: Context{}, UnsealContext: nil, ShouldFail: false},                                                                                     // 0 | ||||
| 	{GenKeyID: "ac47be7f", UnsealKeyID: "ac47be7f", GenContext: Context{}, UnsealContext: Context{}, ShouldFail: false},                                                               // 1 | ||||
| 	{GenKeyID: "ac47be7f", UnsealKeyID: "ac47be7f", GenContext: Context{"bucket": "object"}, UnsealContext: Context{"bucket": "object"}, ShouldFail: false},                           // 2 | ||||
| 	{GenKeyID: "", UnsealKeyID: "", GenContext: Context{"bucket": path.Join("bucket", "object")}, UnsealContext: Context{"bucket": path.Join("bucket", "object")}, ShouldFail: false}, // 3 | ||||
| 	{GenKeyID: "", UnsealKeyID: "", GenContext: Context{"a": "a", "0": "0", "b": "b"}, UnsealContext: Context{"b": "b", "a": "a", "0": "0"}, ShouldFail: false},                       // 4 | ||||
| 
 | ||||
| 	{GenKeyID: "ac47be7f", UnsealKeyID: "ac47be7e", GenContext: Context{}, UnsealContext: Context{}, ShouldFail: true},                                                               // 5 | ||||
| 	{GenKeyID: "ac47be7f", UnsealKeyID: "ac47be7f", GenContext: Context{"bucket": "object"}, UnsealContext: Context{"Bucket": "object"}, ShouldFail: true},                           // 6 | ||||
| 	{GenKeyID: "", UnsealKeyID: "", GenContext: Context{"bucket": path.Join("bucket", "Object")}, UnsealContext: Context{"bucket": path.Join("bucket", "object")}, ShouldFail: true}, // 7 | ||||
| 	{GenKeyID: "", UnsealKeyID: "", GenContext: Context{"a": "a", "0": "1", "b": "b"}, UnsealContext: Context{"b": "b", "a": "a", "0": "0"}, ShouldFail: true},                       // 8 | ||||
| } | ||||
| 
 | ||||
| func TestMasterKeyKMS(t *testing.T) { | ||||
| 	for i, test := range masterKeyKMSTests { | ||||
| 		kms := NewMasterKey(test.GenKeyID, [32]byte{}) | ||||
| 
 | ||||
| 		key, err := kms.GenerateKey(test.GenKeyID, test.GenContext) | ||||
| 		if err != nil { | ||||
| 			t.Errorf("Test %d: KMS failed to generate key: %v", i, err) | ||||
| 		} | ||||
| 		unsealedKey, err := kms.DecryptKey(test.UnsealKeyID, key.Ciphertext, test.UnsealContext) | ||||
| 		if err != nil && !test.ShouldFail { | ||||
| 			t.Errorf("Test %d: KMS failed to unseal the generated key: %v", i, err) | ||||
| 		} | ||||
| 		if err == nil && test.ShouldFail { | ||||
| 			t.Errorf("Test %d: KMS unsealed the generated key successfully but should have failed", i) | ||||
| 		} | ||||
| 		if !test.ShouldFail && !bytes.Equal(key.Plaintext, unsealedKey[:]) { | ||||
| 			t.Errorf("Test %d: The generated and unsealed key differ", i) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| var contextMarshalTextTests = []struct { | ||||
| 	Context      Context | ||||
| 	ExpectedJSON string | ||||
| }{ | ||||
| 	0: {Context: Context{}, ExpectedJSON: "{}"}, | ||||
| 	1: {Context: Context{"a": "b"}, ExpectedJSON: `{"a":"b"}`}, | ||||
| 	2: {Context: Context{"a": "b", "c": "d"}, ExpectedJSON: `{"a":"b","c":"d"}`}, | ||||
| 	3: {Context: Context{"c": "d", "a": "b"}, ExpectedJSON: `{"a":"b","c":"d"}`}, | ||||
| 	4: {Context: Context{"0": "1", "-": "2", ".": "#"}, ExpectedJSON: `{"-":"2",".":"#","0":"1"}`}, | ||||
| 	// rfc 8259 escapes | ||||
| 	5: {Context: Context{"0": "1", "key\\": "val\tue\r\n", "\"": "\""}, ExpectedJSON: `{"\"":"\"","0":"1","key\\":"val\tue\r\n"}`}, | ||||
| 	// html sensitive escapes | ||||
| 	6: {Context: Context{"a": "<>&"}, ExpectedJSON: `{"a":"\u003c\u003e\u0026"}`}, | ||||
| } | ||||
| 
 | ||||
| func TestContextMarshalText(t *testing.T) { | ||||
| 	for i, test := range contextMarshalTextTests { | ||||
| 		text, err := test.Context.MarshalText() | ||||
| 		if err != nil { | ||||
| 			t.Fatalf("Test %d: Failed to encode context: %v", i, err) | ||||
| 		} | ||||
| 		if string(text) != test.ExpectedJSON { | ||||
| 			t.Errorf("Test %d: JSON representation differ - got: '%s' want: '%s'", i, string(text), test.ExpectedJSON) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func BenchmarkContext(b *testing.B) { | ||||
| 	tests := []Context{{}, {"bucket": "warp-benchmark-bucket"}, {"0": "1", "-": "2", ".": "#"}, {"34trg": "dfioutr89", "ikjfdghkjf": "jkedfhgfjkhg", "sdfhsdjkh": "if88889", "asddsirfh804": "kjfdshgdfuhgfg78-45604586#$%<>&"}} | ||||
| 	for _, test := range tests { | ||||
| 		b.Run(fmt.Sprintf("%d-elems", len(test)), func(b *testing.B) { | ||||
| 			b.ReportAllocs() | ||||
| 			b.ResetTimer() | ||||
| 			for i := 0; i < b.N; i++ { | ||||
| 				_, err := test.MarshalText() | ||||
| 				if err != nil { | ||||
| 					b.Fatal(err) | ||||
| 				} | ||||
| 			} | ||||
| 		}) | ||||
| 	} | ||||
| } | ||||
| @ -1,184 +0,0 @@ | ||||
| /* | ||||
|  * MinIO Cloud Storage, (C) 2019 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 crypto | ||||
| 
 | ||||
| import ( | ||||
| 	"reflect" | ||||
| 	"strconv" | ||||
| 
 | ||||
| 	"github.com/minio/minio/cmd/config" | ||||
| 	"github.com/minio/minio/pkg/env" | ||||
| 	xnet "github.com/minio/minio/pkg/net" | ||||
| ) | ||||
| 
 | ||||
| const ( | ||||
| 	// EnvKMSMasterKeyLegacy 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". | ||||
| 	EnvKMSMasterKeyLegacy = "MINIO_SSE_MASTER_KEY" | ||||
| 
 | ||||
| 	// EnvAutoEncryptionLegacy is the environment variable used to en/disable | ||||
| 	// SSE-S3 auto-encryption. SSE-S3 auto-encryption, if enabled, | ||||
| 	// requires a valid KMS configuration and turns any non-SSE-C | ||||
| 	// request into an SSE-S3 request. | ||||
| 	// If present EnvAutoEncryption must be either "on" or "off". | ||||
| 	EnvAutoEncryptionLegacy = "MINIO_SSE_AUTO_ENCRYPTION" | ||||
| ) | ||||
| 
 | ||||
| const ( | ||||
| 	// EnvLegacyVaultEndpoint is the environment variable used to specify | ||||
| 	// the vault HTTPS endpoint. | ||||
| 	EnvLegacyVaultEndpoint = "MINIO_SSE_VAULT_ENDPOINT" | ||||
| 
 | ||||
| 	// EnvLegacyVaultAuthType is the environment variable used to specify | ||||
| 	// the authentication type for vault. | ||||
| 	EnvLegacyVaultAuthType = "MINIO_SSE_VAULT_AUTH_TYPE" | ||||
| 
 | ||||
| 	// EnvLegacyVaultAppRoleID is the environment variable used to specify | ||||
| 	// the vault AppRole ID. | ||||
| 	EnvLegacyVaultAppRoleID = "MINIO_SSE_VAULT_APPROLE_ID" | ||||
| 
 | ||||
| 	// EnvLegacyVaultAppSecretID is the environment variable used to specify | ||||
| 	// the vault AppRole secret corresponding to the AppRole ID. | ||||
| 	EnvLegacyVaultAppSecretID = "MINIO_SSE_VAULT_APPROLE_SECRET" | ||||
| 
 | ||||
| 	// EnvLegacyVaultKeyVersion is the environment variable used to specify | ||||
| 	// the vault key version. | ||||
| 	EnvLegacyVaultKeyVersion = "MINIO_SSE_VAULT_KEY_VERSION" | ||||
| 
 | ||||
| 	// EnvLegacyVaultKeyName 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). | ||||
| 	EnvLegacyVaultKeyName = "MINIO_SSE_VAULT_KEY_NAME" | ||||
| 
 | ||||
| 	// EnvLegacyVaultCAPath 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. | ||||
| 	EnvLegacyVaultCAPath = "MINIO_SSE_VAULT_CAPATH" | ||||
| 
 | ||||
| 	// EnvLegacyVaultNamespace is the environment variable used to specify | ||||
| 	// vault namespace. The vault namespace is used if the enterprise | ||||
| 	// version of Hashicorp Vault is used. | ||||
| 	EnvLegacyVaultNamespace = "MINIO_SSE_VAULT_NAMESPACE" | ||||
| ) | ||||
| 
 | ||||
| // SetKMSConfig helper to migrate from older KMSConfig to new KV. | ||||
| func SetKMSConfig(s config.Config, cfg KMSConfig) { | ||||
| 	if cfg.Vault.Endpoint == "" { | ||||
| 		return | ||||
| 	} | ||||
| 	s[config.KmsVaultSubSys][config.Default] = config.KVS{ | ||||
| 		config.KV{ | ||||
| 			Key:   KMSVaultEndpoint, | ||||
| 			Value: cfg.Vault.Endpoint, | ||||
| 		}, | ||||
| 		config.KV{ | ||||
| 			Key:   KMSVaultCAPath, | ||||
| 			Value: cfg.Vault.CAPath, | ||||
| 		}, | ||||
| 		config.KV{ | ||||
| 			Key: KMSVaultAuthType, | ||||
| 			Value: func() string { | ||||
| 				if cfg.Vault.Auth.Type != "" { | ||||
| 					return cfg.Vault.Auth.Type | ||||
| 				} | ||||
| 				return "approle" | ||||
| 			}(), | ||||
| 		}, | ||||
| 		config.KV{ | ||||
| 			Key:   KMSVaultAppRoleID, | ||||
| 			Value: cfg.Vault.Auth.AppRole.ID, | ||||
| 		}, | ||||
| 		config.KV{ | ||||
| 			Key:   KMSVaultAppRoleSecret, | ||||
| 			Value: cfg.Vault.Auth.AppRole.Secret, | ||||
| 		}, | ||||
| 		config.KV{ | ||||
| 			Key:   KMSVaultKeyName, | ||||
| 			Value: cfg.Vault.Key.Name, | ||||
| 		}, | ||||
| 		config.KV{ | ||||
| 			Key:   KMSVaultKeyVersion, | ||||
| 			Value: strconv.Itoa(cfg.Vault.Key.Version), | ||||
| 		}, | ||||
| 		config.KV{ | ||||
| 			Key:   KMSVaultNamespace, | ||||
| 			Value: cfg.Vault.Namespace, | ||||
| 		}, | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // lookupConfigLegacy extracts the KMS configuration provided by legacy | ||||
| // environment variables and merge them with the provided KMS configuration. | ||||
| // The merging follows the following rules: | ||||
| // | ||||
| // 1. A valid value provided as environment variable has higher priority | ||||
| // 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 lookupConfigLegacy(kvs config.KVS) (VaultConfig, error) { | ||||
| 	vcfg := VaultConfig{ | ||||
| 		Auth: VaultAuth{ | ||||
| 			Type: "approle", | ||||
| 		}, | ||||
| 	} | ||||
| 
 | ||||
| 	endpointStr := env.Get(EnvLegacyVaultEndpoint, "") | ||||
| 	if endpointStr != "" { | ||||
| 		// Lookup Hashicorp-Vault configuration & overwrite config entry if ENV var is present | ||||
| 		endpoint, err := xnet.ParseHTTPURL(endpointStr) | ||||
| 		if err != nil { | ||||
| 			return vcfg, err | ||||
| 		} | ||||
| 		endpointStr = endpoint.String() | ||||
| 	} | ||||
| 
 | ||||
| 	var err error | ||||
| 	vcfg.Endpoint = endpointStr | ||||
| 	vcfg.CAPath = env.Get(EnvLegacyVaultCAPath, "") | ||||
| 	vcfg.Auth.Type = env.Get(EnvLegacyVaultAuthType, "") | ||||
| 	if vcfg.Auth.Type == "" { | ||||
| 		vcfg.Auth.Type = "approle" | ||||
| 	} | ||||
| 	vcfg.Auth.AppRole.ID = env.Get(EnvLegacyVaultAppRoleID, "") | ||||
| 	vcfg.Auth.AppRole.Secret = env.Get(EnvLegacyVaultAppSecretID, "") | ||||
| 	vcfg.Key.Name = env.Get(EnvLegacyVaultKeyName, "") | ||||
| 	vcfg.Namespace = env.Get(EnvLegacyVaultNamespace, "") | ||||
| 	if keyVersion := env.Get(EnvLegacyVaultKeyVersion, ""); keyVersion != "" { | ||||
| 		vcfg.Key.Version, err = strconv.Atoi(keyVersion) | ||||
| 		if err != nil { | ||||
| 			return vcfg, Errorf("Invalid ENV variable: Unable to parse %s value (`%s`)", | ||||
| 				EnvLegacyVaultKeyVersion, keyVersion) | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	if reflect.DeepEqual(vcfg, defaultVaultCfg) { | ||||
| 		return vcfg, nil | ||||
| 	} | ||||
| 
 | ||||
| 	if err = vcfg.Verify(); err != nil { | ||||
| 		return vcfg, err | ||||
| 	} | ||||
| 
 | ||||
| 	vcfg.Enabled = true | ||||
| 	return vcfg, nil | ||||
| } | ||||
| @ -1,41 +0,0 @@ | ||||
| // MinIO Cloud Storage, (C) 2017-2019 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 crypto | ||||
| 
 | ||||
| import ( | ||||
| 	"encoding/hex" | ||||
| 	"strings" | ||||
| ) | ||||
| 
 | ||||
| // ParseMasterKey parses the value of the environment variable | ||||
| // `EnvKMSMasterKey` and returns a key-ID and a master-key KMS on success. | ||||
| func ParseMasterKey(envArg string) (KMS, error) { | ||||
| 	values := strings.SplitN(envArg, ":", 2) | ||||
| 	if len(values) != 2 { | ||||
| 		return nil, 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, 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, Errorf("Invalid KMS master key: %v", err) | ||||
| 	} | ||||
| 	return NewMasterKey(keyID, masterKey), nil | ||||
| } | ||||
| @ -1,62 +0,0 @@ | ||||
| // MinIO Cloud Storage, (C) 2019 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 crypto | ||||
| 
 | ||||
| import "testing" | ||||
| 
 | ||||
| func TestParseMasterKey(t *testing.T) { | ||||
| 	tests := []struct { | ||||
| 		envValue      string | ||||
| 		expectedKeyID string | ||||
| 		success       bool | ||||
| 	}{ | ||||
| 		{ | ||||
| 			envValue: "invalid-value", | ||||
| 			success:  false, | ||||
| 		}, | ||||
| 		{ | ||||
| 			envValue: "too:many:colons", | ||||
| 			success:  false, | ||||
| 		}, | ||||
| 		{ | ||||
| 			envValue: "myminio-key:not-a-hex", | ||||
| 			success:  false, | ||||
| 		}, | ||||
| 		{ | ||||
| 			envValue:      "my-minio-key:6368616e676520746869732070617373776f726420746f206120736563726574", | ||||
| 			expectedKeyID: "my-minio-key", | ||||
| 			success:       true, | ||||
| 		}, | ||||
| 	} | ||||
| 
 | ||||
| 	for _, tt := range tests { | ||||
| 		tt := tt | ||||
| 		t.Run(tt.envValue, func(t *testing.T) { | ||||
| 			kms, err := ParseMasterKey(tt.envValue) | ||||
| 			if tt.success && err != nil { | ||||
| 				t.Error(err) | ||||
| 			} | ||||
| 			if !tt.success && err == nil { | ||||
| 				t.Error("Unexpected failure") | ||||
| 			} | ||||
| 			if kms != nil { | ||||
| 				stat, _ := kms.Stat() | ||||
| 				if err == nil && stat.DefaultKey != tt.expectedKeyID { | ||||
| 					t.Errorf("Expected keyID %s, got %s", tt.expectedKeyID, stat.DefaultKey) | ||||
| 				} | ||||
| 			} | ||||
| 		}) | ||||
| 	} | ||||
| } | ||||
| @ -182,45 +182,3 @@ func TestSSECopyUnsealObjectKey(t *testing.T) { | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| var s3UnsealObjectKeyTests = []struct { | ||||
| 	KMS            KMS | ||||
| 	Bucket, Object string | ||||
| 	Metadata       map[string]string | ||||
| 
 | ||||
| 	ExpectedErr error | ||||
| }{ | ||||
| 	{ // 0 - Valid KMS key-ID and valid metadata entries for bucket/object | ||||
| 		KMS:    NewMasterKey("my-minio-key", [32]byte{}), | ||||
| 		Bucket: "bucket", | ||||
| 		Object: "object", | ||||
| 		Metadata: map[string]string{ | ||||
| 			"X-Minio-Internal-Server-Side-Encryption-Iv":                "hhVY0LKR1YtZbzAKxTWUfZt5enDfYX6Fxz1ma8Kiudc=", | ||||
| 			"X-Minio-Internal-Server-Side-Encryption-S3-Sealed-Key":     "IAAfALhsOeD5AE3s5Zgq3DZ5VFGsOa3B0ksVC86veDcaj+fXv2U0VadhPaOKYr9Emd5ssOsO0uIhIIrKiOy9rA==", | ||||
| 			"X-Minio-Internal-Server-Side-Encryption-S3-Kms-Sealed-Key": "IAAfAMRS2iw45FsfiF3QXajSYVWj1lxMpQm6DxDGPtADCX6fJQQ4atHBtfpgqJFyeQmIHsm0FBI+UlHw1Lv4ug==", | ||||
| 			"X-Minio-Internal-Server-Side-Encryption-S3-Kms-Key-Id":     "test-key-1", | ||||
| 			"X-Minio-Internal-Server-Side-Encryption-Seal-Algorithm":    "DAREv2-HMAC-SHA256", | ||||
| 		}, | ||||
| 		ExpectedErr: nil, | ||||
| 	}, | ||||
| 	{ // 1 - Valid KMS key-ID for invalid metadata entries for bucket/object | ||||
| 		KMS:    NewMasterKey("my-minio-key", [32]byte{}), | ||||
| 		Bucket: "bucket", | ||||
| 		Object: "object", | ||||
| 		Metadata: map[string]string{ | ||||
| 			"X-Minio-Internal-Server-Side-Encryption-Iv":                "hhVY0LKR1YtZbzAKxTWUfZt5enDfYX6Fxz1ma8Kiudc=", | ||||
| 			"X-Minio-Internal-Server-Side-Encryption-S3-Sealed-Key":     "IAAfALhsOeD5AE3s5Zgq3DZ5VFGsOa3B0ksVC86veDcaj+fXv2U0VadhPaOKYr9Emd5ssOsO0uIhIIrKiOy9rA==", | ||||
| 			"X-Minio-Internal-Server-Side-Encryption-S3-Kms-Sealed-Key": "IAAfAMRS2iw45FsfiF3QXajSYVWj1lxMpQm6DxDGPtADCX6fJQQ4atHBtfpgqJFyeQmIHsm0FBI+UlHw1Lv4ug==", | ||||
| 			"X-Minio-Internal-Server-Side-Encryption-S3-Kms-Key-Id":     "test-key-1", | ||||
| 		}, | ||||
| 		ExpectedErr: errMissingInternalSealAlgorithm, | ||||
| 	}, | ||||
| } | ||||
| 
 | ||||
| func TestS3UnsealObjectKey(t *testing.T) { | ||||
| 	for i, test := range s3UnsealObjectKeyTests { | ||||
| 		if _, err := S3.UnsealObjectKey(test.KMS, test.Metadata, test.Bucket, test.Object); err != test.ExpectedErr { | ||||
| 			t.Errorf("Test %d: got: %v - want: %v", i, err, test.ExpectedErr) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| @ -1,289 +0,0 @@ | ||||
| // MinIO Cloud Storage, (C) 2015, 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 crypto | ||||
| 
 | ||||
| import ( | ||||
| 	"encoding/base64" | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"strings" | ||||
| 	"time" | ||||
| 
 | ||||
| 	vault "github.com/hashicorp/vault/api" | ||||
| 	"github.com/minio/minio/pkg/kms" | ||||
| ) | ||||
| 
 | ||||
| var ( | ||||
| 	//ErrKMSAuthLogin is raised when there is a failure authenticating to KMS | ||||
| 	ErrKMSAuthLogin = Errorf("Vault service did not return auth info") | ||||
| ) | ||||
| 
 | ||||
| // VaultKey represents vault encryption key-ring. | ||||
| type VaultKey struct { | ||||
| 	Name    string `json:"name"`    // The name of the encryption key-ring | ||||
| 	Version int    `json:"version"` // The key version | ||||
| } | ||||
| 
 | ||||
| // VaultAuth represents vault authentication type. | ||||
| // Currently the only supported authentication type is AppRole. | ||||
| type VaultAuth struct { | ||||
| 	Type    string       `json:"type"`    // The authentication type | ||||
| 	AppRole VaultAppRole `json:"approle"` // The AppRole authentication credentials | ||||
| } | ||||
| 
 | ||||
| // VaultAppRole represents vault AppRole authentication credentials | ||||
| type VaultAppRole struct { | ||||
| 	ID     string `json:"id"`     // The AppRole access ID | ||||
| 	Secret string `json:"secret"` // The AppRole secret | ||||
| } | ||||
| 
 | ||||
| // VaultConfig represents vault configuration. | ||||
| type VaultConfig struct { | ||||
| 	Enabled   bool      `json:"-"` | ||||
| 	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 | ||||
| } | ||||
| 
 | ||||
| // vaultService represents a connection to a vault KMS. | ||||
| type vaultService struct { | ||||
| 	config        *VaultConfig | ||||
| 	client        *vault.Client | ||||
| 	secret        *vault.Secret | ||||
| 	leaseDuration time.Duration | ||||
| } | ||||
| 
 | ||||
| var _ KMS = (*vaultService)(nil) // compiler check that *vaultService implements KMS | ||||
| 
 | ||||
| // 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) { | ||||
| 	switch { | ||||
| 	case v.Endpoint == "": | ||||
| 		err = Errorf("crypto: missing hashicorp vault endpoint") | ||||
| 	case strings.ToLower(v.Auth.Type) != "approle": | ||||
| 		err = Errorf("crypto: invalid hashicorp vault authentication type: %s is not supported", v.Auth.Type) | ||||
| 	case v.Auth.AppRole.ID == "": | ||||
| 		err = Errorf("crypto: missing hashicorp vault AppRole ID") | ||||
| 	case v.Auth.AppRole.Secret == "": | ||||
| 		err = Errorf("crypto: missing hashicorp vault AppSecret ID") | ||||
| 	case v.Key.Name == "": | ||||
| 		err = Errorf("crypto: missing hashicorp vault key name") | ||||
| 	case v.Key.Version < 0: | ||||
| 		err = Errorf("crypto: invalid hashicorp vault key version: The key version must not be negative") | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
| 
 | ||||
| // 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.Enabled { | ||||
| 		return nil, nil | ||||
| 	} | ||||
| 	if err := config.Verify(); err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 
 | ||||
| 	vaultCfg := vault.Config{Address: config.Endpoint} | ||||
| 	if err := vaultCfg.ConfigureTLS(&vault.TLSConfig{CAPath: config.CAPath}); err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	client, err := vault.NewClient(&vaultCfg) | ||||
| 	if err != nil { | ||||
| 		return nil, Errorf("crypto: client error %w", err) | ||||
| 	} | ||||
| 	if config.Namespace != "" { | ||||
| 		client.SetNamespace(config.Namespace) | ||||
| 	} | ||||
| 	v := &vaultService{client: client, config: &config} | ||||
| 	if err := v.authenticate(); err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	v.renewToken() | ||||
| 	return v, nil | ||||
| } | ||||
| 
 | ||||
| // renewToken starts a new go-routine which renews | ||||
| // the vault authentication token periodically and re-authenticates | ||||
| // if the token renewal fails | ||||
| func (v *vaultService) renewToken() { | ||||
| 	retryDelay := v.leaseDuration / 2 | ||||
| 	go func() { | ||||
| 		for { | ||||
| 			if v.secret == nil { | ||||
| 				if err := v.authenticate(); err != nil { | ||||
| 					time.Sleep(retryDelay) | ||||
| 					continue | ||||
| 				} | ||||
| 			} | ||||
| 			s, err := v.client.Auth().Token().RenewSelf(int(v.leaseDuration)) | ||||
| 			if err != nil || s == nil { | ||||
| 				v.secret = nil | ||||
| 				time.Sleep(retryDelay) | ||||
| 				continue | ||||
| 			} | ||||
| 			if ok, err := s.TokenIsRenewable(); !ok || err != nil { | ||||
| 				v.secret = nil | ||||
| 				continue | ||||
| 			} | ||||
| 			ttl, err := s.TokenTTL() | ||||
| 			if err != nil { | ||||
| 				v.secret = nil | ||||
| 				continue | ||||
| 			} | ||||
| 			v.secret = s | ||||
| 			retryDelay = ttl / 2 | ||||
| 			time.Sleep(retryDelay) | ||||
| 		} | ||||
| 	}() | ||||
| } | ||||
| 
 | ||||
| // authenticate logs the app to vault, and starts the auto renewer | ||||
| // before secret expires | ||||
| func (v *vaultService) authenticate() (err error) { | ||||
| 	payload := map[string]interface{}{ | ||||
| 		"role_id":   v.config.Auth.AppRole.ID, | ||||
| 		"secret_id": v.config.Auth.AppRole.Secret, | ||||
| 	} | ||||
| 	var tokenID string | ||||
| 	var ttl time.Duration | ||||
| 	var secret *vault.Secret | ||||
| 	secret, err = v.client.Logical().Write("auth/approle/login", payload) | ||||
| 	if err != nil { | ||||
| 		err = Errorf("crypto: client error %w", err) | ||||
| 		return | ||||
| 	} | ||||
| 	if secret == nil { | ||||
| 		err = ErrKMSAuthLogin | ||||
| 		return | ||||
| 	} | ||||
| 
 | ||||
| 	tokenID, err = secret.TokenID() | ||||
| 	if err != nil { | ||||
| 		err = ErrKMSAuthLogin | ||||
| 		return | ||||
| 	} | ||||
| 	ttl, err = secret.TokenTTL() | ||||
| 	if err != nil { | ||||
| 		err = ErrKMSAuthLogin | ||||
| 		return | ||||
| 	} | ||||
| 	v.client.SetToken(tokenID) | ||||
| 	v.secret = secret | ||||
| 	v.leaseDuration = ttl | ||||
| 	return | ||||
| } | ||||
| 
 | ||||
| // Info returns some information about the Vault, | ||||
| // configuration - like the endpoints or authentication | ||||
| // method. | ||||
| func (v *vaultService) Stat() (kms.Status, error) { | ||||
| 	return kms.Status{ | ||||
| 		Endpoints:  []string{v.config.Endpoint}, | ||||
| 		Name:       "Hashicorp Vault", | ||||
| 		DefaultKey: v.config.Key.Name, | ||||
| 	}, nil | ||||
| } | ||||
| 
 | ||||
| // CreateKey is a stub that exists such that the Vault | ||||
| // client implements the KMS interface. It always returns | ||||
| // a not-implemented error. | ||||
| // | ||||
| // Creating keys requires a KES instance between MinIO and Vault. | ||||
| func (v *vaultService) CreateKey(keyID string) error { | ||||
| 	// Creating new keys requires KES. | ||||
| 	return errors.New("crypto: creating keys is not supported by Vault") | ||||
| } | ||||
| 
 | ||||
| // 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) (kms.DEK, error) { | ||||
| 	if keyID == "" { | ||||
| 		keyID = v.config.Key.Name | ||||
| 	} | ||||
| 	context, err := ctx.MarshalText() | ||||
| 	if err != nil { | ||||
| 		return kms.DEK{}, err | ||||
| 	} | ||||
| 
 | ||||
| 	payload := map[string]interface{}{ | ||||
| 		"context": base64.StdEncoding.EncodeToString(context), | ||||
| 	} | ||||
| 	s, err := v.client.Logical().Write(fmt.Sprintf("/transit/datakey/plaintext/%s", keyID), payload) | ||||
| 	if err != nil { | ||||
| 		return kms.DEK{}, Errorf("crypto: client error %w", err) | ||||
| 	} | ||||
| 	sealKey, ok := s.Data["ciphertext"].(string) | ||||
| 	if !ok { | ||||
| 		return kms.DEK{}, Errorf("crypto: incorrect 'ciphertext' key type %v", s.Data["ciphertext"]) | ||||
| 	} | ||||
| 
 | ||||
| 	plainKeyB64, ok := s.Data["plaintext"].(string) | ||||
| 	if !ok { | ||||
| 		return kms.DEK{}, Errorf("crypto: incorrect 'plaintext' key type %v", s.Data["plaintext"]) | ||||
| 	} | ||||
| 
 | ||||
| 	plainKey, err := base64.StdEncoding.DecodeString(plainKeyB64) | ||||
| 	if err != nil { | ||||
| 		return kms.DEK{}, Errorf("crypto: invalid base64 key %w", err) | ||||
| 	} | ||||
| 	return kms.DEK{ | ||||
| 		KeyID:      keyID, | ||||
| 		Plaintext:  plainKey, | ||||
| 		Ciphertext: []byte(sealKey), | ||||
| 	}, nil | ||||
| } | ||||
| 
 | ||||
| // 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) DecryptKey(keyID string, sealedKey []byte, ctx Context) ([]byte, error) { | ||||
| 	context, err := ctx.MarshalText() | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 
 | ||||
| 	payload := map[string]interface{}{ | ||||
| 		"ciphertext": string(sealedKey), | ||||
| 		"context":    base64.StdEncoding.EncodeToString(context), | ||||
| 	} | ||||
| 
 | ||||
| 	s, err := v.client.Logical().Write(fmt.Sprintf("/transit/decrypt/%s", keyID), payload) | ||||
| 	if err != nil { | ||||
| 		return nil, Errorf("crypto: client error %w", err) | ||||
| 	} | ||||
| 
 | ||||
| 	base64Key, ok := s.Data["plaintext"].(string) | ||||
| 	if !ok { | ||||
| 		return nil, Errorf("crypto: incorrect 'plaintext' key type %v", s.Data["plaintext"]) | ||||
| 	} | ||||
| 
 | ||||
| 	plainKey, err := base64.StdEncoding.DecodeString(base64Key) | ||||
| 	if err != nil { | ||||
| 		return nil, Errorf("crypto: invalid base64 key %w", err) | ||||
| 	} | ||||
| 	return plainKey, nil | ||||
| } | ||||
| @ -1,100 +0,0 @@ | ||||
| // Minio Cloud Storage, (C) 2019 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 crypto | ||||
| 
 | ||||
| import ( | ||||
| 	"testing" | ||||
| ) | ||||
| 
 | ||||
| var verifyVaultConfigTests = []struct { | ||||
| 	Config     VaultConfig | ||||
| 	ShouldFail bool | ||||
| }{ | ||||
| 	{ | ||||
| 		ShouldFail: true, | ||||
| 		Config: VaultConfig{ | ||||
| 			Endpoint: "https://127.0.0.1:8080", | ||||
| 			Enabled:  true, | ||||
| 		}, | ||||
| 	}, | ||||
| 	{ | ||||
| 		ShouldFail: true, // 1 | ||||
| 		Config: VaultConfig{ | ||||
| 			Enabled:  true, | ||||
| 			Endpoint: "https://127.0.0.1:8080", | ||||
| 			Auth:     VaultAuth{Type: "unsupported"}, | ||||
| 		}, | ||||
| 	}, | ||||
| 	{ | ||||
| 		ShouldFail: true, // 2 | ||||
| 		Config: VaultConfig{ | ||||
| 			Enabled:  true, | ||||
| 			Endpoint: "https://127.0.0.1:8080", | ||||
| 			Auth: VaultAuth{ | ||||
| 				Type:    "approle", | ||||
| 				AppRole: VaultAppRole{}, | ||||
| 			}, | ||||
| 		}, | ||||
| 	}, | ||||
| 	{ | ||||
| 		ShouldFail: true, // 3 | ||||
| 		Config: VaultConfig{ | ||||
| 			Enabled:  true, | ||||
| 			Endpoint: "https://127.0.0.1:8080", | ||||
| 			Auth: VaultAuth{ | ||||
| 				Type:    "approle", | ||||
| 				AppRole: VaultAppRole{ID: "123456"}, | ||||
| 			}, | ||||
| 		}, | ||||
| 	}, | ||||
| 	{ | ||||
| 		ShouldFail: true, // 4 | ||||
| 		Config: VaultConfig{ | ||||
| 			Enabled:  true, | ||||
| 			Endpoint: "https://127.0.0.1:8080", | ||||
| 			Auth: VaultAuth{ | ||||
| 				Type:    "approle", | ||||
| 				AppRole: VaultAppRole{ID: "123456", Secret: "abcdef"}, | ||||
| 			}, | ||||
| 		}, | ||||
| 	}, | ||||
| 	{ | ||||
| 		ShouldFail: true, // 5 | ||||
| 		Config: VaultConfig{ | ||||
| 			Enabled:  true, | ||||
| 			Endpoint: "https://127.0.0.1:8080", | ||||
| 			Auth: VaultAuth{ | ||||
| 				Type:    "approle", | ||||
| 				AppRole: VaultAppRole{ID: "123456", Secret: "abcdef"}, | ||||
| 			}, | ||||
| 			Key: VaultKey{Name: "default-key", Version: -1}, | ||||
| 		}, | ||||
| 	}, | ||||
| } | ||||
| 
 | ||||
| func TestVerifyVaultConfig(t *testing.T) { | ||||
| 	for _, test := range verifyVaultConfigTests { | ||||
| 		test := test | ||||
| 		t.Run(test.Config.Endpoint, func(t *testing.T) { | ||||
| 			err := test.Config.Verify() | ||||
| 			if test.ShouldFail && err == nil { | ||||
| 				t.Errorf("Verify should fail but returned 'err == nil'") | ||||
| 			} | ||||
| 			if !test.ShouldFail && err != nil { | ||||
| 				t.Errorf("Verify should succeed but returned err: %s", err) | ||||
| 			} | ||||
| 		}) | ||||
| 	} | ||||
| } | ||||
| @ -19,14 +19,10 @@ package cmd | ||||
| import ( | ||||
| 	"bytes" | ||||
| 	"encoding/base64" | ||||
| 	"encoding/json" | ||||
| 	"fmt" | ||||
| 	"net/http" | ||||
| 	"os" | ||||
| 	"testing" | ||||
| 
 | ||||
| 	humanize "github.com/dustin/go-humanize" | ||||
| 	"github.com/klauspost/compress/zstd" | ||||
| 	"github.com/minio/minio-go/v7/pkg/encrypt" | ||||
| 	"github.com/minio/minio/cmd/crypto" | ||||
| 	xhttp "github.com/minio/minio/cmd/http" | ||||
| @ -619,7 +615,6 @@ func TestGetDefaultOpts(t *testing.T) { | ||||
| 		if err == nil { | ||||
| 			if opts.ServerSideEncryption == nil && test.encryptionType != "" { | ||||
| 				t.Errorf("Case %d: expected opts to be of %v encryption type", i, test.encryptionType) | ||||
| 
 | ||||
| 			} | ||||
| 			if opts.ServerSideEncryption != nil && test.encryptionType != opts.ServerSideEncryption.Type() { | ||||
| 				t.Errorf("Case %d: expected opts to have encryption type %v but was %v ", i, test.encryptionType, opts.ServerSideEncryption.Type()) | ||||
| @ -627,89 +622,3 @@ func TestGetDefaultOpts(t *testing.T) { | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| func Test_decryptObjectInfo(t *testing.T) { | ||||
| 	var testSet []struct { | ||||
| 		Bucket  string | ||||
| 		Name    string | ||||
| 		UserDef map[string]string | ||||
| 	} | ||||
| 	file, err := os.Open("testdata/decryptObjectInfo.json.zst") | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 	defer file.Close() | ||||
| 	dec, err := zstd.NewReader(file) | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 	defer dec.Close() | ||||
| 	js := json.NewDecoder(dec) | ||||
| 	err = js.Decode(&testSet) | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 
 | ||||
| 	os.Setenv("MINIO_KMS_MASTER_KEY", "my-minio-key:6368616e676520746869732070617373776f726420746f206120736563726574") | ||||
| 	defer os.Setenv("MINIO_KMS_MASTER_KEY", "") | ||||
| 	GlobalKMS, err = crypto.NewKMS(crypto.KMSConfig{}) | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 
 | ||||
| 	var dst [32]byte | ||||
| 	for i := range testSet { | ||||
| 		t.Run(fmt.Sprint("case-", i), func(t *testing.T) { | ||||
| 			test := &testSet[i] | ||||
| 			_, err := decryptObjectInfo(dst[:], test.Bucket, test.Name, test.UserDef) | ||||
| 			if err != nil { | ||||
| 				t.Fatal(err) | ||||
| 			} | ||||
| 		}) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func Benchmark_decryptObjectInfo(b *testing.B) { | ||||
| 	var testSet []struct { | ||||
| 		Bucket  string | ||||
| 		Name    string | ||||
| 		UserDef map[string]string | ||||
| 	} | ||||
| 	file, err := os.Open("testdata/decryptObjectInfo.json.zst") | ||||
| 	if err != nil { | ||||
| 		b.Fatal(err) | ||||
| 	} | ||||
| 	defer file.Close() | ||||
| 	dec, err := zstd.NewReader(file) | ||||
| 	if err != nil { | ||||
| 		b.Fatal(err) | ||||
| 	} | ||||
| 	defer dec.Close() | ||||
| 	js := json.NewDecoder(dec) | ||||
| 	err = js.Decode(&testSet) | ||||
| 	if err != nil { | ||||
| 		b.Fatal(err) | ||||
| 	} | ||||
| 
 | ||||
| 	os.Setenv("MINIO_KMS_MASTER_KEY", "my-minio-key:6368616e676520746869732070617373776f726420746f206120736563726574") | ||||
| 	defer os.Setenv("MINIO_KMS_MASTER_KEY", "") | ||||
| 	GlobalKMS, err = crypto.NewKMS(crypto.KMSConfig{}) | ||||
| 	if err != nil { | ||||
| 		b.Fatal(err) | ||||
| 	} | ||||
| 
 | ||||
| 	b.ReportAllocs() | ||||
| 	b.ResetTimer() | ||||
| 	b.SetBytes(int64(len(testSet))) | ||||
| 	b.RunParallel(func(pb *testing.PB) { | ||||
| 		for pb.Next() { | ||||
| 			var dst [32]byte | ||||
| 			for i := range testSet { | ||||
| 				test := &testSet[i] | ||||
| 				_, err := decryptObjectInfo(dst[:], test.Bucket, test.Name, test.UserDef) | ||||
| 				if err != nil { | ||||
| 					b.Fatal(err) | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 	}) | ||||
| } | ||||
|  | ||||
| @ -29,9 +29,11 @@ import ( | ||||
| 
 | ||||
| 	jwtgo "github.com/dgrijalva/jwt-go" | ||||
| 	"github.com/minio/minio-go/v7/pkg/set" | ||||
| 	"github.com/minio/minio/cmd/config" | ||||
| 	"github.com/minio/minio/cmd/logger" | ||||
| 	"github.com/minio/minio/pkg/auth" | ||||
| 	iampolicy "github.com/minio/minio/pkg/iam/policy" | ||||
| 	"github.com/minio/minio/pkg/kms" | ||||
| 	"github.com/minio/minio/pkg/madmin" | ||||
| 	etcd "go.etcd.io/etcd/clientv3" | ||||
| 	"go.etcd.io/etcd/mvcc/mvccpb" | ||||
| @ -85,38 +87,51 @@ func (ies *IAMEtcdStore) runlock() { | ||||
| 	ies.RUnlock() | ||||
| } | ||||
| 
 | ||||
| func (ies *IAMEtcdStore) saveIAMConfig(ctx context.Context, item interface{}, path string, opts ...options) error { | ||||
| func (ies *IAMEtcdStore) saveIAMConfig(ctx context.Context, item interface{}, itemPath string, opts ...options) error { | ||||
| 	data, err := json.Marshal(item) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	if globalConfigEncrypted { | ||||
| 		data, err = madmin.EncryptData(globalActiveCred.String(), data) | ||||
| 	if GlobalKMS != nil { | ||||
| 		data, err = config.EncryptBytes(GlobalKMS, data, kms.Context{ | ||||
| 			minioMetaBucket: path.Join(minioMetaBucket, itemPath), | ||||
| 		}) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 	} | ||||
| 	return saveKeyEtcd(ctx, ies.client, path, data, opts...) | ||||
| 	return saveKeyEtcd(ctx, ies.client, itemPath, data, opts...) | ||||
| } | ||||
| 
 | ||||
| func getIAMConfig(item interface{}, value []byte) error { | ||||
| 	conf := value | ||||
| func getIAMConfig(item interface{}, data []byte, itemPath string) error { | ||||
| 	var err error | ||||
| 	if globalConfigEncrypted && !utf8.Valid(value) { | ||||
| 		conf, err = madmin.DecryptData(globalActiveCred.String(), bytes.NewReader(conf)) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 	if !utf8.Valid(data) { | ||||
| 		if GlobalKMS != nil { | ||||
| 			data, err = config.DecryptBytes(GlobalKMS, data, kms.Context{ | ||||
| 				minioMetaBucket: path.Join(minioMetaBucket, itemPath), | ||||
| 			}) | ||||
| 			if err != nil { | ||||
| 				data, err = madmin.DecryptData(globalActiveCred.String(), bytes.NewReader(data)) | ||||
| 				if err != nil { | ||||
| 					return err | ||||
| 				} | ||||
| 			} | ||||
| 		} else { | ||||
| 			data, err = madmin.DecryptData(globalActiveCred.String(), bytes.NewReader(data)) | ||||
| 			if err != nil { | ||||
| 				return err | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	return json.Unmarshal(conf, item) | ||||
| 	return json.Unmarshal(data, item) | ||||
| } | ||||
| 
 | ||||
| func (ies *IAMEtcdStore) loadIAMConfig(ctx context.Context, item interface{}, path string) error { | ||||
| 	pdata, err := readKeyEtcd(ctx, ies.client, path) | ||||
| 	data, err := readKeyEtcd(ctx, ies.client, path) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	return getIAMConfig(item, pdata) | ||||
| 	return getIAMConfig(item, data, path) | ||||
| } | ||||
| 
 | ||||
| func (ies *IAMEtcdStore) deleteIAMConfig(ctx context.Context, path string) error { | ||||
| @ -265,7 +280,7 @@ func (ies *IAMEtcdStore) loadPolicyDoc(ctx context.Context, policy string, m map | ||||
| 
 | ||||
| func (ies *IAMEtcdStore) getPolicyDoc(ctx context.Context, kvs *mvccpb.KeyValue, m map[string]iampolicy.Policy) error { | ||||
| 	var p iampolicy.Policy | ||||
| 	err := getIAMConfig(&p, kvs.Value) | ||||
| 	err := getIAMConfig(&p, kvs.Value, string(kvs.Key)) | ||||
| 	if err != nil { | ||||
| 		if err == errConfigNotFound { | ||||
| 			return errNoSuchPolicy | ||||
| @ -298,7 +313,7 @@ func (ies *IAMEtcdStore) loadPolicyDocs(ctx context.Context, m map[string]iampol | ||||
| 
 | ||||
| func (ies *IAMEtcdStore) getUser(ctx context.Context, userkv *mvccpb.KeyValue, userType IAMUserType, m map[string]auth.Credentials, basePrefix string) error { | ||||
| 	var u UserIdentity | ||||
| 	err := getIAMConfig(&u, userkv.Value) | ||||
| 	err := getIAMConfig(&u, userkv.Value, string(userkv.Key)) | ||||
| 	if err != nil { | ||||
| 		if err == errConfigNotFound { | ||||
| 			return errNoSuchUser | ||||
| @ -436,7 +451,7 @@ func (ies *IAMEtcdStore) loadMappedPolicy(ctx context.Context, name string, user | ||||
| 
 | ||||
| func getMappedPolicy(ctx context.Context, kv *mvccpb.KeyValue, userType IAMUserType, isGroup bool, m map[string]MappedPolicy, basePrefix string) error { | ||||
| 	var p MappedPolicy | ||||
| 	err := getIAMConfig(&p, kv.Value) | ||||
| 	err := getIAMConfig(&p, kv.Value, string(kv.Key)) | ||||
| 	if err != nil { | ||||
| 		if err == errConfigNotFound { | ||||
| 			return errNoSuchPolicy | ||||
|  | ||||
| @ -27,11 +27,11 @@ import ( | ||||
| 	"time" | ||||
| 	"unicode/utf8" | ||||
| 
 | ||||
| 	jwtgo "github.com/dgrijalva/jwt-go" | ||||
| 
 | ||||
| 	"github.com/minio/minio/cmd/config" | ||||
| 	"github.com/minio/minio/cmd/logger" | ||||
| 	"github.com/minio/minio/pkg/auth" | ||||
| 	iampolicy "github.com/minio/minio/pkg/iam/policy" | ||||
| 	"github.com/minio/minio/pkg/kms" | ||||
| 	"github.com/minio/minio/pkg/madmin" | ||||
| ) | ||||
| 
 | ||||
| @ -204,29 +204,43 @@ func (iamOS *IAMObjectStore) migrateBackendFormat(ctx context.Context) error { | ||||
| 	return iamOS.migrateToV1(ctx) | ||||
| } | ||||
| 
 | ||||
| func (iamOS *IAMObjectStore) saveIAMConfig(ctx context.Context, item interface{}, path string, opts ...options) error { | ||||
| func (iamOS *IAMObjectStore) saveIAMConfig(ctx context.Context, item interface{}, objPath string, opts ...options) error { | ||||
| 	data, err := json.Marshal(item) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	if globalConfigEncrypted { | ||||
| 		data, err = madmin.EncryptData(globalActiveCred.String(), data) | ||||
| 	if GlobalKMS != nil { | ||||
| 		data, err = config.EncryptBytes(GlobalKMS, data, kms.Context{ | ||||
| 			minioMetaBucket: path.Join(minioMetaBucket, objPath), | ||||
| 		}) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 	} | ||||
| 	return saveConfig(ctx, iamOS.objAPI, path, data) | ||||
| 	return saveConfig(ctx, iamOS.objAPI, objPath, data) | ||||
| } | ||||
| 
 | ||||
| func (iamOS *IAMObjectStore) loadIAMConfig(ctx context.Context, item interface{}, path string) error { | ||||
| 	data, err := readConfig(ctx, iamOS.objAPI, path) | ||||
| func (iamOS *IAMObjectStore) loadIAMConfig(ctx context.Context, item interface{}, objPath string) error { | ||||
| 	data, err := readConfig(ctx, iamOS.objAPI, objPath) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	if globalConfigEncrypted && !utf8.Valid(data) { | ||||
| 		data, err = madmin.DecryptData(globalActiveCred.String(), bytes.NewReader(data)) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 	if !utf8.Valid(data) { | ||||
| 		if GlobalKMS != nil { | ||||
| 			data, err = config.DecryptBytes(GlobalKMS, data, kms.Context{ | ||||
| 				minioMetaBucket: path.Join(minioMetaBucket, objPath), | ||||
| 			}) | ||||
| 			if err != nil { | ||||
| 				data, err = madmin.DecryptData(globalActiveCred.String(), bytes.NewReader(data)) | ||||
| 				if err != nil { | ||||
| 					return err | ||||
| 				} | ||||
| 			} | ||||
| 		} else { | ||||
| 			data, err = madmin.DecryptData(globalActiveCred.String(), bytes.NewReader(data)) | ||||
| 			if err != nil { | ||||
| 				return err | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	return json.Unmarshal(data, item) | ||||
| @ -280,26 +294,6 @@ func (iamOS *IAMObjectStore) loadUser(ctx context.Context, user string, userType | ||||
| 		return nil | ||||
| 	} | ||||
| 
 | ||||
| 	// If this is a service account, rotate the session key if needed | ||||
| 	if globalOldCred.IsValid() && u.Credentials.IsServiceAccount() { | ||||
| 		if !globalOldCred.Equal(globalActiveCred) { | ||||
| 			m := jwtgo.MapClaims{} | ||||
| 			stsTokenCallback := func(t *jwtgo.Token) (interface{}, error) { | ||||
| 				return []byte(globalOldCred.SecretKey), nil | ||||
| 			} | ||||
| 			if _, err := jwtgo.ParseWithClaims(u.Credentials.SessionToken, m, stsTokenCallback); err == nil { | ||||
| 				jwt := jwtgo.NewWithClaims(jwtgo.SigningMethodHS512, jwtgo.MapClaims(m)) | ||||
| 				if token, err := jwt.SignedString([]byte(globalActiveCred.SecretKey)); err == nil { | ||||
| 					u.Credentials.SessionToken = token | ||||
| 					err := iamOS.saveIAMConfig(ctx, &u, getUserIdentityPath(user, userType)) | ||||
| 					if err != nil { | ||||
| 						return err | ||||
| 					} | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	if u.Credentials.AccessKey == "" { | ||||
| 		u.Credentials.AccessKey = user | ||||
| 	} | ||||
|  | ||||
| @ -21,12 +21,11 @@ import ( | ||||
| 	"context" | ||||
| 	"io" | ||||
| 	"math/rand" | ||||
| 	"os" | ||||
| 	"strconv" | ||||
| 	"testing" | ||||
| 
 | ||||
| 	"github.com/dustin/go-humanize" | ||||
| 	"github.com/minio/minio/cmd/crypto" | ||||
| 	"github.com/minio/minio/pkg/kms" | ||||
| ) | ||||
| 
 | ||||
| // Return pointer to testOneByteReadEOF{} | ||||
| @ -518,10 +517,8 @@ func enableCompression(t *testing.T, encrypt bool) { | ||||
| 	globalCompressConfigMu.Unlock() | ||||
| 	if encrypt { | ||||
| 		globalAutoEncryption = encrypt | ||||
| 		os.Setenv("MINIO_KMS_MASTER_KEY", "my-minio-key:6368616e676520746869732070617373776f726420746f206120736563726574") | ||||
| 		defer os.Setenv("MINIO_KMS_MASTER_KEY", "") | ||||
| 		var err error | ||||
| 		GlobalKMS, err = crypto.NewKMS(crypto.KMSConfig{}) | ||||
| 		GlobalKMS, err = kms.Parse("my-minio-key:5lF+0pJM0OWwlQrvK2S/I7W9mO4a6rJJI7wzj7v09cw=") | ||||
| 		if err != nil { | ||||
| 			t.Fatal(err) | ||||
| 		} | ||||
| @ -535,10 +532,8 @@ func enableEncrytion(t *testing.T) { | ||||
| 	globalCompressConfigMu.Unlock() | ||||
| 
 | ||||
| 	globalAutoEncryption = true | ||||
| 	os.Setenv("MINIO_KMS_MASTER_KEY", "my-minio-key:6368616e676520746869732070617373776f726420746f206120736563726574") | ||||
| 	defer os.Setenv("MINIO_KMS_MASTER_KEY", "") | ||||
| 	var err error | ||||
| 	GlobalKMS, err = crypto.NewKMS(crypto.KMSConfig{}) | ||||
| 	GlobalKMS, err = kms.Parse("my-minio-key:5lF+0pJM0OWwlQrvK2S/I7W9mO4a6rJJI7wzj7v09cw=") | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
|  | ||||
| @ -81,12 +81,9 @@ func TestMain(m *testing.M) { | ||||
| 
 | ||||
| 	// disable ENVs which interfere with tests. | ||||
| 	for _, env := range []string{ | ||||
| 		crypto.EnvAutoEncryptionLegacy, | ||||
| 		crypto.EnvKMSAutoEncryption, | ||||
| 		config.EnvAccessKey, | ||||
| 		config.EnvAccessKeyOld, | ||||
| 		config.EnvSecretKey, | ||||
| 		config.EnvSecretKeyOld, | ||||
| 	} { | ||||
| 		os.Unsetenv(env) | ||||
| 	} | ||||
|  | ||||
| @ -73,26 +73,26 @@ docker_secrets_env() { | ||||
| 
 | ||||
| ## Set KMS_MASTER_KEY from docker secrets if provided | ||||
| docker_kms_encryption_env() { | ||||
|     if [ -f "$MINIO_KMS_MASTER_KEY_FILE" ]; then | ||||
|         KMS_MASTER_KEY_FILE="$MINIO_KMS_MASTER_KEY_FILE" | ||||
|     if [ -f "$MINIO_KMS_SECRET_KEY_FILE" ]; then | ||||
|         KMS_SECRET_KEY_FILE="$MINIO_KMS_SECRET_KEY_FILE" | ||||
|     else | ||||
|         KMS_MASTER_KEY_FILE="/run/secrets/$MINIO_KMS_MASTER_KEY_FILE" | ||||
|         KMS_SECRET_KEY_FILE="/run/secrets/$MINIO_KMS_SECRET_KEY_FILE" | ||||
|     fi | ||||
| 
 | ||||
|     if [ -f "$KMS_MASTER_KEY_FILE" ]; then | ||||
|         MINIO_KMS_MASTER_KEY="$(cat "$KMS_MASTER_KEY_FILE")" | ||||
|         export MINIO_KMS_MASTER_KEY | ||||
|     if [ -f "$KMS_SECRET_KEY_FILE" ]; then | ||||
|         MINIO_KMS_SECRET_KEY="$(cat "$KMS_SECRET_KEY_FILE")" | ||||
|         export MINIO_KMS_SECRET_KEY | ||||
|     fi | ||||
| } | ||||
| 
 | ||||
| ## Legacy | ||||
| ## Set SSE_MASTER_KEY from docker secrets if provided | ||||
| docker_sse_encryption_env() { | ||||
|     SSE_MASTER_KEY_FILE="/run/secrets/$MINIO_SSE_MASTER_KEY_FILE" | ||||
|     KMS_SECRET_KEY_FILE="/run/secrets/$MINIO_KMS_MASTER_KEY_FILE" | ||||
| 
 | ||||
|     if [ -f "$SSE_MASTER_KEY_FILE" ]; then | ||||
|         MINIO_SSE_MASTER_KEY="$(cat "$SSE_MASTER_KEY_FILE")" | ||||
|         export MINIO_SSE_MASTER_KEY | ||||
|     if [ -f "$KMS_SECRET_KEY_FILE" ]; then | ||||
|         MINIO_KMS_SECRET_KEY="$(cat "$KMS_SECRET_KEY_FILE")" | ||||
|         export MINIO_KMS_SECRET_KEY | ||||
|     fi | ||||
| } | ||||
| 
 | ||||
|  | ||||
							
								
								
									
										6
									
								
								go.mod
									
									
									
									
									
								
							
							
						
						
									
										6
									
								
								go.mod
									
									
									
									
									
								
							| @ -31,7 +31,6 @@ require ( | ||||
| 	github.com/google/uuid v1.1.2 | ||||
| 	github.com/gorilla/handlers v1.5.1 | ||||
| 	github.com/gorilla/mux v1.8.0 | ||||
| 	github.com/hashicorp/vault/api v1.0.4 | ||||
| 	github.com/jcmturner/gokrb5/v8 v8.4.2 | ||||
| 	github.com/json-iterator/go v1.1.10 | ||||
| 	github.com/klauspost/compress v1.11.12 | ||||
| @ -77,15 +76,14 @@ require ( | ||||
| 	github.com/tidwall/gjson v1.6.8 | ||||
| 	github.com/tidwall/sjson v1.0.4 | ||||
| 	github.com/tinylib/msgp v1.1.3 | ||||
| 	github.com/ttacon/chalk v0.0.0-20160626202418-22c06c80ed31 // indirect | ||||
| 	github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a | ||||
| 	github.com/willf/bitset v1.1.11 // indirect | ||||
| 	github.com/willf/bloom v2.0.3+incompatible | ||||
| 	github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c | ||||
| 	go.etcd.io/etcd v0.0.0-20201125193152-8a03d2e9614b | ||||
| 	go.uber.org/zap v1.13.0 | ||||
| 	golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83 | ||||
| 	golang.org/x/net v0.0.0-20201216054612-986b41b23924 | ||||
| 	golang.org/x/crypto v0.0.0-20210415154028-4f45737414dc | ||||
| 	golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 | ||||
| 	golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073 | ||||
| 	golang.org/x/tools v0.1.0 // indirect | ||||
| 	google.golang.org/api v0.5.0 | ||||
|  | ||||
							
								
								
									
										51
									
								
								go.sum
									
									
									
									
									
								
							
							
						
						
									
										51
									
								
								go.sum
									
									
									
									
									
								
							| @ -149,8 +149,6 @@ github.com/go-asn1-ber/asn1-ber v1.5.1/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkPro | ||||
| github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= | ||||
| github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= | ||||
| github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o= | ||||
| github.com/go-ldap/ldap v3.0.2+incompatible h1:kD5HQcAzlQ7yrhfn+h+MSABeAy/jAJhvIJ/QDllP44g= | ||||
| github.com/go-ldap/ldap v3.0.2+incompatible/go.mod h1:qfd9rJvER9Q0/D/Sqn1DfHRoBp40uXYvFoEVrNEPqRc= | ||||
| github.com/go-ldap/ldap/v3 v3.2.4 h1:PFavAq2xTgzo/loE8qNXcQaofAaqIpI4WgaLdv+1l3E= | ||||
| github.com/go-ldap/ldap/v3 v3.2.4/go.mod h1:iYS1MdmrmceOJ1QOTnRXrIs7i3kloqtmGQjRvjKpyMg= | ||||
| github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= | ||||
| @ -162,7 +160,6 @@ github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG | ||||
| github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs= | ||||
| github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= | ||||
| github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= | ||||
| github.com/go-test/deep v1.0.2-0.20181118220953-042da051cf31/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= | ||||
| github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s= | ||||
| github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= | ||||
| github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= | ||||
| @ -239,13 +236,9 @@ github.com/grpc-ecosystem/grpc-gateway v1.9.5 h1:UImYN5qQ8tuGpGE16ZmjvcTtTw24zw1 | ||||
| github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= | ||||
| github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE= | ||||
| github.com/hashicorp/consul/sdk v0.3.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= | ||||
| github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= | ||||
| github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= | ||||
| github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= | ||||
| github.com/hashicorp/go-cleanhttp v0.5.1 h1:dH3aiDG9Jvb5r5+bYHsikaOUIpcM0xvgMXVoDkXMzJM= | ||||
| github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= | ||||
| github.com/hashicorp/go-hclog v0.0.0-20180709165350-ff2cf002a8dd/go.mod h1:9bjs9uLqI8l75knNv3lV1kA55veR+WUPSiKIWcQHudI= | ||||
| github.com/hashicorp/go-hclog v0.8.0/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= | ||||
| github.com/hashicorp/go-hclog v0.9.1/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= | ||||
| github.com/hashicorp/go-hclog v0.14.1 h1:nQcJDQwIAGnmoUWp8ubocEX40cCml/17YkF6csQLReU= | ||||
| github.com/hashicorp/go-hclog v0.14.1/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= | ||||
| @ -255,31 +248,20 @@ github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iP | ||||
| github.com/hashicorp/go-msgpack v0.5.5/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= | ||||
| github.com/hashicorp/go-msgpack v1.1.5 h1:9byZdVjKTe5mce63pRVNP1L7UAmdHOTEMGehn6KvJWs= | ||||
| github.com/hashicorp/go-msgpack v1.1.5/go.mod h1:gWVc3sv/wbDmR3rQsj1CAktEZzoz1YNK9NfGLXJ69/4= | ||||
| github.com/hashicorp/go-multierror v1.0.0 h1:iVjPR7a6H0tWELX5NxNe7bYopibicUzc7uPribsnS6o= | ||||
| github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= | ||||
| github.com/hashicorp/go-plugin v1.0.1/go.mod h1:++UyYGoz3o5w9ZzAdZxtQKrWWP+iqPBn3cQptSMzBuY= | ||||
| github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= | ||||
| github.com/hashicorp/go-retryablehttp v0.5.4 h1:1BZvpawXoJCWX6pNtow9+rpEj+3itIlutiqnntI6jOE= | ||||
| github.com/hashicorp/go-retryablehttp v0.5.4/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= | ||||
| github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= | ||||
| github.com/hashicorp/go-rootcerts v1.0.1 h1:DMo4fmknnz0E0evoNYnV48RjWndOsmd6OW+09R3cEP8= | ||||
| github.com/hashicorp/go-rootcerts v1.0.1/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= | ||||
| github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= | ||||
| github.com/hashicorp/go-sockaddr v1.0.2 h1:ztczhD1jLxIRjVejw8gFomI1BQZOe2WoVOu0SyteCQc= | ||||
| github.com/hashicorp/go-sockaddr v1.0.2/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjGlgmH/UkBUC97A= | ||||
| github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= | ||||
| github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= | ||||
| github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= | ||||
| github.com/hashicorp/go-uuid v1.0.2 h1:cfejS+Tpcp13yd5nYHWDI6qVCny6wyX2Mt5SGur2IGE= | ||||
| github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= | ||||
| github.com/hashicorp/go-version v1.1.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= | ||||
| github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= | ||||
| github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= | ||||
| github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= | ||||
| github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU= | ||||
| github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= | ||||
| github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= | ||||
| github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= | ||||
| github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= | ||||
| github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= | ||||
| github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= | ||||
| @ -287,12 +269,6 @@ github.com/hashicorp/raft v1.2.0 h1:mHzHIrF0S91d3A7RPBvuqkgB4d/7oFJZyvf1Q4m7GA0= | ||||
| github.com/hashicorp/raft v1.2.0/go.mod h1:vPAJM8Asw6u8LxC3eJCUZmRP/E4QmUGE1R7g7k8sG/8= | ||||
| github.com/hashicorp/raft-boltdb v0.0.0-20171010151810-6e5ba93211ea/go.mod h1:pNv7Wc3ycL6F5oOWn+tPGo2gWD4a5X+yp/ntwdKLjRk= | ||||
| github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= | ||||
| github.com/hashicorp/vault/api v1.0.4 h1:j08Or/wryXT4AcHj1oCbMd7IijXcKzYUGw59LGu9onU= | ||||
| github.com/hashicorp/vault/api v1.0.4/go.mod h1:gDcqh3WGcR1cpF5AJz/B1UFheUEneMoIospckxBxk6Q= | ||||
| github.com/hashicorp/vault/sdk v0.1.13 h1:mOEPeOhT7jl0J4AMl1E705+BcmeRs1VmKNb9F0sMLy8= | ||||
| github.com/hashicorp/vault/sdk v0.1.13/go.mod h1:B+hVj7TpuQY1Y/GPbCpffmgd+tSEwvhkWnjtSYCaS2M= | ||||
| github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= | ||||
| github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= | ||||
| github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= | ||||
| github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg= | ||||
| github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= | ||||
| @ -410,19 +386,14 @@ github.com/minio/simdjson-go v0.2.1/go.mod h1:JPUSkRykfSPS+AhO0YPA1h0l5vY7NqrF4z | ||||
| github.com/minio/sio v0.2.1 h1:NjzKiIMSMcHediVQR0AFVx2tp7Wxh9tKPfDI3kH7aHQ= | ||||
| github.com/minio/sio v0.2.1/go.mod h1:8b0yPp2avGThviy/+OCJBI6OMpvxoUuiLvE6F1lebhw= | ||||
| github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= | ||||
| github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= | ||||
| github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= | ||||
| github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= | ||||
| github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= | ||||
| github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= | ||||
| github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= | ||||
| github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= | ||||
| github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= | ||||
| github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= | ||||
| github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= | ||||
| github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= | ||||
| github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= | ||||
| github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= | ||||
| github.com/mmcloughlin/avo v0.0.0-20201105074841-5d2f697d268f/go.mod h1:6aKT4zZIrpGqB3RpFU14ByCSSyKY6LfJz4J/JJChHfI= | ||||
| github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= | ||||
| github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= | ||||
| @ -543,9 +514,6 @@ github.com/rs/xid v1.2.1 h1:mhH9Nq+C1fY2l1XIpgxIiUOfNpRBYH1kKcr+qfKgjRc= | ||||
| github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= | ||||
| github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= | ||||
| github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= | ||||
| github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= | ||||
| github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk= | ||||
| github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc= | ||||
| github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E= | ||||
| github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= | ||||
| github.com/secure-io/sio-go v0.3.1 h1:dNvY9awjabXTYGsTF1PiCySl9Ltofk9GA3VdWlo7rRc= | ||||
| @ -598,8 +566,6 @@ github.com/tinylib/msgp v1.1.3 h1:3giwAkmtaEDLSV0MdO1lDLuPgklgPzmk8H9+So2BVfA= | ||||
| github.com/tinylib/msgp v1.1.3/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE= | ||||
| github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8 h1:ndzgwNDnKIqyCvHTXaCqh9KlOWKvBry6nuXMJmonVsE= | ||||
| github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= | ||||
| github.com/ttacon/chalk v0.0.0-20160626202418-22c06c80ed31 h1:OXcKh35JaYsGMRzpvFkLv/MEyPuL49CThT1pZ8aSml4= | ||||
| github.com/ttacon/chalk v0.0.0-20160626202418-22c06c80ed31/go.mod h1:onvgF043R+lC5RZ8IT9rBXDaEDnpnw/Cl+HFiw+v/7Q= | ||||
| github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= | ||||
| github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= | ||||
| github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= | ||||
| @ -657,8 +623,9 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh | ||||
| golang.org/x/crypto v0.0.0-20200709230013-948cd5f35899/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= | ||||
| golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= | ||||
| golang.org/x/crypto v0.0.0-20201112155050-0c6587e931a9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= | ||||
| golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83 h1:/ZScEX8SfEmUGRHs0gxpqteO5nfNW6axyZbBdw9A12g= | ||||
| golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= | ||||
| golang.org/x/crypto v0.0.0-20210415154028-4f45737414dc h1:+q90ECDSAQirdykUN6sPEiBXBsp8Csjcca8Oy7bgLTA= | ||||
| golang.org/x/crypto v0.0.0-20210415154028-4f45737414dc/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= | ||||
| golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= | ||||
| golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= | ||||
| golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= | ||||
| @ -697,8 +664,8 @@ golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81R | ||||
| golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= | ||||
| golang.org/x/net v0.0.0-20200904194848-62affa334b73/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= | ||||
| golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= | ||||
| golang.org/x/net v0.0.0-20201216054612-986b41b23924 h1:QsnDpLLOKwHBBDa8nDws4DYNc/ryVW2vCpxCs09d4PY= | ||||
| golang.org/x/net v0.0.0-20201216054612-986b41b23924/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= | ||||
| golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 h1:qWPm9rbaAMKs8Bq/9LRpbMqxWRVUAQwMI9fVrssnTfw= | ||||
| golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= | ||||
| golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= | ||||
| golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421 h1:Wo7BWFiOk0QRFMLYMqJGFMd9CgUAcGx7V+qEg/h5IBI= | ||||
| golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= | ||||
| @ -720,11 +687,9 @@ golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5h | ||||
| golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= | ||||
| golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= | ||||
| golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= | ||||
| golang.org/x/sys v0.0.0-20190129075346-302c3dd5f1cc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= | ||||
| golang.org/x/sys v0.0.0-20190130150945-aca44879d564/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= | ||||
| golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= | ||||
| golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= | ||||
| golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||||
| golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||||
| golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||||
| golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||||
| @ -756,13 +721,11 @@ golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXR | ||||
| golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= | ||||
| golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= | ||||
| golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= | ||||
| golang.org/x/text v0.3.1-0.20181227161524-e6919f6577db/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= | ||||
| golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= | ||||
| golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= | ||||
| golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= | ||||
| golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= | ||||
| golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= | ||||
| golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= | ||||
| golang.org/x/time v0.0.0-20191024005414-555d28b269f0 h1:/5xXl8Y5W96D+TtHSlonuFqGHIWVuyCkGJLwGh9JJFs= | ||||
| golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= | ||||
| golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= | ||||
| @ -800,19 +763,16 @@ google.golang.org/appengine v1.4.0 h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO50 | ||||
| google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= | ||||
| google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= | ||||
| google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= | ||||
| google.golang.org/genproto v0.0.0-20190404172233-64821d5d2107/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= | ||||
| google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= | ||||
| google.golang.org/genproto v0.0.0-20190508193815-b515fa19cec8/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= | ||||
| google.golang.org/genproto v0.0.0-20190530194941-fb225487d101/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s= | ||||
| google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55 h1:gSJIx1SDwno+2ElGhA4+qG2zF97qiUzTM+rQ0klBOcE= | ||||
| google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= | ||||
| google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= | ||||
| google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= | ||||
| google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= | ||||
| google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM= | ||||
| google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= | ||||
| google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= | ||||
| google.golang.org/grpc v1.22.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= | ||||
| google.golang.org/grpc v1.22.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= | ||||
| google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= | ||||
| google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= | ||||
| @ -827,7 +787,6 @@ google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2 | ||||
| google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM= | ||||
| google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= | ||||
| gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= | ||||
| gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d/go.mod h1:cuepJuh7vyXfUyUwEgHQXw849cJrilpS5NeIjOWESAw= | ||||
| gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= | ||||
| gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= | ||||
| gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= | ||||
| @ -850,8 +809,6 @@ gopkg.in/jcmturner/gokrb5.v7 v7.5.0/go.mod h1:l8VISx+WGYp+Fp7KRbsiUuXTTOnxIc3Tuv | ||||
| gopkg.in/jcmturner/rpc.v1 v1.1.0 h1:QHIUxTX1ISuAv9dD2wJ9HWQVuWDX/Zc0PfeC2tjc4rU= | ||||
| gopkg.in/jcmturner/rpc.v1 v1.1.0/go.mod h1:YIdkC4XfD6GXbzje11McwsDuOlZQSb9W4vfLvuNnlv8= | ||||
| gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= | ||||
| gopkg.in/square/go-jose.v2 v2.3.1 h1:SK5KegNXmKmqE342YYN2qPHEnUYeoMiXXl1poUlI+o4= | ||||
| gopkg.in/square/go-jose.v2 v2.3.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= | ||||
| gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= | ||||
| gopkg.in/urfave/cli.v1 v1.20.0/go.mod h1:vuBzUtMdQeixQj8LVd+/98pzhxNGQoyuPBlsXHOQNO0= | ||||
| gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= | ||||
|  | ||||
| @ -1,3 +1,5 @@ | ||||
| // +build fips | ||||
| 
 | ||||
| // MinIO Cloud Storage, (C) 2021 MinIO, Inc. | ||||
| // | ||||
| // Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| @ -12,8 +14,6 @@ | ||||
| // See the License for the specific language governing permissions and | ||||
| // limitations under the License. | ||||
| 
 | ||||
| //+build fips | ||||
| 
 | ||||
| package fips | ||||
| 
 | ||||
| import ( | ||||
| @ -37,6 +37,6 @@ func cipherSuitesTLS() []uint16 { | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func ellipticCurvesTLS() []tls.Curve { | ||||
| func ellipticCurvesTLS() []tls.CurveID { | ||||
| 	return []tls.CurveID{tls.CurveP256} | ||||
| } | ||||
|  | ||||
| @ -52,7 +52,7 @@ func Parse(s string) (KMS, error) { | ||||
| } | ||||
| 
 | ||||
| // New returns a single-key KMS that derives new DEKs from the | ||||
| // given key. | ||||
| // given key. The given key must always be 32 bytes. | ||||
| func New(keyID string, key []byte) (KMS, error) { | ||||
| 	if len(key) != 32 { | ||||
| 		return nil, errors.New("kms: invalid key length " + strconv.Itoa(len(key))) | ||||
|  | ||||
| @ -1,5 +1,5 @@ | ||||
| /* | ||||
|  * MinIO Cloud Storage, (C) 2018 MinIO, Inc. | ||||
|  * MinIO Cloud Storage, (C) 2018-2021 MinIO, Inc. | ||||
|  * | ||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|  * you may not use this file except in compliance with the License. | ||||
| @ -19,21 +19,18 @@ package madmin | ||||
| 
 | ||||
| import ( | ||||
| 	"bytes" | ||||
| 	"crypto/sha256" | ||||
| 	"errors" | ||||
| 	"io" | ||||
| 	"io/ioutil" | ||||
| 
 | ||||
| 	"github.com/minio/minio/pkg/argon2" | ||||
| 	"github.com/minio/minio/pkg/fips" | ||||
| 	"github.com/secure-io/sio-go" | ||||
| 	"github.com/secure-io/sio-go/sioutil" | ||||
| 	"golang.org/x/crypto/pbkdf2" | ||||
| ) | ||||
| 
 | ||||
| var idKey func([]byte, []byte, []byte, []byte, uint32) []byte | ||||
| 
 | ||||
| func init() { | ||||
| 	idKey = argon2.NewIDKey(1, 64*1024, 4) | ||||
| } | ||||
| 
 | ||||
| // EncryptData encrypts the data with an unique key | ||||
| // derived from password using the Argon2id PBKDF. | ||||
| // | ||||
| @ -43,24 +40,35 @@ func init() { | ||||
| func EncryptData(password string, data []byte) ([]byte, error) { | ||||
| 	salt := sioutil.MustRandom(32) | ||||
| 
 | ||||
| 	// Derive an unique 256 bit key from the password and the random salt. | ||||
| 	key := idKey([]byte(password), salt, nil, nil, 32) | ||||
| 
 | ||||
| 	var ( | ||||
| 		id     byte | ||||
| 		err    error | ||||
| 		stream *sio.Stream | ||||
| 	) | ||||
| 	if useAES() { // Only use AES-GCM if we can use an optimized implementation | ||||
| 		id = aesGcm | ||||
| 	if fips.Enabled() { | ||||
| 		key := pbkdf2.Key([]byte(password), salt, pbkdf2Cost, 32, sha256.New) | ||||
| 		stream, err = sio.AES_256_GCM.Stream(key) | ||||
| 		if err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
| 		id = pbkdf2AESGCM | ||||
| 	} else { | ||||
| 		id = c20p1305 | ||||
| 		stream, err = sio.ChaCha20Poly1305.Stream(key) | ||||
| 	} | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 		key := argon2.IDKey([]byte(password), salt, argon2idTime, argon2idMemory, argon2idThreads, 32) | ||||
| 		if sioutil.NativeAES() { | ||||
| 			stream, err = sio.AES_256_GCM.Stream(key) | ||||
| 			if err != nil { | ||||
| 				return nil, err | ||||
| 			} | ||||
| 			id = argon2idAESGCM | ||||
| 		} else { | ||||
| 			stream, err = sio.ChaCha20Poly1305.Stream(key) | ||||
| 			if err != nil { | ||||
| 				return nil, err | ||||
| 			} | ||||
| 			id = argon2idChaCHa20Poly1305 | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	nonce := sioutil.MustRandom(stream.NonceSize()) | ||||
| 
 | ||||
| 	// ciphertext = salt || AEAD ID | nonce | encrypted data | ||||
| @ -110,33 +118,43 @@ func DecryptData(password string, data io.Reader) ([]byte, error) { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 
 | ||||
| 	key := idKey([]byte(password), salt[:], nil, nil, 32) | ||||
| 	var ( | ||||
| 		err    error | ||||
| 		stream *sio.Stream | ||||
| 	) | ||||
| 	switch id[0] { | ||||
| 	case aesGcm: | ||||
| 	switch { | ||||
| 	case id[0] == argon2idAESGCM: | ||||
| 		key := argon2.IDKey([]byte(password), salt[:], argon2idTime, argon2idMemory, argon2idThreads, 32) | ||||
| 		stream, err = sio.AES_256_GCM.Stream(key) | ||||
| 	case c20p1305: | ||||
| 	case id[0] == argon2idChaCHa20Poly1305: | ||||
| 		key := argon2.IDKey([]byte(password), salt[:], argon2idTime, argon2idMemory, argon2idThreads, 32) | ||||
| 		stream, err = sio.ChaCha20Poly1305.Stream(key) | ||||
| 	case id[0] == pbkdf2AESGCM: | ||||
| 		key := pbkdf2.Key([]byte(password), salt[:], pbkdf2Cost, 32, sha256.New) | ||||
| 		stream, err = sio.AES_256_GCM.Stream(key) | ||||
| 	default: | ||||
| 		err = errors.New("madmin: invalid AEAD algorithm ID") | ||||
| 		err = errors.New("madmin: invalid encryption algorithm ID") | ||||
| 	} | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 
 | ||||
| 	enBytes, err := ioutil.ReadAll(stream.DecryptReader(data, nonce[:], nil)) | ||||
| 	plaintext, err := ioutil.ReadAll(stream.DecryptReader(data, nonce[:], nil)) | ||||
| 	if err != nil { | ||||
| 		if err == sio.NotAuthentic { | ||||
| 			return enBytes, ErrMaliciousData | ||||
| 		} | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	return enBytes, err | ||||
| 	return plaintext, err | ||||
| } | ||||
| 
 | ||||
| const ( | ||||
| 	aesGcm   = 0x00 | ||||
| 	c20p1305 = 0x01 | ||||
| 	argon2idAESGCM           = 0x00 | ||||
| 	argon2idChaCHa20Poly1305 = 0x01 | ||||
| 	pbkdf2AESGCM             = 0x02 | ||||
| ) | ||||
| 
 | ||||
| const ( | ||||
| 	argon2idTime    = 1 | ||||
| 	argon2idMemory  = 64 * 1024 | ||||
| 	argon2idThreads = 4 | ||||
| 	pbkdf2Cost      = 8192 | ||||
| ) | ||||
|  | ||||
| @ -1,22 +0,0 @@ | ||||
| // MinIO Cloud Storage, (C) 2021 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. | ||||
| 
 | ||||
| // +build fips | ||||
| 
 | ||||
| package madmin | ||||
| 
 | ||||
| // useAES always returns true since AES is the only | ||||
| // option out of AES-GCM and ChaCha20-Poly1305 that | ||||
| // is approved by the NIST. | ||||
| func useAES() bool { return true } | ||||
| @ -1,24 +0,0 @@ | ||||
| // MinIO Cloud Storage, (C) 2021 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. | ||||
| 
 | ||||
| // +build !fips | ||||
| 
 | ||||
| package madmin | ||||
| 
 | ||||
| import "github.com/secure-io/sio-go/sioutil" | ||||
| 
 | ||||
| // useAES returns true if the executing CPU provides | ||||
| // AES-GCM hardware instructions and an optimized | ||||
| // assembler implementation is available. | ||||
| func useAES() bool { return sioutil.NativeAES() } | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user