Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature: How to integrate authd with my own greeter for Entra ID login #443

Open
2 tasks done
chenyu-vmware opened this issue Aug 1, 2024 · 3 comments
Open
2 tasks done
Labels

Comments

@chenyu-vmware
Copy link

Is there an existing request for this feature?

  • I have searched the existing issues and found none that matched mine

Describe the feature

Authd works perfectly to login with Entra ID credentials at ubuntu24.04, thanks for this good project.

Is it possible to integrate my own greeter (not using ubuntu24.04 default greeter) with authd to login to ubuntu24.04? If the answer is yes, could you please give me some guidance how can I implement this requirement, appreciated for any suggestions.

Hereunder are some of unclear things in mind, very thanks for any help.

  1. when one specific username is entered at greeter, how do I know it's one Entra ID user, then display the "Device Authentication" option at greeter?
  2. When selected "Device Authentication", does authd-oidc-brokers provide some interface for me to get the QR code and Login code for "https://microsoft.com/devicelogin"?
  3. After authenticate with Entra ID credentials at browser, authd-oidc-brokers can get the success/error notification from Azure, right? is there some callback way to let broker notify my greeter as well.

Thanks a lot for any help.

Describe the ideal solution

at ubuntu24.04, my own greeter can works fine with authd to authenticated with Entra ID credentials.

Alternatives and current workarounds

N/A

System information and logs

No response

Relevant information

No response

Double check your logs

  • I have redacted any sensitive information from the logs
@3v1n0
Copy link
Collaborator

3v1n0 commented Aug 1, 2024

is it possible to integrate my own greeter (not using ubuntu24.04 default greeter) with authd to login to ubuntu24.04? If the answer is yes, could you please give me some guidance how can I implement this requirement, appreciated for any suggestions.

Can I ask what greeter are you referring to? I assume it's another display manager implementation, isn't it?

Technically any greeter that supports PAM conversations works with authd (in the same way SSH does), (by just using common-auth service) the problem of some greeters is that they don't handle all the PAM items/messages as they should.

So, to see the normal PAM behavior, you can just force using the module native interface by passing the force_native_client=true argument to the pam module and then any tool such as sudo or login should use it.


That said, if you want instead to implement a similar behavior of what GDM in ubuntu does, it's still possible through PAM by implementing our gdm protocol that uses PAM binary messages with JSON content.

You can see examples of what passes through the wire by running go test -C pam/integration-tests -run TestGdmModule -v, so for example in TestGdmModule/Authenticates_user_after_regenerating_the_qrcode:

