Merge pull request #5850 from Icinga/fix/prepare-dirs-5793

init script security fixes
This commit is contained in:
Jean Flach 2018-02-21 13:02:40 +01:00 committed by GitHub
commit aea43ddeb9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 333 additions and 123 deletions

View File

@ -21,7 +21,7 @@
#define ICINGA_INCLUDECONFDIR "${CMAKE_INSTALL_FULL_DATADIR}/icinga2/include"
#define ICINGA_USER "${ICINGA2_USER}"
#define ICINGA_GROUP "${ICINGA2_GROUP}"
#define ICINGA_SYSCONFIGFILE "${ICINGA2_SYSCONFIGFILE}"
#define ICINGA_BUILD_HOST_NAME "${ICINGA2_BUILD_HOST_NAME}"
#define ICINGA_BUILD_COMPILER_NAME "${ICINGA2_BUILD_COMPILER_NAME}"
#define ICINGA_BUILD_COMPILER_VERSION "${ICINGA2_BUILD_COMPILER_VERSION}"

View File

@ -389,8 +389,8 @@ ObjectsPath |**Read-write.** Contains the path of the Icinga 2 objects f
PidPath |**Read-write.** Contains the path of the Icinga 2 PID file. Defaults to RunDir + "/icinga2/icinga2.pid".
Vars |**Read-write.** Contains a dictionary with global custom attributes. Not set by default.
NodeName |**Read-write.** Contains the cluster node name. Set to the local hostname by default.
RunAsUser |**Read-write.** Defines the user the Icinga 2 daemon is running as. Used in the `init.conf` configuration file.
RunAsGroup |**Read-write.** Defines the group the Icinga 2 daemon is running as. Used in the `init.conf` configuration file.
RunAsUser |**Read-write.** Defines the user the Icinga 2 daemon is running as. Set in the Icinga 2 sysconfig.
RunAsGroup |**Read-write.** Defines the group the Icinga 2 daemon is running as. Set in the Icinga 2 sysconfig.
PlatformName |**Read-only.** The name of the operating system, e.g. "Ubuntu".
PlatformVersion |**Read-only.** The version of the operating system, e.g. "14.04.3 LTS".
PlatformKernel |**Read-only.** The name of the operating system kernel, e.g. "Linux".
@ -407,9 +407,9 @@ Variable |Description
--------------------|-------------------
EventEngine |**Read-write.** The name of the socket event engine, can be `poll` or `epoll`. The epoll interface is only supported on Linux.
AttachDebugger |**Read-write.** Whether to attach a debugger when Icinga 2 crashes. Defaults to `false`.
RLimitFiles |**Read-write.** Defines the resource limit for RLIMIT_NOFILE that should be set at start-up. Value cannot be set lower than the default `16 * 1024`. 0 disables the setting. Used in the `init.conf` configuration file.
RLimitProcesses |**Read-write.** Defines the resource limit for RLIMIT_NPROC that should be set at start-up. Value cannot be set lower than the default `16 * 1024`. 0 disables the setting. Used in the `init.conf` configuration file.
RLimitStack |**Read-write.** Defines the resource limit for RLIMIT_STACK that should be set at start-up. Value cannot be set lower than the default `256 * 1024`. 0 disables the setting. Used in the `init.conf` configuration file.
RLimitFiles |**Read-write.** Defines the resource limit for RLIMIT_NOFILE that should be set at start-up. Value cannot be set lower than the default `16 * 1024`. 0 disables the setting. Set in Icinga 2 sysconfig.
RLimitProcesses |**Read-write.** Defines the resource limit for RLIMIT_NPROC that should be set at start-up. Value cannot be set lower than the default `16 * 1024`. 0 disables the setting. Set in Icinga 2 sysconfig.
RLimitStack |**Read-write.** Defines the resource limit for RLIMIT_STACK that should be set at start-up. Value cannot be set lower than the default `256 * 1024`. 0 disables the setting. Set in Icinga 2 sysconfig.
## Apply <a id="apply"></a>

View File

