Skip to content

Commit 2033967

Browse files
committed
gRPC authentication proposal - using gRPC secure channels
Signed-off-by: Jacek Bartynowski <jacek.bartynowski@arm.com>
1 parent 0304ec6 commit 2033967

10 files changed

Lines changed: 291 additions & 11 deletions

File tree

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
*.pyc
2+
__pycache__/*
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
__all__ = ['SERVER_CERTIFICATE', 'SERVER_CERTIFICATE_KEY',
2+
'generate_jwt_token', 'is_token_valid', 'CustomAuthMetadataPlugin',
3+
'SignatureValidationInterceptor']
4+
5+
from .helper_functions import SERVER_CERTIFICATE, SERVER_CERTIFICATE_KEY
6+
from .helper_functions import generate_jwt_token, is_token_valid
7+
from .plugins_interceptors import CustomAuthMetadataPlugin, SignatureValidationInterceptor
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import datetime
2+
import os
3+
import jwt
4+
5+
6+
def load_credential_from_file(filepath):
7+
'''
8+
Loads certificate from file and returns it as bytes
9+
10+
Args:
11+
filepath (str): The path to the file to load
12+
13+
Returns:
14+
The content of the certificate file as bytes
15+
'''
16+
real_path = os.path.join(os.path.dirname(__file__), filepath)
17+
with open(real_path, "rb") as f:
18+
return f.read()
19+
20+
21+
token_secret = '3#pn$%agj02_r119*peydh&w+kt(2gy=n&e-68t19fup#33=)7'
22+
23+
# sample token details
24+
jwt_token_details = {
25+
'id': 'labgrid',
26+
'username': 'labgrid-username',
27+
'firstName': 'firstName',
28+
'lastName': 'lastName',
29+
'email': 'labgrid-user@company.com',
30+
'isStaff': True,
31+
'exp': (datetime.datetime.now(datetime.timezone.utc) + datetime.timedelta(days=1)),}
32+
33+
34+
def generate_jwt_token(token_details=jwt_token_details, secret=token_secret):
35+
'''
36+
Generates a JWT token with the given token details and secret and return it
37+
'''
38+
return jwt.encode(token_details, secret, algorithm='HS256')
39+
40+
41+
def is_token_valid(token, secret_key=token_secret):
42+
'''
43+
Validates the given token using the secret key and returns True if the token is valid
44+
45+
Args:
46+
token (str): The token to validate
47+
secret_key (str): The secret key to use for validation
48+
49+
Returns:
50+
True if the token is valid, False otherwise
51+
'''
52+
try:
53+
decoded_token =jwt.decode(token, secret_key, algorithms=['HS256'])
54+
except jwt.ExpiredSignatureError:
55+
return False
56+
except jwt.InvalidTokenError:
57+
return False
58+
59+
if not 'id' in decoded_token:
60+
return False
61+
62+
if not 'exp' in decoded_token:
63+
return False
64+
65+
exp_date = datetime.datetime.fromtimestamp(decoded_token['exp'])
66+
curr_date = datetime.datetime.utcnow()
67+
68+
if exp_date < curr_date:
69+
return False
70+
71+
return True
72+
73+
SERVER_CERTIFICATE=load_credential_from_file('../Certificates/server.crt')
74+
SERVER_CERTIFICATE_KEY=load_credential_from_file('../Certificates/server.key')
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import grpc
2+
from .helper_functions import generate_jwt_token, is_token_valid
3+
4+
5+
JWT_TOKEN = generate_jwt_token()
6+
7+
class CustomAuthMetadataPlugin(grpc.AuthMetadataPlugin):
8+
'''
9+
Authentication plugin used to add {'x-signature', JWT_TOKEN} HTTP header
10+
'''
11+
def __call__(self, context, callback):
12+
signature = context.method_name[::-1]
13+
signature = JWT_TOKEN
14+
callback((("x-signature", signature),), None)
15+
16+
17+
class SignatureValidationInterceptor(grpc.aio.ServerInterceptor):
18+
'''
19+
Middleware used to validate the JWT token in the HTTP header
20+
'''
21+
def __init__(self):
22+
def abort(ignored_request, context):
23+
context.abort(grpc.StatusCode.UNAUTHENTICATED, "Invalid signature")
24+
self._abort_handler = grpc.unary_unary_rpc_method_handler(abort)
25+
26+
def intercept_service(self, continuation, handler_call_details):
27+
'''
28+
Extracts the token from the HTTP header and validates it
29+
'''
30+
token = ''
31+
32+
for item in handler_call_details.invocation_metadata:
33+
dictionary = item._asdict()
34+
if dictionary['key'] == 'x-signature':
35+
token = dictionary['value']
36+
break
37+
38+
if token != '':
39+
if is_token_valid(token):
40+
return continuation(handler_call_details)
41+
42+
self._abort_handler()

labgrid/remote/certificates/ca.crt

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
-----BEGIN CERTIFICATE-----
2+
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
32+
5pZY1waC7qPyPeEyDyribA==
33+
-----END CERTIFICATE-----
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
-----BEGIN CERTIFICATE-----
2+
MIIFbDCCA1QCAQEwDQYJKoZIhvcNAQELBQAwfDELMAkGA1UEBhMCR0IxEzARBgNV
3+
BAcMCk1hbmNoZXN0ZXIxFDASBgNVBAoMC0FSTSBMaW1pdGVkMQ8wDQYDVQQLDAZD
4+
RS1HUFUxEjAQBgNVBAMMCWxvY2FsaG9zdDEdMBsGCSqGSIb3DQEJARYOY2UtZ3B1
5+
QGFybS5jb20wHhcNMjQxMDI2MTIwNzQ3WhcNMjUxMDI2MTIwNzQ3WjB8MQswCQYD
6+
VQQGEwJHQjETMBEGA1UEBwwKTWFuY2hlc3RlcjEUMBIGA1UECgwLQVJNIExpbWl0
7+
ZWQxDzANBgNVBAsMBkNFLUdQVTESMBAGA1UEAwwJbG9jYWxob3N0MR0wGwYJKoZI
8+
hvcNAQkBFg5jZS1ncHVAYXJtLmNvbTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCC
9+
AgoCggIBAKjd3ctmc6iF31b3fvyNbOKb5LBeeDrspbDYqIPUncDxjfP0Krwd1VKu
10+
7dKQoA5t5WR1gasJRcfvs/T3oRAuYUuZTU2HOeXmYwzhW4g5qnNuZt0M7B6C2j4C
11+
leDRBpp6mwORmACS/ldXpxLkHzAjgb5aI5EdCBhc63mSQd4DlZR0TtVVNGM0In6O
12+
D8kvCD21XW8LvxFKCzVPyOlkY2KOMfhZg7g21Vjql8ZBPZCkW067hS8o12IUyJC/
13+
/WZUWFV0sDhHFEurgwa/RzUg8snwhiGQ+Q6BNTdvZg26X4Sj8Fu31LUz9Wx2Hc7T
14+
qtsmNoDhGY6iPu7T8DnLeGxzsbBAF28IjTx1lHRTwhw/nhv5e0LQ5/CE9F3u6YsO
15+
Ywvj5xm0prlVxdg+uvmsWKJdATIKz1ko5rG1beEO8GCb9FaZ4spBNRC4X90fPwCN
16+
YxSeA4Im+lPmnMPGe/mtzjVDTPRrpoJUdd90ELkof7jVyWj4XBnmFh15l0+1liP1
17+
Ey9P0zm6HuoZNirGHbJryPyqbmF4pFA3t+5psvpy5Oh19O2SzA935LfLH+bZKkd4
18+
6Q7iOz15dbBA1bJfbT9ElozCvaGTNLg9ElXtgb2mAQ0ozTt7Y7WtRiHmKUSo5OBn
19+
h5X9yBhFgkmFeSaJAtFWsnFfqrmgL9JOVQckt0HzhBbZ7f+5nPJLAgMBAAEwDQYJ
20+
KoZIhvcNAQELBQADggIBALnmgUmEquYdgwMlZycw2Kxjg6XaWt9Ere7ngBs8KVYV
21+
md6wbC6vLQxcpVxlX4Xv8rtyF+F+A1vK6E0TQCjLKiwBhZpZmhYK4Wopq/4Z/jOI
22+
o+GiPa9J4KTJtQNmoiJcwElaN3AOfPqPh7tCnepMJWSHoFmmHsRjyo8SxPmblQv1
23+
XJ4L/Wr3dTt2QEItW8IE3TGblBWRuZGpDu/6KIL5li/jC3a+VPLAEQBWZgfDzENV
24+
S0SstPBVGzwjcLXdhw2Okz36sghMVTe0C3kjUamrxmgKBj9y6pmGDsr8vNECCWCL
25+
uKjuUw7KMYORLOSOabbRS+XhjfxzCQUxu0yn+RneykkNipxMHqBJSCM5XBi83iHG
26+
fxr5jYAdYyp7HBvJ/l2iFGYaS5/O/AVmraGsEMzWgGZBG+x9XBQo6CDZbGjjNXUe
27+
bTVB3aVk1tIyeK5rWaJOhZq4YzAvysd0cTR0lLr9dKQuYf0aPDITL0qKbk7JQTrN
28+
yq+dOZ4ipnjiaLI/Fv09TZN/Rin6Hqx9p+41lQQy8cGa2FYLGMvBrthKkOUfTrl9
29+
hVkujbNSXL09cwWuUgV6KLNXNXX02lp2LG3R+b5BWoNHKZYabAInVfv5M3Z40yec
30+
KgMa4bOE6NAxg5F5DqjGWuwWdKG1hIV6CLqVUzhDP7rrg4LnnpELr0ms56EYAXr5
31+
-----END CERTIFICATE-----
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
-----BEGIN RSA PRIVATE KEY-----
2+
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
51+
-----END RSA PRIVATE KEY-----

labgrid/remote/client.py

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
)
4141
from .. import Environment, Target, target_factory
4242
from ..exceptions import NoDriverFoundError, NoResourceFoundError, InvalidConfigError
43+
from .authentication import SERVER_CERTIFICATE, CustomAuthMetadataPlugin
4344
from .generated import labgrid_coordinator_pb2, labgrid_coordinator_pb2_grpc
4445
from ..resource.remote import RemotePlaceManager, RemotePlace
4546
from ..util import diff_dict, flat_dict, dump, atomic_replace, labgrid_version, Timeout
@@ -99,10 +100,21 @@ def __attrs_post_init__(self):
99100
("grpc.http2.max_pings_without_data", 0), # no limit
100101
]
101102

102-
self.channel = grpc.aio.insecure_channel(
103-
target=self.address,
104-
options=channel_options,
105-
)
103+
if self.args.auth:
104+
call_credentials = grpc.metadata_call_credentials(CustomAuthMetadataPlugin(), name="auth")
105+
channel_credentials = grpc.ssl_channel_credentials(SERVER_CERTIFICATE)
106+
composite_credentials = grpc.composite_channel_credentials(channel_credentials, call_credentials)
107+
108+
self.channel = grpc.aio.secure_channel(
109+
target=self.address,
110+
credentials=composite_credentials,
111+
options=channel_options,
112+
)
113+
else:
114+
self.channel = grpc.aio.insecure_channel(
115+
target=self.address,
116+
options=channel_options,
117+
)
106118
self.stub = labgrid_coordinator_pb2_grpc.CoordinatorStub(self.channel)
107119

108120
self.out_queue = asyncio.Queue()
@@ -1699,6 +1711,7 @@ def main():
16991711
)
17001712
parser.add_argument("-v", "--verbose", action="count", default=0)
17011713
parser.add_argument("-P", "--proxy", type=str, help="proxy connections via given ssh host")
1714+
parser.add_argument("-A", "--auth", action="store_true", default=False, help="enable gRPC authentication")
17021715
subparsers = parser.add_subparsers(
17031716
dest="command",
17041717
title="available subcommands",

labgrid/remote/coordinator.py

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
TAG_KEY,
2121
TAG_VAL,
2222
)
23+
from .authentication import SERVER_CERTIFICATE, SERVER_CERTIFICATE_KEY, SignatureValidationInterceptor
2324
from .scheduler import TagSet, schedule
2425
from .generated import labgrid_coordinator_pb2
2526
from .generated import labgrid_coordinator_pb2_grpc
@@ -957,7 +958,7 @@ async def GetReservations(self, request: labgrid_coordinator_pb2.GetReservations
957958
return labgrid_coordinator_pb2.GetReservationsResponse(reservations=reservations)
958959

959960

960-
async def serve(listen, cleanup) -> None:
961+
async def serve(listen, authenticate, cleanup) -> None:
961962
# It seems since https://github.com/grpc/grpc/pull/34647, the
962963
# ping_timeout_ms default of 60 seconds overrides keepalive_timeout_ms,
963964
# so set it as well.
@@ -973,6 +974,7 @@ async def serve(listen, cleanup) -> None:
973974
]
974975
server = grpc.aio.server(
975976
options=channel_options,
977+
interceptors= ( (SignatureValidationInterceptor(),) if authenticate else ()),
976978
)
977979
coordinator = Coordinator()
978980
labgrid_coordinator_pb2_grpc.add_CoordinatorServicer_to_server(coordinator, server)
@@ -993,7 +995,18 @@ async def serve(listen, cleanup) -> None:
993995
except ImportError:
994996
logging.info("Module grpcio-channelz not available")
995997

996-
server.add_insecure_port(listen)
998+
if authenticate:
999+
server_credentials = grpc.ssl_server_credentials(
1000+
(
1001+
(
1002+
SERVER_CERTIFICATE_KEY,
1003+
SERVER_CERTIFICATE,
1004+
),
1005+
)
1006+
)
1007+
server.add_secure_port(listen, server_credentials)
1008+
else:
1009+
server.add_insecure_port(listen)
9971010
logging.debug("Starting server")
9981011
await server.start()
9991012

@@ -1020,6 +1033,7 @@ def main():
10201033
help="coordinator listening host and port",
10211034
)
10221035
parser.add_argument("-d", "--debug", action="store_true", default=False, help="enable debug mode")
1036+
parser.add_argument("-A", "--auth", action="store_true", default=False, help="enable gRPC authentication")
10231037

10241038
args = parser.parse_args()
10251039

@@ -1031,7 +1045,7 @@ def main():
10311045
cleanup = []
10321046
loop.set_debug(True)
10331047
try:
1034-
loop.run_until_complete(serve(args.listen, cleanup))
1048+
loop.run_until_complete(serve(args.listen, args.auth, cleanup))
10351049
finally:
10361050
if cleanup:
10371051
loop.run_until_complete(*cleanup)

labgrid/remote/exporter.py

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import attr
2020
import grpc
2121

22+
from .authentication import SERVER_CERTIFICATE, CustomAuthMetadataPlugin
2223
from .config import ResourceConfig
2324
from .common import ResourceEntry, queue_as_aiter
2425
from .generated import labgrid_coordinator_pb2, labgrid_coordinator_pb2_grpc
@@ -798,10 +799,20 @@ def __init__(self, config) -> None:
798799
if urlsplit(f"//{config['coordinator']}").port is None:
799800
config["coordinator"] += ":20408"
800801

801-
self.channel = grpc.aio.insecure_channel(
802-
target=config["coordinator"],
803-
options=channel_options,
804-
)
802+
if config["authentication"]:
803+
call_credentials = grpc.metadata_call_credentials(CustomAuthMetadataPlugin(), name="auth")
804+
channel_credentials = grpc.ssl_channel_credentials(SERVER_CERTIFICATE)
805+
composite_credentials = grpc.composite_channel_credentials(channel_credentials, call_credentials)
806+
807+
self.channel = grpc.aio.secure_channel(
808+
target=config["coordinator"],
809+
credentials=composite_credentials,
810+
options=channel_options,)
811+
else:
812+
self.channel = grpc.aio.insecure_channel(
813+
target=config["coordinator"],
814+
options=channel_options,
815+
)
805816
self.stub = labgrid_coordinator_pb2_grpc.CoordinatorStub(self.channel)
806817
self.out_queue = asyncio.Queue()
807818
self.pump_task = None
@@ -1044,6 +1055,7 @@ def main():
10441055
help="enable isolated mode (always request SSH forwards)",
10451056
)
10461057
parser.add_argument("resources", metavar="RESOURCES", type=str, help="resource config file name")
1058+
parser.add_argument("-A", "--auth", action="store_true", default=False, help="enable gRPC authentication")
10471059

10481060
args = parser.parse_args()
10491061

@@ -1055,6 +1067,7 @@ def main():
10551067
"resources": args.resources,
10561068
"coordinator": args.coordinator,
10571069
"isolated": args.isolated,
1070+
"authentication": args.auth,
10581071
}
10591072

10601073
print(f"exporter name: {config['name']}")

0 commit comments

Comments
 (0)