push newest version

This commit is contained in:
2025-11-20 17:59:34 +01:00
parent c0e2df2430
commit 97ccd7bfbf
21 changed files with 3978 additions and 65 deletions

View File

@@ -1,9 +1,13 @@
module certigo-addon-backend
go 1.21
go 1.24.0
toolchain go1.24.10
require (
github.com/google/uuid v1.5.0
github.com/gorilla/mux v1.8.1
github.com/mattn/go-sqlite3 v1.14.18
)
require golang.org/x/crypto v0.45.0 // indirect

View File

@@ -4,3 +4,5 @@ github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
github.com/mattn/go-sqlite3 v1.14.18 h1:JL0eqdCOq6DJVNPSvArO/bIV9/P7fbGrV00LZHc+5aI=
github.com/mattn/go-sqlite3 v1.14.18/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q=
golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4=

View File

@@ -0,0 +1,126 @@
package core
import (
"context"
"database/sql"
"encoding/json"
"fmt"
"log"
"time"
"github.com/google/uuid"
)
var berlinLocation *time.Location
func init() {
var err error
berlinLocation, err = time.LoadLocation("Europe/Berlin")
if err != nil {
// Fallback zu UTC falls Europe/Berlin nicht verfügbar ist
log.Printf("Warnung: Europe/Berlin Zeitzone nicht verfügbar, verwende UTC: %v", err)
berlinLocation = time.UTC
}
}
// AuditService handles audit logging
type AuditService struct {
db *sql.DB
}
// NewAuditService creates a new AuditService instance
func NewAuditService(db *sql.DB) *AuditService {
return &AuditService{
db: db,
}
}
// Track logs an audit event asynchronously
// ctx: context for the operation
// action: the action performed (e.g., "CREATE", "UPDATE", "DELETE")
// entity: the entity type (e.g., "user", "space", "fqdn", "csr")
// entityID: the ID of the entity (optional)
// userID: the ID of the user performing the action (optional)
// username: the username of the user performing the action (optional)
// details: additional details as a map (will be stored as JSON)
// ipAddress: IP address of the request (optional)
// userAgent: User-Agent header (optional)
func (s *AuditService) Track(ctx context.Context, action, entity, entityID, userID, username string, details map[string]interface{}, ipAddress, userAgent string) {
// Check if service is nil (should not happen, but safety check)
if s == nil {
log.Printf("Warnung: AuditService ist nil, kann kein Audit-Log schreiben")
return
}
// Check if database is available
if s.db == nil {
log.Printf("Warnung: Datenbank ist nil im AuditService, kann kein Audit-Log schreiben")
return
}
// Execute asynchronously in a goroutine to not block the main operation
go func() {
if err := s.trackSync(ctx, action, entity, entityID, userID, username, details, ipAddress, userAgent); err != nil {
// Log errors but don't fail the main operation
log.Printf("Fehler beim Schreiben des Audit-Logs: %v", err)
}
}()
}
// trackSync performs the actual database write synchronously
func (s *AuditService) trackSync(ctx context.Context, action, entity, entityID, userID, username string, details map[string]interface{}, ipAddress, userAgent string) error {
// Safety check: ensure service and database are not nil
if s == nil {
return fmt.Errorf("AuditService ist nil")
}
if s.db == nil {
return fmt.Errorf("Datenbank ist nil im AuditService")
}
// Generate unique ID for the log entry
logID := uuid.New().String()
// Format timestamp for SQLite - verwende Europe/Berlin Zeitzone
// Speichere als ISO-String mit Zeitzone für bessere Kompatibilität
// Format: YYYY-MM-DD HH:MM:SS (SQLite DATETIME Format)
// Die Zeit wird in Europe/Berlin Zeitzone gespeichert
now := time.Now().In(berlinLocation)
timestamp := now.Format("2006-01-02 15:04:05")
// Marshal details to JSON
var detailsJSON string
if details != nil && len(details) > 0 {
jsonBytes, err := json.Marshal(details)
if err != nil {
return fmt.Errorf("Fehler beim Marshalling der Details: %w", err)
}
detailsJSON = string(jsonBytes)
}
// Create context with timeout for database operation
dbCtx, cancel := context.WithTimeout(context.Background(), time.Second*5)
defer cancel()
// Insert audit log entry
result, err := s.db.ExecContext(dbCtx,
"INSERT INTO audit_logs (id, timestamp, user_id, username, action, resource_type, resource_id, details, ip_address, user_agent) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
logID, timestamp, userID, username, action, entity, entityID, detailsJSON, ipAddress, userAgent)
if err != nil {
return fmt.Errorf("Fehler beim INSERT: %w", err)
}
// Verify that the insert was successful
rowsAffected, err := result.RowsAffected()
if err != nil {
return fmt.Errorf("Fehler beim Prüfen der RowsAffected: %w", err)
}
if rowsAffected == 0 {
return fmt.Errorf("Keine Zeile eingefügt")
}
log.Printf("Audit-Log geschrieben: %s %s %s (ID: %s)", action, entity, entityID, logID)
return nil
}

File diff suppressed because it is too large Load Diff

42
backend/scripts/README.md Normal file
View File

