Skip to content

Commit d0cc8b5

Browse files
author
Joe Ellis
committed
Create Connection abstraction for client communication
This commit is in response to issue #199. Here, we introduce a `Connection` struct which currently contains two things: * `stream` -- an object representing the communication stream between client and service. * `metadata` -- an optional enum instance that captures metadata about the connection. This abstraction allows us to carry more information forwards toward the frontend/authenticator/... . Specifically, this abstraction was created with UNIX domain sockets in mind (but the usefulness is not limited here). UNIX domain sockets allow incoming connections to be queried for peer metadata, which is a triple (uid, gid, pid) of the peer process that is connecting. Under certain configurations, this can be used for authentication. This commit places us in a position of being able to use said metadata for authentication if needed. Signed-off-by: Joe Ellis <[email protected]>
1 parent adaf587 commit d0cc8b5

File tree

6 files changed

+57
-18
lines changed

6 files changed

+57
-18
lines changed

src/authenticators/direct_authenticator/mod.rs

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
1111
use super::ApplicationName;
1212
use super::Authenticate;
13+
use crate::front::listener::ConnectionMetadata;
1314
use log::error;
1415
use parsec_interface::requests::request::RequestAuth;
1516
use parsec_interface::requests::{ResponseStatus, Result};
@@ -20,7 +21,11 @@ use std::str;
2021
pub struct DirectAuthenticator;
2122

