github.com/nats-io/nats-server/v2@v2.11.0-preview.2/server/auth_callout.go (about)

     1  // Copyright 2022-2023 The NATS Authors
     2  // Licensed under the Apache License, Version 2.0 (the "License");
     3  // you may not use this file except in compliance with the License.
     4  // You may obtain a copy of the License at
     5  //
     6  // http://www.apache.org/licenses/LICENSE-2.0
     7  //
     8  // Unless required by applicable law or agreed to in writing, software
     9  // distributed under the License is distributed on an "AS IS" BASIS,
    10  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    11  // See the License for the specific language governing permissions and
    12  // limitations under the License.
    13  
    14  package server
    15  
    16  import (
    17  	"bytes"
    18  	"crypto/tls"
    19  	"encoding/pem"
    20  	"errors"
    21  	"fmt"
    22  	"time"
    23  	"unicode"
    24  
    25  	"github.com/nats-io/jwt/v2"
    26  	"github.com/nats-io/nkeys"
    27  )
    28  
    29  const (
    30  	AuthCalloutSubject    = "$SYS.REQ.USER.AUTH"
    31  	AuthRequestSubject    = "nats-authorization-request"
    32  	AuthRequestXKeyHeader = "Nats-Server-Xkey"
    33  )
    34  
    35  // Process a callout on this client's behalf.
    36  func (s *Server) processClientOrLeafCallout(c *client, opts *Options) (authorized bool, errStr string) {
    37  	isOperatorMode := len(opts.TrustedKeys) > 0
    38  
    39  	// this is the account the user connected in, or the one running the callout
    40  	var acc *Account
    41  	if !isOperatorMode && opts.AuthCallout != nil && opts.AuthCallout.Account != _EMPTY_ {
    42  		aname := opts.AuthCallout.Account
    43  		var err error
    44  		acc, err = s.LookupAccount(aname)
    45  		if err != nil {
    46  			errStr = fmt.Sprintf("No valid account %q for auth callout request: %v", aname, err)
    47  			s.Warnf(errStr)
    48  			return false, errStr
    49  		}
    50  	} else {
    51  		acc = c.acc
    52  	}
    53  
    54  	// Check if we have been requested to encrypt.
    55  	var xkp nkeys.KeyPair
    56  	var xkey string
    57  	var pubAccXKey string
    58  	if !isOperatorMode && opts.AuthCallout != nil && opts.AuthCallout.XKey != _EMPTY_ {
    59  		pubAccXKey = opts.AuthCallout.XKey
    60  	} else if isOperatorMode {
    61  		pubAccXKey = acc.externalAuthXKey()
    62  	}
    63  	// If set grab server's xkey keypair and public key.
    64  	if pubAccXKey != _EMPTY_ {
    65  		// These are only set on creation, so lock not needed.
    66  		xkp, xkey = s.xkp, s.info.XKey
    67  	}
    68  
    69  	// FIXME: so things like the server ID that get assigned, are used as a sort of nonce - but
    70  	//  reality is that the keypair here, is generated, so the response generated a JWT has to be
    71  	//  this user - no replay possible
    72  	// Create a keypair for the user. We will expect this public user to be in the signed response.
    73  	// This prevents replay attacks.
    74  	ukp, _ := nkeys.CreateUser()
    75  	pub, _ := ukp.PublicKey()
    76  
    77  	reply := s.newRespInbox()
    78  	respCh := make(chan string, 1)
    79  
    80  	decodeResponse := func(rc *client, rmsg []byte, acc *Account) (*jwt.UserClaims, error) {
    81  		account := acc.Name
    82  		_, msg := rc.msgParts(rmsg)
    83  
    84  		// This signals not authorized.
    85  		// Since this is an account subscription will always have "\r\n".
    86  		if len(msg) <= LEN_CR_LF {
    87  			return nil, fmt.Errorf("auth callout violation: %q on account %q", "no reason supplied", account)
    88  		}
    89  		// Strip trailing CRLF.
    90  		msg = msg[:len(msg)-LEN_CR_LF]
    91  		encrypted := false
    92  		// If we sent an encrypted request the response could be encrypted as well.
    93  		// we are expecting the input to be `eyJ` if it is a JWT
    94  		if xkp != nil && len(msg) > 0 && !bytes.HasPrefix(msg, []byte(jwtPrefix)) {
    95  			var err error
    96  			msg, err = xkp.Open(msg, pubAccXKey)
    97  			if err != nil {
    98  				return nil, fmt.Errorf("error decrypting auth callout response on account %q: %v", account, err)
    99  			}
   100  			encrypted = true
   101  		}
   102  
   103  		cr, err := jwt.DecodeAuthorizationResponseClaims(string(msg))
   104  		if err != nil {
   105  			return nil, err
   106  		}
   107  		vr := jwt.CreateValidationResults()
   108  		cr.Validate(vr)
   109  		if len(vr.Issues) > 0 {
   110  			return nil, fmt.Errorf("authorization response had validation errors: %v", vr.Issues[0])
   111  		}
   112  
   113  		// the subject is the user id
   114  		if cr.Subject != pub {
   115  			return nil, errors.New("auth callout violation: auth callout response is not for expected user")
   116  		}
   117  
   118  		// check the audience to be the server ID
   119  		if cr.Audience != s.info.ID {
   120  			return nil, errors.New("auth callout violation: auth callout response is not for server")
   121  		}
   122  
   123  		// check if had an error message from the auth account
   124  		if cr.Error != _EMPTY_ {
   125  			return nil, fmt.Errorf("auth callout service returned an error: %v", cr.Error)
   126  		}
   127  
   128  		// if response is encrypted none of this is needed
   129  		if isOperatorMode && !encrypted {
   130  			pkStr := cr.Issuer
   131  			if cr.IssuerAccount != _EMPTY_ {
   132  				pkStr = cr.IssuerAccount
   133  			}
   134  			if pkStr != account {
   135  				if _, ok := acc.signingKeys[pkStr]; !ok {
   136  					return nil, errors.New("auth callout signing key is unknown")
   137  				}
   138  			}
   139  		}
   140  
   141  		return jwt.DecodeUserClaims(cr.Jwt)
   142  	}
   143  
   144  	// getIssuerAccount returns the issuer (as per JWT) - it also asserts that
   145  	// only in operator mode we expect to receive `issuer_account`.
   146  	getIssuerAccount := func(arc *jwt.UserClaims, account string) (string, error) {
   147  		// Make sure correct issuer.
   148  		var issuer string
   149  		if opts.AuthCallout != nil {
   150  			issuer = opts.AuthCallout.Issuer
   151  		} else {
   152  			// Operator mode is who we send the request on unless switching accounts.
   153  			issuer = acc.Name
   154  		}
   155  
   156  		// the jwt issuer can be a signing key
   157  		jwtIssuer := arc.Issuer
   158  		if arc.IssuerAccount != _EMPTY_ {
   159  			if !isOperatorMode {
   160  				// this should be invalid - effectively it would allow the auth callout
   161  				// to issue on another account which may be allowed given the configuration
   162  				// where the auth callout account can handle multiple different ones..
   163  				return _EMPTY_, fmt.Errorf("error non operator mode account %q: attempted to use issuer_account", account)
   164  			}
   165  			jwtIssuer = arc.IssuerAccount
   166  		}
   167  
   168  		if jwtIssuer != issuer {
   169  			if !isOperatorMode {
   170  				return _EMPTY_, fmt.Errorf("wrong issuer for auth callout response on account %q, expected %q got %q", account, issuer, jwtIssuer)
   171  			} else if !acc.isAllowedAcount(jwtIssuer) {
   172  				return _EMPTY_, fmt.Errorf("account %q not permitted as valid account option for auth callout for account %q",
   173  					arc.Issuer, account)
   174  			}
   175  		}
   176  		return jwtIssuer, nil
   177  	}
   178  
   179  	getExpirationAndAllowedConnections := func(arc *jwt.UserClaims, account string) (time.Duration, map[string]struct{}, error) {
   180  		allowNow, expiration := validateTimes(arc)
   181  		if !allowNow {
   182  			c.Errorf("Outside connect times")
   183  			return 0, nil, fmt.Errorf("authorized user on account %q outside of valid connect times", account)
   184  		}
   185  
   186  		allowedConnTypes, err := convertAllowedConnectionTypes(arc.User.AllowedConnectionTypes)
   187  		if err != nil {
   188  			c.Debugf("%v", err)
   189  			if len(allowedConnTypes) == 0 {
   190  				return 0, nil, fmt.Errorf("authorized user on account %q using invalid connection type", account)
   191  			}
   192  		}
   193  		return expiration, allowedConnTypes, nil
   194  	}
   195  
   196  	assignAccountAndPermissions := func(arc *jwt.UserClaims, account string) (*Account, error) {
   197  		// Apply to this client.
   198  		var err error
   199  		issuerAccount, err := getIssuerAccount(arc, account)
   200  		if err != nil {
   201  			return nil, err
   202  		}
   203  
   204  		// if we are not in operator mode, they can specify placement as a tag
   205  		var placement string
   206  		if !isOperatorMode {
   207  			// only allow placement if we are not in operator mode
   208  			placement = arc.Audience
   209  		} else {
   210  			placement = issuerAccount
   211  		}
   212  
   213  		targetAcc, err := s.LookupAccount(placement)
   214  		if err != nil {
   215  			return nil, fmt.Errorf("no valid account %q for auth callout response on account %q: %v", placement, account, err)
   216  		}
   217  		if isOperatorMode {
   218  			// this will validate the signing key that emitted the user, and if it is a signing
   219  			// key it assigns the permissions from the target account
   220  			if scope, ok := targetAcc.hasIssuer(arc.Issuer); !ok {
   221  				return nil, fmt.Errorf("user JWT issuer %q is not known", arc.Issuer)
   222  			} else if scope != nil {
   223  				// this possibly has to be different because it could just be a plain issued by a non-scoped signing key
   224  				if err := scope.ValidateScopedSigner(arc); err != nil {
   225  					return nil, fmt.Errorf("user JWT is not valid: %v", err)
   226  				} else if uSc, ok := scope.(*jwt.UserScope); !ok {
   227  					return nil, fmt.Errorf("user JWT is not a valid scoped user")
   228  				} else if arc.User.UserPermissionLimits, err = processUserPermissionsTemplate(uSc.Template, arc, targetAcc); err != nil {
   229  					return nil, fmt.Errorf("user JWT generated invalid permissions: %v", err)
   230  				}
   231  			}
   232  		}
   233  		return targetAcc, nil
   234  	}
   235  
   236  	processReply := func(_ *subscription, rc *client, racc *Account, subject, reply string, rmsg []byte) {
   237  		titleCase := func(m string) string {
   238  			r := []rune(m)
   239  			return string(append([]rune{unicode.ToUpper(r[0])}, r[1:]...))
   240  		}
   241  
   242  		arc, err := decodeResponse(rc, rmsg, racc)
   243  		if err != nil {
   244  			c.authViolation()
   245  			respCh <- titleCase(err.Error())
   246  			return
   247  		}
   248  		vr := jwt.CreateValidationResults()
   249  		arc.Validate(vr)
   250  		if len(vr.Issues) > 0 {
   251  			c.authViolation()
   252  			respCh <- fmt.Sprintf("Error validating user JWT: %v", vr.Issues[0])
   253  			return
   254  		}
   255  
   256  		// Make sure that the user is what we requested.
   257  		if arc.Subject != pub {
   258  			c.authViolation()
   259  			respCh <- fmt.Sprintf("Expected authorized user of %q but got %q on account %q", pub, arc.Subject, racc.Name)
   260  			return
   261  		}
   262  
   263  		expiration, allowedConnTypes, err := getExpirationAndAllowedConnections(arc, racc.Name)
   264  		if err != nil {
   265  			c.authViolation()
   266  			respCh <- titleCase(err.Error())
   267  			return
   268  		}
   269  
   270  		targetAcc, err := assignAccountAndPermissions(arc, racc.Name)
   271  		if err != nil {
   272  			c.authViolation()
   273  			respCh <- titleCase(err.Error())
   274  			return
   275  		}
   276  
   277  		// the JWT is cleared, because if in operator mode it may hold the JWT
   278  		// for the bearer token that connected to the callout if in operator mode
   279  		// the permissions are already set on the client, this prevents a decode
   280  		// on c.RegisterNKeyUser which would have wrong values
   281  		c.mu.Lock()
   282  		c.opts.JWT = _EMPTY_
   283  		c.mu.Unlock()
   284  
   285  		// Build internal user and bind to the targeted account.
   286  		nkuser := buildInternalNkeyUser(arc, allowedConnTypes, targetAcc)
   287  		if err := c.RegisterNkeyUser(nkuser); err != nil {
   288  			c.authViolation()
   289  			respCh <- fmt.Sprintf("Could not register auth callout user: %v", err)
   290  			return
   291  		}
   292  
   293  		// See if the response wants to override the username.
   294  		if arc.Name != _EMPTY_ {
   295  			c.mu.Lock()
   296  			c.opts.Username = arc.Name
   297  			// Clear any others.
   298  			c.opts.Nkey = _EMPTY_
   299  			c.pubKey = _EMPTY_
   300  			c.opts.Token = _EMPTY_
   301  			c.mu.Unlock()
   302  		}
   303  
   304  		// Check if we need to set an auth timer if the user jwt expires.
   305  		c.setExpiration(arc.Claims(), expiration)
   306  
   307  		respCh <- _EMPTY_
   308  	}
   309  
   310  	// create a subscription to receive a response from the authcallout
   311  	sub, err := acc.subscribeInternal(reply, processReply)
   312  	if err != nil {
   313  		errStr = fmt.Sprintf("Error setting up reply subscription for auth request: %v", err)
   314  		s.Warnf(errStr)
   315  		return false, errStr
   316  	}
   317  	defer acc.unsubscribeInternal(sub)
   318  
   319  	// Build our request claims - jwt subject should be nkey
   320  	jwtSub := acc.Name
   321  	if opts.AuthCallout != nil {
   322  		jwtSub = opts.AuthCallout.Issuer
   323  	}
   324  
   325  	// The public key of the server, if set is available on Varz.Key
   326  	// This means that when a service connects, it can now peer
   327  	// authenticate if it wants to - but that also means that it needs to be
   328  	// listening to cluster changes
   329  	claim := jwt.NewAuthorizationRequestClaims(jwtSub)
   330  	claim.Audience = AuthRequestSubject
   331  	// Set expected public user nkey.
   332  	claim.UserNkey = pub
   333  
   334  	s.mu.RLock()
   335  	claim.Server = jwt.ServerID{
   336  		Name:    s.info.Name,
   337  		Host:    s.info.Host,
   338  		ID:      s.info.ID,
   339  		Version: s.info.Version,
   340  		Cluster: s.info.Cluster,
   341  	}
   342  	s.mu.RUnlock()
   343  
   344  	// Tags
   345  	claim.Server.Tags = s.getOpts().Tags
   346  
   347  	// Check if we have been requested to encrypt.
   348  	// FIXME: possibly this public key also needs to be on the
   349  	//  Varz, because then it can be peer verified?
   350  	if xkp != nil {
   351  		claim.Server.XKey = xkey
   352  	}
   353  
   354  	authTimeout := secondsToDuration(s.getOpts().AuthTimeout)
   355  	claim.Expires = time.Now().Add(time.Duration(authTimeout)).UTC().Unix()
   356  
   357  	// Grab client info for the request.
   358  	c.mu.Lock()
   359  	c.fillClientInfo(&claim.ClientInformation)
   360  	c.fillConnectOpts(&claim.ConnectOptions)
   361  	// If we have a sig in the client opts, fill in nonce.
   362  	if claim.ConnectOptions.SignedNonce != _EMPTY_ {
   363  		claim.ClientInformation.Nonce = string(c.nonce)
   364  	}
   365  
   366  	// TLS
   367  	if c.flags.isSet(handshakeComplete) && c.nc != nil {
   368  		var ct jwt.ClientTLS
   369  		conn := c.nc.(*tls.Conn)
   370  		cs := conn.ConnectionState()
   371  		ct.Version = tlsVersion(cs.Version)
   372  		ct.Cipher = tlsCipher(cs.CipherSuite)
   373  		// Check verified chains.
   374  		for _, vs := range cs.VerifiedChains {
   375  			var certs []string
   376  			for _, c := range vs {
   377  				blk := &pem.Block{
   378  					Type:  "CERTIFICATE",
   379  					Bytes: c.Raw,
   380  				}
   381  				certs = append(certs, string(pem.EncodeToMemory(blk)))
   382  			}
   383  			ct.VerifiedChains = append(ct.VerifiedChains, certs)
   384  		}
   385  		// If we do not have verified chains put in peer certs.
   386  		if len(ct.VerifiedChains) == 0 {
   387  			for _, c := range cs.PeerCertificates {
   388  				blk := &pem.Block{
   389  					Type:  "CERTIFICATE",
   390  					Bytes: c.Raw,
   391  				}
   392  				ct.Certs = append(ct.Certs, string(pem.EncodeToMemory(blk)))
   393  			}
   394  		}
   395  		claim.TLS = &ct
   396  	}
   397  	c.mu.Unlock()
   398  
   399  	b, err := claim.Encode(s.kp)
   400  	if err != nil {
   401  		errStr = fmt.Sprintf("Error encoding auth request claim on account %q: %v", acc.Name, err)
   402  		s.Warnf(errStr)
   403  		return false, errStr
   404  	}
   405  	req := []byte(b)
   406  	var hdr map[string]string
   407  
   408  	// Check if we have been asked to encrypt.
   409  	if xkp != nil {
   410  		req, err = xkp.Seal([]byte(req), pubAccXKey)
   411  		if err != nil {
   412  			errStr = fmt.Sprintf("Error encrypting auth request claim on account %q: %v", acc.Name, err)
   413  			s.Warnf(errStr)
   414  			return false, errStr
   415  		}
   416  		hdr = map[string]string{AuthRequestXKeyHeader: xkey}
   417  	}
   418  
   419  	// Send out our request.
   420  	if err := s.sendInternalAccountMsgWithReply(acc, AuthCalloutSubject, reply, hdr, req, false); err != nil {
   421  		errStr = fmt.Sprintf("Error sending authorization request: %v", err)
   422  		s.Debugf(errStr)
   423  		return false, errStr
   424  	}
   425  	select {
   426  	case errStr = <-respCh:
   427  		if authorized = errStr == _EMPTY_; !authorized {
   428  			s.Warnf(errStr)
   429  		}
   430  	case <-time.After(authTimeout):
   431  		s.Debugf(fmt.Sprintf("Authorization callout response not received in time on account %q", acc.Name))
   432  	}
   433  
   434  	return authorized, errStr
   435  }
   436  
   437  // Fill in client information for the request.
   438  // Lock should be held.
   439  func (c *client) fillClientInfo(ci *jwt.ClientInformation) {
   440  	if c == nil || (c.kind != CLIENT && c.kind != LEAF && c.kind != JETSTREAM && c.kind != ACCOUNT) {
   441  		return
   442  	}
   443  
   444  	// Do it this way to fail to compile if fields are added to jwt.ClientInformation.
   445  	*ci = jwt.ClientInformation{
   446  		Host:    c.host,
   447  		ID:      c.cid,
   448  		User:    c.getRawAuthUser(),
   449  		Name:    c.opts.Name,
   450  		Tags:    c.tags,
   451  		NameTag: c.nameTag,
   452  		Kind:    c.kindString(),
   453  		Type:    c.clientTypeString(),
   454  		MQTT:    c.getMQTTClientID(),
   455  	}
   456  }
   457  
   458  // Fill in client options.
   459  // Lock should be held.
   460  func (c *client) fillConnectOpts(opts *jwt.ConnectOptions) {
   461  	if c == nil || (c.kind != CLIENT && c.kind != LEAF && c.kind != JETSTREAM && c.kind != ACCOUNT) {
   462  		return
   463  	}
   464  
   465  	o := c.opts
   466  
   467  	// Do it this way to fail to compile if fields are added to jwt.ClientInformation.
   468  	*opts = jwt.ConnectOptions{
   469  		JWT:         o.JWT,
   470  		Nkey:        o.Nkey,
   471  		SignedNonce: o.Sig,
   472  		Token:       o.Token,
   473  		Username:    o.Username,
   474  		Password:    o.Password,
   475  		Name:        o.Name,
   476  		Lang:        o.Lang,
   477  		Version:     o.Version,
   478  		Protocol:    o.Protocol,
   479  	}
   480  }