Skip to content

Commit a9a4120

Browse files
committed
resource/remote: support TLS coordinator sessions for RemotePlace
Signed-off-by: Asher Pemberton <asher.pemberton@arm.com> Reviewed-by: Asher Pemberton <asher.pemberton@arm.com> # gatekeeper
1 parent 85e45c7 commit a9a4120

5 files changed

Lines changed: 139 additions & 1 deletion

File tree

doc/getting_started.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -449,6 +449,9 @@ When you are connecting with ``labgrid-client`` or ``labgrid-exporter`` to a
449449
the ``--tls`` (and ``--cert`` if the certificate is not trusted by the host
450450
machine) option.
451451
Refer to the ``labgrid-client`` and ``labgrid-exporter`` man pages for details.
452+
For ``RemotePlace`` connections from an environment config, set the
453+
``coordinator_tls`` option or ``LG_COORDINATOR_TLS``. If the certificate
454+
is not trusted by the host machine, set ``coordinator_cert`` in the config.
452455

453456
Using a Strategy
454457
----------------

doc/man/device-config.rst

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,15 @@ OPTIONS KEYS
4343
takes as parameter the coordinator ``HOST[:PORT]`` to connect to.
4444
Defaults to ``127.0.0.1:20408``.
4545

46+
``coordinator_tls``
47+
enables a TLS gRPC channel for ``RemotePlace`` connections.
48+
Defaults to whether ``LG_COORDINATOR_TLS`` is set.
49+
50+
``coordinator_cert``
51+
path to the coordinator TLS certificate in PEM format for ``RemotePlace``
52+
connections. If unset, the host CA certificates are used. Relative paths are
53+
resolved relative to the configuration file.
54+
4655
.. _labgrid-device-config-images:
4756

4857
IMAGES

labgrid/resource/remote.py

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
from argparse import Namespace
12
import copy
23
import os
34
import attr
@@ -16,13 +17,47 @@ def __attrs_post_init__(self):
1617
self.ready = None
1718
self.unmanaged_resources = []
1819

20+
@staticmethod
21+
def _is_tls_option_enabled(value):
22+
if isinstance(value, str):
23+
return value.strip().lower() == "true"
24+
25+
return value is True
26+
27+
def _get_credentials(self):
28+
from ..remote.common import get_client_credentials
29+
30+
tls = os.environ.get("LG_COORDINATOR_TLS") is not None
31+
cert = None
32+
33+
if self.env:
34+
config = self.env.config
35+
tls = config.get_option("coordinator_tls", tls)
36+
37+
cert = config.get_option("coordinator_cert", "")
38+
if cert:
39+
cert = config.resolve_path(str(cert))
40+
else:
41+
cert = None
42+
43+
args = Namespace(
44+
tls=self._is_tls_option_enabled(tls),
45+
cert=cert,
46+
)
47+
48+
return get_client_credentials(args)
49+
1950
def _start(self):
2051
if self.session:
2152
return
2253

2354
from ..remote.client import start_session
2455
try:
25-
self.session = start_session(self.url, extra={'env': self.env})
56+
self.session = start_session(
57+
self.url,
58+
extra={'env': self.env},
59+
credentials=self._get_credentials(),
60+
)
2661
except ConnectionRefusedError as e:
2762
raise ConnectionRefusedError(f"Could not connect to coordinator {self.url}") \
2863
from e

man/labgrid-device-config.5

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,15 @@ The \fBoptions:\fP top key configures various options such as the coordinator_ad
6060
.B \fBcoordinator_address\fP
6161
takes as parameter the coordinator \fBHOST[:PORT]\fP to connect to.
6262
Defaults to \fB127.0.0.1:20408\fP\&.
63+
.TP
64+
.B \fBcoordinator_tls\fP
65+
enables a TLS gRPC channel for \fBRemotePlace\fP connections.
66+
Defaults to whether \fBLG_COORDINATOR_TLS\fP is set.
67+
.TP
68+
.B \fBcoordinator_cert\fP
69+
path to the coordinator TLS certificate in PEM format for \fBRemotePlace\fP
70+
connections. If unset, the host CA certificates are used. Relative paths are
71+
resolved relative to the configuration file.
6372
.UNINDENT
6473
.SS IMAGES
6574
.sp

