mirror of
https://github.com/hashicorp/vault.git
synced 2026-05-05 04:16:31 +02:00
OCI KMS auto-unseal plugin (#6950)
This commit is contained in:
parent
df173e70fc
commit
feafd1b388
@ -27,6 +27,9 @@ func configureSeal(configSeal *server.Seal, infoKeys *[]string, info *map[string
|
||||
case seal.AzureKeyVault:
|
||||
return configureAzureKeyVaultSeal(configSeal, infoKeys, info, logger, inseal)
|
||||
|
||||
case seal.OCIKMS:
|
||||
return configureOCIKMSSeal(configSeal, infoKeys, info, logger, inseal)
|
||||
|
||||
case seal.Transit:
|
||||
return configureTransitSeal(configSeal, infoKeys, info, logger, inseal)
|
||||
|
||||
|
||||
25
command/server/seal/server_seal_ocikms.go
Normal file
25
command/server/seal/server_seal_ocikms.go
Normal file
@ -0,0 +1,25 @@
|
||||
// Copyright © 2019, Oracle and/or its affiliates.
|
||||
package seal
|
||||
|
||||
import (
|
||||
log "github.com/hashicorp/go-hclog"
|
||||
"github.com/hashicorp/vault/command/server"
|
||||
"github.com/hashicorp/vault/vault"
|
||||
"github.com/hashicorp/vault/vault/seal/ocikms"
|
||||
)
|
||||
|
||||
func configureOCIKMSSeal(configSeal *server.Seal, infoKeys *[]string, info *map[string]string, logger log.Logger, inseal vault.Seal) (vault.Seal, error) {
|
||||
kms := ocikms.NewSeal(logger)
|
||||
kmsInfo, err := kms.SetConfig(configSeal.Config)
|
||||
if err != nil {
|
||||
logger.Error("error on setting up config for OCI KMS", "error", err)
|
||||
return nil, err
|
||||
}
|
||||
autoseal := vault.NewAutoSeal(kms)
|
||||
if kmsInfo != nil {
|
||||
*infoKeys = append(*infoKeys, "Seal Type", "OCI KMS KeyID")
|
||||
(*info)["Seal Type"] = configSeal.Type
|
||||
(*info)["OCI KMS KeyID"] = kmsInfo["key_id"]
|
||||
}
|
||||
return autoseal, nil
|
||||
}
|
||||
382
vault/seal/ocikms/ocikms.go
Normal file
382
vault/seal/ocikms/ocikms.go
Normal file
@ -0,0 +1,382 @@
|
||||
// Copyright © 2019, Oracle and/or its affiliates.
|
||||
package ocikms
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"math"
|
||||
"os"
|
||||
"strconv"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/armon/go-metrics"
|
||||
"github.com/hashicorp/errwrap"
|
||||
log "github.com/hashicorp/go-hclog"
|
||||
"github.com/hashicorp/vault/sdk/physical"
|
||||
"github.com/hashicorp/vault/vault/seal"
|
||||
"github.com/oracle/oci-go-sdk/common"
|
||||
"github.com/oracle/oci-go-sdk/common/auth"
|
||||
"github.com/oracle/oci-go-sdk/keymanagement"
|
||||
)
|
||||
|
||||
const (
|
||||
// OCI KMS key ID to use for encryption and decryption
|
||||
EnvOCIKMSSealKeyID = "VAULT_OCIKMS_SEAL_KEY_ID"
|
||||
// OCI KMS crypto endpoint to use for encryption and decryption
|
||||
EnvOCIKMSCryptoEndpoint = "VAULT_OCIKMS_CRYPTO_ENDPOINT"
|
||||
// OCI KMS management endpoint to manage keys
|
||||
EnvOCIKMSManagementEndpoint = "VAULT_OCIKMS_MANAGEMENT_ENDPOINT"
|
||||
// Maximum number of retries
|
||||
KMSMaximumNumberOfRetries = 5
|
||||
// keyID config
|
||||
KMSConfigKeyID = "key_id"
|
||||
// cryptoEndpoint config
|
||||
KMSConfigCryptoEndpoint = "crypto_endpoint"
|
||||
// managementEndpoint config
|
||||
KMSConfigManagementEndpoint = "management_endpoint"
|
||||
// authTypeAPIKey config
|
||||
KMSConfigAuthTypeAPIKey = "auth_type_api_key"
|
||||
)
|
||||
|
||||
var (
|
||||
metricInit = []string{"ocikms", "init"}
|
||||
metricEncrypt = []string{"ocikms", "encrypt"}
|
||||
metricDecrypt = []string{"ocikms", "decrypt"}
|
||||
|
||||
metricInitFailed = []string{"ocikms", "initFailed"}
|
||||
metricEncryptFailed = []string{"ocikms", "encryptFailed"}
|
||||
metricDecryptFailed = []string{"ocikms", "decryptFailed"}
|
||||
)
|
||||
|
||||
// OCIKMSMechanism is the method used to encrypt/decrypt in auto unseal process
|
||||
type OCIKMSMechanism uint32
|
||||
|
||||
type OCIKMSSeal struct {
|
||||
authTypeAPIKey bool // true for user principal, false for instance principal, default is false
|
||||
keyID string // OCI KMS keyID
|
||||
|
||||
cryptoEndpoint string // OCI KMS crypto endpoint
|
||||
managementEndpoint string // OCI KMS management endpoint
|
||||
|
||||
cryptoClient *keymanagement.KmsCryptoClient // OCI KMS crypto client
|
||||
managementClient *keymanagement.KmsManagementClient // OCI KMS management client
|
||||
|
||||
currentKeyID *atomic.Value // Current key version which is used for encryption/decryption
|
||||
|
||||
logger log.Logger
|
||||
}
|
||||
|
||||
var _ seal.Access = (*OCIKMSSeal)(nil)
|
||||
|
||||
// NewSeal creates a new OCIKMSSeal seal with the provided logger
|
||||
func NewSeal(logger log.Logger) *OCIKMSSeal {
|
||||
k := &OCIKMSSeal{
|
||||
logger: logger,
|
||||
currentKeyID: new(atomic.Value),
|
||||
}
|
||||
k.currentKeyID.Store("")
|
||||
return k
|
||||
}
|
||||
|
||||
func (k *OCIKMSSeal) SetConfig(config map[string]string) (map[string]string, error) {
|
||||
defer metrics.MeasureSince(metricInit, time.Now())
|
||||
if config == nil {
|
||||
config = map[string]string{}
|
||||
}
|
||||
|
||||
// Check and set KeyID
|
||||
switch {
|
||||
case os.Getenv(EnvOCIKMSSealKeyID) != "":
|
||||
k.keyID = os.Getenv(EnvOCIKMSSealKeyID)
|
||||
case config[KMSConfigKeyID] != "":
|
||||
k.keyID = config[KMSConfigKeyID]
|
||||
default:
|
||||
metrics.IncrCounter(metricInitFailed, 1)
|
||||
return nil, fmt.Errorf("'%s' not found for OCI KMS seal configuration", KMSConfigKeyID)
|
||||
}
|
||||
k.logger.Info("OCI KMS configuration", KMSConfigKeyID, k.keyID)
|
||||
|
||||
// Check and set cryptoEndpoint
|
||||
switch {
|
||||
case os.Getenv(EnvOCIKMSCryptoEndpoint) != "":
|
||||
k.cryptoEndpoint = os.Getenv(EnvOCIKMSCryptoEndpoint)
|
||||
case config[KMSConfigCryptoEndpoint] != "":
|
||||
k.cryptoEndpoint = config[KMSConfigCryptoEndpoint]
|
||||
default:
|
||||
metrics.IncrCounter(metricInitFailed, 1)
|
||||
return nil, fmt.Errorf("'%s' not found for OCI KMS seal configuration", KMSConfigCryptoEndpoint)
|
||||
}
|
||||
k.logger.Info("OCI KMS configuration", KMSConfigCryptoEndpoint, k.cryptoEndpoint)
|
||||
|
||||
// Check and set managementEndpoint
|
||||
switch {
|
||||
case os.Getenv(EnvOCIKMSManagementEndpoint) != "":
|
||||
k.managementEndpoint = os.Getenv(EnvOCIKMSManagementEndpoint)
|
||||
case config[KMSConfigManagementEndpoint] != "":
|
||||
k.managementEndpoint = config[KMSConfigManagementEndpoint]
|
||||
default:
|
||||
metrics.IncrCounter(metricInitFailed, 1)
|
||||
return nil, fmt.Errorf("'%s' not found for OCI KMS seal configuration", KMSConfigManagementEndpoint)
|
||||
}
|
||||
k.logger.Info("OCI KMS configuration", KMSConfigManagementEndpoint, k.managementEndpoint)
|
||||
|
||||
// Check and set authTypeAPIKey
|
||||
var err error
|
||||
k.authTypeAPIKey = false
|
||||
authTypeAPIKeyStr := config[KMSConfigAuthTypeAPIKey]
|
||||
if authTypeAPIKeyStr != "" {
|
||||
k.authTypeAPIKey, err = strconv.ParseBool(authTypeAPIKeyStr)
|
||||
if err != nil {
|
||||
metrics.IncrCounter(metricInitFailed, 1)
|
||||
return nil, errwrap.Wrapf("failed parsing "+KMSConfigAuthTypeAPIKey+" parameter: {{err}}", err)
|
||||
}
|
||||
}
|
||||
if k.authTypeAPIKey {
|
||||
k.logger.Info("using OCI KMS with user principal")
|
||||
} else {
|
||||
k.logger.Info("using OCI KMS with instance principal")
|
||||
}
|
||||
|
||||
// Check and set OCI KMS crypto client
|
||||
if k.cryptoClient == nil {
|
||||
kmsCryptoClient, err := k.getOCIKMSCryptoClient()
|
||||
if err != nil {
|
||||
metrics.IncrCounter(metricInitFailed, 1)
|
||||
return nil, errwrap.Wrapf("error initializing OCI KMS client: {{err}}", err)
|
||||
}
|
||||
k.cryptoClient = kmsCryptoClient
|
||||
}
|
||||
|
||||
// Check and set OCI KMS management client
|
||||
if k.managementClient == nil {
|
||||
kmsManagementClient, err := k.getOCIKMSManagementClient()
|
||||
if err != nil {
|
||||
metrics.IncrCounter(metricInitFailed, 1)
|
||||
return nil, errwrap.Wrapf("error initializing OCI KMS client: {{err}}", err)
|
||||
}
|
||||
k.managementClient = kmsManagementClient
|
||||
}
|
||||
|
||||
// Calling Encrypt method with empty string just to validate keyId access and store current keyVersion
|
||||
encryptedBlobInfo, err := k.Encrypt(context.Background(), []byte(""))
|
||||
if err != nil || encryptedBlobInfo == nil {
|
||||
metrics.IncrCounter(metricInitFailed, 1)
|
||||
return nil, errwrap.Wrapf("failed "+KMSConfigKeyID+" validation: {{err}}", err)
|
||||
}
|
||||
k.logger.Info("successfully validated " + KMSConfigKeyID)
|
||||
|
||||
// Map that holds non-sensitive configuration info
|
||||
sealInfo := make(map[string]string)
|
||||
sealInfo[KMSConfigKeyID] = k.keyID
|
||||
sealInfo[KMSConfigCryptoEndpoint] = k.cryptoEndpoint
|
||||
sealInfo[KMSConfigManagementEndpoint] = k.managementEndpoint
|
||||
|
||||
return sealInfo, nil
|
||||
}
|
||||
|
||||
func (k *OCIKMSSeal) SealType() string {
|
||||
return seal.OCIKMS
|
||||
}
|
||||
|
||||
func (k *OCIKMSSeal) KeyID() string {
|
||||
return k.currentKeyID.Load().(string)
|
||||
}
|
||||
|
||||
func (k *OCIKMSSeal) Init(context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (k *OCIKMSSeal) Finalize(context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (k *OCIKMSSeal) Encrypt(ctx context.Context, plaintext []byte) (*physical.EncryptedBlobInfo, error) {
|
||||
defer metrics.MeasureSince(metricEncrypt, time.Now())
|
||||
if plaintext == nil {
|
||||
metrics.IncrCounter(metricEncryptFailed, 1)
|
||||
return nil, fmt.Errorf("given plaintext for encryption is nil")
|
||||
}
|
||||
|
||||
env, err := seal.NewEnvelope().Encrypt(plaintext)
|
||||
if err != nil {
|
||||
metrics.IncrCounter(metricEncryptFailed, 1)
|
||||
return nil, errwrap.Wrapf("error wrapping data: {{err}}", err)
|
||||
}
|
||||
|
||||
if k.cryptoClient == nil {
|
||||
metrics.IncrCounter(metricEncryptFailed, 1)
|
||||
return nil, fmt.Errorf("nil client")
|
||||
}
|
||||
|
||||
// OCI KMS required base64 encrypted plain text before sending to the service
|
||||
encodedKey := base64.StdEncoding.EncodeToString(env.Key)
|
||||
|
||||
// Build Encrypt Request
|
||||
requestMetadata := k.getRequestMetadata()
|
||||
encryptedDataDetails := keymanagement.EncryptDataDetails{
|
||||
KeyId: &k.keyID,
|
||||
Plaintext: &encodedKey,
|
||||
}
|
||||
|
||||
input := keymanagement.EncryptRequest{
|
||||
EncryptDataDetails: encryptedDataDetails,
|
||||
RequestMetadata: requestMetadata,
|
||||
}
|
||||
output, err := k.cryptoClient.Encrypt(ctx, input)
|
||||
if err != nil {
|
||||
metrics.IncrCounter(metricEncryptFailed, 1)
|
||||
return nil, errwrap.Wrapf("error encrypting data: {{err}}", err)
|
||||
}
|
||||
|
||||
// Note: It is potential a timing issue if the key gets rotated between this
|
||||
// getCurrentKeyVersion operation and above Encrypt operation
|
||||
keyVersion, err := k.getCurrentKeyVersion()
|
||||
if err != nil {
|
||||
metrics.IncrCounter(metricEncryptFailed, 1)
|
||||
return nil, errwrap.Wrapf("error getting current key version: {{err}}", err)
|
||||
}
|
||||
// Update key version
|
||||
k.currentKeyID.Store(keyVersion)
|
||||
|
||||
ret := &physical.EncryptedBlobInfo{
|
||||
Ciphertext: env.Ciphertext,
|
||||
IV: env.IV,
|
||||
KeyInfo: &physical.SealKeyInfo{
|
||||
// Storing current key version in case we want to re-wrap older entries
|
||||
KeyID: keyVersion,
|
||||
WrappedKey: []byte(*output.Ciphertext),
|
||||
},
|
||||
}
|
||||
|
||||
k.logger.Debug("successfully encrypted")
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func (k *OCIKMSSeal) Decrypt(ctx context.Context, in *physical.EncryptedBlobInfo) ([]byte, error) {
|
||||
defer metrics.MeasureSince(metricDecrypt, time.Now())
|
||||
if in == nil {
|
||||
return nil, fmt.Errorf("given input for decryption is nil")
|
||||
}
|
||||
|
||||
requestMetadata := k.getRequestMetadata()
|
||||
cipherTextBlob := string(in.KeyInfo.WrappedKey)
|
||||
decryptedDataDetails := keymanagement.DecryptDataDetails{
|
||||
KeyId: &k.keyID,
|
||||
Ciphertext: &cipherTextBlob,
|
||||
}
|
||||
input := keymanagement.DecryptRequest{
|
||||
DecryptDataDetails: decryptedDataDetails,
|
||||
RequestMetadata: requestMetadata,
|
||||
}
|
||||
output, err := k.cryptoClient.Decrypt(ctx, input)
|
||||
if err != nil {
|
||||
metrics.IncrCounter(metricDecryptFailed, 1)
|
||||
return nil, errwrap.Wrapf("error decrypting data: {{err}}", err)
|
||||
}
|
||||
envelopKey, err := base64.StdEncoding.DecodeString(*output.Plaintext)
|
||||
if err != nil {
|
||||
metrics.IncrCounter(metricDecryptFailed, 1)
|
||||
return nil, errwrap.Wrapf("error base64 decrypting data: {{err}}", err)
|
||||
}
|
||||
envInfo := &seal.EnvelopeInfo{
|
||||
Key: envelopKey,
|
||||
IV: in.IV,
|
||||
Ciphertext: in.Ciphertext,
|
||||
}
|
||||
|
||||
plaintext, err := seal.NewEnvelope().Decrypt(envInfo)
|
||||
if err != nil {
|
||||
return nil, errwrap.Wrapf("error decrypting data: {{err}}", err)
|
||||
}
|
||||
|
||||
return plaintext, nil
|
||||
}
|
||||
|
||||
func (k *OCIKMSSeal) getConfigProvider() (common.ConfigurationProvider, error) {
|
||||
var cp common.ConfigurationProvider
|
||||
var err error
|
||||
if k.authTypeAPIKey {
|
||||
cp = common.DefaultConfigProvider()
|
||||
} else {
|
||||
cp, err = auth.InstancePrincipalConfigurationProvider()
|
||||
if err != nil {
|
||||
return nil, errwrap.Wrapf("failed creating InstancePrincipalConfigurationProvider: {{err}}", err)
|
||||
}
|
||||
}
|
||||
return cp, nil
|
||||
}
|
||||
|
||||
// Build OCI KMS crypto client
|
||||
func (k *OCIKMSSeal) getOCIKMSCryptoClient() (*keymanagement.KmsCryptoClient, error) {
|
||||
cp, err := k.getConfigProvider()
|
||||
if err != nil {
|
||||
return nil, errwrap.Wrapf("failed creating configuration provider: {{err}}", err)
|
||||
}
|
||||
|
||||
// Build crypto client
|
||||
kmsCryptoClient, err := keymanagement.NewKmsCryptoClientWithConfigurationProvider(cp, k.cryptoEndpoint)
|
||||
if err != nil {
|
||||
return nil, errwrap.Wrapf("failed creating NewKmsCryptoClientWithConfigurationProvider: {{err}}", err)
|
||||
}
|
||||
|
||||
return &kmsCryptoClient, nil
|
||||
}
|
||||
|
||||
// Build OCI KMS management client
|
||||
func (k *OCIKMSSeal) getOCIKMSManagementClient() (*keymanagement.KmsManagementClient, error) {
|
||||
cp, err := k.getConfigProvider()
|
||||
if err != nil {
|
||||
return nil, errwrap.Wrapf("failed creating configuration provider: {{err}}", err)
|
||||
}
|
||||
|
||||
// Build crypto client
|
||||
kmsManagementClient, err := keymanagement.NewKmsManagementClientWithConfigurationProvider(cp, k.managementEndpoint)
|
||||
if err != nil {
|
||||
return nil, errwrap.Wrapf("failed creating NewKmsCryptoClientWithConfigurationProvider: {{err}}", err)
|
||||
}
|
||||
|
||||
return &kmsManagementClient, nil
|
||||
}
|
||||
|
||||
// Request metadata includes retry policy
|
||||
func (k *OCIKMSSeal) getRequestMetadata() common.RequestMetadata {
|
||||
// Only retry for 5xx errors
|
||||
retryOn5xxFunc := func(r common.OCIOperationResponse) bool {
|
||||
return r.Error != nil && r.Response.HTTPResponse().StatusCode >= 500
|
||||
}
|
||||
return getRequestMetadataWithCustomizedRetryPolicy(retryOn5xxFunc)
|
||||
}
|
||||
|
||||
func getRequestMetadataWithCustomizedRetryPolicy(fn func(r common.OCIOperationResponse) bool) common.RequestMetadata {
|
||||
return common.RequestMetadata{
|
||||
RetryPolicy: getExponentialBackoffRetryPolicy(uint(KMSMaximumNumberOfRetries), fn),
|
||||
}
|
||||
}
|
||||
|
||||
func getExponentialBackoffRetryPolicy(n uint, fn func(r common.OCIOperationResponse) bool) *common.RetryPolicy {
|
||||
// The duration between each retry operation, you might want to wait longer each time the retry fails
|
||||
exponentialBackoff := func(r common.OCIOperationResponse) time.Duration {
|
||||
return time.Duration(math.Pow(float64(2), float64(r.AttemptNumber-1))) * time.Second
|
||||
}
|
||||
policy := common.NewRetryPolicy(n, fn, exponentialBackoff)
|
||||
return &policy
|
||||
}
|
||||
|
||||
func (k *OCIKMSSeal) getCurrentKeyVersion() (string, error) {
|
||||
if k.managementClient == nil {
|
||||
return "", fmt.Errorf("managementClient has not yet initialized")
|
||||
}
|
||||
requestMetadata := k.getRequestMetadata()
|
||||
getKeyInput := keymanagement.GetKeyRequest{
|
||||
KeyId: &k.keyID,
|
||||
RequestMetadata: requestMetadata,
|
||||
}
|
||||
getKeyResponse, err := k.managementClient.GetKey(context.Background(), getKeyInput)
|
||||
if err != nil || getKeyResponse.CurrentKeyVersion == nil {
|
||||
return "", errwrap.Wrapf("failed getting current key version: {{err}}", err)
|
||||
}
|
||||
|
||||
return *getKeyResponse.CurrentKeyVersion, nil
|
||||
}
|
||||
68
vault/seal/ocikms/ocikms_test.go
Normal file
68
vault/seal/ocikms/ocikms_test.go
Normal file
@ -0,0 +1,68 @@
|
||||
// Copyright © 2019, Oracle and/or its affiliates.
|
||||
package ocikms
|
||||
|
||||
import (
|
||||
"os"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
log "github.com/hashicorp/go-hclog"
|
||||
"github.com/hashicorp/vault/sdk/helper/logging"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
/*
|
||||
* To run these tests, ensure you setup:
|
||||
* 1. OCI SDK with your credentials. Refer to here:
|
||||
* https://docs.cloud.oracle.com/iaas/Content/API/Concepts/sdkconfig.htm
|
||||
* 2. Go to ocikms folder: vault/vault/seal/ocikms
|
||||
* VAULT_OCIKMS_SEAL_KEY_ID="your-kms-key" VAULT_OCIKMS_CRYPTO_ENDPOINT="your-kms-crypto-endpoint" go test
|
||||
*/
|
||||
|
||||
func TestOCIKMSSeal(t *testing.T) {
|
||||
initSeal(t)
|
||||
}
|
||||
|
||||
func TestOCIKMSSeal_LifeCycle(t *testing.T) {
|
||||
s := initSeal(t)
|
||||
|
||||
// Test Encrypt and Decrypt calls
|
||||
input := []byte("foo")
|
||||
swi, err := s.Encrypt(context.Background(), input)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err.Error())
|
||||
}
|
||||
|
||||
pt, err := s.Decrypt(context.Background(), swi)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err.Error())
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(input, pt) {
|
||||
t.Fatalf("expected %s, got %s", input, pt)
|
||||
}
|
||||
}
|
||||
|
||||
func initSeal(t *testing.T) *OCIKMSSeal {
|
||||
// Skip tests if we are not running acceptance tests
|
||||
if os.Getenv("VAULT_ACC") == "" {
|
||||
t.SkipNow()
|
||||
}
|
||||
s := NewSeal(logging.NewVaultLogger(log.Trace))
|
||||
_, err := s.SetConfig(nil)
|
||||
if err == nil {
|
||||
t.Fatal("expected error when OCIKMSSeal required values are not provided")
|
||||
}
|
||||
|
||||
mockConfig := map[string]string{
|
||||
|
||||
"auth_type_api_key": "true",
|
||||
}
|
||||
|
||||
_, err = s.SetConfig(mockConfig)
|
||||
if err != nil {
|
||||
t.Fatalf("error setting seal config: %v", err)
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
@ -13,6 +13,7 @@ const (
|
||||
AWSKMS = "awskms"
|
||||
GCPCKMS = "gcpckms"
|
||||
AzureKeyVault = "azurekeyvault"
|
||||
OCIKMS = "ocikms"
|
||||
Transit = "transit"
|
||||
Test = "test-auto"
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user