ANC installations relocatable - #292
Conversation
Tar releases can now be extracted to a user-chosen prefix (blank/absent falls back to the legacy /opt/amdtools). deb/rpm ignore the tag since their archives carry predefined absolute locations. - resolve_anc_install_prefix/resolve_anc_paths thread the per-install prefix through version check, install verify, group run, and readiness - relocated tar installs rewrite content YAML exe_path entries from /opt/amdtools to the prefix, leaving relative/system paths intact - auto-sudo: extract without sudo when the target is user-writable - unit tests for prefix resolution, tag semantics, and the sudo probe
…lution ANC changed release packaging in 1.5.0: instead of an outer .tar.gz whose name carries a -deb-/-rpm-/-tar- token wrapping the real artifacts, the URL now points straight at a .deb/.rpm/.tar.gz that is itself the package/tree. Both generations are auto-detected from the filename and installed side-by-side. - detect_package_flavour returns (pkg_type, is_direct); legacy token forms (-<flavour>- and -<flavour>.) are matched before the direct extension fallback so a legacy tar is never mistaken for a direct one - direct installers: deb (dpkg -i) / rpm (dnf install) download-and-install; tar untars straight into the (relocatable) prefix and rewrites exe_path - exe_path validation shared between legacy and direct tar installers - fail-fast guard aborts before any SSH when config anc_version disagrees with the version parsed from anc_release_url (both generations) - resolve_anc_install_location probes the node for anc.py by URL flavour, logs the resolved path, caches it for the session, and fails clearly when absent; ensure_anc_ready populates it and run_anc_groups builds its anc.py -g command from the cached, node-verified path - unit tests for detection, version parsing/guard, direct installer command shape, and the path resolver
anc.py --version reports the tool version on 1.5.0+ direct packaging, which diverges from the release/archive version, so the version check could never match. Read the release version from the anc-release-* plugin's version column in `anc.py --content-list` for direct installs; legacy <=1.4.x keeps --version (where tool and release versions are the same). Updates the ANC README to mirror the current install-path and version-check behaviour.
solaiys
left a comment
There was a problem hiding this comment.
Line-specific notes on the one blocking issue are inline below; a plain-language summary is in the PR comment.
Review summary: one blocking issueThe relocatable-install work reads well overall. In particular I checked and am happy with: the legacy The one blocking issue: the new In plain terms: Two concrete consequences:
The fix is small, but needs both halves:
I've left inline comments on the exact lines (the guard, the prefix resolution, and the three interpolation sites, including the duplicated one in the direct-tar installer) so the line references are attached to the code rather than listed here. Nothing else is blocking. |
|
one more IMPORTANT change is needed in the full ANC code: Please change it to /tmp/{user-id}/ ( if it is in config files , {user-id} will be auto-resolved) Reason: |
Address review findings that config/cluster values reached remote shell commands unvalidated, plus follow-on issues found in re-review. - broaden _assert_shell_safe to reject ' " ` $ \ and newline (was ' only) and guard ANC_INSTALL_PATH, both up front in install_anc and at its resolve chokepoint - resolve_anc_install_prefix rejects a resolved filesystem-root prefix, including the POSIX-special "//" that abspath preserves (not just "/") - single-quote the untrusted prefix in the tar cleanup rm -rf, and filter '.'/'..' from the top-level list (plus a per-name guard) so a malicious archive entry cannot rm -rf outside the prefix; both tar installers - escape sed-replacement metacharacters (\ & #) in the exe_path rewrite - validate the cluster username at the fixture boundary and single-quote it in the chown; it never passed through the config guard - namespace remote temp paths under /tmp/<user> (validate script, log tarball, quiet-mode stdout) to avoid cross-user ownership collisions - consolidate fail-fast checks into validate_anc_config: non-blank anc_release_url, version/URL match, safe non-root prefix (surfaced as a clean message, not a mid-run traceback), and username - migrate config from bespoke REPLACE_ME to the standard <changeme> placeholder and drop the now-redundant REPLACE_ME machinery - unit tests for every guard and the safe command shapes
Fixed. |
Both halves are in d0335091.
Beyond that I closed the related sinks you pointed to inline: single-quoted, the rm -rf prefix in both installers, escaped the sed-replacement metacharacters (\ & #) in the exe_path rewrite, and — found while hardening — validated the cluster username (it reaches sudo chown but never went through the config guard) and namespaced remote /tmp paths per user. All fail-fast checks are consolidated into validate_anc_config so a bad prefix/URL/username aborts cleanly before any node is contacted, rather than as a mid-run traceback. |
solaiys
left a comment
There was a problem hiding this comment.
Re-review @ d0335091 — the blocking issue is properly fixed
I re-traced ANC_INSTALL_PATH from config parsing to every remote shell sink, and the hardening is more thorough than what I asked for. One small documentation inconsistency this PR introduces is worth a fix before merge; nothing else is blocking.
The shell-injection blocker is closed, on both halves
The guard (anc_lib.py:416) now rejects ', ", backtick, $, \ and newline, so the double-quoted sink that worried me most is no longer reachable with a $(...) or a bare $VAR.
Coverage is better than the two-line fix I suggested. Rather than only adding the key to the install_anc call, validation moved to a single choke point in resolve_anc_install_prefix (:719). I checked that this actually holds: ANC_INSTALL_PATH_KEY is read in exactly one place in the whole file (:717), and every consumer — install, group run, version check — reaches it through resolve_anc_paths. The extra _assert_shell_safe(anc_cfg, ("anc_release_url", ANC_INSTALL_PATH_KEY)) at :869 is then belt-and-braces rather than the load-bearing check. That's the right shape; it can't be bypassed by a future caller that forgets the guard.
The root check is sharper than my suggestion. I proposed rejecting == "/"; the implementation tests not resolved.strip("/") (:726) with a comment explaining that abspath preserves a POSIX-special leading //, so // and //x/.. are also filesystem root. My version would have missed those.
Both rm -rf loops are fixed, legacy (:1275) and direct-tar (:1417), and the fix is layered rather than relying on the guard alone:
f"echo \" {prefix}/$name\"; $SUDO rm -rf '{prefix}'/\"$name\"; done; "The prefix is single-quoted (and ' is now rejected upstream), and $name can't escape the prefix through three independent filters: awk -F/ '{print $1}' keeps only the first path component, grep -vE '^([.]{1,2})?$' drops ./../empty, and a case "$name" in */*|.|..) continue guard repeats the check in the loop. So a malicious or corrupt archive can't redirect the delete.
The sed sinks are escaped, at :1249 and :1396, via _sed_replacement_safe. The escape order is correct — backslash first, then & and the # delimiter — so the added escapes aren't themselves re-escaped.
The two issues you found while hardening are real ones
The cluster username reaching sudo chown without ever passing the config guard was a genuine gap I missed; validate_cluster_username rejecting whitespace as well as the shell set is the right call, since a space would have split into two chown arguments. And consolidating everything into validate_anc_config, wired into the conftest at conftest.py:101, means a bad prefix, URL or username now aborts before any node is contacted instead of surfacing as a mid-run traceback.
My /tmp request is addressed
All three sites are namespaced under /tmp/<user> — the validate_exe_paths.py upload (:1319), the log tarball (:1717), and the quiet-mode run output (:2120) — each with its own mkdir -p first, and each sanitised to a single path component. Using the SSH username instead of the {user-id} config token is fine; these paths are built in code, and the isolation is what I was after. The log-tarball case is handled correctly too: the archive is created by root inside the user-owned directory and then chowned back so SFTP can pull it.
Checked and clear
- The
<changeme>migration doesn't regress the placeholder check. This one needed care, because the removedfind_replace_me_placeholdersdeliberately skipped_comment*keys and required an exact match, whereas the standard resolver (utils_lib.py:293) does a substring match and skips nothing — so a comment merely mentioning the placeholder would hard-exit a correctly-filled config. I walked the shipped config: exactly two strings contain<changeme>(anc.anc_release_url,anc.log_folder_path, both intended), and the two comments that discuss it deliberately write "changeme" without the angle brackets. No false positives. - Tests. 86 new tests, all passing, and they cover the security fixes specifically rather than just the happy path (
TestTarCleanupFiltersDotDot,TestValidateClusterUsername, the shell-safe cases). Full suite goes 484 → 570 with no new failures. - Lint.
ruff format --checkandruff checkclean on all three Python files, pylint 10.00/10 on the logging gate. - Concurrency, re-checked after the refactor: still a single writer to the module-level cache, no threads or background jobs introduced.
One thing to fix before merge (docs, not code)
This PR adds three README lines that tell users to write REPLACE_ME, while the same PR deletes the code that recognised it:
"anc_release_url": "REPLACE_ME",
"log_folder_path": "REPLACE_ME",
`anc_release_url` and `log_folder_path` ship as the `REPLACE_ME` sentinel andThe shipped config now uses <changeme>, and REPLACE_ME_SENTINEL is gone, so nothing recognises that string any more. Reading the code paths: a literal REPLACE_ME passes the non-blank check in validate_anc_config, passes _prefix_problem, and then _resolve_prefix returns it as a plain relative path — so an operator who follows the README gets ANC trying to download REPLACE_ME as a URL and writing logs to a relative directory, rather than the clean "replace the placeholder" message the old sentinel produced. It's a three-line docs edit (REPLACE_ME → <changeme> in the example block and the sentence at line 145), but worth doing in this PR since this PR is what created the mismatch.
Minor, entirely optional: _assert_shell_safe runs on the raw value before expanduser, so in principle a home directory containing a metacharacter could reintroduce one after validation. Pathological and out of scope for the config-is-trusted threat model this guard operates in — noting it only for completeness.
Aside from the README lines, this looks good to me — the security fix is well-constructed and the tests back it up.
The README still described the removed REPLACE_ME sentinel and claimed
log_folder_path rejects {home}/{user-id} tokens, both stale after the
config migrated to <changeme> and the standard resolver.
- sample config + prose now use <changeme> and note it hard-exits up front
- document ANC_INSTALL_PATH {home}/{user-id} resolution and the up-front
shell-metacharacter / filesystem-root rejection
- correct the log_folder_path token note ({home}/{user-id} are resolved;
only the CVS-owned <node>/<test_name>/<timestamp> tokens are not)
This PR resolves the issue #283.
This PR Make ANC installations relocatable and add support for ANC's new 1.5.0+ packaging layout
Technical Details :
one.
builds its anc.py -g command from the cached, node-verified path.
cvs/tests/anc/conftest.py config validation.
All the UTs are passing with this change.