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