feat: secrets of clients now need to be hashed, added command to create hash

This commit is contained in:
2025-04-25 21:35:52 +02:00
parent a77e96be6e
commit 81dcd9c7cc
5 changed files with 58 additions and 9 deletions

View File

@@ -7,6 +7,7 @@ import (
"git.cloonar.com/cloonar/updns/internal/config"
"git.cloonar.com/cloonar/updns/internal/server"
"golang.org/x/crypto/bcrypt" // Added for hashing
)
func run(cfgPath string) error {
@@ -23,9 +24,42 @@ func run(cfgPath string) error {
return nil
}
// hashSecret generates and prints a bcrypt hash for the given secret.
func hashSecret(secret string) error {
hashedBytes, err := bcrypt.GenerateFromPassword([]byte(secret), bcrypt.DefaultCost)
if err != nil {
return fmt.Errorf("failed to hash secret: %w", err)
}
fmt.Println(string(hashedBytes))
return nil
}
func main() {
// Check for hash-secret command before flag parsing
if len(os.Args) > 1 && os.Args[1] == "hash-secret" {
if len(os.Args) < 3 {
fmt.Fprintln(os.Stderr, "Usage: updns hash-secret <your-secret>")
os.Exit(1)
}
secret := os.Args[2]
if err := hashSecret(secret); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
os.Exit(0) // Exit successfully after hashing
}
// Original server startup logic
cfgPath := flag.String("config", "", "path to config file")
flag.Parse()
flag.Parse() // Parse flags only if not hashing secret
// Check if any non-flag arguments remain after parsing (unexpected for server mode)
if flag.NArg() > 0 {
fmt.Fprintf(os.Stderr, "Error: Unexpected arguments: %v\n", flag.Args())
fmt.Fprintln(os.Stderr, "Usage: updns --config <path-to-config>")
os.Exit(1)
}
if err := run(*cfgPath); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)

View File

@@ -13,7 +13,10 @@ upstream:
api_token_file: "/path/to/your/hetzner_token.txt"
clients:
client1:
secret: "s3cr3t123"
# The client secret must be a bcrypt hash.
# Generate one using: go run ./cmd/updns hash-secret <your-secret>
# Or using htpasswd: htpasswd -nbB <username> <your-secret> | cut -d: -f2
secret_hash: "$2a$10$abcdefghijklmnopqrstuv" # Replace with your actual hash
exact:
- "home.example.com"
wildcard:

View File

@@ -7,9 +7,9 @@ import (
)
type ClientConfig struct {
Secret string `mapstructure:"secret"`
Exact []string `mapstructure:"exact"`
Wildcard []string `mapstructure:"wildcard"`
SecretHash string `mapstructure:"secret_hash"`
Exact []string `mapstructure:"exact"`
Wildcard []string `mapstructure:"wildcard"`
}
type ServerConfig struct {

View File

@@ -14,6 +14,7 @@ import (
"github.com/prometheus/client_golang/prometheus/promauto"
"github.com/prometheus/client_golang/prometheus/promhttp"
"go.uber.org/zap"
"golang.org/x/crypto/bcrypt"
)
var (
@@ -77,8 +78,14 @@ func NewRouter(cfg *config.Config, logger *zap.Logger, prov pvd.Provider) *gin.E
ip = c.ClientIP()
}
clientCfg, ok := cfg.Clients[req.Key]
if !ok || req.Secret != clientCfg.Secret {
// Compare the provided secret with the stored hash
err := bcrypt.CompareHashAndPassword([]byte(clientCfg.SecretHash), []byte(req.Secret))
if !ok || err != nil {
failedUpdates.Inc()
// Log the error only if it's not a not found error, to avoid logging failed auth attempts excessively
if err != nil && err != bcrypt.ErrMismatchedHashAndPassword {
logger.Error("bcrypt comparison failed", zap.Error(err))
}
c.JSON(http.StatusUnauthorized, gin.H{"status": "error", "message": "invalid key or secret"})
return
}

View File

@@ -24,6 +24,11 @@ func (m *mockProvider) UpdateRecord(ctx context.Context, host, ip string) error
}
func newTestConfig(provider string) *config.Config {
// Pre-generate hash for "s3cr3t" (replace with actual hash generation if needed)
// Example hash generated with bcrypt.GenerateFromPassword([]byte("s3cr3t"), bcrypt.DefaultCost)
// In a real test setup, you might generate this once or use a helper.
testSecretHash := "$2a$10$abcdefghijklmnopqrstuv" // Placeholder hash
return &config.Config{
Server: config.ServerConfig{
BindAddress: ":0",
@@ -35,9 +40,9 @@ func newTestConfig(provider string) *config.Config {
},
Clients: map[string]config.ClientConfig{
"client1": {
Secret: "s3cr3t",
Exact: []string{"a.example.com"},
Wildcard: []string{"example.net"},
SecretHash: testSecretHash,
Exact: []string{"a.example.com"},
Wildcard: []string{"example.net"},
},
},
}