Score:1

Running Bash Script as root with non root script section

gh flag

I have a bash script that needs to run as root to accomplish a task in this case it is to make the validator take a snapshot of the Helium blockchain.

I have edited the /etc/sudoers file to allow me to run this script as root. This works fine.

useraccount   ALL=(ALL:ALL) NOPASSWD:/home/useraccount/validator_data/snapshotmaker.sh

The command in the script is as follows:

sudo docker exec validator miner snapshot take /var/data/$dtt

after this, the command generates a file like this '30-10-2021T233752.bin'.

My next step is to take this file and copy it to IPFS so I can share it. The command to do this is:

ipfs files cp /ipfs/$(ipfs add -Q $localfile) $ip

At the moment I get the following error:

Error: no IPFS repo found in /root/.ipfs. please run: 'ipfs init'

This is because it is trying to run it as root when the configuration is in my useraccount.

I have tried to switch accounts in the middle of the script but it seems to kill it.

So my question is how to run the IPFS command as my original user from within the root script.

My Bash Scrip:

#!/bin/bash

dt=$(date '+%d-%m-%YT%H%M%S');
dtt="${dt}.bin"
a='/var/data/'
c="${a}${dt}.bin"

echo "${c}"

sudo docker exec validator miner snapshot take /var/data/$dtt


localfile="/home/useraccount/validator_data/${dt}.bin"
echo "LocalFile: ${localfile}"

ip=" /Helium/Snapshots/2021/${dt}.bin"
echo "IPFS Location: ${ip}"

sleep 2

if [ -f "$localfile" ]; then
        echo "$localfile exists."
        sudo chown useraccount $localfile
        whoami
        su - useraccount
        whoami
        ipfs files cp /ipfs/$(ipfs add -Q $localfile) $ip
        #ipfs files cp /ipfs/$(ipfs add -Q <local-file>) "/Helium/Snapshots/2021/<dest-name>"
else 
    echo "$localfile does not exist."
fi

The output is as follows:

/var/data/31-10-2021T005728.bin
ok
LocalFile: /home/useraccount/validator_data/31-10-2021T005728.bin
IPFS Location:  /Helium/Snapshots/2021/31-10-2021T005728.bin
/home/useraccount/validator_data/31-10-2021T005728.bin exists.
root

And then it dies. If I remove the su line then I get the root issue as mentioned at the top of this post.

Hoping someone can help.

sudodus avatar
jp flag
Are you running this script manually or automatically (for example via `cron`)? - Do not run the whole script as root. Instead run it as your normal user. Then, when it reaches the commands that need elevated permissions, you already call them with sudo, and you will be prompted to enter password. But **you can make 'only' the necessary sudo tasks possible to run without password**: edit `/etc/sudoers` via `visudo`.
DevilCode avatar
gh flag
I am running manually at the moment but will cron it once all the stuff works. Are you saying I should add only the sudo docker command to sudoers rather than the bash script ?
bac0n avatar
cn flag
@sudodus I would say the opposite. e.g., in this case, would give the user access to `chown` like giving away the key. As long the script has well-defined tasks `sudo` shouldn't pose a problem.
bac0n avatar
cn flag
There is a space on `ip=" /Helium..."`
bac0n avatar
cn flag
@DevilCode I suggest you remove all sudo from the script, and run the script with sudo, and use su with ipfs: `su <user> -c 'ipfs files cp /ipfs/$(ipfs add -Q $1) $2' _ "$localfile" "$ip"`
sudodus avatar
jp flag
@bac0n, I agree that you should not give general access to chown, but only to the specific command line(s), used by the script. If I remember correctly, this is possible, and should make it safe. - Otherwise with access to the whole shellscript, you should also trap ctrl C in order to reduce the risk of abuse. This would also be a good solution.
sudodus avatar
jp flag
@DevilCode, Yes, that's what I suggested, **the whole command lines** with `sudo docker ...` and `sudo chown useraccount $localfile`
DevilCode avatar
gh flag
Someone is going to have to write this out as I am lost now. @bac0n how does that command work ? I thought $1 $2 were arguments to a script ?
bac0n avatar
cn flag
`su -c` works much the same way as `sh -c` where `-c` takes the first argument as 'command string' and the following arguments becomes positional parameters within the command string, e.g., `su user -c 'echo $0 $1 $2' _ one two`.
DevilCode avatar
gh flag
Does not work unfortunately. I tried sudo su useraccount -c '/usr/local/bin/ipfs stats repo' does not work with or without sudo. it is ok as a script but when it is run via crontab it fails. crontab must do something weird. If i just call the command straight from the scrip /usr/local/bin/ipfs stats repo this works. If i do it with the files command it doesn't. No idea other than it is running in some weird location or something.
Score:1
jp flag

I made a 'dry run' test without docker and whatever you do in it and without ipfs. The method should work for you with the real programs after some tweaks.

  • Run the shellscript scrip as your normal user, not with sudo.

  • Edit the script to change from 'tester' in my demo to your user-name.

  • Edit the exact command lines that need sudo without password with visudo,

    $ LANG =C sudo tail -n2 /etc/sudoers
    %tester ALL=NOPASSWD: /usr/sbin/docker exec validator miner snapshot take /var/data/tmpfil
    %tester ALL=NOPASSWD: /usr/bin/chown tester /home/tester/validator_data/tmpfil
    
    • It is important to have a fixed name of the file for the sudo operations. After that it can be given a name that depends on the time.
    • Check the location of the docker executable (maybe not in /usr/sbin).

