Skip to content

Add a JWT-SVID authentication method #61

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

Merged
merged 3 commits into from
Nov 26, 2020
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
1 change: 1 addition & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# Executing our tests on Arm64 with Travis CI
dist: focal
arch: arm64
language: rust
script:
Expand Down
3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
49 changes: 44 additions & 5 deletions src/auth.rs
Original file line number Diff line number Diff line change
@@ -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)]
Expand All @@ -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 {
Expand All @@ -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<Self> {
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()))
}
}
}
Expand All @@ -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,
}
}
Expand Down
2 changes: 2 additions & 0 deletions src/core/basic_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
3 changes: 2 additions & 1 deletion src/core/operation_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
///
Expand Down Expand Up @@ -66,7 +67,7 @@ impl OperationClient {
Ok(Request {
header,
body,
auth: auth.into(),
auth: auth.try_into()?,
})
}

Expand Down
26 changes: 16 additions & 10 deletions src/core/testing/core_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down Expand Up @@ -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]
Expand All @@ -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]
Expand All @@ -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]
Expand Down Expand Up @@ -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]
Expand Down Expand Up @@ -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 {
Expand Down
35 changes: 6 additions & 29 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down Expand Up @@ -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<ClientErrorKind> for Error {
Expand All @@ -50,34 +53,6 @@ impl From<ClientErrorKind> 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 {
Expand All @@ -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),
}
}
}
Expand Down
1 change: 1 addition & 0 deletions tests/ci.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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 #
Expand Down