Merge pull request #737 from nats-io/route_perm2

Route permission propogation
This commit is contained in:
Derek Collison
2018-09-05 17:32:19 -07:00
committed by GitHub
7 changed files with 89 additions and 51 deletions

View File

@@ -253,6 +253,10 @@ type clientOpts struct {
Lang string `json:"lang"`
Version string `json:"version"`
Protocol int `json:"protocol"`
// Routes only
Import *SubjectPermission `json:"import,omitempty"`
Export *SubjectPermission `json:"export,omitempty"`
}
var defaultOpts = clientOpts{Verbose: true, Pedantic: true, Echo: true}

View File

@@ -55,8 +55,8 @@ const (
DEFAULT_HOST = "0.0.0.0"
// MAX_CONTROL_LINE_SIZE is the maximum allowed protocol control line size.
// 1k should be plenty since payloads sans connect string are separate
MAX_CONTROL_LINE_SIZE = 1024
// 4k should be plenty since payloads sans connect/info string are separate.
MAX_CONTROL_LINE_SIZE = 4096
// MAX_PAYLOAD_SIZE is the maximum allowed payload size. Should be using
// something different if > 1MB payloads are needed.

View File

@@ -532,8 +532,8 @@ func (s *Server) HandleConnz(w http.ResponseWriter, r *http.Request) {
type Routez struct {
ID string `json:"server_id"`
Now time.Time `json:"now"`
Imports *SubjectPermission `json:"imports,omitempty"`
Exports *SubjectPermission `json:"exports,omitempty"`
Import *SubjectPermission `json:"import,omitempty"`
Export *SubjectPermission `json:"export,omitempty"`
NumRoutes int `json:"num_routes"`
Routes []*RouteInfo `json:"routes"`
}
@@ -546,19 +546,21 @@ type RoutezOptions struct {
// RouteInfo has detailed information on a per connection basis.
type RouteInfo struct {
Rid uint64 `json:"rid"`
RemoteID string `json:"remote_id"`
DidSolicit bool `json:"did_solicit"`
IsConfigured bool `json:"is_configured"`
IP string `json:"ip"`
Port int `json:"port"`
Pending int `json:"pending_size"`
InMsgs int64 `json:"in_msgs"`
OutMsgs int64 `json:"out_msgs"`
InBytes int64 `json:"in_bytes"`
OutBytes int64 `json:"out_bytes"`
NumSubs uint32 `json:"subscriptions"`
Subs []string `json:"subscriptions_list,omitempty"`
Rid uint64 `json:"rid"`
RemoteID string `json:"remote_id"`
DidSolicit bool `json:"did_solicit"`
IsConfigured bool `json:"is_configured"`
IP string `json:"ip"`
Port int `json:"port"`
Import *SubjectPermission `json:"import,omitempty"`
Export *SubjectPermission `json:"export,omitempty"`
Pending int `json:"pending_size"`
InMsgs int64 `json:"in_msgs"`
OutMsgs int64 `json:"out_msgs"`
InBytes int64 `json:"in_bytes"`
OutBytes int64 `json:"out_bytes"`
NumSubs uint32 `json:"subscriptions"`
Subs []string `json:"subscriptions_list,omitempty"`
}
// Routez returns a Routez struct containing inormation about routes.
@@ -575,10 +577,9 @@ func (s *Server) Routez(routezOpts *RoutezOptions) (*Routez, error) {
rs.ID = s.info.ID
// Check for defined permissions for all connected routes.
perms := s.getOpts().Cluster.Permissions
if perms != nil {
rs.Imports = perms.Import
rs.Exports = perms.Export
if perms := s.getOpts().Cluster.Permissions; perms != nil {
rs.Import = perms.Import
rs.Export = perms.Export
}
// Walk the list
@@ -594,6 +595,8 @@ func (s *Server) Routez(routezOpts *RoutezOptions) (*Routez, error) {
InBytes: atomic.LoadInt64(&r.inBytes),
OutBytes: r.outBytes,
NumSubs: uint32(len(r.subs)),
Import: r.opts.Import,
Export: r.opts.Export,
}
if subs && len(r.subs) > 0 {

View File

@@ -1946,23 +1946,38 @@ func TestRoutezPermissions(t *testing.T) {
rz := pollRoutez(t, servers[i], mode, url, nil)
// For server 1, we expect to see imports and exports
if i == 0 {
if rz.Imports == nil || rz.Imports.Allow == nil ||
len(rz.Imports.Allow) != 1 || rz.Imports.Allow[0] != "foo" ||
rz.Imports.Deny != nil {
t.Fatalf("Unexpected Imports %v", rz.Imports)
if rz.Import == nil || rz.Import.Allow == nil ||
len(rz.Import.Allow) != 1 || rz.Import.Allow[0] != "foo" ||
rz.Import.Deny != nil {
t.Fatalf("Unexpected Import %v", rz.Import)
}
if rz.Exports == nil || rz.Exports.Allow == nil || rz.Exports.Deny == nil ||
len(rz.Exports.Allow) != 1 || rz.Exports.Allow[0] != "*" ||
len(rz.Exports.Deny) != 2 || rz.Exports.Deny[0] != "foo" || rz.Exports.Deny[1] != "nats" {
t.Fatalf("Unexpected Exports %v", rz.Exports)
if rz.Export == nil || rz.Export.Allow == nil || rz.Export.Deny == nil ||
len(rz.Export.Allow) != 1 || rz.Export.Allow[0] != "*" ||
len(rz.Export.Deny) != 2 || rz.Export.Deny[0] != "foo" || rz.Export.Deny[1] != "nats" {
t.Fatalf("Unexpected Export %v", rz.Export)
}
} else {
// We expect to see NO imports and exports for server B.
if rz.Imports != nil {
t.Fatal("Routez body should NOT contain \"imports\" information.")
// We expect to see NO imports and exports for server B by default.
if rz.Import != nil {
t.Fatal("Routez body should NOT contain \"import\" information.")
}
if rz.Exports != nil {
t.Fatal("Routez body should NOT contain \"exports\" information.")
if rz.Export != nil {
t.Fatal("Routez body should NOT contain \"export\" information.")
}
// We do expect to see them show up for the information we have on Server A though.
if len(rz.Routes) != 1 {
t.Fatalf("Expected route array of 1, got %v\n", len(rz.Routes))
}
route := rz.Routes[0]
if route.Import == nil || route.Import.Allow == nil ||
len(route.Import.Allow) != 1 || route.Import.Allow[0] != "foo" ||
route.Import.Deny != nil {
t.Fatalf("Unexpected Import %v", route.Import)
}
if route.Export == nil || route.Export.Allow == nil || route.Export.Deny == nil ||
len(route.Export.Allow) != 1 || route.Export.Allow[0] != "*" ||
len(route.Export.Deny) != 2 || route.Export.Deny[0] != "foo" || route.Export.Deny[1] != "nats" {
t.Fatalf("Unexpected Export %v", route.Export)
}
}
}

View File

@@ -134,7 +134,7 @@ func TestConfigReloadUnsupported(t *testing.T) {
Debug: false,
Trace: false,
Logtime: false,
MaxControlLine: 1024,
MaxControlLine: 4096,
MaxPayload: 1048576,
MaxConn: 65536,
PingInterval: 2 * time.Minute,
@@ -213,7 +213,7 @@ func TestConfigReloadInvalidConfig(t *testing.T) {
Debug: false,
Trace: false,
Logtime: false,
MaxControlLine: 1024,
MaxControlLine: 4096,
MaxPayload: 1048576,
MaxConn: 65536,
PingInterval: 2 * time.Minute,
@@ -284,7 +284,7 @@ func TestConfigReload(t *testing.T) {
Trace: false,
NoLog: true,
Logtime: false,
MaxControlLine: 1024,
MaxControlLine: 4096,
MaxPayload: 1048576,
MaxConn: 65536,
PingInterval: 2 * time.Minute,

View File

@@ -21,6 +21,7 @@ import (
"math/rand"
"net"
"net/url"
"runtime"
"strconv"
"strings"
"sync/atomic"
@@ -268,6 +269,7 @@ func (c *client) sendConnect(tlsRequired bool) {
TLS: tlsRequired,
Name: c.srv.info.ID,
}
b, err := json.Marshal(cinfo)
if err != nil {
c.Errorf("Error marshaling CONNECT to route: %v\n", err)
@@ -316,6 +318,10 @@ func (c *client) processRouteInfo(info *Info) {
c.route.authRequired = info.AuthRequired
c.route.tlsRequired = info.TLSRequired
// Copy over permissions as well.
c.opts.Import = info.Import
c.opts.Export = info.Export
// If we do not know this route's URL, construct one on the fly
// from the information provided.
if c.route.url == nil {
@@ -911,6 +917,7 @@ func (s *Server) routeAcceptLoop(ch chan struct{}) {
info := Info{
ID: s.info.ID,
Version: s.info.Version,
GoVersion: runtime.Version(),
AuthRequired: false,
TLSRequired: tlsReq,
TLSVerify: tlsReq,
@@ -932,6 +939,11 @@ func (s *Server) routeAcceptLoop(ch chan struct{}) {
if opts.Cluster.Username != "" {
info.AuthRequired = true
}
// Check for permissions.
if opts.Cluster.Permissions != nil {
info.Import = opts.Cluster.Permissions.Import
info.Export = opts.Cluster.Permissions.Export
}
s.routeInfo = info
// Possibly override Host/Port and set IP based on Cluster.Advertise
if err := s.setRouteInfoHostPortAndIP(); err != nil {

View File

@@ -41,20 +41,24 @@ import (
// Info is the information sent to clients to help them understand information
// about this server.
type Info struct {
ID string `json:"server_id"`
Version string `json:"version"`
Proto int `json:"proto"`
GitCommit string `json:"git_commit,omitempty"`
GoVersion string `json:"go"`
Host string `json:"host"`
Port int `json:"port"`
AuthRequired bool `json:"auth_required,omitempty"`
TLSRequired bool `json:"tls_required,omitempty"`
TLSVerify bool `json:"tls_verify,omitempty"`
MaxPayload int `json:"max_payload"`
IP string `json:"ip,omitempty"`
CID uint64 `json:"client_id,omitempty"`
ClientConnectURLs []string `json:"connect_urls,omitempty"` // Contains URLs a client can connect to.
ID string `json:"server_id"`
Version string `json:"version"`
Proto int `json:"proto"`
GitCommit string `json:"git_commit,omitempty"`
GoVersion string `json:"go"`
Host string `json:"host"`
Port int `json:"port"`
AuthRequired bool `json:"auth_required,omitempty"`
TLSRequired bool `json:"tls_required,omitempty"`
TLSVerify bool `json:"tls_verify,omitempty"`
MaxPayload int `json:"max_payload"`
IP string `json:"ip,omitempty"`
CID uint64 `json:"client_id,omitempty"`
// Route Specific
ClientConnectURLs []string `json:"connect_urls,omitempty"` // Contains URLs a client can connect to.
Import *SubjectPermission `json:"import,omitempty"`
Export *SubjectPermission `json:"export,omitempty"`
}
// Server is our main struct.