Skip to content

Commit 6c447ac

Browse files
authored
o365: fix error propagation within cel program (#15445)
The logic for handling error propagation from failed subscriptions requests was incorrect. The downstream logic was expecting to find the error in the root of state, but the subscription request block was leaving it in the work object. Later error handling was incorrectly leaving any error state in the root of state, when it should have been placed under events. In the case that a subscription has failed, there may not be an entry in the last_for list for the subscription, resulting in a look-up failure during assessment for completion of the hour-window steps. Finally, the postamble logic was brittle to failed requests, assuming that the todo_type and todo_content list would be present, which is not necessarily the case when a subscription has failed. This has been manually tested against the mock committed in the deploy directory (with removal of authentication for convenience) with the following outcomes: only one, invalid, subscription: """ -- data.json -- { "url": "http://localhost:9001", "want_more": false, "base": { "tenant_id": "test-cel-tenant-id", "list_contents_start_time": "12h", "batch_interval": "1h", "maximum_age": "167h55m", "content_types": "Audit.TypeRequiringAdditionalPermissions" } } -- out.json -- { "base": { "batch_interval": "1h", "content_types": "Audit.TypeRequiringAdditionalPermissions", "list_contents_start_time": "12h", "maximum_age": "167h55m", "tenant_id": "test-cel-tenant-id" }, "events": { "error": { "code": "401", "id": "401 Unauthorized", "message": "POST /activity/feed/subscriptions/start?contentType=Audit.TypeRequiringAdditionalPermissions: {\"error\":{\"code\":\"AF10001\",\"message\":\"The permission set (...) sent in the request does not include the expected permission.\"}}" } }, "url": "http://localhost:9001", "want_more": false, "work": { "curr_type": "Audit.TypeRequiringAdditionalPermissions", "todo_type": [] } } """ start with an invalid subscription: """ -- data.json -- { "url": "http://localhost:9001", "want_more": false, "base": { "tenant_id": "test-cel-tenant-id", "list_contents_start_time": "12h", "batch_interval": "1h", "maximum_age": "167h55m", "content_types": "Audit.TypeRequiringAdditionalPermissions, Audit.SharePoint" } } -- out.json -- { "base": { "batch_interval": "1h", "content_types": "Audit.TypeRequiringAdditionalPermissions, Audit.SharePoint", "list_contents_start_time": "12h", "maximum_age": "167h55m", "tenant_id": "test-cel-tenant-id" }, "events": { "error": { "code": "401", "id": "401 Unauthorized", "message": "POST /activity/feed/subscriptions/start?contentType=Audit.TypeRequiringAdditionalPermissions: {\"error\":{\"code\":\"AF10001\",\"message\":\"The permission set (...) sent in the request does not include the expected permission.\"}}" } }, "url": "http://localhost:9001", "want_more": true, "work": { "curr_type": "Audit.TypeRequiringAdditionalPermissions", "todo_type": [ "Audit.SharePoint" ] } } { "base": { "batch_interval": "1h", "content_types": "Audit.TypeRequiringAdditionalPermissions, Audit.SharePoint", "list_contents_start_time": "12h", "maximum_age": "167h55m", "tenant_id": "test-cel-tenant-id" }, "cursor": { "last_for": { "audit.sharepoint": "2025-09-23T13:07:36.915764483Z" } }, "events": [ { … more … """ end with an invalid subscription: """ -- data.json -- { "url": "http://localhost:9001", "want_more": false, "base": { "tenant_id": "test-cel-tenant-id", "list_contents_start_time": "12h", "batch_interval": "1h", "maximum_age": "167h55m", "content_types": "Audit.SharePoint, Audit.TypeRequiringAdditionalPermissions" } } -- out.json -- … more before … "enabled": true, "type": "Audit.SharePoint" }, "todo_content": [], "todo_type": [ "Audit.TypeRequiringAdditionalPermissions" ] } } { "base": { "batch_interval": "1h", "content_types": "Audit.SharePoint, Audit.TypeRequiringAdditionalPermissions", "list_contents_start_time": "12h", "maximum_age": "167h55m", "tenant_id": "test-cel-tenant-id" }, "cursor": { "last_for": { "audit.sharepoint": "2025-09-24T00:10:30.808672557Z" } }, "events": { "error": { "code": "401", "id": "401 Unauthorized", "message": "POST /activity/feed/subscriptions/start?contentType=Audit.TypeRequiringAdditionalPermissions: {\"error\":{\"code\":\"AF10001\",\"message\":\"The permission set (...) sent in the request does not include the expected permission.\"}}" } }, "url": "http://localhost:9001", "want_more": false, "work": { "curr_type": "Audit.TypeRequiringAdditionalPermissions", "todo_type": [] } } """
1 parent 34b9ef5 commit 6c447ac

File tree

3 files changed

+38
-28
lines changed

3 files changed

+38
-28
lines changed

packages/o365/changelog.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
11
# newer versions go on top
2+
- version: "2.29.2"
3+
changes:
4+
- description: Fix handling of error propagation within agent CEL program.
5+
type: bugfix
6+
link: https://github.com/elastic/integrations/pull/15445
27
- version: "2.29.1"
38
changes:
49
- description: Fix handling of error conditions when requesting work continuation.

packages/o365/data_stream/audit/agent/stream/cel.yml.hbs

Lines changed: 32 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -234,15 +234,17 @@ program: |-
234234
)
235235
:
236236
{
237-
"error": {
238-
"code": string(list_resp.StatusCode),
239-
"id": string(list_resp.Status),
240-
"message": "GET " + state.work.next_list + ": " + (
241-
(size(list_resp.Body) != 0) ?
242-
string(list_resp.Body)
243-
:
244-
string(list_resp.Status) + " (" + string(list_resp.StatusCode) + ")"
245-
),
237+
"events": {
238+
"error": {
239+
"code": string(list_resp.StatusCode),
240+
"id": string(list_resp.Status),
241+
"message": "GET " + state.work.next_list + ": " + (
242+
(size(list_resp.Body) != 0) ?
243+
string(list_resp.Body)
244+
:
245+
string(list_resp.Status) + " (" + string(list_resp.StatusCode) + ")"
246+
),
247+
},
246248
},
247249
}
248250
)
@@ -314,9 +316,9 @@ program: |-
314316
).as(state, !has(state.base) ?
315317
// No current work item above, so finish.
316318
{}
317-
: has(state.error) ?
319+
: has(state.?work.sub.error) ?
318320
// Getting subscription detail failed.
319-
state
321+
state.work.sub.as(err, state.drop("work.sub").with({"events": err}))
320322
:
321323
(
322324
// This exists purely to rewrite the cursor from the original
@@ -440,15 +442,17 @@ program: |-
440442
)
441443
:
442444
{
443-
"error": {
444-
"code": string(list_resp.StatusCode),
445-
"id": string(list_resp.Status),
446-
"message": "GET /activity/feed/subscriptions/content?contentType=" + state.work.curr_type + ": " + (
447-
(size(list_resp.Body) != 0) ?
448-
string(list_resp.Body)
449-
:
450-
string(list_resp.Status) + " (" + string(list_resp.StatusCode) + ")"
451-
),
445+
"events": {
446+
"error": {
447+
"code": string(list_resp.StatusCode),
448+
"id": string(list_resp.Status),
449+
"message": "GET /activity/feed/subscriptions/content?contentType=" + state.work.curr_type + ": " + (
450+
(size(list_resp.Body) != 0) ?
451+
string(list_resp.Body)
452+
:
453+
string(list_resp.Status) + " (" + string(list_resp.StatusCode) + ")"
454+
),
455+
},
452456
},
453457
}
454458
)
@@ -461,8 +465,12 @@ program: |-
461465
(
462466
// Ensure that we bring the current type up to the current time,
463467
// even if we did not get any content for the query period.
464-
has(state.?work.curr_type) && state.?cursor.last_for.optMap(l,
465-
timestamp(l[state.work.curr_type.to_lower()]) < now - duration("1h")
468+
has(state.?work.curr_type) && state.?cursor.last_for.optMap(last_for,
469+
state.work.curr_type.to_lower().as(curr_type, curr_type in last_for ?
470+
timestamp(last_for[curr_type]) < now - duration("1h")
471+
:
472+
false
473+
)
466474
).orValue(false) && !state.work.todo_type.exists(t, t == state.work.curr_type)
467475
) ?
468476
state.with(
@@ -476,17 +484,14 @@ program: |-
476484
state.with(
477485
{
478486
"want_more": state.work.as(w,
479-
size(w.todo_type) != 0 || size(w.todo_content) != 0 || w.?next_list.orValue("") != ""
487+
size(w.?todo_type.orValue([])) != 0 || size(w.?todo_content.orValue([])) != 0 || w.?next_list.orValue("") != ""
480488
),
481489
}
482490
)
483491
).as(state,
484492
// Make sure we complete the remaining work if we got
485493
// no events but the work lists are not empty.
486-
// We do not need to put a dummy event in in the
487-
// case that we have errored, since the error will
488-
// be raised to an event by the input.
489-
(state.want_more && !has(state.error) && type(state.events) == list && size(state.events) == 0) ?
494+
(state.want_more && type(state.events) == list && size(state.events) == 0) ?
490495
state.with(
491496
{
492497
"events": [{"retry": true}],

packages/o365/manifest.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
name: o365
22
title: Microsoft Office 365
3-
version: "2.29.1"
3+
version: "2.29.2"
44
description: Collect logs from Microsoft Office 365 with Elastic Agent.
55
type: integration
66
format_version: "3.2.3"

0 commit comments

Comments
 (0)