Repository navigation
fix(baseline): keep node connectivity under DevSec CIS hardening - #24
Conversation
devsec.hardening.os_hardening applies CIS network sysctls (and a ufw config template) that can sever a deCDN node's connectivity. baseline applied it with no overrides, so three defaults degraded/endangered a node: - IPv6 RA/autoconf disabled (accept_ra/autoconf=0, plus accept_ra_pinfo/ accept_ra_defrtr=0): SLAAC-addressed hosts (common on VPS) lose their IPv6 address + default route on the next RA cycle / reboot. - Strict reverse-path filtering (rp_filter=1) silently drops inbound QUIC on multi-homed / policy-routed hosts. - os_hardening templates a DROP-policy /etc/default/ufw, inviting an operator to enable ufw and flush the nftables ruleset. Feed os_hardening a sysctl_overwrite (merged last, so it wins only for the relaxed keys) via two opt-in knobs with node-safe defaults, and disable its ufw template: - baseline_preserve_ipv6_autoconf (default true) re-enables the full RA path SLAAC needs, incl. accept_ra_pinfo/accept_ra_defrtr on `default` so a rebooted host reacquires its address + route. - baseline_rp_filter_loose (default false) optionally sets rp_filter=2. - ufw_manage_defaults: false stops the misleading /etc/default/ufw write. An is-boolean assert on both knobs fails loud instead of letting a mistyped value silently coerce to false (matching the decdn_node guards). Documented in ansible/README.md and roles/baseline/README.md, incl. the container-VPS (lxc/openvz) no-op caveat. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Warning Review limit reached
Next review available in: 11 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (4)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Code Review
This pull request introduces sysctl overrides for DevSec os_hardening in the Ansible baseline role to prevent security hardening from disrupting node connectivity. Specifically, it adds configuration options to preserve IPv6 RA/autoconf (SLAAC) and to optionally loosen reverse-path filtering for multi-homed hosts, alongside boolean validation for these settings. It also disables the default UFW management since nftables is used as the firewall. The review feedback highlights an issue where setting router_solicitations to -1 disables Router Solicitations, potentially causing significant delays in acquiring IPv6 addresses; it is recommended to change this value to the kernel default of 3.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| net.ipv6.conf.all.router_solicitations: -1 | ||
| net.ipv6.conf.default.router_solicitations: -1 |
There was a problem hiding this comment.
Setting router_solicitations to -1 disables sending Router Solicitations in the Linux kernel. In Linux, any value less than or equal to 0 for router_solicitations is treated as disabled, meaning the host will not actively solicit Router Advertisements (RAs) on boot or interface up. This can cause significant delays (up to 10 minutes) in acquiring a SLAAC IPv6 address and default route, as the host must wait for the next unsolicited periodic RA from the router. To ensure prompt IPv6 autoconfiguration, this should be set to the Linux kernel default of 3.
net.ipv6.conf.all.router_solicitations: 3
net.ipv6.conf.default.router_solicitations: 3There was a problem hiding this comment.
Verified on a stock (non-hardened) host: net.ipv6.conf.{all,default}.router_solicitations defaults to -1, not 3. Per the kernel ip-sysctl semantics, -1 = unlimited Router Solicitations (keep sending until a router answers) and 0 = send none. os_hardening sets it to 0 — that 0 is the "wait for the next unsolicited RA" delay described here; this override restores -1, the kernel default and the most robust value for prompt SLAAC acquisition. 3 would send only 3 RS then stop, so -1 is strictly better here — keeping it. (RFC 4861 MAX_RTR_SOLICITATIONS=3 is a protocol constant, but Linux's sysctl default is -1.)
There was a problem hiding this comment.
Pull request overview
This PR adjusts the Ansible baseline role’s DevSec os_hardening integration to preserve deCDN node connectivity under CIS hardening by applying targeted sysctl overrides (IPv6 RA/SLAAC and optional loose rp_filter) and disabling os_hardening’s ufw defaults template.
Changes:
- Add boolean validation and computed
sysctl_overwriteinputs todevsec.hardening.os_hardeningto keep IPv6 SLAAC and (optionally) multi-homed QUIC reachability working. - Disable
os_hardening’s ufw defaults management to avoid writing a misleading/etc/default/ufw. - Document the new baseline knobs and the rationale in the baseline role README and top-level
ansible/README.md.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| ansible/roles/baseline/tasks/main.yml | Adds boolean guards and assembles/passes sysctl_overwrite, plus disables ufw defaults in os_hardening. |
| ansible/roles/baseline/README.md | Documents connectivity-preserving hardening overrides and new role variables. |
| ansible/roles/baseline/defaults/main.yml | Introduces defaulted knobs for IPv6 RA/autoconf preservation and optional loose rp_filter. |
| ansible/README.md | Updates security model documentation to reflect the hardening overrides and ufw template disablement. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| # The `| bool` filter is non-strict: an unparseable value (typo, quoted string, trailing | ||
| # space) coerces to false with no error — which would silently DROP the connectivity | ||
| # override and let CIS hardening sever IPv6/QUIC. Fail loud instead, matching the | ||
| # `is boolean` guards in roles/decdn_node/tasks/main.yml. |
There was a problem hiding this comment.
Ansible's | bool is not Jinja truthiness. {{ x | bool }} calls boolean(x, strict=False), which returns True/False only for the recognized token sets and False for everything else. Verified against this repo's Ansible: 'flase'|bool, 'yes please'|bool, 'true '|bool, ' no '|bool all evaluate to False. So an unrecognized/typo'd value drops toward false → the override is skipped and CIS hardening is applied → the "silently DROP the connectivity override" failure the comment describes. Native Jinja {% if 'garbage' %} is truthy, but | bool is not that. Keeping the comment as written.
| fail_msg: >- | ||
| baseline_preserve_ipv6_autoconf and baseline_rp_filter_loose must be real | ||
| booleans (true/false), not quoted strings — each gates a `| bool` conditional | ||
| that decides whether the node-connectivity sysctl overrides apply. A mistyped | ||
| value silently coerces to false and lets CIS hardening sever IPv6/QUIC. |
There was a problem hiding this comment.
Same basis as the comment above: | bool returns False for unrecognized strings in this repo's Ansible ('flase'|bool → False, etc.), so "silently coerces to false" is accurate for the | bool filter (not Jinja truthiness). The assert itself uses is boolean, which is stricter still — it rejects even recognized strings like 'true'/'yes', forcing a real bool. Leaving the fail_msg as-is.
Context
The shared
baselinerole appliesdevsec.hardening.os_hardeningwith no overrides. Investigation found three of its CIS defaults degrade or endanger a deCDN node — the firewall path itself (nftables default-deny + udp/4433 QUIC hole, loopback metrics/admin) is coherent, but hardening can silently break node reachability.Problems
accept_ra,autoconf, andaccept_ra_pinfo/accept_ra_defrtr. On SLAAC-addressed hosts (common on VPS) the node loses its IPv6 address and default route on the next RA cycle / reboot. Re-enablingaccept_ra/autoconfalone is insufficient — withoutaccept_ra_pinfono prefix is learned (no address) and withoutaccept_ra_defrtrno default route is installed.rp_filter=1) silently drops inbound QUIC on multi-homed / policy-routed hosts./etc/default/ufw(inert, but invites an operator to enable ufw and flush the nftables ruleset).Fix
Feed os_hardening a
sysctl_overwrite(it merges last, so it wins only for the relaxed keys — everything else keeps the hardened value) via two opt-in knobs with node-safe defaults, plus disable its ufw template:baseline_preserve_ipv6_autoconf(defaulttrue) — re-enables the full RA path SLAAC needs, incl.accept_ra_pinfo/accept_ra_defrtrondefaultso a rebooted host reacquires address + route. Setfalsefor static-IPv6 hosts.baseline_rp_filter_loose(defaultfalse) — optionally setsrp_filter=2for multi-homed nodes.ufw_manage_defaults: false— stops the misleading/etc/default/ufwwrite.An
is booleanassert on both knobs fails loud instead of letting a mistyped/quoted value silently coerce tofalse(matching the existing guards inroles/decdn_node/tasks/main.yml).Review
Findings 1 (IPv6 incompleteness) and 2 (boolean guard) were surfaced by a multi-agent PR review and are folded into this branch. The ufw wording and a container-VPS (
lxc/openvz) no-op caveat were tightened in the docs.Verification
ansible-playbook playbooks/site.yml --syntax-check→ OKansible-lint roles/baseline playbooks/site.yml→ 0 failures (production profile)combineexpression runtime-checked across knob states (default = 9 keys incl.pinfo=1/defrtr=1; both-off ={}safe no-op); boolean assert verifiedip -6 addr) and default route (ip -6 route) survive, andss -lun | grep 4433stays reachable.🤖 Generated with Claude Code