-
Notifications
You must be signed in to change notification settings - Fork 128
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
base: main
Are you sure you want to change the base?
Conversation
Hm, I wonder if we shouldn't just switch to native roots and call it a day... What's your opinion @niklasmohrin? |
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. |
@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 |
There was a problem hiding this 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 } |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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?
There was a problem hiding this 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
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) | ||
} | ||
}; |
There was a problem hiding this comment.
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)
There was a problem hiding this comment.
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
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. | ||
} | ||
)* | ||
}; |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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
Cargo.toml
Outdated
native-tls = ["reqwest/native-tls"] | ||
native-tls-with-webpki-roots = ["reqwest/native-tls", "reqwest/rustls-tls-webpki-roots-no-provider"] |
There was a problem hiding this comment.
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 :)
There was a problem hiding this comment.
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.
There was a problem hiding this 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
native-tls = ["reqwest/native-tls"] | ||
native-tls-with-webpki-roots = ["reqwest/native-tls", "reqwest/rustls-tls-webpki-roots-no-provider"] |
There was a problem hiding this comment.
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
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) | ||
} | ||
}; |
There was a problem hiding this comment.
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
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. | ||
} | ||
)* | ||
}; |
There was a problem hiding this comment.
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
} else { | ||
0 | ||
}; | ||
const SUPPORTED_TLS_BACKENDS: [RawTlsBackend; SUPPORTED_NUM_TLS_BACKENDS] = [ |
There was a problem hiding this comment.
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:
const SUPPORTED_TLS_BACKENDS: [RawTlsBackend; SUPPORTED_NUM_TLS_BACKENDS] = [ | |
const SUPPORTED_TLS_BACKENDS: &[RawTlsBackend] = &[ |
/// Builds HTTPS client based on configuration. | ||
/// | ||
/// Note that `Cargo.toml` also defines default feature . |
There was a problem hiding this comment.
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 :)
/// Builds HTTPS client based on configuration. | |
/// | |
/// Note that `Cargo.toml` also defines default feature . |
assert!( | ||
Cache::build_client(TlsBackend::NativeTls).is_ok(), | ||
"fails to build a client." | ||
); |
There was a problem hiding this comment.
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
#[test] | ||
fn test_create_https_client_with_rustls() { |
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
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:
pub enum RawTlsBackend { | |
#[serde(rename_all = "kebab-case")] | |
pub enum RawTlsBackend { |
match self { | ||
RawTlsBackend::NativeTls => write!(f, "native-tls"), | ||
RawTlsBackend::Rustls => write!(f, "rustls"), | ||
RawTlsBackend::RustlsWithNativeRoots => write!(f, "rustls-with-native-roots"), | ||
} |
There was a problem hiding this comment.
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:
match self { | |
RawTlsBackend::NativeTls => write!(f, "native-tls"), | |
RawTlsBackend::Rustls => write!(f, "rustls"), | |
RawTlsBackend::RustlsWithNativeRoots => write!(f, "rustls-with-native-roots"), | |
} | |
self.serialize(f) |
/// Rustls with WebPKI roots. | ||
Rustls, | ||
/// Rustls with native roots. | ||
RustlsWithNativeRoots, |
There was a problem hiding this comment.
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
"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(", ") | ||
)) |
There was a problem hiding this comment.
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
Fixes #384.