Add in user JWT support for ResponsePermissions

Signed-off-by: Derek Collison <derek@nats.io>
This commit is contained in:
Derek Collison
2019-07-26 16:06:14 -07:00
parent 34ff5aade6
commit bf902d9e7c
20 changed files with 441 additions and 144 deletions

5
go.mod
View File

@@ -1,11 +1,10 @@
module github.com/nats-io/nats-server/v2 module github.com/nats-io/nats-server/v2
require ( require (
github.com/golang/protobuf v1.3.2 // indirect github.com/nats-io/jwt v0.2.13-0.20190726194050-829b612a49c3
github.com/nats-io/jwt v0.2.8
github.com/nats-io/nats.go v1.8.1 github.com/nats-io/nats.go v1.8.1
github.com/nats-io/nkeys v0.1.0 github.com/nats-io/nkeys v0.1.0
github.com/nats-io/nuid v1.0.1 github.com/nats-io/nuid v1.0.1
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4
golang.org/x/sys v0.0.0-20190710143415-6ec70d6a5542 golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e
) )

11
go.sum
View File

@@ -1,10 +1,7 @@
github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs= github.com/nats-io/jwt v0.2.13-0.20190726194050-829b612a49c3 h1:5vVSRJhjWOTv/TeJX1NUGuDYbZkfcWTu/97AHlsC02o=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/nats-io/jwt v0.2.13-0.20190726194050-829b612a49c3/go.mod h1:mQxQ0uHQ9FhEVPIcTSKwx2lqZEpXWWcCgA7R6NrWvvY=
github.com/nats-io/jwt v0.2.8 h1:PXr0mRjPCPX4cXsdfHcsqoplrNXnKOD+g2yHoh9qy1I=
github.com/nats-io/jwt v0.2.8/go.mod h1:mQxQ0uHQ9FhEVPIcTSKwx2lqZEpXWWcCgA7R6NrWvvY=
github.com/nats-io/nats.go v1.8.1 h1:6lF/f1/NN6kzUDBz6pyvQDEXO39jqXcWRLu/tKjtOUQ= github.com/nats-io/nats.go v1.8.1 h1:6lF/f1/NN6kzUDBz6pyvQDEXO39jqXcWRLu/tKjtOUQ=
github.com/nats-io/nats.go v1.8.1/go.mod h1:BrFz9vVn0fU3AcH9Vn4Kd7W0NpJ651tD5omQ3M8LwxM= github.com/nats-io/nats.go v1.8.1/go.mod h1:BrFz9vVn0fU3AcH9Vn4Kd7W0NpJ651tD5omQ3M8LwxM=
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/nkeys v0.0.2/go.mod h1:dab7URMsZm6Z/jp9Z5UGa87Uutgc2mVpXLC4B7TDb/4=
github.com/nats-io/nkeys v0.1.0 h1:qMd4+pRHgdr1nAClu+2h/2a5F2TmKcCzjCDazVgRoX4= github.com/nats-io/nkeys v0.1.0 h1:qMd4+pRHgdr1nAClu+2h/2a5F2TmKcCzjCDazVgRoX4=
github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
@@ -16,8 +13,6 @@ golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 h1:HuIa8hRrWRSrqYzx1qI49N
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d h1:+R4KGOnez64A81RvjARKc4UT5/tI9ujCIVX+P5KiHuI=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190710143415-6ec70d6a5542 h1:6ZQFf1D2YYDDI7eSwW8adlkkavTB9sw5I24FVtEvNUQ= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190710143415-6ec70d6a5542/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=

View File

