Security directory lookup improvements (#332)
* Changing security directory lookup to a prefix match rather than exact match. Signed-off-by: Emerson Knapp <eknapp@amazon.com> * Changing security directory lookup to a prefix match rather than exact match. Signed-off-by: Emerson Knapp <eknapp@amazon.com> * Changing security directory lookup to a prefix match rather than exact match. Signed-off-by: Emerson Knapp <eknapp@amazon.com> * Changing security directory lookup to a prefix match rather than exact match. Signed-off-by: Emerson Knapp <eknapp@amazon.com> * Adding security_directory module and moving rcl_get_secure_root function to it. Adding tests. Signed-off-by: Emerson Knapp <eknapp@amazon.com> * Adding security_directory module and moving rcl_get_secure_root function to it. Adding tests. Signed-off-by: Emerson Knapp <eknapp@amazon.com> * Adding security_directory module and moving rcl_get_secure_root function to it. Adding tests. Signed-off-by: Emerson Knapp <eknapp@amazon.com> * Adding security_directory module and moving rcl_get_secure_root function to it. Adding tests. Signed-off-by: Emerson Knapp <eknapp@amazon.com> * Adding security_directory module and moving rcl_get_secure_root function to it. Adding tests. Signed-off-by: Emerson Knapp <eknapp@amazon.com> * Changing security directory prefix matching to be optional. Improving error messages around security directory lookup. Signed-off-by: Emerson Knapp <eknapp@amazon.com> * Fixing get_best_matching_directory so that it always fetches the next file inside the loop. Signed-off-by: Emerson Knapp <eknapp@amazon.com> * make pr ready for ros2cli security feature (#1) * update docs about possibility of rcl_take no taking (#356) * update rcl_wait doc with respect to subs and possibility of failing takes * add a note about possible failing takes in rcl_take docs * 0.6.2 * Set rmw_wait timeout using ros timers too (#357) * 0.6.3 * Avoid timer period being set to 0 (#359) * Fix logic that moves goal handles when one expires (#360) * Fix error from uncrustify v0.68 (#364) * Ensure that context instance id storage is aligned correctly (#365) * Ensure that context instance id storage is aligned correctly * Make alignment compatible with MSVC * Namespace alignment macro with RCL_ * [rcl] Guard against bad allocation calling rcl_arguments_copy() (#367) * [rcl] Add test for copying arguments struct with no arguments * Override allocate function in test to reveal bug * [rcl] Only allocate arrays if there are things to copy in rcl_argument_copy() Also guard against freeing invalid pointers if rcl_argument_copy() fails. * Remove uncessary guard against NULL pointer * linter, styles, uncrustify fixes Signed-off-by: Emerson Knapp <eknapp@amazon.com> * Update rcl/include/rcl/security_directory.h Co-Authored-By: AAlon <avishayalon@gmail.com> Signed-off-by: Emerson Knapp <eknapp@amazon.com> * Adding line break in docstring Signed-off-by: Emerson Knapp <eknapp@amazon.com> * Removing duplicate doc string Signed-off-by: Emerson Knapp <eknapp@amazon.com> * Removing tinydir from the source tree, instead using the ROS package tinydir_vendor. Signed-off-by: Emerson Knapp <eknapp@amazon.com> * Removing tinydir Signed-off-by: Emerson Knapp <eknapp@amazon.com> * Reformatting license notice as per linter template. Signed-off-by: Emerson Knapp <eknapp@amazon.com> * Update test_security_directory.cpp Signed-off-by: Emerson Knapp <eknapp@amazon.com> * Changing input to putenv to be a global, statically allocated buffer. Signed-off-by: Emerson Knapp <eknapp@amazon.com> * test_security_directory - Using a larger buffer for env string manipulations. Signed-off-by: Emerson Knapp <eknapp@amazon.com> * Copy environment variable to allocated string so it is not clobbered by next lookup Signed-off-by: Emerson Knapp <eknapp@amazon.com> * Address review comments fix security directory exact match comment and unset env vars before tests Signed-off-by: Emerson Knapp <eknapp@amazon.com> * Remove strncpy Signed-off-by: Emerson Knapp <eknapp@amazon.com>
This commit is contained in:
parent
471075197c
commit
b0a124dcf6
8 changed files with 499 additions and 88 deletions
|
@ -9,6 +9,7 @@ find_package(rcutils REQUIRED)
|
||||||
find_package(rmw REQUIRED)
|
find_package(rmw REQUIRED)
|
||||||
find_package(rmw_implementation REQUIRED)
|
find_package(rmw_implementation REQUIRED)
|
||||||
find_package(rosidl_generator_c REQUIRED)
|
find_package(rosidl_generator_c REQUIRED)
|
||||||
|
find_package(tinydir_vendor REQUIRED)
|
||||||
|
|
||||||
include_directories(include)
|
include_directories(include)
|
||||||
|
|
||||||
|
@ -54,6 +55,7 @@ set(${PROJECT_NAME}_sources
|
||||||
src/rcl/timer.c
|
src/rcl/timer.c
|
||||||
src/rcl/validate_topic_name.c
|
src/rcl/validate_topic_name.c
|
||||||
src/rcl/wait.c
|
src/rcl/wait.c
|
||||||
|
src/rcl/security_directory.c
|
||||||
)
|
)
|
||||||
|
|
||||||
add_library(${PROJECT_NAME} ${${PROJECT_NAME}_sources})
|
add_library(${PROJECT_NAME} ${${PROJECT_NAME}_sources})
|
||||||
|
@ -65,6 +67,7 @@ ament_target_dependencies(${PROJECT_NAME}
|
||||||
"rcutils"
|
"rcutils"
|
||||||
"rosidl_generator_c"
|
"rosidl_generator_c"
|
||||||
${RCL_LOGGING_IMPL}
|
${RCL_LOGGING_IMPL}
|
||||||
|
"tinydir_vendor"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Causes the visibility macros to use dllexport rather than dllimport,
|
# Causes the visibility macros to use dllexport rather than dllimport,
|
||||||
|
|
66
rcl/include/rcl/security_directory.h
Normal file
66
rcl/include/rcl/security_directory.h
Normal file
|
@ -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_
|
|
@ -16,10 +16,12 @@
|
||||||
<build_depend>rcl_interfaces</build_depend>
|
<build_depend>rcl_interfaces</build_depend>
|
||||||
<build_depend>rcutils</build_depend>
|
<build_depend>rcutils</build_depend>
|
||||||
<build_depend>rosidl_generator_c</build_depend>
|
<build_depend>rosidl_generator_c</build_depend>
|
||||||
|
<build_depend>tinydir_vendor</build_depend>
|
||||||
|
|
||||||
<build_export_depend>rcl_interfaces</build_export_depend>
|
<build_export_depend>rcl_interfaces</build_export_depend>
|
||||||
<build_export_depend>rcutils</build_export_depend>
|
<build_export_depend>rcutils</build_export_depend>
|
||||||
<build_export_depend>rosidl_generator_c</build_export_depend>
|
<build_export_depend>rosidl_generator_c</build_export_depend>
|
||||||
|
<build_export_depend>tinydir_vendor</build_export_depend>
|
||||||
|
|
||||||
<exec_depend>rcl_interfaces</exec_depend>
|
<exec_depend>rcl_interfaces</exec_depend>
|
||||||
<exec_depend>ament_cmake</exec_depend>
|
<exec_depend>ament_cmake</exec_depend>
|
||||||
|
|
|
@ -29,6 +29,7 @@ extern "C"
|
||||||
#include "rcl/logging_rosout.h"
|
#include "rcl/logging_rosout.h"
|
||||||
#include "rcl/rcl.h"
|
#include "rcl/rcl.h"
|
||||||
#include "rcl/remap.h"
|
#include "rcl/remap.h"
|
||||||
|
#include "rcl/security_directory.h"
|
||||||
#include "rcutils/filesystem.h"
|
#include "rcutils/filesystem.h"
|
||||||
#include "rcutils/find.h"
|
#include "rcutils/find.h"
|
||||||
#include "rcutils/format_string.h"
|
#include "rcutils/format_string.h"
|
||||||
|
@ -46,9 +47,8 @@ extern "C"
|
||||||
|
|
||||||
#include "./common.h"
|
#include "./common.h"
|
||||||
#include "./context_impl.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_STRATEGY_VAR_NAME "ROS_SECURITY_STRATEGY"
|
||||||
#define ROS_SECURITY_ENABLE_VAR_NAME "ROS_SECURITY_ENABLE"
|
#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 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_node_t
|
||||||
rcl_get_zero_initialized_node()
|
rcl_get_zero_initialized_node()
|
||||||
{
|
{
|
||||||
|
@ -385,15 +305,10 @@ rcl_node_init(
|
||||||
// File discovery magic here
|
// File discovery magic here
|
||||||
const char * node_secure_root = rcl_get_secure_root(name, local_namespace_, allocator);
|
const char * node_secure_root = rcl_get_secure_root(name, local_namespace_, allocator);
|
||||||
if (node_secure_root) {
|
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;
|
node_security_options.security_root_path = node_secure_root;
|
||||||
} else {
|
} else {
|
||||||
if (RMW_SECURITY_ENFORCEMENT_ENFORCE == node_security_options.enforce_security) {
|
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;
|
ret = RCL_RET_ERROR;
|
||||||
goto cleanup;
|
goto cleanup;
|
||||||
}
|
}
|
||||||
|
|
249
rcl/src/rcl/security_directory.c
Normal file
249
rcl/src/rcl/security_directory.c
Normal file
|
@ -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 <root dir>/<namespace>.
|
||||||
|
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 <root dir>/<namespace>.
|
||||||
|
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;
|
||||||
|
}
|
|
@ -15,6 +15,8 @@ include(cmake/rcl_add_custom_gtest.cmake)
|
||||||
include(cmake/rcl_add_custom_launch_test.cmake)
|
include(cmake/rcl_add_custom_launch_test.cmake)
|
||||||
|
|
||||||
set(extra_lib_dirs "${rcl_lib_dir}")
|
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
|
# finding gtest once in the highest scope
|
||||||
# prevents finding it repeatedly in each local 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}
|
APPEND_LIBRARY_DIRS ${extra_lib_dirs}
|
||||||
LIBRARIES ${PROJECT_NAME}
|
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})
|
164
rcl/test/rcl/test_security_directory.cpp
Normal file
164
rcl/test/rcl/test_security_directory.cpp
Normal file
|
@ -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 <gtest/gtest.h>
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#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<char *>(const_cast<char *>(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);
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue