diff --git a/serverutil/tls.go b/serverutil/tls.go index 58dbf6c0fb7a58e5682fdeab3405ec4c602da154..fbf39f7ee0eae65009d60179a5449255487fc03e 100644 --- a/serverutil/tls.go +++ b/serverutil/tls.go @@ -14,7 +14,8 @@ import ( // TLSAuthACL describes a single access control entry. Path and // CommonName are anchored regular expressions (they must match the -// entire string). +// entire string). The first path to match in a list of ACLs will +// identify the ACL to be applied. type TLSAuthACL struct { Path string `yaml:"path"` CommonName string `yaml:"cn"` @@ -32,10 +33,11 @@ func (p *TLSAuthACL) compile() error { return err } -func (p *TLSAuthACL) match(req *http.Request) bool { - if !p.pathRx.MatchString(req.URL.Path) { - return false - } +func (p *TLSAuthACL) matchPath(req *http.Request) bool { + return p.pathRx.MatchString(req.URL.Path) +} + +func (p *TLSAuthACL) matchCN(req *http.Request) bool { for _, cert := range req.TLS.PeerCertificates { if p.cnRx.MatchString(cert.Subject.CommonName) { return true @@ -81,9 +83,13 @@ func (c *TLSAuthConfig) match(req *http.Request) bool { if c == nil || len(c.Allow) == 0 { return true } + for _, acl := range c.Allow { - if acl.match(req) { - return true + if acl.matchPath(req) { + if acl.matchCN(req) { + return true + } + break } } return false diff --git a/serverutil/tls_test.go b/serverutil/tls_test.go index 02bec027a4296afd4cf17abf93259a9d6cb67b49..a0fbce5c63a81507842988f233acc4f808d325c9 100644 --- a/serverutil/tls_test.go +++ b/serverutil/tls_test.go @@ -145,6 +145,18 @@ func TestTLS_Serve(t *testing.T) { Cert: dir + "/server_cert.pem", Key: dir + "/server_key.pem", CA: dir + "/ca.pem", + Auth: &TLSAuthConfig{ + Allow: []*TLSAuthACL{ + &TLSAuthACL{ + Path: "/testpath", + CommonName: "client1.*", + }, + &TLSAuthACL{ + Path: ".*", + CommonName: ".*", + }, + }, + }, }, } h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { @@ -162,15 +174,35 @@ func TestTLS_Serve(t *testing.T) { }, }, } - _, err := c.Get("https://localhost:19898/") - if err == nil { - t.Fatal("client without certificate got a successful reply") - } // A client with a properly signed cert will get a successful reply. - c = newTestClient(t, dir, "client1") - _, err = c.Get("https://localhost:19898/") - if err != nil { - t.Fatalf("client with cert got error: %v", err) + c1 := newTestClient(t, dir, "client1") + c2 := newTestClient(t, dir, "client2") + testdata := []struct { + tag string + client *http.Client + uri string + expectedOk bool + }{ + {"no-cert", c, "/", false}, + {"client1", c1, "/", true}, + {"client2", c2, "/", true}, + {"client1", c1, "/testpath", true}, + {"client2", c2, "/testpath", false}, + } + + for _, td := range testdata { + resp, err := td.client.Get("https://localhost:19898" + td.uri) + ok := false + if err == nil { + if resp.StatusCode == 200 { + ok = true + } else { + err = fmt.Errorf("HTTP status %s", resp.Status) + } + } + if ok != td.expectedOk { + t.Errorf("client %s requesting %s got ok=%v, expected=%v (err=%v)", td.tag, td.uri, td.expectedOk, ok, err) + } } }