2223
impl Authenticate for DirectAuthenticator {
23-
fn authenticate(&self, auth: &RequestAuth) -> Result<ApplicationName> {
24+
fn authenticate(
25+
&self,
26+
auth: &RequestAuth,
27+
_: Option<ConnectionMetadata>,
28+
) -> Result<ApplicationName> {
2429
if auth.buffer.expose_secret().is_empty() {
2530
error!("The direct authenticator does not expect empty authentication values.");
2631
Err(ResponseStatus::AuthenticationError)
@@ -49,9 +54,10 @@ mod test {
4954

5055
let app_name = "app_name".to_string();
5156
let req_auth = RequestAuth::new(app_name.clone().into_bytes());
57+
let conn_metadata = None;
5258

5359
let auth_name = authenticator
54-
.authenticate(&req_auth)
60+
.authenticate(&req_auth, conn_metadata)
5561
.expect("Failed to authenticate");
5662

5763
assert_eq!(auth_name.get_name(), app_name);
@@ -60,8 +66,9 @@ mod test {
6066
#[test]
6167
fn failed_authentication() {
6268
let authenticator = DirectAuthenticator {};
69+
let conn_metadata = None;
6370
let status = authenticator
64-
.authenticate(&RequestAuth::new(vec![0xff; 5]))
71+
.authenticate(&RequestAuth::new(vec![0xff; 5]), conn_metadata)
6572
.expect_err("Authentication should have failed");
6673

6774
assert_eq!(status, ResponseStatus::AuthenticationError);
@@ -70,8 +77,9 @@ mod test {
7077
#[test]
7178
fn empty_auth() {
7279
let authenticator = DirectAuthenticator {};
80+
let conn_metadata = None;
7381
let status = authenticator
74-
.authenticate(&RequestAuth::new(Vec::new()))
82+
.authenticate(&RequestAuth::new(Vec::new()), conn_metadata)
7583
.expect_err("Empty auth should have failed");
7684

7785
assert_eq!(status, ResponseStatus::AuthenticationError);

src/authenticators/mod.rs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
1414
pub mod direct_authenticator;
1515

16+
use crate::front::listener::ConnectionMetadata;
1617
use parsec_interface::requests::request::RequestAuth;
1718
use parsec_interface::requests::Result;
1819

@@ -24,12 +25,19 @@ pub struct ApplicationName(String);
2425
///
2526
/// Interface that must be implemented for each authentication type available for the service.
2627
pub trait Authenticate {
27-
/// Authenticates a `RequestAuth` payload and returns the `ApplicationName` if successfull.
28+
/// Authenticates a `RequestAuth` payload and returns the `ApplicationName` if successful. A
29+
/// optional `ConnectionMetadata` object is passed in too, since it is sometimes possible to
30+
/// perform authentication based on the connection's metadata (i.e. as is the case for UNIX
31+
/// domain sockets with peer credentials).
2832
///
2933
/// # Errors
3034
///
3135
/// If the authentification fails, returns a `ResponseStatus::AuthenticationError`.
32-
fn authenticate(&self, auth: &RequestAuth) -> Result<ApplicationName>;
36+
fn authenticate(
37+
&self,
38+
auth: &RequestAuth,
39+
meta: Option<ConnectionMetadata>,
40+
) -> Result<ApplicationName>;
3341
}
3442

3543
impl ApplicationName {

src/bin/main.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -125,10 +125,10 @@ fn main() -> Result<()> {
125125
info!("Parsec configuration reloaded.");
126126
}
127127

128-
if let Some(stream) = listener.accept() {
128+
if let Some(connection) = listener.accept() {
129129
let front_end_handler = front_end_handler.clone();
130130
threadpool.execute(move || {
131-
front_end_handler.handle_request(stream);
131+
front_end_handler.handle_request(connection);
132132
trace!("handle_request egress");
133133
});
134134
} else {

src/front/domain_socket.rs

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@
55
//! Expose Parsec functionality using Unix domain sockets as an IPC layer.
66
//! The local socket is created at a predefined location.
77
use super::listener;
8+
use listener::Connection;
89
use listener::Listen;
9-
use listener::ReadWrite;
1010
use log::error;
1111
use std::fs;
1212
use std::fs::Permissions;
@@ -91,7 +91,7 @@ impl Listen for DomainSocketListener {
9191
self.timeout = duration;
9292
}
9393

94-
fn accept(&self) -> Option<Box<dyn ReadWrite + Send>> {
94+
fn accept(&self) -> Option<Connection> {
9595
let stream_result = self.listener.accept();
9696
match stream_result {
9797
Ok((stream, _)) => {
@@ -105,7 +105,12 @@ impl Listen for DomainSocketListener {
105105
format_error!("Failed to set stream as blocking", err);
106106
None
107107
} else {
108-
Some(Box::from(stream))
108+
Some(Connection {
109+
stream: Box::new(stream),
110+
// TODO: when possible, we want to replace this with the (uid, gid, pid)
111+
// triple for peer credentials. See listener.rs.
112+
metadata: None,
113+
})
109114
}
110115
}
111116
Err(err) => {

src/front/front_end.rs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,14 @@
66
//! pass them to the rest of the service and write the responses back.
77
use crate::authenticators::Authenticate;
88
use crate::back::dispatcher::Dispatcher;
9+
use crate::front::listener::Connection;
910
use derivative::Derivative;
1011
use log::{info, trace};
1112
use parsec_interface::requests::AuthType;
1213
use parsec_interface::requests::ResponseStatus;
1314
use parsec_interface::requests::{Request, Response};
1415
use std::collections::HashMap;
1516
use std::io::{Error, ErrorKind, Result};
16-
use std::io::{Read, Write};
1717

1818
/// Read and verify request from IPC stream
1919
///
@@ -40,17 +40,17 @@ impl FrontEndHandler {
4040
///
4141
/// If an error occurs during (un)marshalling, no operation will be performed and the
4242
/// method will return.
43-
pub fn handle_request<T: Read + Write>(&self, mut stream: T) {
43+
pub fn handle_request(&self, mut connection: Connection) {
4444
trace!("handle_request ingress");
4545
// Read bytes from stream
4646
// De-Serialise bytes into a request
47-
let request = match Request::read_from_stream(&mut stream, self.body_len_limit) {
47+
let request = match Request::read_from_stream(&mut connection.stream, self.body_len_limit) {
4848
Ok(request) => request,
4949
Err(status) => {
5050
format_error!("Failed to read request", status);
5151

5252
let response = Response::from_status(status);
53-
if let Err(status) = response.write_to_stream(&mut stream) {
53+
if let Err(status) = response.write_to_stream(&mut connection.stream) {
5454
format_error!("Failed to write response", status);
5555
}
5656
return;
@@ -63,7 +63,7 @@ impl FrontEndHandler {
6363
// Otherwise find an authenticator that is capable to authenticate the request
6464
} else if let Some(authenticator) = self.authenticators.get(&request.header.auth_type) {
6565
// Authenticate the request
66-
match authenticator.authenticate(&request.auth) {
66+
match authenticator.authenticate(&request.auth, connection.metadata) {
6767
// Send the request to the dispatcher
6868
// Get a response back
6969
Ok(app_name) => (Some(app_name), None),
@@ -102,7 +102,7 @@ impl FrontEndHandler {
102102

103103
// Serialise the response into bytes
104104
// Write bytes to stream
105-
match response.write_to_stream(&mut stream) {
105+
match response.write_to_stream(&mut connection.stream) {
106106
Ok(_) => {
107107
if crate::utils::GlobalConfig::log_error_details() {
108108
if let Some(app_name_string) = app_name {

src/front/listener.rs

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
//! The [`Listen`](https://parallaxsecond.github.io/parsec-book/parsec_service/listeners.html)
66
//! trait acts as an interface for the operations that must be supported by any implementation
77
//! of the IPC mechanism used as a Parsec front.
8+
use derivative::Derivative;
89
use serde::Deserialize;
910
use std::time::Duration;
1011

@@ -25,6 +26,23 @@ pub struct ListenerConfig {
2526
pub timeout: u64,
2627
}
2728

29+
/// Specifies metadata associated with a connection, if any.
30+
#[derive(Copy, Clone, Debug)]
31+
pub enum ConnectionMetadata {
32+
// TODO: nothing here right now. Metadata types will be added as needed.
33+
}
34+
35+
/// Represents a connection to a single client. Contains a stream, used for communication with the
36+
/// client, and some metadata associated with the connection that might be useful elsewhere (i.e.
37+
/// authentication, etc).
38+
#[derive(Derivative)]
39+
#[derivative(Debug)]
40+
pub struct Connection {
41+
#[derivative(Debug = "ignore")]
42+
pub stream: Box<dyn ReadWrite + Send>,
43+
pub metadata: Option<ConnectionMetadata>,
44+
}
45+
2846
/// IPC front manager interface
2947
///
3048
/// Interface defining the functionality that any IPC front manager has to expose to Parsec for normal
@@ -45,5 +63,5 @@ pub trait Listen {
4563
/// # Panics
4664
///
4765
/// If the listener has not been initialised before, with the `init` method.
48-
fn accept(&self) -> Option<Box<dyn ReadWrite + Send>>;
66+
fn accept(&self) -> Option<Connection>;
4967
}

0 commit comments

Comments
 (0)