代码拉取完成,页面将自动刷新
同步操作将从 infraboard/go-course 强制同步,此操作会覆盖自 Fork 仓库以来所做的任何修改,且无法恢复!!!
确定后同步将在后台操作,完成时将刷新页面,请耐心等待。
我们对4个厂商的凭证进行抽象:
TX_CLOUD_SECRET_ID=xxx
TX_CLOUD_SECRET_KEY=xxx
AL_CLOUD_ACCESS_KEY=xx
AL_CLOUD_ACCESS_SECRET=xxx
HW_CLOUD_ACCESS_KEY=xxx
HW_CLOUD_ACCESS_SECRET=xxx
VS_HOST=xxx
VS_USERNAME=xxxx
VS_PASSWORD=xxx
我们定义2中凭证类型
const (
CrendentialAPIKey CrendentialType = iota
CrendentialPassword
)
type CrendentialType int
针对vsphere, 需要独立出一个字段: VS_HOST 用来保存 vshpere服务的地址
我们定义secret表,来存储我们通过过程中要使用到的这些密钥:
CREATE TABLE `secret` (
`id` varchar(64) NOT NULL,
`create_at` bigint(13) NOT NULL,
`description` varchar(255) CHARACTER SET utf8mb4 NOT NULL,
`vendor` tinyint(1) NOT NULL,
`address` varchar(255) NOT NULL,
`allow_regions` text NOT NULL,
`crendential_type` tinyint(1) NOT NULL,
`api_key` varchar(255) NOT NULL,
`api_secret` text NOT NULL,
`request_rate` int(11) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `idx_key` (`api_key`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=latin1
定义数据结构
type Secret struct {
Id string `json:"id"` // 全局唯一Id
CreateAt int64 `json:"create_at"` // 创建时间
*CreateSecretRequest
}
type CreateSecretRequest struct {
Description string `json:"description" validate:"required,lte=100"` // 描述
Vendor resource.Vendor `json:"vendor"` // 厂商
AllowRegions []string `json:"allow_regions"` // 允许同步的区域
CrendentialType CrendentialType `json:"crendential_type"` // 凭证类型
Address string `json:"address"` // 服务地址, 云商不用填写
APIKey string `json:"api_key" validate:"required,lte=100"` // key
APISecret string `json:"api_secret" validate:"required,lte=100"` // secrete
RequestRate int `json:"request_rate"` // 请求速率限制, 默认1秒5个
}
定义接口
type Service interface {
SecretService
}
type SecretService interface {
CreateSecret(context.Context, *CreateSecretRequest) (*Secret, error)
QuerySecret(context.Context, *QuerySecretRequest) (*SecretSet, error)
DescribeSecret(context.Context, *DescribeSecretRequest) (*Secret, error)
}
全局实例添加该Service
package pkg
import (
"github.com/infraboard/cmdb/pkg/host"
"github.com/infraboard/cmdb/pkg/syncer"
)
var (
Host host.Service
Syncer syncer.Service
)
impl模块里面定义个secret.go, 用于实现SecretService, 具体参考: secret CRUD
然后编写HTTP暴露模块:
func (h *handler) QuerySecret(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
req := syncer.NewQuerySecretRequest()
set, err := h.service.QuerySecret(r.Context(), req)
if err != nil {
response.Failed(w, err)
return
}
response.Success(w, set)
}
func (h *handler) CreateSecret(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
req := syncer.NewCreateSecretRequest()
if err := request.GetDataFromRequest(r, req); err != nil {
response.Failed(w, err)
return
}
ins, err := h.service.CreateSecret(r.Context(), req)
if err != nil {
response.Failed(w, err)
return
}
response.Success(w, ins)
}
func (h *handler) DescribeSecret(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
req := syncer.NewDescribeSecretRequest(ps.ByName("id"))
set, err := h.service.DescribeSecret(r.Context(), req)
if err != nil {
response.Failed(w, err)
return
}
response.Success(w, set)
}
最后由handler注册:
var (
api = &handler{}
)
type handler struct {
service syncer.Service
log logger.Logger
}
func (h *handler) Config() error {
h.log = zap.L().Named("Syncer")
if pkg.Syncer == nil {
return fmt.Errorf("dependence service syncer not ready")
}
h.service = pkg.Syncer
return nil
}
func RegistAPI(r *httprouter.Router) {
api.Config()
r.POST("/secrets", api.CreateSecret)
r.GET("/secrets", api.QuerySecret)
r.GET("/secrets/:id", api.DescribeSecret)
}
// 初始化服务层 Ioc初始化
if err := impl.Service.Config(); err != nil {
return err
}
pkg.Host = impl.Service
// Syncer Service
if err := syncer.Service.Config(); err != nil {
return err
}
pkg.Syncer = syncer.Service
// Start 启动服务
func (s *HTTPService) Start() error {
// 装置子服务路由
hostAPI.RegistAPI(s.r)
syncerAPI.RegistAPI(s.r)
...
}
通过Postman测试接口的使用性
现在我们的Secret Key都是明文存储的, 为了安全, 我们加密存储,
为了确保安全, Secret在返回的时候,需要做脱敏, 及时是秘文也不应该显示
我们采用cbc来实现对称加密: 这是之前封装的模块 cbc加密
为了能区分 原始数据和密码数据,我们给秘文数据加一个前缀,以避免重复加密或者解密
conf/config.go:
const (
CIPHER_TEXT_PREFIX = "@ciphered@"
)
然后为我们secret对象 添加加解密的方法:
func (s *Secret) EncryptAPISecret(key string) error {
// 判断文本是否已经加密
if strings.HasPrefix(s.APISecret, conf.CIPHER_TEXT_PREFIX) {
return fmt.Errorf("text has ciphered")
}
cipherText, err := cbc.Encrypt([]byte(s.APISecret), []byte(key))
if err != nil {
return err
}
base64Str := base64.StdEncoding.EncodeToString(cipherText)
s.APISecret = fmt.Sprintf("%s%s", conf.CIPHER_TEXT_PREFIX, base64Str)
return nil
}
func (s *Secret) DecryptAPISecret(key string) error {
// 判断文本是否已经是明文
if !strings.HasPrefix(s.APISecret, conf.CIPHER_TEXT_PREFIX) {
return fmt.Errorf("text is plan text")
}
base64CipherText := strings.TrimPrefix(s.APISecret, conf.CIPHER_TEXT_PREFIX)
cipherText, err := base64.StdEncoding.DecodeString(base64CipherText)
if err != nil {
return err
}
planText, err := cbc.Decrypt([]byte(cipherText), []byte(key))
if err != nil {
return err
}
s.APISecret = string(planText)
return nil
}
为secret对象补充一个脱敏方法:
func (s *Secret) Desense() {
if s.APISecret != "" {
s.APISecret = "******"
}
}
对List方法查询出来的数据进行过敏:
for rows.Next() {
ins := syncer.NewDefaultSecret()
...
ins.Desense()
set.Add(ins)
}
针对Get方法, 在API暴露时脱敏:
func (h *handler) DescribeSecret(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
req := syncer.NewDescribeSecretRequest(ps.ByName("id"))
ins, err := h.service.DescribeSecret(r.Context(), req)
if err != nil {
response.Failed(w, err)
return
}
ins.Desense()
response.Success(w, ins)
}
这样我们系统内部交互时, 通过Describe拿到的数据 依然是可以解密的
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。