-
Notifications
You must be signed in to change notification settings - Fork 9
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add Rust crate to act as the NSS client
- Loading branch information
1 parent
bc5adfc
commit c25609c
Showing
10 changed files
with
1,832 additions
and
0 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
[workspace] | ||
members = ["nss/"] | ||
exclude = ["vendor_rust/"] | ||
resolver = "2" | ||
|
||
[profile.release] | ||
lto = "thin" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
[package] | ||
name = "nss" | ||
version = "0.1.0" | ||
edition = "2021" | ||
|
||
[lib] | ||
crate-type = ["cdylib"] | ||
path = "src/lib.rs" | ||
name = "nss_authd" | ||
|
||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html | ||
|
||
[dependencies] | ||
libnss = "0.5.0" | ||
lazy_static = "1.4.0" | ||
libc = "0.2.147" | ||
paste = "1.0.14" | ||
tonic = "0.10.0" | ||
prost = "0.12.0" | ||
tokio = { version = "1.32.0", features = ["macros", "rt-multi-thread"] } | ||
tower = "0.4.13" | ||
log = "0.4.20" | ||
simple_logger = {version = "4.2.0", features = ["stderr"]} | ||
syslog = "6.1.0" | ||
ctor = "0.2.4" | ||
|
||
[build-dependencies] | ||
tonic-build = "0.10.0" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
fn main() -> Result<(), Box<dyn std::error::Error>> { | ||
tonic_build::configure() | ||
.build_server(false) | ||
.compile(&["../authd.proto"], &["../"])?; | ||
Ok(()) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
use authd::nss_client::NssClient; | ||
use std::error::Error; | ||
use tokio::net::UnixStream; | ||
use tonic::transport::{Channel, Endpoint, Uri}; | ||
use tower::service_fn; | ||
|
||
use crate::debug; | ||
|
||
pub mod authd { | ||
tonic::include_proto!("authd"); | ||
} | ||
|
||
/// new_client creates a new client connection to the gRPC server or returns an active one. | ||
pub async fn new_client() -> Result<NssClient<Channel>, Box<dyn Error>> { | ||
debug!("Connecting to authd on {}...", super::SOCKET_PATH); | ||
|
||
let ch = Endpoint::try_from("http://[::]:50051")? | ||
.connect_with_connector(service_fn(|_: Uri| UnixStream::connect(super::SOCKET_PATH))) | ||
.await?; | ||
|
||
Ok(NssClient::new(ch)) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,107 @@ | ||
use crate::error; | ||
use libc::gid_t; | ||
use libnss::group::{Group, GroupHooks}; | ||
use libnss::interop::Response; | ||
use tonic::Request; | ||
|
||
use crate::client::{self, authd}; | ||
use authd::GroupEntry; | ||
|
||
pub struct AuthdGroup; | ||
impl GroupHooks for AuthdGroup { | ||
/// get_all_entries returns all group entries. | ||
fn get_all_entries() -> Response<Vec<Group>> { | ||
get_all_entries() | ||
} | ||
|
||
/// get_entry_by_gid returns the group entry for the given gid. | ||
fn get_entry_by_gid(gid: gid_t) -> Response<Group> { | ||
get_entry_by_gid(gid) | ||
} | ||
|
||
/// get_entry_by_name returns the group entry for the given name. | ||
fn get_entry_by_name(name: String) -> Response<Group> { | ||
get_entry_by_name(name) | ||
} | ||
} | ||
|
||
/// get_entry_by_name connects to the grpc server and asks for all group entries. | ||
fn get_all_entries() -> Response<Vec<Group>> { | ||
super::RT.block_on(async { | ||
let mut client = match client::new_client().await { | ||
Ok(c) => c, | ||
Err(e) => { | ||
error!("could not connect to gRPC server: {}", e); | ||
return Response::Unavail; | ||
} | ||
}; | ||
|
||
let request = Request::new(authd::Empty {}); | ||
match client.get_group_entries(request).await { | ||
Ok(r) => Response::Success(group_entries_to_groups(r.into_inner().entries)), | ||
Err(e) => { | ||
error!("error when listing groups: {}", e); | ||
Response::NotFound | ||
} | ||
} | ||
}) | ||
} | ||
|
||
/// get_entry_by_name connects to the grpc server and asks for the group entry with the given gid. | ||
fn get_entry_by_gid(gid: gid_t) -> Response<Group> { | ||
super::RT.block_on(async { | ||
let mut client = match client::new_client().await { | ||
Ok(c) => c, | ||
Err(e) => { | ||
error!("could not connect to gRPC server: {}", e); | ||
return Response::Unavail; | ||
} | ||
}; | ||
|
||
let req = Request::new(authd::GetByIdRequest { id: gid }); | ||
match client.get_group_by_gid(req).await { | ||
Ok(r) => Response::Success(group_entry_to_group(r.into_inner())), | ||
Err(e) => { | ||
error!("error when getting group by gid: {}", e); | ||
Response::NotFound | ||
} | ||
} | ||
}) | ||
} | ||
|
||
/// get_entry_by_name connects to the grpc server and asks for the group entry with the given name. | ||
fn get_entry_by_name(name: String) -> Response<Group> { | ||
super::RT.block_on(async { | ||
let mut client = match client::new_client().await { | ||
Ok(c) => c, | ||
Err(e) => { | ||
error!("could not connect to gRPC server: {}", e); | ||
return Response::Unavail; | ||
} | ||
}; | ||
|
||
let req = Request::new(authd::GetByNameRequest { name }); | ||
match client.get_group_by_name(req).await { | ||
Ok(r) => Response::Success(group_entry_to_group(r.into_inner())), | ||
Err(e) => { | ||
error!("error when getting group by name: {}", e); | ||
Response::NotFound | ||
} | ||
} | ||
}) | ||
} | ||
|
||
/// group_entry_to_group converts a GroupEntry to a libnss::Group. | ||
fn group_entry_to_group(entry: GroupEntry) -> Group { | ||
Group { | ||
name: entry.name, | ||
passwd: entry.passwd, | ||
gid: entry.gid, | ||
members: entry.members, | ||
} | ||
} | ||
|
||
/// group_entries_to_groups converts a Vec<GroupEntry> to a Vec<libnss::Group>. | ||
fn group_entries_to_groups(entries: Vec<GroupEntry>) -> Vec<Group> { | ||
entries.into_iter().map(group_entry_to_group).collect() | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
#[macro_use] | ||
extern crate lazy_static; | ||
use libnss::{libnss_group_hooks, libnss_passwd_hooks, libnss_shadow_hooks}; | ||
|
||
mod passwd; | ||
use passwd::AuthdPasswd; | ||
libnss_passwd_hooks!(authd, AuthdPasswd); | ||
|
||
mod group; | ||
use group::AuthdGroup; | ||
libnss_group_hooks!(authd, AuthdGroup); | ||
|
||
mod shadow; | ||
use shadow::AuthdShadow; | ||
use tokio::runtime::{Builder, Runtime}; | ||
libnss_shadow_hooks!(authd, AuthdShadow); | ||
|
||
mod logs; | ||
|
||
mod client; | ||
|
||
const SOCKET_PATH: &str = "/run/authd.sock"; | ||
|
||
lazy_static! { | ||
pub static ref RT: Runtime = Builder::new_current_thread().enable_all().build().unwrap(); | ||
} | ||
|
||
#[ctor::ctor] | ||
/// init_logger is a constructor that ensures the logger object initialization only happens once per | ||
/// library invocation in order to avoid races to the log file. | ||
fn init_logger() { | ||
logs::init_logger(); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
use log::{LevelFilter, Metadata}; | ||
use simple_logger::SimpleLogger; | ||
use std::env; | ||
use syslog::{BasicLogger, Facility, Formatter3164}; | ||
|
||
#[macro_export] | ||
macro_rules! debug { | ||
($($arg:tt)*) => { | ||
let log_prefix = "authd:"; | ||
log::debug!("{} {}", log_prefix, format_args!($($arg)*)); | ||
} | ||
} | ||
|
||
#[macro_export] | ||
macro_rules! error { | ||
($($arg:tt)*) => { | ||
let log_prefix = "authd:"; | ||
log::error!("{} {}", log_prefix, format_args!($($arg)*)); | ||
} | ||
} | ||
|
||
/// init_logger initialize the global logger with a default level set to info. This function is only | ||
/// required to be called once and is a no-op on subsequent calls. | ||
/// | ||
/// The log level can be set to debug by setting the environment variable NSS_AUTHD_DEBUG. | ||
pub fn init_logger() { | ||
if log::logger().enabled(&Metadata::builder().build()) { | ||
return; | ||
} | ||
|
||
let mut level = LevelFilter::Info; | ||
if let Ok(target) = env::var("NSS_AUTHD_DEBUG") { | ||
level = LevelFilter::Debug; | ||
match target { | ||
s if s == *"stderr" => init_stderr_logger(level), | ||
_ => init_sys_logger(level), | ||
} | ||
} else { | ||
init_sys_logger(level); | ||
} | ||
|
||
debug!("Log level set to {:?}", level); | ||
} | ||
|
||
/// init_sys_logger initializes a global log that prints messages to the system logs. | ||
fn init_sys_logger(log_level: LevelFilter) { | ||
let formatter = Formatter3164 { | ||
facility: Facility::LOG_USER, | ||
hostname: None, | ||
process: "authd".into(), | ||
pid: 0, | ||
}; | ||
|
||
let logger = match syslog::unix(formatter) { | ||
Err(err) => { | ||
println!("cannot connect to syslog: {err:?}"); | ||
return; | ||
} | ||
Ok(l) => l, | ||
}; | ||
|
||
if let Err(err) = log::set_boxed_logger(Box::new(BasicLogger::new(logger))) | ||
.map(|()| log::set_max_level(log_level)) | ||
{ | ||
eprintln!("cannot set log level: {err:?}"); | ||
return; | ||
}; | ||
|
||
debug!("Log output set to syslog"); | ||
} | ||
|
||
/// init_stderr_logger initializes a global log that prints the messages to stderr. | ||
fn init_stderr_logger(log_level: LevelFilter) { | ||
SimpleLogger::new().with_level(log_level).init().unwrap(); | ||
debug!("Log output set to stderr"); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,110 @@ | ||
use crate::error; | ||
use libc::uid_t; | ||
use libnss::interop::Response; | ||
use libnss::passwd::{Passwd, PasswdHooks}; | ||
use tonic::Request; | ||
|
||
use crate::client::{self, authd}; | ||
use authd::PasswdEntry; | ||
|
||
pub struct AuthdPasswd; | ||
impl PasswdHooks for AuthdPasswd { | ||
/// get_all_entries returns all passwd entries. | ||
fn get_all_entries() -> Response<Vec<Passwd>> { | ||
get_all_entries() | ||
} | ||
|
||
/// get_entry_by_uid returns the passwd entry for the given uid. | ||
fn get_entry_by_uid(uid: uid_t) -> Response<Passwd> { | ||
get_entry_by_uid(uid) | ||
} | ||
|
||
/// get_entry_by_name returns the passwd entry for the given name. | ||
fn get_entry_by_name(name: String) -> Response<Passwd> { | ||
get_entry_by_name(name) | ||
} | ||
} | ||
|
||
/// get_all_entries connects to the grpc server and asks for all passwd entries. | ||
pub fn get_all_entries() -> Response<Vec<Passwd>> { | ||
super::RT.block_on(async { | ||
let mut client = match client::new_client().await { | ||
Ok(c) => c, | ||
Err(e) => { | ||
error!("could not connect to gRPC server: {}", e); | ||
return Response::Unavail; | ||
} | ||
}; | ||
|
||
let req = Request::new(authd::Empty {}); | ||
match client.get_passwd_entries(req).await { | ||
Ok(r) => Response::Success(passwd_entries_to_passwds(r.into_inner().entries)), | ||
Err(e) => { | ||
error!("error when listing passwd: {}", e); | ||
Response::NotFound | ||
} | ||
} | ||
}) | ||
} | ||
|
||
/// get_entry_by_uid connects to the grpc server and asks for the passwd entry with the given uid. | ||
pub fn get_entry_by_uid(uid: uid_t) -> Response<Passwd> { | ||
super::RT.block_on(async { | ||
let mut client = match client::new_client().await { | ||
Ok(c) => c, | ||
Err(e) => { | ||
error!("could not connect to gRPC server: {}", e); | ||
return Response::Unavail; | ||
} | ||
}; | ||
|
||
let req = Request::new(authd::GetByIdRequest { id: uid }); | ||
match client.get_passwd_by_uid(req).await { | ||
Ok(r) => Response::Success(passwd_entry_to_passwd(r.into_inner())), | ||
Err(e) => { | ||
error!("error when getting passwd by uid: {}", e); | ||
Response::NotFound | ||
} | ||
} | ||
}) | ||
} | ||
|
||
/// get_entry_by_name connects to the grpc server and asks for the passwd entry with the given name. | ||
pub fn get_entry_by_name(name: String) -> Response<Passwd> { | ||
super::RT.block_on(async { | ||
let mut client = match client::new_client().await { | ||
Ok(c) => c, | ||
Err(e) => { | ||
error!("could not connect to gRPC server: {}", e); | ||
return Response::Unavail; | ||
} | ||
}; | ||
|
||
let req = Request::new(authd::GetByNameRequest { name }); | ||
match client.get_passwd_by_name(req).await { | ||
Ok(r) => Response::Success(passwd_entry_to_passwd(r.into_inner())), | ||
Err(e) => { | ||
error!("error when getting passwd by name: {}", e); | ||
Response::NotFound | ||
} | ||
} | ||
}) | ||
} | ||
|
||
/// passwd_entry_to_passwd converts a PasswdEntry to a libnss::Passwd. | ||
fn passwd_entry_to_passwd(entry: PasswdEntry) -> Passwd { | ||
Passwd { | ||
name: entry.name, | ||
passwd: entry.passwd, | ||
uid: entry.uid, | ||
gid: entry.gid, | ||
gecos: entry.gecos, | ||
dir: entry.homedir, | ||
shell: entry.shell, | ||
} | ||
} | ||
|
||
/// passwd_entries_to_passwds converts a Vec<PasswdEntry> to a Vec<libnss::Passwd>. | ||
fn passwd_entries_to_passwds(entries: Vec<PasswdEntry>) -> Vec<Passwd> { | ||
entries.into_iter().map(passwd_entry_to_passwd).collect() | ||
} |
Oops, something went wrong.