333 lines
10 KiB
Go
333 lines
10 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"crypto/tls"
|
|
"crypto/x509"
|
|
"log"
|
|
"net"
|
|
"os"
|
|
"time"
|
|
|
|
"gitea.d3m0k1d.ru/d3m0k1d/HellreigN/backend/docs"
|
|
"gitea.d3m0k1d.ru/d3m0k1d/HellreigN/backend/internal/config"
|
|
"gitea.d3m0k1d.ru/d3m0k1d/HellreigN/backend/internal/grpcsrv/collector"
|
|
"gitea.d3m0k1d.ru/d3m0k1d/HellreigN/backend/internal/grpcsrv/commander"
|
|
"gitea.d3m0k1d.ru/d3m0k1d/HellreigN/backend/internal/handlers"
|
|
"gitea.d3m0k1d.ru/d3m0k1d/HellreigN/backend/internal/repository"
|
|
"gitea.d3m0k1d.ru/d3m0k1d/HellreigN/backend/internal/service"
|
|
"gitea.d3m0k1d.ru/d3m0k1d/HellreigN/backend/internal/storage"
|
|
"gitea.d3m0k1d.ru/d3m0k1d/HellreigN/proto/proto"
|
|
"github.com/gin-gonic/gin"
|
|
swaggerFiles "github.com/swaggo/files"
|
|
ginSwagger "github.com/swaggo/gin-swagger"
|
|
"golang.org/x/sync/errgroup"
|
|
"google.golang.org/grpc"
|
|
"google.golang.org/grpc/credentials"
|
|
)
|
|
|
|
// @securityDefinitions.apikey Bearer
|
|
// @in header
|
|
// @name Authorization
|
|
// @description Type "Bearer" followed by a space and the JWT token.
|
|
|
|
func main() {
|
|
cfg_path, ok := os.LookupEnv("CONFIG_FILE")
|
|
if !ok {
|
|
cfg_path = "/etc/hellreign/config.yml"
|
|
}
|
|
cfg, err := config.ImportSettings(cfg_path)
|
|
if err != nil {
|
|
log.Fatalf("Err loading config: %v", err)
|
|
}
|
|
|
|
db, err := storage.Open(cfg.Database.Token_db)
|
|
if err != nil {
|
|
log.Fatalf("Err opening database: %v", err)
|
|
}
|
|
defer db.Close()
|
|
|
|
h := handlers.New(db)
|
|
|
|
// Initialize registration tokens table
|
|
if err := h.Repo.InitRegistrationTokens(); err != nil {
|
|
log.Printf("Warning: failed to initialize registration tokens table: %v", err)
|
|
}
|
|
|
|
// Initialize jobs table
|
|
jobRepo := repository.NewJobRepository(db)
|
|
if err := jobRepo.Init(context.Background()); err != nil {
|
|
log.Printf("Warning: failed to initialize jobs table: %v", err)
|
|
}
|
|
|
|
// Initialize ClickHouse and log repository
|
|
logRepo := repository.NewLogRepository()
|
|
if cfg.Database.Clickhouse_host != "" {
|
|
go func() {
|
|
db, err := storage.OpenClickHouseWithRetry(storage.ClickHouseConfig{
|
|
Host: cfg.Database.Clickhouse_host,
|
|
User: cfg.Database.Clickhouse_user,
|
|
Password: cfg.Database.Clickhouse_password,
|
|
Database: cfg.Database.Clickhouse_database,
|
|
}, 10, 5*time.Second)
|
|
if err != nil {
|
|
log.Printf("Warning: ClickHouse connection failed: %v", err)
|
|
return
|
|
}
|
|
log.Println("ClickHouse connected successfully")
|
|
logRepo.SetDB(db)
|
|
if err := logRepo.Init(context.Background()); err != nil {
|
|
log.Printf("Warning: Failed to initialize logs table: %v", err)
|
|
}
|
|
}()
|
|
}
|
|
|
|
// Initialize Collector gRPC service
|
|
coll := collector.New(logRepo)
|
|
|
|
cmdr := commander.New(jobRepo)
|
|
|
|
// Initialize script interpreter repository and service
|
|
scriptRepo := repository.NewScriptInterpreterRepo(db)
|
|
if err := scriptRepo.Init(context.Background()); err != nil {
|
|
log.Printf("Warning: failed to initialize script interpreters table: %v", err)
|
|
}
|
|
scriptSvc := service.NewScriptServiceWithInterpreters(h.Repo, scriptRepo)
|
|
scriptHandlers := handlers.NewScriptHandlers(scriptSvc, cmdr)
|
|
jobsHandlers := handlers.NewJobsHandlers(cmdr, scriptSvc)
|
|
|
|
// Initialize script management service and handlers
|
|
scriptManageSvc := service.NewScriptService(h.Repo)
|
|
scriptManageHandlers := handlers.NewScriptHandlersGroup(scriptManageSvc, cmdr)
|
|
|
|
agents := handlers.NewAgentsGroup(h, coll)
|
|
auth := handlers.AuthGroup{Handlers: h}
|
|
agentReg := handlers.NewAgentRegistrationGroup(h)
|
|
agentDeploy := handlers.NewAgentDeployGroup(h)
|
|
|
|
// Create admin user from config if not exists
|
|
if cfg.Admin.Admin_login != "" && cfg.Admin.Admin_password != "" {
|
|
if !h.Repo.ExistsByLogin(cfg.Admin.Admin_login) {
|
|
_, err := h.Repo.CreateToken(repository.TokenCreate{
|
|
Name: cfg.Admin.Admin_name,
|
|
LastName: cfg.Admin.Admin_last_name,
|
|
Login: cfg.Admin.Admin_login,
|
|
Password: cfg.Admin.Admin_password,
|
|
PermissionView: true,
|
|
PermissionManage: true,
|
|
PermissionAdmin: true,
|
|
IsActive: true,
|
|
})
|
|
if err != nil {
|
|
log.Printf("Warning: failed to create admin user: %v", err)
|
|
} else {
|
|
log.Println("Admin user created from config")
|
|
}
|
|
} else {
|
|
// Ensure existing admin is activated
|
|
if err := h.Repo.ActivateUserByLogin(cfg.Admin.Admin_login); err != nil {
|
|
log.Printf("Warning: failed to activate admin user: %v", err)
|
|
} else {
|
|
log.Println("Admin user activated")
|
|
}
|
|
}
|
|
}
|
|
|
|
router := gin.Default()
|
|
router.Use(handlers.CorsMiddleware("http://127.0.0.1:5173;http://localhost:5173"))
|
|
docs.SwaggerInfo.BasePath = "/api/v1"
|
|
docs.SwaggerInfo.Title = "HellreigN"
|
|
docs.SwaggerInfo.Version = "1.0"
|
|
docs.SwaggerInfo.Description = "API for HellreigN"
|
|
docs.SwaggerInfo.Schemes = []string{"http"}
|
|
router.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))
|
|
|
|
v1 := router.Group("/api/v1")
|
|
{
|
|
// Auth routes (public)
|
|
authGroup := v1.Group("/auth")
|
|
{
|
|
authGroup.POST("/login", auth.Login)
|
|
authGroup.POST("/register", auth.RegisterUser)
|
|
}
|
|
|
|
// Auth token management (requires auth)
|
|
authTokenGroup := v1.Group("/auth")
|
|
authTokenGroup.Use(auth.AuthMiddleware())
|
|
{
|
|
authTokenGroup.POST("/token", auth.CreateToken)
|
|
authTokenGroup.GET("/validate", auth.ValidateToken)
|
|
authTokenGroup.GET("/tokens", handlers.RequireAdmin(), auth.ListTokens)
|
|
authTokenGroup.DELETE("/token", auth.DeleteMyToken)
|
|
authTokenGroup.DELETE("/tokens/:login", handlers.RequireAdmin(), auth.DeleteToken)
|
|
|
|
// User management (admin only) - Full CRUD
|
|
authTokenGroup.GET("/users/:login", handlers.RequireAdmin(), auth.GetUser)
|
|
authTokenGroup.PUT("/users/:login", handlers.RequireAdmin(), auth.UpdateUser)
|
|
authTokenGroup.PUT(
|
|
"/users/:login/permissions",
|
|
handlers.RequireAdmin(),
|
|
auth.UpdateUserPermissions,
|
|
)
|
|
authTokenGroup.PUT(
|
|
"/users/:login/password",
|
|
handlers.RequireAdmin(),
|
|
auth.ResetUserPassword,
|
|
)
|
|
|
|
// User activation management (admin only)
|
|
authTokenGroup.POST(
|
|
"/users/:login/activate",
|
|
handlers.RequireAdmin(),
|
|
auth.ActivateUser,
|
|
)
|
|
authTokenGroup.POST(
|
|
"/users/:login/deactivate",
|
|
handlers.RequireAdmin(),
|
|
auth.DeactivateUser,
|
|
)
|
|
authTokenGroup.GET("/users/inactive", handlers.RequireAdmin(), auth.ListInactiveUsers)
|
|
}
|
|
|
|
// Agents (requires manage_agent permission)
|
|
agentsGroup := v1.Group("/agents")
|
|
agentsGroup.Use(auth.AuthMiddleware(), handlers.RequireManageAgent())
|
|
{
|
|
agentsGroup.GET("", agents.List)
|
|
}
|
|
|
|
// Jobs (requires admin permission)
|
|
jobsGroup := v1.Group("/jobs")
|
|
jobsGroup.Use(auth.AuthMiddleware(), handlers.RequireAdmin())
|
|
{
|
|
jobsGroup.POST("", jobsHandlers.AddJob)
|
|
}
|
|
|
|
// Agent registration
|
|
agentRegGroup := v1.Group("/agents")
|
|
{
|
|
agentRegGroup.POST("/register", agentReg.Register)
|
|
}
|
|
agentRegTokenGroup := v1.Group("/agents")
|
|
agentRegTokenGroup.Use(auth.AuthMiddleware(), handlers.RequireManageAgent())
|
|
{
|
|
agentRegTokenGroup.POST("/register-token", agentReg.CreateRegistrationToken)
|
|
agentRegTokenGroup.POST("/deploy", agentDeploy.DeployAgents)
|
|
}
|
|
|
|
// Logs (requires view permission)
|
|
logsGroup := v1.Group("/logs")
|
|
logsGroup.Use(auth.AuthMiddleware(), handlers.RequireView())
|
|
{
|
|
// Mock logs endpoint (always available, no ClickHouse required)
|
|
mockLogHandlers := handlers.NewLogHandlers(nil)
|
|
logsGroup.GET("/mock", mockLogHandlers.GetMockLogs)
|
|
|
|
// ClickHouse log handlers (always registered, work when ClickHouse connects)
|
|
logHandlers := handlers.NewLogHandlers(logRepo)
|
|
logsGroup.POST("", logHandlers.Insert)
|
|
logsGroup.POST("/batch", logHandlers.InsertBatch)
|
|
logsGroup.GET("", logHandlers.Search)
|
|
logsGroup.GET("/services", logHandlers.GetServices)
|
|
logsGroup.GET("/agents", logHandlers.GetAgents)
|
|
logsGroup.GET("/levels", logHandlers.GetLevels)
|
|
}
|
|
|
|
// Scripts (requires admin permission)
|
|
scriptsGroup := v1.Group("/scripts")
|
|
scriptsGroup.Use(auth.AuthMiddleware(), handlers.RequireAdmin())
|
|
{
|
|
scriptsGroup.POST("/run", scriptHandlers.RunScript)
|
|
scriptsGroup.GET("/interpreters", scriptHandlers.ListInterpreters)
|
|
scriptsGroup.POST("/interpreters", scriptHandlers.CreateInterpreter)
|
|
scriptsGroup.GET("/interpreters/:id", scriptHandlers.GetInterpreter)
|
|
scriptsGroup.PUT("/interpreters/:id", scriptHandlers.UpdateInterpreter)
|
|
scriptsGroup.DELETE("/interpreters/:id", scriptHandlers.DeleteInterpreter)
|
|
|
|
// Script management (tree, CRUD)
|
|
scriptsGroup.GET("/tree", scriptManageHandlers.GetTree)
|
|
scriptsGroup.POST("", scriptManageHandlers.CreateScript)
|
|
scriptsGroup.GET("/:id", scriptManageHandlers.GetScript)
|
|
scriptsGroup.PUT("/:id", scriptManageHandlers.UpdateScript)
|
|
scriptsGroup.DELETE("/:id", scriptManageHandlers.DeleteScript)
|
|
scriptsGroup.POST("/:id/run", scriptManageHandlers.RunScriptByID)
|
|
}
|
|
}
|
|
|
|
// Start gRPC server with mTLS in background
|
|
grpcPort := os.Getenv("GRPC_PORT")
|
|
if grpcPort == "" {
|
|
grpcPort = "9001"
|
|
}
|
|
|
|
certDir := os.Getenv("SSL_CERT_DIR")
|
|
if certDir == "" {
|
|
certDir = "/var/lib/hellreign/ssl"
|
|
}
|
|
|
|
certFile := certDir + "/server.crt"
|
|
keyFile := certDir + "/server.key"
|
|
caFile := certDir + "/ca.crt"
|
|
|
|
// Load server cert
|
|
cert, err := tls.LoadX509KeyPair(certFile, keyFile)
|
|
if err != nil {
|
|
log.Fatalf("Failed to load server cert: %v", err)
|
|
}
|
|
|
|
// Load CA cert for client verification
|
|
caCert, err := os.ReadFile(caFile)
|
|
if err != nil {
|
|
log.Fatalf("Failed to load CA cert: %v", err)
|
|
}
|
|
caCertPool := x509.NewCertPool()
|
|
caCertPool.AppendCertsFromPEM(caCert)
|
|
|
|
tlsConfig := &tls.Config{
|
|
Certificates: []tls.Certificate{cert},
|
|
ClientCAs: caCertPool,
|
|
ClientAuth: tls.RequireAndVerifyClientCert,
|
|
MinVersion: tls.VersionTLS12,
|
|
}
|
|
|
|
grpcServer := grpc.NewServer(grpc.Creds(credentials.NewTLS(tlsConfig)))
|
|
proto.RegisterCommanderServer(grpcServer, cmdr)
|
|
proto.RegisterCollectorServer(grpcServer, coll)
|
|
|
|
lis, err := net.Listen("tcp", ":"+grpcPort)
|
|
if err != nil {
|
|
log.Fatalf("Failed to listen on gRPC port %s: %v", grpcPort, err)
|
|
}
|
|
|
|
g, ctx := errgroup.WithContext(context.Background())
|
|
|
|
g.Go(func() error {
|
|
log.Printf("gRPC server starting on port %s with mTLS", grpcPort)
|
|
errCh := make(chan error, 1)
|
|
go func() { errCh <- grpcServer.Serve(lis) }()
|
|
select {
|
|
case err := <-errCh:
|
|
return err
|
|
case <-ctx.Done():
|
|
grpcServer.GracefulStop()
|
|
return nil
|
|
}
|
|
})
|
|
|
|
g.Go(func() error {
|
|
log.Printf("HTTP server starting on :8080")
|
|
errCh := make(chan error, 1)
|
|
go func() { errCh <- router.Run(":8080") }()
|
|
select {
|
|
case err := <-errCh:
|
|
return err
|
|
case <-ctx.Done():
|
|
return nil
|
|
}
|
|
})
|
|
|
|
if err := g.Wait(); err != nil {
|
|
log.Fatalf("Server error: %v", err)
|
|
}
|
|
}
|