feat: K8s 部署清单、Token 接入加固与日志/设置页优化
- kubernetes/: 30Gi PVC、单副本 Recreate、Secret/Service/NodePort/Ingress 清单与部署文档 - 数据安全: kubeconfig/token 统一加密存储,启动迁移历史明文,DB 0600 - Token 接入: 命名空间级最小权限提示、长期 Token、自签证书 TLS 兼容 - 部署: 命名空间级 Token 下跳过 Namespace patch/枚举,节点信息容错 - 日志页: namespace → app → pod 选择顺序与作用域收窄 - 设置页: 拆分为通用/告警/LDAP/镜像源四个 tab - 弹框: 统一 max-h 滚动与长命令换行,修复内容溢出
This commit is contained in:
1 parent
2d73661dd1
commit
9f7c522157
50 files changed
+1346
-149
No files matched your search
+56
-17
@@ -6,6 +6,7 @@ import (
|
||||
"opsxloki/pkg/crypto"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"opsxloki/config"
|
||||
@@ -32,6 +33,39 @@ func SaveKubeConfig(name, content string) (string, error) {
|
||||
return path, nil
|
||||
}
|
||||
|
||||
// MigrateClusterKubeconfigs 启动时把历史明文 kubeconfig 加密回 DB,并删除明文文件。
|
||||
// 幂等:已加密的直接跳过。
|
||||
func MigrateClusterKubeconfigs() {
|
||||
db := repository.GetDB()
|
||||
if db == nil {
|
||||
return
|
||||
}
|
||||
kubeDir := filepath.Clean(config.GetConfig().KubeDir)
|
||||
db.Lock()
|
||||
changed := false
|
||||
for i := range db.Clusters {
|
||||
c := &db.Clusters[i]
|
||||
if c.KubeConfig == "" || c.IsKubeconfigEncrypted() {
|
||||
continue
|
||||
}
|
||||
if err := c.EncryptAndSetKubeconfig(c.KubeConfig); err != nil {
|
||||
continue
|
||||
}
|
||||
if c.KubeConfigPath != "" {
|
||||
p := filepath.Clean(c.KubeConfigPath)
|
||||
if strings.HasPrefix(p, kubeDir+string(os.PathSeparator)) {
|
||||
_ = os.Remove(p)
|
||||
}
|
||||
c.KubeConfigPath = ""
|
||||
}
|
||||
changed = true
|
||||
}
|
||||
db.Unlock()
|
||||
if changed {
|
||||
_ = db.RequestSaveSync()
|
||||
}
|
||||
}
|
||||
|
||||
// Create 创建集群。token 接入时自动构造等价 kubeconfig,统一存储,使部署模块同样可用。
|
||||
func (s *ClusterService) Create(name, description, configType, apiServer, kubeconfig, token, contextName string) (*models.Cluster, error) {
|
||||
if configType == "" {
|
||||
@@ -40,33 +74,32 @@ func (s *ClusterService) Create(name, description, configType, apiServer, kubeco
|
||||
|
||||
finalKubeconfig := kubeconfig
|
||||
if configType == "token" {
|
||||
// 安全默认:不嵌入 CA、不跳过 TLS 验证,依赖系统 CA 池。
|
||||
// 自签证书的集群请改用 kubeconfig 模式(其内含 CA 证书)。
|
||||
finalKubeconfig = BuildKubeconfigFromToken(apiServer, token, "", false)
|
||||
// Token 模式通常用于内网/自签证书集群:默认跳过 TLS 校验。
|
||||
// 如需严格校验,请使用 kubeconfig 模式(其中包含 CA 证书)。
|
||||
finalKubeconfig = BuildKubeconfigFromToken(apiServer, token, "", true) // Token 模式默认跳过 TLS 校验,兼容内网自签证书
|
||||
}
|
||||
// 展示用 APIServer:token 模式取入参,kubeconfig 模式从内容提取
|
||||
if apiServer == "" {
|
||||
apiServer = ExtractAPIServer(finalKubeconfig)
|
||||
}
|
||||
|
||||
path, err := SaveKubeConfig(name, finalKubeconfig)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("kubeconfig 保存失败: %w", err)
|
||||
}
|
||||
|
||||
// 安全加固:kubeconfig 只加密存 DB,不再落明文文件
|
||||
cluster := models.Cluster{
|
||||
ID: repository.GetDB().NextID("clusters"),
|
||||
Name: name,
|
||||
Description: description,
|
||||
ConfigType: configType,
|
||||
APIServer: apiServer,
|
||||
KubeConfig: finalKubeconfig,
|
||||
KubeConfigPath: path,
|
||||
KubeConfig: "",
|
||||
KubeConfigPath: "",
|
||||
Context: contextName,
|
||||
Status: "pending",
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
}
|
||||
if err := cluster.EncryptAndSetKubeconfig(finalKubeconfig); err != nil {
|
||||
return nil, fmt.Errorf("kubeconfig 加密失败: %w", err)
|
||||
}
|
||||
|
||||
db := repository.GetDB()
|
||||
db.Lock()
|
||||
@@ -144,18 +177,24 @@ func (s *ClusterService) Update(id uint, updates map[string]interface{}) error {
|
||||
if v, ok := updates["apiServer"].(string); ok {
|
||||
db.Clusters[i].APIServer = v
|
||||
}
|
||||
// kubeconfig 编辑约定:非空才更新(留空=保留旧密文)
|
||||
// 凭据:kubeconfig 或 token 二选一,非空才更新,统一加密存 DB
|
||||
if v, ok := updates["kubeconfig"].(string); ok && v != "" {
|
||||
if enc, err := crypto.EncryptField(v); err == nil {
|
||||
db.Clusters[i].KubeConfig = enc
|
||||
db.Clusters[i].KubeConfigPath = ""
|
||||
}
|
||||
}
|
||||
// 凭据字段:空值不覆盖,避免编辑其它项时清空
|
||||
if v, ok := updates["kubeconfig"].(string); ok && v != "" {
|
||||
db.Clusters[i].KubeConfig = v
|
||||
if db.Clusters[i].KubeConfigPath != "" {
|
||||
_ = os.WriteFile(db.Clusters[i].KubeConfigPath, []byte(v), 0600)
|
||||
} else if tok, ok := updates["token"].(string); ok && tok != "" {
|
||||
apiServer := db.Clusters[i].APIServer
|
||||
if v, ok := updates["apiServer"].(string); ok && v != "" {
|
||||
apiServer = v
|
||||
}
|
||||
if apiServer == "" {
|
||||
return fmt.Errorf("Token 接入需要 API Server")
|
||||
}
|
||||
kc := BuildKubeconfigFromToken(apiServer, tok, "", true) // Token 模式默认跳过 TLS 校验
|
||||
if enc, err := crypto.EncryptField(kc); err == nil {
|
||||
db.Clusters[i].KubeConfig = enc
|
||||
db.Clusters[i].KubeConfigPath = ""
|
||||
}
|
||||
}
|
||||
db.Clusters[i].UpdatedAt = time.Now()
|
||||
|
||||
Reference in new issue
Block a user