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
6 changes: 6 additions & 0 deletions .github/workflows/e2e-tests-provision-and-run.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@ on:
type: string
required: false
default: ''
authd-ppa:
description: 'If set, use this PPA instead of authd-edge when installing the authd package and its dependencies'
type: string
required: false
default: ''
secrets:
E2E_VM_SSH_PRIV_KEY:
required: true
Expand Down Expand Up @@ -152,4 +157,5 @@ jobs:
ubuntu-version: ${{ inputs.ubuntu-version }}
broker: ${{ matrix.broker }}
broker-snap-channel: ${{ inputs.broker-snap-channel }}
authd-ppa: ${{ inputs.authd-ppa }}
secrets: inherit
6 changes: 6 additions & 0 deletions .github/workflows/e2e-tests-run.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@ on:
required: false
type: string
default: ''
authd-ppa:
description: 'If set, use this PPA instead of authd-edge when installing the authd package and its dependencies'
required: false
type: string
default: ''
secrets:
E2E_VM_SSH_PRIV_KEY:
required: true
Expand Down Expand Up @@ -199,6 +204,7 @@ jobs:
# broker credentials from the environment)
${{ env.E2E_TESTS_DIR }}/vm/provision-authd.sh \
--authd-deb "${{ steps.download-authd-deb.outputs.deb }}" \
${{ inputs.authd-ppa && format('--authd-ppa {0}', inputs.authd-ppa) || '' }} \
--broker-snap "${{ steps.download-broker-snap-from-store.outputs.snap || steps.download-broker-snap-from-oci.outputs.snap }}"

- name: Checkout YARF repo
Expand Down
11 changes: 11 additions & 0 deletions .github/workflows/e2e-tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ on:
- synchronize
- reopened
- labeled
- edited
workflow_dispatch:
inputs:
broker-snap-channel:
Expand Down Expand Up @@ -110,4 +111,14 @@ jobs:
files-hash: ${{ needs.compute-hash.outputs.files-hash }}
force-fresh-image: ${{ github.event_name == 'schedule' }}
broker-snap-channel: ${{ inputs.broker-snap-channel || '' }}
# PRs opt in by adding "e2e-ppa: authd-dev" to their description.
authd-ppa: >-
${{
github.event_name == 'pull_request' &&
contains(
github.event.pull_request.body,
'e2e-ppa: authd-dev'
) &&
'ubuntu-enterprise-desktop/authd-dev' || ''
}}
secrets: inherit
2 changes: 2 additions & 0 deletions debian/authd.service.in
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ Description=authd daemon service
After=authd.socket
Requires=authd.socket
PartOf=authd.socket
# AccountsService starts after this target to populate the GDM user list.
Before=nss-user-lookup.target

[Service]
Type=notify
Expand Down
2 changes: 2 additions & 0 deletions debian/authd.socket
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ Wants=dbus.service
After=dbus.service
Before=nss-user-lookup.target
Wants=nss-user-lookup.target
# Start the daemon before AccountsService queries NSS for its cached users.
Wants=authd.service

[Socket]
ListenStream=/run/authd.sock
Expand Down
2 changes: 1 addition & 1 deletion debian/control
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ Depends: ${shlibs:Depends},
${misc:Depends},
Recommends: ${misc:Recommends},
libpam-modules,
Breaks: gnome-shell (<< 46.3.1-1ubuntu1~24.04.1authd6~)
Breaks: ${authd:gnome-shell-breaks}
Description: ${source:Synopsis}
${source:Extended-Description}
.
Expand Down
13 changes: 13 additions & 0 deletions debian/rules
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,16 @@
include /usr/share/dpkg/pkg-info.mk
include /usr/share/rustc/architecture.mk

