go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/server/auth/auth.go (about) 1 // Copyright 2015 The LUCI Authors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package auth 16 17 import ( 18 "context" 19 "net" 20 "net/http" 21 22 "golang.org/x/oauth2" 23 "google.golang.org/grpc/codes" 24 25 "go.chromium.org/luci/auth/identity" 26 "go.chromium.org/luci/common/errors" 27 "go.chromium.org/luci/common/logging" 28 "go.chromium.org/luci/common/retry/transient" 29 "go.chromium.org/luci/grpc/grpcutil" 30 31 "go.chromium.org/luci/server/auth/authdb" 32 "go.chromium.org/luci/server/auth/delegation" 33 "go.chromium.org/luci/server/auth/internal/tracing" 34 "go.chromium.org/luci/server/auth/signing" 35 "go.chromium.org/luci/server/router" 36 ) 37 38 var ( 39 // Authenticate errors (must be grpc-tagged). 40 41 // ErrNotConfigured is returned by Authenticate and other functions if the 42 // context wasn't previously initialized via 'Initialize'. 43 ErrNotConfigured = errors.New("auth: the library is not properly configured", grpcutil.InternalTag) 44 45 // ErrBadClientID is returned by Authenticate if caller is using an OAuth2 46 // client ID not in the list of allowed IDs. More info is in the log. 47 ErrBadClientID = errors.New("auth: OAuth client_id is not in the allowlist", grpcutil.PermissionDeniedTag) 48 49 // ErrBadAudience is returned by Authenticate if token's audience is unknown. 50 ErrBadAudience = errors.New("auth: bad token audience", grpcutil.PermissionDeniedTag) 51 52 // ErrBadRemoteAddr is returned by Authenticate if request's remote_addr can't 53 // be parsed. 54 ErrBadRemoteAddr = errors.New("auth: bad remote addr", grpcutil.InternalTag) 55 56 // ErrForbiddenIP is returned when an account is restricted by an IP allowlist 57 // and request's remote_addr is not in it. 58 ErrForbiddenIP = errors.New("auth: IP is not in the allowlist", grpcutil.PermissionDeniedTag) 59 60 // ErrProjectHeaderForbidden is returned by Authenticate if an unknown caller 61 // tries to use X-Luci-Project header. Only a preapproved set of callers are 62 // allowed to use this header, see InternalServicesGroup. 63 ErrProjectHeaderForbidden = errors.New("auth: the caller is not allowed to use X-Luci-Project", grpcutil.PermissionDeniedTag) 64 65 // Other errors. 66 67 // ErrNoUsersAPI is returned by LoginURL and LogoutURL if none of 68 // the authentication methods support UsersAPI. 69 ErrNoUsersAPI = errors.New("auth: methods do not support login or logout URL") 70 71 // ErrNoForwardableCreds is returned by GetRPCTransport when attempting to 72 // forward credentials (via AsCredentialsForwarder) that are not forwardable. 73 ErrNoForwardableCreds = errors.New("auth: no forwardable credentials in the context") 74 75 // ErrNoStateEndpoint is returned by StateEndpointURL if the state endpoint is 76 // not exposed. 77 ErrNoStateEndpoint = errors.New("auth: the state endpoint is not available") 78 ) 79 80 const ( 81 // InternalServicesGroup is a name of a group with service accounts of LUCI 82 // microservices of the current LUCI deployment (and only them!). 83 // 84 // Accounts in this group are allowed to use X-Luci-Project header to specify 85 // that RPCs are done in a context of some particular project. For such 86 // requests CurrentIdentity() == 'project:<X-Luci-Project value>'. 87 // 88 // This group should contain only **fully trusted** services, deployed and 89 // managed by the LUCI deployment administrators. Adding "random" services 90 // here is a security risk, since they will be able to impersonate any LUCI 91 // project. 92 InternalServicesGroup = "auth-luci-services" 93 ) 94 95 // RequestMetadata is metadata used when authenticating a request. 96 // 97 // Can be constructed by: 98 // - RequestMetadataForHTTP based on http.Request. 99 // - authtest.NewFakeRequestMetadata based on fakes for unit tests. 100 type RequestMetadata interface { 101 // Header returns a value of a given header or an empty string. 102 // 103 // Headers are also known as simply "metadata" in gRPC world. 104 // 105 // The key is case-insensitive. If the request has multiple headers matching 106 // the key, returns only the first one. 107 Header(key string) string 108 109 // Cookie returns a cookie or an error if there's no such cookie. 110 // 111 // Transports that do not support cookies (e.g. gRPC) can always return 112 // an error. They will just not work with authentication schemes based on 113 // cookies. 114 Cookie(key string) (*http.Cookie, error) 115 116 // RemoteAddr returns the IP address the request came from or "" if unknown. 117 // 118 // It is used by default for IP allowlist checks if there's no EndUserIP 119 // callback set in the auth library configuration. The EndUserIP callback is 120 // usually set in environments where the server runs behind a proxy, when 121 // the real end user IP is passed via some trusted header or other form of 122 // metadata. 123 // 124 // If "", IP allowlist check will be skipped and the request will be assumed 125 // to come from "0.0.0.0" aka "unspecified IPv4". 126 RemoteAddr() string 127 128 // Host returns the hostname the request was sent to or "" if unknown. 129 // 130 // Also known as HTTP2 `:authority` pseudo-header. 131 Host() string 132 } 133 134 // Method implements a particular low-level authentication mechanism. 135 // 136 // It may also optionally implement a bunch of other interfaces: 137 // 138 // UsersAPI: if the method supports login and logout URLs. 139 // Warmable: if the method supports warm up. 140 // HasHandlers: if the method needs to install HTTP handlers. 141 // 142 // Methods are not usually used directly, but passed to Authenticator{...} that 143 // knows how to apply them. 144 type Method interface { 145 // Authenticate extracts user information from the incoming request. 146 // 147 // It returns: 148 // * (*User, Session or nil, nil) on success. 149 // * (nil, nil, nil) if the method is not applicable. 150 // * (nil, nil, error) if the method is applicable, but credentials are bad. 151 // 152 // The returned error may be tagged with an grpcutil error tag. Its code will 153 // be used to derive the response status code. Internal error messages (e.g. 154 // ones tagged with grpcutil.InternalTag or similar) are logged, but not sent 155 // to clients. All other errors are sent to clients as is. 156 Authenticate(context.Context, RequestMetadata) (*User, Session, error) 157 } 158 159 // UsersAPI may be additionally implemented by Method if it supports login and 160 // logout URLs. 161 type UsersAPI interface { 162 // LoginURL returns a URL that, when visited, prompts the user to sign in, 163 // then redirects the user to the URL specified by dest. 164 LoginURL(ctx context.Context, dest string) (string, error) 165 166 // LogoutURL returns a URL that, when visited, signs the user out, 167 // then redirects the user to the URL specified by dest. 168 LogoutURL(ctx context.Context, dest string) (string, error) 169 } 170 171 // Warmable may be additionally implemented by Method if it supports warm up. 172 type Warmable interface { 173 // Warmup may be called to precache the data needed by the method. 174 // 175 // There's no guarantee when it will be called or if it will be called at all. 176 // Should always do best-effort initialization. Errors are logged and ignored. 177 Warmup(ctx context.Context) error 178 } 179 180 // HasHandlers may be additionally implemented by Method if it needs to 181 // install HTTP handlers. 182 type HasHandlers interface { 183 // InstallHandlers installs necessary HTTP handlers into the router. 184 InstallHandlers(r *router.Router, base router.MiddlewareChain) 185 } 186 187 // HasStateEndpoint may be additionally implemented by Method if it exposes 188 // an HTTP endpoints that returns the authentication state, OAuth and ID tokens 189 // for frontend applications. 190 type HasStateEndpoint interface { 191 // StateEndpointURL returns an URL that serves StateEndpointResponse JSON. 192 // 193 // See StateEndpointResponse for the format and meaning of the response. 194 // 195 // Returns ErrNoStateEndpoint if the endpoint is not actually exposed. This 196 // can happen if the method generally supports the state endpoint, but it is 197 // turned off in the method's configuration. 198 StateEndpointURL(ctx context.Context) (string, error) 199 } 200 201 // UserCredentialsGetter may be additionally implemented by Method if it knows 202 // how to extract end-user credentials from the incoming request. Currently 203 // understands only OAuth2 tokens. 204 type UserCredentialsGetter interface { 205 // GetUserCredentials extracts an OAuth access token from the incoming request 206 // or returns an error if it isn't possible. 207 // 208 // May omit token's expiration time if it isn't known. 209 // 210 // Guaranteed to be called only after the successful authentication, so it 211 // doesn't have to recheck the validity of the token. 212 GetUserCredentials(context.Context, RequestMetadata) (*oauth2.Token, error) 213 } 214 215 // Session holds some extra information pertaining to the request. 216 // 217 // It is stored in the context as part of State. Used by AsSessionUser RPC 218 // authority kind. 219 type Session interface { 220 // AccessToken returns an OAuth access token identifying the session user. 221 AccessToken(ctx context.Context) (*oauth2.Token, error) 222 // IDToken returns an ID token identifying the session user. 223 IDToken(ctx context.Context) (*oauth2.Token, error) 224 } 225 226 // User represents identity and profile of a user. 227 type User struct { 228 // Identity is identity string of the user (may be AnonymousIdentity). 229 // If User is returned by Authenticate(...), Identity string is always present 230 // and valid. 231 Identity identity.Identity `json:"identity,omitempty"` 232 233 // Superuser is true if the user is site-level administrator. For example, on 234 // GAE this bit is set for GAE-level administrators. Optional, default false. 235 Superuser bool `json:"superuser,omitempty"` 236 237 // Email is email of the user. Optional, default "". Don't use it as a key 238 // in various structures. Prefer to use Identity() instead (it is always 239 // available). 240 Email string `json:"email,omitempty"` 241 242 // Name is full name of the user. Optional, default "". 243 Name string `json:"name,omitempty"` 244 245 // Picture is URL of the user avatar. Optional, default "". 246 Picture string `json:"picture,omitempty"` 247 248 // ClientID is the ID of the pre-registered OAuth2 client so its identity can 249 // be verified. Used only by authentication methods based on OAuth2. 250 // See https://developers.google.com/console/help/#generatingoauth2 for more. 251 ClientID string `json:"client_id,omitempty"` 252 253 // Extra is any additional information the authentication method produces. 254 // 255 // Its exact type depends on the authentication method used. Usually the 256 // authentication method will have an accompanying getter function that knows 257 // how to interpret this field. 258 Extra any `json:"-"` 259 } 260 261 // StateEndpointResponse defines a JSON structure of a state endpoint response. 262 // 263 // It represents the state of the authentication session based on the session 264 // cookie (or other ambient credential) in the request metadata. 265 // 266 // It is intended to be called via a same origin URL fetch request by the 267 // frontend code that needs an OAuth access token or an ID token representing 268 // the signed in user. 269 // 270 // If there's a valid authentication credential, the state endpoint replies with 271 // HTTP 200 status code and the JSON-serialized StateEndpointResponse struct 272 // with state details. The handler refreshes access and ID tokens if they expire 273 // soon. 274 // 275 // If there is no authentication credential or it has expired or was revoked, 276 // the state endpoint still replies with HTTP 200 code and the JSON-serialized 277 // StateEndpointResponse struct, except its `identity` field is 278 // `anonymous:anonymous` and no other fields are populated. 279 // 280 // On errors the state endpoint replies with a non-200 HTTP status code with a 281 // `plain/text` body containing the error message. This is an exceptional 282 // situation (usually internal transient errors caused by the session store 283 // unavailability or some misconfiguration in code). Replies with HTTP code 284 // equal or larger than 500 indicate transient errors and can be retried. 285 // 286 // The state endpoint is exposed only by auth methods that implement 287 // HasStateEndpoint interface (e.g. `encryptedcookies`), and only if they are 288 // configured to expose it. 289 type StateEndpointResponse struct { 290 // Identity is a LUCI identity string of the user or `anonymous:anonymous` if 291 // the user is not logged in. 292 Identity string `json:"identity"` 293 294 // Email is the email of the user account if the user is logged in. 295 Email string `json:"email,omitempty"` 296 297 // Picture is the https URL of the user profile picture if available. 298 Picture string `json:"picture,omitempty"` 299 300 // AccessToken is an OAuth access token of the logged in user. 301 // 302 // See RequiredScopes and OptionalScopes in AuthMethod for what scopes this 303 // token can have. 304 AccessToken string `json:"accessToken,omitempty"` 305 306 // AccessTokenExpiry is an absolute expiration time (as a unix timestamp) of 307 // the access token. 308 // 309 // It is at least 10 min in the future. 310 AccessTokenExpiry int64 `json:"accessTokenExpiry,omitempty"` 311 312 // AccessTokenExpiresIn is approximately how long the access token will be 313 // valid since when the response was generated, in seconds. 314 // 315 // It is at least 600 sec. 316 AccessTokenExpiresIn int32 `json:"accessTokenExpiresIn,omitempty"` 317 318 // IDToken is an identity token of the logged in user. 319 // 320 // Its `aud` claim is equal to ClientID in OpenIDConfig passed to AuthMethod. 321 IDToken string `json:"idToken,omitempty"` 322 323 // IDTokenExpiry is an absolute expiration time (as a unix timestamp) of 324 // the identity token. 325 // 326 // It is at least 10 min in the future. 327 IDTokenExpiry int64 `json:"idTokenExpiry,omitempty"` 328 329 // IDTokenExpiresIn is approximately how long the identity token will be 330 // valid since when the response was generated, in seconds. 331 // 332 // It is at least 600 sec. 333 IDTokenExpiresIn int32 `json:"idTokenExpiresIn,omitempty"` 334 } 335 336 // Authenticator performs authentication of incoming requests. 337 // 338 // It is a stateless object configured with a list of methods to try when 339 // authenticating incoming requests. It implements Authenticate method that 340 // performs high-level authentication logic using the provided list of low-level 341 // auth methods. 342 // 343 // Note that most likely you don't need to instantiate this object directly. 344 // Use Authenticate middleware instead. Authenticator is exposed publicly only 345 // to be used in advanced cases, when you need to fine-tune authentication 346 // behavior. 347 type Authenticator struct { 348 Methods []Method // a list of authentication methods to try 349 } 350 351 // GetMiddleware returns a middleware that uses this Authenticator for 352 // authentication. 353 // 354 // It uses a.Authenticate internally and handles errors appropriately. 355 // 356 // TODO(vadimsh): Refactor to be a function instead of a method and move to 357 // http.go. 358 func (a *Authenticator) GetMiddleware() router.Middleware { 359 return func(c *router.Context, next router.Handler) { 360 ctx, err := a.AuthenticateHTTP(c.Request.Context(), c.Request) 361 if err != nil { 362 code, ok := grpcutil.Tag.In(err) 363 if !ok { 364 if transient.Tag.In(err) { 365 code = codes.Internal 366 } else { 367 code = codes.Unauthenticated 368 } 369 } 370 replyError(c.Request.Context(), c.Writer, grpcutil.CodeStatus(code), err) 371 } else { 372 c.Request = c.Request.WithContext(ctx) 373 next(c) 374 } 375 } 376 } 377 378 // AuthenticateHTTP authenticates an HTTP request. 379 // 380 // See Authenticate for all details. 381 // 382 // This method is likely temporary until pRPC server switches to use gRPC 383 // interceptors for authentication. 384 func (a *Authenticator) AuthenticateHTTP(ctx context.Context, r *http.Request) (context.Context, error) { 385 return a.Authenticate(ctx, RequestMetadataForHTTP(r)) 386 } 387 388 // Authenticate authenticates the request and adds State into the context. 389 // 390 // Returns an error if credentials are provided, but invalid. If no credentials 391 // are provided (i.e. the request is anonymous), finishes successfully, but in 392 // that case CurrentIdentity() returns AnonymousIdentity. 393 // 394 // The returned error may be tagged with an grpcutil error tag. Its code should 395 // be used to derive the response status code. Internal error messages (e.g. 396 // ones tagged with grpcutil.InternalTag or similar) should be logged, but not 397 // sent to clients. All other errors should be sent to clients as is. 398 func (a *Authenticator) Authenticate(ctx context.Context, r RequestMetadata) (_ context.Context, err error) { 399 tracedCtx, span := tracing.Start(ctx, "go.chromium.org/luci/server/auth.Authenticate") 400 report := durationReporter(tracedCtx, authenticateDuration) 401 402 // This variable is changed throughout the function's execution. It it used 403 // in the defer to figure out at what stage the call failed. 404 stage := "" 405 406 // This defer reports the outcome of the authentication to the monitoring. 407 defer func() { 408 switch { 409 case err == nil: 410 report(nil, "SUCCESS") 411 case err == ErrNotConfigured: 412 report(err, "ERROR_NOT_CONFIGURED") 413 case err == ErrBadClientID: 414 report(err, "ERROR_FORBIDDEN_OAUTH_CLIENT") 415 case err == ErrBadAudience: 416 report(err, "ERROR_FORBIDDEN_AUDIENCE") 417 case err == ErrBadRemoteAddr: 418 report(err, "ERROR_BAD_REMOTE_ADDR") 419 case err == ErrForbiddenIP: 420 report(err, "ERROR_FORBIDDEN_IP") 421 case err == ErrProjectHeaderForbidden: 422 report(err, "ERROR_PROJECT_HEADER_FORBIDDEN") 423 case transient.Tag.In(err): 424 report(err, "ERROR_TRANSIENT_IN_"+stage) 425 default: 426 report(err, "ERROR_IN_"+stage) 427 } 428 tracing.End(span, err) 429 }() 430 431 // We will need working DB factory below to check IP allowlist. 432 cfg := getConfig(tracedCtx) 433 if cfg == nil || cfg.DBProvider == nil || len(a.Methods) == 0 { 434 return nil, ErrNotConfigured 435 } 436 437 // The future state that will be placed into the context. 438 s := state{authenticator: a, endUserErr: ErrNoForwardableCreds} 439 440 // Pick the first authentication method that applies. 441 stage = "AUTH" 442 for _, m := range a.Methods { 443 var err error 444 if s.user, s.session, err = m.Authenticate(tracedCtx, r); err != nil { 445 return nil, err 446 } 447 if s.user != nil { 448 if err = s.user.Identity.Validate(); err != nil { 449 stage = "ID_REGEXP_CHECK" 450 return nil, err 451 } 452 s.method = m 453 break 454 } 455 } 456 457 // If no authentication method is applicable, default to anonymous identity. 458 if s.method == nil { 459 s.user = &User{Identity: identity.AnonymousIdentity} 460 s.session = nil 461 } 462 463 // peerIdent always matches the identity of a remote peer. It may end up being 464 // different from s.user.Identity if the delegation tokens or project 465 // identities are used (see below). They affect s.user.Identity but don't 466 // touch s.peerIdent. 467 s.peerIdent = s.user.Identity 468 469 // Grab a snapshot of auth DB to use it consistently for the duration of this 470 // request. 471 stage = "AUTHDB_FETCH" 472 s.db, err = cfg.DBProvider(tracedCtx) 473 if err != nil { 474 return nil, err 475 } 476 477 // If using OAuth2, make sure the ClientID is allowlisted. 478 if s.user.ClientID != "" { 479 stage = "OAUTH_CLIENT_ID_CHECK" 480 if err := checkClientID(tracedCtx, cfg, s.db, s.user.Email, s.user.ClientID); err != nil { 481 return nil, err 482 } 483 } 484 485 // Extract peer's IP address and, if necessary, check it against an allowlist. 486 stage = "IP_CHECK" 487 if s.peerIP, err = checkEndUserIP(tracedCtx, cfg, s.db, r, s.peerIdent); err != nil { 488 return nil, err 489 } 490 491 // Check X-Delegation-Token-V1 and X-Luci-Project headers. They are used in 492 // LUCI-specific protocols to allow LUCI micro-services to act on behalf of 493 // end-users or projects. 494 var delegationToken string 495 var projectHeader string 496 if delegationToken = r.Header(delegation.HTTPHeaderName); delegationToken != "" { 497 stage = "DELEGATION_TOKEN_CHECK" 498 if s.user, err = checkDelegationToken(tracedCtx, cfg, s.db, delegationToken, s.peerIdent); err != nil { 499 return nil, err 500 } 501 } else if projectHeader = r.Header(XLUCIProjectHeader); projectHeader != "" { 502 stage = "PROJECT_HEADER_CHECK" 503 if s.user, err = checkProjectHeader(tracedCtx, s.db, projectHeader, s.peerIdent); err != nil { 504 return nil, err 505 } 506 } 507 508 // If the main authentication mechanism is based on forwardable OAuth tokens, 509 // grab all forwardable headers for GetRPCTransport(AsCredentialsForwarder). 510 if credsGetter, _ := s.method.(UserCredentialsGetter); credsGetter != nil { 511 s.endUserTok, s.endUserErr = credsGetter.GetUserCredentials(tracedCtx, r) 512 if s.endUserErr == nil && (delegationToken != "" || projectHeader != "") { 513 s.endUserExtraHeaders = make(map[string]string, 2) 514 if delegationToken != "" { 515 s.endUserExtraHeaders[delegation.HTTPHeaderName] = delegationToken 516 } 517 if projectHeader != "" { 518 s.endUserExtraHeaders[XLUCIProjectHeader] = projectHeader 519 } 520 } 521 } 522 523 // Inject the auth state into the original context (not the traced one). 524 return WithState(ctx, &s), nil 525 } 526 527 // usersAPI returns implementation of UsersAPI by examining Methods. 528 // 529 // Returns nil if none of Methods implement UsersAPI. 530 func (a *Authenticator) usersAPI() UsersAPI { 531 for _, m := range a.Methods { 532 if api, ok := m.(UsersAPI); ok { 533 return api 534 } 535 } 536 return nil 537 } 538 539 // LoginURL returns a URL that, when visited, prompts the user to sign in, 540 // then redirects the user to the URL specified by dest. 541 // 542 // Returns ErrNoUsersAPI if none of the authentication methods support login 543 // URLs. 544 func (a *Authenticator) LoginURL(ctx context.Context, dest string) (string, error) { 545 if api := a.usersAPI(); api != nil { 546 return api.LoginURL(ctx, dest) 547 } 548 return "", ErrNoUsersAPI 549 } 550 551 // LogoutURL returns a URL that, when visited, signs the user out, then 552 // redirects the user to the URL specified by dest. 553 // 554 // Returns ErrNoUsersAPI if none of the authentication methods support login 555 // URLs. 556 func (a *Authenticator) LogoutURL(ctx context.Context, dest string) (string, error) { 557 if api := a.usersAPI(); api != nil { 558 return api.LogoutURL(ctx, dest) 559 } 560 return "", ErrNoUsersAPI 561 } 562 563 //// 564 565 // replyError logs the error and writes a response to ResponseWriter. 566 // 567 // For codes < 500, the error is logged at Warning level and written to the 568 // response as is. For codes >= 500 the error is logged at Error level and 569 // the generic error message is written instead. 570 func replyError(ctx context.Context, rw http.ResponseWriter, code int, err error) { 571 if code < 500 { 572 logging.Warningf(ctx, "HTTP %d: %s", code, err) 573 http.Error(rw, err.Error(), code) 574 } else { 575 logging.Errorf(ctx, "HTTP %d: %s", code, err) 576 http.Error(rw, http.StatusText(code), code) 577 } 578 } 579 580 // checkClientID returns nil if the clientID is allowed, ErrBadClientID if not, 581 // and a transient errors if the check itself failed. 582 func checkClientID(ctx context.Context, cfg *Config, db authdb.DB, email, clientID string) error { 583 // Check the global allowlist in the AuthDB. 584 switch valid, err := db.IsAllowedOAuthClientID(ctx, email, clientID); { 585 case err != nil: 586 return errors.Annotate(err, "failed to check client ID allowlist").Tag(transient.Tag).Err() 587 case valid: 588 return nil 589 } 590 591 // It may be an app-specific client ID supplied via cfg.FrontendClientID. 592 if cfg.FrontendClientID != nil { 593 switch frontendClientID, err := cfg.FrontendClientID(ctx); { 594 case err != nil: 595 return errors.Annotate(err, "failed to grab frontend client ID").Tag(transient.Tag).Err() 596 case clientID == frontendClientID: 597 return nil 598 } 599 } 600 601 logging.Errorf(ctx, "auth: %q is using client_id %q not in the allowlist", email, clientID) 602 return ErrBadClientID 603 } 604 605 // checkEndUserIP parses the caller IP address and checks it against an 606 // allowlist (if necessary). Returns ErrBadRemoteAddr if the IP is malformed, 607 // ErrForbiddenIP if the IP is not allowlisted or a transient error if the check 608 // itself failed. 609 func checkEndUserIP(ctx context.Context, cfg *Config, db authdb.DB, r RequestMetadata, peerID identity.Identity) (net.IP, error) { 610 var ipAddr string 611 if cfg.EndUserIP != nil { 612 ipAddr = cfg.EndUserIP(r) 613 } else { 614 ipAddr = r.RemoteAddr() 615 } 616 peerIP, err := parseRemoteIP(ipAddr) 617 if err != nil { 618 logging.Errorf(ctx, "auth: bad remote_addr %q in a call from %q - %s", ipAddr, peerID, err) 619 return nil, ErrBadRemoteAddr 620 } 621 if peerIP.IsUnspecified() { 622 return peerIP, nil 623 } 624 625 // Some callers may be constrained by an IP allowlist. 626 switch ipAllowlist, err := db.GetAllowlistForIdentity(ctx, peerID); { 627 case err != nil: 628 return nil, errors.Annotate(err, "failed to get IP allowlist for identity %q", peerID).Tag(transient.Tag).Err() 629 case ipAllowlist != "": 630 switch allowed, err := db.IsAllowedIP(ctx, peerIP, ipAllowlist); { 631 case err != nil: 632 return nil, errors.Annotate(err, "failed to check IP %s is in the allowlist %q", peerIP, ipAllowlist).Tag(transient.Tag).Err() 633 case !allowed: 634 return nil, ErrForbiddenIP 635 } 636 } 637 638 return peerIP, nil 639 } 640 641 // checkDelegationToken checks correctness of a delegation token and returns 642 // a delegated *User. 643 func checkDelegationToken(ctx context.Context, cfg *Config, db authdb.DB, token string, peerID identity.Identity) (*User, error) { 644 // Log the token fingerprint (even before parsing the token), it can be used 645 // to grab the info about the token from the token server logs. 646 logging.Fields{ 647 "fingerprint": tokenFingerprint(token), 648 }.Debugf(ctx, "auth: Received delegation token") 649 650 // Need to grab our own identity to verify that the delegation token is 651 // minted for consumption by us and not some other service. 652 ownServiceIdentity, err := getOwnServiceIdentity(ctx, cfg.Signer) 653 if err != nil { 654 return nil, err 655 } 656 delegatedIdentity, err := delegation.CheckToken(ctx, delegation.CheckTokenParams{ 657 Token: token, 658 PeerID: peerID, 659 CertificatesProvider: db, 660 GroupsChecker: db, 661 OwnServiceIdentity: ownServiceIdentity, 662 }) 663 if err != nil { 664 return nil, err 665 } 666 667 // Log that peerID is pretending to be delegatedIdentity. 668 logging.Fields{ 669 "peerID": peerID, 670 "delegatedID": delegatedIdentity, 671 }.Debugf(ctx, "auth: Using delegation") 672 673 return &User{Identity: delegatedIdentity}, nil 674 } 675 676 // checkProjectHeader verifies the caller is allowed to use X-Luci-Project 677 // mechanism and returns a *User (with project-scoped identity) to use for 678 // the request. 679 func checkProjectHeader(ctx context.Context, db authdb.DB, project string, peerID identity.Identity) (*User, error) { 680 // See comment for InternalServicesGroup. 681 switch yes, err := db.IsMember(ctx, peerID, []string{InternalServicesGroup}); { 682 case err != nil: 683 return nil, errors.Annotate(err, "error when checking if %q is in %q", peerID, InternalServicesGroup).Tag(transient.Tag).Err() 684 case !yes: 685 return nil, ErrProjectHeaderForbidden 686 } 687 688 // Verify the actual value passes the regexp check. 689 projIdent, err := identity.MakeIdentity("project:" + project) 690 if err != nil { 691 return nil, errors.Annotate(err, "bad %s", XLUCIProjectHeader).Err() 692 } 693 694 // Log that peerID is using project-scoped identity. 695 logging.Fields{ 696 "peerID": peerID, 697 "projectID": projIdent, 698 }.Debugf(ctx, "auth: Using project identity") 699 700 return &User{Identity: projIdent}, nil 701 } 702 703 // getOwnServiceIdentity returns 'service:<appID>' identity of the current 704 // service. 705 func getOwnServiceIdentity(ctx context.Context, signer signing.Signer) (identity.Identity, error) { 706 if signer == nil { 707 return "", ErrNotConfigured 708 } 709 switch serviceInfo, err := signer.ServiceInfo(ctx); { 710 case err != nil: 711 return "", err 712 case serviceInfo.AppID == "": 713 return "", errors.Reason("auth: don't known our own app ID to check the delegation token is for us").Err() 714 default: 715 return identity.MakeIdentity("service:" + serviceInfo.AppID) 716 } 717 }