@@ -586,8 +586,9 @@ func (a *Account) AddStreamExport(subject string, accounts []*Account) error {
func (a *Account) checkStreamImportAuthorized(account *Account, subject string, imClaim *jwt.Import) bool { func (a *Account) checkStreamImportAuthorized(account *Account, subject string, imClaim *jwt.Import) bool {
// Find the subject in the exports list. // Find the subject in the exports list.
a.mu.RLock() a.mu.RLock()
defer a.mu.RUnlock() auth := a.checkStreamImportAuthorizedNoLock(account, subject, imClaim)
return a.checkStreamImportAuthorizedNoLock(account, subject, imClaim) a.mu.RUnlock()
return auth
} }
func (a *Account) checkStreamImportAuthorizedNoLock(account *Account, subject string, imClaim *jwt.Import) bool { func (a *Account) checkStreamImportAuthorizedNoLock(account *Account, subject string, imClaim *jwt.Import) bool {
@@ -1165,6 +1166,19 @@ func buildInternalNkeyUser(uc *jwt.UserClaims, acc *Account) *NkeyUser {
p.Subscribe.Allow = uc.Sub.Allow p.Subscribe.Allow = uc.Sub.Allow
p.Subscribe.Deny = uc.Sub.Deny p.Subscribe.Deny = uc.Sub.Deny
} }
if uc.Resp != nil {
if p == nil {
p = &Permissions{Publish: &SubjectPermission{}}
}
if p.Publish.Allow == nil {
// We turn off the blanket allow statement.
p.Publish.Allow = []string{}
}
p.Response = &ResponsePermission{
MaxMsgs: uc.Resp.MaxMsgs,
Expires: uc.Resp.Expires,
}
}
nu.Permissions = p nu.Permissions = p
return nu return nu
} }

View File

