From 2655b43ee7bdf1eb1ec54fda7685551aa0e861f7 Mon Sep 17 00:00:00 2001 From: Jean Christophe Roques Date: Thu, 20 Jun 2024 12:46:14 +0200 Subject: [PATCH] first centagent executable --- .github/scripts/collect-unit-tests.sh | 1 + .github/workflows/centreon-collect.yml | 2 + .github/workflows/package-collect.yml | 5 +- .gitignore | 5 + CMakeLists.txt | 5 +- agent/CMakeLists.txt | 176 +++++++++++++ agent/conf/CMakeLists.txt | 43 ++++ agent/conf/centagent.cfg.in | 121 +++++++++ agent/doc/agent-doc.md | 24 ++ agent/doc/pictures/logo.jpg | Bin 0 -> 49244 bytes agent/precomp_inc/precomp.hh | 45 ++++ agent/proto/agent.proto | 91 +++++++ agent/scripts/centagent.service.in | 33 +++ agent/src/main.cc | 239 ++++++++++++++++++ agent/test/CMakeLists.txt | 56 ++++ agent/test/test_main.cc | 59 +++++ broker/CMakeLists.txt | 2 + clib/inc/com/centreon/process.hh | 4 +- clib/src/process.cc | 2 +- common/inc/com/centreon/common/process.hh | 24 +- common/src/process.cc | 4 +- common/tests/CMakeLists.txt | 2 + packaging/centreon-agent-debuginfo.yaml | 42 +++ packaging/centreon-agent-selinux.yaml | 40 +++ packaging/centreon-agent.yaml | 66 +++++ .../centreon-agent-daemon-postinstall.sh | 25 ++ .../centreon-agent-daemon-postremove.sh | 8 + .../centreon-agent-daemon-preinstall.sh | 10 + .../centreon-agent-daemon-preremove.sh | 3 + .../centreon-agent-selinux-postinstall.sh | 25 ++ .../centreon-agent-selinux-preremove.sh | 5 + selinux/centreon-agent/centreon-agent.fc | 1 + selinux/centreon-agent/centreon-agent.if | 1 + selinux/centreon-agent/centreon-agent.te | 170 +++++++++++++ 34 files changed, 1319 insertions(+), 20 deletions(-) create mode 100644 agent/CMakeLists.txt create mode 100644 agent/conf/CMakeLists.txt create mode 100644 agent/conf/centagent.cfg.in create mode 100644 agent/doc/agent-doc.md create mode 100644 agent/doc/pictures/logo.jpg create mode 100644 agent/precomp_inc/precomp.hh create mode 100644 agent/proto/agent.proto create mode 100644 agent/scripts/centagent.service.in create mode 100644 agent/src/main.cc create mode 100644 agent/test/CMakeLists.txt create mode 100644 agent/test/test_main.cc create mode 100644 packaging/centreon-agent-debuginfo.yaml create mode 100644 packaging/centreon-agent-selinux.yaml create mode 100644 packaging/centreon-agent.yaml create mode 100644 packaging/scripts/centreon-agent-daemon-postinstall.sh create mode 100644 packaging/scripts/centreon-agent-daemon-postremove.sh create mode 100644 packaging/scripts/centreon-agent-daemon-preinstall.sh create mode 100644 packaging/scripts/centreon-agent-daemon-preremove.sh create mode 100644 packaging/scripts/centreon-agent-selinux-postinstall.sh create mode 100644 packaging/scripts/centreon-agent-selinux-preremove.sh create mode 100644 selinux/centreon-agent/centreon-agent.fc create mode 100644 selinux/centreon-agent/centreon-agent.if create mode 100644 selinux/centreon-agent/centreon-agent.te diff --git a/.github/scripts/collect-unit-tests.sh b/.github/scripts/collect-unit-tests.sh index cd8e89bd0b4..077ff0291b9 100755 --- a/.github/scripts/collect-unit-tests.sh +++ b/.github/scripts/collect-unit-tests.sh @@ -25,4 +25,5 @@ tests/ut_engine --gtest_output=xml:ut_engine.xml tests/ut_clib --gtest_output=xml:ut_clib.xml tests/ut_connector --gtest_output=xml:ut_connector.xml tests/ut_common --gtest_output=xml:ut_common.xml +tests/ut_agent --gtest_output=xml:ut_agent.xml echo "---------------------------------------------------------- end of ut tests ------------------------------------------------" diff --git a/.github/workflows/centreon-collect.yml b/.github/workflows/centreon-collect.yml index 5525e3df391..b0dc3532ac1 100644 --- a/.github/workflows/centreon-collect.yml +++ b/.github/workflows/centreon-collect.yml @@ -8,6 +8,7 @@ on: workflow_dispatch: pull_request: paths: + - agent/** - bbdo/** - broker/** - ccc/** @@ -33,6 +34,7 @@ on: - master - "[2-9][0-9].[0-9][0-9].x" paths: + - agent/** - bbdo/** - broker/** - ccc/** diff --git a/.github/workflows/package-collect.yml b/.github/workflows/package-collect.yml index b3b0cc4ded6..88c596ebc89 100644 --- a/.github/workflows/package-collect.yml +++ b/.github/workflows/package-collect.yml @@ -105,7 +105,7 @@ jobs: if: ${{ matrix.package_extension == 'rpm' }} run: | cd selinux - for MODULE in "centreon-engine" "centreon-broker"; do + for MODULE in "centreon-engine" "centreon-broker" "centreon-agent"; do cd $MODULE sed -i "s/@VERSION@/${{ inputs.version }}/g" $MODULE.te make -f /usr/share/selinux/devel/Makefile @@ -187,7 +187,8 @@ jobs: "build/engine/modules/bench/centengine_bench_passive" "build/connectors/perl/centreon_connector_perl" "build/connectors/ssh/centreon_connector_ssh" - "build/ccc/ccc") + "build/ccc/ccc" + "build/agent/centagent") for file in ${exe[@]}; do echo "Making a debug file of $file" objcopy --only-keep-debug $file $file.debug diff --git a/.gitignore b/.gitignore index 8243275dd6e..77cd000405a 100644 --- a/.gitignore +++ b/.gitignore @@ -65,6 +65,10 @@ log.html output.xml report.html +# agent +agent/scripts/centagent.service +opentelemetry-proto + # bbdo bbdo/*_accessor.hh @@ -140,3 +144,4 @@ tests/bench.unqlite tests/resources/*_pb2.py tests/resources/*_pb2_grpc.py tests/resources/grpc_stream.proto +tests/resources/opentelemetry diff --git a/CMakeLists.txt b/CMakeLists.txt index 27dc8e2264f..dc6a87921df 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -147,6 +147,7 @@ endif() set(USER_BROKER centreon-broker) set(USER_ENGINE centreon-engine) + find_package(fmt CONFIG REQUIRED) find_package(spdlog CONFIG REQUIRED) find_package(gRPC CONFIG REQUIRED) @@ -236,6 +237,7 @@ add_subdirectory(bbdo) add_subdirectory(engine) add_subdirectory(connectors) add_subdirectory(ccc) +add_subdirectory(agent) if (WITH_MALLOC_TRACE) add_subdirectory(malloc-trace) @@ -247,9 +249,10 @@ add_custom_target(test-engine COMMAND tests/ut_engine) add_custom_target(test-clib COMMAND tests/ut_clib) add_custom_target(test-connector COMMAND tests/ut_connector) add_custom_target(test-common COMMAND tests/ut_common) +add_custom_target(test-agent COMMAND tests/ut_agent) add_custom_target(test DEPENDS test-broker test-engine test-clib test-connector - test-common) + test-common test-agent) add_custom_target(test-coverage DEPENDS broker-test-coverage engine-test-coverage clib-test-coverage) diff --git a/agent/CMakeLists.txt b/agent/CMakeLists.txt new file mode 100644 index 00000000000..b289ab3dd41 --- /dev/null +++ b/agent/CMakeLists.txt @@ -0,0 +1,176 @@ +# +# Copyright 2024 Centreon +# +# 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. +# +# For more information : contact@centreon.com +# + +# Global options. +project("Centreon agent" C CXX) + +# Set directories. +set(INCLUDE_DIR "${PROJECT_SOURCE_DIR}/inc/com/centreon/agent") +set(SRC_DIR "${PROJECT_SOURCE_DIR}/src") +set(SCRIPT_DIR "${PROJECT_SOURCE_DIR}/scripts") + + +add_definitions("-D_GLIBCXX_USE_CXX11_ABI=1") +add_definitions(-DSPDLOG_ACTIVE_LEVEL=SPDLOG_LEVEL_TRACE) + +option(WITH_LIBCXX "compiles and link cbd with clang++/libc++") + +if(WITH_LIBCXX) + set(CMAKE_CXX_COMPILER "clang++") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++") + + # set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=thread -Werror -O1 + # -fno-omit-frame-pointer") +endif() + +#otel service +set(service_files + opentelemetry/proto/collector/metrics/v1/metrics_service +) + +foreach(name IN LISTS service_files) + set(proto_file "${name}.proto") + add_custom_command( + OUTPUT "${SRC_DIR}/${name}.grpc.pb.cc" + COMMENT "Generating grpc files of the otl service file ${proto_file}" + DEPENDS opentelemetry-proto-files + COMMAND + ${Protobuf_PROTOC_EXECUTABLE} ARGS + --plugin=protoc-gen-grpc=${GRPC_CPP_PLUGIN} + --proto_path=${CMAKE_SOURCE_DIR}/opentelemetry-proto + --grpc_out=${SRC_DIR} ${proto_file} + VERBATIM + WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) + +endforeach() + +set(otl_protobuf_files + opentelemetry/proto/collector/metrics/v1/metrics_service + opentelemetry/proto/metrics/v1/metrics + opentelemetry/proto/common/v1/common + opentelemetry/proto/resource/v1/resource +) +foreach(name IN LISTS otl_protobuf_files) + set(proto_file "${name}.proto") + add_custom_command( + OUTPUT "${SRC_DIR}/${name}.pb.cc" + COMMENT "Generating interface files of the otl file ${proto_file}" + DEPENDS opentelemetry-proto-files + COMMAND + ${Protobuf_PROTOC_EXECUTABLE} ARGS --cpp_out=${SRC_DIR} + --proto_path=${CMAKE_SOURCE_DIR}/opentelemetry-proto ${proto_file} + VERBATIM) +endforeach() + + +#centagent server and client +add_custom_command( + DEPENDS ${PROJECT_SOURCE_DIR}/proto/agent.proto + COMMENT "Generating interface files of the conf centagent proto file (grpc)" + OUTPUT ${SRC_DIR}/agent.grpc.pb.cc + COMMAND + ${Protobuf_PROTOC_EXECUTABLE} ARGS + --plugin=protoc-gen-grpc=${GRPC_CPP_PLUGIN} + --proto_path=${PROJECT_SOURCE_DIR}/proto --proto_path=${CMAKE_SOURCE_DIR}/opentelemetry-proto + --grpc_out=${SRC_DIR} ${PROJECT_SOURCE_DIR}/proto/agent.proto + DEPENDS ${PROJECT_SOURCE_DIR}/proto/agent.proto + COMMENT "Generating interface files of the conf centagent proto file (protobuf)" + OUTPUT ${SRC_DIR}/agent.pb.cc + COMMAND + ${Protobuf_PROTOC_EXECUTABLE} ARGS --cpp_out=${SRC_DIR} + --proto_path=${PROJECT_SOURCE_DIR}/proto --proto_path=${CMAKE_SOURCE_DIR}/opentelemetry-proto + ${PROJECT_SOURCE_DIR}/proto/agent.proto + WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) + + +add_library(centagent_lib STATIC + ${SRC_DIR}/opentelemetry/proto/collector/metrics/v1/metrics_service.grpc.pb.cc + ${SRC_DIR}/opentelemetry/proto/collector/metrics/v1/metrics_service.pb.cc + ${SRC_DIR}/opentelemetry/proto/metrics/v1/metrics.pb.cc + ${SRC_DIR}/opentelemetry/proto/common/v1/common.pb.cc + ${SRC_DIR}/opentelemetry/proto/resource/v1/resource.pb.cc +) + +include_directories( + ${INCLUDE_DIR} + ${SRC_DIR} + ${CMAKE_SOURCE_DIR}/common/inc + ${CMAKE_SOURCE_DIR}/common/grpc/inc +) + +target_precompile_headers(centagent_lib PRIVATE precomp_inc/precomp.hh) + +SET(CENTREON_AGENT centagent) + +add_executable(${CENTREON_AGENT} ${SRC_DIR}/main.cc) + +target_link_libraries( + ${CENTREON_AGENT} PRIVATE + -L${PROTOBUF_LIB_DIR} + gRPC::gpr gRPC::grpc gRPC::grpc++ gRPC::grpc++_alts + centagent_lib + centreon_common + centreon_grpc + -L${Boost_LIBRARY_DIR_RELEASE} + boost_program_options + fmt::fmt) + +target_precompile_headers(${CENTREON_AGENT} REUSE_FROM centagent_lib) + +target_include_directories(${CENTREON_AGENT} PRIVATE + ${INCLUDE_DIR} + ${SRC_DIR} + ${CMAKE_SOURCE_DIR}/common/inc +) + +set(AGENT_VAR_LOG_DIR + "${CMAKE_INSTALL_FULL_LOCALSTATEDIR}/log/centreon-agent") + + +install(TARGETS ${CENTREON_AGENT} RUNTIME DESTINATION "${CMAKE_INSTALL_FULL_BINDIR}") + +if(WITH_TESTING) + add_subdirectory(test) +endif() + + +set(PREFIX_AGENT_CONF "${CMAKE_INSTALL_FULL_SYSCONFDIR}/centreon-agent") +set(USER_AGENT centreon-agent) + + +if(WITH_CONF) + add_subdirectory(conf) +endif() + +# Generate Systemd script. +message(STATUS "Generating systemd startup script.") +configure_file("${SCRIPT_DIR}/centagent.service.in" + "${SCRIPT_DIR}/centagent.service") + +# Startup dir. +if(WITH_STARTUP_DIR) + set(STARTUP_DIR "${WITH_STARTUP_DIR}") +else() + set(STARTUP_DIR "/etc/systemd/system") +endif() + +# Script install rule. +install( + PROGRAMS "${SCRIPT_DIR}/centagent.service" + DESTINATION "${STARTUP_DIR}" + COMPONENT "runtime") diff --git a/agent/conf/CMakeLists.txt b/agent/conf/CMakeLists.txt new file mode 100644 index 00000000000..8387e7c2956 --- /dev/null +++ b/agent/conf/CMakeLists.txt @@ -0,0 +1,43 @@ +# +# Copyright 2024 Centreon +# +# 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. +# +# For more information : contact@centreon.com +# + +# Set directories. +set(SRC_DIR "${PROJECT_SOURCE_DIR}/conf") + +# Configure files. +configure_file("${SRC_DIR}/centagent.cfg.in" + "${SRC_DIR}/centagent.cfg") + +# Install files if necessary. +option(WITH_SAMPLE_CONFIG "Install sample configuration files." ON) +if (WITH_SAMPLE_CONFIG) + install(DIRECTORY "${SRC_DIR}/" + DESTINATION "${PREFIX_AGENT_CONF}" + COMPONENT "runtime" + FILES_MATCHING PATTERN "*.cfg") + + install(CODE " + function(my_chown user group file) + if (APPLE OR (UNIX AND NOT CYGWIN)) + execute_process(COMMAND \"chown\" \"\${user}:\${group}\" \"\${file}\") + endif () + endfunction() + + my_chown(\"${USER_AGENT}\" \"${USER_AGENT}\" \"${PREFIX_AGENT_CONF}/centagent.cfg\") + ") +endif () diff --git a/agent/conf/centagent.cfg.in b/agent/conf/centagent.cfg.in new file mode 100644 index 00000000000..9475d8f9ae1 --- /dev/null +++ b/agent/conf/centagent.cfg.in @@ -0,0 +1,121 @@ +# +# Copyright 2024 Centreon +# +# This file is part of Centreon Agent. +# +# 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. +# +# For more information : contact@centreon.com +# + +# file: centagent.cfg +# brief: Sample main config file for Centreon Agent @VERSION@ +# +# Read the documentation for more information on this configuration file. I've +# provided some comments here, but things may not be so clear without further +# explanation. +# If you want to have a list of all options, +# in a terminal, executes @CMAKE_INSTALL_FULL_BINDIR@/@CENTREON_AGENT@ --help + + +# var: log-file +# brief: in case of log-type=file, logs will be written in this file + +log-file=@AGENT_VAR_LOG_DIR@/@CENTREON_AGENT@.log + + +# var: log-level +# brief: You can choose log verbosity: +# critical, error, info, debug, trace +# You can change log verbosity during runtime with USR1 and USR2 signals +# Ex: kill -USR1 to decrease verbosity (error => info for example) +# kill -USR2 to increase verbosity + +log-level=info + + +# var: log-type +# brief: type of log output: stdout or to a file + +log-type=file + +# var: log-max-file-size +# brief: max log file size in bytes until rotate + +log-max-file-size=10000000 + + +# var: log-max-files +# brief: max number of log files (oldest will be removed) + +log-max-files=3 + + +# var: endpoint +# brief: This parameter is mandatory +# In normal case (agent connects to engine), this endpoint is the +# opentelemetry listening endpoint of engine +# His syntax is :port + +#endpoint=:4317 + + +# var: encryption +# brief: false by default +# set to true to enable encryption between engine and agent + +encryption=false + + +# var: certificate +# brief: path of the certificate file used by encryption + +#certificate=/etc/centron-agent/certif.crt + + +# var: private_key +# brief: path of the key file of the certificate file + +#private_key=/etc/centreon-agent/certif.key + + +# var: ca_certificate +# brief: path of the authority certificate file used by encryption + +#ca_certificate=/etc/centreon-agent/ca.crt + + +# var: ca_name +# brief: name of the host declared in authority certificate + +#ca_name= + + +# var: host +# brief: name of the host declared in centreon configuration +# if not given, hostname of the computer will be used + +#host= + + +# var: reversed-grpc-streaming +# brief: used when centreon agent is not allowed to connect to poller +# if this option if set to true, centreon agent become a +# grpc server listening on pair interface:port given by +# endpoint parameter where poller will have to connect to + +reversed-grpc-streaming=false + + + + diff --git a/agent/doc/agent-doc.md b/agent/doc/agent-doc.md new file mode 100644 index 00000000000..f8c167ab93b --- /dev/null +++ b/agent/doc/agent-doc.md @@ -0,0 +1,24 @@ +# Centreon Agent documentation {#mainpage} + +## Introduction + +The goal of this program is to execute checks in both windows and linux OS +It's full asynchronous, excepted grpc layers, it's single threaded and you won't find mutex in non grpc code. +This is why when we receive request, we post it to asio in order to process it in the main thread. + +## Configuration +configuration is given by Engine by a AgentConfiguration sent over grpc +The configuration object is embedded in MessageToAgent::config + +## Scheduler +We trie to spread checks over check_period. +Example: We have 10 checks to execute during one second. check1 will start at now + 0.1s, second at now + 0.2s.. + +When Agent receives configuration, all checks are recreated. +For example, we have 100 checks to execute in 10 minute, at it is 12:00:00. +First service check will start right now, second one at 12:00:06, third at 12:00:12... and the last one at 12:09:54 +We don't care about tests duration, we work with time points. +In the previous example, time of second check of first service will be scheduled at 12:00:10 even if all other checks has not been yet started. + +In case of check duration is too long, we might exceed maximum of concurrent checks. In that case checks will b executed as soon one will be ended. +So second check may start later than scheduled time point (12:00:10) if other first checks are too long. Order of checks is always respected even in case of bottleneck. diff --git a/agent/doc/pictures/logo.jpg b/agent/doc/pictures/logo.jpg new file mode 100644 index 0000000000000000000000000000000000000000..0bcd7358aa9c7ffa817d9a30008efcd4d1fe676d GIT binary patch literal 49244 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

-T6XX>XkTFzkUd{gwQrJ+erbvG&s zlz$RhSjccaKS;;dG3R(z+{9~;+nCllp@4r&@3(E0;T9lRSXN9cx`0&DpAnPaTvrQx zIg1kAqHMe=#>5po3P)XGaVHS^zwGM{L`N|d5n{6(h`J)>L-X0?24C9}@)}sE{94b5 z{NOV7!KL&8j1MCmN-4jbm|2$9d-ax}eBl+f<&HA42FV`TkJLta!1KNL+3KRY*<4FB z6J0hWa9FY{{k4?Llkluow%0|ojKR+ty(S&mpmFmcfOL+VC5+}uSu9ECD)(`IAHx8p z8F;%cBZNi^I7U{gdO`O)Uq3)Xs$ganP1;E&p>QJ8jXJ{1Qhzn(lKF!lX}s@^Os~z= zU{<&GCj;NX!tB#()wC{#VmcHwWImH0L?*{A;LW~9c({Ma#3%mRpsvNzDwfk;ZKXxD z^R!A508h}}W!+kv^-S??DM2Ym_{8g@7di_AJBQZRA;|h=1=9{mBaQvzf{Q`KeLTYR z>wF0*x%u9;Dzm`lJ6g2xUP~W!1t)Br&C`cA&PMI~L+Mw&Dx+GQy=dx%1qeZ&KEYgM zd|AK~Z??89x<*le1cK}?Y=cAyV}e#=)-kQ-yew*)5p&G?Ed1{TDoS?CA?G_^S@(%m zuV$?9-&>4%Sqk?-;g0^jbv;+JSecEe$W5rq2hVz_)nL^;tg?_tar0tsYDoec5KV&_D|>v zfax&UQ}RQy)=4eN(XjL`7~mb`LP=8=N^X%8U6LKPbZ=F~@H(5}RORY`)0MBSr>VdL zb1+M(KzJovbBDJ523@s^>8*>X@84WtA|IXLEE@(DG{qoF@pV#59^&C4IED+?ga#2cVnUGfHXEQYi!$h8jtVYcPAHC@F z>&Dw^m_@A0lSKoBVhcDMcUa`+=BKvM;y0`(D5CY09LlcoP~hlq*($cXVmgS`pZ+&y$iQ=jG0?;dq~Jh zgrYrmZ~B%r;O6r4!@j|!?L)SF=@7DwHst!#^K4!Hdp_56-Y+?3uTeEBsS|Kze(7g> z=^f%*^LOd0Z+gCg+aKKK)I0X7GI{~OjzgcA`x%5EI3N8Hu3AEMJ32 zdm_yo7vz%(W24uEyX@E`9c{EP)CwXc|Ec5oHjQc>bN&EMB=Pe@*MXP6q}8si zuT>LFkpZoG@6OEJOxWTD1HqleIZLDyDPB zIm^-Lftq8HIfFk38G?#k*FG~>^w8czCDwyoV1@2CeI9AAw-p`t~ zA_dtrJBQd2AJt)?hF2sK-&cWp&HKb}0{xrD>)Cj3&g`&%s@>9RS<_9V6Dvx)lq1pISZ->lj^ zjsBeaRttz&sqG+y(MYEYBqaIP-$;?4iU{J30U4`1^tLPxh^^6Y?Px|`v0wvIQm2Vc zgM*tWtY%>WD|(*rm?fCWGEb+e5mq-prM{Q@1mDLRdux(jE4V%X1q$ zvyA>AhNNsVlk;P}qN!-Ylvc0*gdf>zie$!Mxf-@}(e`n;rW87&B zb?)6o$KITwv@$bhfCiOSi}VrslQw5#US+^R_S>CvJv-@g~Z`d zn*Tmre(vytx(<9bC`?(L8kUU$z?T>12w4e_Nta1+p#kZvOT3mYr&?4Npk^0xhURs~ zbI;mTof9i&gF7Sn>VDctl}*7NJ0aMhW}6lUvmH?2E=P!PBk#%ksWv73(#2Cs_wHx=lCVi73f z`OB^=O~`S=grdVk%<2S!L^?H^Q=zm)fAk_JMZmb8xzd*g&!oV1uXLJsABOr!C{sqA zO8XvYODiUXb`;R|#=FxlRcRWIxOF|{$e!^|eTGlP`(uCiGI`+aKk<_O+#Mge0d~j_ zl;ot3H8TXZQA<{0mRBq-j7CqR=?=Me3)Fy%nS$6F-nZ$B%7vquIN2dR6=o-jft8q)R}+-?~N{@eFe z)XM)HS9Mg=Hw^^qMA2ldTUX$*!v4*65_>|;=_7<>S^YF>DA7yp!o9SwUM~(xir3PTf3WX=AwGr0?TVO%L|U_{YO;n{Bqlp%9XIN#=703 zj*Nj!lSb;H;1`jaPP5LD-Z8r1@L zpgc`my$2ntO~!K#)k#=}x6#sRIs3{zd;%#Ix42DPI+Z??gls}1zTtJ#Ik!_qR6SB^ zBar0*yN=r^Dd5i~(VuIPd{Vc*24%&QFZF-6P1uxD{SuImaVgn=E~IN#LaDc)8gOmk zuVA=TfN-Qb$hJ2z*_zPwG!W2#m#{UMbciTG2Na{ffZDu;N3FwG?YE;P4JKw5MsZ#B z!uKW&{?RhVU+YQs0c17v*c?uusJvf=>kO#%p1R=LzowfS=+(Q`W<>KKDLK>YJu94Z zy(f^{&+U2@v@Om_Z-`oi^ab6HaH3^Ab6H(0bXna9ovn1#5F31hwLgYPI$+h?a)%bc zlZd!X^fi~YDjPdM0UchASi`JUV78B@*AKE@`w4DUnlL$AwGg?;ldz*`9vjf@k7!+v zA<=4|!khT_4_Nc;{sP>3-VboGbS_9VA9SNx?R|Az<#=iDl63N-4{Ih9@99I^aLb7SgA( z8Klu36Y_ikuwa|e&t0h9ZH?Jj-5=a_UuakBSjgj;>?{8GfHAE!*RMg4*c=Ppkn&1d z@wcf}wS3nGdP_{iEA=*bxN$@k0;&PSTH~zDq>IaY_$Q-cenR?+SRYF?X`z)C^_@@d zYHoqQr6tsnic@DMb7R(T5nkI;VnDl!D-=LV+Qa4M{>TIDZQQgCSR4t!H zv1tpDc2QNlD#*x9*)yc8qA>2kF4VGSK7J?RC?cSxv_ao}gh9xs1yuZ+$C;^s3*37B zC-;`$gER1w6x3nk%0HVZ$2G~^DuDbRZ9n%&u`MOcqAK*IML8Y6L@I+0Hxf?(xJ}oP zE%O(N7G5m@!_dR8@`+UWv)Z(;Ga*H4-)tdV;@CNp#we%3>QJ(4@vZpTDu zDZjNlDbN+($yyvdMs)%Ys#kwIBCI|+5jg0HdCg4(5`Z2zbc}EOqn>iq^PMl`q|%i= z^lFQjtANL-&S2%CJKJ-FnTsITF}u9^KIGT#FHO^J-KUnYb2;QE&8P+EV3P&n?T+j; zsFru*uYM?byvL`AQAewh?0$1`>t%I=fUd5|H&qXLGp;_)ALfo-`Moe*O^s<7t#8AT zmGq062u&%FrIVhH#EjfSKl$~jQnQzZFH8>a6us%Mrc$sZc_@1Ucu-!w9)2q9`n7B2 zL^(pH8t*kExjvz&El7xQNa25&=T*-Yus;qvi454hzni$}bPcIpH`q_7O(meriGM5~ z+bwV1SrfLVvZ6_l`ASZn+{GUnc|ji5sYg!r^n$g=pKXg34w2N{b%n2=C*r%^k{;P< zf5L`z`aa2%-0j;G)krDSHA$J-fAFkVD`=rxK=AHN$&p1bk8?I=#wzCip!1B!Fg`-q z@m8%WaMzfBj5Nxf2mRaj%6YaIcp5?gOV0|I@pF+qGlymJuV*I_&Rib2@PZS7I=LOK z#<3S(Lw|}+#7KRJQ2Ufjava4g=Dla$t(|B&n{oD0Yb{9D^T|s4oGmfL4#7gPezm87OQ!YLkVfxM&AO!lDe<)mE8J9S*Z0mDFk(4Y%|9h^tJJ;&Ss8I$(kzX zm&r+$YRXZt1Fw#~iW7-?Z@uLh*?A}*+O(Og6CBk&@kE8+=Qe)p0XmV9!%M4~UT_e7 zU$?%t?W3rHhpe>RXQyfhX8YD*7UbbOCd8l`M4lmtw6 zTK4%kzAWpgbJJozZ?7RYS=A;_Vg_$KY;{ohvqIatMbWnF#Z`ecG7bRk+PJbNekJKyQM76Xb{i^?(O&@9?Tj`*E#KaAB#Bg7mk1qYA_ z0c$Y@CWshi#7u<$D$tC=r6b0jB1uCqXG;pz2Tcv865|Iw==T zcXn2K&sPm6ygA<#x^OwA{c-0w7r`5a@O<+`lKrIoh%z~@mZPhxd^lLguyJ90#N-q_ zie8S!0)4iJZ2@I%0+MpLgZn25Fmo4Fd-SJSb>y#oT=VnDw{Nh^Z;zV&$7g;D?95_r z#%h;0agyDi2!%akS?EeJww9Zk8V78Gvg9w-4WxT>vFHs)CtI9YPWCR|JtoFs9q{4E z-p^v7A~LOLOdDXodYn=2&V%hhsrWby(R$O@u2P=IQy@hQb&!fWPF#7N(p!S32KIZr z4d_%}&)tY6x@Wj1I*-anae)Ye+3jKzFRPaH!;;srHeKY0o`_ z0cmybU74bY?|f$l%1>eiTV!^9JlfRisA0(0M<29WZza0-N(qXCI`s^)PegKeJk~T_ zR%6>ie`^*DUOy}QaAHrp5I_s6U-Z3-)8amCpBTeAK>A<{}u^6S+859O$R~^&LjQ;m-lSXBi$?0P- zTqDzid8xt6fX&OhUY$*?AP?+H}HT)G)s!3p%f?CsyZF|%C*pRAcaEq=H zYnpdOA>Db&y^4yGb~^%MXLj)4`F?CBpQ;6Uedi0j`kgNc_GFAC32eTA?1%)8p!U&6 zEzUc8;SQZMck}Nxs}Ol`NFyq3e8f&$gvu_!$%NTy3mD+-c_SCVbVJMiDNViouho2`uKq}LjRp6do48zs?x(8{K z+aiS~-)ib;+b;oo89@Me5ogA=yxY-=Edza?oxL^oH4-izeuT)AT#dAHgX)rdP$Gw_ zdrvUA*cPMjd@@N@xw1Cu+tSkxWee- z$k1q1kXnUHscdSMN7C}NJ8v4jO^SPZs)iiKX_N&`ee%Sbeoa>y7dFp*CA=|q_zbeV znx{lJupvcSzc}(x8o|vm2v6VkSvind>RmW`6}hO_;oxqe67F9h=@vw|6j_thq>SLM zyo<7n`C1$^imdPE3KC{1gmuuwdBN29JC{9N)KmqWavFbGlNp=7i(AGt(*I@yJs(Uq77mv4_s?#^}=^g|0pZdI{kJU4OGvr-#dI#Yt~?np@2+zoh++O_`Vp$d%4 z;!V5;@eZTjApKzu(l=y={n|LUuC-WFmFtQ2-a{@==Ql;^83FkQhTMNPINvXQ5X^o5oliI+O0Q5T zf#nu>cB9%5I@P{}urQI8T9$RfS|&EB54fiaPedgYoa)L0lUV8E$K$jEC}Dg)9FpGF zs7odyh?4t)6ubK(mNPD^%}@K*va25S%&X}dY8N!S(WHZ~ze4-G)iGoGYA(a%REfPm zW0S#1yThO`!&|j$s%vt!nPIv6s{+!4edS-uI?&i+2@9j90cO)}(pAC*&pY&cA=@LD zBZGBaXTqyxJI1y;6IOnk(AxIw@wJ)%yubezs^z!GlPle*_7{Q$#AFgnkBlGYI|N#6 z`b^3h2rd9MN8%PlmXs3}Ow5fbOjvCBP8-bO?}aV@C8dn|sZs?Gy@l8||NLOZlV7NQ z{fB=*?zg&v%W5B@9(FClEb0!5++-T9EmA8vUU=a`z+1nR9E?lD@uTS{`VfSdg#2AQ zISbputaU*H-J^(;XPUe@++&pe6K)I#UjDBJiRzleb6>=O@m`pI_Y@u5wvfEIUfbGk z=Rdr5w5PaY=k3?+4Q>t9zVrRnb5FqwR+e*_BJg*)>5aY8PrHrkUU@6k_pRT|<}D>r z#I2=cTfNYu;zi+A&#R=Z9oPYhq4ZSz+=ktT%e`;g(4(U)Qj@m%k#1nj-ZEj)bx}!4 z_*j{XiGEk#@b4r?<)EiAN+G6o@DpivV{jE<{+$mWV>XNA>nJ3Y156dqcX^acp3d?rbMN}aB(Q6ZI#}u@K6My3 zSl@xG7ZJTzrF_{+;|oYQ<^|_k`YYdZaGjqAKM{ExlhNu>W35sNmBmY_37LnLd$+;E z+3lQfsgyvy2Fw&O+7BwJhhDWU{DhDn@Lwzt2bArPeHAJESMO4JxAEXjU-3L`d1%`g zH%+o$Q`s$b3kXmk)9~p-EDd6cD{34X^XQfs;yjzvT36E!Ivzi@X9FK`mHMU}71!0< zkfWe_gxydLFzQT?{$qEY_|dS|!h@~HjU#3K1RRXhSsJWZIKzt@fT`v}w-&F*4Bw#9 z-Ze2`Yh%TDmvxU+rH&isGQT=N69U1FZu(D6>`@nZ+*;;qf51`_zFst^TW#3AlVZTz z2__#Xdjyov%^i3k;AZHdR%I0hxA8(x z!(aVh?VVRtlj*w0-OEwOf^0=Zr5#0JV<-a%NZ)%zkQ!;BgVLpi-a?s?Eg%pO0@BGy z3ndWgQZoV}K!8XKC6EY_5<*B2NQ2URUUb>2USC?mAOAoa9$NR+5_igSI&x~+~&52SC3w86^%SJ6BAT2ICn|!-X?YX=3MLi&Qa~%#wkP%9px075mbk+qj8dt|uiY z(c)qgc0jE!m>95qPEWKW86!sapS&hT65zaVz$qEQ(r?SRjz51Qr-PwscKY!PHPs8L zU1vj^)nz?Y#3Z(F@79!6VDhoV>`iHzfsh;@r%r{JE*T09Ae4*ruL_jx*&WM}ODxak zneAV`kbDz^>6E@mLFH5ugf>V-b04DefjJ{yFS&i!?{Sj)Oyc1?D3?W!eAyuspVy*i zo|0h!w434HuUnixsvHU1lKYD4F&rXh){j&DNyuYufPH{I*LfV7WaYRgTZ75)lGDW~ zy`%x*BL@6a@0_diJ~)vaS>Jt+%luh3#OC?Eyh#x0-uE+Tc9X}N?G$;&cfzd)JpIcl z#xVM3HtG=h*Ur>A7I30S?xWO6uc^lHlqh-PnaRK$mJB@~ec?1kh0gyBdw;Xg7`2|o zU+>dCd7uH@ZY+Fbb&Y3__Dou?{D|J3<9|Gs70P~-oCJPK9*p_ju*U6K#avPjoTYT! z`fBzxH-$wz{=Cd`Y8-2VW6#zImDWn3yVDfBF8d-R4>obdo6TBNooy71kJn0YtxL?X zeR<1C`=}?|BHQ_5W7WH-H0P=pbnoajhZUa0{A4W>JYW_HkI7H;@FXm(mFZ+!5pCt2 z@e8W^WO}5q5Lwhcj!DD^tNK4GyKpZoiSJv$U&z$NiPe4XD8&A{Gsn^o)XhFHNA4#7 zYKM9_cCLreh!t0|D?ZvW^=PxN!Za)gtD8IUrlmYU`+MZZEmI_}rZ@we^ltwRoNGmv zEdDp~^nrm3Zuw7#X`aL9$N(Yy^Xg?a@vHpuuX?YcA&Ld2op)mIM1FAm+25xY)45{5 zYefqiY&24mji*$f*@GWF%Jl=FnB;+XSWaA-f$rHP4GoJl7^==n$ko% zX&)sU&juGVA(~=hA6|rDk28zPChu@1qG}V7dF4g!@Q7LRN=mzButpJ0WM0cD6Qtzc zq8rR)b-d?HHwXw^k7>dSg7Cbn1K()Q9PyZ^ESyHZ?2?{TSW02r!x^ttjyOPBmUttJ(_+znRFHEqJFiE|iPP{)Bg(nylbifT zvQPAXL0 zB;Jb}kpYjb(F`PMCM!8d(l@1g>Z05X9h)}G+WQ)#9(aX6dL!e=S&*-uZc#jXMA&@& z)AVc-^o~=Ms&odg0}beL>TzU>xLKi+Uzm$*q3Br;If+y4XIRj(L$0A z_V?(!dyFo8Cq1)V{GfJguFF!wbW)HCxgMlUdixc% z+{Qo|im4jPL(V(XqkmGr1T+|!`)TT|<6si*YER@x4Di1D^w+pF*)~sX-r|-7NlzY2 zVZx;vJl3_QL0tbbTtAuMyM{*A(L`(v%XD~COsvoMYz~h<4*y(>kjD-t&Z3vPrrs_*{eDIStQY2951omx zTF**;w(t4iUnbI{9LWp=!>^`0An1n{qEE~k*eiM>tpEw0smc`9t9X(}q&p_SdUUle zVIh_+9`nE~LOd3Wcx56iipy?s{mA!@jj4==qhbrZw~{j;FL8k}s*+lV*vG&nW&6Ba zTJDZ3u%HxSMWo{lMjzmUvCcmFIB&miFk=5XZjoGW6CGC-12Pa1G0ex2(!Zrs4d^Fb z(cbF^{zXHXy#^5>y%0(W+)PCxcgz(xC#Ys^#7sTL(-YcoC*>dh9HZkTxCyLet%5d5 zJ$I3FY1pCVTQ;UNdo0EOlVtq^qUSqKy(Pnk)X z=vU;Q1Q@oGCBFc)DgHS6*de>{Fi5(^qb2Ub<9-xn-Tic%uJVPBAt!%7180yO2(Af$ zLx2AN+I2xsqU%Jlp?)53E%fBXA##1|L1F{Rayqw>t>V{xgSJlo?8&d=FZf+JEmhg& zGs`}m`S{QOq;WrkU2v_qo%j#VZ_gVSHu;(uA;vW`vpXo2Z5H6UHo&cU%lv+1^5rqJ zSWO9W-**mO(gjV?)mANv1r*c^nzk)s-gg!_+Ia}y!KY2L>pSjP=)m#K3+Yt<{qJWg zNMpunxu>F2;fT`CvM2M>H-h4cUm@G*TtT10eDc#JbYB!5gIvIW@-^<7o@rrL&5Mrx zfM}oK5;6$s?pCEk1AeDgxqfm(5MHbWqd_GiI_!;9-vIQY%1$s1hd51HsE^ksML-nfa#2_x<906jQ? zX`6e8U@94;XWJ;nD2Nv0x|6c{+=Kf(v8y>hGm?c3Yuy$<8W|05;7DwcCU0vm-OM~C zRysXCkm7pb@671eDU=F5LJ^Civ3o@&2;B0*nO1C3PO};F(<*I~v1Xy%N+9o~Dqhyc zSJ~eK=W$iE#zKy2lyW3hZ4}JdfJ?nRe%dg{yyf~p&A=V#0bD0-OF6V=zT!P{ZsXMX z>s2$sF}of#Wi@q4i0D5a^lUxI`!4B4FqMXV`?Or$6WXe1#u_2<^Zdj_YxV4~J94Xw z#MpMD$j82G(YQi6$H{BtQs5@uax0l~c)awQS;nMJa)Lrp_x259^7ma^yy7-)cVCwv z$bh=*KV`oP209aF#bg1+y;E%uOn*y2Ysh-pD^?{r1MER*PW4cGYAhRdGa*&ve{aKn7k*amVm%HR+tw)$I-+z$Jx(E0$eIu zca36saV5&H985c&s9Cgj*X6HZp!VBM4bwYb=I_F-v>XnN5!>%94D+6%xh-Jrh0a{U49XH6}iHy zDT|7;sVR6(GNs{#GBhcHPT@$p*R}FR^^MuD42mF2D6*ytNv_Dw;w3m|B~BDDnMM0h z!9}qd06hc~6=AQR!9KdN1Idh*G&31+jfgY%AZiF@S6js+Bb$3|uv-9cZ|@{K7u6cO z;>iP9N85eV_^od z6>Fpb*24&DtgyqzUym=^v@05A{JcmoCR7QyvLCv^G?fIH4XyD>^0!pIFpqo>EP2Dj$p#+yEO$gI*&y(1*0kfms%|fze_NOdW%e)XscVoz zKlyP}F5|!(6Qs;{jb6fa?sG0FA@Ik(2t^TWjA0^JCno4hgzG%?RzZQN_4IfiG@5y)!&bjM)oEAX-2QON zlHi+idCVC=@7>f76~9C%=v195PWVO%|NH8Q#p%qLwXb)08yeVvL$punCKT`IC=iM5 z-_J~2*3GQVjVuAaRSiy4r15Qo3Da2{hOfbe4Sq_xuXSoD38x+RbMmtE5DmM$?C^Xm z_ua-2LXA|&fQ@k_%jHUH$vaFzgC;m#)eN&Z=dO4XO-q4u*$M+k*`g z60co~O6NHBbeF7H;EzOR->GF*2ROE*xm!#*XUoRWb5aXd#U;kR8pqfLB8@ZZpbi~k z6YvPk2Vg3IAuYVlIkAQxJDW@z4&JWac33;2s#ovsiRWqC%0$$YoClpyZwJXMOgm=T zOQa7#8wMKv!dWT|X}epH8W~a9R@k5G4fX~B>=C}2qt5~!y$d4C!Sj&V!YyDv!!U%b z)(ad8T`THf<6?^{(4`_$VN&|?%lKUBcF1)X6ZeY$f?vF;LD))*v5-un&TBNs&)$bU zAAGFm43^)4fj)f$6&{>0-d)ig=Gk_V?jAg)%8rm}Ns6+ff*u~4286}Oc&d38!S^_cb;z=NeER+-6me9A zn!ozKK0#6cnE?5o3E6S^J!(Z}Gr%2PN72317r*>;wFd0TOU`}RK9nrd=z;= zN@tLdnkWZGkvUx?oczHlnw6iiac&2e+7xwdHbys8L*npG$CT$flmTrFdbe#}(DVu( zwxIH*5(k84%qgR92>0aa$W2}!&26+Xe!zvbiSpfD<21?h%Sv5?o^Ek1ycH)^Mpsgs z_=%Udm0^?lS3%1;j_1I?;+M?0QhXBTvFpeGhah7m#Hk--CK1;vfdmvv< zul1Iucy|xG0{;l>Z%S=aP%UVKQR4^H-3kHcbzIbSvNWZ0BAZ^%jBn2v1&kg6)dn}v zNF40GNwkW(F3{sP`P4Mat}J$cwx6_TWPB=ht!ud{a~JJEW=UV2i`KK&6Y6CAom3lm zz!Iy2f-~!eMwV;;-7{sI-3obvz&CqG17^GnWo?72hciH%lt3@;gC^&lhOM~V>*l7z zGM1V1QwnG|p4Di-hj6)-;}}i16eNQL6Xa(~kY*+BRY`FZ_``_9je=YwP(nVK( qiHG=q&U*PDeSSQDDDXpp9}4_X;D-V~6!@XQ4+Z{H3jFQ+(tiVmB. + */ + +#ifndef CA_PRECOMP_HH +#define CA_PRECOMP_HH + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + +#include + +namespace asio = boost::asio; + +#include +#include +#include + +#endif diff --git a/agent/proto/agent.proto b/agent/proto/agent.proto new file mode 100644 index 00000000000..5a9190d2c12 --- /dev/null +++ b/agent/proto/agent.proto @@ -0,0 +1,91 @@ +/* + * Copyright 2024 Centreon (https://www.centreon.com/) + * + * 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. + * + * For more information : contact@centreon.com + * + */ + +syntax = "proto3"; + +import "opentelemetry/proto/collector/metrics/v1/metrics_service.proto"; + +package com.centreon.agent; + +// Agent connects to engine +service AgentService { + rpc Export(stream MessageFromAgent) returns (stream MessageToAgent) {} +} + + +// Engine connects to agent (reversed connection) +service ReversedAgentService { + rpc Import(stream MessageToAgent) returns (stream MessageFromAgent) {} +} + + +//Message sent to agent reversed connection or not +message MessageToAgent { + oneof content { + AgentConfiguration config = 1; + opentelemetry.proto.collector.metrics.v1.ExportMetricsServiceResponse otel_response = 2; + } +} + +//Message sent to Engine reversed connection or not +message MessageFromAgent { + oneof content { + AgentInfo init = 1; + opentelemetry.proto.collector.metrics.v1.ExportMetricsServiceRequest otel_request = 2; + } +} + +//Binary version Engine or Agent +message Version { + uint32 major = 1; + uint32 minor = 2; + uint32 patch = 3; +} + +//First message sent to engine +message AgentInfo { + //host name of the computer of the agent + string host=1; + Version centreon_version=2; +} + +//Agent configuration sent by Engine +message AgentConfiguration { + Version centreon_version = 1; + //delay between 2 checks of one service, so we will do all check in that period (in seconds) + uint32 check_interval = 2; + //limit the number of active checks in order to limit charge + uint32 max_concurrent_checks = 3; + //period of metric exports (in seconds) + uint32 export_period = 4; + //after this timeout, process is killed (in seconds) + uint32 check_timeout = 5; + //if true we store nagios other metrics (min max warn crit in Exemplar otel objects) + bool use_exemplar = 6; + //list of services with their commands + repeated Service services = 7; +} + +//Service (poller configuration definition) +message Service { + //empty if host check + string service_description = 1; + string command_name = 2; + string command_line = 3; +} \ No newline at end of file diff --git a/agent/scripts/centagent.service.in b/agent/scripts/centagent.service.in new file mode 100644 index 00000000000..d95249cbb47 --- /dev/null +++ b/agent/scripts/centagent.service.in @@ -0,0 +1,33 @@ +# +# Copyright 2016 Centreon +# +# 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. +# +# For more information : contact@centreon.com +# + +[Unit] +Description=Centreon Agent +PartOf=centreon.service +After=centreon.service +ReloadPropagatedFrom=centreon.service + +[Service] +ExecStart=@CMAKE_INSTALL_FULL_BINDIR@/@CENTREON_AGENT@ --config-file @PREFIX_AGENT_CONF@/@CENTREON_AGENT@.cfg +ExecReload=/bin/kill -HUP $MAINPID +Type=simple +User=centreon-agent + +[Install] +WantedBy=default.target + diff --git a/agent/src/main.cc b/agent/src/main.cc new file mode 100644 index 00000000000..aa20e99483f --- /dev/null +++ b/agent/src/main.cc @@ -0,0 +1,239 @@ +/** + * Copyright 2024 Centreon + * + * 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. + * + * For more information : contact@centreon.com + */ + +#include +#include +#include + +#include "com/centreon/common/grpc/grpc_config.hh" + +namespace po = boost::program_options; + +std::shared_ptr g_io_context = + std::make_shared(); + +std::shared_ptr g_logger; + +static asio::signal_set _signals(*g_io_context, SIGTERM, SIGUSR1, SIGUSR2); + +static void signal_handler(const boost::system::error_code& error, + int signal_number) { + if (!error) { + switch (signal_number) { + case SIGTERM: + SPDLOG_LOGGER_INFO(g_logger, "SIGTERM received"); + g_io_context->stop(); + break; + case SIGUSR2: + SPDLOG_LOGGER_INFO(g_logger, "SIGUSR2 received"); + if (g_logger->level()) { + g_logger->set_level( + static_cast(g_logger->level() - 1)); + } + break; + case SIGUSR1: + SPDLOG_LOGGER_INFO(g_logger, "SIGUSR1 received"); + if (g_logger->level() < spdlog::level::off) { + g_logger->set_level( + static_cast(g_logger->level() + 1)); + } + break; + } + _signals.async_wait(signal_handler); + } +} + +static std::string read_crypto_file(const char* field, + const po::variables_map& vm) { + if (!vm.count(field)) { + return {}; + } + std::string path = vm[field].as(); + try { + std::ifstream file(path); + if (file.is_open()) { + std::stringstream ss; + ss << file.rdbuf(); + file.close(); + return ss.str(); + } + } catch (const std::exception& e) { + SPDLOG_LOGGER_ERROR(g_logger, "{} fail to read {}: {}", field, path, + e.what()); + } + return ""; +} + +int main(int argc, char* argv[]) { + po::options_description desc("Allowed options"); + desc.add_options()("help,h", "produce help message")( + "log-level", po::value()->default_value("info"), + "log level, may be critical, error, info, debug, trace")( + "endpoint", po::value(), + "connect or listen endpoint ex: 192.168.1.1:4443")( + "config-file", po::value(), + "command line options in a file with format key=value")( + "encryption", po::value()->default_value(false), + "true if encryption")("certificate", po::value(), + "path of the certificate file")( + "private_key", po::value(), + "path of the certificate key file")( + "ca_certificate", po::value(), + "path of the certificate authority file")( + "ca_name", po::value(), "hostname of the certificate")( + "host", po::value(), + "host supervised by this agent (if none given we use name of this " + "host)")("grpc-streaming", po::value()->default_value(true), + "this agent connect to engine in streaming mode")( + "reversed-grpc-streaming", po::value()->default_value(false), + "this agent accept connection from engine in streaming mode")( + "log-type", po::value()->default_value("stdout"), + "type of logger: stdout, file")( + "log-file", po::value(), + "log file used in case of log_type = file")( + "log-max-file-size", po::value(), + "max size of log file in Mo before rotate")( + "log-max-files", po::value(), "max log files"); + + po::variables_map vm; + + try { + po::store(po::parse_command_line(argc, argv, desc), vm); + po::notify(vm); + + if (vm.count("help")) { + std::cout << desc << "\n"; + return 1; + } + + if (vm.count("config-file")) { + po::store(po::parse_config_file( + vm["config-file"].as().c_str(), desc), + vm); + } + + } catch (const std::exception& e) { + SPDLOG_ERROR("fail to parse arguments {}", e.what()); + return 2; + } + + SPDLOG_INFO( + "centreon-agent start, you can decrease log verbosity by kill -USR1 " + "{} or increase by kill -USR2 {}", + getpid(), getpid()); + + const std::string logger_name = "centreon-agent"; + + std::string log_type = vm["log-type"].as(); + + if (log_type == "file") { + try { + if (vm.count("log-file")) { + if (vm.count("log-max-file-size") && vm.count("log-max-files")) { + g_logger = spdlog::rotating_logger_mt( + logger_name, vm["log-file"].as(), + vm["log-max-file-size"].as(), + vm["log-max-files"].as()); + } else { + SPDLOG_INFO( + "no log-max-file-size option or no log-max-files option provided " + "=> logs will not be rotated by centagent"); + g_logger = spdlog::basic_logger_mt(logger_name, + vm["log-file"].as()); + } + } else { + SPDLOG_ERROR( + "log-type=file needs the option log-file => log to stdout"); + g_logger = spdlog::stdout_color_mt(logger_name); + } + } catch (const std::exception& e) { + SPDLOG_CRITICAL("Can't log to {}: {}", vm["log-file"].as(), + e.what()); + return 2; + } + } else { + g_logger = spdlog::stdout_color_mt(logger_name); + } + + spdlog::set_level(spdlog::level::info); + if (vm.count("log-level")) { + std::string log_level = vm["log-level"].as(); + if (log_level == "critical") { + g_logger->set_level(spdlog::level::critical); + } else if (log_level == "error") { + g_logger->set_level(spdlog::level::err); + } else if (log_level == "debug") { + g_logger->set_level(spdlog::level::debug); + } else if (log_level == "trace") { + g_logger->set_level(spdlog::level::trace); + } + } + + SPDLOG_LOGGER_INFO(g_logger, + "centreon-agent start, you can decrease log " + "verbosity by kill -USR1 {} or increase by kill -USR2 {}", + getpid(), getpid()); + std::shared_ptr conf; + std::string supervised_host; + + try { + // ignored but mandatory because of forks + _signals.add(SIGPIPE); + + _signals.async_wait(signal_handler); + if (!vm.count("endpoint")) { + SPDLOG_CRITICAL( + "endpoint param is mandatory (represents where to connect or where " + "to listen example: 127.0.0.1:4317)"); + return -1; + } + std::string host_port = vm["endpoint"].as(); + std::string ca_name; + if (vm.count("ca_name")) { + ca_name = vm["ca_name"].as(); + } + + if (vm.count("host")) { + supervised_host = vm["host"].as(); + } + if (supervised_host.empty()) { + supervised_host = boost::asio::ip::host_name(); + } + + conf = std::make_shared( + host_port, vm["encryption"].as(), + read_crypto_file("certificate", vm), + read_crypto_file("private_key", vm), + read_crypto_file("ca_certificate", vm), ca_name, true, 30); + + } catch (const std::exception& e) { + SPDLOG_CRITICAL("fail to parse input params: {}", e.what()); + return -1; + } + + try { + g_io_context->run(); + } catch (const std::exception& e) { + SPDLOG_LOGGER_CRITICAL(g_logger, "unhandled exception: {}", e.what()); + return -1; + } + + SPDLOG_LOGGER_INFO(g_logger, "centreon-agent end"); + + return 0; +} \ No newline at end of file diff --git a/agent/test/CMakeLists.txt b/agent/test/CMakeLists.txt new file mode 100644 index 00000000000..1b73659a6a5 --- /dev/null +++ b/agent/test/CMakeLists.txt @@ -0,0 +1,56 @@ +# +# Copyright 2024 Centreon +# +# 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. +# +# For more information : contact@centreon.com +# + + + +add_executable(ut_agent + test_main.cc + ${TESTS_SOURCES}) + +add_test(NAME tests COMMAND ut_agent) + +set_target_properties( + ut_agent + PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/tests + RUNTIME_OUTPUT_DIRECTORY_DEBUG ${CMAKE_BINARY_DIR}/tests + RUNTIME_OUTPUT_DIRECTORY_RELEASE ${CMAKE_BINARY_DIR}/tests + RUNTIME_OUTPUT_DIRECTORY_RELWITHDEBINFO ${CMAKE_BINARY_DIR}/tests + RUNTIME_OUTPUT_DIRECTORY_MINSIZEREL ${CMAKE_BINARY_DIR}/tests) + + +target_link_libraries(ut_agent PRIVATE + centagent_lib + centreon_common + GTest::gtest + GTest::gtest_main + GTest::gmock + GTest::gmock_main + -L${Boost_LIBRARY_DIR_RELEASE} + boost_program_options + -L${PROTOBUF_LIB_DIR} + gRPC::gpr gRPC::grpc gRPC::grpc++ gRPC::grpc++_alts + fmt::fmt pthread + crypto ssl + ) + +add_dependencies(ut_agent centreon_common centagent_lib) + +set_property(TARGET ut_agent PROPERTY POSITION_INDEPENDENT_CODE ON) + +target_precompile_headers(ut_agent PRIVATE ${PROJECT_SOURCE_DIR}/precomp_inc/precomp.hh) + diff --git a/agent/test/test_main.cc b/agent/test/test_main.cc new file mode 100644 index 00000000000..919a087af50 --- /dev/null +++ b/agent/test/test_main.cc @@ -0,0 +1,59 @@ +/** + * Copyright 2024 Centreon + * + * 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. + * + * For more information : contact@centreon.com + */ + +#include + +std::shared_ptr g_io_context( + std::make_shared()); + +class CentreonEngineEnvironment : public testing::Environment { + public: + void SetUp() override { + setenv("TZ", ":Europe/Paris", 1); + return; + } + + void TearDown() override { return; } +}; + +/** + * Tester entry point. + * + * @param[in] argc Argument count. + * @param[in] argv Argument values. + * + * @return 0 on success, any other value on failure. + */ +int main(int argc, char* argv[]) { + // GTest initialization. + testing::InitGoogleTest(&argc, argv); + + spdlog::default_logger()->set_level(spdlog::level::trace); + + // Set specific environment. + testing::AddGlobalTestEnvironment(new CentreonEngineEnvironment()); + + auto worker{asio::make_work_guard(*g_io_context)}; + std::thread asio_thread([]() { g_io_context->run(); }); + // Run all tests. + int ret = RUN_ALL_TESTS(); + g_io_context->stop(); + asio_thread.join(); + spdlog::shutdown(); + return ret; +} diff --git a/broker/CMakeLists.txt b/broker/CMakeLists.txt index ad2373471fe..1e4aefc5057 100644 --- a/broker/CMakeLists.txt +++ b/broker/CMakeLists.txt @@ -484,6 +484,8 @@ target_link_libraries( "-Wl,--no-whole-archive" nlohmann_json::nlohmann_json fmt::fmt + -L${Boost_LIBRARY_DIR_RELEASE} + boost_program_options -L${PROTOBUF_LIB_DIR} "-Wl,--whole-archive" gRPC::grpc++ diff --git a/clib/inc/com/centreon/process.hh b/clib/inc/com/centreon/process.hh index 475aef8e7ac..9948208a690 100644 --- a/clib/inc/com/centreon/process.hh +++ b/clib/inc/com/centreon/process.hh @@ -105,7 +105,7 @@ class process { bool in_stream = true, bool out_stream = true, bool err_stream = true); - virtual ~process() noexcept; + virtual ~process(); process(const process&) = delete; process& operator=(const process&) = delete; // void enable_stream(stream s, bool enable); @@ -129,6 +129,6 @@ class process { void set_timeout(bool timeout); }; -} +} // namespace com::centreon #endif // !CC_PROCESS_POSIX_HH diff --git a/clib/src/process.cc b/clib/src/process.cc index d5ef993b9bd..cf0475c5b67 100644 --- a/clib/src/process.cc +++ b/clib/src/process.cc @@ -63,7 +63,7 @@ process::process(process_listener* listener, /** * Destructor. */ -process::~process() noexcept { +process::~process() { std::unique_lock lock(_lock_process); _kill(SIGKILL); _cv_process_running.wait(lock, [this] { return !_is_running(); }); diff --git a/common/inc/com/centreon/common/process.hh b/common/inc/com/centreon/common/process.hh index 74562bfc18b..caca6a1dbc9 100644 --- a/common/inc/com/centreon/common/process.hh +++ b/common/inc/com/centreon/common/process.hh @@ -38,9 +38,6 @@ struct boost_process; * locked */ class process : public std::enable_shared_from_this { - std::shared_ptr _io_context; - std::shared_ptr _logger; - std::string _exe_path; std::vector _args; @@ -62,6 +59,9 @@ class process : public std::enable_shared_from_this { void stderr_read(); protected: + std::shared_ptr _io_context; + std::shared_ptr _logger; + char _stdout_read_buffer[0x1000] ABSL_GUARDED_BY(_protect); char _stderr_read_buffer[0x1000] ABSL_GUARDED_BY(_protect); @@ -132,10 +132,10 @@ process::process(const std::shared_ptr& io_context, const std::string_view& exe_path, string_iterator arg_begin, string_iterator arg_end) - : _io_context(io_context), - _logger(logger), - _exe_path(exe_path), - _args(arg_begin, arg_end) {} + : _exe_path(exe_path), + _args(arg_begin, arg_end), + _io_context(io_context), + _logger(logger) {} /** * @brief Construct a new process::process object @@ -151,10 +151,10 @@ process::process(const std::shared_ptr& io_context, const std::shared_ptr& logger, const std::string_view& exe_path, const args_container& args) - : _io_context(io_context), - _logger(logger), - _exe_path(exe_path), - _args(args) {} + : _exe_path(exe_path), + _args(args), + _io_context(io_context), + _logger(logger) {} /** * @brief Construct a new process::process object @@ -171,7 +171,7 @@ process::process(const std::shared_ptr& io_context, const std::shared_ptr& logger, const std::string_view& exe_path, const std::initializer_list& args) - : _io_context(io_context), _logger(logger), _exe_path(exe_path) { + : _exe_path(exe_path), _io_context(io_context), _logger(logger) { _args.reserve(args.size()); for (const auto& str : args) { _args.emplace_back(str); diff --git a/common/src/process.cc b/common/src/process.cc index c66e1cbcc21..9e0282b38fb 100644 --- a/common/src/process.cc +++ b/common/src/process.cc @@ -19,6 +19,7 @@ #include #include +#include #include "process.hh" @@ -63,8 +64,7 @@ process::process(const std::shared_ptr& io_context, const std::shared_ptr& logger, const std::string_view& cmd_line) : _io_context(io_context), _logger(logger) { - auto split_res = - absl::StrSplit(cmd_line, absl::ByAnyChar(" \t"), absl::SkipEmpty()); + auto split_res = boost::program_options::split_unix(std::string(cmd_line)); if (split_res.begin() == split_res.end()) { SPDLOG_LOGGER_ERROR(_logger, "empty command line:\"{}\"", cmd_line); throw exceptions::msg_fmt("empty command line:\"{}\"", cmd_line); diff --git a/common/tests/CMakeLists.txt b/common/tests/CMakeLists.txt index 1bd982a7743..0c3a0c30d16 100644 --- a/common/tests/CMakeLists.txt +++ b/common/tests/CMakeLists.txt @@ -50,6 +50,8 @@ target_link_libraries( ut_common PRIVATE centreon_common centreon_http + -L${Boost_LIBRARY_DIR_RELEASE} + boost_program_options re2::re2 log_v2 crypto diff --git a/packaging/centreon-agent-debuginfo.yaml b/packaging/centreon-agent-debuginfo.yaml new file mode 100644 index 00000000000..f2bf9933c05 --- /dev/null +++ b/packaging/centreon-agent-debuginfo.yaml @@ -0,0 +1,42 @@ +name: "centreon-agent-debuginfo" +arch: "${ARCH}" +platform: "linux" +version_schema: "none" +version: "${VERSION}" +release: "${RELEASE}${DIST}" +section: "default" +priority: "optional" +maintainer: "Centreon " +description: | + Debuginfo package for centagent. + Commit: @COMMIT_HASH@ +vendor: "Centreon" +homepage: "https://www.centreon.com" +license: "Apache-2.0" + +contents: + - src: "../build/agent/centagent.debug" + dst: "/usr/lib/debug/usr/bin/centagent.debug" + file_info: + mode: 0644 + +overrides: + rpm: + depends: + - centreon-agent = ${VERSION}-${RELEASE}${DIST} + deb: + depends: + - centreon-agent (= ${VERSION}-${RELEASE}${DIST}) + conflicts: + - centreon-agent-dbgsym + replaces: + - centreon-agent-dbgsym + provides: + - centreon-agent-dbgsym + +rpm: + summary: Debuginfo package for centagent. + compression: zstd + signature: + key_file: ${RPM_SIGNING_KEY_FILE} + key_id: ${RPM_SIGNING_KEY_ID} diff --git a/packaging/centreon-agent-selinux.yaml b/packaging/centreon-agent-selinux.yaml new file mode 100644 index 00000000000..068b0004b24 --- /dev/null +++ b/packaging/centreon-agent-selinux.yaml @@ -0,0 +1,40 @@ +name: "centreon-agent-selinux" +arch: "${ARCH}" +platform: "linux" +version_schema: "none" +version: "${VERSION}" +release: "${RELEASE}${DIST}" +section: "default" +priority: "optional" +maintainer: "Centreon " +description: | + SELinux context for centreon-agent +vendor: "Centreon" +homepage: "https://centreon.com" +license: "Apache-2.0" + +depends: + - policycoreutils + - centreon-common-selinux +replaces: + - centreon-agent-selinux-debuginfo +conflicts: + - centreon-agent-selinux-debuginfo +provides: + - centreon-agent-selinux-debuginfo + +contents: + - src: "../selinux/centreon-agent/centreon-agent.pp" + dst: "/usr/share/selinux/packages/centreon/centreon-agent.pp" + file_info: + mode: 0655 + +scripts: + postinstall: ./scripts/centreon-agent-selinux-postinstall.sh + preremove: ./scripts/centreon-agent-selinux-preremove.sh + +rpm: + summary: SELinux context for centreon-agent + signature: + key_file: ${RPM_SIGNING_KEY_FILE} + key_id: ${RPM_SIGNING_KEY_ID} diff --git a/packaging/centreon-agent.yaml b/packaging/centreon-agent.yaml new file mode 100644 index 00000000000..a5f34d2406a --- /dev/null +++ b/packaging/centreon-agent.yaml @@ -0,0 +1,66 @@ +name: "centreon-agent" +arch: "${ARCH}" +platform: "linux" +version_schema: "none" +version: "${VERSION}" +release: "${RELEASE}${DIST}" +section: "default" +priority: "optional" +maintainer: "Centreon " +description: | + This software is an agent used to executes commands on remote computers as nsclient does. + Commit: @COMMIT_HASH@ +vendor: "Centreon" +homepage: "https://www.centreon.com" +license: "Apache-2.0" + +contents: + - src: "../agent/conf/centagent.cfg" + dst: "/etc/centreon-agent/centagent.cfg" + type: config|noreplace + file_info: + mode: 0664 + owner: centreon-agent + group: centreon-agent + + - src: "../agent/scripts/centagent.service" + dst: "/usr/lib/systemd/system/centagent.service" + file_info: + mode: 0644 + packager: rpm + - src: "../agent/scripts/centagent.service" + dst: "/lib/systemd/system/centagent.service" + file_info: + mode: 0644 + packager: deb + + - src: "../build/agent/centagent" + dst: "/usr/bin/centagent" + + - dst: "/etc/centreon-agent" + type: dir + file_info: + mode: 0775 + owner: centreon-agent + group: centreon-agent + + - dst: "/var/log/centreon-agent" + type: dir + file_info: + mode: 0755 + owner: centreon-agent + group: centreon-agent + +scripts: + preinstall: ./scripts/centreon-agent-daemon-preinstall.sh + postinstall: ./scripts/centreon-agent-daemon-postinstall.sh + preremove: ./scripts/centreon-agent-daemon-preremove.sh + postremove: ./scripts/centreon-agent-daemon-postremove.sh + + +rpm: + summary: Centreon Collect Agent. It can be used to execute remotely plugins + compression: zstd + signature: + key_file: ${RPM_SIGNING_KEY_FILE} + key_id: ${RPM_SIGNING_KEY_ID} diff --git a/packaging/scripts/centreon-agent-daemon-postinstall.sh b/packaging/scripts/centreon-agent-daemon-postinstall.sh new file mode 100644 index 00000000000..87bebfa5649 --- /dev/null +++ b/packaging/scripts/centreon-agent-daemon-postinstall.sh @@ -0,0 +1,25 @@ +#!/bin/sh + +startCentagent() { + systemctl daemon-reload ||: + systemctl unmask centagent.service ||: + systemctl preset centagent.service ||: + systemctl enable centagent.service ||: + systemctl restart centagent.service ||: +} + +# on debian, it is needed to recreate centreon-agent user at each upgrade because it is removed on postrm step on versions < 23.10 +if [ "$1" = "configure" ] ; then + if [ ! "$(getent passwd centreon-agent)" ]; then + adduser --system --group --shell /bin/bash --no-create-home centreon-agent + fi + if [ "$(getent passwd nagios)" ]; then + usermod -a -G centreon-agent nagios + fi + chown -R centreon-agent:centreon-agent \ + /etc/centreon-agent \ + /var/log/centreon-agent +fi + +startCentagent + diff --git a/packaging/scripts/centreon-agent-daemon-postremove.sh b/packaging/scripts/centreon-agent-daemon-postremove.sh new file mode 100644 index 00000000000..d550f7b752f --- /dev/null +++ b/packaging/scripts/centreon-agent-daemon-postremove.sh @@ -0,0 +1,8 @@ +#!/bin/sh + +case "$1" in + purge) + deluser centreon-agent || : + delgroup centreon-agent || : + ;; +esac diff --git a/packaging/scripts/centreon-agent-daemon-preinstall.sh b/packaging/scripts/centreon-agent-daemon-preinstall.sh new file mode 100644 index 00000000000..6a0afa62e14 --- /dev/null +++ b/packaging/scripts/centreon-agent-daemon-preinstall.sh @@ -0,0 +1,10 @@ +#!/bin/sh + +if ! id centreon-agent > /dev/null 2>&1; then + useradd -r centreon-agent > /dev/null 2>&1 +fi + +if id -g nagios > /dev/null 2>&1; then + usermod -a -G centreon-agent nagios +fi + diff --git a/packaging/scripts/centreon-agent-daemon-preremove.sh b/packaging/scripts/centreon-agent-daemon-preremove.sh new file mode 100644 index 00000000000..e156b0c586e --- /dev/null +++ b/packaging/scripts/centreon-agent-daemon-preremove.sh @@ -0,0 +1,3 @@ +#!/bin/sh + +systemctl stop centagent.service ||: diff --git a/packaging/scripts/centreon-agent-selinux-postinstall.sh b/packaging/scripts/centreon-agent-selinux-postinstall.sh new file mode 100644 index 00000000000..f58e756b6d5 --- /dev/null +++ b/packaging/scripts/centreon-agent-selinux-postinstall.sh @@ -0,0 +1,25 @@ +#!/bin/sh + +install() { + semodule -i /usr/share/selinux/packages/centreon/centreon-engine.pp > /dev/null 2>&1 || : +} + +upgrade() { + semodule -i /usr/share/selinux/packages/centreon/centreon-engine.pp > /dev/null 2>&1 || : +} + +action="$1" +if [ "$1" = "configure" ] && [ -z "$2" ]; then + action="install" +elif [ "$1" = "configure" ] && [ -n "$2" ]; then + action="upgrade" +fi + +case "$action" in + "1" | "install") + install + ;; + "2" | "upgrade") + upgrade + ;; +esac diff --git a/packaging/scripts/centreon-agent-selinux-preremove.sh b/packaging/scripts/centreon-agent-selinux-preremove.sh new file mode 100644 index 00000000000..0b68b4805d5 --- /dev/null +++ b/packaging/scripts/centreon-agent-selinux-preremove.sh @@ -0,0 +1,5 @@ +#!/bin/sh + +if [ "$1" -lt "1" ]; then # Final removal + semodule -r centreon-agent > /dev/null 2>&1 || : +fi diff --git a/selinux/centreon-agent/centreon-agent.fc b/selinux/centreon-agent/centreon-agent.fc new file mode 100644 index 00000000000..7b127e8eb07 --- /dev/null +++ b/selinux/centreon-agent/centreon-agent.fc @@ -0,0 +1 @@ +/usr/bin/centagent -- gen_context(system_u:object_r:centreon_agent_exec_t,s0) diff --git a/selinux/centreon-agent/centreon-agent.if b/selinux/centreon-agent/centreon-agent.if new file mode 100644 index 00000000000..fbeed8af430 --- /dev/null +++ b/selinux/centreon-agent/centreon-agent.if @@ -0,0 +1 @@ +##

