Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
e7cedcb
Initial draft work for introducing a separate TLS provider replaceing
May 25, 2021
4f8ab56
Initial draft work for introducing a separate TLS provider replaceing
Feb 15, 2021
db8435e
Fixing formatting
Mar 22, 2021
d148149
Cleanup
Mar 22, 2021
ee23261
Reducing the amount of duplicate for the boring implementation
Mar 23, 2021
b1bec69
Fixing mistakenly committed cargo change where debug symbols are gene…
Mar 23, 2021
a33f125
BoringSSL will now be built in FIPS mode
Mar 31, 2021
0cf011c
Formatting
Mar 31, 2021
b7ece50
Rebasing from main
May 25, 2021
87d3baf
Updated boring to 1.1.6
May 25, 2021
20059f1
Removing application specific ignores from .gitignore
Jun 2, 2021
9d4fe1e
fmt
olix0r Jun 2, 2021
89392bb
Appease clippy
olix0r Jun 2, 2021
ebcf6d9
Merge branch 'main' into task/introduce-openssl-bindings
olix0r Jun 3, 2021
7c54e1d
Simplifying implemenation of the Error trait by using thiserror:Error
Jun 4, 2021
e5efdd2
Addressing comments and removing more error trait boilerplate
Jun 4, 2021
3f25b9f
Simplifying verication on subject alt names
Jun 4, 2021
1b0ec20
Formatting
Jun 4, 2021
0d35990
Additional improvements, println removals
Jun 4, 2021
c78694b
Simplifying client id construction
Jun 4, 2021
7c42181
Reducing field visibility since no access is needed.
Jun 4, 2021
d8bd349
Client config display made more idiomatic
Jun 4, 2021
9afc8ae
Continuing with idiomatic improvements
Jun 4, 2021
607226e
Starting to remove the abundant amount of unwrap() calls that are not…
Jun 7, 2021
0b396b2
Temporarily reverting the SAN verification for the openssl/boringssl
Jun 7, 2021
612e4c7
Removing nested match call for certifications
Jun 7, 2021
93e19a3
Cleaning up the certificate verification
Jun 7, 2021
d359e11
Some additional cleanup and validation
Jun 7, 2021
c503c21
Minor logging fixes
Jun 8, 2021
6ac549e
Formatting
Jun 8, 2021
13db857
Removing commented out blocks
Jun 8, 2021
5c3440c
Reducing visibility of new constructor methods for both ClientConfig …
Jun 8, 2021
7916a12
Reverting change done to the dns name debug method and trust anchors …
Jun 8, 2021
9a94bdc
Swapping if statement to an if-let statement
Jun 8, 2021
81cccbb
Using thiserror to implement InvalidCrt error
Jun 8, 2021
a677a04
Additional improvements
Jun 8, 2021
2dabb0a
Removing std error
Jun 8, 2021
d8c25c8
Reimplemented Elisa's certificate SAN check
Jun 8, 2021
5d3e9c1
Fixing debug implementations for rustls
Jun 8, 2021
bd2f7a3
Revering dev symbols
Jun 9, 2021
7814330
Minor code organization
Jun 9, 2021
ee1da30
Formatting
Jun 9, 2021
1e5f92f
Accept and Connect handling for Boringssl updated
Jun 9, 2021
eb5f1cf
Openssl implementation accept and connect modified slightly
Jun 9, 2021
00abf28
Readded removed newline
Jun 9, 2021
f416e8b
Merge branch 'main' into task/introduce-openssl-bindings
olix0r Jun 17, 2021
aa16192
Merge branch 'main' of github.com:linkerd/linkerd2-proxy into task/in…
Sep 28, 2021
0b36303
Fixing unused import
Sep 29, 2021
a35e44b
Fixing borrow reference that was immediately dereferenced
Sep 29, 2021
83fe386
Removing openssl entirely
Sep 29, 2021
08092f3
Fixing local changes that were committed by mistake
Sep 29, 2021
bbb88c2
Removing extra newline
Sep 29, 2021
47a75d3
Adding newline at the end of file
Sep 29, 2021
2658a40
Updating cargo lock for updated boring module
Sep 29, 2021
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
355 changes: 336 additions & 19 deletions Cargo.lock

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -73,3 +73,6 @@ lto = true

