diff --git a/api/server/auth/auth.go b/api/server/auth/auth.go index b17ef8ce..e4a216c5 100644 --- a/api/server/auth/auth.go +++ b/api/server/auth/auth.go @@ -2,6 +2,7 @@ package auth import ( "fmt" + "net" "net/http" "net/url" "strings" @@ -9,6 +10,7 @@ import ( "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 @@ -36,6 +38,16 @@ type authHandler struct { } 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 { @@ -56,7 +68,14 @@ 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{} + 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.Warnf("Cross namespace request forbidden: account %v (%v) requested access to %v in the %v namespace", acc.ID, acc.Namespace, req.URL.Path, namespace) + w.WriteHeader(http.StatusForbidden) } // Determine the name of the service being requested @@ -85,15 +104,15 @@ func (h authHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { // 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} + 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 403. + // The account is set, but they don't have enough permissions, hence + // we return a forbidden error. if len(acc.ID) > 0 { w.WriteHeader(http.StatusForbidden) return @@ -111,3 +130,42 @@ func (h authHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { loginWithRedirect := fmt.Sprintf("%v?%v", loginURL, params.Encode()) http.Redirect(w, req, loginWithRedirect, http.StatusTemporaryRedirect) } + +func namespaceFromRequest(req *http.Request) (string, error) { + // determine the host, e.g. dev.micro.mu:8080 + host := req.URL.Host + if len(host) == 0 { + host = req.Host + } + + // check for an ip address + if net.ParseIP(host) != nil { + return auth.DefaultNamespace, nil + } + + // split the host to remove the port + host, _, err := net.SplitHostPort(req.Host) + if err != nil { + return "", err + } + + // check for dev enviroment + if host == "localhost" || host == "127.0.0.1" { + return auth.DefaultNamespace, nil + } + + // 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 +} diff --git a/auth/auth.go b/auth/auth.go index 206d2d76..5f954776 100644 --- a/auth/auth.go +++ b/auth/auth.go @@ -21,8 +21,6 @@ var ( ErrInvalidRole = errors.New("invalid role") // ErrForbidden is returned when a user does not have the necessary roles to access a resource ErrForbidden = errors.New("resource forbidden") - // BearerScheme used for Authorization header - BearerScheme = "Bearer " ) // Auth providers authentication and authorization @@ -50,11 +48,13 @@ type Auth interface { // Resource is an entity such as a user or type Resource struct { // Name of the resource - Name string + Name string `json:"name"` // Type of resource, e.g. - Type string + Type string `json:"type"` // Endpoint resource e.g NotesService.Create - Endpoint string + Endpoint string `json:"endpoint"` + // Namespace the resource belongs to + Namespace string `json:"namespace"` } // Account provided by an auth provider @@ -69,7 +69,7 @@ type Account struct { Roles []string `json:"roles"` // Any other associated metadata Metadata map[string]string `json:"metadata"` - // Namespace the account belongs to, default blank + // Namespace the account belongs to Namespace string `json:"namespace"` // Secret for the account, e.g. the password Secret string `json:"secret"` @@ -88,12 +88,18 @@ type Token struct { } const ( + // DefaultNamespace used for auth + DefaultNamespace = "micro" + // NamespaceKey is the key used when storing the namespace in metadata + NamespaceKey = "Micro-Namespace" // MetadataKey is the key used when storing the account in metadata MetadataKey = "auth-account" // TokenCookieName is the name of the cookie which stores the auth token TokenCookieName = "micro-token" // SecretCookieName is the name of the cookie which stores the auth secret SecretCookieName = "micro-secret" + // BearerScheme used for Authorization header + BearerScheme = "Bearer " ) // AccountFromContext gets the account from the context, which diff --git a/auth/default.go b/auth/default.go index 20f16a4f..358cf099 100644 --- a/auth/default.go +++ b/auth/default.go @@ -38,10 +38,11 @@ func (n *noop) Generate(id string, opts ...GenerateOption) (*Account, error) { options := NewGenerateOptions(opts...) return &Account{ - ID: id, - Roles: options.Roles, - Secret: options.Secret, - Metadata: options.Metadata, + ID: id, + Roles: options.Roles, + Secret: options.Secret, + Metadata: options.Metadata, + Namespace: DefaultNamespace, }, nil } @@ -63,7 +64,8 @@ func (n *noop) Verify(acc *Account, res *Resource) error { // Inspect a token func (n *noop) Inspect(token string) (*Account, error) { return &Account{ - ID: uuid.New().String(), + ID: uuid.New().String(), + Namespace: DefaultNamespace, }, nil } diff --git a/auth/service/proto/auth.pb.go b/auth/service/proto/auth.pb.go index 3cffecb4..aac61c2a 100644 --- a/auth/service/proto/auth.pb.go +++ b/auth/service/proto/auth.pb.go @@ -272,6 +272,7 @@ type Resource struct { Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` Type string `protobuf:"bytes,2,opt,name=type,proto3" json:"type,omitempty"` Endpoint string `protobuf:"bytes,3,opt,name=endpoint,proto3" json:"endpoint,omitempty"` + Namespace string `protobuf:"bytes,4,opt,name=namespace,proto3" json:"namespace,omitempty"` XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_unrecognized []byte `json:"-"` XXX_sizecache int32 `json:"-"` @@ -323,6 +324,13 @@ func (m *Resource) GetEndpoint() string { return "" } +func (m *Resource) GetNamespace() string { + if m != nil { + return m.Namespace + } + return "" +} + type GenerateRequest struct { Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` Roles []string `protobuf:"bytes,2,rep,name=roles,proto3" json:"roles,omitempty"` @@ -1123,12 +1131,12 @@ func init() { } var fileDescriptor_11312eec02fd5712 = []byte{ - // 896 bytes of a gzipped FileDescriptorProto + // 901 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xcc, 0x56, 0x4b, 0x6f, 0xdb, 0x46, 0x10, 0x36, 0x49, 0x89, 0x92, 0x47, 0x0f, 0x0b, 0x1b, 0x27, 0x25, 0x98, 0x47, 0x1d, 0xa6, 0x28, 0xdc, 0xa0, 0xa1, 0x0b, 0xe5, 0xd0, 0x47, 0x2e, 0x35, 0x22, 0x41, 0x4d, 0xda, 0xa8, 0x28, 0x91, - 0x22, 0xbd, 0x14, 0x01, 0x43, 0x4d, 0x6d, 0xc2, 0x32, 0xc9, 0xee, 0x2e, 0x8d, 0xfa, 0x52, 0xa0, - 0xa7, 0xde, 0x7a, 0xea, 0x4f, 0xe8, 0xcf, 0xea, 0xbd, 0x7f, 0xa3, 0xe0, 0x3e, 0x28, 0x91, 0xa2, + 0x22, 0xbd, 0x14, 0x01, 0x43, 0x4d, 0x6d, 0xc2, 0x32, 0xc9, 0xee, 0x2e, 0x8d, 0xea, 0x52, 0xa0, + 0xa7, 0xde, 0x7a, 0xea, 0x4f, 0xe8, 0xcf, 0xea, 0xbd, 0x7f, 0xa3, 0xe0, 0x3e, 0x68, 0x91, 0xa2, 0x02, 0xa3, 0xf5, 0x21, 0xb7, 0x9d, 0x07, 0x67, 0xe6, 0xfb, 0x66, 0x76, 0xb8, 0xf0, 0xe9, 0x49, 0xcc, 0x4f, 0xf3, 0x37, 0x7e, 0x94, 0x9e, 0x1f, 0x9d, 0xc7, 0x11, 0x4d, 0x8f, 0x4e, 0xd2, 0x47, 0xf2, 0x10, 0xe6, 0xfc, 0xf4, 0x88, 0x21, 0xbd, 0x88, 0x23, 0x3c, 0xca, 0x68, 0xca, 0xa5, 0xca, @@ -1139,45 +1147,46 @@ var fileDescriptor_11312eec02fd5712 = []byte{ 0x4c, 0xc8, 0x7d, 0xe8, 0x87, 0x51, 0x84, 0x8c, 0xbd, 0xe6, 0x85, 0xec, 0x18, 0x07, 0xc6, 0xe1, 0x6e, 0xd0, 0x93, 0x3a, 0xe9, 0xf2, 0x00, 0x06, 0x14, 0x7f, 0xa2, 0xc8, 0x4e, 0x95, 0x8f, 0x29, 0x7c, 0xfa, 0x4a, 0x29, 0x9d, 0x1c, 0xe8, 0x44, 0x14, 0x43, 0x8e, 0x0b, 0xc7, 0x3a, 0x30, 0x0e, - 0xad, 0x40, 0x8b, 0xe4, 0x16, 0xd8, 0xf8, 0x4b, 0x16, 0xd3, 0x4b, 0xa7, 0x25, 0x0c, 0x4a, 0xf2, - 0xfe, 0x34, 0xa1, 0xa3, 0x2a, 0x23, 0x43, 0x30, 0xe3, 0x85, 0xca, 0x6d, 0xc6, 0x0b, 0x42, 0xa0, - 0xc5, 0x2f, 0x33, 0x54, 0x99, 0xc4, 0x99, 0xec, 0x43, 0x9b, 0xa6, 0x4b, 0x64, 0x8e, 0x75, 0x60, - 0x1d, 0xee, 0x06, 0x52, 0x20, 0x5f, 0x42, 0xf7, 0x1c, 0x79, 0xb8, 0x08, 0x79, 0xe8, 0xb4, 0x04, - 0xfa, 0x0f, 0x9a, 0xd1, 0xfb, 0x2f, 0x94, 0xdb, 0x34, 0xe1, 0xf4, 0x32, 0x28, 0xbf, 0x22, 0x77, - 0x60, 0x37, 0x09, 0xcf, 0x91, 0x65, 0x61, 0x84, 0x4e, 0x5b, 0x24, 0x5c, 0x29, 0x88, 0x0b, 0xdd, - 0x8c, 0xa6, 0x17, 0xf1, 0x02, 0xa9, 0x63, 0x0b, 0x63, 0x29, 0x17, 0xc8, 0x18, 0x46, 0x14, 0xb9, - 0xd3, 0x11, 0x16, 0x25, 0xb9, 0x4f, 0x60, 0x50, 0x49, 0x46, 0x46, 0x60, 0x9d, 0xe1, 0xa5, 0xc2, - 0x57, 0x1c, 0x0b, 0x30, 0x17, 0xe1, 0x32, 0xd7, 0x08, 0xa5, 0xf0, 0x85, 0xf9, 0x99, 0xe1, 0xcd, - 0xa1, 0x1b, 0x20, 0x4b, 0x73, 0x1a, 0x61, 0x41, 0x43, 0x51, 0x89, 0xfa, 0x50, 0x9c, 0x1b, 0xa9, - 0x71, 0xa1, 0x8b, 0xc9, 0x22, 0x4b, 0xe3, 0x84, 0x0b, 0xf6, 0x77, 0x83, 0x52, 0xf6, 0xfe, 0x32, - 0x61, 0x6f, 0x86, 0x09, 0xd2, 0x90, 0xa3, 0x1a, 0xa5, 0x0d, 0xba, 0x4b, 0x6a, 0xcd, 0x75, 0x6a, - 0xbf, 0x5a, 0xa3, 0xd6, 0x12, 0xd4, 0x7e, 0x5c, 0xa3, 0xb6, 0x16, 0xf7, 0x6a, 0x14, 0xb7, 0xea, - 0x14, 0xaf, 0x68, 0x6c, 0xaf, 0xd3, 0x58, 0x22, 0xb5, 0xab, 0x48, 0xcb, 0x76, 0x74, 0xaa, 0xed, - 0xf8, 0x7f, 0xb4, 0x4f, 0x60, 0xb4, 0x42, 0xa3, 0x6e, 0xd6, 0x27, 0xd0, 0x51, 0x37, 0x46, 0xc4, - 0xd8, 0x7e, 0xb1, 0xb4, 0x9b, 0xf7, 0x0a, 0xfa, 0x33, 0x1a, 0x26, 0x5c, 0x13, 0x4d, 0xa0, 0x55, - 0x70, 0xa9, 0x1b, 0x58, 0x9c, 0xc9, 0x63, 0xe8, 0x52, 0xd5, 0x60, 0x51, 0x46, 0x6f, 0xfc, 0x5e, - 0x2d, 0xac, 0xee, 0x7f, 0x50, 0x3a, 0x7a, 0x7b, 0x30, 0x50, 0x81, 0x65, 0x6d, 0xde, 0x0f, 0x30, - 0x08, 0xf0, 0x22, 0x3d, 0xc3, 0x6b, 0x4f, 0x35, 0x82, 0xa1, 0x8e, 0xac, 0x72, 0x7d, 0x08, 0xc3, - 0x67, 0x09, 0xcb, 0x30, 0x2a, 0x71, 0xed, 0x43, 0x7b, 0x7d, 0x5d, 0x48, 0xc1, 0x7b, 0x0a, 0x7b, - 0xa5, 0xdf, 0x7f, 0xa6, 0xf0, 0x57, 0xe8, 0x8b, 0x8d, 0xb2, 0x6d, 0x56, 0x57, 0xd3, 0x62, 0x56, - 0xa6, 0x65, 0x63, 0x4b, 0x59, 0x0d, 0x5b, 0xea, 0x3e, 0xf4, 0x85, 0xf1, 0x75, 0x65, 0x23, 0xf5, - 0x84, 0x6e, 0x2a, 0xd7, 0xd2, 0x13, 0x18, 0xa8, 0xfc, 0x0a, 0xc2, 0xc3, 0x75, 0xac, 0xbd, 0xf1, - 0x7e, 0x0d, 0x80, 0x74, 0x56, 0x0c, 0xfc, 0x61, 0x40, 0x2b, 0xc8, 0x97, 0xd8, 0xb4, 0xd0, 0x44, - 0x77, 0xcc, 0x2d, 0xdd, 0xb1, 0xae, 0xd8, 0x1d, 0xf2, 0x08, 0x6c, 0xb9, 0x9b, 0x45, 0xed, 0xc3, - 0xf1, 0xcd, 0x4d, 0x3e, 0x91, 0xb1, 0x40, 0x39, 0x79, 0xbf, 0x1b, 0x30, 0x78, 0x2a, 0x16, 0xf1, - 0x75, 0xcf, 0xc9, 0x5a, 0x25, 0xd6, 0x55, 0x2a, 0x19, 0xc1, 0x50, 0x17, 0xa2, 0xc6, 0xaa, 0xa8, - 0x6d, 0x82, 0x4b, 0x7c, 0x27, 0x6a, 0xd3, 0x85, 0xa8, 0xda, 0x06, 0xd0, 0x2b, 0x7e, 0xb6, 0xfa, - 0xdf, 0xfb, 0x39, 0xf4, 0xa5, 0xa8, 0x66, 0xe2, 0x23, 0x68, 0xd3, 0xbc, 0x58, 0x98, 0xf2, 0x87, - 0x7b, 0xa3, 0x5e, 0x51, 0xbe, 0xc4, 0x40, 0x7a, 0x3c, 0xf4, 0xc1, 0x96, 0xd9, 0x48, 0x0f, 0x3a, - 0xdf, 0xcf, 0xbf, 0x9e, 0x7f, 0xfb, 0x6a, 0x3e, 0xda, 0x29, 0x84, 0x59, 0x70, 0x3c, 0x7f, 0x39, - 0x9d, 0x8c, 0x0c, 0x02, 0x60, 0x4f, 0xa6, 0xf3, 0x67, 0xd3, 0xc9, 0xc8, 0x1c, 0xff, 0x63, 0x40, - 0xeb, 0x38, 0xe7, 0xa7, 0xe4, 0x05, 0x74, 0xf5, 0x46, 0x22, 0xf7, 0xde, 0xbe, 0x78, 0xdd, 0xf7, - 0xb7, 0xda, 0x15, 0x9e, 0x1d, 0xf2, 0x1c, 0x3a, 0xea, 0x72, 0x92, 0xbb, 0x35, 0xef, 0xea, 0xe5, - 0x76, 0xef, 0x6d, 0x33, 0x97, 0xb1, 0x26, 0xfa, 0xf5, 0x70, 0xbb, 0xf1, 0x32, 0xa8, 0x38, 0x77, - 0x9a, 0x8d, 0x3a, 0xca, 0xf8, 0x47, 0xe8, 0xea, 0xc7, 0x0c, 0xf9, 0x0e, 0x5a, 0x05, 0xc1, 0xc4, - 0xab, 0x7d, 0xd3, 0xf0, 0x10, 0x72, 0x1f, 0xbc, 0xd5, 0xa7, 0x0c, 0xff, 0xb7, 0x01, 0xed, 0xa2, - 0x11, 0x8c, 0xcc, 0xc0, 0x96, 0xa3, 0x47, 0xea, 0x25, 0x55, 0xae, 0x86, 0x7b, 0x77, 0x8b, 0xb5, - 0xc4, 0x3d, 0x03, 0x5b, 0xce, 0xc9, 0x46, 0xa0, 0xca, 0x1c, 0x6f, 0x04, 0xaa, 0x0d, 0xd7, 0x0e, - 0x39, 0x56, 0x70, 0xdd, 0x06, 0x28, 0x3a, 0xc8, 0xed, 0x46, 0x9b, 0x0e, 0xf1, 0xc6, 0x16, 0x6f, - 0xc7, 0xc7, 0xff, 0x06, 0x00, 0x00, 0xff, 0xff, 0x4b, 0x8f, 0xf4, 0x22, 0x76, 0x0a, 0x00, 0x00, + 0xad, 0x40, 0x8b, 0xe4, 0x16, 0xd8, 0xf8, 0x4b, 0x16, 0xd3, 0x95, 0xd3, 0x12, 0x06, 0x25, 0x79, + 0x7f, 0x9a, 0xd0, 0x51, 0x95, 0x91, 0x21, 0x98, 0xf1, 0x42, 0xe5, 0x36, 0xe3, 0x05, 0x21, 0xd0, + 0xe2, 0xab, 0x0c, 0x55, 0x26, 0x71, 0x26, 0xfb, 0xd0, 0xa6, 0xe9, 0x12, 0x99, 0x63, 0x1d, 0x58, + 0x87, 0xbb, 0x81, 0x14, 0xc8, 0x97, 0xd0, 0x3d, 0x47, 0x1e, 0x2e, 0x42, 0x1e, 0x3a, 0x2d, 0x81, + 0xfe, 0x83, 0x66, 0xf4, 0xfe, 0x0b, 0xe5, 0x36, 0x4d, 0x38, 0x5d, 0x05, 0xe5, 0x57, 0xe4, 0x0e, + 0xec, 0x26, 0xe1, 0x39, 0xb2, 0x2c, 0x8c, 0xd0, 0x69, 0x8b, 0x84, 0x97, 0x0a, 0xe2, 0x42, 0x37, + 0xa3, 0xe9, 0x45, 0xbc, 0x40, 0xea, 0xd8, 0xc2, 0x58, 0xca, 0x05, 0x32, 0x86, 0x11, 0x45, 0xee, + 0x74, 0x84, 0x45, 0x49, 0xee, 0x13, 0x18, 0x54, 0x92, 0x91, 0x11, 0x58, 0x67, 0xb8, 0x52, 0xf8, + 0x8a, 0x63, 0x01, 0xe6, 0x22, 0x5c, 0xe6, 0x1a, 0xa1, 0x14, 0xbe, 0x30, 0x3f, 0x33, 0xbc, 0x25, + 0x74, 0x03, 0x64, 0x69, 0x4e, 0x23, 0x2c, 0x68, 0x28, 0x2a, 0x51, 0x1f, 0x8a, 0x73, 0x23, 0x35, + 0x2e, 0x74, 0x31, 0x59, 0x64, 0x69, 0x9c, 0x70, 0xc1, 0xfe, 0x6e, 0x50, 0xca, 0x55, 0x78, 0xad, + 0x1a, 0x3c, 0xef, 0x2f, 0x13, 0xf6, 0x66, 0x98, 0x20, 0x0d, 0x39, 0xaa, 0x41, 0xdb, 0x68, 0x46, + 0x49, 0xbc, 0xb9, 0x4e, 0xfc, 0x57, 0x6b, 0xc4, 0x5b, 0x82, 0xf8, 0x8f, 0x6b, 0xc4, 0xd7, 0xe2, + 0x5e, 0xad, 0x01, 0xf5, 0x0a, 0xd7, 0x48, 0x6e, 0xaf, 0x93, 0x5c, 0xf2, 0x60, 0x57, 0x79, 0x28, + 0x9b, 0xd5, 0xa9, 0x36, 0xeb, 0xff, 0x35, 0x65, 0x02, 0xa3, 0x4b, 0x34, 0xea, 0xde, 0x7d, 0x02, + 0x1d, 0x75, 0x9f, 0x44, 0x8c, 0xed, 0xd7, 0x4e, 0xbb, 0x79, 0xaf, 0xa0, 0x3f, 0xa3, 0x61, 0xc2, + 0x35, 0xd1, 0x04, 0x5a, 0x05, 0x97, 0xba, 0xbd, 0xc5, 0x99, 0x3c, 0x86, 0x2e, 0x55, 0xed, 0x17, + 0x65, 0xf4, 0xc6, 0xef, 0xd5, 0xc2, 0xea, 0xe9, 0x08, 0x4a, 0x47, 0x6f, 0x0f, 0x06, 0x2a, 0xb0, + 0xac, 0xcd, 0xfb, 0x01, 0x06, 0x01, 0x5e, 0xa4, 0x67, 0x78, 0xed, 0xa9, 0x46, 0x30, 0xd4, 0x91, + 0x55, 0xae, 0x0f, 0x61, 0xf8, 0x2c, 0x61, 0x19, 0x46, 0x25, 0xae, 0x7d, 0x68, 0xaf, 0x2f, 0x13, + 0x29, 0x78, 0x4f, 0x61, 0xaf, 0xf4, 0xfb, 0xcf, 0x14, 0xfe, 0x0a, 0x7d, 0xb1, 0x6f, 0xb6, 0xcd, + 0xea, 0xe5, 0xb4, 0x98, 0x95, 0x69, 0xd9, 0xd8, 0x61, 0x56, 0xc3, 0x0e, 0xbb, 0x0f, 0x7d, 0x61, + 0x7c, 0x5d, 0xd9, 0x57, 0x3d, 0xa1, 0x9b, 0xca, 0xa5, 0xf5, 0x04, 0x06, 0x2a, 0xbf, 0x82, 0xf0, + 0x70, 0x1d, 0x6b, 0x6f, 0xbc, 0x5f, 0x03, 0x20, 0x9d, 0x15, 0x03, 0x7f, 0x18, 0xd0, 0x0a, 0xf2, + 0x25, 0x36, 0xad, 0x3b, 0xd1, 0x1d, 0x73, 0x4b, 0x77, 0xac, 0x2b, 0x76, 0x87, 0x3c, 0x02, 0x5b, + 0x6e, 0x6e, 0x51, 0xfb, 0x70, 0x7c, 0x73, 0x93, 0x4f, 0x64, 0x2c, 0x50, 0x4e, 0xde, 0xef, 0x06, + 0x0c, 0x9e, 0x8a, 0x35, 0x7d, 0xdd, 0x73, 0xb2, 0x56, 0x89, 0x75, 0x95, 0x4a, 0x46, 0x30, 0xd4, + 0x85, 0xa8, 0xb1, 0x2a, 0x6a, 0x9b, 0xe0, 0x12, 0xdf, 0x89, 0xda, 0x74, 0x21, 0xaa, 0xb6, 0x01, + 0xf4, 0x8a, 0x5f, 0xb1, 0xfe, 0x33, 0x7f, 0x0e, 0x7d, 0x29, 0xaa, 0x99, 0xf8, 0x08, 0xda, 0x34, + 0x2f, 0x16, 0xa6, 0xfc, 0x1d, 0xdf, 0xa8, 0x57, 0x94, 0x2f, 0x31, 0x90, 0x1e, 0x0f, 0x7d, 0xb0, + 0x65, 0x36, 0xd2, 0x83, 0xce, 0xf7, 0xf3, 0xaf, 0xe7, 0xdf, 0xbe, 0x9a, 0x8f, 0x76, 0x0a, 0x61, + 0x16, 0x1c, 0xcf, 0x5f, 0x4e, 0x27, 0x23, 0x83, 0x00, 0xd8, 0x93, 0xe9, 0xfc, 0xd9, 0x74, 0x32, + 0x32, 0xc7, 0xff, 0x18, 0xd0, 0x3a, 0xce, 0xf9, 0x29, 0x79, 0x01, 0x5d, 0xbd, 0x91, 0xc8, 0xbd, + 0xb7, 0x2f, 0x5e, 0xf7, 0xfd, 0xad, 0x76, 0x85, 0x67, 0x87, 0x3c, 0x87, 0x8e, 0xba, 0x9c, 0xe4, + 0x6e, 0xcd, 0xbb, 0x7a, 0xb9, 0xdd, 0x7b, 0xdb, 0xcc, 0x65, 0xac, 0x89, 0x7e, 0x5b, 0xdc, 0x6e, + 0xbc, 0x0c, 0x2a, 0xce, 0x9d, 0x66, 0xa3, 0x8e, 0x32, 0xfe, 0x11, 0xba, 0xfa, 0xa9, 0x43, 0xbe, + 0x83, 0x56, 0x41, 0x30, 0xf1, 0x6a, 0xdf, 0x34, 0x3c, 0x93, 0xdc, 0x07, 0x6f, 0xf5, 0x29, 0xc3, + 0xff, 0x6d, 0x40, 0xbb, 0x68, 0x04, 0x23, 0x33, 0xb0, 0xe5, 0xe8, 0x91, 0x7a, 0x49, 0x95, 0xab, + 0xe1, 0xde, 0xdd, 0x62, 0x2d, 0x71, 0xcf, 0xc0, 0x96, 0x73, 0xb2, 0x11, 0xa8, 0x32, 0xc7, 0x1b, + 0x81, 0x6a, 0xc3, 0xb5, 0x43, 0x8e, 0x15, 0x5c, 0xb7, 0x01, 0x8a, 0x0e, 0x72, 0xbb, 0xd1, 0xa6, + 0x43, 0xbc, 0xb1, 0xc5, 0xcb, 0xf2, 0xf1, 0xbf, 0x01, 0x00, 0x00, 0xff, 0xff, 0xfa, 0x51, 0x05, + 0x6e, 0x94, 0x0a, 0x00, 0x00, } diff --git a/auth/service/proto/auth.proto b/auth/service/proto/auth.proto index d7d09418..77a44af8 100644 --- a/auth/service/proto/auth.proto +++ b/auth/service/proto/auth.proto @@ -46,6 +46,7 @@ message Resource{ string name = 1; string type = 2; string endpoint = 3; + string namespace = 4; } message GenerateRequest { diff --git a/auth/service/service.go b/auth/service/service.go index a0ce48b7..57d6082e 100644 --- a/auth/service/service.go +++ b/auth/service/service.go @@ -132,9 +132,10 @@ func (s *svc) Grant(role string, res *auth.Resource) error { Role: role, Access: pb.Access_GRANTED, Resource: &pb.Resource{ - Type: res.Type, - Name: res.Name, - Endpoint: res.Endpoint, + Namespace: res.Namespace, + Type: res.Type, + Name: res.Name, + Endpoint: res.Endpoint, }, }) return err @@ -146,9 +147,10 @@ func (s *svc) Revoke(role string, res *auth.Resource) error { Role: role, Access: pb.Access_GRANTED, Resource: &pb.Resource{ - Type: res.Type, - Name: res.Name, - Endpoint: res.Endpoint, + Namespace: res.Namespace, + Type: res.Type, + Name: res.Name, + Endpoint: res.Endpoint, }, }) return err @@ -157,10 +159,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 { queries := [][]string{ - {res.Type, res.Name, res.Endpoint}, // check for specific role, e.g. service.foo.ListFoo:admin (role is checked in accessForRule) - {res.Type, res.Name, "*"}, // check for wildcard endpoint, e.g. service.foo* - {res.Type, "*"}, // check for wildcard name, e.g. service.* - {"*"}, // check for wildcard type, e.g. * + {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, "*"}, // check for wildcard name, e.g. service.* + {res.Namespace, "*"}, // check for wildcard type, e.g. * + {"*"}, // check for wildcard namespace } // endpoint is a url which can have wildcard excludes, e.g. @@ -172,23 +175,29 @@ func (s *svc) Verify(acc *auth.Account, res *auth.Resource) error { } } + // set a default account id to log + logID := acc.ID + if len(logID) == 0 { + logID = "[no account]" + } + for _, q := range queries { for _, rule := range s.listRules(q...) { switch accessForRule(rule, acc, res) { case pb.Access_UNKNOWN: continue // rule did not specify access, check the next rule case pb.Access_GRANTED: - log.Infof("%v granted access to %v:%v:%v by rule %v", acc.ID, res.Type, res.Name, res.Endpoint, rule.Id) + log.Infof("%v:%v granted access to %v:%v:%v:%v by rule %v", acc.Namespace, logID, res.Namespace, res.Type, res.Name, res.Endpoint, rule.Id) return nil // rule grants the account access to the resource case pb.Access_DENIED: - log.Infof("%v denied access to %v:%v:%v by rule %v", acc.ID, res.Type, res.Name, res.Endpoint, rule.Id) + log.Infof("%v:%v denied access to %v:%v:%v:%v by rule %v", acc.Namespace, logID, res.Namespace, res.Type, res.Name, res.Endpoint, rule.Id) return auth.ErrForbidden // rule denies access to the resource } } } // no rules were found for the resource, default to denying access - log.Infof("%v denied access to %v:%v:%v by lack of rule (%v rules found)", acc.ID, res.Type, res.Name, res.Endpoint, len(s.rules)) + log.Infof("%v:%v denied access to %v:%v:%v:%v by lack of rule (%v rules found for namespace)", acc.Namespace, logID, res.Namespace, res.Type, res.Name, res.Endpoint, len(s.listRules(res.Namespace))) return auth.ErrForbidden } @@ -249,19 +258,28 @@ func accessForRule(rule *pb.Rule, acc *auth.Account, res *auth.Resource) pb.Acce return pb.Access_UNKNOWN } -// listRules gets all the rules from the store which have an id -// prefix matching the filters +// listRules gets all the rules from the store which match the filters. +// filters are namespace, type, name and then endpoint. func (s *svc) listRules(filters ...string) []*pb.Rule { s.Lock() defer s.Unlock() - prefix := strings.Join(filters, ruleJoinKey) - var rules []*pb.Rule for _, r := range s.rules { - if strings.HasPrefix(r.Id, prefix) { - rules = append(rules, r) + if len(filters) > 0 && r.Resource.Namespace != filters[0] { + continue } + if len(filters) > 1 && r.Resource.Type != filters[1] { + continue + } + if len(filters) > 2 && r.Resource.Name != filters[2] { + continue + } + if len(filters) > 3 && r.Resource.Endpoint != filters[3] { + continue + } + + rules = append(rules, r) } return rules diff --git a/config/cmd/cmd.go b/config/cmd/cmd.go index 7b90b29a..021156b9 100644 --- a/config/cmd/cmd.go +++ b/config/cmd/cmd.go @@ -494,6 +494,7 @@ func (c *cmd) Before(ctx *cli.Context) error { *c.opts.Auth = a() clientOpts = append(clientOpts, client.Auth(*c.opts.Auth)) + serverOpts = append(serverOpts, server.Auth(*c.opts.Auth)) } // Set the profile diff --git a/server/grpc/options.go b/server/grpc/options.go index 5cd52cb0..6591fac2 100644 --- a/server/grpc/options.go +++ b/server/grpc/options.go @@ -5,6 +5,7 @@ import ( "crypto/tls" "net" + "github.com/micro/go-micro/v2/auth" "github.com/micro/go-micro/v2/broker" "github.com/micro/go-micro/v2/codec" "github.com/micro/go-micro/v2/registry" @@ -66,6 +67,7 @@ func MaxMsgSize(s int) server.Option { func newOptions(opt ...server.Option) server.Options { opts := server.Options{ + Auth: auth.DefaultAuth, Codecs: make(map[string]codec.NewCodec), Metadata: map[string]string{}, Broker: broker.DefaultBroker, diff --git a/server/options.go b/server/options.go index 5caba936..63b895bb 100644 --- a/server/options.go +++ b/server/options.go @@ -60,6 +60,10 @@ func newOptions(opt ...Option) Options { o(&opts) } + if opts.Auth == nil { + opts.Auth = auth.DefaultAuth + } + if opts.Broker == nil { opts.Broker = broker.DefaultBroker } diff --git a/service.go b/service.go index a13b4e19..36044338 100644 --- a/service.go +++ b/service.go @@ -34,8 +34,9 @@ func newService(opts ...Option) Service { // service name serviceName := options.Server.Options().Name - // TODO: better accessors - authFn := func() auth.Auth { return service.opts.Auth } + // authFn returns the auth, we pass as a function since auth + // has not yet been set at this point. + authFn := func() auth.Auth { return options.Server.Options().Auth } // wrap client to inject From-Service header on any calls options.Client = wrapper.FromService(serviceName, options.Client, authFn) diff --git a/util/wrapper/wrapper.go b/util/wrapper/wrapper.go index f795e705..839240c2 100644 --- a/util/wrapper/wrapper.go +++ b/util/wrapper/wrapper.go @@ -9,6 +9,7 @@ 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" ) @@ -165,24 +166,47 @@ func AuthHandler(fn func() auth.Auth) server.HandlerWrapper { if header, ok := metadata.Get(ctx, "Authorization"); ok { // Ensure the correct scheme is being used if !strings.HasPrefix(header, auth.BearerScheme) { - return errors.Unauthorized("go.micro.auth", "invalid authorization header. expected Bearer schema") + return errors.Unauthorized(req.Service(), "invalid authorization header. expected Bearer schema") } token = header[len(auth.BearerScheme):] } + // Get the namespace for the request + namespace, ok := metadata.Get(ctx, auth.NamespaceKey) + if !ok { + logger.Errorf("Missing request namespace") + namespace = auth.DefaultNamespace + } + // Inspect the token and get the account account, err := a.Inspect(token) if err != nil { - account = &auth.Account{} + 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.Warnf("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 + res := &auth.Resource{ + Type: "service", + Name: req.Service(), + Endpoint: req.Endpoint(), + Namespace: namespace, } // Verify the caller has access to the resource - err = a.Verify(account, &auth.Resource{Type: "service", Name: req.Service(), Endpoint: req.Endpoint()}) + err = a.Verify(account, res) if err != nil && len(account.ID) > 0 { - return errors.Forbidden("go.micro.auth", "Forbidden call made to %v:%v by %v", req.Service(), req.Endpoint(), account.ID) + return errors.Forbidden(req.Service(), "Forbidden call made to %v:%v by %v", req.Service(), req.Endpoint(), account.ID) } else if err != nil { - return errors.Unauthorized("go.micro.auth", "Unauthorised call made to %v:%v", req.Service(), req.Endpoint()) + return errors.Unauthorized(req.Service(), "Unauthorised call made to %v:%v", req.Service(), req.Endpoint()) } // There is an account, set it in the context