Skip to content

Commit a62e8cb

Browse files
authored
Merge pull request #283 from hug-dev/spiffe
Add a JWT-SVID authenticator
2 parents e0d7f40 + 7b56d7b commit a62e8cb

File tree

13 files changed

+1014
-84
lines changed

13 files changed

+1014
-84
lines changed

Cargo.lock

Lines changed: 858 additions & 68 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ name = "parsec"
1818
path = "src/bin/main.rs"
1919

2020
[dependencies]
21-
# Set to the newest interface version before releasing
2221
parsec-interface = "0.21.0"
2322
rand = { version = "0.7.3", features = ["small_rng"], optional = true }
2423
base64 = "0.12.3"
@@ -45,6 +44,8 @@ picky-asn1-x509 = { version = "0.3.2", optional = true }
4544
users = "0.10.0"
4645
libc = "0.2.77"
4746
anyhow = "1.0.32"
47+
# Using a fork until the JWT support is merged into the main rust-spiffe repository
48+
spiffe = { git = "https://github.com/hug-dev/rust-spiffe", branch = "refactor-jwt" }
4849

4950
[dev-dependencies]
5051
rand = { version = "0.7.3", features = ["small_rng"] }

ci.sh

Lines changed: 39 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,24 @@ if [ "$PROVIDER_NAME" = "pkcs11" ] || [ "$PROVIDER_NAME" = "all" ]; then
107107
popd
108108
fi
109109

110+
if [ "$PROVIDER_NAME" = "all" ]; then
111+
# Start SPIRE server and agent
112+
pushd /tmp/spire-0.11.1
113+
./bin/spire-server run -config conf/server/server.conf &
114+
sleep 2
115+
TOKEN=`bin/spire-server token generate -spiffeID spiffe://example.org/myagent | cut -d ' ' -f 2`
116+
./bin/spire-agent run -config conf/agent/agent.conf -joinToken $TOKEN &
117+
sleep 2
118+
# Register parsec-client-1
119+
./bin/spire-server entry create -parentID spiffe://example.org/myagent \
120+
-spiffeID spiffe://example.org/parsec-client-1 -selector unix:uid:$(id -u parsec-client-1)
121+
# Register parsec-client-2
122+
./bin/spire-server entry create -parentID spiffe://example.org/myagent \
123+
-spiffeID spiffe://example.org/parsec-client-2 -selector unix:uid:$(id -u parsec-client-2)
124+
sleep 5
125+
popd
126+
fi
127+
110128
echo "Build test"
111129
RUST_BACKTRACE=1 cargo build $FEATURES
112130

@@ -139,16 +157,31 @@ if [ "$PROVIDER_NAME" = "all" ]; then
139157
RUST_BACKTRACE=1 cargo test $TEST_FEATURES --manifest-path ./e2e_tests/Cargo.toml all_providers::normal
140158