@@ -370,6 +370,78 @@ func TestJWTUserPermissionClaims(t *testing.T) {
} }
} }
func TestJWTUserResponsePermissionClaims(t *testing.T) {
okp, _ := nkeys.FromSeed(oSeed)
nkp, _ := nkeys.CreateUser()
pub, _ := nkp.PublicKey()
nuc := jwt.NewUserClaims(pub)
nuc.Permissions.Resp = &jwt.ResponsePermission{
MaxMsgs: 22,
Expires: 100 * time.Millisecond,
}
akp, _ := nkeys.FromSeed(aSeed)
apub, _ := akp.PublicKey()
nac := jwt.NewAccountClaims(apub)
ajwt, err := nac.Encode(okp)
if err != nil {
t.Fatalf("Error generating account JWT: %v", err)
}
jwt, err := nuc.Encode(akp)
if err != nil {
t.Fatalf("Error generating user JWT: %v", err)
}
s := opTrustBasicSetup()
defer s.Shutdown()
buildMemAccResolver(s)
addAccountToMemResolver(s, apub, ajwt)
c, cr, l := newClientForServer(s)
// Sign Nonce
var info nonceInfo
json.Unmarshal([]byte(l[5:]), &info)
sigraw, _ := nkp.Sign([]byte(info.Nonce))
sig := base64.RawURLEncoding.EncodeToString(sigraw)
// PING needed to flush the +OK/-ERR to us.
// This should fail too since no account resolver is defined.
cs := fmt.Sprintf("CONNECT {\"jwt\":%q,\"sig\":\"%s\",\"verbose\":true,\"pedantic\":true}\r\nPING\r\n", jwt, sig)
go c.parse([]byte(cs))
l, _ = cr.ReadString('\n')
if !strings.HasPrefix(l, "+OK") {
t.Fatalf("Expected an OK, got: %v", l)
}
// Now check client to make sure permissions transferred.
c.mu.Lock()
defer c.mu.Unlock()
if c.perms == nil {
t.Fatalf("Expected client permissions to be set")
}
if c.perms.pub.allow == nil {
t.Fatalf("Expected client perms for pub allow to be non-nil")
}
if lpa := c.perms.pub.allow.Count(); lpa != 0 {
t.Fatalf("Expected 0 publish allow subjects, got %d", lpa)
}
if c.perms.resp == nil {
t.Fatalf("Expected client perms for response permissions to be non-nil")
}
if c.perms.resp.MaxMsgs != nuc.Permissions.Resp.MaxMsgs {
t.Fatalf("Expected client perms for response permissions MaxMsgs to be same as jwt: %d vs %d",
c.perms.resp.MaxMsgs, nuc.Permissions.Resp.MaxMsgs)
}
if c.perms.resp.Expires != nuc.Permissions.Resp.Expires {
t.Fatalf("Expected client perms for response permissions Expires to be same as jwt: %v vs %v",
c.perms.resp.Expires, nuc.Permissions.Resp.Expires)
}
}
func TestJWTAccountExpired(t *testing.T) { func TestJWTAccountExpired(t *testing.T) {
s := opTrustBasicSetup() s := opTrustBasicSetup()
defer s.Shutdown() defer s.Shutdown()

5
vendor/github.com/nats-io/jwt/ReleaseNotes.md generated vendored Normal file
View File

@@ -0,0 +1,5 @@
# Release Notes
## 0.3.0
* Removed revocation claims in favor of timestamp-based revocation maps in account and export claims.

View File

@@ -17,11 +17,12 @@ package jwt
import ( import (
"errors" "errors"
"time"
"github.com/nats-io/nkeys" "github.com/nats-io/nkeys"
) )
// Signifies no limit. // NoLimit is used to indicate a limit field is unlimited in value.
const NoLimit = -1 const NoLimit = -1
// OperatorLimits are used to limit access by an account // OperatorLimits are used to limit access by an account
@@ -58,6 +59,7 @@ type Account struct {
Identities []Identity `json:"identity,omitempty"` Identities []Identity `json:"identity,omitempty"`
Limits OperatorLimits `json:"limits,omitempty"` Limits OperatorLimits `json:"limits,omitempty"`
SigningKeys StringList `json:"signing_keys,omitempty"` SigningKeys StringList `json:"signing_keys,omitempty"`
Revocations RevocationList `json:"revocations,omitempty"`
} }
// Validate checks if the account is valid, based on the wrapper // Validate checks if the account is valid, based on the wrapper
@@ -127,7 +129,7 @@ func (a *AccountClaims) Encode(pair nkeys.KeyPair) (string, error) {
} }
a.ClaimsData.Type = AccountClaim a.ClaimsData.Type = AccountClaim
return a.ClaimsData.encode(pair, a) return a.ClaimsData.Encode(pair, a)
} }
// DecodeAccountClaims decodes account claims from a JWT string // DecodeAccountClaims decodes account claims from a JWT string
@@ -184,3 +186,35 @@ func (a *AccountClaims) DidSign(op Claims) bool {
} }
return false return false
} }
// Revoke enters a revocation by publickey using time.Now().
func (a *AccountClaims) Revoke(pubKey string) {
a.RevokeAt(pubKey, time.Now())
}
// RevokeAt enters a revocation by publickey and timestamp into this export
// If there is already a revocation for this public key that is newer, it is kept.
func (a *AccountClaims) RevokeAt(pubKey string, timestamp time.Time) {
if a.Revocations == nil {
a.Revocations = RevocationList{}
}
a.Revocations.Revoke(pubKey, timestamp)
}
// ClearRevocation removes any revocation for the public key
func (a *AccountClaims) ClearRevocation(pubKey string) {
a.Revocations.ClearRevocation(pubKey)
}
// IsRevokedAt checks if the public key is in the revoked list with a timestamp later than
// the one passed in. Generally this method is called with time.Now() but other time's can
// be used for testing.
func (a *AccountClaims) IsRevokedAt(pubKey string, timestamp time.Time) bool {
return a.Revocations.IsRevoked(pubKey, timestamp)
}
// IsRevoked checks if the public key is in the revoked list with time.Now()
func (a *AccountClaims) IsRevoked(pubKey string) bool {
return a.Revocations.IsRevoked(pubKey, time.Now())
}

View File

@@ -83,7 +83,7 @@ func (a *ActivationClaims) Encode(pair nkeys.KeyPair) (string, error) {
return "", errors.New("expected subject to be an account") return "", errors.New("expected subject to be an account")
} }
a.ClaimsData.Type = ActivationClaim a.ClaimsData.Type = ActivationClaim
return a.ClaimsData.encode(pair, a) return a.ClaimsData.Encode(pair, a)
} }
// DecodeActivationClaims tries to create an activation claim from a JWT string // DecodeActivationClaims tries to create an activation claim from a JWT string

View File

