diff --git a/web_test.go b/web_test.go new file mode 100644 index 0000000..fc4b4ec --- /dev/null +++ b/web_test.go @@ -0,0 +1,374 @@ +package main + +import ( + "log" + "net" + "net/http" + "reflect" + "testing" + + "github.com/google/go-cmp/cmp" +) + +var ( + addr = "localhost" + port uint16 = 1234 + rawCfg = Config{ + ServerPort: port, + Users: []User{ + User{ + Username: "user", + // "secret" -> bcrypt-hashed + Password: "$2a$12$He9X4KNAFJQy0CL2c9.Df.tjXwCkideOogwJ7DNtO/I8qzeJZfc3i", + Records: []string{ + "record.example.org", + }, + }, + User{ + Username: "user2", + // "secret" -> bcrypt-hashed + Password: "$2a$12$He9X4KNAFJQy0CL2c9.Df.tjXwCkideOogwJ7DNtO/I8qzeJZfc3i", + Records: []string{ + "record2.example.org", + }, + }, + User{ + Username: "bad-hash", + Password: "secret", + Records: []string{ + "record3.example.org", + }, + }, + }, + RRConfigs: []RRConfig{ + RRConfig{ + Recordname: "record.example.org", + Zonename: "zone.example.org", + Nameserver: "ns1.example.org", + Tsigalgo: "hmac-sha256", + Tsigid: "tsig-id", + Tsigkey: "some-secret-key", + }, + RRConfig{ + Recordname: "record2.example.org", + Zonename: "zone.example.org", + Nameserver: "ns1.example.org", + Tsigalgo: "hmac-sha256", + Tsigid: "tsig-id", + Tsigkey: "some-secret-key", + }, + RRConfig{ + Recordname: "record3.example.org", + Zonename: "zone.example.org", + Nameserver: "ns1.example.org", + Tsigalgo: "hmac-sha256", + Tsigid: "tsig-id", + Tsigkey: "some-secret-key", + }, + }, + } + cfg *Config +) + +// pre-initialize config for all testcases +func init() { + var err error + cfg, err = prepareConfig(&rawCfg) + if err != nil { + log.Fatalf("Preparing config failed: %v", err) + } +} + +func prepareRequest(t *testing.T, params map[string]string, headers map[string]string) *http.Request { + req, err := http.NewRequest("GET", "http://"+addr, nil) + if err != nil { + t.Fatalf("Generating request failed: %v", err) + } + + // append http parameters to http request + q := req.URL.Query() + for k, v := range params { + q.Add(k, v) + } + req.URL.RawQuery = q.Encode() + + // set headers + for k, v := range headers { + req.Header.Set(k, v) + } + + return req +} + +type credentials struct { + username string + password string +} + +func TestIsAuthenticated(t *testing.T) { + tests := []struct { + name string + auth *credentials + expUser *User + }{ + { + "missing basicauth", + nil, + nil, + }, + { + "non-existent user", + &credentials{ + "unknown", + "secret", + }, + nil, + }, + { + "invalid hash", + &credentials{ + "bad-hash", + "secret", + }, + nil, + }, + { + "correct user", + &credentials{ + "user", + "secret", + }, + &User{ + Username: "user", + Password: "$2a$12$He9X4KNAFJQy0CL2c9.Df.tjXwCkideOogwJ7DNtO/I8qzeJZfc3i", + Records: []string{ + "record.example.org", + }, + records: map[string]bool{ + "record.example.org": true, + }, + }, + }, + } + + for _, tc := range tests { + req := prepareRequest(t, nil, nil) + if tc.auth != nil { + req.SetBasicAuth(tc.auth.username, tc.auth.password) + } + + res := isAuthenticated(cfg, req) + if !reflect.DeepEqual(tc.expUser, res) { + t.Errorf("%s: res: %s", tc.name, + cmp.Diff(tc.expUser, res, cmp.AllowUnexported(User{}))) + } + } +} + +func TestVerifyHostname(t *testing.T) { + tests := []struct { + name string + user *User + hostname string + expMsg string + expRrconfig *RRConfig + }{ + { + "empty hostname", + cfg.users["user"], + "", + "nohost", + nil, + }, + { + "user not allowed", + cfg.users["user2"], + "record.example.org", + "badauth", + nil, + }, + { + "correct hostname", + cfg.users["user"], + "record.example.org", + "", + cfg.rrconfigs["record.example.org"], + }, + } + + for _, tc := range tests { + msg, rrconfig := verifyHostname(cfg, tc.user, tc.hostname) + + if !reflect.DeepEqual(tc.expMsg, msg) { + t.Errorf("%s: res: %s", tc.name, + cmp.Diff(tc.expMsg, msg)) + } + if !reflect.DeepEqual(tc.expRrconfig, rrconfig) { + t.Errorf("%s: res: %s", tc.name, + cmp.Diff(tc.expRrconfig, rrconfig)) + } + } +} + +func TestGetIpAddress(t *testing.T) { + tests := []struct { + name string + params map[string]string + headers map[string]string + remoteAddr string + expAddr net.IP + }{ + { + "no address", + nil, + nil, + "", + nil, + }, + { + "remote addr - invalid ip", + nil, + nil, + "1.1.1:1234", + nil, + }, + { + "remote addr - missing port", + nil, + nil, + "1.1.1.1", + nil, + }, + { + "remote addr - valid", + nil, + nil, + "1.1.1.1:1234", + net.ParseIP("1.1.1.1"), + }, + { + "X-Real-IP - empty", + nil, + map[string]string{ + "X-Real-IP": "", + }, + "1.1.1.1:1234", + net.ParseIP("1.1.1.1"), + }, + { + "X-Real-IP - invalid", + nil, + map[string]string{ + "X-Real-IP": "2.2.2", + }, + "1.1.1.1:1234", + nil, + }, + { + "X-Real-IP - valid", + nil, + map[string]string{ + "X-Real-IP": "2.2.2.2", + }, + "1.1.1.1:1234", + net.ParseIP("2.2.2.2"), + }, + { + "X-Forwarded-For - empty", + nil, + map[string]string{ + "X-Forwarded-For": "", + "X-Real-IP": "2.2.2.2", + }, + "1.1.1.1:1234", + net.ParseIP("2.2.2.2"), + }, + { + "X-Forwarded-For - comma only", + nil, + map[string]string{ + "X-Forwarded-For": ",", + "X-Real-IP": "2.2.2.2", + }, + "1.1.1.1:1234", + nil, + }, + { + "X-Forwarded-For - invalid entry", + nil, + map[string]string{ + "X-Forwarded-For": "3.3.3", + "X-Real-IP": "2.2.2.2", + }, + "1.1.1.1:1234", + nil, + }, + { + "X-Forwarded-For - one entry", + nil, + map[string]string{ + "X-Forwarded-For": "3.3.3.3", + "X-Real-IP": "2.2.2.2", + }, + "1.1.1.1:1234", + net.ParseIP("3.3.3.3"), + }, + { + "X-Forwarded-For - multiple entries", + nil, + map[string]string{ + "X-Forwarded-For": "3.3.3.3, 3.3.3.4", + "X-Real-IP": "2.2.2.2", + }, + "1.1.1.1:1234", + net.ParseIP("3.3.3.3"), + }, + { + "myip - empty", + map[string]string{ + "myip": "", + }, + map[string]string{ + "X-Forwarded-For": "3.3.3.3, 3.3.3.4", + "X-Real-IP": "2.2.2.2", + }, + "1.1.1.1:1234", + net.ParseIP("3.3.3.3"), + }, + { + "myip - invalid", + map[string]string{ + "myip": "1.2.3", + }, + map[string]string{ + "X-Forwarded-For": "3.3.3.3, 3.3.3.4", + "X-Real-IP": "2.2.2.2", + }, + "1.1.1.1:1234", + nil, + }, + { + "myip - valid", + map[string]string{ + "myip": "4.4.4.4", + }, + map[string]string{ + "X-Forwarded-For": "3.3.3.3, 3.3.3.4", + "X-Real-IP": "2.2.2.2", + }, + "1.1.1.1:1234", + net.ParseIP("4.4.4.4"), + }, + } + + for _, tc := range tests { + req := prepareRequest(t, tc.params, tc.headers) + req.RemoteAddr = tc.remoteAddr + + addr := getIpAddress(req) + if !reflect.DeepEqual(tc.expAddr, addr) { + t.Errorf("%s: res: %s", tc.name, + cmp.Diff(tc.expAddr, addr)) + } + } +}