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

Allows configuring TLS backend #386

Open
wants to merge 4 commits into
base: main
Choose a base branch
from

Conversation

erickguan
Copy link

Fixes #384.

@dbrgn
Copy link
Collaborator

dbrgn commented Oct 27, 2024

Hm, I wonder if we shouldn't just switch to native roots and call it a day... What's your opinion @niklasmohrin?

@erickguan
Copy link
Author

FYI, the default build uses the native roots.

I didn't change the build default in this PR. I can see some adjustments required and your inputs.

@niklasmohrin
Copy link
Collaborator

@dbrgn I have some thoughts written down in the issue, I think that having the functionality is nice and people can still build it without the bloat by disabling features

Copy link
Collaborator

@niklasmohrin niklasmohrin left a comment

Choose a reason for hiding this comment

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

Thanks! I have some requests regarding the code first, once we have this ironed out I will take a closer look at the documentation. Please feel free to reach out if you have any questions or thoughts :)

Cargo.toml Outdated
@@ -25,7 +25,7 @@ app_dirs = { version = "2", package = "app_dirs2" }
clap = { version = "4", features = ["std", "derive", "help", "usage", "cargo", "error-context", "color", "wrap_help"], default-features = false }
env_logger = { version = "0.11", optional = true }
log = "0.4"
reqwest = { version = "0.12.5", features = ["blocking"], default-features = false }
reqwest = { version = "0.12.5", features = ["blocking", "native-tls", "rustls-tls", "rustls-tls-native-roots", "rustls-tls-webpki-roots"], default-features = false }
Copy link
Collaborator

Choose a reason for hiding this comment

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

I had something else in mind: By disabling the native-roots (etc.) features of tealdeer, we would also disable the features for reqwest here, so that the resulting tealdeer binary is as small as possible.

Copy link
Author

Choose a reason for hiding this comment

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

I see. Fixed. I saw the reqwest 0.12.9 supports additional roots. So I also extended it. What do you think about it?

src/cache.rs Outdated Show resolved Hide resolved
src/config.rs Outdated Show resolved Hide resolved
src/cache.rs Outdated Show resolved Hide resolved
Copy link
Collaborator

@niklasmohrin niklasmohrin left a comment

Choose a reason for hiding this comment

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

I finally found some time to take another look :)

src/cache.rs Outdated Show resolved Hide resolved
src/cache.rs Outdated
Comment on lines 145 to 158
builder = match tls_backend {
#[cfg(feature = "native-tls")]
TlsBackend::NativeTLS => builder.use_native_tls(),
#[cfg(feature = "native-tls-with-webpki-roots")]
TlsBackend::NativeTLSWithWebPKIRoots => {
builder.use_native_tls().tls_built_in_webpki_certs(true)
}
#[cfg(feature = "rustls")]
TlsBackend::Rustls => builder.use_native_tls().tls_built_in_webpki_certs(true),
#[cfg(feature = "rustls-with-native-roots")]
TlsBackend::RustlsWithNativeRoots => {
builder.use_native_tls().tls_built_in_native_certs(true)
}
};
Copy link
Collaborator

Choose a reason for hiding this comment

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

General structure is correct, but the content of the match arms is not quite right. In the Rustls* cases, we shouldn't call use_native_tls, but use_rustls_tls.

Then, there are three methods related to to TLS certificates available: tls_built_in_root_certs, tls_built_in_webpki_certs, and tls_built_in_native_certs. If I understand the description correctly, the first one applies to both native-tls and rustls, and the latter two only apply to rustls. To be sure, I would call all three of the methods in each case of our match and pass true or false accordingly. Otherwise, we might use more certificates than we want (the default is true for some of them)

Copy link
Author

Choose a reason for hiding this comment

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

Good catch on Rustls arm! My mistake.

I include 3 match arms:

  • TlsBackend::NativeTLS
  • TlsBackend::Rustls: reqwest uses Rustls with WebPKI as default. So I reflect that in the names.
  • TlsBackend::RustlsWithNativeRoots

