Skip to content

Nightly features CI #244

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

Closed
Closed
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
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -70,3 +70,4 @@ pkcs11-provider = ["pkcs11", "picky-asn1-der", "picky-asn1", "picky-asn1-x509",
tpm-provider = ["tss-esapi", "picky-asn1-der", "picky-asn1", "picky-asn1-x509"]
all-providers = ["tpm-provider", "pkcs11-provider", "mbed-crypto-provider"]
docs = ["pkcs11-provider", "tpm-provider", "tss-esapi/docs", "mbed-crypto-provider"]
unix-peer-credentials-authenticator = []
8 changes: 8 additions & 0 deletions ci.sh
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,14 @@ fi
echo "Unit, doc and integration tests"
RUST_BACKTRACE=1 cargo test $FEATURES

# Run tests for Parsec features that require a nightly version of the compiler
# to function. For example: Unix peer credentials authenticator is currently
# only functional if we use the nightly compiler, since it depends on the
# `peer_cred` function.
# TODO: change this list as features trickle into the stable branch.
echo "Unit testing peer credentials authenticator"
RUST_BACKTRACE=1 cargo +nightly test --features=peer-credentials-authenticator

# Removing any mappings left over from integration tests
rm -rf mappings/

Expand Down
4 changes: 4 additions & 0 deletions e2e_tests/provider_cfg/all/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -46,4 +46,8 @@ RUN softhsm2-util --init-token --slot 0 --label "Parsec Tests" --pin 123456 --so

# Install Rust toolchain
RUN curl https://sh.rustup.rs -sSf | bash -s -- -y

# Install Rust nightly.
RUN rustup install nightly

ENV PATH="/root/.cargo/bin:${PATH}"
4 changes: 4 additions & 0 deletions e2e_tests/provider_cfg/mbed-crypto/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,8 @@ RUN cd mbed-crypto-mbedcrypto-2.0.0 \

# Install Rust toolchain
RUN curl https://sh.rustup.rs -sSf | bash -s -- -y

# Install Rust nightly.
RUN rustup install nightly

ENV PATH="/root/.cargo/bin:${PATH}"
4 changes: 4 additions & 0 deletions e2e_tests/provider_cfg/pkcs11/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ RUN cd SoftHSMv2-2.5.0 \

# Install Rust toolchain
RUN curl https://sh.rustup.rs -sSf | bash -s -- -y

# Install Rust nightly.
RUN rustup install nightly

ENV PATH="/root/.cargo/bin:${PATH}"

# Create a new token in a new slot. The slot number assigned will be random
Expand Down
4 changes: 4 additions & 0 deletions e2e_tests/provider_cfg/tpm/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,8 @@ RUN cd tpm2-tools \

# Install Rust toolchain
RUN curl https://sh.rustup.rs -sSf | bash -s -- -y

# Install Rust nightly.
RUN rustup install nightly

ENV PATH="/root/.cargo/bin:${PATH}"
7 changes: 4 additions & 3 deletions src/authenticators/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,12 @@
//! used throughout the service for identifying the request initiator. The input to an authentication
//! is the `RequestAuth` field of a request, which is parsed by the authenticator specified in the header.
//! The authentication functionality is abstracted through an `Authenticate` trait.
//!
//! Currently only a simple Direct Authenticator component is implemented.

pub mod direct_authenticator;

#[cfg(feature = "unix-peer-credentials-authenticator")]
pub mod unix_peer_credentials_authenticator;

use crate::front::listener::ConnectionMetadata;
use parsec_interface::operations::list_authenticators;
use parsec_interface::requests::request::RequestAuth;
Expand All @@ -35,7 +36,7 @@ pub trait Authenticate {
/// Authenticates a `RequestAuth` payload and returns the `ApplicationName` if successful. A
/// optional `ConnectionMetadata` object is passed in too, since it is sometimes possible to
/// perform authentication based on the connection's metadata (i.e. as is the case for UNIX
/// domain sockets with peer credentials).
/// domain sockets with Unix peer credentials).
///
/// # Errors
///
Expand Down
190 changes: 190 additions & 0 deletions src/authenticators/unix_peer_credentials_authenticator/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
// Copyright 2020 Contributors to the Parsec project.
// SPDX-License-Identifier: Apache-2.0
//! Unix peer credentials authenticator
//!
//! The `UnixPeerCredentialsAuthenticator` uses Unix peer credentials to perform authentication. As
//! such, it uses the effective Unix user ID (UID) to authenticate the connecting process. Unix
//! peer credentials also allow us to access the effective Unix group ID (GID) of the connecting
//! process, although this information is currently unused.
//!
//! Currently, the stringified UID is used as the application name.

use super::ApplicationName;
use super::Authenticate;
use crate::front::listener::ConnectionMetadata;
use log::error;
use parsec_interface::operations::list_authenticators;
use parsec_interface::requests::request::RequestAuth;
use parsec_interface::requests::AuthType;
use parsec_interface::requests::{ResponseStatus, Result};
use parsec_interface::secrecy::ExposeSecret;
use std::convert::TryInto;

#[derive(Copy, Clone, Debug)]
pub struct UnixPeerCredentialsAuthenticator;

impl Authenticate for UnixPeerCredentialsAuthenticator {
fn describe(&self) -> Result<list_authenticators::AuthenticatorInfo> {
Ok(list_authenticators::AuthenticatorInfo {
description: String::from(
"Uses Unix peer credentials to authenticate the client. Verifies that the self-declared \
Unix user identifier (UID) in the request's authentication header matches that which is \
found from the peer credentials."
),
version_maj: 0,
version_min: 1,
version_rev: 0,
id: AuthType::PeerCredentials,
})
}

fn authenticate(
&self,
auth: &RequestAuth,
meta: Option<ConnectionMetadata>,
) -> Result<ApplicationName> {
// Parse authentication request.
let expected_uid_bytes = auth.buffer.expose_secret();
if expected_uid_bytes.is_empty() {
error!("Expected UID in authentication request, but it is empty.");
return Err(ResponseStatus::AuthenticationError);
}

const EXPECTED_UID_SIZE_BYTES: usize = 4;
if expected_uid_bytes.len() != EXPECTED_UID_SIZE_BYTES {
error!(
"UID in authentication request is not the right size (expected: {}, got: {}).",
EXPECTED_UID_SIZE_BYTES,
expected_uid_bytes.len()
);
return Err(ResponseStatus::AuthenticationError);
}

let expected_uid = auth.buffer.expose_secret();
let expected_uid: [u8; 4] = expected_uid_bytes.as_slice().try_into().map_err(|e| {
error!(
"UID in authentication request is not the right size (expected: {}, got: {}).",
EXPECTED_UID_SIZE_BYTES,
expected_uid_bytes.len()
);
ResponseStatus::AuthenticationError
})?;
let expected_uid = u32::from_le_bytes(expected_uid);

let meta = meta.ok_or_else(|| {
error!("Authenticator did not receive any metadata; cannot perform authentication.");
ResponseStatus::AuthenticationError
})?;

let (uid, _gid) = match meta {
ConnectionMetadata::UnixPeerCredentials { uid, gid } => (uid, gid),
// TODO: add wildcard pattern when `ConnectionMetadata` has more possibilities.
};

// 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()))
} else {
error!("Declared UID in authentication request does not match the process's UID.");
Err(ResponseStatus::AuthenticationError)
}
}
}

#[cfg(test)]
mod test {
use super::super::Authenticate;
use super::UnixPeerCredentialsAuthenticator;
use crate::front::listener::ConnectionMetadata;
use parsec_interface::requests::request::RequestAuth;
use parsec_interface::requests::ResponseStatus;
use rand::Rng;
use std::os::unix::net::UnixStream;
use users::get_current_uid;

#[test]
fn successful_authentication() {
// This test should PASS; we are verifying that our username gets set as the application
// secret when using Unix peer credentials authentication with Unix domain sockets.

// Create two connected sockets.
let (sock_a, _sock_b) = UnixStream::pair().unwrap();
let (cred_a, _cred_b) = (sock_a.peer_cred().unwrap(), _sock_b.peer_cred().unwrap());

let authenticator = UnixPeerCredentialsAuthenticator {};

let req_auth_data = cred_a.uid.to_string().as_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,
});

let auth_name = authenticator
.authenticate(&req_auth, conn_metadata)
.expect("Failed to authenticate");

assert_eq!(auth_name.get_name(), get_current_uid().to_string());
}

