Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[eventLog] search for actions/alerts as hidden saved objects #70395

Merged
merged 9 commits into from
Jul 16, 2020

Conversation

pmuellr
Copy link
Member

@pmuellr pmuellr commented Jul 1, 2020

resolves #70086

Configures the saved object client for the event log to access the recently
hidden action and alert saved objects.

We didn't have tests for action/alert event log activity, so added some now.

Also found a buglet that was preventing access to event log data from actions
and alerts in non-default spaces.

Also changed the event log query to enforce namespace matches for the saved object references. Technically, not a huge issue right now as the only queryable actions/alerts should be unique by their UUID ids, but when pre-configured actions become queryable, we'd absolutely need this.

Checklist

Delete any items that are not applicable to this PR.

@pmuellr pmuellr force-pushed the eventLog/hidden-saved-objects branch 3 times, most recently from 53fe564 to 6397143 Compare July 3, 2020 03:14
@pmuellr pmuellr added Feature:Alerting Team:ResponseOps Label for the ResponseOps team (formerly the Cases and Alerting teams) v7.9.0 v8.0.0 release_note:skip Skip the PR/issue when compiling release notes labels Jul 3, 2020
@pmuellr pmuellr marked this pull request as ready for review July 3, 2020 03:17
@pmuellr pmuellr requested a review from a team as a code owner July 3, 2020 03:17
@elasticmachine
Copy link
Contributor

Pinging @elastic/kibana-alerting-services (Team:Alerting Services)

