From ef773e8fa5f9a6260416696fcc1949bcb26feb74 Mon Sep 17 00:00:00 2001 From: Kondo Takeo Date: Tue, 30 Jun 2026 21:22:37 +0900 Subject: [PATCH] feat: enhance TLS support by merging Mozilla WebPKI roots with OS certificate store ( #11 ) --- ARCHITECTURE.md | 4 +-- Cargo.lock | 72 +++++++++++++++++++++++++++++++++++++++++++++++-- Cargo.toml | 15 ++++++++++- README.md | 2 +- 4 files changed, 87 insertions(+), 6 deletions(-) diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index df8e6ff..7691369 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -85,7 +85,7 @@ confluence2md is a single Rust crate that exposes one binary (`confluence2md`) a - **Responsibility:** Talk to the Confluence REST API v1 and download regular binary assets referenced by the page HTML. draw.io and PlantUML assets are resolved by their dedicated modules first. - **Key types:** `EnvConfig`, `PageResult`, `Attachment`, `AttachmentMaps`, `DownloadBinaryOptions`, `DownloadAttachmentOptions`, `DownloadImagesOptions`. - **Key functions:** - - `build_http_client` — `reqwest` client with `rustls-tls`. + - `build_http_client` — `reqwest` client backed by `rustls`. Trust anchors come from both the bundled Mozilla WebPKI root set (`rustls-tls-webpki-roots`) and the host OS certificate store (`rustls-tls-native-roots`); the two sets are merged so corporate / internal CAs trusted by the host work without configuration, and minimal container images without a system CA bundle still work via the bundled roots. - `get_required_env` — reads the personal access token from `CONFLUENCE2MD_PERSONAL_ACCESS_TOKEN`. - `resolve_page_id_from_url` — supports `pageId`, `/spaces/.../pages//`, `/display//`, and `spaceKey`+`title` URL formats. - `fetch_confluence_page` — fetches the page with `body.export_view` and `body.storage` expansions. @@ -310,7 +310,7 @@ No other external services are called. ## 8. Security Considerations - **Authentication:** Confluence Personal Access Token, read from `CONFLUENCE2MD_PERSONAL_ACCESS_TOKEN`. Never logged. -- **Transport:** HTTPS via `reqwest` with `rustls-tls` (no OpenSSL dependency). +- **Transport:** HTTPS via `reqwest` with `rustls` (no OpenSSL dependency). Trust anchors are the union of the bundled Mozilla WebPKI root set and the host OS certificate store, so the binary works on minimal container images (no system CA bundle required) and also honors corporate / internal CAs that the host already trusts. This means corporate TLS-inspection proxies whose root CA is installed in the OS store will be silently traversed, which is the standard tradeoff for tools that respect the OS trust store. - **Authorization:** Whatever the token's owner can read in Confluence. - **Sandbox:** Output is restricted to the resolved `--output-path` directory. `sanitize_file_name` strips path separators and forbidden characters to prevent path traversal in attachment titles. - **No `unsafe`:** The crate compiles without `unsafe` blocks; lints are enforced with `-D warnings`. diff --git a/Cargo.lock b/Cargo.lock index dc04a21..e553615 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -225,6 +225,22 @@ version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c" +[[package]] +name = "core-foundation" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + [[package]] name = "cpufeatures" version = "0.3.0" @@ -575,6 +591,7 @@ dependencies = [ "hyper", "hyper-util", "rustls", + "rustls-native-certs", "tokio", "tokio-rustls", "tower-service", @@ -872,6 +889,12 @@ version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" +[[package]] +name = "openssl-probe" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" + [[package]] name = "parking_lot" version = "0.12.5" @@ -1160,6 +1183,7 @@ dependencies = [ "pin-project-lite", "quinn", "rustls", + "rustls-native-certs", "rustls-pki-types", "serde", "serde_json", @@ -1211,6 +1235,18 @@ dependencies = [ "zeroize", ] +[[package]] +name = "rustls-native-certs" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dab5152771c58876a2146916e53e35057e1a4dfa2b9df0f0305b07f611fdea4d" +dependencies = [ + "openssl-probe", + "rustls-pki-types", + "schannel", + "security-framework", +] + [[package]] name = "rustls-pki-types" version = "1.14.1" @@ -1244,12 +1280,44 @@ version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" +[[package]] +name = "schannel" +version = "0.1.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939" +dependencies = [ + "windows-sys 0.61.2", +] + [[package]] name = "scopeguard" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "security-framework" +version = "3.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" +dependencies = [ + "bitflags", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "serde" version = "1.0.228" @@ -1845,9 +1913,9 @@ dependencies = [ [[package]] name = "webpki-roots" -version = "1.0.7" +version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52f5ee44c96cf55f1b349600768e3ece3a8f26010c05265ab73f945bb1a2eb9d" +checksum = "bf85cb06032201fa7c6f829d7db5a7e5aa45bcc0655327713065f6f0576731bf" dependencies = [ "rustls-pki-types", ] diff --git a/Cargo.toml b/Cargo.toml index b7b877e..492b56e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,8 +20,21 @@ once_cell = "1" percent-encoding = "2.3" quick-xml = "0.37" regex = "1.10" +# TLS trust anchors come from both: +# * the Mozilla WebPKI root set bundled at compile time +# (via the `rustls-tls-webpki-roots` feature), and +# * the host operating system certificate store loaded at runtime +# (via the `rustls-tls-native-roots` feature). +# +# Enabling both features makes `reqwest` merge the two trust sets by +# default, so the tool works out-of-the-box on minimal container images +# that ship without a system CA bundle, while still honoring corporate +# / internal CAs trusted by the host (e.g. Windows certificate store, +# macOS Keychain, Linux NSS / `/etc/ssl/certs`). No additional +# `ClientBuilder` configuration is required. reqwest = { version = "0.12", default-features = false, features = [ - "rustls-tls", + "rustls-tls-webpki-roots", + "rustls-tls-native-roots", ] } serde = { version = "1", features = ["derive"] } serde_json = "1" diff --git a/README.md b/README.md index bb4bc8e..e8d1403 100644 --- a/README.md +++ b/README.md @@ -176,6 +176,6 @@ confluence2md automatically detects the page from various Confluence URL formats | Runtime | [Rust](https://www.rust-lang.org/) (stable, 2024 edition) with [Tokio](https://tokio.rs/) | | Language | Rust | | HTML parsing | [`htmd`](https://crates.io/crates/htmd) + [`markup5ever_rcdom`](https://crates.io/crates/markup5ever_rcdom) | -| HTTP client | [`reqwest`](https://crates.io/crates/reqwest) with `rustls-tls` | +| HTTP client | [`reqwest`](https://crates.io/crates/reqwest) with `rustls` (bundled Mozilla WebPKI roots + OS certificate store) | | CLI parsing | [`clap`](https://crates.io/crates/clap) (derive macros) | | API | [Confluence REST API v1](https://developer.atlassian.com/cloud/confluence/rest/v1/intro/#about) |