Merge pull request #41 from launchbadge/ab/tls

implement TLS for Postgres and MySQL
This commit is contained in:
Ryan Leckey 2020-01-14 12:22:10 -08:00 committed by GitHub
commit 684068aa9e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 800 additions and 25 deletions

View File

@ -25,7 +25,10 @@ jobs:
# will assign a random free host port
- 3306/tcp
# needed because the container does not provide a healthcheck
options: --health-cmd "mysqladmin ping --silent" --health-interval 30s --health-timeout 30s --health-retries 10
options: >-
--health-cmd "mysqladmin ping --silent" --health-interval 30s --health-timeout 30s
--health-retries 10 -v /data/mysql:/var/lib/mysql
steps:
- uses: actions/checkout@v1
@ -48,9 +51,11 @@ jobs:
# -----------------------------------------------------
- run: cargo test -p sqlx --no-default-features --features 'mysql macros chrono'
- run: cargo test -p sqlx --no-default-features --features 'mysql macros chrono tls'
env:
DATABASE_URL: mysql://root:password@localhost:${{ job.services.mysql.ports[3306] }}/sqlx
# pass the path to the CA that the MySQL service generated
# Github Actions' YML parser doesn't handle multiline strings correctly
DATABASE_URL: mysql://root:password@localhost:${{ job.services.mysql.ports[3306] }}/sqlx?ssl-mode=VERIFY_CA&ssl-ca=%2Fdata%2Fmysql%2Fca.pem
# Rust ------------------------------------------------

View File

@ -49,6 +49,9 @@ jobs:
# -----------------------------------------------------
# Check that we build with TLS support (TODO: we need a postgres image with SSL certs to test)
- run: cargo check -p sqlx-core --no-default-features --features 'postgres macros uuid chrono tls'
- run: cargo test -p sqlx --no-default-features --features 'postgres macros uuid chrono'
env:
DATABASE_URL: postgres://postgres:postgres@localhost:${{ job.services.postgres.ports[5432] }}/postgres

181
Cargo.lock generated
View File

