Skip to content

Conversation

@dsupru
Copy link

@dsupru dsupru commented Nov 25, 2025

To start off, big thank you for working on this project !
I found it extremely useful and would love to offer a tiny improvement.

Context

My org needs to have 2FA on client devices, and that's one feature I couldn't figure out how to do.
Seems like I'm not alone (e.g. #1124 ), so I hope this PR adds value for everyone else with this requirement.

We are using authd with Entra plugin on our client and server machines. Regulations require us to enforce 2FA.
I know it's technically the case with the "device authentication", but I don't want to make users go through it every time - it's quite slow.

I can edit /etc/pam.d/gdm-authd (to take gdm as an example) and always require a yubikey, but I couldn't figure out a way to not enforce a yubikey when user goes through the device auth mode.

I want it to work specifically this way to cover the case when the user looses their yubikey -- device authentication should allow them to regain access and set up another yubikey.

Proposed solution

I spent some time looking at pam, and from what I understood authd does either device flow or password flow internally
There is no way I can see what mode the user used. (Let me know if I'm wrong !)

I then realized I can export a pam env when the user changes the authentication method.
We can read this variable downstream to branch on the authentication method, such as requiring a second factor when the user authenticates with a local password.

GDM Example

(please don't roll this out in production, more needs to be done here)

  1. Configure a yubikey
  2. /etc/pam.d/gdm-authd:
#%PAM-1.0
auth    [success=ok user_unknown=ignore default=bad] pam_succeed_if.so user != root quiet_success
auth    [success=ok ignore=ignore default=die authinfo_unavail=ignore] pam_authd.so debug=true

auth    [success=1 default=ignore] pam_exec.so quiet /usr/lib/x86_64-linux-gnu/security/require_u2f_when_local.sh
auth    required        pam_u2f.so cue

auth    requisite       pam_nologin.so
auth    optional        pam_gnome_keyring.so
  1. /usr/lib/x86_64-linux-gnu/security/require_u2f_when_local.sh
#!/bin/sh
[ "$AUTHD_AUTH_MODE" = "newpassword" ] && exit 0
exit 1

With the steps above I achieved the desired functionality.
I'm no security expert, but I think this is safe. The proposed solution in the linked issue also suggested passing an env variable
Please let me know if you need supporting photo/video.

Thank you

Downstream PAM modules can read this variable to branch on the authentication method,
such as requiring a second factor when the user authenticates with a local password.

This makes it possible to address MFA-related requests (e.g. ubuntu#1124) by allowing
administrators to configure their PAM stack to enforce 2FA based on the
selected auth mode.
@dsupru dsupru requested a review from a team as a code owner November 25, 2025 07:31
@dsupru
Copy link
Author

dsupru commented Nov 25, 2025

just signed the CLA 🙂
not sure how to rerun the CI step that checks that

@adombeck adombeck requested a review from 3v1n0 November 25, 2025 11:15
Copy link
Collaborator

@3v1n0 3v1n0 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi, thanks for contributing to authd,

Per se it's not a bad change for us, since once you're authenticated (reason why I'd prefer to save this only if all went well) I think it's fine to leave the information for the rest of the stack, the variable is likely more AUTHD_LAST_AUTH_MODE though, since in practice when we go through device-auth we are using the device auth mode, that gets actually replaced by the new password.

Now, the check you're doing in the script works for the current setup (authd as daemon is potentially much more generic than just entra-id), but indeed it would be more likely correct if you return an error if $AUTHD_AUTH_LAST_MODE != "password".

One more thing, having such env variable puts us in a situation in which we have to consider it kinda of an API, although I feel that this may break in future.

Another option we could have, is maybe having a PAM argument that makes the module to return PAM_CRED_INSUFFICIENT instead if 2fa is required?

So you just set: pam_authd.so .... require_2fa=true then if a device authentication is used during the process, we exit in that way and you can just use [... cred_insufficient=X...] to perform the action you like ...

})
}
// Export selected auth mode to the PAM environment.
m.pamMTx.PutEnv("AUTHD_AUTH_MODE=" + msg.ID)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we just set this on quit using m.authModeSelectionModel.currentAuthModeSelectedID value?

cmds = append(cmds, m.updateClientModel(msg))

// Export last selected auth mode to the PAM environment.
m.pamMTx.PutEnv("AUTHD_AUTH_LAST_MODE=" + m.authModeSelectionModel.currentAuthModeSelectedID)
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@3v1n0 do you mean here ?

@dsupru
Copy link
Author

dsupru commented Nov 25, 2025

Hi, thanks for contributing to authd,

Per se it's not a bad change for us, since once you're authenticated (reason why I'd prefer to save this only if all went well) I think it's fine to leave the information for the rest of the stack, the variable is likely more AUTHD_LAST_AUTH_MODE though, since in practice when we go through device-auth we are using the device auth mode, that gets actually replaced by the new password.

Now, the check you're doing in the script works for the current setup (authd as daemon is potentially much more generic than just entra-id), but indeed it would be more likely correct if you return an error if $AUTHD_AUTH_LAST_MODE != "password".

One more thing, having such env variable puts us in a situation in which we have to consider it kinda of an API, although I feel that this may break in future.

Another option we could have, is maybe having a PAM argument that makes the module to return PAM_CRED_INSUFFICIENT instead if 2fa is required?

So you just set: pam_authd.so .... require_2fa=true then if a device authentication is used during the process, we exit in that way and you can just use [... cred_insufficient=X...] to perform the action you like ...

thank you for responding !

I like the idea of returning PAM_CRED_INSUFFICIENT if require_2fa arg is present.

That said, would you use a mode string to check when to error out like that ? (i.e. "password", "newpassword", etc..)
Cleaner (but involved) change might be to add something like IsMfa() on the brokerer interface.
Though that would require a change from every plugin.. and i'm sure I don't fully understand yet how everything here works
I can spend time this weekend on former option if you'd much rather have that than set an environment like i'm currently proposing in this PR

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants