Repository: nats-io/nats-server

Issue:
Reject API requests with invalid JSON fields
### Proposed change

Today when receiving JSON fields in API requests that have invalid flags we accept them but log about it.  These requests should be rejected.

See ADR-44

### Use case

Toward safety in using new clients on old servers, this will ensure that attempts to use new features will fail.


Hints from issue comments:
@ripienaar: Related https://github.com/nats-io/nats-server/issues/704@neilalexander: So in effect this is enabling `strict` mode by default?@ripienaar: Yes with the option to turn off in the config 

Solution patch:
diff --git a/server/client.go b/server/client.go
index f9025b39..0646557b 100644
--- a/server/client.go
+++ b/server/client.go
@@ -1868,14 +1868,28 @@ func (c *client) markConnAsClosed(reason ClosedState) {
 	// Be consistent with the creation: for routes, gateways and leaf,
 	// we use Noticef on create, so use that too for delete.
 	if c.srv != nil {
-		if c.kind == LEAF {
+		if c.kind == LEAF || c.kind == ROUTER || c.kind == GATEWAY {
+			var tags []string
+			var remoteName string
+			switch {
+			case c.kind == LEAF && c.leaf != nil:
+				remoteName = c.leaf.remoteServer
+			case c.kind == ROUTER && c.route != nil:
+				remoteName = c.route.remoteName
+			case c.kind == GATEWAY && c.gw != nil:
+				remoteName = c.gw.remoteName
+			}
+			if remoteName != _EMPTY_ {
+				tags = append(tags, fmt.Sprintf("Remote: %s", remoteName))
+			}
 			if c.acc != nil {
-				c.Noticef("%s connection closed: %s - Account: %s", c.kindString(), reason, c.acc.traceLabel())
+				tags = append(tags, fmt.Sprintf("Account: %s", c.acc.traceLabel()))
+			}
+			if len(tags) > 0 {
+				c.Noticef("%s connection closed: %s - %s", c.kindString(), reason, strings.Join(tags, ", "))
 			} else {
 				c.Noticef("%s connection closed: %s", c.kindString(), reason)
 			}
-		} else if c.kind == ROUTER || c.kind == GATEWAY {
-			c.Noticef("%s connection closed: %s", c.kindString(), reason)
 		} else { // Client, System, Jetstream, and Account connections.
 			c.Debugf("%s connection closed: %s", c.kindString(), reason)
 		}
diff --git a/server/jetstream.go b/server/jetstream.go
index d5e3c9f1..f6b1a517 100644
--- a/server/jetstream.go
+++ b/server/jetstream.go
@@ -558,7 +558,7 @@ func (s *Server) restartJetStream() error {
 		MaxMemory:    opts.JetStreamMaxMemory,
 		MaxStore:     opts.JetStreamMaxStore,
 		Domain:       opts.JetStreamDomain,
-		Strict:       opts.JetStreamStrict,
+		Strict:       !opts.NoJetStreamStrict,
 	}
 	s.Noticef("Restarting JetStream")
 	err := s.EnableJetStream(&cfg)
@@ -2539,7 +2539,7 @@ func (s *Server) dynJetStreamConfig(storeDir string, maxStore, maxMem int64) *Je
 	opts := s.getOpts()
 
 	// Strict mode.
-	jsc.Strict = opts.JetStreamStrict
+	jsc.Strict = !opts.NoJetStreamStrict
 
 	// Sync options.
 	jsc.SyncInterval = opts.SyncInterval
@@ -2921,12 +2921,12 @@ func canonicalName(name string) string {
 }
 
 // To throttle the out of resources errors.
-func (s *Server) resourcesExceededError() {
+func (s *Server) resourcesExceededError(storeType StorageType) {
 	var didAlert bool
 
 	s.rerrMu.Lock()
 	if now := time.Now(); now.Sub(s.rerrLast) > 10*time.Second {
-		s.Errorf("JetStream resource limits exceeded for server")
+		s.Errorf("JetStream %s resource limits exceeded for server", strings.ToLower(storeType.String()))
 		s.rerrLast = now
 		didAlert = true
 	}
diff --git a/server/jetstream_api.go b/server/jetstream_api.go
index 555678a8..c15e73e6 100644
--- a/server/jetstream_api.go
+++ b/server/jetstream_api.go
@@ -3898,7 +3898,7 @@ func (s *Server) processStreamRestore(ci *ClientInfo, acc *Account, cfg *StreamC
 		// TODO(dlc) - We could check apriori and cancel initial request if we know it won't fit.
 		total += len(msg)
 		if js.wouldExceedLimits(FileStorage, total) {
-			s.resourcesExceededError()
+			s.resourcesExceededError(FileStorage)
 			resultCh <- result{NewJSInsufficientResourcesError(), reply}
 			return
 		}
diff --git a/server/jetstream_cluster.go b/server/jetstream_cluster.go
index 5c4107e2..53c24d18 100644
--- a/server/jetstream_cluster.go
+++ b/server/jetstream_cluster.go
@@ -8030,7 +8030,7 @@ func (mset *stream) processClusteredInboundMsg(subject, reply string, hdr, msg [
 
 	// Check here pre-emptively if we have exceeded this server limits.
 	if js.limitsExceeded(stype) {
-		s.resourcesExceededError()
+		s.resourcesExceededError(stype)
 		if canRespond {
 			b, _ := json.Marshal(&JSPubAckResponse{PubAck: &PubAck{Stream: name}, Error: NewJSInsufficientResourcesError()})
 			outq.send(newJSPubMsg(reply, _EMPTY_, _EMPTY_, nil, b, nil, 0))
@@ -8755,7 +8755,7 @@ RETRY:
 				} else if err == NewJSInsufficientResourcesError() {
 					notifyLeaderStopCatchup(mrec, err)
 					if mset.js.limitsExceeded(mset.cfg.Storage) {
-						s.resourcesExceededError()
+						s.resourcesExceededError(mset.cfg.Storage)
 					} else {
 						s.Warnf("Catchup for stream '%s > %s' errored, account resources exceeded: %v", mset.account(), mset.name(), err)
 					}
diff --git a/server/opts.go b/server/opts.go
index 01f41e85..64745e98 100644
--- a/server/opts.go
+++ b/server/opts.go
@@ -336,7 +336,7 @@ type Options struct {
 	Gateway                    GatewayOpts   `json:"gateway,omitempty"`
 	LeafNode                   LeafNodeOpts  `json:"leaf,omitempty"`
 	JetStream                  bool          `json:"jetstream"`
-	JetStreamStrict            bool          `json:"-"`
+	NoJetStreamStrict          bool          `json:"-"` // Strict by default.
 	JetStreamMaxMemory         int64         `json:"-"`
 	JetStreamMaxStore          int64         `json:"-"`
 	JetStreamDomain            string        `json:"-"`
@@ -2416,7 +2416,7 @@ func parseJetStream(v any, opts *Options, errors *[]error, warnings *[]error) er
 			switch strings.ToLower(mk) {
 			case "strict":
 				if v, ok := mv.(bool); ok {
-					opts.JetStreamStrict = v
+					opts.NoJetStreamStrict = !v
 				} else {
 					return &configErr{tk, fmt.Sprintf("Expected 'true' or 'false' for bool value, got '%s'", mv)}
 				}
diff --git a/server/server.go b/server/server.go
index d5aaa3c5..6c699a3b 100644
--- a/server/server.go
+++ b/server/server.go
@@ -2378,7 +2378,7 @@ func (s *Server) Start() {
 			StoreDir:     opts.StoreDir,
 			SyncInterval: opts.SyncInterval,
 			SyncAlways:   opts.SyncAlways,
-			Strict:       opts.JetStreamStrict,
+			Strict:       !opts.NoJetStreamStrict,
 			MaxMemory:    opts.JetStreamMaxMemory,
 			MaxStore:     opts.JetStreamMaxStore,
 			Domain:       opts.JetStreamDomain,
diff --git a/server/stream.go b/server/stream.go
index a0d6c4ec..10905c9c 100644
--- a/server/stream.go
+++ b/server/stream.go
@@ -2694,7 +2694,7 @@ func (mset *stream) processInboundMirrorMsg(m *inMsg) bool {
 	var err error
 	if node != nil {
 		if js.limitsExceeded(stype) {
-			s.resourcesExceededError()
+			s.resourcesExceededError(stype)
 			err = ApiErrors[JSInsufficientResourcesErr]
 		} else {
 			err = node.Propose(encodeStreamMsg(m.subj, _EMPTY_, m.hdr, m.msg, sseq-1, ts, true))
@@ -5447,7 +5447,7 @@ func (mset *stream) processJetStreamMsg(subject, reply string, hdr, msg []byte,
 
 	// Check to see if we have exceeded our limits.
 	if js.limitsExceeded(stype) {
-		s.resourcesExceededError()
+		s.resourcesExceededError(stype)
 		mset.mu.Unlock()
 		bumpCLFS()
 		if canRespond {
@@ -5537,7 +5537,7 @@ func (mset *stream) processJetStreamMsg(subject, reply string, hdr, msg []byte,
 			if err == nil {
 				err = NewJSAccountResourcesExceededError()
 			}
-			s.RateLimitWarnf("JetStream resource limits exceeded for account: %q", accName)
+			s.RateLimitWarnf("JetStream %s resource limits exceeded for account: %q", strings.ToLower(stype.String()), accName)
 			if canRespond {
 				resp.PubAck = &PubAck{Stream: name}
 				resp.Error = err



Test patch:
diff --git a/server/test_test.go b/server/test_test.go
index 9859398a..79303ea1 100644
--- a/server/test_test.go
+++ b/server/test_test.go
@@ -35,7 +35,7 @@ var DefaultTestOptions = Options{
 	NoSigs:                true,
 	MaxControlLine:        4096,
 	DisableShortFirstPing: true,
-	JetStreamStrict:       true,
+	NoJetStreamStrict:     false,
 }
 
 func testDefaultClusterOptionsForLeafNodes() *Options {