Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions scripts/validate-axebc2-core31-dev.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,12 @@ def require(condition, message):
require("create_host_path: false" in compose, "build metadata bind must fail closed")
require("/etc/5tratumos/build.json" in compose, "build metadata must be mounted")
require('JWT_SECRET: "${JWT_SECRET}"' in compose, "init must receive the platform JWT secret")
require(
"chown -R 1000:1000 /data/pool/config" in compose
and compose.index("chown -R 1000:1000 /data/pool/config")
< compose.index("exec /bin/sh /opt/axebc2/init.sh"),
"versioned Compose init must keep fresh and preserved CKPool config writable",
)
require(
"chown -R 1000:1000 /data/pool/www" in compose
and compose.count("$$(stat -c '%u:%g' /data/pool/www") == 3,
Expand All @@ -83,6 +89,10 @@ def require(condition, message):
".5tratumos-rollback-policy.json" in (APP / "data/init/init.sh").read_text(encoding="utf-8"),
"init must use the policy filename consumed by AxeBC2 and 5tratumOS",
)
require(
'chown -R 1000:1000 "${data_dir}/pool/config"' in (APP / "data/init/init.sh").read_text(encoding="utf-8"),
"seeded init must retain targeted CKPool config ownership repair",
)
require(
"alpine:3.22.1@sha256:4bcff63911fcb4448bd4fdacec207030997caf25e9bea4045fa6c8c44de311d1"
in compose,
Expand Down
38 changes: 38 additions & 0 deletions tests/test_axebc2_core31_init.py
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,44 @@ def test_current_ckpool_config_still_repairs_sharelog_ownership(self):
self.assertEqual(chown_log.read_text(encoding="utf-8").splitlines().count(repair), 1)
self.assertEqual(config.read_text(encoding="utf-8"), original)

def test_fresh_install_repairs_mutable_pool_config_ownership(self):
fake_bin = self.tmp / "fake-bin"
fake_bin.mkdir()
chown_log = self.tmp / "chown.log"
fake_chown = fake_bin / "chown"
fake_chown.write_text(
"#!/bin/sh\n"
'printf "%s\\n" "$*" >> "$AXEBC2_TEST_CHOWN_LOG"\n',
encoding="utf-8",
)
fake_chown.chmod(0o755)
fake_stat = fake_bin / "stat"
fake_stat.write_text("#!/bin/sh\nprintf '1000:1000\\n'\n", encoding="utf-8")
fake_stat.chmod(0o755)

self.run_init(
extra_env={
"AXEBC2_TEST_SKIP_CHOWN": "false",
"AXEBC2_TEST_CHOWN_LOG": str(chown_log),
"PATH": f"{fake_bin}{os.pathsep}{os.environ['PATH']}",
}
)

config_dir = self.data / "pool/config"
self.assertTrue((config_dir / "ckpool.conf").is_file())
self.assertIn(
f"-R 1000:1000 {config_dir}",
chown_log.read_text(encoding="utf-8").splitlines(),
)
# Atomic payout saves need directory-level create and replace access.
replacement = config_dir / ".ckpool.conf.atomic-test"
replacement.write_text("replacement\n", encoding="utf-8")
replacement.replace(config_dir / "ckpool.conf")
self.assertEqual(
(config_dir / "ckpool.conf").read_text(encoding="utf-8"),
"replacement\n",
)

def test_missing_or_malformed_build_metadata_fails_closed(self):
self.build.write_text("not-json", encoding="utf-8")
env = os.environ.copy()
Expand Down
5 changes: 5 additions & 0 deletions tests/test_axebc2_platform_integration.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,14 @@ def load_policy_module(platform: Path):
class AxeBC2PlatformIntegrationTests(unittest.TestCase):
def test_upgrade_repair_is_in_versioned_compose_not_only_seeded_data(self):
source = COMPOSE.read_text(encoding="utf-8")
self.assertIn("chown -R 1000:1000 /data/pool/config", source)
self.assertEqual(source.count("$$(stat -c '%u:%g' /data/pool/www"), 3)
self.assertIn("chown -R 1000:1000 /data/pool/www", source)
self.assertNotIn('"$(stat -c', source)
self.assertLess(
source.index("chown -R 1000:1000 /data/pool/config"),
source.index("exec /bin/sh /opt/axebc2/init.sh"),
)
self.assertLess(
source.index("chown -R 1000:1000 /data/pool/www"),
source.index("exec /bin/sh /opt/axebc2/init.sh"),
Expand Down
9 changes: 8 additions & 1 deletion willitmod-dev-bc2/data/init/init.sh
Original file line number Diff line number Diff line change
Expand Up @@ -206,12 +206,19 @@ if [ -f "$settings" ]; then
fi
fi

# CKPool runs as uid/gid 1000 and creates per-height sharelog directories
# The app atomically replaces files in pool/config as uid/gid 1000, so the
# directory itself must remain writable even when a fresh install seeded it as
# root. This tree is tiny and safe to repair on every initializer run.
#
# CKPool also runs as uid/gid 1000 and creates per-height sharelog directories
# directly under /www. Existing installs can already have a current config and
# therefore skip the regeneration branch above while /www remains root-owned.
# Check only the three known writable directories on normal starts; recursively
# repair the existing sharelog tree once if an upgrade left any of them behind.
if [ "${AXEBC2_TEST_SKIP_CHOWN:-false}" != "true" ]; then
chown -R 1000:1000 "${data_dir}/pool/config" 2>/dev/null ||
fail "cannot assign CKPool config data to the app user"

repair_sharelog_ownership=false
for writable_dir in \
"${data_dir}/pool/www" \
Expand Down
6 changes: 5 additions & 1 deletion willitmod-dev-bc2/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,11 @@ services:
# update, including its previously seeded init script. Keep this
# upgrade repair in the versioned Compose command so 0.1.10 installs
# receive it even when /data/init/init.sh is intentionally retained.
mkdir -p /data/pool/www/pool /data/pool/www/users
mkdir -p /data/pool/config /data/pool/www/pool /data/pool/www/users
# Keep the small mutable config directory writable by the unprivileged
# app. This is required on fresh installs as well as preserved upgrades.
chown -R 1000:1000 /data/pool/config ||
{ echo >&2 "cannot assign CKPool config data to uid/gid 1000"; exit 1; }
if [ "$$(stat -c '%u:%g' /data/pool/www 2>/dev/null || true)" != "1000:1000" ] ||
[ "$$(stat -c '%u:%g' /data/pool/www/pool 2>/dev/null || true)" != "1000:1000" ] ||
[ "$$(stat -c '%u:%g' /data/pool/www/users 2>/dev/null || true)" != "1000:1000" ]; then
Expand Down
Loading