@ -32,6 +32,16 @@ dependencies = [
"syn 1.0.13 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "async-native-tls"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"async-std 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"native-tls 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
"thiserror 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "async-std"
version = "1.4.0"
@ -247,6 +257,20 @@ name = "constant_time_eq"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "core-foundation"
version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"core-foundation-sys 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "core-foundation-sys"
version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "crossbeam-channel"
version = "0.4.0"
@ -376,6 +400,19 @@ name = "fnv"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "foreign-types"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"foreign-types-shared 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "foreign-types-shared"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "fuchsia-zircon"
version = "0.3.3"
@ -831,6 +868,23 @@ dependencies = [
"ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "native-tls"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
"openssl 0.10.26 (registry+https://github.com/rust-lang/crates.io-index)",
"openssl-probe 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
"openssl-sys 0.9.53 (registry+https://github.com/rust-lang/crates.io-index)",
"schannel 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)",
"security-framework 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
"security-framework-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
"tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "net2"
version = "0.2.33"
@ -887,6 +941,36 @@ name = "opaque-debug"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "openssl"
version = "0.10.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
"cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
"foreign-types 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)",
"openssl-sys 0.9.53 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "openssl-probe"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "openssl-sys"
version = "0.9.53"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
"cc 1.0.50 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)",
"pkg-config 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)",
"vcpkg 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "parking_lot"
version = "0.9.0"
@ -931,6 +1015,11 @@ name = "pin-utils"
version = "0.1.0-alpha.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "pkg-config"
version = "0.3.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "ppv-lite86"
version = "0.2.6"
@ -1030,6 +1119,14 @@ name = "regex-syntax"
version = "0.6.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "remove_dir_all"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "ring"
version = "0.14.6"
@ -1077,11 +1174,39 @@ name = "ryu"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "schannel"
version = "0.1.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "scopeguard"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "security-framework"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"core-foundation 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)",
"core-foundation-sys 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)",
"security-framework-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "security-framework-sys"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"core-foundation-sys 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "semver"
version = "0.9.0"
@ -1186,6 +1311,7 @@ dependencies = [
"anyhow 1.0.26 (registry+https://github.com/rust-lang/crates.io-index)",
"async-std 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"dotenv 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)",
"env_logger 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)",
"futures 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
"hex 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"proc-macro-hack 0.5.11 (registry+https://github.com/rust-lang/crates.io-index)",
@ -1197,6 +1323,7 @@ dependencies = [
name = "sqlx-core"
version = "0.1.4"
dependencies = [
"async-native-tls 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
"async-std 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"async-stream 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"base64 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
@ -1278,6 +1405,19 @@ dependencies = [
"unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "tempfile"
version = "3.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)",
"rand 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)",
"redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)",
"remove_dir_all 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "termcolor"
version = "1.0.5"
@ -1286,6 +1426,24 @@ dependencies = [
"wincolor 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "thiserror"
version = "1.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"thiserror-impl 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "thiserror-impl"
version = "1.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"proc-macro2 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)",
"quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
"syn 1.0.13 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "thread_local"
version = "1.0.0"
@ -1496,6 +1654,11 @@ name = "uuid"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "vcpkg"
version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "version_check"
version = "0.1.5"
@ -1577,6 +1740,7 @@ dependencies = [
"checksum arrayref 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "0d382e583f07208808f6b1249e60848879ba3543f57c32277bf52d69c2f0f0ee"
"checksum arrayvec 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cff77d8686867eceff3105329d4698d96c2391c176d5d03adc90c7389162b5b8"
"checksum async-attributes 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "efd3d156917d94862e779f356c5acae312b08fd3121e792c857d7928c8088423"
"checksum async-native-tls 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "d40a615e861c981117e15c28c577daf9918cabd2e2d588a5e06811ae79c9da1a"
"checksum async-std 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "0bf6039b315300e057d198b9d3ab92ee029e31c759b7f1afae538145e6f18a3e"
"checksum async-stream 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "58982858be7540a465c790b95aaea6710e5139bf8956b1d1344d014fa40100b0"
"checksum async-stream-impl 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "393356ed99aa7bff0ac486dde592633b83ab02bd254d8c209d5b9f1d0f533480"
@ -1602,6 +1766,8 @@ dependencies = [
"checksum chrono 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)" = "31850b4a4d6bae316f7a09e691c944c28299298837edc0a03f755618c23cbc01"
"checksum cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f"
"checksum constant_time_eq 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "995a44c877f9212528ccc74b21a232f66ad69001e40ede5bcee2ac9ef2657120"
"checksum core-foundation 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)" = "25b9e03f145fd4f2bf705e07b900cd41fc636598fe5dc452fd0db1441c3f496d"
"checksum core-foundation-sys 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e7ca8a5221364ef15ce201e8ed2f609fc312682a8f4e0e3d4aa5879764e0fa3b"
"checksum crossbeam-channel 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "acec9a3b0b3559f15aee4f90746c4e5e293b701c0f7d3925d24e01645267b68c"
"checksum crossbeam-deque 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c3aa945d63861bfe624b55d153a39684da1e8c0bc8fba932f7ee3a3c16cea3ca"
"checksum crossbeam-epoch 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5064ebdbf05ce3cb95e45c8b086f72263f4166b29b97f6baff7ef7fe047b55ac"
@ -1618,6 +1784,8 @@ dependencies = [
"checksum error-chain 0.12.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3ab49e9dcb602294bc42f9a7dfc9bc6e936fca4418ea300dbfb84fe16de0b7d9"
"checksum fake-simd 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed"
"checksum fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "2fad85553e09a6f881f739c29f0b00b0f01357c743266d478b68951ce23285f3"
"checksum foreign-types 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1"
"checksum foreign-types-shared 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
"checksum fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82"
"checksum fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7"
"checksum futures 0.1.29 (registry+https://github.com/rust-lang/crates.io-index)" = "1b980f2816d6ee8673b6517b52cb0e808a180efc92e5c19d02cdda79066703ef"
@ -1669,6 +1837,7 @@ dependencies = [
"checksum mio 0.6.21 (registry+https://github.com/rust-lang/crates.io-index)" = "302dec22bcf6bae6dfb69c647187f4b4d0fb6f535521f7bc022430ce8e12008f"
"checksum mio-uds 0.6.7 (registry+https://github.com/rust-lang/crates.io-index)" = "966257a94e196b11bb43aca423754d87429960a768de9414f3691d6957abf125"
"checksum miow 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "8c1f2f3b1cf331de6896aabf6e9d55dca90356cc9960cca7eaaf408a355ae919"
"checksum native-tls 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "4b2df1a4c22fd44a62147fd8f13dd0f95c9d8ca7b2610299b2a2f9cf8964274e"
"checksum net2 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)" = "42550d9fb7b6684a6d404d9fa7250c2eb2646df731d1c06afc06dcee9e1bcf88"
"checksum num-bigint 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "f6f115de20ad793e857f76da2563ff4a09fbcfd6fe93cca0c5d996ab5f3ee38d"
"checksum num-integer 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)" = "3f6ea62e9d81a77cd3ee9a2a5b9b609447857f3d358704331e4ef39eb247fcba"
@ -1676,12 +1845,16 @@ dependencies = [
"checksum num_cpus 1.11.1 (registry+https://github.com/rust-lang/crates.io-index)" = "76dac5ed2a876980778b8b85f75a71b6cbf0db0b1232ee12f826bccb00d09d72"
"checksum once_cell 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "891f486f630e5c5a4916c7e16c4b24a53e78c860b646e9f8e005e4f16847bfed"
"checksum opaque-debug 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c"
"checksum openssl 0.10.26 (registry+https://github.com/rust-lang/crates.io-index)" = "3a3cc5799d98e1088141b8e01ff760112bbd9f19d850c124500566ca6901a585"
"checksum openssl-probe 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "77af24da69f9d9341038eba93a073b1fdaaa1b788221b00a69bce9e762cb32de"
"checksum openssl-sys 0.9.53 (registry+https://github.com/rust-lang/crates.io-index)" = "465d16ae7fc0e313318f7de5cecf57b2fbe7511fd213978b457e1c96ff46736f"
"checksum parking_lot 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f842b1982eb6c2fe34036a4fbfb06dd185a3f5c8edfaacdf7d1ea10b07de6252"
"checksum parking_lot_core 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "b876b1b9e7ac6e1a74a6da34d25c42e17e8862aa409cbbbdcfc8d86c6f3bc62b"
"checksum percent-encoding 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "31010dd2e1ac33d5b46a5b413495239882813e0369f8ed8a5e266f173602f831"
"checksum percent-encoding 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e"
"checksum pin-project-lite 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e8822eb8bb72452f038ebf6048efa02c3fe22bf83f76519c9583e47fc194a422"
"checksum pin-utils 0.1.0-alpha.4 (registry+https://github.com/rust-lang/crates.io-index)" = "5894c618ce612a3fa23881b152b608bafb8c56cfc22f434a3ba3120b40f7b587"
"checksum pkg-config 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)" = "05da548ad6865900e60eaba7f589cc0783590a92e940c26953ff81ddbab2d677"
"checksum ppv-lite86 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "74490b50b9fbe561ac330df47c08f3f33073d2d00c150f719147d7c54522fa1b"
"checksum proc-macro-hack 0.5.11 (registry+https://github.com/rust-lang/crates.io-index)" = "ecd45702f76d6d3c75a80564378ae228a85f0b59d2f3ed43c91b4a69eb2ebfc5"
"checksum proc-macro-nested 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "369a6ed065f249a159e06c45752c780bda2fb53c995718f9e484d08daa9eb42e"
@ -1695,13 +1868,17 @@ dependencies = [
"checksum redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)" = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84"
"checksum regex 1.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "b5508c1941e4e7cb19965abef075d35a9a8b5cdf0846f30b4050e9b55dc55e87"
"checksum regex-syntax 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)" = "e734e891f5b408a29efbf8309e656876276f49ab6a6ac208600b4419bd893d90"
"checksum remove_dir_all 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "4a83fa3702a688b9359eccba92d153ac33fd2e8462f9e0e3fdf155239ea7792e"
"checksum ring 0.14.6 (registry+https://github.com/rust-lang/crates.io-index)" = "426bc186e3e95cac1e4a4be125a4aca7e84c2d616ffc02244eef36e2a60a093c"
"checksum route-recognizer 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)" = "ea509065eb0b3c446acdd0102f0d46567dc30902dc0be91d6552035d92b0f4f8"
"checksum rust-argon2 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "416f5109bdd413cec4f04c029297838e7604c993f8d1483b1d438f23bdc3eb35"
"checksum rustc-demangle 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)" = "4c691c0e608126e00913e33f0ccf3727d5fc84573623b8d65b2df340b5201783"
"checksum rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a"
"checksum ryu 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "bfa8506c1de11c9c4e4c38863ccbe02a305c8188e85a05a784c9e11e1c3910c8"
"checksum schannel 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)" = "87f550b06b6cba9c8b8be3ee73f391990116bf527450d2556e9b9ce263b9a021"
"checksum scopeguard 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b42e15e59b18a828bbf5c58ea01debb36b9b096346de35d941dcb89009f24a0d"
"checksum security-framework 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "8ef2429d7cefe5fd28bd1d2ed41c944547d4ff84776f5935b456da44593a16df"
"checksum security-framework-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "e31493fc37615debb8c5090a7aeb4a9730bc61e77ab10b9af59f1a202284f895"
"checksum semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403"
"checksum semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
"checksum serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)" = "414115f25f818d7dfccec8ee535d76949ae78584fc4f79a6f45a904bf8ab4449"
@ -1717,7 +1894,10 @@ dependencies = [
"checksum string 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d24114bfcceb867ca7f71a0d3fe45d45619ec47a6fbfa98cb14e14250bfa5d6d"
"checksum subtle 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2d67a5a62ba6e01cb2192ff309324cb4875d0c451d55fe2319433abe7a05a8ee"
"checksum syn 1.0.13 (registry+https://github.com/rust-lang/crates.io-index)" = "1e4ff033220a41d1a57d8125eab57bf5263783dfdcc18688b1dacc6ce9651ef8"
"checksum tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e24d9338a0a5be79593e2fa15a648add6138caa803e2d5bc782c371732ca9"
"checksum termcolor 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "96d6098003bde162e4277c70665bd87c326f5a0c3f3fbfb285787fa482d54e6e"
"checksum thiserror 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)" = "6f357d1814b33bc2dc221243f8424104bfe72dbe911d5b71b3816a2dff1c977e"
"checksum thiserror-impl 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)" = "eb2e25d25307eb8436894f727aba8f65d07adf02e5b35a13cebed48bd282bfef"
"checksum thread_local 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "88ddf1ad580c7e3d1efff877d972bcc93f995556b9087a5a259630985c88ceab"
"checksum tide 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "13c99b1991db81e611a2614cd1b07fec89ae33c5f755e1f8eb70826fb5af0eea"
"checksum time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)" = "db8dcfca086c1143c9270ac42a2bbd8a7ee477b78ac8e45b19abfb0cbede4b6f"
@ -1739,6 +1919,7 @@ dependencies = [
"checksum untrusted 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "55cd1f4b4e96b46aeb8d4855db4a7a9bd96eeeb5c6a1ab54593328761642ce2f"
"checksum url 2.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "829d4a8476c35c9bf0bbce5a3b23f4106f79728039b726d292bb93bc106787cb"
"checksum uuid 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "9fde2f6a4bea1d6e007c4ad38c6839fa71cbb63b6dbf5b595aa38dc9b1093c11"
"checksum vcpkg 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "3fc439f2794e98976c88a2a2dafce96b930fe8010b0a256b3c2199a773933168"
"checksum version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "914b1a6776c4c929a602fafd8bc742e06365d4bcbe48c30f9cca5824f70dc9dd"
"checksum want 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b6395efa4784b027708f7451087e647ec73cc74f5d9bc2e418404248d679a230"
"checksum wasi 0.9.0+wasi-snapshot-preview1 (registry+https://github.com/rust-lang/crates.io-index)" = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519"

View File

@ -30,6 +30,7 @@ all-features = true
[features]
default = [ "macros" ]
macros = [ "sqlx-macros", "proc-macro-hack" ]
tls = ["sqlx-core/tls"]
# database
postgres = [ "sqlx-core/postgres", "sqlx-macros/postgres" ]
@ -48,6 +49,7 @@ hex = "0.4.0"
[dev-dependencies]
anyhow = "1.0.26"
futures = "0.3.1"
env_logger = "0.7"
async-std = { version = "1.4.0", features = [ "attributes" ] }
dotenv = "0.15.0"

View File

@ -20,8 +20,10 @@ default = []
unstable = []
postgres = [ "md-5", "sha2", "base64", "sha-1", "rand", "hmac" ]
mysql = [ "sha-1", "sha2", "generic-array", "num-bigint", "base64", "digest", "rand" ]
tls = ["async-native-tls"]
[dependencies]
async-native-tls = { version = "0.3", optional = true }
async-std = "1.4.0"
async-stream = { version = "0.2.0", default-features = false }
base64 = { version = "0.11.0", default-features = false, optional = true, features = [ "std" ] }

View File

@ -44,6 +44,9 @@ pub enum Error {
/// [Pool::close] was called while we were waiting in [Pool::acquire].
PoolClosed,
/// An error occurred during a TLS upgrade.
TlsUpgrade(Box<dyn StdError + Send + Sync>),
Decode(DecodeError),
// TODO: Remove and replace with `#[non_exhaustive]` when possible
@ -62,6 +65,8 @@ impl StdError for Error {
Error::Decode(DecodeError::Other(error)) => Some(&**error),
Error::TlsUpgrade(error) => Some(&**error),
_ => None,
}
}
@ -100,6 +105,8 @@ impl Display for Error {
Error::PoolClosed => f.write_str("attempted to acquire a connection on a closed pool"),
Error::TlsUpgrade(ref err) => write!(f, "error during TLS upgrade: {}", err),
Error::__Nonexhaustive => unreachable!(),
}
}
@ -140,6 +147,21 @@ impl From<ProtocolError<'_>> for Error {
}
}
#[cfg(feature = "tls")]
impl From<async_native_tls::Error> for Error {
#[inline]
fn from(err: async_native_tls::Error) -> Self {
Error::TlsUpgrade(err.into())
}
}
impl From<TlsError<'_>> for Error {
#[inline]
fn from(err: TlsError<'_>) -> Self {
Error::TlsUpgrade(err.args.to_string().into())
}
}
impl<T> From<T> for Error
where
T: 'static + DatabaseError,
@ -189,6 +211,15 @@ macro_rules! protocol_err (
}
);
pub(crate) struct TlsError<'a> {
pub args: fmt::Arguments<'a>,
}
#[allow(unused_macros)]
macro_rules! tls_err {
($($args:tt)*) => { crate::error::TlsError { args: format_args!($($args)*)} };
}
#[allow(unused_macros)]
macro_rules! impl_fmt_error {
($err:ty) => {

View File

@ -3,6 +3,7 @@ use async_std::io::{
Read, Write,
};
use std::io;
use std::ops::{Deref, DerefMut};
const RBUF_SIZE: usize = 8 * 1024;
@ -51,6 +52,12 @@ where
Ok(())
}
pub fn clear_bufs(&mut self) {
self.rbuf_rindex = 0;
self.rbuf_windex = 0;
self.wbuf.clear();
}
#[inline]
pub fn consume(&mut self, cnt: usize) {
self.rbuf_rindex += cnt;
@ -118,6 +125,20 @@ where
}
}
impl<S> Deref for BufStream<S> {
type Target = S;
fn deref(&self) -> &Self::Target {
&self.stream
}
}
impl<S> DerefMut for BufStream<S> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.stream
}
}
// TODO: Find a nicer way to do this
// Return `Ok(None)` immediately from a function if the wrapped value is `None`
#[allow(unused)]

View File

@ -5,11 +5,14 @@ mod buf;
mod buf_mut;
mod byte_str;
mod tls;
pub use self::{
buf::{Buf, ToBuf},
buf_mut::BufMut,
buf_stream::BufStream,
byte_str::ByteStr,
tls::MaybeTlsStream,
};
#[cfg(test)]

126
sqlx-core/src/io/tls.rs Normal file
View File

@ -0,0 +1,126 @@
use std::io::{IoSlice, IoSliceMut};
use std::pin::Pin;
use std::task::{Context, Poll};
use async_std::io::{self, Read, Write};
use async_std::net::{Shutdown, TcpStream};
use crate::url::Url;
use self::Inner::*;
pub struct MaybeTlsStream {
inner: Inner,
}
enum Inner {
NotTls(TcpStream),
#[cfg(feature = "tls")]
Tls(async_native_tls::TlsStream<TcpStream>),
#[cfg(feature = "tls")]
Upgrading,
}
impl MaybeTlsStream {
pub async fn connect(url: &Url, default_port: u16) -> crate::Result<Self> {
let conn = TcpStream::connect((url.host(), url.port(default_port))).await?;
Ok(Self {
inner: Inner::NotTls(conn),
})
}
#[allow(dead_code)]
pub fn is_tls(&self) -> bool {
match self.inner {
Inner::NotTls(_) => false,
#[cfg(feature = "tls")]
Inner::Tls(_) => true,
#[cfg(feature = "tls")]
Inner::Upgrading => false,
}
}
#[cfg(feature = "tls")]
pub async fn upgrade(
&mut self,
url: &Url,
connector: async_native_tls::TlsConnector,
) -> crate::Result<()> {
let conn = match std::mem::replace(&mut self.inner, Upgrading) {
NotTls(conn) => conn,
Tls(_) => return Err(tls_err!("connection already upgraded").into()),
Upgrading => return Err(tls_err!("connection already failed to upgrade").into()),
};
self.inner = Tls(connector.connect(url.host(), conn).await?);
Ok(())
}
pub fn shutdown(&self, how: Shutdown) -> io::Result<()> {
match self.inner {
NotTls(ref conn) => conn.shutdown(how),
#[cfg(feature = "tls")]
Tls(ref conn) => conn.get_ref().shutdown(how),
#[cfg(feature = "tls")]
// connection already closed
Upgrading => Ok(()),
}
}
}
macro_rules! forward_pin (
($self:ident.$method:ident($($arg:ident),*)) => (
match &mut $self.inner {
NotTls(ref mut conn) => Pin::new(conn).$method($($arg),*),
#[cfg(feature = "tls")]
Tls(ref mut conn) => Pin::new(conn).$method($($arg),*),
#[cfg(feature = "tls")]
Upgrading => Err(io::Error::new(io::ErrorKind::Other, "connection broken; TLS upgrade failed")).into(),
}
)
);
impl Read for MaybeTlsStream {
fn poll_read(
mut self: Pin<&mut Self>,
cx: &mut Context,
buf: &mut [u8],
) -> Poll<io::Result<usize>> {
forward_pin!(self.poll_read(cx, buf))
}
fn poll_read_vectored(
mut self: Pin<&mut Self>,
cx: &mut Context,
bufs: &mut [IoSliceMut],
) -> Poll<io::Result<usize>> {
forward_pin!(self.poll_read_vectored(cx, bufs))
}
}
impl Write for MaybeTlsStream {
fn poll_write(
mut self: Pin<&mut Self>,
cx: &mut Context,
buf: &[u8],
) -> Poll<io::Result<usize>> {
forward_pin!(self.poll_write(cx, buf))
}
fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll<io::Result<()>> {
forward_pin!(self.poll_flush(cx))
}
fn poll_close(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll<io::Result<()>> {
forward_pin!(self.poll_close(cx))
}
fn poll_write_vectored(
mut self: Pin<&mut Self>,
cx: &mut Context,
bufs: &[IoSlice],
) -> Poll<io::Result<usize>> {
forward_pin!(self.poll_write_vectored(cx, bufs))
}
}

View File

@ -1,18 +1,18 @@
use std::convert::TryInto;
use std::io;
use async_std::net::{Shutdown, TcpStream};
use async_std::net::Shutdown;
use byteorder::{ByteOrder, LittleEndian};
use futures_core::future::BoxFuture;
use sha1::Sha1;
use crate::cache::StatementCache;
use crate::connection::Connection;
use crate::io::{Buf, BufMut, BufStream};
use crate::io::{Buf, BufMut, BufStream, MaybeTlsStream};
use crate::mysql::error::MySqlError;
use crate::mysql::protocol::{
AuthPlugin, AuthSwitch, Capabilities, Decode, Encode, EofPacket, ErrPacket, Handshake,
HandshakeResponse, OkPacket,
HandshakeResponse, OkPacket, SslRequest,
};
use crate::mysql::rsa;
use crate::mysql::util::xor_eq;
@ -28,8 +28,64 @@ const COLLATE_UTF8MB4_UNICODE_CI: u8 = 224;
/// The connection string expected by [Connection::open] should be a MySQL connection
/// string, as documented at
/// <https://dev.mysql.com/doc/refman/8.0/en/connecting-using-uri-or-key-value-pairs.html#connecting-using-uri>
///
/// ### TLS Support (requires `tls` feature)
/// This connection type supports some of the same flags as the `mysql` CLI application for SSL
/// connections, but they must be specified via the query segment of the connection string
/// rather than as program arguments.
///
/// The same options for `--ssl-mode` are supported as the `ssl-mode` query parameter:
/// <https://dev.mysql.com/doc/refman/8.0/en/connection-options.html#option_general_ssl-mode>
///
/// ```text
/// mysql://<user>[:<password>]@<host>[:<port>]/<database>[?ssl-mode=<ssl-mode>[&ssl-ca=<path>]]
/// ```
/// where
/// ```text
/// ssl-mode = DISABLED | PREFERRED | REQUIRED | VERIFY_CA | VERIFY_IDENTITY
/// path = percent (URL) encoded path on the local machine
/// ```
///
/// If the `tls` feature is not enabled, `ssl-mode=DISABLED` and `ssl-mode=PREFERRED` are no-ops and
/// `ssl-mode=REQUIRED`, `ssl-mode=VERIFY_CA` and `ssl-mode=VERIFY_IDENTITY` are forbidden
/// (attempting to connect with these will return an error).
///
/// If the `tls` feature is enabled, an upgrade to TLS is attempted on every connection by default
/// (equivalent to `ssl-mode=PREFERRED`). If the server does not support TLS (because `--ssl=0` was
/// passed to the server or an invalid certificate or key was used:
/// <https://dev.mysql.com/doc/refman/8.0/en/using-encrypted-connections.html>)
/// then it falls back to an unsecured connection and logs a warning.
///
/// Add `ssl-mode=REQUIRED` to your connection string to emit an error if the TLS upgrade fails.
///
/// However, like with `mysql` the server certificate is **not** checked for validity by default.
///
/// Specifying `ssl-mode=VERIFY_CA` will cause the TLS upgrade to verify the server's SSL
/// certificate against a local CA root certificate; this is not the system root certificate
/// but is instead expected to be specified as a local path with the `ssl-ca` query parameter
/// (percent-encoded so the URL remains valid).
///
/// If you're running MySQL locally it might look something like this (for `VERIFY_CA`):
/// ```text
/// mysql://root:password@localhost/my_database?ssl-mode=VERIFY_CA&ssl-ca=%2Fvar%2Flib%2Fmysql%2Fca.pem
/// ```
///
/// `%2F` is the percent-encoding for forward slash (`/`). In the example we give `/var/lib/mysql/ca.pem`
/// as the CA certificate path, which is generated by the MySQL server automatically if
/// no certificate is manually specified. Note that the path may vary based on the default `my.cnf`
/// packaged with MySQL for your Linux distribution. Also note that unlike MySQL, MariaDB does *not*
/// generate certificates automatically and they must always be passed in to enable TLS.
///
/// If `ssl-ca` is not specified or the file cannot be read, then an error is returned.
/// `ssl-ca` implies `ssl-mode=VERIFY_CA` so you only actually need to specify the former
/// but you may prefer having both to be more explicit.
///
/// If `ssl-mode=VERIFY_IDENTITY` is specified, in addition to checking the certificate as with
/// `ssl-mode=VERIFY_CA`, the hostname in the connection string will be verified
/// against the hostname in the server certificate, so they must be the same for the TLS
/// upgrade to succeed. `ssl-ca` must still be specified.
pub struct MySqlConnection {
pub(super) stream: BufStream<TcpStream>,
pub(super) stream: BufStream<MaybeTlsStream>,
// Active capabilities of the client _&_ the server
pub(super) capabilities: Capabilities,
@ -171,6 +227,10 @@ impl MySqlConnection {
client_capabilities |= Capabilities::CONNECT_WITH_DB;
}
if cfg!(feature = "tls") {
client_capabilities |= Capabilities::SSL;
}
self.capabilities =
(client_capabilities & handshake.server_capabilities) | Capabilities::PROTOCOL_41;
@ -197,9 +257,7 @@ impl MySqlConnection {
}
})
}
}
impl MySqlConnection {
pub(crate) fn handle_ok(&mut self) -> crate::Result<OkPacket> {
let ok = OkPacket::decode(self.packet())?;
@ -301,7 +359,14 @@ impl MySqlConnection {
) -> crate::Result<Box<[u8]>> {
// https://mariadb.com/kb/en/caching_sha2_password-authentication-plugin/
// TODO: Handle SSL
if self.stream.is_tls() {
// If in a TLS stream, send the password directly in clear text
let mut clear_text = String::with_capacity(password.len() + 1);
clear_text.push_str(password);
clear_text.push('\0');
return Ok(clear_text.into_bytes().into_boxed_slice());
}
// client sends a public key request
self.send(&[public_key_request_id][..]).await?;
@ -324,11 +389,17 @@ impl MySqlConnection {
impl MySqlConnection {
async fn new(url: &Url) -> crate::Result<Self> {
let stream = TcpStream::connect((url.host(), url.port(3306))).await?;
let stream = MaybeTlsStream::connect(url, 3306).await?;
let mut capabilities = Capabilities::empty();
if cfg!(feature = "tls") {
capabilities |= Capabilities::SSL;
}
Ok(Self {
stream: BufStream::new(stream),
capabilities: Capabilities::empty(),
capabilities,
packet: Vec::with_capacity(8192),
packet_len: 0,
next_seq_no: 0,
@ -372,6 +443,35 @@ impl MySqlConnection {
Ok(())
}
#[cfg(feature = "tls")]
async fn try_ssl(
&mut self,
url: &Url,
ca_file: Option<&str>,
invalid_hostnames: bool,
) -> crate::Result<()> {
use async_native_tls::{Certificate, TlsConnector};
use async_std::fs;
let mut connector = TlsConnector::new()
.danger_accept_invalid_certs(ca_file.is_none())
.danger_accept_invalid_hostnames(invalid_hostnames);
if let Some(ca_file) = ca_file {
let root_cert = fs::read(ca_file).await?;
connector = connector.add_root_certificate(Certificate::from_pem(&root_cert)?);
}
// send upgrade request and then immediately try TLS handshake
self.send(SslRequest {
client_collation: COLLATE_UTF8MB4_UNICODE_CI,
max_packet_size: MAX_PACKET_SIZE,
})
.await?;
self.stream.stream.upgrade(url, connector).await
}
}
impl MySqlConnection {
@ -383,7 +483,72 @@ impl MySqlConnection {
// https://mariadb.com/kb/en/connection/
// On connect, server immediately sends the handshake
let handshake = self_.receive_handshake(&url).await?;
let mut handshake = self_.receive_handshake(&url).await?;
let ca_file = url.get_param("ssl-ca");
let ssl_mode = url.get_param("ssl-mode").unwrap_or(
if ca_file.is_some() {
"VERIFY_CA"
} else {
"PREFERRED"
}
.into(),
);
let supports_ssl = handshake.server_capabilities.contains(Capabilities::SSL);
match &*ssl_mode {
"DISABLED" => (),
// don't try upgrade
#[cfg(feature = "tls")]
"PREFERRED" if !supports_ssl => {
log::warn!("server does not support TLS; using unencrypted connection")
}
// try to upgrade
#[cfg(feature = "tls")]
"PREFERRED" => {
if let Err(e) = self_.try_ssl(&url, None, true).await {
log::warn!("TLS handshake failed, falling back to insecure: {}", e);
// fallback, redo connection
self_ = Self::new(&url).await?;
handshake = self_.receive_handshake(&url).await?;
}
}
#[cfg(not(feature = "tls"))]
"PREFERRED" => log::info!("compiled without TLS, skipping upgrade"),
#[cfg(feature = "tls")]
"REQUIRED" if !supports_ssl => {
return Err(tls_err!("server does not support TLS").into())
}
#[cfg(feature = "tls")]
"REQUIRED" => self_.try_ssl(&url, None, true).await?,
#[cfg(feature = "tls")]
"VERIFY_CA" | "VERIFY_FULL" if ca_file.is_none() => {
return Err(
tls_err!("`ssl-mode` of {:?} requires `ssl-ca` to be set", ssl_mode).into(),
)
}
#[cfg(feature = "tls")]
"VERIFY_CA" | "VERIFY_FULL" => {
self_
.try_ssl(&url, ca_file.as_deref(), ssl_mode != "VERIFY_FULL")
.await?
}
#[cfg(not(feature = "tls"))]
"REQUIRED" | "VERIFY_CA" | "VERIFY_FULL" => {
return Err(tls_err!("compiled without TLS").into())
}
_ => return Err(tls_err!("unknown `ssl-mode` value: {:?}", ssl_mode).into()),
}
// Pre-generate an auth response by using the auth method in the [Handshake]
let password = url.password().unwrap_or_default();

View File

@ -41,6 +41,7 @@ mod err;
mod handshake_response;
mod ok;
mod row;
mod ssl_request;
pub use auth_switch::AuthSwitch;
pub use column_count::ColumnCount;
@ -51,3 +52,4 @@ pub use err::ErrPacket;
pub use handshake_response::HandshakeResponse;
pub use ok::OkPacket;
pub use row::Row;
pub use ssl_request::SslRequest;

View File

@ -0,0 +1,35 @@
use byteorder::LittleEndian;
use crate::io::BufMut;
use crate::mysql::io::BufMutExt;
use crate::mysql::protocol::{AuthPlugin, Capabilities, Encode};
// https://dev.mysql.com/doc/dev/mysql-server/8.0.12/page_protocol_connection_phase_packets_protocol_handshake_response.html
// https://dev.mysql.com/doc/internals/en/connection-phase-packets.html#packet-Protocol::SSLRequest
#[derive(Debug)]
pub struct SslRequest {
pub max_packet_size: u32,
pub client_collation: u8,
}
impl Encode for SslRequest {
fn encode(&self, buf: &mut Vec<u8>, capabilities: Capabilities) {
// SSL must be set or else it makes no sense to ask for an upgrade
assert!(
capabilities.contains(Capabilities::SSL),
"SSL bit must be set for Capabilities"
);
// client capabilities : int<4>
buf.put_u32::<LittleEndian>(capabilities.bits() as u32);
// max packet size : int<4>
buf.put_u32::<LittleEndian>(self.max_packet_size);
// client character collation : int<1>
buf.put_u8(self.client_collation);
// reserved : string<23>
buf.advance(23);
}
}

View File

@ -178,7 +178,6 @@ mod tests {
use super::{BigUint, PublicKey};
use rand::rngs::adapter::ReadRng;
use sha1::Sha1;
use sha2::Sha256;
const INPUT: &str = "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAv9E+l0oFIoGnZmu6bdil\nI3WK79iug/hukj5QrWRrJVVCHL8rRxNsQGYPvQfXgqEnJW0Rqy2BBebNrnSMduny\nCazz1KM1h57hSI1xHGhg/o82Us1j9fUucKo0Pt3vg7xjVVcN0j1bwr96gEbt6B4Q\nt4eKZBhtle1bgoBcqFBhGfU17cnedSzMUCutM+kXTzzOTplKoqXeJpEZDTX8AP9F\nQ9JkoA22yTn8H2GROIAffm1UQS7DXXjI5OnzBJNs72oNSeK8i72xLkoSdfVw3vCu\ni+mpt4LJgAZLvzc2O4nLzu4Bljb+Mrch34HSWyxOfWzt1v9vpJfEVQ2/VZaIng6U\nUQIDAQAB\n-----END PUBLIC KEY-----\n";
@ -239,7 +238,7 @@ mod tests {
0x2c, 0x49,
];
let mut seed = &[
let seed = &[
0xaa, 0xfd, 0x12, 0xf6, 0x59, 0xca, 0xe6, 0x34, 0x89, 0xb4, 0x79, 0xe5, 0x07, 0x6d,
0xde, 0xc2, 0xf0, 0x6c, 0xb5, 0x8f,
][..];

View File

@ -43,6 +43,12 @@ where
DB::Connection: crate::Connection<Database = DB>,
{
/// Creates a connection pool with the default configuration.
///
/// The connection URL syntax is documented on the connection type for the respective
/// database you're connecting to:
///
/// * MySQL/MariaDB: [crate::MySqlConnection]
/// * PostgreSQL: [crate::PgConnection]
pub async fn new(url: &str) -> crate::Result<Self> {
Self::builder().build(url).await
}

View File

@ -1,12 +1,15 @@
use std::convert::TryInto;
use async_std::net::{Shutdown, TcpStream};
use async_std::net::Shutdown;
use byteorder::NetworkEndian;
use futures_core::future::BoxFuture;
use hmac::{Hmac, Mac};
use rand::Rng;
use sha2::{Digest, Sha256};
use crate::cache::StatementCache;
use crate::connection::Connection;
use crate::io::{Buf, BufStream};
use crate::io::{Buf, BufStream, MaybeTlsStream};
use crate::postgres::protocol::{
self, hi, Authentication, Decode, Encode, Message, SaslInitialResponse, SaslResponse,
StatementId,
@ -14,17 +17,69 @@ use crate::postgres::protocol::{
use crate::postgres::PgError;
use crate::url::Url;
use crate::Result;
use hmac::{Hmac, Mac};
use rand::Rng;
use sha2::{Digest, Sha256};
/// An asynchronous connection to a [Postgres] database.
///
/// The connection string expected by [Connection::open] should be a PostgreSQL connection
/// string, as documented at
/// <https://www.postgresql.org/docs/12/libpq-connect.html#LIBPQ-CONNSTRING>
///
/// ### TLS Support (requires `tls` feature)
/// This connection type supports the same `sslmode` query parameter that `libpq` does in
/// connection strings: <https://www.postgresql.org/docs/12/libpq-ssl.html>
///
/// ```text
/// postgresql://<user>[:<password>]@<host>[:<port>]/<database>[?sslmode=<ssl-mode>[&sslcrootcert=<path>]]
/// ```
/// where
/// ```text
/// ssl-mode = disable | allow | prefer | require | verify-ca | verify-full
/// path = percent (URL) encoded path on the local machine
/// ```
///
/// If the `tls` feature is not enabled, `disable`, `allow` and `prefer` are no-ops and `require`,
/// `verify-ca` and `verify-full` are forbidden (attempting to connect with these will return
/// an error).
///
/// If the `tls` feature is enabled, an upgrade to TLS is attempted on every connection by default
/// (equivalent to `sslmode=prefer`). If the server does not support TLS (because it was not
/// started with a valid certificate and key, see <https://www.postgresql.org/docs/12/ssl-tcp.html>)
/// then it falls back to an unsecured connection and logs a warning.
///
/// Add `sslmode=require` to your connection string to emit an error if the TLS upgrade fails.
///
/// If you're running Postgres locally, your connection string might look like this:
/// ```text
/// postgresql://root:password@localhost/my_database?sslmode=require
/// ```
///
/// However, like with `libpq` the server certificate is **not** checked for validity by default.
///
/// Specifying `sslmode=verify-ca` will cause the TLS upgrade to verify the server's SSL
/// certificate against a local CA root certificate; this is not the system root certificate
/// but is instead expected to be specified in one of a few ways:
///
/// * The path to the certificate can be specified by adding the `sslrootcert` query parameter
/// to the connection string. (Remember to percent-encode it!)
///
/// * The path may also be specified via the `PGSSLROOTCERT` environment variable (which
/// should *not* be percent-encoded.)
///
/// * Otherwise, the library will look for the Postgres global root CA certificate in the default
/// location:
///
/// * `$HOME/.postgresql/root.crt` on POSIX systems
/// * `%APPDATA%\postgresql\root.crt` on Windows
///
/// These locations are documented here: <https://www.postgresql.org/docs/12/libpq-ssl.html#LIBQ-SSL-CERTIFICATES>
/// If the root certificate cannot be found by any of these means then the TLS upgrade will fail.
///
/// If `sslmode=verify-full` is specified, in addition to checking the certificate as with
/// `sslmode=verify-ca`, the hostname in the connection string will be verified
/// against the hostname in the server certificate, so they must be the same for the TLS
/// upgrade to succeed.
pub struct PgConnection {
pub(super) stream: BufStream<TcpStream>,
pub(super) stream: BufStream<MaybeTlsStream>,
// Map of query to statement id
pub(super) statement_cache: StatementCache<StatementId>,
@ -43,8 +98,49 @@ pub struct PgConnection {
}
impl PgConnection {
#[cfg(feature = "tls")]
async fn try_ssl(
&mut self,
url: &Url,
invalid_certs: bool,
invalid_hostnames: bool,
) -> crate::Result<bool> {
use async_native_tls::TlsConnector;
protocol::SslRequest::encode(self.stream.buffer_mut());
self.stream.flush().await?;
match self.stream.peek(1).await? {
Some(b"N") => return Ok(false),
Some(b"S") => (),
Some(other) => {
return Err(tls_err!("unexpected single-byte response: 0x{:02X}", other[0]).into())
}
None => return Err(tls_err!("server unexpectedly closed connection").into()),
}
let mut connector = TlsConnector::new()
.danger_accept_invalid_certs(invalid_certs)
.danger_accept_invalid_hostnames(invalid_hostnames);
if !invalid_certs {
match read_root_certificate(&url).await {
Ok(cert) => {
connector = connector.add_root_certificate(cert);
}
Err(e) => log::warn!("failed to read Postgres root certificate: {}", e),
}
}
self.stream.clear_bufs();
self.stream.stream.upgrade(url, connector).await?;
Ok(true)
}
// https://www.postgresql.org/docs/12/protocol-flow.html#id-1.10.5.7.3
async fn startup(&mut self, url: Url) -> Result<()> {
async fn startup(&mut self, url: &Url) -> Result<()> {
// Defaults to postgres@.../postgres
let username = url.username().unwrap_or("postgres");
let database = url.database().unwrap_or("postgres");
@ -240,7 +336,8 @@ impl PgConnection {
impl PgConnection {
pub(super) async fn open(url: Result<Url>) -> Result<Self> {
let url = url?;
let stream = TcpStream::connect((url.host(), url.port(5432))).await?;
let stream = MaybeTlsStream::connect(&url, 5432).await?;
let mut self_ = Self {
stream: BufStream::new(stream),
process_id: 0,
@ -251,7 +348,48 @@ impl PgConnection {
ready: true,
};
self_.startup(url).await?;
let ssl_mode = url.get_param("sslmode").unwrap_or("prefer".into());
match &*ssl_mode {
// TODO: on "allow" retry with TLS if startup fails
"disable" | "allow" => (),
#[cfg(feature = "tls")]
"prefer" => {
if !self_.try_ssl(&url, true, true).await? {
log::warn!("server does not support TLS, falling back to unsecured connection")
}
}
#[cfg(not(feature = "tls"))]
"prefer" => log::info!("compiled without TLS, skipping upgrade"),
#[cfg(feature = "tls")]
"require" | "verify-ca" | "verify-full" => {
if !self_
.try_ssl(
&url,
ssl_mode == "require", // false for both verify-ca and verify-full
ssl_mode != "verify-full", // false for only verify-full
)
.await?
{
return Err(tls_err!("Postgres server does not support TLS").into());
}
}
#[cfg(not(feature = "tls"))]
"require" | "verify-ca" | "verify-full" => {
return Err(tls_err!(
"sslmode {:?} unsupported; SQLx was compiled without `tls` feature",
ssl_mode
)
.into())
}
_ => return Err(tls_err!("unknown `sslmode` value: {:?}", ssl_mode).into()),
}
self_.startup(&url).await?;
Ok(self_)
}
@ -271,6 +409,26 @@ impl Connection for PgConnection {
}
}
#[cfg(feature = "tls")]
async fn read_root_certificate(url: &Url) -> crate::Result<async_native_tls::Certificate> {
use std::env;
let root_cert_path = if let Some(path) = url.get_param("sslrootcert") {
path.into()
} else if let Ok(cert_path) = env::var("PGSSLROOTCERT") {
cert_path
} else if cfg!(windows) {
let appdata = env::var("APPDATA").map_err(|_| tls_err!("APPDATA not set"))?;
format!("{}\\postgresql\\root.crt", appdata)
} else {
let home = env::var("HOME").map_err(|_| tls_err!("HOME not set"))?;
format!("{}/.postgresql/root.crt", home)
};
let root_cert = async_std::fs::read(root_cert_path).await?;
Ok(async_native_tls::Certificate::from_pem(&root_cert)?)
}
static GS2_HEADER: &'static str = "n,,";
static CHANNEL_ATTR: &'static str = "c";
static USERNAME_ATTR: &'static str = "n";
@ -354,7 +512,7 @@ async fn sasl_auth<T: AsRef<str>>(conn: &mut PgConnection, username: T, password
);
// AuthMessage := client-first-message-bare + "," + server-first-message + "," + client-final-message-without-proof
let auth_message = format!("{client_first_message_bare},{server_first_message},{client_final_message_wo_proof}",
let auth_message = format!("{client_first_message_bare},{server_first_message},{client_final_message_wo_proof}",
client_first_message_bare = client_first_message_bare,
server_first_message = server_first_message,
client_final_message_wo_proof = client_final_message_wo_proof);

View File

@ -5,6 +5,7 @@
// the size of this module to exactly what is necessary.
#![allow(unused)]
// REQUESTS
mod bind;
mod cancel_request;
mod close;
@ -16,6 +17,7 @@ mod parse;
mod password_message;
mod query;
mod sasl;
mod ssl_request;
mod startup_message;
mod statement;
mod sync;
@ -32,11 +34,13 @@ pub use parse::Parse;
pub use password_message::PasswordMessage;
pub use query::Query;
pub use sasl::{hi, SaslInitialResponse, SaslResponse};
pub use ssl_request::SslRequest;
pub use startup_message::StartupMessage;
pub use statement::StatementId;
pub use sync::Sync;
pub use terminate::Terminate;
// RESPONSES
mod authentication;
mod backend_key_data;
mod command_complete;

View File

@ -0,0 +1,23 @@
use crate::io::{Buf, BufMut};
use byteorder::NetworkEndian;
pub struct SslRequest;
impl SslRequest {
pub fn encode(buf: &mut Vec<u8>) {
// packet length: 8 bytes including self
buf.put_u32::<NetworkEndian>(8);
// 1234 in high 16 bits, 5679 in low 16
buf.put_u32::<NetworkEndian>((1234 << 16) | 5679);
}
}
#[test]
fn test_ssl_request() {
use crate::io::Buf;
let mut buf = Vec::new();
SslRequest::encode(&mut buf);
assert_eq!(&buf, b"\x00\x00\x00\x08\x04\xd2\x16/");
}

View File

@ -1,3 +1,4 @@
use std::borrow::Cow;
use std::convert::{TryFrom, TryInto};
pub struct Url(url::Url);
@ -64,4 +65,10 @@ impl Url {
Some(database)
}
}
pub fn get_param(&self, key: &str) -> Option<Cow<str>> {
self.0
.query_pairs()
.find_map(|(key_, val)| if key == key_ { Some(val) } else { None })
}
}

View File

@ -66,5 +66,7 @@ async fn it_remains_stable_issue_30() -> anyhow::Result<()> {
}
async fn connect() -> anyhow::Result<PgConnection> {
let _ = dotenv::dotenv();
let _ = env_logger::try_init();
Ok(PgConnection::open(dotenv::var("DATABASE_URL")?).await?)
}