a (hacky, wip) multi-tenant oidc-terminating reverse proxy, written in anger on top of pingora

Compare changes

Choose any two refs to compare.

+905 -10
+390 -7
Cargo.lock
··· 117 117 ] 118 118 119 119 [[package]] 120 + name = "anyhow" 121 + version = "1.0.101" 122 + source = "registry+https://github.com/rust-lang/crates.io-index" 123 + checksum = "5f0e0fee31ef5ed1ba1316088939cea399010ed7731dba877ed44aeb407a75ea" 124 + 125 + [[package]] 120 126 name = "arc-swap" 121 127 version = "1.8.1" 122 128 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 192 198 version = "0.1.0" 193 199 dependencies = [ 194 200 "async-trait", 201 + "color-eyre", 202 + "http", 195 203 "pingora", 204 + "prost", 205 + "prost-reflect", 206 + "prost-reflect-build", 207 + "rustls", 208 + "tokio", 209 + "tracing", 210 + "tracing-subscriber", 196 211 ] 197 212 198 213 [[package]] ··· 243 258 version = "0.22.1" 244 259 source = "registry+https://github.com/rust-lang/crates.io-index" 245 260 checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" 261 + 262 + [[package]] 263 + name = "beef" 264 + version = "0.5.2" 265 + source = "registry+https://github.com/rust-lang/crates.io-index" 266 + checksum = "3a8241f3ebb85c056b509d4327ad0358fbbba6ffb340bf388f26350aeda225b1" 246 267 247 268 [[package]] 248 269 name = "bitflags" ··· 423 444 ] 424 445 425 446 [[package]] 447 + name = "color-eyre" 448 + version = "0.6.5" 449 + source = "registry+https://github.com/rust-lang/crates.io-index" 450 + checksum = "e5920befb47832a6d61ee3a3a846565cfa39b331331e68a3b1d1116630f2f26d" 451 + dependencies = [ 452 + "backtrace", 453 + "color-spantrace", 454 + "eyre", 455 + "indenter", 456 + "once_cell", 457 + "owo-colors", 458 + "tracing-error", 459 + ] 460 + 461 + [[package]] 462 + name = "color-spantrace" 463 + version = "0.3.0" 464 + source = "registry+https://github.com/rust-lang/crates.io-index" 465 + checksum = "b8b88ea9df13354b55bc7234ebcce36e6ef896aca2e42a15de9e10edce01b427" 466 + dependencies = [ 467 + "once_cell", 468 + "owo-colors", 469 + "tracing-core", 470 + "tracing-error", 471 + ] 472 + 473 + [[package]] 426 474 name = "colorchoice" 427 475 version = "1.0.4" 428 476 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 644 692 ] 645 693 646 694 [[package]] 695 + name = "eyre" 696 + version = "0.6.12" 697 + source = "registry+https://github.com/rust-lang/crates.io-index" 698 + checksum = "7cd915d99f24784cdc19fd37ef22b97e3ff0ae756c7e492e9fbfe897d61e2aec" 699 + dependencies = [ 700 + "indenter", 701 + "once_cell", 702 + ] 703 + 704 + [[package]] 705 + name = "fastrand" 706 + version = "2.3.0" 707 + source = "registry+https://github.com/rust-lang/crates.io-index" 708 + checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" 709 + 710 + [[package]] 647 711 name = "find-msvc-tools" 648 712 version = "0.1.9" 649 713 source = "registry+https://github.com/rust-lang/crates.io-index" 650 714 checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" 715 + 716 + [[package]] 717 + name = "fixedbitset" 718 + version = "0.5.7" 719 + source = "registry+https://github.com/rust-lang/crates.io-index" 720 + checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" 651 721 652 722 [[package]] 653 723 name = "flate2" ··· 665 735 version = "1.0.7" 666 736 source = "registry+https://github.com/rust-lang/crates.io-index" 667 737 checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 738 + 739 + [[package]] 740 + name = "foldhash" 741 + version = "0.1.5" 742 + source = "registry+https://github.com/rust-lang/crates.io-index" 743 + checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" 668 744 669 745 [[package]] 670 746 name = "foldhash" ··· 845 921 846 922 [[package]] 847 923 name = "hashbrown" 924 + version = "0.15.5" 925 + source = "registry+https://github.com/rust-lang/crates.io-index" 926 + checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" 927 + dependencies = [ 928 + "foldhash 0.1.5", 929 + ] 930 + 931 + [[package]] 932 + name = "hashbrown" 848 933 version = "0.16.1" 849 934 source = "registry+https://github.com/rust-lang/crates.io-index" 850 935 checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" 851 936 dependencies = [ 852 937 "allocator-api2", 853 938 "equivalent", 854 - "foldhash", 939 + "foldhash 0.2.0", 855 940 ] 856 941 857 942 [[package]] ··· 912 997 checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" 913 998 914 999 [[package]] 1000 + name = "indenter" 1001 + version = "0.3.4" 1002 + source = "registry+https://github.com/rust-lang/crates.io-index" 1003 + checksum = "964de6e86d545b246d84badc0fef527924ace5134f30641c203ef52ba83f58d5" 1004 + 1005 + [[package]] 915 1006 name = "indexmap" 916 1007 version = "1.9.3" 917 1008 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 938 1029 checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" 939 1030 940 1031 [[package]] 1032 + name = "itertools" 1033 + version = "0.14.0" 1034 + source = "registry+https://github.com/rust-lang/crates.io-index" 1035 + checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" 1036 + dependencies = [ 1037 + "either", 1038 + ] 1039 + 1040 + [[package]] 941 1041 name = "itoa" 942 1042 version = "1.0.17" 943 1043 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 976 1076 ] 977 1077 978 1078 [[package]] 1079 + name = "linux-raw-sys" 1080 + version = "0.11.0" 1081 + source = "registry+https://github.com/rust-lang/crates.io-index" 1082 + checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" 1083 + 1084 + [[package]] 979 1085 name = "local-ip-address" 980 1086 version = "0.6.10" 981 1087 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 1002 1108 checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" 1003 1109 1004 1110 [[package]] 1111 + name = "logos" 1112 + version = "0.15.1" 1113 + source = "registry+https://github.com/rust-lang/crates.io-index" 1114 + checksum = "ff472f899b4ec2d99161c51f60ff7075eeb3097069a36050d8037a6325eb8154" 1115 + dependencies = [ 1116 + "logos-derive", 1117 + ] 1118 + 1119 + [[package]] 1120 + name = "logos-codegen" 1121 + version = "0.15.1" 1122 + source = "registry+https://github.com/rust-lang/crates.io-index" 1123 + checksum = "192a3a2b90b0c05b27a0b2c43eecdb7c415e29243acc3f89cc8247a5b693045c" 1124 + dependencies = [ 1125 + "beef", 1126 + "fnv", 1127 + "lazy_static", 1128 + "proc-macro2", 1129 + "quote", 1130 + "regex-syntax", 1131 + "rustc_version", 1132 + "syn 2.0.114", 1133 + ] 1134 + 1135 + [[package]] 1136 + name = "logos-derive" 1137 + version = "0.15.1" 1138 + source = "registry+https://github.com/rust-lang/crates.io-index" 1139 + checksum = "605d9697bcd5ef3a42d38efc51541aa3d6a4a25f7ab6d1ed0da5ac632a26b470" 1140 + dependencies = [ 1141 + "logos-codegen", 1142 + ] 1143 + 1144 + [[package]] 1005 1145 name = "lru" 1006 1146 version = "0.16.3" 1007 1147 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 1011 1151 ] 1012 1152 1013 1153 [[package]] 1154 + name = "matchers" 1155 + version = "0.2.0" 1156 + source = "registry+https://github.com/rust-lang/crates.io-index" 1157 + checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" 1158 + dependencies = [ 1159 + "regex-automata", 1160 + ] 1161 + 1162 + [[package]] 1014 1163 name = "memchr" 1015 - version = "2.7.6" 1164 + version = "2.8.0" 1016 1165 source = "registry+https://github.com/rust-lang/crates.io-index" 1017 - checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" 1166 + checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" 1018 1167 1019 1168 [[package]] 1020 1169 name = "memoffset" ··· 1051 1200 "wasi", 1052 1201 "windows-sys 0.61.2", 1053 1202 ] 1203 + 1204 + [[package]] 1205 + name = "multimap" 1206 + version = "0.10.1" 1207 + source = "registry+https://github.com/rust-lang/crates.io-index" 1208 + checksum = "1d87ecb2933e8aeadb3e3a02b828fed80a7528047e68b4f424523a0981a3a084" 1054 1209 1055 1210 [[package]] 1056 1211 name = "neli" ··· 1110 1265 ] 1111 1266 1112 1267 [[package]] 1268 + name = "nu-ansi-term" 1269 + version = "0.50.3" 1270 + source = "registry+https://github.com/rust-lang/crates.io-index" 1271 + checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" 1272 + dependencies = [ 1273 + "windows-sys 0.61.2", 1274 + ] 1275 + 1276 + [[package]] 1113 1277 name = "num-bigint" 1114 1278 version = "0.4.6" 1115 1279 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 1204 1368 ] 1205 1369 1206 1370 [[package]] 1371 + name = "owo-colors" 1372 + version = "4.2.3" 1373 + source = "registry+https://github.com/rust-lang/crates.io-index" 1374 + checksum = "9c6901729fa79e91a0913333229e9ca5dc725089d1c363b2f4b4760709dc4a52" 1375 + 1376 + [[package]] 1207 1377 name = "parking_lot" 1208 1378 version = "0.12.5" 1209 1379 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 1231 1401 version = "2.3.2" 1232 1402 source = "registry+https://github.com/rust-lang/crates.io-index" 1233 1403 checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" 1404 + 1405 + [[package]] 1406 + name = "petgraph" 1407 + version = "0.8.3" 1408 + source = "registry+https://github.com/rust-lang/crates.io-index" 1409 + checksum = "8701b58ea97060d5e5b155d383a69952a60943f0e6dfe30b04c287beb0b27455" 1410 + dependencies = [ 1411 + "fixedbitset", 1412 + "hashbrown 0.15.5", 1413 + "indexmap 2.13.0", 1414 + ] 1234 1415 1235 1416 [[package]] 1236 1417 name = "pin-project-lite" ··· 1526 1707 ] 1527 1708 1528 1709 [[package]] 1710 + name = "prettyplease" 1711 + version = "0.2.37" 1712 + source = "registry+https://github.com/rust-lang/crates.io-index" 1713 + checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" 1714 + dependencies = [ 1715 + "proc-macro2", 1716 + "syn 2.0.114", 1717 + ] 1718 + 1719 + [[package]] 1529 1720 name = "proc-macro-error-attr2" 1530 1721 version = "2.0.0" 1531 1722 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 1585 1776 ] 1586 1777 1587 1778 [[package]] 1779 + name = "prost" 1780 + version = "0.14.3" 1781 + source = "registry+https://github.com/rust-lang/crates.io-index" 1782 + checksum = "d2ea70524a2f82d518bce41317d0fae74151505651af45faf1ffbd6fd33f0568" 1783 + dependencies = [ 1784 + "bytes", 1785 + "prost-derive", 1786 + ] 1787 + 1788 + [[package]] 1789 + name = "prost-build" 1790 + version = "0.14.3" 1791 + source = "registry+https://github.com/rust-lang/crates.io-index" 1792 + checksum = "343d3bd7056eda839b03204e68deff7d1b13aba7af2b2fd16890697274262ee7" 1793 + dependencies = [ 1794 + "heck 0.5.0", 1795 + "itertools", 1796 + "log", 1797 + "multimap", 1798 + "petgraph", 1799 + "prettyplease", 1800 + "prost", 1801 + "prost-types", 1802 + "regex", 1803 + "syn 2.0.114", 1804 + "tempfile", 1805 + ] 1806 + 1807 + [[package]] 1808 + name = "prost-derive" 1809 + version = "0.14.3" 1810 + source = "registry+https://github.com/rust-lang/crates.io-index" 1811 + checksum = "27c6023962132f4b30eb4c172c91ce92d933da334c59c23cddee82358ddafb0b" 1812 + dependencies = [ 1813 + "anyhow", 1814 + "itertools", 1815 + "proc-macro2", 1816 + "quote", 1817 + "syn 2.0.114", 1818 + ] 1819 + 1820 + [[package]] 1821 + name = "prost-reflect" 1822 + version = "0.16.3" 1823 + source = "registry+https://github.com/rust-lang/crates.io-index" 1824 + checksum = "b89455ef41ed200cafc47c76c552ee7792370ac420497e551f16123a9135f76e" 1825 + dependencies = [ 1826 + "logos", 1827 + "prost", 1828 + "prost-reflect-derive", 1829 + "prost-types", 1830 + ] 1831 + 1832 + [[package]] 1833 + name = "prost-reflect-build" 1834 + version = "0.16.0" 1835 + source = "registry+https://github.com/rust-lang/crates.io-index" 1836 + checksum = "8214ae2c30bbac390db0134d08300e770ef89b6d4e5abf855e8d300eded87e28" 1837 + dependencies = [ 1838 + "prost-build", 1839 + "prost-reflect", 1840 + ] 1841 + 1842 + [[package]] 1843 + name = "prost-reflect-derive" 1844 + version = "0.16.0" 1845 + source = "registry+https://github.com/rust-lang/crates.io-index" 1846 + checksum = "7b6d90e29fa6c0d13c2c19ba5e4b3fb0efbf5975d27bcf4e260b7b15455bcabe" 1847 + dependencies = [ 1848 + "proc-macro2", 1849 + "quote", 1850 + "syn 2.0.114", 1851 + ] 1852 + 1853 + [[package]] 1854 + name = "prost-types" 1855 + version = "0.14.3" 1856 + source = "registry+https://github.com/rust-lang/crates.io-index" 1857 + checksum = "8991c4cbdb8bc5b11f0b074ffe286c30e523de90fee5ba8132f1399f23cb3dd7" 1858 + dependencies = [ 1859 + "prost", 1860 + ] 1861 + 1862 + [[package]] 1588 1863 name = "protobuf" 1589 1864 version = "2.28.0" 1590 1865 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 1752 2027 checksum = "b50b8869d9fc858ce7266cce0194bd74df58b9d0e3f6df3a9fc8eb470d95c09d" 1753 2028 1754 2029 [[package]] 2030 + name = "rustc_version" 2031 + version = "0.4.1" 2032 + source = "registry+https://github.com/rust-lang/crates.io-index" 2033 + checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" 2034 + dependencies = [ 2035 + "semver", 2036 + ] 2037 + 2038 + [[package]] 1755 2039 name = "rusticata-macros" 1756 2040 version = "4.1.0" 1757 2041 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 1761 2045 ] 1762 2046 1763 2047 [[package]] 2048 + name = "rustix" 2049 + version = "1.1.3" 2050 + source = "registry+https://github.com/rust-lang/crates.io-index" 2051 + checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34" 2052 + dependencies = [ 2053 + "bitflags 2.10.0", 2054 + "errno", 2055 + "libc", 2056 + "linux-raw-sys", 2057 + "windows-sys 0.61.2", 2058 + ] 2059 + 2060 + [[package]] 1764 2061 name = "rustls" 1765 2062 version = "0.23.36" 1766 2063 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 1826 2123 1827 2124 [[package]] 1828 2125 name = "ryu" 1829 - version = "1.0.22" 2126 + version = "1.0.23" 1830 2127 source = "registry+https://github.com/rust-lang/crates.io-index" 1831 - checksum = "a50f4cf475b65d88e057964e0e9bb1f0aa9bbb2036dc65c64596b42932536984" 2128 + checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" 1832 2129 1833 2130 [[package]] 1834 2131 name = "schannel" ··· 1869 2166 ] 1870 2167 1871 2168 [[package]] 2169 + name = "semver" 2170 + version = "1.0.27" 2171 + source = "registry+https://github.com/rust-lang/crates.io-index" 2172 + checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" 2173 + 2174 + [[package]] 1872 2175 name = "serde" 1873 2176 version = "1.0.228" 1874 2177 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 1920 2223 "base64", 1921 2224 "indexmap 2.13.0", 1922 2225 "rust_decimal", 2226 + ] 2227 + 2228 + [[package]] 2229 + name = "sharded-slab" 2230 + version = "0.1.7" 2231 + source = "registry+https://github.com/rust-lang/crates.io-index" 2232 + checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" 2233 + dependencies = [ 2234 + "lazy_static", 1923 2235 ] 1924 2236 1925 2237 [[package]] ··· 2040 2352 ] 2041 2353 2042 2354 [[package]] 2355 + name = "tempfile" 2356 + version = "3.24.0" 2357 + source = "registry+https://github.com/rust-lang/crates.io-index" 2358 + checksum = "655da9c7eb6305c55742045d5a8d2037996d61d8de95806335c7c86ce0f82e9c" 2359 + dependencies = [ 2360 + "fastrand", 2361 + "getrandom 0.3.4", 2362 + "once_cell", 2363 + "rustix", 2364 + "windows-sys 0.61.2", 2365 + ] 2366 + 2367 + [[package]] 2043 2368 name = "thiserror" 2044 2369 version = "1.0.69" 2045 2370 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 2188 2513 checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" 2189 2514 dependencies = [ 2190 2515 "pin-project-lite", 2516 + "tracing-attributes", 2191 2517 "tracing-core", 2192 2518 ] 2193 2519 2194 2520 [[package]] 2521 + name = "tracing-attributes" 2522 + version = "0.1.31" 2523 + source = "registry+https://github.com/rust-lang/crates.io-index" 2524 + checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" 2525 + dependencies = [ 2526 + "proc-macro2", 2527 + "quote", 2528 + "syn 2.0.114", 2529 + ] 2530 + 2531 + [[package]] 2195 2532 name = "tracing-core" 2196 2533 version = "0.1.36" 2197 2534 source = "registry+https://github.com/rust-lang/crates.io-index" 2198 2535 checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" 2199 2536 dependencies = [ 2200 2537 "once_cell", 2538 + "valuable", 2539 + ] 2540 + 2541 + [[package]] 2542 + name = "tracing-error" 2543 + version = "0.2.1" 2544 + source = "registry+https://github.com/rust-lang/crates.io-index" 2545 + checksum = "8b1581020d7a273442f5b45074a6a57d5757ad0a47dac0e9f0bd57b81936f3db" 2546 + dependencies = [ 2547 + "tracing", 2548 + "tracing-subscriber", 2549 + ] 2550 + 2551 + [[package]] 2552 + name = "tracing-log" 2553 + version = "0.2.0" 2554 + source = "registry+https://github.com/rust-lang/crates.io-index" 2555 + checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" 2556 + dependencies = [ 2557 + "log", 2558 + "once_cell", 2559 + "tracing-core", 2560 + ] 2561 + 2562 + [[package]] 2563 + name = "tracing-subscriber" 2564 + version = "0.3.22" 2565 + source = "registry+https://github.com/rust-lang/crates.io-index" 2566 + checksum = "2f30143827ddab0d256fd843b7a66d164e9f271cfa0dde49142c5ca0ca291f1e" 2567 + dependencies = [ 2568 + "matchers", 2569 + "nu-ansi-term", 2570 + "once_cell", 2571 + "regex-automata", 2572 + "sharded-slab", 2573 + "smallvec", 2574 + "thread_local", 2575 + "tracing", 2576 + "tracing-core", 2577 + "tracing-log", 2201 2578 ] 2202 2579 2203 2580 [[package]] ··· 2233 2610 2234 2611 [[package]] 2235 2612 name = "unicode-ident" 2236 - version = "1.0.22" 2613 + version = "1.0.23" 2237 2614 source = "registry+https://github.com/rust-lang/crates.io-index" 2238 - checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" 2615 + checksum = "537dd038a89878be9b64dd4bd1b260315c1bb94f4d784956b81e27a088d9a09e" 2239 2616 2240 2617 [[package]] 2241 2618 name = "unsafe-libyaml" ··· 2254 2631 version = "0.2.2" 2255 2632 source = "registry+https://github.com/rust-lang/crates.io-index" 2256 2633 checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" 2634 + 2635 + [[package]] 2636 + name = "valuable" 2637 + version = "0.1.1" 2638 + source = "registry+https://github.com/rust-lang/crates.io-index" 2639 + checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" 2257 2640 2258 2641 [[package]] 2259 2642 name = "version_check"
+14 -1
Cargo.toml
··· 5 5 6 6 [dependencies] 7 7 async-trait = "0.1.89" 8 - pingora = { version = "0.7.0", features = ["rustls", "proxy"] } 8 + color-eyre = "0.6.5" 9 + http = "1.4.0" 10 + pingora = { version = "0.7.0", features = ["rustls", "proxy", "lb"] } 11 + prost = "0.14.3" 12 + prost-reflect = { version = "0.16.3", features = ["derive", "text-format"] } 13 + rustls = { version = "0.23.36", features = ["aws-lc-rs"] } 14 + tokio = { version = "1.49.0", features = ["fs"] } 15 + tracing = "0.1.44" 16 + tracing-subscriber = { version = "0.3.22", features = ["env-filter"] } 17 + 18 + [build-dependencies] 19 + # use prost-reflect-build & prost-reflect so that we can deserialize 20 + # textprotos, cause prost can't natively 21 + prost-reflect-build = "0.16.0"
+6
build.rs
··· 1 + fn main() -> std::io::Result<()> { 2 + prost_reflect_build::Builder::new() 3 + .descriptor_pool("crate::config::DESCRIPTOR_POOL") 4 + .compile_protos(&["src/config/format.proto"], &["src/"])?; 5 + Ok(()) 6 + }
+1
flake.nix
··· 46 46 # rustdoc, rustfmt, and other tools. 47 47 cmake 48 48 rustToolchain 49 + protobuf 49 50 ]) ++ pkgs.lib.optionals pkgs.stdenv.isDarwin (with pkgs; [ libiconv ]); 50 51 }; 51 52 });
+156
src/config/format.proto
··· 1 + syntax = "proto3"; 2 + 3 + package config.format; 4 + 5 + // the root config 6 + message Config { 7 + // the domains to serve 8 + map<string, Domain> domains = 1; 9 + 10 + // bind to tcp ports, with optional tls 11 + repeated TCPBinding bind_to_tcp = 2; 12 + // bind to unix domain sockets 13 + repeated UDSBinding bind_to_uds = 4; 14 + 15 + // lower-level pingora config 16 + Pingora pingora = 3; 17 + } 18 + 19 + message Domain { 20 + // require oidc auth if this is set 21 + optional OIDC oidc_auth = 1; 22 + 23 + // TODO: ACME challenge hosting support natively? 24 + 25 + // https backends 26 + repeated HTTPSBackend https = 3; 27 + // http backends 28 + repeated HTTPSBackend http = 7; 29 + // unix domain socket backends 30 + repeated UDSBackend uds = 4; 31 + 32 + enum TLSMode { 33 + // don't support redirects, they can be _very_ unsafe. just use hsts 34 + 35 + // only allow https, no redirect 36 + TLS_MODE_ONLY = 0; 37 + // allow http, for testing purposes 38 + TLS_MODE_UNSAFE_ALLOW_HTTP = 1; 39 + } 40 + 41 + TLSMode tls_mode = 5; 42 + 43 + // set or clear headers on the backend request 44 + ManageHeaders manage_headers = 6; 45 + } 46 + 47 + message OIDC { 48 + // the base oidc discovery url, without the `.well-known/openid-configuration` part 49 + // 50 + // per [OIDC Discovery 1.0](https://openid.net/specs/openid-connect-discovery-1_0.html) 51 + string discovery_url_base = 1; 52 + 53 + string client_id = 2; 54 + string client_secret_path = 3; 55 + 56 + Scopes scopes = 4; 57 + Claims claims = 5; 58 + } 59 + 60 + message Scopes { 61 + repeated string optional = 1; 62 + repeated string required = 2; 63 + } 64 + 65 + // information on how to process returned claims 66 + message Claims { 67 + // map the given claims to a header in the backend request 68 + // 69 + // headers specified here with no corresponding claim value will be wiped 70 + map<string, string> claim_to_header = 1; 71 + } 72 + 73 + // a standard https backend 74 + message HTTPSBackend { 75 + // full ipv4 or v6 address, including port 76 + string addr = 1; 77 + // weight of this backend, if load-balancing 78 + optional uint64 weight = 2; 79 + } 80 + 81 + // a unix domain socket backend 82 + message UDSBackend { 83 + // path to the uds socket 84 + string path = 1; 85 + // weight of this backend, if load-balancing 86 + optional uint64 weight = 2; 87 + } 88 + 89 + // these headers will be set if they are set to something 90 + // 91 + // either way, they will be wiped if the client tries to send them 92 + message ManageHeaders { 93 + // set the given header to be the request host 94 + optional string host = 1; 95 + // set an `X-Forwarded-For`-style header, appending `,<remote_addr>` to any existing value 96 + optional string x_forwarded_for = 2; 97 + // set an `X-Forwarded-Proto`-style header to the original scheme of the request 98 + optional string x_forwarded_proto = 3; 99 + // set an `X-Real-IP`-style header (i.e. _just_ the remote address) 100 + repeated string remote_addr = 4; 101 + 102 + // always clear these headers 103 + repeated string always_clear = 5; 104 + } 105 + 106 + // equivalent to [`pingora::server::configuration::Config`] 107 + message Pingora { 108 + uint64 version = 1; 109 + bool daemon = 2; 110 + optional string error_log = 3; 111 + string pid_file = 4; 112 + string upgrade_sock = 5; 113 + optional string user = 6; 114 + optional string group = 7; 115 + uint64 threads = 8; 116 + uint64 listener_tasks_per_fd = 9; 117 + bool work_stealing = 10; 118 + optional string ca_file = 11; 119 + optional uint64 grace_period_seconds = 12; 120 + optional uint64 graceful_shutdown_timeout_seconds = 13; 121 + repeated string client_bind_to_ipv4 = 14; 122 + repeated string client_bind_to_ipv6 = 15; 123 + uint64 upstream_keepalive_pool_size = 16; 124 + optional uint64 upstream_connect_offload_threadpools = 17; 125 + optional uint64 upstream_connect_offload_thread_per_pool = 18; 126 + bool upstream_debug_ssl_keylog = 19; 127 + uint64 max_retries = 20; 128 + optional uint64 upgrade_sock_connect_accept_max_retries = 21; 129 + 130 + } 131 + 132 + message TCPBinding { 133 + // configure used tls settings 134 + message TLS { 135 + // path to the (public) tls certificate, with all intermediate certificates (fullchain.pem for most acme clients) 136 + string cert_path = 1; 137 + // path to the (privat) tls key 138 + string key_path = 2; 139 + } 140 + 141 + // host an port to bind to 142 + string addr = 1; 143 + // tls, if desired 144 + optional TLS tls = 2; 145 + 146 + // TODO: surface tcp options from pingora 147 + } 148 + message UDSBinding { 149 + message Permissions { 150 + uint32 mode = 1; 151 + } 152 + // socket path 153 + string path = 1; 154 + // permissions to set on the socket path 155 + optional Permissions permissions = 2; 156 + }
+65
src/config.rs
··· 1 + use std::path::Path; 2 + use std::sync::LazyLock; 3 + 4 + use color_eyre::eyre::Context as _; 5 + use prost_reflect::DescriptorPool; 6 + 7 + #[allow(unused, reason = "for prost-reflect-build")] 8 + static DESCRIPTOR_POOL: LazyLock<DescriptorPool> = LazyLock::new(|| { 9 + DescriptorPool::decode( 10 + include_bytes!(concat!(env!("OUT_DIR"), "/file_descriptor_set.bin")).as_ref(), 11 + ) 12 + .unwrap() 13 + }); 14 + 15 + pub mod format { 16 + include!(concat!(env!("OUT_DIR"), "/config.format.rs")); 17 + 18 + impl From<&Pingora> for pingora::server::configuration::ServerConf { 19 + fn from(raw: &Pingora) -> Self { 20 + Self { 21 + version: raw.version as usize, 22 + daemon: raw.daemon, 23 + error_log: raw.error_log.clone(), 24 + pid_file: raw.pid_file.clone(), 25 + upgrade_sock: raw.upgrade_sock.clone(), 26 + user: raw.user.clone(), 27 + group: raw.group.clone(), 28 + threads: raw.threads as usize, 29 + listener_tasks_per_fd: raw.listener_tasks_per_fd as usize, 30 + work_stealing: raw.work_stealing, 31 + ca_file: raw.ca_file.clone(), 32 + grace_period_seconds: raw.grace_period_seconds, 33 + graceful_shutdown_timeout_seconds: raw.graceful_shutdown_timeout_seconds, 34 + client_bind_to_ipv4: raw.client_bind_to_ipv4.clone(), 35 + client_bind_to_ipv6: raw.client_bind_to_ipv6.clone(), 36 + upstream_keepalive_pool_size: raw.upstream_keepalive_pool_size as usize, 37 + upstream_connect_offload_threadpools: raw 38 + .upstream_connect_offload_threadpools 39 + .map(|x| x as usize), 40 + upstream_connect_offload_thread_per_pool: raw 41 + .upstream_connect_offload_thread_per_pool 42 + .map(|x| x as usize), 43 + upstream_debug_ssl_keylog: raw.upstream_debug_ssl_keylog, 44 + max_retries: raw.max_retries as usize, 45 + upgrade_sock_connect_accept_max_retries: raw 46 + .upgrade_sock_connect_accept_max_retries 47 + .map(|x| x as usize), 48 + } 49 + } 50 + } 51 + } 52 + 53 + /// load the config from the given file 54 + pub fn load(src: impl AsRef<Path>) -> color_eyre::Result<format::Config> { 55 + use prost_reflect::{DynamicMessage, ReflectMessage as _}; 56 + 57 + let dynamic = DynamicMessage::parse_text_format( 58 + format::Config::default().descriptor(), 59 + &std::fs::read_to_string(src) 60 + .context("reading config file")?, 61 + ) 62 + .context("parsing config")?; 63 + 64 + dynamic.transcode_to().context("validating config") 65 + }
+273 -2
src/main.rs
··· 1 - fn main() { 2 - println!("Hello, world!"); 1 + use std::collections::HashMap; 2 + use std::sync::Arc; 3 + 4 + use async_trait::async_trait; 5 + use color_eyre::eyre::Context as _; 6 + use http::status::StatusCode; 7 + use pingora::lb; 8 + use pingora::lb::selection::consistent::KetamaHashing; 9 + use pingora::modules::http::HttpModules; 10 + use pingora::modules::http::compression::ResponseCompressionBuilder; 11 + use pingora::prelude::*; 12 + 13 + mod config; 14 + 15 + struct BackendInfo { 16 + balancer: Arc<LoadBalancer<KetamaHashing>>, 17 + tls_mode: config::format::domain::TlsMode, 18 + name: String, 19 + // TODO: force ssl 20 + } 21 + 22 + pub struct AuthGateway { 23 + backends: HashMap<String, BackendInfo>, 24 + } 25 + 26 + fn status_error(why: &'static str, src: ErrorSource, status: http::StatusCode) -> impl FnOnce() -> Box<Error> { 27 + move || Error::create(ErrorType::HTTPStatus(status.into()), src, Some(why.into()), None) 28 + } 29 + 30 + impl AuthGateway { 31 + fn backend_info<'s>(&'s self, session: &Session) -> Result<&'s BackendInfo> { 32 + let req = session.req_header(); 33 + // TODO: afaict, right now, afaict, pingora a) does not check that SNI matches the `Host` 34 + // header, b) does not support extracting the SNI info on rustls, so we'll have to switch 35 + // to boringssl and implement that ourselves T_T 36 + let host = req 37 + .headers 38 + .get(http::header::HOST) 39 + .ok_or_else(status_error("no host set", ErrorSource::Downstream, StatusCode::BAD_REQUEST))? 40 + .to_str() 41 + .map_err(|e| { 42 + Error::because( 43 + ErrorType::HTTPStatus(StatusCode::BAD_REQUEST.into()), 44 + "no host", 45 + e, 46 + ) 47 + })?; 48 + let info = self 49 + .backends 50 + .get(host) 51 + .ok_or_else(status_error("unknown host", ErrorSource::Downstream, StatusCode::SERVICE_UNAVAILABLE))?; 52 + 53 + Ok(info) 54 + } 55 + } 56 + 57 + #[async_trait] 58 + impl ProxyHttp for AuthGateway { 59 + type CTX = (); 60 + fn new_ctx(&self) -> Self::CTX {} 61 + 62 + fn init_downstream_modules(&self, modules: &mut HttpModules) { 63 + // TODO: make this configurable? 64 + modules.add_module(ResponseCompressionBuilder::enable(1)); 65 + } 66 + 67 + async fn request_filter(&self, session: &mut Session, _ctx: &mut Self::CTX) -> Result<bool> { 68 + // check if this is http, and redirect 69 + // TODO: maybe should be a module? 70 + let is_https = session.digest().and_then(|d| d.ssl_digest.as_ref()).is_some(); 71 + if !is_https { 72 + use config::format::domain::TlsMode; 73 + let info = self.backend_info(session)?; 74 + match info.tls_mode { 75 + TlsMode::Only => { 76 + // we should just drop the connection, although people should really just be 77 + // using HSTS 78 + session.shutdown().await; 79 + return Ok(true); 80 + }, 81 + TlsMode::UnsafeAllowHttp => {}, 82 + } 83 + } 84 + 85 + Ok(false) 86 + } 87 + 88 + async fn upstream_peer(&self, session: &mut Session, _ctx: &mut ()) -> Result<Box<HttpPeer>> { 89 + fn client_addr_key(sock_addr: &pingora::protocols::l4::socket::SocketAddr) -> Vec<u8> { 90 + use pingora::protocols::l4::socket::SocketAddr; 91 + match sock_addr { 92 + SocketAddr::Inet(socket_addr) => match socket_addr { 93 + std::net::SocketAddr::V4(v4) => Vec::from(v4.ip().octets()), 94 + std::net::SocketAddr::V6(v6) => Vec::from(v6.ip().octets()), 95 + }, 96 + // TODO: this is... not a great key for hashing 97 + SocketAddr::Unix(_socket_addr) => vec![], 98 + } 99 + } 100 + 101 + let backends = self.backend_info(session)?; 102 + let backend = backends.balancer 103 + // NB: this means that CGNAT, other proxies, etc will? consistently hit the same 104 + // backend, so we might wanna take that into consideration. fine for now, this is 105 + // currently for personal use ;-) 106 + .select( 107 + &client_addr_key( 108 + session 109 + .client_addr() 110 + .ok_or_else(status_error("no client address", ErrorSource::Downstream, StatusCode::BAD_REQUEST))?, 111 + ), /* lb on client address */ 112 + 256, 113 + ) 114 + .ok_or_else(status_error("no available backends", ErrorSource::Upstream, StatusCode::SERVICE_UNAVAILABLE))?; 115 + 116 + let needs_tls = backend 117 + .ext 118 + .get::<BackendData>() 119 + .map(|d| d.tls) 120 + .unwrap_or(true); 121 + 122 + Ok(Box::new(HttpPeer::new( 123 + backend, 124 + needs_tls, 125 + backends.name.to_string(), 126 + ))) 127 + } 128 + 129 + // TODO: upstream_request_filter to insert the right headers 130 + 131 + async fn response_filter( 132 + &self, 133 + _session: &mut Session, 134 + _upstream_response: &mut ResponseHeader, 135 + _ctx: &mut Self::CTX, 136 + ) -> Result<()> 137 + where 138 + Self::CTX: Send + Sync, 139 + { 140 + Ok(()) 141 + } 142 + 143 + // TODO: logging 144 + } 145 + 146 + #[derive(Clone)] 147 + struct BackendData { 148 + tls: bool, 149 + } 150 + 151 + fn balancer( 152 + domains: &HashMap<String, config::format::Domain>, 153 + ) -> color_eyre::Result<( 154 + Vec<pingora::services::background::GenBackgroundService<LoadBalancer<KetamaHashing>>>, 155 + HashMap<String, BackendInfo>, 156 + )> 157 + { 158 + use lb::{self, Backend, discovery}; 159 + use pingora::protocols::l4::socket::SocketAddr; 160 + 161 + let mut balancers = HashMap::with_capacity(domains.len()); 162 + let mut svcs = Vec::with_capacity(domains.len()); 163 + for (name, domain) in domains { 164 + let backends = domain 165 + .https 166 + .iter() 167 + .map(|backend| { 168 + let mut ext = lb::Extensions::new(); 169 + ext.insert(BackendData { tls: true }); 170 + let mut backend = Backend::new_with_weight( 171 + &backend.addr, 172 + backend.weight.map(|w| w as usize).unwrap_or(1), 173 + ) 174 + .context("parsing addr of https socket backend")?; 175 + backend.ext = ext; 176 + Ok(backend) 177 + }) 178 + .chain(domain.http.iter().map(|backend| { 179 + let mut ext = lb::Extensions::new(); 180 + ext.insert(BackendData { tls: false }); 181 + let mut backend = Backend::new_with_weight( 182 + &backend.addr, 183 + backend.weight.map(|w| w as usize).unwrap_or(1), 184 + ) 185 + .context("parsing addr of http socket backend")?; 186 + backend.ext = ext; 187 + Ok(backend) 188 + })) 189 + .chain(domain.uds.iter().map(|backend| { 190 + let mut ext = lb::Extensions::new(); 191 + ext.insert(BackendData { tls: false }); 192 + Ok(Backend { 193 + addr: SocketAddr::Unix( 194 + std::os::unix::net::SocketAddr::from_pathname(&backend.path) 195 + .context("turning uds path into socketaddr")?, 196 + ), 197 + weight: backend.weight.map(|w| w as usize).unwrap_or(1), 198 + ext, 199 + }) 200 + })) 201 + .collect::<color_eyre::Result<_>>() 202 + .context("constucting backends for domain")?; 203 + let backends = lb::Backends::new(discovery::Static::new(backends)); 204 + // TODO: allow configuring healthchecks 205 + let balancer = LoadBalancer::from_backends(backends); 206 + let svc = background_service("health checking", balancer); 207 + 208 + let info = BackendInfo { 209 + balancer: svc.task(), 210 + tls_mode: config::format::domain::TlsMode::try_from(domain.tls_mode).context("invalid tls mode")?, 211 + name: name.clone(), 212 + }; 213 + 214 + balancers.insert(name.clone(), info); 215 + svcs.push(svc) 216 + } 217 + 218 + Ok((svcs, balancers)) 219 + } 220 + 221 + fn main() -> color_eyre::Result<()> { 222 + use color_eyre::eyre::eyre; 223 + 224 + use std::os::unix::fs::PermissionsExt as _; 225 + 226 + tracing_subscriber::fmt().init(); 227 + color_eyre::install()?; 228 + rustls::crypto::aws_lc_rs::default_provider() 229 + .install_default() 230 + .expect("unable to install crypto provider"); 231 + 232 + let opts = Opt::parse_args(); 233 + 234 + let config = config::load(opts.conf.as_ref().ok_or_else(|| { 235 + eyre!("no config file specified, refusing to do anything (try `-c FILE`?)") 236 + })?)?; 237 + 238 + let pingora_config = match config.pingora.as_ref().map(Into::into) { 239 + Some(conf) => conf, 240 + None => pingora::server::configuration::ServerConf::new_with_opt_override(&opts) 241 + .ok_or_else(|| { 242 + eyre!("could not create a base pingora config, and none was specified") 243 + })?, 244 + }; 245 + let mut server = Server::new_with_opt_and_conf(opts, pingora_config); 246 + server.bootstrap(); 247 + 248 + let (balancer_svcs, balancers) = 249 + balancer(&config.domains).context("setting up load balancing")?; 250 + let mut gateway = http_proxy_service(&server.configuration, AuthGateway { backends: balancers }); 251 + for binding in config.bind_to_tcp { 252 + match binding.tls { 253 + Some(tls) => gateway 254 + .add_tls(&binding.addr, &tls.cert_path, &tls.key_path) 255 + .context("setting up tls")?, 256 + None => gateway.add_tcp(&binding.addr), 257 + } 258 + } 259 + for binding in config.bind_to_uds { 260 + gateway.add_uds( 261 + &binding.path, 262 + binding 263 + .permissions 264 + .map(|p| std::fs::Permissions::from_mode(p.mode)), 265 + ); 266 + } 267 + 268 + balancer_svcs 269 + .into_iter() 270 + .for_each(|svc| server.add_service(svc)); 271 + server.add_service(gateway); 272 + 273 + server.run_forever(); 3 274 }