Skip to content

Headless SSO web endpoint and UI#22914

Merged
jakule merged 11 commits intomasterfrom
jakule/headless-authn-tsh
Mar 22, 2023
Merged

Headless SSO web endpoint and UI#22914
jakule merged 11 commits intomasterfrom
jakule/headless-authn-tsh

Conversation

@jakule
Copy link
Copy Markdown
Contributor

@jakule jakule commented Mar 10, 2023

Closes #22272

Comment thread lib/client/api.go Outdated
Comment thread web/packages/teleport/src/HeadlessSSO/Cards.tsx Outdated
Comment thread web/packages/teleport/src/HeadlessSSO/HeadlessSSO.tsx Outdated
Comment thread lib/web/headless.go Outdated
Comment thread lib/web/headless.go Outdated
Comment thread web/packages/teleport/src/HeadlessSSO/Cards.tsx
Comment thread web/packages/teleport/src/HeadlessSSO/Cards.tsx
Comment thread web/packages/teleport/src/HeadlessSSO/Cards.tsx
Comment thread web/packages/teleport/src/HeadlessSSO/HeadlessSSO.tsx Outdated
Comment thread web/packages/teleport/src/components/HeadlessSsoDialog/HeadlessSsoDialog.tsx Outdated
Comment thread web/packages/teleport/src/HeadlessSSO/HeadlessSSO.tsx Outdated
Comment thread web/packages/teleport/src/HeadlessSSO/HeadlessSSO.tsx Outdated
Comment thread web/packages/teleport/src/HeadlessSSO/HeadlessSSO.tsx Outdated
@Joerger Joerger force-pushed the joerger/headless-authn-tsh branch 2 times, most recently from d707fc2 to 804d728 Compare March 15, 2023 17:50
Base automatically changed from joerger/headless-authn-tsh to master March 15, 2023 20:27
@Joerger Joerger force-pushed the jakule/headless-authn-tsh branch from 4877f38 to 2701462 Compare March 17, 2023 01:06
@Joerger Joerger changed the base branch from master to joerger/headless-authn-server March 17, 2023 01:06
@Joerger Joerger marked this pull request as ready for review March 17, 2023 01:06
Base automatically changed from joerger/headless-authn-server to master March 17, 2023 01:18
Comment thread lib/web/headless.go Outdated
Comment thread web/packages/teleport/src/HeadlessSso/HeadlessSso.tsx Outdated
jakule and others added 6 commits March 16, 2023 18:48
Update UI text

Update the code to add headless request get

Remove commented code

