ZNHOO Whatever you are, be a good one!

sudo

  1. sudo Front End
  2. Installation
  3. Policy Syntax
  4. Configure Policy
  5. Run Command
    1. Preserve Env

sudo Front End

sudo allows a permitted user to execute a command as the superuser (by default) or another user (if provided), as specified by a security policy.

sudo is just a front end and depends on a plugin architecture to load different security policies. We can enable plugins in /etc/sudo.conf as below. The default plugin is sudoers.

[root@ip-172-31-34-111 ~]# grep ^Plugin /etc/sudo.conf
Plugin sudoers_policy sudoers.so
Plugin sudoers_io sudoers.so

Once a plugin is enabled, we can configure its security policies. For example, security polices of plugin sudoers is located in file /etc/sudoers or /etc/sudoers.d.

Third party developers can distribute their own plugins and security policies to work seamlessly with the sudo front end. This post only talks about plugin sudoers.

Installation

[root@host ~]# pacman -S sudo

List sudoers policy of a user.

[root@host ~]# sudo -ll -U <user>

Policy Syntax

Check the SUDOERS FILE FORMAT section in the man page of suoers(5).

sudoers policy is prescribed with Extented Backus-Naur Form (EBNF). Do not despair if you are unfamiliar with EBNF; it is fairly simple and take the chance to study it.

An EBNF definition looks like the following.

symbol ::= definition | alternate1 | alternate2

Special characters ? (optional, 0 or 1), * (zero or more), and + (one or more) mean the same thing as regex. Single quotes denote verbatim character string as opposed to a defined symbol (variable) name. The rest part of EBNF of sudoer is left to yourself.

The sudoers file is composed of two types of entries: alias and user specification. The advantage of alias is to group multiple items together and assign a meaningful label. There exist four kinds of aliases, namely 'User_Alias', 'Runas_Alias', 'Host_Alias' and 'Cmd_Alias'. When there exist only few accounts and hostnames concerned, alias is optional.

When a user is matched by multiple entries, they are applied in order but only the last match takes effect. Let's forget about EBNF and alias part (not the focus of this post). The following is a sample of user specification. It is divided into two groups by the = separator. The left side defines the object while the right side defines the actions.

