Refactorization and further implementation

- renamed clientoinfo to updaterequest
- added userconfig skeleton
- added hashed password skeleton
This commit is contained in:
Torben Nehmer 2021-08-21 20:34:45 +02:00
parent a6a065cafb
commit 9f1b9f1690
8 changed files with 149 additions and 28 deletions

49
cmd/hash-password.go Normal file
View File

@ -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)
}

1
go.mod
View File

@ -8,4 +8,5 @@ require (
github.com/miekg/dns v1.0.14 github.com/miekg/dns v1.0.14
github.com/spf13/cobra v1.2.1 github.com/spf13/cobra v1.2.1
github.com/spf13/viper v1.8.1 github.com/spf13/viper v1.8.1
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9
) )

View File

@ -4,7 +4,8 @@ import "github.com/spf13/viper"
type config struct { type config struct {
DNSServer string DNSServer string
DefaultTTL uint32 DNSDefaultTTL uint32
UsersConfigDir string
} }
var C config var C config
@ -16,9 +17,11 @@ func init() {
func SetConfigDefaults() { func SetConfigDefaults() {
viper.SetDefault("Service.DNS.Server", "10.10.11.254:53") viper.SetDefault("Service.DNS.Server", "10.10.11.254:53")
viper.SetDefault("Service.DNS.DefaultTTL", 60) viper.SetDefault("Service.DNS.DefaultTTL", 60)
viper.SetDefault("Service.Users.ConfigDir", "users/")
} }
func LoadConfig() { func LoadConfig() {
C.DNSServer = viper.GetString("Service.DNS.Server") 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")
} }

View File

@ -42,7 +42,7 @@ func UpdateDNSEntry(domain string, hostname string, ip4 net.IP, ip6 net.IP) erro
Name: fqdn, Name: fqdn,
Rrtype: dns.TypeA, Rrtype: dns.TypeA,
Class: dns.ClassINET, Class: dns.ClassINET,
Ttl: C.DefaultTTL, Ttl: C.DNSDefaultTTL,
}, },
A: ip4bin, A: ip4bin,
}) })
@ -58,7 +58,7 @@ func UpdateDNSEntry(domain string, hostname string, ip4 net.IP, ip6 net.IP) erro
Name: fqdn, Name: fqdn,
Rrtype: dns.TypeAAAA, Rrtype: dns.TypeAAAA,
Class: dns.ClassINET, Class: dns.ClassINET,
Ttl: C.DefaultTTL, Ttl: C.DNSDefaultTTL,
}, },
AAAA: ip6bin, AAAA: ip6bin,
}) })

30
service/userconfig.go Normal file
View File

@ -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
}

16
users/test.yml Normal file
View File

@ -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

View File

@ -7,6 +7,7 @@ import (
"gitea.nehmer.net/torben/dyndns/service" "gitea.nehmer.net/torben/dyndns/service"
"github.com/gorilla/mux" "github.com/gorilla/mux"
"golang.org/x/crypto/bcrypt"
) )
func Server() { func Server() {
@ -36,16 +37,27 @@ func handleDNSUpdate(w http.ResponseWriter, r *http.Request) {
return return
} }
ci, err := service.CreateClientInfoFromForm(r.Form) ur, err := createUpdateRequestFromForm(r.Form)
if err != nil { if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest) http.Error(w, err.Error(), http.StatusBadRequest)
return 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") w.Header().Set("Content-Type", "text/plain")
fmt.Fprintln(w, "OK") fmt.Fprintln(w, "OK")
fmt.Fprintln(w, ci) fmt.Fprintln(w, ur.PrettyPrint())
log.Println(ci) log.Println(ur.PrettyPrint())
log.Println(ci.IPv6Net.IP) log.Println(ur.IPv6Net.IP)
log.Println(ci.IPv6Net.Mask) 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)
} }

View File

@ -1,13 +1,15 @@
package service package webapi
import ( import (
"encoding/json"
"errors" "errors"
"fmt" "fmt"
"log"
"net" "net"
"net/url" "net/url"
) )
type ClientInfo struct { type UpdateRequest struct {
IPv4 net.IP IPv4 net.IP
IPv6 net.IP IPv6 net.IP
UserName string UserName string
@ -17,45 +19,53 @@ type ClientInfo struct {
IPv6Net *net.IPNet 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", 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) { func (ur *UpdateRequest) PrettyPrint() string {
ci := &ClientInfo{} 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 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") return nil, errors.New("could not parse IPv4 address")
} }
} }
if form.Get("IPv6") != "" { 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") return nil, errors.New("could not parse IPv6 address")
} }
} }
ci.UserName = form.Get("UserName") ur.UserName = form.Get("UserName")
if ci.UserName == "" { if ur.UserName == "" {
return nil, errors.New("a UserName must be specified") return nil, errors.New("a UserName must be specified")
} }
ci.Password = form.Get("Password") ur.Password = form.Get("Password")
if ci.Password == "" { if ur.Password == "" {
return nil, errors.New("a Password must be specified") return nil, errors.New("a Password must be specified")
} }
ci.Domain = form.Get("Domain") ur.Domain = form.Get("Domain")
if ci.Domain == "" { if ur.Domain == "" {
return nil, errors.New("a Domain must be specified") return nil, errors.New("a Domain must be specified")
} }
if form.Get("DualStack") == "1" { if form.Get("DualStack") == "1" {
ci.DualStack = true ur.DualStack = true
} else { } else {
ci.DualStack = false ur.DualStack = false
} }
if ip6net := form.Get("IPv6Net"); ip6net != "" { if ip6net := form.Get("IPv6Net"); ip6net != "" {
@ -63,8 +73,8 @@ func CreateClientInfoFromForm(form url.Values) (*ClientInfo, error) {
if err != nil { if err != nil {
return nil, errors.New("could not parse IPv6Net") return nil, errors.New("could not parse IPv6Net")
} }
ci.IPv6Net = ipnet ur.IPv6Net = ipnet
} }
return ci, nil return ur, nil
} }