Skip to content

Add basic JWT-SVID API support #1

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
wants to merge 1 commit into from
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
20 changes: 12 additions & 8 deletions spiffe/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,19 @@
name = "spiffe"
version = "0.1.0"
authors = ["Sabree Blackmon <[email protected]>"]
edition = "2018"

[dependencies]
openssl = { version = "0.10", features = ["vendored"] }
error-chain = "0.12.0"
hyper = "0.10.0"
protobuf = "~2.0"
grpcio = { git = "https://github.com/heavypackets/grpc-rs", version = "0.3", default-features = false, features = ["protobuf-codec"] }
futures = "0.1.24"
lazy_static = "^1.1"
openssl = "0.10.30"
error-chain = "0.12.4"
hyper = "0.13.8"
protobuf = "2.18.0"
grpcio = { git = "https://github.com/tikv/grpc-rs", rev = "b9ddf27a81d5cfef057638ffc2d02bd34d85a422", default-features = false, features = ["protobuf-codec", "openssl"] }
futures = "0.3.6"
lazy_static = "1.4.0"
url = "2.1.1"
log = "0.4.11"
zeroize = { version = "1.1.1", features = ["zeroize_derive"] }

[dev-dependencies]
assert_matches = "1.3"
assert_matches = "1.4.0"
2 changes: 1 addition & 1 deletion spiffe/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ spiffe:
cargo build --color always

protobuf:
protoc --rust_out=src/api/ --grpc_out=src/api/ --plugin=protoc-gen-grpc=`which grpc_rust_plugin` src/api/workload_api.proto
protoc --rust_out=src/workload/ --grpc_out=src/workload/ --plugin=protoc-gen-grpc=`which grpc_rust_plugin` src/workload/workload_api.proto

test:
cargo test --color always
11 changes: 0 additions & 11 deletions spiffe/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,3 @@
extern crate futures;
extern crate grpcio;
extern crate hyper;
extern crate openssl;
extern crate protobuf;

#[macro_use]
extern crate error_chain;
#[macro_use]
extern crate lazy_static;

pub mod svid;
pub mod uri;
pub mod workload;
51 changes: 51 additions & 0 deletions spiffe/src/svid/jwt.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
use crate::svid::{SVIDKind, SVID};
use crate::uri::URI;
use error_chain::error_chain;
use std::ops::Deref;
use std::str::FromStr;
use zeroize::Zeroize;

error_chain! {
errors {
InvalidURI {
description("An error occured during the parsing of the SPIFFE ID")
display("The SPIFFE ID can not be parsed into a valid SPIFFE URI")
}
}
}

impl SVIDKind for Jwt {}

#[derive(Zeroize)]
#[zeroize(drop)]
pub struct Jwt {
svid: String,
}

impl Jwt {
pub fn new(svid: String) -> Jwt {
Jwt { svid }
}

pub fn svid(&self) -> &str {
&self.svid
}
}

impl SVID<Jwt> {
pub fn new(svid: String, uri: &str) -> Result<SVID<Jwt>> {
Ok(SVID::<Jwt> {
doc: Jwt { svid },
uri: URI::from_str(uri).chain_err(|| ErrorKind::InvalidURI)?,
})
}
}

impl Deref for SVID<Jwt> {
type Target = Jwt;

fn deref(&self) -> &Jwt {
let SVID::<Jwt> { doc, .. } = self;
&doc
}
}
221 changes: 3 additions & 218 deletions spiffe/src/svid/mod.rs
Original file line number Diff line number Diff line change
@@ -1,227 +1,12 @@
use std::fmt;
use std::fs;
use std::ops::Deref;
use std::path::Path;
pub mod jwt;
pub mod x509;

type OpenSSlX509Cert = ::openssl::x509::X509;

use uri::URI;

error_chain!{
errors {
InvalidFilePath(path: String) {
description("An IO error during the parsing of an SVID")
display("Unable to parse SVID: Invalid file path {}", path)
}

InvalidPEM {
description("An error during the parsing of an SVID PEM")
display("Unable to parse SVID: Not a valid PEM")
}

InvalidDER {
description("An error during the parsing of an SVID DER")
display("Unable to parse SVID: Not a valid DER")
}

InvalidSAN {
description("An error during the validation of SVID SANs")
display("Unable to parse SVID: SANs do not contain a valid SPIFFE URI")
}

MultipleURIFound(first: String, next: String) {
description("An error during the validation of SVID certificate")
display("Multiple valid SPIFFE URIs found in SVID: {} & {}", first, next)
}
}

links {
Uri(::uri::Error, ::uri::ErrorKind);
}

foreign_links {
SSL(::openssl::error::ErrorStack);
Io(::std::io::Error);
}
}

impl<'a> From<&'a Path> for ErrorKind {
fn from(path: &'a Path) -> Self {
ErrorKind::InvalidFilePath(path.to_str().unwrap_or("").to_string())
}
}
use crate::uri::URI;

pub trait SVIDKind {}

pub type Bundle = Vec<u8>;
pub type Key = Vec<u8>;

