Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import FileSaver from 'file-saver';

import Button from 'components/buttons/Button';
import enrollSecretInterface from 'interfaces/enroll_secret';
Expand Down Expand Up @@ -35,6 +36,20 @@ class EnrollSecretRow extends Component {
return false;
}

onDownloadSecret = (evt) => {
evt.preventDefault();

const { secret } = this.props;

const filename = 'secret.txt';
const file = new global.window.File([secret], filename);

FileSaver.saveAs(file);

return false;
}


onToggleSecret = (evt) => {
evt.preventDefault();

Expand All @@ -47,7 +62,7 @@ class EnrollSecretRow extends Component {
renderLabel = () => {
const { name } = this.props;
const { showSecret, copyMessage } = this.state;
const { onCopySecret, onToggleSecret } = this;
const { onCopySecret, onDownloadSecret, onToggleSecret } = this;

return (
<span>
Expand All @@ -61,6 +76,16 @@ class EnrollSecretRow extends Component {
>
<Icon name="clipboard" />
</Button>
|
<a
href="#onDownloadSecret"
variant="unstyled"
className={`${baseClass}__secret-download-icon`}
onClick={onDownloadSecret}
>
Download
</a>
|
<a
href="#showSecret"
onClick={onToggleSecret}
Expand Down
143 changes: 114 additions & 29 deletions frontend/components/hosts/AddHostModal/AddHostModal.jsx
Original file line number Diff line number Diff line change
@@ -1,69 +1,154 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import FileSaver from 'file-saver';

import Kolide from 'kolide';
import Button from 'components/buttons/Button';
import configInterface from 'interfaces/config';
import enrollSecretInterface from 'interfaces/enroll_secret';
import EnrollSecretTable from 'components/config/EnrollSecretTable';
import Icon from 'components/icons/Icon';
import certificate from '../../../../assets/images/osquery-certificate.svg';

const baseClass = 'add-host-modal';


class AddHostModal extends Component {
static propTypes = {
onFetchCertificate: PropTypes.func,
onReturnToApp: PropTypes.func,
enrollSecret: enrollSecretInterface,
config: configInterface,
};

constructor(props) {
super(props);
this.state = { fetchCertificateError: undefined };
}

componentDidMount() {
Kolide.config.loadCertificate()
.then((certificate) => {
this.setState({ certificate });
})
.catch(() => {
this.setState({ fetchCertificateError: 'Failed to load certificate. Is Fleet App URL configured properly?' });
});
}

onFetchCertificate = (evt) => {
evt.preventDefault();

const { certificate } = this.state;

const filename = 'fleet.pem';
const file = new global.window.File([certificate], filename, { type: 'application/x-pem-file' });

FileSaver.saveAs(file);

return false;
}

render() {
const {
onFetchCertificate,
config,
onReturnToApp,
enrollSecret,
} = this.props;

const { fetchCertificateError } = this.state;

let tlsHostname = config.kolide_server_url;
try {
const serverUrl = new URL(config.kolide_server_url);
tlsHostname = serverUrl.hostname;
if (serverUrl.port) {
tlsHostname += `:${serverUrl.port}`;
}
} catch (e) {
if (!(e instanceof TypeError)) {
throw e;
}
}

const flagfileContent = `--enroll_secret_path=secret.txt
--tls_server_certs=fleet.pem
--tls_hostname=${tlsHostname}
--host_identifier=uuid
--enroll_tls_endpoint=/api/v1/osquery/enroll
--config_plugin=tls
--config_tls_endpoint=/api/v1/osquery/config
--config_refresh=10
--disable_distributed=false
--distributed_plugin=tls
--distributed_interval=10
--distributed_tls_max_attempts=3
--distributed_tls_read_endpoint=/api/v1/osquery/distributed/read
--distributed_tls_write_endpoint=/api/v1/osquery/distributed/write
--logger_plugin=tls
--logger_tls_endpoint=/api/v1/osquery/log
--logger_tls_period=10`;

const onDownloadFlagfile = (evt) => {
evt.preventDefault();

const filename = 'flagfile.txt';
const file = new global.window.File([flagfileContent], filename);

FileSaver.saveAs(file);

return false;
};

return (
<div className={baseClass}>
<p>
Follow the instructions below to add hosts to your Fleet Instance.
</p>

<div className={`${baseClass}__manual-install-content`}>
<div className={`${baseClass}__documentation-link`}>
<h4>
<a
href="https://github.com/kolide/fleet/blob/master/docs/infrastructure/adding-hosts-to-fleet.md"
target="_blank"
rel="noopener noreferrer"
>
Add Hosts Documentation <Icon name="external-link" />
</a>
</h4>
</div>
<ol className={`${baseClass}__install-steps`}>
<li>
<h4>
<a
href="https://github.com/kolide/fleet/blob/master/docs/infrastructure/adding-hosts-to-fleet.md"
target="_blank"
rel="noopener noreferrer"
>
Fleet / Osquery - Install Docs <Icon name="external-link" />
</a>
</h4>
</li>
<li>
<h4>Osquery Enroll Secret</h4>
<h4>1. Enroll Secret</h4>
<p>
Provide osquery with one of the following active enroll secrets:
Provide an active enroll secret to allow osquery to authenticate with the Fleet server:
</p>
<div className={`${baseClass}__secret-wrapper`}>
<EnrollSecretTable secrets={enrollSecret} />
</div>
</li>
<li>
<h4>Download Server Certificate (Optional)</h4>
<h4>2. Server Certificate</h4>
<p>
Provide the TLS certificate used by the Fleet server to enable secure connections from osquery:
</p>
<p>
{ fetchCertificateError
? <span className={`${baseClass}__error`}>{fetchCertificateError}</span>
: <a href="#downloadCertificate" onClick={this.onFetchCertificate}>Download Certificate</a>
}
</p>
</li>
<li>
<h4>3. Flagfile</h4>
<p>
If using the enroll secret and server certificate downloaded above, use the generated flagfile. In some configurations, modifications may need to be made:
</p>
<p>
If you use the native osquery TLS plugins, Osquery requires the
same TLS certificate that Fleet is using in order to
authenticate. You can fetch the certificate below:
<a href="#downloadFlagfile" onClick={onDownloadFlagfile}>Download Flagfile</a>
</p>
<p className={`${baseClass}__download-cert`}>
<Button variant="unstyled" onClick={onFetchCertificate}>
<img src={certificate} alt="" />
<span>Fetch Fleet Certificate</span>
</Button>
</li>
<li>
<h4>4. Run Osquery</h4>
<p>
Run osquery from the directory containing the above files (may require sudo or Run as Administrator privileges):
</p>
<pre>osqueryd --flagfile=flagfile.txt --verbose</pre>
</li>
</ol>
</div>
Expand Down
20 changes: 18 additions & 2 deletions frontend/components/hosts/AddHostModal/_styles.scss
Original file line number Diff line number Diff line change
Expand Up @@ -46,14 +46,14 @@
margin-left: 10px;
padding: 20px;
box-sizing: border-box;

h4 {
font-size: 16px;
font-weight: $bold;
line-height: 1.5;
letter-spacing: -0.5px;
color: rgba(32, 37, 50, 0.66);
margin: 40px 0 0;
margin: 10px 0 0;

.kolidecon {
margin-left: 5px;
Expand All @@ -75,6 +75,12 @@
}
}

&__documentation-link {
h4 {
margin: 0;
}
}

&__install-steps {
margin: 0;
padding: 0;
Expand Down Expand Up @@ -134,4 +140,14 @@
color: $link;
}
}

pre, code {
background-color: #f9f9f9;
}

&__error {
color: $alert;
}

}

24 changes: 7 additions & 17 deletions frontend/pages/hosts/ManageHostsPage/ManageHostsPage.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,13 @@ import React, { PureComponent } from 'react';
import PropTypes from 'prop-types';
import AceEditor from 'react-ace';
import { connect } from 'react-redux';
import FileSaver from 'file-saver';
import { push } from 'react-router-redux';
import { sortBy } from 'lodash';
import classNames from 'classnames';

import Kolide from 'kolide';
import AddHostModal from 'components/hosts/AddHostModal';
import Button from 'components/buttons/Button';
import configInterface from 'interfaces/config';
import HostContainer from 'components/hosts/HostContainer';
import HostPagination from 'components/hosts/HostPagination';
import HostSidePanel from 'components/side_panels/HostSidePanel';
Expand Down Expand Up @@ -45,6 +44,7 @@ const baseClass = 'manage-hosts';

export class ManageHostsPage extends PureComponent {
static propTypes = {
config: configInterface,
dispatch: PropTypes.func,
display: PropTypes.oneOf(['Grid', 'List']),
hosts: PropTypes.arrayOf(hostInterface),
Expand Down Expand Up @@ -155,18 +155,6 @@ export class ManageHostsPage extends PureComponent {
.catch(() => false);
}

onFetchCertificate = () => {
return Kolide.config.loadCertificate()
.then((certificate) => {
const filename = `${global.window.location.host}.pem`;
const file = new global.window.File([certificate], filename, { type: 'application/x-pem-file' });

FileSaver.saveAs(file);

return false;
});
}

onLabelClick = (selectedLabel) => {
return (evt) => {
evt.preventDefault();
Expand Down Expand Up @@ -304,9 +292,9 @@ export class ManageHostsPage extends PureComponent {
}

renderAddHostModal = () => {
const { onFetchCertificate, toggleAddHostModal } = this;
const { toggleAddHostModal } = this;
const { showAddHostModal } = this.state;
const { enrollSecret } = this.props;
const { enrollSecret, config } = this.props;

if (!showAddHostModal) {
return false;
Expand All @@ -319,9 +307,9 @@ export class ManageHostsPage extends PureComponent {
className={`${baseClass}__invite-modal`}
>
<AddHostModal
onFetchCertificate={onFetchCertificate}
onReturnToApp={toggleAddHostModal}
enrollSecret={enrollSecret}
config={config}
/>
</Modal>
);
Expand Down Expand Up @@ -668,6 +656,7 @@ const mapStateToProps = (state, { location, params }) => {
const { errors: labelErrors, loading: loadingLabels } = state.entities.labels;
const { loading: loadingHosts } = state.entities.hosts;
const enrollSecret = state.app.enrollSecret;
const config = state.app.config;

return {
selectedFilter,
Expand All @@ -684,6 +673,7 @@ const mapStateToProps = (state, { location, params }) => {
selectedLabel,
selectedOsqueryTable,
statusLabels,
config,
};
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import hostActions from 'redux/nodes/entities/hosts/actions';
import labelActions from 'redux/nodes/entities/labels/actions';
import ConnectedManageHostsPage, { ManageHostsPage } from 'pages/hosts/ManageHostsPage/ManageHostsPage';
import { connectedComponent, createAceSpy, reduxMockStore, stubbedOsqueryTable } from 'test/helpers';
import { hostStub } from 'test/stubs';
import { hostStub, configStub } from 'test/stubs';
import * as manageHostsPageActions from 'redux/nodes/components/ManageHostsPage/actions';

const allHostsLabel = { id: 1, display_text: 'All Hosts', slug: 'all-hosts', type: 'all', count: 22 };
Expand Down Expand Up @@ -49,6 +49,7 @@ const mockStore = reduxMockStore({

describe('ManageHostsPage - component', () => {
const props = {
config: configStub,
dispatch: noop,
hosts: [],
labels: [],
Expand Down
Loading