Your IP : 18.222.117.94
Current Path : /sbin/ |
|
Current File : //sbin/munin-run |
#!/usr/bin/perl -T
# -*- perl -*-
#
# Copyright and license: see bottom of file
#
use strict;
use warnings;
# Trust PERL5LIB from environment
use lib map { /(.*)/ } split(/:/, ($ENV{PERL5LIB} || ''));
use English qw(-no_match_vars);
use File::Temp;
use Getopt::Long;
use Munin::Common::Defaults;
use Munin::Node::Config;
use Munin::Node::OS;
use Munin::Node::Service;
my $services;
my $servicedir;
my $conffile = "$Munin::Common::Defaults::MUNIN_CONFDIR/munin-node.conf";
my $DEBUG = 0;
my $PIDEBUG = 0;
my $paranoia = 0;
my $ignore_systemd_properties = 0;
# The following parameters of "systemd-run" require rather recent systemd versions:
# --wait: 232 or later
# --pipe: 235 or later
# --collect: 236 or later
my $REQUIRED_SYSTEMD_VERSION = 999;
# See "man systemd.exec" for the list of all systemd properties.
# The following properties belong to the relevant sections "Capabilities",
# "Security", "Mandatory Access Control", "Process Properties",
# "Sandboxing" and "System Call Filtering".
# These properties will be imported from the specification of
# "munin-node.service" if systemd is enabled.
# See "--ignore-systemd-properties" for details.
# See "get_systemd_hardening_flags" for a few exceptions from the list below.
my @SYSTEMD_PROPERTY_IMPORT_PATTERNS = qw(
AmbientCapabilities
AppArmorProfile
CapabilityBoundingSet
DynamicUser
Environment
EnvironmentFile
Group
Limit\w+
MemoryDenyWriteExecute
MountFlags
NetworkNamespacePath
NoNewPrivileges
PassEnvironment
Private\w+
Protect\w+
Restrict\w+
SecureBits
SELinuxContext
SmackProcessLabel
SystemCallArchitectures
SystemCallFilter
TemporaryFileSystem
UMask
UnsetEnvironment
User
\w+Directory
\w+Paths
);
# The following environment variables are assigned automatically by
# systemd-run (see "man systemd.exec"). We should not override them
# when calling "systemd-run".
# See "--ignore-systemd-properties" for details.
my %ENVIRONMENT_IGNORE_HASH = map { $_ => 1 } qw(
PATH
LANG
USER
HOME
SHELL
LOGNAME
INVOCATION_ID
XDG_RUNTIME_DIR
RUNTIME_DIRECTORY
STATE_DIRECTORY
CACHE_DIRECTORY
LOGS_DIRECTORY
CONFIGURATION_DIRECTORY
MAINPID
MANAGERPID
LISTEN_FDS
LISTEN_PID
LISTEN_FDNAMES
NOTIFY_SOCKET
WATCHDOG_PID
WATCHDOG_USEC
TERM
JOURNAL_STREAM
SERVICE_RESULT
EXIT_CODE
EXIT_STATUS
PIDFILE
);
my $config = Munin::Node::Config->instance();
sub main
{
# "Clean" environment to disable taint-checking on the environment. We _know_
# that the environment is insecure, but we want to let admins shoot themselves
# in the foot with it, if they want to.
foreach my $key (keys %ENV) {
$ENV{$key} =~ /^(.*)$/s;
$ENV{$key} = $1;
}
$0 =~ /^(.*)$/;
$0 = $1;
my @original_argv = @ARGV;
my ($plugin, $arg) = parse_args();
# Loads the settings from munin-node.conf.
# Ensures that, where options can be set both in the config and in
# @ARGV, the latter takes precedence.
$paranoia = $config->{paranoia};
my $config = Munin::Node::Config->instance();
$config->parse_config_from_file($conffile);
# Run directly or execute recursively via "systemd-run".
if (($ignore_systemd_properties) || (! -d "/run/systemd/system")) {
return execute_plugin($plugin, $arg);
} elsif (!check_systemd_run_permissions()) {
print STDERR "# Skipping systemd properties simulation due to lack of permissions.\n" if $config->{DEBUG};
return execute_plugin($plugin, $arg);
} else {
my $systemd_version = get_systemd_version();
if ((not defined $systemd_version) or ($systemd_version < $REQUIRED_SYSTEMD_VERSION)) {
print STDERR "# Skipping systemd properties simulation due to required systemd version ($REQUIRED_SYSTEMD_VERSION)\n" if $config->{DEBUG};
return execute_plugin($plugin, $arg);
} else {
my @munin_node_hardening_flags;
my $parse_flags_success = 0;
eval {
@munin_node_hardening_flags = get_systemd_hardening_flags();
$parse_flags_success = 1;
};
if ($parse_flags_success) {
return run_via_systemd(\@munin_node_hardening_flags,
\@original_argv, $config->{DEBUG});
} else {
# Failed to retrieve systemd properties of munin-node service.
# Probable causes: systemd is not installed/enabled or the
# service unit does not exist.
return execute_plugin($plugin, $arg);
}
}
}
}
sub check_systemd_run_permissions {
# verify whether systemd-run can be exected (e.g. unprivileged users cannot execute it)
return system("systemd-run --collect --pipe --quiet --wait -- true </dev/null >/dev/null 2>&1") == 0;
}
# Retrieve the locally configured hardening flags for the "munin-node" systemd
# service.
# The result of the function is a list of strings like the following:
# ProtectHome=yes
sub get_systemd_hardening_flags {
# retrieve all active properties except for soft (runtime) limits
my @munin_service_properties = grep !/^Limit\w+Soft=/, `systemctl show munin-node 2>/dev/null`;
die "no systemd enabled or failed to retrieve unit properties" unless ($CHILD_ERROR >> 8 == 0);
my $flag_name_regex = '^((?:' . join("|", @SYSTEMD_PROPERTY_IMPORT_PATTERNS) . ')=.*)$';
my @flag_list;
foreach my $property_definition (@munin_service_properties) {
# The effect of files referenced in "DropInPaths" (e.g. files overriding the properties of
# a service) is already applied to the output of "systemd-show".
# Thus we can safely ignore this property (which is not accepted by "systemd-run" anyway).
next if ($property_definition =~ /^DropInPaths=/);
# "systemd show" does not output the EnvironmentFile property in a readable format.
# See https://github.com/systemd/systemd/issues/14723.
next if ($property_definition =~ /^EnvironmentFiles?=(.*) \(ignore_errors=(yes|no)\)$/);
next if ($property_definition =~ /^RestrictSUIDSGID=/);
push @flag_list, $1 if $property_definition =~ /$flag_name_regex/;
}
return @flag_list;
}
# "man systemd.exec" describes the quoting rules for EnvironmentFile.
# We apply the following steps:
# 1) escape all double quotes with a backslash
# 2) surround the value with double quotes
# This combination ensures that even line breaks are properly parsed by
# systemd-run.
sub quote_for_systemd_environment_file {
my ($key, $value) = @_;
# escape double quotes
$value =~ s/"/\\"/;
return $key . '="' . $value . "\"\n";
}
sub get_systemd_version {
my @version_output = `systemd-run --version 2>/dev/null`;
foreach my $line (@version_output) {
if ($line =~ /^systemd(?:-run)?\s+(\d+).*$/) {
return int($1);
}
}
return;
}
# Recursively execute this script ("munin-run") via "systemd-run".
# This allows to apply the hardening properties defined in "munin-node.service".
# Thus the behavior of "munin-run" should be the same as the behavior of
# munin-node service itself. This is less surprising for users.
sub run_via_systemd {
my ($systemd_properties_ref, $original_argv_ref, $debug_enabled) = @_;
my @call_args;
push @call_args, "systemd-run";
# discard the transient service even in case of errors
push @call_args, "--collect";
# use our stdin/stdout/stderr for the created process
push @call_args, "--pipe";
push @call_args, "--quiet";
# wait for the end of the command execution
push @call_args, "--wait";
# Preserve the environment (e.g. manual plugin configuration applied by
# the user).
# We use systemd-run's property "EnvironmentFile" for transferring the
# environment of the current process to the new process. The following
# simpler approaches ("properties") are not suitable:
# * Environment: would expose the private environment of the calling
# user to all other users (via the commandline).
# * PassEnvironment: the variables are only transferred from the
# system manager (PID 1) instead of the calling process.
# This approach causes a problem
my $environment_file = File::Temp->new();
# The order of systemd-run's environment variable processing may cause
# problems, if "munin-node.service" specifies "Environment" properties,
# which exist in the caller's environment. Such variables (being written
# to the temporary EnvironmentFile) take precedence over the ones defined
# in "munin-node.service". There does not seem to be a clean generic
# workaround for this issue.
foreach my $key (keys %ENV) {
next if exists($ENVIRONMENT_IGNORE_HASH{$key});
print $environment_file quote_for_systemd_environment_file($key, $ENV{$key});
}
push @call_args, "--property";
push @call_args, "EnvironmentFile=" . $environment_file->filename;
# enable the hardening flags of the munin-node service
foreach my $key_value (@$systemd_properties_ref) {
push @call_args, "--property";
push @call_args, $key_value;
}
push @call_args, "--";
# append the untainted name/path of "munin-run" itself
$0 =~ /^(.*)$/s;
push @call_args, $1;
push @call_args, "--ignore-systemd-properties";
foreach my $arg (@$original_argv_ref) {
# untaint our arguments
$arg =~ /^(.*)$/s;
push @call_args, $1;
}
if ($debug_enabled) {
print STDERR ("# Running 'munin-run' via 'systemd-run' with systemd "
. "properties based on 'munin-node.service'.\n");
my $command_printable = "";
foreach my $token (@call_args) {
$command_printable .= " " if ($command_printable);
if ($token =~ /\s/) {
$command_printable .= "'$token'";
} else {
$command_printable .= "$token";
}
}
print STDERR "# Command invocation: $command_printable\n";
}
# We need to use "system" instead of "exec in order to remove the EnvironmentFile
# afterwards. This is indirectly handled by the object cleanup from File::Temp.
my $result = system(@call_args);
if ($result == -1) {
die "Failed to execute the 'systemd-run' wrapper. Maybe try '--ignore-systemd-properties'.";
} else {
my $exitcode = $result >> 8;
if ($exitcode != 0) {
# Sadly problems with "systemd-run" are only visible in the log (no error output).
print STDERR ("Warning: the execution of 'munin-run' via 'systemd-run' returned an "
. "error. This may either be caused by a problem with the plugin to be "
. "executed or a failure of the 'systemd-run' wrapper. Details of the "
. "latter can be found via 'journalctl'.\n");
}
return $exitcode;
}
}
sub execute_plugin {
my ($plugin, $arg) = @_;
$services = Munin::Node::Service->new(
servicedir => $servicedir,
defuser => $config->{defuser},
defgroup => $config->{defgroup},
pidebug => $PIDEBUG,
);
$config->reinitialize({
%$config,
paranoia => $paranoia,
});
unless ($services->is_a_runnable_service($plugin)) {
print STDERR "# Unknown service '$plugin'\n";
exit 1;
}
$services->prepare_plugin_environment($plugin);
# no need for a timeout -- the user can kill this process any
# time they want.
$services->exec_service($plugin, $arg);
# Never reached, but just in case...
print STDERR "# FATAL: Failed to exec.\n";
exit 42;
}
sub parse_args
{
# Default configuration values
my $sconfdir = "$Munin::Common::Defaults::MUNIN_CONFDIR/plugin-conf.d";
my $sconffile;
my ($plugin, $arg);
print_usage_and_exit() unless GetOptions(
"config=s" => \$conffile,
"debug!" => \$DEBUG,
"pidebug!" => \$PIDEBUG,
"servicedir=s" => \$servicedir,
"sconfdir=s" => \$sconfdir,
"sconffile=s" => \$sconffile,
"paranoia!" => \$paranoia,
"ignore-systemd-properties" => \$ignore_systemd_properties,
"version" => \&print_version_and_exit,
"help" => \&print_usage_and_exit,
);
print_usage_and_exit() unless $ARGV[0];
# Detaint the plugin name
($plugin) = ($ARGV[0] =~ m/^([-\w.:]+)$/) or die "# ERROR: Invalid plugin name '$ARGV[0].\n";
if ($ARGV[1]) {
($arg) = ($ARGV[1] =~ m/^(\w+)$/)
or die "# ERROR: Invalid characters in argument '$ARGV[1]'.\n";
}
# Detaint service directory. FIXME: do more strict detainting?
if ($servicedir) {
$servicedir =~ /(.*)/;
$servicedir = $1;
}
# Update the config
$config->reinitialize({
%$config,
sconfdir => $sconfdir,
conffile => $conffile,
sconffile => $sconffile,
DEBUG => $DEBUG,
paranoia => $paranoia,
});
return ($plugin, $arg);
}
sub print_usage_and_exit
{
require Pod::Usage;
Pod::Usage::pod2usage(-verbose => 1);
}
sub print_version_and_exit
{
require Pod::Usage;
Pod::Usage::pod2usage(
-verbose => 99,
-sections => 'VERSION|COPYRIGHT',
);
}
exit main() unless caller;
1;
__END__
=head1 NAME
munin-run - A program to run Munin plugins from the command line
=head1 SYNOPSIS
munin-run [options] <plugin> [ config | autoconf | snmpconf | suggest ]
=head1 DESCRIPTION
munin-run is a script to run Munin plugins from the command-line.
It's useful when debugging plugins, as they are run in the same conditions
as they are under munin-node.
=head1 OPTIONS
=over 5
=item B<< --config <configfile> >>
Use E<lt>fileE<gt> as configuration file. [/etc/munin/munin-node.conf]
=item B<< --servicedir <dir> >>
Use E<lt>dirE<gt> as plugin dir. [/etc/munin/plugins/]
=item B<< --sconfdir <dir> >>
Use E<lt>dirE<gt> as plugin configuration dir. [/etc/munin/plugin-conf.d/]
=item B<< --sconffile <file> >>
Use E<lt>fileE<gt> as plugin configuration. Overrides sconfdir. [undefined]
=item B<--paranoia >
Only run plugins owned by root and check permissions. [disabled]
=item B<--ignore-systemd-properties >
Do not try to detect and enforce the locally configured hardening flags of the
"munin-node" service unit. This detection is skipped, if systemd is not enabled.
The hardening flags may cause subtile surprises.
For example "ProtectHome=yes" prevents the "df" plugin from determining the
state of the "home" partition. [disabled]
=item B<--help >
View this help message.
=item B<--debug >
Print debug messages. Debug messages are sent to STDOUT and are
prefixed with "#" (this makes it easier for other parts of munin to
use munin-run and still have --debug on). Only errors go to STDERR.
=item B<--pidebug >
Plugin debug. Sets the environment variable MUNIN_DEBUG to 1 so
that plugins may enable debugging. [disabled]
=item B<--version >
Show version information.
=back
=head1 NOTES FOR SYSTEMD USERS
The "munin-node" service is usually started by systemd via a
"munin-node.service" definition. Some distributions enable hardening
settings in this service file in order to restrict the allowed set of
activities for the "munin-node" process.
This may cause surprising differences between the result of "munin-run"
and the real "munin-node" service.
A popular example of such a surprising restriction is "ProtectHome=yes"
combined with the "df" plugin. The restriction silently prevents the
plugin from determining the status of mountpoints below /home.
"munin-run" tries to mimic this behavior of "munin-node" automatically.
Thus the execution of "munin-run df" should provide the same output as
"echo fetch df | nc localhost munin".
If you want to debug potential issues of systemd restrictions, then you
may want to use the parameters "--ignore-systemd-properties" and
"--debug". Permanent overrides of systemd properties can be configured
locally via "systemctl edit munin-node".
See "man systemd.exec" for the documentation of systemd's properties.
=head1 FILES
/etc/munin/munin-node.conf
/etc/munin/plugins/*
/etc/munin/plugin-conf.d/*
/var/run/munin/munin-node.pid
/var/log/munin/munin-node.log
=head1 VERSION
This is munin-run (munin-node) v2.0.66
=head1 AUTHORS
Audun Ytterdal, Jimmy Olsen, Tore Anderson, Nicolai Langfeldt,
Lars Kruse.
=head1 BUGS
Please see L<http://munin-monitoring.org/report/1>.
=head1 COPYRIGHT
Copyright (C) 2002-2009 Audun Ytterdal, Jimmy Olsen, Tore Anderson,
Nicolai Langfeldt / Linpro AS.
Copyright (C) 2020 Lars Kruse
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; version 2 dated June,
1991.
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 Street, Fifth Floor, Boston,
MA 02110-1301 USA.
=cut
# vim: sw=4 : ts=4 : expandtab