From 6bdddfacf0c68314ec242a22205ad1982802a583 Mon Sep 17 00:00:00 2001 From: Zachary Wasserman Date: Tue, 17 Jul 2018 11:27:30 -0700 Subject: [PATCH] Expose API Token in UI (#1868) Useful for SAML login users who cannot log in with `fleetctl login`. Instead they can pull their session token from the UI and configure the fleetctl client to use it. Closes #1865 --- docs/cli/setup-guide.md | 26 ++- .../hosts/AddHostModal/AddHostModal.jsx | 68 +++++-- .../UserSettingsPage/UserSettingsPage.jsx | 171 +++++++++++++++--- frontend/pages/UserSettingsPage/_styles.scss | 45 +++++ .../helpers.js => utilities/copy_text.js} | 5 +- 5 files changed, 266 insertions(+), 49 deletions(-) rename frontend/{components/hosts/AddHostModal/helpers.js => utilities/copy_text.js} (79%) diff --git a/docs/cli/setup-guide.md b/docs/cli/setup-guide.md index 77936e19d6..27c36a1772 100644 --- a/docs/cli/setup-guide.md +++ b/docs/cli/setup-guide.md @@ -1,6 +1,8 @@ # Setting Up Fleet via the CLI -In this document, I'm going to walk through how to setup and configure Kolide Fleet using just the CLI (which in-turn uses the Go API client). This document will hopefully illustrate: +This document walks through setting up and configuring Fleet via the CLI. If you already have a running fleet instance, skip ahead to [Logging In To An Existing Fleet Instance](#logging-in-to-an-existing-fleet-instance) to configure the `fleetctl` CLI. + +This guide illustrates: - A minimal CLI workflow for managing an osquery fleet - The set of API interactions that are required if you want to perform remote, automated management of a Fleet instance @@ -147,9 +149,9 @@ fleetctl apply -f ./options.yaml Now run a live query again. You should notice results coming back more quickly. -## Logging In To An Existing Fleet Instance +# Logging In To An Existing Fleet Instance -If you have an existing Fleet instance (version 2.0.0 or above), then simply run `fleet login` (after configuring your local CLI context): +If you have an existing Fleet instance (version 2.0.0 or above), then simply run `fleetctl login` (after configuring your local CLI context): ``` $ fleetctl config set --address https://fleet.corp.example.com @@ -163,3 +165,21 @@ Password: ``` Once your local context is configured, you can use the above `fleetctl` normally. See `fleetctl --help` for more information. + +## Logging In with SAML (SSO) Authentication + +Users that authenticate to Fleet via SSO should retrieve their API token from the UI and set it manually in their `fleetctl` configuration (instead of logging in via `fleetctl login`). + +1. Go to the "Account Settings" page in Fleet (https://fleet.corp.example.com/settings). Click the "Get API Token" button to bring up a modal with the API token. + +2. Set the API token in the `~/.fleet/config` file. The file should look like the following: + +``` +contexts: + default: + address: https://fleet.corp.example.com + email: example@example.com + token: your_token_here +``` + +Note the token can also be set with `fleetctl config set --token`, but this may leak the token into a user's shell history. diff --git a/frontend/components/hosts/AddHostModal/AddHostModal.jsx b/frontend/components/hosts/AddHostModal/AddHostModal.jsx index 6eb3360961..c4c534fd4c 100644 --- a/frontend/components/hosts/AddHostModal/AddHostModal.jsx +++ b/frontend/components/hosts/AddHostModal/AddHostModal.jsx @@ -4,7 +4,11 @@ import Button from 'components/buttons/Button'; import Icon from 'components/icons/Icon'; import InputField from 'components/forms/fields/InputField'; import { renderFlash } from 'redux/nodes/notifications/actions'; -import { copyText } from './helpers'; +import { + copyText, + COPY_TEXT_SUCCESS, + COPY_TEXT_ERROR, +} from 'utilities/copy_text'; import certificate from '../../../../assets/images/osquery-certificate.svg'; const baseClass = 'add-host-modal'; @@ -17,7 +21,7 @@ class AddHostModal extends Component { osqueryEnrollSecret: PropTypes.string, }; - constructor (props) { + constructor(props) { super(props); this.state = { revealSecret: false }; @@ -30,13 +34,13 @@ class AddHostModal extends Component { const { dispatch } = this.props; if (copyText(elementClass)) { - dispatch(renderFlash('success', 'Text copied to clipboard')); + dispatch(renderFlash('success', COPY_TEXT_SUCCESS)); } else { this.setState({ revealSecret: true }); - dispatch(renderFlash('error', 'Text not copied. Use CMD + C to copy text')); + dispatch(renderFlash('error', COPY_TEXT_ERROR)); } }; - } + }; toggleSecret = (evt) => { const { revealSecret } = this.state; @@ -44,34 +48,59 @@ class AddHostModal extends Component { this.setState({ revealSecret: !revealSecret }); return false; - } + }; - render () { + render() { const { onCopySecret, toggleSecret } = this; const { revealSecret } = this.state; - const { onFetchCertificate, onReturnToApp, osqueryEnrollSecret } = this.props; + const { + onFetchCertificate, + onReturnToApp, + osqueryEnrollSecret, + } = this.props; return (
-

Follow the instructions below to add hosts to your Kolide Instance.

+

+ Follow the instructions below to add hosts to your Kolide Instance. +

Manual Install

-

Fully Customize Your Osquery Installation

+

+ Fully Customize Your Osquery Installation +

  1. -

    Kolide / Osquery - Install Docs

    -

    In order to install osquery on a client you will need the following information:

    +

    + + Kolide / Osquery - Install Docs + +

    +

    + In order to install osquery on a client you + will need the following information: +

  2. Retrieve Osquery Enroll Secret

    The following is your enroll secret: - {revealSecret ? 'Hide' : 'Reveal'} Secret + + {revealSecret ? 'Hide' : 'Reveal'} Secret +

    -
    -
  3. Download Server Certificate (Optional)

    -

    If you use the native osquery TLS plugins, Osquery requires the same TLS certificate that Kolide is using in order to authenticate. You can fetch the certificate below:

    +

    + If you use the native osquery TLS plugins, Osquery requires the + same TLS certificate that Kolide is using in order to + authenticate. You can fetch the certificate below: +

    +

+
+ +
+ + ); + }; + + render() { const { handleSubmit, onCancel, onLogout, onShowModal, + onShowApiTokenModal, renderEmailModal, renderPasswordModal, + renderApiTokenModal, } = this; const { errors, user } = this.props; const { pendingEmail } = this.state; @@ -217,9 +308,19 @@ export class UserSettingsPage extends Component {
- Change Photo at Gravatar + + Change Photo at Gravatar +
+ +
Role - {roleText} @@ -228,16 +329,28 @@ export class UserSettingsPage extends Component { Password
- -

Last changed: {lastUpdatedAt}

-
{renderEmailModal()} {renderPasswordModal()} + {renderApiTokenModal()} ); } diff --git a/frontend/pages/UserSettingsPage/_styles.scss b/frontend/pages/UserSettingsPage/_styles.scss index 136655c1bf..8d753132b4 100644 --- a/frontend/pages/UserSettingsPage/_styles.scss +++ b/frontend/pages/UserSettingsPage/_styles.scss @@ -69,4 +69,49 @@ border-bottom: 1px solid $accent-medium; margin: 0 0 35px; } + + &__reveal-secret { + float: right; + text-decoration: none; + } + + &__secret-label { + font-size: 15px; + font-weight: $normal; + line-height: 1.6; + letter-spacing: normal; + color: rgba(32, 37, 50, 0.66); + margin: 0; + } + + &__secret-wrapper { + position: relative; + } + + &__secret-copy-icon { + position: absolute; + top: 8px; + right: 10px; + font-size: 18px; + color: $link; + + &:active { + top: 8px; + } + } + + &__secret-input { + input { + border-radius: 2px; + background-color: $bg-medium; + border-color: $bg-medium; + color: $link; + padding-right: 36px; + font-family: 'SourceCodePro', $monospace; + + &[type="password"] { + letter-spacing: 3px; + } + } + } } diff --git a/frontend/components/hosts/AddHostModal/helpers.js b/frontend/utilities/copy_text.js similarity index 79% rename from frontend/components/hosts/AddHostModal/helpers.js rename to frontend/utilities/copy_text.js index 67fc1cbea9..10b1fa03eb 100644 --- a/frontend/components/hosts/AddHostModal/helpers.js +++ b/frontend/utilities/copy_text.js @@ -27,4 +27,7 @@ export const copyText = (elementSelector) => { return true; }; -export default { copyText }; +export const COPY_TEXT_SUCCESS = 'Text copied to clipboard'; +export const COPY_TEXT_ERROR = 'Text not copied. Please copy manually.'; + +export default copyText;