Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions packages/entityanalytics_ad/changelog.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
# newer versions go on top
- version: "0.22.0"
changes:
- description: Add agentless deployment mode and minimal-state sync support.
type: enhancement
link: https://github.com/elastic/integrations/pull/20212
- version: "0.21.0"
changes:
- description: Add the Advanced Analytics (UEBA) subcategory
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# Verify the minimal-state sync path routes entity documents to per-type data
# streams without producing marker events in the entity data stream.
#
# This test explicitly sets use_minimal_state: true.
#
# The mock LDAP server provides:
# - 1 user (Administrator) → entityanalytics_ad.user
# - 1 device (WORKSTATION-01) → entityanalytics_ad.device
# - 2 empty groups (Decommissioned, Contractors) → entityanalytics_ad.group

[!external_stack] skip 'Skipping external stack test.'
[!exec:jq] skip 'Skipping test requiring absent jq command'

use_stack -profile ${CONFIG_PROFILES}/${PROFILE}

install_agent -profile ${CONFIG_PROFILES}/${PROFILE} -network_name NETWORK_NAME

exec ln -s ${PACKAGE_ROOT}/_dev/deploy/docker ldap-mock-service
docker_up -profile ${CONFIG_PROFILES}/${PROFILE} -network ${NETWORK_NAME} ldap-mock-service

add_package -profile ${CONFIG_PROFILES}/${PROFILE}

add_package_policy -profile ${CONFIG_PROFILES}/${PROFILE} test_config.yaml ENTITY_DS

# Verify user documents arrive in the user data stream.
get_docs -profile ${CONFIG_PROFILES}/${PROFILE} -want 1 -timeout 5m logs-entityanalytics_ad.user-*
cp stdout user_docs.json
exec jq '[.hits.hits[]._source.user.id] | unique[]' user_docs.json
stdout 'S-1-5-21-374105552-1189110957-4047727897-500'
exec jq '[.hits.hits[]._source.user.name] | unique[]' user_docs.json
stdout 'Administrator'

# Verify device documents arrive in the device data stream.
get_docs -profile ${CONFIG_PROFILES}/${PROFILE} -want 1 -timeout 2m logs-entityanalytics_ad.device-*
cp stdout device_docs.json
exec jq '[.hits.hits[]._source.device.id] | unique[]' device_docs.json
stdout 'S-1-5-21-374105552-1189110957-4047727897-1001'

# Verify empty group documents arrive in the group data stream.
get_docs -profile ${CONFIG_PROFILES}/${PROFILE} -want 2 -timeout 2m logs-entityanalytics_ad.group-*
cp stdout group_docs.json
exec jq '[.hits.hits[]._source.group.id] | unique | sort[]' group_docs.json
stdout 'S-1-5-21-374105552-1189110957-4047727897-1101'
stdout 'S-1-5-21-374105552-1189110957-4047727897-1102'
exec jq '[.hits.hits[]._source.group.name] | unique | sort[]' group_docs.json
stdout 'Contractors'
stdout 'Decommissioned'

# The entity data stream should have no documents (no markers in minimal mode).
get_docs -profile ${CONFIG_PROFILES}/${PROFILE} -want 0 -timeout 30s ${ENTITY_DS}

# Clean up.
remove_package_policy -profile ${CONFIG_PROFILES}/${PROFILE} ${ENTITY_DS}
uninstall_agent -profile ${CONFIG_PROFILES}/${PROFILE} -timeout 1m
docker_down ldap-mock-service

-- test_config.yaml --
input: entity-analytics
data_stream:
vars:
ad_base_dn: "DC=testserver,DC=local"
ad_url: "ldap://svc-ldap-mock-service:10389"
ad_user: "cn=admin,dc=testserver,dc=local"
ad_password: "password"
dataset: all
use_minimal_state: true
include_empty_groups: true
sync_interval: 24h
update_interval: 15m
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# Verify that the default configuration (use_minimal_state not set) uses the
# minimal-state sync path. The package default is true, so the behavior should
# be identical to setting it explicitly.
#
# The mock LDAP server provides:
# - 1 user (Administrator) → entityanalytics_ad.user
# - 1 device (WORKSTATION-01) → entityanalytics_ad.device
# - 2 empty groups (Decommissioned, Contractors) → entityanalytics_ad.group