pub struct X509 {
cert: OpenSSlX509Cert,
key: Option<Key>,
bundle: Option<Bundle>,
}

impl X509 {
pub fn new(cert: OpenSSlX509Cert, key: Option<Key>, bundle: Option<Bundle>) -> X509 {
X509 {
cert,
key: match key {
Some(k) => Some(k.to_vec()),
None => None,
},
bundle: match bundle {
Some(b) => Some(b.to_vec()),
None => None,
},
}
}

pub fn cert(&self) -> &OpenSSlX509Cert {
&self.cert
}

pub fn key(&self) -> Option<&Vec<u8>> {
match self.key {
Some(ref k) => Some(&k),
None => None,
}
}

pub fn bundle(&self) -> Option<&Vec<u8>> {
match self.bundle {
Some(ref b) => Some(&b),
None => None,
}
}
}

impl fmt::Debug for X509 {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let X509 { cert, .. } = self;
write!(
f,
"OpenSSL X509 Certificate: {{ {:?} }}",
cert.to_pem().unwrap_or_else(|_| vec![])
)
}
}

impl SVIDKind for X509 {}

#[derive(Debug)]
pub struct SVID<T: SVIDKind> {
doc: T,
uri: URI,
}

impl SVID<X509> {
pub fn from_pem(pem: &[u8], key: Option<Key>, bundle: Option<Bundle>) -> Result<SVID<X509>> {
let cert = OpenSSlX509Cert::from_pem(pem).chain_err(|| ErrorKind::InvalidPEM)?;

match SVID::<X509>::parse_uri(&cert) {
Ok(uri) => Ok(SVID::<X509> {
doc: X509::new(cert, key, bundle),
uri,
}),
Err(e) => Err(e.chain_err(|| ErrorKind::InvalidSAN)),
}
}

pub fn from_path(path: &Path, key: Option<&Path>, bundle: Option<&Path>) -> Result<SVID<X509>> {
let contents = fs::read(path).chain_err(|| path)?;
let cert =
OpenSSlX509Cert::from_pem(contents.as_slice()).chain_err(|| ErrorKind::InvalidPEM)?;

let key_contents = match key {
Some(path) => Some(fs::read(path).chain_err(|| path)?.to_vec()),
None => None,
};
let bundle_contents = match bundle {
Some(path) => Some(fs::read(path).chain_err(|| path)?.to_vec()),
None => None,
};

match SVID::<X509>::parse_uri(&cert) {
Ok(uri) => Ok(SVID::<X509> {
doc: X509::new(cert, key_contents, bundle_contents),
uri,
}),
Err(e) => Err(e.chain_err(|| ErrorKind::InvalidPEM)),
}
}

pub fn from_der(der: &[u8], key: Option<Key>, bundle: Option<Bundle>) -> Result<SVID<X509>> {
let cert = OpenSSlX509Cert::from_der(der).chain_err(|| ErrorKind::InvalidDER)?;

match SVID::<X509>::parse_uri(&cert) {
Ok(uri) => Ok(SVID::<X509> {
doc: X509::new(cert, key, bundle),
uri,
}),
Err(e) => Err(e.chain_err(|| ErrorKind::InvalidSAN)),
}
}

pub fn from_x509(
cert: OpenSSlX509Cert,
key: Option<Key>,
bundle: Option<Bundle>,
) -> Result<SVID<X509>> {
match SVID::<X509>::parse_uri(&cert) {
Ok(uri) => Ok(SVID::<X509> {
doc: X509::new(cert, key, bundle),
uri,
}),
Err(e) => Err(e.chain_err(|| ErrorKind::InvalidPEM)),
}
}

pub fn uri(&self) -> &URI {
let SVID::<X509> { uri, .. } = self;
&uri
}

pub fn x509(&self) -> &X509 {
let SVID::<X509> { doc, .. } = self;
&doc
}

pub fn match_spiffe_uri(&self, uri: &str) -> Result<bool> {
Ok(self.uri().to_string().eq_ignore_ascii_case(uri))
}

fn parse_uri(cert: &OpenSSlX509Cert) -> Result<URI> {
let sans = match cert.subject_alt_names() {
Some(val) => val,
None => Err(ErrorKind::InvalidSAN)?,
};

let mut validated_uri: Option<URI> = None;
// Only allows one valid SPIFFE uri in SAN field per SPIFFE specification - returns error if multiple found
for san_entry in sans {
if let Some(uri) = san_entry.uri() {
if let Ok(spiffe_uri) = uri.parse::<URI>() {
if validated_uri.is_some() {
Err(ErrorKind::MultipleURIFound(
validated_uri.unwrap().to_string(),
uri.to_string(),
))?;
}
validated_uri = Some(spiffe_uri);
}
}
}

validated_uri.ok_or_else(|| Error::from(ErrorKind::InvalidSAN))
}
}

impl Deref for SVID<X509> {
type Target = X509;

fn deref(&self) -> &X509 {
let SVID::<X509> { doc, .. } = self;
&doc
}
}
Loading