CVTSUDOERS(1)

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

๐Ÿ’– 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 the SUDOERS_BASE environment 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 in input_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 with sudoers.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 -M is also specified, group queries use file instead 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, cvtsudoers will change rules that specify a host name of ALL to 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 -d command line option ๐ŸŽ€
  • expand_aliases = yes | no โ€” same as the -e command line option โœจ
  • group_file = file โ€” same as the --group-file command line option ๐Ÿ‘ฅ
  • input_format = ldif | sudoers โ€” same as the -i command line option ๐Ÿ“ฅ
  • match = filter โ€” same as the -m command line option ๐Ÿ”
  • match_local = yes | no โ€” same as the -M command line option ๐Ÿ 
  • order_increment = increment โ€” same as the -I command line option ๐Ÿ”ข
  • order_start = start_point โ€” same as the -O command line option ๐Ÿš€
  • output_format = csv | json | ldif | sudoers โ€” same as the -f command line option ๐Ÿ“ค
  • padding = padding โ€” same as the -P command line option ๐Ÿงธ
  • passwd_file = file โ€” same as the --passwd-file command line option ๐Ÿ—‚๏ธ
  • prune_matches = yes | no โ€” same as the -p command line option โœ‚๏ธ
  • sudoers_base = dn โ€” same as the -b command line option ๐ŸŒณ
  • suppress = sections โ€” same as the -s command 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 value ALL matches 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 of defaults, defaults_command, defaults_host, defaults_runas, or defaults_user.
  • binding โ€” for defaults_command, defaults_host, defaults_runas, and defaults_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's true or false.

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 of Cmnd_Alias, Host_Alias, Runas_Alias, or User_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, members gets 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 value ALL, 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 value ALL, 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 to ALL, 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 to ALL, 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 value ALL, 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 at https://bugzilla.sudo.ws/, or open an issue at https://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 at https://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