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
- Configure LDAP sync with a sync group, for example
VPN-Defguard.
- 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.
- 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.
- In Active Directory, add the user to
DG-Standard. Do not touch defguard.
- Wait for the next sync run. The membership appears in
group_user and in the UI.
- 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.
Environment
ldap_is_authoritative = true,ldap_sync_interval = 30acl_enabled = trueandacl_default_allow = falseProblem
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 tothe gateway immediately.
Steps to reproduce
VPN-Defguard.DG-Standardto reach one destination,for example 172.17.1.7 with tcp 80 and 445. Apply the rule.
The user is a member of the sync group but not of
DG-Standard.DG-Standard. Do not touch defguard.group_userand in the UI.nft list table inet DEFGUARD-wg0on the gateway and the core log.Expected result
After the sync run, the allow rules for
DG-Standardcontain 172.30.248.5.Actual result
Measured on 2026-09-08:
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-assignwith{"groups": [], "users": []}produces the log lineimmediately. The gateway logs
Reconfigured firewall with new configurationand the source setbecomes
{ 172.30.248.2, 172.30.248.4/31 }.Cause
In
crates/defguard_core/src/enterprise/ldap/sync.rs,apply_user_group_sync_changes()callsUser::add_to_group()andUser::remove_from_group()and emits activity log events. It has nogateway channel and never calls
sync_all_networks().do_ldap_sync()incrates/defguard_core/src/enterprise/ldap/mod.rsonly callsupdate_counts()after the sync.
sync_all_networks(), which emitsGatewayCommand::FirewallConfigChanged, is only called from thehandlers 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 newconfig.
User deletion through the LDAP sync has a related gap.
sync.rscallsuser.delete(), which removesthe rows through the foreign key cascade.
delete_user_and_cleanup_devices()is not used, so theWireGuard 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 groupmembership 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_activeand the device IP assignments. When the hash changes, it calls
POST /api/v1/groups-assignwithempty lists using an API token of a local admin user. This regenerates the config through the
normal handler path.