Skip to content

Commit e6ab7e9

Browse files
committed
Cherry-picks for 2.12.6-RC.3 (#59)
Includes the following: - #7958 - #7959 - #7960 - #7961 - #7962 - #7896 - Security fixes Signed-off-by: Neil Twigg <neil@nats.io>
2 parents 7c65836 + 9f4d960 commit e6ab7e9

22 files changed

+1203
-283
lines changed

conf/parse.go

Lines changed: 10 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -114,42 +114,28 @@ func ParseFileWithChecks(fp string) (map[string]any, error) {
114114
return p.mapping, nil
115115
}
116116

117-
// cleanupUsedEnvVars will recursively remove all already used
118-
// environment variables which might be in the parsed tree.
119-
func cleanupUsedEnvVars(m map[string]any) {
120-
for k, v := range m {
121-
t := v.(*token)
122-
if t.usedVariable {
123-
delete(m, k)
124-
continue
125-
}
126-
// Cleanup any other env var that is still in the map.
127-
if tm, ok := t.value.(map[string]any); ok {
128-
cleanupUsedEnvVars(tm)
129-
}
117+
// configDigest returns a digest for the parsed config.
118+
func configDigest(m map[string]any) (string, error) {
119+
digest := sha256.New()
120+
e := json.NewEncoder(digest)
121+
if err := e.Encode(m); err != nil {
122+
return _EMPTY_, err
130123
}
124+
return fmt.Sprintf("sha256:%x", digest.Sum(nil)), nil
131125
}
132126

133127
// ParseFileWithChecksDigest returns the processed config and a digest
134128
// that represents the configuration.
135129
func ParseFileWithChecksDigest(fp string) (map[string]any, string, error) {
136-
data, err := os.ReadFile(fp)
130+
m, err := ParseFileWithChecks(fp)
137131
if err != nil {
138132
return nil, _EMPTY_, err
139133
}
140-
p, err := parse(string(data), fp, true)
141-
if err != nil {
142-
return nil, _EMPTY_, err
143-
}
144-
// Filter out any environment variables before taking the digest.
145-
cleanupUsedEnvVars(p.mapping)
146-
digest := sha256.New()
147-
e := json.NewEncoder(digest)
148-
err = e.Encode(p.mapping)
134+
digest, err := configDigest(m)
149135
if err != nil {
150136
return nil, _EMPTY_, err
151137
}
152-
return p.mapping, fmt.Sprintf("sha256:%x", digest.Sum(nil)), nil
138+
return m, digest, nil
153139
}
154140

155141
type token struct {

conf/parse_test.go

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,24 @@ func TestConvenientNumbers(t *testing.T) {
218218
test(t, easynum, ex)
219219
}
220220

221+
func TestParseFileWithChecksDigestPreservesConfigKeyUsedAsVariable(t *testing.T) {
222+
confFile := filepath.Join(t.TempDir(), "nats.conf")
223+
if err := os.WriteFile(confFile, []byte(`
224+
port = 4222
225+
monitor_port = $port
226+
`), 0666); err != nil {
227+
t.Fatal(err)
228+
}
229+
230+
m, _, err := ParseFileWithChecksDigest(confFile)
231+
if err != nil {
232+
t.Fatal(err)
233+
}
234+
if _, ok := m["port"]; !ok {
235+
t.Fatal("expected config key used as a variable reference to be preserved")
236+
}
237+
}
238+
221239
var sample1 = `
222240
foo {
223241
host {
@@ -919,7 +937,7 @@ func TestParseDigest(t *testing.T) {
919937
very { nested { env { VAR = 'NESTED', quux = $VAR }}}
920938
`,
921939
nil,
922-
"sha256:34f8faf3f269fe7509edc4742f20c8c4a7ad51fe21f8b361764314b533ac3ab5",
940+
"sha256:bddb282343249c26d3edcef9bfaa9d2711505fc67210380e871405ba394a9186",
923941
},
924942
{
925943
`# substitutions, same as previous one without env vars.
@@ -942,7 +960,7 @@ func TestParseDigest(t *testing.T) {
942960
}
943961
`,
944962
nil,
945-
"sha256:f5d943b4ed22b80c6199203f8a7eaa8eb68ef7b2d46ef6b1b26f05e21f8beb13",
963+
"sha256:b0e2ba0b8ec2cb75681b5c5d61789cda0c9942c2f9fe16cdb7f6703364485360",
946964
},
947965
{
948966
`# substitutions

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ require (
88
github.com/antithesishq/antithesis-sdk-go v0.6.0-default-no-op
99
github.com/google/go-tpm v0.9.8
1010
github.com/klauspost/compress v1.18.4
11-
github.com/nats-io/jwt/v2 v2.8.0
11+
github.com/nats-io/jwt/v2 v2.8.1
1212
github.com/nats-io/nats.go v1.49.0
1313
github.com/nats-io/nkeys v0.4.15
1414
github.com/nats-io/nuid v1.0.1

go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ github.com/klauspost/compress v1.18.4 h1:RPhnKRAQ4Fh8zU2FY/6ZFDwTVTxgJ/EMydqSTzE
66
github.com/klauspost/compress v1.18.4/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4=
77
github.com/minio/highwayhash v1.0.4-0.20251030100505-070ab1a87a76 h1:KGuD/pM2JpL9FAYvBrnBBeENKZNh6eNtjqytV6TYjnk=
88
github.com/minio/highwayhash v1.0.4-0.20251030100505-070ab1a87a76/go.mod h1:GGYsuwP/fPD6Y9hMiXuapVvlIUEhFhMTh0rxU3ik1LQ=
9-
github.com/nats-io/jwt/v2 v2.8.0 h1:K7uzyz50+yGZDO5o772eRE7atlcSEENpL7P+b74JV1g=
10-
github.com/nats-io/jwt/v2 v2.8.0/go.mod h1:me11pOkwObtcBNR8AiMrUbtVOUGkqYjMQZ6jnSdVUIA=
9+
github.com/nats-io/jwt/v2 v2.8.1 h1:V0xpGuD/N8Mi+fQNDynXohVvp7ZztevW5io8CUWlPmU=
10+
github.com/nats-io/jwt/v2 v2.8.1/go.mod h1:nWnOEEiVMiKHQpnAy4eXlizVEtSfzacZ1Q43LIRavZg=
1111
github.com/nats-io/nats.go v1.49.0 h1:yh/WvY59gXqYpgl33ZI+XoVPKyut/IcEaqtsiuTJpoE=
1212
github.com/nats-io/nats.go v1.49.0/go.mod h1:fDCn3mN5cY8HooHwE2ukiLb4p4G4ImmzvXyJt+tGwdw=
1313
github.com/nats-io/nkeys v0.4.15 h1:JACV5jRVO9V856KOapQ7x+EY8Jo3qw1vJt/9Jpwzkk4=

server/accounts.go

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1616,10 +1616,12 @@ func (a *Account) checkServiceImportsForCycles(from string, visited map[string]b
16161616
}
16171617
// Push ourselves and check si.acc
16181618
visited[a.Name] = true
1619-
if subjectIsSubsetMatch(si.from, from) {
1620-
from = si.from
1619+
// Make a copy to not overwrite the passed value.
1620+
f := from
1621+
if subjectIsSubsetMatch(si.from, f) {
1622+
f = si.from
16211623
}
1622-
if err := si.acc.checkServiceImportsForCycles(from, visited); err != nil {
1624+
if err := si.acc.checkServiceImportsForCycles(f, visited); err != nil {
16231625
return err
16241626
}
16251627
a.mu.RLock()
@@ -1674,10 +1676,12 @@ func (a *Account) checkStreamImportsForCycles(to string, visited map[string]bool
16741676
}
16751677
// Push ourselves and check si.acc
16761678
visited[a.Name] = true
1677-
if subjectIsSubsetMatch(si.to, to) {
1678-
to = si.to
1679+
// Make a copy to not overwrite the passed value.
1680+
t := to
1681+
if subjectIsSubsetMatch(si.to, t) {
1682+
t = si.to
16791683
}
1680-
if err := si.acc.checkStreamImportsForCycles(to, visited); err != nil {
1684+
if err := si.acc.checkStreamImportsForCycles(t, visited); err != nil {
16811685
return err
16821686
}
16831687
a.mu.RLock()

server/auth_callout.go

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -81,9 +81,6 @@ func (s *Server) processClientOrLeafCallout(c *client, opts *Options, proxyRequi
8181
xkp, xkey = s.xkp, s.info.XKey
8282
}
8383

84-
// FIXME: so things like the server ID that get assigned, are used as a sort of nonce - but
85-
// reality is that the keypair here, is generated, so the response generated a JWT has to be
86-
// this user - no replay possible
8784
// Create a keypair for the user. We will expect this public user to be in the signed response.
8885
// This prevents replay attacks.
8986
ukp, _ := nkeys.CreateUser()

server/client.go

Lines changed: 73 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2817,8 +2817,8 @@ func (c *client) processHeaderPub(arg, remaining []byte) error {
28172817
// Do this only for CLIENT connections.
28182818
if c.kind == CLIENT && c.pa.hdr > 0 && len(remaining) > 0 {
28192819
hdr := remaining[:min(len(remaining), c.pa.hdr)]
2820-
if td := getHeader(MsgTraceDest, hdr); len(td) > 0 {
2821-
c.initAndSendIngressErrEvent(hdr, string(td), ErrMaxPayload)
2820+
if td, ok := c.allowedMsgTraceDest(hdr, false); ok && td != _EMPTY_ {
2821+
c.initAndSendIngressErrEvent(hdr, td, ErrMaxPayload)
28222822
}
28232823
}
28242824
c.maxPayloadViolation(c.pa.size, maxPayload)
@@ -4066,6 +4066,41 @@ func (c *client) pubAllowed(subject string) bool {
40664066
return c.pubAllowedFullCheck(subject, true, false)
40674067
}
40684068

4069+
// allowedMsgTraceDest returns the trace destination if present and authorized.
4070+
// It only considers static publish permissions and does not consume dynamic
4071+
// reply permissions because the client is not publishing the trace event itself.
4072+
func (c *client) allowedMsgTraceDest(hdr []byte, hasLock bool) (string, bool) {
4073+
if len(hdr) == 0 {
4074+
return _EMPTY_, true
4075+
}
4076+
td := sliceHeader(MsgTraceDest, hdr)
4077+
if len(td) == 0 {
4078+
return _EMPTY_, true
4079+
}
4080+
dest := bytesToString(td)
4081+
if c.kind == CLIENT {
4082+
if hasGWRoutedReplyPrefix(td) {
4083+
return dest, false
4084+
}
4085+
var acc *Account
4086+
var srv *Server
4087+
if !hasLock {
4088+
c.mu.Lock()
4089+
}
4090+
acc, srv = c.acc, c.srv
4091+
if !hasLock {
4092+
c.mu.Unlock()
4093+
}
4094+
if bytes.HasPrefix(td, clientNRGPrefix) && srv != nil && acc != srv.SystemAccount() {
4095+
return dest, false
4096+
}
4097+
}
4098+
if c.perms != nil && (c.perms.pub.allow != nil || c.perms.pub.deny != nil) && !c.pubAllowedFullCheck(dest, false, hasLock) {
4099+
return dest, false
4100+
}
4101+
return dest, true
4102+
}
4103+
40694104
// pubAllowedFullCheck checks on all publish permissioning depending
40704105
// on the flag for dynamic reply permissions.
40714106
func (c *client) pubAllowedFullCheck(subject string, fullCheck, hasLock bool) bool {
@@ -4201,10 +4236,19 @@ func (c *client) processInboundClientMsg(msg []byte) (bool, bool) {
42014236
genidAddr := &acc.sl.genid
42024237

42034238
// Check pub permissions
4204-
if c.perms != nil && (c.perms.pub.allow != nil || c.perms.pub.deny != nil) && !c.pubAllowedFullCheck(string(c.pa.subject), true, true) {
4205-
c.mu.Unlock()
4206-
c.pubPermissionViolation(c.pa.subject)
4207-
return false, true
4239+
if c.perms != nil && (c.perms.pub.allow != nil || c.perms.pub.deny != nil) {
4240+
if !c.pubAllowedFullCheck(string(c.pa.subject), true, true) {
4241+
c.mu.Unlock()
4242+
c.pubPermissionViolation(c.pa.subject)
4243+
return false, true
4244+
}
4245+
}
4246+
if c.pa.hdr > 0 {
4247+
if td, ok := c.allowedMsgTraceDest(msg[:c.pa.hdr], true); !ok {
4248+
c.mu.Unlock()
4249+
c.pubPermissionViolation(stringToBytes(td))
4250+
return false, true
4251+
}
42084252
}
42094253
c.mu.Unlock()
42104254

@@ -4775,29 +4819,40 @@ func (c *client) processServiceImport(si *serviceImport, acc *Account, msg []byt
47754819
if !isResponse {
47764820
isSysImport := siAcc == c.srv.SystemAccount()
47774821
var ci *ClientInfo
4778-
if hadPrevSi && c.pa.hdr >= 0 {
4779-
var cis ClientInfo
4780-
if err := json.Unmarshal(sliceHeader(ClientInfoHdr, msg[:c.pa.hdr]), &cis); err == nil {
4781-
ci = &cis
4822+
var cis *ClientInfo
4823+
if c.pa.hdr >= 0 {
4824+
var hci ClientInfo
4825+
if err := json.Unmarshal(sliceHeader(ClientInfoHdr, msg[:c.pa.hdr]), &hci); err == nil {
4826+
cis = &hci
4827+
}
4828+
}
4829+
if c.kind == LEAF && c.pa.hdr >= 0 && len(sliceHeader(ClientInfoHdr, msg[:c.pa.hdr])) > 0 {
4830+
// Leaf nodes may forward a Nats-Request-Info from a remote domain,
4831+
// but the local server must replace it with the identity of the
4832+
// authenticated leaf connection instead of trusting forwarded values.
4833+
ci = c.getClientInfo(share)
4834+
if hadPrevSi {
47824835
ci.Service = acc.Name
4783-
// Check if we are moving into a share details account from a non-shared
4784-
// and add in server and cluster details.
47854836
if !share && (si.share || isSysImport) {
47864837
c.addServerAndClusterInfo(ci)
47874838
}
4839+
} else if !share && isSysImport {
4840+
c.addServerAndClusterInfo(ci)
4841+
}
4842+
} else if hadPrevSi && cis != nil {
4843+
ci = cis
4844+
ci.Service = acc.Name
4845+
// Check if we are moving into a share details account from a non-shared
4846+
// and add in server and cluster details.
4847+
if !share && (si.share || isSysImport) {
4848+
c.addServerAndClusterInfo(ci)
47884849
}
47894850
} else if c.kind != LEAF || c.pa.hdr < 0 || len(sliceHeader(ClientInfoHdr, msg[:c.pa.hdr])) == 0 {
47904851
ci = c.getClientInfo(share)
47914852
// If we did not share but the imports destination is the system account add in the server and cluster info.
47924853
if !share && isSysImport {
47934854
c.addServerAndClusterInfo(ci)
47944855
}
4795-
} else if c.kind == LEAF && (si.share || isSysImport) {
4796-
// We have a leaf header here for ci, augment as above.
4797-
ci = c.getClientInfo(si.share)
4798-
if !si.share && isSysImport {
4799-
c.addServerAndClusterInfo(ci)
4800-
}
48014856
}
48024857
// Set clientInfo if present.
48034858
if ci != nil {

0 commit comments

Comments
 (0)