sudo
13 Apr 2019 • Leave Commentssudo 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) COMMANDS
-
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. -
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.
-
'(RUNAS_USER) COMMANDS' refers to the combination of target user and target commands. They comprise an integrated component.
- '(RUNAS_USER)' is an optional field. By default, it is assumed to be '(root)'.
- COMMANDS specifies what commands can be executed.
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) /bin/ls, (root) /bin/kill, /usr/bin/apg
There are also other interesting fields like 'NOPASSWD:', which is left to yourself.
Configure Policy
- To modify sudoers policy, please use visudo.
-
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.
-
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/orenv_check
like below. Envs inenv_keep
will be preserved whileenv_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.
-
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.
-
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.