mirror of
https://github.com/gogrlx/nats-server.git
synced 2026-04-02 03:38:42 -07:00
Add JWT support for users, accounts and import activations.
Add in trusted keys options and binary stamp User JWT and Account fetch with AccountResolver Account and User expiration Account Imports/Exports w/ updates Import activation expiration Signed-off-by: Derek Collison <derek@nats.io>
This commit is contained in:
@@ -5,6 +5,7 @@ go:
|
||||
install:
|
||||
- go get github.com/nats-io/go-nats
|
||||
- go get github.com/nats-io/nkeys
|
||||
- go get github.com/nats-io/jwt
|
||||
- go get github.com/mattn/goveralls
|
||||
- go get github.com/wadey/gocovmerge
|
||||
- go get -u honnef.co/go/tools/cmd/megacheck
|
||||
|
||||
@@ -14,10 +14,16 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/nats-io/jwt"
|
||||
)
|
||||
|
||||
// For backwards compatibility, users who are not explicitly defined into an
|
||||
@@ -36,9 +42,13 @@ type rme struct {
|
||||
type Account struct {
|
||||
Name string
|
||||
Nkey string
|
||||
Issuer string
|
||||
claimJWT string
|
||||
updated time.Time
|
||||
mu sync.RWMutex
|
||||
sl *Sublist
|
||||
clients int
|
||||
etmr *time.Timer
|
||||
clients map[*client]*client
|
||||
rm map[string]*rme
|
||||
imports importMap
|
||||
exports exportMap
|
||||
@@ -46,22 +56,33 @@ type Account struct {
|
||||
maxnae int
|
||||
maxaettl time.Duration
|
||||
pruning bool
|
||||
expired bool
|
||||
}
|
||||
|
||||
// Import stream mapping struct
|
||||
type streamImport struct {
|
||||
acc *Account
|
||||
from string
|
||||
prefix string
|
||||
acc *Account
|
||||
from string
|
||||
prefix string
|
||||
claim *jwt.Import
|
||||
invalid bool
|
||||
}
|
||||
|
||||
// Import service mapping struct
|
||||
type serviceImport struct {
|
||||
acc *Account
|
||||
from string
|
||||
to string
|
||||
ae bool
|
||||
ts int64
|
||||
acc *Account
|
||||
from string
|
||||
to string
|
||||
ae bool
|
||||
ts int64
|
||||
claim *jwt.Import
|
||||
}
|
||||
|
||||
// exportAuth holds configured approvals or boolean indicating an
|
||||
// auth token is required for import.
|
||||
type exportAuth struct {
|
||||
tokenReq bool
|
||||
approved map[string]*Account
|
||||
}
|
||||
|
||||
// importMap tracks the imported streams and services.
|
||||
@@ -72,15 +93,15 @@ type importMap struct {
|
||||
|
||||
// exportMap tracks the exported streams and services.
|
||||
type exportMap struct {
|
||||
streams map[string]map[string]*Account
|
||||
services map[string]map[string]*Account
|
||||
streams map[string]*exportAuth
|
||||
services map[string]*exportAuth
|
||||
}
|
||||
|
||||
// NumClients returns active number of clients for this account.
|
||||
func (a *Account) NumClients() int {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
return a.clients
|
||||
return len(a.clients)
|
||||
}
|
||||
|
||||
// RoutedSubs returns how many subjects we would send across a route when first
|
||||
@@ -99,12 +120,11 @@ func (a *Account) TotalSubs() int {
|
||||
}
|
||||
|
||||
// addClient keeps our accounting of active clients updated.
|
||||
// Call in with client but just track total for now.
|
||||
// Returns previous total.
|
||||
func (a *Account) addClient(c *client) int {
|
||||
a.mu.Lock()
|
||||
n := a.clients
|
||||
a.clients++
|
||||
n := len(a.clients)
|
||||
a.clients[c] = c
|
||||
a.mu.Unlock()
|
||||
return n
|
||||
}
|
||||
@@ -112,8 +132,8 @@ func (a *Account) addClient(c *client) int {
|
||||
// removeClient keeps our accounting of active clients updated.
|
||||
func (a *Account) removeClient(c *client) int {
|
||||
a.mu.Lock()
|
||||
n := a.clients
|
||||
a.clients--
|
||||
n := len(a.clients)
|
||||
delete(a.clients, c)
|
||||
a.mu.Unlock()
|
||||
return n
|
||||
}
|
||||
@@ -126,16 +146,22 @@ func (a *Account) AddServiceExport(subject string, accounts []*Account) error {
|
||||
return ErrMissingAccount
|
||||
}
|
||||
if a.exports.services == nil {
|
||||
a.exports.services = make(map[string]map[string]*Account)
|
||||
a.exports.services = make(map[string]*exportAuth)
|
||||
}
|
||||
ma := a.exports.services[subject]
|
||||
if accounts != nil && ma == nil {
|
||||
ma = make(map[string]*Account)
|
||||
ea := a.exports.services[subject]
|
||||
if accounts != nil && ea == nil {
|
||||
ea = &exportAuth{}
|
||||
// empty means auth required but will be import token.
|
||||
if len(accounts) == 0 {
|
||||
ea.tokenReq = true
|
||||
} else {
|
||||
ea.approved = make(map[string]*Account)
|
||||
for _, acc := range accounts {
|
||||
ea.approved[acc.Name] = acc
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, a := range accounts {
|
||||
ma[a.Name] = a
|
||||
}
|
||||
a.exports.services[subject] = ma
|
||||
a.exports.services[subject] = ea
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -146,11 +172,7 @@ func (a *Account) numServiceRoutes() int {
|
||||
return len(a.imports.services)
|
||||
}
|
||||
|
||||
// AddServiceImport will add a route to an account to send published messages / requests
|
||||
// to the destination account. From is the local subject to map, To is the
|
||||
// subject that will appear on the destination account. Destination will need
|
||||
// to have an import rule to allow access via addService.
|
||||
func (a *Account) AddServiceImport(destination *Account, from, to string) error {
|
||||
func (a *Account) AddServiceImportWithClaim(destination *Account, from, to string, imClaim *jwt.Import) error {
|
||||
if destination == nil {
|
||||
return ErrMissingAccount
|
||||
}
|
||||
@@ -162,11 +184,19 @@ func (a *Account) AddServiceImport(destination *Account, from, to string) error
|
||||
return ErrInvalidSubject
|
||||
}
|
||||
// First check to see if the account has authorized us to route to the "to" subject.
|
||||
if !destination.checkServiceImportAuthorized(a, to) {
|
||||
if !destination.checkServiceImportAuthorized(a, to, imClaim) {
|
||||
return ErrServiceImportAuthorization
|
||||
}
|
||||
|
||||
return a.addImplicitServiceImport(destination, from, to, false)
|
||||
return a.addImplicitServiceImport(destination, from, to, false, imClaim)
|
||||
}
|
||||
|
||||
// AddServiceImport will add a route to an account to send published messages / requests
|
||||
// to the destination account. From is the local subject to map, To is the
|
||||
// subject that will appear on the destination account. Destination will need
|
||||
// to have an import rule to allow access via addService.
|
||||
func (a *Account) AddServiceImport(destination *Account, from, to string) error {
|
||||
return a.AddServiceImportWithClaim(destination, from, to, nil)
|
||||
}
|
||||
|
||||
// removeServiceImport will remove the route by subject.
|
||||
@@ -239,12 +269,12 @@ func (a *Account) autoExpireResponseMaps() []*serviceImport {
|
||||
// Add a route to connect from an implicit route created for a response to a request.
|
||||
// This does no checks and should be only called by the msg processing code. Use
|
||||
// addServiceImport from above if responding to user input or config, etc.
|
||||
func (a *Account) addImplicitServiceImport(destination *Account, from, to string, autoexpire bool) error {
|
||||
func (a *Account) addImplicitServiceImport(destination *Account, from, to string, autoexpire bool, claim *jwt.Import) error {
|
||||
a.mu.Lock()
|
||||
if a.imports.services == nil {
|
||||
a.imports.services = make(map[string]*serviceImport)
|
||||
}
|
||||
si := &serviceImport{destination, from, to, autoexpire, 0}
|
||||
si := &serviceImport{destination, from, to, autoexpire, 0, claim}
|
||||
a.imports.services[from] = si
|
||||
if autoexpire {
|
||||
a.nae++
|
||||
@@ -300,14 +330,14 @@ func (a *Account) pruneAutoExpireResponseMaps() {
|
||||
}
|
||||
}
|
||||
|
||||
// AddStreamImport will add in the stream import from a specific account.
|
||||
func (a *Account) AddStreamImport(account *Account, from, prefix string) error {
|
||||
// AddStreamImport will add in the stream import from a specific account with optional token.
|
||||
func (a *Account) AddStreamImportWithClaim(account *Account, from, prefix string, imClaim *jwt.Import) error {
|
||||
if account == nil {
|
||||
return ErrMissingAccount
|
||||
}
|
||||
|
||||
// First check to see if the account has authorized export of the subject.
|
||||
if !account.checkStreamImportAuthorized(a, from) {
|
||||
if !account.checkStreamImportAuthorized(a, from, imClaim) {
|
||||
return ErrStreamImportAuthorization
|
||||
}
|
||||
|
||||
@@ -320,10 +350,15 @@ func (a *Account) AddStreamImport(account *Account, from, prefix string) error {
|
||||
prefix = prefix + string(btsep)
|
||||
}
|
||||
// TODO(dlc) - collisions, etc.
|
||||
a.imports.streams[from] = &streamImport{account, from, prefix}
|
||||
a.imports.streams[from] = &streamImport{account, from, prefix, imClaim, false}
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddStreamImport will add in the stream import from a specific account.
|
||||
func (a *Account) AddStreamImport(account *Account, from, prefix string) error {
|
||||
return a.AddStreamImportWithClaim(account, from, prefix, nil)
|
||||
}
|
||||
|
||||
// IsPublicExport is a placeholder to denote public export.
|
||||
var IsPublicExport = []*Account(nil)
|
||||
|
||||
@@ -336,38 +371,51 @@ func (a *Account) AddStreamExport(subject string, accounts []*Account) error {
|
||||
return ErrMissingAccount
|
||||
}
|
||||
if a.exports.streams == nil {
|
||||
a.exports.streams = make(map[string]map[string]*Account)
|
||||
a.exports.streams = make(map[string]*exportAuth)
|
||||
}
|
||||
var ma map[string]*Account
|
||||
for _, aa := range accounts {
|
||||
if ma == nil {
|
||||
ma = make(map[string]*Account, len(accounts))
|
||||
ea := a.exports.services[subject]
|
||||
if accounts != nil && ea == nil {
|
||||
ea = &exportAuth{}
|
||||
// empty means auth required but will be import token.
|
||||
if len(accounts) == 0 {
|
||||
ea.tokenReq = true
|
||||
} else {
|
||||
ea.approved = make(map[string]*Account)
|
||||
}
|
||||
ma[aa.Name] = aa
|
||||
}
|
||||
a.exports.streams[subject] = ma
|
||||
for _, acc := range accounts {
|
||||
ea.approved[acc.Name] = acc
|
||||
}
|
||||
a.exports.streams[subject] = ea
|
||||
return nil
|
||||
}
|
||||
|
||||
// Check if another account is authorized to import from us.
|
||||
func (a *Account) checkStreamImportAuthorized(account *Account, subject string) bool {
|
||||
func (a *Account) checkStreamImportAuthorized(account *Account, subject string, imClaim *jwt.Import) bool {
|
||||
// Find the subject in the exports list.
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
return a.checkStreamImportAuthorizedNoLock(account, subject, imClaim)
|
||||
}
|
||||
|
||||
func (a *Account) checkStreamImportAuthorizedNoLock(account *Account, subject string, imClaim *jwt.Import) bool {
|
||||
if a.exports.streams == nil || !IsValidSubject(subject) {
|
||||
return false
|
||||
}
|
||||
|
||||
// Check direct match of subject first
|
||||
am, ok := a.exports.streams[subject]
|
||||
ea, ok := a.exports.streams[subject]
|
||||
if ok {
|
||||
// if am is nil that denotes a public export
|
||||
if am == nil {
|
||||
// if ea is nil that denotes a public export
|
||||
if ea == nil {
|
||||
return true
|
||||
}
|
||||
// Check if token required
|
||||
if ea != nil && ea.tokenReq {
|
||||
return a.checkActivation(account, imClaim, true)
|
||||
}
|
||||
// If we have a matching account we are authorized
|
||||
_, ok := am[account.Name]
|
||||
_, ok := ea.approved[account.Name]
|
||||
return ok
|
||||
}
|
||||
// ok if we are here we did not match directly so we need to test each one.
|
||||
@@ -375,18 +423,112 @@ func (a *Account) checkStreamImportAuthorized(account *Account, subject string)
|
||||
// has to be a true subset of the import claim. We already checked for
|
||||
// exact matches above.
|
||||
tokens := strings.Split(subject, tsep)
|
||||
for subj, am := range a.exports.streams {
|
||||
for subj, ea := range a.exports.streams {
|
||||
if isSubsetMatch(tokens, subj) {
|
||||
if am == nil {
|
||||
if ea == nil || ea.approved == nil {
|
||||
return true
|
||||
}
|
||||
_, ok := am[account.Name]
|
||||
// Check if token required
|
||||
if ea != nil && ea.tokenReq {
|
||||
return a.checkActivation(account, imClaim, true)
|
||||
}
|
||||
_, ok := ea.approved[account.Name]
|
||||
return ok
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Will fetch the activation token for an import.
|
||||
func fetchActivation(url string) string {
|
||||
// FIXME(dlc) - Make configurable.
|
||||
c := &http.Client{Timeout: 2 * time.Second}
|
||||
resp, err := c.Get(url)
|
||||
if err != nil || resp == nil {
|
||||
return ""
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return string(body)
|
||||
}
|
||||
|
||||
// Fires for expired activation tokens. We could track this with timers etc.
|
||||
// Instead we just re-analyze where we are and if we need to act.
|
||||
func (a *Account) activationExpired(subject string) {
|
||||
a.mu.RLock()
|
||||
if a.expired || a.imports.streams == nil {
|
||||
a.mu.RUnlock()
|
||||
return
|
||||
}
|
||||
// FIXME(dlc) - check services too?
|
||||
si := a.imports.streams[subject]
|
||||
a.mu.RUnlock()
|
||||
|
||||
if si == nil || si.invalid {
|
||||
return
|
||||
}
|
||||
if si.acc.checkActivation(a, si.claim, false) {
|
||||
// The token has been updated most likely and we are good to go.
|
||||
return
|
||||
}
|
||||
|
||||
a.mu.Lock()
|
||||
si.invalid = true
|
||||
clients := make([]*client, 0, len(a.clients))
|
||||
for _, c := range a.clients {
|
||||
clients = append(clients, c)
|
||||
}
|
||||
awcsti := map[string]struct{}{a.Name: struct{}{}}
|
||||
a.mu.Unlock()
|
||||
for _, c := range clients {
|
||||
c.processSubsOnConfigReload(awcsti)
|
||||
}
|
||||
}
|
||||
|
||||
// checkActivation will check the activation token for validity.
|
||||
func (a *Account) checkActivation(acc *Account, claim *jwt.Import, expTimer bool) bool {
|
||||
if claim == nil || claim.Token == "" {
|
||||
return false
|
||||
}
|
||||
// Create a quick clone so we can inline Token JWT.
|
||||
clone := *claim
|
||||
|
||||
// We grab the token from a URL by hand here since we need expiration etc.
|
||||
if url, err := url.Parse(clone.Token); err == nil && url.Scheme != "" {
|
||||
clone.Token = fetchActivation(url.String())
|
||||
}
|
||||
vr := jwt.CreateValidationResults()
|
||||
clone.Validate(a.Name, vr)
|
||||
if vr.IsBlocking(true) {
|
||||
return false
|
||||
}
|
||||
act, err := jwt.DecodeActivationClaims(clone.Token)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
vr = jwt.CreateValidationResults()
|
||||
act.Validate(vr)
|
||||
if vr.IsBlocking(true) {
|
||||
return false
|
||||
}
|
||||
if act.Expires != 0 {
|
||||
tn := time.Now().Unix()
|
||||
if act.Expires <= tn {
|
||||
return false
|
||||
}
|
||||
if expTimer && len(act.Exports) > 0 {
|
||||
expiresAt := time.Duration(act.Expires - tn)
|
||||
time.AfterFunc(expiresAt*time.Second, func() {
|
||||
acc.activationExpired(string(act.Exports[0].Subject))
|
||||
})
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// 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
|
||||
@@ -409,8 +551,24 @@ func (a *Account) checkStreamImportsEqual(b *Account) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (a *Account) checkStreamExportsEqual(b *Account) bool {
|
||||
if len(a.exports.streams) != len(b.exports.streams) {
|
||||
return false
|
||||
}
|
||||
for subj, aea := range a.exports.streams {
|
||||
bea, ok := b.exports.streams[subj]
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
if !reflect.DeepEqual(aea, bea) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Check if another account is authorized to route requests to this service.
|
||||
func (a *Account) checkServiceImportAuthorized(account *Account, subject string) bool {
|
||||
func (a *Account) checkServiceImportAuthorized(account *Account, subject string, imClaim *jwt.Import) bool {
|
||||
// Find the subject in the services list.
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
@@ -419,15 +577,229 @@ func (a *Account) checkServiceImportAuthorized(account *Account, subject string)
|
||||
return false
|
||||
}
|
||||
// These are always literal subjects so just lookup.
|
||||
am, ok := a.exports.services[subject]
|
||||
ae, ok := a.exports.services[subject]
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
if ae != nil && ae.tokenReq {
|
||||
return a.checkActivation(account, imClaim, false)
|
||||
}
|
||||
|
||||
// Check to see if we are public or if we need to search for the account.
|
||||
if am == nil {
|
||||
if ae == nil || ae.approved == nil {
|
||||
return true
|
||||
}
|
||||
// Check that we allow this account.
|
||||
_, ok = am[account.Name]
|
||||
_, ok = ae.approved[account.Name]
|
||||
return ok
|
||||
}
|
||||
|
||||
// IsExpired returns expiration status.
|
||||
func (a *Account) IsExpired() bool {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
return a.expired
|
||||
}
|
||||
|
||||
// Called when an account has expired.
|
||||
func (a *Account) expiredTimeout() {
|
||||
// Collect the clients.
|
||||
a.mu.Lock()
|
||||
a.expired = true
|
||||
a.mu.Unlock()
|
||||
|
||||
cs := make([]*client, 0, len(a.clients))
|
||||
a.mu.RLock()
|
||||
for c := range a.clients {
|
||||
cs = append(cs, c)
|
||||
}
|
||||
a.mu.RUnlock()
|
||||
|
||||
for _, c := range cs {
|
||||
c.accountAuthExpired()
|
||||
}
|
||||
}
|
||||
|
||||
// Sets the expiration timer for an account JWT that has it set.
|
||||
func (a *Account) setExpirationTimer(d time.Duration) {
|
||||
a.etmr = time.AfterFunc(d, a.expiredTimeout)
|
||||
}
|
||||
|
||||
// Lock should be held
|
||||
func (a *Account) clearExpirationTimer() bool {
|
||||
if a.etmr == nil {
|
||||
return true
|
||||
}
|
||||
stopped := a.etmr.Stop()
|
||||
a.etmr = nil
|
||||
return stopped
|
||||
}
|
||||
|
||||
func (a *Account) checkExpiration(claims *jwt.ClaimsData) {
|
||||
a.clearExpirationTimer()
|
||||
if claims.Expires == 0 {
|
||||
a.expired = false
|
||||
return
|
||||
}
|
||||
tn := time.Now().Unix()
|
||||
if claims.Expires <= tn {
|
||||
a.expired = true
|
||||
return
|
||||
}
|
||||
expiresAt := time.Duration(claims.Expires - tn)
|
||||
a.setExpirationTimer(expiresAt * time.Second)
|
||||
a.expired = false
|
||||
}
|
||||
|
||||
// Placeholder for signaling token auth required.
|
||||
var tokenAuthReq = []*Account{}
|
||||
|
||||
func authAccounts(tokenReq bool) []*Account {
|
||||
if tokenReq {
|
||||
return tokenAuthReq
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetAccountResolver will assign the account resolver.
|
||||
func (s *Server) SetAccountResolver(ar AccountResolver) {
|
||||
s.mu.Lock()
|
||||
s.accResolver = ar
|
||||
s.mu.Unlock()
|
||||
}
|
||||
|
||||
// UpdateAccountClaims will update and existing account with new claims.
|
||||
// This will replace any exports or imports previously defined.
|
||||
func (s *Server) UpdateAccountClaims(a *Account, ac *jwt.AccountClaims) {
|
||||
if a == nil {
|
||||
return
|
||||
}
|
||||
a.checkExpiration(ac.Claims())
|
||||
|
||||
// Clone to update
|
||||
old := &Account{Name: a.Name, imports: a.imports, exports: a.exports}
|
||||
|
||||
// Reset exports and imports here.
|
||||
a.exports = exportMap{}
|
||||
a.imports = importMap{}
|
||||
|
||||
for _, e := range ac.Exports {
|
||||
switch e.Type {
|
||||
case jwt.Stream:
|
||||
if err := a.AddStreamExport(string(e.Subject), authAccounts(e.TokenReq)); err != nil {
|
||||
s.Debugf("Error adding stream export to account [%s]: %v", a.Name, err.Error())
|
||||
}
|
||||
case jwt.Service:
|
||||
if err := a.AddServiceExport(string(e.Subject), authAccounts(e.TokenReq)); err != nil {
|
||||
s.Debugf("Error adding service export to account [%s]: %v", a.Name, err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, i := range ac.Imports {
|
||||
acc := s.accounts[i.Account]
|
||||
if acc == nil {
|
||||
if acc = s.FetchAccount(i.Account); acc == nil {
|
||||
s.Debugf("Can't locate account [%s] for import of [%v] %s", i.Account, i.Subject, i.Type)
|
||||
continue
|
||||
}
|
||||
}
|
||||
switch i.Type {
|
||||
case jwt.Stream:
|
||||
a.AddStreamImportWithClaim(acc, string(i.Subject), string(i.To), i)
|
||||
case jwt.Service:
|
||||
a.AddServiceImportWithClaim(acc, string(i.Subject), string(i.To), i)
|
||||
}
|
||||
}
|
||||
// Now let's apply any needed changes from import/export changes.
|
||||
if !a.checkStreamImportsEqual(old) {
|
||||
awcsti := map[string]struct{}{a.Name: struct{}{}}
|
||||
a.mu.RLock()
|
||||
clients := make([]*client, 0, len(a.clients))
|
||||
for _, c := range a.clients {
|
||||
clients = append(clients, c)
|
||||
}
|
||||
a.mu.RUnlock()
|
||||
for _, c := range clients {
|
||||
c.processSubsOnConfigReload(awcsti)
|
||||
}
|
||||
}
|
||||
// Now check if exports have changed.
|
||||
if !a.checkStreamExportsEqual(old) {
|
||||
clients := make([]*client, 0, 16)
|
||||
// We need to check all accounts that have an import claim from this account.
|
||||
awcsti := map[string]struct{}{}
|
||||
for _, acc := range s.accounts {
|
||||
acc.mu.Lock()
|
||||
for _, im := range acc.imports.streams {
|
||||
if im != nil && im.acc.Name == a.Name {
|
||||
// Check for if we are still authorized for an import.
|
||||
im.invalid = !a.checkStreamImportAuthorizedNoLock(im.acc, im.from, im.claim)
|
||||
awcsti[acc.Name] = struct{}{}
|
||||
for _, c := range acc.clients {
|
||||
clients = append(clients, c)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
acc.mu.Unlock()
|
||||
}
|
||||
// Now walk clients.
|
||||
for _, c := range clients {
|
||||
c.processSubsOnConfigReload(awcsti)
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME(dlc) - Limits etc..
|
||||
}
|
||||
|
||||
// Helper to build an internal account structure from a jwt.AccountClaims.
|
||||
func (s *Server) buildInternalAccount(ac *jwt.AccountClaims) *Account {
|
||||
acc := &Account{Name: ac.Subject, Issuer: ac.Issuer}
|
||||
s.UpdateAccountClaims(acc, ac)
|
||||
return acc
|
||||
}
|
||||
|
||||
// Helper to build internal NKeyUser.
|
||||
func buildInternalNkeyUser(uc *jwt.UserClaims, acc *Account) *NkeyUser {
|
||||
nu := &NkeyUser{Nkey: uc.Subject, Account: acc}
|
||||
|
||||
// Now check for permissions.
|
||||
var p *Permissions
|
||||
|
||||
if len(uc.Pub.Allow) > 0 || len(uc.Pub.Deny) > 0 {
|
||||
if p == nil {
|
||||
p = &Permissions{}
|
||||
}
|
||||
p.Publish = &SubjectPermission{}
|
||||
p.Publish.Allow = uc.Pub.Allow
|
||||
p.Publish.Deny = uc.Pub.Deny
|
||||
}
|
||||
if len(uc.Sub.Allow) > 0 || len(uc.Sub.Deny) > 0 {
|
||||
if p == nil {
|
||||
p = &Permissions{}
|
||||
}
|
||||
p.Subscribe = &SubjectPermission{}
|
||||
p.Subscribe.Allow = uc.Sub.Allow
|
||||
p.Subscribe.Deny = uc.Sub.Deny
|
||||
}
|
||||
nu.Permissions = p
|
||||
return nu
|
||||
}
|
||||
|
||||
// AccountResolver interface. This is to fetch Account JWTs by public nkeys
|
||||
type AccountResolver interface {
|
||||
Fetch(pub string) (string, error)
|
||||
}
|
||||
|
||||
// Mostly for testing.
|
||||
type MemAccResolver struct {
|
||||
sync.Map
|
||||
}
|
||||
|
||||
func (m *MemAccResolver) Fetch(pub string) (string, error) {
|
||||
if j, ok := m.Load(pub); ok {
|
||||
return j.(string), nil
|
||||
}
|
||||
return "", ErrMissingAccount
|
||||
}
|
||||
|
||||
@@ -527,60 +527,60 @@ func TestImportExportConfigFailures(t *testing.T) {
|
||||
func TestImportAuthorized(t *testing.T) {
|
||||
_, foo, bar := simpleAccountServer(t)
|
||||
|
||||
checkBool(foo.checkStreamImportAuthorized(bar, "foo"), false, t)
|
||||
checkBool(foo.checkStreamImportAuthorized(bar, "*"), false, t)
|
||||
checkBool(foo.checkStreamImportAuthorized(bar, ">"), false, t)
|
||||
checkBool(foo.checkStreamImportAuthorized(bar, "foo.*"), false, t)
|
||||
checkBool(foo.checkStreamImportAuthorized(bar, "foo.>"), false, t)
|
||||
checkBool(foo.checkStreamImportAuthorized(bar, "foo", nil), false, t)
|
||||
checkBool(foo.checkStreamImportAuthorized(bar, "*", nil), false, t)
|
||||
checkBool(foo.checkStreamImportAuthorized(bar, ">", nil), false, t)
|
||||
checkBool(foo.checkStreamImportAuthorized(bar, "foo.*", nil), false, t)
|
||||
checkBool(foo.checkStreamImportAuthorized(bar, "foo.>", nil), false, t)
|
||||
|
||||
foo.AddStreamExport("foo", IsPublicExport)
|
||||
checkBool(foo.checkStreamImportAuthorized(bar, "foo"), true, t)
|
||||
checkBool(foo.checkStreamImportAuthorized(bar, "bar"), false, t)
|
||||
checkBool(foo.checkStreamImportAuthorized(bar, "*"), false, t)
|
||||
checkBool(foo.checkStreamImportAuthorized(bar, "foo", nil), true, t)
|
||||
checkBool(foo.checkStreamImportAuthorized(bar, "bar", nil), false, t)
|
||||
checkBool(foo.checkStreamImportAuthorized(bar, "*", nil), false, t)
|
||||
|
||||
foo.AddStreamExport("*", []*Account{bar})
|
||||
checkBool(foo.checkStreamImportAuthorized(bar, "foo"), true, t)
|
||||
checkBool(foo.checkStreamImportAuthorized(bar, "bar"), true, t)
|
||||
checkBool(foo.checkStreamImportAuthorized(bar, "baz"), true, t)
|
||||
checkBool(foo.checkStreamImportAuthorized(bar, "foo.bar"), false, t)
|
||||
checkBool(foo.checkStreamImportAuthorized(bar, ">"), false, t)
|
||||
checkBool(foo.checkStreamImportAuthorized(bar, "*"), true, t)
|
||||
checkBool(foo.checkStreamImportAuthorized(bar, "foo.*"), false, t)
|
||||
checkBool(foo.checkStreamImportAuthorized(bar, "*.*"), false, t)
|
||||
checkBool(foo.checkStreamImportAuthorized(bar, "*.>"), false, t)
|
||||
checkBool(foo.checkStreamImportAuthorized(bar, "foo", nil), true, t)
|
||||
checkBool(foo.checkStreamImportAuthorized(bar, "bar", nil), true, t)
|
||||
checkBool(foo.checkStreamImportAuthorized(bar, "baz", nil), true, t)
|
||||
checkBool(foo.checkStreamImportAuthorized(bar, "foo.bar", nil), false, t)
|
||||
checkBool(foo.checkStreamImportAuthorized(bar, ">", nil), false, t)
|
||||
checkBool(foo.checkStreamImportAuthorized(bar, "*", nil), true, t)
|
||||
checkBool(foo.checkStreamImportAuthorized(bar, "foo.*", nil), false, t)
|
||||
checkBool(foo.checkStreamImportAuthorized(bar, "*.*", nil), false, t)
|
||||
checkBool(foo.checkStreamImportAuthorized(bar, "*.>", nil), false, t)
|
||||
|
||||
// Reset and test '>' public export
|
||||
_, foo, bar = simpleAccountServer(t)
|
||||
foo.AddStreamExport(">", nil)
|
||||
// Everything should work.
|
||||
checkBool(foo.checkStreamImportAuthorized(bar, "foo"), true, t)
|
||||
checkBool(foo.checkStreamImportAuthorized(bar, "bar"), true, t)
|
||||
checkBool(foo.checkStreamImportAuthorized(bar, "baz"), true, t)
|
||||
checkBool(foo.checkStreamImportAuthorized(bar, "foo.bar"), true, t)
|
||||
checkBool(foo.checkStreamImportAuthorized(bar, ">"), true, t)
|
||||
checkBool(foo.checkStreamImportAuthorized(bar, "*"), true, t)
|
||||
checkBool(foo.checkStreamImportAuthorized(bar, "foo.*"), true, t)
|
||||
checkBool(foo.checkStreamImportAuthorized(bar, "*.*"), true, t)
|
||||
checkBool(foo.checkStreamImportAuthorized(bar, "*.>"), true, t)
|
||||
checkBool(foo.checkStreamImportAuthorized(bar, "foo", nil), true, t)
|
||||
checkBool(foo.checkStreamImportAuthorized(bar, "bar", nil), true, t)
|
||||
checkBool(foo.checkStreamImportAuthorized(bar, "baz", nil), true, t)
|
||||
checkBool(foo.checkStreamImportAuthorized(bar, "foo.bar", nil), true, t)
|
||||
checkBool(foo.checkStreamImportAuthorized(bar, ">", nil), true, t)
|
||||
checkBool(foo.checkStreamImportAuthorized(bar, "*", nil), true, t)
|
||||
checkBool(foo.checkStreamImportAuthorized(bar, "foo.*", nil), true, t)
|
||||
checkBool(foo.checkStreamImportAuthorized(bar, "*.*", nil), true, t)
|
||||
checkBool(foo.checkStreamImportAuthorized(bar, "*.>", nil), true, t)
|
||||
|
||||
// Reset and test pwc and fwc
|
||||
s, foo, bar := simpleAccountServer(t)
|
||||
foo.AddStreamExport("foo.*.baz.>", []*Account{bar})
|
||||
checkBool(foo.checkStreamImportAuthorized(bar, "foo.bar.baz.1"), true, t)
|
||||
checkBool(foo.checkStreamImportAuthorized(bar, "foo.bar.baz.*"), true, t)
|
||||
checkBool(foo.checkStreamImportAuthorized(bar, "foo.*.baz.1.1"), true, t)
|
||||
checkBool(foo.checkStreamImportAuthorized(bar, "foo.22.baz.22"), true, t)
|
||||
checkBool(foo.checkStreamImportAuthorized(bar, "foo.bar.baz"), false, t)
|
||||
checkBool(foo.checkStreamImportAuthorized(bar, ""), false, t)
|
||||
checkBool(foo.checkStreamImportAuthorized(bar, "foo.bar.*.*"), false, t)
|
||||
checkBool(foo.checkStreamImportAuthorized(bar, "foo.bar.baz.1", nil), true, t)
|
||||
checkBool(foo.checkStreamImportAuthorized(bar, "foo.bar.baz.*", nil), true, t)
|
||||
checkBool(foo.checkStreamImportAuthorized(bar, "foo.*.baz.1.1", nil), true, t)
|
||||
checkBool(foo.checkStreamImportAuthorized(bar, "foo.22.baz.22", nil), true, t)
|
||||
checkBool(foo.checkStreamImportAuthorized(bar, "foo.bar.baz", nil), false, t)
|
||||
checkBool(foo.checkStreamImportAuthorized(bar, "", nil), false, t)
|
||||
checkBool(foo.checkStreamImportAuthorized(bar, "foo.bar.*.*", nil), false, t)
|
||||
|
||||
// Make sure we match the account as well
|
||||
|
||||
fb, _ := s.RegisterAccount("foobar")
|
||||
bz, _ := s.RegisterAccount("baz")
|
||||
|
||||
checkBool(foo.checkStreamImportAuthorized(fb, "foo.bar.baz.1"), false, t)
|
||||
checkBool(foo.checkStreamImportAuthorized(bz, "foo.bar.baz.1"), false, t)
|
||||
checkBool(foo.checkStreamImportAuthorized(fb, "foo.bar.baz.1", nil), false, t)
|
||||
checkBool(foo.checkStreamImportAuthorized(bz, "foo.bar.baz.1", nil), false, t)
|
||||
}
|
||||
|
||||
func TestSimpleMapping(t *testing.T) {
|
||||
@@ -1135,7 +1135,7 @@ func TestAccountMapsUsers(t *testing.T) {
|
||||
}
|
||||
|
||||
// Now test nkeys as well.
|
||||
kp, _ := nkeys.FromSeed(seed1)
|
||||
kp, _ := nkeys.FromSeed([]byte(seed1))
|
||||
pubKey, _ := kp.PublicKey()
|
||||
|
||||
c, cr, l := newClientForServer(s)
|
||||
@@ -1166,7 +1166,7 @@ func TestAccountMapsUsers(t *testing.T) {
|
||||
}
|
||||
|
||||
// Now nats account nkey user.
|
||||
kp, _ = nkeys.FromSeed(seed2)
|
||||
kp, _ = nkeys.FromSeed([]byte(seed2))
|
||||
pubKey, _ = kp.PublicKey()
|
||||
|
||||
c, cr, l = newClientForServer(s)
|
||||
|
||||
@@ -18,6 +18,7 @@ import (
|
||||
"encoding/base64"
|
||||
"strings"
|
||||
|
||||
"github.com/nats-io/jwt"
|
||||
"github.com/nats-io/nkeys"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
@@ -181,6 +182,8 @@ func (s *Server) configureAuthorization() {
|
||||
// This just checks and sets up the user map if we have multiple users.
|
||||
if opts.CustomClientAuthentication != nil {
|
||||
s.info.AuthRequired = true
|
||||
} else if len(s.trustedNkeys) > 0 {
|
||||
s.info.AuthRequired = true
|
||||
} else if opts.Nkeys != nil || opts.Users != nil {
|
||||
// Support both at the same time.
|
||||
if opts.Nkeys != nil {
|
||||
@@ -205,9 +208,9 @@ func (s *Server) configureAuthorization() {
|
||||
}
|
||||
}
|
||||
|
||||
// checkAuthorization will check authorization based on client type and
|
||||
// checkAuthentication will check based on client type and
|
||||
// return boolean indicating if client is authorized.
|
||||
func (s *Server) checkAuthorization(c *client) bool {
|
||||
func (s *Server) checkAuthentication(c *client) bool {
|
||||
switch c.typ {
|
||||
case CLIENT:
|
||||
return s.isClientAuthorized(c)
|
||||
@@ -229,14 +232,20 @@ func (s *Server) isClientAuthorized(c *client) bool {
|
||||
password := s.opts.Password
|
||||
s.optsMu.RUnlock()
|
||||
|
||||
// Check custom auth first, then nkeys, then multiple users, then token, then single user/pass.
|
||||
// Check custom auth first, then jwts, then nkeys, then multiple users, then token, then single user/pass.
|
||||
if customClientAuthentication != nil {
|
||||
return customClientAuthentication.Check(c)
|
||||
}
|
||||
|
||||
var nkey *NkeyUser
|
||||
var user *User
|
||||
var ok bool
|
||||
// Grab under lock but process after.
|
||||
var (
|
||||
nkey *NkeyUser
|
||||
juc *jwt.UserClaims
|
||||
acc *Account
|
||||
user *User
|
||||
ok bool
|
||||
err error
|
||||
)
|
||||
|
||||
s.mu.Lock()
|
||||
authRequired := s.info.AuthRequired
|
||||
@@ -246,6 +255,30 @@ func (s *Server) isClientAuthorized(c *client) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// Check if we have trustedNkeys defined in the server. If so we require a user jwt.
|
||||
if s.trustedNkeys != nil {
|
||||
if c.opts.JWT == "" {
|
||||
s.mu.Unlock()
|
||||
c.Debugf("Authentication requires a user JWT")
|
||||
return false
|
||||
}
|
||||
// So we have a valid user jwt here.
|
||||
juc, err = jwt.DecodeUserClaims(c.opts.JWT)
|
||||
if err != nil {
|
||||
// Should we debug log here?
|
||||
s.mu.Unlock()
|
||||
c.Debugf("User JWT not valid: %v", err)
|
||||
return false
|
||||
}
|
||||
vr := jwt.CreateValidationResults()
|
||||
juc.Validate(vr)
|
||||
if vr.IsBlocking(true) {
|
||||
s.mu.Unlock()
|
||||
c.Debugf("User JWT no longer valid: %+v", vr)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// Check if we have nkeys or users for client.
|
||||
hasNkeys := s.nkeys != nil
|
||||
hasUsers := s.users != nil
|
||||
@@ -264,7 +297,48 @@ func (s *Server) isClientAuthorized(c *client) bool {
|
||||
}
|
||||
s.mu.Unlock()
|
||||
|
||||
// Verify the signature against the nonce.
|
||||
// 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 {
|
||||
c.Debugf("Account JWT can not be found")
|
||||
return false
|
||||
}
|
||||
if !s.isTrustedIssuer(acc.Issuer) {
|
||||
c.Debugf("Account JWT not signed by trusted operator")
|
||||
return false
|
||||
}
|
||||
if acc.IsExpired() {
|
||||
c.Debugf("Account JWT has expired")
|
||||
return false
|
||||
}
|
||||
// Verify the signature against the nonce.
|
||||
if c.opts.Sig == "" {
|
||||
c.Debugf("Signature missing")
|
||||
return false
|
||||
}
|
||||
sig, err := base64.StdEncoding.DecodeString(c.opts.Sig)
|
||||
if err != nil {
|
||||
c.Debugf("Signature not valid base64")
|
||||
return false
|
||||
}
|
||||
pub, err := nkeys.FromPublicKey([]byte(juc.Subject))
|
||||
if err != nil {
|
||||
c.Debugf("User nkey not valid: %v", err)
|
||||
return false
|
||||
}
|
||||
if err := pub.Verify(c.nonce, sig); err != nil {
|
||||
c.Debugf("Signature not verified")
|
||||
return false
|
||||
}
|
||||
nkey = buildInternalNkeyUser(juc, acc)
|
||||
c.RegisterNkeyUser(nkey)
|
||||
|
||||
// Check if we need to set an auth timer if the user jwt expires.
|
||||
c.checkExpiration(juc.Claims())
|
||||
return true
|
||||
}
|
||||
|
||||
if nkey != nil {
|
||||
if c.opts.Sig == "" {
|
||||
return false
|
||||
@@ -273,7 +347,7 @@ func (s *Server) isClientAuthorized(c *client) bool {
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
pub, err := nkeys.FromPublicKey(c.opts.Nkey)
|
||||
pub, err := nkeys.FromPublicKey([]byte(c.opts.Nkey))
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
125
server/client.go
125
server/client.go
@@ -26,6 +26,8 @@ import (
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/nats-io/jwt"
|
||||
)
|
||||
|
||||
// Type of client connection.
|
||||
@@ -128,6 +130,7 @@ const (
|
||||
DuplicateRoute
|
||||
RouteRemoved
|
||||
ServerShutdown
|
||||
AuthenticationExpired
|
||||
)
|
||||
|
||||
type client struct {
|
||||
@@ -289,6 +292,7 @@ type clientOpts struct {
|
||||
Pedantic bool `json:"pedantic"`
|
||||
TLSRequired bool `json:"tls_required"`
|
||||
Nkey string `json:"nkey,omitempty"`
|
||||
JWT string `json:"jwt,omitempty"`
|
||||
Sig string `json:"sig,omitempty"`
|
||||
Authorization string `json:"auth_token,omitempty"`
|
||||
Username string `json:"user,omitempty"`
|
||||
@@ -364,16 +368,12 @@ func (c *client) registerWithAccount(acc *Account) error {
|
||||
// If we were previously register, usually to $G, do accounting here to remove.
|
||||
if c.acc != nil {
|
||||
if prev := c.acc.removeClient(c); prev == 1 && c.srv != nil {
|
||||
c.srv.mu.Lock()
|
||||
c.srv.activeAccounts--
|
||||
c.srv.mu.Unlock()
|
||||
c.srv.decActiveAccounts()
|
||||
}
|
||||
}
|
||||
// Add in new one.
|
||||
if prev := acc.addClient(c); prev == 0 && c.srv != nil {
|
||||
c.srv.mu.Lock()
|
||||
c.srv.activeAccounts++
|
||||
c.srv.mu.Unlock()
|
||||
c.srv.incActiveAccounts()
|
||||
}
|
||||
c.mu.Lock()
|
||||
c.acc = acc
|
||||
@@ -412,14 +412,18 @@ func (c *client) RegisterUser(user *User) {
|
||||
// client with the authenticated user. This is used to map
|
||||
// any permissions into the client and setup accounts.
|
||||
func (c *client) RegisterNkeyUser(user *NkeyUser) {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
|
||||
// Register with proper account and sublist.
|
||||
if user.Account != nil {
|
||||
c.acc = user.Account
|
||||
if err := c.registerWithAccount(user.Account); err != nil {
|
||||
c.Errorf("Problem registering with account [%s]", user.Account.Name)
|
||||
c.sendErr("Failed Account Registration")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
|
||||
// Assign permissions.
|
||||
if user.Permissions == nil {
|
||||
// Reset perms to nil in case client previously had them.
|
||||
@@ -479,6 +483,20 @@ func (c *client) setPermissions(perms *Permissions) {
|
||||
}
|
||||
}
|
||||
|
||||
// Check to see if we have an expiration for the user JWT via base claims.
|
||||
// FIXME(dlc) - Clear on connect with new JWT.
|
||||
func (c *client) checkExpiration(claims *jwt.ClaimsData) {
|
||||
if claims.Expires == 0 {
|
||||
return
|
||||
}
|
||||
tn := time.Now().Unix()
|
||||
if claims.Expires < tn {
|
||||
return
|
||||
}
|
||||
expiresAt := time.Duration(claims.Expires - tn)
|
||||
c.setExpirationTimer(expiresAt * time.Second)
|
||||
}
|
||||
|
||||
// This will load up the deny structure used for filtering delivered
|
||||
// messages based on a deny clause for subscriptions.
|
||||
// Lock should be held.
|
||||
@@ -559,7 +577,7 @@ func (c *client) readLoop() {
|
||||
// to process messages, etc.
|
||||
if err := c.parse(b[:n]); err != nil {
|
||||
// handled inline
|
||||
if err != ErrMaxPayload && err != ErrAuthorization {
|
||||
if err != ErrMaxPayload && err != ErrAuthentication {
|
||||
c.Errorf("%s", err.Error())
|
||||
c.closeConnection(ProtocolViolation)
|
||||
}
|
||||
@@ -905,9 +923,9 @@ func (c *client) processConnect(arg []byte) error {
|
||||
}
|
||||
|
||||
// Check for Auth
|
||||
if ok := srv.checkAuthorization(c); !ok {
|
||||
if ok := srv.checkAuthentication(c); !ok {
|
||||
c.authViolation()
|
||||
return ErrAuthorization
|
||||
return ErrAuthentication
|
||||
}
|
||||
|
||||
// Check for Account designation
|
||||
@@ -978,44 +996,68 @@ func (c *client) processConnect(arg []byte) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *client) sendErrAndErr(err string) {
|
||||
c.sendErr(err)
|
||||
c.Errorf(err)
|
||||
}
|
||||
|
||||
func (c *client) sendErrAndDebug(err string) {
|
||||
c.sendErr(err)
|
||||
c.Debugf(err)
|
||||
}
|
||||
|
||||
func (c *client) authTimeout() {
|
||||
c.sendErr(ErrAuthTimeout.Error())
|
||||
c.Debugf("Authorization Timeout")
|
||||
c.sendErrAndDebug("Authentication Timeout")
|
||||
c.closeConnection(AuthenticationTimeout)
|
||||
}
|
||||
|
||||
func (c *client) authExpired() {
|
||||
c.sendErrAndDebug("User Authentication Expired")
|
||||
c.closeConnection(AuthenticationExpired)
|
||||
}
|
||||
|
||||
func (c *client) accountAuthExpired() {
|
||||
c.sendErrAndDebug("Account Authentication Expired")
|
||||
c.closeConnection(AuthenticationExpired)
|
||||
}
|
||||
|
||||
func (c *client) authViolation() {
|
||||
var hasNkeys, hasUsers bool
|
||||
var hasTrustedNkeys, hasNkeys, hasUsers bool
|
||||
if s := c.srv; s != nil {
|
||||
s.mu.Lock()
|
||||
hasTrustedNkeys = len(s.trustedNkeys) > 0
|
||||
hasNkeys = s.nkeys != nil
|
||||
hasUsers = s.users != nil
|
||||
s.mu.Unlock()
|
||||
}
|
||||
if hasNkeys {
|
||||
if hasTrustedNkeys {
|
||||
if c.opts.JWT != "" {
|
||||
c.Errorf("%v", ErrAuthentication)
|
||||
} else {
|
||||
c.Errorf("%v", ErrAuthentication)
|
||||
}
|
||||
} else if hasNkeys {
|
||||
c.Errorf("%s - Nkey %q",
|
||||
ErrAuthorization.Error(),
|
||||
ErrAuthentication.Error(),
|
||||
c.opts.Nkey)
|
||||
} else if hasUsers {
|
||||
c.Errorf("%s - User %q",
|
||||
ErrAuthorization.Error(),
|
||||
ErrAuthentication.Error(),
|
||||
c.opts.Username)
|
||||
} else {
|
||||
c.Errorf(ErrAuthorization.Error())
|
||||
c.Errorf(ErrAuthentication.Error())
|
||||
}
|
||||
c.sendErr("Authorization Violation")
|
||||
c.closeConnection(AuthenticationViolation)
|
||||
}
|
||||
|
||||
func (c *client) maxConnExceeded() {
|
||||
c.Errorf(ErrTooManyConnections.Error())
|
||||
c.sendErr(ErrTooManyConnections.Error())
|
||||
c.sendErrAndErr(ErrTooManyConnections.Error())
|
||||
c.closeConnection(MaxConnectionsExceeded)
|
||||
}
|
||||
|
||||
func (c *client) maxSubsExceeded() {
|
||||
c.Errorf(ErrTooManySubs.Error())
|
||||
c.sendErr(ErrTooManySubs.Error())
|
||||
c.sendErrAndErr(ErrTooManySubs.Error())
|
||||
}
|
||||
|
||||
func (c *client) maxPayloadViolation(sz int, max int64) {
|
||||
@@ -1412,11 +1454,14 @@ func (c *client) addShadowSubscriptions(acc *Account, sub *subscription) error {
|
||||
acc.mu.RLock()
|
||||
// Loop over the import subjects. We have 3 scenarios. If we exact
|
||||
// match or we know the proposed subject is a strict subset of the
|
||||
// import we can subscribe the subcsription's subject directly.
|
||||
// import we can subscribe to the subscription's subject directly.
|
||||
// The third scenario is where the proposed subject has a wildcard
|
||||
// and may not be an exact subset, but is a match. Therefore we have to
|
||||
// subscribe to the import subject, not the subscription's subject.
|
||||
for _, im := range acc.imports.streams {
|
||||
if im.invalid {
|
||||
continue
|
||||
}
|
||||
subj := string(sub.subject)
|
||||
if subj == im.prefix+im.from {
|
||||
ims = append(ims, im)
|
||||
@@ -1426,7 +1471,7 @@ func (c *client) addShadowSubscriptions(acc *Account, sub *subscription) error {
|
||||
tokens = tsa[:0]
|
||||
start := 0
|
||||
for i := 0; i < len(subj); i++ {
|
||||
//This is not perfect, but the test below will
|
||||
// This is not perfect, but the test below will
|
||||
// be more exact, this is just to trigger the
|
||||
// additional test.
|
||||
if subj[i] == pwc || subj[i] == fwc {
|
||||
@@ -1551,7 +1596,7 @@ func (c *client) unsubscribe(acc *Account, sub *subscription, force bool) {
|
||||
defer c.mu.Unlock()
|
||||
if !force && sub.max > 0 && sub.nm < sub.max {
|
||||
c.Debugf(
|
||||
"Deferring actual UNSUB(%s): %d max, %d received\n",
|
||||
"Deferring actual UNSUB(%s): %d max, %d received",
|
||||
string(sub.subject), sub.max, sub.nm)
|
||||
return
|
||||
}
|
||||
@@ -1704,7 +1749,7 @@ func (c *client) deliverMsg(sub *subscription, mh, msg []byte) bool {
|
||||
// still process the message in hand, otherwise
|
||||
// unsubscribe and drop message on the floor.
|
||||
if sub.nm == sub.max {
|
||||
client.Debugf("Auto-unsubscribe limit of %d reached for sid '%s'\n", sub.max, string(sub.sid))
|
||||
client.Debugf("Auto-unsubscribe limit of %d reached for sid '%s'", sub.max, string(sub.sid))
|
||||
// Due to defer, reverse the code order so that execution
|
||||
// is consistent with other cases where we unsubscribe.
|
||||
if shouldForward {
|
||||
@@ -1712,7 +1757,7 @@ func (c *client) deliverMsg(sub *subscription, mh, msg []byte) bool {
|
||||
}
|
||||
defer client.unsubscribe(client.acc, sub, true)
|
||||
} else if sub.nm > sub.max {
|
||||
client.Debugf("Auto-unsubscribe limit [%d] exceeded\n", sub.max)
|
||||
client.Debugf("Auto-unsubscribe limit [%d] exceeded", sub.max)
|
||||
client.mu.Unlock()
|
||||
client.unsubscribe(client.acc, sub, true)
|
||||
if shouldForward {
|
||||
@@ -1959,7 +2004,7 @@ func (c *client) checkForImportServices(acc *Account, msg []byte) {
|
||||
if c.pa.reply != nil {
|
||||
// We want to remap this to provide anonymity.
|
||||
nrr = c.newServiceReply()
|
||||
rm.acc.addImplicitServiceImport(acc, string(nrr), string(c.pa.reply), true)
|
||||
rm.acc.addImplicitServiceImport(acc, string(nrr), string(c.pa.reply), true, nil)
|
||||
}
|
||||
// FIXME(dlc) - Do L1 cache trick from above.
|
||||
rr := rm.acc.sl.Match(rm.to)
|
||||
@@ -1972,7 +2017,7 @@ func (c *client) addSubToRouteTargets(sub *subscription) {
|
||||
c.in.rts = make([]routeTarget, 0, routeTargetInit)
|
||||
}
|
||||
|
||||
for i, _ := range c.in.rts {
|
||||
for i := range c.in.rts {
|
||||
rt := &c.in.rts[i]
|
||||
if rt.sub.client == sub.client {
|
||||
if sub.queue != nil {
|
||||
@@ -2135,7 +2180,7 @@ func (c *client) processMsgResults(acc *Account, r *SublistResult, msg, subject,
|
||||
|
||||
// We address by index to avoimd struct copy. We have inline structs for memory
|
||||
// layout and cache coherency.
|
||||
for i, _ := range c.in.rts {
|
||||
for i := range c.in.rts {
|
||||
rt := &c.in.rts[i]
|
||||
|
||||
mh := c.msgb[:msgHeadProtoLen]
|
||||
@@ -2237,11 +2282,21 @@ func (c *client) clearAuthTimer() bool {
|
||||
return stopped
|
||||
}
|
||||
|
||||
func (c *client) isAuthTimerSet() bool {
|
||||
// We may reuse atmr for expiring user jwts,
|
||||
// so check connectReceived.
|
||||
func (c *client) awaitingAuth() bool {
|
||||
c.mu.Lock()
|
||||
isSet := c.atmr != nil
|
||||
authSet := !c.flags.isSet(connectReceived) && c.atmr != nil
|
||||
c.mu.Unlock()
|
||||
return authSet
|
||||
}
|
||||
|
||||
// This will set the atmr for the JWT expiration time.
|
||||
// We will lock on entry.
|
||||
func (c *client) setExpirationTimer(d time.Duration) {
|
||||
c.mu.Lock()
|
||||
c.atmr = time.AfterFunc(d, c.authExpired)
|
||||
c.mu.Unlock()
|
||||
return isSet
|
||||
}
|
||||
|
||||
// Lock should be held
|
||||
|
||||
@@ -717,8 +717,8 @@ func TestAuthorizationTimeout(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("Error receiving info from server: %v\n", err)
|
||||
}
|
||||
if !strings.Contains(l, "Authorization Timeout") {
|
||||
t.Fatalf("Authorization Timeout response incorrect: %q\n", l)
|
||||
if !strings.Contains(l, "Authentication Timeout") {
|
||||
t.Fatalf("Authentication Timeout response incorrect: %q\n", l)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -50,7 +50,7 @@ func TestClosedConnsAccounting(t *testing.T) {
|
||||
s := RunServer(opts)
|
||||
defer s.Shutdown()
|
||||
|
||||
wait := 20 * time.Millisecond
|
||||
wait := time.Second
|
||||
|
||||
nc, err := nats.Connect(fmt.Sprintf("nats://%s:%d", opts.Host, opts.Port))
|
||||
if err != nil {
|
||||
|
||||
@@ -34,6 +34,9 @@ const (
|
||||
var (
|
||||
// gitCommit injected at build
|
||||
gitCommit string
|
||||
// trustedNkeys is a whitespace separated array of
|
||||
// trusted operator public nkeys.
|
||||
trustedNkeys string
|
||||
)
|
||||
|
||||
const (
|
||||
|
||||
@@ -22,11 +22,14 @@ var (
|
||||
// ErrConnectionClosed represents an error condition on a closed connection.
|
||||
ErrConnectionClosed = errors.New("Connection Closed")
|
||||
|
||||
// ErrAuthorization represents an error condition on failed authorization.
|
||||
ErrAuthorization = errors.New("Authorization Error")
|
||||
// ErrAuthorization represents an error condition on failed authentication.
|
||||
ErrAuthentication = errors.New("Authentication Error")
|
||||
|
||||
// ErrAuthTimeout represents an error condition on failed authorization due to timeout.
|
||||
ErrAuthTimeout = errors.New("Authorization Timeout")
|
||||
ErrAuthTimeout = errors.New("Authentication Timeout")
|
||||
|
||||
// ErrAuthExpired represents an expired authorization due to timeout.
|
||||
ErrAuthExpired = errors.New("Authentication Expired")
|
||||
|
||||
// ErrMaxPayload represents an error condition when the payload is too big.
|
||||
ErrMaxPayload = errors.New("Maximum Payload Exceeded")
|
||||
@@ -65,6 +68,12 @@ var (
|
||||
// ErrMissingAccount is returned when an account does not exist.
|
||||
ErrMissingAccount = errors.New("Account Missing")
|
||||
|
||||
// ErrAccountValidation is returned when an account has failed validation.
|
||||
ErrAccountValidation = errors.New("Account Validation Failed")
|
||||
|
||||
// ErrNoAccountResolver is returned when we attempt an update but do not have an account resolver.
|
||||
ErrNoAccountResolver = errors.New("Account Resolver Missing")
|
||||
|
||||
// ErrStreamImportAuthorization is returned when a stream import is not authorized.
|
||||
ErrStreamImportAuthorization = errors.New("Stream Import Not Authorized")
|
||||
|
||||
|
||||
1013
server/jwt_test.go
Normal file
1013
server/jwt_test.go
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1039,6 +1039,8 @@ func (reason ClosedState) String() string {
|
||||
return "Route Removed"
|
||||
case ServerShutdown:
|
||||
return "Server Shutdown"
|
||||
case AuthenticationExpired:
|
||||
return "Authentication Expired"
|
||||
}
|
||||
return "Unknown State"
|
||||
}
|
||||
|
||||
@@ -28,7 +28,8 @@ const (
|
||||
func (s *Server) nonceRequired() bool {
|
||||
s.optsMu.RLock()
|
||||
defer s.optsMu.RUnlock()
|
||||
return len(s.opts.Nkeys) > 0
|
||||
|
||||
return len(s.opts.Nkeys) > 0 || len(s.opts.TrustedNkeys) > 0
|
||||
}
|
||||
|
||||
// Generate a nonce for INFO challenge.
|
||||
|
||||
@@ -36,13 +36,13 @@ type nonceInfo struct {
|
||||
}
|
||||
|
||||
// This is a seed for a user. We can extract public and private keys from this for testing.
|
||||
const seed = "SUAKYRHVIOREXV7EUZTBHUHL7NUMHPMAS7QMDU3GTIUWEI5LDNOXD43IZY"
|
||||
var seed = []byte("SUAKYRHVIOREXV7EUZTBHUHL7NUMHPMAS7QMDU3GTIUWEI5LDNOXD43IZY")
|
||||
|
||||
func nkeyBasicSetup() (*Server, *client, *bufio.Reader, string) {
|
||||
kp, _ := nkeys.FromSeed(seed)
|
||||
pub, _ := kp.PublicKey()
|
||||
opts := defaultServerOptions
|
||||
opts.Nkeys = []*NkeyUser{&NkeyUser{Nkey: pub}}
|
||||
opts.Nkeys = []*NkeyUser{&NkeyUser{Nkey: string(pub)}}
|
||||
return rawSetup(opts)
|
||||
}
|
||||
|
||||
@@ -50,7 +50,7 @@ func mixedSetup() (*Server, *client, *bufio.Reader, string) {
|
||||
kp, _ := nkeys.FromSeed(seed)
|
||||
pub, _ := kp.PublicKey()
|
||||
opts := defaultServerOptions
|
||||
opts.Nkeys = []*NkeyUser{&NkeyUser{Nkey: pub}}
|
||||
opts.Nkeys = []*NkeyUser{&NkeyUser{Nkey: string(pub)}}
|
||||
opts.Users = []*User{&User{Username: "derek", Password: "foo"}}
|
||||
return rawSetup(opts)
|
||||
}
|
||||
|
||||
@@ -96,6 +96,7 @@ type Options struct {
|
||||
RQSubsSweep time.Duration `json:"-"` // Deprecated
|
||||
MaxClosedClients int `json:"-"`
|
||||
LameDuckDuration time.Duration `json:"-"`
|
||||
TrustedNkeys []string `json:"-"`
|
||||
|
||||
CustomClientAuthentication Authentication `json:"-"`
|
||||
CustomRouterAuthentication Authentication `json:"-"`
|
||||
@@ -418,6 +419,36 @@ func (o *Options) ProcessConfigFile(configFile string) error {
|
||||
continue
|
||||
}
|
||||
o.LameDuckDuration = dur
|
||||
case "trusted":
|
||||
switch v.(type) {
|
||||
case string:
|
||||
o.TrustedNkeys = []string{v.(string)}
|
||||
case []string:
|
||||
o.TrustedNkeys = v.([]string)
|
||||
case []interface{}:
|
||||
keys := make([]string, 0, len(v.([]interface{})))
|
||||
for _, mv := range v.([]interface{}) {
|
||||
tk, mv = unwrapValue(mv)
|
||||
if key, ok := mv.(string); ok {
|
||||
keys = append(keys, key)
|
||||
} else {
|
||||
err := &configErr{tk, fmt.Sprintf("error parsing trusted: unsupported type in array %T", mv)}
|
||||
errors = append(errors, err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
o.TrustedNkeys = keys
|
||||
default:
|
||||
err := &configErr{tk, fmt.Sprintf("error parsing trusted: unsupported type %T", v)}
|
||||
errors = append(errors, err)
|
||||
}
|
||||
// Do a quick sanity check on keys
|
||||
for _, key := range o.TrustedNkeys {
|
||||
if !nkeys.IsValidPublicOperatorKey([]byte(key)) {
|
||||
err := &configErr{tk, fmt.Sprintf("trust key %q required to be a valid public operator nkey", key)}
|
||||
errors = append(errors, err)
|
||||
}
|
||||
}
|
||||
default:
|
||||
if !tk.IsUsedVariable() {
|
||||
err := &unknownConfigFieldErr{
|
||||
@@ -688,7 +719,7 @@ func parseAccounts(v interface{}, opts *Options, errors *[]error, warnings *[]er
|
||||
switch strings.ToLower(k) {
|
||||
case "nkey":
|
||||
nk, ok := mv.(string)
|
||||
if !ok || !nkeys.IsValidPublicAccountKey(nk) {
|
||||
if !ok || !nkeys.IsValidPublicAccountKey([]byte(nk)) {
|
||||
err := &configErr{tk, fmt.Sprintf("Not a valid public nkey for an account: %q", mv)}
|
||||
*errors = append(*errors, err)
|
||||
continue
|
||||
@@ -1245,7 +1276,7 @@ func parseUsers(mv interface{}, opts *Options, errors *[]error, warnings *[]erro
|
||||
return nil, nil, &configErr{tk, fmt.Sprintf("User entry requires a user and a password")}
|
||||
} else if nkey.Nkey != "" {
|
||||
// Make sure the nkey a proper public nkey for a user..
|
||||
if !nkeys.IsValidPublicUserKey(nkey.Nkey) {
|
||||
if !nkeys.IsValidPublicUserKey([]byte(nkey.Nkey)) {
|
||||
return nil, nil, &configErr{tk, fmt.Sprintf("Not a valid public nkey for a user")}
|
||||
}
|
||||
// If we have user or password defined here that is an error.
|
||||
|
||||
@@ -113,9 +113,9 @@ func (c *client) parse(buf []byte) error {
|
||||
mcl = c.srv.getOpts().MaxControlLine
|
||||
}
|
||||
|
||||
// snapshot this, and reset when we receive a
|
||||
// Snapshot this, and reset when we receive a
|
||||
// proper CONNECT if needed.
|
||||
authSet := c.isAuthTimerSet()
|
||||
authSet := c.awaitingAuth()
|
||||
|
||||
// Move to loop instead of range syntax to allow jumping of i
|
||||
for i = 0; i < len(buf); i++ {
|
||||
@@ -595,7 +595,7 @@ func (c *client) parse(buf []byte) error {
|
||||
}
|
||||
c.drop, c.state = 0, OP_START
|
||||
// Reset notion on authSet
|
||||
authSet = c.isAuthTimerSet()
|
||||
authSet = c.awaitingAuth()
|
||||
default:
|
||||
if c.argBuf != nil {
|
||||
c.argBuf = append(c.argBuf, b)
|
||||
@@ -837,7 +837,7 @@ func (c *client) parse(buf []byte) error {
|
||||
|
||||
authErr:
|
||||
c.authViolation()
|
||||
return ErrAuthorization
|
||||
return ErrAuthentication
|
||||
|
||||
parseErr:
|
||||
c.sendErr("Unknown Protocol Operation")
|
||||
|
||||
@@ -2751,8 +2751,8 @@ func TestConfigReloadAccountNKeyUsers(t *testing.T) {
|
||||
synadia := s.LookupAccount("synadia")
|
||||
nats := s.LookupAccount("nats.io")
|
||||
|
||||
seed1 := "SUAPM67TC4RHQLKBX55NIQXSMATZDOZK6FNEOSS36CAYA7F7TY66LP4BOM"
|
||||
seed2 := "SUAIS5JPX4X4GJ7EIIJEQ56DH2GWPYJRPWN5XJEDENJOZHCBLI7SEPUQDE"
|
||||
seed1 := []byte("SUAPM67TC4RHQLKBX55NIQXSMATZDOZK6FNEOSS36CAYA7F7TY66LP4BOM")
|
||||
seed2 := []byte("SUAIS5JPX4X4GJ7EIIJEQ56DH2GWPYJRPWN5XJEDENJOZHCBLI7SEPUQDE")
|
||||
|
||||
kp, _ := nkeys.FromSeed(seed1)
|
||||
pubKey, _ := kp.PublicKey()
|
||||
|
||||
178
server/server.go
178
server/server.go
@@ -36,6 +36,8 @@ import (
|
||||
_ "net/http/pprof"
|
||||
|
||||
"github.com/nats-io/gnatsd/logger"
|
||||
"github.com/nats-io/jwt"
|
||||
"github.com/nats-io/nkeys"
|
||||
)
|
||||
|
||||
// Time to wait before starting closing clients when in LD mode.
|
||||
@@ -84,6 +86,7 @@ type Server struct {
|
||||
gacc *Account
|
||||
accounts map[string]*Account
|
||||
activeAccounts int
|
||||
accResolver AccountResolver
|
||||
clients map[uint64]*client
|
||||
routes map[uint64]*client
|
||||
remotes map[string]*client
|
||||
@@ -142,6 +145,9 @@ type Server struct {
|
||||
// LameDuck mode
|
||||
ldm bool
|
||||
ldmCh chan bool
|
||||
|
||||
// Trusted public operator keys.
|
||||
trustedNkeys []string
|
||||
}
|
||||
|
||||
// Make sure all are 64bits for atomic use
|
||||
@@ -186,6 +192,11 @@ func New(opts *Options) *Server {
|
||||
configTime: now,
|
||||
}
|
||||
|
||||
// ProcessTrustedNkeys
|
||||
if !s.processTrustedNkeys() {
|
||||
return nil
|
||||
}
|
||||
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
@@ -266,6 +277,68 @@ func (s *Server) generateRouteInfoJSON() {
|
||||
s.routeInfoJSON = bytes.Join(pcs, []byte(" "))
|
||||
}
|
||||
|
||||
// isTrustedIssuer will check that the issuer is a trusted public key.
|
||||
// This is used to make sure and account was signed by a trusted operator.
|
||||
func (s *Server) isTrustedIssuer(issuer string) bool {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
for _, tk := range s.trustedNkeys {
|
||||
if tk == issuer {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// processTrustedNkeys will process stamped and option based
|
||||
// trusted nkeys. Returns success.
|
||||
func (s *Server) processTrustedNkeys() bool {
|
||||
if trustedNkeys != "" && !s.initStampedTrustedNkeys() {
|
||||
return false
|
||||
} else if s.opts.TrustedNkeys != nil {
|
||||
for _, key := range s.opts.TrustedNkeys {
|
||||
if !nkeys.IsValidPublicOperatorKey([]byte(key)) {
|
||||
return false
|
||||
}
|
||||
s.trustedNkeys = s.opts.TrustedNkeys
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// checkTrustedNkeyString will check that the string is a valid array
|
||||
// of public operator nkeys.
|
||||
func checkTrustedNkeyString(keys string) []string {
|
||||
tks := strings.Fields(keys)
|
||||
if len(tks) == 0 {
|
||||
return nil
|
||||
}
|
||||
// Walk all the keys and make sure they are valid.
|
||||
for _, key := range tks {
|
||||
if !nkeys.IsValidPublicOperatorKey([]byte(key)) {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return tks
|
||||
}
|
||||
|
||||
// initStampedTrustedNkeys will check the stamped trusted keys
|
||||
// and will set the server field 'trustedNkeys'. Returns whether
|
||||
// it succeeded or not.
|
||||
func (s *Server) initStampedTrustedNkeys() bool {
|
||||
tks := checkTrustedNkeyString(trustedNkeys)
|
||||
if len(tks) == 0 {
|
||||
return false
|
||||
}
|
||||
// Check to see if we have an override in options, which will
|
||||
// cause us to fail also.
|
||||
if len(s.opts.TrustedNkeys) > 0 {
|
||||
return false
|
||||
}
|
||||
s.trustedNkeys = tks
|
||||
return true
|
||||
}
|
||||
|
||||
// PrintAndDie is exported for access in other packages.
|
||||
func PrintAndDie(msg string) {
|
||||
fmt.Fprintf(os.Stderr, "%s\n", msg)
|
||||
@@ -329,6 +402,20 @@ func (s *Server) NumActiveAccounts() int {
|
||||
return s.activeAccounts
|
||||
}
|
||||
|
||||
// incActiveAccounts() just adds one under lock.
|
||||
func (s *Server) incActiveAccounts() {
|
||||
s.mu.Lock()
|
||||
s.activeAccounts++
|
||||
s.mu.Unlock()
|
||||
}
|
||||
|
||||
// dev=cActiveAccounts() just subtracts one under lock.
|
||||
func (s *Server) decActiveAccounts() {
|
||||
s.mu.Lock()
|
||||
s.activeAccounts--
|
||||
s.mu.Unlock()
|
||||
}
|
||||
|
||||
// LookupOrRegisterAccount will return the given account if known or create a new entry.
|
||||
func (s *Server) LookupOrRegisterAccount(name string) (account *Account, isNew bool) {
|
||||
s.mu.Lock()
|
||||
@@ -369,6 +456,9 @@ func (s *Server) registerAccount(acc *Account) {
|
||||
if acc.maxaettl == 0 {
|
||||
acc.maxaettl = DEFAULT_TTL_AE_RESPONSE_MAP
|
||||
}
|
||||
if acc.clients == nil {
|
||||
acc.clients = make(map[*client]*client)
|
||||
}
|
||||
// If we are capable of routing we will track subscription
|
||||
// information for efficient interest propagation.
|
||||
// During config reload, it is possible that account was
|
||||
@@ -387,7 +477,93 @@ func (s *Server) registerAccount(acc *Account) {
|
||||
func (s *Server) LookupAccount(name string) *Account {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
return s.accounts[name]
|
||||
|
||||
acc := s.accounts[name]
|
||||
if acc != nil {
|
||||
// If we are expired and we have a resolver, then
|
||||
// return the latest information from the resolver.
|
||||
if s.accResolver != nil && acc.IsExpired() {
|
||||
s.UpdateAccount(acc)
|
||||
}
|
||||
return acc
|
||||
}
|
||||
// If we have a resolver see if it can fetch the account.
|
||||
return s.FetchAccount(name)
|
||||
}
|
||||
|
||||
// This will fetch new claims and if found update the account with new claims.
|
||||
func (s *Server) UpdateAccount(acc *Account) bool {
|
||||
// TODO(dlc) - Make configurable
|
||||
if time.Since(acc.updated) < time.Second {
|
||||
s.Debugf("Requested account update for [%s] ignored, too soon", acc.Name)
|
||||
return false
|
||||
}
|
||||
claimJWT, err := s.fetchRawAccountClaims(acc.Name)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
acc.updated = time.Now()
|
||||
if acc.claimJWT != "" && acc.claimJWT == claimJWT {
|
||||
s.Debugf("Requested account update for [%s], same claims detected", acc.Name)
|
||||
return false
|
||||
}
|
||||
accClaims, err := s.verifyAccountClaims(claimJWT)
|
||||
if err == nil && accClaims != nil {
|
||||
s.UpdateAccountClaims(acc, accClaims)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// fetchRawAccountClaims will grab raw account claims iff we have a resolver.
|
||||
func (s *Server) fetchRawAccountClaims(name string) (string, error) {
|
||||
accResolver := s.accResolver
|
||||
if accResolver == nil {
|
||||
return "", ErrNoAccountResolver
|
||||
}
|
||||
// Need to do actual Fetch without the lock.
|
||||
s.mu.Unlock()
|
||||
claimJWT, err := accResolver.Fetch(name)
|
||||
s.mu.Lock()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return claimJWT, nil
|
||||
}
|
||||
|
||||
// fetchAccountClaims will attempt to fetch new claims if a resolver is present.
|
||||
func (s *Server) fetchAccountClaims(name string) (*jwt.AccountClaims, error) {
|
||||
claimJWT, err := s.fetchRawAccountClaims(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return s.verifyAccountClaims(claimJWT)
|
||||
}
|
||||
|
||||
// verifyAccountClaims will decode and validate any account claims.
|
||||
func (s *Server) verifyAccountClaims(claimJWT string) (*jwt.AccountClaims, error) {
|
||||
if accClaims, err := jwt.DecodeAccountClaims(claimJWT); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
vr := jwt.CreateValidationResults()
|
||||
accClaims.Validate(vr)
|
||||
if vr.IsBlocking(true) {
|
||||
return nil, ErrAccountValidation
|
||||
}
|
||||
return accClaims, nil
|
||||
}
|
||||
}
|
||||
|
||||
// This will fetch an account from a resolver if defined.
|
||||
// Lock should be held.
|
||||
func (s *Server) FetchAccount(name string) *Account {
|
||||
if accClaims, _ := s.fetchAccountClaims(name); accClaims != nil {
|
||||
if acc := s.buildInternalAccount(accClaims); acc != nil {
|
||||
s.registerAccount(acc)
|
||||
return acc
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Start up the server, this will block.
|
||||
|
||||
123
server/trust_test.go
Normal file
123
server/trust_test.go
Normal file
@@ -0,0 +1,123 @@
|
||||
// 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 server
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
const (
|
||||
t1 = "OBYEOZQ46VZMFMNETBAW2H6VGDSOBLP67VUEZJ5LPR3PIZBWWRIY4UI4"
|
||||
t2 = "OAHC7NGAHG3YVPTD6QOUFZGPM2OMU6EOS67O2VHBUOA6BJLPTWFHGLKU"
|
||||
)
|
||||
|
||||
func TestStampedTrustedNkeys(t *testing.T) {
|
||||
opts := DefaultOptions()
|
||||
defer func() { trustedNkeys = "" }()
|
||||
|
||||
// Set this to a bad key. We require valid operator public keys.
|
||||
trustedNkeys = "bad"
|
||||
if s := New(opts); s != nil {
|
||||
s.Shutdown()
|
||||
t.Fatalf("Expected a bad trustedNkeys to return nil server")
|
||||
}
|
||||
|
||||
trustedNkeys = t1
|
||||
s := New(opts)
|
||||
if s == nil {
|
||||
t.Fatalf("Expected non-nil server")
|
||||
}
|
||||
if len(s.trustedNkeys) != 1 || s.trustedNkeys[0] != t1 {
|
||||
t.Fatalf("Trusted Nkeys not setup properly")
|
||||
}
|
||||
trustedNkeys = strings.Join([]string{t1, t2}, " ")
|
||||
if s = New(opts); s == nil {
|
||||
t.Fatalf("Expected non-nil server")
|
||||
}
|
||||
if len(s.trustedNkeys) != 2 || s.trustedNkeys[0] != t1 || s.trustedNkeys[1] != t2 {
|
||||
t.Fatalf("Trusted Nkeys not setup properly")
|
||||
}
|
||||
|
||||
opts.TrustedNkeys = []string{"OVERRIDE ME"}
|
||||
if s = New(opts); s != nil {
|
||||
t.Fatalf("Expected opts.TrustedNkeys to return nil server")
|
||||
}
|
||||
}
|
||||
|
||||
func TestTrustedKeysOptions(t *testing.T) {
|
||||
trustedNkeys = ""
|
||||
opts := DefaultOptions()
|
||||
opts.TrustedNkeys = []string{"bad"}
|
||||
if s := New(opts); s != nil {
|
||||
s.Shutdown()
|
||||
t.Fatalf("Expected a bad opts.TrustedNkeys to return nil server")
|
||||
}
|
||||
opts.TrustedNkeys = []string{t1}
|
||||
s := New(opts)
|
||||
if s == nil {
|
||||
t.Fatalf("Expected non-nil server")
|
||||
}
|
||||
if len(s.trustedNkeys) != 1 || s.trustedNkeys[0] != t1 {
|
||||
t.Fatalf("Trusted Nkeys not setup properly via options")
|
||||
}
|
||||
opts.TrustedNkeys = []string{t1, t2}
|
||||
if s = New(opts); s == nil {
|
||||
t.Fatalf("Expected non-nil server")
|
||||
}
|
||||
if len(s.trustedNkeys) != 2 || s.trustedNkeys[0] != t1 || s.trustedNkeys[1] != t2 {
|
||||
t.Fatalf("Trusted Nkeys not setup properly via options")
|
||||
}
|
||||
}
|
||||
|
||||
func TestTrustConfigOption(t *testing.T) {
|
||||
confFileName := createConfFile(t, []byte(fmt.Sprintf("trusted = %q", t1)))
|
||||
defer os.Remove(confFileName)
|
||||
opts, err := ProcessConfigFile(confFileName)
|
||||
if err != nil {
|
||||
t.Fatalf("Error parsing config: %v", err)
|
||||
}
|
||||
if l := len(opts.TrustedNkeys); l != 1 {
|
||||
t.Fatalf("Expected 1 trusted key, got %d", l)
|
||||
}
|
||||
if opts.TrustedNkeys[0] != t1 {
|
||||
t.Fatalf("Expected trusted key to be %q, got %q", t1, opts.TrustedNkeys[0])
|
||||
}
|
||||
|
||||
confFileName = createConfFile(t, []byte(fmt.Sprintf("trusted = [%q, %q]", t1, t2)))
|
||||
defer os.Remove(confFileName)
|
||||
opts, err = ProcessConfigFile(confFileName)
|
||||
if err != nil {
|
||||
t.Fatalf("Error parsing config: %v", err)
|
||||
}
|
||||
if l := len(opts.TrustedNkeys); l != 2 {
|
||||
t.Fatalf("Expected 2 trusted key, got %d", l)
|
||||
}
|
||||
if opts.TrustedNkeys[0] != t1 {
|
||||
t.Fatalf("Expected trusted key to be %q, got %q", t1, opts.TrustedNkeys[0])
|
||||
}
|
||||
if opts.TrustedNkeys[1] != t2 {
|
||||
t.Fatalf("Expected trusted key to be %q, got %q", t2, opts.TrustedNkeys[1])
|
||||
}
|
||||
|
||||
// Now do a bad one.
|
||||
confFileName = createConfFile(t, []byte(fmt.Sprintf("trusted = [%q, %q]", t1, "bad")))
|
||||
defer os.Remove(confFileName)
|
||||
_, err = ProcessConfigFile(confFileName)
|
||||
if err == nil {
|
||||
t.Fatalf("Expected an error parsing trust keys with a bad key")
|
||||
}
|
||||
}
|
||||
@@ -30,7 +30,7 @@ import (
|
||||
func genID() string {
|
||||
kp, _ := nkeys.CreateServer()
|
||||
pub, _ := kp.PublicKey()
|
||||
return pub
|
||||
return string(pub)
|
||||
}
|
||||
|
||||
// Ascii numbers 0-9
|
||||
|
||||
@@ -1315,6 +1315,12 @@ func TestNewRouteLargeDistinctQueueSubscribers(t *testing.T) {
|
||||
qsubs[i], _ = ncB.QueueSubscribeSync("foo", qg)
|
||||
}
|
||||
ncB.Flush()
|
||||
checkFor(t, time.Second, 10*time.Millisecond, func() error {
|
||||
if ns := srvA.NumSubscriptions(); ns != 100 {
|
||||
return fmt.Errorf("Number of subscriptions is %d", ns)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
// Send 10 messages. We should receive 1000 responses.
|
||||
for i := 0; i < 10; i++ {
|
||||
@@ -1322,10 +1328,10 @@ func TestNewRouteLargeDistinctQueueSubscribers(t *testing.T) {
|
||||
}
|
||||
ncA.Flush()
|
||||
|
||||
checkFor(t, time.Second, 10*time.Millisecond, func() error {
|
||||
checkFor(t, 2*time.Second, 10*time.Millisecond, func() error {
|
||||
for i := 0; i < nqsubs; i++ {
|
||||
if n, _, _ := qsubs[i].Pending(); n != 10 {
|
||||
return fmt.Errorf("Number of messgaes is %d", n)
|
||||
return fmt.Errorf("Number of messages is %d", n)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
20
vendor/github.com/nats-io/nkeys/keypair.go
generated
vendored
20
vendor/github.com/nats-io/nkeys/keypair.go
generated
vendored
@@ -23,7 +23,7 @@ import (
|
||||
|
||||
// kp is the internal struct for a kepypair using seed.
|
||||
type kp struct {
|
||||
seed string
|
||||
seed []byte
|
||||
}
|
||||
|
||||
// createPair will create a KeyPair based on the rand entropy and a type/prefix byte. rand can be nil.
|
||||
@@ -57,30 +57,36 @@ func (pair *kp) keys() (ed25519.PublicKey, ed25519.PrivateKey, error) {
|
||||
return ed25519.GenerateKey(bytes.NewReader(raw))
|
||||
}
|
||||
|
||||
// Wipe will randomize the contents of the seed key
|
||||
func (pair *kp) Wipe() {
|
||||
io.ReadFull(rand.Reader, pair.seed)
|
||||
pair.seed = nil
|
||||
}
|
||||
|
||||
// Seed will return the encoded seed.
|
||||
func (pair *kp) Seed() (string, error) {
|
||||
func (pair *kp) Seed() ([]byte, error) {
|
||||
return pair.seed, nil
|
||||
}
|
||||
|
||||
// PublicKey will return the encoded public key associated with the KeyPair.
|
||||
// All KeyPairs have a public key.
|
||||
func (pair *kp) PublicKey() (string, error) {
|
||||
func (pair *kp) PublicKey() ([]byte, error) {
|
||||
public, raw, err := DecodeSeed(pair.seed)
|
||||
if err != nil {
|
||||
return "", err
|
||||
return nil, err
|
||||
}
|
||||
pub, _, err := ed25519.GenerateKey(bytes.NewReader(raw))
|
||||
if err != nil {
|
||||
return "", err
|
||||
return nil, err
|
||||
}
|
||||
return Encode(public, pub)
|
||||
}
|
||||
|
||||
// PrivateKey will return the encoded private key for KeyPair.
|
||||
func (pair *kp) PrivateKey() (string, error) {
|
||||
func (pair *kp) PrivateKey() ([]byte, error) {
|
||||
_, priv, err := pair.keys()
|
||||
if err != nil {
|
||||
return "", err
|
||||
return nil, err
|
||||
}
|
||||
return Encode(PrefixBytePrivate, priv)
|
||||
}
|
||||
|
||||
14
vendor/github.com/nats-io/nkeys/main.go
generated
vendored
14
vendor/github.com/nats-io/nkeys/main.go
generated
vendored
@@ -34,11 +34,12 @@ var (
|
||||
|
||||
// KeyPair provides the central interface to nkeys.
|
||||
type KeyPair interface {
|
||||
Seed() (string, error)
|
||||
PublicKey() (string, error)
|
||||
PrivateKey() (string, error)
|
||||
Seed() ([]byte, error)
|
||||
PublicKey() ([]byte, error)
|
||||
PrivateKey() ([]byte, error)
|
||||
Sign(input []byte) ([]byte, error)
|
||||
Verify(input []byte, sig []byte) error
|
||||
Wipe()
|
||||
}
|
||||
|
||||
// CreateUser will create a User typed KeyPair.
|
||||
@@ -67,7 +68,7 @@ func CreateOperator() (KeyPair, error) {
|
||||
}
|
||||
|
||||
// FromPublicKey will create a KeyPair capable of verifying signatures.
|
||||
func FromPublicKey(public string) (KeyPair, error) {
|
||||
func FromPublicKey(public []byte) (KeyPair, error) {
|
||||
raw, err := decode(public)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -80,12 +81,13 @@ func FromPublicKey(public string) (KeyPair, error) {
|
||||
}
|
||||
|
||||
// FromSeed will create a KeyPair capable of signing and verifying signatures.
|
||||
func FromSeed(seed string) (KeyPair, error) {
|
||||
func FromSeed(seed []byte) (KeyPair, error) {
|
||||
_, _, err := DecodeSeed(seed)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &kp{seed}, nil
|
||||
copy := append([]byte{}, seed...)
|
||||
return &kp{copy}, nil
|
||||
}
|
||||
|
||||
// Create a KeyPair from the raw 32 byte seed for a given type.
|
||||
|
||||
136
vendor/github.com/nats-io/nkeys/nk/main.go
generated
vendored
136
vendor/github.com/nats-io/nkeys/nk/main.go
generated
vendored
@@ -14,9 +14,12 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"encoding/base32"
|
||||
"encoding/base64"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
@@ -26,8 +29,11 @@ import (
|
||||
"github.com/nats-io/nkeys"
|
||||
)
|
||||
|
||||
// this will be set during compilation when a release is made on tools
|
||||
var Version string
|
||||
|
||||
func usage() {
|
||||
log.Fatalf("Usage: nk [-gen type] [-sign file] [-verify file] [-inkey keyfile] [-pubin keyfile] [-sigfile file] [-pubout] [-e entropy]\n")
|
||||
log.Fatalf("Usage: nk [-v] [-gen type] [-sign file] [-verify file] [-inkey keyfile] [-pubin keyfile] [-sigfile file] [-pubout] [-e entropy]\n")
|
||||
}
|
||||
|
||||
func main() {
|
||||
@@ -43,15 +49,38 @@ func main() {
|
||||
var keyType = flag.String("gen", "", "Generate key for <type>, e.g. nk -gen user")
|
||||
var pubout = flag.Bool("pubout", false, "Output public key")
|
||||
|
||||
var version = flag.Bool("v", false, "Show version")
|
||||
var vanPre = flag.String("pre", "", "Attempt to generate public key given prefix, e.g. nk -gen user -pre derek")
|
||||
var vanMax = flag.Int("maxpre", 1000000, "Maximum attempts at generating the correct key prefix")
|
||||
|
||||
log.SetFlags(0)
|
||||
log.SetOutput(os.Stdout)
|
||||
|
||||
flag.Usage = usage
|
||||
flag.Parse()
|
||||
|
||||
if *version {
|
||||
fmt.Printf("nk version %s\n", Version)
|
||||
}
|
||||
|
||||
// Create Key
|
||||
if *keyType != "" {
|
||||
createKey(*keyType, *entropy)
|
||||
var kp nkeys.KeyPair
|
||||
// Check to see if we are trying to do a vanity public key.
|
||||
if *vanPre != "" {
|
||||
kp = createVanityKey(*keyType, *vanPre, *entropy, *vanMax)
|
||||
} else {
|
||||
kp = genKeyPair(preForType(*keyType), *entropy)
|
||||
}
|
||||
seed, err := kp.Seed()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
log.Printf("%s", seed)
|
||||
if *pubout || *vanPre != "" {
|
||||
pub, _ := kp.PublicKey()
|
||||
log.Printf("%s", pub)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@@ -81,12 +110,8 @@ func main() {
|
||||
}
|
||||
|
||||
func printPublicFromSeed(keyFile string) {
|
||||
seed, err := ioutil.ReadFile(keyFile)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
kp, err := nkeys.FromSeed(string(seed))
|
||||
seed := readKeyFile(keyFile)
|
||||
kp, err := nkeys.FromSeed(seed)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
@@ -98,12 +123,8 @@ func sign(fname, keyFile string) {
|
||||
if keyFile == "" {
|
||||
log.Fatalf("Sign requires a seed/private key via -inkey <file>")
|
||||
}
|
||||
seed, err := ioutil.ReadFile(keyFile)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
kp, err := nkeys.FromSeed(string(seed))
|
||||
seed := readKeyFile(keyFile)
|
||||
kp, err := nkeys.FromSeed(seed)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
@@ -135,7 +156,7 @@ func verify(fname, keyFile, pubFile, sigFile string) {
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
kp, err = nkeys.FromSeed(string(seed))
|
||||
kp, err = nkeys.FromSeed(seed)
|
||||
} else {
|
||||
// Public Key
|
||||
var public []byte
|
||||
@@ -143,7 +164,7 @@ func verify(fname, keyFile, pubFile, sigFile string) {
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
kp, err = nkeys.FromPublicKey(string(public))
|
||||
kp, err = nkeys.FromPublicKey(public)
|
||||
}
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
@@ -168,25 +189,26 @@ func verify(fname, keyFile, pubFile, sigFile string) {
|
||||
log.Printf("Verified OK")
|
||||
}
|
||||
|
||||
func createKey(keyType, entropy string) {
|
||||
func preForType(keyType string) nkeys.PrefixByte {
|
||||
keyType = strings.ToLower(keyType)
|
||||
var pre nkeys.PrefixByte
|
||||
|
||||
switch keyType {
|
||||
case "user":
|
||||
pre = nkeys.PrefixByteUser
|
||||
return nkeys.PrefixByteUser
|
||||
case "account":
|
||||
pre = nkeys.PrefixByteAccount
|
||||
return nkeys.PrefixByteAccount
|
||||
case "server":
|
||||
pre = nkeys.PrefixByteServer
|
||||
return nkeys.PrefixByteServer
|
||||
case "cluster":
|
||||
pre = nkeys.PrefixByteCluster
|
||||
return nkeys.PrefixByteCluster
|
||||
case "operator":
|
||||
pre = nkeys.PrefixByteOperator
|
||||
return nkeys.PrefixByteOperator
|
||||
default:
|
||||
log.Fatalf("Usage: nk -gen [user|account|server|cluster|operator]\n")
|
||||
}
|
||||
return nkeys.PrefixByte(0)
|
||||
}
|
||||
|
||||
func genKeyPair(pre nkeys.PrefixByte, entropy string) nkeys.KeyPair {
|
||||
// See if we override entropy.
|
||||
ef := rand.Reader
|
||||
if entropy != "" {
|
||||
@@ -205,11 +227,71 @@ func createKey(keyType, entropy string) {
|
||||
}
|
||||
kp, err := nkeys.FromRawSeed(pre, rawSeed[:])
|
||||
if err != nil {
|
||||
log.Fatalf("Error creating %s: %v", keyType, err)
|
||||
log.Fatalf("Error creating %c: %v", pre, err)
|
||||
}
|
||||
seed, err := kp.Seed()
|
||||
return kp
|
||||
}
|
||||
|
||||
var b32Enc = base32.StdEncoding.WithPadding(base32.NoPadding)
|
||||
|
||||
func createVanityKey(keyType, vanity, entropy string, max int) nkeys.KeyPair {
|
||||
spinners := []rune(`⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏`)
|
||||
pre := preForType(keyType)
|
||||
vanity = strings.ToUpper(vanity)
|
||||
// Check to make sure we can base32 into it by trying to decode it.
|
||||
_, err := b32Enc.DecodeString(vanity)
|
||||
if err != nil {
|
||||
log.Fatalf("Can not generate base32 encoded strings to match '%s'", vanity)
|
||||
}
|
||||
|
||||
for i := 0; i < max; i++ {
|
||||
spin := spinners[i%len(spinners)]
|
||||
fmt.Fprintf(os.Stderr, "\r\033[mcomputing\033[m %s ", string(spin))
|
||||
kp := genKeyPair(pre, entropy)
|
||||
pub, _ := kp.PublicKey()
|
||||
if bytes.HasPrefix(pub[1:], []byte(vanity)) {
|
||||
fmt.Fprintf(os.Stderr, "\r")
|
||||
return kp
|
||||
}
|
||||
}
|
||||
fmt.Fprintf(os.Stderr, "\r")
|
||||
log.Fatalf("Failed to generate prefix after %d attempts", max)
|
||||
return nil
|
||||
}
|
||||
|
||||
func readKeyFile(filename string) []byte {
|
||||
var key []byte
|
||||
contents, err := ioutil.ReadFile(filename)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
log.Printf("%s", seed)
|
||||
defer wipeSlice(contents)
|
||||
|
||||
lines := bytes.Split(contents, []byte("\n"))
|
||||
for _, line := range lines {
|
||||
if nkeys.IsValidEncoding(line) {
|
||||
key = make([]byte, len(line))
|
||||
copy(key, line)
|
||||
return key
|
||||
}
|
||||
}
|
||||
if key == nil {
|
||||
log.Fatalf("Could not find a valid key")
|
||||
}
|
||||
return key
|
||||
}
|
||||
|
||||
func isValidLeadingByte(c byte) bool {
|
||||
switch c {
|
||||
case 'S', 'P', 'N', 'C', 'O', 'A', 'U':
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func wipeSlice(buf []byte) {
|
||||
for i := range buf {
|
||||
buf[i] = 'x'
|
||||
}
|
||||
}
|
||||
|
||||
19
vendor/github.com/nats-io/nkeys/public.go
generated
vendored
19
vendor/github.com/nats-io/nkeys/public.go
generated
vendored
@@ -14,6 +14,9 @@
|
||||
package nkeys
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"io"
|
||||
|
||||
"golang.org/x/crypto/ed25519"
|
||||
)
|
||||
|
||||
@@ -25,18 +28,18 @@ type pub struct {
|
||||
|
||||
// PublicKey will return the encoded public key associated with the KeyPair.
|
||||
// All KeyPairs have a public key.
|
||||
func (p *pub) PublicKey() (string, error) {
|
||||
func (p *pub) PublicKey() ([]byte, error) {
|
||||
return Encode(p.pre, p.pub)
|
||||
}
|
||||
|
||||
// Seed will return an error since this is not available for public key only KeyPairs.
|
||||
func (p *pub) Seed() (string, error) {
|
||||
return "", ErrPublicKeyOnly
|
||||
func (p *pub) Seed() ([]byte, error) {
|
||||
return nil, ErrPublicKeyOnly
|
||||
}
|
||||
|
||||
// PrivateKey will return an error since this is not available for public key only KeyPairs.
|
||||
func (p *pub) PrivateKey() (string, error) {
|
||||
return "", ErrPublicKeyOnly
|
||||
func (p *pub) PrivateKey() ([]byte, error) {
|
||||
return nil, ErrPublicKeyOnly
|
||||
}
|
||||
|
||||
// Sign will return an error since this is not available for public key only KeyPairs.
|
||||
@@ -51,3 +54,9 @@ func (p *pub) Verify(input []byte, sig []byte) error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Wipe will randomize the public key and erase the pre byte.
|
||||
func (p *pub) Wipe() {
|
||||
p.pre = '0'
|
||||
io.ReadFull(rand.Reader, p.pub)
|
||||
}
|
||||
|
||||
60
vendor/github.com/nats-io/nkeys/strkey.go
generated
vendored
60
vendor/github.com/nats-io/nkeys/strkey.go
generated
vendored
@@ -51,40 +51,43 @@ const (
|
||||
var b32Enc = base32.StdEncoding.WithPadding(base32.NoPadding)
|
||||
|
||||
// Encode will encode a raw key or seed with the prefix and crc16 and then base32 encoded.
|
||||
func Encode(prefix PrefixByte, src []byte) (string, error) {
|
||||
func Encode(prefix PrefixByte, src []byte) ([]byte, error) {
|
||||
if err := checkValidPrefixByte(prefix); err != nil {
|
||||
return "", err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var raw bytes.Buffer
|
||||
|
||||
// write prefix byte
|
||||
if err := raw.WriteByte(byte(prefix)); err != nil {
|
||||
return "", err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// write payload
|
||||
if _, err := raw.Write(src); err != nil {
|
||||
return "", err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Calculate and write crc16 checksum
|
||||
err := binary.Write(&raw, binary.LittleEndian, crc16(raw.Bytes()))
|
||||
if err != nil {
|
||||
return "", err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return b32Enc.EncodeToString(raw.Bytes()), nil
|
||||
data := raw.Bytes()
|
||||
buf := make([]byte, b32Enc.EncodedLen(len(data)))
|
||||
b32Enc.Encode(buf, data)
|
||||
return buf[:], nil
|
||||
}
|
||||
|
||||
// EncodeSeed will encode a raw key with the prefix and then seed prefix and crc16 and then base32 encoded.
|
||||
func EncodeSeed(public PrefixByte, src []byte) (string, error) {
|
||||
func EncodeSeed(public PrefixByte, src []byte) ([]byte, error) {
|
||||
if err := checkValidPublicPrefixByte(public); err != nil {
|
||||
return "", err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(src) != ed25519.SeedSize {
|
||||
return "", ErrInvalidSeedLen
|
||||
return nil, ErrInvalidSeedLen
|
||||
}
|
||||
|
||||
// In order to make this human printable for both bytes, we need to do a little
|
||||
@@ -99,24 +102,35 @@ func EncodeSeed(public PrefixByte, src []byte) (string, error) {
|
||||
|
||||
// write payload
|
||||
if _, err := raw.Write(src); err != nil {
|
||||
return "", err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Calculate and write crc16 checksum
|
||||
err := binary.Write(&raw, binary.LittleEndian, crc16(raw.Bytes()))
|
||||
if err != nil {
|
||||
return "", err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return b32Enc.EncodeToString(raw.Bytes()), nil
|
||||
data := raw.Bytes()
|
||||
buf := make([]byte, b32Enc.EncodedLen(len(data)))
|
||||
b32Enc.Encode(buf, data)
|
||||
return buf, nil
|
||||
}
|
||||
|
||||
// decode will decode the base32 string and check crc16 and the prefix for validity.
|
||||
func decode(src string) ([]byte, error) {
|
||||
raw, err := b32Enc.DecodeString(src)
|
||||
// IsValidEncoding will tell you if the encoding is a valid key.
|
||||
func IsValidEncoding(src []byte) bool {
|
||||
_, err := decode(src)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
// decode will decode the base32 and check crc16 and the prefix for validity.
|
||||
func decode(src []byte) ([]byte, error) {
|
||||
raw := make([]byte, b32Enc.EncodedLen(len(src)))
|
||||
n, err := b32Enc.Decode(raw, src)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
raw = raw[:n]
|
||||
|
||||
if len(raw) < 4 {
|
||||
return nil, ErrInvalidEncoding
|
||||
@@ -133,11 +147,11 @@ func decode(src string) ([]byte, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return raw[0 : len(raw)-2], nil
|
||||
return raw[:len(raw)-2], nil
|
||||
}
|
||||
|
||||
// Decode will decode the base32 string and check crc16 and enforce the prefix is what is expected.
|
||||
func Decode(expectedPrefix PrefixByte, src string) ([]byte, error) {
|
||||
func Decode(expectedPrefix PrefixByte, src []byte) ([]byte, error) {
|
||||
if err := checkValidPrefixByte(expectedPrefix); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -155,7 +169,7 @@ func Decode(expectedPrefix PrefixByte, src string) ([]byte, error) {
|
||||
|
||||
// DecodeSeed will decode the base32 string and check crc16 and enforce the prefix is a seed
|
||||
// and the subsequent type is a valid type.
|
||||
func DecodeSeed(src string) (PrefixByte, []byte, error) {
|
||||
func DecodeSeed(src []byte) (PrefixByte, []byte, error) {
|
||||
raw, err := decode(src)
|
||||
if err != nil {
|
||||
return PrefixByteSeed, nil, err
|
||||
@@ -174,31 +188,31 @@ func DecodeSeed(src string) (PrefixByte, []byte, error) {
|
||||
}
|
||||
|
||||
// IsValidPublicUserKey will decode and verify the string is a valid encoded Public User Key.
|
||||
func IsValidPublicUserKey(src string) bool {
|
||||
func IsValidPublicUserKey(src []byte) bool {
|
||||
_, err := Decode(PrefixByteUser, src)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
// IsValidPublicAccountKey will decode and verify the string is a valid encoded Public Account Key.
|
||||
func IsValidPublicAccountKey(src string) bool {
|
||||
func IsValidPublicAccountKey(src []byte) bool {
|
||||
_, err := Decode(PrefixByteAccount, src)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
// IsValidPublicServerKey will decode and verify the string is a valid encoded Public Server Key.
|
||||
func IsValidPublicServerKey(src string) bool {
|
||||
func IsValidPublicServerKey(src []byte) bool {
|
||||
_, err := Decode(PrefixByteServer, src)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
// IsValidPublicClusterKey will decode and verify the string is a valid encoded Public Cluster Key.
|
||||
func IsValidPublicClusterKey(src string) bool {
|
||||
func IsValidPublicClusterKey(src []byte) bool {
|
||||
_, err := Decode(PrefixByteCluster, src)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
// IsValidPublicOperatorKey will decode and verify the string is a valid encoded Public Operator Key.
|
||||
func IsValidPublicOperatorKey(src string) bool {
|
||||
func IsValidPublicOperatorKey(src []byte) bool {
|
||||
_, err := Decode(PrefixByteOperator, src)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
2
vendor/manifest
vendored
2
vendor/manifest
vendored
@@ -5,7 +5,7 @@
|
||||
"importpath": "github.com/nats-io/nkeys",
|
||||
"repository": "https://github.com/nats-io/nkeys",
|
||||
"vcs": "git",
|
||||
"revision": "9206fa847ab4dfdf7378fb1965717f32978852e2",
|
||||
"revision": "f9a6cffeb5910dad4674c41f28abde89ce590d50",
|
||||
"branch": "master",
|
||||
"notests": true
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user