Skip to content
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

Allow rustls configuration in rumqttd #748

Closed
wants to merge 9 commits 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
2 changes: 2 additions & 0 deletions rumqttd/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Added
- Log warning if websocket config is getting ignored
- Ability to use rustls without client certificates.

### Changed
- Console endpoint /config prints Router Config instead of returning console settings
- v4 config is optional, user can specify v4 and/or v5 config
- websocket feature is enabled by default
- console configuration is optional
- rustls accepts all private key types available in [`rustls_pemfile`](https://docs.rs/rustls-pemfile/latest/rustls_pemfile/).

### Deprecated
- "websockets" feature is removed in favour of "websocket"
Expand Down
14 changes: 10 additions & 4 deletions rumqttd/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ pub struct PrometheusSetting {
#[serde(untagged)]
pub enum TlsConfig {
Rustls {
capath: String,
capath: Option<String>,
certpath: String,
keypath: String,
},
Expand All @@ -88,9 +88,15 @@ impl TlsConfig {
capath,
certpath,
keypath,
} => [capath, certpath, keypath]
.iter()
.all(|v| Path::new(v).exists()),
} => {
let cert_key_exist = Path::new(certpath).exists() && Path::new(keypath).exists();
let ca_exists = match capath {
Some(p) => Path::new(p).exists(),
None => true,
};

cert_key_exist && ca_exists
}
TlsConfig::NativeTls { pkcs12path, .. } => Path::new(pkcs12path).exists(),
}
}
Expand Down
83 changes: 51 additions & 32 deletions rumqttd/src/server/tls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ pub enum Error {
CertificateParse,
}

#[cfg(feature = "use-rustls")]
#[cfg(all(feature = "use-rustls", feature = "validate-tenant-prefix"))]
/// Extract uid from certificate's subject organization field
fn extract_tenant_id(der: &[u8]) -> Result<Option<String>, Error> {
let (_, cert) =
Expand Down Expand Up @@ -102,7 +102,7 @@ impl TLSAcceptor {
capath,
certpath,
keypath,
} => Self::rustls(capath, certpath, keypath),
} => Self::rustls(capath.as_ref(), certpath, keypath),
#[cfg(feature = "use-native-tls")]
TlsConfig::NativeTls {
pkcs12path,
Expand All @@ -117,7 +117,13 @@ impl TLSAcceptor {

pub async fn accept(&self, stream: TcpStream) -> Result<(Option<String>, Box<dyn N>), Error> {
match self {
#[cfg(feature = "use-rustls")]
#[cfg(all(feature = "use-rustls", not(feature = "validate-tenant-prefix")))]
TLSAcceptor::Rustls { acceptor } => {
let stream = acceptor.accept(stream).await?;
let network = Box::new(stream);
Ok((None, network))
}
#[cfg(all(feature = "use-rustls", feature = "validate-tenant-prefix"))]
TLSAcceptor::Rustls { acceptor } => {
let stream = acceptor.accept(stream).await?;
let (_, session) = stream.get_ref();
Expand Down Expand Up @@ -171,7 +177,7 @@ impl TLSAcceptor {

#[cfg(feature = "use-rustls")]
fn rustls(
ca_path: &String,
ca_path: Option<&String>,
cert_path: &String,
key_path: &String,
) -> Result<TLSAcceptor, Error> {
Expand All @@ -189,38 +195,51 @@ impl TLSAcceptor {
// Get private key
let key_file = File::open(key_path);
let key_file = key_file.map_err(|_| Error::ServerKeyNotFound(key_path.clone()))?;
let keys = rustls_pemfile::rsa_private_keys(&mut BufReader::new(key_file));
let keys = keys.map_err(|_| Error::InvalidServerKey(key_path.clone()))?;
let mut key_file_buf = BufReader::new(key_file);
let mut key: Option<Vec<u8>> = None;
while let Ok(item) = rustls_pemfile::read_one(&mut key_file_buf) {
match item {
Some(item) => match item {
rustls_pemfile::Item::RSAKey(k)
| rustls_pemfile::Item::PKCS8Key(k)
| rustls_pemfile::Item::ECKey(k) => {
key = Some(k);
break;
}
_ => continue,
},
None => return Err(Error::InvalidServerKey(key_path.clone())),
}
}

// Get the first key
let key = match keys.first() {
Some(k) => k.clone(),
match key {
Some(k) => (certs, PrivateKey(k)),
None => return Err(Error::InvalidServerKey(key_path.clone())),
};

(certs, PrivateKey(key))
}
};

// client authentication with a CA. CA isn't required otherwise
let server_config = {
let ca_file = File::open(ca_path);
let ca_file = ca_file.map_err(|_| Error::CaFileNotFound(ca_path.clone()))?;
let ca_file = &mut BufReader::new(ca_file);
let ca_certs = rustls_pemfile::certs(ca_file)?;
let ca_cert = ca_certs
.first()
.map(|c| Certificate(c.to_owned()))
.ok_or_else(|| Error::InvalidCACert(ca_path.to_string()))?;

let mut store = RootCertStore::empty();
store
.add(&ca_cert)
.map_err(|_| Error::InvalidCACert(ca_path.to_string()))?;

ServerConfig::builder()
.with_safe_defaults()
.with_client_cert_verifier(Arc::new(AllowAnyAuthenticatedClient::new(store)))
.with_single_cert(certs, key)?
let builder = ServerConfig::builder().with_safe_defaults();
let server_config = match ca_path {
Some(ca_path) => {
let ca_file = File::open(ca_path);
let ca_file = ca_file.map_err(|_| Error::CaFileNotFound(ca_path.clone()))?;
let ca_file = &mut BufReader::new(ca_file);
let ca_certs = rustls_pemfile::certs(ca_file)?;
let ca_cert = ca_certs
.first()
.map(|c| Certificate(c.to_owned()))
.ok_or_else(|| Error::InvalidCACert(ca_path.to_string()))?;

let mut store = RootCertStore::empty();
store
.add(&ca_cert)
.map_err(|_| Error::InvalidCACert(ca_path.to_string()))?;

builder
.with_client_cert_verifier(Arc::new(AllowAnyAuthenticatedClient::new(store)))
.with_single_cert(certs, key)?
}
None => builder.with_no_client_auth().with_single_cert(certs, key)?,
};

let acceptor = tokio_rustls::TlsAcceptor::from(Arc::new(server_config));
Expand Down