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

feat: add pam interaction prompt #24

Merged
merged 1 commit into from
Dec 29, 2024
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
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
Loading