mirror of
https://github.com/BigBodyCobain/Shadowbroker.git
synced 2026-05-08 02:16:41 +02:00
Fix Docker Infonet and Wormhole startup
This commit is contained in:
@@ -210,6 +210,7 @@ class Settings(BaseSettings):
|
||||
MESH_ALLOW_RAW_SECURE_STORAGE_FALLBACK: bool = False
|
||||
MESH_ACK_RAW_FALLBACK_AT_OWN_RISK: bool = False
|
||||
MESH_SECURE_STORAGE_SECRET: str = ""
|
||||
MESH_SECURE_STORAGE_SECRET_FILE: str = ""
|
||||
MESH_PRIVATE_LOG_TTL_S: int = 900
|
||||
# Sprint 1 rollout: restored DM boot probes stay disabled by default until
|
||||
# the architect reviews false positives from the observe-only path.
|
||||
|
||||
@@ -230,11 +230,16 @@ def _raw_fallback_allowed() -> bool:
|
||||
return False
|
||||
|
||||
|
||||
def _generated_secret_file() -> Path:
|
||||
return DATA_DIR / "secure_storage_secret.key"
|
||||
|
||||
|
||||
def _get_storage_secret() -> str | None:
|
||||
"""Return the operator-supplied secure storage secret, or None."""
|
||||
"""Return the operator-supplied or local generated secure storage secret."""
|
||||
secret = os.environ.get("MESH_SECURE_STORAGE_SECRET", "").strip()
|
||||
if secret:
|
||||
return secret
|
||||
secret_file_override = os.environ.get("MESH_SECURE_STORAGE_SECRET_FILE", "").strip()
|
||||
try:
|
||||
from services.config import get_settings
|
||||
|
||||
@@ -242,8 +247,36 @@ def _get_storage_secret() -> str | None:
|
||||
secret = str(getattr(settings, "MESH_SECURE_STORAGE_SECRET", "") or "").strip()
|
||||
if secret:
|
||||
return secret
|
||||
secret_file_override = (
|
||||
secret_file_override
|
||||
or str(getattr(settings, "MESH_SECURE_STORAGE_SECRET_FILE", "") or "").strip()
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
if not _is_windows():
|
||||
if _raw_fallback_allowed():
|
||||
return None
|
||||
secret_file = Path(secret_file_override or _generated_secret_file())
|
||||
try:
|
||||
if secret_file.exists():
|
||||
secret = secret_file.read_text(encoding="utf-8").strip()
|
||||
if secret:
|
||||
return secret
|
||||
secret_file.parent.mkdir(parents=True, exist_ok=True)
|
||||
secret = _b64(os.urandom(48))
|
||||
_atomic_write_text(secret_file, secret + "\n", encoding="utf-8")
|
||||
try:
|
||||
os.chmod(secret_file, 0o600)
|
||||
except OSError:
|
||||
pass
|
||||
logger.info("Generated local secure storage secret at %s", secret_file)
|
||||
return secret
|
||||
except Exception as exc:
|
||||
logger.warning(
|
||||
"Failed to load or generate local secure storage secret at %s: %s",
|
||||
secret_file,
|
||||
exc,
|
||||
)
|
||||
return None
|
||||
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
Tests prove:
|
||||
- Docker no longer auto-allows raw fallback
|
||||
- Non-Windows with no secure secret and no raw opt-in fails closed
|
||||
- Non-Windows with no secure secret generates a local passphrase file
|
||||
- Non-Windows with MESH_SECURE_STORAGE_SECRET works (passphrase provider)
|
||||
- Passphrase-protected envelopes round-trip correctly (master + domain)
|
||||
- Raw-to-passphrase migration works when secret is supplied
|
||||
@@ -64,10 +64,10 @@ class TestDockerNoAutoRawFallback:
|
||||
assert mesh_secure_storage._raw_fallback_allowed() is True
|
||||
|
||||
|
||||
class TestFailClosedWithoutSecret:
|
||||
"""Non-Windows with no secret and no raw opt-in must fail closed."""
|
||||
class TestGeneratedLocalSecretWithoutOperatorSecret:
|
||||
"""Non-Windows with no supplied secret generates a local passphrase file."""
|
||||
|
||||
def test_master_key_creation_fails_closed(self, tmp_path, monkeypatch):
|
||||
def test_master_key_creation_uses_generated_local_secret(self, tmp_path, monkeypatch):
|
||||
from services.mesh import mesh_secure_storage
|
||||
from services import config as config_mod
|
||||
|
||||
@@ -76,6 +76,7 @@ class TestFailClosedWithoutSecret:
|
||||
monkeypatch.setattr(mesh_secure_storage, "_is_windows", lambda: False)
|
||||
monkeypatch.delenv("PYTEST_CURRENT_TEST", raising=False)
|
||||
monkeypatch.delenv("MESH_SECURE_STORAGE_SECRET", raising=False)
|
||||
monkeypatch.delenv("MESH_SECURE_STORAGE_SECRET_FILE", raising=False)
|
||||
monkeypatch.setattr(
|
||||
config_mod,
|
||||
"get_settings",
|
||||
@@ -86,10 +87,14 @@ class TestFailClosedWithoutSecret:
|
||||
)
|
||||
_reset(mesh_secure_storage)
|
||||
|
||||
with pytest.raises(mesh_secure_storage.SecureStorageError, match="MESH_SECURE_STORAGE_SECRET"):
|
||||
mesh_secure_storage._load_master_key()
|
||||
key = mesh_secure_storage._load_master_key()
|
||||
assert len(key) == 32
|
||||
assert (tmp_path / "secure_storage_secret.key").exists()
|
||||
envelope = json.loads((tmp_path / "master.key").read_text(encoding="utf-8"))
|
||||
assert envelope["provider"] == "passphrase"
|
||||
assert "key" not in envelope
|
||||
|
||||
def test_domain_key_creation_fails_closed(self, tmp_path, monkeypatch):
|
||||
def test_domain_key_creation_uses_generated_local_secret(self, tmp_path, monkeypatch):
|
||||
from services.mesh import mesh_secure_storage
|
||||
from services import config as config_mod
|
||||
|
||||
@@ -98,6 +103,7 @@ class TestFailClosedWithoutSecret:
|
||||
monkeypatch.setattr(mesh_secure_storage, "_is_windows", lambda: False)
|
||||
monkeypatch.delenv("PYTEST_CURRENT_TEST", raising=False)
|
||||
monkeypatch.delenv("MESH_SECURE_STORAGE_SECRET", raising=False)
|
||||
monkeypatch.delenv("MESH_SECURE_STORAGE_SECRET_FILE", raising=False)
|
||||
monkeypatch.setattr(
|
||||
config_mod,
|
||||
"get_settings",
|
||||
@@ -108,8 +114,12 @@ class TestFailClosedWithoutSecret:
|
||||
)
|
||||
_reset(mesh_secure_storage)
|
||||
|
||||
with pytest.raises(mesh_secure_storage.SecureStorageError, match="MESH_SECURE_STORAGE_SECRET"):
|
||||
mesh_secure_storage._load_domain_key("test_domain", base_dir=tmp_path)
|
||||
key = mesh_secure_storage._load_domain_key("test_domain", base_dir=tmp_path)
|
||||
assert len(key) == 32
|
||||
assert (tmp_path / "secure_storage_secret.key").exists()
|
||||
envelope = json.loads((tmp_path / "_domain_keys" / "test_domain.key").read_text(encoding="utf-8"))
|
||||
assert envelope["provider"] == "passphrase"
|
||||
assert "key" not in envelope
|
||||
|
||||
|
||||
class TestPassphraseProvider:
|
||||
@@ -311,7 +321,7 @@ class TestWrongPassphraseFails:
|
||||
),
|
||||
)
|
||||
|
||||
with pytest.raises(mesh_secure_storage.SecureStorageError, match="MESH_SECURE_STORAGE_SECRET is not set"):
|
||||
with pytest.raises(mesh_secure_storage.SecureStorageError, match="Failed to unwrap"):
|
||||
mesh_secure_storage._load_master_key()
|
||||
|
||||
|
||||
@@ -517,6 +527,7 @@ class TestGetStorageSecret:
|
||||
from services import config as config_mod
|
||||
|
||||
monkeypatch.delenv("MESH_SECURE_STORAGE_SECRET", raising=False)
|
||||
monkeypatch.setattr(mesh_secure_storage, "_is_windows", lambda: True)
|
||||
monkeypatch.setattr(
|
||||
config_mod,
|
||||
"get_settings",
|
||||
@@ -524,6 +535,27 @@ class TestGetStorageSecret:
|
||||
)
|
||||
assert mesh_secure_storage._get_storage_secret() is None
|
||||
|
||||
def test_generates_local_secret_file_on_non_windows(self, tmp_path, monkeypatch):
|
||||
from services.mesh import mesh_secure_storage
|
||||
from services import config as config_mod
|
||||
|
||||
secret_file = tmp_path / "generated_secret.key"
|
||||
monkeypatch.delenv("MESH_SECURE_STORAGE_SECRET", raising=False)
|
||||
monkeypatch.delenv("PYTEST_CURRENT_TEST", raising=False)
|
||||
monkeypatch.setenv("MESH_SECURE_STORAGE_SECRET_FILE", str(secret_file))
|
||||
monkeypatch.setattr(mesh_secure_storage, "_is_windows", lambda: False)
|
||||
monkeypatch.setattr(
|
||||
config_mod,
|
||||
"get_settings",
|
||||
lambda: SimpleNamespace(MESH_SECURE_STORAGE_SECRET=""),
|
||||
)
|
||||
|
||||
first = mesh_secure_storage._get_storage_secret()
|
||||
second = mesh_secure_storage._get_storage_secret()
|
||||
assert first
|
||||
assert second == first
|
||||
assert secret_file.read_text(encoding="utf-8").strip() == first
|
||||
|
||||
def test_falls_back_to_config(self, monkeypatch):
|
||||
from services.mesh import mesh_secure_storage
|
||||
from services import config as config_mod
|
||||
|
||||
@@ -364,6 +364,7 @@ function summarizeNodePeer(peerUrl?: string): string {
|
||||
}
|
||||
|
||||
function describeBootstrapState(snapshot?: InfonetNodeStatusSnapshot | null): string {
|
||||
if (snapshot && !snapshot.node_enabled) return 'READY / DISABLED';
|
||||
const bootstrap = snapshot?.bootstrap;
|
||||
if (!bootstrap) return 'LOCAL ONLY';
|
||||
if (bootstrap.manifest_loaded) {
|
||||
@@ -376,6 +377,7 @@ function describeBootstrapState(snapshot?: InfonetNodeStatusSnapshot | null): st
|
||||
}
|
||||
|
||||
function describeSyncOutcome(snapshot?: InfonetNodeStatusSnapshot | null): string {
|
||||
if (snapshot && !snapshot.node_enabled) return 'OFF - click NODE to activate';
|
||||
const sync = snapshot?.sync_runtime;
|
||||
if (!sync) return 'IDLE';
|
||||
const outcome = String(sync.last_outcome || 'idle').trim().toLowerCase();
|
||||
@@ -433,6 +435,12 @@ function buildNodeRuntimeLines(snapshot: InfonetNodeStatusSnapshot): TermLine[]
|
||||
type: 'error',
|
||||
});
|
||||
}
|
||||
if (!snapshot.node_enabled) {
|
||||
lines.push({
|
||||
text: ' Activate: click the NODE button in the top-right controls to join the public testnet seed',
|
||||
type: 'dim',
|
||||
});
|
||||
}
|
||||
lines.push({ text: '', type: 'dim' });
|
||||
return lines;
|
||||
}
|
||||
@@ -5945,7 +5953,7 @@ export default function MeshTerminal({ isOpen, launchToken = 0, onClose, onDmCou
|
||||
PARTICIPANT NODE
|
||||
</div>
|
||||
<div className="mt-1 text-sm leading-5 text-slate-400">
|
||||
Automatic bootstrap and sync now live on the backend lane. This node can keep a local chain even with Wormhole off.
|
||||
Backend bootstrap is configured; activate the participant node to sync the public testnet seed without Wormhole.
|
||||
</div>
|
||||
</div>
|
||||
<div className="border border-cyan-500/20 bg-cyan-500/8 px-3 py-1.5 text-[13px] tracking-[0.22em] text-cyan-200">
|
||||
|
||||
Reference in New Issue
Block a user