@ -15,8 +15,6 @@
# along with this program; if not, write to the Free Software Foundation
# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
configure_file(icinga2/init.conf.cmake ${CMAKE_CURRENT_BINARY_DIR}/icinga2/init.conf @ONLY)
if(NOT WIN32)
configure_file(icinga2/constants.conf.cmake ${CMAKE_CURRENT_BINARY_DIR}/icinga2/constants.conf @ONLY)
endif()
@ -25,8 +23,6 @@ if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
configure_file(logrotate.d/icinga2.cmake ${CMAKE_CURRENT_BINARY_DIR}/logrotate.d/icinga2 @ONLY)
endif()
install_if_not_exists(${CMAKE_CURRENT_BINARY_DIR}/icinga2/init.conf ${CMAKE_INSTALL_SYSCONFDIR}/icinga2)
if(NOT WIN32)
install_if_not_exists(${CMAKE_CURRENT_BINARY_DIR}/icinga2/constants.conf ${CMAKE_INSTALL_SYSCONFDIR}/icinga2)
install_if_not_exists(icinga2/icinga2.conf ${CMAKE_INSTALL_SYSCONFDIR}/icinga2)

View File

@ -1,7 +0,0 @@
/**
* This file is read by Icinga 2 before the main
* configuration file (icinga2.conf) is processed.
*/
const RunAsUser = "@ICINGA2_USER@"
const RunAsGroup = "@ICINGA2_GROUP@"

View File