ifeq ($(DEB_DISTRIBUTION),noble)
GNOME_SHELL_BREAKS := gnome-shell (<< 46.3.1-1ubuntu1+authd1~24.04.1)
else ifeq ($(DEB_DISTRIBUTION),resolute)
GNOME_SHELL_BREAKS := gnome-shell (<< 50.1-0ubuntu1.1+authd2~26.04.1)
else ifeq ($(DEB_DISTRIBUTION),stonking)
GNOME_SHELL_BREAKS := gnome-shell (<< 50.2-3ubuntu1+authd1~26.10.1)
else
$(error Unsupported distribution "$(DEB_DISTRIBUTION)" for gnome-shell dependency)
endif

# The package name for authd, used only locally to avoid repetitions
AUTHD_GO_PACKAGE := $(shell grep-dctrl -s XS-Go-Import-Path -n - ./debian/control)

Expand Down Expand Up @@ -60,6 +70,9 @@ export BUILT_PAM_LIBS_PATH := obj-$(DEB_HOST_GNU_TYPE)/src/$(AUTHD_GO_PACKAGE)/p
# --without=single-binary can be removed with dh 15.
dh $@ --buildsystem=golang --with=golang,apport --without=single-binary

override_dh_gencontrol:
dh_gencontrol -- -Vauthd:gnome-shell-breaks="$(GNOME_SHELL_BREAKS)"

override_dh_auto_clean:
dh_auto_clean

Expand Down
14 changes: 10 additions & 4 deletions e2e-tests/vm/provision-authd.sh
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ DATA_DIR="${XDG_DATA_HOME:-$HOME/.local/share}/authd-e2e-tests"

usage(){
cat << EOF
Usage: $0 [--config-file <file>] [--release <release>] [--authd-deb <deb>] [--broker-snap <snap>]
Usage: $0 [--config-file <file>] [--release <release>] [--authd-deb <deb>] [--authd-ppa <ppa>] [--broker-snap <snap>]

Options:
--config-file <file> Path to the configuration file (default: config.env)
Expand All @@ -18,6 +18,8 @@ Options:
The existing snapshots will be deleted and recreated with the new installation.
--broker <broker> The broker to install ("authd-google", "authd-msentraid", ...)
--authd-deb <deb> Path to the authd deb file to install (default: install from the edge PPA)
--authd-ppa <ppa> PPA to use instead of authd-edge when installing authd
and its dependencies
--broker-snap <snap> Path to the broker snap file to install (default: install from the edge channel)
-h, --help Show this help message and exit

Expand Down Expand Up @@ -47,6 +49,10 @@ while [[ $# -gt 0 ]]; do
AUTHD_DEB="$2"
shift 2
;;
--authd-ppa)
AUTHD_PPA="$2"
shift 2
;;
--broker-snap)
BROKER_SNAP="$2"
shift 2
Expand Down Expand Up @@ -292,9 +298,9 @@ fi
# Revert to the pre-authd setup snapshot before installing the version to test
restore_snapshot_and_sync_time "$PRE_AUTHD_SNAPSHOT"

# Add the edge PPA. We also need that when installing authd from a deb file,
# because it depends on gnome-shell from the edge PPA.
PPA="ubuntu-enterprise-desktop/authd-edge"
# Add the PPA needed to resolve dependencies for the authd package under test.
# authd-edge remains the default for local and normal CI runs.
PPA="${AUTHD_PPA:-ubuntu-enterprise-desktop/authd-edge}"
$SSH "add-apt-repository -y ppa:${PPA}"

# Configure authd to be verbose. We do this before installing authd to avoid
Expand Down
8 changes: 7 additions & 1 deletion e2e-tests/vm/provision.sh
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,14 @@ CONFIG_FILE="${SCRIPT_DIR}/config.env"

usage(){
cat << EOF
Usage: $0 [--config-file <config file>] [--release <release>] [--broker <broker>] [--authd-deb <deb>] [--broker-snap <snap>] [--force]
Usage: $0 [--config-file <config file>] [--release <release>] [--broker <broker>] [--authd-deb <deb>] [--authd-ppa <ppa>] [--broker-snap <snap>] [--force]

Options:
--config-file <config file> Path to the configuration file (default: config.env)
--release <release> Ubuntu release to provision (e.g. noble, resolute); overrides config file
--broker <broker> The broker to install ("authd-google", "authd-msentraid", ...)
--authd-deb <deb> Path to the authd deb file to install (default: install from the edge PPA)
--authd-ppa <ppa> PPA to use instead of authd-edge when installing authd and its dependencies
--broker-snap <snap> Path to the broker snap file to install (default: install from the edge channel)
--force Force provisioning: remove existing VM and artifacts and create a fresh VM
-h, --help Show this help message and exit
Expand Down Expand Up @@ -45,6 +46,10 @@ while [[ $# -gt 0 ]]; do
AUTHD_DEB="$2"
shift 2
;;
--authd-ppa)
AUTHD_PPA="$2"
shift 2
;;
--broker-snap)
BROKER_SNAP="$2"
shift 2
Expand Down Expand Up @@ -78,5 +83,6 @@ set -x
${RELEASE_ARG:+--release "${RELEASE_ARG}"} \
${BROKER:+--broker "${BROKER}"} \
${AUTHD_DEB:+--authd-deb "${AUTHD_DEB}"} \
${AUTHD_PPA:+--authd-ppa "${AUTHD_PPA}"} \
${BROKER_SNAP:+--broker-snap "${BROKER_SNAP}"} \
${FORCE:+--force}
15 changes: 13 additions & 2 deletions pam/internal/adapter/authmodeselection.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ type authModesReceived struct {

// authModeSelected is the internal event signalling that the an authentication mode has been selected.
type authModeSelected struct {
id string
id string
fromGDM bool
}

// selectAuthMode selects current authentication mode.
Expand All @@ -49,6 +50,15 @@ func selectAuthMode(id string) tea.Cmd {
}
}

func selectGdmAuthMode(id string) tea.Cmd {
return func() tea.Msg {
return authModeSelected{
id: id,
fromGDM: true,
}
}
}

// newAuthModeSelectionModel initializes an empty list with default options of authModeSelectionModel.
func newAuthModeSelectionModel(clientType PamClientType) authModeSelectionModel {
return authModeSelectionModel{
Expand Down Expand Up @@ -209,7 +219,8 @@ func (m authModeSelectionModel) Update(msg tea.Msg) (authModeSelectionModel, tea
}

return m, sendEvent(AuthModeSelected{
ID: msg.id,
ID: msg.id,
fromGDM: msg.fromGDM,
})
}

Expand Down
50 changes: 42 additions & 8 deletions pam/internal/adapter/gdmmodel.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,14 @@ type gdmModel struct {
// further conversation with GDM should happen.
conversationsStopped bool
stoppingConversations bool

// pendingEchoAuthModeID is the auth mode we last told GDM to select and
// whose echo we still expect back. GDM echoes our selection in its next
// poll; acting on that echo would issue a second SelectAuthenticationMode
// RPC (and, for device auth, mint a second device code that orphans the
// in-flight poll). It is consumed (cleared) by the first matching echo, so
// a later genuine re-selection of the same mode is still honored.
pendingEchoAuthModeID string
}

type gdmPollResponse struct {
Expand Down Expand Up @@ -111,7 +119,7 @@ func (m gdmModel) pollGdm() tea.Cmd {
}
}

func (m gdmModel) handlePollResponse(gdmPollResults []*gdm.EventData) tea.Cmd {
func (m gdmModel) handlePollResponse(gdmPollResults []*gdm.EventData) (gdmModel, tea.Cmd) {
if log.IsLevelEnabled(log.DebugLevel) {
for _, result := range gdmPollResults {
log.Debugf(context.TODO(), "GDM poll response: %v", result.SafeString())
Expand All @@ -127,7 +135,7 @@ func (m gdmModel) handlePollResponse(gdmPollResults []*gdm.EventData) tea.Cmd {

case *gdm.EventData_BrokerSelected:
if res.BrokerSelected == nil {
return sendEvent(pamError{status: pam.ErrSystem,
return m, sendEvent(pamError{status: pam.ErrSystem,
msg: "missing broker selected",
})
}
Expand All @@ -137,11 +145,24 @@ func (m gdmModel) handlePollResponse(gdmPollResults []*gdm.EventData) tea.Cmd {

case *gdm.EventData_AuthModeSelected:
if res.AuthModeSelected == nil {
return sendEvent(pamError{
return m, sendEvent(pamError{
status: pam.ErrSystem, msg: "missing auth mode id",
})
}
commands = append(commands, selectAuthMode(res.AuthModeSelected.AuthModeId))
// GDM echoes back the auth mode we just told it to select. Ignore
// that one echo to avoid issuing a duplicate SelectAuthenticationMode
// RPC (which, for device auth, mints a second device code and
// orphans the in-flight poll). This is a one-shot per selection:
// a later genuine re-selection of the same mode (the user picking
// it again) is honored because the pending echo has been consumed.
if res.AuthModeSelected.AuthModeId == m.pendingEchoAuthModeID {
log.Debugf(context.TODO(),
"Ignoring GDM auth mode selection echo for %q",
res.AuthModeSelected.AuthModeId)
m.pendingEchoAuthModeID = ""
break
}
commands = append(commands, selectGdmAuthMode(res.AuthModeSelected.AuthModeId))

case *gdm.EventData_IsAuthenticatedRequested:
if !m.waitingAuth {
Expand All @@ -150,7 +171,7 @@ func (m gdmModel) handlePollResponse(gdmPollResults []*gdm.EventData) tea.Cmd {
}
m.waitingAuth = false
if res.IsAuthenticatedRequested == nil || res.IsAuthenticatedRequested.AuthenticationData == nil {
return sendEvent(pamError{
return m, sendEvent(pamError{
status: pam.ErrSystem, msg: "missing auth requested",
})
}
Expand All @@ -173,7 +194,7 @@ func (m gdmModel) handlePollResponse(gdmPollResults []*gdm.EventData) tea.Cmd {

case *gdm.EventData_StageChanged:
if res.StageChanged == nil {
return sendEvent(pamError{
return m, sendEvent(pamError{
status: pam.ErrSystem, msg: "missing stage changed",
})
}
Expand All @@ -183,7 +204,7 @@ func (m gdmModel) handlePollResponse(gdmPollResults []*gdm.EventData) tea.Cmd {
}

commands = append(commands, sendEvent(gdmPollDone{}))
return tea.Sequence(commands...)
return m, tea.Sequence(commands...)
}

func (m gdmModel) emitEvent(event gdm.Event) tea.Cmd {
Expand Down Expand Up @@ -211,14 +232,20 @@ func (m gdmModel) Update(msg tea.Msg) (gdmModel, tea.Cmd) {

switch msg := msg.(type) {
case gdmPollResponse:
return m, m.handlePollResponse(msg.pollResponse)
var cmd tea.Cmd
m, cmd = m.handlePollResponse(msg.pollResponse)
return m, cmd

case gdmPollDone:
return m, tea.Sequence(
tea.Tick(gdmPollFrequency, func(time.Time) tea.Msg { return nil }),
m.pollGdm())

case StageChanged:
// A genuine (re-)selection always follows a stage change into the
// authModeSelection stage, so any echo we were still expecting from a
// previous selection is no longer relevant once the stage changes.
m.pendingEchoAuthModeID = ""
return m, m.changeStage(msg.Stage)

case userSelected:
Expand All @@ -242,6 +269,13 @@ func (m gdmModel) Update(msg tea.Msg) (gdmModel, tea.Cmd) {
})

case AuthModeSelected:
if !msg.fromGDM {
// Only selections sent to GDM are echoed in a later poll. A
// selection received from GDM is already that echo (or a genuine
// user re-selection), so recording it would suppress the next
// selection of the same mode.
m.pendingEchoAuthModeID = msg.ID
}
return m, m.emitEvent(&gdm.EventData_AuthModeSelected{
AuthModeSelected: &gdm.Events_AuthModeSelected{AuthModeId: msg.ID},
})
Expand Down
Loading
Loading