Skip to content

Commit

Permalink
Generate unique UIDs and GIDs (#663)
Browse files Browse the repository at this point in the history
These changes have already partly been reviewed in
canonical/authd-private#9.

Closes #509 
UDENG-5416
UDENG-4352
  • Loading branch information
3v1n0 authored Jan 17, 2025
2 parents 154aab9 + eaeae48 commit aba1e3c
Show file tree
Hide file tree
Showing 134 changed files with 2,556 additions and 592 deletions.
6 changes: 3 additions & 3 deletions cmd/authd/integrationtests.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import (
"strings"

"github.com/ubuntu/authd/internal/services/permissions"
"github.com/ubuntu/authd/internal/users/localgroups"
"github.com/ubuntu/authd/internal/users/localentries"
)

// load any behaviour modifiers from env variable.
Expand All @@ -21,6 +21,6 @@ func init() {
if gpasswdArgs == "" || grpFilePath == "" {
panic("AUTHD_INTEGRATIONTESTS_GPASSWD_ARGS and AUTHD_INTEGRATIONTESTS_GPASSWD_GRP_FILE_PATH must be set")
}
localgroups.Z_ForTests_SetGpasswdCmd(strings.Split(gpasswdArgs, " "))
localgroups.Z_ForTests_SetGroupPath(grpFilePath)
localentries.Z_ForTests_SetGpasswdCmd(strings.Split(gpasswdArgs, " "))
localentries.Z_ForTests_SetGroupPath(grpFilePath)
}
11 changes: 5 additions & 6 deletions examplebroker/broker.go
Original file line number Diff line number Diff line change
Expand Up @@ -930,12 +930,11 @@ func userInfoFromName(name string) string {
Groups []groupJSONInfo
Gecos string
}{
Name: name,
UUID: "uuid-" + name,
Home: "/home/" + name,
Shell: "/usr/bin/bash",
Groups: []groupJSONInfo{{Name: "group-" + name, UGID: "ugid-" + name}},
Gecos: "gecos for " + name,
Name: name,
UUID: "uuid-" + name,
Home: "/home/" + name,
Shell: "/usr/bin/bash",
Gecos: "gecos for " + name,
}

switch name {
Expand Down
22 changes: 6 additions & 16 deletions internal/brokers/broker.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import (
"github.com/godbus/dbus/v5"
"github.com/ubuntu/authd/internal/brokers/auth"
"github.com/ubuntu/authd/internal/brokers/layouts"
"github.com/ubuntu/authd/internal/users"
"github.com/ubuntu/authd/internal/users/types"
"github.com/ubuntu/authd/log"
"github.com/ubuntu/decorate"
"golang.org/x/exp/slices"
Expand Down Expand Up @@ -184,7 +184,7 @@ func (b Broker) IsAuthenticated(ctx context.Context, sessionID, authenticationDa
return "", "", err
}

d, err := json.Marshal(info.UserInfo)
d, err := json.Marshal(info)
if err != nil {
return "", "", fmt.Errorf("can't marshal UserInfo: %v", err)
}
Expand Down Expand Up @@ -317,22 +317,17 @@ func (b Broker) parseSessionID(sessionID string) string {
return strings.TrimPrefix(sessionID, fmt.Sprintf("%s-", b.ID))
}

type userInfo struct {
users.UserInfo
UUID string
}

// unmarshalUserInfo tries to unmarshal the rawMsg into a userinfo.
func unmarshalUserInfo(rawMsg json.RawMessage) (userInfo, error) {
var u userInfo
func unmarshalUserInfo(rawMsg json.RawMessage) (types.UserInfo, error) {
var u types.UserInfo
if err := json.Unmarshal(rawMsg, &u); err != nil {
return userInfo{}, fmt.Errorf("message is not JSON formatted: %v", err)
return types.UserInfo{}, fmt.Errorf("message is not JSON formatted: %v", err)
}
return u, nil
}

// validateUserInfo checks if the specified userinfo is valid.
func validateUserInfo(uInfo userInfo) (err error) {
func validateUserInfo(uInfo types.UserInfo) (err error) {
defer decorate.OnError(&err, "provided userinfo is invalid")

// Validate username. We don't want to check here if it matches the username from the request, because it's the
Expand All @@ -350,11 +345,6 @@ func validateUserInfo(uInfo userInfo) (err error) {
return fmt.Errorf("value provided for shell is not an absolute path: %s", uInfo.Shell)
}

// Validate UUID
if uInfo.UUID == "" {
return errors.New("empty UUID")
}

// Validate groups
for _, g := range uInfo.Groups {
if g.Name == "" {
Expand Down
1 change: 0 additions & 1 deletion internal/brokers/broker_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -229,7 +229,6 @@ func TestIsAuthenticated(t *testing.T) {
"Error when broker returns invalid userinfo": {sessionID: "IA_invalid_userinfo"},
"Error when broker returns userinfo with empty username": {sessionID: "IA_info_empty_user_name"},
"Error when broker returns userinfo with empty group name": {sessionID: "IA_info_empty_group_name"},
"Error when broker returns userinfo with empty UUID": {sessionID: "IA_info_empty_uuid"},
"Error when broker returns userinfo with invalid homedir": {sessionID: "IA_info_invalid_home"},
"Error when broker returns userinfo with invalid shell": {sessionID: "IA_info_invalid_shell"},
"Error when broker returns data on auth.Next": {sessionID: "IA_next_with_data"},
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FIRST CALL:
access:
data:
err: message is not JSON formatted: json: cannot unmarshal string into Go value of type brokers.userInfo
err: message is not JSON formatted: json: cannot unmarshal string into Go value of type types.UserInfo

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1 +1 @@
{"Name":"success","UID":82162,"Gecos":"gecos for success","Dir":"/home/success","Shell":"/bin/sh/success","Groups":[{"Name":"success","GID":82162,"UGID":""},{"Name":"group-success","GID":81868,"UGID":""}],"UUID":""}
{"Name":"success","UID":82162,"Gecos":"gecos for success","Dir":"/home/success","Shell":"/bin/sh/success","Groups":[{"Name":"success","GID":82162,"UGID":""},{"Name":"group-success","GID":81868,"UGID":""}]}
Original file line number Diff line number Diff line change
@@ -1 +1 @@
{"Name":"success","UID":82162,"Gecos":"gecos for success","Dir":"/home/success","Shell":"/bin/sh/success","Groups":[{"Name":"success","GID":82162,"UGID":""},{"Name":"group-success","GID":81868,"UGID":""}],"UUID":""}
{"Name":"success","UID":82162,"Gecos":"gecos for success","Dir":"/home/success","Shell":"/bin/sh/success","Groups":[{"Name":"success","GID":82162,"UGID":""},{"Name":"group-success","GID":81868,"UGID":""}]}
Original file line number Diff line number Diff line change
@@ -1 +1 @@
{"Name":"","UID":82162,"Gecos":"gecos for success","Dir":"/home/success","Shell":"/bin/sh/success","Groups":[{"Name":"success","GID":82162,"UGID":""},{"Name":"group-success","GID":81868,"UGID":""}],"UUID":""}
{"Name":"","UID":82162,"Gecos":"gecos for success","Dir":"/home/success","Shell":"/bin/sh/success","Groups":[{"Name":"success","GID":82162,"UGID":""},{"Name":"group-success","GID":81868,"UGID":""}]}
Original file line number Diff line number Diff line change
@@ -1 +1 @@
{"Name":"","UID":82162,"Gecos":"gecos for success","Dir":"/home/success","Shell":"/bin/sh/success","Groups":[{"Name":"success","GID":82162,"UGID":""},{"Name":"group-success","GID":81868,"UGID":""}],"UUID":""}
{"Name":"","UID":82162,"Gecos":"gecos for success","Dir":"/home/success","Shell":"/bin/sh/success","Groups":[{"Name":"success","GID":82162,"UGID":""},{"Name":"group-success","GID":81868,"UGID":""}]}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "user-pre-check",
"uuid": "uuid-user-pre-check",
"uuid": "",
"gecos": "gecos for user-pre-check",
"dir": "/home/user-pre-check",
"shell": "/bin/sh/user-pre-check",
Expand Down
84 changes: 84 additions & 0 deletions internal/errno/errno_c.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
// Package errno provide utilities to use C errno from the Go side.
package errno

/*
#include <errno.h>
#include <string.h>
static void unset_errno(void) {
errno = 0;
}
static int get_errno(void) {
return errno;
}
static void set_errno(int e) {
errno = e;
}
*/
import "C"

import (
"errors"
"sync"
)

// Error is the type for the errno error.
type Error C.int

func (errno Error) Error() string {
return C.GoString(C.strerror(C.int(errno)))
}

const (
// ErrNoEnt is the errno ENOENT.
ErrNoEnt Error = C.ENOENT
// ErrSrch is the errno ESRCH.
ErrSrch Error = C.ESRCH
// ErrBadf is the errno EBADF.
ErrBadf Error = C.EBADF
// ErrPerm is the errno EPERM.
ErrPerm Error = C.EPERM
)

// All these functions are expected to be called while this mutex is locked.
var mu sync.Mutex

// Lock the usage of errno.
func Lock() {
mu.Lock()
C.unset_errno()
}

// Unlock unlocks the errno package for being re-used.
func Unlock() {
C.unset_errno()
mu.Unlock()
}

// Get gets the current errno as [Error].
func Get() error {
if mu.TryLock() {
mu.Unlock()
panic("Using errno without locking!")
}
if errno := C.get_errno(); errno != 0 {
return Error(errno)
}
return nil
}

func set(err error) {
if mu.TryLock() {
mu.Unlock()
panic("Using errno without locking!")
}

var e Error
if err != nil && !errors.As(err, &e) {
panic("Not a valid errno value")
}
C.set_errno(C.int(e))
}
64 changes: 64 additions & 0 deletions internal/errno/errno_c_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package errno

import (
"errors"
"testing"

"github.com/stretchr/testify/require"
)

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

Lock()
defer Unlock()

require.NoError(t, Get())
}

func TestGetWithoutLocking(t *testing.T) {
// This test can't be parallel, since other tests may locking meanwhile.

require.PanicsWithValue(t, "Using errno without locking!", func() { _ = Get() })
}

func TestSetWithoutLocking(t *testing.T) {
// This test can't be parallel, since other tests may locking meanwhile.

require.PanicsWithValue(t, "Using errno without locking!", func() { set(nil) })
}

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

Lock()
defer Unlock()

require.PanicsWithValue(t, "Not a valid errno value", func() { set(errors.New("invalid")) })
}

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

testCases := map[string]struct {
err error
}{
"No error": {},
"No such file or directory": {err: ErrNoEnt},
"No such process": {err: ErrSrch},
"Bad file descriptor": {err: ErrBadf},
"Operation not permitted": {err: ErrPerm},
}
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
t.Parallel()

Lock()
defer Unlock()

set(tc.err)
t.Logf("Checking for error %v", tc.err)
require.ErrorIs(t, Get(), tc.err, "Errorno is not matching")
})
}
}
24 changes: 15 additions & 9 deletions internal/services/nss/nss.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"github.com/ubuntu/authd/internal/proto/authd"
"github.com/ubuntu/authd/internal/services/permissions"
"github.com/ubuntu/authd/internal/users"
"github.com/ubuntu/authd/internal/users/types"
"github.com/ubuntu/authd/log"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
Expand Down Expand Up @@ -180,19 +181,23 @@ func (s Service) userPreCheck(ctx context.Context, username string) (pwent *auth
return nil, fmt.Errorf("user %q is not known by any broker", username)
}

var u users.UserEntry
var u types.UserEntry
if err := json.Unmarshal([]byte(userinfo), &u); err != nil {
return nil, fmt.Errorf("user data from broker invalid: %v", err)
}
// We need to generate the ID for the user, as its business logic is authd responsibility, not the broker's.
u.UID = s.userManager.GenerateUID(u.Name)
u.GID = s.userManager.GenerateGID(u.Name)

// Register a temporary user with a unique UID. If the user authenticates successfully, the user will be added to
// the database with the same UID.
u.UID, err = s.userManager.RegisterUserPreAuth(u.Name)
if err != nil {
return nil, fmt.Errorf("failed to add temporary record for user %q: %v", username, err)
}

return nssPasswdFromUsersPasswd(u), nil
}

// nssPasswdFromUsersPasswd returns a PasswdEntry from users.UserEntry.
func nssPasswdFromUsersPasswd(u users.UserEntry) *authd.PasswdEntry {
func nssPasswdFromUsersPasswd(u types.UserEntry) *authd.PasswdEntry {
return &authd.PasswdEntry{
Name: u.Name,
Passwd: "x",
Expand All @@ -205,17 +210,18 @@ func nssPasswdFromUsersPasswd(u users.UserEntry) *authd.PasswdEntry {
}

// nssGroupFromUsersGroup returns a GroupEntry from users.GroupEntry.
func nssGroupFromUsersGroup(g users.GroupEntry) *authd.GroupEntry {
func nssGroupFromUsersGroup(g types.GroupEntry) *authd.GroupEntry {
return &authd.GroupEntry{
Name: g.Name,
Passwd: "x",
Name: g.Name,
// We set the passwd field here because we use it to store the identifier of a temporary group record
Passwd: g.Passwd,
Gid: g.GID,
Members: g.Users,
}
}

// nssShadowFromUsersShadow returns a ShadowEntry from users.ShadowEntry.
func nssShadowFromUsersShadow(u users.ShadowEntry) *authd.ShadowEntry {
func nssShadowFromUsersShadow(u types.ShadowEntry) *authd.ShadowEntry {
return &authd.ShadowEntry{
Name: u.Name,
Passwd: "x",
Expand Down
15 changes: 13 additions & 2 deletions internal/services/nss/nss_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@ import (
"github.com/ubuntu/authd/internal/testutils/golden"
"github.com/ubuntu/authd/internal/users"
"github.com/ubuntu/authd/internal/users/cache"
localgroupstestutils "github.com/ubuntu/authd/internal/users/localgroups/testutils"
"github.com/ubuntu/authd/internal/users/idgenerator"
localgroupstestutils "github.com/ubuntu/authd/internal/users/localentries/testutils"
"github.com/ubuntu/authd/log"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/credentials/insecure"
Expand Down Expand Up @@ -330,7 +332,14 @@ func newUserManagerForTests(t *testing.T, sourceDB string) *users.Manager {
}
cache.Z_ForTests_CreateDBFromYAML(t, filepath.Join("testdata", sourceDB), cacheDir)

m, err := users.NewManager(users.DefaultConfig, cacheDir)
managerOpts := []users.Option{
users.WithIDGenerator(&idgenerator.IDGeneratorMock{
UIDsToGenerate: []uint32{1234},
GIDsToGenerate: []uint32{1234},
}),
}

m, err := users.NewManager(users.DefaultConfig, cacheDir, managerOpts...)
require.NoError(t, err, "Setup: could not create user manager")

t.Cleanup(func() { _ = m.Stop() })
Expand Down Expand Up @@ -392,6 +401,8 @@ func TestMain(m *testing.M) {
os.Exit(m.Run())
}

log.SetLevel(log.DebugLevel)

cleanup, err := testutils.StartSystemBusMock()
if err != nil {
fmt.Println("Error starting system bus mock:", err)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
name: group1
passwd: x
passwd: ""
gid: 11111
members:
- user1
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
name: group1
passwd: x
passwd: ""
gid: 11111
members:
- user1
Loading

0 comments on commit aba1e3c

Please sign in to comment.