diff --git a/rcl/CMakeLists.txt b/rcl/CMakeLists.txt
index c8ee07353..cee1035e8 100644
--- a/rcl/CMakeLists.txt
+++ b/rcl/CMakeLists.txt
@@ -9,6 +9,7 @@ find_package(rcutils REQUIRED)
find_package(rmw REQUIRED)
find_package(rmw_implementation REQUIRED)
find_package(rosidl_generator_c REQUIRED)
+find_package(tinydir_vendor REQUIRED)
include_directories(include)
@@ -54,6 +55,7 @@ set(${PROJECT_NAME}_sources
src/rcl/timer.c
src/rcl/validate_topic_name.c
src/rcl/wait.c
+ src/rcl/security_directory.c
)
add_library(${PROJECT_NAME} ${${PROJECT_NAME}_sources})
@@ -65,6 +67,7 @@ ament_target_dependencies(${PROJECT_NAME}
"rcutils"
"rosidl_generator_c"
${RCL_LOGGING_IMPL}
+ "tinydir_vendor"
)
# Causes the visibility macros to use dllexport rather than dllimport,
diff --git a/rcl/include/rcl/security_directory.h b/rcl/include/rcl/security_directory.h
new file mode 100644
index 000000000..c9292a613
--- /dev/null
+++ b/rcl/include/rcl/security_directory.h
@@ -0,0 +1,66 @@
+// Copyright 2018 Open Source Robotics Foundation, Inc.
+//
+// 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.
+
+#ifndef RCL__SECURITY_DIRECTORY_H_
+#define RCL__SECURITY_DIRECTORY_H_
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+#include "rcl/allocator.h"
+#include "rcl/visibility_control.h"
+
+#ifndef ROS_SECURITY_NODE_DIRECTORY_VAR_NAME
+ #define ROS_SECURITY_NODE_DIRECTORY_VAR_NAME "ROS_SECURITY_NODE_DIRECTORY"
+#endif
+
+#ifndef ROS_SECURITY_ROOT_DIRECTORY_VAR_NAME
+ #define ROS_SECURITY_ROOT_DIRECTORY_VAR_NAME "ROS_SECURITY_ROOT_DIRECTORY"
+#endif
+
+#ifndef ROS_SECURITY_LOOKUP_TYPE_VAR_NAME
+ #define ROS_SECURITY_LOOKUP_TYPE_VAR_NAME "ROS_SECURITY_LOOKUP_TYPE"
+#endif
+
+/// Return the secure root directory associated with a node given its validated name and namespace.
+/**
+ * E.g. for a node named "c" in namespace "/a/b", the secure root path will be
+ * "a/b/c", where the delimiter "/" is native for target file system (e.g. "\\" for _WIN32).
+ * If no exact match is found for the node name, a best match would be used instead
+ * (by performing longest-prefix matching).
+ *
+ * However, this expansion can be overridden by setting the secure node directory environment
+ * variable, allowing users to explicitly specify the exact secure root directory to be utilized.
+ * Such an override is useful for where the FQN of a node is non-deterministic before runtime,
+ * or when testing and using additional tools that may not otherwise be easily provisioned.
+ *
+ * \param[in] node_name validated node name (a single token)
+ * \param[in] node_namespace validated, absolute namespace (starting with "/")
+ * \param[in] allocator the allocator to use for allocation
+ * \returns machine specific (absolute) node secure root path or NULL on failure
+ */
+RCL_PUBLIC
+const char * rcl_get_secure_root(
+ const char * node_name,
+ const char * node_namespace,
+ const rcl_allocator_t * allocator
+);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // RCL__SECURITY_DIRECTORY_H_
diff --git a/rcl/package.xml b/rcl/package.xml
index e9371d831..b4d7b9917 100644
--- a/rcl/package.xml
+++ b/rcl/package.xml
@@ -16,10 +16,12 @@
rcl_interfaces
rcutils
rosidl_generator_c
+ tinydir_vendor
rcl_interfaces
rcutils
rosidl_generator_c
+ tinydir_vendor
rcl_interfaces
ament_cmake
diff --git a/rcl/src/rcl/node.c b/rcl/src/rcl/node.c
index 536cddbcc..4973a9b5b 100644
--- a/rcl/src/rcl/node.c
+++ b/rcl/src/rcl/node.c
@@ -29,6 +29,7 @@ extern "C"
#include "rcl/logging_rosout.h"
#include "rcl/rcl.h"
#include "rcl/remap.h"
+#include "rcl/security_directory.h"
#include "rcutils/filesystem.h"
#include "rcutils/find.h"
#include "rcutils/format_string.h"
@@ -46,9 +47,8 @@ extern "C"
#include "./common.h"
#include "./context_impl.h"
+#include "tinydir/tinydir.h"
-#define ROS_SECURITY_NODE_DIRECTORY_VAR_NAME "ROS_SECURITY_NODE_DIRECTORY"
-#define ROS_SECURITY_ROOT_DIRECTORY_VAR_NAME "ROS_SECURITY_ROOT_DIRECTORY"
#define ROS_SECURITY_STRATEGY_VAR_NAME "ROS_SECURITY_STRATEGY"
#define ROS_SECURITY_ENABLE_VAR_NAME "ROS_SECURITY_ENABLE"
@@ -101,86 +101,6 @@ const char * rcl_create_node_logger_name(
return node_logger_name;
}
-/// Return the secure root directory associated with a node given its validated name and namespace.
-/**
- * E.g. for a node named "c" in namespace "/a/b", the secure root path will be
- * "a/b/c", where the delimiter "/" is native for target file system (e.g. "\\" for _WIN32).
- * However, this expansion can be overridden by setting the secure node directory environment
- * variable, allowing users to explicitly specify the exact secure root directory to be utilized.
- * Such an override is useful for where the FQN of a node is non-deterministic before runtime,
- * or when testing and using additional tools that may not otherwise not be easily provisioned.
- *
- * \param[in] node_name validated node name (a single token)
- * \param[in] node_namespace validated, absolute namespace (starting with "/")
- * \param[in] allocator the allocator to use for allocation
- * \returns machine specific (absolute) node secure root path or NULL on failure
- */
-const char * rcl_get_secure_root(
- const char * node_name,
- const char * node_namespace,
- const rcl_allocator_t * allocator)
-{
- bool ros_secure_node_override = true;
- const char * ros_secure_root_env = NULL;
- if (NULL == node_name) {
- return NULL;
- }
- if (rcutils_get_env(ROS_SECURITY_NODE_DIRECTORY_VAR_NAME, &ros_secure_root_env)) {
- return NULL;
- }
- if (!ros_secure_root_env) {
- return NULL;
- }
- size_t ros_secure_root_size = strlen(ros_secure_root_env);
- if (!ros_secure_root_size) {
- // check root directory if node directory environment variable is empty
- if (rcutils_get_env(ROS_SECURITY_ROOT_DIRECTORY_VAR_NAME, &ros_secure_root_env)) {
- return NULL;
- }
- if (!ros_secure_root_env) {
- return NULL;
- }
- ros_secure_root_size = strlen(ros_secure_root_env);
- if (!ros_secure_root_size) {
- return NULL; // environment variable was empty
- } else {
- ros_secure_node_override = false;
- }
- }
- char * node_secure_root = NULL;
- if (ros_secure_node_override) {
- node_secure_root =
- (char *)allocator->allocate(ros_secure_root_size + 1, allocator->state);
- memcpy(node_secure_root, ros_secure_root_env, ros_secure_root_size + 1);
- // TODO(ros2team): This make an assumption on the value and length of the root namespace.
- // This should likely come from another (rcl/rmw?) function for reuse.
- // If the namespace is the root namespace ("/"), the secure root is just the node name.
- } else if (strlen(node_namespace) == 1) {
- node_secure_root = rcutils_join_path(ros_secure_root_env, node_name, *allocator);
- } else {
- char * node_fqn = NULL;
- char * node_root_path = NULL;
- // Combine node namespace with node name
- // TODO(ros2team): remove the hard-coded value of the root namespace.
- node_fqn = rcutils_format_string(*allocator, "%s%s%s", node_namespace, "/", node_name);
- // Get native path, ignore the leading forward slash.
- // TODO(ros2team): remove the hard-coded length, use the length of the root namespace instead.
- node_root_path = rcutils_to_native_path(node_fqn + 1, *allocator);
- node_secure_root = rcutils_join_path(ros_secure_root_env, node_root_path, *allocator);
- allocator->deallocate(node_fqn, allocator->state);
- allocator->deallocate(node_root_path, allocator->state);
- }
- // Check node_secure_root is not NULL before checking directory
- if (NULL == node_secure_root) {
- allocator->deallocate(node_secure_root, allocator->state);
- return NULL;
- } else if (!rcutils_is_directory(node_secure_root)) {
- allocator->deallocate(node_secure_root, allocator->state);
- return NULL;
- }
- return node_secure_root;
-}
-
rcl_node_t
rcl_get_zero_initialized_node()
{
@@ -385,15 +305,10 @@ rcl_node_init(
// File discovery magic here
const char * node_secure_root = rcl_get_secure_root(name, local_namespace_, allocator);
if (node_secure_root) {
+ RCUTILS_LOG_INFO_NAMED(ROS_PACKAGE_NAME, "Found security directory: %s", node_secure_root);
node_security_options.security_root_path = node_secure_root;
} else {
if (RMW_SECURITY_ENFORCEMENT_ENFORCE == node_security_options.enforce_security) {
- RCL_SET_ERROR_MSG(
- "SECURITY ERROR: unable to find a folder matching the node name in the "
- RCUTILS_STRINGIFY(ROS_SECURITY_NODE_DIRECTORY_VAR_NAME)
- " or "
- RCUTILS_STRINGIFY(ROS_SECURITY_ROOT_DIRECTORY_VAR_NAME)
- " directories while the requested security strategy requires it");
ret = RCL_RET_ERROR;
goto cleanup;
}
diff --git a/rcl/src/rcl/security_directory.c b/rcl/src/rcl/security_directory.c
new file mode 100644
index 000000000..a30baac10
--- /dev/null
+++ b/rcl/src/rcl/security_directory.c
@@ -0,0 +1,249 @@
+// Copyright 2018 Open Source Robotics Foundation, Inc.
+//
+// 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.
+
+#include "rcl/security_directory.h"
+#include "tinydir/tinydir.h"
+#include "rcutils/filesystem.h"
+#include "rcutils/get_env.h"
+#include "rcutils/format_string.h"
+#include "rcl/error_handling.h"
+
+/**
+ * A security lookup function takes in the node's name, namespace, a security root directory and an allocator;
+ * It returns the relevant information required to load the security credentials,
+ * which is currently a path to a directory on the filesystem containing DDS Security permission files.
+ */
+typedef char * (* security_lookup_fn_t) (
+ const char * node_name,
+ const char * node_namespace,
+ const char * ros_secure_root_env,
+ const rcl_allocator_t * allocator
+);
+
+char * exact_match_lookup(
+ const char * node_name,
+ const char * node_namespace,
+ const char * ros_secure_root_env,
+ const rcl_allocator_t * allocator
+);
+
+char * prefix_match_lookup(
+ const char * node_name,
+ const char * node_namespace,
+ const char * ros_secure_root_env,
+ const rcl_allocator_t * allocator
+);
+
+security_lookup_fn_t g_security_lookup_fns[] = {
+ NULL,
+ exact_match_lookup,
+ prefix_match_lookup,
+};
+
+typedef enum ros_security_lookup_type_e
+{
+ ROS_SECURITY_LOOKUP_NODE_OVERRIDE = 0,
+ ROS_SECURITY_LOOKUP_MATCH_EXACT = 1,
+ ROS_SECURITY_LOOKUP_MATCH_PREFIX = 2,
+} ros_security_lookup_type_t;
+
+char * g_security_lookup_type_strings[] = {
+ "NODE_OVERRIDE",
+ "MATCH_EXACT",
+ "MATCH_PREFIX"
+};
+
+/// Return the directory whose name most closely matches node_name (longest-prefix match),
+/// scanning under base_dir.
+/**
+ * By using a prefix match, a node named e.g. "my_node_123" will be able to load and use the
+ * directory "my_node" if no better match exists.
+ * \param[in] base_dir
+ * \param[in] node_name
+ * \param[out] matched_name must be a valid memory address allocated with at least
+ * _TINYDIR_FILENAME_MAX characters.
+ * \return true if a match was found
+ */
+static bool get_best_matching_directory(
+ const char * base_dir,
+ const char * node_name,
+ char * matched_name)
+{
+ size_t max_match_length = 0;
+ tinydir_dir dir;
+ if (NULL == base_dir || NULL == node_name || NULL == matched_name) {
+ return false;
+ }
+ if (-1 == tinydir_open(&dir, base_dir)) {
+ return false;
+ }
+ while (dir.has_next) {
+ tinydir_file file;
+ if (-1 == tinydir_readfile(&dir, &file)) {
+ goto cleanup;
+ }
+ if (file.is_dir) {
+ size_t matched_name_length = strnlen(file.name, sizeof(file.name) - 1);
+ if (0 ==
+ strncmp(file.name, node_name,
+ matched_name_length) && matched_name_length > max_match_length)
+ {
+ max_match_length = matched_name_length;
+ memcpy(matched_name, file.name, max_match_length);
+ }
+ }
+ if (-1 == tinydir_next(&dir)) {
+ goto cleanup;
+ }
+ }
+cleanup:
+ tinydir_close(&dir);
+ return max_match_length > 0;
+}
+
+char * exact_match_lookup(
+ const char * node_name,
+ const char * node_namespace,
+ const char * ros_secure_root_env,
+ const rcl_allocator_t * allocator)
+{
+ // Perform an exact match for the node's name in directory /.
+ char * node_secure_root = NULL;
+ // "/" case when root namespace is explicitly passed in
+ if (1 == strlen(node_namespace)) {
+ node_secure_root = rcutils_join_path(ros_secure_root_env, node_name, *allocator);
+ } else {
+ char * node_fqn = NULL;
+ char * node_root_path = NULL;
+ // Combine node namespace with node name
+ // TODO(ros2team): remove the hard-coded value of the root namespace
+ node_fqn = rcutils_format_string(*allocator, "%s%s%s", node_namespace, "/", node_name);
+ // Get native path, ignore the leading forward slash
+ // TODO(ros2team): remove the hard-coded length, use the length of the root namespace instead
+ node_root_path = rcutils_to_native_path(node_fqn + 1, *allocator);
+ node_secure_root = rcutils_join_path(ros_secure_root_env, node_root_path, *allocator);
+ allocator->deallocate(node_fqn, allocator->state);
+ allocator->deallocate(node_root_path, allocator->state);
+ }
+ return node_secure_root;
+}
+
+char * prefix_match_lookup(
+ const char * node_name,
+ const char * node_namespace,
+ const char * ros_secure_root_env,
+ const rcl_allocator_t * allocator)
+{
+ // Perform longest prefix match for the node's name in directory /.
+ char * node_secure_root = NULL;
+ char matched_dir[_TINYDIR_FILENAME_MAX] = {0};
+ char * base_lookup_dir = NULL;
+ if (strlen(node_namespace) == 1) {
+ base_lookup_dir = (char *) ros_secure_root_env;
+ } else {
+ // TODO(ros2team): remove the hard-coded length, use the length of the root namespace instead.
+ base_lookup_dir = rcutils_join_path(ros_secure_root_env, node_namespace + 1, *allocator);
+ }
+ if (get_best_matching_directory(base_lookup_dir, node_name, matched_dir)) {
+ node_secure_root = rcutils_join_path(base_lookup_dir, matched_dir, *allocator);
+ }
+ if (base_lookup_dir != ros_secure_root_env && NULL != base_lookup_dir) {
+ allocator->deallocate(base_lookup_dir, allocator->state);
+ }
+ return node_secure_root;
+}
+
+const char * rcl_get_secure_root(
+ const char * node_name,
+ const char * node_namespace,
+ const rcl_allocator_t * allocator)
+{
+ bool ros_secure_node_override = true;
+
+ // find out if either of the configuration environment variables are set
+ const char * env_buf = NULL;
+ if (NULL == node_name) {
+ return NULL;
+ }
+ if (rcutils_get_env(ROS_SECURITY_NODE_DIRECTORY_VAR_NAME, &env_buf)) {
+ return NULL;
+ }
+ if (!env_buf) {
+ return NULL;
+ }
+ size_t ros_secure_root_size = strlen(env_buf);
+ if (!ros_secure_root_size) {
+ // check root directory if node directory environment variable is empty
+ if (rcutils_get_env(ROS_SECURITY_ROOT_DIRECTORY_VAR_NAME, &env_buf)) {
+ return NULL;
+ }
+ if (!env_buf) {
+ return NULL;
+ }
+ ros_secure_root_size = strlen(env_buf);
+ if (!ros_secure_root_size) {
+ return NULL; // environment variable was empty
+ } else {
+ ros_secure_node_override = false;
+ }
+ }
+
+ // found a usable environment variable, copy into our memory before overwriting with next lookup
+ char * ros_secure_root_env =
+ (char *)allocator->allocate(ros_secure_root_size + 1, allocator->state);
+ memcpy(ros_secure_root_env, env_buf, ros_secure_root_size + 1);
+ // TODO(ros2team): This make an assumption on the value and length of the root namespace.
+ // This should likely come from another (rcl/rmw?) function for reuse.
+ // If the namespace is the root namespace ("/"), the secure root is just the node name.
+
+ char * lookup_strategy = NULL;
+ char * node_secure_root = NULL;
+ if (ros_secure_node_override) {
+ node_secure_root = ros_secure_root_env;
+ lookup_strategy = g_security_lookup_type_strings[ROS_SECURITY_LOOKUP_NODE_OVERRIDE];
+
+ } else {
+ // Check which lookup method to use and invoke the relevant function.
+ const char * ros_security_lookup_type = NULL;
+ if (rcutils_get_env(ROS_SECURITY_LOOKUP_TYPE_VAR_NAME, &ros_security_lookup_type)) {
+ return NULL;
+ }
+ if (0 == strcmp(ros_security_lookup_type,
+ g_security_lookup_type_strings[ROS_SECURITY_LOOKUP_MATCH_PREFIX]))
+ {
+ node_secure_root = g_security_lookup_fns[ROS_SECURITY_LOOKUP_MATCH_PREFIX]
+ (node_name, node_namespace, ros_secure_root_env, allocator);
+ lookup_strategy = g_security_lookup_type_strings[ROS_SECURITY_LOOKUP_MATCH_PREFIX];
+ } else { /* Default is MATCH_EXACT */
+ node_secure_root = g_security_lookup_fns[ROS_SECURITY_LOOKUP_MATCH_EXACT]
+ (node_name, node_namespace, ros_secure_root_env, allocator);
+ lookup_strategy = g_security_lookup_type_strings[ROS_SECURITY_LOOKUP_MATCH_EXACT];
+ }
+ }
+ // Check node_secure_root is not NULL before checking directory
+ if (NULL == node_secure_root) {
+ RCL_SET_ERROR_MSG_WITH_FORMAT_STRING(
+ "SECURITY ERROR: unable to find a folder matching the node name in %s%s."
+ "Lookup strategy: %s",
+ ros_secure_root_env, node_namespace, lookup_strategy);
+ } else if (!rcutils_is_directory(node_secure_root)) {
+ RCL_SET_ERROR_MSG_WITH_FORMAT_STRING(
+ "SECURITY ERROR: directory %s does not exist. Lookup strategy: %s",
+ node_secure_root, lookup_strategy);
+ allocator->deallocate(node_secure_root, allocator->state);
+ } else {
+ return node_secure_root;
+ }
+ return NULL;
+}
diff --git a/rcl/test/CMakeLists.txt b/rcl/test/CMakeLists.txt
index 5eed488d0..6e17bd7ce 100644
--- a/rcl/test/CMakeLists.txt
+++ b/rcl/test/CMakeLists.txt
@@ -15,6 +15,8 @@ include(cmake/rcl_add_custom_gtest.cmake)
include(cmake/rcl_add_custom_launch_test.cmake)
set(extra_lib_dirs "${rcl_lib_dir}")
+set(test_resources_dir_name "resources")
+add_definitions(-DTEST_RESOURCES_DIRECTORY="${CMAKE_CURRENT_BINARY_DIR}/${test_resources_dir_name}")
# finding gtest once in the highest scope
# prevents finding it repeatedly in each local scope
@@ -298,3 +300,13 @@ rcl_add_custom_gtest(test_timer${target_suffix}
APPEND_LIBRARY_DIRS ${extra_lib_dirs}
LIBRARIES ${PROJECT_NAME}
)
+
+rcl_add_custom_gtest(test_security_directory
+ SRCS rcl/test_security_directory.cpp
+ APPEND_LIBRARY_DIRS ${extra_lib_dirs}
+ LIBRARIES ${PROJECT_NAME}
+)
+
+# Install test resources
+install(DIRECTORY ${test_resources_dir_name}
+ DESTINATION ${CMAKE_CURRENT_BINARY_DIR})
\ No newline at end of file
diff --git a/rcl/test/rcl/test_security_directory.cpp b/rcl/test/rcl/test_security_directory.cpp
new file mode 100644
index 000000000..3b054a317
--- /dev/null
+++ b/rcl/test/rcl/test_security_directory.cpp
@@ -0,0 +1,164 @@
+// Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+//
+// 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.
+
+#include
+
+#include
+#include "rcl/security_directory.h"
+#include "rcutils/filesystem.h"
+
+#define ROOT_NAMESPACE "/"
+#define TEST_SECURITY_DIRECTORY_RESOURCES_DIR_NAME "test_security_directory"
+#define TEST_NODE_NAME "dummy_node"
+#define TEST_NODE_NAMESPACE ROOT_NAMESPACE TEST_SECURITY_DIRECTORY_RESOURCES_DIR_NAME
+
+char g_envstring[512] = {0};
+
+static int putenv_wrapper(const char * env_var)
+{
+#ifdef _WIN32
+ return _putenv(env_var);
+#else
+ return putenv(reinterpret_cast(const_cast(env_var)));
+#endif
+}
+
+static int unsetenv_wrapper(const char * var_name)
+{
+#ifdef _WIN32
+ // On windows, putenv("VAR=") deletes VAR from environment
+ std::string var(var_name);
+ var += "=";
+ return _putenv(var.c_str());
+#else
+ return unsetenv(var_name);
+#endif
+}
+
+class TestGetSecureRoot : public ::testing::Test
+{
+protected:
+ void SetUp() override
+ {
+ // Always make sure the variable we set is unset at the beginning of a test
+ unsetenv_wrapper(ROS_SECURITY_ROOT_DIRECTORY_VAR_NAME);
+ unsetenv_wrapper(ROS_SECURITY_NODE_DIRECTORY_VAR_NAME);
+ unsetenv_wrapper(ROS_SECURITY_LOOKUP_TYPE_VAR_NAME);
+ }
+};
+
+TEST_F(TestGetSecureRoot, failureScenarios) {
+ rcl_allocator_t allocator = rcl_get_default_allocator();
+ ASSERT_EQ(rcl_get_secure_root(TEST_NODE_NAME, TEST_NODE_NAMESPACE, &allocator),
+ (char *) NULL);
+
+ putenv_wrapper(ROS_SECURITY_ROOT_DIRECTORY_VAR_NAME "=" TEST_RESOURCES_DIRECTORY);
+
+ /* Security directory is set, but there's no matching directory */
+ /// Wrong namespace
+ ASSERT_EQ(rcl_get_secure_root(TEST_NODE_NAME, "/some_other_namespace", &allocator),
+ (char *) NULL);
+ /// Wrong node name
+ ASSERT_EQ(rcl_get_secure_root("not_" TEST_NODE_NAME, TEST_NODE_NAMESPACE, &allocator),
+ (char *) NULL);
+}
+
+TEST_F(TestGetSecureRoot, successScenarios) {
+ rcl_allocator_t allocator = rcl_get_default_allocator();
+ putenv_wrapper(ROS_SECURITY_ROOT_DIRECTORY_VAR_NAME "=" TEST_RESOURCES_DIRECTORY);
+ /* --------------------------
+ * Namespace : Custom (local)
+ * Match type : Exact
+ * --------------------------
+ * Root: ${CMAKE_BINARY_DIR}/tests/resources
+ * Namespace: /test_security_directory
+ * Node: dummy_node
+ */
+ std::string secure_root = rcl_get_secure_root(TEST_NODE_NAME, TEST_NODE_NAMESPACE, &allocator);
+ ASSERT_STREQ(TEST_NODE_NAME,
+ secure_root.substr(secure_root.size() - (sizeof(TEST_NODE_NAME) - 1)).c_str());
+
+ /* --------------------------
+ * Namespace : Custom (local)
+ * Match type : Prefix
+ * --------------------------
+ * Root: ${CMAKE_BINARY_DIR}/tests/resources
+ * Namespace: /test_security_directory
+ * Node: dummy_node_and_some_suffix_added */
+ ASSERT_STRNE(rcl_get_secure_root(TEST_NODE_NAME "_and_some_suffix_added", TEST_NODE_NAMESPACE,
+ &allocator),
+ secure_root.c_str());
+ putenv_wrapper(ROS_SECURITY_LOOKUP_TYPE_VAR_NAME "=MATCH_PREFIX");
+ ASSERT_STREQ(rcl_get_secure_root(TEST_NODE_NAME "_and_some_suffix_added", TEST_NODE_NAMESPACE,
+ &allocator),
+ secure_root.c_str());
+
+ /* Include the namespace as part of the root security directory and test root namespace */
+ char * base_lookup_dir_fqn = rcutils_join_path(TEST_RESOURCES_DIRECTORY,
+ TEST_SECURITY_DIRECTORY_RESOURCES_DIR_NAME, allocator);
+ std::string putenv_input = ROS_SECURITY_ROOT_DIRECTORY_VAR_NAME "=";
+ putenv_input += base_lookup_dir_fqn;
+ memcpy(g_envstring, putenv_input.c_str(), sizeof(g_envstring) - 1);
+ putenv_wrapper(g_envstring);
+ /* --------------------------
+ * Namespace : Root
+ * Match type : Exact
+ * --------------------------
+ * Root: ${CMAKE_BINARY_DIR}/tests/resources/test_security_directory
+ * Namespace: /
+ * Node: dummy_node */
+ ASSERT_STREQ(rcl_get_secure_root(TEST_NODE_NAME, ROOT_NAMESPACE,
+ &allocator),
+ secure_root.c_str());
+ putenv_wrapper(ROS_SECURITY_LOOKUP_TYPE_VAR_NAME "=MATCH_EXACT");
+ ASSERT_STREQ(rcl_get_secure_root(TEST_NODE_NAME, ROOT_NAMESPACE,
+ &allocator),
+ secure_root.c_str());
+
+ /* --------------------------
+ * Namespace : Root
+ * Match type : Prefix
+ * --------------------------
+ * Root dir: ${CMAKE_BINARY_DIR}/tests/resources/test_security_directory
+ * Namespace: /
+ * Node: dummy_node_and_some_suffix_added */
+ ASSERT_STRNE(rcl_get_secure_root(TEST_NODE_NAME "_and_some_suffix_added", ROOT_NAMESPACE,
+ &allocator),
+ secure_root.c_str());
+ putenv_wrapper(ROS_SECURITY_LOOKUP_TYPE_VAR_NAME "=MATCH_PREFIX");
+ ASSERT_STREQ(rcl_get_secure_root(TEST_NODE_NAME "_and_some_suffix_added", ROOT_NAMESPACE,
+ &allocator),
+ secure_root.c_str());
+}
+
+TEST_F(TestGetSecureRoot, nodeSecurityDirectoryOverride) {
+ rcl_allocator_t allocator = rcl_get_default_allocator();
+ /* Specify a valid directory */
+ putenv_wrapper(ROS_SECURITY_NODE_DIRECTORY_VAR_NAME "=" TEST_RESOURCES_DIRECTORY);
+ ASSERT_STREQ(rcl_get_secure_root("name shouldn't matter", "namespace shouldn't matter",
+ &allocator), TEST_RESOURCES_DIRECTORY);
+
+ /* Setting root dir has no effect */
+ putenv_wrapper(ROS_SECURITY_ROOT_DIRECTORY_VAR_NAME "=" TEST_RESOURCES_DIRECTORY);
+ ASSERT_STREQ(rcl_get_secure_root("name shouldn't matter", "namespace shouldn't matter",
+ &allocator), TEST_RESOURCES_DIRECTORY);
+
+ /* The override provided should exist. Providing correct node/namespace/root dir won't help
+ * if the node override is invalid. */
+ putenv_wrapper(
+ ROS_SECURITY_NODE_DIRECTORY_VAR_NAME
+ "=TheresN_oWayThi_sDirectory_Exists_hence_this_would_fail");
+ ASSERT_EQ(rcl_get_secure_root(TEST_NODE_NAME, TEST_NODE_NAMESPACE, &allocator),
+ (char *) NULL);
+}
diff --git a/rcl/test/resources/test_security_directory/dummy_node/.gitkeep b/rcl/test/resources/test_security_directory/dummy_node/.gitkeep
new file mode 100644
index 000000000..e69de29bb