[patch.crates-io]
webpki = { git = "https://github.com/linkerd/webpki", branch = "cert-dns-names-0.21" }
boring = { git = 'https://github.com/netapp/boring', branch = "feat/fips-support"}
boring-sys = { git = 'https://github.com/netapp/boring', branch = "feat/fips-support"}
tokio-boring = { git = 'https://github.com/netapp/boring', branch = "feat/fips-support"}
14 changes: 11 additions & 3 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -36,18 +36,26 @@ ARG PROXY_FEATURES

RUN --mount=type=cache,target=/var/lib/apt/lists \
--mount=type=cache,target=/var/tmp \
apt update && apt install -y time cmake
apt update && \
apt install -y \
time \
cmake \
golang \
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we really need golang here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah yeah that's crazy, the boring-sys crate uses go as part of the whole build process.

We probably have to figure out a better way to handle this docker file.

clang

ENV CC=/usr/bin/clang
ENV CXX=/usr/bin/clang++

WORKDIR /usr/src/linkerd2-proxy
COPY . .
RUN --mount=type=cache,target=target \
--mount=type=cache,from=rust:1.55.0-buster,source=/usr/local/cargo,target=/usr/local/cargo \
mkdir -p /out && \
if [ -n "$PROXY_UNOPTIMIZED" ]; then \
(cd linkerd2-proxy && /usr/bin/time -v cargo build --locked --features="$PROXY_FEATURES") && \
(cd linkerd2-proxy && /usr/bin/time -v cargo build --locked --no-default-features --features="$PROXY_FEATURES") && \
mv target/debug/linkerd2-proxy /out/linkerd2-proxy ; \
else \
(cd linkerd2-proxy && /usr/bin/time -v cargo build --locked --release --features="$PROXY_FEATURES") && \
(cd linkerd2-proxy && /usr/bin/time -v cargo build --locked --release --no-default-features --features="$PROXY_FEATURES") && \
mv target/release/linkerd2-proxy /out/linkerd2-proxy ; \
fi

Expand Down
2 changes: 2 additions & 0 deletions linkerd/app/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ This is used by tests and the executable.

[features]
allow-loopback = ["linkerd-app-outbound/allow-loopback"]
boring-tls = ["linkerd-app-core/boring-tls"]
rustls-tls = ["linkerd-app-core/rustls-tls"]

