github.com/safing/portbase@v0.19.5/api/authentication.go (about) 1 package api 2 3 import ( 4 "context" 5 "encoding/base64" 6 "errors" 7 "fmt" 8 "net/http" 9 "net/url" 10 "strings" 11 "sync" 12 "time" 13 14 "github.com/tevino/abool" 15 16 "github.com/safing/portbase/config" 17 "github.com/safing/portbase/log" 18 "github.com/safing/portbase/modules" 19 "github.com/safing/portbase/rng" 20 ) 21 22 const ( 23 sessionCookieName = "Portmaster-API-Token" 24 sessionCookieTTL = 5 * time.Minute 25 ) 26 27 var ( 28 apiKeys = make(map[string]*AuthToken) 29 apiKeysLock sync.Mutex 30 31 authFnSet = abool.New() 32 authFn AuthenticatorFunc 33 34 sessions = make(map[string]*session) 35 sessionsLock sync.Mutex 36 37 // ErrAPIAccessDeniedMessage should be wrapped by errors returned by 38 // AuthenticatorFunc in order to signify a blocked request, including a error 39 // message for the user. This is an empty message on purpose, as to allow the 40 // function to define the full text of the error shown to the user. 41 ErrAPIAccessDeniedMessage = errors.New("") 42 ) 43 44 // Permission defines an API requests permission. 45 type Permission int8 46 47 const ( 48 // NotFound declares that the operation does not exist. 49 NotFound Permission = -2 50 51 // Dynamic declares that the operation requires permission to be processed, 52 // but anyone can execute the operation, as it reacts to permissions itself. 53 Dynamic Permission = -1 54 55 // NotSupported declares that the operation is not supported. 56 NotSupported Permission = 0 57 58 // PermitAnyone declares that anyone can execute the operation without any 59 // authentication. 60 PermitAnyone Permission = 1 61 62 // PermitUser declares that the operation may be executed by authenticated 63 // third party applications that are categorized as representing a simple 64 // user and is limited in access. 65 PermitUser Permission = 2 66 67 // PermitAdmin declares that the operation may be executed by authenticated 68 // third party applications that are categorized as representing an 69 // administrator and has broad in access. 70 PermitAdmin Permission = 3 71 72 // PermitSelf declares that the operation may only be executed by the 73 // software itself and its own (first party) components. 74 PermitSelf Permission = 4 75 ) 76 77 // AuthenticatorFunc is a function that can be set as the authenticator for the 78 // API endpoint. If none is set, all requests will have full access. 79 // The returned AuthToken represents the permissions that the request has. 80 type AuthenticatorFunc func(r *http.Request, s *http.Server) (*AuthToken, error) 81 82 // AuthToken represents either a set of required or granted permissions. 83 // All attributes must be set when the struct is built and must not be changed 84 // later. Functions may be called at any time. 85 // The Write permission implicitly also includes reading. 86 type AuthToken struct { 87 Read Permission 88 Write Permission 89 ValidUntil *time.Time 90 } 91 92 type session struct { 93 sync.Mutex 94 95 token *AuthToken 96 validUntil time.Time 97 } 98 99 // Expired returns whether the session has expired. 100 func (sess *session) Expired() bool { 101 sess.Lock() 102 defer sess.Unlock() 103 104 return time.Now().After(sess.validUntil) 105 } 106 107 // Refresh refreshes the validity of the session with the given TTL. 108 func (sess *session) Refresh(ttl time.Duration) { 109 sess.Lock() 110 defer sess.Unlock() 111 112 sess.validUntil = time.Now().Add(ttl) 113 } 114 115 // AuthenticatedHandler defines the handler interface to specify custom 116 // permission for an API handler. The returned permission is the required 117 // permission for the request to proceed. 118 type AuthenticatedHandler interface { 119 ReadPermission(*http.Request) Permission 120 WritePermission(*http.Request) Permission 121 } 122 123 // SetAuthenticator sets an authenticator function for the API endpoint. If none is set, all requests will be permitted. 124 func SetAuthenticator(fn AuthenticatorFunc) error { 125 if module.Online() { 126 return ErrAuthenticationImmutable 127 } 128 129 if !authFnSet.SetToIf(false, true) { 130 return ErrAuthenticationAlreadySet 131 } 132 133 authFn = fn 134 return nil 135 } 136 137 func authenticateRequest(w http.ResponseWriter, r *http.Request, targetHandler http.Handler, readMethod bool) *AuthToken { 138 tracer := log.Tracer(r.Context()) 139 140 // Get required permission for target handler. 141 requiredPermission := PermitSelf 142 if authdHandler, ok := targetHandler.(AuthenticatedHandler); ok { 143 if readMethod { 144 requiredPermission = authdHandler.ReadPermission(r) 145 } else { 146 requiredPermission = authdHandler.WritePermission(r) 147 } 148 } 149 150 // Check if we need to do any authentication at all. 151 switch requiredPermission { //nolint:exhaustive 152 case NotFound: 153 // Not found. 154 tracer.Debug("api: no API endpoint registered for this path") 155 http.Error(w, "Not found.", http.StatusNotFound) 156 return nil 157 case NotSupported: 158 // A read or write permission can be marked as not supported. 159 tracer.Trace("api: authenticated handler reported: not supported") 160 http.Error(w, "Method not allowed.", http.StatusMethodNotAllowed) 161 return nil 162 case PermitAnyone: 163 // Don't process permissions, as we don't need them. 164 tracer.Tracef("api: granted %s access to public handler", r.RemoteAddr) 165 return &AuthToken{ 166 Read: PermitAnyone, 167 Write: PermitAnyone, 168 } 169 case Dynamic: 170 // Continue processing permissions, but treat as PermitAnyone. 171 requiredPermission = PermitAnyone 172 } 173 174 // The required permission must match the request permission values after 175 // handling the specials. 176 if requiredPermission < PermitAnyone || requiredPermission > PermitSelf { 177 tracer.Warningf( 178 "api: handler returned invalid permission: %s (%d)", 179 requiredPermission, 180 requiredPermission, 181 ) 182 http.Error(w, "Internal server error during authentication.", http.StatusInternalServerError) 183 return nil 184 } 185 186 // Authenticate request. 187 token, handled := checkAuth(w, r, requiredPermission > PermitAnyone) 188 switch { 189 case handled: 190 return nil 191 case token == nil: 192 // Use default permissions. 193 token = &AuthToken{ 194 Read: PermitAnyone, 195 Write: PermitAnyone, 196 } 197 } 198 199 // Get effective permission for request. 200 var requestPermission Permission 201 if readMethod { 202 requestPermission = token.Read 203 } else { 204 requestPermission = token.Write 205 } 206 207 // Check for valid request permission. 208 if requestPermission < PermitAnyone || requestPermission > PermitSelf { 209 tracer.Warningf( 210 "api: authenticator returned invalid permission: %s (%d)", 211 requestPermission, 212 requestPermission, 213 ) 214 http.Error(w, "Internal server error during authentication.", http.StatusInternalServerError) 215 return nil 216 } 217 218 // Check permission. 219 if requestPermission < requiredPermission { 220 // If the token is strictly public, return an authentication request. 221 if token.Read == PermitAnyone && token.Write == PermitAnyone { 222 w.Header().Set( 223 "WWW-Authenticate", 224 `Bearer realm="Portmaster API" domain="/"`, 225 ) 226 http.Error(w, "Authorization required.", http.StatusUnauthorized) 227 return nil 228 } 229 230 // Otherwise just inform of insufficient permissions. 231 http.Error(w, "Insufficient permissions.", http.StatusForbidden) 232 return nil 233 } 234 235 tracer.Tracef("api: granted %s access to protected handler", r.RemoteAddr) 236 237 // Make a copy of the AuthToken in order mitigate the handler poisoning the 238 // token, as changes would apply to future requests. 239 return &AuthToken{ 240 Read: token.Read, 241 Write: token.Write, 242 } 243 } 244 245 func checkAuth(w http.ResponseWriter, r *http.Request, authRequired bool) (token *AuthToken, handled bool) { 246 // Return highest possible permissions in dev mode. 247 if devMode() { 248 return &AuthToken{ 249 Read: PermitSelf, 250 Write: PermitSelf, 251 }, false 252 } 253 254 // Database Bridge Access. 255 if r.RemoteAddr == endpointBridgeRemoteAddress { 256 return &AuthToken{ 257 Read: dbCompatibilityPermission, 258 Write: dbCompatibilityPermission, 259 }, false 260 } 261 262 // Check for valid API key. 263 token = checkAPIKey(r) 264 if token != nil { 265 return token, false 266 } 267 268 // Check for valid session cookie. 269 token = checkSessionCookie(r) 270 if token != nil { 271 return token, false 272 } 273 274 // Check if an external authentication method is available. 275 if !authFnSet.IsSet() { 276 return nil, false 277 } 278 279 // Authenticate externally. 280 token, err := authFn(r, server) 281 if err != nil { 282 // Check if the authentication process failed internally. 283 if !errors.Is(err, ErrAPIAccessDeniedMessage) { 284 log.Tracer(r.Context()).Errorf("api: authenticator failed: %s", err) 285 http.Error(w, "Internal server error during authentication.", http.StatusInternalServerError) 286 return nil, true 287 } 288 289 // Return authentication failure message if authentication is required. 290 if authRequired { 291 log.Tracer(r.Context()).Warningf("api: denying api access from %s", r.RemoteAddr) 292 http.Error(w, err.Error(), http.StatusForbidden) 293 return nil, true 294 } 295 296 return nil, false 297 } 298 299 // Abort if no token is returned. 300 if token == nil { 301 return nil, false 302 } 303 304 // Create session cookie for authenticated request. 305 err = createSession(w, r, token) 306 if err != nil { 307 log.Tracer(r.Context()).Warningf("api: failed to create session: %s", err) 308 } 309 return token, false 310 } 311 312 func checkAPIKey(r *http.Request) *AuthToken { 313 // Get API key from request. 314 key := r.Header.Get("Authorization") 315 if key == "" { 316 return nil 317 } 318 319 // Parse API key. 320 switch { 321 case strings.HasPrefix(key, "Bearer "): 322 key = strings.TrimPrefix(key, "Bearer ") 323 case strings.HasPrefix(key, "Basic "): 324 user, pass, _ := r.BasicAuth() 325 key = user + pass 326 default: 327 log.Tracer(r.Context()).Tracef( 328 "api: provided api key type %s is unsupported", strings.Split(key, " ")[0], 329 ) 330 return nil 331 } 332 333 apiKeysLock.Lock() 334 defer apiKeysLock.Unlock() 335 336 // Check if the provided API key exists. 337 token, ok := apiKeys[key] 338 if !ok { 339 log.Tracer(r.Context()).Tracef( 340 "api: provided api key %s... is unknown", key[:4], 341 ) 342 return nil 343 } 344 345 // Abort if the token is expired. 346 if token.ValidUntil != nil && time.Now().After(*token.ValidUntil) { 347 log.Tracer(r.Context()).Warningf("api: denying api access from %s using expired token", r.RemoteAddr) 348 return nil 349 } 350 351 return token 352 } 353 354 func updateAPIKeys(_ context.Context, _ interface{}) error { 355 apiKeysLock.Lock() 356 defer apiKeysLock.Unlock() 357 358 log.Debug("api: importing possibly updated API keys from config") 359 360 // Delete current keys. 361 for k := range apiKeys { 362 delete(apiKeys, k) 363 } 364 365 // whether or not we found expired API keys that should be removed 366 // from the setting 367 hasExpiredKeys := false 368 369 // a list of valid API keys. Used when hasExpiredKeys is set to true. 370 // in that case we'll update the setting to only contain validAPIKeys 371 validAPIKeys := []string{} 372 373 // Parse new keys. 374 for _, key := range configuredAPIKeys() { 375 u, err := url.Parse(key) 376 if err != nil { 377 log.Errorf("api: failed to parse configured API key %s: %s", key, err) 378 379 continue 380 } 381 382 if u.Path == "" { 383 log.Errorf("api: malformed API key %s: missing path section", key) 384 385 continue 386 } 387 388 // Create token with default permissions. 389 token := &AuthToken{ 390 Read: PermitAnyone, 391 Write: PermitAnyone, 392 } 393 394 // Update with configured permissions. 395 q := u.Query() 396 // Parse read permission. 397 readPermission, err := parseAPIPermission(q.Get("read")) 398 if err != nil { 399 log.Errorf("api: invalid API key %s: %s", key, err) 400 continue 401 } 402 token.Read = readPermission 403 // Parse write permission. 404 writePermission, err := parseAPIPermission(q.Get("write")) 405 if err != nil { 406 log.Errorf("api: invalid API key %s: %s", key, err) 407 continue 408 } 409 token.Write = writePermission 410 411 expireStr := q.Get("expires") 412 if expireStr != "" { 413 validUntil, err := time.Parse(time.RFC3339, expireStr) 414 if err != nil { 415 log.Errorf("api: invalid API key %s: %s", key, err) 416 continue 417 } 418 419 // continue to the next token if this one is already invalid 420 if time.Now().After(validUntil) { 421 // mark the key as expired so we'll remove it from the setting afterwards 422 hasExpiredKeys = true 423 424 continue 425 } 426 427 token.ValidUntil = &validUntil 428 } 429 430 // Save token. 431 apiKeys[u.Path] = token 432 validAPIKeys = append(validAPIKeys, key) 433 } 434 435 if hasExpiredKeys { 436 module.StartLowPriorityMicroTask("api key cleanup", 0, func(ctx context.Context) error { 437 if err := config.SetConfigOption(CfgAPIKeys, validAPIKeys); err != nil { 438 log.Errorf("api: failed to remove expired API keys: %s", err) 439 } else { 440 log.Infof("api: removed expired API keys from %s", CfgAPIKeys) 441 } 442 443 return nil 444 }) 445 } 446 447 return nil 448 } 449 450 func checkSessionCookie(r *http.Request) *AuthToken { 451 // Get session cookie from request. 452 c, err := r.Cookie(sessionCookieName) 453 if err != nil { 454 return nil 455 } 456 457 // Check if session cookie is registered. 458 sessionsLock.Lock() 459 sess, ok := sessions[c.Value] 460 sessionsLock.Unlock() 461 if !ok { 462 log.Tracer(r.Context()).Tracef("api: provided session cookie %s is unknown", c.Value) 463 return nil 464 } 465 466 // Check if session is still valid. 467 if sess.Expired() { 468 log.Tracer(r.Context()).Tracef("api: provided session cookie %s has expired", c.Value) 469 return nil 470 } 471 472 // Refresh session and return. 473 sess.Refresh(sessionCookieTTL) 474 log.Tracer(r.Context()).Tracef("api: session cookie %s is valid, refreshing", c.Value) 475 return sess.token 476 } 477 478 func createSession(w http.ResponseWriter, r *http.Request, token *AuthToken) error { 479 // Generate new session key. 480 secret, err := rng.Bytes(32) // 256 bit 481 if err != nil { 482 return err 483 } 484 sessionKey := base64.RawURLEncoding.EncodeToString(secret) 485 486 // Set token cookie in response. 487 http.SetCookie(w, &http.Cookie{ 488 Name: sessionCookieName, 489 Value: sessionKey, 490 Path: "/", 491 HttpOnly: true, 492 SameSite: http.SameSiteStrictMode, 493 }) 494 495 // Create session. 496 sess := &session{ 497 token: token, 498 } 499 sess.Refresh(sessionCookieTTL) 500 501 // Save session. 502 sessionsLock.Lock() 503 defer sessionsLock.Unlock() 504 sessions[sessionKey] = sess 505 log.Tracer(r.Context()).Debug("api: issued session cookie") 506 507 return nil 508 } 509 510 func cleanSessions(_ context.Context, _ *modules.Task) error { 511 sessionsLock.Lock() 512 defer sessionsLock.Unlock() 513 514 for sessionKey, sess := range sessions { 515 if sess.Expired() { 516 delete(sessions, sessionKey) 517 } 518 } 519 520 return nil 521 } 522 523 func deleteSession(sessionKey string) { 524 sessionsLock.Lock() 525 defer sessionsLock.Unlock() 526 527 delete(sessions, sessionKey) 528 } 529 530 func getEffectiveMethod(r *http.Request) (eMethod string, readMethod bool, ok bool) { 531 method := r.Method 532 533 // Get CORS request method if OPTIONS request. 534 if r.Method == http.MethodOptions { 535 method = r.Header.Get("Access-Control-Request-Method") 536 if method == "" { 537 return "", false, false 538 } 539 } 540 541 switch method { 542 case http.MethodGet, http.MethodHead: 543 return http.MethodGet, true, true 544 case http.MethodPost, http.MethodPut, http.MethodDelete: 545 return method, false, true 546 default: 547 return "", false, false 548 } 549 } 550 551 func parseAPIPermission(s string) (Permission, error) { 552 switch strings.ToLower(s) { 553 case "", "anyone": 554 return PermitAnyone, nil 555 case "user": 556 return PermitUser, nil 557 case "admin": 558 return PermitAdmin, nil 559 default: 560 return PermitAnyone, fmt.Errorf("invalid permission: %s", s) 561 } 562 } 563 564 func (p Permission) String() string { 565 switch p { 566 case NotSupported: 567 return "NotSupported" 568 case Dynamic: 569 return "Dynamic" 570 case PermitAnyone: 571 return "PermitAnyone" 572 case PermitUser: 573 return "PermitUser" 574 case PermitAdmin: 575 return "PermitAdmin" 576 case PermitSelf: 577 return "PermitSelf" 578 case NotFound: 579 return "NotFound" 580 default: 581 return "Unknown" 582 } 583 } 584 585 // Role returns a string representation of the permission role. 586 func (p Permission) Role() string { 587 switch p { 588 case PermitAnyone: 589 return "Anyone" 590 case PermitUser: 591 return "User" 592 case PermitAdmin: 593 return "Admin" 594 case PermitSelf: 595 return "Self" 596 case Dynamic, NotFound, NotSupported: 597 return "Invalid" 598 default: 599 return "Invalid" 600 } 601 }