mirror of
https://github.com/tealdeer-rs/tealdeer.git
synced 2026-08-21 23:54:18 +02:00
Allow configuring TLS backend (#386)
This commit is contained in:
parent
120e2a92c2
commit
09ef7f534e
9 changed files with 228 additions and 47 deletions
7
.github/workflows/ci.yml
vendored
7
.github/workflows/ci.yml
vendored
|
|
@ -22,8 +22,11 @@ jobs:
|
|||
toolchain: ${{ matrix.toolchain }}
|
||||
- name: Build with default features
|
||||
run: cargo build
|
||||
- name: Build with logging and webpki roots
|
||||
run: cargo build --features logging,webpki-roots --no-default-features
|
||||
- name: Build with logging and Rustls with webpki roots
|
||||
run: cargo build --features logging,rustls-with-webpki-roots --no-default-features
|
||||
- name: Build with native TLS backend
|
||||
# expects runners have the proper Native SSL library
|
||||
run: cargo build --features native-tls --no-default-features
|
||||
- name: Run tests
|
||||
run: cargo test -- --test-threads 1
|
||||
|
||||
|
|
|
|||
17
Cargo.lock
generated
17
Cargo.lock
generated
|
|
@ -201,6 +201,12 @@ version = "1.0.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "cfg_aliases"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.5.19"
|
||||
|
|
@ -1153,15 +1159,16 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "quinn-udp"
|
||||
version = "0.5.5"
|
||||
version = "0.5.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4fe68c2e9e1a1234e218683dbdf9f9dfcb094113c5ac2b938dfcb9bab4c4140b"
|
||||
checksum = "7d5a626c6807713b15cac82a6acaccd6043c9a5408c24baae07611fec3f243da"
|
||||
dependencies = [
|
||||
"cfg_aliases",
|
||||
"libc",
|
||||
"once_cell",
|
||||
"socket2",
|
||||
"tracing",
|
||||
"windows-sys 0.59.0",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -1243,9 +1250,9 @@ checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
|
|||
|
||||
[[package]]
|
||||
name = "reqwest"
|
||||
version = "0.12.8"
|
||||
version = "0.12.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f713147fbe92361e52392c73b8c9e48c04c6625bce969ef54dc901e58e042a7b"
|
||||
checksum = "a77c62af46e79de0a562e1a9849205ffcb7fc1238876e9bd743357570e04046f"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"bytes",
|
||||
|
|
|
|||
24
Cargo.toml
24
Cargo.toml
|
|
@ -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.9", features = ["blocking"], default-features = false }
|
||||
serde = "1.0.21"
|
||||
serde_derive = "1.0.21"
|
||||
toml = "0.8.19"
|
||||
|
|
@ -44,21 +44,23 @@ tempfile = "3.1.0"
|
|||
filetime = "0.2.10"
|
||||
|
||||
[features]
|
||||
default = ["native-roots"]
|
||||
default = ["native-tls", "rustls-with-webpki-roots", "rustls-with-native-roots"]
|
||||
logging = ["env_logger"]
|
||||
|
||||
# Reqwest (the HTTP client library) can handle TLS connections in three
|
||||
# Reqwest (the HTTP client library) can handle TLS connections in four
|
||||
# different modes:
|
||||
#
|
||||
# - Rustls with native roots
|
||||
# - Rustls with WebPK roots
|
||||
# - Native TLS (SChannel on Windows, Secure Transport on macOS and OpenSSL otherwise)
|
||||
# - Rustls with:
|
||||
# - native roots
|
||||
# - WebPK roots
|
||||
# - Native TLS (SChannel on Windows, Secure Transport on macOS and OpenSSL otherwise) with:
|
||||
# - native roots
|
||||
# - WebPK roots (not implemented in tealdeer)
|
||||
#
|
||||
# Exactly one of the three variants must be selected. By default, Rustls with
|
||||
# native roots is enabled.
|
||||
native-roots = ["reqwest/rustls-tls-native-roots"]
|
||||
webpki-roots = ["reqwest/rustls-tls-webpki-roots"]
|
||||
native-tls = ["reqwest/native-tls"]
|
||||
# At least one of variants must be selected. By default, uses native TLS and native roots.
|
||||
native-tls = ["reqwest/rustls-tls-native-roots-no-provider", "reqwest/rustls-tls-webpki-roots-no-provider", "reqwest/native-tls"]
|
||||
rustls-with-webpki-roots = ["reqwest/rustls-tls-native-roots-no-provider", "reqwest/rustls-tls-webpki-roots"]
|
||||
rustls-with-native-roots = ["reqwest/rustls-tls-webpki-roots-no-provider", "reqwest/rustls-tls-native-roots"]
|
||||
|
||||
ignore-online-tests = []
|
||||
|
||||
|
|
|
|||
|
|
@ -32,3 +32,21 @@ fetched from the latest `tldr-pages/tldr` GitHub release.
|
|||
[updates]
|
||||
archive_source = https://my-company.example.com/tldr/
|
||||
|
||||
### `tls_backend`
|
||||
|
||||
Specifies which TLS backend to use. Try changing this setting if you encounter certificate errors.
|
||||
|
||||
Available options:
|
||||
- `rustls-with-native-roots` - [Rustls][rustls] (a TLS library in Rust) with native roots
|
||||
- `rustls-with-webpki-roots` - Rustls with [WebPKI][rustls-webpki] roots
|
||||
- `native-tls` - Native TLS
|
||||
- SChannel on Windows
|
||||
- Secure Transport on macOS
|
||||
- OpenSSL on other platforms
|
||||
|
||||
[updates]
|
||||
tls_backend = "native-tls"
|
||||
|
||||
|
||||
[rustls]: https://github.com/rustls/rustls
|
||||
[rustls-webpki]: https://github.com/rustls/webpki
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@ Release build:
|
|||
|
||||
Release build with bundled CA roots:
|
||||
|
||||
$ cargo build --release --no-default-features --features webpki-roots
|
||||
$ cargo build --release --no-default-features --features rustls-with-webpki-roots
|
||||
|
||||
Debug build with logging support:
|
||||
|
||||
|
|
|
|||
60
src/cache.rs
60
src/cache.rs
|
|
@ -13,7 +13,7 @@ use reqwest::{blocking::Client, Proxy};
|
|||
use walkdir::{DirEntry, WalkDir};
|
||||
use zip::ZipArchive;
|
||||
|
||||
use crate::{types::PlatformType, utils::print_warning};
|
||||
use crate::{config::TlsBackend, types::PlatformType, utils::print_warning};
|
||||
|
||||
pub static TLDR_PAGES_DIR: &str = "tldr-pages";
|
||||
static TLDR_OLD_PAGES_DIR: &str = "tldr-master";
|
||||
|
|
@ -22,6 +22,7 @@ static TLDR_OLD_PAGES_DIR: &str = "tldr-master";
|
|||
pub struct Cache {
|
||||
cache_dir: PathBuf,
|
||||
enable_styles: bool,
|
||||
tls_backend: TlsBackend,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
|
|
@ -86,13 +87,14 @@ pub enum CacheFreshness {
|
|||
}
|
||||
|
||||
impl Cache {
|
||||
pub fn new<P>(cache_dir: P, enable_styles: bool) -> Self
|
||||
pub fn new<P>(cache_dir: P, enable_styles: bool, tls_backend: TlsBackend) -> Self
|
||||
where
|
||||
P: Into<PathBuf>,
|
||||
{
|
||||
Self {
|
||||
cache_dir: cache_dir.into(),
|
||||
enable_styles,
|
||||
tls_backend,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -135,9 +137,28 @@ impl Cache {
|
|||
self.cache_dir.join(TLDR_PAGES_DIR)
|
||||
}
|
||||
|
||||
/// Download the archive from the specified URL.
|
||||
fn download(archive_url: &str) -> Result<Vec<u8>> {
|
||||
fn build_client(tls_backend: TlsBackend) -> Result<Client> {
|
||||
let mut builder = Client::builder();
|
||||
builder = match tls_backend {
|
||||
#[cfg(feature = "native-tls")]
|
||||
TlsBackend::NativeTls => builder
|
||||
.use_native_tls()
|
||||
.tls_built_in_root_certs(true)
|
||||
.tls_built_in_webpki_certs(false)
|
||||
.tls_built_in_native_certs(false),
|
||||
#[cfg(feature = "rustls-with-webpki-roots")]
|
||||
TlsBackend::RustlsWithWebpkiRoots => builder
|
||||
.use_rustls_tls()
|
||||
.tls_built_in_root_certs(false)
|
||||
.tls_built_in_webpki_certs(true)
|
||||
.tls_built_in_native_certs(false),
|
||||
#[cfg(feature = "rustls-with-native-roots")]
|
||||
TlsBackend::RustlsWithNativeRoots => builder
|
||||
.use_rustls_tls()
|
||||
.tls_built_in_root_certs(false)
|
||||
.tls_built_in_webpki_certs(false)
|
||||
.tls_built_in_native_certs(true),
|
||||
};
|
||||
if let Ok(ref host) = env::var("HTTP_PROXY") {
|
||||
if let Ok(proxy) = Proxy::http(host) {
|
||||
builder = builder.proxy(proxy);
|
||||
|
|
@ -148,9 +169,11 @@ impl Cache {
|
|||
builder = builder.proxy(proxy);
|
||||
}
|
||||
}
|
||||
let client = builder
|
||||
.build()
|
||||
.context("Could not instantiate HTTP client")?;
|
||||
builder.build().context("Could not instantiate HTTP client")
|
||||
}
|
||||
|
||||
/// Download the archive from the specified URL.
|
||||
fn download(client: &Client, archive_url: &str) -> Result<Vec<u8>> {
|
||||
let mut resp = client
|
||||
.get(archive_url)
|
||||
.send()?
|
||||
|
|
@ -166,10 +189,11 @@ impl Cache {
|
|||
pub fn update(&self, archive_source: &str) -> Result<()> {
|
||||
self.ensure_cache_dir_exists()?;
|
||||
|
||||
let archive_url = format!("{}/tldr.zip", archive_source);
|
||||
let archive_url = format!("{archive_source}/tldr.zip");
|
||||
|
||||
let client = Self::build_client(self.tls_backend)?;
|
||||
// First, download the compressed data
|
||||
let bytes: Vec<u8> = Self::download(&archive_url)?;
|
||||
let bytes: Vec<u8> = Self::download(&client, &archive_url)?;
|
||||
|
||||
// Decompress the response body into an `Archive`
|
||||
let mut archive = ZipArchive::new(Cursor::new(bytes))
|
||||
|
|
@ -505,4 +529,22 @@ mod tests {
|
|||
|
||||
assert_eq!(&buf, b"Hello\n");
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "native-tls")]
|
||||
fn test_create_https_client_with_native_tls() {
|
||||
Cache::build_client(TlsBackend::NativeTls).expect("fails to build a client.");
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "rustls-with-webpki-roots")]
|
||||
fn test_create_https_client_with_rustls() {
|
||||
Cache::build_client(TlsBackend::RustlsWithWebpkiRoots).expect("fails to build a client.");
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "rustls-with-native-roots")]
|
||||
fn test_create_https_client_with_rustls_with_native_roots() {
|
||||
Cache::build_client(TlsBackend::RustlsWithNativeRoots).expect("fails to build a client.");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,9 +5,10 @@ use std::{
|
|||
time::Duration,
|
||||
};
|
||||
|
||||
use anyhow::{bail, ensure, Context, Result};
|
||||
use anyhow::{anyhow, bail, ensure, Context, Result};
|
||||
use app_dirs::{get_app_root, AppDataType};
|
||||
use log::debug;
|
||||
use serde::Serialize as _;
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use yansi::{Color, Style};
|
||||
|
||||
|
|
@ -16,6 +17,14 @@ use crate::types::PathSource;
|
|||
pub const CONFIG_FILE_NAME: &str = "config.toml";
|
||||
pub const MAX_CACHE_AGE: Duration = Duration::from_secs(2_592_000); // 30 days
|
||||
const DEFAULT_UPDATE_INTERVAL_HOURS: u64 = MAX_CACHE_AGE.as_secs() / 3600; // 30 days
|
||||
const SUPPORTED_TLS_BACKENDS: &[RawTlsBackend] = &[
|
||||
#[cfg(feature = "native-tls")]
|
||||
RawTlsBackend::NativeTls,
|
||||
#[cfg(feature = "rustls-with-webpki-roots")]
|
||||
RawTlsBackend::RustlsWithWebpkiRoots,
|
||||
#[cfg(feature = "rustls-with-native-roots")]
|
||||
RawTlsBackend::RustlsWithNativeRoots,
|
||||
];
|
||||
|
||||
fn default_underline() -> bool {
|
||||
false
|
||||
|
|
@ -178,6 +187,8 @@ struct RawUpdatesConfig {
|
|||
pub auto_update_interval_hours: u64,
|
||||
#[serde(default = "default_archive_source")]
|
||||
pub archive_source: String,
|
||||
#[serde(default)]
|
||||
pub tls_backend: RawTlsBackend,
|
||||
}
|
||||
|
||||
impl Default for RawUpdatesConfig {
|
||||
|
|
@ -186,19 +197,39 @@ impl Default for RawUpdatesConfig {
|
|||
auto_update: false,
|
||||
auto_update_interval_hours: DEFAULT_UPDATE_INTERVAL_HOURS,
|
||||
archive_source: default_archive_source(),
|
||||
tls_backend: RawTlsBackend::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<RawUpdatesConfig> for UpdatesConfig {
|
||||
fn from(raw_updates_config: RawUpdatesConfig) -> Self {
|
||||
Self {
|
||||
impl TryFrom<RawUpdatesConfig> for UpdatesConfig {
|
||||
type Error = anyhow::Error;
|
||||
|
||||
fn try_from(raw_updates_config: RawUpdatesConfig) -> Result<Self> {
|
||||
let tls_backend = match raw_updates_config.tls_backend {
|
||||
#[cfg(feature = "native-tls")]
|
||||
RawTlsBackend::NativeTls => TlsBackend::NativeTls,
|
||||
#[cfg(feature = "rustls-with-webpki-roots")]
|
||||
RawTlsBackend::RustlsWithWebpkiRoots => TlsBackend::RustlsWithWebpkiRoots,
|
||||
#[cfg(feature = "rustls-with-native-roots")]
|
||||
RawTlsBackend::RustlsWithNativeRoots => TlsBackend::RustlsWithNativeRoots,
|
||||
// when compiling without all TLS backend features, we want to handle config error.
|
||||
#[allow(unreachable_patterns)]
|
||||
_ => return Err(anyhow!(
|
||||
"Unsupported TLS backend: {}. This tealdeer build has support for the following options: {}",
|
||||
raw_updates_config.tls_backend,
|
||||
SUPPORTED_TLS_BACKENDS.iter().map(std::string::ToString::to_string).collect::<Vec<String>>().join(", ")
|
||||
))
|
||||
};
|
||||
|
||||
Ok(Self {
|
||||
auto_update: raw_updates_config.auto_update,
|
||||
auto_update_interval: Duration::from_secs(
|
||||
raw_updates_config.auto_update_interval_hours * 3600,
|
||||
),
|
||||
archive_source: raw_updates_config.archive_source,
|
||||
}
|
||||
tls_backend,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -265,6 +296,7 @@ pub struct UpdatesConfig {
|
|||
pub auto_update: bool,
|
||||
pub auto_update_interval: Duration,
|
||||
pub archive_source: String,
|
||||
pub tls_backend: TlsBackend,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
|
|
@ -291,6 +323,43 @@ pub struct DirectoriesConfig {
|
|||
pub custom_pages_dir: Option<PathWithSource>,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
pub enum RawTlsBackend {
|
||||
/// Native TLS (`SChannel` on Windows, Secure Transport on macOS and OpenSSL otherwise)
|
||||
NativeTls,
|
||||
/// Rustls with `WebPKI` roots.
|
||||
RustlsWithWebpkiRoots,
|
||||
/// Rustls with native roots.
|
||||
RustlsWithNativeRoots,
|
||||
}
|
||||
|
||||
impl Default for RawTlsBackend {
|
||||
fn default() -> Self {
|
||||
*SUPPORTED_TLS_BACKENDS.first().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for RawTlsBackend {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
self.serialize(f)
|
||||
}
|
||||
}
|
||||
|
||||
/// Allows choosing a `reqwest`'s TLS backend. Available TLS backends:
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
pub enum TlsBackend {
|
||||
/// Native TLS (`SChannel` on Windows, Secure Transport on macOS and OpenSSL otherwise)
|
||||
#[cfg(feature = "native-tls")]
|
||||
NativeTls,
|
||||
/// Rustls with `WebPKI` roots.
|
||||
#[cfg(feature = "rustls-with-webpki-roots")]
|
||||
RustlsWithWebpkiRoots,
|
||||
/// Rustls with native roots.
|
||||
#[cfg(feature = "rustls-with-native-roots")]
|
||||
RustlsWithNativeRoots,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct Config {
|
||||
pub style: StyleConfig,
|
||||
|
|
@ -307,7 +376,7 @@ impl Config {
|
|||
fn from_raw(raw_config: RawConfig, relative_path_root: &Path) -> Result<Self> {
|
||||
let style = raw_config.style.into();
|
||||
let display = raw_config.display.into();
|
||||
let updates = raw_config.updates.into();
|
||||
let updates = raw_config.updates.try_into()?;
|
||||
|
||||
// Determine directories config. For this, we need to take some
|
||||
// additional factory into account, like env variables, or the
|
||||
|
|
|
|||
23
src/main.rs
23
src/main.rs
|
|
@ -16,18 +16,13 @@
|
|||
#![allow(clippy::struct_excessive_bools)]
|
||||
#![allow(clippy::too_many_lines)]
|
||||
|
||||
#[cfg(any(
|
||||
all(feature = "native-roots", feature = "webpki-roots"),
|
||||
all(feature = "native-roots", feature = "native-tls"),
|
||||
all(feature = "webpki-roots", feature = "native-tls"),
|
||||
not(any(
|
||||
feature = "native-roots",
|
||||
feature = "webpki-roots",
|
||||
feature = "native-tls"
|
||||
)),
|
||||
))]
|
||||
#[cfg(not(any(
|
||||
feature = "native-tls",
|
||||
feature = "rustls-with-webpki-roots",
|
||||
feature = "rustls-with-native-roots",
|
||||
)))]
|
||||
compile_error!(
|
||||
"exactly one of the features \"native-roots\", \"webpki-roots\" or \"native-tls\" must be enabled"
|
||||
"at least one of the features \"native-tls\", \"rustls-with-webpki-roots\" or \"rustls-with-native-roots\" must be enabled"
|
||||
);
|
||||
|
||||
use std::{
|
||||
|
|
@ -295,7 +290,11 @@ fn main() {
|
|||
}
|
||||
|
||||
// Instantiate cache. This will not yet create the cache directory!
|
||||
let cache = Cache::new(&config.directories.cache_dir.path, enable_styles);
|
||||
let cache = Cache::new(
|
||||
&config.directories.cache_dir.path,
|
||||
enable_styles,
|
||||
config.updates.tls_backend,
|
||||
);
|
||||
|
||||
// Clear cache, pass through
|
||||
if args.clear_cache {
|
||||
|
|
|
|||
43
tests/lib.rs
43
tests/lib.rs
|
|
@ -220,7 +220,31 @@ fn test_update_cache_default_features() {
|
|||
fn test_update_cache_rustls_webpki() {
|
||||
let testenv = TestEnv::new()
|
||||
.no_default_features()
|
||||
.with_feature("webpki-roots");
|
||||
.with_feature("rustls-with-webpki-roots");
|
||||
|
||||
testenv
|
||||
.command()
|
||||
.args(["sl"])
|
||||
.assert()
|
||||
.failure()
|
||||
.stderr(contains("Page cache not found. Please run `tldr --update`"));
|
||||
|
||||
testenv
|
||||
.command()
|
||||
.args(["--update"])
|
||||
.assert()
|
||||
.success()
|
||||
.stderr(contains("Successfully updated cache."));
|
||||
|
||||
testenv.command().args(["sl"]).assert().success();
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "ignore-online-tests", ignore = "online test")]
|
||||
#[test]
|
||||
fn test_update_cache_native_tls() {
|
||||
let testenv = TestEnv::new()
|
||||
.no_default_features()
|
||||
.with_feature("rustls-with-native-roots");
|
||||
|
||||
testenv
|
||||
.command()
|
||||
|
|
@ -258,6 +282,23 @@ fn test_quiet_cache() {
|
|||
.stdout(is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_warn_invalid_tls_backend() {
|
||||
let testenv = TestEnv::new()
|
||||
.no_default_features()
|
||||
.with_feature("rustls-with-webpki-roots")
|
||||
.remove_initial_config();
|
||||
|
||||
testenv.append_to_config("updates.tls_backend = 'invalid-tls-backend'\n");
|
||||
|
||||
testenv
|
||||
.command()
|
||||
.args(["sl"])
|
||||
.assert()
|
||||
.failure()
|
||||
.stderr(contains("unknown variant `invalid-tls-backend`, expected one of `native-tls`, `rustls-with-webpki-roots`, `rustls-with-native-roots`"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_quiet_failures() {
|
||||
let testenv = TestEnv::new().install_default_cache();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue