feat: secrets of clients now need to be hashed, added command to create hash
This commit is contained in:
@@ -7,6 +7,7 @@ import (
|
|||||||
|
|
||||||
"git.cloonar.com/cloonar/updns/internal/config"
|
"git.cloonar.com/cloonar/updns/internal/config"
|
||||||
"git.cloonar.com/cloonar/updns/internal/server"
|
"git.cloonar.com/cloonar/updns/internal/server"
|
||||||
|
"golang.org/x/crypto/bcrypt" // Added for hashing
|
||||||
)
|
)
|
||||||
|
|
||||||
func run(cfgPath string) error {
|
func run(cfgPath string) error {
|
||||||
@@ -23,9 +24,42 @@ func run(cfgPath string) error {
|
|||||||
return nil
|
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() {
|
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")
|
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 {
|
if err := run(*cfgPath); err != nil {
|
||||||
fmt.Fprintln(os.Stderr, err)
|
fmt.Fprintln(os.Stderr, err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
|
|||||||
@@ -13,7 +13,10 @@ upstream:
|
|||||||
api_token_file: "/path/to/your/hetzner_token.txt"
|
api_token_file: "/path/to/your/hetzner_token.txt"
|
||||||
clients:
|
clients:
|
||||||
client1:
|
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:
|
exact:
|
||||||
- "home.example.com"
|
- "home.example.com"
|
||||||
wildcard:
|
wildcard:
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type ClientConfig struct {
|
type ClientConfig struct {
|
||||||
Secret string `mapstructure:"secret"`
|
SecretHash string `mapstructure:"secret_hash"`
|
||||||
Exact []string `mapstructure:"exact"`
|
Exact []string `mapstructure:"exact"`
|
||||||
Wildcard []string `mapstructure:"wildcard"`
|
Wildcard []string `mapstructure:"wildcard"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import (
|
|||||||
"github.com/prometheus/client_golang/prometheus/promauto"
|
"github.com/prometheus/client_golang/prometheus/promauto"
|
||||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
|
"golang.org/x/crypto/bcrypt"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -77,8 +78,14 @@ func NewRouter(cfg *config.Config, logger *zap.Logger, prov pvd.Provider) *gin.E
|
|||||||
ip = c.ClientIP()
|
ip = c.ClientIP()
|
||||||
}
|
}
|
||||||
clientCfg, ok := cfg.Clients[req.Key]
|
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()
|
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"})
|
c.JSON(http.StatusUnauthorized, gin.H{"status": "error", "message": "invalid key or secret"})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,6 +24,11 @@ func (m *mockProvider) UpdateRecord(ctx context.Context, host, ip string) error
|
|||||||
}
|
}
|
||||||
|
|
||||||
func newTestConfig(provider string) *config.Config {
|
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{
|
return &config.Config{
|
||||||
Server: config.ServerConfig{
|
Server: config.ServerConfig{
|
||||||
BindAddress: ":0",
|
BindAddress: ":0",
|
||||||
@@ -35,7 +40,7 @@ func newTestConfig(provider string) *config.Config {
|
|||||||
},
|
},
|
||||||
Clients: map[string]config.ClientConfig{
|
Clients: map[string]config.ClientConfig{
|
||||||
"client1": {
|
"client1": {
|
||||||
Secret: "s3cr3t",
|
SecretHash: testSecretHash,
|
||||||
Exact: []string{"a.example.com"},
|
Exact: []string{"a.example.com"},
|
||||||
Wildcard: []string{"example.net"},
|
Wildcard: []string{"example.net"},
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user