diff --git a/.travis.yml b/.travis.yml index 4f5fdb6..48f93b7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,4 +1,5 @@ # Executing our tests on Arm64 with Travis CI +dist: focal arch: arm64 language: rust script: diff --git a/Cargo.toml b/Cargo.toml index 0bd916b..9fc9d76 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,9 +19,12 @@ log = "0.4.11" derivative = "2.1.1" zeroize = "1.1.0" users = "0.10.0" +spiffe = { git = "https://github.com/hug-dev/rust-spiffe", branch = "refactor-jwt", optional = true } [dev-dependencies] mockstream = "0.0.3" [features] +default = ["spiffe-auth"] +spiffe-auth = ["spiffe"] testing = ["parsec-interface/testing"] diff --git a/src/auth.rs b/src/auth.rs index 167fab8..a40085c 100644 --- a/src/auth.rs +++ b/src/auth.rs @@ -1,7 +1,9 @@ // Copyright 2020 Contributors to the Parsec project. // SPDX-License-Identifier: Apache-2.0 //! Client app authentication data +use crate::error::{Error, Result}; use parsec_interface::requests::{request::RequestAuth, AuthType}; +use std::convert::TryFrom; /// Authentication data used in Parsec requests #[derive(Clone, Debug)] @@ -19,6 +21,11 @@ pub enum Authentication { /// Used for authentication via Peer Credentials provided by Unix /// operating systems for Domain Socket connections. UnixPeerCredentials, + /// Authentication using JWT SVID tokens. The will fetch its JWT-SVID and pass it in the + /// Authentication field. The socket endpoint is found through the SPIFFE_ENDPOINT_SOCKET + /// environment variable. + #[cfg(feature = "spiffe-auth")] + JwtSvid, } impl Authentication { @@ -28,18 +35,48 @@ impl Authentication { Authentication::None => AuthType::NoAuth, Authentication::Direct(_) => AuthType::Direct, Authentication::UnixPeerCredentials => AuthType::UnixPeerCredentials, + #[cfg(feature = "spiffe-auth")] + Authentication::JwtSvid => AuthType::JwtSvid, } } } -impl From<&Authentication> for RequestAuth { - fn from(data: &Authentication) -> Self { +impl TryFrom<&Authentication> for RequestAuth { + type Error = Error; + + fn try_from(data: &Authentication) -> Result { match data { - Authentication::None => RequestAuth::new(Vec::new()), - Authentication::Direct(name) => RequestAuth::new(name.bytes().collect()), + Authentication::None => Ok(RequestAuth::new(Vec::new())), + Authentication::Direct(name) => Ok(RequestAuth::new(name.bytes().collect())), Authentication::UnixPeerCredentials => { let current_uid = users::get_current_uid(); - RequestAuth::new(current_uid.to_le_bytes().to_vec()) + Ok(RequestAuth::new(current_uid.to_le_bytes().to_vec())) + } + #[cfg(feature = "spiffe-auth")] + Authentication::JwtSvid => { + use crate::error::ClientErrorKind; + use log::error; + use spiffe::workload::jwt::JWTClient; + use std::env; + + let client = JWTClient::new( + &env::var("SPIFFE_ENDPOINT_SOCKET").map_err(|e| { + error!( + "Cannot read the SPIFFE_ENDPOINT_SOCKET environment variable ({}).", + e + ); + Error::Client(ClientErrorKind::NoAuthenticator) + })?, + None, + None, + ); + let audience = String::from("parsec"); + + let result = client.fetch(audience).map_err(|e| { + error!("Error while fetching the JWT-SVID ({}).", e); + Error::Client(ClientErrorKind::Spiffe(e)) + })?; + Ok(RequestAuth::new(result.svid().as_bytes().into())) } } } @@ -53,6 +90,8 @@ impl PartialEq for Authentication { (Authentication::Direct(app_name), Authentication::Direct(other_app_name)) => { app_name == other_app_name } + #[cfg(feature = "spiffe-auth")] + (Authentication::JwtSvid, Authentication::JwtSvid) => true, _ => false, } } diff --git a/src/core/basic_client.rs b/src/core/basic_client.rs index 9091a95..f5f94e1 100644 --- a/src/core/basic_client.rs +++ b/src/core/basic_client.rs @@ -258,6 +258,8 @@ impl BasicClient { AuthType::UnixPeerCredentials => { self.auth_data = Authentication::UnixPeerCredentials } + #[cfg(feature = "spiffe-auth")] + AuthType::JwtSvid => self.auth_data = Authentication::JwtSvid, auth => { warn!( "Authenticator of type \"{:?}\" not supported by this client library", diff --git a/src/core/operation_client.rs b/src/core/operation_client.rs index 2e453ef..48d2e5a 100644 --- a/src/core/operation_client.rs +++ b/src/core/operation_client.rs @@ -12,6 +12,7 @@ use parsec_interface::operations_protobuf::ProtobufConverter; use parsec_interface::requests::{ request::RequestHeader, Opcode, ProviderID, Request, Response, ResponseStatus, }; +use std::convert::TryInto; /// Low-level client optimised for communicating with the Parsec service at an operation level. /// @@ -66,7 +67,7 @@ impl OperationClient { Ok(Request { header, body, - auth: auth.into(), + auth: auth.try_into()?, }) } diff --git a/src/core/testing/core_tests.rs b/src/core/testing/core_tests.rs index 0537872..825317c 100644 --- a/src/core/testing/core_tests.rs +++ b/src/core/testing/core_tests.rs @@ -179,7 +179,10 @@ fn core_provider_for_crypto_test() { .psa_destroy_key(String::from("random key")) .expect_err("Expected a failure!!"); - assert_eq!(res, Error::Client(ClientErrorKind::InvalidProvider)); + assert!(matches!( + res, + Error::Client(ClientErrorKind::InvalidProvider) + )); } #[test] @@ -694,10 +697,10 @@ fn different_response_type_test() { .psa_destroy_key(key_name) .expect_err("Error was expected"); - assert_eq!( + assert!(matches!( err, Error::Client(ClientErrorKind::InvalidServiceResponseType) - ); + )); } #[test] @@ -711,7 +714,10 @@ fn response_status_test() { client.set_mock_read(&stream.pop_bytes_written()); let err = client.ping().expect_err("Error was expected"); - assert_eq!(err, Error::Service(status)); + assert!(matches!( + err, + Error::Service(ResponseStatus::PsaErrorDataCorrupt) + )); } #[test] @@ -720,10 +726,10 @@ fn malformed_response_test() { client.set_mock_read(&[0xcb_u8; 130]); let err = client.ping().expect_err("Error was expected"); - assert_eq!( + assert!(matches!( err, Error::Client(ClientErrorKind::Interface(ResponseStatus::InvalidHeader)) - ); + )); } #[test] @@ -788,10 +794,10 @@ fn failing_ipc_test() { )))); let err = client.ping().expect_err("Expected to fail"); - assert_eq!( + assert!(matches!( err, Error::Client(ClientErrorKind::Interface(ResponseStatus::ConnectionError)) - ); + )); } #[test] @@ -866,10 +872,10 @@ fn set_default_auth_direct() { }), )); - assert_eq!( + assert!(matches!( client.set_default_auth(None).unwrap_err(), Error::Client(ClientErrorKind::MissingParam) - ); + )); client.set_mock_read(&get_response_bytes_from_result( NativeResult::ListAuthenticators(operations::list_authenticators::Result { diff --git a/src/error.rs b/src/error.rs index 879c543..474d15e 100644 --- a/src/error.rs +++ b/src/error.rs @@ -6,7 +6,7 @@ use std::error; use std::fmt; /// Enum used to denote errors returned to the library user -#[derive(Debug, PartialEq)] +#[derive(Debug)] pub enum Error { /// Errors originating in the service Service(ResponseStatus), @@ -42,6 +42,9 @@ pub enum ClientErrorKind { NoAuthenticator, /// Required parameter was not provided MissingParam, + /// Error while using the SPIFFE Workload API + #[cfg(feature = "spiffe-auth")] + Spiffe(spiffe::workload::Error), } impl From for Error { @@ -50,34 +53,6 @@ impl From for Error { } } -impl PartialEq for ClientErrorKind { - fn eq(&self, other: &Self) -> bool { - match self { - ClientErrorKind::Interface(status) => { - if let ClientErrorKind::Interface(other_status) = other { - other_status == status - } else { - false - } - } - ClientErrorKind::Ipc(error) => { - if let ClientErrorKind::Ipc(other_error) = other { - other_error.kind() == error.kind() - } else { - false - } - } - ClientErrorKind::InvalidServiceResponseType => { - matches!(other, ClientErrorKind::InvalidServiceResponseType) - } - ClientErrorKind::InvalidProvider => matches!(other, ClientErrorKind::InvalidProvider), - ClientErrorKind::NoProvider => matches!(other, ClientErrorKind::NoProvider), - ClientErrorKind::NoAuthenticator => matches!(other, ClientErrorKind::NoAuthenticator), - ClientErrorKind::MissingParam => matches!(other, ClientErrorKind::MissingParam), - } - } -} - impl fmt::Display for ClientErrorKind { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { @@ -93,6 +68,8 @@ impl fmt::Display for ClientErrorKind { ClientErrorKind::NoProvider => write!(f, "client is missing an implicit provider"), ClientErrorKind::NoAuthenticator => write!(f, "service is not reporting any authenticators or none of the reported ones are supported by the client"), ClientErrorKind::MissingParam => write!(f, "one of the `Option` parameters was required but was not provided"), + #[cfg(feature = "spiffe-auth")] + ClientErrorKind::Spiffe(error) => error.fmt(f), } } } diff --git a/tests/ci.sh b/tests/ci.sh index a40324e..746db3e 100755 --- a/tests/ci.sh +++ b/tests/ci.sh @@ -13,6 +13,7 @@ set -euf -o pipefail ################ RUST_BACKTRACE=1 cargo build RUST_BACKTRACE=1 cargo build --features testing +RUST_BACKTRACE=1 cargo build --no-default-features ################# # Static checks #