diff --git a/sros2/.coveragerc b/sros2/.coveragerc
index c07be3ca..8eb3e6d5 100644
--- a/sros2/.coveragerc
+++ b/sros2/.coveragerc
@@ -2,3 +2,4 @@
omit =
# omit test directory
test/*
+ setup.py
diff --git a/sros2/sros2/api/_artifact_generation.py b/sros2/sros2/api/_artifact_generation.py
index 80746ce6..59e945ba 100644
--- a/sros2/sros2/api/_artifact_generation.py
+++ b/sros2/sros2/api/_artifact_generation.py
@@ -21,10 +21,12 @@
from . import _policy
+# FIXME move away from mutable default (linter should complain about it)
def generate_artifacts(
- keystore_path: Optional[pathlib.Path] = None,
- identity_names: List[str] = [],
- policy_files: List[pathlib.Path] = []) -> None:
+ keystore_path: Optional[pathlib.Path] = None,
+ identity_names: List[str] = [],
+ policy_files: List[pathlib.Path] = []
+) -> None:
if keystore_path is None:
keystore_path = _utilities.get_keystore_path_from_env()
if keystore_path is None:
@@ -37,6 +39,8 @@ def generate_artifacts(
for identity in identity_names:
keystore.create_enclave(keystore_path, identity)
for policy_file in policy_files:
+ # FIXME load_policy should raise something else
+ # than RuntimeError and it should be caught here
policy_tree = load_policy(policy_file)
enclaves_element = policy_tree.find('enclaves')
for enclave in enclaves_element:
diff --git a/sros2/test/policies/invalid_policy_missing_topics_tag.xml b/sros2/test/policies/invalid_policy_missing_topics_tag.xml
new file mode 100644
index 00000000..fcf7da8e
--- /dev/null
+++ b/sros2/test/policies/invalid_policy_missing_topics_tag.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+ chatter
+
+
+
+
+
+
diff --git a/sros2/test/sros2/commands/security/verbs/test_create_enclave.py b/sros2/test/sros2/commands/security/verbs/test_create_enclave.py
index bed0c740..396d2082 100644
--- a/sros2/test/sros2/commands/security/verbs/test_create_enclave.py
+++ b/sros2/test/sros2/commands/security/verbs/test_create_enclave.py
@@ -34,8 +34,8 @@
# This fixture will run once for the entire module (as opposed to once per test)
@pytest.fixture(scope='module')
-def enclave_keys_dir(tmpdir_factory) -> Path:
- keystore_dir = Path(str(tmpdir_factory.mktemp('keystore')))
+def enclave_keys_dir(tmp_path_factory) -> Path:
+ keystore_dir = tmp_path_factory.mktemp('keystore')
# First, create the keystore
sros2.keystore.create_keystore(keystore_dir)
@@ -96,12 +96,11 @@ def test_create_enclave(enclave_keys_dir):
assert (enclave_keys_dir / expected_file).is_file()
-def test_create_enclave_twice(tmpdir):
- keystore_dir = Path(tmpdir)
-
+def test_create_enclave_twice(tmp_path):
# First, create the keystore
- sros2.keystore.create_keystore(keystore_dir)
- assert keystore_dir.is_dir()
+ sros2.keystore.create_keystore(tmp_path)
+ assert tmp_path.is_dir()
+ keystore_dir = tmp_path
# Now using that keystore, create an enclave
assert cli.main(
diff --git a/sros2/test/sros2/commands/security/verbs/test_create_keystore.py b/sros2/test/sros2/commands/security/verbs/test_create_keystore.py
index 4939fba6..f51a30ac 100644
--- a/sros2/test/sros2/commands/security/verbs/test_create_keystore.py
+++ b/sros2/test/sros2/commands/security/verbs/test_create_keystore.py
@@ -29,14 +29,14 @@
# This fixture will run once for the entire module (as opposed to once per test)
@pytest.fixture(scope='module')
-def keystore_dir(tmpdir_factory) -> Path:
- keystore_dir = str(tmpdir_factory.mktemp('keystore'))
+def keystore_dir(tmp_path_factory) -> Path:
+ keystore_dir = tmp_path_factory.mktemp('keystore')
# Create the keystore
- assert cli.main(argv=['security', 'create_keystore', keystore_dir]) == 0
+ assert cli.main(argv=['security', 'create_keystore', str(keystore_dir)]) == 0
# Return path to keystore directory
- return Path(keystore_dir)
+ return keystore_dir
def test_create_keystore(keystore_dir):
@@ -95,3 +95,12 @@ def test_governance_p7s(keystore_dir):
def test_governance_xml(keystore_dir):
# Validates valid XML
ElementTree.parse(str(keystore_dir / 'enclaves' / 'governance.xml'))
+
+
+def test_create_keystore_twice_fails(tmp_path):
+ keystore_dir = tmp_path / 'keystore'
+ keystore_dir.mkdir()
+
+ # Create the keystore
+ assert cli.main(argv=['security', 'create_keystore', str(keystore_dir)]) == 0
+ assert cli.main(argv=['security', 'create_keystore', str(keystore_dir)]) == 1
diff --git a/sros2/test/sros2/commands/security/verbs/test_create_permission.py b/sros2/test/sros2/commands/security/verbs/test_create_permission.py
index 72b4419b..eeca4468 100644
--- a/sros2/test/sros2/commands/security/verbs/test_create_permission.py
+++ b/sros2/test/sros2/commands/security/verbs/test_create_permission.py
@@ -30,8 +30,8 @@
# This fixture will run once for the entire module (as opposed to once per test)
@pytest.fixture(scope='module')
-def enclave_dir(tmpdir_factory, test_policy_dir) -> pathlib.Path:
- keystore_dir = pathlib.Path(str(tmpdir_factory.mktemp('keystore')))
+def enclave_dir(tmp_path_factory, test_policy_dir) -> pathlib.Path:
+ keystore_dir = tmp_path_factory.mktemp('keystore')
# First, create the keystore as well as an enclave for the talker
sros2.keystore.create_keystore(keystore_dir)
diff --git a/sros2/test/sros2/commands/security/verbs/test_generate_artifacts.py b/sros2/test/sros2/commands/security/verbs/test_generate_artifacts.py
new file mode 100644
index 00000000..cc46f5ee
--- /dev/null
+++ b/sros2/test/sros2/commands/security/verbs/test_generate_artifacts.py
@@ -0,0 +1,126 @@
+# Copyright 2024 Mikael Arguedas
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from pathlib import Path
+
+import pytest
+
+from ros2cli import cli
+
+from sros2 import _utilities
+
+
+# Here we provide only very high level testing as this verb
+# is just a combination of calls to the others ones covered by precise tests
+
+# This fixture will run once for the entire module (as opposed to once per test)
+@pytest.fixture(scope='module')
+def keystore_dir(tmp_path_factory) -> Path:
+ keystore_dir = tmp_path_factory.mktemp('keystore')
+
+ # Create the keystore
+ assert cli.main(argv=['security', 'create_keystore', str(keystore_dir)]) == 0
+
+ # Return path to keystore directory
+ return keystore_dir
+
+
+def test_cli_keystore_args(capsys, tmp_path, monkeypatch, keystore_dir):
+ # invalid keystore
+ assert cli.main(argv=['security', 'generate_artifacts', '-k', str(tmp_path)]) == 0
+ output = capsys.readouterr().out.rstrip()
+ assert 'is not a valid keystore, creating new keystore' in output
+
+ assert cli.main(argv=['security', 'generate_artifacts', '-k', str(keystore_dir)]) == 0
+
+ # keystore from env
+ with monkeypatch.context() as m:
+ m.setenv(_utilities._KEYSTORE_DIR_ENV, str(keystore_dir))
+ assert cli.main(argv=['security', 'generate_artifacts']) == 0
+
+ # invalid keystore from env
+ tmp_keystore_folder = tmp_path
+ with monkeypatch.context() as m:
+ m.setenv(_utilities._KEYSTORE_DIR_ENV, str(tmp_keystore_folder / 'bar'))
+ assert cli.main(argv=['security', 'generate_artifacts']) == 0
+ output = capsys.readouterr().out.rstrip()
+ assert 'is not a valid keystore, creating new keystore' in output
+
+ # no keystore in args or in env
+ with monkeypatch.context() as m:
+ m.delenv(_utilities._KEYSTORE_DIR_ENV, raising=False)
+ assert cli.main(argv=['security', 'generate_artifacts']) == 1
+ output = capsys.readouterr().err.rstrip()
+ assert (
+ 'Unable to generate artifacts: '
+ "'ROS_SECURITY_KEYSTORE' isn't pointing at a valid keystore"
+ in output
+ )
+
+
+def test_cli_enclave_args(keystore_dir):
+ # no enclaves
+ assert cli.main(argv=['security', 'generate_artifacts', '-k', str(keystore_dir)]) == 0
+
+ # 1 existing enclave and 1 to create
+ assert cli.main(
+ argv=['security', 'create_enclave', str(keystore_dir), '/test_enclave']) == 0
+ enclave_list = ['/test_enclave', '/test_enclave2']
+ command_args = ['security', 'generate_artifacts', '-k', str(keystore_dir)]
+ for name in enclave_list:
+ command_args.append('-e')
+ command_args.append(name)
+ assert cli.main(argv=command_args) == 0
+ expected_files = (
+ 'cert.pem', 'governance.p7s', 'identity_ca.cert.pem', 'key.pem', 'permissions.p7s',
+ 'permissions.xml', 'permissions_ca.cert.pem'
+ )
+ for enclave in enclave_list:
+ enclave_keys_dir = keystore_dir / 'enclaves' / enclave.lstrip('/')
+ assert len(list(enclave_keys_dir.iterdir())) == len(expected_files)
+
+ for expected_file in expected_files:
+ assert (enclave_keys_dir / expected_file).is_file()
+
+
+def test_cli_policies_args(capsys, keystore_dir, test_policy_dir):
+ enclave_list = ['/test_enclave', '/test_enclave2', '/minimal_action/minimal_action_server']
+ command_args = ['security', 'generate_artifacts', '-k', str(keystore_dir)]
+ for name in enclave_list:
+ command_args.append('-e')
+ command_args.append(name)
+ # Test an invalid policy file
+ retcode = cli.main(
+ argv=command_args + [
+ '-p', str(test_policy_dir / 'invalid_policy_missing_topics_tag.xml')
+ ]
+ )
+ assert "Element 'topic': This element is not expected." in retcode
+ # Test a valid policy file
+ assert cli.main(
+ argv=command_args + [
+ '-p', str(test_policy_dir / 'minimal_action.policy.xml')
+ ]
+ ) == 0
+ # ensure that missing enclaves have been created on the fly
+ for name in enclave_list:
+ assert Path(keystore_dir / 'enclaves' / name.lstrip('/')).is_dir()
+ # Test a valid set of policy files
+ assert cli.main(
+ argv=command_args + [
+ '-p', str(test_policy_dir / 'minimal_action.policy.xml'),
+ '-p', str(test_policy_dir / 'add_two_ints.policy.xml'),
+ '-p', str(test_policy_dir / 'talker_listener.policy.xml'),
+ ]
+ ) == 0
diff --git a/sros2/test/sros2/commands/security/verbs/test_list_enclaves.py b/sros2/test/sros2/commands/security/verbs/test_list_enclaves.py
index 47ec6948..d14996b9 100644
--- a/sros2/test/sros2/commands/security/verbs/test_list_enclaves.py
+++ b/sros2/test/sros2/commands/security/verbs/test_list_enclaves.py
@@ -57,7 +57,7 @@ def test_list_enclaves_no_keys(capsys):
def test_list_enclaves_uninitialized_keystore(capsys):
with tempfile.TemporaryDirectory() as keystore_dir:
# Verify that list_enclaves properly handles an uninitialized keystore
- assert cli.main(argv=['security', 'list_enclaves', keystore_dir]) != 0
+ assert cli.main(argv=['security', 'list_enclaves', keystore_dir]) == 1
assert (capsys.readouterr().err.strip() ==
f"Unable to list enclaves: '{keystore_dir}' is not a valid keystore")
@@ -65,6 +65,6 @@ def test_list_enclaves_uninitialized_keystore(capsys):
def test_list_enclaves_no_keystore(capsys):
# Verify that list_enclaves properly handles a non-existent keystore
keystore = os.path.join(tempfile.gettempdir(), 'non-existent')
- assert cli.main(argv=['security', 'list_enclaves', keystore]) != 0
+ assert cli.main(argv=['security', 'list_enclaves', keystore]) == 1
assert (capsys.readouterr().err.strip() ==
f"Unable to list enclaves: '{keystore}' is not a valid keystore")
diff --git a/sros2/test/sros2/commands/security/verbs/test_no_verb.py b/sros2/test/sros2/commands/security/verbs/test_no_verb.py
new file mode 100644
index 00000000..e6b02406
--- /dev/null
+++ b/sros2/test/sros2/commands/security/verbs/test_no_verb.py
@@ -0,0 +1,21 @@
+# Copyright 2024 Mikael Arguedas
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from ros2cli import cli
+
+
+def test_no_verb(capsys):
+ assert cli.main(argv=['security']) == 0
+ output = capsys.readouterr().out.rstrip()
+ assert 'Call `ros2 security -h` for more detailed usage.' in output
diff --git a/sros2/test/sros2/keystore/test_enclave.py b/sros2/test/sros2/keystore/test_enclave.py
index fbc45e50..b1192ff0 100644
--- a/sros2/test/sros2/keystore/test_enclave.py
+++ b/sros2/test/sros2/keystore/test_enclave.py
@@ -12,6 +12,16 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+from pathlib import Path
+
+import pytest
+
+from ros2cli import cli
+
+from sros2.errors import (
+ InvalidEnclaveNameError,
+ InvalidKeystoreError,
+)
from sros2.keystore import _enclave
@@ -29,3 +39,23 @@ def test_is_key_name_valid():
assert not _enclave._is_enclave_name_valid('foo/bar')
assert not _enclave._is_enclave_name_valid('/42foo')
assert not _enclave._is_enclave_name_valid('/foo/42bar')
+
+
+@pytest.fixture()
+def keystore_dir(tmp_path_factory) -> Path:
+ keystore_dir = tmp_path_factory.mktemp('keystore')
+
+ # Create the keystore
+ assert cli.main(argv=['security', 'create_keystore', str(keystore_dir)]) == 0
+
+ # Return path to keystore directory
+ return keystore_dir
+
+
+def test_create_enclave_invalid_arguments(keystore_dir):
+ with pytest.raises(InvalidKeystoreError):
+ _enclave.create_enclave(Path('foo/bar'), '/baz/foobar')
+ with pytest.raises(InvalidKeystoreError):
+ _enclave.create_enclave(Path('foo/bar'), 'baz/foobar')
+ with pytest.raises(InvalidEnclaveNameError):
+ _enclave.create_enclave(keystore_dir, 'baz/foobar')