141159
echo "Execute all-providers multi-tenancy tests"
142-
su -c "RUST_BACKTRACE=1 cargo test $TEST_FEATURES --manifest-path ./e2e_tests/Cargo.toml --target-dir /home/parsec-client-1 all_providers::multitenancy::client1_before" parsec-client-1
143-
su -c "RUST_BACKTRACE=1 cargo test $TEST_FEATURES --manifest-path ./e2e_tests/Cargo.toml --target-dir /home/parsec-client-2 all_providers::multitenancy::client2" parsec-client-2
144-
su -c "RUST_BACKTRACE=1 cargo test $TEST_FEATURES --manifest-path ./e2e_tests/Cargo.toml --target-dir /home/parsec-client-1 all_providers::multitenancy::client1_after" parsec-client-1
160+
# Needed because parsec-client-1 and 2 write to those locations owned by root
161+
chmod 777 /tmp/parsec/e2e_tests
162+
chmod 777 /tmp/
163+
export SPIFFE_ENDPOINT_SOCKET="unix:///tmp/agent.sock"
164+
165+
# PATH is defined before each command for user to use their own version of the Rust toolchain
166+
su -c "PATH=\"/home/parsec-client-1/.cargo/bin:${PATH}\";RUST_BACKTRACE=1 cargo test $TEST_FEATURES --manifest-path ./e2e_tests/Cargo.toml --target-dir /home/parsec-client-1 all_providers::multitenancy::client1_before" parsec-client-1
167+
su -c "PATH=\"/home/parsec-client-2/.cargo/bin:${PATH}\";RUST_BACKTRACE=1 cargo test $TEST_FEATURES --manifest-path ./e2e_tests/Cargo.toml --target-dir /home/parsec-client-2 all_providers::multitenancy::client2" parsec-client-2
168+
su -c "PATH=\"/home/parsec-client-1/.cargo/bin:${PATH}\";RUST_BACKTRACE=1 cargo test $TEST_FEATURES --manifest-path ./e2e_tests/Cargo.toml --target-dir /home/parsec-client-1 all_providers::multitenancy::client1_after" parsec-client-1
145169
# Change the authentication method
146170
sed -i 's/^\(auth_type\s*=\s*\).*$/\1\"UnixPeerCredentials\"/' $CONFIG_PATH
147171
pkill -SIGHUP parsec
148172
sleep 5
149-
su -c "RUST_BACKTRACE=1 cargo test $TEST_FEATURES --manifest-path ./e2e_tests/Cargo.toml --target-dir /home/parsec-client-1 all_providers::multitenancy::client1_before" parsec-client-1
150-
su -c "RUST_BACKTRACE=1 cargo test $TEST_FEATURES --manifest-path ./e2e_tests/Cargo.toml --target-dir /home/parsec-client-2 all_providers::multitenancy::client2" parsec-client-2
151-
su -c "RUST_BACKTRACE=1 cargo test $TEST_FEATURES --manifest-path ./e2e_tests/Cargo.toml --target-dir /home/parsec-client-1 all_providers::multitenancy::client1_after" parsec-client-1
173+
su -c "PATH=\"/home/parsec-client-1/.cargo/bin:${PATH}\";RUST_BACKTRACE=1 cargo test $TEST_FEATURES --manifest-path ./e2e_tests/Cargo.toml --target-dir /home/parsec-client-1 all_providers::multitenancy::client1_before" parsec-client-1
174+
su -c "PATH=\"/home/parsec-client-2/.cargo/bin:${PATH}\";RUST_BACKTRACE=1 cargo test $TEST_FEATURES --manifest-path ./e2e_tests/Cargo.toml --target-dir /home/parsec-client-2 all_providers::multitenancy::client2" parsec-client-2
175+
su -c "PATH=\"/home/parsec-client-1/.cargo/bin:${PATH}\";RUST_BACKTRACE=1 cargo test $TEST_FEATURES --manifest-path ./e2e_tests/Cargo.toml --target-dir /home/parsec-client-1 all_providers::multitenancy::client1_after" parsec-client-1
176+
177+
# Change the authentication method
178+
sed -i 's/^\(auth_type\s*=\s*\).*$/\1\"JwtSvid\"/' $CONFIG_PATH
179+
sed -i 's@#workload_endpoint@workload_endpoint@' $CONFIG_PATH
180+
pkill -SIGHUP parsec
181+
sleep 5
182+
su -c "PATH=\"/home/parsec-client-1/.cargo/bin:${PATH}\";RUST_BACKTRACE=1 cargo test $TEST_FEATURES --manifest-path ./e2e_tests/Cargo.toml --target-dir /home/parsec-client-1 all_providers::multitenancy::client1_before" parsec-client-1
183+
su -c "PATH=\"/home/parsec-client-2/.cargo/bin:${PATH}\";RUST_BACKTRACE=1 cargo test $TEST_FEATURES --manifest-path ./e2e_tests/Cargo.toml --target-dir /home/parsec-client-2 all_providers::multitenancy::client2" parsec-client-2
184+
su -c "PATH=\"/home/parsec-client-1/.cargo/bin:${PATH}\";RUST_BACKTRACE=1 cargo test $TEST_FEATURES --manifest-path ./e2e_tests/Cargo.toml --target-dir /home/parsec-client-1 all_providers::multitenancy::client1_after" parsec-client-1
152185

153186
# Last test as it changes the service configuration
154187
echo "Execute all-providers config tests"

config.toml

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,12 +58,18 @@ timeout = 200 # in milliseconds
5858
[authenticator]
5959
# (Required) Type of authenticator that will be used to authenticate clients' authentication
6060
# payloads.
61-
# Possible values: "Direct" and "UnixPeerCredentials".
61+
# Possible values: "Direct", "UnixPeerCredentials" and "JwtSvid".
6262
# WARNING: The "Direct" authenticator is only secure under specific requirements. Please make sure
6363
# to read the Recommendations on a Secure Parsec Deployment at
6464
# https://parallaxsecond.github.io/parsec-book/parsec_security/secure_deployment.html
6565
auth_type = "UnixPeerCredentials"
6666

67+
# (Required only for JwtSvid) Location of the Workload API endpoint
68+
# WARNING: only use this authenticator if the Workload API socket is TRUSTED. A malicious entity
69+
# owning that socket would have access to all the keys owned by clients using this authentication
70+
# method. This path *must* be trusted for as long as Parsec is running.
71+
#workload_endpoint="unix:///tmp/agent.sock"
72+
6773
# (Required) Configuration for the components managing key info for providers.
6874
# Defined as an array of tables: https://github.com/toml-lang/toml#user-content-array-of-tables
6975
[[key_manager]]

