mirror of
https://github.com/fleetdm/fleet
synced 2026-04-21 13:37:30 +00:00
<!-- Add the related story/sub-task/bug number, like Resolves #123, or remove if NA --> **Related issue:** Resolves #34528 # Details This PR implements the agent changes for allowing Fleet admins to require that users authenticate with an IdP prior to having their devices set up. I'll comment on changes inline but the high-level is: 1. Orbit calls the enroll endpoint as usual. This is triggered lazily by any one of a number of subsystems like device token rotation or requesting Fleet config 2. If the enroll endpoint returns the new `ErrEndUserAuthRequired` response, then it opens a window to the `/mdm/sso` Fleet page and retries the enroll endpoint every 30 seconds indefinitely. 3. Any other non-200 response to the enroll request is treated as before (limited # of retries, with backoff) # Checklist for submitter If some of the following don't apply, delete the relevant line. - [ ] Changes file added for user-visible changes in `changes/`, `orbit/changes/` or `ee/fleetd-chrome/changes`. See [Changes files](https://github.com/fleetdm/fleet/blob/main/docs/Contributing/guides/committing- changes.md#changes-files) for more information. Will add changelog when story is one. ## Testing - [X] Added/updated automated tests Added test for new retry logic - [X] QA'd all new/changed functionality manually This is kinda hard to test without the associated backend PR: https://github.com/fleetdm/fleet/pull/34835 ## fleetd/orbit/Fleet Desktop - [X] Verified compatibility with the latest released version of Fleet (see [Must rule](https://github.com/fleetdm/fleet/blob/main/docs/Contributing/workflows/fleetd-development-and-release-strategy.md)) This is compatible with all Fleet versions, since older ones won't send the new error. - [X] If the change applies to only one platform, confirmed that `runtime.GOOS` is used as needed to isolate changes This is compatible with all platforms, although it currently should only ever run on Windows and Linux since macOS devices will have end-user auth taken care of before they even download Orbit. - [ ] Verified that fleetd runs on macOS, Linux and Windows Testing this now. - [ ] Verified auto-update works from the released version of component to the new version (see [tools/tuf/test](../tools/tuf/test/README.md)) <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Added SSO (Single Sign-On) enrollment support for end-user authentication * Enhanced error messaging for authentication-required scenarios * **Bug Fixes** * Improved error handling and retry logic for enrollment failures <!-- end of auto-generated comment: release notes by coderabbit.ai -->
121 lines
2.9 KiB
Go
121 lines
2.9 KiB
Go
// Package retry has utilities to retry operations.
|
||
package retry
|
||
|
||
import (
|
||
"time"
|
||
)
|
||
|
||
// ErrorOutcome tells retry.Do how to react to a returned error.
|
||
// Use with WithErrorFilter to control retry behavior.
|
||
type ErrorOutcome int
|
||
|
||
const (
|
||
// ErrorOutcomeNormalRetry indicates that the error is retryable
|
||
// and the retry loop should continue as normal.
|
||
ErrorOutcomeNormalRetry ErrorOutcome = iota
|
||
// ErrorOutcomeResetAttempts indicates that the retry attempts counter
|
||
// should be reset to zero.
|
||
// Useful for hijacking the retry cycle to retry indefinitely
|
||
// until a certain condition is met.
|
||
ErrorOutcomeResetAttempts
|
||
// ErrorOutcomeIgnore indicates that the error should be ignored
|
||
// and the retry loop should exit successfully.
|
||
ErrorOutcomeIgnore
|
||
// ErrorOutcomeDoNotRetry indicates that the error is not retryable
|
||
// and the retry loop should exit with the error.
|
||
ErrorOutcomeDoNotRetry
|
||
)
|
||
|
||
type config struct {
|
||
initialInterval time.Duration
|
||
backoffMultiplier int
|
||
maxAttempts int
|
||
errorFilter func(error) ErrorOutcome
|
||
}
|
||
|
||
// Option allows to configure the behavior of retry.Do
|
||
type Option func(*config)
|
||
|
||
// WithInterval allows to specify a custom duration to wait
|
||
// between retries.
|
||
func WithInterval(i time.Duration) Option {
|
||
return func(c *config) {
|
||
c.initialInterval = i
|
||
}
|
||
}
|
||
|
||
// WithBackoffMultiplier allows to specify the backoff multiplier between retries.
|
||
func WithBackoffMultiplier(m int) Option {
|
||
return func(c *config) {
|
||
c.backoffMultiplier = m
|
||
}
|
||
}
|
||
|
||
// WithMaxAttempts allows to specify a maximum number of attempts
|
||
// before the doer gives up.
|
||
func WithMaxAttempts(a int) Option {
|
||
return func(c *config) {
|
||
c.maxAttempts = a
|
||
}
|
||
}
|
||
|
||
// WithErrorFilter sets a function that maps errors to retry outcomes.
|
||
// The filter is evaluated before max‑attempts/backoff handling.
|
||
func WithErrorFilter(f func(error) ErrorOutcome) Option {
|
||
return func(c *config) {
|
||
c.errorFilter = f
|
||
}
|
||
}
|
||
|
||
// Do executes the provided function, if the function returns a
|
||
// non-nil error it performs a retry according to the options
|
||
// provided.
|
||
//
|
||
// By default operations are retried an unlimited number of times for 30
|
||
// seconds
|
||
func Do(fn func() error, opts ...Option) error {
|
||
cfg := &config{
|
||
initialInterval: 30 * time.Second,
|
||
}
|
||
for _, opt := range opts {
|
||
opt(cfg)
|
||
}
|
||
|
||
attempts := 0
|
||
ticker := time.NewTicker(cfg.initialInterval)
|
||
defer ticker.Stop()
|
||
|
||
backoff := 1
|
||
for {
|
||
attempts++
|
||
err := fn()
|
||
if err == nil {
|
||
return nil
|
||
}
|
||
if cfg.errorFilter != nil {
|
||
switch cfg.errorFilter(err) {
|
||
case ErrorOutcomeIgnore:
|
||
return nil
|
||
case ErrorOutcomeResetAttempts:
|
||
attempts = 0
|
||
backoff = 1
|
||
case ErrorOutcomeDoNotRetry:
|
||
return err
|
||
default:
|
||
// continue with normal retry
|
||
}
|
||
}
|
||
|
||
if cfg.maxAttempts != 0 && attempts >= cfg.maxAttempts {
|
||
return err
|
||
}
|
||
|
||
if cfg.backoffMultiplier != 0 {
|
||
interval := time.Duration(backoff) * cfg.initialInterval
|
||
backoff *= cfg.backoffMultiplier
|
||
ticker.Reset(interval)
|
||
}
|
||
|
||
<-ticker.C
|
||
}
|
||
}
|