Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 47 additions & 0 deletions cmd/authctl/user/set-uid.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package user

import (
"context"
"fmt"
"strconv"

"github.com/spf13/cobra"
"github.com/ubuntu/authd/internal/proto/authd"
)

// setUIDCmd is a command to set the UID of a user managed by authd.
var setUIDCmd = &cobra.Command{
Use: "set-uid <name> <uid>",
Short: "Set the UID of a user managed by authd",
Args: cobra.ExactArgs(2),
RunE: func(cmd *cobra.Command, args []string) error {
name := args[0]
uidStr := args[1]
uid, err := strconv.ParseUint(uidStr, 10, 32)
if err != nil {
return err
}

fmt.Printf("Setting UID of user %q to %d...\n", name, uid)

client, err := NewUserServiceClient()
if err != nil {
return err
}

resp, err := client.SetUserID(context.Background(), &authd.SetUserIDRequest{
Name: name,
Id: uint32(uid),
})
if err != nil {
return err
}

// Print any warnings returned by the server.
for _, warning := range resp.Warnings {
fmt.Printf("Warning: %s\n", warning)
}

return nil
},
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ Usage:
Available Commands:
lock Lock (disable) a user managed by authd
unlock Unlock (enable) a user managed by authd
set-uid Set the UID of a user managed by authd

Flags:
-h, --help help for user
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ Usage:
Available Commands:
lock Lock (disable) a user managed by authd
unlock Unlock (enable) a user managed by authd
set-uid Set the UID of a user managed by authd

Flags:
-h, --help help for user
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ Usage:
Available Commands:
lock Lock (disable) a user managed by authd
unlock Unlock (enable) a user managed by authd
set-uid Set the UID of a user managed by authd

Flags:
-h, --help help for user
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ Usage:
Available Commands:
lock Lock (disable) a user managed by authd
unlock Unlock (enable) a user managed by authd
set-uid Set the UID of a user managed by authd

Flags:
-h, --help help for user
Expand Down
1 change: 1 addition & 0 deletions cmd/authctl/user/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,4 +46,5 @@ func NewUserServiceClient() (authd.UserServiceClient, error) {
func init() {
UserCmd.AddCommand(lockCmd)
UserCmd.AddCommand(unlockCmd)
UserCmd.AddCommand(setUIDCmd)
}
8 changes: 6 additions & 2 deletions debian/authd.service.in
Original file line number Diff line number Diff line change
Expand Up @@ -87,5 +87,9 @@ SystemCallFilter=@system-service
# This makes all files and directories not associated with process management invisible in /proc
ProcSubset=pid

# gpasswd requires this specific capability to alter the shadow files
CapabilityBoundingSet=CAP_CHOWN
# CAP_CHOWN: Required by gpasswd to alter the shadow files.
# CAP_DAC_READ_SEARCH: Required by the chown system call to change ownership of
# files not owned by the user. We need this to change the
# ownership of the user's home directory when changing the
# user's UID.
CapabilityBoundingSet=CAP_CHOWN CAP_DAC_READ_SEARCH
35 changes: 35 additions & 0 deletions internal/fileutils/fileutils.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@ import (
"errors"
"fmt"
"io"
"math"
"os"
"path/filepath"
"syscall"

"golang.org/x/sys/unix"
)
Expand Down Expand Up @@ -130,3 +132,36 @@ func LockDir(dir string) (func() error, error) {

return unlock, nil
}

// ChownRecursiveFrom changes ownership of files and directories under root
// from the current UID/GID (fromUID, fromGID) to the new UID/GID (toUID, toGID).
func ChownRecursiveFrom(root string, fromUID, fromGID, toUID, toGID uint32) error {
if toUID > math.MaxInt32 || toGID > math.MaxInt32 {
return fmt.Errorf("toUID (%d) or toGID (%d) is too large to convert to int", toUID, toGID)
}

return filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}

stat, ok := info.Sys().(*syscall.Stat_t)
if !ok {
return fmt.Errorf("failed to get raw stat for %q", path)
}

if stat.Uid == fromUID {
if err := os.Chown(path, int(toUID), -1); err != nil {
return fmt.Errorf("failed to change owner of %q from UID %d to %d: %w", path, fromUID, toUID, err)
}
}

if stat.Gid == fromGID {
if err := os.Chown(path, -1, int(toGID)); err != nil {
return fmt.Errorf("failed to change group of %q from GID %d to %d: %w", path, fromGID, toGID, err)
}
}

return nil
})
}
Loading
Loading