Added simple UI and endpoints
Implement reject SSO handler and UI
@Joerger Joerger force-pushed the jakule/headless-authn-tsh branch from 345bcc6 to 916ec0f Compare March 17, 2023 01:48
Comment thread lib/web/headless.go Outdated
func (h *Handler) getHeadless(_ http.ResponseWriter, r *http.Request, params httprouter.Params, sctx *SessionContext) (any, error) {
headlessAuthenticationID := params.ByName("headless_authentication_id")
if headlessAuthenticationID == "" {
return nil, trace.NotFound("failed to find Headless session") // TODO this or just failed?
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is "Headless" here supposed to be uppercase? It's inconsistent with this fragment:

teleport/lib/client/api.go

Lines 3462 to 3463 in eda29be

fmt.Fprintf(tc.Stdout, "Complete headless authentication in your local web browser:\n\n%s\n"+
"\nor execute this command in your local terminal:\n\n%s\n", webUILink, tshApprove)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lowercase, fixed.

Comment thread lib/web/headless.go Outdated
func (h *Handler) putHeadlessState(w http.ResponseWriter, r *http.Request, params httprouter.Params, sctx *SessionContext) (any, error) {
headlessAuthenticationID := params.ByName("headless_authentication_id")
if headlessAuthenticationID == "" {
return nil, trace.NotFound("failed to find Headless session") // TODO this or just failed?
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same q about capitalization as above.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The same a as above. It should be lowercase.

Comment on lines +48 to +57
auth
.headlessSSOGet(requestId)
.then(setIpAddress)
.catch(e => {
setState({
...state,
status: 'error',
errorText: e.toString(),
});
});
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: I assume this catch is supposed to catch errors from auth.headlessSSOGet and we don't expect setIpAddress to throw errors. In cases like this, I find the double argument form of then to express the intent better. It's a bit unfortunate how Prettier formats it though:

    auth.headlessSSOGet(requestId).then(setIpAddress, e => {
      setState({
        ...state,
        status: 'error',
        errorText: e.toString(),
      });
    });

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there an easy way to test this feature locally?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, checkout this branch, enable mandatory webauthn (this is fixed in https://github.com/gravitational/teleport/pull/23271/files), call tsh ls --headless --user=<user> --proxy=<proxy> and follow the prompts. If you have any questions, let me know.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks. I made a very silly assumption by thinking that because the component has Sso in its name, then I need to have SAML or OIDC enabled on my cluster. 🤦

Do you think something like HeadlessRequest would be a better name than HeadlessSso for this route?

@jakule jakule requested review from hatched, ravicious and zmb3 March 20, 2023 22:01
@Joerger Joerger mentioned this pull request Mar 20, 2023
18 tasks
)}
<Text textAlign="center">
Someone has initiated a command from {ipAddress}. If it was not you,
click cancel and contact your administrator.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This says "click cancel" but the button to close the dialog says "Reject".

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point, I'll rename that.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I reject the request and then refresh the URL, I see a spinner for a solid few seconds until eventually the deadline gets exceeded and I see "ApiError: rpc error: code = Unknown desc = context deadline exceeded" with a half functioning dialog (clicking Reject doesn't seem to do anything and Retry asks me for a hardware key touch).

Would it be possible to display the error sooner?

deadline exceeded error

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I changed the UI error screen. Let me know what do you think about it.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks. I made a very silly assumption by thinking that because the component has Sso in its name, then I need to have SAML or OIDC enabled on my cluster. 🤦

Do you think something like HeadlessRequest would be a better name than HeadlessSso for this route?

@jakule jakule force-pushed the jakule/headless-authn-tsh branch from a837548 to cc45878 Compare March 21, 2023 20:58
@jakule jakule requested a review from ravicious March 21, 2023 22:49
Copy link
Copy Markdown
Member

@ravicious ravicious left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's good enough for the 12.2 release. I'd consider two improvements in the future:

  • As I mentioned before, return the error immediately if the session is not valid. The user shouldn't have to wait for the deadline if the session was rejected already or if there's a typo in the UUID because the user copy-pasted just a part of the URL from the terminal.
  • Accepting a request and then opening its URL again shows the modal again, as if I didn't approve it already. Only after clicking Approve or Reject I get a relevant error message. It feels like this should be shown immediately instead.

onReject,
errorText,
}: Props) {
const dialogContent = () => {
Copy link
Copy Markdown
Member

@ravicious ravicious Mar 22, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not a fan of splitting a component into parts like this, mostly because you can no longer read the component top to bottom to understand the structure of what is going to end up being rendered.

The conditionals could be inlined which IMHO would do a better job of showing what goes in the dialog content and what goes in the footer:

Updated version of HeadlessRequestDialog
export default function HeadlessRequestDialog({
  ipAddress,
  onAccept,
  onReject,
  errorText,
}: Props) {
  return (
    <Dialog dialogCss={() => ({ width: '400px' })} open={true}>
      <DialogHeader style={{ flexDirection: 'column' }}>
        <DialogTitle textAlign="center">
          Host {ipAddress} wants to execute a command
        </DialogTitle>
      </DialogHeader>
      <DialogContent mb={6}>
        {errorText && (
          <Danger mt={2} width="100%">
            {errorText}
          </Danger>
        )}
        <Text textAlign="center">
          {errorText ? (
            <>
              The requested session doesn't exist or is invalid. Please generate
              a new request.
              <br />
              <br />
              You can close this window.
            </>
          ) : (
            <>
              Someone has initiated a command from {ipAddress}. If it was not
              you, click reject and contact your administrator.
              <br />
              <br />
              If it was you, please use your hardware key to approve.
            </>
          )}
        </Text>
      </DialogContent>
      <DialogFooter textAlign="center">
        {!errorText && (
          <>
            <ButtonPrimary onClick={onAccept} autoFocus mr={3} width="130px">
              Approve
            </ButtonPrimary>
            <ButtonSecondary onClick={onReject}>Reject</ButtonSecondary>
          </>
        )}
      </DialogFooter>
    </Dialog>
  );
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not React Dev. Applying what people tells me is better 😅

return (
<Text textAlign="center">
Someone has initiated a command from {ipAddress}. If it was not you,
click reject and contact your administrator.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
click reject and contact your administrator.
click Reject and contact your administrator.

From what I see this is the capitalization we use in docs. Grepping the project for "\bclick " didn't really return any results from the web folder.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added

@jakule
Copy link
Copy Markdown
Contributor Author

jakule commented Mar 22, 2023

I think it's good enough for the 12.2 release. I'd consider two improvements in the future:

  • As I mentioned before, return the error immediately if the session is not valid. The user shouldn't have to wait for the deadline if the session was rejected already or if there's a typo in the UUID because the user copy-pasted just a part of the URL from the terminal.
  • Accepting a request and then opening its URL again shows the modal again, as if I didn't approve it already. Only after clicking Approve or Reject I get a relevant error message. It feels like this should be shown immediately instead.

@ravicious Thank you for the comments. I created an issue to track them. I just want to point out that this PR only introduces "REST" API endpoints and WebUI changes. The issues described above are more related to the underlying implementation which IMO should be fixed as a separate PR.

@jakule jakule added this pull request to the merge queue Mar 22, 2023
@jakule jakule merged commit f24f59b into master Mar 22, 2023
@jakule jakule deleted the jakule/headless-authn-tsh branch March 22, 2023 15:36
Joerger added a commit that referenced this pull request Mar 31, 2023
* Update UI

Update UI text

Update the code to add headless request get

Remove commented code

Added simple UI and endpoints

* Update UI
Implement reject SSO handler and UI

* Fix linter issues

* Fix more linter issues

* Fix UI tests

* Use url.JoinPath.

* center spinner on the page and animate it.

* Address code review comments

* Address code review comments

* Renamed React component

* Address PR comments

---------

Co-authored-by: joerger <bjoerger@goteleport.com>
Co-authored-by: Jeff Pihach <jeff.pihach@goteleport.com>
Joerger added a commit that referenced this pull request Apr 3, 2023
* RFD 105 - Headless Authentication (#21005)

* Draft UX section.

* Complete draft.

* Minor edits.

* Address comments, polish.

* Condense headless login request into a single HTTP endpoint.

* Update security section for limited certificate permissions.

* Address doyensec comments.

* Update RFD.

* Remove certificate limitation from RFD scope; Add RFD number; smaller
edits.

* Small fixes.

* * Update auth flow to use auth.AuthenticateSSHUser endpoint instead of CreateHeadlessAuthRequest and GenerateUserCerts endpoints

 * Remove CreateHeadlessAuthRequest rpc

 * Remove token and other unneeded fields from headless authentication

* * Add resource watcher section

* Don't insert backend data without authenticaion

* Remove view headless requests page

* Update diagram

* Use the client's public key to derive a request ID.

* Add HeadlessAuthentication protobuf type and Resource implementation. (#22350)

* Add headless auth preference logic. (#22148)

* Add Headless Authn backend service. (#22553)

* Headless Login: add headless authentication resource watcher (#22699)

* Add headless authentication resource watcher.

* Handle OpInit event and Watcher errors.

* Headless Login: proxy server changes (#22734)

* * Add proper context handling to auth.AuthenticateUser.

 * Move PublicKey field to AuthenticateUserRequest where it can be used
   for actual authentication.

 * Use a simple switch statement in /webapi/ssh/certs logic to switch
   between password, otp, and eventually headless login.

* Add Headless flow to /webapi/ssh/certs login enpdoint.

* Add 3 minute callback timeout.

* Headless Login: protobuf service (#22750)

* Add Headless Authn proto server.

* Add Headless Authn proto client.

* Resolve comments.

* Headless Login: tsh implementation (#22751)

* * Implement tsh --headless

 * Implement tsh headless approve

* Add better headless authn state handling.

* Add godoc for new tsh field.

* Headless login: Mlockall (#23159)

* Use Mlockall for Headless login.

* Skip memory lock on unsupported OSs.

Resolve comments

* Headless Login: auth server changes (#22726)

* Add Headless Authn service.

* Add/fix 3 minute headless login timeout.

* * Prevent repeated updates to headless authentication state

* Prevent user lock out from headless authentication failure

* Delete headless authentication on failed attempts

* Add auth_with_roles test.

* Extend timeout in test to reduce flakiness.

* Fix error typo.

* Add context timeouts, remove initial GetHeadlessAuthentication call.

* Resolve comments.

* Move http client to it's own file; Add ability to clone HTTP client for per-request configuration changes.

* Fix flaky test.

* Remove shared state from test.

* Update error handling and testing for auth_with_roles.

* Fix rebase misshap.

* Fix race condition in test.

* update e ref

* Fix ctx missing.

* Extend test timeout to prevent flakiness.

* Fix issue with roundtrip.ClientParams not being applied due to roundtripper wrapping.

---------

Co-authored-by: Tim Ross <tim.ross@goteleport.com>

* Extend context timeouts in TestHeadlessAuthenticationWatcher tests to reduce flakiness. (#23160)

* Fix flaky test due to context deadline. (#23260)

* Fix headless login with `second_factor: on | optional` (#23271)

* Fix headless login with second_factor:on|optional.

* Update ssh/certs endpoint to only configure necessary authentication fields; clarify comments; update test to cover headless authenticaiton preference.

* Update test to cover user locking logic.

* Change generic headless error. (#23331)

* Headless SSO web endpoint and UI (#22914)

* Update UI

Update UI text

Update the code to add headless request get

Remove commented code

Added simple UI and endpoints

* Update UI
Implement reject SSO handler and UI

* Fix linter issues

* Fix more linter issues

* Fix UI tests

* Use url.JoinPath.

* center spinner on the page and animate it.

* Address code review comments

* Address code review comments

* Renamed React component

* Address PR comments

---------

Co-authored-by: joerger <bjoerger@goteleport.com>
Co-authored-by: Jeff Pihach <jeff.pihach@goteleport.com>

* Fix flaky test `TestHeadlessAuthenticationWatcher` (#23417)

* Fix race condition in test by using a helper function instead of complex channel mechanisms.

* Avoid creating new methods solely for testing; resolve other comments.

* Reuse more code; resolve other comments.

* Fix race condition that could cause a new watcher to be marked as stale before the channel is consumed; Fix minor test issues.

* Remove race condition on headless authentication expires field when (#23578)

using memory storage.

* Headless Authn: documentation (#23272)

* Add docs.

* Update docs/pages/access-controls/guides/headless-login.mdx

Co-authored-by: Zac Bergquist <zac.bergquist@goteleport.com>

* Fix lint error.

* Ellaborate on how headless login differs from standard login.

* Resolve comments; Fix capitalization.

* Resolves comments.

* Add cli reference docs.

* Restructure guide; Remove scoped blocks; Update descriptions; resolve other comments.

* Make configuration options/alternatives collapsible; Fix typos.

* Fix file names, titles, and make new config details begin as closed.

* Fix hidden merge conflict.

* Add line breaks.

* Fix dead link.

---------

Co-authored-by: Zac Bergquist <zac.bergquist@goteleport.com>

---------

Co-authored-by: Tim Ross <tim.ross@goteleport.com>
Co-authored-by: Jakub Nyckowski <jakub.nyckowski@goteleport.com>
Co-authored-by: Jeff Pihach <jeff.pihach@goteleport.com>
Co-authored-by: Zac Bergquist <zac.bergquist@goteleport.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Headless SSO WebUI

5 participants