@@ -44,8 +44,6 @@ const (
ClusterClaim = "cluster" ClusterClaim = "cluster"
//OperatorClaim is the type of an operator JWT //OperatorClaim is the type of an operator JWT
OperatorClaim = "operator" OperatorClaim = "operator"
//RevocationClaim is the type of an revocation JWT
RevocationClaim = "revocation"
) )
// Claims is a JWT claims // Claims is a JWT claims
@@ -180,9 +178,9 @@ func (c *ClaimsData) hash() (string, error) {
return base32.StdEncoding.WithPadding(base32.NoPadding).EncodeToString(h.Sum(nil)), nil return base32.StdEncoding.WithPadding(base32.NoPadding).EncodeToString(h.Sum(nil)), nil
} }
// encode encodes a claim into a JWT token. The claim is signed with the // Encode encodes a claim into a JWT token. The claim is signed with the
// provided nkey's private key // provided nkey's private key
func (c *ClaimsData) encode(kp nkeys.KeyPair, payload Claims) (string, error) { func (c *ClaimsData) Encode(kp nkeys.KeyPair, payload Claims) (string, error) {
return c.doEncode(&Header{TokenTypeJwt, AlgorithmNkey}, kp, payload) return c.doEncode(&Header{TokenTypeJwt, AlgorithmNkey}, kp, payload)
} }
@@ -241,7 +239,7 @@ func (c *ClaimsData) IsSelfSigned() bool {
// Decode takes a JWT string decodes it and validates it // Decode takes a JWT string decodes it and validates it
// and return the embedded Claims. If the token header // and return the embedded Claims. If the token header
// doesn't match the expected algorithm, or the claim is // doesn't match the expected algorithm, or the claim is
// not valid or verification fails an error is returned // not valid or verification fails an error is returned.
func Decode(token string, target Claims) error { func Decode(token string, target Claims) error {
// must have 3 chunks // must have 3 chunks
chunks := strings.Split(token, ".") chunks := strings.Split(token, ".")

View File

@@ -56,7 +56,7 @@ func (c *ClusterClaims) Encode(pair nkeys.KeyPair) (string, error) {
return "", errors.New("expected subject to be a cluster public key") return "", errors.New("expected subject to be a cluster public key")
} }
c.ClaimsData.Type = ClusterClaim c.ClaimsData.Type = ClusterClaim
return c.ClaimsData.encode(pair, c) return c.ClaimsData.Encode(pair, c)
} }
// DecodeClusterClaims tries to parse cluster claims from a JWT string // DecodeClusterClaims tries to parse cluster claims from a JWT string

196
vendor/github.com/nats-io/jwt/creds_utils.go generated vendored Normal file
View File

