diff --git a/server/account_resolver_custom.go b/server/account_resolver_custom.go new file mode 100644 index 00000000000..55595ff2746 --- /dev/null +++ b/server/account_resolver_custom.go @@ -0,0 +1,144 @@ +// 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" +) + +// 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 { + *DirAccResolver +} + +var _ AccountResolver = (*CustomLookupDirAccResolver)(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 +} + +// 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 +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/opts.go b/server/opts.go index 172377253ee..4c787b5eea9 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] @@ -1383,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, <) @@ -1418,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 { @@ -1437,7 +1442,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 +1475,43 @@ 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": + 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 { + *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 = NewCustomLookupDirAccResolver(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..81b891ad46d 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_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 + } for _, tk := range s.trustedKeys { if tk == issuer { return true