src/cache.rs Outdated
Comment on lines 529 to 549
macro_rules! https_client_tests {
// Define each test with an optional cfg attribute for conditional compilation
($(
$(#[$cfg:meta])? $name:ident: $backend:expr
),* $(,)?) => {
$(
$( #[$cfg] )?
#[test]
fn $name() {
let dir = tempfile::tempdir().unwrap();

let _ = Cache::build_client(&Cache {
cache_dir: dir.into_path(),
enable_styles: false,
tls_backend: $backend,
}, $backend).context("Expect built the client.");

// intentionally empty, assumes we have built the client.
}
)*
};
Copy link
Collaborator

Choose a reason for hiding this comment

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

I would just remove the macro and write three tests. They should also indicate that we are just testing that the client can be created, something like test_create_https_client_with_native_tls or so.

Finally, just calling .context on a Result will not check it - we need to use .expect or the ? operator

Copy link
Author

Choose a reason for hiding this comment

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

Make sense.

Yeah, .context is my mistake. It should have been .expect

src/config.rs Outdated Show resolved Hide resolved
src/config.rs Outdated Show resolved Hide resolved
src/config.rs Outdated Show resolved Hide resolved
src/cache.rs Outdated Show resolved Hide resolved
src/cache.rs Outdated Show resolved Hide resolved
Cargo.toml Outdated
native-tls = ["reqwest/native-tls"]
native-tls-with-webpki-roots = ["reqwest/native-tls", "reqwest/rustls-tls-webpki-roots-no-provider"]
Copy link
Collaborator

Choose a reason for hiding this comment

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

I don't think this works as intended. If I understand correctly, this still just uses the native tls with whatever certificates this has configured. Let's not add a new option in this PR, if this really works, we can still do it in a separate discussion :)

Copy link
Author

Choose a reason for hiding this comment

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

Hmm, my understanding is reqwest will be able to use WebPKI roots with this feature.

Removed.

Cargo.toml Outdated Show resolved Hide resolved
Copy link
Author

@erickguan erickguan left a comment

Choose a reason for hiding this comment

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

Thanks for the review!

I implemented tls_backend as a somewhat special config to bubble up the config conversion error as your comment.

Please have a look. If everything works well, I will update the documentation.

Cargo.toml Outdated Show resolved Hide resolved
Cargo.toml Outdated
native-tls = ["reqwest/native-tls"]
native-tls-with-webpki-roots = ["reqwest/native-tls", "reqwest/rustls-tls-webpki-roots-no-provider"]
Copy link
Author

Choose a reason for hiding this comment

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

Hmm, my understanding is reqwest will be able to use WebPKI roots with this feature.

Removed.

src/cache.rs Outdated Show resolved Hide resolved
src/cache.rs Outdated Show resolved Hide resolved
src/cache.rs Outdated
Comment on lines 145 to 158
builder = match tls_backend {
#[cfg(feature = "native-tls")]
TlsBackend::NativeTLS => builder.use_native_tls(),
#[cfg(feature = "native-tls-with-webpki-roots")]
TlsBackend::NativeTLSWithWebPKIRoots => {
builder.use_native_tls().tls_built_in_webpki_certs(true)
}
#[cfg(feature = "rustls")]
TlsBackend::Rustls => builder.use_native_tls().tls_built_in_webpki_certs(true),
#[cfg(feature = "rustls-with-native-roots")]
TlsBackend::RustlsWithNativeRoots => {
builder.use_native_tls().tls_built_in_native_certs(true)
}
};
Copy link
Author

Choose a reason for hiding this comment

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

Good catch on Rustls arm! My mistake.

I include 3 match arms:

  • TlsBackend::NativeTLS
  • TlsBackend::Rustls: reqwest uses Rustls with WebPKI as default. So I reflect that in the names.
  • TlsBackend::RustlsWithNativeRoots

src/cache.rs Outdated Show resolved Hide resolved
src/cache.rs Outdated
Comment on lines 529 to 549
macro_rules! https_client_tests {
// Define each test with an optional cfg attribute for conditional compilation
($(
$(#[$cfg:meta])? $name:ident: $backend:expr
),* $(,)?) => {
$(
$( #[$cfg] )?
#[test]
fn $name() {
let dir = tempfile::tempdir().unwrap();

let _ = Cache::build_client(&Cache {
cache_dir: dir.into_path(),
enable_styles: false,
tls_backend: $backend,
}, $backend).context("Expect built the client.");

// intentionally empty, assumes we have built the client.
}
)*
};
Copy link
Author

Choose a reason for hiding this comment

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

Make sense.

Yeah, .context is my mistake. It should have been .expect

src/config.rs Outdated Show resolved Hide resolved
src/config.rs Outdated Show resolved Hide resolved
src/config.rs Outdated Show resolved Hide resolved
} else {
0
};
const SUPPORTED_TLS_BACKENDS: [RawTlsBackend; SUPPORTED_NUM_TLS_BACKENDS] = [
Copy link
Collaborator

Choose a reason for hiding this comment

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

We can avoid the calculation of SUPPORTED_NUM_TLS_BACKENDS if we use the following:

Suggested change
const SUPPORTED_TLS_BACKENDS: [RawTlsBackend; SUPPORTED_NUM_TLS_BACKENDS] = [
const SUPPORTED_TLS_BACKENDS: &[RawTlsBackend] = &[

Comment on lines +140 to +142
/// Builds HTTPS client based on configuration.
///
/// Note that `Cargo.toml` also defines default feature .
Copy link
Collaborator

Choose a reason for hiding this comment

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

I think this is not needed, the function signature speaks for itself :)

Suggested change
/// Builds HTTPS client based on configuration.
///
/// Note that `Cargo.toml` also defines default feature .

Comment on lines +537 to +540
assert!(
Cache::build_client(TlsBackend::NativeTls).is_ok(),
"fails to build a client."
);
Copy link
Collaborator

Choose a reason for hiding this comment

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

Nit: All three tests here can just use Cache::build_client(...).expect("Failed to build client") and not use assert! at all. Both will have the behavior regarding pass or fail, but the expect version will also print where the error in build_client came from

Comment on lines +543 to +544
#[test]
fn test_create_https_client_with_rustls() {
Copy link
Collaborator

Choose a reason for hiding this comment

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

These tests don't compile unless the respective features are activated, for example running cargo test --no-default-features -F rustls-with-native-roots will give a compile error on this test here. The tests should thus also have a cfg(...) attribute

@@ -282,11 +300,52 @@ pub struct DirectoriesConfig {
pub custom_pages_dir: Option<PathWithSource>,
}

#[derive(Copy, Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
pub enum RawTlsBackend {
Copy link
Collaborator

Choose a reason for hiding this comment

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

This still needs the following to allow the correct strings in the config file:

Suggested change
pub enum RawTlsBackend {
#[serde(rename_all = "kebab-case")]
pub enum RawTlsBackend {

Comment on lines +321 to +325
match self {
RawTlsBackend::NativeTls => write!(f, "native-tls"),
RawTlsBackend::Rustls => write!(f, "rustls"),
RawTlsBackend::RustlsWithNativeRoots => write!(f, "rustls-with-native-roots"),
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

If we use serde::Serialize as _; at the top of the file, we can actually reuse the renaming scheme from serde here:

Suggested change
match self {
RawTlsBackend::NativeTls => write!(f, "native-tls"),
RawTlsBackend::Rustls => write!(f, "rustls"),
RawTlsBackend::RustlsWithNativeRoots => write!(f, "rustls-with-native-roots"),
}
self.serialize(f)

Comment on lines +307 to +310
/// Rustls with WebPKI roots.
Rustls,
/// Rustls with native roots.
RustlsWithNativeRoots,
Copy link
Collaborator

Choose a reason for hiding this comment

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

I feel that both options should be called rustls-with-something for consistency. The first option would then be RustlsWithWebpkiRoots

Comment on lines +401 to +404
"Unsupported TLS backend: {}. This tealdeer build has support for the following options: {}",
raw_config.updates.tls_backend,
SUPPORTED_TLS_BACKENDS.map(|b| b.to_string()).join(", ")
))
Copy link
Collaborator

@niklasmohrin niklasmohrin Dec 31, 2024

Choose a reason for hiding this comment

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

Very nice error message! Let's add a test that checks the output if this case occurs. To do so, you can use for example let testenv = TestEnv::new().no_default_features().with_feature("rustls"); in our integration tests at tests/lib.rs.

I discovered that our implementation of TestEnv::command is not correct at the moment, sorry about that. You should change line 123 of tests/lib.rs
edit: I am fixing this in #399

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

Configure TLS at runtime
3 participants