@@ -0,0 +1,196 @@
package jwt
import (
"bytes"
"errors"
"fmt"
"regexp"
"strings"
"github.com/nats-io/nkeys"
)
// DecorateJWT returns a decorated JWT that describes the kind of JWT
func DecorateJWT(jwtString string) ([]byte, error) {
gc, err := DecodeGeneric(jwtString)
if err != nil {
return nil, err
}
return formatJwt(string(gc.Type), jwtString)
}
func formatJwt(kind string, jwtString string) ([]byte, error) {
templ := `-----BEGIN NATS %s JWT-----
%s
------END NATS %s JWT------
`
w := bytes.NewBuffer(nil)
kind = strings.ToUpper(kind)
_, err := fmt.Fprintf(w, templ, kind, jwtString, kind)
if err != nil {
return nil, err
}
return w.Bytes(), nil
}
func DecorateSeed(seed []byte) ([]byte, error) {
w := bytes.NewBuffer(nil)
ts := bytes.TrimSpace(seed)
pre := string(ts[0:2])
kind := ""
switch pre {
case "SU":
kind = "USER"
case "SA":
kind = "ACCOUNT"
case "SO":
kind = "OPERATOR"
default:
return nil, errors.New("seed is not an operator, account or user seed")
}
header := `************************* IMPORTANT *************************
NKEY Seed printed below can be used to sign and prove identity.
NKEYs are sensitive and should be treated as secrets.
-----BEGIN %s NKEY SEED-----
`
_, err := fmt.Fprintf(w, header, kind)
if err != nil {
return nil, err
}
w.Write(ts)
footer := `
------END %s NKEY SEED------
*************************************************************
`
_, err = fmt.Fprintf(w, footer, kind)
if err != nil {
return nil, err
}
return w.Bytes(), nil
}
var userConfigRE = regexp.MustCompile(`\s*(?:(?:[-]{3,}[^\n]*[-]{3,}\n)(.+)(?:\n\s*[-]{3,}[^\n]*[-]{3,}\n))`)
// An user config file looks like this:
// -----BEGIN NATS USER JWT-----
// eyJ0eXAiOiJqd3QiLCJhbGciOiJlZDI1NTE5...
// ------END NATS USER JWT------
//
// ************************* IMPORTANT *************************
// NKEY Seed printed below can be used sign and prove identity.
// NKEYs are sensitive and should be treated as secrets.
//
// -----BEGIN USER NKEY SEED-----
// SUAIO3FHUX5PNV2LQIIP7TZ3N4L7TX3W53MQGEIVYFIGA635OZCKEYHFLM
// ------END USER NKEY SEED------
// FormatUserConfig returns a decorated file with a decorated JWT and decorated seed
func FormatUserConfig(jwtString string, seed []byte) ([]byte, error) {
gc, err := DecodeGeneric(jwtString)
if err != nil {
return nil, err
}
if gc.Type != UserClaim {
return nil, fmt.Errorf("%q cannot be serialized as a user config", string(gc.Type))
}
w := bytes.NewBuffer(nil)
jd, err := formatJwt(string(gc.Type), jwtString)
if err != nil {
return nil, err
}
_, err = w.Write(jd)
if err != nil {
return nil, err
}
if !bytes.HasPrefix(bytes.TrimSpace(seed), []byte("SU")) {
return nil, fmt.Errorf("nkey seed is not an user seed")
}
d, err := DecorateSeed(seed)
if err != nil {
return nil, err
}
_, err = w.Write(d)
if err != nil {
return nil, err
}
return w.Bytes(), nil
}
func ParseDecoratedJWT(contents []byte) (string, error) {
defer wipeSlice(contents)
items := userConfigRE.FindAllSubmatch(contents, -1)
if len(items) == 0 {
return string(contents), nil
}
// First result should be the user JWT.
// We copy here so that if the file contained a seed file too we wipe appropriately.
raw := items[0][1]
tmp := make([]byte, len(raw))
copy(tmp, raw)
return string(tmp), nil
}
func ParseDecoratedNKey(contents []byte) (nkeys.KeyPair, error) {
var seed []byte
defer wipeSlice(contents)
items := userConfigRE.FindAllSubmatch(contents, -1)
if len(items) > 1 {
seed = items[1][1]
} else {
lines := bytes.Split(contents, []byte("\n"))
for _, line := range lines {
if bytes.HasPrefix(bytes.TrimSpace(line), []byte("SO")) ||
bytes.HasPrefix(bytes.TrimSpace(line), []byte("SA")) ||
bytes.HasPrefix(bytes.TrimSpace(line), []byte("SU")) {
seed = line
break
}
}
}
if seed == nil {
return nil, errors.New("no nkey seed found")
}
if !bytes.HasPrefix(seed, []byte("SO")) &&
!bytes.HasPrefix(seed, []byte("SA")) &&
!bytes.HasPrefix(seed, []byte("SU")) {
return nil, errors.New("doesn't contain a seed nkey")
}
kp, err := nkeys.FromSeed(seed)
if err != nil {
return nil, err
}
return kp, nil
}
func ParseDecoratedUserNKey(contents []byte) (nkeys.KeyPair, error) {
nk, err := ParseDecoratedNKey(contents)
if err != nil {
return nil, err
}
seed, err := nk.Seed()
if err != nil {
return nil, err
}
if !bytes.HasPrefix(seed, []byte("SU")) {
return nil, errors.New("doesn't contain an user seed nkey")
}
kp, err := nkeys.FromSeed(seed)
if err != nil {
return nil, err
}
return kp, nil
}
// Just wipe slice with 'x', for clearing contents of nkey seed file.
func wipeSlice(buf []byte) {
for i := range buf {
buf[i] = 'x'
}
}

View File

