Skip to content

Commit

Permalink
Keymaker master key with KMS (#553)
Browse files Browse the repository at this point in the history
* zhars/keymaker_master_key_with_keymaker

Implement keymaker KMS support for MasterKey generation
  • Loading branch information
Zhaars authored Jul 26, 2022
1 parent 1072de8 commit 94e13a0
Show file tree
Hide file tree
Showing 12 changed files with 517 additions and 117 deletions.
63 changes: 60 additions & 3 deletions cmd/acra-keymaker/acra-keymaker.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ limitations under the License.
package main

import (
"context"
"crypto/x509"
"encoding/pem"
"flag"
Expand All @@ -30,17 +31,18 @@ import (
"os"
"strings"

"github.com/cossacklabs/acra/network"

"github.com/cossacklabs/acra/cmd"
"github.com/cossacklabs/acra/keystore"
"github.com/cossacklabs/acra/keystore/filesystem"
"github.com/cossacklabs/acra/keystore/keyloader"
"github.com/cossacklabs/acra/keystore/keyloader/hashicorp"
"github.com/cossacklabs/acra/keystore/keyloader/kms"
baseKMS "github.com/cossacklabs/acra/keystore/kms"
keystoreV2 "github.com/cossacklabs/acra/keystore/v2/keystore"
filesystemV2 "github.com/cossacklabs/acra/keystore/v2/keystore/filesystem"
filesystemBackendV2 "github.com/cossacklabs/acra/keystore/v2/keystore/filesystem/backend"
"github.com/cossacklabs/acra/logging"
"github.com/cossacklabs/acra/network"
"github.com/cossacklabs/acra/utils"

log "github.com/sirupsen/logrus"
Expand All @@ -65,10 +67,12 @@ func main() {
poisonRecord := flag.Bool("generate_poisonrecord_keys", false, "Generate keypair and symmetric key for poison records")
cmd.RegisterRedisKeyStoreParameters()
keystoreVersion := flag.String("keystore", "", "set keystore format: v1 (current), v2 (new)")
kmsKeyPolicy := flag.String("kms_key_policy", kms.KeyPolicyCreate, fmt.Sprintf("KMS usage key policy: <%s>", strings.Join(kms.SupportedPolicies, "|")))

tlsClientCert := flag.String("tls_cert", "", "Path to TLS certificate to use as client_id identifier")
tlsIdentifierExtractorType := flag.String("tls_identifier_extractor_type", network.IdentifierExtractorTypeDistinguishedName, fmt.Sprintf("Decide which field of TLS certificate to use as ClientID (%s). Default is %s.", strings.Join(network.IdentifierExtractorTypesList, "|"), network.IdentifierExtractorTypeDistinguishedName))

kms.RegisterCLIParameters()
hashicorp.RegisterVaultCLIParameters()
logging.SetLogLevel(logging.LogVerbose)

Expand Down Expand Up @@ -146,6 +150,28 @@ func main() {
log.WithError(err).Errorln("Failed to generate master key")
os.Exit(1)
}

if kmsOptions := kms.GetCLIParameters(); kmsOptions.KMSType != "" {
keyManager, err := kmsOptions.NewKeyManager()
if err != nil {
log.WithError(err).WithField("path", *masterKey).Errorln("Failed to initializer kms KeyManager")
os.Exit(1)
}

switch *kmsKeyPolicy {
case kms.KeyPolicyCreate:
newKey, err = newMasterKeyWithKMSCreate(keyManager, newKey)
if err != nil {
log.WithField("path", *masterKey).Errorln("Failed to create key with KMS")
os.Exit(1)
}

default:
log.WithField("supported", kms.SupportedPolicies).WithField("policy", kmsOptions.KeyPolicy).Errorln("Unsupported key policy for `kms_key_policy`")
os.Exit(1)
}
}

if err := ioutil.WriteFile(*masterKey, newKey, 0600); err != nil {
log.WithError(err).WithField("path", *masterKey).Errorln("Failed to write master key")
os.Exit(1)
Expand All @@ -163,7 +189,7 @@ func main() {
}
}

keyLoader, err := keyloader.GetInitializedMasterKeyLoader(hashicorp.GetVaultCLIParameters())
keyLoader, err := keyloader.GetInitializedMasterKeyLoader(hashicorp.GetVaultCLIParameters(), kms.GetCLIParameters())
if err != nil {
log.WithError(err).Errorln("Can't initialize ACRA_MASTER_KEY loader")
os.Exit(1)
Expand Down Expand Up @@ -333,3 +359,34 @@ func openKeyStoreV2(keyDirPath string, loader keyloader.MasterKeyLoader) keystor
}
return keystoreV2.NewServerKeyStore(keyDirectory)
}

func newMasterKeyWithKMSCreate(keyManager baseKMS.KeyManager, key []byte) ([]byte, error) {
ctx, _ := context.WithTimeout(context.Background(), network.DefaultNetworkTimeout)

ok, err := keyManager.IsKeyExist(ctx, kms.AcraMasterKeyKEKID)
if err != nil {
log.WithError(err).WithField("key", kms.AcraMasterKeyKEKID).Errorln("Failed to check if key is exist in KMS")
return nil, err
}
if ok {
log.WithField("key", kms.AcraMasterKeyKEKID).Errorln("Key already exist in KMS")
return nil, err
}

keyMetaData, err := keyManager.CreateKey(ctx, baseKMS.CreateKeyMetadata{
KeyName: kms.AcraMasterKeyKEKID,
})
if err != nil {
log.WithError(err).WithField("key", kms.AcraMasterKeyKEKID).Errorln("Failed to create KMS key")
return nil, err
}

log.WithField("keyID", keyMetaData.KeyID).Infof("New KMS key created")
key, err = keyManager.Encrypt(ctx, []byte(kms.AcraMasterKeyKEKID), key, nil)
if err != nil {
log.WithError(err).WithField("key", kms.AcraMasterKeyKEKID).Errorln("Failed to encrypt with KMS key")
return nil, err
}

return key, nil
}
9 changes: 9 additions & 0 deletions configs/acra-keymaker.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,15 @@ keys_public_output_dir: .acrakeys
# set keystore format: v1 (current), v2 (new)
keystore:

# KMS credentials JSON file path
kms_credentials_path:

# KMS usage key policy: <create>
kms_key_policy: create

# KMS type for using: <aws>
kms_type:

# Number of Redis database for keys
redis_db_keys: 0

Expand Down
2 changes: 1 addition & 1 deletion keystore/keyloader/kms/aws_enable.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,5 @@ import (
)

func init() {
kms.RegisterEncryptorCreator(kms.TypeAWS, aws.NewEncryptor)
kms.RegisterKeyManagerCreator(TypeAWS, aws.NewKeyManager)
}
48 changes: 44 additions & 4 deletions keystore/keyloader/kms/kms_cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,30 @@ import (
log "github.com/sirupsen/logrus"
)

const kmsTypeFlag = "kms_type"
// AcraMasterKeyKEKID represent ID/alias of encryption key used for MasterKey loading
const AcraMasterKeyKEKID = "acra_master_key"

// TypeAWS supported KMS type AWS
const TypeAWS = "aws"

// supportedTypes contains all possible values for flag `--kms_type`
var supportedTypes = []string{
TypeAWS,
}

// KeyPolicyCreate represent KMS key policy
const KeyPolicyCreate = "create"

// SupportedPolicies contains all possible values for flag `--kms_key_policy`
var SupportedPolicies = []string{
KeyPolicyCreate,
}

// CLIOptions keep command-line options related to KMS ACRA_MASTER_KEY loading.
type CLIOptions struct {
KMSType string
CredentialsPath string
KeyPolicy string
}

var cliOptions CLIOptions
Expand All @@ -31,8 +49,8 @@ func (options *CLIOptions) RegisterCLIParameters(flags *flag.FlagSet, prefix str
if description != "" {
description = " (" + description + ")"
}
if flags.Lookup(prefix+kmsTypeFlag) == nil {
flags.StringVar(&options.KMSType, prefix+kmsTypeFlag, "", fmt.Sprintf("KMS type for using: <%s>", strings.Join(kms.SupportedTypes, "|")+description))
if flags.Lookup(prefix+"kms_type") == nil {
flags.StringVar(&options.KMSType, prefix+"kms_type", "", fmt.Sprintf("KMS type for using: <%s>", strings.Join(supportedTypes, "|")+description))
// TODO: how to better provide an example of configuration files for different providers
flags.StringVar(&options.CredentialsPath, prefix+"kms_credentials_path", "", "KMS credentials JSON file path"+description)
}
Expand All @@ -49,6 +67,28 @@ func (options *CLIOptions) New() (keyloader.MasterKeyLoader, error) {
return nil, nil
}

keyManager, err := options.NewKeyManager()
if err != nil {
return nil, err
}

log.Infoln("Using KMS for ACRA_MASTER_KEY loading...")
return NewLoader(options.CredentialsPath, options.KMSType)
return NewLoader(keyManager), nil
}

// NewKeyManager create kms.KeyManager from kms.CLIOptions
func (options *CLIOptions) NewKeyManager() (kms.KeyManager, error) {
createKeyManager, ok := kms.GetKeyManagerCreator(options.KMSType)
if !ok {
log.Errorf("Unknown KMS type provided %s", options.KMSType)
return nil, nil
}

keyManager, err := createKeyManager(options.CredentialsPath)
if err != nil {
return nil, err
}

log.Infof("Initialized %s KeyManager", keyManager.ID())
return keyManager, nil
}
25 changes: 6 additions & 19 deletions keystore/keyloader/kms/kms_loader.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,30 +17,17 @@ type Loader struct {
}

// NewLoader create new kms MasterKeyLoader
func NewLoader(credentialPath, kmsType string) (*Loader, error) {
createEncryptor, ok := kms.GetEncryptorCreator(kmsType)
if !ok {
log.Errorf("Unknown KMS type provided %s", kmsType)
return nil, nil
}

encryptor, err := createEncryptor(credentialPath)
if err != nil {
log.WithError(err).Errorf("Failed to initialize %s MasterKeyLoader", encryptor.ID())
return nil, err
}

log.Infof("Initialized %s MasterKeyLoader", encryptor.ID())
func NewLoader(encryptor kms.Encryptor) *Loader {
return &Loader{
encryptor: encryptor,
}, nil
}
}

// LoadMasterKey implementation kms MasterKeyLoader for loading AcraMasterKey for keystore v1
func (loader *Loader) LoadMasterKey() ([]byte, error) {
rawKey, err := loader.decryptWithKMSKey([]byte(kms.AcraMasterKeyKEKID))
rawKey, err := loader.decryptWithKMSKey([]byte(AcraMasterKeyKEKID))
if err != nil {
log.WithError(err).Warnf("Failed to decrypt ACRA_MASTER_KEY with KMS keyID %s", kms.AcraMasterKeyKEKID)
log.WithError(err).Warnf("Failed to decrypt ACRA_MASTER_KEY with KMS keyID %s", AcraMasterKeyKEKID)
return nil, err
}

Expand All @@ -54,9 +41,9 @@ func (loader *Loader) LoadMasterKey() ([]byte, error) {

// LoadMasterKeys implementation kms MasterKeyLoader for loading AcraMasterKey for keystore v2
func (loader *Loader) LoadMasterKeys() (encryption []byte, signature []byte, err error) {
rawKey, err := loader.decryptWithKMSKey([]byte(kms.AcraMasterKeyKEKID))
rawKey, err := loader.decryptWithKMSKey([]byte(AcraMasterKeyKEKID))
if err != nil {
log.WithError(err).Warnf("Failed to decrypt ACRA_MASTER_KEY with KMS keyID %s", kms.AcraMasterKeyKEKID)
log.WithError(err).Warnf("Failed to decrypt ACRA_MASTER_KEY with KMS keyID %s", AcraMasterKeyKEKID)
return nil, nil, err
}

Expand Down
15 changes: 4 additions & 11 deletions keystore/keyloader/kms/kms_loader_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,13 @@ import (
"testing"

"github.com/cossacklabs/acra/keystore"
"github.com/cossacklabs/acra/keystore/kms"
"github.com/cossacklabs/acra/keystore/mocks"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
)

func TestSuccessMasterKeyLoading(t *testing.T) {
kmsEncryptor := &mocks.Encryptor{}
kmsKeyManager := &mocks.KeyManager{}

key := make([]byte, 64)
_, err := rand.Read(key)
Expand All @@ -25,16 +24,10 @@ func TestSuccessMasterKeyLoading(t *testing.T) {
os.Setenv(keystore.AcraMasterKeyVarName, masterKey)
defer os.Unsetenv(keystore.AcraMasterKeyVarName)

kmsEncryptor.On("ID").Return("mocked KMS encryptor")
kmsEncryptor.On("Decrypt", mock.Anything, []byte(kms.AcraMasterKeyKEKID), key, []byte(nil)).Return([]byte(masterKey), nil)
kmsKeyManager.On("ID").Return("mocked KMS encryptor")
kmsKeyManager.On("Decrypt", mock.Anything, []byte(AcraMasterKeyKEKID), key, []byte(nil)).Return([]byte(masterKey), nil)

encryptorCreator := func(path string) (kms.Encryptor, error) {
return kmsEncryptor, nil
}
kms.RegisterEncryptorCreator(kms.TypeAWS, encryptorCreator)

kmsLoader, err := NewLoader("config.json", kms.TypeAWS)
assert.NoError(t, err)
kmsLoader := NewLoader(kmsKeyManager)

loadedMasterKey, err := kmsLoader.LoadMasterKey()
assert.NoError(t, err)
Expand Down
Loading

0 comments on commit 94e13a0

Please sign in to comment.