Skip to content
This repository has been archived by the owner on May 2, 2024. It is now read-only.

Command line to get passwd, group and shadow entries from local cache. #56

Merged
merged 21 commits into from Sep 5, 2022
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
f89b24b
Adding String() functions to the structs
denisonbarbosa Aug 31, 2022
fca3b70
Initial implementation of the cli to query the local aad cache.
denisonbarbosa Aug 31, 2022
dcf72d9
Adding some tests for the aad_auth cli, along with their golden files.
denisonbarbosa Aug 31, 2022
27ed7ce
Adding tests for String() in internal/nss/(passwd|group|shadow)
denisonbarbosa Sep 1, 2022
a60a4d4
Improving debug message in errToCStatus
denisonbarbosa Sep 1, 2022
2394948
Adding checks for invalid arguments, -help and usage msg in main func.
denisonbarbosa Sep 1, 2022
621691e
Addressing PR comments and refactoring getent.go
denisonbarbosa Sep 1, 2022
db9c623
Improving the internal tests for fmtOutput
denisonbarbosa Sep 1, 2022
2e5a46c
Merging some previous integration test cases into aadauth_test
denisonbarbosa Sep 1, 2022
dfec000
Adding the golden files for the new test cases and the renamed ones.
denisonbarbosa Sep 1, 2022
09858e8
Fixing a typo in a test case and updating its golden file.
denisonbarbosa Sep 1, 2022
2f10aad
Changing the returned error from trying to get a shadow entry by id.
denisonbarbosa Sep 1, 2022
0234106
Adding test case that tries to get a shadow entry by id.
denisonbarbosa Sep 1, 2022
272e55c
Addressing PR comments part 1: non-test related.
denisonbarbosa Sep 2, 2022
2b4f6cd
Addressing PR comments part 2: test related.
denisonbarbosa Sep 2, 2022
9346c00
Updating golden files.
denisonbarbosa Sep 2, 2022
fc27659
Fixing the error returned for trying to list empty databases.
denisonbarbosa Sep 2, 2022
4155227
Addressing PR comments
denisonbarbosa Sep 5, 2022
0129a7b
Fixing returned values for nss.ErrNotFoundSuccess
denisonbarbosa Sep 5, 2022
236b5cb
Updating golden files to reflect the latest code changes.
denisonbarbosa Sep 5, 2022
d6b7d48
Addressing PR comments
denisonbarbosa Sep 5, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
13 changes: 13 additions & 0 deletions internal/nss/group/group.go
Expand Up @@ -4,6 +4,8 @@ import (
"context"
"errors"
"fmt"
"strconv"
"strings"

"github.com/ubuntu/aad-auth/internal/cache"
"github.com/ubuntu/aad-auth/internal/logger"
Expand All @@ -18,6 +20,17 @@ type Group struct {
members []string /* Members of the group */
}

// String creates a string with Group values.
func (g Group) String() string {
didrocks marked this conversation as resolved.
Show resolved Hide resolved
v := []string{
g.name,
g.passwd,
strconv.FormatUint(uint64(g.gid), 10),
}
v = append(v, g.members...)
return strings.Join(v, ":")
}

// NewByName returns a passwd entry from a name.
func NewByName(ctx context.Context, name string, cacheOpts ...cache.Option) (g Group, err error) {
defer func() {
Expand Down
8 changes: 8 additions & 0 deletions internal/nss/group/group_test.go
Expand Up @@ -248,6 +248,14 @@ func TestRestartIterationWithoutEndingPreviousOne(t *testing.T) {
require.Equal(t, want, got, "Should list all groups from the start")
}

func TestString(t *testing.T) {
g := group.NewTestGroup(2)

got := g.String()
want := testutils.LoadAndUpdateFromGolden(t, got)
require.Equal(t, want, got, "Group strings must match")
}

func TestMain(m *testing.M) {
testutils.InstallUpdateFlag()
flag.Parse()
Expand Down
1 change: 1 addition & 0 deletions internal/nss/group/testdata/golden/TestString
@@ -0,0 +1 @@
testusername@domain.com:x:2345:testusername@domain.com:testusername-1@domain.com
17 changes: 17 additions & 0 deletions internal/nss/passwd/passwd.go
Expand Up @@ -4,6 +4,8 @@ import (
"context"
"errors"
"fmt"
"strconv"
"strings"

"github.com/ubuntu/aad-auth/internal/cache"
"github.com/ubuntu/aad-auth/internal/logger"
Expand All @@ -21,6 +23,21 @@ type Passwd struct {
shell string /* shell program */
}

// String creates a string with Passwd values.
func (p Passwd) String() string {
v := []string{
p.name,
p.passwd,
strconv.FormatUint(uint64(p.uid), 10),
strconv.FormatUint(uint64(p.gid), 10),
p.gecos,
p.dir,
p.shell,
}

return strings.Join(v, ":")
}

// NewByName returns a passwd entry from a name.
func NewByName(ctx context.Context, name string, cacheOpts ...cache.Option) (p Passwd, err error) {
defer func() {
Expand Down
8 changes: 8 additions & 0 deletions internal/nss/passwd/passwd_test.go
Expand Up @@ -247,6 +247,14 @@ func TestRestartIterationWithoutEndingPreviousOne(t *testing.T) {
require.Equal(t, want, got, "Should list all users from the start")
}

func TestString(t *testing.T) {
p := passwd.NewTestPasswd()

got := p.String()
want := testutils.LoadAndUpdateFromGolden(t, got)
require.Equal(t, want, got, "Passwd strings must match")
}

func TestMain(m *testing.M) {
testutils.InstallUpdateFlag()
flag.Parse()
Expand Down
1 change: 1 addition & 0 deletions internal/nss/passwd/testdata/golden/TestString
@@ -0,0 +1 @@
testusername@domain.com:x:1234:2345::/home/testusername@domain.com:/bin/bash
17 changes: 17 additions & 0 deletions internal/nss/shadow/shadow.go
Expand Up @@ -4,6 +4,8 @@ import (
"context"
"errors"
"fmt"
"strconv"
"strings"

"github.com/ubuntu/aad-auth/internal/cache"
"github.com/ubuntu/aad-auth/internal/logger"
Expand All @@ -22,6 +24,21 @@ type Shadow struct {
expire int /* Number of days since 1970-01-01 until account expires. */
}

// String creates a string with Shadow values.
func (s Shadow) String() string {
v := []string{
s.name,
s.passwd,
strconv.Itoa(s.lstchg),
strconv.Itoa(s.min),
strconv.Itoa(s.max),
strconv.Itoa(s.warn),
strconv.Itoa(s.inact),
strconv.Itoa(s.expire),
}
return strings.Join(v, ":")
}

// NewByName returns a passwd entry from a name.
func NewByName(ctx context.Context, name string, cacheOpts ...cache.Option) (s Shadow, err error) {
defer func() {
Expand Down
8 changes: 8 additions & 0 deletions internal/nss/shadow/shadow_test.go
Expand Up @@ -202,6 +202,14 @@ func TestRestartIterationWithoutEndingPreviousOne(t *testing.T) {
require.Equal(t, want, got, "Should list all users from the start")
}

func TestString(t *testing.T) {
s := shadow.NewTestShadow()

got := s.String()
want := testutils.LoadAndUpdateFromGolden(t, got)
require.Equal(t, want, got, "Shadow strings must match")
}

func TestMain(m *testing.M) {
testutils.InstallUpdateFlag()
flag.Parse()
Expand Down
1 change: 1 addition & 0 deletions internal/nss/shadow/testdata/golden/TestString
@@ -0,0 +1 @@
testusername@domain.com:*:-1:-1:-1:-1:-1:-1
125 changes: 125 additions & 0 deletions nss/aad_auth/aadauth_test.go
@@ -0,0 +1,125 @@
package main

import (
"context"
"flag"
"testing"

"github.com/stretchr/testify/require"
"github.com/ubuntu/aad-auth/internal/cache"
"github.com/ubuntu/aad-auth/internal/testutils"
)

func TestGetEnt(t *testing.T) {
noShadow := 0
tests := map[string]struct {
db string
key string
cacheDB string
rootUID int
shadowMode *int

wantErr bool
}{
// List entry by name
"list entry from passwd by name": {db: "passwd", key: "myuser@domain.com"},
"list entry from group by name": {db: "group", key: "myuser@domain.com"},
"list entry from shadow by name": {db: "shadow", key: "myuser@domain.com"},
"try to list entry from shadow by name without access to shadow": {db: "shadow", key: "myuser@domain.com", shadowMode: &noShadow},

// List entry by UID/GID
"list entry from passwd by uid": {db: "passwd", key: "165119649"},
"list entry from group by gid": {db: "group", key: "165119649"},
"try to list entry from shadow by uid": {db: "shadow", key: "165119649"},

// List entries
"list entries in passwd": {db: "passwd"},
"list entries in group": {db: "group"},
"list entries in shadow": {db: "shadow"},

// List entries without access to shadow
"list entries in passwd without access to shadow": {db: "passwd", shadowMode: &noShadow},
"list entries in group without access to shadow": {db: "group", shadowMode: &noShadow},
"try to list shadow without access to shadow": {db: "shadow", shadowMode: &noShadow},
didrocks marked this conversation as resolved.
Show resolved Hide resolved

// Try to list non-existent entry
"try to list non-existent entry in passwd": {db: "passwd", key: "doesnotexist@domain.com"},
"try to list non-existent entry in group": {db: "group", key: "doesnotexist@domain.com"},
"try to list non-existent entry in shadow": {db: "shadow", key: "doesnotexist@domain.com"},

// Try to list without cache
"try to list passwd without any cache": {db: "passwd", cacheDB: "nocache"},
"try to list group without any cache": {db: "group", cacheDB: "nocache"},
"try to list shadow without any cache": {db: "shadow", cacheDB: "nocache"},

// Try to list with empty cache
"try to list passwd with empty cache": {db: "passwd", cacheDB: "empty"},
"try to list group with empty cache": {db: "group", cacheDB: "empty"},
"try to list shadow with empty cache": {db: "shadow", cacheDB: "empty"},

// List local entry without cache
"list local passwd entry without cache": {db: "passwd", cacheDB: "nocache", key: "0"},
"list local group entry without cache": {db: "group", cacheDB: "nocache", key: "0"},
"list local shadow entry without cache": {db: "shadow", cacheDB: "nocache", key: "root"},

// Cleans up old entries
"old entries in passwd are cleaned": {db: "passwd", cacheDB: "db_with_old_users"},
"old entries in group are cleaned": {db: "group", cacheDB: "db_with_old_users"},
"old entries in shadow are cleaned": {db: "shadow", cacheDB: "db_with_old_users"},

// Try to list without permission on cache
"try to list passwd without permission on cache": {db: "passwd", rootUID: 4242},
"try to list group without permission on cache": {db: "group", rootUID: 4242},
"try to list shadow without permission on cache": {db: "shadow", rootUID: 4242},

// Error when trying to list from unsupported database
"error trying to list entry by name from unsupported db": {db: "unsupported", key: "myuser@domain.com", wantErr: true},
"error trying to list unsupported db": {db: "unsupported", wantErr: true},
}

for name, tc := range tests {
tc := tc
t.Run(name, func(t *testing.T) {
uid, gid := testutils.GetCurrentUIDGID(t)
if tc.rootUID != 0 {
uid = tc.rootUID
}

cacheDir := t.TempDir()
switch tc.cacheDB {
case "":
testutils.PrepareDBsForTests(t, cacheDir, "users_in_db")
case "db_with_old_users":
testutils.PrepareDBsForTests(t, cacheDir, tc.cacheDB)
case "empty":
testutils.NewCacheForTests(t, cacheDir)
case "nocache":
break
default:
t.Fatalf("Unexpected value used for cacheDB: %q", tc.cacheDB)
}

opts := []cache.Option{cache.WithCacheDir(cacheDir), cache.WithRootUID(uid), cache.WithRootGID(gid), cache.WithShadowGID(gid)}

if tc.shadowMode != nil {
opts = append(opts, cache.WithShadowMode(*tc.shadowMode))
}

got, err := Getent(context.Background(), tc.db, tc.key, opts...)
if tc.wantErr {
require.Error(t, err, "Expected an error but got none.")
return
}
require.NoError(t, err, "Expected no error but got one.")

want := testutils.LoadAndUpdateFromGolden(t, got)
require.Equal(t, want, got, "Output must match")
})
}
}

func TestMain(m *testing.M) {
testutils.InstallUpdateFlag()
flag.Parse()
m.Run()
}
51 changes: 51 additions & 0 deletions nss/aad_auth/error_c.go
@@ -0,0 +1,51 @@
package main

/*
#include <errno.h>
#include <nss.h>

typedef enum nss_status nss_status;
*/
import "C"
import (
"context"
"errors"

"github.com/ubuntu/aad-auth/internal/logger"
"github.com/ubuntu/aad-auth/internal/nss"
)

// errToCStatus converts our Go errors to corresponding nss status returned code and errno.
// If err is nil, it returns a success.
func errToCStatus(ctx context.Context, err error) (nssStatus, errno int) {
nssStatus = C.NSS_STATUS_SUCCESS

switch {
case errors.Is(err, nss.ErrTryAgainEAgain):
nssStatus = C.NSS_STATUS_TRYAGAIN
errno = C.EAGAIN
case errors.Is(err, nss.ErrTryAgainERange):
nssStatus = C.NSS_STATUS_TRYAGAIN
errno = C.ERANGE
case errors.Is(err, nss.ErrUnavailableENoEnt):
nssStatus = C.NSS_STATUS_UNAVAIL
errno = C.ENOENT
case errors.Is(err, nss.ErrNotFoundENoEnt):
nssStatus = C.NSS_STATUS_NOTFOUND
errno = C.ENOENT
case errors.Is(err, nss.ErrNotFoundSuccess):
nssStatus = C.NSS_STATUS_SUCCESS
errno = C.ENOENT
case err != nil: // Unexpected returned error
nssStatus = C.NSS_STATUS_SUCCESS
errno = C.EINVAL
}

if err != nil {
logger.Debug(ctx, "Returning to NSS error: %d with errno: %d", nssStatus, errno)
} else {
logger.Debug(ctx, "Returning NSS STATUS SUCCESS (%d) with errno: %d", nssStatus, errno)
didrocks marked this conversation as resolved.
Show resolved Hide resolved
}

didrocks marked this conversation as resolved.
Show resolved Hide resolved
return nssStatus, errno
}