e2e_tests/Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,13 @@ publish = false
1414

1515
[dependencies]
1616
serde = { version = "1.0.115", features = ["derive"] }
17-
parsec-client = { version = "0.11.0", features = ["testing"] }
17+
parsec-client = { git = "https://github.com/parallaxsecond/parsec-client-rust.git", features = ["testing", "spiffe-auth"] }
1818
log = "0.4.11"
1919
rand = "0.7.3"
20+
env_logger = "0.7.1"
2021

2122
[dev-dependencies]
2223
ring = "0.16.15"
23-
env_logger = "0.7.1"
2424
rsa = "0.3.0"
2525
picky-asn1-x509 = "0.3.2"
2626
base64 = "0.12.3"

e2e_tests/provider_cfg/all/Dockerfile

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,7 @@ RUN apt-get update && \
66
apt-get install -y git make gcc python3 python curl wget cmake && \
77
apt-get install -y automake autoconf libtool pkg-config libssl-dev libgcc1 && \
88
# These libraries are needed for bindgen as it uses libclang.so
9-
apt-get install -y clang libclang-dev libc6-dev-i386 && \
10-
# Install cargo globally to not have to install it for each user for multitenancy tests
11-
apt-get install -y cargo
9+
apt-get install -y clang libclang-dev libc6-dev-i386
1210

1311
WORKDIR /tmp
1412
RUN wget https://github.com/ARMmbed/mbed-crypto/archive/mbedcrypto-2.0.0.tar.gz
@@ -55,3 +53,17 @@ RUN softhsm2-util --init-token --slot 0 --label "Parsec Tests" --pin 123456 --so
5553
# Add users for multitenancy tests
5654
RUN useradd -m parsec-client-1
5755
RUN useradd -m parsec-client-2
56+
57+
USER parsec-client-1
58+
RUN curl https://sh.rustup.rs -sSf | bash -s -- -y
59+
60+
USER parsec-client-2
61+
RUN curl https://sh.rustup.rs -sSf | bash -s -- -y
62+
63+
# Install Rust toolchain for root
64+
USER root
65+
RUN curl https://sh.rustup.rs -sSf | bash -s -- -y
66+
ENV PATH="/root/.cargo/bin:${PATH}"
67+
68+
# Download the SPIRE server and agent
69+
RUN curl -s -N -L https://github.com/spiffe/spire/releases/download/v0.11.1/spire-0.11.1-linux-x86_64-glibc.tar.gz | tar xz

e2e_tests/provider_cfg/all/config.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ socket_path = "/tmp/parsec.sock"
1414

1515
[authenticator]
1616
auth_type = "Direct"
17+
#workload_endpoint="unix:///tmp/agent.sock"
1718

1819
[[key_manager]]
1920
name = "on-disk-manager"

e2e_tests/provider_cfg/tpm/Dockerfile

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@ FROM tpm2software/tpm2-tss:ubuntu-18.04
22

33
ENV PKG_CONFIG_PATH /usr/local/lib/pkgconfig
44

5+
RUN apt-get update && \
6+
apt-get install -y cmake
7+
58
# Download and install TSS 2.0
69
RUN git clone https://github.com/tpm2-software/tpm2-tss.git --branch 2.3.3
710
RUN cd tpm2-tss \

