Skip to content

Add functionality to Lock/Unlock users #782

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

Open
wants to merge 49 commits into
base: main
Choose a base branch
from

Conversation

shiv-tyagi
Copy link
Contributor

@shiv-tyagi shiv-tyagi commented Feb 7, 2025

As discussed in #640, this adds a property to the UserDB to mark a user enable/disabled. Before creating an authentication session in pam, we check if the user exists in the cache and is disabled.

This also adds the initial implementation of the authctl tool which can be used to enable/disable a user.

UDENG-5270

@shiv-tyagi shiv-tyagi requested a review from a team as a code owner February 7, 2025 16:14
@shiv-tyagi
Copy link
Contributor Author

@adombeck Does this look good?

I see that the tests are failing on main branch as well. So I am hoping that it is not me.

@codecov-commenter
Copy link

codecov-commenter commented Feb 7, 2025

Codecov Report

❌ Patch coverage is 76.78571% with 26 lines in your changes missing coverage. Please review.
✅ Project coverage is 87.90%. Comparing base (229aa56) to head (3585468).
⚠️ Report is 23 commits behind head on main.

Files with missing lines Patch % Lines
cmd/authctl/main.go 30.76% 9 Missing ⚠️
internal/users/db/migration.go 83.33% 4 Missing ⚠️
cmd/authctl/user/user.go 72.72% 3 Missing ⚠️
cmd/authctl/user/lock.go 71.42% 2 Missing ⚠️
cmd/authctl/user/unlock.go 71.42% 2 Missing ⚠️
internal/services/pam/pam.go 75.00% 2 Missing ⚠️
internal/services/user/user.go 85.71% 2 Missing ⚠️
internal/users/db/update.go 80.00% 2 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #782      +/-   ##
==========================================
- Coverage   88.10%   87.90%   -0.21%     
==========================================
  Files          85       89       +4     
  Lines        6013     6125     +112     
  Branches      111      111              
==========================================
+ Hits         5298     5384      +86     
- Misses        659      685      +26     
  Partials       56       56              

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

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@shiv-tyagi shiv-tyagi changed the title Check is a user is disabled before logging in Check if a user is disabled before logging in Feb 10, 2025
@adombeck
Copy link
Contributor

Hi @shiv-tyagi, yes this looks good, thanks! One small thing: I would prefer to have a Disabled field instead of Enabled, so that the default value (false) means that the user is enabled, even when we're not using NewUserDB to instantiate the UserDB struct (like we do here).

@adombeck
Copy link
Contributor

There should also be a new test case "Error_when_user_is_disabled" in https://github.com/shiv-tyagi/authd/blob/fed39218cfcce6c23e80e0d02fe2a2d748d3d912/internal/brokers/manager_test.go#L187-L190.

@adombeck
Copy link
Contributor

Oh and I don't think it makes sense to merge this before we have code which actually causes a user to be disabled (i.e. the command-line tool). If you still plan to work on that soon, feel free to repurpose this PR. Otherwise, I'll start working on that soon and would then cherry-pick your commits to a new branch.

@shiv-tyagi
Copy link
Contributor Author

I would prefer to have a Disabled field instead of Enabled, so that the default value (false) means that the user is enabled, even when we're not using NewUserDB to instantiate the UserDB struct (like we do here).

Sure. I will do that.

There should also be a new test case "Error_when_user_is_disabled"

Noted.

If you still plan to work on that soon, feel free to repurpose this PR.

Yes, I really intend to work on that. I will push my work soon :)

@shiv-tyagi shiv-tyagi force-pushed the user-disable branch 4 times, most recently from 7183092 to 5e81b35 Compare February 15, 2025 18:22
@adombeck
Copy link
Contributor

I rebased on main and resolved the conflicts caused by #779.

@shiv-tyagi
Copy link
Contributor Author

I rebased on main and resolved the conflicts caused by #779.

Thanks for this @adombeck. I had some more work which I had not pushed yet. I would need to fix that as well. Planning to push that work soon.

@shiv-tyagi shiv-tyagi force-pushed the user-disable branch 2 times, most recently from 7f1338f to fce5e88 Compare February 19, 2025 06:51
@shiv-tyagi
Copy link
Contributor Author

I have finished the part till extending the GRPC API with new methods and adding unit tests for those. Now only the part of creating the CLI tool to call those methods remains.

@shiv-tyagi shiv-tyagi changed the title Check if a user is disabled before logging in Add functionality to Enable/Disable users Feb 26, 2025
@shiv-tyagi
Copy link
Contributor Author

@adombeck I have added the initial implementation of the authctl tool. Please check if it looks good and provide any suggestions for me to proceed. This is my first time writing Go code, so I apologise if I made any rookie mistakes. I’ll be happy to fix them.

Thanks in advance.

Copy link
Contributor

@adombeck adombeck left a comment

Choose a reason for hiding this comment

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

I've got a few comments but the overall code style is good! Thanks a lot, @shiv-tyagi!

Comment on lines 343 to 349
// We don't care about the output of gpasswd in this test, but we still need to mock it.
_ = localgroupstestutils.SetupGPasswdMock(t, "empty.group")
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't understand why we would need to mock gpasswd in this test, but I also don't see why that's done in many of the other tests in this file which are unrelated to /etc/group. I see that was introduced in fd10538, do you remember the reason, @denisonbarbosa?

Copy link
Collaborator

Choose a reason for hiding this comment

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

I think the reason is to make sure that for no reason the groups are changed when unexpected (and if that's the case, it's a good negative test IMHO)

