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:
Alberto Ricart
2019-04-18 19:08:26 -05:00
committed by GitHub
parent bc11c1c284
commit 84a7e289b0
15 changed files with 443 additions and 67 deletions

6
go.mod
View File

@@ -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
View File

@@ -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=

View File

@@ -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

View File

@@ -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

View File

@@ -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)
}

View File

@@ -9,6 +9,7 @@ test:
go vet ./...
go test -v
go test -v --race
staticcheck ./...
fmt:
gofmt -w -s *.go

View File

@@ -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
}

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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
}

View File

@@ -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
View 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
View 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
View File

@@ -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