mirror of
https://github.com/dani-garcia/vaultwarden.git
synced 2026-05-08 04:23:59 +02:00
Merge 17201121f4 into f21a3adae2
This commit is contained in:
commit
defab99ddd
44 changed files with 2101 additions and 297 deletions
49
.github/workflows/lambda.yml
vendored
Normal file
49
.github/workflows/lambda.yml
vendored
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
name: Build Lambda Package
|
||||
|
||||
on: workflow_dispatch
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-24.04-arm
|
||||
|
||||
container:
|
||||
image: public.ecr.aws/codebuild/amazonlinux2-aarch64-standard:3.0
|
||||
|
||||
steps:
|
||||
- name: Checkout source code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install development packages
|
||||
run: sudo yum install -y krb5-devel openldap-devel
|
||||
|
||||
- name: Install Rust toolchain
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
|
||||
- name: Install Cargo Lambda
|
||||
uses: jaxxstorm/action-install-gh-release@v1.9.0
|
||||
with:
|
||||
repo: cargo-lambda/cargo-lambda
|
||||
platform: linux
|
||||
arch: aarch64
|
||||
|
||||
- name: Setup rust Cache
|
||||
uses: Swatinem/rust-cache@v2
|
||||
|
||||
- name: Build with Cargo
|
||||
run: cargo lambda build --verbose
|
||||
|
||||
- name: Copy libpq and its dependencies
|
||||
run: cp /lib64/{libcrypt.so.2,liblber-2.4.so.2,libldap_r-2.4.so.2,libpq.so.5,libsasl2.so.3} target/lambda/vaultwarden/
|
||||
|
||||
# This ensures passes the startup checks for the web-vault, which is
|
||||
# instead served statically from an S3 Bucket
|
||||
- name: Create placeholder web-vault/index.html
|
||||
run: |-
|
||||
mkdir target/lambda/vaultwarden/web-vault
|
||||
echo "<html><body><h1>Web Vault Placeholder</h1></body></html>" > target/lambda/vaultwarden/web-vault/index.html
|
||||
|
||||
- name: Archive function package
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: vaultwarden-lambda
|
||||
path: target/lambda/vaultwarden/*
|
||||
572
Cargo.lock
generated
572
Cargo.lock
generated
|
|
@ -8,17 +8,6 @@ version = "2.0.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
|
||||
|
||||
[[package]]
|
||||
name = "aes"
|
||||
version = "0.8.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cipher",
|
||||
"cpufeatures 0.2.17",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ahash"
|
||||
version = "0.8.12"
|
||||
|
|
@ -391,6 +380,28 @@ dependencies = [
|
|||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aws-lc-rs"
|
||||
version = "1.16.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0ec6fb3fe69024a75fa7e1bfb48aa6cf59706a101658ea01bfd33b2b248a038f"
|
||||
dependencies = [
|
||||
"aws-lc-sys",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aws-lc-sys"
|
||||
version = "0.40.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f50037ee5e1e41e7b8f9d161680a725bd1626cb6f8c7e901f91f942850852fe7"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"cmake",
|
||||
"dunce",
|
||||
"fs_extra",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aws-runtime"
|
||||
version = "1.7.3"
|
||||
|
|
@ -416,6 +427,56 @@ dependencies = [
|
|||
"uuid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aws-sdk-dsql"
|
||||
version = "1.55.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f914acf80007b4d0fc1e68d7f8045b39d58367bc3aaa8270c44368e8b8dd3ee1"
|
||||
dependencies = [
|
||||
"aws-credential-types",
|
||||
"aws-runtime",
|
||||
"aws-sigv4",
|
||||
"aws-smithy-async",
|
||||
"aws-smithy-http",
|
||||
"aws-smithy-json",
|
||||
"aws-smithy-observability",
|
||||
"aws-smithy-runtime",
|
||||
"aws-smithy-runtime-api",
|
||||
"aws-smithy-types",
|
||||
"aws-types",
|
||||
"bytes",
|
||||
"fastrand",
|
||||
"http 0.2.12",
|
||||
"http 1.4.0",
|
||||
"regex-lite",
|
||||
"tracing",
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aws-sdk-sesv2"
|
||||
version = "1.118.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b8d0642857f4fe76cd9a3d8c4f2b393546f7561f7725052dd9f268005fda92b7"
|
||||
dependencies = [
|
||||
"aws-credential-types",
|
||||
"aws-runtime",
|
||||
"aws-smithy-async",
|
||||
"aws-smithy-http",
|
||||
"aws-smithy-json",
|
||||
"aws-smithy-observability",
|
||||
"aws-smithy-runtime",
|
||||
"aws-smithy-runtime-api",
|
||||
"aws-smithy-types",
|
||||
"aws-types",
|
||||
"bytes",
|
||||
"fastrand",
|
||||
"http 0.2.12",
|
||||
"http 1.4.0",
|
||||
"regex-lite",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aws-sdk-sso"
|
||||
version = "1.98.0"
|
||||
|
|
@ -633,6 +694,7 @@ dependencies = [
|
|||
"base64-simd",
|
||||
"bytes",
|
||||
"bytes-utils",
|
||||
"futures-core",
|
||||
"http 0.2.12",
|
||||
"http 1.4.0",
|
||||
"http-body 0.4.6",
|
||||
|
|
@ -645,6 +707,8 @@ dependencies = [
|
|||
"ryu",
|
||||
"serde",
|
||||
"time",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -670,17 +734,6 @@ dependencies = [
|
|||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "backon"
|
||||
version = "1.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cffb0e931875b666fc4fcb20fee52e9bbd1ef836fd9e9e04ec21555f9f85f7ef"
|
||||
dependencies = [
|
||||
"fastrand",
|
||||
"gloo-timers",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "base16ct"
|
||||
version = "0.2.0"
|
||||
|
|
@ -778,15 +831,6 @@ dependencies = [
|
|||
"hybrid-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "block-padding"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a8894febbff9f758034a5b8e12d87918f56dfc64a8e1fe757d65e29041538d93"
|
||||
dependencies = [
|
||||
"generic-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "blocking"
|
||||
version = "1.6.2"
|
||||
|
|
@ -892,15 +936,6 @@ version = "0.1.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ade8366b8bd5ba243f0a58f036cc0ca8a2f069cff1a2351ef1cac6b083e16fc0"
|
||||
|
||||
[[package]]
|
||||
name = "cbc"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "26b52a9543ae338f279b96b0b9fed9c8093744685043739079ce85cd58f289a6"
|
||||
dependencies = [
|
||||
"cipher",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.2.61"
|
||||
|
|
@ -913,6 +948,12 @@ dependencies = [
|
|||
"shlex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cesu8"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.4"
|
||||
|
|
@ -961,13 +1002,12 @@ dependencies = [
|
|||
]
|
||||
|
||||
[[package]]
|
||||
name = "cipher"
|
||||
version = "0.4.4"
|
||||
name = "cmake"
|
||||
version = "0.1.58"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad"
|
||||
checksum = "c0f78a02292a74a88ac736019ab962ece0bc380e3f977bf72e376c5d78ff0678"
|
||||
dependencies = [
|
||||
"crypto-common 0.1.6",
|
||||
"inout",
|
||||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -1668,6 +1708,12 @@ dependencies = [
|
|||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dunce"
|
||||
version = "1.0.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813"
|
||||
|
||||
[[package]]
|
||||
name = "dyn-clone"
|
||||
version = "1.0.20"
|
||||
|
|
@ -1916,6 +1962,12 @@ dependencies = [
|
|||
"percent-encoding",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fs_extra"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c"
|
||||
|
||||
[[package]]
|
||||
name = "futures"
|
||||
version = "0.3.32"
|
||||
|
|
@ -2275,7 +2327,7 @@ dependencies = [
|
|||
"hickory-proto",
|
||||
"idna",
|
||||
"ipnet",
|
||||
"jni",
|
||||
"jni 0.22.4",
|
||||
"rand 0.10.1",
|
||||
"thiserror 2.0.18",
|
||||
"tinyvec",
|
||||
|
|
@ -2293,7 +2345,7 @@ dependencies = [
|
|||
"data-encoding",
|
||||
"idna",
|
||||
"ipnet",
|
||||
"jni",
|
||||
"jni 0.22.4",
|
||||
"once_cell",
|
||||
"prefix-trie",
|
||||
"rand 0.10.1",
|
||||
|
|
@ -2316,7 +2368,7 @@ dependencies = [
|
|||
"hickory-proto",
|
||||
"ipconfig",
|
||||
"ipnet",
|
||||
"jni",
|
||||
"jni 0.22.4",
|
||||
"moka",
|
||||
"ndk-context",
|
||||
"once_cell",
|
||||
|
|
@ -2357,15 +2409,6 @@ dependencies = [
|
|||
"digest 0.11.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "home"
|
||||
version = "0.5.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cc627f471c528ff0c4a49e1d5e60450c8f6461dd6d10ba9dcd3a61d3dff7728d"
|
||||
dependencies = [
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hostname"
|
||||
version = "0.4.2"
|
||||
|
|
@ -2716,16 +2759,6 @@ version = "0.1.15"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c8fae54786f62fb2918dcfae3d568594e50eb9b5c25bf04371af6fe7516452fb"
|
||||
|
||||
[[package]]
|
||||
name = "inout"
|
||||
version = "0.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01"
|
||||
dependencies = [
|
||||
"block-padding",
|
||||
"generic-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ipconfig"
|
||||
version = "0.3.4"
|
||||
|
|
@ -2798,10 +2831,12 @@ checksum = "f00b5dbd620d61dfdcb6007c9c1f6054ebd75319f163d886a9055cec1155073d"
|
|||
dependencies = [
|
||||
"jiff-static",
|
||||
"jiff-tzdb-platform",
|
||||
"js-sys",
|
||||
"log",
|
||||
"portable-atomic",
|
||||
"portable-atomic-util",
|
||||
"serde_core",
|
||||
"wasm-bindgen",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
|
|
@ -2831,6 +2866,22 @@ dependencies = [
|
|||
"jiff-tzdb",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jni"
|
||||
version = "0.21.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97"
|
||||
dependencies = [
|
||||
"cesu8",
|
||||
"cfg-if",
|
||||
"combine",
|
||||
"jni-sys 0.3.1",
|
||||
"log",
|
||||
"thiserror 1.0.69",
|
||||
"walkdir",
|
||||
"windows-sys 0.45.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jni"
|
||||
version = "0.22.4"
|
||||
|
|
@ -2840,7 +2891,7 @@ dependencies = [
|
|||
"cfg-if",
|
||||
"combine",
|
||||
"jni-macros",
|
||||
"jni-sys",
|
||||
"jni-sys 0.4.1",
|
||||
"log",
|
||||
"simd_cesu8",
|
||||
"thiserror 2.0.18",
|
||||
|
|
@ -2861,6 +2912,15 @@ dependencies = [
|
|||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jni-sys"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "41a652e1f9b6e0275df1f15b32661cf0d4b78d4d87ddec5e0c3c20f097433258"
|
||||
dependencies = [
|
||||
"jni-sys 0.4.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jni-sys"
|
||||
version = "0.4.1"
|
||||
|
|
@ -2913,21 +2973,6 @@ dependencies = [
|
|||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jsonwebtoken"
|
||||
version = "9.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5a87cc7a48537badeae96744432de36f4be2b4a34a05a5ef32e9dd8a1c169dde"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"js-sys",
|
||||
"pem",
|
||||
"ring",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"simple_asn1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jsonwebtoken"
|
||||
version = "10.3.0"
|
||||
|
|
@ -3131,6 +3176,15 @@ dependencies = [
|
|||
"digest 0.10.7",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mea"
|
||||
version = "0.6.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6747f54621d156e1b47eb6b25f39a941b9fc347f98f67d25d8881ff99e8ed832"
|
||||
dependencies = [
|
||||
"slab",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.8.0"
|
||||
|
|
@ -3408,7 +3462,7 @@ dependencies = [
|
|||
"getrandom 0.2.17",
|
||||
"http 1.4.0",
|
||||
"rand 0.8.6",
|
||||
"reqwest",
|
||||
"reqwest 0.12.28",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_path_to_error",
|
||||
|
|
@ -3438,31 +3492,76 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "opendal"
|
||||
version = "0.55.0"
|
||||
version = "0.56.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d075ab8a203a6ab4bc1bce0a4b9fe486a72bf8b939037f4b78d95386384bc80a"
|
||||
checksum = "97b31d3d8e99a85d83b73ec26647f5607b80578ed9375810b6e44ffa3590a236"
|
||||
dependencies = [
|
||||
"opendal-core",
|
||||
"opendal-service-fs",
|
||||
"opendal-service-s3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "opendal-core"
|
||||
version = "0.56.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1849dd2687e173e776d3af5fce1ba3ae47b9dd37a09d1c4deba850ef45fe00ca"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"backon",
|
||||
"base64 0.22.1",
|
||||
"bytes",
|
||||
"crc32c",
|
||||
"futures",
|
||||
"getrandom 0.2.17",
|
||||
"http 1.4.0",
|
||||
"http-body 1.0.1",
|
||||
"jiff",
|
||||
"log",
|
||||
"md-5",
|
||||
"mea",
|
||||
"percent-encoding",
|
||||
"quick-xml 0.38.4",
|
||||
"reqsign",
|
||||
"reqwest",
|
||||
"reqsign-core",
|
||||
"reqwest 0.13.3",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tokio",
|
||||
"url",
|
||||
"uuid",
|
||||
"web-time",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "opendal-service-fs"
|
||||
version = "0.56.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf0be0417abeeb0053376d816b90fceb9ca98f20dfb54ebf1f2a282729f83663"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"log",
|
||||
"opendal-core",
|
||||
"serde",
|
||||
"tokio",
|
||||
"xattr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "opendal-service-s3"
|
||||
version = "0.56.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9dadddeb9bb50b0d30927dd914c298c4ddca47e4c1cfa7674d311f0cf9b051c8"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"bytes",
|
||||
"crc32c",
|
||||
"http 1.4.0",
|
||||
"log",
|
||||
"md-5",
|
||||
"opendal-core",
|
||||
"quick-xml 0.38.4",
|
||||
"reqsign-aws-v4",
|
||||
"reqsign-core",
|
||||
"reqsign-file-read-tokio",
|
||||
"serde",
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -3651,16 +3750,6 @@ version = "0.2.2"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c5a797f0e07bdf071d15742978fc3128ec6c22891c31a3a931513263904c982a"
|
||||
|
||||
[[package]]
|
||||
name = "pbkdf2"
|
||||
version = "0.12.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2"
|
||||
dependencies = [
|
||||
"digest 0.10.7",
|
||||
"hmac 0.12.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pear"
|
||||
version = "0.2.9"
|
||||
|
|
@ -3852,21 +3941,6 @@ dependencies = [
|
|||
"spki",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pkcs5"
|
||||
version = "0.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e847e2c91a18bfa887dd028ec33f2fe6f25db77db3619024764914affe8b69a6"
|
||||
dependencies = [
|
||||
"aes",
|
||||
"cbc",
|
||||
"der",
|
||||
"pbkdf2",
|
||||
"scrypt",
|
||||
"sha2 0.10.9",
|
||||
"spki",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pkcs8"
|
||||
version = "0.10.2"
|
||||
|
|
@ -3874,8 +3948,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7"
|
||||
dependencies = [
|
||||
"der",
|
||||
"pkcs5",
|
||||
"rand_core 0.6.4",
|
||||
"spki",
|
||||
]
|
||||
|
||||
|
|
@ -4040,9 +4112,9 @@ checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3"
|
|||
|
||||
[[package]]
|
||||
name = "quick-xml"
|
||||
version = "0.37.5"
|
||||
version = "0.38.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "331e97a1af0bf59823e6eadffe373d7b27f485be8748f71471c662c1f269b7fb"
|
||||
checksum = "b66c2058c55a409d601666cffe35f04333cf1013010882cec174a7467cd4e21c"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
"serde",
|
||||
|
|
@ -4050,9 +4122,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "quick-xml"
|
||||
version = "0.38.4"
|
||||
version = "0.39.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b66c2058c55a409d601666cffe35f04333cf1013010882cec174a7467cd4e21c"
|
||||
checksum = "721da970c312655cde9b4ffe0547f20a8494866a4af5ff51f18b7c633d0c870b"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
"serde",
|
||||
|
|
@ -4084,6 +4156,7 @@ version = "0.11.14"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "434b42fec591c96ef50e21e886936e66d3cc3f737104fdb9b737c40ffb94c098"
|
||||
dependencies = [
|
||||
"aws-lc-rs",
|
||||
"bytes",
|
||||
"getrandom 0.3.4",
|
||||
"lru-slab",
|
||||
|
|
@ -4312,36 +4385,57 @@ dependencies = [
|
|||
]
|
||||
|
||||
[[package]]
|
||||
name = "reqsign"
|
||||
version = "0.16.5"
|
||||
name = "reqsign-aws-v4"
|
||||
version = "3.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "43451dbf3590a7590684c25fb8d12ecdcc90ed3ac123433e500447c7d77ed701"
|
||||
checksum = "44eaca382e94505a49f1a4849658d153aebf79d9c1a58e5dd3b10361511e9f43"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
"base64 0.22.1",
|
||||
"chrono",
|
||||
"bytes",
|
||||
"form_urlencoded",
|
||||
"getrandom 0.2.17",
|
||||
"hex",
|
||||
"hmac 0.12.1",
|
||||
"home",
|
||||
"http 1.4.0",
|
||||
"jsonwebtoken 9.3.1",
|
||||
"log",
|
||||
"once_cell",
|
||||
"percent-encoding",
|
||||
"quick-xml 0.37.5",
|
||||
"rand 0.8.6",
|
||||
"reqwest",
|
||||
"rsa",
|
||||
"quick-xml 0.39.3",
|
||||
"reqsign-core",
|
||||
"rust-ini",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_urlencoded",
|
||||
"sha1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "reqsign-core"
|
||||
version = "3.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b10302cf0a7d7e7352ba211fc92c3c5bebf1286153e49cc5aa87348078a8e102"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"base64 0.22.1",
|
||||
"bytes",
|
||||
"form_urlencoded",
|
||||
"futures",
|
||||
"hex",
|
||||
"hmac 0.12.1",
|
||||
"http 1.4.0",
|
||||
"jiff",
|
||||
"log",
|
||||
"percent-encoding",
|
||||
"sha1",
|
||||
"sha2 0.10.9",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "reqsign-file-read-tokio"
|
||||
version = "3.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e2d89295b3d17abea31851cc8de55d843d89c52132c864963c38d41920613dc5"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"reqsign-core",
|
||||
"tokio",
|
||||
"toml 0.8.23",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -4387,11 +4481,49 @@ dependencies = [
|
|||
"url",
|
||||
"wasm-bindgen",
|
||||
"wasm-bindgen-futures",
|
||||
"wasm-streams",
|
||||
"wasm-streams 0.4.2",
|
||||
"web-sys",
|
||||
"webpki-roots",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "reqwest"
|
||||
version = "0.13.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "62e0021ea2c22aed41653bc7e1419abb2c97e038ff2c33d0e1309e49a97deec0"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"bytes",
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
"http 1.4.0",
|
||||
"http-body 1.0.1",
|
||||
"http-body-util",
|
||||
"hyper 1.9.0",
|
||||
"hyper-rustls",
|
||||
"hyper-util",
|
||||
"js-sys",
|
||||
"log",
|
||||
"percent-encoding",
|
||||
"pin-project-lite",
|
||||
"quinn",
|
||||
"rustls 0.23.40",
|
||||
"rustls-pki-types",
|
||||
"rustls-platform-verifier",
|
||||
"sync_wrapper",
|
||||
"tokio",
|
||||
"tokio-rustls 0.26.4",
|
||||
"tokio-util",
|
||||
"tower",
|
||||
"tower-http",
|
||||
"tower-service",
|
||||
"url",
|
||||
"wasm-bindgen",
|
||||
"wasm-bindgen-futures",
|
||||
"wasm-streams 0.5.0",
|
||||
"web-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "resolv-conf"
|
||||
version = "0.7.6"
|
||||
|
|
@ -4560,7 +4692,6 @@ dependencies = [
|
|||
"pkcs1",
|
||||
"pkcs8",
|
||||
"rand_core 0.6.4",
|
||||
"sha2 0.10.9",
|
||||
"signature",
|
||||
"spki",
|
||||
"subtle",
|
||||
|
|
@ -4652,6 +4783,7 @@ version = "0.23.40"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ef86cd5876211988985292b91c96a8f2d298df24e75989a43a3c73f2d4d8168b"
|
||||
dependencies = [
|
||||
"aws-lc-rs",
|
||||
"log",
|
||||
"once_cell",
|
||||
"ring",
|
||||
|
|
@ -4692,6 +4824,33 @@ dependencies = [
|
|||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls-platform-verifier"
|
||||
version = "0.6.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1d99feebc72bae7ab76ba994bb5e121b8d83d910ca40b36e0921f53becc41784"
|
||||
dependencies = [
|
||||
"core-foundation 0.10.1",
|
||||
"core-foundation-sys",
|
||||
"jni 0.21.1",
|
||||
"log",
|
||||
"once_cell",
|
||||
"rustls 0.23.40",
|
||||
"rustls-native-certs",
|
||||
"rustls-platform-verifier-android",
|
||||
"rustls-webpki 0.103.13",
|
||||
"security-framework",
|
||||
"security-framework-sys",
|
||||
"webpki-root-certs",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls-platform-verifier-android"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f"
|
||||
|
||||
[[package]]
|
||||
name = "rustls-webpki"
|
||||
version = "0.101.7"
|
||||
|
|
@ -4708,6 +4867,7 @@ version = "0.103.13"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e"
|
||||
dependencies = [
|
||||
"aws-lc-rs",
|
||||
"ring",
|
||||
"rustls-pki-types",
|
||||
"untrusted",
|
||||
|
|
@ -4725,15 +4885,6 @@ version = "1.0.23"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f"
|
||||
|
||||
[[package]]
|
||||
name = "salsa20"
|
||||
version = "0.10.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "97a22f5af31f73a954c10289c93e8a50cc23d971e80ee446f1f6f7137a088213"
|
||||
dependencies = [
|
||||
"cipher",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "same-file"
|
||||
version = "1.0.6"
|
||||
|
|
@ -4797,17 +4948,6 @@ version = "1.2.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
||||
|
||||
[[package]]
|
||||
name = "scrypt"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0516a385866c09368f0b5bcd1caff3366aace790fcd46e2bb032697bb172fd1f"
|
||||
dependencies = [
|
||||
"pbkdf2",
|
||||
"salsa20",
|
||||
"sha2 0.10.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sct"
|
||||
version = "0.7.1"
|
||||
|
|
@ -5869,10 +6009,11 @@ checksum = "7ba6f5989077681266825251a52748b8c1d8a4ad098cc37e440103d0ea717fc0"
|
|||
name = "vaultwarden"
|
||||
version = "1.0.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"argon2",
|
||||
"aws-config",
|
||||
"aws-credential-types",
|
||||
"aws-sdk-dsql",
|
||||
"aws-sdk-sesv2",
|
||||
"aws-smithy-runtime-api",
|
||||
"bigdecimal",
|
||||
"bytes",
|
||||
|
|
@ -5899,7 +6040,7 @@ dependencies = [
|
|||
"html5gum",
|
||||
"http 1.4.0",
|
||||
"job_scheduler_ng",
|
||||
"jsonwebtoken 10.3.0",
|
||||
"jsonwebtoken",
|
||||
"lettre",
|
||||
"libsqlite3-sys",
|
||||
"log",
|
||||
|
|
@ -5916,8 +6057,9 @@ dependencies = [
|
|||
"pico-args",
|
||||
"rand 0.10.1",
|
||||
"regex",
|
||||
"reqsign",
|
||||
"reqwest",
|
||||
"reqsign-aws-v4",
|
||||
"reqsign-core",
|
||||
"reqwest 0.12.28",
|
||||
"ring",
|
||||
"rmpv",
|
||||
"rocket",
|
||||
|
|
@ -6094,6 +6236,19 @@ dependencies = [
|
|||
"web-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-streams"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9d1ec4f6517c9e11ae630e200b2b65d193279042e28edd4a2cda233e46670bbb"
|
||||
dependencies = [
|
||||
"futures-util",
|
||||
"js-sys",
|
||||
"wasm-bindgen",
|
||||
"wasm-bindgen-futures",
|
||||
"web-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasmparser"
|
||||
version = "0.244.0"
|
||||
|
|
@ -6194,6 +6349,15 @@ dependencies = [
|
|||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "webpki-root-certs"
|
||||
version = "1.0.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f31141ce3fc3e300ae89b78c0dd67f9708061d1d2eda54b8209346fd6be9a92c"
|
||||
dependencies = [
|
||||
"rustls-pki-types",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "webpki-roots"
|
||||
version = "1.0.7"
|
||||
|
|
@ -6328,6 +6492,15 @@ dependencies = [
|
|||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.45.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0"
|
||||
dependencies = [
|
||||
"windows-targets 0.42.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.52.0"
|
||||
|
|
@ -6364,6 +6537,21 @@ dependencies = [
|
|||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071"
|
||||
dependencies = [
|
||||
"windows_aarch64_gnullvm 0.42.2",
|
||||
"windows_aarch64_msvc 0.42.2",
|
||||
"windows_i686_gnu 0.42.2",
|
||||
"windows_i686_msvc 0.42.2",
|
||||
"windows_x86_64_gnu 0.42.2",
|
||||
"windows_x86_64_gnullvm 0.42.2",
|
||||
"windows_x86_64_msvc 0.42.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.48.5"
|
||||
|
|
@ -6412,6 +6600,12 @@ dependencies = [
|
|||
"windows_x86_64_msvc 0.53.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.48.5"
|
||||
|
|
@ -6430,6 +6624,12 @@ version = "0.53.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.48.5"
|
||||
|
|
@ -6448,6 +6648,12 @@ version = "0.53.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.48.5"
|
||||
|
|
@ -6478,6 +6684,12 @@ version = "0.53.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.48.5"
|
||||
|
|
@ -6496,6 +6708,12 @@ version = "0.53.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.48.5"
|
||||
|
|
@ -6514,6 +6732,12 @@ version = "0.53.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.48.5"
|
||||
|
|
@ -6532,6 +6756,12 @@ version = "0.53.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.48.5"
|
||||
|
|
@ -6691,6 +6921,16 @@ dependencies = [
|
|||
"time",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "xattr"
|
||||
version = "1.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "32e45ad4206f6d2479085147f02bc2ef834ac85886624a23575ae137c8aa8156"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"rustix",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "xml"
|
||||
version = "1.2.1"
|
||||
|
|
@ -6746,7 +6986,7 @@ dependencies = [
|
|||
"futures",
|
||||
"hmac 0.12.1",
|
||||
"rand 0.9.4",
|
||||
"reqwest",
|
||||
"reqwest 0.12.28",
|
||||
"sha1",
|
||||
"threadpool",
|
||||
]
|
||||
|
|
|
|||
13
Cargo.toml
13
Cargo.toml
|
|
@ -39,7 +39,10 @@ vendored_openssl = ["openssl/vendored"]
|
|||
# Enable MiMalloc memory allocator to replace the default malloc
|
||||
# This can improve performance for Alpine builds
|
||||
enable_mimalloc = ["dep:mimalloc"]
|
||||
s3 = ["opendal/services-s3", "dep:aws-config", "dep:aws-credential-types", "dep:aws-smithy-runtime-api", "dep:anyhow", "dep:http", "dep:reqsign"]
|
||||
aws = ["dsql", "s3", "ses"]
|
||||
dsql = ["postgresql", "dep:aws-config", "dep:aws-sdk-dsql", "dep:aws-smithy-runtime-api"]
|
||||
ses = ["dep:aws-config", "dep:aws-sdk-sesv2", "dep:aws-smithy-runtime-api"]
|
||||
s3 = ["opendal/services-s3", "opendal/reqwest-rustls-tls", "dep:aws-config", "dep:aws-credential-types", "dep:aws-smithy-runtime-api", "dep:http", "dep:reqsign-aws-v4", "dep:reqsign-core"]
|
||||
|
||||
# OIDC specific features
|
||||
oidc-accept-rfc3339-timestamps = ["openidconnect/accept-rfc3339-timestamps"]
|
||||
|
|
@ -196,15 +199,17 @@ rpassword = "7.5.1"
|
|||
grass_compiler = { version = "0.13.4", default-features = false }
|
||||
|
||||
# File are accessed through Apache OpenDAL
|
||||
opendal = { version = "0.55.0", features = ["services-fs"], default-features = false }
|
||||
opendal = { version = "0.56.0", features = ["services-fs"], default-features = false }
|
||||
|
||||
# For retrieving AWS credentials, including temporary SSO credentials
|
||||
anyhow = { version = "1.0.102", optional = true }
|
||||
aws-config = { version = "1.8.16", features = ["behavior-version-latest", "rt-tokio", "credentials-process", "sso"], default-features = false, optional = true }
|
||||
aws-credential-types = { version = "1.2.14", optional = true }
|
||||
aws-sdk-dsql = { version = "1.55.0", features = ["behavior-version-latest", "rt-tokio"], default-features = false, optional = true }
|
||||
aws-sdk-sesv2 = { version = "1.118.0", features = ["behavior-version-latest", "rt-tokio"], default-features = false, optional = true }
|
||||
aws-smithy-runtime-api = { version = "1.12.0", optional = true }
|
||||
http = { version = "1.4.0", optional = true }
|
||||
reqsign = { version = "0.16.5", optional = true }
|
||||
reqsign-aws-v4 = { version = "3.0.0", optional = true }
|
||||
reqsign-core = { version = "3.0.0", optional = true }
|
||||
|
||||
# Strip debuginfo from the release builds
|
||||
# The debug symbols are to provide better panic traces
|
||||
|
|
|
|||
7
CargoLambda.toml
Normal file
7
CargoLambda.toml
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
[build]
|
||||
features = ["aws"]
|
||||
release = true
|
||||
arm64 = true
|
||||
|
||||
[build.compiler]
|
||||
type = "cargo"
|
||||
3
aws/.gitignore
vendored
Normal file
3
aws/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
.aws-sam
|
||||
vaultwarden-lambda.zip
|
||||
web-vault
|
||||
62
aws/README.md
Normal file
62
aws/README.md
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
# AWS Serverless Deployment Instructions
|
||||
|
||||
## Architecture
|
||||
```
|
||||
CloudFront CDN
|
||||
├─ API Lambda Function
|
||||
│ ├─ Data S3 Bucket
|
||||
│ ├─ Aurora DSQL Database
|
||||
│ └─ Amazon Simple Email Service (SES)
|
||||
└─ Web-vault static assets S3 Bucket
|
||||
```
|
||||
|
||||
## A Note On AWS Accounts and Security
|
||||
It is common to have one AWS account host multiple services. But it's easy, and doesn't cost any additional amount, to separate workloads into their own accounts. Doing so makes it easier to control for security concerns and monitor costs. AWS Identity and Access Management (IAM) enforces additional controls for cross-account access than for within-account access, for example, making it harder for security attacks to hop from workload to workload when they are in separate accounts.
|
||||
|
||||
Given the confidential nature of data stored in Vaultwarden, it is *highly* recommended that you create a new, separate AWS account just for Vaultwarden. If you only have one account, investigate creating an [AWS Organization](https://aws.amazon.com/organizations/) to make it easy to create a second account tied to the same billing and account management mechanism, and investigate creating an [AWS IAM Identity Center](https://aws.amazon.com/iam/identity-center/) instance for easy SSO access across your accounts.
|
||||
|
||||
## Initial Deployment
|
||||
1. Create an AWS account
|
||||
1. Install the AWS CLI
|
||||
1. Install AWS SAM CLI
|
||||
1. Build or download the `vaultwarden-lambda.zip` Lambda Function code package to this directory
|
||||
1. Pick a region that supports DSQL to deploy the Vaultwarden application into (must be one of us-east-1 or us-east-2 during DSQL Preview)
|
||||
1. Create an Aurora DSQL Cluster in the region using the AWS Console (this will be automated when CloudFormation ships DSQL support at GA)
|
||||
1. Setup local AWS configuration to access account and region from CLI
|
||||
1. Copy DSQL Cluster ID
|
||||
1. Run `./deploy.sh` in this directory
|
||||
* Most parameters can be skipped at first, but you must provide the `DSQLClusterID` parameter value.
|
||||
1. Note the "Output" values from the deploy command
|
||||
* These can also be retrieved later by running `sam list stack-outputs`
|
||||
1. Download the latest [web-vault build](https://github.com/dani-garcia/bw_web_builds/releases) and extract it
|
||||
1. Sync the web-vault build contents into the WebVaultAssetsBucket:
|
||||
* Inside the web-vault build folder run `aws s3 sync . s3://<WebVaultAssetsBucket>`, where `WebVaultAssetsBucket` is a stack output value
|
||||
1. You can now navigate to your instance at the location of your `CDNDomain` stack output value
|
||||
|
||||
## Building The Lambda Package Locally
|
||||
The GitHub Actions Lambda workflow builds inside Amazon Linux for arm64 and packages the contents of `target/lambda/vaultwarden`. To do the same locally, run the VS Code `Build Lambda package` task or run:
|
||||
|
||||
```sh
|
||||
./aws/build-lambda.sh
|
||||
```
|
||||
|
||||
This uses Docker with `public.ecr.aws/codebuild/amazonlinux2-aarch64-standard:3.0`, installs the same build dependencies as `.github/workflows/lambda.yml`, runs `cargo lambda build --verbose`, copies the required shared libraries, creates the placeholder web vault file, and writes `aws/vaultwarden-lambda.zip`.
|
||||
|
||||
## Custom Domain
|
||||
1. Create an AWS Certificate Manager (ACM) Certificate for your domain **in the us-east-1 region**
|
||||
* There are many tutorials and/or automated ways to do this, including following the official docs [here](https://docs.aws.amazon.com/acm/latest/userguide/acm-public-certificates.html)
|
||||
* It must be in the us-east-1 region because CloudFront only supports certificates from us-east-1
|
||||
* Use key algorithm RSA 2048
|
||||
* Continue to the next step once the certificate is in the *Issued* state
|
||||
* Note the certificate's ARN
|
||||
1. Run `./deploy.sh` again and add the following parameter values:
|
||||
* **Domain**: `https://<custom domain>`
|
||||
* **ACMCertificateArn**: The ARN of the certificate you created for the domain
|
||||
1. Create a CNAME record for the custom domain set to the value of the CDNDomain stack output
|
||||
|
||||
## Email via AWS Simple Email Service (SES)
|
||||
Email is complicated. These instructions will not attempt to walk you through setting up SES identities for sending email. You may find docs and guides online for how to do this.
|
||||
|
||||
In order for Vaultwarden to send emails using SES you must have an SES Email Address Identity that **does not have a default configuration set**. An identity with a default configuration set breaks the IAM permission model set up for the Vaultwarden API Function.
|
||||
|
||||
Once you have an SES Identity for the sending email address, run `./deploy.sh` again and provide the email address in the `SMTP_FROM` parameter.
|
||||
74
aws/build-lambda.sh
Executable file
74
aws/build-lambda.sh
Executable file
|
|
@ -0,0 +1,74 @@
|
|||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
repo_root="$(cd "${script_dir}/.." && pwd)"
|
||||
|
||||
image="${VAULTWARDEN_LAMBDA_BUILD_IMAGE:-public.ecr.aws/codebuild/amazonlinux2-aarch64-standard:3.0}"
|
||||
platform="${VAULTWARDEN_LAMBDA_BUILD_PLATFORM:-linux/arm64}"
|
||||
package_path="${repo_root}/aws/vaultwarden-lambda.zip"
|
||||
|
||||
docker_tty_args=()
|
||||
if [ -t 1 ]; then
|
||||
docker_tty_args=(-t)
|
||||
fi
|
||||
|
||||
printf 'Building Lambda package with %s for %s\n' "${image}" "${platform}"
|
||||
|
||||
docker run \
|
||||
--rm \
|
||||
--pull=missing \
|
||||
--platform "${platform}" \
|
||||
"${docker_tty_args[@]}" \
|
||||
--entrypoint /bin/bash \
|
||||
-e HOST_UID="$(id -u)" \
|
||||
-e HOST_GID="$(id -g)" \
|
||||
-v "${repo_root}:/work" \
|
||||
-v vaultwarden-lambda-cargo-home:/root/.cargo \
|
||||
-v vaultwarden-lambda-rustup-home:/root/.rustup \
|
||||
-w /work \
|
||||
"${image}" \
|
||||
-lc '
|
||||
set -euo pipefail
|
||||
|
||||
export PATH="$HOME/.cargo/bin:$PATH"
|
||||
|
||||
restore_ownership() {
|
||||
for path in target aws/vaultwarden-lambda.zip; do
|
||||
if [ -e "$path" ]; then
|
||||
chown -R "${HOST_UID}:${HOST_GID}" "$path"
|
||||
fi
|
||||
done
|
||||
}
|
||||
trap restore_ownership EXIT
|
||||
|
||||
yum install -y krb5-devel openldap-devel unzip xz zip
|
||||
|
||||
if ! command -v rustup >/dev/null 2>&1; then
|
||||
curl --proto "=https" --tlsv1.2 -sSf https://sh.rustup.rs \
|
||||
| sh -s -- -y --profile minimal --default-toolchain stable
|
||||
fi
|
||||
|
||||
rustup default stable
|
||||
|
||||
if ! command -v cargo-lambda >/dev/null 2>&1; then
|
||||
curl -fsSL https://cargo-lambda.info/install.sh | sh
|
||||
fi
|
||||
|
||||
cargo lambda build --verbose
|
||||
|
||||
cp /lib64/{libcrypt.so.2,liblber-2.4.so.2,libldap_r-2.4.so.2,libpq.so.5,libsasl2.so.3} \
|
||||
target/lambda/vaultwarden/
|
||||
|
||||
mkdir -p target/lambda/vaultwarden/web-vault
|
||||
printf "%s\n" "<html><body><h1>Web Vault Placeholder</h1></body></html>" \
|
||||
> target/lambda/vaultwarden/web-vault/index.html
|
||||
|
||||
rm -f aws/vaultwarden-lambda.zip
|
||||
(
|
||||
cd target/lambda/vaultwarden
|
||||
zip -r /work/aws/vaultwarden-lambda.zip .
|
||||
)
|
||||
'
|
||||
|
||||
printf 'Created %s\n' "${package_path}"
|
||||
9
aws/deploy.sh
Executable file
9
aws/deploy.sh
Executable file
|
|
@ -0,0 +1,9 @@
|
|||
#!/bin/sh -e
|
||||
|
||||
echo 'Building template...'
|
||||
|
||||
sam build
|
||||
|
||||
echo ''
|
||||
|
||||
sam deploy --guided
|
||||
12
aws/samconfig.toml
Normal file
12
aws/samconfig.toml
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
version = 0.1
|
||||
|
||||
[default.global.parameters]
|
||||
stack_name = "vaultwarden"
|
||||
|
||||
[default.deploy.parameters]
|
||||
resolve_s3 = true
|
||||
s3_prefix = "vaultwarden"
|
||||
confirm_changeset = true
|
||||
capabilities = "CAPABILITY_IAM"
|
||||
image_repositories = []
|
||||
disable_rollback = true
|
||||
575
aws/template.yaml
Normal file
575
aws/template.yaml
Normal file
|
|
@ -0,0 +1,575 @@
|
|||
AWSTemplateFormatVersion: '2010-09-09'
|
||||
Description: AWS CloudFormation template for running VaultWarden on AWS serverless services.
|
||||
|
||||
Parameters:
|
||||
Domain:
|
||||
Type: String
|
||||
Description: >-
|
||||
The domain name for the Vaultwarden instance (e.g. https://example.com). If this parameter or the ACMCertificateArn
|
||||
parameter are left empty, the Vaultwarden instance can still be reached at the output CDN domain
|
||||
(e.g. https://xxxxxxxx.cloudfront.net).
|
||||
AllowedPattern: (https://[a-z0-9.-]+|)
|
||||
Default: ''
|
||||
ACMCertificateArn:
|
||||
Type: String
|
||||
Description: The ARN of a us-east-1 ACM certificate to use for the domain. Required if the `Domain` parameter is set.
|
||||
AllowedPattern: (arn:aws:acm:us-east-1:[0-9]+:certificate/[0-9a-f-]+|)
|
||||
Default: ''
|
||||
DSQLClusterId:
|
||||
Type: String
|
||||
Description: The endpoint of the DSQL database.
|
||||
AllowedPattern: '[a-z0-9]+'
|
||||
APILogRetention:
|
||||
Type: Number
|
||||
Description: The number of days to retain the API logs. -1 means to never expire.
|
||||
Default: -1
|
||||
AllowedValues: [-1, 1, 3, 5, 7, 14, 30, 60, 90, 120, 150, 180, 365, 400, 545, 731, 1096, 1827, 2192, 2557, 2922, 3288, 3653]
|
||||
SignupsAllowed:
|
||||
Type: String
|
||||
Description: Controls if new users can register
|
||||
Default: 'true'
|
||||
AllowedValues: ['true', 'false']
|
||||
IconService:
|
||||
Type: String
|
||||
Description: Allowed icon service sources.
|
||||
Default: bitwarden
|
||||
AdminToken:
|
||||
Type: String
|
||||
Description: Token for the admin interface, preferably an Argon2 PCH string. If empty, the admin interface will be disabled.
|
||||
Default: ''
|
||||
SMTPFrom:
|
||||
Type: String
|
||||
Description: The email address to send emails from. Email service is disabled if this value is empty.
|
||||
Default: ''
|
||||
SMTPFromName:
|
||||
Type: String
|
||||
Description: The name to send emails from.
|
||||
Default: Vaultwarden
|
||||
|
||||
Mappings:
|
||||
IconSource:
|
||||
internal:
|
||||
CSP: ''
|
||||
bitwarden:
|
||||
CSP: https://icons.bitwarden.net/
|
||||
duckduckgo:
|
||||
CSP: https://icons.duckduckgo.com/ip3/
|
||||
google:
|
||||
CSP: https://www.google.com/s2/favicons https://*.gstatic.com/favicon
|
||||
|
||||
Conditions:
|
||||
IsDomainAndCertificateSet: !And
|
||||
- !Not [!Equals [!Ref Domain, '']]
|
||||
- !Not [!Equals [!Ref ACMCertificateArn, '']]
|
||||
IsApiLogRetentionNeverExpire: !Equals
|
||||
- !Ref APILogRetention
|
||||
- -1
|
||||
IconSourceIsPredefined: !Or
|
||||
- !Equals [!Ref IconService, internal]
|
||||
- !Equals [!Ref IconService, bitwarden]
|
||||
- !Equals [!Ref IconService, duckduckgo]
|
||||
- !Equals [!Ref IconService, google]
|
||||
IsAdminTokenEmpty: !Equals
|
||||
- !Ref AdminToken
|
||||
- ''
|
||||
IsEmailEnabled: !Not
|
||||
- !Equals
|
||||
- !Ref SMTPFrom
|
||||
- ''
|
||||
|
||||
Resources:
|
||||
DataBucket:
|
||||
Type: AWS::S3::Bucket
|
||||
Properties:
|
||||
BucketEncryption:
|
||||
ServerSideEncryptionConfiguration:
|
||||
- BucketKeyEnabled: true
|
||||
ServerSideEncryptionByDefault:
|
||||
SSEAlgorithm: aws:kms
|
||||
BucketName: !Sub ${AWS::StackName}-${AWS::AccountId}-${AWS::Region}-data
|
||||
CorsConfiguration:
|
||||
CorsRules:
|
||||
- AllowedMethods:
|
||||
- GET
|
||||
- HEAD
|
||||
AllowedOrigins:
|
||||
- '*'
|
||||
LifecycleConfiguration:
|
||||
Rules:
|
||||
- AbortIncompleteMultipartUpload:
|
||||
DaysAfterInitiation: 2
|
||||
ExpiredObjectDeleteMarker: true
|
||||
NoncurrentVersionExpiration:
|
||||
NoncurrentDays: 30
|
||||
Status: Enabled
|
||||
PublicAccessBlockConfiguration:
|
||||
BlockPublicAcls: true
|
||||
BlockPublicPolicy: true
|
||||
IgnorePublicAcls: true
|
||||
RestrictPublicBuckets: true
|
||||
VersioningConfiguration:
|
||||
Status: Enabled
|
||||
|
||||
DataBucketEnforceEncryptionAndStorageTier:
|
||||
Type: AWS::S3::BucketPolicy
|
||||
Properties:
|
||||
Bucket: !Ref DataBucket
|
||||
PolicyDocument:
|
||||
Version: '2012-10-17'
|
||||
Statement:
|
||||
- Sid: DenyUnencryptedObjectUploads
|
||||
Effect: Deny
|
||||
Principal: '*'
|
||||
Action: s3:PutObject
|
||||
Resource: !Sub arn:${AWS::Partition}:s3:::${DataBucket}/*
|
||||
Condition:
|
||||
'Null':
|
||||
s3:x-amz-server-side-encryption-aws-kms-key-id: true
|
||||
- Sid: DenyUnencryptedTransit
|
||||
Effect: Deny
|
||||
Principal: '*'
|
||||
Action: s3:*
|
||||
Resource:
|
||||
- !Sub arn:${AWS::Partition}:s3:::${DataBucket}
|
||||
- !Sub arn:${AWS::Partition}:s3:::${DataBucket}/*
|
||||
Condition:
|
||||
Bool:
|
||||
aws:SecureTransport: false
|
||||
- Sid: DenyNonIntelligentTieringStorageClass
|
||||
Effect: Deny
|
||||
Principal: '*'
|
||||
Action: s3:PutObject
|
||||
Resource: !Sub arn:aws:s3:::${DataBucket}/*
|
||||
Condition:
|
||||
StringNotEquals:
|
||||
s3:x-amz-storage-class: INTELLIGENT_TIERING
|
||||
|
||||
ApiFunctionRole:
|
||||
Type: AWS::IAM::Role
|
||||
Properties:
|
||||
AssumeRolePolicyDocument:
|
||||
Version: '2012-10-17'
|
||||
Statement:
|
||||
- Action: sts:AssumeRole
|
||||
Effect: Allow
|
||||
Principal:
|
||||
Service: lambda.amazonaws.com
|
||||
ManagedPolicyArns:
|
||||
- arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
|
||||
Policies:
|
||||
- PolicyName: AccessAWSServices
|
||||
PolicyDocument:
|
||||
Version: '2012-10-17'
|
||||
Statement:
|
||||
- Effect: Allow
|
||||
Action:
|
||||
- s3:AbortMultipartUpload
|
||||
- s3:GetObject
|
||||
- s3:ListBucket
|
||||
- s3:PutObject
|
||||
- s3:DeleteObject
|
||||
Resource:
|
||||
- !Sub arn:${AWS::Partition}:s3:::${DataBucket}
|
||||
- !Sub arn:${AWS::Partition}:s3:::${DataBucket}/*
|
||||
- Effect: Allow
|
||||
Action: dsql:DbConnectAdmin
|
||||
Resource: !Sub arn:${AWS::Partition}:dsql:${AWS::Region}:${AWS::AccountId}:cluster/${DSQLClusterId}
|
||||
- !If
|
||||
- IsEmailEnabled
|
||||
- Effect: Allow
|
||||
Action:
|
||||
- ses:SendEmail
|
||||
- ses:SendRawEmail
|
||||
Resource: '*'
|
||||
Condition:
|
||||
StringEquals:
|
||||
ses:FromAddress: !Ref SMTPFrom
|
||||
ses:FromDisplayName: !Ref SMTPFromName
|
||||
- !Ref AWS::NoValue
|
||||
|
||||
ApiFunction:
|
||||
Type: AWS::Lambda::Function
|
||||
Properties:
|
||||
Architectures:
|
||||
- arm64
|
||||
Code: ./vaultwarden-lambda.zip
|
||||
Environment:
|
||||
Variables:
|
||||
AWS_LWA_PORT: 8000
|
||||
AWS_LWA_READINESS_CHECK_PATH: /alive
|
||||
AWS_LWA_ASYNC_INIT: true
|
||||
AWS_LWA_ENABLE_COMPRESSION: true
|
||||
AWS_LWA_INVOKE_MODE: RESPONSE_STREAM
|
||||
DATA_FOLDER: !Sub s3://${DataBucket}
|
||||
TMP_FOLDER: /tmp
|
||||
DATABASE_URL: !Sub dsql://${DSQLClusterId}.dsql.${AWS::Region}.on.aws
|
||||
ENABLE_WEBSOCKET: false
|
||||
DOMAIN: !If
|
||||
- IsDomainAndCertificateSet
|
||||
- !Ref Domain
|
||||
- !Ref AWS::NoValue
|
||||
SIGNUPS_ALLOWED: !Ref SignupsAllowed
|
||||
IP_HEADER: X-Forwarded-For
|
||||
ICON_SERVICE: !Ref IconService
|
||||
ICON_REDIRECT_CODE: 301
|
||||
ADMIN_TOKEN: !If
|
||||
- IsAdminTokenEmpty
|
||||
- !Ref AWS::NoValue
|
||||
- !Ref AdminToken
|
||||
SMTP_FROM: !If
|
||||
- IsEmailEnabled
|
||||
- !Ref SMTPFrom
|
||||
- !Ref AWS::NoValue
|
||||
SMTP_FROM_NAME: !Ref SMTPFromName
|
||||
USE_AWS_SES: !If
|
||||
- IsEmailEnabled
|
||||
- 'true'
|
||||
- 'false'
|
||||
FunctionName: !Sub ${AWS::StackName}-api
|
||||
Handler: bootstrap
|
||||
Layers:
|
||||
- !Sub arn:aws:lambda:${AWS::Region}:753240598075:layer:LambdaAdapterLayerArm64:24
|
||||
MemorySize: 3008 # Maximum value allowed for new accounts, higher value reduces cold start times, should still fit under free tier usage for personal use
|
||||
Role: !GetAtt ApiFunctionRole.Arn
|
||||
Runtime: provided.al2023
|
||||
Timeout: 300
|
||||
|
||||
ApiFunctionLogs:
|
||||
Type: AWS::Logs::LogGroup
|
||||
DeletionPolicy: RetainExceptOnCreate
|
||||
Properties:
|
||||
LogGroupName: !Sub /aws/lambda/${ApiFunction}
|
||||
RetentionInDays: !If
|
||||
- IsApiLogRetentionNeverExpire
|
||||
- !Ref AWS::NoValue
|
||||
- !Ref APILogRetention
|
||||
|
||||
ApiFunctionUrl:
|
||||
Type: AWS::Lambda::Url
|
||||
Properties:
|
||||
TargetFunctionArn: !Ref ApiFunction
|
||||
AuthType: NONE
|
||||
InvokeMode: RESPONSE_STREAM
|
||||
|
||||
ApiFunctionUrlPublicPermissions:
|
||||
Type: AWS::Lambda::Permission
|
||||
Properties:
|
||||
Action: lambda:InvokeFunctionUrl
|
||||
FunctionName: !Ref ApiFunction
|
||||
Principal: '*'
|
||||
FunctionUrlAuthType: NONE
|
||||
|
||||
WebVaultAssetsBucket:
|
||||
Type: AWS::S3::Bucket
|
||||
Properties:
|
||||
BucketName: !Sub ${AWS::StackName}-${AWS::AccountId}-${AWS::Region}-web-vault
|
||||
PublicAccessBlockConfiguration:
|
||||
BlockPublicAcls: true
|
||||
BlockPublicPolicy: true
|
||||
IgnorePublicAcls: true
|
||||
RestrictPublicBuckets: true
|
||||
|
||||
WebVaultAssetsBucketPolicies:
|
||||
Type: AWS::S3::BucketPolicy
|
||||
Properties:
|
||||
Bucket: !Ref WebVaultAssetsBucket
|
||||
PolicyDocument:
|
||||
Version: '2012-10-17'
|
||||
Statement:
|
||||
- Sid: DenyUnencryptedTransit
|
||||
Effect: Deny
|
||||
Principal: '*'
|
||||
Action: s3:*
|
||||
Resource:
|
||||
- !Sub arn:${AWS::Partition}:s3:::${WebVaultAssetsBucket}
|
||||
- !Sub arn:${AWS::Partition}:s3:::${WebVaultAssetsBucket}/*
|
||||
Condition:
|
||||
Bool:
|
||||
aws:SecureTransport: false
|
||||
- Sid: AllowCloudFrontOriginAccessControlRead
|
||||
Effect: Allow
|
||||
Principal:
|
||||
Service: !Sub cloudfront.${AWS::URLSuffix}
|
||||
Action: s3:GetObject
|
||||
Resource: !Sub ${WebVaultAssetsBucket.Arn}/*
|
||||
Condition:
|
||||
StringEquals:
|
||||
AWS:SourceArn: !Sub arn:${AWS::Partition}:cloudfront::${AWS::AccountId}:distribution/${CDN.Id}
|
||||
|
||||
WebVaultAssetsBucketOriginAccessControl:
|
||||
Type: AWS::CloudFront::OriginAccessControl
|
||||
Properties:
|
||||
OriginAccessControlConfig:
|
||||
Name: !Sub ${AWS::StackName}-${AWS::Region}-web-vault-access-control
|
||||
OriginAccessControlOriginType: s3
|
||||
SigningBehavior: always
|
||||
SigningProtocol: sigv4
|
||||
|
||||
# The following mirrors the header values in util.rs
|
||||
ResponseHeaderPolicy:
|
||||
Type: AWS::CloudFront::ResponseHeadersPolicy
|
||||
Properties:
|
||||
ResponseHeadersPolicyConfig:
|
||||
Name: !Sub ${AWS::StackName}-${AWS::Region}
|
||||
CustomHeadersConfig:
|
||||
Items:
|
||||
- Header: Cache-Control
|
||||
Override: false
|
||||
Value: no-cache, no-store, max-age=0
|
||||
- Header: X-Robots-Tag
|
||||
Override: true
|
||||
Value: noindex, nofollow
|
||||
- Header: Permissions-Policy
|
||||
Override: true
|
||||
Value: accelerometer=(), ambient-light-sensor=(), autoplay=(), battery=(), camera=(), display-capture=(), document-domain=(), encrypted-media=(), execution-while-not-rendered=(), execution-while-out-of-viewport=(), fullscreen=(), geolocation=(), gyroscope=(), keyboard-map=(), magnetometer=(), microphone=(), midi=(), payment=(), picture-in-picture=(), screen-wake-lock=(), sync-xhr=(), usb=(), web-share=(), xr-spatial-tracking=()
|
||||
SecurityHeadersConfig:
|
||||
ContentSecurityPolicy:
|
||||
ContentSecurityPolicy: !Sub
|
||||
- >-
|
||||
default-src 'none';
|
||||
font-src 'self';
|
||||
manifest-src 'self';
|
||||
base-uri 'self';
|
||||
form-action 'self';
|
||||
media-src 'self';
|
||||
object-src 'self' blob:;
|
||||
script-src 'self' 'wasm-unsafe-eval';
|
||||
style-src 'self' 'unsafe-inline';
|
||||
child-src 'self' https://*.duosecurity.com https://*.duofederal.com;
|
||||
frame-src 'self' https://*.duosecurity.com https://*.duofederal.com;
|
||||
frame-ancestors 'self'
|
||||
chrome-extension://nngceckbapebfimnlniiiahkandclblb
|
||||
chrome-extension://jbkfoedolllekgbhcbcoahefnbanhhlh
|
||||
moz-extension://*;
|
||||
img-src 'self' data:
|
||||
https://haveibeenpwned.com
|
||||
${IconServiceCSP};
|
||||
connect-src 'self'
|
||||
https://api.pwnedpasswords.com
|
||||
https://api.2fa.directory
|
||||
https://app.simplelogin.io/api/
|
||||
https://app.addy.io/api/
|
||||
https://api.fastmail.com/
|
||||
https://api.forwardemail.net
|
||||
https://${DataBucket.RegionalDomainName};
|
||||
- IconServiceCSP: !If
|
||||
- IconSourceIsPredefined
|
||||
- !FindInMap [IconSource, !Ref IconService, CSP]
|
||||
- !Select
|
||||
- 0
|
||||
- !Split ['{', !Ref IconService]
|
||||
Override: true
|
||||
ContentTypeOptions:
|
||||
Override: true
|
||||
FrameOptions:
|
||||
FrameOption: SAMEORIGIN
|
||||
Override: true
|
||||
ReferrerPolicy:
|
||||
Override: true
|
||||
ReferrerPolicy: same-origin
|
||||
StrictTransportSecurity:
|
||||
AccessControlMaxAgeSec: 63072000
|
||||
IncludeSubdomains: true
|
||||
Override: true
|
||||
Preload: true
|
||||
XSSProtection:
|
||||
Override: true
|
||||
Protection: false
|
||||
|
||||
# The following mirrors the header values in util.rs
|
||||
ConnectorHtmlResponseHeaderPolicy:
|
||||
Type: AWS::CloudFront::ResponseHeadersPolicy
|
||||
Properties:
|
||||
ResponseHeadersPolicyConfig:
|
||||
Name: !Sub ${AWS::StackName}-${AWS::Region}-connector-html
|
||||
CustomHeadersConfig:
|
||||
Items:
|
||||
- Header: Cache-Control
|
||||
Override: true
|
||||
Value: no-cache, no-store, max-age=0
|
||||
- Header: X-Robots-Tag
|
||||
Override: true
|
||||
Value: noindex, nofollow
|
||||
- Header: Permissions-Policy
|
||||
Override: true
|
||||
Value: accelerometer=(), ambient-light-sensor=(), autoplay=(), battery=(), camera=(), display-capture=(), document-domain=(), encrypted-media=(), execution-while-not-rendered=(), execution-while-out-of-viewport=(), fullscreen=(), geolocation=(), gyroscope=(), keyboard-map=(), magnetometer=(), microphone=(), midi=(), payment=(), picture-in-picture=(), screen-wake-lock=(), sync-xhr=(), usb=(), web-share=(), xr-spatial-tracking=()
|
||||
SecurityHeadersConfig:
|
||||
ContentTypeOptions:
|
||||
Override: true
|
||||
ReferrerPolicy:
|
||||
Override: true
|
||||
ReferrerPolicy: same-origin
|
||||
StrictTransportSecurity:
|
||||
AccessControlMaxAgeSec: 63072000
|
||||
IncludeSubdomains: true
|
||||
Override: true
|
||||
Preload: true
|
||||
XSSProtection:
|
||||
Override: true
|
||||
Protection: false
|
||||
|
||||
CDN:
|
||||
Type: AWS::CloudFront::Distribution
|
||||
Properties:
|
||||
DistributionConfig:
|
||||
Aliases: !If
|
||||
- IsDomainAndCertificateSet
|
||||
- - !Select
|
||||
- 2
|
||||
- !Split
|
||||
- /
|
||||
- !Ref Domain
|
||||
- !Ref AWS::NoValue
|
||||
CacheBehaviors:
|
||||
- AllowedMethods:
|
||||
- DELETE
|
||||
- HEAD
|
||||
- GET
|
||||
- OPTIONS
|
||||
- PATCH
|
||||
- POST
|
||||
- PUT
|
||||
CachePolicyId: 4135ea2d-6df8-44a3-9df3-4b5a84be39ad # CachingDisabled
|
||||
Compress: true
|
||||
OriginRequestPolicyId: b689b0a8-53d0-40ab-baf2-68738e2966ac # AllViewerExceptHostHeader
|
||||
PathPattern: /api/*
|
||||
ResponseHeadersPolicyId: !Ref ResponseHeaderPolicy
|
||||
TargetOriginId: Api
|
||||
ViewerProtocolPolicy: redirect-to-https
|
||||
- AllowedMethods:
|
||||
- DELETE
|
||||
- HEAD
|
||||
- GET
|
||||
- OPTIONS
|
||||
- PATCH
|
||||
- POST
|
||||
- PUT
|
||||
CachePolicyId: 4135ea2d-6df8-44a3-9df3-4b5a84be39ad # CachingDisabled
|
||||
Compress: true
|
||||
OriginRequestPolicyId: b689b0a8-53d0-40ab-baf2-68738e2966ac # AllViewerExceptHostHeader
|
||||
PathPattern: /admin
|
||||
ResponseHeadersPolicyId: !Ref ResponseHeaderPolicy
|
||||
TargetOriginId: Api
|
||||
ViewerProtocolPolicy: redirect-to-https
|
||||
- AllowedMethods:
|
||||
- DELETE
|
||||
- HEAD
|
||||
- GET
|
||||
- OPTIONS
|
||||
- PATCH
|
||||
- POST
|
||||
- PUT
|
||||
CachePolicyId: 4135ea2d-6df8-44a3-9df3-4b5a84be39ad # CachingDisabled
|
||||
Compress: true
|
||||
OriginRequestPolicyId: b689b0a8-53d0-40ab-baf2-68738e2966ac # AllViewerExceptHostHeader
|
||||
PathPattern: /admin/*
|
||||
ResponseHeadersPolicyId: !Ref ResponseHeaderPolicy
|
||||
TargetOriginId: Api
|
||||
ViewerProtocolPolicy: redirect-to-https
|
||||
- AllowedMethods:
|
||||
- DELETE
|
||||
- HEAD
|
||||
- GET
|
||||
- OPTIONS
|
||||
- PATCH
|
||||
- POST
|
||||
- PUT
|
||||
CachePolicyId: 4135ea2d-6df8-44a3-9df3-4b5a84be39ad # CachingDisabled
|
||||
Compress: true
|
||||
OriginRequestPolicyId: b689b0a8-53d0-40ab-baf2-68738e2966ac # AllViewerExceptHostHeader
|
||||
PathPattern: /events/*
|
||||
ResponseHeadersPolicyId: !Ref ResponseHeaderPolicy
|
||||
TargetOriginId: Api
|
||||
ViewerProtocolPolicy: redirect-to-https
|
||||
- AllowedMethods:
|
||||
- DELETE
|
||||
- HEAD
|
||||
- GET
|
||||
- OPTIONS
|
||||
- PATCH
|
||||
- POST
|
||||
- PUT
|
||||
CachePolicyId: 4135ea2d-6df8-44a3-9df3-4b5a84be39ad # CachingDisabled
|
||||
Compress: true
|
||||
OriginRequestPolicyId: b689b0a8-53d0-40ab-baf2-68738e2966ac # AllViewerExceptHostHeader
|
||||
PathPattern: /identity/*
|
||||
ResponseHeadersPolicyId: !Ref ResponseHeaderPolicy
|
||||
TargetOriginId: Api
|
||||
ViewerProtocolPolicy: redirect-to-https
|
||||
- CachePolicyId: 4135ea2d-6df8-44a3-9df3-4b5a84be39ad # CachingDisabled
|
||||
Compress: true
|
||||
OriginRequestPolicyId: b689b0a8-53d0-40ab-baf2-68738e2966ac # AllViewerExceptHostHeader
|
||||
PathPattern: /css/*
|
||||
ResponseHeadersPolicyId: !Ref ResponseHeaderPolicy
|
||||
TargetOriginId: Api
|
||||
ViewerProtocolPolicy: redirect-to-https
|
||||
- CachePolicyId: 4135ea2d-6df8-44a3-9df3-4b5a84be39ad # CachingDisabled
|
||||
Compress: true
|
||||
OriginRequestPolicyId: b689b0a8-53d0-40ab-baf2-68738e2966ac # AllViewerExceptHostHeader
|
||||
PathPattern: /vw_static/*
|
||||
ResponseHeadersPolicyId: !Ref ResponseHeaderPolicy
|
||||
TargetOriginId: Api
|
||||
ViewerProtocolPolicy: redirect-to-https
|
||||
- CachePolicyId: 658327ea-f89d-4fab-a63d-7e88639e58f6 # CachingOptimized
|
||||
Compress: true
|
||||
OriginRequestPolicyId: b689b0a8-53d0-40ab-baf2-68738e2966ac # AllViewerExceptHostHeader
|
||||
PathPattern: /icons/*
|
||||
ResponseHeadersPolicyId: !Ref ResponseHeaderPolicy
|
||||
TargetOriginId: Api
|
||||
ViewerProtocolPolicy: redirect-to-https
|
||||
- CachePolicyId: 4135ea2d-6df8-44a3-9df3-4b5a84be39ad # CachingDisabled
|
||||
Compress: true
|
||||
PathPattern: '*.html'
|
||||
ResponseHeadersPolicyId: !Ref ResponseHeaderPolicy
|
||||
TargetOriginId: WebVaultAssetsBucket
|
||||
ViewerProtocolPolicy: redirect-to-https
|
||||
- CachePolicyId: 4135ea2d-6df8-44a3-9df3-4b5a84be39ad # CachingDisabled
|
||||
Compress: true
|
||||
PathPattern: '*connector.html'
|
||||
ResponseHeadersPolicyId: !Ref ConnectorHtmlResponseHeaderPolicy
|
||||
TargetOriginId: WebVaultAssetsBucket
|
||||
ViewerProtocolPolicy: redirect-to-https
|
||||
Comment: Vaultwarden CDN
|
||||
CustomErrorResponses:
|
||||
- ErrorCode: 403
|
||||
ResponseCode: 200
|
||||
ResponsePagePath: /404.html
|
||||
DefaultCacheBehavior:
|
||||
CachePolicyId: 658327ea-f89d-4fab-a63d-7e88639e58f6 # CachingOptimized
|
||||
Compress: true
|
||||
ResponseHeadersPolicyId: !Ref ResponseHeaderPolicy
|
||||
TargetOriginId: WebVaultAssetsBucket
|
||||
ViewerProtocolPolicy: redirect-to-https
|
||||
DefaultRootObject: index.html
|
||||
Enabled: true
|
||||
HttpVersion: http2and3
|
||||
IPV6Enabled: true
|
||||
Origins:
|
||||
- Id: WebVaultAssetsBucket
|
||||
DomainName: !GetAtt WebVaultAssetsBucket.RegionalDomainName
|
||||
OriginAccessControlId: !GetAtt WebVaultAssetsBucketOriginAccessControl.Id
|
||||
S3OriginConfig:
|
||||
OriginAccessIdentity: ''
|
||||
- Id: Api
|
||||
CustomOriginConfig:
|
||||
OriginProtocolPolicy: https-only
|
||||
OriginSSLProtocols:
|
||||
- TLSv1.2
|
||||
DomainName: !Select
|
||||
- 2
|
||||
- !Split
|
||||
- /
|
||||
- !GetAtt ApiFunctionUrl.FunctionUrl
|
||||
PriceClass: PriceClass_All
|
||||
ViewerCertificate: !If
|
||||
- IsDomainAndCertificateSet
|
||||
- AcmCertificateArn: !Ref ACMCertificateArn
|
||||
MinimumProtocolVersion: TLSv1.2_2021
|
||||
SslSupportMethod: sni-only
|
||||
- !Ref AWS::NoValue
|
||||
|
||||
Outputs:
|
||||
WebVaultAssetsBucket:
|
||||
Value: !Ref WebVaultAssetsBucket
|
||||
CDNDomain:
|
||||
Value: !GetAtt CDN.DomainName
|
||||
11
build.rs
11
build.rs
|
|
@ -9,20 +9,29 @@ fn main() {
|
|||
println!("cargo:rustc-cfg=mysql");
|
||||
#[cfg(feature = "postgresql")]
|
||||
println!("cargo:rustc-cfg=postgresql");
|
||||
#[cfg(not(any(feature = "sqlite_system", feature = "mysql", feature = "postgresql")))]
|
||||
#[cfg(feature = "dsql")]
|
||||
println!("cargo:rustc-cfg=dsql");
|
||||
#[cfg(not(any(feature = "sqlite_system", feature = "mysql", feature = "postgresql", feature = "dsql")))]
|
||||
compile_error!(
|
||||
"You need to enable one DB backend. To build with previous defaults do: cargo build --features sqlite"
|
||||
);
|
||||
|
||||
#[cfg(feature = "s3")]
|
||||
println!("cargo:rustc-cfg=s3");
|
||||
#[cfg(feature = "ses")]
|
||||
println!("cargo:rustc-cfg=ses");
|
||||
#[cfg(feature = "aws")]
|
||||
println!("cargo:rustc-cfg=aws");
|
||||
|
||||
// Use check-cfg to let cargo know which cfg's we define,
|
||||
// and avoid warnings when they are used in the code.
|
||||
println!("cargo::rustc-check-cfg=cfg(sqlite)");
|
||||
println!("cargo::rustc-check-cfg=cfg(mysql)");
|
||||
println!("cargo::rustc-check-cfg=cfg(postgresql)");
|
||||
println!("cargo::rustc-check-cfg=cfg(dsql)");
|
||||
println!("cargo::rustc-check-cfg=cfg(s3)");
|
||||
println!("cargo::rustc-check-cfg=cfg(ses)");
|
||||
println!("cargo::rustc-check-cfg=cfg(aws)");
|
||||
|
||||
// Rerun when these paths are changed.
|
||||
// Someone could have checked-out a tag or specific commit, but no other files changed.
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
run_in_transaction = false
|
||||
281
migrations/dsql/2024-12-30-100000_create_tables/up.sql
Normal file
281
migrations/dsql/2024-12-30-100000_create_tables/up.sql
Normal file
|
|
@ -0,0 +1,281 @@
|
|||
CREATE TABLE attachments (
|
||||
id text NOT NULL PRIMARY KEY,
|
||||
cipher_uuid character varying(40) NOT NULL,
|
||||
file_name text NOT NULL,
|
||||
file_size bigint NOT NULL,
|
||||
akey text
|
||||
);
|
||||
|
||||
CREATE TABLE auth_requests (
|
||||
uuid character(36) NOT NULL PRIMARY KEY,
|
||||
user_uuid character(36) NOT NULL,
|
||||
organization_uuid character(36),
|
||||
request_device_identifier character(36) NOT NULL,
|
||||
device_type integer NOT NULL,
|
||||
request_ip text NOT NULL,
|
||||
response_device_id character(36),
|
||||
access_code text NOT NULL,
|
||||
public_key text NOT NULL,
|
||||
enc_key text,
|
||||
master_password_hash text,
|
||||
approved boolean,
|
||||
creation_date timestamp without time zone NOT NULL,
|
||||
response_date timestamp without time zone,
|
||||
authentication_date timestamp without time zone
|
||||
);
|
||||
|
||||
CREATE TABLE ciphers (
|
||||
uuid character varying(40) NOT NULL PRIMARY KEY,
|
||||
created_at timestamp without time zone NOT NULL,
|
||||
updated_at timestamp without time zone NOT NULL,
|
||||
user_uuid character varying(40),
|
||||
organization_uuid character varying(40),
|
||||
atype integer NOT NULL,
|
||||
name text NOT NULL,
|
||||
notes text,
|
||||
fields text,
|
||||
data text NOT NULL,
|
||||
password_history text,
|
||||
deleted_at timestamp without time zone,
|
||||
reprompt integer,
|
||||
key text
|
||||
);
|
||||
|
||||
CREATE TABLE ciphers_collections (
|
||||
cipher_uuid character varying(40) NOT NULL,
|
||||
collection_uuid character varying(40) NOT NULL,
|
||||
PRIMARY KEY (cipher_uuid, collection_uuid)
|
||||
);
|
||||
|
||||
CREATE TABLE collections (
|
||||
uuid character varying(40) NOT NULL PRIMARY KEY,
|
||||
org_uuid character varying(40) NOT NULL,
|
||||
name text NOT NULL,
|
||||
external_id text
|
||||
);
|
||||
|
||||
CREATE TABLE collections_groups (
|
||||
collections_uuid character varying(40) NOT NULL,
|
||||
groups_uuid character(36) NOT NULL,
|
||||
read_only boolean NOT NULL,
|
||||
hide_passwords boolean NOT NULL,
|
||||
PRIMARY KEY (collections_uuid, groups_uuid)
|
||||
);
|
||||
|
||||
CREATE TABLE devices (
|
||||
uuid character varying(40) NOT NULL,
|
||||
created_at timestamp without time zone NOT NULL,
|
||||
updated_at timestamp without time zone NOT NULL,
|
||||
user_uuid character varying(40) NOT NULL,
|
||||
name text NOT NULL,
|
||||
atype integer NOT NULL,
|
||||
push_token text,
|
||||
refresh_token text NOT NULL,
|
||||
twofactor_remember text,
|
||||
push_uuid text,
|
||||
PRIMARY KEY (uuid, user_uuid)
|
||||
);
|
||||
|
||||
CREATE TABLE emergency_access (
|
||||
uuid character(36) NOT NULL PRIMARY KEY,
|
||||
grantor_uuid character(36),
|
||||
grantee_uuid character(36),
|
||||
email character varying(255),
|
||||
key_encrypted text,
|
||||
atype integer NOT NULL,
|
||||
status integer NOT NULL,
|
||||
wait_time_days integer NOT NULL,
|
||||
recovery_initiated_at timestamp without time zone,
|
||||
last_notification_at timestamp without time zone,
|
||||
updated_at timestamp without time zone NOT NULL,
|
||||
created_at timestamp without time zone NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE event (
|
||||
uuid character(36) NOT NULL PRIMARY KEY,
|
||||
event_type integer NOT NULL,
|
||||
user_uuid character(36),
|
||||
org_uuid character(36),
|
||||
cipher_uuid character(36),
|
||||
collection_uuid character(36),
|
||||
group_uuid character(36),
|
||||
org_user_uuid character(36),
|
||||
act_user_uuid character(36),
|
||||
device_type integer,
|
||||
ip_address text,
|
||||
event_date timestamp without time zone NOT NULL,
|
||||
policy_uuid character(36),
|
||||
provider_uuid character(36),
|
||||
provider_user_uuid character(36),
|
||||
provider_org_uuid character(36)
|
||||
);
|
||||
|
||||
CREATE TABLE favorites (
|
||||
user_uuid character varying(40) NOT NULL,
|
||||
cipher_uuid character varying(40) NOT NULL,
|
||||
PRIMARY KEY (user_uuid, cipher_uuid)
|
||||
);
|
||||
|
||||
CREATE TABLE folders (
|
||||
uuid character varying(40) NOT NULL PRIMARY KEY,
|
||||
created_at timestamp without time zone NOT NULL,
|
||||
updated_at timestamp without time zone NOT NULL,
|
||||
user_uuid character varying(40) NOT NULL,
|
||||
name text NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE folders_ciphers (
|
||||
cipher_uuid character varying(40) NOT NULL,
|
||||
folder_uuid character varying(40) NOT NULL,
|
||||
PRIMARY KEY (cipher_uuid, folder_uuid)
|
||||
);
|
||||
|
||||
CREATE TABLE groups (
|
||||
uuid character(36) NOT NULL PRIMARY KEY,
|
||||
organizations_uuid character varying(40) NOT NULL,
|
||||
name character varying(100) NOT NULL,
|
||||
access_all boolean NOT NULL,
|
||||
external_id character varying(300),
|
||||
creation_date timestamp without time zone NOT NULL,
|
||||
revision_date timestamp without time zone NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE groups_users (
|
||||
groups_uuid character(36) NOT NULL,
|
||||
users_organizations_uuid character varying(36) NOT NULL,
|
||||
PRIMARY KEY (groups_uuid, users_organizations_uuid)
|
||||
);
|
||||
|
||||
CREATE TABLE invitations (
|
||||
email text NOT NULL PRIMARY KEY
|
||||
);
|
||||
|
||||
CREATE TABLE org_policies (
|
||||
uuid character(36) NOT NULL PRIMARY KEY,
|
||||
org_uuid character(36) NOT NULL,
|
||||
atype integer NOT NULL,
|
||||
enabled boolean NOT NULL,
|
||||
data text NOT NULL,
|
||||
UNIQUE (org_uuid, atype)
|
||||
);
|
||||
|
||||
CREATE TABLE organization_api_key (
|
||||
uuid character(36) NOT NULL,
|
||||
org_uuid character(36) NOT NULL,
|
||||
atype integer NOT NULL,
|
||||
api_key character varying(255),
|
||||
revision_date timestamp without time zone NOT NULL,
|
||||
PRIMARY KEY (uuid, org_uuid)
|
||||
);
|
||||
|
||||
CREATE TABLE organizations (
|
||||
uuid character varying(40) NOT NULL PRIMARY KEY,
|
||||
name text NOT NULL,
|
||||
billing_email text NOT NULL,
|
||||
private_key text,
|
||||
public_key text
|
||||
);
|
||||
|
||||
CREATE TABLE sends (
|
||||
uuid character(36) NOT NULL PRIMARY KEY,
|
||||
user_uuid character(36),
|
||||
organization_uuid character(36),
|
||||
name text NOT NULL,
|
||||
notes text,
|
||||
atype integer NOT NULL,
|
||||
data text NOT NULL,
|
||||
akey text NOT NULL,
|
||||
password_hash bytea,
|
||||
password_salt bytea,
|
||||
password_iter integer,
|
||||
max_access_count integer,
|
||||
access_count integer NOT NULL,
|
||||
creation_date timestamp without time zone NOT NULL,
|
||||
revision_date timestamp without time zone NOT NULL,
|
||||
expiration_date timestamp without time zone,
|
||||
deletion_date timestamp without time zone NOT NULL,
|
||||
disabled boolean NOT NULL,
|
||||
hide_email boolean
|
||||
);
|
||||
|
||||
CREATE TABLE twofactor (
|
||||
uuid character varying(40) NOT NULL PRIMARY KEY,
|
||||
user_uuid character varying(40) NOT NULL,
|
||||
atype integer NOT NULL,
|
||||
enabled boolean NOT NULL,
|
||||
data text NOT NULL,
|
||||
last_used bigint DEFAULT 0 NOT NULL,
|
||||
UNIQUE (user_uuid, atype)
|
||||
);
|
||||
|
||||
CREATE TABLE twofactor_duo_ctx (
|
||||
state character varying(64) NOT NULL PRIMARY KEY,
|
||||
user_email character varying(255) NOT NULL,
|
||||
nonce character varying(64) NOT NULL,
|
||||
exp bigint NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE twofactor_incomplete (
|
||||
user_uuid character varying(40) NOT NULL,
|
||||
device_uuid character varying(40) NOT NULL,
|
||||
device_name text NOT NULL,
|
||||
login_time timestamp without time zone NOT NULL,
|
||||
ip_address text NOT NULL,
|
||||
device_type integer DEFAULT 14 NOT NULL,
|
||||
PRIMARY KEY (user_uuid, device_uuid)
|
||||
);
|
||||
|
||||
CREATE TABLE users (
|
||||
uuid character varying(40) NOT NULL PRIMARY KEY,
|
||||
created_at timestamp without time zone NOT NULL,
|
||||
updated_at timestamp without time zone NOT NULL,
|
||||
email text NOT NULL UNIQUE,
|
||||
name text NOT NULL,
|
||||
password_hash bytea NOT NULL,
|
||||
salt bytea NOT NULL,
|
||||
password_iterations integer NOT NULL,
|
||||
password_hint text,
|
||||
akey text NOT NULL,
|
||||
private_key text,
|
||||
public_key text,
|
||||
totp_secret text,
|
||||
totp_recover text,
|
||||
security_stamp text NOT NULL,
|
||||
equivalent_domains text NOT NULL,
|
||||
excluded_globals text NOT NULL,
|
||||
client_kdf_type integer DEFAULT 0 NOT NULL,
|
||||
client_kdf_iter integer DEFAULT 100000 NOT NULL,
|
||||
verified_at timestamp without time zone,
|
||||
last_verifying_at timestamp without time zone,
|
||||
login_verify_count integer DEFAULT 0 NOT NULL,
|
||||
email_new character varying(255) DEFAULT NULL::character varying,
|
||||
email_new_token character varying(16) DEFAULT NULL::character varying,
|
||||
enabled boolean DEFAULT true NOT NULL,
|
||||
stamp_exception text,
|
||||
api_key text,
|
||||
avatar_color text,
|
||||
client_kdf_memory integer,
|
||||
client_kdf_parallelism integer,
|
||||
external_id text
|
||||
);
|
||||
|
||||
CREATE TABLE users_collections (
|
||||
user_uuid character varying(40) NOT NULL,
|
||||
collection_uuid character varying(40) NOT NULL,
|
||||
read_only boolean DEFAULT false NOT NULL,
|
||||
hide_passwords boolean DEFAULT false NOT NULL,
|
||||
PRIMARY KEY (user_uuid, collection_uuid)
|
||||
);
|
||||
|
||||
CREATE TABLE users_organizations (
|
||||
uuid character varying(40) NOT NULL PRIMARY KEY,
|
||||
user_uuid character varying(40) NOT NULL,
|
||||
org_uuid character varying(40) NOT NULL,
|
||||
access_all boolean NOT NULL,
|
||||
akey text NOT NULL,
|
||||
status integer NOT NULL,
|
||||
atype integer NOT NULL,
|
||||
reset_password_key text,
|
||||
external_id text,
|
||||
UNIQUE (user_uuid, org_uuid)
|
||||
);
|
||||
0
migrations/dsql/2025-01-09-172300_add_manage/down.sql
Normal file
0
migrations/dsql/2025-01-09-172300_add_manage/down.sql
Normal file
|
|
@ -0,0 +1 @@
|
|||
run_in_transaction = false
|
||||
8
migrations/dsql/2025-01-09-172300_add_manage/up.sql
Normal file
8
migrations/dsql/2025-01-09-172300_add_manage/up.sql
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
-- DSQL preview can't add columns with constraints, dropping `NOT NULL DEFAULT FALSE` constraint
|
||||
-- It appears Diesel will ensure the column has appropriate values when saving records.
|
||||
|
||||
ALTER TABLE users_collections
|
||||
ADD COLUMN manage BOOLEAN;
|
||||
|
||||
ALTER TABLE collections_groups
|
||||
ADD COLUMN manage BOOLEAN;
|
||||
|
|
@ -0,0 +1 @@
|
|||
|
||||
|
|
@ -0,0 +1 @@
|
|||
run_in_transaction = false
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
ALTER TABLE users_organizations
|
||||
ADD COLUMN invited_by_email TEXT;
|
||||
1
migrations/dsql/2025-08-20-120100_add_sso_users/down.sql
Normal file
1
migrations/dsql/2025-08-20-120100_add_sso_users/down.sql
Normal file
|
|
@ -0,0 +1 @@
|
|||
|
||||
|
|
@ -0,0 +1 @@
|
|||
run_in_transaction = false
|
||||
4
migrations/dsql/2025-08-20-120100_add_sso_users/up.sql
Normal file
4
migrations/dsql/2025-08-20-120100_add_sso_users/up.sql
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
CREATE TABLE sso_users (
|
||||
user_uuid character(36) NOT NULL PRIMARY KEY,
|
||||
identifier text NOT NULL UNIQUE
|
||||
);
|
||||
1
migrations/dsql/2025-08-20-120200_add_sso_auth/down.sql
Normal file
1
migrations/dsql/2025-08-20-120200_add_sso_auth/down.sql
Normal file
|
|
@ -0,0 +1 @@
|
|||
|
||||
|
|
@ -0,0 +1 @@
|
|||
run_in_transaction = false
|
||||
10
migrations/dsql/2025-08-20-120200_add_sso_auth/up.sql
Normal file
10
migrations/dsql/2025-08-20-120200_add_sso_auth/up.sql
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
CREATE TABLE sso_auth (
|
||||
state text NOT NULL PRIMARY KEY,
|
||||
client_challenge text NOT NULL,
|
||||
nonce text NOT NULL,
|
||||
redirect_uri text NOT NULL,
|
||||
code_response text,
|
||||
auth_response text,
|
||||
created_at timestamp without time zone NOT NULL DEFAULT now(),
|
||||
updated_at timestamp without time zone NOT NULL DEFAULT now()
|
||||
);
|
||||
1
migrations/dsql/2026-03-09-005927_add_archives/down.sql
Normal file
1
migrations/dsql/2026-03-09-005927_add_archives/down.sql
Normal file
|
|
@ -0,0 +1 @@
|
|||
|
||||
|
|
@ -0,0 +1 @@
|
|||
run_in_transaction = false
|
||||
6
migrations/dsql/2026-03-09-005927_add_archives/up.sql
Normal file
6
migrations/dsql/2026-03-09-005927_add_archives/up.sql
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
CREATE TABLE archives (
|
||||
user_uuid character(36) NOT NULL,
|
||||
cipher_uuid character(36) NOT NULL,
|
||||
archived_at timestamp without time zone NOT NULL DEFAULT now(),
|
||||
PRIMARY KEY (user_uuid, cipher_uuid)
|
||||
);
|
||||
|
|
@ -0,0 +1 @@
|
|||
|
||||
|
|
@ -0,0 +1 @@
|
|||
run_in_transaction = false
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
ALTER TABLE sso_auth
|
||||
ADD COLUMN binding_hash TEXT;
|
||||
|
|
@ -83,6 +83,8 @@ pub fn catchers() -> Vec<Catcher> {
|
|||
}
|
||||
|
||||
static DB_TYPE: LazyLock<&str> = LazyLock::new(|| match ACTIVE_DB_TYPE.get() {
|
||||
#[cfg(dsql)]
|
||||
Some(DbConnType::Dsql) => "Aurora DSQL",
|
||||
#[cfg(mysql)]
|
||||
Some(DbConnType::Mysql) => "MySQL",
|
||||
#[cfg(postgresql)]
|
||||
|
|
|
|||
|
|
@ -568,7 +568,7 @@ async fn post_access_file(
|
|||
async fn download_url(host: &Host, send_id: &SendId, file_id: &SendFileId) -> Result<String, crate::Error> {
|
||||
let operator = CONFIG.opendal_operator_for_path_type(&PathType::Sends)?;
|
||||
|
||||
if operator.info().scheme() == <&'static str>::from(opendal::Scheme::Fs) {
|
||||
if crate::storage::is_fs_operator(&operator) {
|
||||
let token_claims = crate::auth::generate_send_claims(send_id, file_id);
|
||||
let token = crate::auth::encode_jwt(&token_claims);
|
||||
|
||||
|
|
|
|||
|
|
@ -54,12 +54,8 @@ static PUBLIC_RSA_KEY: OnceLock<DecodingKey> = OnceLock::new();
|
|||
pub async fn initialize_keys() -> Result<(), Error> {
|
||||
use std::io::Error;
|
||||
|
||||
let rsa_key_filename = std::path::PathBuf::from(CONFIG.private_rsa_key())
|
||||
.file_name()
|
||||
.ok_or_else(|| Error::other("Private RSA key path missing filename"))?
|
||||
.to_str()
|
||||
.ok_or_else(|| Error::other("Private RSA key path filename is not valid UTF-8"))?
|
||||
.to_string();
|
||||
let rsa_key_filename = crate::storage::file_name(&CONFIG.private_rsa_key())
|
||||
.ok_or_else(|| Error::other("Private RSA key path missing filename"))?;
|
||||
|
||||
let operator = CONFIG.opendal_operator_for_path_type(&PathType::RsaKey).map_err(Error::other)?;
|
||||
|
||||
|
|
|
|||
39
src/aws.rs
Normal file
39
src/aws.rs
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
#[cfg(dsql)]
|
||||
use std::io::Error;
|
||||
|
||||
use aws_config::{AppName, BehaviorVersion};
|
||||
use tokio::sync::OnceCell;
|
||||
|
||||
use crate::http_client::aws::AwsReqwestConnector;
|
||||
|
||||
fn aws_reqwest_connector() -> AwsReqwestConnector {
|
||||
let reqwest_client = reqwest::Client::builder().build().expect("Failed to build reqwest client");
|
||||
|
||||
AwsReqwestConnector {
|
||||
client: reqwest_client,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn aws_sdk_config() -> &'static aws_config::SdkConfig {
|
||||
static AWS_CONFIG: OnceCell<aws_config::SdkConfig> = OnceCell::const_new();
|
||||
|
||||
AWS_CONFIG
|
||||
.get_or_init(|| async {
|
||||
aws_config::defaults(BehaviorVersion::latest())
|
||||
.app_name(AppName::new("vaultwarden").expect("Failed to build AWS app name"))
|
||||
.http_client(aws_reqwest_connector())
|
||||
.load()
|
||||
.await
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
#[cfg(dsql)]
|
||||
pub(crate) fn aws_sdk_config_blocking() -> std::io::Result<&'static aws_config::SdkConfig> {
|
||||
std::thread::spawn(|| {
|
||||
let rt = tokio::runtime::Builder::new_current_thread().enable_all().build()?;
|
||||
std::io::Result::Ok(rt.block_on(aws_sdk_config()))
|
||||
})
|
||||
.join()
|
||||
.map_err(|e| Error::other(format!("Failed to load AWS SDK config: {e:?}")))?
|
||||
}
|
||||
144
src/config.rs
144
src/config.rs
|
|
@ -14,6 +14,7 @@ use serde::de::{self, Deserialize, Deserializer, MapAccess, Visitor};
|
|||
|
||||
use crate::{
|
||||
error::Error,
|
||||
storage,
|
||||
util::{
|
||||
get_active_web_release, get_env, get_env_bool, is_valid_email, parse_experimental_client_feature_flags,
|
||||
FeatureFlagFilter,
|
||||
|
|
@ -22,18 +23,14 @@ use crate::{
|
|||
|
||||
static CONFIG_FILE: LazyLock<String> = LazyLock::new(|| {
|
||||
let data_folder = get_env("DATA_FOLDER").unwrap_or_else(|| String::from("data"));
|
||||
get_env("CONFIG_FILE").unwrap_or_else(|| format!("{data_folder}/config.json"))
|
||||
get_env("CONFIG_FILE").unwrap_or_else(|| storage::join_path(&data_folder, "config.json"))
|
||||
});
|
||||
|
||||
static CONFIG_FILE_PARENT_DIR: LazyLock<String> = LazyLock::new(|| {
|
||||
let path = std::path::PathBuf::from(&*CONFIG_FILE);
|
||||
path.parent().unwrap_or(std::path::Path::new("data")).to_str().unwrap_or("data").to_string()
|
||||
});
|
||||
static CONFIG_FILE_PARENT_DIR: LazyLock<String> =
|
||||
LazyLock::new(|| storage::parent(&CONFIG_FILE).unwrap_or_else(|| "data".to_string()));
|
||||
|
||||
static CONFIG_FILENAME: LazyLock<String> = LazyLock::new(|| {
|
||||
let path = std::path::PathBuf::from(&*CONFIG_FILE);
|
||||
path.file_name().unwrap_or(std::ffi::OsStr::new("config.json")).to_str().unwrap_or("config.json").to_string()
|
||||
});
|
||||
static CONFIG_FILENAME: LazyLock<String> =
|
||||
LazyLock::new(|| storage::file_name(&CONFIG_FILE).unwrap_or_else(|| "config.json".to_string()));
|
||||
|
||||
pub static SKIP_CONFIG_VALIDATION: AtomicBool = AtomicBool::new(false);
|
||||
|
||||
|
|
@ -263,7 +260,7 @@ macro_rules! make_config {
|
|||
}
|
||||
|
||||
async fn from_file() -> Result<Self, Error> {
|
||||
let operator = opendal_operator_for_path(&CONFIG_FILE_PARENT_DIR)?;
|
||||
let operator = storage::operator_for_path(&CONFIG_FILE_PARENT_DIR)?;
|
||||
let config_bytes = operator.read(&CONFIG_FILENAME).await?;
|
||||
println!("[INFO] Using saved config from `{}` for configuration.\n", *CONFIG_FILE);
|
||||
serde_json::from_slice(&config_bytes.to_vec()).map_err(Into::into)
|
||||
|
|
@ -507,19 +504,19 @@ make_config! {
|
|||
/// Data folder |> Main data folder
|
||||
data_folder: String, false, def, "data".to_string();
|
||||
/// Database URL
|
||||
database_url: String, false, auto, |c| format!("{}/db.sqlite3", c.data_folder);
|
||||
database_url: String, false, auto, |c| storage::join_path(&c.data_folder, "db.sqlite3");
|
||||
/// Icon cache folder
|
||||
icon_cache_folder: String, false, auto, |c| format!("{}/icon_cache", c.data_folder);
|
||||
icon_cache_folder: String, false, auto, |c| storage::join_path(&c.data_folder, "icon_cache");
|
||||
/// Attachments folder
|
||||
attachments_folder: String, false, auto, |c| format!("{}/attachments", c.data_folder);
|
||||
attachments_folder: String, false, auto, |c| storage::join_path(&c.data_folder, "attachments");
|
||||
/// Sends folder
|
||||
sends_folder: String, false, auto, |c| format!("{}/sends", c.data_folder);
|
||||
sends_folder: String, false, auto, |c| storage::join_path(&c.data_folder, "sends");
|
||||
/// Temp folder |> Used for storing temporary file uploads
|
||||
tmp_folder: String, false, auto, |c| format!("{}/tmp", c.data_folder);
|
||||
tmp_folder: String, false, auto, |c| storage::join_path(&c.data_folder, "tmp");
|
||||
/// Templates folder
|
||||
templates_folder: String, false, auto, |c| format!("{}/templates", c.data_folder);
|
||||
templates_folder: String, false, auto, |c| storage::join_path(&c.data_folder, "templates");
|
||||
/// Session JWT key
|
||||
rsa_key_filename: String, false, auto, |c| format!("{}/rsa_key", c.data_folder);
|
||||
rsa_key_filename: String, false, auto, |c| storage::join_path(&c.data_folder, "rsa_key");
|
||||
/// Web vault folder
|
||||
web_vault_folder: String, false, def, "web-vault/".to_string();
|
||||
},
|
||||
|
|
@ -904,12 +901,14 @@ make_config! {
|
|||
smtp_accept_invalid_certs: bool, true, def, false;
|
||||
/// Accept Invalid Hostnames (Know the risks!) |> DANGEROUS: Allow invalid hostnames. This option introduces significant vulnerabilities to man-in-the-middle attacks!
|
||||
smtp_accept_invalid_hostnames: bool, true, def, false;
|
||||
/// Use AWS SES |> Whether to send mail via AWS Simple Email Service (SES)
|
||||
use_aws_ses: bool, true, def, false;
|
||||
},
|
||||
|
||||
/// Email 2FA Settings
|
||||
email_2fa: _enable_email_2fa {
|
||||
/// Enabled |> Disabling will prevent users from setting up new email 2FA and using existing email 2FA configured
|
||||
_enable_email_2fa: bool, true, auto, |c| c._enable_smtp && (c.smtp_host.is_some() || c.use_sendmail);
|
||||
_enable_email_2fa: bool, true, auto, |c| c._enable_smtp && (c.smtp_host.is_some() || c.use_sendmail || c.use_aws_ses);
|
||||
/// Email token size |> Number of digits in an email 2FA token (min: 6, max: 255). Note that the Bitwarden clients are hardcoded to mention 6 digit codes regardless of this setting.
|
||||
email_token_size: u8, true, def, 6;
|
||||
/// Token expiration time |> Maximum time in seconds a token is valid. The time the user has to open email client and copy token.
|
||||
|
|
@ -1133,6 +1132,9 @@ fn validate_config(cfg: &ConfigItems, on_update: bool) -> Result<(), Error> {
|
|||
}
|
||||
}
|
||||
}
|
||||
} else if cfg.use_aws_ses {
|
||||
#[cfg(not(ses))]
|
||||
err!("`USE_AWS_SES` is set, but the `ses` feature is not enabled in this build");
|
||||
} else {
|
||||
if cfg.smtp_host.is_some() == cfg.smtp_from.is_empty() {
|
||||
err!("Both `SMTP_HOST` and `SMTP_FROM` need to be set for email support without `USE_SENDMAIL`")
|
||||
|
|
@ -1143,7 +1145,7 @@ fn validate_config(cfg: &ConfigItems, on_update: bool) -> Result<(), Error> {
|
|||
}
|
||||
}
|
||||
|
||||
if (cfg.smtp_host.is_some() || cfg.use_sendmail) && !is_valid_email(&cfg.smtp_from) {
|
||||
if (cfg.smtp_host.is_some() || cfg.use_sendmail || cfg.use_aws_ses) && !is_valid_email(&cfg.smtp_from) {
|
||||
err!(format!("SMTP_FROM '{}' is not a valid email address", cfg.smtp_from))
|
||||
}
|
||||
|
||||
|
|
@ -1152,7 +1154,7 @@ fn validate_config(cfg: &ConfigItems, on_update: bool) -> Result<(), Error> {
|
|||
}
|
||||
}
|
||||
|
||||
if cfg._enable_email_2fa && !(cfg.smtp_host.is_some() || cfg.use_sendmail) {
|
||||
if cfg._enable_email_2fa && !(cfg.smtp_host.is_some() || cfg.use_sendmail || cfg.use_aws_ses) {
|
||||
err!("To enable email 2FA, a mail transport must be configured")
|
||||
}
|
||||
|
||||
|
|
@ -1366,90 +1368,6 @@ fn smtp_convert_deprecated_ssl_options(smtp_ssl: Option<bool>, smtp_explicit_tls
|
|||
"starttls".to_string()
|
||||
}
|
||||
|
||||
fn opendal_operator_for_path(path: &str) -> Result<opendal::Operator, Error> {
|
||||
// Cache of previously built operators by path
|
||||
static OPERATORS_BY_PATH: LazyLock<dashmap::DashMap<String, opendal::Operator>> =
|
||||
LazyLock::new(dashmap::DashMap::new);
|
||||
|
||||
if let Some(operator) = OPERATORS_BY_PATH.get(path) {
|
||||
return Ok(operator.clone());
|
||||
}
|
||||
|
||||
let operator = if path.starts_with("s3://") {
|
||||
#[cfg(not(s3))]
|
||||
return Err(opendal::Error::new(opendal::ErrorKind::ConfigInvalid, "S3 support is not enabled").into());
|
||||
|
||||
#[cfg(s3)]
|
||||
opendal_s3_operator_for_path(path)?
|
||||
} else {
|
||||
let builder = opendal::services::Fs::default().root(path);
|
||||
opendal::Operator::new(builder)?.finish()
|
||||
};
|
||||
|
||||
OPERATORS_BY_PATH.insert(path.to_string(), operator.clone());
|
||||
|
||||
Ok(operator)
|
||||
}
|
||||
|
||||
#[cfg(s3)]
|
||||
fn opendal_s3_operator_for_path(path: &str) -> Result<opendal::Operator, Error> {
|
||||
use crate::http_client::aws::AwsReqwestConnector;
|
||||
use aws_config::{default_provider::credentials::DefaultCredentialsChain, provider_config::ProviderConfig};
|
||||
|
||||
// This is a custom AWS credential loader that uses the official AWS Rust
|
||||
// SDK config crate to load credentials. This ensures maximum compatibility
|
||||
// with AWS credential configurations. For example, OpenDAL doesn't support
|
||||
// AWS SSO temporary credentials yet.
|
||||
struct OpenDALS3CredentialLoader {}
|
||||
|
||||
#[async_trait]
|
||||
impl reqsign::AwsCredentialLoad for OpenDALS3CredentialLoader {
|
||||
async fn load_credential(&self, _client: reqwest::Client) -> anyhow::Result<Option<reqsign::AwsCredential>> {
|
||||
use aws_credential_types::provider::ProvideCredentials as _;
|
||||
use tokio::sync::OnceCell;
|
||||
|
||||
static DEFAULT_CREDENTIAL_CHAIN: OnceCell<DefaultCredentialsChain> = OnceCell::const_new();
|
||||
|
||||
let chain = DEFAULT_CREDENTIAL_CHAIN
|
||||
.get_or_init(|| {
|
||||
let reqwest_client = reqwest::Client::builder().build().unwrap();
|
||||
let connector = AwsReqwestConnector {
|
||||
client: reqwest_client,
|
||||
};
|
||||
|
||||
let conf = ProviderConfig::default().with_http_client(connector);
|
||||
|
||||
DefaultCredentialsChain::builder().configure(conf).build()
|
||||
})
|
||||
.await;
|
||||
|
||||
let creds = chain.provide_credentials().await?;
|
||||
|
||||
Ok(Some(reqsign::AwsCredential {
|
||||
access_key_id: creds.access_key_id().to_string(),
|
||||
secret_access_key: creds.secret_access_key().to_string(),
|
||||
session_token: creds.session_token().map(|s| s.to_string()),
|
||||
expires_in: creds.expiry().map(|expiration| expiration.into()),
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
const OPEN_DAL_S3_CREDENTIAL_LOADER: OpenDALS3CredentialLoader = OpenDALS3CredentialLoader {};
|
||||
|
||||
let url = Url::parse(path).map_err(|e| format!("Invalid path S3 URL path {path:?}: {e}"))?;
|
||||
|
||||
let bucket = url.host_str().ok_or_else(|| format!("Missing Bucket name in data folder S3 URL {path:?}"))?;
|
||||
|
||||
let builder = opendal::services::S3::default()
|
||||
.customized_credential_load(Box::new(OPEN_DAL_S3_CREDENTIAL_LOADER))
|
||||
.enable_virtual_host_style()
|
||||
.bucket(bucket)
|
||||
.root(url.path())
|
||||
.default_storage_class("INTELLIGENT_TIERING");
|
||||
|
||||
Ok(opendal::Operator::new(builder)?.finish())
|
||||
}
|
||||
|
||||
pub enum PathType {
|
||||
Data,
|
||||
IconCache,
|
||||
|
|
@ -1547,7 +1465,7 @@ impl Config {
|
|||
}
|
||||
|
||||
//Save to file
|
||||
let operator = opendal_operator_for_path(&CONFIG_FILE_PARENT_DIR)?;
|
||||
let operator = storage::operator_for_path(&CONFIG_FILE_PARENT_DIR)?;
|
||||
operator.write(&CONFIG_FILENAME, config_str).await?;
|
||||
|
||||
Ok(())
|
||||
|
|
@ -1612,7 +1530,7 @@ impl Config {
|
|||
}
|
||||
|
||||
pub async fn delete_user_config(&self) -> Result<(), Error> {
|
||||
let operator = opendal_operator_for_path(&CONFIG_FILE_PARENT_DIR)?;
|
||||
let operator = storage::operator_for_path(&CONFIG_FILE_PARENT_DIR)?;
|
||||
operator.delete(&CONFIG_FILENAME).await?;
|
||||
|
||||
// Empty user config
|
||||
|
|
@ -1636,11 +1554,11 @@ impl Config {
|
|||
}
|
||||
|
||||
pub fn private_rsa_key(&self) -> String {
|
||||
format!("{}.pem", self.rsa_key_filename())
|
||||
storage::with_extension(&self.rsa_key_filename(), "pem")
|
||||
}
|
||||
pub fn mail_enabled(&self) -> bool {
|
||||
let inner = &self.inner.read().unwrap().config;
|
||||
inner._enable_smtp && (inner.smtp_host.is_some() || inner.use_sendmail)
|
||||
inner._enable_smtp && (inner.smtp_host.is_some() || inner.use_sendmail || inner.use_aws_ses)
|
||||
}
|
||||
|
||||
pub async fn get_duo_akey(&self) -> String {
|
||||
|
|
@ -1677,15 +1595,11 @@ impl Config {
|
|||
PathType::IconCache => self.icon_cache_folder(),
|
||||
PathType::Attachments => self.attachments_folder(),
|
||||
PathType::Sends => self.sends_folder(),
|
||||
PathType::RsaKey => std::path::Path::new(&self.rsa_key_filename())
|
||||
.parent()
|
||||
.ok_or_else(|| std::io::Error::other("Failed to get directory of RSA key file"))?
|
||||
.to_str()
|
||||
.ok_or_else(|| std::io::Error::other("Failed to convert RSA key file directory to UTF-8 string"))?
|
||||
.to_string(),
|
||||
PathType::RsaKey => storage::parent(&self.private_rsa_key())
|
||||
.ok_or_else(|| std::io::Error::other("Failed to get directory of RSA key file"))?,
|
||||
};
|
||||
|
||||
opendal_operator_for_path(&path)
|
||||
storage::operator_for_path(&path)
|
||||
}
|
||||
|
||||
pub fn render_template<T: serde::ser::Serialize>(&self, name: &str, data: &T) -> Result<String, Error> {
|
||||
|
|
|
|||
110
src/db/dsql.rs
Normal file
110
src/db/dsql.rs
Normal file
|
|
@ -0,0 +1,110 @@
|
|||
use std::{
|
||||
collections::HashMap,
|
||||
sync::{Arc, LazyLock, Mutex},
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use diesel::ConnectionError;
|
||||
use url::Url;
|
||||
|
||||
// Generate a Postgres libpq connection string. The input connection string has
|
||||
// the following format:
|
||||
//
|
||||
// dsql://<dsql-id>.dsql.<aws-region>.on.aws
|
||||
//
|
||||
// The generated connection string has the form:
|
||||
//
|
||||
// postgresql://<dsql-id>.dsql.<aws-region>.on.aws/postgres?sslmode=require&user=admin&password=<auth-token>
|
||||
//
|
||||
// The auth token is generated by the AWS SDK for DSQL and is valid for up to
|
||||
// 15 minutes. Cache each unique DSQL URL for 14 minutes to avoid regenerating
|
||||
// a token for every pooled connection.
|
||||
pub(crate) fn psql_url(url: &str) -> Result<String, ConnectionError> {
|
||||
struct PsqlUrl {
|
||||
timestamp: std::time::Instant,
|
||||
url: String,
|
||||
}
|
||||
|
||||
static PSQL_URLS: LazyLock<Mutex<HashMap<String, Arc<Mutex<Option<PsqlUrl>>>>>> =
|
||||
LazyLock::new(|| Mutex::new(HashMap::new()));
|
||||
|
||||
let mut psql_urls =
|
||||
PSQL_URLS.lock().map_err(|e| ConnectionError::BadConnection(format!("Failed to lock DSQL URLs: {e}")))?;
|
||||
|
||||
let psql_url_lock = if let Some(existing_psql_url_lock) = psql_urls.get(url) {
|
||||
existing_psql_url_lock.clone()
|
||||
} else {
|
||||
let psql_url_lock = Arc::new(Mutex::new(None));
|
||||
psql_urls.insert(url.to_string(), psql_url_lock.clone());
|
||||
psql_url_lock
|
||||
};
|
||||
|
||||
let mut psql_url_lock_guard =
|
||||
psql_url_lock.lock().map_err(|e| ConnectionError::BadConnection(format!("Failed to lock DSQL URL: {e}")))?;
|
||||
|
||||
drop(psql_urls);
|
||||
|
||||
if let Some(ref psql_url) = *psql_url_lock_guard {
|
||||
if psql_url.timestamp.elapsed() < Duration::from_secs(14 * 60) {
|
||||
debug!("Reusing DSQL auth token for connection '{url}'");
|
||||
return Ok(psql_url.url.clone());
|
||||
}
|
||||
|
||||
info!("Refreshing DSQL auth token for connection '{url}'");
|
||||
} else {
|
||||
info!("Generating new DSQL auth token for connection '{url}'");
|
||||
}
|
||||
|
||||
let mut psql_url = Url::parse(url).map_err(|e| ConnectionError::InvalidConnectionUrl(e.to_string()))?;
|
||||
|
||||
let host = psql_url
|
||||
.host_str()
|
||||
.ok_or_else(|| ConnectionError::InvalidConnectionUrl("Missing hostname in DSQL URL".to_string()))?
|
||||
.to_string();
|
||||
|
||||
static DSQL_REGION_FROM_HOST_RE: LazyLock<regex::Regex> = LazyLock::new(|| {
|
||||
regex::Regex::new(r"^[a-z0-9]+\.dsql\.(?P<region>[a-z0-9-]+)\.on\.aws$")
|
||||
.expect("Failed to compile DSQL region regex")
|
||||
});
|
||||
|
||||
let region = DSQL_REGION_FROM_HOST_RE
|
||||
.captures(&host)
|
||||
.and_then(|captures| captures.name("region"))
|
||||
.ok_or_else(|| ConnectionError::InvalidConnectionUrl("Failed to find AWS region in DSQL hostname".to_string()))?
|
||||
.as_str()
|
||||
.to_string();
|
||||
|
||||
let auth_config = aws_sdk_dsql::auth_token::Config::builder()
|
||||
.hostname(host)
|
||||
.region(aws_config::Region::new(region))
|
||||
.build()
|
||||
.map_err(|e| ConnectionError::BadConnection(format!("Failed to build DSQL auth token signer config: {e}")))?;
|
||||
|
||||
let signer = aws_sdk_dsql::auth_token::AuthTokenGenerator::new(auth_config);
|
||||
let sdk_config = crate::aws::aws_sdk_config_blocking()
|
||||
.map_err(|e| ConnectionError::BadConnection(format!("Failed to load AWS SDK config: {e}")))?;
|
||||
let now = std::time::Instant::now();
|
||||
|
||||
let auth_token = std::thread::spawn(move || {
|
||||
let rt = tokio::runtime::Builder::new_current_thread().enable_all().build()?;
|
||||
rt.block_on(signer.db_connect_admin_auth_token(sdk_config))
|
||||
})
|
||||
.join()
|
||||
.map_err(|e| ConnectionError::BadConnection(format!("Failed to generate DSQL auth token: {e:?}")))?
|
||||
.map_err(|e| ConnectionError::BadConnection(format!("Failed to generate DSQL auth token: {e}")))?;
|
||||
|
||||
psql_url.set_scheme("postgresql").expect("Failed to set 'postgresql' as scheme for DSQL connection URL");
|
||||
psql_url.set_path("postgres");
|
||||
psql_url
|
||||
.query_pairs_mut()
|
||||
.append_pair("sslmode", "require")
|
||||
.append_pair("user", "admin")
|
||||
.append_pair("password", auth_token.as_str());
|
||||
|
||||
psql_url_lock_guard.replace(PsqlUrl {
|
||||
timestamp: now,
|
||||
url: psql_url.to_string(),
|
||||
});
|
||||
|
||||
Ok(psql_url.to_string())
|
||||
}
|
||||
|
|
@ -1,3 +1,5 @@
|
|||
#[cfg(dsql)]
|
||||
mod dsql;
|
||||
mod query_logger;
|
||||
|
||||
use std::{
|
||||
|
|
@ -68,6 +70,12 @@ impl DbConnManager {
|
|||
|
||||
fn establish_connection(&self) -> Result<DbConnInner, diesel::r2d2::Error> {
|
||||
match DbConnType::from_url(&self.database_url) {
|
||||
#[cfg(dsql)]
|
||||
Ok(DbConnType::Dsql) => {
|
||||
let db_url = dsql::psql_url(&self.database_url).map_err(diesel::r2d2::Error::ConnectionError)?;
|
||||
let conn = diesel::pg::PgConnection::establish(&db_url)?;
|
||||
Ok(DbConnInner::Postgresql(conn))
|
||||
}
|
||||
#[cfg(mysql)]
|
||||
Ok(DbConnType::Mysql) => {
|
||||
let conn = diesel::mysql::MysqlConnection::establish(&self.database_url)?;
|
||||
|
|
@ -110,8 +118,10 @@ impl diesel::r2d2::ManageConnection for DbConnManager {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Eq, PartialEq)]
|
||||
#[derive(Clone, Copy, Eq, PartialEq)]
|
||||
pub enum DbConnType {
|
||||
#[cfg(dsql)]
|
||||
Dsql,
|
||||
#[cfg(mysql)]
|
||||
Mysql,
|
||||
#[cfg(postgresql)]
|
||||
|
|
@ -195,6 +205,10 @@ impl DbPool {
|
|||
}
|
||||
|
||||
match conn_type {
|
||||
#[cfg(dsql)]
|
||||
DbConnType::Dsql => {
|
||||
dsql_migrations::run_migrations(&db_url)?;
|
||||
}
|
||||
#[cfg(mysql)]
|
||||
DbConnType::Mysql => {
|
||||
mysql_migrations::run_migrations(&db_url)?;
|
||||
|
|
@ -272,6 +286,14 @@ impl DbConnType {
|
|||
#[cfg(not(postgresql))]
|
||||
err!("`DATABASE_URL` is a PostgreSQL URL, but the 'postgresql' feature is not enabled")
|
||||
|
||||
// Amazon Aurora DSQL
|
||||
} else if url.starts_with("dsql:") {
|
||||
#[cfg(dsql)]
|
||||
return Ok(DbConnType::Dsql);
|
||||
|
||||
#[cfg(not(dsql))]
|
||||
err!("`DATABASE_URL` is a DSQL URL, but the 'dsql' feature is not enabled")
|
||||
|
||||
//Sqlite
|
||||
} else {
|
||||
#[cfg(sqlite)]
|
||||
|
|
@ -293,6 +315,8 @@ impl DbConnType {
|
|||
|
||||
pub fn default_init_stmts(&self) -> String {
|
||||
match self {
|
||||
#[cfg(dsql)]
|
||||
Self::Dsql => String::new(),
|
||||
#[cfg(mysql)]
|
||||
Self::Mysql => String::new(),
|
||||
#[cfg(postgresql)]
|
||||
|
|
@ -517,3 +541,19 @@ mod postgresql_migrations {
|
|||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(dsql)]
|
||||
mod dsql_migrations {
|
||||
use diesel::Connection;
|
||||
use diesel_migrations::{EmbeddedMigrations, MigrationHarness};
|
||||
|
||||
pub const MIGRATIONS: EmbeddedMigrations = embed_migrations!("migrations/dsql");
|
||||
|
||||
pub fn run_migrations(db_url: &str) -> Result<(), super::Error> {
|
||||
let db_url = super::dsql::psql_url(db_url)?;
|
||||
let mut connection = diesel::pg::PgConnection::establish(&db_url)?;
|
||||
|
||||
connection.run_pending_migrations(MIGRATIONS).expect("Error running DSQL migrations");
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@ impl Attachment {
|
|||
pub async fn get_url(&self, host: &str) -> Result<String, crate::Error> {
|
||||
let operator = CONFIG.opendal_operator_for_path_type(&PathType::Attachments)?;
|
||||
|
||||
if operator.info().scheme() == <&'static str>::from(opendal::Scheme::Fs) {
|
||||
if crate::storage::is_fs_operator(&operator) {
|
||||
let token = encode_jwt(&generate_file_download_claims(self.cipher_uuid.clone(), self.id.clone()));
|
||||
Ok(format!("{host}/attachments/{}/{}?token={token}", self.cipher_uuid, self.id))
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -237,7 +237,7 @@ impl Send {
|
|||
|
||||
if self.atype == SendType::File as i32 {
|
||||
let operator = CONFIG.opendal_operator_for_path_type(&PathType::Sends)?;
|
||||
operator.remove_all(&self.uuid).await.ok();
|
||||
operator.delete_with(&self.uuid).recursive(true).await.ok();
|
||||
}
|
||||
|
||||
db_run! { conn: {
|
||||
|
|
|
|||
|
|
@ -295,7 +295,7 @@ impl Resolve for CustomDnsResolver {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(s3)]
|
||||
#[cfg(any(dsql, s3, ses))]
|
||||
pub(crate) mod aws {
|
||||
use aws_smithy_runtime_api::client::{
|
||||
http::{HttpClient, HttpConnector, HttpConnectorFuture, HttpConnectorSettings, SharedHttpConnector},
|
||||
|
|
|
|||
47
src/mail.rs
47
src/mail.rs
|
|
@ -95,6 +95,44 @@ fn smtp_transport() -> AsyncSmtpTransport<Tokio1Executor> {
|
|||
smtp_client.build()
|
||||
}
|
||||
|
||||
#[cfg(ses)]
|
||||
async fn send_with_aws_ses(email: Message) -> std::io::Result<()> {
|
||||
use std::io::Error;
|
||||
|
||||
use aws_sdk_sesv2::{
|
||||
types::{EmailContent, RawMessage},
|
||||
Client,
|
||||
};
|
||||
use tokio::sync::OnceCell;
|
||||
|
||||
static AWS_SESV2_CLIENT: OnceCell<Client> = OnceCell::const_new();
|
||||
|
||||
let client = AWS_SESV2_CLIENT
|
||||
.get_or_init(|| async {
|
||||
let config = crate::aws::aws_sdk_config().await;
|
||||
Client::new(config)
|
||||
})
|
||||
.await;
|
||||
|
||||
client
|
||||
.send_email()
|
||||
.content(
|
||||
EmailContent::builder()
|
||||
.raw(
|
||||
RawMessage::builder()
|
||||
.data(email.formatted().into())
|
||||
.build()
|
||||
.map_err(|e| Error::other(format!("Failed to build AWS SESv2 RawMessage: {e:?}")))?,
|
||||
)
|
||||
.build(),
|
||||
)
|
||||
.send()
|
||||
.await
|
||||
.map_err(Error::other)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// This will sanitize the string values by stripping all the html tags to prevent XSS and HTML Injections
|
||||
fn sanitize_data(data: &mut serde_json::Value) {
|
||||
use regex::Regex;
|
||||
|
|
@ -670,6 +708,15 @@ async fn send_with_selected_transport(email: Message) -> EmptyResult {
|
|||
}
|
||||
}
|
||||
}
|
||||
} else if CONFIG.use_aws_ses() {
|
||||
#[cfg(ses)]
|
||||
match send_with_aws_ses(email).await {
|
||||
Ok(_) => Ok(()),
|
||||
Err(e) => err!("Failed to send email", format!("Failed to send email using AWS SES: {e:?}")),
|
||||
}
|
||||
|
||||
#[cfg(not(ses))]
|
||||
unreachable!("Failed to send email using AWS SES: `ses` feature is not enabled");
|
||||
} else {
|
||||
match smtp_transport().send(email).await {
|
||||
Ok(_) => Ok(()),
|
||||
|
|
|
|||
|
|
@ -48,6 +48,8 @@ use tokio::signal::unix::SignalKind;
|
|||
mod error;
|
||||
mod api;
|
||||
mod auth;
|
||||
#[cfg(any(dsql, s3, ses))]
|
||||
mod aws;
|
||||
mod config;
|
||||
mod crypto;
|
||||
#[macro_use]
|
||||
|
|
@ -57,6 +59,7 @@ mod mail;
|
|||
mod ratelimit;
|
||||
mod sso;
|
||||
mod sso_client;
|
||||
mod storage;
|
||||
mod util;
|
||||
|
||||
use crate::api::core::two_factor::duo_oidc::purge_duo_contexts;
|
||||
|
|
|
|||
283
src/storage.rs
Normal file
283
src/storage.rs
Normal file
|
|
@ -0,0 +1,283 @@
|
|||
use std::sync::LazyLock;
|
||||
|
||||
pub(crate) fn join_path(base: &str, child: &str) -> String {
|
||||
#[cfg(s3)]
|
||||
if s3::is_uri(base) {
|
||||
return s3::join_path(base, child);
|
||||
}
|
||||
|
||||
let base = base.trim_end_matches('/');
|
||||
let child = child.trim_start_matches('/');
|
||||
if base.is_empty() {
|
||||
child.to_string()
|
||||
} else if child.is_empty() {
|
||||
base.to_string()
|
||||
} else {
|
||||
format!("{base}/{child}")
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn with_extension(path: &str, extension: &str) -> String {
|
||||
let extension = extension.trim_start_matches('.');
|
||||
|
||||
#[cfg(s3)]
|
||||
if s3::is_uri(path) {
|
||||
return s3::with_extension(path, extension);
|
||||
}
|
||||
|
||||
format!("{path}.{extension}")
|
||||
}
|
||||
|
||||
pub(crate) fn parent(path: &str) -> Option<String> {
|
||||
#[cfg(s3)]
|
||||
if s3::is_uri(path) {
|
||||
return s3::parent(path);
|
||||
}
|
||||
|
||||
std::path::Path::new(path).parent()?.to_str().map(ToString::to_string)
|
||||
}
|
||||
|
||||
pub(crate) fn file_name(path: &str) -> Option<String> {
|
||||
#[cfg(s3)]
|
||||
if s3::is_uri(path) {
|
||||
return s3::file_name(path);
|
||||
}
|
||||
|
||||
std::path::Path::new(path).file_name()?.to_str().map(ToString::to_string)
|
||||
}
|
||||
|
||||
pub(crate) fn is_fs_operator(operator: &opendal::Operator) -> bool {
|
||||
operator.info().scheme() == opendal::services::FS_SCHEME
|
||||
}
|
||||
|
||||
pub(crate) fn operator_for_path(path: &str) -> Result<opendal::Operator, crate::Error> {
|
||||
// Cache of previously built operators by path
|
||||
static OPERATORS_BY_PATH: LazyLock<dashmap::DashMap<String, opendal::Operator>> =
|
||||
LazyLock::new(dashmap::DashMap::new);
|
||||
|
||||
if let Some(operator) = OPERATORS_BY_PATH.get(path) {
|
||||
return Ok(operator.clone());
|
||||
}
|
||||
|
||||
let operator = if path.starts_with("s3://") {
|
||||
#[cfg(not(s3))]
|
||||
return Err(opendal::Error::new(opendal::ErrorKind::ConfigInvalid, "S3 support is not enabled").into());
|
||||
|
||||
#[cfg(s3)]
|
||||
s3::operator_for_path(path)?
|
||||
} else {
|
||||
let builder = opendal::services::Fs::default().root(path);
|
||||
opendal::Operator::new(builder)?.finish()
|
||||
};
|
||||
|
||||
OPERATORS_BY_PATH.insert(path.to_string(), operator.clone());
|
||||
|
||||
Ok(operator)
|
||||
}
|
||||
|
||||
#[cfg(s3)]
|
||||
mod s3 {
|
||||
use reqwest::Url;
|
||||
|
||||
use crate::error::Error;
|
||||
|
||||
pub(super) fn is_uri(path: &str) -> bool {
|
||||
path.starts_with("s3://")
|
||||
}
|
||||
|
||||
pub(super) fn join_path(base: &str, child: &str) -> String {
|
||||
if let Ok(mut url) = Url::parse(base) {
|
||||
let mut segments = path_segments(&url);
|
||||
segments.extend(child.split('/').filter(|segment| !segment.is_empty()).map(ToString::to_string));
|
||||
set_path_segments(&mut url, &segments);
|
||||
return url.to_string();
|
||||
}
|
||||
|
||||
let base = base.trim_end_matches('/');
|
||||
let child = child.trim_start_matches('/');
|
||||
if base.is_empty() {
|
||||
child.to_string()
|
||||
} else if child.is_empty() {
|
||||
base.to_string()
|
||||
} else {
|
||||
format!("{base}/{child}")
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn with_extension(path: &str, extension: &str) -> String {
|
||||
if let Ok(mut url) = Url::parse(path) {
|
||||
let mut segments = path_segments(&url);
|
||||
if let Some(file_name) = segments.last_mut() {
|
||||
file_name.push('.');
|
||||
file_name.push_str(extension);
|
||||
set_path_segments(&mut url, &segments);
|
||||
return url.to_string();
|
||||
}
|
||||
}
|
||||
|
||||
format!("{path}.{extension}")
|
||||
}
|
||||
|
||||
pub(super) fn parent(path: &str) -> Option<String> {
|
||||
if let Ok(mut url) = Url::parse(path) {
|
||||
let mut segments = path_segments(&url);
|
||||
segments.pop()?;
|
||||
set_path_segments(&mut url, &segments);
|
||||
return Some(url.to_string());
|
||||
}
|
||||
|
||||
std::path::Path::new(path).parent()?.to_str().map(ToString::to_string)
|
||||
}
|
||||
|
||||
pub(super) fn file_name(path: &str) -> Option<String> {
|
||||
if let Ok(url) = Url::parse(path) {
|
||||
return path_segments(&url).pop();
|
||||
}
|
||||
|
||||
std::path::Path::new(path).file_name()?.to_str().map(ToString::to_string)
|
||||
}
|
||||
|
||||
fn path_segments(url: &Url) -> Vec<String> {
|
||||
url.path_segments()
|
||||
.map(|segments| segments.filter(|segment| !segment.is_empty()).map(ToString::to_string).collect())
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
fn set_path_segments(url: &mut Url, segments: &[String]) {
|
||||
if segments.is_empty() {
|
||||
url.set_path("");
|
||||
} else {
|
||||
url.set_path(&format!("/{}", segments.join("/")));
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn operator_for_path(path: &str) -> Result<opendal::Operator, Error> {
|
||||
use opendal::Configurator;
|
||||
use reqsign_aws_v4::Credential;
|
||||
use reqsign_core::{Context, ProvideCredential, ProvideCredentialChain};
|
||||
|
||||
// This is a custom AWS credential loader that uses the official AWS Rust
|
||||
// SDK config crate to load credentials. This ensures maximum compatibility
|
||||
// with AWS credential configurations. For example, OpenDAL doesn't support
|
||||
// AWS SSO temporary credentials yet.
|
||||
#[derive(Debug)]
|
||||
struct OpenDALS3CredentialProvider;
|
||||
|
||||
impl ProvideCredential for OpenDALS3CredentialProvider {
|
||||
type Credential = Credential;
|
||||
|
||||
async fn provide_credential(&self, _ctx: &Context) -> reqsign_core::Result<Option<Self::Credential>> {
|
||||
use aws_credential_types::provider::ProvideCredentials as _;
|
||||
use reqsign_core::time::Timestamp;
|
||||
|
||||
let credentials_provider =
|
||||
crate::aws::aws_sdk_config().await.credentials_provider().ok_or_else(|| {
|
||||
reqsign_core::Error::unexpected("failed to load AWS credentials provider from AWS SDK config")
|
||||
})?;
|
||||
let creds = credentials_provider.provide_credentials().await.map_err(|e| {
|
||||
reqsign_core::Error::unexpected("failed to load AWS credentials via AWS SDK").with_source(e)
|
||||
})?;
|
||||
|
||||
let expires_in = if let Some(expiration) = creds.expiry() {
|
||||
let duration = expiration.duration_since(std::time::UNIX_EPOCH).map_err(|e| {
|
||||
reqsign_core::Error::unexpected("AWS credential expiration is before the Unix epoch")
|
||||
.with_source(e)
|
||||
})?;
|
||||
let seconds = i64::try_from(duration.as_secs()).map_err(|e| {
|
||||
reqsign_core::Error::unexpected("AWS credential expiration is too large").with_source(e)
|
||||
})?;
|
||||
Some(Timestamp::from_second(seconds)?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
Ok(Some(Credential {
|
||||
access_key_id: creds.access_key_id().to_string(),
|
||||
secret_access_key: creds.secret_access_key().to_string(),
|
||||
session_token: creds.session_token().map(|s| s.to_string()),
|
||||
expires_in,
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
let uri = opendal::OperatorUri::new(path, std::iter::empty::<(String, String)>())?;
|
||||
let mut config = opendal::services::S3Config::from_uri(&uri)?;
|
||||
|
||||
if !uri_has_option(&uri, &["default_storage_class"]) {
|
||||
config.default_storage_class = Some("INTELLIGENT_TIERING".to_string());
|
||||
}
|
||||
|
||||
if !uri_has_option(
|
||||
&uri,
|
||||
&["enable_virtual_host_style", "aws_virtual_hosted_style_request", "virtual_hosted_style_request"],
|
||||
) {
|
||||
config.enable_virtual_host_style = true;
|
||||
}
|
||||
|
||||
let use_aws_sdk_credentials = !uri_has_credential_options(&uri, &config);
|
||||
let mut builder = config.into_builder();
|
||||
|
||||
if use_aws_sdk_credentials {
|
||||
builder =
|
||||
builder.credential_provider_chain(ProvideCredentialChain::new().push(OpenDALS3CredentialProvider));
|
||||
}
|
||||
|
||||
Ok(opendal::Operator::new(builder)?.finish())
|
||||
}
|
||||
|
||||
fn uri_has_option(uri: &opendal::OperatorUri, names: &[&str]) -> bool {
|
||||
names.iter().any(|name| uri.options().contains_key(*name))
|
||||
}
|
||||
|
||||
fn uri_has_credential_options(uri: &opendal::OperatorUri, config: &opendal::services::S3Config) -> bool {
|
||||
config.access_key_id.is_some()
|
||||
|| config.secret_access_key.is_some()
|
||||
|| config.session_token.is_some()
|
||||
|| config.role_arn.is_some()
|
||||
|| config.external_id.is_some()
|
||||
|| config.role_session_name.is_some()
|
||||
|| uri_has_option(uri, &["allow_anonymous", "disable_config_load", "disable_ec2_metadata"])
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn handles_local_paths() {
|
||||
assert_eq!(join_path("data", "attachments"), "data/attachments");
|
||||
assert_eq!(with_extension("data/rsa_key", "pem"), "data/rsa_key.pem");
|
||||
assert_eq!(parent("data/rsa_key.pem").as_deref(), Some("data"));
|
||||
assert_eq!(file_name("data/rsa_key.pem").as_deref(), Some("rsa_key.pem"));
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(test, s3))]
|
||||
mod s3_tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn joins_s3_path_before_query_string() {
|
||||
assert_eq!(
|
||||
join_path("s3://bucket/base?region=us-west-2", "attachments"),
|
||||
"s3://bucket/base/attachments?region=us-west-2"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn appends_extension_before_s3_query_string() {
|
||||
assert_eq!(
|
||||
with_extension("s3://bucket/base/rsa_key?region=us-west-2", "pem"),
|
||||
"s3://bucket/base/rsa_key.pem?region=us-west-2"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn splits_s3_parent_and_file_name_without_query_string() {
|
||||
let path = "s3://bucket/base/config.json?region=us-west-2";
|
||||
|
||||
assert_eq!(parent(path).as_deref(), Some("s3://bucket/base?region=us-west-2"));
|
||||
assert_eq!(file_name(path).as_deref(), Some("config.json"));
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue