任务执行1-19
This commit is contained in:
219
internal/logic/audit/audit.go
Normal file
219
internal/logic/audit/audit.go
Normal file
@@ -0,0 +1,219 @@
|
||||
package audit
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"git.apinb.com/ops/logs/internal/impl"
|
||||
"git.apinb.com/ops/logs/internal/models"
|
||||
)
|
||||
|
||||
const (
|
||||
RiskNormal = "normal"
|
||||
RiskDangerous = "dangerous"
|
||||
|
||||
ApprovalPending = "pending"
|
||||
ApprovalApproved = "approved"
|
||||
ApprovalRejected = "rejected"
|
||||
)
|
||||
|
||||
type Record struct {
|
||||
TraceID string `json:"trace_id,omitempty"`
|
||||
SourceService string `json:"source_service,omitempty"`
|
||||
ActorID string `json:"actor_id,omitempty"`
|
||||
ActorName string `json:"actor_name,omitempty"`
|
||||
Action string `json:"action,omitempty"`
|
||||
ObjectType string `json:"object_type,omitempty"`
|
||||
ObjectID string `json:"object_id,omitempty"`
|
||||
OperationRisk string `json:"operation_risk,omitempty"`
|
||||
ApprovalID string `json:"approval_id,omitempty"`
|
||||
RequestMethod string `json:"request_method,omitempty"`
|
||||
RequestPath string `json:"request_path,omitempty"`
|
||||
ClientIP string `json:"client_ip,omitempty"`
|
||||
BeforeJSON string `json:"before_json,omitempty"`
|
||||
AfterJSON string `json:"after_json,omitempty"`
|
||||
Result string `json:"result,omitempty"`
|
||||
ErrorMessage string `json:"error_message,omitempty"`
|
||||
}
|
||||
|
||||
type ApprovalRequest struct {
|
||||
RequestID string `json:"request_id,omitempty"`
|
||||
SourceService string `json:"source_service,omitempty"`
|
||||
Action string `json:"action,omitempty"`
|
||||
ObjectType string `json:"object_type,omitempty"`
|
||||
ObjectID string `json:"object_id,omitempty"`
|
||||
RequesterID string `json:"requester_id,omitempty"`
|
||||
RequesterName string `json:"requester_name,omitempty"`
|
||||
Reason string `json:"reason,omitempty"`
|
||||
BeforeJSON string `json:"before_json,omitempty"`
|
||||
AfterJSON string `json:"after_json,omitempty"`
|
||||
Status string `json:"status,omitempty"`
|
||||
ReviewerID string `json:"reviewer_id,omitempty"`
|
||||
ReviewerName string `json:"reviewer_name,omitempty"`
|
||||
ReviewComment string `json:"review_comment,omitempty"`
|
||||
ReviewedAt *time.Time `json:"reviewed_at,omitempty"`
|
||||
}
|
||||
|
||||
func NormalizeRecord(record Record) Record {
|
||||
record.TraceID = strings.TrimSpace(record.TraceID)
|
||||
record.SourceService = strings.TrimSpace(record.SourceService)
|
||||
record.ActorID = strings.TrimSpace(record.ActorID)
|
||||
record.ActorName = strings.TrimSpace(record.ActorName)
|
||||
record.Action = strings.TrimSpace(record.Action)
|
||||
record.ObjectType = strings.TrimSpace(record.ObjectType)
|
||||
record.ObjectID = strings.TrimSpace(record.ObjectID)
|
||||
record.OperationRisk = strings.TrimSpace(strings.ToLower(record.OperationRisk))
|
||||
record.ApprovalID = strings.TrimSpace(record.ApprovalID)
|
||||
record.RequestMethod = strings.TrimSpace(strings.ToUpper(record.RequestMethod))
|
||||
record.RequestPath = strings.TrimSpace(record.RequestPath)
|
||||
record.ClientIP = strings.TrimSpace(record.ClientIP)
|
||||
record.Result = strings.TrimSpace(record.Result)
|
||||
if record.Result == "" {
|
||||
record.Result = "success"
|
||||
}
|
||||
if record.OperationRisk == "" {
|
||||
if IsDangerousOperation(record.Action, record.ObjectType) {
|
||||
record.OperationRisk = RiskDangerous
|
||||
} else {
|
||||
record.OperationRisk = RiskNormal
|
||||
}
|
||||
}
|
||||
return record
|
||||
}
|
||||
|
||||
func ValidateRecord(record Record) error {
|
||||
record = NormalizeRecord(record)
|
||||
if record.SourceService == "" {
|
||||
return errors.New("source_service is required")
|
||||
}
|
||||
if record.ActorID == "" {
|
||||
return errors.New("actor_id is required")
|
||||
}
|
||||
if record.Action == "" {
|
||||
return errors.New("action is required")
|
||||
}
|
||||
if record.ObjectType == "" {
|
||||
return errors.New("object_type is required")
|
||||
}
|
||||
if record.ObjectID == "" {
|
||||
return errors.New("object_id is required")
|
||||
}
|
||||
if record.OperationRisk != RiskNormal && record.OperationRisk != RiskDangerous {
|
||||
return errors.New("operation_risk must be normal or dangerous")
|
||||
}
|
||||
if record.OperationRisk == RiskDangerous && record.ApprovalID == "" {
|
||||
return errors.New("approval_id is required for dangerous operation")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func IsDangerousOperation(action, objectType string) bool {
|
||||
key := strings.ToLower(strings.TrimSpace(action) + " " + strings.TrimSpace(objectType))
|
||||
dangerWords := []string{
|
||||
"notification_policy",
|
||||
"notification policy",
|
||||
"silence_policy",
|
||||
"suppression",
|
||||
"escalation_policy",
|
||||
"automation_script",
|
||||
"script.execute",
|
||||
"script.rollback",
|
||||
}
|
||||
for _, word := range dangerWords {
|
||||
if strings.Contains(key, word) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func SaveRecord(record Record) (models.AuditLog, error) {
|
||||
record = NormalizeRecord(record)
|
||||
if err := ValidateRecord(record); err != nil {
|
||||
return models.AuditLog{}, err
|
||||
}
|
||||
row := models.AuditLog{
|
||||
TraceID: record.TraceID,
|
||||
SourceService: record.SourceService,
|
||||
ActorID: record.ActorID,
|
||||
ActorName: record.ActorName,
|
||||
Action: record.Action,
|
||||
ObjectType: record.ObjectType,
|
||||
ObjectID: record.ObjectID,
|
||||
OperationRisk: record.OperationRisk,
|
||||
ApprovalID: record.ApprovalID,
|
||||
RequestMethod: record.RequestMethod,
|
||||
RequestPath: record.RequestPath,
|
||||
ClientIP: record.ClientIP,
|
||||
BeforeJSON: record.BeforeJSON,
|
||||
AfterJSON: record.AfterJSON,
|
||||
Result: record.Result,
|
||||
ErrorMessage: record.ErrorMessage,
|
||||
}
|
||||
if err := impl.DBService.Create(&row).Error; err != nil {
|
||||
return models.AuditLog{}, err
|
||||
}
|
||||
return row, nil
|
||||
}
|
||||
|
||||
func NormalizeApproval(req ApprovalRequest) ApprovalRequest {
|
||||
req.RequestID = strings.TrimSpace(req.RequestID)
|
||||
req.SourceService = strings.TrimSpace(req.SourceService)
|
||||
req.Action = strings.TrimSpace(req.Action)
|
||||
req.ObjectType = strings.TrimSpace(req.ObjectType)
|
||||
req.ObjectID = strings.TrimSpace(req.ObjectID)
|
||||
req.RequesterID = strings.TrimSpace(req.RequesterID)
|
||||
req.RequesterName = strings.TrimSpace(req.RequesterName)
|
||||
req.Status = strings.TrimSpace(strings.ToLower(req.Status))
|
||||
req.ReviewerID = strings.TrimSpace(req.ReviewerID)
|
||||
req.ReviewerName = strings.TrimSpace(req.ReviewerName)
|
||||
if req.Status == "" {
|
||||
req.Status = ApprovalPending
|
||||
}
|
||||
return req
|
||||
}
|
||||
|
||||
func ValidateApprovalRequest(req ApprovalRequest) error {
|
||||
req = NormalizeApproval(req)
|
||||
if req.SourceService == "" {
|
||||
return errors.New("source_service is required")
|
||||
}
|
||||
if req.Action == "" {
|
||||
return errors.New("action is required")
|
||||
}
|
||||
if req.ObjectType == "" {
|
||||
return errors.New("object_type is required")
|
||||
}
|
||||
if req.ObjectID == "" {
|
||||
return errors.New("object_id is required")
|
||||
}
|
||||
if req.RequesterID == "" {
|
||||
return errors.New("requester_id is required")
|
||||
}
|
||||
if !IsDangerousOperation(req.Action, req.ObjectType) {
|
||||
return errors.New("operation is not classified as dangerous")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func Transition(req ApprovalRequest, nextStatus, reviewerID, comment string) (ApprovalRequest, error) {
|
||||
req = NormalizeApproval(req)
|
||||
nextStatus = strings.TrimSpace(strings.ToLower(nextStatus))
|
||||
reviewerID = strings.TrimSpace(reviewerID)
|
||||
if req.Status != ApprovalPending {
|
||||
return ApprovalRequest{}, errors.New("only pending approval can be reviewed")
|
||||
}
|
||||
if nextStatus != ApprovalApproved && nextStatus != ApprovalRejected {
|
||||
return ApprovalRequest{}, errors.New("next status must be approved or rejected")
|
||||
}
|
||||
if reviewerID == "" {
|
||||
return ApprovalRequest{}, errors.New("reviewer_id is required")
|
||||
}
|
||||
now := time.Now()
|
||||
req.Status = nextStatus
|
||||
req.ReviewerID = reviewerID
|
||||
req.ReviewComment = strings.TrimSpace(comment)
|
||||
req.ReviewedAt = &now
|
||||
return req, nil
|
||||
}
|
||||
Reference in New Issue
Block a user