package security import ( "backea/internal/logging" "bufio" "crypto/rand" "fmt" "os" "strings" ) type SecurityError struct { Message string Cause error } func (e *SecurityError) Error() string { if e.Cause != nil { return e.Message + ": " + e.Cause.Error() } return e.Message } func (e *SecurityError) Unwrap() error { return e.Cause } func NewSecurityError(message string, cause error) *SecurityError { return &SecurityError{ Message: message, Cause: cause, } } func GetOrCreatePassword(serviceName string, length int) (string, error) { logger := logging.GetLogger() safeServiceName := strings.ReplaceAll(serviceName, ".", "_") passwordKey := fmt.Sprintf("KOPIA_%s_PASSWORD", strings.ToUpper(safeServiceName)) kopiaEnvPath := "kopia.env" if _, err := os.Stat(kopiaEnvPath); err == nil { file, err := os.Open(kopiaEnvPath) if err != nil { return "", NewSecurityError("failed to open kopia.env", err) } defer file.Close() scanner := bufio.NewScanner(file) for scanner.Scan() { line := scanner.Text() if strings.HasPrefix(line, passwordKey+"=") { parts := strings.SplitN(line, "=", 2) if len(parts) == 2 { password := parts[1] password = strings.Trim(password, "\"") return password, nil } } } if err := scanner.Err(); err != nil { return "", NewSecurityError("error reading kopia.env", err) } } password, err := GenerateSecurePassword(length) if err != nil { return "", NewSecurityError("failed to generate password", err) } file, err := os.OpenFile(kopiaEnvPath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0600) if err != nil { return "", NewSecurityError("failed to open kopia.env for writing", err) } defer file.Close() passwordEntry := fmt.Sprintf("%s=\"%s\"\n", passwordKey, password) if _, err := file.WriteString(passwordEntry); err != nil { return "", NewSecurityError("failed to write to kopia.env", err) } logger.Info("Created new password for service %s and stored in kopia.env", serviceName) return password, nil } func GenerateSecurePassword(length int) (string, error) { const chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" result := make([]byte, length) randomBytes := make([]byte, length) if _, err := rand.Read(randomBytes); err != nil { return "", NewSecurityError("failed to generate random bytes", err) } if length >= 6 { result[0] = 'A' + byte(randomBytes[0]%26) result[1] = 'A' + byte(randomBytes[1]%26) result[2] = 'a' + byte(randomBytes[2]%26) result[3] = 'a' + byte(randomBytes[3]%26) result[4] = '0' + byte(randomBytes[4]%10) result[5] = '0' + byte(randomBytes[5]%10) for i := 6; i < length; i++ { result[i] = chars[int(randomBytes[i])%len(chars)] } for i := length - 1; i > 0; i-- { j := int(randomBytes[i]) % (i + 1) result[i], result[j] = result[j], result[i] } } else { for i := 0; i < length; i++ { result[i] = chars[int(randomBytes[i])%len(chars)] } } return string(result), nil }