Skip to content

LDAP sync does not push firewall config after group membership changes #3643

Description

@Ramalama2

Environment

  • defguard core 2.1.0, gateway 2.1.x, installed from the APT repository on Ubuntu 26.04
  • Enterprise license, LDAP sync against Active Directory
  • ldap_is_authoritative = true, ldap_sync_interval = 30
  • One location with acl_enabled = true and acl_default_allow = false
  • Gateway manages nftables

Problem

Group membership changes that arrive through the periodic LDAP sync are written to the database
and are visible in the web UI. The gateway is not notified. Its nftables rules keep the old set of
device IPs until some unrelated event regenerates the firewall config.

Events that do regenerate the config: enrolling a device, applying an ACL rule, changing a group or
a user through the UI or the REST API, or a gateway reconnect.

The same membership change made through the UI or through POST /api/v1/group/{id} is pushed to
the gateway immediately.

Steps to reproduce

  1. Configure LDAP sync with a sync group, for example VPN-Defguard.
  2. Create an ACL rule that allows group DG-Standard to reach one destination,
    for example 172.17.1.7 with tcp 80 and 445. Apply the rule.
  3. Have a synced user with one enrolled device (here 172.30.248.5).
    The user is a member of the sync group but not of DG-Standard.
  4. In Active Directory, add the user to DG-Standard. Do not touch defguard.
  5. Wait for the next sync run. The membership appears in group_user and in the UI.
  6. Inspect nft list table inet DEFGUARD-wg0 on the gateway and the core log.

Expected result

After the sync run, the allow rules for DG-Standard contain 172.30.248.5.

Actual result

Measured on 2026-09-08:

03:23:41  LDAP sync writes the membership to group_user
03:24:11  nft source set still { 172.30.248.2, 172.30.248.4 }
03:24:41  unchanged
03:25:41  unchanged
03:26:41  unchanged

During these three minutes the core log contains no line
Generating firewall config for location. The device cannot reach 172.17.1.7.

Calling POST /api/v1/groups-assign with {"groups": [], "users": []} produces the log line
immediately. The gateway logs Reconfigured firewall with new configuration and the source set
becomes { 172.30.248.2, 172.30.248.4/31 }.

Cause

In crates/defguard_core/src/enterprise/ldap/sync.rs, apply_user_group_sync_changes() calls
User::add_to_group() and User::remove_from_group() and emits activity log events. It has no
gateway channel and never calls sync_all_networks().

do_ldap_sync() in crates/defguard_core/src/enterprise/ldap/mod.rs only calls update_counts()
after the sync.

sync_all_networks(), which emits GatewayCommand::FirewallConfigChanged, is only called from the
handlers in crates/defguard_core/src/handlers/group.rs.

The firewall config resolves group rules to concrete device IPs in
crates/defguard_core/src/enterprise/firewall/mod.rs. A membership change therefore requires a new
config.

User deletion through the LDAP sync has a related gap. sync.rs calls user.delete(), which removes
the rows through the foreign key cascade. delete_user_and_cleanup_devices() is not used, so the
WireGuard peer and its IP stay on the gateway until the next unrelated regeneration.

Suggested fix

Call sync_all_networks() once at the end of a sync run whenever the run applied at least one group
membership change or created or deleted a user. The gateway already compares incoming configs with
its current state, so an extra call without changes costs nothing.

For deleted users, use delete_user_and_cleanup_devices() so the gateway removes the peer.

Workaround

A cron job runs every 30 seconds. It computes a hash over group_user, group, user.is_active
and the device IP assignments. When the hash changes, it calls POST /api/v1/groups-assign with
empty lists using an API token of a local admin user. This regenerates the config through the
normal handler path.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

bugSomething isn't working

Type

No type

Projects

Relationships

None yet

Development

No branches or pull requests

Issue actions