@ -31,15 +31,13 @@ if [ ! -e $ICINGA2_CONFIG_FILE ]; then
exit 6
fi
ICINGA2_USER=`$DAEMON variable get --current RunAsUser`
if [ $? != 0 ]; then
echo "Could not fetch RunAsUser variable. Error '$ICINGA2_USER'. Exiting."
if [ ! $ICINGA2_USER ]; then
echo "Could not fetch \$ICINGA2_USER. Exiting."
exit 6
fi
ICINGA2_GROUP=`$DAEMON variable get --current RunAsGroup`
if [ $? != 0 ]; then
echo "Could not fetch RunAsGroup variable. Error '$ICINGA2_GROUP'. Exiting."
if [ ! $ICINGA2_GROUP ]; then
echo "Could not fetch \$ICINGA2_GROUP. Exiting."
exit 6
fi
@ -87,20 +85,19 @@ stop() {
pid=`cat $ICINGA2_PID_FILE`
if kill -INT $pid >/dev/null 2>&1; then
if icinga2 internal signal -s SIGINT -p $pid >/dev/null 2>&1; then
for i in 1 2 3 4 5 6 7 8 9 10; do
if ! kill -CHLD $pid >/dev/null 2>&1; then
if ! icinga2 internal signal -s SIGCHLD -p $pid >/dev/null 2>&1; then
break
fi
printf '.'
sleep 3
done
fi
if kill -CHLD $pid >/dev/null 2>&1; then
kill -KILL $pid
if icinga2 internal signal -s SIGCHLD -p $pid >/dev/null 2>&1; then
icinga2 internal signal -s SIGKILL -p $pid >/dev/null 2>&1
fi
echo "Done"
@ -145,7 +142,7 @@ status() {
fi
pid=`cat $ICINGA2_PID_FILE`
if kill -CHLD $pid >/dev/null 2>&1; then
if icinga2 internal signal -s SIGCHLD -p $pid >/dev/null 2>&1; then
echo "Running"
else
echo "Not running"
@ -186,4 +183,5 @@ case "$1" in
echo "Usage: $0 {start|stop|restart|reload|checkconfig|status}"
exit 3
esac
exit 0

View File

@ -3,9 +3,10 @@ ICINGA2_CONFIG_FILE=@CMAKE_INSTALL_FULL_SYSCONFDIR@/icinga2/icinga2.conf
ICINGA2_RUN_DIR=@ICINGA2_RUNDIR@
ICINGA2_STATE_DIR=@CMAKE_INSTALL_FULL_LOCALSTATEDIR@
ICINGA2_PID_FILE=$ICINGA2_RUN_DIR/icinga2/icinga2.pid
ICINGA2_ERROR_LOG=@CMAKE_INSTALL_FULL_LOCALSTATEDIR@/log/icinga2/error.log
ICINGA2_STARTUP_LOG=@CMAKE_INSTALL_FULL_LOCALSTATEDIR@/log/icinga2/startup.log
ICINGA2_LOG=@CMAKE_INSTALL_FULL_LOCALSTATEDIR@/log/icinga2/icinga2.log
ICINGA2_LOG_DIR=@CMAKE_INSTALL_FULL_LOCALSTATEDIR@/log/icinga2
ICINGA2_ERROR_LOG=$ICINGA2_LOG_DIR/error.log
ICINGA2_STARTUP_LOG=$ICINGA2_LOG_DIR/startup.log
ICINGA2_LOG=$ICINGA2_LOG_DIR/icinga2.log
ICINGA2_CACHE_DIR=$ICINGA2_STATE_DIR/cache/icinga2
ICINGA2_USER=@ICINGA2_USER@
ICINGA2_GROUP=@ICINGA2_GROUP@

View File

@ -13,15 +13,13 @@ else
fi
ICINGA2_USER=`$DAEMON variable get --current RunAsUser`
if [ $? != 0 ]; then
echo "Could not fetch RunAsUser variable. Error '$ICINGA2_USER'. Exiting."
if [ ! $ICINGA2_USER ]; then
echo "Could not fetch \$ICINGA2_USER. Exiting."
exit 6
fi
ICINGA2_GROUP=`$DAEMON variable get --current RunAsGroup`
if [ $? != 0 ]; then
echo "Could not fetch RunAsGroup variable. Error '$ICINGA2_GROUP'. Exiting."
if [ ! $ICINGA2_GROUP ]; then
echo "Could not fetch \$ICINGA2_GROUP. Exiting."
exit 6
fi
@ -29,33 +27,28 @@ getent passwd $ICINGA2_USER >/dev/null 2>&1 || (echo "Icinga user '$ICINGA2_USER
getent group $ICINGA2_GROUP >/dev/null 2>&1 || (echo "Icinga group '$ICINGA2_GROUP' does not exist. Exiting." && exit 6)
getent group $ICINGA2_COMMAND_GROUP >/dev/null 2>&1 || (echo "Icinga command group '$ICINGA2_COMMAND_GROUP' does not exist. Exiting." && exit 6)
mkdir -p $(dirname -- $ICINGA2_PID_FILE)
chown $ICINGA2_USER:$ICINGA2_COMMAND_GROUP $(dirname -- $ICINGA2_PID_FILE)
if [ -f $ICINGA2_PID_FILE ]; then
chown $ICINGA2_USER:$ICINGA2_GROUP $ICINGA2_PID_FILE
if [ ! -e "$ICINGA2_RUN_DIR"/icinga2 ]; then
mkdir "$ICINGA2_RUN_DIR"/icinga2
mkdir "$ICINGA2_RUN_DIR"/icinga2/cmd
chmod 755 "$ICINGA2_RUN_DIR"/icinga2
chmod 2750 "$ICINGA2_RUN_DIR"/icinga2/cmd
chown -R $ICINGA2_USER:$ICINGA2_COMMAND_GROUP "$ICINGA2_RUN_DIR"/icinga2
fi
mkdir -p $(dirname -- $ICINGA2_ERROR_LOG)
chown $ICINGA2_USER:$ICINGA2_COMMAND_GROUP $(dirname -- $ICINGA2_ERROR_LOG)
chmod 750 $(dirname -- $ICINGA2_ERROR_LOG)
if [ -f $ICINGA2_ERROR_LOG ]; then
chown $ICINGA2_USER:$ICINGA2_COMMAND_GROUP $ICINGA2_ERROR_LOG
fi
if [ -f $ICINGA2_LOG ]; then
chown $ICINGA2_USER:$ICINGA2_COMMAND_GROUP $ICINGA2_LOG
# Could be undefined in installations where sysconf is not overridden on upgrade
if [ -z "$ICINGA2_LOG_DIR" ]; then
$ICINGA2_LOG_DIR=$(dirname -- "$ICINGA2_LOG")
fi
mkdir -p $ICINGA2_RUN_DIR/icinga2/cmd
chown $ICINGA2_USER:$ICINGA2_COMMAND_GROUP $ICINGA2_RUN_DIR/icinga2/cmd
test -e "$ICINGA2_LOG_DIR" || install -m 750 -o $ICINGA2_USER -g $ICINGA2_COMMAND_GROUP -d "$ICINGA2_LOG_DIR"
if type restorecon >/dev/null 2>&1; then
restorecon -R $ICINGA2_RUN_DIR/icinga2/
restorecon -R "$ICINGA2_RUN_DIR"/icinga2/
fi
chmod 2750 $ICINGA2_RUN_DIR/icinga2/cmd
# Add a fallback if the user did not specify this directory in the sysconfig file
if [ -z "$ICINGA2_CACHE_DIR" ]; then
ICINGA2_CACHE_DIR=$ICINGA2_STATE_DIR/cache/icinga2
ICINGA2_CACHE_DIR="$ICINGA2_STATE_DIR"/cache/icinga2
fi
mkdir -p $ICINGA2_CACHE_DIR
chown $ICINGA2_USER:$ICINGA2_GROUP $ICINGA2_CACHE_DIR
chmod 750 $ICINGA2_CACHE_DIR
test -e "$ICINGA2_CACHE_DIR" || install -m 750 -o $ICINGA2_USER -g $ICINGA2_COMMAND_GROUP -d "$ICINGA2_CACHE_DIR"

View File

@ -143,6 +143,7 @@ static int Main()
#endif /* _WIN32 */
Application::DeclarePrefixDir(ICINGA_PREFIX);
Application::DeclareSysconfigFile(ICINGA_SYSCONFIGFILE);
Application::DeclareSysconfDir(ICINGA_SYSCONFDIR);
Application::DeclareRunDir(ICINGA_RUNDIR);
Application::DeclareLocalStateDir(ICINGA_LOCALSTATEDIR);
@ -153,13 +154,63 @@ static int Main()
#endif /* _WIN32 */
Application::DeclareZonesDir(Application::GetSysconfDir() + "/icinga2/zones.d");
Application::DeclareRunAsUser(ICINGA_USER);
Application::DeclareRunAsGroup(ICINGA_GROUP);
#ifdef __linux__
String icingaUser = Utility::GetFromSysconfig("ICINGA2_USER");
if (icingaUser.IsEmpty())
icingaUser = ICINGA_USER;
String icingaGroup = Utility::GetFromSysconfig("ICINGA2_GROUP");
if (icingaGroup.IsEmpty())
icingaGroup = ICINGA_GROUP;
Application::DeclareRunAsUser(icingaUser);
Application::DeclareRunAsGroup(icingaGroup);
#ifdef RLIMIT_NOFILE
String rLimitFiles = Utility::GetFromSysconfig("ICINGA2_RLIMIT_FILES");
if (rLimitFiles.IsEmpty())
Application::DeclareRLimitFiles(Application::GetDefaultRLimitFiles());
else {
try {
Application::DeclareRLimitFiles(Convert::ToLong(rLimitFiles));
} catch (const std::invalid_argument& ex) {
std::cout
<< "Error while parsing \"ICINGA2_RLIMIT_FILES\" from sysconfig: " << ex.what() << '\n';
return EXIT_FAILURE;
}
}
#endif /* RLIMIT_NOFILE */
#ifdef RLIMIT_NPROC
String rLimitProcesses = Utility::GetFromSysconfig("ICINGA2_RLIMIT_PROCESSES");
if (rLimitProcesses.IsEmpty())
Application::DeclareRLimitProcesses(Application::GetDefaultRLimitProcesses());
else {
try {
Application::DeclareRLimitProcesses(Convert::ToLong(rLimitProcesses));
} catch (const std::invalid_argument& ex) {
std::cout
<< "Error while parsing \"ICINGA2_RLIMIT_PROCESSES\" from sysconfig: " << ex.what() << '\n';
return EXIT_FAILURE;
}
}
#endif /* RLIMIT_NPROC */
#ifdef RLIMIT_STACK
String rLimitStack = Utility::GetFromSysconfig("ICINGA2_RLIMIT_STACK");
if (rLimitStack.IsEmpty())
Application::DeclareRLimitStack(Application::GetDefaultRLimitStack());
#endif /* __linux__ */
else {
try {
Application::DeclareRLimitStack(Convert::ToLong(rLimitStack));
} catch (const std::invalid_argument& ex) {
std::cout
<< "Error while parsing \"ICINGA2_RLIMIT_STACK\" from sysconfig: " << ex.what() << '\n';
return EXIT_FAILURE;
}
}
#endif /* RLIMIT_STACK */
Application::DeclareConcurrency(std::thread::hardware_concurrency());
Application::DeclareMaxConcurrentChecks(Application::GetDefaultMaxConcurrentChecks());
@ -175,21 +226,6 @@ static int Main()
ScriptGlobal::Set("BuildCompilerName", ICINGA_BUILD_COMPILER_NAME);
ScriptGlobal::Set("BuildCompilerVersion", ICINGA_BUILD_COMPILER_VERSION);
String initconfig = Application::GetSysconfDir() + "/icinga2/init.conf";
if (Utility::PathExists(initconfig)) {
std::unique_ptr<Expression> expression;
try {
expression = ConfigCompiler::CompileFile(initconfig);
ScriptFrame frame(true);
expression->Evaluate(frame);
} catch (const std::exception& ex) {
Log(LogCritical, "config", DiagnosticInformation(ex));
return EXIT_FAILURE;
}
}
if (!autocomplete)
Application::SetResourceLimits();

View File

@ -1326,6 +1326,27 @@ void Application::DeclareStatePath(const String& path)
ScriptGlobal::Set("StatePath", path);
}
/**
* Retrives the path of the sysconfig file.
*
* @returns The path.
*/
String Application::GetSysconfigFile(void)
{
return ScriptGlobal::Get("SysconfigFile");
}
/**
* Sets the path of the sysconfig file.
*
* @param path The new path.
*/
void Application::DeclareSysconfigFile(const String& path)
{
if (!ScriptGlobal::Exists("SysconfigFile"))
ScriptGlobal::Set("SysconfigFile", path);
}
/**
* Retrieves the path for the modified attributes file.
*

View File

@ -106,7 +106,10 @@ public:
static String GetIncludeConfDir();
static void DeclareIncludeConfDir(const String& path);
static String GetStatePath();
static String GetSysconfigFile(void);
static void DeclareSysconfigFile(const String& path);
static String GetStatePath(void);
static void DeclareStatePath(const String& path);
static String GetModAttrPath();

View File

@ -1935,3 +1935,35 @@ String Utility::GetIcingaDataPath()
}
#endif /* _WIN32 */
String Utility::GetFromSysconfig(const String& env)
{
#ifndef _WIN32
String sysconf = Application::GetSysconfigFile();
if (sysconf.IsEmpty())
return "";
String cmdInner = ". " + EscapeShellArg(sysconf) + " 2>&1 >/dev/null;echo \"$" + env + "\"";
String cmd = "sh -c " + EscapeShellArg(cmdInner);
FILE *fp = popen(cmd.CStr(), "r");
if (!fp)
return "";
char line[1024];
String out;
if (fgets(line, sizeof(line), fp))
out = line;
else
return "";
pclose(fp);
return out.Trim();
#else
//TODO: Figure out how to do this on windows
return "";
#endif /* _WIN32 */
}

View File

@ -147,6 +147,8 @@ public:
static String GetIcingaDataPath();
#endif /* _WIN32 */
static String GetFromSysconfig(const String& env);
#ifdef I2_DEBUG
static void SetTime(double);
static void IncrementTime(double);

View File

@ -30,6 +30,7 @@ set(cli_SOURCES
featureenablecommand.cpp featureenablecommand.hpp
featurelistcommand.cpp featurelistcommand.hpp
featureutility.cpp featureutility.hpp
internalsignalcommand.cpp internalsignalcommand.hpp
nodesetupcommand.cpp nodesetupcommand.hpp
nodeutility.cpp nodeutility.hpp
nodewizardcommand.cpp nodewizardcommand.hpp

View File

@ -0,0 +1,84 @@
/******************************************************************************
* Icinga 2 *
* Copyright (C) 2012-2018 Icinga Development Team (https://www.icinga.com/) *
* *
* This program is free software; you can redistribute it and/or *
* modify it under the terms of the GNU General Public License *
* as published by the Free Software Foundation; either version 2 *
* of the License, or (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program; if not, write to the Free Software Foundation *
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. *
******************************************************************************/
#include "cli/internalsignalcommand.hpp"
#include "base/logger.hpp"
#include <signal.h>
using namespace icinga;
namespace po = boost::program_options;
REGISTER_CLICOMMAND("internal/signal", InternalSignalCommand);
String InternalSignalCommand::GetDescription() const
{
return "Send signal as Icinga user";
}
String InternalSignalCommand::GetShortDescription() const
{
return "Send signal as Icinga user";
}
ImpersonationLevel InternalSignalCommand::GetImpersonationLevel() const
{
return ImpersonateIcinga;
}
bool InternalSignalCommand::IsHidden() const
{
return true;
}
void InternalSignalCommand::InitParameters(boost::program_options::options_description& visibleDesc,
boost::program_options::options_description& hiddenDesc) const
{
visibleDesc.add_options()
("pid,p", po::value<int>(), "Target PID")
("sig,s", po::value<String>(), "Signal (POSIX string) to send")
;
}
/**
* The entry point for the "internal signal" CLI command.
*
* @returns An exit status.
*/
int InternalSignalCommand::Run(const boost::program_options::variables_map& vm, const std::vector<std::string>& ap) const
{
#ifndef _WIN32
String signal = vm["sig"].as<String>();
/* Thank POSIX */
if (signal == "SIGKILL")
return kill(vm["pid"].as<int>(), SIGKILL);
if (signal == "SIGINT")
return kill(vm["pid"].as<int>(), SIGINT);
if (signal == "SIGCHLD")
return kill(vm["pid"].as<int>(), SIGCHLD);
if (signal == "SIGHUP")
return kill(vm["pid"].as<int>(), SIGHUP);
Log(LogCritical, "cli") << "Unsupported signal \"" << signal << "\"";
#else
Log(LogCritical, "cli", "Unsupported action on Windows.");
#endif /* _Win32 */
return 1;
}

View File

@ -0,0 +1,50 @@
/******************************************************************************
* Icinga 2 *
* Copyright (C) 2012-2018 Icinga Development Team (https://www.icinga.com/) *
* *
* This program is free software; you can redistribute it and/or *
* modify it under the terms of the GNU General Public License *
* as published by the Free Software Foundation; either version 2 *
* of the License, or (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program; if not, write to the Free Software Foundation *
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. *
******************************************************************************/
#ifndef INTERNALSIGNALCOMMAND_H
#define INTERNALSIGNALCOMMAND_H
#include "cli/clicommand.hpp"
namespace icinga
{
/**
* The "internal signal" command.
*
* @ingroup cli
*/
class InternalSignalCommand final : public CLICommand
{
public:
DECLARE_PTR_TYPEDEFS(InternalSignalCommand);
String GetDescription() const override;
String GetShortDescription() const override;
ImpersonationLevel GetImpersonationLevel() const override;
bool IsHidden() const override;
void InitParameters(boost::program_options::options_description& visibleDesc,
boost::program_options::options_description& hiddenDesc) const override;
int Run(const boost::program_options::variables_map& vm, const std::vector<std::string>& ap) const override;
};
}
#endif /* INTERNALSIGNALCOMMAND_H */