From deffa5555757444013bf386b18e00da66fff3843 Mon Sep 17 00:00:00 2001 From: Ionut Mihalcea Date: Tue, 12 Jan 2021 15:28:09 +0000 Subject: [PATCH] Add admin configuration This commit adds support for authenticators to identify admin applications. The admin app names can be configured from the config.toml file and lead to the ApplicationName having a flag set indicating whether the app is an admin or not. Signed-off-by: Ionut Mihalcea --- config.toml | 10 +++ .../normal_tests/asym_sign_verify.rs | 2 +- .../direct_authenticator/mod.rs | 69 +++++++++++++--- .../jwt_svid_authenticator/mod.rs | 12 +-- src/authenticators/mod.rs | 79 ++++++++++++++++--- .../mod.rs | 73 ++++++++++++++--- src/key_info_managers/on_disk_manager/mod.rs | 14 ++-- src/utils/service_builder.rs | 25 ++++-- 8 files changed, 236 insertions(+), 48 deletions(-) diff --git a/config.toml b/config.toml index 324f9424..be502072 100644 --- a/config.toml +++ b/config.toml @@ -64,6 +64,16 @@ timeout = 200 # in milliseconds # https://parallaxsecond.github.io/parsec-book/parsec_security/secure_deployment.html auth_type = "UnixPeerCredentials" +# List of admins to be identified by the authenticator. +# The "name" field of each entry in the list must contain the application name (as required by the +# identifier in `auth_type`). For example, for `UnixPeerCredentials`, the names should be UIDs of +# the admin users. +# WARNING: Admins have special privileges and access to operations that are not permitted for normal +# users of the service. Only enable this feature with some list of admins if you are confident +# about the need for those permissions. +# Read more here: https://parallaxsecond.github.io/parsec-book/parsec_client/operations/index.html#core-operations +#admins = [ { name = "admin_1" }, { name = "admin_2" } ] + # (Required only for JwtSvid) Location of the Workload API endpoint # WARNING: only use this authenticator if the Workload API socket is TRUSTED. A malicious entity # owning that socket would have access to all the keys owned by clients using this authentication diff --git a/e2e_tests/tests/per_provider/normal_tests/asym_sign_verify.rs b/e2e_tests/tests/per_provider/normal_tests/asym_sign_verify.rs index 28159f9f..f3e5e146 100644 --- a/e2e_tests/tests/per_provider/normal_tests/asym_sign_verify.rs +++ b/e2e_tests/tests/per_provider/normal_tests/asym_sign_verify.rs @@ -289,7 +289,7 @@ fn fail_verify_hash() -> Result<()> { let mut signature = client.sign_with_rsa_sha256(key_name.clone(), hash.clone())?; // Modify signature - signature[4] += 1; + signature[4] ^= 1; let status = client .verify_with_rsa_sha256(key_name, hash, signature) .unwrap_err(); diff --git a/src/authenticators/direct_authenticator/mod.rs b/src/authenticators/direct_authenticator/mod.rs index 6a0d10ba..124a7fd1 100644 --- a/src/authenticators/direct_authenticator/mod.rs +++ b/src/authenticators/direct_authenticator/mod.rs @@ -8,8 +8,7 @@ //! This authenticator does not offer any security value and should only be used in environments //! where all the clients and the service are mutually trustworthy. -use super::ApplicationName; -use super::Authenticate; +use super::{Admin, AdminList, ApplicationName, Authenticate}; use crate::front::listener::ConnectionMetadata; use log::error; use parsec_interface::operations::list_authenticators; @@ -20,8 +19,19 @@ use parsec_interface::secrecy::ExposeSecret; use std::str; /// Direct authentication authenticator implementation -#[derive(Copy, Clone, Debug)] -pub struct DirectAuthenticator; +#[derive(Clone, Debug)] +pub struct DirectAuthenticator { + admins: AdminList, +} + +impl DirectAuthenticator { + /// Create new direct authenticator + pub fn new(admins: Vec) -> Self { + DirectAuthenticator { + admins: admins.into(), + } + } +} impl Authenticate for DirectAuthenticator { fn describe(&self) -> Result { @@ -47,7 +57,11 @@ impl Authenticate for DirectAuthenticator { Err(ResponseStatus::AuthenticationError) } else { match str::from_utf8(auth.buffer.expose_secret()) { - Ok(str) => Ok(ApplicationName(String::from(str))), + Ok(str) => { + let app_name = String::from(str); + let is_admin = self.admins.is_admin(&app_name); + Ok(ApplicationName::new(app_name, is_admin)) + } Err(_) => { error!("Error parsing the authentication value as a UTF-8 string."); Err(ResponseStatus::AuthenticationError) @@ -59,14 +73,16 @@ impl Authenticate for DirectAuthenticator { #[cfg(test)] mod test { - use super::super::Authenticate; + use super::super::{Admin, Authenticate}; use super::DirectAuthenticator; use parsec_interface::requests::request::RequestAuth; use parsec_interface::requests::ResponseStatus; #[test] fn successful_authentication() { - let authenticator = DirectAuthenticator {}; + let authenticator = DirectAuthenticator { + admins: Default::default(), + }; let app_name = "app_name".to_string(); let req_auth = RequestAuth::new(app_name.clone().into_bytes()); @@ -77,11 +93,14 @@ mod test { .expect("Failed to authenticate"); assert_eq!(auth_name.get_name(), app_name); + assert_eq!(auth_name.is_admin, false); } #[test] fn failed_authentication() { - let authenticator = DirectAuthenticator {}; + let authenticator = DirectAuthenticator { + admins: Default::default(), + }; let conn_metadata = None; let status = authenticator .authenticate(&RequestAuth::new(vec![0xff; 5]), conn_metadata) @@ -92,7 +111,9 @@ mod test { #[test] fn empty_auth() { - let authenticator = DirectAuthenticator {}; + let authenticator = DirectAuthenticator { + admins: Default::default(), + }; let conn_metadata = None; let status = authenticator .authenticate(&RequestAuth::new(Vec::new()), conn_metadata) @@ -100,4 +121,34 @@ mod test { assert_eq!(status, ResponseStatus::AuthenticationError); } + + #[test] + fn admin_check() { + let admin_name = String::from("admin_name"); + let authenticator = DirectAuthenticator { + admins: vec![Admin { + name: admin_name.clone(), + }] + .into(), + }; + + let app_name = "app_name".to_string(); + let req_auth = RequestAuth::new(app_name.clone().into_bytes()); + let conn_metadata = None; + + let auth_name = authenticator + .authenticate(&req_auth, conn_metadata) + .expect("Failed to authenticate"); + + assert_eq!(auth_name.get_name(), app_name); + assert_eq!(auth_name.is_admin, false); + + let req_auth = RequestAuth::new(admin_name.clone().into_bytes()); + let auth_name = authenticator + .authenticate(&req_auth, conn_metadata) + .expect("Failed to authenticate"); + + assert_eq!(auth_name.get_name(), admin_name); + assert_eq!(auth_name.is_admin, true); + } } diff --git a/src/authenticators/jwt_svid_authenticator/mod.rs b/src/authenticators/jwt_svid_authenticator/mod.rs index afaeefd7..e065c35d 100644 --- a/src/authenticators/jwt_svid_authenticator/mod.rs +++ b/src/authenticators/jwt_svid_authenticator/mod.rs @@ -2,8 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 //! JWT SVID authenticator -use super::ApplicationName; -use super::Authenticate; +use super::{Admin, AdminList, ApplicationName, Authenticate}; use crate::front::listener::ConnectionMetadata; use log::error; use parsec_interface::operations::list_authenticators; @@ -19,13 +18,15 @@ use std::str; #[allow(missing_debug_implementations)] pub struct JwtSvidAuthenticator { jwt_client: JWTClient, + admins: AdminList, } impl JwtSvidAuthenticator { /// Create a new JWT-SVID authenticator with a specific path to the Workload API socket. - pub fn new(workload_endpoint: String) -> Self { + pub fn new(workload_endpoint: String, admins: Vec) -> Self { JwtSvidAuthenticator { jwt_client: JWTClient::new(&workload_endpoint, None, None), + admins: admins.into(), } } } @@ -65,7 +66,8 @@ impl Authenticate for JwtSvidAuthenticator { error!("The validation of the JWT-SVID failed ({}).", e); ResponseStatus::AuthenticationError })?; - - Ok(ApplicationName(validate_response.spiffe_id().to_string())) + let app_name = validate_response.spiffe_id().to_string(); + let is_admin = self.admins.is_admin(&app_name); + Ok(ApplicationName::new(app_name, is_admin)) } } diff --git a/src/authenticators/mod.rs b/src/authenticators/mod.rs index ecb75ec6..5c91f803 100644 --- a/src/authenticators/mod.rs +++ b/src/authenticators/mod.rs @@ -20,11 +20,15 @@ use parsec_interface::operations::list_authenticators; use parsec_interface::requests::request::RequestAuth; use parsec_interface::requests::Result; use serde::Deserialize; +use std::ops::Deref; use zeroize::Zeroize; /// String wrapper for app names #[derive(Debug, Clone, Eq, PartialEq, Hash)] -pub struct ApplicationName(String); +pub struct ApplicationName { + name: String, + is_admin: bool, +} /// Authentication interface /// @@ -52,20 +56,33 @@ pub trait Authenticate { } impl ApplicationName { - /// Create a new ApplicationName from a String - pub fn new(name: String) -> ApplicationName { - ApplicationName(name) + /// Create a new ApplicationName + fn new(name: String, is_admin: bool) -> ApplicationName { + ApplicationName { name, is_admin } + } + + /// Create ApplicationName from name string only + pub fn from_name(name: String) -> ApplicationName { + ApplicationName { + name, + is_admin: false, + } } /// Get a reference to the inner string pub fn get_name(&self) -> &str { - &self.0 + &self.name + } + + /// Check whether the application is an admin + pub fn is_admin(&self) -> bool { + self.is_admin } } impl std::fmt::Display for ApplicationName { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", self.0) + write!(f, "{}", self.name) } } @@ -75,12 +92,56 @@ impl std::fmt::Display for ApplicationName { #[serde(tag = "auth_type")] pub enum AuthenticatorConfig { /// Direct authentication - Direct, - /// Unix Peer Credenditals authentication - UnixPeerCredentials, + Direct { + /// List of service admins + admins: Option>, + }, + /// Unix Peer Credentials authentication + UnixPeerCredentials { + /// List of service admins + admins: Option>, + }, /// JWT-SVID JwtSvid { /// Path to the Workload API socket workload_endpoint: String, + /// List of service admins + admins: Option>, }, } + +/// Structure defining the properties of a service admin +#[derive(Deserialize, Debug, Zeroize, Clone)] +#[zeroize(drop)] +pub struct Admin { + name: String, +} + +impl Admin { + fn name(&self) -> &str { + &self.name + } +} + +#[derive(Debug, Clone, Default)] +struct AdminList(Vec); + +impl AdminList { + fn is_admin(&self, app_name: &str) -> bool { + self.iter().any(|admin| admin.name() == app_name) + } +} + +impl Deref for AdminList { + type Target = Vec; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl From> for AdminList { + fn from(admin_list: Vec) -> Self { + AdminList(admin_list) + } +} diff --git a/src/authenticators/unix_peer_credentials_authenticator/mod.rs b/src/authenticators/unix_peer_credentials_authenticator/mod.rs index 240d030d..8081b5c0 100644 --- a/src/authenticators/unix_peer_credentials_authenticator/mod.rs +++ b/src/authenticators/unix_peer_credentials_authenticator/mod.rs @@ -9,8 +9,7 @@ //! //! Currently, the stringified UID is used as the application name. -use super::ApplicationName; -use super::Authenticate; +use super::{Admin, AdminList, ApplicationName, Authenticate}; use crate::front::listener::ConnectionMetadata; use log::error; use parsec_interface::operations::list_authenticators; @@ -21,8 +20,19 @@ use parsec_interface::secrecy::ExposeSecret; use std::convert::TryInto; /// Unix peer credentials authenticator. -#[derive(Copy, Clone, Debug)] -pub struct UnixPeerCredentialsAuthenticator; +#[derive(Clone, Debug)] +pub struct UnixPeerCredentialsAuthenticator { + admins: AdminList, +} + +impl UnixPeerCredentialsAuthenticator { + /// Create new Unix peer credentials authenticator + pub fn new(admins: Vec) -> Self { + UnixPeerCredentialsAuthenticator { + admins: admins.into(), + } + } +} impl Authenticate for UnixPeerCredentialsAuthenticator { fn describe(&self) -> Result { @@ -76,7 +86,9 @@ impl Authenticate for UnixPeerCredentialsAuthenticator { // Authentication is successful if the _actual_ UID from the Unix peer credentials equals // the self-declared UID in the authentication request. if uid == expected_uid { - Ok(ApplicationName(uid.to_string())) + let app_name = uid.to_string(); + let is_admin = self.admins.is_admin(&app_name); + Ok(ApplicationName::new(app_name, is_admin)) } else { error!("Declared UID in authentication request does not match the process's UID."); Err(ResponseStatus::AuthenticationError) @@ -86,7 +98,7 @@ impl Authenticate for UnixPeerCredentialsAuthenticator { #[cfg(test)] mod test { - use super::super::Authenticate; + use super::super::{Admin, Authenticate}; use super::UnixPeerCredentialsAuthenticator; use crate::front::domain_socket::peer_credentials; use crate::front::listener::ConnectionMetadata; @@ -108,7 +120,9 @@ mod test { peer_credentials::peer_cred(&_sock_b).unwrap(), ); - let authenticator = UnixPeerCredentialsAuthenticator {}; + let authenticator = UnixPeerCredentialsAuthenticator { + admins: Default::default(), + }; let req_auth_data = cred_a.uid.to_le_bytes().to_vec(); let req_auth = RequestAuth::new(req_auth_data); @@ -123,6 +137,7 @@ mod test { .expect("Failed to authenticate"); assert_eq!(auth_name.get_name(), get_current_uid().to_string()); + assert_eq!(auth_name.is_admin, false); } #[test] @@ -137,7 +152,9 @@ mod test { peer_credentials::peer_cred(&_sock_b).unwrap(), ); - let authenticator = UnixPeerCredentialsAuthenticator {}; + let authenticator = UnixPeerCredentialsAuthenticator { + admins: Default::default(), + }; let wrong_uid = cred_a.uid + 1; let wrong_req_auth_data = wrong_uid.to_le_bytes().to_vec(); @@ -163,7 +180,9 @@ mod test { peer_credentials::peer_cred(&_sock_b).unwrap(), ); - let authenticator = UnixPeerCredentialsAuthenticator {}; + let authenticator = UnixPeerCredentialsAuthenticator { + admins: Default::default(), + }; let garbage_data = rand::thread_rng().gen::<[u8; 32]>().to_vec(); let req_auth = RequestAuth::new(garbage_data); @@ -179,7 +198,9 @@ mod test { #[test] fn unsuccessful_authentication_no_metadata() { - let authenticator = UnixPeerCredentialsAuthenticator {}; + let authenticator = UnixPeerCredentialsAuthenticator { + admins: Default::default(), + }; let req_auth = RequestAuth::new("secret".into()); let conn_metadata = None; @@ -187,6 +208,38 @@ mod test { assert_eq!(auth_result, Err(ResponseStatus::AuthenticationError)); } + #[test] + fn admin_check() { + // Create two connected sockets. + let (sock_a, _sock_b) = UnixStream::pair().unwrap(); + let (cred_a, _cred_b) = ( + peer_credentials::peer_cred(&sock_a).unwrap(), + peer_credentials::peer_cred(&_sock_b).unwrap(), + ); + + let authenticator = UnixPeerCredentialsAuthenticator { + admins: vec![Admin { + name: get_current_uid().to_string(), + }] + .into(), + }; + + let req_auth_data = cred_a.uid.to_le_bytes().to_vec(); + let req_auth = RequestAuth::new(req_auth_data); + let conn_metadata = Some(ConnectionMetadata::UnixPeerCredentials { + uid: cred_a.uid, + gid: cred_a.gid, + pid: None, + }); + + let auth_name = authenticator + .authenticate(&req_auth, conn_metadata) + .expect("Failed to authenticate"); + + assert_eq!(auth_name.get_name(), get_current_uid().to_string()); + assert_eq!(auth_name.is_admin, true); + } + #[test] fn unsuccessful_authentication_wrong_metadata() { // TODO(new_metadata_variant): this test needs implementing when we have more than one diff --git a/src/key_info_managers/on_disk_manager/mod.rs b/src/key_info_managers/on_disk_manager/mod.rs index 0daf9c83..9dd9591c 100644 --- a/src/key_info_managers/on_disk_manager/mod.rs +++ b/src/key_info_managers/on_disk_manager/mod.rs @@ -75,7 +75,7 @@ fn base64_data_triple_to_key_triple( provider_id: ProviderID, key_name: &[u8], ) -> Result { - let app_name = ApplicationName::new(base64_data_to_string(app_name)?); + let app_name = ApplicationName::from_name(base64_data_to_string(app_name)?); let key_name = base64_data_to_string(key_name)?; Ok(KeyTriple { @@ -531,7 +531,7 @@ mod test { let path = PathBuf::from(env!("OUT_DIR").to_owned() + "/big_names_ascii_mappings"); let mut manager = OnDiskKeyInfoManager::new(path.clone()).unwrap(); - let big_app_name_ascii = ApplicationName::new(" Lorem ipsum dolor sit amet, ei suas viris sea, deleniti repudiare te qui. Natum paulo decore ut nec, ne propriae offendit adipisci has. Eius clita legere mel at, ei vis minimum tincidunt.".to_string()); + let big_app_name_ascii = ApplicationName::from_name(" Lorem ipsum dolor sit amet, ei suas viris sea, deleniti repudiare te qui. Natum paulo decore ut nec, ne propriae offendit adipisci has. Eius clita legere mel at, ei vis minimum tincidunt.".to_string()); let big_key_name_ascii = " Lorem ipsum dolor sit amet, ei suas viris sea, deleniti repudiare te qui. Natum paulo decore ut nec, ne propriae offendit adipisci has. Eius clita legere mel at, ei vis minimum tincidunt.".to_string(); let key_triple = KeyTriple::new(big_app_name_ascii, ProviderID::Core, big_key_name_ascii); @@ -549,7 +549,7 @@ mod test { let path = PathBuf::from(env!("OUT_DIR").to_owned() + "/big_names_emoticons_mappings"); let mut manager = OnDiskKeyInfoManager::new(path.clone()).unwrap(); - let big_app_name_emoticons = ApplicationName::new("😀😁😂😃😄😅😆😇😈😉😊😋😌😍😎😏😐😑😒😓😔😕😖😗😘😙😚😛😜😝😞😟😠😡😢😣😤😥😦😧😨😩😪😫😬😭😮".to_string()); + let big_app_name_emoticons = ApplicationName::from_name("😀😁😂😃😄😅😆😇😈😉😊😋😌😍😎😏😐😑😒😓😔😕😖😗😘😙😚😛😜😝😞😟😠😡😢😣😤😥😦😧😨😩😪😫😬😭😮".to_string()); let big_key_name_emoticons = "😀😁😂😃😄😅😆😇😈😉😊😋😌😍😎😏😐😑😒😓😔😕😖😗😘😙😚😛😜😝😞😟😠😡😢😣😤😥😦😧😨😩😪😫😬😭😮".to_string(); let key_triple = KeyTriple::new( @@ -570,12 +570,12 @@ mod test { fn create_and_load() { let path = PathBuf::from(env!("OUT_DIR").to_owned() + "/create_and_load_mappings"); - let app_name1 = ApplicationName::new("😀 Application One 😀".to_string()); + let app_name1 = ApplicationName::from_name("😀 Application One 😀".to_string()); let key_name1 = "😀 Key One 😀".to_string(); let key_triple1 = KeyTriple::new(app_name1, ProviderID::Core, key_name1); let key_info1 = test_key_info(); - let app_name2 = ApplicationName::new("😇 Application Two 😇".to_string()); + let app_name2 = ApplicationName::from_name("😇 Application Two 😇".to_string()); let key_name2 = "😇 Key Two 😇".to_string(); let key_triple2 = KeyTriple::new(app_name2, ProviderID::MbedCrypto, key_name2); let key_info2 = KeyInfo { @@ -583,7 +583,7 @@ mod test { attributes: test_key_attributes(), }; - let app_name3 = ApplicationName::new("😈 Application Three 😈".to_string()); + let app_name3 = ApplicationName::from_name("😈 Application Three 😈".to_string()); let key_name3 = "😈 Key Three 😈".to_string(); let key_triple3 = KeyTriple::new(app_name3, ProviderID::Core, key_name3); let key_info3 = KeyInfo { @@ -617,7 +617,7 @@ mod test { fn new_key_triple(key_name: String) -> KeyTriple { KeyTriple::new( - ApplicationName::new("Testing Application 😎".to_string()), + ApplicationName::from_name("Testing Application 😎".to_string()), ProviderID::MbedCrypto, key_name, ) diff --git a/src/utils/service_builder.rs b/src/utils/service_builder.rs index 84f5e7a6..28fd9c24 100644 --- a/src/utils/service_builder.rs +++ b/src/utils/service_builder.rs @@ -398,16 +398,27 @@ fn build_authenticators(config: &AuthenticatorConfig) -> Vec<(AuthType, Authenti let mut authenticators: Vec<(AuthType, Authenticator)> = Vec::new(); match config { - AuthenticatorConfig::Direct => { - authenticators.push((AuthType::Direct, Box::from(DirectAuthenticator {}))) - } - AuthenticatorConfig::UnixPeerCredentials => authenticators.push(( + AuthenticatorConfig::Direct { admins } => authenticators.push(( + AuthType::Direct, + Box::from(DirectAuthenticator::new( + admins.as_ref().cloned().unwrap_or_default(), + )), + )), + AuthenticatorConfig::UnixPeerCredentials { admins } => authenticators.push(( AuthType::UnixPeerCredentials, - Box::from(UnixPeerCredentialsAuthenticator {}), + Box::from(UnixPeerCredentialsAuthenticator::new( + admins.as_ref().cloned().unwrap_or_default(), + )), )), - AuthenticatorConfig::JwtSvid { workload_endpoint } => authenticators.push(( + AuthenticatorConfig::JwtSvid { + workload_endpoint, + admins, + } => authenticators.push(( AuthType::JwtSvid, - Box::from(JwtSvidAuthenticator::new(workload_endpoint.to_string())), + Box::from(JwtSvidAuthenticator::new( + workload_endpoint.to_string(), + admins.as_ref().cloned().unwrap_or_default(), + )), )), };