diff --git a/contrib/sync-places.py b/contrib/sync-places.py index 0140dcbf4..dd265f3d6 100755 --- a/contrib/sync-places.py +++ b/contrib/sync-places.py @@ -161,7 +161,7 @@ async def do_dump(session, args): strings and tags should be assigned to those places. The files are structured like: - places: # A dictonary of places where each key is a place name + places: # A dictionary of places where each key is a place name my-place1: # Replace with your place matches: # A list of match patterns. Replace with your match patterns - "*/my-place1/*" diff --git a/labgrid/remote/coordinator.py b/labgrid/remote/coordinator.py index ea60f4933..1511462f9 100644 --- a/labgrid/remote/coordinator.py +++ b/labgrid/remote/coordinator.py @@ -5,6 +5,8 @@ import traceback from enum import Enum from functools import wraps +from ipaddress import ip_address +from os import environ import time from contextlib import contextmanager import copy @@ -173,6 +175,20 @@ class ResourceImport(ResourceEntry): orphaned = attr.ib(init=False, default=False, validator=attr.validators.instance_of(bool)) +def _is_from_loopback_ip(peer: str) -> bool: + family, address_and_port = peer.split(":", maxsplit=1) + if family not in ("ipv4", "ipv6"): + msg = "Invalid address family found" + logging.warning(msg) + return False + address, _port = address_and_port.rsplit(":", maxsplit=1) + address = address.removeprefix("%5B").removesuffix("%5D") + try: + return ip_address(address).is_loopback + except ValueError: + return False + + def locked(func): @wraps(func) async def wrapper(self, *args, **kwargs): @@ -182,6 +198,20 @@ async def wrapper(self, *args, **kwargs): return wrapper +def auth_required(func): + @wraps(func) + async def decorated(self, request, context): + if environ.get("AUTH") == "LOCAL_ADMIN": + peer = context.peer() + if not _is_from_loopback_ip(peer): + context.set_code(grpc.StatusCode.UNAUTHENTICATED) + context.set_details("Access was not local") + raise grpc.RpcError(grpc.StatusCode.UNAUTHENTICATED, "Access was not local") + return await func(self, request, context) + + return decorated + + class ExporterCommand: def __init__(self, request) -> None: self.request = request @@ -499,6 +529,7 @@ async def request_task(): except KeyError: logging.info("Never received startup from peer %s that disconnected", peer) + @auth_required @locked async def AddPlace(self, request, context): name = request.name @@ -513,6 +544,7 @@ async def AddPlace(self, request, context): self.save_later() return labgrid_coordinator_pb2.AddPlaceResponse() + @auth_required @locked async def DeletePlace(self, request, context): name = request.name @@ -529,6 +561,7 @@ async def DeletePlace(self, request, context): self.save_later() return labgrid_coordinator_pb2.DeletePlaceResponse() + @auth_required @locked async def AddPlaceAlias(self, request, context): placename = request.placename @@ -543,6 +576,7 @@ async def AddPlaceAlias(self, request, context): self.save_later() return labgrid_coordinator_pb2.AddPlaceAliasResponse() + @auth_required @locked async def DeletePlaceAlias(self, request, context): placename = request.placename @@ -560,6 +594,7 @@ async def DeletePlaceAlias(self, request, context): self.save_later() return labgrid_coordinator_pb2.DeletePlaceAliasResponse() + @auth_required @locked async def SetPlaceTags(self, request, context): placename = request.placename @@ -589,6 +624,7 @@ async def SetPlaceTags(self, request, context): self.save_later() return labgrid_coordinator_pb2.SetPlaceTagsResponse() + @auth_required @locked async def SetPlaceComment(self, request, context): placename = request.placename @@ -603,6 +639,7 @@ async def SetPlaceComment(self, request, context): self.save_later() return labgrid_coordinator_pb2.SetPlaceCommentResponse() + @auth_required @locked async def AddPlaceMatch(self, request, context): placename = request.placename @@ -621,6 +658,7 @@ async def AddPlaceMatch(self, request, context): self.save_later() return labgrid_coordinator_pb2.AddPlaceMatchResponse() + @auth_required @locked async def DeletePlaceMatch(self, request, context): placename = request.placename diff --git a/pyproject.toml b/pyproject.toml index 4a1a99897..14fb4fe7b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -36,6 +36,7 @@ dependencies = [ "exceptiongroup>=1.3.0", # TODO: drop if Python >= 3.11 guaranteed "grpcio>=1.64.1, <2.0.0", "grpcio-reflection>=1.64.1, <2.0.0", + "ipaddress>=1.0", "protobuf>=5.27.0", "jinja2>=3.0.2", "pexpect>=4.8.0",