The main concept is that we use binary conversations to implement the data polling on authd side, and receive back information from it.

    gdm-module-handler_test.go:241: -> {"type":"hello"}
    gdm-module-handler_test.go:244: <- {"type":"hello", "hello":{"version":1}}
    gdm-module-handler_test.go:241: -> {"type":"request", "request":{"type":"uiLayoutCapabilities", "uiLayoutCapabilities":{}}}
    gdm-module-handler_test.go:244: <- {"type":"response", "response":{"type":"uiLayoutCapabilities", "uiLayoutCapabilities":{"supportedUiLayouts":[{"type":"form", "label":"required", "button":"optional", "wait":"optional:true,false", "entry":"optional:chars,chars_password"}, {"type":"qrcode", "label":"optional", "button":"optional", "wait":"required:true,false", "content":"required"}]}}}
    gdm-module-handler_test.go:241: -> {"type":"event", "event":{"type":"userSelected", "userSelected":{"userId":"user-integration-Authenticates-user-after-regenerating-the-qrcode"}}}
    gdm-module-handler_test.go:244: <- {"type":"eventAck"}
    gdm-module-handler_test.go:241: -> {"type":"event", "event":{"type":"brokersReceived", "brokersReceived":{"brokersInfos":[{"id":"local", "name":"local", "brandIcon":""}, {"id":"3434009568", "name":"ExampleBroker", "brandIcon":"/usr/share/backgrounds/warty-final-ubuntu.png"}]}}}
    gdm-module-handler_test.go:244: <- {"type":"eventAck"}
    gdm-module-handler_test.go:241: -> {"type":"request", "request":{"type":"changeStage", "changeStage":{"stage":"brokerSelection"}}}
    gdm-module-handler_test.go:244: <- {"type":"response", "response":{"type":"changeStage", "ack":{}}}
    gdm-module-handler_test.go:241: -> {"type":"poll"}
    gdm-module-handler_test.go:244: <- {"type":"pollResponse", "pollResponse":[{"type":"brokerSelected", "brokerSelected":{"brokerId":"3434009568"}}]}
    gdm-module-handler_test.go:127: Using broker 'ExampleBroker'
    gdm-module-handler_test.go:241: -> {"type":"event", "event":{"type":"brokerSelected", "brokerSelected":{"brokerId":"3434009568"}}}
    gdm-module-handler_test.go:244: <- {"type":"eventAck"}
    gdm-module-handler_test.go:241: -> {"type":"request", "request":{"type":"changeStage", "changeStage":{"stage":"authModeSelection"}}}
    gdm-module-handler_test.go:244: <- {"type":"response", "response":{"type":"changeStage", "ack":{}}}
    gdm-module-handler_test.go:241: -> {"type":"event", "event":{"type":"authModeSelected", "authModeSelected":{"authModeId":"password"}}}
    gdm-module-handler_test.go:244: <- {"type":"eventAck"}
    gdm-module-handler_test.go:241: -> {"type":"event", "event":{"type":"authModesReceived", "authModesReceived":{"authModes":[{"id":"password", "label":"Password authentication"}, {"id":"entry_or_wait_for_user-integration-Authenticates-user-after-regenerating-the-qrcode_gmail.com", "label":"Send URL to user-integration-Authenticates-user-after-regenerating-the-qrcode@gmail.com"}, {"id":"fidodevice1", "label":"Use your fido device foo"}, {"id":"phoneack1", "label":"Use your phone +33…"}, {"id":"phoneack2", "label":"Use your phone +1…"}, {"id":"qrcodewithtypo", "label":"Use a QR code"}, {"id":"totp_with_button", "label":"Authentication code"}]}}}
    gdm-module-handler_test.go:244: <- {"type":"eventAck"}
    gdm-module-handler_test.go:140: Gimme your password:
    gdm-module-handler_test.go:143: :
    gdm-module-handler_test.go:241: -> {"type":"event", "event":{"type":"uiLayoutReceived", "uiLayoutReceived":{"uiLayout":{"type":"form", "label":"Gimme your password", "button":"", "wait":"", "entry":"chars_password", "content":"", "code":""}}}}
    gdm-module-handler_test.go:244: <- {"type":"eventAck"}
    gdm-module-handler_test.go:241: -> {"type":"event", "event":{"type":"startAuthentication", "startAuthentication":{}}}
    gdm-module-handler_test.go:244: <- {"type":"eventAck"}
    gdm-module-handler_test.go:241: -> {"type":"request", "request":{"type":"changeStage", "changeStage":{"stage":"challenge"}}}
    gdm-module-handler_test.go:244: <- {"type":"response", "response":{"type":"changeStage", "ack":{}}}
    gdm-module-handler_test.go:241: -> {"type":"poll"}
    gdm-module-handler_test.go:244: <- {"type":"pollResponse", "pollResponse":[{"type":"stageChanged", "stageChanged":{"stage":"authModeSelection"}}, {"type":"authModeSelected", "authModeSelected":{"authModeId":"qrcodewithtypo"}}]}
    gdm-module-handler_test.go:173: Authentication event: access:"cancelled"
    gdm-module-handler_test.go:241: -> {"type":"event", "event":{"type":"authEvent", "authEvent":{"response":{"access":"cancelled"}}}}
    gdm-module-handler_test.go:244: <- {"type":"eventAck"}
    gdm-module-handler_test.go:241: -> {"type":"request", "request":{"type":"changeStage", "changeStage":{"stage":"authModeSelection"}}}
    gdm-module-handler_test.go:244: <- {"type":"response", "response":{"type":"changeStage", "ack":{}}}
    gdm-module-handler_test.go:241: -> {"type":"event", "event":{"type":"authModeSelected", "authModeSelected":{"authModeId":"qrcodewithtypo"}}}
    gdm-module-handler_test.go:244: <- {"type":"eventAck"}
    gdm-module-handler_test.go:140: Enter the following code after flashing the address: 1337:
    gdm-module-handler_test.go:143: https://ubuntu.com:
    gdm-module-handler_test.go:241: -> {"type":"event", "event":{"type":"uiLayoutReceived", "uiLayoutReceived":{"uiLayout":{"type":"qrcode", "label":"Enter the following code after flashing the address: 1337", "button":"Regenerate code", "wait":"true", "entry":"", "content":"https://ubuntu.com", "code":""}}}}
    gdm-module-handler_test.go:244: <- {"type":"eventAck"}
    gdm-module-handler_test.go:241: -> {"type":"event", "event":{"type":"startAuthentication", "startAuthentication":{}}}
    gdm-module-handler_test.go:244: <- {"type":"eventAck"}
    gdm-module-handler_test.go:241: -> {"type":"request", "request":{"type":"changeStage", "changeStage":{"stage":"challenge"}}}
    gdm-module-handler_test.go:244: <- {"type":"response", "response":{"type":"changeStage", "ack":{}}}
    gdm-module-handler_test.go:241: -> {"type":"poll"}
    gdm-module-handler_test.go:244: <- {"type":"pollResponse", "pollResponse":[{"type":"isAuthenticatedRequested", "isAuthenticatedRequested":{"authenticationData":{"wait":"true"}}}, {"type":"reselectAuthMode", "reselectAuthMode":{}}]}
    gdm-module-handler_test.go:173: Authentication event: access:"cancelled"
    gdm-module-handler_test.go:241: -> {"type":"event", "event":{"type":"authEvent", "authEvent":{"response":{"access":"cancelled"}}}}
    gdm-module-handler_test.go:244: <- {"type":"eventAck"}
    gdm-module-handler_test.go:140: Enter the following code after flashing the address: 1338:
    gdm-module-handler_test.go:143: https://ubuntu.fr/:
    gdm-module-handler_test.go:241: -> {"type":"event", "event":{"type":"uiLayoutReceived", "uiLayoutReceived":{"uiLayout":{"type":"qrcode", "label":"Enter the following code after flashing the address: 1338", "button":"Regenerate code", "wait":"true", "entry":"", "content":"https://ubuntu.fr/", "code":""}}}}
    gdm-module-handler_test.go:244: <- {"type":"eventAck"}
    gdm-module-handler_test.go:241: -> {"type":"event", "event":{"type":"startAuthentication", "startAuthentication":{}}}
    gdm-module-handler_test.go:244: <- {"type":"eventAck"}
    gdm-module-handler_test.go:241: -> {"type":"poll"}
    gdm-module-handler_test.go:244: <- {"type":"pollResponse", "pollResponse":[{"type":"reselectAuthMode", "reselectAuthMode":{}}]}
    gdm-module-handler_test.go:140: Enter the following code after flashing the address: 1339:
    gdm-module-handler_test.go:143: https://ubuntuforum-br.org/:
    gdm-module-handler_test.go:241: -> {"type":"event", "event":{"type":"uiLayoutReceived", "uiLayoutReceived":{"uiLayout":{"type":"qrcode", "label":"Enter the following code after flashing the address: 1339", "button":"Regenerate code", "wait":"true", "entry":"", "content":"https://ubuntuforum-br.org/", "code":""}}}}
    gdm-module-handler_test.go:244: <- {"type":"eventAck"}
    gdm-module-handler_test.go:241: -> {"type":"event", "event":{"type":"startAuthentication", "startAuthentication":{}}}
    gdm-module-handler_test.go:244: <- {"type":"eventAck"}
    gdm-module-handler_test.go:241: -> {"type":"poll"}
    gdm-module-handler_test.go:244: <- {"type":"pollResponse", "pollResponse":[{"type":"isAuthenticatedRequested", "isAuthenticatedRequested":{"authenticationData":{"wait":"true"}}}, {"type":"reselectAuthMode", "reselectAuthMode":{}}]}
    gdm-module-handler_test.go:173: Authentication event: access:"cancelled"
    gdm-module-handler_test.go:241: -> {"type":"event", "event":{"type":"authEvent", "authEvent":{"response":{"access":"cancelled"}}}}
    gdm-module-handler_test.go:244: <- {"type":"eventAck"}
    gdm-module-handler_test.go:140: Enter the following code after flashing the address: 1340:
    gdm-module-handler_test.go:143: https://www.ubuntu-it.org/:
    gdm-module-handler_test.go:241: -> {"type":"event", "event":{"type":"uiLayoutReceived", "uiLayoutReceived":{"uiLayout":{"type":"qrcode", "label":"Enter the following code after flashing the address: 1340", "button":"Regenerate code", "wait":"true", "entry":"", "content":"https://www.ubuntu-it.org/", "code":""}}}}
    gdm-module-handler_test.go:244: <- {"type":"eventAck"}
    gdm-module-handler_test.go:241: -> {"type":"event", "event":{"type":"startAuthentication", "startAuthentication":{}}}
    gdm-module-handler_test.go:244: <- {"type":"eventAck"}
    gdm-module-handler_test.go:241: -> {"type":"poll"}
    gdm-module-handler_test.go:244: <- {"type":"pollResponse", "pollResponse":[{"type":"reselectAuthMode", "reselectAuthMode":{}}]}
    gdm-module-handler_test.go:140: Enter the following code after flashing the address: 1341:
    gdm-module-handler_test.go:143: https://ubuntu.com:
    gdm-module-handler_test.go:241: -> {"type":"event", "event":{"type":"uiLayoutReceived", "uiLayoutReceived":{"uiLayout":{"type":"qrcode", "label":"Enter the following code after flashing the address: 1341", "button":"Regenerate code", "wait":"true", "entry":"", "content":"https://ubuntu.com", "code":""}}}}
    gdm-module-handler_test.go:244: <- {"type":"eventAck"}
    gdm-module-handler_test.go:241: -> {"type":"event", "event":{"type":"startAuthentication", "startAuthentication":{}}}
    gdm-module-handler_test.go:244: <- {"type":"eventAck"}
    gdm-module-handler_test.go:241: -> {"type":"poll"}
    gdm-module-handler_test.go:244: <- {"type":"pollResponse", "pollResponse":[{"type":"isAuthenticatedRequested", "isAuthenticatedRequested":{"authenticationData":{"wait":"true"}}}]}
    gdm-module-handler_test.go:173: Authentication event: access:"granted"
    gdm-module-handler_test.go:241: -> {"type":"event", "event":{"type":"authEvent", "authEvent":{"response":{"access":"granted"}}}}
    gdm-module-handler_test.go:244: <- {"type":"eventAck"}

