SUDOERS(5)

๐Ÿ“ฆ version 1.9.17p2, in ubuntu questing, ubuntu resolute, ubuntu stonking

๐Ÿ’– sudoers(5) ๐Ÿ’–

File Formats Manual, the bestie of all sudo docs ๐ŸŽ€

โœจ NAME โœจ

okay so the name is sudoers ๐ŸŒธ and it's the default sudo security policy plugin! it's literally the one that decides who gets to use sudo ๐Ÿ‘‘

โœจ DESCRIPTION โœจ

okay bestie, so the sudoers policy plugin figures out a user's sudo privileges ๐Ÿ’… it's the default sudo policy plugin, so it just shows up. the policy is driven by the /etc/sudoers file or, optionally, in LDAP โ˜๏ธ the policy format gets the full breakdown in the "SUDOERS FILE FORMAT" section, and if you want to store sudoers policy in LDAP, check out sudoers.ldap(5) ๐Ÿฆ‹

โœจ Configuring sudo.conf for sudoers โœจ

so sudo checks the sudo.conf(5) file to see which plugins it should load ๐Ÿงš if there's no sudo.conf(5) file, or it has no Plugin lines in it, sudoers gets used for auditing, policy decisions and I/O logging. like, it's the default girlie for everything ๐Ÿ’œ if you want to explicitly tell sudo.conf(5) to use the sudoers plugin, you can do this:

Plugin sudoers_audit sudoers.so
Plugin sudoers_policy sudoers.so
Plugin sudoers_io sudoers.so

starting with sudo 1.8.5, you can pass optional arguments to the sudoers plugin in the sudo.conf(5) file ๐ŸŽ€ plugin arguments (if you have any) go after the path to the plugin, so after sudoers.so. these arguments only do something for the plugin that opens (and parses) the sudoers file, so they're kinda picky ๐Ÿ™ˆ

for sudo version 1.9.1 and higher, that plugin is sudoers_audit. for older versions, it's sudoers_policy โœจ you can throw in multiple arguments, just separate them with white space. for example:

Plugin sudoers_audit sudoers.so sudoers_mode=0400 error_recovery=false

these are the plugin arguments that are supported, bestie ๐Ÿซถ

  • error_recovery=bool โ€” this one controls whether sudoers tries to bounce back from syntax errors in the sudoers file ๐Ÿ”„ when it's set to true (the default), sudoers will try to recover by tossing out the part of the line with the error, all the way to the end of that line. setting it to false turns error recovery off. before version 1.9.3, no error recovery was done at all, so it was a whole vibe ๐Ÿ˜ญ
  • ignore_perms=bool โ€” this one can disable the security checks when the sudoers file is loaded ๐Ÿ™ˆ if you turn it on, the sudoers file gets loaded no matter who owns it or what its file mode is. this one is meant for testing only, so please don't turn it on on production systems, it's giving danger ๐Ÿ’€
  • ldap_conf=pathname โ€” use this to override the default path to the ldap.conf file ๐Ÿ—บ๏ธ
  • ldap_secret=pathname โ€” use this to override the default path to the ldap.secret file ๐Ÿ”
  • sudoers_file=pathname โ€” use this to override the default path to the sudoers file ๐Ÿ“‚
  • sudoers_uid=user-ID โ€” use this to override the default owner of the sudoers file ๐Ÿ‘‘ it should be a numeric user-ID.
  • sudoers_gid=group-ID โ€” use this to override the default group of the sudoers file ๐Ÿซง it must be a numeric group-ID, not a group name, so don't get it twisted ๐Ÿ™ƒ
  • sudoers_mode=mode โ€” use this to override the default file mode for the sudoers file ๐Ÿ”’ it should be an octal value.

for more on configuring sudo.conf(5), go check out its manual ๐Ÿ“–

โœจ User Authentication โœจ

