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. +
In order to install osquery on a client you will need the following information:
++ In order to install osquery on a client you + will need the following information: +
The following is your enroll secret: - {revealSecret ? 'Hide' : 'Reveal'} Secret + + {revealSecret ? 'Hide' : 'Reveal'} Secret +
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: +
+ The following is your API Token:
+
+ {revealSecret ? 'Hide' : 'Reveal'} Token
+
+ Last changed: {lastUpdatedAt}
+ Last changed: {lastUpdatedAt}
+
diff --git a/frontend/pages/UserSettingsPage/UserSettingsPage.jsx b/frontend/pages/UserSettingsPage/UserSettingsPage.jsx
index e4fa09d192..f5adc8eee2 100644
--- a/frontend/pages/UserSettingsPage/UserSettingsPage.jsx
+++ b/frontend/pages/UserSettingsPage/UserSettingsPage.jsx
@@ -2,6 +2,12 @@ import React, { Component, PropTypes } from 'react';
import { connect } from 'react-redux';
import { goBack } from 'react-router-redux';
import moment from 'moment';
+import { authToken } from 'utilities/local';
+import {
+ copyText,
+ COPY_TEXT_SUCCESS,
+ COPY_TEXT_ERROR,
+} from 'utilities/copy_text';
import Avatar from 'components/Avatar';
import Button from 'components/buttons/Button';
@@ -9,6 +15,7 @@ import ChangeEmailForm from 'components/forms/ChangeEmailForm';
import ChangePasswordForm from 'components/forms/ChangePasswordForm';
import deepDifference from 'utilities/deep_difference';
import Icon from 'components/icons/Icon';
+import InputField from 'components/forms/fields/InputField';
import { logoutUser, updateUser } from 'redux/nodes/auth/actions';
import Modal from 'components/modals/Modal';
import { renderFlash } from 'redux/nodes/notifications/actions';
@@ -33,7 +40,7 @@ export class UserSettingsPage extends Component {
}),
};
- constructor (props) {
+ constructor(props) {
super(props);
this.state = {
@@ -52,7 +59,7 @@ export class UserSettingsPage extends Component {
dispatch(goBack());
return false;
- }
+ };
onLogout = (evt) => {
evt.preventDefault();
@@ -62,7 +69,7 @@ export class UserSettingsPage extends Component {
dispatch(logoutUser());
return false;
- }
+ };
onShowModal = (evt) => {
evt.preventDefault();
@@ -70,7 +77,15 @@ export class UserSettingsPage extends Component {
this.setState({ showPasswordModal: true });
return false;
- }
+ };
+
+ onShowApiTokenModal = (evt) => {
+ evt.preventDefault();
+
+ this.setState({ showApiTokenModal: true });
+
+ return false;
+ };
onToggleEmailModal = (updatedUser = {}) => {
const { showEmailModal } = this.state;
@@ -81,7 +96,7 @@ export class UserSettingsPage extends Component {
});
return false;
- }
+ };
onTogglePasswordModal = (evt) => {
evt.preventDefault();
@@ -91,7 +106,41 @@ export class UserSettingsPage extends Component {
this.setState({ showPasswordModal: !showPasswordModal });
return false;
- }
+ };
+
+ onToggleApiTokenModal = (evt) => {
+ evt.preventDefault();
+
+ const { showApiTokenModal } = this.state;
+
+ this.setState({ showApiTokenModal: !showApiTokenModal });
+
+ return false;
+ };
+
+ onToggleSecret = (evt) => {
+ evt.preventDefault();
+
+ const { revealSecret } = this.state;
+
+ this.setState({ revealSecret: !revealSecret });
+ return false;
+ };
+
+ onCopySecret = (elementClass) => {
+ return (evt) => {
+ evt.preventDefault();
+
+ const { dispatch } = this.props;
+
+ if (copyText(elementClass)) {
+ dispatch(renderFlash('success', COPY_TEXT_SUCCESS));
+ } else {
+ this.setState({ revealSecret: true });
+ dispatch(renderFlash('error', COPY_TEXT_ERROR));
+ }
+ };
+ };
handleSubmit = (formData) => {
const { dispatch, user } = this.props;
@@ -112,19 +161,18 @@ export class UserSettingsPage extends Component {
return true;
})
.catch(() => false);
- }
+ };
handleSubmitPasswordForm = (formData) => {
const { dispatch, user } = this.props;
- return dispatch(userActions.changePassword(user, formData))
- .then(() => {
- dispatch(renderFlash('success', 'Password changed successfully'));
- this.setState({ showPasswordModal: false });
+ return dispatch(userActions.changePassword(user, formData)).then(() => {
+ dispatch(renderFlash('success', 'Password changed successfully'));
+ this.setState({ showPasswordModal: false });
- return false;
- });
- }
+ return false;
+ });
+ };
renderEmailModal = () => {
const { errors } = this.props;
@@ -132,10 +180,9 @@ export class UserSettingsPage extends Component {
const { handleSubmit, onToggleEmailModal } = this;
const emailSubmit = (formData) => {
- handleSubmit(formData)
- .then((r) => {
- return r ? onToggleEmailModal() : false;
- });
+ handleSubmit(formData).then((r) => {
+ return r ? onToggleEmailModal() : false;
+ });
};
if (!showEmailModal) {
@@ -155,7 +202,7 @@ export class UserSettingsPage extends Component {
/>
);
- }
+ };
renderPasswordModal = () => {
const { userErrors } = this.props;
@@ -167,10 +214,7 @@ export class UserSettingsPage extends Component {
}
return (
-