The core protocol of WoopChain
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
woop/internal/blsgen/kms.go

235 lines
6.2 KiB

package blsgen
import (
"fmt"
"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"
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 awsOptProvider
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 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 err
}
}
return nil
}
func (kd *kmsDecrypter) makeACProvider() {
config := kd.config
switch config.awsCfgSrcType {
case AwsCfgSrcFile:
kd.provider = newFileAOProvider(*config.awsConfigFile)
case AwsCfgSrcPrompt:
kd.provider = newPromptAOProvider(defKmsPromptTimeout)
case AwsCfgSrcShared:
kd.provider = newSharedAOProvider()
}
}
func (kd *kmsDecrypter) getKMSClient() (*kms.KMS, error) {
kd.once.Do(func() {
opt, err := kd.provider.getAwsOpt()
if err != nil {
kd.err = err
return
}
kd.client, kd.err = kmsClientWithOpt(opt)
})
if kd.err != nil {
return nil, kd.err
}
return kd.client, nil
}
// awsOptProvider provides the aws session option. Implemented by
// sharedAOProvider - provide the nil to use shared AWS configuration
// fileAOProvider - provide the aws config with a json file
// promptAOProvider - provide the config field from prompt with time out
type awsOptProvider interface {
getAwsOpt() (session.Options, error)
}
// sharedAOProvider returns the shared aws config
type sharedAOProvider struct{}
func newSharedAOProvider() *sharedAOProvider {
return &sharedAOProvider{}
}
func (provider *sharedAOProvider) getAwsOpt() (session.Options, error) {
return session.Options{
SharedConfigState: session.SharedConfigEnable,
}, nil
}
// fileAOProvider get aws config through a customized json file
type fileAOProvider struct {
file string
}
func newFileAOProvider(file string) *fileAOProvider {
return &fileAOProvider{file}
}
func (provider *fileAOProvider) getAwsOpt() (session.Options, error) {
return session.Options{
SharedConfigFiles: []string{provider.file},
SharedConfigState: session.SharedConfigEnable,
}, nil
}
// promptAOProvider 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 promptAOProvider struct {
timeout time.Duration
}
func newPromptAOProvider(timeout time.Duration) *promptAOProvider {
return &promptAOProvider{
timeout: timeout,
}
}
func (provider *promptAOProvider) getAwsOpt() (session.Options, error) {
console.println("Please provide AWS configurations for KMS encoded BLS keys:")
accessKey, err := provider.prompt(" AccessKey:")
if err != nil {
return session.Options{}, fmt.Errorf("cannot get aws access key: %v", err)
}
secretKey, err := provider.prompt(" SecretKey:")
if err != nil {
return session.Options{}, fmt.Errorf("cannot get aws secret key: %v", err)
}
region, err := provider.prompt("Region:")
if err != nil {
return session.Options{}, fmt.Errorf("cannot get aws region: %v", err)
}
return session.Options{
Config: aws.Config{
Region: aws.String(region),
Credentials: credentials.NewStaticCredentials(accessKey, secretKey, ""),
},
SharedConfigState: session.SharedConfigEnable,
}, nil
}
// prompt prompt the user to input a string for a certain field with timeout.
func (provider *promptAOProvider) prompt(hint string) (string, error) {
var (
res string
err error
finished = make(chan struct{})
timedOut = time.After(provider.timeout)
)
cs := console
go func() {
res, err = provider.threadedPrompt(cs, hint)
close(finished)
}()
for {
select {
case <-finished:
return res, err
case <-timedOut:
return "", errors.New("timed out")
}
}
}
func (provider *promptAOProvider) threadedPrompt(cs consoleItf, hint string) (string, error) {
cs.print(hint)
return cs.readPassword()
}
func kmsClientWithOpt(opt session.Options) (*kms.KMS, error) {
sess, err := session.NewSessionWithOptions(opt)
if err != nil {
return nil, errors.Wrapf(err, "failed to create aws session")
}
return kms.New(sess), err
}