From 44bde3bb76a6dbfea5b15bd067db94d2539e7bf3 Mon Sep 17 00:00:00 2001 From: Dominik George <dominik.george@teckids.org> Date: Thu, 13 May 2021 18:44:30 +0200 Subject: [PATCH] [NSS] Implement reverse-mapping of getent search fields Closes #7 --- etc/nss_pam_oidc.example.toml | 3 +++ src/nss.rs | 2 +- src/oauth.rs | 46 +++++++++++++++++++++++++++++------ 3 files changed, 43 insertions(+), 8 deletions(-) diff --git a/etc/nss_pam_oidc.example.toml b/etc/nss_pam_oidc.example.toml index a1d6291..2b37ec5 100644 --- a/etc/nss_pam_oidc.example.toml +++ b/etc/nss_pam_oidc.example.toml @@ -32,3 +32,6 @@ maps.passwd = """ shell: .login_shell } """ + +# Reverse mapping to make sure uid lookups on entries mapped above still work +maps.rev.passwd.by_uid = ". - 10000" diff --git a/src/nss.rs b/src/nss.rs index b7bbad8..dca4b9f 100644 --- a/src/nss.rs +++ b/src/nss.rs @@ -119,7 +119,7 @@ impl PasswdHooks for OidcPasswd { } }; - let data: Passwd = match get_data_jq(&conf, "nss", "passwd.by_uid", uid.to_string(), &token, false).map(|PasswdHelper(p)| p) { + let data: Passwd = match get_data_jq(&conf, "nss", "passwd.by_uid", uid, &token, false).map(|PasswdHelper(p)| p) { Ok(d) => d, Err(e) => { error!("Could not load JSON data for passwd: {}", e); diff --git a/src/oauth.rs b/src/oauth.rs index 2e0a500..72f3f62 100644 --- a/src/oauth.rs +++ b/src/oauth.rs @@ -39,7 +39,7 @@ use oauth2::reqwest::http_client; use std::error; -use serde::Deserialize; +use serde::{Deserialize, Serialize}; use reqwest; use serde_json; @@ -151,15 +151,23 @@ fn get_data(conf: &Config, prefix: &str, endpoint: &str, param: String, token: & .text()?) } -fn get_jq_prog(conf: &Config, prefix: &str, endpoint: &str) -> Option<String> { - match get_optional(&conf, &full_key(vec![prefix, "maps", endpoint])) { +fn get_jq_prog(conf: &Config, prefix: &str, endpoint: &str, rev: bool) -> Option<String> { + let prog_key = match rev { + false => full_key(vec![prefix, "maps", endpoint]), + true => full_key(vec![prefix, "maps.rev", endpoint]), + }; + match get_optional(&conf, &prog_key) { Some(v) => Some (v), None => { + if rev { + // Do not fallback to more generic program for reverse mapping + return None; + } // Try falling back to more generic program match endpoint.find('.') { Some(i) => { debug!("JQ mapping program for {} not found; trying more generic definition", endpoint); - get_jq_prog(conf, prefix, &endpoint[..i]) + get_jq_prog(conf, prefix, &endpoint[..i], rev) }, None => None } @@ -167,11 +175,11 @@ fn get_jq_prog(conf: &Config, prefix: &str, endpoint: &str) -> Option<String> { } } -pub fn get_data_jq<T: for<'de> Deserialize<'de>>(conf: &Config, prefix: &str, endpoint: &str, param: String, token: &BasicTokenResponse, multi: bool) -> Result<T, Box<dyn error::Error>> { - let res: Option<String> = get_jq_prog(&conf, prefix, endpoint); +pub fn get_data_jq<T: for<'de> Deserialize<'de>, V: Serialize>(conf: &Config, prefix: &str, endpoint: &str, param: V, token: &BasicTokenResponse, multi: bool) -> Result<T, Box<dyn error::Error>> { + let res: Option<String> = get_jq_prog(&conf, prefix, endpoint, false); let jq_code = match res { Some(s) => { - debug!("Found jq mapping program for endpoint {}", endpoint); + debug!("Found jq mapping program for endpoint {}: {}", endpoint, s); match multi { true => "map(".to_string() + &s + ")", false => s @@ -183,6 +191,30 @@ pub fn get_data_jq<T: for<'de> Deserialize<'de>>(conf: &Config, prefix: &str, en } }; let mut jq_prog = jq_rs::compile(&jq_code)?; + let res: Option<String> = get_jq_prog(&conf, prefix, endpoint, true); + let jq_code = match res { + Some(s) => { + debug!("Found jq reverse mapping program for endpoint {}: {}", endpoint, s); + s + }, + None => { + debug!("No jq reverse mapping program for endpoint {}; using default (no-op)", endpoint); + ".".to_string() + } + }; + let mut jq_prog_rev = jq_rs::compile(&jq_code)?; + + // Convert and transform the passed param using the reverse JQ mapping program + // 1. Serialize into JSON value (atomic) to be bale to pass into jq + let param_serialized = serde_json::to_string(¶m)?; + // 2. Transform using the JQ program loaded above + let param_trans = jq_prog_rev.run(¶m_serialized)?.trim().to_string(); + // 3. Deserialize into serde_json value so we get numbers as numbers, strings properly unquoted + let param_deserialized: serde_json::Value = serde_json::from_str(¶m_trans)?; + let param = match param_deserialized { + serde_json::Value::String(v) => v, // We want strings verbatim without JSON quoting + _ => param_deserialized.to_string() // We want numbers converted to string + }; let data_raw = get_data(&conf, prefix, endpoint, param, token)?; let data_trans = jq_prog.run(&data_raw)?; -- GitLab