parent
61577b803e
commit
c05fa0ac85
@ -1,8 +0,0 @@ |
|||||||
package blsloader |
|
||||||
|
|
||||||
import bls_core "github.com/harmony-one/bls/ffi/go/bls" |
|
||||||
|
|
||||||
type decrypter interface { |
|
||||||
extension() string |
|
||||||
decrypt(keyFile string) (*bls_core.SecretKey, error) |
|
||||||
} |
|
@ -0,0 +1,286 @@ |
|||||||
|
package blsloader |
||||||
|
|
||||||
|
import ( |
||||||
|
"encoding/json" |
||||||
|
"fmt" |
||||||
|
"io/ioutil" |
||||||
|
"sync" |
||||||
|
"time" |
||||||
|
|
||||||
|
"github.com/harmony-one/harmony/internal/blsgen" |
||||||
|
|
||||||
|
"github.com/aws/aws-sdk-go/aws" |
||||||
|
"github.com/aws/aws-sdk-go/aws/credentials" |
||||||
|
"github.com/aws/aws-sdk-go/aws/session" |
||||||
|
"github.com/aws/aws-sdk-go/service/kms" |
||||||
|
bls_core "github.com/harmony-one/bls/ffi/go/bls" |
||||||
|
"github.com/pkg/errors" |
||||||
|
) |
||||||
|
|
||||||
|
// AwsConfigSrcType is the type of src to load aws config. Four options available:
|
||||||
|
// AwsCfgSrcNil - Disable kms decryption
|
||||||
|
// AwsCfgSrcFile - Provide the aws config through a file (json).
|
||||||
|
// AwsCfgSrcPrompt - Provide the aws config though prompt.
|
||||||
|
// AwsCfgSrcShared - Use the shard aws config (env -> default .aws directory)
|
||||||
|
type AwsCfgSrcType uint8 |
||||||
|
|
||||||
|
const ( |
||||||
|
AwsCfgSrcNil AwsCfgSrcType = iota // nil place holder.
|
||||||
|
AwsCfgSrcFile // through a config file (json)
|
||||||
|
AwsCfgSrcPrompt // through console prompt.
|
||||||
|
AwsCfgSrcShared // through shared aws config
|
||||||
|
) |
||||||
|
|
||||||
|
func (srcType AwsCfgSrcType) isValid() bool { |
||||||
|
switch srcType { |
||||||
|
case AwsCfgSrcFile, AwsCfgSrcPrompt, AwsCfgSrcShared: |
||||||
|
return true |
||||||
|
default: |
||||||
|
return false |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// kmsDecrypterConfig is the data structure of kmsClientProvider config
|
||||||
|
type kmsDecrypterConfig struct { |
||||||
|
awsCfgSrcType AwsCfgSrcType |
||||||
|
awsConfigFile *string |
||||||
|
} |
||||||
|
|
||||||
|
// kmsDecrypter provide the kms client with singleton lazy initialization with config get
|
||||||
|
// from awsConfigProvider for aws credential and regions loading.
|
||||||
|
type kmsDecrypter struct { |
||||||
|
config kmsDecrypterConfig |
||||||
|
|
||||||
|
provider awsConfigProvider |
||||||
|
client *kms.KMS |
||||||
|
err error |
||||||
|
once sync.Once |
||||||
|
} |
||||||
|
|
||||||
|
// newKmsDecrypter creates a kmsDecrypter with the given config
|
||||||
|
func newKmsDecrypter(config kmsDecrypterConfig) (*kmsDecrypter, error) { |
||||||
|
kd := &kmsDecrypter{config: config} |
||||||
|
if err := kd.validateConfig(); err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
kd.makeACProvider() |
||||||
|
return kd, nil |
||||||
|
} |
||||||
|
|
||||||
|
// extension returns the kms key file extension
|
||||||
|
func (kd *kmsDecrypter) extension() string { |
||||||
|
return kmsKeyExt |
||||||
|
} |
||||||
|
|
||||||
|
// decryptFile decrypt a kms key file to a secret key
|
||||||
|
func (kd *kmsDecrypter) decryptFile(keyFile string) (*bls_core.SecretKey, error) { |
||||||
|
kms, err := kd.getKMSClient() |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
return blsgen.LoadAwsCMKEncryptedBLSKey(keyFile, kms) |
||||||
|
} |
||||||
|
|
||||||
|
func (kd *kmsDecrypter) validateConfig() error { |
||||||
|
config := kd.config |
||||||
|
if !config.awsCfgSrcType.isValid() { |
||||||
|
return errors.New("unknown AwsCfgSrcType") |
||||||
|
} |
||||||
|
if config.awsCfgSrcType == AwsCfgSrcFile { |
||||||
|
if !stringIsSet(config.awsConfigFile) { |
||||||
|
return errors.New("config field AwsConfig file must set for AwsCfgSrcFile") |
||||||
|
} |
||||||
|
if err := checkIsFile(*config.awsConfigFile); err != nil { |
||||||
|
return fmt.Errorf("aws config file %v: %v", *config.awsConfigFile, err) |
||||||
|
} |
||||||
|
} |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
func (kd *kmsDecrypter) makeACProvider() { |
||||||
|
config := kd.config |
||||||
|
switch config.awsCfgSrcType { |
||||||
|
case AwsCfgSrcFile: |
||||||
|
kd.provider = newFileACProvider(*config.awsConfigFile) |
||||||
|
case AwsCfgSrcPrompt: |
||||||
|
kd.provider = newPromptACProvider(defKmsPromptTimeout) |
||||||
|
case AwsCfgSrcShared: |
||||||
|
kd.provider = newSharedAwsConfigProvider() |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func (kd *kmsDecrypter) getKMSClient() (*kms.KMS, error) { |
||||||
|
kd.once.Do(func() { |
||||||
|
cfg, err := kd.provider.getAwsConfig() |
||||||
|
if err != nil { |
||||||
|
kd.err = err |
||||||
|
return |
||||||
|
} |
||||||
|
kd.client, kd.err = kmsClientWithConfig(cfg) |
||||||
|
}) |
||||||
|
if kd.err != nil { |
||||||
|
return nil, kd.err |
||||||
|
} |
||||||
|
return kd.client, nil |
||||||
|
} |
||||||
|
|
||||||
|
// AwsConfig is the config data structure for credentials and region. Used for AWS KMS
|
||||||
|
// decryption.
|
||||||
|
type AwsConfig struct { |
||||||
|
AccessKey string `json:"aws-access-key-id"` |
||||||
|
SecretKey string `json:"aws-secret-access-key"` |
||||||
|
Region string `json:"aws-region"` |
||||||
|
Token string `json:"aws-token,omitempty"` |
||||||
|
} |
||||||
|
|
||||||
|
func (cfg AwsConfig) toAws() *aws.Config { |
||||||
|
cred := credentials.NewStaticCredentials(cfg.AccessKey, cfg.SecretKey, cfg.Token) |
||||||
|
return &aws.Config{ |
||||||
|
Region: aws.String(cfg.Region), |
||||||
|
Credentials: cred, |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// awsConfigProvider provides the aws config. Implemented by
|
||||||
|
// sharedACProvider - provide the nil to use shared AWS configuration
|
||||||
|
// fileACProvider - provide the aws config with a json file
|
||||||
|
// promptACProvider - provide the config field from prompt with time out
|
||||||
|
type awsConfigProvider interface { |
||||||
|
getAwsConfig() (*AwsConfig, error) |
||||||
|
String() string |
||||||
|
} |
||||||
|
|
||||||
|
// sharedACProvider returns nil for getAwsConfig to use shared aws configurations
|
||||||
|
type sharedACProvider struct{} |
||||||
|
|
||||||
|
func newSharedAwsConfigProvider() *sharedACProvider { |
||||||
|
return &sharedACProvider{} |
||||||
|
} |
||||||
|
|
||||||
|
func (provider *sharedACProvider) getAwsConfig() (*AwsConfig, error) { |
||||||
|
return nil, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (provider *sharedACProvider) String() string { |
||||||
|
return "shared aws config" |
||||||
|
} |
||||||
|
|
||||||
|
// fileACProvider get aws config through a customized json file
|
||||||
|
type fileACProvider struct { |
||||||
|
file string |
||||||
|
} |
||||||
|
|
||||||
|
func newFileACProvider(file string) *fileACProvider { |
||||||
|
return &fileACProvider{file} |
||||||
|
} |
||||||
|
|
||||||
|
func (provider *fileACProvider) getAwsConfig() (*AwsConfig, error) { |
||||||
|
b, err := ioutil.ReadFile(provider.file) |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
var cfg AwsConfig |
||||||
|
if err := json.Unmarshal(b, &cfg); err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
return &cfg, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (provider *fileACProvider) String() string { |
||||||
|
return fmt.Sprintf("file %v", provider.file) |
||||||
|
} |
||||||
|
|
||||||
|
// promptACProvider provide a user interactive console for AWS config.
|
||||||
|
// Four fields are asked:
|
||||||
|
// 1. AccessKey 2. SecretKey 3. Region
|
||||||
|
// Each field is asked with a timeout mechanism.
|
||||||
|
type promptACProvider struct { |
||||||
|
timeout time.Duration |
||||||
|
} |
||||||
|
|
||||||
|
func newPromptACProvider(timeout time.Duration) *promptACProvider { |
||||||
|
return &promptACProvider{ |
||||||
|
timeout: timeout, |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func (provider *promptACProvider) getAwsConfig() (*AwsConfig, error) { |
||||||
|
console.println("Please provide AWS configurations for KMS encoded BLS keys:") |
||||||
|
accessKey, err := provider.prompt(" AccessKey:") |
||||||
|
if err != nil { |
||||||
|
return nil, fmt.Errorf("cannot get aws access key: %v", err) |
||||||
|
} |
||||||
|
secretKey, err := provider.prompt(" SecretKey:") |
||||||
|
if err != nil { |
||||||
|
return nil, fmt.Errorf("cannot get aws secret key: %v", err) |
||||||
|
} |
||||||
|
region, err := provider.prompt("Region:") |
||||||
|
if err != nil { |
||||||
|
return nil, fmt.Errorf("cannot get aws region: %v", err) |
||||||
|
} |
||||||
|
return &AwsConfig{ |
||||||
|
AccessKey: accessKey, |
||||||
|
SecretKey: secretKey, |
||||||
|
Region: region, |
||||||
|
Token: "", |
||||||
|
}, nil |
||||||
|
} |
||||||
|
|
||||||
|
// prompt prompt the user to input a string for a certain field with timeout.
|
||||||
|
func (provider *promptACProvider) prompt(hint string) (string, error) { |
||||||
|
var ( |
||||||
|
res string |
||||||
|
err error |
||||||
|
|
||||||
|
finished = make(chan struct{}) |
||||||
|
timedOut = time.After(provider.timeout) |
||||||
|
) |
||||||
|
|
||||||
|
go func() { |
||||||
|
res, err = provider.threadedPrompt(hint) |
||||||
|
close(finished) |
||||||
|
}() |
||||||
|
|
||||||
|
for { |
||||||
|
select { |
||||||
|
case <-finished: |
||||||
|
return res, err |
||||||
|
case <-timedOut: |
||||||
|
return "", errors.New("timed out") |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func (provider *promptACProvider) threadedPrompt(hint string) (string, error) { |
||||||
|
console.print(hint) |
||||||
|
return console.readPassword() |
||||||
|
} |
||||||
|
|
||||||
|
func (provider *promptACProvider) String() string { |
||||||
|
return "prompt" |
||||||
|
} |
||||||
|
|
||||||
|
func kmsClientWithConfig(config *AwsConfig) (*kms.KMS, error) { |
||||||
|
if config == nil { |
||||||
|
return getSharedKMSClient() |
||||||
|
} |
||||||
|
return getKMSClientFromConfig(*config) |
||||||
|
} |
||||||
|
|
||||||
|
func getSharedKMSClient() (*kms.KMS, error) { |
||||||
|
sess, err := session.NewSessionWithOptions(session.Options{ |
||||||
|
SharedConfigState: session.SharedConfigEnable, |
||||||
|
}) |
||||||
|
if err != nil { |
||||||
|
return nil, errors.Wrapf(err, "failed to create aws session") |
||||||
|
} |
||||||
|
return kms.New(sess), err |
||||||
|
} |
||||||
|
|
||||||
|
func getKMSClientFromConfig(config AwsConfig) (*kms.KMS, error) { |
||||||
|
sess, err := session.NewSession(config.toAws()) |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
return kms.New(sess), nil |
||||||
|
} |
@ -1,249 +0,0 @@ |
|||||||
package blsloader |
|
||||||
|
|
||||||
import ( |
|
||||||
"encoding/json" |
|
||||||
"fmt" |
|
||||||
"io/ioutil" |
|
||||||
"sync" |
|
||||||
"time" |
|
||||||
|
|
||||||
"github.com/aws/aws-sdk-go/aws" |
|
||||||
"github.com/aws/aws-sdk-go/aws/credentials" |
|
||||||
"github.com/aws/aws-sdk-go/aws/session" |
|
||||||
"github.com/aws/aws-sdk-go/service/kms" |
|
||||||
"github.com/pkg/errors" |
|
||||||
) |
|
||||||
|
|
||||||
// AwsConfig is the config data structure for credentials and region. Used for AWS KMS
|
|
||||||
// decryption.
|
|
||||||
type AwsConfig struct { |
|
||||||
AccessKey string `json:"aws-access-key-id"` |
|
||||||
SecretKey string `json:"aws-secret-access-key"` |
|
||||||
Region string `json:"aws-region"` |
|
||||||
Token string `json:"aws-token,omitempty"` |
|
||||||
} |
|
||||||
|
|
||||||
func (cfg AwsConfig) toAws() *aws.Config { |
|
||||||
cred := credentials.NewStaticCredentials(cfg.AccessKey, cfg.SecretKey, cfg.Token) |
|
||||||
return &aws.Config{ |
|
||||||
Region: aws.String(cfg.Region), |
|
||||||
Credentials: cred, |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// kmsProviderConfig is the data structure of kmsClientProvider config
|
|
||||||
type kmsProviderConfig struct { |
|
||||||
awsCfgSrcType AwsCfgSrcType |
|
||||||
awsConfigFile *string |
|
||||||
} |
|
||||||
|
|
||||||
func (config kmsProviderConfig) validate() error { |
|
||||||
if !config.awsCfgSrcType.isValid() { |
|
||||||
return errors.New("unknown AwsCfgSrcType") |
|
||||||
} |
|
||||||
if config.awsCfgSrcType == AwsCfgSrcFile { |
|
||||||
if !stringIsSet(config.awsConfigFile) { |
|
||||||
return errors.New("config field AwsConfig file must set for AwsCfgSrcFile") |
|
||||||
} |
|
||||||
if !isFile(*config.awsConfigFile) { |
|
||||||
return fmt.Errorf("aws config file not exist %v", *config.awsConfigFile) |
|
||||||
} |
|
||||||
} |
|
||||||
return nil |
|
||||||
} |
|
||||||
|
|
||||||
// kmsProvider provide the aws kms client
|
|
||||||
type kmsProvider interface { |
|
||||||
getKMSClient() (*kms.KMS, error) |
|
||||||
} |
|
||||||
|
|
||||||
// lazyKmsProvider provide the kms client with singleton lazy initialization with config get
|
|
||||||
// from awsConfigGetter for aws credential and regions loading.
|
|
||||||
type lazyKmsProvider struct { |
|
||||||
acGetter awsConfigGetter |
|
||||||
|
|
||||||
client *kms.KMS |
|
||||||
err error |
|
||||||
once sync.Once |
|
||||||
} |
|
||||||
|
|
||||||
// newLazyKmsProvider creates a kmsProvider with the given config
|
|
||||||
func newLazyKmsProvider(config kmsProviderConfig) (*lazyKmsProvider, error) { |
|
||||||
var acg awsConfigGetter |
|
||||||
switch config.awsCfgSrcType { |
|
||||||
case AwsCfgSrcFile: |
|
||||||
if stringIsSet(config.awsConfigFile) { |
|
||||||
acg = newFileACGetter(*config.awsConfigFile) |
|
||||||
} else { |
|
||||||
acg = newSharedAwsConfigGetter() |
|
||||||
} |
|
||||||
case AwsCfgSrcPrompt: |
|
||||||
acg = newPromptACGetter(defKmsPromptTimeout) |
|
||||||
case AwsCfgSrcShared: |
|
||||||
acg = newSharedAwsConfigGetter() |
|
||||||
default: |
|
||||||
return nil, errors.New("unknown aws config source type") |
|
||||||
} |
|
||||||
return &lazyKmsProvider{ |
|
||||||
acGetter: acg, |
|
||||||
}, nil |
|
||||||
} |
|
||||||
|
|
||||||
func (provider *lazyKmsProvider) getKMSClient() (*kms.KMS, error) { |
|
||||||
provider.once.Do(func() { |
|
||||||
cfg, err := provider.acGetter.getAwsConfig() |
|
||||||
if err != nil { |
|
||||||
provider.err = err |
|
||||||
return |
|
||||||
} |
|
||||||
provider.client, provider.err = kmsClientWithConfig(cfg) |
|
||||||
}) |
|
||||||
if provider.err != nil { |
|
||||||
return nil, provider.err |
|
||||||
} |
|
||||||
return provider.client, nil |
|
||||||
} |
|
||||||
|
|
||||||
// awsConfigGetter provides the aws config. Implemented by
|
|
||||||
// sharedACGetter - provide the nil to use shared AWS configuration
|
|
||||||
// fileACGetter - provide the aws config with a json file
|
|
||||||
// promptACGetter - provide the config field from prompt with time out
|
|
||||||
type awsConfigGetter interface { |
|
||||||
getAwsConfig() (*AwsConfig, error) |
|
||||||
String() string |
|
||||||
} |
|
||||||
|
|
||||||
// sharedACGetter returns nil for getAwsConfig to use shared aws configurations
|
|
||||||
type sharedACGetter struct{} |
|
||||||
|
|
||||||
func newSharedAwsConfigGetter() *sharedACGetter { |
|
||||||
return &sharedACGetter{} |
|
||||||
} |
|
||||||
|
|
||||||
func (getter *sharedACGetter) getAwsConfig() (*AwsConfig, error) { |
|
||||||
return nil, nil |
|
||||||
} |
|
||||||
|
|
||||||
func (getter *sharedACGetter) String() string { |
|
||||||
return "shared aws config" |
|
||||||
} |
|
||||||
|
|
||||||
// fileACGetter get aws config through a customized json file
|
|
||||||
type fileACGetter struct { |
|
||||||
file string |
|
||||||
} |
|
||||||
|
|
||||||
func newFileACGetter(file string) *fileACGetter { |
|
||||||
return &fileACGetter{file} |
|
||||||
} |
|
||||||
|
|
||||||
func (getter *fileACGetter) getAwsConfig() (*AwsConfig, error) { |
|
||||||
b, err := ioutil.ReadFile(getter.file) |
|
||||||
if err != nil { |
|
||||||
return nil, err |
|
||||||
} |
|
||||||
var cfg AwsConfig |
|
||||||
if err := json.Unmarshal(b, &cfg); err != nil { |
|
||||||
return nil, err |
|
||||||
} |
|
||||||
return &cfg, nil |
|
||||||
} |
|
||||||
|
|
||||||
func (getter *fileACGetter) String() string { |
|
||||||
return fmt.Sprintf("file %v", getter.file) |
|
||||||
} |
|
||||||
|
|
||||||
// promptACGetter provide a user interactive console for AWS config.
|
|
||||||
// Four fields are asked:
|
|
||||||
// 1. AccessKey 2. SecretKey 3. Region
|
|
||||||
// Each field is asked with a timeout mechanism.
|
|
||||||
type promptACGetter struct { |
|
||||||
timeout time.Duration |
|
||||||
} |
|
||||||
|
|
||||||
func newPromptACGetter(timeout time.Duration) *promptACGetter { |
|
||||||
return &promptACGetter{ |
|
||||||
timeout: timeout, |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func (getter *promptACGetter) getAwsConfig() (*AwsConfig, error) { |
|
||||||
console.println("Please provide AWS configurations for KMS encoded BLS keys:") |
|
||||||
accessKey, err := getter.prompt(" AccessKey:") |
|
||||||
if err != nil { |
|
||||||
return nil, fmt.Errorf("cannot get aws access key: %v", err) |
|
||||||
} |
|
||||||
secretKey, err := getter.prompt(" SecretKey:") |
|
||||||
if err != nil { |
|
||||||
return nil, fmt.Errorf("cannot get aws secret key: %v", err) |
|
||||||
} |
|
||||||
region, err := getter.prompt("Region:") |
|
||||||
if err != nil { |
|
||||||
return nil, fmt.Errorf("cannot get aws region: %v", err) |
|
||||||
} |
|
||||||
return &AwsConfig{ |
|
||||||
AccessKey: accessKey, |
|
||||||
SecretKey: secretKey, |
|
||||||
Region: region, |
|
||||||
Token: "", |
|
||||||
}, nil |
|
||||||
} |
|
||||||
|
|
||||||
// prompt prompt the user to input a string for a certain field with timeout.
|
|
||||||
func (getter *promptACGetter) prompt(hint string) (string, error) { |
|
||||||
var ( |
|
||||||
res string |
|
||||||
err error |
|
||||||
|
|
||||||
finished = make(chan struct{}) |
|
||||||
timedOut = time.After(getter.timeout) |
|
||||||
) |
|
||||||
|
|
||||||
go func() { |
|
||||||
res, err = getter.threadedPrompt(hint) |
|
||||||
close(finished) |
|
||||||
}() |
|
||||||
|
|
||||||
for { |
|
||||||
select { |
|
||||||
case <-finished: |
|
||||||
return res, err |
|
||||||
case <-timedOut: |
|
||||||
return "", errors.New("timed out") |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func (getter *promptACGetter) threadedPrompt(hint string) (string, error) { |
|
||||||
console.print(hint) |
|
||||||
return console.readPassword() |
|
||||||
} |
|
||||||
|
|
||||||
func (getter *promptACGetter) String() string { |
|
||||||
return "prompt" |
|
||||||
} |
|
||||||
|
|
||||||
func kmsClientWithConfig(config *AwsConfig) (*kms.KMS, error) { |
|
||||||
if config == nil { |
|
||||||
return getSharedKMSClient() |
|
||||||
} |
|
||||||
return getKMSClientFromConfig(*config) |
|
||||||
} |
|
||||||
|
|
||||||
func getSharedKMSClient() (*kms.KMS, error) { |
|
||||||
sess, err := session.NewSessionWithOptions(session.Options{ |
|
||||||
SharedConfigState: session.SharedConfigEnable, |
|
||||||
}) |
|
||||||
if err != nil { |
|
||||||
return nil, errors.Wrapf(err, "failed to create aws session") |
|
||||||
} |
|
||||||
return kms.New(sess), err |
|
||||||
} |
|
||||||
|
|
||||||
func getKMSClientFromConfig(config AwsConfig) (*kms.KMS, error) { |
|
||||||
sess, err := session.NewSession(config.toAws()) |
|
||||||
if err != nil { |
|
||||||
return nil, err |
|
||||||
} |
|
||||||
return kms.New(sess), nil |
|
||||||
} |
|
Loading…
Reference in new issue