Skip to content

Commit edc6202

Browse files
tests(pam/testutils): Integration tests for the PAM CLI (#109)
This adds integration tests for the PAM module and moves some code around to avoid copying and maintain consistency between our integration tests. The golden files are `ASCII` representations of terminal screenshots, so don't get scared by the number of lines changed (I promise it will be okay). UDENG-1456
2 parents 67805db + 10d92c3 commit edc6202

46 files changed

Lines changed: 5650 additions & 152 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/qa.yaml

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ on:
1010
env:
1111
apt_deps: >-
1212
libpam0g-dev
13+
14+
test_apt_deps: >-
15+
ffmpeg
1316
# In Rust the grpc stubs are generated at build time
1417
# so we always need to install the protobuf compilers
1518
# when building the NSS crate.
@@ -31,6 +34,11 @@ jobs:
3134
with:
3235
golangci-lint-configfile: ".golangci.yaml"
3336
tools-directory: "tools"
37+
env:
38+
# The PAM module generator relies on this environment variable to define the output directory for the modules.
39+
# If it does not exist, it uses a default value but emits a warning which fails the action so we need to
40+
# define the variable for the action to pass successfully.
41+
AUTHD_PAM_MODULES_PATH: "/tmp/authd_pam_modules"
3442
- name: Build cmd/authd with withexamplebroker tag
3543
run: |
3644
set -eu
@@ -89,11 +97,21 @@ jobs:
8997
sudo DEBIAN_FRONTEND=noninteractive apt update
9098
9199
# The integration tests build the NSS crate, so we need the cargo build dependencies in order to run them.
92-
sudo DEBIAN_FRONTEND=noninteractive apt install -y ${{ env.apt_deps }} ${{ env.protobuf_compilers}}
100+
sudo DEBIAN_FRONTEND=noninteractive apt install -y ${{ env.apt_deps }} ${{ env.protobuf_compilers}} ${{ env.test_apt_deps }}
93101
- uses: actions/checkout@v4
94102
- uses: actions/setup-go@v5
95103
with:
96104
go-version-file: go.mod
105+
- name: Install VHS and ttyd for integration tests
106+
run: |
107+
set -eu
108+
go install github.com/charmbracelet/vhs@latest
109+
110+
# VHS requires ttyd >= 1.7.2 to work properly.
111+
wget https://github.com/tsl0922/ttyd/releases/download/1.7.4/ttyd.x86_64
112+
chmod +x ttyd.x86_64
113+
sudo mv ttyd.x86_64 /usr/bin/ttyd
114+
97115
- uses: actions-rs/toolchain@v1
98116
with:
99117
profile: minimal
@@ -137,7 +155,7 @@ jobs:
137155
CGO_CFLAGS: "-O0 -g3 -fno-omit-frame-pointer"
138156
run: |
139157
# Use `-dwarflocationlists` to give ASAN a better time to unwind the stack trace
140-
go test -C pam -asan -gcflags="-dwarflocationlists=true" ./...
158+
go test -asan -gcflags="-dwarflocationlists=true" ./pam/pam_test ./pam/gdm/
141159
- name: Upload coverage to Codecov
142160
uses: codecov/codecov-action@v3
143161
with:

CONTRIBUTING.md

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,20 @@ These are mostly guidelines, not rules. Use your best judgment, and feel free to
88

99
## Quicklinks
1010

11-
* [Code of Conduct](#code-of-conduct)
12-
* [Getting Started](#getting-started)
13-
* [Issues](#issues)
14-
* [Pull Requests](#pull-requests)
15-
* [Contributing to the code](#contributing-to-the-code)
16-
* [Contributor License Agreement](#contributor-license-agreement)
17-
* [Getting Help](#getting-help)
11+
- [Contributing to authd](#contributing-to-authd)
12+
- [Quicklinks](#quicklinks)
13+
- [Code of Conduct](#code-of-conduct)
14+
- [Getting Started](#getting-started)
15+
- [Issues](#issues)
16+
- [Pull Requests](#pull-requests)
17+
- [Contributing to the code](#contributing-to-the-code)
18+
- [Required dependencies](#required-dependencies)
19+
- [Building and running the binaries](#building-and-running-the-binaries)
20+
- [About the testsuite](#about-the-testsuite)
21+
- [Tests with dependencies](#tests-with-dependencies)
22+
- [Code style](#code-style)
23+
- [Contributor License Agreement](#contributor-license-agreement)
24+
- [Getting Help](#getting-help)
1825

1926
## Code of Conduct
2027

@@ -79,6 +86,15 @@ TODO
7986

8087
The test suite must pass before merging the PR to our main branch. Any new feature, change or fix must be covered by corresponding tests.
8188

89+
#### Tests with dependencies
90+
91+
Some tests, e.g. the [PAM CLI tests](https://github.com/ubuntu/authd/blob/5ba54c0a573f34e99782fe624b090ab229798fc3/pam/integration-tests/integration_test.go#L21), use external tools such as [vhs](https://github.com/charmbracelet/vhs)
92+
to record and run the tape files needed for the tests. Those tools are not included in the project dependencies and must be installed manually.
93+
94+
For more information about their usage, refer to the following documentations:
95+
96+
- [vhs](https://github.com/charmbracelet/vhs?tab=readme-ov-file#tutorial)
97+
8298
### Code style
8399

84100
This project follow the Go code-style. For more informative information about the code style in use, please check <https://google.github.io/styleguide/go/>.

debian/rules

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,10 @@ export CARGO_HOME = $(CURDIR)/debian/cargo_home
2929
# Needed by the pam module loader
3030
export AUTHD_PAM_MODULES_PATH = /usr/lib/$(DEB_TARGET_GNU_TYPE)/security
3131

32+
# Skip some tests that rely on external dependencies when building package:
33+
# they need external commands (e.g. `vhs`) that are not available in the build environment.
34+
export AUTHD_SKIP_EXTERNAL_DEPENDENT_TESTS=1
35+
3236
%:
3337
dh $@ --buildsystem=golang --with=golang,apport
3438

debian/tests/control

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
Test-Command: go test -v -mod=vendor ./...
1+
Test-Command: AUTHD_SKIP_EXTERNAL_DEPENDENT_TESTS=1 go test -v -mod=vendor ./...
22
Restrictions: allow-stderr
33
Depends: @builddeps@

internal/brokers/examplebroker/broker.go

Lines changed: 45 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import (
1010
"errors"
1111
"fmt"
1212
"html/template"
13-
"math/rand"
1413
"sort"
1514
"strings"
1615
"sync"
@@ -62,11 +61,12 @@ type Broker struct {
6261

6362
var (
6463
exampleUsers = map[string]struct{}{
65-
"user1": {},
66-
"user2": {},
67-
"user-mfa": {},
68-
"user-needs-reset": {},
69-
"user-can-reset": {},
64+
"user1": {},
65+
"user2": {},
66+
"user-mfa": {},
67+
"user-needs-reset": {},
68+
"user-can-reset": {},
69+
"user-local-groups": {},
7070
}
7171
)
7272

@@ -109,6 +109,8 @@ func (b *Broker) NewSession(ctx context.Context, username, lang string) (session
109109
case "user-mfa-with-reset":
110110
info.neededAuthSteps = 3
111111
info.pwdChange = canReset
112+
case "user-unexistent":
113+
return "", "", fmt.Errorf("user %q does not exist", username)
112114
}
113115

114116
b.currentSessionsMu.Lock()
@@ -370,10 +372,7 @@ func (b *Broker) SelectAuthenticationMode(ctx context.Context, sessionID, authen
370372
// start transaction with fideo device
371373
case "qrcodewithtypo":
372374
// generate the url and finish the prompt on the fly.
373-
//nolint:gosec // this is some example code not shipped in production
374-
i := rand.Intn(3)
375-
contents := []string{"https://ubuntu.com", "https://ubuntu-fr.org", "https://canonical.com"}
376-
uiLayoutInfo["content"] = contents[i]
375+
uiLayoutInfo["content"] = "https://ubuntu.com"
377376
uiLayoutInfo["label"] = uiLayoutInfo["label"] + "1337"
378377
}
379378

@@ -477,7 +476,7 @@ func (b *Broker) handleIsAuthenticated(ctx context.Context, sessionInfo sessionI
477476
}
478477
// Send notification to phone1 and wait on server signal to return if OK or not
479478
select {
480-
case <-time.After(5 * time.Second):
479+
case <-time.After(2 * time.Second):
481480
case <-ctx.Done():
482481
return responses.AuthCancelled, "", nil
483482
}
@@ -502,7 +501,7 @@ func (b *Broker) handleIsAuthenticated(ctx context.Context, sessionInfo sessionI
502501

503502
// simulate direct exchange with the FIDO device
504503
select {
505-
case <-time.After(5 * time.Second):
504+
case <-time.After(2 * time.Second):
506505
case <-ctx.Done():
507506
return responses.AuthCancelled, "", nil
508507
}
@@ -513,7 +512,7 @@ func (b *Broker) handleIsAuthenticated(ctx context.Context, sessionInfo sessionI
513512
}
514513
// Simulate connexion with remote server to check that the correct code was entered
515514
select {
516-
case <-time.After(4 * time.Second):
515+
case <-time.After(2 * time.Second):
517516
case <-ctx.Done():
518517
return responses.AuthCancelled, "", nil
519518
}
@@ -540,7 +539,7 @@ func (b *Broker) handleIsAuthenticated(ctx context.Context, sessionInfo sessionI
540539
}
541540
}
542541

543-
if _, exists := exampleUsers[sessionInfo.username]; !exists {
542+
if _, exists := exampleUsers[sessionInfo.username]; !exists && !strings.HasPrefix(sessionInfo.username, "user-integration") {
544543
return responses.AuthDenied, `{"message": "user not found"}`, nil
545544
}
546545
return responses.AuthGranted, fmt.Sprintf(`{"userinfo": %s}`, userInfoFromName(sessionInfo.username)), nil
@@ -660,21 +659,43 @@ func (b *Broker) updateSession(sessionID string, info sessionInfo) error {
660659

661660
// userInfoFromName transform a given name to the strinfigy userinfo string.
662661
func userInfoFromName(name string) string {
663-
user := struct {
662+
type groupJSONInfo struct {
664663
Name string
665-
}{Name: name}
664+
UGID string
665+
}
666666

667+
user := struct {
668+
Name string
669+
UUID string
670+
Home string
671+
Shell string
672+
Groups []groupJSONInfo
673+
Gecos string
674+
}{
675+
Name: name,
676+
UUID: "uuid-" + name,
677+
Home: "/home/" + name,
678+
Shell: "/usr/bin/bash",
679+
Groups: []groupJSONInfo{{Name: "group-" + name, UGID: "ugid-" + name}},
680+
Gecos: "gecos for " + name,
681+
}
682+
683+
if name == "user-local-groups" {
684+
user.Groups = append(user.Groups, groupJSONInfo{Name: "localgroup", UGID: ""})
685+
}
686+
687+
// only used for tests, we can ignore the template execution error as the returned data will be failing.
667688
var buf bytes.Buffer
668-
669-
// only used for the example, we can ignore the template execution error as the returned data will be failing.
670689
_ = template.Must(template.New("").Parse(`{
671690
"name": "{{.Name}}",
672-
"uuid": "uuid-{{.Name}}",
673-
"gecos": "gecos for {{.Name}}",
674-
"dir": "/home/{{.Name}}",
675-
"shell": "/usr/bin/bash",
676-
"avatar": "avatar for {{.Name}}",
677-
"groups": [ {"name": "group-{{.Name}}", "ugid": "group-{{.Name}}"}, {"name": "sudo"} ]
691+
"uuid": "{{.UUID}}",
692+
"gecos": "{{.Gecos}}",
693+
"dir": "{{.Home}}",
694+
"shell": "{{.Shell}}",
695+
"groups": [ {{range $index, $g := .Groups}}
696+
{{- if $index}}, {{end -}}
697+
{"name": "{{.Name}}", "ugid": "{{.UGID}}"}
698+
{{- end}} ]
678699
}`)).Execute(&buf, user)
679700

680701
return buf.String()

0 commit comments

Comments
 (0)