@@ -17,14 +17,16 @@ package jwt
import ( import (
"fmt" "fmt"
"time"
) )
// Export represents a single export // Export represents a single export
type Export struct { type Export struct {
Name string `json:"name,omitempty"` Name string `json:"name,omitempty"`
Subject Subject `json:"subject,omitempty"` Subject Subject `json:"subject,omitempty"`
Type ExportType `json:"type,omitempty"` Type ExportType `json:"type,omitempty"`
TokenReq bool `json:"token_req,omitempty"` TokenReq bool `json:"token_req,omitempty"`
Revocations RevocationList `json:"revocations,omitempty"`
} }
// IsService returns true if an export is for a service // IsService returns true if an export is for a service
@@ -45,6 +47,38 @@ func (e *Export) Validate(vr *ValidationResults) {
e.Subject.Validate(vr) e.Subject.Validate(vr)
} }
// Revoke enters a revocation by publickey using time.Now().
func (e *Export) Revoke(pubKey string) {
e.RevokeAt(pubKey, time.Now())
}
// RevokeAt enters a revocation by publickey and timestamp into this export
// If there is already a revocation for this public key that is newer, it is kept.
func (e *Export) RevokeAt(pubKey string, timestamp time.Time) {
if e.Revocations == nil {
e.Revocations = RevocationList{}
}
e.Revocations.Revoke(pubKey, timestamp)
}
// ClearRevocation removes any revocation for the public key
func (e *Export) ClearRevocation(pubKey string) {
e.Revocations.ClearRevocation(pubKey)
}
// IsRevokedAt checks if the public key is in the revoked list with a timestamp later than
// the one passed in. Generally this method is called with time.Now() but other time's can
// be used for testing.
func (e *Export) IsRevokedAt(pubKey string, timestamp time.Time) bool {
return e.Revocations.IsRevoked(pubKey, timestamp)
}
// IsRevoked checks if the public key is in the revoked list with time.Now()
func (e *Export) IsRevoked(pubKey string) bool {
return e.Revocations.IsRevoked(pubKey, time.Now())
}
// Exports is an array of exports // Exports is an array of exports
type Exports []*Export type Exports []*Export

View File

@@ -48,14 +48,14 @@ func (gc *GenericClaims) Claims() *ClaimsData {
return &gc.ClaimsData return &gc.ClaimsData
} }
// Payload returns the custom part of the claiims data // Payload returns the custom part of the claims data
func (gc *GenericClaims) Payload() interface{} { func (gc *GenericClaims) Payload() interface{} {
return &gc.Data return &gc.Data
} }
// Encode takes a generic claims and creates a JWT string // Encode takes a generic claims and creates a JWT string
func (gc *GenericClaims) Encode(pair nkeys.KeyPair) (string, error) { func (gc *GenericClaims) Encode(pair nkeys.KeyPair) (string, error) {
return gc.ClaimsData.encode(pair, gc) return gc.ClaimsData.Encode(pair, gc)
} }
// Validate checks the generic part of the claims data // Validate checks the generic part of the claims data

View File

