What is best way to write puppet code - "stop crond if /filesystem is not mounted" (filesystem mount is taken care by Redhat Cluter)"
exec is probably easiest way to deal with this :
exec { "stop_crond" :
command => "service crond stop",
path => "/sbin:/usr/sbin:/bin:/usr/bin",
unless => 'mount | grep -oq "/filesystem"',
}
I'd suggest writing a custom fact to check your filesystem mount. Using that variable, you can declare your crond service either stopped, or running, etc.
Alternatively, you could do something like an exec to stop crond, using onlyif to check the mount. For example, grepping for the name of your filesystem in the output of mount.
The custom fact approach is cleaner, more reliable, and better style; if you're in a rush, the exec should work fine.
Below works for me.
exec { "stop_crond":
command => "service crond stop",
path => "/sbin:/usr/sbin:/bin:/usr/bin",
unless => '/bin/cat /proc/mounts | /bin/grep -q \' /mnt \'';
}
Related
I'm trying to execute a .sh script from within a .Net Core service daemon and getting weird behavior. The purpose of the script is to create an encrypted container, format it, set some settings, then mount it.
I'm using .Net Core version 3.1.4 on Raspbian on a Raspberry Pi 4.
The problem: I have the below script which creates the container, formats it, sets the settings, then attempts to mount it. It all seems to work fine but the last command, mount call, never actually works. The mount point is not valid.
The kicker: After the script is run via the service, if I open a terminal and issue the mount command there manully, it mounts correctly. I can then goto that mount point and it shows ~10GB of space available meaning it's using the container.
Note: Make sure the script is chmod +x when testing. Also you'll need cryptsetup installed to work.
Thoughts:
I'm not sure if some environment or PATH variables are missing for the shell script to properly function. Since this is a service, I can edit the Unit to include this information, if I knew what it was.
In previous attempts at issuing bash commands, I've had to set the DISPLAY variable like below for it to work correctly (because of needing to work with the desktop). For this issue that doesn't seem to matter but if I need to set the script as executable, then this command is uses as an example
string chmodArgs = string.Format("DISPLAY=:0.0; export DISPLAY && chmod +x {0}", scriptPath);
chmodArgs = string.Format("-c \"{0}\"", chmodArgs);
I'd like to see if someone can take the below and test on their end to confirm and possibly help come up with a solution. Thanks!
#!/bin/bash
# variables
# s0f4e7n4r4h8x4j4
# /usr/sbin/content1
# content1
# /mnt/content1
# 10240
# change the size of M to what the size of container should be
echo "Allocating 10240MB..."
fallocate -l 10240M /usr/sbin/content1
sleep 1
# using echo with -n passes in the password required for cryptsetup command. The dash at the end tells cryptsetup to read in from console
echo "Formatting..."
echo -n s0f4e7n4r4h8x4j4 | cryptsetup luksFormat /usr/sbin/content1 -
sleep 1
echo "Opening..."
echo -n s0f4e7n4r4h8x4j4 | cryptsetup luksOpen /usr/sbin/content1 content1 -
sleep 1
# create without journaling
echo "Creating filesystem..."
mkfs.ext4 -O ^has_journal /dev/mapper/content1
sleep 1
# enable writeback mode
echo "Tuning..."
tune2fs -o journal_data_writeback /dev/mapper/content1
sleep 1
if [ ! -d "/mnt/content1" ]; then
echo "Creating directory..."
mkdir -p /mnt/content1
sleep 1
fi
# mount with no access time to stop unnecessary writes to disk for just access
echo "Mounting..."
mount /dev/mapper/content1 /mnt/content1 -o noatime
sleep 1
This is how I'm executing the script in .Net
var proc = new System.Diagnostics.Process {
StartInfo =
{
FileName = pathToScript,
WorkingDirectory = workingDir,
Arguments = args,
UseShellExecute = false
}
};
if (proc.Start())
{
while (!proc.HasExited)
{
System.Threading.Thread.Sleep(33);
}
}
The Unit file use for service daemon
[Unit]
Description=Service name
[Service]
ExecStart=/bin/bash -c 'PATH=/sbin/dotnet:$PATH exec dotnet myservice.dll'
WorkingDirectory=/sbin/myservice/
User=root
Group=root
Restart=on-failure
SyslogIdentifier=my-service
PrivateTmp=true
[Install]
WantedBy=multi-user.target
The problem was not being able to run the mount command from within a service directly. From extensive trial and error, even printing verbose of the mount command would show that there was NO errors and it would NOT be mounted. Very misleading to not provide some failure message for users.
Solution is to create a Unit file "service" to handle the mount/umount. Below explains with a link to the inspiring article that brought me here.
Step 1: Create the Unit File
The key is the .mount file needs to be named in a pattern that matches the Where= in the Unit file. So if you're mounting /mnt/content1, your file would be:
sudo nano /etc/systemd/system/mnt-content1.mount
Here is the Unit file details I used.
[Unit]
Description=Mount Content (/mnt/content1)
DefaultDependencies=no
Conflicts=umount.target
Before=local-fs.target umount.target
After=swap.target
[Mount]
What=/dev/mapper/content1
Where=/mnt/content1
Type=ext4
Options=noatime
[Install]
WantedBy=multi-user.target
Step 2: Reload systemctl
systemctl daemon-reload
Final steps:
You can now issue start/stop on the new "service" that is dedicated just to mount and unmount. This will not auto mount on reboot, if you need that you'll need to enable the service to do such.
systemctl start mnt-content1.mount
systemctl stop mnt-content1.mount
Article: https://www.golinuxcloud.com/mount-filesystem-without-fstab-systemd-rhel-8/
Im writing shell script to check if user may be doing some nasty things in Linux enviroment. One check i would like to do is determine if / filesyste was mounted using external OS (like using live SO) in previous mount.
First i think to exec script when boot to get the mount time in previous boot using journalctl and actual last mount using tune2fs, to compare it. But last mount using tune2fs gets current mount, not previous, because system is mounted when ckecks it.
Any idea to solve it?
Thanks!
dmesg's output shows about the mounting of / (and other infos as well). If your current OS's dmesg's output has that info, it was mounted by the current system.
You can use the output of dmesg in your script like :
#!/bin/bash
number=$(dmesg | grep -c "sdaN")
if [ $number == 0 ]; then
echo "It was not mounted by the current system"
else
echo "It was mounted by the current system"
fi
My application allows the user to bind mount a source directory to a target mount point. This is all working correctly except the mount does not exist outside the process that corrected it.
I have boiled down the issue to a very simple script.
#!/bin/bash
echo "Content-type: text/html"
echo ""
echo ""
echo "<p>Hello</p>"
echo "<p>Results from pid #{$$}:</p>"
echo "<ul>"
c="sudo mkdir /shares/target"
echo "<li>Executed '$c', Results: " $(eval $c) "</li>"
c="sudo mount --bind /root/source /shares/target"
echo "<li>Executed '$c', Results: " $(eval $c) "</li>"
c="sudo mount | grep shares"
echo "<li>Executed '$c', Results: " $(eval $c) "</li>"
c="sudo cat /proc/mounts | grep shares"
echo "<li>Executed '$c', Results: " $(eval $c) "</li>"
echo "</ul>"
The first two commands create a mount point and execute the mount. The last two commands verify the result. The script executes without issue. However, the mount is not visible or available in a separate shell process. Executing the last two commands in a separate shell does not show the mount being available. If I attempt to execute "rm -rf /shares/target" I get "rm: cannot remove '/shares/target/': Device or resource busy”. Executing "losf | grep /shares/target" generates no output. In a seperate shell I have switch to the apache user but the mount is still not available. I have verified the apache process is not in a chroot by logging the output of "ls /proc/$$/root". It points to "/".
I am running:
Apache 2.4.6
CentOS 7
httpd-2.4.6-31.el7.centos.1.x86_64
httpd-tools-2.4.6-31.el7.centos.1.x86_64
I turned logging to debug but the error_log indicates nothing.
Thanks in advance.
This behavior is due to the following line in the httpd.service systemd unit:
PrivateTmp=true
From the systemd.exec(5) man page:
PrivateTmp=
Takes a boolean argument. If true, sets up a new file
system namespace for the executed processes and mounts
private /tmp and /var/tmp directories inside it that is not
shared by processes outside of the namespace.
[...]
Note that using this setting will disconnect propagation of
mounts from the service to the host (propagation in the
opposite direction continues to work). This means that this
setting may not be used for services which shall be able to
install mount points in the main mount namespace.
In other words, mounts made by httpd and child processes will not be
visible to other processes on your host.
The PrivateTmp directive is useful from a security perspective, as described here:
/tmp traditionally has been a shared space for all local services and
users. Over the years it has been a major source of security problems
for a multitude of services. Symlink attacks and DoS vulnerabilities
due to guessable /tmp temporary files are common. By isolating the
service's /tmp from the rest of the host, such vulnerabilities become
moot.
You can safely remove the PrivateTmp directive from the unit file (well, don't actually modify the unit file -- create a new one at /etc/systemd/system/httpd.service, then systemctl daemon-reload, then systemctl restart httpd).
I have a bash script that perform a check and return a boolean 0|1.
Example of such script below :
# less /path/to/script/check_kernel.sh
#! /bin/bash
# Check if running kernel match default=0 kernel in grub.conf
KERNEL_RUNN=`/bin/uname -r | /bin/sed -e 's/^//' -e 's/[[:space:]]*$//'`
KERNEL_GRUB=`/bin/grep kernel /boot/grub/menu.lst | /bin/grep -v '#' \
| /bin/awk '{print $2}' | /bin/sed -e 's/\/vmlinuz-//g' | /usr/bin/head -1 \
| /bin/sed -e 's/^//' -e 's/[[:space:]]*$//'`
if [ "$KERNEL_RUNN" == "$KERNEL_GRUB" ]; then
exit 0
else
exit 1
fi
To run the above shell script in Puppet I would use the following code :
$check_kernel_cmd="/path/to/script/check_kernel.sh"
exec {'check_kernel':
provider => shell,
returns => [ "0", "1", ],
command => "$check_kernel_cmd",
}
So now I need to use the returned exit status of above exec resource Exec['check_kernel'] as a trigger to another exec resource Exec['reboot_node'], something like :
if $check_kernel == '1' {
$reboot = "/sbin/runuser - root -s /bin/bash -c '/sbin/shutdown -r'"
exec {'reboot_node':
provider => shell,
command => "$reboot",
}
}
or maybe another style approach would be to use unless as follows :
$reboot = "/sbin/runuser - root -s /bin/bash -c '/sbin/shutdown -r'"
exec {'reboot_node':
provider => shell,
command => "$reboot",
unless => "/bin/echo $check_kernel",
require => Exec['check_kernel'],
}
What would the recommended approach/code be to use the exit status of an exec resource as a trigger to another exec resource in the same manifest ?
TL;DR this cannot work. Make your first script an external fact so that you can query its result from a variable in your manifests. Alternatively, if that is valid, call the prior script through the latter's onlyif or unless parameter, instead of as its own exec resource.
Long answer
The scheme you have in mind is not compatible with Puppet's master/agent paradigm. The complete manifest is compiled in one go, leading to an abstract representation, the catalog. This whole catalog is sent to the agent for evaluation. Only then will the agent start and sync resources, including both exec resources. The information about the return value of any of them cannot be used in the manifest, because the manifest is no longer available at that point.
The canonical way for the master to use information from the agent machine are Custom Facts. You place code on the master which the agent consumes and runs prior to compilation. All fact values can be used in the manifest as variables.
In a simple case such as your's, using an exec for the check script is likely unnecessary. I believe the following would work.
exec {
'/sbin/shutdown -r':
unless => '/path/to/script/check_kernel.sh';
}
Final note: Programming your Puppet agent to reboot your nodes by some homegrown logic could be rather dangerous - the agent runs at startup by default, so it may end up in a vicious cycle if that logic breaks (you can likely fix that on the master, but it's still not a cheerful perspective).
Services default to starting as root at boot time on my RHEL box. If I recall correctly, the same is true for other Linux distros which use the init scripts in /etc/init.d.
What do you think is the best way to instead have the processes run as a (static) user of my choosing?
The only method I'd arrived at was to use something like:
su my_user -c 'daemon my_cmd &>/dev/null &'
But this seems a bit untidy...
Is there some bit of magic tucked away that provides an easy mechanism to automatically start services as other, non-root users?
EDIT: I should have said that the processes I'm starting in this instance are either Python scripts or Java programs. I'd rather not write a native wrapper around them, so unfortunately I'm unable to call setuid() as Black suggests.
On Debian we use the start-stop-daemon utility, which handles pid-files, changing the user, putting the daemon into background and much more.
I'm not familiar with RedHat, but the daemon utility that you are already using (which is defined in /etc/init.d/functions, btw.) is mentioned everywhere as the equivalent to start-stop-daemon, so either it can also change the uid of your program, or the way you do it is already the correct one.
If you look around the net, there are several ready-made wrappers that you can use. Some may even be already packaged in RedHat. Have a look at daemonize, for example.
After looking at all the suggestions here, I've discovered a few things which I hope will be useful to others in my position:
hop is right to point me back
at /etc/init.d/functions: the
daemon function already allows you
to set an alternate user:
daemon --user=my_user my_cmd &>/dev/null &
This is implemented by wrapping the
process invocation with runuser -
more on this later.
Jonathan Leffler is right:
there is setuid in Python:
import os
os.setuid(501) # UID of my_user is 501
I still don't think you can setuid
from inside a JVM, however.
Neither su nor runuser
gracefully handle the case where you
ask to run a command as the user you
already are. E.g.:
[my_user#my_host]$ id
uid=500(my_user) gid=500(my_user) groups=500(my_user)
[my_user#my_host]$ su my_user -c "id"
Password: # don't want to be prompted!
uid=500(my_user) gid=500(my_user) groups=500(my_user)
To workaround that behaviour of su and runuser, I've changed my init script to something like:
if [[ "$USER" == "my_user" ]]
then
daemon my_cmd &>/dev/null &
else
daemon --user=my_user my_cmd &>/dev/null &
fi
Thanks all for your help!
Some daemons (e.g. apache) do this by themselves by calling setuid()
You could use the setuid-file flag to run the process as a different user.
Of course, the solution you mentioned works as well.
If you intend to write your own daemon, then I recommend calling setuid().
This way, your process can
Make use of its root privileges (e.g. open log files, create pid files).
Drop its root privileges at a certain point during startup.
Just to add some other things to watch out for:
Sudo in a init.d script is no good since it needs a tty ("sudo: sorry, you must have a tty to run sudo")
If you are daemonizing a java application, you might want to consider Java Service Wrapper (which provides a mechanism for setting the user id)
Another alternative could be su --session-command=[cmd] [user]
on a CENTOS (Red Hat) virtual machine for svn server:
edited /etc/init.d/svnserver
to change the pid to something that svn can write:
pidfile=${PIDFILE-/home/svn/run/svnserve.pid}
and added option --user=svn:
daemon --pidfile=${pidfile} --user=svn $exec $args
The original pidfile was /var/run/svnserve.pid. The daemon did not start becaseu only root could write there.
These all work:
/etc/init.d/svnserve start
/etc/init.d/svnserve stop
/etc/init.d/svnserve restart
Some things to watch out for:
As you mentioned, su will prompt for a password if you are already the target user
Similarly, setuid(2) will fail if you are already the target user (on some OSs)
setuid(2) does not install privileges or resource controls defined in /etc/limits.conf (Linux) or /etc/user_attr (Solaris)
If you go the setgid(2)/setuid(2) route, don't forget to call initgroups(3) -- more on this here
I generally use /sbin/su to switch to the appropriate user before starting daemons.
Why not try the following in the init script:
setuid $USER application_name
It worked for me.
I needed to run a Spring .jar application as a service, and found a simple way to run this as a specific user:
I changed the owner and group of my jar file to the user I wanted to run as.
Then symlinked this jar in init.d and started the service.
So:
#chown myuser:myuser /var/lib/jenkins/workspace/springApp/target/springApp-1.0.jar
#ln -s /var/lib/jenkins/workspace/springApp/target/springApp-1.0.jar /etc/init.d/springApp
#service springApp start
#ps aux | grep java
myuser 9970 5.0 9.9 4071348 386132 ? Sl 09:38 0:21 /bin/java -Dsun.misc.URLClassPath.disableJarChecking=true -jar /var/lib/jenkins/workspace/springApp/target/springApp-1.0.jar