diff --git a/api/resolver/resolver.go b/api/resolver/resolver.go index 12854b19..daa7078d 100644 --- a/api/resolver/resolver.go +++ b/api/resolver/resolver.go @@ -11,6 +11,12 @@ var ( ErrInvalidPath = errors.New("invalid path") ) +// NamespaceResolver resolves request to the namespace +type NamespaceResolver interface { + Resolve(r *http.Request) string + String() string +} + // Resolver resolves requests to endpoints type Resolver interface { Resolve(r *http.Request) (*Endpoint, error) @@ -27,6 +33,8 @@ type Endpoint struct { Method string // HTTP Path e.g /greeter. Path string + // Namespace, g.g. go.micro + Namespace string } type Options struct { diff --git a/api/server/auth/auth.go b/api/server/auth/auth.go index 7ae99214..deffa3dc 100644 --- a/api/server/auth/auth.go +++ b/api/server/auth/auth.go @@ -3,44 +3,35 @@ package auth import ( "context" "fmt" - "net" "net/http" "net/url" "strings" "github.com/micro/go-micro/v2/api/resolver" - "github.com/micro/go-micro/v2/api/resolver/path" "github.com/micro/go-micro/v2/auth" "github.com/micro/go-micro/v2/logger" - "golang.org/x/net/publicsuffix" ) // CombinedAuthHandler wraps a server and authenticates requests -func CombinedAuthHandler(prefix, namespace string, r resolver.Resolver, h http.Handler) http.Handler { - if r == nil { - r = path.NewResolver() - } - +func CombinedAuthHandler(r resolver.Resolver, nr resolver.NamespaceResolver, h http.Handler) http.Handler { return authHandler{ - handler: h, - resolver: r, - auth: auth.DefaultAuth, - servicePrefix: prefix, - namespace: namespace, + handler: h, + resolver: r, + nsResolver: nr, + auth: auth.DefaultAuth, } } type authHandler struct { - handler http.Handler - auth auth.Auth - resolver resolver.Resolver - namespace string - servicePrefix string + handler http.Handler + auth auth.Auth + resolver resolver.Resolver + nsResolver resolver.NamespaceResolver } func (h authHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { // Determine the namespace and set it in the header - namespace := h.NamespaceFromRequest(req) + namespace := h.nsResolver.Resolve(req) req.Header.Set(auth.NamespaceKey, namespace) // Extract the token from the request @@ -63,14 +54,7 @@ func (h authHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { // account doesn't necesserially mean a forbidden request acc, err := h.auth.Inspect(token) if err != nil { - acc = &auth.Account{Namespace: namespace} - } - - // Check the accounts namespace matches the namespace we're operating - // within. If not forbid the request and log the occurance. - if acc.Namespace != namespace { - logger.Debugf("Cross namespace request warning: account %v (%v) requested access to %v in the %v namespace", acc.ID, acc.Namespace, req.URL.Path, namespace) - // http.Error(w, "Forbidden namespace", 403) + acc = &auth.Account{} } // Determine the name of the service being requested @@ -90,9 +74,9 @@ func (h authHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { } // construct the resource name, e.g. home => go.micro.web.home - resName := h.servicePrefix + resName := namespace if len(endpoint.Name) > 0 { - resName = resName + "." + endpoint.Name + resName = namespace + "." + endpoint.Name } // determine the resource path. there is an inconsistency in how resolvers @@ -127,59 +111,7 @@ func (h authHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { } // Redirect to the login path - params := url.Values{"redirect_to": {req.URL.Path}} + params := url.Values{"redirect_to": {req.URL.String()}} loginWithRedirect := fmt.Sprintf("%v?%v", loginURL, params.Encode()) http.Redirect(w, req, loginWithRedirect, http.StatusTemporaryRedirect) } - -func (h authHandler) NamespaceFromRequest(req *http.Request) string { - // check to see what the provided namespace is, we only do - // domain mapping if the namespace is set to 'domain' - if h.namespace != "domain" { - return h.namespace - } - - // determine the host, e.g. dev.micro.mu:8080 - host := req.URL.Hostname() - if len(host) == 0 { - if h, _, err := net.SplitHostPort(req.Host); err == nil { - host = h // host does contain a port - } else if strings.Contains(err.Error(), "missing port in address") { - host = req.Host // host does not contain a port - } - } - - // check for an ip address - if net.ParseIP(host) != nil { - return auth.DefaultNamespace - } - - // check for dev enviroment - if host == "localhost" || host == "127.0.0.1" { - return auth.DefaultNamespace - } - - // extract the top level domain plus one (e.g. 'myapp.com') - domain, err := publicsuffix.EffectiveTLDPlusOne(host) - if err != nil { - logger.Debugf("Unable to extract domain from %v", host) - return auth.DefaultNamespace - } - - // check to see if the domain matches the host of micro.mu, in - // these cases we return the default namespace - if domain == host || domain == "micro.mu" { - return auth.DefaultNamespace - } - - // remove the domain from the host, leaving the subdomain - subdomain := strings.TrimSuffix(host, "."+domain) - - // return the reversed subdomain as the namespace - comps := strings.Split(subdomain, ".") - for i := len(comps)/2 - 1; i >= 0; i-- { - opp := len(comps) - 1 - i - comps[i], comps[opp] = comps[opp], comps[i] - } - return strings.Join(comps, ".") -} diff --git a/api/server/auth/auth_test.go b/api/server/auth/auth_test.go deleted file mode 100644 index 7fd09581..00000000 --- a/api/server/auth/auth_test.go +++ /dev/null @@ -1,38 +0,0 @@ -package auth - -import ( - "net/http" - "net/url" - "testing" - - "github.com/micro/go-micro/v2/auth" -) - -func TestNamespaceFromRequest(t *testing.T) { - tt := []struct { - Host string - Namespace string - }{ - {Host: "micro.mu", Namespace: auth.DefaultNamespace}, - {Host: "micro.com.au", Namespace: auth.DefaultNamespace}, - {Host: "web.micro.mu", Namespace: auth.DefaultNamespace}, - {Host: "api.micro.mu", Namespace: auth.DefaultNamespace}, - {Host: "myapp.com", Namespace: auth.DefaultNamespace}, - {Host: "staging.myapp.com", Namespace: "staging"}, - {Host: "staging.myapp.m3o.app", Namespace: "myapp.staging"}, - {Host: "127.0.0.1", Namespace: auth.DefaultNamespace}, - {Host: "localhost", Namespace: auth.DefaultNamespace}, - {Host: "81.151.101.146", Namespace: auth.DefaultNamespace}, - } - - h := &authHandler{namespace: "domain"} - - for _, tc := range tt { - t.Run(tc.Host, func(t *testing.T) { - ns := h.NamespaceFromRequest(&http.Request{Host: tc.Host, URL: &url.URL{Host: tc.Host}}) - if ns != tc.Namespace { - t.Errorf("Expected namespace %v for host %v, actually got %v", tc.Namespace, tc.Host, ns) - } - }) - } -} diff --git a/api/server/http/http.go b/api/server/http/http.go index 3d1030ac..c35d24b7 100644 --- a/api/server/http/http.go +++ b/api/server/http/http.go @@ -53,7 +53,7 @@ func (s *httpServer) Init(opts ...server.Option) error { func (s *httpServer) Handle(path string, handler http.Handler) { h := handlers.CombinedLoggingHandler(os.Stdout, handler) - h = auth.CombinedAuthHandler(s.opts.ServicePrefix, s.opts.Namespace, s.opts.Resolver, handler) + h = auth.CombinedAuthHandler(s.opts.Resolver, s.opts.NamespaceResolver, handler) if s.opts.EnableCORS { h = cors.CombinedCORSHandler(h) diff --git a/api/server/options.go b/api/server/options.go index 6fd61672..9aa14579 100644 --- a/api/server/options.go +++ b/api/server/options.go @@ -10,15 +10,14 @@ import ( type Option func(o *Options) type Options struct { - EnableACME bool - EnableCORS bool - ACMEProvider acme.Provider - EnableTLS bool - ACMEHosts []string - TLSConfig *tls.Config - Resolver resolver.Resolver - Namespace string - ServicePrefix string + EnableACME bool + EnableCORS bool + ACMEProvider acme.Provider + EnableTLS bool + ACMEHosts []string + TLSConfig *tls.Config + Resolver resolver.Resolver + NamespaceResolver resolver.NamespaceResolver } func EnableCORS(b bool) Option { @@ -57,20 +56,14 @@ func TLSConfig(t *tls.Config) Option { } } -func ServicePrefix(n string) Option { - return func(o *Options) { - o.ServicePrefix = n - } -} - -func Namespace(n string) Option { - return func(o *Options) { - o.Namespace = n - } -} - func Resolver(r resolver.Resolver) Option { return func(o *Options) { o.Resolver = r } } + +func NamespaceResolver(r resolver.NamespaceResolver) Option { + return func(o *Options) { + o.NamespaceResolver = r + } +} diff --git a/auth/service/service.go b/auth/service/service.go index 57d6082e..cb1740c7 100644 --- a/auth/service/service.go +++ b/auth/service/service.go @@ -158,6 +158,11 @@ func (s *svc) Revoke(role string, res *auth.Resource) error { // Verify an account has access to a resource func (s *svc) Verify(acc *auth.Account, res *auth.Resource) error { + // set the namespace on the resource + if len(res.Namespace) == 0 { + res.Namespace = s.Options().Namespace + } + queries := [][]string{ {res.Namespace, res.Type, res.Name, res.Endpoint}, // check for specific role, e.g. service.foo.ListFoo:admin (role is checked in accessForRule) {res.Namespace, res.Type, res.Name, "*"}, // check for wildcard endpoint, e.g. service.foo* @@ -205,16 +210,15 @@ func (s *svc) Verify(acc *auth.Account, res *auth.Resource) error { func (s *svc) Inspect(token string) (*auth.Account, error) { // try to decode JWT locally and fall back to srv if an error occurs if len(strings.Split(token, ".")) == 3 && s.jwt != nil { - if acc, err := s.jwt.Inspect(token); err == nil { - return acc, nil - } + return s.jwt.Inspect(token) } + // the token is not a JWT or we do not have the keys to decode it, + // fall back to the auth service rsp, err := s.auth.Inspect(context.TODO(), &pb.InspectRequest{Token: token}) if err != nil { return nil, err } - return serializeAccount(rsp.Account), nil } diff --git a/go.sum b/go.sum index b7d9c90b..4de561e2 100644 --- a/go.sum +++ b/go.sum @@ -399,6 +399,7 @@ go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/multierr v1.3.0 h1:sFPn2GLc3poCkfrpIXGhBD2X0CMIo4Q/zSULXrj/+uc= go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= go.uber.org/ratelimit v0.0.0-20180316092928-c15da0234277/go.mod h1:2X8KaoNd1J0lZV+PxJk/5+DGbO/tpwLR1m++a7FnB/Y= +go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee h1:0mgffUl7nfd+FpvXMVz4IDEaUSmT1ysygQC7qYo7sG4= go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= go.uber.org/zap v1.13.0 h1:nR6NoDBgAf67s68NhaXbsojM+2gxp3S1hWkHDl27pVU= go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= @@ -432,6 +433,7 @@ golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHl golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f h1:J5lckAjkw6qYlOZNj90mLYNTEKDvWeuc1yieZ8qUzUE= golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= @@ -529,6 +531,7 @@ golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20191216173652-a0e659d51361 h1:RIIXAeV6GvDBuADKumTODatUqANFZ+5BPMnzsy4hulY= golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -600,6 +603,7 @@ honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3 h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXeM= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= k8s.io/kubernetes v1.13.0/go.mod h1:ocZa8+6APFNC2tX1DZASIbocyYT5jHzqFVsY5aoB7Jk= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= diff --git a/util/wrapper/wrapper.go b/util/wrapper/wrapper.go index 100f2f2b..9a7fe54f 100644 --- a/util/wrapper/wrapper.go +++ b/util/wrapper/wrapper.go @@ -9,7 +9,6 @@ import ( "github.com/micro/go-micro/v2/debug/stats" "github.com/micro/go-micro/v2/debug/trace" "github.com/micro/go-micro/v2/errors" - "github.com/micro/go-micro/v2/logger" "github.com/micro/go-micro/v2/metadata" "github.com/micro/go-micro/v2/server" ) @@ -155,11 +154,6 @@ func AuthHandler(fn func() auth.Auth) server.HandlerWrapper { return h(ctx, req, rsp) } - // Check for auth service endpoints which should be excluded from auth - if strings.HasPrefix(req.Endpoint(), "Auth.") { - return h(ctx, req, rsp) - } - // Extract the token if present. Note: if noop is being used // then the token can be blank without erroring var token string @@ -172,33 +166,17 @@ func AuthHandler(fn func() auth.Auth) server.HandlerWrapper { token = header[len(auth.BearerScheme):] } - // Get the namespace for the request - namespace, ok := metadata.Get(ctx, auth.NamespaceKey) - if !ok { - logger.Debugf("Missing request namespace") - namespace = auth.DefaultNamespace - } - // Inspect the token and get the account account, err := a.Inspect(token) if err != nil { - account = &auth.Account{Namespace: namespace} - } - - // Check the accounts namespace matches the namespace we're operating - // within. If not forbid the request and log the occurance. - if account.Namespace != namespace { - logger.Debugf("Cross namespace request forbidden: account %v (%v) requested access to %v %v in the %v namespace", - account.ID, account.Namespace, req.Service(), req.Endpoint(), namespace) - // return errors.Forbidden(req.Service(), "cross namespace request") + account = &auth.Account{} } // construct the resource res := &auth.Resource{ - Type: "service", - Name: req.Service(), - Endpoint: req.Endpoint(), - Namespace: namespace, + Type: "service", + Name: req.Service(), + Endpoint: req.Endpoint(), } // Verify the caller has access to the resource