init
This commit is contained in:
61
ca-server/internal/config/config.go
Normal file
61
ca-server/internal/config/config.go
Normal file
@@ -0,0 +1,61 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"ca-mini/pkg/utils"
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
type AppConfig struct {
|
||||
Name string `mapstructure:"name"`
|
||||
Version string `mapstructure:"version"`
|
||||
CopyrightYear int `mapstructure:"copyrightYear"`
|
||||
}
|
||||
|
||||
type ServerConfig struct {
|
||||
Port int `mapstructure:"port"`
|
||||
ContextPath string `mapstructure:"context-path"`
|
||||
}
|
||||
|
||||
type LoggingConfig struct {
|
||||
Level string `mapstructure:"level"`
|
||||
Path string `mapstructure:"path"`
|
||||
}
|
||||
|
||||
type DatasourceConfig struct {
|
||||
Url string `mapstructure:"url"`
|
||||
Username string `mapstructure:"username"`
|
||||
Password string `mapstructure:"password"`
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
App AppConfig `mapstructure:"app"`
|
||||
Server ServerConfig `mapstructure:"server"`
|
||||
Datasource DatasourceConfig `mapstructure:"datasource"`
|
||||
Logging LoggingConfig `mapstructure:"logging"`
|
||||
ServerAddress string
|
||||
}
|
||||
|
||||
func Load() (*Config, error) {
|
||||
viper.SetConfigName("config")
|
||||
viper.SetConfigType("yaml")
|
||||
|
||||
viper.AddConfigPath(utils.WORK_PATH + "/conf")
|
||||
|
||||
err := viper.ReadInConfig()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var config Config
|
||||
err = viper.Unmarshal(&config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 设置ServerAddress
|
||||
config.ServerAddress = fmt.Sprintf(":%d", config.Server.Port)
|
||||
|
||||
return &config, nil
|
||||
}
|
||||
317
ca-server/internal/handlers/certificate_handler.go
Normal file
317
ca-server/internal/handlers/certificate_handler.go
Normal file
@@ -0,0 +1,317 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"ca-mini/db"
|
||||
"ca-mini/internal/logger"
|
||||
"ca-mini/internal/model"
|
||||
"ca-mini/internal/repository"
|
||||
"ca-mini/pkg/utils"
|
||||
"crypto/x509"
|
||||
"encoding/json"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
const (
|
||||
// 错误码
|
||||
ERROR_CODE_SUCCESS = 0
|
||||
ERROR_CODE_FAIL = 1
|
||||
|
||||
// 错误信息
|
||||
ERROR_MSG_SUCCESS = "success"
|
||||
ERROR_MSG_FAIL = "fail"
|
||||
|
||||
WORK_PATH = "/opt/arrokoth/ca-mini"
|
||||
CA_CERT_PATH = WORK_PATH + "/ca/CaRoot.crt"
|
||||
CA_CONFIG_PATH = WORK_PATH + "/ca/ca.conf"
|
||||
CRL_FILE_PATH = WORK_PATH + "/crl/CaRoot.crl"
|
||||
)
|
||||
|
||||
type ErrorResponse struct {
|
||||
Code int `json:"code"`
|
||||
Message string `json:"message"`
|
||||
Date string `json:"date"`
|
||||
}
|
||||
|
||||
type SuccessResponse struct {
|
||||
Code int `json:"code"`
|
||||
Message string `json:"message"`
|
||||
Date string `json:"date"`
|
||||
}
|
||||
|
||||
type CertificateResponse struct {
|
||||
Code int `json:"code"`
|
||||
Message string `json:"message"`
|
||||
Date string `json:"date"`
|
||||
Data model.CertInfo `json:"data"`
|
||||
}
|
||||
|
||||
// IssueCertificate 处理证书签发请求
|
||||
func IssueCertificate(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
// 解析CSR请求
|
||||
var csrRequest struct {
|
||||
Subject string `json:"subject"`
|
||||
SubjectAltName string `json:"subjectAltName"`
|
||||
Alg string `json:"alg"`
|
||||
Len int `json:"len"`
|
||||
}
|
||||
if err := json.NewDecoder(r.Body).Decode(&csrRequest); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
// 检查Subject是否为空
|
||||
if csrRequest.Subject == "" {
|
||||
http.Error(w, "Subject is required", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
// 生成密钥
|
||||
id, err := utils.GenerateKey(csrRequest.Alg, csrRequest.Len)
|
||||
if err != nil {
|
||||
http.Error(w, "Failed to generate key", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
var sans []string
|
||||
if csrRequest.SubjectAltName != "" {
|
||||
sans = strings.Split(strings.TrimSpace(csrRequest.SubjectAltName), ",")
|
||||
}
|
||||
// 如果SubjectAltName为空,则使用Subject作为SAN
|
||||
if len(sans) == 0 {
|
||||
sans = []string{csrRequest.Subject}
|
||||
}
|
||||
// 生成CSR文件
|
||||
id, err = utils.GenerateCsr(id, csrRequest.Subject, sans)
|
||||
if err != nil {
|
||||
http.Error(w, "Failed to generate CSR file", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
// 生成证书
|
||||
id, err = utils.GenerateCert(id, 0)
|
||||
if err != nil {
|
||||
http.Error(w, "Failed to generate certificate", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
// 解析证书信息
|
||||
certInfo, err := ParseCert(id)
|
||||
if err != nil {
|
||||
http.Error(w, "Failed to parse certificate", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
// 新增证书到数据库
|
||||
repository := repository.NewCertificateRepository(db.DB)
|
||||
if err := repository.AddCert(certInfo); err != nil {
|
||||
logger.Error("Failed to save certificate to database %v", err)
|
||||
http.Error(w, "Failed to save certificate", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
// 返回证书信息
|
||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
response := CertificateResponse{
|
||||
Code: http.StatusOK,
|
||||
Message: "Certificate issued successfully",
|
||||
Date: time.Now().Format(time.RFC3339),
|
||||
Data: *certInfo,
|
||||
}
|
||||
json.NewEncoder(w).Encode(response)
|
||||
}
|
||||
|
||||
// 查询证书
|
||||
func GetCertificate(w http.ResponseWriter, r *http.Request) {
|
||||
// 从URL查询参数中获取id
|
||||
//id := r.URL.Query().Get("id")
|
||||
// 获取路径参数 id
|
||||
vars := mux.Vars(r)
|
||||
id := vars["id"]
|
||||
if id == "" {
|
||||
http.Error(w, "id is required", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
// 新增证书到数据库
|
||||
repository := repository.NewCertificateRepository(db.DB)
|
||||
certInfo, err := repository.SelectCertById(id)
|
||||
if err != nil {
|
||||
logger.Error("Failed to query certificate to database %v", err)
|
||||
http.Error(w, "Failed to query certificate", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
// 读取CA证书文件内容
|
||||
caData, err := utils.GetCaCert()
|
||||
if err == nil {
|
||||
certInfo.Ca = caData
|
||||
}
|
||||
|
||||
// 返回证书信息
|
||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
response := CertificateResponse{
|
||||
Code: http.StatusOK,
|
||||
Message: "Certificate retrieved successfully",
|
||||
Date: time.Now().Format(time.RFC3339),
|
||||
Data: *certInfo,
|
||||
}
|
||||
json.NewEncoder(w).Encode(response)
|
||||
}
|
||||
|
||||
// CheckBlacklist 处理黑名单查询请求
|
||||
func CheckBlacklist(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(map[string]bool{
|
||||
"isBlacklisted": false,
|
||||
})
|
||||
}
|
||||
|
||||
// RevokeCertificate 处理证书撤销请求
|
||||
func RevokeCertificate(w http.ResponseWriter, r *http.Request) {
|
||||
// 从URL查询参数中获取id
|
||||
// id := r.URL.Query().Get("id")
|
||||
// 获取路径参数 id
|
||||
vars := mux.Vars(r)
|
||||
id := vars["id"]
|
||||
if id == "" {
|
||||
http.Error(w, "id is required", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
// 检查管理员权限(假设通过HTTP Basic Auth验证)
|
||||
username, password, ok := r.BasicAuth()
|
||||
if !ok || username != "admin" || password != "admin" {
|
||||
http.Error(w, "Unauthorized", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
// 解析证书信息
|
||||
id, err := utils.RevokeCert(id)
|
||||
if err != nil {
|
||||
http.Error(w, "Failed to revoke certificate", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
// 返回证书信息
|
||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
response := SuccessResponse{
|
||||
Code: http.StatusOK,
|
||||
Message: id,
|
||||
Date: time.Now().Format(time.RFC3339),
|
||||
}
|
||||
json.NewEncoder(w).Encode(response)
|
||||
}
|
||||
|
||||
// 解析证书
|
||||
func ParseCert(id string) (*model.CertInfo, error) {
|
||||
|
||||
// 读取私钥文件内容
|
||||
keyData, err := utils.GetKey(id)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("获取私钥失败: %v", err)
|
||||
}
|
||||
|
||||
// 读取证书请求文件内容
|
||||
csrData, err := utils.GetCsr(id)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("获取证书请求失败: %v", err)
|
||||
}
|
||||
|
||||
// 读取CA证书文件内容
|
||||
caData, err := utils.GetCaCert()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("获取CA证书失败: %v", err)
|
||||
}
|
||||
|
||||
// 读取证书文件内容
|
||||
certData, err := utils.GetCert(id)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("获取证书失败: %v", err)
|
||||
}
|
||||
|
||||
// 解析PEM编码的证书
|
||||
block, _ := pem.Decode([]byte(certData))
|
||||
if block == nil {
|
||||
return nil, fmt.Errorf("无法解析PEM块")
|
||||
}
|
||||
|
||||
// 解析X.509证书
|
||||
cert, err := x509.ParseCertificate(block.Bytes)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("解析证书失败: %v", err)
|
||||
}
|
||||
|
||||
version := fmt.Sprintf("%d", cert.Version)
|
||||
certCn := cert.Subject.CommonName
|
||||
certDn := cert.Subject.String()
|
||||
issuerCn := cert.Issuer.CommonName
|
||||
issuerDn := cert.Issuer.String()
|
||||
publicKeyAlg := cert.PublicKeyAlgorithm.String()
|
||||
signatureAlg := cert.SignatureAlgorithm.String()
|
||||
certSubAltName := ""
|
||||
for i, DNSName := range cert.DNSNames {
|
||||
if i == 0 {
|
||||
certSubAltName = DNSName
|
||||
} else {
|
||||
certSubAltName = fmt.Sprintf("%s,%s", certSubAltName, DNSName)
|
||||
}
|
||||
}
|
||||
// beforeTime := cert.NotBefore.Format(time.RFC3339)
|
||||
// afterTime := cert.NotAfter.Format(time.RFC3339)
|
||||
beforeTime := cert.NotBefore
|
||||
afterTime := cert.NotAfter
|
||||
serialNumber := cert.SerialNumber.String()
|
||||
|
||||
return &model.CertInfo{
|
||||
CertId: id,
|
||||
Version: version,
|
||||
CertCn: certCn,
|
||||
CertDn: certDn,
|
||||
PublicKeyAlg: publicKeyAlg,
|
||||
SignatureAlg: signatureAlg,
|
||||
IssuerCn: issuerCn,
|
||||
IssuerDn: issuerDn,
|
||||
SerialNumber: serialNumber,
|
||||
CertSubAltName: certSubAltName,
|
||||
BeforeTime: beforeTime,
|
||||
AfterTime: afterTime,
|
||||
PrivateKey: keyData,
|
||||
Csr: csrData,
|
||||
Cert: certData,
|
||||
Ca: caData,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// DownloadCRL 处理黑名单文件下载请求
|
||||
func DownloadCRL(w http.ResponseWriter, r *http.Request) {
|
||||
// 打开黑名单文件
|
||||
filePath := CRL_FILE_PATH
|
||||
file, err := os.Open(filePath)
|
||||
if err != nil {
|
||||
http.Error(w, "Failed to open blacklist file", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
// 设置响应头,触发浏览器下载行为
|
||||
w.Header().Set("Content-Type", "application/octet-stream")
|
||||
w.Header().Set("Content-Disposition", "attachment; filename=CaRoot.crl")
|
||||
|
||||
// 将文件内容写入响应体
|
||||
if _, err := io.Copy(w, file); err != nil {
|
||||
http.Error(w, "Error streaming the file", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
116
ca-server/internal/handlers/login_handler.go
Normal file
116
ca-server/internal/handlers/login_handler.go
Normal file
@@ -0,0 +1,116 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"ca-mini/pkg/utils"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
type LoginRequest struct {
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
}
|
||||
|
||||
type TokenInfo struct {
|
||||
Token string `json:"token"`
|
||||
}
|
||||
|
||||
type UserInfo struct {
|
||||
Name string `json:"name"`
|
||||
Introduction string `json:"introduction"`
|
||||
Avatar string `json:"avatar"`
|
||||
Roles []string `json:"roles"`
|
||||
}
|
||||
|
||||
type LoginResponse struct {
|
||||
Code int `json:"code"`
|
||||
Message string `json:"message"`
|
||||
Date string `json:"date"`
|
||||
Data TokenInfo `json:"data"`
|
||||
}
|
||||
type LogutResponse struct {
|
||||
Code int `json:"code"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
type UserInfoResponse struct {
|
||||
Code int `json:"code"`
|
||||
Message string `json:"message"`
|
||||
Date string `json:"date"`
|
||||
Data UserInfo `json:"data"`
|
||||
}
|
||||
|
||||
// IssueCertificate 处理证书签发请求
|
||||
func Login(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
// 解析CSR请求
|
||||
var loginRequest LoginRequest
|
||||
if err := json.NewDecoder(r.Body).Decode(&loginRequest); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
// 检查用户
|
||||
if !CheckUser(loginRequest) {
|
||||
http.Error(w, "Unauthorized", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
// 生成Token
|
||||
token, err := utils.GenerateRandomString(32)
|
||||
if err != nil {
|
||||
http.Error(w, fmt.Sprintf("Failed to generate token: %v", err), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
// 返回Token信息
|
||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
response := LoginResponse{
|
||||
Code: http.StatusOK,
|
||||
Message: "Login successful",
|
||||
Date: time.Now().Format(time.RFC3339),
|
||||
Data: TokenInfo{Token: token},
|
||||
}
|
||||
json.NewEncoder(w).Encode(response)
|
||||
}
|
||||
|
||||
func Logout(w http.ResponseWriter, r *http.Request) {
|
||||
// 这里可以添加注销逻辑,例如清除用户会话等
|
||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
response := LogutResponse{
|
||||
Code: http.StatusOK,
|
||||
Message: "Logout successful",
|
||||
}
|
||||
json.NewEncoder(w).Encode(response)
|
||||
}
|
||||
|
||||
// CheckBlacklist 处理黑名单查询请求
|
||||
func CheckUser(login LoginRequest) bool {
|
||||
if login.Username == "admin" && login.Password == "111111" {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func GetUserInfo(w http.ResponseWriter, r *http.Request) {
|
||||
// 假设用户信息存储在某个地方,这里直接返回一个示例用户信息
|
||||
userInfo := UserInfo{
|
||||
Name: "Admin User",
|
||||
Introduction: "This is an admin",
|
||||
Avatar: "https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif",
|
||||
Roles: []string{"admin"},
|
||||
}
|
||||
// 返回Token信息
|
||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
response := UserInfoResponse{
|
||||
Code: http.StatusOK,
|
||||
Message: "Login successful",
|
||||
Date: time.Now().Format(time.RFC3339),
|
||||
Data: userInfo,
|
||||
}
|
||||
json.NewEncoder(w).Encode(response)
|
||||
}
|
||||
107
ca-server/internal/logger/logger.go
Normal file
107
ca-server/internal/logger/logger.go
Normal file
@@ -0,0 +1,107 @@
|
||||
package logger
|
||||
|
||||
import (
|
||||
"ca-mini/internal/config"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
var (
|
||||
// Logger 日志记录器
|
||||
Logger *logrus.Logger
|
||||
// LogFile 日志文件
|
||||
LogFile *os.File
|
||||
// LogFilePath 日志文件路径
|
||||
LogFilePath string
|
||||
// LogFileName 日志文件名
|
||||
LogFileName string
|
||||
// LogFileExt 日志文件扩展名
|
||||
LogFileExt string
|
||||
)
|
||||
|
||||
// InitLogger 初始化日志记录器
|
||||
func InitLogger() {
|
||||
// 创建日志记录器
|
||||
Logger = logrus.New()
|
||||
|
||||
// 显示行号
|
||||
logrus.SetReportCaller(true)
|
||||
|
||||
// 设置日志格式
|
||||
Logger.SetFormatter(&logrus.JSONFormatter{
|
||||
TimestampFormat: "2006-01-02 15:04:05",
|
||||
})
|
||||
|
||||
cfg, err := config.Load()
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to load config: %v", err)
|
||||
}
|
||||
|
||||
// 设置日志级别
|
||||
level, err := logrus.ParseLevel(cfg.Logging.Level)
|
||||
if err != nil {
|
||||
log.Fatalf("Invalid log level: %v", err)
|
||||
}
|
||||
Logger.SetLevel(level)
|
||||
|
||||
// 设置日志输出到文件
|
||||
LogFilePath = cfg.Logging.Path
|
||||
if LogFilePath == "" {
|
||||
LogFilePath = "./logs"
|
||||
}
|
||||
if _, err := os.Stat(LogFilePath); os.IsNotExist(err) {
|
||||
err := os.MkdirAll(LogFilePath, os.ModePerm)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to create log directory: %v", err)
|
||||
}
|
||||
}
|
||||
LogFileName = fmt.Sprintf("ca-server-%s.log", time.Now().Format("2006-01-02"))
|
||||
LogFileExt = ".log"
|
||||
LogFile, err = os.OpenFile(filepath.Join(LogFilePath, LogFileName), os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to open log file: %v", err)
|
||||
}
|
||||
Logger.SetOutput(LogFile)
|
||||
}
|
||||
|
||||
// CloseLogger 关闭日志记录器
|
||||
func CloseLogger() {
|
||||
if LogFile != nil {
|
||||
err := LogFile.Close()
|
||||
if err != nil {
|
||||
if Logger != nil {
|
||||
Logger.Errorf("Failed to close log file: %v", err)
|
||||
} else {
|
||||
log.Printf("Failed to close log file: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// LogDebugf 记录调试日志(格式化)
|
||||
func Debug(format string, args ...interface{}) {
|
||||
if Logger != nil {
|
||||
Logger.Debugf(format, args...)
|
||||
}
|
||||
}
|
||||
|
||||
// LogInfof 记录信息日志(格式化)
|
||||
func Info(format string, args ...interface{}) {
|
||||
if Logger != nil {
|
||||
Logger.Infof(format, args...)
|
||||
}
|
||||
}
|
||||
|
||||
// LogErrorf 记录错误日志(格式化)
|
||||
func Error(format string, args ...interface{}) {
|
||||
if Logger != nil {
|
||||
Logger.Errorf(format, args...)
|
||||
} else {
|
||||
log.Printf(format, args...)
|
||||
}
|
||||
}
|
||||
29
ca-server/internal/middleware/jwt_middleware.go
Normal file
29
ca-server/internal/middleware/jwt_middleware.go
Normal file
@@ -0,0 +1,29 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/dgrijalva/jwt-go"
|
||||
)
|
||||
|
||||
// JWTMiddleware 实现JWT认证中间件
|
||||
func JWTMiddleware(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
tokenString := r.Header.Get("Authorization")
|
||||
if tokenString == "" {
|
||||
http.Error(w, "Authorization header required", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
|
||||
return []byte("your-secret-key"), nil
|
||||
})
|
||||
|
||||
if err != nil || !token.Valid {
|
||||
http.Error(w, "Invalid token", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
97
ca-server/internal/middleware/logger_middleware.go
Normal file
97
ca-server/internal/middleware/logger_middleware.go
Normal file
@@ -0,0 +1,97 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
|
||||
"ca-mini/internal/config"
|
||||
"ca-mini/internal/logger"
|
||||
)
|
||||
|
||||
type RequestInfo struct {
|
||||
Method string `json:"method"`
|
||||
Path string `json:"path"`
|
||||
Header http.Header `json:"header"`
|
||||
Body string `json:"body"`
|
||||
}
|
||||
|
||||
type ResponseInfo struct {
|
||||
Code int `json:"code"`
|
||||
Header http.Header `json:"header"`
|
||||
Body string `json:"body"`
|
||||
}
|
||||
|
||||
// responseWriter 包装 http.ResponseWriter 以捕获状态码和响应体
|
||||
type responseWriter struct {
|
||||
http.ResponseWriter
|
||||
statusCode int
|
||||
body *bytes.Buffer
|
||||
}
|
||||
|
||||
// WriteHeader 捕获状态码
|
||||
func (rw *responseWriter) WriteHeader(code int) {
|
||||
rw.statusCode = code
|
||||
rw.ResponseWriter.WriteHeader(code)
|
||||
}
|
||||
|
||||
// Write 捕获响应体
|
||||
func (rw *responseWriter) Write(b []byte) (int, error) {
|
||||
rw.body.Write(b)
|
||||
return rw.ResponseWriter.Write(b)
|
||||
}
|
||||
|
||||
// LoggerMiddleware 日志中间件
|
||||
func LoggerMiddleware(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
cfg, err := config.Load()
|
||||
if err != nil || cfg.Logging.Level == "debug" {
|
||||
next.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
// 开始计时
|
||||
start := time.Now()
|
||||
|
||||
// 读取请求体并重置 r.Body
|
||||
var requestBody bytes.Buffer
|
||||
_, err = io.Copy(&requestBody, r.Body)
|
||||
if err != nil {
|
||||
logger.Error("Failed to read request body: %v", err)
|
||||
}
|
||||
r.Body = io.NopCloser(&requestBody)
|
||||
|
||||
// 创建一个响应包装器来捕获响应状态码和响应体
|
||||
lrw := &responseWriter{
|
||||
ResponseWriter: w,
|
||||
statusCode: http.StatusOK,
|
||||
body: &bytes.Buffer{},
|
||||
}
|
||||
|
||||
// 处理请求
|
||||
next.ServeHTTP(lrw, r)
|
||||
|
||||
// 统计请求处理时间
|
||||
duration := time.Since(start).Milliseconds()
|
||||
|
||||
// 记录日志
|
||||
logger.Logger.WithFields(logrus.Fields{
|
||||
"request": RequestInfo{
|
||||
Method: r.Method,
|
||||
Path: r.URL.Path,
|
||||
Header: r.Header,
|
||||
Body: requestBody.String(),
|
||||
},
|
||||
"response": ResponseInfo{
|
||||
Code: lrw.statusCode,
|
||||
Header: lrw.Header(),
|
||||
Body: lrw.body.String(),
|
||||
},
|
||||
"duration": fmt.Sprintf("%dms", duration),
|
||||
}).Debug("Request processed")
|
||||
})
|
||||
}
|
||||
23
ca-server/internal/model/model.go
Normal file
23
ca-server/internal/model/model.go
Normal file
@@ -0,0 +1,23 @@
|
||||
package model
|
||||
|
||||
import "time"
|
||||
|
||||
type CertInfo struct {
|
||||
CertId string `json:"certId"`
|
||||
CertCn string `json:"certCn"`
|
||||
CertDn string `json:"certDn"`
|
||||
PublicKeyAlg string `json:"publicKeyAlg"`
|
||||
SignatureAlg string `json:"signatureAlg"`
|
||||
KeyLength int `json:"keyLength"`
|
||||
IssuerCn string `json:"issuerCn"`
|
||||
IssuerDn string `json:"issuerDn"`
|
||||
SerialNumber string `json:"serialNumber"`
|
||||
CertSubAltName string `json:"certSubAltName"`
|
||||
BeforeTime time.Time `json:"beforeTime"`
|
||||
AfterTime time.Time `json:"afterTime"`
|
||||
Version string `json:"version"`
|
||||
PrivateKey string `json:"privateKey"`
|
||||
Csr string `json:"csr"`
|
||||
Cert string `json:"cert"`
|
||||
Ca string `json:"ca"`
|
||||
}
|
||||
56
ca-server/internal/repository/cert_service.go
Normal file
56
ca-server/internal/repository/cert_service.go
Normal file
@@ -0,0 +1,56 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"ca-mini/internal/model"
|
||||
"database/sql"
|
||||
"time"
|
||||
)
|
||||
|
||||
type CertificateRepository struct {
|
||||
db *sql.DB
|
||||
}
|
||||
|
||||
func NewCertificateRepository(db *sql.DB) *CertificateRepository {
|
||||
return &CertificateRepository{db: db}
|
||||
}
|
||||
|
||||
func (r *CertificateRepository) AddCert(cert *model.CertInfo) error {
|
||||
createdAt := time.Now()
|
||||
updatedAt := time.Now()
|
||||
_, err := r.db.Exec(
|
||||
"INSERT INTO certificates (id, serial_number, cert_cn, cert_dn, cert_version, public_key_alg, signature_alg, issuer_cn, issuer_dn, cert_sub_alt_name, algorithm, key_length, csr, private_key, certificate, valid_from, valid_to, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
|
||||
cert.CertId, cert.SerialNumber, cert.CertCn, cert.CertDn, cert.Version, cert.PublicKeyAlg, cert.SignatureAlg, cert.IssuerCn, cert.IssuerDn, cert.CertSubAltName, cert.PublicKeyAlg, 2048, cert.Csr, cert.PrivateKey, cert.Cert, cert.BeforeTime, cert.AfterTime, createdAt, updatedAt,
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
func (r *CertificateRepository) SelectCertById(id string) (*model.CertInfo, error) {
|
||||
var cert model.CertInfo
|
||||
err := r.db.QueryRow(
|
||||
"SELECT id, serial_number, cert_cn, cert_dn, cert_version, public_key_alg, signature_alg, issuer_cn, issuer_dn, cert_sub_alt_name, algorithm, key_length, csr, private_key, certificate, valid_from, valid_to FROM certificates WHERE id = ?",
|
||||
id,
|
||||
).Scan(
|
||||
&cert.CertId, &cert.SerialNumber, &cert.CertCn, &cert.CertDn, &cert.Version, &cert.PublicKeyAlg, &cert.SignatureAlg, &cert.IssuerCn, &cert.IssuerDn, &cert.CertSubAltName, &cert.PublicKeyAlg, &cert.KeyLength, &cert.Csr, &cert.PrivateKey, &cert.Cert, &cert.BeforeTime, &cert.AfterTime,
|
||||
)
|
||||
if err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
return nil, nil // 未找到记录
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return &cert, nil
|
||||
}
|
||||
|
||||
func (r *CertificateRepository) UpdateCert(cert *model.CertInfo) error {
|
||||
updatedAt := time.Now()
|
||||
_, err := r.db.Exec(
|
||||
"UPDATE certificates SET serial_number = ?, cert_cn = ?, cert_dn = ?, cert_version = ?, public_key_alg = ?, signature_alg = ?, issuer_cn = ?, issuer_dn = ?, cert_sub_alt_name = ?, algorithm = ?, key_length = ?, csr = ?, private_key = ?, certificate = ?, valid_from = ?, valid_to = ?, updated_at = ? WHERE id = ?",
|
||||
cert.SerialNumber, cert.CertCn, cert.CertDn, cert.Version, cert.PublicKeyAlg, cert.SignatureAlg, cert.IssuerCn, cert.IssuerDn, cert.CertSubAltName, cert.PublicKeyAlg, cert.KeyLength, cert.Csr, cert.PrivateKey, cert.Cert, cert.BeforeTime, cert.AfterTime, updatedAt, cert.CertId,
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
func (r *CertificateRepository) DeleteCert(id string) error {
|
||||
_, err := r.db.Exec("DELETE FROM certificates WHERE id = ?", id)
|
||||
return err
|
||||
}
|
||||
27
ca-server/internal/routes/api_routes.go
Normal file
27
ca-server/internal/routes/api_routes.go
Normal file
@@ -0,0 +1,27 @@
|
||||
package routes
|
||||
|
||||
import (
|
||||
"ca-mini/internal/handlers"
|
||||
"ca-mini/internal/middleware"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
// SetupAPIRoutes 初始化API路由
|
||||
func SetupAPIRoutes(r *mux.Router) {
|
||||
|
||||
r.HandleFunc("/CaRoot.crl", handlers.DownloadCRL).Methods("GET")
|
||||
r.HandleFunc("/user/login", handlers.Login).Methods("POST")
|
||||
r.HandleFunc("/user/logout", handlers.Logout).Methods("POST")
|
||||
r.HandleFunc("/user/info", handlers.GetUserInfo).Methods("GET")
|
||||
|
||||
api := r.PathPrefix("/api/v1").Subrouter()
|
||||
|
||||
// api.Use(middleware.JWTMiddleware)
|
||||
api.Use(middleware.LoggerMiddleware)
|
||||
|
||||
api.HandleFunc("/certificates", handlers.IssueCertificate).Methods("POST")
|
||||
api.HandleFunc("/blacklist", handlers.CheckBlacklist).Methods("GET")
|
||||
api.HandleFunc("/certificates/{id}", handlers.GetCertificate).Methods("GET")
|
||||
api.HandleFunc("/certificates/{id}", handlers.RevokeCertificate).Methods("DELETE")
|
||||
}
|
||||
Reference in New Issue
Block a user