diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..0e082fb --- /dev/null +++ b/.dockerignore @@ -0,0 +1,10 @@ +* +!docker/**/run.sh +!crates/ +!lib/ +lib/index.node +!src/ +!test/ +!Cargo.* +!package* +!install_krb5.sh diff --git a/.gitignore b/.gitignore index bdf9a5b..630a0f5 100644 --- a/.gitignore +++ b/.gitignore @@ -6,4 +6,7 @@ .vscode package-lock.json yarn.lock -.vagrant/ \ No newline at end of file +.vagrant/ +target/ +lib/index.node +vendor/ diff --git a/.npmignore b/.npmignore index 378eac2..7a86917 100644 --- a/.npmignore +++ b/.npmignore @@ -1 +1 @@ -build +lib/index.node diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..c4bbf3c --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,414 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "aho-corasick" +version = "0.7.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac" +dependencies = [ + "memchr", +] + +[[package]] +name = "base64" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a" + +[[package]] +name = "bindgen" +version = "0.63.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36d860121800b2a9a94f9b5604b332d5cffb234ce17609ea479d723dbc9d3885" +dependencies = [ + "bitflags", + "cexpr", + "clang-sys", + "lazy_static", + "lazycell", + "log", + "peeking_take_while", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex", + "syn", + "which", +] + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "cc" +version = "1.0.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a20104e2335ce8a659d6dd92a51a767a0c062599c73b343fd152cb401e828c3d" + +[[package]] +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "clang-sys" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa2e27ae6ab525c3d369ded447057bca5438d86dc3a68f6faafb8269ba82ebf3" +dependencies = [ + "glob", + "libc", + "libloading", +] + +[[package]] +name = "convert_case" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "ctor" +version = "0.1.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d2301688392eb071b0bf1a37be05c469d3cc4dbbd95df672fe28ab021e6a096" +dependencies = [ + "quote", + "syn", +] + +[[package]] +name = "either" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797" + +[[package]] +name = "glob" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" + +[[package]] +name = "krb5-js" +version = "0.1.0" +dependencies = [ + "base64", + "krb5-rs", + "lazy_static", + "napi", + "napi-build", + "napi-derive", + "regex", + "thiserror", +] + +[[package]] +name = "krb5-rs" +version = "0.1.0" +dependencies = [ + "krb5-sys", + "thiserror", +] + +[[package]] +name = "krb5-sys" +version = "0.1.0" +dependencies = [ + "bindgen", + "cc", + "pkg-config", + "winreg", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "lazycell" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" + +[[package]] +name = "libc" +version = "0.2.139" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79" + +[[package]] +name = "libloading" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" +dependencies = [ + "cfg-if", + "winapi", +] + +[[package]] +name = "log" +version = "0.4.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "memchr" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "napi" +version = "2.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b60e2bc632f89dad5d47da61591bd468f87f5dbfc85af27aa876772c89d8c4e4" +dependencies = [ + "bitflags", + "ctor", + "napi-sys", + "once_cell", + "thread_local", +] + +[[package]] +name = "napi-build" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebd4419172727423cf30351406c54f6cc1b354a2cfb4f1dba3e6cd07f6d5522b" + +[[package]] +name = "napi-derive" +version = "2.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47bff5a8ed70117bce55053a74ff8f423f90c48c704030609272e6e3bde1c5a4" +dependencies = [ + "convert_case", + "napi-derive-backend", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "napi-derive-backend" +version = "1.0.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13b412301aeebee17724fff6d73536e9ecb8387f10bbbf317a9f7a006cc1c5b" +dependencies = [ + "convert_case", + "once_cell", + "proc-macro2", + "quote", + "regex", + "syn", +] + +[[package]] +name = "napi-sys" +version = "2.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "166b5ef52a3ab5575047a9fe8d4a030cdd0f63c96f071cd6907674453b07bae3" +dependencies = [ + "libloading", +] + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "once_cell" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f61fba1741ea2b3d6a1e3178721804bb716a68a6aeba1149b5d52e3d464ea66" + +[[package]] +name = "peeking_take_while" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" + +[[package]] +name = "pkg-config" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160" + +[[package]] +name = "proc-macro2" +version = "1.0.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ef7d57beacfaf2d8aee5937dab7b7f28de3cb8b1828479bb5de2a7106f2bae2" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "regex" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48aaa5748ba571fb95cd2c85c09f629215d3a6ece942baa100950af03a34f733" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.6.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" + +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + +[[package]] +name = "shlex" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3" + +[[package]] +name = "syn" +version = "1.0.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "1.0.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a9cd18aa97d5c45c6603caea1da6628790b37f7a34b6ca89522331c5180fed0" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fb327af4685e4d03fa8cbcf1716380da910eeb2bb8be417e7f9fd3fb164f36f" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thread_local" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5516c27b78311c50bf42c071425c560ac799b11c30b31f87e3081965fe5e0180" +dependencies = [ + "once_cell", +] + +[[package]] +name = "unicode-ident" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc" + +[[package]] +name = "unicode-segmentation" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fdbf052a0783de01e944a6ce7a8cb939e295b1e7be835a1112c3b9a7f047a5a" + +[[package]] +name = "which" +version = "4.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2441c784c52b289a054b7201fc93253e288f094e2f4be9058343127c4226a269" +dependencies = [ + "either", + "libc", + "once_cell", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "winreg" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d" +dependencies = [ + "winapi", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..1f5e930 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,9 @@ +[workspace] +members = ["crates/krb5_sys", "crates/krb5_rs", "crates/krb5_js"] +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + + +[profile.release] +lto = true +opt-level = 3 +codegen-units = 1 diff --git a/binding.gyp b/binding.gyp deleted file mode 100644 index 7a1a889..0000000 --- a/binding.gyp +++ /dev/null @@ -1,95 +0,0 @@ -{ - "targets": [{ - "target_name": "krb5", - "sources": [ - "./src/module.cc", - "./src/krb5_bind.cc", - "./src/gss_bind.cc" - ], - 'include_dirs': [" for Error { + fn from(error: Krb5Error) -> Self { + Error::Library(error) + } +} + +impl From for napi::Error { + fn from(error: Error) -> Self { + napi::Error::from_reason(error.to_string()) + } +} diff --git a/crates/krb5_js/src/kdestroy.rs b/crates/krb5_js/src/kdestroy.rs new file mode 100644 index 0000000..c336951 --- /dev/null +++ b/crates/krb5_js/src/kdestroy.rs @@ -0,0 +1,42 @@ +use crate::error::Error; +use krb5_rs::{CCache, Context}; + +use napi::{Env, JsUndefined, Task}; +use napi_derive::napi; + +#[napi(object)] +#[derive(Debug)] +pub struct KdestroyParameters { + pub ccname: Option, +} + +pub fn kdestroy_function( + KdestroyParameters { ccname }: &mut KdestroyParameters, +) -> std::result::Result<(), Error> { + let context = Context::new()?; + let ccache = match ccname { + Some(ref cc_name) => CCache::resolve(&context, cc_name)?, + None => CCache::default(&context)?, + }; + ccache.destroy()?; + + Ok(()) +} + +pub struct Kdestroy { + pub input: KdestroyParameters, +} + +#[napi] +impl Task for Kdestroy { + type Output = (); + type JsValue = JsUndefined; + + fn compute(&mut self) -> napi::Result { + kdestroy_function(&mut self.input).map_err(napi::Error::from) + } + + fn resolve(&mut self, env: Env, _output: Self::Output) -> napi::Result { + env.get_undefined() + } +} diff --git a/crates/krb5_js/src/kinit.rs b/crates/krb5_js/src/kinit.rs new file mode 100644 index 0000000..b5f6a81 --- /dev/null +++ b/crates/krb5_js/src/kinit.rs @@ -0,0 +1,90 @@ +use std::path::Path; + +use napi::{Env, JsString, Task}; +use napi_derive::napi; + +use krb5_rs::{CCache, Context, Credentials, Keytab, Principal}; + +use crate::error::Error; + +#[napi(object)] +#[derive(Debug)] +pub struct KinitParameters { + pub principal: String, + pub password: Option, + pub keytab: Option, + pub realm: Option, + pub ccname: Option, +} + +pub fn kinit_function( + KinitParameters { + principal, + password, + keytab, + realm, + ccname, + }: &mut KinitParameters, +) -> Result { + if password.is_none() && keytab.is_none() { + return Err(Error::MissingPasswordOrKeytab); + } + + let (principal, realm_from_split) = if principal.contains('@') { + let splits: Vec<&str> = principal.split('@').collect(); + if splits.len() > 2 { + return Err(Error::Generic(String::from( + "Principal can contain at most one `@`", + ))); + } + (splits[0], Some(splits[1])) + } else { + (principal.as_str(), None) + }; + let context = Context::new()?; + let realm = match realm_from_split.or(realm.as_deref()) { + Some(realm) => realm.to_owned(), + None => context.get_default_realm()?, + }; + let principal = Principal::build_principal(&context, realm.as_str(), principal)?; + let ccache = match ccname { + Some(ref cc_name) => CCache::resolve(&context, cc_name)?, + None => CCache::default(&context)?, + }; + let credentials = match keytab { + Some(ref keytab) => { + let keytab = Keytab::resolve(&context, keytab)?; + Credentials::get_init_credentials_keytab(&context, &principal, &keytab)? + } + None => Credentials::get_init_credentials_password( + &context, + &principal, + password.as_ref().unwrap(), + )?, + }; + let ccache_path = ccache.name()?; + if !Path::new(&ccache_path).exists() { + ccache.initialize(&principal)?; + } + ccache.store_creds(credentials)?; + + Ok(ccache_path) +} + +pub struct Kinit { + pub input: KinitParameters, +} + +#[napi] +impl Task for Kinit { + type Output = String; + type JsValue = JsString; + + fn compute(&mut self) -> napi::Result { + kinit_function(&mut self.input).map_err(napi::Error::from) + } + + fn resolve(&mut self, env: Env, output: Self::Output) -> napi::Result { + env.create_string(&output) + } +} diff --git a/crates/krb5_js/src/lib.rs b/crates/krb5_js/src/lib.rs new file mode 100644 index 0000000..112df73 --- /dev/null +++ b/crates/krb5_js/src/lib.rs @@ -0,0 +1,25 @@ +pub(crate) mod error; +mod kdestroy; +mod kinit; +mod spnego; + +use kdestroy::{Kdestroy, KdestroyParameters}; +use kinit::{Kinit, KinitParameters}; +use napi::bindgen_prelude::AsyncTask; +use napi_derive::napi; +use spnego::{GenerateSpnegoTokenParameters, Spnego}; + +#[napi] +pub fn kinit(input: KinitParameters) -> AsyncTask { + AsyncTask::new(Kinit { input }) +} + +#[napi(ts_return_type = "Promise")] +pub fn kdestroy(input: KdestroyParameters) -> AsyncTask { + AsyncTask::new(Kdestroy { input }) +} + +#[napi] +pub fn spnego(input: GenerateSpnegoTokenParameters) -> AsyncTask { + AsyncTask::new(Spnego { input }) +} diff --git a/crates/krb5_js/src/spnego.rs b/crates/krb5_js/src/spnego.rs new file mode 100644 index 0000000..83e1c82 --- /dev/null +++ b/crates/krb5_js/src/spnego.rs @@ -0,0 +1,75 @@ +use base64::{engine, Engine}; +use krb5_rs::gssapi; +use napi::{Env, JsString, Task}; +use napi_derive::napi; + +use lazy_static::lazy_static; +use regex::Regex; + +lazy_static! { + static ref RE: Regex = Regex::new(".*[@]").unwrap(); +} + +#[napi(object)] +#[derive(Debug)] +pub struct GenerateSpnegoTokenParameters { + #[napi(js_name = "service_principal")] + pub service_principal: Option, + #[napi(js_name = "service_fqdn")] + pub service_fqdn: Option, + #[napi(js_name = "hostbased_service")] + pub hostbased_service: Option, + pub ccname: Option, +} + +pub fn generate_spnego_token_function( + GenerateSpnegoTokenParameters { + service_principal, + service_fqdn, + hostbased_service, + ccname, + }: &mut GenerateSpnegoTokenParameters, +) -> std::result::Result { + let (service_principal, input_name_type) = if let Some(service) = service_principal { + (service.to_owned(), "GSS_C_NT_USER_NAME") + } else { + let mut service = match service_fqdn { + Some(service) => service.to_string(), + None => hostbased_service + .as_ref() + .ok_or("Invalid service principal in input")? + .to_string(), + }; + + if !RE.is_match(&service) { + service.insert_str(0, "HTTP@"); + } + (service.to_owned(), "GSS_C_NT_HOSTBASED_SERVICE") + }; + + let target_name = gssapi::import_name(&service_principal, input_name_type)?; + + if ccname.is_some() { + gssapi::krb5_ccache_name(ccname.as_ref().unwrap())?; + } + let token = gssapi::get_token(target_name)?; + Ok(engine::general_purpose::STANDARD.encode(token)) +} + +pub struct Spnego { + pub input: GenerateSpnegoTokenParameters, +} + +#[napi] +impl Task for Spnego { + type Output = String; + type JsValue = JsString; + + fn compute(&mut self) -> napi::Result { + generate_spnego_token_function(&mut self.input).map_err(napi::Error::from_reason) + } + + fn resolve(&mut self, env: Env, output: Self::Output) -> napi::Result { + env.create_string(&output) + } +} diff --git a/crates/krb5_rs/Cargo.toml b/crates/krb5_rs/Cargo.toml new file mode 100644 index 0000000..0bc4550 --- /dev/null +++ b/crates/krb5_rs/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "krb5-rs" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +thiserror = "1" +krb5-sys = {path = "../krb5_sys"} diff --git a/crates/krb5_rs/src/ccache.rs b/crates/krb5_rs/src/ccache.rs new file mode 100644 index 0000000..f1e873b --- /dev/null +++ b/crates/krb5_rs/src/ccache.rs @@ -0,0 +1,86 @@ +use std::{ + ffi::{CStr, CString}, + mem::MaybeUninit, +}; + +use krb5_sys::{ + krb5_cc_close, krb5_cc_default, krb5_cc_destroy, krb5_cc_get_name, krb5_cc_initialize, + krb5_cc_resolve, krb5_cc_store_cred, krb5_ccache, +}; + +use super::{Context, Credentials, Krb5Error, Principal, Result}; + +pub struct CCache<'context> { + pub(crate) context: &'context Context, + pub(crate) inner: krb5_ccache, +} + +impl<'context> CCache<'context> { + pub fn default(context: &'context Context) -> Result { + let mut ccache: MaybeUninit = MaybeUninit::uninit(); + let error_code = unsafe { krb5_cc_default(context.inner, ccache.as_mut_ptr()) }; + Krb5Error::exit_if_library_error(context, error_code)?; + + Ok(CCache { + context, + inner: unsafe { ccache.assume_init() }, + }) + } + + pub fn resolve(context: &'context Context, cc_name: &str) -> Result> { + let mut ccache: MaybeUninit = MaybeUninit::uninit(); + + let cc_name = CString::new(cc_name).map_err(|_| Krb5Error::StringConversionError)?; + + let error_code = + unsafe { krb5_cc_resolve(context.inner, cc_name.as_ptr(), ccache.as_mut_ptr()) }; + Krb5Error::exit_if_library_error(context, error_code)?; + Ok(CCache { + context, + inner: unsafe { ccache.assume_init() }, + }) + } + + pub fn destroy(mut self) -> Result<()> { + if !self.context.inner.is_null() && !self.inner.is_null() { + let error_code = unsafe { krb5_cc_destroy(self.context.inner, self.inner) }; + self.inner = std::ptr::null_mut() as *mut _; + Krb5Error::exit_if_library_error(self.context, error_code)?; + } + Ok(()) + } + + pub fn initialize(&self, principal: &Principal) -> Result<()> { + let error_code = + unsafe { krb5_cc_initialize(self.context.inner, self.inner, principal.inner) }; + Krb5Error::exit_if_library_error(self.context, error_code)?; + + Ok(()) + } + + pub fn store_creds(&self, mut credentials: Credentials) -> Result<()> { + let error_code = + unsafe { krb5_cc_store_cred(self.context.inner, self.inner, &mut credentials.inner) }; + Krb5Error::exit_if_library_error(self.context, error_code)?; + Ok(()) + } + + pub fn name(&self) -> Result { + let ccache_name = unsafe { krb5_cc_get_name(self.context.inner, self.inner) }; + unsafe { + let cstr = CStr::from_ptr(ccache_name); + match cstr.to_owned().into_string() { + Ok(ccache_name) => Ok(ccache_name), + Err(_) => Err(Krb5Error::StringConversionError), + } + } + } +} + +impl<'context> Drop for CCache<'context> { + fn drop(&mut self) { + if !self.context.inner.is_null() && !self.inner.is_null() { + unsafe { krb5_cc_close(self.context.inner, self.inner) }; + } + } +} diff --git a/crates/krb5_rs/src/context.rs b/crates/krb5_rs/src/context.rs new file mode 100644 index 0000000..830fd10 --- /dev/null +++ b/crates/krb5_rs/src/context.rs @@ -0,0 +1,78 @@ +use std::{ffi::CStr, mem::MaybeUninit, os::raw::c_char, sync::Mutex}; + +use crate::Result; +use krb5_sys::{ + krb5_context, krb5_error_code, krb5_free_context, krb5_free_default_realm, + krb5_free_error_message, krb5_get_default_realm, krb5_get_error_message, krb5_init_context, +}; + +use super::Krb5Error; + +static CONTEXT_LOCK: Mutex<()> = Mutex::new(()); + +pub struct Context { + pub(crate) inner: krb5_context, +} + +impl Context { + pub fn get_default_realm(&self) -> Result { + let mut default_realm: MaybeUninit<*mut c_char> = MaybeUninit::zeroed(); + let error_code = unsafe { krb5_get_default_realm(self.inner, default_realm.as_mut_ptr()) }; + let default_realm = unsafe { default_realm.assume_init() }; + if error_code != 0 { + unsafe { krb5_free_default_realm(self.inner, default_realm) } + return Err(Krb5Error::from_library_error(self, error_code)); + } + + let result = unsafe { + match CStr::from_ptr(default_realm).to_owned().into_string() { + Ok(string) => Ok(string), + Err(_) => Err(Krb5Error::StringConversionError), + } + }; + unsafe { krb5_free_default_realm(self.inner, default_realm) } + result + } + + pub(crate) fn get_error_message(&self, error_code: krb5_error_code) -> String { + let c_str = unsafe { krb5_get_error_message(self.inner, error_code) }; + + if c_str.is_null() { + unsafe { + krb5_free_error_message(self.inner, c_str); + } + return String::from("krb5 error message is null"); + } + let result = unsafe { + match CStr::from_ptr(c_str).to_owned().into_string() { + Ok(string) => string, + Err(_) => String::from("Failed to convert CString to String"), + } + }; + unsafe { krb5_free_error_message(self.inner, c_str) }; + result + } + + pub fn new() -> Result { + let _guard = &CONTEXT_LOCK.lock().expect("Failed to lock context"); + let mut krb5_context: MaybeUninit = MaybeUninit::uninit(); + let error_code = unsafe { krb5_init_context(krb5_context.as_mut_ptr()) }; + if error_code != 0 { + return Err(Krb5Error::ContextInitializationError); + } + + let krb5_context = Context { + inner: unsafe { krb5_context.assume_init() }, + }; + Ok(krb5_context) + } +} + +impl Drop for Context { + fn drop(&mut self) { + let _guard = &CONTEXT_LOCK.lock().expect("Failed to lock context"); + if !self.inner.is_null() { + unsafe { krb5_free_context(self.inner) } + } + } +} diff --git a/crates/krb5_rs/src/credentials.rs b/crates/krb5_rs/src/credentials.rs new file mode 100644 index 0000000..6e1f274 --- /dev/null +++ b/crates/krb5_rs/src/credentials.rs @@ -0,0 +1,75 @@ +use std::{ffi::CString, mem::MaybeUninit}; + +use krb5_sys::{ + krb5_creds, krb5_free_cred_contents, krb5_get_init_creds_keytab, krb5_get_init_creds_password, +}; + +use crate::{Context, Keytab, Krb5Error, Principal, Result}; + +pub struct Credentials<'context> { + pub(crate) context: &'context Context, + pub(crate) inner: krb5_creds, +} + +impl<'context> Credentials<'context> { + pub fn get_init_credentials_password( + context: &'context Context, + principal: &Principal, + password: &str, + ) -> Result> { + let mut credentials: MaybeUninit = MaybeUninit::uninit(); + let password = CString::new(password).map_err(|_| Krb5Error::StringConversionError)?; + let error_code = unsafe { + krb5_get_init_creds_password( + context.inner, + credentials.as_mut_ptr(), + principal.inner, + password.as_ptr(), + None, + std::ptr::null_mut() as *mut _, + 0, + std::ptr::null() as *const _, + std::ptr::null_mut() as *mut _, + ) + }; + Krb5Error::exit_if_library_error(context, error_code)?; + + Ok(Credentials { + context, + inner: unsafe { credentials.assume_init() }, + }) + } + + pub fn get_init_credentials_keytab( + context: &'context Context, + principal: &Principal, + keytab: &Keytab, + ) -> Result> { + let mut credentials: MaybeUninit = MaybeUninit::uninit(); + let error_code = unsafe { + krb5_get_init_creds_keytab( + context.inner, + credentials.as_mut_ptr(), + principal.inner, + keytab.inner, + 0, + std::ptr::null_mut() as *mut _, + std::ptr::null_mut() as *mut _, + ) + }; + Krb5Error::exit_if_library_error(context, error_code)?; + + Ok(Credentials { + context, + inner: unsafe { credentials.assume_init() }, + }) + } +} + +impl<'context> Drop for Credentials<'context> { + fn drop(&mut self) { + if !self.context.inner.is_null() { + unsafe { krb5_free_cred_contents(self.context.inner, &mut self.inner) } + } + } +} diff --git a/crates/krb5_rs/src/error.rs b/crates/krb5_rs/src/error.rs new file mode 100644 index 0000000..1702f64 --- /dev/null +++ b/crates/krb5_rs/src/error.rs @@ -0,0 +1,35 @@ +use super::Context; +use krb5_sys::krb5_error_code; + +use thiserror::Error; + +pub type Result = std::result::Result; + +#[derive(Error, Debug)] +pub enum Krb5Error { + #[error("{0}")] + LibraryError(String), + #[error("Failed to convert string returned from libkr5")] + StringConversionError, + #[error("Invalid principal")] + InvalidPrincipal, + #[error("Failed to initialize context")] + ContextInitializationError, +} + +impl Krb5Error { + pub fn from_library_error(context: &Context, error_code: krb5_error_code) -> Krb5Error { + Krb5Error::LibraryError(context.get_error_message(error_code)) + } + + pub(crate) fn exit_if_library_error( + context: &Context, + error_code: krb5_error_code, + ) -> Result<()> { + if error_code != 0 { + Err(Krb5Error::from_library_error(context, error_code)) + } else { + Ok(()) + } + } +} diff --git a/crates/krb5_rs/src/gssapi.rs b/crates/krb5_rs/src/gssapi.rs new file mode 100644 index 0000000..b776aab --- /dev/null +++ b/crates/krb5_rs/src/gssapi.rs @@ -0,0 +1,164 @@ +use std::{ + ffi::{c_void, CString}, + mem::MaybeUninit, + slice, +}; + +use krb5_sys::{ + gss_OID, gss_OID_desc, gss_buffer_desc, gss_buffer_desc_struct, gss_c_nt_hostbased_service, + gss_c_nt_user_name, gss_delete_sec_context, gss_display_status, gss_error, gss_import_name, + gss_init_sec_context, gss_int32, gss_krb5_ccache_name, gss_name_t, gss_release_buffer, + gss_release_name, OM_uint32, GSS_C_NO_CHANNEL_BINDINGS, GSS_C_NO_CONTEXT, GSS_C_NO_CREDENTIAL, +}; + +const MAX_AD_TOKEN_SIZE_BEFORE_B64: usize = 48000; + +const GSS_C_REPLAY_FLAG: OM_uint32 = 4; +const GSS_C_SEQUENCE_FLAG: OM_uint32 = 8; +const GSS_C_INDEFINITE: OM_uint32 = 0xffffffff; +const GSS_C_GSS_CODE: gss_int32 = 1; + +pub struct Name(gss_name_t); + +impl Drop for Name { + fn drop(&mut self) { + if !self.0.is_null() { + unsafe { + gss_release_name(std::ptr::null_mut(), &mut self.0); + } + } + } +} + +pub fn import_name(principal: &str, input_name_type: &str) -> Result { + let mut minor = 0; + let mut service = gss_buffer_desc { + length: principal.len(), + value: principal.as_ptr() as *mut _, + }; + let gss_oid = unsafe { + if input_name_type == "GSS_C_NT_USER_NAME" { + gss_c_nt_user_name() + } else { + gss_c_nt_hostbased_service() + } + }; + let mut desired_name: MaybeUninit = MaybeUninit::uninit(); + let error_code = + unsafe { gss_import_name(&mut minor, &mut service, gss_oid, desired_name.as_mut_ptr()) }; + if unsafe { gss_error(error_code) } != 0 { + return Err(convert_gss_error(error_code, minor)); + } + + let desired_name = unsafe { desired_name.assume_init() }; + + Ok(Name(desired_name)) +} + +pub fn krb5_ccache_name(ccache_name: &str) -> Result<(), String> { + let mut minor = 0; + let ccache_name = + CString::new(ccache_name).map_err(|_| "Failed to convert ccache name to a c string")?; + + let error_code = + unsafe { gss_krb5_ccache_name(&mut minor, ccache_name.as_ptr(), std::ptr::null_mut()) }; + if unsafe { gss_error(error_code) } != 0 { + return Err(convert_gss_error(error_code, minor)); + } + Ok(()) +} + +pub fn get_token(target_name: Name) -> Result, String> { + let mut minor: OM_uint32 = 0; + let mut gss_context = unsafe { GSS_C_NO_CONTEXT }; + let mut gss_mech_spnego = gss_OID_desc { + length: 6, + elements: (*b"\x2b\x06\x01\x05\x05\x02").as_ptr() as *mut c_void, + }; + let mut input_token: MaybeUninit = MaybeUninit::uninit(); + let mut output_token: MaybeUninit = MaybeUninit::uninit(); + let error_code = unsafe { + gss_init_sec_context( + &mut minor, + GSS_C_NO_CREDENTIAL, // uses ccache specified with gss_krb5_ccache_name or default + &mut gss_context, + target_name.0, + &mut gss_mech_spnego, + GSS_C_REPLAY_FLAG | GSS_C_SEQUENCE_FLAG, + GSS_C_INDEFINITE, + GSS_C_NO_CHANNEL_BINDINGS, + input_token.as_mut_ptr(), // make null + std::ptr::null_mut(), + output_token.as_mut_ptr(), + std::ptr::null_mut(), + std::ptr::null_mut(), + ) + }; + let res = if unsafe { gss_error(error_code) } != 0 { + Err(convert_gss_error(error_code, minor)) + } else { + let output_token = unsafe { output_token.assume_init() }; + if output_token.length > MAX_AD_TOKEN_SIZE_BEFORE_B64 { + Err(String::from( + "The token returned by GSS is greater than the size allowed by Windows AD", + )) + } else { + let size = output_token.length; + let mut vec: Vec = Vec::with_capacity(size); + unsafe { + std::ptr::copy(output_token.value, vec.as_mut_ptr() as *mut _, size); + vec.set_len(size); + }; + + Ok(vec) + } + }; + let error_code = + unsafe { gss_delete_sec_context(&mut minor, &mut gss_context, output_token.as_mut_ptr()) }; + if unsafe { gss_error(error_code) } != 0 { + let error_message = convert_gss_error(error_code, minor); + if let Err(message) = res { + Err(message + &error_message) + } else { + Err(error_message) + } + } else { + res + } +} + +fn convert_gss_error(error_code: OM_uint32, minor: OM_uint32) -> String { + let mut message_context = 0; + let mut min_status = 0; + let gss_c_no_oid: gss_OID = unsafe { MaybeUninit::zeroed().assume_init() }; + let mut status_string = gss_buffer_desc { + length: 0, + value: std::ptr::null_mut(), + }; + let mut error_msg = Vec::new(); + loop { + unsafe { + gss_display_status( + &mut min_status, + error_code, + GSS_C_GSS_CODE, + gss_c_no_oid, + &mut message_context, + &mut status_string, + ) + }; + let slice_from_data: &[u8] = + unsafe { slice::from_raw_parts(status_string.value as *mut _, status_string.length) }; + error_msg.extend_from_slice(slice_from_data); + unsafe { gss_release_buffer(&mut min_status, &mut status_string) }; + + if message_context == 0 { + break; + } + } + error_msg.extend_from_slice(b" (minor "); + error_msg.extend_from_slice(minor.to_string().as_bytes()); + error_msg.extend_from_slice(b")"); + + unsafe { String::from_utf8_unchecked(error_msg) } +} diff --git a/crates/krb5_rs/src/keytab.rs b/crates/krb5_rs/src/keytab.rs new file mode 100644 index 0000000..d29dedb --- /dev/null +++ b/crates/krb5_rs/src/keytab.rs @@ -0,0 +1,34 @@ +use std::ffi::CString; +use std::mem::MaybeUninit; + +use crate::{Context, Result}; +use krb5_sys::{krb5_keytab, krb5_kt_close, krb5_kt_resolve}; + +use super::Krb5Error; + +pub struct Keytab<'context> { + pub(crate) context: &'context Context, + pub(crate) inner: krb5_keytab, +} + +impl<'context> Keytab<'context> { + pub fn resolve(context: &'context Context, name: &str) -> Result> { + let mut keytab: MaybeUninit = MaybeUninit::uninit(); + let name = CString::new(name).map_err(|_| Krb5Error::StringConversionError)?; + let error_code = + unsafe { krb5_kt_resolve(context.inner, name.as_ptr(), keytab.as_mut_ptr()) }; + Krb5Error::exit_if_library_error(context, error_code)?; + Ok(Keytab { + context, + inner: unsafe { keytab.assume_init() }, + }) + } +} + +impl<'context> Drop for Keytab<'context> { + fn drop(&mut self) { + if !self.context.inner.is_null() && !self.inner.is_null() { + unsafe { krb5_kt_close(self.context.inner, self.inner) }; + } + } +} diff --git a/crates/krb5_rs/src/lib.rs b/crates/krb5_rs/src/lib.rs new file mode 100644 index 0000000..d6d9a35 --- /dev/null +++ b/crates/krb5_rs/src/lib.rs @@ -0,0 +1,102 @@ +mod error; +pub use error::*; + +mod context; +pub use context::*; + +mod credentials; +pub use credentials::*; + +mod keytab; +pub use keytab::*; + +mod principal; +pub use principal::*; + +mod ccache; +pub use ccache::*; + +pub mod gssapi; + +#[cfg(test)] +mod test { + use super::CCache; + use super::Context; + use super::Credentials; + use super::Krb5Error; + use super::Principal; + use super::Result; + + #[test] + fn test_init() -> Result<()> { + let _krb5_context = Context::new()?; + Ok(()) + } + + #[test] + fn test_default_realm() -> Result<()> { + let krb5_context = Context::new()?; + let realm = krb5_context.get_default_realm()?; + println!("Default realm: {}", realm); + Ok(()) + } + + #[test] + fn test_principal_data() -> Result<()> { + let krb5_context = Context::new()?; + let realm = krb5_context.get_default_realm()?; + let _princ = Principal::build_principal(&krb5_context, &realm, "patrick")?; + let _princ = Principal::build_principal(&krb5_context, &realm, "patrick/host")?; + Ok(()) + } + + #[test] + fn test_cc_default() -> Result<()> { + let context = Context::new()?; + let _ccache = CCache::default(&context)?; + Ok(()) + } + + #[test] + fn test_cc_resolve() -> Result<()> { + let context = Context::new()?; + let _ccache = CCache::resolve(&context, "pipo")?; + Ok(()) + } + + #[test] + fn test_cc_destroy_memory_ccache() -> Result<()> { + let context = Context::new()?; + let ccache = CCache::resolve(&context, "MEMORY:pipo")?; + ccache.destroy()?; + Ok(()) + } + + #[test] + fn test_cc_destroy_unexisting_ccache() { + let context = Context::new().unwrap(); + let ccache = CCache::resolve(&context, "/tmp/pipo").unwrap(); + match ccache.destroy() { + Err(Krb5Error::LibraryError(_)) => assert!(true), + Ok(_) => assert!(false, "Shouldn't be able to destroy a non-existing ccache"), + _ => assert!( + false, + "Destroy a non-existing ccache should trigger a `Krb5Error::LibraryError`" + ), + } + } + + #[test] + fn test_store_creds_then_destroy() -> Result<()> { + let context = Context::new()?; + let principal = + Principal::build_principal(&context, &context.get_default_realm()?, "admin")?; + let ccache = CCache::default(&context)?; + ccache.initialize(&principal)?; + let credentials = + Credentials::get_init_credentials_password(&context, &principal, "adm1n_p4ssw0rd")?; + ccache.store_creds(credentials)?; + ccache.destroy()?; + Ok(()) + } +} diff --git a/crates/krb5_rs/src/principal.rs b/crates/krb5_rs/src/principal.rs new file mode 100644 index 0000000..7d3fdb9 --- /dev/null +++ b/crates/krb5_rs/src/principal.rs @@ -0,0 +1,65 @@ +use std::{mem::MaybeUninit, os::raw::c_uint}; + +use crate::{Context, Krb5Error, Result}; +use krb5_sys::{krb5_build_principal_ext, krb5_free_principal, krb5_principal}; + +pub struct Principal<'context> { + pub(crate) context: &'context Context, + pub(crate) inner: krb5_principal, +} + +impl<'context> Principal<'context> { + pub fn build_principal( + context: &'context Context, + realm: &str, + user: &str, + ) -> Result> { + let mut krb5_principal: MaybeUninit = MaybeUninit::uninit(); + let sp: Vec<&str> = user.split('/').collect(); + let error_code = match sp.len() { + 1 => unsafe { + let user = sp[0]; + + krb5_build_principal_ext( + context.inner, + krb5_principal.as_mut_ptr(), + realm.len() as c_uint, + realm.as_ptr() as *const i8, + user.len() as c_uint, + user.as_ptr(), + 0, + ) + }, + 2 => unsafe { + let user = sp[0]; + let host = sp[1]; + krb5_build_principal_ext( + context.inner, + krb5_principal.as_mut_ptr(), + realm.len() as c_uint, + realm.as_ptr() as *const i8, + user.len() as c_uint, + user.as_ptr(), + host.len() as c_uint, + host.as_ptr(), + 0, + ) + }, + _ => return Err(Krb5Error::InvalidPrincipal), + }; + Krb5Error::exit_if_library_error(context, error_code)?; + + Ok(Principal { + context, + inner: unsafe { krb5_principal.assume_init() }, + }) + } +} + +impl<'context> Drop for Principal<'context> { + fn drop(&mut self) { + if !self.context.inner.is_null() && !self.inner.is_null() { + unsafe { krb5_free_principal(self.context.inner, self.inner) } + } + } +} diff --git a/crates/krb5_sys/Cargo.toml b/crates/krb5_sys/Cargo.toml new file mode 100644 index 0000000..a246e4a --- /dev/null +++ b/crates/krb5_sys/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "krb5-sys" +version = "0.1.0" +edition = "2021" +links = "krb5" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + + +[build-dependencies] +bindgen = "0.63.0" +cc = "1.0" + +[target.'cfg(not(windows))'.build-dependencies] +pkg-config = "0.3" + +[target.'cfg(any(windows))'.build-dependencies] +winreg = { version = "0.10" } diff --git a/crates/krb5_sys/build.rs b/crates/krb5_sys/build.rs new file mode 100644 index 0000000..40d3a58 --- /dev/null +++ b/crates/krb5_sys/build.rs @@ -0,0 +1,128 @@ +use std::{env, path::PathBuf}; + +#[cfg(target_os = "windows")] +use winreg::enums::*; +#[cfg(target_os = "windows")] +use winreg::RegKey; + +#[cfg(target_os = "windows")] +fn link_library() -> Vec { + let krb5_home = env::var("KRB5_HOME"); + let path = match krb5_home { + Ok(krb5_home) => krb5_home, + Err(_) => { + let hklm = RegKey::predef(HKEY_LOCAL_MACHINE); + let mit_kerberos = hklm + .open_subkey(r"SOFTWARE\MIT\Kerberos\SDK\4.1.0") + .expect("Failed to find MIT Kerberos in WinRegistry"); + mit_kerberos + .get_value("PathName") + .expect("Failed to find MIT Kerberos path in WinRegistry") + } + }; + println!(r"cargo:rustc-link-search=native={}\lib\amd64\", path); + println!("cargo:rustc-link-lib=dylib=krb5_64"); + println!("cargo:rustc-link-lib=dylib=gssapi64"); + vec![ + [&path, r"include"].iter().collect::(), + [&path, r"include\gssapi"].iter().collect::(), + ] +} + +#[cfg(any(target_os = "linux", target_os = "macos"))] +fn link_library() -> Vec { + let krb5_home = env::var("KRB5_HOME"); + if let Ok(krb5_home) = krb5_home { + let path: PathBuf = [&krb5_home, "lib", "pkgconfig"].iter().collect(); + if path.exists() { + env::set_var("PKG_CONFIG_PATH", path); + } + } + let krb5 = pkg_config::Config::new() + .atleast_version("1.15") + .probe("mit-krb5") + .expect("Failed to bind libkrb5"); + + let gssapi = pkg_config::Config::new() + .atleast_version("1.15") + .probe("mit-krb5-gssapi") + .expect("Failed to bind libgssapi_krb5"); + + let mut include_dirs = Vec::new(); + include_dirs.extend(krb5.include_paths); + include_dirs.extend(gssapi.include_paths); + include_dirs +} + +fn main() { + println!("cargo:rerun-if-env-changed=KRB5_HOME"); + let include_dirs = link_library(); + + println!("cargo:rerun-if-changed=src/wrapper.h"); + + let bindings = bindgen::Builder::default() + // The input header we would like to generate + // bindings for. + .header("src/wrapper.h") + .allowlist_var("krb5_.*") + .allowlist_function("krb5_init_context") + .allowlist_function("krb5_free_context") + .allowlist_function("krb5_get_error_message") + .allowlist_function("krb5_free_error_message") + .allowlist_function("krb5_get_default_realm") + .allowlist_function("krb5_free_default_realm") + .allowlist_function("krb5_build_principal_ext") + .allowlist_function("krb5_free_principal") + .allowlist_function("krb5_cc_default") + .allowlist_function("krb5_cc_initialize") + .allowlist_function("krb5_cc_resolve") + .allowlist_function("krb5_cc_close") + .allowlist_function("krb5_cc_destroy") + .allowlist_function("krb5_cc_store_cred") + .allowlist_function("krb5_cc_get_name") + .allowlist_function("krb5_get_init_creds_password") + .allowlist_function("krb5_free_cred_contents") + .allowlist_function("krb5_kt_resolve") + .allowlist_function("krb5_get_init_creds_keytab") + .allowlist_function("krb5_kt_close") + .allowlist_function("gss_import_name") + .allowlist_function("gss_release_name") + .allowlist_function("gss_krb5_ccache_name") + .allowlist_function("gss_init_sec_context") + .allowlist_function("gss_delete_sec_context") + .allowlist_function("gss_display_status") + .allowlist_function("gss_release_buffer") + .allowlist_function("gss_error") + .allowlist_function("gss_c_nt_user_name") + .allowlist_function("gss_c_nt_hostbased_service") + .allowlist_type("gss_int32") + .allowlist_var("GSS_C_NO_CREDENTIAL") + .allowlist_var("GSS_C_NO_CHANNEL_BINDINGS") + .allowlist_var("GSS_C_NO_CONTEXT") + .clang_args( + include_dirs + .iter() + .map(|include_dir| String::from("-I") + include_dir.to_str().unwrap()) + .collect::>(), + ) + // Tell cargo to invalidate the built crate whenever any of the + // included header files changed. + .parse_callbacks(Box::new(bindgen::CargoCallbacks)) + // Finish the builder and generate the bindings. + .generate() + // Unwrap the Result and panic on failure. + .expect("Unable to generate bindings"); + + // Write the bindings to the $OUT_DIR/bindings.rs file. + let out_path = PathBuf::from(env::var("OUT_DIR").unwrap()); + bindings + .write_to_file(out_path.join("bindings.rs")) + .expect("Couldn't write bindings!"); + + println!("cargo:rerun-if-changed=src/wrapper.c"); + cc::Build::new() + .includes(include_dirs) + .include("src") + .file("src/wrapper.c") + .compile("gssapi_wrapper"); +} diff --git a/crates/krb5_sys/src/lib.rs b/crates/krb5_sys/src/lib.rs new file mode 100644 index 0000000..a38a13a --- /dev/null +++ b/crates/krb5_sys/src/lib.rs @@ -0,0 +1,5 @@ +#![allow(non_upper_case_globals)] +#![allow(non_camel_case_types)] +#![allow(non_snake_case)] + +include!(concat!(env!("OUT_DIR"), "/bindings.rs")); diff --git a/crates/krb5_sys/src/wrapper.c b/crates/krb5_sys/src/wrapper.c new file mode 100644 index 0000000..21a77ef --- /dev/null +++ b/crates/krb5_sys/src/wrapper.c @@ -0,0 +1,7 @@ +#include "wrapper.h" + +OM_uint32 gss_error(OM_uint32 x) { return GSS_ERROR(x); } + +gss_OID gss_c_nt_hostbased_service() { return GSS_C_NT_HOSTBASED_SERVICE; } + +gss_OID gss_c_nt_user_name() { return GSS_C_NT_USER_NAME; } diff --git a/crates/krb5_sys/src/wrapper.h b/crates/krb5_sys/src/wrapper.h new file mode 100644 index 0000000..95d7d13 --- /dev/null +++ b/crates/krb5_sys/src/wrapper.h @@ -0,0 +1,19 @@ +#include +#include +#include + +#undef GSS_C_NO_CREDENTIAL +const gss_cred_id_t GSS_C_NO_CREDENTIAL = ((gss_cred_id_t)0); + +#undef GSS_C_NO_CHANNEL_BINDINGS +const gss_channel_bindings_t GSS_C_NO_CHANNEL_BINDINGS = + ((gss_channel_bindings_t)0); + +#undef GSS_C_NO_CONTEXT +const gss_ctx_id_t GSS_C_NO_CONTEXT = ((gss_ctx_id_t)0); + +OM_uint32 gss_error(OM_uint32 x); + +gss_OID gss_c_nt_hostbased_service(); + +gss_OID gss_c_nt_user_name(); diff --git a/docker/archlinux/Dockerfile b/docker/archlinux/Dockerfile index 94d6c77..e4ed5e6 100644 --- a/docker/archlinux/Dockerfile +++ b/docker/archlinux/Dockerfile @@ -1,14 +1,13 @@ FROM archlinux/archlinux:base -# Update repositories -RUN echo "Server = http://mirror.aarnet.edu.au/pub/archlinux/$repo/os/$arch" >> /etc/pacman.d/mirrorlist RUN pacman --noconfirm -Syu # Install misc -RUN pacman --noconfirm -S wget git vim sudo +RUN pacman --noconfirm -S wget git python3 make clang pkg-config -# Install compilation tools -RUN pacman --noconfirm -S python3 gcc make +RUN curl https://sh.rustup.rs -sSf | bash -s -- -y --profile minimal + +RUN echo 'source $HOME/.cargo/env' >> /etc/profile # Install Kerberos libs RUN pacman --noconfirm -S krb5 @@ -21,5 +20,10 @@ RUN n 14 RUN n 16 RUN mkdir -p /node-krb5 -COPY ./run.sh /run.sh +WORKDIR /node-krb5 + +COPY . . + +# Context is node-krb5 folder +COPY docker/archlinux/run.sh /run.sh ENTRYPOINT ["/run.sh"] diff --git a/docker/archlinux/run.sh b/docker/archlinux/run.sh index f24bb48..0e3a88d 100755 --- a/docker/archlinux/run.sh +++ b/docker/archlinux/run.sh @@ -6,6 +6,8 @@ RED='\033[0;31m' GREEN='\033[0;32m' NC='\033[0m' # No Color +source /etc/profile + while [ ! -f /tmp/krb5_test/rest_ready ] do echo "Waiting for REST server to be ready..." @@ -16,9 +18,8 @@ cp /tmp/krb5_test/krb5.conf /etc/krb5.conf for node_version in "16" "14"; do echo "Node.js version "$node_version n $node_version - # --unsafe-perm allows node-gyp build as root # --force forces reinstallation - npm install --unsafe-perm --force + npm install --force npm test err=$? if [ $err -ne 0 ] @@ -30,4 +31,4 @@ for node_version in "16" "14"; do fi done echo -e $GREEN"Tests passed for Node.js version 14 and 16"$NC -exit 0 \ No newline at end of file +exit 0 diff --git a/docker/centos7/Dockerfile b/docker/centos7/Dockerfile index dbd64d2..4850c27 100644 --- a/docker/centos7/Dockerfile +++ b/docker/centos7/Dockerfile @@ -1,16 +1,15 @@ FROM centos:7 -# Install epel (requirement for service nginx) -RUN yum install -y epel-release - +# Install scl for llvm-toolset RUN yum install -y centos-release-scl -RUN yum install -y devtoolset-10-gcc-c++ # Install misc -RUN yum install -y wget git vim sudo make +RUN yum install -y curl git sudo make devtoolset-10-gcc-c++ llvm-toolset-7 # Install compilation tools -RUN yum install -y python3 make +RUN curl https://sh.rustup.rs -sSf | bash -s -- -y --profile minimal + +RUN echo 'source $HOME/.cargo/env' >> /etc/profile # Install Kerberos libs RUN yum install -y krb5-devel @@ -23,5 +22,10 @@ RUN n 14 RUN n 16 RUN mkdir -p /node-krb5 -COPY ./run.sh /run.sh +WORKDIR /node-krb5 + +COPY . . + +# Context is node-krb5 folder +COPY docker/centos7/run.sh /run.sh ENTRYPOINT ["/run.sh"] diff --git a/docker/centos7/run.sh b/docker/centos7/run.sh index 1a8279f..9c8441a 100755 --- a/docker/centos7/run.sh +++ b/docker/centos7/run.sh @@ -6,7 +6,9 @@ RED='\033[0;31m' GREEN='\033[0;32m' NC='\033[0m' # No Color +source /etc/profile source scl_source enable devtoolset-10 # load environment configuration to use gcc 7 +source scl_source enable llvm-toolset-7 # load environment configuration to use clang 5 while [ ! -f /tmp/krb5_test/rest_ready ] do @@ -18,9 +20,8 @@ cp /tmp/krb5_test/krb5.conf /etc/krb5.conf for node_version in "16" "14"; do echo "Node.js version "$node_version n $node_version - # --unsafe-perm allows node-gyp build as root # --force forces reinstallation - npm install --unsafe-perm --force + npm install --force npm test err=$? if [ $err -ne 0 ] @@ -32,4 +33,4 @@ for node_version in "16" "14"; do fi done echo -e $GREEN"Tests passed for Node.js version 14 and 16"$NC -exit 0 \ No newline at end of file +exit 0 diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index f4de97c..4153f27 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -23,15 +23,12 @@ services: ports: - 8080:8080 volumes: - - ../src:/node-krb5/src - - ../lib:/node-krb5/lib - - ../test:/node-krb5/test - - ../package.json:/node-krb5/package.json - - ../binding.gyp:/node-krb5/binding.gyp - - ../install_krb5.sh:/node-krb5/install_krb5.sh + - ../src/server.py:/node-krb5/src/server.py - /tmp/krb5_test:/tmp/krb5_test archlinux: - build: ./archlinux + build: + context: ../ + dockerfile: docker/archlinux/Dockerfile image: krb5_archlinux depends_on: - kerberos @@ -40,15 +37,11 @@ services: - "kerberos:kdc.krb.local" - "rest:rest.krb.local" volumes: - - ../src:/node-krb5/src - - ../lib:/node-krb5/lib - - ../test:/node-krb5/test - - ../package.json:/node-krb5/package.json - - ../binding.gyp:/node-krb5/binding.gyp - - ../install_krb5.sh:/node-krb5/install_krb5.sh - /tmp/krb5_test:/tmp/krb5_test centos7: - build: ./centos7 + build: + context: ../ + dockerfile: docker/centos7/Dockerfile image: krb5_centos7 depends_on: - kerberos @@ -57,15 +50,11 @@ services: - "kerberos:kdc.krb.local" - "rest:rest.krb.local" volumes: - - ../src:/node-krb5/src - - ../lib:/node-krb5/lib - - ../test:/node-krb5/test - - ../package.json:/node-krb5/package.json - - ../binding.gyp:/node-krb5/binding.gyp - - ../install_krb5.sh:/node-krb5/install_krb5.sh - /tmp/krb5_test:/tmp/krb5_test ubuntu: - build: ./ubuntu + build: + context: ../ + dockerfile: docker/ubuntu/Dockerfile image: krb5_ubuntu depends_on: - kerberos @@ -74,10 +63,17 @@ services: - "kerberos:kdc.krb.local" - "rest:rest.krb.local" volumes: - - ../src:/node-krb5/src - - ../lib:/node-krb5/lib - - ../test:/node-krb5/test - - ../package.json:/node-krb5/package.json - - ../binding.gyp:/node-krb5/binding.gyp - - ../install_krb5.sh:/node-krb5/install_krb5.sh + - /tmp/krb5_test:/tmp/krb5_test + ubuntu-vendored: + build: + context: ../ + dockerfile: docker/ubuntu-vendored/Dockerfile + image: krb5_ubuntu_vendored + depends_on: + - kerberos + - rest + links: + - "kerberos:kdc.krb.local" + - "rest:rest.krb.local" + volumes: - /tmp/krb5_test:/tmp/krb5_test diff --git a/docker/ubuntu-vendored/Dockerfile b/docker/ubuntu-vendored/Dockerfile new file mode 100644 index 0000000..aad5b0e --- /dev/null +++ b/docker/ubuntu-vendored/Dockerfile @@ -0,0 +1,36 @@ +FROM ubuntu:bionic + +# Update repositories +RUN apt-get update + +# Install misc +RUN apt-get install -y wget git make curl clang bison pkg-config + +# Install compilation tools +RUN curl https://sh.rustup.rs -sSf | bash -s -- -y --profile minimal + +RUN echo 'source $HOME/.cargo/env' >> /etc/profile + +#RUN rm -f /usr/bin/python && ln -s /usr/bin/python3 /usr/bin/python + +# Kerberos libs will be built there +ENV KRB5_HOME="/node-krb5/vendor" + +# Install Node.js via n +ENV NPM_CONFIG_LOGLEVEL info +RUN git clone https://github.com/tj/n /n +RUN cd /n && make install +RUN n 14 +RUN n 16 + +RUN mkdir -p /node-krb5 +WORKDIR /node-krb5 + +COPY ./install_krb5.sh . +RUN ./install_krb5.sh + +COPY . . + +# Context is node-krb5 folder +COPY docker/ubuntu-vendored/run.sh /run.sh +ENTRYPOINT ["/run.sh"] diff --git a/docker/ubuntu-vendored/run.sh b/docker/ubuntu-vendored/run.sh new file mode 100755 index 0000000..ae7570b --- /dev/null +++ b/docker/ubuntu-vendored/run.sh @@ -0,0 +1,34 @@ +#!/bin/bash + +[[ "TRACE" ]] + +RED='\033[0;31m' +GREEN='\033[0;32m' +NC='\033[0m' # No Color + +source /etc/profile + +while [ ! -f /tmp/krb5_test/rest_ready ] +do + echo "Waiting for REST server to be ready..." + sleep 1 +done + +cp /tmp/krb5_test/krb5.conf /etc/krb5.conf +for node_version in "16" "14"; do + echo "Node.js version "$node_version + n $node_version + # --force forces reinstallation + npm install --force + npm test + err=$? + if [ $err -ne 0 ] + then + echo -e $RED"Tests failed for Node.js "$node_version$NC + exit $err + else + echo -e $GREEN"Tests passed for Node.js "$node_version$NC + fi +done +echo -e $GREEN"Tests passed for Node.js version 14 and 16"$NC +exit 0 diff --git a/docker/ubuntu/Dockerfile b/docker/ubuntu/Dockerfile index f5aecff..6524b1a 100644 --- a/docker/ubuntu/Dockerfile +++ b/docker/ubuntu/Dockerfile @@ -4,13 +4,14 @@ FROM ubuntu:bionic RUN apt-get update # Install misc -RUN apt-get install -y wget git vim sudo make +RUN apt-get install -y wget git make curl clang pkg-config # Install compilation tools -RUN apt-get install -y python3 g++ make -#RUN rm -f /usr/bin/python && ln -s /usr/bin/python3 /usr/bin/python +RUN curl https://sh.rustup.rs -sSf | bash -s -- -y --profile minimal -# Install Kerberos libs +RUN echo 'source $HOME/.cargo/env' >> /etc/profile + +# Kerberos libs will be built there RUN apt-get install -y libkrb5-dev # Install Node.js via n @@ -21,5 +22,11 @@ RUN n 14 RUN n 16 RUN mkdir -p /node-krb5 -COPY ./run.sh /run.sh +WORKDIR /node-krb5 + + +COPY . . + +# Context is node-krb5 folder +COPY docker/ubuntu/run.sh /run.sh ENTRYPOINT ["/run.sh"] diff --git a/docker/ubuntu/run.sh b/docker/ubuntu/run.sh index f24bb48..ae7570b 100755 --- a/docker/ubuntu/run.sh +++ b/docker/ubuntu/run.sh @@ -6,19 +6,20 @@ RED='\033[0;31m' GREEN='\033[0;32m' NC='\033[0m' # No Color +source /etc/profile + while [ ! -f /tmp/krb5_test/rest_ready ] do echo "Waiting for REST server to be ready..." sleep 1 done -cd /node-krb5 + cp /tmp/krb5_test/krb5.conf /etc/krb5.conf for node_version in "16" "14"; do echo "Node.js version "$node_version n $node_version - # --unsafe-perm allows node-gyp build as root # --force forces reinstallation - npm install --unsafe-perm --force + npm install --force npm test err=$? if [ $err -ne 0 ] @@ -30,4 +31,4 @@ for node_version in "16" "14"; do fi done echo -e $GREEN"Tests passed for Node.js version 14 and 16"$NC -exit 0 \ No newline at end of file +exit 0 diff --git a/install_krb5.sh b/install_krb5.sh index 7999a1f..cb36f87 100755 --- a/install_krb5.sh +++ b/install_krb5.sh @@ -1,18 +1,30 @@ #!/usr/bin/env bash set -e -echo "Downloading MIT Kerberos..." -wget https://kerberos.org/dist/krb5/1.16/krb5-1.16.1.tar.gz -echo "Extracting..." -mkdir -p deps -tar -xzf krb5-1.16.1.tar.gz --directory deps -cd deps/krb5-1.16.1/src -echo "Compiling" -./configure -make -echo "Installing requires root privilege" -sudo make install -echo "Cleaning files..." -cd ../../.. -rm -rf deps -rm -rf krb5-1.16.1.tar.gz +vendor="$( cd -- "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )/vendor" +if [[ ! -d "$vendor/lib" || ! -d "$vendor/include" || ! -d "$vendor/sbin" ]]; then + echo "Downloading MIT Kerberos..." + wget https://kerberos.org/dist/krb5/1.16/krb5-1.16.1.tar.gz + echo "Extracting..." + mkdir deps + if [[ -d "$vendor" ]]; then + rm -rf "$vendor" + fi + mkdir "$vendor" + tar -xzf krb5-1.16.1.tar.gz --directory deps + cd deps/krb5-1.16.1/src + echo "Compiling" + ./configure --prefix "$vendor" + make + echo "Installing binaries to $vendor" + make install + echo "Cleaning files..." + cd ../../.. + rm -rf deps + rm -rf krb5-1.16.1.tar.gz +else + echo "$vendor seems to already exists, if you need to rebuild it, delete the folder" +fi + +echo "Export the following variable to make the vendored krb5 available to the build" +echo "export KRB5_HOME=$vendor" diff --git a/lib/index.d.ts b/lib/index.d.ts new file mode 100644 index 0000000..279ee7c --- /dev/null +++ b/lib/index.d.ts @@ -0,0 +1,24 @@ +/* tslint:disable */ +/* eslint-disable */ + +/* auto-generated by NAPI-RS */ + +export interface KdestroyParameters { + ccname?: string +} +export interface KinitParameters { + principal: string + password?: string + keytab?: string + realm?: string + ccname?: string +} +export interface GenerateSpnegoTokenParameters { + service_principal?: string + service_fqdn?: string + hostbased_service?: string + ccname?: string +} +export function kinit(input: KinitParameters): Promise +export function kdestroy(input: KdestroyParameters): Promise +export function spnego(input: GenerateSpnegoTokenParameters): Promise diff --git a/lib/index.js b/lib/index.js index b6ee541..d82bd48 100644 --- a/lib/index.js +++ b/lib/index.js @@ -1,259 +1,44 @@ // Generated by CoffeeScript 2.4.1 -var cleanup, fs, handle_error, k, kdestroy, kinit, spnego; +var krb5; -k = require('../build/Release/krb5'); - -fs = require('fs'); - -cleanup = function(ctx, princ, ccache) { - if (princ) { - k.krb5_free_principal_sync(ctx, princ); - } - if (ccache) { - return k.krb5_cc_close(ctx, ccache, function(err) { - if (ctx) { - return k.krb5_free_context_sync(ctx); - } - }); - } else { - if (ctx) { - return k.krb5_free_context_sync(ctx); - } - } -}; - -handle_error = function(callback, err, ctx, princ, ccache) { - if (!err) { - return err; - } - err = k.krb5_get_error_message_sync(ctx, err); - cleanup(ctx, princ, ccache); - return callback(Error(err)); -}; - -kinit = function(options, callback) { - var do_ccache, do_creds, do_init, do_principal, do_realm, split; - if (!options.principal) { - return callback(Error('Please specify principal for kinit')); - } - if (!(options.password || options.keytab)) { - return callback(Error('Please specify password or keytab for kinit')); - } - if (options.principal.indexOf('@') !== -1) { - split = options.principal.split('@'); - options.principal = split[0]; - options.realm = split[1]; - } - do_init = function() { - return k.krb5_init_context(function(err, ctx) { - if (err) { - return handle_error(callback, err, ctx); - } - return do_realm(ctx); - }); - }; - do_realm = function(ctx) { - if (!options.realm) { - return k.krb5_get_default_realm(ctx, function(err, realm) { - if (err) { - return handle_error(callback, err, ctx); - } - options.realm = realm; - return do_principal(ctx); - }); - } else { - return do_principal(ctx); - } - }; - do_principal = function(ctx) { - return k.krb5_build_principal(ctx, options.realm.length, options.realm, options.principal, function(err, princ) { - if (err) { - return handle_error(callback, err, ctx); - } - return do_ccache(ctx, princ); - }); - }; - do_ccache = function(ctx, princ) { - if (options.ccname) { - if (options.ccname.indexOf(':KEYRING') !== -1) { - cleanup(ctx, princ); - return callback(Error('KEYRING method not supported.')); - } - return k.krb5_cc_resolve(ctx, options.ccname, function(err, ccache) { - if (err) { - return handle_error(callback, err, ctx, princ); - } - return do_creds(ctx, princ, ccache); - }); - } else { - return k.krb5_cc_default(ctx, function(err, ccache) { - if (err) { - return handle_error(callback, err, ctx, princ); - } - return do_creds(ctx, princ, ccache); - }); - } - }; - do_creds = function(ctx, princ, ccache) { - var ccname, get_creds_keytab, get_creds_password, store_creds; - ccname = k.krb5_cc_get_name_sync(ctx, ccache); - fs.exists(ccname, function(exists) { - if (!exists) { - return k.krb5_cc_initialize(ctx, ccache, princ, function(err) { - if (err) { - return handle_error(callback, err, ctx, princ); - } - if (options.password) { - return get_creds_password(); - } else { - return get_creds_keytab(); - } - }); - } else { - if (options.password) { - return get_creds_password(); - } else { - return get_creds_keytab(); - } - } - }); - get_creds_password = function() { - return k.krb5_get_init_creds_password(ctx, princ, options.password, function(err, creds) { - if (err) { - return handle_error(callback, err, ctx, princ, ccache); - } - return store_creds(creds); - }); - }; - get_creds_keytab = function() { - return k.krb5_kt_resolve(ctx, options.keytab, function(err, kt) { - if (err) { - return handle_error(callback, err, ctx, princ, ccache); - } - return k.krb5_get_init_creds_keytab(ctx, princ, kt, 0, function(err, creds) { - if (err) { - return handle_error(callback, err, ctx, princ, ccache); - } - return store_creds(creds); - }); - }); - }; - return store_creds = function(creds) { - return k.krb5_cc_store_cred(ctx, ccache, creds, function(err) { - if (err) { - return handle_error(callback, err, ctx, princ, ccache); - } - cleanup(ctx, princ, ccache); - return callback(void 0, ccname); - }); - }; - }; - return do_init(); -}; - -kdestroy = function(options, callback) { - var do_ccache, do_destroy; - k.krb5_init_context(function(err, ctx) { - if (err) { - return handle_error(callback, err, ctx); - } - return do_ccache(ctx); - }); - do_ccache = function(ctx) { - if (options.ccname) { - return k.krb5_cc_resolve(ctx, options.ccname, function(err, ccache) { - if (err) { - return handle_error(callback, err, ctx, null, ccache); - } - return do_destroy(ctx, ccache); - }); - } else { - return k.krb5_cc_default(ctx, function(err, ccache) { - if (err) { - return handle_error(callback, err, ctx, null, ccache); - } - return do_destroy(ctx, ccache); - }); - } - }; - return do_destroy = function(ctx, ccache) { - return k.krb5_cc_destroy(ctx, ccache, function(err) { - if (err) { - return handle_error(callback, err, ctx); - } - return callback(void 0); - }); - }; -}; - -spnego = function(options, callback) { - var input_name_type, service; - if (options.ccname == null) { - options.ccname = ""; - } - if (options.service_principal) { - input_name_type = 'GSS_C_NT_USER_NAME'; - service = options.service_principal; - } else if (options.service_fqdn || options.hostbased_service) { - input_name_type = 'GSS_C_NT_HOSTBASED_SERVICE'; - service = options.service_fqdn || options.hostbased_service; - if (!/.*[@]/.test(service)) { - service = `HTTP@${service}`; - } - } else { - return callback(Error('Missing option "service_principal" or "hostbased_service"')); - } - return k.generate_spnego_token(service, input_name_type, options.ccname, function(err, token) { - return callback((err === "" ? void 0 : Error(err)), token != null ? token.toString('base64') : void 0); - }); -}; +krb5 = require('./index.node'); module.exports = { kinit: function(options, callback) { - if (typeof callback === 'function') { - return kinit(options, callback); + if (!callback) { + return krb5.kinit(options); } - return new Promise(function(resolve, reject) { - return kinit(options, function(err, ccname) { - if (err) { - reject(err); - } - return resolve(ccname); - }); + krb5.kinit(options).then(function(ccname) { + return callback(void 0, ccname); + }).catch(function(err) { + return callback(err); }); }, spnego: function(options, callback) { - if (typeof callback === 'function') { - return spnego(options, callback); + if (!callback) { + return krb5.spnego(options); } - return new Promise(function(resolve, reject) { - return spnego(options, function(err, token) { - if (err) { - reject(err); - } - return resolve(token); - }); + krb5.spnego(options).then(function(token) { + return callback(void 0, token); + }).catch(function(err) { + return callback(err); }); }, kdestroy: function(options, callback) { if (options == null) { options = {}; } + if (!(typeof options === 'function' || callback)) { + return krb5.kdestroy(options); + } if (typeof options === 'function') { callback = options; - return kdestroy({}, callback); - } else { - if (typeof callback === 'function') { - return kdestroy(options, callback); - } - return new Promise(function(resolve, reject) { - return kdestroy(options, function(err) { - if (err) { - reject(err); - } - return resolve(); - }); - }); + options = {}; } + krb5.kdestroy(options).then(function() { + return callback(); + }).catch(function(err) { + return callback(err); + }); } }; diff --git a/package.json b/package.json index c4387d1..92e27eb 100644 --- a/package.json +++ b/package.json @@ -3,21 +3,18 @@ "version": "0.5.4", "description": "Kerberos library bindings for Node.js", "main": "./lib/index.js", - "dependencies": { - "bindings": "1.5.0", - "node-addon-api": "1.7.1" - }, "devDependencies": { "coffeescript": "2.4.1", "mocha": "6.2.2", - "should": "13.2.3" + "should": "13.2.3", + "@napi-rs/cli": "^2.0.0" }, - "gypfile": true, "author": "Xavier Hermand (https://www.adaltas.com)", "contributors": [ "David Worms (https://www.adaltas.com)", "Pierre Sauvage (https://www.adaltas.com)", - "Xavier Hermand (https://www.adaltas.com)" + "Xavier Hermand (https://www.adaltas.com)", + "Guillaume Boutry (https://www.adaltas.com)" ], "license": "BSD-3-Clause", "keywords": [ @@ -31,6 +28,14 @@ "type": "git", "url": "https://github.com/adaltas/node-krb5" }, + "napi": { + "triples": { + "defaults": true + } + }, + "engines": { + "node": ">= 14" + }, "scripts": { "preversion": "grep '## Trunk' CHANGELOG.md", "version": "version=`grep '^ \"version\": ' package.json | sed 's/.*\"\\([0-9\\.]*\\)\".*/\\1/'` && sed -i \"s/## Trunk/## Version $version/\" CHANGELOG.md && git add CHANGELOG.md", @@ -39,10 +44,12 @@ "minor": "npm version minor -m 'Bump to version %s'", "major": "npm version major -m 'Bump to version %s'", "coffee": "coffee -b -o lib src", - "rebuild": "node-gyp rebuild", "test": "mocha test/*.coffee", - "install": "node-gyp rebuild", + "test_rs": "cargo test", + "local_test": "$SHELL test/local_test.sh", "krb5-lib": "./install_krb5.sh", - "krb5-lib-zos": "./install_krb5_zos.sh" + "build": "napi build --cargo-name krb5_js ./lib", + "build-release": "npm run build -- --release --strip", + "install": "npm run build-release" } } diff --git a/src/gss_bind.cc b/src/gss_bind.cc deleted file mode 100644 index 6a588cd..0000000 --- a/src/gss_bind.cc +++ /dev/null @@ -1,185 +0,0 @@ -#include -#include "gss_bind.h" -#ifdef __MVS__ -#include "krb5_zos.h" -#endif - -// https://docs.microsoft.com/en-us/troubleshoot/windows-server/group-policy/group-policy-add-maxtokensize-registry-entry -// Max token size in most recent Microsoft OS is 48 000 before B64 -// the hard limit is set to 65 535 but discouraged because of B64 conversion that is around +30% in size -const size_t MAX_AD_TOKEN_SIZE_BEFORE_B64 = 48000; - -static gss_OID_desc _gss_mech_spnego = {6, (void *)"\x2b\x06\x01\x05\x05\x02"}; //Spnego OID: 1.3.6.1.5.5.2 -static const gss_OID GSS_MECH_SPNEGO = &_gss_mech_spnego; //gss_OID == gss_OID_desc* - -void _convert_gss_error(const OM_uint32 gss_err, const OM_uint32 gss_minor, std::string &error_msg); - -bool exists(const char *path) -{ - struct stat buffer; - return (stat(path, &buffer) == 0); -} - -/** - * generate_spnego_token - */ -class Worker_generate_spnego_token : public Napi::AsyncWorker -{ -public: - Worker_generate_spnego_token(std::string str_server, - std::string gss_name_type, - std::string ccname, - Napi::Function &callback) - : Napi::AsyncWorker(callback), str_server(str_server), - input_name_type(gss_name_type), - krb_ccname(ccname), - error_msg("") - { - } - -private: - OM_uint32 import_name(const char *principal, gss_name_t *desired_name) - { - gss_buffer_desc service; - service.length = strlen(principal); - service.value = (char *)principal; - auto name_type = (input_name_type == "GSS_C_NT_USER_NAME") ? GSS_C_NT_USER_NAME - : GSS_C_NT_HOSTBASED_SERVICE; - return gss_import_name(&gss_minor, &service, name_type, desired_name); - } - - void Execute() - { -#ifdef __MVS__ - __ae_runmode rm(__AE_ASCII_MODE); -#endif - const char *server = str_server.c_str(); - gss_ctx_id_t gss_context = GSS_C_NO_CONTEXT; - gss_buffer_desc input_buf, output_buf; - gss_name_t target_name; - gss_err = import_name(server, &target_name); - if (gss_err) - { - error_msg = "Error while importing name."; - return; - } - - if (krb_ccname != "") - { - // gss_krb5_ccache_name is actually thread safe, - // as discussed in https://krbdev.mit.narkive.com/hOSNjHRA/krb5-get-in-tkt-with-password-gss-init-sec-context - gss_err = gss_krb5_ccache_name(&gss_minor, krb_ccname.c_str(), NULL); - } - - gss_err = gss_init_sec_context(&gss_minor, - GSS_C_NO_CREDENTIAL, // uses ccache specified with gss_krb5_ccache_name or default - &gss_context, - target_name, - GSS_MECH_SPNEGO, - GSS_C_REPLAY_FLAG | GSS_C_SEQUENCE_FLAG, - GSS_C_INDEFINITE, - GSS_C_NO_CHANNEL_BINDINGS, - &input_buf, - NULL, - &output_buf, - NULL, - NULL); - - if (!(GSS_ERROR(gss_err))) - { - if (output_buf.length > MAX_AD_TOKEN_SIZE_BEFORE_B64) - { - this->error_msg += "The token returned by GSS is greater than the size allowed by Windows AD"; - this->spnego_token_length = 0; - this->spnego_token = NULL; - } - else - { - this->spnego_token_length = output_buf.length; - this->spnego_token = new uint8_t[this->spnego_token_length]; - memcpy((void *)this->spnego_token, output_buf.value, output_buf.length); - } - } - else - { - _convert_gss_error(gss_err, gss_minor, this->error_msg); - this->spnego_token_length = 0; - this->spnego_token = NULL; - } - gss_err = gss_delete_sec_context(&gss_minor, &gss_context, &output_buf); - if (GSS_ERROR(gss_err)) - { - _convert_gss_error(gss_err, gss_minor, this->error_msg); - } - } - - void OnOK() - { - Napi::HandleScope scope(Env()); - - Callback().Call({Napi::String::New(Env(), error_msg), - (spnego_token) ? Napi::Buffer::New(Env(), this->spnego_token, this->spnego_token_length, [](Napi::Env env, uint8_t *ref) - { free(ref); }) - : Env().Undefined()}); - } - - // - OM_uint32 gss_err; - OM_uint32 gss_minor; - - // In parameter - std::string str_server; - std::string input_name_type; - std::string krb_ccname; - - // Out parameter - uint8_t *spnego_token; - size_t spnego_token_length; - std::string error_msg; -}; - -void _convert_gss_error(const OM_uint32 gss_err, const OM_uint32 gss_minor, std::string &error_msg) -{ - if (error_msg.length() > 0) - { - error_msg += "\n"; - } - OM_uint32 message_context; - OM_uint32 min_status; - gss_buffer_desc status_string; - message_context = 0; - do - { - gss_display_status(&min_status, - gss_err, - GSS_C_GSS_CODE, - GSS_C_NO_OID, - &message_context, - &status_string); - error_msg += (char *)status_string.value; - gss_release_buffer(&min_status, &status_string); - } while (message_context != 0); - error_msg += " (minor "; - error_msg += std::to_string(gss_minor); - error_msg += ")"; -} - -Napi::Value _generate_spnego_token(const Napi::CallbackInfo &info) -{ - if (info.Length() < 4) - { - throw Napi::TypeError::New(info.Env(), "4 arguments expected"); - } - - std::string server = info[0].As().Utf8Value(); - std::string input_name_type = info[1].As().Utf8Value(); - std::string ccname = info[2].As().Utf8Value(); - Napi::Function callback = info[3].As(); - - Worker_generate_spnego_token *worker = new Worker_generate_spnego_token(server, - input_name_type, - ccname, - callback); - worker->Queue(); - return info.Env().Undefined(); -} diff --git a/src/gss_bind.h b/src/gss_bind.h deleted file mode 100644 index b7a86f7..0000000 --- a/src/gss_bind.h +++ /dev/null @@ -1,7 +0,0 @@ -#include -#include -#include -#include -#include - -Napi::Value _generate_spnego_token(const Napi::CallbackInfo& info); diff --git a/src/index.coffee b/src/index.coffee index c3af25d..d7c468b 100644 --- a/src/index.coffee +++ b/src/index.coffee @@ -1,146 +1,28 @@ -k = require '../build/Release/krb5' -fs = require 'fs' - -cleanup = (ctx, princ, ccache) -> - k.krb5_free_principal_sync ctx, princ if princ - - if ccache - k.krb5_cc_close ctx, ccache, (err) -> - k.krb5_free_context_sync ctx if ctx - else - k.krb5_free_context_sync ctx if ctx - -handle_error = (callback, err, ctx, princ, ccache) -> - return err unless err - err = k.krb5_get_error_message_sync(ctx, err) - cleanup ctx, princ, ccache - return callback Error err - -kinit = (options, callback) -> - return callback Error 'Please specify principal for kinit' unless options.principal - return callback Error 'Please specify password or keytab for kinit' unless options.password or options.keytab - if options.principal.indexOf('@') != -1 - split = options.principal.split('@') - options.principal = split[0] - options.realm = split[1] - do_init = -> - k.krb5_init_context (err, ctx) -> - return handle_error callback, err, ctx if err - do_realm ctx - do_realm = (ctx) -> - if !options.realm - k.krb5_get_default_realm ctx, (err, realm) -> - return handle_error callback, err, ctx if err - options.realm = realm - do_principal ctx - else - do_principal ctx - do_principal = (ctx) -> - k.krb5_build_principal ctx, - options.realm.length, - options.realm, - options.principal, - (err, princ) -> - return handle_error callback, err, ctx if err - do_ccache ctx, princ - do_ccache = (ctx, princ) -> - if options.ccname - if options.ccname.indexOf(':KEYRING') != -1 - cleanup ctx, princ - return callback Error 'KEYRING method not supported.' - k.krb5_cc_resolve ctx, options.ccname, (err, ccache) -> - return handle_error callback, err, ctx, princ if err - do_creds ctx, princ, ccache - else - k.krb5_cc_default ctx, (err, ccache) -> - return handle_error callback, err, ctx, princ if err - do_creds ctx, princ, ccache - do_creds = (ctx, princ, ccache) -> - ccname = k.krb5_cc_get_name_sync ctx, ccache - fs.exists ccname, (exists) -> - if !exists - k.krb5_cc_initialize ctx, ccache, princ, (err) -> - return handle_error callback, err, ctx, princ if err - if options.password then get_creds_password() else get_creds_keytab() - else - if options.password then get_creds_password() else get_creds_keytab() - get_creds_password = -> - k.krb5_get_init_creds_password ctx, princ, options.password, (err, creds) -> - return handle_error callback, err, ctx, princ, ccache if err - store_creds creds - get_creds_keytab = -> - k.krb5_kt_resolve ctx, options.keytab, (err, kt) -> - return handle_error callback, err, ctx, princ, ccache if err - k.krb5_get_init_creds_keytab ctx, princ, kt, 0, (err, creds) -> - return handle_error callback, err, ctx, princ, ccache if err - store_creds creds - store_creds = (creds) -> - k.krb5_cc_store_cred ctx, ccache, creds, (err) -> - return handle_error callback, err, ctx, princ, ccache if err - cleanup ctx, princ, ccache - callback undefined, ccname - do_init() - - -kdestroy = (options, callback) -> - k.krb5_init_context (err, ctx) -> - return handle_error(callback, err, ctx) if err - do_ccache(ctx) - - do_ccache = (ctx) -> - if options.ccname - k.krb5_cc_resolve ctx, options.ccname, (err, ccache) -> - return handle_error(callback, err, ctx, null, ccache) if err - do_destroy ctx, ccache - else - k.krb5_cc_default ctx, (err, ccache) -> - return handle_error(callback, err, ctx, null, ccache) if err - do_destroy ctx, ccache - - do_destroy = (ctx, ccache) -> - k.krb5_cc_destroy ctx, ccache, (err) -> - return handle_error(callback, err, ctx) if err - callback undefined - - -spnego = (options, callback) -> - options.ccname ?= "" - if options.service_principal - input_name_type = 'GSS_C_NT_USER_NAME' - service = options.service_principal - else if options.service_fqdn or options.hostbased_service - input_name_type = 'GSS_C_NT_HOSTBASED_SERVICE' - service = options.service_fqdn or options.hostbased_service - service = "HTTP@#{service}" unless /.*[@]/.test service - else return callback Error 'Missing option "service_principal" or "hostbased_service"' - - k.generate_spnego_token service, input_name_type, options.ccname, (err, token) -> - return callback (if err is "" then undefined else Error err), token?.toString 'base64' - +krb5 = require './index.node' module.exports = kinit: (options, callback) -> - return kinit options, callback if typeof callback is 'function' - return new Promise (resolve, reject) -> - kinit options, (err, ccname) -> - reject err if err - resolve ccname + return krb5.kinit options unless callback + krb5.kinit options + .then (ccname) -> callback undefined, ccname + .catch (err) -> callback err + return spnego: (options, callback) -> - return spnego options, callback if typeof callback is 'function' - return new Promise (resolve, reject) -> - spnego options, (err, token) -> - reject err if err - resolve token + return krb5.spnego options unless callback + krb5.spnego options + .then (token) -> callback undefined, token + .catch (err) -> callback err + return kdestroy: (options, callback) -> options ?= {} + return krb5.kdestroy options unless typeof options is 'function' or callback if typeof options is 'function' callback = options - return kdestroy {}, callback - else - return kdestroy options, callback if typeof callback is 'function' - return new Promise (resolve, reject) -> - kdestroy options, (err) -> - reject err if err - resolve() + options = {} + + krb5.kdestroy options + .then () -> callback() + .catch (err) -> callback err + return diff --git a/src/krb5_bind.cc b/src/krb5_bind.cc deleted file mode 100644 index 89251f1..0000000 --- a/src/krb5_bind.cc +++ /dev/null @@ -1,869 +0,0 @@ -#include "krb5_bind.h" -#ifdef __MVS__ -#include "krb5_zos.h" -#endif - - -/** - * krb5_build_principal - */ -class Worker_krb5_build_principal : public Napi::AsyncWorker { - public: - Worker_krb5_build_principal(krb5_context ctx, - uint32_t rlen, - std::string realm, - std::string user, - Napi::Function& callback) - : Napi::AsyncWorker(callback), context(ctx), - rlen(rlen), - realm(realm), - user(user) { - } - - private: - void Execute() { -#ifdef __MVS__ - __ae_runmode rm(__AE_ASCII_MODE); -#endif - auto pos = user.find("/"); - if(pos != std::string::npos) { - err = krb5_build_principal(context, - &princ, - rlen, - realm.c_str(), - user.substr(0, pos).c_str(), - user.substr(pos+1, user.length()).c_str(), - NULL); - } - else { - err = krb5_build_principal(context, - &princ, - rlen, - realm.c_str(), - user.c_str(), - NULL); - } - } - - void OnOK() { - Napi::HandleScope scope(Env()); - - Callback().Call({ - Napi::Number::New(Env(), err), - Napi::External::New(Env(), princ) - }); - } - - // In parameters - krb5_context context; - uint32_t rlen; - std::string realm; - std::string user; - - // Out parameters - krb5_error_code err; - krb5_principal princ; -}; - -Napi::Value _krb5_build_principal(const Napi::CallbackInfo& info) { - if (info.Length() < 5) { - throw Napi::TypeError::New(info.Env(), - "5 arguments expected : context, rlen, realm, user, callback"); - } - - krb5_context krb_context = info[0].As>().Data(); - uint32_t rlen = info[1].As().Uint32Value(); - std::string realm = info[2].As().Utf8Value(); - std::string user = info[3].As().Utf8Value(); - Napi::Function callback = info[4].As(); - - Worker_krb5_build_principal* worker = - new Worker_krb5_build_principal(krb_context, - rlen, - realm, - user, - callback); - worker->Queue(); - return info.Env().Undefined(); -} - - -/** - * krb5_cc_close - */ -class Worker_krb5_cc_close : public Napi::AsyncWorker { - public: - Worker_krb5_cc_close(krb5_context ctx, - krb5_ccache ccache, - Napi::Function& callback) - : Napi::AsyncWorker(callback), krb_context(ctx), - krb_ccache(ccache) { - } - - private: - void Execute() { -#ifdef __MVS__ - __ae_runmode rm(__AE_ASCII_MODE); -#endif - err = krb5_cc_close(krb_context, krb_ccache); - } - - void OnOK() { - Napi::HandleScope scope(Env()); - - Callback().Call({ - Napi::Number::New(Env(), err) - }); - } - - // In parameters - krb5_context krb_context; - krb5_ccache krb_ccache; - - // Out parameters - krb5_error_code err; -}; - -Napi::Value _krb5_cc_close(const Napi::CallbackInfo& info) { - if (info.Length() < 3) { - throw Napi::TypeError::New(info.Env(), "3 arguments expected"); - } - - krb5_context krb_context = info[0].As>().Data(); - krb5_ccache krb_ccache = info[1].As>().Data(); - Napi::Function callback = info[2].As(); - - Worker_krb5_cc_close* worker = new Worker_krb5_cc_close(krb_context, - krb_ccache, - callback); - worker->Queue(); - return info.Env().Undefined(); -} - - -/** - * krb5_cc_defaut - */ -class Worker_krb5_cc_default: public Napi::AsyncWorker { - public: - Worker_krb5_cc_default(krb5_context ctx, Napi::Function& callback) - : Napi::AsyncWorker(callback), context(ctx) { - } - - private: - void Execute() { -#ifdef __MVS__ - __ae_runmode rm(__AE_ASCII_MODE); -#endif - err = krb5_cc_default(context, &ccache); - } - - void OnOK() { - Napi::HandleScope scope(Env()); - - Callback().Call({ - Napi::Number::New(Env(), err), - Napi::External::New(Env(), ccache) - }); - } - - // In parameters - krb5_context context; - - // Out parameters - krb5_error_code err; - krb5_ccache ccache; -}; - -Napi::Value _krb5_cc_default(const Napi::CallbackInfo& info) { - if (info.Length() < 2) { - throw Napi::TypeError::New(info.Env(), "2 argument expected"); - } - - krb5_context krb_context = info[0].As>().Data(); - Napi::Function callback = info[1].As(); - - Worker_krb5_cc_default* worker = new Worker_krb5_cc_default(krb_context, callback); - worker->Queue(); - return info.Env().Undefined(); -} - - -/** - * krb5_cc_destroy - */ -class Worker_krb5_cc_destroy : public Napi::AsyncWorker { - public: - Worker_krb5_cc_destroy(krb5_context ctx, - krb5_ccache ccache, - Napi::Function& callback) - : Napi::AsyncWorker(callback), krb_context(ctx), - krb_ccache(ccache) { - } - - private: - void Execute() { -#ifdef __MVS__ - __ae_runmode rm(__AE_ASCII_MODE); -#endif - err = krb5_cc_destroy(krb_context, krb_ccache); - } - - void OnOK() { - Napi::HandleScope scope(Env()); - - Callback().Call({ - Napi::Number::New(Env(), err) - }); - } - - // In parameters - krb5_context krb_context; - krb5_ccache krb_ccache; - - // Out parameters - krb5_error_code err; -}; - -Napi::Value _krb5_cc_destroy(const Napi::CallbackInfo& info) { - if (info.Length() < 3) { - throw Napi::TypeError::New(info.Env(), "3 arguments expected"); - } - - krb5_context krb_context = info[0].As>().Data(); - krb5_ccache krb_ccache = info[1].As>().Data(); - Napi::Function callback = info[2].As(); - - Worker_krb5_cc_destroy* worker = new Worker_krb5_cc_destroy(krb_context, - krb_ccache, - callback); - worker->Queue(); - return info.Env().Undefined(); -} - - -/** - * krb5_cc_get_name_sync - */ -Napi::Value _krb5_cc_get_name_sync(const Napi::CallbackInfo& info) { - if (info.Length() < 2) { - throw Napi::TypeError::New(info.Env(), "2 argument expected"); - } - - krb5_context krb_context = info[0].As>().Data(); - krb5_ccache krb_ccache = info[1].As>().Data(); - - const char* cc_name = krb5_cc_get_name(krb_context, krb_ccache); - - return Napi::String::New(info.Env(), cc_name); -} - - -/** - * krb5_cc_initialize_sync - */ -Napi::Value _krb5_cc_initialize_sync(const Napi::CallbackInfo& info) { - if (info.Length() < 3) { - throw Napi::TypeError::New(info.Env(), "3 argument expected"); - } - - krb5_context krb_context = info[0].As>().Data(); - krb5_ccache krb_ccache = info[1].As>().Data(); - krb5_principal krb_princ = info[2].As>().Data(); - - krb5_error_code err = krb5_cc_initialize(krb_context, krb_ccache, krb_princ); - - return Napi::Number::New(info.Env(), err); -} - - -/** - * krb5_cc_initialize - */ -class Worker_krb5_cc_initialize : public Napi::AsyncWorker { - public: - Worker_krb5_cc_initialize(krb5_context ctx, - krb5_ccache ccache, - krb5_principal princ, - Napi::Function& callback) - : Napi::AsyncWorker(callback), krb_context(ctx), - krb_ccache(ccache), - krb_princ(princ) { - } - - private: - void Execute() { -#ifdef __MVS__ - __ae_runmode rm(__AE_ASCII_MODE); -#endif - err = krb5_cc_initialize(krb_context, krb_ccache, krb_princ); - } - - void OnOK() { - Napi::HandleScope scope(Env()); - - Callback().Call({ - Napi::Number::New(Env(), err) - }); - } - - // In parameter - krb5_context krb_context; - krb5_ccache krb_ccache; - krb5_principal krb_princ; - - // Out parameter - krb5_error_code err; -}; - -Napi::Value _krb5_cc_initialize(const Napi::CallbackInfo& info) { - if (info.Length() < 4) { - throw Napi::TypeError::New(info.Env(), "4 arguments expected"); - } - - krb5_context krb_context = info[0].As>().Data(); - krb5_ccache krb_ccache = info[1].As>().Data(); - krb5_principal krb_princ = info[2].As>().Data(); - Napi::Function callback = info[3].As(); - - Worker_krb5_cc_initialize* worker = new Worker_krb5_cc_initialize(krb_context, - krb_ccache, - krb_princ, - callback); - worker->Queue(); - return info.Env().Undefined(); -} - - - - - - -/** - * krb5_cc_resolve - */ -class Worker_krb5_cc_resolve : public Napi::AsyncWorker { - public: - Worker_krb5_cc_resolve(krb5_context ctx, - std::string name, - Napi::Function& callback) - : Napi::AsyncWorker(callback), krb_context(ctx), - cc_name(name) { - } - - private: - void Execute() { -#ifdef __MVS__ - __ae_runmode rm(__AE_ASCII_MODE); -#endif - err = krb5_cc_resolve(krb_context, cc_name.c_str(), &ccache); - } - - void OnOK() { - Napi::HandleScope scope(Env()); - - Callback().Call({ - Napi::Number::New(Env(), err), - Napi::External::New(Env(), ccache) - }); - - } - - // In parameter - krb5_context krb_context; - std::string cc_name; - - // Out parameter - krb5_error_code err; - krb5_ccache ccache; -}; - -Napi::Value _krb5_cc_resolve(const Napi::CallbackInfo& info) { - if (info.Length() < 3) { - throw Napi::TypeError::New(info.Env(), "3 arguments expected"); - } - - krb5_context krb_context = info[0].As>().Data(); - std::string name = info[1].As().Utf8Value(); - Napi::Function callback = info[2].As(); - - Worker_krb5_cc_resolve* worker = new Worker_krb5_cc_resolve(krb_context, - name, - callback); - worker->Queue(); - return info.Env().Undefined(); -} - - - - - - - - - -/** - * krb5_cc_store_cred - */ -class Worker_krb5_cc_store_cred : public Napi::AsyncWorker { - public: - Worker_krb5_cc_store_cred(krb5_context ctx, - krb5_ccache ccache, - krb5_creds creds, - Napi::Function& callback) - : Napi::AsyncWorker(callback), krb_context(ctx), - krb_ccache(ccache), - krb_creds(creds) { - } - - private: - void Execute() { -#ifdef __MVS__ - __ae_runmode rm(__AE_ASCII_MODE); -#endif - err = krb5_cc_store_cred(krb_context, krb_ccache, &krb_creds); - } - - void OnOK() { - Napi::HandleScope scope(Env()); - - Callback().Call({ - Napi::Number::New(Env(), err) - }); - } - - // In parameter - krb5_context krb_context; - krb5_ccache krb_ccache; - krb5_creds krb_creds; - - // Out parameter - krb5_error_code err; -}; - -Napi::Value _krb5_cc_store_cred(const Napi::CallbackInfo& info) { - if (info.Length() < 4) { - throw Napi::TypeError::New(info.Env(), "4 arguments expected"); - } - - krb5_context krb_context = info[0].As>().Data(); - krb5_ccache krb_ccache = info[1].As>().Data(); - krb5_creds krb_creds = *info[2].As>().Data(); - Napi::Function callback = info[3].As(); - - Worker_krb5_cc_store_cred* worker = new Worker_krb5_cc_store_cred(krb_context, - krb_ccache, - krb_creds, - callback); - worker->Queue(); - return info.Env().Undefined(); -} - - -/** - * krb5_free_context - */ -class Worker_krb5_free_context : public Napi::AsyncWorker { - public: - Worker_krb5_free_context(krb5_context ctx, Napi::Function& callback) - : Napi::AsyncWorker(callback), context(ctx) { - } - - private: - void Execute() { -#ifdef __MVS__ - __ae_runmode rm(__AE_ASCII_MODE); -#endif - krb5_free_context(context); - } - - void OnOK() { - Napi::HandleScope scope(Env()); - - Callback().Call({}); - } - - // In parameters - krb5_context context; -}; - -Napi::Value _krb5_free_context(const Napi::CallbackInfo& info) { - if (info.Length() < 2) { - throw Napi::TypeError::New(info.Env(), "2 arguments expected"); - } - - krb5_context krb_context = info[0].As>().Data(); - Napi::Function callback = info[1].As(); - - Worker_krb5_free_context* worker = new Worker_krb5_free_context(krb_context, callback); - worker->Queue(); - return info.Env().Undefined(); -} - -Napi::Value _krb5_free_context_sync(const Napi::CallbackInfo& info) { - if (info.Length() < 1) { - throw Napi::TypeError::New(info.Env(), "1 arguments expected"); - } - - krb5_context krb_context = info[0].As>().Data(); - krb5_free_context(krb_context); - - return info.Env().Undefined(); -} - - -/** - * krb5_free_principal - */ -Napi::Value _krb5_free_principal_sync(const Napi::CallbackInfo& info) { - if (info.Length() < 2) { - throw Napi::TypeError::New(info.Env(), "2 arguments expected"); - } - - krb5_context krb_context = info[0].As>().Data(); - krb5_principal krb_princ = info[1].As>().Data(); - krb5_free_principal(krb_context, krb_princ); - - return info.Env().Undefined(); -} - -/** - * krb5_free_creds - */ -Napi::Value _krb5_free_creds_sync(const Napi::CallbackInfo& info) { - if (info.Length() < 2) { - throw Napi::TypeError::New(info.Env(), "2 arguments expected"); - } - - krb5_context krb_context = info[0].As>().Data(); - krb5_creds krb_creds = *info[1].As>().Data(); - krb5_free_creds(krb_context, &krb_creds); - - return info.Env().Undefined(); -} - - -/** - * krb5_get_default_realm - */ -class Worker_krb5_get_default_realm : public Napi::AsyncWorker { - public: - Worker_krb5_get_default_realm(krb5_context ctx, Napi::Function& callback) - : Napi::AsyncWorker(callback), context(ctx), realm(nullptr) { - } - - private: - void Execute() { -#ifdef __MVS__ - __ae_runmode rm(__AE_ASCII_MODE); -#endif - err = krb5_get_default_realm(context, &realm); - //pid_t pid = syscall(__NR_gettid); - //std::cout << "get)Thread id : " << pid << std::endl; - } - - void OnOK() { - Napi::HandleScope scope(Env()); - - Callback().Call({ - Napi::Number::New(Env(), err), - Napi::String::New(Env(), realm ? realm : "") - }); - } - - // In parameters - krb5_context context; - - // Out parameters - krb5_error_code err; - char* realm; -}; - -Napi::Value _krb5_get_default_realm(const Napi::CallbackInfo& info) { - if (info.Length() < 2) { - throw Napi::TypeError::New(info.Env(), "2 argument expected"); - } - - krb5_context krb_context = info[0].As>().Data(); - Napi::Function callback = info[1].As(); - - Worker_krb5_get_default_realm* worker = - new Worker_krb5_get_default_realm(krb_context, callback); - worker->Queue(); - return info.Env().Undefined(); -} - - -/** - * krb5_get_error_message_sync - */ -Napi::Value _krb5_get_error_message_sync(const Napi::CallbackInfo& info) { - if (info.Length() < 2) { - throw Napi::TypeError::New(info.Env(), "2 argument expected"); - } - krb5_context krb_context = info[0].As>().Data(); - krb5_error_code err_code = info[1].As(); - - const char* err_msg = krb5_get_error_message(krb_context, err_code); - Napi::String ret_msg = Napi::String::New(info.Env(), err_msg); - krb5_free_error_message(krb_context, err_msg); - - return ret_msg; -} - - -/** - * krb5_get_init_creds_password - */ - -class Worker_krb5_get_init_creds_password : public Napi::AsyncWorker { - public: - Worker_krb5_get_init_creds_password(krb5_context ctx, - krb5_principal princ, - std::string password, - Napi::Function& callback) - : Napi::AsyncWorker(callback), krb_context(ctx), - krb_princ(princ), - krb_password(password) { - } - - private: - void Execute() { -#ifdef __MVS__ - __ae_runmode rm(__AE_ASCII_MODE); -#endif - err = krb5_get_init_creds_password(krb_context, - &creds, - krb_princ, - krb_password.c_str(), - nullptr, - nullptr, - 0, - NULL, - nullptr); - } - - void OnOK() { - Napi::HandleScope scope(Env()); - - Callback().Call({ - Napi::Number::New(Env(), err), - Napi::Buffer::Copy(Env(), &creds, sizeof(creds)) - }); - } - - // In parameter - krb5_context krb_context; - krb5_principal krb_princ; - std::string krb_password; - - // Out parameter - krb5_error_code err; - krb5_creds creds; -}; - -Napi::Value _krb5_get_init_creds_password(const Napi::CallbackInfo& info) { - if (info.Length() < 4) { - throw Napi::TypeError::New(info.Env(), "4 arguments expected"); - } - - krb5_context krb_context = info[0].As>().Data(); - krb5_principal krb_princ = info[1].As>().Data(); - std::string password = info[2].As().Utf8Value(); - Napi::Function callback = info[3].As(); - - Worker_krb5_get_init_creds_password* worker = - new Worker_krb5_get_init_creds_password(krb_context, - krb_princ, - password, - callback); - worker->Queue(); - return info.Env().Undefined(); -} - - -/** - * krb5_init_context - */ -class Worker_krb5_init_context : public Napi::AsyncWorker { - public: - Worker_krb5_init_context(Napi::Function& callback) - : Napi::AsyncWorker(callback) { - } - - private: - void Execute() { -#ifdef __MVS__ - __ae_runmode rm(__AE_ASCII_MODE); -#endif - err = krb5_init_context(&context); - } - - void OnOK() { - Napi::HandleScope scope(Env()); - //pid_t pid = syscall(__NR_gettid); - //std::cout << "cb)Thread id : " << pid << std::endl; - - Callback().Call({ - Napi::Number::New(Env(), err), - Napi::External::New(Env(), context) - }); - } - - // Out parameters - krb5_error_code err; - krb5_context context; -}; - -Napi::Value _krb5_init_context(const Napi::CallbackInfo& info) { - if (info.Length() < 1) { - throw Napi::TypeError::New(info.Env(), "1 argument expected"); - } - if (!info[0].IsFunction()) { - throw Napi::TypeError::New(info.Env(), "Expected callback"); - } - - Napi::Function callback = info[0].As(); - Worker_krb5_init_context* worker = new Worker_krb5_init_context(callback); - worker->Queue(); - return info.Env().Undefined(); -} - - -/** - * krb5_kt_resolve - */ -class Worker_krb5_kt_resolve : public Napi::AsyncWorker { - public: - Worker_krb5_kt_resolve(krb5_context ctx, - std::string kt_name, - Napi::Function& callback) - : Napi::AsyncWorker(callback), krb_context(ctx), - kt_name(kt_name) { - } - - private: - void Execute() { -#ifdef __MVS__ - __ae_runmode rm(__AE_ASCII_MODE); -#endif - err = krb5_kt_resolve(krb_context, kt_name.c_str(), &ktid); - } - - void OnOK() { - Napi::HandleScope scope(Env()); - - Callback().Call({ - Napi::Number::New(Env(), err), - Napi::External::New(Env(), ktid) - }); - } - - - // In parameters - krb5_context krb_context; - std::string kt_name; - - // Out parameters - krb5_error_code err; - krb5_keytab ktid; -}; - -Napi::Value _krb5_kt_resolve(const Napi::CallbackInfo& info) { - if (info.Length() < 3) { - throw Napi::TypeError::New(info.Env(), "3 argument expected"); - } - - krb5_context krb_context = info[0].As>().Data(); - std::string name = info[1].As().Utf8Value(); - Napi::Function callback = info[2].As(); - - Worker_krb5_kt_resolve* worker = - new Worker_krb5_kt_resolve(krb_context, name, callback); - worker->Queue(); - return info.Env().Undefined(); -} - - -/** - * krb5_get_init_creds_keytab - * missing param (nullptr): const char* in_tkt_service - * missing param (nullptr): krb5_get_init_creds_opt * k5_gic_options - */ -class Worker_krb5_get_init_creds_keytab : public Napi::AsyncWorker { - public: - Worker_krb5_get_init_creds_keytab(krb5_context ctx, - krb5_principal princ, - krb5_keytab kt, - krb5_deltat start, - std::string tkt_service, - krb5_get_init_creds_opt * k5_gic_options, - Napi::Function& callback) - : Napi::AsyncWorker(callback), krb_context(ctx), - krb_client(princ), - krb_kt(kt), - start_time(start), - krb_tkt_service(tkt_service), - krb_init_creds_opt(k5_gic_options) { - } - - private: - void Execute() { -#ifdef __MVS__ - __ae_runmode rm(__AE_ASCII_MODE); -#endif - err = krb5_get_init_creds_keytab(krb_context, - &creds, - krb_client, - krb_kt, - start_time, - (krb_tkt_service == "") ? - nullptr : krb_tkt_service.c_str(), - krb_init_creds_opt); - } - - void OnOK() { - Napi::HandleScope scope(Env()); - - Callback().Call({ - Napi::Number::New(Env(), err), - Napi::Buffer::Copy(Env(), &creds, sizeof(creds)) - }); - } - - - // In parameters - krb5_context krb_context; - krb5_principal krb_client; - krb5_keytab krb_kt; - krb5_deltat start_time; - std::string krb_tkt_service; - krb5_get_init_creds_opt* krb_init_creds_opt; - - // Out parameters - krb5_error_code err; - krb5_creds creds; -}; - -Napi::Value _krb5_get_init_creds_keytab(const Napi::CallbackInfo& info) { - if (info.Length() < 3) { - throw Napi::TypeError::New(info.Env(), "5 argument expected"); - } - - krb5_context krb_context = info[0].As>().Data(); - krb5_principal krb_princ = info[1].As>().Data(); - krb5_keytab krb_kt = info[2].As>().Data(); - krb5_deltat start = info[3].As().Int32Value(); - Napi::Function callback = info[4].As(); - - Worker_krb5_get_init_creds_keytab* worker = - new Worker_krb5_get_init_creds_keytab(krb_context, - krb_princ, - krb_kt, - start, - "", // std::string("") becomes nullptr - nullptr, - callback); - worker->Queue(); - return info.Env().Undefined(); -} diff --git a/src/krb5_bind.h b/src/krb5_bind.h deleted file mode 100644 index d1da4f6..0000000 --- a/src/krb5_bind.h +++ /dev/null @@ -1,24 +0,0 @@ -#include -#include -#include -#include - -Napi::Value _krb5_build_principal(const Napi::CallbackInfo& info); -Napi::Value _krb5_cc_close(const Napi::CallbackInfo& info); -Napi::Value _krb5_cc_default(const Napi::CallbackInfo& info); -Napi::Value _krb5_cc_destroy(const Napi::CallbackInfo& info); -Napi::Value _krb5_cc_get_name_sync(const Napi::CallbackInfo& info); -Napi::Value _krb5_cc_initialize(const Napi::CallbackInfo& info); -Napi::Value _krb5_cc_initialize_sync(const Napi::CallbackInfo& info); -Napi::Value _krb5_cc_resolve(const Napi::CallbackInfo& info); -Napi::Value _krb5_cc_store_cred(const Napi::CallbackInfo& info); -Napi::Value _krb5_free_context(const Napi::CallbackInfo& info); -Napi::Value _krb5_free_context_sync(const Napi::CallbackInfo& info); -Napi::Value _krb5_free_creds_sync(const Napi::CallbackInfo& info); -Napi::Value _krb5_free_principal_sync(const Napi::CallbackInfo& info); -Napi::Value _krb5_get_default_realm(const Napi::CallbackInfo& info); -Napi::Value _krb5_get_error_message_sync(const Napi::CallbackInfo& info); -Napi::Value _krb5_get_init_creds_keytab(const Napi::CallbackInfo& info); -Napi::Value _krb5_get_init_creds_password(const Napi::CallbackInfo& info); -Napi::Value _krb5_init_context(const Napi::CallbackInfo& info); -Napi::Value _krb5_kt_resolve(const Napi::CallbackInfo& info); diff --git a/src/krb5_zos.h b/src/krb5_zos.h deleted file mode 100644 index d17350d..0000000 --- a/src/krb5_zos.h +++ /dev/null @@ -1,12 +0,0 @@ -#ifndef KRB5_ZOS_H_ -#define KRB5_ZOS_H_ -#include <_Nascii.h> - -class __ae_runmode { - int mode; - -public: - __ae_runmode(int new_mode) { mode = __ae_thread_swapmode(new_mode); } - ~__ae_runmode() { __ae_thread_swapmode(mode); } -}; -#endif diff --git a/src/module.cc b/src/module.cc deleted file mode 100644 index cd9054b..0000000 --- a/src/module.cc +++ /dev/null @@ -1,31 +0,0 @@ -#include "krb5_bind.h" -#include "gss_bind.h" - - -Napi::Object init(Napi::Env env, Napi::Object exports) { - exports.Set(Napi::String::New(env, "krb5_build_principal"), Napi::Function::New(env, _krb5_build_principal)); - exports.Set(Napi::String::New(env, "krb5_cc_close"), Napi::Function::New(env, _krb5_cc_close)); - exports.Set(Napi::String::New(env, "krb5_cc_default"), Napi::Function::New(env, _krb5_cc_default)); - exports.Set(Napi::String::New(env, "krb5_cc_destroy"), Napi::Function::New(env, _krb5_cc_destroy)); - exports.Set(Napi::String::New(env, "krb5_cc_get_name_sync"), Napi::Function::New(env, _krb5_cc_get_name_sync)); - exports.Set(Napi::String::New(env, "krb5_cc_initialize"), Napi::Function::New(env, _krb5_cc_initialize)); - exports.Set(Napi::String::New(env, "krb5_cc_initialize_sync"), Napi::Function::New(env, _krb5_cc_initialize_sync)); - exports.Set(Napi::String::New(env, "krb5_cc_resolve"), Napi::Function::New(env, _krb5_cc_resolve)); - exports.Set(Napi::String::New(env, "krb5_cc_store_cred"), Napi::Function::New(env, _krb5_cc_store_cred)); - exports.Set(Napi::String::New(env, "krb5_free_context"), Napi::Function::New(env, _krb5_free_context)); - exports.Set(Napi::String::New(env, "krb5_free_context_sync"), Napi::Function::New(env, _krb5_free_context_sync)); - exports.Set(Napi::String::New(env, "krb5_free_creds_sync"), Napi::Function::New(env, _krb5_free_creds_sync)); - exports.Set(Napi::String::New(env, "krb5_free_principal_sync"), Napi::Function::New(env, _krb5_free_principal_sync)); - exports.Set(Napi::String::New(env, "krb5_get_default_realm"), Napi::Function::New(env, _krb5_get_default_realm)); - exports.Set(Napi::String::New(env, "krb5_get_error_message_sync"), Napi::Function::New(env, _krb5_get_error_message_sync)); - exports.Set(Napi::String::New(env, "krb5_get_init_creds_keytab"), Napi::Function::New(env, _krb5_get_init_creds_keytab)); - exports.Set(Napi::String::New(env, "krb5_get_init_creds_password"), Napi::Function::New(env, _krb5_get_init_creds_password)); - exports.Set(Napi::String::New(env, "krb5_init_context"), Napi::Function::New(env, _krb5_init_context)); - exports.Set(Napi::String::New(env, "krb5_kt_resolve"), Napi::Function::New(env, _krb5_kt_resolve)); - - exports.Set(Napi::String::New(env, "generate_spnego_token"), Napi::Function::New(env, _generate_spnego_token)); - - return exports; -}; - -NODE_API_MODULE(NODE_GYP_MODULE_NAME, init); diff --git a/src/server.py b/src/server.py index 3c50363..0b95f05 100644 --- a/src/server.py +++ b/src/server.py @@ -3,10 +3,14 @@ import base64 import socket import gssapi +import sys -FQDN = socket.getfqdn() +if len(sys.argv) == 2 and sys.argv[1] == "-local": + FQDN = "rest.krb.local" + print("Local mode, FQDN forced") +else: + FQDN = socket.getfqdn() print('FQDN: %s' % FQDN) - princ_name = "HTTP/%s@%s" % (FQDN, 'KRB.LOCAL') server_name = gssapi.Name( 'HTTP@' + FQDN, name_type=gssapi.NameType.hostbased_service) diff --git a/test/kdestroy.coffee b/test/kdestroy.coffee index f026372..024010a 100644 --- a/test/kdestroy.coffee +++ b/test/kdestroy.coffee @@ -1,5 +1,8 @@ krb5 = require '../lib/' +ccacheStartName = process.env.DEFAULT_CCACHE_LOCATION || '/tmp' +customcc = ccacheStartName + "/customcc" +restKeytab = process.env.REST_KEYTAB || '/tmp/krb5_test/rest.service.keytab' describe 'kdestroy', -> diff --git a/test/kinit.coffee b/test/kinit.coffee index a773302..011073e 100644 --- a/test/kinit.coffee +++ b/test/kinit.coffee @@ -1,5 +1,9 @@ krb5 = require '../lib/' +ccacheStartName = process.env.DEFAULT_CCACHE_LOCATION || '/tmp' +customcc = ccacheStartName + "/customcc" +restKeytab = process.env.REST_KEYTAB || '/tmp/krb5_test/rest.service.keytab' + describe 'kinit', -> describe 'function with callback', -> @@ -11,7 +15,7 @@ describe 'kinit', -> realm: 'KRB.LOCAL' , (err, ccname) -> return done err if err - ccname.should.startWith('/tmp') + ccname.should.startWith(ccacheStartName) done() it 'returns default credential cache path (password provided using default realm)', (done) -> @@ -20,7 +24,7 @@ describe 'kinit', -> password: 'adm1n_p4ssw0rd' , (err, ccname) -> return done err if err - ccname.should.startWith('/tmp') + ccname.should.startWith(ccacheStartName) done err it 'returns default credential cache path (password provided using realm in principal)', (done) -> @@ -30,28 +34,28 @@ describe 'kinit', -> realm: 'to_override' , (err, ccname) -> return done err if err - ccname.should.startWith('/tmp') + ccname.should.startWith(ccacheStartName) done err it 'returns default credential cache path (keytab provided)', (done) -> krb5.kinit principal: 'rest/rest.krb.local' - keytab: '/tmp/krb5_test/rest.service.keytab' + keytab: restKeytab realm: 'KRB.LOCAL' , (err, ccname) -> return done err if err - ccname.should.startWith('/tmp') + ccname.should.startWith(ccacheStartName) done() it 'returns given credential cache path (keytab provided)', (done) -> krb5.kinit principal: 'rest/rest.krb.local' - keytab: '/tmp/krb5_test/rest.service.keytab' + keytab: restKeytab realm: 'KRB.LOCAL' - ccname: '/tmp/customcc' + ccname: customcc , (err, ccname) -> return done err if err - ccname.should.be.eql('/tmp/customcc') + ccname.should.be.eql(customcc) done() describe 'function with promise', -> @@ -62,4 +66,4 @@ describe 'kinit', -> password: 'adm1n_p4ssw0rd' realm: 'KRB.LOCAL' .then (ccname) -> - ccname.should.startWith('/tmp') + ccname.should.startWith(ccacheStartName) diff --git a/test/local_kdc/.gitignore b/test/local_kdc/.gitignore new file mode 100644 index 0000000..f819d80 --- /dev/null +++ b/test/local_kdc/.gitignore @@ -0,0 +1,5 @@ +* +!.gitignore +!conf.sh +!start_kdc.sh +!stop_kdc.sh diff --git a/test/local_kdc/conf.sh b/test/local_kdc/conf.sh new file mode 100644 index 0000000..d632c84 --- /dev/null +++ b/test/local_kdc/conf.sh @@ -0,0 +1,58 @@ +#!/usr/bin/env bash + +function re_mkdir() { + if [[ -d $1 ]]; then + rm -Rf "$1" + fi + mkdir -p "$1" +} + +function kill_pid() { + if [[ -f "$1" ]]; then + kill "$(cat $1)" + rm "$1" + fi +} + +function rm_kt { + if [[ -f "$1" ]]; then + rm "$1" + fi +} + +if [[ ! -z $1 ]]; then + basedir="$1" +else + basedir="$( cd -- "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )" +fi + +export CONF_DIR="$basedir/conf" +export KRB5_CONFIG="$CONF_DIR/krb5.conf" +export LOG_DIR="$basedir/logs" +export DEFAULT_LOG_FILE="$LOG_DIR/krb5libs.log" +export KDC_LOG_FILE="$LOG_DIR/krb5kdc.log" +export ADMIN_SERVER_LOG_FILE="$LOG_DIR/kadmind.log" +export DEFAULT_CCACHE_LOCATION="$basedir/ccaches" +export DB_LOCATION="$basedir/db" +export STASH_FILE="$DB_LOCATION/.k5.KRB.LOCAL" +export DB_NAME="$DB_LOCATION/principal" +export PID_DIR="$basedir/pid" +export ACL_FILE="$basedir/conf/kadm5.acl" +export KEYTAB_DIR="$basedir/keytab" +export REST_KEYTAB="$KEYTAB_DIR/rest.service.keytab" +export HTTP_KEYTAB="$KEYTAB_DIR/http.service.keytab" +export KRB5_KTNAME="FILE:$HTTP_KEYTAB" # keytab used by server.py +export REST_ADDRESS="$(hostname -f)" + + +if [[ 0 -ne $(command -v krb5kdc >/dev/null; echo $?) ]]; then + if [[ ! -z "$KRB5_HOME" ]]; then + export PATH="$KRB5_HOME/sbin:$PATH" + fi + if [[ 0 -ne $(command -v krb5kdc >/dev/null; echo $?) ]]; then + echo "Fail to find krb5kdc in PATH, either you installed krb5 via homebrew then you must set KRB5_HOME to your cellar/krb5 path" + echo "(example /opt/homebrew/opt/krb5/) or you didn't install the relevant package (krb5-kdc on debian-like)" + echo "In case you compiled mit krb5 to a custom prefix, set the prefix as KRB5_HOME" + exit 1 + fi +fi diff --git a/test/local_kdc/start_kdc.sh b/test/local_kdc/start_kdc.sh new file mode 100755 index 0000000..9d5ddff --- /dev/null +++ b/test/local_kdc/start_kdc.sh @@ -0,0 +1,83 @@ +#!/usr/bin/env bash + + +if [[ ! -z $1 ]]; then + basedir="$1" +else + basedir="$( cd -- "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )" +fi + + +source "$basedir/conf.sh" + +kill_pid "$PID_DIR/krb5kdc.pid" +kill_pid "$PID_DIR/kadmind.pid" +kill_pid "$PID_DIR/server.pid" + +re_mkdir "$CONF_DIR" +re_mkdir "$LOG_DIR" +re_mkdir "$DEFAULT_CCACHE_LOCATION" +re_mkdir "$DB_LOCATION" +re_mkdir "$PID_DIR" +re_mkdir "$KEYTAB_DIR" + +cat>"$KRB5_CONFIG"< "$ACL_FILE" + + +if [[ -f "$DB_NAME" ]]; then + rm -f "$DB_NAME*" +fi + +kdb5_util -d "$DB_NAME" -P masterkey create -s +sleep 2 +kadmin.local -q "addprinc -pw adm1n_p4ssw0rd admin" + +rm_kt "$REST_KEYTAB" +rm_kt "$HTTP_KEYTAB" + + +kadmin.local -q "addprinc -randkey rest/rest.krb.local@KRB.LOCAL" +kadmin.local -q "xst -k $REST_KEYTAB rest/rest.krb.local@KRB.LOCAL" + +kadmin.local -q "addprinc -randkey HTTP/rest.krb.local@KRB.LOCAL" +kadmin.local -q "xst -k $HTTP_KEYTAB HTTP/rest.krb.local@KRB.LOCAL" + + +krb5kdc -P "$PID_DIR/krb5kdc.pid" +kadmind -P "$PID_DIR/kadmind.pid" +sleep 2 +PYTHONUNBUFFERED=1 python3 "$basedir/../../src/server.py" -local > $LOG_DIR/server.log & echo $! > "$PID_DIR/server.pid" diff --git a/test/local_kdc/stop_kdc.sh b/test/local_kdc/stop_kdc.sh new file mode 100755 index 0000000..6bd8eac --- /dev/null +++ b/test/local_kdc/stop_kdc.sh @@ -0,0 +1,13 @@ +#!/usr/bin/env bash + +if [[ ! -z $1 ]]; then + basedir="$1" +else + basedir="$( cd -- "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )" +fi + +source "$basedir/conf.sh" + +kill_pid "$PID_DIR/krb5kdc.pid" +kill_pid "$PID_DIR/kadmind.pid" +kill_pid "$PID_DIR/server.pid" diff --git a/test/local_test.sh b/test/local_test.sh new file mode 100755 index 0000000..2c4ec53 --- /dev/null +++ b/test/local_test.sh @@ -0,0 +1,9 @@ +#!/bin/bash + +current_script_basedir="$( cd -- "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )" +source "$current_script_basedir/local_kdc/conf.sh" "$current_script_basedir/local_kdc" +. "$current_script_basedir/local_kdc/start_kdc.sh" "$current_script_basedir/local_kdc" + +npm run test + +. "$current_script_basedir/local_kdc/stop_kdc.sh" "$current_script_basedir/local_kdc" diff --git a/test/spnego.coffee b/test/spnego.coffee index 25aa6e8..5a062bc 100644 --- a/test/spnego.coffee +++ b/test/spnego.coffee @@ -1,13 +1,18 @@ krb5 = require '../lib/' http = require 'http' +ccacheStartName = process.env.DEFAULT_CCACHE_LOCATION || '/tmp' +customcc = ccacheStartName + "/customcc" +restKeytab = process.env.REST_KEYTAB || '/tmp/krb5_test/rest.service.keytab' +hostname = process.env.REST_ADDRESS || 'rest.krb.local' + describe 'spnego', -> describe 'function with callback', -> it 'checks that server requires authentication', (done) -> http.request - hostname: 'rest.krb.local' + hostname: hostname port: 8080 path: '/' method: 'GET' @@ -19,7 +24,7 @@ describe 'spnego', -> it 'authenticates to REST server with SPNEGO token (hostbased service)', (done) -> krb5.kinit principal: 'rest/rest.krb.local' - keytab: 'FILE:/tmp/krb5_test/rest.service.keytab' + keytab: 'FILE:' + restKeytab realm: 'KRB.LOCAL' , (err, ccname) -> krb5.spnego @@ -28,7 +33,7 @@ describe 'spnego', -> return done err if err token.should.be.String() http.request - hostname: 'rest.krb.local' + hostname: hostname port: 8080 path: '/' method: 'GET' @@ -42,7 +47,7 @@ describe 'spnego', -> it 'authenticates to REST server with SPNEGO token (service principal)', (done) -> krb5.kinit principal: 'rest/rest.krb.local' - keytab: 'FILE:/tmp/krb5_test/rest.service.keytab' + keytab: 'FILE:' + restKeytab realm: 'KRB.LOCAL' , (err, ccname) -> krb5.spnego @@ -51,7 +56,7 @@ describe 'spnego', -> return done err if err token.should.be.String() http.request - hostname: 'rest.krb.local' + hostname: hostname port: 8080 path: '/' method: 'GET' @@ -65,7 +70,7 @@ describe 'spnego', -> it 'authenticates to REST server with SPNEGO token (service fqdn)', (done) -> krb5.kinit principal: 'rest/rest.krb.local' - keytab: 'FILE:/tmp/krb5_test/rest.service.keytab' + keytab: 'FILE:' + restKeytab realm: 'KRB.LOCAL' , (err, ccname) -> krb5.spnego @@ -74,7 +79,7 @@ describe 'spnego', -> return done err if err token.should.be.String() http.request - hostname: 'rest.krb.local' + hostname: hostname port: 8080 path: '/' method: 'GET' @@ -90,16 +95,16 @@ describe 'spnego', -> principal: 'admin' password: 'adm1n_p4ssw0rd' realm: 'KRB.LOCAL' - ccname: '/tmp/customcc' + ccname: customcc , (err, ccname) -> krb5.spnego hostbased_service: 'HTTP@rest.krb.local' - ccname: '/tmp/customcc' + ccname: customcc , (err, token) -> return done err if err token.should.be.String() http.request - hostname: 'rest.krb.local' + hostname: hostname port: 8080 path: '/' method: 'GET' @@ -115,7 +120,7 @@ describe 'spnego', -> it 'returns SPNEGO token', -> krb5.kinit principal: 'rest/rest.krb.local' - keytab: '/tmp/krb5_test/rest.service.keytab' + keytab: restKeytab realm: 'KRB.LOCAL' .then (ccname) -> krb5.spnego @@ -123,7 +128,7 @@ describe 'spnego', -> .then (token) -> token.should.be.String() http.request - hostname: 'rest.krb.local' + hostname: hostname port: 8080 path: '/' method: 'GET' diff --git a/vagrant/provision.yaml b/vagrant/provision.yaml index be23d56..cc07f19 100644 --- a/vagrant/provision.yaml +++ b/vagrant/provision.yaml @@ -14,9 +14,11 @@ - name: Install dev dependencies win_chocolatey: name: - - python - - nodejs + - llvm + - rustup.install - git + - nodejs + - python state: present - name: Install MIT Kerberos 4.1 @@ -29,4 +31,4 @@ win_shell: npm config set msvs_version 2017 - name: Clone krb5 folder - win_shell: git clone https://github.com/adaltas/node-krb5 /node-krb5 \ No newline at end of file + win_shell: git clone https://github.com/adaltas/node-krb5 /node-krb5