mirror of
https://github.com/gogrlx/nats-server.git
synced 2026-04-02 11:48:43 -07:00
A new config section allows to specify specific TLS parameters for
the account resolver:
```
resolver_tls {
cert_file: ...
key_file: ...
ca_file: ...
}
```
Resolves #1271
Signed-off-by: Ivan Kozlovic <ivan@synadia.com>
2452 lines
67 KiB
Go
2452 lines
67 KiB
Go
// Copyright 2018 The NATS Authors
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package server
|
|
|
|
import (
|
|
"bufio"
|
|
"encoding/base64"
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"os"
|
|
"strings"
|
|
"sync"
|
|
"sync/atomic"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/nats-io/jwt"
|
|
"github.com/nats-io/nkeys"
|
|
)
|
|
|
|
var (
|
|
// This matches ./configs/nkeys_jwts/test.seed
|
|
oSeed = []byte("SOAFYNORQLQFJYBYNUGC5D7SH2MXMUX5BFEWWGHN3EK4VGG5TPT5DZP7QU")
|
|
)
|
|
|
|
func opTrustBasicSetup() *Server {
|
|
kp, _ := nkeys.FromSeed(oSeed)
|
|
pub, _ := kp.PublicKey()
|
|
opts := defaultServerOptions
|
|
opts.TrustedKeys = []string{pub}
|
|
s, c, _, _ := rawSetup(opts)
|
|
c.close()
|
|
return s
|
|
}
|
|
|
|
func buildMemAccResolver(s *Server) {
|
|
mr := &MemAccResolver{}
|
|
s.SetAccountResolver(mr)
|
|
}
|
|
|
|
func addAccountToMemResolver(s *Server, pub, jwtclaim string) {
|
|
s.AccountResolver().Store(pub, jwtclaim)
|
|
}
|
|
|
|
func createClient(t *testing.T, s *Server, akp nkeys.KeyPair) (*testAsyncClient, *bufio.Reader, string) {
|
|
return createClientWithIssuer(t, s, akp, "")
|
|
}
|
|
|
|
func createClientWithIssuer(t *testing.T, s *Server, akp nkeys.KeyPair, optIssuerAccount string) (*testAsyncClient, *bufio.Reader, string) {
|
|
t.Helper()
|
|
nkp, _ := nkeys.CreateUser()
|
|
pub, _ := nkp.PublicKey()
|
|
nuc := jwt.NewUserClaims(pub)
|
|
if optIssuerAccount != "" {
|
|
nuc.IssuerAccount = optIssuerAccount
|
|
}
|
|
ujwt, err := nuc.Encode(akp)
|
|
if err != nil {
|
|
t.Fatalf("Error generating user JWT: %v", err)
|
|
}
|
|
c, cr, l := newClientForServer(s)
|
|
|
|
// Sign Nonce
|
|
var info nonceInfo
|
|
json.Unmarshal([]byte(l[5:]), &info)
|
|
sigraw, _ := nkp.Sign([]byte(info.Nonce))
|
|
sig := base64.RawURLEncoding.EncodeToString(sigraw)
|
|
|
|
cs := fmt.Sprintf("CONNECT {\"jwt\":%q,\"sig\":\"%s\"}\r\nPING\r\n", ujwt, sig)
|
|
return c, cr, cs
|
|
}
|
|
|
|
func setupJWTTestWithClaims(t *testing.T, nac *jwt.AccountClaims, nuc *jwt.UserClaims, expected string) (*Server, nkeys.KeyPair, *testAsyncClient, *bufio.Reader) {
|
|
t.Helper()
|
|
|
|
okp, _ := nkeys.FromSeed(oSeed)
|
|
|
|
akp, _ := nkeys.CreateAccount()
|
|
apub, _ := akp.PublicKey()
|
|
if nac == nil {
|
|
nac = jwt.NewAccountClaims(apub)
|
|
} else {
|
|
nac.Subject = apub
|
|
}
|
|
ajwt, err := nac.Encode(okp)
|
|
if err != nil {
|
|
t.Fatalf("Error generating account JWT: %v", err)
|
|
}
|
|
|
|
nkp, _ := nkeys.CreateUser()
|
|
pub, _ := nkp.PublicKey()
|
|
if nuc == nil {
|
|
nuc = jwt.NewUserClaims(pub)
|
|
} else {
|
|
nuc.Subject = pub
|
|
}
|
|
jwt, err := nuc.Encode(akp)
|
|
if err != nil {
|
|
t.Fatalf("Error generating user JWT: %v", err)
|
|
}
|
|
|
|
s := opTrustBasicSetup()
|
|
buildMemAccResolver(s)
|
|
addAccountToMemResolver(s, apub, ajwt)
|
|
|
|
c, cr, l := newClientForServer(s)
|
|
|
|
// Sign Nonce
|
|
var info nonceInfo
|
|
json.Unmarshal([]byte(l[5:]), &info)
|
|
sigraw, _ := nkp.Sign([]byte(info.Nonce))
|
|
sig := base64.RawURLEncoding.EncodeToString(sigraw)
|
|
|
|
// PING needed to flush the +OK/-ERR to us.
|
|
cs := fmt.Sprintf("CONNECT {\"jwt\":%q,\"sig\":\"%s\",\"verbose\":true,\"pedantic\":true}\r\nPING\r\n", jwt, sig)
|
|
wg := sync.WaitGroup{}
|
|
wg.Add(1)
|
|
go func() {
|
|
c.parse([]byte(cs))
|
|
wg.Done()
|
|
}()
|
|
l, _ = cr.ReadString('\n')
|
|
if !strings.HasPrefix(l, expected) {
|
|
t.Fatalf("Expected %q, got %q", expected, l)
|
|
}
|
|
wg.Wait()
|
|
|
|
return s, akp, c, cr
|
|
}
|
|
|
|
func setupJWTTestWitAccountClaims(t *testing.T, nac *jwt.AccountClaims, expected string) (*Server, nkeys.KeyPair, *testAsyncClient, *bufio.Reader) {
|
|
t.Helper()
|
|
return setupJWTTestWithClaims(t, nac, nil, expected)
|
|
}
|
|
|
|
// This is used in test to create account claims and pass it
|
|
// to setupJWTTestWitAccountClaims.
|
|
func newJWTTestAccountClaims() *jwt.AccountClaims {
|
|
// We call NewAccountClaims() because it sets some defaults.
|
|
// However, this call needs a subject, but the real subject will
|
|
// be set in setupJWTTestWitAccountClaims(). Use some temporary one
|
|
// here.
|
|
return jwt.NewAccountClaims("temp")
|
|
}
|
|
|
|
func setupJWTTestWithUserClaims(t *testing.T, nuc *jwt.UserClaims, expected string) (*Server, *testAsyncClient, *bufio.Reader) {
|
|
t.Helper()
|
|
s, _, c, cr := setupJWTTestWithClaims(t, nil, nuc, expected)
|
|
return s, c, cr
|
|
}
|
|
|
|
// This is used in test to create user claims and pass it
|
|
// to setupJWTTestWithUserClaims.
|
|
func newJWTTestUserClaims() *jwt.UserClaims {
|
|
// As of now, tests could simply do &jwt.UserClaims{}, but in
|
|
// case some defaults are later added, we call NewUserClaims().
|
|
// However, this call needs a subject, but the real subject will
|
|
// be set in setupJWTTestWithUserClaims(). Use some temporary one
|
|
// here.
|
|
return jwt.NewUserClaims("temp")
|
|
}
|
|
|
|
func TestJWTUser(t *testing.T) {
|
|
s := opTrustBasicSetup()
|
|
defer s.Shutdown()
|
|
|
|
// Check to make sure we would have an authTimer
|
|
if !s.info.AuthRequired {
|
|
t.Fatalf("Expect the server to require auth")
|
|
}
|
|
|
|
c, cr, _ := newClientForServer(s)
|
|
defer c.close()
|
|
|
|
// Don't send jwt field, should fail.
|
|
c.parseAsync("CONNECT {\"verbose\":true,\"pedantic\":true}\r\nPING\r\n")
|
|
l, _ := cr.ReadString('\n')
|
|
if !strings.HasPrefix(l, "-ERR ") {
|
|
t.Fatalf("Expected an error")
|
|
}
|
|
|
|
okp, _ := nkeys.FromSeed(oSeed)
|
|
|
|
// Create an account that will be expired.
|
|
akp, _ := nkeys.CreateAccount()
|
|
apub, _ := akp.PublicKey()
|
|
nac := jwt.NewAccountClaims(apub)
|
|
ajwt, err := nac.Encode(okp)
|
|
if err != nil {
|
|
t.Fatalf("Error generating account JWT: %v", err)
|
|
}
|
|
|
|
c, cr, cs := createClient(t, s, akp)
|
|
defer c.close()
|
|
|
|
// PING needed to flush the +OK/-ERR to us.
|
|
// This should fail too since no account resolver is defined.
|
|
c.parseAsync(cs)
|
|
l, _ = cr.ReadString('\n')
|
|
if !strings.HasPrefix(l, "-ERR ") {
|
|
t.Fatalf("Expected an error")
|
|
}
|
|
|
|
// Ok now let's walk through and make sure all is good.
|
|
// We will set the account resolver by hand to a memory resolver.
|
|
buildMemAccResolver(s)
|
|
addAccountToMemResolver(s, apub, ajwt)
|
|
|
|
c, cr, cs = createClient(t, s, akp)
|
|
defer c.close()
|
|
|
|
c.parseAsync(cs)
|
|
l, _ = cr.ReadString('\n')
|
|
if !strings.HasPrefix(l, "PONG") {
|
|
t.Fatalf("Expected a PONG, got %q", l)
|
|
}
|
|
}
|
|
|
|
func TestJWTUserBadTrusted(t *testing.T) {
|
|
s := opTrustBasicSetup()
|
|
defer s.Shutdown()
|
|
|
|
// Check to make sure we would have an authTimer
|
|
if !s.info.AuthRequired {
|
|
t.Fatalf("Expect the server to require auth")
|
|
}
|
|
// Now place bad trusted key
|
|
s.mu.Lock()
|
|
s.trustedKeys = []string{"bad"}
|
|
s.mu.Unlock()
|
|
|
|
buildMemAccResolver(s)
|
|
|
|
okp, _ := nkeys.FromSeed(oSeed)
|
|
|
|
// Create an account that will be expired.
|
|
akp, _ := nkeys.CreateAccount()
|
|
apub, _ := akp.PublicKey()
|
|
nac := jwt.NewAccountClaims(apub)
|
|
ajwt, err := nac.Encode(okp)
|
|
if err != nil {
|
|
t.Fatalf("Error generating account JWT: %v", err)
|
|
}
|
|
addAccountToMemResolver(s, apub, ajwt)
|
|
|
|
c, cr, cs := createClient(t, s, akp)
|
|
defer c.close()
|
|
c.parseAsync(cs)
|
|
l, _ := cr.ReadString('\n')
|
|
if !strings.HasPrefix(l, "-ERR ") {
|
|
t.Fatalf("Expected an error")
|
|
}
|
|
}
|
|
|
|
// Test that if a user tries to connect with an expired user JWT we do the right thing.
|
|
func TestJWTUserExpired(t *testing.T) {
|
|
nuc := newJWTTestUserClaims()
|
|
nuc.IssuedAt = time.Now().Add(-10 * time.Second).Unix()
|
|
nuc.Expires = time.Now().Add(-2 * time.Second).Unix()
|
|
s, c, _ := setupJWTTestWithUserClaims(t, nuc, "-ERR ")
|
|
c.close()
|
|
s.Shutdown()
|
|
}
|
|
|
|
func TestJWTUserExpiresAfterConnect(t *testing.T) {
|
|
nuc := newJWTTestUserClaims()
|
|
nuc.IssuedAt = time.Now().Unix()
|
|
nuc.Expires = time.Now().Add(time.Second).Unix()
|
|
s, c, cr := setupJWTTestWithUserClaims(t, nuc, "+OK")
|
|
defer s.Shutdown()
|
|
defer c.close()
|
|
l, _ := cr.ReadString('\n')
|
|
if !strings.HasPrefix(l, "PONG") {
|
|
t.Fatalf("Expected a PONG")
|
|
}
|
|
|
|
// Now we should expire after 1 second or so.
|
|
time.Sleep(1250 * time.Millisecond)
|
|
|
|
l, _ = cr.ReadString('\n')
|
|
if !strings.HasPrefix(l, "-ERR ") {
|
|
t.Fatalf("Expected an error")
|
|
}
|
|
if !strings.Contains(l, "Expired") {
|
|
t.Fatalf("Expected 'Expired' to be in the error")
|
|
}
|
|
}
|
|
|
|
func TestJWTUserPermissionClaims(t *testing.T) {
|
|
nuc := newJWTTestUserClaims()
|
|
nuc.Permissions.Pub.Allow.Add("foo")
|
|
nuc.Permissions.Pub.Allow.Add("bar")
|
|
nuc.Permissions.Pub.Deny.Add("baz")
|
|
nuc.Permissions.Sub.Allow.Add("foo")
|
|
nuc.Permissions.Sub.Allow.Add("bar")
|
|
nuc.Permissions.Sub.Deny.Add("baz")
|
|
|
|
s, c, _ := setupJWTTestWithUserClaims(t, nuc, "+OK")
|
|
defer s.Shutdown()
|
|
defer c.close()
|
|
|
|
// Now check client to make sure permissions transferred.
|
|
c.mu.Lock()
|
|
defer c.mu.Unlock()
|
|
|
|
if c.perms == nil {
|
|
t.Fatalf("Expected client permissions to be set")
|
|
}
|
|
|
|
if lpa := c.perms.pub.allow.Count(); lpa != 2 {
|
|
t.Fatalf("Expected 2 publish allow subjects, got %d", lpa)
|
|
}
|
|
if lpd := c.perms.pub.deny.Count(); lpd != 1 {
|
|
t.Fatalf("Expected 1 publish deny subjects, got %d", lpd)
|
|
}
|
|
if lsa := c.perms.sub.allow.Count(); lsa != 2 {
|
|
t.Fatalf("Expected 2 subscribe allow subjects, got %d", lsa)
|
|
}
|
|
if lsd := c.perms.sub.deny.Count(); lsd != 1 {
|
|
t.Fatalf("Expected 1 subscribe deny subjects, got %d", lsd)
|
|
}
|
|
}
|
|
|
|
func TestJWTUserResponsePermissionClaims(t *testing.T) {
|
|
nuc := newJWTTestUserClaims()
|
|
nuc.Permissions.Resp = &jwt.ResponsePermission{
|
|
MaxMsgs: 22,
|
|
Expires: 100 * time.Millisecond,
|
|
}
|
|
s, c, _ := setupJWTTestWithUserClaims(t, nuc, "+OK")
|
|
defer s.Shutdown()
|
|
defer c.close()
|
|
|
|
// Now check client to make sure permissions transferred.
|
|
c.mu.Lock()
|
|
defer c.mu.Unlock()
|
|
|
|
if c.perms == nil {
|
|
t.Fatalf("Expected client permissions to be set")
|
|
}
|
|
if c.perms.pub.allow == nil {
|
|
t.Fatalf("Expected client perms for pub allow to be non-nil")
|
|
}
|
|
if lpa := c.perms.pub.allow.Count(); lpa != 0 {
|
|
t.Fatalf("Expected 0 publish allow subjects, got %d", lpa)
|
|
}
|
|
if c.perms.resp == nil {
|
|
t.Fatalf("Expected client perms for response permissions to be non-nil")
|
|
}
|
|
if c.perms.resp.MaxMsgs != nuc.Permissions.Resp.MaxMsgs {
|
|
t.Fatalf("Expected client perms for response permissions MaxMsgs to be same as jwt: %d vs %d",
|
|
c.perms.resp.MaxMsgs, nuc.Permissions.Resp.MaxMsgs)
|
|
}
|
|
if c.perms.resp.Expires != nuc.Permissions.Resp.Expires {
|
|
t.Fatalf("Expected client perms for response permissions Expires to be same as jwt: %v vs %v",
|
|
c.perms.resp.Expires, nuc.Permissions.Resp.Expires)
|
|
}
|
|
}
|
|
|
|
func TestJWTUserResponsePermissionClaimsDefaultValues(t *testing.T) {
|
|
nuc := newJWTTestUserClaims()
|
|
nuc.Permissions.Resp = &jwt.ResponsePermission{}
|
|
s, c, _ := setupJWTTestWithUserClaims(t, nuc, "+OK")
|
|
defer s.Shutdown()
|
|
defer c.close()
|
|
|
|
// Now check client to make sure permissions transferred
|
|
// and defaults are set.
|
|
c.mu.Lock()
|
|
defer c.mu.Unlock()
|
|
|
|
if c.perms == nil {
|
|
t.Fatalf("Expected client permissions to be set")
|
|
}
|
|
if c.perms.pub.allow == nil {
|
|
t.Fatalf("Expected client perms for pub allow to be non-nil")
|
|
}
|
|
if lpa := c.perms.pub.allow.Count(); lpa != 0 {
|
|
t.Fatalf("Expected 0 publish allow subjects, got %d", lpa)
|
|
}
|
|
if c.perms.resp == nil {
|
|
t.Fatalf("Expected client perms for response permissions to be non-nil")
|
|
}
|
|
if c.perms.resp.MaxMsgs != DEFAULT_ALLOW_RESPONSE_MAX_MSGS {
|
|
t.Fatalf("Expected client perms for response permissions MaxMsgs to be default %v, got %v",
|
|
DEFAULT_ALLOW_RESPONSE_MAX_MSGS, c.perms.resp.MaxMsgs)
|
|
}
|
|
if c.perms.resp.Expires != DEFAULT_ALLOW_RESPONSE_EXPIRATION {
|
|
t.Fatalf("Expected client perms for response permissions Expires to be default %v, got %v",
|
|
DEFAULT_ALLOW_RESPONSE_EXPIRATION, c.perms.resp.Expires)
|
|
}
|
|
}
|
|
|
|
func TestJWTUserResponsePermissionClaimsNegativeValues(t *testing.T) {
|
|
nuc := newJWTTestUserClaims()
|
|
nuc.Permissions.Resp = &jwt.ResponsePermission{
|
|
MaxMsgs: -1,
|
|
Expires: -1 * time.Second,
|
|
}
|
|
s, c, _ := setupJWTTestWithUserClaims(t, nuc, "+OK")
|
|
defer s.Shutdown()
|
|
defer c.close()
|
|
|
|
// Now check client to make sure permissions transferred
|
|
// and negative values are transferred.
|
|
c.mu.Lock()
|
|
defer c.mu.Unlock()
|
|
|
|
if c.perms == nil {
|
|
t.Fatalf("Expected client permissions to be set")
|
|
}
|
|
if c.perms.pub.allow == nil {
|
|
t.Fatalf("Expected client perms for pub allow to be non-nil")
|
|
}
|
|
if lpa := c.perms.pub.allow.Count(); lpa != 0 {
|
|
t.Fatalf("Expected 0 publish allow subjects, got %d", lpa)
|
|
}
|
|
if c.perms.resp == nil {
|
|
t.Fatalf("Expected client perms for response permissions to be non-nil")
|
|
}
|
|
if c.perms.resp.MaxMsgs != -1 {
|
|
t.Fatalf("Expected client perms for response permissions MaxMsgs to be %v, got %v",
|
|
-1, c.perms.resp.MaxMsgs)
|
|
}
|
|
if c.perms.resp.Expires != -1*time.Second {
|
|
t.Fatalf("Expected client perms for response permissions Expires to be %v, got %v",
|
|
-1*time.Second, c.perms.resp.Expires)
|
|
}
|
|
}
|
|
|
|
func TestJWTAccountExpired(t *testing.T) {
|
|
nac := newJWTTestAccountClaims()
|
|
nac.IssuedAt = time.Now().Add(-10 * time.Second).Unix()
|
|
nac.Expires = time.Now().Add(-2 * time.Second).Unix()
|
|
s, _, c, _ := setupJWTTestWitAccountClaims(t, nac, "-ERR ")
|
|
defer s.Shutdown()
|
|
defer c.close()
|
|
}
|
|
|
|
func TestJWTAccountExpiresAfterConnect(t *testing.T) {
|
|
nac := newJWTTestAccountClaims()
|
|
now := time.Now()
|
|
nac.IssuedAt = now.Add(-10 * time.Second).Unix()
|
|
nac.Expires = now.Round(time.Second).Add(time.Second).Unix()
|
|
s, akp, c, cr := setupJWTTestWitAccountClaims(t, nac, "+OK")
|
|
defer s.Shutdown()
|
|
defer c.close()
|
|
|
|
apub, _ := akp.PublicKey()
|
|
acc, err := s.LookupAccount(apub)
|
|
if acc == nil || err != nil {
|
|
t.Fatalf("Expected to retrieve the account")
|
|
}
|
|
|
|
if l, _ := cr.ReadString('\n'); !strings.HasPrefix(l, "PONG") {
|
|
t.Fatalf("Expected PONG, got %q", l)
|
|
}
|
|
|
|
// Wait for the account to be expired.
|
|
checkFor(t, 3*time.Second, 100*time.Millisecond, func() error {
|
|
if acc.IsExpired() {
|
|
return nil
|
|
}
|
|
return fmt.Errorf("Account not expired yet")
|
|
})
|
|
|
|
l, _ := cr.ReadString('\n')
|
|
if !strings.HasPrefix(l, "-ERR ") {
|
|
t.Fatalf("Expected an error, got %q", l)
|
|
}
|
|
if !strings.Contains(l, "Expired") {
|
|
t.Fatalf("Expected 'Expired' to be in the error")
|
|
}
|
|
|
|
// Now make sure that accounts that have expired return an error.
|
|
c, cr, cs := createClient(t, s, akp)
|
|
defer c.close()
|
|
c.parseAsync(cs)
|
|
l, _ = cr.ReadString('\n')
|
|
if !strings.HasPrefix(l, "-ERR ") {
|
|
t.Fatalf("Expected an error")
|
|
}
|
|
}
|
|
|
|
func TestJWTAccountRenew(t *testing.T) {
|
|
nac := newJWTTestAccountClaims()
|
|
// Create an account that has expired.
|
|
nac.IssuedAt = time.Now().Add(-10 * time.Second).Unix()
|
|
nac.Expires = time.Now().Add(-2 * time.Second).Unix()
|
|
// Expect an error
|
|
s, akp, c, _ := setupJWTTestWitAccountClaims(t, nac, "-ERR ")
|
|
defer s.Shutdown()
|
|
defer c.close()
|
|
|
|
okp, _ := nkeys.FromSeed(oSeed)
|
|
apub, _ := akp.PublicKey()
|
|
|
|
// Now update with new expiration
|
|
nac.IssuedAt = time.Now().Unix()
|
|
nac.Expires = time.Now().Add(5 * time.Second).Unix()
|
|
ajwt, err := nac.Encode(okp)
|
|
if err != nil {
|
|
t.Fatalf("Error generating account JWT: %v", err)
|
|
}
|
|
|
|
// Update the account
|
|
addAccountToMemResolver(s, apub, ajwt)
|
|
acc, _ := s.LookupAccount(apub)
|
|
if acc == nil {
|
|
t.Fatalf("Expected to retrieve the account")
|
|
}
|
|
s.updateAccountClaims(acc, nac)
|
|
|
|
// Now make sure we can connect.
|
|
c, cr, cs := createClient(t, s, akp)
|
|
defer c.close()
|
|
c.parseAsync(cs)
|
|
if l, _ := cr.ReadString('\n'); !strings.HasPrefix(l, "PONG") {
|
|
t.Fatalf("Expected a PONG, got: %q", l)
|
|
}
|
|
}
|
|
|
|
func TestJWTAccountRenewFromResolver(t *testing.T) {
|
|
s := opTrustBasicSetup()
|
|
defer s.Shutdown()
|
|
buildMemAccResolver(s)
|
|
|
|
okp, _ := nkeys.FromSeed(oSeed)
|
|
|
|
akp, _ := nkeys.CreateAccount()
|
|
apub, _ := akp.PublicKey()
|
|
nac := jwt.NewAccountClaims(apub)
|
|
nac.IssuedAt = time.Now().Add(-10 * time.Second).Unix()
|
|
nac.Expires = time.Now().Add(time.Second).Unix()
|
|
ajwt, err := nac.Encode(okp)
|
|
if err != nil {
|
|
t.Fatalf("Error generating account JWT: %v", err)
|
|
}
|
|
|
|
addAccountToMemResolver(s, apub, ajwt)
|
|
// Force it to be loaded by the server and start the expiration timer.
|
|
acc, _ := s.LookupAccount(apub)
|
|
if acc == nil {
|
|
t.Fatalf("Could not retrieve account for %q", apub)
|
|
}
|
|
|
|
// Create a new user
|
|
c, cr, cs := createClient(t, s, akp)
|
|
defer c.close()
|
|
// Wait for expiration.
|
|
time.Sleep(1250 * time.Millisecond)
|
|
|
|
c.parseAsync(cs)
|
|
l, _ := cr.ReadString('\n')
|
|
if !strings.HasPrefix(l, "-ERR ") {
|
|
t.Fatalf("Expected an error")
|
|
}
|
|
|
|
// Now update with new expiration
|
|
nac.IssuedAt = time.Now().Unix()
|
|
nac.Expires = time.Now().Add(5 * time.Second).Unix()
|
|
ajwt, err = nac.Encode(okp)
|
|
if err != nil {
|
|
t.Fatalf("Error generating account JWT: %v", err)
|
|
}
|
|
|
|
// Update the account
|
|
addAccountToMemResolver(s, apub, ajwt)
|
|
// Make sure the too quick update suppression does not bite us.
|
|
acc.mu.Lock()
|
|
acc.updated = time.Now().Add(-1 * time.Hour)
|
|
acc.mu.Unlock()
|
|
|
|
// Do not update the account directly. The resolver should
|
|
// happen automatically.
|
|
|
|
// Now make sure we can connect.
|
|
c, cr, cs = createClient(t, s, akp)
|
|
defer c.close()
|
|
c.parseAsync(cs)
|
|
l, _ = cr.ReadString('\n')
|
|
if !strings.HasPrefix(l, "PONG") {
|
|
t.Fatalf("Expected a PONG, got: %q", l)
|
|
}
|
|
}
|
|
|
|
func TestJWTAccountBasicImportExport(t *testing.T) {
|
|
s := opTrustBasicSetup()
|
|
defer s.Shutdown()
|
|
buildMemAccResolver(s)
|
|
|
|
okp, _ := nkeys.FromSeed(oSeed)
|
|
|
|
// Create accounts and imports/exports.
|
|
fooKP, _ := nkeys.CreateAccount()
|
|
fooPub, _ := fooKP.PublicKey()
|
|
fooAC := jwt.NewAccountClaims(fooPub)
|
|
|
|
// Now create Exports.
|
|
streamExport := &jwt.Export{Subject: "foo", Type: jwt.Stream}
|
|
streamExport2 := &jwt.Export{Subject: "private", Type: jwt.Stream, TokenReq: true}
|
|
serviceExport := &jwt.Export{Subject: "req.echo", Type: jwt.Service, TokenReq: true}
|
|
serviceExport2 := &jwt.Export{Subject: "req.add", Type: jwt.Service, TokenReq: true}
|
|
|
|
fooAC.Exports.Add(streamExport, streamExport2, serviceExport, serviceExport2)
|
|
fooJWT, err := fooAC.Encode(okp)
|
|
if err != nil {
|
|
t.Fatalf("Error generating account JWT: %v", err)
|
|
}
|
|
|
|
addAccountToMemResolver(s, fooPub, fooJWT)
|
|
|
|
acc, _ := s.LookupAccount(fooPub)
|
|
if acc == nil {
|
|
t.Fatalf("Expected to retrieve the account")
|
|
}
|
|
|
|
// Check to make sure exports transferred over.
|
|
if les := len(acc.exports.streams); les != 2 {
|
|
t.Fatalf("Expected exports streams len of 2, got %d", les)
|
|
}
|
|
if les := len(acc.exports.services); les != 2 {
|
|
t.Fatalf("Expected exports services len of 2, got %d", les)
|
|
}
|
|
_, ok := acc.exports.streams["foo"]
|
|
if !ok {
|
|
t.Fatalf("Expected to map a stream export")
|
|
}
|
|
se, ok := acc.exports.services["req.echo"]
|
|
if !ok || se == nil {
|
|
t.Fatalf("Expected to map a service export")
|
|
}
|
|
if !se.tokenReq {
|
|
t.Fatalf("Expected the service export to require tokens")
|
|
}
|
|
|
|
barKP, _ := nkeys.CreateAccount()
|
|
barPub, _ := barKP.PublicKey()
|
|
barAC := jwt.NewAccountClaims(barPub)
|
|
|
|
streamImport := &jwt.Import{Account: fooPub, Subject: "foo", To: "import.foo", Type: jwt.Stream}
|
|
serviceImport := &jwt.Import{Account: fooPub, Subject: "req.echo", Type: jwt.Service}
|
|
barAC.Imports.Add(streamImport, serviceImport)
|
|
barJWT, err := barAC.Encode(okp)
|
|
if err != nil {
|
|
t.Fatalf("Error generating account JWT: %v", err)
|
|
}
|
|
addAccountToMemResolver(s, barPub, barJWT)
|
|
|
|
acc, _ = s.LookupAccount(barPub)
|
|
if acc == nil {
|
|
t.Fatalf("Expected to retrieve the account")
|
|
}
|
|
if les := len(acc.imports.streams); les != 1 {
|
|
t.Fatalf("Expected imports streams len of 1, got %d", les)
|
|
}
|
|
// Our service import should have failed without a token.
|
|
if les := len(acc.imports.services); les != 0 {
|
|
t.Fatalf("Expected imports services len of 0, got %d", les)
|
|
}
|
|
|
|
// Now add in a bad activation token.
|
|
barAC = jwt.NewAccountClaims(barPub)
|
|
serviceImport = &jwt.Import{Account: fooPub, Subject: "req.echo", Token: "not a token", Type: jwt.Service}
|
|
barAC.Imports.Add(serviceImport)
|
|
barJWT, err = barAC.Encode(okp)
|
|
if err != nil {
|
|
t.Fatalf("Error generating account JWT: %v", err)
|
|
}
|
|
addAccountToMemResolver(s, barPub, barJWT)
|
|
|
|
s.updateAccountClaims(acc, barAC)
|
|
|
|
// Our service import should have failed with a bad token.
|
|
if les := len(acc.imports.services); les != 0 {
|
|
t.Fatalf("Expected imports services len of 0, got %d", les)
|
|
}
|
|
|
|
// Now make a correct one.
|
|
barAC = jwt.NewAccountClaims(barPub)
|
|
serviceImport = &jwt.Import{Account: fooPub, Subject: "req.echo", Type: jwt.Service}
|
|
|
|
activation := jwt.NewActivationClaims(barPub)
|
|
activation.ImportSubject = "req.echo"
|
|
activation.ImportType = jwt.Service
|
|
actJWT, err := activation.Encode(fooKP)
|
|
if err != nil {
|
|
t.Fatalf("Error generating activation token: %v", err)
|
|
}
|
|
serviceImport.Token = actJWT
|
|
barAC.Imports.Add(serviceImport)
|
|
barJWT, err = barAC.Encode(okp)
|
|
if err != nil {
|
|
t.Fatalf("Error generating account JWT: %v", err)
|
|
}
|
|
addAccountToMemResolver(s, barPub, barJWT)
|
|
s.updateAccountClaims(acc, barAC)
|
|
// Our service import should have succeeded.
|
|
if les := len(acc.imports.services); les != 1 {
|
|
t.Fatalf("Expected imports services len of 1, got %d", les)
|
|
}
|
|
|
|
// Now test url
|
|
barAC = jwt.NewAccountClaims(barPub)
|
|
serviceImport = &jwt.Import{Account: fooPub, Subject: "req.add", Type: jwt.Service}
|
|
|
|
activation = jwt.NewActivationClaims(barPub)
|
|
activation.ImportSubject = "req.add"
|
|
activation.ImportType = jwt.Service
|
|
actJWT, err = activation.Encode(fooKP)
|
|
if err != nil {
|
|
t.Fatalf("Error generating activation token: %v", err)
|
|
}
|
|
|
|
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.Write([]byte(actJWT))
|
|
}))
|
|
defer ts.Close()
|
|
|
|
serviceImport.Token = ts.URL
|
|
barAC.Imports.Add(serviceImport)
|
|
barJWT, err = barAC.Encode(okp)
|
|
if err != nil {
|
|
t.Fatalf("Error generating account JWT: %v", err)
|
|
}
|
|
addAccountToMemResolver(s, barPub, barJWT)
|
|
s.updateAccountClaims(acc, barAC)
|
|
// Our service import should have succeeded. Should be the only one since we reset.
|
|
if les := len(acc.imports.services); les != 1 {
|
|
t.Fatalf("Expected imports services len of 1, got %d", les)
|
|
}
|
|
|
|
// Now streams
|
|
barAC = jwt.NewAccountClaims(barPub)
|
|
streamImport = &jwt.Import{Account: fooPub, Subject: "private", To: "import.private", Type: jwt.Stream}
|
|
|
|
barAC.Imports.Add(streamImport)
|
|
barJWT, err = barAC.Encode(okp)
|
|
if err != nil {
|
|
t.Fatalf("Error generating account JWT: %v", err)
|
|
}
|
|
addAccountToMemResolver(s, barPub, barJWT)
|
|
s.updateAccountClaims(acc, barAC)
|
|
// Our stream import should have not succeeded.
|
|
if les := len(acc.imports.streams); les != 0 {
|
|
t.Fatalf("Expected imports services len of 0, got %d", les)
|
|
}
|
|
|
|
// Now add in activation.
|
|
barAC = jwt.NewAccountClaims(barPub)
|
|
streamImport = &jwt.Import{Account: fooPub, Subject: "private", To: "import.private", Type: jwt.Stream}
|
|
|
|
activation = jwt.NewActivationClaims(barPub)
|
|
activation.ImportSubject = "private"
|
|
activation.ImportType = jwt.Stream
|
|
actJWT, err = activation.Encode(fooKP)
|
|
if err != nil {
|
|
t.Fatalf("Error generating activation token: %v", err)
|
|
}
|
|
streamImport.Token = actJWT
|
|
barAC.Imports.Add(streamImport)
|
|
barJWT, err = barAC.Encode(okp)
|
|
if err != nil {
|
|
t.Fatalf("Error generating account JWT: %v", err)
|
|
}
|
|
addAccountToMemResolver(s, barPub, barJWT)
|
|
s.updateAccountClaims(acc, barAC)
|
|
// Our stream import should have not succeeded.
|
|
if les := len(acc.imports.streams); les != 1 {
|
|
t.Fatalf("Expected imports services len of 1, got %d", les)
|
|
}
|
|
}
|
|
|
|
func TestJWTAccountExportWithResponseType(t *testing.T) {
|
|
s := opTrustBasicSetup()
|
|
defer s.Shutdown()
|
|
buildMemAccResolver(s)
|
|
|
|
okp, _ := nkeys.FromSeed(oSeed)
|
|
|
|
// Create accounts and imports/exports.
|
|
fooKP, _ := nkeys.CreateAccount()
|
|
fooPub, _ := fooKP.PublicKey()
|
|
fooAC := jwt.NewAccountClaims(fooPub)
|
|
|
|
// Now create Exports.
|
|
serviceStreamExport := &jwt.Export{Subject: "test.stream", Type: jwt.Service, ResponseType: jwt.ResponseTypeStream, TokenReq: false}
|
|
serviceChunkExport := &jwt.Export{Subject: "test.chunk", Type: jwt.Service, ResponseType: jwt.ResponseTypeChunked, TokenReq: false}
|
|
serviceSingletonExport := &jwt.Export{Subject: "test.single", Type: jwt.Service, ResponseType: jwt.ResponseTypeSingleton, TokenReq: true}
|
|
serviceDefExport := &jwt.Export{Subject: "test.def", Type: jwt.Service, TokenReq: true}
|
|
serviceOldExport := &jwt.Export{Subject: "test.old", Type: jwt.Service, TokenReq: false}
|
|
|
|
fooAC.Exports.Add(serviceStreamExport, serviceSingletonExport, serviceChunkExport, serviceDefExport, serviceOldExport)
|
|
fooJWT, err := fooAC.Encode(okp)
|
|
if err != nil {
|
|
t.Fatalf("Error generating account JWT: %v", err)
|
|
}
|
|
|
|
addAccountToMemResolver(s, fooPub, fooJWT)
|
|
|
|
fooAcc, _ := s.LookupAccount(fooPub)
|
|
if fooAcc == nil {
|
|
t.Fatalf("Expected to retrieve the account")
|
|
}
|
|
|
|
services := fooAcc.exports.services
|
|
|
|
if len(services) != 5 {
|
|
t.Fatalf("Expected 4 services")
|
|
}
|
|
|
|
se, ok := services["test.stream"]
|
|
if !ok || se == nil {
|
|
t.Fatalf("Expected to map a service export")
|
|
}
|
|
if se.tokenReq {
|
|
t.Fatalf("Expected the service export to not require tokens")
|
|
}
|
|
if se.respType != Stream {
|
|
t.Fatalf("Expected the service export to respond with a stream")
|
|
}
|
|
|
|
se, ok = services["test.chunk"]
|
|
if !ok || se == nil {
|
|
t.Fatalf("Expected to map a service export")
|
|
}
|
|
if se.tokenReq {
|
|
t.Fatalf("Expected the service export to not require tokens")
|
|
}
|
|
if se.respType != Chunked {
|
|
t.Fatalf("Expected the service export to respond with a stream")
|
|
}
|
|
|
|
se, ok = services["test.def"]
|
|
if !ok || se == nil {
|
|
t.Fatalf("Expected to map a service export")
|
|
}
|
|
if !se.tokenReq {
|
|
t.Fatalf("Expected the service export to not require tokens")
|
|
}
|
|
if se.respType != Singleton {
|
|
t.Fatalf("Expected the service export to respond with a stream")
|
|
}
|
|
|
|
se, ok = services["test.single"]
|
|
if !ok || se == nil {
|
|
t.Fatalf("Expected to map a service export")
|
|
}
|
|
if !se.tokenReq {
|
|
t.Fatalf("Expected the service export to not require tokens")
|
|
}
|
|
if se.respType != Singleton {
|
|
t.Fatalf("Expected the service export to respond with a stream")
|
|
}
|
|
|
|
se, ok = services["test.old"]
|
|
if !ok || se != nil {
|
|
t.Fatalf("Service with a singleton response and no tokens should be nil in the map")
|
|
}
|
|
}
|
|
|
|
func expectPong(t *testing.T, cr *bufio.Reader) {
|
|
t.Helper()
|
|
l, _ := cr.ReadString('\n')
|
|
if !strings.HasPrefix(l, "PONG") {
|
|
t.Fatalf("Expected a PONG, got %q", l)
|
|
}
|
|
}
|
|
|
|
func expectMsg(t *testing.T, cr *bufio.Reader, sub, payload string) {
|
|
t.Helper()
|
|
l, _ := cr.ReadString('\n')
|
|
expected := "MSG " + sub
|
|
if !strings.HasPrefix(l, expected) {
|
|
t.Fatalf("Expected %q, got %q", expected, l)
|
|
}
|
|
l, _ = cr.ReadString('\n')
|
|
if l != payload+"\r\n" {
|
|
t.Fatalf("Expected %q, got %q", payload, l)
|
|
}
|
|
expectPong(t, cr)
|
|
}
|
|
|
|
func TestJWTAccountImportExportUpdates(t *testing.T) {
|
|
s := opTrustBasicSetup()
|
|
defer s.Shutdown()
|
|
buildMemAccResolver(s)
|
|
|
|
okp, _ := nkeys.FromSeed(oSeed)
|
|
|
|
// Create accounts and imports/exports.
|
|
fooKP, _ := nkeys.CreateAccount()
|
|
fooPub, _ := fooKP.PublicKey()
|
|
fooAC := jwt.NewAccountClaims(fooPub)
|
|
streamExport := &jwt.Export{Subject: "foo", Type: jwt.Stream}
|
|
|
|
fooAC.Exports.Add(streamExport)
|
|
fooJWT, err := fooAC.Encode(okp)
|
|
if err != nil {
|
|
t.Fatalf("Error generating account JWT: %v", err)
|
|
}
|
|
addAccountToMemResolver(s, fooPub, fooJWT)
|
|
|
|
barKP, _ := nkeys.CreateAccount()
|
|
barPub, _ := barKP.PublicKey()
|
|
barAC := jwt.NewAccountClaims(barPub)
|
|
streamImport := &jwt.Import{Account: fooPub, Subject: "foo", To: "import", Type: jwt.Stream}
|
|
|
|
barAC.Imports.Add(streamImport)
|
|
barJWT, err := barAC.Encode(okp)
|
|
if err != nil {
|
|
t.Fatalf("Error generating account JWT: %v", err)
|
|
}
|
|
addAccountToMemResolver(s, barPub, barJWT)
|
|
|
|
// Create a client.
|
|
c, cr, cs := createClient(t, s, barKP)
|
|
defer c.close()
|
|
|
|
c.parseAsync(cs)
|
|
expectPong(t, cr)
|
|
|
|
c.parseAsync("SUB import.foo 1\r\nPING\r\n")
|
|
expectPong(t, cr)
|
|
|
|
checkShadow := func(expected int) {
|
|
t.Helper()
|
|
c.mu.Lock()
|
|
defer c.mu.Unlock()
|
|
sub := c.subs["1"]
|
|
if ls := len(sub.shadow); ls != expected {
|
|
t.Fatalf("Expected shadows to be %d, got %d", expected, ls)
|
|
}
|
|
}
|
|
|
|
// We created a SUB on foo which should create a shadow subscription.
|
|
checkShadow(1)
|
|
|
|
// Now update bar and remove the import which should make the shadow go away.
|
|
barAC = jwt.NewAccountClaims(barPub)
|
|
barJWT, _ = barAC.Encode(okp)
|
|
addAccountToMemResolver(s, barPub, barJWT)
|
|
acc, _ := s.LookupAccount(barPub)
|
|
s.updateAccountClaims(acc, barAC)
|
|
|
|
checkShadow(0)
|
|
|
|
// Now add it back and make sure the shadow comes back.
|
|
streamImport = &jwt.Import{Account: string(fooPub), Subject: "foo", To: "import", Type: jwt.Stream}
|
|
barAC.Imports.Add(streamImport)
|
|
barJWT, _ = barAC.Encode(okp)
|
|
addAccountToMemResolver(s, barPub, barJWT)
|
|
s.updateAccountClaims(acc, barAC)
|
|
|
|
checkShadow(1)
|
|
|
|
// Now change export and make sure it goes away as well. So no exports anymore.
|
|
fooAC = jwt.NewAccountClaims(fooPub)
|
|
fooJWT, _ = fooAC.Encode(okp)
|
|
addAccountToMemResolver(s, fooPub, fooJWT)
|
|
acc, _ = s.LookupAccount(fooPub)
|
|
s.updateAccountClaims(acc, fooAC)
|
|
checkShadow(0)
|
|
|
|
// Now add it in but with permission required.
|
|
streamExport = &jwt.Export{Subject: "foo", Type: jwt.Stream, TokenReq: true}
|
|
fooAC.Exports.Add(streamExport)
|
|
fooJWT, _ = fooAC.Encode(okp)
|
|
addAccountToMemResolver(s, fooPub, fooJWT)
|
|
s.updateAccountClaims(acc, fooAC)
|
|
|
|
checkShadow(0)
|
|
|
|
// Now put it back as normal.
|
|
fooAC = jwt.NewAccountClaims(fooPub)
|
|
streamExport = &jwt.Export{Subject: "foo", Type: jwt.Stream}
|
|
fooAC.Exports.Add(streamExport)
|
|
fooJWT, _ = fooAC.Encode(okp)
|
|
addAccountToMemResolver(s, fooPub, fooJWT)
|
|
s.updateAccountClaims(acc, fooAC)
|
|
|
|
checkShadow(1)
|
|
}
|
|
|
|
func TestJWTAccountImportActivationExpires(t *testing.T) {
|
|
s := opTrustBasicSetup()
|
|
defer s.Shutdown()
|
|
buildMemAccResolver(s)
|
|
|
|
okp, _ := nkeys.FromSeed(oSeed)
|
|
|
|
// Create accounts and imports/exports.
|
|
fooKP, _ := nkeys.CreateAccount()
|
|
fooPub, _ := fooKP.PublicKey()
|
|
fooAC := jwt.NewAccountClaims(fooPub)
|
|
streamExport := &jwt.Export{Subject: "foo", Type: jwt.Stream, TokenReq: true}
|
|
fooAC.Exports.Add(streamExport)
|
|
|
|
fooJWT, err := fooAC.Encode(okp)
|
|
if err != nil {
|
|
t.Fatalf("Error generating account JWT: %v", err)
|
|
}
|
|
|
|
addAccountToMemResolver(s, fooPub, fooJWT)
|
|
acc, _ := s.LookupAccount(fooPub)
|
|
if acc == nil {
|
|
t.Fatalf("Expected to retrieve the account")
|
|
}
|
|
|
|
barKP, _ := nkeys.CreateAccount()
|
|
barPub, _ := barKP.PublicKey()
|
|
barAC := jwt.NewAccountClaims(barPub)
|
|
streamImport := &jwt.Import{Account: fooPub, Subject: "foo", To: "import.", Type: jwt.Stream}
|
|
|
|
activation := jwt.NewActivationClaims(barPub)
|
|
activation.ImportSubject = "foo"
|
|
activation.ImportType = jwt.Stream
|
|
now := time.Now()
|
|
activation.IssuedAt = now.Add(-10 * time.Second).Unix()
|
|
// These are second resolution. So round up before adding a second.
|
|
activation.Expires = now.Round(time.Second).Add(time.Second).Unix()
|
|
actJWT, err := activation.Encode(fooKP)
|
|
if err != nil {
|
|
t.Fatalf("Error generating activation token: %v", err)
|
|
}
|
|
streamImport.Token = actJWT
|
|
barAC.Imports.Add(streamImport)
|
|
barJWT, err := barAC.Encode(okp)
|
|
if err != nil {
|
|
t.Fatalf("Error generating account JWT: %v", err)
|
|
}
|
|
addAccountToMemResolver(s, barPub, barJWT)
|
|
if acc, _ := s.LookupAccount(barPub); acc == nil {
|
|
t.Fatalf("Expected to retrieve the account")
|
|
}
|
|
|
|
// Create a client.
|
|
c, cr, cs := createClient(t, s, barKP)
|
|
defer c.close()
|
|
|
|
c.parseAsync(cs)
|
|
expectPong(t, cr)
|
|
|
|
c.parseAsync("SUB import.foo 1\r\nPING\r\n")
|
|
expectPong(t, cr)
|
|
|
|
checkShadow := func(t *testing.T, expected int) {
|
|
t.Helper()
|
|
checkFor(t, 3*time.Second, 15*time.Millisecond, func() error {
|
|
c.mu.Lock()
|
|
defer c.mu.Unlock()
|
|
sub := c.subs["1"]
|
|
if ls := len(sub.shadow); ls != expected {
|
|
return fmt.Errorf("Expected shadows to be %d, got %d", expected, ls)
|
|
}
|
|
return nil
|
|
})
|
|
}
|
|
|
|
// We created a SUB on foo which should create a shadow subscription.
|
|
checkShadow(t, 1)
|
|
|
|
time.Sleep(1250 * time.Millisecond)
|
|
|
|
// Should have expired and been removed.
|
|
checkShadow(t, 0)
|
|
}
|
|
|
|
func TestJWTAccountLimitsSubs(t *testing.T) {
|
|
fooAC := newJWTTestAccountClaims()
|
|
fooAC.Limits.Subs = 10
|
|
s, fooKP, c, _ := setupJWTTestWitAccountClaims(t, fooAC, "+OK")
|
|
defer s.Shutdown()
|
|
defer c.close()
|
|
|
|
okp, _ := nkeys.FromSeed(oSeed)
|
|
fooPub, _ := fooKP.PublicKey()
|
|
|
|
// Create a client.
|
|
c, cr, cs := createClient(t, s, fooKP)
|
|
defer c.close()
|
|
|
|
c.parseAsync(cs)
|
|
expectPong(t, cr)
|
|
|
|
// Check to make sure we have the limit set.
|
|
// Account first
|
|
fooAcc, _ := s.LookupAccount(fooPub)
|
|
fooAcc.mu.RLock()
|
|
if fooAcc.msubs != 10 {
|
|
fooAcc.mu.RUnlock()
|
|
t.Fatalf("Expected account to have msubs of 10, got %d", fooAcc.msubs)
|
|
}
|
|
fooAcc.mu.RUnlock()
|
|
// Now test that the client has limits too.
|
|
c.mu.Lock()
|
|
if c.msubs != 10 {
|
|
c.mu.Unlock()
|
|
t.Fatalf("Expected client msubs to be 10, got %d", c.msubs)
|
|
}
|
|
c.mu.Unlock()
|
|
|
|
// Now make sure its enforced.
|
|
/// These should all work ok.
|
|
for i := 0; i < 10; i++ {
|
|
c.parseAsync(fmt.Sprintf("SUB foo %d\r\nPING\r\n", i))
|
|
expectPong(t, cr)
|
|
}
|
|
|
|
// This one should fail.
|
|
c.parseAsync("SUB foo 22\r\n")
|
|
l, _ := cr.ReadString('\n')
|
|
if !strings.HasPrefix(l, "-ERR") {
|
|
t.Fatalf("Expected an ERR, got: %v", l)
|
|
}
|
|
if !strings.Contains(l, "maximum subscriptions exceeded") {
|
|
t.Fatalf("Expected an ERR for max subscriptions exceeded, got: %v", l)
|
|
}
|
|
|
|
// Now update the claims and expect if max is lower to be disconnected.
|
|
fooAC.Limits.Subs = 5
|
|
fooJWT, err := fooAC.Encode(okp)
|
|
if err != nil {
|
|
t.Fatalf("Error generating account JWT: %v", err)
|
|
}
|
|
addAccountToMemResolver(s, fooPub, fooJWT)
|
|
s.updateAccountClaims(fooAcc, fooAC)
|
|
l, _ = cr.ReadString('\n')
|
|
if !strings.HasPrefix(l, "-ERR") {
|
|
t.Fatalf("Expected an ERR, got: %v", l)
|
|
}
|
|
if !strings.Contains(l, "maximum subscriptions exceeded") {
|
|
t.Fatalf("Expected an ERR for max subscriptions exceeded, got: %v", l)
|
|
}
|
|
}
|
|
|
|
func TestJWTAccountLimitsSubsButServerOverrides(t *testing.T) {
|
|
s := opTrustBasicSetup()
|
|
defer s.Shutdown()
|
|
buildMemAccResolver(s)
|
|
|
|
// override with server setting of 2.
|
|
opts := s.getOpts()
|
|
opts.MaxSubs = 2
|
|
|
|
okp, _ := nkeys.FromSeed(oSeed)
|
|
|
|
// Create accounts and imports/exports.
|
|
fooKP, _ := nkeys.CreateAccount()
|
|
fooPub, _ := fooKP.PublicKey()
|
|
fooAC := jwt.NewAccountClaims(fooPub)
|
|
fooAC.Limits.Subs = 10
|
|
fooJWT, err := fooAC.Encode(okp)
|
|
if err != nil {
|
|
t.Fatalf("Error generating account JWT: %v", err)
|
|
}
|
|
addAccountToMemResolver(s, fooPub, fooJWT)
|
|
fooAcc, _ := s.LookupAccount(fooPub)
|
|
fooAcc.mu.RLock()
|
|
if fooAcc.msubs != 10 {
|
|
fooAcc.mu.RUnlock()
|
|
t.Fatalf("Expected account to have msubs of 10, got %d", fooAcc.msubs)
|
|
}
|
|
fooAcc.mu.RUnlock()
|
|
|
|
// Create a client.
|
|
c, cr, cs := createClient(t, s, fooKP)
|
|
defer c.close()
|
|
|
|
c.parseAsync(cs)
|
|
expectPong(t, cr)
|
|
|
|
c.parseAsync("SUB foo 1\r\nSUB bar 2\r\nSUB baz 3\r\nPING\r\n")
|
|
l, _ := cr.ReadString('\n')
|
|
|
|
if !strings.HasPrefix(l, "-ERR ") {
|
|
t.Fatalf("Expected an error")
|
|
}
|
|
if !strings.Contains(l, "maximum subscriptions exceeded") {
|
|
t.Fatalf("Expected an ERR for max subscriptions exceeded, got: %v", l)
|
|
}
|
|
// Read last PONG so does not hold up test.
|
|
cr.ReadString('\n')
|
|
}
|
|
|
|
func TestJWTAccountLimitsMaxPayload(t *testing.T) {
|
|
fooAC := newJWTTestAccountClaims()
|
|
fooAC.Limits.Payload = 8
|
|
s, fooKP, c, _ := setupJWTTestWitAccountClaims(t, fooAC, "+OK")
|
|
defer s.Shutdown()
|
|
defer c.close()
|
|
|
|
fooPub, _ := fooKP.PublicKey()
|
|
|
|
// Create a client.
|
|
c, cr, cs := createClient(t, s, fooKP)
|
|
defer c.close()
|
|
|
|
c.parseAsync(cs)
|
|
expectPong(t, cr)
|
|
|
|
// Check to make sure we have the limit set.
|
|
// Account first
|
|
fooAcc, _ := s.LookupAccount(fooPub)
|
|
fooAcc.mu.RLock()
|
|
if fooAcc.mpay != 8 {
|
|
fooAcc.mu.RUnlock()
|
|
t.Fatalf("Expected account to have mpay of 8, got %d", fooAcc.mpay)
|
|
}
|
|
fooAcc.mu.RUnlock()
|
|
// Now test that the client has limits too.
|
|
c.mu.Lock()
|
|
if c.mpay != 8 {
|
|
c.mu.Unlock()
|
|
t.Fatalf("Expected client to have mpay of 10, got %d", c.mpay)
|
|
}
|
|
c.mu.Unlock()
|
|
|
|
c.parseAsync("PUB foo 4\r\nXXXX\r\nPING\r\n")
|
|
expectPong(t, cr)
|
|
|
|
c.parseAsync("PUB foo 10\r\nXXXXXXXXXX\r\nPING\r\n")
|
|
l, _ := cr.ReadString('\n')
|
|
if !strings.HasPrefix(l, "-ERR ") {
|
|
t.Fatalf("Expected an error")
|
|
}
|
|
if !strings.Contains(l, "Maximum Payload") {
|
|
t.Fatalf("Expected an ERR for max payload violation, got: %v", l)
|
|
}
|
|
}
|
|
|
|
func TestJWTAccountLimitsMaxPayloadButServerOverrides(t *testing.T) {
|
|
s := opTrustBasicSetup()
|
|
defer s.Shutdown()
|
|
buildMemAccResolver(s)
|
|
|
|
// override with server setting of 4.
|
|
opts := s.getOpts()
|
|
opts.MaxPayload = 4
|
|
|
|
okp, _ := nkeys.FromSeed(oSeed)
|
|
|
|
// Create accounts and imports/exports.
|
|
fooKP, _ := nkeys.CreateAccount()
|
|
fooPub, _ := fooKP.PublicKey()
|
|
fooAC := jwt.NewAccountClaims(fooPub)
|
|
fooAC.Limits.Payload = 8
|
|
fooJWT, err := fooAC.Encode(okp)
|
|
if err != nil {
|
|
t.Fatalf("Error generating account JWT: %v", err)
|
|
}
|
|
addAccountToMemResolver(s, fooPub, fooJWT)
|
|
|
|
// Create a client.
|
|
c, cr, cs := createClient(t, s, fooKP)
|
|
defer c.close()
|
|
|
|
c.parseAsync(cs)
|
|
expectPong(t, cr)
|
|
|
|
c.parseAsync("PUB foo 6\r\nXXXXXX\r\nPING\r\n")
|
|
l, _ := cr.ReadString('\n')
|
|
if !strings.HasPrefix(l, "-ERR ") {
|
|
t.Fatalf("Expected an error")
|
|
}
|
|
if !strings.Contains(l, "Maximum Payload") {
|
|
t.Fatalf("Expected an ERR for max payload violation, got: %v", l)
|
|
}
|
|
}
|
|
|
|
func TestJWTAccountLimitsMaxConns(t *testing.T) {
|
|
fooAC := newJWTTestAccountClaims()
|
|
fooAC.Limits.Conn = 8
|
|
s, fooKP, c, _ := setupJWTTestWitAccountClaims(t, fooAC, "+OK")
|
|
defer s.Shutdown()
|
|
defer c.close()
|
|
|
|
newClient := func(expPre string) *testAsyncClient {
|
|
t.Helper()
|
|
// Create a client.
|
|
c, cr, cs := createClient(t, s, fooKP)
|
|
c.parseAsync(cs)
|
|
l, _ := cr.ReadString('\n')
|
|
if !strings.HasPrefix(l, expPre) {
|
|
t.Fatalf("Expected a response starting with %q, got %q", expPre, l)
|
|
}
|
|
return c
|
|
}
|
|
|
|
// A connection is created in setupJWTTestWitAccountClaims(), so limit
|
|
// to 7 here (8 total).
|
|
for i := 0; i < 7; i++ {
|
|
c := newClient("PONG")
|
|
defer c.close()
|
|
}
|
|
// Now this one should fail.
|
|
c = newClient("-ERR ")
|
|
c.close()
|
|
}
|
|
|
|
// This will test that we can switch from a public export to a private
|
|
// one and back with export claims to make sure the claim update mechanism
|
|
// is working properly.
|
|
func TestJWTAccountServiceImportAuthSwitch(t *testing.T) {
|
|
s := opTrustBasicSetup()
|
|
defer s.Shutdown()
|
|
buildMemAccResolver(s)
|
|
|
|
okp, _ := nkeys.FromSeed(oSeed)
|
|
|
|
// Create accounts and imports/exports.
|
|
fooKP, _ := nkeys.CreateAccount()
|
|
fooPub, _ := fooKP.PublicKey()
|
|
fooAC := jwt.NewAccountClaims(fooPub)
|
|
serviceExport := &jwt.Export{Subject: "ngs.usage.*", Type: jwt.Service}
|
|
fooAC.Exports.Add(serviceExport)
|
|
fooJWT, err := fooAC.Encode(okp)
|
|
if err != nil {
|
|
t.Fatalf("Error generating account JWT: %v", err)
|
|
}
|
|
addAccountToMemResolver(s, fooPub, fooJWT)
|
|
|
|
barKP, _ := nkeys.CreateAccount()
|
|
barPub, _ := barKP.PublicKey()
|
|
barAC := jwt.NewAccountClaims(barPub)
|
|
serviceImport := &jwt.Import{Account: fooPub, Subject: "ngs.usage", To: "ngs.usage.DEREK", Type: jwt.Service}
|
|
barAC.Imports.Add(serviceImport)
|
|
barJWT, err := barAC.Encode(okp)
|
|
if err != nil {
|
|
t.Fatalf("Error generating account JWT: %v", err)
|
|
}
|
|
addAccountToMemResolver(s, barPub, barJWT)
|
|
|
|
// Create a client that will send the request
|
|
ca, cra, csa := createClient(t, s, barKP)
|
|
defer ca.close()
|
|
ca.parseAsync(csa)
|
|
expectPong(t, cra)
|
|
|
|
// Create the client that will respond to the requests.
|
|
cb, crb, csb := createClient(t, s, fooKP)
|
|
defer cb.close()
|
|
cb.parseAsync(csb)
|
|
expectPong(t, crb)
|
|
|
|
// Create Subscriber.
|
|
cb.parseAsync("SUB ngs.usage.* 1\r\nPING\r\n")
|
|
expectPong(t, crb)
|
|
|
|
// Send Request
|
|
ca.parseAsync("PUB ngs.usage 2\r\nhi\r\nPING\r\n")
|
|
expectPong(t, cra)
|
|
|
|
// We should receive the request mapped into our account. PING needed to flush.
|
|
cb.parseAsync("PING\r\n")
|
|
expectMsg(t, crb, "ngs.usage.DEREK", "hi")
|
|
|
|
// Now update to make the export private.
|
|
fooACPrivate := jwt.NewAccountClaims(fooPub)
|
|
serviceExport = &jwt.Export{Subject: "ngs.usage.*", Type: jwt.Service, TokenReq: true}
|
|
fooACPrivate.Exports.Add(serviceExport)
|
|
fooJWTPrivate, err := fooACPrivate.Encode(okp)
|
|
if err != nil {
|
|
t.Fatalf("Error generating account JWT: %v", err)
|
|
}
|
|
addAccountToMemResolver(s, fooPub, fooJWTPrivate)
|
|
acc, _ := s.LookupAccount(fooPub)
|
|
s.updateAccountClaims(acc, fooACPrivate)
|
|
|
|
// Send Another Request
|
|
ca.parseAsync("PUB ngs.usage 2\r\nhi\r\nPING\r\n")
|
|
expectPong(t, cra)
|
|
|
|
// We should not receive the request this time.
|
|
cb.parseAsync("PING\r\n")
|
|
expectPong(t, crb)
|
|
|
|
// Now put it back again to public and make sure it works again.
|
|
addAccountToMemResolver(s, fooPub, fooJWT)
|
|
s.updateAccountClaims(acc, fooAC)
|
|
|
|
// Send Request
|
|
ca.parseAsync("PUB ngs.usage 2\r\nhi\r\nPING\r\n")
|
|
expectPong(t, cra)
|
|
|
|
// We should receive the request mapped into our account. PING needed to flush.
|
|
cb.parseAsync("PING\r\n")
|
|
expectMsg(t, crb, "ngs.usage.DEREK", "hi")
|
|
}
|
|
|
|
func TestJWTAccountServiceImportExpires(t *testing.T) {
|
|
s := opTrustBasicSetup()
|
|
defer s.Shutdown()
|
|
buildMemAccResolver(s)
|
|
|
|
okp, _ := nkeys.FromSeed(oSeed)
|
|
|
|
// Create accounts and imports/exports.
|
|
fooKP, _ := nkeys.CreateAccount()
|
|
fooPub, _ := fooKP.PublicKey()
|
|
fooAC := jwt.NewAccountClaims(fooPub)
|
|
serviceExport := &jwt.Export{Subject: "foo", Type: jwt.Service}
|
|
|
|
fooAC.Exports.Add(serviceExport)
|
|
fooJWT, err := fooAC.Encode(okp)
|
|
if err != nil {
|
|
t.Fatalf("Error generating account JWT: %v", err)
|
|
}
|
|
addAccountToMemResolver(s, fooPub, fooJWT)
|
|
|
|
barKP, _ := nkeys.CreateAccount()
|
|
barPub, _ := barKP.PublicKey()
|
|
barAC := jwt.NewAccountClaims(barPub)
|
|
serviceImport := &jwt.Import{Account: fooPub, Subject: "foo", Type: jwt.Service}
|
|
|
|
barAC.Imports.Add(serviceImport)
|
|
barJWT, err := barAC.Encode(okp)
|
|
if err != nil {
|
|
t.Fatalf("Error generating account JWT: %v", err)
|
|
}
|
|
addAccountToMemResolver(s, barPub, barJWT)
|
|
|
|
// Create a client that will send the request
|
|
ca, cra, csa := createClient(t, s, barKP)
|
|
defer ca.close()
|
|
ca.parseAsync(csa)
|
|
expectPong(t, cra)
|
|
|
|
// Create the client that will respond to the requests.
|
|
cb, crb, csb := createClient(t, s, fooKP)
|
|
defer cb.close()
|
|
cb.parseAsync(csb)
|
|
expectPong(t, crb)
|
|
|
|
// Create Subscriber.
|
|
cb.parseAsync("SUB foo 1\r\nPING\r\n")
|
|
expectPong(t, crb)
|
|
|
|
// Send Request
|
|
ca.parseAsync("PUB foo 2\r\nhi\r\nPING\r\n")
|
|
expectPong(t, cra)
|
|
|
|
// We should receive the request. PING needed to flush.
|
|
cb.parseAsync("PING\r\n")
|
|
expectMsg(t, crb, "foo", "hi")
|
|
|
|
// Now update the exported service to require auth.
|
|
fooAC = jwt.NewAccountClaims(fooPub)
|
|
serviceExport = &jwt.Export{Subject: "foo", Type: jwt.Service, TokenReq: true}
|
|
|
|
fooAC.Exports.Add(serviceExport)
|
|
fooJWT, err = fooAC.Encode(okp)
|
|
if err != nil {
|
|
t.Fatalf("Error generating account JWT: %v", err)
|
|
}
|
|
addAccountToMemResolver(s, fooPub, fooJWT)
|
|
acc, _ := s.LookupAccount(fooPub)
|
|
s.updateAccountClaims(acc, fooAC)
|
|
|
|
// Send Another Request
|
|
ca.parseAsync("PUB foo 2\r\nhi\r\nPING\r\n")
|
|
expectPong(t, cra)
|
|
|
|
// We should not receive the request this time.
|
|
cb.parseAsync("PING\r\n")
|
|
expectPong(t, crb)
|
|
|
|
// Now get an activation token such that it will work, but will expire.
|
|
barAC = jwt.NewAccountClaims(barPub)
|
|
serviceImport = &jwt.Import{Account: fooPub, Subject: "foo", Type: jwt.Service}
|
|
|
|
now := time.Now()
|
|
activation := jwt.NewActivationClaims(barPub)
|
|
activation.ImportSubject = "foo"
|
|
activation.ImportType = jwt.Service
|
|
activation.IssuedAt = now.Add(-10 * time.Second).Unix()
|
|
activation.Expires = now.Add(time.Second).Round(time.Second).Unix()
|
|
actJWT, err := activation.Encode(fooKP)
|
|
if err != nil {
|
|
t.Fatalf("Error generating activation token: %v", err)
|
|
}
|
|
serviceImport.Token = actJWT
|
|
|
|
barAC.Imports.Add(serviceImport)
|
|
barJWT, err = barAC.Encode(okp)
|
|
if err != nil {
|
|
t.Fatalf("Error generating account JWT: %v", err)
|
|
}
|
|
addAccountToMemResolver(s, barPub, barJWT)
|
|
acc, _ = s.LookupAccount(barPub)
|
|
s.updateAccountClaims(acc, barAC)
|
|
|
|
// Now it should work again.
|
|
// Send Another Request
|
|
ca.parseAsync("PUB foo 3\r\nhi2\r\nPING\r\n")
|
|
expectPong(t, cra)
|
|
|
|
// We should receive the request. PING needed to flush.
|
|
cb.parseAsync("PING\r\n")
|
|
expectMsg(t, crb, "foo", "hi2")
|
|
|
|
// Now wait for it to expire, then retry.
|
|
waitTime := time.Duration(activation.Expires-time.Now().Unix()) * time.Second
|
|
time.Sleep(waitTime + 250*time.Millisecond)
|
|
|
|
// Send Another Request
|
|
ca.parseAsync("PUB foo 3\r\nhi3\r\nPING\r\n")
|
|
expectPong(t, cra)
|
|
|
|
// We should NOT receive the request. PING needed to flush.
|
|
cb.parseAsync("PING\r\n")
|
|
expectPong(t, crb)
|
|
}
|
|
|
|
func TestAccountURLResolver(t *testing.T) {
|
|
for _, test := range []struct {
|
|
name string
|
|
useTLS bool
|
|
}{
|
|
{"plain", false},
|
|
{"tls", true},
|
|
} {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
kp, _ := nkeys.FromSeed(oSeed)
|
|
akp, _ := nkeys.CreateAccount()
|
|
apub, _ := akp.PublicKey()
|
|
nac := jwt.NewAccountClaims(apub)
|
|
ajwt, err := nac.Encode(kp)
|
|
if err != nil {
|
|
t.Fatalf("Error generating account JWT: %v", err)
|
|
}
|
|
|
|
hf := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.Write([]byte(ajwt))
|
|
})
|
|
var ts *httptest.Server
|
|
if test.useTLS {
|
|
ts = httptest.NewTLSServer(hf)
|
|
} else {
|
|
ts = httptest.NewServer(hf)
|
|
}
|
|
defer ts.Close()
|
|
|
|
confTemplate := `
|
|
listen: -1
|
|
resolver: URL("%s/ngs/v1/accounts/jwt/")
|
|
resolver_tls {
|
|
insecure: true
|
|
}
|
|
`
|
|
conf := createConfFile(t, []byte(fmt.Sprintf(confTemplate, ts.URL)))
|
|
defer os.Remove(conf)
|
|
|
|
s, opts := RunServerWithConfig(conf)
|
|
pub, _ := kp.PublicKey()
|
|
opts.TrustedKeys = []string{pub}
|
|
defer s.Shutdown()
|
|
|
|
acc, _ := s.LookupAccount(apub)
|
|
if acc == nil {
|
|
t.Fatalf("Expected to receive an account")
|
|
}
|
|
if acc.Name != apub {
|
|
t.Fatalf("Account name did not match claim key")
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestAccountURLResolverTimeout(t *testing.T) {
|
|
kp, _ := nkeys.FromSeed(oSeed)
|
|
akp, _ := nkeys.CreateAccount()
|
|
apub, _ := akp.PublicKey()
|
|
nac := jwt.NewAccountClaims(apub)
|
|
ajwt, err := nac.Encode(kp)
|
|
if err != nil {
|
|
t.Fatalf("Error generating account JWT: %v", err)
|
|
}
|
|
|
|
basePath := "/ngs/v1/accounts/jwt/"
|
|
|
|
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
if r.URL.Path == basePath {
|
|
w.Write([]byte("ok"))
|
|
return
|
|
}
|
|
// Purposely be slow on account lookup.
|
|
time.Sleep(200 * time.Millisecond)
|
|
w.Write([]byte(ajwt))
|
|
}))
|
|
defer ts.Close()
|
|
|
|
confTemplate := `
|
|
listen: -1
|
|
resolver: URL("%s%s")
|
|
`
|
|
conf := createConfFile(t, []byte(fmt.Sprintf(confTemplate, ts.URL, basePath)))
|
|
defer os.Remove(conf)
|
|
|
|
s, opts := RunServerWithConfig(conf)
|
|
pub, _ := kp.PublicKey()
|
|
opts.TrustedKeys = []string{pub}
|
|
defer s.Shutdown()
|
|
|
|
// Lower default timeout to speed-up test
|
|
s.AccountResolver().(*URLAccResolver).c.Timeout = 50 * time.Millisecond
|
|
|
|
acc, _ := s.LookupAccount(apub)
|
|
if acc != nil {
|
|
t.Fatalf("Expected to not receive an account due to timeout")
|
|
}
|
|
}
|
|
|
|
func TestAccountURLResolverNoFetchOnReload(t *testing.T) {
|
|
kp, _ := nkeys.FromSeed(oSeed)
|
|
akp, _ := nkeys.CreateAccount()
|
|
apub, _ := akp.PublicKey()
|
|
nac := jwt.NewAccountClaims(apub)
|
|
ajwt, err := nac.Encode(kp)
|
|
if err != nil {
|
|
t.Fatalf("Error generating account JWT: %v", err)
|
|
}
|
|
|
|
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.Write([]byte(ajwt))
|
|
}))
|
|
defer ts.Close()
|
|
|
|
confTemplate := `
|
|
listen: -1
|
|
resolver: URL("%s/ngs/v1/accounts/jwt/")
|
|
`
|
|
conf := createConfFile(t, []byte(fmt.Sprintf(confTemplate, ts.URL)))
|
|
defer os.Remove(conf)
|
|
|
|
s, _ := RunServerWithConfig(conf)
|
|
defer s.Shutdown()
|
|
|
|
acc, _ := s.LookupAccount(apub)
|
|
if acc == nil {
|
|
t.Fatalf("Expected to receive an account")
|
|
}
|
|
|
|
// Reload would produce a DATA race during the DeepEqual check for the account resolver,
|
|
// so close the current one and we will create a new one that keeps track of fetch calls.
|
|
ts.Close()
|
|
|
|
fetch := int32(0)
|
|
ts = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
atomic.AddInt32(&fetch, 1)
|
|
w.Write([]byte(ajwt))
|
|
}))
|
|
defer ts.Close()
|
|
|
|
changeCurrentConfigContentWithNewContent(t, conf, []byte(fmt.Sprintf(confTemplate, ts.URL)))
|
|
|
|
if err := s.Reload(); err != nil {
|
|
t.Fatalf("Error on reload: %v", err)
|
|
}
|
|
if atomic.LoadInt32(&fetch) != 0 {
|
|
t.Fatalf("Fetch invoked during reload")
|
|
}
|
|
|
|
// Now stop the resolver and make sure that on startup, we report URL resolver failure
|
|
s.Shutdown()
|
|
s = nil
|
|
ts.Close()
|
|
|
|
opts := LoadConfig(conf)
|
|
if s, err := NewServer(opts); err == nil || !strings.Contains(err.Error(), "could not fetch") {
|
|
if s != nil {
|
|
s.Shutdown()
|
|
}
|
|
t.Fatalf("Expected error regarding account resolver, got %v", err)
|
|
}
|
|
}
|
|
|
|
func TestJWTUserSigningKey(t *testing.T) {
|
|
s := opTrustBasicSetup()
|
|
defer s.Shutdown()
|
|
|
|
// Check to make sure we would have an authTimer
|
|
if !s.info.AuthRequired {
|
|
t.Fatalf("Expect the server to require auth")
|
|
}
|
|
|
|
c, cr, _ := newClientForServer(s)
|
|
defer c.close()
|
|
// Don't send jwt field, should fail.
|
|
c.parseAsync("CONNECT {\"verbose\":true,\"pedantic\":true}\r\nPING\r\n")
|
|
l, _ := cr.ReadString('\n')
|
|
if !strings.HasPrefix(l, "-ERR ") {
|
|
t.Fatalf("Expected an error")
|
|
}
|
|
|
|
okp, _ := nkeys.FromSeed(oSeed)
|
|
|
|
// Create an account
|
|
akp, _ := nkeys.CreateAccount()
|
|
apub, _ := akp.PublicKey()
|
|
|
|
// Create a signing key for the account
|
|
askp, _ := nkeys.CreateAccount()
|
|
aspub, _ := askp.PublicKey()
|
|
|
|
nac := jwt.NewAccountClaims(apub)
|
|
ajwt, err := nac.Encode(okp)
|
|
if err != nil {
|
|
t.Fatalf("Error generating account JWT: %v", err)
|
|
}
|
|
|
|
// Create a client with the account signing key
|
|
c, cr, cs := createClientWithIssuer(t, s, askp, apub)
|
|
defer c.close()
|
|
|
|
// PING needed to flush the +OK/-ERR to us.
|
|
// This should fail too since no account resolver is defined.
|
|
c.parseAsync(cs)
|
|
l, _ = cr.ReadString('\n')
|
|
if !strings.HasPrefix(l, "-ERR ") {
|
|
t.Fatalf("Expected an error")
|
|
}
|
|
|
|
// Ok now let's walk through and make sure all is good.
|
|
// We will set the account resolver by hand to a memory resolver.
|
|
buildMemAccResolver(s)
|
|
addAccountToMemResolver(s, apub, ajwt)
|
|
|
|
// Create a client with a signing key
|
|
c, cr, cs = createClientWithIssuer(t, s, askp, apub)
|
|
defer c.close()
|
|
// should fail because the signing key is not known
|
|
c.parseAsync(cs)
|
|
l, _ = cr.ReadString('\n')
|
|
if !strings.HasPrefix(l, "-ERR ") {
|
|
t.Fatalf("Expected an error: %v", l)
|
|
}
|
|
|
|
// add a signing key
|
|
nac.SigningKeys.Add(aspub)
|
|
// update the memory resolver
|
|
acc, _ := s.LookupAccount(apub)
|
|
s.updateAccountClaims(acc, nac)
|
|
|
|
// Create a client with a signing key
|
|
c, cr, cs = createClientWithIssuer(t, s, askp, apub)
|
|
defer c.close()
|
|
|
|
// expect this to work
|
|
c.parseAsync(cs)
|
|
l, _ = cr.ReadString('\n')
|
|
if !strings.HasPrefix(l, "PONG") {
|
|
t.Fatalf("Expected a PONG, got %q", l)
|
|
}
|
|
|
|
isClosed := func() bool {
|
|
c.mu.Lock()
|
|
defer c.mu.Unlock()
|
|
return c.isClosed()
|
|
}
|
|
|
|
if isClosed() {
|
|
t.Fatal("expected client to be alive")
|
|
}
|
|
// remove the signing key should bounce client
|
|
nac.SigningKeys = nil
|
|
acc, _ = s.LookupAccount(apub)
|
|
s.updateAccountClaims(acc, nac)
|
|
|
|
if !isClosed() {
|
|
t.Fatal("expected client to be gone")
|
|
}
|
|
}
|
|
|
|
func TestJWTAccountImportSignerRemoved(t *testing.T) {
|
|
s := opTrustBasicSetup()
|
|
defer s.Shutdown()
|
|
buildMemAccResolver(s)
|
|
|
|
okp, _ := nkeys.FromSeed(oSeed)
|
|
|
|
// Exporter keys
|
|
srvKP, _ := nkeys.CreateAccount()
|
|
srvPK, _ := srvKP.PublicKey()
|
|
srvSignerKP, _ := nkeys.CreateAccount()
|
|
srvSignerPK, _ := srvSignerKP.PublicKey()
|
|
|
|
// Importer keys
|
|
clientKP, _ := nkeys.CreateAccount()
|
|
clientPK, _ := clientKP.PublicKey()
|
|
|
|
createSrvJwt := func(signingKeys ...string) (string, *jwt.AccountClaims) {
|
|
ac := jwt.NewAccountClaims(srvPK)
|
|
ac.SigningKeys.Add(signingKeys...)
|
|
ac.Exports.Add(&jwt.Export{Subject: "foo", Type: jwt.Service, TokenReq: true})
|
|
ac.Exports.Add(&jwt.Export{Subject: "bar", Type: jwt.Stream, TokenReq: true})
|
|
token, err := ac.Encode(okp)
|
|
if err != nil {
|
|
t.Fatalf("Error generating exporter JWT: %v", err)
|
|
}
|
|
return token, ac
|
|
}
|
|
|
|
createImportToken := func(sub string, kind jwt.ExportType) string {
|
|
actC := jwt.NewActivationClaims(clientPK)
|
|
actC.IssuerAccount = srvPK
|
|
actC.ImportType = kind
|
|
actC.ImportSubject = jwt.Subject(sub)
|
|
token, err := actC.Encode(srvSignerKP)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return token
|
|
}
|
|
|
|
createClientJwt := func() string {
|
|
ac := jwt.NewAccountClaims(clientPK)
|
|
ac.Imports.Add(&jwt.Import{Account: srvPK, Subject: "foo", Type: jwt.Service, Token: createImportToken("foo", jwt.Service)})
|
|
ac.Imports.Add(&jwt.Import{Account: srvPK, Subject: "bar", Type: jwt.Stream, Token: createImportToken("bar", jwt.Stream)})
|
|
token, err := ac.Encode(okp)
|
|
if err != nil {
|
|
t.Fatalf("Error generating importer JWT: %v", err)
|
|
}
|
|
return token
|
|
}
|
|
|
|
srvJWT, _ := createSrvJwt(srvSignerPK)
|
|
addAccountToMemResolver(s, srvPK, srvJWT)
|
|
|
|
clientJWT := createClientJwt()
|
|
addAccountToMemResolver(s, clientPK, clientJWT)
|
|
|
|
// Create a client that will send the request
|
|
client, clientReader, clientCS := createClient(t, s, clientKP)
|
|
defer client.close()
|
|
client.parseAsync(clientCS)
|
|
expectPong(t, clientReader)
|
|
|
|
checkShadow := func(expected int) {
|
|
t.Helper()
|
|
client.mu.Lock()
|
|
defer client.mu.Unlock()
|
|
sub := client.subs["1"]
|
|
count := 0
|
|
if sub != nil {
|
|
count = len(sub.shadow)
|
|
}
|
|
if count != expected {
|
|
t.Fatalf("Expected shadows to be %d, got %d", expected, count)
|
|
}
|
|
}
|
|
|
|
checkShadow(0)
|
|
// Create the client that will respond to the requests.
|
|
srv, srvReader, srvCS := createClient(t, s, srvKP)
|
|
defer srv.close()
|
|
srv.parseAsync(srvCS)
|
|
expectPong(t, srvReader)
|
|
|
|
// Create Subscriber.
|
|
srv.parseAsync("SUB foo 1\r\nPING\r\n")
|
|
expectPong(t, srvReader)
|
|
|
|
// Send Request
|
|
client.parseAsync("PUB foo 2\r\nhi\r\nPING\r\n")
|
|
expectPong(t, clientReader)
|
|
|
|
// We should receive the request. PING needed to flush.
|
|
srv.parseAsync("PING\r\n")
|
|
expectMsg(t, srvReader, "foo", "hi")
|
|
|
|
client.parseAsync("SUB bar 1\r\nPING\r\n")
|
|
expectPong(t, clientReader)
|
|
checkShadow(1)
|
|
|
|
srv.parseAsync("PUB bar 2\r\nhi\r\nPING\r\n")
|
|
expectPong(t, srvReader)
|
|
|
|
// We should receive from stream. PING needed to flush.
|
|
client.parseAsync("PING\r\n")
|
|
expectMsg(t, clientReader, "bar", "hi")
|
|
|
|
// Now update the exported service no signer
|
|
srvJWT, srvAC := createSrvJwt()
|
|
addAccountToMemResolver(s, srvPK, srvJWT)
|
|
acc, _ := s.LookupAccount(srvPK)
|
|
s.updateAccountClaims(acc, srvAC)
|
|
|
|
// Send Another Request
|
|
client.parseAsync("PUB foo 2\r\nhi\r\nPING\r\n")
|
|
expectPong(t, clientReader)
|
|
|
|
// We should not receive the request this time.
|
|
srv.parseAsync("PING\r\n")
|
|
expectPong(t, srvReader)
|
|
|
|
// Publish on the stream
|
|
srv.parseAsync("PUB bar 2\r\nhi\r\nPING\r\n")
|
|
expectPong(t, srvReader)
|
|
|
|
// We should not receive from the stream this time
|
|
client.parseAsync("PING\r\n")
|
|
expectPong(t, clientReader)
|
|
checkShadow(0)
|
|
}
|
|
|
|
func TestJWTAccountImportSignerDeadlock(t *testing.T) {
|
|
s := opTrustBasicSetup()
|
|
defer s.Shutdown()
|
|
buildMemAccResolver(s)
|
|
|
|
okp, _ := nkeys.FromSeed(oSeed)
|
|
|
|
// Exporter keys
|
|
srvKP, _ := nkeys.CreateAccount()
|
|
srvPK, _ := srvKP.PublicKey()
|
|
srvSignerKP, _ := nkeys.CreateAccount()
|
|
srvSignerPK, _ := srvSignerKP.PublicKey()
|
|
|
|
// Importer keys
|
|
clientKP, _ := nkeys.CreateAccount()
|
|
clientPK, _ := clientKP.PublicKey()
|
|
|
|
createSrvJwt := func(signingKeys ...string) (string, *jwt.AccountClaims) {
|
|
ac := jwt.NewAccountClaims(srvPK)
|
|
ac.SigningKeys.Add(signingKeys...)
|
|
ac.Exports.Add(&jwt.Export{Subject: "foo", Type: jwt.Service, TokenReq: true})
|
|
ac.Exports.Add(&jwt.Export{Subject: "bar", Type: jwt.Stream, TokenReq: true})
|
|
token, err := ac.Encode(okp)
|
|
if err != nil {
|
|
t.Fatalf("Error generating exporter JWT: %v", err)
|
|
}
|
|
return token, ac
|
|
}
|
|
|
|
createImportToken := func(sub string, kind jwt.ExportType) string {
|
|
actC := jwt.NewActivationClaims(clientPK)
|
|
actC.IssuerAccount = srvPK
|
|
actC.ImportType = kind
|
|
actC.ImportSubject = jwt.Subject(sub)
|
|
token, err := actC.Encode(srvSignerKP)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return token
|
|
}
|
|
|
|
createClientJwt := func() string {
|
|
ac := jwt.NewAccountClaims(clientPK)
|
|
ac.Imports.Add(&jwt.Import{Account: srvPK, Subject: "foo", Type: jwt.Service, Token: createImportToken("foo", jwt.Service)})
|
|
ac.Imports.Add(&jwt.Import{Account: srvPK, Subject: "bar", Type: jwt.Stream, Token: createImportToken("bar", jwt.Stream)})
|
|
token, err := ac.Encode(okp)
|
|
if err != nil {
|
|
t.Fatalf("Error generating importer JWT: %v", err)
|
|
}
|
|
return token
|
|
}
|
|
|
|
srvJWT, _ := createSrvJwt(srvSignerPK)
|
|
addAccountToMemResolver(s, srvPK, srvJWT)
|
|
|
|
clientJWT := createClientJwt()
|
|
addAccountToMemResolver(s, clientPK, clientJWT)
|
|
|
|
acc, _ := s.LookupAccount(srvPK)
|
|
// Have a go routine that constantly gets/releases the acc's write lock.
|
|
// There was a bug that could cause AddServiceImportWithClaim to deadlock.
|
|
ch := make(chan bool, 1)
|
|
wg := sync.WaitGroup{}
|
|
wg.Add(1)
|
|
go func() {
|
|
defer wg.Done()
|
|
for {
|
|
select {
|
|
case <-ch:
|
|
return
|
|
default:
|
|
acc.mu.Lock()
|
|
acc.mu.Unlock()
|
|
time.Sleep(time.Millisecond)
|
|
}
|
|
}
|
|
}()
|
|
|
|
// Create a client that will send the request
|
|
client, clientReader, clientCS := createClient(t, s, clientKP)
|
|
defer client.close()
|
|
client.parseAsync(clientCS)
|
|
expectPong(t, clientReader)
|
|
|
|
close(ch)
|
|
wg.Wait()
|
|
}
|
|
|
|
func TestJWTAccountImportWrongIssuerAccount(t *testing.T) {
|
|
s := opTrustBasicSetup()
|
|
defer s.Shutdown()
|
|
buildMemAccResolver(s)
|
|
|
|
l := &captureErrorLogger{errCh: make(chan string, 2)}
|
|
s.SetLogger(l, false, false)
|
|
|
|
okp, _ := nkeys.FromSeed(oSeed)
|
|
|
|
// Exporter keys
|
|
srvKP, _ := nkeys.CreateAccount()
|
|
srvPK, _ := srvKP.PublicKey()
|
|
srvSignerKP, _ := nkeys.CreateAccount()
|
|
srvSignerPK, _ := srvSignerKP.PublicKey()
|
|
|
|
// Importer keys
|
|
clientKP, _ := nkeys.CreateAccount()
|
|
clientPK, _ := clientKP.PublicKey()
|
|
|
|
createSrvJwt := func(signingKeys ...string) (string, *jwt.AccountClaims) {
|
|
ac := jwt.NewAccountClaims(srvPK)
|
|
ac.SigningKeys.Add(signingKeys...)
|
|
ac.Exports.Add(&jwt.Export{Subject: "foo", Type: jwt.Service, TokenReq: true})
|
|
ac.Exports.Add(&jwt.Export{Subject: "bar", Type: jwt.Stream, TokenReq: true})
|
|
token, err := ac.Encode(okp)
|
|
if err != nil {
|
|
t.Fatalf("Error generating exporter JWT: %v", err)
|
|
}
|
|
return token, ac
|
|
}
|
|
|
|
createImportToken := func(sub string, kind jwt.ExportType) string {
|
|
actC := jwt.NewActivationClaims(clientPK)
|
|
// Reference ourselves, which is wrong.
|
|
actC.IssuerAccount = clientPK
|
|
actC.ImportType = kind
|
|
actC.ImportSubject = jwt.Subject(sub)
|
|
token, err := actC.Encode(srvSignerKP)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return token
|
|
}
|
|
|
|
createClientJwt := func() string {
|
|
ac := jwt.NewAccountClaims(clientPK)
|
|
ac.Imports.Add(&jwt.Import{Account: srvPK, Subject: "foo", Type: jwt.Service, Token: createImportToken("foo", jwt.Service)})
|
|
ac.Imports.Add(&jwt.Import{Account: srvPK, Subject: "bar", Type: jwt.Stream, Token: createImportToken("bar", jwt.Stream)})
|
|
token, err := ac.Encode(okp)
|
|
if err != nil {
|
|
t.Fatalf("Error generating importer JWT: %v", err)
|
|
}
|
|
return token
|
|
}
|
|
|
|
srvJWT, _ := createSrvJwt(srvSignerPK)
|
|
addAccountToMemResolver(s, srvPK, srvJWT)
|
|
|
|
clientJWT := createClientJwt()
|
|
addAccountToMemResolver(s, clientPK, clientJWT)
|
|
|
|
// Create a client that will send the request
|
|
client, clientReader, clientCS := createClient(t, s, clientKP)
|
|
defer client.close()
|
|
client.parseAsync(clientCS)
|
|
expectPong(t, clientReader)
|
|
|
|
for i := 0; i < 2; i++ {
|
|
select {
|
|
case e := <-l.errCh:
|
|
if !strings.HasPrefix(e, fmt.Sprintf("Invalid issuer account %q in activation claim", clientPK)) {
|
|
t.Fatalf("Unexpected error: %v", e)
|
|
}
|
|
case <-time.After(2 * time.Second):
|
|
t.Fatalf("Did not get error regarding issuer account")
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestJWTUserRevokedOnAccountUpdate(t *testing.T) {
|
|
nac := newJWTTestAccountClaims()
|
|
s, akp, c, cr := setupJWTTestWitAccountClaims(t, nac, "+OK")
|
|
defer s.Shutdown()
|
|
defer c.close()
|
|
|
|
expectPong(t, cr)
|
|
|
|
okp, _ := nkeys.FromSeed(oSeed)
|
|
apub, _ := akp.PublicKey()
|
|
|
|
c.mu.Lock()
|
|
pub := c.user.Nkey
|
|
c.mu.Unlock()
|
|
|
|
// Now revoke the user.
|
|
nac.Revoke(pub)
|
|
|
|
ajwt, err := nac.Encode(okp)
|
|
if err != nil {
|
|
t.Fatalf("Error generating account JWT: %v", err)
|
|
}
|
|
|
|
// Update the account on the server.
|
|
addAccountToMemResolver(s, apub, ajwt)
|
|
acc, err := s.LookupAccount(apub)
|
|
if err != nil {
|
|
t.Fatalf("Error looking up the account: %v", err)
|
|
}
|
|
|
|
// This is simulating a system update for the account claims.
|
|
go s.updateAccountWithClaimJWT(acc, ajwt)
|
|
|
|
l, _ := cr.ReadString('\n')
|
|
if !strings.HasPrefix(l, "-ERR ") {
|
|
t.Fatalf("Expected an error")
|
|
}
|
|
if !strings.Contains(l, "Revoked") {
|
|
t.Fatalf("Expected 'Revoked' to be in the error")
|
|
}
|
|
}
|
|
|
|
func TestJWTUserRevoked(t *testing.T) {
|
|
okp, _ := nkeys.FromSeed(oSeed)
|
|
|
|
// Create a new user that we will make sure has been revoked.
|
|
nkp, _ := nkeys.CreateUser()
|
|
pub, _ := nkp.PublicKey()
|
|
nuc := jwt.NewUserClaims(pub)
|
|
|
|
akp, _ := nkeys.CreateAccount()
|
|
apub, _ := akp.PublicKey()
|
|
nac := jwt.NewAccountClaims(apub)
|
|
// Revoke the user right away.
|
|
nac.Revoke(pub)
|
|
ajwt, err := nac.Encode(okp)
|
|
if err != nil {
|
|
t.Fatalf("Error generating account JWT: %v", err)
|
|
}
|
|
|
|
// Sign for the user.
|
|
jwt, err := nuc.Encode(akp)
|
|
if err != nil {
|
|
t.Fatalf("Error generating user JWT: %v", err)
|
|
}
|
|
|
|
s := opTrustBasicSetup()
|
|
defer s.Shutdown()
|
|
buildMemAccResolver(s)
|
|
addAccountToMemResolver(s, apub, ajwt)
|
|
|
|
c, cr, l := newClientForServer(s)
|
|
defer c.close()
|
|
|
|
// Sign Nonce
|
|
var info nonceInfo
|
|
json.Unmarshal([]byte(l[5:]), &info)
|
|
sigraw, _ := nkp.Sign([]byte(info.Nonce))
|
|
sig := base64.RawURLEncoding.EncodeToString(sigraw)
|
|
|
|
// PING needed to flush the +OK/-ERR to us.
|
|
cs := fmt.Sprintf("CONNECT {\"jwt\":%q,\"sig\":\"%s\"}\r\nPING\r\n", jwt, sig)
|
|
|
|
c.parseAsync(cs)
|
|
|
|
l, _ = cr.ReadString('\n')
|
|
if !strings.HasPrefix(l, "-ERR ") {
|
|
t.Fatalf("Expected an error")
|
|
}
|
|
if !strings.Contains(l, "Authorization") {
|
|
t.Fatalf("Expected 'Revoked' to be in the error")
|
|
}
|
|
}
|
|
|
|
// Test that an account update that revokes an import authorization cancels the import.
|
|
func TestJWTImportTokenRevokedAfter(t *testing.T) {
|
|
s := opTrustBasicSetup()
|
|
defer s.Shutdown()
|
|
buildMemAccResolver(s)
|
|
|
|
okp, _ := nkeys.FromSeed(oSeed)
|
|
|
|
// Create accounts and imports/exports.
|
|
fooKP, _ := nkeys.CreateAccount()
|
|
fooPub, _ := fooKP.PublicKey()
|
|
fooAC := jwt.NewAccountClaims(fooPub)
|
|
|
|
// Now create Exports.
|
|
export := &jwt.Export{Subject: "foo.private", Type: jwt.Stream, TokenReq: true}
|
|
|
|
fooAC.Exports.Add(export)
|
|
fooJWT, err := fooAC.Encode(okp)
|
|
if err != nil {
|
|
t.Fatalf("Error generating account JWT: %v", err)
|
|
}
|
|
|
|
addAccountToMemResolver(s, fooPub, fooJWT)
|
|
|
|
barKP, _ := nkeys.CreateAccount()
|
|
barPub, _ := barKP.PublicKey()
|
|
barAC := jwt.NewAccountClaims(barPub)
|
|
simport := &jwt.Import{Account: fooPub, Subject: "foo.private", Type: jwt.Stream}
|
|
|
|
activation := jwt.NewActivationClaims(barPub)
|
|
activation.ImportSubject = "foo.private"
|
|
activation.ImportType = jwt.Stream
|
|
actJWT, err := activation.Encode(fooKP)
|
|
if err != nil {
|
|
t.Fatalf("Error generating activation token: %v", err)
|
|
}
|
|
|
|
simport.Token = actJWT
|
|
barAC.Imports.Add(simport)
|
|
barJWT, err := barAC.Encode(okp)
|
|
if err != nil {
|
|
t.Fatalf("Error generating account JWT: %v", err)
|
|
}
|
|
addAccountToMemResolver(s, barPub, barJWT)
|
|
|
|
// Now revoke the export.
|
|
decoded, _ := jwt.DecodeActivationClaims(actJWT)
|
|
export.Revoke(decoded.Subject)
|
|
|
|
fooJWT, err = fooAC.Encode(okp)
|
|
if err != nil {
|
|
t.Fatalf("Error generating account JWT: %v", err)
|
|
}
|
|
|
|
addAccountToMemResolver(s, fooPub, fooJWT)
|
|
|
|
fooAcc, _ := s.LookupAccount(fooPub)
|
|
if fooAcc == nil {
|
|
t.Fatalf("Expected to retrieve the account")
|
|
}
|
|
|
|
// Now lookup bar account and make sure it was revoked.
|
|
acc, _ := s.LookupAccount(barPub)
|
|
if acc == nil {
|
|
t.Fatalf("Expected to retrieve the account")
|
|
}
|
|
if les := len(acc.imports.streams); les != 0 {
|
|
t.Fatalf("Expected imports streams len of 0, got %d", les)
|
|
}
|
|
}
|
|
|
|
// Test that an account update that revokes an import authorization cancels the import.
|
|
func TestJWTImportTokenRevokedBefore(t *testing.T) {
|
|
s := opTrustBasicSetup()
|
|
defer s.Shutdown()
|
|
buildMemAccResolver(s)
|
|
|
|
okp, _ := nkeys.FromSeed(oSeed)
|
|
|
|
// Create accounts and imports/exports.
|
|
fooKP, _ := nkeys.CreateAccount()
|
|
fooPub, _ := fooKP.PublicKey()
|
|
fooAC := jwt.NewAccountClaims(fooPub)
|
|
|
|
// Now create Exports.
|
|
export := &jwt.Export{Subject: "foo.private", Type: jwt.Stream, TokenReq: true}
|
|
|
|
fooAC.Exports.Add(export)
|
|
|
|
// Import account
|
|
barKP, _ := nkeys.CreateAccount()
|
|
barPub, _ := barKP.PublicKey()
|
|
barAC := jwt.NewAccountClaims(barPub)
|
|
simport := &jwt.Import{Account: fooPub, Subject: "foo.private", Type: jwt.Stream}
|
|
|
|
activation := jwt.NewActivationClaims(barPub)
|
|
activation.ImportSubject = "foo.private"
|
|
activation.ImportType = jwt.Stream
|
|
actJWT, err := activation.Encode(fooKP)
|
|
if err != nil {
|
|
t.Fatalf("Error generating activation token: %v", err)
|
|
}
|
|
|
|
simport.Token = actJWT
|
|
barAC.Imports.Add(simport)
|
|
|
|
// Now revoke the export.
|
|
decoded, _ := jwt.DecodeActivationClaims(actJWT)
|
|
export.Revoke(decoded.Subject)
|
|
|
|
fooJWT, err := fooAC.Encode(okp)
|
|
if err != nil {
|
|
t.Fatalf("Error generating account JWT: %v", err)
|
|
}
|
|
|
|
addAccountToMemResolver(s, fooPub, fooJWT)
|
|
|
|
barJWT, err := barAC.Encode(okp)
|
|
if err != nil {
|
|
t.Fatalf("Error generating account JWT: %v", err)
|
|
}
|
|
addAccountToMemResolver(s, barPub, barJWT)
|
|
|
|
fooAcc, _ := s.LookupAccount(fooPub)
|
|
if fooAcc == nil {
|
|
t.Fatalf("Expected to retrieve the account")
|
|
}
|
|
|
|
// Now lookup bar account and make sure it was revoked.
|
|
acc, _ := s.LookupAccount(barPub)
|
|
if acc == nil {
|
|
t.Fatalf("Expected to retrieve the account")
|
|
}
|
|
if les := len(acc.imports.streams); les != 0 {
|
|
t.Fatalf("Expected imports streams len of 0, got %d", les)
|
|
}
|
|
}
|
|
|
|
func TestJWTCircularAccountServiceImport(t *testing.T) {
|
|
s := opTrustBasicSetup()
|
|
defer s.Shutdown()
|
|
buildMemAccResolver(s)
|
|
|
|
okp, _ := nkeys.FromSeed(oSeed)
|
|
|
|
// Create accounts
|
|
fooKP, _ := nkeys.CreateAccount()
|
|
fooPub, _ := fooKP.PublicKey()
|
|
fooAC := jwt.NewAccountClaims(fooPub)
|
|
|
|
barKP, _ := nkeys.CreateAccount()
|
|
barPub, _ := barKP.PublicKey()
|
|
barAC := jwt.NewAccountClaims(barPub)
|
|
|
|
// Create service export/import for account foo
|
|
serviceExport := &jwt.Export{Subject: "foo", Type: jwt.Service, TokenReq: true}
|
|
serviceImport := &jwt.Import{Account: barPub, Subject: "bar", Type: jwt.Service}
|
|
|
|
fooAC.Exports.Add(serviceExport)
|
|
fooAC.Imports.Add(serviceImport)
|
|
fooJWT, err := fooAC.Encode(okp)
|
|
if err != nil {
|
|
t.Fatalf("Error generating account JWT: %v", err)
|
|
}
|
|
|
|
addAccountToMemResolver(s, fooPub, fooJWT)
|
|
|
|
// Create service export/import for account bar
|
|
serviceExport = &jwt.Export{Subject: "bar", Type: jwt.Service, TokenReq: true}
|
|
serviceImport = &jwt.Import{Account: fooPub, Subject: "foo", Type: jwt.Service}
|
|
|
|
barAC.Exports.Add(serviceExport)
|
|
barAC.Imports.Add(serviceImport)
|
|
barJWT, err := barAC.Encode(okp)
|
|
if err != nil {
|
|
t.Fatalf("Error generating account JWT: %v", err)
|
|
}
|
|
|
|
addAccountToMemResolver(s, barPub, barJWT)
|
|
|
|
c, cr, cs := createClient(t, s, fooKP)
|
|
defer c.close()
|
|
|
|
c.parseAsync(cs)
|
|
expectPong(t, cr)
|
|
|
|
c.parseAsync("SUB foo 1\r\nPING\r\n")
|
|
expectPong(t, cr)
|
|
}
|
|
|
|
// This test ensures that connected clients are properly evicted
|
|
// (no deadlock) if the max conns of an account has been lowered
|
|
// and the account is being updated (following expiration during
|
|
// a lookup).
|
|
func TestJWTAccountLimitsMaxConnsAfterExpired(t *testing.T) {
|
|
s := opTrustBasicSetup()
|
|
defer s.Shutdown()
|
|
buildMemAccResolver(s)
|
|
|
|
okp, _ := nkeys.FromSeed(oSeed)
|
|
|
|
// Create accounts and imports/exports.
|
|
fooKP, _ := nkeys.CreateAccount()
|
|
fooPub, _ := fooKP.PublicKey()
|
|
fooAC := jwt.NewAccountClaims(fooPub)
|
|
fooAC.Limits.Conn = 10
|
|
fooJWT, err := fooAC.Encode(okp)
|
|
if err != nil {
|
|
t.Fatalf("Error generating account JWT: %v", err)
|
|
}
|
|
addAccountToMemResolver(s, fooPub, fooJWT)
|
|
|
|
newClient := func(expPre string) *testAsyncClient {
|
|
t.Helper()
|
|
// Create a client.
|
|
c, cr, cs := createClient(t, s, fooKP)
|
|
c.parseAsync(cs)
|
|
l, _ := cr.ReadString('\n')
|
|
if !strings.HasPrefix(l, expPre) {
|
|
t.Fatalf("Expected a response starting with %q, got %q", expPre, l)
|
|
}
|
|
go func() {
|
|
for {
|
|
if _, _, err := cr.ReadLine(); err != nil {
|
|
return
|
|
}
|
|
}
|
|
}()
|
|
return c
|
|
}
|
|
|
|
for i := 0; i < 4; i++ {
|
|
c := newClient("PONG")
|
|
defer c.close()
|
|
}
|
|
|
|
// We will simulate that the account has expired. When
|
|
// a new client will connect, the server will do a lookup
|
|
// and find the account expired, which then will cause
|
|
// a fetch and a rebuild of the account. Since max conns
|
|
// is now lower, some clients should have been removed.
|
|
acc, _ := s.LookupAccount(fooPub)
|
|
acc.mu.Lock()
|
|
acc.expired = true
|
|
acc.mu.Unlock()
|
|
|
|
// Now update with new expiration and max connections lowered to 2
|
|
fooAC.Limits.Conn = 2
|
|
fooJWT, err = fooAC.Encode(okp)
|
|
if err != nil {
|
|
t.Fatalf("Error generating account JWT: %v", err)
|
|
}
|
|
addAccountToMemResolver(s, fooPub, fooJWT)
|
|
|
|
// Cause the lookup that will detect that account was expired
|
|
// and rebuild it, and kick clients out.
|
|
c := newClient("-ERR ")
|
|
defer c.close()
|
|
|
|
acc, _ = s.LookupAccount(fooPub)
|
|
checkFor(t, 2*time.Second, 15*time.Millisecond, func() error {
|
|
acc.mu.RLock()
|
|
numClients := len(acc.clients)
|
|
acc.mu.RUnlock()
|
|
if numClients != 2 {
|
|
return fmt.Errorf("Should have 2 clients, got %v", numClients)
|
|
}
|
|
return nil
|
|
})
|
|
}
|