* ci: add semgrep SAST scanning to CI and pre-commit
Add semgrep static analysis to the security workflow and pre-commit
hooks. Uses `--config auto` for comprehensive coverage across Python,
Dockerfile, and GitHub Actions. The intentional `USER root` in the
Dockerfile (required for bind mount compatibility) is excluded via
`--exclude-rule`.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* ci: pin semgrep container image to digest for zizmor
Pin semgrep/semgrep to sha256 digest (v1.151.0) to satisfy
zizmor's unpinned-images audit.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* ci: add zizmor pre-commit hook for Actions security audit
Matches the existing actionlint pattern — gracefully skips if zizmor
is not installed.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* fix: add explicit bool filters for Ansible 12 jinja2_native compatibility
Ansible 12 enables jinja2_native by default, which means string values
like "true"/"false" are no longer automatically coerced to booleans in
when: conditions and Jinja2 if statements. Add | bool filters to all
boolean variable references in tasks, templates, and handlers.
Also reformats long single-line Jinja2 conditionals into multi-line for
readability, fixes GCE default() calls for native mode, adds help
command to the algo script, and updates test fixtures to register the
bool filter.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* ci: add j2lint for Jinja2 template linting
Add j2lint (aristanetworks/j2lint) to catch syntax errors, spacing
issues, and operator formatting in Jinja2 templates. Integrated into
pre-commit hooks, lint.yml CI, and smart-tests.yml.
Rules S3/S5/S6/S7/V1 are ignored — they enforce conventions
incompatible with Ansible's config-file-embedded templates.
Also fixes int+1 → int + 1 operator spacing in server.conf.j2.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: resolve all ansible-lint warnings and enforce zero-tolerance policy
Fix 18 jinja[spacing] errors across 12 files by moving Jinja2 block
delimiters to prevent YAML >- folding from introducing trailing spaces.
Fix 27 key-order[task] warnings across 17 files by reordering task keys
to canonical order (name → when → tags → environment → become → block).
Promote key-order[task] and yaml[line-length] from warn_list to hard
errors by removing warn_list entirely from .ansible-lint.
Add zero-tolerance warning policy to CLAUDE.md explaining why warnings
are unacceptable in a security tool and documenting resolution order.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Document all variables and environment variable fallbacks that enable
fully headless deployment, so LLM agents and CI pipelines can discover
how to deploy without interactive prompts.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Replace toy data tests with actual config.cfg validation to catch
deprecated instance types before deployment fails. Checks for:
- Hetzner Intel CX series (removed Sept 2024, use AMD CPX)
- DigitalOcean old naming scheme (use s-*vcpu-* format)
- EC2 previous gen types (unavailable in VPC)
Also adds basic sanity checks for required fields and malformed values.
Refs #14730
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Remove sshpubkeys from cloudstack extras to eliminate the vulnerable
python-ecdsa transitive dependency. The sshpubkeys package was declared
but never actually imported or used anywhere in the codebase - CloudStack
role uses Ansible collection modules, not this Python package.
CVE-2024-23342 is a high severity Minerva timing attack affecting all
versions of python-ecdsa with no fix planned by maintainers.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* ci: modernize tooling with prek, ty, and security scanning
Migrate from pre-commit to prek (Rust-native, faster hooks) and add
comprehensive CI improvements for code quality and security.
Changes:
- Replace pre-commit with prek for git hooks
- Add ty type checker (Rust-based, replaces mypy)
- Expand ruff rules: security (S), simplify (SIM), commented code (ERA)
- Add pip-audit workflow for Python dependency CVE scanning
- Add actionlint and zizmor for GitHub Actions linting/security
- Add ruff format check to CI
- Enable stricter ansible-lint rules (no-changed-when, risky-file-permissions)
- Remove obsolete Claude workflow files
- Apply ruff formatting fixes to test files
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix(ci): resolve actionlint install and ty type errors
- Use actionlint's official install script instead of broken URL pattern
- Exclude test mock modules from ty type checking
- Run workflows on push only for main/master to avoid duplicate PR runs
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix(ci): use glob pattern for actionlint, exclude all tests from ty
- actionlint requires *.yml glob, not directory path
- Exclude all tests from ty type checking (test code has looser typing)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix(ci): quote shell variables to fix shellcheck warnings
Fix SC2046/SC2086 warnings in workflow scripts:
- Quote $(uname -r) in apt-get install
- Quote $(pwd) in docker volume mount
- Quote $existing in gh issue comment
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix(ci): move key-order[task] to warn_list
Too many existing violations in the codebase to enable as error.
Move to warn_list for gradual fixes over time.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
The Jinja2 template was building a string that looked like a list
instead of an actual list. When Ansible iterated over this string,
it yielded one character per iteration, causing the region prompt
to display:
1. [
2. '
3. a
...
Now follows the pattern used by other cloud providers (EC2, DigitalOcean,
etc.) by storing region objects directly and accessing .name property.
Fixes#14944
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
The T2 instance family is legacy and not available in newer AWS regions
like il-central-1 (Israel). Changing to t3.micro ensures deployments
work in all regions.
T3 instances offer better performance than T2 and are still covered
under the AWS Free Tier.
Fixes#14947
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Tests (`is X`) return native booleans; filters (`| X`) return data.
Use tests in assert.that and when: clauses.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
- Add 7-day cooldown to protect against supply chain attacks
- Group updates by ecosystem to reduce PR noise
- Change schedule from daily to weekly
- Add Docker ecosystem for base image updates
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
After PR #14908 migrated cloud providers to Ansible collections,
users deploying to Linode (and other providers using collections)
get errors because the collections aren't automatically installed.
This adds automatic collection installation to:
- The `algo` script (runs before playbook execution)
- The Dockerfile (baked into the image at build time)
Fixes#14923🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* Add end-to-end VPN connectivity tests using network namespaces
Addresses #14912
Current integration tests verify that VPN services start, but don't verify
they actually work. This adds true E2E tests using Linux network namespaces
to simulate a client connecting to the server.
New tests verify:
- WireGuard handshake completes and tunnel is functional
- IPsec/StrongSwan service is configured and listening
- DNS resolution works through VPN (172.16.0.1)
- mobileconfig XML files are valid
- CA certificate chain is correct
Changes:
- Add tests/e2e/test-vpn-connectivity.sh - main E2E test script
- Add tests/e2e/README.md - documentation for running tests
- Update integration-tests.yml to run E2E tests after deployment
- Delete tests/legacy-lxd/ - replaced by new E2E tests
- Update .ansible-lint to remove legacy-lxd from excludes
- Rewrite tests/README.md for clarity
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* Fix WireGuard handshake timeout by allowing VPN traffic on veth
The namespace test was timing out because the firewall was blocking
UDP traffic on the veth interface. This adds explicit INPUT rules
to allow WireGuard (51820) and IPsec (500, 4500) traffic.
Also refines the MASQUERADE rule to not apply to bridge-local traffic.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* Use -I instead of -A for iptables rules; add debug output
The firewall rules were being appended (-A) after existing DROP rules
and never matched. Changed to -I to insert at beginning of chain.
Also added debug output to show:
- Server WireGuard peers before client connects
- Server port listening status
- iptables INPUT chain on timeout (to verify rules)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* Work around deployment bug where WireGuard handlers don't fire
The async role execution in server.yml causes handlers not to fire
properly. This workaround restarts WireGuard if no peers are found,
ensuring the peer configuration is loaded.
Root cause: import_role with async: 300, poll: 0 breaks handler
notification flow. The 'restart wireguard' handler is notified but
never executed because the async context loses track of handlers.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* Add packet capture and rp_filter diagnostics to debug WireGuard handshake
- Disable reverse path filtering on veth interface (can drop packets)
- Add tcpdump capture to see if UDP packets are arriving
- Show host and namespace routing tables
- Add route debugging to error output
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* Add PersistentKeepalive to trigger WireGuard handshake
WireGuard only initiates a handshake when there's outgoing traffic or
a keepalive timer fires. Without PersistentKeepalive, the test was
waiting forever because no traffic was being sent through the tunnel
(Table=off prevents route creation).
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* Clean up verbose debug output from WireGuard tests
Remove routing table and rp_filter debug output that was printed on every
run. Keep the packet capture and detailed error diagnostics that are only
shown on failure.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* Document configuration assumptions in E2E test README
Add explicit documentation about the hardcoded IP addresses and test user
requirements as suggested in code review. This helps users understand what
default values are expected and why tests might fail on custom configurations.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* Remove unused pip cache from integration tests workflow
We use uv for dependency management, not pip, so the pip cache setting
was causing warnings about missing cache folders.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
- Update error message to present troubleshooting and issue filing equally
- Remove dead GitHub Discussions link from troubleshooting page
- Simplify bug report template from 6 sections to 3
- Add config.yml to show troubleshooting link in issue chooser
- Lead CONTRIBUTING.md with "We welcome bug reports!"
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude <noreply@anthropic.com>
Preparatory work for issue #14810 (stroke to swanctl migration).
This change adds AF_UNIX to the allowed socket address families in the
StrongSwan SystemD service hardening configuration. This is required for
the VICI socket (/var/run/charon.vici) that swanctl uses to communicate
with the charon daemon.
The change is:
- Backward-compatible (doesn't affect current stroke interface)
- Zero-risk (additive change only)
- Future-proof (enables eventual swanctl migration)
Full migration is deferred until Ubuntu 26.04 approaches, per research
findings documented in #14810.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude <noreply@anthropic.com>
* Migrate Azure module to azure.azcollection
Replace deprecated azure_rm_deployment module with FQCN:
- azure_rm_deployment → azure.azcollection.azure_rm_deployment
Changes:
- Add azure.azcollection (>=3.0.0) to requirements.yml
- Update roles/cloud-azure/tasks/main.yml to use FQCN
No parameter changes needed - the collection module supports all
existing parameters via aliases (deployment_name, resource_group_name,
client_id, secret, tenant, subscription_id).
Closes#14904🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* Fix trailing comma in Azure deployment template
Remove invalid trailing comma in outputs section (line 222).
This was a pre-existing JSON syntax error.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
- Replace ignore_errors: true with failed_when: false in 5 files
(main.yml, users.yml, ubuntu.yml, umount.yml, test-wireguard-real-async.yml)
- Add pytest.ini configuration for test discovery
- Add tests/conftest.py with shared fixtures and mock helpers
The failed_when: false pattern is preferred by ansible-lint as it
explicitly indicates expected failure handling rather than silently
ignoring all errors.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude <noreply@anthropic.com>
* Refactor CLAUDE.md: add quality gates, reduce redundancy
Analyzed the last 25 PRs to identify patterns where better guidance
could have prevented iteration. Key findings:
- PRs had lint failures caught after submission
- with_items→loop conversions broke list flattening
- Missing input validation and explicit file modes
- Duplicate DNS documentation in 3 places
Changes:
- Add Quality Gates section with mandatory pre-submission checks
- Add Design Requirements checklist (validate inputs, explicit modes)
- Add Ansible Pitfalls section (with_items vs loop, path variables)
- Add Self-Review Checklist
- Consolidate DNS architecture from 3 sections into 1
- Remove redundant sections (Final Notes, User Profiles, duplicate philosophy)
- Condense linting rules into table format
Result: 496 → 307 lines (-38%) while adding actionable new guidance
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* Add Jinja2 native mode guidance (Ansible 12 compatibility)
Analysis of PRs 26-50 revealed that 28% (7 PRs) were fixing Ansible 12
compatibility issues caused by jinja2_native mode being always enabled.
Added guidance for:
- Boolean conditionals requiring actual booleans
- No nested templates in lookup()
- JSON files needing explicit | from_json parsing
- default() not triggering on empty strings (need true param)
- Complex Jinja loops breaking in set_fact
Also added "Undocumented workarounds" to What to Avoid section based on
Scaleway/Vultr upstream module bug patterns.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* Fix Ansible version and add OpenSSL explanation
- Update Ansible version from "v9.x" to "v12+" for consistency
with Jinja2 native mode guidance
- Add one-line explanation to OpenSSL snippet for clarity
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* Soften language and add troubleshooting cross-reference
- Remove "CRITICAL" and "Critical Gotcha" labels
- Change "90% of routing issues are DNS" to "Many routing issues"
- Add link to docs/troubleshooting.md for comprehensive diagnostics
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* Clarify 'verify before encoding' philosophy statement
Change ambiguous "Verify before encoding" (encoding could mean data
encoding) to clearer "Test assumptions - run the code before committing"
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
* Fix Docker --cap-drop=all and add multi-arch support
Fixes#14899
## Bug Fix: CAP_DROP_ALL Permission Denied
The `chown -R algo:algo /algo` line added in commit 2ab57c3 broke
`--cap-drop=all` functionality. When running as root with all
capabilities dropped, root loses CAP_DAC_OVERRIDE and cannot write
to files owned by other users.
The fix removes the unnecessary chown since:
- Container runs as USER root
- algo-docker.sh writes to /algo/config.cfg at runtime
- /algo must be root-owned for --cap-drop=all to work
## Multi-arch Support
The Docker image was only built for linux/amd64. Added:
- QEMU setup for ARM emulation
- Docker Buildx for multi-platform builds
- platforms: linux/amd64,linux/arm64
This enables native support for Apple Silicon Macs.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* Update Docker actions to latest versions
- setup-qemu-action: v3.6.0 → v3.7.0
- setup-buildx-action: v3.10.0 → v3.11.1
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
Enhance the local installation warning to require typing 'yes' to
proceed, making risks impossible to miss. Users following external
tutorials often don't see the documentation warning until after
their server is already modified.
Changes:
- Replace press-ENTER prompt with explicit 'yes' confirmation
- List specific consequences (firewall, DNS, no uninstall)
- Add local_install_confirmed variable for automation
- Document skip method in deploy-to-ubuntu.md
Closes#14362🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude <noreply@anthropic.com>