#[test]
fn unsuccessful_authentication_wrong_declared_uid() {
// This test should FAIL; we are trying to authenticate, but we are declaring the wrong
// UID.

// Create two connected sockets.
let (sock_a, _sock_b) = UnixStream::pair().unwrap();
let (cred_a, _cred_b) = (sock_a.peer_cred().unwrap(), _sock_b.peer_cred().unwrap());

let authenticator = UnixPeerCredentialsAuthenticator {};

let wrong_uid = cred_a.uid + 1;
let wrong_req_auth_data = wrong_uid.to_string().as_bytes().to_vec();
let req_auth = RequestAuth::new(wrong_req_auth_data);
let conn_metadata = Some(ConnectionMetadata::UnixPeerCredentials {
uid: cred_a.uid,
gid: cred_a.gid,
});

let auth_result = authenticator.authenticate(&req_auth, conn_metadata);
assert_eq!(auth_result, Err(ResponseStatus::AuthenticationError));
}

#[test]
fn unsuccessful_authentication_garbage_data() {
// This test should FAIL; we are sending garbage (random) data in the request.

// Create two connected sockets.
let (sock_a, _sock_b) = UnixStream::pair().unwrap();
let (cred_a, _cred_b) = (sock_a.peer_cred().unwrap(), _sock_b.peer_cred().unwrap());

let authenticator = UnixPeerCredentialsAuthenticator {};

let garbage_data = rand::thread_rng().gen::<[u8; 32]>().to_vec();
let req_auth = RequestAuth::new(garbage_data);
let conn_metadata = Some(ConnectionMetadata::UnixPeerCredentials {
uid: cred_a.uid,
gid: cred_a.gid,
});

let auth_result = authenticator.authenticate(&req_auth, conn_metadata);
assert_eq!(auth_result, Err(ResponseStatus::AuthenticationError));
}