Copy link
Collaborator

Choose a reason for hiding this comment

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

Assuming this goes after #993 (mostly because otherwise I won't be likely around to rebase that one), this will likely not needed anymore later on

@shiv-tyagi
Copy link
Contributor Author

Thank you so much @adombeck for reviewing this. I have addressed all your suggestions. What next? How should I test it locally?

adombeck added 14 commits July 11, 2025 22:34
I would expect a function called RunDaemon to start the daemon and block
until it has finished running. StartDaemon makes it clear that the
function returns once the daemon was started.
It can take a while to build and start the daemon. Lets add some logs to
make it a bit clearer what is taking so long.
authctl shows these errors messages, so let's improve them.
For example, instead of:

    no result matching "foo" in "users"

use:

    user "foo" not found
The BuildDaemon() function is only called to build the daemon for
integration tests with the example broker. Lets avoid passing the same
arguments everywhere.

Also renames the function to BuildDaemonWithExampleBroker.
Everywhere we called StartDaemon(), we registered the cleanup of the
daemon process via t.Cleanup(). Let's avoid the duplicate code by
inlining the cleanup registration into StartDaemon().
Lets be more consistent with other command-line tools which control
system services, like systemctl and machinectl, and not print any output
if the command succeeds (for example like `systemctl start`/`systemctl stop` or
`machinectl kill`).
When trying to log in with a locked user, the following error message is
printed:

    $ su [email protected]
    can't select broker: error PermissionDenied from server: can't start authentication transaction: rpc error: code = PermissionDenied desc = user [email protected] is locked

The "can't start authentication transaction" part doesn't add any
valuable information to the error message, which is already too long.

By omitting it, we also avoid that the gRPC status is formatted to a
string containing the error code, which would duplicate information
which the caller adds to the error message.

With this commit, the error message is simplified to:

    can't select broker: error PermissionDenied from server: user [email protected] is locked
Following up on the error message printed when trying to log in as a
locked user, we can further improve the message:

    $ su [email protected]
    can't select broker: error PermissionDenied from server: user [email protected] is locked

by omitting the "can't select broker:" part, which doesn't add any
useful information.

With this commit, the error message is simplified to:

    error PermissionDenied from server: user [email protected] is locked
Further improve the error message printed when trying to log in with a
locked user from:

    error PermissionDenied from server: user [email protected] is locked

to
    permission denied: user [email protected] is locked
@adombeck
Copy link
Contributor

Rebased on main and resolved the conflicts caused by #993

@adombeck adombeck requested a review from 3v1n0 July 12, 2025 15:52
@shiv-tyagi
Copy link
Contributor Author

Thanks @adombeck for all the work you did.

Any update on this @3v1n0?

@adombeck
Copy link
Contributor

adombeck commented Jul 22, 2025

Thanks @adombeck for all the work you did.

Any update on this @3v1n0?

@3v1n0 is out of office until August, and I will be out of office soon until the second week of August, so I don't expect things to move forward until then. I'm sorry this is taking so long! It seems the issue wasn't such a good candidate after all, because there was a lot more work and iterations of reviews involved than I first anticipated :/

@shiv-tyagi
Copy link
Contributor Author

No worries @adombeck. I just keep bugging to make sure this doesn't get lost. Once this one is merged, I plan to add more functionality to it or find another issue to work on.

Comment on lines +5 to +7
//go:generate sh -c "go run ../cmd/authctl/main.go completion bash > bash/authctl"
//go:generate sh -c "go run ../cmd/authctl/main.go completion zsh > zsh/_authctl"
//go:generate sh -c "go run ../cmd/authctl/main.go completion fish > fish/authctl.fish"
Copy link
Collaborator

Choose a reason for hiding this comment

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

Is adding a file comment such as:

# Code generated by authctl completion. DO NOT EDIT.

Helping with github not showing the files in reviews?

Copy link
Contributor

Choose a reason for hiding this comment

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

No, I don't think a file comment will change how GitHub shows the files on a PR. I did a quick search and found this: https://github.com/github-linguist/linguist/blob/main/docs/overrides.md#generated-code

So we might be able to suppress diffs of generated files via entries in a .gitattributes file.

Copy link
Contributor

Choose a reason for hiding this comment

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

I can file a issue to do that as a follow-up if you think it's worth it.

Comment on lines 84 to 97
> ssh ${AUTHD_PAM_SSH_USER}@localhost ${AUTHD_PAM_SSH_ARGS}
permission denied: user user-integration-pre-check-ssh-authenticate-user-locks-and-unlocks-it-on-shared-sshd is locked
Received disconnect from ${SSH_HOST} port ${SSH_PORT} Too many authentication failures
Disconnected from ${SSH_HOST} port ${SSH_PORT}
Copy link
Collaborator

Choose a reason for hiding this comment

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

This does not seem to have been re-generated, see: https://github.com/ubuntu/authd/actions/runs/16239608510/job/45854663647?pr=782

Copy link
Contributor

Choose a reason for hiding this comment

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

Fixed

Same as pam_unix, we now avoid leaking to unauthenticated users whether
a user account is locked. That's achieved by only checking if the user
is locked after they successfully authenticated to the broker, aborting
login in that case.

That has the side effect that locked users can still refresh the token
and user info which the broker stores on disk.

This commit also implements the same improvements to the "permission
denied: user is locked" error message which were already implemented in
the SelectBroker method. I'm not reverting the changes to the
SelectBroker method because I think they are still improvements, even if
they are not relevant for this specific error message anymore.
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