Skip to content
Merged
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
8 changes: 8 additions & 0 deletions .github/workflows/qa.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,14 @@ jobs:
- name: Run tests (with race detector)
run: |
go test -race ./...
- name: Run PAM tests (with Address Sanitizer)
env:
# Do not optimize, keep debug symbols and frame pointer for better
# stack trace information in case of ASAN errors.
CGO_CFLAGS: "-O0 -g3 -fno-omit-frame-pointer"
run: |
# Use `-dwarflocationlists` to give ASAN a better time to unwind the stack trace
go test -C pam -asan -gcflags="-dwarflocationlists=true" ./...
Comment thread
denisonbarbosa marked this conversation as resolved.
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v3
with:
Expand Down
6 changes: 6 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ require (
github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf
github.com/godbus/dbus/v5 v5.1.0
github.com/google/uuid v1.4.0
github.com/msteinert/pam v1.2.0
github.com/sirupsen/logrus v1.9.3
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
github.com/spf13/cobra v1.8.0
Expand Down Expand Up @@ -62,3 +63,8 @@ require (
golang.org/x/text v0.13.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20230920204549-e6e6cdab5c13 // indirect
)

// FIXME: Use released version once we have one!
Comment thread
denisonbarbosa marked this conversation as resolved.
// The branch below includes changes from this upstream PR:
// - https://github.com/msteinert/pam/pull/13
replace github.com/msteinert/pam => github.com/3v1n0/go-pam v0.0.0-20231130030658-0f1cc6f16d45
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RX
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
github.com/3v1n0/go-pam v0.0.0-20231130030658-0f1cc6f16d45 h1:EmfxQhrTNAVT4rzbv6TcP2XScDl16Q8J0ZlCVbazl8c=
github.com/3v1n0/go-pam v0.0.0-20231130030658-0f1cc6f16d45/go.mod h1:d2n0DCUK8rGecChV3JzvmsDjOY4R7AYbsNxAT+ftQl0=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4=
Expand Down
5 changes: 5 additions & 0 deletions internal/log/log.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,11 @@ func Info(_ context.Context, args ...interface{}) {
logrus.Info(args...)
}

// Warning is a temporary placeholder.
func Warning(_ context.Context, args ...interface{}) {
logrus.Warning(args...)
}

// Warningf is a temporary placeholder.
func Warningf(_ context.Context, format string, args ...interface{}) {
logrus.Warningf(format, args...)
Expand Down
2 changes: 2 additions & 0 deletions pam/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
./*.h
*.so
45 changes: 28 additions & 17 deletions pam/authentication.go
Comment thread
GabrielNagy marked this conversation as resolved.
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
// Package main is the package for the PAM library
Comment thread
GabrielNagy marked this conversation as resolved.
package main

import (
Expand All @@ -7,6 +8,7 @@ import (

tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
"github.com/msteinert/pam"
"github.com/ubuntu/authd"
"github.com/ubuntu/authd/internal/brokers/responses"
"github.com/ubuntu/authd/internal/log"
Expand All @@ -32,8 +34,9 @@ func sendIsAuthenticated(ctx context.Context, client authd.PAMClient, sessionID,
access: responses.AuthCancelled,
}
}
return pamSystemError{
msg: fmt.Sprintf("Authentication status failure: %v", err),
return pamError{
status: pam.ErrSystem,
msg: fmt.Sprintf("authentication status failure: %v", err),
}
}

Expand Down Expand Up @@ -126,15 +129,22 @@ func (m *authenticationModel) Update(msg tea.Msg) (authenticationModel, tea.Cmd)
return *m, sendEvent(pamSuccess{brokerID: m.currentBrokerID})

case responses.AuthRetry:
m.errorMsg = dataToMsg(msg.msg)
errorMsg, err := dataToMsg(msg.msg)
if err != nil {
return *m, sendEvent(pamError{status: pam.ErrSystem, msg: err.Error()})
}
m.errorMsg = errorMsg
return *m, sendEvent(startAuthentication{})

case responses.AuthDenied:
errMsg := "Access denied"
if err := dataToMsg(msg.msg); err != "" {
errMsg = err
errMsg, err := dataToMsg(msg.msg)
if err != nil {
return *m, sendEvent(pamError{status: pam.ErrSystem, msg: err.Error()})
}
if errMsg == "" {
errMsg = "Access denied"
}
return *m, sendEvent(pamAuthError{msg: errMsg})
return *m, sendEvent(pamError{status: pam.ErrAuth, msg: errMsg})

case responses.AuthNext:
return *m, sendEvent(GetAuthenticationModesRequested{})
Expand Down Expand Up @@ -204,7 +214,7 @@ func (m *authenticationModel) Compose(brokerID, sessionID string, layout *authd.
case "qrcode":
qrcodeModel, err := newQRCodeModel(layout.GetContent(), layout.GetLabel(), layout.GetButton(), layout.GetWait() == "true")
if err != nil {
return sendEvent(pamSystemError{msg: err.Error()})
return sendEvent(pamError{status: pam.ErrSystem, msg: err.Error()})
}
m.currentModel = qrcodeModel

Expand All @@ -213,7 +223,10 @@ func (m *authenticationModel) Compose(brokerID, sessionID string, layout *authd.
m.currentModel = newPasswordModel

default:
return sendEvent(pamSystemError{msg: fmt.Sprintf("unknown layout type: %q", layout.Type)})
return sendEvent(pamError{
status: pam.ErrSystem,
msg: fmt.Sprintf("unknown layout type: %q", layout.Type),
})
}

return sendEvent(startAuthentication{})
Expand Down Expand Up @@ -246,24 +259,22 @@ func (m *authenticationModel) Reset() {
}

// dataToMsg returns the data message from a given JSON message.
func dataToMsg(data string) string {
func dataToMsg(data string) (string, error) {
if data == "" {
return ""
return "", nil
}

v := make(map[string]string)
if err := json.Unmarshal([]byte(data), &v); err != nil {
log.Infof(context.TODO(), "Invalid json data from provider: %v", data)
return ""
return "", fmt.Errorf("invalid json data from provider: %v", err)
}
if len(v) == 0 {
return ""
return "", nil
}

r, ok := v["message"]
if !ok {
log.Debugf(context.TODO(), "No message entry in json data from provider: %v", data)
return ""
return "", fmt.Errorf("no message entry in json data from provider: %v", v)
}
return r
return r, nil
}
11 changes: 5 additions & 6 deletions pam/authmodeselection.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"github.com/charmbracelet/bubbles/list"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
"github.com/msteinert/pam"
"github.com/ubuntu/authd"
"github.com/ubuntu/authd/internal/log"
)
Expand Down Expand Up @@ -241,17 +242,15 @@ func getAuthenticationModes(client authd.PAMClient, sessionID string, uiLayouts

gamResp, err := client.GetAuthenticationModes(context.Background(), gamReq)
if err != nil {
return pamSystemError{
msg: fmt.Sprintf("could not get authentication modes: %v", err),
return pamError{
status: pam.ErrSystem,
msg: fmt.Sprintf("could not get authentication modes: %v", err),
}
}

authModes := gamResp.GetAuthenticationModes()
if len(authModes) == 0 {
return pamIgnore{
// TODO: probably go back to broker selection here
msg: "no supported authentication mode available for this provider",
}
return pamIgnore{msg: "no supported authentication mode available for this provider"}
}
log.Info(context.TODO(), authModes)

Expand Down
6 changes: 4 additions & 2 deletions pam/brokerselection.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"github.com/charmbracelet/bubbles/list"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
"github.com/msteinert/pam"
"github.com/ubuntu/authd"
"github.com/ubuntu/authd/internal/log"
)
Expand Down Expand Up @@ -232,8 +233,9 @@ func getAvailableBrokers(client authd.PAMClient) tea.Cmd {
return func() tea.Msg {
brokersInfo, err := client.AvailableBrokers(context.TODO(), &authd.Empty{})
if err != nil {
return pamSystemError{
msg: fmt.Sprintf("could not get current available brokers: %v", err),
return pamError{
status: pam.ErrSystem,
msg: fmt.Sprintf("could not get current available brokers: %v", err),
}
}

Expand Down
21 changes: 12 additions & 9 deletions pam/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"strings"

tea "github.com/charmbracelet/bubbletea"
"github.com/msteinert/pam"
"github.com/ubuntu/authd"
"github.com/ubuntu/authd/internal/log"
)
Expand Down Expand Up @@ -44,16 +45,16 @@ func startBrokerSession(client authd.PAMClient, brokerID, username string) tea.C

sbResp, err := client.SelectBroker(context.TODO(), sbReq)
if err != nil {
return pamSystemError{msg: fmt.Sprintf("can't select broker: %v", err)}
return pamError{status: pam.ErrSystem, msg: fmt.Sprintf("can't select broker: %v", err)}
}

sessionID := sbResp.GetSessionId()
if sessionID == "" {
return pamSystemError{msg: "no session ID returned by broker"}
return pamError{status: pam.ErrSystem, msg: "no session ID returned by broker"}
}
encryptionKey := sbResp.GetEncryptionKey()
if encryptionKey == "" {
return pamSystemError{msg: "no encryption key returned by broker"}
return pamError{status: pam.ErrSystem, msg: "no encryption key returned by broker"}
}

return SessionStarted{
Expand All @@ -73,16 +74,18 @@ func getLayout(client authd.PAMClient, sessionID, authModeID string) tea.Cmd {
}
uiInfo, err := client.SelectAuthenticationMode(context.TODO(), samReq)
if err != nil {
return pamSystemError{
// TODO: probably go back to broker selection here
msg: fmt.Sprintf("can't select authentication mode: %v", err),
// TODO: probably go back to broker selection here
return pamError{
status: pam.ErrSystem,
msg: fmt.Sprintf("can't select authentication mode: %v", err),
}
}

if uiInfo.UiLayoutInfo == nil {
return pamSystemError{
// TODO: probably go back to broker selection here
msg: "invalid empty UI Layout information from broker",
// TODO: probably go back to broker selection here
return pamError{
status: pam.ErrSystem,
msg: "invalid empty UI Layout information from broker",
}
}

Expand Down
45 changes: 45 additions & 0 deletions pam/main-cli.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
//go:build pam_binary_cli

package main

import (
"fmt"
"os"

"github.com/msteinert/pam"
"github.com/sirupsen/logrus"
"github.com/ubuntu/authd/internal/log"
"github.com/ubuntu/authd/pam/pam_test"
)

// Simulating pam on the CLI for manual testing.
func main() {
log.SetLevel(log.DebugLevel)
f, err := os.OpenFile("/tmp/logdebug", os.O_CREATE|os.O_APPEND|os.O_RDWR, 0600)
if err != nil {
panic(err)
}
defer f.Close()
logrus.SetOutput(f)

module := &pamModule{}
mTx := pam_test.NewModuleTransactionDummy(pam.ConversationFunc(
func(style pam.Style, msg string) (string, error) {
switch style {
case pam.TextInfo:
fmt.Fprintf(os.Stderr, "PAM INFO: %s\n", msg)
case pam.ErrorMsg:
fmt.Fprintf(os.Stderr, "PAM ERROR: %s\n", msg)
default:
return "", fmt.Errorf("pam style %d not implemented", style)
}
return "", nil
}))

authResult := module.Authenticate(mTx, pam.Flags(0), nil)
fmt.Println("Auth return:", authResult)

// Simulate setting auth broker as default.
accMgmtResult := module.AcctMgmt(mTx, pam.Flags(0), nil)
fmt.Println("Acct mgmt return:", accMgmtResult)
}
35 changes: 15 additions & 20 deletions pam/model.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@ package main

import (
"context"
"fmt"
"strings"

tea "github.com/charmbracelet/bubbletea"
"github.com/msteinert/pam"
"github.com/ubuntu/authd"
"github.com/ubuntu/authd/internal/log"
)
Expand Down Expand Up @@ -35,7 +35,7 @@ type sessionInfo struct {

// model is the global models orchestrator.
type model struct {
pamh pamHandle
pamMTx pam.ModuleTransaction
client authd.PAMClient

height int
Expand All @@ -49,7 +49,7 @@ type model struct {
authModeSelectionModel authModeSelectionModel
authenticationModel authenticationModel

exitMsg fmt.Stringer
exitStatus pamReturnStatus
}

/* global events */
Expand Down Expand Up @@ -87,7 +87,8 @@ type SessionEnded struct{}

// Init initializes the main model orchestrator.
func (m *model) Init() tea.Cmd {
m.userSelectionModel = newUserSelectionModel(m.pamh)
m.exitStatus = pamError{status: pam.ErrSystem, msg: "model did not return anything"}
m.userSelectionModel = newUserSelectionModel(m.pamMTx)
var cmds []tea.Cmd
cmds = append(cmds, m.userSelectionModel.Init())

Expand All @@ -113,7 +114,10 @@ func (m *model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
case tea.KeyMsg:
switch msg.String() {
case "ctrl+c":
return m, sendEvent(pamAbort{msg: "cancel requested"})
return m, sendEvent(pamError{
status: pam.ErrAbort,
msg: "cancel requested",
})
case "esc":
if m.brokerSelectionModel.WillCaptureEscape() || m.authModeSelectionModel.WillCaptureEscape() {
break
Expand All @@ -137,20 +141,8 @@ func (m *model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
m.brokerSelectionModel.SetWidth(m.width)

// Exit cases
case pamIgnore:
Comment thread
denisonbarbosa marked this conversation as resolved.
m.exitMsg = msg
return m, m.quit()
case pamAbort:
m.exitMsg = msg
return m, m.quit()
case pamSystemError:
m.exitMsg = msg
return m, m.quit()
case pamAuthError:
m.exitMsg = msg
return m, m.quit()
case pamSuccess:
m.exitMsg = msg
case pamReturnStatus:
Comment thread
denisonbarbosa marked this conversation as resolved.
m.exitStatus = msg
return m, m.quit()

// Events
Expand Down Expand Up @@ -194,7 +186,10 @@ func (m *model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
msg.ID = m.authModeSelectionModel.currentAuthModeSelectedID
}
if msg.ID == "" {
return m, sendEvent(pamSystemError{msg: "reselection of current auth mode without current ID"})
return m, sendEvent(pamError{
status: pam.ErrSystem,
msg: "reselection of current auth mode without current ID",
})
}
return m, getLayout(m.client, m.currentSession.sessionID, msg.ID)

Expand Down
Loading