Skip to content

Add authctl user set-shell command#1165

Open
adombeck wants to merge 14 commits intomainfrom
939-set-shell
Open

Add authctl user set-shell command#1165
adombeck wants to merge 14 commits intomainfrom
939-set-shell

Conversation

@adombeck
Copy link
Contributor

@adombeck adombeck commented Dec 15, 2025

Important

This is based on #1271, please review and merge that first.

Allow users to change their shell.

Closes #939
UDENG-7089

@adombeck adombeck force-pushed the 939-set-shell branch 2 times, most recently from eb3fed6 to dfb2353 Compare December 17, 2025 11:35
@codecov-commenter
Copy link

Codecov Report

❌ Patch coverage is 63.65462% with 181 lines in your changes missing coverage. Please review.
✅ Project coverage is 86.08%. Comparing base (6ff09d2) to head (dfb2353).

Files with missing lines Patch % Lines
internal/users/proc/proc.go 60.41% 38 Missing ⚠️
cmd/authctl/internal/completion/completion.go 0.00% 29 Missing ⚠️
internal/users/db/update.go 70.96% 27 Missing ⚠️
cmd/authctl/user/set-uid.go 0.00% 19 Missing ⚠️
cmd/authctl/group/set-gid.go 0.00% 16 Missing ⚠️
cmd/authctl/user/set-shell.go 0.00% 14 Missing ⚠️
internal/services/user/user.go 70.58% 10 Missing ⚠️
internal/services/permissions/permissions.go 66.66% 7 Missing ⚠️
internal/testlog/testlog.go 61.11% 7 Missing ⚠️
internal/users/manager.go 95.00% 6 Missing ⚠️
... and 4 more
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1165      +/-   ##
==========================================
- Coverage   87.64%   86.08%   -1.56%     
==========================================
  Files          90       97       +7     
  Lines        6222     6682     +460     
  Branches      111      111              
==========================================
+ Hits         5453     5752     +299     
- Misses        713      874     +161     
  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.

@adombeck adombeck marked this pull request as ready for review December 17, 2025 12:29
@adombeck adombeck requested a review from a team as a code owner December 17, 2025 12:29
@adombeck adombeck marked this pull request as draft February 18, 2026 14:15
@adombeck adombeck force-pushed the 939-set-shell branch 2 times, most recently from 4380115 to e564efe Compare February 18, 2026 14:49
@codecov
Copy link

codecov bot commented Feb 18, 2026

Codecov Report

❌ Patch coverage is 91.40625% with 11 lines in your changes missing coverage. Please review.
✅ Project coverage is 87.17%. Comparing base (6925080) to head (ee00943).
⚠️ Report is 2 commits behind head on main.

Files with missing lines Patch % Lines
cmd/authctl/user/set-shell.go 64.28% 5 Missing ⚠️
internal/users/db/update.go 80.00% 2 Missing ⚠️
cmd/authctl/internal/client/client.go 0.00% 1 Missing ⚠️
internal/services/permissions/permissions.go 95.83% 1 Missing ⚠️
internal/users/manager.go 94.73% 1 Missing ⚠️
internal/users/userutils.go 96.96% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1165      +/-   ##
==========================================
+ Coverage   86.26%   87.17%   +0.90%     
==========================================
  Files          99      102       +3     
  Lines        6685     6798     +113     
  Branches      111      111              
==========================================
+ Hits         5767     5926     +159     
+ Misses        862      816      -46     
  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.