USER HOSTNAME = (RUNAS_USER:RUNAS_GROUP) COMMANDS
  1. USER is the primary key, for which this entry is designated.

    It can be a user name (e.g. jim, user ID (#1000), group name (%wheel), group ID (%#10) etc.

  2. HOSTNAME defines the location where this user specification takes effect. The sudoers file can be shared among multiple systems.

    This field can be hostname, IP address, network range etc. Attention please; the loopback interface, namely locahost or 127.0.0.1 will be ignored by sudoers.

  3. '(RUNAS_USER:RUNAS_GROUP) COMMANDS' refers to the combination of target user/group and target commands. They comprise an integrated component.

    1. '(RUNAS_USER:RUNAS_GROUP)' is an optional field. By default, it is assumed to be '(root:root)'. Usually, 'RUNAS_GROUP' is optional and defaults to that of 'RUNAS_USER'.
    2. 'COMMANDS' specifies the list of commands can be executed. By default, a user must authenticate himself by password before executing the command. We can prepend 'COMMANDS' with 'NOPASSWD:'.

Multiple components are separated by comma. Special value 'ALL' matches everything (e.g. any hostnames).

Entry below means on debain host, account jim can run /bin/ls as acount operator; he can also run /bin/kill and /usr/bin/apg as root.

jim debian = (operator) NOPASSWD: /bin/ls, (root) /bin/kill, /usr/bin/apg

Let us see a more aggressive config.

jim ALL=(ALL:ALL) NOPASSWD: ALL

Configure Policy

  1. To modify sudoers policy, please use visudo.
  2. Personally, I'd like to put per-user policy file under directory /etc/sudoers.d/. Usually, we name the file after the user account in question.

    To make the directory loaded, add the #includedir directive in /etc/sudoers. As per the man page, sudo will read each policy file in /etc/sudoers.d, skipping filenames that contain a dot '.' ro end with tilde '~'.

Below is an example.

[root@host ~]# visudo -f /etc/sudoers
[root@host ~]# visudo -f /etc/sudoers.d/user
#
gray	localhost	=	(jim) ALL
%wheel	ALL		=	(root) /bin/iptables

Once finished editting, use the option -c to verify syntax.

[root@host ~]# visudo -c -f /etc/sudoers
[root@host ~]# visudo -c -f /etc/sudoers.d/user

Then check the result:

[root@host ~]# sudo -ll -U <user>

Run Command

# run as root
[user@host ~]$ sudo <command>

# run as specific user
[userhost ~]$ sudo -u <target-user> <command>

If the command to ran is to edit a protected file, sudoedit is preferred than sudo vim.

Preserve Env

Sometimes it is useful to bring current Envs to target user (e.g. root) and commands, so that we don't bother to set again. There are multiple methods to achieve this.

Set an Env in current user.

[ec2-user@ip-172-31-34-111 ~]$ export MY_VAR=123

[ec2-user@ip-172-31-34-111 ~]$ echo $MY_VAR
123

[ec2-user@ip-172-31-34-111 ~]$ printenv | grep MY_VAR
MY_VAR=123

Three methods as below.

  1. Globally set by plugin sudoers. By default, /etc/sudoers enables option env_reset to reset Envs so that commands are ran in a new, minimal environment, including TERM, PATH, HOME, MAIL, SHELL, LOGNAME, USER, USERNAME, etc.

    We can add to the default list new Envs by option env_keep and/or env_check like below. Envs in env_keep will be preserved while env_check will remove Envs that is unsafe. Unsafe Envs refer to those Envs that contain character / or %.

    Defaults    env_reset
    Defaults    env_keep =  "COLORS DISPLAY HOSTNAME HISTSIZE KDEDIR LS_COLORS"
    Defaults    env_keep += "MAIL PS1 PS2 QTDIR USERNAME LANG LC_ADDRESS LC_CTYPE"
    Defaults    env_keep += "LC_COLLATE LC_IDENTIFICATION LC_MEASUREMENT LC_MESSAGES"
    Defaults    env_keep += "LC_MONETARY LC_NAME LC_NUMERIC LC_PAPER LC_TELEPHONE"
    Defaults    env_keep += "LC_TIME LC_ALL LANGUAGE LINGUAS _XKB_CHARSET XAUTHORITY"
       
    Defaults    env_keep += "MY_VAR1 MY_VAR2"
    
    Defaults    env_check = "MY_VAR1 MY_VAR2"
    

    Alternatively, we can even prefix env_reset with ! to disable the default behaviour, so that all Envs are preserved. But this is not recommended.

    Defaults !env_reset
    

    Setting by security policies is globally available and applies to all scenarios.

    Refer to 'Command environment' section of sudoers man page.

  2. Set sudo option -E, --preserve-env[=list] to sudo. We can restrict Envs by opt-arg =list.

    This method is convenient, especially for user-defined Envs.

  3. Set directly on CLI.

    [ec2-user@ip-172-31-34-111 ~]$ sudo [env] MY_VAR=$MY_VAR printenv | grep MY_VAR
    MY_VAR=123
    

    The env is optional but suggested to be present when preserving default Envs like 'PATH'.

    [ec2-user@ip-172-31-34-111 ~]$ sudo printenv | grep ^PATH
    PATH=/sbin:/bin:/usr/sbin:/usr/bin
    
    [ec2-user@ip-172-31-34-111 ~]$ sudo env "PATH=$PATH" printenv | grep ^PATH
    PATH=/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/home/ec2-user/.local/bin:/home/ec2-user/bin
    

    This method is stupid but powerful.

Before closing this section, please be cautious that bringing current Envs to target user/command is dangerous. For example, sudo by default enables option secure_path to restrict the PATH as below, so that only secure PATH is included. That is the reason we need to add env when preserving PATH in method 3 above.

[root@ip-172-31-34-111 ~]# grep secure_path /etc/sudoers
Defaults    secure_path = /sbin:/bin:/usr/sbin:/usr/bin

Of course, if you'd like, updating secure_path policy is also fine.