Accept systemd credential delivery and clarify manual K1 control
This commit is contained in:
@@ -191,14 +191,31 @@ class LinuxApplicationAuthorityLoader:
|
||||
os.environ.get("CREDENTIALS_DIRECTORY", "/run/credentials/mission-core-k1.service")
|
||||
)
|
||||
buffer = bytearray()
|
||||
directory_fd = None
|
||||
try:
|
||||
fd = os.open(directory / "k1-application", os.O_RDONLY | os.O_NOFOLLOW)
|
||||
directory_fd = os.open(directory, os.O_RDONLY | os.O_DIRECTORY | os.O_NOFOLLOW)
|
||||
directory_info = os.fstat(directory_fd)
|
||||
if (
|
||||
directory_info.st_uid not in {0, os.geteuid()}
|
||||
or directory_info.st_mode & 0o027
|
||||
):
|
||||
raise ValueError("Invalid credential directory")
|
||||
fd = os.open(
|
||||
"k1-application", os.O_RDONLY | os.O_NOFOLLOW | os.O_NONBLOCK,
|
||||
dir_fd=directory_fd,
|
||||
)
|
||||
with os.fdopen(fd, "rb") as stream:
|
||||
info = os.fstat(stream.fileno())
|
||||
# systemd prefers root ownership + a service-UID read ACL.
|
||||
# Its ACL mask appears as group-read in st_mode (0440).
|
||||
# The trusted root-owned directory/file and kernel access check
|
||||
# own that authorization; do not mistake the mask for exposure.
|
||||
acl_delivered = info.st_uid == 0 and directory_info.st_uid == 0
|
||||
if (
|
||||
not stat.S_ISREG(info.st_mode)
|
||||
or info.st_uid != os.geteuid()
|
||||
or info.st_mode & 0o077
|
||||
or info.st_uid not in {0, os.geteuid()}
|
||||
or info.st_mode & (0o037 if acl_delivered else 0o077)
|
||||
or info.st_mode & 0o111
|
||||
):
|
||||
raise ValueError("Invalid credential permissions")
|
||||
buffer.extend(stream.read(1025))
|
||||
@@ -207,8 +224,10 @@ class LinuxApplicationAuthorityLoader:
|
||||
return ApplicationControlAuthority(openapi_key=buffer.decode("ascii").strip())
|
||||
except (OSError, ValueError, UnicodeError):
|
||||
raise ApplicationAuthorityLoadError(
|
||||
"Служебный ключ K1 не установлен на БК.",
|
||||
"Службе K1 на БК не удалось загрузить служебный ключ.",
|
||||
reason_code="application_authority_unavailable",
|
||||
) from None
|
||||
finally:
|
||||
if directory_fd is not None:
|
||||
os.close(directory_fd)
|
||||
buffer[:] = b"\0" * len(buffer)
|
||||
|
||||
@@ -29,6 +29,7 @@ from .facade import (
|
||||
_validate_installed_compatibility_profile,
|
||||
)
|
||||
from .linux_host import LinuxApplicationAuthorityLoader, LinuxWifiAssociationProbe, wifi_networks
|
||||
from .protocol.application_authority import ApplicationAuthorityLoadError
|
||||
|
||||
ATTESTATION = {
|
||||
"firmware_version": "3.0.2",
|
||||
@@ -94,11 +95,20 @@ def node_operation_id(plugin_id: str | None) -> str | None:
|
||||
class NodeBridge:
|
||||
def __init__(self, repository_root: Path, *, service=None):
|
||||
self.rerun = NodeRerunHub()
|
||||
self.application_authority_available = None
|
||||
if service is None:
|
||||
_validate_installed_compatibility_profile(repository_root)
|
||||
loader = LinuxApplicationAuthorityLoader()
|
||||
# systemd's credential mount is immutable for this service lifetime.
|
||||
# This startup check proves loading, not K1 authentication or control.
|
||||
try:
|
||||
loader.load()
|
||||
self.application_authority_available = True
|
||||
except ApplicationAuthorityLoadError:
|
||||
self.application_authority_available = False
|
||||
service = XgridsK1CompatibilityService(
|
||||
repository_root,
|
||||
application_authority_loader=LinuxApplicationAuthorityLoader(),
|
||||
application_authority_loader=loader,
|
||||
host_wifi_association_probe=LinuxWifiAssociationProbe(),
|
||||
visualization_bridge_factory=self.rerun.create,
|
||||
)
|
||||
@@ -108,7 +118,10 @@ class NodeBridge:
|
||||
|
||||
async def state(self) -> dict:
|
||||
snapshot = await self.invoke("state.read", {}, "state-read")
|
||||
return self.project(snapshot)
|
||||
result = self.project(snapshot)
|
||||
if self.application_authority_available is not None:
|
||||
result["application_authority_available"] = self.application_authority_available
|
||||
return result
|
||||
|
||||
@staticmethod
|
||||
def project(snapshot: dict) -> dict:
|
||||
@@ -343,7 +356,11 @@ def create_app(repository_root: Path):
|
||||
raise ValueError("Command expired")
|
||||
result = await sensor.execute(command, request.headers["X-Node-Id"])
|
||||
return {"state": "complete", "result": result}
|
||||
except Exception:
|
||||
except Exception as error:
|
||||
logging.getLogger(__name__).warning(
|
||||
"K1 sensor operation failed: exception=%s chain=%s",
|
||||
type(error).__name__, failure_locations(error),
|
||||
)
|
||||
return {
|
||||
"state": "unknown",
|
||||
"error": "Действие K1 не подтверждено. Обновите состояние устройства.",
|
||||
|
||||
@@ -64,6 +64,7 @@ def project_sensor(snapshot, node_id):
|
||||
"opened_at": session["opened_at"],
|
||||
}
|
||||
control = snapshot.get("application_control_session") or {}
|
||||
attempt = snapshot.get("connection_attempt") or {}
|
||||
return {
|
||||
"id": identifier,
|
||||
"name": "XGRIDS K1",
|
||||
@@ -90,6 +91,12 @@ def project_sensor(snapshot, node_id):
|
||||
"phase": control.get("state"),
|
||||
"can_start": lifecycle.get("ready_to_start", False),
|
||||
"can_stop": control.get("state") in {"start-requested", "initializing", "scanning"},
|
||||
"can_verify": any(action in lifecycle.get("allowed_actions", []) for action in (
|
||||
"verify-control-read-only", "observe-current-device-network",
|
||||
"observe-configured-device-network", "observe-fresh-device-network",
|
||||
)),
|
||||
"network_applied": connected or attempt.get("phase") == "network_applied",
|
||||
"reason_code": None if connected else attempt.get("public_error_code"),
|
||||
"acquisition_id": acquisition.get("acquisition_id"),
|
||||
},
|
||||
"live_settings": snapshot.get("viewer_settings", {}),
|
||||
|
||||
Reference in New Issue
Block a user