get.pme.sh/pnats@v0.0.0-20240304004023-26bb5a137ed0/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 }