From 84a7e289b0cb1f073dfe63006c9ebb32294bae7d Mon Sep 17 00:00:00 2001 From: Alberto Ricart Date: Thu, 18 Apr 2019 19:08:26 -0500 Subject: [PATCH] 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. --- go.mod | 6 +- go.sum | 10 + server/accounts.go | 91 ++++++- server/auth.go | 11 +- server/jwt_test.go | 250 ++++++++++++++++++ vendor/github.com/nats-io/jwt/Makefile | 1 + .../github.com/nats-io/jwt/account_claims.go | 27 +- .../nats-io/jwt/activation_claims.go | 6 + vendor/github.com/nats-io/jwt/header.go | 2 +- vendor/github.com/nats-io/jwt/imports.go | 23 +- .../github.com/nats-io/jwt/operator_claims.go | 62 ++--- vendor/github.com/nats-io/jwt/user_claims.go | 6 + vendor/github.com/nats-io/nuid/GOVERNANCE.md | 3 + vendor/github.com/nats-io/nuid/MAINTAINERS.md | 6 + vendor/modules.txt | 6 +- 15 files changed, 443 insertions(+), 67 deletions(-) create mode 100644 vendor/github.com/nats-io/nuid/GOVERNANCE.md create mode 100644 vendor/github.com/nats-io/nuid/MAINTAINERS.md diff --git a/go.mod b/go.mod index cc02ae8c..af455ba1 100644 --- a/go.mod +++ b/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 ) diff --git a/go.sum b/go.sum index 980a8167..6a73f722 100644 --- a/go.sum +++ b/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= diff --git a/server/accounts.go b/server/accounts.go index 2de6b695..e81eb158 100644 --- a/server/accounts.go +++ b/server/accounts.go @@ -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 diff --git a/server/auth.go b/server/auth.go index d9856d60..7cd8e4b7 100644 --- a/server/auth.go +++ b/server/auth.go @@ -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 diff --git a/server/jwt_test.go b/server/jwt_test.go index 26917c55..705a7458 100644 --- a/server/jwt_test.go +++ b/server/jwt_test.go @@ -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) +} diff --git a/vendor/github.com/nats-io/jwt/Makefile b/vendor/github.com/nats-io/jwt/Makefile index 3e6a5cd5..95e468e1 100644 --- a/vendor/github.com/nats-io/jwt/Makefile +++ b/vendor/github.com/nats-io/jwt/Makefile @@ -9,6 +9,7 @@ test: go vet ./... go test -v go test -v --race + staticcheck ./... fmt: gofmt -w -s *.go diff --git a/vendor/github.com/nats-io/jwt/account_claims.go b/vendor/github.com/nats-io/jwt/account_claims.go index 2f8cee89..04a2f6ac 100644 --- a/vendor/github.com/nats-io/jwt/account_claims.go +++ b/vendor/github.com/nats-io/jwt/account_claims.go @@ -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 +} diff --git a/vendor/github.com/nats-io/jwt/activation_claims.go b/vendor/github.com/nats-io/jwt/activation_claims.go index a9f2a39d..ebc7b7a8 100644 --- a/vendor/github.com/nats-io/jwt/activation_claims.go +++ b/vendor/github.com/nats-io/jwt/activation_claims.go @@ -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 diff --git a/vendor/github.com/nats-io/jwt/header.go b/vendor/github.com/nats-io/jwt/header.go index aabb89b0..2625856b 100644 --- a/vendor/github.com/nats-io/jwt/header.go +++ b/vendor/github.com/nats-io/jwt/header.go @@ -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 diff --git a/vendor/github.com/nats-io/jwt/imports.go b/vendor/github.com/nats-io/jwt/imports.go index c93048b2..b41f7000 100644 --- a/vendor/github.com/nats-io/jwt/imports.go +++ b/vendor/github.com/nats-io/jwt/imports.go @@ -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 diff --git a/vendor/github.com/nats-io/jwt/operator_claims.go b/vendor/github.com/nats-io/jwt/operator_claims.go index 8eef305e..8c6d8e92 100644 --- a/vendor/github.com/nats-io/jwt/operator_claims.go +++ b/vendor/github.com/nats-io/jwt/operator_claims.go @@ -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 } diff --git a/vendor/github.com/nats-io/jwt/user_claims.go b/vendor/github.com/nats-io/jwt/user_claims.go index 7a8e8dae..8c64d5c2 100644 --- a/vendor/github.com/nats-io/jwt/user_claims.go +++ b/vendor/github.com/nats-io/jwt/user_claims.go @@ -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 diff --git a/vendor/github.com/nats-io/nuid/GOVERNANCE.md b/vendor/github.com/nats-io/nuid/GOVERNANCE.md new file mode 100644 index 00000000..01aee70d --- /dev/null +++ b/vendor/github.com/nats-io/nuid/GOVERNANCE.md @@ -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). \ No newline at end of file diff --git a/vendor/github.com/nats-io/nuid/MAINTAINERS.md b/vendor/github.com/nats-io/nuid/MAINTAINERS.md new file mode 100644 index 00000000..6d0ed3e3 --- /dev/null +++ b/vendor/github.com/nats-io/nuid/MAINTAINERS.md @@ -0,0 +1,6 @@ +# Maintainers + +Maintainership is on a per project basis. + +### Core-maintainers + - Derek Collison [@derekcollison](https://github.com/derekcollison) \ No newline at end of file diff --git a/vendor/modules.txt b/vendor/modules.txt index 71753c5f..070aef95 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -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