Sudo – Is ‘echo “Defaults insults” >> /etc/sudoers’ Safe?

sudo

Is this safe?

echo "Defaults    insults" >> /etc/sudoers

If yes, can I do this?

echo "## First line" >> /etc/sudoers
echo "### Second line" >> /etc/sudoers
echo "Defaults    insults" >> /etc/sudoers
echo "### Totally the last line" >> /etc/sudoers

Is there a better way to do this incorporating visudo?

I'm making a bash script, this bit needs to turn insults on and off.

Best Answer

There are at least 3 ways in which it can be dangerous:

  1. If /etc/sudoers doesn't end in a newline character (which sudo and visudo allow), for instance, if it ends in a non-terminated #includedir /etc/sudoers.d line, your command will make it:

    #includedir /etc/sudoers.dDefaults insults
    

    which will break it and render sudo unusable.

  2. echo may fail to write the full string, for instance if the file system is full. For instance, it may just be able to write Defaults in. Which again will break your sudoers file.
  3. On a machine with multiple admins, if both attempt to modify /etc/sudoers at the same time, the data they write may be interlaced.

visudo avoids these problems because it lets you edit a temporary file instead (/etc/sudoers.tmp), detects if the file was modified (unfortunately not if the file was successfully modified as it doesn't seem to be checking the editor's exit status), checks the syntax, and does a rename (an atomic operation) to move the new file in place. So it will either successfully update the file (provided your editor also leaves the file unmodified if it fails to write the new one) or fail if it can't or the syntax is invalid.

visudo also guards against several persons editing the sudoers files at the same time.

Now, reliably using visudo in an automatic fashion is tricky as well. There are several problems with that:

  • You can specify an editor command for visudo with the VISUAL environment variable (takes precedence over EDITOR), but only if the env_editor option has not been disabled.
  • my version of visudo at least, under some conditions, edits all of /etc/sudoers and all the files it includes (runs $VISUAL for all of them). So you have to make sure your $VISUAL only modifies /etc/sudoers.
  • as seen above, it doesn't check the exit status of the editor. So you need to make sure the file your editor saves is either successfully written or not modified at all.
  • It prompts the user in case of problem.

Addressing all those is a bit tricky. Here is how you could do it:

NEW_TEXT='Defaults insults' \
  CODE='
    if [ "$2" = /etc/sudoers.tmp ]; then
      printf >&2 "Editing %s\n" "$2"
      umask 077
      {
        cat /etc/sudoers.tmp && printf "\n%s\n" "$NEW_TEXT"
      } > /etc/sudoers.tmp.tmp &&
        mv -f /etc/sudoers.tmp.tmp /etc/sudoers.tmp
    else
      printf >&2 "Skipping %s\n" "$2"
    fi' \
  VISUAL='sh -fc IFS=:;$1 sh eval:eval:"$CODE"' visudo < /dev/null

Won't work if env_editor is unset.

On a GNU system, a better alternative would be to use sed -i which should leave sudoers.tmp unmodified if it fails to write the newer version:

Add insults:

SED_CODE='
  /^[[:blank:]]*Defaults.*insults/,${
    /^[[:blank:]]*Default/s/!*\(insults\)/\1/g
    $q
  }
  $a\Defaults insults' \
CODE='
  if [ "$2" = /etc/sudoers.tmp ]; then
    printf >&2 "Editing %s\n" "$2"
    sed -i -- "$SED_CODE" "$2"
  else
    printf >&2 "Skipping %s\n" "$2"
  fi' \
VISUAL='sh -fc IFS=:;$1 sh eval:eval:"$CODE"' visudo < /dev/null

Remove insults:

SED_CODE='
  /^[[:blank:]]*Defaults.*insults/,${
    /^[[:blank:]]*Defaults/s/!*\(insults\)/!\1/g
    $q
  }
  $a\Defaults !insults' \
CODE='
  if [ "$2" = /etc/sudoers.tmp ]; then
    printf >&2 "Editing %s\n" "$2"
    sed -i -- "$SED_CODE" "$2"
  else
    printf >&2 "Skipping %s\n" "$2"
  fi' \
VISUAL='sh -fc IFS=:;$1 sh eval:eval:"$CODE"' visudo < /dev/null
Related Question