-
Notifications
You must be signed in to change notification settings - Fork 714
End-user authentication for Window/Linux setup experience: agent #34847
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
14ef3a3
61bcc63
9c4788b
78b1eb6
4e1897b
d07a968
2999ca3
71a69a4
bf664d4
ab5f3f9
d4b0ed5
4fb193a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -67,6 +67,13 @@ type OrbitClient struct { | |
| // If set then it will be deleted on HTTP 401 errors from Fleet and it will cause ExecuteConfigReceivers | ||
| // to terminate to trigger a restart. | ||
| hostIdentityCertPath string | ||
|
|
||
| // initiatedIdpAuth is a flag indicating whether a window has been opened | ||
| // to the sign-on page for the organization's Identity Provider. | ||
| initiatedIdpAuth bool | ||
|
|
||
| // openSSOWindow is a function that opens a browser window to the SSO URL. | ||
| openSSOWindow func() error | ||
| } | ||
|
|
||
| // time-to-live for config cache | ||
|
|
@@ -79,6 +86,10 @@ type configCache struct { | |
| err error | ||
| } | ||
|
|
||
| func (oc *OrbitClient) SetOpenSSOWindowFunc(f func() error) { | ||
| oc.openSSOWindow = f | ||
| } | ||
|
|
||
| func (oc *OrbitClient) request(verb string, path string, params interface{}, resp interface{}) error { | ||
| return oc.requestWithExternal(verb, path, params, resp, false) | ||
| } | ||
|
|
@@ -537,38 +548,58 @@ func (oc *OrbitClient) getNodeKeyOrEnroll() (string, error) { | |
| default: | ||
| return "", fmt.Errorf("read orbit node key file: %w", err) | ||
| } | ||
| var ( | ||
| orbitNodeKey_ string | ||
| endpointDoesNotExist bool | ||
| ) | ||
| var orbitNodeKey_ string | ||
| if err := retry.Do( | ||
| func() error { | ||
| var err error | ||
| orbitNodeKey_, err = oc.enrollAndWriteNodeKeyFile() | ||
| switch { | ||
| case err == nil: | ||
| return nil | ||
| case errors.Is(err, notFoundErr{}): | ||
| // Do not retry if the endpoint does not exist. | ||
| endpointDoesNotExist = true | ||
| return nil | ||
| default: | ||
| logging.LogErrIfEnvNotSet(constant.SilenceEnrollLogErrorEnvVar, err, "enroll failed, retrying") | ||
| return err | ||
| } | ||
| return err | ||
|
Comment on lines
545
to
+555
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. All of this logic is now moved into the |
||
| }, | ||
| // The below configuration means the following retry intervals (exponential backoff): | ||
| // 10s, 20s, 40s, 80s, 160s and then return the failure (max attempts = 6) | ||
| // thus executing no more than ~6 enroll request failures every ~5 minutes. | ||
| retry.WithInterval(orbitEnrollRetryInterval()), | ||
| retry.WithMaxAttempts(constant.OrbitEnrollMaxRetries), | ||
| retry.WithBackoffMultiplier(constant.OrbitEnrollBackoffMultiplier), | ||
| retry.WithErrorFilter(func(err error) (errorOutcome retry.ErrorOutcome) { | ||
| log.Info().Err(err).Msg("orbit enroll attempt failed") | ||
| switch { | ||
| case errors.Is(err, notFoundErr{}): | ||
| // Do not retry if the endpoint does not exist. | ||
| return retry.ErrorOutcomeDoNotRetry | ||
| case errors.Is(err, ErrEndUserAuthRequired): | ||
| // If we get an ErrEndUserAuthRequired error, then the user | ||
| // needs to authenticate with the identity provider. | ||
| // | ||
| // Open a browser window to the sign-on page and | ||
| // then keep retrying until they authenticate. | ||
| log.Debug().Msg("enroll unauthenticated, waiting for end-user to authenticate via SSO") | ||
| if !oc.initiatedIdpAuth { | ||
| if oc.openSSOWindow == nil { | ||
| log.Error().Msg("SSO window open function not set") | ||
| return retry.ErrorOutcomeNormalRetry | ||
| } | ||
| log.Debug().Msg("opening SSO window") | ||
| openWindowErr := oc.openSSOWindow() | ||
| if openWindowErr != nil { | ||
| log.Error().Err(openWindowErr).Msg("opening SSO window") | ||
| return retry.ErrorOutcomeNormalRetry | ||
| } | ||
| oc.initiatedIdpAuth = true | ||
| } | ||
| // Sleep for 20 seconds, making the total retry interval 30 seconds | ||
| time.Sleep(20 * time.Second) | ||
| return retry.ErrorOutcomeResetAttempts | ||
| default: | ||
| logging.LogErrIfEnvNotSet(constant.SilenceEnrollLogErrorEnvVar, err, "enroll failed, retrying") | ||
| return retry.ErrorOutcomeNormalRetry | ||
| } | ||
| }), | ||
| ); err != nil { | ||
| if errors.Is(err, notFoundErr{}) { | ||
| return "", errors.New("enroll endpoint does not exist") | ||
| } | ||
| return "", fmt.Errorf("orbit node key enroll failed, attempts=%d", constant.OrbitEnrollMaxRetries) | ||
iansltx marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| } | ||
| if endpointDoesNotExist { | ||
| return "", errors.New("enroll endpoint does not exist") | ||
| } | ||
| return orbitNodeKey_, nil | ||
| } | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -19,6 +19,16 @@ | |
| 'simplesaml.nameidattribute' => 'email', | ||
| ); | ||
|
|
||
| # Use for local testing of devices on the same network. | ||
| $metadata['mdm.host.docker.internal'] = array( | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Probably fine but wanted to make sure you knew you were committing this.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes! Meant to comment on it. It makes testing this easier, I added testing instructions which rely on this 😄
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Or at least I have a very strong memory of adding those instructions, but I don't see them 😩 . Wonder if I didn't save. |
||
| 'AssertionConsumerService' => [ | ||
| 'https://host.docker.internal:8080/api/v1/fleet/mdm/sso/callback', | ||
| ], | ||
| 'NameIDFormat' => 'urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddres', | ||
| 'simplesaml.nameidattribute' => 'email', | ||
| ); | ||
|
|
||
|
|
||
| # Used for testing when sso_settings.entity_id ("sso.test.com") is different than | ||
| # server_settings.server_url (usually "https://localhost:8080"). | ||
| $metadata['sso.test.com'] = array( | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.