the sudoers security policy says most users have to authenticate themselves before they can use sudo ๐Ÿ” a password isn't needed if the invoking user is root, if the target user is the same as the invoking user, or if the policy has turned off authentication for that user or command. like, no password needed, it's giving free pass ๐Ÿฆ‹ unlike su(1), when sudoers needs authentication, it checks the invoking user's credentials, not the target user's (or root's) credentials ๐Ÿ™Œ this can be changed with the rootpw, targetpw and runaspw flags, which we'll get into later.

if someone who isn't listed in the policy tries to run a command with sudo, mail gets sent to the proper authorities ๐Ÿ“ง the address for that mail is configurable through the mailto Defaults entry (also later!) and it defaults to root.

no mail gets sent if an unauthorized user tries to run sudo with the -l or -v option, unless there's an authentication error and either the mail_always or mail_badpass flags are enabled ๐Ÿ™ˆ this lets users figure out on their own whether they're allowed to use sudo, which is honestly so considerate. by default, every attempt to run sudo gets logged (successful or not), no matter whether mail was sent or not ๐Ÿ“

if sudo is run by root and the SUDO_USER environment variable is set, the sudoers policy uses that value to figure out who the actual user is ๐Ÿ•ต๏ธโ€โ™€๏ธ this lets someone log commands through sudo even when a root shell has been invoked, which is iconic. it also keeps the -e option useful even when it's invoked via a sudo-run script or program โœจ just note that the sudoers file lookup is still done for root, not for the user in SUDO_USER ๐Ÿ™ƒ

sudoers uses per-user time stamp files for credential caching ๐Ÿง once a user has been authenticated, a record gets written with the user-ID that was used to authenticate, the terminal session ID, the start time of the session leader (or parent process), and a time stamp (using a monotonic clock if one is available). then the user can use sudo without a password for a short period of time: 15 minutes, unless you override it with the timestamp_timeout option โณ by default, sudoers uses a separate record for each terminal, which means a user's login sessions get authenticated separately ๐Ÿ’… the timestamp_type option can be used to pick the type of time stamp record sudoers will use.

the tsdump utility, which comes with the sudo source distribution, can display the contents of a time stamp file ๐Ÿ‘€ see sudoers_timestamp(5) for all the details on the time stamp file format.

โœจ Logging โœจ

by default, sudoers logs both successful and unsuccessful attempts (plus errors) ๐Ÿ“œ the log_allowed and log_denied flags control this behaviour. messages can go to syslog(3), a log file, or both ๐Ÿ“š the default is syslog(3), but you can change that with the syslog and logfile settings. check out "EVENT LOGGING" for a description of the log file format ๐ŸŒท

sudoers can also run a command in a pseudo-terminal and log input and/or output ๐ŸŽฌ standard input, standard output, and standard error can all be logged, even when they're not connected to a terminal. for more about I/O logging, see the "I/O LOGGING" section ๐Ÿ’œ

starting with version 1.9, the log_servers setting can be used to send event and I/O log data to a remote server running sudo_logsrvd, or to another service that implements the protocol described by sudo_logsrv.proto(5) ๐Ÿ›ฐ๏ธ

โœจ Command environment โœจ

okay this part is so important, bestie. environment variables can totally influence how a program behaves, so sudoers gives you a way to restrict which variables from the user's environment get inherited by the command being run ๐Ÿงš there are two distinct ways sudoers can handle environment variables ๐ŸŒˆ

by default, the env_reset flag is enabled ๐Ÿงผ this makes commands run with a brand new, minimal environment. on AIX (and on Linux systems without PAM), the environment gets initialized with the contents of the /etc/environment file ๐Ÿ“„ the HOME, MAIL, SHELL, LOGNAME and USER environment variables get set based on the target user, and the SUDO_* variables get set based on the invoking user ๐Ÿ’… other variables, like DISPLAY, PATH and TERM, are kept from the invoking user's environment if the env_check or env_keep options allow it โœจ a few variables get special treatment though. if PATH and TERM aren't kept from the user's environment, they get set to default values ๐Ÿซถ LOGNAME and USER are handled as a single entity: if one of them is kept (or removed) from the user's environment, the other one is too ๐Ÿ‘ฏ if LOGNAME and USER are supposed to be kept but only one of them is in the user's environment, the other gets set to the same value. this stops you from ending up with an inconsistent environment where one of the user name variables says the invoking user and the other says the target user ๐Ÿ™ˆ and environment variables whose value begins with โ€˜()โ€™ get removed unless both the name and value parts are matched by env_keep or env_check, because bash may read them as functions ๐Ÿ’€ before version 1.8.11, those were always removed.

but if the env_reset flag is disabled, any variables that aren't explicitly denied by the env_check and env_delete options get allowed, and their values are inherited from the invoking process ๐Ÿ˜ฌ before version 1.8.21, environment variables with a value beginning with โ€˜()โ€™ were always removed. starting with version 1.8.21, a pattern in env_delete is used to match bash shell functions instead ๐Ÿงฉ since it's impossible to block every potentially dangerous environment variable, using the default env_reset behaviour is strongly encouraged ๐Ÿซถ

environment variables specified by env_check, env_delete, or env_keep can include one or more * characters, which match zero or more characters ๐ŸŒŸ no other wildcard characters are supported.

by default, environment variables are matched by name โœจ however, if the pattern includes an equal sign (=), both the variable's name and value must match ๐ŸŽฏ for example, a bash shell function could be matched like this:

env_keep += "BASH_FUNC_my_func%%=()*"

without the =()* suffix, this wouldn't match, because bash shell functions aren't preserved by default ๐Ÿ™ƒ

the complete list of environment variables that are preserved or removed, as modified by global Defaults parameters in sudoers, gets displayed when sudo is run by root with the -V option ๐Ÿ“‹ the list of environment variables to remove depends on the operating system sudo is running on.

other settings can influence the command environment too, bestie ๐Ÿ’–

  • sudoers options such as always_set_home, secure_path, set_logname, set_home, and setenv.
  • Command tags, such as SETENV and NOSETENV. note that SETENV is implied if the command matched is ALL ๐Ÿ‘‘
  • sudo options, such as -E and -i.

Okay so the PAM stuff is next, and bestie, it's giving "who's the priority, PAM or sudo?" ๐Ÿ’… On systems where the pam_env module is enabled for sudo, variables in the PAM environment can get merged into the environment. If a variable is already in the user's environment, the PAM value only overrides it when sudoers didn't preserve that variable. ๐Ÿซถ When env_reset is enabled, variables kept from the invoking user's environment through the env_keep list win over the PAM ones. When env_reset is disabled, the invoking user's variables win over PAM ones, unless they match a pattern in the env_delete list. Main character energy for env_keep โœจ

The dynamic linker on most operating systems strips variables that control dynamic linking from the environment of set-user-ID executables, and that includes sudo ๐Ÿ˜ญ Depending on the OS that can mean _RLD*, DYLD_*, LD_*, LDR_*, LIBPATH, SHLIB_PATH, and others. These get removed before sudo even starts running, so sudo literally cannot preserve them. Not me being locked out of my own variables ๐Ÿ’€

Now the special case: if you use the -i option (initial login), sudoers sets up the environment no matter what env_reset is set to ๐ŸŒธ DISPLAY, PATH and TERM stay unchanged, while HOME, MAIL, SHELL, USER, and LOGNAME get set based on the target user. On AIX, and on Linux systems without PAM, the contents of /etc/environment get included too. Everything else gets removed unless env_keep or env_check lets it through, which we just covered ๐ŸŽ€

Last thing: the restricted_env_file and env_file files get applied if they exist ๐Ÿฆ‹ The variables in restricted_env_file go first and have the same restrictions as the invoking user's environment we talked about. The variables in env_file go last and are NOT subject to those restrictions, so it's the VIP lane. In both cases, a variable from the file only gets its specified value if it wouldn't clash with an existing environment variable. โœจ


โœจ SUDOERS FILE FORMAT โœจ

Okay the sudoers file is made of two kinds of entries: aliases (basically variables) and user specifications, which decide who may run what ๐Ÿ”ฅ

When multiple entries match a user, they get applied in order. If several match, the last one wins, which is not always the most specific one, so read carefully bestie ๐Ÿงš

The sudoers file grammar gets explained below in Extended Backus-Naur Form (EBNF). Don't panic if EBNF is new to you ๐Ÿ™ˆ It's honestly pretty simple, and the definitions below come with notes.

Resource limits

By default, sudoers uses the operating system's native way of setting resource limits for the target user ๐Ÿ’œ On Linux systems that's usually handled by the pam_limits.so PAM module. On some BSD systems, the /etc/login.conf file sets the resource limits for the user. On AIX, resource limits are configured in the /etc/security/limits file. If there's no system mechanism for per-user resource limits, the command runs with the same limits as the invoking user. The one exception is the core dump file size, which sudoers sets to 0 by default. Turning off core dumps by default helps avoid potential security problems where the core file gets treated as trusted input. Iconic safety move ๐Ÿ›ก๏ธ

Resource limits can also be set in the sudoers file itself, and when they are, they override the ones from the system. Check out the rlimit_as, rlimit_core, rlimit_cpu, rlimit_data, rlimit_fsize, rlimit_locks, rlimit_memlock, rlimit_nofile, rlimit_nproc, rlimit_rss, rlimit_stack options described below ๐Ÿ‘‘ Resource limits in sudoers can be written in one of these formats:

  • "value" โ€” Both the soft and hard limits get set to the same value. The special value "infinity" means unlimited, no cap, iykyk ๐ŸŒˆ
  • "soft,hard" โ€” Two comma-separated values. The first one sets the soft limit and the second one sets the hard limit. Both values need to be wrapped in double quotes, or the comma needs a backslash escape (\). The special value "infinity" can be used in place of either value โœจ
  • "default" โ€” The user's default resource limit gets used. This could be a user-specific value (see above), or the value the resource limit had when sudo was invoked, on systems that don't support per-user limits ๐Ÿ“
  • "user" โ€” The invoking user's resource limits get kept when the command runs ๐Ÿ’–

For example, if you want the old-school core dump file size behaviour back, you can add a line like this:

Defaults rlimit_core=default

Resource limits in sudoers are only supported in version 1.8.7 or higher. Be careful with older versions bestie ๐Ÿ˜ญ

Quick guide to EBNF

EBNF is a concise and exact way of describing the grammar of a language ๐Ÿฆ‹ Every EBNF definition is made up of production rules. For example:

symbol ::= definition | alternate1 | alternate2 ...

Each production rule references others, and together they make up a grammar for the language. EBNF also has some operators that lots of people already know from regular expressions. Don't mix them up with "wildcard" characters though, because those mean something different ๐Ÿ™ƒ

  • ? โ€” The preceding symbol (or group of symbols) is optional. It may appear once or not at all.
  • * โ€” The preceding symbol (or group of symbols) may appear zero or more times.
  • + โ€” The preceding symbol (or group of symbols) may appear one or more times.

You can use parentheses to group symbols together. To keep things clear, verbatim character strings (as opposed to symbol names) will be wrapped in single quotes ('') ๐Ÿ’…

Aliases

There are four kinds of aliases: User_Alias, Runas_Alias, Host_Alias and Cmnd_Alias. Starting with sudo 1.9.0, Cmd_Alias can be used in place of Cmnd_Alias if you like ๐Ÿ’œ

Alias ::= 'User_Alias'  User_Alias_Spec (':' User_Alias_Spec)* |
          'Runas_Alias' Runas_Alias_Spec (':' Runas_Alias_Spec)* |
          'Host_Alias'  Host_Alias_Spec (':' Host_Alias_Spec)* |
          'Cmnd_Alias'  Cmnd_Alias_Spec (':' Cmnd_Alias_Spec)* |
          'Cmd_Alias'   Cmnd_Alias_Spec (':' Cmnd_Alias_Spec)*

User_Alias ::= NAME

User_Alias_Spec ::= User_Alias '=' User_List

Runas_Alias ::= NAME

Runas_Alias_Spec ::= Runas_Alias '=' Runas_List

Host_Alias ::= NAME

Host_Alias_Spec ::= Host_Alias '=' Host_List

Cmnd_Alias ::= NAME

Cmnd_Alias_Spec ::= Cmnd_Alias '=' Cmnd_List

NAME ::= [Aโ€“Z]([Aโ€“Z][0โ€“9]_)*

Each alias definition looks like this:

Alias_Type NAME = item1, item2, ...

Where Alias_Type is one of User_Alias, Runas_Alias, Host_Alias, or Cmnd_Alias. A NAME is a string of uppercase letters, numbers, and underscores (_). A NAME has to start with an uppercase letter ๐ŸŽ€ You can put several alias definitions of the same type on one line by separating them with a colon (:). For example:

Alias_Type NAME = item1, item2, item3 : NAME = item4, item5
Redefining an existing alias is a syntax error, so don't do it bestie ๐Ÿšซ You can reuse the same name for aliases of different types, but it's not recommended. Messy ๐Ÿ’€

The rules for what counts as a valid alias member come next ๐ŸŒธ

User_List ::= User |
              User ',' User_List

User ::= '!'* user name |
         '!'* #user-ID |
         '!'* %group |
         '!'* %#group-ID |
         '!'* +netgroup |
         '!'* %:nonunix_group |
         '!'* %:#nonunix_gid |
         '!'* User_Alias

A User_List is made of one or more user names, user-IDs (prefixed with #), system group names and IDs (prefixed with % and %# respectively), netgroups (prefixed with +), non-Unix group names and IDs (prefixed with %: and %:# respectively), and User_Aliases. Each list item can be prefixed with zero or more ! operators. An odd number of ! negates the item, and an even number just cancels out ๐Ÿ™ƒ User netgroups are matched using only the user and domain members. The host member isn't used when matching.

A user name, user-ID, group, group-ID, netgroup, nonunix_group or nonunix_gid can be wrapped in double quotes so you don't have to escape special characters. You can also write special characters in escaped hex mode, for example \x20 for a space. When you use double quotes, any prefix characters have to go inside the quotes too โœจ

The actual nonunix_group and nonunix_gid syntax depends on the group provider plugin being used. For instance, the QAS AD plugin supports these formats:

  • Group in the same domain: "%:Group Name"
  • Group in any domain: "%:Group Name@FULLY.QUALIFIED.DOMAIN"
  • Group SID: "%:S-1-2-34-5678901234-5678901234-5678901234-567"

Check out โ€œGROUP PROVIDER PLUGINSโ€ for more info ๐Ÿงš

Quotes around group names are optional. Unquoted strings need a backslash (\) to escape spaces and special characters. See โ€œOther special characters and reserved wordsโ€ for a list of characters that need escaping ๐Ÿฆ‹

Runas_List ::= Runas_Member |
               Runas_Member ',' Runas_List

Runas_Member ::= '!'* user name |
                 '!'* #user-ID |
                 '!'* %group |
                 '!'* %#group-ID |
                 '!'* %:nonunix_group |
                 '!'* %:#nonunix_gid |
                 '!'* +netgroup |
                 '!'* Runas_Alias |
                 '!'* ALL

A Runas_List is similar to a User_List, except that instead of User_Aliases it can hold Runas_Aliases. User names and groups get matched as strings, which means two users (or groups) with the same user (or group) ID are considered distinct ๐Ÿ˜ญ If you want to match every user name that shares a user-ID (like root and toor), you can use a user-ID instead of a name (#0 in the example given). The user-ID or group-ID in a Runas_Member doesn't need to be listed in the password or group database ๐Ÿ’…

Host_List ::= Host |
              Host ',' Host_List

Host ::= '!'* host name |
         '!'* ip_addr |
         '!'* network(/netmask)? |
         '!'* +netgroup |
         '!'* Host_Alias |
         '!'* ALL

Okay so the host stuff keeps going, bestie ๐Ÿ’– A Host_List is one or more host names, IP addresses, network numbers, netgroups (prefixed with +), and other aliases ๐Ÿฆ‹ Same deal as before: you can negate any item with the ! operator ๐Ÿ™…โ€โ™€๏ธ

Netgroups get matched using the host (qualified and unqualified) and domain members only. The user member is NOT used when matching ๐Ÿ™ˆ

If you give a network number without a netmask, sudo checks each of the local host's network interfaces. If the network number matches one of them, sudo uses that interface's netmask โœจ The netmask can be written in standard IP address notation (like 255.255.255.0 or ffff:ffff:ffff:ffff::) or CIDR notation (number of bits, like 24 or 64) ๐ŸŒธ

A host name can use shell-style wildcards (see the โ€œWildcardsโ€ section below) ๐ŸŽ€ BUT, unless the hostname command on your machine returns the fully qualified host name, you'll need the fqdn flag for wildcards to actually be useful ๐Ÿ˜ญ

Also, sudo only inspects actual network interfaces, so IP address 127.0.0.1 (localhost) will never match ๐Ÿ’€ And the host name โ€œlocalhostโ€ only matches if that's the actual host name, which is usually only the case for non-networked systems ๐Ÿงš


Now let's get into the digest grammar, it's giving formal ๐Ÿค“

digest ::= [Aโ€“Faโ€“f0โ€“9]+ |
           [Aโ€“Zaโ€“z0โ€“9\+/=]+

Digest_Spec ::= "sha224" ':' digest |
                "sha256" ':' digest |
                "sha384" ':' digest |
                "sha512" ':' digest

Digest_List ::= Digest_Spec |
                Digest_Spec ',' Digest_List

Cmnd_List ::= Cmnd |
              Cmnd ',' Cmnd_List

command name ::= regex |
                 file name

command ::= command name |
            command name args |
            command name regex |
            command name '""' |
            ALL

Edit_Spec ::= "sudoedit" file name+ |
              "sudoedit" regex |
              "sudoedit"

List_Spec ::= "list"

Cmnd ::= Digest_List? '!'* command |
         '!'* directory |
         '!'* Edit_Spec |
         '!'* List_Spec |
         '!'* Cmnd_Alias

A Cmnd_List is a list of one or more commands, directories, or aliases ๐Ÿซถ A command is a fully qualified file name (wildcards allowed, see โ€œWildcardsโ€), or a regular expression that starts with ^ and ends with $ (see the โ€œRegular expressionsโ€ section below) ๐Ÿ’… A directory is a fully qualified path name ending in a / ๐ŸŒˆ When you put a directory in a Cmnd_List, the user can run any file inside that directory (but NOT in any sub-directories in there) ๐Ÿ“

If no command line arguments are specified, the user may run the command with whatever arguments they want ๐Ÿ‘‘ Command line arguments can include wildcards or be a regular expression that starts with ^ and ends with $ โœจ If the command line arguments are "", the command can only run with NO arguments ๐Ÿ™…โ€โ™€๏ธ

If a Cmnd has command line arguments, the arguments in the Cmnd must match what the user types on the command line ๐Ÿ’– If the arguments in a Cmnd begin with the ^ character, they're treated as a regular expression and matched that way. Otherwise, shell-style wildcards are used ๐Ÿฆ‹ Unless you're using a regular expression, these characters must be escaped with a \ if they show up in command arguments: ,, :, =, \ ๐Ÿ™ˆ And if you want an argument starting with ^ to NOT be treated as a regular expression, escape the ^ with a \ ๐Ÿงš

There are two built-in commands in sudo itself: โ€œlistโ€ and โ€œsudoeditโ€ ๐ŸŽ€ Unlike other commands, these two must be written in the sudoers file WITHOUT a leading path ๐Ÿ™…โ€โ™€๏ธ

The โ€œlistโ€ built-in lets a user list another user's privileges with sudo's -U option, like โ€œsudo -l -U otheruserโ€ ๐ŸŒธ A user with the โ€œlistโ€ privilege can list another user's privileges even if they aren't allowed to run commands as that user ๐Ÿ‘€ By default, only root, or a user who can run any command as root or the specified user on the current host, may use the -U option ๐Ÿ’… No command line arguments may be specified with the โ€œlistโ€ built-in ๐Ÿšซ

The โ€œsudoeditโ€ built-in lets a user run sudo with the -e option (or as sudoedit) โœจ It can take command line arguments just like a normal command ๐Ÿ’– Unlike other commands, โ€œsudoeditโ€ is built into sudo itself and must be written in the sudoers file WITHOUT a leading path ๐Ÿฆ‹ If there IS a leading path, like /usr/bin/sudoedit, the path name gets silently converted to โ€œsudoeditโ€ ๐Ÿคซ A fully-qualified path for sudoedit is treated as an error by visudo ๐Ÿ˜ญ

A command may be preceded by a Digest_List, which is a comma-separated list of one or more Digest_Spec entries ๐Ÿ”ฅ If there's a Digest_List, the command only matches if it can be verified using one of the SHA-2 digests in the list ๐Ÿ” Starting with version 1.9.0, the ALL reserved word can be used together with a Digest_List ๐ŸŒˆ The supported digest formats are sha224, sha256, sha384, and sha512 ๐Ÿ“ The string can be in hex or base64 format (base64 is more compact) ๐Ÿ’œ There are several utilities that can generate SHA-2 digests in hex format, like openssl, shasum, sha224sum, sha256sum, sha384sum, and sha512sum ๐Ÿงš

For example, using openssl:

$ openssl dgst -sha224 /bin/ls
SHA224(/bin/ls)= 118187da8364d490b4a7debbf483004e8f3e053ec954309de2c41a25

You can also use openssl to generate base64 output:

$ openssl dgst -binary -sha224 /bin/ls | openssl base64
EYGH2oNk1JC0p9679IMATo8+BT7JVDCd4sQaJQ==
๐Ÿšจ Warning, bestie: if the user has write access to the command itself (directly or via a sudo command), it may be possible for them to replace the command AFTER the digest check has been done but BEFORE the command runs. A similar race condition exists on systems that lack the fexecve(2) system call when the directory the command lives in is writable by the user. See the description of the fdexec setting for more info on how sudo executes commands that have an associated digest ๐Ÿ˜ญ

Command digests are only supported by version 1.8.7 or higher ๐Ÿฅบ

โœจ Defaults โœจ

Certain configuration options can be changed from their default values at run-time via one or more Default_Entry lines ๐ŸŽ€ These can affect all users on any host (Defaults), all users on a specific host (Defaults@host), a specific user (Defaults:user), a specific command (Defaults!cmnd), or commands being run as a specific user (Defaults>runasuser) ๐ŸŒˆ

No white space is allowed between Defaults and the @, :, !, or > characters ๐Ÿ™…โ€โ™€๏ธ You can use a comma-separated list in place of a single value after the @, :, !, or > character, but using an alias instead of a list is often improve readability ๐Ÿ’… Per-command entries may NOT include command line arguments. If you need arguments, define a Cmnd_Alias and reference that instead ๐Ÿง 

Default_Type ::= 'Defaults' |
                 'Defaults@' Host_List |
                 'Defaults:' User_List |
                 'Defaults!' Cmnd_List |
                 'Defaults>' Runas_List

Default_Entry ::= Default_Type Parameter_List

Parameter_List ::= Parameter |
                   Parameter ',' Parameter_List

Parameter ::= Parameter '=' Value |
              Parameter '+=' Value |
              Parameter '-=' Value |
              '!'* Parameter

Parameters can be flags, integer values, strings, or lists ๐Ÿ“ Flags are implicitly boolean and can be turned off with the ! operator ๐Ÿ™ƒ Some integer, string, and list parameters can also be used in a boolean context to disable them ๐Ÿซ  Values may be wrapped in double quotes ("") when they contain multiple words โœจ Special characters can be escaped with a backslash (\) ๐Ÿฆ‹

To include a literal backslash in a command line argument, you have to escape the backslash TWICE ๐Ÿคฏ For example, to match \n as part of a command line argument, you must use \\\\n in the sudoers file ๐Ÿ˜ญ This is because there are two levels of escaping: one in the sudoers parser itself, and another when command line arguments are matched by the fnmatch(3) or regexec(3) function ๐Ÿงš

Lists have two extra assignment operators, += and -= ๐Ÿ’œ These are used to add to and delete from a list, respectively ๐ŸŒธ It is NOT an error to use the -= operator to remove an element that doesn't exist in a list ๐Ÿ™Œ

In general, Defaults settings are applied in order, so later entries override earlier ones ๐Ÿ” However, command-specific Defaults settings are applied later, once the command's path is known ๐Ÿ‘‘ Also, the following Defaults settings must be applied before all others, since they may affect subsequent entries: fqdn, group_plugin, ignore_unknown_defaults, match_group_by_gid, runas_default, sudoers_locale ๐Ÿซถ

See โ€œSUDOERS OPTIONSโ€ for a list of supported Defaults parameters ๐Ÿ“–

โœจ User specification โœจ

User_Spec ::= User_List Host_List '=' Cmnd_Spec_List \
              (':' Host_List '=' Cmnd_Spec_List)*

Cmnd_Spec_List ::= Cmnd_Spec |
                   Cmnd_Spec ',' Cmnd_Spec_List

Cmnd_Spec ::= Runas_Spec? Option_Spec* (Tag_Spec ':')* Cmnd

Runas_Spec ::= '(' Runas_List? (':' Runas_List)? ')'

Option_Spec ::= (SELinux_Spec | Date_Spec | Timeout_Spec | Chdir_Spec | Chroot_Spec)

SELinux_Spec ::= ('ROLE=role' | 'TYPE=type')

AppArmor_Spec ::= 'APPARMOR_PROFILE=profile'

Date_Spec ::= ('NOTBEFORE=timestamp' | 'NOTAFTER=timestamp')

Timeout_Spec ::= 'TIMEOUT=timeout'

Chdir_Spec ::= 'CWD=directory'

Chroot_Spec ::= 'CHROOT=directory'

Tag_Spec ::= ('EXEC' | 'NOEXEC' | 'FOLLOW' | 'NOFOLLOW' |
              'LOG_INPUT' | 'NOLOG_INPUT' | 'LOG_OUTPUT' |
              'NOLOG_OUTPUT' | 'MAIL' | 'NOMAIL' | 'INTERCEPT' |
              'NOINTERCEPT' | 'PASSWD' | 'NOPASSWD' | 'SETENV' |
              'NOSETENV')

A user specification decides which commands a user may run (and as WHO) on specified hosts ๐ŸŽ€ By default, commands run as root (unless runas_default has been set to something else), but you can change that on a per-command basis ๐Ÿ‘‘

The basic structure of a user specification is โ€œwho where = (as_whom) whatโ€ ๐Ÿ’– Let's break that down into its constituent parts:

๐ŸŽ€ Runas_Spec ๐ŸŽ€

okay so a Runas_Spec decides who a command is allowed to run as ๐Ÿ‘‘ A full one is two Runas_Lists (as defined above) separated by a colon (:) and wrapped in parentheses. The first list says which users the command can run as with the -u option. The second is a list of groups you can pick with -g (on top of any of the target user's groups) ๐Ÿ’…

here's how the combos shake out โœจ

  • if you've got both lists, the command can run with any combo of users and groups in them ๐Ÿซถ
  • if you only have the first, it can run as any user in that list and, optionally, with any group that target user belongs to
  • if the first is empty but the second isn't, it runs as the invoking user with the group set to whatever's in the Runas_List ๐ŸŒธ
  • if both are empty, it can only run as the invoking user, and the group (if you specify one) has to be one the invoking user is a member of ๐Ÿฆ‹
  • if there's no Runas_Spec at all, it can only run as the runas_default user (root by default), and the group (if specified) has to be one the runas_default user is in ๐Ÿ˜ญ

a Runas_Spec sets the default for every command after it in the entry, so this:

dgb     boulder = (operator) /bin/ls, /bin/kill, /usr/bin/lprm

means user dgb can run /bin/ls, /bin/kill, and /usr/bin/lprm on the host boulder, but only as operator ๐Ÿ’– like:

$ sudo -u operator /bin/ls

you can also override a Runas_Spec partway through an entry ๐Ÿ”ฅ if we change it to this:

dgb     boulder = (operator) /bin/ls, (root) /bin/kill, /usr/bin/lprm

then dgb can run /bin/ls as operator, but /bin/kill and /usr/bin/lprm as root ๐Ÿ‘‘

we can also let dgb run /bin/ls with either the user or the group set to operator ๐Ÿ“

dgb     boulder = (operator : operator) /bin/ls, (root) /bin/kill,\
        /usr/bin/lprm

the group part of a Runas_Spec lets the user run as that group, but it doesn't force them to. if no group is given on the command line, the command runs with the group from the target user's password database entry. all of these are allowed by the sudoers entry above:

$ sudo -u operator /bin/ls
$ sudo -u operator -g operator /bin/ls
$ sudo -g operator /bin/ls

in this one, user tcm can run commands that touch a modem device file with the dialer group ๐ŸŽง

tcm     boulder = (:dialer) /usr/bin/tip, /usr/bin/cu,\
        /usr/local/bin/minicom

in this case only the group gets set, and the command still runs as user tcm:

$ sudo -g dialer /usr/bin/cu

a Runas_Spec can also hold multiple users and groups, and then the user can pick any combo with -u and -g ๐Ÿงš here, alan can run any command as either root or bin, and optionally set the group to operator or system:

alan    ALL = (root, bin : operator, system) ALL

๐ŸŒˆ Option_Spec ๐ŸŒˆ

a Cmnd can have zero or more options attached ๐Ÿ’œ options can be SELinux roles and/or types, AppArmor profiles, start and/or end dates, and command timeouts โญ once you set an option on a Cmnd, every Cmnd after it in the Cmnd_Spec_List inherits that option, unless another option overrides it ๐Ÿซถ heads up: option names are reserved words in sudoers, so none of the valid option names (see below) can be used when you declare an alias ๐Ÿ™ˆ


๐Ÿฆ‹ SELinux_Spec ๐Ÿฆ‹

on systems with SELinux support, sudoers entries can optionally have an SELinux role and/or type tied to a command. this can be used for a form of role-based access control (RBAC) ๐Ÿ”ฅ a role or type set in sudoers overrides any default values, but a role or type given on the command line supersedes the ones in sudoers ๐Ÿ’…


๐Ÿงš AppArmor_Spec ๐Ÿงš

on systems that support AppArmor, sudoers entries can optionally name an AppArmor profile to confine a command โœจ if a profile is given with the command, it overrides any default values in sudoers. you also need proper profile transition rules defined to support the profile change for a user ๐Ÿ›ก๏ธ

AppArmor profiles can be written any way that follows the rules of aa_change_profile(2). for example, in this entry:

alice   ALL = (root)    APPARMOR_PROFILE=my-profile     ALL

user alice can run any command as root under confinement by the profile my-profile ๐Ÿ’– you can also stack profiles, or let someone run commands unconfined by any profile:

bob     ALL = (root)    APPARMOR_PROFILE=foo//&bar      /usr/bin/vi
cathy   ALL = (root)    APPARMOR_PROFILE=unconfined     /bin/ls

so user bob can run /usr/bin/vi as root under the stacked profiles foo and bar, and user cathy can run /bin/ls with zero confinement at all ๐ŸŒธ


๐Ÿ“… Date_Spec ๐Ÿ“…

sudoers rules can have a start and end date with the NOTBEFORE and NOTAFTER settings โฐ the time stamp has to be in "Generalized Time" as defined by RFC 4517. the format is basically yyyymmddHHMMSSZ, where the minutes and seconds are optional. the Z at the end means the time is in Coordinated Universal Time (UTC) ๐ŸŒ you can also give a timezone offset from UTC in hours and minutes instead of a Z. for example, -0500 would be Eastern Standard time in the US. as an extension, if you don't add a Z or a timezone offset, local time gets used ๐Ÿซถ

these are all valid time stamps:

20170214083000Z
2017021408Z
20160315220000-0500
20151201235900

โณ Timeout_Spec โณ

a command can have a timeout attached ๐Ÿ’€ if the timeout runs out before the command exits, the command gets terminated ๐Ÿ˜ญ you write timeouts as combos of days, hours, minutes, and seconds, each with a single-letter suffix that tells the unit (and it's not case-sensitive) โœจ for example, 7 days, 8 hours, 30 minutes, and 10 seconds is written 7d8h30m10s. if you leave off the unit, seconds are assumed ๐Ÿ“ you can skip any of the days, minutes, hours, or seconds. the order has to go from largest to smallest unit, and no unit can show up more than once ๐Ÿ™…โ€โ™€๏ธ

these are all valid timeout values: 7d8h30m10s, 14d, 8h30m, 600s, 3600 ๐Ÿ’–

these are invalid timeout values: 12m2w1d, 30s10m4h, 1d2d3h โŒ

this setting is only supported by version 1.8.20 or higher. so if you're on something older, it won't work ๐Ÿฅบ

๐Ÿ“‚ Chdir_Spec ๐Ÿ“‚

you can set the working directory the command runs in with the CWD setting ๐Ÿ—‚๏ธ the directory has to be a fully-qualified path name starting with a / or ~ character, or the special value *. a value of * means the user can pick the working directory themselves by running sudo with the -D option ๐ŸŽ€ by default, commands run from the invoking user's current working directory, unless the -i option is given. path names like ~user/path/name are read as relative to that named user's home directory. if you leave out the user name, the path is relative to the runas user's home directory ๐Ÿก

this setting is only supported by version 1.9.3 or higher.

๐Ÿ”’ Chroot_Spec ๐Ÿ”’

support for setting the root directory is deprecated and will be removed in a future version of sudo. so please don't build anything new on it ๐Ÿ’€

you can set the root directory the command runs in with the CHROOT setting ๐Ÿ“ฆ the directory has to be a fully-qualified path name starting with a / or ~ character, or the special value *. a value of * means the user can pick the root directory by running sudo with the -R option ๐Ÿซฃ this setting can run the command in a chroot(2) "sandbox", similar to the chroot(8) utility. path names like ~user/path/name are relative to the named user's home directory, and if the user name is left out, the path is relative to the runas user's home directory ๐ŸŒธ

this setting is only supported by version 1.9.3 or higher.

๐Ÿท๏ธ Tag_Spec ๐Ÿท๏ธ

a command can have zero or more tags attached ๐Ÿ’œ these tag values are supported: EXEC, NOEXEC, FOLLOW, NOFOLLOW, LOG_INPUT, NOLOG_INPUT, LOG_OUTPUT, NOLOG_OUTPUT, MAIL, NOMAIL, INTERCEPT, NOINTERCEPT, PASSWD, NOPASSWD, SETENV, and NOSETENV โœจ once a tag is set on a Cmnd, every Cmnd after it in the Cmnd_Spec_List inherits that tag, unless it gets overridden by the opposite tag. so PASSWD overrides NOPASSWD, and NOEXEC overrides EXEC ๐Ÿ”ฅ

๐Ÿšซ EXEC and NOEXEC ๐Ÿšซ

if sudo was compiled with noexec support and the underlying operating system supports it, the NOEXEC tag can stop a dynamically-linked executable from running further commands itself ๐Ÿ™…โ€โ™€๏ธ

in this example, user aaron can run /usr/bin/more and /usr/bin/vi on the host shanty, but shell escapes are disabled ๐Ÿš

aaron   shanty = NOEXEC: /usr/bin/more, /usr/bin/vi

check out the "Preventing shell escapes" section below for more details on how NOEXEC works and whether it'll work on your system ๐Ÿง

๐Ÿ”— FOLLOW and NOFOLLOW ๐Ÿ”—

starting with version 1.8.15, sudoedit won't open a file that's a symbolic link unless the sudoedit_follow flag is enabled ๐Ÿ™ˆ the FOLLOW and NOFOLLOW tags override the value of sudoedit_follow, and you can use them to allow (or deny) editing symbolic links on a per-command basis ๐Ÿฆ‹ these tags only work for the sudoedit command and get ignored for every other command

๐Ÿ“ LOG_INPUT and NOLOG_INPUT ๐Ÿ“

these tags override the value of the log_input flag on a per-command basis ๐ŸŽ€ for more info, see "I/O LOGGING" ๐Ÿ’–

๐Ÿ“ค LOG_OUTPUT and NOLOG_OUTPUT ๐Ÿ“ค

these tags override the value of the log_output flag on a per-command basis ๐ŸŒˆ for more info, see "I/O LOGGING" โœจ

๐Ÿ“ง MAIL and NOMAIL ๐Ÿ“ง

these tags give you fine-grained control over whether mail gets sent when a user runs a command, by overriding the value of the mail_all_cmnds flag on a per-command basis ๐Ÿ’Œ they have no effect when sudo is run with the -l or -v options. a NOMAIL tag also overrides the mail_always and mail_no_perms options ๐Ÿซถ for more info, check the descriptions of mail_all_cmnds, mail_always, and mail_no_perms in the "SUDOERS OPTIONS" section below ๐Ÿ“

๐Ÿ”‘ PASSWD and NOPASSWD ๐Ÿ”‘

By default, sudo makes you prove you're you with a password before running a command, bestie ๐Ÿ” But the NOPASSWD tag can skip that step. Like a Runas_Spec, NOPASSWD sets a default for every command after it in the Cmnd_Spec_List ๐Ÿ’… And the PASSWD tag can flip it back if you need to undo it. Here's the vibe:

ray     rushmore = NOPASSWD: /bin/kill, /bin/ls, /usr/bin/lprm

So ray can run /bin/kill, /bin/ls, and /usr/bin/lprm as root on the machine โ€œrushmoreโ€ and never has to authenticate himself ๐Ÿ™ˆโœจ But what if we only want ray to skip the password for /bin/kill? Easy, just swap in the PASSWD tag for the rest:

ray     rushmore = NOPASSWD: /bin/kill, PASSWD: /bin/ls, /usr/bin/lprm

Now, plot twist ๐Ÿซข the PASSWD tag does nothing for anyone in the group named by the exempt_group setting. Those people are exempt, so no password for them, no matter what the tag says.

Also, if the NOPASSWD tag shows up on any of a user's entries for the current host, that user can run sudo -l without a password ๐Ÿ“‹ And they can only run sudo -v without a password if every one of their entries for the current host has the NOPASSWD tag. This behaviour can be overridden with the verifypw and listpw options, so check those if the defaults aren't giving you the energy you want ๐Ÿฆ‹

โœจ SETENV and NOSETENV โœจ

These tags override the setenv flag on a per-command basis ๐Ÿ’œ If SETENV is set for a command, the user can turn off the env_reset flag from the command line with the -E option. Also, environment variables set on the command line don't have to follow the rules set by env_check, env_delete, or env_keep. So honestly? Only trusted users should be allowed to set variables this way ๐Ÿšจ If the command matched is ALL, the SETENV tag is implied for that command, but you can override that default with the NOSETENV tag.

๐Ÿงš INTERCEPT and NOINTERCEPT ๐Ÿงš

If sudo was compiled with intercept support and the underlying operating system supports it, the INTERCEPT tag makes programs spawned by a command get checked against sudoers and logged, just like they would be if you ran them through sudo directly ๐Ÿ•ต๏ธโ€โ™€๏ธ This is super useful with commands that allow shell escapes, like editors, shells, and paginators. The catch is there's extra overhead from the policy check, so running stuff like shell scripts that spin up a ton of sub-commands might add some latency. For interactive commands, like a shell or an editor, the overhead is usually not noticeable ๐ŸŒธ

Here, user chuck can run any command on the machine โ€œresearchโ€ in intercept mode:

chuck   research = INTERCEPT: ALL

Check out the โ€œPreventing shell escapesโ€ section below for more on how INTERCEPT works and whether it'll actually work on your system ๐Ÿ™ˆ

๐ŸŽ€ Wildcards ๐ŸŽ€

okay so sudo lets you use shell-style wildcards (aka meta or glob characters) in host names, path names, and command line arguments in the sudoers file ๐Ÿ“ Wildcard matching is handled by the glob(3) and fnmatch(3) functions, as specified by IEEE Std 1003.1 (โ€œPOSIX.1โ€).

  • * โ€” Matches any set of zero or more characters (including white space).
  • ? โ€” Matches any single character (including white space).
  • [...] โ€” Matches any character in the specified range.
  • [!...] โ€” Matches any character not in the specified range.
  • \x โ€” For any character โ€˜xโ€™, evaluates to โ€˜xโ€™. This is how you escape special characters like โ€˜*โ€™, โ€˜?โ€™, โ€˜[โ€™, and โ€˜]โ€™.

Quick reality check: these are not regular expressions ๐Ÿ™…โ€โ™€๏ธ Unlike a regular expression, there's no way to match one or more characters within a range.

Character classes work too, if your system's glob(3) and fnmatch(3) functions support them. But because the โ€˜:โ€™ character has special meaning in sudoers, it has to be escaped. For example:

/bin/ls [[\:alpha\:]]*

That would match any file name beginning with a letter ๐Ÿ’–

A forward slash (โ€˜/โ€™) won't be matched by wildcards used in the file name portion of the command. This keeps a path like this one in check:

/usr/bin/*

It matches /usr/bin/who but not /usr/bin/X11/xterm ๐Ÿซถ

When it comes to command line arguments though, a slash does get matched by wildcards, since arguments can be arbitrary strings and not just path names.

โš ๏ธ Be careful with wildcards in command line arguments! They can match any character, including white space. In most cases it's safer to use a regular expression to match command line arguments. For more information, see โ€œWildcards in command argumentsโ€ below.

๐Ÿ’… Exceptions to wildcard rules ๐Ÿ’…

The following exceptions apply to the rules above:

  • "" โ€” If the empty string "" is the only command line argument in the sudoers file entry, that command isn't allowed to run with any arguments at all ๐Ÿ™…โ€โ™€๏ธ
  • sudoedit โ€” Command line arguments to the sudoedit built-in command should always be path names, so a forward slash (โ€˜/โ€™) won't be matched by a wildcard.

๐Ÿ”ฎ Regular expressions ๐Ÿ”ฎ

Starting with version 1.9.10, you can use regular expressions for path names and command line arguments ๐ŸŒˆ They're more expressive than shell-style wildcards and are usually safer because they give you way more control over matching. The regular expressions sudoers supports are POSIX extended regular expressions, similar to the ones used by the egrep(1) utility. They're usually documented in the regex(7) or re_format(7) manual, depending on your system. As an extra perk, if a regular expression begins with โ€œ(?i)โ€, it'll be matched case-insensitively โœจ

In sudoers, regular expressions must start with a โ€˜^โ€™ character and end with a โ€˜$โ€™. This makes it crystal clear what is and isn't a regular expression ๐Ÿ’œ Either the path name, the command line arguments, or both can be regular expressions. Since the path name and arguments get matched separately, you can even use wildcards for the path name and regular expressions for the arguments. What you can't do is use a single regular expression to match both the command and its arguments ๐Ÿ™…โ€โ™€๏ธ Regular expressions in sudoers are limited to 1024 characters.

There's no need to escape sudoers special characters in a regular expression, except for the pound sign (โ€˜#โ€™) ๐Ÿ™ˆ

In the following example, user john can run the passwd(1) command as root on any host, but he's not allowed to change root's password. This kind of rule is basically impossible to express safely using wildcards ๐Ÿ˜ญ

john    ALL = /usr/bin/passwd ^[a-zA-Z0-9_]+$,\
              !/usr/bin/passwd root

You can also pair a regular expression with sudoedit rules. This one gives user bob the ability to edit the /etc/motd, /etc/issue, and /etc/hosts files only ๐Ÿ“‹

bob    ALL = sudoedit ^/etc/(motd|issue|hosts)$

Regular expressions can even match the command itself! In this example, a regular expression lets user sid run the /usr/sbin/groupadd, /usr/sbin/groupmod, /usr/sbin/groupdel, /usr/sbin/useradd, /usr/sbin/usermod, and /usr/sbin/userdel commands as root ๐Ÿ‘‘

sid    ALL = ^/usr/sbin/(group|user)(add|mod|del)$

One downside of using a regular expression for the command name is that you can't match relative paths like ./useradd or ../sbin/useradd. That has security implications when the regular expression is combined with the negation operator, โ€˜!โ€™, because rules like that can be trivially bypassed ๐Ÿ’€ So using a negated regular expression for the command name is strongly discouraged. This doesn't apply to negated commands that only use a regular expression to match the command arguments. See โ€œRegular expressions in command namesโ€ below for more info.

๐Ÿงฉ Including other files from within sudoers ๐Ÿงฉ

You can pull other sudoers files into the one currently being parsed with the @include and @includedir directives ๐Ÿฆ‹ For compatibility with sudo versions prior to 1.9.1, #include and #includedir are also accepted.

An include file is handy for keeping a site-wide sudoers file alongside a local, per-machine one. For the sake of this example, the site-wide sudoers file will be /etc/sudoers and the per-machine one will be /etc/sudoers.local. To include /etc/sudoers.local from within /etc/sudoers, you'd put this line in /etc/sudoers:

@include /etc/sudoers.local

When sudo hits this line, it pauses processing of the current file (/etc/sudoers) and switches to /etc/sudoers.local. Once it reaches the end of /etc/sudoers.local, the rest of /etc/sudoers gets processed. Included files can include other files too, but there's a hard limit of 128 nested include files to prevent include loops ๐Ÿ”

Starting with version 1.9.1, the path to the include file can contain white space if it's escaped with a backslash (โ€˜\โ€™). Or you can wrap the entire path in double quotes (""), in which case no escaping is needed. To include a literal backslash in the path, use โ€˜\โ€™ ๐Ÿซถ

If the path to the include file isn't fully-qualified (it doesn't begin with a โ€˜/โ€™), it has to live in the same directory as the sudoers file it was included from. For example, if /etc/sudoers contains the line:

@include sudoers.local

then the file that gets included is /etc/sudoers.local ๐ŸŒธ

The file name can also include the โ€˜%hโ€™ escape, which stands for the short form of the host name. So if the machine's host name is โ€œxerxesโ€, then

@include /etc/sudoers.%h

will make sudo include the file /etc/sudoers.xerxes. Any path name separator characters (โ€˜/โ€™) in the host name get replaced with an underbar (โ€˜_โ€™) during expansion ๐Ÿง 

The @includedir directive lets you create a sudoers.d directory that your system package manager can drop sudoers rules into during package installation ๐Ÿ“ฆ For example, given:

@includedir /etc/sudoers.d

sudo will pause the current file and read each file in /etc/sudoers.d. It skips file names that end in โ€˜~โ€™ or contain a โ€˜.โ€™ character, so package manager and editor temp or backup files don't cause problems ๐Ÿ’…

Okay so the sudoers files get read in sorted lexical order, bestie ๐Ÿ“‚โœจ /etc/sudoers.d/01_first gets parsed before /etc/sudoers.d/10_second. BUT ๐Ÿ™ˆ because it's lexical and not numeric, /etc/sudoers.d/1_whoops gets loaded after /etc/sudoers.d/10_second. Pro tip: keep a consistent number of leading zeroes in your file names so this never bites you ๐Ÿ’… After everything in the directory is parsed, control goes back to whichever file had the @includedir directive.

Unlike files pulled in with @include, visudo will NOT edit the files in an @includedir directory unless one of them has a syntax error ๐Ÿ˜ญ You can still run visudo with the -f flag to edit those files directly, but it won't catch an alias being redefined in a different file. So be careful, babe ๐Ÿซถ

๐ŸŒธ Other special characters and reserved words ๐ŸŒธ

The pound sign (#) marks a comment ๐Ÿ’ฌ unless it's part of a #include directive, or it shows up in a user name and is followed by one or more digits, in which case it's a user-ID instead. Either way, the comment character and everything after it, up to the end of the line, gets ignored.

The reserved word ALL is a built-in alias that always matches ๐Ÿ‘‘ You can use it anywhere you'd otherwise use a Cmnd_Alias, User_Alias, Runas_Alias, or Host_Alias. Trying to define an alias called ALL gives you a syntax error. And honestly? Using ALL can be dangerous ๐Ÿ’€ In a command context it lets the user run literally any command on the system.

These option names from an Option_Spec are also reserved words: CHROOT, ROLE, TYPE, TIMEOUT, CWD, NOTBEFORE and NOTAFTER ๐ŸŽ€ Defining an alias with the same name as one of those options will also throw a syntax error.

An exclamation point (!) works as a logical not operator in a list or alias, and in front of a Cmnd too ๐Ÿ™…โ€โ™€๏ธ That's how you exclude stuff. The ! only does anything if there's something for it to exclude. So if you want to match every user except root, you'd write:

ALL, !root

If you drop the ALL, and just write:

!root

then it explicitly denies root but doesn't match any other users. This is not a true "negation" operator, so don't let it fool you ๐Ÿฆ‹

Also, a ! combined with the built-in ALL alias to let someone run "all but a few" commands rarely works the way you'd hope ๐Ÿ˜ฉ Check out "SECURITY NOTES" below for the tea on that one.

Long lines can be continued with a backslash (\) as the last character on the line ๐Ÿงš And white space between elements in a list, plus the special syntax characters in a User Specification (=, :, (, )), is totally optional. Your call, queen โœจ

The following characters need a backslash (\) escape when they're part of a word, like a user name or host name: !, =, :, ,, (, ), \ ๐Ÿ™ˆ Remember that one or you'll be crying later ๐Ÿ˜ญ


๐Ÿ”ฎ SUDOERS OPTIONS ๐Ÿ”ฎ

sudo's behavior can be tweaked with Default_Entry lines, as we covered earlier ๐Ÿ’– Below is every supported Defaults parameter, grouped by type. Let's go, bestie ๐Ÿš€

๐ŸŽ€ Boolean Flags ๐ŸŽ€

  • always_query_group_plugin โ€” If a group_plugin is configured, sudo uses it to resolve groups of the form %group, as long as there isn't also a system group with that same name ๐ŸŒท Normally only groups written as %:group get passed to the group_plugin. This flag is off by default.
  • always_set_home โ€” If enabled, sudo sets the HOME environment variable to the home directory of the target user (which is the runas_default user unless you use the -u option) ๐Ÿก This flag is largely obsolete and does nothing unless the env_reset flag has been disabled or HOME is in the env_keep list. Both of those are strongly discouraged, so ya know ๐Ÿ™…โ€โ™€๏ธ This flag is off by default.
  • authenticate โ€” If set, users have to authenticate themselves with a password (or some other means of authentication) before they can run commands ๐Ÿ” This default can be overridden with the PASSWD and NOPASSWD tags. This flag is on by default.
  • case_insensitive_group โ€” If enabled, group names in sudoers get matched case insensitively ๐ŸŒˆ This might be necessary when users are stored in LDAP or AD. This flag is on by default.
  • case_insensitive_user โ€” If enabled, user names in sudoers get matched case insensitively ๐Ÿ’œ This might be necessary when groups are stored in LDAP or AD. This flag is on by default.
  • closefrom_override โ€” If set, the user can use the -C option, which overrides the default starting point where sudo begins closing open file descriptors ๐Ÿฆ‹ This flag is off by default.
  • compress_io โ€” If set, and sudo is configured to log a command's input or output, the I/O logs get compressed using zlib ๐Ÿ—œ๏ธ This flag is on by default when sudo is compiled with zlib support.
  • exec_background โ€” Okay, this one's a whole mood ๐ŸŒŠ By default, sudo runs a command as the foreground process as long as sudo itself is running in the foreground. When exec_background is enabled and the command runs in a pseudo-terminal (because of I/O logging or the use_pty flag), the command runs as a background process instead. If it tries to read from the controlling terminal (or change terminal settings), it gets suspended with the SIGTTIN signal (or SIGTTOU when it's terminal settings). If that happens while sudo is a foreground process, the command gets the controlling terminal and resumes in the foreground with zero user intervention needed โœจ The upside of starting in the background is that sudo doesn't have to read from the terminal unless the command actually asks for it. Otherwise, any terminal input has to be passed along whether the command wanted it or not, since the kernel buffers terminals and there's no way to tell if the command really wants the input ๐Ÿ™ƒ This is different from historic sudo behavior, or when the command isn't running in a pseudo-terminal.

For this to work seamlessly, the operating system has to support automatic restarting of system calls. Unfortunately, not all operating systems do this by default, and even the ones that do might have bugs ๐Ÿ› For example, macOS fails to restart the tcgetattr(3) and tcsetattr(3) functions (yes, that's a bug in macOS ๐Ÿ’€). Also, because this behavior depends on the command stopping with the SIGTTIN or SIGTTOU signals, programs that catch those signals and suspend themselves with a different signal (usually SIGTOP) won't get automatically foregrounded. Some versions of the linux su(1) command behave this way ๐Ÿซ  This flag is off by default.

This setting is only supported in version 1.8.7 or higher ๐Ÿ”ข It does nothing unless I/O logging is enabled or the use_pty flag is enabled.

  • env_editor โ€” If set, visudo uses the value of the SUDO_EDITOR, VISUAL or EDITOR environment variables before falling back on the default editor list ๐Ÿ“ visudo usually runs as root, so this flag might let a user with visudo privileges run arbitrary commands as root without getting logged. ๐Ÿ˜ณ A safer move is to put a colon-separated list of "safe" editors in the editor setting. Then visudo will only use SUDO_EDITOR, VISUAL or EDITOR if they match a value in editor. If the env_reset flag is enabled, the SUDO_EDITOR, VISUAL and/or EDITOR variables have to be in the env_keep list for env_editor to work when visudo is invoked via sudo. This flag is on by default.
  • env_reset โ€” If set, sudo runs the command in a minimal environment containing the TERM, PATH, HOME, MAIL, SHELL, LOGNAME, USER and SUDO_* variables ๐ŸŒธ Then any variables in the caller's environment, or in the file specified by the restricted_env_file setting, that match the env_keep and env_check lists get added. After that, any variables in the file specified by the env_file setting (if there is one) get added too. The contents of the env_keep and env_check lists, as modified by global Defaults parameters in sudoers, are displayed when sudo is run by root with the -V option ๐Ÿ‘€ If the secure_path setting is enabled, its value is used for the PATH environment variable. This flag is on by default.
  • fast_glob โ€” ๐Ÿ” Normally, sudo uses the glob(3) function for shell-style globbing when it matches path names. The catch is that glob(3) touches the file system, so some patterns can take forever, especially when they point at a network file system that gets auto mounted on demand. Turn on fast_glob and sudo uses the fnmatch(3) function instead, which never touches the file system while matching. The downside is that it can't match relative paths like ./ls or ../bin/ls. That's a security problem when path names with globbing characters go with the negation operator, โ€˜!โ€™, because those rules can be trivially bypassed. So please don't use this flag if your sudoers file has negated path names that include globbing characters. It's off by default. ๐Ÿ™…โ€โ™€๏ธ
  • log_passwords โ€” ๐Ÿ” Most programs that ask for a password turn off echo before they read it, so the plaintext never shows up on screen. But if terminal input is being logged (see โ€œI/O LOGGINGโ€), the password can still end up in the I/O log. Disable log_passwords and sudoers will try to stop that. It uses the regular expressions in passprompt_regex to spot a password prompt in the terminal output buffer. Once it finds one, input characters going into the I/O log get replaced with โ€˜*โ€™ until a line feed or carriage return shows up in the terminal input, or a new terminal output buffer arrives. If a program echoes characters as you type (like sudo does when pwfeedback is set), only the first character of the password gets replaced in the I/O log. This option does nothing unless log_input or log_ttyin is also set. It's on by default. โœจ

This setting is only supported by version 1.9.10 or higher.

  • fqdn โ€” ๐ŸŒ Set this if you want fully qualified host names in the sudoers file, for when the local host name (the one the hostname command gives you) doesn't include the domain. So instead of myhost you'd write myhost.mydomain.edu. You can still use the short form if you like, and you can even mix the two. This only works when the "canonical" host name from the getaddrinfo(3) or gethostbyname(3) function is a fully-qualified domain name, which usually happens when your system uses DNS for host name resolution.

If your system prefers the /etc/hosts file over DNS, the "canonical" host name might not be fully qualified. The order sources get queried in is usually set in /etc/nsswitch.conf, /etc/netsvc.conf, /etc/host.conf, or sometimes /etc/resolv.conf. In /etc/hosts, the first host name on the line is the "canonical" one, and any names after it are aliases that sudoers doesn't use. For example, this hosts file line gives the machine "xyzzy" a fully-qualified canonical name, with the short version as an alias:

192.168.1.1 xyzzy.sudo.ws xyzzy

If your hosts file entry isn't formatted properly, fqdn won't work when the hosts file gets queried before DNS. ๐Ÿซ 

Beware! If you're using DNS for host name resolution, turning on fqdn makes sudoers do DNS lookups. That means sudo stops working if DNS goes down, like when the machine gets disconnected from the network. Just like with the hosts file, you have to use the "canonical" name as DNS knows it. Don't use a host alias (CNAME entry), since that causes performance issues and there's no way to fetch all the aliases from DNS.

This flag is on by default. ๐Ÿ’…

  • ignore_audit_errors โ€” ๐Ÿงพ Lets commands run even when sudoers can't write to the audit log. When it's enabled, an audit log write failure isn't treated as fatal. When it's disabled, a command only runs after the audit event has been written successfully. This only does anything on systems where sudoers supports audit logging, which includes FreeBSD, Linux, macOS, and Solaris. It's on by default.
  • ignore_dot โ€” ๐Ÿ™ˆ If set, sudo ignores "." or "" (both mean the current directory) in the PATH environment variable. The PATH itself doesn't get modified. It's on by default.
  • ignore_iolog_errors โ€” ๐Ÿ“ผ Lets commands run even when sudoers can't write to the I/O log, whether that's local or remote. When it's enabled, an I/O log write failure isn't fatal. When it's disabled, the command gets terminated if the I/O log can't be written to. It's off by default.
  • ignore_logfile_errors โ€” ๐Ÿ“ Lets commands run even when sudoers can't write to the log file. When it's enabled, a log file write failure isn't fatal. When it's disabled, a command only runs after its log file entry has been written successfully. This only matters when sudoers uses file-based logging through the logfile setting. It's on by default.
  • ignore_local_sudoers โ€” ๐Ÿšซ If set via LDAP, sudo skips parsing /etc/sudoers. This is for sites that want only LDAP and no local sudoers files, which shuts down rogue operators trying to sneak roles into /etc/sudoers. When this flag is enabled, /etc/sudoers doesn't even have to exist. Since this flag tells sudo how to act when no specific LDAP entries match, this sudoOption only means something in the โ€˜cn=defaultsโ€™ section. It's off by default.
  • ignore_unknown_defaults โ€” ๐Ÿคทโ€โ™€๏ธ If set, sudo won't warn you when it runs into an unknown Defaults entry in the sudoers file or an unknown sudoOption in LDAP. It's off by default.
  • insults โ€” ๐Ÿ˜ค If set, sudo will roast users when they type in the wrong password. It's off by default. ๐Ÿ”ฅ
  • log_allowed โ€” โœ… If set, sudoers logs commands the policy allows to the system audit log (where supported), plus syslog and/or a log file. It's on by default.

This setting is only supported by version 1.8.29 or higher.

  • log_denied โ€” โ›” If set, sudoers logs commands the policy denies to the system audit log (where supported), plus syslog and/or a log file. It's on by default.

This setting is only supported by version 1.8.29 or higher.

  • log_exit_status โ€” ๐Ÿ If set, sudoers logs the exit value of commands it runs to syslog and/or a log file. If a command gets killed by a signal, the signal name gets logged too. It's off by default.

This setting is only supported by version 1.9.8 or higher.

  • log_host โ€” ๐Ÿ  If set, the host name gets included in log entries written to the file configured by the logfile setting. It's off by default.
  • log_input โ€” โŒจ๏ธ If set, sudo runs the command in a pseudo-terminal (if sudo was run from a terminal) and logs all user input. If standard input isn't connected to the user's terminal, because of I/O redirection or because the command is part of a pipeline, that input gets logged too. For more on I/O logging, check the โ€œI/O LOGGINGโ€ section. It's off by default.
  • log_output โ€” ๐Ÿ–ฅ๏ธ If set, sudo runs the command in a pseudo-terminal (if sudo was run from a terminal) and logs all output sent to the user's terminal, standard output, or standard error. If standard output or standard error isn't connected to the user's terminal, because of I/O redirection or because the command is part of a pipeline, that output gets logged too. For more on I/O logging, check the โ€œI/O LOGGINGโ€ section. It's off by default. ๐Ÿฆ‹

okay bestie, we're still in the options list ๐Ÿ’… and these ones are kinda the paranoid-but-make-it-fashion flags ๐Ÿ™ˆ

  • log_server_keepalive โ€” ๐Ÿซถ turns on TCP keepalive on the connection to the log server, so little "still alive?" pings get sent regularly. If the server stops answering, the connection gets closed and the command you're running gets terminated, unless ignore_iolog_errors (with I/O logging on) or ignore_log_errors (with I/O logging off) is set. This one is on by default โœจ

This setting is only supported by version 1.9.0 or higher ๐Ÿ’€

  • log_server_verify โ€” ๐Ÿ” the server certificate you get during the TLS handshake has to be valid AND it has to include either the server name (from log_servers) or its IP address. If neither checks out, the handshake fails. Also on by default ๐Ÿ’œ

This setting is only supported by version 1.9.0 or higher ๐Ÿ’€

  • log_stderr โ€” ๐Ÿชต if this is set, sudo logs standard error whenever it isn't hooked up to your terminal. Handy for stuff going into a pipe or getting redirected into a file. It's off by default, but it flips on if either log_output or the LOG_OUTPUT command tag is set ๐ŸŽ€
  • log_stdin โ€” โŒจ๏ธ same vibe as log_stderr, but for standard input that isn't connected to your terminal. Think input coming from a pipe or redirected from a file. Off by default, and it turns on when log_input or the LOG_INPUT command tag is set ๐ŸŒธ
  • log_stdout โ€” ๐Ÿ“ค logs standard output when it isn't connected to your terminal, like output going into a pipe or into a file. Off by default, but on if log_output or the LOG_OUTPUT command tag is set ๐Ÿฆ‹
  • log_subcmds โ€” ๐Ÿงš when a command spawns a child process and runs a program through execve(2), execl(3), execle(3), execlp(3), execv(3), execvp(3), execvpe(3), or system(3), sudoers logs it. So if sudo starts a shell, every individual command typed in that shell gets logged too ๐Ÿ‘€ Off by default.

The log_subcmds flag uses the same underlying machinery as the intercept setting. Some commands might not behave properly with it on, because of the way it intercepts sub-commands ๐Ÿ™ƒ For more on which systems support this and what the limits are, check out โ€œPreventing shell escapes.โ€ This setting is only supported by version 1.9.8 or higher, and it's incompatible with SELinux RBAC support unless the system supports seccomp(2) filter mode โš ๏ธ

  • log_ttyin โ€” ๐ŸŽน runs the command inside a pseudo-terminal and logs the keystrokes the user sends to their terminal, as long as there is one. Off by default, but it's switched on when log_input or the LOG_INPUT command tag is set. If there's no terminal, like when you're running a remote command with ssh(1), this flag does absolutely nothing ๐Ÿซ 
  • log_ttyout โ€” ๐Ÿ“บ same pseudo-terminal energy, but this time it logs everything that shows up on the user's terminal. Off by default, and on when log_output or the LOG_OUTPUT command tag is set. With no terminal around (again, ssh(1) remote commands), it does nothing ๐Ÿ’…
  • log_year โ€” ๐Ÿ“… if set, the four-digit year gets written into the (non-syslog) sudo log file. Off by default ๐ŸŒˆ
  • long_otp_prompt โ€” ๐Ÿ”ข when you're validating with a One Time Password scheme like S/Key or OPIE, this makes the prompt two lines long so you can cut and paste the challenge into a local window. It's not as cute as the default, but some people find it way more convenient ๐Ÿ“ Off by default.
  • mail_all_cmnds โ€” ๐Ÿ’Œ sends mail to the mailto user every single time someone tries to run a command with sudo (sudoedit included). If the user runs sudo with -l or -v, no mail goes out, unless there's an authentication error and mail_badpass is also set. Off by default โœจ
  • mail_always โ€” ๐Ÿ“จ mail to the mailto user every time someone runs sudo, period. Off by default ๐Ÿ”ฅ
  • mail_badpass โ€” ๐Ÿ™…โ€โ™€๏ธ mail goes to the mailto user if the person running sudo types the wrong password. But if the command they're attempting isn't allowed by sudoers AND any of mail_all_cmnds, mail_always, mail_no_host, mail_no_perms or mail_no_user is set, this flag has no effect. Off by default ๐Ÿ’œ
  • mail_no_host โ€” ๐Ÿ  if set, mail goes to the mailto user when the invoking user is in the sudoers file but isn't allowed to run commands on the current host. Off by default ๐Ÿฆ‹
  • mail_no_perms โ€” ๐Ÿšซ if set, mail goes to the mailto user when the invoking user is allowed to use sudo, but the command they're trying isn't listed in their sudoers entry or is explicitly denied. Off by default ๐ŸŒธ
  • mail_no_user โ€” ๐Ÿ‘ป if set, mail goes to the mailto user when the invoking user isn't in the sudoers file at all. This one is on by default ๐ŸŽ€
  • match_group_by_gid โ€” ๐Ÿชช by default, sudoers looks up each group the user belongs to by group-ID to get its name (and it only does this once). The list of group names it ends up with is what gets matched against groups in the sudoers file. That works great when the sudoers file lists more groups than a typical user is in. But on systems where group lookups are slow, where users belong to tons of groups, or where the sudoers file only lists a few groups, it can be super expensive and sudo can get laggy ๐Ÿข In that case, match_group_by_gid helps by skipping the group-ID to name conversion. Instead, sudoers has to look up each group name in the sudoers file and compare the group-ID instead of the name when checking membership ๐Ÿง 

If match_group_by_gid is enabled, group database lookups done by sudoers are keyed by group name instead of group-ID. On systems with multiple sources for the group database, you can end up with conflicting group names or group-IDs between the local /etc/group file and the remote group database ๐Ÿ˜ญ On those systems, turning match_group_by_gid on or off decides whether group queries are done by name (enabled) or by ID (disabled), which can help dodge group entry conflicts.

The match_group_by_gid flag does nothing when sudoers data is stored in LDAP. It's off by default ๐Ÿ’…

This setting is only supported by version 1.8.18 or higher ๐Ÿ’€

  • intercept โ€” ๐Ÿ•ต๏ธโ€โ™€๏ธ if set, every command run through sudo acts like the INTERCEPT tag was set, unless a NOINTERCEPT tag overrides it. Some commands might not play nice with intercept on, because of the way it intercepts sub-commands. Check out the descriptions of INTERCEPT and NOINTERCEPT above, plus the โ€œPreventing shell escapesโ€ section at the end of this manual ๐Ÿ“– Off by default.

This setting is only supported by version 1.9.8 or higher and is incompatible with SELinux RBAC support unless the system supports seccomp(2) filter mode ๐Ÿฅน

  • intercept_allow_setid โ€” ๐Ÿช„ on most systems, the dynamic loader ignores LD_PRELOAD (or its equivalent) when it's running set-user-ID and set-group-ID programs, which basically kills intercept mode. To stop that from happening, sudoers won't let a set-user-ID or set-group-ID program run in intercept mode unless intercept_allow_setid is enabled. This flag does nothing unless the intercept flag is enabled or the INTERCEPT tag is set for the command. It's on by default when the intercept_type option is set to trace, otherwise it defaults to off ๐ŸŒˆ

This setting is only supported by version 1.9.8 or higher ๐Ÿ’–

  • intercept_authenticate โ€” ๐Ÿ” Okay so, when this one is on, commands run by an intercepted process have to be authenticated again whenever the user's time stamp isn't current. Say a shell starts with intercept enabled. The second the invoking user's time stamp goes out of date, every command after that needs authentication, no exceptions ๐Ÿ’…. It does nothing unless the intercept flag is enabled or the INTERCEPT tag is set for the command. It's off by default.
    • This setting is only supported by version 1.9.8 or higher โœจ
  • intercept_verify โ€” ๐Ÿ•ต๏ธโ€โ™€๏ธ This is the main character of the intercept flags. When it's set, sudo tries to double-check that a command running in intercept mode has the path name, command line arguments, and environment it's supposed to have.
    • The process gets paused after execve(2) finishes, but before the new command gets a chance to run. To check it, sudo reads the command's path from /proc/PID/exe, reads the command line arguments and environment from the process's memory, and compares all of that against what got passed to execve(2). If anything doesn't match, the command gets a SIGKILL signal and is terminated ๐Ÿ’€.
    • This helps stop a time of check versus time of use issue with intercept mode, where the execve(2) arguments could get changed after the sudoers policy check. These checks can only happen if the proc(5) file system is available. The flag does nothing unless the intercept flag is enabled or the INTERCEPT tag is set for the command, and the intercept_type option is set to trace.
    • > ๐Ÿšจ This setting doesn't play nicely with programs that change their root directory via chroot(2). If a program changes its root directory, path names won't match what the sudo parent process sees, and sub-commands will get terminated before they can run.
    • This flag is on by default.
    • This setting is only supported by version 1.9.12 or higher ๐Ÿฆ‹
  • netgroup_tuple โ€” ๐ŸŒธ When this is set, netgroup lookups use the full netgroup tuple: host name, user name, and domain (if one is set). Historically, sudo only matched the user name and domain for netgroups used in a User_List, and only matched the host name and domain for netgroups used in a Host_List. Off by default.
  • noexec โ€” ๐Ÿšซ When this is set, every command run through sudo acts like the NOEXEC tag has been set, unless an EXEC tag overrides it. Check out the description of EXEC and NOEXEC above, plus the โ€œPreventing shell escapesโ€ section at the end of this manual. It's off by default.
  • noninteractive_auth โ€” ๐Ÿ™Œ When this is set, authentication gets attempted even in non-interactive mode (when sudo's -n option is specified). That lets authentication methods that don't need user interaction succeed. Methods that need input from the user's terminal will still fail. If it's disabled, authentication won't be attempted in non-interactive mode. It's off by default.
    • This setting is only supported by version 1.9.10 or higher ๐Ÿ’œ
  • pam_acct_mgmt โ€” ๐Ÿงพ On systems that use PAM for authentication, sudo does PAM account validation for the invoking user by default. What actually gets checked depends on which PAM modules are configured. When enabled, account validation happens regardless of whether or not a password is required. This flag is on by default.
    • This setting is only supported by version 1.8.28 or higher โœจ
  • pam_rhost โ€” ๐ŸŒ On systems that use PAM for authentication, sudo sets the PAM remote host value to the name of the local host when pam_rhost is enabled. On Linux systems, enabling pam_rhost may result in DNS lookups of the local host name when PAM is initialized. On Solaris versions prior to Solaris 8, pam_rhost must be enabled if pam_ruser is also enabled, or the Solaris PAM implementation will crash ๐Ÿ˜ญ.
    • This flag is off by default on systems other than Solaris.
    • This setting is only supported by version 1.9.0 or higher ๐ŸŽ€
  • pam_ruser โ€” ๐Ÿ‘ค On systems that use PAM for authentication, sudo sets the PAM remote user value to the name of the user who invoked sudo when pam_ruser is enabled. This flag is on by default.
    • This setting is only supported by version 1.9.0 or higher ๐ŸŒท
  • pam_session โ€” ๐Ÿชช On systems that use PAM for authentication, sudo creates a new PAM session for the command to run in. Unless sudo is given the -i or -s options, PAM session modules run with the โ€œsilentโ€ flag enabled. That keeps last login information from showing up for every command on some systems. Disabling pam_session might be needed on older PAM implementations, or on operating systems where opening a PAM session changes the utmp or wtmp files. If PAM session support is disabled, resource limits may not get updated for the command being run.
    • If pam_session, pam_setcred, and use_pty are all disabled, log_servers hasn't been set, and I/O logging hasn't been configured, sudo runs the command directly instead of as a child process ๐Ÿƒโ€โ™€๏ธ.
    • This flag is on by default.
    • This setting is only supported by version 1.8.7 or higher ๐Ÿ“
  • pam_setcred โ€” ๐Ÿ—๏ธ On systems that use PAM for authentication, sudo tries to establish credentials for the target user by default, if the underlying authentication system supports it. One example of a credential is a Kerberos ticket.
    • If pam_session, pam_setcred, and use_pty are disabled, log_servers hasn't been set, and I/O logging hasn't been configured, sudo runs the command directly instead of as a child process.
    • This flag is on by default.
    • This setting is only supported by version 1.8.8 or higher ๐Ÿ’–
  • pam_silent โ€” ๐Ÿคซ When set, PAM authentication runs in silent mode. That stops PAM authentication modules from generating output. Heads up though: in some cases this might hide important info about why authentication failed. For example, PAM modules like pam_faillock will only display a warning if pam_silent is disabled. This flag is on by default.
    • This setting is only supported by version 1.9.16 or higher ๐ŸŒˆ
  • passprompt_override โ€” ๐Ÿ’ฌ When set, the prompt specified by passprompt or the SUDO_PROMPT environment variable is always used, and it replaces the prompt provided by a PAM module or another authentication method. It's off by default.
  • path_info โ€” ๐Ÿ” Normally, sudo tells the user when a command can't be found in their PATH environment variable. Some sites might want to turn that off, since it could be used to gather info about where executables are located that the normal user doesn't have access to. The downside is that if the executable just isn't in the user's PATH, sudo will tell them they're not allowed to run it, which can be super confusing ๐Ÿ™ƒ. This flag is on by default.
  • preserve_groups โ€” ๐Ÿงบ By default, sudo sets up the group vector to the list of groups the target user is in. When preserve_groups is set, the user's existing group vector stays unaltered. The real and effective group-IDs still get set to match the target user, though. It's off by default.
  • pwfeedback โ€” โŒจ๏ธ By default, sudo reads the password like most other Unix programs do, by turning off echo until the user hits the return (or enter) key. Some users get confused by this, since it looks like sudo has hung right there. When pwfeedback is set, sudo gives visual feedback when the user presses a key. Heads up though: this has a security impact, because an onlooker might be able to figure out the length of the password being entered ๐Ÿ‘€. It's off by default.
  • requiretty โ€” ๐Ÿ“บ When set, sudo only runs when the user is logged in to a real tty. It can only be run from a login session, not via other means like cron(8) or cgi-bin scripts. It's off by default.
  • root_sudo โ€” ๐Ÿ‘‘ When this is on, root is allowed to run sudo too. Turn it off and users can't "chain" sudo commands to sneak into a root shell with something like sudo sudo /bin/sh, but heads up: it also stops root from running sudoedit. Disabling it doesn't add any real security. It's only here for historical reasons. It's on by default.
  • rootpw โ€” ๐Ÿ” With this on, sudo asks for the root password instead of your own whenever you run a command or edit a file. It's off by default.
  • runas_allow_unknown_id โ€” ๐Ÿฆ‹ When enabled, runas user and group IDs that aren't in the password or group databases can still get matched. Besides explicitly matching unknown user or group IDs in a Runas_List, this option also lets the ALL alias match unknown IDs. It's off by default.
    • This setting is only supported by version 1.8.30 or higher. Older versions of sudo always allowed matching of unknown user and group IDs.
  • runas_check_shell โ€” ๐Ÿš If enabled, sudo only runs commands as a user whose shell is listed in the /etc/shells file, even if the invoking user's Runas_List would otherwise allow it. If there's no /etc/shells file, a system-dependent list of built-in default shells gets used. Lots of operating systems give system users like "bin" no valid shell, so this flag can stop commands from running as those users. It's off by default.
    • This setting is only supported by version 1.8.30 or higher.
  • runaspw โ€” ๐Ÿ”‘ With this on, sudo asks for the password of the user named by the runas_default option (which defaults to root) instead of your own when running a command or editing a file. It's off by default.
  • selinux โ€” ๐ŸŒˆ If enabled, the user can specify an SELinux role and/or type to use when running the command, as long as the SELinux policy permits it. If SELinux is disabled on the system, this flag does nothing. It's on by default.
  • set_home โ€” ๐Ÿก If enabled and sudo is invoked with the -s option, the HOME environment variable gets set to the home directory of the target user (which is the runas_default user, unless the -u option is used). Honestly, this one is mostly obsolete. It has no effect unless the env_reset flag has been disabled or HOME is in the env_keep list, and both of those are strongly discouraged. It's off by default.
  • set_logname โ€” ๐Ÿชช Normally, sudo sets the LOGNAME and USER environment variables to the name of the target user (the user specified by runas_default, unless the -u option is given). But some programs, including the RCS revision control system, use LOGNAME to figure out the real identity of the user, so you might want to change this. You can do that by negating the set_logname option. Heads up: set_logname won't do anything if env_reset hasn't been disabled and the env_keep list contains LOGNAME or USER. It's on by default.
  • set_utmp โ€” ๐Ÿ“ When enabled, sudo creates an entry in the utmp (or utmpx) file whenever a pseudo-terminal gets allocated. sudo allocates one when it's running in a terminal and at least one of these is enabled: log_input, log_output, log_stdin, log_stdout, log_stderr, log_ttyin, log_ttyout, or use_pty. By default, the new entry is a copy of the user's existing utmp entry (if they have one), with the tty, time, type, and pid fields updated. It's on by default.
  • setenv โ€” ๐Ÿงš Lets the user turn off the env_reset option from the command line with the -E option. On top of that, environment variables set from the command line skip the restrictions imposed by env_check, env_delete, and env_keep. So only trusted users should be allowed to set variables this way, no cap. It's off by default.
  • shell_noargs โ€” ๐Ÿš If set and sudo is invoked with no arguments, it acts like you passed the -s option. That means it runs a shell as root. The shell is picked from the SHELL environment variable if it's set, and falls back to the shell listed in the invoking user's /etc/passwd entry if not. It's off by default.
  • stay_setuid โ€” ๐ŸŽ€ Normally, when sudo runs a command, the real and effective user-IDs get set to the target user (root by default). This option changes that so the real user-ID stays as the invoking user's user-ID. In other words, sudo acts like a set-user-ID wrapper. That can help on systems that disable some potentially dangerous functionality when a program runs set-user-ID. This option only does anything on systems that support either the setreuid(2) or setresuid(2) system call. It's off by default.
  • sudoedit_checkdir โ€” ๐Ÿ“‚ If set, sudoedit checks every directory component of the path being edited for writability by the invoking user. Symbolic links won't be followed in writable directories, and sudoedit refuses to edit a file that lives in a writable directory. These restrictions aren't enforced when sudoedit runs as root. On some systems, if any directory component of the path isn't readable by the target user, sudoedit can't edit the file at all. It's on by default.
    • This setting was first introduced in version 1.8.15, but it initially had a race condition. The check for symbolic links in writable intermediate directories was added in version 1.8.16.
  • sudoedit_follow โ€” ๐Ÿ”— By default, sudoedit won't follow symbolic links when opening files. Enable sudoedit_follow to let sudoedit open symbolic links. You can override it on a per-command basis with the FOLLOW and NOFOLLOW tags. It's off by default.
    • This setting is only supported by version 1.8.15 or higher.
  • syslog_pid โ€” ๐Ÿชต When logging via syslog(3), this includes the process ID in the log entry. It's off by default.
    • This setting is only supported by version 1.8.21 or higher.
  • targetpw โ€” ๐ŸŽฏ If set, sudo asks for the password of the user specified by the -u option (which defaults to the value of runas_default) instead of your own when running a command or editing a file. This flag rules out using a user-ID that isn't in the passwd database as an argument to -u. It's off by default.
  • tty_tickets โ€” ๐ŸŽŸ๏ธ If set, users have to authenticate on a per-tty basis. With this on, sudo keeps a separate record in the time stamp file for each terminal. If it's disabled, one record is shared across all login sessions.
    • This option has been superseded by the timestamp_type option.
  • umask_override โ€” ๐ŸŽญ If set, sudo sets the umask exactly as specified in the sudoers file, with no changes. That makes it possible to specify a umask in sudoers that's more permissive than the user's own umask, which matches historical behavior. If umask_override isn't set, sudo sets the umask to the union of the user's umask and whatever sudoers specifies. It's off by default.
  • use_netgroups โ€” ๐ŸŒ If set, netgroups (prefixed with +) can be used in place of a user or host. For LDAP-based sudoers, netgroup support needs an expensive sub-string match on the server, unless the NETGROUP_BASE directive is present in the /etc/ldap.conf file. If you don't need netgroups, disable this option to take some load off your LDAP server. It's on by default.
  • use_pty โ€” ๐Ÿ›ก๏ธ If set, and sudo is running in a terminal, the command runs in a new pseudo-terminal. If the sudo process isn't attached to a terminal, use_pty does nothing.
    • A malicious program running under sudo could inject commands into your terminal, or run a background process that keeps access to your terminal device even after the main program finishes. Running the command in a separate pseudo-terminal shuts that attack down. ๐Ÿ’œ

Okay so, picking up right where the use_pty vibe left off ๐Ÿ’…

A side effect of running the command in a new pseudo-terminal is that input will be passed to the command even if it is non-interactive. ๐Ÿ˜ญ So, for example, keys pressed while a non-interactive command is running will be eaten by sudo instead of being passed to the shell after the command exits. Chaotic, I know.

This flag is on by default for sudo 1.9.14 and above. ๐ŸŒธ

  • user_command_timeouts โ€” okay this one is so useful ๐Ÿ•’ If set, the user can put a timeout on the command line. If the timeout runs out before the command finishes, the command gets terminated. If there's a timeout in the sudoers file AND one on the command line, the smaller one wins. See the Timeout_Spec section for how the timeout syntax works. This flag is off by default.

This setting is only supported by version 1.8.20 or higher. โœจ

  • utmp_runas โ€” if set, sudo stores the name of the runas user when it updates the utmp (or utmpx) file ๐Ÿ‘€ By default, sudo stores the name of the invoking user instead. This flag is off by default.
  • visiblepw โ€” so by default, sudo will refuse to run if the user has to type a password but the terminal can't turn off echo ๐Ÿ™ˆ If the visiblepw flag is set, sudo will prompt for a password even when it would be visible on the screen. This is what makes stuff like ssh somehost sudo ls possible, since by default ssh(1) doesn't allocate a tty when running a command. This flag is off by default.

๐Ÿ”ข Integers

  • closefrom โ€” before it runs a command, sudo closes every open file descriptor except standard input, standard output, and standard error (file descriptors 0โ€“2) ๐Ÿงน The closefrom option can be used to pick a different file descriptor to start closing from. The default is 3.
  • command_timeout โ€” the maximum amount of time a command is allowed to run before it gets terminated โณ See the Timeout_Spec section for a description of the timeout syntax.

This setting is only supported by version 1.8.20 or higher. โœจ

  • log_server_timeout โ€” the maximum amount of time to wait when connecting to a log server or waiting for a server response ๐Ÿ“ก See the Timeout_Spec section for a description of the timeout syntax. The default value is 30 seconds.

This setting is only supported by version 1.9.0 or higher. โœจ

  • maxseq โ€” the maximum sequence number that will be substituted for the %{seq} escape in the I/O log file (see the iolog_dir description below for more info) ๐Ÿ”ข While the value substituted for %{seq} is in base 36, maxseq itself should be written in decimal. Values larger than 2176782336 (which corresponds to the base 36 sequence number "ZZZZZZ") will be silently truncated to 2176782336. The default value is 2176782336.

Once the local sequence number reaches the value of maxseq, it will "roll over" to zero, after which sudoers will truncate and reuse any existing I/O log path names. ๐Ÿ”

This setting is only supported by version 1.8.7 or higher.

  • passwd_tries โ€” the number of tries a user gets to type their password before sudo logs the failure and exits ๐Ÿ˜ค The default is 3.
  • syslog_maxlen โ€” okay, real talk: on lots of systems, syslog(3) has a pretty small log buffer ๐Ÿ“ IETF RFC 5424 says syslog servers must support messages of at least 480 bytes and should support messages up to 2048 bytes. By default, sudoers makes log messages up to 980 bytes, which matches the historic BSD syslog implementation (that used a 1024 byte buffer to store the message, date, hostname, and program name). To keep syslog messages from getting truncated, sudoers splits up log messages that are larger than syslog_maxlen bytes. When a message is split, the extra parts include the string "(command continued)" after the user name and before the continued command line arguments.

This setting is only supported by version 1.8.19 or higher. โœจ

๐Ÿงฎ Integers that can be used in a boolean context

  • loglinelen โ€” number of characters per line for the file log ๐Ÿ“ This value decides when to wrap lines so the log files look nicer. It has no effect on the syslog log file, only the file log. The default is 80 (use 0 or negate the option to disable word wrap).
  • passwd_timeout โ€” number of minutes before the sudo password prompt times out, or 0 for no timeout โŒ› The timeout may include a fractional component if minute granularity isn't enough, for example 2.5. The default is 0.
  • timestamp_timeout โ€” number of minutes that can pass before sudo asks for a password again ๐Ÿ•ฐ๏ธ The timeout may include a fractional component if minute granularity isn't enough, for example 2.5. The default is 15. Set this to 0 to always prompt for a password. If set to a value less than 0, the user's time stamp won't expire until the system is rebooted. This can be used to let users create or delete their own time stamps via sudo -v and sudo -k respectively. ๐ŸŽŸ๏ธ
  • umask โ€” the file mode creation mask to use when running the command ๐ŸŽญ Negate this option or set it to 0777 to stop sudoers from changing the umask. Unless the umask_override flag is set, the actual umask will be the union of the user's umask and the value of the umask setting, which defaults to 0022. This guarantees that sudo never lowers the umask when running a command. ๐Ÿ›ก๏ธ

If umask is explicitly set in sudoers, it overrides any umask setting in PAM or login.conf. If umask isn't set in sudoers, the umask specified by PAM or login.conf takes precedence. The umask setting in PAM isn't used for sudoedit, since it doesn't create a new PAM session.

๐Ÿงต Strings

  • apparmor_profile โ€” the default AppArmor profile to transition into when executing the command ๐Ÿฆ‹ The default apparmor_profile can be overridden for individual sudoers entries by specifying the APPARMOR_PROFILE option. This option is only available when sudo is built with AppArmor support.
  • cmddenial_message โ€” if set, sudo displays this message when a user is denied access to run the specified command, but is listed in the sudoers file for the host ๐Ÿšซ This can be used to give users extra, site-specific info when a command is denied by the security policy. It doesn't override the standard warning users get when a command is denied.
  • authfail_message โ€” message displayed after a user fails to authenticate ๐Ÿ˜ฌ The message may include the %d escape, which expands to the number of failed password attempts. If set, it overrides the default message, "%d incorrect password attempt(s)".
  • badpass_message โ€” message displayed if a user types an incorrect password ๐Ÿ™ƒ The default is "Sorry, try again." unless insults are enabled. ๐Ÿ”ฅ
  • editor โ€” a colon (:) separated list of editor path names used by sudoedit and visudo ๐Ÿ“š For sudoedit, this list is used to find an editor when none of the SUDO_EDITOR, VISUAL or EDITOR environment variables are set to an editor that exists and is executable. For visudo, it's used as an allowlist of editors: visudo picks the editor that matches the user's SUDO_EDITOR, VISUAL or EDITOR environment variable if possible, or the first editor in the list that exists and is executable if not. Unless invoked as sudoedit, sudo doesn't preserve the SUDO_EDITOR, VISUAL or EDITOR environment variables unless they're present in the env_keep list or the env_reset option is disabled. The default is /usr/bin/editor.
  • intercept_type โ€” the underlying mechanism used by the intercept and log_subcmds options ๐Ÿงฉ It has the following possible values:

The trace bit is how sudo can intercept execve(2) on Linux, but only when seccomp(2) filtering is turned on. โœจ If /proc/sys/kernel/seccomp/actions_avail is missing or doesn't have a "trap" element, intercept_type set to trace does absolutely nothing and sudo falls back to dso ๐Ÿซ 

The default is trace if your system supports it, and dso if it doesn't. Simple, iconic, no notes ๐Ÿ’…

๐Ÿ“ iolog_dir

This is the top-level directory used to build the path for the input/output log directory ๐Ÿ—‚๏ธ It only matters if log_input or log_output is turned on, or if the LOG_INPUT or LOG_OUTPUT tags show up on a command. The session sequence number, if there is one, gets stored in that directory. The default is /var/log/sudo-io ๐ŸŒธ

Here are the percent (%) escapes you can use, bestie:

  • %{seq} โ€” expands to a monotonically increasing base-36 sequence number, like 0100A5. Every two digits become a new directory, so that one turns into 01/00/A5 ๐Ÿ”ข
  • %{user} โ€” expands to the invoking user's login name ๐Ÿ™‹
  • %{group} โ€” expands to the name of the invoking user's real group-ID ๐Ÿ‘ฅ
  • %{runas_user} โ€” expands to the login name of the user the command runs as, like root ๐Ÿ‘‘
  • %{runas_group} โ€” expands to the group name of the user the command runs as, like wheel ๐ŸŽ€
  • %{hostname} โ€” expands to the local host name, minus the domain name ๐Ÿ–ฅ๏ธ
  • %{command} โ€” expands to the base name of the command being run ๐Ÿงพ

On top of those, any escape sequences your system's strftime(3) function supports will also get expanded โฐ

If you want a literal % character, type %% ๐Ÿ’–

Any path name separator characters (/) in the user, group, or host name get swapped out for an underbar (_) during expansion. No sneaky directory creation allowed ๐Ÿ™…โ€โ™€๏ธ

๐Ÿ“„ iolog_file

This is the path name, relative to iolog_dir, where input/output logs get stored when log_input or log_output is enabled, or when the LOG_INPUT or LOG_OUTPUT tags are on a command. iolog_file is allowed to have directory components in it ๐Ÿ—ƒ๏ธ The default is %{seq} ๐Ÿฆ‹

Check the iolog_dir option above for the full list of supported percent (%) escapes. It's the same deal ๐Ÿซถ

On top of those escapes, path names that end in six or more Xs get those Xs replaced with a unique mix of digits and letters, kind of like the mktemp(3) function does โœจ

If the path you get by smooshing iolog_dir and iolog_file together already exists, the existing I/O log file gets truncated and overwritten. The only way to avoid that is for iolog_file to end in six or more Xs ๐Ÿ˜ญ

๐Ÿ‘€ iolog_flush

If this is set, sudo flushes I/O log data to disk after every write instead of buffering it ๐Ÿ’ง This makes it possible to watch the logs in real time while the program is still running, which is so cool, but it can seriously reduce how well I/O log compression works ๐Ÿ“‰ This flag is off by default ๐Ÿ™ˆ

This setting is only supported in version 1.8.20 or higher.

๐Ÿ‘ฅ iolog_group

This is the group name to look up when setting the group-ID on new I/O log files and directories ๐Ÿซง If iolog_group isn't set, the primary group-ID of the user specified by iolog_user gets used. If neither iolog_group nor iolog_user is set, I/O log files and directories get created with group-ID 0 ๐ŸŒˆ

This setting is only supported in version 1.8.19 or higher.

๐Ÿ” iolog_mode

This is the file mode used when creating I/O log files ๐Ÿ” Only the read and write permission bits for owner, group, or other are honored. Everything else gets ignored. The file permissions will always include the owner read and write bits, even if they aren't in the mode you specified ๐Ÿ™Œ When creating I/O log directories, search (execute) bits get added so they match the read and write bits from iolog_mode ๐Ÿงš

The default is 0600, which means read and write for the user only ๐Ÿ’œ

This setting is only supported in version 1.8.19 or higher.

๐Ÿง‘ iolog_user

This is the user name to look up when setting the user and group-IDs on new I/O log files and directories ๐Ÿ’… If iolog_group is set, that gets used instead of the user's primary group-ID. By default, I/O log files and directories get created with user and group-ID 0 ๐Ÿ“

This setting is super useful when your I/O logs live on a Network File System (NFS) share ๐Ÿ—„๏ธ Having a dedicated user own the log files means sudoers doesn't write to them as user-ID 0, which NFS usually won't allow ๐Ÿ™ƒ

This setting is only supported in version 1.8.19 or higher.

๐Ÿ—‚๏ธ lecture_status_dir

This is the directory where sudo keeps per-user lecture status files ๐Ÿ“ Once a user has gotten the lecture, a zero-length file gets created in this directory so sudo doesn't lecture them again. Don't clear this directory when the system reboots, it needs to stick around ๐Ÿซถ The default is /var/lib/sudo/lectured ๐ŸŒธ

๐Ÿ” log_server_cabundle

This is the path to a certificate authority bundle file, in PEM format ๐Ÿ“œ It gets used instead of the system's default certificate authority database when authenticating the log server ๐Ÿ”‘ The default is to use the system's default certificate authority database. This setting does nothing unless log_servers is set and the remote log server is secured with TLS ๐Ÿ™…โ€โ™€๏ธ

This setting is only supported in version 1.9.0 or higher.

๐Ÿชช log_server_peer_cert

This is the path to the sudo client's certificate file, in PEM format ๐ŸŽซ It's required when the remote log server is secured with TLS and client certificate validation is enabled. For sudo_logsrvd, client certificate validation is controlled by the tls_checkpeer option, which defaults to false ๐Ÿ™ˆ

This setting is only supported in version 1.9.0 or higher.

๐Ÿ—๏ธ log_server_peer_key

This is the path to the sudo client's private key file, in PEM format ๐Ÿคซ It's required when the remote log server is secured with TLS and client certificate validation is enabled. For sudo_logsrvd, client certificate validation is controlled by the tls_checkpeer flag, which defaults to false ๐Ÿ”ฅ

This setting is only supported in version 1.9.0 or higher.

๐Ÿ“ง mailsub

This is the subject line of the mail sent to the mailto user ๐Ÿ’Œ The escape %h expands to the machine's host name. The default is *** SECURITY information for %h *** ๐Ÿšจ

๐Ÿšซ noexec_file

As of sudo version 1.8.1 this option is no longer supported ๐Ÿ’€ The path to the noexec file should now be set in the sudo.conf(5) file instead ๐Ÿซ 

๐Ÿ”‘ pam_askpass_service

On systems that use PAM for authentication, this is the service name used when the -A option is specified ๐Ÿง The default value is either sudo or sudo-i, depending on whether the -i option is also specified. Check out the description of pam_service for more deets โœจ

This setting is only supported in version 1.9.9 or higher.

๐Ÿ” pam_login_service

On systems that use PAM for authentication, this is the service name used when the -i option is specified ๐ŸŒท The default value is sudo-i. See the description of pam_service for more info ๐Ÿ’…

This setting is only supported in version 1.8.8 or higher.

๐Ÿ›ก๏ธ pam_service

On systems that use PAM for authentication, the service name picks which PAM policy to apply ๐Ÿ›ก๏ธ This usually lines up with an entry in the pam.conf file or a file in the /etc/pam.d directory ๐Ÿ“‚ The default value is sudo ๐Ÿ’–

This setting is only supported in version 1.8.8 or higher.

๐Ÿ’ฌ passprompt

This is the default prompt used when asking for a password ๐Ÿ—ฃ๏ธ You can override it with the -p option or the SUDO_PROMPT environment variable ๐Ÿซง These percent (%) escapes are supported:

  • %H โ€” expands to the local host name including the domain name, but only if the machine's host name is fully qualified or the fqdn option is set ๐ŸŒ
  • %h โ€” expands to the local host name without the domain name ๐Ÿ 

Okay bestie, picking up right where the passprompt escapes left off ๐Ÿ’…

  • %p โ€” gets swapped for the user whose password is being asked for ๐Ÿ” (it respects the rootpw, targetpw and runaspw flags in sudoers)
  • %U โ€” gets swapped for the login name of the user the command will run as (it defaults to root ๐Ÿ‘‘)
  • %u โ€” gets swapped for the invoking user's login name ๐Ÿซถ
  • %% โ€” two % characters in a row collapse into one single % ๐Ÿ™ˆ

On systems that use PAM for authentication, passprompt only kicks in if the prompt from the PAM module matches Password: or username's Password: . This is the vibe check that keeps passprompt from crashing challenge-response style authentication ๐Ÿงš The passprompt_override flag can change this if you want it to behave differently.

The default is [sudo] password for %p: โœจ

  • role โ€” the default SELinux role used when sudo builds a fresh security context to run the command ๐Ÿฆ‹ You can override it per command in the sudoers file or with command line options. This one only shows up if sudo was built with SELinux support ๐ŸŒˆ
  • runas_default โ€” the default user to run commands as when you don't pass -u on the command line ๐Ÿ‘‘ It defaults to root.
  • sudoers_locale โ€” the locale used when parsing the sudoers file, logging commands, and sending email ๐Ÿ’œ Changing the locale can change how sudoers gets interpreted, so be careful bestie. It defaults to C.
  • timestamp_type โ€” sudoers uses per-user time stamp files for credential caching ๐ŸŒธ This option picks the type of time stamp record. The possible values are:
    • global โ€” one time stamp record for all of a user's login sessions, no matter the terminal or parent process ID โœจ There's an extra record that serializes password prompts when sudo runs multiple times in a pipeline, but that doesn't affect authentication.
    • ppid โ€” one time stamp record for every process sharing the same parent process ID (usually the shell) ๐Ÿš Commands run from the same shell (or another common parent) won't ask for a password for timestamp_timeout minutes (15 by default). Commands run via sudo from a different parent process ID, like from a shell script, get authenticated separately.
    • tty โ€” one time stamp record per terminal, so each login session gets authenticated separately ๐Ÿ’– If there's no terminal, it acts the same as ppid. Commands from the same terminal won't need a password for timestamp_timeout minutes (15 by default).
    • kernel โ€” the time stamp gets stored in the kernel as an attribute of the terminal device ๐Ÿ”ฅ If there's no terminal, it acts the same as ppid. Negative timestamp_timeout values aren't supported and positive values cap out at 60 minutes. This is currently only supported on OpenBSD ๐Ÿก

The default value is tty. This setting is only supported by version 1.8.21 or higher ๐ŸŽ€

  • timestampdir โ€” the directory where sudo stores its time stamp files ๐Ÿ“ It should get cleared when the system reboots. The default is /run/sudo/ts.
  • timestampowner โ€” the owner of the lecture status directory, the time stamp directory, and every file stored in them ๐Ÿงš It defaults to root.
  • type โ€” the default SELinux type used when sudo builds a new security context to run the command ๐ŸŒˆ You can override it per command in the sudoers file or with command line options. Like role, this is only available when sudo is built with SELinux support.

Next up are the options that work in a boolean context ๐Ÿ’…

  • admin_flag โ€” this one points to a file that gets created the first time a user in the sudo or admin groups runs sudo ๐ŸŽ€ It's only available if sudo was configured with the --enable-admin-flag option. The default value is ~/.sudo_as_admin_successful.
  • env_file โ€” the fully qualified path to a file full of variables to set in the environment of the program being run ๐Ÿ“‚ Entries should look like VARIABLE=value or export VARIABLE=value, and the value can optionally be wrapped in single or double quotes. Variables only get added if they don't already exist in the environment. This file counts as part of the security policy, so its contents skip the other sudo environment restrictions like env_keep and env_check ๐Ÿ”
  • exempt_group โ€” users in this group are exempt from password and PATH requirements ๐Ÿซถ The group name shouldn't have a % prefix. It's not set by default.
  • fdexec โ€” decides whether sudo runs a command by its path or by an open file descriptor ๐Ÿฆ‹ The possible values are:
    • always โ€” always executes by file descriptor ๐Ÿ”ฅ
    • never โ€” never executes by file descriptor ๐Ÿ™ˆ
    • digest_only โ€” only executes by file descriptor if the command has a digest attached in the sudoers file ๐Ÿ’œ

The default is digest_only, and that's the move because it avoids a time of check versus time of use race condition when the command lives in a directory the invoking user can write to ๐Ÿซฃ

โš ๏ธ fdexec changes the first element of the argument vector for scripts ($0 in the shell) because of how the kernel runs script interpreters. Instead of being a normal path, it refers to a file descriptor, like /dev/fd/4 on Solaris or /proc/self/fd/4 on Linux. The workaround is to use the SUDO_COMMAND environment variable instead ๐Ÿ˜ญ

The fdexec setting only matters when the command gets matched by path name ๐ŸŒธ It has no effect if the command is matched by the built-in ALL alias. This setting is only supported by version 1.8.20 or higher, and if the operating system doesn't support the fexecve(2) system call, it does nothing at all ๐Ÿ’€

  • group_plugin โ€” a string containing a sudoers group plugin with optional arguments ๐Ÿงš It should be made of the plugin path (fully qualified or relative to the /usr/libexec/sudo directory), followed by any configuration arguments the plugin needs. Those arguments get passed to the plugin's initialization function. If there are arguments, the whole string has to be wrapped in double quotes, "" ๐ŸŽ€

On 64-bit systems, if the plugin is there but won't load, sudoers goes looking for a 64-bit version and loads that as a fallback if it exists โœจ The exact rules depend on the system. On Solaris, if the plugin lives in a directory ending in "lib", sudoers appends "/64" to the directory name for the fallback path, so /usr/lib/group_plugin.so becomes /usr/lib/64/group_plugin.so. On Linux, a directory ending in "lib" turns into "lib64" as the fallback, so /usr/lib/group_plugin.so becomes /usr/lib64/group_plugin.so. On all other systems, the fallback gets made by adding a "64" before the file extension, so group_plugin.so becomes group_plugin64.so ๐ŸŒˆ

On AIX systems, the plugin can be either a shared object ending in .so, or an archive file containing a shared object ending in .a with the name of the shared object in parentheses at the very end ๐Ÿฆ‹

For more info, check out "GROUP PROVIDER PLUGINS" ๐Ÿ‘‘

  • lecture โ€” controls when a short lecture gets printed along with the password prompt ๐Ÿ“š The possible values are:
    • always โ€” always lectures the user ๐Ÿ˜ญ
    • never โ€” never lectures the user ๐Ÿซถ
    • once โ€” only lectures the user the first time they run sudo ๐Ÿ“

If you don't give it a value, it implies once. Negating the option means never gets used. The default value is never ๐Ÿ’–

  • lecture_file โ€” the path to a file with an alternate sudo lecture ๐Ÿ“ If the named file exists, it gets used in place of the standard lecture. By default, sudo uses a built-in lecture ๐Ÿงš
  • listpw โ€” controls when a password is required when a user runs sudo with the -l option ๐Ÿ” The possible values are:
    • all โ€” every one of the user's sudoers file entries for the current host has to have the NOPASSWD flag set to skip entering a password ๐Ÿฅน
  • always ๐Ÿ”’ โ€” You'll have to type your password every single time you use the -l option, no exceptions bestie ๐Ÿ˜ญ
  • any โœจ โ€” At least one of your sudoers entries for the current host needs the NOPASSWD flag set so you can skip the password prompt ๐Ÿ’…
  • never ๐Ÿฆ‹ โ€” You'll never need to enter a password to use the -l option, main character energy ๐Ÿ‘‘

If no value is specified, any is implied ๐Ÿซถ Negating the option gives you never. The default is any ๐Ÿ’–

  • log_format ๐Ÿ“ โ€” The event log format. These are the supported ones:
    • json ๐Ÿ“ โ€” Right now this is just an alias for json_pretty. In a future version of sudo, json will be equivalent to json_compact. JSON log entries include the full user details, plus the execution environment if the command was allowed ๐Ÿงš
    • json_compact ๐Ÿ’œ โ€” Logs events in "compact" (minified) JSON format. Each event is its own JSON object on a single line with zero extra whitespace. Heads up: because of protocol limits, JSON events sent via syslog may get truncated ๐Ÿ™ˆ
    • json_pretty ๐ŸŒธ โ€” Logs events in "pretty" JSON format. When logging to a file, the whole file is treated as one single JSON object made of multiple events, and each event spans multiple lines. When logging via syslog, json_pretty and json_compact are exactly the same โœจ
    • sudo ๐ŸŽ€ โ€” Logs events in the traditional sudo-style format. Check out "EVENT LOGGING" for the deets ๐Ÿ’…

This setting affects logs sent via syslog(3) as well as the file specified by the logfile setting, if you've got one. The default is sudo ๐ŸŒˆ

  • logfile ๐Ÿ“„ โ€” The path to the sudo log file (not the syslog one!). Setting a path turns on logging to a file, and negating this option turns it off. By default, sudo logs via syslog ๐Ÿซง
  • mailerflags ๐Ÿ’Œ โ€” Flags to use when invoking the mailer. Defaults to -t ๐Ÿฆ‹
  • mailerpath ๐Ÿ“ฌ โ€” The path to the mail program used to send warning mail. Negate it to stop sudo from sending mail altogether ๐Ÿ™…โ€โ™€๏ธ It defaults to the path to sendmail that was found at configure time ๐Ÿงš
  • mailfrom ๐Ÿ’• โ€” The address used for the "from" address when sending warning and error mail. Wrap the address in double quotes ("") so sudo doesn't get confused by the @ sign ๐Ÿ™ˆ It defaults to the name of the user running sudo ๐Ÿ‘‘
  • mailto ๐Ÿ“จ โ€” The address that warning and error mail gets sent to. Negate it to stop sudo from sending mail ๐Ÿซถ Wrap the address in double quotes ("") here too, so sudo doesn't misinterpret the @ sign ๐Ÿ’… It defaults to root ๐ŸŒธ
  • rlimit_as ๐Ÿง  โ€” The maximum size (in bytes) the process's address space may grow to, if your OS supports it. See "Resource limits" for more info โœจ
  • rlimit_core ๐Ÿ’ฅ โ€” The largest size (in bytes) a core dump file may be. See "Resource limits" for more info. Defaults to 0, which means no core dump gets created ๐Ÿ™…โ€โ™€๏ธ
  • rlimit_cpu โฑ๏ธ โ€” The maximum amount of CPU time (in seconds) the process may use. See "Resource limits" for more info ๐Ÿ’œ
  • rlimit_data ๐Ÿ—‚๏ธ โ€” The maximum size (in bytes) of the data segment for the process. See "Resource limits" for more info ๐ŸŒˆ
  • rlimit_fsize ๐Ÿ“ โ€” The largest file (in bytes) the process may create. See "Resource limits" for more info ๐ŸŽ€
  • rlimit_locks ๐Ÿ” โ€” The maximum number of locks the process may establish, if your OS supports it. See "Resource limits" for more info ๐Ÿฆ‹
  • rlimit_memlock ๐ŸงŠ โ€” The maximum size (in bytes) the process may lock in memory, if your OS supports it. See "Resource limits" for more info ๐Ÿ’…
  • rlimit_nofile ๐Ÿ—ƒ๏ธ โ€” The maximum number of files the process may have open. See "Resource limits" for more info โœจ
  • rlimit_nproc ๐Ÿƒโ€โ™€๏ธ โ€” The maximum number of processes the user may run at the same time. See "Resource limits" for more info ๐Ÿ‘‘
  • rlimit_rss ๐Ÿง  โ€” The maximum size (in bytes) the process's resident set size may grow to. See "Resource limits" for more info ๐Ÿซง
  • rlimit_stack ๐Ÿฅž โ€” The maximum size (in bytes) the process's stack may grow to. See "Resource limits" for more info ๐ŸŒธ
  • restricted_env_file ๐ŸŒท โ€” This option specifies the fully qualified path to a file containing variables to be set in the environment of the program being run. Entries in the file should be assignments in the form VARIABLE=value or export VARIABLE=value, separated by newline characters. The value may optionally be wrapped in single or double quotes. Shell-style variable substitution is not supported ๐Ÿ™…โ€โ™€๏ธ Empty lines and comments starting with a pound sign (#) get ignored ๐Ÿ’–

Variables in this file only get added if they don't already exist in the environment. Unlike env_file, the file's contents are not trusted, and they're processed in a way that's similar to the invoking user's environment ๐Ÿšซ If env_reset is enabled, variables in the file only get added if they're matched by either the env_check or env_keep list. If env_reset is disabled, variables in the file are added as long as they aren't matched by the env_delete list. Either way, the contents of restricted_env_file are processed before the contents of env_file ๐Ÿงš

  • runchroot ๐Ÿฐ โ€” If set, sudo will use this value as the root directory when running a command. The special value * lets the user pick the root directory with sudo's -R option. Check out the "Chroot_Spec" section for more details ๐Ÿซถ

You can only use runchroot as a command-specific Defaults setting if the command exists at the same path both inside and outside the chroot jail. This restriction doesn't apply to global, host, or user-based Defaults settings, or to a Cmnd_Spec that includes a Chroot_Spec ๐Ÿ’…

This setting is only supported by version 1.9.3 or higher ๐ŸŒˆ

โš ๏ธ Support for setting the root directory is deprecated and will be removed in a future version of sudo. Plan accordingly, bestie ๐Ÿ’€
  • runcwd ๐Ÿ“‚ โ€” If set, sudo will use this value as the working directory when running a command. The special value * lets the user pick the working directory with sudo's -D option. Check out the "Chdir_Spec" section for more details โœจ

This setting is only supported by version 1.9.3 or higher ๐ŸŒท

  • secure_path ๐Ÿ›ก๏ธ โ€” If set, sudo will use this value in place of the user's PATH environment variable. There are two basic use cases for secure_path:
    1. To make it possible for sudo to find system administrator commands in directories that may not be in the default user path, like /usr/sbin ๐Ÿงฐ
    2. To help protect scripts and programs that run other commands without first setting PATH to a safe value. Otherwise, a user with limited privileges may be able to run arbitrary commands by manipulating the PATH, if the command being run executes other commands without using a fully-qualified path name ๐Ÿ˜ค

Users in the group specified by the exempt_group option are not affected by secure_path. This option is disabled by default ๐Ÿซง

  • syslog ๐Ÿ“š โ€” The syslog facility if syslog is being used for logging (negate to disable syslog logging). Defaults to authpriv ๐ŸŽ€

The following syslog facilities are supported: authpriv (if your OS supports it), auth, daemon, user, local0, local1, local2, local3, local4, local5, local6, and local7 ๐ŸŒˆ

  • syslog_badpri ๐Ÿšจ โ€” The syslog priority to use when the user isn't allowed to run a command, or when authentication is unsuccessful. Defaults to alert ๐Ÿ˜ฌ

The following syslog priorities are supported: alert, crit, debug, emerg, err, info, notice, warning, and none. Negating the option, or setting it to a value of none, will disable logging of unsuccessful commands ๐Ÿ™ˆ

  • syslog_goodpri ๐ŸŒŸ โ€” The syslog priority to use when the user is allowed to run a command and authentication is successful. Defaults to notice ๐Ÿฆ‹

See syslog_badpri for the list of supported syslog priorities. Negating the option, or setting it to a value of none, will disable logging of successful commands ๐Ÿ’œ

  • verifypw ๐Ÿ”‘ โ€” This option controls when a password is required when a user runs sudo with the -v option. It has the following possible values:
    • all ๐Ÿซถ โ€” All the user's sudoers file entries for the current host must have the NOPASSWD flag set to avoid entering a password
    • always ๐Ÿ”’ โ€” The user must always enter a password to use the -v option
    • any โœจ โ€” At least one of the user's sudoers file entries for the current host must have the NOPASSWD flag set to avoid entering a password
    • never ๐Ÿฆ‹ โ€” The user need never enter a password to use the -v option

If no value is specified, all is implied ๐Ÿ’… Negating the option results in a value of never being used. The default is all ๐Ÿ‘‘

Lists that can be used in a boolean context ๐Ÿง โœจ

env_check ๐Ÿซง Environment variables that get yanked from the user's environment unless they count as "safe". For everything except TZ, "safe" means the value has no % or / characters in it. This guards against printf-style format vulnerabilities in badly written programs ๐Ÿ™ˆ The TZ variable is considered unsafe if any of these are true:

  • It's a fully-qualified path name (optionally starting with a colon :) that doesn't match where the zoneinfo directory lives.
  • It contains a .. path element.
  • It contains white space or non-printable characters.
  • It's longer than the value of PATH_MAX.

The argument can be a double-quoted, space-separated list or a single value with no quotes. You can replace, add to, delete from, or disable the list with the =, +=, -=, and ! operators. Doesn't matter whether env_reset is on or off, anything listed in env_check gets kept in the environment as long as it passes that check โœจ The global list shows up when root runs sudo with the -V option.

env_delete ๐Ÿ—‘๏ธ Environment variables to be removed from the user's environment when env_reset is not in effect. The argument can be a double-quoted, space-separated list or a single value without quotes. Same deal with the =, +=, -=, and ! operators for replacing, adding, deleting, or disabling. The global list to remove is displayed when root runs sudo with the -V option. Lowkey, plenty of operating systems already strip potentially dangerous variables from any set-user-ID process (like sudo) ๐Ÿ’…

env_keep ๐ŸŒธ Environment variables to keep in the user's environment when env_reset is in effect. This gives you fine-grained control over the environment sudo-spawned processes get ๐ŸŽ€ The argument can be a double-quoted, space-separated list or a single value without quotes, and you can use the =, +=, -=, and ! operators to replace, add to, delete from, or disable the list. Root can see the global list of variables to keep with the -V option.

๐Ÿšจ Preserving HOME is a security risk, bestie. Plenty of programs use it to hunt down config or data files, and adding HOME to env_keep may let a user run unrestricted commands via sudo. It's strongly discouraged ๐Ÿ˜ญ If someone wants to edit files with sudo, they should run sudoedit (or sudo -e) so they get their usual editor config, instead of invoking the editor directly.

log_servers ๐Ÿฆ‹ A list of one or more servers for remote event and I/O log storage, separated by white space. The log servers have to be running sudo_logsrvd or another service that implements the protocol described in sudo_logsrv.proto(5).

Server addresses should look like host[:port][(tls)]. The host part can be a host name, an IPv4 address, or an IPv6 address wrapped in square brackets.

If the optional tls flag is there, the connection gets secured with Transport Layer Security (TLS) version 1.2 or 1.3. Anything older than TLS 1.2 is not supported, so no throwbacks ๐Ÿ”’

If you specify a port, it can be a port number or a well-known service name from the system service name database. If you don't, port 30343 is used for plaintext connections and port 30344 is used for TLS connections.

When log_servers is set, event log data gets logged both locally (see the syslog and log_file settings) and remotely, but I/O log data only goes remote. If multiple hosts are listed, they're tried in reverse order. If no log servers are available, the user can't run a command unless either the ignore_iolog_errors flag (I/O logging enabled) or the ignore_log_errors flag (I/O logging disabled) is set ๐Ÿ’€ Same goes if the connection to the log server drops while sudo is running: the command gets terminated, unless ignore_iolog_errors (I/O logging enabled) or ignore_log_errors (I/O logging disabled) is set.

This setting is only supported by version 1.9.0 or higher.

passprompt_regex ๐Ÿ” A list of POSIX extended regular expressions used to match password prompts in the terminal output. As an extension, if the regular expression begins with (?i), it gets matched case-insensitively. Each regular expression is limited to 1024 characters. This option is only used when log_passwords has been disabled. The default value is [Pp]assword[: ]*

This setting is only supported by version 1.9.10 or higher.


โœจ GROUP PROVIDER PLUGINS โœจ

The sudoers plugin has its own plugin interface so you can do non-Unix group lookups ๐Ÿงš It can query a group source other than the standard Unix group database, which is how you get support for the nonunix_group syntax we talked about earlier.

Group provider plugins are set with the group_plugin setting. The argument should be the plugin path, either fully-qualified or relative to the /usr/libexec/sudo directory, followed by any configuration options the plugin needs. Those options (if you give any) get passed to the plugin's initialization function. If there are options present, the whole string has to be wrapped in double quotes ("") ๐ŸŽ€

The following group provider plugins come installed by default:

group_file ๐Ÿ“„

The group_file plugin supports an alternate group file that uses the same syntax as the /etc/group file. You pass the path to the group file as an option to the plugin. For example, if the group file you want is /etc/sudo-group:

Defaults group_plugin="group_file.so /etc/sudo-group"

system_group ๐ŸŒˆ

The system_group plugin does group lookups through the standard C library functions getgrnam(3) and getgrid(3). It's useful when the user belongs to groups that aren't in the user's supplemental group vector. This plugin takes no options:

Defaults group_plugin=system_group.so

The group provider plugin API is described in detail in sudo_plugin(5) ๐Ÿ‘‘

โœจ EVENT LOGGING โœจ

okay so sudoers can log events in JSON or sudo format, and this bit is all about the sudo format ๐Ÿ“ depending on how sudoers is configured, it can log through syslog(3), to a local log file, or to both ๐Ÿซถ the two versions are almost identical, so you're not learning two things.

the one thing to know about the log data is that any control characters get written in octal with a leading #. so a horizontal tab shows up as #011 and an embedded carriage return shows up as #015 ๐Ÿ˜ญ space characters in the command path get stored as #040 too. command line arguments that contain spaces are wrapped in single quotes (''), which is how you can tell "multiple arguments" apart from "one argument that just has spaces in it" ๐Ÿงš and literal single quotes and backslashes (\) inside command line arguments get escaped with a backslash.


๐Ÿ’œ Accepted command log entries

this is the format for commands that sudo actually runs ๐Ÿฆ‹ it's split across multiple lines here so it's easier to read, but it's really one line:

date hostname progname: username : TTY=ttyname ; CHROOT=chroot ; \
    PWD=cwd ; USER=runasuser ; GROUP=runasgroup ; TSID=logid ; \
    ENV=env_vars COMMAND=command

here's what each field means, bestie ๐Ÿ‘‡

  • date โ€” the date the command was run ๐Ÿ“… it's usually in the format MMM, DD, HH:MM:SS. if you're logging via syslog(3), the actual date format is up to your syslog daemon. if you're logging to a file and the log_year option is on, the date also includes the year.
  • hostname โ€” the name of the host sudo was run on ๐Ÿ–ฅ๏ธ this only shows up when logging via syslog(3).
  • progname โ€” the name of the program, usually sudo or sudoedit ๐Ÿ’… also only shows up when logging via syslog(3).
  • username โ€” the login name of the user who ran sudo ๐Ÿซถ
  • ttyname โ€” the short name of the terminal sudo was run on (like console, tty01, or pts/0) โœจ if there wasn't a terminal, it says unknown.
  • chroot โ€” the root directory the command was run in, but only if one was specified ๐ŸŒธ
  • cwd โ€” the current working directory sudo was run from ๐Ÿ“‚
  • runasuser โ€” the user the command ran as ๐Ÿ‘‘
  • runasgroup โ€” the group the command ran as, but only if one was specified on the command line ๐ŸŽ€
  • logid โ€” an I/O log identifier you can use to replay the command's output ๐ŸŽฌ this only appears when the log_input or log_output option is enabled.
  • env_vars โ€” a list of environment variables set on the command line, if any were specified ๐ŸŒˆ
  • command โ€” the actual command that got executed, including any command line arguments ๐Ÿ”ฅ

messages get logged using the locale set by sudoers_locale, which defaults to the C locale. just so you know ๐Ÿค“


๐Ÿ™ˆ Denied command log entries

if the user isn't allowed to run the command, the reason for the denial comes right after the user name. the possible reasons are:

  • user NOT in sudoers โ€” the user isn't listed in the sudoers file at all ๐Ÿ’€
  • user NOT authorized on host โ€” the user is listed in the sudoers file, but they aren't allowed to run commands on this host ๐Ÿ™…โ€โ™€๏ธ
  • command not allowed โ€” the user is listed for this host, but they aren't allowed to run that specific command ๐Ÿ˜ญ
  • 3 incorrect password attempts โ€” the user failed to enter their password after 3 tries. the actual number of tries can vary depending on how many attempts failed and the value of the passwd_tries option ๐Ÿ”
  • a password is required โ€” the -n option was specified, but a password was needed anyway ๐Ÿ™ˆ
  • sorry, you are not allowed to set the following environment variables โ€” the user tried to set environment variables on the command line that sudoers doesn't allow ๐Ÿšซ

๐Ÿ’ฅ Error log entries

if an error happens, sudoers logs a message and, in most cases, emails the administrator too ๐Ÿ“ง these are the errors that can pop up, periodt:

  • parse error in /etc/sudoers near line N โ€” sudoers hit an error while parsing the file ๐Ÿง depending on the type of error, the real problem might be one line above or below the line number it gives you.
  • problem with defaults entries โ€” the sudoers file has one or more unknown Defaults settings ๐Ÿซ  this doesn't stop sudo from running, but you should check the sudoers file with visudo.
  • timestamp owner (username): No such user โ€” the time stamp directory owner (set by the timestampowner setting) couldn't be found in the password database ๐Ÿ‘€
  • unable to open/read /etc/sudoers โ€” the sudoers file couldn't be opened for reading ๐Ÿ˜ต this can happen when the file lives on a remote file system that maps user-ID 0 to something else. normally sudoers tries to open the file using group permissions to dodge this. you can either change the ownership of /etc/sudoers, or add an argument like sudoers_uid=N (where N is the user-ID that owns the sudoers file) to the end of the sudoers Plugin line in the sudo.conf(5) file ๐Ÿ› ๏ธ
  • unable to open /etc/sudoers โ€” the /etc/sudoers file is missing ๐Ÿ˜ฑ
  • /etc/sudoers is not a regular file โ€” the /etc/sudoers file exists, but it isn't a regular file or a symbolic link ๐Ÿ™ƒ
  • /etc/sudoers is owned by uid N, should be 0 โ€” the sudoers file has the wrong owner ๐Ÿ™…โ€โ™€๏ธ if you want the owner changed, add sudoers_uid=N (where N is the user-ID that owns the sudoers file) to the sudoers Plugin line in the sudo.conf(5) file.
  • /etc/sudoers is world writable โ€” the permissions let every user write to the sudoers file ๐Ÿšจ it must not be world-writable! the default file mode is 0440 (readable by owner and group, writable by nobody). you can change the default mode with the sudoers_mode option on the sudoers Plugin line in the sudo.conf(5) file.
  • /etc/sudoers is owned by gid N, should be 1 โ€” the sudoers file has the wrong group ownership ๐ŸŽ€ if you want to change the group, add sudoers_gid=N (where N is the group-ID that owns the sudoers file) to the sudoers Plugin line in the sudo.conf(5) file.
  • unable to open /run/sudo/ts/user-ID โ€” sudoers couldn't read or create the user's time stamp file ๐Ÿฅฒ this can happen when timestampowner is set to someone other than root, and /run/sudo isn't searchable by group or other. the default mode for /run/sudo is 0711.
  • unable to write to /run/sudo/ts/user-ID โ€” sudoers couldn't write to the user's time stamp file โœ๏ธ
  • /run/sudo/ts is owned by uid X, should be Y โ€” the time stamp directory is owned by someone other than timestampowner ๐Ÿง this can happen when the value of timestampowner has been changed. sudoers will ignore the time stamp directory until the owner is fixed.
  • /run/sudo/ts is group writable โ€” the time stamp directory is group-writable, but it should only be writable by timestampowner ๐Ÿšซ the default mode for the time stamp directory is 0700. sudoers will ignore the time stamp directory until the mode is fixed.

๐Ÿฆ‹ Notes on logging via syslog

by default, sudoers logs messages via syslog(3) ๐Ÿ“ฎ the date, hostname, and progname fields get added by the system's syslog(3) function, not by sudoers itself. so they can look different depending on the system ๐ŸŒˆ

the maximum size of syslog messages also varies from system to system. the syslog_maxlen setting lets you change the maximum syslog message size from the default of 980 bytes โœจ for more info, check out the description of syslog_maxlen.


๐ŸŒธ Notes on logging to a file

if the logfile option is set, sudoers logs to a local file, like /var/log/sudo ๐Ÿ“‚ when it logs to a file, it uses a format similar to syslog(3), but with a few important differences:

  1. the progname field is not present.
  2. the hostname is only logged if the log_host option is enabled.
  3. the date doesn't include the year unless the log_year option is enabled.
  4. lines longer than loglinelen characters (80 by default) get word-wrapped and continued on the next line with a four character indent ๐Ÿงต this makes entries way easier for a human to read, but it's harder to use grep(1) on the log files ๐Ÿ˜ญ if you set loglinelen to 0 (or negate it with a !), word wrap gets disabled.

โœจ I/O LOGGING โœจ

okay so when I/O logging is turned on, sudo runs the command inside a pseudo-terminal ๐Ÿ–ฅ๏ธ and logs the user input and/or output, depending on which sudoers flags are enabled ๐Ÿ’…. There are five distinct types of I/O that can get logged, and each one has its own sudoers flag ๐Ÿซถ

  • terminal input (log_ttyin) โ€” keystrokes the user types โŒจ๏ธ
  • terminal output (log_ttyout) โ€” command output shown on the screen ๐Ÿ“บ
  • standard input (log_stdin) โ€” input coming from a pipe or a file ๐Ÿ“ฅ
  • standard output (log_stdout) โ€” output going to a pipe or a file ๐Ÿ“ค
  • standard error (log_stderr) โ€” error output going to a pipe or a file ๐Ÿšจ

Also, the log_input flag and the LOG_INPUT command tag both turn on log_ttyin and log_stdin together ๐ŸŽ€. The log_output flag and the LOG_OUTPUT command tag turn on log_ttyout, log_stdout, and log_stderr all at once ๐ŸŒธ

To capture terminal input and output, sudo runs the command in a pseudo-terminal, logs the input and output, and then passes it along to the user ๐Ÿงš To capture standard input, standard output, or standard error, sudo uses a pipe to sit right in the middle of the stream, logs the I/O, and then hands it off to the other end of the pipe ๐Ÿฆ‹

I/O can be logged locally on the machine or sent to a remote log server ๐ŸŒˆ For local logs, the I/O goes into the directory set by the iolog_dir option (/var/log/sudo-io by default), and each session gets a unique session ID that shows up in the sudo log line, prefixed with TSID=. The iolog_file option controls how that session ID is formatted โœจ For remote logs, the log_servers setting names one or more log servers running sudo_logsrvd or some other server that speaks the protocol described in sudo_logsrv.proto(5) ๐Ÿ’œ

I/O logging pitfals ๐Ÿ™ˆ

okay this part is important, bestie ๐Ÿ˜ญ When standard input is being logged, anything sent to standard input gets consumed, even if the command you ran with sudo isn't actually reading standard input at all ๐Ÿ’€ This can go sideways when you use sudo inside a shell script that's supposed to process standard input. Say you've got this script:

#!/bin/sh
sudo echo testing
echo done

It behaves exactly as expected when you pass the script to the shell as an argument:

$ sh test.sh
testing
done

But if you pass the script to the shell on standard input instead, the sudo echo testing command eats the rest of the script ๐Ÿ˜ฑ That means the echo done line never gets executed:

$ sh -s < test.sh
testing

Good news though, there are a few ways to work around this ๐Ÿซถ

  1. Redirect standard input from /dev/null when you run a command through sudo that doesn't need to read standard input.
sudo echo testing < /dev/null
  1. Pass the script to the shell by its path name instead of sending it over standard input.
sh test.sh
  1. Turn off logging of standard input for commands that don't need to read it.
Defaults!/bin/echo !log_stdin

Sometimes logging standard input or standard output just isn't worth it ๐Ÿ™…โ€โ™€๏ธ For example, commands that push or pull huge amounts of data through standard output or standard input, like rsync(1) and tar(1), can fill up your log file system with superfluous data ๐Ÿ“ฆ You can turn off logging of standard input and standard output for those commands like this:

Cmnd_Alias COPY_CMDS = /usr/bin/tar, /usr/bin/cpio, /usr/bin/rsync

# Log input and output but omit stdin and stdout when copying files.
Defaults log_input, log_output
Defaults!COPY_CMDS !log_stdin, !log_stdout
โš ๏ธ Be aware, bestie: using the log_input flag or the LOG_INPUT command tag will also turn on log_stdin. And the log_ouput flag or the LOG_OUTPUT command tag will turn on log_stdout and log_stderr too ๐Ÿ˜ญ Careful ordering of your rules might be needed to get the results you actually want ๐Ÿ’…

I/O log format ๐Ÿ“‚

For both local and remote I/O logs, each log lives in its own directory, and that directory holds the following files ๐Ÿ—‚๏ธ

  • log โ€” a plain text file with info about the command ๐Ÿ“ The first line is a set of colon-delimited fields: the time the command was run, the name of the user who ran sudo, the name of the target user, the name of the target group (optional), the terminal sudo was run from, and the number of lines and columns of that terminal. The second and third lines hold the working directory the command was run from and the path name of the command itself (with arguments, if there were any).
  • log.json โ€” a JSON-formatted file with info about the command ๐Ÿ’œ It's kind of like the log file but has extra info and is super easy to extend. sudoreplay(8) will use log.json over the log file if it exists. The file may contain these elements:
    • timestamp โ€” a JSON object with the time the command was run. It has two values, seconds and nanoseconds.
    • columns โ€” the number of columns of the terminal the command ran on, or zero if there wasn't a terminal.
    • command โ€” the fully-qualified path of the command that was run.
    • lines โ€” the number of lines of the terminal the command ran on, or zero if there wasn't a terminal.
    • runargv โ€” a JSON array holding the command's argument vector, as passed to the execve(2) system call.
    • runenv โ€” a JSON array holding the command's environment, as passed to the execve(2) system call.
    • rungid โ€” the group ID the command ran as. This element only shows up when the user specifies a group on the command line.
    • rungroup โ€” the name of the group the command ran as. This element only shows up when the user specifies a group on the command line.
    • runuid โ€” the user ID the command ran as.
    • runuser โ€” the name of the user the command ran as.
    • submitcwd โ€” the current working directory at the time sudo was run.
    • submithost โ€” the name of the host the command was run on.
    • submituser โ€” the name of the user who ran the command via sudo.
    • ttyname โ€” the path name of the terminal the user invoked sudo from. If the command ran in a pseudo-terminal, ttyname will be different from the terminal the command actually ran in.
  • timing โ€” timing info used to replay the session ๐ŸŽฌ Each line has the I/O log entry type, the amount of time since the last entry, and then some type-specific data. The entry types and their data are:
    • 0 โ€” standard input, number of bytes in the entry
    • 1 โ€” standard output, number of bytes in the entry
    • 2 โ€” standard error, number of bytes in the entry
    • 3 โ€” terminal input, number of bytes in the entry
    • 4 โ€” terminal output, number of bytes in the entry
    • 5 โ€” window change, new number of lines and columns
    • 6 โ€” bug compatibility for sudo 1.8.7 terminal output
    • 7 โ€” command suspend or resume, signal received
  • ttyin โ€” raw input from the user's terminal, exactly as it was received ๐ŸŽ€ This file only shows up if the log_input or log_ttyin flags are set and sudo was run from a terminal. No post-processing happens here. If you want to read it by hand, you might wish to convert carriage return characters into line feeds. For example: gunzip -c ttyin | tr "\r" "\n"
  • stdin โ€” the standard input when there's no terminal, or input redirected from a pipe or file. This file only shows up if the log_input or log_stdin flags are set and standard input isn't connected to a terminal.
  • ttyout โ€” output from the pseudo-terminal, aka what the command writes to the screen ๐Ÿ“บ Terminal-specific post-processing gets done before the data is logged. So line feeds are usually turned into line feed/carriage return pairs, and tabs may get expanded into spaces. This file only shows up if the log_output or log_ttyout flags are set and sudo was run from a terminal.
  • stdout โ€” the standard output when there's no terminal, or output redirected to a pipe or file. This file only shows up if the log_output or log_stdout flags are set and standard output isn't connected to a terminal.
  • stderr โ€” the standard error when there's no terminal, or output redirected to a pipe or file ๐Ÿšจ This file only shows up if the log_output or log_stderr flags are set and standard error isn't connected to a terminal.

Every file except log gets compressed in gzip format, unless the compress_io flag has been disabled ๐Ÿ—œ๏ธ Because of buffering, you normally can't watch the I/O logs in real-time while the program is running โณ The I/O log data won't be complete until the program run by sudo has exited or been terminated by a signal. If you want data written to disk as soon as it's available, the iolog_flush flag disables buffering for that. The output portion of an I/O log can be viewed with the sudoreplay(8) utility, which can also list or search the available logs ๐Ÿ”

๐Ÿ” Big warning, bestie: user input may contain sensitive stuff like passwords (even if they aren't echoed to the screen), and those get stored in the log file unencrypted ๐Ÿ˜ญ In most cases, logging the command output with log_output or LOG_OUTPUT is all you need. When you do log input, think about turning off the log_passwords flag.

Since each session's I/O logs live in their own directory, regular log rotation tools can't be used to limit how many I/O logs you keep ๐Ÿ™ƒ The simplest way to cap it is to set the maxseq option to the maximum number of logs you want to store. Once the I/O log sequence number reaches maxseq, it resets to zero, and sudoers will truncate and reuse any existing I/O logs ๐Ÿ”„โœจ

โœจ FILES โœจ

Here's where all the important sudo stuff lives, bestie ๐Ÿ—‚๏ธ๐Ÿ’–

  • /etc/sudo.conf โ€” the sudo front-end config ๐ŸŽ€
  • /etc/sudoers โ€” the list of who can run what ๐Ÿ‘‘
  • /etc/group โ€” the local groups file ๐Ÿซถ
  • /etc/netgroup โ€” the list of network groups ๐ŸŒธ
  • /var/log/sudo-io โ€” the I/O log files ๐Ÿ“œ
  • /run/sudo/ts โ€” the directory holding time stamps for the sudoers security policy โฐ
  • /var/lib/sudo/lectured โ€” the directory holding lecture status files for the sudoers security policy ๐Ÿ™ˆ
  • /etc/environment โ€” the starting environment for -i mode on AIX and Linux systems ๐Ÿงš

โœจ EXAMPLES โœจ

Okay so, here are some example sudoers file entries. Admittedly, some of these are a bit contrived ๐Ÿ’€ but they're iconic for showing how everything fits together. First, we let a few environment variables pass through and then define our aliases:

# Run X applications through sudo; HOME is used to find the
# .Xauthority file.  Other programs use HOME to locate configuration
# files and this may lead to privilege escalation!
Defaults env_keep += "DISPLAY HOME"

# User alias specification
User_Alias      FULLTIMERS = millert, mikef, dowdy
User_Alias      PARTTIMERS = bostley, jwfox, crawl
User_Alias      WEBADMIN = will, wendy, wim

# Runas alias specification
Runas_Alias     OP = root, operator
Runas_Alias     DB = oracle, sybase
Runas_Alias     ADMINGRP = adm, oper

# Host alias specification
Host_Alias      SPARC = bigtime, eclipse, moet, anchor :\
                SGI = grolsch, dandelion, black :\
                ALPHA = widget, thalamus, foobar :\
                HPPA = boa, nag, python
Host_Alias      CUNETS = 128.138.0.0/255.255.0.0
Host_Alias      CSNETS = 128.138.243.0, 128.138.204.0/24, 128.138.242.0
Host_Alias      SERVERS = primary, mail, www, ns
Host_Alias      CDROM = orion, perseus, hercules

# Cmnd alias specification
Cmnd_Alias      DUMPS = /usr/bin/mt, /usr/sbin/dump, /usr/sbin/rdump,\
                        /usr/sbin/restore, /usr/sbin/rrestore,\
                        sha224:0GomF8mNN3wlDt1HD9XldjJ3SNgpFdbjO1+NsQ== \
                        /home/operator/bin/start_backups
Cmnd_Alias      KILL = /usr/bin/kill
Cmnd_Alias      PRINTING = /usr/sbin/lpc, /usr/bin/lprm
Cmnd_Alias      SHUTDOWN = /usr/sbin/shutdown
Cmnd_Alias      HALT = /usr/sbin/halt
Cmnd_Alias      REBOOT = /usr/sbin/reboot
Cmnd_Alias      SHELLS = /usr/bin/sh, /usr/bin/csh, /usr/bin/ksh,\
                         /usr/local/bin/tcsh, /usr/bin/rsh,\
                         /usr/local/bin/zsh
Cmnd_Alias      SU = /usr/bin/su
Cmnd_Alias      PAGERS = /usr/bin/more, /usr/bin/pg, /usr/bin/less

Next up, we override some of the compiled-in default values ๐ŸŽ€ We want sudo to log via syslog(3) using the auth facility in all cases, and for commands to run with the target user's home directory as the working directory. We don't want to subject the full time staff to the sudo lecture ๐Ÿ™ˆ User millert doesn't need to provide a password, and we don't want to reset the LOGNAME or USER environment variables when running commands as root. Also, on the machines in the SERVERS Host_Alias, we keep an additional local log file and make sure we log the year in each log line, since those entries will be kept around for several years ๐Ÿ“š Lastly, we disable shell escapes for the commands in the PAGERS Cmnd_Alias (/usr/bin/more, /usr/bin/pg and /usr/bin/less). This will not effectively constrain users with sudo ALL privileges, so keep that in mind ๐Ÿ‘€

# Override built-in defaults
Defaults                syslog=auth,runcwd=~
Defaults>root           !set_logname
Defaults:FULLTIMERS     !lecture
Defaults:millert        !authenticate
Defaults@SERVERS        log_year, logfile=/var/log/sudo.log
Defaults!PAGERS         noexec

The User specification is the part that actually decides who may run what, so this is the main character moment ๐Ÿ‘‘

root            ALL = (ALL) ALL
%wheel          ALL = (ALL) ALL

We let root and any user in group wheel run any command on any host as any user. Iconic ๐Ÿ’…

FULLTIMERS      ALL = NOPASSWD: ALL

Full time sysadmins (millert, mikef, and dowdy) may run any command on any host without authenticating themselves ๐Ÿ”ฅ

PARTTIMERS      ALL = ALL

Part time sysadmins (bostley, jwfox, and crawl) may run any command on any host, but they have to authenticate themselves first, since the entry lacks the NOPASSWD tag ๐Ÿซถ

jack            CSNETS = ALL

The user jack may run any command on the machines in the CSNETS alias (the networks 128.138.243.0, 128.138.204.0, and 128.138.242.0). Of those networks, only 128.138.204.0 has an explicit netmask (in CIDR notation) indicating it's a class C network. For the other networks in CSNETS, the local machine's netmask will be used during matching ๐Ÿง

lisa            CUNETS = ALL

The user lisa may run any command on any host in the CUNETS alias (the class B network 128.138.0.0) ๐Ÿฆ‹

operator        ALL = DUMPS, KILL, SHUTDOWN, HALT, REBOOT, PRINTING,\
                sudoedit /etc/printcap, /usr/oper/bin/

The operator user may run commands limited to simple maintenance ๐Ÿ› ๏ธ Here, those are commands related to backups, killing processes, the printing system, shutting down the system, and any commands in the directory /usr/oper/bin/. One command in the DUMPS Cmnd_Alias includes a sha224 digest, /home/operator/bin/start_backups. This is because the directory containing the script is writable by the operator user. If the script is modified (resulting in a digest mismatch), it will no longer be possible to run it via sudo ๐Ÿšซ

joe             ALL = /usr/bin/su operator

The user joe may only su(1) to operator ๐Ÿ’–

pete            HPPA = /usr/bin/passwd [A-Za-z]*, !/usr/bin/passwd *root*

The user pete is allowed to change anyone's password except for root on the HPPA machines. Because command line arguments are matched as a single, concatenated string, the โ€˜*โ€™ wildcard will match multiple words. This example assumes that passwd(1) does not take multiple user names on the command line. On systems with GNU getopt(3), options to passwd(1) may be specified after the user argument. As a result, this rule will also allow:

    passwd username --expire

which may not be desirable ๐Ÿ˜ฌ

%opers          ALL = (: ADMINGRP) /usr/sbin/

Users in the opers group may run commands in /usr/sbin/ as themselves, with any group in the ADMINGRP Runas_Alias (the adm and oper groups) ๐ŸŒˆ

bob             SPARC = (OP) ALL : SGI = (OP) ALL

The user bob may run anything on the SPARC and SGI machines as any user listed in the OP Runas_Alias (root and operator.) โœจ

jim             +biglab = ALL

The user jim may run any command on machines in the biglab netgroup. sudo knows that โ€œbiglabโ€ is a netgroup because of the โ€˜+โ€™ prefix ๐Ÿงš

+secretaries    ALL = PRINTING, /usr/bin/adduser, /usr/bin/rmuser

Users in the secretaries netgroup need to help manage the printers, as well as add and remove users, so they're allowed to run those commands on all machines ๐Ÿ’…

fred            ALL = (DB) NOPASSWD: ALL

The user fred can run commands as any user in the DB Runas_Alias (oracle or sybase) without giving a password ๐Ÿ“

john            ALPHA = /usr/bin/su [!-]*, !/usr/bin/su *root*

On the ALPHA machines, user john may su to anyone except root, but he isn't allowed to specify any options to the su(1) command ๐Ÿ™…

jen             ALL, !SERVERS = ALL

The user jen may run any command on any machine except for those in the SERVERS Host_Alias (primary, mail, www, and ns) ๐Ÿ’œ

jill            SERVERS = /usr/bin/, !SU, !SHELLS

For any machine in the SERVERS Host_Alias, jill may run any commands in the directory /usr/bin/ except for those commands belonging to the SU and SHELLS Cmnd_Aliases. While not specifically mentioned in the rule, the commands in the PAGERS Cmnd_Alias all reside in /usr/bin and have the noexec option set ๐Ÿ”’

steve           CSNETS = (operator) /usr/local/op_commands/

The user steve may run any command in the directory /usr/local/op_commands/, but only as user operator ๐ŸŒธ

matt            valkyrie = KILL

On his personal workstation, valkyrie, matt needs to be able to kill hung processes ๐Ÿ’€

WEBADMIN        www = (www) ALL, (root) /usr/bin/su www

On the host www, any user in the WEBADMIN User_Alias (will, wendy, and wim) may run any command as user www (which owns the web pages), or simply su(1) to www ๐Ÿ•ธ๏ธ

ALL             CDROM = NOPASSWD: /sbin/umount /CDROM,\
                /sbin/mount -o nosuid\,nodev /dev/cd0a /CDROM

Any user may mount or unmount a CD-ROM on the machines in the CDROM Host_Alias (orion, perseus, hercules) without entering a password. This is a bit tedious to type, so it's a prime candidate for encapsulating in a shell script ๐Ÿš

โœจ SECURITY NOTES โœจ

Real talk, bestie, this section is important ๐Ÿšจ Read it before you get creative with your rules ๐Ÿซถ

Limitations of the โ€˜!โ€™ operator

It's generally not effective to โ€œsubtractโ€ commands from ALL using the โ€˜!โ€™ operator. A user can trivially get around this by copying the desired command to a different name and then executing that. For example:

bill    ALL = ALL, !SU, !SHELLS

Doesn't really stop bill from running the commands listed in SU or SHELLS, since he can simply copy those commands to a different name, or use a shell escape from an editor or other program ๐Ÿ™ƒ Therefore, these kinds of restrictions should be considered advisory at best (and reinforced by policy).

In general, if a user has sudo ALL, there's nothing to prevent them from creating their own program that gives them a root shell (or making their own copy of a shell), regardless of any โ€˜!โ€™ elements in the user specification ๐Ÿ˜ญ

Security implications of fast_glob

If the fast_glob option is in use, it's not possible to reliably negate commands where the path name includes globbing (aka wildcard) characters. This is because the C library's fnmatch(3) function cannot resolve relative paths. While this is typically only an inconvenience for rules that grant privileges, it can result in a security issue for rules that subtract or revoke privileges ๐Ÿšจ

For example, given the following sudoers file entry:

john    ALL = /usr/bin/passwd [a-zA-Z0-9]*, /usr/bin/chsh [a-zA-Z0-9]*,\
              /usr/bin/chfn [a-zA-Z0-9]*, !/usr/bin/* root

User john can still run โ€˜/usr/bin/passwd rootโ€™ if fast_glob is enabled by changing to /usr/bin and running โ€˜./passwd rootโ€™ instead ๐Ÿ˜ฑ

Another potential issue is that when sudo executes the command, it must use the command or path specified by the user instead of a path listed in the sudoers file. This may lead to a time of check versus time of use race condition โฑ๏ธ

Wildcards in command arguments

Command line arguments are matched as a single, concatenated string. This means a wildcard character such as โ€˜?โ€™ or โ€˜*โ€™ will match across word boundaries, which may be unexpected ๐Ÿ‘€ For example, while a sudoers entry like:

Right, so the example that looks harmless is actually a trap ๐Ÿ™ˆ. %operator ALL = /bin/cat /var/log/messages* lets operator run:

$ sudo cat /var/log/messages.1

but it also lets them run:

$ sudo cat /var/log/messages /etc/shadow

which is definitely not what you meant ๐Ÿ’€. A safer move is a regular expression for the command line arguments. You can rewrite the example like this:

%operator ALL = /bin/cat ^/var/log/messages[^[:space:]]*$

This regex only matches a single file whose name starts with /var/log/messages and has no white space in it โœจ. Honestly, for anything non-trivial, it's better to do the argument processing outside the sudoers file, in a proper scripting language ๐Ÿซถ.

Regular expressions in command names

Using a regex to match a command name comes with the same security baggage as the fast_glob option ๐Ÿ˜ฌ:

  • It is not possible to reliably negate commands when the path name is a regular expression.
  • When sudo executes the command, it must use the command or path specified by the user instead of a path listed in the sudoers file. This may lead to a time of check versus time of use race condition.

These issues do not apply to rules where only the command line options are matched with a regular expression ๐Ÿ’–.

Preventing shell escapes

Once sudo runs a program, that program can do literally whatever it wants, including running other programs ๐Ÿ™ˆ. This is a real security problem, because it's super common for a program to allow shell escapes, which lets a user dodge sudo's access control and logging. Usual suspects include shells (obviously ๐Ÿ’…), editors, paginators, mail, and terminal programs.

There are four basic approaches to this problem:

  • restrict โ€” Keep users away from commands that let them run arbitrary commands ๐Ÿšซ. Many editors have a restricted mode where shell escapes are disabled, though sudoedit is a better way to run editors via sudo. There are just so many programs with shell escapes that restricting users to the ones that don't is often unworkable ๐Ÿ˜ญ.
  • intercept โ€” On most systems, sudo's intercept functionality can transparently catch an attempt to run a new command, allow or deny it based on sudoers rules, and log the result ๐Ÿฆ‹. For example, this can restrict the commands run from within a privileged shell or editor. Heads up though: not all programs behave correctly when intercept is enabled.

There are two underlying mechanisms that can implement intercept mode: dso and trace. The intercept_type setting lets you pick between them ๐Ÿงš.

The first one, dso, overrides the standard C library functions used to execute a command. It does this by setting an environment variable (usually LD_PRELOAD) to the path of a dynamic shared object (a shared library) containing custom versions of the execve(2), execl(3), execle(3), execlp(3), execv(3), execvp(3), execvpe(3), and system(3) library functions. Those custom versions connect back to sudo for a policy decision. Note that this only applies to dynamically-linked executables ๐Ÿ™ƒ. You can't intercept commands for statically-linked executables or executables that run under binary emulation this way. Most dynamic loaders ignore LD_PRELOAD (or the equivalent) when running set-user-ID and set-group-ID programs, so sudoers will not permit those programs to run in intercept mode by default. The dso mechanism is incompatible with sudo's SELinux RBAC support (but see below). SELinux disables LD_PRELOAD by default and interferes with the file descriptor inheritance sudo relies on ๐Ÿ’€.

The second one, trace, is available on Linux systems that support seccomp(2) filtering. Instead of pre-loading a dynamic shared object, it uses ptrace(2) and seccomp(2) to intercept the execve(2) system call โœจ. Both static and dynamic executables are supported, and it's compatible with sudo's SELinux RBAC mode. Functions that use the execveat(2) system call, such as fexecve(3), are not currently intercepted. Programs that rely on ptrace(2) themselves, like debuggers and system call tracers (such as strace(1) and truss(1)), won't be able to function if intercept is enabled in trace mode. The same restriction applies to the log_subcmds sudoers option.

The intercept feature is known to work on Solaris, *BSD, Linux, macOS, HP-UX 11.x and AIX 5.3 and above ๐ŸŒˆ. It should be supported on most operating systems that support the LD_PRELOAD environment variable or an equivalent. It is not possible to intercept shell built-in commands, or to restrict the ability to read or write sensitive files from within a shell.

To turn on intercept mode for a specific command, use the INTERCEPT tag, as documented in the User Specification section above. Here is that example again:

chuck   research = INTERCEPT: ALL

This lets user chuck run any command on the machine โ€œresearchโ€ in intercept mode ๐ŸŽ€. Any commands run via shell escapes will be validated and logged by sudo. If you're not sure whether your system can support intercept, just try it and check whether external commands run via a shell get logged when intercept is enabled ๐Ÿ”.

There is an inherent race condition between when a command is checked against sudoers rules and when it is actually executed. If a user is allowed to run arbitrary commands, they may be able to change the execve(2) arguments in the program after the sudoers policy check has completed but before the new command is executed. Starting with version 1.9.12, the trace method will verify that the command and its arguments have not changed after execve(2) has completed but before execution of the new program has had a chance to run. This is not the case with the dso method. See the description of the intercept_verify setting for more information โš ๏ธ.
  • log โ€” There are two separate but related ways to log additional commands ๐Ÿ“. The first is to enable I/O logging with the log_output flag. This logs the command's output, but it won't create an event log entry when the additional command is run. The second is to enable the log_subcmds flag in sudoers, which creates an event log entry every time a new command runs ๐Ÿซถ. If I/O logging is also enabled, the log entry includes a time offset into the I/O log showing when the command ran. You can pass that offset to the sudoreplay(8) utility to replay the I/O log at the exact moment the command was run ๐ŸŽฌ. The log_subcmds flag uses the same mechanism as intercept (see above), so it has the same limitations.
  • noexec โ€” sudo's noexec functionality can stop a program run by sudo from executing any other programs ๐Ÿ›‘. On most systems it uses the same LD_PRELOAD mechanism as intercept (see above), so the same caveats apply. noexec can block execution of commands run via the execve(2), execl(3), execle(3), execlp(3), exect(3), execv(3), execveat(3), execvP(3), execvp(3), execvpe(3), fexecve(3), popen(3), posix_spawn(3), posix_spawnp(3), system(3), and wordexp(3) functions. On Linux, a seccomp(2) filter is used to implement noexec. On Solaris 10 and higher, noexec uses Solaris privileges instead of the LD_PRELOAD environment variable ๐ŸŒธ.

To enable noexec for a command, use the NOEXEC tag, as documented in the User Specification section above. Here is that example again:

aaron   shanty = NOEXEC: /usr/bin/more, /usr/bin/vi

This lets user aaron run /usr/bin/more and /usr/bin/vi with noexec enabled. That stops those two commands from executing other commands (like a shell) ๐Ÿ™…โ€โ™€๏ธ. If you're unsure whether your system supports noexec, just try it and check whether shell escapes work when noexec is enabled.

Restricting shell escapes is not a panacea, babe ๐Ÿ˜ญ. Programs running as root can still do plenty of hazardous things, like changing or overwriting files, that could lead to unintended privilege escalation. In the specific case of an editor, a much safer approach is to give the user permission to run sudoedit (see below) ๐Ÿ’….

Secure editing

The sudoers plugin includes sudoedit support, which lets users securely edit files with the editor of their choice ๐Ÿ’–. Since sudoedit is a built-in command, it must be specified in the sudoers file without a leading path. It can still take command line arguments just like a normal command. Wildcards used in sudoedit command line arguments are expected to be path names, so a forward slash (โ€˜/โ€™) will not be matched by a wildcard ๐Ÿง.

Unlike other sudo commands, the editor runs with the permissions of the invoking user, and with the environment unmodified ๐ŸŒˆ. You can find more info in the description of the -e option in sudo(8).

For example, to let user operator edit the โ€œmessage of the dayโ€ file on any machine:

operator ALL = sudoedit /etc/motd

Then the operator user runs sudoedit like this:

$ sudoedit /etc/motd

The editor runs as the operator user, not root, on a temporary copy of /etc/motd. After the file has been edited, /etc/motd gets updated with the contents of that temporary copy ๐ŸŽ€โœจ.

Users should never be granted sudoedit permission to edit a file that lives in a directory they can write to, whether directly or through a wildcard. ๐Ÿ˜ญ If they can write to that directory, they can swap the real file for a link pointing at some other file, and then they can edit whatever they want. ๐Ÿ’€ To stop that, starting with version 1.8.16, sudoedit won't follow symbolic links in writable directories and will refuse to edit a file sitting in one, unless the sudoedit_checkdir option has been disabled or the invoking user is root. โœจ Also, in version 1.8.15 and higher, sudoedit won't open a symbolic link unless the sudoedit_follow option is enabled or the sudoedit command has the FOLLOW tag in the sudoers file. ๐Ÿซถ

๐Ÿ•ฐ๏ธ Time stamp file checks

sudoers checks who owns its time stamp directory (/run/sudo/ts by default). If that directory isn't owned by root, or if anyone other than root can write to it, sudoers ignores what's inside. ๐Ÿ™…โ€โ™€๏ธ Older versions of sudo stored time stamp files in /tmp, and that's no longer recommended, because on systems that let unprivileged users change the ownership of files they create, someone could make the time stamp themselves. ๐Ÿ™ˆ

The time stamp directory should get cleared at reboot, but not every system has a /run or /var/run directory. ๐Ÿฅบ So to avoid problems, sudoers ignores time stamp files from before the machine booted, on systems where the boot time is available. โฐ

Some systems with graphical desktop environments let unprivileged users change the system clock. ๐Ÿ‘€ sudoers relies on the system clock to validate time stamps, so on those systems a user could potentially keep running sudo for longer than timestamp_timeout just by setting the clock back. Rude! ๐Ÿ˜ค To shut that down, sudoers uses a monotonic clock (one that never moves backwards) for its time stamps when the system supports it. ๐Ÿ’…

sudoers won't honor time stamps set way in the future. Any time stamp dated later than current_time + 2 * TIMEOUT gets ignored, and sudoers logs it and complains. ๐Ÿšจ

If the timestamp_type option is set to โ€œttyโ€, the time stamp record includes the device number of the terminal the user authenticated with. ๐Ÿ“Ÿ That gives you per-terminal granularity, but time stamp records can still outlive the user's session. ๐Ÿ‘ป

Unless timestamp_type is set to โ€œglobalโ€, the time stamp record also includes the session ID of the process that last authenticated. ๐Ÿ” That stops processes in different terminal sessions from sharing one time stamp record. On systems where a process's start time can be queried, the session leader's start time gets recorded in the time stamp record too. If there's no terminal, or timestamp_type is set to โ€œppidโ€, the parent process's start time is used instead. ๐Ÿงš In most cases, this stops a time stamp record from being reused without a password when you log out and back in. โœจ


๐Ÿ› DEBUGGING ๐Ÿ›

Versions 1.8.4 and higher of the sudoers plugin come with a flexible debugging framework that helps you track down what the plugin is doing internally when something goes wrong. ๐Ÿ” You configure it in the sudo.conf(5) file. ๐ŸŽ€

The sudoers plugin uses the same debug flag format as the sudo front-end: subsystem@priority. ๐Ÿ’œ

The priorities sudoers uses, from most severe to least, are: crit, err, warn, notice, diag, info, trace, and debug. Each priority also includes every priority above it. ๐ŸŒˆ For example, notice covers debug messages logged at notice and higher. ๐Ÿ“

Here are the subsystems the sudoers plugin uses:

  • alias โ€” User_Alias, Runas_Alias, Host_Alias and Cmnd_Alias processing ๐Ÿท๏ธ
  • all โ€” matches every subsystem ๐ŸŒ
  • audit โ€” BSM and Linux audit code ๐Ÿงพ
  • auth โ€” user authentication ๐Ÿ”‘
  • defaults โ€” sudoers file Defaults settings โš™๏ธ
  • env โ€” environment handling ๐ŸŒฟ
  • ldap โ€” LDAP-based sudoers ๐Ÿ“‚
  • logging โ€” logging support ๐Ÿชต
  • match โ€” matching of users, groups, hosts, and netgroups in the sudoers file ๐ŸŽฏ
  • netif โ€” network interface handling ๐ŸŒ
  • nss โ€” network service switch handling in sudoers ๐Ÿ”€
  • parser โ€” sudoers file parsing ๐Ÿงฉ
  • perms โ€” permission setting ๐Ÿ”’
  • plugin โ€” The equivalent of main for the plugin. ๐Ÿ‘‘
  • pty โ€” pseudo-terminal related code ๐Ÿ–ฅ๏ธ
  • rbtree โ€” redblack tree internals ๐ŸŒณ
  • sssd โ€” SSSD-based sudoers ๐Ÿชช
  • util โ€” utility functions ๐Ÿ› ๏ธ

Here's an example:

Debug sudoers.so /var/log/sudoers_debug match@info,nss@info

For more info, check out the sudo.conf(5) manual. ๐Ÿ“–๐Ÿ’–


๐Ÿ”— SEE ALSO ๐Ÿ”—

ssh(1), su(1), fnmatch(3), glob(3), mktemp(3), strftime(3), sudo.conf(5), sudo_logsrv.proto(5), sudo_plugin(5), sudoers.ldap(5), sudoers_timestamp(5), sudo(8), sudo_logsrvd(8), visudo(8) ๐Ÿฆ‹


๐Ÿ‘ฉโ€๐Ÿ’ป AUTHORS ๐Ÿ‘ฉโ€๐Ÿ’ป

Loads of people have worked on sudo over the years. ๐Ÿฅฐ This version is mainly code written by:

Todd C. Miller

For the full list of everyone who's contributed, check out the CONTRIBUTORS.md file in the sudo distribution, at https://www.sudo.ws/about/contributors/. ๐Ÿซถ


โš ๏ธ CAVEATS โš ๏ธ

The sudoers file should ALWAYS be edited with the visudo utility, because it locks the file and checks for syntax errors. ๐Ÿ˜ฑ If sudoers has syntax errors, sudo may refuse to run, and that's a serious problem if sudo is your only way to get superuser privileges. ๐Ÿ’€

Recent versions of sudoers try to recover from a syntax error by ignoring the rest of the line where the error happened. ๐Ÿฉน Older versions of sudo won't run at all if sudoers has a syntax error. ๐Ÿ™ƒ

When you use netgroups of machines (as opposed to users), if you store fully qualified host names in the netgroup (which is usually the case), then either the machine's host name has to be fully qualified as returned by the hostname command, or you need to use the fqdn option in sudoers. ๐ŸŒ


๐Ÿž BUGS ๐Ÿž

If you think you've found a bug in sudoers, you can file a bug report in the sudo bug database at https://bugzilla.sudo.ws/, or open an issue at https://github.com/sudo-project/sudo/issues. ๐Ÿ› If you'd rather use email, you can send messages to the sudo-workers mailing list, https://www.sudo.ws/mailman/listinfo/sudo-workers (public), or to <sudo@sudo.ws> (private). ๐Ÿ“ฌ

Please, please do NOT report security vulnerabilities through public GitHub issues, Bugzilla, or mailing lists. ๐Ÿ™…โ€โ™€๏ธ๐Ÿšซ Instead, email them to <Todd.Miller@sudo.ws>. You can encrypt your message with PGP if you want, using the key found at https://www.sudo.ws/dist/PGPKEYS. ๐Ÿ”

๐Ÿ’ฌ SUPPORT ๐Ÿ’ฌ

You can get limited free support through the sudo-users mailing list. ๐ŸŒธ Go to https://www.sudo.ws/mailman/listinfo/sudo-users to subscribe or to search the archives. ๐Ÿ”


๐Ÿ“œ DISCLAIMER ๐Ÿ“œ

sudo is provided โ€œAS ISโ€, and any express or implied warranties are disclaimed, including, but not limited to, the implied warranties of merchantability and fitness for a particular purpose. ๐Ÿ™ For complete details, see the LICENSE.md file distributed with sudo, or https://www.sudo.ws/about/license/. โญ


And that's the whole page, bestie! ๐ŸŽ€ Sudo 1.9.17p2, from June 7, 2025, is the version we've been reading about, so go forth and sudo responsibly. ๐Ÿ’…โœจ๐Ÿซถ

๐Ÿ“ฆ where this page comes from