Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add admin configuration #316

Merged
merged 1 commit into from
Jan 13, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👌

#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() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice tests 👌

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 {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wasn't sure if I should just change this to Application

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think for this PR this is fine keeping the name to not trigger too many changes

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