Is 'echo "Defaults insults" >> /etc/sudoers' safe?

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

I poked around a bit and seems includedir support is available in sudo going back a long way (~2001 from what I can tell), so it should be supported in RHEL 5. Look for an include directive in your sudoers:

sudo grep '#includedir' /etc/sudoers

If you get a result, then ideally you should add and remove files in that directory, and it is usually /etc/sudoers.d. This path may be difficult to determine in a shell script.

At any rate, assuming sudoers.d, you should add files with permissions at most 0600 in that directory. Something like:

sudo sh -c '
  umask 0177;
  filename="/etc/sudoers.d/$1";
  echo 'Defaults insults' > "$filename";
  visudo -cf "$filename" || rm "$filename"
' _ 99-insults

(Where 99-insults is the name of the file to be created in sudoers.d.) Then removing the setting is as simple as:

sudo rm /etc/sudoers.d/99-insults

And indeed, that's what the last part of the sh -c command does - remove this file if visudo fails its check, so that you're not left with a broken sudoers.


You can use tee -a as the EDITOR:

$ sudo VISUAL='tee -a' visudo <<<"Defaults insults"
Defaults insults
$ sudo tail /etc/sudoers
# Members of the admin group may gain root privileges
%admin ALL=(ALL) ALL

# Allow members of group sudo to execute any command
%sudo   ALL=(ALL:ALL) ALL

# See sudoers(5) for more information on "#include" directives:

#includedir /etc/sudoers.d
Defaults insults

I don't know if EL5's sudo has includedir support, but if it does, then create new files in it with visudo -f, please.

Tags:

Sudo