Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@
[submodule "third_party/googletest"]
path = third_party/googletest
url = https://github.com/google/googletest.git
[submodule "third_party/spdlog"]
Comment thread
ryanwixon-emerson marked this conversation as resolved.
path = third_party/spdlog
url = https://github.com/gabime/spdlog.git
[submodule "third_party/ni-apis"]
path = third_party/ni-apis
url = https://github.com/ni/ni-apis

url = https://github.com/ni/ni-apis
6 changes: 6 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ if(USE_SUBMODULE_LIBS)
add_subdirectory(third_party/json ${CMAKE_CURRENT_BINARY_DIR}/json)
add_subdirectory(third_party/utfcpp ${CMAKE_CURRENT_BINARY_DIR}/utfcpp)
add_subdirectory(third_party/grpc-sideband ${CMAKE_CURRENT_BINARY_DIR}/grpc-sideband)
add_subdirectory(third_party/spdlog ${CMAKE_CURRENT_BINARY_DIR}/spdlog EXCLUDE_FROM_ALL)

set(_PROTOBUF_PROTOC $<TARGET_FILE:protobuf::protoc>)
set(_REFLECTION grpc++_reflection)
Expand All @@ -80,6 +81,7 @@ if(USE_SUBMODULE_LIBS)
set(_PROTOBUF_LIBPROTOBUF libprotobuf)
set(_UTF8CPP utf8cpp)
set(_GRPC_SIDEBAND ni_grpc_sideband)
set(_SPDLOG spdlog::spdlog)
else()
find_program(_PROTOBUF_PROTOC protoc)
find_program(_GRPC_CPP_PLUGIN_EXECUTABLE grpc_cpp_plugin)
Expand All @@ -103,9 +105,11 @@ else()
find_package(GTest REQUIRED)
find_package(nlohmann_json REQUIRED)
find_package(utf8cpp REQUIRED)
find_package(spdlog REQUIRED)

set(_GRPC gRPC::grpc)
set(_UTF8CPP utf8cpp::utf8cpp)
set(_SPDLOG spdlog::spdlog)
endif()
endif()

Expand Down Expand Up @@ -548,6 +552,7 @@ add_executable(ni_grpc_device_server
"imports/include/nierr_Status.cpp"
"source/server/calibration_operations_restricted_service_registrar.cpp"
"source/server/calibration_operations_restricted_service.cpp"
"source/server/client_connection_logger.cpp"
"source/server/core_server.cpp"
"source/server/core_services_registrar.cpp"
"source/server/data_moniker_service.cpp"
Expand Down Expand Up @@ -657,6 +662,7 @@ set(server_lib_deps
${_UTF8CPP}
${CMAKE_DL_LIBS}
${_GRPC_SIDEBAND}
${_SPDLOG}
nlohmann_json::nlohmann_json
)

Expand Down
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -191,3 +191,15 @@ With this setting the server reads TLS configuration at startup from the per-ser
If `"security": "ni-tls-config"` is configured but the `ni-tls-config` library is not installed, the server logs an error and exits instead of starting insecurely.

Once `ni-tls-config` is enabled, use NI Hardware Configuration Utility on each client machine to configure the desired security settings.

### Audit Logging

The server logs audit messages to the Windows Event Log and Linux Syslog. On Windows, events are logged from the `ni-grpc-device-server` source. These events will be automatically placed under the default Application log (seen under the Windows folder in the Event Viewer app). It is recommended to add the following registry key to properly register it as a source:
Comment thread
ryanwixon-emerson marked this conversation as resolved.

`HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\EventLog\National Instruments\ni-grpc-device-server`

Then add the following values:
- `EventMessageFile (REG_EXPAND_SZ): %systemroot%\System32\mscoree.dll`
- `TypesSupported (REG_DWORD): 7`

This recommendation is taken directly from [spdlog's documentation](https://github.com/gabime/spdlog/blob/v1.x/include/spdlog/sinks/win_eventlog_sink.h).
54 changes: 54 additions & 0 deletions source/server/client_connection_logger.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
#include "client_connection_logger.h"

#include "logging.h"

namespace nidevice_grpc {

namespace {

bool parse_peer(const std::string& peer, std::string& ip, std::string& port)
Comment thread
ryanwixon-emerson marked this conversation as resolved.
{
if (peer.rfind("ipv4:", 0) != 0 && peer.rfind("ipv6:", 0) != 0)
return false;

const auto scheme_end = peer.find(':');
const auto port_pos = peer.rfind(':');
Comment thread
ryanwixon-emerson marked this conversation as resolved.
Outdated
if (port_pos <= scheme_end)
return false;

ip = peer.substr(scheme_end + 1, port_pos - scheme_end - 1);
port = peer.substr(port_pos + 1);

// Strip ipv6 brackets, e.g. "[::1]" -> "::1".
if (ip.size() >= 2 && ip.front() == '[' && ip.back() == ']')
ip = ip.substr(1, ip.size() - 2);

return !ip.empty() && !port.empty();
Comment thread
ryanwixon-emerson marked this conversation as resolved.
}

}

void ClientConnectionLogger::PreSynchronousRequest(grpc::ServerContext* context)
Comment thread
ryanwixon-emerson marked this conversation as resolved.
Comment thread
ryanwixon-emerson marked this conversation as resolved.
{
const auto peer = context->peer();
std::string ip, port;

// Try to parse the IP + Port out of the peer uri so that the log message can be formatted nicely. If it fails, just log the raw string.
Comment thread
ryanwixon-emerson marked this conversation as resolved.
if (parse_peer(peer, ip, port))
nidevice_grpc::logging::log_to_audit_source(nidevice_grpc::logging::Level_Info, "Remote client successfully connected from %s:%s", ip.c_str(), port.c_str());
else
nidevice_grpc::logging::log_to_audit_source(nidevice_grpc::logging::Level_Info, "Remote client successfully connected from %s", peer.c_str());
}

void ClientConnectionLogger::PostSynchronousRequest(grpc::ServerContext*)
{
}

void register_client_connection_logger()
{
// gRPC does not take ownership of the callbacks object, so it will be a static object.
Comment thread
ryanwixon-emerson marked this conversation as resolved.
Outdated
static ClientConnectionLogger logger;
grpc::Server::SetGlobalCallbacks(&logger);
}

} // namespace nidevice_grpc
22 changes: 22 additions & 0 deletions source/server/client_connection_logger.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#ifndef NIDEVICE_GRPC_CLIENT_CONNECTION_LOGGER_H
#define NIDEVICE_GRPC_CLIENT_CONNECTION_LOGGER_H

#include <grpcpp/grpcpp.h>

#include <string>

namespace nidevice_grpc {

// Implementation of callbacks that the grpc::Server will invoke for individual client RPC calls.
class ClientConnectionLogger : public grpc::Server::GlobalCallbacks {
public:
void PreSynchronousRequest(grpc::ServerContext* context) override;
void PostSynchronousRequest(grpc::ServerContext* context) override;
};

// Registers a process-wide ClientConnectionLogger with gRPC, must be called before any grpc::Server is built.
void register_client_connection_logger();

} // namespace nidevice_grpc

#endif // NIDEVICE_GRPC_CLIENT_CONNECTION_LOGGER_H
3 changes: 3 additions & 0 deletions source/server/core_server.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#include <thread>
#include <algorithm>

#include "client_connection_logger.h"
#include "feature_toggles.h"
#include "logging.h"
#include "tls_config_loader.h"
Expand Down Expand Up @@ -99,6 +100,8 @@ static void RunServer(const ServerConfiguration& config)
config.config_file_path.c_str());
}

