From 7a2b399ca346fdbdfcf16fd5b823ef194cf9453b Mon Sep 17 00:00:00 2001 From: Jacek Bartynowski Date: Tue, 12 Nov 2024 07:38:21 +0000 Subject: [PATCH] gRPC authentication proposal - using gRPC secure channels --- labgrid/remote/authentication/.gitignore | 2 + labgrid/remote/authentication/__init__.py | 7 ++ .../remote/authentication/helper_functions.py | 74 +++++++++++++++++++ .../authentication/plugins_interceptors.py | 42 +++++++++++ labgrid/remote/certificates/ca.crt | 33 +++++++++ labgrid/remote/certificates/server.crt | 31 ++++++++ labgrid/remote/certificates/server.key | 51 +++++++++++++ labgrid/remote/client.py | 21 +++++- labgrid/remote/coordinator.py | 20 ++++- labgrid/remote/exporter.py | 21 +++++- 10 files changed, 291 insertions(+), 11 deletions(-) create mode 100644 labgrid/remote/authentication/.gitignore create mode 100644 labgrid/remote/authentication/__init__.py create mode 100644 labgrid/remote/authentication/helper_functions.py create mode 100644 labgrid/remote/authentication/plugins_interceptors.py create mode 100644 labgrid/remote/certificates/ca.crt create mode 100644 labgrid/remote/certificates/server.crt create mode 100644 labgrid/remote/certificates/server.key diff --git a/labgrid/remote/authentication/.gitignore b/labgrid/remote/authentication/.gitignore new file mode 100644 index 000000000..d42fd7da7 --- /dev/null +++ b/labgrid/remote/authentication/.gitignore @@ -0,0 +1,2 @@ +*.pyc +__pycache__/* diff --git a/labgrid/remote/authentication/__init__.py b/labgrid/remote/authentication/__init__.py new file mode 100644 index 000000000..634f9f9ab --- /dev/null +++ b/labgrid/remote/authentication/__init__.py @@ -0,0 +1,7 @@ +__all__ = ['SERVER_CERTIFICATE', 'SERVER_CERTIFICATE_KEY', + 'generate_jwt_token', 'is_token_valid', 'CustomAuthMetadataPlugin', + 'SignatureValidationInterceptor'] + +from .helper_functions import SERVER_CERTIFICATE, SERVER_CERTIFICATE_KEY +from .helper_functions import generate_jwt_token, is_token_valid +from .plugins_interceptors import CustomAuthMetadataPlugin, SignatureValidationInterceptor diff --git a/labgrid/remote/authentication/helper_functions.py b/labgrid/remote/authentication/helper_functions.py new file mode 100644 index 000000000..e960ca1dd --- /dev/null +++ b/labgrid/remote/authentication/helper_functions.py @@ -0,0 +1,74 @@ +import datetime +import os +import jwt + + +def load_credential_from_file(filepath): + ''' + Loads certificate from file and returns it as bytes + + Args: + filepath (str): The path to the file to load + + Returns: + The content of the certificate file as bytes + ''' + real_path = os.path.join(os.path.dirname(__file__), filepath) + with open(real_path, "rb") as f: + return f.read() + + +token_secret = '3#pn$%agj02_r119*peydh&w+kt(2gy=n&e-68t19fup#33=)7' + +# sample token details +jwt_token_details = { + 'id': 'labgrid', + 'username': 'labgrid-username', + 'firstName': 'firstName', + 'lastName': 'lastName', + 'email': 'labgrid-user@company.com', + 'isStaff': True, + 'exp': (datetime.datetime.now(datetime.timezone.utc) + datetime.timedelta(days=1)),} + + +def generate_jwt_token(token_details=jwt_token_details, secret=token_secret): + ''' + Generates a JWT token with the given token details and secret and return it + ''' + return jwt.encode(token_details, secret, algorithm='HS256') + + +def is_token_valid(token, secret_key=token_secret): + ''' + Validates the given token using the secret key and returns True if the token is valid + + Args: + token (str): The token to validate + secret_key (str): The secret key to use for validation + + Returns: + True if the token is valid, False otherwise + ''' + try: + decoded_token =jwt.decode(token, secret_key, algorithms=['HS256']) + except jwt.ExpiredSignatureError: + return False + except jwt.InvalidTokenError: + return False + + if not 'id' in decoded_token: + return False + + if not 'exp' in decoded_token: + return False + + exp_date = datetime.datetime.fromtimestamp(decoded_token['exp']) + curr_date = datetime.datetime.utcnow() + + if exp_date < curr_date: + return False + + return True + +SERVER_CERTIFICATE=load_credential_from_file('../Certificates/server.crt') +SERVER_CERTIFICATE_KEY=load_credential_from_file('../Certificates/server.key') diff --git a/labgrid/remote/authentication/plugins_interceptors.py b/labgrid/remote/authentication/plugins_interceptors.py new file mode 100644 index 000000000..44aff2934 --- /dev/null +++ b/labgrid/remote/authentication/plugins_interceptors.py @@ -0,0 +1,42 @@ +import grpc +from .helper_functions import generate_jwt_token, is_token_valid + + +JWT_TOKEN = generate_jwt_token() + +class CustomAuthMetadataPlugin(grpc.AuthMetadataPlugin): + ''' + Authentication plugin used to add {'x-signature', JWT_TOKEN} HTTP header + ''' + def __call__(self, context, callback): + signature = context.method_name[::-1] + signature = JWT_TOKEN + callback((("x-signature", signature),), None) + + +class SignatureValidationInterceptor(grpc.aio.ServerInterceptor): + ''' + Middleware used to validate the JWT token in the HTTP header + ''' + def __init__(self): + def abort(ignored_request, context): + context.abort(grpc.StatusCode.UNAUTHENTICATED, "Invalid signature") + self._abort_handler = grpc.unary_unary_rpc_method_handler(abort) + + def intercept_service(self, continuation, handler_call_details): + ''' + Extracts the token from the HTTP header and validates it + ''' + token = '' + + for item in handler_call_details.invocation_metadata: + dictionary = item._asdict() + if dictionary['key'] == 'x-signature': + token = dictionary['value'] + break + + if token != '': + if is_token_valid(token): + return continuation(handler_call_details) + + self._abort_handler() diff --git a/labgrid/remote/certificates/ca.crt b/labgrid/remote/certificates/ca.crt new file mode 100644 index 000000000..9b317378d --- /dev/null +++ b/labgrid/remote/certificates/ca.crt @@ -0,0 +1,33 @@ +-----BEGIN CERTIFICATE----- +MIIFrDCCA5SgAwIBAgIUF/RY4CD6al79N6+WEl0OLmRuv5YwDQYJKoZIhvcNAQEL +BQAwfDELMAkGA1UEBhMCR0IxEzARBgNVBAcMCk1hbmNoZXN0ZXIxFDASBgNVBAoM +C0FSTSBMaW1pdGVkMQ8wDQYDVQQLDAZDRS1HUFUxEjAQBgNVBAMMCWxvY2FsaG9z +dDEdMBsGCSqGSIb3DQEJARYOY2UtZ3B1QGFybS5jb20wHhcNMjQxMDI2MTE0NDQ5 +WhcNMjUxMDI2MTE0NDQ5WjB8MQswCQYDVQQGEwJHQjETMBEGA1UEBwwKTWFuY2hl +c3RlcjEUMBIGA1UECgwLQVJNIExpbWl0ZWQxDzANBgNVBAsMBkNFLUdQVTESMBAG +A1UEAwwJbG9jYWxob3N0MR0wGwYJKoZIhvcNAQkBFg5jZS1ncHVAYXJtLmNvbTCC +AiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAOc3FStpCnjB5DdZW4wMZFX8 +l6TetEo02QSonD0JCoyMIvc1oUUDR1EZdPDNIEsNUA//V5o6I4zENbPz9vRyvXzZ +/Qn6q55CLwPwFfnOkjD6SHDcS4KTjAVYhPSZxak5pqCJoERkvJFfUeSwR5ksYBhE +rvniiP9brmL9jiNywysPcFwOEC6Sidku6zYoNz0L+++JWBbMoFwfLvQPoQcdqidY +3ULLX7VsPE0MzkXsNjiKo68XiqqNjExld111hfq1hZxpfQr5xdYw21djK/ELEKYY +EP0VlCdRysCvO1RE/FrNlTPlJkTW/suTaDq8Y77peDmBkyfvCGfHraCkIalUDFTs +n7SQURUuVdTQK5D1Wrdym7ux1eAUcLVLjBTaKbug3dTh1bFnIJySUERaGfxo7b+Q +7HEzc00wsrPbTTCnJIgZUDmD+f8BFxcyOscQZr9z3Jz+zTi1kAVTrkzDAsl/I41l +OjmnP8ICDUL8A1WB7z/moJjUrX7DpkPyLHMQbbuqGXEzUMdK2Rs54a69FYwGT3NR +xaVjREsqawxfpnvsbsRzcJYTZ7jmhJkAUd2mZ8eryAmuyReKFXbBAT/nef5gVMWK +zSVix6Bnxm0QHVlooCy3IjygQdpTgYXpcYBB0h2CQcfkD1PjhLggh3ckCvWBzuwH +x/vrDPGRc5jgqAVKtIGdAgMBAAGjJjAkMBIGA1UdEwEB/wQIMAYBAf8CAQMwDgYD +VR0PAQH/BAQDAgHGMA0GCSqGSIb3DQEBCwUAA4ICAQDkF/w4X/D3zWfpBCEKWWdh +s30cUYZLO7xUr+yXb/M3ZVXSLZ2FaM9OeE3DA1oE21N9khBkw6XpqN9YTENrYyue +ycUbDnkUiHMHM2k5zc3SIqpMIza3UasjS+zMuMHP/mEaummF8Ou8JLyaEMLIIg/I +ry+trqV8Z/10w5o2J3JcrlOaA2CF25QF+HGgYFSYuVpwO/Gp8lHnNTh8TJjXxmve +f4Jt2DzWr1pkvnTYPNlOz+2HIP+SxZs+k4kQ0wwQOAVcJAlhLLiTbwK47IeV8BHR +ALMzvW3Ahjh6WZhnr86oU7Sm0GAQOT+lCqholRi6RbcXnCux2BST/oiwDe1ajmE2 +EZjq+l4A6CVp0CUfA/pBl+0oPNZWjnvDQeY1mI5bK2o/54IsOFNRl5SBIpBvMtqM +EZTKwM958UyQqOrWUCwBNFJ+jcYR+coKKmbGiYYRCkT4fIjLauzJGnJ+Ni9cA9ty +zBGow461rUeoMDCzIlBrXNbIZoLkmXT1/2+UXWZmLmgK5wSoOFug0CEpSgr3dzlP +4bhMzyxcxEe1in8haFRv35y10dhVTNJ7M/Oxo+r2EqrS9aDkzADIsUta1Huz/V0Z +93rFBhRZBbCeEtZ+G0i3XOsSAPueyf6EJplI+Hpd9Nw/hNjbdbxk8c5WgUR3oT8q +5pZY1waC7qPyPeEyDyribA== +-----END CERTIFICATE----- diff --git a/labgrid/remote/certificates/server.crt b/labgrid/remote/certificates/server.crt new file mode 100644 index 000000000..7068e14a3 --- /dev/null +++ b/labgrid/remote/certificates/server.crt @@ -0,0 +1,31 @@ +-----BEGIN CERTIFICATE----- +MIIFbDCCA1QCAQEwDQYJKoZIhvcNAQELBQAwfDELMAkGA1UEBhMCR0IxEzARBgNV +BAcMCk1hbmNoZXN0ZXIxFDASBgNVBAoMC0FSTSBMaW1pdGVkMQ8wDQYDVQQLDAZD +RS1HUFUxEjAQBgNVBAMMCWxvY2FsaG9zdDEdMBsGCSqGSIb3DQEJARYOY2UtZ3B1 +QGFybS5jb20wHhcNMjQxMDI2MTIwNzQ3WhcNMjUxMDI2MTIwNzQ3WjB8MQswCQYD +VQQGEwJHQjETMBEGA1UEBwwKTWFuY2hlc3RlcjEUMBIGA1UECgwLQVJNIExpbWl0 +ZWQxDzANBgNVBAsMBkNFLUdQVTESMBAGA1UEAwwJbG9jYWxob3N0MR0wGwYJKoZI +hvcNAQkBFg5jZS1ncHVAYXJtLmNvbTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCC +AgoCggIBAKjd3ctmc6iF31b3fvyNbOKb5LBeeDrspbDYqIPUncDxjfP0Krwd1VKu +7dKQoA5t5WR1gasJRcfvs/T3oRAuYUuZTU2HOeXmYwzhW4g5qnNuZt0M7B6C2j4C +leDRBpp6mwORmACS/ldXpxLkHzAjgb5aI5EdCBhc63mSQd4DlZR0TtVVNGM0In6O +D8kvCD21XW8LvxFKCzVPyOlkY2KOMfhZg7g21Vjql8ZBPZCkW067hS8o12IUyJC/ +/WZUWFV0sDhHFEurgwa/RzUg8snwhiGQ+Q6BNTdvZg26X4Sj8Fu31LUz9Wx2Hc7T +qtsmNoDhGY6iPu7T8DnLeGxzsbBAF28IjTx1lHRTwhw/nhv5e0LQ5/CE9F3u6YsO +Ywvj5xm0prlVxdg+uvmsWKJdATIKz1ko5rG1beEO8GCb9FaZ4spBNRC4X90fPwCN +YxSeA4Im+lPmnMPGe/mtzjVDTPRrpoJUdd90ELkof7jVyWj4XBnmFh15l0+1liP1 +Ey9P0zm6HuoZNirGHbJryPyqbmF4pFA3t+5psvpy5Oh19O2SzA935LfLH+bZKkd4 +6Q7iOz15dbBA1bJfbT9ElozCvaGTNLg9ElXtgb2mAQ0ozTt7Y7WtRiHmKUSo5OBn +h5X9yBhFgkmFeSaJAtFWsnFfqrmgL9JOVQckt0HzhBbZ7f+5nPJLAgMBAAEwDQYJ +KoZIhvcNAQELBQADggIBALnmgUmEquYdgwMlZycw2Kxjg6XaWt9Ere7ngBs8KVYV +md6wbC6vLQxcpVxlX4Xv8rtyF+F+A1vK6E0TQCjLKiwBhZpZmhYK4Wopq/4Z/jOI +o+GiPa9J4KTJtQNmoiJcwElaN3AOfPqPh7tCnepMJWSHoFmmHsRjyo8SxPmblQv1 +XJ4L/Wr3dTt2QEItW8IE3TGblBWRuZGpDu/6KIL5li/jC3a+VPLAEQBWZgfDzENV +S0SstPBVGzwjcLXdhw2Okz36sghMVTe0C3kjUamrxmgKBj9y6pmGDsr8vNECCWCL +uKjuUw7KMYORLOSOabbRS+XhjfxzCQUxu0yn+RneykkNipxMHqBJSCM5XBi83iHG +fxr5jYAdYyp7HBvJ/l2iFGYaS5/O/AVmraGsEMzWgGZBG+x9XBQo6CDZbGjjNXUe +bTVB3aVk1tIyeK5rWaJOhZq4YzAvysd0cTR0lLr9dKQuYf0aPDITL0qKbk7JQTrN +yq+dOZ4ipnjiaLI/Fv09TZN/Rin6Hqx9p+41lQQy8cGa2FYLGMvBrthKkOUfTrl9 +hVkujbNSXL09cwWuUgV6KLNXNXX02lp2LG3R+b5BWoNHKZYabAInVfv5M3Z40yec +KgMa4bOE6NAxg5F5DqjGWuwWdKG1hIV6CLqVUzhDP7rrg4LnnpELr0ms56EYAXr5 +-----END CERTIFICATE----- diff --git a/labgrid/remote/certificates/server.key b/labgrid/remote/certificates/server.key new file mode 100644 index 000000000..8ac1de395 --- /dev/null +++ b/labgrid/remote/certificates/server.key @@ -0,0 +1,51 @@ +-----BEGIN RSA PRIVATE KEY----- +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 +-----END RSA PRIVATE KEY----- diff --git a/labgrid/remote/client.py b/labgrid/remote/client.py index 5ab4f0683..326190c24 100755 --- a/labgrid/remote/client.py +++ b/labgrid/remote/client.py @@ -40,6 +40,7 @@ ) from .. import Environment, Target, target_factory from ..exceptions import NoDriverFoundError, NoResourceFoundError, InvalidConfigError +from .authentication import SERVER_CERTIFICATE, CustomAuthMetadataPlugin from .generated import labgrid_coordinator_pb2, labgrid_coordinator_pb2_grpc from ..resource.remote import RemotePlaceManager, RemotePlace from ..util import diff_dict, flat_dict, dump, atomic_replace, labgrid_version, Timeout @@ -99,10 +100,21 @@ def __attrs_post_init__(self): ("grpc.http2.max_pings_without_data", 0), # no limit ] - self.channel = grpc.aio.insecure_channel( - target=self.address, - options=channel_options, - ) + if self.args.auth: + call_credentials = grpc.metadata_call_credentials(CustomAuthMetadataPlugin(), name="auth") + channel_credentials = grpc.ssl_channel_credentials(SERVER_CERTIFICATE) + composite_credentials = grpc.composite_channel_credentials(channel_credentials, call_credentials) + + self.channel = grpc.aio.secure_channel( + target=self.address, + credentials=composite_credentials, + options=channel_options, + ) + else: + self.channel = grpc.aio.insecure_channel( + target=self.address, + options=channel_options, + ) self.stub = labgrid_coordinator_pb2_grpc.CoordinatorStub(self.channel) self.out_queue = asyncio.Queue() @@ -1699,6 +1711,7 @@ def main(): ) parser.add_argument("-v", "--verbose", action="count", default=0) parser.add_argument("-P", "--proxy", type=str, help="proxy connections via given ssh host") + parser.add_argument("-A", "--auth", action="store_true", default=False, help="enable gRPC authentication") subparsers = parser.add_subparsers( dest="command", title="available subcommands", diff --git a/labgrid/remote/coordinator.py b/labgrid/remote/coordinator.py index 930f3306c..a4c9c0870 100644 --- a/labgrid/remote/coordinator.py +++ b/labgrid/remote/coordinator.py @@ -20,6 +20,7 @@ TAG_KEY, TAG_VAL, ) +from .authentication import SERVER_CERTIFICATE, SERVER_CERTIFICATE_KEY, SignatureValidationInterceptor from .scheduler import TagSet, schedule from .generated import labgrid_coordinator_pb2 from .generated import labgrid_coordinator_pb2_grpc @@ -957,7 +958,7 @@ async def GetReservations(self, request: labgrid_coordinator_pb2.GetReservations return labgrid_coordinator_pb2.GetReservationsResponse(reservations=reservations) -async def serve(listen, cleanup) -> None: +async def serve(listen, authenticate, cleanup) -> None: # It seems since https://github.com/grpc/grpc/pull/34647, the # ping_timeout_ms default of 60 seconds overrides keepalive_timeout_ms, # so set it as well. @@ -973,6 +974,7 @@ async def serve(listen, cleanup) -> None: ] server = grpc.aio.server( options=channel_options, + interceptors= ( (SignatureValidationInterceptor(),) if authenticate else ()), ) coordinator = Coordinator() labgrid_coordinator_pb2_grpc.add_CoordinatorServicer_to_server(coordinator, server) @@ -993,7 +995,18 @@ async def serve(listen, cleanup) -> None: except ImportError: logging.info("Module grpcio-channelz not available") - server.add_insecure_port(listen) + if authenticate: + server_credentials = grpc.ssl_server_credentials( + ( + ( + SERVER_CERTIFICATE_KEY, + SERVER_CERTIFICATE, + ), + ) + ) + server.add_secure_port(listen, server_credentials) + else: + server.add_insecure_port(listen) logging.debug("Starting server") await server.start() @@ -1020,6 +1033,7 @@ def main(): help="coordinator listening host and port", ) parser.add_argument("-d", "--debug", action="store_true", default=False, help="enable debug mode") + parser.add_argument("-A", "--auth", action="store_true", default=False, help="enable gRPC authentication") args = parser.parse_args() @@ -1031,7 +1045,7 @@ def main(): cleanup = [] loop.set_debug(True) try: - loop.run_until_complete(serve(args.listen, cleanup)) + loop.run_until_complete(serve(args.listen, args.auth, cleanup)) finally: if cleanup: loop.run_until_complete(*cleanup) diff --git a/labgrid/remote/exporter.py b/labgrid/remote/exporter.py index 8f0f77243..7d981ccad 100755 --- a/labgrid/remote/exporter.py +++ b/labgrid/remote/exporter.py @@ -19,6 +19,7 @@ import attr import grpc +from .authentication import SERVER_CERTIFICATE, CustomAuthMetadataPlugin from .config import ResourceConfig from .common import ResourceEntry, queue_as_aiter from .generated import labgrid_coordinator_pb2, labgrid_coordinator_pb2_grpc @@ -798,10 +799,20 @@ def __init__(self, config) -> None: if urlsplit(f"//{config['coordinator']}").port is None: config["coordinator"] += ":20408" - self.channel = grpc.aio.insecure_channel( - target=config["coordinator"], - options=channel_options, - ) + if config["authentication"]: + call_credentials = grpc.metadata_call_credentials(CustomAuthMetadataPlugin(), name="auth") + channel_credentials = grpc.ssl_channel_credentials(SERVER_CERTIFICATE) + composite_credentials = grpc.composite_channel_credentials(channel_credentials, call_credentials) + + self.channel = grpc.aio.secure_channel( + target=config["coordinator"], + credentials=composite_credentials, + options=channel_options,) + else: + self.channel = grpc.aio.insecure_channel( + target=config["coordinator"], + options=channel_options, + ) self.stub = labgrid_coordinator_pb2_grpc.CoordinatorStub(self.channel) self.out_queue = asyncio.Queue() self.pump_task = None @@ -1044,6 +1055,7 @@ def main(): help="enable isolated mode (always request SSH forwards)", ) parser.add_argument("resources", metavar="RESOURCES", type=str, help="resource config file name") + parser.add_argument("-A", "--auth", action="store_true", default=False, help="enable gRPC authentication") args = parser.parse_args() @@ -1055,6 +1067,7 @@ def main(): "resources": args.resources, "coordinator": args.coordinator, "isolated": args.isolated, + "authentication": args.auth, } print(f"exporter name: {config['name']}")