diff --git a/.gitignore b/.gitignore index fd32655..96cfb51 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ gertdns gertdns.exe conf.toml +auth.toml diff --git a/README.md b/README.md index 4a23007..412e41b 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,55 @@ # gertdns A DynDNS server meant for gertroot + +Running: +```sh +go run main.go +``` + +Bullding: +```sh +go build main.go +``` + +## Config +`conf.toml` by default +```toml +[DNS] +Port = 5353 # DNS server port +Host = '0.0.0.0' # DNS server host +Domains = ['example.com.'] # enabled domains, suffix with a . + +[HTTP] +Port = 8080 # HTTP server port +Host = '127.0.0.1' # HTTP server host +Socket = '' # HTTP unix socket +SocketFileMode = 420 # File mode for HTTP unix socket in decimal (420 = 0644) +``` + +## Users +`auth.toml` by default +```toml +[someusername] # user name of the user +Password = '1234' # password of the user +Hashed = false # false if omitted; if false, password will be hashed +Domains = ["subdomain.example.com."] # domains the user can register, suffix with a . + +# .. +``` + +## Flags +### --enable-debug-mode +Will output all registered records on the index page of the HTTP server. +Type: `bool` +Default: `false` + +### --config-file +Will define what config file should be used. +Type: `string` +Default: `conf.toml` + + +### --auth-file +Will define what file should be used to define users that can log in. +Type: `string` +Default: `auth.toml` diff --git a/auth.example.toml b/auth.example.toml new file mode 100644 index 0000000..856828e --- /dev/null +++ b/auth.example.toml @@ -0,0 +1,5 @@ +[username] +Password = '1234' # will be converted on start +Domains = [ + 'subdomain.example.com.', +] diff --git a/auth/auth.go b/auth/auth.go index 6f847a1..32de358 100644 --- a/auth/auth.go +++ b/auth/auth.go @@ -1,11 +1,89 @@ package auth -type AuthenticationRequest struct { +import ( + "log" + + "github.com/raja/argon2pw" +) + +type PasswordAuthenticationRequest struct { User string Password string Domain string } -func IsAuthenticated(request AuthenticationRequest) (bool, error) { - return true, nil +type userRaw struct { + Password string + Hashed bool + Domains []string +} + +type user struct { + password string + domains map[string]string +} + +var parsedUsers map[string]user = map[string]user{} + +func (user user) Authenticate(password string) (bool, error) { + return argon2pw.CompareHashWithPassword(user.password, password) +} + +func (selfUser *userRaw) Tidy() (user, error) { + if !selfUser.Hashed { + pw, err := argon2pw.GenerateSaltedHash(selfUser.Password) + if err != nil { + return user{}, err + } + selfUser.Password = pw + selfUser.Hashed = true + } + + // Create a map => faster access times + parsedDomains := map[string]string{} + for _, domain := range selfUser.Domains { + parsedDomains[domain] = domain + } + + parsedUser := user{ + password: selfUser.Password, + domains: parsedDomains, + } + + return parsedUser, nil +} + +func IsPasswordAuthenticated(request PasswordAuthenticationRequest) (bool, error) { + currentUser, found := parsedUsers[request.User] + if !found { + return false, nil + } + + if _, ok := currentUser.domains[request.Domain]; !ok { + return false, nil + } + + return currentUser.Authenticate(request.Password) +} + +func Init(authFilePath string) error { + users, err := loadAuthFile(authFilePath) + if err != nil { + return err + } + + for name, user := range users { + log.Printf("%s\n", name) + log.Printf("%+v\n", user) + parsedUser, err := user.Tidy() + if err != nil { + return err + } + + parsedUsers[name] = parsedUser + } + + writeAuthFile(authFilePath, users) + + return nil } diff --git a/auth/toml.go b/auth/toml.go new file mode 100644 index 0000000..89b9c8d --- /dev/null +++ b/auth/toml.go @@ -0,0 +1,45 @@ +package auth + +import ( + "io/ioutil" + "os" + + "github.com/gookit/color" + "github.com/pelletier/go-toml/v2" +) + +func loadAuthFile(authFilePath string) (map[string]*userRaw, error) { + bytes, err := ioutil.ReadFile(authFilePath) + + if err != nil { + color.Errorln(err.Error()) + color.Warnln("Creating new authentication file") + return writeAuthFile(authFilePath, map[string]*userRaw{}) + } + + var users map[string]*userRaw + err = toml.Unmarshal(bytes, &users) + if err != nil { + color.Errorln(err.Error()) + color.Errorln("Authentication file " + authFilePath + " could not be read as TOML file") + return users, err + } + color.Infoln("Loaded authentication file " + authFilePath) + return users, err +} + +func writeAuthFile(authFilePath string, users map[string]*userRaw) (map[string]*userRaw, error) { + + authBytes, err := toml.Marshal(users) + if err != nil { + color.Errorln(err.Error()) + color.Errorln("Default authentication struct is not TOML conform") + return users, err + } + err = os.WriteFile(authFilePath, authBytes, 0644) + if err != nil { + color.Errorln(err.Error()) + color.Warnln("Using default authentication without file") + } + return users, err +} diff --git a/go.mod b/go.mod index eee31e5..50875a5 100644 --- a/go.mod +++ b/go.mod @@ -7,6 +7,7 @@ require ( github.com/gookit/color v1.4.2 github.com/miekg/dns v1.1.43 github.com/pelletier/go-toml/v2 v2.0.0-beta.3 + github.com/raja/argon2pw v1.0.1 github.com/valyala/fasthttp v1.31.0 ) @@ -16,6 +17,7 @@ require ( github.com/savsgio/gotils v0.0.0-20210921075833-21a6215cb0e4 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778 // indirect + golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a // indirect golang.org/x/net v0.0.0-20210510120150-4163338589ed // indirect golang.org/x/sys v0.0.0-20211015200801-69063c4bb744 // indirect ) diff --git a/go.sum b/go.sum index 7f9388a..87f87ba 100644 --- a/go.sum +++ b/go.sum @@ -16,6 +16,8 @@ github.com/pelletier/go-toml/v2 v2.0.0-beta.3 h1:PNCTU4naEJ8mKal97P3A2qDU74QRQGl github.com/pelletier/go-toml/v2 v2.0.0-beta.3/go.mod h1:aNseLYu/uKskg0zpr/kbr2z8yGuWtotWf/0BpGIAL2Y= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/raja/argon2pw v1.0.1 h1:RIUM12+uQdj5/cWQLlEmZDD8xj5kQN1X9kTK0xfXjGQ= +github.com/raja/argon2pw v1.0.1/go.mod h1:idX/fPqwjX31YMTF2iIpEpNApV2YbQhSFr4iIhJaqp4= github.com/savsgio/gotils v0.0.0-20210921075833-21a6215cb0e4 h1:ocK/D6lCgLji37Z2so4xhMl46se1ntReQQCUIU4BWI8= github.com/savsgio/gotils v0.0.0-20210921075833-21a6215cb0e4/go.mod h1:oejLrk1Y/5zOF+c/aHtXqn3TFlzzbAgPWg8zBiAHDas= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -29,6 +31,7 @@ github.com/valyala/fasthttp v1.31.0/go.mod h1:2rsYD01CKFrjjsvFxx75KlEUNpWNBY9JWD github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc= github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778 h1:QldyIu/L63oPpyvQmHgvgickp1Yw510KJOqX7H24mg8= github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778/go.mod h1:2MuV+tbUrU1zIOPMxZ5EncGwgmMJsa+9ucAQZXxsObs= +golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a h1:kr2P4QFmQr29mSLA43kwrOcgcReGTfbE9N577tCTuBc= golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210510120150-4163338589ed h1:p9UgmWI9wKpfYmgaV/IZKGdXc5qEK45tDwwwDyjS26I= diff --git a/main.go b/main.go index 0432d2a..1531622 100644 --- a/main.go +++ b/main.go @@ -4,6 +4,7 @@ import ( "flag" "log" + "github.com/MarekWojt/gertdns/auth" "github.com/MarekWojt/gertdns/config" "github.com/MarekWojt/gertdns/dns" "github.com/MarekWojt/gertdns/web" @@ -11,7 +12,8 @@ import ( ) var ( - configFile = flag.String("configFile", "conf.toml", "Path to configuration file") + configFile = flag.String("config-file", "conf.toml", "Path to configuration file") + authFile = flag.String("auth-file", "auth.toml", "Path to authentication file") ) type dnsResult struct { @@ -29,6 +31,10 @@ func main() { dns.Init() web.Init() + err = auth.Init(*authFile) + if err != nil { + log.Fatalf("Failed to initialize authentication module: %s\n", err.Error()) + } webChan := make(chan error) dnsChan := make(chan dnsResult) diff --git a/web/web.go b/web/web.go index 8687fa5..b421957 100644 --- a/web/web.go +++ b/web/web.go @@ -108,13 +108,13 @@ func authenticatedRequest(request func(ctx *fasthttp.RequestCtx)) func(ctx *fast return } - authRequest := auth.AuthenticationRequest{ + authRequest := auth.PasswordAuthenticationRequest{ Domain: domain, User: user, Password: password, } - authenticated, err := auth.IsAuthenticated(authRequest) + authenticated, err := auth.IsPasswordAuthenticated(authRequest) if err != nil { ctx.SetStatusCode(fasthttp.StatusInternalServerError) return