diff --git a/.appveyor.yml b/.appveyor.yml index ed5a41d41ea..ac7d3229def 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -42,4 +42,4 @@ build_script: - set CGO_ENABLED=1 - go build -o ghostunnel-%GIT_VERSION%-windows-%GOARCH%-with-pkcs11.exe -ldflags "-w -extldflags \"-static\" -extld=%EXTLD%" -i . # Execute tests - - go test -v . ./auth + - go test -v . ./auth ./certloader ./proxy ./wildcard diff --git a/Makefile b/Makefile index 1ef35e51e00..85ff99a198e 100644 --- a/Makefile +++ b/Makefile @@ -27,6 +27,7 @@ unit: go test -v -covermode=count -coverprofile=coverage-unit-test-auth.out ./auth go test -v -covermode=count -coverprofile=coverage-unit-test-certloader.out ./certloader go test -v -covermode=count -coverprofile=coverage-unit-test-proxy.out ./proxy + go test -v -covermode=count -coverprofile=coverage-unit-test-wildcard.out ./wildcard .PHONY: unit # Run integration tests diff --git a/auth/auth.go b/auth/auth.go index 81b56e6028a..8bcbaf186fd 100644 --- a/auth/auth.go +++ b/auth/auth.go @@ -21,6 +21,8 @@ import ( "errors" "net" "net/url" + + "github.com/square/ghostunnel/wildcard" ) // Logger is used by this package to log messages @@ -53,7 +55,7 @@ type ACL struct { // AllowURIs lists URI SANs that should be allowed access. If a principal // has a valid certificate with at least one of these URI SANs, we grant // access. - AllowedURIs []string + AllowedURIs []wildcard.Matcher // Logger is used to log authorization decisions. Logger Logger } @@ -181,10 +183,10 @@ func intersectsIP(left, right []net.IP) bool { } // Returns true if at least one item from left is also contained in right. -func intersectsURI(left []string, right []*url.URL) bool { +func intersectsURI(left []wildcard.Matcher, right []*url.URL) bool { for _, l := range left { for _, r := range right { - if r.String() == l { + if l.Matches(r.String()) { return true } } diff --git a/auth/auth_test.go b/auth/auth_test.go index cda60966f63..8cc8cb9eb43 100644 --- a/auth/auth_test.go +++ b/auth/auth_test.go @@ -23,6 +23,7 @@ import ( "net/url" "testing" + "github.com/square/ghostunnel/wildcard" "github.com/stretchr/testify/assert" ) @@ -55,7 +56,7 @@ func TestAuthorizeReject(t *testing.T) { AllowedCNs: []string{"test"}, AllowedOUs: []string{"test"}, AllowedDNSs: []string{"test"}, - AllowedURIs: []string{"test"}, + AllowedURIs: []wildcard.Matcher{wildcard.MustCompile("test")}, } assert.NotNil(t, testACL.VerifyPeerCertificateServer(nil, fakeChains), "should reject cert w/o matching CN/OU") @@ -103,7 +104,7 @@ func TestAuthorizeAllowIP(t *testing.T) { func TestAuthorizeAllowURI(t *testing.T) { testACL := ACL{ - AllowedURIs: []string{"scheme://valid/path"}, + AllowedURIs: []wildcard.Matcher{wildcard.MustCompile("scheme://valid/path")}, } assert.Nil(t, testACL.VerifyPeerCertificateServer(nil, fakeChains), "allow-uri-san should allow clients with matching URI SAN") @@ -111,7 +112,7 @@ func TestAuthorizeAllowURI(t *testing.T) { func TestAuthorizeRejectURI(t *testing.T) { testACL := ACL{ - AllowedURIs: []string{"schema://invalid/path"}, + AllowedURIs: []wildcard.Matcher{wildcard.MustCompile("scheme://invalid/path")}, } assert.NotNil(t, testACL.VerifyPeerCertificateServer(nil, fakeChains), "should reject cert w/o matching URI") @@ -192,7 +193,7 @@ func TestVerifyRejectIP(t *testing.T) { func TestVerifyAllowURI(t *testing.T) { testACL := ACL{ - AllowedURIs: []string{"scheme://valid/path"}, + AllowedURIs: []wildcard.Matcher{wildcard.MustCompile("scheme://valid/path")}, } assert.Nil(t, testACL.VerifyPeerCertificateClient(nil, fakeChains), "verify-uri-san should allow clients with matching URI SAN") @@ -200,7 +201,7 @@ func TestVerifyAllowURI(t *testing.T) { func TestVerifyRejectURI(t *testing.T) { testACL := ACL{ - AllowedURIs: []string{"scheme://invalid/path"}, + AllowedURIs: []wildcard.Matcher{wildcard.MustCompile("scheme://invalid/path")}, } assert.NotNil(t, testACL.VerifyPeerCertificateClient(nil, fakeChains), "should reject cert w/o matching URI") diff --git a/main.go b/main.go index a61aaa60ce0..b30deef3ddc 100644 --- a/main.go +++ b/main.go @@ -38,6 +38,7 @@ import ( "github.com/square/ghostunnel/auth" "github.com/square/ghostunnel/certloader" "github.com/square/ghostunnel/proxy" + "github.com/square/ghostunnel/wildcard" "github.com/square/go-sq-metrics" "gopkg.in/alecthomas/kingpin.v2" @@ -389,13 +390,19 @@ func serverListen(context *Context) error { return err } + allowedURIs, err := wildcard.CompileList(*serverAllowedURIs) + if err != nil { + logger.Printf("invalid URI pattern in --allow-uri flag (%s)", err) + return err + } + serverACL := auth.ACL{ AllowAll: *serverAllowAll, AllowedCNs: *serverAllowedCNs, AllowedOUs: *serverAllowedOUs, AllowedDNSs: *serverAllowedDNSs, AllowedIPs: *serverAllowedIPs, - AllowedURIs: *serverAllowedURIs, + AllowedURIs: allowedURIs, Logger: logger, } @@ -578,12 +585,18 @@ func clientBackendDialer(cert certloader.Certificate, network, address, host str config.ServerName = *clientServerName } + allowedURIs, err := wildcard.CompileList(*clientAllowedURIs) + if err != nil { + logger.Printf("invalid URI pattern in --verify-uri flag (%s)", err) + return nil, err + } + clientACL := auth.ACL{ AllowedCNs: *clientAllowedCNs, AllowedOUs: *clientAllowedOUs, AllowedDNSs: *clientAllowedDNSs, AllowedIPs: *clientAllowedIPs, - AllowedURIs: *clientAllowedURIs, + AllowedURIs: allowedURIs, Logger: logger, } diff --git a/wildcard/matcher.go b/wildcard/matcher.go index b69887d4173..6fe1b98af2e 100644 --- a/wildcard/matcher.go +++ b/wildcard/matcher.go @@ -64,13 +64,36 @@ type regexpMatcher struct { pattern *regexp.Regexp } -// New creates a new Matcher given a pattern, using '/' as the separator. -func New(pattern string) (Matcher, error) { - return NewWithSeparator(pattern, defaultSeparator) +// Compile creates a new Matcher given a pattern, using '/' as the separator. +func Compile(pattern string) (Matcher, error) { + return CompileWithSeparator(pattern, defaultSeparator) } -// New creates a new Matcher given a pattern and separator rune. -func NewWithSeparator(pattern string, separator rune) (Matcher, error) { +// CompileList creates new Matchers given a list patterns, using '/' as the separator. +func CompileList(patterns []string) ([]Matcher, error) { + ms := []Matcher{} + for _, pattern := range patterns { + m, err := Compile(pattern) + if err != nil { + return nil, err + } + ms = append(ms, m) + } + return ms, nil +} + +// MustCompile creates a new Matcher given a pattern, using '/' as the separator, +// and panics if the given pattern was invalid. +func MustCompile(pattern string) Matcher { + m, err := CompileWithSeparator(pattern, defaultSeparator) + if err != nil { + panic(err) + } + return m +} + +// CompileWithSeparator creates a new Matcher given a pattern and separator rune. +func CompileWithSeparator(pattern string, separator rune) (Matcher, error) { // Build regular expression from wildcard pattern // - Wildcard '*' should match all chars except forward slash // - Wildcard '**' should match all chars, including forward slash diff --git a/wildcard/matcher_test.go b/wildcard/matcher_test.go index 280fc6a7ec3..ad9a94ef004 100644 --- a/wildcard/matcher_test.go +++ b/wildcard/matcher_test.go @@ -19,7 +19,7 @@ package wildcard import "testing" func testMatches(t *testing.T, pattern string, matches []string, invalids []string) { - matcher, err := New(pattern) + matcher, err := Compile(pattern) if err != nil { t.Fatalf("bad pattern: '%s' (%s)", pattern, err) } @@ -233,7 +233,7 @@ func TestInvalidPatterns(t *testing.T) { "test://**/asdf", "**://foo/asdf", } { - _, err := New(pattern) + _, err := Compile(pattern) if err == nil { t.Errorf("should reject invalid pattern '%s'", pattern) }