e2e_tests/src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,9 @@ fn convert_error(err: Error) -> ResponseStatus {
5252
impl TestClient {
5353
/// Creates a TestClient instance.
5454
pub fn new() -> TestClient {
55+
// As this method is called in test, it will be called more than once per application.
56+
if let Err(_) = env_logger::try_init() {};
57+
5558
let mut basic_client = BasicClient::new_naked();
5659

5760
let ipc_handler = unix_socket::Handler::new(TEST_SOCKET_PATH.into(), Some(TEST_TIMEOUT));

e2e_tests/tests/per_provider/stress_test.rs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,6 @@ use std::time::Duration;
55

66
#[test]
77
fn stress_test() {
8-
env_logger::init();
9-
108
let config = StressTestConfig {
119
no_threads: num_cpus::get(),
1210
req_per_thread: 250,
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
// Copyright 2020 Contributors to the Parsec project.
2+
// SPDX-License-Identifier: Apache-2.0
3+
//! JWT SVID authenticator
4+
5+
use super::ApplicationName;
6+
use super::Authenticate;
7+
use crate::front::listener::ConnectionMetadata;
8+
use log::error;
9+
use parsec_interface::operations::list_authenticators;
10+
use parsec_interface::requests::request::RequestAuth;
11+
use parsec_interface::requests::Result;
12+
use parsec_interface::requests::{AuthType, ResponseStatus};
13+
use parsec_interface::secrecy::ExposeSecret;
14+
use spiffe::svid::jwt::Jwt;
15+
use spiffe::workload::jwt::JWTClient;
16+
use std::str;
17+
18+
/// JWT SVID authenticator
19+
#[allow(missing_debug_implementations)]
20+
pub struct JwtSvidAuthenticator {
21+
jwt_client: JWTClient,
22+
}
23+
24+
impl JwtSvidAuthenticator {
25+
/// Create a new JWT-SVID authenticator with a specific path to the Workload API socket.
26+
pub fn new(workload_endpoint: String) -> Self {
27+
JwtSvidAuthenticator {
28+
jwt_client: JWTClient::new(&workload_endpoint, None, None),
29+
}
30+
}
31+
}
32+
33+
impl Authenticate for JwtSvidAuthenticator {
34+
fn describe(&self) -> Result<list_authenticators::AuthenticatorInfo> {
35+
Ok(list_authenticators::AuthenticatorInfo {
36+
description: String::from(
37+
"Authenticator validating a JWT SPIFFE Verifiable Identity Document",
38+
),
39+
version_maj: 0,
40+
version_min: 1,
41+
version_rev: 0,
42+
id: AuthType::JwtSvid,
43+
})
44+
}
45+
46+
fn authenticate(
47+
&self,
48+
auth: &RequestAuth,
49+
_: Option<ConnectionMetadata>,
50+
) -> Result<ApplicationName> {
51+
let svid = Jwt::new(
52+
str::from_utf8(auth.buffer.expose_secret())
53+
.map_err(|e| {
54+
error!(
55+
"The authentication buffer can not be parsed into a UTF-8 string ({}).",
56+
e
57+
);
58+
ResponseStatus::InvalidEncoding
59+
})?
60+
.to_string(),
61+
);
62+
let audience = String::from("parsec");
63+
64+
let validate_response = self.jwt_client.validate(audience, svid).map_err(|e| {
65+
error!("The validation of the JWT-SVID failed ({}).", e);
66+
ResponseStatus::AuthenticationError
67+
})?;
68+
69+
Ok(ApplicationName(validate_response.spiffe_id().to_string()))
70+
}
71+
}

src/authenticators/mod.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ pub mod direct_authenticator;
1313

1414
pub mod unix_peer_credentials_authenticator;
1515

16+
pub mod jwt_svid_authenticator;
17+
1618
use crate::front::listener::ConnectionMetadata;
1719
use parsec_interface::operations::list_authenticators;
1820
use parsec_interface::requests::request::RequestAuth;
@@ -68,12 +70,17 @@ impl std::fmt::Display for ApplicationName {
6870
}
6971

7072
/// Authenticator configuration structure
71-
#[derive(Copy, Clone, Deserialize, Debug, Zeroize)]
73+
#[derive(Deserialize, Debug, Zeroize)]
7274
#[zeroize(drop)]
7375
#[serde(tag = "auth_type")]
7476
pub enum AuthenticatorConfig {
7577
/// Direct authentication
7678
Direct,
7779
/// Unix Peer Credenditals authentication
7880
UnixPeerCredentials,
81+
/// JWT-SVID
82+
JwtSvid {
83+
/// Path to the Workload API socket
84+
workload_endpoint: String,
85+
},
7986
}

src/utils/service_builder.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
//! provided configuration.
77
use super::global_config::GlobalConfigBuilder;
88
use crate::authenticators::direct_authenticator::DirectAuthenticator;
9+
use crate::authenticators::jwt_svid_authenticator::JwtSvidAuthenticator;
910
use crate::authenticators::unix_peer_credentials_authenticator::UnixPeerCredentialsAuthenticator;
1011
use crate::authenticators::{Authenticate, AuthenticatorConfig};
1112
use crate::back::{
@@ -383,6 +384,10 @@ fn build_authenticators(config: &AuthenticatorConfig) -> Vec<(AuthType, Authenti
383384
AuthType::UnixPeerCredentials,
384385
Box::from(UnixPeerCredentialsAuthenticator {}),
385386
)),
387+
AuthenticatorConfig::JwtSvid { workload_endpoint } => authenticators.push((
388+
AuthType::JwtSvid,
389+
Box::from(JwtSvidAuthenticator::new(workload_endpoint.to_string())),
390+
)),
386391
};
387392

388393
authenticators

0 commit comments

Comments
 (0)