@@ -22,7 +22,7 @@ export interface QueryEventsBySavedObjectResult {
page: number;
per_page: number;
total: number;
data: IEvent[];
data: IValidatedEvent[];
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IValidatedEvent is a little nicer to use as a result of the query since it's not partial-ized or writable.

getClient(request: KibanaRequest) {
const savedObjectsClient: SavedObjectsClientContract = this.savedObjectsService.getScopedClient(
request,
{ includedHiddenTypes }
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here's the fix for the hidden types.

@@ -125,8 +125,7 @@ export class Plugin implements CorePlugin<IEventLogService, IEventLogClientServi
> => {
return async (context, request) => {
return {
getEventLogClient: () =>
this.eventLogClientService!.getClient(request, context.core.savedObjects.client),
getEventLogClient: () => this.eventLogClientService!.getClient(request),
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here's the fix for accessing event log for saved objects in non-default spaces. The getClient() call already built a properly scoped SO client, we were passing one in here that didn't work, so just changed to use the one it built instead.

@@ -32,6 +32,7 @@ const enabledActionTypes = [
'test.index-record',
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remainder of the files are basic tests for event log usage by actions and alerts. They weren't added when event log support was added to actions and alerts, because we didn't have the find function built yet. These tests would have caught the bugs fixed in this PR.

Copy link
Contributor

@gmmorris gmmorris left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I still need to look through the whole PR, but I'm concerned this isn't going to work once the RBAC work is done- but I also don't want to block the RBAC branch anymore than we have to.
Could we have this PR use the ActionsClient and AlertsClient as a first step and then introduce some kind of pluggable approach in a future PR?

Comment on lines 29 to 32
expect(savedObjectsService.getScopedClient).toHaveBeenCalledWith(request, {
includedHiddenTypes: ['action', 'alert'],
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this approach means we won't have the alert/actions RBAC on the event log, but only the default SavedObjects RBAC. This means that if the user doesn't have privileges over the action and alert saved object types (as granted by some plugin) this code will throw.
You'll have to explicitly grant privileges via the EventLog feature for this to work.

I think we need an approach where plugins can plug handlers into the Event Log.
For example, the Actions plugin could "tell" the EventLog how to handle action saved object types, and it will use the ActionsClient internally to actually get the object.
That way you'll enjoy RBAC and you won't have to explicitly have to add actions to the EventLog feature's privileges config.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, that's basically the plan for how to fix issue: #63961

Dang, I guess it means this code is throw away anyway (despite it being some one-liner-ish simple fixes to the existing code). Probably makes sense to fix that issue in this PR as well.

Also forgot that I noticed the event log query should include the namespace, but doesn't currently. I think this isn't critical for 7.9, will take a quick peek at fixing that here, but afraid more tests will be required / existing ones fixed, perhaps better as a separate issue:

public async queryEventsBySavedObject(
index: string,
type: string,
id: string,
{ page, per_page: perPage, start, end, sort_field, sort_order }: FindOptionsType
): Promise<QueryEventsBySavedObjectResult> {

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I haven't fixed this on the RBAC branch as it's broken on master and that PR is already way too big... but I could make a quick follow up PR that uses the Alerts/Actions clients after that to fix master and that would mean that this PR can be used for the long term solution and doesn't have to be done by 7.9.

What do you think?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I ended up spending 2 days on using the alerts/actionsClient to resolve the saved objects, waaay more complicated than I thought. Going to have to punt on adding that here, which seems fine since RBAC for alerts got pushed as well.

@pmuellr pmuellr force-pushed the eventLog/hidden-saved-objects branch 8 times, most recently from e8fc10f to e4c91b3 Compare July 9, 2020 15:48
resolves elastic#70086

Configures the saved object client for the event log to access the recently
hidden action and alert saved objects.

We didn't have tests for action/alert event log activity, so added some now.

Also found a buglet that was preventing access to event log data from actions
and alerts in non-default spaces.
@pmuellr pmuellr force-pushed the eventLog/hidden-saved-objects branch from e4c91b3 to 8957be7 Compare July 9, 2020 18:15
value: SAVED_OBJECT_REL_PRIMARY,
},
},
const defaultNamespaceQuery = {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are the new query DSL clauses to ensure we only return SO's in the relevent space - likely not an issue now, but will be once we can query pre-configured actions - if you could query those now (you can't), the query would return events across all spaces, without these additional clauses.

The rule with namespaces is that they are string | undefined (undefined for the default space), and that's how we "store" them in ES - the SO reference in the event will not have a namespace field for the default space.

@@ -11,11 +11,8 @@ import { FtrProviderContext } from '../../../../common/ftr_provider_context';
// eslint-disable-next-line import/no-default-export
export default function emailTest({ getService }: FtrProviderContext) {
const supertest = getService('supertest');
const esArchiver = getService('esArchiver');
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

File this under "how did this ever work?!". esArchiver.unload('empty_kibana')) will delete any existing spaces within Kibana when it runs. We have a bunch of test modules that used this on their own after() processing, but the spaces are created in an outer describe() section with it's own after() section that calls esArchiver.unload(). Which means that any tests run after one of these modules did the unload, would likely fail because the spaces no longer existed. But the tests didn't fail. No idea why. Maybe actions/alerts isn't "touching" the same spaces APIs that event log does (my guess). But the new tests in this PR did fail. So I ended removing all these calls in the "inner" module tests. Everything still passes!

Also note, esArchiver.unload() is an async method, and most/all of our usage didn't wait on it, so ... comedy of errors there :-)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also note, esArchiver.unload() is an async method, and most/all of our usage didn't wait on it, so ... comedy of errors there :-)

I believe the after function also accepts promises being returned and will wait for them to resolve. I think that's how these have worked before.


await logTestEvent(id, expectedEvents[0]);
await logTestEvent(id, expectedEvents[1]);
for (const namespace of [undefined, 'namespace-a']) {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The tests here previously just tested an event with a SO in the default space, so I refactored all those tests to test with the default space AND a named space. Hence the indentation changes, and parameterization of the namespace. The diff came out much worse than I expected :-)

@pmuellr
Copy link
Member Author

pmuellr commented Jul 9, 2020

Alrighty, this should be ready for review again.

Per Gidi's comment about fixing #63961 (use actions/alerts client to validate SOs instead of the generic SO client), I spent about 2 days trying to get that to work and ... didn't, and it was a bit of a mess. Should be cleaner to do it in a separate PR anyway, without all the other churn here.

While working on that code though, I realized we have issues with SO namespaces, and no tests for them, so that's a large amount of the new code in this PR.

@pmuellr
Copy link
Member Author

pmuellr commented Jul 13, 2020

@elasticmachine merge upstream

@mikecote mikecote self-requested a review July 14, 2020 14:57
Copy link
Contributor

@mikecote mikecote left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changes LGTM 👍 few nits, questions and comments.

x-pack/plugins/event_log/server/plugin.ts Outdated Show resolved Hide resolved
)
) {
getClient(request: KibanaRequest) {
const savedObjectsClient: SavedObjectsClientContract = this.savedObjectsService.getScopedClient(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will this break again once @gmmorris's RBAC work gets merged or will this skip privilege checks?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes it will break by skipping the privilege checks. I've updated the title and a little of the description of #63961, which is where we'll fix this.

I tried to squeeze it in here, but it was getting pretty hairy so I backed out it out. I'll make some notes in that issue on what I tried.

x-pack/plugins/event_log/server/event_log_client.ts Outdated Show resolved Hide resolved
@@ -11,11 +11,8 @@ import { FtrProviderContext } from '../../../../common/ftr_provider_context';
// eslint-disable-next-line import/no-default-export
export default function emailTest({ getService }: FtrProviderContext) {
const supertest = getService('supertest');
const esArchiver = getService('esArchiver');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also note, esArchiver.unload() is an async method, and most/all of our usage didn't wait on it, so ... comedy of errors there :-)

I believe the after function also accepts promises being returned and will wait for them to resolve. I think that's how these have worked before.

Comment on lines +27 to +34
const throwActionType: ActionType = {
id: 'test.throw',
name: 'Test: Throw',
minimumLicenseRequired: 'gold',
async executor() {
throw new Error('this action is intended to fail');
},
};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: this seems similar to test.failing action type below. I we can merge them, maybe test.failing could be modified to not always require an index param for ease of use.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

these are simple enough, and will be reused with the getAlertStatus() PR, so seems better to just keep them separate.

producer: 'alerting',
defaultActionGroupId: 'default',
async executor({ services, params, state }: AlertExecutorOptions) {
throw new Error('this alert is intended to fail');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar comment here with test.failing.

Comment on lines +304 to +331
const patternFiringAlertType: AlertType = {
id: 'test.patternFiring',
name: 'Test: Firing on a Pattern',
actionGroups: [{ id: 'default', name: 'Default' }],
producer: 'alerting',
defaultActionGroupId: 'default',
async executor(alertExecutorOptions: AlertExecutorOptions) {
const { services, state, params } = alertExecutorOptions;
const pattern = params.pattern;
if (!Array.isArray(pattern)) throw new Error('pattern is not an array');
if (pattern.length === 0) throw new Error('pattern is empty');

// get the pattern index, return if past it
const patternIndex = state.patternIndex ?? 0;
if (patternIndex > pattern.length) {
return { patternIndex };
}

// fire if pattern says to
if (pattern[patternIndex]) {
services.alertInstanceFactory('instance').scheduleActions('default');
}

return {
patternIndex: (patternIndex + 1) % pattern.length,
};
},
};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems similar to test.cumulative-firing?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nah. This alert takes a "pattern" of booleans that indicate if it should fire on specific executions. Eg, [false, true, false, true] means on the 1s and 3rd executions it will schedule actions, but on the 2nd and 4th it will. I realized I was going to need something like this to be able to test the new-instance / resolved-instance events, in the getAlertStatus() PR, so I did a first pass here (getAlertStatus() will require per-instance patterns, or something :-) ).

@pmuellr
Copy link
Member Author

pmuellr commented Jul 15, 2020

@elasticmachine merge upstream

Copy link
Contributor

@gmmorris gmmorris left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried playing around with the API locally and it seems to be working as expected.
I'd feel more confident if we also had a test suite under the security_and_spaces package, so we know that security isn't affecting this somehow, but I guess that can wait until we address the pluggable behaviour and use the ActionsClient and AlertsClient.

try {
result = await eventLogClient.findEventsBySavedObject(type, id, query);
} catch (err) {
return res.notFound();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should log the err as we might be swallowing some failure state here


let result: QueryEventsBySavedObjectResult;
try {
result = await eventLogClient.findEventsBySavedObject(type, id, query);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit. but as we're returning from the catch I'd personally find it more readable if we did this:

Suggested change
result = await eventLogClient.findEventsBySavedObject(type, id, query);
return res.ok({
body: await eventLogClient.findEventsBySavedObject(type, id, query)
});

Super subjective and nitty :)

* you may not use this file except in compliance with the Elastic License.
*/

// import { IEvent } from '../../../../../../plugins/event_log/server';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

an abandoned piece of code :)


await logTestEvent(id, expectedEvents[0]);
await logTestEvent(id, expectedEvents[1]);
for (const namespace of [undefined, 'namespace-a']) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit.

Could we use some kind of const here instead of just undefined?

something like:

const DEFAULT_NAMESPACE = undefined;
for (const namespace of [DEFAULT_NAMESPACE, 'namespace-a']) {
   const namespaceName = namespace === DEFAULT_NAMESPACE ? 'default' : namespace;
 /// ...

There's a bunch of places in this test where we use undefined to refer to the default namespace and I think, for ease of maintenance for future developers, that using a const when undefined means the default NS will make life a little easier.

Copy link
Member Author

@pmuellr pmuellr Jul 15, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmm, I just tried that, but felt like keeping the definition of namespace to string | undefined still makes sense, since that's what it looks like in the non-test code. So then changing the constant values besides that to a defined constant looked really weird:

function urlPrefixFromNamespace(namespace: string | undefined): string {
  if (namespace === DEFAULT_NAMESPACE) return '';
  return `/s/${namespace}`;
}

Feels better to just leave it as undefined to me. There were only 4 references to the constant value outside the type definition anyway.

@pmuellr
Copy link
Member Author

pmuellr commented Jul 15, 2020

from @gmmorris in #70395 (review)

I'd feel more confident if we also had a test suite under the security_and_spaces package, so we know that security isn't affecting this somehow, but I guess that can wait until we address the pluggable behaviour and use the ActionsClient and AlertsClient.

Same same - I'll see if I can squeeze something into security_and_spaces for now, but for sure the pluggable SO lookup/validation will need some significant s+s tests.

@pmuellr
Copy link
Member Author

pmuellr commented Jul 16, 2020

@elasticmachine merge upstream

@kibanamachine
Copy link
Contributor

💚 Build Succeeded

Build metrics

✅ unchanged

History

To update your PR or re-run it, just comment with:
@elasticmachine merge upstream

@pmuellr pmuellr merged commit b167d77 into elastic:master Jul 16, 2020
pmuellr added a commit to pmuellr/kibana that referenced this pull request Jul 16, 2020
…#70395)

resolves elastic#70086

Configures the saved object client for the event log to access the recently
hidden action and alert saved objects.

We didn't have tests for action/alert event log activity, so added some now.

Also found a buglet that was preventing access to event log data from actions
and alerts in non-default spaces.
pmuellr added a commit to pmuellr/kibana that referenced this pull request Jul 16, 2020
…#70395)

resolves elastic#70086

Configures the saved object client for the event log to access the recently
hidden action and alert saved objects.

We didn't have tests for action/alert event log activity, so added some now.

Also found a buglet that was preventing access to event log data from actions
and alerts in non-default spaces.
pmuellr added a commit that referenced this pull request Jul 16, 2020
…#72050)

resolves #70086

Configures the saved object client for the event log to access the recently
hidden action and alert saved objects.

We didn't have tests for action/alert event log activity, so added some now.

Also found a buglet that was preventing access to event log data from actions
and alerts in non-default spaces.
pmuellr added a commit that referenced this pull request Jul 16, 2020
…#72049)

resolves #70086

Configures the saved object client for the event log to access the recently
hidden action and alert saved objects.

We didn't have tests for action/alert event log activity, so added some now.

Also found a buglet that was preventing access to event log data from actions
and alerts in non-default spaces.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
backported Feature:Alerting release_note:skip Skip the PR/issue when compiling release notes Team:ResponseOps Label for the ResponseOps team (formerly the Cases and Alerting teams) v7.9.0 v8.0.0
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[event log] can't find events since action/alert saved object are now hidden
5 participants