Skip to content

Commit

Permalink
Add Rust crate to act as the NSS client
Browse files Browse the repository at this point in the history
  • Loading branch information
denisonbarbosa committed Sep 15, 2023
1 parent bc5adfc commit c25609c
Show file tree
Hide file tree
Showing 10 changed files with 1,832 additions and 0 deletions.
1,358 changes: 1,358 additions & 0 deletions Cargo.lock

Large diffs are not rendered by default.

7 changes: 7 additions & 0 deletions Cargo.toml
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"
28 changes: 28 additions & 0 deletions nss/Cargo.toml
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"
6 changes: 6 additions & 0 deletions nss/build.rs
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(())
}
22 changes: 22 additions & 0 deletions nss/src/client/mod.rs
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))
}
107 changes: 107 additions & 0 deletions nss/src/group/mod.rs
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()
}
33 changes: 33 additions & 0 deletions nss/src/lib.rs
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();
}
76 changes: 76 additions & 0 deletions nss/src/logs/mod.rs
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");
}
110 changes: 110 additions & 0 deletions nss/src/passwd/mod.rs
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()
}
Loading

0 comments on commit c25609c

Please sign in to comment.