Skip to content

Commit

Permalink
fix/TLS loading fix (#25)
Browse files Browse the repository at this point in the history
* Fix TLS certs loading.

If the root_ca_certificate field was an empty string, then an error was thrown.
This commit fixes the issue and also implements the functionalities already existing on Zenoh
to load an embedded certificate.

* Fix certs loading bug

* Modifying log
  • Loading branch information
DariusIMP authored Nov 28, 2023
1 parent de8f8af commit 477a0b5
Show file tree
Hide file tree
Showing 6 changed files with 128 additions and 52 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ serde_json = "1.0.94"
tokio = { version = "1.26.0", features = ["full"] }
uhlc = "0.5.2"
webpki = "0.22.0"
webpki-roots = "0.25"
zenoh = { git = "https://github.com/eclipse-zenoh/zenoh", branch = "master", features = ["unstable"] }
zenoh_backend_traits = { git = "https://github.com/eclipse-zenoh/zenoh", branch = "master" }
zenoh-buffers = { git = "https://github.com/eclipse-zenoh/zenoh", branch = "master" }
Expand Down
28 changes: 19 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,10 +85,15 @@ If successful, then the console can be accessed on http://localhost:9090.

// Optional TLS specific parameters to enable HTTPS with MinIO. Configuration shared by
// all the associated storages.
tls: {
// Certificate authority to authenticate the server.
root_ca_certificate: "/home/user/certificates/minio/ca.pem",
},
// tls: {
// private: {
// // Certificate authority to authenticate the server.
// root_ca_certificate_file: "/home/user/certificates/minio/ca.pem",
//
// // Alternatively you can inline your certificate encoded with base 64:
// root_ca_certificate_base64: "<YOUR_CERTIFICATE_ENCODED_WITH_BASE64>"
// }
//},
},
},
storages: {
Expand Down Expand Up @@ -236,16 +241,19 @@ a private key, a public certificate and a certificate authority certificate is g
└── minica.pem
```

On the config file, we need to specify the `root_ca_certificate` as this will allow the s3 plugin to validate the MinIO server keys.
On the config file, we need to specify the `root_ca_certificate_file` as this will allow the s3 plugin to validate the MinIO server keys.
Example:

```
tls: {
root_ca_certificate: "/home/user/certificates/minio/minica.pem",
private: {
root_ca_certificate_file: "/home/user/certificates/minio/minica.pem",
},
},
```

Here, the `root_ca_certificate` corresponds to the generated _minica.pem_ file.
Here, the `root_ca_certificate_file` corresponds to the generated _minica.pem_ file.
You can also embed directly the root_ca_certificate by inlining it under the filed `root_ca_certificate_base64`, encoded with base64.

The _cert.pem_ and _key.pem_ files correspond to the public certificate and private key respectively. We need to rename them as _public.crt_ and _private.key_ respectively and store them under the MinIO configuration directory (as specified in the [MinIO documentation](https://min.io/docs/minio/linux/operations/network-encryption.html#enabling-tls)). In case you are using running a docker container as previously shown, then we will need to mount the folder containing the certificates as a volume; supposing we stored our certificates under `${HOME}/minio/certs`, we need to start our container as follows:

Expand All @@ -264,9 +272,11 @@ storage_manager: {
// Configure TLS specific parameters
tls: {
root_ca_certificate: "/home/user/certificates/minio_certs/minica.pem",
private: {
root_ca_certificate_file: "/home/user/certificates/minio_certs/minica.pem",
},
},
}
},
},
```

Expand Down
89 changes: 77 additions & 12 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,9 @@ const PROP_STORAGE_ON_CLOSURE: &str = "on_closure";
const DEFAULT_PROVIDER: &str = "zenoh-s3-backend";

// TLS properties
const PROP_TLS_ROOT_CA: &str = "root_ca_certificate";
pub const TLS_PROP: &str = "tls";
pub const TLS_ROOT_CA_CERTIFICATE_FILE: &str = "root_ca_certificate_file";
pub const TLS_ROOT_CA_CERTIFICATE_BASE64: &str = "root_ca_certificate_base64";

pub enum OnClosure {
DestroyBucket,
Expand Down Expand Up @@ -252,15 +254,28 @@ impl TlsClientConfig {
pub fn new(tls_config: &Map<String, Value>) -> ZResult<Self> {
log::debug!("Loading TLS config values...");

let mut root_cert_store = RootCertStore::empty();
let root_ca_certificate = match tls_config
.get(PROP_TLS_ROOT_CA)
.ok_or_else(|| zerror!("Missing property {PROP_TLS_ROOT_CA}."))? {
serde_json::Value::String(value) => Ok(value),
_ => Err(zerror!("Property {PROP_TLS_ROOT_CA} must be of type string and point to the path of the root certificate."))
}?;
// Allows mixed user-generated CA and webPKI CA
log::debug!("Loading default Web PKI certificates.");
let mut root_cert_store: RootCertStore = RootCertStore {
roots: Self::load_default_webpki_certs().roots,
};

TlsClientConfig::load_trust_anchors(root_ca_certificate.to_string(), &mut root_cert_store)?;
if let Some(root_ca_cert_file) = get_private_conf(tls_config, TLS_ROOT_CA_CERTIFICATE_FILE)?
{
log::debug!("Loading certificate specified under {TLS_ROOT_CA_CERTIFICATE_FILE}.");
Self::load_root_ca_certificate_file_trust_anchors(
root_ca_cert_file,
&mut root_cert_store,
)?;
} else if let Some(root_ca_cert_base64) =
get_private_conf(tls_config, TLS_ROOT_CA_CERTIFICATE_BASE64)?
{
log::debug!("Loading certificate specified under {TLS_ROOT_CA_CERTIFICATE_BASE64}.");
Self::load_root_ca_certificate_base64_trust_anchors(
root_ca_cert_base64,
&mut root_cert_store,
)?;
}

let client_config = ClientConfig::builder()
.with_safe_defaults()
Expand All @@ -277,11 +292,41 @@ impl TlsClientConfig {
})
}

fn load_trust_anchors(
root_ca_certificate: String,
fn load_root_ca_certificate_file_trust_anchors(
root_ca_cert_file: &String,
root_cert_store: &mut RootCertStore,
) -> ZResult<()> {
if root_ca_cert_file.is_empty() {
log::warn!("Provided an empty value for `{TLS_ROOT_CA_CERTIFICATE_FILE}`. Ignornig...");
return Ok(());
};

let mut pem = BufReader::new(File::open(root_ca_cert_file)?);
let certs = rustls_pemfile::certs(&mut pem)?;
let trust_anchors = certs.iter().map(|cert| {
let ta = TrustAnchor::try_from_cert_der(&cert[..]).unwrap();
OwnedTrustAnchor::from_subject_spki_name_constraints(
ta.subject,
ta.spki,
ta.name_constraints,
)
});
root_cert_store.add_trust_anchors(trust_anchors.into_iter());
Ok(())
}

fn load_root_ca_certificate_base64_trust_anchors(
b64_certificate: &String,
root_cert_store: &mut RootCertStore,
) -> ZResult<()> {
let mut pem = BufReader::new(File::open(root_ca_certificate)?);
if b64_certificate.is_empty() {
log::warn!(
"Provided an empty value for `{TLS_ROOT_CA_CERTIFICATE_BASE64}`. Ignornig..."
);
return Ok(());
};
let certificate_pem = Self::base64_decode(b64_certificate.as_str())?;
let mut pem = BufReader::new(certificate_pem.as_slice());
let certs = rustls_pemfile::certs(&mut pem)?;
let trust_anchors = certs.iter().map(|cert| {
let ta = TrustAnchor::try_from_cert_der(&cert[..]).unwrap();
Expand All @@ -294,4 +339,24 @@ impl TlsClientConfig {
root_cert_store.add_trust_anchors(trust_anchors.into_iter());
Ok(())
}

fn load_default_webpki_certs() -> RootCertStore {
let mut root_cert_store = RootCertStore::empty();
root_cert_store.add_trust_anchors(webpki_roots::TLS_SERVER_ROOTS.iter().map(|ta| {
OwnedTrustAnchor::from_subject_spki_name_constraints(
ta.subject,
ta.spki,
ta.name_constraints,
)
}));
root_cert_store
}

pub fn base64_decode(data: &str) -> ZResult<Vec<u8>> {
use base64::engine::general_purpose;
use base64::Engine;
Ok(general_purpose::STANDARD
.decode(data)
.map_err(|e| zerror!("Unable to perform base64 decoding: {e:?}"))?)
}
}
11 changes: 4 additions & 7 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ use async_std::sync::Arc;
use async_trait::async_trait;

use client::S3Client;
use config::{S3Config, TlsClientConfig};
use config::{S3Config, TlsClientConfig, TLS_PROP};
use futures::future::join_all;
use futures::stream::FuturesUnordered;
use utils::S3Key;
Expand Down Expand Up @@ -49,9 +49,6 @@ pub const NONE_KEY: &str = "@@none_key@@";
// Metadata keys
pub const TIMESTAMP_METADATA_KEY: &str = "timestamp_uhlc";

// TLS properties
const PROP_TLS: &str = "tls";

// Amount of worker threads to be used by the tokio runtime of the [S3Storage] to handle incoming
// operations.
const STORAGE_WORKER_THREADS: usize = 2;
Expand Down Expand Up @@ -108,10 +105,10 @@ fn get_optional_string_property(property: &str, config: &VolumeConfig) -> ZResul
}

fn load_tls_config(config: &VolumeConfig) -> ZResult<Option<TlsClientConfig>> {
match config.rest.get(PROP_TLS) {
match config.rest.get(TLS_PROP) {
Some(serde_json::Value::Object(tls_config)) => Ok(Some(TlsClientConfig::new(tls_config)?)),
None => Ok(None),
_ => Err(zerror!("Property {PROP_TLS} is malformed.").into()),
_ => Err(zerror!("Property {TLS_PROP} is malformed.").into()),
}
}

Expand All @@ -129,7 +126,7 @@ impl Volume for S3Backend {
}

async fn create_storage(&mut self, config: StorageConfig) -> ZResult<Box<dyn Storage>> {
log::debug!("Creating storage {:?}", config);
log::debug!("Creating storage...");
let config: S3Config = S3Config::new(&config).await?;

let client = S3Client::new(
Expand Down
50 changes: 26 additions & 24 deletions zenoh.json5
Original file line number Diff line number Diff line change
Expand Up @@ -4,33 +4,35 @@
storage_manager: {
volumes: {
s3: {
// AWS region to which connect (see https://docs.aws.amazon.com/general/latest/gr/s3.html).
// This field is mandatory if you are going to communicate with an AWS S3 server and
// optional in case you are working with a MinIO S3 server.
region: "eu-west-1",

// AWS region to which connect (see https://docs.aws.amazon.com/general/latest/gr/s3.html).
// This field is mandatory if you are going to communicate with an AWS S3 server and
// optional in case you are working with a MinIO S3 server.
region: "eu-west-1",
// Endpoint where the S3 server is located.
// This parameter allows you to specify a custom endpoint when working with a MinIO S3
// server.
// This field is mandatory if you are working with a MinIO server and optional in case
// you are working with an AWS S3 server as long as you specified the region, in which
// case the endpoint will be resolved automatically.
url: "https://s3.eu-west-1.amazonaws.com",

// Endpoint where the S3 server is located.
// This parameter allows you to specify a custom endpoint when working with a MinIO S3
// server.
// This field is mandatory if you are working with a MinIO server and optional in case
// you are working with an AWS S3 server as long as you specified the region, in which
// case the endpoint will be resolved automatically.
url: "https://s3.eu-west-1.amazonaws.com",

// Optional TLS specific parameters to enable HTTPS with MinIO. Configuration shared by
// all the associated storages.
tls: {
// Optional TLS specific parameters to enable HTTPS with MINIO.
// Configuration shared by all the associated storages.
tls: {
private: {
// Certificate authority to authenticate the server.
root_ca_certificate: "./certificates/minio/ca.pem",
root_ca_certificate_file: "<YOUR_CERTIFICATE_PATH>",
// Alternatively you can inline your certificate encoded with base 64:
root_ca_certificate_base64: "<YOUR_CERTIFICATE_ENCODED_WITH_BASE64>",
},
}
},
},
},
storages: {
// Configuration of a "demo" storage using the S3 volume. Each storage is associated to a
// single S3 bucket.
s3_storage: {

// The key expression this storage will subscribes to
key_expr: "s3/example/*",

Expand All @@ -57,15 +59,15 @@
on_closure: "destroy_bucket",

private: {
// Credentials for interacting with the S3 bucket
access_key: "AKIAIOSFODNN7EXAMPLE",
secret_key: "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY",
// Credentials for interacting with the S3 bucket
access_key: "AKIAIOSFODNN7EXAMPLE",
secret_key: "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY",
},
}
},
},
}
},
},
// Optionally, add the REST plugin
rest: { http_port: 8000 }
rest: { http_port: 8000 },
},
}

0 comments on commit 477a0b5

Please sign in to comment.