CVTSUDOERS(1)
๐ NAME ๐
okay so this is the manual page for cvtsudoers, and its whole job is to convert between sudoers file formats ๐ฆ
โจ SYNOPSIS โจ
cvtsudoers [-ehMpV] [-b dn] [-c conf_file] [-d deftypes]
[-f output_format] [-i input_format] [-I increment]
[-l log_file] [-m filter] [-o output_file] [-O start_point]
[-P padding] [-s sections] [input_file ...]
๐ธ DESCRIPTION ๐ธ
bestie, cvtsudoers takes one or more security policies as input, either in sudoers or LDIF format, and spits out a single policy in whatever format you ask for ๐
The default input is sudoers and the default output is LDIF. It only works on a policy file that is syntactically correct, so clean up your act first ๐
If you don't give it an input_file, or you give it -, it reads the policy from standard input. You can also prefix input files with a host name and a colon (:) to make the rules specific to that host when you merge a bunch of files together โจ By default the result goes to standard output.
Here are the options ๐
-b dn,--base=dnโ this is the base DN (distinguished name) used for LDAP queries. It usually looks likeโou=SUDOers,dc=my-domain,dc=comโfor the domain my-domain.com ๐ง If you skip this flag, it grabs theSUDOERS_BASEenvironment variable instead. It only matters when you're converting to LDIF.-c conf_file,--config=conf_fileโ point it at a configuration file. If you don't, it defaults to/etc/cvtsudoers.conf๐-d deftypes,--defaults=deftypesโ only convert the Defaults entries of the types you name. You can list more than one, separated by a comma (,). The types are:allโ every Defaults entry, no exceptions ๐globalโ global Defaults entries that get applied no matter the user, runas, host, or command ๐userโ per-user Defaults entries ๐runasโ per-runas user Defaults entries ๐ฆhostโ per-host Defaults entries โจcommandโ per-command Defaults entries ๐
For more on this, check the Defaults section in sudoers(5). And if you don't use -d at all, every Defaults entry gets converted ๐
-e,--expand-aliasesโ expands the aliases ininput_file. Fyi, aliases are kept as they are by default when the output format is JSON or sudoers ๐ซถ-f output_format,--output-format=output_formatโ pick your output format. It's not picky about capitals ๐ The formats you can choose from are:CSVโ CSV (comma-separated value) files are the spreadsheet and report generator favourite ๐ Check out โCSV output formatโ for the deets.JSONโ JSON (JavaScript Object Notation) files are usually way easier for third-party apps to read than the old-school sudoers format. The values have explicit types, so there's way less guessing going on than with sudoers ๐ง See โJSON output formatโ for more.LDIFโ LDIF (LDAP Data Interchange Format) files can be imported into an LDAP server and used withsudoers.ldap(5)๐ธ
โ ๏ธ heads up, converting to LDIF has some limitations, babe: - Command, host, runas, and user-specific Defaults lines can't be translated, because the sudoers LDAP schema doesn't have an equivalent for them ๐ - Command, host, runas, and user aliases aren't supported by the sudoers LDAP schema, so they get expanded during the conversion ๐
sudoersโ the traditional sudoers format ๐ A brand new sudoers file gets rebuilt from the parsed input. Comments are NOT preserved, and any data from include files gets output inline.
--group-file=fileโ when-Mis also specified, group queries usefileinstead of the system group database ๐ซง-h,--helpโ shows a short help message on standard output and then exits ๐
Okay so picking up where we left off, bestie ๐ Per-user rules get merged and duplicates get yeeted ๐๏ธโจ
- Per-user rules are merged and duplicates are removed. If a host name is specified with the input file,
cvtsudoerswill change rules that specify a host name ofALLto the host name associated with the policy file being merged. The merging of rules is currently fairly simplistic but will be improved in a later release. ๐ธ
It's also possible to merge policy files with differing formats. Mix and match, no cap ๐ฆ
โจ The cvtsudoers.conf file โจ
Okay so you can ALSO set options in the form keyword = value inside a config file. By default that's /etc/cvtsudoers.conf ๐๐ These are the keywords it recognizes, so let's go:
defaults = deftypesโ same as the-dcommand line option ๐expand_aliases = yes | noโ same as the-ecommand line option โจgroup_file = fileโ same as the--group-filecommand line option ๐ฅinput_format = ldif | sudoersโ same as the-icommand line option ๐ฅmatch = filterโ same as the-mcommand line option ๐match_local = yes | noโ same as the-Mcommand line option ๐order_increment = incrementโ same as the-Icommand line option ๐ขorder_start = start_pointโ same as the-Ocommand line option ๐output_format = csv | json | ldif | sudoersโ same as the-fcommand line option ๐คpadding = paddingโ same as the-Pcommand line option ๐งธpasswd_file = fileโ same as the--passwd-filecommand line option ๐๏ธprune_matches = yes | noโ same as the-pcommand line option โ๏ธsudoers_base = dnโ same as the-bcommand line option ๐ณsuppress = sectionsโ same as the-scommand line option ๐
Heads up: options on the command line will override values from the configuration file. The command line always wins, periodt ๐
โจ JSON output format โจ
Okay the sudoers JSON format can contain any of the following top-level objects, and each one is its own little vibe ๐ง
Defaults
An array of objects, each containing an Options array and an optional Binding array. ๐
The Options array is made of one or more objects, each holding a โname:valueโ pair that matches a sudoers Defaults setting. Options that work on a list also get an operation entry, with a value of โlist_assignโ for =, โlist_addโ for +=, or โlist_removeโ for -=. ๐
The optional Binding array is one or more objects, each with a โname:valueโ pair and an optional negated entry. That negated entry negates any comparison done with the object. If a Binding is there, the setting only takes effect if one of these matches: command, hostname, netgroup, networkaddr, nonunixgid, nonunixgroup, usergid, usergroup, userid, username, or alias ๐
Here's an example, because examples make everything click โจ This sudoers entry:
Defaults@somehost set_home, env_keep += DISPLAY
converts to:
"Defaults": [
{
"Binding": [
{ "hostname": "somehost" }
],
"Options": [
{ "set_home": true },
{
"operation": "list_add",
"env_keep": [
"DISPLAY"
]
}
]
}
]
User_Aliases
A JSON object with one or more sudoers User_Alias entries. Each named alias has an array as its value, and that array holds one or more objects ๐ Each object has a โname:valueโ pair and an optional negated entry, which negates any comparison done with the object. The name can be one of netgroup, nonunixgid, nonunixgroup, useralias, usergid, usergroup, userid, or username. ๐
Example time! This sudoers entry:
User_Alias SYSADMIN = will, %wheel, +admin
converts to:
"User_Aliases": {
"SYSADMIN": [
{ "username": "will" },
{ "usergroup": "wheel" },
{ "netgroup": "admin" }
]
}
Runas_Aliases
A JSON object with one or more sudoers Runas_Alias entries, where each named alias has an array of one or more objects as its value ๐ธ Each object has a โname:valueโ pair and an optional negated entry, which negates any comparison done with the object. The name can be one of netgroup, nonunixgid, nonunixgroup, runasalias, usergid, usergroup, userid, or username. ๐ฆ
Example:
Runas_Alias DB = oracle, sybase : OP = root, operator
converts to:
"Runas_Aliases": {
"DB": [
{ "username": "oracle" },
{ "username": "sybase" }
],
"OP": [
{ "username": "root" },
{ "username": "operator" }
]
}
Host_Aliases
A JSON object with one or more sudoers Host_Alias entries. Each named alias maps to an array of one or more objects ๐ Each object has a โname:valueโ pair and an optional negated entry, which negates any comparison done with the object. The name can be one of hostalias, hostname, netgroup, or networkaddr. โจ
Example! These sudoers entries:
Host_Alias DORMNET = 128.138.243.0, 128.138.204.0/24
Host_Alias SERVERS = boulder, refuge
convert to:
"Host_Aliases": {
"DORMNET": [
{ "networkaddr": "128.138.243.0" },
{ "networkaddr": "128.138.204.0/24" }
],
"SERVERS": [
{ "hostname": "boulder" },
{ "hostname": "refuge" }
]
}
Cmnd_Aliases
A JSON object with one or more sudoers Cmnd_Alias entries, where each named alias has an array of one or more objects as its value ๐
Each object has a โname:valueโ pair and an optional negated entry, which negates any comparison done with the object. The name can be either another cmndalias or a command. ๐ฅ
These sudoers entries:
Cmnd_Alias SHELLS = /bin/bash, /bin/csh, /bin/sh, /bin/zsh
Cmnd_Alias VIPW = /usr/bin/chpass, /usr/bin/chfn, /usr/bin/chsh, \
/usr/bin/passwd, /usr/sbin/vigr, /usr/sbin/vipw
convert to:
"Cmnd_Aliases": {
"SHELLS": [
{ "command": "/bin/bash" },
{ "command": "/bin/csh" },
{ "command": "/bin/sh" },
{ "command": "/bin/zsh" }
],
"VIPW": [
{ "command": "/usr/bin/chpass" },
{ "command": "/usr/bin/chfn" },
{ "command": "/usr/bin/chsh" },
{ "command": "/usr/bin/passwd" },
{ "command": "/usr/sbin/vigr" },
{ "command": "/usr/sbin/vipw" }
]
}
User_Specs
A JSON array with one or more objects, each representing a sudoers User_Spec ๐ง Each object in the User_Specs array should contain a User_List array, a Host_List array, and a Cmnd_Specs array. ๐
A User_List is one or more objects. Each object has a โname:valueโ pair and an optional negated entry, which negates any comparison done with the object. The name can be one of netgroup, nonunixgid, nonunixgroup, useralias, usergid, usergroup, userid, or username. If username is set to the special value ALL, it matches any user. ๐ซถ
A Host_List is one or more objects. Each object has a โname:valueโ pair and an optional negated entry, which negates any comparison done with the object. The name can be one of hostalias, hostname, netgroup, or networkaddr. If hostname is set to the special value ALL, it matches any host. ๐
The Cmnd_Specs array is one or more JSON objects describing a command that may be run. Each Cmnd_Specs is made up of a Commands array, an optional runasusers array, an optional runasgroups array, and an optional Options array. โญ
The Commands array is one or more objects containing โname:valueโ pair elements. These are the names and values that are supported:
commandโ a string with the command to run. The special valueALLmatches any command. ๐ปnegatedโ a boolean value that, if true, negates any comparison done with the object. ๐sha224โ one or more SHA224 digests for the command, in string form. Multiple digests of the same type are stored as an array. ๐sha256โ one or more SHA256 digests for the command, in string form. Multiple digests of the same type are stored as an array. ๐sha384โ one or more SHA384 digests for the command, in string form. Multiple digests of the same type are stored as an array. ๐sha512โ one or more SHA512 digests for the command, in string form. Multiple digests of the same type are stored as an array. ๐
The runasusers array is made of objects describing the users the command may be run as. Each object has a โname:valueโ pair and an optional negated entry, which negates any comparison done with the object. The name can be one of netgroup, nonunixgid, nonunixgroup, runasalias, usergid, usergroup, userid, or username. If username is set to the special value ALL, it matches any user. If username is set to the empty string โโ, it matches the invoking user. ๐ง
The runasgroups array is made of objects describing the groups the command may be run as. Each object has a โname:valueโ pair and an optional negated entry, which negates any comparison done with the object. The name can be one of runasalias, usergid, or usergroup. If usergroup is set to the special value ALL, it matches any group. ๐ธ
The Options array uses the same format as the one in the Defaults object. Any Tag_Spec entries in sudoers get converted into Options, and a user with sudo ALL privileges will automatically have the setenv option turned on, matching the implicit behavior sudoers already gives them โจ
For example, this sudoers entry:
millert ALL = (ALL : ALL) NOPASSWD: ALL, !/usr/bin/id
comes out as:
"User_Specs": [
{
"User_List": [
{ "username": "millert" }
],
"Host_List": [
{ "hostname": "ALL" }
],
"Cmnd_Specs": [
{
"runasusers": [
{ "username": "ALL" }
],
"runasgroups": [
{ "usergroup": "ALL" }
],
"Options": [
{ "authenticate": false },
{ "setenv": true }
],
"Commands": [
{ "command": "ALL" },
{
"command": "/usr/bin/id",
"negated": true
}
]
}
]
}
]
๐ CSV output format ๐
okay so CSV (comma-separated value) files are the go-to for spreadsheets and report generators ๐ For CSV output, cvtsudoers double quotes any strings that contain commas. Every literal double quote character inside a string becomes two double quotes, and this way of quoting commas plays nicely with most spreadsheet programs ๐ซถ
There are three possible sections in cvtsudoers's CSV output, and each one is separated by a blank line ๐ฆ
defaults ๐
This section holds any Defaults settings from sudoers. It starts with this heading:
defaults_type,binding,name,operator,value
Here's what each field means:
defaults_typeโ the type of Defaults setting. It's one ofdefaults,defaults_command,defaults_host,defaults_runas, ordefaults_user.bindingโ fordefaults_command,defaults_host,defaults_runas, anddefaults_user, this is the value that has to match before the setting gets applied.nameโ the name of the Defaults setting.operatorโ this decides how the value gets applied to the setting. It can be=(assignment),+=(append), or-=(remove).valueโ the setting's value. Usually it's a string, or for settings used in a boolean context, it'strueorfalse.
aliases ๐ธ
This section covers any Cmnd_Alias, Host_Alias, Runas_Alias, or User_Alias entries from sudoers. It starts with this heading:
alias_type,alias_name,members
Here's the breakdown:
alias_typeโ the type of alias. It's one ofCmnd_Alias,Host_Alias,Runas_Alias, orUser_Alias.alias_nameโ the name of the alias. It's a string that starts with an upper-case letter and is made up of upper-case letters, digits, or underscores.membersโ a comma-separated list of the members in the alias. Because of those commas,membersgets wrapped in double quotes whenever there's more than one member ๐
rules ๐
This section has the sudoers rules that grant privileges. It starts with this heading:
rule,user,host,runusers,rungroups,options,command
And the fields, bestie:
ruleโ marks a sudoers rule entry.userโ the user the rule applies to. This can also be a Unix group (with a%in front), a non-Unix group (with%:in front), a netgroup (with a+in front), or a User_Alias. If it's set to the special valueALL, it matches any user ๐hostโ the host the rule applies to. This can also be a netgroup (with a+in front) or a Host_Alias. If it's set to the special valueALL, it matches any host ๐งrunusersโ an optional comma-separated list of users (or Runas_Aliases) the command may be run as. If there's more than one member, the value gets wrapped in double quotes. If it's set toALL, it matches any user. If it's empty, the root user is assumed.rungroupsโ an optional comma-separated list of groups (or Runas_Aliases) the command may be run as. If there's more than one member, the value gets wrapped in double quotes. If it's set toALL, it matches any group. If it's empty, the runuser's group is used.optionsโ an optional list of Defaults settings to apply to the command. Any Tag_Spec entries in sudoers get converted into options.commandsโ a list of commands, with optional arguments, that the user is allowed to run. If it's set to the special valueALL, it matches any command.
For example, this sudoers entry:
millert ALL = (ALL : ALL) NOPASSWD: ALL, !/usr/bin/id
converts to:
rule,millert,ALL,ALL,ALL,"!authenticate","ALL,!/usr/bin/id"
๐ FILES ๐
/etc/cvtsudoers.confโ the default configuration for cvtsudoers ๐
๐ก EXAMPLES ๐ก
okay these are my favs, bestie ๐ฅน
Convert /etc/sudoers to LDIF (LDAP Data Interchange Format), where the ldap.conf file uses a sudoers_base of my-domain,dc=com, and store the result in sudoers.ldif:
$ cvtsudoers -b ou=SUDOers,dc=my-domain,dc=com -o sudoers.ldif \
/etc/sudoers
Convert /etc/sudoers to JSON format and store the result in sudoers.json:
$ cvtsudoers -f json -o sudoers.json /etc/sudoers
Parse /etc/sudoers and display only the rules that match user ambrose on host hastur:
$ cvtsudoers -f sudoers -m user=ambrose,host=hastur /etc/sudoers
Same as above, but expand aliases and prune out any non-matching users and hosts from the expanded entries:
$ cvtsudoers -ep -f sudoers -m user=ambrose,host=hastur /etc/sudoers
Convert sudoers.ldif from LDIF to the traditional sudoers format:
$ cvtsudoers -i ldif -f sudoers -o sudoers.new sudoers.ldif
Merge a global sudoers file with two host-specific policy files from the hosts โxyzzyโ and โplughโ:
$ cvtsudoers -f sudoers -o sudoers.merged sudoers \
xyzzy:sudoers.xyzzy plugh:sudoers.plugh
๐ SEE ALSO ๐
sudoers(5), sudoers.ldap(5), sudo(8)
๐ฉโ๐ป AUTHORS ๐ฉโ๐ป
Many people have worked on sudo over the years ๐ซถ This version is made up of code written primarily by:
- Todd C. Miller
For an exhaustive list of everyone who has contributed to sudo, check the CONTRIBUTORS.md file in the sudo distribution (https://www.sudo.ws/about/contributors/).
๐ BUGS ๐
if you think you've found a bug in cvtsudoers, you can file a bug report in the sudo bug database athttps://bugzilla.sudo.ws/, or open an issue athttps://github.com/sudo-project/sudo/issues. If email is more your thing, messages can go to the sudo-workers mailing list,https://www.sudo.ws/mailman/listinfo/sudo-workers(public), or to<sudo@sudo.ws>(private).
please do NOT report security vulnerabilities through public GitHub issues, Bugzilla, or mailing lists ๐ญ Instead, email them to<Todd.Miller@sudo.ws>. You may encrypt your message with PGP if you'd like, using the key found athttps://www.sudo.ws/dist/PGPKEYS.
๐ SUPPORT ๐
Limited free support is available through the sudo-users mailing list. Subscribe or search the archives at https://www.sudo.ws/mailman/listinfo/sudo-users.
โ๏ธ DISCLAIMER โ๏ธ
cvtsudoers is provided โAS ISโ, and any express or implied warranties, including but not limited to the implied warranties of merchantability and fitness for a particular purpose, are disclaimed ๐
โโ๏ธ For complete details, see the LICENSE.md file distributed with sudo, or https://www.sudo.ws/about/license/.
That's a wrap on cvtsudoers, bestie ๐โจ Sudo 1.9.17p2, last touched on February 22, 2025, and still slaying ๐ ๐
๐ฆ where this page comes from
- alpine 3.24: sudo-doc 1.9.17_p2-r2 ยท custom ISC
- arch rolling: sudo 1.9.17.p2-6 ยท custom
- centos-stream 10: sudo 1.9.17-13.p2.el10 ยท ISC
- centos-stream 9: sudo 1.9.17p2-6.el9 ยท ISC
- fedora 43: sudo 1.9.17-7.p2.fc43 ยท ISC
- fedora 44: sudo 1.9.17-10.p2.fc44 ยท ISC
- fedora 44: sudo 1.9.17-7.p2.fc44 ยท ISC
- nixos 26.05: sudo 1.9.17p2
- nixpkgs unstable: sudo 1.9.17p2
- ๐ the page's license: ISC
- Copyright (c) 2018, 2021-2025 Todd C. Miller <Todd.Miller@sudo.ws>
- ๐ girlified by anthropic/claude-haiku-5.5 on : a language model's rewrite of the original, under its terms