From db9fd45be242d37cb4b3385a8019bbb61c3cc7fe Mon Sep 17 00:00:00 2001 From: Matthias Hanel Date: Tue, 10 Aug 2021 18:15:07 -0400 Subject: [PATCH] [fixed] issue where js overwrote leafnode remotes permissions from creds Fixes #2415. We did a set instead of merge. changes in `jwt_test.go` are to make the `createUserWithLimit` usable by my new test. Signed-off-by: Matthias Hanel --- server/jwt_test.go | 44 ++++++++------- server/leafnode.go | 3 +- server/leafnode_test.go | 121 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 145 insertions(+), 23 deletions(-) diff --git a/server/jwt_test.go b/server/jwt_test.go index 3f18deaf..b524c565 100644 --- a/server/jwt_test.go +++ b/server/jwt_test.go @@ -3864,7 +3864,7 @@ func newTimeRange(start time.Time, dur time.Duration) jwt.TimeRange { return jwt.TimeRange{Start: start.Format("15:04:05"), End: start.Add(dur).Format("15:04:05")} } -func createUserWithLimit(t *testing.T, accKp nkeys.KeyPair, expiration time.Time, limits func(*jwt.Limits)) string { +func createUserWithLimit(t *testing.T, accKp nkeys.KeyPair, expiration time.Time, limits func(*jwt.UserPermissionLimits)) string { t.Helper() ukp, _ := nkeys.CreateUser() seed, _ := ukp.Seed() @@ -3872,7 +3872,7 @@ func createUserWithLimit(t *testing.T, accKp nkeys.KeyPair, expiration time.Time uclaim := newJWTTestUserClaims() uclaim.Subject = upub if limits != nil { - limits(&uclaim.Limits) + limits(&uclaim.UserPermissionLimits) } if !expiration.IsZero() { uclaim.Expires = expiration.Unix() @@ -3909,31 +3909,33 @@ func TestJWTUserLimits(t *testing.T) { defer sA.Shutdown() for _, v := range []struct { pass bool - f func(*jwt.Limits) + f func(*jwt.UserPermissionLimits) }{ {true, nil}, - {false, func(j *jwt.Limits) { j.Src.Set("8.8.8.8/8") }}, - {true, func(j *jwt.Limits) { j.Src.Set("8.8.8.8/0") }}, - {true, func(j *jwt.Limits) { j.Src.Set("127.0.0.1/8") }}, - {true, func(j *jwt.Limits) { j.Src.Set("8.8.8.8/8,127.0.0.1/8") }}, - {false, func(j *jwt.Limits) { j.Src.Set("8.8.8.8/8,9.9.9.9/8") }}, - {true, func(j *jwt.Limits) { j.Times = append(j.Times, newTimeRange(time.Now(), time.Hour)) }}, - {false, func(j *jwt.Limits) { j.Times = append(j.Times, newTimeRange(time.Now().Add(time.Hour), time.Hour)) }}, - {true, func(j *jwt.Limits) { + {false, func(j *jwt.UserPermissionLimits) { j.Src.Set("8.8.8.8/8") }}, + {true, func(j *jwt.UserPermissionLimits) { j.Src.Set("8.8.8.8/0") }}, + {true, func(j *jwt.UserPermissionLimits) { j.Src.Set("127.0.0.1/8") }}, + {true, func(j *jwt.UserPermissionLimits) { j.Src.Set("8.8.8.8/8,127.0.0.1/8") }}, + {false, func(j *jwt.UserPermissionLimits) { j.Src.Set("8.8.8.8/8,9.9.9.9/8") }}, + {true, func(j *jwt.UserPermissionLimits) { j.Times = append(j.Times, newTimeRange(time.Now(), time.Hour)) }}, + {false, func(j *jwt.UserPermissionLimits) { + j.Times = append(j.Times, newTimeRange(time.Now().Add(time.Hour), time.Hour)) + }}, + {true, func(j *jwt.UserPermissionLimits) { j.Times = append(j.Times, newTimeRange(inAnHour, time.Hour), newTimeRange(time.Now(), time.Hour)) }}, // last one is within range - {false, func(j *jwt.Limits) { + {false, func(j *jwt.UserPermissionLimits) { j.Times = append(j.Times, newTimeRange(inAnHour, time.Hour), newTimeRange(inTwoHours, time.Hour)) }}, // out of range - {false, func(j *jwt.Limits) { + {false, func(j *jwt.UserPermissionLimits) { j.Times = append(j.Times, newTimeRange(inAnHour, 3*time.Hour), newTimeRange(inTwoHours, 2*time.Hour)) }}, // overlapping [a[]b] out of range*/ - {false, func(j *jwt.Limits) { + {false, func(j *jwt.UserPermissionLimits) { j.Times = append(j.Times, newTimeRange(inAnHour, 3*time.Hour), newTimeRange(inTwoHours, time.Hour)) }}, // overlapping [a[b]] out of range // next day tests where end < begin - {true, func(j *jwt.Limits) { j.Times = append(j.Times, newTimeRange(time.Now(), 25*time.Hour)) }}, - {true, func(j *jwt.Limits) { j.Times = append(j.Times, newTimeRange(time.Now(), -time.Hour)) }}, + {true, func(j *jwt.UserPermissionLimits) { j.Times = append(j.Times, newTimeRange(time.Now(), 25*time.Hour)) }}, + {true, func(j *jwt.UserPermissionLimits) { j.Times = append(j.Times, newTimeRange(time.Now(), -time.Hour)) }}, } { t.Run("", func(t *testing.T) { creds := createUserWithLimit(t, kp, doNotExpire, v.f) @@ -3976,7 +3978,7 @@ func TestJWTTimeExpiration(t *testing.T) { for _, l := range []string{"", "Europe/Berlin", "America/New_York"} { t.Run("simple expiration "+l, func(t *testing.T) { start := time.Now() - creds := createUserWithLimit(t, kp, doNotExpire, func(j *jwt.Limits) { + creds := createUserWithLimit(t, kp, doNotExpire, func(j *jwt.UserPermissionLimits) { if l == "" { j.Times = []jwt.TimeRange{newTimeRange(start, validFor)} } else { @@ -4020,7 +4022,7 @@ func TestJWTTimeExpiration(t *testing.T) { t.Run("double expiration", func(t *testing.T) { start1 := time.Now() start2 := start1.Add(2 * validFor) - creds := createUserWithLimit(t, kp, doNotExpire, func(j *jwt.Limits) { + creds := createUserWithLimit(t, kp, doNotExpire, func(j *jwt.UserPermissionLimits) { j.Times = []jwt.TimeRange{newTimeRange(start1, validFor), newTimeRange(start2, validFor)} }) defer removeFile(t, creds) @@ -4059,7 +4061,7 @@ func TestJWTTimeExpiration(t *testing.T) { }) t.Run("lower jwt expiration overwrites time", func(t *testing.T) { start := time.Now() - creds := createUserWithLimit(t, kp, start.Add(validFor), func(j *jwt.Limits) { j.Times = []jwt.TimeRange{newTimeRange(start, 2*validFor)} }) + creds := createUserWithLimit(t, kp, start.Add(validFor), func(j *jwt.UserPermissionLimits) { j.Times = []jwt.TimeRange{newTimeRange(start, 2*validFor)} }) defer removeFile(t, creds) disconnectChan := make(chan struct{}) defer close(disconnectChan) @@ -4114,7 +4116,7 @@ func TestJWTLimits(t *testing.T) { errChan := make(chan struct{}) defer close(errChan) t.Run("subs", func(t *testing.T) { - creds := createUserWithLimit(t, kp, doNotExpire, func(j *jwt.Limits) { j.Subs = 1 }) + creds := createUserWithLimit(t, kp, doNotExpire, func(j *jwt.UserPermissionLimits) { j.Subs = 1 }) defer removeFile(t, creds) c := natsConnect(t, sA.ClientURL(), nats.UserCredentials(creds), nats.DisconnectErrHandler(func(conn *nats.Conn, err error) { @@ -4133,7 +4135,7 @@ func TestJWTLimits(t *testing.T) { chanRecv(t, errChan, time.Second) }) t.Run("payload", func(t *testing.T) { - creds := createUserWithLimit(t, kp, doNotExpire, func(j *jwt.Limits) { j.Payload = 5 }) + creds := createUserWithLimit(t, kp, doNotExpire, func(j *jwt.UserPermissionLimits) { j.Payload = 5 }) defer removeFile(t, creds) c := natsConnect(t, sA.ClientURL(), nats.UserCredentials(creds)) defer c.Close() diff --git a/server/leafnode.go b/server/leafnode.go index 87b1733e..515b4f46 100644 --- a/server/leafnode.go +++ b/server/leafnode.go @@ -1362,8 +1362,7 @@ func (c *client) processLeafNodeConnect(s *Server, arg []byte, lang string) erro // If we have JS enabled and the other side does as well we need to add in an import deny clause. if jsConfigured && proto.JetStream { - // We should never have existing perms here, if that changes this needs to be reworked. - c.setPermissions(&Permissions{Publish: &SubjectPermission{Deny: []string{jsAllAPI}}}) + c.mergePubDenyPermissions([]string{jsAllAPI}) } // Set the Ping timer diff --git a/server/leafnode_test.go b/server/leafnode_test.go index 0096ea68..17d8e7a1 100644 --- a/server/leafnode_test.go +++ b/server/leafnode_test.go @@ -3786,3 +3786,124 @@ func TestLeafNodeUniqueServerNameCrossJSDomain(t *testing.T) { test(sA, sA.ID(), sA, sL) }) } + +func TestLeafNodeJwtPermsAndJetStreamDomains(t *testing.T) { + createAcc := func(js bool) (string, string, nkeys.KeyPair) { + kp, _ := nkeys.CreateAccount() + aPub, _ := kp.PublicKey() + claim := jwt.NewAccountClaims(aPub) + if js { + claim.Limits.JetStreamLimits = jwt.JetStreamLimits{ + MemoryStorage: 1024 * 1024, + DiskStorage: 1024 * 1024, + Streams: 1, Consumer: 2} + } + aJwt, err := claim.Encode(oKp) + require_NoError(t, err) + return aPub, aJwt, kp + } + sysPub, sysJwt, sysKp := createAcc(false) + accPub, accJwt, accKp := createAcc(true) + noExpiration := time.Now().Add(time.Hour) + // create user for acc to be used in leaf node. + lnCreds := createUserWithLimit(t, accKp, noExpiration, func(j *jwt.UserPermissionLimits) { + j.Sub.Deny.Add("subdeny") + j.Pub.Deny.Add("pubdeny") + }) + defer removeFile(t, lnCreds) + unlimitedCreds := createUserWithLimit(t, accKp, noExpiration, nil) + defer removeFile(t, unlimitedCreds) + + sysCreds := createUserWithLimit(t, sysKp, noExpiration, nil) + defer removeFile(t, sysCreds) + + tmplA := ` +operator: %s +system_account: %s +resolver: MEMORY +resolver_preload: { + %s: %s + %s: %s +} +listen: localhost:-1 +leafnodes: { + listen: localhost:-1 +} +jetstream :{ + domain: "cluster" + store_dir: "%s" + max_mem: 100Mb + max_file: 100Mb +} +` + + tmplL := ` +listen: localhost:-1 +accounts :{ + A:{ jetstream: enable, users:[ {user:a1,password:a1}]}, + SYS:{ users:[ {user:s1,password:s1}]}, +} +system_account = SYS +jetstream: { + domain: ln1 + store_dir: %s + max_mem: 50Mb + max_file: 50Mb +} +leafnodes:{ + remotes:[{ url:nats://localhost:%d, account: A, credentials: %s}, + { url:nats://localhost:%d, account: SYS, credentials: %s}] +} +` + + confA := createConfFile(t, []byte(fmt.Sprintf(tmplA, ojwt, sysPub, + sysPub, sysJwt, accPub, accJwt, + createDir(t, JetStreamStoreDir)))) + defer removeFile(t, confA) + sA, _ := RunServerWithConfig(confA) + defer sA.Shutdown() + + confL := createConfFile(t, []byte(fmt.Sprintf(tmplL, createDir(t, JetStreamStoreDir), + sA.opts.LeafNode.Port, lnCreds, sA.opts.LeafNode.Port, sysCreds))) + defer removeFile(t, confL) + sL, _ := RunServerWithConfig(confL) + defer sL.Shutdown() + + checkLeafNodeConnectedCount(t, sA, 2) + checkLeafNodeConnectedCount(t, sL, 2) + + ncA := natsConnect(t, sA.ClientURL(), nats.UserCredentials(unlimitedCreds)) + defer ncA.Close() + + ncL := natsConnect(t, fmt.Sprintf("nats://a1:a1@localhost:%d", sL.opts.Port)) + defer ncL.Close() + + test := func(subject string, cSub, cPub *nats.Conn, pass bool) { + sub, err := cSub.SubscribeSync(subject) + require_NoError(t, err) + require_NoError(t, cSub.Flush()) + require_NoError(t, cPub.Publish(subject, []byte("hello world"))) + require_NoError(t, cPub.Flush()) + m, err := sub.NextMsg(500 * time.Millisecond) + if pass { + require_NoError(t, err) + require_True(t, m.Subject == subject) + require_Equal(t, string(m.Data), "hello world") + } else { + require_True(t, err == nats.ErrTimeout) + } + } + + t.Run("sub-on-ln-pass", func(t *testing.T) { + test("sub", ncL, ncA, true) + }) + t.Run("sub-on-ln-fail", func(t *testing.T) { + test("subdeny", ncL, ncA, false) + }) + t.Run("pub-on-ln-pass", func(t *testing.T) { + test("pub", ncA, ncL, true) + }) + t.Run("pub-on-ln-fail", func(t *testing.T) { + test("pubdeny", ncA, ncL, false) + }) +}