diff --git a/apiserver/httpserver/config_console_access.go b/apiserver/httpserver/config_console_access.go index 3b7b114b6..41ddbced6 100644 --- a/apiserver/httpserver/config_console_access.go +++ b/apiserver/httpserver/config_console_access.go @@ -408,3 +408,16 @@ func (h *HTTPServer) CreateConfigFileTemplate(req *restful.Request, rsp *restful handler.WriteHeaderAndProto(h.configServer.CreateConfigFileTemplate(ctx, configFileTemplate)) } + +// GetAllConfigEncryptAlgorithm get all config encrypt algorithm +func (h *HTTPServer) GetAllConfigEncryptAlgorithms(req *restful.Request, rsp *restful.Response) { + handler := &httpcommon.Handler{ + Request: req, + Response: rsp, + } + response := h.configServer.GetAllConfigEncryptAlgorithms(handler.ParseHeaderContext()) + configLog.Info("response", + zap.Uint32("code", response.GetCode().GetValue()), + ) + handler.WriteHeaderAndProto(response) +} diff --git a/apiserver/httpserver/config_server.go b/apiserver/httpserver/config_server.go index cbeacc9df..4e791911d 100644 --- a/apiserver/httpserver/config_server.go +++ b/apiserver/httpserver/config_server.go @@ -77,6 +77,8 @@ func (h *HTTPServer) bindConfigConsoleEndpoint(ws *restful.WebService) { ws.Route(docs.EnrichBatchDeleteConfigFileApiDocs(ws.POST("/configfiles/batchdelete").To(h.BatchDeleteConfigFile))) ws.Route(docs.EnrichExportConfigFileApiDocs(ws.POST("/configfiles/export").To(h.ExportConfigFile))) ws.Route(docs.EnrichImportConfigFileApiDocs(ws.POST("/configfiles/import").To(h.ImportConfigFile))) + ws.Route(docs.EnrichGetAllConfigEncryptAlgorithmsApiDocs(ws.GET("/configfiles/encryptalgorithm"). + To(h.GetAllConfigEncryptAlgorithms))) // 配置文件发布 ws.Route(docs.EnrichPublishConfigFileApiDocs(ws.POST("/configfiles/release").To(h.PublishConfigFile))) diff --git a/apiserver/httpserver/docs/config_server_apidoc.go b/apiserver/httpserver/docs/config_server_apidoc.go index 4e8843068..e6b0333fa 100644 --- a/apiserver/httpserver/docs/config_server_apidoc.go +++ b/apiserver/httpserver/docs/config_server_apidoc.go @@ -175,6 +175,12 @@ func EnrichImportConfigFileApiDocs(r *restful.RouteBuilder) *restful.RouteBuilde " \"namespace\":\"someNamespace\",\n \"group\":\"someGroup\"\n }\n]\n```") } +func EnrichGetAllConfigEncryptAlgorithmsApiDocs(r *restful.RouteBuilder) *restful.RouteBuilder { + return r. + Doc("获取配置加密算法"). + Metadata(restfulspec.KeyOpenAPITags, configConsoleApiTags) +} + func EnrichPublishConfigFileApiDocs(r *restful.RouteBuilder) *restful.RouteBuilder { return r. Doc("发布配置文件"). diff --git a/cache/config_file.go b/cache/config_file.go index ddd7899a0..fb8d365db 100644 --- a/cache/config_file.go +++ b/cache/config_file.go @@ -90,6 +90,7 @@ type Entry struct { Content string Md5 string Version uint64 + DataKey string // 创建的时候,设置过期时间 ExpireTime time.Time // 标识是否是空缓存 @@ -239,7 +240,7 @@ func (fc *fileCache) GetOrLoadIfAbsent(namespace, group, fileName string) (*Entr // 从数据库中加载数据 atomic.AddInt32(&fc.loadCnt, 1) - file, err := fc.storage.GetConfigFileRelease(nil, namespace, group, fileName) + file, dataKey, err := fc.getConfigFileReleaseAndDataKey(namespace, group, fileName) if err != nil { configLog.Error("[Config][Cache] load config file release error.", zap.String("namespace", namespace), @@ -269,6 +270,7 @@ func (fc *fileCache) GetOrLoadIfAbsent(namespace, group, fileName string) (*Entr Content: file.Content, Md5: file.Md5, Version: file.Version, + DataKey: dataKey, ExpireTime: fc.getExpireTime(), } @@ -287,6 +289,35 @@ func (fc *fileCache) GetOrLoadIfAbsent(namespace, group, fileName string) (*Entr return newEntry, nil } +func (fc *fileCache) getConfigFileReleaseAndDataKey( + namespace, group, fileName string) (*model.ConfigFileRelease, string, error) { + file, err := fc.storage.GetConfigFileRelease(nil, namespace, group, fileName) + if err != nil { + configLog.Error("[Config][Cache] load config file release error.", + zap.String("namespace", namespace), + zap.String("group", group), + zap.String("fileName", fileName), + zap.Error(err)) + return nil, "", err + } + tags, err := fc.storage.QueryTagByConfigFile(namespace, group, fileName) + if err != nil { + configLog.Error("[Config][Cache] load config file tag error.", + zap.String("namespace", namespace), + zap.String("group", group), + zap.String("fileName", fileName), + zap.Error(err)) + return nil, "", err + } + var dataKey string + for _, tag := range tags { + if tag.Key == utils.ConfigFileTagKeyDataKey { + dataKey = tag.Value + } + } + return file, dataKey, nil +} + // Remove 删除缓存对象 func (fc *fileCache) Remove(namespace, group, fileName string) { atomic.AddInt32(&fc.removeCnt, 1) diff --git a/cache/config_file_test.go b/cache/config_file_test.go index e39afedfc..371380d19 100644 --- a/cache/config_file_test.go +++ b/cache/config_file_test.go @@ -45,9 +45,11 @@ func TestGetAndRemoveAndReloadConfigFile(t *testing.T) { // Mock 数据 configFile := assembleConfigFile() configFileRelease := assembleConfigFileRelease(configFile) + configFileTags := assembelConfigFileTags(configFile) // 前三次调用返回一个值,第四次调用返回另外一个值,默认更新 mockedStorage.EXPECT().GetConfigFileRelease(nil, testNamespace, testGroup, testFile).Return(configFileRelease, nil).Times(3) + mockedStorage.EXPECT().QueryTagByConfigFile(testNamespace, testGroup, testFile).Return(configFileTags, nil).Times(3) for i := 0; i < 100; i++ { go func() { @@ -93,9 +95,11 @@ func TestConcurrentGetConfigFile(t *testing.T) { // Mock 数据 configFile := assembleConfigFile() configFileRelease := assembleConfigFileRelease(configFile) + configFileTags := assembelConfigFileTags(configFile) // 一共调用三次, mockedStorage.EXPECT().GetConfigFileRelease(nil, testNamespace, testGroup, testFile).Return(configFileRelease, nil).Times(1) + mockedStorage.EXPECT().QueryTagByConfigFile(testNamespace, testGroup, testFile).Return(configFileTags, nil).Times(1) for i := 0; i < 1000; i++ { go func() { @@ -121,7 +125,9 @@ func TestUpdateCache(t *testing.T) { configFileRelease := assembleConfigFileRelease(configFile) configFileRelease.Content = firstValue configFileRelease.Version = firstVersion + configFileTags := assembelConfigFileTags(configFile) first := mockedStorage.EXPECT().GetConfigFileRelease(nil, testNamespace, testGroup, testFile).Return(configFileRelease, nil).Times(1) + mockedStorage.EXPECT().QueryTagByConfigFile(testNamespace, testGroup, testFile).Return(configFileTags, nil).Times(1) // 第二次调用返会值 secondValue := "secondValue" @@ -130,7 +136,9 @@ func TestUpdateCache(t *testing.T) { configFileRelease2 := assembleConfigFileRelease(configFile2) configFileRelease2.Content = secondValue configFileRelease2.Version = secondVersion + configFileTags = assembelConfigFileTags(configFile) second := mockedStorage.EXPECT().GetConfigFileRelease(nil, testNamespace, testGroup, testFile).Return(configFileRelease2, nil).Times(1) + mockedStorage.EXPECT().QueryTagByConfigFile(testNamespace, testGroup, testFile).Return(configFileTags, nil).Times(1) gomock.InOrder(first, second) @@ -190,6 +198,19 @@ func assembleConfigFileRelease(configFile *model.ConfigFile) *model.ConfigFileRe } } +func assembelConfigFileTags(configFile *model.ConfigFile) []*model.ConfigFileTag { + tags := []*model.ConfigFileTag{ + { + Key: utils.ConfigFileTagKeyDataKey, + Value: "data key", + Namespace: configFile.Namespace, + Group: configFile.Group, + FileName: configFile.Name, + }, + } + return tags +} + func assembleConfigFileGroup() *model.ConfigFileGroup { return &model.ConfigFileGroup{ Id: uint64(100), diff --git a/common/api/v1/config_file_response_build.go b/common/api/v1/config_file_response_build.go index 4a5909213..8321e76fd 100644 --- a/common/api/v1/config_file_response_build.go +++ b/common/api/v1/config_file_response_build.go @@ -21,6 +21,7 @@ import ( "github.com/golang/protobuf/ptypes/wrappers" apiconfig "github.com/polarismesh/specification/source/go/api/v1/config_manage" apimodel "github.com/polarismesh/specification/source/go/api/v1/model" + "google.golang.org/protobuf/types/known/wrapperspb" ) func NewConfigClientResponse( @@ -100,6 +101,14 @@ func NewConfigFileBatchQueryResponse( } } +func NewConfigFileBatchQueryResponseWithMessage( + code apimodel.Code, message string) *apiconfig.ConfigBatchQueryResponse { + return &apiconfig.ConfigBatchQueryResponse{ + Code: &wrappers.UInt32Value{Value: uint32(code)}, + Info: &wrappers.StringValue{Value: code2info[uint32(code)]}, + } +} + func NewConfigFileTemplateResponse( code apimodel.Code, template *apiconfig.ConfigFileTemplate) *apiconfig.ConfigResponse { return &apiconfig.ConfigResponse{ @@ -176,3 +185,20 @@ func NewConfigFileExportResponse(code apimodel.Code, data []byte) *apiconfig.Con Data: &wrappers.BytesValue{Value: data}, } } + +func NewConfigFileExportResponseWithMessage(code apimodel.Code, message string) *apiconfig.ConfigExportResponse { + return &apiconfig.ConfigExportResponse{ + Code: &wrappers.UInt32Value{Value: uint32(code)}, + Info: &wrappers.StringValue{Value: code2info[uint32(code)] + ":" + message}, + } +} + +func NewConfigEncryptAlgorithmResponse(code apimodel.Code, + algorithms []*wrapperspb.StringValue) *apiconfig.ConfigEncryptAlgorithmResponse { + resp := &apiconfig.ConfigEncryptAlgorithmResponse{ + Code: &wrappers.UInt32Value{Value: uint32(code)}, + Info: &wrappers.StringValue{Value: code2info[uint32(code)]}, + Algorithms: algorithms, + } + return resp +} diff --git a/common/rsa/rsa.go b/common/rsa/rsa.go new file mode 100644 index 000000000..ca3782095 --- /dev/null +++ b/common/rsa/rsa.go @@ -0,0 +1,121 @@ +/** + * Tencent is pleased to support the open source community by making Polaris available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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 rsa + +import ( + "bytes" + "crypto/rand" + "crypto/rsa" + "crypto/x509" + "encoding/base64" +) + +// RSAKey RSA key pair +type RSAKey struct { + PrivateKey string + PublicKey string +} + +// GenerateKey generate RSA key pair +func GenerateRSAKey() (*RSAKey, error) { + privateKey, err := rsa.GenerateKey(rand.Reader, 1024) + if err != nil { + return nil, err + } + rsaKey := &RSAKey{ + PrivateKey: base64.StdEncoding.EncodeToString(x509.MarshalPKCS1PrivateKey(privateKey)), + PublicKey: base64.StdEncoding.EncodeToString(x509.MarshalPKCS1PublicKey(&privateKey.PublicKey)), + } + return rsaKey, nil +} + +// Encrypt RSA encrypt plaintext using public key +func Encrypt(plaintext, publicKey []byte) ([]byte, error) { + pub, err := x509.ParsePKCS1PublicKey(publicKey) + if err != nil { + return nil, err + } + totalLen := len(plaintext) + segLen := pub.Size() - 11 + start := 0 + buffer := bytes.Buffer{} + for start < totalLen { + end := start + segLen + if end > totalLen { + end = totalLen + } + seg, err := rsa.EncryptPKCS1v15(rand.Reader, pub, plaintext[start:end]) + if err != nil { + return nil, err + } + buffer.Write(seg) + start = end + } + return buffer.Bytes(), nil +} + +// Decrypt RSA decrypt ciphertext using private key +func Decrypt(ciphertext, privateKey []byte) ([]byte, error) { + priv, err := x509.ParsePKCS1PrivateKey(privateKey) + if err != nil { + return nil, err + } + keySize := priv.Size() + totalLen := len(ciphertext) + start := 0 + buffer := bytes.Buffer{} + for start < totalLen { + end := start + keySize + if end > totalLen { + end = totalLen + } + seg, err := rsa.DecryptPKCS1v15(rand.Reader, priv, ciphertext[start:end]) + if err != nil { + return nil, err + } + buffer.Write(seg) + start = end + } + return buffer.Bytes(), nil +} + +// EncryptToBase64 RSA encrypt plaintext and base64 encode ciphertext +func EncryptToBase64(plaintext []byte, base64PublicKey string) (string, error) { + pub, err := base64.StdEncoding.DecodeString(base64PublicKey) + if err != nil { + return "", err + } + ciphertext, err := Encrypt(plaintext, pub) + if err != nil { + return "", err + } + return base64.StdEncoding.EncodeToString(ciphertext), nil +} + +// DecryptFromBase64 base64 decode ciphertext and RSA decrypt +func DecryptFromBase64(base64Ciphertext, base64PrivateKey string) ([]byte, error) { + priv, err := base64.StdEncoding.DecodeString(base64PrivateKey) + if err != nil { + return nil, err + } + ciphertext, err := base64.StdEncoding.DecodeString(base64Ciphertext) + if err != nil { + return nil, err + } + return Decrypt(ciphertext, priv) +} diff --git a/common/rsa/rsa_test.go b/common/rsa/rsa_test.go new file mode 100644 index 000000000..6a4701129 --- /dev/null +++ b/common/rsa/rsa_test.go @@ -0,0 +1,81 @@ +/** + * Tencent is pleased to support the open source community by making Polaris available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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 rsa + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestGenerateRSAKey(t *testing.T) { + tests := []struct { + name string + err error + }{ + { + name: "generate rsa key", + err: nil, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := GenerateRSAKey() + t.Logf("PrivateKey: %s", got.PrivateKey) + t.Logf("PublicKey: %s", got.PublicKey) + assert.Nil(t, err) + }) + } +} + +func TestEncryptToBase64(t *testing.T) { + type args struct { + plaintext []byte + } + tests := []struct { + name string + args args + want string + err error + }{ + { + name: "encrypt to base64", + args: args{ + plaintext: []byte("1234abcd!@#$"), + }, + }, + { + name: "encrypt lang text to base64", + args: args{ + plaintext: []byte(`aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa`), + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + rasKey, err := GenerateRSAKey() + assert.Nil(t, err) + ciphertext, err := EncryptToBase64(tt.args.plaintext, rasKey.PublicKey) + assert.Nil(t, err) + plaintext, err := DecryptFromBase64(ciphertext, rasKey.PrivateKey) + assert.Nil(t, err) + assert.Equal(t, plaintext, tt.args.plaintext) + }) + } +} diff --git a/common/utils/common.go b/common/utils/common.go index abc8300d6..344143b12 100644 --- a/common/utils/common.go +++ b/common/utils/common.go @@ -441,6 +441,21 @@ func ZapInstanceID(id string) zap.Field { return zap.String("instance-id", id) } +// ZapNamespace 生成namespace的日志描述 +func ZapNamespace(namespace string) zap.Field { + return zap.String("namesapce", namespace) +} + +// ZapGroup 生成group的日志描述 +func ZapGroup(group string) zap.Field { + return zap.String("group", group) +} + +// ZapFileName 生成fileName的日志描述 +func ZapFileName(fileName string) zap.Field { + return zap.String("file-name", fileName) +} + // CheckDbStrFieldLen 检查name字段是否超过DB中对应字段的最大字符长度限制 func CheckDbStrFieldLen(param *wrappers.StringValue, dbLen int) error { return CheckDbRawStrFieldLen(param.GetValue(), dbLen) diff --git a/common/utils/config_file.go b/common/utils/config_file.go index ed4f740d1..40727eb40 100644 --- a/common/utils/config_file.go +++ b/common/utils/config_file.go @@ -52,6 +52,10 @@ const ( ConfigFileImportConflictSkip = "skip" // ConfigFileImportConflictOverwrite 导入配置文件发生冲突覆盖原配置文件 ConfigFileImportConflictOverwrite = "overwrite" + // ConfigFileTagKeyDataKey 加密密钥 tag key + ConfigFileTagKeyDataKey = "data_key" + // ConfigFileTagKeyEncryptAlgo 加密算法 tag key + ConfigFileTagKeyEncryptAlgo = "encrypt_algo" ) // GenFileId 生成文件 Id diff --git a/config/api.go b/config/api.go index b1885b37d..a1d3994b7 100644 --- a/config/api.go +++ b/config/api.go @@ -85,6 +85,9 @@ type ConfigFileOperate interface { // ImportConfigFile 导入配置文件 ImportConfigFile(ctx context.Context, configFiles []*apiconfig.ConfigFile, conflictHandling string) *apiconfig.ConfigImportResponse + + // GetAllConfigEncryptAlgorithms 获取配置加密算法 + GetAllConfigEncryptAlgorithms(ctx context.Context) *apiconfig.ConfigEncryptAlgorithmResponse } // ConfigFileReleaseOperate 配置文件发布接口 diff --git a/config/bootstrap_test.go b/config/bootstrap_test.go index e6e8696c7..fdc91268d 100644 --- a/config/bootstrap_test.go +++ b/config/bootstrap_test.go @@ -38,6 +38,7 @@ import ( "github.com/polarismesh/polaris/common/utils" "github.com/polarismesh/polaris/namespace" "github.com/polarismesh/polaris/plugin" + _ "github.com/polarismesh/polaris/plugin/crypto/aes" _ "github.com/polarismesh/polaris/plugin/healthchecker/memory" _ "github.com/polarismesh/polaris/plugin/healthchecker/redis" _ "github.com/polarismesh/polaris/plugin/history/logger" diff --git a/config/client_config_file.go b/config/client_config_file.go index dc9c3df8c..1f182a609 100644 --- a/config/client_config_file.go +++ b/config/client_config_file.go @@ -19,6 +19,7 @@ package config import ( "context" + "encoding/base64" apiconfig "github.com/polarismesh/specification/source/go/api/v1/config_manage" apimodel "github.com/polarismesh/specification/source/go/api/v1/model" @@ -26,6 +27,7 @@ import ( "github.com/polarismesh/polaris/cache" api "github.com/polarismesh/polaris/common/api/v1" + "github.com/polarismesh/polaris/common/rsa" "github.com/polarismesh/polaris/common/utils" utils2 "github.com/polarismesh/polaris/config/utils" ) @@ -41,6 +43,7 @@ func (s *Server) GetConfigFileForClient(ctx context.Context, group := client.GetGroup().GetValue() fileName := client.GetFileName().GetValue() clientVersion := client.GetVersion().GetValue() + publicKey := client.GetPublicKey().GetValue() if namespace == "" || group == "" || fileName == "" { return api.NewConfigClientResponseWithMessage( @@ -50,8 +53,11 @@ func (s *Server) GetConfigFileForClient(ctx context.Context, requestID := utils.ParseRequestID(ctx) log.Info("[Config][Service] load config file from cache.", - zap.String("requestId", requestID), zap.String("namespace", namespace), - zap.String("group", group), zap.String("file", fileName)) + utils.ZapRequestID(requestID), + utils.ZapNamespace(namespace), + utils.ZapGroup(group), + utils.ZapFileName(fileName), + zap.String("publicKey", publicKey)) // 从缓存中获取配置内容 entry, err := s.fileCache.GetOrLoadIfAbsent(namespace, group, fileName) @@ -88,7 +94,40 @@ func (s *Server) GetConfigFileForClient(ctx context.Context, zap.String("file", fileName), zap.Uint64("version", entry.Version)) - resp := utils2.GenConfigFileResponse(namespace, group, fileName, entry.Content, entry.Md5, entry.Version) + // 加密配置返回用公钥加密的数据密钥 + if entry.DataKey != "" && publicKey != "" { + dataKeyBytes, err := base64.StdEncoding.DecodeString(entry.DataKey) + if err != nil { + log.Error("[Config][Service] base64 decode data key error.", + zap.String("requestId", requestID), + zap.String("dataKey", entry.DataKey), + zap.Error(err)) + } + cipherDataKey, err := rsa.EncryptToBase64(dataKeyBytes, publicKey) + if err != nil { + log.Error("[Config][Service] rsa encrypt data key error.", + zap.String("requestId", requestID), + zap.String("dataKey", entry.DataKey), + zap.Error(err)) + } + resp := utils2.GenConfigFileResponse(namespace, group, fileName, + entry.Content, entry.Md5, entry.Version, true, cipherDataKey) + log.Info("[Config][Client] client get config file resp.", + zap.String("requestId", requestID), + zap.String("file", resp.GetConfigFile().GetFileName().GetValue()), + zap.String("dataKey", resp.GetConfigFile().GetDataKey().GetValue()), + zap.Bool("isEncrypted", resp.GetConfigFile().GetIsEncrypted().GetValue())) + return resp + } + isEntrypted := entry.DataKey != "" + resp := utils2.GenConfigFileResponse(namespace, group, fileName, + entry.Content, entry.Md5, entry.Version, isEntrypted, "") + + log.Info("[Config][Client] client get config file resp.", + zap.String("requestId", requestID), + zap.String("file", resp.GetConfigFile().GetFileName().GetValue()), + zap.String("dataKey", resp.GetConfigFile().GetDataKey().GetValue()), + zap.Bool("isEncrypted", resp.GetConfigFile().GetIsEncrypted().GetValue())) return resp } @@ -151,7 +190,7 @@ func (s *Server) doCheckClientConfigFile(ctx context.Context, configFiles []*api } if compartor(configFile, entry) { - return utils2.GenConfigFileResponse(namespace, group, fileName, "", entry.Md5, entry.Version) + return utils2.GenConfigFileResponse(namespace, group, fileName, "", entry.Md5, entry.Version, false, "") } } diff --git a/config/config_file.go b/config/config_file.go index 6c23b85d5..d7b051f29 100644 --- a/config/config_file.go +++ b/config/config_file.go @@ -21,6 +21,7 @@ import ( "archive/zip" "bytes" "context" + "encoding/base64" "encoding/json" "errors" "path" @@ -31,6 +32,7 @@ import ( apiconfig "github.com/polarismesh/specification/source/go/api/v1/config_manage" apimodel "github.com/polarismesh/specification/source/go/api/v1/model" "go.uber.org/zap" + "google.golang.org/protobuf/types/known/wrapperspb" api "github.com/polarismesh/polaris/common/api/v1" "github.com/polarismesh/polaris/common/model" @@ -54,9 +56,9 @@ func (s *Server) CreateConfigFile(ctx context.Context, configFile *apiconfig.Con if err != nil { log.Error("[Config][Service] get config file error.", utils.ZapRequestID(requestID), - zap.String("namespace", namespace), - zap.String("group", group), - zap.String("name", name), + utils.ZapNamespace(namespace), + utils.ZapGroup(group), + utils.ZapFileName(name), zap.Error(err)) return api.NewConfigFileResponse(apimodel.Code_StoreLayerException, configFile) @@ -65,6 +67,19 @@ func (s *Server) CreateConfigFile(ctx context.Context, configFile *apiconfig.Con return api.NewConfigFileResponse(apimodel.Code_ExistedResource, configFile) } + // 配置加密 + if configFile.IsEncrypted.GetValue() && configFile.EncryptAlgo.GetValue() != "" { + if err := s.encryptConfigFile(ctx, configFile, configFile.EncryptAlgo.GetValue(), ""); err != nil { + log.Error("[Config][Service] encrypt config file error.", + utils.ZapRequestID(requestID), + utils.ZapNamespace(namespace), + utils.ZapGroup(group), + utils.ZapFileName(name), + zap.Error(err)) + return api.NewConfigFileResponse(apimodel.Code_EncryptConfigFileException, configFile) + } + } + fileStoreModel := transferConfigFileAPIModel2StoreModel(configFile) fileStoreModel.ModifyBy = fileStoreModel.CreateBy @@ -73,9 +88,9 @@ func (s *Server) CreateConfigFile(ctx context.Context, configFile *apiconfig.Con if err != nil { log.Error("[Config][Service] create config file error.", utils.ZapRequestID(requestID), - zap.String("namespace", namespace), - zap.String("group", group), - zap.String("name", name), + utils.ZapNamespace(namespace), + utils.ZapGroup(group), + utils.ZapFileName(name), zap.Error(err)) return api.NewConfigFileResponse(apimodel.Code_StoreLayerException, configFile) } @@ -89,13 +104,25 @@ func (s *Server) CreateConfigFile(ctx context.Context, configFile *apiconfig.Con // 创建成功 log.Info("[Config][Service] create config file success.", utils.ZapRequestID(requestID), - zap.String("namespace", namespace), - zap.String("group", group), - zap.String("name", name)) + utils.ZapNamespace(namespace), + utils.ZapGroup(group), + utils.ZapFileName(name)) s.RecordHistory(ctx, configFileRecordEntry(ctx, configFile, model.OCreate)) - return api.NewConfigFileResponse(apimodel.Code_ExecuteSuccess, transferConfigFileStoreModel2APIModel(createdFile)) + retConfigFile := transferConfigFileStoreModel2APIModel(createdFile) + + if err := s.decryptConfigFile(ctx, retConfigFile); err != nil { + log.Error("[Config][Service] decrypt config file error.", + utils.ZapRequestID(requestID), + utils.ZapNamespace(namespace), + utils.ZapGroup(group), + utils.ZapFileName(name), + zap.Error(err)) + return api.NewConfigFileResponse(apimodel.Code_DecryptConfigFileException, retConfigFile) + } + + return api.NewConfigFileResponse(apimodel.Code_ExecuteSuccess, retConfigFile) } func (s *Server) prepareCreateConfigFile(ctx context.Context, @@ -144,9 +171,9 @@ func (s *Server) GetConfigFileBaseInfo(ctx context.Context, namespace, group, na if err != nil { log.Error("[Config][Service] get config file error.", utils.ZapRequestIDByCtx(ctx), - zap.String("namespace", namespace), - zap.String("group", group), - zap.String("name", name), + utils.ZapNamespace(namespace), + utils.ZapGroup(group), + utils.ZapFileName(name), zap.Error(err)) return api.NewConfigFileResponse(apimodel.Code_StoreLayerException, nil) @@ -156,7 +183,17 @@ func (s *Server) GetConfigFileBaseInfo(ctx context.Context, namespace, group, na return api.NewConfigFileResponse(apimodel.Code_NotFoundResource, nil) } - return api.NewConfigFileResponse(apimodel.Code_ExecuteSuccess, transferConfigFileStoreModel2APIModel(file)) + retConfigFile := transferConfigFileStoreModel2APIModel(file) + if err := s.decryptConfigFile(ctx, retConfigFile); err != nil { + log.Error("[Config][Service] decrypt config file error.", + utils.ZapRequestIDByCtx(ctx), + utils.ZapNamespace(namespace), + utils.ZapGroup(group), + utils.ZapFileName(name), + zap.Error(err)) + return api.NewConfigFileResponse(apimodel.Code_DecryptConfigFileException, nil) + } + return api.NewConfigFileResponse(apimodel.Code_ExecuteSuccess, retConfigFile) } // GetConfigFileRichInfo 获取单个配置文件基础信息,包含发布状态等信息 @@ -167,9 +204,9 @@ func (s *Server) GetConfigFileRichInfo(ctx context.Context, namespace, group, na if configFileBaseInfoRsp.Code.GetValue() != uint32(apimodel.Code_ExecuteSuccess) { log.Error("[Config][Service] get config file release error.", utils.ZapRequestID(requestID), - zap.String("namespace", namespace), - zap.String("group", group), - zap.String("name", name)) + utils.ZapNamespace(namespace), + utils.ZapGroup(group), + utils.ZapFileName(name)) return api.NewConfigFileResponse(apimodel.Code(configFileBaseInfoRsp.Code.GetValue()), nil) } @@ -182,6 +219,15 @@ func (s *Server) GetConfigFileRichInfo(ctx context.Context, namespace, group, na return api.NewConfigFileResponse(apimodel.Code_StoreLayerException, nil) } + if err := s.decryptConfigFile(ctx, configFileBaseInfo); err != nil { + log.Error("[Config][Service] decrypt config file error.", + utils.ZapRequestIDByCtx(ctx), + utils.ZapNamespace(namespace), + utils.ZapGroup(group), + utils.ZapFileName(name), + zap.Error(err)) + return api.NewConfigFileResponse(apimodel.Code_DecryptConfigFileException, nil) + } return api.NewConfigFileResponse(apimodel.Code_ExecuteSuccess, configFileBaseInfo) } @@ -204,8 +250,8 @@ func (s *Server) QueryConfigFilesByGroup(ctx context.Context, namespace, group s if err != nil { log.Error("[Config][Service]get config files by group error.", utils.ZapRequestIDByCtx(ctx), - zap.String("namespace", namespace), - zap.String("group", group), + utils.ZapNamespace(namespace), + utils.ZapGroup(group), zap.Error(err)) return api.NewConfigFileBatchQueryResponse(apimodel.Code_StoreLayerException, 0, nil) @@ -222,9 +268,18 @@ func (s *Server) QueryConfigFilesByGroup(ctx context.Context, namespace, group s if err != nil { return api.NewConfigFileBatchQueryResponse(apimodel.Code_StoreLayerException, 0, nil) } + log.Error("[Config][Service] decrypt config file error.", zap.String("file", file.Name)) fileAPIModels = append(fileAPIModels, baseFile) } + if err := s.decryptMultiConfigFile(ctx, fileAPIModels); err != nil { + log.Error("[Config][Service] decrypt config file error.", + utils.ZapRequestIDByCtx(ctx), + utils.ZapNamespace(namespace), + utils.ZapGroup(group), + zap.Error(err)) + return api.NewConfigFileBatchQueryResponse(apimodel.Code_DecryptConfigFileException, 0, nil) + } return api.NewConfigFileBatchQueryResponse(apimodel.Code_ExecuteSuccess, count, fileAPIModels) } @@ -255,8 +310,8 @@ func (s *Server) SearchConfigFile(ctx context.Context, namespace, group, name, t if err != nil { log.Error("[Config][Service] query config file tags error.", utils.ZapRequestID(requestID), - zap.String("namespace", namespace), - zap.String("group", group), + utils.ZapNamespace(namespace), + utils.ZapGroup(group), zap.String("fileName", name), zap.Error(err)) return api.NewConfigFileBatchQueryResponse(apimodel.Code_StoreLayerException, 0, nil) @@ -273,6 +328,15 @@ func (s *Server) SearchConfigFile(ctx context.Context, namespace, group, name, t enrichedFiles = append(enrichedFiles, rsp.ConfigFile) } + if err := s.decryptMultiConfigFile(ctx, enrichedFiles); err != nil { + log.Error("[Config][Service] decrypt config file error.", + utils.ZapRequestIDByCtx(ctx), + utils.ZapNamespace(namespace), + utils.ZapGroup(group), + zap.Error(err)) + return api.NewConfigFileBatchQueryResponse(apimodel.Code_DecryptConfigFileException, 0, nil) + } + return api.NewConfigFileBatchQueryResponse(apimodel.Code_ExecuteSuccess, uint32(count), enrichedFiles) } @@ -283,9 +347,9 @@ func (s *Server) queryConfigFileWithoutTags(ctx context.Context, namespace, grou if err != nil { log.Error("[Config][Service]search config file error.", utils.ZapRequestID(requestID), - zap.String("namespace", namespace), - zap.String("group", group), - zap.String("name", name), + utils.ZapNamespace(namespace), + utils.ZapGroup(group), + utils.ZapFileName(name), zap.Error(err)) return api.NewConfigFileBatchQueryResponse(apimodel.Code_StoreLayerException, 0, nil) @@ -306,6 +370,16 @@ func (s *Server) queryConfigFileWithoutTags(ctx context.Context, namespace, grou fileAPIModels = append(fileAPIModels, baseFile) } + if err := s.decryptMultiConfigFile(ctx, fileAPIModels); err != nil { + log.Error("[Config][Service] decrypt config file error.", + utils.ZapRequestIDByCtx(ctx), + utils.ZapNamespace(namespace), + utils.ZapGroup(group), + utils.ZapFileName(name), + zap.Error(err)) + return api.NewConfigFileBatchQueryResponse(apimodel.Code_DecryptConfigFileException, 0, nil) + } + return api.NewConfigFileBatchQueryResponse(apimodel.Code_ExecuteSuccess, count, fileAPIModels) } @@ -325,9 +399,9 @@ func (s *Server) UpdateConfigFile(ctx context.Context, configFile *apiconfig.Con if err != nil { log.Error("[Config][Service] get config file error.", utils.ZapRequestID(requestID), - zap.String("namespace", namespace), - zap.String("group", group), - zap.String("name", name), + utils.ZapNamespace(namespace), + utils.ZapGroup(group), + utils.ZapFileName(name), zap.Error(err)) return api.NewConfigFileResponse(apimodel.Code_StoreLayerException, configFile) @@ -340,6 +414,23 @@ func (s *Server) UpdateConfigFile(ctx context.Context, configFile *apiconfig.Con userName := utils.ParseUserName(ctx) configFile.ModifyBy = utils.NewStringValue(userName) + // 配置加密 + algorithm, dataKey, err := s.getEncryptAlgorithmAndDataKey(ctx, namespace, group, name) + if err != nil { + return api.NewConfigFileResponse(apimodel.Code_StoreLayerException, configFile) + } + if dataKey != "" && algorithm != "" { + if err := s.encryptConfigFile(ctx, configFile, algorithm, dataKey); err != nil { + log.Error("[Config][Service] update encrypt config file error.", + utils.ZapRequestID(requestID), + utils.ZapNamespace(namespace), + utils.ZapGroup(group), + utils.ZapFileName(name), + zap.Error(err)) + return api.NewConfigFileResponse(apimodel.Code_EncryptConfigFileException, configFile) + } + } + toUpdateFile := transferConfigFileAPIModel2StoreModel(configFile) toUpdateFile.ModifyBy = configFile.ModifyBy.GetValue() @@ -351,9 +442,9 @@ func (s *Server) UpdateConfigFile(ctx context.Context, configFile *apiconfig.Con if err != nil { log.Error("[Config][Service] update config file error.", utils.ZapRequestID(requestID), - zap.String("namespace", namespace), - zap.String("group", group), - zap.String("name", name), + utils.ZapNamespace(namespace), + utils.ZapGroup(group), + utils.ZapFileName(name), zap.Error(err)) return api.NewConfigFileResponse(apimodel.Code_StoreLayerException, configFile) @@ -366,6 +457,24 @@ func (s *Server) UpdateConfigFile(ctx context.Context, configFile *apiconfig.Con baseFile := transferConfigFileStoreModel2APIModel(updatedFile) baseFile, err = s.fillReleaseAndTags(ctx, baseFile) + if err != nil { + log.Error("[Config][Service] update config file error.", + utils.ZapRequestIDByCtx(ctx), + utils.ZapNamespace(namespace), + utils.ZapGroup(group), + utils.ZapFileName(name), + zap.Error(err)) + return api.NewConfigFileResponse(apimodel.Code_StoreLayerException, configFile) + } + + if err := s.decryptConfigFile(ctx, baseFile); err != nil { + log.Error("[Config][Service] decrypt config file error.", + utils.ZapRequestIDByCtx(ctx), + utils.ZapNamespace(namespace), + utils.ZapGroup(group), + zap.Error(err)) + return api.NewConfigFileResponse(apimodel.Code_DecryptConfigFileException, configFile) + } s.RecordHistory(ctx, configFileRecordEntry(ctx, configFile, model.OUpdate)) @@ -391,17 +500,17 @@ func (s *Server) DeleteConfigFile( log.Info("[Config][Service] delete config file.", utils.ZapRequestID(requestID), - zap.String("namespace", namespace), - zap.String("group", group), - zap.String("name", name)) + utils.ZapNamespace(namespace), + utils.ZapGroup(group), + utils.ZapFileName(name)) file, err := s.storage.GetConfigFile(nil, namespace, group, name) if err != nil { log.Error("[Config][Service] get config file error.", utils.ZapRequestID(requestID), - zap.String("namespace", namespace), - zap.String("group", group), - zap.String("name", name), + utils.ZapNamespace(namespace), + utils.ZapGroup(group), + utils.ZapFileName(name), zap.Error(err)) return api.NewConfigFileResponse(apimodel.Code_StoreLayerException, nil) } @@ -427,9 +536,9 @@ func (s *Server) DeleteConfigFile( if err = s.storage.DeleteConfigFile(tx, namespace, group, name); err != nil { log.Error("[Config][Service] delete config file error.", utils.ZapRequestID(requestID), - zap.String("namespace", namespace), - zap.String("group", group), - zap.String("name", name), + utils.ZapNamespace(namespace), + utils.ZapGroup(group), + utils.ZapFileName(name), zap.Error(err)) return api.NewConfigFileResponse(apimodel.Code_StoreLayerException, nil) } @@ -438,9 +547,9 @@ func (s *Server) DeleteConfigFile( if err = s.deleteTagByConfigFile(newCtx, namespace, group, name); err != nil { log.Error("[Config][Service] delete config file tags error.", utils.ZapRequestID(requestID), - zap.String("namespace", namespace), - zap.String("group", group), - zap.String("name", name), + utils.ZapNamespace(namespace), + utils.ZapGroup(group), + utils.ZapFileName(name), zap.Error(err)) return api.NewConfigFileResponse(apimodel.Code_StoreLayerException, nil) } @@ -448,9 +557,9 @@ func (s *Server) DeleteConfigFile( if err := tx.Commit(); err != nil { log.Error("[Config][Service] commit delete config file tx error.", utils.ZapRequestID(requestID), - zap.String("namespace", namespace), - zap.String("group", group), - zap.String("name", name), + utils.ZapNamespace(namespace), + utils.ZapGroup(group), + utils.ZapFileName(name), zap.Error(err)) return api.NewConfigFileResponse(apimodel.Code_StoreLayerException, nil) } @@ -508,8 +617,8 @@ func (s *Server) ExportConfigFile(ctx context.Context, if err != nil { log.Error("[Config][Service] get config file by group error.", utils.ZapRequestIDByCtx(ctx), - zap.String("namespace", namespace), - zap.String("group", group), + utils.ZapNamespace(namespace), + utils.ZapGroup(group), zap.Error(err)) return api.NewConfigFileExportResponse(apimodel.Code_StoreLayerException, nil) } @@ -522,9 +631,9 @@ func (s *Server) ExportConfigFile(ctx context.Context, if err != nil { log.Error("[Config][Service] get config file error.", utils.ZapRequestIDByCtx(ctx), - zap.String("namespace", namespace), + utils.ZapNamespace(namespace), zap.String("group", groups[0]), - zap.String("name", name), + utils.ZapFileName(name), zap.Error(err)) return api.NewConfigFileExportResponse(apimodel.Code_StoreLayerException, nil) } @@ -533,7 +642,7 @@ func (s *Server) ExportConfigFile(ctx context.Context, } else { log.Error("[Config][Service] export config file error.", utils.ZapRequestIDByCtx(ctx), - zap.String("namespace", namespace), + utils.ZapNamespace(namespace), zap.String("groups", strings.Join(groups, ",")), zap.String("names", strings.Join(names, ","))) return api.NewConfigFileExportResponse(apimodel.Code_InvalidParameter, nil) @@ -554,7 +663,19 @@ func (s *Server) ExportConfigFile(ctx context.Context, zap.Error(err)) return api.NewConfigFileExportResponse(apimodel.Code_StoreLayerException, nil) } - fileID2Tags[file.Id] = tags + // 加密配置创建人可以导出加密密钥 + userName := utils.ParseUserName(ctx) + filterTags := make([]*model.ConfigFileTag, 0, len(tags)) + for _, tag := range tags { + if tag.Key == utils.ConfigFileTagKeyDataKey { + if userName == file.CreateBy { + filterTags = append(filterTags, tag) + } + } else { + filterTags = append(filterTags, tag) + } + } + fileID2Tags[file.Id] = filterTags } // 生成ZIP文件 buf, err := compressToZIP(configFiles, fileID2Tags, isExportGroup) @@ -596,9 +717,9 @@ func (s *Server) ImportConfigFile(ctx context.Context, if err != nil { log.Error("[Config][Service] get config file error.", utils.ZapRequestID(requestID), - zap.String("namespace", namespace), - zap.String("group", group), - zap.String("name", name), + utils.ZapNamespace(namespace), + utils.ZapGroup(group), + utils.ZapFileName(name), zap.Error(err)) return api.NewConfigFileImportResponse(apimodel.Code_StoreLayerException, nil, nil, nil) } @@ -612,9 +733,9 @@ func (s *Server) ImportConfigFile(ctx context.Context, if err != nil { log.Error("[Config][Service] update config file error.", utils.ZapRequestID(requestID), - zap.String("namespace", namespace), - zap.String("group", group), - zap.String("name", name), + utils.ZapNamespace(namespace), + utils.ZapGroup(group), + utils.ZapFileName(name), zap.Error(err)) return api.NewConfigFileImportResponse(apimodel.Code_StoreLayerException, nil, nil, nil) } @@ -630,9 +751,9 @@ func (s *Server) ImportConfigFile(ctx context.Context, if err != nil { log.Error("[Config][Service] create config file error.", utils.ZapRequestID(requestID), - zap.String("namespace", namespace), - zap.String("group", group), - zap.String("name", name), + utils.ZapNamespace(namespace), + utils.ZapGroup(group), + utils.ZapFileName(name), zap.Error(err)) return api.NewConfigFileImportResponse(apimodel.Code_StoreLayerException, nil, nil, nil) } @@ -808,8 +929,8 @@ func (s *Server) createOrUpdateConfigFileTags(ctx context.Context, configFile *a if err != nil { log.Error("[Config][Service] create or update config file tags error.", utils.ZapRequestIDByCtx(ctx), - zap.String("namespace", namespace), - zap.String("group", group), + utils.ZapNamespace(namespace), + utils.ZapGroup(group), zap.String("fileName", name), zap.Error(err)) return api.NewConfigFileResponse(apimodel.Code_StoreLayerException, configFile), false @@ -827,9 +948,9 @@ func (s *Server) fillReleaseAndTags(ctx context.Context, file *apiconfig.ConfigF if latestReleaseRsp.Code.GetValue() != uint32(apimodel.Code_ExecuteSuccess) { log.Error("[Config][Service] get config file latest release error.", utils.ZapRequestIDByCtx(ctx), - zap.String("namespace", namespace), - zap.String("group", group), - zap.String("name", name)) + utils.ZapNamespace(namespace), + utils.ZapGroup(group), + utils.ZapFileName(name)) return nil, errors.New("enrich config file release info error") } @@ -856,9 +977,9 @@ func (s *Server) fillReleaseAndTags(ctx context.Context, file *apiconfig.ConfigF if err != nil { log.Error("[Config][Service] create config file error.", utils.ZapRequestIDByCtx(ctx), - zap.String("namespace", namespace), - zap.String("group", group), - zap.String("name", name), + utils.ZapNamespace(namespace), + utils.ZapGroup(group), + utils.ZapFileName(name), zap.Error(err)) return nil, err } @@ -886,3 +1007,148 @@ func configFileRecordEntry(ctx context.Context, req *apiconfig.ConfigFile, return entry } + +// encryptConfigFile 加密配置文件 +func (s *Server) encryptConfigFile(ctx context.Context, + configFile *apiconfig.ConfigFile, algorithm string, dataKey string) error { + if s.cryptoManager == nil || configFile == nil { + return nil + } + crypto, err := s.cryptoManager.GetCrytpo(algorithm) + if err != nil { + return err + } + + content := configFile.Content.GetValue() + var dateKeyBytes []byte + if dataKey == "" { + dateKeyBytes, err = crypto.GenerateKey() + if err != nil { + return err + } + } else { + dateKeyBytes, err = base64.StdEncoding.DecodeString(dataKey) + if err != nil { + return err + } + } + cipherContent, err := crypto.Encrypt(content, dateKeyBytes) + if err != nil { + return err + } + configFile.Content = utils.NewStringValue(cipherContent) + tags := []*apiconfig.ConfigFileTag{ + { + Key: utils.NewStringValue(utils.ConfigFileTagKeyDataKey), + Value: utils.NewStringValue(base64.StdEncoding.EncodeToString(dateKeyBytes)), + }, + { + Key: utils.NewStringValue(utils.ConfigFileTagKeyEncryptAlgo), + Value: utils.NewStringValue(algorithm), + }, + } + configFile.Tags = append(configFile.Tags, tags...) + return nil +} + +// decryptConfigFile 解密配置文件 +func (s *Server) decryptConfigFile(ctx context.Context, configFile *apiconfig.ConfigFile) (err error) { + if s.cryptoManager == nil || configFile == nil { + return nil + } + var ( + algorithm string + dataKey string + ) + if len(configFile.Tags) == 0 { + algorithm, dataKey, err = s.getEncryptAlgorithmAndDataKey(ctx, + configFile.GetNamespace().GetValue(), + configFile.GetGroup().GetValue(), + configFile.GetName().GetValue()) + if err != nil { + return err + } + } else { + // 从Tags中获取DataKey并过滤掉该Tag + filterTags := make([]*apiconfig.ConfigFileTag, 0, len(configFile.Tags)) + for _, tag := range configFile.Tags { + if tag.Key.GetValue() == utils.ConfigFileTagKeyDataKey { + dataKey = tag.Value.GetValue() + } else if tag.Key.GetValue() == utils.ConfigFileTagKeyEncryptAlgo { + algorithm = tag.Value.GetValue() + filterTags = append(filterTags, tag) + } else { + filterTags = append(filterTags, tag) + } + } + configFile.Tags = filterTags + } + // 非创建人请求不解密 + if utils.ParseUserName(ctx) != configFile.CreateBy.GetValue() { + return nil + } + // 非加密文件不解密 + if dataKey == "" { + return nil + } + dateKeyBytes, err := base64.StdEncoding.DecodeString(dataKey) + if err != nil { + return err + } + crypto, err := s.cryptoManager.GetCrytpo(algorithm) + if err != nil { + return err + } + + // 解密 + plainContent, err := crypto.Decrypt(configFile.Content.GetValue(), dateKeyBytes) + if err != nil { + return err + } + configFile.Content = utils.NewStringValue(plainContent) + return nil +} + +// decryptBatchConfigFile 解密多个配置文件 +func (s *Server) decryptMultiConfigFile(ctx context.Context, configFiles []*apiconfig.ConfigFile) error { + for _, configFile := range configFiles { + if err := s.decryptConfigFile(ctx, configFile); err != nil { + return err + } + } + return nil +} + +// getConfigFileDataKey 获取加密配置文件数据密钥 +func (s *Server) getEncryptAlgorithmAndDataKey(ctx context.Context, + namespace, group, fileName string) (string, string, error) { + tags, err := s.queryTagsByConfigFileWithAPIModels(ctx, namespace, group, fileName) + if err != nil { + return "", "", err + } + var ( + algorithm string + dataKey string + ) + for _, tag := range tags { + if tag.Key.GetValue() == utils.ConfigFileTagKeyDataKey { + dataKey = tag.Value.GetValue() + } + if tag.Key.GetValue() == utils.ConfigFileTagKeyEncryptAlgo { + algorithm = tag.Value.GetValue() + } + } + return algorithm, dataKey, nil +} + +// GetAllConfigEncryptAlgorithms 获取配置加密算法 +func (s *Server) GetAllConfigEncryptAlgorithms(ctx context.Context) *apiconfig.ConfigEncryptAlgorithmResponse { + if s.cryptoManager == nil { + return api.NewConfigEncryptAlgorithmResponse(apimodel.Code_ExecuteSuccess, nil) + } + var algorithms []*wrapperspb.StringValue + for _, name := range s.cryptoManager.GetCryptoAlgoNames() { + algorithms = append(algorithms, utils.NewStringValue(name)) + } + return api.NewConfigEncryptAlgorithmResponse(apimodel.Code_ExecuteSuccess, algorithms) +} diff --git a/config/config_file_authibility.go b/config/config_file_authibility.go index 265a9708f..fc8439640 100644 --- a/config/config_file_authibility.go +++ b/config/config_file_authibility.go @@ -45,23 +45,73 @@ func (s *serverAuthability) CreateConfigFile(ctx context.Context, // GetConfigFileBaseInfo 获取配置文件,只返回基础元信息 func (s *serverAuthability) GetConfigFileBaseInfo(ctx context.Context, namespace, group, name string) *apiconfig.ConfigResponse { + configFile := &apiconfig.ConfigFile{ + Namespace: utils.NewStringValue(namespace), + Group: utils.NewStringValue(group), + Name: utils.NewStringValue(name), + } + authCtx := s.collectConfigFileAuthContext( + ctx, []*apiconfig.ConfigFile{configFile}, model.Read, "GetConfigFileBaseInfo") + if _, err := s.strategyMgn.GetAuthChecker().CheckConsolePermission(authCtx); err != nil { + return api.NewConfigFileResponseWithMessage(convertToErrCode(err), err.Error()) + } + ctx = authCtx.GetRequestContext() + ctx = context.WithValue(ctx, utils.ContextAuthContextKey, authCtx) + return s.targetServer.GetConfigFileBaseInfo(ctx, namespace, group, name) } // GetConfigFileRichInfo 获取单个配置文件基础信息,包含发布状态等信息 func (s *serverAuthability) GetConfigFileRichInfo(ctx context.Context, namespace, group, name string) *apiconfig.ConfigResponse { + configFile := &apiconfig.ConfigFile{ + Namespace: utils.NewStringValue(namespace), + Group: utils.NewStringValue(group), + Name: utils.NewStringValue(name), + } + authCtx := s.collectConfigFileAuthContext( + ctx, []*apiconfig.ConfigFile{configFile}, model.Read, "GetConfigFileRichInfo") + if _, err := s.strategyMgn.GetAuthChecker().CheckConsolePermission(authCtx); err != nil { + return api.NewConfigFileResponseWithMessage(convertToErrCode(err), err.Error()) + } + ctx = authCtx.GetRequestContext() + ctx = context.WithValue(ctx, utils.ContextAuthContextKey, authCtx) + return s.targetServer.GetConfigFileRichInfo(ctx, namespace, group, name) } func (s *serverAuthability) QueryConfigFilesByGroup(ctx context.Context, namespace, group string, offset, limit uint32) *apiconfig.ConfigBatchQueryResponse { + configFile := &apiconfig.ConfigFile{ + Namespace: utils.NewStringValue(namespace), + Group: utils.NewStringValue(group), + } + authCtx := s.collectConfigFileAuthContext( + ctx, []*apiconfig.ConfigFile{configFile}, model.Read, "QueryConfigFilesByGroup") + if _, err := s.strategyMgn.GetAuthChecker().CheckConsolePermission(authCtx); err != nil { + return api.NewConfigFileBatchQueryResponseWithMessage(convertToErrCode(err), err.Error()) + } + ctx = authCtx.GetRequestContext() + ctx = context.WithValue(ctx, utils.ContextAuthContextKey, authCtx) + return s.targetServer.QueryConfigFilesByGroup(ctx, namespace, group, offset, limit) } // SearchConfigFile 查询配置文件 func (s *serverAuthability) SearchConfigFile(ctx context.Context, namespace, group, name, tags string, offset, limit uint32) *apiconfig.ConfigBatchQueryResponse { + configFile := &apiconfig.ConfigFile{ + Namespace: utils.NewStringValue(namespace), + Group: utils.NewStringValue(group), + } + authCtx := s.collectConfigFileAuthContext( + ctx, []*apiconfig.ConfigFile{configFile}, model.Read, "SearchConfigFile") + if _, err := s.strategyMgn.GetAuthChecker().CheckConsolePermission(authCtx); err != nil { + return api.NewConfigFileBatchQueryResponseWithMessage(convertToErrCode(err), err.Error()) + } + ctx = authCtx.GetRequestContext() + ctx = context.WithValue(ctx, utils.ContextAuthContextKey, authCtx) + return s.targetServer.SearchConfigFile(ctx, namespace, group, name, tags, offset, limit) } @@ -115,6 +165,21 @@ func (s *serverAuthability) BatchDeleteConfigFile(ctx context.Context, configFil func (s *serverAuthability) ExportConfigFile(ctx context.Context, configFileExport *apiconfig.ConfigFileExportRequest) *apiconfig.ConfigExportResponse { + var configFiles []*apiconfig.ConfigFile + for _, group := range configFileExport.Groups { + configFile := &apiconfig.ConfigFile{ + Namespace: configFileExport.Namespace, + Group: group, + } + configFiles = append(configFiles, configFile) + } + authCtx := s.collectConfigFileAuthContext(ctx, configFiles, model.Read, "ExportConfigFile") + if _, err := s.strategyMgn.GetAuthChecker().CheckConsolePermission(authCtx); err != nil { + return api.NewConfigFileExportResponseWithMessage(convertToErrCode(err), err.Error()) + } + ctx = authCtx.GetRequestContext() + ctx = context.WithValue(ctx, utils.ContextAuthContextKey, authCtx) + return s.targetServer.ExportConfigFile(ctx, configFileExport) } @@ -129,3 +194,8 @@ func (s *serverAuthability) ImportConfigFile(ctx context.Context, ctx = context.WithValue(ctx, utils.ContextAuthContextKey, authCtx) return s.targetServer.ImportConfigFile(ctx, configFiles, conflictHandling) } + +func (s *serverAuthability) GetAllConfigEncryptAlgorithms( + ctx context.Context) *apiconfig.ConfigEncryptAlgorithmResponse { + return s.targetServer.GetAllConfigEncryptAlgorithms(ctx) +} diff --git a/config/config_file_release.go b/config/config_file_release.go index c2fe594b0..4f84113b6 100644 --- a/config/config_file_release.go +++ b/config/config_file_release.go @@ -19,6 +19,7 @@ package config import ( "context" + "encoding/base64" "time" "github.com/gogo/protobuf/jsonpb" @@ -207,7 +208,21 @@ func (s *Server) GetConfigFileRelease( return api.NewConfigFileResponse(apimodel.Code_StoreLayerException, nil) } - return api.NewConfigFileReleaseResponse(apimodel.Code_ExecuteSuccess, configFileRelease2Api(fileRelease)) + if fileRelease == nil { + return api.NewConfigFileReleaseResponse(apimodel.Code_ExecuteSuccess, nil) + } + // 解密发布纪录中配置 + release := configFileRelease2Api(fileRelease) + if err := s.decryptConfigFileRelease(ctx, release); err != nil { + log.Error("[Config][Service]get config file release error.", + utils.ZapRequestIDByCtx(ctx), + zap.String("namespace", namespace), + zap.String("group", group), + zap.String("fileName", fileName), + zap.Error(err)) + return api.NewConfigFileResponse(apimodel.Code_DecryptConfigFileException, nil) + } + return api.NewConfigFileReleaseResponse(apimodel.Code_ExecuteSuccess, release) } // DeleteConfigFileRelease 删除配置文件发布,删除配置文件的时候,同步删除配置文件发布数据 @@ -388,3 +403,37 @@ func configFileReleaseRecordEntry(ctx context.Context, req *apiconfig.ConfigFile return entry } + +// decryptConfigFileRelease 解密配置文件发布纪录 +func (s *Server) decryptConfigFileRelease(ctx context.Context, release *apiconfig.ConfigFileRelease) error { + if s.cryptoManager == nil || release == nil { + return nil + } + // 非创建人请求不解密 + if utils.ParseUserName(ctx) != release.CreateBy.GetValue() { + return nil + } + algorithm, dataKey, err := s.getEncryptAlgorithmAndDataKey(ctx, release.GetNamespace().GetValue(), + release.GetGroup().GetValue(), release.GetName().GetValue()) + if err != nil { + return err + } + if dataKey == "" { + return nil + } + dateKeyBytes, err := base64.StdEncoding.DecodeString(dataKey) + if err != nil { + return err + } + crypto, err := s.cryptoManager.GetCrytpo(algorithm) + if err != nil { + return err + } + + plainContent, err := crypto.Decrypt(release.Content.GetValue(), dateKeyBytes) + if err != nil { + return err + } + release.Content = utils.NewStringValue(plainContent) + return nil +} diff --git a/config/config_file_release_authibility.go b/config/config_file_release_authibility.go index 38ebf407b..e2ac0db37 100644 --- a/config/config_file_release_authibility.go +++ b/config/config_file_release_authibility.go @@ -47,7 +47,19 @@ func (s *serverAuthability) PublishConfigFile(ctx context.Context, // GetConfigFileRelease 获取配置文件发布内容 func (s *serverAuthability) GetConfigFileRelease(ctx context.Context, namespace, group, fileName string) *apiconfig.ConfigResponse { + configFileRelease := &apiconfig.ConfigFileRelease{ + Namespace: utils.NewStringValue(namespace), + Group: utils.NewStringValue(group), + FileName: utils.NewStringValue(fileName), + } + authCtx := s.collectConfigFileReleaseAuthContext(ctx, + []*apiconfig.ConfigFileRelease{configFileRelease}, model.Read, "GetConfigFileRelease") + if _, err := s.strategyMgn.GetAuthChecker().CheckConsolePermission(authCtx); err != nil { + return api.NewConfigFileResponseWithMessage(convertToErrCode(err), err.Error()) + } + ctx = authCtx.GetRequestContext() + ctx = context.WithValue(ctx, utils.ContextAuthContextKey, authCtx) return s.targetServer.GetConfigFileRelease(ctx, namespace, group, fileName) } diff --git a/config/config_file_release_history.go b/config/config_file_release_history.go index 01c8bec45..54df3348d 100644 --- a/config/config_file_release_history.go +++ b/config/config_file_release_history.go @@ -19,6 +19,7 @@ package config import ( "context" + "encoding/base64" apiconfig "github.com/polarismesh/specification/source/go/api/v1/config_manage" apimodel "github.com/polarismesh/specification/source/go/api/v1/model" @@ -47,6 +48,13 @@ func (s *Server) recordReleaseHistory(ctx context.Context, fileRelease *model.Co // 获取配置文件标签信息 tags, _ := s.queryTagsByConfigFileWithAPIModels(ctx, namespace, group, fileName) + // 过滤数据密钥 tag,不保存到发布历史中 + filterTags := make([]*apiconfig.ConfigFileTag, 0, len(tags)) + for _, tag := range tags { + if tag.Key.GetValue() != utils.ConfigFileTagKeyDataKey { + filterTags = append(filterTags, tag) + } + } releaseHistory := &model.ConfigFileReleaseHistory{ Name: fileRelease.Name, Namespace: namespace, @@ -54,7 +62,7 @@ func (s *Server) recordReleaseHistory(ctx context.Context, fileRelease *model.Co FileName: fileName, Content: fileRelease.Content, Format: format, - Tags: utils2.ToTagJsonStr(tags), + Tags: utils2.ToTagJsonStr(filterTags), Comment: fileRelease.Comment, Md5: fileRelease.Md5, Type: releaseType, @@ -106,6 +114,16 @@ func (s *Server) GetConfigFileReleaseHistory(ctx context.Context, namespace, gro apiReleaseHistory = append(apiReleaseHistory, historyAPIModel) } + if err := s.decryptMultiConfigFileReleaseHistory(ctx, apiReleaseHistory); err != nil { + log.Error("[Config][Service] decrypt config file release history error.", + utils.ZapRequestIDByCtx(ctx), + zap.String("namespace", namespace), + zap.String("group", group), + zap.String("name", fileName), + zap.Error(err)) + return api.NewConfigFileReleaseHistoryBatchQueryResponse(apimodel.Code_EncryptConfigFileException, 0, nil) + } + return api.NewConfigFileReleaseHistoryBatchQueryResponse(apimodel.Code_ExecuteSuccess, count, apiReleaseHistory) } @@ -137,9 +155,8 @@ func (s *Server) GetConfigFileLatestReleaseHistory(ctx context.Context, namespac ) return api.NewConfigFileReleaseHistoryResponse(apimodel.Code_StoreLayerException, nil) } - - return api.NewConfigFileReleaseHistoryResponse(apimodel.Code_ExecuteSuccess, - transferReleaseHistoryStoreModel2APIModel(history)) + apiHistory := transferReleaseHistoryStoreModel2APIModel(history) + return api.NewConfigFileReleaseHistoryResponse(apimodel.Code_ExecuteSuccess, apiHistory) } func transferReleaseHistoryStoreModel2APIModel( @@ -167,3 +184,50 @@ func transferReleaseHistoryStoreModel2APIModel( ModifyTime: utils.NewStringValue(time.Time2String(releaseHistory.ModifyTime)), } } + +// decryptMultiConfigFileReleaseHistory 解密多个配置文件发布历史 +func (s *Server) decryptMultiConfigFileReleaseHistory(ctx context.Context, + releaseHistories []*apiconfig.ConfigFileReleaseHistory) error { + for _, releaseHistory := range releaseHistories { + if err := s.decryptConfigFileReleaseHistory(ctx, releaseHistory); err != nil { + return err + } + } + return nil +} + +// decryptConfigFileReleaseHistory 解密配置文件发布历史 +func (s *Server) decryptConfigFileReleaseHistory(ctx context.Context, + releaseHistory *apiconfig.ConfigFileReleaseHistory) error { + if s.cryptoManager == nil || releaseHistory == nil { + return nil + } + // 非创建人请求不解密 + if utils.ParseUserName(ctx) != releaseHistory.CreateBy.GetValue() { + return nil + } + algorithm, dataKey, err := s.getEncryptAlgorithmAndDataKey(ctx, releaseHistory.GetNamespace().GetValue(), + releaseHistory.GetGroup().GetValue(), releaseHistory.GetName().GetValue()) + if err != nil { + return err + } + // 非加密文件不解密 + if dataKey == "" { + return nil + } + dateKeyBytes, err := base64.StdEncoding.DecodeString(dataKey) + if err != nil { + return err + } + crypto, err := s.cryptoManager.GetCrytpo(algorithm) + if err != nil { + return err + } + // 解密 + plainContent, err := crypto.Decrypt(releaseHistory.Content.GetValue(), dateKeyBytes) + if err != nil { + return err + } + releaseHistory.Content = utils.NewStringValue(plainContent) + return nil +} diff --git a/config/config_file_release_history_authibility.go b/config/config_file_release_history_authibility.go index 9fd769a2e..ebac1b379 100644 --- a/config/config_file_release_history_authibility.go +++ b/config/config_file_release_history_authibility.go @@ -21,11 +21,28 @@ import ( "context" apiconfig "github.com/polarismesh/specification/source/go/api/v1/config_manage" + + api "github.com/polarismesh/polaris/common/api/v1" + "github.com/polarismesh/polaris/common/model" + "github.com/polarismesh/polaris/common/utils" ) // GetConfigFileReleaseHistory 获取配置文件发布历史记录 func (s *serverAuthability) GetConfigFileReleaseHistory(ctx context.Context, namespace, group, fileName string, offset, limit uint32, endId uint64) *apiconfig.ConfigBatchQueryResponse { + configFileReleaseHistory := &apiconfig.ConfigFileReleaseHistory{ + Namespace: utils.NewStringValue(namespace), + Group: utils.NewStringValue(group), + FileName: utils.NewStringValue(fileName), + } + authCtx := s.collectConfigFileReleaseHistoryAuthContext(ctx, + []*apiconfig.ConfigFileReleaseHistory{configFileReleaseHistory}, model.Read, "GetConfigFileReleaseHistory") + + if _, err := s.strategyMgn.GetAuthChecker().CheckConsolePermission(authCtx); err != nil { + return api.NewConfigFileBatchQueryResponseWithMessage(convertToErrCode(err), err.Error()) + } + ctx = authCtx.GetRequestContext() + ctx = context.WithValue(ctx, utils.ContextAuthContextKey, authCtx) return s.targetServer.GetConfigFileReleaseHistory(ctx, namespace, group, fileName, offset, limit, endId) } @@ -33,6 +50,18 @@ func (s *serverAuthability) GetConfigFileReleaseHistory(ctx context.Context, nam // GetConfigFileLatestReleaseHistory 获取配置文件最后一次发布记录 func (s *serverAuthability) GetConfigFileLatestReleaseHistory(ctx context.Context, namespace, group, fileName string) *apiconfig.ConfigResponse { + configFileReleaseHistory := &apiconfig.ConfigFileReleaseHistory{ + Namespace: utils.NewStringValue(namespace), + Group: utils.NewStringValue(group), + FileName: utils.NewStringValue(fileName), + } + authCtx := s.collectConfigFileReleaseHistoryAuthContext(ctx, + []*apiconfig.ConfigFileReleaseHistory{configFileReleaseHistory}, model.Read, "GetConfigFileLatestReleaseHistory") + if _, err := s.strategyMgn.GetAuthChecker().CheckConsolePermission(authCtx); err != nil { + return api.NewConfigFileReleaseResponseWithMessage(convertToErrCode(err), err.Error()) + } + ctx = authCtx.GetRequestContext() + ctx = context.WithValue(ctx, utils.ContextAuthContextKey, authCtx) return s.targetServer.GetConfigFileLatestReleaseHistory(ctx, namespace, group, fileName) } diff --git a/config/config_file_test.go b/config/config_file_test.go index 8f55b96b0..ad59477bc 100644 --- a/config/config_file_test.go +++ b/config/config_file_test.go @@ -19,15 +19,17 @@ package config import ( "context" + "encoding/base64" + "encoding/hex" "fmt" "testing" "github.com/golang/protobuf/ptypes/wrappers" - apiconfig "github.com/polarismesh/specification/source/go/api/v1/config_manage" - "github.com/stretchr/testify/assert" - api "github.com/polarismesh/polaris/common/api/v1" "github.com/polarismesh/polaris/common/utils" + apiconfig "github.com/polarismesh/specification/source/go/api/v1/config_manage" + "github.com/stretchr/testify/assert" + "google.golang.org/protobuf/types/known/wrapperspb" ) var ( @@ -372,6 +374,37 @@ func TestConfigFileCRUD(t *testing.T) { assert.Equal(t, 0, len(rsp.SkipConfigFiles)) assert.Equal(t, 2, len(rsp.OverwriteConfigFiles)) }) + + t.Run("step12-create-entrypted", func(t *testing.T) { + configFile := assembleConfigFile() + configFile.IsEncrypted = utils.NewBoolValue(true) + configFile.EncryptAlgo = utils.NewStringValue("AES") + rsp := testSuit.testService.CreateConfigFile(testSuit.defaultCtx, configFile) + assert.Equal(t, api.ExecuteSuccess, rsp.Code.GetValue()) + assert.Equal(t, testNamespace, rsp.ConfigFile.Namespace.GetValue()) + assert.Equal(t, testGroup, rsp.ConfigFile.Group.GetValue()) + assert.Equal(t, testFile, rsp.ConfigFile.Name.GetValue()) + assert.NotEqual(t, configFile.Content.GetValue(), rsp.ConfigFile.Content.GetValue()) + assert.Equal(t, configFile.Format.GetValue(), rsp.ConfigFile.Format.GetValue()) + assert.Equal(t, operator, rsp.ConfigFile.CreateBy.GetValue()) + assert.Equal(t, operator, rsp.ConfigFile.ModifyBy.GetValue()) + + // 重复创建 + rsp2 := testSuit.testService.CreateConfigFile(testSuit.defaultCtx, configFile) + assert.Equal(t, uint32(api.ExistedResource), rsp2.Code.GetValue()) + + // 创建完之后再查询 + rsp3 := testSuit.testService.GetConfigFileBaseInfo(testSuit.defaultCtx, testNamespace, testGroup, testFile) + assert.Equal(t, api.ExecuteSuccess, rsp3.Code.GetValue()) + assert.NotNil(t, rsp.ConfigFile) + assert.Equal(t, testNamespace, rsp.ConfigFile.Namespace.GetValue()) + assert.Equal(t, testGroup, rsp.ConfigFile.Group.GetValue()) + assert.Equal(t, testFile, rsp.ConfigFile.Name.GetValue()) + assert.NotEqual(t, configFile.Content.GetValue(), rsp.ConfigFile.Content.GetValue()) + assert.Equal(t, configFile.Format.GetValue(), rsp.ConfigFile.Format.GetValue()) + assert.Equal(t, operator, rsp.ConfigFile.CreateBy.GetValue()) + assert.Equal(t, operator, rsp.ConfigFile.ModifyBy.GetValue()) + }) } // TestPublishConfigFile 测试配置文件发布相关的用例 @@ -453,3 +486,197 @@ func TestPublishConfigFile(t *testing.T) { assert.Equal(t, 2, len(rsp9.ConfigFileReleaseHistories)) } + +func TestServer_encryptConfigFile(t *testing.T) { + testSuit, err := newConfigCenterTest(t) + if err != nil { + t.Fatal(err) + } + + defer func() { + if err := testSuit.clearTestData(); err != nil { + t.Fatal(err) + } + }() + type args struct { + ctx context.Context + algorithm string + configFile *apiconfig.ConfigFile + dataKey string + } + dataKey, _ := hex.DecodeString("777b162a185673cb1b72b467a78221cd") + fmt.Println(base64.StdEncoding.EncodeToString(dataKey)) + + tests := []struct { + name string + args args + want string + wantErr error + }{ + { + name: "encrypt config file", + args: args{ + ctx: context.Background(), + algorithm: "AES", + configFile: &apiconfig.ConfigFile{ + Content: utils.NewStringValue("polaris"), + }, + dataKey: "", + }, + wantErr: nil, + }, + { + name: "encrypt config file with dataKey", + args: args{ + ctx: context.Background(), + algorithm: "AES", + configFile: &apiconfig.ConfigFile{ + Content: utils.NewStringValue("polaris"), + }, + dataKey: base64.StdEncoding.EncodeToString(dataKey), + }, + want: "YnLZ0SYuujFBHjYHAZVN5A==", + wantErr: nil, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + s := testSuit.testServer + err := s.encryptConfigFile(tt.args.ctx, tt.args.configFile, tt.args.algorithm, tt.args.dataKey) + assert.Equal(t, tt.wantErr, err) + if tt.want != "" { + assert.Equal(t, tt.want, tt.args.configFile.Content.GetValue()) + } + hasDataKeyTag := false + hasAlgoTag := false + for _, tag := range tt.args.configFile.Tags { + if tag.Key.GetValue() == utils.ConfigFileTagKeyDataKey { + hasDataKeyTag = true + if tt.args.dataKey != "" { + assert.Equal(t, tt.args.dataKey, tag.Value.GetValue()) + } + } + if tag.Key.GetValue() == utils.ConfigFileTagKeyEncryptAlgo { + hasAlgoTag = true + assert.Equal(t, tt.args.algorithm, tag.Value.GetValue()) + } + } + assert.True(t, hasDataKeyTag) + assert.True(t, hasAlgoTag) + }) + } +} + +func TestServer_decryptConfigFile(t *testing.T) { + testSuit, err := newConfigCenterTest(t) + if err != nil { + t.Fatal(err) + } + + defer func() { + if err := testSuit.clearTestData(); err != nil { + t.Fatal(err) + } + }() + type args struct { + ctx context.Context + configFile *apiconfig.ConfigFile + } + + dataKey, _ := hex.DecodeString("777b162a185673cb1b72b467a78221cd") + + tests := []struct { + name string + args args + want string + wantErr error + }{ + { + name: "decrypt config file", + args: args{ + ctx: context.WithValue(context.Background(), utils.ContextUserNameKey, "polaris"), + configFile: &apiconfig.ConfigFile{ + Content: utils.NewStringValue("YnLZ0SYuujFBHjYHAZVN5A=="), + Tags: []*apiconfig.ConfigFileTag{ + { + Key: utils.NewStringValue(utils.ConfigFileTagKeyDataKey), + Value: utils.NewStringValue(base64.StdEncoding.EncodeToString(dataKey)), + }, + { + Key: utils.NewStringValue(utils.ConfigFileTagKeyEncryptAlgo), + Value: utils.NewStringValue("AES"), + }, + }, + CreateBy: utils.NewStringValue("polaris"), + }, + }, + want: "polaris", + wantErr: nil, + }, + { + name: "non creator don't decrypt config file", + args: args{ + ctx: context.WithValue(context.Background(), utils.ContextUserNameKey, "test"), + configFile: &apiconfig.ConfigFile{ + Content: utils.NewStringValue("YnLZ0SYuujFBHjYHAZVN5A=="), + Tags: []*apiconfig.ConfigFileTag{ + { + Key: utils.NewStringValue(utils.ConfigFileTagKeyDataKey), + Value: utils.NewStringValue(base64.StdEncoding.EncodeToString(dataKey)), + }, + { + Key: utils.NewStringValue(utils.ConfigFileTagKeyEncryptAlgo), + Value: utils.NewStringValue("AES"), + }, + }, + CreateBy: utils.NewStringValue("polaris"), + }, + }, + want: "YnLZ0SYuujFBHjYHAZVN5A==", + wantErr: nil, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + s := testSuit.testServer + err := s.decryptConfigFile(tt.args.ctx, tt.args.configFile) + assert.Equal(t, tt.wantErr, err) + assert.Equal(t, tt.want, tt.args.configFile.Content.GetValue()) + for _, tag := range tt.args.configFile.Tags { + if tag.Key.GetValue() == utils.ConfigFileTagKeyDataKey { + t.Fatal("config tags has data key") + } + } + }) + } +} + +func TestServer_GetConfigEncryptAlgorithm(t *testing.T) { + testSuit, err := newConfigCenterTest(t) + if err != nil { + t.Fatal(err) + } + defer func() { + if err := testSuit.clearTestData(); err != nil { + t.Fatal(err) + } + }() + tests := []struct { + name string + want []*wrapperspb.StringValue + }{ + { + name: "get config encrypt algorithm", + want: []*wrapperspb.StringValue{ + utils.NewStringValue("AES"), + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + rsp := testSuit.testService.GetAllConfigEncryptAlgorithms(testSuit.defaultCtx) + assert.Equal(t, api.ExecuteSuccess, rsp.Code.GetValue()) + assert.Equal(t, tt.want, rsp.GetAlgorithms()) + }) + } +} diff --git a/config/server.go b/config/server.go index 093eca6b4..f04daf5d0 100644 --- a/config/server.go +++ b/config/server.go @@ -62,8 +62,9 @@ type Server struct { namespaceOperator namespace.NamespaceOperateServer initialized bool - history plugin.History - hooks []ResourceHook + history plugin.History + cryptoManager *plugin.CryptoManager + hooks []ResourceHook } // Initialize 初始化配置中心模块 @@ -109,6 +110,11 @@ func (s *Server) initialize(ctx context.Context, config Config, ss store.Store, if s.history == nil { log.Warnf("Not Found History Log Plugin") } + // 获取Crypto插件 + s.cryptoManager = plugin.GetCryptoManager() + if s.cryptoManager == nil { + log.Warnf("Not Found Crypto Plugin") + } // 初始化发布事件扫描器 if err := initReleaseMessageScanner(ctx, ss, s.fileCache, eventCenter, time.Second); err != nil { diff --git a/config/server_authability.go b/config/server_authability.go index 902ef78dd..82c25257d 100644 --- a/config/server_authability.go +++ b/config/server_authability.go @@ -72,6 +72,19 @@ func (s *serverAuthability) collectConfigFileReleaseAuthContext(ctx context.Cont ) } +func (s *serverAuthability) collectConfigFileReleaseHistoryAuthContext( + ctx context.Context, + req []*apiconfig.ConfigFileReleaseHistory, + op model.ResourceOperation, methodName string) *model.AcquireContext { + return model.NewAcquireContext( + model.WithRequestContext(ctx), + model.WithModule(model.ConfigModule), + model.WithOperation(op), + model.WithMethod(methodName), + model.WithAccessResources(s.queryConfigFileReleaseHistoryResource(ctx, req)), + ) +} + func (s *serverAuthability) collectConfigGroupAuthContext(ctx context.Context, req []*apiconfig.ConfigFileGroup, op model.ResourceOperation, methodName string) *model.AcquireContext { return model.NewAcquireContext( @@ -166,6 +179,32 @@ func (s *serverAuthability) queryConfigFileReleaseResource(ctx context.Context, return ret } +func (s *serverAuthability) queryConfigFileReleaseHistoryResource(ctx context.Context, + req []*apiconfig.ConfigFileReleaseHistory) map[apisecurity.ResourceType][]model.ResourceEntry { + + if len(req) == 0 { + return nil + } + namespace := req[0].Namespace.GetValue() + groupNames := utils.NewStringSet() + + for _, apiConfigFile := range req { + groupNames.Add(apiConfigFile.Group.GetValue()) + } + entries, err := s.queryConfigGroupRsEntryByNames(ctx, namespace, groupNames.ToSlice()) + if err != nil { + authLog.Debug("[Config][Server] collect config_file res", + utils.ZapRequestIDByCtx(ctx), zap.Error(err)) + return nil + } + ret := map[apisecurity.ResourceType][]model.ResourceEntry{ + apisecurity.ResourceType_ConfigGroups: entries, + } + authLog.Debug("[Config][Server] collect config_file access res", + utils.ZapRequestIDByCtx(ctx), zap.Any("res", ret)) + return ret +} + func (s *serverAuthability) queryConfigGroupRsEntryByNames(ctx context.Context, namespace string, names []string) ([]model.ResourceEntry, error) { diff --git a/config/utils/utils.go b/config/utils/utils.go index c17cd4e04..82281b45d 100644 --- a/config/utils/utils.go +++ b/config/utils/utils.go @@ -96,15 +96,17 @@ func CheckContentLength(content string) error { } // GenConfigFileResponse 为客户端生成响应对象 -func GenConfigFileResponse( - namespace, group, fileName, content, md5str string, version uint64) *apiconfig.ConfigClientResponse { +func GenConfigFileResponse(namespace, group, fileName, content, md5str string, + version uint64, isEncrypted bool, dataKey string) *apiconfig.ConfigClientResponse { configFile := &apiconfig.ClientConfigFileInfo{ - Namespace: utils.NewStringValue(namespace), - Group: utils.NewStringValue(group), - FileName: utils.NewStringValue(fileName), - Content: utils.NewStringValue(content), - Version: utils.NewUInt64Value(version), - Md5: utils.NewStringValue(md5str), + Namespace: utils.NewStringValue(namespace), + Group: utils.NewStringValue(group), + FileName: utils.NewStringValue(fileName), + Content: utils.NewStringValue(content), + Version: utils.NewUInt64Value(version), + Md5: utils.NewStringValue(md5str), + IsEncrypted: utils.NewBoolValue(isEncrypted), + DataKey: utils.NewStringValue(dataKey), } return api.NewConfigClientResponse(apimodel.Code_ExecuteSuccess, configFile) } diff --git a/config/watcher.go b/config/watcher.go index 5be09e3ad..ceea7cb5f 100644 --- a/config/watcher.go +++ b/config/watcher.go @@ -147,7 +147,7 @@ func (wc *watchCenter) notifyToWatchers(publishConfigFile *model.ConfigFileRelea } response := utils2.GenConfigFileResponse(publishConfigFile.Namespace, publishConfigFile.Group, - publishConfigFile.FileName, "", publishConfigFile.Md5, publishConfigFile.Version) + publishConfigFile.FileName, "", publishConfigFile.Md5, publishConfigFile.Version, false, "") watcherMap := watchers.(*sync.Map) watcherMap.Range(func(clientId, watchCtx interface{}) bool { diff --git a/go.mod b/go.mod index f1ce81c74..e9516ad45 100644 --- a/go.mod +++ b/go.mod @@ -25,7 +25,7 @@ require ( github.com/prometheus/client_golang v1.12.2 github.com/smartystreets/goconvey v1.6.4 github.com/spf13/cobra v1.2.1 - github.com/stretchr/testify v1.8.0 + github.com/stretchr/testify v1.8.2 go.uber.org/atomic v1.10.0 go.uber.org/automaxprocs v1.4.0 go.uber.org/zap v1.23.0 @@ -78,13 +78,14 @@ require ( go.uber.org/multierr v1.8.0 // indirect golang.org/x/sys v0.2.0 // indirect google.golang.org/genproto v0.0.0-20221014213838-99cd37c6964a // indirect - gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) require ( github.com/DATA-DOG/go-sqlmock v1.5.0 - github.com/polarismesh/specification v1.3.0 + github.com/polarismesh/specification v1.3.1 ) +require gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect + replace gopkg.in/yaml.v2 => gopkg.in/yaml.v2 v2.2.2 diff --git a/go.sum b/go.sum index 9828d3920..3e392ae47 100644 --- a/go.sum +++ b/go.sum @@ -321,8 +321,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/polarismesh/go-restful-openapi/v2 v2.0.0-20220928152401-083908d10219 h1:XnFyNUWnciM6zgXaz6tm+Egs35rhoD0KGMmKh4gCdi0= github.com/polarismesh/go-restful-openapi/v2 v2.0.0-20220928152401-083908d10219/go.mod h1:4WhwBysTom9Eoy0hQ4W69I0FmO+T0EpjEW9/5sgHoUk= -github.com/polarismesh/specification v1.3.0 h1:O8Lo39Tz6QxhGFX1lLoAG+z1Rthp/+LMTUWBilcXONM= -github.com/polarismesh/specification v1.3.0/go.mod h1:rDvMMtl5qebPmqiBLNa5Ps0XtwkP31ZLirbH4kXA0YU= +github.com/polarismesh/specification v1.3.1 h1:smBI3hMYJFt20dlVGEryJKVoydWe9D1ZoxMhj88+l74= +github.com/polarismesh/specification v1.3.1/go.mod h1:rDvMMtl5qebPmqiBLNa5Ps0XtwkP31ZLirbH4kXA0YU= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= @@ -373,6 +373,7 @@ github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= @@ -380,8 +381,9 @@ github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5 github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= +github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= diff --git a/plugin.go b/plugin.go index 8d550ae60..3f3740cb9 100644 --- a/plugin.go +++ b/plugin.go @@ -27,6 +27,7 @@ import ( _ "github.com/polarismesh/polaris/auth/defaultauth" _ "github.com/polarismesh/polaris/cache" _ "github.com/polarismesh/polaris/plugin/cmdb/memory" + _ "github.com/polarismesh/polaris/plugin/crypto/aes" _ "github.com/polarismesh/polaris/plugin/discoverevent/local" _ "github.com/polarismesh/polaris/plugin/healthchecker/leader" _ "github.com/polarismesh/polaris/plugin/healthchecker/memory" diff --git a/plugin/crypto.go b/plugin/crypto.go new file mode 100644 index 000000000..3c1e457f2 --- /dev/null +++ b/plugin/crypto.go @@ -0,0 +1,125 @@ +/** + * Tencent is pleased to support the open source community by making Polaris available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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 plugin + +import ( + "fmt" + "os" + "sync" +) + +var ( + cryptoManagerOnce sync.Once + cryptoManager *CryptoManager +) + +// Crypto Crypto interface +type Crypto interface { + Plugin + GenerateKey() ([]byte, error) + Encrypt(plaintext string, key []byte) (cryptotext string, err error) + Decrypt(cryptotext string, key []byte) (string, error) +} + +// GetCrypto get the crypto plugin +func GetCryptoManager() *CryptoManager { + if cryptoManager != nil { + return cryptoManager + } + + cryptoManagerOnce.Do(func() { + var ( + entries []ConfigEntry + ) + if len(config.Crypto.Entries) != 0 { + entries = append(entries, config.Crypto.Entries...) + } else { + entries = append(entries, ConfigEntry{ + Name: config.Crypto.Name, + Option: config.Crypto.Option, + }) + } + cryptoManager = &CryptoManager{ + cryptos: make(map[string]Crypto), + options: entries, + } + + if err := cryptoManager.Initialize(); err != nil { + log.Errorf("Crypto plugin init err: %s", err.Error()) + os.Exit(-1) + } + }) + return cryptoManager +} + +// CryptoManager crypto algorithm manager +type CryptoManager struct { + cryptos map[string]Crypto + options []ConfigEntry +} + +func (c *CryptoManager) Name() string { + return "CryptoManager" +} + +func (c *CryptoManager) Initialize() error { + for i := range c.options { + entry := c.options[i] + item, exist := pluginSet[entry.Name] + if !exist { + log.Errorf("plugin Crypto not found target: %s", entry.Name) + continue + } + crypto, ok := item.(Crypto) + if !ok { + log.Errorf("plugin target: %s not Crypto", entry.Name) + continue + } + if err := crypto.Initialize(&entry); err != nil { + return err + } + c.cryptos[entry.Name] = crypto + } + return nil +} + +func (c *CryptoManager) Destroy() error { + for i := range c.cryptos { + if err := c.cryptos[i].Destroy(); err != nil { + return err + } + } + return nil +} + +func (c *CryptoManager) GetCryptoAlgoNames() []string { + var names []string + for name := range c.cryptos { + names = append(names, name) + } + return names +} + +func (c *CryptoManager) GetCrytpo(algo string) (Crypto, error) { + crypto, ok := c.cryptos[algo] + if !ok { + log.Errorf("plugin Crypto not found target: %s", algo) + return nil, fmt.Errorf("plugin Crypto not found target: %s", algo) + } + return crypto, nil +} diff --git a/plugin/crypto/aes/aes.go b/plugin/crypto/aes/aes.go new file mode 100644 index 000000000..2dcff19d5 --- /dev/null +++ b/plugin/crypto/aes/aes.go @@ -0,0 +1,135 @@ +/** + * Tencent is pleased to support the open source community by making Polaris available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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 aes + +import ( + "bytes" + "crypto/aes" + "crypto/cipher" + "crypto/rand" + "encoding/base64" + "errors" + + "github.com/polarismesh/polaris/plugin" +) + +const ( + // PluginName plugin name + PluginName = "AES" +) + +func init() { + plugin.RegisterPlugin(PluginName, &AESCrypto{}) +} + +// AESCrypto AES crypto +type AESCrypto struct { +} + +// Name 返回插件名字 +func (h *AESCrypto) Name() string { + return PluginName +} + +// Destroy 销毁插件 +func (h *AESCrypto) Destroy() error { + return nil +} + +// Initialize 插件初始化 +func (h *AESCrypto) Initialize(c *plugin.ConfigEntry) error { + return nil +} + +// GenerateKey generate key +func (c *AESCrypto) GenerateKey() ([]byte, error) { + key := make([]byte, 16) + _, err := rand.Read(key) + if err != nil { + return nil, err + } + return key, nil +} + +// Encrypt AES encrypt plaintext and base64 encode ciphertext +func (c *AESCrypto) Encrypt(plaintext string, key []byte) (string, error) { + ciphertext, err := c.encrypt([]byte(plaintext), key) + if err != nil { + return "", err + } + return base64.StdEncoding.EncodeToString(ciphertext), nil +} + +// Decrypt base64 decode ciphertext and AES decrypt +func (c *AESCrypto) Decrypt(ciphertext string, key []byte) (string, error) { + ciphertextBytes, err := base64.StdEncoding.DecodeString(ciphertext) + if err != nil { + return "", err + } + plaintext, err := c.decrypt(ciphertextBytes, key) + if err != nil { + return "", err + } + return string(plaintext), nil +} + +// Encrypt AES encryption +func (c *AESCrypto) encrypt(plaintext []byte, key []byte) ([]byte, error) { + block, err := aes.NewCipher(key) + if err != nil { + return nil, err + } + blockSize := block.BlockSize() + paddingData := pkcs7Padding(plaintext, blockSize) + ciphertext := make([]byte, len(paddingData)) + blockMode := cipher.NewCBCEncrypter(block, key[:blockSize]) + blockMode.CryptBlocks(ciphertext, paddingData) + return ciphertext, nil +} + +// Decrypt AES decryption +func (c *AESCrypto) decrypt(ciphertext []byte, key []byte) ([]byte, error) { + block, err := aes.NewCipher(key) + if err != nil { + return nil, err + } + blockSize := block.BlockSize() + blockMode := cipher.NewCBCDecrypter(block, key[:blockSize]) + paddingPlaintext := make([]byte, len(ciphertext)) + blockMode.CryptBlocks(paddingPlaintext, ciphertext) + plaintext, err := pkcs7UnPadding(paddingPlaintext) + if err != nil { + return nil, err + } + return plaintext, nil +} + +func pkcs7Padding(data []byte, blockSize int) []byte { + padding := blockSize - len(data)%blockSize + padText := bytes.Repeat([]byte{byte(padding)}, padding) + return append(data, padText...) +} + +func pkcs7UnPadding(data []byte) ([]byte, error) { + length := len(data) + if length == 0 { + return nil, errors.New("invalid encryption data") + } + unPadding := int(data[length-1]) + return data[:(length - unPadding)], nil +} diff --git a/plugin/crypto/aes/aes_test.go b/plugin/crypto/aes/aes_test.go new file mode 100644 index 000000000..5fae98555 --- /dev/null +++ b/plugin/crypto/aes/aes_test.go @@ -0,0 +1,110 @@ +/** + * Tencent is pleased to support the open source community by making Polaris available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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 aes + +import ( + "encoding/hex" + "testing" + + "github.com/stretchr/testify/assert" +) + +func Test_AESCrypto_GenerateKey(t *testing.T) { + tests := []struct { + name string + keyLen int + err error + }{ + { + name: "genrate aes key", + keyLen: 16, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := &AESCrypto{} + got, err := c.GenerateKey() + assert.Nil(t, err) + assert.Equal(t, tt.keyLen, len(got)) + t.Logf("%x", got) + t.Log(hex.EncodeToString(got)) + }) + } +} + +func Test_AESCrypto_EncryptToBase64(t *testing.T) { + type args struct { + plaintext string + key []byte + } + key, _ := hex.DecodeString("777b162a185673cb1b72b467a78221cd") + tests := []struct { + name string + args args + want string + wangErr error + }{ + { + name: "encrypt to base64", + args: args{ + plaintext: "polaris", + key: key, + }, + want: "YnLZ0SYuujFBHjYHAZVN5A==", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := &AESCrypto{} + ciphertext, err := c.Encrypt(tt.args.plaintext, tt.args.key) + assert.Equal(t, tt.want, ciphertext) + assert.Equal(t, tt.wangErr, err) + }) + } +} + +func Test_AESCrypto_DecryptFromBase64(t *testing.T) { + type args struct { + base64Ciphertext string + key []byte + } + key, _ := hex.DecodeString("777b162a185673cb1b72b467a78221cd") + tests := []struct { + name string + args args + want string + wantErr error + }{ + { + name: "decrypt from base64", + args: args{ + base64Ciphertext: "YnLZ0SYuujFBHjYHAZVN5A==", + key: key, + }, + want: "polaris", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := &AESCrypto{} + got, err := c.Decrypt(tt.args.base64Ciphertext, tt.args.key) + assert.Equal(t, tt.want, got) + assert.Equal(t, tt.wantErr, err) + }) + } +} diff --git a/plugin/plugin.go b/plugin/plugin.go index aa02e3f49..3f71d5fa4 100644 --- a/plugin/plugin.go +++ b/plugin/plugin.go @@ -67,6 +67,7 @@ type Config struct { Whitelist ConfigEntry `yaml:"whitelist"` MeshResourceValidate ConfigEntry `yaml:"meshResourceValidate"` DiscoverEvent PluginChanConfig `yaml:"discoverEvent"` + Crypto PluginChanConfig `yaml:"crypto"` } // PluginChanConfig 插件执行链配置 diff --git a/plugin/whitelist.go b/plugin/whitelist.go index 5d83f5586..64046dec4 100644 --- a/plugin/whitelist.go +++ b/plugin/whitelist.go @@ -33,7 +33,7 @@ type Whitelist interface { Contain(entry interface{}) bool } -// GetWhitelist Get the whitelist plug -in +// GetWhitelist Get the whitelist plugin func GetWhitelist() Whitelist { c := &config.Whitelist plugin, exist := pluginSet[c.Name] diff --git a/release/conf/polaris-server.yaml b/release/conf/polaris-server.yaml index cce1ee244..5cb039d7c 100644 --- a/release/conf/polaris-server.yaml +++ b/release/conf/polaris-server.yaml @@ -453,6 +453,9 @@ store: # txIsolationLevel: 2 #LevelReadCommitted # 插件配置 plugin: + crypto: + entries: + - name: AES # whitelist: # name: whitelist # option: diff --git a/test/data/config_test.yaml b/test/data/config_test.yaml index c392014b2..2927c067a 100644 --- a/test/data/config_test.yaml +++ b/test/data/config_test.yaml @@ -250,3 +250,7 @@ cache: expireTimeAfterWrite: 3600 - name: faultDetectRule # - name: l5 # 加载l5数据 +plugin: + crypto: + entries: + - name: AES \ No newline at end of file diff --git a/test/data/config_test_sqldb.yaml b/test/data/config_test_sqldb.yaml index e6414064b..b10da4438 100644 --- a/test/data/config_test_sqldb.yaml +++ b/test/data/config_test_sqldb.yaml @@ -248,3 +248,7 @@ cache: - name: faultDetectRule # - name: l5 # 加载l5数据 +plugin: + crypto: + entries: + - name: AES diff --git a/test/integrate/config_file_test.go b/test/integrate/config_file_test.go index 8236d6a01..9de7429a5 100644 --- a/test/integrate/config_file_test.go +++ b/test/integrate/config_file_test.go @@ -109,4 +109,11 @@ func TestConfigCenter_ConfigFile(t *testing.T) { } }) + t.Run("配置中心-获取全部加密算法", func(t *testing.T) { + resp, err := client.GetAllConfigEncryptAlgorithms() + if err != nil { + t.Fatal(err) + } + assert.Equal(t, resp.GetCode().GetValue(), api.ExecuteSuccess, resp.GetInfo().GetValue()) + }) } diff --git a/test/integrate/http/client.go b/test/integrate/http/client.go index be741ea87..db868b94d 100644 --- a/test/integrate/http/client.go +++ b/test/integrate/http/client.go @@ -281,3 +281,29 @@ func GetConfigImportResponse(response *http.Response) (*apiconfig.ConfigImportRe return nil, errors.New("body decode failed") } } + +func GetConfigEncryptAlgorithmResponse(response *http.Response) (*apiconfig.ConfigEncryptAlgorithmResponse, error) { + // 打印回复 + fmt.Printf("http code: %v\n", response.StatusCode) + + ret := &apiconfig.ConfigEncryptAlgorithmResponse{} + checkErr := jsonpb.Unmarshal(response.Body, ret) + if checkErr == nil { + fmt.Printf("%+v\n", ret) + } else { + fmt.Printf("%v\n", checkErr) + } + + // 检查回复 + if response.StatusCode != 200 { + return nil, errors.New("invalid http code") + } + + if checkErr == nil { + return ret, nil + } else if checkErr == io.EOF { + return nil, io.EOF + } else { + return nil, errors.New("body decode failed") + } +} diff --git a/test/integrate/http/config_center.go b/test/integrate/http/config_center.go index 217320f47..7465695a5 100644 --- a/test/integrate/http/config_center.go +++ b/test/integrate/http/config_center.go @@ -384,6 +384,29 @@ func (c *Client) CreateConfigFileRelease(file *apiconfig.ConfigFileRelease) (*ap return checkCreateConfigResponse(ret) } +func (c *Client) GetAllConfigEncryptAlgorithms() (*apiconfig.ConfigEncryptAlgorithmResponse, error) { + fmt.Printf("\nquery config encrypt algorithm\n") + + url := fmt.Sprintf("http://%v/config/%v/configfiles/encryptalgorithm", c.Address, c.Version) + + response, err := c.SendRequest("GET", url, nil) + if err != nil { + fmt.Printf("%v\n", err) + return nil, err + } + + ret, err := GetConfigEncryptAlgorithmResponse(response) + if err != nil { + fmt.Printf("%v\n", err) + return nil, err + } + + if ret.GetCode().GetValue() != api.ExecuteSuccess { + return nil, errors.New(ret.GetInfo().GetValue()) + } + return ret, nil +} + func checkCreateConfigResponse(ret *apiconfig.ConfigResponse) ( *apiconfig.ConfigResponse, error) {