diff --git a/frontend/components/forms/admin/AppConfigForm/AppConfigForm.tests.jsx b/frontend/components/forms/admin/AppConfigForm/AppConfigForm.tests.jsx
index 2e5bce61cd..2e9f123574 100644
--- a/frontend/components/forms/admin/AppConfigForm/AppConfigForm.tests.jsx
+++ b/frontend/components/forms/admin/AppConfigForm/AppConfigForm.tests.jsx
@@ -11,6 +11,11 @@ describe('AppConfigForm - form', () => {
formData: { org_name: 'Kolide' },
handleSubmit: noop,
smtpConfigured: false,
+ enrollSecret: [
+ { name: 'foo', secret: 'foo_secret', active: true },
+ { name: 'bar', secret: 'bar_secret', active: true },
+ { name: 'inactive', secret: 'inactive', active: false },
+ ],
};
const form = mount(
);
@@ -74,6 +79,13 @@ describe('AppConfigForm - form', () => {
});
});
+ describe('Enroll Secret', () => {
+ it('renders enroll secrets table', () => {
+ expect(form.find('EnrollSecretTable').length).toEqual(1);
+ });
+ });
+
+
it('does not render advanced options by default', () => {
expect(form.find({ name: 'domain' }).length).toEqual(0);
expect(form.find('Slider').length).toEqual(0);
diff --git a/frontend/components/hosts/AddHostModal/AddHostModal.jsx b/frontend/components/hosts/AddHostModal/AddHostModal.jsx
index 36c649750d..9c541c1a9c 100644
--- a/frontend/components/hosts/AddHostModal/AddHostModal.jsx
+++ b/frontend/components/hosts/AddHostModal/AddHostModal.jsx
@@ -2,62 +2,25 @@ import React, { Component } from 'react';
import PropTypes from 'prop-types';
import Button from 'components/buttons/Button';
+import enrollSecretInterface from 'interfaces/enroll_secret';
+import EnrollSecretTable from 'components/config/EnrollSecretTable';
import Icon from 'components/icons/Icon';
-import InputField from 'components/forms/fields/InputField';
-import { renderFlash } from 'redux/nodes/notifications/actions';
-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';
class AddHostModal extends Component {
static propTypes = {
- dispatch: PropTypes.func,
onFetchCertificate: PropTypes.func,
onReturnToApp: PropTypes.func,
- osqueryEnrollSecret: PropTypes.string,
- };
-
- constructor(props) {
- super(props);
-
- this.state = { revealSecret: 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));
- }
- };
- };
-
- toggleSecret = (evt) => {
- const { revealSecret } = this.state;
- evt.preventDefault();
-
- this.setState({ revealSecret: !revealSecret });
- return false;
+ enrollSecret: enrollSecretInterface,
};
render() {
- const { onCopySecret, toggleSecret } = this;
- const { revealSecret } = this.state;
const {
onFetchCertificate,
onReturnToApp,
- osqueryEnrollSecret,
+ enrollSecret,
} = this.props;
return (
@@ -66,14 +29,6 @@ class AddHostModal extends Component {
Follow the instructions below to add hosts to your Fleet Instance.
-
-
-
Manual Install
-
- Fully Customize Your Osquery Installation
-
-
-
@@ -86,38 +41,14 @@ class AddHostModal extends Component {
Fleet / Osquery - Install Docs
-
- In order to install osquery on a client you
- will need the following information:
-
- Retrieve Osquery Enroll Secret
+ Osquery Enroll Secret
- The following is your enroll secret:
-
- {revealSecret ? 'Hide' : 'Reveal'} Secret
-
+ Provide osquery with one of the following active enroll secrets:
-
-
-
-
+
diff --git a/frontend/components/hosts/AddHostModal/AddHostModal.tests.jsx b/frontend/components/hosts/AddHostModal/AddHostModal.tests.jsx
deleted file mode 100644
index 0e20b57fe4..0000000000
--- a/frontend/components/hosts/AddHostModal/AddHostModal.tests.jsx
+++ /dev/null
@@ -1,32 +0,0 @@
-import React from 'react';
-import expect from 'expect';
-import { mount } from 'enzyme';
-import { noop } from 'lodash';
-
-import AddHostModal from './AddHostModal';
-
-describe('AddHostModal - component', () => {
- it('clicking Reveal Secret should change input type', () => {
- const component = mount( );
- const revealSecretLink = component.find('.add-host-modal__reveal-secret');
- let secretInput = component.find('.add-host-modal__secret-input').find('input');
-
- expect(component.state().revealSecret).toEqual(false);
- expect(secretInput.prop('type')).toEqual('password');
- expect(revealSecretLink.text()).toEqual('Reveal Secret');
-
- revealSecretLink.simulate('click');
-
- secretInput = component.find('.add-host-modal__secret-input').find('input');
- expect(component.state().revealSecret).toEqual(true);
- expect(secretInput.prop('type')).toEqual('text');
- expect(revealSecretLink.text()).toEqual('Hide Secret');
-
- revealSecretLink.simulate('click');
-
- secretInput = component.find('.add-host-modal__secret-input').find('input');
- expect(component.state().revealSecret).toEqual(false);
- expect(secretInput.prop('type')).toEqual('password');
- expect(revealSecretLink.text()).toEqual('Reveal Secret');
- });
-});
diff --git a/frontend/interfaces/enroll_secret.js b/frontend/interfaces/enroll_secret.js
new file mode 100644
index 0000000000..2694851f34
--- /dev/null
+++ b/frontend/interfaces/enroll_secret.js
@@ -0,0 +1,10 @@
+import PropTypes from 'prop-types';
+
+export default PropTypes.arrayOf(
+ PropTypes.shape({
+ name: PropTypes.string,
+ secret: PropTypes.string,
+ active: PropTypes.bool,
+ created_at: PropTypes.string,
+ }),
+);
diff --git a/frontend/kolide/entities/config.js b/frontend/kolide/entities/config.js
index 012e9c987d..ad645ad563 100644
--- a/frontend/kolide/entities/config.js
+++ b/frontend/kolide/entities/config.js
@@ -16,6 +16,11 @@ export default (client) => {
return client.authenticatedGet(endpoint)
.then(response => global.window.atob(response.certificate_chain));
},
+ loadEnrollSecret: () => {
+ const endpoint = client._endpoint('/v1/kolide/spec/enroll_secret');
+
+ return client.authenticatedGet(endpoint);
+ },
update: (formData) => {
const { CONFIG } = endpoints;
const configData = helpers.formatConfigDataForServer(formData);
diff --git a/frontend/pages/admin/AppSettingsPage/AppSettingsPage.jsx b/frontend/pages/admin/AppSettingsPage/AppSettingsPage.jsx
index 03478f85f5..e923652f2f 100644
--- a/frontend/pages/admin/AppSettingsPage/AppSettingsPage.jsx
+++ b/frontend/pages/admin/AppSettingsPage/AppSettingsPage.jsx
@@ -5,6 +5,7 @@ import { size } from 'lodash';
import AppConfigForm from 'components/forms/admin/AppConfigForm';
import configInterface from 'interfaces/config';
+import enrollSecretInterface from 'interfaces/enroll_secret';
import deepDifference from 'utilities/deep_difference';
import { renderFlash } from 'redux/nodes/notifications/actions';
import WarningBanner from 'components/WarningBanner';
@@ -17,6 +18,7 @@ class AppSettingsPage extends Component {
appConfig: configInterface,
dispatch: PropTypes.func.isRequired,
error: PropTypes.object, // eslint-disable-line react/forbid-prop-types
+ enrollSecret: enrollSecretInterface,
};
constructor (props) {
@@ -53,7 +55,7 @@ class AppSettingsPage extends Component {
}
render () {
- const { appConfig, error } = this.props;
+ const { appConfig, error, enrollSecret } = this.props;
const { onDismissSmtpWarning, onFormSubmit } = this;
const { showSmtpWarning } = this.state;
const { configured: smtpConfigured } = appConfig;
@@ -78,6 +80,7 @@ class AppSettingsPage extends Component {
handleSubmit={onFormSubmit}
serverErrors={error}
smtpConfigured={smtpConfigured}
+ enrollSecret={enrollSecret}
/>
);
@@ -85,9 +88,9 @@ class AppSettingsPage extends Component {
}
const mapStateToProps = ({ app }) => {
- const { config: appConfig, error } = app;
+ const { config: appConfig, error, enrollSecret } = app;
- return { appConfig, error };
+ return { appConfig, error, enrollSecret };
};
export default connect(mapStateToProps)(AppSettingsPage);
diff --git a/frontend/pages/admin/AppSettingsPage/AppSettingsPage.tests.jsx b/frontend/pages/admin/AppSettingsPage/AppSettingsPage.tests.jsx
index 1ed8d58991..03227a4aa0 100644
--- a/frontend/pages/admin/AppSettingsPage/AppSettingsPage.tests.jsx
+++ b/frontend/pages/admin/AppSettingsPage/AppSettingsPage.tests.jsx
@@ -7,10 +7,22 @@ import testHelpers from 'test/helpers';
const { connectedComponent, reduxMockStore } = testHelpers;
const baseStore = {
- app: { config: flatConfigStub },
+ app: { config: flatConfigStub, enrollSecret: [] },
+};
+const storeWithoutSMTPConfig = {
+ ...baseStore,
+ app: {
+ config: { ...flatConfigStub, configured: false },
+ enrollSecret: [],
+ },
+};
+const storeWithSMTPConfig = {
+ ...baseStore,
+ app: {
+ config: { ...flatConfigStub, configured: true },
+ enrollSecret: [],
+ },
};
-const storeWithoutSMTPConfig = { ...baseStore, app: { config: { ...flatConfigStub, configured: false } } };
-const storeWithSMTPConfig = { ...baseStore, app: { config: { ...flatConfigStub, configured: true } } };
describe('AppSettingsPage - component', () => {
afterEach(restoreSpies);
diff --git a/frontend/pages/hosts/ManageHostsPage/ManageHostsPage.jsx b/frontend/pages/hosts/ManageHostsPage/ManageHostsPage.jsx
index c6e59eee4d..99d027c666 100644
--- a/frontend/pages/hosts/ManageHostsPage/ManageHostsPage.jsx
+++ b/frontend/pages/hosts/ManageHostsPage/ManageHostsPage.jsx
@@ -22,6 +22,7 @@ import labelInterface from 'interfaces/label';
import hostInterface from 'interfaces/host';
import osqueryTableInterface from 'interfaces/osquery_table';
import statusLabelsInterface from 'interfaces/status_labels';
+import enrollSecretInterface from 'interfaces/enroll_secret';
import { selectOsqueryTable } from 'redux/nodes/components/QueryPages/actions';
import { getStatusLabelCounts, setDisplay, silentGetStatusLabelCounts } from 'redux/nodes/components/ManageHostsPage/actions';
import hostActions from 'redux/nodes/entities/hosts/actions';
@@ -51,7 +52,7 @@ export class ManageHostsPage extends PureComponent {
labels: PropTypes.arrayOf(labelInterface),
loadingHosts: PropTypes.bool.isRequired,
loadingLabels: PropTypes.bool.isRequired,
- osqueryEnrollSecret: PropTypes.string,
+ enrollSecret: enrollSecretInterface,
selectedLabel: labelInterface,
selectedOsqueryTable: osqueryTableInterface,
statusLabels: statusLabelsInterface,
@@ -356,7 +357,7 @@ export class ManageHostsPage extends PureComponent {
renderAddHostModal = () => {
const { onFetchCertificate, toggleAddHostModal } = this;
const { showAddHostModal } = this.state;
- const { dispatch, osqueryEnrollSecret } = this.props;
+ const { enrollSecret } = this.props;
if (!showAddHostModal) {
return false;
@@ -369,10 +370,9 @@ export class ManageHostsPage extends PureComponent {
className={`${baseClass}__invite-modal`}
>
);
@@ -676,7 +676,7 @@ const mapStateToProps = (state, { location, params }) => {
const { selectedOsqueryTable } = state.components.QueryPages;
const { errors: labelErrors, loading: loadingLabels } = state.entities.labels;
const { loading: loadingHosts } = state.entities.hosts;
- const { osquery_enroll_secret: osqueryEnrollSecret } = state.app.config;
+ const enrollSecret = state.app.enrollSecret;
return {
display,
@@ -686,7 +686,7 @@ const mapStateToProps = (state, { location, params }) => {
labels,
loadingHosts,
loadingLabels,
- osqueryEnrollSecret,
+ enrollSecret,
selectedLabel,
selectedOsqueryTable,
statusLabels,
diff --git a/frontend/pages/hosts/ManageHostsPage/ManageHostsPage.tests.jsx b/frontend/pages/hosts/ManageHostsPage/ManageHostsPage.tests.jsx
index fcb2dad3ec..22612ea8e0 100644
--- a/frontend/pages/hosts/ManageHostsPage/ManageHostsPage.tests.jsx
+++ b/frontend/pages/hosts/ManageHostsPage/ManageHostsPage.tests.jsx
@@ -56,6 +56,7 @@ describe('ManageHostsPage - component', () => {
loadingLabels: false,
selectedOsqueryTable: stubbedOsqueryTable,
statusLabels: {},
+ enrollSecret: [],
};
beforeEach(() => {
diff --git a/frontend/redux/nodes/app/actions.js b/frontend/redux/nodes/app/actions.js
index 043a09699a..f798315cd4 100644
--- a/frontend/redux/nodes/app/actions.js
+++ b/frontend/redux/nodes/app/actions.js
@@ -6,6 +6,9 @@ import { frontendFormattedConfig } from 'redux/nodes/app/helpers';
export const CONFIG_FAILURE = 'CONFIG_FAILURE';
export const CONFIG_START = 'CONFIG_START';
export const CONFIG_SUCCESS = 'CONFIG_SUCCESS';
+export const ENROLL_SECRET_FAILURE = 'ENROLL_SECRET_FAILURE';
+export const ENROLL_SECRET_START = 'ENROLL_SECRET_START';
+export const ENROLL_SECRET_SUCCESS = 'ENROLL_SECRET_SUCCESS';
export const SHOW_BACKGROUND_IMAGE = 'SHOW_BACKGROUND_IMAGE';
export const HIDE_BACKGROUND_IMAGE = 'HIDE_BACKGROUND_IMAGE';
export const TOGGLE_SMALL_NAV = 'TOGGLE_SMALL_NAV';
@@ -26,6 +29,13 @@ export const loadConfig = { type: CONFIG_START };
export const configSuccess = (data) => {
return { type: CONFIG_SUCCESS, payload: { data } };
};
+export const enrollSecretFailure = (error) => {
+ return { type: ENROLL_SECRET_FAILURE, payload: { error } };
+};
+export const loadEnrollSecret = { type: ENROLL_SECRET_START };
+export const enrollSecretSuccess = (data) => {
+ return { type: ENROLL_SECRET_SUCCESS, payload: { data } };
+};
export const getConfig = () => {
return (dispatch) => {
dispatch(loadConfig);
@@ -67,3 +77,22 @@ export const updateConfig = (configData) => {
});
};
};
+export const getEnrollSecret = () => {
+ return (dispatch) => {
+ dispatch(loadEnrollSecret);
+
+ return Kolide.config.loadEnrollSecret()
+ .then((secret) => {
+ dispatch(enrollSecretSuccess(secret.specs.secrets));
+
+ return secret;
+ })
+ .catch((error) => {
+ const formattedErrors = formatApiErrors(error);
+
+ dispatch(enrollSecretFailure(formattedErrors));
+
+ throw formattedErrors;
+ });
+ };
+};
diff --git a/frontend/redux/nodes/app/actions.tests.js b/frontend/redux/nodes/app/actions.tests.js
index 8f2056dc9a..3a0d2c1216 100644
--- a/frontend/redux/nodes/app/actions.tests.js
+++ b/frontend/redux/nodes/app/actions.tests.js
@@ -1,6 +1,14 @@
import expect from 'expect';
-import { CONFIG_START, CONFIG_SUCCESS, getConfig, updateConfig } from 'redux/nodes/app/actions';
+import {
+ CONFIG_START,
+ CONFIG_SUCCESS,
+ ENROLL_SECRET_START,
+ ENROLL_SECRET_SUCCESS,
+ getConfig,
+ updateConfig,
+ getEnrollSecret,
+} from 'redux/nodes/app/actions';
import { configStub } from 'test/stubs';
import { frontendFormattedConfig } from 'redux/nodes/app/helpers';
import Kolide from 'kolide';
@@ -78,4 +86,38 @@ describe('App - actions', () => {
.catch(done);
});
});
+
+ describe('getEnrollSecret action', () => {
+ const store = reduxMockStore({});
+
+ it('calls the api enrollSecret endpoint', (done) => {
+ const bearerToken = 'abc123';
+ const request = configMocks.loadAll.valid(bearerToken);
+
+ Kolide.setBearerToken(bearerToken);
+ store.dispatch(getEnrollSecret())
+ .then(() => {
+ expect(request.isDone()).toEqual(true);
+ done();
+ })
+ .catch(done);
+ });
+
+ it('dispatches ENROLLSECRET_START & ENROLLSECRET_SUCCESS actions', (done) => {
+ const bearerToken = 'abc123';
+ configMocks.loadAll.valid(bearerToken);
+
+ Kolide.setBearerToken(bearerToken);
+ store.dispatch(getEnrollSecret())
+ .then(() => {
+ const actions = store.getActions()
+ .map((action) => { return action.type; });
+
+ expect(actions).toInclude(ENROLL_SECRET_START);
+ expect(actions).toInclude(ENROLL_SECRET_SUCCESS);
+ done();
+ })
+ .catch(done);
+ });
+ });
});
diff --git a/frontend/redux/nodes/app/reducer.js b/frontend/redux/nodes/app/reducer.js
index 293509d9a8..b8cc9d43ac 100644
--- a/frontend/redux/nodes/app/reducer.js
+++ b/frontend/redux/nodes/app/reducer.js
@@ -2,6 +2,9 @@ import {
CONFIG_FAILURE,
CONFIG_START,
CONFIG_SUCCESS,
+ ENROLL_SECRET_FAILURE,
+ ENROLL_SECRET_START,
+ ENROLL_SECRET_SUCCESS,
HIDE_BACKGROUND_IMAGE,
SHOW_BACKGROUND_IMAGE,
TOGGLE_SMALL_NAV,
@@ -9,6 +12,7 @@ import {
export const initialState = {
config: {},
+ enrollSecret: [],
error: {},
isSmallNav: false,
loading: false,
@@ -35,6 +39,24 @@ const reducer = (state = initialState, { type, payload }) => {
error: payload.error,
loading: false,
};
+ case ENROLL_SECRET_START:
+ return {
+ ...state,
+ loading: true,
+ };
+ case ENROLL_SECRET_SUCCESS:
+ return {
+ ...state,
+ enrollSecret: payload.data,
+ error: {},
+ loading: false,
+ };
+ case ENROLL_SECRET_FAILURE:
+ return {
+ ...state,
+ error: payload.error,
+ loading: false,
+ };
case HIDE_BACKGROUND_IMAGE:
return {
...state,
diff --git a/frontend/redux/nodes/app/reducer.tests.js b/frontend/redux/nodes/app/reducer.tests.js
index d6016c4aa4..64b0808a96 100644
--- a/frontend/redux/nodes/app/reducer.tests.js
+++ b/frontend/redux/nodes/app/reducer.tests.js
@@ -2,12 +2,15 @@ import expect from 'expect';
import reducer, { initialState } from './reducer';
import {
+ loadConfig,
configFailure,
configSuccess,
+ loadEnrollSecret,
+ enrollSecretFailure,
+ enrollSecretSuccess,
hideBackgroundImage,
showBackgroundImage,
toggleSmallNav,
- loadConfig,
} from './actions';
describe('App - reducer', () => {
@@ -76,6 +79,7 @@ describe('App - reducer', () => {
};
expect(reducer(loadingConfigState, configSuccess(config))).toEqual({
config,
+ enrollSecret: [],
error: {},
loading: false,
isSmallNav: false,
@@ -92,6 +96,52 @@ describe('App - reducer', () => {
loading: true,
};
expect(reducer(loadingConfigState, configFailure(error))).toEqual({
+ config: {},
+ enrollSecret: [],
+ error,
+ loading: false,
+ isSmallNav: false,
+ showBackgroundImage: false,
+ });
+ });
+ });
+
+ context('loadEnrollSecret action', () => {
+ it('sets the state to loading', () => {
+ expect(reducer(initialState, loadEnrollSecret)).toEqual({
+ ...initialState,
+ loading: true,
+ });
+ });
+ });
+
+ context('enrollSecretSuccess action', () => {
+ it('sets the enrollSecret in state', () => {
+ const enrollSecret = [{ name: 'Kolide' }];
+ const loadingEnrollSecretState = {
+ ...initialState,
+ loading: true,
+ };
+ expect(reducer(loadingEnrollSecretState, enrollSecretSuccess(enrollSecret))).toEqual({
+ enrollSecret,
+ config: {},
+ error: {},
+ loading: false,
+ isSmallNav: false,
+ showBackgroundImage: false,
+ });
+ });
+ });
+
+ context('enrollSecretFailure action', () => {
+ it('sets the error in state', () => {
+ const error = 'Unable to get enrollSecret';
+ const loadingEnrollSecretState = {
+ ...initialState,
+ loading: true,
+ };
+ expect(reducer(loadingEnrollSecretState, enrollSecretFailure(error))).toEqual({
+ enrollSecret: [],
config: {},
error,
loading: false,
diff --git a/frontend/utilities/copy_text.js b/frontend/utilities/copy_text.js
index 10b1fa03eb..7bd1777eaf 100644
--- a/frontend/utilities/copy_text.js
+++ b/frontend/utilities/copy_text.js
@@ -27,6 +27,12 @@ export const copyText = (elementSelector) => {
return true;
};
+export const stringToClipboard = (string) => {
+ const { navigator } = global;
+
+ return navigator.clipboard.writeText(string);
+};
+
export const COPY_TEXT_SUCCESS = 'Text copied to clipboard';
export const COPY_TEXT_ERROR = 'Text not copied. Please copy manually.';
diff --git a/server/datastore/datastore_app_test.go b/server/datastore/datastore_app_test.go
index de50f1023e..587e12c46e 100644
--- a/server/datastore/datastore_app_test.go
+++ b/server/datastore/datastore_app_test.go
@@ -2,6 +2,7 @@ package datastore
import (
"encoding/json"
+ "sort"
"testing"
"github.com/kolide/fleet/server/kolide"
@@ -75,3 +76,78 @@ func testAdditionalQueries(t *testing.T, ds kolide.Datastore) {
assert.Nil(t, err)
assert.JSONEq(t, `{"foo":"bar"}`, string(*info.AdditionalQueries))
}
+
+func testEnrollSecrets(t *testing.T, ds kolide.Datastore) {
+ name, err := ds.VerifyEnrollSecret("missing")
+ assert.Error(t, err)
+ assert.Empty(t, name)
+
+ err = ds.ApplyEnrollSecretSpec(
+ &kolide.EnrollSecretSpec{
+ Secrets: []kolide.EnrollSecret{
+ kolide.EnrollSecret{Name: "one", Secret: "one_secret", Active: true},
+ kolide.EnrollSecret{Name: "two", Secret: "two_secret", Active: false},
+ },
+ },
+ )
+ assert.NoError(t, err)
+
+ name, err = ds.VerifyEnrollSecret("one")
+ assert.Error(t, err, "secret should not match")
+ assert.Empty(t, name, "secret name should be empty")
+ name, err = ds.VerifyEnrollSecret("one_secret")
+ assert.NoError(t, err)
+ assert.Equal(t, "one", name)
+ name, err = ds.VerifyEnrollSecret("two_secret")
+ assert.Error(t, err)
+ assert.Equal(t, "", name)
+
+ err = ds.ApplyEnrollSecretSpec(
+ &kolide.EnrollSecretSpec{
+ Secrets: []kolide.EnrollSecret{
+ kolide.EnrollSecret{Name: "one", Secret: "one_secret", Active: false},
+ kolide.EnrollSecret{Name: "two", Secret: "two_secret", Active: true},
+ },
+ },
+ )
+ assert.NoError(t, err)
+
+ name, err = ds.VerifyEnrollSecret("one_secret")
+ assert.Error(t, err)
+ assert.Equal(t, "", name)
+ name, err = ds.VerifyEnrollSecret("two_secret")
+ assert.NoError(t, err)
+ assert.Equal(t, "two", name)
+
+}
+
+func testEnrollSecretRoundtrip(t *testing.T, ds kolide.Datastore) {
+ spec, err := ds.GetEnrollSecretSpec()
+ require.NoError(t, err)
+ assert.Len(t, spec.Secrets, 1)
+
+ expectedSpec := kolide.EnrollSecretSpec{
+ Secrets: []kolide.EnrollSecret{
+ kolide.EnrollSecret{Name: "one", Secret: "one_secret", Active: false},
+ kolide.EnrollSecret{Name: "two", Secret: "two_secret", Active: true},
+ },
+ }
+ err = ds.ApplyEnrollSecretSpec(&expectedSpec)
+ require.NoError(t, err)
+
+ spec, err = ds.GetEnrollSecretSpec()
+ require.NoError(t, err)
+ require.Len(t, spec.Secrets, 3)
+ // sort secrets before equality checks to ensure proper order
+ sort.Slice(spec.Secrets, func(i, j int) bool { return spec.Secrets[i].Name < spec.Secrets[j].Name })
+
+ assert.Equal(t, "default", spec.Secrets[0].Name)
+
+ assert.Equal(t, "one", spec.Secrets[1].Name)
+ assert.Equal(t, "one_secret", spec.Secrets[1].Secret)
+ assert.Equal(t, false, spec.Secrets[1].Active)
+
+ assert.Equal(t, "two", spec.Secrets[2].Name)
+ assert.Equal(t, "two_secret", spec.Secrets[2].Secret)
+ assert.Equal(t, true, spec.Secrets[2].Active)
+}
diff --git a/server/datastore/datastore_hosts_test.go b/server/datastore/datastore_hosts_test.go
index 7d730c00e8..d90b22c4a0 100644
--- a/server/datastore/datastore_hosts_test.go
+++ b/server/datastore/datastore_hosts_test.go
@@ -16,28 +16,27 @@ import (
)
var enrollTests = []struct {
- uuid, hostname, platform string
- nodeKeySize int
+ uuid, hostname, platform, nodeKey string
}{
0: {uuid: "6D14C88F-8ECF-48D5-9197-777647BF6B26",
- hostname: "web.kolide.co",
- platform: "linux",
- nodeKeySize: 12,
+ hostname: "web.kolide.co",
+ platform: "linux",
+ nodeKey: "key0",
},
1: {uuid: "B998C0EB-38CE-43B1-A743-FBD7A5C9513B",
- hostname: "mail.kolide.co",
- platform: "linux",
- nodeKeySize: 10,
+ hostname: "mail.kolide.co",
+ platform: "linux",
+ nodeKey: "key1",
},
2: {uuid: "008F0688-5311-4C59-86EE-00C2D6FC3EC2",
- hostname: "home.kolide.co",
- platform: "darwin",
- nodeKeySize: 25,
+ hostname: "home.kolide.co",
+ platform: "darwin",
+ nodeKey: "key2",
},
3: {uuid: "uuid123",
- hostname: "fakehostname",
- platform: "darwin",
- nodeKeySize: 1,
+ hostname: "fakehostname",
+ platform: "darwin",
+ nodeKey: "key3",
},
}
@@ -259,7 +258,7 @@ func testListHost(t *testing.T, ds kolide.Datastore) {
func testEnrollHost(t *testing.T, ds kolide.Datastore) {
var hosts []*kolide.Host
for _, tt := range enrollTests {
- h, err := ds.EnrollHost(tt.uuid, tt.nodeKeySize)
+ h, err := ds.EnrollHost(tt.uuid, tt.nodeKey, "default")
require.Nil(t, err)
hosts = append(hosts, h)
@@ -271,7 +270,7 @@ func testEnrollHost(t *testing.T, ds kolide.Datastore) {
func testAuthenticateHost(t *testing.T, ds kolide.Datastore) {
for _, tt := range enrollTests {
- h, err := ds.EnrollHost(tt.uuid, tt.nodeKeySize)
+ h, err := ds.EnrollHost(tt.uuid, tt.nodeKey, "default")
require.Nil(t, err)
returned, err := ds.AuthenticateHost(h.NodeKey)
diff --git a/server/datastore/datastore_labels_test.go b/server/datastore/datastore_labels_test.go
index c442c1149d..57456beacc 100644
--- a/server/datastore/datastore_labels_test.go
+++ b/server/datastore/datastore_labels_test.go
@@ -17,7 +17,7 @@ func testLabels(t *testing.T, db kolide.Datastore) {
var host *kolide.Host
var err error
for i := 0; i < 10; i++ {
- host, err = db.EnrollHost(string(i), 10)
+ host, err = db.EnrollHost(string(i), string(i), "default")
require.Nil(t, err, "enrollment should succeed")
hosts = append(hosts, *host)
}
diff --git a/server/datastore/datastore_targets_test.go b/server/datastore/datastore_targets_test.go
index 4f97b87ff5..b68c532424 100644
--- a/server/datastore/datastore_targets_test.go
+++ b/server/datastore/datastore_targets_test.go
@@ -126,7 +126,7 @@ func testHostStatus(t *testing.T, ds kolide.Datastore) {
mockClock := clock.NewMockClock()
- h, err := ds.EnrollHost("1", 24)
+ h, err := ds.EnrollHost("1", "key1", "default")
require.Nil(t, err)
// Make host no longer appear new
diff --git a/server/datastore/datastore_test.go b/server/datastore/datastore_test.go
index 9b8ac32edc..0d6d4888ac 100644
--- a/server/datastore/datastore_test.go
+++ b/server/datastore/datastore_test.go
@@ -18,6 +18,8 @@ func functionName(f func(*testing.T, kolide.Datastore)) string {
var testFunctions = [...]func(*testing.T, kolide.Datastore){
testOrgInfo,
testAdditionalQueries,
+ testEnrollSecrets,
+ testEnrollSecretRoundtrip,
testCreateInvite,
testInviteByEmail,
testInviteByToken,
diff --git a/server/datastore/inmem/hosts.go b/server/datastore/inmem/hosts.go
index 1896655829..50ab8e201a 100644
--- a/server/datastore/inmem/hosts.go
+++ b/server/datastore/inmem/hosts.go
@@ -136,7 +136,7 @@ func (d *Datastore) GenerateHostStatusStatistics(now time.Time) (online, offline
return online, offline, mia, new, nil
}
-func (d *Datastore) EnrollHost(osQueryHostID string, nodeKeySize int) (*kolide.Host, error) {
+func (d *Datastore) EnrollHost(osQueryHostID, nodeKey, secretName string) (*kolide.Host, error) {
d.mtx.Lock()
defer d.mtx.Unlock()
@@ -144,11 +144,6 @@ func (d *Datastore) EnrollHost(osQueryHostID string, nodeKeySize int) (*kolide.H
return nil, errors.New("missing host identifier from osquery for host enrollment")
}
- nodeKey, err := kolide.RandomText(nodeKeySize)
- if err != nil {
- return nil, err
- }
-
host := kolide.Host{
OsqueryHostID: osQueryHostID,
NodeKey: nodeKey,
diff --git a/server/datastore/mysql/app_configs.go b/server/datastore/mysql/app_configs.go
index 1960b06106..692d20adcb 100644
--- a/server/datastore/mysql/app_configs.go
+++ b/server/datastore/mysql/app_configs.go
@@ -5,6 +5,7 @@ import (
"github.com/VividCortex/mysqlerr"
"github.com/go-sql-driver/mysql"
+ "github.com/jmoiron/sqlx"
"github.com/kolide/fleet/server/kolide"
"github.com/pkg/errors"
)
@@ -94,7 +95,6 @@ func (d *Datastore) SaveAppConfig(info *kolide.AppConfig) error {
org_name,
org_logo_url,
kolide_server_url,
- osquery_enroll_secret,
smtp_configured,
smtp_sender_address,
smtp_server,
@@ -121,12 +121,11 @@ func (d *Datastore) SaveAppConfig(info *kolide.AppConfig) error {
live_query_disabled,
additional_queries
)
- VALUES( 1, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ? )
+ VALUES( 1, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ? )
ON DUPLICATE KEY UPDATE
org_name = VALUES(org_name),
org_logo_url = VALUES(org_logo_url),
kolide_server_url = VALUES(kolide_server_url),
- osquery_enroll_secret = VALUES(osquery_enroll_secret),
smtp_configured = VALUES(smtp_configured),
smtp_sender_address = VALUES(smtp_sender_address),
smtp_server = VALUES(smtp_server),
@@ -158,7 +157,6 @@ func (d *Datastore) SaveAppConfig(info *kolide.AppConfig) error {
info.OrgName,
info.OrgLogoURL,
info.KolideServerURL,
- info.EnrollSecret,
info.SMTPConfigured,
info.SMTPSenderAddress,
info.SMTPServer,
@@ -188,3 +186,45 @@ func (d *Datastore) SaveAppConfig(info *kolide.AppConfig) error {
return err
}
+
+func (d *Datastore) VerifyEnrollSecret(secret string) (string, error) {
+ var s kolide.EnrollSecret
+ err := d.db.Get(&s, "SELECT name, active FROM enroll_secrets WHERE secret = ?", secret)
+ if err != nil {
+ return "", errors.New("no matching secret found")
+ }
+ if !s.Active {
+ return "", errors.New("secret is inactive")
+ }
+
+ return s.Name, nil
+}
+
+func (d *Datastore) ApplyEnrollSecretSpec(spec *kolide.EnrollSecretSpec) error {
+ err := d.withRetryTxx(func(tx *sqlx.Tx) error {
+ for _, secret := range spec.Secrets {
+ sql := `
+ INSERT INTO enroll_secrets (name, secret, active)
+ VALUES (?, ?, ?)
+ ON DUPLICATE KEY UPDATE
+ secret = VALUES(secret),
+ active = VALUES(active)
+ `
+ if _, err := tx.Exec(sql, secret.Name, secret.Secret, secret.Active); err != nil {
+ return errors.Wrap(err, "upsert secret")
+ }
+ }
+ return nil
+ })
+
+ return err
+}
+
+func (d *Datastore) GetEnrollSecretSpec() (*kolide.EnrollSecretSpec, error) {
+ var spec kolide.EnrollSecretSpec
+ sql := `SELECT * FROM enroll_secrets`
+ if err := d.db.Select(&spec.Secrets, sql); err != nil {
+ return nil, errors.Wrap(err, "get secrets")
+ }
+ return &spec, nil
+}
diff --git a/server/datastore/mysql/hosts.go b/server/datastore/mysql/hosts.go
index 2868103f12..a3ee0ab6ef 100644
--- a/server/datastore/mysql/hosts.go
+++ b/server/datastore/mysql/hosts.go
@@ -172,7 +172,8 @@ func (d *Datastore) SaveHost(host *kolide.Host) error {
distributed_interval = ?,
config_tls_refresh = ?,
logger_tls_period = ?,
- additional = COALESCE(?, additional)
+ additional = COALESCE(?, additional),
+ enroll_secret_name = ?
WHERE id = ?
`
err := d.withRetryTxx(func(tx *sqlx.Tx) error {
@@ -205,6 +206,7 @@ func (d *Datastore) SaveHost(host *kolide.Host) error {
host.ConfigTLSRefresh,
host.LoggerTLSPeriod,
host.Additional,
+ host.EnrollSecretName,
host.ID,
)
if err != nil {
@@ -260,7 +262,6 @@ func (d *Datastore) DeleteHost(hid uint) error {
return nil
}
-// TODO needs test
func (d *Datastore) Host(id uint) (*kolide.Host, error) {
sqlStatement := `
SELECT * FROM hosts
@@ -442,24 +443,20 @@ func (d *Datastore) getNetInterfacesForHost(host *kolide.Host) error {
}
// EnrollHost enrolls a host
-func (d *Datastore) EnrollHost(osqueryHostID string, nodeKeySize int) (*kolide.Host, error) {
+func (d *Datastore) EnrollHost(osqueryHostID, nodeKey, secretName string) (*kolide.Host, error) {
if osqueryHostID == "" {
return nil, fmt.Errorf("missing osquery host identifier")
}
detailUpdateTime := time.Unix(0, 0).Add(24 * time.Hour)
- nodeKey, err := kolide.RandomText(nodeKeySize)
- if err != nil {
- return nil, errors.Wrap(err, "generating random text")
- }
-
sqlInsert := `
INSERT INTO hosts (
detail_update_time,
osquery_host_id,
seen_time,
- node_key
- ) VALUES (?, ?, ?, ?)
+ node_key,
+ enroll_secret_name
+ ) VALUES (?, ?, ?, ?, ?)
ON DUPLICATE KEY UPDATE
node_key = VALUES(node_key),
deleted = FALSE
@@ -467,7 +464,7 @@ func (d *Datastore) EnrollHost(osqueryHostID string, nodeKeySize int) (*kolide.H
var result sql.Result
- result, err = d.db.Exec(sqlInsert, detailUpdateTime, osqueryHostID, time.Now().UTC(), nodeKey)
+ result, err := d.db.Exec(sqlInsert, detailUpdateTime, osqueryHostID, time.Now().UTC(), nodeKey, secretName)
if err != nil {
return nil, errors.Wrap(err, "inserting")
@@ -522,7 +519,8 @@ func (d *Datastore) AuthenticateHost(nodeKey string) (*kolide.Host, error) {
seen_time,
distributed_interval,
logger_tls_period,
- config_tls_refresh
+ config_tls_refresh,
+ enroll_secret_name
FROM hosts
WHERE node_key = ? AND NOT deleted
LIMIT 1
diff --git a/server/datastore/mysql/migrations/tables/20200512120000_CreateTableEnrollSecrets.go b/server/datastore/mysql/migrations/tables/20200512120000_CreateTableEnrollSecrets.go
new file mode 100644
index 0000000000..aeb9fd2d23
--- /dev/null
+++ b/server/datastore/mysql/migrations/tables/20200512120000_CreateTableEnrollSecrets.go
@@ -0,0 +1,56 @@
+package tables
+
+import (
+ "database/sql"
+
+ "github.com/pkg/errors"
+)
+
+func init() {
+ MigrationClient.AddMigration(Up_20200512120000, Down_20200512120000)
+}
+
+func Up_20200512120000(tx *sql.Tx) error {
+ _, err := tx.Exec(
+ "CREATE TABLE `enroll_secrets` (" +
+ "`name` VARCHAR(255) NOT NULL," +
+ "`created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP," +
+ "`secret` VARCHAR(255) NOT NULL," +
+ "`active` TINYINT(1) DEFAULT TRUE," +
+ "PRIMARY KEY (`name`)" +
+ ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;",
+ )
+ if err != nil {
+ return errors.Wrap(err, "create enroll_secrets table")
+ }
+
+ _, err = tx.Exec(
+ "INSERT INTO `enroll_secrets` (`name`, `secret`, `active`)" +
+ "SELECT 'default', `osquery_enroll_secret`, TRUE FROM `app_configs`",
+ )
+ if err != nil {
+ return errors.Wrap(err, "copy existing enroll secret")
+ }
+
+ _, err = tx.Exec(
+ "ALTER TABLE `hosts`" +
+ "ADD COLUMN `enroll_secret_name` VARCHAR(255) NOT NULL DEFAULT ''",
+ )
+ if err != nil {
+ return errors.Wrap(err, "drop old secret column")
+ }
+
+ _, err = tx.Exec(
+ "ALTER TABLE `app_configs`" +
+ "DROP COLUMN `osquery_enroll_secret`",
+ )
+ if err != nil {
+ return errors.Wrap(err, "drop old secret column")
+ }
+
+ return nil
+}
+
+func Down_20200512120000(tx *sql.Tx) error {
+ return nil
+}
diff --git a/server/kolide/app.go b/server/kolide/app.go
index e23ef273cc..f6014d4f5f 100644
--- a/server/kolide/app.go
+++ b/server/kolide/app.go
@@ -3,6 +3,7 @@ package kolide
import (
"context"
"encoding/json"
+ "time"
)
// AppConfigStore contains method for saving and retrieving
@@ -11,6 +12,16 @@ type AppConfigStore interface {
NewAppConfig(info *AppConfig) (*AppConfig, error)
AppConfig() (*AppConfig, error)
SaveAppConfig(info *AppConfig) error
+
+ // VerifyEnrollSecret checks that the provided secret matches an active
+ // enroll secret. If it is successfully matched, the name of the secret is
+ // returned. Otherwise an error is returned.
+ VerifyEnrollSecret(secret string) (string, error)
+ // ApplyEnrollSecretSpec adds and updates the enroll secrets specified in
+ // the spec.
+ ApplyEnrollSecretSpec(spec *EnrollSecretSpec) error
+ // GetEnrollSecretSpec gets the spec for the current enroll secrets.
+ GetEnrollSecretSpec() (*EnrollSecretSpec, error)
}
// AppConfigService provides methods for configuring
@@ -21,6 +32,12 @@ type AppConfigService interface {
ModifyAppConfig(ctx context.Context, p AppConfigPayload) (info *AppConfig, err error)
SendTestEmail(ctx context.Context, config *AppConfig) error
+ // ApplyEnrollSecretSpec adds and updates the enroll secrets specified in
+ // the spec.
+ ApplyEnrollSecretSpec(ctx context.Context, spec *EnrollSecretSpec) error
+ // GetEnrollSecretSpec gets the spec for the current enroll secrets.
+ GetEnrollSecretSpec(ctx context.Context) (*EnrollSecretSpec, error)
+
// Certificate returns the PEM encoded certificate chain for osqueryd TLS termination.
// For cases where the connection is self-signed, the server will attempt to
// connect using the InsecureSkipVerify option in tls.Config.
@@ -84,11 +101,6 @@ type AppConfig struct {
OrgLogoURL string `db:"org_logo_url"`
KolideServerURL string `db:"kolide_server_url"`
- // EnrollSecret is the config value that must be given by osqueryd hosts
- // on enrollment.
- // See https://osquery.readthedocs.io/en/stable/deployment/remote/#remote-authentication
- EnrollSecret string `db:"osquery_enroll_secret"`
-
// SMTPConfigured is a flag that indicates if smtp has been successfully
// tested with the settings provided by an admin user.
SMTPConfigured bool `db:"smtp_configured"`
@@ -238,7 +250,6 @@ type OrgInfo struct {
// ServerSettings contains general settings about the kolide App.
type ServerSettings struct {
KolideServerURL *string `json:"kolide_server_url,omitempty"`
- EnrollSecret *string `json:"osquery_enroll_secret,omitempty"`
LiveQueryDisabled *bool `json:"live_query_disabled,omitempty"`
}
@@ -272,3 +283,23 @@ type ListOptions struct {
// Direction of ordering
OrderDirection OrderDirection
}
+
+// EnrollSecret contains information about an enroll secret, name, and active
+// status. Enroll secrets are used for osquery authentication.
+type EnrollSecret struct {
+ // Name is the name assigned to the secret
+ Name string `json:"name" db:"name"`
+ // Secret is the actual secret key.
+ Secret string `json:"secret" db:"secret"`
+ // Active determines whether the secret is currently allowed to be used for
+ // authentication.
+ Active bool `json:"active" db:"active"`
+ // CreatedAt is the time this enroll secret was first added.
+ CreatedAt time.Time `json:"created_at" db:"created_at"`
+}
+
+// EnrollSecretSpec is the fleetctl spec type for enroll secrets.
+type EnrollSecretSpec struct {
+ // Secrets is the list of enroll secrets.
+ Secrets []EnrollSecret `json:"secrets"`
+}
diff --git a/server/kolide/hosts.go b/server/kolide/hosts.go
index ba3498c596..65f2b08510 100644
--- a/server/kolide/hosts.go
+++ b/server/kolide/hosts.go
@@ -34,12 +34,14 @@ const (
)
type HostStore interface {
+ // NewHost is deprecated and will be removed. Hosts should always be
+ // enrolled via EnrollHost.
NewHost(host *Host) (*Host, error)
SaveHost(host *Host) error
DeleteHost(hid uint) error
Host(id uint) (*Host, error)
ListHosts(opt ListOptions) ([]*Host, error)
- EnrollHost(osqueryHostId string, nodeKeySize int) (*Host, error)
+ EnrollHost(osqueryHostId, nodeKey, secretName string) (*Host, error)
// AuthenticateHost authenticates and returns host metadata by node key.
// This method should not return the host "additional" information as this
// is not typically necessary for the operations performed by the osquery
@@ -113,6 +115,7 @@ type Host struct {
ConfigTLSRefresh uint `json:"config_tls_refresh" db:"config_tls_refresh"`
LoggerTLSPeriod uint `json:"logger_tls_period" db:"logger_tls_period"`
Additional *json.RawMessage `json:"additional,omitempty" db:"additional"`
+ EnrollSecretName string `json:"enroll_secret_name" db:"enroll_secret_name"`
}
// HostSummary is a structure which represents a data summary about the total
diff --git a/server/mock/datastore_appconfig.go b/server/mock/datastore_appconfig.go
index 89fec941f3..6764179dca 100644
--- a/server/mock/datastore_appconfig.go
+++ b/server/mock/datastore_appconfig.go
@@ -12,6 +12,12 @@ type AppConfigFunc func() (*kolide.AppConfig, error)
type SaveAppConfigFunc func(info *kolide.AppConfig) error
+type VerifyEnrollSecretFunc func(secret string) (string, error)
+
+type ApplyEnrollSecretSpecFunc func(spec *kolide.EnrollSecretSpec) error
+
+type GetEnrollSecretSpecFunc func() (*kolide.EnrollSecretSpec, error)
+
type AppConfigStore struct {
NewAppConfigFunc NewAppConfigFunc
NewAppConfigFuncInvoked bool
@@ -21,6 +27,15 @@ type AppConfigStore struct {
SaveAppConfigFunc SaveAppConfigFunc
SaveAppConfigFuncInvoked bool
+
+ VerifyEnrollSecretFunc VerifyEnrollSecretFunc
+ VerifyEnrollSecretFuncInvoked bool
+
+ ApplyEnrollSecretSpecFunc ApplyEnrollSecretSpecFunc
+ ApplyEnrollSecretSpecFuncInvoked bool
+
+ GetEnrollSecretSpecFunc GetEnrollSecretSpecFunc
+ GetEnrollSecretSpecFuncInvoked bool
}
func (s *AppConfigStore) NewAppConfig(info *kolide.AppConfig) (*kolide.AppConfig, error) {
@@ -37,3 +52,18 @@ func (s *AppConfigStore) SaveAppConfig(info *kolide.AppConfig) error {
s.SaveAppConfigFuncInvoked = true
return s.SaveAppConfigFunc(info)
}
+
+func (s *AppConfigStore) VerifyEnrollSecret(secret string) (string, error) {
+ s.VerifyEnrollSecretFuncInvoked = true
+ return s.VerifyEnrollSecretFunc(secret)
+}
+
+func (s *AppConfigStore) ApplyEnrollSecretSpec(spec *kolide.EnrollSecretSpec) error {
+ s.ApplyEnrollSecretSpecFuncInvoked = true
+ return s.ApplyEnrollSecretSpecFunc(spec)
+}
+
+func (s *AppConfigStore) GetEnrollSecretSpec() (*kolide.EnrollSecretSpec, error) {
+ s.GetEnrollSecretSpecFuncInvoked = true
+ return s.GetEnrollSecretSpecFunc()
+}
diff --git a/server/mock/datastore_hosts.go b/server/mock/datastore_hosts.go
index e337bb49b4..cb04a9b03c 100644
--- a/server/mock/datastore_hosts.go
+++ b/server/mock/datastore_hosts.go
@@ -20,7 +20,7 @@ type HostFunc func(id uint) (*kolide.Host, error)
type ListHostsFunc func(opt kolide.ListOptions) ([]*kolide.Host, error)
-type EnrollHostFunc func(osqueryHostId string, nodeKeySize int) (*kolide.Host, error)
+type EnrollHostFunc func(osqueryHostId, nodeKey, secretName string) (*kolide.Host, error)
type AuthenticateHostFunc func(nodeKey string) (*kolide.Host, error)
@@ -102,9 +102,9 @@ func (s *HostStore) ListHosts(opt kolide.ListOptions) ([]*kolide.Host, error) {
return s.ListHostsFunc(opt)
}
-func (s *HostStore) EnrollHost(osqueryHostId string, nodeKeySize int) (*kolide.Host, error) {
+func (s *HostStore) EnrollHost(osqueryHostId, nodeKey, secretName string) (*kolide.Host, error) {
s.EnrollHostFuncInvoked = true
- return s.EnrollHostFunc(osqueryHostId, nodeKeySize)
+ return s.EnrollHostFunc(osqueryHostId, nodeKey, secretName)
}
func (s *HostStore) AuthenticateHost(nodeKey string) (*kolide.Host, error) {
diff --git a/server/service/client_appconfig.go b/server/service/client_appconfig.go
index 38000eba9a..858910c45e 100644
--- a/server/service/client_appconfig.go
+++ b/server/service/client_appconfig.go
@@ -69,3 +69,61 @@ func (c *Client) GetServerSettings() (*kolide.ServerSettings, error) {
}
return appConfig.ServerSettings, nil
}
+
+// GetEnrollSecretSpec fetches the enroll secrets stored on the server
+func (c *Client) GetEnrollSecretSpec() (*kolide.EnrollSecretSpec, error) {
+ response, err := c.AuthenticatedDo("GET", "/api/v1/kolide/spec/enroll_secret", nil)
+ if err != nil {
+ return nil, errors.Wrap(err, "GET /api/v1/kolide/spec/enroll_secret")
+ }
+ defer response.Body.Close()
+
+ if response.StatusCode != http.StatusOK {
+ return nil, errors.Errorf(
+ "get enroll_secrets received status %d %s",
+ response.StatusCode,
+ extractServerErrorText(response.Body),
+ )
+ }
+
+ var responseBody getEnrollSecretSpecResponse
+ err = json.NewDecoder(response.Body).Decode(&responseBody)
+ if err != nil {
+ return nil, errors.Wrap(err, "decode get enroll secret spec response")
+ }
+
+ if responseBody.Err != nil {
+ return nil, errors.Errorf("get enroll secret spec: %s", responseBody.Err)
+ }
+
+ return responseBody.Spec, nil
+}
+
+// ApplyEnrollSecretSpec applies the enroll secrets.
+func (c *Client) ApplyEnrollSecretSpec(spec *kolide.EnrollSecretSpec) error {
+ req := applyEnrollSecretSpecRequest{Spec: spec}
+ response, err := c.AuthenticatedDo("POST", "/api/v1/kolide/spec/enroll_secret", req)
+ if err != nil {
+ return errors.Wrap(err, "POST /api/v1/kolide/spec/enroll_secret")
+ }
+ defer response.Body.Close()
+
+ if response.StatusCode != http.StatusOK {
+ return errors.Errorf(
+ "apply enroll secret received status %d %s",
+ response.StatusCode,
+ extractServerErrorText(response.Body),
+ )
+ }
+
+ var responseBody applyEnrollSecretSpecResponse
+ err = json.NewDecoder(response.Body).Decode(&responseBody)
+ if err != nil {
+ return errors.Wrap(err, "decode apply enroll secret response")
+ }
+
+ if responseBody.Err != nil {
+ return errors.Errorf("apply enroll secret: %s", responseBody.Err)
+ }
+ return nil
+}
diff --git a/server/service/endpoint_appconfig.go b/server/service/endpoint_appconfig.go
index 41b4366fae..84e56e857d 100644
--- a/server/service/endpoint_appconfig.go
+++ b/server/service/endpoint_appconfig.go
@@ -65,7 +65,6 @@ func makeGetAppConfigEndpoint(svc kolide.Service) endpoint.Endpoint {
},
ServerSettings: &kolide.ServerSettings{
KolideServerURL: &config.KolideServerURL,
- EnrollSecret: &config.EnrollSecret,
LiveQueryDisabled: &config.LiveQueryDisabled,
},
SMTPSettings: smtpSettings,
@@ -93,7 +92,6 @@ func makeModifyAppConfigEndpoint(svc kolide.Service) endpoint.Endpoint {
},
ServerSettings: &kolide.ServerSettings{
KolideServerURL: &config.KolideServerURL,
- EnrollSecret: &config.EnrollSecret,
LiveQueryDisabled: &config.LiveQueryDisabled,
},
SMTPSettings: smtpSettingsFromAppConfig(config),
@@ -137,3 +135,49 @@ func smtpSettingsFromAppConfig(config *kolide.AppConfig) *kolide.SMTPSettingsPay
SMTPEnableStartTLS: &config.SMTPEnableStartTLS,
}
}
+
+////////////////////////////////////////////////////////////////////////////////
+// Apply Enroll Secret Spec
+////////////////////////////////////////////////////////////////////////////////
+
+type applyEnrollSecretSpecRequest struct {
+ Spec *kolide.EnrollSecretSpec `json:"spec"`
+}
+
+type applyEnrollSecretSpecResponse struct {
+ Err error `json:"error,omitempty"`
+}
+
+func (r applyEnrollSecretSpecResponse) error() error { return r.Err }
+
+func makeApplyEnrollSecretSpecEndpoint(svc kolide.Service) endpoint.Endpoint {
+ return func(ctx context.Context, request interface{}) (interface{}, error) {
+ req := request.(applyEnrollSecretSpecRequest)
+ err := svc.ApplyEnrollSecretSpec(ctx, req.Spec)
+ if err != nil {
+ return applyEnrollSecretSpecResponse{Err: err}, nil
+ }
+ return applyEnrollSecretSpecResponse{}, nil
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Get Pack Specs
+////////////////////////////////////////////////////////////////////////////////
+
+type getEnrollSecretSpecResponse struct {
+ Spec *kolide.EnrollSecretSpec `json:"specs"`
+ Err error `json:"error,omitempty"`
+}
+
+func (r getEnrollSecretSpecResponse) error() error { return r.Err }
+
+func makeGetEnrollSecretSpecEndpoint(svc kolide.Service) endpoint.Endpoint {
+ return func(ctx context.Context, request interface{}) (interface{}, error) {
+ specs, err := svc.GetEnrollSecretSpec(ctx)
+ if err != nil {
+ return getEnrollSecretSpecResponse{Err: err}, nil
+ }
+ return getEnrollSecretSpecResponse{Spec: specs}, nil
+ }
+}
diff --git a/server/service/endpoint_middleware_test.go b/server/service/endpoint_middleware_test.go
index 68a7737d3f..4b2dc111f6 100644
--- a/server/service/endpoint_middleware_test.go
+++ b/server/service/endpoint_middleware_test.go
@@ -3,12 +3,16 @@ package service
import (
"context"
"testing"
+ "time"
"github.com/go-kit/kit/endpoint"
"github.com/kolide/fleet/server/config"
+ hostctx "github.com/kolide/fleet/server/contexts/host"
"github.com/kolide/fleet/server/contexts/viewer"
"github.com/kolide/fleet/server/datastore/inmem"
"github.com/kolide/fleet/server/kolide"
+ "github.com/kolide/fleet/server/mock"
+ "github.com/pkg/errors"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@@ -197,25 +201,36 @@ func TestGetNodeKey(t *testing.T) {
}
func TestAuthenticatedHost(t *testing.T) {
- ds, err := inmem.New(config.TestConfig())
- require.Nil(t, err)
- _, err = ds.NewAppConfig(&kolide.AppConfig{EnrollSecret: "foobarbaz"})
- require.Nil(t, err)
+ ds := new(mock.Store)
svc, err := newTestService(ds, nil)
require.Nil(t, err)
+ expectedHost := kolide.Host{HostName: "foo!"}
+ goodNodeKey := "foo bar baz bing bang boom"
+
+ ds.AuthenticateHostFunc = func(secret string) (*kolide.Host, error) {
+ switch secret {
+ case goodNodeKey:
+ return &expectedHost, nil
+ default:
+ return nil, errors.New("no host found")
+
+ }
+ }
+ ds.MarkHostSeenFunc = func(host *kolide.Host, t time.Time) error {
+ return nil
+ }
+
endpoint := authenticatedHost(
svc,
func(ctx context.Context, request interface{}) (interface{}, error) {
+ host, ok := hostctx.FromContext(ctx)
+ assert.True(t, ok)
+ assert.Equal(t, expectedHost, host)
return nil, nil
},
)
- ctx := context.Background()
- goodNodeKey, err := svc.EnrollAgent(ctx, "foobarbaz", "host123", nil)
- assert.Nil(t, err)
- require.NotEmpty(t, goodNodeKey)
-
var authenticatedHostTests = []struct {
nodeKey string
shouldErr bool
diff --git a/server/service/endpoint_setup.go b/server/service/endpoint_setup.go
index 58104f34ac..8bd0eabd78 100644
--- a/server/service/endpoint_setup.go
+++ b/server/service/endpoint_setup.go
@@ -42,9 +42,6 @@ func makeSetupEndpoint(svc kolide.Service) endpoint.Endpoint {
if req.KolideServerURL != nil {
configPayload.ServerSettings.KolideServerURL = req.KolideServerURL
}
- if req.EnrollSecret != nil {
- configPayload.ServerSettings.EnrollSecret = req.EnrollSecret
- }
config, err = svc.NewAppConfig(ctx, configPayload)
if err != nil {
return setupResponse{Err: err}, nil
@@ -81,7 +78,6 @@ func makeSetupEndpoint(svc kolide.Service) endpoint.Endpoint {
OrgLogoURL: &config.OrgLogoURL,
},
KolideServerURL: &config.KolideServerURL,
- EnrollSecret: &config.EnrollSecret,
Token: token,
}, nil
}
diff --git a/server/service/handler.go b/server/service/handler.go
index 3876475f00..a254e771c4 100644
--- a/server/service/handler.go
+++ b/server/service/handler.go
@@ -36,6 +36,8 @@ type KolideEndpoints struct {
DeleteSession endpoint.Endpoint
GetAppConfig endpoint.Endpoint
ModifyAppConfig endpoint.Endpoint
+ ApplyEnrollSecretSpec endpoint.Endpoint
+ GetEnrollSecretSpec endpoint.Endpoint
CreateInvite endpoint.Endpoint
ListInvites endpoint.Endpoint
DeleteInvite endpoint.Endpoint
@@ -138,6 +140,8 @@ func MakeKolideServerEndpoints(svc kolide.Service, jwtKey, urlPrefix string) Kol
DeleteSession: authenticatedUser(jwtKey, svc, mustBeAdmin(makeDeleteSessionEndpoint(svc))),
GetAppConfig: authenticatedUser(jwtKey, svc, canPerformActions(makeGetAppConfigEndpoint(svc))),
ModifyAppConfig: authenticatedUser(jwtKey, svc, mustBeAdmin(makeModifyAppConfigEndpoint(svc))),
+ ApplyEnrollSecretSpec: authenticatedUser(jwtKey, svc, mustBeAdmin(makeApplyEnrollSecretSpecEndpoint(svc))),
+ GetEnrollSecretSpec: authenticatedUser(jwtKey, svc, canPerformActions(makeGetEnrollSecretSpecEndpoint(svc))),
CreateInvite: authenticatedUser(jwtKey, svc, mustBeAdmin(makeCreateInviteEndpoint(svc))),
ListInvites: authenticatedUser(jwtKey, svc, mustBeAdmin(makeListInvitesEndpoint(svc))),
DeleteInvite: authenticatedUser(jwtKey, svc, mustBeAdmin(makeDeleteInviteEndpoint(svc))),
@@ -225,6 +229,8 @@ type kolideHandlers struct {
DeleteSession http.Handler
GetAppConfig http.Handler
ModifyAppConfig http.Handler
+ ApplyEnrollSecretSpec http.Handler
+ GetEnrollSecretSpec http.Handler
CreateInvite http.Handler
ListInvites http.Handler
DeleteInvite http.Handler
@@ -315,6 +321,8 @@ func makeKolideKitHandlers(e KolideEndpoints, opts []kithttp.ServerOption) *koli
DeleteSession: newServer(e.DeleteSession, decodeDeleteSessionRequest),
GetAppConfig: newServer(e.GetAppConfig, decodeNoParamsRequest),
ModifyAppConfig: newServer(e.ModifyAppConfig, decodeModifyAppConfigRequest),
+ ApplyEnrollSecretSpec: newServer(e.ApplyEnrollSecretSpec, decodeApplyEnrollSecretSpecRequest),
+ GetEnrollSecretSpec: newServer(e.GetEnrollSecretSpec, decodeNoParamsRequest),
CreateInvite: newServer(e.CreateInvite, decodeCreateInviteRequest),
ListInvites: newServer(e.ListInvites, decodeListInvitesRequest),
DeleteInvite: newServer(e.DeleteInvite, decodeDeleteInviteRequest),
@@ -446,6 +454,8 @@ func attachKolideAPIRoutes(r *mux.Router, h *kolideHandlers) {
r.Handle("/api/v1/kolide/config/certificate", h.GetCertificate).Methods("GET").Name("get_certificate")
r.Handle("/api/v1/kolide/config", h.GetAppConfig).Methods("GET").Name("get_app_config")
r.Handle("/api/v1/kolide/config", h.ModifyAppConfig).Methods("PATCH").Name("modify_app_config")
+ r.Handle("/api/v1/kolide/spec/enroll_secret", h.ApplyEnrollSecretSpec).Methods("POST").Name("apply_enroll_secret_spec")
+ r.Handle("/api/v1/kolide/spec/enroll_secret", h.GetEnrollSecretSpec).Methods("GET").Name("get_enroll_secret_spec")
r.Handle("/api/v1/kolide/invites", h.CreateInvite).Methods("POST").Name("create_invite")
r.Handle("/api/v1/kolide/invites", h.ListInvites).Methods("GET").Name("list_invites")
r.Handle("/api/v1/kolide/invites/{id}", h.DeleteInvite).Methods("DELETE").Name("delete_invite")
diff --git a/server/service/service_appconfig.go b/server/service/service_appconfig.go
index 45da027935..1dc547fd01 100644
--- a/server/service/service_appconfig.go
+++ b/server/service/service_appconfig.go
@@ -36,18 +36,31 @@ func (svc service) NewAppConfig(ctx context.Context, p kolide.AppConfigPayload)
return nil, err
}
fromPayload := appConfigFromAppConfigPayload(p, *config)
- if fromPayload.EnrollSecret == "" {
- // generate a random string if the user hasn't set one in the form.
- rand, err := kolide.RandomText(24)
- if err != nil {
- return nil, errors.Wrap(err, "generate enroll secret string")
- }
- fromPayload.EnrollSecret = rand
- }
+
newConfig, err := svc.ds.NewAppConfig(fromPayload)
if err != nil {
return nil, err
}
+
+ // Set up a default enroll secret
+ secret, err := kolide.RandomText(24)
+ if err != nil {
+ return nil, errors.Wrap(err, "generate enroll secret string")
+ }
+ spec := &kolide.EnrollSecretSpec{
+ Secrets: []kolide.EnrollSecret{
+ kolide.EnrollSecret{
+ Name: "default",
+ Secret: secret,
+ Active: true,
+ },
+ },
+ }
+ err = svc.ds.ApplyEnrollSecretSpec(spec)
+ if err != nil {
+ return nil, errors.Wrap(err, "save enroll secret")
+ }
+
return newConfig, nil
}
@@ -117,9 +130,6 @@ func appConfigFromAppConfigPayload(p kolide.AppConfigPayload, config kolide.AppC
if p.ServerSettings != nil && p.ServerSettings.KolideServerURL != nil {
config.KolideServerURL = cleanupURL(*p.ServerSettings.KolideServerURL)
}
- if p.ServerSettings != nil && p.ServerSettings.EnrollSecret != nil {
- config.EnrollSecret = *p.ServerSettings.EnrollSecret
- }
if p.ServerSettings != nil && p.ServerSettings.LiveQueryDisabled != nil {
config.LiveQueryDisabled = *p.ServerSettings.LiveQueryDisabled
}
@@ -229,3 +239,11 @@ func appConfigFromAppConfigPayload(p kolide.AppConfigPayload, config kolide.AppC
}
return &config
}
+
+func (svc service) ApplyEnrollSecretSpec(ctx context.Context, spec *kolide.EnrollSecretSpec) error {
+ return svc.ds.ApplyEnrollSecretSpec(spec)
+}
+
+func (svc service) GetEnrollSecretSpec(ctx context.Context) (*kolide.EnrollSecretSpec, error) {
+ return svc.ds.GetEnrollSecretSpec()
+}
diff --git a/server/service/service_appconfig_test.go b/server/service/service_appconfig_test.go
index 8ec9d78608..752a0cd56d 100644
--- a/server/service/service_appconfig_test.go
+++ b/server/service/service_appconfig_test.go
@@ -4,9 +4,8 @@ import (
"context"
"testing"
- "github.com/kolide/fleet/server/config"
- "github.com/kolide/fleet/server/datastore/inmem"
"github.com/kolide/fleet/server/kolide"
+ "github.com/kolide/fleet/server/mock"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@@ -32,12 +31,14 @@ func TestCleanupURL(t *testing.T) {
}
func TestCreateAppConfig(t *testing.T) {
- ds, err := inmem.New(config.TestConfig())
- require.Nil(t, err)
- require.Nil(t, ds.MigrateData())
-
+ ds := new(mock.Store)
svc, err := newTestService(ds, nil)
require.Nil(t, err)
+
+ ds.AppConfigFunc = func() (*kolide.AppConfig, error) {
+ return &kolide.AppConfig{}, nil
+ }
+
var appConfigTests = []struct {
configPayload kolide.AppConfigPayload
}{
@@ -48,7 +49,7 @@ func TestCreateAppConfig(t *testing.T) {
OrgName: stringPtr("Acme"),
},
ServerSettings: &kolide.ServerSettings{
- KolideServerURL: stringPtr("https://acme.co:8080/"),
+ KolideServerURL: stringPtr("https://acme.co:8080/"),
LiveQueryDisabled: boolPtr(true),
},
},
@@ -56,14 +57,30 @@ func TestCreateAppConfig(t *testing.T) {
}
for _, tt := range appConfigTests {
- result, err := svc.NewAppConfig(context.Background(), tt.configPayload)
+ var result *kolide.AppConfig
+ ds.NewAppConfigFunc = func(config *kolide.AppConfig) (*kolide.AppConfig, error) {
+ result = config
+ return config, nil
+ }
+
+ var gotSecretSpec *kolide.EnrollSecretSpec
+ ds.ApplyEnrollSecretSpecFunc = func(spec *kolide.EnrollSecretSpec) error {
+ gotSecretSpec = spec
+ return nil
+ }
+
+ _, err := svc.NewAppConfig(context.Background(), tt.configPayload)
require.Nil(t, err)
payload := tt.configPayload
- assert.NotEmpty(t, result.ID)
assert.Equal(t, *payload.OrgInfo.OrgLogoURL, result.OrgLogoURL)
assert.Equal(t, *payload.OrgInfo.OrgName, result.OrgName)
assert.Equal(t, "https://acme.co:8080", result.KolideServerURL)
assert.Equal(t, *payload.ServerSettings.LiveQueryDisabled, result.LiveQueryDisabled)
+
+ // Ensure enroll secret was set
+ require.NotNil(t, gotSecretSpec)
+ require.Len(t, gotSecretSpec.Secrets, 1)
+ assert.Len(t, gotSecretSpec.Secrets[0].Secret, 32)
}
}
diff --git a/server/service/service_osquery.go b/server/service/service_osquery.go
index 57067e5e41..7cba905e3d 100644
--- a/server/service/service_osquery.go
+++ b/server/service/service_osquery.go
@@ -72,18 +72,25 @@ func (svc service) AuthenticateHost(ctx context.Context, nodeKey string) (*kolid
}
func (svc service) EnrollAgent(ctx context.Context, enrollSecret, hostIdentifier string, hostDetails map[string](map[string]string)) (string, error) {
- config, err := svc.ds.AppConfig()
+ secretName, err := svc.ds.VerifyEnrollSecret(enrollSecret)
if err != nil {
- return "", osqueryError{message: "getting enroll secret: " + err.Error(), nodeInvalid: true}
+ return "", osqueryError{
+ message: "enroll failed: " + err.Error(),
+ nodeInvalid: true,
+ }
}
- if enrollSecret != config.EnrollSecret {
- return "", osqueryError{message: "invalid enroll secret", nodeInvalid: true}
+ nodeKey, err := kolide.RandomText(svc.config.Osquery.NodeKeySize)
+ if err != nil {
+ return "", osqueryError{
+ message: "generate node key failed: " + err.Error(),
+ nodeInvalid: true,
+ }
}
- host, err := svc.ds.EnrollHost(hostIdentifier, svc.config.Osquery.NodeKeySize)
+ host, err := svc.ds.EnrollHost(hostIdentifier, nodeKey, secretName)
if err != nil {
- return "", osqueryError{message: "enrollment failed: " + err.Error(), nodeInvalid: true}
+ return "", osqueryError{message: "save enroll failed: " + err.Error(), nodeInvalid: true}
}
// Save enrollment details if provided
diff --git a/server/service/service_osquery_test.go b/server/service/service_osquery_test.go
index 389a01d39a..9df00be7b9 100644
--- a/server/service/service_osquery_test.go
+++ b/server/service/service_osquery_test.go
@@ -24,42 +24,66 @@ import (
)
func TestEnrollAgent(t *testing.T) {
- ds, svc, _ := setupOsqueryTests(t)
- ctx := context.Background()
+ ds := new(mock.Store)
+ ds.VerifyEnrollSecretFunc = func(secret string) (string, error) {
+ switch secret {
+ case "valid_secret":
+ return "valid", nil
+ default:
+ return "", errors.New("not found")
+ }
+ }
+ ds.EnrollHostFunc = func(osqueryHostId, nodeKey, secretName string) (*kolide.Host, error) {
+ return &kolide.Host{
+ OsqueryHostID: osqueryHostId, NodeKey: nodeKey, EnrollSecretName: secretName,
+ }, nil
+ }
- hosts, err := ds.ListHosts(kolide.ListOptions{})
- assert.Nil(t, err)
- assert.Len(t, hosts, 0)
+ svc, err := newTestService(ds, nil)
+ require.Nil(t, err)
- nodeKey, err := svc.EnrollAgent(ctx, "", "host123", nil)
+ nodeKey, err := svc.EnrollAgent(context.Background(), "valid_secret", "host123", nil)
require.Nil(t, err)
assert.NotEmpty(t, nodeKey)
-
- hosts, err = ds.ListHosts(kolide.ListOptions{})
- assert.Nil(t, err)
- assert.Len(t, hosts, 1)
}
func TestEnrollAgentIncorrectEnrollSecret(t *testing.T) {
- ds, svc, _ := setupOsqueryTests(t)
- ctx := context.Background()
+ ds := new(mock.Store)
+ ds.VerifyEnrollSecretFunc = func(secret string) (string, error) {
+ switch secret {
+ case "valid_secret":
+ return "valid", nil
+ default:
+ return "", errors.New("not found")
+ }
+ }
- hosts, err := ds.ListHosts(kolide.ListOptions{})
- assert.Nil(t, err)
- assert.Len(t, hosts, 0)
+ svc, err := newTestService(ds, nil)
+ require.Nil(t, err)
- nodeKey, err := svc.EnrollAgent(ctx, "not_correct", "host123", nil)
+ nodeKey, err := svc.EnrollAgent(context.Background(), "not_correct", "host123", nil)
assert.NotNil(t, err)
assert.Empty(t, nodeKey)
-
- hosts, err = ds.ListHosts(kolide.ListOptions{})
- assert.Nil(t, err)
- assert.Len(t, hosts, 0)
}
func TestEnrollAgentDetails(t *testing.T) {
- ds, svc, _ := setupOsqueryTests(t)
- ctx := context.Background()
+ ds := new(mock.Store)
+ ds.VerifyEnrollSecretFunc = func(secret string) (string, error) {
+ return "valid", nil
+ }
+ ds.EnrollHostFunc = func(osqueryHostId, nodeKey, secretName string) (*kolide.Host, error) {
+ return &kolide.Host{
+ OsqueryHostID: osqueryHostId, NodeKey: nodeKey, EnrollSecretName: secretName,
+ }, nil
+ }
+ var gotHost *kolide.Host
+ ds.SaveHostFunc = func(host *kolide.Host) error {
+ gotHost = host
+ return nil
+ }
+
+ svc, err := newTestService(ds, nil)
+ require.Nil(t, err)
details := map[string](map[string]string){
"osquery_info": {"version": "2.12.0"},
@@ -73,48 +97,51 @@ func TestEnrollAgentDetails(t *testing.T) {
},
"foo": {"foo": "bar"},
}
- nodeKey, err := svc.EnrollAgent(ctx, "", "host123", details)
+ nodeKey, err := svc.EnrollAgent(context.Background(), "", "host123", details)
require.Nil(t, err)
assert.NotEmpty(t, nodeKey)
- hosts, err := ds.ListHosts(kolide.ListOptions{})
- require.Nil(t, err)
- require.Len(t, hosts, 1)
-
- h := hosts[0]
- assert.Equal(t, "Mac OS X 10.14.5", h.OSVersion)
- assert.Equal(t, "darwin", h.Platform)
- assert.Equal(t, "2.12.0", h.OsqueryVersion)
- assert.Equal(t, "zwass.local", h.HostName)
- assert.Equal(t, "froobling_uuid", h.UUID)
+ assert.Equal(t, "Mac OS X 10.14.5", gotHost.OSVersion)
+ assert.Equal(t, "darwin", gotHost.Platform)
+ assert.Equal(t, "2.12.0", gotHost.OsqueryVersion)
+ assert.Equal(t, "zwass.local", gotHost.HostName)
+ assert.Equal(t, "froobling_uuid", gotHost.UUID)
+ assert.Equal(t, "valid", gotHost.EnrollSecretName)
}
func TestAuthenticateHost(t *testing.T) {
- ds, svc, mockClock := setupOsqueryTests(t)
- ctx := context.Background()
-
- nodeKey, err := svc.EnrollAgent(ctx, "", "host123", nil)
+ ds := new(mock.Store)
+ svc, err := newTestService(ds, nil)
require.Nil(t, err)
- mockClock.AddTime(1 * time.Minute)
+ var gotKey string
+ host := kolide.Host{HostName: "foobar"}
+ ds.AuthenticateHostFunc = func(key string) (*kolide.Host, error) {
+ gotKey = key
+ return &host, nil
+ }
+ ds.MarkHostSeenFunc = func(host *kolide.Host, t time.Time) error {
+ return nil
+ }
- host, err := svc.AuthenticateHost(ctx, nodeKey)
+ h, err := svc.AuthenticateHost(context.Background(), "test")
+ require.Nil(t, err)
+ assert.Equal(t, "test", gotKey)
+ assert.True(t, ds.MarkHostSeenFuncInvoked)
+ assert.Equal(t, host, *h)
+}
+
+func TestAuthenticateHostFailure(t *testing.T) {
+ ds := new(mock.Store)
+ svc, err := newTestService(ds, nil)
require.Nil(t, err)
- // Verify that the update time is set appropriately
- checkHost, err := ds.Host(host.ID)
- require.Nil(t, err)
- assert.Equal(t, mockClock.Now(), checkHost.UpdatedAt)
+ ds.AuthenticateHostFunc = func(key string) (*kolide.Host, error) {
+ return nil, errors.New("not found")
+ }
- // Advance clock time and check that seen time is updated
- mockClock.AddTime(1*time.Minute + 27*time.Second)
-
- _, err = svc.AuthenticateHost(ctx, nodeKey)
- require.Nil(t, err)
-
- checkHost, err = ds.Host(host.ID)
- require.Nil(t, err)
- assert.Equal(t, mockClock.Now(), checkHost.UpdatedAt)
+ _, err = svc.AuthenticateHost(context.Background(), "test")
+ require.NotNil(t, err)
}
type testJSONLogger struct {
@@ -127,18 +154,10 @@ func (n *testJSONLogger) Write(ctx context.Context, logs []json.RawMessage) erro
}
func TestSubmitStatusLogs(t *testing.T) {
- ds, svc, _ := setupOsqueryTests(t)
- ctx := context.Background()
-
- _, err := svc.EnrollAgent(ctx, "", "host123", nil)
+ ds := new(mock.Store)
+ svc, err := newTestService(ds, nil)
require.Nil(t, err)
- hosts, err := ds.ListHosts(kolide.ListOptions{})
- require.Nil(t, err)
- require.Len(t, hosts, 1)
- host := hosts[0]
- ctx = hostctx.NewContext(ctx, *host)
-
// Hack to get at the service internals and modify the writer
serv := ((svc.(validationMiddleware)).Service).(service)
@@ -155,6 +174,8 @@ func TestSubmitStatusLogs(t *testing.T) {
err = json.Unmarshal([]byte(logJSON), &status)
require.Nil(t, err)
+ host := kolide.Host{}
+ ctx := hostctx.NewContext(context.Background(), host)
err = serv.SubmitStatusLogs(ctx, status)
assert.Nil(t, err)
@@ -162,18 +183,10 @@ func TestSubmitStatusLogs(t *testing.T) {
}
func TestSubmitResultLogs(t *testing.T) {
- ds, svc, _ := setupOsqueryTests(t)
- ctx := context.Background()
-
- _, err := svc.EnrollAgent(ctx, "", "host123", nil)
+ ds := new(mock.Store)
+ svc, err := newTestService(ds, nil)
require.Nil(t, err)
- hosts, err := ds.ListHosts(kolide.ListOptions{})
- require.Nil(t, err)
- require.Len(t, hosts, 1)
- host := hosts[0]
- ctx = hostctx.NewContext(ctx, *host)
-
// Hack to get at the service internals and modify the writer
serv := ((svc.(validationMiddleware)).Service).(service)
@@ -194,6 +207,8 @@ func TestSubmitResultLogs(t *testing.T) {
err = json.Unmarshal([]byte(logJSON), &results)
require.Nil(t, err)
+ host := kolide.Host{}
+ ctx := hostctx.NewContext(context.Background(), host)
err = serv.SubmitResultLogs(ctx, results)
assert.Nil(t, err)
@@ -496,16 +511,23 @@ func TestGetClientConfig(t *testing.T) {
}
func TestDetailQueriesWithEmptyStrings(t *testing.T) {
- ds, svc, mockClock := setupOsqueryTests(t)
- ctx := context.Background()
-
- nodeKey, err := svc.EnrollAgent(ctx, "", "host123", nil)
- assert.Nil(t, err)
-
- host, err := ds.AuthenticateHost(nodeKey)
+ ds := new(mock.Store)
+ mockClock := clock.NewMockClock()
+ svc, err := newTestServiceWithClock(ds, nil, mockClock)
require.Nil(t, err)
- ctx = hostctx.NewContext(ctx, *host)
+ host := kolide.Host{}
+ ctx := hostctx.NewContext(context.Background(), host)
+
+ ds.AppConfigFunc = func() (*kolide.AppConfig, error) {
+ return &kolide.AppConfig{}, nil
+ }
+ ds.LabelQueriesForHostFunc = func(*kolide.Host, time.Time) (map[string]string, error) {
+ return map[string]string{}, nil
+ }
+ ds.DistributedQueriesForHostFunc = func(*kolide.Host) (map[uint]string, error) {
+ return map[uint]string{}, nil
+ }
// With a new host, we should get the detail queries (and accelerated
// queries)
@@ -606,39 +628,41 @@ func TestDetailQueriesWithEmptyStrings(t *testing.T) {
err = json.Unmarshal([]byte(resultJSON), &results)
require.Nil(t, err)
+ var gotHost *kolide.Host
+ ds.SaveHostFunc = func(host *kolide.Host) error {
+ gotHost = host
+ return nil
+ }
+
// Verify that results are ingested properly
svc.SubmitDistributedQueryResults(ctx, results, map[string]kolide.OsqueryStatus{})
- // Make sure the result saved to the datastore
- host, err = ds.AuthenticateHost(nodeKey)
- require.Nil(t, err)
-
// osquery_info
- assert.Equal(t, "darwin", host.Platform)
- assert.Equal(t, "1.8.2", host.OsqueryVersion)
+ assert.Equal(t, "darwin", gotHost.Platform)
+ assert.Equal(t, "1.8.2", gotHost.OsqueryVersion)
// system_info
- assert.Equal(t, 17179869184, host.PhysicalMemory)
- assert.Equal(t, "computer.local", host.HostName)
- assert.Equal(t, "uuid", host.UUID)
+ assert.Equal(t, 17179869184, gotHost.PhysicalMemory)
+ assert.Equal(t, "computer.local", gotHost.HostName)
+ assert.Equal(t, "uuid", gotHost.UUID)
// os_version
- assert.Equal(t, "Mac OS X 10.10.6", host.OSVersion)
+ assert.Equal(t, "Mac OS X 10.10.6", gotHost.OSVersion)
// uptime
- assert.Equal(t, 1730893*time.Second, host.Uptime)
+ assert.Equal(t, 1730893*time.Second, gotHost.Uptime)
// osquery_flags
- assert.Equal(t, uint(0), host.ConfigTLSRefresh)
- assert.Equal(t, uint(0), host.DistributedInterval)
- assert.Equal(t, uint(0), host.LoggerTLSPeriod)
+ assert.Equal(t, uint(0), gotHost.ConfigTLSRefresh)
+ assert.Equal(t, uint(0), gotHost.DistributedInterval)
+ assert.Equal(t, uint(0), gotHost.LoggerTLSPeriod)
+ host.HostName = "computer.local"
+ host.DetailUpdateTime = mockClock.Now()
mockClock.AddTime(1 * time.Minute)
// Now no detail queries should be required
- host, err = ds.AuthenticateHost(nodeKey)
- require.Nil(t, err)
- ctx = hostctx.NewContext(ctx, *host)
+ ctx = hostctx.NewContext(context.Background(), host)
queries, acc, err = svc.GetDistributedQueries(ctx)
assert.Nil(t, err)
assert.Len(t, queries, 0)
@@ -647,12 +671,6 @@ func TestDetailQueriesWithEmptyStrings(t *testing.T) {
// Advance clock and queries should exist again
mockClock.AddTime(1*time.Hour + 1*time.Minute)
- err = svc.SubmitDistributedQueryResults(ctx, kolide.OsqueryDistributedQueryResults{}, map[string]kolide.OsqueryStatus{})
- require.Nil(t, err)
- host, err = ds.AuthenticateHost(nodeKey)
- require.Nil(t, err)
-
- ctx = hostctx.NewContext(ctx, *host)
queries, acc, err = svc.GetDistributedQueries(ctx)
assert.Nil(t, err)
assert.Len(t, queries, len(detailQueries))
@@ -660,16 +678,23 @@ func TestDetailQueriesWithEmptyStrings(t *testing.T) {
}
func TestDetailQueries(t *testing.T) {
- ds, svc, mockClock := setupOsqueryTests(t)
- ctx := context.Background()
-
- nodeKey, err := svc.EnrollAgent(ctx, "", "host123", nil)
- assert.Nil(t, err)
-
- host, err := ds.AuthenticateHost(nodeKey)
+ ds := new(mock.Store)
+ mockClock := clock.NewMockClock()
+ svc, err := newTestServiceWithClock(ds, nil, mockClock)
require.Nil(t, err)
- ctx = hostctx.NewContext(ctx, *host)
+ host := kolide.Host{}
+ ctx := hostctx.NewContext(context.Background(), host)
+
+ ds.AppConfigFunc = func() (*kolide.AppConfig, error) {
+ return &kolide.AppConfig{}, nil
+ }
+ ds.LabelQueriesForHostFunc = func(*kolide.Host, time.Time) (map[string]string, error) {
+ return map[string]string{}, nil
+ }
+ ds.DistributedQueriesForHostFunc = func(*kolide.Host) (map[uint]string, error) {
+ return map[uint]string{}, nil
+ }
// With a new host, we should get the detail queries (and accelerated
// queries)
@@ -774,39 +799,40 @@ func TestDetailQueries(t *testing.T) {
err = json.Unmarshal([]byte(resultJSON), &results)
require.Nil(t, err)
+ var gotHost *kolide.Host
+ ds.SaveHostFunc = func(host *kolide.Host) error {
+ gotHost = host
+ return nil
+ }
// Verify that results are ingested properly
svc.SubmitDistributedQueryResults(ctx, results, map[string]kolide.OsqueryStatus{})
- // Make sure the result saved to the datastore
- host, err = ds.AuthenticateHost(nodeKey)
- require.Nil(t, err)
-
// osquery_info
- assert.Equal(t, "darwin", host.Platform)
- assert.Equal(t, "1.8.2", host.OsqueryVersion)
+ assert.Equal(t, "darwin", gotHost.Platform)
+ assert.Equal(t, "1.8.2", gotHost.OsqueryVersion)
// system_info
- assert.Equal(t, 17179869184, host.PhysicalMemory)
- assert.Equal(t, "computer.local", host.HostName)
- assert.Equal(t, "uuid", host.UUID)
+ assert.Equal(t, 17179869184, gotHost.PhysicalMemory)
+ assert.Equal(t, "computer.local", gotHost.HostName)
+ assert.Equal(t, "uuid", gotHost.UUID)
// os_version
- assert.Equal(t, "Mac OS X 10.10.6", host.OSVersion)
+ assert.Equal(t, "Mac OS X 10.10.6", gotHost.OSVersion)
// uptime
- assert.Equal(t, 1730893*time.Second, host.Uptime)
+ assert.Equal(t, 1730893*time.Second, gotHost.Uptime)
// osquery_flags
- assert.Equal(t, uint(10), host.ConfigTLSRefresh)
- assert.Equal(t, uint(5), host.DistributedInterval)
- assert.Equal(t, uint(60), host.LoggerTLSPeriod)
+ assert.Equal(t, uint(10), gotHost.ConfigTLSRefresh)
+ assert.Equal(t, uint(5), gotHost.DistributedInterval)
+ assert.Equal(t, uint(60), gotHost.LoggerTLSPeriod)
+ host.HostName = "computer.local"
+ host.DetailUpdateTime = mockClock.Now()
mockClock.AddTime(1 * time.Minute)
// Now no detail queries should be required
- host, err = ds.AuthenticateHost(nodeKey)
- require.Nil(t, err)
- ctx = hostctx.NewContext(ctx, *host)
+ ctx = hostctx.NewContext(ctx, host)
queries, acc, err = svc.GetDistributedQueries(ctx)
assert.Nil(t, err)
assert.Len(t, queries, 0)
@@ -815,12 +841,6 @@ func TestDetailQueries(t *testing.T) {
// Advance clock and queries should exist again
mockClock.AddTime(1*time.Hour + 1*time.Minute)
- err = svc.SubmitDistributedQueryResults(ctx, kolide.OsqueryDistributedQueryResults{}, map[string]kolide.OsqueryStatus{})
- require.Nil(t, err)
- host, err = ds.AuthenticateHost(nodeKey)
- require.Nil(t, err)
-
- ctx = hostctx.NewContext(ctx, *host)
queries, acc, err = svc.GetDistributedQueries(ctx)
assert.Nil(t, err)
assert.Len(t, queries, len(detailQueries))
@@ -995,63 +1015,49 @@ func TestDistributedQueryResults(t *testing.T) {
}
func TestOrphanedQueryCampaign(t *testing.T) {
- ds, err := inmem.New(config.TestConfig())
- require.Nil(t, err)
-
- _, err = ds.NewAppConfig(&kolide.AppConfig{EnrollSecret: ""})
- require.Nil(t, err)
-
+ ds := new(mock.Store)
rs := pubsub.NewInmemQueryResults()
svc, err := newTestService(ds, rs)
require.Nil(t, err)
- ctx := context.Background()
+ ds.DistributedQueryCampaignFunc = func(id uint) (*kolide.DistributedQueryCampaign, error) {
+ return &kolide.DistributedQueryCampaign{ID: 1}, nil
+ }
+ ds.NewDistributedQueryExecutionFunc = func(*kolide.DistributedQueryExecution) (*kolide.DistributedQueryExecution, error) {
+ return nil, nil
+ }
- nodeKey, err := svc.EnrollAgent(ctx, "", "host123", nil)
- require.Nil(t, err)
-
- host, err := ds.AuthenticateHost(nodeKey)
- require.Nil(t, err)
-
- ctx = viewer.NewContext(context.Background(), viewer.Viewer{
- User: &kolide.User{
- ID: 0,
- },
- })
- q := "select year, month, day, hour, minutes, seconds from time"
- campaign, err := svc.NewDistributedQueryCampaign(ctx, q, []uint{}, []uint{})
- require.Nil(t, err)
-
- campaign.Status = kolide.QueryRunning
- err = ds.SaveDistributedQueryCampaign(campaign)
- require.Nil(t, err)
-
- queryKey := fmt.Sprintf("%s%d", hostDistributedQueryPrefix, campaign.ID)
+ savedCampaign := &kolide.DistributedQueryCampaign{}
+ ds.SaveDistributedQueryCampaignFunc = func(campaign *kolide.DistributedQueryCampaign) error {
+ savedCampaign = campaign
+ return nil
+ }
+ // Submit results
+ queryKey := hostDistributedQueryPrefix + "1"
expectedRows := []map[string]string{
- {
- "year": "2016",
- "month": "11",
- "day": "11",
- "hour": "6",
- "minutes": "12",
- "seconds": "10",
+ map[string]string{
+ "foo": "bar",
+ },
+ map[string]string{
+ "baz": "boom",
},
}
+ host := kolide.Host{HostName: "the fooer"}
results := map[string][]map[string]string{
queryKey: expectedRows,
}
- // Submit results
- ctx = hostctx.NewContext(context.Background(), *host)
+ ctx := context.Background()
+ ctx = hostctx.NewContext(context.Background(), host)
err = svc.SubmitDistributedQueryResults(ctx, results, map[string]kolide.OsqueryStatus{})
require.Nil(t, err)
- // The campaign should be set to completed because it is orphaned
- campaign, err = ds.DistributedQueryCampaign(campaign.ID)
- require.Nil(t, err)
- assert.Equal(t, kolide.QueryComplete, campaign.Status)
+ // Ensure that status is changed to completed when there is no listener for
+ // results.
+ require.NotNil(t, savedCampaign)
+ assert.Equal(t, kolide.QueryComplete, savedCampaign.Status)
}
func TestUpdateHostIntervals(t *testing.T) {
@@ -1184,9 +1190,6 @@ func setupOsqueryTests(t *testing.T) (kolide.Datastore, kolide.Service, *clock.M
ds, err := inmem.New(config.TestConfig())
require.Nil(t, err)
- _, err = ds.NewAppConfig(&kolide.AppConfig{EnrollSecret: ""})
- require.Nil(t, err)
-
mockClock := clock.NewMockClock()
svc, err := newTestServiceWithClock(ds, nil, mockClock)
require.Nil(t, err)
diff --git a/server/service/transport_appconfig.go b/server/service/transport_appconfig.go
index cd5b77c3f8..2ef2ed4018 100644
--- a/server/service/transport_appconfig.go
+++ b/server/service/transport_appconfig.go
@@ -15,3 +15,12 @@ func decodeModifyAppConfigRequest(ctx context.Context, r *http.Request) (interfa
}
return appConfigRequest{Payload: payload}, nil
}
+
+func decodeApplyEnrollSecretSpecRequest(ctx context.Context, r *http.Request) (interface{}, error) {
+ var req applyEnrollSecretSpecRequest
+ if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
+ return nil, err
+ }
+ return req, nil
+
+}