mirror of
https://scm.tikali.ai/tikali/applications/monky/monky-deployd.git
synced 2026-09-18 05:36:15 +00:00
1c42e913a8
Stdlib-only Python 3.12 agent for docker VMs and laptops: flock → checkin
(bearer = the agent's OpenBao token, bootstrapped from the install kit's
jwt-tenancy deploy grant) → action apply|none|down → bundle (sha256
verified) → refusal checks (unresolved ${VAR} names only, manifest paths
pinned to monky/data/<env>/see/, privileged/host-network, rollback, disk
need×1.5+headroom) → lease → POST /v1/auth/jwt-tenancy/login → KV reads →
.env 0600 → promote → compose pull/up → wait healthy → report; finally
renew-self / re-lease before max TTL, scrub. Exit 0/75/78/1. Redactor log
filter. Transports sdk (openziti) / proxy (ziti tunnel proxy 18443/18200) /
system. Laptop mode.
Packaging: hardened oneshot + 60 s timer + proxy unit, nfpm .deb with
/opt/monky-deployd/venv, install.sh for Ubuntu 26.04 (Gitea release
download, enrol, ACLs, bootstrap from stdin), ansible role skeleton for
osg1-07. CI: lint/test on every change; wheel (openziti on ubuntu:26.04) and
package (nfpm) allow_failure until runner egress is proven; GitLab release +
release:gitea on v* tags. Docs: README, PROTOCOL, OPERATIONS, CHANGELOG,
CLAUDE/AGENTS.
Divergence noted: monky-tenancy main (MR !15) still ships the AppRole lease
and kit; this agent implements the plan's Gate 1 RESULT (login_jwt, no
unwrap) and refuses an AppRole lease loudly (LEASE_SHAPE).
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KLB7jieMNRkTsJ2epr4Ds1
82 lines
3.2 KiB
Python
82 lines
3.2 KiB
Python
import pytest
|
|
|
|
from monky_deployd import bundle as b
|
|
from monky_deployd.bao import ManifestPathError, kv_data_path
|
|
from tests.fakes import ENV, make_files, make_manifest, tar_bytes
|
|
|
|
|
|
def test_parse_and_sha_match_tenancy_formula():
|
|
files = make_files()
|
|
bd = b.parse(tar_bytes(files))
|
|
assert bd.sha == b.bundle_sha(files) and len(bd.sha) == 64
|
|
assert bd.compose_name == "docker-compose.yml"
|
|
assert bd.env_id == ENV and bd.tier == "qa"
|
|
assert b.manifest_vars(bd) == {"GEMINI_API_KEY", "POSTGRES_PASSWORD", "SEE_ADMIN_TOKEN"}
|
|
assert b.unresolved_vars(bd, b.manifest_vars(bd)) == []
|
|
|
|
|
|
def test_unresolved_vars_names_only_defaults_resolve():
|
|
files = make_files(
|
|
compose="services:\n x:\n image: i:${TAG:-dev}\n environment:\n A: ${NOT_IN_MANIFEST}\n B: ${POSTGRES_PASSWORD}\n"
|
|
)
|
|
bd = b.parse(tar_bytes(files))
|
|
assert b.unresolved_vars(bd, b.manifest_vars(bd)) == ["NOT_IN_MANIFEST"]
|
|
|
|
|
|
def test_privileged_and_host_network_detected():
|
|
files = make_files(
|
|
compose="services:\n x:\n image: i\n privileged: true\n network_mode: host\n cap_add:\n - SYS_ADMIN\n"
|
|
)
|
|
bd = b.parse(tar_bytes(files))
|
|
assert b.privileged_findings(bd) == ["privileged: true", "network_mode: host", "cap_add SYS_ADMIN/ALL"]
|
|
assert bd.flag("allow_privileged") is False
|
|
bd2 = b.parse(tar_bytes(make_files(meta={"agent": {"allow_privileged": True}})))
|
|
assert bd2.flag("allow_privileged") is True
|
|
|
|
|
|
def test_manifest_with_value_is_refused():
|
|
m = make_manifest()
|
|
m["entries"][0]["value"] = "leaked"
|
|
with pytest.raises(b.BundleError, match="carries a value"):
|
|
b.parse(tar_bytes(make_files(manifest=m)))
|
|
|
|
|
|
def test_unsafe_members_refused():
|
|
import io
|
|
import tarfile
|
|
|
|
buf = io.BytesIO()
|
|
with tarfile.open(fileobj=buf, mode="w") as tar:
|
|
info = tarfile.TarInfo(name="../etc/passwd")
|
|
info.size = 1
|
|
tar.addfile(info, io.BytesIO(b"x"))
|
|
with pytest.raises(b.BundleError, match="unsafe"):
|
|
b.parse(buf.getvalue())
|
|
|
|
|
|
def test_render_env_quotes_the_compose_way():
|
|
tmpl = "# c\nA=${A}\nB=${B}\nC=${C}\nKEEP=${KEEP}\n"
|
|
out = b.render_env(tmpl, {"A": "plain-value.1", "B": 'has "quote" and $dollar', "C": "multi\nline"})
|
|
assert 'A=plain-value.1\nB="has \\"quote\\" and $$dollar"\nC="multi\\nline"\nKEEP=${KEEP}\n' in out
|
|
assert b.referenced_vars(out) == {"KEEP"}
|
|
|
|
|
|
def test_kv_paths_are_pinned_to_the_env():
|
|
assert kv_data_path("monky", f"monky/{ENV}/see/pg_password", ENV) == f"/v1/monky/data/{ENV}/see/pg_password"
|
|
assert kv_data_path("monky", f"monky/data/{ENV}/see/x", ENV) == f"/v1/monky/data/{ENV}/see/x"
|
|
for bad in (
|
|
"monky/env-dev-01/see/x",
|
|
"monky/companies/c1/ai/gemini",
|
|
"other/env-qa-02/see/x",
|
|
f"monky/{ENV}/zitadel/x",
|
|
f"monky/{ENV}/see/../x",
|
|
):
|
|
with pytest.raises(ManifestPathError):
|
|
kv_data_path("monky", bad, ENV)
|
|
|
|
|
|
def test_disk_need_bytes_spellings():
|
|
assert b.parse(tar_bytes(make_files(meta={"agent": {"disk_need_bytes": 5}}))).disk_need_bytes == 5
|
|
assert b.parse(tar_bytes(make_files(meta={"disk": {"need_bytes": 7}}))).disk_need_bytes == 7
|
|
assert b.parse(tar_bytes(make_files())).disk_need_bytes == 0
|