2025-03-28 19:57:44 +01:00

120 lines
3.0 KiB
Go

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
}