Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
6003598
refactor: Split cmd/authctl/user/user_test.go
adombeck Nov 21, 2025
94065d3
Fix comment
adombeck Jun 17, 2025
a787555
Add gRPC method SetUserID
adombeck Jun 17, 2025
1747b57
Add `authctl user set-uid` command
adombeck Jun 17, 2025
2e24ad8
Return a gRPC error with code PermissionDenied
adombeck Jun 17, 2025
82a571a
debian/authd.service.in: Grant capability CAP_DAC_READ_SEARCH
adombeck Jun 17, 2025
99e55e9
fileutils: Add ChownRecursiveFrom
adombeck Jun 17, 2025
09de1bd
testutils: Add RunTestAsRoot
adombeck Nov 20, 2025
9632666
Add TestChownRecursiveFrom
adombeck Nov 20, 2025
85033ba
Automatically change owner of home directory when changing UID
adombeck Jun 17, 2025
5b8a218
Add TestSetUIDCommand
adombeck Nov 12, 2025
a10ce88
Add db_test.TestSetUserID
adombeck Nov 17, 2025
07f0860
Support creating users/groups in bubblewrap
adombeck Nov 18, 2025
3fa2dc2
tests: Support chown in bubblewrap
adombeck Nov 27, 2025
6239815
tests: Run TestChownRecursiveFrom in bubblewrap
adombeck Dec 1, 2025
d0d9b26
refactor: Remove unused parameter from runInBubbleWrap
adombeck Dec 2, 2025
8eab98f
testutils/bubblewrap: Support updating golden files
adombeck Dec 2, 2025
06f2bde
testutils: Skip cleanup when in bubblewrap
adombeck Dec 2, 2025
5fa76fc
bubblewrap: Mount a tmpfs to /tmp in the sandbox
adombeck Dec 2, 2025
024785f
Add users_test.TestSetUserID
adombeck Dec 2, 2025
2a49f7d
Add SetGroupID
adombeck Nov 25, 2025
22503b8
Add db_test.TestSetGroupID
adombeck Nov 25, 2025
d45de53
Add users_test.TestSetGroupID
adombeck Dec 2, 2025
bb5718d
Add `authctl group` command
adombeck Dec 2, 2025
6bf162f
Add `authctl group set-gid` command
adombeck Dec 2, 2025
bf0272f
Add TestSetGIDCommand
adombeck Dec 2, 2025
ea2ac9c
pam/integration-tests: Don't print authctl usage message in VHS tape
adombeck Dec 2, 2025
167c881
authctl: Add long description for set-uid command
adombeck Dec 2, 2025
78b62e9
authctl: Add long description for set-gid command
adombeck Dec 3, 2025
5db300a
Take lock in SetUserID/SetGroupID
adombeck Dec 4, 2025
73b39b9
tests: Don't skip bubblewrap tests in CI
adombeck Dec 4, 2025
a2bcab2
Try enabling unprivileged user namespaces in CI
adombeck Dec 4, 2025
545661a
tests: Avoid unshare hanging forever in some environments
adombeck Dec 4, 2025
049b7dc
tests: Make chown work with bubblewrap executed via sudo
adombeck Dec 4, 2025
f6f84b9
tests: Skip bubblewrap tests in autopkgtest
adombeck Dec 8, 2025
d5511f5
ci: Group log lines of llvm-symbolizer installation
adombeck Dec 10, 2025
225b205
autopkgtest: Run go tests without -v
adombeck Dec 10, 2025
a1a514f
Check if user is busy before changing its UID
adombeck Dec 11, 2025
33b3e20
authctl: Add argument completion
adombeck Dec 10, 2025
9b4fa0b
testlog: Fix duplicate newlines
adombeck Dec 16, 2025
6b124f4
tests: Improve logging of bubblewrap commands
adombeck Dec 16, 2025
d17766f
tests: Improve logging when building authd and authctl
adombeck Dec 16, 2025
0b45588
tests: Improve logging of authctl tests
adombeck Dec 16, 2025
ad0dd6d
authctl: Tell authd which language to return warnings in
adombeck Dec 19, 2025
d2585ba
Revert "ci: Install and load apparmor-profiles"
adombeck Jan 18, 2026
b2954fe
Initial plan
Copilot Jan 27, 2026
f3895ee
Add tests for getHomeDirOwner function
Copilot Jan 27, 2026
00acfef
Improve test assertions to work in diverse environments
Copilot Jan 27, 2026
e34fb3c
Remove trailing whitespace
Copilot Jan 27, 2026
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
5 changes: 1 addition & 4 deletions .github/actions/setup-go-tests/action.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,7 @@ runs:
sudo apt-get install -y protobuf-compiler

sudo apt-get install -y ${{ env.go_build_dependencies }} ${{ env.go_test_dependencies}}

# Load the apparmor profile for bubblewrap.
sudo ln -s /usr/share/apparmor/extra-profiles/bwrap-userns-restrict /etc/apparmor.d/
sudo apparmor_parser /etc/apparmor.d/bwrap-userns-restrict

