120 lines
3.0 KiB
Go
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
|
|
}
|