diff --git a/Cargo.lock b/Cargo.lock index 9e9505c0dd..1392cc437b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -23,6 +23,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "ansi_term" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" +dependencies = [ + "winapi", +] + [[package]] name = "ansi_term" version = "0.12.1" @@ -79,6 +88,17 @@ dependencies = [ "syn", ] +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi", + "libc", + "winapi", +] + [[package]] name = "autocfg" version = "1.0.1" @@ -91,17 +111,73 @@ version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" +[[package]] +name = "bindgen" +version = "0.59.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "453c49e5950bb0eb63bb3df640e31618846c89d5b7faa54040d76e98e0134375" +dependencies = [ + "bitflags", + "cexpr", + "clang-sys", + "clap", + "env_logger", + "lazy_static", + "lazycell", + "log", + "peeking_take_while", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex", + "which 3.1.1", +] + [[package]] name = "bitflags" version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +[[package]] +name = "bitvec" +version = "0.19.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8942c8d352ae1838c9dda0b0ca2ab657696ef2232a20147cf1b30ae1a9cb4321" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + +[[package]] +name = "boring" +version = "1.1.6" +source = "git+https://github.com/netapp/boring?branch=feat/fips-support#0f431cbc3be689c477b3a8e8bc8d1ae177cd1e4e" +dependencies = [ + "bitflags", + "boring-sys", + "foreign-types", + "lazy_static", + "libc", +] + +[[package]] +name = "boring-sys" +version = "1.1.1" +source = "git+https://github.com/netapp/boring?branch=feat/fips-support#0f431cbc3be689c477b3a8e8bc8d1ae177cd1e4e" +dependencies = [ + "bindgen", + "cmake", +] + [[package]] name = "bumpalo" -version = "3.7.0" +version = "3.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c59e7af012c713f529e7a3ee57ce9b31ddd858d4b512923602f74608b009631" +checksum = "d9df67f7bf9ef8498769f994239c45613ef0c5899415fb58e9add412d2c1a538" [[package]] name = "byteorder" @@ -121,12 +197,56 @@ version = "1.0.70" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d26a6ce4b6a484fa3edb70f7efa6fc430fd2b87285fe8b84304fd0936faa0dc0" +[[package]] +name = "cexpr" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db507a7679252d2276ed0dd8113c6875ec56d3089f9225b2b42c30cc1f8e5c89" +dependencies = [ + "nom 6.1.2", +] + [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "clang-sys" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10612c0ec0e0a1ff0e97980647cb058a6e7aedb913d01d009c406b8b7d0b26ee" +dependencies = [ + "glob", + "libc", + "libloading", +] + +[[package]] +name = "clap" +version = "2.33.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002" +dependencies = [ + "ansi_term 0.11.0", + "atty", + "bitflags", + "strsim", + "textwrap", + "unicode-width", + "vec_map", +] + +[[package]] +name = "cmake" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb6210b637171dfba4cda12e579ac6dc73f5165ad56133e5d72ef3131f320855" +dependencies = [ + "cc", +] + [[package]] name = "crc32fast" version = "1.2.1" @@ -192,6 +312,19 @@ dependencies = [ "syn", ] +[[package]] +name = "env_logger" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a19187fea3ac7e84da7dacf48de0c45d63c6a76f9490dae389aead16c243fce3" +dependencies = [ + "atty", + "humantime", + "log", + "regex", + "termcolor", +] + [[package]] name = "fixedbitset" version = "0.2.0" @@ -216,6 +349,33 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "foreign-types" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" +dependencies = [ + "foreign-types-macros", + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-macros" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63f713f8b2aa9e24fec85b0e290c56caee12e3b6ae0aeeda238a75b28251afd6" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "foreign-types-shared" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7684cf33bb7f28497939e8c7cf17e3e4e3b8d9a0080ffa4f8ae2f515442ee855" + [[package]] name = "form_urlencoded" version = "1.0.1" @@ -226,6 +386,12 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "funty" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fed34cd105917e91daa4da6b3728c47b068749d6a62c59811f06ed2ac71d9da7" + [[package]] name = "futures" version = "0.3.17" @@ -315,6 +481,12 @@ dependencies = [ "wasi", ] +[[package]] +name = "glob" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" + [[package]] name = "gzip-header" version = "0.3.0" @@ -437,6 +609,12 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6456b8a6c8f33fee7d958fcd1b60d55b11940a79e63ae87013e6d22e26034440" +[[package]] +name = "humantime" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" + [[package]] name = "hyper" version = "0.14.13" @@ -509,9 +687,9 @@ dependencies = [ [[package]] name = "instant" -version = "0.1.10" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bee0328b1209d157ef001c94dd85b4f8f64139adb0eac2659f4b08382b2f474d" +checksum = "716d3d89f35ac6a34fd0eed635395f4c3b76fa889338a4632e5231a8684216bd" dependencies = [ "cfg-if", ] @@ -564,11 +742,17 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +[[package]] +name = "lazycell" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" + [[package]] name = "libc" -version = "0.2.102" +version = "0.2.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2a5ac8f984bfcf3a823267e5fde638acc3325f6496633a5da6bb6eb2171e103" +checksum = "dd8f7255a17a627354f321ef0055d63b898c6fb27eff628af4d1b66b7331edf6" [[package]] name = "libfuzzer-sys" @@ -581,6 +765,16 @@ dependencies = [ "once_cell", ] +[[package]] +name = "libloading" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f84d96438c15fcd6c3f244c8fce01d1e2b9c6b5623e9c711dc9286d8fc92d6a" +dependencies = [ + "cfg-if", + "winapi", +] + [[package]] name = "libmimalloc-sys" version = "0.1.22" @@ -989,8 +1183,10 @@ dependencies = [ name = "linkerd-identity" version = "0.1.0" dependencies = [ + "boring", "linkerd-dns-name", "ring", + "rustls", "thiserror", "tokio-rustls", "tracing", @@ -1354,6 +1550,7 @@ name = "linkerd-tls" version = "0.1.0" dependencies = [ "async-trait", + "boring", "bytes", "futures", "linkerd-conditional", @@ -1364,8 +1561,10 @@ dependencies = [ "linkerd-proxy-transport", "linkerd-stack", "linkerd-tracing", + "rustls", "thiserror", "tokio", + "tokio-boring", "tokio-rustls", "tower", "tracing", @@ -1588,6 +1787,18 @@ version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf51a729ecf40266a2368ad335a5fdde43471f545a967109cd62146ecf8b66ff" +[[package]] +name = "nom" +version = "6.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7413f999671bd4745a7b624bd370a569fb6bc574b23c83a3c5ed2e453f3d5e2" +dependencies = [ + "bitvec", + "funty", + "memchr", + "version_check", +] + [[package]] name = "ntapi" version = "0.3.6" @@ -1658,6 +1869,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "peeking_take_while" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" + [[package]] name = "percent-encoding" version = "2.1.0" @@ -1729,7 +1946,7 @@ checksum = "6ab1427f3d2635891f842892dda177883dca0639e05fe66796a62c9d2f23b49c" dependencies = [ "byteorder", "libc", - "nom", + "nom 2.2.1", "rustc_version", ] @@ -1758,7 +1975,7 @@ dependencies = [ "prost", "prost-types", "tempfile", - "which", + "which 4.2.2", ] [[package]] @@ -1808,6 +2025,12 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "radium" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "941ba9d78d8e2f7ce474c015eea4d9c6d25b6a3327f9832ee29a4de27f91bbb8" + [[package]] name = "rand" version = "0.8.4" @@ -1917,6 +2140,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + [[package]] name = "rustc_version" version = "0.2.3" @@ -2002,6 +2231,12 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "shlex" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3" + [[package]] name = "signal-hook-registry" version = "1.4.0" @@ -2019,9 +2254,9 @@ checksum = "c307a32c1c5c437f38c7fd45d753050587732ba8628319fbdf12a7e289ccc590" [[package]] name = "smallvec" -version = "1.6.1" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe0f37c9e8f3c5a4a66ad655a93c74daac4ad00c441533bf5c6e7990bb42604e" +checksum = "1ecab6c735a6bb4139c0caafd0cc3635748bbb3acf4550e8138122099251f309" [[package]] name = "socket2" @@ -2050,17 +2285,29 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" +[[package]] +name = "strsim" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" + [[package]] name = "syn" -version = "1.0.76" +version = "1.0.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6f107db402c2c2055242dbf4d2af0e69197202e9faacbef9571bbe47f5a1b84" +checksum = "5239bc68e0fef57495900cfea4e8dc75596d9a319d7e16b1e0a440d24e6fe0a0" dependencies = [ "proc-macro2", "quote", "unicode-xid", ] +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + [[package]] name = "tempfile" version = "3.2.0" @@ -2075,6 +2322,24 @@ dependencies = [ "winapi", ] +[[package]] +name = "termcolor" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "textwrap" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" +dependencies = [ + "unicode-width", +] + [[package]] name = "thiserror" version = "1.0.29" @@ -2106,9 +2371,9 @@ dependencies = [ [[package]] name = "tinyvec" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5241dd6f21443a3606b432718b166d3cedc962fd4b8bea54a8bc7f514ebda986" +checksum = "f83b2a3d4d9091d0abd7eba4dc2710b1718583bd4d8992e2190720ea38f391f7" dependencies = [ "tinyvec_macros", ] @@ -2139,6 +2404,16 @@ dependencies = [ "winapi", ] +[[package]] +name = "tokio-boring" +version = "2.1.3" +source = "git+https://github.com/netapp/boring?branch=feat/fips-support#0f431cbc3be689c477b3a8e8bc8d1ae177cd1e4e" +dependencies = [ + "boring", + "boring-sys", + "tokio", +] + [[package]] name = "tokio-io-timeout" version = "1.1.1" @@ -2369,7 +2644,7 @@ version = "0.2.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fdd0568dbfe3baf7048b7908d2b32bca0d81cd56bec6d2a8f894b01d74f86be3" dependencies = [ - "ansi_term", + "ansi_term 0.12.1", "lazy_static", "matchers", "parking_lot", @@ -2387,9 +2662,9 @@ dependencies = [ [[package]] name = "trust-dns-proto" -version = "0.21.0-alpha.2" +version = "0.21.0-alpha.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09637dee9c56a62b9acd8ca59ab2ed9459d8430b005100d9063ea326a0a3590a" +checksum = "0c92f7753caf275df6cff04b2ecbade15d62dbe38edf57e764180ba011d077ce" dependencies = [ "async-trait", "cfg-if", @@ -2412,9 +2687,9 @@ dependencies = [ [[package]] name = "trust-dns-resolver" -version = "0.21.0-alpha.2" +version = "0.21.0-alpha.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d449ae262d022d3f273e46483adbb8553e66327baa5c18717d7790e60d7c6721" +checksum = "717ae0b892f6e9f251ee878f7d3625f145d53c5df2fd67f751e85dc00b5cc4f6" dependencies = [ "cfg-if", "futures-util", @@ -2457,6 +2732,12 @@ version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8895849a949e7845e06bd6dc1aa51731a103c42707010a5b591c0038fb73385b" +[[package]] +name = "unicode-width" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973" + [[package]] name = "unicode-xid" version = "0.2.2" @@ -2487,6 +2768,18 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7cf7d77f457ef8dfa11e4cd5933c5ddb5dc52a94664071951219a97710f0a32b" +[[package]] +name = "vec_map" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" + +[[package]] +name = "version_check" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe" + [[package]] name = "want" version = "0.3.0" @@ -2576,6 +2869,15 @@ dependencies = [ "untrusted", ] +[[package]] +name = "which" +version = "3.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d011071ae14a2f6671d0b74080ae0cd8ebf3a6f8c9589a2cd45f23126fe29724" +dependencies = [ + "libc", +] + [[package]] name = "which" version = "4.2.2" @@ -2609,6 +2911,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +[[package]] +name = "winapi-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi", +] + [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" @@ -2623,3 +2934,9 @@ checksum = "b2986deb581c4fe11b621998a5e53361efe6b48a151178d0cd9eeffa4dc6acc9" dependencies = [ "winapi", ] + +[[package]] +name = "wyz" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85e60b0d1b5f99db2556934e21937020776a5d31520bf169e851ac44e6420214" diff --git a/Cargo.toml b/Cargo.toml index 1ce77e888b..d9842f54da 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -73,3 +73,6 @@ lto = true [patch.crates-io] webpki = { git = "https://github.com/linkerd/webpki", branch = "cert-dns-names-0.21" } +boring = { git = 'https://github.com/netapp/boring', branch = "feat/fips-support"} +boring-sys = { git = 'https://github.com/netapp/boring', branch = "feat/fips-support"} +tokio-boring = { git = 'https://github.com/netapp/boring', branch = "feat/fips-support"} diff --git a/Dockerfile b/Dockerfile index eec73b4bc4..deaf78d457 100644 --- a/Dockerfile +++ b/Dockerfile @@ -36,7 +36,15 @@ ARG PROXY_FEATURES RUN --mount=type=cache,target=/var/lib/apt/lists \ --mount=type=cache,target=/var/tmp \ - apt update && apt install -y time cmake + apt update && \ + apt install -y \ + time \ + cmake \ + golang \ + clang + +ENV CC=/usr/bin/clang +ENV CXX=/usr/bin/clang++ WORKDIR /usr/src/linkerd2-proxy COPY . . @@ -44,10 +52,10 @@ RUN --mount=type=cache,target=target \ --mount=type=cache,from=rust:1.55.0-buster,source=/usr/local/cargo,target=/usr/local/cargo \ mkdir -p /out && \ if [ -n "$PROXY_UNOPTIMIZED" ]; then \ - (cd linkerd2-proxy && /usr/bin/time -v cargo build --locked --features="$PROXY_FEATURES") && \ + (cd linkerd2-proxy && /usr/bin/time -v cargo build --locked --no-default-features --features="$PROXY_FEATURES") && \ mv target/debug/linkerd2-proxy /out/linkerd2-proxy ; \ else \ - (cd linkerd2-proxy && /usr/bin/time -v cargo build --locked --release --features="$PROXY_FEATURES") && \ + (cd linkerd2-proxy && /usr/bin/time -v cargo build --locked --release --no-default-features --features="$PROXY_FEATURES") && \ mv target/release/linkerd2-proxy /out/linkerd2-proxy ; \ fi diff --git a/linkerd/app/Cargo.toml b/linkerd/app/Cargo.toml index b5a8b5c3a4..f3e1980cab 100644 --- a/linkerd/app/Cargo.toml +++ b/linkerd/app/Cargo.toml @@ -13,6 +13,8 @@ This is used by tests and the executable. [features] allow-loopback = ["linkerd-app-outbound/allow-loopback"] +boring-tls = ["linkerd-app-core/boring-tls"] +rustls-tls = ["linkerd-app-core/rustls-tls"] [dependencies] futures = { version = "0.3", default-features = false } diff --git a/linkerd/app/core/Cargo.toml b/linkerd/app/core/Cargo.toml index a6dc6ae08a..4cfe083c0d 100644 --- a/linkerd/app/core/Cargo.toml +++ b/linkerd/app/core/Cargo.toml @@ -12,6 +12,10 @@ This crate conglomerates proxy configuration, runtime administration, etc, independently of the inbound and outbound proxy logic. """ +[features] +rustls-tls = ["linkerd-identity/rustls-tls", "linkerd-tls/rustls-tls"] +boring-tls = ["linkerd-identity/boring-tls", "linkerd-tls/boring-tls"] + [dependencies] bytes = "1" drain = { version = "0.1.0", features = ["retain"] } diff --git a/linkerd/app/inbound/src/direct.rs b/linkerd/app/inbound/src/direct.rs index c4cbb53bf6..303f7552f0 100644 --- a/linkerd/app/inbound/src/direct.rs +++ b/linkerd/app/inbound/src/direct.rs @@ -350,9 +350,7 @@ impl svc::Param for WithTransportHeaderAlpn { // be preferable if rustls::ServerConfig wrapped individual fields in an // Arc so they could be overridden independently. let mut config = self.0.server_config().as_ref().clone(); - config - .alpn_protocols - .push(transport_header::PROTOCOL.into()); + config.add_protocols(transport_header::PROTOCOL.into()); config.into() } } diff --git a/linkerd/identity/Cargo.toml b/linkerd/identity/Cargo.toml index 0f2faa28fa..4c819cc913 100644 --- a/linkerd/identity/Cargo.toml +++ b/linkerd/identity/Cargo.toml @@ -8,13 +8,16 @@ edition = "2018" [features] default = [] test-util = [] +rustls-tls = ["ring", "rustls"] +boring-tls = ["boring"] [dependencies] linkerd-dns-name = { path = "../dns/name" } -ring = "0.16.19" +ring = { version = "0.16.19", optional = true, features = ["std"] } +rustls = { version = "0.19", optional = true } thiserror = "1.0" tokio-rustls = "0.22" tracing = "0.1.28" untrusted = "0.7" webpki = "=0.21.4" - +boring = { version = "1.1.6", optional = true, features = ["fips"]} diff --git a/linkerd/identity/src/imp/boringssl.rs b/linkerd/identity/src/imp/boringssl.rs new file mode 100644 index 0000000000..e42e4d6af4 --- /dev/null +++ b/linkerd/identity/src/imp/boringssl.rs @@ -0,0 +1,304 @@ +use std::fmt; +use std::sync::Arc; +use std::time::SystemTime; + +use boring::{ + error::ErrorStack, + pkey::{PKey, Private}, + stack::Stack, + x509::{ + store::{X509Store, X509StoreBuilder}, + {X509StoreContext, X509VerifyResult, X509}, + }, +}; + +use tracing::{debug, error, trace}; + +use crate::{LocalId, Name}; +use std::fmt::Formatter; +use thiserror::Error; + +#[derive(Clone, Debug)] +pub struct Key(pub Arc>); + +impl Key { + pub fn from_pkcs8(b: &[u8]) -> Result { + let key = PKey::private_key_from_pkcs8(b)?; + Ok(Key(Arc::new(key))) + } +} + +#[derive(Clone, Debug, Error)] +#[error(transparent)] +pub struct Error(#[from] ErrorStack); + +#[derive(Clone)] +pub struct TrustAnchors(Arc); + +impl TrustAnchors { + fn store() -> X509StoreBuilder { + X509StoreBuilder::new().expect("unable to create certificate store") + } + + #[cfg(any(test, feature = "test-util"))] + pub fn empty() -> Self { + Self(Arc::new(TrustAnchors::store().build())) + } + + pub fn from_pem(s: &str) -> Option { + debug!("constructing trust trust anchors from pem {}", s); + return match X509::from_pem(s.as_bytes()) { + Ok(cert) => { + let mut store = TrustAnchors::store(); + trace!("adding certificate to trust anchors {:?}", cert); + if let Err(err) = store.add_cert(cert) { + error!("unable to add certificate to trust anchors, {}", err); + return None; + } + + Some(Self(Arc::new(store.build()))) + } + Err(err) => { + error!("unable to construct trust anchor {}", err); + None + } + }; + } + + pub fn certify(&self, key: Key, crt: Crt) -> Result { + let cert = crt.cert.clone(); + if !cert + .subject_alt_names() + .into_iter() + .flat_map(|alt_names| alt_names.into_iter()) + .any(|n| n.dnsname().expect("unable to convert to dns name") == crt.id.0.as_ref()) + { + return Err(InvalidCrt::SubjectAltName(crt.id)); + } + + let mut chain = Stack::new()?; + chain.push(cert.clone())?; + for chain_crt in crt.chain.clone() { + chain.push(chain_crt)?; + } + + let mut context = X509StoreContext::new()?; + context.init(&self.0, &cert, &chain, |c| match c.verify_cert() { + Ok(true) => Ok(Ok(true)), + Ok(false) => Ok(Err(InvalidCrt::Verify(c.error()))), + Err(err) => Err(err), + })??; + + let server_config = + ServerConfig::new(vec![], self.0.clone(), Some(crt.clone()), Some(key.clone())); + let client_config = + ClientConfig::new(vec![], self.0.clone(), Some(crt.clone()), Some(key.clone())); + + Ok(CrtKey { + id: crt.id.clone(), + expiry: crt.expiry.clone(), + client_config: Arc::new(client_config), + server_config: Arc::new(server_config), + }) + } + + pub fn client_config(&self) -> Arc { + Arc::new(ClientConfig::new(vec![], self.0.clone(), None, None)) + } +} + +impl fmt::Debug for TrustAnchors { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + f.pad("boringssl::TrustAnchors") + } +} + +#[derive(Clone, Debug, Error)] +pub enum InvalidCrt { + #[error("subject alt name incorrect ({0})")] + SubjectAltName(LocalId), + #[error("{0}")] + Verify(#[source] X509VerifyResult), + #[error(transparent)] + General(#[from] Error), +} + +impl From for InvalidCrt { + fn from(err: ErrorStack) -> Self { + InvalidCrt::General(err.into()) + } +} + +#[derive(Clone)] +pub struct CrtKey { + id: LocalId, + expiry: SystemTime, + client_config: Arc, + server_config: Arc, +} + +// === CrtKey === +impl CrtKey { + pub fn name(&self) -> &Name { + self.id.as_ref() + } + + pub fn expiry(&self) -> SystemTime { + self.expiry + } + + pub fn id(&self) -> &LocalId { + &self.id + } + + pub fn client_config(&self) -> Arc { + self.client_config.clone() + } + + pub fn server_config(&self) -> Arc { + self.server_config.clone() + } +} + +impl fmt::Debug for CrtKey { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { + f.debug_struct("boringssl::CrtKey") + .field("id", &self.id) + .field("expiry", &self.expiry) + .finish() + } +} + +#[derive(Clone, Debug)] +pub struct Crt { + pub(crate) id: LocalId, + expiry: SystemTime, + pub cert: X509, + pub chain: Vec, +} + +impl Crt { + pub fn new( + id: LocalId, + leaf: Vec, + intermediates: Vec>, + expiry: SystemTime, + ) -> Self { + let mut chain = Vec::with_capacity(intermediates.len() + 1); + let cert = X509::from_der(&leaf).expect("unable to convert to a x509 certificate"); + chain.extend( + intermediates + .into_iter() + .map(|crt| X509::from_der(&crt).expect("unable to add intermediate certificate")), + ); + + Self { + id, + cert, + chain, + expiry, + } + } + + pub fn name(&self) -> &Name { + self.id.as_ref() + } +} + +#[derive(Clone)] +pub struct ClientConfig { + pub root_certs: Arc, + pub key: Option>, + pub cert: Option>, + protocols: Arc>>, +} + +impl fmt::Debug for ClientConfig { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + f.debug_struct("openssl::ClientConfig") + .field("protocols", &self.protocols) + .finish() + } +} + +impl ClientConfig { + fn new( + protocols: Vec>, + root_certs: Arc, + cert: Option, + key: Option, + ) -> Self { + Self { + root_certs, + protocols: Arc::new(protocols), + key: key.map(Arc::new), + cert: cert.map(Arc::new), + } + } + pub fn empty() -> Self { + ClientConfig::new( + Vec::new(), + Arc::new( + X509StoreBuilder::new() + .expect("unable to construct root certs") + .build(), + ), + None, + None, + ) + } + + pub fn set_protocols(&mut self, protocols: Vec>) { + self.protocols = Arc::new(protocols) + } +} + +#[derive(Clone)] +pub struct ServerConfig { + pub root_certs: Arc, + pub key: Option>, + pub cert: Option>, + alpn_protocols: Arc>>, +} + +impl ServerConfig { + fn new( + alpn_protocols: Vec>, + root_certs: Arc, + cert: Option, + key: Option, + ) -> Self { + Self { + alpn_protocols: Arc::new(alpn_protocols), + root_certs, + key: key.map(Arc::new), + cert: cert.map(Arc::new), + } + } + /// Produces a server config that fails to handshake all connections. + pub fn empty() -> Self { + ServerConfig::new( + Vec::new(), + Arc::new( + X509StoreBuilder::new() + .expect("unable to construct root certs") + .build(), + ), + None, + None, + ) + } + + pub fn add_protocols(&mut self, protocols: Vec) { + self.alpn_protocols.as_ref().clone().push(protocols) + } +} + +impl fmt::Debug for ServerConfig { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + f.debug_struct("openssl::ServerConfig") + .field("alpn_protocols", &self.alpn_protocols) + .field("key", &self.key) + .finish() + } +} diff --git a/linkerd/identity/src/imp/rustls.rs b/linkerd/identity/src/imp/rustls.rs new file mode 100644 index 0000000000..2ba36b758d --- /dev/null +++ b/linkerd/identity/src/imp/rustls.rs @@ -0,0 +1,381 @@ +use crate::{LocalId, Name}; +use ring::error::KeyRejected; +use ring::rand; +use ring::signature::EcdsaKeyPair; +use std::fmt; +use std::sync::Arc; +use std::time::SystemTime; +use thiserror::Error; +use tracing::{debug, warn}; + +// These must be kept in sync: +static SIGNATURE_ALG_RING_SIGNING: &ring::signature::EcdsaSigningAlgorithm = + &ring::signature::ECDSA_P256_SHA256_ASN1_SIGNING; +const SIGNATURE_ALG_RUSTLS_SCHEME: rustls::SignatureScheme = + rustls::SignatureScheme::ECDSA_NISTP256_SHA256; +const SIGNATURE_ALG_RUSTLS_ALGORITHM: rustls::internal::msgs::enums::SignatureAlgorithm = + rustls::internal::msgs::enums::SignatureAlgorithm::ECDSA; +const TLS_VERSIONS: &[rustls::ProtocolVersion] = &[ + rustls::ProtocolVersion::TLSv1_2, + rustls::ProtocolVersion::TLSv1_3, +]; + +struct SigningKey(Arc); +struct Signer(Arc); + +#[derive(Clone, Debug)] +pub struct Key(Arc); + +impl Key { + pub fn from_pkcs8(b: &[u8]) -> Result { + let k = EcdsaKeyPair::from_pkcs8(SIGNATURE_ALG_RING_SIGNING, b)?; + Ok(Key(Arc::new(k))) + } +} + +#[derive(Clone, Debug, Error)] +#[error(transparent)] +pub struct Error(#[from] KeyRejected); + +#[derive(Clone)] +pub struct TrustAnchors(Arc); + +impl TrustAnchors { + #[cfg(any(test, feature = "test-util"))] + pub fn empty() -> Self { + TrustAnchors(Arc::new(ClientConfig(rustls::ClientConfig::new()))) + } + + pub fn from_pem(s: &str) -> Option { + use std::io::Cursor; + + let mut roots = rustls::RootCertStore::empty(); + let (added, skipped) = roots.add_pem_file(&mut Cursor::new(s)).ok()?; + if skipped != 0 { + warn!("skipped {} trust anchors in trust anchors file", skipped); + } + if added == 0 { + return None; + } + + let mut c = rustls::ClientConfig::new(); + + // XXX: Rustls's built-in verifiers don't let us tweak things as fully + // as we'd like (e.g. controlling the set of trusted signature + // algorithms), but they provide good enough defaults for now. + // TODO: lock down the verification further. + // TODO: Change Rustls's API to Avoid needing to clone `root_cert_store`. + c.root_store = roots; + + // Disable session resumption for the time-being until resumption is + // more tested. + c.enable_tickets = false; + + Some(TrustAnchors(Arc::new(ClientConfig(c)))) + } + + pub fn certify(&self, key: Key, crt: Crt) -> Result { + let mut client = self.0.as_ref().clone().as_ref().clone(); + + // Ensure the certificate is valid for the services we terminate for + // TLS. This assumes that server cert validation does the same or + // more validation than client cert validation. + // + // XXX: Rustls currently only provides access to a + // `ServerCertVerifier` through + // `rustls::ClientConfig::get_verifier()`. + // + // XXX: Once `rustls::ServerCertVerified` is exposed in Rustls's + // safe API, use it to pass proof to CertCertResolver::new.... + // + // TODO: Restrict accepted signature algorithms. + static NO_OCSP: &[u8] = &[]; + client + .get_verifier() + .verify_server_cert(&client.root_store, &crt.chain, (&crt.id).into(), NO_OCSP) + .map_err(InvalidCrt)?; + + let k = SigningKey(key.0); + let key = rustls::sign::CertifiedKey::new(crt.chain, Arc::new(Box::new(k))); + let resolver = Arc::new(CertResolver(key)); + + // Enable client authentication. + client.client_auth_cert_resolver = resolver.clone(); + + // Ask TLS clients for a certificate and accept any certificate issued + // by our trusted CA(s). + // + // XXX: Rustls's built-in verifiers don't let us tweak things as fully + // as we'd like (e.g. controlling the set of trusted signature + // algorithms), but they provide good enough defaults for now. + // TODO: lock down the verification further. + // + // TODO: Change Rustls's API to Avoid needing to clone `root_cert_store`. + let mut server = rustls::ServerConfig::new( + rustls::AllowAnyAnonymousOrAuthenticatedClient::new(client.root_store.clone()), + ); + server.versions = TLS_VERSIONS.to_vec(); + server.cert_resolver = resolver; + + Ok(CrtKey { + id: crt.id, + expiry: crt.expiry, + client_config: Arc::new(ClientConfig(client)), + server_config: Arc::new(ServerConfig(server)), + }) + } + + pub fn client_config(&self) -> Arc { + self.0.clone() + } +} + +impl fmt::Debug for TrustAnchors { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_tuple("rustls::TrustAnchors").finish() + } +} + +#[derive(Clone, Debug, Error)] +#[error(transparent)] +pub struct InvalidCrt(#[from] rustls::TLSError); + +impl rustls::sign::SigningKey for SigningKey { + fn choose_scheme( + &self, + offered: &[rustls::SignatureScheme], + ) -> Option> { + if offered.contains(&SIGNATURE_ALG_RUSTLS_SCHEME) { + Some(Box::new(Signer(self.0.clone()))) + } else { + None + } + } + + fn algorithm(&self) -> rustls::internal::msgs::enums::SignatureAlgorithm { + SIGNATURE_ALG_RUSTLS_ALGORITHM + } +} + +impl rustls::sign::Signer for Signer { + fn sign(&self, message: &[u8]) -> Result, rustls::TLSError> { + let rng = rand::SystemRandom::new(); + self.0 + .sign(&rng, message) + .map(|signature| signature.as_ref().to_owned()) + .map_err(|ring::error::Unspecified| { + rustls::TLSError::General("Signing Failed".to_owned()) + }) + } + + fn get_scheme(&self) -> rustls::SignatureScheme { + SIGNATURE_ALG_RUSTLS_SCHEME + } +} + +#[derive(Clone)] +pub struct CrtKey { + id: LocalId, + expiry: SystemTime, + client_config: Arc, + server_config: Arc, +} + +// === CrtKey === +impl CrtKey { + pub fn name(&self) -> &Name { + self.id.as_ref() + } + + pub fn expiry(&self) -> SystemTime { + self.expiry + } + + pub fn id(&self) -> &LocalId { + &self.id + } + + pub fn client_config(&self) -> Arc { + self.client_config.clone() + } + + pub fn server_config(&self) -> Arc { + self.server_config.clone() + } +} + +impl fmt::Debug for CrtKey { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { + f.debug_struct("CrtKey") + .field("id", &self.id) + .field("expiry", &self.expiry) + .finish() + } +} + +pub struct CertResolver(rustls::sign::CertifiedKey); + +impl rustls::ResolvesClientCert for CertResolver { + fn resolve( + &self, + _acceptable_issuers: &[&[u8]], + sigschemes: &[rustls::SignatureScheme], + ) -> Option { + // The proxy's server-side doesn't send the list of acceptable issuers so + // don't bother looking at `_acceptable_issuers`. + self.resolve_(sigschemes) + } + + fn has_certs(&self) -> bool { + true + } +} + +impl CertResolver { + fn resolve_( + &self, + sigschemes: &[rustls::SignatureScheme], + ) -> Option { + if !sigschemes.contains(&SIGNATURE_ALG_RUSTLS_SCHEME) { + debug!("signature scheme not supported -> no certificate"); + return None; + } + Some(self.0.clone()) + } +} + +impl rustls::ResolvesServerCert for CertResolver { + fn resolve(&self, hello: rustls::ClientHello<'_>) -> Option { + let server_name = if let Some(server_name) = hello.server_name() { + server_name + } else { + debug!("no SNI -> no certificate"); + return None; + }; + + // Verify that our certificate is valid for the given SNI name. + let c = (&self.0.cert) + .first() + .map(rustls::Certificate::as_ref) + .unwrap_or(&[]); // An empty input will fail to parse. + if let Err(err) = + webpki::EndEntityCert::from(c).and_then(|c| c.verify_is_valid_for_dns_name(server_name)) + { + debug!( + "our certificate is not valid for the SNI name -> no certificate: {:?}", + err + ); + return None; + } + + self.resolve_(hello.sigschemes()) + } +} + +#[derive(Clone, Debug)] +pub struct Crt { + pub(crate) id: LocalId, + expiry: SystemTime, + chain: Vec, +} + +impl Crt { + pub fn new( + id: LocalId, + leaf: Vec, + intermediates: Vec>, + expiry: SystemTime, + ) -> Self { + let mut chain = Vec::with_capacity(intermediates.len() + 1); + chain.push(rustls::Certificate(leaf)); + chain.extend(intermediates.into_iter().map(rustls::Certificate)); + + Self { id, chain, expiry } + } + + pub fn name(&self) -> &Name { + self.id.as_ref() + } +} + +#[derive(Clone)] +pub struct ClientConfig(rustls::ClientConfig); + +impl ClientConfig { + pub fn empty() -> Self { + Self(rustls::ClientConfig::new()) + } + + pub fn set_protocols(&mut self, protocols: Vec>) { + self.0.set_protocols(&*protocols) + } +} + +impl From for Arc { + fn from(cc: ClientConfig) -> Arc { + Arc::new(cc.into()) + } +} + +impl From for rustls::ClientConfig { + fn from(cc: ClientConfig) -> rustls::ClientConfig { + cc.0 + } +} + +impl AsRef for ClientConfig { + fn as_ref(&self) -> &rustls::ClientConfig { + &self.0 + } +} + +impl fmt::Debug for ClientConfig { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("rustls::ClientConfig") + .field("alpn", &self.0.alpn_protocols) + .field("protocols", &self.0.versions) + .finish() + } +} + +#[derive(Clone)] +pub struct ServerConfig(rustls::ServerConfig); + +impl ServerConfig { + /// Produces a server config that fails to handshake all connections. + pub fn empty() -> Self { + let verifier = rustls::NoClientAuth::new(); + Self(rustls::ServerConfig::new(verifier)) + } + + pub fn add_protocols(&mut self, protocols: Vec) { + self.0.alpn_protocols.push(protocols) + } +} + +impl From for Arc { + fn from(sc: ServerConfig) -> Arc { + Arc::new(sc.into()) + } +} + +impl From for rustls::ServerConfig { + fn from(sc: ServerConfig) -> rustls::ServerConfig { + sc.0 + } +} + +impl AsRef for ServerConfig { + fn as_ref(&self) -> &rustls::ServerConfig { + &self.0 + } +} + +impl fmt::Debug for ServerConfig { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("rustls::ServerConfig") + .field("alpn", &self.0.alpn_protocols) + .field("protocols", &self.0.versions) + .finish() + } +} diff --git a/linkerd/identity/src/lib.rs b/linkerd/identity/src/lib.rs index 04104d3974..70c6cd0ee2 100644 --- a/linkerd/identity/src/lib.rs +++ b/linkerd/identity/src/lib.rs @@ -1,13 +1,16 @@ #![deny(warnings, rust_2018_idioms)] #![forbid(unsafe_code)] -pub use ring::error::KeyRejected; -use ring::rand; -use ring::signature::EcdsaKeyPair; use std::{convert::TryFrom, fmt, fs, io, str::FromStr, sync::Arc, time::SystemTime}; use thiserror::Error; -use tokio_rustls::rustls; -use tracing::{debug, warn}; +use tracing::debug; + +#[cfg(feature = "rustls-tls")] +#[path = "imp/rustls.rs"] +mod imp; +#[cfg(not(feature = "rustls-tls"))] +#[path = "imp/boringssl.rs"] +mod imp; #[cfg(any(test, feature = "test-util"))] pub mod test_util; @@ -18,59 +21,28 @@ pub use linkerd_dns_name::InvalidName; #[derive(Clone, Debug)] pub struct Csr(Arc>); -/// An endpoint's identity. -#[derive(Clone, Eq, PartialEq, Hash)] -pub struct Name(Arc); +/// An error returned from the TLS implementation. +#[derive(Clone, Debug, Error)] +#[error(transparent)] +pub struct Error(#[from] imp::Error); #[derive(Clone, Debug)] -pub struct Key(Arc); - -struct SigningKey(Arc); -struct Signer(Arc); - -#[derive(Clone)] -pub struct TrustAnchors(Arc); +pub struct Key(imp::Key); #[derive(Clone, Debug)] pub struct TokenSource(Arc); #[derive(Clone, Debug)] -pub struct Crt { - id: LocalId, - expiry: SystemTime, - chain: Vec, -} - -#[derive(Clone)] -pub struct CrtKey { - id: LocalId, - expiry: SystemTime, - client_config: Arc, - server_config: Arc, -} - -struct CertResolver(rustls::sign::CertifiedKey); +pub struct Crt(imp::Crt); #[derive(Clone, Debug, Error)] #[error(transparent)] -pub struct InvalidCrt(rustls::TLSError); +pub struct InvalidCrt(#[from] imp::InvalidCrt); /// A newtype for local server identities. #[derive(Clone, Debug, Eq, PartialEq, Hash)] pub struct LocalId(pub Name); -// These must be kept in sync: -static SIGNATURE_ALG_RING_SIGNING: &ring::signature::EcdsaSigningAlgorithm = - &ring::signature::ECDSA_P256_SHA256_ASN1_SIGNING; -const SIGNATURE_ALG_RUSTLS_SCHEME: rustls::SignatureScheme = - rustls::SignatureScheme::ECDSA_NISTP256_SHA256; -const SIGNATURE_ALG_RUSTLS_ALGORITHM: rustls::internal::msgs::enums::SignatureAlgorithm = - rustls::internal::msgs::enums::SignatureAlgorithm::ECDSA; -const TLS_VERSIONS: &[rustls::ProtocolVersion] = &[ - rustls::ProtocolVersion::TLSv1_2, - rustls::ProtocolVersion::TLSv1_3, -]; - // === impl Csr === impl Csr { @@ -90,50 +62,32 @@ impl Csr { // === impl Key === impl Key { - pub fn from_pkcs8(b: &[u8]) -> Result { - let k = EcdsaKeyPair::from_pkcs8(SIGNATURE_ALG_RING_SIGNING, b)?; - Ok(Key(Arc::new(k))) + pub fn from_pkcs8(b: &[u8]) -> Result { + let key = imp::Key::from_pkcs8(b)?; + Ok(Key(key)) } } -impl rustls::sign::SigningKey for SigningKey { - fn choose_scheme( - &self, - offered: &[rustls::SignatureScheme], - ) -> Option> { - if offered.contains(&SIGNATURE_ALG_RUSTLS_SCHEME) { - Some(Box::new(Signer(self.0.clone()))) - } else { - None - } - } +// === impl Name === +/// An endpoint's identity. +#[derive(Clone, Eq, PartialEq, Hash)] +pub struct Name(Arc); - fn algorithm(&self) -> rustls::internal::msgs::enums::SignatureAlgorithm { - SIGNATURE_ALG_RUSTLS_ALGORITHM +impl From for Name { + fn from(n: linkerd_dns_name::Name) -> Self { + Name(Arc::new(n)) } } -impl rustls::sign::Signer for Signer { - fn sign(&self, message: &[u8]) -> Result, rustls::TLSError> { - let rng = rand::SystemRandom::new(); - self.0 - .sign(&rng, message) - .map(|signature| signature.as_ref().to_owned()) - .map_err(|ring::error::Unspecified| { - rustls::TLSError::General("Signing Failed".to_owned()) - }) - } - - fn get_scheme(&self) -> rustls::SignatureScheme { - SIGNATURE_ALG_RUSTLS_SCHEME +impl From for linkerd_dns_name::Name { + fn from(n: Name) -> linkerd_dns_name::Name { + n.0.as_ref().clone() } } -// === impl Name === - -impl From for Name { - fn from(n: linkerd_dns_name::Name) -> Self { - Name(Arc::new(n)) +impl From<&Name> for Name { + fn from(n: &Name) -> Self { + Self(n.0.clone()) } } @@ -210,102 +164,28 @@ impl TokenSource { } } -// === impl TrustAnchors === +// === TrustAnchors === +#[derive(Clone, Debug)] +pub struct TrustAnchors(imp::TrustAnchors); impl TrustAnchors { #[cfg(any(test, feature = "test-util"))] fn empty() -> Self { - TrustAnchors(Arc::new(rustls::ClientConfig::new())) + TrustAnchors(imp::TrustAnchors::empty()) } - pub fn from_pem(s: &str) -> Option { - use std::io::Cursor; - - let mut roots = rustls::RootCertStore::empty(); - let (added, skipped) = roots.add_pem_file(&mut Cursor::new(s)).ok()?; - if skipped != 0 { - warn!("skipped {} trust anchors in trust anchors file", skipped); - } - if added == 0 { - return None; - } - - let mut c = rustls::ClientConfig::new(); - - // XXX: Rustls's built-in verifiers don't let us tweak things as fully - // as we'd like (e.g. controlling the set of trusted signature - // algorithms), but they provide good enough defaults for now. - // TODO: lock down the verification further. - // TODO: Change Rustls's API to Avoid needing to clone `root_cert_store`. - c.root_store = roots; - - // Disable session resumption for the time-being until resumption is - // more tested. - c.enable_tickets = false; - - Some(TrustAnchors(Arc::new(c))) + pub fn from_pem(s: &str) -> Option { + imp::TrustAnchors::from_pem(s).map(TrustAnchors) } pub fn certify(&self, key: Key, crt: Crt) -> Result { - let mut client = self.0.as_ref().clone(); - - // Ensure the certificate is valid for the services we terminate for - // TLS. This assumes that server cert validation does the same or - // more validation than client cert validation. - // - // XXX: Rustls currently only provides access to a - // `ServerCertVerifier` through - // `rustls::ClientConfig::get_verifier()`. - // - // XXX: Once `rustls::ServerCertVerified` is exposed in Rustls's - // safe API, use it to pass proof to CertCertResolver::new.... - // - // TODO: Restrict accepted signatutre algorithms. - static NO_OCSP: &[u8] = &[]; - client - .get_verifier() - .verify_server_cert(&client.root_store, &crt.chain, (&crt.id).into(), NO_OCSP) - .map_err(InvalidCrt)?; - debug!("certified {}", crt.id); - - let k = SigningKey(key.0); - let key = rustls::sign::CertifiedKey::new(crt.chain, Arc::new(Box::new(k))); - let resolver = Arc::new(CertResolver(key)); - - // Enable client authentication. - client.client_auth_cert_resolver = resolver.clone(); - - // Ask TLS clients for a certificate and accept any certificate issued - // by our trusted CA(s). - // - // XXX: Rustls's built-in verifiers don't let us tweak things as fully - // as we'd like (e.g. controlling the set of trusted signature - // algorithms), but they provide good enough defaults for now. - // TODO: lock down the verification further. - // - // TODO: Change Rustls's API to Avoid needing to clone `root_cert_store`. - let mut server = rustls::ServerConfig::new( - rustls::AllowAnyAnonymousOrAuthenticatedClient::new(self.0.root_store.clone()), - ); - server.versions = TLS_VERSIONS.to_vec(); - server.cert_resolver = resolver; - - Ok(CrtKey { - id: crt.id, - expiry: crt.expiry, - client_config: Arc::new(client), - server_config: Arc::new(server), - }) - } - - pub fn client_config(&self) -> Arc { - self.0.clone() + let key = self.0.certify(key.0, crt.0).map(CrtKey)?; + debug!("Certified {}", key.id()); + Ok(key) } -} -impl fmt::Debug for TrustAnchors { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("TrustAnchors").finish() + pub fn client_config(&self) -> Arc { + Arc::new(ClientConfig(self.0.client_config().as_ref().clone())) } } @@ -318,139 +198,106 @@ impl Crt { intermediates: Vec>, expiry: SystemTime, ) -> Self { - let mut chain = Vec::with_capacity(intermediates.len() + 1); - chain.push(rustls::Certificate(leaf)); - chain.extend(intermediates.into_iter().map(rustls::Certificate)); - - Self { id, chain, expiry } + Self(imp::Crt::new(id, leaf, intermediates, expiry)) } pub fn name(&self) -> &Name { - self.id.as_ref() - } -} - -impl From<&'_ Crt> for LocalId { - fn from(crt: &Crt) -> LocalId { - crt.id.clone() + self.0.name() } } // === CrtKey === +#[derive(Clone, Debug)] +pub struct CrtKey(imp::CrtKey); impl CrtKey { pub fn name(&self) -> &Name { - self.id.as_ref() + self.0.name() } pub fn expiry(&self) -> SystemTime { - self.expiry + self.0.expiry() } pub fn id(&self) -> &LocalId { - &self.id + self.0.id() } - pub fn client_config(&self) -> Arc { - self.client_config.clone() + pub fn client_config(&self) -> Arc { + Arc::new(ClientConfig(self.0.client_config().as_ref().clone())) } - pub fn server_config(&self) -> Arc { - self.server_config.clone() + pub fn server_config(&self) -> Arc { + Arc::new(ServerConfig(self.0.server_config().as_ref().clone())) } } -impl fmt::Debug for CrtKey { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { - f.debug_struct("CrtKey") - .field("id", &self.id) - .field("expiry", &self.expiry) - .finish() +// === impl LocalId === + +impl From for LocalId { + fn from(n: Name) -> Self { + Self(n) } } -// === impl CertResolver === - -impl rustls::ResolvesClientCert for CertResolver { - fn resolve( - &self, - _acceptable_issuers: &[&[u8]], - sigschemes: &[rustls::SignatureScheme], - ) -> Option { - // The proxy's server-side doesn't send the list of acceptable issuers so - // don't bother looking at `_acceptable_issuers`. - self.resolve_(sigschemes) +impl From for Name { + fn from(LocalId(name): LocalId) -> Name { + name } +} - fn has_certs(&self) -> bool { - true +impl AsRef for LocalId { + fn as_ref(&self) -> &Name { + &self.0 } } -impl CertResolver { - fn resolve_( - &self, - sigschemes: &[rustls::SignatureScheme], - ) -> Option { - if !sigschemes.contains(&SIGNATURE_ALG_RUSTLS_SCHEME) { - debug!("signature scheme not supported -> no certificate"); - return None; - } - Some(self.0.clone()) +impl From<&'_ Crt> for LocalId { + fn from(crt: &Crt) -> LocalId { + crt.name().clone().into() } } -impl rustls::ResolvesServerCert for CertResolver { - fn resolve(&self, hello: rustls::ClientHello<'_>) -> Option { - let server_name = if let Some(server_name) = hello.server_name() { - server_name - } else { - debug!("no SNI -> no certificate"); - return None; - }; - - // Verify that our certificate is valid for the given SNI name. - let c = (&self.0.cert) - .first() - .map(rustls::Certificate::as_ref) - .unwrap_or(&[]); // An empty input will fail to parse. - if let Err(err) = - webpki::EndEntityCert::from(c).and_then(|c| c.verify_is_valid_for_dns_name(server_name)) - { - debug!( - "our certificate is not valid for the SNI name -> no certificate: {:?}", - err - ); - return None; - } - - self.resolve_(hello.sigschemes()) +impl fmt::Display for LocalId { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.0.fmt(f) } } -// === impl LocalId === +#[derive(Clone)] +pub struct ClientConfig(pub imp::ClientConfig); -impl From for LocalId { - fn from(n: Name) -> Self { - Self(n) +impl ClientConfig { + pub fn empty() -> Self { + Self(imp::ClientConfig::empty()) + } + pub fn set_protocols(&mut self, protocols: Vec>) { + self.0.set_protocols(protocols); } } -impl From for Name { - fn from(LocalId(name): LocalId) -> Name { - name +impl From for ClientConfig { + fn from(conf: imp::ClientConfig) -> Self { + Self(conf) } } -impl AsRef for LocalId { - fn as_ref(&self) -> &Name { - &self.0 +#[derive(Clone, Debug)] +pub struct ServerConfig(pub imp::ServerConfig); + +impl ServerConfig { + pub fn empty() -> Self { + Self(imp::ServerConfig::empty()) + } + + pub fn add_protocols(&mut self, protocols: Vec) { + self.0.add_protocols(protocols); } } -impl fmt::Display for LocalId { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - self.0.fmt(f) +impl From for ServerConfig { + fn from(conf: imp::ServerConfig) -> Self { + Self(conf) } } diff --git a/linkerd/io/src/lib.rs b/linkerd/io/src/lib.rs index 8993c81217..6653e63c3a 100644 --- a/linkerd/io/src/lib.rs +++ b/linkerd/io/src/lib.rs @@ -64,18 +64,6 @@ impl PeerAddr for tokio::net::TcpStream { } } -impl PeerAddr for tokio_rustls::client::TlsStream { - fn peer_addr(&self) -> Result { - self.get_ref().0.peer_addr() - } -} - -impl PeerAddr for tokio_rustls::server::TlsStream { - fn peer_addr(&self) -> Result { - self.get_ref().0.peer_addr() - } -} - #[cfg(feature = "tokio-test")] impl PeerAddr for tokio_test::io::Mock { fn peer_addr(&self) -> Result { diff --git a/linkerd/proxy/identity/src/certify.rs b/linkerd/proxy/identity/src/certify.rs index ae814b6cee..8e7a461cf4 100644 --- a/linkerd/proxy/identity/src/certify.rs +++ b/linkerd/proxy/identity/src/certify.rs @@ -97,6 +97,7 @@ impl Daemon { } = self; debug!("Identity daemon running"); + let mut curr_expiry = UNIX_EPOCH; loop { diff --git a/linkerd/tls/Cargo.toml b/linkerd/tls/Cargo.toml index 7710c2fd2e..e1f9f9c7b2 100644 --- a/linkerd/tls/Cargo.toml +++ b/linkerd/tls/Cargo.toml @@ -6,6 +6,11 @@ license = "Apache-2.0" edition = "2018" publish = false +[features] +default = [] +rustls-tls = ["linkerd-identity/rustls-tls", "rustls", "tokio-rustls"] +boring-tls = ["linkerd-identity/boring-tls", "boring", "tokio-boring"] + [dependencies] async-trait = "0.1" bytes = "1" @@ -16,9 +21,12 @@ linkerd-error = { path = "../error" } linkerd-identity = { path = "../identity" } linkerd-io = { path = "../io" } linkerd-stack = { path = "../stack" } -thiserror = "1.0" tokio = { version = "1", features = ["macros", "time"] } -tokio-rustls = "0.22" +rustls = { version = "0.19", optional = true } +tokio-rustls = { version = "0.22", optional = true } +boring = { version = "1.1.6", optional = true, features = ["fips"]} +tokio-boring = { version = "2.1.3", optional = true } +thiserror = "1.0" tower = "0.4.8" tracing = "0.1.28" webpki = "0.21" diff --git a/linkerd/tls/src/client.rs b/linkerd/tls/src/client.rs index ba051a4cb0..31f5cc3094 100644 --- a/linkerd/tls/src/client.rs +++ b/linkerd/tls/src/client.rs @@ -2,6 +2,7 @@ use futures::{ future::{Either, MapOk}, prelude::*, }; +use io::ReadBuf; use linkerd_conditional::Conditional; use linkerd_identity as id; use linkerd_io as io; @@ -14,10 +15,70 @@ use std::{ sync::Arc, task::{Context, Poll}, }; -pub use tokio_rustls::client::TlsStream; -use tokio_rustls::rustls::{self, Session}; use tracing::{debug, trace}; +use crate::imp; +use crate::{HasNegotiatedProtocol, NegotiatedProtocolRef, TlsConnector}; + +#[derive(Debug)] +pub struct TlsStream(imp::client::TlsStream); + +impl TlsStream { + pub fn get_alpn_protocol(&self) -> Option<&[u8]> { + self.0.get_alpn_protocol() + } +} + +impl From> for TlsStream { + fn from(stream: imp::client::TlsStream) -> Self { + TlsStream(stream) + } +} + +impl io::AsyncRead for TlsStream +where + IO: io::AsyncRead + io::AsyncWrite + Unpin, +{ + fn poll_read( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &mut ReadBuf<'_>, + ) -> Poll> { + Pin::new(&mut self.0).poll_read(cx, buf) + } +} + +impl io::AsyncWrite for TlsStream +where + IO: io::AsyncRead + io::AsyncWrite + Unpin, +{ + fn poll_write( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &[u8], + ) -> Poll> { + Pin::new(&mut self.0).poll_write(cx, buf) + } + + fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + Pin::new(&mut self.0).poll_flush(cx) + } + + fn poll_shutdown(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + Pin::new(&mut self.0).poll_shutdown(cx) + } +} + +impl HasNegotiatedProtocol for TlsStream +where + IO: HasNegotiatedProtocol, +{ + #[inline] + fn negotiated_protocol(&self) -> Option> { + self.0.negotiated_protocol() + } +} + /// A newtype for target server identities. #[derive(Clone, Debug, Eq, PartialEq, Hash)] pub struct ServerId(pub id::Name); @@ -54,7 +115,7 @@ pub enum NoClientTls { /// known TLS identity. pub type ConditionalClientTls = Conditional; -pub type Config = Arc; +pub type Config = Arc; #[derive(Clone, Debug)] pub struct Client { @@ -128,11 +189,11 @@ where // TODO it would be better to avoid cloning the whole TLS config // per-connection. match alpn { - None => tokio_rustls::TlsConnector::from(local.param()), + None => TlsConnector::from(local.param()), Some(AlpnProtocols(protocols)) => { - let mut config: rustls::ClientConfig = local.param().as_ref().clone(); - config.alpn_protocols = protocols; - tokio_rustls::TlsConnector::from(Arc::new(config)) + let mut config: id::ClientConfig = local.param().as_ref().clone(); + config.set_protocols(protocols); + TlsConnector::from(Arc::new(config)) } } } @@ -147,7 +208,7 @@ where Either::Right(Box::pin(async move { let io = connect.await?; let io = handshake.connect((&server_id.0).into(), io).await?; - if let Some(alpn) = io.get_ref().1.get_alpn_protocol() { + if let Some(alpn) = io.get_alpn_protocol() { debug!(alpn = ?std::str::from_utf8(alpn)); } Ok(io::EitherIo::Right(io)) diff --git a/linkerd/tls/src/imp/boringssl.rs b/linkerd/tls/src/imp/boringssl.rs new file mode 100644 index 0000000000..46d8a1ab8d --- /dev/null +++ b/linkerd/tls/src/imp/boringssl.rs @@ -0,0 +1,261 @@ +use crate::{ClientId, HasNegotiatedProtocol, NegotiatedProtocolRef}; +use linkerd_identity::{ClientConfig, Name, ServerConfig}; +use linkerd_io::{self as io, AsyncRead, AsyncWrite, PeerAddr, ReadBuf, Result}; +use std::net::SocketAddr; +use std::{ + pin::Pin, + sync::Arc, + task::{Context, Poll}, +}; + +use std::str::FromStr; +use tracing::{debug, trace, warn}; + +use { + boring::{ + fips, ssl, + ssl::{SslAcceptor, SslConnector, SslConnectorBuilder, SslMethod, SslVerifyMode}, + version, + x509::store::X509StoreBuilder, + }, + tokio_boring::SslStream, +}; + +#[derive(Clone)] +pub struct TlsConnector(ssl::SslConnector); + +impl TlsConnector { + fn new(conf: Arc) -> Result { + trace!("Setting up ssl connector"); + let mut builder = SslConnector::builder(SslMethod::tls())?; + + match conf.0.key.clone() { + None => debug!("No private key provided"), + Some(key) => { + debug!("Setting private key {:?}", key); + builder.set_private_key(key.as_ref().0.as_ref())?; + } + } + + match conf.0.cert.clone() { + None => debug!("No certificate provided"), + Some(cert) => { + trace!("Setting certificate {:?}", cert); + builder.set_certificate(cert.cert.as_ref())?; + + for cert in &cert.chain { + trace!("Adding extra chain certificate {:?}", cert); + builder.add_extra_chain_cert(cert.clone())?; + } + } + } + + builder.set_cert_store(X509StoreBuilder::new()?.build()); + conf.0 + .root_certs + .objects() + .iter() + .map(|x| x.x509().expect("Unable to get x509 certificate").to_owned()) + .for_each(|cert| { + trace!("Adding Root certificate {:?}", cert); + builder + .cert_store_mut() + .add_cert(cert) + .expect("unable to add root certificate") + }); + + Ok(builder.into()) + } + + #[cfg(not(feature = "boring-tls"))] + pub async fn connect(&self, domain: Name, stream: IO) -> Result> + where + IO: AsyncRead + AsyncWrite + Unpin, + { + let ssl = self.0.configure()?.into_ssl(domain.as_ref())?; + let mut s = TlsStream::new(ssl, stream); + Pin::new(&mut s.0).connect().await.map_err(|err| { + err.into_io_error() + .unwrap_or_else(|e| io::Error::new(io::ErrorKind::Other, e)) + })?; + + Ok(s) + } + #[cfg(feature = "boring-tls")] + pub async fn connect(&self, domain: Name, stream: IO) -> Result> + where + IO: AsyncRead + AsyncWrite + Unpin, + { + let conf = self.0.configure()?; + tokio_boring::connect(conf, domain.as_ref(), stream) + .await + .map(|ss| ss.into()) + .map_err(|_| io::Error::new(io::ErrorKind::Other, "unable to establish connection")) + } +} + +impl From for TlsConnector { + fn from(connector: SslConnector) -> Self { + Self(connector) + } +} + +impl From for TlsConnector { + fn from(builder: SslConnectorBuilder) -> Self { + builder.build().into() + } +} + +impl From> for TlsConnector { + fn from(conf: Arc) -> Self { + TlsConnector::new(conf).expect("unable to create TlsConnector") + } +} + +#[derive(Clone)] +pub struct TlsAcceptor(ssl::SslAcceptor); + +impl TlsAcceptor { + fn new(conf: Arc) -> Result { + debug!("SSL provider version {}", version::version()); + match fips::enable(true) { + Err(err) => warn!("FIPS mode can not be enabled {}", err), + _ => debug!("FIPS mode is enabled"), + } + + let mut builder = SslAcceptor::mozilla_intermediate(SslMethod::tls())?; + + let key = conf.0.key.as_ref().unwrap().as_ref().0.as_ref(); + trace!("Setting private key {:?}", key); + builder.set_private_key(key)?; + + let cert = conf.0.cert.as_ref().unwrap().as_ref(); + trace!("Setting certificate {:?}", cert); + builder.set_certificate(cert.cert.as_ref())?; + + for cert in &cert.chain { + trace!("Adding extra chain certificate {:?}", cert); + builder.add_extra_chain_cert(cert.clone())?; + } + + builder.set_cert_store(X509StoreBuilder::new()?.build()); + conf.0 + .root_certs + .objects() + .iter() + .map(|x| x.x509().expect("Unable to get x509 certificate").to_owned()) + .for_each(|cert| { + trace!("Adding Root certificate {:?}", cert); + builder + .cert_store_mut() + .add_cert(cert) + .expect("unable to add root certificate") + }); + + builder.set_verify(SslVerifyMode::PEER); + builder.check_private_key()?; + Ok(builder.build().into()) + } + + pub async fn accept(&self, stream: IO) -> Result> + where + IO: AsyncRead + AsyncWrite + Unpin, + { + tokio_boring::accept(&self.0, stream) + .await + .map(|ss| ss.into()) + .map_err(|_| io::Error::new(io::ErrorKind::Other, "unable to accept connection")) + } +} + +impl From for TlsAcceptor { + fn from(acceptor: SslAcceptor) -> Self { + Self(acceptor) + } +} + +impl From> for TlsAcceptor { + fn from(conf: Arc) -> Self { + TlsAcceptor::new(conf).expect("unable to create TlsAcceptor") + } +} + +#[derive(Debug)] +pub struct TlsStream(SslStream); + +impl TlsStream { + pub fn get_alpn_protocol(&self) -> Option<&[u8]> { + self.0.ssl().selected_alpn_protocol() + } + + pub fn client_identity(&self) -> Option { + let cert = self.0.ssl().peer_certificate()?; + let peer = cert.subject_alt_names()?.pop()?; + + ClientId::from_str(peer.dnsname()?).ok() + } +} + +impl From> for TlsStream { + fn from(stream: SslStream) -> Self { + TlsStream(stream) + } +} + +impl PeerAddr for TlsStream { + fn peer_addr(&self) -> Result { + self.0.get_ref().peer_addr() + } +} + +impl HasNegotiatedProtocol for TlsStream { + #[inline] + fn negotiated_protocol(&self) -> Option> { + self.0 + .ssl() + .selected_alpn_protocol() + .map(NegotiatedProtocolRef) + } +} + +impl AsyncRead for TlsStream +where + IO: AsyncRead + AsyncWrite + Unpin, +{ + fn poll_read( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &mut ReadBuf<'_>, + ) -> Poll> { + Pin::new(&mut self.0).poll_read(cx, buf) + } +} + +impl AsyncWrite for TlsStream +where + IO: AsyncRead + AsyncWrite + Unpin, +{ + fn poll_write( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &[u8], + ) -> Poll> { + Pin::new(&mut self.0).poll_write(cx, buf) + } + + fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + Pin::new(&mut self.0).poll_flush(cx) + } + + fn poll_shutdown(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + Pin::new(&mut self.0).poll_shutdown(cx) + } +} + +pub mod client { + pub use super::TlsStream; +} + +pub mod server { + pub use super::TlsStream; +} diff --git a/linkerd/tls/src/imp/rustls.rs b/linkerd/tls/src/imp/rustls.rs new file mode 100644 index 0000000000..d1406f1697 --- /dev/null +++ b/linkerd/tls/src/imp/rustls.rs @@ -0,0 +1,258 @@ +use futures::Future; +use linkerd_identity::{ClientConfig, Name, ServerConfig}; +use linkerd_io::{AsyncRead, AsyncWrite, Result}; +use std::{ + pin::Pin, + sync::Arc, + task::{Context, Poll}, +}; +use webpki::DNSNameRef; + +#[derive(Clone)] +pub struct TlsConnector(tokio_rustls::TlsConnector); + +impl TlsConnector { + pub fn connect(&self, domain: Name, stream: IO) -> Connect + where + IO: AsyncRead + AsyncWrite + Unpin, + { + let dns = DNSNameRef::try_from_ascii_str(domain.as_ref()).unwrap(); + Connect(self.0.connect(dns, stream)) + } +} + +impl From for TlsConnector { + fn from(connector: tokio_rustls::TlsConnector) -> Self { + TlsConnector(connector) + } +} + +impl From> for TlsConnector { + fn from(conf: Arc) -> Self { + let rustls_config: Arc = conf.as_ref().clone().0.into(); + tokio_rustls::TlsConnector::from(rustls_config).into() + } +} + +pub struct Connect(tokio_rustls::Connect); + +impl Future for Connect { + type Output = Result>; + + #[inline] + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + Pin::new(&mut self.0).poll(cx).map(|f| f.map(|s| s.into())) + } +} + +#[derive(Clone)] +pub struct TlsAcceptor(tokio_rustls::TlsAcceptor); + +impl TlsAcceptor { + pub fn accept(&self, stream: IO) -> Accept + where + IO: AsyncRead + AsyncWrite + Unpin, + { + Accept(self.0.accept(stream)) + } +} + +impl From for TlsAcceptor { + fn from(acceptor: tokio_rustls::TlsAcceptor) -> Self { + TlsAcceptor(acceptor) + } +} + +impl From> for TlsAcceptor { + fn from(conf: Arc) -> Self { + let rustls_config: Arc = conf.as_ref().clone().0.into(); + tokio_rustls::TlsAcceptor::from(rustls_config).into() + } +} + +pub mod client { + use std::{ + net::SocketAddr, + pin::Pin, + task::{Context, Poll}, + }; + + use linkerd_io::{AsyncRead, AsyncWrite, PeerAddr, ReadBuf, Result}; + use rustls::Session; + + use crate::{HasNegotiatedProtocol, NegotiatedProtocolRef}; + + #[derive(Debug)] + pub struct TlsStream(tokio_rustls::client::TlsStream); + + impl TlsStream { + pub fn get_alpn_protocol(&self) -> Option<&[u8]> { + self.0.get_ref().1.get_alpn_protocol() + } + } + + impl From> for TlsStream { + fn from(stream: tokio_rustls::client::TlsStream) -> Self { + TlsStream(stream) + } + } + + impl PeerAddr for TlsStream { + fn peer_addr(&self) -> Result { + self.0.get_ref().0.peer_addr() + } + } + + impl HasNegotiatedProtocol for TlsStream { + #[inline] + fn negotiated_protocol(&self) -> Option> { + self.0 + .get_ref() + .1 + .get_alpn_protocol() + .map(NegotiatedProtocolRef) + } + } + + impl AsyncRead for TlsStream + where + IO: AsyncRead + AsyncWrite + Unpin, + { + fn poll_read( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &mut ReadBuf<'_>, + ) -> Poll> { + Pin::new(&mut self.0).poll_read(cx, buf) + } + } + + impl AsyncWrite for TlsStream + where + IO: AsyncRead + AsyncWrite + Unpin, + { + fn poll_write( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &[u8], + ) -> Poll> { + Pin::new(&mut self.0).poll_write(cx, buf) + } + + fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + Pin::new(&mut self.0).poll_flush(cx) + } + + fn poll_shutdown(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + Pin::new(&mut self.0).poll_shutdown(cx) + } + } +} + +pub mod server { + use std::{ + net::SocketAddr, + pin::Pin, + task::{Context, Poll}, + }; + + use linkerd_dns_name as dns; + use linkerd_identity::Name; + use linkerd_io::{AsyncRead, AsyncWrite, PeerAddr, ReadBuf, Result}; + use rustls::Session; + + use crate::{ClientId, HasNegotiatedProtocol, NegotiatedProtocolRef}; + + #[derive(Debug)] + pub struct TlsStream(tokio_rustls::server::TlsStream); + + impl TlsStream { + pub fn client_identity(&self) -> Option { + use webpki::GeneralDNSNameRef; + + let (_io, session) = self.0.get_ref(); + let certs = session.get_peer_certificates()?; + let c = certs.first().map(rustls::Certificate::as_ref)?; + let end_cert = webpki::EndEntityCert::from(c).ok()?; + let dns_names = end_cert.dns_names().ok()?; + + match dns_names.first()? { + GeneralDNSNameRef::DNSName(n) => { + Some(ClientId(Name::from(dns::Name::from(n.to_owned())))) + } + GeneralDNSNameRef::Wildcard(_) => { + // Wildcards can perhaps be handled in a future path... + None + } + } + } + } + + impl From> for TlsStream { + fn from(stream: tokio_rustls::server::TlsStream) -> Self { + TlsStream(stream) + } + } + + impl PeerAddr for TlsStream { + fn peer_addr(&self) -> Result { + self.0.get_ref().0.peer_addr() + } + } + + impl AsyncRead for TlsStream + where + IO: AsyncRead + AsyncWrite + Unpin, + { + fn poll_read( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &mut ReadBuf<'_>, + ) -> Poll> { + Pin::new(&mut self.0).poll_read(cx, buf) + } + } + + impl AsyncWrite for TlsStream + where + IO: AsyncRead + AsyncWrite + Unpin, + { + fn poll_write( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &[u8], + ) -> Poll> { + Pin::new(&mut self.0).poll_write(cx, buf) + } + + fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + Pin::new(&mut self.0).poll_flush(cx) + } + + fn poll_shutdown(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + Pin::new(&mut self.0).poll_shutdown(cx) + } + } + + impl HasNegotiatedProtocol for TlsStream { + #[inline] + fn negotiated_protocol(&self) -> Option> { + self.0 + .get_ref() + .1 + .get_alpn_protocol() + .map(|b| NegotiatedProtocolRef(b)) + } + } +} + +pub struct Accept(tokio_rustls::Accept); + +impl Future for Accept { + type Output = Result>; + + #[inline] + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + Pin::new(&mut self.0).poll(cx).map(|f| f.map(|s| s.into())) + } +} diff --git a/linkerd/tls/src/lib.rs b/linkerd/tls/src/lib.rs index b9e17d7251..05f6b91681 100755 --- a/linkerd/tls/src/lib.rs +++ b/linkerd/tls/src/lib.rs @@ -2,114 +2,73 @@ #![forbid(unsafe_code)] pub use linkerd_identity::LocalId; -use linkerd_io as io; -pub use tokio_rustls::rustls::Session; +use linkerd_identity::{ClientConfig, Name, ServerConfig}; +use linkerd_io::{AsyncRead, AsyncWrite, Result}; + +#[cfg(feature = "rustls-tls")] +#[path = "imp/rustls.rs"] +mod imp; +#[cfg(not(feature = "rustls-tls"))] +#[path = "imp/boringssl.rs"] +mod imp; + +mod protocol; +// pub use tokio_rustls::rustls::Session; pub mod client; pub mod server; pub use self::{ client::{Client, ClientTls, ConditionalClientTls, NoClientTls, ServerId}, + protocol::{HasNegotiatedProtocol, NegotiatedProtocol, NegotiatedProtocolRef}, server::{ClientId, ConditionalServerTls, NewDetectTls, NoServerTls, ServerTls}, }; +use std::sync::Arc; -/// A trait implented by transport streams to indicate its negotiated protocol. -pub trait HasNegotiatedProtocol { - fn negotiated_protocol(&self) -> Option>; -} - -#[derive(Clone, Eq, PartialEq, Hash)] -pub struct NegotiatedProtocol(pub Vec); - -/// Indicates a negotiated protocol. -#[derive(Copy, Clone, Eq, PartialEq, Hash)] -pub struct NegotiatedProtocolRef<'t>(pub &'t [u8]); +#[derive(Clone)] +pub struct TlsConnector(imp::TlsConnector); -impl NegotiatedProtocol { - pub fn as_ref(&self) -> NegotiatedProtocolRef<'_> { - NegotiatedProtocolRef(&self.0) +impl TlsConnector { + pub async fn connect(&self, domain: Name, stream: IO) -> Result> + where + IO: AsyncRead + AsyncWrite + Unpin, + { + self.0.connect(domain, stream).await.map(|s| s.into()) } } -impl std::fmt::Debug for NegotiatedProtocol { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - NegotiatedProtocolRef(&self.0).fmt(f) +impl From for TlsConnector { + fn from(connector: imp::TlsConnector) -> Self { + TlsConnector(connector) } } -impl NegotiatedProtocolRef<'_> { - pub fn to_owned(self) -> NegotiatedProtocol { - NegotiatedProtocol(self.0.into()) +impl From> for TlsConnector { + fn from(conf: Arc) -> Self { + imp::TlsConnector::from(conf).into() } } -impl From> for NegotiatedProtocol { - fn from(protocol: NegotiatedProtocolRef<'_>) -> NegotiatedProtocol { - protocol.to_owned() - } -} - -impl std::fmt::Debug for NegotiatedProtocolRef<'_> { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match std::str::from_utf8(self.0) { - Ok(s) => s.fmt(f), - Err(_) => self.0.fmt(f), - } - } -} - -impl HasNegotiatedProtocol for self::client::TlsStream { - #[inline] - fn negotiated_protocol(&self) -> Option> { - self.get_ref() - .1 - .get_alpn_protocol() - .map(NegotiatedProtocolRef) - } -} - -impl HasNegotiatedProtocol for self::server::TlsStream { - #[inline] - fn negotiated_protocol(&self) -> Option> { - self.get_ref() - .1 - .get_alpn_protocol() - .map(NegotiatedProtocolRef) - } -} - -impl HasNegotiatedProtocol for tokio::net::TcpStream { - #[inline] - fn negotiated_protocol(&self) -> Option> { - None - } -} +#[derive(Clone)] +pub struct TlsAcceptor(imp::TlsAcceptor); -impl HasNegotiatedProtocol for io::ScopedIo { - #[inline] - fn negotiated_protocol(&self) -> Option> { - self.get_ref().negotiated_protocol() +impl TlsAcceptor { + pub async fn accept(&self, stream: IO) -> Result> + where + IO: AsyncRead + AsyncWrite + Unpin, + { + self.0.accept(stream).await.map(|s| s.into()) } } -impl HasNegotiatedProtocol for io::EitherIo -where - L: HasNegotiatedProtocol, - R: HasNegotiatedProtocol, -{ - #[inline] - fn negotiated_protocol(&self) -> Option> { - match self { - io::EitherIo::Left(l) => l.negotiated_protocol(), - io::EitherIo::Right(r) => r.negotiated_protocol(), - } +impl From for TlsAcceptor { + fn from(acceptor: imp::TlsAcceptor) -> Self { + TlsAcceptor(acceptor) } } -/// Needed for tests. -impl HasNegotiatedProtocol for io::BoxedIo { - #[inline] - fn negotiated_protocol(&self) -> Option> { - None +impl From> for TlsAcceptor { + fn from(conf: Arc) -> Self { + imp::TlsAcceptor::from(conf).into() } } diff --git a/linkerd/tls/src/protocol.rs b/linkerd/tls/src/protocol.rs new file mode 100644 index 0000000000..1cc43e90aa --- /dev/null +++ b/linkerd/tls/src/protocol.rs @@ -0,0 +1,81 @@ +use linkerd_io as io; +/// A trait implented by transport streams to indicate its negotiated protocol. +pub trait HasNegotiatedProtocol { + fn negotiated_protocol(&self) -> Option>; +} + +#[derive(Clone, Eq, PartialEq, Hash)] +pub struct NegotiatedProtocol(pub Vec); + +/// Indicates a negotiated protocol. +#[derive(Copy, Clone, Eq, PartialEq, Hash)] +pub struct NegotiatedProtocolRef<'t>(pub &'t [u8]); + +impl NegotiatedProtocol { + pub fn as_ref(&self) -> NegotiatedProtocolRef<'_> { + NegotiatedProtocolRef(&self.0) + } +} + +impl std::fmt::Debug for NegotiatedProtocol { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + NegotiatedProtocolRef(&self.0).fmt(f) + } +} + +impl NegotiatedProtocolRef<'_> { + pub fn to_owned(&self) -> NegotiatedProtocol { + NegotiatedProtocol(self.0.into()) + } +} + +impl From> for NegotiatedProtocol { + fn from(npr: NegotiatedProtocolRef<'_>) -> NegotiatedProtocol { + npr.to_owned() + } +} + +impl std::fmt::Debug for NegotiatedProtocolRef<'_> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match std::str::from_utf8(self.0) { + Ok(s) => s.fmt(f), + Err(_) => self.0.fmt(f), + } + } +} + +impl HasNegotiatedProtocol for tokio::net::TcpStream { + #[inline] + fn negotiated_protocol(&self) -> Option> { + None + } +} + +impl HasNegotiatedProtocol for io::ScopedIo { + #[inline] + fn negotiated_protocol(&self) -> Option> { + self.get_ref().negotiated_protocol() + } +} + +impl HasNegotiatedProtocol for io::EitherIo +where + L: HasNegotiatedProtocol, + R: HasNegotiatedProtocol, +{ + #[inline] + fn negotiated_protocol(&self) -> Option> { + match self { + io::EitherIo::Left(l) => l.negotiated_protocol(), + io::EitherIo::Right(r) => r.negotiated_protocol(), + } + } +} + +/// Needed for tests. +impl HasNegotiatedProtocol for io::BoxedIo { + #[inline] + fn negotiated_protocol(&self) -> Option> { + None + } +} diff --git a/linkerd/tls/src/server/mod.rs b/linkerd/tls/src/server/mod.rs index 437ca7d0cd..1950b422d7 100644 --- a/linkerd/tls/src/server/mod.rs +++ b/linkerd/tls/src/server/mod.rs @@ -1,16 +1,19 @@ mod client_hello; -use crate::{LocalId, NegotiatedProtocol, ServerId}; +use crate::{ + HasNegotiatedProtocol, LocalId, NegotiatedProtocol, NegotiatedProtocolRef, ServerId, + TlsAcceptor, +}; use bytes::BytesMut; use futures::prelude::*; use linkerd_conditional::Conditional; -use linkerd_dns_name as dns; use linkerd_error::Error; use linkerd_identity as id; -use linkerd_io::{self as io, AsyncReadExt, EitherIo, PrefixedIo}; +use linkerd_io::{self as io, AsyncReadExt, EitherIo, PeerAddr, PrefixedIo, ReadBuf}; use linkerd_stack::{layer, ExtractParam, InsertParam, NewService, Param}; use std::{ fmt, + net::SocketAddr, pin::Pin, str::FromStr, sync::Arc, @@ -18,17 +21,78 @@ use std::{ }; use thiserror::Error; use tokio::time::{self, Duration}; -use tokio_rustls::rustls::{self, Session}; -pub use tokio_rustls::server::TlsStream; use tower::util::ServiceExt; use tracing::{debug, trace, warn}; -pub type Config = Arc; +use crate::imp; + +#[derive(Debug)] +pub struct TlsStream(imp::server::TlsStream); + +impl TlsStream { + pub fn client_identity(&self) -> Option { + self.0.client_identity() + } +} + +impl From> for TlsStream { + fn from(stream: imp::server::TlsStream) -> Self { + TlsStream(stream) + } +} + +impl io::AsyncRead for TlsStream +where + IO: io::AsyncRead + io::AsyncWrite + Unpin, +{ + fn poll_read( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &mut ReadBuf<'_>, + ) -> Poll> { + Pin::new(&mut self.0).poll_read(cx, buf) + } +} + +impl io::AsyncWrite for TlsStream +where + IO: io::AsyncRead + io::AsyncWrite + Unpin, +{ + fn poll_write( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &[u8], + ) -> Poll> { + Pin::new(&mut self.0).poll_write(cx, buf) + } + + fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + Pin::new(&mut self.0).poll_flush(cx) + } + + fn poll_shutdown(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + Pin::new(&mut self.0).poll_shutdown(cx) + } +} + +impl PeerAddr for TlsStream { + fn peer_addr(&self) -> io::Result { + self.0.peer_addr() + } +} + +impl HasNegotiatedProtocol for TlsStream { + #[inline] + fn negotiated_protocol(&self) -> Option> { + self.0.negotiated_protocol() + } +} + +pub type Config = Arc; /// Produces a server config that fails to handshake all connections. pub fn empty_config() -> Config { - let verifier = rustls::NoClientAuth::new(); - Arc::new(rustls::ServerConfig::new(verifier)) + Arc::new(id::ServerConfig::empty()) } /// A newtype for remote client idenities. @@ -272,18 +336,12 @@ async fn handshake(tls_config: Config, io: T) -> io::Result<(ServerTls, TlsSt where T: io::AsyncRead + io::AsyncWrite + Unpin, { - let io = tokio_rustls::TlsAcceptor::from(tls_config) - .accept(io) - .await?; + let io = TlsAcceptor::from(tls_config).accept(io).await?; // Determine the peer's identity, if it exist. - let client_id = client_identity(&io); - - let negotiated_protocol = io - .get_ref() - .1 - .get_alpn_protocol() - .map(|b| NegotiatedProtocol(b.into())); + let client_id = io.client_identity(); + // Extract the negotiated protocol for the stream. + let negotiated_protocol = io.negotiated_protocol().map(|p| p.to_owned()); debug!(client.id = ?client_id, alpn = ?negotiated_protocol, "Accepted TLS connection"); let tls = ServerTls::Established { @@ -293,26 +351,6 @@ where Ok((tls, io)) } -fn client_identity(tls: &TlsStream) -> Option { - use webpki::GeneralDNSNameRef; - - let (_io, session) = tls.get_ref(); - let certs = session.get_peer_certificates()?; - let c = certs.first().map(rustls::Certificate::as_ref)?; - let end_cert = webpki::EndEntityCert::from(c).ok()?; - let dns_names = end_cert.dns_names().ok()?; - - match dns_names.first()? { - GeneralDNSNameRef::DNSName(n) => { - Some(ClientId(id::Name::from(dns::Name::from(n.to_owned())))) - } - GeneralDNSNameRef::Wildcard(_) => { - // Wildcards can perhaps be handled in a future path... - None - } - } -} - // === impl ClientId === impl From for ClientId { diff --git a/linkerd2-proxy/Cargo.toml b/linkerd2-proxy/Cargo.toml index 0222d01a95..99d43c2379 100644 --- a/linkerd2-proxy/Cargo.toml +++ b/linkerd2-proxy/Cargo.toml @@ -8,8 +8,10 @@ publish = false description = "The main proxy executable" [features] -default = ["multicore"] +default = ["multicore", "rustls-tls"] multicore = ["tokio/rt-multi-thread", "num_cpus"] +rustls-tls = ["linkerd-app/rustls-tls"] +boring-tls = ["linkerd-app/boring-tls"] [dependencies] futures = { version = "0.3", default-features = false }