diff --git a/clients/config_client/config_client.go b/clients/config_client/config_client.go index 5b88d70b..4f12b23e 100644 --- a/clients/config_client/config_client.go +++ b/clients/config_client/config_client.go @@ -24,10 +24,13 @@ import ( "sync" "time" - "github.com/aliyun/alibaba-cloud-sdk-go/services/kms" + "github.com/alibabacloud-go/tea/tea" + dkms_api "github.com/aliyun/alibabacloud-dkms-gcs-go-sdk/openapi" "github.com/nacos-group/nacos-sdk-go/v2/clients/cache" "github.com/nacos-group/nacos-sdk-go/v2/clients/nacos_client" "github.com/nacos-group/nacos-sdk-go/v2/common/constant" + nacos_inner_encryption "github.com/nacos-group/nacos-sdk-go/v2/common/encryption" + "github.com/nacos-group/nacos-sdk-go/v2/common/filter" "github.com/nacos-group/nacos-sdk-go/v2/common/logger" "github.com/nacos-group/nacos-sdk-go/v2/common/monitor" "github.com/nacos-group/nacos-sdk-go/v2/common/nacos_error" @@ -49,7 +52,7 @@ type ConfigClient struct { ctx context.Context cancel context.CancelFunc nacos_client.INacosClient - kmsClient *kms.Client + kmsClient *nacos_inner_encryption.KmsClient localConfigs []vo.ConfigParam mutex sync.Mutex configProxy IConfigProxy @@ -66,6 +69,7 @@ type cacheData struct { group string content string contentType string + encryptedDataKey string tenant string cacheDataListener *cacheDataListener md5 string @@ -84,12 +88,18 @@ func (cacheData *cacheData) executeListener() { cacheData.cacheDataListener.lastMd5 = cacheData.md5 cacheData.configClient.cacheMap.Set(util.GetConfigCacheKey(cacheData.dataId, cacheData.group, cacheData.tenant), *cacheData) - decryptedContent, err := cacheData.configClient.decrypt(cacheData.dataId, cacheData.content) - if err != nil { - logger.Errorf("decrypt content fail ,dataId=%s,group=%s,tenant=%s,err:%+v ", cacheData.dataId, + param := &vo.ConfigParam{ + DataId: cacheData.dataId, + Content: cacheData.content, + EncryptedDataKey: cacheData.encryptedDataKey, + UsageType: vo.ResponseType, + } + if err := filter.GetDefaultConfigFilterChainManager().DoFilters(param); err != nil { + logger.Errorf("do filters failed ,dataId=%s,group=%s,tenant=%s,err:%+v ", cacheData.dataId, cacheData.group, cacheData.tenant, err) return } + decryptedContent := param.Content go cacheData.cacheDataListener.listener(cacheData.tenant, cacheData.group, cacheData.dataId, decryptedContent) } @@ -121,7 +131,15 @@ func NewConfigClient(nc nacos_client.INacosClient) (*ConfigClient, error) { } if clientConfig.OpenKMS { - kmsClient, err := kms.NewClientWithAccessKey(clientConfig.RegionId, clientConfig.AccessKey, clientConfig.SecretKey) + var kmsClient *nacos_inner_encryption.KmsClient + switch clientConfig.KMSVersion { + case constant.KMSv1, constant.DEFAULT_KMS_VERSION: + kmsClient, err = initKmsV1Client(clientConfig) + case constant.KMSv3: + kmsClient, err = initKmsV3Client(clientConfig) + default: + err = fmt.Errorf("init kms client failed. unknown kms version:%s\n", clientConfig.KMSVersion) + } if err != nil { return nil, err } @@ -144,48 +162,57 @@ func initLogger(clientConfig constant.ClientConfig) error { return logger.InitLogger(logger.BuildLoggerConfig(clientConfig)) } +func initKmsV1Client(clientConfig constant.ClientConfig) (*nacos_inner_encryption.KmsClient, error) { + return nacos_inner_encryption.InitDefaultKmsV1ClientWithAccessKey(clientConfig.RegionId, clientConfig.AccessKey, clientConfig.SecretKey) +} + +func initKmsV3Client(clientConfig constant.ClientConfig) (*nacos_inner_encryption.KmsClient, error) { + return nacos_inner_encryption.InitDefaultKmsV3ClientWithConfig(&dkms_api.Config{ + Protocol: tea.String("https"), + Endpoint: tea.String(clientConfig.KMSv3Config.Endpoint), + ClientKeyContent: tea.String(clientConfig.KMSv3Config.ClientKeyContent), + Password: tea.String(clientConfig.KMSv3Config.Password), + }, clientConfig.KMSv3Config.CaContent) +} + func (client *ConfigClient) GetConfig(param vo.ConfigParam) (content string, err error) { - content, err = client.getConfigInner(param) + content, err = client.getConfigInner(¶m) if err != nil { return "", err } - return client.decrypt(param.DataId, content) + param.UsageType = vo.ResponseType + if err = filter.GetDefaultConfigFilterChainManager().DoFilters(¶m); err != nil { + return "", err + } + content = param.Content + return content, nil } func (client *ConfigClient) decrypt(dataId, content string) (string, error) { + var plainContent string + var err error if client.kmsClient != nil && strings.HasPrefix(dataId, "cipher-") { - request := kms.CreateDecryptRequest() - request.Method = "POST" - request.Scheme = "https" - request.AcceptFormat = "json" - request.CiphertextBlob = content - response, err := client.kmsClient.Decrypt(request) + plainContent, err = client.kmsClient.Decrypt(content) if err != nil { return "", fmt.Errorf("kms decrypt failed: %v", err) } - content = response.Plaintext } - return content, nil + return plainContent, nil } -func (client *ConfigClient) encrypt(dataId, content string) (string, error) { +func (client *ConfigClient) encrypt(dataId, content, kmsKeyId string) (string, error) { + var cipherContent string + var err error if client.kmsClient != nil && strings.HasPrefix(dataId, "cipher-") { - request := kms.CreateEncryptRequest() - request.Method = "POST" - request.Scheme = "https" - request.AcceptFormat = "json" - request.KeyId = "alias/acs/mse" // use default key - request.Plaintext = content - response, err := client.kmsClient.Encrypt(request) + cipherContent, err = client.kmsClient.Encrypt(content, kmsKeyId) if err != nil { return "", fmt.Errorf("kms encrypt failed: %v", err) } - content = response.CiphertextBlob } - return content, nil + return cipherContent, nil } -func (client *ConfigClient) getConfigInner(param vo.ConfigParam) (content string, err error) { +func (client *ConfigClient) getConfigInner(param *vo.ConfigParam) (content string, err error) { if len(param.DataId) <= 0 { err = errors.New("[client.GetConfig] param.dataId can not be empty") return "", err @@ -220,6 +247,8 @@ func (client *ConfigClient) getConfigInner(param vo.ConfigParam) (content string logger.Warnf("read config from cache success, dataId=%s, group=%s, namespaceId=%s", param.DataId, param.Group, clientConfig.NamespaceId) return cacheContent, nil } + param.EncryptedDataKey = response.EncryptedDataKey + param.Content = response.Content return response.Content, nil } @@ -236,8 +265,10 @@ func (client *ConfigClient) PublishConfig(param vo.ConfigParam) (published bool, if len(param.Group) <= 0 { param.Group = constant.DEFAULT_GROUP } - if param.Content, err = client.encrypt(param.DataId, param.Content); err != nil { - return + + param.UsageType = vo.RequestType + if err = filter.GetDefaultConfigFilterChainManager().DoFilters(¶m); err != nil { + return false, err } clientConfig, _ := client.GetClientConfig() @@ -480,6 +511,7 @@ func (client *ConfigClient) refreshContentAndCheck(cacheData cacheData, notify b } cacheData.content = configQueryResponse.Content cacheData.contentType = configQueryResponse.ContentType + cacheData.encryptedDataKey = configQueryResponse.EncryptedDataKey if notify { logger.Infof("[config_rpc_client] [data-received] dataId=%s, group=%s, tenant=%s, md5=%s, content=%s, type=%s", cacheData.dataId, cacheData.group, cacheData.tenant, cacheData.md5, diff --git a/common/constant/client_config_options.go b/common/constant/client_config_options.go index e379a58e..75b2cbf5 100644 --- a/common/constant/client_config_options.go +++ b/common/constant/client_config_options.go @@ -130,6 +130,19 @@ func WithOpenKMS(openKMS bool) ClientOption { } } +// WithOpenKMS ... +func WithKMSVersion(kmsVersion KMSVersion) ClientOption { + return func(config *ClientConfig) { + config.KMSVersion = kmsVersion + } +} + +func WithKMSv3Config(kmsv3Config *KMSv3Config) ClientOption { + return func(config *ClientConfig) { + config.KMSv3Config = kmsv3Config + } +} + // WithCacheDir ... func WithCacheDir(cacheDir string) ClientOption { return func(config *ClientConfig) { diff --git a/common/constant/config.go b/common/constant/config.go index 940ae2d9..478c4573 100644 --- a/common/constant/config.go +++ b/common/constant/config.go @@ -37,7 +37,9 @@ type ClientConfig struct { RegionId string // the regionId for kms AccessKey string // the AccessKey for kms SecretKey string // the SecretKey for kms - OpenKMS bool // it's to open kms,default is false. https://help.aliyun.com/product/28933.html + OpenKMS bool // it's to open kms, default is false. https://help.aliyun.com/product/28933.html + KMSVersion KMSVersion // kms client version. https://help.aliyun.com/document_detail/380927.html + KMSv3Config *KMSv3Config //KMSv3 configuration. https://help.aliyun.com/document_detail/601596.html CacheDir string // the directory for persist nacos service info,default value is current path DisableUseSnapShot bool // It's a switch, default is false, means that when get remote config fail, use local cache file instead UpdateThreadNum int // the number of goroutine for update nacos service info,default value is 20 @@ -98,3 +100,10 @@ type TLSConfig struct { KeyFile string // server use when verifying client certificates ServerNameOverride string // serverNameOverride is for testing only } + +type KMSv3Config struct { + ClientKeyContent string + Password string + Endpoint string + CaContent string +} diff --git a/common/constant/const.go b/common/constant/const.go index 53d11bc8..13478e83 100644 --- a/common/constant/const.go +++ b/common/constant/const.go @@ -18,6 +18,14 @@ package constant import "time" +type KMSVersion string + +const ( + KMSv1 KMSVersion = "KMSv1" + KMSv3 KMSVersion = "KMSv3" + DEFAULT_KMS_VERSION KMSVersion = "" //to fit original version + UNKNOWN_KMS_VERSION KMSVersion = "UNKNOWN_KMS_VERSION" +) const ( KEY_USERNAME = "username" KEY_PASSWORD = "password" @@ -99,4 +107,5 @@ const ( GRPC = "grpc" FAILOVER_FILE_SUFFIX = "_failover" RpcPortOffset = 1000 + MSE_KMSv1_DEFAULT_KEY_ID = "alias/acs/mse" ) diff --git a/common/encoding/encryption.go b/common/encoding/encryption.go new file mode 100644 index 00000000..38055bd0 --- /dev/null +++ b/common/encoding/encryption.go @@ -0,0 +1,72 @@ +/* + * Copyright 1999-2020 Alibaba Group Holding Ltd. + * + * 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 encoding + +import ( + "encoding/base64" + "unicode/utf8" +) + +func DecodeString2Utf8Bytes(data string) []byte { + resBytes := make([]byte, 0, 8) + if len(data) == 0 { + return resBytes + } + bytesLen := 0 + runes := []rune(data) + for _, r := range runes { + bytesLen += utf8.RuneLen(r) + } + resBytes = make([]byte, bytesLen) + pos := 0 + for _, r := range runes { + pos += utf8.EncodeRune(resBytes[pos:], r) + } + return resBytes +} + +func EncodeUtf8Bytes2String(bytes []byte) string { + if len(bytes) == 0 { + return "" + } + var startPos, endPos int + resRunes := make([]rune, 0, 8) + for endPos <= len(bytes) { + if utf8.FullRune(bytes[startPos:endPos]) { + decodedRune, _ := utf8.DecodeRune(bytes[startPos:endPos]) + resRunes = append(resRunes, decodedRune) + startPos = endPos + } + endPos++ + } + return string(resRunes) +} + +func DecodeBase64(bytes []byte) ([]byte, error) { + dst := make([]byte, base64.StdEncoding.DecodedLen(len(bytes))) + n, err := base64.StdEncoding.Decode(dst, bytes) + if err != nil { + return nil, err + } + return dst[:n], nil +} + +func EncodeBase64(bytes []byte) ([]byte, error) { + dst := make([]byte, base64.StdEncoding.EncodedLen(len(bytes))) + base64.StdEncoding.Encode(dst, bytes) + return dst[:], nil +} diff --git a/common/encryption/aes_ecb_pkcs5padding.go b/common/encryption/aes_ecb_pkcs5padding.go new file mode 100644 index 00000000..3e65f782 --- /dev/null +++ b/common/encryption/aes_ecb_pkcs5padding.go @@ -0,0 +1,88 @@ +/* + * Copyright 1999-2020 Alibaba Group Holding Ltd. + * + * 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 encryption + +import ( + "bytes" + "crypto/aes" + "crypto/cipher" + "fmt" +) + +func AesEcbPkcs5PaddingEncrypt(plainContent, key []byte) (retBytes []byte, err error) { + if len(plainContent) == 0 { + return nil, nil + } + aesCipherBlock, err := aes.NewCipher(key) + if err != nil { + return nil, err + } + pkcs5PaddingBytes := PKCS5Padding(plainContent, aesCipherBlock.BlockSize()) + return BlockEncrypt(pkcs5PaddingBytes, aesCipherBlock) +} + +func AesEcbPkcs5PaddingDecrypt(cipherContent, key []byte) (retBytes []byte, err error) { + if len(cipherContent) == 0 { + return nil, nil + } + aesCipherBlock, err := aes.NewCipher(key) + if err != nil { + return nil, err + } + decryptBytes, err := BlockDecrypt(cipherContent, aesCipherBlock) + if err != nil { + return nil, err + } + retBytes = PKCS5UnPadding(decryptBytes) + return retBytes, nil +} + +func PKCS5Padding(ciphertext []byte, blockSize int) []byte { + padding := blockSize - len(ciphertext)%blockSize + padtext := bytes.Repeat([]byte{byte(padding)}, padding) + return append(ciphertext, padtext...) +} + +func PKCS5UnPadding(origData []byte) []byte { + length := len(origData) + unpadding := int(origData[length-1]) + return origData[:(length - unpadding)] +} + +func BlockEncrypt(src []byte, b cipher.Block) (dst []byte, err error) { + if len(src)%b.BlockSize() != 0 { + return nil, fmt.Errorf("input not full blocks") + } + buf := make([]byte, b.BlockSize()) + for i := 0; i < len(src); i += b.BlockSize() { + b.Encrypt(buf, src[i:i+b.BlockSize()]) + dst = append(dst, buf...) + } + return +} + +func BlockDecrypt(src []byte, b cipher.Block) (dst []byte, err error) { + if len(src)%b.BlockSize() != 0 { + return nil, fmt.Errorf("input not full blocks") + } + buf := make([]byte, b.BlockSize()) + for i := 0; i < len(src); i += b.BlockSize() { + b.Decrypt(buf, src[i:i+b.BlockSize()]) + dst = append(dst, buf...) + } + return +} diff --git a/common/encryption/const.go b/common/encryption/const.go new file mode 100644 index 00000000..e3a769bc --- /dev/null +++ b/common/encryption/const.go @@ -0,0 +1,65 @@ +/* + * Copyright 1999-2020 Alibaba Group Holding Ltd. + * + * 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 encryption + +import "fmt" + +const ( + CipherPrefix = "cipher-" + + KmsAes128AlgorithmName = "cipher-kms-aes-128" + KmsAes256AlgorithmName = "cipher-kms-aes-256" + KmsAlgorithmName = "cipher" + + kmsAes128KeySpec = "AES_128" + kmsAes256KeySpec = "AES_256" + + kmsScheme = "https" + kmsAcceptFormat = "JSON" + + kmsCipherAlgorithm = "AES/ECB/PKCS5Padding" + + maskUnit8Width = 8 + maskUnit32Width = 32 +) + +var ( + DataIdParamCheckError = fmt.Errorf("dataId prefix should start with: %s", CipherPrefix) + ContentParamCheckError = fmt.Errorf("content need to encrypt is nil") + KeyIdParamCheckError = fmt.Errorf("keyId is nil, need to be set") +) + +var ( + PluginNotFoundError = fmt.Errorf("cannot find encryption plugin by dataId prefix") +) + +var ( + EmptyEncryptedDataKeyError = fmt.Errorf("empty encrypted data key error") + EmptyPlainDataKeyError = fmt.Errorf("empty plain data key error") + EmptyContentError = fmt.Errorf("encrypt empty content error") +) + +var ( + EmptyRegionKmsV1ClientInitError = fmt.Errorf("init kmsV1 client failed with empty region") + EmptyAkKmsV1ClientInitError = fmt.Errorf("init kmsV1 client failed with empty ak") + EmptySkKmsV1ClientInitError = fmt.Errorf("init kmsV1 client failed with empty sk") + + EmptyEndpointKmsV3ClientInitError = fmt.Errorf("init kmsV3 client failed with empty endpoint") + EmptyPasswordKmsV3ClientInitError = fmt.Errorf("init kmsV3 client failed with empty password") + EmptyClientKeyContentKmsV3ClientInitError = fmt.Errorf("init kmsV3 client failed with empty client key content") + EmptyCaVerifyKmsV3ClientInitError = fmt.Errorf("init kmsV3 client failed with empty ca verify") +) diff --git a/common/encryption/handler.go b/common/encryption/handler.go new file mode 100644 index 00000000..e2b8742e --- /dev/null +++ b/common/encryption/handler.go @@ -0,0 +1,173 @@ +/* + * Copyright 1999-2020 Alibaba Group Holding Ltd. + * + * 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 encryption + +import ( + "fmt" + "github.com/nacos-group/nacos-sdk-go/v2/common/logger" + "strings" + "sync" +) + +var ( + initDefaultHandlerOnce = &sync.Once{} + defaultHandler *DefaultHandler +) + +type HandlerParam struct { + DataId string `json:"dataId"` //required + Content string `json:"content"` //required + EncryptedDataKey string `json:"encryptedDataKey"` + PlainDataKey string `json:"plainDataKey"` + KeyId string `json:"keyId"` +} + +type Plugin interface { + Encrypt(*HandlerParam) error + Decrypt(*HandlerParam) error + AlgorithmName() string + GenerateSecretKey(*HandlerParam) (string, error) + EncryptSecretKey(*HandlerParam) (string, error) + DecryptSecretKey(*HandlerParam) (string, error) +} + +type Handler interface { + EncryptionHandler(*HandlerParam) error + DecryptionHandler(*HandlerParam) error + RegisterPlugin(Plugin) error +} + +func GetDefaultHandler() Handler { + if defaultHandler == nil { + initDefaultHandler() + } + return defaultHandler +} + +func initDefaultHandler() { + initDefaultHandlerOnce.Do(func() { + defaultHandler = &DefaultHandler{ + encryptionPlugins: make(map[string]Plugin, 2), + } + logger.Info("successfully create encryption defaultHandler") + }) +} + +type DefaultHandler struct { + encryptionPlugins map[string]Plugin +} + +func (d *DefaultHandler) EncryptionHandler(param *HandlerParam) error { + if err := d.encryptionParamCheck(*param); err != nil { + if err == DataIdParamCheckError || err == ContentParamCheckError { + return nil + } + return err + } + plugin, err := d.getPluginByDataIdPrefix(param.DataId) + if err != nil { + return err + } + plainSecretKey, err := plugin.GenerateSecretKey(param) + if err != nil { + return err + } + param.PlainDataKey = plainSecretKey + return plugin.Encrypt(param) +} + +func (d *DefaultHandler) DecryptionHandler(param *HandlerParam) error { + if err := d.decryptionParamCheck(*param); err != nil { + if err == DataIdParamCheckError || err == ContentParamCheckError { + return nil + } + return err + } + plugin, err := d.getPluginByDataIdPrefix(param.DataId) + if err != nil { + return err + } + plainSecretkey, err := plugin.DecryptSecretKey(param) + if err != nil { + return err + } + param.PlainDataKey = plainSecretkey + return plugin.Decrypt(param) +} + +func (d *DefaultHandler) getPluginByDataIdPrefix(dataId string) (Plugin, error) { + var ( + matchedCount int + matchedPlugin Plugin + ) + for k, v := range d.encryptionPlugins { + if strings.Contains(dataId, k) { + if len(k) > matchedCount { + matchedCount = len(k) + matchedPlugin = v + } + } + } + if matchedPlugin == nil { + return matchedPlugin, PluginNotFoundError + } + return matchedPlugin, nil +} + +func (d *DefaultHandler) RegisterPlugin(plugin Plugin) error { + if _, v := d.encryptionPlugins[plugin.AlgorithmName()]; v { + logger.Warnf("encryption algorithm [%s] has already registered to defaultHandler, will be update", plugin.AlgorithmName()) + } else { + logger.Infof("register encryption algorithm [%s] to defaultHandler", plugin.AlgorithmName()) + } + d.encryptionPlugins[plugin.AlgorithmName()] = plugin + return nil +} + +func (d *DefaultHandler) encryptionParamCheck(param HandlerParam) error { + if err := d.dataIdParamCheck(param.DataId); err != nil { + return DataIdParamCheckError + } + if err := d.contentParamCheck(param.Content); err != nil { + return ContentParamCheckError + } + return nil +} + +func (d *DefaultHandler) decryptionParamCheck(param HandlerParam) error { + if err := d.dataIdParamCheck(param.DataId); err != nil { + return DataIdParamCheckError + } + if err := d.contentParamCheck(param.Content); err != nil { + return ContentParamCheckError + } + return nil +} + +func (d *DefaultHandler) dataIdParamCheck(dataId string) error { + if !strings.Contains(dataId, CipherPrefix) { + return fmt.Errorf("dataId prefix should start with: %s", CipherPrefix) + } + return nil +} + +func (d *DefaultHandler) contentParamCheck(content string) error { + if len(content) == 0 { + return fmt.Errorf("content need to encrypt is nil") + } + return nil +} diff --git a/common/encryption/kms_client.go b/common/encryption/kms_client.go new file mode 100644 index 00000000..5fcb1d48 --- /dev/null +++ b/common/encryption/kms_client.go @@ -0,0 +1,193 @@ +/* + * Copyright 1999-2020 Alibaba Group Holding Ltd. + * + * 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 encryption + +import ( + "fmt" + "github.com/aliyun/alibaba-cloud-sdk-go/services/kms" + dkms_api "github.com/aliyun/alibabacloud-dkms-gcs-go-sdk/openapi" + dkms_transfer "github.com/aliyun/alibabacloud-dkms-transfer-go-sdk/sdk" + "github.com/nacos-group/nacos-sdk-go/v2/common/constant" + "github.com/nacos-group/nacos-sdk-go/v2/common/logger" + "github.com/pkg/errors" + "net/http" + "strings" + "sync" +) + +var ( + initKmsClientOnce = &sync.Once{} + kmsClient *KmsClient +) + +type KmsClient struct { + *dkms_transfer.KmsTransferClient + kmsVersion constant.KMSVersion +} + +func InitDefaultKmsV1ClientWithAccessKey(regionId, ak, sk string) (*KmsClient, error) { + var rErr error + if GetDefaultKmsClient() != nil { + return GetDefaultKmsClient(), rErr + } + if rErr = checkKmsV1InitParam(regionId, ak, sk); rErr != nil { + return nil, rErr + } + initKmsClientOnce.Do(func() { + client, err := NewKmsV1ClientWithAccessKey(regionId, ak, sk) + if err != nil { + rErr = errors.Wrap(err, "init kms v1 client with ak/sk failed") + } else { + client.SetKmsVersion(constant.KMSv1) + kmsClient = client + } + }) + return GetDefaultKmsClient(), rErr +} + +func checkKmsV1InitParam(regionId, ak, sk string) error { + if len(regionId) == 0 { + return EmptyRegionKmsV1ClientInitError + } + if len(ak) == 0 { + return EmptyAkKmsV1ClientInitError + } + if len(sk) == 0 { + return EmptySkKmsV1ClientInitError + } + return nil +} + +func InitDefaultKmsV3ClientWithConfig(config *dkms_api.Config, caVerify string) (*KmsClient, error) { + var rErr error + if GetDefaultKmsClient() != nil { + return GetDefaultKmsClient(), rErr + } + if rErr = checkKmsV3InitParam(config, caVerify); rErr != nil { + return nil, rErr + } + initKmsClientOnce.Do(func() { + client, err := NewKmsV3ClientWithConfig(config) + if err != nil { + rErr = errors.Wrap(err, "init kms v3 client with config failed") + } else { + if len(strings.TrimSpace(caVerify)) != 0 { + logger.Infof("set kms client Ca with content: %s\n", caVerify[:len(caVerify)/maskUnit32Width]) + client.SetVerify(caVerify) + } + client.SetKmsVersion(constant.KMSv3) + kmsClient = client + } + }) + return GetDefaultKmsClient(), rErr +} + +func checkKmsV3InitParam(config *dkms_api.Config, caVerify string) error { + if len(*config.Endpoint) == 0 { + return EmptyEndpointKmsV3ClientInitError + } + if len(*config.Password) == 0 { + return EmptyPasswordKmsV3ClientInitError + } + if len(*config.ClientKeyContent) == 0 { + return EmptyClientKeyContentKmsV3ClientInitError + } + if len(caVerify) == 0 { + return EmptyCaVerifyKmsV3ClientInitError + } + return nil +} + +func GetDefaultKmsClient() *KmsClient { + return kmsClient +} + +func NewKmsV1ClientWithAccessKey(regionId, ak, sk string) (*KmsClient, error) { + logger.Infof("init kms client with region:[%s], ak:[%s]xxx, sk:[%s]xxx\n", + regionId, ak[:len(ak)/maskUnit8Width], sk[:len(sk)/maskUnit8Width]) + return newKmsClient(regionId, ak, sk, nil) +} + +func NewKmsV3ClientWithConfig(config *dkms_api.Config) (*KmsClient, error) { + logger.Infof("init kms client with endpoint:[%s], clientKeyContent:[%s], password:[%s]\n", + config.Endpoint, (*config.ClientKeyContent)[:len(*config.ClientKeyContent)/maskUnit8Width], + (*config.Password)[:len(*config.Password)/maskUnit8Width]) + return newKmsClient("", "", "", config) +} + +func newKmsClient(regionId, ak, sk string, config *dkms_api.Config) (*KmsClient, error) { + client, err := dkms_transfer.NewClientWithAccessKey(regionId, ak, sk, config) + if err != nil { + return nil, err + } + return &KmsClient{ + KmsTransferClient: client, + }, nil +} + +func (kmsClient *KmsClient) GetKmsVersion() constant.KMSVersion { + return kmsClient.kmsVersion +} + +func (kmsClient *KmsClient) SetKmsVersion(kmsVersion constant.KMSVersion) { + logger.Info("successfully set kms client version to " + kmsVersion) + kmsClient.kmsVersion = kmsVersion +} + +func (kmsClient *KmsClient) GenerateDataKey(keyId, keySpec string) (string, string, error) { + generateDataKeyRequest := kms.CreateGenerateDataKeyRequest() + generateDataKeyRequest.Scheme = kmsScheme + generateDataKeyRequest.AcceptFormat = kmsAcceptFormat + generateDataKeyRequest.KeyId = keyId + generateDataKeyRequest.KeySpec = keySpec + generateDataKeyResponse, err := kmsClient.KmsTransferClient.GenerateDataKey(generateDataKeyRequest) + if err != nil { + return "", "", err + } + return generateDataKeyResponse.Plaintext, generateDataKeyResponse.CiphertextBlob, nil +} + +func (kmsClient *KmsClient) Decrypt(cipherContent string) (string, error) { + request := kms.CreateDecryptRequest() + request.Method = http.MethodPost + request.Scheme = kmsScheme + request.AcceptFormat = kmsAcceptFormat + request.CiphertextBlob = cipherContent + response, err := kmsClient.KmsTransferClient.Decrypt(request) + if err != nil { + return "", fmt.Errorf("kms decrypt failed: %v", err) + } + return response.Plaintext, nil +} + +func (kmsClient *KmsClient) Encrypt(content, keyId string) (string, error) { + request := kms.CreateEncryptRequest() + request.Method = http.MethodPost + request.Scheme = kmsScheme + request.AcceptFormat = kmsAcceptFormat + request.Plaintext = content + request.KeyId = keyId + response, err := kmsClient.KmsTransferClient.Encrypt(request) + if err != nil { + return "", fmt.Errorf("kms encrypt failed: %v", err) + } + return response.CiphertextBlob, nil +} + +func GetDefaultKMSv1KeyId() string { + return constant.MSE_KMSv1_DEFAULT_KEY_ID +} diff --git a/common/encryption/kms_plugins.go b/common/encryption/kms_plugins.go new file mode 100644 index 00000000..1a647b16 --- /dev/null +++ b/common/encryption/kms_plugins.go @@ -0,0 +1,273 @@ +/* + * Copyright 1999-2020 Alibaba Group Holding Ltd. + * + * 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 encryption + +import ( + inner_encoding "github.com/nacos-group/nacos-sdk-go/v2/common/encoding" + "github.com/nacos-group/nacos-sdk-go/v2/common/logger" + "strings" +) + +func init() { + if err := GetDefaultHandler().RegisterPlugin(&KmsAes128Plugin{}); err != nil { + logger.Errorf("failed to register encryption plugin[%s] to defaultHandler", KmsAes128AlgorithmName) + } else { + logger.Infof("successfully register encryption plugin[%s] to defaultHandler", KmsAes128AlgorithmName) + } + if err := GetDefaultHandler().RegisterPlugin(&KmsAes256Plugin{}); err != nil { + logger.Errorf("failed to register encryption plugin[%s] to defaultHandler", KmsAes256AlgorithmName) + } else { + logger.Infof("successfully register encryption plugin[%s] to defaultHandler", KmsAes256AlgorithmName) + } + if err := GetDefaultHandler().RegisterPlugin(&KmsBasePlugin{}); err != nil { + logger.Errorf("failed to register encryption plugin[%s] to defaultHandler", KmsAlgorithmName) + } else { + logger.Infof("successfully register encryption plugin[%s] to defaultHandler", KmsAlgorithmName) + + } +} + +type kmsPlugin struct { +} + +func (k *kmsPlugin) Encrypt(param *HandlerParam) error { + if len(param.Content) == 0 { + return nil + } + if len(param.PlainDataKey) == 0 { + return EmptyPlainDataKeyError + } + secretKeyBase64Decoded, err := inner_encoding.DecodeBase64(inner_encoding.DecodeString2Utf8Bytes(param.PlainDataKey)) + if err != nil { + return err + } + contentUtf8Bytes := inner_encoding.DecodeString2Utf8Bytes(param.Content) + encryptedContent, err := AesEcbPkcs5PaddingEncrypt(contentUtf8Bytes, secretKeyBase64Decoded) + if err != nil { + return err + } + contentBase64Encoded, err := inner_encoding.EncodeBase64(encryptedContent) + if err != nil { + return err + } + param.Content = inner_encoding.EncodeUtf8Bytes2String(contentBase64Encoded) + return nil +} + +func (k *kmsPlugin) Decrypt(param *HandlerParam) error { + if len(param.Content) == 0 { + return nil + } + if len(param.PlainDataKey) == 0 { + return EmptyPlainDataKeyError + } + secretKeyBase64Decoded, err := inner_encoding.DecodeBase64(inner_encoding.DecodeString2Utf8Bytes(param.PlainDataKey)) + if err != nil { + return err + } + contentBase64Decoded, err := inner_encoding.DecodeBase64(inner_encoding.DecodeString2Utf8Bytes(param.Content)) + if err != nil { + return err + } + decryptedContent, err := AesEcbPkcs5PaddingDecrypt(contentBase64Decoded, secretKeyBase64Decoded) + if err != nil { + return err + } + param.Content = inner_encoding.EncodeUtf8Bytes2String(decryptedContent) + return nil +} + +func (k *kmsPlugin) AlgorithmName() string { + return "" +} + +func (k *kmsPlugin) GenerateSecretKey(param *HandlerParam) (string, error) { + return "", nil +} + +func (k *kmsPlugin) EncryptSecretKey(param *HandlerParam) (string, error) { + if err := keyIdParamCheck(param.KeyId); err != nil { + return "", err + } + if len(param.PlainDataKey) == 0 { + return "", nil + } + encryptedDataKey, err := GetDefaultKmsClient().Encrypt(param.PlainDataKey, param.KeyId) + if err != nil { + return "", err + } + if len(encryptedDataKey) == 0 { + return "", EmptyEncryptedDataKeyError + } + param.EncryptedDataKey = encryptedDataKey + return encryptedDataKey, nil +} + +func (k *kmsPlugin) DecryptSecretKey(param *HandlerParam) (string, error) { + if len(param.EncryptedDataKey) == 0 { + return "", nil + } + plainDataKey, err := GetDefaultKmsClient().Decrypt(param.EncryptedDataKey) + if err != nil { + return "", err + } + if len(plainDataKey) == 0 { + return "", EmptyPlainDataKeyError + } + param.PlainDataKey = plainDataKey + return plainDataKey, nil +} + +type KmsAes128Plugin struct { + kmsPlugin +} + +func (k *KmsAes128Plugin) Encrypt(param *HandlerParam) error { + return k.kmsPlugin.Encrypt(param) +} + +func (k *KmsAes128Plugin) Decrypt(param *HandlerParam) error { + return k.kmsPlugin.Decrypt(param) +} + +func (k *KmsAes128Plugin) AlgorithmName() string { + return KmsAes128AlgorithmName +} + +func (k *KmsAes128Plugin) GenerateSecretKey(param *HandlerParam) (string, error) { + if err := keyIdParamCheck(param.KeyId); err != nil { + return "", err + } + plainSecretKey, encryptedSecretKey, err := GetDefaultKmsClient().GenerateDataKey(param.KeyId, kmsAes128KeySpec) + if err != nil { + return "", err + } + param.PlainDataKey = plainSecretKey + param.EncryptedDataKey = encryptedSecretKey + if len(param.PlainDataKey) == 0 { + return "", EmptyPlainDataKeyError + } + if len(param.EncryptedDataKey) == 0 { + return "", EmptyEncryptedDataKeyError + } + return plainSecretKey, nil +} + +func (k *KmsAes128Plugin) EncryptSecretKey(param *HandlerParam) (string, error) { + return k.kmsPlugin.EncryptSecretKey(param) +} + +func (k *KmsAes128Plugin) DecryptSecretKey(param *HandlerParam) (string, error) { + return k.kmsPlugin.DecryptSecretKey(param) +} + +type KmsAes256Plugin struct { + kmsPlugin +} + +func (k *KmsAes256Plugin) Encrypt(param *HandlerParam) error { + return k.kmsPlugin.Encrypt(param) + +} + +func (k *KmsAes256Plugin) Decrypt(param *HandlerParam) error { + return k.kmsPlugin.Decrypt(param) +} + +func (k *KmsAes256Plugin) AlgorithmName() string { + return KmsAes256AlgorithmName +} + +func (k *KmsAes256Plugin) GenerateSecretKey(param *HandlerParam) (string, error) { + if err := keyIdParamCheck(param.KeyId); err != nil { + return "", err + } + plainSecretKey, encryptedSecretKey, err := GetDefaultKmsClient().GenerateDataKey(param.KeyId, kmsAes256KeySpec) + if err != nil { + return "", err + } + param.PlainDataKey = plainSecretKey + param.EncryptedDataKey = encryptedSecretKey + if len(param.PlainDataKey) == 0 { + return "", EmptyPlainDataKeyError + } + if len(param.EncryptedDataKey) == 0 { + return "", EmptyEncryptedDataKeyError + } + return plainSecretKey, nil +} + +func (k *KmsAes256Plugin) EncryptSecretKey(param *HandlerParam) (string, error) { + return k.kmsPlugin.EncryptSecretKey(param) +} + +func (k *KmsAes256Plugin) DecryptSecretKey(param *HandlerParam) (string, error) { + return k.kmsPlugin.DecryptSecretKey(param) +} + +type KmsBasePlugin struct { +} + +func (k *KmsBasePlugin) Encrypt(param *HandlerParam) error { + if err := keyIdParamCheck(param.KeyId); err != nil { + return err + } + if len(param.Content) == 0 { + return nil + } + encryptedContent, err := GetDefaultKmsClient().Encrypt(param.Content, param.KeyId) + if err != nil { + return err + } + param.Content = encryptedContent + return nil +} + +func (k *KmsBasePlugin) Decrypt(param *HandlerParam) error { + if len(param.Content) == 0 { + return nil + } + plainContent, err := GetDefaultKmsClient().Decrypt(param.Content) + if err != nil { + return err + } + param.Content = plainContent + return nil +} + +func (k *KmsBasePlugin) AlgorithmName() string { + return KmsAlgorithmName +} + +func (k *KmsBasePlugin) GenerateSecretKey(param *HandlerParam) (string, error) { + return "", nil +} + +func (k *KmsBasePlugin) EncryptSecretKey(param *HandlerParam) (string, error) { + return "", nil +} + +func (k *KmsBasePlugin) DecryptSecretKey(param *HandlerParam) (string, error) { + return "", nil +} + +func keyIdParamCheck(keyId string) error { + if len(strings.TrimSpace(keyId)) == 0 { + return KeyIdParamCheckError + } + return nil +} diff --git a/common/filter/config_encryption_filter.go b/common/filter/config_encryption_filter.go new file mode 100644 index 00000000..11cf5f4f --- /dev/null +++ b/common/filter/config_encryption_filter.go @@ -0,0 +1,93 @@ +/* + * Copyright 1999-2020 Alibaba Group Holding Ltd. + * + * 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 filter + +import ( + "fmt" + "github.com/nacos-group/nacos-sdk-go/v2/common/constant" + nacos_inner_encryption "github.com/nacos-group/nacos-sdk-go/v2/common/encryption" + "github.com/nacos-group/nacos-sdk-go/v2/common/logger" + "github.com/nacos-group/nacos-sdk-go/v2/vo" + "strings" + "sync" +) + +const ( + defaultConfigEncryptionFilterName = "defaultConfigEncryptionFilter" +) + +var ( + initDefaultConfigEncryptionFilterOnce = &sync.Once{} + defaultConfigEncryptionFilter IConfigFilter +) + +type DefaultConfigEncryptionFilter struct { +} + +func GetDefaultConfigEncryptionFilter() IConfigFilter { + if defaultConfigEncryptionFilter == nil { + initDefaultConfigEncryptionFilterOnce.Do(func() { + defaultConfigEncryptionFilter = &DefaultConfigEncryptionFilter{} + logger.Infof("successfully create ConfigFilter[%s]", defaultConfigEncryptionFilter.GetFilterName()) + }) + } + return defaultConfigEncryptionFilter +} + +func (d *DefaultConfigEncryptionFilter) DoFilter(param *vo.ConfigParam) error { + if !strings.HasPrefix(param.DataId, nacos_inner_encryption.CipherPrefix) { + return nil + } + if nacos_inner_encryption.GetDefaultKmsClient() == nil { + return fmt.Errorf("kms client hasn't inited, can't publish config dataId start with: %s", nacos_inner_encryption.CipherPrefix) + } + if param.UsageType == vo.RequestType { + encryptionParam := &nacos_inner_encryption.HandlerParam{ + DataId: param.DataId, + Content: param.Content, + KeyId: param.KmsKeyId, + } + if len(encryptionParam.KeyId) == 0 && nacos_inner_encryption.GetDefaultKmsClient().GetKmsVersion() == constant.KMSv1 { + encryptionParam.KeyId = nacos_inner_encryption.GetDefaultKMSv1KeyId() + } + if err := nacos_inner_encryption.GetDefaultHandler().EncryptionHandler(encryptionParam); err != nil { + return err + } + param.Content = encryptionParam.Content + param.EncryptedDataKey = encryptionParam.EncryptedDataKey + + } else if param.UsageType == vo.ResponseType { + decryptionParam := &nacos_inner_encryption.HandlerParam{ + DataId: param.DataId, + Content: param.Content, + EncryptedDataKey: param.EncryptedDataKey, + } + if err := nacos_inner_encryption.GetDefaultHandler().DecryptionHandler(decryptionParam); err != nil { + return err + } + param.Content = decryptionParam.Content + } + return nil +} + +func (d *DefaultConfigEncryptionFilter) GetOrder() int { + return 0 +} + +func (d *DefaultConfigEncryptionFilter) GetFilterName() string { + return defaultConfigEncryptionFilterName +} diff --git a/common/filter/config_filter.go b/common/filter/config_filter.go new file mode 100644 index 00000000..6ff72e9e --- /dev/null +++ b/common/filter/config_filter.go @@ -0,0 +1,128 @@ +/* + * Copyright 1999-2020 Alibaba Group Holding Ltd. + * + * 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 filter + +import ( + "fmt" + "github.com/nacos-group/nacos-sdk-go/v2/common/logger" + "github.com/nacos-group/nacos-sdk-go/v2/vo" + "sync" +) + +var ( + initConfigFilterChainManagerOnce = &sync.Once{} + defaultConfigFilterChainManagerInstance IConfigFilterChain +) + +type IConfigFilterChain interface { + AddFilter(IConfigFilter) error + GetFilters() []IConfigFilter + DoFilters(*vo.ConfigParam) error + DoFilterByName(*vo.ConfigParam, string) error +} + +type IConfigFilter interface { + DoFilter(*vo.ConfigParam) error + GetOrder() int + GetFilterName() string +} + +func init() { + err := RegisterConfigFilter(GetDefaultConfigFilterChainManager(), GetDefaultConfigEncryptionFilter()) + if err != nil { + logger.Errorf("failed to register configFilter[%s] to DefaultConfigFilterChainManager", + GetDefaultConfigEncryptionFilter().GetFilterName()) + return + } else { + logger.Infof("successfully register ConfigFilter[%s] to DefaultConfigFilterChainManager", GetDefaultConfigEncryptionFilter().GetFilterName()) + } +} + +func GetDefaultConfigFilterChainManager() IConfigFilterChain { + if defaultConfigFilterChainManagerInstance == nil { + initConfigFilterChainManagerOnce.Do(func() { + defaultConfigFilterChainManagerInstance = newDefaultConfigFilterChainManager() + logger.Info("successfully create DefaultConfigFilterChainManager") + }) + } + return defaultConfigFilterChainManagerInstance +} + +func newDefaultConfigFilterChainManager() *DefaultConfigFilterChainManager { + return &DefaultConfigFilterChainManager{ + configFilterPriorityQueue: make([]IConfigFilter, 0, 2), + } +} + +type DefaultConfigFilterChainManager struct { + configFilterPriorityQueue +} + +func (m *DefaultConfigFilterChainManager) AddFilter(filter IConfigFilter) error { + return m.configFilterPriorityQueue.addFilter(filter) +} + +func (m *DefaultConfigFilterChainManager) GetFilters() []IConfigFilter { + return m.configFilterPriorityQueue +} + +func (m *DefaultConfigFilterChainManager) DoFilters(param *vo.ConfigParam) error { + for index := 0; index < len(m.GetFilters()); index++ { + if err := m.GetFilters()[index].DoFilter(param); err != nil { + return err + } + } + return nil +} + +func (m *DefaultConfigFilterChainManager) DoFilterByName(param *vo.ConfigParam, name string) error { + for index := 0; index < len(m.GetFilters()); index++ { + if m.GetFilters()[index].GetFilterName() == name { + if err := m.GetFilters()[index].DoFilter(param); err != nil { + return err + } + return nil + } + } + return fmt.Errorf("cannot find the filter[%s]", name) +} + +func RegisterConfigFilter(chain IConfigFilterChain, filter IConfigFilter) error { + return chain.AddFilter(filter) +} + +type configFilterPriorityQueue []IConfigFilter + +func (c *configFilterPriorityQueue) addFilter(filter IConfigFilter) error { + var pos int = len(*c) + for i := 0; i < len(*c); i++ { + if filter.GetFilterName() == (*c)[i].GetFilterName() { + return nil + } + if filter.GetOrder() < (*c)[i].GetOrder() { + pos = i + break + } + } + if pos == len(*c) { + *c = append((*c)[:], filter) + } else { + temp := append((*c)[:pos], filter) + *c = append(temp[:], (*c)[pos:]...) + } + return nil +} diff --git a/example/config-acm/ak b/example/config-acm/ak new file mode 100644 index 00000000..1ef046fe --- /dev/null +++ b/example/config-acm/ak @@ -0,0 +1 @@ +LTAIxxxxxxxxxxxBHS21E6 \ No newline at end of file diff --git a/example/config-acm/main.go b/example/config-acm/main.go index 30462b99..cd5fc30b 100644 --- a/example/config-acm/main.go +++ b/example/config-acm/main.go @@ -18,67 +18,124 @@ package main import ( "fmt" - - "github.com/nacos-group/nacos-sdk-go/v2/clients" + "github.com/nacos-group/nacos-sdk-go/v2/clients/config_client" + "github.com/nacos-group/nacos-sdk-go/v2/clients/nacos_client" "github.com/nacos-group/nacos-sdk-go/v2/common/constant" + "github.com/nacos-group/nacos-sdk-go/v2/common/http_agent" "github.com/nacos-group/nacos-sdk-go/v2/vo" + "time" ) -func main() { - cc := constant.ClientConfig{ - Endpoint: "acm.aliyun.com:8080", - NamespaceId: "e525eafa-f7d7-4029-83d9-008937f9d468", - RegionId: "cn-shanghai", - AccessKey: "LTAI4G8KxxxxxxxxxxxxxbwZLBr", - SecretKey: "n5jTL9YxxxxxxxxxxxxaxmPLZV9", - OpenKMS: true, - TimeoutMs: 5000, - } +var localServerConfigWithOptions = constant.NewServerConfig( + "mse-d12e6112-p.nacos-ans.mse.aliyuncs.com", + 8848, +) - // a more graceful way to create config client - client, err := clients.NewConfigClient( - vo.NacosClientParam{ - ClientConfig: &cc, - }, - ) +var localClientConfigWithOptions = constant.NewClientConfig( + constant.WithTimeoutMs(10*1000), + constant.WithBeatInterval(2*1000), + constant.WithNotLoadCacheAtStart(true), + //constant.WithAccessKey(getFileContent(path.Join(getWDR(), "ak"))), + //constant.WithSecretKey(getFileContent(path.Join(getWDR(), "sk"))), + constant.WithAccessKey("LTAxxxgQL"), + constant.WithSecretKey("iG4xxxV6C"), + constant.WithNamespaceId("791fd262-3735-40df-a605-e3236f8ff495"), + constant.WithOpenKMS(true), + constant.WithKMSVersion(constant.KMSv1), + constant.WithRegionId("cn-beijing"), +) + +var localConfigList = []vo.ConfigParam{ + { + DataId: "common-config", + Group: "default", + Content: "common", + }, + { + DataId: "cipher-crypt", + Group: "default", + Content: "cipher", + }, + { + DataId: "cipher-kms-aes-128-crypt", + Group: "default", + Content: "cipher-aes-128", + }, + { + DataId: "cipher-kms-aes-256-crypt", + Group: "default", + Content: "cipher-aes-256", + }, +} + +func main() { + client, err := createConfigClient() if err != nil { panic(err) } - // to enable encrypt/decrypt, DataId should be start with "cipher-" - _, err = client.PublishConfig(vo.ConfigParam{ - DataId: "cipher-dataId-1", - Group: "test-group", - Content: "hello world!", - }) + for _, localConfig := range localConfigList { + // to enable encrypt/decrypt, DataId should be start with "cipher-" + configParam := vo.ConfigParam{ + DataId: localConfig.DataId, + Group: localConfig.Group, + Content: localConfig.Content, + OnChange: func(namespace, group, dataId, data string) { + fmt.Printf("successfully receive changed config: \n"+ + "group[%s], dataId[%s], data[%s]\n", group, dataId, data) + }, + } - if err != nil { - fmt.Printf("PublishConfig err: %v\n", err) - } + err := client.ListenConfig(configParam) + if err != nil { + fmt.Printf("failed to listen: group[%s], dataId[%s] with error: %s\n", + configParam.Group, configParam.DataId, err) + } else { + fmt.Printf("successfully ListenConfig: group[%s], dataId[%s]\n", configParam.Group, configParam.DataId) + } - //get config - content, err := client.GetConfig(vo.ConfigParam{ - DataId: "cipher-dataId-3", - Group: "test-group", - }) - fmt.Printf("GetConfig, config: %s, error: %v\n", content, err) + published, err := client.PublishConfig(configParam) + if published && err == nil { + fmt.Printf("successfully publish: group[%s], dataId[%s], data[%s]\n", configParam.Group, configParam.DataId, configParam.Content) + } else { + fmt.Printf("failed to publish: group[%s], dataId[%s], data[%s]\n with error: %s\n", + configParam.Group, configParam.DataId, configParam.Content, err) + } - // DataId is not start with "cipher-", content will not be encrypted. - _, err = client.PublishConfig(vo.ConfigParam{ - DataId: "dataId-1", - Group: "test-group", - Content: "hello world!", - }) + //wait for config change callback to execute + time.Sleep(2 * time.Second) - if err != nil { - fmt.Printf("PublishConfig err: %v\n", err) + //get config + content, err := client.GetConfig(configParam) + if err == nil { + fmt.Printf("successfully get config: group[%s], dataId[%s], data[%s]\n", configParam.Group, configParam.DataId, configParam.Content) + } else { + fmt.Printf("failed to get config: group[%s], dataId[%s], data[%s]\n with error: %s\n", + configParam.Group, configParam.DataId, configParam.Content, err) + } + + if content != localConfig.Content { + panic("publish/get encrypted config failed.") + } else { + fmt.Println("publish/get encrypted config success.") + } + //wait for config change callback to execute + //time.Sleep(2 * time.Second) } - //get config - content, err = client.GetConfig(vo.ConfigParam{ - DataId: "dataId-1", - Group: "test-group", - }) - fmt.Printf("GetConfig, config: %s, error: %v\n", content, err) +} + +func createConfigClient() (*config_client.ConfigClient, error) { + nc := nacos_client.NacosClient{} + _ = nc.SetServerConfig([]constant.ServerConfig{*localServerConfigWithOptions}) + _ = nc.SetClientConfig(*localClientConfigWithOptions) + fmt.Println("ak: " + localClientConfigWithOptions.AccessKey) + fmt.Println("sk: " + localClientConfigWithOptions.SecretKey) + _ = nc.SetHttpAgent(&http_agent.HttpAgent{}) + client, err := config_client.NewConfigClient(&nc) + if err != nil { + return nil, err + } + return client, nil } diff --git a/example/config-acm/sk b/example/config-acm/sk new file mode 100644 index 00000000..e5d766bd --- /dev/null +++ b/example/config-acm/sk @@ -0,0 +1 @@ +kr6JxxxxxxxxxxxxxY8nHnsD6 \ No newline at end of file diff --git a/example/config-mse-kmsv3/ak b/example/config-mse-kmsv3/ak new file mode 100644 index 00000000..a01e9b37 --- /dev/null +++ b/example/config-mse-kmsv3/ak @@ -0,0 +1 @@ +LTAxxxxgQL \ No newline at end of file diff --git a/example/config-mse-kmsv3/ca.pem b/example/config-mse-kmsv3/ca.pem new file mode 100644 index 00000000..e3ab6854 --- /dev/null +++ b/example/config-mse-kmsv3/ca.pem @@ -0,0 +1,3 @@ +-----BEGIN CERTIFICATE----- +xxx +-----END CERTIFICATE----- diff --git a/example/config-mse-kmsv3/client_key.json b/example/config-mse-kmsv3/client_key.json new file mode 100644 index 00000000..3ebda5bb --- /dev/null +++ b/example/config-mse-kmsv3/client_key.json @@ -0,0 +1,4 @@ +{ + "KeyId": "KAAxxxxe74", + "PrivateKeyData": "MIIxxxA==" +} \ No newline at end of file diff --git a/example/config-mse-kmsv3/endpoint b/example/config-mse-kmsv3/endpoint new file mode 100644 index 00000000..316e189f --- /dev/null +++ b/example/config-mse-kmsv3/endpoint @@ -0,0 +1 @@ +kst-bjj64f82f41yjrs66eygc.cryptoservice.kms.aliyuncs.com \ No newline at end of file diff --git a/example/config-mse-kmsv3/main.go b/example/config-mse-kmsv3/main.go new file mode 100644 index 00000000..55e5bf55 --- /dev/null +++ b/example/config-mse-kmsv3/main.go @@ -0,0 +1,191 @@ +/* + * Copyright 1999-2020 Alibaba Group Holding Ltd. + * + * 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 main + +import ( + "fmt" + "github.com/nacos-group/nacos-sdk-go/v2/clients/config_client" + "github.com/nacos-group/nacos-sdk-go/v2/clients/nacos_client" + "github.com/nacos-group/nacos-sdk-go/v2/common/constant" + "github.com/nacos-group/nacos-sdk-go/v2/common/filter" + "github.com/nacos-group/nacos-sdk-go/v2/common/http_agent" + "github.com/nacos-group/nacos-sdk-go/v2/common/logger" + "github.com/nacos-group/nacos-sdk-go/v2/vo" + "io/ioutil" + "os" + "path" + "time" +) + +var localServerConfigWithOptions = constant.NewServerConfig( + "mse-d12e6112-p.nacos-ans.mse.aliyuncs.com", + 8848, +) + +var localClientConfigWithOptions = constant.NewClientConfig( + constant.WithTimeoutMs(10*1000), + constant.WithBeatInterval(2*1000), + constant.WithNotLoadCacheAtStart(true), + constant.WithAccessKey(getFileContent(path.Join(getWDR(), "ak"))), + constant.WithSecretKey(getFileContent(path.Join(getWDR(), "sk"))), + constant.WithNamespaceId("791fd262-3735-40df-a605-e3236f8ff495"), + constant.WithOpenKMS(true), + constant.WithKMSVersion(constant.KMSv3), + constant.WithKMSv3Config(&constant.KMSv3Config{ + ClientKeyContent: getFileContent(path.Join(getWDR(), "client_key.json")), + Password: getFileContent(path.Join(getWDR(), "password")), + Endpoint: getFileContent(path.Join(getWDR(), "endpoint")), + CaContent: getFileContent(path.Join(getWDR(), "ca.pem")), + }), + constant.WithRegionId("cn-beijing"), +) + +var localConfigList = []vo.ConfigParam{ + { + DataId: "common-config", + Group: "default", + Content: "common", + KmsKeyId: "key-xxx", //可以识别 + }, + { + DataId: "cipher-crypt", + Group: "default", + Content: "cipher", + KmsKeyId: "key-xxx", //可以识别 + }, + { + DataId: "cipher-kms-aes-128-crypt", + Group: "default", + Content: "cipher-aes-128", + KmsKeyId: "key-xxx", //可以识别 + }, + { + DataId: "cipher-kms-aes-256-crypt", + Group: "default", + Content: "cipher-aes-256", + KmsKeyId: "key-xxx", //可以识别 + }, +} + +func main() { + usingKMSv3ClientAndStoredByNacos() + //onlyUsingFilters() +} + +func usingKMSv3ClientAndStoredByNacos() { + client := createConfigClient() + if client == nil { + panic("init ConfigClient failed") + } + + for _, localConfig := range localConfigList { + // to enable encrypt/decrypt, DataId should be start with "cipher-" + configParam := vo.ConfigParam{ + DataId: localConfig.DataId, + Group: localConfig.Group, + Content: localConfig.Content, + KmsKeyId: localConfig.KmsKeyId, + OnChange: func(namespace, group, dataId, data string) { + fmt.Printf("successfully receive changed config: \n"+ + "group[%s], dataId[%s], data[%s]\n", group, dataId, data) + }, + } + + err := client.ListenConfig(configParam) + if err != nil { + fmt.Printf("failed to listen: group[%s], dataId[%s] with error: %s\n", + configParam.Group, configParam.DataId, err) + } else { + fmt.Printf("successfully ListenConfig: group[%s], dataId[%s]\n", configParam.Group, configParam.DataId) + } + + published, err := client.PublishConfig(configParam) + if published && err == nil { + fmt.Printf("successfully publish: group[%s], dataId[%s], data[%s]\n", configParam.Group, configParam.DataId, configParam.Content) + } else { + fmt.Printf("failed to publish: group[%s], dataId[%s], data[%s]\n with error: %s\n", + configParam.Group, configParam.DataId, configParam.Content, err) + } + + //wait for config change callback to execute + time.Sleep(2 * time.Second) + + //get config + content, err := client.GetConfig(configParam) + if err == nil { + fmt.Printf("successfully get config: group[%s], dataId[%s], data[%s]\n", configParam.Group, configParam.DataId, configParam.Content) + } else { + fmt.Printf("failed to get config: group[%s], dataId[%s], data[%s]\n with error: %s\n", + configParam.Group, configParam.DataId, configParam.Content, err) + } + + if content != localConfig.Content { + panic("publish/get encrypted config failed.") + } else { + fmt.Println("publish/get encrypted config success.") + } + //wait for config change callback to execute + //time.Sleep(2 * time.Second) + } +} + +func onlyUsingFilters() error { + createConfigClient() + for _, param := range localConfigList { + param.UsageType = vo.RequestType + fmt.Println("param = ", param) + if err := filter.GetDefaultConfigFilterChainManager().DoFilters(¶m); err != nil { + return err + } + fmt.Println("after encrypt param = ", param) + param.UsageType = vo.ResponseType + if err := filter.GetDefaultConfigFilterChainManager().DoFilters(¶m); err != nil { + return err + } + fmt.Println("after decrypt param = ", param) + } + return nil +} + +func createConfigClient() *config_client.ConfigClient { + nc := nacos_client.NacosClient{} + _ = nc.SetServerConfig([]constant.ServerConfig{*localServerConfigWithOptions}) + _ = nc.SetClientConfig(*localClientConfigWithOptions) + _ = nc.SetHttpAgent(&http_agent.HttpAgent{}) + client, err := config_client.NewConfigClient(&nc) + if err != nil { + logger.Errorf("create config client failed: " + err.Error()) + return nil + } + return client +} + +func getWDR() string { + getwd, err := os.Getwd() + if err != nil { + return "" + } + return getwd +} + +func getFileContent(filePath string) string { + file, err := ioutil.ReadFile(filePath) + if err != nil { + return "" + } + return string(file) +} diff --git a/example/config-mse-kmsv3/password b/example/config-mse-kmsv3/password new file mode 100644 index 00000000..09311c32 --- /dev/null +++ b/example/config-mse-kmsv3/password @@ -0,0 +1 @@ +19axxxxx213 \ No newline at end of file diff --git a/example/config-mse-kmsv3/sk b/example/config-mse-kmsv3/sk new file mode 100644 index 00000000..a3d978e3 --- /dev/null +++ b/example/config-mse-kmsv3/sk @@ -0,0 +1 @@ +iG48xxxV6C \ No newline at end of file diff --git a/go.mod b/go.mod index 8acd2b9a..b8dd9173 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,10 @@ module github.com/nacos-group/nacos-sdk-go/v2 go 1.18 require ( - github.com/aliyun/alibaba-cloud-sdk-go v1.61.1704 + github.com/alibabacloud-go/tea v1.1.17 + github.com/aliyun/alibaba-cloud-sdk-go v1.61.1800 + github.com/aliyun/alibabacloud-dkms-gcs-go-sdk v0.2.2 + github.com/aliyun/alibabacloud-dkms-transfer-go-sdk v0.1.7 github.com/buger/jsonparser v1.1.1 github.com/golang/mock v1.6.0 github.com/golang/protobuf v1.5.2 @@ -19,6 +22,8 @@ require ( ) require ( + github.com/alibabacloud-go/debug v0.0.0-20190504072949-9472017b5c68 // indirect + github.com/alibabacloud-go/tea-utils v1.4.4 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect @@ -35,6 +40,7 @@ require ( github.com/prometheus/procfs v0.7.3 // indirect go.uber.org/atomic v1.7.0 // indirect go.uber.org/multierr v1.6.0 // indirect + golang.org/x/crypto v0.14.0 // indirect golang.org/x/net v0.17.0 // indirect golang.org/x/sys v0.13.0 // indirect golang.org/x/text v0.13.0 // indirect diff --git a/go.sum b/go.sum index d2553b14..c8913930 100644 --- a/go.sum +++ b/go.sum @@ -39,8 +39,19 @@ github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuy github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= -github.com/aliyun/alibaba-cloud-sdk-go v1.61.1704 h1:PpfENOj/vPfhhy9N2OFRjpue0hjM5XqAp2thFmkXXIk= -github.com/aliyun/alibaba-cloud-sdk-go v1.61.1704/go.mod h1:RcDobYh8k5VP6TNybz9m++gL3ijVI5wueVr0EM10VsU= +github.com/alibabacloud-go/debug v0.0.0-20190504072949-9472017b5c68 h1:NqugFkGxx1TXSh/pBcU00Y6bljgDPaFdh5MUSeJ7e50= +github.com/alibabacloud-go/debug v0.0.0-20190504072949-9472017b5c68/go.mod h1:6pb/Qy8c+lqua8cFpEy7g39NRRqOWc3rOwAy8m5Y2BY= +github.com/alibabacloud-go/tea v1.1.0/go.mod h1:IkGyUSX4Ba1V+k4pCtJUc6jDpZLFph9QMy2VUPTwukg= +github.com/alibabacloud-go/tea v1.1.17 h1:05R5DnaJXe9sCNIe8KUgWHC/z6w/VZIwczgUwzRnul8= +github.com/alibabacloud-go/tea v1.1.17/go.mod h1:nXxjm6CIFkBhwW4FQkNrolwbfon8Svy6cujmKFUq98A= +github.com/alibabacloud-go/tea-utils v1.4.4 h1:lxCDvNCdTo9FaXKKq45+4vGETQUKNOW/qKTcX9Sk53o= +github.com/alibabacloud-go/tea-utils v1.4.4/go.mod h1:KNcT0oXlZZxOXINnZBs6YvgOd5aYp9U67G+E3R8fcQw= +github.com/aliyun/alibaba-cloud-sdk-go v1.61.1800 h1:ie/8RxBOfKZWcrbYSJi2Z8uX8TcOlSMwPlEJh83OeOw= +github.com/aliyun/alibaba-cloud-sdk-go v1.61.1800/go.mod h1:RcDobYh8k5VP6TNybz9m++gL3ijVI5wueVr0EM10VsU= +github.com/aliyun/alibabacloud-dkms-gcs-go-sdk v0.2.2 h1:rWkH6D2XlXb/Y+tNAQROxBzp3a0p92ni+pXcaHBe/WI= +github.com/aliyun/alibabacloud-dkms-gcs-go-sdk v0.2.2/go.mod h1:GDtq+Kw+v0fO+j5BrrWiUHbBq7L+hfpzpPfXKOZMFE0= +github.com/aliyun/alibabacloud-dkms-transfer-go-sdk v0.1.7 h1:olLiPI2iM8Hqq6vKnSxpM3awCrm9/BeOgHpzQkOYnI4= +github.com/aliyun/alibabacloud-dkms-transfer-go-sdk v0.1.7/go.mod h1:oDg1j4kFxnhgftaiLJABkGeSvuEvSF5Lo6UmRAMruX4= github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= @@ -235,7 +246,10 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= +golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= diff --git a/vo/config_param.go b/vo/config_param.go index e7e7d7fb..3360f162 100644 --- a/vo/config_param.go +++ b/vo/config_param.go @@ -19,19 +19,28 @@ package vo type Listener func(namespace, group, dataId, data string) type ConfigParam struct { - DataId string `param:"dataId"` //required - Group string `param:"group"` //required - Content string `param:"content"` //required - Tag string `param:"tag"` - AppName string `param:"appName"` - BetaIps string `param:"betaIps"` - CasMd5 string `param:"casMd5"` - Type string `param:"type"` - SrcUser string `param:"srcUser"` - EncryptedDataKey string `param:"encryptedDataKey"` + DataId string `param:"dataId"` //required + Group string `param:"group"` //required + Content string `param:"content"` //required + Tag string `param:"tag"` + AppName string `param:"appName"` + BetaIps string `param:"betaIps"` + CasMd5 string `param:"casMd5"` + Type string `param:"type"` + SrcUser string `param:"srcUser"` + EncryptedDataKey string `param:"encryptedDataKey"` + KmsKeyId string `param:"kmsKeyId"` + UsageType UsageType `param:"usageType"` OnChange func(namespace, group, dataId, data string) } +type UsageType string + +const ( + RequestType UsageType = "RequestType" + ResponseType UsageType = "ResponseType" +) + type SearchConfigParam struct { Search string `param:"search"` DataId string `param:"dataId"`