@@ -0,0 +1,42 @@
# Test-Skripte für Audit-Logs
## Test-Logs generieren
Das Skript `generate_test_logs.go` erstellt 3000 Test-Audit-Logs für Testzwecke.
### Verwendung:
```bash
cd backend/scripts
go run generate_test_logs.go
```
### Konfiguration:
Das Skript verwendet standardmäßig:
- URL: `http://localhost:8080`
- Username: `admin`
- Password: `admin`
Diese können im Skript geändert werden, falls nötig.
### Was wird erstellt:
- 3000 verschiedene Audit-Log-Einträge
- Verschiedene Aktionen: CREATE, UPDATE, DELETE, UPLOAD, SIGN, ENABLE, DISABLE
- Verschiedene Ressourcentypen: user, space, fqdn, csr, provider, certificate
- Realistische Testdaten mit verschiedenen Details
- Fortschrittsanzeige alle 100 Logs
## Alle Logs löschen
Verwende die API, um alle Audit-Logs zu löschen:
```bash
curl -X DELETE "http://localhost:8080/api/audit-logs?confirm=true" \
-u admin:admin \
-H "Content-Type: application/json"
```
**Wichtig**: Der `confirm=true` Query-Parameter ist erforderlich, um versehentliches Löschen zu verhindern.

View File

@@ -0,0 +1,132 @@
package main
import (
"bytes"
"encoding/base64"
"encoding/json"
"fmt"
"log"
"net/http"
"time"
)
func main() {
baseURL := "http://localhost:8080"
username := "admin"
password := "admin"
// Erstelle Basic Auth Header
auth := base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("%s:%s", username, password)))
// Verschiedene Aktionen und Ressourcen für realistische Testdaten
actions := []string{"CREATE", "UPDATE", "DELETE", "UPLOAD", "SIGN", "ENABLE", "DISABLE"}
resourceTypes := []string{"user", "space", "fqdn", "csr", "provider", "certificate"}
usernames := []string{"admin", "user1", "user2", "operator", "manager"}
fmt.Printf("Generiere 3000 Test-Audit-Logs...\n")
client := &http.Client{
Timeout: 30 * time.Second,
}
successCount := 0
errorCount := 0
for i := 0; i < 3000; i++ {
// Wähle zufällige Werte für realistische Testdaten
action := actions[i%len(actions)]
resourceType := resourceTypes[i%len(resourceTypes)]
username := usernames[i%len(usernames)]
// Erstelle Details mit verschiedenen Informationen
details := map[string]interface{}{
"message": fmt.Sprintf("Test-Log Eintrag #%d", i+1),
"iteration": i + 1,
"timestamp": time.Now().Format(time.RFC3339),
"testData": true,
"resourceId": fmt.Sprintf("test-resource-%d", i+1),
"description": fmt.Sprintf("Dies ist ein Test-Log-Eintrag für %s %s", action, resourceType),
}
// Füge spezifische Details basierend auf Resource-Type hinzu
switch resourceType {
case "user":
details["username"] = fmt.Sprintf("testuser%d", i+1)
details["email"] = fmt.Sprintf("test%d@example.com", i+1)
case "space":
details["name"] = fmt.Sprintf("Test Space %d", i+1)
details["description"] = "Test Space Description"
case "fqdn":
details["fqdn"] = fmt.Sprintf("test%d.example.com", i+1)
details["spaceId"] = fmt.Sprintf("space-%d", i%100)
case "csr":
details["fqdnId"] = fmt.Sprintf("fqdn-%d", i%200)
details["keySize"] = 2048
case "provider":
details["providerId"] = fmt.Sprintf("provider-%d", i%10)
details["enabled"] = i%2 == 0
case "certificate":
details["certificateId"] = fmt.Sprintf("cert-%d", i+1)
details["status"] = "issued"
}
// Erstelle Request Body
requestBody := map[string]interface{}{
"action": action,
"entity": resourceType,
"entityID": fmt.Sprintf("test-id-%d", i+1),
"userID": fmt.Sprintf("user-id-%d", i%5+1),
"username": username,
"details": details,
"ipAddress": fmt.Sprintf("192.168.1.%d", i%255+1),
"userAgent": "Test-Script/1.0",
}
jsonData, err := json.Marshal(requestBody)
if err != nil {
log.Printf("Fehler beim Marshalling: %v", err)
errorCount++
continue
}
// Erstelle HTTP Request
req, err := http.NewRequest("POST", fmt.Sprintf("%s/api/audit-logs/test", baseURL), bytes.NewBuffer(jsonData))
if err != nil {
log.Printf("Fehler beim Erstellen des Requests: %v", err)
errorCount++
continue
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", fmt.Sprintf("Basic %s", auth))
// Sende Request
resp, err := client.Do(req)
if err != nil {
log.Printf("Fehler beim Senden des Requests: %v", err)
errorCount++
continue
}
resp.Body.Close()
if resp.StatusCode == http.StatusOK || resp.StatusCode == http.StatusCreated {
successCount++
if (i+1)%100 == 0 {
fmt.Printf("Progress: %d/3000 Logs erstellt\n", i+1)
}
} else {
errorCount++
log.Printf("Unerwarteter Status Code: %d für Log %d", resp.StatusCode, i+1)
}
// Kleine Pause, um die Datenbank nicht zu überlasten
if i%50 == 0 && i > 0 {
time.Sleep(10 * time.Millisecond)
}
}
fmt.Printf("\nFertig!\n")
fmt.Printf("Erfolgreich: %d\n", successCount)
fmt.Printf("Fehler: %d\n", errorCount)
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB