Sudo
GitHub Blog Toggle Dark/Light/Auto mode Toggle Dark/Light/Auto mode Toggle Dark/Light/Auto mode Back to homepage

cvtsudoers: merging multiple sudoers files into one

We learned in my previous sudo blog that cvtsudoers is not just for LDAP. Version 1.9.9 of sudo extends the querying possibilities of cvtsudoers further and adds a brand new feature: merging multiple sudoers files into one. Both are especially useful when you have complex configurations. Querying lets you to better understand what the various rules allow in your sudoers file. Merging helps you to combine multiple configurations into one, so you do not have to maintain a separate sudoers file on each of your hosts.

Before you begin

The functionality described in this blog became available in sudo version 1.9.9. There is a good chance that it is not yet available as part of the operating system of your choice. Merging had a bug in the initial release, so you should either wait for 1.9.10 or compile it yourself from git sources. Sources are available at https://github.com/sudo-project/sudo/. Binaries will be available at https://www.sudo.ws/getting/packages/

Sample sudoers file

This is the system sudoers file I use as an example. It is not a production policy, just some made-up rules, but it is sufficient to show how the new features of sudo 1.9.9 work. I removed all the comments and empty lines to make it shorter.

# cat sudoers | grep -v ^# | grep -v '^$'
Defaults always_set_home
Defaults secure_path="/usr/sbin:/usr/bin:/sbin:/bin:/usr/local/bin:/usr/local/sbin"
Defaults env_reset
Defaults env_keep = "LANG LC_ADDRESS LC_CTYPE LC_COLLATE LC_IDENTIFICATION LC_MEASUREMENT LC_MESSAGES LC_MONETARY LC_NAME LC_NUMERIC LC_PAPER LC_TELEPHONE LC_ATIME LC_ALL LANGUAGE LINGUAS XDG_SESSION_COOKIE"
Defaults !insults
root ALL=(ALL) ALL
%wheel ALL=(ALL) ALL
Defaults log_subcmds
Defaults log_format=json
Host_Alias WEBSERVERS = www1, www2, www3
User_Alias ADMINS = smith, johnson, williams
Cmnd_Alias REBOOT = /sbin/halt, /sbin/reboot, /sbin/poweroff
ADMINS WEBSERVERS = REBOOT
User_Alias BLADMINS = bsmith, bjohnson, bwilliams
Cmnd_Alias BLABOOT = /bin/ls
BLADMINS WEBSERVERS = BLABOOT
@includedir /etc/sudoers.d

I also prepared a second sudoers file for merging that is identical except that secure_path has an additional directory:

Defaults secure_path="/usr/sbin:/usr/bin:/sbin:/bin:/usr/local/bin:/usr/local/sbin:/opt/syslog-ng/sbin"

We will use this in the second part of the blog.

Checking who has access to a command

Starting with sudo 1.9.9 you can query who has access to a given command using the filter function of cvtsudoers. You have to use the cmd key to specify the command you are looking for.

# cvtsudoers -f sudoers -m cmd=/sbin/reboot /etc/sudoers 
Defaults always_set_home
Defaults\
  secure_path=/usr/sbin\:/usr/bin\:/sbin\:/bin\:/usr/local/bin\:/usr/local/sbin
Defaults env_reset
Defaults env_keep="LANG LC_ADDRESS LC_CTYPE LC_COLLATE LC_IDENTIFICATION\
  LC_MEASUREMENT LC_MESSAGES LC_MONETARY LC_NAME LC_NUMERIC LC_PAPER\
  LC_TELEPHONE LC_ATIME LC_ALL LANGUAGE LINGUAS XDG_SESSION_COOKIE"
Defaults !insults
Defaults log_subcmds
Defaults log_format=json

User_Alias ADMINS = smith, johnson, williams
Cmnd_Alias REBOOT = /sbin/halt, /sbin/reboot, /sbin/poweroff
Host_Alias WEBSERVERS = www1, www2, www3

root ALL = (ALL) ALL

%wheel ALL = (ALL) ALL

ADMINS WEBSERVERS = REBOOT

As you can see, most of the original sudoers file is here, only lines related to the ls command are missing. You may wonder why this shows more than just the ADMINS rule, which includes explicit permission to run reboot. The answer is that root and members of the wheel group can run everything, and that includes reboot as well.

If you find the output too verbose and think that log_format and the other Defaults are not relevant, you can easily exclude those lines from the output. Just use the -s defaults command line option.

Merging multiple sudoers files into one

When you have a second host to manage you will most likely simply customize the sudoers file again. But once you have to revoke sudo access for a user on multiple hosts you will find that maintaining multiple sudoers file can be difficult. It is better to have a single sudoers file that covers all hosts. Creating it might be difficult, but once it is ready, making modifications on all hosts becomes a lot easier. A new feature of cvtsudoers makes the initial conversion easier as it can merge multiple sudoers file into a single output. You can also add host names when you list input files and in this case, host specific differences are also considered.

# cvtsudoers -f sudoers -o sudoers.merged /root/sudoers /root/sudoers2
cvtsudoers: /root/sudoers2:89:12: removing duplicate alias ADMINS
cvtsudoers: /root/sudoers2:94:12: removing duplicate alias BLABOOT
cvtsudoers: /root/sudoers2:93:12: removing duplicate alias BLADMINS
cvtsudoers: /root/sudoers2:90:12: removing duplicate alias REBOOT
cvtsudoers: /root/sudoers2:88:12: removing duplicate alias WEBSERVERS
cvtsudoers: /root/sudoers:39:83: conflicting Defaults entry "secure_path" host-specific in /root/sudoers2:39:103
cvtsudoers: /root/sudoers:39:83: removing Defaults "secure_path" overridden by subsequent entries
cvtsudoers: /root/sudoers2:37:25: unable to make Defaults "always_set_home" host-specific
cvtsudoers: /root/sudoers2:39:103: unable to make Defaults "secure_path" host-specific
cvtsudoers: /root/sudoers2:40:19: unable to make Defaults "env_reset" host-specific
cvtsudoers: /root/sudoers2:43:207: unable to make Defaults "env_keep" host-specific
cvtsudoers: /root/sudoers2:50:11: unable to make Defaults "insults" host-specific
cvtsudoers: /root/sudoers2:85:21: unable to make Defaults "log_subcmds" host-specific
cvtsudoers: /root/sudoers2:86:21: unable to make Defaults "log_format" host-specific
cvtsudoers: /root/sudoers:96:30: removing userspec overridden by subsequent entries
cvtsudoers: /root/sudoers:92:27: removing userspec overridden by subsequent entries
cvtsudoers: /root/sudoers:82:21: removing userspec overridden by subsequent entries
cvtsudoers: /root/sudoers:79:19: removing userspec overridden by subsequent entries
cvtsudoers: /root/sudoers2:82:21: converting host list to ALL
cvtsudoers: /root/sudoers2:79:19: converting host list to ALL

# cat sudoers.merged
Defaults always_set_home
Defaults\
    secure_path=/usr/sbin\:/usr/bin\:/sbin\:/bin\:/usr/local/bin\:/usr/local/sbin\:/opt/syslog-ng/sbin
Defaults env_reset
Defaults env_keep="LANG LC_ADDRESS LC_CTYPE LC_COLLATE LC_IDENTIFICATION\
    LC_MEASUREMENT LC_MESSAGES LC_MONETARY LC_NAME LC_NUMERIC LC_PAPER\
    LC_TELEPHONE LC_ATIME LC_ALL LANGUAGE LINGUAS XDG_SESSION_COOKIE"
Defaults !insults
Defaults log_subcmds
Defaults log_format=json

User_Alias ADMINS = smith, johnson, williams
Cmnd_Alias BLABOOT = /bin/ls
User_Alias BLADMINS = bsmith, bjohnson, bwilliams
Cmnd_Alias REBOOT = /sbin/halt, /sbin/reboot, /sbin/poweroff
Host_Alias WEBSERVERS = www1, www2, www3

root ALL = (ALL) ALL

%wheel ALL = (ALL) ALL

ADMINS WEBSERVERS = REBOOT

BLADMINS WEBSERVERS = BLABOOT

As you can see, if you do not specify host names on the command line then the last setting wins. In this case secure_path is taken from the second sudoers file. Now let’s check what happens if we supply host names on the command line:

# cvtsudoers -f sudoers -o sudoers.merged laptop:/root/sudoers other:/root/sudoers2
cvtsudoers: /root/sudoers2:89:12: removing duplicate alias ADMINS
cvtsudoers: /root/sudoers2:94:12: removing duplicate alias BLABOOT
cvtsudoers: /root/sudoers2:93:12: removing duplicate alias BLADMINS
cvtsudoers: /root/sudoers2:90:12: removing duplicate alias REBOOT
cvtsudoers: /root/sudoers2:88:12: removing duplicate alias WEBSERVERS
cvtsudoers: /root/sudoers:37:25: made Defaults "always_set_home" specific to host laptop
cvtsudoers: /root/sudoers:39:83: made Defaults "secure_path" specific to host laptop
cvtsudoers: /root/sudoers:40:19: made Defaults "env_reset" specific to host laptop
cvtsudoers: /root/sudoers:43:207: made Defaults "env_keep" specific to host laptop
cvtsudoers: /root/sudoers:50:11: made Defaults "insults" specific to host laptop
cvtsudoers: /root/sudoers:85:21: made Defaults "log_subcmds" specific to host laptop
cvtsudoers: /root/sudoers:86:21: made Defaults "log_format" specific to host laptop
cvtsudoers: /root/sudoers2:37:25: made Defaults "always_set_home" specific to host other
cvtsudoers: /root/sudoers2:39:103: made Defaults "secure_path" specific to host other
cvtsudoers: /root/sudoers2:40:19: made Defaults "env_reset" specific to host other
cvtsudoers: /root/sudoers2:43:207: made Defaults "env_keep" specific to host other
cvtsudoers: /root/sudoers2:50:11: made Defaults "insults" specific to host other
cvtsudoers: /root/sudoers2:85:21: made Defaults "log_subcmds" specific to host other
cvtsudoers: /root/sudoers2:86:21: made Defaults "log_format" specific to host other
cvtsudoers: /root/sudoers2:37:25: converting host list to ALL
cvtsudoers: /root/sudoers2:40:19: converting host list to ALL
cvtsudoers: /root/sudoers2:43:207: converting host list to ALL
cvtsudoers: /root/sudoers2:50:11: converting host list to ALL
cvtsudoers: /root/sudoers2:85:21: converting host list to ALL
cvtsudoers: /root/sudoers2:86:21: converting host list to ALL
cvtsudoers: /root/sudoers:96:30: removing userspec overridden by subsequent entries
cvtsudoers: /root/sudoers:92:27: removing userspec overridden by subsequent entries
cvtsudoers: /root/sudoers:82:21: merging userspec into /root/sudoers2:82:21
cvtsudoers: /root/sudoers:79:19: merging userspec into /root/sudoers2:79:19
cvtsudoers: /root/sudoers2:82:21: converting host list to ALL
cvtsudoers: /root/sudoers2:79:19: converting host list to ALL
# cat sudoers.merged
Defaults@laptop\
    secure_path=/usr/sbin\:/usr/bin\:/sbin\:/bin\:/usr/local/bin\:/usr/local/sbin
Defaults always_set_home
Defaults@other\
    secure_path=/usr/sbin\:/usr/bin\:/sbin\:/bin\:/usr/local/bin\:/usr/local/sbin\:/opt/syslog-ng/sbin
Defaults env_reset
Defaults env_keep="LANG LC_ADDRESS LC_CTYPE LC_COLLATE LC_IDENTIFICATION\
    LC_MEASUREMENT LC_MESSAGES LC_MONETARY LC_NAME LC_NUMERIC LC_PAPER\
    LC_TELEPHONE LC_ATIME LC_ALL LANGUAGE LINGUAS XDG_SESSION_COOKIE"
Defaults !insults
Defaults log_subcmds
Defaults log_format=json

User_Alias ADMINS = smith, johnson, williams
Cmnd_Alias BLABOOT = /bin/ls
User_Alias BLADMINS = bsmith, bjohnson, bwilliams
Cmnd_Alias REBOOT = /sbin/halt, /sbin/reboot, /sbin/poweroff
Host_Alias WEBSERVERS = www1, www2, www3

root ALL = (ALL) ALL

%wheel ALL = (ALL) ALL

ADMINS WEBSERVERS = REBOOT

BLADMINS WEBSERVERS = BLABOOT

In this case cvtsudoers makes sure that common parts are merged, but host specific parts are marked with the host name. There is a separate secure_path setting for my laptop and the host called other.

I hope you learned, just as I did, that cvtsudoers is a lot more than a sudo LDAP utility. You can query the sudoers file using the filter option and find information more quickly than is possible by just browsing the file. And you can use cvtsudoers also to merge multiple sudoers file into a single one: preparing the way to centrally manage sudo on all your hosts.

If you would like to be notified about new posts and sudo news, sign up for the sudo blog announcement mailing list.