Add comprehensive configuration and testing for HMAC File Server 3.2

- Introduced configuration files for Docker, Podman, and SystemD deployments.
- Implemented a comprehensive test suite for HMAC validation, file uploads, and network resilience.
- Added debugging scripts for live monitoring of upload issues and service status.
- Created minimal configuration for testing purposes.
- Developed multiple test scripts to validate HMAC calculations and response handling.
- Enhanced upload tests to cover various scenarios including invalid HMAC and unsupported file extensions.
- Improved logging and error analysis capabilities for better diagnostics.
This commit is contained in:
2025-07-20 18:04:23 +00:00
parent f8e4d8fcba
commit 68ede52336
37 changed files with 2754 additions and 591 deletions

View File

@ -14,6 +14,9 @@ import (
"time"
)
// Global variable to store config file path for validation
var configFileGlobal string
// ConfigValidationError represents a configuration validation error
type ConfigValidationError struct {
Field string
@ -88,6 +91,14 @@ func ValidateConfigComprehensive(c *Config) *ConfigValidationResult {
checkDiskSpace(c.Deduplication.Directory, result)
}
// Check for common configuration field naming mistakes
// This helps users identify issues like 'storagepath' vs 'storage_path'
if configFileGlobal != "" {
if configBytes, err := os.ReadFile(configFileGlobal); err == nil {
checkCommonConfigurationMistakes(result, configBytes)
}
}
return result
}
@ -111,7 +122,7 @@ func validateServerConfig(server *ServerConfig, result *ConfigValidationResult)
// StoragePath validation
if server.StoragePath == "" {
result.AddError("server.storagepath", server.StoragePath, "storage path is required")
result.AddError("server.storagepath", server.StoragePath, "storage path is required - check your config.toml uses 'storage_path' (with underscore) not 'storagepath'")
} else {
if err := validateDirectoryPath(server.StoragePath, true); err != nil {
result.AddError("server.storagepath", server.StoragePath, err.Error())
@ -1129,3 +1140,29 @@ func countPassedChecks(result *ConfigValidationResult) int {
totalPossibleChecks := 50 // Approximate number of validation checks
return totalPossibleChecks - len(result.Errors) - len(result.Warnings)
}
// checkCommonConfigurationMistakes checks for common TOML field naming errors
func checkCommonConfigurationMistakes(result *ConfigValidationResult, configBytes []byte) {
configStr := string(configBytes)
// Common field naming mistakes
commonMistakes := map[string]string{
"storagepath": "storage_path",
"listenport": "listen_address",
"bindip": "bind_ip",
"pidfilepath": "pid_file",
"metricsenabled": "metrics_enabled",
"metricsport": "metrics_port",
"maxuploadsize": "max_upload_size",
"cleanupinterval": "cleanup_interval",
"dedupenabled": "deduplication_enabled",
"ttlenabled": "ttl_enabled",
"chunksize": "chunk_size",
}
for incorrect, correct := range commonMistakes {
if strings.Contains(configStr, incorrect+" =") || strings.Contains(configStr, incorrect+"=") {
result.AddWarning("config.syntax", incorrect, fmt.Sprintf("field name '%s' should be '%s' (use underscores)", incorrect, correct))
}
}
}

View File

@ -682,21 +682,30 @@ func setupRouter() *http.ServeMux {
// Catch-all handler for all upload protocols (v, v2, token, v3)
// This must be added last as it matches all paths
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
log.Infof("🔍 ROUTER DEBUG: Catch-all handler called - method:%s path:%s query:%s", r.Method, r.URL.Path, r.URL.RawQuery)
// Handle PUT requests for all upload protocols
if r.Method == http.MethodPut {
query := r.URL.Query()
log.Infof("🔍 ROUTER DEBUG: Query parameters - v:%s v2:%s v3:%s token:%s expires:%s",
query.Get("v"), query.Get("v2"), query.Get("v3"), query.Get("token"), query.Get("expires"))
// Check if this is a v3 request (mod_http_upload_external)
if query.Get("v3") != "" && query.Get("expires") != "" {
log.Info("🔍 ROUTER DEBUG: Routing to handleV3Upload")
handleV3Upload(w, r)
return
}
// Check if this is a legacy protocol request (v, v2, token)
if query.Get("v") != "" || query.Get("v2") != "" || query.Get("token") != "" {
log.Info("🔍 ROUTER DEBUG: Routing to handleLegacyUpload")
handleLegacyUpload(w, r)
return
}
log.Info("🔍 ROUTER DEBUG: PUT request with no matching protocol parameters")
}
// Handle GET/HEAD requests for downloads

View File

@ -553,6 +553,7 @@ func main() {
log.Fatalf("Failed to load configuration: %v", err)
}
conf = *loadedConfig
configFileGlobal = configFile // Store for validation helper functions
log.Info("Configuration loaded successfully.")
err = validateConfig(&conf)
@ -1869,6 +1870,8 @@ func handleLegacyUpload(w http.ResponseWriter, r *http.Request) {
activeConnections.Inc()
defer activeConnections.Dec()
log.Infof("🔥 DEBUG: handleLegacyUpload called - method:%s path:%s query:%s", r.Method, r.URL.Path, r.URL.RawQuery)
log.Debugf("handleLegacyUpload: Processing request to %s with query: %s", r.URL.Path, r.URL.RawQuery)
// Only allow PUT method for legacy uploads
@ -1886,29 +1889,40 @@ func handleLegacyUpload(w http.ResponseWriter, r *http.Request) {
return
}
log.Debugf("✅ HMAC validation passed for: %s", r.URL.Path)
// Extract filename from the URL path
fileStorePath := strings.TrimPrefix(r.URL.Path, "/")
if fileStorePath == "" {
log.Debugf("❌ No filename specified")
http.Error(w, "No filename specified", http.StatusBadRequest)
uploadErrorsTotal.Inc()
return
}
log.Debugf("✅ File path extracted: %s", fileStorePath)
// Validate file extension if configured
if len(conf.Uploads.AllowedExtensions) > 0 {
ext := strings.ToLower(filepath.Ext(fileStorePath))
log.Infof("<22> DEBUG: Checking file extension: %s against %d allowed extensions", ext, len(conf.Uploads.AllowedExtensions))
log.Infof("<22> DEBUG: Allowed extensions: %v", conf.Uploads.AllowedExtensions)
allowed := false
for _, allowedExt := range conf.Uploads.AllowedExtensions {
for i, allowedExt := range conf.Uploads.AllowedExtensions {
log.Infof("<22> DEBUG: [%d] Comparing '%s' == '%s'", i, ext, allowedExt)
if ext == allowedExt {
allowed = true
log.Infof("🔥 DEBUG: Extension match found!")
break
}
}
if !allowed {
log.Infof("🔥 DEBUG: Extension %s not found in allowed list", ext)
http.Error(w, fmt.Sprintf("File extension %s not allowed", ext), http.StatusBadRequest)
uploadErrorsTotal.Inc()
return
}
log.Infof("🔥 DEBUG: File extension %s is allowed", ext)
}
// Validate file size against max_upload_size if configured