My modified script scrip:

#!/bin/bash

if [ "$(whoami)" != "tester" ]
then
 echo "run as 'tester'"
 exit
fi

dt=$(date '+%d-%m-%YT%H%M%S');
dtt="${dt}.bin"
a='/var/data/'
c="${a}${dt}.bin"

echo "${c}"

sudo /usr/sbin/docker exec validator miner snapshot take /var/data/tmpfil

if [ -f "/home/tester/validator_data/tmpfil" ]; then
        ls -l "/home/tester/validator_data/tmpfil"

        sudo /usr/bin/chown tester "/home/tester/validator_data/tmpfil"
        mv "/home/tester/validator_data/tmpfil" "/home/tester/validator_data/${dt}.bin"
        localfile="/home/tester/validator_data/${dt}.bin"
        echo "LocalFile: ${localfile}"
        ls -l "${localfile}"

        ip=" /Helium/Snapshots/2021/${dt}.bin"
        echo "IPFS Location: ${ip}"

        sleep 2

        echo "$localfile exists."
        whoami
        /usr/local/bin/ipfs files cp /ipfs/$(/usr/local/bin/ipfs add -Q $localfile) $ip
        #ipfs files cp /ipfs/$(ipfs add -Q <local-file>) "/Helium/Snapshots/2021/<dest-name>"
else 
    echo "$localfile does not exist."
fi

A script pretending to be the docker command line:

#!/bin/bash

if [ "$EUID" == "0" ]
then
 echo "docker's world" > "/home/tester/validator_data/tmpfil"
 chown root:root "/home/tester/validator_data/tmpfil"
 echo "ok"
else
 echo "run with sudo"
fi

Demo run:

tester@lenovo-v130:~$ LANG=C ./scrip 
/var/data/01-11-2021T051748.bin
ok
-rw-r--r-- 1 root root 15 Nov  1 05:17 /home/tester/validator_data/tmpfil
LocalFile: /home/tester/validator_data/01-11-2021T051748.bin
-rw-r--r-- 1 tester root 15 Nov  1 05:17 /home/tester/validator_data/01-11-2021T051748.bin
IPFS Location:  /Helium/Snapshots/2021/01-11-2021T051748.bin
/home/tester/validator_data/01-11-2021T051748.bin exists.
tester
./scrip: line 34: ipfs: command not found
./scrip: line 34: ipfs: command not found
DevilCode avatar
gh flag
Ok so I changed it to run as you have suggested. When called via command line it works perfectly. When called via crontab everything works except the ipfs files cp line. For some reson when ipfs is called that line will not run when done via crontab. I have even added the full path /usr/local/bin/ipfs and no luck.
DevilCode avatar
gh flag
Found it: /usr/local/bin/ipfs files cp /ipfs/$(/usr/local/bin/ipfs add -Q
DevilCode avatar
gh flag
Thank you everyone !!
sudodus avatar
jp flag
@DevilCode, You made it work :-) I'm glad that I could help you along the way.
Score:0
cn flag

First, you need to create a sudo configuration for your script, in this example, we place it in /etc/sudoers.d with permission 440:

Cmnd_Alias CMDS = /opt/bin/docker_example.sh
User_Alias CMDUSERS = bac0n
CMDUSERS ALL=(ALL) NOPASSWD: CMDS
Defaults!CMDS !requiretty

We name the script docker_example.sh, as shown in the sudo example:

#!/bin/bash

# Exit immediately on non-zero exit status.
set -e

((EUID != 0)) && {
    echo This script should run as root.
    exit 1
}

[[ ${SUDO_USER+ } ]] && \
user_account=$SUDO_USER || user_account=$(id -nu)

printf -v bin_file \
'%(%d-%m-%YT%H%M%S)T.%s' -1 bin

local_file=$HOME/validator_data/$bin_file
 ipfs_file=/Helium/Snapshots/2021/$bin_file

printf '
Bin File: %s
Local File: %s
IPFS File: %s
' "$bin_file" "$local_file" "$ipfs_file"

docker exec validator miner snapshot take "/var/data/$bin_file"
sleep 2

[[ -f $local_file ]] || {
    echo File does not exist: "$local_file"
    exit 1
}

chown $user_account "$local_file"

#
# su $user_account -c 'echo whoami: $1, su: $(id -nu)' _ $(id -nu)
#
su $user_account -c 'ipfs files cp "/ipfs/$(ipfs add -Q "$1")" "$2"' _ "$local_file" "$ipfs_file"

Now you should be able to add this script with sudo to crontab, and it should run with NOPASSWD.

$ sudo crontab -u bac0n -e
# min hour dom month dow command
* * * * * sudo /opt/bin/docker_example.sh > /dev/null 2>&1
Score:0
cn flag

So my question is how to run the IPFS command as my original user from within the root script.

sudo su - useraccount -c COMMAND
mangohost

Post an answer

Most people don’t grasp that asking a lot of questions unlocks learning and improves interpersonal bonding. In Alison’s studies, for example, though people could accurately recall how many questions had been asked in their conversations, they didn’t intuit the link between questions and liking. Across four studies, in which participants were engaged in conversations themselves or read transcripts of others’ conversations, people tended not to realize that question asking would influence—or had influenced—the level of amity between the conversationalists.