From e349aef76b337f9c263abb11cbf4a9279655e334 Mon Sep 17 00:00:00 2001 From: Herb Stahl Date: Thu, 30 Jan 2025 10:38:06 -0800 Subject: [PATCH 1/3] https://github.com/nats-io/nats-server/issues/6434 Introduced type CustomLookupAccResolver struct { *DirAccResolver } --- server/accounts.go | 36 ++++++++++++++++++++++++++++++++++++ server/opts.go | 23 ++++++++++++++++++++++- server/server.go | 6 ++++++ 3 files changed, 64 insertions(+), 1 deletion(-) diff --git a/server/accounts.go b/server/accounts.go index 9f3e6cac579..5101bc4da4d 100644 --- a/server/accounts.go +++ b/server/accounts.go @@ -3941,6 +3941,42 @@ func (*resolverDefaultsOpsImpl) Store(_, _ string) error { return fmt.Errorf("store operation not supported for URL Resolver") } +type CustomLookupAccResolver struct { + *DirAccResolver +} + +func NewCustomLookupAccResolver(path string, limit int64, syncInterval time.Duration, delete deleteType, opts ...DirResOption) (*CustomLookupAccResolver, error) { + dirAccResolver, err := NewDirAccResolver(path, limit, syncInterval, delete, opts...) + if err != nil { + return nil, err + } + return &CustomLookupAccResolver{ + DirAccResolver: dirAccResolver, + }, nil +} +func (ca *CustomLookupAccResolver) Reload() error { + return ca.DirAccResolver.Reload() +} +func (ca *CustomLookupAccResolver) Start(s *Server) error { + ca.Server = s + return nil +} + +func (ca *CustomLookupAccResolver) Fetch(name string) (string, error) { + + res, err := ca.DirAccResolver.Fetch(name) + if err == nil { + return res, nil + } + // we have an error file not found + + if err == ErrMissingAccount { + ss, err := ca.Server.fetch(ca, name, ca.fetchTimeout) + return ss, err + } + return _EMPTY_, ErrMissingAccount +} + // MemAccResolver is a memory only resolver. // Mostly for testing. type MemAccResolver struct { diff --git a/server/opts.go b/server/opts.go index 172377253ee..548f0a69d8a 100644 --- a/server/opts.go +++ b/server/opts.go @@ -391,6 +391,7 @@ type Options struct { TrustedKeys []string `json:"-"` TrustedOperators []*jwt.OperatorClaims `json:"-"` AccountResolver AccountResolver `json:"-"` + AccountResolverType string `json:"-"` AccountResolverTLSConfig *tls.Config `json:"-"` // AlwaysEnableNonce will always present a nonce to new connections @@ -1357,6 +1358,7 @@ func (o *Options) processConfigFileLine(k string, v any, errors *[]error, warnin memResolverRe := regexp.MustCompile(`(?i)(MEM|MEMORY)\s*`) resolverRe := regexp.MustCompile(`(?i)(?:URL){1}(?:\({1}\s*"?([^\s"]*)"?\s*\){1})?\s*`) if memResolverRe.MatchString(v) { + o.AccountResolverType = "MEMORY" o.AccountResolver = &MemAccResolver{} } else if items := resolverRe.FindStringSubmatch(v); len(items) == 2 { url := items[1] @@ -1437,7 +1439,8 @@ func (o *Options) processConfigFileLine(k string, v any, errors *[]error, warnin } var res AccountResolver - switch strings.ToUpper(dirType) { + uDirType := strings.ToUpper(dirType) + switch uDirType { case "CACHE": checkDir() if sync != 0 { @@ -1469,11 +1472,29 @@ func (o *Options) processConfigFileLine(k string, v any, errors *[]error, warnin res, err = NewDirAccResolver(dir, limit, sync, delete, opts...) case "MEM", "MEMORY": res = &MemAccResolver{} + case "CUSTOM_LOOKUP": + checkDir() + if ttl != 0 { + *errors = append(*errors, &configErr{tk, "FULL does not accept ttl"}) + } + if hdel_set && !del { + *errors = append(*errors, &configErr{tk, "hard_delete has no effect without delete"}) + } + delete := NoDelete + if del { + if hdel { + delete = HardDelete + } else { + delete = RenameDeleted + } + } + res, err = NewCustomLookupAccResolver(dir, limit, sync, delete, opts...) } if err != nil { *errors = append(*errors, &configErr{tk, err.Error()}) return } + o.AccountResolverType = uDirType o.AccountResolver = res default: err := &configErr{tk, fmt.Sprintf("error parsing operator resolver, wrong type %T", v)} diff --git a/server/server.go b/server/server.go index 41c9cefb5f9..42db0c4942f 100644 --- a/server/server.go +++ b/server/server.go @@ -191,6 +191,7 @@ type Server struct { tmpAccounts sync.Map // Temporarily stores accounts that are being built activeAccounts int32 accResolver AccountResolver + accResolverType string clients map[uint64]*client routes map[string][]*client routesPoolSize int // Configured pool size @@ -1398,6 +1399,7 @@ func (s *Server) configureAccounts(reloading bool) (map[string]struct{}, error) func (s *Server) configureResolver() error { opts := s.getOpts() s.accResolver = opts.AccountResolver + s.accResolverType = opts.AccountResolverType if opts.AccountResolver != nil { // For URL resolver, set the TLSConfig if specified. if opts.AccountResolverTLSConfig != nil { @@ -1506,6 +1508,10 @@ func (s *Server) isTrustedIssuer(issuer string) bool { if s.trustedKeys == nil && issuer == _EMPTY_ { return true } + if s.accResolverType == "CUSTOM_LOOKUP" { + // the trusted keys is the authorization.auth_callout.issuer + return issuer == s.opts.AuthCallout.Issuer + } for _, tk := range s.trustedKeys { if tk == issuer { return true From c46367e5ac8767a93b2a48acb7492aeae8d89d04 Mon Sep 17 00:00:00 2001 From: herb stahl Date: Fri, 31 Jan 2025 09:55:32 -0800 Subject: [PATCH 2/3] refactor Added 2 custom resolver. CUSTOM_LOOKUP_FULL which is based on the FULL directly resolver CUSTOM_LOOKUP which always makes an external call. --- server/account_resolver_custom.go | 107 ++++++++++++++++++++++++++++++ server/accounts.go | 36 ---------- server/opts.go | 7 +- server/server.go | 2 +- 4 files changed, 114 insertions(+), 38 deletions(-) create mode 100644 server/account_resolver_custom.go diff --git a/server/account_resolver_custom.go b/server/account_resolver_custom.go new file mode 100644 index 00000000000..5edefe23f3b --- /dev/null +++ b/server/account_resolver_custom.go @@ -0,0 +1,107 @@ +// Copyright 2018-2024 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" + "time" +) + +// CustomLookupDirAccResolver is based on the FULL DirAccResolver and will lookup the account every time by sending a message to $SYS.REQ.ACCOUNT.*.CLAIMS.LOOKUP +// this is primarily to not go through the operatorMode flow paths. +type CustomLookupDirAccResolver struct { + *DirAccResolver +} + +var _ AccountResolver = (*CustomLookupDirAccResolver)(nil) + +// CustomLookupAccResolver is an account resolver that will lookup the account every time by sending a message to $SYS.REQ.ACCOUNT.*.CLAIMS.LOOKUP +type CustomLookupAccResolver struct { + *Server + fetchTimeout time.Duration + resolverDefaultsOpsImpl +} + +var _ AccountResolver = (*CustomLookupAccResolver)(nil) + +func NewCustomLookupDirAccResolver(path string, limit int64, syncInterval time.Duration, delete deleteType, opts ...DirResOption) (*CustomLookupDirAccResolver, error) { + dirAccResolver, err := NewDirAccResolver(path, limit, syncInterval, delete, opts...) + if err != nil { + return nil, err + } + return &CustomLookupDirAccResolver{ + DirAccResolver: dirAccResolver, + }, nil +} +func (ca *CustomLookupDirAccResolver) Reload() error { + return ca.DirAccResolver.Reload() +} +func (ca *CustomLookupDirAccResolver) Start(s *Server) error { + ca.Server = s + return nil +} + +func (ca *CustomLookupDirAccResolver) Fetch(name string) (string, error) { + + res, err := ca.DirAccResolver.Fetch(name) + if err == nil { + return res, nil + } + // we have an error file not found + + if err == ErrMissingAccount { + ss, err := ca.Server.fetch(ca, name, ca.fetchTimeout) + return ss, err + } + return _EMPTY_, ErrMissingAccount +} + +type CustomLookupResOption func(s *CustomLookupAccResolver) error + +// limits the amount of time spent waiting for an account fetch to complete +func CustomLookupResFetchTimeout(to time.Duration) CustomLookupResOption { + return func(r *CustomLookupAccResolver) error { + if to <= time.Duration(0) { + return fmt.Errorf("Fetch timeout %v is too smal", to) + } + r.fetchTimeout = to + return nil + } +} +func NewCustomLookupAccResolver(opt ...CustomLookupResOption) (*CustomLookupAccResolver, error) { + res := &CustomLookupAccResolver{ + fetchTimeout: DEFAULT_ACCOUNT_FETCH_TIMEOUT, + } + for _, o := range opt { + if err := o(res); err != nil { + return nil, err + } + } + return res, nil +} + +func (ca *CustomLookupAccResolver) Start(s *Server) error { + ca.Server = s + return nil +} +func (*CustomLookupAccResolver) Reload() error { + return nil +} +func (*CustomLookupAccResolver) Store(_, _ string) error { + return nil +} +func (ca *CustomLookupAccResolver) Fetch(name string) (string, error) { + ss, err := ca.Server.fetch(ca, name, ca.fetchTimeout) + return ss, err +} diff --git a/server/accounts.go b/server/accounts.go index 5101bc4da4d..9f3e6cac579 100644 --- a/server/accounts.go +++ b/server/accounts.go @@ -3941,42 +3941,6 @@ func (*resolverDefaultsOpsImpl) Store(_, _ string) error { return fmt.Errorf("store operation not supported for URL Resolver") } -type CustomLookupAccResolver struct { - *DirAccResolver -} - -func NewCustomLookupAccResolver(path string, limit int64, syncInterval time.Duration, delete deleteType, opts ...DirResOption) (*CustomLookupAccResolver, error) { - dirAccResolver, err := NewDirAccResolver(path, limit, syncInterval, delete, opts...) - if err != nil { - return nil, err - } - return &CustomLookupAccResolver{ - DirAccResolver: dirAccResolver, - }, nil -} -func (ca *CustomLookupAccResolver) Reload() error { - return ca.DirAccResolver.Reload() -} -func (ca *CustomLookupAccResolver) Start(s *Server) error { - ca.Server = s - return nil -} - -func (ca *CustomLookupAccResolver) Fetch(name string) (string, error) { - - res, err := ca.DirAccResolver.Fetch(name) - if err == nil { - return res, nil - } - // we have an error file not found - - if err == ErrMissingAccount { - ss, err := ca.Server.fetch(ca, name, ca.fetchTimeout) - return ss, err - } - return _EMPTY_, ErrMissingAccount -} - // MemAccResolver is a memory only resolver. // Mostly for testing. type MemAccResolver struct { diff --git a/server/opts.go b/server/opts.go index 548f0a69d8a..7c49573bbde 100644 --- a/server/opts.go +++ b/server/opts.go @@ -1385,6 +1385,8 @@ func (o *Options) processConfigFileLine(k string, v any, errors *[]error, warnin ttl := time.Duration(0) sync := time.Duration(0) opts := []DirResOption{} + customLookupOpts := []CustomLookupResOption{} + var err error if v, ok := v["dir"]; ok { _, v := unwrapValue(v, <) @@ -1420,6 +1422,7 @@ func (o *Options) processConfigFileLine(k string, v any, errors *[]error, warnin var to time.Duration if to, err = time.ParseDuration(v.(string)); err == nil { opts = append(opts, FetchTimeout(to)) + customLookupOpts = append(customLookupOpts, CustomLookupResFetchTimeout(to)) } } if err != nil { @@ -1473,6 +1476,8 @@ func (o *Options) processConfigFileLine(k string, v any, errors *[]error, warnin case "MEM", "MEMORY": res = &MemAccResolver{} case "CUSTOM_LOOKUP": + res, err = NewCustomLookupAccResolver(customLookupOpts...) + case "CUSTOM_LOOKUP_FULL": checkDir() if ttl != 0 { *errors = append(*errors, &configErr{tk, "FULL does not accept ttl"}) @@ -1488,7 +1493,7 @@ func (o *Options) processConfigFileLine(k string, v any, errors *[]error, warnin delete = RenameDeleted } } - res, err = NewCustomLookupAccResolver(dir, limit, sync, delete, opts...) + res, err = NewCustomLookupDirAccResolver(dir, limit, sync, delete, opts...) } if err != nil { *errors = append(*errors, &configErr{tk, err.Error()}) diff --git a/server/server.go b/server/server.go index 42db0c4942f..177e0384e6b 100644 --- a/server/server.go +++ b/server/server.go @@ -1508,7 +1508,7 @@ func (s *Server) isTrustedIssuer(issuer string) bool { if s.trustedKeys == nil && issuer == _EMPTY_ { return true } - if s.accResolverType == "CUSTOM_LOOKUP" { + if s.accResolverType == "CUSTOM_LOOKUP_FULL" || s.accResolverType == "CUSTOM_LOOKUP" { // the trusted keys is the authorization.auth_callout.issuer return issuer == s.opts.AuthCallout.Issuer } From 8f05f071ea4e72d042417f05209dd040c4b1d2a0 Mon Sep 17 00:00:00 2001 From: Herb Stahl Date: Fri, 31 Jan 2025 15:12:20 -0800 Subject: [PATCH 3/3] Added CustomLookupCacheDirAccResolver via CUSTOM_LOOKUP_CACHE --- server/account_resolver_custom.go | 57 +++++++++++++++++++++++++------ server/opts.go | 12 +++++++ server/server.go | 2 +- 3 files changed, 60 insertions(+), 11 deletions(-) diff --git a/server/account_resolver_custom.go b/server/account_resolver_custom.go index 5edefe23f3b..55595ff2746 100644 --- a/server/account_resolver_custom.go +++ b/server/account_resolver_custom.go @@ -18,6 +18,44 @@ import ( "time" ) +// CustomLookupCacheDirAccResolver is based on the FULL CacheDirAccResolver and will lookup the account every time by sending a message to $SYS.REQ.ACCOUNT.*.CLAIMS.LOOKUP +type CustomLookupCacheDirAccResolver struct { + *CacheDirAccResolver +} + +var _ AccountResolver = (*CustomLookupCacheDirAccResolver)(nil) + +func NewCustomLookupCacheDirAccResolver(path string, limit int64, ttl time.Duration, opts ...DirResOption) (*CustomLookupCacheDirAccResolver, error) { + dirAccResolver, err := NewCacheDirAccResolver(path, limit, ttl, opts...) + if err != nil { + return nil, err + } + return &CustomLookupCacheDirAccResolver{ + CacheDirAccResolver: dirAccResolver, + }, nil +} +func (ca *CustomLookupCacheDirAccResolver) Reload() error { + return ca.DirAccResolver.Reload() +} +func (ca *CustomLookupCacheDirAccResolver) Start(s *Server) error { + ca.Server = s + return nil +} +func (ca *CustomLookupCacheDirAccResolver) Fetch(name string) (string, error) { + + res, err := ca.DirAccResolver.Fetch(name) + if err == nil { + return res, nil + } + // we have an error file not found + + if err == ErrMissingAccount { + ss, err := ca.Server.fetch(ca, name, ca.fetchTimeout) + return ss, err + } + return _EMPTY_, ErrMissingAccount +} + // CustomLookupDirAccResolver is based on the FULL DirAccResolver and will lookup the account every time by sending a message to $SYS.REQ.ACCOUNT.*.CLAIMS.LOOKUP // this is primarily to not go through the operatorMode flow paths. type CustomLookupDirAccResolver struct { @@ -26,15 +64,6 @@ type CustomLookupDirAccResolver struct { var _ AccountResolver = (*CustomLookupDirAccResolver)(nil) -// CustomLookupAccResolver is an account resolver that will lookup the account every time by sending a message to $SYS.REQ.ACCOUNT.*.CLAIMS.LOOKUP -type CustomLookupAccResolver struct { - *Server - fetchTimeout time.Duration - resolverDefaultsOpsImpl -} - -var _ AccountResolver = (*CustomLookupAccResolver)(nil) - func NewCustomLookupDirAccResolver(path string, limit int64, syncInterval time.Duration, delete deleteType, opts ...DirResOption) (*CustomLookupDirAccResolver, error) { dirAccResolver, err := NewDirAccResolver(path, limit, syncInterval, delete, opts...) if err != nil { @@ -51,7 +80,6 @@ func (ca *CustomLookupDirAccResolver) Start(s *Server) error { ca.Server = s return nil } - func (ca *CustomLookupDirAccResolver) Fetch(name string) (string, error) { res, err := ca.DirAccResolver.Fetch(name) @@ -67,6 +95,15 @@ func (ca *CustomLookupDirAccResolver) Fetch(name string) (string, error) { return _EMPTY_, ErrMissingAccount } +// CustomLookupAccResolver is an account resolver that will lookup the account every time by sending a message to $SYS.REQ.ACCOUNT.*.CLAIMS.LOOKUP +type CustomLookupAccResolver struct { + *Server + fetchTimeout time.Duration + resolverDefaultsOpsImpl +} + +var _ AccountResolver = (*CustomLookupAccResolver)(nil) + type CustomLookupResOption func(s *CustomLookupAccResolver) error // limits the amount of time spent waiting for an account fetch to complete diff --git a/server/opts.go b/server/opts.go index 7c49573bbde..4c787b5eea9 100644 --- a/server/opts.go +++ b/server/opts.go @@ -1477,6 +1477,18 @@ func (o *Options) processConfigFileLine(k string, v any, errors *[]error, warnin res = &MemAccResolver{} case "CUSTOM_LOOKUP": res, err = NewCustomLookupAccResolver(customLookupOpts...) + case "CUSTOM_LOOKUP_CACHE": + checkDir() + if sync != 0 { + *errors = append(*errors, &configErr{tk, "CACHE does not accept sync"}) + } + if del { + *errors = append(*errors, &configErr{tk, "CACHE does not accept allow_delete"}) + } + if hdel_set { + *errors = append(*errors, &configErr{tk, "CACHE does not accept hard_delete"}) + } + res, err = NewCustomLookupCacheDirAccResolver(dir, limit, ttl, opts...) case "CUSTOM_LOOKUP_FULL": checkDir() if ttl != 0 { diff --git a/server/server.go b/server/server.go index 177e0384e6b..81b891ad46d 100644 --- a/server/server.go +++ b/server/server.go @@ -1508,7 +1508,7 @@ func (s *Server) isTrustedIssuer(issuer string) bool { if s.trustedKeys == nil && issuer == _EMPTY_ { return true } - if s.accResolverType == "CUSTOM_LOOKUP_FULL" || s.accResolverType == "CUSTOM_LOOKUP" { + if s.accResolverType == "CUSTOM_LOOKUP_FULL" || s.accResolverType == "CUSTOM_LOOKUP_CACHE" || s.accResolverType == "CUSTOM_LOOKUP" { // the trusted keys is the authorization.auth_callout.issuer return issuer == s.opts.AuthCallout.Issuer }