-
Notifications
You must be signed in to change notification settings - Fork 8.3k
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
ECS audit events for alerting #84113
Conversation
Pinging @elastic/kibana-alerting-services (Team:Alerting Services) |
src/core/server/index.ts
Outdated
@@ -311,6 +311,7 @@ export { | |||
exportSavedObjectsToStream, | |||
importSavedObjectsFromStream, | |||
resolveSavedObjectsImportErrors, | |||
generateSavedObjectId, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should reduce non-contract APIs exposed from core
to a minimum (the import/export static functions are remains from legacy that will be moved to the SO service mid term). Any reason this generateSavedObjectId
is exposed as a static function instead of being provided by the savedObjects
service contract?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's a pure function that doesn't require any shared state so didn't make sense to me to add it to the SavedObjectsSerializer
. Maybe we could add it as a static method to SavedObjectsServiceSetup
if that addresses your concern?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
cc @rudolf wdyt? is exposing generateSavedObjectId
statically from the index acceptable to you, or do you think this should be exposed from the SO service contract?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Another option could be to put this into kbn-utils
package.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Another option could be to put this into kbn-utils package.
I don't think we should. Separate by domain
is one of our main principles, so it should belong to saved objects
.
++ to add it as a setup/start
contract property. id creation
operation doesn't sound like serializer
responsibility.
btw why other code in SO service still calls uuid
to generate id
?
if (object.id == null) object.id = uuid.v1(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
However in that case, I'd say that the best option may be to stick to using uuid.v1 from the consuming code until we do implement this new API. @rudolf @restrry wdyt?
👍
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
However in that case, I'd say that the best option may be to stick to using
uuid.v1
from the consuming code until we do implement this new API. @rudolf @restrry wdyt?
I believe the logic for ID generation is changing in the very near future (👀 @jportner), so I feel like it'd be safer to keep the logic consolidated in the static helper. Asking consumers to use uuid.v1
is leaking an implementation detail of the SO service, which feels worse to me than exposing a static function from the service.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe the logic for ID generation is changing in the very near future (👀 @jportner)
Sharing Saved Objects will implement ID (re-)generation when existing objects are converted, but I have no plans to change "regular" ID generation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's keep it in SavedObjectsUtils
then. it would be easier to track than greping for uuid
anyway when we'll need to move that to the service contract.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
All done. Please give it another parse.
This reverts commit 7d929fe.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(approved changes to Alerts & Actions)
LGTM, thanks for taking on this mammoth of a task!
It looks good and I'm happy to approve, but there's one thing that worries me (would be good to hear more from @elastic/kibana-alerting-services on this), which is that the audit logging concerns are yet another think the AlertsClient does.... I was really hoping we could hide that implementation inside of the AlertsAuthorization class (same thing fo Actions).
I'm happy to ago ahead and merge this with the hope that we find away of decoupling these things later... but I thought it might be worth flagging incase anyone on the team feels otherwise.
this.auditLogger?.log( | ||
alertRuleEvent({ | ||
action: AlertRuleAction.CREATE, | ||
outcome: EventOutcome.UNKNOWN, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this "unknown" because we haven't actually tried to create yet? If so, shouldn't this be explicit about not having an unknown outcome as much as just not having reached an outcome yet?
I might be misunderstanding the reasons it's unknown 🤔
Just wondering if 'Unknown' is conceptually the right way of thinking about this event as it could be confusing if we see this in the log...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You're correct, the outcome for write operations is logged as unknown since we don't know whether the operation succeeded or failed at that point in time.
I wanted to be explicit about it so that it's clear to the user that the operation has not completed yet, which is also why the message is phrased in present progressive rather than past tense.
I also wanted to keep the option open to add logging the outcome as a separate event if users require that information for auditing purposes.
My concern with omitting the outcome
field would be that users might not fully realise what the event means and when it was logged.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fair enough, I guess the very existence of an unknown outcomes suggests to me that there was an outcome, we just couldn't identify it...
I can go either way, which is why I was happy to approve 👍
Hi @thomheymann, regarding the terminology, I know we've been back and forth on what terms to use so the alerting team had a discussion on what to use. We've decided against gradually introducing the new terms into our codebase (would cause increase in confusion vs changing everything at once) as it seems the new terminology won't be used just yet. I hope this doesn't cause too many changes in your PR but please let me know if I can help out. I did a pass at the events in the description and came up with a map of what I think they would now become:
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I started my review but ran short on time to finish. I finished looking at the implementation for actions plugin and left a few comments. I'm sure some of these would apply to the alerts plugin as well but I haven't looked at it yet.
this.preconfiguredActions.find((preconfiguredAction) => preconfiguredAction.id === id) !== | ||
undefined | ||
) { | ||
throw new PreconfiguredActionDisabledModificationError( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it intentional to capture the errors thrown when the action type is disabled? If so, there's a few other places (this.actionTypeRegistry.ensureActionTypeEnabled
) that will throw similar errors.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When does this error occur? Is it role based or just some kind of global configuration?
I thought it was an authorisation error but looks like I made a mistake here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When does this error occur? Is it role based or just some kind of global configuration?
There are two types of actions in the system, pre-configured and dynamic / user created actions. This code will throw whenever the update API is called for a pre-configured action. Since those actions are configured via kibana.yml, we don't support updates and throw the error.
So I think we may be ok here to skip auditing this error?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've double checked with Oleg and we think this is audit worthy. As an auditor you'd want to know why someone is trying to update preconfigured connectors.
I'll make ensureActionTypeEnabled
error are included in the audit log.
Are there any other errors I should include?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually, looking at the errors thrown inside ensureActionTypeEnabled
, I'm not sure they're audit worthy.
The first type of error is thrown when an action type is disabled. That just feels like a validation error.
The second type of error is thrown when the license doesn't allow a certain type. That's definitely not security related / audit worthy.
I'd say let's leave these as is until we get a requirement to include those errors.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍 works for me!
this.preconfiguredActions.find((preconfiguredAction) => preconfiguredAction.id === id) !== | ||
undefined | ||
) { | ||
throw new PreconfiguredActionDisabledModificationError( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same question here:
Is it intentional to capture the errors thrown when the action type is disabled? If so, there's a few other places (
this.actionTypeRegistry.ensureActionTypeEnabled
) that will throw similar errors.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
KibanaApp changes LGTM, just adding ids to the saved object migration tests which should have been there anyway
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I reviewed the remaining changes, LGTM once the requests below are complete! Great work!
- Renaming of events: ECS audit events for alerting #84113 (comment)
- Moving out the validation code discussed in ECS audit events for alerting #84113 (comment) and ECS audit events for alerting #84113 (comment)
@elasticmachine merge upstream |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looking great, mostly nits below!
public static isRandomId(id: string | undefined) { | ||
return typeof id === 'string' && UUID_REGEX.test(id); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit
public static isRandomId(id: string | undefined) { | |
return typeof id === 'string' && UUID_REGEX.test(id); | |
} | |
public static isRandomId(id: string | undefined) { | |
return typeof id === 'string' && UUID_REGEX.test(id.toLowerCase()); | |
} |
Alternatively you could change the regex to search for upper-case characters 🤷♂️
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think UUIDs are case insensitive: https://github.com/uuidjs/uuid/blob/master/src/validate.js
I'd prefer to use the package directly but upgrading to latest version is not trivial and should be a PR in its own right.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the uuid
package is wrong. See the spec:
Each field is treated as an integer and has its value printed as a zero-filled hexadecimal digit string with the most significant digit first. The hexadecimal values "a" through "f" are output as lower case characters and are case insensitive on input.
But I suppose if we only ever care to validate IDs that were generated with the uuid
package, the current implementation is correct 😄
ids.forEach((id) => | ||
this.auditLogger?.log( | ||
connectorAuditEvent({ | ||
action: ConnectorAuditAction.GET, | ||
savedObject: { type: 'action', id }, | ||
}) | ||
) | ||
); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I know this is not intuitive, but when executing a bulk operation, the entire operation may succeed even though there are one or more errors for individual objects. For example, if an invalid type was specified (bad request), or if the object was not found.
To be consistent with our other access logs, we don't want to create an audit record for an object that wasn't actually accessed by the user. You could account for these cases with the following change:
ids.forEach((id) => | |
this.auditLogger?.log( | |
connectorAuditEvent({ | |
action: ConnectorAuditAction.GET, | |
savedObject: { type: 'action', id }, | |
}) | |
) | |
); | |
bulkGetResult.saved_objects.forEach(({ id, error }) => { | |
if (!error && this.auditLogger) { | |
this.auditLogger.log( | |
connectorAuditEvent({ | |
action: ConnectorAuditAction.GET, | |
savedObject: { type: 'action', id }, | |
}) | |
); | |
} | |
}); |
Note, we should make the same change in x-pack/plugins/security/server/saved_objects/secure_saved_objects_client_wrapper.ts
too.
const canSpecifyID = | ||
(options.overwrite && options.version) || SavedObjectsUtils.isRandomId(options.id); | ||
if (options.id && !canSpecifyID) { | ||
throw new Error( | ||
`Predefined IDs are not allowed for encrypted saved objects of type "${type}".` | ||
'Predefined IDs are not allowed for saved objects with encrypted attributes, unless the ID has been generated using `SavedObjectsUtils.generateId`.' | ||
); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: the same code is used in two different places, could be abstracted out to a function like
private throwIfIdIsNotAllowed(id: string | undefined, overwrite: boolean, version: string) {
const canSpecifyID = (overwrite && version) || SavedObjectsUtils.isRandomId(id);
if (id && !canSpecifyID) {
throw new Error(
'Predefined IDs are not allowed for saved objects with encrypted attributes, unless the ID has been generated using `SavedObjectsUtils.generateId`.'
);
}
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have added a getValidId
method that handles all that logic and also generates an id if none has been specified:
459e0c4#diff-06a76df7e9f18756a4bcd62574dd9d6fd505a9ed7c8f275a1bdabd52a81a4bb6R298-R320
public static generateId() { | ||
return uuid.v1(); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perhaps we should use uuid.v4()
here instead, considering that the ESO wrapper now relies on this?
I know that you should not rely on UUIDs being hard to guess, but UUIDv1 is very predictable where UUIDv4 is not. I don't see any practical benefit to using v1 over v4.
@pgayvallet do you have any thoughts on this either way?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe we already had this discussion a while ago (probably with @rudolf), and I can't exactly remember the outcome, but to restart the conversation:
- Objectively, v4 is better than v1
- Still, I think v1 is good enough, it 'just work (tm)', so there is no urge to change it (or do you see one?)
- I don't think we should switch from one to the other during a minor
- Even if we do, it should probably not be in this PR
Do you want me to open an issue to discuss this change?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Still, I think v1 is good enough, it 'just work (tm)', so there is no urge to change it (or do you see one?)
My only justification is that the ESO wrapper used to generate its own IDs upon object creation, and it used v4. Now it's relying on the core generateId
function which uses v1.
I think it's worth discussing further. I'm curious to know why we're hesitant to change to v4 in a minor.
x-pack/plugins/security/server/saved_objects/secure_saved_objects_client_wrapper.test.ts
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
public static generateId() { | ||
return uuid.v1(); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe we already had this discussion a while ago (probably with @rudolf), and I can't exactly remember the outcome, but to restart the conversation:
- Objectively, v4 is better than v1
- Still, I think v1 is good enough, it 'just work (tm)', so there is no urge to change it (or do you see one?)
- I don't think we should switch from one to the other during a minor
- Even if we do, it should probably not be in this PR
Do you want me to open an issue to discuss this change?
/** | ||
* Validates that a saved object ID matches UUID format. | ||
* | ||
* @param {string} id The ID of a saved object. | ||
* @todo Use `uuid.validate` once upgraded to v5.3+ | ||
*/ | ||
public static isRandomId(id: string | undefined) { | ||
return typeof id === 'string' && UUID_REGEX.test(id); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this naming is misleading. We are asserting that the id matches the uuid format, not that is was randomly generated (which is properly explained in the tsdoc, but not really reflected on the method name)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The purpose of the isRandomId
method is to allow plugin developers to assert whether an ID has been randomly generated or whether it's a static ID. In order to do so the function checks whether a string matches UUID format but that's an implementation detail that consumers don't need to be aware of in the same way that they don't need to know that generateId
generates a UUID.
Would isRandomlyGeneratedId
be more explicit?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That would just be a rephrasing.
In that case, change Validates that a saved object ID matches UUID format.
to Validates that a saved object ID has been randomly generated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM, great stuff!
* ECS audit events for alerts plugin * added api changes * fixed linting and testing errors * fix test * Fixed linting errors after prettier update * Revert "Allow predefined ids for encrypted saved objects (#83482)" This reverts commit 7d929fe. * Added suggestions from code review * Fixed unit tests * Added suggestions from code review * Changed names of alert events * Changed naming as suggested in code review * Added suggestions from PR Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
⏳ Build in-progress, with failures
Failed CI Steps
History
To update your PR or re-run it, just comment with: |
Closes #80288
Closes #49823
Release Note
Adds ECS audit events for alerts and actions plugins.
Docs
https://kibana_84113.docs-preview.app.elstc.co/guide/en/kibana/master/xpack-security-audit-logging.html
Summary
The following events are now logged in ECS format when the new audit logger is enabled:
NOTE: @LeeDr - some of these event names changed, see https://www.elastic.co/guide/en/kibana/7.11/xpack-security-audit-logging.html
connector_create
connector_update
connector_delete
connector_get
connector_find
alert_rule_create
alert_rule_update
alert_rule_update_api_key
alert_rule_enable
alert_rule_disable
alert_rule_delete
alert_rule_get
alert_rule_find
alert_rule_mute
alert_rule_unmute
alert_instance_mute
alert_instance_unmute
Approach
action=connector
,alert=alert_rule
)id
alongside the create operation we are generating an ID using coregenerateSavedObjectId
function directly within alerts and actions client (based on Allow predefined ids for encrypted saved objects #83482)Known issues
alert_rule_create
oralert_rule_update
events. This should be tackled as part of a separate PR once we worked out how we can support a concept of "authenticated fake requests". (Related to Scope-able elasticsearch clients #39430)Out of scope
connector_execute
events are out of scope as the code path is currently different between direct execution and async execution (background tasks) which would cause duplicate or missing events. @gmmorris confirmed that the code can be refactored to support audit logging.How to test
Checklist
Delete any items that are not applicable to this PR.
For maintainers