From 9f1b9f1690b43d8efba480fa80a1ef03ef2dd829 Mon Sep 17 00:00:00 2001 From: Torben Nehmer Date: Sat, 21 Aug 2021 20:34:45 +0200 Subject: [PATCH] Refactorization and further implementation - renamed clientoinfo to updaterequest - added userconfig skeleton - added hashed password skeleton --- cmd/hash-password.go | 49 +++++++++++++++++++ go.mod | 1 + service/config.go | 9 ++-- service/dns.go | 4 +- service/userconfig.go | 30 ++++++++++++ users/test.yml | 16 ++++++ webapi/server.go | 22 +++++++-- .../clientinfo.go => webapi/updaterequest.go | 46 ++++++++++------- 8 files changed, 149 insertions(+), 28 deletions(-) create mode 100644 cmd/hash-password.go create mode 100644 service/userconfig.go create mode 100644 users/test.yml rename service/clientinfo.go => webapi/updaterequest.go (50%) diff --git a/cmd/hash-password.go b/cmd/hash-password.go new file mode 100644 index 0000000..df200fc --- /dev/null +++ b/cmd/hash-password.go @@ -0,0 +1,49 @@ +package cmd + +import ( + "bytes" + "fmt" + "log" + "os" + + "gitea.nehmer.net/torben/dyndns/service" + "github.com/spf13/cobra" + "github.com/spf13/viper" + "golang.org/x/crypto/bcrypt" + "golang.org/x/crypto/ssh/terminal" +) + +var cmdHashPassword = &cobra.Command{ + Use: "hash-password", + Short: "Hashes a password for usage in user config files", + Long: `Creates a salted hash using bcrypt for use in userconfig files, so that we don't +have to store a plaintext password.`, + Run: func(cmd *cobra.Command, args []string) { + log.Printf("Configuration in use: %v", viper.AllSettings()) + service.LoadConfig() + + fmt.Println("Enter password:") + password, err := terminal.ReadPassword(int(os.Stdin.Fd())) + if err != nil { + log.Fatalf("failed to read password: %v", err) + } + fmt.Println("Enter password again:") + passwordCheck, err := terminal.ReadPassword(int(os.Stdin.Fd())) + if err != nil { + log.Fatalf("failed to read password: %v", err) + } + if !bytes.Equal(password, passwordCheck) { + log.Fatalln("the passwords do not match.") + } + + hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) + if err != nil { + log.Fatalf("failed to create password hash: %v", err) + } + fmt.Printf("Hashed password: %s\n", hash) + }, +} + +func init() { + rootCmd.AddCommand(cmdHashPassword) +} diff --git a/go.mod b/go.mod index 81472d8..cc1da79 100644 --- a/go.mod +++ b/go.mod @@ -8,4 +8,5 @@ require ( github.com/miekg/dns v1.0.14 github.com/spf13/cobra v1.2.1 github.com/spf13/viper v1.8.1 + golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 ) diff --git a/service/config.go b/service/config.go index a158686..4cef7b6 100644 --- a/service/config.go +++ b/service/config.go @@ -3,8 +3,9 @@ package service import "github.com/spf13/viper" type config struct { - DNSServer string - DefaultTTL uint32 + DNSServer string + DNSDefaultTTL uint32 + UsersConfigDir string } var C config @@ -16,9 +17,11 @@ func init() { func SetConfigDefaults() { viper.SetDefault("Service.DNS.Server", "10.10.11.254:53") viper.SetDefault("Service.DNS.DefaultTTL", 60) + viper.SetDefault("Service.Users.ConfigDir", "users/") } func LoadConfig() { C.DNSServer = viper.GetString("Service.DNS.Server") - C.DefaultTTL = viper.GetUint32("Service.DNS.DefaultTTL") + C.DNSDefaultTTL = viper.GetUint32("Service.DNS.DefaultTTL") + C.UsersConfigDir = viper.GetString("Service.Users.ConfigDir") } diff --git a/service/dns.go b/service/dns.go index f4185f3..1d9035f 100644 --- a/service/dns.go +++ b/service/dns.go @@ -42,7 +42,7 @@ func UpdateDNSEntry(domain string, hostname string, ip4 net.IP, ip6 net.IP) erro Name: fqdn, Rrtype: dns.TypeA, Class: dns.ClassINET, - Ttl: C.DefaultTTL, + Ttl: C.DNSDefaultTTL, }, A: ip4bin, }) @@ -58,7 +58,7 @@ func UpdateDNSEntry(domain string, hostname string, ip4 net.IP, ip6 net.IP) erro Name: fqdn, Rrtype: dns.TypeAAAA, Class: dns.ClassINET, - Ttl: C.DefaultTTL, + Ttl: C.DNSDefaultTTL, }, AAAA: ip6bin, }) diff --git a/service/userconfig.go b/service/userconfig.go new file mode 100644 index 0000000..02d844e --- /dev/null +++ b/service/userconfig.go @@ -0,0 +1,30 @@ +package service + +import ( + "fmt" + "log" + "os" + "path" + + "github.com/spf13/viper" +) + +func LoadConfigForUser(username string) (*viper.Viper, error) { + configFile := fmt.Sprintf("%s/%s.yml", C.UsersConfigDir, username) + configFile = path.Clean(configFile) + log.Printf("Trying to load config file %s for user %s", configFile, username) + + if _, err := os.Stat(configFile); err != nil { + return nil, fmt.Errorf("cannot stat the file %s: %v", configFile, err) + } + + v := viper.New() + v.SetConfigFile(configFile) + err := v.ReadInConfig() + + if err != nil { + return nil, fmt.Errorf("failed to parse config file %s: %v", configFile, err) + } + + return v, nil +} diff --git a/users/test.yml b/users/test.yml new file mode 100644 index 0000000..983fa64 --- /dev/null +++ b/users/test.yml @@ -0,0 +1,16 @@ +username: test +password: $2a$10$7eYMA3zoyDb.2dM6bbeiqexxS9LhLz7XM.Q0EL0VHVbcxyfRkfp7. +router: + DNS: brandfeld.dyn.local + NFT: + table: sshguard + set4: blackhole + set6: blackhole6 +others: + - registerv4: true + v6iid: ::dead:beef:dead:beef + DNS: atlantis.dynlocal + NFT: + table: sshguard + set4: blackhole + set6: blackhole6 \ No newline at end of file diff --git a/webapi/server.go b/webapi/server.go index e40bae3..276ed03 100644 --- a/webapi/server.go +++ b/webapi/server.go @@ -7,6 +7,7 @@ import ( "gitea.nehmer.net/torben/dyndns/service" "github.com/gorilla/mux" + "golang.org/x/crypto/bcrypt" ) func Server() { @@ -36,16 +37,27 @@ func handleDNSUpdate(w http.ResponseWriter, r *http.Request) { return } - ci, err := service.CreateClientInfoFromForm(r.Form) + ur, err := createUpdateRequestFromForm(r.Form) if err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } + v, err := service.LoadConfigForUser(ur.UserName) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + w.Header().Set("Content-Type", "text/plain") fmt.Fprintln(w, "OK") - fmt.Fprintln(w, ci) - log.Println(ci) - log.Println(ci.IPv6Net.IP) - log.Println(ci.IPv6Net.Mask) + fmt.Fprintln(w, ur.PrettyPrint()) + log.Println(ur.PrettyPrint()) + log.Println(ur.IPv6Net.IP) + log.Println(ur.IPv6Net.Mask) + log.Println(v.AllSettings()) + log.Printf("Request PW: %s, Config PW: %s", ur.Password, v.GetString("password")) + + err = bcrypt.CompareHashAndPassword([]byte(v.GetString("password")), []byte(ur.Password)) + log.Printf("PW Compare Result: %v", err) } diff --git a/service/clientinfo.go b/webapi/updaterequest.go similarity index 50% rename from service/clientinfo.go rename to webapi/updaterequest.go index 405b80c..72fbd3c 100644 --- a/service/clientinfo.go +++ b/webapi/updaterequest.go @@ -1,13 +1,15 @@ -package service +package webapi import ( + "encoding/json" "errors" "fmt" + "log" "net" "net/url" ) -type ClientInfo struct { +type UpdateRequest struct { IPv4 net.IP IPv6 net.IP UserName string @@ -17,45 +19,53 @@ type ClientInfo struct { IPv6Net *net.IPNet } -func (ci *ClientInfo) String() string { +func (ur *UpdateRequest) String() string { return fmt.Sprintf("IPv4: %v, IPv6: %v, UserName: %v, Password: %v, Domain: %v, DualStack: %v, IPv6Net: %v", - ci.IPv4, ci.IPv6, ci.UserName, ci.Password, ci.Domain, ci.DualStack, ci.IPv6Net) + ur.IPv4, ur.IPv6, ur.UserName, ur.Password, ur.Domain, ur.DualStack, ur.IPv6Net) } -func CreateClientInfoFromForm(form url.Values) (*ClientInfo, error) { - ci := &ClientInfo{} +func (ur *UpdateRequest) PrettyPrint() string { + s, err := json.MarshalIndent(ur, "", " ") + if err != nil { + log.Fatalf("Failed to pretty print UpdateRequest via JSON: %v", err) + } + return string(s) +} + +func createUpdateRequestFromForm(form url.Values) (*UpdateRequest, error) { + ur := &UpdateRequest{} if form.Get("IPv4") != "" { - if ci.IPv4 = net.ParseIP(form.Get("IPv4")); ci.IPv4 == nil { + if ur.IPv4 = net.ParseIP(form.Get("IPv4")); ur.IPv4 == nil { return nil, errors.New("could not parse IPv4 address") } } if form.Get("IPv6") != "" { - if ci.IPv6 = net.ParseIP(form.Get("IPv6")); ci.IPv6 == nil { + if ur.IPv6 = net.ParseIP(form.Get("IPv6")); ur.IPv6 == nil { return nil, errors.New("could not parse IPv6 address") } } - ci.UserName = form.Get("UserName") - if ci.UserName == "" { + ur.UserName = form.Get("UserName") + if ur.UserName == "" { return nil, errors.New("a UserName must be specified") } - ci.Password = form.Get("Password") - if ci.Password == "" { + ur.Password = form.Get("Password") + if ur.Password == "" { return nil, errors.New("a Password must be specified") } - ci.Domain = form.Get("Domain") - if ci.Domain == "" { + ur.Domain = form.Get("Domain") + if ur.Domain == "" { return nil, errors.New("a Domain must be specified") } if form.Get("DualStack") == "1" { - ci.DualStack = true + ur.DualStack = true } else { - ci.DualStack = false + ur.DualStack = false } if ip6net := form.Get("IPv6Net"); ip6net != "" { @@ -63,8 +73,8 @@ func CreateClientInfoFromForm(form url.Values) (*ClientInfo, error) { if err != nil { return nil, errors.New("could not parse IPv6Net") } - ci.IPv6Net = ipnet + ur.IPv6Net = ipnet } - return ci, nil + return ur, nil }