mirror of
https://github.com/Dvorinka/Containr.git
synced 2026-06-03 20:12:58 +00:00
update
This commit is contained in:
@@ -19,6 +19,17 @@ type BuildHandler struct {
|
||||
dockerClient *docker.Client
|
||||
}
|
||||
|
||||
func (h *BuildHandler) buildUnavailable(c *gin.Context) bool {
|
||||
if h.buildManager != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
c.JSON(http.StatusServiceUnavailable, gin.H{
|
||||
"error": "Build service is unavailable: Docker client not initialized",
|
||||
})
|
||||
return true
|
||||
}
|
||||
|
||||
// NewBuildHandler creates a new build handler
|
||||
func NewBuildHandler(buildManager *build.BuildManager, dockerClient *docker.Client) *BuildHandler {
|
||||
return &BuildHandler{
|
||||
@@ -96,6 +107,10 @@ type BuildListResponse struct {
|
||||
// @Failure 500 {object} map[string]string
|
||||
// @Router /api/v1/builds [post]
|
||||
func (h *BuildHandler) StartBuild(c *gin.Context) {
|
||||
if h.buildUnavailable(c) {
|
||||
return
|
||||
}
|
||||
|
||||
var req BuildRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
@@ -286,6 +301,10 @@ func (h *BuildHandler) GetBuildLogs(c *gin.Context) {
|
||||
// @Failure 500 {object} map[string]string
|
||||
// @Router /api/v1/builds/plan [post]
|
||||
func (h *BuildHandler) GetBuildPlan(c *gin.Context) {
|
||||
if h.buildUnavailable(c) {
|
||||
return
|
||||
}
|
||||
|
||||
var req BuildRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
@@ -327,6 +346,10 @@ func (h *BuildHandler) GetBuildPlan(c *gin.Context) {
|
||||
// @Failure 500 {object} map[string]string
|
||||
// @Router /api/v1/builds/detect [get]
|
||||
func (h *BuildHandler) DetectBuildType(c *gin.Context) {
|
||||
if h.buildUnavailable(c) {
|
||||
return
|
||||
}
|
||||
|
||||
sourcePath := c.Query("source_path")
|
||||
if sourcePath == "" {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "source_path is required"})
|
||||
|
||||
+203
-51
@@ -4,8 +4,8 @@ import (
|
||||
"containr/internal/database"
|
||||
"containr/internal/deployment"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
@@ -139,6 +139,10 @@ func handleCreateDeployment(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
if req.Trigger == "" {
|
||||
req.Trigger = "manual"
|
||||
}
|
||||
|
||||
userID, exists := c.Get("user_id")
|
||||
if !exists {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "User not authenticated"})
|
||||
@@ -172,11 +176,20 @@ func handleCreateDeployment(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
if req.Branch == "" {
|
||||
req.Branch = service.GitBranch
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
var commitHash *string
|
||||
if trimmed := strings.TrimSpace(req.CommitHash); trimmed != "" {
|
||||
commitHash = &trimmed
|
||||
}
|
||||
|
||||
d := DeploymentModel{
|
||||
ID: uuid.New(),
|
||||
ServiceID: serviceID,
|
||||
CommitHash: &req.CommitHash,
|
||||
CommitHash: commitHash,
|
||||
Status: "pending",
|
||||
ImageName: "",
|
||||
ImageTag: "",
|
||||
@@ -184,10 +197,6 @@ func handleCreateDeployment(c *gin.Context) {
|
||||
UpdatedAt: now,
|
||||
}
|
||||
|
||||
if req.CommitHash != "" {
|
||||
d.CommitHash = &req.CommitHash
|
||||
}
|
||||
|
||||
_, err = db.(*database.DB).Exec(
|
||||
`INSERT INTO deployments
|
||||
(id, service_id, commit_hash, status, image_name, image_tag, created_at, updated_at)
|
||||
@@ -200,59 +209,202 @@ func handleCreateDeployment(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
_, err = db.(*database.DB).Exec(
|
||||
`UPDATE services SET status = 'building', updated_at = $1 WHERE id = $2`,
|
||||
time.Now(), serviceID,
|
||||
)
|
||||
if err != nil {
|
||||
}
|
||||
|
||||
engine, exists := c.Get("deployment_engine")
|
||||
if exists && engine != nil {
|
||||
go func() {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Minute)
|
||||
defer cancel()
|
||||
if !exists || engine == nil {
|
||||
unavailableErr := "Deployment engine unavailable. Docker may not be configured on this server."
|
||||
completedAt := time.Now()
|
||||
_, _ = db.(*database.DB).Exec(
|
||||
`UPDATE deployments
|
||||
SET status = 'failed', error = $1, completed_at = $2, updated_at = $2
|
||||
WHERE id = $3`,
|
||||
unavailableErr, completedAt, d.ID,
|
||||
)
|
||||
d.Status = "failed"
|
||||
d.Error = &unavailableErr
|
||||
d.CompletedAt = &completedAt
|
||||
} else {
|
||||
_, err = db.(*database.DB).Exec(
|
||||
`UPDATE services SET status = 'building', updated_at = $1 WHERE id = $2`,
|
||||
time.Now(), serviceID,
|
||||
)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to update service status"})
|
||||
return
|
||||
}
|
||||
|
||||
envVarsJSON, _ := json.Marshal(req.EnvVars)
|
||||
_ = envVarsJSON
|
||||
|
||||
deployReq := &deployment.DeploymentRequest{
|
||||
ProjectID: service.ProjectID.String(),
|
||||
ServiceID: serviceID.String(),
|
||||
Environment: service.Environment,
|
||||
Config: deployment.ServiceConfig{
|
||||
Name: service.Name,
|
||||
Image: service.Image,
|
||||
Environment: req.EnvVars,
|
||||
Replicas: 1,
|
||||
},
|
||||
BuildConfig: &deployment.BuildConfig{
|
||||
BuildType: "nixpacks",
|
||||
SourcePath: service.BuildPath,
|
||||
Branch: service.GitBranch,
|
||||
Commit: req.CommitHash,
|
||||
},
|
||||
Trigger: deployment.TriggerConfig{
|
||||
Type: req.Trigger,
|
||||
Source: "api",
|
||||
User: userID.(string),
|
||||
Timestamp: now,
|
||||
},
|
||||
}
|
||||
|
||||
_, _ = engine.(*deployment.DeploymentEngine).Deploy(ctx, deployReq)
|
||||
}()
|
||||
engineInstance := engine.(*deployment.DeploymentEngine)
|
||||
go runDeploymentAndSync(context.Background(), db.(*database.DB), engineInstance, &d, service, req, userID.(string))
|
||||
}
|
||||
|
||||
c.JSON(http.StatusCreated, DeploymentResponse{
|
||||
ID: d.ID,
|
||||
ServiceID: d.ServiceID,
|
||||
CommitHash: d.CommitHash,
|
||||
Status: d.Status,
|
||||
CreatedAt: d.CreatedAt,
|
||||
ID: d.ID,
|
||||
ServiceID: d.ServiceID,
|
||||
CommitHash: d.CommitHash,
|
||||
Status: d.Status,
|
||||
Error: d.Error,
|
||||
CompletedAt: d.CompletedAt,
|
||||
CreatedAt: d.CreatedAt,
|
||||
})
|
||||
}
|
||||
|
||||
func runDeploymentAndSync(
|
||||
parentCtx context.Context,
|
||||
db *database.DB,
|
||||
engine *deployment.DeploymentEngine,
|
||||
dbDeployment *DeploymentModel,
|
||||
service Service,
|
||||
req CreateDeploymentRequest,
|
||||
userID string,
|
||||
) {
|
||||
ctx, cancel := context.WithTimeout(parentCtx, 30*time.Minute)
|
||||
defer cancel()
|
||||
|
||||
sourcePath := strings.TrimSpace(service.BuildPath)
|
||||
if sourcePath == "" {
|
||||
sourcePath = "."
|
||||
}
|
||||
|
||||
deployReq := &deployment.DeploymentRequest{
|
||||
ProjectID: service.ProjectID.String(),
|
||||
ServiceID: service.ID.String(),
|
||||
Environment: service.Environment,
|
||||
Config: deployment.ServiceConfig{
|
||||
Name: service.Name,
|
||||
Image: service.Image,
|
||||
Environment: req.EnvVars,
|
||||
Replicas: 1,
|
||||
},
|
||||
BuildConfig: &deployment.BuildConfig{
|
||||
BuildType: "nixpacks",
|
||||
SourcePath: sourcePath,
|
||||
Branch: req.Branch,
|
||||
Commit: req.CommitHash,
|
||||
},
|
||||
Trigger: deployment.TriggerConfig{
|
||||
Type: req.Trigger,
|
||||
Source: "api",
|
||||
User: userID,
|
||||
Timestamp: time.Now(),
|
||||
},
|
||||
}
|
||||
|
||||
engineDeployment, err := engine.Deploy(ctx, deployReq)
|
||||
if err != nil {
|
||||
failedAt := time.Now()
|
||||
failure := "Failed to start deployment engine: " + err.Error()
|
||||
_, _ = db.Exec(
|
||||
`UPDATE deployments
|
||||
SET status = 'failed', error = $1, completed_at = $2, updated_at = $2
|
||||
WHERE id = $3`,
|
||||
failure, failedAt, dbDeployment.ID,
|
||||
)
|
||||
_, _ = db.Exec(
|
||||
`UPDATE services SET status = 'failed', updated_at = $1 WHERE id = $2`,
|
||||
failedAt, service.ID,
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
syncTicker := time.NewTicker(1 * time.Second)
|
||||
defer syncTicker.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
failedAt := time.Now()
|
||||
timeoutErr := "Deployment timed out before completion"
|
||||
_, _ = db.Exec(
|
||||
`UPDATE deployments
|
||||
SET status = 'failed', error = $1, completed_at = $2, updated_at = $2
|
||||
WHERE id = $3`,
|
||||
timeoutErr, failedAt, dbDeployment.ID,
|
||||
)
|
||||
_, _ = db.Exec(
|
||||
`UPDATE services SET status = 'failed', updated_at = $1 WHERE id = $2`,
|
||||
failedAt, service.ID,
|
||||
)
|
||||
return
|
||||
case <-syncTicker.C:
|
||||
current, getErr := engine.GetDeployment(engineDeployment.ID)
|
||||
if getErr != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
dbStatus := mapEngineStatusToDBStatus(current.Status)
|
||||
imageName, imageTag := splitImageReference(current.ImageName, dbDeployment.ImageTag)
|
||||
|
||||
var dbError interface{}
|
||||
if current.Error != "" {
|
||||
dbError = current.Error
|
||||
}
|
||||
|
||||
_, _ = db.Exec(
|
||||
`UPDATE deployments
|
||||
SET status = $1,
|
||||
image_name = $2,
|
||||
image_tag = $3,
|
||||
build_log = $4,
|
||||
runtime_log = $5,
|
||||
error = $6,
|
||||
started_at = $7,
|
||||
completed_at = $8,
|
||||
updated_at = $9
|
||||
WHERE id = $10`,
|
||||
dbStatus,
|
||||
imageName,
|
||||
imageTag,
|
||||
current.BuildLog,
|
||||
current.DeployLog,
|
||||
dbError,
|
||||
current.StartedAt,
|
||||
current.CompletedAt,
|
||||
time.Now(),
|
||||
dbDeployment.ID,
|
||||
)
|
||||
|
||||
switch dbStatus {
|
||||
case "deployed":
|
||||
_, _ = db.Exec(
|
||||
`UPDATE services SET status = 'running', updated_at = $1 WHERE id = $2`,
|
||||
time.Now(), service.ID,
|
||||
)
|
||||
return
|
||||
case "failed":
|
||||
_, _ = db.Exec(
|
||||
`UPDATE services SET status = 'failed', updated_at = $1 WHERE id = $2`,
|
||||
time.Now(), service.ID,
|
||||
)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func mapEngineStatusToDBStatus(status string) string {
|
||||
switch status {
|
||||
case "running":
|
||||
return "deployed"
|
||||
case "cancelled":
|
||||
return "failed"
|
||||
default:
|
||||
return status
|
||||
}
|
||||
}
|
||||
|
||||
func splitImageReference(image, fallbackTag string) (string, string) {
|
||||
if image == "" {
|
||||
return "", fallbackTag
|
||||
}
|
||||
|
||||
lastSlash := strings.LastIndex(image, "/")
|
||||
lastColon := strings.LastIndex(image, ":")
|
||||
if lastColon > lastSlash && !strings.Contains(image[lastColon:], "@") {
|
||||
return image[:lastColon], image[lastColon+1:]
|
||||
}
|
||||
|
||||
return image, fallbackTag
|
||||
}
|
||||
|
||||
func handleGetDeployment(c *gin.Context) {
|
||||
db, exists := c.Get("db")
|
||||
if !exists {
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
package api
|
||||
|
||||
import "github.com/gin-gonic/gin"
|
||||
|
||||
// firstPathParam returns the first non-empty route param from the provided names.
|
||||
func firstPathParam(c *gin.Context, names ...string) string {
|
||||
for _, name := range names {
|
||||
if value := c.Param(name); value != "" {
|
||||
return value
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package api
|
||||
|
||||
import (
|
||||
"containr/internal/database"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
@@ -32,7 +33,7 @@ type PreviewEnvironment struct {
|
||||
|
||||
// CreatePreviewEnvironmentRequest represents a request to create a preview environment
|
||||
type CreatePreviewEnvironmentRequest struct {
|
||||
ProjectID uuid.UUID `json:"project_id" binding:"required"`
|
||||
ProjectID uuid.UUID `json:"project_id"`
|
||||
ServiceID uuid.UUID `json:"service_id" binding:"required"`
|
||||
BranchName string `json:"branch_name" binding:"required"`
|
||||
PRNumber *int `json:"pr_number"`
|
||||
@@ -61,7 +62,7 @@ func handleGetPreviewEnvironments(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
projectIDStr := c.Param("project_id")
|
||||
projectIDStr := firstPathParam(c, "id", "project_id", "projectId")
|
||||
projectID, err := uuid.Parse(projectIDStr)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid project ID"})
|
||||
@@ -113,20 +114,29 @@ func handleGetPreviewEnvironments(c *gin.Context) {
|
||||
var environments []PreviewEnvironment
|
||||
for rows.Next() {
|
||||
var env PreviewEnvironment
|
||||
var service Service
|
||||
var serviceID sql.NullString
|
||||
var serviceName sql.NullString
|
||||
var serviceType sql.NullString
|
||||
|
||||
err := rows.Scan(
|
||||
&env.ID, &env.ProjectID, &env.ServiceID, &env.BranchName, &env.PRNumber,
|
||||
&env.Environment, &env.Status, &env.URL, &env.ExpiresAt, &env.CreatedAt, &env.UpdatedAt,
|
||||
&service.ID, &service.Name, &service.Type,
|
||||
&serviceID, &serviceName, &serviceType,
|
||||
)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to scan preview environment"})
|
||||
return
|
||||
}
|
||||
|
||||
if service.ID != uuid.Nil {
|
||||
env.Service = &service
|
||||
if serviceID.Valid {
|
||||
parsedServiceID, parseErr := uuid.Parse(serviceID.String)
|
||||
if parseErr == nil {
|
||||
env.Service = &Service{
|
||||
ID: parsedServiceID,
|
||||
Name: serviceName.String,
|
||||
Type: serviceType.String,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
environments = append(environments, env)
|
||||
@@ -143,12 +153,26 @@ func handleCreatePreviewEnvironment(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
projectIDStr := firstPathParam(c, "id", "project_id", "projectId")
|
||||
projectID, err := uuid.Parse(projectIDStr)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid project ID"})
|
||||
return
|
||||
}
|
||||
|
||||
var req CreatePreviewEnvironmentRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
if req.ProjectID == uuid.Nil {
|
||||
req.ProjectID = projectID
|
||||
} else if req.ProjectID != projectID {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Project ID in URL and request body must match"})
|
||||
return
|
||||
}
|
||||
|
||||
// Get user ID from JWT token
|
||||
userID, exists := c.Get("user_id")
|
||||
if !exists {
|
||||
@@ -158,7 +182,7 @@ func handleCreatePreviewEnvironment(c *gin.Context) {
|
||||
|
||||
// Check if project exists and user has access
|
||||
var project Project
|
||||
err := db.(*database.DB).QueryRow(
|
||||
err = db.(*database.DB).QueryRow(
|
||||
"SELECT id, name, owner_id FROM projects WHERE id = $1",
|
||||
req.ProjectID,
|
||||
).Scan(&project.ID, &project.Name, &project.OwnerID)
|
||||
@@ -268,7 +292,9 @@ func handleGetPreviewEnvironment(c *gin.Context) {
|
||||
|
||||
// Get preview environment with project ownership check
|
||||
var env PreviewEnvironment
|
||||
var serviceName, serviceType string
|
||||
var serviceID sql.NullString
|
||||
var serviceName sql.NullString
|
||||
var serviceType sql.NullString
|
||||
err = db.(*database.DB).QueryRow(
|
||||
`SELECT pe.id, pe.project_id, pe.service_id, pe.branch_name, pe.pr_number,
|
||||
pe.environment, pe.status, pe.url, pe.expires_at, pe.created_at, pe.updated_at,
|
||||
@@ -281,7 +307,7 @@ func handleGetPreviewEnvironment(c *gin.Context) {
|
||||
).Scan(
|
||||
&env.ID, &env.ProjectID, &env.ServiceID, &env.BranchName, &env.PRNumber,
|
||||
&env.Environment, &env.Status, &env.URL, &env.ExpiresAt, &env.CreatedAt, &env.UpdatedAt,
|
||||
&env.ServiceID, &serviceName, &serviceType,
|
||||
&serviceID, &serviceName, &serviceType,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
@@ -290,11 +316,14 @@ func handleGetPreviewEnvironment(c *gin.Context) {
|
||||
}
|
||||
|
||||
// Populate service info if available
|
||||
if env.ServiceID != uuid.Nil {
|
||||
env.Service = &Service{
|
||||
ID: env.ServiceID,
|
||||
Name: serviceName,
|
||||
Type: serviceType,
|
||||
if serviceID.Valid {
|
||||
parsedServiceID, parseErr := uuid.Parse(serviceID.String)
|
||||
if parseErr == nil {
|
||||
env.Service = &Service{
|
||||
ID: parsedServiceID,
|
||||
Name: serviceName.String,
|
||||
Type: serviceType.String,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -21,7 +21,8 @@ import (
|
||||
func SetupRoutes(router *gin.Engine, db *database.DB, redis *database.Redis, cfg *config.Config) {
|
||||
// Initialize Docker client (non-fatal if it fails)
|
||||
var dockerClient *docker.Client
|
||||
buildManager := &build.BuildManager{} // Default empty manager
|
||||
var buildManager *build.BuildManager
|
||||
var deploymentEngine *deployment.DeploymentEngine
|
||||
|
||||
if client, err := docker.NewClient(); err != nil {
|
||||
log.Printf("Warning: Failed to initialize Docker client: %v", err)
|
||||
@@ -29,6 +30,7 @@ func SetupRoutes(router *gin.Engine, db *database.DB, redis *database.Redis, cfg
|
||||
} else {
|
||||
dockerClient = client
|
||||
buildManager = build.NewBuildManager("/tmp/containr-builds", dockerClient)
|
||||
deploymentEngine = deployment.NewDeploymentEngine(buildManager, dockerClient)
|
||||
}
|
||||
|
||||
// Initialize build handler
|
||||
@@ -81,6 +83,9 @@ func SetupRoutes(router *gin.Engine, db *database.DB, redis *database.Redis, cfg
|
||||
c.Set("jwt_secret", cfg.JWTSecret)
|
||||
c.Set("docker_client", dockerClient)
|
||||
c.Set("build_manager", buildManager)
|
||||
if deploymentEngine != nil {
|
||||
c.Set("deployment_engine", deploymentEngine)
|
||||
}
|
||||
c.Set("scheduler", scheduler)
|
||||
c.Set("metrics_collector", metricsCollector)
|
||||
c.Set("auto_scaler", autoScaler)
|
||||
|
||||
+204
-15
@@ -3,6 +3,7 @@ package api
|
||||
import (
|
||||
"containr/internal/database"
|
||||
"containr/internal/security"
|
||||
"database/sql"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
@@ -48,7 +49,35 @@ func (sh *SecurityHandler) StartSecurityScan(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
userID := c.MustGet("user_id").(string)
|
||||
userID, ok := sh.requireProjectAccess(c, req.ProjectID)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
if req.ServiceID != "" {
|
||||
if _, err := uuid.Parse(req.ServiceID); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid service ID"})
|
||||
return
|
||||
}
|
||||
|
||||
var serviceExists bool
|
||||
err := sh.db.QueryRow(
|
||||
`SELECT EXISTS(
|
||||
SELECT 1 FROM services WHERE id = $1 AND project_id = $2
|
||||
)`,
|
||||
req.ServiceID,
|
||||
req.ProjectID,
|
||||
).Scan(&serviceExists)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to validate service"})
|
||||
return
|
||||
}
|
||||
|
||||
if !serviceExists {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Service not found in project"})
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Log audit event
|
||||
sh.auditLogger.LogSecurityEvent(userID, "security_scan_started", "project",
|
||||
@@ -69,7 +98,10 @@ func (sh *SecurityHandler) StartSecurityScan(c *gin.Context) {
|
||||
|
||||
// GetSecurityScan retrieves a security scan
|
||||
func (sh *SecurityHandler) GetSecurityScan(c *gin.Context) {
|
||||
scanID := c.Param("scanId")
|
||||
scanID := firstPathParam(c, "scanId", "id")
|
||||
if !sh.requireSecurityScanAccess(c, scanID) {
|
||||
return
|
||||
}
|
||||
|
||||
scan, err := sh.scanner.GetSecurityScan(scanID)
|
||||
if err != nil {
|
||||
@@ -82,11 +114,14 @@ func (sh *SecurityHandler) GetSecurityScan(c *gin.Context) {
|
||||
|
||||
// GetProjectSecurityHistory retrieves security scan history for a project
|
||||
func (sh *SecurityHandler) GetProjectSecurityHistory(c *gin.Context) {
|
||||
projectID := c.Param("projectId")
|
||||
projectID := firstPathParam(c, "projectId", "id", "project_id")
|
||||
if _, ok := sh.requireProjectAccess(c, projectID); !ok {
|
||||
return
|
||||
}
|
||||
|
||||
limit := 10
|
||||
if limitStr := c.Query("limit"); limitStr != "" {
|
||||
if parsedLimit, err := strconv.Atoi(limitStr); err == nil && parsedLimit > 0 {
|
||||
if parsedLimit, err := strconv.Atoi(limitStr); err == nil && parsedLimit > 0 && parsedLimit <= 1000 {
|
||||
limit = parsedLimit
|
||||
}
|
||||
}
|
||||
@@ -102,7 +137,10 @@ func (sh *SecurityHandler) GetProjectSecurityHistory(c *gin.Context) {
|
||||
|
||||
// GetVulnerabilities retrieves vulnerabilities for a project
|
||||
func (sh *SecurityHandler) GetVulnerabilities(c *gin.Context) {
|
||||
projectID := c.Param("projectId")
|
||||
projectID := firstPathParam(c, "projectId", "id", "project_id")
|
||||
if _, ok := sh.requireProjectAccess(c, projectID); !ok {
|
||||
return
|
||||
}
|
||||
|
||||
// Query vulnerabilities
|
||||
rows, err := sh.db.Query(`
|
||||
@@ -146,8 +184,11 @@ func (sh *SecurityHandler) GetVulnerabilities(c *gin.Context) {
|
||||
|
||||
// UpdateVulnerability updates a vulnerability status
|
||||
func (sh *SecurityHandler) UpdateVulnerability(c *gin.Context) {
|
||||
vulnID := c.Param("vulnId")
|
||||
userID := c.MustGet("user_id").(string)
|
||||
vulnID := firstPathParam(c, "vulnId", "id")
|
||||
userID, ok := sh.requireVulnerabilityAccess(c, vulnID)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
var req struct {
|
||||
Status string `json:"status" binding:"required,oneof=open resolved ignored"`
|
||||
@@ -199,7 +240,31 @@ func (sh *SecurityHandler) StartComplianceAssessment(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
userID := c.MustGet("user_id").(string)
|
||||
userID, ok := sh.requireProjectAccess(c, req.ProjectID)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
if _, err := uuid.Parse(req.FrameworkID); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid framework ID"})
|
||||
return
|
||||
}
|
||||
|
||||
var frameworkExists bool
|
||||
err := sh.db.QueryRow(
|
||||
`SELECT EXISTS(
|
||||
SELECT 1 FROM compliance_frameworks WHERE id = $1
|
||||
)`,
|
||||
req.FrameworkID,
|
||||
).Scan(&frameworkExists)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to validate framework"})
|
||||
return
|
||||
}
|
||||
if !frameworkExists {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "Compliance framework not found"})
|
||||
return
|
||||
}
|
||||
|
||||
// Log audit event
|
||||
sh.auditLogger.LogSecurityEvent(userID, "compliance_assessment_started", "project",
|
||||
@@ -219,7 +284,10 @@ func (sh *SecurityHandler) StartComplianceAssessment(c *gin.Context) {
|
||||
|
||||
// GetComplianceReport retrieves a compliance report
|
||||
func (sh *SecurityHandler) GetComplianceReport(c *gin.Context) {
|
||||
reportID := c.Param("reportId")
|
||||
reportID := firstPathParam(c, "reportId", "id")
|
||||
if !sh.requireComplianceReportAccess(c, reportID) {
|
||||
return
|
||||
}
|
||||
|
||||
report, err := sh.complianceManager.GetComplianceReport(reportID)
|
||||
if err != nil {
|
||||
@@ -280,7 +348,10 @@ func (sh *SecurityHandler) InitializeGDPRFramework(c *gin.Context) {
|
||||
|
||||
// GetSecurityMetrics retrieves security metrics for a project
|
||||
func (sh *SecurityHandler) GetSecurityMetrics(c *gin.Context) {
|
||||
projectID := c.Param("projectId")
|
||||
projectID := firstPathParam(c, "projectId", "id", "project_id")
|
||||
if _, ok := sh.requireProjectAccess(c, projectID); !ok {
|
||||
return
|
||||
}
|
||||
|
||||
// Get vulnerability counts
|
||||
var vulnMetrics struct {
|
||||
@@ -293,7 +364,7 @@ func (sh *SecurityHandler) GetSecurityMetrics(c *gin.Context) {
|
||||
Resolved int `json:"resolved"`
|
||||
}
|
||||
|
||||
sh.db.QueryRow(`
|
||||
err := sh.db.QueryRow(`
|
||||
SELECT
|
||||
COUNT(*) as total,
|
||||
COUNT(*) FILTER (WHERE severity = 'critical') as critical,
|
||||
@@ -306,6 +377,10 @@ func (sh *SecurityHandler) GetSecurityMetrics(c *gin.Context) {
|
||||
WHERE project_id = $1
|
||||
`, projectID).Scan(&vulnMetrics.Total, &vulnMetrics.Critical, &vulnMetrics.High,
|
||||
&vulnMetrics.Medium, &vulnMetrics.Low, &vulnMetrics.Open, &vulnMetrics.Resolved)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to get vulnerability metrics"})
|
||||
return
|
||||
}
|
||||
|
||||
// Get latest scan
|
||||
var latestScan struct {
|
||||
@@ -315,7 +390,7 @@ func (sh *SecurityHandler) GetSecurityMetrics(c *gin.Context) {
|
||||
Status string `json:"status"`
|
||||
}
|
||||
|
||||
err := sh.db.QueryRow(`
|
||||
err = sh.db.QueryRow(`
|
||||
SELECT id, score, started_at as scanned_at, status
|
||||
FROM security_scans
|
||||
WHERE project_id = $1
|
||||
@@ -323,13 +398,16 @@ func (sh *SecurityHandler) GetSecurityMetrics(c *gin.Context) {
|
||||
LIMIT 1
|
||||
`, projectID).Scan(&latestScan.ID, &latestScan.Score, &latestScan.ScannedAt, &latestScan.Status)
|
||||
|
||||
if err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
latestScan = struct {
|
||||
ID string `json:"id"`
|
||||
Score int `json:"score"`
|
||||
ScannedAt time.Time `json:"scanned_at"`
|
||||
Status string `json:"status"`
|
||||
}{ID: "", Score: 0, ScannedAt: time.Time{}, Status: "never_scanned"}
|
||||
} else if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to get latest scan"})
|
||||
return
|
||||
}
|
||||
|
||||
// Get compliance status
|
||||
@@ -347,12 +425,15 @@ func (sh *SecurityHandler) GetSecurityMetrics(c *gin.Context) {
|
||||
LIMIT 1
|
||||
`, projectID).Scan(&complianceStatus.OverallStatus, &complianceStatus.Score, &complianceStatus.LastAssessed)
|
||||
|
||||
if err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
complianceStatus = struct {
|
||||
OverallStatus string `json:"overall_status"`
|
||||
Score int `json:"score"`
|
||||
LastAssessed *time.Time `json:"last_assessed"`
|
||||
}{OverallStatus: "not_assessed", Score: 0, LastAssessed: nil}
|
||||
} else if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to get compliance status"})
|
||||
return
|
||||
}
|
||||
|
||||
metrics := gin.H{
|
||||
@@ -386,7 +467,10 @@ func (sh *SecurityHandler) calculateOverallSecurityScore(vulnMetrics struct {
|
||||
|
||||
// GetAuditLogs retrieves audit logs for security events
|
||||
func (sh *SecurityHandler) GetAuditLogs(c *gin.Context) {
|
||||
_ = c.Param("projectId") // projectID is available but not used in this implementation
|
||||
projectID := firstPathParam(c, "projectId", "id", "project_id")
|
||||
if _, ok := sh.requireProjectAccess(c, projectID); !ok {
|
||||
return
|
||||
}
|
||||
|
||||
limit := 50
|
||||
if limitStr := c.Query("limit"); limitStr != "" {
|
||||
@@ -414,6 +498,111 @@ func (sh *SecurityHandler) GetAuditLogs(c *gin.Context) {
|
||||
})
|
||||
}
|
||||
|
||||
func (sh *SecurityHandler) requireProjectAccess(c *gin.Context, projectID string) (string, bool) {
|
||||
userIDValue, exists := c.Get("user_id")
|
||||
if !exists {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "User not authenticated"})
|
||||
return "", false
|
||||
}
|
||||
userID, ok := userIDValue.(string)
|
||||
if !ok || userID == "" {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid user context"})
|
||||
return "", false
|
||||
}
|
||||
|
||||
if _, err := uuid.Parse(projectID); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid project ID"})
|
||||
return "", false
|
||||
}
|
||||
|
||||
var hasAccess bool
|
||||
err := sh.db.QueryRow(
|
||||
`SELECT EXISTS (
|
||||
SELECT 1
|
||||
FROM projects p
|
||||
WHERE p.id = $1
|
||||
AND (p.owner_id = $2 OR EXISTS (
|
||||
SELECT 1 FROM project_members pm
|
||||
WHERE pm.project_id = p.id AND pm.user_id = $2
|
||||
))
|
||||
)`,
|
||||
projectID, userID,
|
||||
).Scan(&hasAccess)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to verify project access"})
|
||||
return "", false
|
||||
}
|
||||
|
||||
if !hasAccess {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "Project not found"})
|
||||
return "", false
|
||||
}
|
||||
|
||||
return userID, true
|
||||
}
|
||||
|
||||
func (sh *SecurityHandler) requireSecurityScanAccess(c *gin.Context, scanID string) bool {
|
||||
if _, err := uuid.Parse(scanID); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid scan ID"})
|
||||
return false
|
||||
}
|
||||
|
||||
var projectID string
|
||||
err := sh.db.QueryRow("SELECT project_id FROM security_scans WHERE id = $1", scanID).Scan(&projectID)
|
||||
if err == sql.ErrNoRows {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "Security scan not found"})
|
||||
return false
|
||||
}
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to verify scan access"})
|
||||
return false
|
||||
}
|
||||
|
||||
_, ok := sh.requireProjectAccess(c, projectID)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (sh *SecurityHandler) requireComplianceReportAccess(c *gin.Context, reportID string) bool {
|
||||
if _, err := uuid.Parse(reportID); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid report ID"})
|
||||
return false
|
||||
}
|
||||
|
||||
var projectID string
|
||||
err := sh.db.QueryRow("SELECT project_id FROM compliance_reports WHERE id = $1", reportID).Scan(&projectID)
|
||||
if err == sql.ErrNoRows {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "Compliance report not found"})
|
||||
return false
|
||||
}
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to verify report access"})
|
||||
return false
|
||||
}
|
||||
|
||||
_, ok := sh.requireProjectAccess(c, projectID)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (sh *SecurityHandler) requireVulnerabilityAccess(c *gin.Context, vulnID string) (string, bool) {
|
||||
if _, err := uuid.Parse(vulnID); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid vulnerability ID"})
|
||||
return "", false
|
||||
}
|
||||
|
||||
var projectID string
|
||||
err := sh.db.QueryRow("SELECT project_id FROM vulnerabilities WHERE id = $1", vulnID).Scan(&projectID)
|
||||
if err == sql.ErrNoRows {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "Vulnerability not found"})
|
||||
return "", false
|
||||
}
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to verify vulnerability access"})
|
||||
return "", false
|
||||
}
|
||||
|
||||
return sh.requireProjectAccess(c, projectID)
|
||||
}
|
||||
|
||||
// max helper function
|
||||
func max(a, b int) int {
|
||||
if a > b {
|
||||
|
||||
@@ -30,7 +30,7 @@ type Service struct {
|
||||
|
||||
// CreateServiceRequest represents a request to create a service
|
||||
type CreateServiceRequest struct {
|
||||
ProjectID uuid.UUID `json:"project_id" binding:"required"`
|
||||
ProjectID uuid.UUID `json:"project_id"`
|
||||
Name string `json:"name" binding:"required,min=1,max=255"`
|
||||
Type string `json:"type" binding:"required,oneof=web worker database cron"`
|
||||
Image string `json:"image"`
|
||||
@@ -65,7 +65,7 @@ func handleGetServices(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
projectIDStr := c.Param("project_id")
|
||||
projectIDStr := firstPathParam(c, "id", "project_id", "projectId")
|
||||
projectID, err := uuid.Parse(projectIDStr)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid project ID"})
|
||||
@@ -139,12 +139,26 @@ func handleCreateService(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
projectIDStr := firstPathParam(c, "id", "project_id", "projectId")
|
||||
projectID, err := uuid.Parse(projectIDStr)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid project ID"})
|
||||
return
|
||||
}
|
||||
|
||||
var req CreateServiceRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
if req.ProjectID == uuid.Nil {
|
||||
req.ProjectID = projectID
|
||||
} else if req.ProjectID != projectID {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Project ID in URL and request body must match"})
|
||||
return
|
||||
}
|
||||
|
||||
// Get user ID from JWT token
|
||||
userID, exists := c.Get("user_id")
|
||||
if !exists {
|
||||
@@ -154,7 +168,7 @@ func handleCreateService(c *gin.Context) {
|
||||
|
||||
// Check if project exists and user has access
|
||||
var project Project
|
||||
err := db.(*database.DB).QueryRow(
|
||||
err = db.(*database.DB).QueryRow(
|
||||
"SELECT id, name, owner_id FROM projects WHERE id = $1",
|
||||
req.ProjectID,
|
||||
).Scan(&project.ID, &project.Name, &project.OwnerID)
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
@@ -13,11 +14,11 @@ import (
|
||||
|
||||
// DNSServer provides internal DNS resolution for services
|
||||
type DNSServer struct {
|
||||
server *dns.Server
|
||||
server *dns.Server
|
||||
serviceDiscovery *ServiceDiscovery
|
||||
domain string
|
||||
addresses []string
|
||||
mu sync.RWMutex
|
||||
domain string
|
||||
addresses []string
|
||||
mu sync.RWMutex
|
||||
}
|
||||
|
||||
// DNSConfig holds DNS server configuration
|
||||
@@ -31,8 +32,8 @@ type DNSConfig struct {
|
||||
// NewDNSServer creates a new DNS server
|
||||
func NewDNSServer(config DNSConfig, serviceDiscovery *ServiceDiscovery) *DNSServer {
|
||||
return &DNSServer{
|
||||
domain: config.Domain,
|
||||
addresses: config.Addresses,
|
||||
domain: config.Domain,
|
||||
addresses: config.Addresses,
|
||||
serviceDiscovery: serviceDiscovery,
|
||||
}
|
||||
}
|
||||
@@ -309,7 +310,7 @@ func (nu *NetworkUtils) GetLocalIP() (string, error) {
|
||||
|
||||
// IsPortOpen checks if a port is open on a host
|
||||
func (nu *NetworkUtils) IsPortOpen(host string, port int, timeout time.Duration) bool {
|
||||
address := fmt.Sprintf("%s:%d", host, port)
|
||||
address := net.JoinHostPort(host, strconv.Itoa(port))
|
||||
conn, err := net.DialTimeout("tcp", address, timeout)
|
||||
if err != nil {
|
||||
return false
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
@@ -354,7 +355,7 @@ func (sd *ServiceDiscovery) startHealthCheck(instance *ServiceInstance) {
|
||||
func (sd *ServiceDiscovery) checkInstanceHealth(ctx context.Context, instance *ServiceInstance) bool {
|
||||
// Simple TCP connection check
|
||||
if instance.Port > 0 {
|
||||
address := fmt.Sprintf("%s:%d", instance.IPAddress, instance.Port)
|
||||
address := net.JoinHostPort(instance.IPAddress, strconv.Itoa(instance.Port))
|
||||
conn, err := net.DialTimeout("tcp", address, 5*time.Second)
|
||||
if err != nil {
|
||||
return false
|
||||
|
||||
+138
-138
@@ -4,138 +4,138 @@ import "time"
|
||||
|
||||
// Node represents a Proxmox cluster node
|
||||
type Node struct {
|
||||
Node string `json:"node"`
|
||||
Status string `json:"status"`
|
||||
Node string `json:"node"`
|
||||
Status string `json:"status"`
|
||||
CPU float64 `json:"cpu"`
|
||||
Memory int `json:"mem"`
|
||||
MemoryUsed int `json:"mem"`
|
||||
MaxMemory int `json:"maxmem"`
|
||||
Disk int `json:"disk"`
|
||||
DiskUsed int `json:"diskused"`
|
||||
MaxDisk int `json:"maxdisk"`
|
||||
Uptime int `json:"uptime"`
|
||||
Level string `json:"level"`
|
||||
ID string `json:"id"`
|
||||
Type string `json:"type"`
|
||||
Memory int `json:"-"`
|
||||
MemoryUsed int `json:"mem"`
|
||||
MaxMemory int `json:"maxmem"`
|
||||
Disk int `json:"disk"`
|
||||
DiskUsed int `json:"diskused"`
|
||||
MaxDisk int `json:"maxdisk"`
|
||||
Uptime int `json:"uptime"`
|
||||
Level string `json:"level"`
|
||||
ID string `json:"id"`
|
||||
Type string `json:"type"`
|
||||
}
|
||||
|
||||
// VM represents a virtual machine in Proxmox
|
||||
type VM struct {
|
||||
VMID int `json:"vmid"`
|
||||
Name string `json:"name"`
|
||||
Status string `json:"status"`
|
||||
CPU float64 `json:"cpu"`
|
||||
Memory int `json:"mem"`
|
||||
MemoryUsed int `json:"maxmem"`
|
||||
Disk int `json:"disk"`
|
||||
DiskUsed int `json:"maxdisk"`
|
||||
Uptime int `json:"uptime"`
|
||||
Template bool `json:"template"`
|
||||
Node string `json:"node"`
|
||||
Type string `json:"type"`
|
||||
NetIn int64 `json:"netin"`
|
||||
NetOut int64 `json:"netout"`
|
||||
DiskRead int64 `json:"diskread"`
|
||||
DiskWrite int64 `json:"diskwrite"`
|
||||
CPUUsage float64 `json:"cpuusage"`
|
||||
VMID int `json:"vmid"`
|
||||
Name string `json:"name"`
|
||||
Status string `json:"status"`
|
||||
CPU float64 `json:"cpu"`
|
||||
Memory int `json:"mem"`
|
||||
MemoryUsed int `json:"maxmem"`
|
||||
Disk int `json:"disk"`
|
||||
DiskUsed int `json:"maxdisk"`
|
||||
Uptime int `json:"uptime"`
|
||||
Template bool `json:"template"`
|
||||
Node string `json:"node"`
|
||||
Type string `json:"type"`
|
||||
NetIn int64 `json:"netin"`
|
||||
NetOut int64 `json:"netout"`
|
||||
DiskRead int64 `json:"diskread"`
|
||||
DiskWrite int64 `json:"diskwrite"`
|
||||
CPUUsage float64 `json:"cpuusage"`
|
||||
}
|
||||
|
||||
// Container represents an LXC container in Proxmox
|
||||
type Container struct {
|
||||
VMID int `json:"vmid"`
|
||||
Name string `json:"name"`
|
||||
Status string `json:"status"`
|
||||
CPU float64 `json:"cpu"`
|
||||
Memory int `json:"mem"`
|
||||
MemoryUsed int `json:"maxmem"`
|
||||
Disk int `json:"disk"`
|
||||
DiskUsed int `json:"maxdisk"`
|
||||
Uptime int `json:"uptime"`
|
||||
Template bool `json:"template"`
|
||||
Node string `json:"node"`
|
||||
Type string `json:"type"`
|
||||
NetIn int64 `json:"netin"`
|
||||
NetOut int64 `json:"netout"`
|
||||
DiskRead int64 `json:"diskread"`
|
||||
DiskWrite int64 `json:"diskwrite"`
|
||||
CPUUsage float64 `json:"cpuusage"`
|
||||
VMID int `json:"vmid"`
|
||||
Name string `json:"name"`
|
||||
Status string `json:"status"`
|
||||
CPU float64 `json:"cpu"`
|
||||
Memory int `json:"mem"`
|
||||
MemoryUsed int `json:"maxmem"`
|
||||
Disk int `json:"disk"`
|
||||
DiskUsed int `json:"maxdisk"`
|
||||
Uptime int `json:"uptime"`
|
||||
Template bool `json:"template"`
|
||||
Node string `json:"node"`
|
||||
Type string `json:"type"`
|
||||
NetIn int64 `json:"netin"`
|
||||
NetOut int64 `json:"netout"`
|
||||
DiskRead int64 `json:"diskread"`
|
||||
DiskWrite int64 `json:"diskwrite"`
|
||||
CPUUsage float64 `json:"cpuusage"`
|
||||
}
|
||||
|
||||
// VMConfig represents the configuration for creating a VM
|
||||
type VMConfig struct {
|
||||
VMID int `json:"vmid"`
|
||||
Name string `json:"name"`
|
||||
Memory int `json:"memory"`
|
||||
Cores int `json:"cores"`
|
||||
DiskSize int `json:"disk_size"` // in GB
|
||||
Storage string `json:"storage"`
|
||||
NetworkBridge string `json:"network_bridge"`
|
||||
Template string `json:"template,omitempty"`
|
||||
VMID int `json:"vmid"`
|
||||
Name string `json:"name"`
|
||||
Memory int `json:"memory"`
|
||||
Cores int `json:"cores"`
|
||||
DiskSize int `json:"disk_size"` // in GB
|
||||
Storage string `json:"storage"`
|
||||
NetworkBridge string `json:"network_bridge"`
|
||||
Template string `json:"template,omitempty"`
|
||||
}
|
||||
|
||||
// ContainerConfig represents the configuration for creating an LXC container
|
||||
type ContainerConfig struct {
|
||||
VMID int `json:"vmid"`
|
||||
Hostname string `json:"hostname"`
|
||||
Memory int `json:"memory"`
|
||||
Cores int `json:"cores"`
|
||||
DiskSize int `json:"disk_size"` // in GB
|
||||
Storage string `json:"storage"`
|
||||
NetworkBridge string `json:"network_bridge"`
|
||||
Template string `json:"template"`
|
||||
VMID int `json:"vmid"`
|
||||
Hostname string `json:"hostname"`
|
||||
Memory int `json:"memory"`
|
||||
Cores int `json:"cores"`
|
||||
DiskSize int `json:"disk_size"` // in GB
|
||||
Storage string `json:"storage"`
|
||||
NetworkBridge string `json:"network_bridge"`
|
||||
Template string `json:"template"`
|
||||
}
|
||||
|
||||
// VMStatus represents the current status of a VM
|
||||
type VMStatus struct {
|
||||
VMID int `json:"vmid"`
|
||||
Name string `json:"name"`
|
||||
Status string `json:"status"`
|
||||
VMID int `json:"vmid"`
|
||||
Name string `json:"name"`
|
||||
Status string `json:"status"`
|
||||
CPU float64 `json:"cpu"`
|
||||
Memory int `json:"mem"`
|
||||
MemoryUsed int `json:"maxmem"`
|
||||
Disk int `json:"disk"`
|
||||
DiskUsed int `json:"maxdisk"`
|
||||
Uptime int `json:"uptime"`
|
||||
Lock string `json:"lock,omitempty"`
|
||||
HA bool `json:"ha"`
|
||||
QMPStatus string `json:"qmpstatus"`
|
||||
Spice bool `json:"spice"`
|
||||
Template bool `json:"template"`
|
||||
Agent bool `json:"agent"`
|
||||
Memory int `json:"mem"`
|
||||
MemoryUsed int `json:"maxmem"`
|
||||
Disk int `json:"disk"`
|
||||
DiskUsed int `json:"maxdisk"`
|
||||
Uptime int `json:"uptime"`
|
||||
Lock string `json:"lock,omitempty"`
|
||||
HA bool `json:"ha"`
|
||||
QMPStatus string `json:"qmpstatus"`
|
||||
Spice bool `json:"spice"`
|
||||
Template bool `json:"template"`
|
||||
Agent bool `json:"agent"`
|
||||
}
|
||||
|
||||
// ContainerStatus represents the current status of a container
|
||||
type ContainerStatus struct {
|
||||
VMID int `json:"vmid"`
|
||||
Name string `json:"name"`
|
||||
Status string `json:"status"`
|
||||
VMID int `json:"vmid"`
|
||||
Name string `json:"name"`
|
||||
Status string `json:"status"`
|
||||
CPU float64 `json:"cpu"`
|
||||
Memory int `json:"mem"`
|
||||
MemoryUsed int `json:"maxmem"`
|
||||
Disk int `json:"disk"`
|
||||
DiskUsed int `json:"maxdisk"`
|
||||
Uptime int `json:"uptime"`
|
||||
Lock string `json:"lock,omitempty"`
|
||||
HA bool `json:"ha"`
|
||||
Template bool `json:"template"`
|
||||
Memory int `json:"mem"`
|
||||
MemoryUsed int `json:"maxmem"`
|
||||
Disk int `json:"disk"`
|
||||
DiskUsed int `json:"maxdisk"`
|
||||
Uptime int `json:"uptime"`
|
||||
Lock string `json:"lock,omitempty"`
|
||||
HA bool `json:"ha"`
|
||||
Template bool `json:"template"`
|
||||
}
|
||||
|
||||
// NodeStats represents detailed statistics for a node
|
||||
type NodeStats struct {
|
||||
Node string `json:"node"`
|
||||
Status string `json:"status"`
|
||||
CPU float64 `json:"cpu"`
|
||||
MemoryTotal int `json:"memory_total"`
|
||||
MemoryUsed int `json:"memory_used"`
|
||||
MemoryFree int `json:"memory_free"`
|
||||
DiskTotal int `json:"disk_total"`
|
||||
DiskUsed int `json:"disk_used"`
|
||||
DiskFree int `json:"disk_free"`
|
||||
Uptime int `json:"uptime"`
|
||||
LoadAverage []float64 `json:"load_average"`
|
||||
NetworkIn int64 `json:"network_in"`
|
||||
NetworkOut int64 `json:"network_out"`
|
||||
LastUpdate time.Time `json:"last_update"`
|
||||
Node string `json:"node"`
|
||||
Status string `json:"status"`
|
||||
CPU float64 `json:"cpu"`
|
||||
MemoryTotal int `json:"memory_total"`
|
||||
MemoryUsed int `json:"memory_used"`
|
||||
MemoryFree int `json:"memory_free"`
|
||||
DiskTotal int `json:"disk_total"`
|
||||
DiskUsed int `json:"disk_used"`
|
||||
DiskFree int `json:"disk_free"`
|
||||
Uptime int `json:"uptime"`
|
||||
LoadAverage []float64 `json:"load_average"`
|
||||
NetworkIn int64 `json:"network_in"`
|
||||
NetworkOut int64 `json:"network_out"`
|
||||
LastUpdate time.Time `json:"last_update"`
|
||||
}
|
||||
|
||||
// VMTemplate represents a VM template that can be cloned
|
||||
@@ -165,17 +165,17 @@ type ContainerTemplate struct {
|
||||
|
||||
// StorageInfo represents storage information on a node
|
||||
type StorageInfo struct {
|
||||
Storage string `json:"storage"`
|
||||
Node string `json:"node"`
|
||||
Type string `json:"type"`
|
||||
Total int `json:"total"`
|
||||
Used int `json:"used"`
|
||||
Available int `json:"avail"`
|
||||
Shared bool `json:"shared"`
|
||||
Content string `json:"content"`
|
||||
Active bool `json:"active"`
|
||||
Enabled bool `json:"enabled"`
|
||||
ReadOnly bool `json:"read_only"`
|
||||
Storage string `json:"storage"`
|
||||
Node string `json:"node"`
|
||||
Type string `json:"type"`
|
||||
Total int `json:"total"`
|
||||
Used int `json:"used"`
|
||||
Available int `json:"avail"`
|
||||
Shared bool `json:"shared"`
|
||||
Content string `json:"content"`
|
||||
Active bool `json:"active"`
|
||||
Enabled bool `json:"enabled"`
|
||||
ReadOnly bool `json:"read_only"`
|
||||
}
|
||||
|
||||
// NetworkInfo represents network interface information
|
||||
@@ -193,15 +193,15 @@ type NetworkInfo struct {
|
||||
|
||||
// TaskInfo represents a task running on Proxmox
|
||||
type TaskInfo struct {
|
||||
UPID string `json:"upid"`
|
||||
Node string `json:"node"`
|
||||
Type string `json:"type"`
|
||||
Status string `json:"status"`
|
||||
User string `json:"user"`
|
||||
StartTime time.Time `json:"starttime"`
|
||||
EndTime time.Time `json:"endtime"`
|
||||
Duration string `json:"duration"`
|
||||
PID int `json:"pid"`
|
||||
UPID string `json:"upid"`
|
||||
Node string `json:"node"`
|
||||
Type string `json:"type"`
|
||||
Status string `json:"status"`
|
||||
User string `json:"user"`
|
||||
StartTime time.Time `json:"starttime"`
|
||||
EndTime time.Time `json:"endtime"`
|
||||
Duration string `json:"duration"`
|
||||
PID int `json:"pid"`
|
||||
}
|
||||
|
||||
// ClusterInfo represents cluster information
|
||||
@@ -226,23 +226,23 @@ type Resource struct {
|
||||
|
||||
// Pool represents a resource pool
|
||||
type Pool struct {
|
||||
PoolID string `json:"poolid"`
|
||||
Name string `json:"name"`
|
||||
PoolID string `json:"poolid"`
|
||||
Name string `json:"name"`
|
||||
Comment string `json:"comment,omitempty"`
|
||||
}
|
||||
|
||||
// User represents a Proxmox user
|
||||
type User struct {
|
||||
UserID string `json:"userid"`
|
||||
Realm string `json:"realm"`
|
||||
Enabled bool `json:"enabled"`
|
||||
Email string `json:"email,omitempty"`
|
||||
FirstName string `json:"firstname,omitempty"`
|
||||
LastName string `json:"lastname,omitempty"`
|
||||
Groups []string `json:"groups,omitempty"`
|
||||
Comment string `json:"comment,omitempty"`
|
||||
Expire int `json:"expire,omitempty"`
|
||||
LastLogin int64 `json:"last_login,omitempty"`
|
||||
UserID string `json:"userid"`
|
||||
Realm string `json:"realm"`
|
||||
Enabled bool `json:"enabled"`
|
||||
Email string `json:"email,omitempty"`
|
||||
FirstName string `json:"firstname,omitempty"`
|
||||
LastName string `json:"lastname,omitempty"`
|
||||
Groups []string `json:"groups,omitempty"`
|
||||
Comment string `json:"comment,omitempty"`
|
||||
Expire int `json:"expire,omitempty"`
|
||||
LastLogin int64 `json:"last_login,omitempty"`
|
||||
}
|
||||
|
||||
// Role represents a Proxmox user role
|
||||
@@ -254,9 +254,9 @@ type Role struct {
|
||||
|
||||
// Permission represents a permission in Proxmox
|
||||
type Permission struct {
|
||||
Path string `json:"path"`
|
||||
Role string `json:"role"`
|
||||
User string `json:"user,omitempty"`
|
||||
Group string `json:"group,omitempty"`
|
||||
Realm string `json:"realm,omitempty"`
|
||||
Path string `json:"path"`
|
||||
Role string `json:"role"`
|
||||
User string `json:"user,omitempty"`
|
||||
Group string `json:"group,omitempty"`
|
||||
Realm string `json:"realm,omitempty"`
|
||||
}
|
||||
|
||||
@@ -217,6 +217,18 @@ func (cm *ComplianceManager) performAssessment(report *ComplianceReport) {
|
||||
var recommendations []string
|
||||
compliantCount := 0
|
||||
|
||||
if len(controls) == 0 {
|
||||
_, updateErr := cm.db.Exec(`
|
||||
UPDATE compliance_reports
|
||||
SET overall_status = $1, score = $2
|
||||
WHERE id = $3
|
||||
`, "non_compliant", 0, report.ID)
|
||||
if updateErr != nil {
|
||||
log.Printf("Failed to update compliance report %s with empty control set: %v", report.ID, updateErr)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
for _, control := range controls {
|
||||
assessedControl := cm.assessControl(ctx, report.ProjectID, control)
|
||||
assessedControls = append(assessedControls, assessedControl)
|
||||
|
||||
@@ -143,9 +143,14 @@ func (s *Scanner) scanDependencies(ctx context.Context, scan *SecurityScan) []Vu
|
||||
var vulnerabilities []Vulnerability
|
||||
|
||||
// Get project services
|
||||
rows, err := s.db.Query(`
|
||||
SELECT id, name FROM services WHERE project_id = $1
|
||||
`, scan.ProjectID)
|
||||
query := `SELECT id, name FROM services WHERE project_id = $1`
|
||||
args := []interface{}{scan.ProjectID}
|
||||
if scan.ServiceID != nil {
|
||||
query += ` AND id = $2`
|
||||
args = append(args, *scan.ServiceID)
|
||||
}
|
||||
|
||||
rows, err := s.db.Query(query, args...)
|
||||
|
||||
if err != nil {
|
||||
log.Printf("Failed to query services for scan: %v", err)
|
||||
@@ -160,7 +165,7 @@ func (s *Scanner) scanDependencies(ctx context.Context, scan *SecurityScan) []Vu
|
||||
}
|
||||
|
||||
// Simulate dependency scanning (in real implementation, this would check package.json, go.mod, etc.)
|
||||
serviceVulns := s.simulateDependencyScan(serviceID, serviceName)
|
||||
serviceVulns := s.simulateDependencyScan(serviceID, serviceName, scan.ProjectID)
|
||||
vulnerabilities = append(vulnerabilities, serviceVulns...)
|
||||
}
|
||||
|
||||
@@ -168,7 +173,7 @@ func (s *Scanner) scanDependencies(ctx context.Context, scan *SecurityScan) []Vu
|
||||
}
|
||||
|
||||
// simulateDependencyScan simulates scanning for vulnerable dependencies
|
||||
func (s *Scanner) simulateDependencyScan(serviceID, serviceName string) []Vulnerability {
|
||||
func (s *Scanner) simulateDependencyScan(serviceID, serviceName, projectID string) []Vulnerability {
|
||||
var vulns []Vulnerability
|
||||
|
||||
// Simulate finding some common vulnerabilities
|
||||
@@ -190,7 +195,7 @@ func (s *Scanner) simulateDependencyScan(serviceID, serviceName string) []Vulner
|
||||
Title: vuln.title,
|
||||
Description: vuln.description,
|
||||
ServiceID: serviceID,
|
||||
ProjectID: "", // Will be filled by caller
|
||||
ProjectID: projectID,
|
||||
Status: "open",
|
||||
FoundAt: time.Now(),
|
||||
Metadata: fmt.Sprintf(`{"service": "%s", "package": "example-package-%d"}`, serviceName, i+1),
|
||||
|
||||
Reference in New Issue
Block a user