diff --git a/rmw_fastrtps_shared_cpp/CMakeLists.txt b/rmw_fastrtps_shared_cpp/CMakeLists.txt index c3f98a0f9..66908fd18 100644 --- a/rmw_fastrtps_shared_cpp/CMakeLists.txt +++ b/rmw_fastrtps_shared_cpp/CMakeLists.txt @@ -74,6 +74,7 @@ add_library(rmw_fastrtps_shared_cpp src/rmw_publisher.cpp src/rmw_request.cpp src/rmw_response.cpp + src/rmw_security_logging.cpp src/rmw_service.cpp src/rmw_service_names_and_types.cpp src/rmw_service_server_is_available.cpp diff --git a/rmw_fastrtps_shared_cpp/include/rmw_fastrtps_shared_cpp/rmw_security_logging.hpp b/rmw_fastrtps_shared_cpp/include/rmw_fastrtps_shared_cpp/rmw_security_logging.hpp new file mode 100644 index 000000000..d50a0b7af --- /dev/null +++ b/rmw_fastrtps_shared_cpp/include/rmw_fastrtps_shared_cpp/rmw_security_logging.hpp @@ -0,0 +1,22 @@ +// 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_LOGGING_HPP_ +#define RMW_FASTRTPS_SHARED_CPP__RMW_SECURITY_LOGGING_HPP_ + +#include "fastrtps/rtps/attributes/PropertyPolicy.h" + +bool apply_security_logging_configuration(eprosima::fastrtps::rtps::PropertyPolicy & policy); + +#endif // RMW_FASTRTPS_SHARED_CPP__RMW_SECURITY_LOGGING_HPP_ diff --git a/rmw_fastrtps_shared_cpp/src/participant.cpp b/rmw_fastrtps_shared_cpp/src/participant.cpp index 2f6cad6ae..06c328545 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_logging.hpp" using Domain = eprosima::fastrtps::Domain; using IPLocator = eprosima::fastrtps::rtps::IPLocator; @@ -230,6 +231,10 @@ rmw_fastrtps_shared_cpp::create_participant( Property( "dds.sec.access.builtin.Access-Permissions.permissions", security_files_paths[5])); + if (!apply_security_logging_configuration(property_policy)) { + return nullptr; + } + participantAttrs.rtps.properties = property_policy; } else if (security_options->enforce_security) { RMW_SET_ERROR_MSG("couldn't find all security files!"); diff --git a/rmw_fastrtps_shared_cpp/src/rmw_security_logging.cpp b/rmw_fastrtps_shared_cpp/src/rmw_security_logging.cpp new file mode 100644 index 000000000..f22f68a08 --- /dev/null +++ b/rmw_fastrtps_shared_cpp/src/rmw_security_logging.cpp @@ -0,0 +1,170 @@ +// 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 "rcutils/get_env.h" +#include "rmw/error_handling.h" +#include "rmw/qos_profiles.h" +#include "rmw/types.h" + +#include "rmw_fastrtps_shared_cpp/rmw_security_logging.hpp" + +#if HAVE_SECURITY + +namespace +{ +// Environment variable names +const char log_file_variable_name[] = "ROS_SECURITY_LOG_FILE"; +const char log_publish_variable_name[] = "ROS_SECURITY_LOG_PUBLISH"; +const char log_verbosity_variable_name[] = "ROS_SECURITY_LOG_VERBOSITY"; + +// 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"}, +}; + +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; +} + +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 get_env(const std::string & variable_name, std::string & variable_value) +{ + const char * value; + const char * error_message = rcutils_get_env(variable_name.c_str(), &value); + if (error_message != NULL) { + RMW_SET_ERROR_MSG_WITH_FORMAT_STRING( + "unable to get %s environment variable: %s", + variable_name, + error_message); + return false; + } + + variable_value = std::string(value); + + return true; +} +} // namespace + +#endif + +bool apply_security_logging_configuration(eprosima::fastrtps::rtps::PropertyPolicy & policy) +{ +#if HAVE_SECURITY + eprosima::fastrtps::rtps::PropertySeq properties; + std::string env_value; + + // Handle logging to file + if (!get_env(log_file_variable_name, env_value)) { + return false; + } + if (!env_value.empty()) { + add_property( + properties, + eprosima::fastrtps::rtps::Property( + log_file_property_name, env_value.c_str())); + } + + // Handle log distribution over DDS + if (!get_env(log_publish_variable_name, env_value)) { + return false; + } + if (!env_value.empty()) { + add_property( + properties, + eprosima::fastrtps::rtps::Property( + distribute_enable_property_name, env_value.c_str())); + } + + // Handle log verbosity + if (!get_env(log_verbosity_variable_name, env_value)) { + return false; + } + if (!env_value.empty()) { + std::string verbosity; + if (!string_to_verbosity(env_value, verbosity)) { + RMW_SET_ERROR_MSG_WITH_FORMAT_STRING( + "failed to set security logging verbosity: %s is not a supported verbosity", + env_value.c_str()); + return false; + } + + add_property( + properties, + eprosima::fastrtps::rtps::Property(verbosity_property_name, verbosity.c_str())); + } + + if (!properties.empty()) { + add_property( + properties, + eprosima::fastrtps::rtps::Property( + logging_plugin_property_name, + "builtin.DDS_LogTopic")); + } + + // 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 +} diff --git a/rmw_fastrtps_shared_cpp/test/CMakeLists.txt b/rmw_fastrtps_shared_cpp/test/CMakeLists.txt index 5cb62c731..19201042a 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_logging test_security_logging.cpp) +if(TARGET test_security_logging) + ament_target_dependencies(test_security_logging) + target_link_libraries(test_security_logging ${PROJECT_NAME}) +endif() diff --git a/rmw_fastrtps_shared_cpp/test/test_security_logging.cpp b/rmw_fastrtps_shared_cpp/test/test_security_logging.cpp new file mode 100644 index 000000000..bcb91d9b1 --- /dev/null +++ b/rmw_fastrtps_shared_cpp/test/test_security_logging.cpp @@ -0,0 +1,258 @@ +// 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_logging.hpp" + +#include "gmock/gmock.h" + +using ::testing::HasSubstr; +using ::testing::MatchesRegex; + +namespace +{ + +// Environment variable names +const char log_file_variable_name[] = "ROS_SECURITY_LOG_FILE"; +#if HAVE_SECURITY +const char log_publish_variable_name[] = "ROS_SECURITY_LOG_PUBLISH"; +const char log_verbosity_variable_name[] = "ROS_SECURITY_LOG_VERBOSITY"; + +// 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 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 & 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); +} + +#endif + +void custom_setenv(const std::string & variable_name, const std::string & value) +{ +#ifdef _WIN32 + auto ret = _putenv_s(variable_name.c_str(), value.c_str()); +#else + auto ret = setenv(variable_name.c_str(), value.c_str(), 1); +#endif + if (ret != 0) { + ADD_FAILURE() << "Unable to set environment variable: expected 0, got " << ret; + } +} + +class SecurityLoggingTest : public ::testing::Test +{ +public: + void SetUp() + { +#if HAVE_SECURITY + custom_setenv(log_file_variable_name, ""); + custom_setenv(log_publish_variable_name, ""); + custom_setenv(log_verbosity_variable_name, ""); +#endif + } + void TearDown() + { + rmw_reset_error(); + } +}; +} // namespace + +#if HAVE_SECURITY + +TEST_F(SecurityLoggingTest, test_no_logging) +{ + eprosima::fastrtps::rtps::PropertyPolicy policy; + EXPECT_TRUE(apply_security_logging_configuration(policy)); + EXPECT_FALSE(rmw_error_is_set()); + + ASSERT_TRUE(policy.properties().empty()); +} + +TEST_F(SecurityLoggingTest, test_log_to_file) +{ + custom_setenv(log_file_variable_name, "/test.log"); + + eprosima::fastrtps::rtps::PropertyPolicy policy; + EXPECT_TRUE(apply_security_logging_configuration(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(), "/test.log"); +} + +TEST_F(SecurityLoggingTest, test_log_publish_true) +{ + custom_setenv(log_publish_variable_name, "true"); + + eprosima::fastrtps::rtps::PropertyPolicy policy; + EXPECT_TRUE(apply_security_logging_configuration(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(SecurityLoggingTest, test_log_publish_false) +{ + custom_setenv(log_publish_variable_name, "false"); + + eprosima::fastrtps::rtps::PropertyPolicy policy; + EXPECT_TRUE(apply_security_logging_configuration(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(), "false"); +} + +TEST_F(SecurityLoggingTest, test_log_verbosity) +{ + custom_setenv(log_verbosity_variable_name, "CRITICAL"); + + eprosima::fastrtps::rtps::PropertyPolicy policy; + EXPECT_TRUE(apply_security_logging_configuration(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(SecurityLoggingTest, test_log_verbosity_invalid) +{ + custom_setenv(log_verbosity_variable_name, "INVALID_VERBOSITY"); + + eprosima::fastrtps::rtps::PropertyPolicy policy; + EXPECT_FALSE(apply_security_logging_configuration(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(SecurityLoggingTest, test_all) +{ + custom_setenv(log_file_variable_name, "/test.log"); + custom_setenv(log_publish_variable_name, "true"); + custom_setenv(log_verbosity_variable_name, "ERROR"); + + eprosima::fastrtps::rtps::PropertyPolicy policy; + EXPECT_TRUE(apply_security_logging_configuration(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(), "/test.log"); + + property = distribute_enable_property(policy.properties()); + EXPECT_EQ(property.name(), distribute_enable_property_name); + EXPECT_EQ(property.value(), "true"); + + property = verbosity_property(policy.properties()); + EXPECT_EQ(property.name(), verbosity_property_name); + EXPECT_EQ(property.value(), "ERROR_LEVEL"); +} + +#else + +TEST_F(SecurityLoggingTest, test_apply_logging_fails) +{ + custom_setenv(log_file_variable_name, "/test.log"); + + eprosima::fastrtps::rtps::PropertyPolicy policy; + EXPECT_FALSE(apply_security_logging_configuration(policy)); + EXPECT_TRUE(rmw_error_is_set()); + EXPECT_THAT(rmw_get_error_string().str, HasSubstr("Please compile Fast-RTPS")); +} + +#endif