perkeep/pkg/httputil/auth_test.go

183 lines
4.1 KiB
Go
Raw Normal View History

buildbot/master: add Basic Auth support. Moved BasicAuth parsing and localhost detection code from pkg/auth -> pkg/httputil for use by buildbot master. Added user config file for remote access. The file's name is "masterbot-config.json" and is located in osutil.CamliConfigDir(), which on Unix will resolve to $XDG_CONFIG_HOME/camlistore/, if XDG_CONFIG_HOME set, or ~/.config/camlistore/. On Windows it will be under %APPDATA%\Camlistore\. The expected format is a json object with usernames as the keys and sha1 sums of the password as the values, i.e.: { "user1": "1234567890abcdef12341234567890abcdef1234", "user2": "1234abcdef12345678901234abcdef1234567890" } This file is polled at a 1 minute interval and reparsed if the file's modification time is more recent then the previous parse attempt. It is ok for the file to go missing, it will zero out the remote user list. A malformed file will result in the master exiting. New commandline flags, -tlsCertFile & -tlsKeyFile, added. Specifying both will enable TLS on the listener specified by -host. The go source contains generate_cert.go in crypto/tls that can be used to generate self-signed cert.pem and key.pem for testing. Added -skiptlscheck commandline option to builder. This allows the builder to report to https:// addresses with self-signed certs as we don't currently have a way to specify the cert chains to be used for TLS verification. This is a stop-gap solution. When launching a master that listens for secure connections, we currently need tell the builders to skip certificate validation. Add '-builderopts="-skiptlscheck"' to the master's commandline to skip cerfication verification. Change-Id: I0750b5c9fa8f4def67fc05a841087b50abded2f7
2013-10-31 05:00:17 +00:00
/*
Copyright 2013 The Camlistore Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package httputil
import (
"fmt"
"io/ioutil"
"net"
"net/http"
"net/http/httptest"
"testing"
)
func testServer(t *testing.T, l net.Listener) *httptest.Server {
ts := &httptest.Server{
Listener: l,
Config: &http.Server{
Handler: http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
if IsLocalhost(r) {
fmt.Fprintf(rw, "authorized")
return
}
fmt.Fprintf(rw, "unauthorized")
}),
},
}
ts.Start()
return ts
}
func TestLocalhostAuthIPv6(t *testing.T) {
l, err := net.Listen("tcp", "[::1]:0")
if err != nil {
t.Skip("skipping IPv6 test; can't listen on [::1]:0")
}
_, port, err := net.SplitHostPort(l.Addr().String())
if err != nil {
t.Fatal(err)
}
// See if IPv6 works on this machine first. It seems the above
// Listen can pass on Linux but fail here in the dial.
c, err := net.Dial("tcp6", l.Addr().String())
if err != nil {
t.Skipf("skipping IPv6 test; dial back to %s failed with %v", l.Addr(), err)
}
c.Close()
ts := testServer(t, l)
defer ts.Close()
// Use an explicit transport to force IPv6 (http.Get resolves localhost in IPv4 otherwise)
trans := &http.Transport{
Dial: func(network, addr string) (net.Conn, error) {
c, err := net.Dial("tcp6", addr)
return c, err
},
}
testLoginRequest(t, &http.Client{Transport: trans}, "http://[::1]:"+port)
// See if we can get an IPv6 from resolving localhost
localips, err := net.LookupIP("localhost")
if err != nil {
t.Skipf("skipping IPv6 test; resolving localhost failed with %v", err)
}
if hasIPv6(localips) {
testLoginRequest(t, &http.Client{Transport: trans}, "http://localhost:"+port)
} else {
t.Logf("incomplete IPv6 test; resolving localhost didn't return any IPv6 addresses")
}
}
func hasIPv6(ips []net.IP) bool {
for _, ip := range ips {
if ip.To4() == nil {
return true
}
}
return false
}
func TestLocalhostAuthIPv4(t *testing.T) {
l, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
t.Skip("skipping IPv4 test; can't listen on 127.0.0.1:0")
}
_, port, err := net.SplitHostPort(l.Addr().String())
if err != nil {
t.Fatal(err)
}
ts := testServer(t, l)
defer ts.Close()
testLoginRequest(t, &http.Client{}, "http://127.0.0.1:"+port)
testLoginRequest(t, &http.Client{}, "http://localhost:"+port)
}
func testLoginRequest(t *testing.T, client *http.Client, URL string) {
res, err := client.Get(URL)
if err != nil {
t.Fatal(err)
}
body, err := ioutil.ReadAll(res.Body)
if err != nil {
t.Fatal(err)
}
const exp = "authorized"
if string(body) != exp {
t.Errorf("got %q (instead of %v)", string(body), exp)
}
}
func TestBasicAuth(t *testing.T) {
for _, d := range []struct {
header string
u, pw string
valid bool
}{
// Empty is invalid.
{},
{
// Missing password.
header: "Basic QWxhZGRpbg==",
},
{
// Malformed base64 encoding.
header: "Basic foo",
},
{
// Malformed header, no 'Basic ' prefix.
header: "QWxhZGRpbg==",
},
{
header: "Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==",
u: "Aladdin",
pw: "open sesame",
valid: true,
},
} {
req, err := http.NewRequest("GET", "/", nil)
if err != nil {
t.Fatal(err)
}
if d.header != "" {
req.Header.Set("Authorization", d.header)
}
u, pw, err := BasicAuth(req)
t.Log(d.header, err)
if d.valid && err != nil {
t.Error("Want success parse of auth header, got", err)
}
if !d.valid && err == nil {
t.Error("Want error parsing", d.header)
}
if d.u != u {
t.Errorf("Want user %q, got %q", d.u, u)
}
if d.pw != pw {
t.Errorf("Want password %q, got %q", d.pw, pw)
}
}
}