From a83a26b2790ddd9fcc38a611f8aa63d371b43468 Mon Sep 17 00:00:00 2001 From: billcobbler Date: Mon, 13 Jan 2020 18:53:04 -0600 Subject: [PATCH] Add ability to disable live queries (#2167) - Add toggle to disable live queries in advanced settings - Add new live query status endpoint (checks for disabled via config and Redis health) - Update QueryPage UI to use new live query status endpoint Implements #2140 --- .../admin/AppConfigForm/AppConfigForm.jsx | 4 ++ .../AppConfigForm/AppConfigForm.tests.jsx | 7 ++- frontend/interfaces/config.js | 1 + frontend/kolide/endpoints.js | 1 + frontend/kolide/helpers.js | 2 +- frontend/kolide/helpers.tests.js | 1 + frontend/kolide/status.js | 6 +++ .../pages/queries/QueryPage/QueryPage.jsx | 20 ++++----- frontend/test/stubs.js | 2 + server/datastore/mysql/app_configs.go | 9 ++-- ...0130734_AddLiveQueryDisabledToAppConfig.go | 25 +++++++++++ server/kolide/app.go | 8 +++- server/kolide/status.go | 4 ++ server/mock/datastore.go | 2 + server/mock/datastore_query_results.go | 43 +++++++++++++++++++ server/pubsub/redis_query_results.go | 6 ++- server/service/endpoint_appconfig.go | 10 +++-- server/service/endpoint_appconfig_test.go | 7 ++- server/service/endpoint_status.go | 16 +++++-- server/service/handler.go | 5 +++ server/service/service_appconfig.go | 3 ++ server/service/service_appconfig_test.go | 4 +- server/service/service_campaigns.go | 8 +++- server/service/service_osquery_test.go | 16 ++++++- server/service/service_status.go | 19 +++++++- 25 files changed, 196 insertions(+), 33 deletions(-) create mode 100644 server/datastore/mysql/migrations/tables/20191220130734_AddLiveQueryDisabledToAppConfig.go create mode 100644 server/mock/datastore_query_results.go diff --git a/frontend/components/forms/admin/AppConfigForm/AppConfigForm.jsx b/frontend/components/forms/admin/AppConfigForm/AppConfigForm.jsx index 349d8f9f62..1d62936bf9 100644 --- a/frontend/components/forms/admin/AppConfigForm/AppConfigForm.jsx +++ b/frontend/components/forms/admin/AppConfigForm/AppConfigForm.jsx @@ -27,6 +27,7 @@ const formFields = [ 'org_logo_url', 'org_name', 'osquery_enroll_secret', 'password', 'port', 'sender_address', 'server', 'user_name', 'verify_ssl_certs', 'idp_name', 'entity_id', 'issuer_uri', 'idp_image_url', 'metadata', 'metadata_url', 'enable_sso', 'enable_smtp', 'host_expiry_enabled', 'host_expiry_window', + 'live_query_disabled', ]; const Header = ({ showAdvancedOptions }) => { const CaratIcon = ; @@ -63,6 +64,7 @@ class AppConfigForm extends Component { enable_smtp: formFieldInterface.isRequired, host_expiry_enabled: formFieldInterface.isRequired, host_expiry_window: formFieldInterface.isRequired, + live_query_disabled: formFieldInterface.isRequired, }).isRequired, handleSubmit: PropTypes.func.isRequired, smtpConfigured: PropTypes.bool.isRequired, @@ -111,6 +113,7 @@ class AppConfigForm extends Component { + @@ -120,6 +123,7 @@ class AppConfigForm extends Component {

Enable STARTTLS - Detects if STARTTLS is enabled in your SMTP server and starts to use it. (Default: On)

Host Expiry - When enabled, allows automatic cleanup of hosts that have not communicated with Fleet in some number of days. (Default: Off)

Host Expiry Window - If a host has not communicated with Fleet in the specified number of days, it will be removed.

+

Disable Live Queries - When enabled, disables the ability to run live queries (ad hoc queries executed via the UI or fleetctl). (Default: Off)

); diff --git a/frontend/components/forms/admin/AppConfigForm/AppConfigForm.tests.jsx b/frontend/components/forms/admin/AppConfigForm/AppConfigForm.tests.jsx index 298e345e29..2e5bce61cd 100644 --- a/frontend/components/forms/admin/AppConfigForm/AppConfigForm.tests.jsx +++ b/frontend/components/forms/admin/AppConfigForm/AppConfigForm.tests.jsx @@ -86,7 +86,7 @@ describe('AppConfigForm - form', () => { it('renders advanced options when "Advanced Options" is clicked', () => { expect(form.find({ name: 'domain' }).hostNodes().length).toEqual(1); - expect(form.find('Slider').length).toEqual(3); + expect(form.find('Slider').length).toEqual(4); }); it('disables host expiry window by default', () => { @@ -102,5 +102,10 @@ describe('AppConfigForm - form', () => { const inputElement = InputField.find('input'); expect(inputElement.hasClass('input-field--disabled')).toBe(false); }); + + it('renders live query disabled input', () => { + form.find({ name: 'live_query_disabled' }); + expect(form.length).toEqual(1); + }); }); }); diff --git a/frontend/interfaces/config.js b/frontend/interfaces/config.js index 669393b9d1..90f5ce853b 100644 --- a/frontend/interfaces/config.js +++ b/frontend/interfaces/config.js @@ -1,6 +1,7 @@ import PropTypes from 'prop-types'; export default PropTypes.shape({ + live_query_disabled: PropTypes.bool, authentication_method: PropTypes.string, authentication_type: PropTypes.string, configured: PropTypes.bool, diff --git a/frontend/kolide/endpoints.js b/frontend/kolide/endpoints.js index 0d3f4f57f3..69ab871f6f 100644 --- a/frontend/kolide/endpoints.js +++ b/frontend/kolide/endpoints.js @@ -34,5 +34,6 @@ export default { return `/v1/kolide/users/${id}/admin`; }, SSO: '/v1/kolide/sso', + STATUS_LIVE_QUERY: '/v1/kolide/status/live_query', STATUS_RESULT_STORE: '/v1/kolide/status/result_store', }; diff --git a/frontend/kolide/helpers.js b/frontend/kolide/helpers.js index 07d37dcb03..fcc575f372 100644 --- a/frontend/kolide/helpers.js +++ b/frontend/kolide/helpers.js @@ -74,7 +74,7 @@ const filterTarget = (targetType) => { export const formatConfigDataForServer = (config) => { const orgInfoAttrs = pick(config, ['org_logo_url', 'org_name']); - const serverSettingsAttrs = pick(config, ['kolide_server_url', 'osquery_enroll_secret']); + const serverSettingsAttrs = pick(config, ['kolide_server_url', 'osquery_enroll_secret', 'live_query_disabled']); const smtpSettingsAttrs = pick(config, [ 'authentication_method', 'authentication_type', 'domain', 'enable_ssl_tls', 'enable_start_tls', 'password', 'port', 'sender_address', 'server', 'user_name', 'verify_ssl_certs', diff --git a/frontend/kolide/helpers.tests.js b/frontend/kolide/helpers.tests.js index bc29d800d4..5aae3b753c 100644 --- a/frontend/kolide/helpers.tests.js +++ b/frontend/kolide/helpers.tests.js @@ -38,6 +38,7 @@ describe('Kolide API - helpers', () => { enable_start_tls: true, host_expiry_enabled: false, host_expiry_window: 0, + live_query_disabled: false, }; it('splits config into categories for the server', () => { diff --git a/frontend/kolide/status.js b/frontend/kolide/status.js index 50fe25c872..2ffd4dbefd 100644 --- a/frontend/kolide/status.js +++ b/frontend/kolide/status.js @@ -6,6 +6,12 @@ export default (client) => { const { STATUS_RESULT_STORE } = endpoints; const endpoint = client.baseURL + STATUS_RESULT_STORE; + return client.authenticatedGet(endpoint); + }, + live_query: () => { + const { STATUS_LIVE_QUERY } = endpoints; + const endpoint = client.baseURL + STATUS_LIVE_QUERY; + return client.authenticatedGet(endpoint); }, }; diff --git a/frontend/pages/queries/QueryPage/QueryPage.jsx b/frontend/pages/queries/QueryPage/QueryPage.jsx index e9579cfd6c..458f28a67d 100644 --- a/frontend/pages/queries/QueryPage/QueryPage.jsx +++ b/frontend/pages/queries/QueryPage/QueryPage.jsx @@ -93,8 +93,8 @@ export class QueryPage extends Component { dispatch(hostActions.loadAll()); } - Kolide.status.result_store().catch((response) => { - this.setState({ resultStoreError: response.message.errors[0].reason }); + Kolide.status.live_query().catch((response) => { + this.setState({ liveQueryError: response.message.errors[0].reason }); }); helpers.selectHosts(dispatch, { @@ -451,14 +451,14 @@ export class QueryPage extends Component { return false; } - renderResultStoreWarning = () => { - const { resultStoreError } = this.state; + renderLiveQueryWarning = () => { + const { liveQueryError } = this.state; - if (!resultStoreError) { + if (!liveQueryError) { return false; } - const message = `Live query disabled due to Redis error: ${resultStoreError}`; + const message = `Live query disabled due to error: ${liveQueryError}`; return ( @@ -505,7 +505,7 @@ export class QueryPage extends Component { renderTargetsInput = () => { const { onFetchTargets, onRunQuery, onStopQuery, onTargetSelect } = this; - const { campaign, queryIsRunning, targetsCount, targetsError, runQueryMilliseconds, resultStoreError } = this.state; + const { campaign, queryIsRunning, targetsCount, targetsError, runQueryMilliseconds, liveQueryError } = this.state; const { selectedTargets } = this.props; return ( @@ -520,7 +520,7 @@ export class QueryPage extends Component { selectedTargets={selectedTargets} targetsCount={targetsCount} queryTimerMilliseconds={runQueryMilliseconds} - disableRun={resultStoreError !== undefined} + disableRun={liveQueryError !== undefined} /> ); } @@ -536,7 +536,7 @@ export class QueryPage extends Component { onUpdateQuery, renderResultsTable, renderTargetsInput, - renderResultStoreWarning, + renderLiveQueryWarning, } = this; const { queryIsRunning } = this.state; const { @@ -569,7 +569,7 @@ export class QueryPage extends Component { title={title} /> - {renderResultStoreWarning()} + {renderLiveQueryWarning()} {renderTargetsInput()} {renderResultsTable()} diff --git a/frontend/test/stubs.js b/frontend/test/stubs.js index 5c90248cf3..9bc03bb303 100644 --- a/frontend/test/stubs.js +++ b/frontend/test/stubs.js @@ -13,6 +13,7 @@ export const configStub = { }, server_settings: { kolide_server_url: '', + live_query_disabled: false, }, smtp_settings: { configured: false, @@ -52,6 +53,7 @@ export const flatConfigStub = { enable_start_tls: true, host_expiry_enabled: false, host_expiry_window: 0, + live_query_disabled: false, }; export const hostStub = { diff --git a/server/datastore/mysql/app_configs.go b/server/datastore/mysql/app_configs.go index 0b9e082b22..7d06b2c5ce 100644 --- a/server/datastore/mysql/app_configs.go +++ b/server/datastore/mysql/app_configs.go @@ -117,9 +117,10 @@ func (d *Datastore) SaveAppConfig(info *kolide.AppConfig) error { fim_interval, fim_file_accesses, host_expiry_enabled, - host_expiry_window + host_expiry_window, + live_query_disabled ) - VALUES( 1, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ? ) + VALUES( 1, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ? ) ON DUPLICATE KEY UPDATE org_name = VALUES(org_name), org_logo_url = VALUES(org_logo_url), @@ -147,7 +148,8 @@ func (d *Datastore) SaveAppConfig(info *kolide.AppConfig) error { fim_interval = VALUES(fim_interval), fim_file_accesses = VALUES(fim_file_accesses), host_expiry_enabled = VALUES(host_expiry_enabled), - host_expiry_window = VALUES(host_expiry_window) + host_expiry_window = VALUES(host_expiry_window), + live_query_disabled = VALUES(live_query_disabled) ` _, err = d.db.Exec(insertStatement, @@ -178,6 +180,7 @@ func (d *Datastore) SaveAppConfig(info *kolide.AppConfig) error { info.FIMFileAccesses, info.HostExpiryEnabled, info.HostExpiryWindow, + info.LiveQueryDisabled, ) return err diff --git a/server/datastore/mysql/migrations/tables/20191220130734_AddLiveQueryDisabledToAppConfig.go b/server/datastore/mysql/migrations/tables/20191220130734_AddLiveQueryDisabledToAppConfig.go new file mode 100644 index 0000000000..77bc7bc2a7 --- /dev/null +++ b/server/datastore/mysql/migrations/tables/20191220130734_AddLiveQueryDisabledToAppConfig.go @@ -0,0 +1,25 @@ +package tables + +import ( + "database/sql" +) + +func init() { + MigrationClient.AddMigration(Up20191220130734, Down20191220130734) +} + +func Up20191220130734(tx *sql.Tx) error { + _, err := tx.Exec( + "ALTER TABLE `app_configs` " + + "ADD COLUMN `live_query_disabled` TINYINT(1) NOT NULL DEFAULT FALSE;", + ) + return err +} + +func Down20191220130734(tx *sql.Tx) error { + _, err := tx.Exec( + "ALTER TABLE `app_configs` " + + "DROP COLUMN `live_query_disabled`;", + ) + return err +} diff --git a/server/kolide/app.go b/server/kolide/app.go index 1e9c24cf8d..8531afc9a2 100644 --- a/server/kolide/app.go +++ b/server/kolide/app.go @@ -140,6 +140,9 @@ type AppConfig struct { HostExpiryEnabled bool `db:"host_expiry_enabled"` // HostExpiryWindow defines a number in days after which a host will be removed if it has not communicated with Fleet. HostExpiryWindow int `db:"host_expiry_window"` + + // LiveQueryDisabled defines whether live queries are disabled. + LiveQueryDisabled bool `db:"live_query_disabled"` } // ModifyAppConfigRequest contains application configuration information @@ -228,8 +231,9 @@ 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"` + KolideServerURL *string `json:"kolide_server_url,omitempty"` + EnrollSecret *string `json:"osquery_enroll_secret,omitempty"` + LiveQueryDisabled *bool `json:"live_query_disabled,omitempty"` } // HostExpirySettings contains settings pertaining to automatic host expiry. diff --git a/server/kolide/status.go b/server/kolide/status.go index 97f4d3a850..5445771e58 100644 --- a/server/kolide/status.go +++ b/server/kolide/status.go @@ -6,4 +6,8 @@ type StatusService interface { // StatusResultStore returns nil if the result store is functioning // correctly, or an error indicating the problem. StatusResultStore(ctx context.Context) error + + // StatusLiveQuery returns nil if live queries are enabled, or an + // error indicating the problem. + StatusLiveQuery(ctx context.Context) error } diff --git a/server/mock/datastore.go b/server/mock/datastore.go index ce95d1d9f9..09a7e5fbdb 100644 --- a/server/mock/datastore.go +++ b/server/mock/datastore.go @@ -11,6 +11,7 @@ package mock //go:generate mockimpl -o datastore_osquery_options.go "s *OsqueryOptionsStore" "kolide.OsqueryOptionsStore" //go:generate mockimpl -o datastore_scheduled_queries.go "s *ScheduledQueryStore" "kolide.ScheduledQueryStore" //go:generate mockimpl -o datastore_queries.go "s *QueryStore" "kolide.QueryStore" +//go:generate mockimpl -o datastore_query_results.go "s *QueryResultStore" "kolide.QueryResultStore" //go:generate mockimpl -o datastore_campaigns.go "s *CampaignStore" "kolide.CampaignStore" //go:generate mockimpl -o datastore_sessions.go "s *SessionStore" "kolide.SessionStore" @@ -35,6 +36,7 @@ type Store struct { PackStore UserStore QueryStore + QueryResultStore } func (m *Store) Drop() error { diff --git a/server/mock/datastore_query_results.go b/server/mock/datastore_query_results.go new file mode 100644 index 0000000000..66dc2ffeae --- /dev/null +++ b/server/mock/datastore_query_results.go @@ -0,0 +1,43 @@ +// Automatically generated by mockimpl. DO NOT EDIT! + +package mock + +import ( + "context" + + "github.com/kolide/fleet/server/kolide" +) + +var _ kolide.QueryResultStore = (*QueryResultStore)(nil) + +type WriteResultFunc func(result kolide.DistributedQueryResult) error + +type ReadChannelFunc func(ctx context.Context, query kolide.DistributedQueryCampaign) (<-chan interface{}, error) + +type HealthCheckFunc func() error + +type QueryResultStore struct { + WriteResultFunc WriteResultFunc + WriteResultFuncInvoked bool + + ReadChannelFunc ReadChannelFunc + ReadChannelFuncInvoked bool + + HealthCheckFunc HealthCheckFunc + HealthCheckFuncInvoked bool +} + +func (s *QueryResultStore) WriteResult(result kolide.DistributedQueryResult) error { + s.WriteResultFuncInvoked = true + return s.WriteResultFunc(result) +} + +func (s *QueryResultStore) ReadChannel(ctx context.Context, query kolide.DistributedQueryCampaign) (<-chan interface{}, error) { + s.ReadChannelFuncInvoked = true + return s.ReadChannelFunc(ctx, query) +} + +func (s *QueryResultStore) HealthCheck() error { + s.HealthCheckFuncInvoked = true + return s.HealthCheckFunc() +} diff --git a/server/pubsub/redis_query_results.go b/server/pubsub/redis_query_results.go index 4bfa3af453..40d23a01d5 100644 --- a/server/pubsub/redis_query_results.go +++ b/server/pubsub/redis_query_results.go @@ -160,6 +160,8 @@ func (r *redisQueryResults) HealthCheck() error { conn := r.pool.Get() defer conn.Close() - _, err := conn.Do("PING") - return err + if _, err := conn.Do("PING"); err != nil { + return errors.Wrap(err, "reading from redis") + } + return nil } diff --git a/server/service/endpoint_appconfig.go b/server/service/endpoint_appconfig.go index 937634d4e8..acd9968631 100644 --- a/server/service/endpoint_appconfig.go +++ b/server/service/endpoint_appconfig.go @@ -63,8 +63,9 @@ func makeGetAppConfigEndpoint(svc kolide.Service) endpoint.Endpoint { OrgLogoURL: &config.OrgLogoURL, }, ServerSettings: &kolide.ServerSettings{ - KolideServerURL: &config.KolideServerURL, - EnrollSecret: &config.EnrollSecret, + KolideServerURL: &config.KolideServerURL, + EnrollSecret: &config.EnrollSecret, + LiveQueryDisabled: &config.LiveQueryDisabled, }, SMTPSettings: smtpSettings, SSOSettings: ssoSettings, @@ -87,8 +88,9 @@ func makeModifyAppConfigEndpoint(svc kolide.Service) endpoint.Endpoint { OrgLogoURL: &config.OrgLogoURL, }, ServerSettings: &kolide.ServerSettings{ - KolideServerURL: &config.KolideServerURL, - EnrollSecret: &config.EnrollSecret, + KolideServerURL: &config.KolideServerURL, + EnrollSecret: &config.EnrollSecret, + LiveQueryDisabled: &config.LiveQueryDisabled, }, SMTPSettings: smtpSettingsFromAppConfig(config), SSOSettings: &kolide.SSOSettingsPayload{ diff --git a/server/service/endpoint_appconfig_test.go b/server/service/endpoint_appconfig_test.go index b25459cdf6..7cfa254973 100644 --- a/server/service/endpoint_appconfig_test.go +++ b/server/service/endpoint_appconfig_test.go @@ -41,6 +41,7 @@ func testGetAppConfig(t *testing.T, r *testResource) { assert.Equal(t, "http://foo.bar/image.png", *configInfo.OrgInfo.OrgLogoURL) assert.False(t, *configInfo.HostExpirySettings.HostExpiryEnabled) assert.Equal(t, 0, *configInfo.HostExpirySettings.HostExpiryWindow) + assert.False(t, *configInfo.ServerSettings.LiveQueryDisabled) } @@ -62,6 +63,7 @@ func testModifyAppConfig(t *testing.T, r *testResource) { EntityID: "kolide", HostExpiryEnabled: true, HostExpiryWindow: 42, + LiveQueryDisabled: true, } payload := appConfigPayloadFromAppConfig(config) payload.SMTPTest = new(bool) @@ -95,6 +97,8 @@ func testModifyAppConfig(t *testing.T, r *testResource) { // verify that host expiry settings were saved assert.True(t, saved.HostExpiryEnabled) assert.Equal(t, 42, saved.HostExpiryWindow) + //verify that live query disabled setting was saved + assert.True(t, saved.LiveQueryDisabled) } @@ -131,7 +135,8 @@ func appConfigPayloadFromAppConfig(config *kolide.AppConfig) *kolide.AppConfigPa OrgName: &config.OrgName, }, ServerSettings: &kolide.ServerSettings{ - KolideServerURL: &config.KolideServerURL, + KolideServerURL: &config.KolideServerURL, + LiveQueryDisabled: &config.LiveQueryDisabled, }, SMTPSettings: smtpSettingsFromAppConfig(config), SSOSettings: &kolide.SSOSettingsPayload{ diff --git a/server/service/endpoint_status.go b/server/service/endpoint_status.go index d13ec148c3..01f9500548 100644 --- a/server/service/endpoint_status.go +++ b/server/service/endpoint_status.go @@ -7,15 +7,25 @@ import ( "github.com/kolide/fleet/server/kolide" ) -type statusResultStoreResponse struct { +type statusResponse struct { Err error `json:"error,omitempty"` } -func (m statusResultStoreResponse) error() error { return m.Err } +func (m statusResponse) error() error { return m.Err } + +func makeStatusLiveQueryEndpoint(svc kolide.Service) endpoint.Endpoint { + return func(ctx context.Context, req interface{}) (interface{}, error) { + var resp statusResponse + if err := svc.StatusLiveQuery(ctx); err != nil { + resp.Err = err + } + return resp, nil + } +} func makeStatusResultStoreEndpoint(svc kolide.Service) endpoint.Endpoint { return func(ctx context.Context, req interface{}) (interface{}, error) { - var resp statusResultStoreResponse + var resp statusResponse if err := svc.StatusResultStore(ctx); err != nil { resp.Err = err } diff --git a/server/service/handler.go b/server/service/handler.go index fdded503b5..3876475f00 100644 --- a/server/service/handler.go +++ b/server/service/handler.go @@ -98,6 +98,7 @@ type KolideEndpoints struct { GetFIM endpoint.Endpoint ModifyFIM endpoint.Endpoint StatusResultStore endpoint.Endpoint + StatusLiveQuery endpoint.Endpoint } // MakeKolideServerEndpoints creates the Kolide API endpoints. @@ -192,6 +193,7 @@ func MakeKolideServerEndpoints(svc kolide.Service, jwtKey, urlPrefix string) Kol // Authenticated status endpoints StatusResultStore: authenticatedUser(jwtKey, svc, makeStatusResultStoreEndpoint(svc)), + StatusLiveQuery: authenticatedUser(jwtKey, svc, makeStatusLiveQueryEndpoint(svc)), // Osquery endpoints EnrollAgent: makeEnrollAgentEndpoint(svc), @@ -285,6 +287,7 @@ type kolideHandlers struct { ModifyFIM http.Handler GetFIM http.Handler StatusResultStore http.Handler + StatusLiveQuery http.Handler } func makeKolideKitHandlers(e KolideEndpoints, opts []kithttp.ServerOption) *kolideHandlers { @@ -374,6 +377,7 @@ func makeKolideKitHandlers(e KolideEndpoints, opts []kithttp.ServerOption) *koli ModifyFIM: newServer(e.ModifyFIM, decodeModifyFIMRequest), GetFIM: newServer(e.GetFIM, decodeNoParamsRequest), StatusResultStore: newServer(e.StatusResultStore, decodeNoParamsRequest), + StatusLiveQuery: newServer(e.StatusLiveQuery, decodeNoParamsRequest), } } @@ -504,6 +508,7 @@ func attachKolideAPIRoutes(r *mux.Router, h *kolideHandlers) { r.Handle("/api/v1/kolide/targets", h.SearchTargets).Methods("POST").Name("search_targets") r.Handle("/api/v1/kolide/status/result_store", h.StatusResultStore).Methods("GET").Name("status_result_store") + r.Handle("/api/v1/kolide/status/live_query", h.StatusLiveQuery).Methods("GET").Name("status_live_query") r.Handle("/api/v1/osquery/enroll", h.EnrollAgent).Methods("POST").Name("enroll_agent") r.Handle("/api/v1/osquery/config", h.GetClientConfig).Methods("POST").Name("get_client_config") diff --git a/server/service/service_appconfig.go b/server/service/service_appconfig.go index 830d5ee817..9ddb0018a9 100644 --- a/server/service/service_appconfig.go +++ b/server/service/service_appconfig.go @@ -120,6 +120,9 @@ func appConfigFromAppConfigPayload(p kolide.AppConfigPayload, config kolide.AppC 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 + } if p.SSOSettings != nil { if p.SSOSettings.EnableSSO != nil { diff --git a/server/service/service_appconfig_test.go b/server/service/service_appconfig_test.go index ca00ae5f0a..8ec9d78608 100644 --- a/server/service/service_appconfig_test.go +++ b/server/service/service_appconfig_test.go @@ -48,7 +48,8 @@ func TestCreateAppConfig(t *testing.T) { OrgName: stringPtr("Acme"), }, ServerSettings: &kolide.ServerSettings{ - KolideServerURL: stringPtr("https://acme.co:8080/"), + KolideServerURL: stringPtr("https://acme.co:8080/"), + LiveQueryDisabled: boolPtr(true), }, }, }, @@ -63,5 +64,6 @@ func TestCreateAppConfig(t *testing.T) { 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) } } diff --git a/server/service/service_campaigns.go b/server/service/service_campaigns.go index ce363891f7..23cd7c31d1 100644 --- a/server/service/service_campaigns.go +++ b/server/service/service_campaigns.go @@ -30,6 +30,10 @@ func uintPtr(n uint) *uint { } func (svc service) NewDistributedQueryCampaign(ctx context.Context, queryString string, hosts []uint, labels []uint) (*kolide.DistributedQueryCampaign, error) { + if err := svc.StatusLiveQuery(ctx); err != nil { + return nil, err + } + vc, ok := viewer.FromContext(ctx) if !ok { return nil, errNoContext @@ -57,7 +61,7 @@ func (svc service) NewDistributedQueryCampaign(ctx context.Context, queryString // Add host targets for _, hid := range hosts { _, err = svc.ds.NewDistributedQueryCampaignTarget(&kolide.DistributedQueryCampaignTarget{ - Type: kolide.TargetHost, + Type: kolide.TargetHost, DistributedQueryCampaignID: campaign.ID, TargetID: hid, }) @@ -69,7 +73,7 @@ func (svc service) NewDistributedQueryCampaign(ctx context.Context, queryString // Add label targets for _, lid := range labels { _, err = svc.ds.NewDistributedQueryCampaignTarget(&kolide.DistributedQueryCampaignTarget{ - Type: kolide.TargetLabel, + Type: kolide.TargetLabel, DistributedQueryCampaignID: campaign.ID, TargetID: lid, }) diff --git a/server/service/service_osquery_test.go b/server/service/service_osquery_test.go index 175d894e72..146f32a51b 100644 --- a/server/service/service_osquery_test.go +++ b/server/service/service_osquery_test.go @@ -816,9 +816,21 @@ func TestDetailQueries(t *testing.T) { } func TestNewDistributedQueryCampaign(t *testing.T) { + ds := &mock.Store{ + AppConfigStore: mock.AppConfigStore{ + AppConfigFunc: func() (*kolide.AppConfig, error) { + config := &kolide.AppConfig{} + return config, nil + }, + }, + } + rs := &mock.QueryResultStore{ + HealthCheckFunc: func() error { + return nil + }, + } mockClock := clock.NewMockClock() - ds := new(mock.Store) - svc, err := newTestServiceWithClock(ds, nil, mockClock) + svc, err := newTestServiceWithClock(ds, rs, mockClock) require.Nil(t, err) ds.LabelQueriesForHostFunc = func(host *kolide.Host, cutoff time.Time) (map[string]string, error) { diff --git a/server/service/service_status.go b/server/service/service_status.go index e278e22c81..1beef9adee 100644 --- a/server/service/service_status.go +++ b/server/service/service_status.go @@ -1,7 +1,24 @@ package service -import "context" +import ( + "context" + + "github.com/pkg/errors" +) func (svc service) StatusResultStore(ctx context.Context) error { return svc.resultStore.HealthCheck() } + +func (svc service) StatusLiveQuery(ctx context.Context) error { + cfg, err := svc.AppConfig(ctx) + if err != nil { + return errors.Wrap(err, "retreiving app config") + } + + if cfg.LiveQueryDisabled { + return errors.New("disabled by administrator") + } + + return svc.StatusResultStore(ctx) +}