Centreon Agent monitoring agent. diff --git a/selinux/centreon-agent/centreon-agent.te b/selinux/centreon-agent/centreon-agent.te new file mode 100644 index 00000000000..9bc1e0013a2 --- /dev/null +++ b/selinux/centreon-agent/centreon-agent.te @@ -0,0 +1,170 @@ +policy_module(centreon-agent, @VERSION@) + +######################################## +# +# Declarations +# +require { + type centreon_agent_t; + type centreon_etc_t; + type unconfined_t; + type unconfined_service_t; + type setroubleshootd_t; + type init_t; + type kernel_t; + type fs_t; + type bin_t; + type tmp_t; + type node_t; + type httpd_t; + type ld_so_cache_t; + type ldconfig_exec_t; + type sysfs_t; + type sysctl_net_t; + type var_log_t; + type var_lib_t; + type cert_t; + type nagios_unconfined_plugin_exec_t; + type snmpd_var_lib_t; + type mysqld_db_t; + type ssh_exec_t; + type ssh_home_t; + type setfiles_t; + type unconfined_domain_type; +} + +type centreon_agent_t; +type centreon_agent_exec_t; +init_daemon_domain(centreon_agent_t, centreon_agent_exec_t) + +######################################## +# +# Centreon local policy +# + +allow centreon_agent_t self:process { setpgid signal_perms execmem }; +allow centreon_agent_t self:fifo_file { read open getattr ioctl write rw_fifo_file_perms }; +allow centreon_agent_t self:tcp_socket { create accept listen bind setopt getopt getattr shutdown }; +allow centreon_agent_t self:udp_socket { create accept listen bind setopt getopt getattr }; +allow centreon_agent_t self:file { create read open write getattr read_file_perms relabelto unlink rename }; +allow centreon_agent_t self:dir { add_name write remove_name }; +allow centreon_agent_t self:capability { setuid net_raw }; +allow centreon_agent_t self:rawip_socket { create read write setopt }; +allow centreon_agent_t fs_t:filesystem associate; +allow centreon_agent_t ld_so_cache_t:file execute; +allow centreon_agent_t bin_t:file { execute execute_no_trans }; +allow centreon_agent_t sysfs_t:dir read; +allow centreon_agent_t sysctl_net_t:dir search; +allow centreon_agent_t sysctl_net_t:file { open read getattr }; +allow centreon_agent_t cert_t:dir search; +allow centreon_agent_t node_t:tcp_socket node_bind; +allow centreon_agent_t nagios_unconfined_plugin_exec_t:file { open read execute execute_no_trans }; +allow centreon_agent_t var_log_t:dir { write add_name remove_name }; +allow centreon_agent_t var_log_t:file { create open write read setattr unlink }; +allow centreon_agent_t snmpd_var_lib_t:dir { open read getattr search }; +allow centreon_agent_t snmpd_var_lib_t:file { open read getattr }; +allow centreon_agent_t centreon_agent_t:dir search; +allow centreon_agent_t centreon_agent_t:fifo_file { open read getattr ioctl }; +allow centreon_agent_t ldconfig_exec_t:file { open execute getattr ioctl read}; +allow centreon_agent_t tmp_t:dir { add_name search getattr setattr write unlink create open read remove_name rmdir }; +allow centreon_agent_t tmp_t:file { getattr setattr write unlink create open read }; +allow centreon_agent_t centreon_etc_t:dir { add_name search getattr setattr write unlink create open read remove_name rmdir }; +allow centreon_agent_t ssh_exec_t:file { create read open write getattr setattr read_file_perms relabelto unlink rename ioctl }; +allow centreon_agent_t ssh_home_t:dir { add_name search getattr setattr write unlink create open read remove_name rmdir }; +allow centreon_agent_t ssh_home_t:file { create read open write getattr setattr read_file_perms relabelto unlink rename ioctl }; + +#============= setroubleshootd_t ============== +allow setroubleshootd_t centreon_agent_t:file getattr; +allow setroubleshootd_t centreon_agent_t:dir { search getattr }; +allow setroubleshootd_t centreon_agent_t:fifo_file getattr; + +#============= unconfined_t ============== +allow unconfined_t centreon_agent_t:dir { getattr setattr search relabelto relabelfrom create write add_name }; +allow unconfined_t centreon_agent_t:file { create read open write getattr setattr read_file_perms relabelto unlink rename ioctl }; +allow unconfined_t centreon_agent_t:fifo_file { read open getattr ioctl write setattr }; + +#============= unconfined_service_t ============== +allow unconfined_service_t centreon_agent_t:fifo_file { open read write getattr ioctl }; +allow unconfined_service_t centreon_agent_t:dir { getattr setattr search relabelto relabelfrom create write add_name remove_name }; +allow unconfined_service_t centreon_agent_t:file { create read open write getattr setattr read_file_perms relabelto unlink rename ioctl }; + +#============= httpd_t ============== +allow httpd_t centreon_agent_t:dir { search getattr }; +allow httpd_t centreon_agent_t:fifo_file { open read write getattr }; +allow httpd_t centreon_agent_t:file { execute execute_no_trans map open read getattr setattr }; +allow httpd_t centreon_agent_exec_t:file { execute execute_no_trans map open read getattr setattr }; + +#============= setfiles_t ============== +allow setfiles_t centreon_agent_t:dir relabelto; +allow setfiles_t centreon_agent_t:fifo_file relabelto; +allow setfiles_t centreon_agent_t:file relabelto; + +#============= init_t ============== +allow init_t centreon_agent_t:dir { add_name open read remove_name write search }; +allow init_t centreon_agent_t:fifo_file { create open read write getattr unlink }; +allow init_t centreon_agent_t:file { create execute execute_no_trans getattr map open read unlink write rename }; + +#============= kernel_t ============== +allow kernel_t centreon_agent_t:dir { add_name open read remove_name write search }; +allow kernel_t centreon_agent_t:fifo_file { create open read write getattr unlink }; +allow kernel_t centreon_agent_t:file { create execute execute_no_trans getattr map open read unlink write rename }; + +#============= cluster =============== +allow daemon initrc_transition_domain:fifo_file { ioctl read write getattr lock append }; +allow centreon_agent_t domain:lnk_file { read getattr }; +allow centreon_agent_t domain:dir { ioctl read getattr lock search open }; +allow domain unconfined_domain_type:association recvfrom; +allow domain domain:key { search link }; +allow domain unconfined_domain_type:tcp_socket recvfrom; +allow centreon_agent_t domain:file { ioctl read getattr lock open }; +allow daemon initrc_domain:fd use; +allow daemon initrc_domain:process sigchld; +allow domain unconfined_domain_type:peer recv; +allow daemon initrc_transition_domain:fd use; +allow daemon initrc_domain:fifo_file { ioctl read write getattr lock append }; + +kernel_read_kernel_sysctls(centreon_agent_t) +kernel_read_net_sysctls(centreon_agent_t) +kernel_read_network_state(centreon_agent_t) +kernel_read_system_state(centreon_agent_t) +kernel_request_load_module(centreon_agent_t) + +corecmd_exec_bin(centreon_agent_t) +corecmd_exec_shell(centreon_agent_t) + +corenet_port(centreon_agent_t) +corenet_all_recvfrom_unlabeled(centreon_agent_t) +corenet_all_recvfrom_netlabel(centreon_agent_t) +corenet_tcp_sendrecv_generic_if(centreon_agent_t) +corenet_udp_sendrecv_generic_if(centreon_agent_t) +corenet_tcp_sendrecv_generic_node(centreon_agent_t) +corenet_udp_sendrecv_generic_node(centreon_agent_t) +corenet_tcp_bind_generic_node(centreon_agent_t) +corenet_udp_bind_generic_node(centreon_agent_t) +corenet_sendrecv_all_client_packets(centreon_agent_t) +corenet_tcp_connect_all_ports(centreon_agent_t) +corenet_tcp_sendrecv_all_ports(centreon_agent_t) + +corenet_sendrecv_inetd_child_server_packets(centreon_agent_t) +corenet_tcp_bind_inetd_child_port(centreon_agent_t) +corenet_tcp_sendrecv_inetd_child_port(centreon_agent_t) + +dev_read_sysfs(centreon_agent_t) +dev_read_urand(centreon_agent_t) + +domain_use_interactive_fds(centreon_agent_t) +domain_read_all_domains_state(centreon_agent_t) + +files_read_etc_runtime_files(centreon_agent_t) +files_read_usr_files(centreon_agent_t) + +fs_getattr_all_fs(centreon_agent_t) +fs_search_auto_mountpoints(centreon_agent_t) + +auth_use_nsswitch(centreon_agent_t) + +logging_send_syslog_msg(centreon_agent_t) + +miscfiles_read_localization(centreon_agent_t) + +userdom_dontaudit_use_unpriv_user_fds(centreon_agent_t)