Fix project encryption and gateway vault CLI
This commit is contained in:
@@ -607,6 +607,23 @@ def service_logs(kind, args):
|
||||
def handle_gateway(args):
|
||||
subcommand = args[0] if args else "ls"
|
||||
rest = args[1:] if args else []
|
||||
if subcommand in {"help", "-h", "--help"}:
|
||||
print("Usage: nap gateway [start|stop|status|logs|configure|setup|unlock|lock|restart|kill|vault|terminal|schedule|service]")
|
||||
print(" nap gateway start [--port PORT] Start the local gateway service.")
|
||||
print(" nap gateway status Show the managed gateway process.")
|
||||
print(" nap gateway configure [--command CMD] Configure the local gateway command.")
|
||||
print(" nap gateway setup Create or unlock the encrypted gateway vault.")
|
||||
print(" nap gateway vault Show gateway vault status and pending setup requests.")
|
||||
print(" nap gateway vault process Complete pending setup requests.")
|
||||
print(" nap gateway vault rotate-secret --kind chat|tabs|gateway")
|
||||
return 0
|
||||
if subcommand in {"vault", "vault-setup", "vault_setup", "project-vault"} and rest and rest[0] in {"help", "-h", "--help"}:
|
||||
print("Usage: nap gateway vault [status|list|process|rotate-secret] [--kind chat|tabs|gateway] [--all] [--project-id ID]")
|
||||
print(" status Show configured/locked state and pending setup requests.")
|
||||
print(" process Generate local secret material and send encrypted envelopes only.")
|
||||
print(" rotate-secret Rotate one encrypted project secret.")
|
||||
print(" vault-setup Deprecated alias for vault.")
|
||||
return 0
|
||||
if subcommand == "start":
|
||||
return print_json(start_service("gateway", ["--no-browser", *rest])) or 0
|
||||
if subcommand == "stop":
|
||||
@@ -617,9 +634,9 @@ def handle_gateway(args):
|
||||
return print_json(service_logs("gateway", rest)) or 0
|
||||
if subcommand in {"configure", "config"}:
|
||||
return run_wrapper("gateway", ["configure", *rest])
|
||||
if subcommand in {"setup", "unlock", "lock", "restart", "kill"}:
|
||||
if subcommand in {"setup", "unlock", "lock", "restart", "kill", "vault", "vault-setup", "vault_setup", "project-vault", "terminal", "schedule", "service"}:
|
||||
return run_wrapper("gateway", [subcommand, *rest])
|
||||
raise RuntimeError("unknown gateway command; use nap gateway start|stop|configure|setup|unlock|lock|restart|kill|logs|status")
|
||||
raise RuntimeError("unknown gateway command; use nap gateway start|stop|configure|setup|unlock|lock|restart|kill|logs|status|vault|terminal|schedule|service")
|
||||
|
||||
|
||||
def print_json(value):
|
||||
@@ -680,11 +697,13 @@ def main(argv):
|
||||
print("Usage: nap [auth|project|gateway|agent|feedback|status|update|where|install]")
|
||||
print(" auth Auth commands: login-local, status.")
|
||||
print(" Example: nap auth login-local")
|
||||
print(" project Project commands: create, status, encryption, plaintext, encrypted.")
|
||||
print(" Examples: nap project create, nap project status, nap project encryption set plaintext")
|
||||
print(" gateway Gateway commands: start, stop, configure, setup, unlock, lock, restart, kill, logs, status.")
|
||||
print(" project Project commands: create, status, encryption, encrypted, plaintext.")
|
||||
print(" Examples: nap project create --encrypted, nap project encrypted, nap project plaintext")
|
||||
print(" Project encryption uses a project passphrase; use --passphrase or NAPSEER_PROJECT_PASSPHRASE for automation.")
|
||||
print(" Equivalent explicit form: nap project encryption set encrypted|plaintext")
|
||||
print(" gateway Gateway commands: start, stop, configure, setup, unlock, lock, restart, kill, logs, status, vault.")
|
||||
print(" Start auto-selects a local port; use --port to pin one.")
|
||||
print(" Examples: nap gateway start, nap gateway start --port 8767, nap gateway logs")
|
||||
print(" Examples: nap gateway start, nap gateway vault, nap gateway vault rotate-secret --kind chat")
|
||||
print(" agent Agent commands: list, workspaces, create, show, edit.")
|
||||
print(" list shows registered project agents; workspaces shows /agents folders.")
|
||||
print(" feedback Feedback commands: list, global, status, resolve.")
|
||||
|
||||
@@ -477,6 +477,7 @@ GATEWAY_SCHEDULES_SCHEMA = "napseer.gateway-schedules.v1"
|
||||
CONTAINER_IDENTITY_SCHEMA = "napseer.container-identity.v1"
|
||||
GATEWAY_RELAY_SECRET_ITERATIONS = int(os.environ.get("NAPSEER_GATEWAY_RELAY_SECRET_ITERATIONS", "210000"))
|
||||
VAULT_KDF_ITERATIONS = int(os.environ.get("NAPSEER_VAULT_KDF_ITERATIONS", "600000"))
|
||||
PROJECT_KEY_KDF_ITERATIONS = int(os.environ.get("NAPSEER_PROJECT_KEY_KDF_ITERATIONS", "600000"))
|
||||
GATEWAY_IDLE_TIMEOUT_SECONDS = int(os.environ.get("NAPSEER_GATEWAY_IDLE_TIMEOUT_SECONDS", "900"))
|
||||
GATEWAY_MAX_LOG_BYTES = int(os.environ.get("NAPSEER_GATEWAY_MAX_LOG_BYTES", str(5 * 1024 * 1024)))
|
||||
GATEWAY_PROJECT_SECRET_KINDS = ("chat", "tabs", "gateway")
|
||||
@@ -682,6 +683,23 @@ def read_master_passphrase(create=False):
|
||||
return first
|
||||
|
||||
|
||||
def read_project_passphrase(create=False):
|
||||
value = os.environ.get("NAPSEER_PROJECT_PASSPHRASE") or os.environ.get("NAPSEER_MASTER_PASSPHRASE")
|
||||
if value:
|
||||
return value
|
||||
if not sys.stdin.isatty():
|
||||
raise RuntimeError(
|
||||
"Project encryption needs a local wrapping passphrase. "
|
||||
"Pass --passphrase TEXT or set NAPSEER_PROJECT_PASSPHRASE."
|
||||
)
|
||||
first = getpass.getpass("Create project encryption passphrase: " if create else "Project encryption passphrase: ")
|
||||
if create:
|
||||
second = getpass.getpass("Confirm project encryption passphrase: ")
|
||||
if first != second:
|
||||
raise RuntimeError("project encryption passphrases did not match")
|
||||
return first
|
||||
|
||||
|
||||
def vault_exists():
|
||||
return VAULT_PATH.exists()
|
||||
|
||||
@@ -3042,6 +3060,46 @@ def gateway_vault_master_wrap_aad(project_id, credential_id):
|
||||
return f"napseer.vault-master-wrap.v1:{project_id}:{credential_id}".encode("utf-8")
|
||||
|
||||
|
||||
def project_key_wrap_aad(project_id, credential_id):
|
||||
return f"napseer.project-key-wrap.v1:{project_id}:{credential_id}".encode("utf-8")
|
||||
|
||||
|
||||
def local_wrapped_project_key(project_id, passphrase=None, project_key=None):
|
||||
project_id = str(project_id or current_project_id()).strip()
|
||||
if not project_id:
|
||||
raise RuntimeError("project_id is required")
|
||||
passphrase = passphrase or read_project_passphrase(create=True)
|
||||
if not isinstance(passphrase, str) or not passphrase:
|
||||
raise RuntimeError("project encryption passphrase is required")
|
||||
project_key = project_key or secrets.token_bytes(32)
|
||||
credential_id = f"local-passphrase:{AUTH.get('agent_id') or 'local'}"
|
||||
salt = secrets.token_bytes(16)
|
||||
nonce = secrets.token_bytes(12)
|
||||
wrapping_key = hashlib.pbkdf2_hmac(
|
||||
"sha256",
|
||||
passphrase.encode("utf-8"),
|
||||
salt,
|
||||
PROJECT_KEY_KDF_ITERATIONS,
|
||||
dklen=32,
|
||||
)
|
||||
aad = project_key_wrap_aad(project_id, credential_id)
|
||||
packed = aes_gcm_encrypt(wrapping_key, nonce, aad, project_key)
|
||||
return {
|
||||
"credential_id": credential_id,
|
||||
"wrapped_project_key_b64": base64.b64encode(packed).decode("ascii"),
|
||||
"wrap_alg": "AES-GCM-256",
|
||||
"kdf_alg": "PBKDF2-SHA256",
|
||||
"salt_b64": base64.b64encode(salt).decode("ascii"),
|
||||
"kdf_params": {
|
||||
"source": "local_passphrase",
|
||||
"iterations": PROJECT_KEY_KDF_ITERATIONS,
|
||||
"aad_hash": hashlib.sha256(aad).hexdigest(),
|
||||
"nonce_b64": base64.b64encode(nonce).decode("ascii"),
|
||||
"runtime": "nap-cli",
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def gateway_project_secret_envelope(project_id, secret_kind, version, vault_master_secret, secret_material):
|
||||
nonce = secrets.token_bytes(12)
|
||||
aad = gateway_project_secret_aad(project_id, secret_kind, version)
|
||||
@@ -3138,6 +3196,27 @@ def gateway_vault_setup_requests(args=None):
|
||||
return request_json("GET", f"/v1/projects/{project_id}/vault/setup-requests")
|
||||
|
||||
|
||||
def gateway_vault_status(args=None):
|
||||
args = args or {}
|
||||
project_id = args.get("project_id") or current_project_id()
|
||||
requests = gateway_vault_setup_requests({"project_id": project_id})
|
||||
items = requests.get("items", [])
|
||||
pending = [item for item in items if item.get("status") == "pending"]
|
||||
return {
|
||||
"status": "ok",
|
||||
"project_id": project_id,
|
||||
"locked": not gateway_is_unlocked(),
|
||||
"vault_configured": vault_exists(),
|
||||
"pending_setup_requests": len(pending),
|
||||
"items": items,
|
||||
"message": (
|
||||
"No pending gateway vault setup requests."
|
||||
if not pending
|
||||
else f"{len(pending)} pending gateway vault setup request(s). Run `nap gateway vault process` to complete them."
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
def gateway_complete_vault_setup_request(setup_request, project_id=None):
|
||||
require_unlocked("completing project vault setup")
|
||||
project_id = str(project_id or setup_request.get("project_id") or current_project_id())
|
||||
@@ -3164,12 +3243,22 @@ def gateway_complete_vault_setup_request(setup_request, project_id=None):
|
||||
|
||||
def gateway_process_vault_setup_requests(args=None):
|
||||
args = args or {}
|
||||
require_unlocked("processing project vault setup requests")
|
||||
project_id = args.get("project_id") or current_project_id()
|
||||
items = gateway_vault_setup_requests({"project_id": project_id}).get("items", [])
|
||||
pending = [item for item in items if item.get("status") == "pending"]
|
||||
if not pending:
|
||||
return {
|
||||
"status": "noop",
|
||||
"project_id": project_id,
|
||||
"completed_count": 0,
|
||||
"completed": [],
|
||||
"skipped": [{"id": item.get("id"), "status": item.get("status")} for item in items if item.get("status") != "pending"],
|
||||
"message": "No pending gateway vault setup requests.",
|
||||
}
|
||||
require_unlocked("processing project vault setup requests")
|
||||
completed = []
|
||||
skipped = []
|
||||
for item in items:
|
||||
for item in pending:
|
||||
if item.get("status") != "pending":
|
||||
skipped.append({"id": item.get("id"), "status": item.get("status")})
|
||||
continue
|
||||
@@ -5863,10 +5952,11 @@ def bootstrap_project(args):
|
||||
project, project_status, message = create_project_with_state(args)
|
||||
encryption_state = "plaintext"
|
||||
if encryption == "encrypted":
|
||||
wrapped_project_key = local_wrapped_project_key(project["id"], passphrase=args.get("passphrase"))
|
||||
status = request_project_write(
|
||||
"POST",
|
||||
f"/v1/projects/{project['id']}/encryption/enable",
|
||||
{"state": "encrypted"},
|
||||
{"state": "encrypted", "wrapped_project_key": wrapped_project_key},
|
||||
project["id"],
|
||||
"enable project encryption",
|
||||
)
|
||||
@@ -5915,6 +6005,8 @@ def project_encryption_transition(args=None):
|
||||
project_id = resolve_project_id(args)
|
||||
operation = "enable" if desired == "encrypted" else "disable"
|
||||
payload = {"state": desired}
|
||||
if desired == "encrypted":
|
||||
payload["wrapped_project_key"] = local_wrapped_project_key(project_id, passphrase=args.get("passphrase"))
|
||||
result = request_project_write(
|
||||
"POST",
|
||||
f"/v1/projects/{project_id}/encryption/{operation}",
|
||||
@@ -9666,17 +9758,26 @@ def cli_main(argv):
|
||||
if action == "set":
|
||||
if len(argv) <= 4:
|
||||
raise RuntimeError("nap project encryption set requires plaintext or encrypted")
|
||||
print(json.dumps(project_encryption_transition({"state": argv[4]}), indent=2))
|
||||
print(json.dumps(project_encryption_transition({
|
||||
"state": argv[4],
|
||||
"passphrase": cli_option(argv[5:], "--passphrase", default=None),
|
||||
}), indent=2))
|
||||
return
|
||||
print(json.dumps(project_encryption_transition({"state": action}), indent=2))
|
||||
print(json.dumps(project_encryption_transition({
|
||||
"state": action,
|
||||
"passphrase": cli_option(argv[4:], "--passphrase", default=None),
|
||||
}), indent=2))
|
||||
return
|
||||
if subcommand in {"plaintext", "plain", "unencrypt", "decrypt", "disable-encryption"}:
|
||||
print(json.dumps(project_encryption_transition({"state": "plaintext"}), indent=2))
|
||||
return
|
||||
if subcommand in {"encrypted", "encrypt", "enable-encryption"}:
|
||||
print(json.dumps(project_encryption_transition({"state": "encrypted"}), indent=2))
|
||||
print(json.dumps(project_encryption_transition({
|
||||
"state": "encrypted",
|
||||
"passphrase": cli_option(argv[3:], "--passphrase", default=None),
|
||||
}), indent=2))
|
||||
return
|
||||
print("Usage: napseer_mcp_server.py project [create [slug] [--name NAME] [--description TEXT] [--encrypted|--encryption plaintext|encrypted] [--passphrase TEXT]|claim [--no-browser] [--timeout SECONDS] [--return-url URL]|status|encryption [status|set plaintext|set encrypted|plaintext|encrypted]|plaintext|encrypted]")
|
||||
print("Usage: napseer_mcp_server.py project [create [slug] [--name NAME] [--description TEXT] [--encrypted|--encryption plaintext|encrypted] [--passphrase TEXT]|claim [--no-browser] [--timeout SECONDS] [--return-url URL]|status|encryption [status|set plaintext|set encrypted|plaintext|encrypted] [--passphrase TEXT]|plaintext|encrypted [--passphrase TEXT]]")
|
||||
return
|
||||
if command == "gateway":
|
||||
subcommand = argv[2] if len(argv) > 2 else "status"
|
||||
@@ -9809,16 +9910,19 @@ def cli_main(argv):
|
||||
gateway_service_run(payload)
|
||||
return
|
||||
raise RuntimeError("unknown gateway service action; use preregister|activate|run")
|
||||
if subcommand in {"vault-setup", "vault_setup", "project-vault"}:
|
||||
action = args[0] if args else "process"
|
||||
if subcommand in {"vault", "vault-setup", "vault_setup", "project-vault"}:
|
||||
action = args[0] if args else "status"
|
||||
vault_args = args[1:] if args else []
|
||||
if action in {"help", "-h", "--help"}:
|
||||
print("Usage: napseer_mcp_server.py gateway vault [status|list|process|rotate-secret] [--kind chat|tabs|gateway] [--all] [--project-id ID]")
|
||||
return
|
||||
payload = {
|
||||
"project_id": cli_option(vault_args, "--project-id", default=None),
|
||||
"secret_kind": cli_option(vault_args, "--secret-kind", "--kind", default=None),
|
||||
"complete_all": cli_flag(vault_args, "--all", "--complete-all"),
|
||||
}
|
||||
if action in {"list", "ls", "status"}:
|
||||
print(json.dumps(gateway_vault_setup_requests(payload), indent=2))
|
||||
print(json.dumps(gateway_vault_status(payload), indent=2))
|
||||
return
|
||||
if action in {"process", "complete", "run"}:
|
||||
print(json.dumps(gateway_process_vault_setup_requests(payload), indent=2))
|
||||
@@ -9828,7 +9932,7 @@ def cli_main(argv):
|
||||
payload["secret_kind"] = vault_args[0] if vault_args and not vault_args[0].startswith("-") else "chat"
|
||||
print(json.dumps(gateway_rotate_project_vault_secret(payload), indent=2))
|
||||
return
|
||||
raise RuntimeError("unknown gateway vault-setup action; use list|process")
|
||||
raise RuntimeError("unknown gateway vault action; use status|list|process|rotate-secret")
|
||||
if subcommand == "setup":
|
||||
print(json.dumps(gateway_setup({
|
||||
"passphrase": cli_option(args, "--passphrase", default=None),
|
||||
@@ -9846,7 +9950,7 @@ def cli_main(argv):
|
||||
if subcommand == "lock":
|
||||
print(json.dumps(gateway_lock(), indent=2))
|
||||
return
|
||||
print("Usage: napseer_mcp_server.py gateway [status|configure [--command CMD] [--target session:window.pane]|vault-setup [list|process] [--all]|terminal [list|create|close|capture|input]|schedule [list|create|update|delete|run]|restart [--passphrase TEXT]|kill [--passphrase TEXT]|setup|rotate-passphrase --new-passphrase TEXT|unlock|lock]")
|
||||
print("Usage: napseer_mcp_server.py gateway [status|configure [--command CMD] [--target session:window.pane]|vault [status|list|process|rotate-secret] [--kind chat|tabs|gateway] [--all]|terminal [list|create|close|capture|input]|schedule [list|create|update|delete|run]|restart [--passphrase TEXT]|kill [--passphrase TEXT]|setup|rotate-passphrase --new-passphrase TEXT|unlock|lock]")
|
||||
return
|
||||
if command == "feedback":
|
||||
subcommand = argv[2] if len(argv) > 2 else "list"
|
||||
@@ -9943,4 +10047,8 @@ def main():
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
cli_main(sys.argv)
|
||||
try:
|
||||
cli_main(sys.argv)
|
||||
except Exception as exc:
|
||||
print(json.dumps({"status": "error", "error": str(exc)}, indent=2), file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
+25
-6
@@ -607,6 +607,23 @@ def service_logs(kind, args):
|
||||
def handle_gateway(args):
|
||||
subcommand = args[0] if args else "ls"
|
||||
rest = args[1:] if args else []
|
||||
if subcommand in {"help", "-h", "--help"}:
|
||||
print("Usage: nap gateway [start|stop|status|logs|configure|setup|unlock|lock|restart|kill|vault|terminal|schedule|service]")
|
||||
print(" nap gateway start [--port PORT] Start the local gateway service.")
|
||||
print(" nap gateway status Show the managed gateway process.")
|
||||
print(" nap gateway configure [--command CMD] Configure the local gateway command.")
|
||||
print(" nap gateway setup Create or unlock the encrypted gateway vault.")
|
||||
print(" nap gateway vault Show gateway vault status and pending setup requests.")
|
||||
print(" nap gateway vault process Complete pending setup requests.")
|
||||
print(" nap gateway vault rotate-secret --kind chat|tabs|gateway")
|
||||
return 0
|
||||
if subcommand in {"vault", "vault-setup", "vault_setup", "project-vault"} and rest and rest[0] in {"help", "-h", "--help"}:
|
||||
print("Usage: nap gateway vault [status|list|process|rotate-secret] [--kind chat|tabs|gateway] [--all] [--project-id ID]")
|
||||
print(" status Show configured/locked state and pending setup requests.")
|
||||
print(" process Generate local secret material and send encrypted envelopes only.")
|
||||
print(" rotate-secret Rotate one encrypted project secret.")
|
||||
print(" vault-setup Deprecated alias for vault.")
|
||||
return 0
|
||||
if subcommand == "start":
|
||||
return print_json(start_service("gateway", ["--no-browser", *rest])) or 0
|
||||
if subcommand == "stop":
|
||||
@@ -617,9 +634,9 @@ def handle_gateway(args):
|
||||
return print_json(service_logs("gateway", rest)) or 0
|
||||
if subcommand in {"configure", "config"}:
|
||||
return run_wrapper("gateway", ["configure", *rest])
|
||||
if subcommand in {"setup", "unlock", "lock", "restart", "kill"}:
|
||||
if subcommand in {"setup", "unlock", "lock", "restart", "kill", "vault", "vault-setup", "vault_setup", "project-vault", "terminal", "schedule", "service"}:
|
||||
return run_wrapper("gateway", [subcommand, *rest])
|
||||
raise RuntimeError("unknown gateway command; use nap gateway start|stop|configure|setup|unlock|lock|restart|kill|logs|status")
|
||||
raise RuntimeError("unknown gateway command; use nap gateway start|stop|configure|setup|unlock|lock|restart|kill|logs|status|vault|terminal|schedule|service")
|
||||
|
||||
|
||||
def print_json(value):
|
||||
@@ -680,11 +697,13 @@ def main(argv):
|
||||
print("Usage: nap [auth|project|gateway|agent|feedback|status|update|where|install]")
|
||||
print(" auth Auth commands: login-local, status.")
|
||||
print(" Example: nap auth login-local")
|
||||
print(" project Project commands: create, status, encryption, plaintext, encrypted.")
|
||||
print(" Examples: nap project create, nap project status, nap project encryption set plaintext")
|
||||
print(" gateway Gateway commands: start, stop, configure, setup, unlock, lock, restart, kill, logs, status.")
|
||||
print(" project Project commands: create, status, encryption, encrypted, plaintext.")
|
||||
print(" Examples: nap project create --encrypted, nap project encrypted, nap project plaintext")
|
||||
print(" Project encryption uses a project passphrase; use --passphrase or NAPSEER_PROJECT_PASSPHRASE for automation.")
|
||||
print(" Equivalent explicit form: nap project encryption set encrypted|plaintext")
|
||||
print(" gateway Gateway commands: start, stop, configure, setup, unlock, lock, restart, kill, logs, status, vault.")
|
||||
print(" Start auto-selects a local port; use --port to pin one.")
|
||||
print(" Examples: nap gateway start, nap gateway start --port 8767, nap gateway logs")
|
||||
print(" Examples: nap gateway start, nap gateway vault, nap gateway vault rotate-secret --kind chat")
|
||||
print(" agent Agent commands: list, workspaces, create, show, edit.")
|
||||
print(" list shows registered project agents; workspaces shows /agents folders.")
|
||||
print(" feedback Feedback commands: list, global, status, resolve.")
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Smoke-test top-level nap gateway command routing."""
|
||||
|
||||
import importlib.util
|
||||
import pathlib
|
||||
import sys
|
||||
|
||||
|
||||
def load_module():
|
||||
script_path = pathlib.Path(__file__).resolve().parents[1] / "scripts" / "nap_install.py"
|
||||
spec = importlib.util.spec_from_file_location("nap_install", script_path)
|
||||
module = importlib.util.module_from_spec(spec)
|
||||
assert spec and spec.loader
|
||||
spec.loader.exec_module(module)
|
||||
return module
|
||||
|
||||
|
||||
def run():
|
||||
mod = load_module()
|
||||
calls = []
|
||||
|
||||
mod.run_wrapper = lambda command, args: calls.append((command, args)) or 0
|
||||
|
||||
assert mod.handle_gateway(["--help"]) == 0
|
||||
assert calls == []
|
||||
|
||||
assert mod.handle_gateway(["vault", "--help"]) == 0
|
||||
assert calls == []
|
||||
|
||||
assert mod.handle_gateway(["vault", "list"]) == 0
|
||||
assert calls[-1] == ("gateway", ["vault", "list"])
|
||||
|
||||
assert mod.handle_gateway(["vault", "rotate-secret", "--kind", "chat"]) == 0
|
||||
assert calls[-1] == ("gateway", ["vault", "rotate-secret", "--kind", "chat"])
|
||||
|
||||
assert mod.handle_gateway(["vault-setup", "list"]) == 0
|
||||
assert calls[-1] == ("gateway", ["vault-setup", "list"])
|
||||
|
||||
assert mod.handle_gateway(["terminal", "list"]) == 0
|
||||
assert calls[-1] == ("gateway", ["terminal", "list"])
|
||||
|
||||
print("ok: nap installer gateway route smoke passed")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
run()
|
||||
@@ -0,0 +1,59 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Smoke-test project encryption enable payload generation."""
|
||||
|
||||
import base64
|
||||
import importlib.util
|
||||
import pathlib
|
||||
import sys
|
||||
|
||||
|
||||
def load_module():
|
||||
script_path = pathlib.Path(__file__).resolve().parents[1] / "resources" / "scripts" / "napseer_mcp_server.py"
|
||||
sys.path.insert(0, str(script_path.parent))
|
||||
spec = importlib.util.spec_from_file_location("napseer_mcp_server", script_path)
|
||||
module = importlib.util.module_from_spec(spec)
|
||||
assert spec and spec.loader
|
||||
spec.loader.exec_module(module)
|
||||
return module
|
||||
|
||||
|
||||
def run():
|
||||
mod = load_module()
|
||||
calls = []
|
||||
|
||||
mod.DEFAULT_PROJECT_ID = "project-1"
|
||||
mod.AUTH = {"agent_id": "agent-1"}
|
||||
mod.request_project_write = lambda method, path, body, project_id, purpose, scope_type="project": calls.append(
|
||||
(method, path, body, project_id, purpose, scope_type)
|
||||
) or {"state": "encrypted", "active_project_key_version": {"version": 1}}
|
||||
mod.save_auth = lambda payload: None
|
||||
|
||||
result = mod.project_encryption_transition({"state": "encrypted", "passphrase": "pw"})
|
||||
assert result["status"] == "updated"
|
||||
assert calls, "encryption enable should call backend"
|
||||
method, path, body, project_id, purpose, scope_type = calls[0]
|
||||
assert method == "POST"
|
||||
assert path == "/v1/projects/project-1/encryption/enable"
|
||||
assert project_id == "project-1"
|
||||
assert scope_type == "encryption"
|
||||
assert body["state"] == "encrypted"
|
||||
wrapped = body["wrapped_project_key"]
|
||||
assert wrapped["credential_id"] == "local-passphrase:agent-1"
|
||||
assert wrapped["wrap_alg"] == "AES-GCM-256"
|
||||
assert wrapped["kdf_alg"] == "PBKDF2-SHA256"
|
||||
assert wrapped["kdf_params"]["source"] == "local_passphrase"
|
||||
assert wrapped["kdf_params"]["iterations"] == mod.PROJECT_KEY_KDF_ITERATIONS
|
||||
assert len(base64.b64decode(wrapped["salt_b64"])) == 16
|
||||
assert len(base64.b64decode(wrapped["kdf_params"]["nonce_b64"])) == 12
|
||||
assert len(base64.b64decode(wrapped["wrapped_project_key_b64"])) == 48
|
||||
|
||||
calls.clear()
|
||||
result = mod.project_encryption_transition({"state": "plaintext"})
|
||||
assert result["status"] == "updated"
|
||||
assert "wrapped_project_key" not in calls[0][2]
|
||||
|
||||
print("ok: project encryption enable payload smoke passed")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
run()
|
||||
Reference in New Issue
Block a user