diff --git a/rcl/include/rcl/security.h b/rcl/include/rcl/security.h index 56328bd5fd..2de77dc494 100644 --- a/rcl/include/rcl/security.h +++ b/rcl/include/rcl/security.h @@ -69,30 +69,33 @@ RCL_PUBLIC rcl_ret_t rcl_get_enforcement_policy(rmw_security_enforcement_policy_t * policy); -/// Return the secure root directory associated with a node given its validated name and namespace. +/// Return the secure root given a 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 + * The returned security directory is associated with the node or context depending on the + * rmw implementation. + * + * E.g. for a node/context 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/context 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. + * variable (only in rmw implementations that associate a security directory with a node), + * 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] name validated node name (a single token) + * \param[in] 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 - * returned pointer must be deallocated by the caller of this function + * \returns Machine specific (absolute) node secure root path or NULL on failure. + * Returned pointer must be deallocated by the caller of this function */ RCL_PUBLIC char * rcl_get_secure_root( - const char * node_name, - const char * node_namespace, - const rcl_allocator_t * allocator -); + const char * name, + const char * namespace_, + const rcl_allocator_t * allocator); #ifdef __cplusplus } diff --git a/rcl/src/rcl/init.c b/rcl/src/rcl/init.c index eab0558013..a3a778b139 100644 --- a/rcl/src/rcl/init.c +++ b/rcl/src/rcl/init.c @@ -32,6 +32,7 @@ extern "C" #include "rcutils/logging_macros.h" #include "rcutils/stdatomic_helper.h" #include "rmw/error_handling.h" +#include "rmw/security.h" #include "tracetools/tracetools.h" static atomic_uint_least64_t __rcl_next_unique_id = ATOMIC_VAR_INIT(1); @@ -159,25 +160,44 @@ rcl_init( } // TODO(ivanpauno): Uncomment this when new path discovery magic is ready. - // bool use_security = false; - // ret = rcl_use_security(&use_security); - // if (RCL_RET_OK != ret) { - // fail_ret = ret; - // goto fail; - // } - // RCUTILS_LOG_DEBUG_NAMED( - // ROS_PACKAGE_NAME, "Using security: %s", use_security ? "true" : "false"); - - // ret = rcl_get_enforcement_policy( - // &context->impl->init_options.impl->rmw_init_options.security_options.enforce_security); - // if (RCL_RET_OK != ret) { - // fail_ret = ret; - // goto fail; - // } - - // if (use_security) { - // ... - // } + bool use_security = false; + ret = rcl_use_security(&use_security); + if (RCL_RET_OK != ret) { + fail_ret = ret; + goto fail; + } + RCUTILS_LOG_DEBUG_NAMED( + ROS_PACKAGE_NAME, "Using security: %s", use_security ? "true" : "false"); + + if (!rmw_use_node_name_in_security_directory_lookup()) { + rmw_security_options_t * security_options = + &context->impl->init_options.impl->rmw_init_options.security_options; + + ret = rcl_get_enforcement_policy(&security_options->enforce_security); + if (RCL_RET_OK != ret) { + fail_ret = ret; + goto fail; + } + + if (!use_security) { + security_options->enforce_security = RMW_SECURITY_ENFORCEMENT_PERMISSIVE; + } else { // if use_security + // File discovery magic here + char * secure_root = rcl_get_secure_root( + options->impl->rmw_init_options.name, + options->impl->rmw_init_options.namespace_, + &options->impl->allocator); + if (secure_root) { + RCUTILS_LOG_INFO_NAMED(ROS_PACKAGE_NAME, "Found security directory: %s", secure_root); + security_options->security_root_path = secure_root; + } else { + if (RMW_SECURITY_ENFORCEMENT_ENFORCE == security_options->enforce_security) { + ret = RCL_RET_ERROR; + goto fail; + } + } + } + } // Initialize rmw_init. rmw_ret_t rmw_ret = rmw_init( diff --git a/rcl/src/rcl/node.c b/rcl/src/rcl/node.c index 28d982f2c6..5c13ce315c 100644 --- a/rcl/src/rcl/node.c +++ b/rcl/src/rcl/node.c @@ -47,6 +47,7 @@ extern "C" #include "rmw/error_handling.h" #include "rmw/security_options.h" #include "rmw/rmw.h" +#include "rmw/security.h" #include "rmw/validate_namespace.h" #include "rmw/validate_node_name.h" #include "tracetools/tracetools.h" @@ -264,34 +265,37 @@ rcl_node_init( rmw_security_options_t node_security_options = rmw_get_zero_initialized_security_options(); + rmw_security_options_t * node_security_options_ptr = NULL; + if (rmw_use_node_name_in_security_directory_lookup()) { + node_security_options_ptr = &node_security_options; + bool use_security; + ret = rcl_use_security(&use_security); + if (RCL_RET_OK != ret) { + fail_ret = ret; + goto fail; + } + RCUTILS_LOG_DEBUG_NAMED( + ROS_PACKAGE_NAME, "Using security: %s", use_security ? "true" : "false"); - bool use_security; - ret = rcl_use_security(&use_security); - if (RCL_RET_OK != ret) { - fail_ret = ret; - goto fail; - } - RCUTILS_LOG_DEBUG_NAMED( - ROS_PACKAGE_NAME, "Using security: %s", use_security ? "true" : "false"); - - ret = rcl_get_enforcement_policy(&node_security_options.enforce_security); - if (RCL_RET_OK != ret) { - fail_ret = ret; - goto fail; - } + ret = rcl_get_enforcement_policy(&node_security_options.enforce_security); + if (RCL_RET_OK != ret) { + fail_ret = ret; + goto fail; + } - if (!use_security) { - node_security_options.enforce_security = RMW_SECURITY_ENFORCEMENT_PERMISSIVE; - } else { // if use_security - // File discovery magic here - 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) { - ret = RCL_RET_ERROR; - goto cleanup; + if (!use_security) { + node_security_options.enforce_security = RMW_SECURITY_ENFORCEMENT_PERMISSIVE; + } else { // if use_security + // File discovery magic here + 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) { + ret = RCL_RET_ERROR; + goto cleanup; + } } } } @@ -302,7 +306,7 @@ rcl_node_init( node->impl->rmw_node_handle = rmw_create_node( &(node->context->impl->rmw_context), - name, local_namespace_, domain_id, &node_security_options, localhost_only); + name, local_namespace_, domain_id, node_security_options_ptr, localhost_only); RCL_CHECK_FOR_NULL_WITH_MSG( node->impl->rmw_node_handle, rmw_get_error_string().str, goto fail); diff --git a/rcl/src/rcl/security.c b/rcl/src/rcl/security.c index 43562a8968..0854a86e04 100644 --- a/rcl/src/rcl/security.c +++ b/rcl/src/rcl/security.c @@ -17,9 +17,13 @@ #include #include "rcl/error_handling.h" + #include "rcutils/filesystem.h" -#include "rcutils/get_env.h" #include "rcutils/format_string.h" +#include "rcutils/get_env.h" +#include "rcutils/strdup.h" + +#include "rmw/security.h" #include "rmw/security_options.h" #ifdef __clang__ @@ -227,20 +231,22 @@ char * rcl_get_secure_root( const rcl_allocator_t * allocator) { bool ros_secure_node_override = true; + bool use_node_name_in_lookup = rmw_use_node_name_in_security_directory_lookup(); // 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; + if (use_node_name_in_lookup) { + 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) { + if (!use_node_name_in_lookup || 0 == strcmp("", env_buf)) { // check root directory if node directory environment variable is empty if (rcutils_get_env(ROS_SECURITY_ROOT_DIRECTORY_VAR_NAME, &env_buf)) { return NULL; @@ -248,8 +254,7 @@ char * rcl_get_secure_root( if (!env_buf) { return NULL; } - ros_secure_root_size = strlen(env_buf); - if (!ros_secure_root_size) { + if (0 == strcmp("", env_buf)) { return NULL; // environment variable was empty } else { ros_secure_node_override = false; @@ -257,20 +262,13 @@ char * rcl_get_secure_root( } // 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 * ros_secure_root_env = rcutils_strdup(env_buf, *allocator); char * lookup_strategy = NULL; 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); + node_secure_root = rcutils_strdup(ros_secure_root_env, *allocator); 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; diff --git a/rcl/src/rcl/security_directory.c b/rcl/src/rcl/security_directory.c index b4e0fdb6a4..6fef4c6933 100644 --- a/rcl/src/rcl/security_directory.c +++ b/rcl/src/rcl/security_directory.c @@ -34,22 +34,22 @@ * 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 * name, + const char * 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 * name, + const char * 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 * name, + const char * namespace_, const char * ros_secure_root_env, const rcl_allocator_t * allocator ); @@ -73,25 +73,25 @@ char * g_security_lookup_type_strings[] = { "MATCH_PREFIX" }; -/// Return the directory whose name most closely matches node_name (longest-prefix match), +/// Return the directory whose name most closely matches 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[in] 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, + const char * name, char * matched_name) { size_t max_match_length = 0; tinydir_dir dir; - if (NULL == base_dir || NULL == node_name || NULL == matched_name) { + if (NULL == base_dir || NULL == name || NULL == matched_name) { return false; } if (-1 == tinydir_open(&dir, base_dir)) { @@ -105,7 +105,7 @@ static bool get_best_matching_directory( if (file.is_dir) { size_t matched_name_length = strnlen(file.name, sizeof(file.name) - 1); if (0 == - strncmp(file.name, node_name, + strncmp(file.name, name, matched_name_length) && matched_name_length > max_match_length) { max_match_length = matched_name_length; @@ -122,22 +122,22 @@ static bool get_best_matching_directory( } char * exact_match_lookup( - const char * node_name, - const char * node_namespace, + const char * name, + const char * 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); + if (1 == strlen(namespace_)) { + node_secure_root = rcutils_join_path(ros_secure_root_env, 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); + node_fqn = rcutils_format_string(*allocator, "%s%s%s", namespace_, "/", 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); @@ -149,8 +149,8 @@ char * exact_match_lookup( } char * prefix_match_lookup( - const char * node_name, - const char * node_namespace, + const char * name, + const char * namespace_, const char * ros_secure_root_env, const rcl_allocator_t * allocator) { @@ -158,13 +158,13 @@ char * prefix_match_lookup( char * node_secure_root = NULL; char matched_dir[_TINYDIR_FILENAME_MAX] = {0}; char * base_lookup_dir = NULL; - if (strlen(node_namespace) == 1) { + if (strlen(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); + base_lookup_dir = rcutils_join_path(ros_secure_root_env, namespace_ + 1, *allocator); } - if (get_best_matching_directory(base_lookup_dir, node_name, matched_dir)) { + if (get_best_matching_directory(base_lookup_dir, 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) { @@ -174,15 +174,15 @@ char * prefix_match_lookup( } char * rcl_get_secure_root( - const char * node_name, - const char * node_namespace, + const char * name, + const char * 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) { + if (NULL == name) { return NULL; } if (rcutils_get_env(ROS_SECURITY_NODE_DIRECTORY_VAR_NAME, &env_buf)) { @@ -234,11 +234,11 @@ char * rcl_get_secure_root( 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); + (name, 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); + (name, namespace_, ros_secure_root_env, allocator); lookup_strategy = g_security_lookup_type_strings[ROS_SECURITY_LOOKUP_MATCH_EXACT]; } } @@ -249,7 +249,7 @@ char * rcl_get_secure_root( RCL_SET_ERROR_MSG_WITH_FORMAT_STRING( "SECURITY ERROR: unable to find a folder matching the node name '%s' in '%s%s'. " "Lookup strategy: %s", - node_name, ros_secure_root_env, node_namespace, lookup_strategy); + name, ros_secure_root_env, namespace_, lookup_strategy); } else { RCL_SET_ERROR_MSG_WITH_FORMAT_STRING( "SECURITY ERROR: directory '%s' does not exist. Lookup strategy: %s", diff --git a/rcl/test/rcl/test_security_directory.cpp b/rcl/test/rcl/test_security_directory.cpp index 5044e7f4d0..29695eb3d5 100644 --- a/rcl/test/rcl/test_security_directory.cpp +++ b/rcl/test/rcl/test_security_directory.cpp @@ -14,12 +14,18 @@ #include -#include #include +#include + #include "rcl/security.h" +#include "rcl/error_handling.h" + #include "rcutils/filesystem.h" + +#include "rmw/security.h" + #include "osrf_testing_tools_cpp/scope_exit.hpp" -#include "rcl/error_handling.h" + #define ROOT_NAMESPACE "/" #define TEST_SECURITY_DIRECTORY_RESOURCES_DIR_NAME "test_security_directory" @@ -101,6 +107,7 @@ class TestGetSecureRoot : public ::testing::Test TEST_F(TestGetSecureRoot, failureScenarios) { ASSERT_EQ(rcl_get_secure_root(TEST_NODE_NAME, TEST_NODE_NAMESPACE, &allocator), (char *) NULL); + rcl_reset_error(); putenv_wrapper(ROS_SECURITY_ROOT_DIRECTORY_VAR_NAME "=" TEST_RESOURCES_DIRECTORY); @@ -108,9 +115,11 @@ TEST_F(TestGetSecureRoot, failureScenarios) { /// Wrong namespace ASSERT_EQ(rcl_get_secure_root(TEST_NODE_NAME, "/some_other_namespace", &allocator), (char *) NULL); + rcl_reset_error(); /// Wrong node name ASSERT_EQ(rcl_get_secure_root("not_" TEST_NODE_NAME, TEST_NODE_NAMESPACE, &allocator), (char *) NULL); + rcl_reset_error(); } TEST_F(TestGetSecureRoot, successScenarios_local_exactMatch) { @@ -188,29 +197,35 @@ TEST_F(TestGetSecureRoot, successScenarios_root_prefixMatch) { } TEST_F(TestGetSecureRoot, nodeSecurityDirectoryOverride_validDirectory) { - /* Specify a valid directory */ - putenv_wrapper(ROS_SECURITY_NODE_DIRECTORY_VAR_NAME "=" TEST_RESOURCES_DIRECTORY); - root_path = rcl_get_secure_root("name shouldn't matter", - "namespace shouldn't matter", &allocator); - ASSERT_STREQ(root_path, TEST_RESOURCES_DIRECTORY); + if (rmw_use_node_name_in_security_directory_lookup()) { + /* Specify a valid directory */ + putenv_wrapper(ROS_SECURITY_NODE_DIRECTORY_VAR_NAME "=" TEST_RESOURCES_DIRECTORY); + root_path = rcl_get_secure_root("name shouldn't matter", + "namespace shouldn't matter", &allocator); + ASSERT_STREQ(root_path, TEST_RESOURCES_DIRECTORY); + } } TEST_F(TestGetSecureRoot, nodeSecurityDirectoryOverride_validDirectory_overrideRootDirectoryAttempt) { - /* Setting root dir has no effect */ - putenv_wrapper(ROS_SECURITY_NODE_DIRECTORY_VAR_NAME "=" TEST_RESOURCES_DIRECTORY); - root_path = rcl_get_secure_root("name shouldn't matter", - "namespace shouldn't matter", &allocator); - putenv_wrapper(ROS_SECURITY_ROOT_DIRECTORY_VAR_NAME "=" TEST_RESOURCES_DIRECTORY); - ASSERT_STREQ(root_path, TEST_RESOURCES_DIRECTORY); + if (rmw_use_node_name_in_security_directory_lookup()) { + /* Setting root dir has no effect */ + putenv_wrapper(ROS_SECURITY_NODE_DIRECTORY_VAR_NAME "=" TEST_RESOURCES_DIRECTORY); + root_path = rcl_get_secure_root("name shouldn't matter", + "namespace shouldn't matter", &allocator); + putenv_wrapper(ROS_SECURITY_ROOT_DIRECTORY_VAR_NAME "=" TEST_RESOURCES_DIRECTORY); + ASSERT_STREQ(root_path, TEST_RESOURCES_DIRECTORY); + } } TEST_F(TestGetSecureRoot, nodeSecurityDirectoryOverride_invalidDirectory) { /* 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); + if (rmw_use_node_name_in_security_directory_lookup()) { + 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); + } }