Scert/backend/internal/httpapi/ssh_key.go
xuans 56c88c3ec2 权限整改:完成 SSH 私钥项目归属隔离
为 SSH 私钥记录项目归属,限制服务器引用及私钥列表、新增、删除操作的项目权限,兼容历史默认项目数据并补充专项测试。
2026-09-29 19:05:52 +08:00

105 lines
3.1 KiB
Go

package httpapi
import (
"errors"
"net/http"
"strings"
"github.com/gin-gonic/gin"
"git.pcux.cn/xuans/Scert/backend/internal/domain"
)
const maxSSHKeyRequestBytes = 2 * 1024 * 1024
type sshKeyRequest struct {
Name string `json:"name" binding:"required"`
PrivateKey string `json:"private_key" binding:"required"`
Passphrase string `json:"passphrase"`
ProjectID string `json:"project_id"`
}
func (s *Server) listSSHKeys(c *gin.Context) {
if s.sshKeys == nil {
s.writeError(c, http.StatusNotImplemented, "SSH 私钥管理接口尚未配置", errors.New("SSH 私钥管理服务未配置"))
return
}
projectID := strings.TrimSpace(c.Query("project_id"))
if projectID == "" {
projectID = domain.DefaultProjectID
}
if !s.authorizeServerMutation(c, projectID) {
return
}
items, err := s.sshKeys.ListForProject(c.Request.Context(), projectID)
if err != nil {
s.writeError(c, http.StatusInternalServerError, "查询 SSH 私钥失败", err)
return
}
c.JSON(http.StatusOK, gin.H{"items": items})
}
func (s *Server) createSSHKey(c *gin.Context) {
if s.sshKeys == nil {
s.writeError(c, http.StatusNotImplemented, "SSH 私钥管理接口尚未配置", errors.New("SSH 私钥管理服务未配置"))
return
}
var request sshKeyRequest
if err := bindJSON(c, &request, maxSSHKeyRequestBytes); err != nil {
var tooLarge *http.MaxBytesError
if errors.As(err, &tooLarge) {
s.writeError(c, http.StatusRequestEntityTooLarge, "SSH 私钥请求内容过大", err)
return
}
s.writeError(c, http.StatusBadRequest, "请求参数无效", err)
return
}
projectID := strings.TrimSpace(request.ProjectID)
if projectID == "" {
projectID = domain.DefaultProjectID
}
if !s.authorizeServerMutation(c, projectID) {
return
}
item, err := s.sshKeys.CreateForProject(c.Request.Context(), request.Name, request.PrivateKey, request.Passphrase, projectID)
if err != nil {
s.writeError(c, http.StatusBadRequest, "SSH 私钥无效", err)
return
}
c.JSON(http.StatusCreated, item)
}
func (s *Server) deleteSSHKey(c *gin.Context) {
if s.sshKeys == nil || s.servers == nil {
s.writeError(c, http.StatusNotImplemented, "SSH 私钥管理接口尚未配置", errors.New("SSH 私钥或服务器管理服务未配置"))
return
}
reference, err := s.sshKeys.Reference(c.Param("id"))
if err != nil {
s.writeError(c, http.StatusBadRequest, "SSH 私钥 ID 无效", err)
return
}
metadata, err := s.sshKeys.ValidateReference(c.Request.Context(), reference)
if err != nil {
s.writeError(c, http.StatusBadRequest, "SSH 私钥引用无效", err)
return
}
if !s.authorizeServerMutation(c, metadata.ProjectID) {
return
}
used, err := s.servers.IsPrivateKeyRefUsed(c.Request.Context(), reference)
if err != nil {
s.writeError(c, http.StatusInternalServerError, "检查 SSH 私钥占用状态失败", err)
return
}
if used {
s.writeError(c, http.StatusConflict, "SSH 私钥仍被部署服务器使用", errors.New("SSH 私钥引用正在使用"))
return
}
if err := s.sshKeys.Delete(c.Request.Context(), c.Param("id")); err != nil {
s.writeError(c, http.StatusNotFound, "SSH 私钥不存在", err)
return
}
c.Status(http.StatusNoContent)
}