[!external_stack] skip 'Skipping external stack test.'
[!exec:jq] skip 'Skipping test requiring absent jq command'

use_stack -profile ${CONFIG_PROFILES}/${PROFILE}

install_agent -profile ${CONFIG_PROFILES}/${PROFILE} -network_name NETWORK_NAME

exec ln -s ${PACKAGE_ROOT}/_dev/deploy/docker ldap-mock-service
docker_up -profile ${CONFIG_PROFILES}/${PROFILE} -network ${NETWORK_NAME} ldap-mock-service

add_package -profile ${CONFIG_PROFILES}/${PROFILE}

add_package_policy -profile ${CONFIG_PROFILES}/${PROFILE} test_config.yaml ENTITY_DS

# Verify user documents arrive in the user data stream.
get_docs -profile ${CONFIG_PROFILES}/${PROFILE} -want 1 -timeout 5m logs-entityanalytics_ad.user-*
cp stdout user_docs.json
exec jq '[.hits.hits[]._source.user.id] | unique[]' user_docs.json
stdout 'S-1-5-21-374105552-1189110957-4047727897-500'
exec jq '[.hits.hits[]._source.user.name] | unique[]' user_docs.json
stdout 'Administrator'

# Verify device documents arrive in the device data stream.
get_docs -profile ${CONFIG_PROFILES}/${PROFILE} -want 1 -timeout 2m logs-entityanalytics_ad.device-*
cp stdout device_docs.json
exec jq '[.hits.hits[]._source.device.id] | unique[]' device_docs.json
stdout 'S-1-5-21-374105552-1189110957-4047727897-1001'

# Verify empty group documents arrive in the group data stream.
get_docs -profile ${CONFIG_PROFILES}/${PROFILE} -want 2 -timeout 2m logs-entityanalytics_ad.group-*
cp stdout group_docs.json
exec jq '[.hits.hits[]._source.group.id] | unique | sort[]' group_docs.json
stdout 'S-1-5-21-374105552-1189110957-4047727897-1101'
stdout 'S-1-5-21-374105552-1189110957-4047727897-1102'
exec jq '[.hits.hits[]._source.group.name] | unique | sort[]' group_docs.json
stdout 'Contractors'
stdout 'Decommissioned'

# The entity data stream should have no documents (no markers in minimal mode).
get_docs -profile ${CONFIG_PROFILES}/${PROFILE} -want 0 -timeout 30s ${ENTITY_DS}

# Clean up.
remove_package_policy -profile ${CONFIG_PROFILES}/${PROFILE} ${ENTITY_DS}
uninstall_agent -profile ${CONFIG_PROFILES}/${PROFILE} -timeout 1m
docker_down ldap-mock-service

-- test_config.yaml --
input: entity-analytics
data_stream:
vars:
ad_base_dn: "DC=testserver,DC=local"
ad_url: "ldap://svc-ldap-mock-service:10389"
ad_user: "cn=admin,dc=testserver,dc=local"
ad_password: "password"
dataset: all
include_empty_groups: true
sync_interval: 24h
update_interval: 15m
Original file line number Diff line number Diff line change
Expand Up @@ -41,14 +41,10 @@ exec jq '[.hits.hits[]._source.user.name] | unique[]' user_docs.json
stdout 'Administrator'

# Verify device documents were routed to the device data stream.
# NOTE: The device pipeline (device.yml) has a pre-existing bug — it references
# activedirectory.device.* but the beats input puts device attributes under
# activedirectory.user.*. The pipeline fails, so device.id retains the DN set
# by the input rather than being overwritten with the SID.
get_docs -profile ${CONFIG_PROFILES}/${PROFILE} -want 1 -timeout 2m logs-entityanalytics_ad.device-*
cp stdout device_docs.json
exec jq '[.hits.hits[]._source.device.id] | unique[]' device_docs.json
stdout 'WORKSTATION-01'
stdout 'S-1-5-21-374105552-1189110957-4047727897-1001'

