Skip to content

Commit

Permalink
Add admin configuration
Browse files Browse the repository at this point in the history
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 <ionut.mihalcea@arm.com>
  • Loading branch information
ionut-arm committed Jan 13, 2021
1 parent c67b25a commit deffa55
Show file tree
Hide file tree
Showing 8 changed files with 236 additions and 48 deletions.
10 changes: 10 additions & 0 deletions config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
69 changes: 60 additions & 9 deletions src/authenticators/direct_authenticator/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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<Admin>) -> Self {
DirectAuthenticator {
admins: admins.into(),
}
}
}

impl Authenticate for DirectAuthenticator {
fn describe(&self) -> Result<list_authenticators::AuthenticatorInfo> {
Expand All @@ -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)
Expand All @@ -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());
Expand All @@ -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)
Expand All @@ -92,12 +111,44 @@ 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)
.expect_err("Empty auth should have failed");

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);
}
}
12 changes: 7 additions & 5 deletions src/authenticators/jwt_svid_authenticator/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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<Admin>) -> Self {
JwtSvidAuthenticator {
jwt_client: JWTClient::new(&workload_endpoint, None, None),
admins: admins.into(),
}
}
}
Expand Down Expand Up @@ -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))
}
}
79 changes: 70 additions & 9 deletions src/authenticators/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
///
Expand Down Expand Up @@ -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)
}
}

Expand All @@ -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<Vec<Admin>>,
},
/// Unix Peer Credentials authentication
UnixPeerCredentials {
/// List of service admins
admins: Option<Vec<Admin>>,
},
/// JWT-SVID
JwtSvid {
/// Path to the Workload API socket
workload_endpoint: String,
/// List of service admins
admins: Option<Vec<Admin>>,
},
}

/// 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<Admin>);

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<Admin>;

fn deref(&self) -> &Self::Target {
&self.0
}
}

impl From<Vec<Admin>> for AdminList {
fn from(admin_list: Vec<Admin>) -> Self {
AdminList(admin_list)
}
}
Loading

0 comments on commit deffa55

Please sign in to comment.