mirror of
https://github.com/gogrlx/nats-server.git
synced 2026-04-02 03:38:42 -07:00
Added support for account signing keys. (#962)
* Added support for account signing keys. When account signing keys change the validity of the client JWT and token imports need to be checked as well as it is possible for the signing key used to sign the user or import token to have been removed from the source account.
This commit is contained in:
6
go.mod
6
go.mod
@@ -2,9 +2,9 @@ module github.com/nats-io/gnatsd
|
||||
|
||||
require (
|
||||
github.com/nats-io/go-nats v1.7.2
|
||||
github.com/nats-io/jwt v0.1.0
|
||||
github.com/nats-io/jwt v0.2.4
|
||||
github.com/nats-io/nkeys v0.0.2
|
||||
github.com/nats-io/nuid v1.0.1
|
||||
golang.org/x/crypto v0.0.0-20190404164418-38d8ce5564a5
|
||||
golang.org/x/sys v0.0.0-20190405154228-4b34438f7a67
|
||||
golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a
|
||||
golang.org/x/sys v0.0.0-20190411185658-b44545bcd369
|
||||
)
|
||||
|
||||
10
go.sum
10
go.sum
@@ -4,6 +4,12 @@ github.com/nats-io/jwt v0.0.8 h1:oQsISWFvSmzKEs13h6X7p+8jQaXa9/X2fnBWoU2Zh4g=
|
||||
github.com/nats-io/jwt v0.0.8/go.mod h1:mQxQ0uHQ9FhEVPIcTSKwx2lqZEpXWWcCgA7R6NrWvvY=
|
||||
github.com/nats-io/jwt v0.1.0 h1:xO7kj8fyt0ECycBVG6WtOW+TnX8Aax4tI8i2fwspUro=
|
||||
github.com/nats-io/jwt v0.1.0/go.mod h1:mQxQ0uHQ9FhEVPIcTSKwx2lqZEpXWWcCgA7R6NrWvvY=
|
||||
github.com/nats-io/jwt v0.2.2 h1:tvCi/V+vJjSxOVmh5Zs3FN+n4jWDME4tH8BmGuZcnN0=
|
||||
github.com/nats-io/jwt v0.2.2/go.mod h1:mQxQ0uHQ9FhEVPIcTSKwx2lqZEpXWWcCgA7R6NrWvvY=
|
||||
github.com/nats-io/jwt v0.2.3-0.20190415222205-ef45966189e5 h1:hTRkgUpVZlCym185L0HG6tkO+EQxV4y9YOrsWx7K0wA=
|
||||
github.com/nats-io/jwt v0.2.3-0.20190415222205-ef45966189e5/go.mod h1:mQxQ0uHQ9FhEVPIcTSKwx2lqZEpXWWcCgA7R6NrWvvY=
|
||||
github.com/nats-io/jwt v0.2.4 h1:SZ27ZMqlP8US5QYFE+GPYtZZQa7PZUVZ6lNV3L7Z7EU=
|
||||
github.com/nats-io/jwt v0.2.4/go.mod h1:mQxQ0uHQ9FhEVPIcTSKwx2lqZEpXWWcCgA7R6NrWvvY=
|
||||
github.com/nats-io/nkeys v0.0.2 h1:+qM7QpgXnvDDixitZtQUBDY9w/s9mu1ghS+JIbsrx6M=
|
||||
github.com/nats-io/nkeys v0.0.2/go.mod h1:dab7URMsZm6Z/jp9Z5UGa87Uutgc2mVpXLC4B7TDb/4=
|
||||
github.com/nats-io/nuid v0.0.0-20180712044959-3024a71c3cbe h1:2nFZc8mo/vXfkJX5mTrTUUhHt6mIHwDoamuqIs3U1jU=
|
||||
@@ -17,8 +23,12 @@ golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9 h1:mKdxBk7AujPs8kU4m80U72
|
||||
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190404164418-38d8ce5564a5 h1:bselrhR0Or1vomJZC8ZIjWtbDmn9OYFLX5Ik9alpJpE=
|
||||
golang.org/x/crypto v0.0.0-20190404164418-38d8ce5564a5/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
|
||||
golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a h1:Igim7XhdOpBnWPuYJ70XcNpq8q3BCACtVgNfoJxOV7g=
|
||||
golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
|
||||
golang.org/x/sys v0.0.0-20170627012538-f7928cfef4d0 h1:zBqTV7BZW0C9WnFZU5Izl5ZfxL5+qtufgtwXGFU4t7g=
|
||||
golang.org/x/sys v0.0.0-20170627012538-f7928cfef4d0/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190405154228-4b34438f7a67 h1:1Fzlr8kkDLQwqMP8GxrhptBLqZG/EDpiATneiZHY998=
|
||||
golang.org/x/sys v0.0.0-20190405154228-4b34438f7a67/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190411185658-b44545bcd369 h1:aBlRBZoCuZNRDClvfkDoklQqdLzBaA3uViASg2z2p24=
|
||||
golang.org/x/sys v0.0.0-20190411185658-b44545bcd369/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
|
||||
@@ -51,10 +51,11 @@ type Account struct {
|
||||
imports importMap
|
||||
exports exportMap
|
||||
limits
|
||||
nae int32
|
||||
pruning bool
|
||||
expired bool
|
||||
srv *Server // server this account is registered with (possibly nil)
|
||||
nae int32
|
||||
pruning bool
|
||||
expired bool
|
||||
signingKeys []string
|
||||
srv *Server // server this account is registered with (possibly nil)
|
||||
}
|
||||
|
||||
// Account based limits.
|
||||
@@ -648,6 +649,9 @@ func (a *Account) checkActivation(acc *Account, claim *jwt.Import, expTimer bool
|
||||
if vr.IsBlocking(true) {
|
||||
return false
|
||||
}
|
||||
if !a.isIssuerClaimTrusted(act) {
|
||||
return false
|
||||
}
|
||||
if act.Expires != 0 {
|
||||
tn := time.Now().Unix()
|
||||
if act.Expires <= tn {
|
||||
@@ -660,9 +664,29 @@ func (a *Account) checkActivation(acc *Account, claim *jwt.Import, expTimer bool
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// Returns true if the activation claim is trusted. That is the issuer matches
|
||||
// the account or is an entry in the signing keys.
|
||||
func (a *Account) isIssuerClaimTrusted(claims *jwt.ActivationClaims) bool {
|
||||
// if no issuer account, issuer is the account
|
||||
if claims.IssuerAccount == "" {
|
||||
return true
|
||||
}
|
||||
// get the referenced account
|
||||
if a.srv != nil {
|
||||
ia, err := a.srv.lookupAccount(claims.IssuerAccount)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return ia.hasIssuer(claims.Issuer)
|
||||
}
|
||||
// couldn't verify
|
||||
return false
|
||||
}
|
||||
|
||||
// Returns true if `a` and `b` stream imports are the same. Note that the
|
||||
// check is done with the account's name, not the pointer. This is used
|
||||
// during config reload where we are comparing current and new config
|
||||
@@ -793,6 +817,23 @@ func (a *Account) checkExpiration(claims *jwt.ClaimsData) {
|
||||
a.expired = false
|
||||
}
|
||||
|
||||
// hasIssuer returns true if the issuer matches the account
|
||||
// issuer or it is a signing key for the account.
|
||||
func (a *Account) hasIssuer(issuer string) bool {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
// same issuer
|
||||
if a.Issuer == issuer {
|
||||
return true
|
||||
}
|
||||
for i := 0; i < len(a.signingKeys); i++ {
|
||||
if a.signingKeys[i] == issuer {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Placeholder for signaling token auth required.
|
||||
var tokenAuthReq = []*Account{}
|
||||
|
||||
@@ -831,13 +872,34 @@ func (s *Server) updateAccountClaims(a *Account, ac *jwt.AccountClaims) {
|
||||
s.Debugf("Updating account claims: %s", a.Name)
|
||||
a.checkExpiration(ac.Claims())
|
||||
|
||||
a.mu.Lock()
|
||||
// Clone to update, only select certain fields.
|
||||
old := &Account{Name: a.Name, imports: a.imports, exports: a.exports, limits: a.limits}
|
||||
old := &Account{Name: a.Name, imports: a.imports, exports: a.exports, limits: a.limits, signingKeys: a.signingKeys}
|
||||
|
||||
// Reset exports and imports here.
|
||||
a.exports = exportMap{}
|
||||
a.imports = importMap{}
|
||||
|
||||
// update account signing keys
|
||||
a.signingKeys = nil
|
||||
signersChanged := false
|
||||
if len(ac.SigningKeys) > 0 {
|
||||
// insure copy the new keys and sort
|
||||
a.signingKeys = append(a.signingKeys, ac.SigningKeys...)
|
||||
sort.Strings(a.signingKeys)
|
||||
}
|
||||
if len(a.signingKeys) != len(old.signingKeys) {
|
||||
signersChanged = true
|
||||
} else {
|
||||
for i := 0; i < len(old.signingKeys); i++ {
|
||||
if a.signingKeys[i] != old.signingKeys[i] {
|
||||
signersChanged = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
a.mu.Unlock()
|
||||
|
||||
gatherClients := func() []*client {
|
||||
a.mu.RLock()
|
||||
clients := make([]*client, 0, len(a.clients))
|
||||
@@ -891,7 +953,7 @@ func (s *Server) updateAccountClaims(a *Account, ac *jwt.AccountClaims) {
|
||||
}
|
||||
}
|
||||
// Now check if stream exports have changed.
|
||||
if !a.checkStreamExportsEqual(old) {
|
||||
if !a.checkStreamExportsEqual(old) || signersChanged {
|
||||
clients := make([]*client, 0, 16)
|
||||
// We need to check all accounts that have an import claim from this account.
|
||||
awcsti := map[string]struct{}{}
|
||||
@@ -915,7 +977,7 @@ func (s *Server) updateAccountClaims(a *Account, ac *jwt.AccountClaims) {
|
||||
}
|
||||
}
|
||||
// Now check if service exports have changed.
|
||||
if !a.checkServiceExportsEqual(old) {
|
||||
if !a.checkServiceExportsEqual(old) || signersChanged {
|
||||
for _, acc := range s.accounts {
|
||||
acc.mu.Lock()
|
||||
for _, im := range acc.imports.services {
|
||||
@@ -949,6 +1011,18 @@ func (s *Server) updateAccountClaims(a *Account, ac *jwt.AccountClaims) {
|
||||
c.applyAccountLimits()
|
||||
c.mu.Unlock()
|
||||
}
|
||||
|
||||
// Check if the signing keys changed, might have to evict
|
||||
if signersChanged {
|
||||
for _, c := range clients {
|
||||
c.mu.Lock()
|
||||
sk := c.user.SigningKey
|
||||
c.mu.Unlock()
|
||||
if sk != "" && !a.hasIssuer(sk) {
|
||||
c.closeConnection(AuthenticationViolation)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Helper to build an internal account structure from a jwt.AccountClaims.
|
||||
@@ -962,6 +1036,9 @@ func (s *Server) buildInternalAccount(ac *jwt.AccountClaims) *Account {
|
||||
// Helper to build internal NKeyUser.
|
||||
func buildInternalNkeyUser(uc *jwt.UserClaims, acc *Account) *NkeyUser {
|
||||
nu := &NkeyUser{Nkey: uc.Subject, Account: acc}
|
||||
if uc.IssuerAccount != "" {
|
||||
nu.SigningKey = uc.Issuer
|
||||
}
|
||||
|
||||
// Now check for permissions.
|
||||
var p *Permissions
|
||||
|
||||
@@ -47,6 +47,7 @@ type NkeyUser struct {
|
||||
Nkey string `json:"user"`
|
||||
Permissions *Permissions `json:"permissions,omitempty"`
|
||||
Account *Account `json:"account,omitempty"`
|
||||
SigningKey string `json:"signing_key,omitempty"`
|
||||
}
|
||||
|
||||
// User is for multiple accounts/users.
|
||||
@@ -348,7 +349,11 @@ func (s *Server) isClientAuthorized(c *client) bool {
|
||||
// If we have a jwt and a userClaim, make sure we have the Account, etc associated.
|
||||
// We need to look up the account. This will use an account resolver if one is present.
|
||||
if juc != nil {
|
||||
if acc, _ = s.LookupAccount(juc.Issuer); acc == nil {
|
||||
issuer := juc.Issuer
|
||||
if juc.IssuerAccount != "" {
|
||||
issuer = juc.IssuerAccount
|
||||
}
|
||||
if acc, _ = s.LookupAccount(issuer); acc == nil {
|
||||
c.Debugf("Account JWT can not be found")
|
||||
return false
|
||||
}
|
||||
@@ -356,6 +361,10 @@ func (s *Server) isClientAuthorized(c *client) bool {
|
||||
c.Debugf("Account JWT not signed by trusted operator")
|
||||
return false
|
||||
}
|
||||
if juc.IssuerAccount != "" && !acc.hasIssuer(juc.Issuer) {
|
||||
c.Debugf("User JWT issuer is not known")
|
||||
return false
|
||||
}
|
||||
if acc.IsExpired() {
|
||||
c.Debugf("Account JWT has expired")
|
||||
return false
|
||||
|
||||
@@ -58,10 +58,17 @@ func addAccountToMemResolver(s *Server, pub, jwtclaim string) {
|
||||
}
|
||||
|
||||
func createClient(t *testing.T, s *Server, akp nkeys.KeyPair) (*client, *bufio.Reader, string) {
|
||||
return createClientWithIssuer(t, s, akp, "")
|
||||
}
|
||||
|
||||
func createClientWithIssuer(t *testing.T, s *Server, akp nkeys.KeyPair, optIssuerAccount string) (*client, *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)
|
||||
@@ -1571,3 +1578,246 @@ func TestAccountURLResolverTimeout(t *testing.T) {
|
||||
t.Fatalf("Expected to not receive an account due to timeout")
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
// Don't send jwt field, should fail.
|
||||
go c.parse([]byte("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)
|
||||
|
||||
// PING needed to flush the +OK/-ERR to us.
|
||||
// This should fail too since no account resolver is defined.
|
||||
go c.parse([]byte(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)
|
||||
// should fail because the signing key is not known
|
||||
go c.parse([]byte(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)
|
||||
|
||||
// expect this to work
|
||||
go c.parse([]byte(cs))
|
||||
l, _ = cr.ReadString('\n')
|
||||
if !strings.HasPrefix(l, "PONG") {
|
||||
t.Fatalf("Expected a PONG, got %q", l)
|
||||
}
|
||||
|
||||
if c.nc == nil {
|
||||
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 c.nc != nil {
|
||||
t.Fatal("expected client to 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)
|
||||
|
||||
expectPong := func(cr *bufio.Reader) {
|
||||
t.Helper()
|
||||
l, _ := cr.ReadString('\n')
|
||||
if !strings.HasPrefix(l, "PONG") {
|
||||
t.Fatalf("Expected a PONG, got %q", l)
|
||||
}
|
||||
}
|
||||
|
||||
expectMsg := func(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(cr)
|
||||
}
|
||||
|
||||
// Create a client that will send the request
|
||||
client, clientReader, clientCS := createClient(t, s, clientKP)
|
||||
clientParser, clientCh := genAsyncParser(client)
|
||||
defer func() { clientCh <- true }()
|
||||
clientParser(clientCS)
|
||||
expectPong(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)
|
||||
srvParser, srvCh := genAsyncParser(srv)
|
||||
defer func() { srvCh <- true }()
|
||||
srvParser(srvCS)
|
||||
expectPong(srvReader)
|
||||
|
||||
// Create Subscriber.
|
||||
srvParser("SUB foo 1\r\nPING\r\n")
|
||||
expectPong(srvReader)
|
||||
|
||||
// Send Request
|
||||
clientParser("PUB foo 2\r\nhi\r\nPING\r\n")
|
||||
expectPong(clientReader)
|
||||
|
||||
// We should receive the request. PING needed to flush.
|
||||
srvParser("PING\r\n")
|
||||
expectMsg(srvReader, "foo", "hi")
|
||||
|
||||
clientParser("SUB bar 1\r\nPING\r\n")
|
||||
expectPong(clientReader)
|
||||
checkShadow(1)
|
||||
|
||||
srvParser("PUB bar 2\r\nhi\r\nPING\r\n")
|
||||
expectPong(srvReader)
|
||||
|
||||
// We should receive from stream. PING needed to flush.
|
||||
clientParser("PING\r\n")
|
||||
expectMsg(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
|
||||
clientParser("PUB foo 2\r\nhi\r\nPING\r\n")
|
||||
expectPong(clientReader)
|
||||
|
||||
// We should not receive the request this time.
|
||||
srvParser("PING\r\n")
|
||||
expectPong(srvReader)
|
||||
|
||||
// Publish on the stream
|
||||
srvParser("PUB bar 2\r\nhi\r\nPING\r\n")
|
||||
expectPong(srvReader)
|
||||
|
||||
// We should not receive from the stream this time
|
||||
clientParser("PING\r\n")
|
||||
expectPong(clientReader)
|
||||
checkShadow(0)
|
||||
}
|
||||
|
||||
1
vendor/github.com/nats-io/jwt/Makefile
generated
vendored
1
vendor/github.com/nats-io/jwt/Makefile
generated
vendored
@@ -9,6 +9,7 @@ test:
|
||||
go vet ./...
|
||||
go test -v
|
||||
go test -v --race
|
||||
staticcheck ./...
|
||||
|
||||
fmt:
|
||||
gofmt -w -s *.go
|
||||
|
||||
27
vendor/github.com/nats-io/jwt/account_claims.go
generated
vendored
27
vendor/github.com/nats-io/jwt/account_claims.go
generated
vendored
@@ -53,10 +53,11 @@ func (o *OperatorLimits) Validate(vr *ValidationResults) {
|
||||
|
||||
// Account holds account specific claims data
|
||||
type Account struct {
|
||||
Imports Imports `json:"imports,omitempty"`
|
||||
Exports Exports `json:"exports,omitempty"`
|
||||
Identities []Identity `json:"identity,omitempty"`
|
||||
Limits OperatorLimits `json:"limits,omitempty"`
|
||||
Imports Imports `json:"imports,omitempty"`
|
||||
Exports Exports `json:"exports,omitempty"`
|
||||
Identities []Identity `json:"identity,omitempty"`
|
||||
Limits OperatorLimits `json:"limits,omitempty"`
|
||||
SigningKeys StringList `json:"signing_keys,omitempty"`
|
||||
}
|
||||
|
||||
// Validate checks if the account is valid, based on the wrapper
|
||||
@@ -92,6 +93,12 @@ func (a *Account) Validate(acct *AccountClaims, vr *ValidationResults) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, k := range a.SigningKeys {
|
||||
if !nkeys.IsValidPublicAccountKey(k) {
|
||||
vr.AddError("%s is not an account public key", k)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// AccountClaims defines the body of an account JWT
|
||||
@@ -165,3 +172,15 @@ func (a *AccountClaims) ExpectedPrefixes() []nkeys.PrefixByte {
|
||||
func (a *AccountClaims) Claims() *ClaimsData {
|
||||
return &a.ClaimsData
|
||||
}
|
||||
|
||||
// DidSign checks the claims against the account's public key and its signing keys
|
||||
func (a *AccountClaims) DidSign(op Claims) bool {
|
||||
if op != nil {
|
||||
issuer := op.Claims().Issuer
|
||||
if issuer == a.Subject {
|
||||
return true
|
||||
}
|
||||
return a.SigningKeys.Contains(issuer)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
6
vendor/github.com/nats-io/jwt/activation_claims.go
generated
vendored
6
vendor/github.com/nats-io/jwt/activation_claims.go
generated
vendored
@@ -62,6 +62,9 @@ func (a *Activation) Validate(vr *ValidationResults) {
|
||||
type ActivationClaims struct {
|
||||
ClaimsData
|
||||
Activation `json:"nats,omitempty"`
|
||||
// IssuerAccount stores the public key for the account the issuer represents.
|
||||
// When set, the claim was issued by a signing key.
|
||||
IssuerAccount string `json:"issuer_account,omitempty"`
|
||||
}
|
||||
|
||||
// NewActivationClaims creates a new activation claim with the provided sub
|
||||
@@ -101,6 +104,9 @@ func (a *ActivationClaims) Payload() interface{} {
|
||||
func (a *ActivationClaims) Validate(vr *ValidationResults) {
|
||||
a.ClaimsData.Validate(vr)
|
||||
a.Activation.Validate(vr)
|
||||
if a.IssuerAccount != "" && !nkeys.IsValidPublicAccountKey(a.IssuerAccount) {
|
||||
vr.AddError("account_id is not an account public key")
|
||||
}
|
||||
}
|
||||
|
||||
// ExpectedPrefixes defines the types that can sign an activation jwt, account and oeprator
|
||||
|
||||
2
vendor/github.com/nats-io/jwt/header.go
generated
vendored
2
vendor/github.com/nats-io/jwt/header.go
generated
vendored
@@ -23,7 +23,7 @@ import (
|
||||
|
||||
const (
|
||||
// Version is semantic version.
|
||||
Version = "0.1.0"
|
||||
Version = "0.0.5"
|
||||
|
||||
// TokenTypeJwt is the JWT token type supported JWT tokens
|
||||
// encoded and decoded by this library
|
||||
|
||||
23
vendor/github.com/nats-io/jwt/imports.go
generated
vendored
23
vendor/github.com/nats-io/jwt/imports.go
generated
vendored
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2018 The NATS Authors
|
||||
* Copyright 2018-2019 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
|
||||
@@ -24,12 +24,21 @@ import (
|
||||
|
||||
// Import describes a mapping from another account into this one
|
||||
type Import struct {
|
||||
Name string `json:"name,omitempty"`
|
||||
Subject Subject `json:"subject,omitempty"`
|
||||
Account string `json:"account,omitempty"`
|
||||
Token string `json:"token,omitempty"`
|
||||
To Subject `json:"to,omitempty"`
|
||||
Type ExportType `json:"type,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
// Subject field in an import is always from the perspective of the
|
||||
// initial publisher - in the case of a stream it is the account owning
|
||||
// the stream (the exporter), and in the case of a service it is the
|
||||
// account making the request (the importer).
|
||||
Subject Subject `json:"subject,omitempty"`
|
||||
Account string `json:"account,omitempty"`
|
||||
Token string `json:"token,omitempty"`
|
||||
// To field in an import is always from the perspective of the subscriber
|
||||
// in the case of a stream it is the client of the stream (the importer),
|
||||
// from the perspective of a service, it is the subscription waiting for
|
||||
// requests (the exporter). If the field is empty, it will default to the
|
||||
// value in the Subject field.
|
||||
To Subject `json:"to,omitempty"`
|
||||
Type ExportType `json:"type,omitempty"`
|
||||
}
|
||||
|
||||
// IsService returns true if the import is of type service
|
||||
|
||||
62
vendor/github.com/nats-io/jwt/operator_claims.go
generated
vendored
62
vendor/github.com/nats-io/jwt/operator_claims.go
generated
vendored
@@ -24,7 +24,7 @@ import (
|
||||
// Operator specific claims
|
||||
type Operator struct {
|
||||
Identities []Identity `json:"identity,omitempty"`
|
||||
SigningKeys []string `json:"signing_keys,omitempty"`
|
||||
SigningKeys StringList `json:"signing_keys,omitempty"`
|
||||
}
|
||||
|
||||
// Validate checks the validity of the operators contents
|
||||
@@ -33,10 +33,6 @@ func (o *Operator) Validate(vr *ValidationResults) {
|
||||
i.Validate(vr)
|
||||
}
|
||||
|
||||
if o.SigningKeys == nil {
|
||||
return
|
||||
}
|
||||
|
||||
for _, k := range o.SigningKeys {
|
||||
if !nkeys.IsValidPublicOperatorKey(k) {
|
||||
vr.AddError("%s is not an operator public key", k)
|
||||
@@ -61,45 +57,29 @@ func NewOperatorClaims(subject string) *OperatorClaims {
|
||||
}
|
||||
|
||||
// DidSign checks the claims against the operator's public key and its signing keys
|
||||
func (s *OperatorClaims) DidSign(op Claims) bool {
|
||||
func (oc *OperatorClaims) DidSign(op Claims) bool {
|
||||
if op == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
issuer := op.Claims().Issuer
|
||||
|
||||
if issuer == s.Subject {
|
||||
if issuer == oc.Subject {
|
||||
return true
|
||||
}
|
||||
|
||||
for _, k := range s.SigningKeys {
|
||||
if k == issuer {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
return oc.SigningKeys.Contains(issuer)
|
||||
}
|
||||
|
||||
// AddSigningKey creates the signing keys array if necessary
|
||||
// appends the new key, NO Validation is performed
|
||||
func (s *OperatorClaims) AddSigningKey(pk string) {
|
||||
|
||||
if s.SigningKeys == nil {
|
||||
s.SigningKeys = []string{pk}
|
||||
return
|
||||
}
|
||||
|
||||
s.SigningKeys = append(s.SigningKeys, pk)
|
||||
// deprecated AddSigningKey, use claim.SigningKeys.Add()
|
||||
func (oc *OperatorClaims) AddSigningKey(pk string) {
|
||||
oc.SigningKeys.Add(pk)
|
||||
}
|
||||
|
||||
// Encode the claims into a JWT string
|
||||
func (s *OperatorClaims) Encode(pair nkeys.KeyPair) (string, error) {
|
||||
if !nkeys.IsValidPublicOperatorKey(s.Subject) {
|
||||
func (oc *OperatorClaims) Encode(pair nkeys.KeyPair) (string, error) {
|
||||
if !nkeys.IsValidPublicOperatorKey(oc.Subject) {
|
||||
return "", errors.New("expected subject to be an operator public key")
|
||||
}
|
||||
s.ClaimsData.Type = OperatorClaim
|
||||
return s.ClaimsData.encode(pair, s)
|
||||
oc.ClaimsData.Type = OperatorClaim
|
||||
return oc.ClaimsData.encode(pair, oc)
|
||||
}
|
||||
|
||||
// DecodeOperatorClaims tries to create an operator claims from a JWt string
|
||||
@@ -111,27 +91,27 @@ func DecodeOperatorClaims(token string) (*OperatorClaims, error) {
|
||||
return &v, nil
|
||||
}
|
||||
|
||||
func (s *OperatorClaims) String() string {
|
||||
return s.ClaimsData.String(s)
|
||||
func (oc *OperatorClaims) String() string {
|
||||
return oc.ClaimsData.String(oc)
|
||||
}
|
||||
|
||||
// Payload returns the operator specific data for an operator JWT
|
||||
func (s *OperatorClaims) Payload() interface{} {
|
||||
return &s.Operator
|
||||
func (oc *OperatorClaims) Payload() interface{} {
|
||||
return &oc.Operator
|
||||
}
|
||||
|
||||
// Validate the contents of the claims
|
||||
func (s *OperatorClaims) Validate(vr *ValidationResults) {
|
||||
s.ClaimsData.Validate(vr)
|
||||
s.Operator.Validate(vr)
|
||||
func (oc *OperatorClaims) Validate(vr *ValidationResults) {
|
||||
oc.ClaimsData.Validate(vr)
|
||||
oc.Operator.Validate(vr)
|
||||
}
|
||||
|
||||
// ExpectedPrefixes defines the nkey types that can sign operator claims, operator
|
||||
func (s *OperatorClaims) ExpectedPrefixes() []nkeys.PrefixByte {
|
||||
func (oc *OperatorClaims) ExpectedPrefixes() []nkeys.PrefixByte {
|
||||
return []nkeys.PrefixByte{nkeys.PrefixByteOperator}
|
||||
}
|
||||
|
||||
// Claims returns the generic claims data
|
||||
func (s *OperatorClaims) Claims() *ClaimsData {
|
||||
return &s.ClaimsData
|
||||
func (oc *OperatorClaims) Claims() *ClaimsData {
|
||||
return &oc.ClaimsData
|
||||
}
|
||||
|
||||
6
vendor/github.com/nats-io/jwt/user_claims.go
generated
vendored
6
vendor/github.com/nats-io/jwt/user_claims.go
generated
vendored
@@ -37,6 +37,9 @@ func (u *User) Validate(vr *ValidationResults) {
|
||||
type UserClaims struct {
|
||||
ClaimsData
|
||||
User `json:"nats,omitempty"`
|
||||
// IssuerAccount stores the public key for the account the issuer represents.
|
||||
// When set, the claim was issued by a signing key.
|
||||
IssuerAccount string `json:"issuer_account,omitempty"`
|
||||
}
|
||||
|
||||
// NewUserClaims creates a user JWT with the specific subject/public key
|
||||
@@ -71,6 +74,9 @@ func DecodeUserClaims(token string) (*UserClaims, error) {
|
||||
func (u *UserClaims) Validate(vr *ValidationResults) {
|
||||
u.ClaimsData.Validate(vr)
|
||||
u.User.Validate(vr)
|
||||
if u.IssuerAccount != "" && !nkeys.IsValidPublicAccountKey(u.IssuerAccount) {
|
||||
vr.AddError("account_id is not an account public key")
|
||||
}
|
||||
}
|
||||
|
||||
// ExpectedPrefixes defines the types that can encode a user JWT, account
|
||||
|
||||
3
vendor/github.com/nats-io/nuid/GOVERNANCE.md
generated
vendored
Normal file
3
vendor/github.com/nats-io/nuid/GOVERNANCE.md
generated
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
# NATS NUID Governance
|
||||
|
||||
NATS NUID is part of the NATS project and is subject to the [NATS Governance](https://github.com/nats-io/nats-general/blob/master/GOVERNANCE.md).
|
||||
6
vendor/github.com/nats-io/nuid/MAINTAINERS.md
generated
vendored
Normal file
6
vendor/github.com/nats-io/nuid/MAINTAINERS.md
generated
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
# Maintainers
|
||||
|
||||
Maintainership is on a per project basis.
|
||||
|
||||
### Core-maintainers
|
||||
- Derek Collison <derek@nats.io> [@derekcollison](https://github.com/derekcollison)
|
||||
6
vendor/modules.txt
vendored
6
vendor/modules.txt
vendored
@@ -2,18 +2,18 @@
|
||||
github.com/nats-io/go-nats
|
||||
github.com/nats-io/go-nats/encoders/builtin
|
||||
github.com/nats-io/go-nats/util
|
||||
# github.com/nats-io/jwt v0.1.0
|
||||
# github.com/nats-io/jwt v0.2.4
|
||||
github.com/nats-io/jwt
|
||||
# github.com/nats-io/nkeys v0.0.2
|
||||
github.com/nats-io/nkeys
|
||||
# github.com/nats-io/nuid v1.0.1
|
||||
github.com/nats-io/nuid
|
||||
# golang.org/x/crypto v0.0.0-20190404164418-38d8ce5564a5
|
||||
# golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a
|
||||
golang.org/x/crypto/bcrypt
|
||||
golang.org/x/crypto/ed25519
|
||||
golang.org/x/crypto/blowfish
|
||||
golang.org/x/crypto/ed25519/internal/edwards25519
|
||||
# golang.org/x/sys v0.0.0-20190405154228-4b34438f7a67
|
||||
# golang.org/x/sys v0.0.0-20190411185658-b44545bcd369
|
||||
golang.org/x/sys/windows/svc/eventlog
|
||||
golang.org/x/sys/windows/svc
|
||||
golang.org/x/sys/windows/svc/debug
|
||||
|
||||
Reference in New Issue
Block a user