check in checkinator into hswaw/checkinator
repository: https://code.hackerspace.pl/checkinator
revision: 713c7e6c1a8fd6147522c1a5e3067898a1d8bf7a
Change-Id: I1bd2975a46ec0d9a89d6594fb4b9d49832001627
Reviewed-on: https://gerrit.hackerspace.pl/c/hscloud/+/1219
Reviewed-by: q3k <q3k@hackerspace.pl>
diff --git a/hswaw/checkinator/at/tracker.py b/hswaw/checkinator/at/tracker.py
new file mode 100644
index 0000000..18a139e
--- /dev/null
+++ b/hswaw/checkinator/at/tracker.py
@@ -0,0 +1,110 @@
+from at.dhcp import DhcpdUpdater, DhcpLease
+from pathlib import Path
+import yaml
+import grpc
+import json
+import re
+import subprocess
+import logging
+from concurrent import futures
+from datetime import datetime
+
+from .tracker_pb2 import DhcpClient, DhcpClients, HwAddrResponse
+from .tracker_pb2_grpc import DhcpTrackerServicer, add_DhcpTrackerServicer_to_server
+
+import argparse
+parser = argparse.ArgumentParser()
+parser.add_argument("--verbose", help="output more info", action="store_true")
+parser.add_argument("config", type=Path, help="input file")
+
+logging.basicConfig(level=logging.INFO)
+
+def lease_to_client(lease: DhcpLease) -> DhcpClient:
+ return DhcpClient(
+ hw_address = bytes.fromhex(lease.hwaddr.replace(':', '')),
+ last_seen = datetime.utcfromtimestamp(lease.atime).isoformat(),
+ client_hostname = lease.name,
+ ip_address = lease.ip
+ )
+
+class DhcpTrackerServicer(DhcpTrackerServicer):
+ def __init__(self, tracker: DhcpdUpdater, *args, **kwargs):
+ self._tracker = tracker
+ super().__init__(*args, **kwargs)
+
+ def _authorize(self, context):
+ auth = context.auth_context()
+ ctype = auth.get('transport_security_type', 'local')
+ print(ctype)
+ if ctype == [b'ssl']:
+ if b'at.hackerspace.pl' not in context.peer_identities():
+ context.abort(
+ grpc.StatusCode.PERMISSION_DENIED,
+ (
+ "Only at.hackespace.pl is allowed to access raw "
+ "clients addresses"
+ )
+ )
+ elif ctype == 'local':
+ # connection from local unix socket is trusted by default
+ pass
+ else:
+ context.abort(
+ grpc.StatusCode.PERMISSION_DENIED,
+ f"Unknown transport type: {ctype}"
+ )
+
+ def GetClients(self, request, context):
+ self._authorize(context)
+
+ clients = [
+ lease_to_client(c) for c in self._tracker.get_active_devices().values()]
+ return DhcpClients(clients = clients)
+
+ def GetHwAddr(self, request, context):
+ self._authorize(context)
+ ip_address = str(request.ip_address)
+ if not re.fullmatch('[0-9a-fA-F:.]*', ip_address):
+ raise ValueError(f'Invalid ip address: {ip_address!r}')
+ logging.info(f'running ip neigh on {ip_address}')
+ r = subprocess.run(['ip', '-json', 'neigh', 'show', ip_address], check=True, capture_output=True)
+ neighs = json.loads(r.stdout)
+ if neighs:
+ return HwAddrResponse(hw_address=bytes.fromhex(neighs[0]['lladdr'].replace(':', '')))
+ return HwAddrResponse(hw_address=None)
+
+def server():
+ args = parser.parse_args()
+
+ config = yaml.safe_load(args.config.read_text())
+ tracker = DhcpdUpdater(config['LEASE_FILE'], config['TIMEOUT'])
+ tracker.start()
+
+ server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))
+ add_DhcpTrackerServicer_to_server(DhcpTrackerServicer(tracker), server)
+
+
+ tls_address = config.get("GRPC_TLS_ADDRESS", None)
+ if tls_address:
+ cert_dir = Path(config.get('GRPC_TLS_CERT_DIR', 'cert'))
+ ca_cert = Path(config.get('GRPC_TLS_CA_CERT', 'ca.pem')).read_bytes()
+
+ server_credentials = grpc.ssl_server_credentials(
+ private_key_certificate_chain_pairs = ((
+ cert_dir.joinpath('key.pem').read_bytes(),
+ cert_dir.joinpath('cert.pem').read_bytes()
+ ),),
+ root_certificates = ca_cert,
+ require_client_auth = True
+ )
+
+ server.add_secure_port(config.get('GRPC_TLS_ADDRESS', '[::]:2847'), server_credentials)
+
+ unix_socket = config.get('GRPC_UNIX_SOCKET', False)
+ if unix_socket:
+ server.add_insecure_port(f'unix://{unix_socket}')
+
+ if tls_address or unix_socket:
+ print('starting grpc server ...')
+ server.start()
+ server.wait_for_termination()