diff --git a/bcs-services/bcs-bscp/cmd/data-service/app/app.go b/bcs-services/bcs-bscp/cmd/data-service/app/app.go index 4ba49ad2f7..8c0ad7a77d 100644 --- a/bcs-services/bcs-bscp/cmd/data-service/app/app.go +++ b/bcs-services/bcs-bscp/cmd/data-service/app/app.go @@ -23,6 +23,7 @@ import ( grpc_recovery "github.com/grpc-ecosystem/go-grpc-middleware/recovery" grpc_prometheus "github.com/grpc-ecosystem/go-grpc-prometheus" + "github.com/hashicorp/vault/api" "google.golang.org/grpc" "google.golang.org/grpc/credentials" @@ -31,6 +32,7 @@ import ( "bscp.io/pkg/cc" "bscp.io/pkg/criteria/uuid" "bscp.io/pkg/dal/dao" + "bscp.io/pkg/dal/vault" "bscp.io/pkg/logs" "bscp.io/pkg/metrics" pbds "bscp.io/pkg/protocol/data-service" @@ -81,6 +83,7 @@ type dataService struct { service *service.Service sd serviced.Service daoSet dao.Set + vault vault.Set } // prepare do prepare jobs before run data service. @@ -130,6 +133,27 @@ func (ds *dataService) prepare(opt *options.Option) error { ds.daoSet = set + // initial Vault set + vaultSet, err := vault.NewSet(cc.DataService().Vault) + if err != nil { + return fmt.Errorf("initial vault set failed, err: %v", err) + } + // 挂载目录 + exists, err := vaultSet.IsMountPathExists(vault.MountPath) + if err != nil { + return fmt.Errorf("error checking mount path: %v", err) + } + if !exists { + mountConfig := &api.MountInput{ + Type: "kv-v2", + } + if err = vaultSet.CreateMountPath(vault.MountPath, mountConfig); err != nil { + return fmt.Errorf("initial vault mount path failed, err: %v", err) + } + } + + ds.vault = vaultSet + return nil } @@ -167,7 +191,7 @@ func (ds *dataService) listenAndServe() error { } serve := grpc.NewServer(opts...) - svc, err := service.NewService(ds.sd, ds.daoSet) + svc, err := service.NewService(ds.sd, ds.daoSet, ds.vault) if err != nil { return err } diff --git a/bcs-services/bcs-bscp/cmd/data-service/service/service.go b/bcs-services/bcs-bscp/cmd/data-service/service/service.go index 78e7f0a103..d2e207ff3f 100644 --- a/bcs-services/bcs-bscp/cmd/data-service/service/service.go +++ b/bcs-services/bcs-bscp/cmd/data-service/service/service.go @@ -22,6 +22,7 @@ import ( "bscp.io/pkg/cc" "bscp.io/pkg/dal/dao" "bscp.io/pkg/dal/repository" + "bscp.io/pkg/dal/vault" "bscp.io/pkg/metrics" pbds "bscp.io/pkg/protocol/data-service" "bscp.io/pkg/serviced" @@ -32,6 +33,7 @@ import ( // Service do all the data service's work type Service struct { dao dao.Set + vault vault.Set gateway *gateway // esb esb api client. esb client.Client @@ -40,7 +42,7 @@ type Service struct { } // NewService create a service instance. -func NewService(sd serviced.Service, daoSet dao.Set) (*Service, error) { +func NewService(sd serviced.Service, daoSet dao.Set, vaultSet vault.Set) (*Service, error) { state, ok := sd.(serviced.State) if !ok { return nil, errors.New("discover convert state failed") @@ -65,6 +67,7 @@ func NewService(sd serviced.Service, daoSet dao.Set) (*Service, error) { svc := &Service{ dao: daoSet, + vault: vaultSet, gateway: gateway, esb: esbCli, repo: repo, diff --git a/bcs-services/bcs-bscp/etc/bcs-bscp.yml b/bcs-services/bcs-bscp/etc/bcs-bscp.yml index 5fae8cc51b..9e74ecfa3f 100644 --- a/bcs-services/bcs-bscp/etc/bcs-bscp.yml +++ b/bcs-services/bcs-bscp/etc/bcs-bscp.yml @@ -65,3 +65,8 @@ repository: user: xx downstream: bounceIntervalHour: 48 + +# vault +vault: + address: http://127.0.0.1:8200 + token: root diff --git a/bcs-services/bcs-bscp/pkg/cc/service.go b/bcs-services/bcs-bscp/pkg/cc/service.go index bab221b091..43d9db367d 100644 --- a/bcs-services/bcs-bscp/pkg/cc/service.go +++ b/bcs-services/bcs-bscp/pkg/cc/service.go @@ -275,6 +275,7 @@ type DataServiceSetting struct { Sharding Sharding `yaml:"sharding"` Esb Esb `yaml:"esb"` Repo Repository `yaml:"repository"` + Vault Vault `yaml:"vault"` } // trySetFlagBindIP try set flag bind ip. @@ -319,6 +320,10 @@ func (s DataServiceSetting) Validate() error { return err } + if err := s.Vault.validate(); err != nil { + return err + } + return nil } diff --git a/bcs-services/bcs-bscp/pkg/cc/types.go b/bcs-services/bcs-bscp/pkg/cc/types.go index b5c735d3ca..3611d19a3e 100644 --- a/bcs-services/bcs-bscp/pkg/cc/types.go +++ b/bcs-services/bcs-bscp/pkg/cc/types.go @@ -965,3 +965,25 @@ func (c Credential) validate() error { return nil } + +// Vault Used to store the configuration information required for connecting to the Vault server. +type Vault struct { + // Address is used to store the address of the Vault server + Address string `yaml:"address"` + // Token is used for accessing the Vault server + Token string `yaml:"token"` +} + +// validate Vault options +func (v Vault) validate() error { + + if v.Address == "" { + return errors.New("vault address is not set") + } + + if v.Token == "" { + return errors.New("vault token is not set") + } + + return nil +} diff --git a/bcs-services/bcs-bscp/pkg/dal/vault/kv.go b/bcs-services/bcs-bscp/pkg/dal/vault/kv.go new file mode 100644 index 0000000000..01f8f0dc75 --- /dev/null +++ b/bcs-services/bcs-bscp/pkg/dal/vault/kv.go @@ -0,0 +1,81 @@ +/* + * Tencent is pleased to support the open source community by making Blueking Container Service available. + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * Licensed under the MIT License (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * http://opensource.org/licenses/MIT + * 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 vault + +import ( + "fmt" + + "bscp.io/pkg/kit" + "bscp.io/pkg/types" +) + +const ( + // MountPath mount path + MountPath = "bk_bscp" + // kvPath kv path + kvPath = "biz/%d/apps/%d/kv/key/%s" +) + +// UpsertKv 创建|更新kv +func (s *set) UpsertKv(kit *kit.Kit, opt *types.UpsertKvOption) (int, error) { + + if err := opt.Validate(); err != nil { + return 0, err + } + + data := map[string]interface{}{ + "type": opt.KvType, + "value": opt.Value, + } + secret, err := s.cli.KVv2(MountPath).Put(kit.Ctx, fmt.Sprintf(kvPath, opt.BizID, opt.AppID, opt.Key), data) + if err != nil { + return 0, err + } + + return secret.VersionMetadata.Version, nil + +} + +// GetLastKv 获取最新的kv +func (s *set) GetLastKv(kit *kit.Kit, opt *types.GetLastKvOpt) (string, error) { + + kv, err := s.cli.KVv2(MountPath).Get(kit.Ctx, fmt.Sprintf(kvPath, opt.BizID, opt.AppID, opt.Key)) + if err != nil { + return "", err + } + + value, ok := kv.Data["data"].(string) + if !ok { + return "", fmt.Errorf("value type assertion failed: err : %v", err) + } + + return value, nil + +} + +// GetKvByVersion 根据版本获取kv +func (s *set) GetKvByVersion(kit *kit.Kit, bizID, appID uint32, key string, version int) (string, error) { + + kv, err := s.cli.KVv2(MountPath).GetVersion(kit.Ctx, fmt.Sprintf(kvPath, bizID, appID, key), version) + if err != nil { + return "", err + } + + value, ok := kv.Data["data"].(string) + if !ok { + return "", fmt.Errorf("value type assertion failed: err : %v", err) + } + + return value, nil + +} diff --git a/bcs-services/bcs-bscp/pkg/dal/vault/mount.go b/bcs-services/bcs-bscp/pkg/dal/vault/mount.go new file mode 100644 index 0000000000..ae44cab0de --- /dev/null +++ b/bcs-services/bcs-bscp/pkg/dal/vault/mount.go @@ -0,0 +1,37 @@ +/* + * Tencent is pleased to support the open source community by making Blueking Container Service available. + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * Licensed under the MIT License (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * http://opensource.org/licenses/MIT + * 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 vault + +import ( + "fmt" + + "github.com/hashicorp/vault/api" +) + +// CreateMountPath 创建挂载目录 +func (s *set) CreateMountPath(path string, config *api.MountInput) error { + return s.cli.Sys().Mount(path, config) +} + +// IsMountPathExists 挂载目录是否存在 +func (s *set) IsMountPathExists(path string) (bool, error) { + // 列出所有的挂载路径 + mounts, err := s.cli.Sys().ListMounts() + if err != nil { + return false, err + } + + // 检查要创建的挂载路径是否已存在 + _, exists := mounts[fmt.Sprintf("%s/", path)] + return exists, nil +} diff --git a/bcs-services/bcs-bscp/pkg/dal/vault/vault.go b/bcs-services/bcs-bscp/pkg/dal/vault/vault.go new file mode 100644 index 0000000000..988e0b956b --- /dev/null +++ b/bcs-services/bcs-bscp/pkg/dal/vault/vault.go @@ -0,0 +1,60 @@ +/* + * Tencent is pleased to support the open source community by making Blueking Container Service available. + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * Licensed under the MIT License (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * http://opensource.org/licenses/MIT + * 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 vault NOTES +package vault + +import ( + vault "github.com/hashicorp/vault/api" + + "bscp.io/pkg/cc" + "bscp.io/pkg/kit" + "bscp.io/pkg/types" +) + +// Set ... +type Set interface { + // IsMountPathExists 挂载目录是否存在 + IsMountPathExists(path string) (bool, error) + // CreateMountPath 创建挂载目录 + CreateMountPath(path string, config *vault.MountInput) error + // UpsertKv 创建|更新kv + UpsertKv(kit *kit.Kit, opt *types.UpsertKvOption) (int, error) + // GetLastKv 获取最新的kv + GetLastKv(kit *kit.Kit, opt *types.GetLastKvOpt) (string, error) + // GetKvByVersion 根据版本获取kv + GetKvByVersion(kit *kit.Kit, bizID, appID uint32, key string, version int) (string, error) +} + +type set struct { + cli *vault.Client +} + +// NewSet ... +func NewSet(opt cc.Vault) (Set, error) { + + config := vault.DefaultConfig() + config.Address = opt.Address + + client, err := vault.NewClient(config) + if err != nil { + return nil, err + } + + client.SetToken(opt.Token) + + s := &set{ + cli: client, + } + + return s, nil +} diff --git a/bcs-services/bcs-bscp/pkg/types/kv.go b/bcs-services/bcs-bscp/pkg/types/kv.go new file mode 100644 index 0000000000..bcbfbc1921 --- /dev/null +++ b/bcs-services/bcs-bscp/pkg/types/kv.go @@ -0,0 +1,139 @@ +/* + * Tencent is pleased to support the open source community by making Blueking Container Service available. + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * Licensed under the MIT License (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * http://opensource.org/licenses/MIT + * 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 types + +import ( + "encoding/json" + "errors" + "fmt" + "strconv" + + "gopkg.in/yaml.v3" +) + +// KvType is the type of kv +type KvType string + +const ( + // KvStr is the type for string kv + KvStr KvType = "string" + // KvNumber is the type for number kv + KvNumber KvType = "number" + // KvText is the type for text kv + KvText KvType = "text" + // KvJson is the type for json kv + KvJson KvType = "json" + // KvYAML is the type for yaml kv + KvYAML KvType = "yaml" +) + +// Validate the kvType and value match +func (k KvType) Validate(value string) error { + + if value == "" { + return errors.New("kv value is null") + } + + switch k { + case KvStr: + return nil + case KvNumber: + if isStringConvertibleToNumber(value) { + return fmt.Errorf("value is not a number") + } + return nil + case KvText: + return nil + case KvJson: + if !json.Valid([]byte(value)) { + return fmt.Errorf("value is not a json") + } + return nil + case KvYAML: + var data interface{} + if err := yaml.Unmarshal([]byte(value), &data); err != nil { + return fmt.Errorf("value is not a yaml, err: %v", err) + } + return nil + default: + return errors.New("revision not set") + } +} + +func isStringConvertibleToNumber(s string) bool { + _, err := strconv.Atoi(s) + if err == nil { + return true + } + + _, err = strconv.ParseFloat(s, 64) + return err == nil + +} + +// UpsertKvOption ... +type UpsertKvOption struct { + BizID uint32 + AppID uint32 + Key string + Value string + KvType KvType +} + +// Validate ... +func (o *UpsertKvOption) Validate() error { + if o.BizID <= 0 { + return errors.New("invalid biz id, should >= 1") + } + + if o.AppID <= 0 { + return errors.New("invalid app id, should >= 1") + } + + if o.Key == "" { + return errors.New("kv key is required") + } + + if o.Value == "" { + return errors.New("kv value is required") + } + + if err := o.KvType.Validate(o.Value); err != nil { + return err + } + + return nil +} + +// GetLastKvOpt ... +type GetLastKvOpt struct { + BizID uint32 + AppID uint32 + Key string +} + +// Validate ... +func (o *GetLastKvOpt) Validate() error { + if o.BizID <= 0 { + return errors.New("invalid biz id, should >= 1") + } + + if o.AppID <= 0 { + return errors.New("invalid app id, should >= 1") + } + + if o.Key == "" { + return errors.New("kv key is required") + } + return nil +}