@chenyu-vmware
Copy link
Author

chenyu-vmware commented Aug 2, 2024

Appreciated for your helpful message, @3v1n0, you bring light to me.

The greeter is simple implemented by ourselves, and its backend does use pam service to do the authentication. Then according to your message, suppose authd can be used in our scenario for Entra ID user login workflow, right?

common-account
common-auth
common-password
gdm-authd
Looks authd update its so or binary to above pam services, "gdm-authd" @include common-account, commn-auth, and common-password services, is it correct that ubuntu default greeter loads gdm-authd service for login workflow?

Then turn to my case, I create my own pam service similar with "gdm-authd", like "my-authd", then my greeter load it to trigger authentication conversations, is it right? And pam_authd_exec.so will echo the QR image and login device code (polling from authd) to my greeter for display, not sure whether my understanding is correct.

Could you please help to guide me about these queries, then I can get basic idea to start my POC work.

As I'm a newer to authd and go language, I have trouble to fully understand your below description, I will practice and learn more try to understand them and apply to my requirement. Really thanks for your help.

So, to see the normal PAM behavior, you can just force using the module native interface by passing the force_native_client=true argument to the pam module and then any tool such as sudo or login should use it.

That said, if you want instead to implement a similar behavior of what GDM in ubuntu does, it's still possible through PAM by implementing our [gdm protocol](https://github.com/ubuntu/authd/blob/main/pam/internal/adapter/gdmmodel.go) that uses [PAM binary messages](https://github.com/ubuntu/authd/blob/main/pam/internal/gdm/extension.go) with [JSON content](https://github.com/ubuntu/authd/blob/main/pam/internal/gdm/extensions/gdm-custom-json-pam-extension.h).

You can see examples of what passes through the wire by running go test -C pam/integration-tests -run TestGdmModule -v, so for example in TestGdmModule/Authenticates_user_after_regenerating_the_qrcode:

The main concept is that we use binary conversations to implement the data polling on authd side, and receive back information from it.

@jibel jibel added the support label Sep 6, 2024
@3v1n0
Copy link
Collaborator

3v1n0 commented Sep 30, 2024

Oh, it looks like I didn't see your message... I'm sorry @chenyu-vmware!

Not sure if you had some progresses yourself so far, but as mentioned if you want to implement this in a UI greeter, I strongly recommend you to follow a similar behavior of what we do for GDM, since if you need to show the qrcode as an image you may want to be able to get it from a communication protocol.

So, indeed what you need to do is:

A very simpler example of how events are managed (in Go, since it was done for this repo):

package main

import (
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"os"
	"os/exec"
	"path/filepath"
	"runtime"
	"slices"

	"github.com/msteinert/pam/v2"
	"github.com/ubuntu/authd"
	"github.com/ubuntu/authd/internal/brokers"
	"github.com/ubuntu/authd/internal/log"
	"github.com/ubuntu/authd/pam/internal/gdm"
	"github.com/ubuntu/authd/pam/internal/pam_test"
	"github.com/ubuntu/authd/pam/internal/proto"
)

// var (
// 	socketPath = flag.String("socket-path", "", "the socket path")
// )

var currentStage proto.Stage
var pollResponses = []*gdm.EventData{}
var authModes []*authd.GAMResponse_AuthenticationMode
var brokersInfos []*authd.ABResponse_BrokerInfo

func exampleHandleGdmData(gdmData *gdm.Data) (*gdm.Data, error) {
	switch gdmData.Type {
	case gdm.DataType_hello:
		return &gdm.Data{
			Type:  gdm.DataType_hello,
			Hello: &gdm.HelloData{Version: gdm.ProtoVersion},
		}, nil

	case gdm.DataType_request:
		return exampleHandleAuthDRequest(gdmData)

	case gdm.DataType_poll:
		responses := pollResponses
		pollResponses = nil
		return &gdm.Data{
			Type:         gdm.DataType_pollResponse,
			PollResponse: responses,
		}, nil

	case gdm.DataType_event:
		err := exampleHandleEvent(gdmData.Event.Data)
		if err != nil {
			return nil, err
		}
		return &gdm.Data{
			Type: gdm.DataType_eventAck,
		}, nil
	}

	return nil, fmt.Errorf("unhandled protocol message %s",
		gdmData.Type.String())
}

func exampleHandleEvent(event gdm.Event) error {
	switch ev := event.(type) {
	case *gdm.EventData_BrokersReceived:
		if len(ev.BrokersReceived.BrokersInfos) == 0 {
			return errors.New("no brokers available")
		}
		brokersInfos = ev.BrokersReceived.BrokersInfos
		pollResponses = append(pollResponses, &gdm.EventData{
			Type: gdm.EventType_brokerSelected,
			Data: &gdm.EventData_BrokerSelected{
				BrokerSelected: &gdm.Events_BrokerSelected{
					BrokerId: brokersInfos[1].Id,
				},
			},
		})

	case *gdm.EventData_BrokerSelected:
		idx := slices.IndexFunc(brokersInfos, func(broker *authd.ABResponse_BrokerInfo) bool {
			return broker.Id == ev.BrokerSelected.BrokerId
		})
		if idx < 0 {
			return fmt.Errorf("unknown auth mode type: %s", ev.BrokerSelected.BrokerId)
		}
		log.Infof(context.TODO(), "Using broker %q", brokersInfos[idx].Name)

	case *gdm.EventData_AuthModesReceived:
		authModes = ev.AuthModesReceived.AuthModes

	case *gdm.EventData_AuthModeSelected:
		idx := slices.IndexFunc(authModes, func(mode *authd.GAMResponse_AuthenticationMode) bool {
			return mode.Id == ev.AuthModeSelected.AuthModeId
		})
		if idx < 0 {
			return fmt.Errorf("unknown auth mode type: %s", ev.AuthModeSelected.AuthModeId)
		}

	case *gdm.EventData_UiLayoutReceived:
		layout := ev.UiLayoutReceived.UiLayout
		if layout.Label != nil {
			log.Infof(context.TODO(), "%s:", *layout.Label)
		}

	case *gdm.EventData_AuthEvent:
		if msg := ev.AuthEvent.Response.Msg; msg != "" {
			var msgData map[string]any
			if err := json.Unmarshal([]byte(msg), &msgData); err != nil {
				return err
			}
			if msg, ok := msgData["message"]; ok {
				log.Infof(context.TODO(), "Got message: %s", msg)
			}
		}
		if ev.AuthEvent.Response.Access == brokers.AuthGranted {
			return nil
		}
		if ev.AuthEvent.Response.Access == brokers.AuthDenied {
			return nil
		}
		if ev.AuthEvent.Response.Access == brokers.AuthRetry {
			pollResponses = append(pollResponses, &gdm.EventData{
				Type: gdm.EventType_isAuthenticatedRequested,
				Data: &gdm.EventData_IsAuthenticatedRequested{
					IsAuthenticatedRequested: &gdm.Events_IsAuthenticatedRequested{
						AuthenticationData: &authd.IARequest_AuthenticationData{
							Item: &authd.IARequest_AuthenticationData_Challenge{
								Challenge: "goodpass",
							},
						},
					},
				},
			})
			return nil
		}

	case *gdm.EventData_StartAuthentication:
		pollResponses = append(pollResponses, &gdm.EventData{
			Type: gdm.EventType_isAuthenticatedRequested,
			Data: &gdm.EventData_IsAuthenticatedRequested{
				IsAuthenticatedRequested: &gdm.Events_IsAuthenticatedRequested{
					AuthenticationData: &authd.IARequest_AuthenticationData{
						Item: &authd.IARequest_AuthenticationData_Challenge{
							Challenge: "wrong-pass",
						},
					},
				},
			},
		})
	}
	return nil
}

func exampleHandleAuthDRequest(gdmData *gdm.Data) (*gdm.Data, error) {
	switch req := gdmData.Request.Data.(type) {
	case *gdm.RequestData_UiLayoutCapabilities:
		required, _ := "required", "optional"
		supportedEntries := "optional:chars,chars_password"
		// requiredWithBooleans := "required:true,false"
		optionalWithBooleans := "optional:true,false"

		return &gdm.Data{
			Type: gdm.DataType_response,
			Response: &gdm.ResponseData{
				Type: gdmData.Request.Type,
				Data: &gdm.ResponseData_UiLayoutCapabilities{
					UiLayoutCapabilities: &gdm.Responses_UiLayoutCapabilities{
						SupportedUiLayouts: []*authd.UILayout{
							{
								Type:  "form",
								Label: &required,
								Entry: &supportedEntries,
								Wait:  &optionalWithBooleans,
								// Button: &optional,
							},
							{
								Type:  "newpassword",
								Label: &required,
								Entry: &supportedEntries,
								// Button: &optional,
							},
						},
					},
				},
			},
		}, nil

	case *gdm.RequestData_ChangeStage:
		if gdmData.Request.Data == nil {
			return nil, fmt.Errorf("missing stage data")
		}
		currentStage = req.ChangeStage.Stage
		log.Debugf(context.TODO(), "Switching to stage %d", currentStage)

		return &gdm.Data{
			Type: gdm.DataType_response,
			Response: &gdm.ResponseData{
				Type: gdmData.Request.Type,
				Data: &gdm.ResponseData_Ack{},
			},
		}, nil

	default:
		return nil, fmt.Errorf("unknown request type")
	}
}

// LoadModule compiles and loads a pam module.
func LoadModule(socketPath string, userName string) (*pam.Transaction, error) {
	servicePath, err := os.MkdirTemp(os.TempDir(), "test-pam-loader-*")
	if err != nil {
		return nil, fmt.Errorf("can't create service path %v", err)
	}
	defer os.RemoveAll(servicePath)

	libPath := filepath.Join(servicePath, "libpam_authd.so")
	log.Debugf(context.TODO(), "Compiling module at %s", libPath)
	_, currentFile, _, ok := runtime.Caller(1)
	if !ok {
		return nil, errors.New("can't get current binary path")
	}
	buildArgs := []string{
		"build",
		"-C", filepath.Join(filepath.Dir(currentFile), "..", "..", "..", "pam"),
		"-buildmode=c-shared",
		"-o", libPath, `-gcflags=-dwarflocationlists=true`,
	}
	if pam_test.IsAddressSanitizerActive() {
		buildArgs = append(buildArgs, "-asan")
	}

	// #nosec:G204 - we control the command arguments in tests
	cmd := exec.Command("go", buildArgs...)
	cmd.Dir = filepath.Dir(currentFile)
	out, err := cmd.CombinedOutput()
	if err != nil {
		return nil, fmt.Errorf("can't build pam module %v: %s", err, out)
	}

	serviceName := "module-loader"
	serviceFile := filepath.Join(servicePath, serviceName)
	log.Debugf(context.TODO(), "Creating service file at %s", serviceFile)

	if err := os.WriteFile(serviceFile,
		[]byte(fmt.Sprintf("auth requisite %s socket=%s", libPath, socketPath)),
		0600); err != nil {
		return nil, fmt.Errorf("can't create service file %v", err)
	}

	tx, err := pam.StartConfDir(serviceName, userName, gdm.DataConversationFunc(
		func(inData *gdm.Data) (*gdm.Data, error) {
			outData, err := exampleHandleGdmData(inData)
			if err != nil {
				return nil, err
			}
			if inData.Type == gdm.DataType_poll && len(outData.PollResponse) == 0 {
				return outData, err
			}
			json, err := inData.JSON()
			if err != nil {
				return nil, err
			}
			log.Debug(context.TODO(), "->", string(json))
			json, err = outData.JSON()
			if err != nil {
				return nil, err
			}
			log.Debug(context.TODO(), "<-", string(json))
			return outData, nil
		}), servicePath)
	if err != nil {
		return nil, fmt.Errorf("can't create PAM handler: %v", err)
	}

	log.Debug(context.TODO(), "PAM Handler created")

	return tx, nil
}

func startAuthentication() error {
	tx, err := LoadModule("/tmp/authd.sock", "")
	if err != nil {
		return err
	}

	err = tx.SetItem(pam.User, "user1")
	if err != nil {
		return err
	}

	return tx.Authenticate(pam.Flags(0))
}

func main() {
	// TODO: Add option to simulate different loading types
	log.SetLevel(log.DebugLevel)

	gdm.AdvertisePamExtensions([]string{gdm.PamExtensionCustomJSON})

	if err := startAuthentication(); err != nil {
		log.Error(context.TODO(), err)
		var pamError pam.Error
		if errors.Is(err, &pamError) {
			os.Exit(int(pamError))
		}
		os.Exit(1)
	}

	os.Exit(0)
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

3 participants