mirror of
https://github.com/empayre/fleet.git
synced 2024-11-06 08:55:24 +00:00
Add support for multiple enroll secrets (#2238)
- Support multiple enroll secrets - Record name of enroll secret used when host enrolls - Update fleetctl and UI to support these features
This commit is contained in:
parent
619e36755c
commit
c1aa8355cb
@ -24,6 +24,7 @@ type specGroup struct {
|
||||
Labels []*kolide.LabelSpec
|
||||
Options *kolide.OptionsSpec
|
||||
AppConfig *kolide.AppConfigPayload
|
||||
EnrollSecret *kolide.EnrollSecretSpec
|
||||
}
|
||||
|
||||
func specGroupFromBytes(b []byte) (*specGroup, error) {
|
||||
@ -91,6 +92,17 @@ func specGroupFromBytes(b []byte) (*specGroup, error) {
|
||||
}
|
||||
specs.AppConfig = appConfigSpec
|
||||
|
||||
case "enroll_secret":
|
||||
if specs.AppConfig != nil {
|
||||
return nil, errors.New("enroll_secret defined twice in the same file")
|
||||
}
|
||||
|
||||
var enrollSecretSpec *kolide.EnrollSecretSpec
|
||||
if err := yaml.Unmarshal(s.Spec, &enrollSecretSpec); err != nil {
|
||||
return nil, errors.Wrap(err, "unmarshaling enroll secret spec")
|
||||
}
|
||||
specs.EnrollSecret = enrollSecretSpec
|
||||
|
||||
default:
|
||||
return nil, errors.Errorf("unknown kind %q", s.Kind)
|
||||
}
|
||||
@ -181,6 +193,14 @@ func applyCommand() cli.Command {
|
||||
|
||||
}
|
||||
|
||||
if specs.EnrollSecret != nil {
|
||||
if err := fleet.ApplyEnrollSecretSpec(specs.EnrollSecret); err != nil {
|
||||
return errors.Wrap(err, "applying enroll secrets")
|
||||
}
|
||||
fmt.Printf("[+] applied enroll secrets\n")
|
||||
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
@ -392,8 +392,9 @@ func getOptionsCommand() cli.Command {
|
||||
|
||||
func getEnrollSecretCommand() cli.Command {
|
||||
return cli.Command{
|
||||
Name: "enroll-secret",
|
||||
Usage: "Retrieve the osquery enroll secret",
|
||||
Name: "enroll_secret",
|
||||
Aliases: []string{"enroll_secrets", "enroll-secret", "enroll-secrets"},
|
||||
Usage: "Retrieve the osquery enroll secrets",
|
||||
Flags: []cli.Flag{
|
||||
configFlag(),
|
||||
contextFlag(),
|
||||
@ -404,16 +405,23 @@ func getEnrollSecretCommand() cli.Command {
|
||||
return err
|
||||
}
|
||||
|
||||
settings, err := fleet.GetServerSettings()
|
||||
secrets, err := fleet.GetEnrollSecretSpec()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if settings == nil {
|
||||
return errors.New("error: server setting were nil")
|
||||
|
||||
spec := specGeneric{
|
||||
Kind: "enroll_secret",
|
||||
Version: kolide.ApiVersion,
|
||||
Spec: secrets,
|
||||
}
|
||||
|
||||
fmt.Println(*settings.EnrollSecret)
|
||||
b, err := yaml.Marshal(spec)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Print(string(b))
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
@ -255,7 +255,6 @@ spec:
|
||||
org_name: Example Org
|
||||
server_settings:
|
||||
kolide_server_url: https://fleet.example.org:8080
|
||||
osquery_enroll_secret: supersekretsecret
|
||||
smtp_settings:
|
||||
authentication_method: authmethod_plain
|
||||
authentication_type: authtype_username_password
|
||||
@ -291,3 +290,25 @@ The following options are available when configuring SMTP authentication:
|
||||
- `authmethod_cram_md5`
|
||||
- `authmethod_login`
|
||||
- `authmethod_plain`
|
||||
|
||||
## Enroll Secrets
|
||||
|
||||
The following file shows how to configure enroll secrets. Note that secrets can be changed or made inactive, but not deleted. Hosts may not enroll with inactive secrets.
|
||||
|
||||
The name of the enroll secret used to authenticate is stored with the host and is included with API results.
|
||||
|
||||
```yaml
|
||||
apiVersion: v1
|
||||
kind: enroll_secret
|
||||
spec:
|
||||
secrets:
|
||||
- active: true
|
||||
name: default
|
||||
secret: RzTlxPvugG4o4O5IKS/HqEDJUmI1hwBoffff
|
||||
- active: true
|
||||
name: new_one
|
||||
secret: reallyworks
|
||||
- active: false
|
||||
name: inactive_secret
|
||||
secret: thissecretwontwork!
|
||||
```
|
||||
|
@ -20,7 +20,8 @@ To directly execute the launcher binary without having to mess with packages, in
|
||||
|
||||
- `--hostname`: the hostname of the gRPC server for your environment
|
||||
- `--root_directory`: the location of the local database, pidfiles, etc.
|
||||
- `--enroll_secret`: the enroll secret you generated above for your environment
|
||||
- `--enroll_secret`: the enroll secret to authenticate hosts with Fleet
|
||||
(retrieve from Fleet UI or `fleetctl get enroll_secret`)
|
||||
|
||||
```
|
||||
./build/launcher \
|
||||
@ -45,7 +46,8 @@ $ ./build/package-builder make \
|
||||
As you can see, to generate a Launcher package, you need only call `package-builder make` with two command-line arguments:
|
||||
|
||||
- `--hostname`: the hostname of the gRPC server for your environment
|
||||
- `--enroll_secret`: the enroll secret you generated above for your environment
|
||||
- `--enroll_secret`: the enroll secret to authenticate hosts with Fleet
|
||||
(retrieve from Fleet UI or `fleetctl get enroll_secret`)
|
||||
|
||||
You can also add the `--mac_package_signing_key` flag to define the name of the macOS package signing key name that you'd like to use to sign the macOS packages. For example:
|
||||
|
||||
@ -61,7 +63,7 @@ You can find various ways to install osquery on a variety of platforms at https:
|
||||
|
||||
#### Set an environment variable with an agent enrollment secret
|
||||
|
||||
The enrollment secret is a value that osquery uses to ensure a level of confidence that the host running osquery is actually a host that you would like to hear from. There are a few ways you can set the enrollment secret on the hosts which you control. You can either set the value as:
|
||||
The enrollment secret is a value that osquery provides to authenticate with Fleet. There are a few ways you can set the enrollment secret on the hosts which you control. You can either set the value as:
|
||||
|
||||
- an value of an environment variable (a common name is `OSQUERY_ENROLL_SECRET`)
|
||||
- the content of a local file (a common path is `/etc/osquery/enrollment_secret`)
|
||||
@ -69,7 +71,10 @@ The enrollment secret is a value that osquery uses to ensure a level of confiden
|
||||
The value of the environment variable or content of the file should be a secret shared between the osqueryd client and the Fleet server. This is basically osqueryd's passphrase which it uses to authenticate with Fleet, convincing Fleet that it is actually one of your hosts. The passphrase could be whatever you'd like, but it would be prudent to have the passphrase long, complex, mixed-case, etc. When you launch the Fleet server, you should specify this same value.
|
||||
|
||||
If you use an environment variable for this, you can specify it with the `--enroll_secret_env` flag when you launch osqueryd. If you use a local file for this, you can specify it's path with the `--enroll_secret_path` flag.
|
||||
s
|
||||
|
||||
To retrieve the enroll secret, use the "Add New Host" dialog in the Fleet UI or
|
||||
`fleetctl get enroll_secret`).
|
||||
|
||||
If your organization has a robust internal public key infrastructure (PKI) and you already deploy TLS client certificates to each host to uniquely identify them, then osquery supports an advanced authentication mechanism which takes advantage of this. Fleet can be fronted with a proxy that will perform the TLS client authentication.
|
||||
|
||||
#### Deploy the TLS certificate that osquery will use to communicate with Fleet
|
||||
@ -140,3 +145,11 @@ Note that osqueryd requires a full certificate chain, even for certificates whic
|
||||
Once you've configured the `config.mk` file with the correct variables, you can run `make` in the `tools/mac` directory. Running `make` will create a new `kolide-enroll.pkg` file which you can import into your software repository and deploy to your mac fleet.
|
||||
|
||||
The enrollment package must installed after the osqueryd package, and will install a LaunchDaemon to keep the osqueryd process running.
|
||||
|
||||
## Multiple Enroll Secrets
|
||||
|
||||
Multiple enroll secrets can be set to allow different groups of hosts to
|
||||
authenticate with Fleet. When a host enrolls, the corresponding enroll secret is
|
||||
recorded and can be used to segment hosts.
|
||||
|
||||
To set the enroll secret, use the `fleetctl` tool to apply an [enroll secret spec](../cli/file-format.md#enroll-secrets)
|
||||
|
14
examples/config-many-files/enroll-secret.yml
Normal file
14
examples/config-many-files/enroll-secret.yml
Normal file
@ -0,0 +1,14 @@
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: enroll_secret
|
||||
spec:
|
||||
secrets:
|
||||
- active: true
|
||||
name: default
|
||||
secret: RzTlxPvugG4o4O5IKS/HqEDJUmI1hwBoffff
|
||||
- active: true
|
||||
name: new_one
|
||||
secret: reallyworks
|
||||
- active: false
|
||||
name: inactive_secret
|
||||
secret: thissecretwontwork!
|
@ -69,6 +69,20 @@ spec:
|
||||
3600: "SELECT total_seconds AS uptime FROM uptime"
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: enroll_secret
|
||||
spec:
|
||||
secrets:
|
||||
- active: true
|
||||
name: default
|
||||
secret: RzTlxPvugG4o4O5IKS/HqEDJUmI1hwBoffff
|
||||
- active: true
|
||||
name: new_one
|
||||
secret: reallyworks
|
||||
- active: false
|
||||
name: inactive_secret
|
||||
secret: thissecretwontwork!
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: label
|
||||
spec:
|
||||
name: pending_updates
|
||||
|
@ -6,7 +6,7 @@ import classnames from 'classnames';
|
||||
|
||||
import { authToken } from 'utilities/local';
|
||||
import { fetchCurrentUser } from 'redux/nodes/auth/actions';
|
||||
import { getConfig } from 'redux/nodes/app/actions';
|
||||
import { getConfig, getEnrollSecret } from 'redux/nodes/app/actions';
|
||||
import userInterface from 'interfaces/user';
|
||||
|
||||
export class App extends Component {
|
||||
@ -32,6 +32,8 @@ export class App extends Component {
|
||||
if (user) {
|
||||
dispatch(getConfig())
|
||||
.catch(() => false);
|
||||
dispatch(getEnrollSecret())
|
||||
.catch(() => false);
|
||||
}
|
||||
|
||||
return false;
|
||||
@ -43,6 +45,8 @@ export class App extends Component {
|
||||
if (user && this.props.user !== user) {
|
||||
dispatch(getConfig())
|
||||
.catch(() => false);
|
||||
dispatch(getEnrollSecret())
|
||||
.catch(() => false);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,122 @@
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import Button from 'components/buttons/Button';
|
||||
import enrollSecretInterface from 'interfaces/enroll_secret';
|
||||
import InputField from 'components/forms/fields/InputField';
|
||||
import Icon from 'components/icons/Icon';
|
||||
import { stringToClipboard } from 'utilities/copy_text';
|
||||
|
||||
const baseClass = 'enroll-secrets';
|
||||
|
||||
class EnrollSecretRow extends Component {
|
||||
static propTypes = {
|
||||
name: PropTypes.string.isRequired,
|
||||
secret: PropTypes.string.isRequired,
|
||||
}
|
||||
|
||||
constructor (props) {
|
||||
super(props);
|
||||
this.state = { showSecret: false, copyMessage: '' };
|
||||
}
|
||||
|
||||
onCopySecret = (evt) => {
|
||||
evt.preventDefault();
|
||||
|
||||
const { secret } = this.props;
|
||||
|
||||
stringToClipboard(secret)
|
||||
.then(() => this.setState({ copyMessage: '(copied)' }))
|
||||
.catch(() => this.setState({ copyMessage: '(copy failed)' }));
|
||||
|
||||
// Clear message after 1 second
|
||||
setTimeout(() => this.setState({ copyMessage: '' }), 1000);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
onToggleSecret = (evt) => {
|
||||
evt.preventDefault();
|
||||
|
||||
const { showSecret } = this.state;
|
||||
|
||||
this.setState({ showSecret: !showSecret });
|
||||
return false;
|
||||
};
|
||||
|
||||
renderLabel = () => {
|
||||
const { name } = this.props;
|
||||
const { showSecret, copyMessage } = this.state;
|
||||
const { onCopySecret, onToggleSecret } = this;
|
||||
|
||||
return (
|
||||
<span>
|
||||
{name}
|
||||
<span className="buttons">
|
||||
{copyMessage && <span>{`${copyMessage} `}</span>}
|
||||
<Button
|
||||
variant="unstyled"
|
||||
className={`${baseClass}__secret-copy-icon`}
|
||||
onClick={onCopySecret}
|
||||
>
|
||||
<Icon name="clipboard" />
|
||||
</Button>
|
||||
<a
|
||||
href="#showSecret"
|
||||
onClick={onToggleSecret}
|
||||
className={`${baseClass}__show-secret`}
|
||||
>
|
||||
{showSecret ? 'Hide' : 'Show'}
|
||||
</a>
|
||||
</span>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
render () {
|
||||
const { secret } = this.props;
|
||||
const { showSecret } = this.state;
|
||||
const { renderLabel } = this;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<InputField
|
||||
disabled
|
||||
inputWrapperClass={`${baseClass}__secret-input`}
|
||||
name="osqueryd-secret"
|
||||
label={renderLabel()}
|
||||
type={showSecret ? 'text' : 'password'}
|
||||
value={secret}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class EnrollSecretTable extends Component {
|
||||
static propTypes = {
|
||||
secrets: enrollSecretInterface.isRequired,
|
||||
}
|
||||
|
||||
render() {
|
||||
const { secrets } = this.props;
|
||||
const activeSecrets = secrets.filter(s => s.active);
|
||||
|
||||
if (activeSecrets.length === 0) {
|
||||
return (<div className={baseClass}><em>No active enroll secrets.</em></div>);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={baseClass}>
|
||||
{activeSecrets.map(({ name, secret }) =>
|
||||
<EnrollSecretRow key={name} name={name} secret={secret} />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default EnrollSecretTable;
|
||||
export { EnrollSecretRow };
|
@ -0,0 +1,76 @@
|
||||
import React from 'react';
|
||||
import expect, { spyOn } from 'expect';
|
||||
import { shallow, mount } from 'enzyme';
|
||||
|
||||
import * as copy from 'utilities/copy_text';
|
||||
import EnrollSecretTable, { EnrollSecretRow } from 'components/config/EnrollSecretTable/EnrollSecretTable';
|
||||
|
||||
describe('EnrollSecretTable', () => {
|
||||
const defaultProps = {
|
||||
secrets: [
|
||||
{ name: 'foo', secret: 'foo_secret', active: true },
|
||||
{ name: 'bar', secret: 'bar_secret', active: true },
|
||||
{ name: 'inactive', secret: 'inactive', active: false },
|
||||
],
|
||||
};
|
||||
|
||||
it('renders properly filtered rows', () => {
|
||||
const table = shallow(<EnrollSecretTable {...defaultProps} />);
|
||||
expect(table.find('EnrollSecretRow').length).toEqual(2);
|
||||
});
|
||||
|
||||
it('renders text when empty', () => {
|
||||
const table = shallow(<EnrollSecretTable secrets={[]} />);
|
||||
expect(table.find('EnrollSecretRow').length).toEqual(0);
|
||||
expect(table.find('div').text()).toEqual('No active enroll secrets.');
|
||||
});
|
||||
});
|
||||
|
||||
describe('EnrollSecretRow', () => {
|
||||
const defaultProps = { name: 'foo', secret: 'bar' };
|
||||
it('should hide secret by default', () => {
|
||||
const row = mount(<EnrollSecretRow {...defaultProps} />);
|
||||
const inputField = row.find('InputField').find('input');
|
||||
expect(inputField.prop('type')).toEqual('password');
|
||||
});
|
||||
|
||||
it('should show secret when enabled', () => {
|
||||
const row = mount(<EnrollSecretRow {...defaultProps} />);
|
||||
row.setState({ showSecret: true });
|
||||
const inputField = row.find('InputField').find('input');
|
||||
expect(inputField.prop('type')).toEqual('text');
|
||||
});
|
||||
|
||||
it('should change input type when show/hide is clicked', () => {
|
||||
const row = mount(<EnrollSecretRow {...defaultProps} />);
|
||||
|
||||
let inputField = row.find('InputField').find('input');
|
||||
expect(inputField.prop('type')).toEqual('password');
|
||||
|
||||
const showLink = row.find('.enroll-secrets__show-secret');
|
||||
expect(showLink.text()).toEqual('Show');
|
||||
|
||||
showLink.simulate('click');
|
||||
|
||||
inputField = row.find('InputField').find('input');
|
||||
expect(inputField.prop('type')).toEqual('text');
|
||||
|
||||
const hideLink = row.find('.enroll-secrets__show-secret');
|
||||
expect(hideLink.text()).toEqual('Hide');
|
||||
|
||||
hideLink.simulate('click');
|
||||
|
||||
inputField = row.find('InputField').find('input');
|
||||
expect(inputField.prop('type')).toEqual('password');
|
||||
});
|
||||
|
||||
it('should call copy when button is clicked', () => {
|
||||
const row = mount(<EnrollSecretRow {...defaultProps} />);
|
||||
const spy = spyOn(copy, 'stringToClipboard').andReturn(Promise.resolve());
|
||||
|
||||
const copyLink = row.find('.enroll-secrets__secret-copy-icon').find('Button');
|
||||
copyLink.simulate('click');
|
||||
|
||||
expect(spy).toHaveBeenCalledWith(defaultProps.secret);
|
||||
});
|
||||
});
|
@ -0,0 +1,9 @@
|
||||
.enroll-secrets {
|
||||
max-height: 10em;
|
||||
overflow: auto;
|
||||
|
||||
|
||||
.buttons {
|
||||
float: right;
|
||||
}
|
||||
}
|
1
frontend/components/config/EnrollSecretTable/index.js
Normal file
1
frontend/components/config/EnrollSecretTable/index.js
Normal file
@ -0,0 +1 @@
|
||||
export default from './EnrollSecretTable';
|
@ -10,7 +10,7 @@ class FormField extends Component {
|
||||
className: PropTypes.string,
|
||||
error: PropTypes.string,
|
||||
hint: PropTypes.oneOfType([PropTypes.array, PropTypes.node, PropTypes.string]),
|
||||
label: PropTypes.oneOfType([PropTypes.array, PropTypes.string]),
|
||||
label: PropTypes.oneOfType([PropTypes.array, PropTypes.string, PropTypes.node]),
|
||||
name: PropTypes.string,
|
||||
type: PropTypes.string,
|
||||
};
|
||||
|
@ -6,6 +6,8 @@ import Checkbox from 'components/forms/fields/Checkbox';
|
||||
import Dropdown from 'components/forms/fields/Dropdown';
|
||||
import Form from 'components/forms/Form';
|
||||
import formFieldInterface from 'interfaces/form_field';
|
||||
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 OrgLogoIcon from 'components/icons/OrgLogoIcon';
|
||||
@ -66,6 +68,7 @@ class AppConfigForm extends Component {
|
||||
host_expiry_window: formFieldInterface.isRequired,
|
||||
live_query_disabled: formFieldInterface.isRequired,
|
||||
}).isRequired,
|
||||
enrollSecret: enrollSecretInterface.isRequired,
|
||||
handleSubmit: PropTypes.func.isRequired,
|
||||
smtpConfigured: PropTypes.bool.isRequired,
|
||||
};
|
||||
@ -73,7 +76,7 @@ class AppConfigForm extends Component {
|
||||
constructor (props) {
|
||||
super(props);
|
||||
|
||||
this.state = { revealSecret: false, showAdvancedOptions: false };
|
||||
this.state = { showAdvancedOptions: false };
|
||||
}
|
||||
|
||||
onToggleAdvancedOptions = (evt) => {
|
||||
@ -86,16 +89,6 @@ class AppConfigForm extends Component {
|
||||
return false;
|
||||
}
|
||||
|
||||
onToggleRevealSecret = (evt) => {
|
||||
evt.preventDefault();
|
||||
|
||||
const { revealSecret } = this.state;
|
||||
|
||||
this.setState({ revealSecret: !revealSecret });
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
renderAdvancedOptions = () => {
|
||||
const { fields } = this.props;
|
||||
const { showAdvancedOptions } = this.state;
|
||||
@ -158,9 +151,9 @@ class AppConfigForm extends Component {
|
||||
}
|
||||
|
||||
render () {
|
||||
const { fields, handleSubmit, smtpConfigured } = this.props;
|
||||
const { onToggleAdvancedOptions, onToggleRevealSecret, renderAdvancedOptions, renderSmtpSection } = this;
|
||||
const { revealSecret, showAdvancedOptions } = this.state;
|
||||
const { fields, handleSubmit, smtpConfigured, enrollSecret } = this.props;
|
||||
const { onToggleAdvancedOptions, renderAdvancedOptions, renderSmtpSection } = this;
|
||||
const { showAdvancedOptions } = this.state;
|
||||
|
||||
return (
|
||||
<form className={baseClass} onSubmit={handleSubmit}>
|
||||
@ -324,16 +317,12 @@ class AppConfigForm extends Component {
|
||||
</div>
|
||||
</div>
|
||||
<div className={`${baseClass}__section`}>
|
||||
<h2>Osquery Enrollment Secret</h2>
|
||||
<h2>Osquery Enrollment Secrets</h2>
|
||||
<div className={`${baseClass}__inputs`}>
|
||||
<p className={`${baseClass}__enroll-secret-label`}>
|
||||
This is the secret that you use to enroll osquery agents with Fleet:
|
||||
<Button variant="unstyled" onClick={onToggleRevealSecret}>Reveal Secret</Button>
|
||||
Manage secrets with <code>fleetctl</code>. Active secrets:
|
||||
</p>
|
||||
<InputField
|
||||
{...fields.osquery_enroll_secret}
|
||||
type={revealSecret ? 'input' : 'password'}
|
||||
/>
|
||||
<EnrollSecretTable secrets={enrollSecret} />
|
||||
</div>
|
||||
</div>
|
||||
<div className={`${baseClass}__section`}>
|
||||
|
@ -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(<AppConfigForm {...defaultProps} />);
|
||||
|
||||
@ -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);
|
||||
|
@ -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.
|
||||
</p>
|
||||
|
||||
<div className={`${baseClass}__manual-install-header`}>
|
||||
<Icon name="wrench-hand" />
|
||||
<h2>Manual Install</h2>
|
||||
<h3>
|
||||
Fully Customize Your <strong>Osquery</strong> Installation
|
||||
</h3>
|
||||
</div>
|
||||
|
||||
<div className={`${baseClass}__manual-install-content`}>
|
||||
<ol className={`${baseClass}__install-steps`}>
|
||||
<li>
|
||||
@ -86,38 +41,14 @@ class AddHostModal extends Component {
|
||||
Fleet / Osquery - Install Docs <Icon name="external-link" />
|
||||
</a>
|
||||
</h4>
|
||||
<p>
|
||||
In order to install <strong>osquery</strong> on a client you
|
||||
will need the following information:
|
||||
</p>
|
||||
</li>
|
||||
<li>
|
||||
<h4>Retrieve Osquery Enroll Secret</h4>
|
||||
<h4>Osquery Enroll Secret</h4>
|
||||
<p>
|
||||
The following is your enroll secret:
|
||||
<a
|
||||
href="#revealSecret"
|
||||
onClick={toggleSecret}
|
||||
className={`${baseClass}__reveal-secret`}
|
||||
>
|
||||
{revealSecret ? 'Hide' : 'Reveal'} Secret
|
||||
</a>
|
||||
Provide osquery with one of the following active enroll secrets:
|
||||
</p>
|
||||
<div className={`${baseClass}__secret-wrapper`}>
|
||||
<InputField
|
||||
disabled
|
||||
inputWrapperClass={`${baseClass}__secret-input`}
|
||||
name="osqueryd-secret"
|
||||
type={revealSecret ? 'text' : 'password'}
|
||||
value={osqueryEnrollSecret}
|
||||
/>
|
||||
<Button
|
||||
variant="unstyled"
|
||||
className={`${baseClass}__secret-copy-icon`}
|
||||
onClick={onCopySecret(`.${baseClass}__secret-input`)}
|
||||
>
|
||||
<Icon name="clipboard" />
|
||||
</Button>
|
||||
<EnrollSecretTable secrets={enrollSecret} />
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
|
@ -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(<AddHostModal dispatch={noop} onReturnToApp={noop} />);
|
||||
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');
|
||||
});
|
||||
});
|
10
frontend/interfaces/enroll_secret.js
Normal file
10
frontend/interfaces/enroll_secret.js
Normal file
@ -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,
|
||||
}),
|
||||
);
|
@ -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);
|
||||
|
@ -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}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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`}
|
||||
>
|
||||
<AddHostModal
|
||||
dispatch={dispatch}
|
||||
onFetchCertificate={onFetchCertificate}
|
||||
onReturnToApp={toggleAddHostModal}
|
||||
osqueryEnrollSecret={osqueryEnrollSecret}
|
||||
enrollSecret={enrollSecret}
|
||||
/>
|
||||
</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,
|
||||
|
@ -56,6 +56,7 @@ describe('ManageHostsPage - component', () => {
|
||||
loadingLabels: false,
|
||||
selectedOsqueryTable: stubbedOsqueryTable,
|
||||
statusLabels: {},
|
||||
enrollSecret: [],
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
|
@ -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;
|
||||
});
|
||||
};
|
||||
};
|
||||
|
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -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.';
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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,
|
||||
nodeKey: "key0",
|
||||
},
|
||||
1: {uuid: "B998C0EB-38CE-43B1-A743-FBD7A5C9513B",
|
||||
hostname: "mail.kolide.co",
|
||||
platform: "linux",
|
||||
nodeKeySize: 10,
|
||||
nodeKey: "key1",
|
||||
},
|
||||
2: {uuid: "008F0688-5311-4C59-86EE-00C2D6FC3EC2",
|
||||
hostname: "home.kolide.co",
|
||||
platform: "darwin",
|
||||
nodeKeySize: 25,
|
||||
nodeKey: "key2",
|
||||
},
|
||||
3: {uuid: "uuid123",
|
||||
hostname: "fakehostname",
|
||||
platform: "darwin",
|
||||
nodeKeySize: 1,
|
||||
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)
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
@ -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"`
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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()
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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")
|
||||
|
@ -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()
|
||||
}
|
||||
|
@ -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
|
||||
}{
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user