-
Notifications
You must be signed in to change notification settings - Fork 17
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
Add functionality to Enable/Disable users #782
base: main
Are you sure you want to change the base?
Conversation
@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 ReportAttention: Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #782 +/- ##
========================================
Coverage 79.05% 79.05%
========================================
Files 102 103 +1
Lines 10383 10489 +106
Branches 74 75 +1
========================================
+ Hits 8208 8292 +84
- Misses 1709 1730 +21
- Partials 466 467 +1 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
internal/services/pam/pam.go
Outdated
@@ -138,6 +138,12 @@ func (s Service) SelectBroker(ctx context.Context, req *authd.SBRequest) (resp * | |||
lang = "C" | |||
} | |||
|
|||
// Throw an error if the user trying to authenticate already exists in cache and is disabled |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We use the term "database" instead of "cache" now (related: #775)
// Throw an error if the user trying to authenticate already exists in cache and is disabled | |
// Throw an error if the user trying to authenticate already exists in the database and is disabled |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
changed.
Hi @shiv-tyagi, yes this looks good, thanks! One small thing: I would prefer to have a |
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. |
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. |
e6410f4
to
a7d839c
Compare
Sure. I will do that.
Noted.
Yes, I really intend to work on that. I will push my work soon :) |
7183092
to
5e81b35
Compare
5e81b35
to
54aae19
Compare
I rebased on main and resolved the conflicts caused by #779. |
7f1338f
to
fce5e88
Compare
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. |
fce5e88
to
7208138
Compare
@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. |
There was a problem hiding this 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!
// We don't care about the output of gpasswd in this test, but we still need to mock it. | ||
_ = localgroupstestutils.SetupGPasswdMock(t, "empty.group") |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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)
Thank you so much @adombeck for reviewing this. I have addressed all your suggestions. What next? How should I test it locally? |
cmd/authctl/user/user.go
Outdated
// UserCmd is a command to perform user-related operations. | ||
var UserCmd = &cobra.Command{ | ||
Use: "user", | ||
Short: "Commands retaled to users", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Short: "Commands retaled to users", | |
Short: "Commands related to users", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Silly me. I've fixed that. Thanks.
Looks great, thanks for addressing the comments, @shiv-tyagi! I just added one more comment. Once you resolved that, I plan to merge this and create follow-up tasks for adding documentation and actually shipping the tool as part of the authd package. |
cmd/authctl/user/disable.go
Outdated
Short: "Disable a user managed by authd", | ||
Args: cobra.ExactArgs(1), | ||
RunE: func(cmd *cobra.Command, args []string) error { | ||
fmt.Printf("Disabling user %s\n", args[0]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe use %q
? Otherwise we can just rely on fmt.Println
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changed to %q. Thanks.
cmd/authctl/user/disable.go
Outdated
Args: cobra.ExactArgs(1), | ||
RunE: func(cmd *cobra.Command, args []string) error { | ||
fmt.Printf("Disabling user %s\n", args[0]) | ||
conn, err := grpc.NewClient("unix://"+consts.DefaultSocketPath, grpc.WithTransportCredentials(insecure.NewCredentials())) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be overridable, for testing purposes mostly.
An env variable is probably enough.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changed to check the value of the AUTHD_SOCKET environment variable first and use the default socket it the env var is unset.
if err != nil { | ||
return err | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also check for the HealthCheck to be around? See pam adapter model but it can be simpler here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What benefit would that provide here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, do not make the request when the socket is there but not ready (so in a socket-activated scenario) or in tests where the daemon may take some time to show up.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Mmh, I don't really understand how calling the Health.Check
RPC method instead of the NSS.DisableUser
RPC method would help us when the daemon is not ready. Both have a timeout and if the daemon is ready before the timeout then they succeed, otherwise they fail.
My understanding of the gRPC health checks is that they are useful for two things: monitoring the status of gRPC services and choosing a healthy service from a list of services. Both don't apply to our use case.
cmd/authctl/user/enable.go
Outdated
conn, err := grpc.NewClient("unix://"+consts.DefaultSocketPath, grpc.WithTransportCredentials(insecure.NewCredentials())) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
client := authd.NewNSSClient(conn) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe group all this to a re-usable function
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Um okay. I will do that. Once we decide if we are going to keep this stuff in NSS or we want to create a separate service.
cmd/authctl/user/enable.go
Outdated
) | ||
|
||
// EnableCmd is a command to enable a user. | ||
var EnableCmd = &cobra.Command{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Any need for being exported?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am a golang newbie. I though I would need that to be able to use this command in users.go. I can move this whole subcommand to users.go if we want to avoid exporting this. Or I will be happy to do anything else you would suggest.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
no need to move it, can just be named with a lower case initial.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done. Thanks.
// DisableUser marks a user as disabled. | ||
func (s Service) DisableUser(ctx context.Context, req *authd.DisableUserRequest) (*authd.Empty, error) { | ||
if err := s.permissionManager.IsRequestFromRoot(ctx); err != nil { | ||
return nil, err | ||
} | ||
|
||
if req.GetName() == "" { | ||
return nil, status.Error(codes.InvalidArgument, "no user name provided") | ||
} | ||
|
||
if err := s.userManager.DisableUser(req.GetName()); err != nil { | ||
return nil, err | ||
} | ||
|
||
return &authd.Empty{}, nil | ||
} | ||
|
||
// EnableUser marks a user as enabled. | ||
func (s Service) EnableUser(ctx context.Context, req *authd.EnableUserRequest) (*authd.Empty, error) { | ||
if err := s.permissionManager.IsRequestFromRoot(ctx); err != nil { | ||
return nil, err | ||
} | ||
|
||
if req.GetName() == "" { | ||
return nil, status.Error(codes.InvalidArgument, "no user name provided") | ||
} | ||
|
||
if err := s.userManager.EnableUser(req.GetName()); err != nil { | ||
return nil, err | ||
} | ||
|
||
return &authd.Empty{}, nil | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm a bit unsure this should be part of the NSS service...
The nss service is meant to be the one for NSS operations and these do not include enabling/disabling an user AFAIK, so this should be IMHO part of an authd-management specific service, rather than in the NSS one, that is the one to be used by the NSS module to provide the group and passwd information.
@didrocks any opinion here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah I saw Didier had the same concerns: #640 (comment)
So we should move this out from NSS and have another management service, let's define a good name then.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, we should decide how we want to restructure the service. I made some suggestions on #640 (comment). I'm not sure we have to block merging this PR on that. I don't expect @shiv-tyagi to do that work, IMO we could do it as a follow-up.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Isn't better to make it before and then rebase this PR on that?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can also do that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we are doing a PR for restructuring this, I will wait till it is merged and then rebase my work on top of it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I feel that all the various GetPasswdByName
and friends (includind pre-check) should also be adjusted, otherwise you may end up returning an user that has been enabled as a valid one from the NSS side, no?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If GetPasswdByName
doesn't return a disabled user, its UID becomes available for use on the system again, which is exactly what we want to avoid and why we support disabling a user instead of deleting it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Mh, I see... That would imply having it available in other places though? For example will it be listed in the gdm list of users?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good question. I didn't test that. If it does, maybe we should use the org.freedesktop.Accounts
D-Bus service to set Locked
to True
when disabling a user. That should hide it from the list of users in the GDM login screen, right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would probably need more help to understand what exactly needs to be done. It would be a good learning opportunity for me, but might be a little annoying for you guys 😬😬.
userIsDisabled, err := s.userManager.IsUserDisabled(username) | ||
if err != nil && !errors.Is(err, users.NoDataFoundError{}) { | ||
return nil, fmt.Errorf("could not check if user %q is disabled: %w", username, err) | ||
} | ||
// Throw an error if the user trying to authenticate already exists in the database and is disabled | ||
if err == nil && userIsDisabled { | ||
return nil, status.Error(codes.PermissionDenied, fmt.Sprintf("user %s is disabled", username)) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This check can't be moved at lower level in the stack so that it's not only PAM-related?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What do you have in mind? Where would "lower in the stack" be here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was thinking to have this in the brokers side, but as per your NSS comment it's not something we want then.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would be nice to have integration tests for this, but if you don't have the time it would be nice if you could open an issue so that we can track it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What do you want to see tested in these integration tests?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would like to have tests which start authd and then use authctl and check that it works as expected. That would be an end-to-end test which we don't have a framework for yet. I'm not sure if makes sense to have something in between the tests we already have and this end-to-end test.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would like to have tests which start authd and then use authctl and check that it works as expected.
Exactly what I mean, and what it should be done IMHO in an integration test, similar to the PAM ones.
Basically, adding a new (VHS based, sorry) test that:
- Authenticates with an user
- Disables it
- Tries authentication again that must fail
- Enables it back
- It should be in
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you point me to the examples of existing integration tests in the code? I will use them as a reference to write new ones.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
❯ sudo gdbus call --system --dest org.freedesktop.Accounts --object-path /org/freedesktop/Accounts/User1001 --method org.freedesktop.Accounts.User.SetLocked true
❯ gdbus call --system --dest org.freedesktop.Accounts --object-path /org/freedesktop/Accounts --method org.freedesktop.Accounts.ListCachedUsers
([objectpath '/org/freedesktop/Accounts/User1000', '/org/freedesktop/Accounts/User1001'],)
But indeed that's marked as locked and so we filtering it out in gdm...
However... What lock does is that eventually calls usermod -L -- $USER
that indeed would fail for authd users, and so the user wouldn't never be marked as locked either.
In fact:
$ sudo gdbus call --system --dest org.freedesktop.Accounts --object-path /org/freedesktop/Accounts/User65692 --method org.freedesktop.Accounts.User.SetLocked true
Errore: GDBus.Error:org.freedesktop.Accounts.Error.Failed: running '/usr/sbin/usermod' failed: Processo figlio uscito con codice 6
Thus it wouldn't be enough... Account service has support for vendor overrides, but I'm unsure if they can help either
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If integration tests are not a blocker to merge this work, can we create another issue for that?
This also adds tests for the same.
5152313
to
85c7275
Compare
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.