Release: v0.1.0-alpha
Release Docker Image / Build & Push Docker Image (release) Failing after 1m30s

This commit is contained in:
2026-04-05 16:56:16 +02:00
parent 23ff731579
commit fd13e67aef
89 changed files with 18786 additions and 0 deletions
+113
View File
@@ -0,0 +1,113 @@
// Keywarden - Centralized SSH Key Management and Deployment
// Copyright (C) 2026 Patrick Asmus (scriptos)
// SPDX-License-Identifier: AGPL-3.0-or-later
package config
import (
"os"
"strconv"
"strings"
)
// Config holds all application configuration
type Config struct {
Port string
DBPath string
DataDir string
KeysDir string
MasterDir string
SessionKey string
EncryptionKey string
LogLevel string // ERROR, WARN, INFO (default), DEBUG, TRACE
// SMTP / Email
SMTPHost string
SMTPPort string
SMTPUser string
SMTPPassword string
SMTPFrom string
SMTPTLS bool
SMTPEnabled bool
// Security / Hardening
BaseURL string // e.g. "https://keywarden.example.com" (used for emails, cookie config)
TrustedProxies string // comma-separated CIDRs, e.g. "10.0.0.0/8,172.16.0.0/12"
SecureCookies bool // set Secure flag on cookies (enable when behind HTTPS proxy)
RateLimitLogin int // max login POST attempts per IP per minute (0 = disabled)
MaxRequestSize int64 // max request body in bytes (0 = no limit)
}
// Load reads configuration from environment variables with sensible defaults
func Load() *Config {
smtpHost := getEnv("KEYWARDEN_SMTP_HOST", "")
// Parse BaseURL auto-derive SecureCookies from scheme if not explicitly set
baseURL := strings.TrimRight(getEnv("KEYWARDEN_BASE_URL", ""), "/")
secureCookiesExplicit := getEnv("KEYWARDEN_SECURE_COOKIES", "")
secureCookies := false
if secureCookiesExplicit != "" {
secureCookies = secureCookiesExplicit == "true"
} else if strings.HasPrefix(baseURL, "https://") {
secureCookies = true
}
rateLimitLogin := getEnvInt("KEYWARDEN_RATE_LIMIT_LOGIN", 10)
maxRequestSize := getEnvInt64("KEYWARDEN_MAX_REQUEST_SIZE", 10*1024*1024) // 10 MB
return &Config{
Port: getEnv("KEYWARDEN_PORT", "8080"),
DBPath: getEnv("KEYWARDEN_DB_PATH", "./data/keywarden.db"),
DataDir: getEnv("KEYWARDEN_DATA_DIR", "./data"),
KeysDir: getEnv("KEYWARDEN_KEYS_DIR", "./data/keys"),
MasterDir: getEnv("KEYWARDEN_MASTER_DIR", "./data/master"),
SessionKey: getEnv("KEYWARDEN_SESSION_KEY", "change-me-in-production-please"),
EncryptionKey: getEnv("KEYWARDEN_ENCRYPTION_KEY", "change-me-encryption-key-32chars"),
LogLevel: getEnv("KEYWARDEN_LOG_LEVEL", "INFO"),
SMTPHost: smtpHost,
SMTPPort: getEnv("KEYWARDEN_SMTP_PORT", "587"),
SMTPUser: getEnv("KEYWARDEN_SMTP_USER", ""),
SMTPPassword: getEnv("KEYWARDEN_SMTP_PASSWORD", ""),
SMTPFrom: getEnv("KEYWARDEN_SMTP_FROM", "keywarden@localhost"),
SMTPTLS: getEnv("KEYWARDEN_SMTP_TLS", "true") == "true",
SMTPEnabled: smtpHost != "",
BaseURL: baseURL,
TrustedProxies: getEnv("KEYWARDEN_TRUSTED_PROXIES", ""),
SecureCookies: secureCookies,
RateLimitLogin: rateLimitLogin,
MaxRequestSize: maxRequestSize,
}
}
func getEnv(key, fallback string) string {
if val, ok := os.LookupEnv(key); ok {
return val
}
return fallback
}
func getEnvInt(key string, fallback int) int {
s := getEnv(key, "")
if s == "" {
return fallback
}
v, err := strconv.Atoi(s)
if err != nil {
return fallback
}
return v
}
func getEnvInt64(key string, fallback int64) int64 {
s := getEnv(key, "")
if s == "" {
return fallback
}
v, err := strconv.ParseInt(s, 10, 64)
if err != nil {
return fallback
}
return v
}
+68
View File
@@ -0,0 +1,68 @@
// Keywarden - Centralized SSH Key Management and Deployment
// Copyright (C) 2026 Patrick Asmus (scriptos)
// SPDX-License-Identifier: AGPL-3.0-or-later
package config
import (
"os"
"testing"
)
func TestLoadDefaults(t *testing.T) {
// Clear all KEYWARDEN_ env vars to ensure defaults
envs := []string{
"KEYWARDEN_PORT", "KEYWARDEN_DB_PATH", "KEYWARDEN_DATA_DIR",
"KEYWARDEN_KEYS_DIR", "KEYWARDEN_MASTER_DIR", "KEYWARDEN_SESSION_KEY",
"KEYWARDEN_ENCRYPTION_KEY",
}
for _, e := range envs {
os.Unsetenv(e)
}
cfg := Load()
if cfg.Port != "8080" {
t.Fatalf("Expected default port 8080, got %q", cfg.Port)
}
if cfg.DBPath != "./data/keywarden.db" {
t.Fatalf("Expected default DBPath, got %q", cfg.DBPath)
}
if cfg.DataDir != "./data" {
t.Fatalf("Expected default DataDir, got %q", cfg.DataDir)
}
if cfg.KeysDir != "./data/keys" {
t.Fatalf("Expected default KeysDir, got %q", cfg.KeysDir)
}
if cfg.MasterDir != "./data/master" {
t.Fatalf("Expected default MasterDir, got %q", cfg.MasterDir)
}
}
func TestLoadFromEnv(t *testing.T) {
os.Setenv("KEYWARDEN_PORT", "9090")
os.Setenv("KEYWARDEN_DB_PATH", "/custom/db.sqlite")
os.Setenv("KEYWARDEN_DATA_DIR", "/custom/data")
os.Setenv("KEYWARDEN_ENCRYPTION_KEY", "my-custom-key")
defer func() {
os.Unsetenv("KEYWARDEN_PORT")
os.Unsetenv("KEYWARDEN_DB_PATH")
os.Unsetenv("KEYWARDEN_DATA_DIR")
os.Unsetenv("KEYWARDEN_ENCRYPTION_KEY")
}()
cfg := Load()
if cfg.Port != "9090" {
t.Fatalf("Expected port 9090, got %q", cfg.Port)
}
if cfg.DBPath != "/custom/db.sqlite" {
t.Fatalf("Expected custom DBPath, got %q", cfg.DBPath)
}
if cfg.DataDir != "/custom/data" {
t.Fatalf("Expected custom DataDir, got %q", cfg.DataDir)
}
if cfg.EncryptionKey != "my-custom-key" {
t.Fatalf("Expected custom EncryptionKey, got %q", cfg.EncryptionKey)
}
}