Skip to content

ast: change Tag.passwd into an enum #725

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Aug 8, 2023
Merged

ast: change Tag.passwd into an enum #725

merged 3 commits into from
Aug 8, 2023

Conversation

japaric
Copy link
Collaborator

@japaric japaric commented Jul 31, 2023

if skip_passwd {
*passwd = Some(false)
*authenticate = Authenticate::Nopasswd;
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for the record, I've found this operation, and the associated tests, "strange" (for lack of a better word). first off, modifying the AST feels wrong; IME, after parsing, the AST should be considered an immutable data structure; rustc works like that.

I think the main issue is that there's no clear separation / delimination between the AST and "business" / "domain" level settings. whether 'pass auth is required' is a function of the sudoers policy, command line flags and who the invoking user is so that setting should not be stored in the AST but in a data structure that is created later in the pipeline.

in my mind, the pipeline should go like this: AST -?-> MIR -?-> Sudoers -> Context. AST is just a syntactically correct sudoers file. MIR is the sudoers AST after expanding aliases (+). Sudoers is a "sound" and processed set of settings and policies, e.g. it contains the final env_keep list rather than a list of operations to be performed on it. this Sudoers data structure then gets embedded in the Context that includes the invoking user, hostname, command line args, etc. and it's that Context what gets queried about auth requirements. sudo --list for example prints the MIR so it should have access to it but should not be able to modify it -- as that would invalidate the Sudoers data structure as well as the Context

the tests modified in the PR are doing Context-level queries so they shouldn't do assertions on AST-level types

(+) in rustc, this would be the HIR (or HAIR?) but we don't have that many immediate representations.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This check does not modify the AST; the check_permission function takes the AST and a specific (context-sensitive request) and returns a "policy object" (which is not the ast but en encoding of a certain judgement).

Sadly there is an exception in sudo when someone is root and (since it is very important to get that right), it needed to be writen down very clearly, so this line modifies something in the resulting judgement (i.e. something that the pipeline that runs on top of this), not the AST (it also couldn't modify the AST since it doesn't have a mutable reference to it).

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I still think there's an organization / delimitation problem. Tag is in the ast module but it's appears to be directly treated as a MIR level object. a proper AST-level tag would be maintained as a list (vec) for each cmnd_spec so CWD=* PASSWD: NOPASSWD: /usr/bin/true would be parsed as having 3 tags: [Tag::Cwd(_), Tag::Nopasswd, Tag::Passwd]

a MIR level Tag would be a reduced and unfolded version, so the above cmnd_spec would only have the tags CWD and NOPASSWD (reduced). unfolding happens in NOPASSWD: /usr/bin/false, /usr/bin/true which unfolds into NOPASSWD: /usr/bin/false, NOPASSWD: /usr/bin/true. IMO, ast::Tag makes more sense as an enum; mir::Tag makes more sense as a struct (like the existing one)

lastly, I wouldn't modify Tag here but rather store the skip_passwd in the Judgment and have a Judgment::needs_passwd method that checks skip_passwd and then falls back to checking the Tag field. if the tests called Judgment::needs_passwd instead of reading a modified MIR-level Tag they'd feel less like they are punching through the abstraction.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To me this does not feel like punching through an abstraction (some other things do) but is the thing being abstracted by the Policy trait.

The skip_passwd logic basically can happen in three spots: check_permission (but that is cleaner to read if it does only the general logic and not the exceptions), the higher-level function that constructs the (opaque to the outside world) Judgement object, or in the trait implementation (but then it is in a completely different file, and the skip_passwd needs to be kept around for longer, increasing the total state). Functionally it doesn't matter that much which one is used.

Right now the earlier parts (unfolding of aliases and tags) happens on-the-fly, which is quite efficient. I don't feel don't need a two- or three-pass compiler just to parse and check sudoers (feels like added complexity).

@github-actions
Copy link

Number of dependencies and binary size impact report

Metric main PR #725 Delta
Direct dependencies 3 3 -
Total dependencies 4 4 -
Binary size 1009.3 KiB 1018.9 KiB +0.9%
Text size 591.1 KiB 596.7 KiB +0.9%
Dependencies diff
 └─ sudo-rs [v0.2.0-dev.20230711]
    ├─ glob [v0.3.1]
    ├─ libc [v0.2.147]
    └─ log [v0.4.19]

@codecov
Copy link

codecov bot commented Jul 31, 2023

Codecov Report

Patch coverage: 55.00% and no project coverage change.

Comparison is base (be4876f) 54.33% compared to head (92b8926) 54.33%.

Additional details and impacted files
@@           Coverage Diff           @@
##             main     #725   +/-   ##
=======================================
  Coverage   54.33%   54.33%           
=======================================
  Files          71       71           
  Lines        9635     9642    +7     
=======================================
+ Hits         5235     5239    +4     
- Misses       4400     4403    +3     
Files Changed Coverage Δ
src/sudoers/entry.rs 0.00% <0.00%> (ø)
src/sudoers/entry/verbose.rs 0.00% <0.00%> (ø)
src/sudoers/mod.rs 69.48% <50.00%> (ø)
src/sudoers/ast.rs 82.71% <80.00%> (-0.21%) ⬇️
src/sudoers/policy.rs 76.92% <100.00%> (ø)
src/sudoers/test/mod.rs 98.33% <100.00%> (ø)

... and 2 files with indirect coverage changes

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@rnijveld rnijveld added this pull request to the merge queue Aug 8, 2023
Merged via the queue into main with commit 38c8c12 Aug 8, 2023
@japaric japaric deleted the passwd-enum branch August 22, 2023 11:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants