Skip to content

Commit

Permalink
feat: add pam interaction prompt
Browse files Browse the repository at this point in the history
  • Loading branch information
seiuneko committed Dec 26, 2024
1 parent 39997cd commit 1b921d0
Show file tree
Hide file tree
Showing 3 changed files with 33 additions and 8 deletions.
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,11 +78,13 @@ The following arguments are supported:
- `auth_key_file=<Path to authorized_keys>` Public keys allowed for user authentication. Defaults to `<home>/.ssh/authorized_keys`. `<home>` is read from system configuration, usually it expands to `/home/<username>`.
- `authorized_keys_command=<Path to command>` A command to generate the authorized_keys. It takes a single argument, the username of the user being authenticated. The standard output of this command will be parsed as authorized_keys. The `auth_key_file` will be ignored if you specify this argument.
- `authorized_keys_command_user=<Username>` The `authorized_keys_command` will be run as the user specified here. If this argument is omitted, the `authorized_keys_command` will be run as the user being authenticated.
- `cue` Enable device interaction prompt. When enabled, displays a message reminding the user to touch their device during authentication.
- `[cue_prompt=<message>]` Set custom prompt message for device interaction. Default: "Please touch the device". Use square brackets to include spaces in the message.

Arguments should be appended to the PAM rule. For example:

```
auth sufficient libpam_rssh.so debug authorized_keys_command=/usr/bin/sss_ssh_authorizedkeys authorized_keys_command_user=nobody
auth sufficient libpam_rssh.so debug authorized_keys_command=/usr/bin/sss_ssh_authorizedkeys authorized_keys_command_user=nobody cue [cue_prompt=long long prompt]
```

## Use Variables in Arguments
Expand Down
2 changes: 2 additions & 0 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ pub enum RsshErr {
OptNameErr(String),
OptValEmptyErr(String),
OptVarErr(String),
SendPromptErr,
}

impl RsshErr {
Expand Down Expand Up @@ -55,6 +56,7 @@ impl Display for RsshErr {
RsshErr::OptNameErr(name) => format!("Unknown option name `{}`", name),
RsshErr::OptValEmptyErr(name) => format!("Value of option `{}` is empty", name),
RsshErr::OptVarErr(name) => format!("Failed to evaluate variables in option `{}`", name),
RsshErr::SendPromptErr => S!("Failed to send prompt message"),
};
f.write_str(&msg)
}
Expand Down
35 changes: 28 additions & 7 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,17 @@ mod ssh_agent_auth;
mod pam_items;

use log::*;
use pam::constants::{PamFlag, PamResultCode};
use pam::constants::{PamFlag, PamResultCode, PAM_TEXT_INFO};
use pam::items::User;
use pam::module::{PamHandle, PamHooks};
use pam::module::{PamHandle, PamHooks, PamResult};
use ssh_agent::proto::KeyTypeEnum;
use ssh_agent::proto::public_key::PublicKey;
use syslog::{BasicLogger, Facility, Formatter3164};

use std::ffi::CStr;
use std::str::FromStr;

use pam::conv::Conv;
use crate::error::RsshErr::SendPromptErr;
use self::error::RsshErr;
use self::logger::ConsoleLogger;

Expand Down Expand Up @@ -56,7 +57,7 @@ fn read_authorized_keys(pamh: &PamHandle, auth_key_file: &str) -> Result<Vec<Pub
fn retrieve_authorized_keys_from_cmd(pamh: &PamHandle, auth_key_cmd: &str, run_as_user: &str) -> Result<Vec<PublicKey>, ErrType> {
let auth_user = get_username_from_pam(pamh)?;
debug!("Run command `{}` as user `{}`", auth_key_cmd, run_as_user);
let content = auth_keys::run_authorized_keys_cmd(&auth_key_cmd,
let content = auth_keys::run_authorized_keys_cmd(&auth_key_cmd,
auth_user,
if run_as_user.is_empty() { auth_user } else { run_as_user })?;
auth_keys::parse_content_of_authorized_keys(&content)
Expand Down Expand Up @@ -114,6 +115,8 @@ impl PamHooks for PamRssh {
let mut auth_key_file = String::new();
let mut authorized_keys_command = String::new();
let mut authorized_keys_command_user = String::new();
let mut cue = false;
let mut cue_prompt = "Please touch the device.".to_string();
for carg in args {
let kv: Vec<&str> = carg.to_str().unwrap_or("").splitn(2, '=').collect();
if kv.len() == 0 {
Expand Down Expand Up @@ -146,6 +149,11 @@ impl PamHooks for PamRssh {
non_empty_option_check(&kv)?;
authorized_keys_command_user = substitute_variables(&kv, &pam_vars)?;
}
"cue" => cue = true,
"cue_prompt" => {
non_empty_option_check(&kv)?;
cue_prompt = substitute_variables(&kv, &pam_vars)?;
}
_ => {
return Err(RsshErr::OptNameErr(kv[0].to_string()).into_ptr());
}
Expand Down Expand Up @@ -187,9 +195,22 @@ impl PamHooks for PamRssh {
return PamResultCode::PAM_CRED_INSUFFICIENT;
}
};

let send_prompt = || -> PamResult<()> {
if cue {
pamh.get_item::<Conv>()?
.ok_or(PamResultCode::PAM_BUF_ERR)?
.send(PAM_TEXT_INFO, &cue_prompt)?;
}
Ok(())
};
if let Err(e) = send_prompt() {
error!("{}", SendPromptErr);
return e;
}

let mut agent = ssh_agent_auth::AgentClient::new(ssh_agent_addr.as_str());
let result = agent.list_identities().and_then(|client_keys| {
let result = agent.list_identities().map(|client_keys| {
debug!("SSH-Agent reports {} keys", client_keys.len());
for (i, key) in client_keys.iter().enumerate() {
if !is_key_authorized(&key, &authorized_keys) {
Expand All @@ -200,7 +221,7 @@ impl PamHooks for PamRssh {
match authenticate_via_agent(&mut agent, &key) {
Ok(_) => {
info!("Successful authentication");
return Ok(true);
return true;
}
Err(e) => {
warn!("Failed to authenticate key {}: {}", i, e);
Expand All @@ -209,7 +230,7 @@ impl PamHooks for PamRssh {
}
}
warn!("None of these keys passed authentication");
Ok(false)
false
});
match result {
Ok(true) => PamResultCode::PAM_SUCCESS,
Expand Down

0 comments on commit 1b921d0

Please sign in to comment.