echo "::endgroup::"

- name: Install gotestfmt and our wrapper script
Expand Down
5 changes: 3 additions & 2 deletions .github/workflows/qa.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,7 @@ env:
libpam-dev
libpwquality-dev

go_test_dependencies: >-
apparmor-profiles
go_test_dependencies: >-
bubblewrap
cracklib-runtime
git-delta
Expand Down Expand Up @@ -333,8 +332,10 @@ jobs:
# Print executed commands to ease debugging
set -x

echo "::group::Install llvm-symbolizer"
# For llvm-symbolizer
sudo apt-get install -y llvm
echo "::endgroup::"

go test -C ./pam/internal -json -asan -gcflags=all="${GO_GC_FLAGS}" -failfast -timeout ${GO_TESTS_TIMEOUT} ./... | \
gotestfmt --logfile "${AUTHD_TESTS_ARTIFACTS_PATH}/gotestfmt.pam-internal-asan.log" || exit_code=$?
Expand Down
18 changes: 18 additions & 0 deletions cmd/authctl/group/group.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// Package group provides utilities for managing group operations.
package group

import (
"github.com/spf13/cobra"
)

// GroupCmd is a command to perform group-related operations.
var GroupCmd = &cobra.Command{
Use: "group",
Short: "Commands related to groups",
Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error { return cmd.Usage() },
}

func init() {
GroupCmd.AddCommand(setGIDCmd)
}
59 changes: 59 additions & 0 deletions cmd/authctl/group/group_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package group_test

import (
"fmt"
"os"
"os/exec"
"testing"

"github.com/canonical/authd/internal/testutils"
)

var authctlPath string
var daemonPath string

func TestGroupCommand(t *testing.T) {
t.Parallel()

tests := map[string]struct {
args []string
expectedExitCode int
}{
"Usage_message_when_no_args": {expectedExitCode: 0},
"Help_flag": {args: []string{"--help"}, expectedExitCode: 0},

"Error_on_invalid_command": {args: []string{"invalid-command"}, expectedExitCode: 1},
"Error_on_invalid_flag": {args: []string{"--invalid-flag"}, expectedExitCode: 1},
}

for name, tc := range tests {
t.Run(name, func(t *testing.T) {
t.Parallel()

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

func TestMain(m *testing.M) {
var authctlCleanup func()
var err error
authctlPath, authctlCleanup, err = testutils.BuildAuthctl()
if err != nil {
fmt.Fprintf(os.Stderr, "Setup: %v\n", err)
os.Exit(1)
}
defer authctlCleanup()

var daemonCleanup func()
daemonPath, daemonCleanup, err = testutils.BuildAuthdWithExampleBroker()
if err != nil {
fmt.Fprintf(os.Stderr, "Setup: %v\n", err)
os.Exit(1)
}
defer daemonCleanup()

m.Run()
}
77 changes: 77 additions & 0 deletions cmd/authctl/group/set-gid.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package group

import (
"context"
"errors"
"fmt"
"os"
"strconv"

"github.com/canonical/authd/cmd/authctl/internal/client"
"github.com/canonical/authd/cmd/authctl/internal/completion"
"github.com/canonical/authd/internal/proto/authd"
"github.com/spf13/cobra"
)

// setGIDCmd is a command to set the GID of a group managed by authd.
var setGIDCmd = &cobra.Command{
Use: "set-gid <name> <gid>",
Short: "Set the GID of a group managed by authd",
Long: `Set the GID of a group managed by authd to the specified value.

The new GID value must be unique and non-negative.

When a group's GID is changed, any users whose primary group is set to this group
will have their primary group GID updated. The home directories of these users and
files within them owned by the group will be updated to the new GID.

Files outside users' home directories are not updated and must be changed
manually. Note that changing a GID can be unsafe if files on the system are
still owned by the original GID: those files may become accessible to a
different group that is later assigned that GID. To change group ownership of
all files on the system from the old GID to the new GID, run:

sudo chown -R --from :OLD_GID :NEW_GID /

This command requires root privileges.

Examples:
authctl group set-gid staff 30000
authctl group set-gid developers 40000`,
Args: cobra.ExactArgs(2),
ValidArgsFunction: completion.Groups,
RunE: func(cmd *cobra.Command, args []string) error {
name := args[0]
gidStr := args[1]
gid, err := strconv.ParseUint(gidStr, 10, 32)
if err != nil {
// Remove the "strconv.ParseUint: parsing ..." part from the error message
// because it doesn't add any useful information.
if unwrappedErr := errors.Unwrap(err); unwrappedErr != nil {
err = unwrappedErr
}
return fmt.Errorf("failed to parse GID %q: %w", gidStr, err)
}

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

resp, err := client.SetGroupID(context.Background(), &authd.SetGroupIDRequest{
Name: name,
Id: uint32(gid),
Lang: os.Getenv("LANG"),
})
if err != nil {
return err
}

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

return nil
},
}
87 changes: 87 additions & 0 deletions cmd/authctl/group/set-gid_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package group_test

import (
"math"
"os"
"os/exec"
"path/filepath"
"strconv"
"testing"

"github.com/canonical/authd/internal/testutils"
"github.com/stretchr/testify/require"
"google.golang.org/grpc/codes"
)

func TestSetGIDCommand(t *testing.T) {
// We can't run these tests in parallel because the daemon with the example
// broker which we're using here uses userslocking.Z_ForTests_OverrideLocking()
// which makes userslocking.WriteLock() return an error immediately when the lock
// is already held - unlike the normal behavior which tries to acquire the lock
// for 15 seconds before returning an error.

daemonSocket := testutils.StartAuthd(t, daemonPath,
testutils.WithGroupFile(filepath.Join("testdata", "empty.group")),
testutils.WithPreviousDBState("one_user_and_group"),
testutils.WithCurrentUserAsRoot,
)

err := os.Setenv("AUTHD_SOCKET", daemonSocket)
require.NoError(t, err, "Failed to set AUTHD_SOCKET environment variable")

tests := map[string]struct {
args []string
authdUnavailable bool

expectedExitCode int
}{
"Set_group_gid_success": {
args: []string{"set-gid", "group1", "123456"},
expectedExitCode: 0,
},

"Error_when_group_does_not_exist": {
args: []string{"set-gid", "invalidgroup", "123456"},
expectedExitCode: int(codes.NotFound),
},
"Error_when_gid_is_invalid": {
args: []string{"set-gid", "group1", "invalidgid"},
expectedExitCode: 1,
},
"Error_when_gid_is_too_large": {
args: []string{"set-gid", "group1", strconv.Itoa(math.MaxInt32 + 1)},
expectedExitCode: int(codes.Unknown),
},
"Error_when_gid_is_already_taken": {
args: []string{"set-gid", "group1", "0"},
expectedExitCode: int(codes.Unknown),
},
"Error_when_gid_is_negative": {
args: []string{"set-gid", "group1", "-1000"},
expectedExitCode: 1,
},
"Error_when_authd_is_unavailable": {
args: []string{"set-gid", "group1", "123456"},
authdUnavailable: true,
expectedExitCode: int(codes.Unavailable),
},
}

for name, tc := range tests {
t.Run(name, func(t *testing.T) {
if tc.authdUnavailable {
origValue := os.Getenv("AUTHD_SOCKET")
err := os.Setenv("AUTHD_SOCKET", "/non-existent")
require.NoError(t, err, "Failed to set AUTHD_SOCKET environment variable")
t.Cleanup(func() {
err := os.Setenv("AUTHD_SOCKET", origValue)
require.NoError(t, err, "Failed to restore AUTHD_SOCKET environment variable")
})
}

//nolint:gosec // G204 it's safe to use exec.Command with a variable here
cmd := exec.Command(authctlPath, append([]string{"group"}, tc.args...)...)
testutils.CheckCommand(t, cmd, tc.expectedExitCode)
})
}
}
17 changes: 17 additions & 0 deletions cmd/authctl/group/testdata/db/one_user_and_group.db.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
users:
- name: user1
uid: 1111
gid: 11111
gecos: |-
User1 gecos
On multiple lines
dir: /home/user1
shell: /bin/bash
broker_id: broker-id
groups:
- name: group1
gid: 11111
ugid: "12345678"
users_to_groups:
- uid: 1111
gid: 11111
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
Usage:
authctl group [flags]
authctl group [command]

Available Commands:
set-gid Set the GID of a group managed by authd

Flags:
-h, --help help for group

Use "authctl group [command] --help" for more information about a command.

unknown command "invalid-command" for "authctl group"
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
Usage:
authctl group [flags]
authctl group [command]

Available Commands:
set-gid Set the GID of a group managed by authd

Flags:
-h, --help help for group

Use "authctl group [command] --help" for more information about a command.

unknown flag: --invalid-flag
13 changes: 13 additions & 0 deletions cmd/authctl/group/testdata/golden/TestGroupCommand/Help_flag
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
Commands related to groups

Usage:
authctl group [flags]
authctl group [command]

Available Commands:
set-gid Set the GID of a group managed by authd

Flags:
-h, --help help for group

Use "authctl group [command] --help" for more information about a command.
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
Usage:
authctl group [flags]
authctl group [command]

Available Commands:
set-gid Set the GID of a group managed by authd

Flags:
-h, --help help for group

Use "authctl group [command] --help" for more information about a command.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Error: connection error: desc = "transport: Error while dialing: dial unix /non-existent: connect: no such file or directory"
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Error: GID 0 already exists
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
failed to parse GID "invalidgid": invalid syntax
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
Usage:
authctl group set-gid <name> <gid> [flags]

Flags:
-h, --help help for set-gid

unknown shorthand flag: '1' in -1000
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Error: GID 2147483648 is too large to convert to int32
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Error: group "invalidgroup" not found
Loading