feat(ansible): install Koncreet host hardening with an Ubuntu 26.04 patch - #28
Merged
Merged
Conversation
…ered on Every apply resolves the latest jimididit/koncreet release, verifies koncreet.tar.gz against the GitHub release-asset digest, extracts it as root into /usr/local/lib/code-factory/koncreet/<version>-<patch hash>/ and links /usr/local/bin/koncreet. Nothing runs it. patches/koncreet/ubuntu-26.04.patch opens the OS gate and doctor for Ubuntu 26.04 and restores the last SSH client IP fallback there (26.04 keeps no utmp, so who -m prints nothing; ask logind instead). The same change is the ubuntu-26.04 branch of undeemed/koncreet. Apply layers the patch on each new release and reports applied, skipped because upstream already supports 26.04, or skipped because it no longer applies; the patch never fails the apply. /etc/koncreet.conf is rendered once for the fleet: the operator account as the sudo user with its own authorized_keys, SSH kept open, 41641/udp for Tailscale. docs/security.md has the one-time manual run, the tailscale0 ufw rule, and upstream's lockout recovery.
A root-run apply now leaves user= and pubkey_file= commented for the operator to fill in, and the template says why the operator login, not factory.user, is the sudo user.
…le and factory user
…te fakeroot for tests
undeemed
force-pushed
the
fm/cf-koncreet-u26-k4
branch
from
October 1, 2026 10:53
2c3c15e to
b3d31fe
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Intent
"https://github.com/jimididit/koncreet add this as well for new machine"
"wtf? can we make a ubuntu 26 version then"
"dont need upstream pr, just make a fork"
Context for reading those asks: Koncreet (jimididit/koncreet, MIT, plain Bash, latest release v0.2.4) is a first-hour hardening toolkit for a fresh Linux VPS: a baseline (sudo user, SSH keys, sysctl, swap, journald, timezone/NTP), a ufw default-deny firewall, fail2ban, unattended security updates, and an SSH hardening module that turns off password and root login only once a sudo user with keys already exists. Its OS gate (
lib/os.sh) refuses everything except Debian 12/13 and Ubuntu 22.04/24.04. This host and Code-Factory's current target are Ubuntu 26.04 LTS ("resolute"), so upstream Koncreet refuses to run there; the second ask is to make it work on 26.04. Code-Factory does no firewall or SSH hardening today; its docs say UFW and sshd stay manual (ansible/tasks/tailscale.yml). The operator's standing model is that tools track the latest upstream release, with his own changes layered on top separately. The third ask replaces an upstream pull request with a fork of jimididit/koncreet that carries the Ubuntu 26.04 change.What Changed
ansible/tasks/koncreet.yml, imported fromsite.yml). It runs on hosts withstart_services: trueand not in the container image.scripts/install_tools.pyresolves the latestjimididit/koncreetrelease, verified against its GitHub SHA-256 digest. Any lookup, digest or download failure skips Koncreet with a warning and the rest of the apply continues. Apply installs it as root under/usr/local/lib/code-factory/koncreet/<version>-<patch hash>, links/usr/local/bin/koncreet, and renders/etc/koncreet.conffrom the newkoncreet.conf.j2. The config never makes the factory user a sudo user. Apply never runs Koncreet. The hand-run steps and lockout recovery are indocs/security.md.patches/koncreet/ubuntu-26.04.patch, layered on each release. It adds 26.04 to the OS gate anddoctor, and falls back tologinctlfor the SSH client address because 26.04 has no utmp. The task prints whether the patch applied, was skipped because the release already supports 26.04, or was skipped because it no longer applies.install_tools.pynow rejects release tags that are not plain versions (e.g.v1;id) for every tool, andpreflight.ymlwarns about optional tools that were skipped. Docs (docs/security.md,dependencies.md,architecture.md,CONTRIBUTING.md) are updated. New tests cover the Koncreet resolution and skip paths, config rendering per tailscale profile and apply user, and the digest-failure and--checkbehavior of the playbook tasks. CI installsfakerootso those tests run unprivileged.Risk Assessment
✅ Low: Koncreet is installed but never run by apply, and every failure (lookup, digest, download, patch, link) is rescued with a warning, so apply continues. The version regex, the digest-gated root-owned install and the factory-user guard in the config template all hold on the paths I traced. I found no source-backed defect or intent contradiction.
Testing
The koncreet files (
koncreet.yml, the template, the patch,preflight.yml, the container config) are byte-identical to d72af44, which the earlier rounds validated. Only base-merge changes landed after it, so the reused evidence still matches the tree. This run I drove the real playbook against the real v0.2.4 release. The fakeroot sandbox could not write /usr/local/bin and was rescued at the link step, so I use it only as evidence for patch-applied, digest-mismatch skip and 404 skip, and I relabeled that file with a scope note. Install, link, config render and idempotence are shown by the mount-namespace run at real paths and by a fresh 24.04 container, both with rescued=0. A mismatch skips koncreet, installs and links nothing, writes no config, and the next task still runs. A correct digest then installs it. A second apply keeps an edit made to the rendered config. The config rules for tailscale on or off, the factory user and root hold. Patched koncreet passes the OS gate on 26.04 and upstream does not, and the fork diff is byte-identical to the shipped patch. The 20 targeted tests pass. Three scenarios are marked untested because the payload does not establish a live result for them: the optional-lookup failure scenario (resolver run only against injected faults in pytest; the live 403 run is from an earlier round), the worker-image exclusion scenario (not rebuilt; rests on a passing test and earlier-round evidence), and the digest-test and CI scenario (workflow not run; YAML parsed and mutation proof from an earlier round). There is no UI surface, so no screenshots.koncreet doctorsays 'OS ubuntu 26.04 supported'lib/os.shhas 0 matches for '26.04'.git ls-remoteshowed refs/heads/ubuntu-26.04.cmpof the compare diff against the patch printed BYTE-IDENTICAL (68 lines).Evidence: Targeted koncreet pytest at HEAD (20 passed, 0 skipped)
Evidence: Mount-namespace run at real paths on 26.04: mismatch skip, recovery, link, doctor, dry-run, idempotence, config variants
Evidence: Ubuntu 24.04 container at HEAD: mismatch skip, then install, link and doctor
Evidence: Fakeroot run, scoped to patch-applied, digest-mismatch skip and 404 skip only (not an install or idempotence proof; scope note inside)
Evidence: Fork branch diff byte-identical to shipped patch (earlier round)
Pipeline
Updates from git push no-mistakes
... (3 earlier update rounds omitted to keep the PR body within GitHub's 65536-char limit; full history is in the run log.)
🔧 **Review** - 3 issues found → auto-fixed (2) ✅
🔧 Fix applied.
4 warnings still open:
ansible/group_vars/all.yml:189-'koncreet'is added tofactory_installer_alsounconditionally, with no profile gate or opt-out. Resolution happens in preflight and fails hard:verified()raises if the release has no SHA-256 digest or the asset is renamed or missing, andfetch()raises on a 404 or rate limit. Concrete sequence: upstream publishes a release withoutkoncreet.tar.gz, or GitHub rate-limits the lookup.resolve_latestraises, preflight aborts, and the whole./factory applystops for every profile (tailscale, agents and the rest). The tool is optional and is never run by apply, and the author made the patch step non-fatal on purpose, so the resolution and download steps contradict that goal. The unconditional install also reaches the Docker image build, which runs the same playbook, where a ufw/sshd hardening toolkit cannot be used. The intent was Koncreet 'for new machine'. A profile gate, or a soft-fail that skips Koncreet with a warning, would fix this. Both change behavior, so this needs the author's decision.ansible/templates/koncreet.conf.j2:1- Simplification: the intent asks for Koncreet to be added to the new-machine setup and for Ubuntu 26.04 support. No requirement asks for a rendered/etc/koncreet.conf. Rendering it adds policy the user never specified: a module list that leaves outssh,firewall_ports=41641/udpopened even when the Tailscale profile is off, and making the account that ran apply the sudo user viaansible_user_id/ansible_user_dir. It also adds a branch for apply-as-root and thefactory_koncreet_configvariable. The template comment says 'Not factory.user', but nothing guardsansible_user_id == factory_cfg.user, whichfactory_become_targettreats as a supported mode. The narrower form is to install only the release (upstream already shipsshare/koncreet.conf.examplein the tarball) and let the operator copy and edit it. The remedy is removal of the template task (ansible/tasks/koncreet.yml:96),factory_koncreet_config(ansible/group_vars/all.yml:366) and the matching docs/security.md text.ansible/templates/koncreet.conf.j2:1- Still open after the fix round, which only changed the version handling. No intent requirement asks for a rendered/etc/koncreet.conf. The intent asks to add Koncreet to the new-machine setup and to make it work on Ubuntu 26.04. The template adds policy nobody asked for: amoduleslist withoutssh, andfirewall_ports=41641/udpopened even when the tailscale profile is off. It also makes the account that ran apply (ansible_user_id/ansible_user_dir, lines 17-19) the sudo user, and adds a root branch (lines 20-24). The key names match upstream'slib/config.sh, so this is not a correctness bug. A concrete defect sits inside the component. The comment at line 15 says 'Not factory.user ... must not gain sudo through this file', but nothing guardsansible_user_id == factory_cfg.user.factory_become_target(ansible/group_vars/all.yml:113) treats that case as supported. Running./factory applyas the agent account rendersuser=<agent>, and the operator then follows the docs and makes the agent a sudo user. The smallest honest remedy is to remove the component, not to add the guard. Narrower form: install only the release, since upstream shipsshare/koncreet.conf.examplein the tarball, and let the operator copy and edit it. Removal covers the template task (ansible/tasks/koncreet.yml:96-104),factory_koncreet_config(ansible/group_vars/all.yml:366), the template file, and the/etc/koncreet.conftext and-c /etc/koncreet.confcommands in docs/security.md (Host hardening section) and docs/dependencies.md.ansible/group_vars/all.yml:190- Still open.'koncreet'is added tofactory_installer_alsofor every profile, with no gate or opt-out. Resolution happens in preflight (ansible/tasks/preflight.yml:93-98), before any other work.verified()raises when the release lists no SHA-256 or nokoncreet.tar.gzasset.fetch()raises on a 404 or on rate limiting. Concrete sequence: GitHub rate-limits the lookup, or upstream publishes a release without the asset.resolve_latestraises and preflight aborts the whole./factory applyfor every profile, including hosts that never use the hardening tool. Koncreet is never run by apply. The same unconditional install reaches the Docker image build (the Dockerfile runs the same playbook), where a ufw/sshd toolkit has no use. The intent says to add it 'for new machine', so installing on every host may be intended. Whether an upstream Koncreet failure should block provisioning is a product decision. Options are a profile gate, or a soft-fail that skips Koncreet with a warning. Either option changes behavior, so the remedy needs the author's authorization. The same unconditional-resolution path is also visible at ansible/site.yml:112-115, where the import has nowhen:.🔧 Fix applied.
✅ Re-checked - no issues remain.
✅ No issues found.
🔧 **Test** - 1 issue found → auto-fixed → no changes applied ✅
tests/test_configuration.py:664-test_a_koncreet_tarball_that_fails_its_digest_is_skipped_and_apply_continuespassed for the wrong reason when run unprivileged.koncreet.ymlcreates root-owned directories, and withansible_become=Falseand no root thechownfailed first. The skip warning readCreate the koncreet release directory: chown failed, so the download was never checked. A correct digest gave the same skipped outcome (digest-test-vacuity.txt). I changedtests/test_configuration.pyso_run_koncreet_taskswraps the run infakerootwhen not root. The digest test also asserts the skip reason isFetch the koncreet tarball: The checksum for .... With theget_urlchecksum line removed, the fixed test fails. With it restored, it passes (digest-test-mutation.txt). The--checkplanning test needs no root, so the fakeroot requirement applies only to runs that chown, and the planning test still runs when fakeroot is missing (digest-test-fakeroot-presence.txt). Product code is unchanged. One point needs a decision..github/workflows/ci.ymlrunsuv run pytestas a non-root user onubuntu-24.04and has no step that installs fakeroot. CI therefore runs the digest test only if the runner image already has fakeroot. Otherwise the test skips, which shows only assunless-rsis passed, and CI stays green with no digest coverage. I could not check the runner image from here. Options: add a fakeroot install step toci.yml, or make the test fail instead of skip whenCIis set. I did not change the workflow.--also obscura --resolvefailed with exit 1. The first rate-limited site.yml run also aborted with 'cannot resolve the latest gh release (HTTP 403)'.site.yml --tags always,koncreet)site.ymlskip koncreet rather than abort--check) installs nothing and warns of nothingloginctlandwhocommands, not a re…koncreet applywould install ufw and change the operator's host firewall, and the workspace boundary forbids system-state changes outside the worktree. The sandbox cannot drive it either: it….venv/bin/pytest -k koncreet tests/test_install_tools.py tests/test_configuration.py(20 passed before my test change; 8 koncreet config tests passed after).venv/bin/pytest -k "digest or planning" tests/test_configuration.pywith fakeroot on PATH (2 passed) and with it hidden (digest test skips with a reason, planning test passes)install_tools.py --resolve --tools herdr,node,bun,uv --also koncreetagainst the real GitHub API: resolved v0.2.4 with the published SHA-256The same resolve with faults injected into the koncreet GitHub call only (403 rate limit, no digest, no asset, unsafe tag): each skipped koncreet with a warning and exited 0. Offlineobscurastill hard-fails.Realansible/tasks/koncreet.ymlwith the real v0.2.4 tarball, real digest and real patch, run as root in a bwrap user namespace with/usr/localredirected: installed, linked, patch applied, config rendered. A second run was a no-op for the koncreet tasks.Installed patchedkoncreet doctorand--dry-run apply -c <rendered conf>on this Ubuntu 26.04 host, compared with unpatched upstream, which refuses withUnsupported OS: ubuntu 26.04(exit 2)Patchedkoncreet_ssh_client_ipsourced from the real library with stubbedloginctlandwho(stubs, not live): the logind fallback works whenwhois empty; junk and empty inputs return no IPFailure scenarios through the real tasks: wrong digest on the real tarball, 404 asset URL,--checkplan mode, a release that already supports 26.04, a release the patch no longer applies toRendered/etc/koncreet.confmatrix: tailscale on/off, apply user equal to the factory user, root, and re-apply not overwriting an operator editRealsite.yml --tags always,koncreet: the host config installed koncreet; rate-limit and no-digest faults skipped it with failed=0; the container config never looked it upCompared theundeemed/koncreetubuntu-26.04fork branch withpatches/koncreet/ubuntu-26.04.patch(identical change lines)Mutation check (pytest, not live): removed theget_urlchecksum line, the fixed digest test failed, then restored it and the test passedReadansible/tasks/koncreet.ymland the upstream tree for anyufw reset(code reading only, not driven)🔧 Fix applied.
3 issues (1 error, 2 warnings) still open:
git -C ~/.no-mistakes/worktrees/5a80d6e36b53/01M3V8HZDS2XJ51RCQ6HFTMFPR log -p 58f2d119db1f974b6ab24c39f48554cf72fe8bb7..d72af44269fad06a7e835dc8750dc46db4d0cdd9). Respond with fix to validate it, or abort.tests/test_configuration.py:664-test_a_koncreet_tarball_that_fails_its_digest_is_skipped_and_apply_continuespassed for the wrong reason when run unprivileged.koncreet.ymlcreates root-owned directories, and withansible_become=Falseand no root thechownfailed first. The skip warning readCreate the koncreet release directory: chown failed, so the download was never checked. A correct digest gave the same skipped outcome (digest-test-vacuity.txt). I changedtests/test_configuration.pyso_run_koncreet_taskswraps the run infakerootwhen not root. The digest test also asserts the skip reason isFetch the koncreet tarball: The checksum for .... With theget_urlchecksum line removed, the fixed test fails. With it restored, it passes (digest-test-mutation.txt). The--checkplanning test needs no root, so the fakeroot requirement applies only to runs that chown, and the planning test still runs when fakeroot is missing (digest-test-fakeroot-presence.txt). Product code is unchanged. One point needs a decision..github/workflows/ci.ymlrunsuv run pytestas a non-root user onubuntu-24.04and has no step that installs fakeroot. CI therefore runs the digest test only if the runner image already has fakeroot. Otherwise the test skips, which shows only assunless-rsis passed, and CI stays green with no digest coverage. I could not check the runner image from here. Options: add a fakeroot install step toci.yml, or make the test fail instead of skip whenCIis set. I did not change the workflow.--also obscura --resolvefailed with exit 1. The first rate-limited site.yml run also aborted with 'cannot resolve the latest gh release (HTTP 403)'.site.yml --tags always,koncreet)site.ymlskip koncreet rather than abort--check) installs nothing and warns of nothingloginctlandwhocommands, not a re…koncreet applywould install ufw and change the operator's host firewall, and the workspace boundary forbids system-state changes outside the worktree. The sandbox cannot drive it either: it….venv/bin/pytest -k koncreet tests/test_install_tools.py tests/test_configuration.py(20 passed before my test change; 8 koncreet config tests passed after).venv/bin/pytest -k "digest or planning" tests/test_configuration.pywith fakeroot on PATH (2 passed) and with it hidden (digest test skips with a reason, planning test passes)install_tools.py --resolve --tools herdr,node,bun,uv --also koncreetagainst the real GitHub API: resolved v0.2.4 with the published SHA-256The same resolve with faults injected into the koncreet GitHub call only (403 rate limit, no digest, no asset, unsafe tag): each skipped koncreet with a warning and exited 0. Offlineobscurastill hard-fails.Realansible/tasks/koncreet.ymlwith the real v0.2.4 tarball, real digest and real patch, run as root in a bwrap user namespace with/usr/localredirected: installed, linked, patch applied, config rendered. A second run was a no-op for the koncreet tasks.Installed patchedkoncreet doctorand--dry-run apply -c <rendered conf>on this Ubuntu 26.04 host, compared with unpatched upstream, which refuses withUnsupported OS: ubuntu 26.04(exit 2)Patchedkoncreet_ssh_client_ipsourced from the real library with stubbedloginctlandwho(stubs, not live): the logind fallback works whenwhois empty; junk and empty inputs return no IPFailure scenarios through the real tasks: wrong digest on the real tarball, 404 asset URL,--checkplan mode, a release that already supports 26.04, a release the patch no longer applies toRendered/etc/koncreet.confmatrix: tailscale on/off, apply user equal to the factory user, root, and re-apply not overwriting an operator editRealsite.yml --tags always,koncreet: the host config installed koncreet; rate-limit and no-digest faults skipped it with failed=0; the container config never looked it upCompared theundeemed/koncreetubuntu-26.04fork branch withpatches/koncreet/ubuntu-26.04.patch(identical change lines)Mutation check (pytest, not live): removed theget_urlchecksum line, the fixed digest test failed, then restored it and the test passedReadansible/tasks/koncreet.ymland the upstream tree for anyufw reset(code reading only, not driven)🔧 No changes applied.
✅ Re-checked - no issues remain.
koncreet doctorreports OS ubuntu 26.04 supporteddoctorreports ubuntu 24.04 supportedinstall_tools --resolveexits 0, while a mandatory tool's 403…koncreet applyincluding the fail2ban restart on a systemd-booted hostuv run pytest tests/test_configuration.py tests/test_install_tools.py -k koncreet -rs -v(20 passed, 0 skipped): supporting evidence only, not liveNew live drive: disposable Ubuntu 24.04.5 container (kc-systemd:24.04 image), ansible-core 2.16.3 from noble apt, repo = git archive of HEAD d72af44.ansible-playbook ansible/site.yml --tags koncreetagainst a stand-in GitHub release API serving the real v0.2.4 tarball and its real sha256. Thenkoncreet doctorand the rendered/etc/koncreet.confNew live adversarial drive in the same 24.04 container: the stand-in published an all-zero sha256 (second stand-in on port 8100). Checked the skip warning, rc, and that nothing was left installed, linked or configured. Then repeated with the correct digestReused recorded live 26.04 container runs from earlier in this round: scenarioA, C, D, E, live-lookup-403-skips-real-rc, live-digest-skip-wrapper-playbook, live-gate-start-services-both-configs, live-resolver-koncreet-faults, host-26.04-patched-vs-unpatched. Before reuse I compared that sandbox's /repo against the worktree by md5 for koncreet.yml, install_tools.py, group_vars/all.yml, koncreet.conf.j2, the 26.04 patch, site.yml and preflight.yml: identical. I did not diff the whole treefork-ls-remote.txtandfork-compare-diff-byte-identical.txt(read-only GitHub queries);ci-fakeroot-step.txt(parsed ci.yml, supporting only)✅ No issues found.
koncreet doctorsays 'OS ubuntu 26.04 supported'lib/os.shhas 0 matches for '26.04'.git ls-remoteshowed refs/heads/ubuntu-26.04.cmpof the compare diff against the patch printed BYTE-IDENTICAL (68 lines).uv run pytest tests/test_configuration.py tests/test_install_tools.py -k koncreet -q -rs: 20 passed, 0 skipped (fakeroot present)git diff d72af442 HEADlimited tokoncreet.yml,koncreet.conf.j2,patches/koncreet,preflight.ymlandcontainers/factory.container.yml: 0 linesgit diff d72af442 HEADongroup_vars/all.yml,site.yml,install_tools.pyand the tests: only base-merge changes (omp pattern-kill, no-mistakes pi agent). Nothing about koncreet changed except one shared test assertion that no longer lists no-mistakes.python3 scripts/install_tools.py --home <tmp> --also koncreet --resolveagainst the live GitHub API: koncreet 0.2.4 with its release-asset SHA-256tasks/koncreet.ymlunderfakerootwith a temp prefix, real v0.2.4 tarball and real digest: patch applied, digest-mismatch skip, 404 skip. Scope is limited to those three, because install and link were rescued here (hard-coded /usr/local/bin).sudo unshare -mwith overlays on /etc and /usr/local (upper dirs in /tmp), real paths and real root: digest-mismatch skip, then correct-digest install, link, config render,koncreet doctor,koncreet --dry-run apply -c /etc/koncreet.conf, second-apply idempotence (rescued=0 in all five runs), and the factory-user, root and tailscale-off config variants. The host was checked afterwards and has no /usr/local/bin/koncreet, /etc/koncreet.conf or /usr/local/lib/code-factory.Unpatched upstream v0.2.4doctoron the same 26.04 host, compared with the patched oneFreshubuntu:24.04container at HEAD: digest-mismatch skip, then correct-digest install, link anddoctorgit ls-remoteof undeemed/koncreet, thencmpof the fork'scompare/main...ubuntu-26.04.diffagainstpatches/koncreet/ubuntu-26.04.patchParsed.github/workflows/ci.yml(jobconfig-and-python): fakeroot install is step 5, pytest is step 6✅ **Document** - passed
✅ No issues found.
✅ No issues found.
✅ **Lint** - passed
✅ No issues found.
✅ No issues found.
✅ **Push** - passed
✅ No issues found.
✅ No issues found.