#[test]
fn unsuccessful_authentication_no_metadata() {
let authenticator = UnixPeerCredentialsAuthenticator {};
let req_auth = RequestAuth::new("secret".into());

let conn_metadata = None;
let auth_result = authenticator.authenticate(&req_auth, conn_metadata);
assert_eq!(auth_result, Err(ResponseStatus::AuthenticationError));
}

#[test]
fn unsuccessful_authentication_wrong_metadata() {
// TODO: this test needs implementing when we have more than one metadata type. At the
// moment, the compiler just complains with an 'unreachable branch' message.
}
}
31 changes: 27 additions & 4 deletions src/front/domain_socket.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
//! Expose Parsec functionality using Unix domain sockets as an IPC layer.
//! The local socket is created at a predefined location.
use super::listener;
use listener::Connection;
use listener::Listen;
use listener::{Connection, ConnectionMetadata, GetMetadata};
use log::error;
#[cfg(not(feature = "no-parsec-user-and-clients-group"))]
use std::ffi::CString;
Expand All @@ -16,6 +16,7 @@ use std::io::{Error, ErrorKind, Result};
use std::os::unix::fs::PermissionsExt;
use std::os::unix::io::FromRawFd;
use std::os::unix::net::UnixListener;
use std::os::unix::net::UnixStream;
use std::path::Path;
use std::time::Duration;

Expand Down Expand Up @@ -202,11 +203,10 @@ impl Listen for DomainSocketListener {
format_error!("Failed to set stream as blocking", err);
None
} else {
let metadata = stream.metadata();
Some(Connection {
stream: Box::new(stream),
// TODO: when possible, we want to replace this with the (uid, gid, pid)
// triple for peer credentials. See listener.rs.
metadata: None,
metadata,
})
}
}
Expand Down Expand Up @@ -248,3 +248,26 @@ impl DomainSocketListenerBuilder {
})?)
}
}

impl GetMetadata for UnixStream {
#[cfg(feature = "unix-peer-credentials-authenticator")]
fn metadata(&self) -> Option<ConnectionMetadata> {
let ucred = self.peer_cred().or_else(|err| {
format_error!(
"Failed to grab peer credentials metadata from UnixStream",
err
);
None
})?;
Some(ConnectionMetadata::UnixPeerCredentials {
uid: ucred.uid,
gid: ucred.gid,
})
}

// If Unix peer credentials authenticator feature is not in use, return None for the metadata.
#[cfg(not(feature = "unix-peer-credentials-authenticator"))]
fn metadata(&self) -> Option<ConnectionMetadata> {
None
}
}
14 changes: 13 additions & 1 deletion src/front/listener.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,13 @@ pub struct ListenerConfig {
/// Specifies metadata associated with a connection, if any.
#[derive(Copy, Clone, Debug)]
pub enum ConnectionMetadata {
// TODO: nothing here right now. Metadata types will be added as needed.
/// Unix peer credentials metadata for Unix domain sockets.
UnixPeerCredentials {
/// The effective UID of the connecting process.
uid: u32,
/// The effective GID of the connecting process.
gid: u32,
},
}

/// Represents a connection to a single client
Expand Down Expand Up @@ -70,3 +76,9 @@ pub trait Listen {
/// If the listener has not been initialised before, with the `init` method.
fn accept(&self) -> Option<Connection>;
}

/// Get metadata for a particular object.
pub trait GetMetadata {
/// Get the metadata associated with this object.
fn metadata(&self) -> Option<ConnectionMetadata>;
}
6 changes: 6 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,12 @@
)]
// This one is hard to avoid.
#![allow(clippy::multiple_crate_versions)]
// TODO: remove this if/when the Unix peer credentials PR gets merged. Link
// here for reference: https://github.com/rust-lang/rust/pull/75148
#![cfg_attr(
feature = "unix-peer-credentials-authenticator",
feature(peer_credentials_unix_socket)
)]

#[allow(unused)]
macro_rules! format_error {
Expand Down