mirror of
https://github.com/StarFleetCPTN/GoMFT.git
synced 2026-10-21 13:34:05 +02:00
Add audit logging to storage providers
This commit is contained in:
1 parent
98e20ea843
commit
495b844c00
11 files changed
+706
-988
No files matched your search
+57
-16
@@ -1,6 +1,8 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -9,14 +11,15 @@ import (
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
ServerAddress string `json:"server_address"`
|
||||
DataDir string `json:"data_dir"`
|
||||
BackupDir string `json:"backup_dir"`
|
||||
JWTSecret string `json:"jwt_secret"`
|
||||
Email EmailConfig `json:"email"`
|
||||
BaseURL string `json:"base_url"` // Base URL for generating links in emails
|
||||
TOTPEncryptKey string `json:"totp_encrypt_key"` // Encryption key for TOTP secrets
|
||||
SkipSSLVerify bool `json:"skip_ssl_verify"` // Skip SSL verification for outgoing webhooks/notifications
|
||||
ServerAddress string `json:"server_address"`
|
||||
DataDir string `json:"data_dir"`
|
||||
BackupDir string `json:"backup_dir"`
|
||||
JWTSecret string `json:"jwt_secret"`
|
||||
Email EmailConfig `json:"email"`
|
||||
BaseURL string `json:"base_url"` // Base URL for generating links in emails
|
||||
TOTPEncryptKey string `json:"totp_encrypt_key"` // Encryption key for TOTP secrets
|
||||
GOMFTEncryptionKey string `json:"gomft_encryption_key"` // Encryption key for database
|
||||
SkipSSLVerify bool `json:"skip_ssl_verify"` // Skip SSL verification for outgoing webhooks/notifications
|
||||
}
|
||||
|
||||
type EmailConfig struct {
|
||||
@@ -33,15 +36,37 @@ type EmailConfig struct {
|
||||
}
|
||||
|
||||
func Load() (*Config, error) {
|
||||
// Generate secure encryption keys
|
||||
defaultTOTPKey, err := generateSecureKey()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defaultGOMFTKey, err := generateSecureKey()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Only set the environment variables if they're not already defined
|
||||
// This ensures user-provided keys take precedence
|
||||
if os.Getenv("TOTP_ENCRYPTION_KEY") == "" {
|
||||
os.Setenv("TOTP_ENCRYPTION_KEY", defaultTOTPKey)
|
||||
}
|
||||
|
||||
if os.Getenv("GOMFT_ENCRYPTION_KEY") == "" {
|
||||
os.Setenv("GOMFT_ENCRYPTION_KEY", defaultGOMFTKey)
|
||||
}
|
||||
|
||||
// Default configuration
|
||||
cfg := &Config{
|
||||
ServerAddress: ":8080",
|
||||
DataDir: "./data",
|
||||
BackupDir: "./backups",
|
||||
JWTSecret: "change_this_to_a_secure_random_string",
|
||||
BaseURL: "http://localhost:8080",
|
||||
TOTPEncryptKey: "this-is-a-dev-key-not-for-production!", // Default development key
|
||||
SkipSSLVerify: false, // Default to verifying SSL
|
||||
ServerAddress: ":8080",
|
||||
DataDir: "./data",
|
||||
BackupDir: "./backups",
|
||||
JWTSecret: "change_this_to_a_secure_random_string",
|
||||
BaseURL: "http://localhost:8080",
|
||||
TOTPEncryptKey: defaultTOTPKey, // Secure randomly generated key
|
||||
GOMFTEncryptionKey: defaultGOMFTKey, // Secure randomly generated key
|
||||
SkipSSLVerify: false, // Default to verifying SSL
|
||||
Email: EmailConfig{
|
||||
Enabled: false,
|
||||
Host: "smtp.example.com",
|
||||
@@ -87,7 +112,9 @@ func Load() (*Config, error) {
|
||||
if totpKey := os.Getenv("TOTP_ENCRYPTION_KEY"); totpKey != "" {
|
||||
cfg.TOTPEncryptKey = totpKey
|
||||
}
|
||||
|
||||
if gomftKey := os.Getenv("GOMFT_ENCRYPTION_KEY"); gomftKey != "" {
|
||||
cfg.GOMFTEncryptionKey = gomftKey
|
||||
}
|
||||
// Email configuration
|
||||
if emailEnabled := os.Getenv("EMAIL_ENABLED"); emailEnabled != "" {
|
||||
cfg.Email.Enabled = strings.ToLower(emailEnabled) == "true"
|
||||
@@ -145,6 +172,7 @@ func Load() (*Config, error) {
|
||||
"",
|
||||
"# Two-Factor Authentication configuration",
|
||||
"TOTP_ENCRYPTION_KEY=" + cfg.TOTPEncryptKey,
|
||||
"GOMFT_ENCRYPTION_KEY=" + cfg.GOMFTEncryptionKey,
|
||||
"",
|
||||
"# Email configuration",
|
||||
"EMAIL_ENABLED=" + strconv.FormatBool(cfg.Email.Enabled),
|
||||
@@ -171,3 +199,16 @@ func Load() (*Config, error) {
|
||||
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
// generateSecureKey creates a cryptographically secure random key encoded as base64
|
||||
func generateSecureKey() (string, error) {
|
||||
// Generate 32 bytes of random data (256 bits)
|
||||
bytes := make([]byte, 32)
|
||||
_, err := rand.Read(bytes)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Encode as base64
|
||||
return base64.StdEncoding.EncodeToString(bytes), nil
|
||||
}
|
||||
@@ -99,9 +99,21 @@ func (h *Handlers) HandleCreateStorageProvider(c *gin.Context) {
|
||||
// Set created by
|
||||
provider.CreatedBy = userID
|
||||
|
||||
// Start a transaction
|
||||
tx := h.DB.Begin()
|
||||
if tx.Error != nil {
|
||||
ctx := components.CreateTemplateContext(c)
|
||||
_ = components.StorageProviderForm(ctx, components.StorageProviderFormData{
|
||||
Provider: &provider,
|
||||
IsEdit: false,
|
||||
Error: "Failed to begin transaction",
|
||||
}).Render(ctx, c.Writer)
|
||||
return
|
||||
}
|
||||
|
||||
// Create provider in database
|
||||
err := h.DB.CreateStorageProvider(&provider)
|
||||
if err != nil {
|
||||
if err := tx.Create(&provider).Error; err != nil {
|
||||
tx.Rollback()
|
||||
ctx := components.CreateTemplateContext(c)
|
||||
_ = components.StorageProviderForm(ctx, components.StorageProviderFormData{
|
||||
Provider: &provider,
|
||||
@@ -111,6 +123,48 @@ func (h *Handlers) HandleCreateStorageProvider(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
// Create audit log
|
||||
auditDetails := map[string]interface{}{
|
||||
"name": provider.Name,
|
||||
"type": provider.Type,
|
||||
"host": provider.Host,
|
||||
"port": provider.Port,
|
||||
"username": provider.Username,
|
||||
}
|
||||
|
||||
auditLog := db.AuditLog{
|
||||
Action: "create",
|
||||
EntityType: "storage_provider",
|
||||
EntityID: provider.ID,
|
||||
UserID: userID,
|
||||
Details: auditDetails,
|
||||
Timestamp: time.Now(),
|
||||
}
|
||||
|
||||
if err := tx.Create(&auditLog).Error; err != nil {
|
||||
tx.Rollback()
|
||||
log.Printf("Error creating audit log: %v", err)
|
||||
ctx := components.CreateTemplateContext(c)
|
||||
_ = components.StorageProviderForm(ctx, components.StorageProviderFormData{
|
||||
Provider: &provider,
|
||||
IsEdit: false,
|
||||
Error: "Failed to create audit log",
|
||||
}).Render(ctx, c.Writer)
|
||||
return
|
||||
}
|
||||
|
||||
// Commit the transaction
|
||||
if err := tx.Commit().Error; err != nil {
|
||||
log.Printf("Error committing transaction: %v", err)
|
||||
ctx := components.CreateTemplateContext(c)
|
||||
_ = components.StorageProviderForm(ctx, components.StorageProviderFormData{
|
||||
Provider: &provider,
|
||||
IsEdit: false,
|
||||
Error: "Failed to commit transaction",
|
||||
}).Render(ctx, c.Writer)
|
||||
return
|
||||
}
|
||||
|
||||
// Test if requested
|
||||
if c.PostForm("test") == "true" {
|
||||
// Create connector service
|
||||
@@ -218,9 +272,51 @@ func (h *Handlers) HandleUpdateStorageProvider(c *gin.Context) {
|
||||
provider.EncryptedRefreshToken = existingProvider.EncryptedRefreshToken
|
||||
}
|
||||
|
||||
// Start a transaction
|
||||
tx := h.DB.Begin()
|
||||
if tx.Error != nil {
|
||||
ctx := components.CreateTemplateContext(c)
|
||||
_ = components.StorageProviderForm(ctx, components.StorageProviderFormData{
|
||||
Provider: &provider,
|
||||
IsEdit: true,
|
||||
Error: "Failed to begin transaction",
|
||||
}).Render(ctx, c.Writer)
|
||||
return
|
||||
}
|
||||
|
||||
// Create audit log before update
|
||||
auditDetails := map[string]interface{}{
|
||||
"name": provider.Name,
|
||||
"type": provider.Type,
|
||||
"host": provider.Host,
|
||||
"port": provider.Port,
|
||||
"username": provider.Username,
|
||||
}
|
||||
|
||||
auditLog := db.AuditLog{
|
||||
Action: "update",
|
||||
EntityType: "storage_provider",
|
||||
EntityID: provider.ID,
|
||||
UserID: userID,
|
||||
Details: auditDetails,
|
||||
Timestamp: time.Now(),
|
||||
}
|
||||
|
||||
if err := tx.Create(&auditLog).Error; err != nil {
|
||||
tx.Rollback()
|
||||
log.Printf("Error creating audit log: %v", err)
|
||||
ctx := components.CreateTemplateContext(c)
|
||||
_ = components.StorageProviderForm(ctx, components.StorageProviderFormData{
|
||||
Provider: &provider,
|
||||
IsEdit: true,
|
||||
Error: "Failed to create audit log",
|
||||
}).Render(ctx, c.Writer)
|
||||
return
|
||||
}
|
||||
|
||||
// Update provider in database
|
||||
err = h.DB.UpdateStorageProvider(&provider)
|
||||
if err != nil {
|
||||
if err := tx.Save(&provider).Error; err != nil {
|
||||
tx.Rollback()
|
||||
ctx := components.CreateTemplateContext(c)
|
||||
_ = components.StorageProviderForm(ctx, components.StorageProviderFormData{
|
||||
Provider: &provider,
|
||||
@@ -230,6 +326,18 @@ func (h *Handlers) HandleUpdateStorageProvider(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
// Commit the transaction
|
||||
if err := tx.Commit().Error; err != nil {
|
||||
log.Printf("Error committing transaction: %v", err)
|
||||
ctx := components.CreateTemplateContext(c)
|
||||
_ = components.StorageProviderForm(ctx, components.StorageProviderFormData{
|
||||
Provider: &provider,
|
||||
IsEdit: true,
|
||||
Error: "Failed to commit transaction",
|
||||
}).Render(ctx, c.Writer)
|
||||
return
|
||||
}
|
||||
|
||||
// Test if requested
|
||||
if c.PostForm("test") == "true" {
|
||||
// Create connector service
|
||||
@@ -286,13 +394,67 @@ func (h *Handlers) HandleDeleteStorageProvider(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
// Get provider
|
||||
var provider db.StorageProvider
|
||||
if err := h.DB.First(&provider, id).Error; err != nil {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "Storage provider not found"})
|
||||
return
|
||||
}
|
||||
|
||||
// Check if user owns this provider
|
||||
if provider.CreatedBy != c.GetUint("userID") {
|
||||
// Check if user is admin
|
||||
isAdmin, exists := c.Get("isAdmin")
|
||||
if !exists || isAdmin != true {
|
||||
c.JSON(http.StatusForbidden, gin.H{"error": "You do not have permission to delete this provider"})
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Start a transaction
|
||||
tx := h.DB.Begin()
|
||||
if tx.Error != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to begin transaction"})
|
||||
return
|
||||
}
|
||||
|
||||
// Create audit log before deletion
|
||||
auditDetails := map[string]interface{}{
|
||||
"name": provider.Name,
|
||||
"type": provider.Type,
|
||||
"host": provider.Host,
|
||||
"port": provider.Port,
|
||||
"username": provider.Username,
|
||||
}
|
||||
|
||||
auditLog := db.AuditLog{
|
||||
Action: "delete",
|
||||
EntityType: "storage_provider",
|
||||
EntityID: provider.ID,
|
||||
UserID: c.GetUint("userID"),
|
||||
Details: auditDetails,
|
||||
Timestamp: time.Now(),
|
||||
}
|
||||
|
||||
if err := tx.Create(&auditLog).Error; err != nil {
|
||||
tx.Rollback()
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create audit log"})
|
||||
return
|
||||
}
|
||||
|
||||
// Delete provider
|
||||
err = h.DB.DeleteStorageProvider(uint(id))
|
||||
if err != nil {
|
||||
if err := tx.Delete(&provider).Error; err != nil {
|
||||
tx.Rollback()
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("Failed to delete provider: %v", err)})
|
||||
return
|
||||
}
|
||||
|
||||
// Commit the transaction
|
||||
if err := tx.Commit().Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to commit transaction"})
|
||||
return
|
||||
}
|
||||
|
||||
c.Header("HX-Refresh", "true")
|
||||
c.JSON(http.StatusOK, gin.H{"message": "Provider deleted successfully"})
|
||||
}
|
||||
@@ -376,13 +538,52 @@ func (h *Handlers) HandleDuplicateStorageProvider(c *gin.Context) {
|
||||
duplicateProvider.UpdatedAt = time.Now()
|
||||
// Deep copy pointer fields here if any are added in the future
|
||||
|
||||
// Save the duplicate
|
||||
err = h.DB.CreateStorageProvider(&duplicateProvider)
|
||||
if err != nil {
|
||||
// Start a transaction
|
||||
tx := h.DB.Begin()
|
||||
if tx.Error != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to begin transaction"})
|
||||
return
|
||||
}
|
||||
|
||||
// Create the duplicate in the database
|
||||
if err := tx.Create(&duplicateProvider).Error; err != nil {
|
||||
tx.Rollback()
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create duplicate provider: " + err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
// Create audit log for duplication
|
||||
auditDetails := map[string]interface{}{
|
||||
"name": duplicateProvider.Name,
|
||||
"type": duplicateProvider.Type,
|
||||
"original_id": originalProvider.ID,
|
||||
"original_name": originalProvider.Name,
|
||||
"host": duplicateProvider.Host,
|
||||
"port": duplicateProvider.Port,
|
||||
"username": duplicateProvider.Username,
|
||||
}
|
||||
|
||||
auditLog := db.AuditLog{
|
||||
Action: "duplicate",
|
||||
EntityType: "storage_provider",
|
||||
EntityID: duplicateProvider.ID,
|
||||
UserID: userID,
|
||||
Details: auditDetails,
|
||||
Timestamp: time.Now(),
|
||||
}
|
||||
|
||||
if err := tx.Create(&auditLog).Error; err != nil {
|
||||
tx.Rollback()
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create audit log"})
|
||||
return
|
||||
}
|
||||
|
||||
// Commit the transaction
|
||||
if err := tx.Commit().Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to commit transaction"})
|
||||
return
|
||||
}
|
||||
|
||||
c.Header("HX-Refresh", "true")
|
||||
c.JSON(http.StatusOK, gin.H{"message": "Provider duplicated successfully"})
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user