[dependencies]
futures = { version = "0.3", default-features = false }
Expand Down
4 changes: 4 additions & 0 deletions linkerd/app/core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ This crate conglomerates proxy configuration, runtime administration, etc,
independently of the inbound and outbound proxy logic.
"""

[features]
rustls-tls = ["linkerd-identity/rustls-tls", "linkerd-tls/rustls-tls"]
boring-tls = ["linkerd-identity/boring-tls", "linkerd-tls/boring-tls"]

[dependencies]
bytes = "1"
drain = { version = "0.1.0", features = ["retain"] }
Expand Down
4 changes: 1 addition & 3 deletions linkerd/app/inbound/src/direct.rs
Original file line number Diff line number Diff line change
Expand Up @@ -350,9 +350,7 @@ impl svc::Param<tls::server::Config> for WithTransportHeaderAlpn {
// be preferable if rustls::ServerConfig wrapped individual fields in an
// Arc so they could be overridden independently.
let mut config = self.0.server_config().as_ref().clone();
config
.alpn_protocols
.push(transport_header::PROTOCOL.into());
config.add_protocols(transport_header::PROTOCOL.into());
config.into()
}
}
Expand Down
7 changes: 5 additions & 2 deletions linkerd/identity/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,16 @@ edition = "2018"
[features]
default = []
test-util = []
rustls-tls = ["ring", "rustls"]
boring-tls = ["boring"]

[dependencies]
linkerd-dns-name = { path = "../dns/name" }
ring = "0.16.19"
ring = { version = "0.16.19", optional = true, features = ["std"] }
rustls = { version = "0.19", optional = true }
thiserror = "1.0"
tokio-rustls = "0.22"
tracing = "0.1.28"
untrusted = "0.7"
webpki = "=0.21.4"

boring = { version = "1.1.6", optional = true, features = ["fips"]}
304 changes: 304 additions & 0 deletions linkerd/identity/src/imp/boringssl.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,304 @@
use std::fmt;
use std::sync::Arc;
use std::time::SystemTime;

use boring::{
error::ErrorStack,
pkey::{PKey, Private},
stack::Stack,
x509::{
store::{X509Store, X509StoreBuilder},
{X509StoreContext, X509VerifyResult, X509},
},
};

use tracing::{debug, error, trace};

use crate::{LocalId, Name};
use std::fmt::Formatter;
use thiserror::Error;

#[derive(Clone, Debug)]
pub struct Key(pub Arc<PKey<Private>>);

impl Key {
pub fn from_pkcs8(b: &[u8]) -> Result<Key, Error> {
let key = PKey::private_key_from_pkcs8(b)?;
Ok(Key(Arc::new(key)))
}
}

#[derive(Clone, Debug, Error)]
#[error(transparent)]
pub struct Error(#[from] ErrorStack);

#[derive(Clone)]
pub struct TrustAnchors(Arc<X509Store>);

impl TrustAnchors {
fn store() -> X509StoreBuilder {
X509StoreBuilder::new().expect("unable to create certificate store")
}

#[cfg(any(test, feature = "test-util"))]
pub fn empty() -> Self {
Self(Arc::new(TrustAnchors::store().build()))
}

pub fn from_pem(s: &str) -> Option<Self> {
debug!("constructing trust trust anchors from pem {}", s);
return match X509::from_pem(s.as_bytes()) {
Ok(cert) => {
let mut store = TrustAnchors::store();
trace!("adding certificate to trust anchors {:?}", cert);
if let Err(err) = store.add_cert(cert) {
error!("unable to add certificate to trust anchors, {}", err);
return None;
}

Some(Self(Arc::new(store.build())))
}
Err(err) => {
error!("unable to construct trust anchor {}", err);
None
}
};
}

pub fn certify(&self, key: Key, crt: Crt) -> Result<CrtKey, InvalidCrt> {
let cert = crt.cert.clone();
if !cert
.subject_alt_names()
.into_iter()
.flat_map(|alt_names| alt_names.into_iter())
.any(|n| n.dnsname().expect("unable to convert to dns name") == crt.id.0.as_ref())
{
return Err(InvalidCrt::SubjectAltName(crt.id));
}

let mut chain = Stack::new()?;
chain.push(cert.clone())?;
for chain_crt in crt.chain.clone() {
chain.push(chain_crt)?;
}

let mut context = X509StoreContext::new()?;
context.init(&self.0, &cert, &chain, |c| match c.verify_cert() {
Ok(true) => Ok(Ok(true)),
Ok(false) => Ok(Err(InvalidCrt::Verify(c.error()))),
Err(err) => Err(err),
})??;

let server_config =
ServerConfig::new(vec![], self.0.clone(), Some(crt.clone()), Some(key.clone()));
let client_config =
ClientConfig::new(vec![], self.0.clone(), Some(crt.clone()), Some(key.clone()));

Ok(CrtKey {
id: crt.id.clone(),
expiry: crt.expiry.clone(),
client_config: Arc::new(client_config),
server_config: Arc::new(server_config),
})
}

pub fn client_config(&self) -> Arc<ClientConfig> {
Arc::new(ClientConfig::new(vec![], self.0.clone(), None, None))
}
}

impl fmt::Debug for TrustAnchors {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
f.pad("boringssl::TrustAnchors")
}
}

#[derive(Clone, Debug, Error)]
pub enum InvalidCrt {
#[error("subject alt name incorrect ({0})")]
SubjectAltName(LocalId),
#[error("{0}")]
Verify(#[source] X509VerifyResult),
#[error(transparent)]
General(#[from] Error),
}

impl From<ErrorStack> for InvalidCrt {
fn from(err: ErrorStack) -> Self {
InvalidCrt::General(err.into())
}
}

#[derive(Clone)]
pub struct CrtKey {
id: LocalId,
expiry: SystemTime,
client_config: Arc<ClientConfig>,
server_config: Arc<ServerConfig>,
}

// === CrtKey ===
impl CrtKey {
pub fn name(&self) -> &Name {
self.id.as_ref()
}

pub fn expiry(&self) -> SystemTime {
self.expiry
}

pub fn id(&self) -> &LocalId {
&self.id
}

pub fn client_config(&self) -> Arc<ClientConfig> {
self.client_config.clone()
}

pub fn server_config(&self) -> Arc<ServerConfig> {
self.server_config.clone()
}
}

impl fmt::Debug for CrtKey {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
f.debug_struct("boringssl::CrtKey")
.field("id", &self.id)
.field("expiry", &self.expiry)
.finish()
}
}

#[derive(Clone, Debug)]
pub struct Crt {
pub(crate) id: LocalId,
expiry: SystemTime,
pub cert: X509,
pub chain: Vec<X509>,
}

impl Crt {
pub fn new(
id: LocalId,
leaf: Vec<u8>,
intermediates: Vec<Vec<u8>>,
expiry: SystemTime,
) -> Self {
let mut chain = Vec::with_capacity(intermediates.len() + 1);
let cert = X509::from_der(&leaf).expect("unable to convert to a x509 certificate");
chain.extend(
intermediates
.into_iter()
.map(|crt| X509::from_der(&crt).expect("unable to add intermediate certificate")),
);

Self {
id,
cert,
chain,
expiry,
}
}

pub fn name(&self) -> &Name {
self.id.as_ref()
}
}

#[derive(Clone)]
pub struct ClientConfig {
pub root_certs: Arc<X509Store>,
pub key: Option<Arc<Key>>,
pub cert: Option<Arc<Crt>>,
protocols: Arc<Vec<Vec<u8>>>,
}

impl fmt::Debug for ClientConfig {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
f.debug_struct("openssl::ClientConfig")
.field("protocols", &self.protocols)
.finish()
}
}

impl ClientConfig {
fn new(
protocols: Vec<Vec<u8>>,
root_certs: Arc<X509Store>,
cert: Option<Crt>,
key: Option<Key>,
) -> Self {
Self {
root_certs,
protocols: Arc::new(protocols),
key: key.map(Arc::new),
cert: cert.map(Arc::new),
}
}
pub fn empty() -> Self {
ClientConfig::new(
Vec::new(),
Arc::new(
X509StoreBuilder::new()
.expect("unable to construct root certs")
.build(),
),
None,
None,
)
}

pub fn set_protocols(&mut self, protocols: Vec<Vec<u8>>) {
self.protocols = Arc::new(protocols)
}
}

#[derive(Clone)]
pub struct ServerConfig {
pub root_certs: Arc<X509Store>,
pub key: Option<Arc<Key>>,
pub cert: Option<Arc<Crt>>,
alpn_protocols: Arc<Vec<Vec<u8>>>,
}

impl ServerConfig {
fn new(
alpn_protocols: Vec<Vec<u8>>,
root_certs: Arc<X509Store>,
cert: Option<Crt>,
key: Option<Key>,
) -> Self {
Self {
alpn_protocols: Arc::new(alpn_protocols),
root_certs,
key: key.map(Arc::new),
cert: cert.map(Arc::new),
}
}
/// Produces a server config that fails to handshake all connections.
pub fn empty() -> Self {
ServerConfig::new(
Vec::new(),
Arc::new(
X509StoreBuilder::new()
.expect("unable to construct root certs")
.build(),
),
None,
None,
)
}

pub fn add_protocols(&mut self, protocols: Vec<u8>) {
self.alpn_protocols.as_ref().clone().push(protocols)
}
}

impl fmt::Debug for ServerConfig {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
f.debug_struct("openssl::ServerConfig")
.field("alpn_protocols", &self.alpn_protocols)
.field("key", &self.key)
.finish()
}
}
Loading