tests/test_remote.py

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
11
import argparse
22
import pexpect
33
import pytest
4+
from labgrid import Environment, Target
45
from labgrid.remote.coordinator import get_server_credentials
56
from labgrid.remote.common import get_client_credentials
7+
from labgrid.resource import RemotePlace
8+
from labgrid.resource.common import ResourceManager
9+
from labgrid.util.yaml import dump
610

711

812
def test_client_help():
@@ -287,3 +291,81 @@ def test_get_client_credentials_valid(tmpdir):
287291
creds = get_client_credentials(ns)
288292

289293
assert creds is not None
294+
295+
296+
def _make_remote_place_config(tmpdir, options=None):
297+
config = tmpdir.join("config.yaml")
298+
data = {"targets": {}}
299+
if options:
300+
data["options"] = options
301+
302+
config.write(dump(data))
303+
304+
return config
305+
306+
307+
def _capture_remote_place_credentials(monkeypatch, tmpdir, *, options=None, tls_env=False):
308+
monkeypatch.setattr(ResourceManager, "instances", {})
309+
monkeypatch.delenv("LG_COORDINATOR_TLS", raising=False)
310+
311+
if tls_env:
312+
monkeypatch.setenv("LG_COORDINATOR_TLS", "true")
313+
314+
class DummySession:
315+
loop = None
316+
317+
def get_place(self, name):
318+
return argparse.Namespace(tags={}, acquired_resources=[])
319+
320+
def get_target_resources(self, place):
321+
return {}
322+
323+
captured = {}
324+
325+
def get_client_credentials(args):
326+
captured["args"] = args
327+
return "credentials" if args.tls else None
328+
329+
def start_session(address, *, extra=None, credentials=None, **kwargs):
330+
captured["credentials"] = credentials
331+
return DummySession()
332+
333+
monkeypatch.setattr("labgrid.remote.common.get_client_credentials", get_client_credentials)
334+
monkeypatch.setattr("labgrid.remote.client.start_session", start_session)
335+
336+
config = _make_remote_place_config(tmpdir, options=options)
337+
env = Environment(str(config))
338+
target = Target("test", env=env)
339+
RemotePlace(target, name="test")
340+
341+
return captured
342+
343+
344+
@pytest.mark.parametrize(
345+
("options", "tls_env", "expected_tls", "expected_cert"),
346+
[
347+
pytest.param({}, False, False, None, id="default-no-tls"),
348+
pytest.param({}, True, True, None, id="tls-env"),
349+
pytest.param(
350+
{"coordinator_tls": True, "coordinator_cert": "cert.pem"},
351+
False,
352+
True,
353+
"cert.pem",
354+
id="tls-config",
355+
),
356+
pytest.param({"coordinator_tls": "TRUE"}, False, True, None, id="tls-config-string"),
357+
pytest.param({"coordinator_tls": False}, True, False, None, id="config-overrides-env"),
358+
pytest.param({"coordinator_tls": "false"}, True, False, None, id="config-string-overrides-env"),
359+
],
360+
)
361+
def test_remote_place_client_credentials(monkeypatch, tmpdir, options, tls_env, expected_tls, expected_cert):
362+
cert_path, _ = setup_tmp_cert_key(tmpdir)
363+
assert cert_path.basename == "cert.pem"
364+
if expected_cert:
365+
expected_cert = str(tmpdir.join(expected_cert))
366+
367+
captured = _capture_remote_place_credentials(monkeypatch, tmpdir, options=options, tls_env=tls_env)
368+
369+
assert captured["args"].tls is expected_tls
370+
assert captured["args"].cert == expected_cert
371+
assert captured["credentials"] == ("credentials" if expected_tls else None)

0 commit comments

Comments
 (0)