nidevice_grpc::register_client_connection_logger();

grpc::EnableDefaultHealthCheckService(true);
grpc::reflection::InitProtoReflectionServerBuilderPlugin();

Expand Down
63 changes: 63 additions & 0 deletions source/server/logging.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,15 @@

#include <cstdio>
#include <iostream>
#include <memory>
#include <string>

#include <spdlog/spdlog.h>
#if defined(_WIN32)
#include <spdlog/sinks/win_eventlog_sink.h>
#else
#include <spdlog/sinks/syslog_sink.h>
#endif

namespace nidevice_grpc {
namespace logging {
Expand Down Expand Up @@ -37,5 +46,59 @@ void log(Level level, const char* fmt, ...)
va_end(args);
}

namespace {

std::shared_ptr<spdlog::logger> get_audit_logger()
{
static std::shared_ptr<spdlog::logger> audit_logger = []() {
#if defined(_WIN32)
auto sink = std::make_shared<spdlog::sinks::win_eventlog_sink_mt>("ni-grpc-device-server");
#else
auto sink = std::make_shared<spdlog::sinks::syslog_sink_mt>("ni-grpc-device-server", LOG_PID, LOG_USER, /*enable_formatting=*/true);
Comment thread
ryanwixon-emerson marked this conversation as resolved.
Outdated
#endif
auto logger = std::make_shared<spdlog::logger>("Server", sink);
logger->set_pattern("[ni-grpc-device-server][%n] %v");
return logger;
}();
return audit_logger;
}

std::string format_message(const char* fmt, va_list args)
{
va_list args_copy;
va_copy(args_copy, args);
const int size = std::vsnprintf(nullptr, 0, fmt, args_copy);
va_end(args_copy);
if (size <= 0) {
return std::string();
}
std::string result(static_cast<size_t>(size), '\0');
std::vsnprintf(&result[0], result.size() + 1, fmt, args);
return result;
}

}

void log_to_audit_source(Level level, const char* fmt, ...)
{
va_list args;
va_start(args, fmt);
const std::string message = format_message(fmt, args);
va_end(args);

auto audit_logger = get_audit_logger();
Comment thread
ckoellin marked this conversation as resolved.
Outdated
switch (level) {
case Level_Info:
audit_logger->info("{}", message);
break;
case Level_Warning:
audit_logger->warn("{}", message);
break;
case Level_Error:
audit_logger->error("{}", message);
break;
}
}

} // namespace logging
} // namespace nidevice_grpc
3 changes: 3 additions & 0 deletions source/server/logging.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ typedef void (*log_fn_impl)(Level level, const char* fmt, va_list args);
void set_logger(log_fn_impl impl);
void log(Level level, const char* fmt, ...);

// Special log option that routes to the platform audit log (Windows Event Log or Syslog)
void log_to_audit_source(Level level, const char* fmt, ...);

} // namespace logging
} // namespace nidevice_grpc

Expand Down
1 change: 1 addition & 0 deletions third_party/spdlog
Submodule spdlog added at 79524d
Loading