@@ -73,6 +73,10 @@ func (o *Operator) validateAccountServerURL() error {
// ValidateOperatorServiceURL returns an error if the URL is not a valid NATS or TLS url. // ValidateOperatorServiceURL returns an error if the URL is not a valid NATS or TLS url.
func ValidateOperatorServiceURL(v string) error { func ValidateOperatorServiceURL(v string) error {
// should be possible for the service url to not be expressed
if v == "" {
return nil
}
u, err := url.Parse(v) u, err := url.Parse(v)
if err != nil { if err != nil {
return fmt.Errorf("error parsing operator service url %q: %v", v, err) return fmt.Errorf("error parsing operator service url %q: %v", v, err)
@@ -152,7 +156,7 @@ func (oc *OperatorClaims) Encode(pair nkeys.KeyPair) (string, error) {
return "", err return "", err
} }
oc.ClaimsData.Type = OperatorClaim oc.ClaimsData.Type = OperatorClaim
return oc.ClaimsData.encode(pair, oc) return oc.ClaimsData.Encode(pair, oc)
} }
// DecodeOperatorClaims tries to create an operator claims from a JWt string // DecodeOperatorClaims tries to create an operator claims from a JWt string

View File

@@ -1,105 +0,0 @@
/*
* Copyright 2018 The NATS Authors
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package jwt
import (
"github.com/nats-io/nkeys"
)
// Revocation defines the custom parts of a revocation JWt
type Revocation struct {
JWT string `json:"jwt,omitempty"`
Reason string `json:"reason,omitempty"`
}
// Validate checks the JWT and reason for a revocation
func (u *Revocation) Validate(vr *ValidationResults) {
if u.JWT == "" {
vr.AddError("revocation token has no JWT to revoke")
}
_, err := DecodeGeneric(u.JWT)
if err != nil {
vr.AddError("revocation token has an invalid JWT")
}
}
// RevocationClaims defines a revocation tokens data
type RevocationClaims struct {
ClaimsData
Revocation `json:"nats,omitempty"`
}
// NewRevocationClaims creates a new revocation JWT for the specified subject/public key
func NewRevocationClaims(subject string) *RevocationClaims {
if subject == "" {
return nil
}
c := &RevocationClaims{}
c.Subject = subject
return c
}
// Encode translates the claims to a JWT string
func (rc *RevocationClaims) Encode(pair nkeys.KeyPair) (string, error) {
rc.ClaimsData.Type = RevocationClaim
return rc.ClaimsData.encode(pair, rc)
}
// DecodeRevocationClaims tries to parse a JWT string as a RevocationClaims
func DecodeRevocationClaims(token string) (*RevocationClaims, error) {
v := RevocationClaims{}
if err := Decode(token, &v); err != nil {
return nil, err
}
return &v, nil
}
func (rc *RevocationClaims) String() string {
return rc.ClaimsData.String(rc)
}
// Payload returns the revocation specific part of the claims
func (rc *RevocationClaims) Payload() interface{} {
return &rc.Revocation
}
// Validate checks the generic and revocation parts of the claims
func (rc *RevocationClaims) Validate(vr *ValidationResults) {
rc.ClaimsData.Validate(vr)
rc.Revocation.Validate(vr)
theJWT, err := DecodeGeneric(rc.Revocation.JWT)
if err != nil {
vr.AddError("revocation contains an invalid JWT")
return // can't do the remaining checks
}
if theJWT.Issuer != rc.Issuer {
vr.AddError("Revocation issuer doesn't match JWT to revoke")
}
}
// ExpectedPrefixes defines who can sign a revocation token, account or operator
func (rc *RevocationClaims) ExpectedPrefixes() []nkeys.PrefixByte {
return []nkeys.PrefixByte{nkeys.PrefixByteOperator, nkeys.PrefixByteAccount}
}
// Claims returns the generic part of the claims
func (rc *RevocationClaims) Claims() *ClaimsData {
return &rc.ClaimsData
}

32
vendor/github.com/nats-io/jwt/revocation_list.go generated vendored Normal file
View File

@@ -0,0 +1,32 @@
package jwt
import (
"time"
)
// RevocationList is used to store a mapping of public keys to unix timestamps
type RevocationList map[string]int64
// Revoke enters a revocation by publickey and timestamp into this export
// If there is already a revocation for this public key that is newer, it is kept.
func (r RevocationList) Revoke(pubKey string, timestamp time.Time) {
newTS := timestamp.Unix()
if ts, ok := r[pubKey]; ok && ts > newTS {
return
}
r[pubKey] = newTS
}
// ClearRevocation removes any revocation for the public key
func (r RevocationList) ClearRevocation(pubKey string) {
delete(r, pubKey)
}
// IsRevoked checks if the public key is in the revoked list with a timestamp later than
// the one passed in. Generally this method is called with time.Now() but other time's can
// be used for testing.
func (r RevocationList) IsRevoked(pubKey string, timestamp time.Time) bool {
ts, ok := r[pubKey]
return ok && ts > timestamp.Unix()
}

View File

@@ -56,7 +56,7 @@ func (s *ServerClaims) Encode(pair nkeys.KeyPair) (string, error) {
return "", errors.New("expected subject to be a server public key") return "", errors.New("expected subject to be a server public key")
} }
s.ClaimsData.Type = ServerClaim s.ClaimsData.Type = ServerClaim
return s.ClaimsData.encode(pair, s) return s.ClaimsData.Encode(pair, s)
} }
// DecodeServerClaims tries to parse server claims from a JWT string // DecodeServerClaims tries to parse server claims from a JWT string

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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
@@ -220,16 +220,32 @@ func (p *Permission) Validate(vr *ValidationResults) {
} }
} }
// ResponsePermission can be used to allow responses to any reply subject
// that is received on a valid subscription.
type ResponsePermission struct {
MaxMsgs int `json:"max"`
Expires time.Duration `json:"ttl"`
}
// Validate the response permission.
func (p *ResponsePermission) Validate(vr *ValidationResults) {
// Any values can be valid for now.
}
// Permissions are used to restrict subject access, either on a user or for everyone on a server by default // Permissions are used to restrict subject access, either on a user or for everyone on a server by default
type Permissions struct { type Permissions struct {
Pub Permission `json:"pub,omitempty"` Pub Permission `json:"pub,omitempty"`
Sub Permission `json:"sub,omitempty"` Sub Permission `json:"sub,omitempty"`
Resp *ResponsePermission `json:"resp,omitempty"`
} }
// Validate the pub and sub fields in the permissions list // Validate the pub and sub fields in the permissions list
func (p *Permissions) Validate(vr *ValidationResults) { func (p *Permissions) Validate(vr *ValidationResults) {
p.Pub.Validate(vr) p.Pub.Validate(vr)
p.Sub.Validate(vr) p.Sub.Validate(vr)
if p.Resp != nil {
p.Resp.Validate(vr)
}
} }
// StringList is a wrapper for an array of strings // StringList is a wrapper for an array of strings

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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
@@ -58,7 +58,7 @@ func (u *UserClaims) Encode(pair nkeys.KeyPair) (string, error) {
return "", errors.New("expected subject to be user public key") return "", errors.New("expected subject to be user public key")
} }
u.ClaimsData.Type = UserClaim u.ClaimsData.Type = UserClaim
return u.ClaimsData.encode(pair, u) return u.ClaimsData.Encode(pair, u)
} }
// DecodeUserClaims tries to parse a user claims from a JWT string // DecodeUserClaims tries to parse a user claims from a JWT string

View File

@@ -197,8 +197,11 @@ const (
FILE_MAP_READ = 0x04 FILE_MAP_READ = 0x04
FILE_MAP_EXECUTE = 0x20 FILE_MAP_EXECUTE = 0x20
CTRL_C_EVENT = 0 CTRL_C_EVENT = 0
CTRL_BREAK_EVENT = 1 CTRL_BREAK_EVENT = 1
CTRL_CLOSE_EVENT = 2
CTRL_LOGOFF_EVENT = 5
CTRL_SHUTDOWN_EVENT = 6
// Windows reserves errors >= 1<<29 for application use. // Windows reserves errors >= 1<<29 for application use.
APPLICATION_ERROR = 1 << 29 APPLICATION_ERROR = 1 << 29

4
vendor/modules.txt vendored
View File

@@ -1,4 +1,4 @@
# github.com/nats-io/jwt v0.2.8 # github.com/nats-io/jwt v0.2.13-0.20190726194050-829b612a49c3
github.com/nats-io/jwt github.com/nats-io/jwt
# github.com/nats-io/nats.go v1.8.1 # github.com/nats-io/nats.go v1.8.1
github.com/nats-io/nats.go github.com/nats-io/nats.go
@@ -13,7 +13,7 @@ golang.org/x/crypto/bcrypt
golang.org/x/crypto/ed25519 golang.org/x/crypto/ed25519
golang.org/x/crypto/blowfish golang.org/x/crypto/blowfish
golang.org/x/crypto/ed25519/internal/edwards25519 golang.org/x/crypto/ed25519/internal/edwards25519
# golang.org/x/sys v0.0.0-20190710143415-6ec70d6a5542 # golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e
golang.org/x/sys/windows/svc/eventlog golang.org/x/sys/windows/svc/eventlog
golang.org/x/sys/windows/svc golang.org/x/sys/windows/svc
golang.org/x/sys/windows/svc/debug golang.org/x/sys/windows/svc/debug