# Verify empty group documents were routed to the group data stream.
get_docs -profile ${CONFIG_PROFILES}/${PROFILE} -want 2 -timeout 2m logs-entityanalytics_ad.group-*
Expand All @@ -74,6 +70,7 @@ data_stream:
ad_user: "cn=admin,dc=testserver,dc=local"
ad_password: "password"
dataset: all
use_minimal_state: false
include_empty_groups: true
sync_interval: 24h
update_interval: 15m
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,6 @@ data_stream:
include_empty_groups: true
sync_interval: 24h
update_interval: 15m
use_minimal_state: false
assert:
hit_count: 2
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,6 @@ data_stream:
include_empty_groups: true
sync_interval: 24h
update_interval: 15m
use_minimal_state: false
assert:
hit_count: 2
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
provider: activedirectory
{{#if use_minimal_state}}
use_minimal_state: true
{{/if}}
sync_interval: {{sync_interval}}
update_interval: {{update_interval}}
ad_base_dn: {{ad_base_dn}}
Expand Down
13 changes: 13 additions & 0 deletions packages/entityanalytics_ad/data_stream/entity/manifest.yml
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,19 @@ streams:
show_user: false
description: >-
Processors are used to reduce the number of fields in the exported event or to enhance the event with metadata. This executes in the agent before the logs are parsed. See [Processors](https://www.elastic.co/guide/en/beats/filebeat/current/filtering-and-enhancing-data.html) for details.
- name: use_minimal_state
type: bool
title: Use minimal state
description: >-
Use the minimal-state sync implementation. Required for agentless
deployments. Uses reduced local storage and supports ES-backed state.
multi: false
required: false
show_user: false
default: true
hide_in_deployment_modes:
- default
- agentless
elasticsearch:
dynamic_dataset: true
dynamic_namespace: true
15 changes: 12 additions & 3 deletions packages/entityanalytics_ad/manifest.yml
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
format_version: "3.0.2"
format_version: "3.3.2"
name: entityanalytics_ad
title: Active Directory Entity Analytics
version: "0.21.0"
version: "0.22.0"
description: "Collect User Identities from Active Directory Entity with Elastic Agent."
type: integration
categories:
- security
- advanced_analytics_ueba
conditions:
kibana:
version: "^9.4.0"
version: "^9.5.0"
elastic:
subscription: "basic"
screenshots: []
Expand All @@ -22,6 +22,15 @@ policy_templates:
- name: entity
title: Identities
description: Collect identities.
deployment_modes:
default:
enabled: true
agentless:
enabled: true
release: beta
organization: security
division: engineering
team: security-service-integrations
inputs:
- type: entity-analytics
title: Collect identities
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,24 @@ rules:
- status_code: 200
body: |-
{"@odata.deltaLink":"http://localhost:8080/v1.0/groups/delta?$skiptoken=1","value":[{"id":"e7089e3a-2c83-4f08-8280-7530ed39b6ca","name":"Group 5202","members@delta":[{"id":"aa534e49-edfd-4541-8256-8bbf34f122b4","type":"#microsoft.graph.user"},{"id":"feb6a386-612a-4ed1-9b13-2adc73074a19","type":"#microsoft.graph.user"}]},{"id":"526588ce-2828-4cb1-9c9b-e57026e94b82","name":"Group 16739","members@delta":[{"id":"aa534e49-edfd-4541-8256-8bbf34f122b4","type":"#microsoft.graph.user"}]}]}
- path: /v1.0/groups
methods: ["GET"]
responses:
- status_code: 200
body: |-
{"value":[{"id":"e7089e3a-2c83-4f08-8280-7530ed39b6ca","displayName":"Group 5202"},{"id":"526588ce-2828-4cb1-9c9b-e57026e94b82","displayName":"Group 16739"}]}
- path: /v1.0/groups/e7089e3a-2c83-4f08-8280-7530ed39b6ca/members
methods: ["GET"]
responses:
- status_code: 200
body: |-
{"value":[{"id":"aa534e49-edfd-4541-8256-8bbf34f122b4","@odata.type":"#microsoft.graph.user"},{"id":"feb6a386-612a-4ed1-9b13-2adc73074a19","@odata.type":"#microsoft.graph.user"}]}
- path: /v1.0/groups/526588ce-2828-4cb1-9c9b-e57026e94b82/members
methods: ["GET"]
responses:
- status_code: 200
body: |-
{"value":[{"id":"aa534e49-edfd-4541-8256-8bbf34f122b4","@odata.type":"#microsoft.graph.user"}]}
- path: /v1.0/reports/authenticationMethods/userRegistrationDetails
methods: ["GET"]
responses:
Expand Down
5 changes: 5 additions & 0 deletions packages/entityanalytics_entra_id/changelog.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
# newer versions go on top
- version: "1.12.0"
changes:
- description: Add agentless deployment mode and minimal-state sync support.
type: enhancement
link: https://github.com/elastic/integrations/pull/20212
- version: "1.11.0"
changes:
- description: Add the Advanced Analytics (UEBA) subcategory
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ inputs:
tenant_id: test-tenant-id
timeout: 30s
update_interval: 15m
use_minimal_state: true
type: entity-analytics
use_output: default
output_permissions:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ inputs:
tenant_id: test-tenant-id
timeout: 30s
update_interval: 15m
use_minimal_state: true
type: entity-analytics
use_output: default
output_permissions:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ inputs:
tenant_id: test-tenant-id
timeout: 30s
update_interval: 15m
use_minimal_state: true
type: entity-analytics
use_output: default
output_permissions:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
[!exec:echo] skip 'Skipping test requiring absent echo command'

exec echo ${PACKAGE_NAME}
stdout '^entityanalytics_entra_id$'

exec echo ${DATA_STREAM}
stdout '^entity$'

exec echo ${CURRENT_VERSION}
stdout '^[0-9]+\.[0-9]+\.[0-9]+$'
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# Verify the minimal-state sync path routes Entra ID entities to per-type data
# streams without producing marker events in the entity data stream.
#
# This test explicitly sets use_minimal_state: true.
#
# The mock Graph API provides:
# - 2 users → entityanalytics_entra_id.user
# - 1 device (object id c9d9f9b3-…) → entityanalytics_entra_id.device

[!external_stack] skip 'Skipping external stack test.'
[!exec:jq] skip 'Skipping test requiring absent jq command'

use_stack -profile ${CONFIG_PROFILES}/${PROFILE}

install_agent -profile ${CONFIG_PROFILES}/${PROFILE} -network_name NETWORK_NAME

exec ln -s ${PACKAGE_ROOT}/_dev/deploy/docker entra_id
docker_up -profile ${CONFIG_PROFILES}/${PROFILE} -network ${NETWORK_NAME} entra_id

add_package -profile ${CONFIG_PROFILES}/${PROFILE}

add_package_policy -profile ${CONFIG_PROFILES}/${PROFILE} test_config.yaml ENTITY_DS

# Verify user documents arrive in the user data stream.
get_docs -profile ${CONFIG_PROFILES}/${PROFILE} -want 2 -timeout 5m logs-entityanalytics_entra_id.user-*
cp stdout user_docs.json
exec jq '[.hits.hits[]._source.user.id] | sort[]' user_docs.json
stdout 'aa534e49-edfd-4541-8256-8bbf34f122b4'
stdout 'feb6a386-612a-4ed1-9b13-2adc73074a19'

# Verify device documents arrive in the device data stream.
get_docs -profile ${CONFIG_PROFILES}/${PROFILE} -want 1 -timeout 2m logs-entityanalytics_entra_id.device-*
cp stdout device_docs.json
exec jq '[.hits.hits[]._source.device.id] | unique[]' device_docs.json
stdout 'c9d9f9b3-0c91-4080-b392-78f775903b3a'

# The entity data stream should have no documents (no markers in minimal mode).
get_docs -profile ${CONFIG_PROFILES}/${PROFILE} -want 0 -timeout 30s ${ENTITY_DS}

# Clean up.
remove_package_policy -profile ${CONFIG_PROFILES}/${PROFILE} ${ENTITY_DS}
uninstall_agent -profile ${CONFIG_PROFILES}/${PROFILE} -timeout 1m
docker_down entra_id

-- test_config.yaml --
input: entity-analytics
data_stream:
vars:
tenant_id: TENANT_ID
client_id: CLIENT_ID
secret: foobar
login_endpoint: http://svc-entra_id:8080
api_endpoint: http://svc-entra_id:8080/v1.0
dataset: all
use_minimal_state: true
sync_interval: 24h
update_interval: 15m
Loading
Loading