Merge pull request #1496 from micro/namespace
Configurable Namespace & Public Suffix Domain Resolution
This commit is contained in:
		| @@ -1,181 +0,0 @@ | |||||||
| 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" |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| // CombinedAuthHandler wraps a server and authenticates requests |  | ||||||
| func CombinedAuthHandler(namespace string, r resolver.Resolver, h http.Handler) http.Handler { |  | ||||||
| 	if r == nil { |  | ||||||
| 		r = path.NewResolver() |  | ||||||
| 	} |  | ||||||
| 	if len(namespace) == 0 { |  | ||||||
| 		namespace = "go.micro" |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	return authHandler{ |  | ||||||
| 		handler:   h, |  | ||||||
| 		resolver:  r, |  | ||||||
| 		auth:      auth.DefaultAuth, |  | ||||||
| 		namespace: namespace, |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| type authHandler struct { |  | ||||||
| 	handler   http.Handler |  | ||||||
| 	auth      auth.Auth |  | ||||||
| 	resolver  resolver.Resolver |  | ||||||
| 	namespace string |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (h authHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { |  | ||||||
| 	// Determine the namespace |  | ||||||
| 	namespace, err := namespaceFromRequest(req) |  | ||||||
| 	if err != nil { |  | ||||||
| 		logger.Error(err) |  | ||||||
| 		namespace = auth.DefaultNamespace |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	// Set the namespace in the header |  | ||||||
| 	req.Header.Set(auth.NamespaceKey, namespace) |  | ||||||
|  |  | ||||||
| 	// Extract the token from the request |  | ||||||
| 	var token string |  | ||||||
| 	if header := req.Header.Get("Authorization"); len(header) > 0 { |  | ||||||
| 		// Extract the auth token from the request |  | ||||||
| 		if strings.HasPrefix(header, auth.BearerScheme) { |  | ||||||
| 			token = header[len(auth.BearerScheme):] |  | ||||||
| 		} |  | ||||||
| 	} else { |  | ||||||
| 		// Get the token out the cookies if not provided in headers |  | ||||||
| 		if c, err := req.Cookie("micro-token"); err == nil && c != nil { |  | ||||||
| 			token = strings.TrimPrefix(c.Value, auth.TokenCookieName+"=") |  | ||||||
| 			req.Header.Set("Authorization", auth.BearerScheme+token) |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	// Get the account using the token, fallback to a blank account |  | ||||||
| 	// since some endpoints can be unauthenticated, so the lack of an |  | ||||||
| 	// 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) |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	// Determine the name of the service being requested |  | ||||||
| 	endpoint, err := h.resolver.Resolve(req) |  | ||||||
| 	if err == resolver.ErrInvalidPath || err == resolver.ErrNotFound { |  | ||||||
| 		// a file not served by the resolver has been requested (e.g. favicon.ico) |  | ||||||
| 		endpoint = &resolver.Endpoint{Path: req.URL.Path} |  | ||||||
| 	} else if err != nil { |  | ||||||
| 		logger.Error(err) |  | ||||||
| 		http.Error(w, err.Error(), 500) |  | ||||||
| 		return |  | ||||||
| 	} else { |  | ||||||
| 		// set the endpoint in the context so it can be used to resolve |  | ||||||
| 		// the request later |  | ||||||
| 		ctx := context.WithValue(req.Context(), resolver.Endpoint{}, endpoint) |  | ||||||
| 		*req = *req.Clone(ctx) |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	// construct the resource name, e.g. home => go.micro.web.home |  | ||||||
| 	resName := h.namespace |  | ||||||
| 	if len(endpoint.Name) > 0 { |  | ||||||
| 		resName = resName + "." + endpoint.Name |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	// determine the resource path. there is an inconsistency in how resolvers |  | ||||||
| 	// use method, some use it as Users.ReadUser (the rpc method), and others |  | ||||||
| 	// use it as the HTTP method, e.g GET. TODO: Refactor this to make it consistent. |  | ||||||
| 	resEndpoint := endpoint.Path |  | ||||||
| 	if len(endpoint.Path) == 0 { |  | ||||||
| 		resEndpoint = endpoint.Method |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	// Perform the verification check to see if the account has access to |  | ||||||
| 	// the resource they're requesting |  | ||||||
| 	res := &auth.Resource{Type: "service", Name: resName, Endpoint: resEndpoint, Namespace: namespace} |  | ||||||
| 	if err := h.auth.Verify(acc, res); err == nil { |  | ||||||
| 		// The account has the necessary permissions to access the resource |  | ||||||
| 		h.handler.ServeHTTP(w, req) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	// The account is set, but they don't have enough permissions, hence |  | ||||||
| 	// we return a forbidden error. |  | ||||||
| 	if len(acc.ID) > 0 { |  | ||||||
| 		http.Error(w, "Forbidden request", 403) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	// If there is no auth login url set, 401 |  | ||||||
| 	loginURL := h.auth.Options().LoginURL |  | ||||||
| 	if loginURL == "" { |  | ||||||
| 		http.Error(w, "unauthorized request", 401) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	// Redirect to the login path |  | ||||||
| 	params := url.Values{"redirect_to": {req.URL.Path}} |  | ||||||
| 	loginWithRedirect := fmt.Sprintf("%v?%v", loginURL, params.Encode()) |  | ||||||
| 	http.Redirect(w, req, loginWithRedirect, http.StatusTemporaryRedirect) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func namespaceFromRequest(req *http.Request) (string, error) { |  | ||||||
| 	// needed to tmp debug host in prod. will be removed. |  | ||||||
| 	logger.Infof("Host is '%v'; URL Host is '%v'; URL Hostname is '%v'", req.Host, req.URL.Host, req.URL.Hostname()) |  | ||||||
|  |  | ||||||
| 	// determine the host, e.g. dev.micro.mu:8080 |  | ||||||
| 	host := req.URL.Hostname() |  | ||||||
| 	if len(host) == 0 { |  | ||||||
| 		// fallback to req.Host |  | ||||||
| 		var err error |  | ||||||
| 		host, _, err = net.SplitHostPort(req.Host) |  | ||||||
| 		if err != nil && strings.Contains(err.Error(), "missing port in address") { |  | ||||||
| 			host = req.Host |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	// check for an ip address |  | ||||||
| 	if net.ParseIP(host) != nil { |  | ||||||
| 		return auth.DefaultNamespace, nil |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	// check for dev enviroment |  | ||||||
| 	if host == "localhost" || host == "127.0.0.1" { |  | ||||||
| 		return auth.DefaultNamespace, nil |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	// TODO: this logic needs to be replaced with usage of publicsuffix |  | ||||||
| 	// if host is not a subdomain, deturn default namespace |  | ||||||
| 	comps := strings.Split(host, ".") |  | ||||||
| 	if len(comps) != 3 { |  | ||||||
| 		return auth.DefaultNamespace, nil |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	// check for the micro.mu domain |  | ||||||
| 	domain := fmt.Sprintf("%v.%v", comps[1], comps[2]) |  | ||||||
| 	if domain == "micro.mu" { |  | ||||||
| 		return auth.DefaultNamespace, nil |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	// return the subdomain as the host |  | ||||||
| 	return comps[0], nil |  | ||||||
| } |  | ||||||
| @@ -10,7 +10,6 @@ import ( | |||||||
|  |  | ||||||
| 	"github.com/gorilla/handlers" | 	"github.com/gorilla/handlers" | ||||||
| 	"github.com/micro/go-micro/v2/api/server" | 	"github.com/micro/go-micro/v2/api/server" | ||||||
| 	"github.com/micro/go-micro/v2/api/server/auth" |  | ||||||
| 	"github.com/micro/go-micro/v2/api/server/cors" | 	"github.com/micro/go-micro/v2/api/server/cors" | ||||||
| 	"github.com/micro/go-micro/v2/logger" | 	"github.com/micro/go-micro/v2/logger" | ||||||
| ) | ) | ||||||
| @@ -53,7 +52,11 @@ func (s *httpServer) Init(opts ...server.Option) error { | |||||||
|  |  | ||||||
| func (s *httpServer) Handle(path string, handler http.Handler) { | func (s *httpServer) Handle(path string, handler http.Handler) { | ||||||
| 	h := handlers.CombinedLoggingHandler(os.Stdout, handler) | 	h := handlers.CombinedLoggingHandler(os.Stdout, handler) | ||||||
| 	h = auth.CombinedAuthHandler(s.opts.Namespace, s.opts.Resolver, handler) |  | ||||||
|  | 	// apply the wrappers, e.g. auth | ||||||
|  | 	for _, wrapper := range s.opts.Wrappers { | ||||||
|  | 		h = wrapper(h) | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	if s.opts.EnableCORS { | 	if s.opts.EnableCORS { | ||||||
| 		h = cors.CombinedCORSHandler(h) | 		h = cors.CombinedCORSHandler(h) | ||||||
|   | |||||||
| @@ -2,6 +2,7 @@ package server | |||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"crypto/tls" | 	"crypto/tls" | ||||||
|  | 	"net/http" | ||||||
|  |  | ||||||
| 	"github.com/micro/go-micro/v2/api/resolver" | 	"github.com/micro/go-micro/v2/api/resolver" | ||||||
| 	"github.com/micro/go-micro/v2/api/server/acme" | 	"github.com/micro/go-micro/v2/api/server/acme" | ||||||
| @@ -16,8 +17,16 @@ type Options struct { | |||||||
| 	EnableTLS    bool | 	EnableTLS    bool | ||||||
| 	ACMEHosts    []string | 	ACMEHosts    []string | ||||||
| 	TLSConfig    *tls.Config | 	TLSConfig    *tls.Config | ||||||
| 	Namespace    string |  | ||||||
| 	Resolver     resolver.Resolver | 	Resolver     resolver.Resolver | ||||||
|  | 	Wrappers     []Wrapper | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type Wrapper func(h http.Handler) http.Handler | ||||||
|  |  | ||||||
|  | func WrapHandler(w Wrapper) Option { | ||||||
|  | 	return func(o *Options) { | ||||||
|  | 		o.Wrappers = append(o.Wrappers, w) | ||||||
|  | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| func EnableCORS(b bool) Option { | func EnableCORS(b bool) Option { | ||||||
| @@ -56,12 +65,6 @@ func TLSConfig(t *tls.Config) Option { | |||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| func Namespace(n string) Option { |  | ||||||
| 	return func(o *Options) { |  | ||||||
| 		o.Namespace = n |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func Resolver(r resolver.Resolver) Option { | func Resolver(r resolver.Resolver) Option { | ||||||
| 	return func(o *Options) { | 	return func(o *Options) { | ||||||
| 		o.Resolver = r | 		o.Resolver = r | ||||||
|   | |||||||
| @@ -89,7 +89,7 @@ type Token struct { | |||||||
|  |  | ||||||
| const ( | const ( | ||||||
| 	// DefaultNamespace used for auth | 	// DefaultNamespace used for auth | ||||||
| 	DefaultNamespace = "micro" | 	DefaultNamespace = "go.micro" | ||||||
| 	// NamespaceKey is the key used when storing the namespace in metadata | 	// NamespaceKey is the key used when storing the namespace in metadata | ||||||
| 	NamespaceKey = "Micro-Namespace" | 	NamespaceKey = "Micro-Namespace" | ||||||
| 	// MetadataKey is the key used when storing the account in metadata | 	// MetadataKey is the key used when storing the account in metadata | ||||||
|   | |||||||
| @@ -8,6 +8,8 @@ import ( | |||||||
| ) | ) | ||||||
|  |  | ||||||
| type Options struct { | type Options struct { | ||||||
|  | 	// Namespace the service belongs to | ||||||
|  | 	Namespace string | ||||||
| 	// ID is the services auth ID | 	// ID is the services auth ID | ||||||
| 	ID string | 	ID string | ||||||
| 	// Secret is used to authenticate the service | 	// Secret is used to authenticate the service | ||||||
| @@ -28,6 +30,13 @@ type Options struct { | |||||||
|  |  | ||||||
| type Option func(o *Options) | type Option func(o *Options) | ||||||
|  |  | ||||||
|  | // Namespace the service belongs to | ||||||
|  | func Namespace(n string) Option { | ||||||
|  | 	return func(o *Options) { | ||||||
|  | 		o.Namespace = n | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
| // Store to back auth | // Store to back auth | ||||||
| func Store(s store.Store) Option { | func Store(s store.Store) Option { | ||||||
| 	return func(o *Options) { | 	return func(o *Options) { | ||||||
|   | |||||||
| @@ -158,6 +158,11 @@ func (s *svc) Revoke(role string, res *auth.Resource) error { | |||||||
|  |  | ||||||
| // Verify an account has access to a resource | // Verify an account has access to a resource | ||||||
| func (s *svc) Verify(acc *auth.Account, res *auth.Resource) error { | 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{ | 	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, 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* | 		{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) { | func (s *svc) Inspect(token string) (*auth.Account, error) { | ||||||
| 	// try to decode JWT locally and fall back to srv if an error occurs | 	// try to decode JWT locally and fall back to srv if an error occurs | ||||||
| 	if len(strings.Split(token, ".")) == 3 && s.jwt != nil { | 	if len(strings.Split(token, ".")) == 3 && s.jwt != nil { | ||||||
| 		if acc, err := s.jwt.Inspect(token); err == nil { | 		return s.jwt.Inspect(token) | ||||||
| 			return acc, nil |  | ||||||
| 		} |  | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	// 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}) | 	rsp, err := s.auth.Inspect(context.TODO(), &pb.InspectRequest{Token: token}) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	return serializeAccount(rsp.Account), nil | 	return serializeAccount(rsp.Account), nil | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										1
									
								
								go.sum
									
									
									
									
									
								
							
							
						
						
									
										1
									
								
								go.sum
									
									
									
									
									
								
							| @@ -546,6 +546,7 @@ golang.org/x/net v0.0.0-20191027093000-83d349e8ac1a/go.mod h1:z5CRVTTTmAJ677TzLL | |||||||
| golang.org/x/net v0.0.0-20191126235420-ef20fe5d7933/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= | golang.org/x/net v0.0.0-20191126235420-ef20fe5d7933/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= | ||||||
| golang.org/x/net v0.0.0-20200222125558-5a598a2470a0 h1:MsuvTghUPjX762sGLnGsxC3HM0B5r83wEtYcYR8/vRs= | golang.org/x/net v0.0.0-20200222125558-5a598a2470a0 h1:MsuvTghUPjX762sGLnGsxC3HM0B5r83wEtYcYR8/vRs= | ||||||
| golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= | golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= | ||||||
|  | golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e h1:3G+cUijn7XD+S4eJFddp53Pv7+slrESplyjG25HgL+k= | ||||||
| golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= | ||||||
| golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= | golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= | ||||||
| golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= | golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= | ||||||
|   | |||||||
| @@ -9,7 +9,6 @@ import ( | |||||||
| 	"github.com/micro/go-micro/v2/debug/stats" | 	"github.com/micro/go-micro/v2/debug/stats" | ||||||
| 	"github.com/micro/go-micro/v2/debug/trace" | 	"github.com/micro/go-micro/v2/debug/trace" | ||||||
| 	"github.com/micro/go-micro/v2/errors" | 	"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/metadata" | ||||||
| 	"github.com/micro/go-micro/v2/server" | 	"github.com/micro/go-micro/v2/server" | ||||||
| ) | ) | ||||||
| @@ -145,11 +144,6 @@ func AuthHandler(fn func() auth.Auth) server.HandlerWrapper { | |||||||
| 				return h(ctx, req, rsp) | 				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 | 			// Extract the token if present. Note: if noop is being used | ||||||
| 			// then the token can be blank without erroring | 			// then the token can be blank without erroring | ||||||
| 			var token string | 			var token string | ||||||
| @@ -162,33 +156,17 @@ func AuthHandler(fn func() auth.Auth) server.HandlerWrapper { | |||||||
| 				token = header[len(auth.BearerScheme):] | 				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 | 			// Inspect the token and get the account | ||||||
| 			account, err := a.Inspect(token) | 			account, err := a.Inspect(token) | ||||||
| 			if err != nil { | 			if err != nil { | ||||||
| 				account = &auth.Account{Namespace: namespace} | 				account = &auth.Account{} | ||||||
| 			} |  | ||||||
|  |  | ||||||
| 			// 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") |  | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			// construct the resource | 			// construct the resource | ||||||
| 			res := &auth.Resource{ | 			res := &auth.Resource{ | ||||||
| 				Type:      "service", | 				Type:     "service", | ||||||
| 				Name:      req.Service(), | 				Name:     req.Service(), | ||||||
| 				Endpoint:  req.Endpoint(), | 				Endpoint: req.Endpoint(), | ||||||
| 				Namespace: namespace, |  | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			// Verify the caller has access to the resource | 			// Verify the caller has access to the resource | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user