From ec4be2d10d47b454a25fa158b9521b30d202301b Mon Sep 17 00:00:00 2001 From: Kyle Fazzari Date: Thu, 2 Apr 2020 15:15:29 -0700 Subject: [PATCH] Add support for security logging plugin If a `logging.xml` file is included in the security files, enable the security logging plugin in Fast RTPS using the properties contained in said file. Also reorganize existing security logic to be more testable, and add some tests for it. Signed-off-by: Kyle Fazzari --- rmw_fastrtps_shared_cpp/CMakeLists.txt | 7 + .../rmw_fastrtps_shared_cpp/rmw_security.hpp | 31 ++ rmw_fastrtps_shared_cpp/package.xml | 1 + rmw_fastrtps_shared_cpp/src/participant.cpp | 86 +--- rmw_fastrtps_shared_cpp/src/rmw_security.cpp | 344 +++++++++++++ rmw_fastrtps_shared_cpp/test/CMakeLists.txt | 6 + .../test/test_security.cpp | 485 ++++++++++++++++++ 7 files changed, 880 insertions(+), 80 deletions(-) create mode 100644 rmw_fastrtps_shared_cpp/include/rmw_fastrtps_shared_cpp/rmw_security.hpp create mode 100644 rmw_fastrtps_shared_cpp/src/rmw_security.cpp create mode 100644 rmw_fastrtps_shared_cpp/test/test_security.cpp diff --git a/rmw_fastrtps_shared_cpp/CMakeLists.txt b/rmw_fastrtps_shared_cpp/CMakeLists.txt index c3f98a0f9..19e00434e 100644 --- a/rmw_fastrtps_shared_cpp/CMakeLists.txt +++ b/rmw_fastrtps_shared_cpp/CMakeLists.txt @@ -45,6 +45,10 @@ find_package(fastrtps REQUIRED CONFIG) find_package(FastRTPS REQUIRED MODULE) find_package(rmw REQUIRED) + +find_package(tinyxml2_vendor REQUIRED) +find_package(TinyXML2 REQUIRED) # provided by tinyxml2 upstream, or tinyxml2_vendor + include_directories(include) add_library(rmw_fastrtps_shared_cpp @@ -74,6 +78,7 @@ add_library(rmw_fastrtps_shared_cpp src/rmw_publisher.cpp src/rmw_request.cpp src/rmw_response.cpp + src/rmw_security.cpp src/rmw_service.cpp src/rmw_service_names_and_types.cpp src/rmw_service_server_is_available.cpp @@ -111,6 +116,8 @@ ament_export_libraries(rmw_fastrtps_shared_cpp) ament_export_dependencies(rcpputils) ament_export_dependencies(rcutils) ament_export_dependencies(rmw) +ament_export_dependencies(tinyxml2_vendor) +ament_export_dependencies(TinyXML2) if(BUILD_TESTING) find_package(ament_lint_auto REQUIRED) diff --git a/rmw_fastrtps_shared_cpp/include/rmw_fastrtps_shared_cpp/rmw_security.hpp b/rmw_fastrtps_shared_cpp/include/rmw_fastrtps_shared_cpp/rmw_security.hpp new file mode 100644 index 000000000..4315da450 --- /dev/null +++ b/rmw_fastrtps_shared_cpp/include/rmw_fastrtps_shared_cpp/rmw_security.hpp @@ -0,0 +1,31 @@ +// Copyright 2020 Canonical Ltd. +// +// 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 RMW_FASTRTPS_SHARED_CPP__RMW_SECURITY_HPP_ +#define RMW_FASTRTPS_SHARED_CPP__RMW_SECURITY_HPP_ + +#include + +#include "rmw/security_options.h" +#include "fastrtps/rtps/attributes/PropertyPolicy.h" + +bool apply_security_options( + const rmw_security_options_t & security_options, + eprosima::fastrtps::rtps::PropertyPolicy & policy); + +bool apply_logging_configuration_from_file( + const std::string & xml_file_path, + eprosima::fastrtps::rtps::PropertyPolicy & policy); + +#endif // RMW_FASTRTPS_SHARED_CPP__RMW_SECURITY_HPP_ diff --git a/rmw_fastrtps_shared_cpp/package.xml b/rmw_fastrtps_shared_cpp/package.xml index 837d1f117..ef7205ee9 100644 --- a/rmw_fastrtps_shared_cpp/package.xml +++ b/rmw_fastrtps_shared_cpp/package.xml @@ -19,6 +19,7 @@ rcpputils rcutils rmw + tinyxml2_vendor rmw_dds_common fastcdr diff --git a/rmw_fastrtps_shared_cpp/src/participant.cpp b/rmw_fastrtps_shared_cpp/src/participant.cpp index 2f6cad6ae..33b2aa7ab 100644 --- a/rmw_fastrtps_shared_cpp/src/participant.cpp +++ b/rmw_fastrtps_shared_cpp/src/participant.cpp @@ -42,6 +42,7 @@ #include "rmw_fastrtps_shared_cpp/custom_participant_info.hpp" #include "rmw_fastrtps_shared_cpp/participant.hpp" #include "rmw_fastrtps_shared_cpp/rmw_common.hpp" +#include "rmw_fastrtps_shared_cpp/rmw_security.hpp" using Domain = eprosima::fastrtps::Domain; using IPLocator = eprosima::fastrtps::rtps::IPLocator; @@ -50,43 +51,6 @@ using Participant = eprosima::fastrtps::Participant; using ParticipantAttributes = eprosima::fastrtps::ParticipantAttributes; using StatefulReader = eprosima::fastrtps::rtps::StatefulReader; -#if HAVE_SECURITY -static -bool -get_security_file_paths( - std::array & security_files_paths, const char * secure_root) -{ - // here assume only 6 files for security - const char * file_names[6] = { - "identity_ca.cert.pem", "cert.pem", "key.pem", - "permissions_ca.cert.pem", "governance.p7s", "permissions.p7s" - }; - size_t num_files = sizeof(file_names) / sizeof(char *); - - std::string file_prefix("file://"); - - for (size_t i = 0; i < num_files; i++) { - rcutils_allocator_t allocator = rcutils_get_default_allocator(); - char * file_path = rcutils_join_path(secure_root, file_names[i], allocator); - - if (!file_path) { - return false; - } - - if (rcutils_is_readable(file_path)) { - security_files_paths[i] = file_prefix + std::string(file_path); - } else { - allocator.deallocate(file_path, allocator.state); - return false; - } - - allocator.deallocate(file_path, allocator.state); - } - - return true; -} -#endif - static CustomParticipantInfo * __create_participant( @@ -196,52 +160,14 @@ rmw_fastrtps_shared_cpp::create_participant( participantAttrs.rtps.builtin.writerHistoryMemoryPolicy = eprosima::fastrtps::rtps::PREALLOCATED_WITH_REALLOC_MEMORY_MODE; } - if (security_options->security_root_path) { - // if security_root_path provided, try to find the key and certificate files -#if HAVE_SECURITY - std::array security_files_paths; - if (get_security_file_paths(security_files_paths, security_options->security_root_path)) { - eprosima::fastrtps::rtps::PropertyPolicy property_policy; - using Property = eprosima::fastrtps::rtps::Property; - property_policy.properties().emplace_back( - Property("dds.sec.auth.plugin", "builtin.PKI-DH")); - property_policy.properties().emplace_back( - Property( - "dds.sec.auth.builtin.PKI-DH.identity_ca", security_files_paths[0])); - property_policy.properties().emplace_back( - Property( - "dds.sec.auth.builtin.PKI-DH.identity_certificate", security_files_paths[1])); - property_policy.properties().emplace_back( - Property( - "dds.sec.auth.builtin.PKI-DH.private_key", security_files_paths[2])); - property_policy.properties().emplace_back( - Property("dds.sec.crypto.plugin", "builtin.AES-GCM-GMAC")); - - property_policy.properties().emplace_back( - Property( - "dds.sec.access.plugin", "builtin.Access-Permissions")); - property_policy.properties().emplace_back( - Property( - "dds.sec.access.builtin.Access-Permissions.permissions_ca", security_files_paths[3])); - property_policy.properties().emplace_back( - Property( - "dds.sec.access.builtin.Access-Permissions.governance", security_files_paths[4])); - property_policy.properties().emplace_back( - Property( - "dds.sec.access.builtin.Access-Permissions.permissions", security_files_paths[5])); - participantAttrs.rtps.properties = property_policy; - } else if (security_options->enforce_security) { - RMW_SET_ERROR_MSG("couldn't find all security files!"); - return nullptr; - } -#else - RMW_SET_ERROR_MSG( - "This Fast-RTPS version doesn't have the security libraries\n" - "Please compile Fast-RTPS using the -DSECURITY=ON CMake option"); + eprosima::fastrtps::rtps::PropertyPolicy property_policy; + if (apply_security_options(*security_options, property_policy)) { + participantAttrs.rtps.properties = property_policy; + } else { return nullptr; -#endif } + return __create_participant( identifier, participantAttrs, diff --git a/rmw_fastrtps_shared_cpp/src/rmw_security.cpp b/rmw_fastrtps_shared_cpp/src/rmw_security.cpp new file mode 100644 index 000000000..b94e3c250 --- /dev/null +++ b/rmw_fastrtps_shared_cpp/src/rmw_security.cpp @@ -0,0 +1,344 @@ +// Copyright 2020 Canonical Ltd. +// +// 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 +#include + +#include "fastrtps/config.h" +#include "rcutils/filesystem.h" +#include "rmw/error_handling.h" +#include "rmw/qos_profiles.h" +#include "rmw/types.h" + +#include "rmw_fastrtps_shared_cpp/rmw_security.hpp" + +#if HAVE_SECURITY + +namespace +{ +// File names +const char identity_ca_cert_file_name[] = "identity_ca.cert.pem"; +const char permissions_ca_cert_file_name[] = "permissions_ca.cert.pem"; +const char governance_file_name[] = "governance.p7s"; +const char cert_file_name[] = "cert.pem"; +const char key_file_name[] = "key.pem"; +const char permissions_file_name[] = "permissions.p7s"; +const char logging_file_name[] = "logging.xml"; + +// Logging properties +const char logging_plugin_property_name[] = "dds.sec.log.plugin"; +const char log_file_property_name[] = "dds.sec.log.builtin.DDS_LogTopic.log_file"; +const char verbosity_property_name[] = "dds.sec.log.builtin.DDS_LogTopic.logging_level"; +const char distribute_enable_property_name[] = + "dds.sec.log.builtin.DDS_LogTopic.distribute"; + +const std::map supported_verbosities { + {"EMERGENCY", "EMERGENCY_LEVEL"}, + {"ALERT", "ALERT_LEVEL"}, + {"CRITICAL", "CRITICAL_LEVEL"}, + {"ERROR", "ERROR_LEVEL"}, + {"WARNING", "WARNING_LEVEL"}, + {"NOTICE", "NOTICE_LEVEL"}, + {"INFORMATIONAL", "INFORMATIONAL_LEVEL"}, + {"DEBUG", "DEBUG_LEVEL"}, +}; + +struct security_files_t +{ + std::string identity_ca_cert_path; + std::string permissions_ca_cert_path; + std::string governance_path; + std::string cert_path; + std::string key_path; + std::string permissions_path; + std::string logging_path; +}; + +bool string_to_verbosity(const std::string & str, std::string & verbosity) +{ + try { + verbosity = supported_verbosities.at(str); + } catch (std::out_of_range &) { + return false; + } + + return true; +} + +bool get_element_text( + const tinyxml2::XMLElement & element, + const std::string & tag_name, + std::string & text) +{ + const char * text_array = element.GetText(); + if (text_array == nullptr) { + RMW_SET_ERROR_MSG_WITH_FORMAT_STRING( + "failed to set security logging %s: improper format", + tag_name.c_str()); + return false; + } + + text = std::string(text_array); + return true; +} + +void add_property( + eprosima::fastrtps::rtps::PropertySeq & properties, + eprosima::fastrtps::rtps::Property && property) +{ + // Add property to vector. If property already exists, overwrite it. + std::string property_name = property.name(); + for (auto & existing_property : properties) { + if (existing_property.name() == property_name) { + existing_property = property; + return; + } + } + + properties.push_back(property); +} + +bool add_property_from_xml_element( + eprosima::fastrtps::rtps::PropertySeq & properties, const std::string & property_name, + const tinyxml2::XMLElement & element, const std::string & tag_name) +{ + auto tag = element.FirstChildElement(tag_name.c_str()); + if (tag != nullptr) { + std::string text; + if (!get_element_text(*tag, tag_name, text)) { + return false; + } + + add_property(properties, eprosima::fastrtps::rtps::Property(property_name, text)); + } + + return true; +} + +bool get_security_file_path( + const std::string & node_secure_root, + const std::string & file_name, + std::string & security_file_path) +{ + rcutils_allocator_t allocator = rcutils_get_default_allocator(); + char * file_path = rcutils_join_path(node_secure_root.c_str(), file_name.c_str(), allocator); + + if (!file_path) { + return false; + } + + bool success = false; + if (rcutils_is_readable(file_path)) { + security_file_path = std::string(file_path); + success = true; + } + + // This has been copied into security_file_path now-- safe to deallocate + allocator.deallocate(file_path, allocator.state); + + return success; +} + +std::string path_to_uri(const std::string & file_path) +{ + return std::string("file://") + file_path; +} + +bool get_security_file_paths( + const std::string & node_secure_root, security_files_t & security_files) +{ + std::string file_path; + + if (!get_security_file_path(node_secure_root, identity_ca_cert_file_name, file_path)) { + return false; + } + security_files.identity_ca_cert_path = file_path; + + if (!get_security_file_path(node_secure_root, permissions_ca_cert_file_name, file_path)) { + return false; + } + security_files.permissions_ca_cert_path = file_path; + + if (!get_security_file_path(node_secure_root, governance_file_name, file_path)) { + return false; + } + security_files.governance_path = file_path; + + if (!get_security_file_path(node_secure_root, cert_file_name, file_path)) { + return false; + } + security_files.cert_path = file_path; + + if (!get_security_file_path(node_secure_root, key_file_name, file_path)) { + return false; + } + security_files.key_path = file_path; + + if (!get_security_file_path(node_secure_root, permissions_file_name, file_path)) { + return false; + } + security_files.permissions_path = file_path; + + // Missing the logging configuration file is non-fatal + if (get_security_file_path(node_secure_root, logging_file_name, file_path)) { + security_files.logging_path = file_path; + } + + return true; +} +} // namespace + +#endif + +bool apply_logging_configuration_from_file( + const std::string & xml_file_path, + eprosima::fastrtps::rtps::PropertyPolicy & policy) +{ +#if HAVE_SECURITY + tinyxml2::XMLDocument document; + document.LoadFile(xml_file_path.c_str()); + + auto log_element = document.FirstChildElement("security_log"); + if (log_element == nullptr) { + RMW_SET_ERROR_MSG("logger xml file missing 'security_log'"); + return RMW_RET_ERROR; + } + + eprosima::fastrtps::rtps::PropertySeq properties; + add_property( + properties, + eprosima::fastrtps::rtps::Property( + logging_plugin_property_name, + "builtin.DDS_LogTopic")); + + bool status = add_property_from_xml_element( + properties, + log_file_property_name, + *log_element, + "file"); + if (!status) { + return status; + } + + status = add_property_from_xml_element( + properties, + distribute_enable_property_name, + *log_element, + "distribute"); + if (!status) { + return status; + } + + auto verbosity_element = log_element->FirstChildElement("verbosity"); + if (verbosity_element != nullptr) { + std::string verbosity_str; + if (!get_element_text(*verbosity_element, "verbosity", verbosity_str)) { + return false; + } + + std::string verbosity; + if (!string_to_verbosity(verbosity_str, verbosity)) { + RMW_SET_ERROR_MSG_WITH_FORMAT_STRING( + "failed to set security logging verbosity: %s is not a supported verbosity", + verbosity_str.c_str()); + return false; + } + + add_property( + properties, + eprosima::fastrtps::rtps::Property(verbosity_property_name, verbosity.c_str())); + } + + // Now that we're done parsing, actually update the properties + for (auto & item : properties) { + add_property(policy.properties(), std::move(item)); + } + + return true; +#else + RMW_SET_ERROR_MSG( + "This Fast-RTPS version doesn't have the security libraries\n" + "Please compile Fast-RTPS using the -DSECURITY=ON CMake option"); + return false; +#endif +} + +bool apply_security_options( + const rmw_security_options_t & security_options, + eprosima::fastrtps::rtps::PropertyPolicy & policy) +{ + if (security_options.security_root_path) { + // if security_root_path provided, try to find the key and certificate files +#if HAVE_SECURITY + security_files_t security_files; + if (get_security_file_paths(security_options.security_root_path, security_files)) { + using Property = eprosima::fastrtps::rtps::Property; + policy.properties().emplace_back( + Property("dds.sec.auth.plugin", "builtin.PKI-DH")); + policy.properties().emplace_back( + Property( + "dds.sec.auth.builtin.PKI-DH.identity_ca", + path_to_uri(security_files.identity_ca_cert_path))); + policy.properties().emplace_back( + Property( + "dds.sec.auth.builtin.PKI-DH.identity_certificate", + path_to_uri(security_files.cert_path))); + policy.properties().emplace_back( + Property( + "dds.sec.auth.builtin.PKI-DH.private_key", + path_to_uri(security_files.key_path))); + policy.properties().emplace_back( + Property("dds.sec.crypto.plugin", "builtin.AES-GCM-GMAC")); + policy.properties().emplace_back( + Property( + "dds.sec.access.plugin", "builtin.Access-Permissions")); + policy.properties().emplace_back( + Property( + "dds.sec.access.builtin.Access-Permissions.permissions_ca", + path_to_uri(security_files.permissions_ca_cert_path))); + policy.properties().emplace_back( + Property( + "dds.sec.access.builtin.Access-Permissions.governance", + path_to_uri(security_files.governance_path))); + policy.properties().emplace_back( + Property( + "dds.sec.access.builtin.Access-Permissions.permissions", + path_to_uri(security_files.permissions_path))); + + std::string security_logging_file_path; + if (!security_files.logging_path.empty()) { + if (!apply_logging_configuration_from_file( + security_files.logging_path, + policy)) + { + return false; + } + } + } else if (security_options.enforce_security) { + RMW_SET_ERROR_MSG("couldn't find all security files!"); + return false; + } +#else + RMW_SET_ERROR_MSG( + "This Fast-RTPS version doesn't have the security libraries\n" + "Please compile Fast-RTPS using the -DSECURITY=ON CMake option"); + return false; +#endif + } + + return true; +} diff --git a/rmw_fastrtps_shared_cpp/test/CMakeLists.txt b/rmw_fastrtps_shared_cpp/test/CMakeLists.txt index 5cb62c731..ae28d3804 100644 --- a/rmw_fastrtps_shared_cpp/test/CMakeLists.txt +++ b/rmw_fastrtps_shared_cpp/test/CMakeLists.txt @@ -5,3 +5,9 @@ if(TARGET test_dds_attributes_to_rmw_qos) ament_target_dependencies(test_dds_attributes_to_rmw_qos) target_link_libraries(test_dds_attributes_to_rmw_qos ${PROJECT_NAME}) endif() + +ament_add_gmock(test_security test_security.cpp) +if(TARGET test_security) + ament_target_dependencies(test_security) + target_link_libraries(test_security ${PROJECT_NAME}) +endif() diff --git a/rmw_fastrtps_shared_cpp/test/test_security.cpp b/rmw_fastrtps_shared_cpp/test/test_security.cpp new file mode 100644 index 000000000..8c5457364 --- /dev/null +++ b/rmw_fastrtps_shared_cpp/test/test_security.cpp @@ -0,0 +1,485 @@ +// Copyright 2020 Canonical Ltd. +// +// 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 + +#include "fastrtps/config.h" +#include "rcutils/filesystem.h" +#include "rmw/error_handling.h" +#include "rmw/security_options.h" +#include "rmw/types.h" + +#include "rmw_fastrtps_shared_cpp/rmw_security.hpp" + +#include "gmock/gmock.h" + +using ::testing::HasSubstr; +using ::testing::MatchesRegex; + +namespace +{ +const char logging_file_name[] = "logging.xml"; + +#if HAVE_SECURITY + +// File names +const char identity_ca_cert_file_name[] = "identity_ca.cert.pem"; +const char permissions_ca_cert_file_name[] = "permissions_ca.cert.pem"; +const char governance_file_name[] = "governance.p7s"; +const char cert_file_name[] = "cert.pem"; +const char key_file_name[] = "key.pem"; +const char permissions_file_name[] = "permissions.p7s"; + +// Authentication properties +const char identity_ca_property_name[] = "dds.sec.auth.builtin.PKI-DH.identity_ca"; +const char cert_property_name[] = "dds.sec.auth.builtin.PKI-DH.identity_certificate"; +const char key_property_name[] = "dds.sec.auth.builtin.PKI-DH.private_key"; + +// Access control properties +const char permissions_ca_property_name[] = + "dds.sec.access.builtin.Access-Permissions.permissions_ca"; +const char governance_property_name[] = "dds.sec.access.builtin.Access-Permissions.governance"; +const char permissions_property_name[] = "dds.sec.access.builtin.Access-Permissions.permissions"; + +// Logging properties +const char logging_plugin_property_name[] = "dds.sec.log.plugin"; +const char log_file_property_name[] = "dds.sec.log.builtin.DDS_LogTopic.log_file"; +const char verbosity_property_name[] = "dds.sec.log.builtin.DDS_LogTopic.logging_level"; +const char distribute_enable_property_name[] = + "dds.sec.log.builtin.DDS_LogTopic.distribute"; +const char distribute_depth_property_name[] = + "com.rti.serv.secure.logging.distribute.writer_history_depth"; + +const eprosima::fastrtps::rtps::Property & lookup_property( + const eprosima::fastrtps::rtps::PropertySeq & properties, const std::string & property_name) +{ + auto iterator = std::find_if( + properties.begin(), properties.end(), + [&property_name](const eprosima::fastrtps::rtps::Property & item) -> bool { + return item.name() == property_name; + }); + + if (iterator == properties.end()) { + ADD_FAILURE() << "Expected property " << property_name << " to be in list"; + } + + return *iterator; +} + +const eprosima::fastrtps::rtps::Property & identity_ca_property( + const eprosima::fastrtps::rtps::PropertySeq & properties) +{ + return lookup_property(properties, identity_ca_property_name); +} + +const eprosima::fastrtps::rtps::Property & cert_property( + const eprosima::fastrtps::rtps::PropertySeq & properties) +{ + return lookup_property(properties, cert_property_name); +} + +const eprosima::fastrtps::rtps::Property & key_property( + const eprosima::fastrtps::rtps::PropertySeq & properties) +{ + return lookup_property(properties, key_property_name); +} + +const eprosima::fastrtps::rtps::Property & permissions_ca_property( + const eprosima::fastrtps::rtps::PropertySeq & properties) +{ + return lookup_property(properties, permissions_ca_property_name); +} + +const eprosima::fastrtps::rtps::Property & governance_property( + const eprosima::fastrtps::rtps::PropertySeq & properties) +{ + return lookup_property(properties, governance_property_name); +} + +const eprosima::fastrtps::rtps::Property & permissions_property( + const eprosima::fastrtps::rtps::PropertySeq & properties) +{ + return lookup_property(properties, permissions_property_name); +} + +const eprosima::fastrtps::rtps::Property & logging_plugin_property( + const eprosima::fastrtps::rtps::PropertySeq & properties) +{ + return lookup_property(properties, logging_plugin_property_name); +} + +const eprosima::fastrtps::rtps::Property & log_file_property( + const eprosima::fastrtps::rtps::PropertySeq & properties) +{ + return lookup_property(properties, log_file_property_name); +} + +const eprosima::fastrtps::rtps::Property & verbosity_property( + const eprosima::fastrtps::rtps::PropertySeq & properties) +{ + return lookup_property(properties, verbosity_property_name); +} + +const eprosima::fastrtps::rtps::Property & distribute_enable_property( + const eprosima::fastrtps::rtps::PropertySeq & properties) +{ + return lookup_property(properties, distribute_enable_property_name); +} + +const eprosima::fastrtps::rtps::Property & distribute_depth_property( + const eprosima::fastrtps::rtps::PropertySeq & properties) +{ + return lookup_property(properties, distribute_depth_property_name); +} + +std::string create_security_files() +{ + // mkstemp isn't cross-platform, and we don't care about security here + std::string directory(std::tmpnam(nullptr)); + rcutils_mkdir(directory.c_str()); + + std::vector file_names { + identity_ca_cert_file_name, cert_file_name, key_file_name, + permissions_ca_cert_file_name, governance_file_name, permissions_file_name + }; + + rcutils_allocator_t allocator = rcutils_get_default_allocator(); + for (const auto & file_name : file_names) { + char * path = rcutils_join_path(directory.c_str(), file_name.c_str(), allocator); + std::ofstream file(path); + file << "I am " << file_name << std::endl; + file.close(); + allocator.deallocate(path, allocator.state); + } + + return directory; +} + +#endif + +std::string write_logging_xml(const std::string & xml, const std::string & directory = "") +{ + std::string xml_file_path; + + if (directory.empty()) { + // mkstemp isn't cross-platform, and we don't care about security here + xml_file_path = std::string(std::tmpnam(nullptr)); + } else { + rcutils_allocator_t allocator = rcutils_get_default_allocator(); + char * file_path = rcutils_join_path(directory.c_str(), logging_file_name, allocator); + + if (!file_path) { + ADD_FAILURE() << "Failed to allocate file path"; + return ""; + } + + xml_file_path = std::string(file_path); + + // This has been copied into xml_file_path now-- safe to deallocate + allocator.deallocate(file_path, allocator.state); + } + + std::ofstream xml_file; + xml_file.open(xml_file_path); + xml_file << "" << std::endl; + xml_file << "" << std::endl; + xml_file << xml << std::endl; + xml_file << "" << std::endl; + xml_file.close(); + + return xml_file_path; +} + +class SecurityTest : public ::testing::Test +{ +public: + void TearDown() + { + rmw_reset_error(); + } +}; +} // namespace + +#if HAVE_SECURITY + +TEST_F(SecurityTest, test_logging_plugin) +{ + std::string xml_file_path = write_logging_xml(""); + eprosima::fastrtps::rtps::PropertyPolicy policy; + EXPECT_TRUE(apply_logging_configuration_from_file(xml_file_path, policy)); + EXPECT_FALSE(rmw_error_is_set()); + + ASSERT_EQ(policy.properties().size(), 1u); + + auto property = logging_plugin_property(policy.properties()); + EXPECT_EQ(property.name(), logging_plugin_property_name); + EXPECT_EQ(property.value(), "builtin.DDS_LogTopic"); +} + +TEST_F(SecurityTest, test_log_file) +{ + std::string xml_file_path = write_logging_xml("foo"); + eprosima::fastrtps::rtps::PropertyPolicy policy; + EXPECT_TRUE(apply_logging_configuration_from_file(xml_file_path, policy)); + EXPECT_FALSE(rmw_error_is_set()); + + ASSERT_EQ(policy.properties().size(), 2u); + + auto property = logging_plugin_property(policy.properties()); + EXPECT_EQ(property.name(), logging_plugin_property_name); + EXPECT_EQ(property.value(), "builtin.DDS_LogTopic"); + + property = log_file_property(policy.properties()); + EXPECT_EQ(property.name(), log_file_property_name); + EXPECT_EQ(property.value(), "foo"); +} + +TEST_F(SecurityTest, test_log_verbosity) +{ + std::string xml_file_path = write_logging_xml("CRITICAL"); + eprosima::fastrtps::rtps::PropertyPolicy policy; + EXPECT_TRUE(apply_logging_configuration_from_file(xml_file_path, policy)); + EXPECT_FALSE(rmw_error_is_set()); + + ASSERT_EQ(policy.properties().size(), 2u); + + auto property = logging_plugin_property(policy.properties()); + EXPECT_EQ(property.name(), logging_plugin_property_name); + EXPECT_EQ(property.value(), "builtin.DDS_LogTopic"); + + property = verbosity_property(policy.properties()); + EXPECT_EQ(property.name(), verbosity_property_name); + EXPECT_EQ(property.value(), "CRITICAL_LEVEL"); +} + +TEST_F(SecurityTest, test_log_verbosity_invalid) +{ + std::string xml_file_path = write_logging_xml("INVALID_VERBOSITY"); + eprosima::fastrtps::rtps::PropertyPolicy policy; + EXPECT_FALSE(apply_logging_configuration_from_file(xml_file_path, policy)); + EXPECT_TRUE(rmw_error_is_set()); + EXPECT_THAT( + rmw_get_error_string().str, HasSubstr( + "INVALID_VERBOSITY is not a supported verbosity")); + + ASSERT_TRUE(policy.properties().empty()); +} + +TEST_F(SecurityTest, test_log_distribute) +{ + std::string xml_file_path = write_logging_xml("true"); + eprosima::fastrtps::rtps::PropertyPolicy policy; + EXPECT_TRUE(apply_logging_configuration_from_file(xml_file_path, policy)); + EXPECT_FALSE(rmw_error_is_set()); + + ASSERT_EQ(policy.properties().size(), 2u); + + auto property = logging_plugin_property(policy.properties()); + EXPECT_EQ(property.name(), logging_plugin_property_name); + EXPECT_EQ(property.value(), "builtin.DDS_LogTopic"); + + property = distribute_enable_property(policy.properties()); + EXPECT_EQ(property.name(), distribute_enable_property_name); + EXPECT_EQ(property.value(), "true"); +} + +TEST_F(SecurityTest, test_all) +{ + std::string xml_file_path = write_logging_xml( + "foo\n" + "CRITICAL\n" + "true"); + eprosima::fastrtps::rtps::PropertyPolicy policy; + EXPECT_TRUE(apply_logging_configuration_from_file(xml_file_path, policy)); + EXPECT_FALSE(rmw_error_is_set()); + + ASSERT_EQ(policy.properties().size(), 4u); + + auto property = log_file_property(policy.properties()); + EXPECT_EQ(property.name(), log_file_property_name); + EXPECT_EQ(property.value(), "foo"); + + property = verbosity_property(policy.properties()); + EXPECT_EQ(property.name(), verbosity_property_name); + EXPECT_EQ(property.value(), "CRITICAL_LEVEL"); + + property = distribute_enable_property(policy.properties()); + EXPECT_EQ(property.name(), distribute_enable_property_name); + EXPECT_EQ(property.value(), "true"); +} + +TEST_F(SecurityTest, test_default_security_options) +{ + eprosima::fastrtps::rtps::PropertyPolicy policy; + rmw_security_options_t security_options = rmw_get_default_security_options(); + + EXPECT_TRUE(apply_security_options(security_options, policy)); + EXPECT_FALSE(rmw_error_is_set()); + + EXPECT_TRUE(policy.properties().empty()); +} + +TEST_F(SecurityTest, test_invalid_security_root_not_enforced) +{ + eprosima::fastrtps::rtps::PropertyPolicy policy; + rmw_security_options_t security_options = rmw_get_default_security_options(); + char root_path[] = "/some/invalid/path"; + security_options.security_root_path = root_path; + + EXPECT_TRUE(apply_security_options(security_options, policy)); + EXPECT_FALSE(rmw_error_is_set()); + + EXPECT_TRUE(policy.properties().empty()); +} + +TEST_F(SecurityTest, test_invalid_security_root_enforced) +{ + eprosima::fastrtps::rtps::PropertyPolicy policy; + rmw_security_options_t security_options = rmw_get_default_security_options(); + char root_path[] = "/some/invalid/path"; + security_options.security_root_path = root_path; + security_options.enforce_security = RMW_SECURITY_ENFORCEMENT_ENFORCE; + + EXPECT_FALSE(apply_security_options(security_options, policy)); + EXPECT_TRUE(rmw_error_is_set()); + EXPECT_THAT(rmw_get_error_string().str, HasSubstr("couldn't find all security files")); +} + +TEST_F(SecurityTest, test_security_file_uris) +{ + std::string security_root = create_security_files(); + eprosima::fastrtps::rtps::PropertyPolicy policy; + rmw_security_options_t security_options = rmw_get_default_security_options(); + security_options.security_root_path = const_cast(security_root.c_str()); + security_options.enforce_security = RMW_SECURITY_ENFORCEMENT_ENFORCE; + + EXPECT_TRUE(apply_security_options(security_options, policy)); + EXPECT_EQ(policy.properties().size(), 9u); + + auto property = identity_ca_property(policy.properties()); + EXPECT_EQ(property.name(), identity_ca_property_name); + EXPECT_THAT( + property.value(), + MatchesRegex(std::string("file://.*/") + identity_ca_cert_file_name)); + + property = cert_property(policy.properties()); + EXPECT_EQ(property.name(), cert_property_name); + EXPECT_THAT(property.value(), MatchesRegex(std::string("file://.*/") + cert_file_name)); + + property = key_property(policy.properties()); + EXPECT_EQ(property.name(), key_property_name); + EXPECT_THAT(property.value(), MatchesRegex(std::string("file://.*/") + key_file_name)); + + property = permissions_ca_property(policy.properties()); + EXPECT_EQ(property.name(), permissions_ca_property_name); + EXPECT_THAT( + property.value(), + MatchesRegex(std::string("file://.*/") + permissions_ca_cert_file_name)); + + property = governance_property(policy.properties()); + EXPECT_EQ(property.name(), governance_property_name); + EXPECT_THAT(property.value(), MatchesRegex(std::string("file://.*/") + governance_file_name)); + + property = permissions_property(policy.properties()); + EXPECT_EQ(property.name(), permissions_property_name); + EXPECT_THAT(property.value(), MatchesRegex(std::string("file://.*/") + permissions_file_name)); +} + +TEST_F(SecurityTest, test_security_files_with_logging) +{ + std::string security_root = create_security_files(); + write_logging_xml("foo", security_root); + + eprosima::fastrtps::rtps::PropertyPolicy policy; + rmw_security_options_t security_options = rmw_get_default_security_options(); + security_options.security_root_path = const_cast(security_root.c_str()); + security_options.enforce_security = RMW_SECURITY_ENFORCEMENT_ENFORCE; + + EXPECT_TRUE(apply_security_options(security_options, policy)); + EXPECT_EQ(policy.properties().size(), 11u); + + auto property = identity_ca_property(policy.properties()); + EXPECT_EQ(property.name(), identity_ca_property_name); + EXPECT_THAT( + property.value(), + MatchesRegex(std::string("file://.*/") + identity_ca_cert_file_name)); + + property = cert_property(policy.properties()); + EXPECT_EQ(property.name(), cert_property_name); + EXPECT_THAT(property.value(), MatchesRegex(std::string("file://.*/") + cert_file_name)); + + property = key_property(policy.properties()); + EXPECT_EQ(property.name(), key_property_name); + EXPECT_THAT(property.value(), MatchesRegex(std::string("file://.*/") + key_file_name)); + + property = permissions_ca_property(policy.properties()); + EXPECT_EQ(property.name(), permissions_ca_property_name); + EXPECT_THAT( + property.value(), + MatchesRegex(std::string("file://.*/") + permissions_ca_cert_file_name)); + + property = governance_property(policy.properties()); + EXPECT_EQ(property.name(), governance_property_name); + EXPECT_THAT(property.value(), MatchesRegex(std::string("file://.*/") + governance_file_name)); + + property = permissions_property(policy.properties()); + EXPECT_EQ(property.name(), permissions_property_name); + EXPECT_THAT(property.value(), MatchesRegex(std::string("file://.*/") + permissions_file_name)); + + property = logging_plugin_property(policy.properties()); + EXPECT_EQ(property.name(), logging_plugin_property_name); + EXPECT_EQ(property.value(), "builtin.DDS_LogTopic"); + + property = log_file_property(policy.properties()); + EXPECT_EQ(property.name(), log_file_property_name); + EXPECT_EQ(property.value(), "foo"); +} + +#else + +TEST_F(SecurityTest, test_apply_logging_fails) +{ + std::string xml_file_path = write_logging_xml(""); + eprosima::fastrtps::rtps::PropertyPolicy policy; + EXPECT_FALSE(apply_logging_configuration_from_file(xml_file_path, policy)); + EXPECT_TRUE(rmw_error_is_set()); + EXPECT_THAT(rmw_get_error_string().str, HasSubstr("Please compile Fast-RTPS")); +} + +TEST_F(SecurityTest, test_apply_security_options_without_root) +{ + eprosima::fastrtps::rtps::PropertyPolicy policy; + rmw_security_options_t security_options = rmw_get_default_security_options(); + + EXPECT_TRUE(apply_security_options(security_options, policy)); + EXPECT_FALSE(rmw_error_is_set()); + + EXPECT_TRUE(policy.properties().empty()); +} + +TEST_F(SecurityTest, test_apply_security_options_with_root_fails) +{ + eprosima::fastrtps::rtps::PropertyPolicy policy; + rmw_security_options_t security_options = rmw_get_default_security_options(); + char root_path[] = "/some/invalid/path"; + security_options.security_root_path = root_path; + + EXPECT_FALSE(apply_security_options(security_options, policy)); + EXPECT_TRUE(rmw_error_is_set()); + EXPECT_THAT(rmw_get_error_string().str, HasSubstr("Please compile Fast-RTPS")); +} + +#endif