Convert and Filter Sudoers Policies Safely with cvtsudoers
You will finish with a read-only workflow for converting a sudoers policy to JSON or CSV, filtering the result, and reconstructing sudoers text when needed. The examples were checked with sudo 1.9.15p5-3ubuntu5.24.04.2, whose cvtsudoers reports grammar version 50. Allow about 15 minutes. You need a shell and a syntactically valid policy file; no elevated privilege is needed for the examples if you can read that file.
The route
Jump straight to the step you need, or tick off Done means at the end.
Safety boundary
cvtsudoers reads policy and writes converted output. It does not install a policy, validate a live sudo configuration for you, or grant access. Keep output in a temporary file until a separate review and deployment process is complete. Do not point -o at /etc/sudoers or an included file while experimenting.
1. Confirm the installed tool
Check the version and grammar before relying on an example. This is an ordinary, non-privileged command:
$ cvtsudoers -V
cvtsudoers version 1.9.15p5
cvtsudoers grammar version 50
The default input format is sudoers and the default output format is LDIF. Always state the format explicitly in scripts so a package upgrade cannot turn an unlabelled output file into an unexpected format.
Checkpoint
If cvtsudoers -V fails, stop and install or repair the sudo package through your normal system administration process. Do not substitute an unverified converter.
2. Convert a policy to JSON
Use a small policy supplied on standard input first. This avoids touching a real policy while you learn the shape of the output:
$ printf '%s\n' \
'User_Alias ADMINS = alice, %wheel' \
'ADMINS ALL=(root) /usr/bin/systemctl status nginx' \
| cvtsudoers -f json -
{
"User_Aliases": {
"ADMINS": [
{ "username": "alice" },
{ "usergroup": "wheel" }
]
},
"User_Specs": [
{
"User_List": [
{ "useralias": "ADMINS" }
],
"Host_List": [
{ "hostname": "ALL" }
],
"Cmnd_Specs": [
{
"runasusers": [
{ "username": "root" }
],
"Commands": [
{ "command": "/usr/bin/systemctl status nginx" }
]
}
]
}
]
}
A file argument works the same way:
$ cvtsudoers -i sudoers -f json /path/to/policy.sudoers > /tmp/policy.json
The input must parse successfully. Includes and comments are represented according to the target format, so do not assume that JSON is a line-for-line transcription of the original file.
3. Inspect a smaller CSV report
CSV is useful for a spreadsheet or a simple report. Filter by a user, host, group, or command with -m. The filter is a comma-separated list of key=value pairs:
$ cvtsudoers -f csv -m user=alice - /path/to/policy.sudoers
rule,user,host,runusers,rungroups,options,command
rule,ADMINS,ALL,root,,"",/usr/bin/systemctl status nginx
Supported filter keys include user, group, host, and cmnd; cmd is accepted as the short spelling for cmnd. Matching does not consult local password or group databases by default. That means a name can match policy text even when the account is absent on this machine.
Add -M only when the filter must use local passwd and group data. With that option, users and groups in the filter must exist locally, and a user's groups can also affect matching. This is a security-sensitive reporting choice: it can change what appears in the report without changing the source policy.
4. Prune a filtered policy when required
A matching rule can still contain other users, groups, or hosts. Use -p when the output must remove those non-matching members:
$ cvtsudoers -i sudoers -f json -m user=alice -p \
/path/to/policy.sudoers > /tmp/alice-policy.json
Review the result before using it. Filtering is not an access-control change, and pruning can make a policy fragment unsuitable as a replacement for the original. Keep the source file unchanged so recovery is simply deleting the temporary report.
5. Reconstruct sudoers text for review
To produce traditional sudoers syntax, select that output format explicitly:
$ cvtsudoers -i sudoers -f sudoers /path/to/policy.sudoers > /tmp/reconstructed.sudoers
$ sed -n '1,80p' /tmp/reconstructed.sudoers
The reconstructed file is parsed and rebuilt. Comments are not preserved, and data from included files is emitted inline. Aliases are normally preserved in sudoers output; add -e only when you specifically want aliases expanded.
Before any deployment, test the reconstructed file with the sudo validation procedure used by your distribution and review its permissions and ownership. Replacing a live sudoers file is an elevated, service-impacting security change. The conversion command itself does not require sudo; use elevated privileges only for a separately approved installation step.
6. Convert or merge several policies
Multiple input files are merged into one policy. If the same logical policy comes from different hosts, prefix each filename with its host name and a colon:
$ cvtsudoers -f json \
web01:/path/to/web.sudoers \
db01:/path/to/db.sudoers \
> /tmp/merged.json
The converter assumes names are consistent across the files. It removes duplicate aliases and renames conflicting aliases with a numeric suffix such as SERVERS_1, updating references. It also warns about conflicting Defaults settings and limitations that cannot be made host-specific. Capture warnings when the merge matters:
$ cvtsudoers -f json -l /tmp/cvtsudoers-warnings.log \
web01:/path/to/web.sudoers db01:/path/to/db.sudoers \
> /tmp/merged.json
$ sed -n '1,120p' /tmp/cvtsudoers-warnings.log
Review both the merged output and the warning log. Do not treat a successful exit as proof that every semantic conflict was resolved as you intended.
7. Check the common failure points
- Syntax error: fix the source policy first.
cvtsudoerscan convert only a syntactically correct policy. - Unexpected empty or narrow output: inspect the
-mfilter, and remember that-premoves non-matching members. - LDIF needs a base DN: when converting to LDIF, use
-b ou=SUDOers,dc=example,dc=orgor the configuredSUDOERS_BASEvalue. LDIF also cannot represent every sudoers-specific Defaults or alias construct. - Output looks unlike the input: comments are discarded, includes are inlined for sudoers output, and the target format has its own structure.
- Options appear ignored: command-line options override values from
/etc/cvtsudoers.confby default. Inspect that file if a local configuration is affecting an unlabelled invocation.
Done means
cvtsudoers -Vreported the version you checked.- The input policy converted to an explicitly selected format.
- Any filter and
-Mbehaviour was chosen deliberately, with-pused only when pruning was intended. - Warnings and merged output were reviewed separately.
- No live sudoers file was overwritten during the conversion or review.