@adombeck adombeck force-pushed the 939-set-shell branch 4 times, most recently from 342adc9 to 5b82500 Compare February 20, 2026 11:37
@adombeck adombeck marked this pull request as ready for review February 20, 2026 11:54
@adombeck adombeck requested a review from 3v1n0 February 20, 2026 12:07
@adombeck adombeck force-pushed the 939-set-shell branch 5 times, most recently from 0d9d00c to a1048eb Compare February 25, 2026 09:50
Comment on lines +286 to +287
if err := s.permissionManager.CheckRequestIsFromRootOrUID(ctx, user.UID); err != nil {
return nil, status.Error(codes.PermissionDenied, err.Error())
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I noticed that in case the request is from the non-root user, chsh requires the user to authenticate in order to change the shell.

We should also be able to do that by letting the user authenticate to the broker. Implementing that seems like quite a lot of effort though, so I would postpone that and only allow root to set user shells for now.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

only allow root to set user shells for now

I implemented that

Copy link
Contributor

Choose a reason for hiding this comment

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

We should also be able to do that by letting the user authenticate to the broker. Implementing that seems like quite a lot of effort though

Can't we do it in the client-side for now, by just re-exec with sudo if any or (better) using polkit?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

My understanding is that requiring the user to authenticate is meant to improve security. If we would do that client-side, it would just make it more cumbersome for the user without improving security at all, because user processes could still change the user's shell without authentication by talking to the server directly instead of using authctl.

Copy link
Contributor

Choose a reason for hiding this comment

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

If we would do that client-side, it would just make it more cumbersome for the user without improving security at all, because user processes could still change the user's shell without authentication by talking to the server directly instead of using authctl.

How that? The server will still only allows request from root. So basically the client has just to do if $UID != 0; then exec pkexec "$0" ${@}; fi or something.

Then ideally, we could do it with proper polkit rules too, so that any user can change it's own password, even if not root or if they have not sudo permissions.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

ah, I misunderstood your proposal. yes, we can add polkit support but I think that's mostly orthogonal to this PR, so I would still do it as a follow-up.

Also renames peerCredsInfo to peerAuthInfo because the field in
peer.Peer is named AuthInfo.

Also improves the comment of WithUnixPeerCreds.
The envutils package provides utilities for manipulating string slices
representing environment variables.
We have to pass the GOCOVERDIR environment variable to the authctl
binary.
The grpc.NewClient does not actually try to connect to authd, it just
sets up the client but doesn't do any I/O.
The original plan was to allow users to change their own shell without
root permissions, like chsh does. However, chsh requires the user to
authenticate before it allows change the shell.

We should also be able to do that by letting the user authenticate to
the broker. That's a lot more effort than originally planned though,
so for now we only allow root to change user shells.
But display a warning, similar to the behavior of chsh.
The warning message in the golden file still didn't have the "Warning:"
prefix we now add to most of our warnings. That's because the tests
replaced the entire warning message.

Fix by only replacing the parts which need to be replaced.

// CheckRequestIsFromRootOrUID checks if the current gRPC request is from a root
// user or a specified user and returns an error if not.
func (m Manager) CheckRequestIsFromRootOrUID(ctx context.Context, uid uint32) (err error) {
Copy link
Contributor

Choose a reason for hiding this comment

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

I think this change should stay in a separate commit

Copy link
Contributor

Choose a reason for hiding this comment

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

A per 93315ac#r2852397294 do we still need this change though or can we post-pone it?

Comment on lines +286 to +287
if err := s.permissionManager.CheckRequestIsFromRootOrUID(ctx, user.UID); err != nil {
return nil, status.Error(codes.PermissionDenied, err.Error())
Copy link
Contributor

Choose a reason for hiding this comment

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

We should also be able to do that by letting the user authenticate to the broker. Implementing that seems like quite a lot of effort though

Can't we do it in the client-side for now, by just re-exec with sudo if any or (better) using polkit?


// Check if the shell is in the list of allowed shells in /etc/shells
shells, err := os.ReadFile("/etc/shells")
if err != nil {
Copy link
Contributor

Choose a reason for hiding this comment

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

Do we want to skip the check if the file does not exist?

Also, wondering if for testing purposes we should allow some kind of overriding via a test-time variable.

return err
}
for _, allowedShell := range strings.Split(string(shells), "\n") {
if allowedShell[0] == '#' {
Copy link
Contributor

Choose a reason for hiding this comment

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

                line := strings.TrimSpace(allowedShell)
                if line == "" || line[0] == '#' {

But I'd actually swap the definition of allowedShell and line.

if err != nil {
return err
}
for _, allowedShell := range strings.Split(string(shells), "\n") {
Copy link
Contributor

Choose a reason for hiding this comment

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

What about using bufio.NewScanner()

Comment on lines +13 to +15
if strings.HasPrefix(kv, key+"=") {
return strings.TrimPrefix(kv, key+"=")
}
Copy link
Contributor

Choose a reason for hiding this comment

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

strings.CutPrefix?

if strings.ContainsRune(value, '\x00') {
return nil, fmt.Errorf("invalid value: %q", value)
}

Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe also check for valid utf value?

@@ -0,0 +1,46 @@
// Package envutils provides utilities for manipulating string slices representing environment variables.
package envutils
Copy link
Contributor

Choose a reason for hiding this comment

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

Wondering if we could make this a struct that holds the env variables in a map and we can create it from a []string and it can generate one on request.


//nolint:gosec // G204 it's safe to use exec.Command with a variable here
cmd := exec.Command(authctlPath, append([]string{"group"}, tc.args...)...)
cmd.Env = []string{testutils.CoverDirEnv()}
Copy link
Contributor

Choose a reason for hiding this comment

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

Oh, I thought this was addressed already as part of #782 (comment) :)

}

message SetShellResponse {
repeated string warnings = 1;
Copy link
Contributor

Choose a reason for hiding this comment

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

This not a new thing, but I feel that we should try to return error / warning codes instead to clients, so that clients can in case localize them depending on the local configuration (while the daemon may know the locale set by the user, we also need to be able to follow LC_ALL or LANG).

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.

Support changing the user's shell via authctl

3 participants