Disable ssh root login by modifying /etc/ssh/sshd_conf from within a script?

Viewed 10140

During the process of setting up my Raspberries, I want to prevent root logins via ssh. As always, it's a "scriptlet" (called by a runner).

My research told me that, even in times of systemd, /etc/ssh/sshd_config is the file to modify. So far, so good. In my humble understanding, what needs to be done is this: read the config file line by line, match for "PermitRootLogin yes" (whitespace match); if no match, write the line to another file, if yes, replace it with "PermitRootLogin no", and write to the other file, and finally replace the original configuration file with the new file, and restart sshd via the systemd stuff.

In Perl, I'd read the whole file, replace() the line, and write stuff back to another file. But as the young Buddhist said: "There is no Perl!"

Bash(2) only, please.

8 Answers

Configuring SSH programmatically

I did some investigation on this and I think JNevevill's answer is the most solid. I have some example script that illustrated the two ways, SED and AWK.

Preparation

Showing the test data only once for better readability in the examples. It has been initialized for each approach the same way. It will define some rules in a test file and a dictionary of new rules that should replace the rules in the file or be written to the file.

echo 'LoginGraceTime 120' > ./data.txt
echo '#PermitRootLogin yes' >> ./data.txt
echo 'PermitRootLogin no' >> ./data.txt
echo 'PasswordAuthentication yes' >> ./data.txt

declare -A rules=( 
    ["LoginGraceTime"]="1m"
    ["PermitRootLogin"]="no"
    ["PasswordAuthentication"]="no"
    ["AllowUsers"]="blue"
)

SED

SED will replace the rules it finds. If a line is commented, it will remove the # and replace the value. This approach is less solid as it can lead to duplicate rules. I.E. A rule exists commented and uncommented. Also, if the rule wasn't there, it will not be written at all.

for rule in "${!rules[@]}"; do
  regex="s/#\?\(${rule}\s*\).*$/\1 ${rules[${rule}]}/"
  sed "${regex}" ./data.txt > temp.txt;
  mv -f temp.txt ./data.txt
done

Result:

LoginGraceTime  1m
PermitRootLogin  no
PermitRootLogin  no
PasswordAuthentication  no

AWK

AWK is more solid in this situation. It yields better control. It will read line by line and replace the value if it exists without changing commented rules. If a rule wasn't found at the end of the file, it will append this rule to the file. This is much more solid than the SED approach. We can be sure there will be not duplicate rule and all rules are defined.

for rule in "${!rules[@]}"; do
awk -v key="${rule}" -v val="${rules[${rule}]}" \
  '$1==key {foundLine=1; print key " " val} $1!=key{print $0} END{if(foundLine!=1) print key " " val}' \
  ./data.txt > sshd_config.tmp && mv sshd_config.tmp ./data.txt
done

Result:

LoginGraceTime 1m
#PermitRootLogin yes
PermitRootLogin no
PasswordAuthentication no
AllowUsers blue

Conclusion

AWK is clearly the better choice. It is more safe and can handle rules that are not in the file.

The accepted sed answer is good for most situations, and the one you should probably use if you're concerned with other people modifying it. Using sed -i does have mild portability issues however, and changing more than one configuration value requires writing the file multiple times. For an elegant (if mildly esoteric) solution, you can use ed:

ed /etc/ssh/sshd_config << EOF
%s/^PermitRootLogin.*/PermitRootLogin no
%s/^ChallengeResponseAuthentication.*/ChallengeResponseAuthentication no
wq
EOF

Explanation of each line:

  1. Run ed on the file and provide a set of commands to standard input with a heredoc
  2. Run a global substitute (%s), on the file, replacing lines that match the ^PermitRootLogin.* regex with PermitRootLogin no
  3. Same as 2, but with ChallengeResponseAuthentication
  4. Write the file and quit

You'll probably raise a few eyebrows using ed but it's portable, easy to extend, and only writes to disk once.

This can be further simplified as a few liner script where you pass variables to change and it can change on the fly for you. Check all sorts of syntax and make sure end results are all perfectly done.

yes and no can be converted into a switch as well

To User Scripts Do

replace_string.sh sshd_config.old PasswordAuthentication PermitRootLogin

My Script Looks Like this


#! /bin/bash 
sshd_config_file=$1

search_replace() 
{
grep -i "^${search_string} yes$" ${sshd_config_file} | grep -v "^#"|grep -v "${search_string} no" || sed -i "" "s|^#*${search_string}.*$|${search_string} yes|" ${sshd_config_file} 
}

#for search_string in $@; do 
for search_string in $(eval echo ${*:2}); do 
  search_replace
done



Related