Protect private bank configuration from drift reconciliation (Refs #152)
This commit is contained in:
@@ -35,7 +35,7 @@ import urllib.parse
|
||||
import urllib.request
|
||||
import uuid
|
||||
|
||||
VERSION = "1.9.2"
|
||||
VERSION = "1.9.3"
|
||||
LABEL = "io.electric-cherry"
|
||||
MAX_FILE = 32 * 1024 * 1024
|
||||
MANIFEST_MAX_ENTRIES = 10000
|
||||
@@ -400,7 +400,17 @@ def managed_open(root, value):
|
||||
os.close(fd)
|
||||
|
||||
|
||||
def private_bank_config(value):
|
||||
return isinstance(value, str) and PurePosixPath(value).as_posix() == "configs/electric_cherry_bank.cfg"
|
||||
|
||||
|
||||
def require_public_file(value):
|
||||
if private_bank_config(value):
|
||||
raise Rejected("Private bank configuration cannot be read or changed by file operations")
|
||||
|
||||
|
||||
def managed_read(root, value):
|
||||
require_public_file(value)
|
||||
try:
|
||||
with managed_open(root, value) as stream:
|
||||
if os.fstat(stream.fileno()).st_size > MAX_FILE:
|
||||
@@ -446,6 +456,8 @@ def managed_manifest(root, payload, stopping):
|
||||
|
||||
def validate_paths():
|
||||
for value in paths:
|
||||
if private_bank_config(value):
|
||||
continue
|
||||
check_budget()
|
||||
try:
|
||||
with managed_open(root, value):
|
||||
@@ -480,7 +492,7 @@ def managed_manifest(root, payload, stopping):
|
||||
entries += 1
|
||||
if entries > MANIFEST_MAX_ENTRIES:
|
||||
raise AgentError("Manifest scan exceeded its entry limit")
|
||||
if child.name in ignored:
|
||||
if child.name in ignored or private_bank_config(relative + "/" + child.name):
|
||||
continue
|
||||
info = os.stat(child.name, dir_fd=directory, follow_symlinks=False)
|
||||
if stat_module.S_ISLNK(info.st_mode):
|
||||
@@ -1949,6 +1961,8 @@ class Agent:
|
||||
files = payload.get("files", [])
|
||||
if not isinstance(files, list):
|
||||
raise Rejected("Invalid initial file list")
|
||||
for item in files:
|
||||
require_public_file(item["path"])
|
||||
bank = payload.get("bank")
|
||||
if bank is None:
|
||||
return files
|
||||
@@ -2219,6 +2233,7 @@ class Agent:
|
||||
paths = set()
|
||||
total_size = 0
|
||||
for item in files:
|
||||
require_public_file(item["path"])
|
||||
scoped(directory / "app", item["path"])
|
||||
if item["path"] in paths:
|
||||
raise AgentError("Duplicate staged file path")
|
||||
@@ -2267,6 +2282,8 @@ class Agent:
|
||||
stage = directory / "stages" / identifier(stage_id)
|
||||
marker = stage / "manifest.json"
|
||||
saved = json.loads(marker.read_text())
|
||||
for item in saved["files"]:
|
||||
require_public_file(item["path"])
|
||||
if saved["state"] == "activated":
|
||||
return saved["before"]
|
||||
if saved["state"] != "staged":
|
||||
@@ -2297,6 +2314,8 @@ class Agent:
|
||||
return before
|
||||
|
||||
def restore_files(self, app, before):
|
||||
for item in before:
|
||||
require_public_file(item["path"])
|
||||
for item in before:
|
||||
managed_write(app, item["path"], None if item["contentBase64"] is None else self.decode_file(item), int(self.config.get("runtimeUid", 1000)), int(self.config.get("runtimeGid", 1000)))
|
||||
|
||||
@@ -2351,6 +2370,7 @@ class Agent:
|
||||
def read_log(self, instance_id, generation, payload):
|
||||
app = self.instance_dir(instance_id, generation) / "app"
|
||||
value = payload["path"]
|
||||
require_public_file(value)
|
||||
count = min(2000, max(1, int(payload.get("lines", 200))))
|
||||
# Validate the original relative spelling independently of symlinks.
|
||||
scoped(self.root / "agent" / "log-validation", value)
|
||||
@@ -2367,6 +2387,7 @@ class Agent:
|
||||
raise AgentError("Log symlink resolves outside the managed instance") from None
|
||||
# Reopen the resolved relative target through no-follow directory FDs;
|
||||
# swapping either the original link or a target component cannot escape.
|
||||
require_public_file(relative)
|
||||
with managed_open(app, relative) as stream:
|
||||
stat = os.fstat(stream.fileno())
|
||||
if payload.get("mode") == "incremental":
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"version": "1.9.2",
|
||||
"version": "1.9.3",
|
||||
"protocolVersion": 1,
|
||||
"platform": "debian-amd64",
|
||||
"sha256": "acff8fdbf75652821638a9c90ee9cc4ef5254bc2f19fabc436b9135f93307a5a",
|
||||
"sha256": "a0baaab0f0a60e848300e63f31eeefff6293d8b749b5ca9397c36e8e75ff4a93",
|
||||
"updaterProtocolVersion": 2,
|
||||
"updaterSha256": "a9d7f96b87d0ec74d8a0f476dff6e3d2a7a0bc454a93a51d7b57de00c9ce23cb",
|
||||
"installerSha256": "384d5e397a51d19cb9fc9ed8793b4314353f726e217030246050859c0bc18231"
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
-----BEGIN PGP SIGNATURE-----
|
||||
|
||||
iJEEABYKADkWIQSpocb6VBfQzvdIh9HF6fdSNniN+gUCas2drBsUgAAAAAAEAA5t
|
||||
YW51MiwyLjUrMS4xMiwwLDMACgkQxen3UjZ4jfqXUwD/UtXrU4iXCqcAGzYY+ETl
|
||||
JyvOrDoi5txZ3qz4pkwsZZIBAO3nz5BzaX4k3GfvKOYFsBJsfHDa76WeWwuC1h7m
|
||||
zdoN
|
||||
=mSzg
|
||||
-----END PGP SIGNATURE-----
|
||||
Reference in New Issue
Block a user