Compare commits

...

169 Commits

Author SHA1 Message Date
Mathijs van Veluw
48836501bf Update crates (#4074)
* Remove another header for websocket connections

* Fix small bake issue

* Update crates

Updated crates and adjusted code where needed.
One major update is Rocket rc4, no need anymore (again) for crates.io patching.

The only item still pending is openssl/openssl-sys for which we need to
wait if https://github.com/sfackler/rust-openssl/pull/2094 will be
merged. If, then we can remove the pinned versions for the openssl crate.
2023-11-15 10:41:14 +01:00
Mathijs van Veluw
f863ffb89a Add Protected Actions Check (#4067)
Since the feature `Login with device` some actions done via the
web-vault need to be verified via an OTP instead of providing the MasterPassword.

This only happens if a user used the `Login with device` on a device
which uses either Biometrics login or PIN. These actions prevent the
athorizing device to send the MasterPasswordHash. When this happens, the
web-vault requests an OTP to be filled-in and this OTP is send to the
users email address which is the same as the email address to login.

The only way to bypass this is by logging in with the your password, in
those cases a password is requested instead of an OTP.

In case SMTP is not enabled, it will show an error message telling to
user to login using there password.

Fixes #4042
2023-11-12 22:15:44 +01:00
Mathijs van Veluw
03c6ed2e07 Disable autofill-v2 (#4056)
Disabled autofill-v2 as it seems to cause strange issues as reported
here: https://github.com/dani-garcia/vaultwarden/discussions/4052

Also added the Vaultwarden server version back again but at a different
location.

Fixes #4052
2023-11-09 00:16:27 +01:00
Mathijs van Veluw
efc6eb0073 Fix missing alpine tag during buildx bake (#4043)
The bake recipt was missing the single `:alpine` tag for the alpine
builds when we were releasing a `stable/latest` version of Vaultwarden.

This PR fixes this by checking for those conditions and add the
`:alpine` tag too.

We will keep the `:latest-alpine` also, which i find even nicer then just
`:alpine`

Fixes #4035
2023-11-07 10:50:58 +01:00
Mathijs van Veluw
cec1e87679 Fix importing Bitwarden exports (#4030)
When importing Bitwarden JSON exports, these would fail because the last
modification date was also imported and caused our out-off-sync check to
kick-in. This PR fixes this by checking if we are doing an import, and
skip this check.

Fixes #4005
2023-11-05 21:44:29 +01:00
nico
512b3b9b7c ci: add trivy workflow (#3997)
* ci: add trivy workflow

to ensure critical and high vulnerabilties are detected quickly

* push trivy-action to 0.13.1
2023-11-05 15:00:24 +01:00
GeekCorner
93da5091e6 feat: Working passkeys storage (#4025) 2023-11-04 03:08:45 +01:00
Daniel García
915496c103 Update web vault to 2023.10.0 2023-11-04 02:55:41 +01:00
Mathijs van Veluw
ecb31c85d6 Fix issue with MariaDB/MySQL migrations (#3994)
MariaDB/MySQL doesn't like the normal `"` quotes around the column name.
This needs to be a backtick **`**.

This PR changes the migration script to fix this issue.

Fixes #3993
2023-10-23 12:23:35 +02:00
Mathijs van Veluw
d722328f05 Container building changes (#3958)
* WIP: Container building changes

* Small updates

- Updated to rust 1.73.0
- Updated crates
- Updated documentation
- Added a bake.sh script to make baking easier

* Update GitHub Actions Workflow

- Updated workflow to use qemu and buildx bake

In the future i would like to extract the alpine based binaries and add
them as artifacts to the release.

* Address review remarks and small updates

- Addressed review remarks
- Added `podman-bake.sh` script to build Vaultwarden with podman
- Updated README
- Updated crates
- Added `VW_VERSION` support
- Added annotations
- Updated web-vault to v2023.9.1
2023-10-23 00:18:38 +02:00
Daniel García
cb4b683dcd Implement cipher key encryption (#3990) 2023-10-23 00:18:14 +02:00
aureateflux
6eaf131922 2FA Confirmation Code Email subject line change to fix triggering Google spam blocker (#3572)
* Update twofactor_email.hbs subject line to avoid triggering gmail content filter

Previous subject line was triggering Google's spam blocker, breaking the 2FA setup process if a gmail SMTP server is configured.  The new subject line does not (currently) run afoul of Google's filter.

* Update twofactor_email.html.hbs subject line to avoid triggering gmail content filter

Previous subject line was triggering Google's spam blocker, breaking the 2FA setup process if a gmail SMTP server is configured.  The new subject line does not (currently) run afoul of Google's filter.

* Update twofactor_email.hbs

Changed "Confirmation" to "Verification" for the sake of internal consistency

* Update twofactor_email.hbs

* Update twofactor_email.html.hbs
2023-10-21 20:50:22 +02:00
Daniel García
8933ac2ee7 Merge pull request #3986 from admav/config_email_change
New config option disable email change
2023-10-21 17:59:25 +02:00
Daniel García
6822e445bb Merge pull request #3804 from BlackDex/fix-3777
Fix External ID not set during DC Sync
2023-10-21 17:58:51 +02:00
Daniel García
18fbc1ccf6 Merge pull request #3909 from tobiasmboelz/reopen-log-file
Reopen log file on SIGHUP
2023-10-21 17:58:15 +02:00
Adrià Martín
4861f6decc New config option disable email change 2023-10-21 17:14:26 +02:00
Tobias Bölz
b435ee49ad tokio::signal::unix::SignalKind::hangup().as_raw_value() insted of 1 2023-10-21 17:14:03 +02:00
Tobias Bölz
193f86e43e reopen removed from Cargo.toml 2023-10-21 17:14:03 +02:00
Tobias Bölz
66a7baa67c Reopen log file on SIGHUP 2023-10-21 17:14:03 +02:00
BlackDex
18d66474e0 Fix External ID not set during DC Sync
While working on the fix I realised the location where the `external_id`
is stored was wrong. It was stored in the `users` table, but it actually
should have been stored in the `users_organizations` table.

This will move the column to the right table. It will not move the
values of the `external_id` column, because if there are more
organizations, there is no way to really know which organization it is
linked to. Setups using the Directory Connector can clear the sync
cache, and sync again, that will store all the `external_id` values at
the right location.

Also changed the function to revoke,restore an org-user and set_external_id to return a boolean.
It will state if the value has been changed or not, and if not, we can
prevent a `save` call to the database.

The `users` table is not changed to remove the `external_id` column, thi
to prevent issue when users want to revert back to an earlier version
for some reason. We can do this after a few minor release i think.

Fixes #3777
2023-10-21 13:54:48 +02:00
Daniel García
ff8db4fd78 Merge pull request #3951 from teicee/last-active
Adds LastActive on /admin/users API route
2023-10-21 00:21:58 +02:00
Daniel García
b2f9af718e Merge pull request #3964 from BlackDex/fix-small-issues
Fix small issues
2023-10-21 00:18:21 +02:00
Daniel García
198fd2fc1d Merge pull request #3965 from AndreasHGK/patch-0
README.md: Fix grammar nit
2023-10-21 00:17:22 +02:00
Daniel García
ec8a9c82df Merge pull request #3981 from teicee/3980-unnecessary-copy
Remove unnecessary variable clone
2023-10-21 00:17:00 +02:00
Mathieu Valois
ef5e0bd4e5 Remove unnecessary variable clone 2023-10-17 20:53:10 +02:00
Mathieu Valois
30b408eaa9 LastActive is null instead of 'Never' when never connected 2023-10-17 11:18:39 +02:00
AndreasHGK
e205e3b7db README.md: Fix grammar nit 2023-10-09 22:54:33 +02:00
BlackDex
ca1a9e26d8 Remove SET CONSTRAINTS during postgres migration
The PostgreSQL migrations do not need this setting.
I tested this by running an old Vaultwarden instance (v1.18.0) on a new
PostrgreSQL database, created a few users and some vault items, after
that run the new code and it doesn't break.

Fixes #3930
2023-10-09 20:55:09 +02:00
BlackDex
f3a1385aee Do not send extra headers for Upgrade connection
During a WebSocket connection we currently also send several headers
which could cause issues with some reverse proxy, or with the CloudFlare
tunnel for example. This PR resolves these issues.

Fixes #3881
2023-10-09 20:11:20 +02:00
Daniel García
008a2cf298 Merge pull request #3859 from stefan0xC/prevent-handlebar-logs
filter handlebars logs
2023-10-08 14:57:23 +02:00
Daniel García
f0c9a7fbc3 Merge pull request #3950 from stefan0xC/rename-anonaddy
csp: rename anonaddy.com to addy.io
2023-10-08 14:56:18 +02:00
Daniel García
9162b13123 Merge pull request #3959 from tuhanayim/fix-typos
Fix typos
2023-10-08 14:55:38 +02:00
tuhana
480bf9b0c1 our selves -> ourselves 2023-10-07 22:07:38 +03:00
Stefan Melmuk
f96c5e8a1e filter handlebars logs 2023-10-07 16:24:21 +02:00
tuhana
3d4be24902 Fix typos 2023-10-05 20:08:26 +03:00
Mathieu Valois
bf41d74501 Adds LastActive on /admin/users API route 2023-10-03 16:41:06 +02:00
Stefan Melmuk
01e33a4919 csp: rename anonaddy.com to addy.io 2023-10-03 08:21:02 +02:00
Daniel García
bc26bfa589 Merge pull request #3911 from BlackDex/fix-arm-build
Fix arm builds
2023-09-22 13:48:11 -06:00
BlackDex
ccc51e7580 Fix arm builds
Because of some changes in the packages of Debian we need to add an
extra package to request it also to install
2023-09-22 21:44:20 +02:00
Daniel García
99a59bc4f3 Merge pull request #3910 from BlackDex/update-git-workflow
Update GitHub Workflow
2023-09-22 13:05:53 -06:00
BlackDex
a77482575a Update GitHub Workflow
- Updated GitHub actions
- Fixed an issue with testing MSRV (It didn't)
2023-09-22 20:31:31 +02:00
Daniel García
bbd630f1ee Merge pull request #3831 from BlackDex/fix-3819
Fix Login With Device without MasterPassword
2023-09-02 14:41:44 +02:00
Daniel García
d18b793c71 Merge branch 'main' into fix-3819 2023-09-01 23:39:02 +02:00
Daniel García
d3a1d875d5 Merge pull request #3821 from stefan0xC/update-to-v2023.8.2
update web-vault to v2023.8.2
2023-09-01 23:37:14 +02:00
BlackDex
d6e0ace192 Fix Login With Device without MasterPassword
It looks like either something changed in the latest v2023.8.x versions,
or when using Biometrics to login, but the MasterPasswordHash is an
optional value during the Approve action.

This PR makes the MasterPasswordHash an optional value which resolves
this issues. Bitwarden works the same way.

I also changed the EncKey to an Option in the database since empty
strings as a default value is not nice in databases, better to use
`null` in these cases.

Fixes #3819
2023-09-01 21:03:50 +02:00
Stefan Melmuk
60cbfa59bf update web-vault to v2023.8.2 2023-09-01 07:45:48 +02:00
Daniel García
5ab7010c37 Fix reference to rust-toolchain.toml in Docker 2023-09-01 00:12:48 +02:00
Daniel García
ad2cfd8b97 Merge pull request #3808 from BlackDex/update-rust-and-crates
Update Rust and Crates
2023-09-01 00:07:14 +02:00
BlackDex
32543c46da Update Rust and Crates
- Updated Rust to v1.72.0
- Updated all the crates
  Including a CVE https://github.com/dani-garcia/vaultwarden/security/dependabot/21
- Updated GitHub Workflows
- Run `cargo fmt` which has some new fmt's
- Moved from `rust-toolchain` to `rust-toolchain.toml`
2023-08-31 22:45:53 +02:00
Daniel García
66bff73ebf Merge pull request #3730 from BlackDex/update-admin-interface
Update admin interface
2023-08-31 21:31:10 +02:00
BlackDex
83d5432cbf Update admin interface
- Updated the admin interface dependencies.
- Replace bootstrap-native with bootstrap
- Added auto theme with an option to switch to dark/light
- Some small color changes
- Added an dev only function to always load static files from disk
2023-08-31 21:14:53 +02:00
Daniel García
f579a4154c Merge pull request #3806 from BlackDex/fix-3776
Allow Authorization header for Web Sockets
2023-08-31 20:46:07 +02:00
Daniel García
f5a19c5f8b Merge pull request #3797 from stefan0xC/add-plans-all-endpoint
add new secretsmanager plan for web-v2023.8.x
2023-08-31 20:37:04 +02:00
BlackDex
aa9bc1f785 Allow Authorization header for Web Sockets
Some clients (Thirdparty) might use the `Authorization` header instead
of a query param. We didn't supported this since all the official
clients do not seem to use this way of working. But Bitwarden does check
both ways.

This PR adds an extra check for this header which can be optional.

Fixes #3776
2023-08-31 12:35:20 +02:00
Stefan Melmuk
f162e85e44 add UserDecryptionOptions to login response (#3813)
needed for web-v2023.8.2+ compatibility due to the inclusion of the new
trusted device encryption feature. without this change, the web vault
will assume that you don't have a master password set and force you to
set one.
2023-08-31 11:02:36 +02:00
Stefan Melmuk
33ef70c192 add minimal secretsmanager plan for web-v2023.8.x
in web-v2023.8.x the getPlans() call was changed from `/plans/` to `/plans/all`
and the create new organization form also requires a bitwardenProduct to
differentiate between plans for PasswordManager and the SecretsManager
2023-08-24 22:39:16 +02:00
Mathijs van Veluw
3d2df6ce11 Merge pull request #3751 from BlackDex/optimize-icon-fetching
Optimized Favicon downloading
2023-08-13 19:31:43 +02:00
BlackDex
6cdcb3b297 Optimized Favicon downloading
Some optimizations in regards to downloading Favicon's.

I also encounterd some issues with accessing some sites where the
connection got dropped or closed early. This seems a reqwest/hyper
thingy, https://github.com/hyperium/hyper/issues/2136. This is now also
fixed.

General:

- Decreased struct size
- Decreased memory allocations
- Optimized tokenizer a bit more to only emit tags when all attributes are there and are valid.

reqwest/hyper connection issue:
The following changes helped solve the connection issues to some sites.
The endresult is that some icons are now able to be downloaded always instead of sometimes.

- Enabled some extra reqwest features, `deflate` and `native-tls-alpn`
  (Which do not bring in any extra crates since other crates already enabled them, but they were not active for Vaultwarden it self)
- Configured reqwest to have a max amount of idle pool connections per host
- Configured reqwest to timeout the idle connections in 10 seconds
2023-08-13 19:13:00 +02:00
Mathijs van Veluw
d1af468700 Merge pull request #3769 from GeekCornerGH/feature/bump-web-vault-v2023.7.1
chore: Bump web vault to v2023.7.1 and bump Rust
2023-08-13 19:10:18 +02:00
GeekCornerGH
ae1c53f4e5 build (deps): Bump Rust version and sync lockfile 2023-08-13 18:52:23 +02:00
GeekCorner
bc57c4b193 feat (web vault): Bump web vault to v2023.7.1 2023-08-13 18:18:00 +02:00
Mathijs van Veluw
61ae4c9cf5 Merge pull request #3592 from quexten/feature/login-with-device
Implement "login with device"
2023-08-13 18:15:09 +02:00
Bernd Schoolmann
8d7b3db33d Implement login-with-device 2023-08-13 17:54:18 +02:00
Daniel García
e9ec3741ae Merge pull request #3573 from BlackDex/update-base-images-and-versions
Update images to Bookworm and PQ15 and Rust v1.71
2023-08-12 23:55:14 +02:00
Daniel García
dacd50f3f1 Merge pull request #3740 from BlackDex/fix-ldap-import-org-status
Fix UserOrg status during LDAP Import
2023-08-12 22:19:20 +02:00
Daniel García
9412112639 Merge pull request #3734 from BlackDex/fix-env-template
Fix .env.template file
2023-08-12 22:18:33 +02:00
BlackDex
aaeae16983 Update images to Bookworm and PQ15
This PR updates the base images to use Debian Bookworm as base image. Also the MUSL/Alpine builds now use OpenSSLv3 and PostgreSQL v15.

The GHA Workflows are updated to use Ubuntu 22.04 to better match the versions of Debian Bookworm.

Also:
- Enabled spares crate registry
- Updated workflow actions
- Updated Rust to v1.71.0
- The rust-musl images now use musl v1.2.3 for the 32bit arch's if the Rust version is v1.71.0 or higher.
   The 64bit arch's already used musl v1.2.3.
- Updated crates.

Improves / Closes #3434
2023-08-12 12:29:33 +02:00
BlackDex
d892880dd2 Fix UserOrg status during LDAP Import
When a user does not have an account yet and SMTP was disabled it would
set the UserOrg status still to Accepted, though that would make it
possible to verify the user by the Org Admin's.
This would fail, since the user didn't actually crated his account, and
therefor no PublicKey existed.

This PR fixes this behaviour by checking if the password is empty and if
so, puts the user to an `Invited` state instead of `Accepted`.

Fixes #3737
2023-07-31 20:40:48 +02:00
BlackDex
4395e8e888 Fix .env.template file
There was one item missing and one item wrongly named.
This has been fixed including a spellcheck.
2023-07-29 13:20:57 +02:00
Daniel García
3dbfc484a5 Merge pull request #3704 from BlackDex/remove-debug-code
Remove debug code during attachment download
2023-07-17 18:22:56 +02:00
BlackDex
4ec2507073 Remove debug code during attachment download
There was some debug code during attachment downloads.
This produces extra logs not needed or even wanted.
2023-07-17 15:36:54 +02:00
Daniel García
ab65d7989b Merge pull request #3690 from BlackDex/fix-issue-3685
Fix some external_id issues
2023-07-14 20:43:51 +02:00
Daniel García
8707728cdb Merge pull request #3686 from GeekCornerGH/feat/add-forwardemail-support
feat: Add support for forwardemail
2023-07-14 20:43:32 +02:00
BlackDex
631d022e17 Fix some external_id issues
- Do not update `externalId` on group updates
   Groups are only updated via the web-vault currently, and those do not
   send the `externalId` value, and thus we need to prevent updating it.
 - Refactored some other ExternalId functions
 - Prevent empty `externalId` on `Collections`
 - Return `externalId` for users

Fixes #3685
2023-07-12 22:04:18 +02:00
GeekCorner
211f4492fa feat: Add support for forwardemail 2023-07-12 10:50:41 +02:00
Daniel García
61f9081827 Merge pull request #3678 from BlackDex/fix-org-api-creation-postgres
Fix Org API Key generation on PosgreSQL
2023-07-10 17:59:53 +02:00
BlackDex
a8e5384c4a Fix Org API Key generation on PosgreSQL
Using PostgreSQL creating or rotating the Org API Key failed because of
some query mismatch. This PR fixes that.

Fixes https://github.com/dani-garcia/vaultwarden/discussions/3671#discussioncomment-6400394
2023-07-10 15:29:06 +02:00
Mathijs van Veluw
1c7338c7c4 Merge pull request #3659 from BlackDex/fix-org-creation
Fix org creation regresion
2023-07-06 10:39:59 +02:00
BlackDex
08f37b9935 Fix org creation regresion
A previous PR added a field which isn't there on the initial creation of
an org. This PR fixes that.
2023-07-06 10:14:04 +02:00
Daniel García
4826ddca4c Merge pull request #3651 from tessus/fix/branch-on-HEAD
fix version when compiled at a specific commit
2023-07-05 18:45:08 +02:00
Helmut K. C. Tessarek
2b32b6f78c fix version when compiled at a specific commit
When a specific commit is checked out from the main branch, the vaultwarden
version is reported as `vaultwarden x.y.z-githash (HEAD)`.
This is a problem, because the admin interface reports this as a version from
a branch called HEAD, while in reality the commit was from the main branch.
2023-07-04 18:08:52 -04:00
Daniel García
a6cfdddfd8 Merge pull request #3649 from BlackDex/update-crates
Update crates and small clippy fix
2023-07-04 20:56:05 +02:00
Daniel García
814ce9a6ac Merge pull request #3632 from sirux88/fix-reset-password-check-issue
fix missing password check while manual reset password enrollment
2023-07-04 20:55:34 +02:00
Daniel García
1bee46f64b Merge pull request #3623 from fashberg/main
Added-External_id for Collections
2023-07-04 20:54:36 +02:00
Daniel García
556d945396 Merge pull request #3620 from DenuxPlays/main
Updated docker run command
2023-07-04 20:54:05 +02:00
Daniel García
664b480c71 Merge pull request #3609 from farodin91/add-user-to-collection-during-creation
add user to collection during creation
2023-07-04 20:53:46 +02:00
Jan Jansen
84e901b7d2 add user to collection during creation
Signed-off-by: Jan Jansen <jan.jansen@gdata.de>
2023-07-04 20:27:37 +02:00
Folke Ashberg
839b2bc950 fix format error 2023-07-04 20:26:03 +02:00
Folke Ashberg
6050c8dac5 Added-External_id for Collections 2023-07-04 20:26:03 +02:00
BlackDex
0a6b797e6e Update crates and small clippy fix
- Update all crates
- Remove async which is reported by clippy in v1.72.0
2023-07-04 20:12:50 +02:00
sirux88
fb6f441a4f fixed unnecessary variable usage 2023-07-04 18:57:49 +02:00
sirux88
9876aedd67 added password check for manual reset
password enrollment endpoint
2023-07-04 18:57:49 +02:00
Daniel García
19e671ff25 Fix dataurl parse panic when icon is malformed 2023-07-03 20:20:26 +02:00
Daniel García
60964c07e6 Add some extra access checks for attachments and groups 2023-07-03 19:58:14 +02:00
Timon Klinkert
e4894524e4 updated docker run command 2023-06-26 00:31:40 +02:00
Daniel García
e7f083dee9 Merge pull request #3593 from GeekCornerGH/feature/store-passkeys-in-the-vault
feat: Support for storing passkeys in the vault
2023-06-22 19:06:55 +02:00
GeekCornerGH
1074315a87 feat: Support for storing passkeys in the vault 2023-06-22 18:48:13 +02:00
Daniel García
c56bf38079 Merge pull request #3608 from BlackDex/fix-issue-3607
Fix send access regression
2023-06-22 17:58:15 +02:00
BlackDex
3c0cac623d Fix send access regression
In a previous commit push notifications for mobile were added, but this
introduced a header guard which caused issues with anonymous endpoints.

This PR fixes this by using a uuid with only 0's.

Fixes #3607
2023-06-22 16:40:26 +02:00
Mathijs van Veluw
550794b127 Merge pull request #3606 from farodin91/add-group-import-on-invite
Add group import on invite
2023-06-22 11:57:49 +02:00
Jan Jansen
e818a0bf37 Add group import on invite
Fixes #3599

Signed-off-by: Jan Jansen <jan.jansen@gdata.de>
2023-06-22 11:10:43 +02:00
Daniel García
2aedff50e8 Merge pull request #3603 from BlackDex/update-crates-and-workflows
Update crates and workflow
2023-06-21 23:29:15 +02:00
BlackDex
84a23008f4 Update crates and workflow
- Updated all the crates
- Updated workflow actions
- Set cargo registry to sparse
2023-06-21 22:01:05 +02:00
Mathijs van Veluw
44e9e1a58e Merge pull request #3578 from quexten/fix/mobile-push-to-empty-uuid
Add mobile push device filter to non-null push uuid
2023-06-18 17:40:10 +02:00
Bernd Schoolmann
e4606431d1 Fix mobile push blocking requests and spamming push server 2023-06-16 23:34:16 +02:00
Mathijs van Veluw
5b7d7390b0 Merge pull request #3568 from BlackDex/org-api-key-refresh
Implement the Organization API Key support for the new Directory Connector v2022
2023-06-13 09:05:22 +02:00
BlackDex
a05187c0ff Some code changes and optimizations
Some cleanups and optimizations done on the code generated by @Kurnihil
2023-06-13 08:51:07 +02:00
BlackDex
8e34495e73 Merge and modify PR from @Kurnihil
Merging a PR from @Kurnihil into the already rebased branch.
Made some small changes to make it work with newer changes.

Some finetuning is probably still needed.

Co-authored-by: Daniele Andrei <daniele.andrei@geo-satis.com>
Co-authored-by: Kurnihil
2023-06-13 08:51:07 +02:00
BlackDex
4219249e11 Add support for Organization token
This is a WIP for adding organization token login support.
It has basic token login and verification support, but that's about it.

This branch is a refresh of the previous version, and will contain code
from a PR based upon my previous branch.
2023-06-13 08:48:18 +02:00
Mathijs van Veluw
bd883de70e Merge pull request #3304 from GeekCornerGH/feature/push-notifications
feat: Implement Push Notifications sync
2023-06-12 23:45:03 +02:00
GeekCornerGH
2d66292350 feat: Push Notifications
Co-authored-by: samb-devel <125741162+samb-devel@users.noreply.github.com>
Co-authored-by: Zoruk <Zoruk@users.noreply.github.com>
2023-06-11 13:28:18 +02:00
Mathijs van Veluw
adf67a8ee8 Merge pull request #3563 from tessus/update/rust-and-crates
Update Rust and Crates
2023-06-04 22:39:40 +02:00
Helmut K. C. Tessarek
f40f5b8399 update web-vault to v2023.5.0 2023-06-04 16:15:10 -04:00
Helmut K. C. Tessarek
2d6ca0ea95 Update a few more crates 2023-06-04 16:14:51 -04:00
Helmut K. C. Tessarek
06a10e2c5a Update Rust and Crates 2023-06-03 17:04:45 -04:00
Mathijs van Veluw
445680fb84 Merge pull request #3546 from BlackDex/GH-3534
Fix collection change ws notifications
2023-05-26 18:03:45 +02:00
BlackDex
83376544d8 Fix collection change ws notifications
When chaning a collection this did not got notified via WebSockets.
This PR adds this feature and resolves #3534
2023-05-26 17:42:00 +02:00
Mathijs van Veluw
04a17dcdef Merge pull request #3548 from BlackDex/update-crates
Update crates and GH Workflow
2023-05-26 17:41:03 +02:00
BlackDex
0851561392 Update crates and GH Workflow
- Updated crates
- Updated GHA where needed
2023-05-26 17:26:09 +02:00
Mathijs van Veluw
95cd6deda6 Merge pull request #3547 from BlackDex/GH-3540
Prevent 401 on main admin page
2023-05-26 17:25:48 +02:00
BlackDex
636f16dc66 Prevent 401 on main admin page
When you are not loggedin, and have no cookie etc.. we always returned a 401.
This was mainly to allow the login page on all the sub pages, and after
login being redirected to the requested page, for these pages a 401 is a
valid response, since, you do not have access.

But for the main `/admin` page, it should just respond with a `200` and
show the login page.

This PR fixes this flow and response. It should prevent people using
Fail2ban, or other tools being triggered by only accessing the login page.

Resolves #3540
2023-05-25 23:40:36 +02:00
Mathijs van Veluw
9e5b049dca Merge pull request #3532 from jjlin/global-domains
Sync global_domains.json (Pinterest)
2023-05-17 21:20:48 +02:00
Jeremy Lin
23aa9088f3 Sync global_domains.json to bitwarden/server@8dda73a (Pinterest) 2023-05-17 12:04:31 -07:00
Mathijs van Veluw
4f0ed06b06 Merge pull request #3522 from stefan0xC/update-to-v2023.4.2
update web-vault to v2023.4.2
2023-05-12 09:48:56 +02:00
Stefan Melmuk
349c97efaf update crates 2023-05-12 09:31:29 +02:00
Stefan Melmuk
8b05a5d192 update web-vault to v2023.4.2 2023-05-12 08:05:35 +02:00
Mathijs van Veluw
83bf77d713 Merge pull request #3513 from stefan0xC/fix-empty-policy
policy data should be `null` not an empty object
2023-05-09 12:00:10 +02:00
Stefan Melmuk
4d5c047ddc policy data should be null not an empty object 2023-05-09 11:14:46 +02:00
Daniel García
147c9c7b50 Merge pull request #3505 from gitouche-sur-osm/dockerfile-fqin
Use fully qualified image names in Dockerfile
2023-05-08 21:00:30 +02:00
Daniel García
6515a2fcad Merge pull request #3502 from BlackDex/fix-trailing-slash
Use Rocket `v0.5` branch to fix endpoints
2023-05-08 21:00:19 +02:00
BlackDex
4a2ed553df Use Rocket v0.5 branch to fix endpoints
There now is a `v0.5` branch which will be the final release version
when the time is there. Switched to this instead of the `master` branch
which contains other fixes and enhancements as well (for `v0.6`).

This should solve all the endpoint issue we were having.
2023-05-06 19:46:55 +02:00
Gitouche
ba492c0602 Use fully qualified image names in Dockerfile 2023-05-03 18:31:28 +02:00
Daniel García
1ec049e2b5 Update web vault to v2023.4.0 2023-05-01 19:49:48 +02:00
Daniel García
0fb8563b13 Merge pull request #3491 from BlackDex/rocket_changes
Change `String` to `&str` for all Rocket functions and some other fixes
2023-04-30 23:53:15 +02:00
BlackDex
f906f6230a Change String to &str for all Rocket functions
During setting the latest commit hash for Rocket and updating all the
other crates, there were some messages regarding the usage of `String`
for the Rocket endpoint function calls. I acted upon this message and
changed all `String` types to `&str` and modified the code where needed.

This ended up in less alloc calls, and probably also a bit less memory usage.

- Updated all the crates and commit hashes
- Modified all `String` to `&str` where applicable
2023-04-30 17:18:12 +02:00
BlackDex
951ba55123 Prevent some ::_ logs from outputting 2023-04-30 17:17:43 +02:00
BlackDex
18abf226be Fix admin post endpoints 2023-04-30 17:09:42 +02:00
Daniel García
393645617e Merge pull request #3469 from BlackDex/rust-and-crate-updates
Update Rust and Crates
2023-04-24 18:54:35 +02:00
Daniel García
5bf243b675 Merge pull request #3475 from vilgotf/inline-statics
inline static rsa keys
2023-04-24 18:54:19 +02:00
BlackDex
cfba8347a3 Update Rust and Crates
- Updated Rust to v1.69.0
- Updated MSRV to v1.67.1
- Updated crates
- Updated GitHub Actions
2023-04-24 14:10:58 +02:00
Tim Vilgot Mikael Fredenberg
55c1b6e8d5 inline static rsa keys 2023-04-23 21:34:26 +02:00
Daniel García
3d7e80a7aa Merge pull request #3440 from BlackDex/switch-ws-to-streams
Small update to Rocket WebSockets
2023-04-17 20:26:03 +02:00
Daniel García
5866338de4 Merge pull request #3439 from kennymc-c/main
Fixed missing footer_text and a few inconsistencies in email templates
2023-04-17 20:25:36 +02:00
kennymc-c
271e3ae757 Changed permissions back to 644 2023-04-12 18:06:46 +02:00
BlackDex
48cc31a59f Small update to Rocket WebSockets
Switched from channels to stream. This is able to use yield, and the
code looks a bit nicer this way.

Also updated all the crates.
2023-04-12 15:59:05 +02:00
kennymc-c
6a7cee4e7e Fixed footer to footer_text 2023-04-11 22:00:10 +02:00
kennymc-c
f850dbb310 Fixed some missing footer_text partials and a few inconsistencies between plain text and html email templates 2023-04-11 21:27:38 +02:00
Daniel García
07099df41a Merge pull request #3436 from BlackDex/fix-admin-base-url
Several config and admin interface fixes
2023-04-10 21:11:44 +02:00
Daniel García
0c0a80720e Merge pull request #3404 from BlackDex/websockets-via-rocket
WebSockets via Rocket's Upgrade connection
2023-04-10 21:10:29 +02:00
BlackDex
ae437f70a3 Several config and admin interface fixes
- Fixed issue with domains starting with `admin`
- Fixed issue with DUO not being enabled globally anymore (regression)
- Renamed `Ciphers` to `Entries` in overview
- Improved `ADMIN_TOKEN` description
- Updated jquery-slim and datatables

Resolves #3382
Resolves #3415
Resolves discussion on #3288
2023-04-10 20:39:51 +02:00
BlackDex
3d11f4cd16 WebSockets via Rocket's Upgrade connection
This PR implements a (not yet fully released) new feature of Rocket which allows WebSockets/Upgrade connections.
No more need for multiple ports to be opened for Vaultwarden.
No explicit need for a reverse proxy to get WebSockets to work (Although I still suggest to use a reverse proxy).

- Using a git revision for Rocket, since `rocket_ws` is not yet released.
- Updated other crates as well.
- Added a connection guard to clear the WS connection from the Users list.

Fixes #685
Fixes #2917
Fixes #1424
2023-04-10 16:58:58 +02:00
Daniel García
3bd4e42fb0 Merge pull request #3427 from stefan0xC/check-if-policies-enabled
check if reset password policy is enabled
2023-04-09 19:02:27 +02:00
Stefan Melmuk
89e94b1d91 check if reset policy is enabled 2023-04-06 22:34:05 +02:00
Daniel García
0b28ab3be1 Merge pull request #3403 from BlackDex/update-dockerfile-and-rust
Revert setcap, update rust and crates
2023-04-02 15:39:36 +02:00
Daniel García
c5bcc340fa Merge pull request #3405 from BlackDex/fix-multiple-websocket-messages
Fix sending out multiple websocket notifications
2023-04-02 15:24:00 +02:00
BlackDex
bff54fbfdb Fix sending out multiple websocket notifications
For some reason I encountered a strange bug which resulted in sending
out multiple websocket notifications for the exact same user.

Added a `distinct()` for the query to filter out multiple uuid's.
2023-04-02 15:23:36 +02:00
Daniel García
867c6ba056 Merge pull request #3398 from stefan0xC/dont-expect-kdf-memory-or-parallelism
always return KdfMemory and KdfParallelism
2023-04-02 15:22:42 +02:00
Daniel García
d1ecf03f44 Merge pull request #3397 from nikolaevn/feature/add-admin-reinvite-endpoint
support `/users/<uuid>/invite/resend` admin api
2023-04-02 15:21:51 +02:00
BlackDex
fc43608eec Revert setcap, update rust and crates
- Revert #3170 as discussed in #3387
  In hindsight it's better to not have this feature
- Update Dockerfile.j2 for easy version changes.
  Just change it in one place instead of multiple
- Updated to Rust to latest patched version
- Updated crates to latest available
- Pinned mimalloc to an older version, as it breaks on musl builds
2023-04-02 15:19:59 +02:00
Daniel García
15dd05c78d Merge pull request #3390 from BlackDex/fix-abort-pw-reset-on-mail-error
Fix abort on pw reset mail error
2023-04-02 15:19:53 +02:00
Nikolay Nikolaev
aa6f774f65 add check user state 2023-03-31 14:03:37 +03:00
Nikolay Nikolaev
379f885354 add mail check 2023-03-31 13:00:57 +03:00
Stefan Melmuk
39a5f2dbe8 clear kdf memory and parallelism with pbkdf2
when changing back from argon2id to PBKDF2 the unused parameters
should be set to 0.

also fix small bug in _register
2023-03-31 07:31:40 +02:00
Stefan Melmuk
0daaa9b175 always return KdfMemory and KdfParallelism
the client will ignore the value of theses fields in case of `PBKDF2`
(whether they are unset or left from trying out `Argon2id` as KDF).

with `Argon2id` those fields should never be `null` but always in a
valid state. if they are `null` (how would that even happen?) the
client still assumes default values for `Argon2id` (i.e. m=64 and p=4)
and if they are set to something else login will fail anyway.
2023-03-31 01:10:28 +02:00
Nikolay Nikolaev
0c085d21ce fmt 2023-03-30 16:04:35 +03:00
Nikolay Nikolaev
dcaaa430f0 support /users/<uuid>/invite/resend admin api 2023-03-30 15:23:16 +03:00
BlackDex
2cda54ceff Fix password reset issues
There was used a wrong macro to produce an error message when mailing
the user his password was reset failed. It was using `error!()` which
does not return an `Err` and aborts the rest of the code.

This resulted in the users password still being resetted, but not being
notified. This PR fixes this by using `err!()`. Also, do not set the
user object as mutable until it really is needed.

Second, when a user was using the new Argon2id KDF with custom values
like memory and parallelism, that would have rendered the password
incorrect. The endpoint which should return all the data did not
returned all the new Argon2id values.

Fixes #3388

Co-authored-by: Stefan Melmuk <509385+stefan0xC@users.noreply.github.com>
2023-03-30 09:41:13 +02:00
Daniel García
525e6bb65a Merge pull request #3376 from jjlin/knowndevices-nopad
Decode knowndevice `X-Request-Email` as base64url with no padding
2023-03-27 09:32:25 +02:00
Jeremy Lin
62cebebd3d Decode knowndevice X-Request-Email as base64url with no padding
The clients end up removing the padding characters [1][2].

[1] https://github.com/bitwarden/clients/blob/web-v2023.3.0/libs/common/src/misc/utils.ts#L141-L143
[2] https://github.com/bitwarden/mobile/blob/v2023.3.1/src/Core/Utilities/CoreHelpers.cs#L227-L234
2023-03-27 00:03:54 -07:00
163 changed files with 15397 additions and 12498 deletions

View File

@@ -30,6 +30,10 @@
## Define the size of the connection pool used for connecting to the database.
# DATABASE_MAX_CONNS=10
## Database timeout
## Timeout when acquiring database connection
# DATABASE_TIMEOUT=30
## Database connection initialization
## Allows SQL statements to be run whenever a new database connection is created.
## This is mainly useful for connection-scoped pragmas.
@@ -72,6 +76,13 @@
# WEBSOCKET_ADDRESS=0.0.0.0
# WEBSOCKET_PORT=3012
## Enables push notifications (requires key and id from https://bitwarden.com/host)
# PUSH_ENABLED=true
# PUSH_INSTALLATION_ID=CHANGEME
# PUSH_INSTALLATION_KEY=CHANGEME
## Don't change this unless you know what you're doing.
# PUSH_RELAY_URI=https://push.bitwarden.com
## Controls whether users are allowed to create Bitwarden Sends.
## This setting applies globally to all users.
## To control this on a per-org basis instead, use the "Disable Send" org policy.
@@ -86,6 +97,10 @@
## Disabled by default. Also check the EVENT_CLEANUP_SCHEDULE and EVENTS_DAYS_RETAIN settings.
# ORG_EVENTS_ENABLED=false
## Controls whether users can change their email.
## This setting applies globally to all users
# EMAIL_CHANGE_ALLOWED=true
## Number of days to retain events stored in the database.
## If unset (the default), events are kept indefinitely and the scheduled job is disabled!
# EVENTS_DAYS_RETAIN=
@@ -264,6 +279,8 @@
## For details see: https://github.com/dani-garcia/vaultwarden/wiki/Enabling-admin-page#secure-the-admin_token
## If not set, the admin panel is disabled
## New Argon2 PHC string
## Note that for some environments, like docker-compose you need to escape all the dollar signs `$` with an extra dollar sign like `$$`
## Also, use single quotes (') instead of double quotes (") to enclose the string when needed
# ADMIN_TOKEN='$argon2id$v=19$m=65540,t=3,p=4$MmeKRnGK5RW5mJS7h3TOL89GrpLPXJPAtTK8FTqj9HM$DqsstvoSAETl9YhnsXbf43WeaUwJC6JhViIvuPoig78'
## Old plain text string (Will generate warnings in favor of Argon2)
# ADMIN_TOKEN=Vy2VyYTTsKPv8W5aEOWUbB/Bt3DEKePbHmI4m9VcemUMS2rEviDowNAFqYi1xjmp

View File

@@ -8,9 +8,11 @@ on:
- "migrations/**"
- "Cargo.*"
- "build.rs"
- "rust-toolchain"
- "rust-toolchain.toml"
- "rustfmt.toml"
- "diesel.toml"
- "docker/Dockerfile.j2"
- "docker/DockerSettings.yaml"
pull_request:
paths:
- ".github/workflows/build.yml"
@@ -18,19 +20,20 @@ on:
- "migrations/**"
- "Cargo.*"
- "build.rs"
- "rust-toolchain"
- "rust-toolchain.toml"
- "rustfmt.toml"
- "diesel.toml"
- "docker/Dockerfile.j2"
- "docker/DockerSettings.yaml"
jobs:
build:
runs-on: ubuntu-20.04
runs-on: ubuntu-22.04
timeout-minutes: 120
# Make warnings errors, this is to prevent warnings slipping through.
# This is done globally to prevent rebuilds when the RUSTFLAGS env variable changes.
env:
RUSTFLAGS: "-D warnings"
CARGO_REGISTRIES_CRATES_IO_PROTOCOL: git # Use the old git protocol until it is stable probably in 1.68 or 1.69. MSRV needs to be at this before removed.
strategy:
fail-fast: false
matrix:
@@ -43,13 +46,13 @@ jobs:
steps:
# Checkout the repo
- name: "Checkout"
uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3 # v3.5.0
uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0
# End Checkout the repo
# Install dependencies
- name: "Install dependencies Ubuntu"
run: sudo apt-get update && sudo apt-get install -y --no-install-recommends openssl sqlite build-essential libmariadb-dev-compat libpq-dev libssl-dev pkg-config
run: sudo apt-get update && sudo apt-get install -y --no-install-recommends openssl build-essential libmariadb-dev-compat libpq-dev libssl-dev pkg-config
# End Install dependencies
@@ -59,7 +62,7 @@ jobs:
shell: bash
run: |
if [[ "${{ matrix.channel }}" == 'rust-toolchain' ]]; then
RUST_TOOLCHAIN="$(cat rust-toolchain)"
RUST_TOOLCHAIN="$(grep -oP 'channel.*"(\K.*?)(?=")' rust-toolchain.toml)"
elif [[ "${{ matrix.channel }}" == 'msrv' ]]; then
RUST_TOOLCHAIN="$(grep -oP 'rust-version.*"(\K.*?)(?=")' Cargo.toml)"
else
@@ -71,7 +74,7 @@ jobs:
# Only install the clippy and rustfmt components on the default rust-toolchain
- name: "Install rust-toolchain version"
uses: dtolnay/rust-toolchain@fc3253060d0c959bea12a59f10f8391454a0b02d # master @ 2023-03-21 - 06:36 GMT+1
uses: dtolnay/rust-toolchain@439cf607258077187679211f12aa6f19af4a0af7 # master @ 2023-09-19 - 05:31 PM GMT+2
if: ${{ matrix.channel == 'rust-toolchain' }}
with:
toolchain: "${{steps.toolchain.outputs.RUST_TOOLCHAIN}}"
@@ -81,17 +84,19 @@ jobs:
# Install the any other channel to be used for which we do not execute clippy and rustfmt
- name: "Install MSRV version"
uses: dtolnay/rust-toolchain@fc3253060d0c959bea12a59f10f8391454a0b02d # master @ 2023-03-21 - 06:36 GMT+1
uses: dtolnay/rust-toolchain@439cf607258077187679211f12aa6f19af4a0af7 # master @ 2023-09-19 - 05:31 PM GMT+2
if: ${{ matrix.channel != 'rust-toolchain' }}
with:
toolchain: "${{steps.toolchain.outputs.RUST_TOOLCHAIN}}"
# End Install the MSRV channel to be used
# Enable Rust Caching
- uses: Swatinem/rust-cache@6fd3edff6979b79f87531400ad694fb7f2c84b1f # v2.2.1
# End Enable Rust Caching
# Set the current matrix toolchain version as default
- name: "Set toolchain ${{steps.toolchain.outputs.RUST_TOOLCHAIN}} as default"
run: |
# Remove the rust-toolchain.toml
rm rust-toolchain.toml
# Set the default
rustup default ${{steps.toolchain.outputs.RUST_TOOLCHAIN}}
# Show environment
- name: "Show environment"
@@ -100,47 +105,55 @@ jobs:
cargo -vV
# End Show environment
# Enable Rust Caching
- uses: Swatinem/rust-cache@a95ba195448af2da9b00fb742d14ffaaf3c21f43 # v2.7.0
with:
# Use a custom prefix-key to force a fresh start. This is sometimes needed with bigger changes.
# Like changing the build host from Ubuntu 20.04 to 22.04 for example.
# Only update when really needed! Use a <year>.<month>[.<inc>] format.
prefix-key: "v2023.07-rust"
# End Enable Rust Caching
# Run cargo tests (In release mode to speed up future builds)
# Run cargo tests
# First test all features together, afterwards test them separately.
- name: "test features: sqlite,mysql,postgresql,enable_mimalloc"
id: test_sqlite_mysql_postgresql_mimalloc
if: $${{ always() }}
run: |
cargo test --release --features sqlite,mysql,postgresql,enable_mimalloc
cargo test --features sqlite,mysql,postgresql,enable_mimalloc
- name: "test features: sqlite,mysql,postgresql"
id: test_sqlite_mysql_postgresql
if: $${{ always() }}
run: |
cargo test --release --features sqlite,mysql,postgresql
cargo test --features sqlite,mysql,postgresql
- name: "test features: sqlite"
id: test_sqlite
if: $${{ always() }}
run: |
cargo test --release --features sqlite
cargo test --features sqlite
- name: "test features: mysql"
id: test_mysql
if: $${{ always() }}
run: |
cargo test --release --features mysql
cargo test --features mysql
- name: "test features: postgresql"
id: test_postgresql
if: $${{ always() }}
run: |
cargo test --release --features postgresql
cargo test --features postgresql
# End Run cargo tests
# Run cargo clippy, and fail on warnings (In release mode to speed up future builds)
# Run cargo clippy, and fail on warnings
- name: "clippy features: sqlite,mysql,postgresql,enable_mimalloc"
id: clippy
if: ${{ always() && matrix.channel == 'rust-toolchain' }}
run: |
cargo clippy --release --features sqlite,mysql,postgresql,enable_mimalloc -- -D warnings
cargo clippy --features sqlite,mysql,postgresql,enable_mimalloc -- -D warnings
# End Run cargo clippy
@@ -182,21 +195,3 @@ jobs:
run: |
echo "### :tada: Checks Passed!" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
# Build the binary to upload to the artifacts
- name: "build features: sqlite,mysql,postgresql"
if: ${{ matrix.channel == 'rust-toolchain' }}
run: |
cargo build --release --features sqlite,mysql,postgresql
# End Build the binary
# Upload artifact to Github Actions
- name: "Upload artifact"
uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce # v3.1.2
if: ${{ matrix.channel == 'rust-toolchain' }}
with:
name: vaultwarden
path: target/release/vaultwarden
# End Upload artifact to Github Actions

View File

@@ -8,15 +8,14 @@ on: [
jobs:
hadolint:
name: Validate Dockerfile syntax
runs-on: ubuntu-20.04
runs-on: ubuntu-22.04
timeout-minutes: 30
steps:
# Checkout the repo
- name: Checkout
uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3 # v3.5.0
uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0
# End Checkout the repo
# Download hadolint - https://github.com/hadolint/hadolint/releases
- name: Download hadolint
shell: bash
@@ -30,5 +29,5 @@ jobs:
# Test Dockerfiles
- name: Run hadolint
shell: bash
run: git ls-files --exclude='docker/*/Dockerfile*' --ignored --cached | xargs hadolint
run: hadolint docker/Dockerfile.{debian,alpine}
# End Test Dockerfiles

View File

@@ -6,15 +6,15 @@ on:
- ".github/workflows/release.yml"
- "src/**"
- "migrations/**"
- "hooks/**"
- "docker/**"
- "Cargo.*"
- "build.rs"
- "diesel.toml"
- "rust-toolchain"
- "rust-toolchain.toml"
branches: # Only on paths above
- main
- release-build-revision
tags: # Always, regardless of paths above
- '*'
@@ -24,7 +24,7 @@ jobs:
# Some checks to determine if we need to continue with building a new docker.
# We will skip this check if we are creating a tag, because that has the same hash as a previous run already.
skip_check:
runs-on: ubuntu-20.04
runs-on: ubuntu-22.04
if: ${{ github.repository == 'dani-garcia/vaultwarden' }}
outputs:
should_skip: ${{ steps.skip_check.outputs.should_skip }}
@@ -35,23 +35,20 @@ jobs:
with:
cancel_others: 'true'
# Only run this when not creating a tag
if: ${{ startsWith(github.ref, 'refs/heads/') }}
if: ${{ github.ref_type == 'branch' }}
docker-build:
runs-on: ubuntu-20.04
runs-on: ubuntu-22.04
timeout-minutes: 120
needs: skip_check
# Start a local docker registry to be used to generate multi-arch images.
services:
registry:
image: registry:2
ports:
- 5000:5000
if: ${{ needs.skip_check.outputs.should_skip != 'true' && github.repository == 'dani-garcia/vaultwarden' }}
# TODO: Start a local docker registry to be used to extract the final Alpine static build images
# services:
# registry:
# image: registry:2
# ports:
# - 5000:5000
env:
# Use BuildKit (https://docs.docker.com/build/buildkit/) for better
# build performance and the ability to copy extended file attributes
# (e.g., for executable capabilities) across build phases.
DOCKER_BUILDKIT: 1
SOURCE_COMMIT: ${{ github.sha }}
SOURCE_REPOSITORY_URL: "https://github.com/${{ github.repository }}"
# The *_REPO variables need to be configured as repository variables
@@ -65,7 +62,6 @@ jobs:
# QUAY_REPO needs to be 'quay.io/<user>/<repo>'
# Check for Quay.io credentials in secrets
HAVE_QUAY_LOGIN: ${{ vars.QUAY_REPO != '' && secrets.QUAY_USERNAME != '' && secrets.QUAY_TOKEN != '' }}
if: ${{ needs.skip_check.outputs.should_skip != 'true' && github.repository == 'dani-garcia/vaultwarden' }}
strategy:
matrix:
base_image: ["debian","alpine"]
@@ -73,163 +69,102 @@ jobs:
steps:
# Checkout the repo
- name: Checkout
uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3 # v3.5.0
uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0
with:
fetch-depth: 0
# Determine Docker Tag
- name: Init Variables
id: vars
- name: Initialize QEMU binfmt support
uses: docker/setup-qemu-action@68827325e0b33c7199eb31dd4e31fbe9023e06e3 # v3.0.0
with:
platforms: "arm64,arm"
# Start Docker Buildx
- name: Setup Docker Buildx
uses: docker/setup-buildx-action@f95db51fddba0c2d1ec667646a06c2ce06100226 # v3.0.0
# https://github.com/moby/buildkit/issues/3969
# Also set max parallelism to 2, the default of 4 breaks GitHub Actions
with:
config-inline: |
[worker.oci]
max-parallelism = 2
driver-opts: |
network=host
# Determine Base Tags and Source Version
- name: Determine Base Tags and Source Version
shell: bash
run: |
# Check which main tag we are going to build determined by github.ref
if [[ "${{ github.ref }}" == refs/tags/* ]]; then
echo "DOCKER_TAG=${GITHUB_REF#refs/*/}" | tee -a "${GITHUB_OUTPUT}"
elif [[ "${{ github.ref }}" == refs/heads/* ]]; then
echo "DOCKER_TAG=testing" | tee -a "${GITHUB_OUTPUT}"
# Check which main tag we are going to build determined by github.ref_type
if [[ "${{ github.ref_type }}" == "tag" ]]; then
echo "BASE_TAGS=latest,${GITHUB_REF#refs/*/}" | tee -a "${GITHUB_ENV}"
elif [[ "${{ github.ref_type }}" == "branch" ]]; then
echo "BASE_TAGS=testing" | tee -a "${GITHUB_ENV}"
fi
# End Determine Docker Tag
# Get the Source Version for this release
GIT_EXACT_TAG="$(git describe --tags --abbrev=0 --exact-match 2>/dev/null || true)"
if [[ -n "${GIT_EXACT_TAG}" ]]; then
echo "SOURCE_VERSION=${GIT_EXACT_TAG}" | tee -a "${GITHUB_ENV}"
else
GIT_LAST_TAG="$(git describe --tags --abbrev=0)"
echo "SOURCE_VERSION=${GIT_LAST_TAG}-${SOURCE_COMMIT:0:8}" | tee -a "${GITHUB_ENV}"
fi
# End Determine Base Tags
# Login to Docker Hub
- name: Login to Docker Hub
uses: docker/login-action@f4ef78c080cd8ba55a85445d5b36e214a81df20a # v2.1.0
uses: docker/login-action@343f7c4344506bcbf9b4de18042ae17996df046d # v3.0.0
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
if: ${{ env.HAVE_DOCKERHUB_LOGIN == 'true' }}
- name: Add registry for DockerHub
if: ${{ env.HAVE_DOCKERHUB_LOGIN == 'true' }}
shell: bash
run: |
echo "CONTAINER_REGISTRIES=${{ vars.DOCKERHUB_REPO }}" | tee -a "${GITHUB_ENV}"
# Login to GitHub Container Registry
- name: Login to GitHub Container Registry
uses: docker/login-action@f4ef78c080cd8ba55a85445d5b36e214a81df20a # v2.1.0
uses: docker/login-action@343f7c4344506bcbf9b4de18042ae17996df046d # v3.0.0
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
if: ${{ env.HAVE_GHCR_LOGIN == 'true' }}
- name: Add registry for ghcr.io
if: ${{ env.HAVE_GHCR_LOGIN == 'true' }}
shell: bash
run: |
echo "CONTAINER_REGISTRIES=${CONTAINER_REGISTRIES:+${CONTAINER_REGISTRIES},}${{ vars.GHCR_REPO }}" | tee -a "${GITHUB_ENV}"
# Login to Quay.io
- name: Login to Quay.io
uses: docker/login-action@f4ef78c080cd8ba55a85445d5b36e214a81df20a # v2.1.0
uses: docker/login-action@343f7c4344506bcbf9b4de18042ae17996df046d # v3.0.0
with:
registry: quay.io
username: ${{ secrets.QUAY_USERNAME }}
password: ${{ secrets.QUAY_TOKEN }}
if: ${{ env.HAVE_QUAY_LOGIN == 'true' }}
# Debian
# Docker Hub
- name: Build Debian based images (docker.io)
- name: Add registry for Quay.io
if: ${{ env.HAVE_QUAY_LOGIN == 'true' }}
shell: bash
env:
DOCKER_REPO: "${{ vars.DOCKERHUB_REPO }}"
DOCKER_TAG: "${{steps.vars.outputs.DOCKER_TAG}}"
run: |
./hooks/build
if: ${{ matrix.base_image == 'debian' && env.HAVE_DOCKERHUB_LOGIN == 'true' }}
echo "CONTAINER_REGISTRIES=${CONTAINER_REGISTRIES:+${CONTAINER_REGISTRIES},}${{ vars.QUAY_REPO }}" | tee -a "${GITHUB_ENV}"
- name: Push Debian based images (docker.io)
shell: bash
- name: Bake ${{ matrix.base_image }} containers
uses: docker/bake-action@511fde2517761e303af548ec9e0ea74a8a100112 # v4.0.0
env:
DOCKER_REPO: "${{ vars.DOCKERHUB_REPO }}"
DOCKER_TAG: "${{steps.vars.outputs.DOCKER_TAG}}"
run: |
./hooks/push
if: ${{ matrix.base_image == 'debian' && env.HAVE_DOCKERHUB_LOGIN == 'true' }}
# GitHub Container Registry
- name: Build Debian based images (ghcr.io)
shell: bash
env:
DOCKER_REPO: "${{ vars.GHCR_REPO }}"
DOCKER_TAG: "${{steps.vars.outputs.DOCKER_TAG}}"
run: |
./hooks/build
if: ${{ matrix.base_image == 'debian' && env.HAVE_GHCR_LOGIN == 'true' }}
- name: Push Debian based images (ghcr.io)
shell: bash
env:
DOCKER_REPO: "${{ vars.GHCR_REPO }}"
DOCKER_TAG: "${{steps.vars.outputs.DOCKER_TAG}}"
run: |
./hooks/push
if: ${{ matrix.base_image == 'debian' && env.HAVE_GHCR_LOGIN == 'true' }}
# Quay.io
- name: Build Debian based images (quay.io)
shell: bash
env:
DOCKER_REPO: "${{ vars.QUAY_REPO }}"
DOCKER_TAG: "${{steps.vars.outputs.DOCKER_TAG}}"
run: |
./hooks/build
if: ${{ matrix.base_image == 'debian' && env.HAVE_QUAY_LOGIN == 'true' }}
- name: Push Debian based images (quay.io)
shell: bash
env:
DOCKER_REPO: "${{ vars.QUAY_REPO }}"
DOCKER_TAG: "${{steps.vars.outputs.DOCKER_TAG}}"
run: |
./hooks/push
if: ${{ matrix.base_image == 'debian' && env.HAVE_QUAY_LOGIN == 'true' }}
# Alpine
# Docker Hub
- name: Build Alpine based images (docker.io)
shell: bash
env:
DOCKER_REPO: "${{ vars.DOCKERHUB_REPO }}"
DOCKER_TAG: "${{steps.vars.outputs.DOCKER_TAG}}-alpine"
run: |
./hooks/build
if: ${{ matrix.base_image == 'alpine' && env.HAVE_DOCKERHUB_LOGIN == 'true' }}
- name: Push Alpine based images (docker.io)
shell: bash
env:
DOCKER_REPO: "${{ vars.DOCKERHUB_REPO }}"
DOCKER_TAG: "${{steps.vars.outputs.DOCKER_TAG}}-alpine"
run: |
./hooks/push
if: ${{ matrix.base_image == 'alpine' && env.HAVE_DOCKERHUB_LOGIN == 'true' }}
# GitHub Container Registry
- name: Build Alpine based images (ghcr.io)
shell: bash
env:
DOCKER_REPO: "${{ vars.GHCR_REPO }}"
DOCKER_TAG: "${{steps.vars.outputs.DOCKER_TAG}}-alpine"
run: |
./hooks/build
if: ${{ matrix.base_image == 'alpine' && env.HAVE_GHCR_LOGIN == 'true' }}
- name: Push Alpine based images (ghcr.io)
shell: bash
env:
DOCKER_REPO: "${{ vars.GHCR_REPO }}"
DOCKER_TAG: "${{steps.vars.outputs.DOCKER_TAG}}-alpine"
run: |
./hooks/push
if: ${{ matrix.base_image == 'alpine' && env.HAVE_GHCR_LOGIN == 'true' }}
# Quay.io
- name: Build Alpine based images (quay.io)
shell: bash
env:
DOCKER_REPO: "${{ vars.QUAY_REPO }}"
DOCKER_TAG: "${{steps.vars.outputs.DOCKER_TAG}}-alpine"
run: |
./hooks/build
if: ${{ matrix.base_image == 'alpine' && env.HAVE_QUAY_LOGIN == 'true' }}
- name: Push Alpine based images (quay.io)
shell: bash
env:
DOCKER_REPO: "${{ vars.QUAY_REPO }}"
DOCKER_TAG: "${{steps.vars.outputs.DOCKER_TAG}}-alpine"
run: |
./hooks/push
if: ${{ matrix.base_image == 'alpine' && env.HAVE_QUAY_LOGIN == 'true' }}
BASE_TAGS: "${{ env.BASE_TAGS }}"
SOURCE_COMMIT: "${{ env.SOURCE_COMMIT }}"
SOURCE_VERSION: "${{ env.SOURCE_VERSION }}"
SOURCE_REPOSITORY_URL: "${{ env.SOURCE_REPOSITORY_URL }}"
CONTAINER_REGISTRIES: "${{ env.CONTAINER_REGISTRIES }}"
with:
pull: true
push: true
files: docker/docker-bake.hcl
targets: "${{ matrix.base_image }}-multi"

43
.github/workflows/trivy.yml vendored Normal file
View File

@@ -0,0 +1,43 @@
name: trivy
on:
push:
branches:
- main
- release-build-revision
tags:
- '*'
pull_request:
branches: [ "main" ]
schedule:
- cron: '00 12 * * *'
permissions:
contents: read
jobs:
trivy-scan:
name: Check
runs-on: ubuntu-22.04
timeout-minutes: 30
permissions:
contents: read
security-events: write
actions: read
steps:
- name: Checkout code
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 #v4.1.1
- name: Run Trivy vulnerability scanner
uses: aquasecurity/trivy-action@f78e9ecf42a1271402d4f484518b9313235990e1 # v0.13.1
with:
scan-type: repo
ignore-unfixed: true
format: sarif
output: trivy-results.sarif
severity: CRITICAL,HIGH
- name: Upload Trivy scan results to GitHub Security tab
uses: github/codeql-action/upload-sarif@bad341350a2f5616f9e048e51360cedc49181ce8 # v2.22.4
with:
sarif_file: 'trivy-results.sarif'

View File

@@ -1,9 +1,13 @@
ignored:
# To prevent issues and make clear some images only work on linux/amd64, we ignore this
- DL3029
# disable explicit version for apt install
- DL3008
# disable explicit version for apk install
- DL3018
# disable check for consecutive `RUN` instructions
- DL3059
# Ignore shellcheck info message
- SC1091
trustedRegistries:
- docker.io
- ghcr.io
- quay.io

View File

@@ -1,7 +1,7 @@
---
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.4.0
rev: v4.5.0
hooks:
- id: check-yaml
- id: check-json

1914
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -3,7 +3,7 @@ name = "vaultwarden"
version = "1.0.0"
authors = ["Daniel García <dani-garcia@users.noreply.github.com>"]
edition = "2021"
rust-version = "1.66.1"
rust-version = "1.71.1"
resolver = "2"
repository = "https://github.com/dani-garcia/vaultwarden"
@@ -36,73 +36,74 @@ unstable = []
[target."cfg(not(windows))".dependencies]
# Logging
syslog = "6.0.1" # Needs to be v4 until fern is updated
syslog = "6.1.0"
[dependencies]
# Logging
log = "0.4.17"
fern = { version = "0.6.2", features = ["syslog-6"] }
tracing = { version = "0.1.37", features = ["log"] } # Needed to have lettre and webauthn-rs trace logging to work
log = "0.4.20"
fern = { version = "0.6.2", features = ["syslog-6", "reopen-1"] }
tracing = { version = "0.1.40", features = ["log"] } # Needed to have lettre and webauthn-rs trace logging to work
# A `dotenv` implementation for Rust
dotenvy = { version = "0.15.7", default-features = false }
# Lazy initialization
once_cell = "1.17.1"
once_cell = "1.18.0"
# Numerical libraries
num-traits = "0.2.15"
num-derive = "0.3.3"
num-traits = "0.2.17"
num-derive = "0.4.1"
# Web framework
rocket = { version = "0.5.0-rc.3", features = ["tls", "json"], default-features = false }
rocket = { version = "0.5.0-rc.4", features = ["tls", "json"], default-features = false }
rocket_ws = { version ="0.1.0-rc.4" }
# WebSockets libraries
tokio-tungstenite = "0.18.0"
rmpv = "1.0.0" # MessagePack library
tokio-tungstenite = "0.20.1"
rmpv = "1.0.1" # MessagePack library
# Concurrent HashMap used for WebSocket messaging and favicons
dashmap = "5.4.0"
dashmap = "5.5.3"
# Async futures
futures = "0.3.27"
tokio = { version = "1.26.0", features = ["rt-multi-thread", "fs", "io-util", "parking_lot", "time", "signal"] }
futures = "0.3.29"
tokio = { version = "1.34.0", features = ["rt-multi-thread", "fs", "io-util", "parking_lot", "time", "signal"] }
# A generic serialization/deserialization framework
serde = { version = "1.0.158", features = ["derive"] }
serde_json = "1.0.94"
serde = { version = "1.0.192", features = ["derive"] }
serde_json = "1.0.108"
# A safe, extensible ORM and Query builder
diesel = { version = "2.0.3", features = ["chrono", "r2d2"] }
diesel_migrations = "2.0.0"
diesel_logger = { version = "0.2.0", optional = true }
diesel = { version = "2.1.4", features = ["chrono", "r2d2"] }
diesel_migrations = "2.1.0"
diesel_logger = { version = "0.3.0", optional = true }
# Bundled/Static SQLite
libsqlite3-sys = { version = "0.25.2", features = ["bundled"], optional = true }
libsqlite3-sys = { version = "0.27.0", features = ["bundled"], optional = true }
# Crypto-related libraries
rand = { version = "0.8.5", features = ["small_rng"] }
ring = "0.16.20"
ring = "0.17.5"
# UUID generation
uuid = { version = "1.3.0", features = ["v4"] }
uuid = { version = "1.5.0", features = ["v4"] }
# Date and time libraries
chrono = { version = "0.4.24", features = ["clock", "serde"], default-features = false }
chrono-tz = "0.8.1"
time = "0.3.20"
chrono = { version = "0.4.31", features = ["clock", "serde"], default-features = false }
chrono-tz = "0.8.4"
time = "0.3.30"
# Job scheduler
job_scheduler_ng = "2.0.4"
# Data encoding library Hex/Base32/Base64
data-encoding = "2.3.3"
data-encoding = "2.4.0"
# JWT library
jsonwebtoken = "8.3.0"
jsonwebtoken = "9.1.0"
# TOTP library
totp-lite = "2.0.0"
totp-lite = "2.0.1"
# Yubico Library
yubico = { version = "0.11.0", features = ["online-tokio"], default-features = false }
@@ -111,55 +112,59 @@ yubico = { version = "0.11.0", features = ["online-tokio"], default-features = f
webauthn-rs = "0.3.2"
# Handling of URL's for WebAuthn and favicons
url = "2.3.1"
url = "2.4.1"
# Email libraries
lettre = { version = "0.10.3", features = ["smtp-transport", "sendmail-transport", "builder", "serde", "tokio1-native-tls", "hostname", "tracing", "tokio1"], default-features = false }
percent-encoding = "2.2.0" # URL encoding library used for URL's in the emails
lettre = { version = "0.11.1", features = ["smtp-transport", "sendmail-transport", "builder", "serde", "tokio1-native-tls", "hostname", "tracing", "tokio1"], default-features = false }
percent-encoding = "2.3.0" # URL encoding library used for URL's in the emails
email_address = "0.2.4"
# HTML Template library
handlebars = { version = "4.3.6", features = ["dir_source"] }
handlebars = { version = "4.5.0", features = ["dir_source"] }
# HTTP client (Used for favicons, version check, DUO and HIBP API)
reqwest = { version = "0.11.15", features = ["stream", "json", "gzip", "brotli", "socks", "cookies", "trust-dns"] }
reqwest = { version = "0.11.22", features = ["stream", "json", "deflate", "gzip", "brotli", "socks", "cookies", "trust-dns", "native-tls-alpn"] }
# Favicon extraction libraries
html5gum = "0.5.2"
regex = { version = "1.7.3", features = ["std", "perf", "unicode-perl"], default-features = false }
data-url = "0.2.0"
bytes = "1.4.0"
html5gum = "0.5.7"
regex = { version = "1.10.2", features = ["std", "perf", "unicode-perl"], default-features = false }
data-url = "0.3.0"
bytes = "1.5.0"
# Cache function results (Used for version check and favicon fetching)
cached = "0.42.0"
cached = { version = "0.46.1", features = ["async"] }
# Used for custom short lived cookie jar during favicon extraction
cookie = "0.16.2"
cookie_store = "0.19.0"
cookie_store = "0.19.1"
# Used by U2F, JWT and PostgreSQL
openssl = "0.10.48"
openssl = "=0.10.57"
# Set openssl-sys fixed to v0.9.92 to prevent building issues with musl, arm and 32bit pointer width
# It will force add a dynamically linked library which prevents the build from being static
openssl-sys = "=0.9.92"
# CLI argument parsing
pico-args = "0.5.0"
# Macro ident concatenation
paste = "1.0.12"
governor = "0.5.1"
paste = "1.0.14"
governor = "0.6.0"
# Check client versions for specific features.
semver = "1.0.17"
semver = "1.0.20"
# Allow overriding the default memory allocator
# Mainly used for the musl builds, since the default musl malloc is very slow
mimalloc = { version = "0.1.34", features = ["secure"], default-features = false, optional = true }
which = "4.4.0"
mimalloc = { version = "0.1.39", features = ["secure"], default-features = false, optional = true }
which = "5.0.0"
# Argon2 library with support for the PHC format
argon2 = "0.5.0"
argon2 = "0.5.2"
# Reading a password from the cli for generating the Argon2id ADMIN_TOKEN
rpassword = "7.2.0"
rpassword = "7.3.1"
# Strip debuginfo from the release builds
# Also enable thin LTO for some optimizations
@@ -167,11 +172,12 @@ rpassword = "7.2.0"
strip = "debuginfo"
lto = "thin"
# Always build argon2 using opt-level 3
# This is a huge speed improvement during testing
[profile.dev.package.argon2]
opt-level = 3
# A little bit of a speedup
[profile.dev]
split-debuginfo = "unpacked"
# Always build argon2 using opt-level 3
# This is a huge speed improvement during testing
[profile.dev.package.argon2]
opt-level = 3

View File

@@ -1 +1 @@
docker/amd64/Dockerfile
docker/Dockerfile.debian

View File

@@ -38,11 +38,11 @@ Pull the docker image and mount a volume from the host for persistent storage:
```sh
docker pull vaultwarden/server:latest
docker run -d --name vaultwarden -v /vw-data/:/data/ -p 80:80 vaultwarden/server:latest
docker run -d --name vaultwarden -v /vw-data/:/data/ --restart unless-stopped -p 80:80 vaultwarden/server:latest
```
This will preserve any persistent data under /vw-data/, you can adapt the path to whatever suits you.
**IMPORTANT**: Most modern web browsers, disallow the use of Web Crypto APIs in insecure contexts. In this case, you might get an error like `Cannot read property 'importKey'`. To solve this problem, you need to access the web vault via HTTPS or localhost.
**IMPORTANT**: Most modern web browsers disallow the use of Web Crypto APIs in insecure contexts. In this case, you might get an error like `Cannot read property 'importKey'`. To solve this problem, you need to access the web vault via HTTPS or localhost.
This can be configured in [vaultwarden directly](https://github.com/dani-garcia/vaultwarden/wiki/Enabling-HTTPS) or using a third-party reverse proxy ([some examples](https://github.com/dani-garcia/vaultwarden/wiki/Proxy-examples)).

View File

@@ -18,7 +18,7 @@ fn main() {
);
#[cfg(all(not(debug_assertions), feature = "query_logger"))]
compile_error!("Query Logging is only allowed during development, it is not intented for production usage!");
compile_error!("Query Logging is only allowed during development, it is not intended for production usage!");
// Support $BWRS_VERSION for legacy compatibility, but default to $VW_VERSION.
// If neither exist, read from git.
@@ -72,7 +72,7 @@ fn version_from_git_info() -> Result<String, std::io::Error> {
// Combined version
if let Some(exact) = exact_tag {
Ok(exact)
} else if &branch != "main" && &branch != "master" {
} else if &branch != "main" && &branch != "master" && &branch != "HEAD" {
Ok(format!("{last_tag}-{rev_short} ({branch})"))
} else {
Ok(format!("{last_tag}-{rev_short}"))

View File

@@ -0,0 +1,28 @@
---
vault_version: "v2023.10.0"
vault_image_digest: "sha256:419e4976921f98f1124f296ed02e68bf7f8ff29b3f1fba59e7e715228a065935"
# Cross Compile Docker Helper Scripts v1.3.0
# We use the linux/amd64 platform shell scripts since there is no difference between the different platform scripts
xx_image_digest: "sha256:c9609ace652bbe51dd4ce90e0af9d48a4590f1214246da5bc70e46f6dd586edc"
rust_version: 1.73.0 # Rust version to be used
debian_version: bookworm # Debian release name to be used
alpine_version: 3.18 # Alpine version to be used
# For which platforms/architectures will we try to build images
platforms: ["linux/amd64", "linux/arm64", "linux/arm/v7", "linux/arm/v6"]
# Determine the build images per OS/Arch
build_stage_image:
debian:
image: "docker.io/library/rust:{{rust_version}}-slim-{{debian_version}}"
platform: "$BUILDPLATFORM"
alpine:
image: "build_${TARGETARCH}${TARGETVARIANT}"
platform: "linux/amd64" # The Alpine build images only have linux/amd64 images
arch_image:
amd64: "ghcr.io/blackdex/rust-musl:x86_64-musl-stable-{{rust_version}}"
arm64: "ghcr.io/blackdex/rust-musl:aarch64-musl-stable-{{rust_version}}"
armv7: "ghcr.io/blackdex/rust-musl:armv7-musleabihf-stable-{{rust_version}}"
armv6: "ghcr.io/blackdex/rust-musl:arm-musleabi-stable-{{rust_version}}"
# The final image which will be used to distribute the container images
runtime_stage_image:
debian: "docker.io/library/debian:{{debian_version}}-slim"
alpine: "docker.io/library/alpine:{{alpine_version}}"

160
docker/Dockerfile.alpine Normal file
View File

@@ -0,0 +1,160 @@
# syntax=docker/dockerfile:1
# This file was generated using a Jinja2 template.
# Please make your changes in `DockerSettings.yaml` or `Dockerfile.j2` and then `make`
# This will generate two Dockerfile's `Dockerfile.debian` and `Dockerfile.alpine`
# Using multistage build:
# https://docs.docker.com/develop/develop-images/multistage-build/
# https://whitfin.io/speeding-up-rust-docker-builds/
####################### VAULT BUILD IMAGE #######################
# The web-vault digest specifies a particular web-vault build on Docker Hub.
# Using the digest instead of the tag name provides better security,
# as the digest of an image is immutable, whereas a tag name can later
# be changed to point to a malicious image.
#
# To verify the current digest for a given tag name:
# - From https://hub.docker.com/r/vaultwarden/web-vault/tags,
# click the tag name to view the digest of the image it currently points to.
# - From the command line:
# $ docker pull docker.io/vaultwarden/web-vault:v2023.10.0
# $ docker image inspect --format "{{.RepoDigests}}" docker.io/vaultwarden/web-vault:v2023.10.0
# [docker.io/vaultwarden/web-vault@sha256:419e4976921f98f1124f296ed02e68bf7f8ff29b3f1fba59e7e715228a065935]
#
# - Conversely, to get the tag name from the digest:
# $ docker image inspect --format "{{.RepoTags}}" docker.io/vaultwarden/web-vault@sha256:419e4976921f98f1124f296ed02e68bf7f8ff29b3f1fba59e7e715228a065935
# [docker.io/vaultwarden/web-vault:v2023.10.0]
#
FROM --platform=linux/amd64 docker.io/vaultwarden/web-vault@sha256:419e4976921f98f1124f296ed02e68bf7f8ff29b3f1fba59e7e715228a065935 as vault
########################## ALPINE BUILD IMAGES ##########################
## NOTE: The Alpine Base Images do not support other platforms then linux/amd64
## And for Alpine we define all build images here, they will only be loaded when actually used
FROM --platform=linux/amd64 ghcr.io/blackdex/rust-musl:x86_64-musl-stable-1.73.0 as build_amd64
FROM --platform=linux/amd64 ghcr.io/blackdex/rust-musl:aarch64-musl-stable-1.73.0 as build_arm64
FROM --platform=linux/amd64 ghcr.io/blackdex/rust-musl:armv7-musleabihf-stable-1.73.0 as build_armv7
FROM --platform=linux/amd64 ghcr.io/blackdex/rust-musl:arm-musleabi-stable-1.73.0 as build_armv6
########################## BUILD IMAGE ##########################
# hadolint ignore=DL3006
FROM --platform=linux/amd64 build_${TARGETARCH}${TARGETVARIANT} as build
ARG TARGETARCH
ARG TARGETVARIANT
ARG TARGETPLATFORM
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
# Build time options to avoid dpkg warnings and help with reproducible builds.
ENV DEBIAN_FRONTEND=noninteractive \
LANG=C.UTF-8 \
TZ=UTC \
TERM=xterm-256color \
CARGO_HOME="/root/.cargo" \
USER="root" \
# Use PostgreSQL v15 during Alpine/MUSL builds instead of the default v11
# Debian Bookworm already contains libpq v15
PQ_LIB_DIR="/usr/local/musl/pq15/lib"
# Create CARGO_HOME folder and don't download rust docs
RUN mkdir -pv "${CARGO_HOME}" \
&& rustup set profile minimal
# Creates a dummy project used to grab dependencies
RUN USER=root cargo new --bin /app
WORKDIR /app
# Shared variables across Debian and Alpine
RUN echo "export CARGO_TARGET=${RUST_MUSL_CROSS_TARGET}" >> /env-cargo && \
# To be able to build the armv6 image with mimalloc we need to tell the linker to also look for libatomic
if [[ "${TARGETARCH}${TARGETVARIANT}" == "armv6" ]] ; then echo "export RUSTFLAGS='-Clink-arg=-latomic'" >> /env-cargo ; fi && \
# Output the current contents of the file
cat /env-cargo
# Enable MiMalloc to improve performance on Alpine builds
ARG DB=sqlite,mysql,postgresql,enable_mimalloc
RUN source /env-cargo && \
rustup target add "${CARGO_TARGET}"
ARG CARGO_PROFILE=release
ARG VW_VERSION
# Copies over *only* your manifests and build files
COPY ./Cargo.* ./
COPY ./rust-toolchain.toml ./rust-toolchain.toml
COPY ./build.rs ./build.rs
# Builds your dependencies and removes the
# dummy project, except the target folder
# This folder contains the compiled dependencies
RUN source /env-cargo && \
cargo build --features ${DB} --profile "${CARGO_PROFILE}" --target="${CARGO_TARGET}" && \
find . -not -path "./target*" -delete
# Copies the complete project
# To avoid copying unneeded files, use .dockerignore
COPY . .
# Builds again, this time it will be the actual source files being build
RUN source /env-cargo && \
# Make sure that we actually build the project by updating the src/main.rs timestamp
touch src/main.rs && \
# Create a symlink to the binary target folder to easy copy the binary in the final stage
cargo build --features ${DB} --profile "${CARGO_PROFILE}" --target="${CARGO_TARGET}" && \
if [[ "${CARGO_PROFILE}" == "dev" ]] ; then \
ln -vfsr "/app/target/${CARGO_TARGET}/debug" /app/target/final ; \
else \
ln -vfsr "/app/target/${CARGO_TARGET}/${CARGO_PROFILE}" /app/target/final ; \
fi
######################## RUNTIME IMAGE ########################
# Create a new stage with a minimal image
# because we already have a binary built
#
# To build these images you need to have qemu binfmt support.
# See the following pages to help install these tools locally
# Ubuntu/Debian: https://wiki.debian.org/QemuUserEmulation
# Arch Linux: https://wiki.archlinux.org/title/QEMU#Chrooting_into_arm/arm64_environment_from_x86_64
#
# Or use a Docker image which modifies your host system to support this.
# The GitHub Actions Workflow uses the same image as used below.
# See: https://github.com/tonistiigi/binfmt
# Usage: docker run --privileged --rm tonistiigi/binfmt --install arm64,arm
# To uninstall: docker run --privileged --rm tonistiigi/binfmt --uninstall 'qemu-*'
#
# We need to add `--platform` here, because of a podman bug: https://github.com/containers/buildah/issues/4742
FROM --platform=$TARGETPLATFORM docker.io/library/alpine:3.18
ENV ROCKET_PROFILE="release" \
ROCKET_ADDRESS=0.0.0.0 \
ROCKET_PORT=80 \
SSL_CERT_DIR=/etc/ssl/certs
# Create data folder and Install needed libraries
RUN mkdir /data && \
apk --no-cache add \
ca-certificates \
curl \
openssl \
tzdata
VOLUME /data
EXPOSE 80
EXPOSE 3012
# Copies the files from the context (Rocket.toml file and web-vault)
# and the binary from the "build" stage to the current stage
WORKDIR /
COPY docker/healthcheck.sh /healthcheck.sh
COPY docker/start.sh /start.sh
COPY --from=vault /web-vault ./web-vault
COPY --from=build /app/target/final/vaultwarden .
HEALTHCHECK --interval=60s --timeout=10s CMD ["/healthcheck.sh"]
CMD ["/start.sh"]

View File

@@ -1,34 +0,0 @@
# syntax=docker/dockerfile:1
# The cross-built images have the build arch (`amd64`) embedded in the image
# manifest, rather than the target arch. For example:
#
# $ docker inspect vaultwarden/server:latest-armv7 | jq -r '.[]|.Architecture'
# amd64
#
# Recent versions of Docker have started printing a warning when the image's
# claimed arch doesn't match the host arch. For example:
#
# WARNING: The requested image's platform (linux/amd64) does not match the
# detected host platform (linux/arm/v7) and no specific platform was requested
#
# The image still works fine, but the spurious warning creates confusion.
#
# Docker doesn't seem to provide a way to directly set the arch of an image
# at build time. To resolve the build vs. target arch discrepancy, we use
# Docker Buildx to build a new set of images with the correct target arch.
#
# Docker Buildx uses this Dockerfile to build an image for each requested
# platform. Since the Dockerfile basically consists of a single `FROM`
# instruction, we're effectively telling Buildx to build a platform-specific
# image by simply copying the existing cross-built image and setting the
# correct target arch as a side effect.
#
# References:
#
# - https://docs.docker.com/buildx/working-with-buildx/#build-multi-platform-images
# - https://docs.docker.com/engine/reference/builder/#automatic-platform-args-in-the-global-scope
# - https://docs.docker.com/engine/reference/builder/#understand-how-arg-and-from-interact
#
ARG LOCAL_REPO
ARG DOCKER_TAG
FROM ${LOCAL_REPO}:${DOCKER_TAG}-${TARGETARCH}${TARGETVARIANT}

194
docker/Dockerfile.debian Normal file
View File

@@ -0,0 +1,194 @@
# syntax=docker/dockerfile:1
# This file was generated using a Jinja2 template.
# Please make your changes in `DockerSettings.yaml` or `Dockerfile.j2` and then `make`
# This will generate two Dockerfile's `Dockerfile.debian` and `Dockerfile.alpine`
# Using multistage build:
# https://docs.docker.com/develop/develop-images/multistage-build/
# https://whitfin.io/speeding-up-rust-docker-builds/
####################### VAULT BUILD IMAGE #######################
# The web-vault digest specifies a particular web-vault build on Docker Hub.
# Using the digest instead of the tag name provides better security,
# as the digest of an image is immutable, whereas a tag name can later
# be changed to point to a malicious image.
#
# To verify the current digest for a given tag name:
# - From https://hub.docker.com/r/vaultwarden/web-vault/tags,
# click the tag name to view the digest of the image it currently points to.
# - From the command line:
# $ docker pull docker.io/vaultwarden/web-vault:v2023.10.0
# $ docker image inspect --format "{{.RepoDigests}}" docker.io/vaultwarden/web-vault:v2023.10.0
# [docker.io/vaultwarden/web-vault@sha256:419e4976921f98f1124f296ed02e68bf7f8ff29b3f1fba59e7e715228a065935]
#
# - Conversely, to get the tag name from the digest:
# $ docker image inspect --format "{{.RepoTags}}" docker.io/vaultwarden/web-vault@sha256:419e4976921f98f1124f296ed02e68bf7f8ff29b3f1fba59e7e715228a065935
# [docker.io/vaultwarden/web-vault:v2023.10.0]
#
FROM --platform=linux/amd64 docker.io/vaultwarden/web-vault@sha256:419e4976921f98f1124f296ed02e68bf7f8ff29b3f1fba59e7e715228a065935 as vault
########################## Cross Compile Docker Helper Scripts ##########################
## We use the linux/amd64 no matter which Build Platform, since these are all bash scripts
## And these bash scripts do not have any significant difference if at all
FROM --platform=linux/amd64 docker.io/tonistiigi/xx@sha256:c9609ace652bbe51dd4ce90e0af9d48a4590f1214246da5bc70e46f6dd586edc AS xx
########################## BUILD IMAGE ##########################
# hadolint ignore=DL3006
FROM --platform=$BUILDPLATFORM docker.io/library/rust:1.73.0-slim-bookworm as build
COPY --from=xx / /
ARG TARGETARCH
ARG TARGETVARIANT
ARG TARGETPLATFORM
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
# Build time options to avoid dpkg warnings and help with reproducible builds.
ENV DEBIAN_FRONTEND=noninteractive \
LANG=C.UTF-8 \
TZ=UTC \
TERM=xterm-256color \
CARGO_HOME="/root/.cargo" \
USER="root"
# Install clang to get `xx-cargo` working
# Install pkg-config to allow amd64 builds to find all libraries
# Install git so build.rs can determine the correct version
# Install the libc cross packages based upon the debian-arch
RUN apt-get update && \
apt-get install -y \
--no-install-recommends \
clang \
pkg-config \
git \
"libc6-$(xx-info debian-arch)-cross" \
"libc6-dev-$(xx-info debian-arch)-cross" \
"linux-libc-dev-$(xx-info debian-arch)-cross" && \
# Run xx-cargo early, since it sometimes seems to break when run at a later stage
echo "export CARGO_TARGET=$(xx-cargo --print-target-triple)" >> /env-cargo
RUN xx-apt-get install -y \
--no-install-recommends \
gcc \
libmariadb3 \
libpq-dev \
libpq5 \
libssl-dev && \
# Force install arch dependend mariadb dev packages
# Installing them the normal way breaks several other packages (again)
apt-get download "libmariadb-dev-compat:$(xx-info debian-arch)" "libmariadb-dev:$(xx-info debian-arch)" && \
dpkg --force-all -i ./libmariadb-dev*.deb
# Create CARGO_HOME folder and don't download rust docs
RUN mkdir -pv "${CARGO_HOME}" \
&& rustup set profile minimal
# Creates a dummy project used to grab dependencies
RUN USER=root cargo new --bin /app
WORKDIR /app
# Environment variables for cargo across Debian and Alpine
RUN source /env-cargo && \
if xx-info is-cross ; then \
# We can't use xx-cargo since that uses clang, which doesn't work for our libraries.
# Because of this we generate the needed environment variables here which we can load in the needed steps.
echo "export CC_$(echo "${CARGO_TARGET}" | tr '[:upper:]' '[:lower:]' | tr - _)=/usr/bin/$(xx-info)-gcc" >> /env-cargo && \
echo "export CARGO_TARGET_$(echo "${CARGO_TARGET}" | tr '[:lower:]' '[:upper:]' | tr - _)_LINKER=/usr/bin/$(xx-info)-gcc" >> /env-cargo && \
echo "export PKG_CONFIG=/usr/bin/$(xx-info)-pkg-config" >> /env-cargo && \
echo "export CROSS_COMPILE=1" >> /env-cargo && \
echo "export OPENSSL_INCLUDE_DIR=/usr/include/$(xx-info)" >> /env-cargo && \
echo "export OPENSSL_LIB_DIR=/usr/lib/$(xx-info)" >> /env-cargo ; \
fi && \
# Output the current contents of the file
cat /env-cargo
# Configure the DB ARG as late as possible to not invalidate the cached layers above
ARG DB=sqlite,mysql,postgresql
RUN source /env-cargo && \
rustup target add "${CARGO_TARGET}"
ARG CARGO_PROFILE=release
ARG VW_VERSION
# Copies over *only* your manifests and build files
COPY ./Cargo.* ./
COPY ./rust-toolchain.toml ./rust-toolchain.toml
COPY ./build.rs ./build.rs
# Builds your dependencies and removes the
# dummy project, except the target folder
# This folder contains the compiled dependencies
RUN source /env-cargo && \
cargo build --features ${DB} --profile "${CARGO_PROFILE}" --target="${CARGO_TARGET}" && \
find . -not -path "./target*" -delete
# Copies the complete project
# To avoid copying unneeded files, use .dockerignore
COPY . .
# Builds again, this time it will be the actual source files being build
RUN source /env-cargo && \
# Make sure that we actually build the project by updating the src/main.rs timestamp
touch src/main.rs && \
# Create a symlink to the binary target folder to easy copy the binary in the final stage
cargo build --features ${DB} --profile "${CARGO_PROFILE}" --target="${CARGO_TARGET}" && \
if [[ "${CARGO_PROFILE}" == "dev" ]] ; then \
ln -vfsr "/app/target/${CARGO_TARGET}/debug" /app/target/final ; \
else \
ln -vfsr "/app/target/${CARGO_TARGET}/${CARGO_PROFILE}" /app/target/final ; \
fi
######################## RUNTIME IMAGE ########################
# Create a new stage with a minimal image
# because we already have a binary built
#
# To build these images you need to have qemu binfmt support.
# See the following pages to help install these tools locally
# Ubuntu/Debian: https://wiki.debian.org/QemuUserEmulation
# Arch Linux: https://wiki.archlinux.org/title/QEMU#Chrooting_into_arm/arm64_environment_from_x86_64
#
# Or use a Docker image which modifies your host system to support this.
# The GitHub Actions Workflow uses the same image as used below.
# See: https://github.com/tonistiigi/binfmt
# Usage: docker run --privileged --rm tonistiigi/binfmt --install arm64,arm
# To uninstall: docker run --privileged --rm tonistiigi/binfmt --uninstall 'qemu-*'
#
# We need to add `--platform` here, because of a podman bug: https://github.com/containers/buildah/issues/4742
FROM --platform=$TARGETPLATFORM docker.io/library/debian:bookworm-slim
ENV ROCKET_PROFILE="release" \
ROCKET_ADDRESS=0.0.0.0 \
ROCKET_PORT=80 \
DEBIAN_FRONTEND=noninteractive
# Create data folder and Install needed libraries
RUN mkdir /data && \
apt-get update && apt-get install -y \
--no-install-recommends \
ca-certificates \
curl \
libmariadb-dev-compat \
libpq5 \
openssl && \
apt-get clean && \
rm -rf /var/lib/apt/lists/*
VOLUME /data
EXPOSE 80
EXPOSE 3012
# Copies the files from the context (Rocket.toml file and web-vault)
# and the binary from the "build" stage to the current stage
WORKDIR /
COPY docker/healthcheck.sh /healthcheck.sh
COPY docker/start.sh /start.sh
COPY --from=vault /web-vault ./web-vault
COPY --from=build /app/target/final/vaultwarden .
HEALTHCHECK --interval=60s --timeout=10s CMD ["/healthcheck.sh"]
CMD ["/start.sh"]

View File

@@ -1,66 +1,14 @@
# syntax=docker/dockerfile:1
# This file was generated using a Jinja2 template.
# Please make your changes in `Dockerfile.j2` and then `make` the individual Dockerfiles.
# Please make your changes in `DockerSettings.yaml` or `Dockerfile.j2` and then `make`
# This will generate two Dockerfile's `Dockerfile.debian` and `Dockerfile.alpine`
{% set build_stage_base_image = "rust:1.68.1-bullseye" %}
{% if "alpine" in target_file %}
{% if "amd64" in target_file %}
{% set build_stage_base_image = "blackdex/rust-musl:x86_64-musl-stable-1.68.1" %}
{% set runtime_stage_base_image = "alpine:3.17" %}
{% set package_arch_target = "x86_64-unknown-linux-musl" %}
{% elif "armv7" in target_file %}
{% set build_stage_base_image = "blackdex/rust-musl:armv7-musleabihf-stable-1.68.1" %}
{% set runtime_stage_base_image = "balenalib/armv7hf-alpine:3.17" %}
{% set package_arch_target = "armv7-unknown-linux-musleabihf" %}
{% elif "armv6" in target_file %}
{% set build_stage_base_image = "blackdex/rust-musl:arm-musleabi-stable-1.68.1" %}
{% set runtime_stage_base_image = "balenalib/rpi-alpine:3.17" %}
{% set package_arch_target = "arm-unknown-linux-musleabi" %}
{% elif "arm64" in target_file %}
{% set build_stage_base_image = "blackdex/rust-musl:aarch64-musl-stable-1.68.1" %}
{% set runtime_stage_base_image = "balenalib/aarch64-alpine:3.17" %}
{% set package_arch_target = "aarch64-unknown-linux-musl" %}
{% endif %}
{% elif "amd64" in target_file %}
{% set runtime_stage_base_image = "debian:bullseye-slim" %}
{% elif "arm64" in target_file %}
{% set runtime_stage_base_image = "balenalib/aarch64-debian:bullseye" %}
{% set package_arch_name = "arm64" %}
{% set package_arch_target = "aarch64-unknown-linux-gnu" %}
{% set package_cross_compiler = "aarch64-linux-gnu" %}
{% elif "armv6" in target_file %}
{% set runtime_stage_base_image = "balenalib/rpi-debian:bullseye" %}
{% set package_arch_name = "armel" %}
{% set package_arch_target = "arm-unknown-linux-gnueabi" %}
{% set package_cross_compiler = "arm-linux-gnueabi" %}
{% elif "armv7" in target_file %}
{% set runtime_stage_base_image = "balenalib/armv7hf-debian:bullseye" %}
{% set package_arch_name = "armhf" %}
{% set package_arch_target = "armv7-unknown-linux-gnueabihf" %}
{% set package_cross_compiler = "arm-linux-gnueabihf" %}
{% endif %}
{% if package_arch_name is defined %}
{% set package_arch_prefix = ":" + package_arch_name %}
{% else %}
{% set package_arch_prefix = "" %}
{% endif %}
{% if package_arch_target is defined %}
{% set package_arch_target_param = " --target=" + package_arch_target %}
{% else %}
{% set package_arch_target_param = "" %}
{% endif %}
{% if "buildkit" in target_file %}
{% set mount_rust_cache = "--mount=type=cache,target=/root/.cargo/git --mount=type=cache,target=/root/.cargo/registry " %}
{% else %}
{% set mount_rust_cache = "" %}
{% endif %}
# Using multistage build:
# https://docs.docker.com/develop/develop-images/multistage-build/
# https://whitfin.io/speeding-up-rust-docker-builds/
####################### VAULT BUILD IMAGE #######################
{% set vault_version = "v2023.3.0b" %}
{% set vault_image_digest = "sha256:aa6ba791911a815ea570ec2ddc59992481c6ba8fbb65eed4f7074b463430d3ee" %}
####################### VAULT BUILD IMAGE #######################
# The web-vault digest specifies a particular web-vault build on Docker Hub.
# Using the digest instead of the tag name provides better security,
# as the digest of an image is immutable, whereas a tag name can later
@@ -70,18 +18,41 @@
# - From https://hub.docker.com/r/vaultwarden/web-vault/tags,
# click the tag name to view the digest of the image it currently points to.
# - From the command line:
# $ docker pull vaultwarden/web-vault:{{ vault_version }}
# $ docker image inspect --format "{{ '{{' }}.RepoDigests}}" vaultwarden/web-vault:{{ vault_version }}
# [vaultwarden/web-vault@{{ vault_image_digest }}]
# $ docker pull docker.io/vaultwarden/web-vault:{{ vault_version }}
# $ docker image inspect --format "{{ '{{' }}.RepoDigests}}" docker.io/vaultwarden/web-vault:{{ vault_version }}
# [docker.io/vaultwarden/web-vault@{{ vault_image_digest }}]
#
# - Conversely, to get the tag name from the digest:
# $ docker image inspect --format "{{ '{{' }}.RepoTags}}" vaultwarden/web-vault@{{ vault_image_digest }}
# [vaultwarden/web-vault:{{ vault_version }}]
# $ docker image inspect --format "{{ '{{' }}.RepoTags}}" docker.io/vaultwarden/web-vault@{{ vault_image_digest }}
# [docker.io/vaultwarden/web-vault:{{ vault_version }}]
#
FROM vaultwarden/web-vault@{{ vault_image_digest }} as vault
FROM --platform=linux/amd64 docker.io/vaultwarden/web-vault@{{ vault_image_digest }} as vault
########################## BUILD IMAGE ##########################
FROM {{ build_stage_base_image }} as build
{% if base == "debian" %}
########################## Cross Compile Docker Helper Scripts ##########################
## We use the linux/amd64 no matter which Build Platform, since these are all bash scripts
## And these bash scripts do not have any significant difference if at all
FROM --platform=linux/amd64 docker.io/tonistiigi/xx@{{ xx_image_digest }} AS xx
{% elif base == "alpine" %}
########################## ALPINE BUILD IMAGES ##########################
## NOTE: The Alpine Base Images do not support other platforms then linux/amd64
## And for Alpine we define all build images here, they will only be loaded when actually used
{% for arch in build_stage_image[base].arch_image %}
FROM --platform={{ build_stage_image[base].platform }} {{ build_stage_image[base].arch_image[arch] }} as build_{{ arch }}
{% endfor %}
{% endif %}
########################## BUILD IMAGE ##########################
# hadolint ignore=DL3006
FROM --platform={{ build_stage_image[base].platform }} {{ build_stage_image[base].image }} as build
{% if base == "debian" %}
COPY --from=xx / /
{% endif %}
ARG TARGETARCH
ARG TARGETVARIANT
ARG TARGETPLATFORM
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
# Build time options to avoid dpkg warnings and help with reproducible builds.
ENV DEBIAN_FRONTEND=noninteractive \
@@ -90,148 +61,161 @@ ENV DEBIAN_FRONTEND=noninteractive \
TERM=xterm-256color \
CARGO_HOME="/root/.cargo" \
USER="root"
{%- if base == "alpine" %} \
# Use PostgreSQL v15 during Alpine/MUSL builds instead of the default v11
# Debian Bookworm already contains libpq v15
PQ_LIB_DIR="/usr/local/musl/pq15/lib"
{% endif %}
{% if base == "debian" %}
# Install clang to get `xx-cargo` working
# Install pkg-config to allow amd64 builds to find all libraries
# Install git so build.rs can determine the correct version
# Install the libc cross packages based upon the debian-arch
RUN apt-get update && \
apt-get install -y \
--no-install-recommends \
clang \
pkg-config \
git \
"libc6-$(xx-info debian-arch)-cross" \
"libc6-dev-$(xx-info debian-arch)-cross" \
"linux-libc-dev-$(xx-info debian-arch)-cross" && \
# Run xx-cargo early, since it sometimes seems to break when run at a later stage
echo "export CARGO_TARGET=$(xx-cargo --print-target-triple)" >> /env-cargo
RUN xx-apt-get install -y \
--no-install-recommends \
gcc \
libmariadb3 \
libpq-dev \
libpq5 \
libssl-dev && \
# Force install arch dependend mariadb dev packages
# Installing them the normal way breaks several other packages (again)
apt-get download "libmariadb-dev-compat:$(xx-info debian-arch)" "libmariadb-dev:$(xx-info debian-arch)" && \
dpkg --force-all -i ./libmariadb-dev*.deb
{% endif %}
# Create CARGO_HOME folder and don't download rust docs
RUN {{ mount_rust_cache -}} mkdir -pv "${CARGO_HOME}" \
RUN mkdir -pv "${CARGO_HOME}" \
&& rustup set profile minimal
{% if "alpine" in target_file %}
{% if "armv6" in target_file %}
# To be able to build the armv6 image with mimalloc we need to specifically specify the libatomic.a file location
ENV RUSTFLAGS='-Clink-arg=/usr/local/musl/{{ package_arch_target }}/lib/libatomic.a'
{% endif %}
{% elif "arm" in target_file %}
# Install build dependencies for the {{ package_arch_name }} architecture
RUN dpkg --add-architecture {{ package_arch_name }} \
&& apt-get update \
&& apt-get install -y \
--no-install-recommends \
gcc-{{ package_cross_compiler }} \
libc6-dev{{ package_arch_prefix }} \
libcap2-bin \
libmariadb-dev{{ package_arch_prefix }} \
libmariadb-dev-compat{{ package_arch_prefix }} \
libmariadb3{{ package_arch_prefix }} \
libpq-dev{{ package_arch_prefix }} \
libpq5{{ package_arch_prefix }} \
libssl-dev{{ package_arch_prefix }} \
#
# Make sure cargo has the right target config
&& echo '[target.{{ package_arch_target }}]' >> "${CARGO_HOME}/config" \
&& echo 'linker = "{{ package_cross_compiler }}-gcc"' >> "${CARGO_HOME}/config" \
&& echo 'rustflags = ["-L/usr/lib/{{ package_cross_compiler }}"]' >> "${CARGO_HOME}/config"
# Set arm specific environment values
ENV CC_{{ package_arch_target | replace("-", "_") }}="/usr/bin/{{ package_cross_compiler }}-gcc" \
CROSS_COMPILE="1" \
OPENSSL_INCLUDE_DIR="/usr/include/{{ package_cross_compiler }}" \
OPENSSL_LIB_DIR="/usr/lib/{{ package_cross_compiler }}"
{% elif "amd64" in target_file %}
# Install build dependencies
RUN apt-get update \
&& apt-get install -y \
--no-install-recommends \
libcap2-bin \
libmariadb-dev \
libpq-dev
{% endif %}
# Creates a dummy project used to grab dependencies
RUN USER=root cargo new --bin /app
WORKDIR /app
# Copies over *only* your manifests and build files
COPY ./Cargo.* ./
COPY ./rust-toolchain ./rust-toolchain
COPY ./build.rs ./build.rs
{% if package_arch_target is defined %}
RUN {{ mount_rust_cache -}} rustup target add {{ package_arch_target }}
{% endif %}
{% if base == "debian" %}
# Environment variables for cargo across Debian and Alpine
RUN source /env-cargo && \
if xx-info is-cross ; then \
# We can't use xx-cargo since that uses clang, which doesn't work for our libraries.
# Because of this we generate the needed environment variables here which we can load in the needed steps.
echo "export CC_$(echo "${CARGO_TARGET}" | tr '[:upper:]' '[:lower:]' | tr - _)=/usr/bin/$(xx-info)-gcc" >> /env-cargo && \
echo "export CARGO_TARGET_$(echo "${CARGO_TARGET}" | tr '[:lower:]' '[:upper:]' | tr - _)_LINKER=/usr/bin/$(xx-info)-gcc" >> /env-cargo && \
echo "export PKG_CONFIG=/usr/bin/$(xx-info)-pkg-config" >> /env-cargo && \
echo "export CROSS_COMPILE=1" >> /env-cargo && \
echo "export OPENSSL_INCLUDE_DIR=/usr/include/$(xx-info)" >> /env-cargo && \
echo "export OPENSSL_LIB_DIR=/usr/lib/$(xx-info)" >> /env-cargo ; \
fi && \
# Output the current contents of the file
cat /env-cargo
# Configure the DB ARG as late as possible to not invalidate the cached layers above
{% if "alpine" in target_file %}
ARG DB=sqlite,mysql,postgresql
{% elif base == "alpine" %}
# Shared variables across Debian and Alpine
RUN echo "export CARGO_TARGET=${RUST_MUSL_CROSS_TARGET}" >> /env-cargo && \
# To be able to build the armv6 image with mimalloc we need to tell the linker to also look for libatomic
if [[ "${TARGETARCH}${TARGETVARIANT}" == "armv6" ]] ; then echo "export RUSTFLAGS='-Clink-arg=-latomic'" >> /env-cargo ; fi && \
# Output the current contents of the file
cat /env-cargo
# Enable MiMalloc to improve performance on Alpine builds
ARG DB=sqlite,mysql,postgresql,enable_mimalloc
{% else %}
ARG DB=sqlite,mysql,postgresql
{% endif %}
RUN source /env-cargo && \
rustup target add "${CARGO_TARGET}"
ARG CARGO_PROFILE=release
ARG VW_VERSION
# Copies over *only* your manifests and build files
COPY ./Cargo.* ./
COPY ./rust-toolchain.toml ./rust-toolchain.toml
COPY ./build.rs ./build.rs
# Builds your dependencies and removes the
# dummy project, except the target folder
# This folder contains the compiled dependencies
RUN {{ mount_rust_cache -}} cargo build --features ${DB} --release{{ package_arch_target_param }} \
&& find . -not -path "./target*" -delete
RUN source /env-cargo && \
cargo build --features ${DB} --profile "${CARGO_PROFILE}" --target="${CARGO_TARGET}" && \
find . -not -path "./target*" -delete
# Copies the complete project
# To avoid copying unneeded files, use .dockerignore
COPY . .
# Make sure that we actually build the project
RUN touch src/main.rs
# Builds again, this time it will be the actual source files being build
RUN source /env-cargo && \
# Make sure that we actually build the project by updating the src/main.rs timestamp
touch src/main.rs && \
# Create a symlink to the binary target folder to easy copy the binary in the final stage
cargo build --features ${DB} --profile "${CARGO_PROFILE}" --target="${CARGO_TARGET}" && \
if [[ "${CARGO_PROFILE}" == "dev" ]] ; then \
ln -vfsr "/app/target/${CARGO_TARGET}/debug" /app/target/final ; \
else \
ln -vfsr "/app/target/${CARGO_TARGET}/${CARGO_PROFILE}" /app/target/final ; \
fi
# Builds again, this time it'll just be
# your actual source files being built
RUN {{ mount_rust_cache -}} cargo build --features ${DB} --release{{ package_arch_target_param }}
{% if "buildkit" in target_file %}
# Add the `cap_net_bind_service` capability to allow listening on
# privileged (< 1024) ports even when running as a non-root user.
# This is only done if building with BuildKit; with the legacy
# builder, the `COPY` instruction doesn't carry over capabilities.
{% if package_arch_target is defined %}
RUN setcap cap_net_bind_service=+ep target/{{ package_arch_target }}/release/vaultwarden
{% else %}
RUN setcap cap_net_bind_service=+ep target/release/vaultwarden
{% endif %}
{% endif %}
######################## RUNTIME IMAGE ########################
# Create a new stage with a minimal image
# because we already have a binary built
FROM {{ runtime_stage_base_image }}
#
# To build these images you need to have qemu binfmt support.
# See the following pages to help install these tools locally
# Ubuntu/Debian: https://wiki.debian.org/QemuUserEmulation
# Arch Linux: https://wiki.archlinux.org/title/QEMU#Chrooting_into_arm/arm64_environment_from_x86_64
#
# Or use a Docker image which modifies your host system to support this.
# The GitHub Actions Workflow uses the same image as used below.
# See: https://github.com/tonistiigi/binfmt
# Usage: docker run --privileged --rm tonistiigi/binfmt --install arm64,arm
# To uninstall: docker run --privileged --rm tonistiigi/binfmt --uninstall 'qemu-*'
#
# We need to add `--platform` here, because of a podman bug: https://github.com/containers/buildah/issues/4742
FROM --platform=$TARGETPLATFORM {{ runtime_stage_image[base] }}
ENV ROCKET_PROFILE="release" \
ROCKET_ADDRESS=0.0.0.0 \
ROCKET_PORT=80
{%- if "alpine" in runtime_stage_base_image %} \
{%- if base == "debian" %} \
DEBIAN_FRONTEND=noninteractive
{% elif base == "alpine" %} \
SSL_CERT_DIR=/etc/ssl/certs
{% endif %}
{% if "amd64" not in target_file %}
RUN [ "cross-build-start" ]
{% endif %}
# Create data folder and Install needed libraries
RUN mkdir /data \
{% if "alpine" in runtime_stage_base_image %}
&& apk add --no-cache \
RUN mkdir /data && \
{% if base == "debian" %}
apt-get update && apt-get install -y \
--no-install-recommends \
ca-certificates \
curl \
libmariadb-dev-compat \
libpq5 \
openssl && \
apt-get clean && \
rm -rf /var/lib/apt/lists/*
{% elif base == "alpine" %}
apk --no-cache add \
ca-certificates \
curl \
openssl \
tzdata
{% else %}
&& apt-get update && apt-get install -y \
--no-install-recommends \
ca-certificates \
curl \
libmariadb-dev-compat \
libpq5 \
openssl \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*
{% endif %}
{% if "armv6" in target_file and "alpine" not in target_file %}
# In the Balena Bullseye images for armv6/rpi-debian there is a missing symlink.
# This symlink was there in the buster images, and for some reason this is needed.
RUN ln -v -s /lib/ld-linux-armhf.so.3 /lib/ld-linux.so.3
{% endif -%}
{% if "amd64" not in target_file %}
RUN [ "cross-build-end" ]
{% endif %}
VOLUME /data
@@ -241,16 +225,13 @@ EXPOSE 3012
# Copies the files from the context (Rocket.toml file and web-vault)
# and the binary from the "build" stage to the current stage
WORKDIR /
COPY --from=vault /web-vault ./web-vault
{% if package_arch_target is defined %}
COPY --from=build /app/target/{{ package_arch_target }}/release/vaultwarden .
{% else %}
COPY --from=build /app/target/release/vaultwarden .
{% endif %}
COPY docker/healthcheck.sh /healthcheck.sh
COPY docker/start.sh /start.sh
COPY --from=vault /web-vault ./web-vault
COPY --from=build /app/target/final/vaultwarden .
HEALTHCHECK --interval=60s --timeout=10s CMD ["/healthcheck.sh"]
CMD ["/start.sh"]

View File

@@ -1,15 +1,4 @@
OBJECTS := $(shell find ./ -mindepth 2 -name 'Dockerfile*')
all: $(OBJECTS)
%/Dockerfile: Dockerfile.j2 render_template
./render_template "$<" "{\"target_file\":\"$@\"}" > "$@"
%/Dockerfile.alpine: Dockerfile.j2 render_template
./render_template "$<" "{\"target_file\":\"$@\"}" > "$@"
%/Dockerfile.buildkit: Dockerfile.j2 render_template
./render_template "$<" "{\"target_file\":\"$@\"}" > "$@"
%/Dockerfile.buildkit.alpine: Dockerfile.j2 render_template
./render_template "$<" "{\"target_file\":\"$@\"}" > "$@"
all:
./render_template Dockerfile.j2 '{"base": "debian"}' > Dockerfile.debian
./render_template Dockerfile.j2 '{"base": "alpine"}' > Dockerfile.alpine
.PHONY: all

View File

@@ -1,3 +1,183 @@
The arch-specific directory names follow the arch identifiers used by the Docker official images:
# Vaultwarden Container Building
https://github.com/docker-library/official-images/blob/master/README.md#architectures-other-than-amd64
To build and release new testing and stable releases of Vaultwarden we use `docker buildx bake`.<br>
This can be used locally by running the command yourself, but it is also used by GitHub Actions.
This makes it easier for us to test and maintain the different architectures we provide.<br>
We also just have two Dockerfile's one for Debian and one for Alpine based images.<br>
With just these two files we can build both Debian and Alpine images for the following platforms:
- amd64 (linux/amd64)
- arm64 (linux/arm64)
- armv7 (linux/arm/v7)
- armv6 (linux/arm/v6)
To build these containers you need to enable QEMU binfmt support to be able to run/emulate architectures which are different then your host.<br>
This ensures the container build process can run binaries from other architectures.<br>
**NOTE**: Run all the examples below from the root of the repo.<br>
## How to install QEMU binfmt support
This is different per host OS, but most support this in some way.<br>
### Ubuntu/Debian
```bash
apt install binfmt-support qemu-user-static
```
### Arch Linux (others based upon it)
```bash
pacman -S qemu-user-static qemu-user-static-binfmt
```
### Fedora
```bash
dnf install qemu-user-static
```
### Others
There also is an option to use an other docker container to provide support for this.
```bash
# To install and activate
docker run --privileged --rm tonistiigi/binfmt --install arm64,arm
# To unistall
docker run --privileged --rm tonistiigi/binfmt --uninstall 'qemu-*'
```
## Single architecture container building
You can build a container per supported architecture as long as you have QEMU binfmt support installed on your system.<br>
```bash
# Default bake triggers a Debian build using the hosts architecture
docker buildx bake --file docker/docker-bake.hcl
# Bake Debian ARM64 using a debug build
CARGO_PROFILE=dev \
SOURCE_COMMIT="$(git rev-parse HEAD)" \
docker buildx bake --file docker/docker-bake.hcl debian-arm64
# Bake Alpine ARMv6 as a release build
SOURCE_COMMIT="$(git rev-parse HEAD)" \
docker buildx bake --file docker/docker-bake.hcl alpine-armv6
```
## Local Multi Architecture container building
Start the initialization, this only needs to be done once.
```bash
# Create and use a new buildx builder instance which connects to the host network
docker buildx create --name vaultwarden --use --driver-opt network=host
# Validate it runs
docker buildx inspect --bootstrap
# Create a local container registry directly reachable on the localhost
docker run -d --name registry --network host registry:2
```
After that is done, you should be able to build and push to the local registry.<br>
Use the following command with the modified variables to bake the Alpine images.<br>
Replace `alpine` with `debian` if you want to build the debian multi arch images.
```bash
# Start a buildx bake using a debug build
CARGO_PROFILE=dev \
SOURCE_COMMIT="$(git rev-parse HEAD)" \
CONTAINER_REGISTRIES="localhost:5000/vaultwarden/server" \
docker buildx bake --file docker/docker-bake.hcl alpine-multi
```
## Using the `bake.sh` script
To make it a bit more easier to trigger a build, there also is a `bake.sh` script.<br>
This script calls `docker buildx bake` with all the right parameters and also generates the `SOURCE_COMMIT` and `SOURCE_VERSION` variables.<br>
This script can be called from both the repo root or within the docker directory.
So, if you want to build a Multi Arch Alpine container pushing to your localhost registry you can run this from within the docker directory. (Just make sure you executed the initialization steps above first)
```bash
CONTAINER_REGISTRIES="localhost:5000/vaultwarden/server" \
./bake.sh alpine-multi
```
Or if you want to just build a Debian container from the repo root, you can run this.
```bash
docker/bake.sh
```
You can append both `alpine` and `debian` with `-amd64`, `-arm64`, `-armv7` or `-armv6`, which will trigger a build for that specific platform.<br>
This will also append those values to the tag so you can see the builded container when running `docker images`.
You can also append extra arguments after the target if you want. This can be useful for example to print what bake will use.
```bash
docker/bake.sh alpine-all --print
```
### Testing baked images
To test these images you can run these images by using the correct tag and provide the platform.<br>
For example, after you have build an arm64 image via `./bake.sh debian-arm64` you can run:
```bash
docker run --rm -it \
-e DISABLE_ADMIN_TOKEN=true \
-e I_REALLY_WANT_VOLATILE_STORAGE=true \
-p8080:80 --platform=linux/arm64 \
vaultwarden/server:testing-arm64
```
## Using the `podman-bake.sh` script
To also make building easier using podman, there is a `podman-bake.sh` script.<br>
This script calls `podman buildx build` with the needed parameters and the same as `bake.sh`, it will generate some variables automatically.<br>
This script can be called from both the repo root or within the docker directory.
**NOTE:** Unlike the `bake.sh` script, this only supports a single `CONTAINER_REGISTRIES`, and a single `BASE_TAGS` value, no comma separated values. It also only supports building separate architectures, no Multi Arch containers.
To build an Alpine arm64 image with only sqlite support and mimalloc, run this:
```bash
DB="sqlite,enable_mimalloc" \
./podman-bake.sh alpine-arm64
```
Or if you want to just build a Debian container from the repo root, you can run this.
```bash
docker/podman-bake.sh
```
You can append extra arguments after the target if you want. This can be useful for example to disable cache like this.
```bash
./podman-bake.sh alpine-arm64 --no-cache
```
For the podman builds you can, just like the `bake.sh` script, also append the architecture to build for that specific platform.<br>
### Testing podman builded images
The command to start a podman built container is almost the same as for the docker/bake built containers. The images start with `localhost/`, so you need to prepend that.
```bash
podman run --rm -it \
-e DISABLE_ADMIN_TOKEN=true \
-e I_REALLY_WANT_VOLATILE_STORAGE=true \
-p8080:80 --platform=linux/arm64 \
localhost/vaultwarden/server:testing-arm64
```
## Variables supported
| Variable | default | description |
| --------------------- | ------------------ | ----------- |
| CARGO_PROFILE | null | Which cargo profile to use. `null` means what is defined in the Dockerfile |
| DB | null | Which `features` to build. `null` means what is defined in the Dockerfile |
| SOURCE_REPOSITORY_URL | null | The source repository form where this build is triggered |
| SOURCE_COMMIT | null | The commit hash of the current commit for this build |
| SOURCE_VERSION | null | The current exact tag of this commit, else the last tag and the first 8 chars of the source commit |
| BASE_TAGS | testing | Tags to be used. Can be a comma separated value like "latest,1.29.2" |
| CONTAINER_REGISTRIES | vaultwarden/server | Comma separated value of container registries. Like `ghcr.io/dani-garcia/vaultwarden,docker.io/vaultwarden/server` |
| VW_VERSION | null | To override the `SOURCE_VERSION` value. This is also used by the `build.rs` code for example |

View File

@@ -1,121 +0,0 @@
# syntax=docker/dockerfile:1
# This file was generated using a Jinja2 template.
# Please make your changes in `Dockerfile.j2` and then `make` the individual Dockerfiles.
# Using multistage build:
# https://docs.docker.com/develop/develop-images/multistage-build/
# https://whitfin.io/speeding-up-rust-docker-builds/
####################### VAULT BUILD IMAGE #######################
# The web-vault digest specifies a particular web-vault build on Docker Hub.
# Using the digest instead of the tag name provides better security,
# as the digest of an image is immutable, whereas a tag name can later
# be changed to point to a malicious image.
#
# To verify the current digest for a given tag name:
# - From https://hub.docker.com/r/vaultwarden/web-vault/tags,
# click the tag name to view the digest of the image it currently points to.
# - From the command line:
# $ docker pull vaultwarden/web-vault:v2023.3.0b
# $ docker image inspect --format "{{.RepoDigests}}" vaultwarden/web-vault:v2023.3.0b
# [vaultwarden/web-vault@sha256:aa6ba791911a815ea570ec2ddc59992481c6ba8fbb65eed4f7074b463430d3ee]
#
# - Conversely, to get the tag name from the digest:
# $ docker image inspect --format "{{.RepoTags}}" vaultwarden/web-vault@sha256:aa6ba791911a815ea570ec2ddc59992481c6ba8fbb65eed4f7074b463430d3ee
# [vaultwarden/web-vault:v2023.3.0b]
#
FROM vaultwarden/web-vault@sha256:aa6ba791911a815ea570ec2ddc59992481c6ba8fbb65eed4f7074b463430d3ee as vault
########################## BUILD IMAGE ##########################
FROM rust:1.68.1-bullseye as build
# Build time options to avoid dpkg warnings and help with reproducible builds.
ENV DEBIAN_FRONTEND=noninteractive \
LANG=C.UTF-8 \
TZ=UTC \
TERM=xterm-256color \
CARGO_HOME="/root/.cargo" \
USER="root"
# Create CARGO_HOME folder and don't download rust docs
RUN mkdir -pv "${CARGO_HOME}" \
&& rustup set profile minimal
# Install build dependencies
RUN apt-get update \
&& apt-get install -y \
--no-install-recommends \
libcap2-bin \
libmariadb-dev \
libpq-dev
# Creates a dummy project used to grab dependencies
RUN USER=root cargo new --bin /app
WORKDIR /app
# Copies over *only* your manifests and build files
COPY ./Cargo.* ./
COPY ./rust-toolchain ./rust-toolchain
COPY ./build.rs ./build.rs
# Configure the DB ARG as late as possible to not invalidate the cached layers above
ARG DB=sqlite,mysql,postgresql
# Builds your dependencies and removes the
# dummy project, except the target folder
# This folder contains the compiled dependencies
RUN cargo build --features ${DB} --release \
&& find . -not -path "./target*" -delete
# Copies the complete project
# To avoid copying unneeded files, use .dockerignore
COPY . .
# Make sure that we actually build the project
RUN touch src/main.rs
# Builds again, this time it'll just be
# your actual source files being built
RUN cargo build --features ${DB} --release
######################## RUNTIME IMAGE ########################
# Create a new stage with a minimal image
# because we already have a binary built
FROM debian:bullseye-slim
ENV ROCKET_PROFILE="release" \
ROCKET_ADDRESS=0.0.0.0 \
ROCKET_PORT=80
# Create data folder and Install needed libraries
RUN mkdir /data \
&& apt-get update && apt-get install -y \
--no-install-recommends \
ca-certificates \
curl \
libmariadb-dev-compat \
libpq5 \
openssl \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*
VOLUME /data
EXPOSE 80
EXPOSE 3012
# Copies the files from the context (Rocket.toml file and web-vault)
# and the binary from the "build" stage to the current stage
WORKDIR /
COPY --from=vault /web-vault ./web-vault
COPY --from=build /app/target/release/vaultwarden .
COPY docker/healthcheck.sh /healthcheck.sh
COPY docker/start.sh /start.sh
HEALTHCHECK --interval=60s --timeout=10s CMD ["/healthcheck.sh"]
CMD ["/start.sh"]

View File

@@ -1,114 +0,0 @@
# syntax=docker/dockerfile:1
# This file was generated using a Jinja2 template.
# Please make your changes in `Dockerfile.j2` and then `make` the individual Dockerfiles.
# Using multistage build:
# https://docs.docker.com/develop/develop-images/multistage-build/
# https://whitfin.io/speeding-up-rust-docker-builds/
####################### VAULT BUILD IMAGE #######################
# The web-vault digest specifies a particular web-vault build on Docker Hub.
# Using the digest instead of the tag name provides better security,
# as the digest of an image is immutable, whereas a tag name can later
# be changed to point to a malicious image.
#
# To verify the current digest for a given tag name:
# - From https://hub.docker.com/r/vaultwarden/web-vault/tags,
# click the tag name to view the digest of the image it currently points to.
# - From the command line:
# $ docker pull vaultwarden/web-vault:v2023.3.0b
# $ docker image inspect --format "{{.RepoDigests}}" vaultwarden/web-vault:v2023.3.0b
# [vaultwarden/web-vault@sha256:aa6ba791911a815ea570ec2ddc59992481c6ba8fbb65eed4f7074b463430d3ee]
#
# - Conversely, to get the tag name from the digest:
# $ docker image inspect --format "{{.RepoTags}}" vaultwarden/web-vault@sha256:aa6ba791911a815ea570ec2ddc59992481c6ba8fbb65eed4f7074b463430d3ee
# [vaultwarden/web-vault:v2023.3.0b]
#
FROM vaultwarden/web-vault@sha256:aa6ba791911a815ea570ec2ddc59992481c6ba8fbb65eed4f7074b463430d3ee as vault
########################## BUILD IMAGE ##########################
FROM blackdex/rust-musl:x86_64-musl-stable-1.68.1 as build
# Build time options to avoid dpkg warnings and help with reproducible builds.
ENV DEBIAN_FRONTEND=noninteractive \
LANG=C.UTF-8 \
TZ=UTC \
TERM=xterm-256color \
CARGO_HOME="/root/.cargo" \
USER="root"
# Create CARGO_HOME folder and don't download rust docs
RUN mkdir -pv "${CARGO_HOME}" \
&& rustup set profile minimal
# Creates a dummy project used to grab dependencies
RUN USER=root cargo new --bin /app
WORKDIR /app
# Copies over *only* your manifests and build files
COPY ./Cargo.* ./
COPY ./rust-toolchain ./rust-toolchain
COPY ./build.rs ./build.rs
RUN rustup target add x86_64-unknown-linux-musl
# Configure the DB ARG as late as possible to not invalidate the cached layers above
# Enable MiMalloc to improve performance on Alpine builds
ARG DB=sqlite,mysql,postgresql,enable_mimalloc
# Builds your dependencies and removes the
# dummy project, except the target folder
# This folder contains the compiled dependencies
RUN cargo build --features ${DB} --release --target=x86_64-unknown-linux-musl \
&& find . -not -path "./target*" -delete
# Copies the complete project
# To avoid copying unneeded files, use .dockerignore
COPY . .
# Make sure that we actually build the project
RUN touch src/main.rs
# Builds again, this time it'll just be
# your actual source files being built
RUN cargo build --features ${DB} --release --target=x86_64-unknown-linux-musl
######################## RUNTIME IMAGE ########################
# Create a new stage with a minimal image
# because we already have a binary built
FROM alpine:3.17
ENV ROCKET_PROFILE="release" \
ROCKET_ADDRESS=0.0.0.0 \
ROCKET_PORT=80 \
SSL_CERT_DIR=/etc/ssl/certs
# Create data folder and Install needed libraries
RUN mkdir /data \
&& apk add --no-cache \
ca-certificates \
curl \
openssl \
tzdata
VOLUME /data
EXPOSE 80
EXPOSE 3012
# Copies the files from the context (Rocket.toml file and web-vault)
# and the binary from the "build" stage to the current stage
WORKDIR /
COPY --from=vault /web-vault ./web-vault
COPY --from=build /app/target/x86_64-unknown-linux-musl/release/vaultwarden .
COPY docker/healthcheck.sh /healthcheck.sh
COPY docker/start.sh /start.sh
HEALTHCHECK --interval=60s --timeout=10s CMD ["/healthcheck.sh"]
CMD ["/start.sh"]

View File

@@ -1,126 +0,0 @@
# syntax=docker/dockerfile:1
# This file was generated using a Jinja2 template.
# Please make your changes in `Dockerfile.j2` and then `make` the individual Dockerfiles.
# Using multistage build:
# https://docs.docker.com/develop/develop-images/multistage-build/
# https://whitfin.io/speeding-up-rust-docker-builds/
####################### VAULT BUILD IMAGE #######################
# The web-vault digest specifies a particular web-vault build on Docker Hub.
# Using the digest instead of the tag name provides better security,
# as the digest of an image is immutable, whereas a tag name can later
# be changed to point to a malicious image.
#
# To verify the current digest for a given tag name:
# - From https://hub.docker.com/r/vaultwarden/web-vault/tags,
# click the tag name to view the digest of the image it currently points to.
# - From the command line:
# $ docker pull vaultwarden/web-vault:v2023.3.0b
# $ docker image inspect --format "{{.RepoDigests}}" vaultwarden/web-vault:v2023.3.0b
# [vaultwarden/web-vault@sha256:aa6ba791911a815ea570ec2ddc59992481c6ba8fbb65eed4f7074b463430d3ee]
#
# - Conversely, to get the tag name from the digest:
# $ docker image inspect --format "{{.RepoTags}}" vaultwarden/web-vault@sha256:aa6ba791911a815ea570ec2ddc59992481c6ba8fbb65eed4f7074b463430d3ee
# [vaultwarden/web-vault:v2023.3.0b]
#
FROM vaultwarden/web-vault@sha256:aa6ba791911a815ea570ec2ddc59992481c6ba8fbb65eed4f7074b463430d3ee as vault
########################## BUILD IMAGE ##########################
FROM rust:1.68.1-bullseye as build
# Build time options to avoid dpkg warnings and help with reproducible builds.
ENV DEBIAN_FRONTEND=noninteractive \
LANG=C.UTF-8 \
TZ=UTC \
TERM=xterm-256color \
CARGO_HOME="/root/.cargo" \
USER="root"
# Create CARGO_HOME folder and don't download rust docs
RUN --mount=type=cache,target=/root/.cargo/git --mount=type=cache,target=/root/.cargo/registry mkdir -pv "${CARGO_HOME}" \
&& rustup set profile minimal
# Install build dependencies
RUN apt-get update \
&& apt-get install -y \
--no-install-recommends \
libcap2-bin \
libmariadb-dev \
libpq-dev
# Creates a dummy project used to grab dependencies
RUN USER=root cargo new --bin /app
WORKDIR /app
# Copies over *only* your manifests and build files
COPY ./Cargo.* ./
COPY ./rust-toolchain ./rust-toolchain
COPY ./build.rs ./build.rs
# Configure the DB ARG as late as possible to not invalidate the cached layers above
ARG DB=sqlite,mysql,postgresql
# Builds your dependencies and removes the
# dummy project, except the target folder
# This folder contains the compiled dependencies
RUN --mount=type=cache,target=/root/.cargo/git --mount=type=cache,target=/root/.cargo/registry cargo build --features ${DB} --release \
&& find . -not -path "./target*" -delete
# Copies the complete project
# To avoid copying unneeded files, use .dockerignore
COPY . .
# Make sure that we actually build the project
RUN touch src/main.rs
# Builds again, this time it'll just be
# your actual source files being built
RUN --mount=type=cache,target=/root/.cargo/git --mount=type=cache,target=/root/.cargo/registry cargo build --features ${DB} --release
# Add the `cap_net_bind_service` capability to allow listening on
# privileged (< 1024) ports even when running as a non-root user.
# This is only done if building with BuildKit; with the legacy
# builder, the `COPY` instruction doesn't carry over capabilities.
RUN setcap cap_net_bind_service=+ep target/release/vaultwarden
######################## RUNTIME IMAGE ########################
# Create a new stage with a minimal image
# because we already have a binary built
FROM debian:bullseye-slim
ENV ROCKET_PROFILE="release" \
ROCKET_ADDRESS=0.0.0.0 \
ROCKET_PORT=80
# Create data folder and Install needed libraries
RUN mkdir /data \
&& apt-get update && apt-get install -y \
--no-install-recommends \
ca-certificates \
curl \
libmariadb-dev-compat \
libpq5 \
openssl \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*
VOLUME /data
EXPOSE 80
EXPOSE 3012
# Copies the files from the context (Rocket.toml file and web-vault)
# and the binary from the "build" stage to the current stage
WORKDIR /
COPY --from=vault /web-vault ./web-vault
COPY --from=build /app/target/release/vaultwarden .
COPY docker/healthcheck.sh /healthcheck.sh
COPY docker/start.sh /start.sh
HEALTHCHECK --interval=60s --timeout=10s CMD ["/healthcheck.sh"]
CMD ["/start.sh"]

View File

@@ -1,119 +0,0 @@
# syntax=docker/dockerfile:1
# This file was generated using a Jinja2 template.
# Please make your changes in `Dockerfile.j2` and then `make` the individual Dockerfiles.
# Using multistage build:
# https://docs.docker.com/develop/develop-images/multistage-build/
# https://whitfin.io/speeding-up-rust-docker-builds/
####################### VAULT BUILD IMAGE #######################
# The web-vault digest specifies a particular web-vault build on Docker Hub.
# Using the digest instead of the tag name provides better security,
# as the digest of an image is immutable, whereas a tag name can later
# be changed to point to a malicious image.
#
# To verify the current digest for a given tag name:
# - From https://hub.docker.com/r/vaultwarden/web-vault/tags,
# click the tag name to view the digest of the image it currently points to.
# - From the command line:
# $ docker pull vaultwarden/web-vault:v2023.3.0b
# $ docker image inspect --format "{{.RepoDigests}}" vaultwarden/web-vault:v2023.3.0b
# [vaultwarden/web-vault@sha256:aa6ba791911a815ea570ec2ddc59992481c6ba8fbb65eed4f7074b463430d3ee]
#
# - Conversely, to get the tag name from the digest:
# $ docker image inspect --format "{{.RepoTags}}" vaultwarden/web-vault@sha256:aa6ba791911a815ea570ec2ddc59992481c6ba8fbb65eed4f7074b463430d3ee
# [vaultwarden/web-vault:v2023.3.0b]
#
FROM vaultwarden/web-vault@sha256:aa6ba791911a815ea570ec2ddc59992481c6ba8fbb65eed4f7074b463430d3ee as vault
########################## BUILD IMAGE ##########################
FROM blackdex/rust-musl:x86_64-musl-stable-1.68.1 as build
# Build time options to avoid dpkg warnings and help with reproducible builds.
ENV DEBIAN_FRONTEND=noninteractive \
LANG=C.UTF-8 \
TZ=UTC \
TERM=xterm-256color \
CARGO_HOME="/root/.cargo" \
USER="root"
# Create CARGO_HOME folder and don't download rust docs
RUN --mount=type=cache,target=/root/.cargo/git --mount=type=cache,target=/root/.cargo/registry mkdir -pv "${CARGO_HOME}" \
&& rustup set profile minimal
# Creates a dummy project used to grab dependencies
RUN USER=root cargo new --bin /app
WORKDIR /app
# Copies over *only* your manifests and build files
COPY ./Cargo.* ./
COPY ./rust-toolchain ./rust-toolchain
COPY ./build.rs ./build.rs
RUN --mount=type=cache,target=/root/.cargo/git --mount=type=cache,target=/root/.cargo/registry rustup target add x86_64-unknown-linux-musl
# Configure the DB ARG as late as possible to not invalidate the cached layers above
# Enable MiMalloc to improve performance on Alpine builds
ARG DB=sqlite,mysql,postgresql,enable_mimalloc
# Builds your dependencies and removes the
# dummy project, except the target folder
# This folder contains the compiled dependencies
RUN --mount=type=cache,target=/root/.cargo/git --mount=type=cache,target=/root/.cargo/registry cargo build --features ${DB} --release --target=x86_64-unknown-linux-musl \
&& find . -not -path "./target*" -delete
# Copies the complete project
# To avoid copying unneeded files, use .dockerignore
COPY . .
# Make sure that we actually build the project
RUN touch src/main.rs
# Builds again, this time it'll just be
# your actual source files being built
RUN --mount=type=cache,target=/root/.cargo/git --mount=type=cache,target=/root/.cargo/registry cargo build --features ${DB} --release --target=x86_64-unknown-linux-musl
# Add the `cap_net_bind_service` capability to allow listening on
# privileged (< 1024) ports even when running as a non-root user.
# This is only done if building with BuildKit; with the legacy
# builder, the `COPY` instruction doesn't carry over capabilities.
RUN setcap cap_net_bind_service=+ep target/x86_64-unknown-linux-musl/release/vaultwarden
######################## RUNTIME IMAGE ########################
# Create a new stage with a minimal image
# because we already have a binary built
FROM alpine:3.17
ENV ROCKET_PROFILE="release" \
ROCKET_ADDRESS=0.0.0.0 \
ROCKET_PORT=80 \
SSL_CERT_DIR=/etc/ssl/certs
# Create data folder and Install needed libraries
RUN mkdir /data \
&& apk add --no-cache \
ca-certificates \
curl \
openssl \
tzdata
VOLUME /data
EXPOSE 80
EXPOSE 3012
# Copies the files from the context (Rocket.toml file and web-vault)
# and the binary from the "build" stage to the current stage
WORKDIR /
COPY --from=vault /web-vault ./web-vault
COPY --from=build /app/target/x86_64-unknown-linux-musl/release/vaultwarden .
COPY docker/healthcheck.sh /healthcheck.sh
COPY docker/start.sh /start.sh
HEALTHCHECK --interval=60s --timeout=10s CMD ["/healthcheck.sh"]
CMD ["/start.sh"]

View File

@@ -1,142 +0,0 @@
# syntax=docker/dockerfile:1
# This file was generated using a Jinja2 template.
# Please make your changes in `Dockerfile.j2` and then `make` the individual Dockerfiles.
# Using multistage build:
# https://docs.docker.com/develop/develop-images/multistage-build/
# https://whitfin.io/speeding-up-rust-docker-builds/
####################### VAULT BUILD IMAGE #######################
# The web-vault digest specifies a particular web-vault build on Docker Hub.
# Using the digest instead of the tag name provides better security,
# as the digest of an image is immutable, whereas a tag name can later
# be changed to point to a malicious image.
#
# To verify the current digest for a given tag name:
# - From https://hub.docker.com/r/vaultwarden/web-vault/tags,
# click the tag name to view the digest of the image it currently points to.
# - From the command line:
# $ docker pull vaultwarden/web-vault:v2023.3.0b
# $ docker image inspect --format "{{.RepoDigests}}" vaultwarden/web-vault:v2023.3.0b
# [vaultwarden/web-vault@sha256:aa6ba791911a815ea570ec2ddc59992481c6ba8fbb65eed4f7074b463430d3ee]
#
# - Conversely, to get the tag name from the digest:
# $ docker image inspect --format "{{.RepoTags}}" vaultwarden/web-vault@sha256:aa6ba791911a815ea570ec2ddc59992481c6ba8fbb65eed4f7074b463430d3ee
# [vaultwarden/web-vault:v2023.3.0b]
#
FROM vaultwarden/web-vault@sha256:aa6ba791911a815ea570ec2ddc59992481c6ba8fbb65eed4f7074b463430d3ee as vault
########################## BUILD IMAGE ##########################
FROM rust:1.68.1-bullseye as build
# Build time options to avoid dpkg warnings and help with reproducible builds.
ENV DEBIAN_FRONTEND=noninteractive \
LANG=C.UTF-8 \
TZ=UTC \
TERM=xterm-256color \
CARGO_HOME="/root/.cargo" \
USER="root"
# Create CARGO_HOME folder and don't download rust docs
RUN mkdir -pv "${CARGO_HOME}" \
&& rustup set profile minimal
# Install build dependencies for the arm64 architecture
RUN dpkg --add-architecture arm64 \
&& apt-get update \
&& apt-get install -y \
--no-install-recommends \
gcc-aarch64-linux-gnu \
libc6-dev:arm64 \
libcap2-bin \
libmariadb-dev:arm64 \
libmariadb-dev-compat:arm64 \
libmariadb3:arm64 \
libpq-dev:arm64 \
libpq5:arm64 \
libssl-dev:arm64 \
#
# Make sure cargo has the right target config
&& echo '[target.aarch64-unknown-linux-gnu]' >> "${CARGO_HOME}/config" \
&& echo 'linker = "aarch64-linux-gnu-gcc"' >> "${CARGO_HOME}/config" \
&& echo 'rustflags = ["-L/usr/lib/aarch64-linux-gnu"]' >> "${CARGO_HOME}/config"
# Set arm specific environment values
ENV CC_aarch64_unknown_linux_gnu="/usr/bin/aarch64-linux-gnu-gcc" \
CROSS_COMPILE="1" \
OPENSSL_INCLUDE_DIR="/usr/include/aarch64-linux-gnu" \
OPENSSL_LIB_DIR="/usr/lib/aarch64-linux-gnu"
# Creates a dummy project used to grab dependencies
RUN USER=root cargo new --bin /app
WORKDIR /app
# Copies over *only* your manifests and build files
COPY ./Cargo.* ./
COPY ./rust-toolchain ./rust-toolchain
COPY ./build.rs ./build.rs
RUN rustup target add aarch64-unknown-linux-gnu
# Configure the DB ARG as late as possible to not invalidate the cached layers above
ARG DB=sqlite,mysql,postgresql
# Builds your dependencies and removes the
# dummy project, except the target folder
# This folder contains the compiled dependencies
RUN cargo build --features ${DB} --release --target=aarch64-unknown-linux-gnu \
&& find . -not -path "./target*" -delete
# Copies the complete project
# To avoid copying unneeded files, use .dockerignore
COPY . .
# Make sure that we actually build the project
RUN touch src/main.rs
# Builds again, this time it'll just be
# your actual source files being built
RUN cargo build --features ${DB} --release --target=aarch64-unknown-linux-gnu
######################## RUNTIME IMAGE ########################
# Create a new stage with a minimal image
# because we already have a binary built
FROM balenalib/aarch64-debian:bullseye
ENV ROCKET_PROFILE="release" \
ROCKET_ADDRESS=0.0.0.0 \
ROCKET_PORT=80
RUN [ "cross-build-start" ]
# Create data folder and Install needed libraries
RUN mkdir /data \
&& apt-get update && apt-get install -y \
--no-install-recommends \
ca-certificates \
curl \
libmariadb-dev-compat \
libpq5 \
openssl \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*
RUN [ "cross-build-end" ]
VOLUME /data
EXPOSE 80
EXPOSE 3012
# Copies the files from the context (Rocket.toml file and web-vault)
# and the binary from the "build" stage to the current stage
WORKDIR /
COPY --from=vault /web-vault ./web-vault
COPY --from=build /app/target/aarch64-unknown-linux-gnu/release/vaultwarden .
COPY docker/healthcheck.sh /healthcheck.sh
COPY docker/start.sh /start.sh
HEALTHCHECK --interval=60s --timeout=10s CMD ["/healthcheck.sh"]
CMD ["/start.sh"]

View File

@@ -1,116 +0,0 @@
# syntax=docker/dockerfile:1
# This file was generated using a Jinja2 template.
# Please make your changes in `Dockerfile.j2` and then `make` the individual Dockerfiles.
# Using multistage build:
# https://docs.docker.com/develop/develop-images/multistage-build/
# https://whitfin.io/speeding-up-rust-docker-builds/
####################### VAULT BUILD IMAGE #######################
# The web-vault digest specifies a particular web-vault build on Docker Hub.
# Using the digest instead of the tag name provides better security,
# as the digest of an image is immutable, whereas a tag name can later
# be changed to point to a malicious image.
#
# To verify the current digest for a given tag name:
# - From https://hub.docker.com/r/vaultwarden/web-vault/tags,
# click the tag name to view the digest of the image it currently points to.
# - From the command line:
# $ docker pull vaultwarden/web-vault:v2023.3.0b
# $ docker image inspect --format "{{.RepoDigests}}" vaultwarden/web-vault:v2023.3.0b
# [vaultwarden/web-vault@sha256:aa6ba791911a815ea570ec2ddc59992481c6ba8fbb65eed4f7074b463430d3ee]
#
# - Conversely, to get the tag name from the digest:
# $ docker image inspect --format "{{.RepoTags}}" vaultwarden/web-vault@sha256:aa6ba791911a815ea570ec2ddc59992481c6ba8fbb65eed4f7074b463430d3ee
# [vaultwarden/web-vault:v2023.3.0b]
#
FROM vaultwarden/web-vault@sha256:aa6ba791911a815ea570ec2ddc59992481c6ba8fbb65eed4f7074b463430d3ee as vault
########################## BUILD IMAGE ##########################
FROM blackdex/rust-musl:aarch64-musl-stable-1.68.1 as build
# Build time options to avoid dpkg warnings and help with reproducible builds.
ENV DEBIAN_FRONTEND=noninteractive \
LANG=C.UTF-8 \
TZ=UTC \
TERM=xterm-256color \
CARGO_HOME="/root/.cargo" \
USER="root"
# Create CARGO_HOME folder and don't download rust docs
RUN mkdir -pv "${CARGO_HOME}" \
&& rustup set profile minimal
# Creates a dummy project used to grab dependencies
RUN USER=root cargo new --bin /app
WORKDIR /app
# Copies over *only* your manifests and build files
COPY ./Cargo.* ./
COPY ./rust-toolchain ./rust-toolchain
COPY ./build.rs ./build.rs
RUN rustup target add aarch64-unknown-linux-musl
# Configure the DB ARG as late as possible to not invalidate the cached layers above
# Enable MiMalloc to improve performance on Alpine builds
ARG DB=sqlite,mysql,postgresql,enable_mimalloc
# Builds your dependencies and removes the
# dummy project, except the target folder
# This folder contains the compiled dependencies
RUN cargo build --features ${DB} --release --target=aarch64-unknown-linux-musl \
&& find . -not -path "./target*" -delete
# Copies the complete project
# To avoid copying unneeded files, use .dockerignore
COPY . .
# Make sure that we actually build the project
RUN touch src/main.rs
# Builds again, this time it'll just be
# your actual source files being built
RUN cargo build --features ${DB} --release --target=aarch64-unknown-linux-musl
######################## RUNTIME IMAGE ########################
# Create a new stage with a minimal image
# because we already have a binary built
FROM balenalib/aarch64-alpine:3.17
ENV ROCKET_PROFILE="release" \
ROCKET_ADDRESS=0.0.0.0 \
ROCKET_PORT=80 \
SSL_CERT_DIR=/etc/ssl/certs
RUN [ "cross-build-start" ]
# Create data folder and Install needed libraries
RUN mkdir /data \
&& apk add --no-cache \
ca-certificates \
curl \
openssl \
tzdata
RUN [ "cross-build-end" ]
VOLUME /data
EXPOSE 80
EXPOSE 3012
# Copies the files from the context (Rocket.toml file and web-vault)
# and the binary from the "build" stage to the current stage
WORKDIR /
COPY --from=vault /web-vault ./web-vault
COPY --from=build /app/target/aarch64-unknown-linux-musl/release/vaultwarden .
COPY docker/healthcheck.sh /healthcheck.sh
COPY docker/start.sh /start.sh
HEALTHCHECK --interval=60s --timeout=10s CMD ["/healthcheck.sh"]
CMD ["/start.sh"]

View File

@@ -1,147 +0,0 @@
# syntax=docker/dockerfile:1
# This file was generated using a Jinja2 template.
# Please make your changes in `Dockerfile.j2` and then `make` the individual Dockerfiles.
# Using multistage build:
# https://docs.docker.com/develop/develop-images/multistage-build/
# https://whitfin.io/speeding-up-rust-docker-builds/
####################### VAULT BUILD IMAGE #######################
# The web-vault digest specifies a particular web-vault build on Docker Hub.
# Using the digest instead of the tag name provides better security,
# as the digest of an image is immutable, whereas a tag name can later
# be changed to point to a malicious image.
#
# To verify the current digest for a given tag name:
# - From https://hub.docker.com/r/vaultwarden/web-vault/tags,
# click the tag name to view the digest of the image it currently points to.
# - From the command line:
# $ docker pull vaultwarden/web-vault:v2023.3.0b
# $ docker image inspect --format "{{.RepoDigests}}" vaultwarden/web-vault:v2023.3.0b
# [vaultwarden/web-vault@sha256:aa6ba791911a815ea570ec2ddc59992481c6ba8fbb65eed4f7074b463430d3ee]
#
# - Conversely, to get the tag name from the digest:
# $ docker image inspect --format "{{.RepoTags}}" vaultwarden/web-vault@sha256:aa6ba791911a815ea570ec2ddc59992481c6ba8fbb65eed4f7074b463430d3ee
# [vaultwarden/web-vault:v2023.3.0b]
#
FROM vaultwarden/web-vault@sha256:aa6ba791911a815ea570ec2ddc59992481c6ba8fbb65eed4f7074b463430d3ee as vault
########################## BUILD IMAGE ##########################
FROM rust:1.68.1-bullseye as build
# Build time options to avoid dpkg warnings and help with reproducible builds.
ENV DEBIAN_FRONTEND=noninteractive \
LANG=C.UTF-8 \
TZ=UTC \
TERM=xterm-256color \
CARGO_HOME="/root/.cargo" \
USER="root"
# Create CARGO_HOME folder and don't download rust docs
RUN --mount=type=cache,target=/root/.cargo/git --mount=type=cache,target=/root/.cargo/registry mkdir -pv "${CARGO_HOME}" \
&& rustup set profile minimal
# Install build dependencies for the arm64 architecture
RUN dpkg --add-architecture arm64 \
&& apt-get update \
&& apt-get install -y \
--no-install-recommends \
gcc-aarch64-linux-gnu \
libc6-dev:arm64 \
libcap2-bin \
libmariadb-dev:arm64 \
libmariadb-dev-compat:arm64 \
libmariadb3:arm64 \
libpq-dev:arm64 \
libpq5:arm64 \
libssl-dev:arm64 \
#
# Make sure cargo has the right target config
&& echo '[target.aarch64-unknown-linux-gnu]' >> "${CARGO_HOME}/config" \
&& echo 'linker = "aarch64-linux-gnu-gcc"' >> "${CARGO_HOME}/config" \
&& echo 'rustflags = ["-L/usr/lib/aarch64-linux-gnu"]' >> "${CARGO_HOME}/config"
# Set arm specific environment values
ENV CC_aarch64_unknown_linux_gnu="/usr/bin/aarch64-linux-gnu-gcc" \
CROSS_COMPILE="1" \
OPENSSL_INCLUDE_DIR="/usr/include/aarch64-linux-gnu" \
OPENSSL_LIB_DIR="/usr/lib/aarch64-linux-gnu"
# Creates a dummy project used to grab dependencies
RUN USER=root cargo new --bin /app
WORKDIR /app
# Copies over *only* your manifests and build files
COPY ./Cargo.* ./
COPY ./rust-toolchain ./rust-toolchain
COPY ./build.rs ./build.rs
RUN --mount=type=cache,target=/root/.cargo/git --mount=type=cache,target=/root/.cargo/registry rustup target add aarch64-unknown-linux-gnu
# Configure the DB ARG as late as possible to not invalidate the cached layers above
ARG DB=sqlite,mysql,postgresql
# Builds your dependencies and removes the
# dummy project, except the target folder
# This folder contains the compiled dependencies
RUN --mount=type=cache,target=/root/.cargo/git --mount=type=cache,target=/root/.cargo/registry cargo build --features ${DB} --release --target=aarch64-unknown-linux-gnu \
&& find . -not -path "./target*" -delete
# Copies the complete project
# To avoid copying unneeded files, use .dockerignore
COPY . .
# Make sure that we actually build the project
RUN touch src/main.rs
# Builds again, this time it'll just be
# your actual source files being built
RUN --mount=type=cache,target=/root/.cargo/git --mount=type=cache,target=/root/.cargo/registry cargo build --features ${DB} --release --target=aarch64-unknown-linux-gnu
# Add the `cap_net_bind_service` capability to allow listening on
# privileged (< 1024) ports even when running as a non-root user.
# This is only done if building with BuildKit; with the legacy
# builder, the `COPY` instruction doesn't carry over capabilities.
RUN setcap cap_net_bind_service=+ep target/aarch64-unknown-linux-gnu/release/vaultwarden
######################## RUNTIME IMAGE ########################
# Create a new stage with a minimal image
# because we already have a binary built
FROM balenalib/aarch64-debian:bullseye
ENV ROCKET_PROFILE="release" \
ROCKET_ADDRESS=0.0.0.0 \
ROCKET_PORT=80
RUN [ "cross-build-start" ]
# Create data folder and Install needed libraries
RUN mkdir /data \
&& apt-get update && apt-get install -y \
--no-install-recommends \
ca-certificates \
curl \
libmariadb-dev-compat \
libpq5 \
openssl \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*
RUN [ "cross-build-end" ]
VOLUME /data
EXPOSE 80
EXPOSE 3012
# Copies the files from the context (Rocket.toml file and web-vault)
# and the binary from the "build" stage to the current stage
WORKDIR /
COPY --from=vault /web-vault ./web-vault
COPY --from=build /app/target/aarch64-unknown-linux-gnu/release/vaultwarden .
COPY docker/healthcheck.sh /healthcheck.sh
COPY docker/start.sh /start.sh
HEALTHCHECK --interval=60s --timeout=10s CMD ["/healthcheck.sh"]
CMD ["/start.sh"]

View File

@@ -1,121 +0,0 @@
# syntax=docker/dockerfile:1
# This file was generated using a Jinja2 template.
# Please make your changes in `Dockerfile.j2` and then `make` the individual Dockerfiles.
# Using multistage build:
# https://docs.docker.com/develop/develop-images/multistage-build/
# https://whitfin.io/speeding-up-rust-docker-builds/
####################### VAULT BUILD IMAGE #######################
# The web-vault digest specifies a particular web-vault build on Docker Hub.
# Using the digest instead of the tag name provides better security,
# as the digest of an image is immutable, whereas a tag name can later
# be changed to point to a malicious image.
#
# To verify the current digest for a given tag name:
# - From https://hub.docker.com/r/vaultwarden/web-vault/tags,
# click the tag name to view the digest of the image it currently points to.
# - From the command line:
# $ docker pull vaultwarden/web-vault:v2023.3.0b
# $ docker image inspect --format "{{.RepoDigests}}" vaultwarden/web-vault:v2023.3.0b
# [vaultwarden/web-vault@sha256:aa6ba791911a815ea570ec2ddc59992481c6ba8fbb65eed4f7074b463430d3ee]
#
# - Conversely, to get the tag name from the digest:
# $ docker image inspect --format "{{.RepoTags}}" vaultwarden/web-vault@sha256:aa6ba791911a815ea570ec2ddc59992481c6ba8fbb65eed4f7074b463430d3ee
# [vaultwarden/web-vault:v2023.3.0b]
#
FROM vaultwarden/web-vault@sha256:aa6ba791911a815ea570ec2ddc59992481c6ba8fbb65eed4f7074b463430d3ee as vault
########################## BUILD IMAGE ##########################
FROM blackdex/rust-musl:aarch64-musl-stable-1.68.1 as build
# Build time options to avoid dpkg warnings and help with reproducible builds.
ENV DEBIAN_FRONTEND=noninteractive \
LANG=C.UTF-8 \
TZ=UTC \
TERM=xterm-256color \
CARGO_HOME="/root/.cargo" \
USER="root"
# Create CARGO_HOME folder and don't download rust docs
RUN --mount=type=cache,target=/root/.cargo/git --mount=type=cache,target=/root/.cargo/registry mkdir -pv "${CARGO_HOME}" \
&& rustup set profile minimal
# Creates a dummy project used to grab dependencies
RUN USER=root cargo new --bin /app
WORKDIR /app
# Copies over *only* your manifests and build files
COPY ./Cargo.* ./
COPY ./rust-toolchain ./rust-toolchain
COPY ./build.rs ./build.rs
RUN --mount=type=cache,target=/root/.cargo/git --mount=type=cache,target=/root/.cargo/registry rustup target add aarch64-unknown-linux-musl
# Configure the DB ARG as late as possible to not invalidate the cached layers above
# Enable MiMalloc to improve performance on Alpine builds
ARG DB=sqlite,mysql,postgresql,enable_mimalloc
# Builds your dependencies and removes the
# dummy project, except the target folder
# This folder contains the compiled dependencies
RUN --mount=type=cache,target=/root/.cargo/git --mount=type=cache,target=/root/.cargo/registry cargo build --features ${DB} --release --target=aarch64-unknown-linux-musl \
&& find . -not -path "./target*" -delete
# Copies the complete project
# To avoid copying unneeded files, use .dockerignore
COPY . .
# Make sure that we actually build the project
RUN touch src/main.rs
# Builds again, this time it'll just be
# your actual source files being built
RUN --mount=type=cache,target=/root/.cargo/git --mount=type=cache,target=/root/.cargo/registry cargo build --features ${DB} --release --target=aarch64-unknown-linux-musl
# Add the `cap_net_bind_service` capability to allow listening on
# privileged (< 1024) ports even when running as a non-root user.
# This is only done if building with BuildKit; with the legacy
# builder, the `COPY` instruction doesn't carry over capabilities.
RUN setcap cap_net_bind_service=+ep target/aarch64-unknown-linux-musl/release/vaultwarden
######################## RUNTIME IMAGE ########################
# Create a new stage with a minimal image
# because we already have a binary built
FROM balenalib/aarch64-alpine:3.17
ENV ROCKET_PROFILE="release" \
ROCKET_ADDRESS=0.0.0.0 \
ROCKET_PORT=80 \
SSL_CERT_DIR=/etc/ssl/certs
RUN [ "cross-build-start" ]
# Create data folder and Install needed libraries
RUN mkdir /data \
&& apk add --no-cache \
ca-certificates \
curl \
openssl \
tzdata
RUN [ "cross-build-end" ]
VOLUME /data
EXPOSE 80
EXPOSE 3012
# Copies the files from the context (Rocket.toml file and web-vault)
# and the binary from the "build" stage to the current stage
WORKDIR /
COPY --from=vault /web-vault ./web-vault
COPY --from=build /app/target/aarch64-unknown-linux-musl/release/vaultwarden .
COPY docker/healthcheck.sh /healthcheck.sh
COPY docker/start.sh /start.sh
HEALTHCHECK --interval=60s --timeout=10s CMD ["/healthcheck.sh"]
CMD ["/start.sh"]

View File

@@ -1,146 +0,0 @@
# syntax=docker/dockerfile:1
# This file was generated using a Jinja2 template.
# Please make your changes in `Dockerfile.j2` and then `make` the individual Dockerfiles.
# Using multistage build:
# https://docs.docker.com/develop/develop-images/multistage-build/
# https://whitfin.io/speeding-up-rust-docker-builds/
####################### VAULT BUILD IMAGE #######################
# The web-vault digest specifies a particular web-vault build on Docker Hub.
# Using the digest instead of the tag name provides better security,
# as the digest of an image is immutable, whereas a tag name can later
# be changed to point to a malicious image.
#
# To verify the current digest for a given tag name:
# - From https://hub.docker.com/r/vaultwarden/web-vault/tags,
# click the tag name to view the digest of the image it currently points to.
# - From the command line:
# $ docker pull vaultwarden/web-vault:v2023.3.0b
# $ docker image inspect --format "{{.RepoDigests}}" vaultwarden/web-vault:v2023.3.0b
# [vaultwarden/web-vault@sha256:aa6ba791911a815ea570ec2ddc59992481c6ba8fbb65eed4f7074b463430d3ee]
#
# - Conversely, to get the tag name from the digest:
# $ docker image inspect --format "{{.RepoTags}}" vaultwarden/web-vault@sha256:aa6ba791911a815ea570ec2ddc59992481c6ba8fbb65eed4f7074b463430d3ee
# [vaultwarden/web-vault:v2023.3.0b]
#
FROM vaultwarden/web-vault@sha256:aa6ba791911a815ea570ec2ddc59992481c6ba8fbb65eed4f7074b463430d3ee as vault
########################## BUILD IMAGE ##########################
FROM rust:1.68.1-bullseye as build
# Build time options to avoid dpkg warnings and help with reproducible builds.
ENV DEBIAN_FRONTEND=noninteractive \
LANG=C.UTF-8 \
TZ=UTC \
TERM=xterm-256color \
CARGO_HOME="/root/.cargo" \
USER="root"
# Create CARGO_HOME folder and don't download rust docs
RUN mkdir -pv "${CARGO_HOME}" \
&& rustup set profile minimal
# Install build dependencies for the armel architecture
RUN dpkg --add-architecture armel \
&& apt-get update \
&& apt-get install -y \
--no-install-recommends \
gcc-arm-linux-gnueabi \
libc6-dev:armel \
libcap2-bin \
libmariadb-dev:armel \
libmariadb-dev-compat:armel \
libmariadb3:armel \
libpq-dev:armel \
libpq5:armel \
libssl-dev:armel \
#
# Make sure cargo has the right target config
&& echo '[target.arm-unknown-linux-gnueabi]' >> "${CARGO_HOME}/config" \
&& echo 'linker = "arm-linux-gnueabi-gcc"' >> "${CARGO_HOME}/config" \
&& echo 'rustflags = ["-L/usr/lib/arm-linux-gnueabi"]' >> "${CARGO_HOME}/config"
# Set arm specific environment values
ENV CC_arm_unknown_linux_gnueabi="/usr/bin/arm-linux-gnueabi-gcc" \
CROSS_COMPILE="1" \
OPENSSL_INCLUDE_DIR="/usr/include/arm-linux-gnueabi" \
OPENSSL_LIB_DIR="/usr/lib/arm-linux-gnueabi"
# Creates a dummy project used to grab dependencies
RUN USER=root cargo new --bin /app
WORKDIR /app
# Copies over *only* your manifests and build files
COPY ./Cargo.* ./
COPY ./rust-toolchain ./rust-toolchain
COPY ./build.rs ./build.rs
RUN rustup target add arm-unknown-linux-gnueabi
# Configure the DB ARG as late as possible to not invalidate the cached layers above
ARG DB=sqlite,mysql,postgresql
# Builds your dependencies and removes the
# dummy project, except the target folder
# This folder contains the compiled dependencies
RUN cargo build --features ${DB} --release --target=arm-unknown-linux-gnueabi \
&& find . -not -path "./target*" -delete
# Copies the complete project
# To avoid copying unneeded files, use .dockerignore
COPY . .
# Make sure that we actually build the project
RUN touch src/main.rs
# Builds again, this time it'll just be
# your actual source files being built
RUN cargo build --features ${DB} --release --target=arm-unknown-linux-gnueabi
######################## RUNTIME IMAGE ########################
# Create a new stage with a minimal image
# because we already have a binary built
FROM balenalib/rpi-debian:bullseye
ENV ROCKET_PROFILE="release" \
ROCKET_ADDRESS=0.0.0.0 \
ROCKET_PORT=80
RUN [ "cross-build-start" ]
# Create data folder and Install needed libraries
RUN mkdir /data \
&& apt-get update && apt-get install -y \
--no-install-recommends \
ca-certificates \
curl \
libmariadb-dev-compat \
libpq5 \
openssl \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*
# In the Balena Bullseye images for armv6/rpi-debian there is a missing symlink.
# This symlink was there in the buster images, and for some reason this is needed.
RUN ln -v -s /lib/ld-linux-armhf.so.3 /lib/ld-linux.so.3
RUN [ "cross-build-end" ]
VOLUME /data
EXPOSE 80
EXPOSE 3012
# Copies the files from the context (Rocket.toml file and web-vault)
# and the binary from the "build" stage to the current stage
WORKDIR /
COPY --from=vault /web-vault ./web-vault
COPY --from=build /app/target/arm-unknown-linux-gnueabi/release/vaultwarden .
COPY docker/healthcheck.sh /healthcheck.sh
COPY docker/start.sh /start.sh
HEALTHCHECK --interval=60s --timeout=10s CMD ["/healthcheck.sh"]
CMD ["/start.sh"]

View File

@@ -1,118 +0,0 @@
# syntax=docker/dockerfile:1
# This file was generated using a Jinja2 template.
# Please make your changes in `Dockerfile.j2` and then `make` the individual Dockerfiles.
# Using multistage build:
# https://docs.docker.com/develop/develop-images/multistage-build/
# https://whitfin.io/speeding-up-rust-docker-builds/
####################### VAULT BUILD IMAGE #######################
# The web-vault digest specifies a particular web-vault build on Docker Hub.
# Using the digest instead of the tag name provides better security,
# as the digest of an image is immutable, whereas a tag name can later
# be changed to point to a malicious image.
#
# To verify the current digest for a given tag name:
# - From https://hub.docker.com/r/vaultwarden/web-vault/tags,
# click the tag name to view the digest of the image it currently points to.
# - From the command line:
# $ docker pull vaultwarden/web-vault:v2023.3.0b
# $ docker image inspect --format "{{.RepoDigests}}" vaultwarden/web-vault:v2023.3.0b
# [vaultwarden/web-vault@sha256:aa6ba791911a815ea570ec2ddc59992481c6ba8fbb65eed4f7074b463430d3ee]
#
# - Conversely, to get the tag name from the digest:
# $ docker image inspect --format "{{.RepoTags}}" vaultwarden/web-vault@sha256:aa6ba791911a815ea570ec2ddc59992481c6ba8fbb65eed4f7074b463430d3ee
# [vaultwarden/web-vault:v2023.3.0b]
#
FROM vaultwarden/web-vault@sha256:aa6ba791911a815ea570ec2ddc59992481c6ba8fbb65eed4f7074b463430d3ee as vault
########################## BUILD IMAGE ##########################
FROM blackdex/rust-musl:arm-musleabi-stable-1.68.1 as build
# Build time options to avoid dpkg warnings and help with reproducible builds.
ENV DEBIAN_FRONTEND=noninteractive \
LANG=C.UTF-8 \
TZ=UTC \
TERM=xterm-256color \
CARGO_HOME="/root/.cargo" \
USER="root"
# Create CARGO_HOME folder and don't download rust docs
RUN mkdir -pv "${CARGO_HOME}" \
&& rustup set profile minimal
# To be able to build the armv6 image with mimalloc we need to specifically specify the libatomic.a file location
ENV RUSTFLAGS='-Clink-arg=/usr/local/musl/arm-unknown-linux-musleabi/lib/libatomic.a'
# Creates a dummy project used to grab dependencies
RUN USER=root cargo new --bin /app
WORKDIR /app
# Copies over *only* your manifests and build files
COPY ./Cargo.* ./
COPY ./rust-toolchain ./rust-toolchain
COPY ./build.rs ./build.rs
RUN rustup target add arm-unknown-linux-musleabi
# Configure the DB ARG as late as possible to not invalidate the cached layers above
# Enable MiMalloc to improve performance on Alpine builds
ARG DB=sqlite,mysql,postgresql,enable_mimalloc
# Builds your dependencies and removes the
# dummy project, except the target folder
# This folder contains the compiled dependencies
RUN cargo build --features ${DB} --release --target=arm-unknown-linux-musleabi \
&& find . -not -path "./target*" -delete
# Copies the complete project
# To avoid copying unneeded files, use .dockerignore
COPY . .
# Make sure that we actually build the project
RUN touch src/main.rs
# Builds again, this time it'll just be
# your actual source files being built
RUN cargo build --features ${DB} --release --target=arm-unknown-linux-musleabi
######################## RUNTIME IMAGE ########################
# Create a new stage with a minimal image
# because we already have a binary built
FROM balenalib/rpi-alpine:3.17
ENV ROCKET_PROFILE="release" \
ROCKET_ADDRESS=0.0.0.0 \
ROCKET_PORT=80 \
SSL_CERT_DIR=/etc/ssl/certs
RUN [ "cross-build-start" ]
# Create data folder and Install needed libraries
RUN mkdir /data \
&& apk add --no-cache \
ca-certificates \
curl \
openssl \
tzdata
RUN [ "cross-build-end" ]
VOLUME /data
EXPOSE 80
EXPOSE 3012
# Copies the files from the context (Rocket.toml file and web-vault)
# and the binary from the "build" stage to the current stage
WORKDIR /
COPY --from=vault /web-vault ./web-vault
COPY --from=build /app/target/arm-unknown-linux-musleabi/release/vaultwarden .
COPY docker/healthcheck.sh /healthcheck.sh
COPY docker/start.sh /start.sh
HEALTHCHECK --interval=60s --timeout=10s CMD ["/healthcheck.sh"]
CMD ["/start.sh"]

View File

@@ -1,151 +0,0 @@
# syntax=docker/dockerfile:1
# This file was generated using a Jinja2 template.
# Please make your changes in `Dockerfile.j2` and then `make` the individual Dockerfiles.
# Using multistage build:
# https://docs.docker.com/develop/develop-images/multistage-build/
# https://whitfin.io/speeding-up-rust-docker-builds/
####################### VAULT BUILD IMAGE #######################
# The web-vault digest specifies a particular web-vault build on Docker Hub.
# Using the digest instead of the tag name provides better security,
# as the digest of an image is immutable, whereas a tag name can later
# be changed to point to a malicious image.
#
# To verify the current digest for a given tag name:
# - From https://hub.docker.com/r/vaultwarden/web-vault/tags,
# click the tag name to view the digest of the image it currently points to.
# - From the command line:
# $ docker pull vaultwarden/web-vault:v2023.3.0b
# $ docker image inspect --format "{{.RepoDigests}}" vaultwarden/web-vault:v2023.3.0b
# [vaultwarden/web-vault@sha256:aa6ba791911a815ea570ec2ddc59992481c6ba8fbb65eed4f7074b463430d3ee]
#
# - Conversely, to get the tag name from the digest:
# $ docker image inspect --format "{{.RepoTags}}" vaultwarden/web-vault@sha256:aa6ba791911a815ea570ec2ddc59992481c6ba8fbb65eed4f7074b463430d3ee
# [vaultwarden/web-vault:v2023.3.0b]
#
FROM vaultwarden/web-vault@sha256:aa6ba791911a815ea570ec2ddc59992481c6ba8fbb65eed4f7074b463430d3ee as vault
########################## BUILD IMAGE ##########################
FROM rust:1.68.1-bullseye as build
# Build time options to avoid dpkg warnings and help with reproducible builds.
ENV DEBIAN_FRONTEND=noninteractive \
LANG=C.UTF-8 \
TZ=UTC \
TERM=xterm-256color \
CARGO_HOME="/root/.cargo" \
USER="root"
# Create CARGO_HOME folder and don't download rust docs
RUN --mount=type=cache,target=/root/.cargo/git --mount=type=cache,target=/root/.cargo/registry mkdir -pv "${CARGO_HOME}" \
&& rustup set profile minimal
# Install build dependencies for the armel architecture
RUN dpkg --add-architecture armel \
&& apt-get update \
&& apt-get install -y \
--no-install-recommends \
gcc-arm-linux-gnueabi \
libc6-dev:armel \
libcap2-bin \
libmariadb-dev:armel \
libmariadb-dev-compat:armel \
libmariadb3:armel \
libpq-dev:armel \
libpq5:armel \
libssl-dev:armel \
#
# Make sure cargo has the right target config
&& echo '[target.arm-unknown-linux-gnueabi]' >> "${CARGO_HOME}/config" \
&& echo 'linker = "arm-linux-gnueabi-gcc"' >> "${CARGO_HOME}/config" \
&& echo 'rustflags = ["-L/usr/lib/arm-linux-gnueabi"]' >> "${CARGO_HOME}/config"
# Set arm specific environment values
ENV CC_arm_unknown_linux_gnueabi="/usr/bin/arm-linux-gnueabi-gcc" \
CROSS_COMPILE="1" \
OPENSSL_INCLUDE_DIR="/usr/include/arm-linux-gnueabi" \
OPENSSL_LIB_DIR="/usr/lib/arm-linux-gnueabi"
# Creates a dummy project used to grab dependencies
RUN USER=root cargo new --bin /app
WORKDIR /app
# Copies over *only* your manifests and build files
COPY ./Cargo.* ./
COPY ./rust-toolchain ./rust-toolchain
COPY ./build.rs ./build.rs
RUN --mount=type=cache,target=/root/.cargo/git --mount=type=cache,target=/root/.cargo/registry rustup target add arm-unknown-linux-gnueabi
# Configure the DB ARG as late as possible to not invalidate the cached layers above
ARG DB=sqlite,mysql,postgresql
# Builds your dependencies and removes the
# dummy project, except the target folder
# This folder contains the compiled dependencies
RUN --mount=type=cache,target=/root/.cargo/git --mount=type=cache,target=/root/.cargo/registry cargo build --features ${DB} --release --target=arm-unknown-linux-gnueabi \
&& find . -not -path "./target*" -delete
# Copies the complete project
# To avoid copying unneeded files, use .dockerignore
COPY . .
# Make sure that we actually build the project
RUN touch src/main.rs
# Builds again, this time it'll just be
# your actual source files being built
RUN --mount=type=cache,target=/root/.cargo/git --mount=type=cache,target=/root/.cargo/registry cargo build --features ${DB} --release --target=arm-unknown-linux-gnueabi
# Add the `cap_net_bind_service` capability to allow listening on
# privileged (< 1024) ports even when running as a non-root user.
# This is only done if building with BuildKit; with the legacy
# builder, the `COPY` instruction doesn't carry over capabilities.
RUN setcap cap_net_bind_service=+ep target/arm-unknown-linux-gnueabi/release/vaultwarden
######################## RUNTIME IMAGE ########################
# Create a new stage with a minimal image
# because we already have a binary built
FROM balenalib/rpi-debian:bullseye
ENV ROCKET_PROFILE="release" \
ROCKET_ADDRESS=0.0.0.0 \
ROCKET_PORT=80
RUN [ "cross-build-start" ]
# Create data folder and Install needed libraries
RUN mkdir /data \
&& apt-get update && apt-get install -y \
--no-install-recommends \
ca-certificates \
curl \
libmariadb-dev-compat \
libpq5 \
openssl \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*
# In the Balena Bullseye images for armv6/rpi-debian there is a missing symlink.
# This symlink was there in the buster images, and for some reason this is needed.
RUN ln -v -s /lib/ld-linux-armhf.so.3 /lib/ld-linux.so.3
RUN [ "cross-build-end" ]
VOLUME /data
EXPOSE 80
EXPOSE 3012
# Copies the files from the context (Rocket.toml file and web-vault)
# and the binary from the "build" stage to the current stage
WORKDIR /
COPY --from=vault /web-vault ./web-vault
COPY --from=build /app/target/arm-unknown-linux-gnueabi/release/vaultwarden .
COPY docker/healthcheck.sh /healthcheck.sh
COPY docker/start.sh /start.sh
HEALTHCHECK --interval=60s --timeout=10s CMD ["/healthcheck.sh"]
CMD ["/start.sh"]

View File

@@ -1,123 +0,0 @@
# syntax=docker/dockerfile:1
# This file was generated using a Jinja2 template.
# Please make your changes in `Dockerfile.j2` and then `make` the individual Dockerfiles.
# Using multistage build:
# https://docs.docker.com/develop/develop-images/multistage-build/
# https://whitfin.io/speeding-up-rust-docker-builds/
####################### VAULT BUILD IMAGE #######################
# The web-vault digest specifies a particular web-vault build on Docker Hub.
# Using the digest instead of the tag name provides better security,
# as the digest of an image is immutable, whereas a tag name can later
# be changed to point to a malicious image.
#
# To verify the current digest for a given tag name:
# - From https://hub.docker.com/r/vaultwarden/web-vault/tags,
# click the tag name to view the digest of the image it currently points to.
# - From the command line:
# $ docker pull vaultwarden/web-vault:v2023.3.0b
# $ docker image inspect --format "{{.RepoDigests}}" vaultwarden/web-vault:v2023.3.0b
# [vaultwarden/web-vault@sha256:aa6ba791911a815ea570ec2ddc59992481c6ba8fbb65eed4f7074b463430d3ee]
#
# - Conversely, to get the tag name from the digest:
# $ docker image inspect --format "{{.RepoTags}}" vaultwarden/web-vault@sha256:aa6ba791911a815ea570ec2ddc59992481c6ba8fbb65eed4f7074b463430d3ee
# [vaultwarden/web-vault:v2023.3.0b]
#
FROM vaultwarden/web-vault@sha256:aa6ba791911a815ea570ec2ddc59992481c6ba8fbb65eed4f7074b463430d3ee as vault
########################## BUILD IMAGE ##########################
FROM blackdex/rust-musl:arm-musleabi-stable-1.68.1 as build
# Build time options to avoid dpkg warnings and help with reproducible builds.
ENV DEBIAN_FRONTEND=noninteractive \
LANG=C.UTF-8 \
TZ=UTC \
TERM=xterm-256color \
CARGO_HOME="/root/.cargo" \
USER="root"
# Create CARGO_HOME folder and don't download rust docs
RUN --mount=type=cache,target=/root/.cargo/git --mount=type=cache,target=/root/.cargo/registry mkdir -pv "${CARGO_HOME}" \
&& rustup set profile minimal
# To be able to build the armv6 image with mimalloc we need to specifically specify the libatomic.a file location
ENV RUSTFLAGS='-Clink-arg=/usr/local/musl/arm-unknown-linux-musleabi/lib/libatomic.a'
# Creates a dummy project used to grab dependencies
RUN USER=root cargo new --bin /app
WORKDIR /app
# Copies over *only* your manifests and build files
COPY ./Cargo.* ./
COPY ./rust-toolchain ./rust-toolchain
COPY ./build.rs ./build.rs
RUN --mount=type=cache,target=/root/.cargo/git --mount=type=cache,target=/root/.cargo/registry rustup target add arm-unknown-linux-musleabi
# Configure the DB ARG as late as possible to not invalidate the cached layers above
# Enable MiMalloc to improve performance on Alpine builds
ARG DB=sqlite,mysql,postgresql,enable_mimalloc
# Builds your dependencies and removes the
# dummy project, except the target folder
# This folder contains the compiled dependencies
RUN --mount=type=cache,target=/root/.cargo/git --mount=type=cache,target=/root/.cargo/registry cargo build --features ${DB} --release --target=arm-unknown-linux-musleabi \
&& find . -not -path "./target*" -delete
# Copies the complete project
# To avoid copying unneeded files, use .dockerignore
COPY . .
# Make sure that we actually build the project
RUN touch src/main.rs
# Builds again, this time it'll just be
# your actual source files being built
RUN --mount=type=cache,target=/root/.cargo/git --mount=type=cache,target=/root/.cargo/registry cargo build --features ${DB} --release --target=arm-unknown-linux-musleabi
# Add the `cap_net_bind_service` capability to allow listening on
# privileged (< 1024) ports even when running as a non-root user.
# This is only done if building with BuildKit; with the legacy
# builder, the `COPY` instruction doesn't carry over capabilities.
RUN setcap cap_net_bind_service=+ep target/arm-unknown-linux-musleabi/release/vaultwarden
######################## RUNTIME IMAGE ########################
# Create a new stage with a minimal image
# because we already have a binary built
FROM balenalib/rpi-alpine:3.17
ENV ROCKET_PROFILE="release" \
ROCKET_ADDRESS=0.0.0.0 \
ROCKET_PORT=80 \
SSL_CERT_DIR=/etc/ssl/certs
RUN [ "cross-build-start" ]
# Create data folder and Install needed libraries
RUN mkdir /data \
&& apk add --no-cache \
ca-certificates \
curl \
openssl \
tzdata
RUN [ "cross-build-end" ]
VOLUME /data
EXPOSE 80
EXPOSE 3012
# Copies the files from the context (Rocket.toml file and web-vault)
# and the binary from the "build" stage to the current stage
WORKDIR /
COPY --from=vault /web-vault ./web-vault
COPY --from=build /app/target/arm-unknown-linux-musleabi/release/vaultwarden .
COPY docker/healthcheck.sh /healthcheck.sh
COPY docker/start.sh /start.sh
HEALTHCHECK --interval=60s --timeout=10s CMD ["/healthcheck.sh"]
CMD ["/start.sh"]

View File

@@ -1,142 +0,0 @@
# syntax=docker/dockerfile:1
# This file was generated using a Jinja2 template.
# Please make your changes in `Dockerfile.j2` and then `make` the individual Dockerfiles.
# Using multistage build:
# https://docs.docker.com/develop/develop-images/multistage-build/
# https://whitfin.io/speeding-up-rust-docker-builds/
####################### VAULT BUILD IMAGE #######################
# The web-vault digest specifies a particular web-vault build on Docker Hub.
# Using the digest instead of the tag name provides better security,
# as the digest of an image is immutable, whereas a tag name can later
# be changed to point to a malicious image.
#
# To verify the current digest for a given tag name:
# - From https://hub.docker.com/r/vaultwarden/web-vault/tags,
# click the tag name to view the digest of the image it currently points to.
# - From the command line:
# $ docker pull vaultwarden/web-vault:v2023.3.0b
# $ docker image inspect --format "{{.RepoDigests}}" vaultwarden/web-vault:v2023.3.0b
# [vaultwarden/web-vault@sha256:aa6ba791911a815ea570ec2ddc59992481c6ba8fbb65eed4f7074b463430d3ee]
#
# - Conversely, to get the tag name from the digest:
# $ docker image inspect --format "{{.RepoTags}}" vaultwarden/web-vault@sha256:aa6ba791911a815ea570ec2ddc59992481c6ba8fbb65eed4f7074b463430d3ee
# [vaultwarden/web-vault:v2023.3.0b]
#
FROM vaultwarden/web-vault@sha256:aa6ba791911a815ea570ec2ddc59992481c6ba8fbb65eed4f7074b463430d3ee as vault
########################## BUILD IMAGE ##########################
FROM rust:1.68.1-bullseye as build
# Build time options to avoid dpkg warnings and help with reproducible builds.
ENV DEBIAN_FRONTEND=noninteractive \
LANG=C.UTF-8 \
TZ=UTC \
TERM=xterm-256color \
CARGO_HOME="/root/.cargo" \
USER="root"
# Create CARGO_HOME folder and don't download rust docs
RUN mkdir -pv "${CARGO_HOME}" \
&& rustup set profile minimal
# Install build dependencies for the armhf architecture
RUN dpkg --add-architecture armhf \
&& apt-get update \
&& apt-get install -y \
--no-install-recommends \
gcc-arm-linux-gnueabihf \
libc6-dev:armhf \
libcap2-bin \
libmariadb-dev:armhf \
libmariadb-dev-compat:armhf \
libmariadb3:armhf \
libpq-dev:armhf \
libpq5:armhf \
libssl-dev:armhf \
#
# Make sure cargo has the right target config
&& echo '[target.armv7-unknown-linux-gnueabihf]' >> "${CARGO_HOME}/config" \
&& echo 'linker = "arm-linux-gnueabihf-gcc"' >> "${CARGO_HOME}/config" \
&& echo 'rustflags = ["-L/usr/lib/arm-linux-gnueabihf"]' >> "${CARGO_HOME}/config"
# Set arm specific environment values
ENV CC_armv7_unknown_linux_gnueabihf="/usr/bin/arm-linux-gnueabihf-gcc" \
CROSS_COMPILE="1" \
OPENSSL_INCLUDE_DIR="/usr/include/arm-linux-gnueabihf" \
OPENSSL_LIB_DIR="/usr/lib/arm-linux-gnueabihf"
# Creates a dummy project used to grab dependencies
RUN USER=root cargo new --bin /app
WORKDIR /app
# Copies over *only* your manifests and build files
COPY ./Cargo.* ./
COPY ./rust-toolchain ./rust-toolchain
COPY ./build.rs ./build.rs
RUN rustup target add armv7-unknown-linux-gnueabihf
# Configure the DB ARG as late as possible to not invalidate the cached layers above
ARG DB=sqlite,mysql,postgresql
# Builds your dependencies and removes the
# dummy project, except the target folder
# This folder contains the compiled dependencies
RUN cargo build --features ${DB} --release --target=armv7-unknown-linux-gnueabihf \
&& find . -not -path "./target*" -delete
# Copies the complete project
# To avoid copying unneeded files, use .dockerignore
COPY . .
# Make sure that we actually build the project
RUN touch src/main.rs
# Builds again, this time it'll just be
# your actual source files being built
RUN cargo build --features ${DB} --release --target=armv7-unknown-linux-gnueabihf
######################## RUNTIME IMAGE ########################
# Create a new stage with a minimal image
# because we already have a binary built
FROM balenalib/armv7hf-debian:bullseye
ENV ROCKET_PROFILE="release" \
ROCKET_ADDRESS=0.0.0.0 \
ROCKET_PORT=80
RUN [ "cross-build-start" ]
# Create data folder and Install needed libraries
RUN mkdir /data \
&& apt-get update && apt-get install -y \
--no-install-recommends \
ca-certificates \
curl \
libmariadb-dev-compat \
libpq5 \
openssl \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*
RUN [ "cross-build-end" ]
VOLUME /data
EXPOSE 80
EXPOSE 3012
# Copies the files from the context (Rocket.toml file and web-vault)
# and the binary from the "build" stage to the current stage
WORKDIR /
COPY --from=vault /web-vault ./web-vault
COPY --from=build /app/target/armv7-unknown-linux-gnueabihf/release/vaultwarden .
COPY docker/healthcheck.sh /healthcheck.sh
COPY docker/start.sh /start.sh
HEALTHCHECK --interval=60s --timeout=10s CMD ["/healthcheck.sh"]
CMD ["/start.sh"]

View File

@@ -1,116 +0,0 @@
# syntax=docker/dockerfile:1
# This file was generated using a Jinja2 template.
# Please make your changes in `Dockerfile.j2` and then `make` the individual Dockerfiles.
# Using multistage build:
# https://docs.docker.com/develop/develop-images/multistage-build/
# https://whitfin.io/speeding-up-rust-docker-builds/
####################### VAULT BUILD IMAGE #######################
# The web-vault digest specifies a particular web-vault build on Docker Hub.
# Using the digest instead of the tag name provides better security,
# as the digest of an image is immutable, whereas a tag name can later
# be changed to point to a malicious image.
#
# To verify the current digest for a given tag name:
# - From https://hub.docker.com/r/vaultwarden/web-vault/tags,
# click the tag name to view the digest of the image it currently points to.
# - From the command line:
# $ docker pull vaultwarden/web-vault:v2023.3.0b
# $ docker image inspect --format "{{.RepoDigests}}" vaultwarden/web-vault:v2023.3.0b
# [vaultwarden/web-vault@sha256:aa6ba791911a815ea570ec2ddc59992481c6ba8fbb65eed4f7074b463430d3ee]
#
# - Conversely, to get the tag name from the digest:
# $ docker image inspect --format "{{.RepoTags}}" vaultwarden/web-vault@sha256:aa6ba791911a815ea570ec2ddc59992481c6ba8fbb65eed4f7074b463430d3ee
# [vaultwarden/web-vault:v2023.3.0b]
#
FROM vaultwarden/web-vault@sha256:aa6ba791911a815ea570ec2ddc59992481c6ba8fbb65eed4f7074b463430d3ee as vault
########################## BUILD IMAGE ##########################
FROM blackdex/rust-musl:armv7-musleabihf-stable-1.68.1 as build
# Build time options to avoid dpkg warnings and help with reproducible builds.
ENV DEBIAN_FRONTEND=noninteractive \
LANG=C.UTF-8 \
TZ=UTC \
TERM=xterm-256color \
CARGO_HOME="/root/.cargo" \
USER="root"
# Create CARGO_HOME folder and don't download rust docs
RUN mkdir -pv "${CARGO_HOME}" \
&& rustup set profile minimal
# Creates a dummy project used to grab dependencies
RUN USER=root cargo new --bin /app
WORKDIR /app
# Copies over *only* your manifests and build files
COPY ./Cargo.* ./
COPY ./rust-toolchain ./rust-toolchain
COPY ./build.rs ./build.rs
RUN rustup target add armv7-unknown-linux-musleabihf
# Configure the DB ARG as late as possible to not invalidate the cached layers above
# Enable MiMalloc to improve performance on Alpine builds
ARG DB=sqlite,mysql,postgresql,enable_mimalloc
# Builds your dependencies and removes the
# dummy project, except the target folder
# This folder contains the compiled dependencies
RUN cargo build --features ${DB} --release --target=armv7-unknown-linux-musleabihf \
&& find . -not -path "./target*" -delete
# Copies the complete project
# To avoid copying unneeded files, use .dockerignore
COPY . .
# Make sure that we actually build the project
RUN touch src/main.rs
# Builds again, this time it'll just be
# your actual source files being built
RUN cargo build --features ${DB} --release --target=armv7-unknown-linux-musleabihf
######################## RUNTIME IMAGE ########################
# Create a new stage with a minimal image
# because we already have a binary built
FROM balenalib/armv7hf-alpine:3.17
ENV ROCKET_PROFILE="release" \
ROCKET_ADDRESS=0.0.0.0 \
ROCKET_PORT=80 \
SSL_CERT_DIR=/etc/ssl/certs
RUN [ "cross-build-start" ]
# Create data folder and Install needed libraries
RUN mkdir /data \
&& apk add --no-cache \
ca-certificates \
curl \
openssl \
tzdata
RUN [ "cross-build-end" ]
VOLUME /data
EXPOSE 80
EXPOSE 3012
# Copies the files from the context (Rocket.toml file and web-vault)
# and the binary from the "build" stage to the current stage
WORKDIR /
COPY --from=vault /web-vault ./web-vault
COPY --from=build /app/target/armv7-unknown-linux-musleabihf/release/vaultwarden .
COPY docker/healthcheck.sh /healthcheck.sh
COPY docker/start.sh /start.sh
HEALTHCHECK --interval=60s --timeout=10s CMD ["/healthcheck.sh"]
CMD ["/start.sh"]

View File

@@ -1,147 +0,0 @@
# syntax=docker/dockerfile:1
# This file was generated using a Jinja2 template.
# Please make your changes in `Dockerfile.j2` and then `make` the individual Dockerfiles.
# Using multistage build:
# https://docs.docker.com/develop/develop-images/multistage-build/
# https://whitfin.io/speeding-up-rust-docker-builds/
####################### VAULT BUILD IMAGE #######################
# The web-vault digest specifies a particular web-vault build on Docker Hub.
# Using the digest instead of the tag name provides better security,
# as the digest of an image is immutable, whereas a tag name can later
# be changed to point to a malicious image.
#
# To verify the current digest for a given tag name:
# - From https://hub.docker.com/r/vaultwarden/web-vault/tags,
# click the tag name to view the digest of the image it currently points to.
# - From the command line:
# $ docker pull vaultwarden/web-vault:v2023.3.0b
# $ docker image inspect --format "{{.RepoDigests}}" vaultwarden/web-vault:v2023.3.0b
# [vaultwarden/web-vault@sha256:aa6ba791911a815ea570ec2ddc59992481c6ba8fbb65eed4f7074b463430d3ee]
#
# - Conversely, to get the tag name from the digest:
# $ docker image inspect --format "{{.RepoTags}}" vaultwarden/web-vault@sha256:aa6ba791911a815ea570ec2ddc59992481c6ba8fbb65eed4f7074b463430d3ee
# [vaultwarden/web-vault:v2023.3.0b]
#
FROM vaultwarden/web-vault@sha256:aa6ba791911a815ea570ec2ddc59992481c6ba8fbb65eed4f7074b463430d3ee as vault
########################## BUILD IMAGE ##########################
FROM rust:1.68.1-bullseye as build
# Build time options to avoid dpkg warnings and help with reproducible builds.
ENV DEBIAN_FRONTEND=noninteractive \
LANG=C.UTF-8 \
TZ=UTC \
TERM=xterm-256color \
CARGO_HOME="/root/.cargo" \
USER="root"
# Create CARGO_HOME folder and don't download rust docs
RUN --mount=type=cache,target=/root/.cargo/git --mount=type=cache,target=/root/.cargo/registry mkdir -pv "${CARGO_HOME}" \
&& rustup set profile minimal
# Install build dependencies for the armhf architecture
RUN dpkg --add-architecture armhf \
&& apt-get update \
&& apt-get install -y \
--no-install-recommends \
gcc-arm-linux-gnueabihf \
libc6-dev:armhf \
libcap2-bin \
libmariadb-dev:armhf \
libmariadb-dev-compat:armhf \
libmariadb3:armhf \
libpq-dev:armhf \
libpq5:armhf \
libssl-dev:armhf \
#
# Make sure cargo has the right target config
&& echo '[target.armv7-unknown-linux-gnueabihf]' >> "${CARGO_HOME}/config" \
&& echo 'linker = "arm-linux-gnueabihf-gcc"' >> "${CARGO_HOME}/config" \
&& echo 'rustflags = ["-L/usr/lib/arm-linux-gnueabihf"]' >> "${CARGO_HOME}/config"
# Set arm specific environment values
ENV CC_armv7_unknown_linux_gnueabihf="/usr/bin/arm-linux-gnueabihf-gcc" \
CROSS_COMPILE="1" \
OPENSSL_INCLUDE_DIR="/usr/include/arm-linux-gnueabihf" \
OPENSSL_LIB_DIR="/usr/lib/arm-linux-gnueabihf"
# Creates a dummy project used to grab dependencies
RUN USER=root cargo new --bin /app
WORKDIR /app
# Copies over *only* your manifests and build files
COPY ./Cargo.* ./
COPY ./rust-toolchain ./rust-toolchain
COPY ./build.rs ./build.rs
RUN --mount=type=cache,target=/root/.cargo/git --mount=type=cache,target=/root/.cargo/registry rustup target add armv7-unknown-linux-gnueabihf
# Configure the DB ARG as late as possible to not invalidate the cached layers above
ARG DB=sqlite,mysql,postgresql
# Builds your dependencies and removes the
# dummy project, except the target folder
# This folder contains the compiled dependencies
RUN --mount=type=cache,target=/root/.cargo/git --mount=type=cache,target=/root/.cargo/registry cargo build --features ${DB} --release --target=armv7-unknown-linux-gnueabihf \
&& find . -not -path "./target*" -delete
# Copies the complete project
# To avoid copying unneeded files, use .dockerignore
COPY . .
# Make sure that we actually build the project
RUN touch src/main.rs
# Builds again, this time it'll just be
# your actual source files being built
RUN --mount=type=cache,target=/root/.cargo/git --mount=type=cache,target=/root/.cargo/registry cargo build --features ${DB} --release --target=armv7-unknown-linux-gnueabihf
# Add the `cap_net_bind_service` capability to allow listening on
# privileged (< 1024) ports even when running as a non-root user.
# This is only done if building with BuildKit; with the legacy
# builder, the `COPY` instruction doesn't carry over capabilities.
RUN setcap cap_net_bind_service=+ep target/armv7-unknown-linux-gnueabihf/release/vaultwarden
######################## RUNTIME IMAGE ########################
# Create a new stage with a minimal image
# because we already have a binary built
FROM balenalib/armv7hf-debian:bullseye
ENV ROCKET_PROFILE="release" \
ROCKET_ADDRESS=0.0.0.0 \
ROCKET_PORT=80
RUN [ "cross-build-start" ]
# Create data folder and Install needed libraries
RUN mkdir /data \
&& apt-get update && apt-get install -y \
--no-install-recommends \
ca-certificates \
curl \
libmariadb-dev-compat \
libpq5 \
openssl \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*
RUN [ "cross-build-end" ]
VOLUME /data
EXPOSE 80
EXPOSE 3012
# Copies the files from the context (Rocket.toml file and web-vault)
# and the binary from the "build" stage to the current stage
WORKDIR /
COPY --from=vault /web-vault ./web-vault
COPY --from=build /app/target/armv7-unknown-linux-gnueabihf/release/vaultwarden .
COPY docker/healthcheck.sh /healthcheck.sh
COPY docker/start.sh /start.sh
HEALTHCHECK --interval=60s --timeout=10s CMD ["/healthcheck.sh"]
CMD ["/start.sh"]

View File

@@ -1,121 +0,0 @@
# syntax=docker/dockerfile:1
# This file was generated using a Jinja2 template.
# Please make your changes in `Dockerfile.j2` and then `make` the individual Dockerfiles.
# Using multistage build:
# https://docs.docker.com/develop/develop-images/multistage-build/
# https://whitfin.io/speeding-up-rust-docker-builds/
####################### VAULT BUILD IMAGE #######################
# The web-vault digest specifies a particular web-vault build on Docker Hub.
# Using the digest instead of the tag name provides better security,
# as the digest of an image is immutable, whereas a tag name can later
# be changed to point to a malicious image.
#
# To verify the current digest for a given tag name:
# - From https://hub.docker.com/r/vaultwarden/web-vault/tags,
# click the tag name to view the digest of the image it currently points to.
# - From the command line:
# $ docker pull vaultwarden/web-vault:v2023.3.0b
# $ docker image inspect --format "{{.RepoDigests}}" vaultwarden/web-vault:v2023.3.0b
# [vaultwarden/web-vault@sha256:aa6ba791911a815ea570ec2ddc59992481c6ba8fbb65eed4f7074b463430d3ee]
#
# - Conversely, to get the tag name from the digest:
# $ docker image inspect --format "{{.RepoTags}}" vaultwarden/web-vault@sha256:aa6ba791911a815ea570ec2ddc59992481c6ba8fbb65eed4f7074b463430d3ee
# [vaultwarden/web-vault:v2023.3.0b]
#
FROM vaultwarden/web-vault@sha256:aa6ba791911a815ea570ec2ddc59992481c6ba8fbb65eed4f7074b463430d3ee as vault
########################## BUILD IMAGE ##########################
FROM blackdex/rust-musl:armv7-musleabihf-stable-1.68.1 as build
# Build time options to avoid dpkg warnings and help with reproducible builds.
ENV DEBIAN_FRONTEND=noninteractive \
LANG=C.UTF-8 \
TZ=UTC \
TERM=xterm-256color \
CARGO_HOME="/root/.cargo" \
USER="root"
# Create CARGO_HOME folder and don't download rust docs
RUN --mount=type=cache,target=/root/.cargo/git --mount=type=cache,target=/root/.cargo/registry mkdir -pv "${CARGO_HOME}" \
&& rustup set profile minimal
# Creates a dummy project used to grab dependencies
RUN USER=root cargo new --bin /app
WORKDIR /app
# Copies over *only* your manifests and build files
COPY ./Cargo.* ./
COPY ./rust-toolchain ./rust-toolchain
COPY ./build.rs ./build.rs
RUN --mount=type=cache,target=/root/.cargo/git --mount=type=cache,target=/root/.cargo/registry rustup target add armv7-unknown-linux-musleabihf
# Configure the DB ARG as late as possible to not invalidate the cached layers above
# Enable MiMalloc to improve performance on Alpine builds
ARG DB=sqlite,mysql,postgresql,enable_mimalloc
# Builds your dependencies and removes the
# dummy project, except the target folder
# This folder contains the compiled dependencies
RUN --mount=type=cache,target=/root/.cargo/git --mount=type=cache,target=/root/.cargo/registry cargo build --features ${DB} --release --target=armv7-unknown-linux-musleabihf \
&& find . -not -path "./target*" -delete
# Copies the complete project
# To avoid copying unneeded files, use .dockerignore
COPY . .
# Make sure that we actually build the project
RUN touch src/main.rs
# Builds again, this time it'll just be
# your actual source files being built
RUN --mount=type=cache,target=/root/.cargo/git --mount=type=cache,target=/root/.cargo/registry cargo build --features ${DB} --release --target=armv7-unknown-linux-musleabihf
# Add the `cap_net_bind_service` capability to allow listening on
# privileged (< 1024) ports even when running as a non-root user.
# This is only done if building with BuildKit; with the legacy
# builder, the `COPY` instruction doesn't carry over capabilities.
RUN setcap cap_net_bind_service=+ep target/armv7-unknown-linux-musleabihf/release/vaultwarden
######################## RUNTIME IMAGE ########################
# Create a new stage with a minimal image
# because we already have a binary built
FROM balenalib/armv7hf-alpine:3.17
ENV ROCKET_PROFILE="release" \
ROCKET_ADDRESS=0.0.0.0 \
ROCKET_PORT=80 \
SSL_CERT_DIR=/etc/ssl/certs
RUN [ "cross-build-start" ]
# Create data folder and Install needed libraries
RUN mkdir /data \
&& apk add --no-cache \
ca-certificates \
curl \
openssl \
tzdata
RUN [ "cross-build-end" ]
VOLUME /data
EXPOSE 80
EXPOSE 3012
# Copies the files from the context (Rocket.toml file and web-vault)
# and the binary from the "build" stage to the current stage
WORKDIR /
COPY --from=vault /web-vault ./web-vault
COPY --from=build /app/target/armv7-unknown-linux-musleabihf/release/vaultwarden .
COPY docker/healthcheck.sh /healthcheck.sh
COPY docker/start.sh /start.sh
HEALTHCHECK --interval=60s --timeout=10s CMD ["/healthcheck.sh"]
CMD ["/start.sh"]

15
docker/bake.sh Executable file
View File

@@ -0,0 +1,15 @@
#!/usr/bin/env bash
# Determine the basedir of this script.
# It should be located in the same directory as the docker-bake.hcl
# This ensures you can run this script from both inside and outside of the docker directory
BASEDIR=$(RL=$(readlink -n "$0"); SP="${RL:-$0}"; dirname "$(cd "$(dirname "${SP}")" || exit; pwd)/$(basename "${SP}")")
# Load build env's
source "${BASEDIR}/bake_env.sh"
# Be verbose on what is being executed
set -x
# Make sure we set the context to `..` so it will go up one directory
docker buildx bake --progress plain --set "*.context=${BASEDIR}/.." -f "${BASEDIR}/docker-bake.hcl" "$@"

33
docker/bake_env.sh Normal file
View File

@@ -0,0 +1,33 @@
#!/usr/bin/env bash
# If SOURCE_COMMIT is provided via env skip this
if [ -z "${SOURCE_COMMIT+x}" ]; then
SOURCE_COMMIT="$(git rev-parse HEAD)"
fi
# If VW_VERSION is provided via env use it as SOURCE_VERSION
# Else define it using git
if [[ -n "${VW_VERSION}" ]]; then
SOURCE_VERSION="${VW_VERSION}"
else
GIT_EXACT_TAG="$(git describe --tags --abbrev=0 --exact-match 2>/dev/null)"
if [[ -n "${GIT_EXACT_TAG}" ]]; then
SOURCE_VERSION="${GIT_EXACT_TAG}"
else
GIT_LAST_TAG="$(git describe --tags --abbrev=0)"
SOURCE_VERSION="${GIT_LAST_TAG}-${SOURCE_COMMIT:0:8}"
GIT_BRANCH="$(git rev-parse --abbrev-ref HEAD)"
case "${GIT_BRANCH}" in
main|master|HEAD)
# Do not add the branch name for these branches
;;
*)
SOURCE_VERSION="${SOURCE_VERSION} (${GIT_BRANCH})"
;;
esac
fi
fi
# Export the rendered variables above so bake will use them
export SOURCE_COMMIT
export SOURCE_VERSION

235
docker/docker-bake.hcl Normal file
View File

@@ -0,0 +1,235 @@
// ==== Baking Variables ====
// Set which cargo profile to use, dev or release for example
// Use the value provided in the Dockerfile as default
variable "CARGO_PROFILE" {
default = null
}
// Set which DB's (features) to enable
// Use the value provided in the Dockerfile as default
variable "DB" {
default = null
}
// The repository this build was triggered from
variable "SOURCE_REPOSITORY_URL" {
default = null
}
// The commit hash of of the current commit this build was triggered on
variable "SOURCE_COMMIT" {
default = null
}
// The version of this build
// Typically the current exact tag of this commit,
// else the last tag and the first 8 characters of the source commit
variable "SOURCE_VERSION" {
default = null
}
// This can be used to overwrite SOURCE_VERSION
// It will be used during the build.rs building stage
variable "VW_VERSION" {
default = null
}
// The base tag(s) to use
// This can be a comma separated value like "testing,1.29.2"
variable "BASE_TAGS" {
default = "testing"
}
// Which container registries should be used for the tagging
// This can be a comma separated value
// Use a full URI like `ghcr.io/dani-garcia/vaultwarden,docker.io/vaultwarden/server`
variable "CONTAINER_REGISTRIES" {
default = "vaultwarden/server"
}
// ==== Baking Groups ====
group "default" {
targets = ["debian"]
}
// ==== Shared Baking ====
function "labels" {
params = []
result = {
"org.opencontainers.image.description" = "Unofficial Bitwarden compatible server written in Rust - ${SOURCE_VERSION}"
"org.opencontainers.image.licenses" = "AGPL-3.0-only"
"org.opencontainers.image.documentation" = "https://github.com/dani-garcia/vaultwarden/wiki"
"org.opencontainers.image.url" = "https://github.com/dani-garcia/vaultwarden"
"org.opencontainers.image.created" = "${formatdate("YYYY-MM-DD'T'hh:mm:ssZZZZZ", timestamp())}"
"org.opencontainers.image.source" = "${SOURCE_REPOSITORY_URL}"
"org.opencontainers.image.revision" = "${SOURCE_COMMIT}"
"org.opencontainers.image.version" = "${SOURCE_VERSION}"
}
}
target "_default_attributes" {
labels = labels()
args = {
DB = "${DB}"
CARGO_PROFILE = "${CARGO_PROFILE}"
VW_VERSION = "${VW_VERSION}"
}
}
// ==== Debian Baking ====
// Default Debian target, will build a container using the hosts platform architecture
target "debian" {
inherits = ["_default_attributes"]
dockerfile = "docker/Dockerfile.debian"
tags = generate_tags("", platform_tag())
output = ["type=docker"]
}
// Multi Platform target, will build one tagged manifest with all supported architectures
// This is mainly used by GitHub Actions to build and push new containers
target "debian-multi" {
inherits = ["debian"]
platforms = ["linux/amd64", "linux/arm64", "linux/arm/v7", "linux/arm/v6"]
tags = generate_tags("", "")
output = [join(",", flatten([["type=registry"], image_index_annotations()]))]
}
// Per platform targets, to individually test building per platform locally
target "debian-amd64" {
inherits = ["debian"]
platforms = ["linux/amd64"]
tags = generate_tags("", "-amd64")
}
target "debian-arm64" {
inherits = ["debian"]
platforms = ["linux/arm64"]
tags = generate_tags("", "-arm64")
}
target "debian-armv7" {
inherits = ["debian"]
platforms = ["linux/arm/v7"]
tags = generate_tags("", "-armv7")
}
target "debian-armv6" {
inherits = ["debian"]
platforms = ["linux/arm/v6"]
tags = generate_tags("", "-armv6")
}
// A Group to build all platforms individually for local testing
group "debian-all" {
targets = ["debian-amd64", "debian-arm64", "debian-armv7", "debian-armv6"]
}
// ==== Alpine Baking ====
// Default Alpine target, will build a container using the hosts platform architecture
target "alpine" {
inherits = ["_default_attributes"]
dockerfile = "docker/Dockerfile.alpine"
tags = generate_tags("-alpine", platform_tag())
output = ["type=docker"]
}
// Multi Platform target, will build one tagged manifest with all supported architectures
// This is mainly used by GitHub Actions to build and push new containers
target "alpine-multi" {
inherits = ["alpine"]
platforms = ["linux/amd64", "linux/arm64", "linux/arm/v7", "linux/arm/v6"]
tags = generate_tags("-alpine", "")
output = [join(",", flatten([["type=registry"], image_index_annotations()]))]
}
// Per platform targets, to individually test building per platform locally
target "alpine-amd64" {
inherits = ["alpine"]
platforms = ["linux/amd64"]
tags = generate_tags("-alpine", "-amd64")
}
target "alpine-arm64" {
inherits = ["alpine"]
platforms = ["linux/arm64"]
tags = generate_tags("-alpine", "-arm64")
}
target "alpine-armv7" {
inherits = ["alpine"]
platforms = ["linux/arm/v7"]
tags = generate_tags("-alpine", "-armv7")
}
target "alpine-armv6" {
inherits = ["alpine"]
platforms = ["linux/arm/v6"]
tags = generate_tags("-alpine", "-armv6")
}
// A Group to build all platforms individually for local testing
group "alpine-all" {
targets = ["alpine-amd64", "alpine-arm64", "alpine-armv7", "alpine-armv6"]
}
// ==== Bake everything locally ====
group "all" {
targets = ["debian-all", "alpine-all"]
}
// ==== Baking functions ====
// This will return the local platform as amd64, arm64 or armv7 for example
// It can be used for creating a local image tag
function "platform_tag" {
params = []
result = "-${replace(replace(BAKE_LOCAL_PLATFORM, "linux/", ""), "/", "")}"
}
function "get_container_registries" {
params = []
result = flatten(split(",", CONTAINER_REGISTRIES))
}
function "get_base_tags" {
params = []
result = flatten(split(",", BASE_TAGS))
}
function "generate_tags" {
params = [
suffix, // What to append to the BASE_TAG when needed, like `-alpine` for example
platform // the platform we are building for if needed
]
result = flatten([
for registry in get_container_registries() :
[for base_tag in get_base_tags() :
concat(
# If the base_tag contains latest, and the suffix contains `-alpine` add a `:alpine` tag too
base_tag == "latest" ? suffix == "-alpine" ? ["${registry}:alpine${platform}"] : [] : [],
# The default tagging strategy
["${registry}:${base_tag}${suffix}${platform}"]
)
]
])
}
function "image_index_annotations" {
params = []
result = flatten([
for key, value in labels() :
value != null ? formatlist("annotation-index.%s=%s", "${key}", "${value}") : []
])
}

View File

@@ -10,7 +10,7 @@ CONFIG_FILE="${DATA_FOLDER}"/config.json
# Given a config key, return the corresponding config value from the
# config file. If the key doesn't exist, return an empty string.
get_config_val() {
local key="$1"
key="$1"
# Extract a line of the form:
# "domain": "https://bw.example.com/path",
grep "\"${key}\":" "${CONFIG_FILE}" |

105
docker/podman-bake.sh Executable file
View File

@@ -0,0 +1,105 @@
#!/usr/bin/env bash
# Determine the basedir of this script.
# It should be located in the same directory as the docker-bake.hcl
# This ensures you can run this script from both inside and outside of the docker directory
BASEDIR=$(RL=$(readlink -n "$0"); SP="${RL:-$0}"; dirname "$(cd "$(dirname "${SP}")" || exit; pwd)/$(basename "${SP}")")
# Load build env's
source "${BASEDIR}/bake_env.sh"
# Check if a target is given as first argument
# If not we assume the defaults and pass the given arguments to the podman command
case "${1}" in
alpine*|debian*)
TARGET="${1}"
# Now shift the $@ array so we only have the rest of the arguments
# This allows us too append these as extra arguments too the podman buildx build command
shift
;;
esac
LABEL_ARGS=(
--label org.opencontainers.image.description="Unofficial Bitwarden compatible server written in Rust"
--label org.opencontainers.image.licenses="AGPL-3.0-only"
--label org.opencontainers.image.documentation="https://github.com/dani-garcia/vaultwarden/wiki"
--label org.opencontainers.image.url="https://github.com/dani-garcia/vaultwarden"
--label org.opencontainers.image.created="$(date --utc --iso-8601=seconds)"
)
if [[ -n "${SOURCE_REPOSITORY_URL}" ]]; then
LABEL_ARGS+=(--label org.opencontainers.image.source="${SOURCE_REPOSITORY_URL}")
fi
if [[ -n "${SOURCE_COMMIT}" ]]; then
LABEL_ARGS+=(--label org.opencontainers.image.revision="${SOURCE_COMMIT}")
fi
if [[ -n "${SOURCE_VERSION}" ]]; then
LABEL_ARGS+=(--label org.opencontainers.image.version="${SOURCE_VERSION}")
fi
# Check if and which --build-arg arguments we need to configure
BUILD_ARGS=()
if [[ -n "${DB}" ]]; then
BUILD_ARGS+=(--build-arg DB="${DB}")
fi
if [[ -n "${CARGO_PROFILE}" ]]; then
BUILD_ARGS+=(--build-arg CARGO_PROFILE="${CARGO_PROFILE}")
fi
if [[ -n "${VW_VERSION}" ]]; then
BUILD_ARGS+=(--build-arg VW_VERSION="${VW_VERSION}")
fi
# Set the default BASE_TAGS if non are provided
if [[ -z "${BASE_TAGS}" ]]; then
BASE_TAGS="testing"
fi
# Set the default CONTAINER_REGISTRIES if non are provided
if [[ -z "${CONTAINER_REGISTRIES}" ]]; then
CONTAINER_REGISTRIES="vaultwarden/server"
fi
# Check which Dockerfile we need to use, default is debian
case "${TARGET}" in
alpine*)
BASE_TAGS="${BASE_TAGS}-alpine"
DOCKERFILE="Dockerfile.alpine"
;;
*)
DOCKERFILE="Dockerfile.debian"
;;
esac
# Check which platform we need to build and append the BASE_TAGS with the architecture
case "${TARGET}" in
*-arm64)
BASE_TAGS="${BASE_TAGS}-arm64"
PLATFORM="linux/arm64"
;;
*-armv7)
BASE_TAGS="${BASE_TAGS}-armv7"
PLATFORM="linux/arm/v7"
;;
*-armv6)
BASE_TAGS="${BASE_TAGS}-armv6"
PLATFORM="linux/arm/v6"
;;
*)
BASE_TAGS="${BASE_TAGS}-amd64"
PLATFORM="linux/amd64"
;;
esac
# Be verbose on what is being executed
set -x
# Build the image with podman
# We use the docker format here since we are using `SHELL`, which is not supported by OCI
# shellcheck disable=SC2086
podman buildx build \
--platform="${PLATFORM}" \
--tag="${CONTAINER_REGISTRIES}:${BASE_TAGS}" \
--format=docker \
"${LABEL_ARGS[@]}" \
"${BUILD_ARGS[@]}" \
--file="${BASEDIR}/${DOCKERFILE}" "$@" \
"${BASEDIR}/.."

View File

@@ -1,17 +1,31 @@
#!/usr/bin/env python3
import os, argparse, json
import os
import argparse
import json
import yaml
import jinja2
# Load settings file
with open("DockerSettings.yaml", 'r') as yaml_file:
yaml_data = yaml.safe_load(yaml_file)
settings_env = jinja2.Environment(
loader=jinja2.FileSystemLoader(os.getcwd()),
)
settings_yaml = yaml.safe_load(settings_env.get_template("DockerSettings.yaml").render(yaml_data))
args_parser = argparse.ArgumentParser()
args_parser.add_argument('template_file', help='Jinja2 template file to render.')
args_parser.add_argument('render_vars', help='JSON-encoded data to pass to the templating engine.')
cli_args = args_parser.parse_args()
# Merge the default config yaml with the json arguments given.
render_vars = json.loads(cli_args.render_vars)
settings_yaml.update(render_vars)
environment = jinja2.Environment(
loader=jinja2.FileSystemLoader(os.getcwd()),
trim_blocks=True,
)
print(environment.get_template(cli_args.template_file).render(render_vars))
print(environment.get_template(cli_args.template_file).render(settings_yaml))

View File

@@ -1,20 +0,0 @@
The hooks in this directory are used to create multi-arch images using Docker Hub automated builds.
Docker Hub hooks provide these predefined [environment variables](https://docs.docker.com/docker-hub/builds/advanced/#environment-variables-for-building-and-testing):
* `SOURCE_BRANCH`: the name of the branch or the tag that is currently being tested.
* `SOURCE_COMMIT`: the SHA1 hash of the commit being tested.
* `COMMIT_MSG`: the message from the commit being tested and built.
* `DOCKER_REPO`: the name of the Docker repository being built.
* `DOCKERFILE_PATH`: the dockerfile currently being built.
* `DOCKER_TAG`: the Docker repository tag being built.
* `IMAGE_NAME`: the name and tag of the Docker repository being built. (This variable is a combination of `DOCKER_REPO:DOCKER_TAG`.)
The current multi-arch image build relies on the original vaultwarden Dockerfiles, which use cross-compilation for architectures other than `amd64`, and don't yet support all arch/distro combinations. However, cross-compilation is much faster than QEMU-based builds (e.g., using `docker buildx`). This situation may need to be revisited at some point.
## References
* https://docs.docker.com/docker-hub/builds/advanced/
* https://docs.docker.com/engine/reference/commandline/manifest/
* https://www.docker.com/blog/multi-arch-build-and-images-the-simple-way/
* https://success.docker.com/article/how-do-i-authenticate-with-the-v2-api

View File

@@ -1,15 +0,0 @@
#!/usr/bin/env bash
# The default Debian-based images support these arches for all database backends.
arches=(
amd64
armv6
armv7
arm64
)
export arches
if [[ "${DOCKER_TAG}" == *alpine ]]; then
distro_suffix=.alpine
fi
export distro_suffix

View File

@@ -1,51 +0,0 @@
#!/usr/bin/env bash
echo ">>> Building images..."
# shellcheck source=arches.sh
source ./hooks/arches.sh
if [[ -z "${SOURCE_COMMIT}" ]]; then
# This var is typically predefined by Docker Hub, but it won't be
# when testing locally.
SOURCE_COMMIT="$(git rev-parse HEAD)"
fi
# Construct a version string in the style of `build.rs`.
GIT_EXACT_TAG="$(git describe --tags --abbrev=0 --exact-match 2>/dev/null)"
if [[ -n "${GIT_EXACT_TAG}" ]]; then
SOURCE_VERSION="${GIT_EXACT_TAG}"
else
GIT_LAST_TAG="$(git describe --tags --abbrev=0)"
SOURCE_VERSION="${GIT_LAST_TAG}-${SOURCE_COMMIT:0:8}"
fi
LABELS=(
# https://github.com/opencontainers/image-spec/blob/master/annotations.md
org.opencontainers.image.created="$(date --utc --iso-8601=seconds)"
org.opencontainers.image.documentation="https://github.com/dani-garcia/vaultwarden/wiki"
org.opencontainers.image.licenses="AGPL-3.0-only"
org.opencontainers.image.revision="${SOURCE_COMMIT}"
org.opencontainers.image.source="${SOURCE_REPOSITORY_URL}"
org.opencontainers.image.url="https://github.com/dani-garcia/vaultwarden"
org.opencontainers.image.version="${SOURCE_VERSION}"
)
LABEL_ARGS=()
for label in "${LABELS[@]}"; do
LABEL_ARGS+=(--label "${label}")
done
# Check if DOCKER_BUILDKIT is set, if so, use the Dockerfile.buildkit as template
if [[ -n "${DOCKER_BUILDKIT}" ]]; then
buildkit_suffix=.buildkit
fi
set -ex
for arch in "${arches[@]}"; do
docker build \
"${LABEL_ARGS[@]}" \
-t "${DOCKER_REPO}:${DOCKER_TAG}-${arch}" \
-f "docker/${arch}/Dockerfile${buildkit_suffix}${distro_suffix}" \
.
done

View File

@@ -1,28 +0,0 @@
#!/usr/bin/env bash
set -ex
# If requested, print some environment info for troubleshooting.
if [[ -n "${DOCKER_HUB_DEBUG}" ]]; then
id
pwd
df -h
env
docker info
docker version
fi
# Install build dependencies.
deps=(
jq
)
apt-get update
apt-get install -y "${deps[@]}"
# Docker Hub uses a shallow clone and doesn't fetch tags, which breaks some
# Git operations that we perform later, so fetch the complete history and
# tags first. Note that if the build is cached, the clone may have been
# unshallowed already; if so, unshallowing will fail, so skip it.
if [[ -f .git/shallow ]]; then
git fetch --unshallow --tags
fi

View File

@@ -1,111 +0,0 @@
#!/usr/bin/env bash
# shellcheck source=arches.sh
source ./hooks/arches.sh
export DOCKER_CLI_EXPERIMENTAL=enabled
# Join a list of args with a single char.
# Ref: https://stackoverflow.com/a/17841619
join() { local IFS="$1"; shift; echo "$*"; }
set -ex
echo ">>> Starting local Docker registry when needed..."
# Docker Buildx's `docker-container` driver is needed for multi-platform
# builds, but it can't access existing images on the Docker host (like the
# cross-compiled ones we just built). Those images first need to be pushed to
# a registry -- Docker Hub could be used, but since it's not trivial to clean
# up those intermediate images on Docker Hub, it's easier to just run a local
# Docker registry, which gets cleaned up automatically once the build job ends.
#
# https://docs.docker.com/registry/deploying/
# https://hub.docker.com/_/registry
#
# Use host networking so the buildx container can access the registry via
# localhost.
#
# First check if there already is a registry container running, else skip it.
# This will only happen either locally or running it via Github Actions
#
if ! timeout 5 bash -c 'cat < /dev/null > /dev/tcp/localhost/5000'; then
# defaults to port 5000
docker run -d --name registry --network host registry:2
fi
# Docker Hub sets a `DOCKER_REPO` env var with the format `index.docker.io/user/repo`.
# Strip the registry portion to construct a local repo path for use in `Dockerfile.buildx`.
LOCAL_REGISTRY="localhost:5000"
REPO="${DOCKER_REPO#*/}"
LOCAL_REPO="${LOCAL_REGISTRY}/${REPO}"
echo ">>> Pushing images to local registry..."
for arch in "${arches[@]}"; do
docker_image="${DOCKER_REPO}:${DOCKER_TAG}-${arch}"
local_image="${LOCAL_REPO}:${DOCKER_TAG}-${arch}"
docker tag "${docker_image}" "${local_image}"
docker push "${local_image}"
done
echo ">>> Setting up Docker Buildx..."
# Same as earlier, use host networking so the buildx container can access the
# registry via localhost.
#
# Ref: https://github.com/docker/buildx/issues/94#issuecomment-534367714
#
# Check if there already is a builder running, else skip this and use the existing.
# This will only happen either locally or running it via Github Actions
#
if ! docker buildx inspect builder > /dev/null 2>&1 ; then
docker buildx create --name builder --use --driver-opt network=host
fi
echo ">>> Running Docker Buildx..."
tags=("${DOCKER_REPO}:${DOCKER_TAG}")
# If the Docker tag starts with a version number, assume the latest release
# is being pushed. Add an extra tag (`latest` or `alpine`, as appropriate)
# to make it easier for users to track the latest release.
if [[ "${DOCKER_TAG}" =~ ^[0-9]+\.[0-9]+\.[0-9]+ ]]; then
if [[ "${DOCKER_TAG}" == *alpine ]]; then
tags+=("${DOCKER_REPO}:alpine")
else
tags+=("${DOCKER_REPO}:latest")
fi
fi
tag_args=()
for tag in "${tags[@]}"; do
tag_args+=(--tag "${tag}")
done
# Docker Buildx takes a list of target platforms (OS/arch/variant), so map
# the arch list to a platform list (assuming the OS is always `linux`).
declare -A arch_to_platform=(
[amd64]="linux/amd64"
[armv6]="linux/arm/v6"
[armv7]="linux/arm/v7"
[arm64]="linux/arm64"
)
platforms=()
for arch in "${arches[@]}"; do
platforms+=("${arch_to_platform[$arch]}")
done
platform="$(join "," "${platforms[@]}")"
# Run the build, pushing the resulting images and multi-arch manifest list to
# Docker Hub. The Dockerfile is read from stdin to avoid sending any build
# context, which isn't needed here since the actual cross-compiled images
# have already been built.
docker buildx build \
--network host \
--build-arg LOCAL_REPO="${LOCAL_REPO}" \
--build-arg DOCKER_TAG="${DOCKER_TAG}" \
--platform "${platform}" \
"${tag_args[@]}" \
--push \
- < ./docker/Dockerfile.buildx

View File

@@ -0,0 +1 @@
ALTER TABLE devices ADD COLUMN push_uuid TEXT;

View File

@@ -0,0 +1,10 @@
CREATE TABLE organization_api_key (
uuid CHAR(36) NOT NULL,
org_uuid CHAR(36) NOT NULL REFERENCES organizations(uuid),
atype INTEGER NOT NULL,
api_key VARCHAR(255) NOT NULL,
revision_date DATETIME NOT NULL,
PRIMARY KEY(uuid, org_uuid)
);
ALTER TABLE users ADD COLUMN external_id TEXT;

View File

@@ -0,0 +1,19 @@
CREATE TABLE auth_requests (
uuid CHAR(36) NOT NULL PRIMARY KEY,
user_uuid CHAR(36) NOT NULL,
organization_uuid CHAR(36),
request_device_identifier CHAR(36) NOT NULL,
device_type INTEGER NOT NULL,
request_ip TEXT NOT NULL,
response_device_id CHAR(36),
access_code TEXT NOT NULL,
public_key TEXT NOT NULL,
enc_key TEXT NOT NULL,
master_password_hash TEXT NOT NULL,
approved BOOLEAN,
creation_date DATETIME NOT NULL,
response_date DATETIME,
authentication_date DATETIME,
FOREIGN KEY(user_uuid) REFERENCES users(uuid),
FOREIGN KEY(organization_uuid) REFERENCES organizations(uuid)
);

View File

@@ -0,0 +1 @@
ALTER TABLE collections ADD COLUMN external_id TEXT;

View File

@@ -0,0 +1,5 @@
ALTER TABLE auth_requests
MODIFY master_password_hash TEXT;
ALTER TABLE auth_requests
MODIFY enc_key TEXT;

View File

@@ -0,0 +1,2 @@
ALTER TABLE users_organizations
ADD COLUMN external_id TEXT;

View File

@@ -0,0 +1,2 @@
ALTER TABLE ciphers
ADD COLUMN `key` TEXT;

View File

@@ -0,0 +1 @@
ALTER TABLE devices ADD COLUMN push_uuid TEXT;

View File

@@ -0,0 +1,10 @@
CREATE TABLE organization_api_key (
uuid CHAR(36) NOT NULL,
org_uuid CHAR(36) NOT NULL REFERENCES organizations(uuid),
atype INTEGER NOT NULL,
api_key VARCHAR(255),
revision_date TIMESTAMP NOT NULL,
PRIMARY KEY(uuid, org_uuid)
);
ALTER TABLE users ADD COLUMN external_id TEXT;

View File

@@ -0,0 +1,19 @@
CREATE TABLE auth_requests (
uuid CHAR(36) NOT NULL PRIMARY KEY,
user_uuid CHAR(36) NOT NULL,
organization_uuid CHAR(36),
request_device_identifier CHAR(36) NOT NULL,
device_type INTEGER NOT NULL,
request_ip TEXT NOT NULL,
response_device_id CHAR(36),
access_code TEXT NOT NULL,
public_key TEXT NOT NULL,
enc_key TEXT NOT NULL,
master_password_hash TEXT NOT NULL,
approved BOOLEAN,
creation_date TIMESTAMP NOT NULL,
response_date TIMESTAMP,
authentication_date TIMESTAMP,
FOREIGN KEY(user_uuid) REFERENCES users(uuid),
FOREIGN KEY(organization_uuid) REFERENCES organizations(uuid)
);

View File

@@ -0,0 +1 @@
ALTER TABLE collections ADD COLUMN external_id TEXT;

View File

@@ -0,0 +1,5 @@
ALTER TABLE auth_requests
ALTER COLUMN master_password_hash DROP NOT NULL;
ALTER TABLE auth_requests
ALTER COLUMN enc_key DROP NOT NULL;

View File

@@ -0,0 +1,2 @@
ALTER TABLE users_organizations
ADD COLUMN external_id TEXT;

View File

@@ -0,0 +1,2 @@
ALTER TABLE ciphers
ADD COLUMN "key" TEXT;

View File

@@ -0,0 +1 @@
ALTER TABLE devices ADD COLUMN push_uuid TEXT;

View File

@@ -0,0 +1,11 @@
CREATE TABLE organization_api_key (
uuid TEXT NOT NULL,
org_uuid TEXT NOT NULL,
atype INTEGER NOT NULL,
api_key TEXT NOT NULL,
revision_date DATETIME NOT NULL,
PRIMARY KEY(uuid, org_uuid),
FOREIGN KEY(org_uuid) REFERENCES organizations(uuid)
);
ALTER TABLE users ADD COLUMN external_id TEXT;

View File

@@ -0,0 +1,19 @@
CREATE TABLE auth_requests (
uuid TEXT NOT NULL PRIMARY KEY,
user_uuid TEXT NOT NULL,
organization_uuid TEXT,
request_device_identifier TEXT NOT NULL,
device_type INTEGER NOT NULL,
request_ip TEXT NOT NULL,
response_device_id TEXT,
access_code TEXT NOT NULL,
public_key TEXT NOT NULL,
enc_key TEXT NOT NULL,
master_password_hash TEXT NOT NULL,
approved BOOLEAN,
creation_date DATETIME NOT NULL,
response_date DATETIME,
authentication_date DATETIME,
FOREIGN KEY(user_uuid) REFERENCES users(uuid),
FOREIGN KEY(organization_uuid) REFERENCES organizations(uuid)
);

View File

@@ -0,0 +1 @@
ALTER TABLE collections ADD COLUMN external_id TEXT;

View File

@@ -0,0 +1,29 @@
-- Create new auth_requests table with master_password_hash as nullable column
CREATE TABLE auth_requests_new (
uuid TEXT NOT NULL PRIMARY KEY,
user_uuid TEXT NOT NULL,
organization_uuid TEXT,
request_device_identifier TEXT NOT NULL,
device_type INTEGER NOT NULL,
request_ip TEXT NOT NULL,
response_device_id TEXT,
access_code TEXT NOT NULL,
public_key TEXT NOT NULL,
enc_key TEXT,
master_password_hash TEXT,
approved BOOLEAN,
creation_date DATETIME NOT NULL,
response_date DATETIME,
authentication_date DATETIME,
FOREIGN KEY (user_uuid) REFERENCES users (uuid),
FOREIGN KEY (organization_uuid) REFERENCES organizations (uuid)
);
-- Transfer current data to new table
INSERT INTO auth_requests_new SELECT * FROM auth_requests;
-- Drop the old table
DROP TABLE auth_requests;
-- Rename the new table to the original name
ALTER TABLE auth_requests_new RENAME TO auth_requests;

View File

@@ -0,0 +1,2 @@
-- Add the external_id to the users_organizations table
ALTER TABLE "users_organizations" ADD COLUMN "external_id" TEXT;

View File

@@ -0,0 +1,2 @@
ALTER TABLE ciphers
ADD COLUMN "key" TEXT;

View File

@@ -1 +0,0 @@
1.68.1

4
rust-toolchain.toml Normal file
View File

@@ -0,0 +1,4 @@
[toolchain]
channel = "1.73.0"
components = [ "rustfmt", "clippy" ]
profile = "minimal"

View File

@@ -13,7 +13,7 @@ use rocket::{
};
use crate::{
api::{core::log_event, ApiResult, EmptyResult, JsonResult, Notify, NumberOrString},
api::{core::log_event, unregister_push_device, ApiResult, EmptyResult, JsonResult, Notify, NumberOrString},
auth::{decode_admin, encode_jwt, generate_admin_claims, ClientIp},
config::ConfigBuilder,
db::{backup_database, get_sql_server_version, models::*, DbConn, DbConnType},
@@ -36,6 +36,7 @@ pub fn routes() -> Vec<Route> {
get_user_by_mail_json,
post_admin_login,
admin_page,
admin_page_login,
invite_user,
logout,
delete_user,
@@ -53,7 +54,8 @@ pub fn routes() -> Vec<Route> {
organizations_overview,
delete_organization,
diagnostics,
get_diagnostics_config
get_diagnostics_config,
resend_user_invite,
]
}
@@ -182,12 +184,11 @@ fn post_admin_login(data: Form<LoginForm>, cookies: &CookieJar<'_>, ip: ClientIp
let claims = generate_admin_claims();
let jwt = encode_jwt(&claims);
let cookie = Cookie::build(COOKIE_NAME, jwt)
let cookie = Cookie::build((COOKIE_NAME, jwt))
.path(admin_path())
.max_age(rocket::time::Duration::minutes(CONFIG.admin_session_lifetime()))
.same_site(SameSite::Strict)
.http_only(true)
.finish();
.http_only(true);
cookies.add(cookie);
if let Some(redirect) = redirect {
@@ -255,6 +256,11 @@ fn admin_page(_token: AdminToken) -> ApiResult<Html<String>> {
render_admin_page()
}
#[get("/", rank = 2)]
fn admin_page_login() -> ApiResult<Html<String>> {
render_admin_login(None, None)
}
#[derive(Deserialize, Debug)]
#[allow(non_snake_case)]
struct InviteData {
@@ -272,12 +278,11 @@ async fn get_user_or_404(uuid: &str, conn: &mut DbConn) -> ApiResult<User> {
#[post("/invite", data = "<data>")]
async fn invite_user(data: Json<InviteData>, _token: AdminToken, mut conn: DbConn) -> JsonResult {
let data: InviteData = data.into_inner();
let email = data.email.clone();
if User::find_by_mail(&data.email, &mut conn).await.is_some() {
err_code!("User already exists", Status::Conflict.code)
}
let mut user = User::new(email);
let mut user = User::new(data.email);
async fn _generate_invite(user: &User, conn: &mut DbConn) -> EmptyResult {
if CONFIG.mail_enabled() {
@@ -307,7 +312,7 @@ async fn test_smtp(data: Json<InviteData>, _token: AdminToken) -> EmptyResult {
#[get("/logout")]
fn logout(cookies: &CookieJar<'_>) -> Redirect {
cookies.remove(Cookie::build(COOKIE_NAME, "").path(admin_path()).finish());
cookies.remove(Cookie::build(COOKIE_NAME).path(admin_path()));
Redirect::to(admin_path())
}
@@ -319,6 +324,10 @@ async fn get_users_json(_token: AdminToken, mut conn: DbConn) -> Json<Value> {
let mut usr = u.to_json(&mut conn).await;
usr["UserEnabled"] = json!(u.enabled);
usr["CreatedAt"] = json!(format_naive_datetime_local(&u.created_at, DT_FMT));
usr["LastActive"] = match u.last_active(&mut conn).await {
Some(dt) => json!(format_naive_datetime_local(&dt, DT_FMT)),
None => json!(None::<String>),
};
users_json.push(usr);
}
@@ -348,8 +357,8 @@ async fn users_overview(_token: AdminToken, mut conn: DbConn) -> ApiResult<Html<
}
#[get("/users/by-mail/<mail>")]
async fn get_user_by_mail_json(mail: String, _token: AdminToken, mut conn: DbConn) -> JsonResult {
if let Some(u) = User::find_by_mail(&mail, &mut conn).await {
async fn get_user_by_mail_json(mail: &str, _token: AdminToken, mut conn: DbConn) -> JsonResult {
if let Some(u) = User::find_by_mail(mail, &mut conn).await {
let mut usr = u.to_json(&mut conn).await;
usr["UserEnabled"] = json!(u.enabled);
usr["CreatedAt"] = json!(format_naive_datetime_local(&u.created_at, DT_FMT));
@@ -360,8 +369,8 @@ async fn get_user_by_mail_json(mail: String, _token: AdminToken, mut conn: DbCon
}
#[get("/users/<uuid>")]
async fn get_user_json(uuid: String, _token: AdminToken, mut conn: DbConn) -> JsonResult {
let u = get_user_or_404(&uuid, &mut conn).await?;
async fn get_user_json(uuid: &str, _token: AdminToken, mut conn: DbConn) -> JsonResult {
let u = get_user_or_404(uuid, &mut conn).await?;
let mut usr = u.to_json(&mut conn).await;
usr["UserEnabled"] = json!(u.enabled);
usr["CreatedAt"] = json!(format_naive_datetime_local(&u.created_at, DT_FMT));
@@ -369,18 +378,18 @@ async fn get_user_json(uuid: String, _token: AdminToken, mut conn: DbConn) -> Js
}
#[post("/users/<uuid>/delete")]
async fn delete_user(uuid: String, token: AdminToken, mut conn: DbConn) -> EmptyResult {
let user = get_user_or_404(&uuid, &mut conn).await?;
async fn delete_user(uuid: &str, token: AdminToken, mut conn: DbConn) -> EmptyResult {
let user = get_user_or_404(uuid, &mut conn).await?;
// Get the user_org records before deleting the actual user
let user_orgs = UserOrganization::find_any_state_by_user(&uuid, &mut conn).await;
let user_orgs = UserOrganization::find_any_state_by_user(uuid, &mut conn).await;
let res = user.delete(&mut conn).await;
for user_org in user_orgs {
log_event(
EventType::OrganizationUserRemoved as i32,
&user_org.uuid,
user_org.org_uuid,
&user_org.org_uuid,
String::from(ACTING_ADMIN_USER),
14, // Use UnknownBrowser type
&token.ip.ip,
@@ -393,21 +402,29 @@ async fn delete_user(uuid: String, token: AdminToken, mut conn: DbConn) -> Empty
}
#[post("/users/<uuid>/deauth")]
async fn deauth_user(uuid: String, _token: AdminToken, mut conn: DbConn, nt: Notify<'_>) -> EmptyResult {
let mut user = get_user_or_404(&uuid, &mut conn).await?;
Device::delete_all_by_user(&user.uuid, &mut conn).await?;
user.reset_security_stamp();
let save_result = user.save(&mut conn).await;
async fn deauth_user(uuid: &str, _token: AdminToken, mut conn: DbConn, nt: Notify<'_>) -> EmptyResult {
let mut user = get_user_or_404(uuid, &mut conn).await?;
nt.send_logout(&user, None).await;
save_result
if CONFIG.push_enabled() {
for device in Device::find_push_devices_by_user(&user.uuid, &mut conn).await {
match unregister_push_device(device.uuid).await {
Ok(r) => r,
Err(e) => error!("Unable to unregister devices from Bitwarden server: {}", e),
};
}
}
Device::delete_all_by_user(&user.uuid, &mut conn).await?;
user.reset_security_stamp();
user.save(&mut conn).await
}
#[post("/users/<uuid>/disable")]
async fn disable_user(uuid: String, _token: AdminToken, mut conn: DbConn, nt: Notify<'_>) -> EmptyResult {
let mut user = get_user_or_404(&uuid, &mut conn).await?;
async fn disable_user(uuid: &str, _token: AdminToken, mut conn: DbConn, nt: Notify<'_>) -> EmptyResult {
let mut user = get_user_or_404(uuid, &mut conn).await?;
Device::delete_all_by_user(&user.uuid, &mut conn).await?;
user.reset_security_stamp();
user.enabled = false;
@@ -420,21 +437,39 @@ async fn disable_user(uuid: String, _token: AdminToken, mut conn: DbConn, nt: No
}
#[post("/users/<uuid>/enable")]
async fn enable_user(uuid: String, _token: AdminToken, mut conn: DbConn) -> EmptyResult {
let mut user = get_user_or_404(&uuid, &mut conn).await?;
async fn enable_user(uuid: &str, _token: AdminToken, mut conn: DbConn) -> EmptyResult {
let mut user = get_user_or_404(uuid, &mut conn).await?;
user.enabled = true;
user.save(&mut conn).await
}
#[post("/users/<uuid>/remove-2fa")]
async fn remove_2fa(uuid: String, _token: AdminToken, mut conn: DbConn) -> EmptyResult {
let mut user = get_user_or_404(&uuid, &mut conn).await?;
async fn remove_2fa(uuid: &str, _token: AdminToken, mut conn: DbConn) -> EmptyResult {
let mut user = get_user_or_404(uuid, &mut conn).await?;
TwoFactor::delete_all_by_user(&user.uuid, &mut conn).await?;
user.totp_recover = None;
user.save(&mut conn).await
}
#[post("/users/<uuid>/invite/resend")]
async fn resend_user_invite(uuid: &str, _token: AdminToken, mut conn: DbConn) -> EmptyResult {
if let Some(user) = User::find_by_uuid(uuid, &mut conn).await {
//TODO: replace this with user.status check when it will be available (PR#3397)
if !user.password_hash.is_empty() {
err_code!("User already accepted invitation", Status::BadRequest.code);
}
if CONFIG.mail_enabled() {
mail::send_invite(&user.email, &user.uuid, None, None, &CONFIG.invitation_org_name(), None).await
} else {
Ok(())
}
} else {
err_code!("User doesn't exist", Status::NotFound.code);
}
}
#[derive(Deserialize, Debug)]
struct UserOrgTypeData {
user_type: NumberOrString,
@@ -481,7 +516,7 @@ async fn update_user_org_type(data: Json<UserOrgTypeData>, token: AdminToken, mu
log_event(
EventType::OrganizationUserUpdated as i32,
&user_to_edit.uuid,
data.org_uuid,
&data.org_uuid,
String::from(ACTING_ADMIN_USER),
14, // Use UnknownBrowser type
&token.ip.ip,
@@ -519,8 +554,8 @@ async fn organizations_overview(_token: AdminToken, mut conn: DbConn) -> ApiResu
}
#[post("/organizations/<uuid>/delete")]
async fn delete_organization(uuid: String, _token: AdminToken, mut conn: DbConn) -> EmptyResult {
let org = Organization::find_by_uuid(&uuid, &mut conn).await.map_res("Organization doesn't exist")?;
async fn delete_organization(uuid: &str, _token: AdminToken, mut conn: DbConn) -> EmptyResult {
let org = Organization::find_by_uuid(uuid, &mut conn).await.map_res("Organization doesn't exist")?;
org.delete(&mut conn).await
}
@@ -742,14 +777,24 @@ impl<'r> FromRequest<'r> for AdminToken {
let access_token = match cookies.get(COOKIE_NAME) {
Some(cookie) => cookie.value(),
None => return Outcome::Failure((Status::Unauthorized, "Unauthorized")),
None => {
let requested_page =
request.segments::<std::path::PathBuf>(0..).unwrap_or_default().display().to_string();
// When the requested page is empty, it is `/admin`, in that case, Forward, so it will render the login page
// Else, return a 401 failure, which will be caught
if requested_page.is_empty() {
return Outcome::Forward(Status::Unauthorized);
} else {
return Outcome::Error((Status::Unauthorized, "Unauthorized"));
}
}
};
if decode_admin(access_token).is_err() {
// Remove admin cookie
cookies.remove(Cookie::build(COOKIE_NAME, "").path(admin_path()).finish());
cookies.remove(Cookie::build(COOKIE_NAME).path(admin_path()));
error!("Invalid or expired admin JWT. IP: {}.", &ip.ip);
return Outcome::Failure((Status::Unauthorized, "Session expired"));
return Outcome::Error((Status::Unauthorized, "Session expired"));
}
Outcome::Success(Self {

View File

@@ -1,12 +1,14 @@
use crate::db::DbPool;
use chrono::Utc;
use rocket::serde::json::Json;
use serde_json::Value;
use crate::{
api::{
core::log_user_event, EmptyResult, JsonResult, JsonUpcase, Notify, NumberOrString, PasswordData, UpdateType,
core::log_user_event, register_push_device, unregister_push_device, AnonymousNotify, EmptyResult, JsonResult,
JsonUpcase, Notify, NumberOrString, PasswordOrOtpData, UpdateType,
},
auth::{decode_delete, decode_invite, decode_verify_email, Headers},
auth::{decode_delete, decode_invite, decode_verify_email, ClientHeaders, Headers},
crypto,
db::{models::*, DbConn},
mail, CONFIG,
@@ -35,6 +37,7 @@ pub fn routes() -> Vec<rocket::Route> {
post_verify_email_token,
post_delete_recover,
post_delete_recover_token,
post_device_token,
delete_account,
post_delete_account,
revision_date,
@@ -46,6 +49,14 @@ pub fn routes() -> Vec<rocket::Route> {
get_known_device,
get_known_device_from_path,
put_avatar,
put_device_token,
put_clear_device_token,
post_clear_device_token,
post_auth_request,
get_auth_request,
put_auth_request,
get_auth_request_response,
get_auth_requests,
]
}
@@ -133,7 +144,7 @@ pub async fn _register(data: JsonUpcase<RegisterData>, mut conn: DbConn) -> Json
err!("Registration email does not match invite email")
}
} else if Invitation::take(&email, &mut conn).await {
for mut user_org in UserOrganization::find_invited_by_user(&user.uuid, &mut conn).await.iter_mut() {
for user_org in UserOrganization::find_invited_by_user(&user.uuid, &mut conn).await.iter_mut() {
user_org.status = UserOrgStatus::Accepted as i32;
user_org.save(&mut conn).await?;
}
@@ -169,8 +180,8 @@ pub async fn _register(data: JsonUpcase<RegisterData>, mut conn: DbConn) -> Json
user.client_kdf_iter = client_kdf_iter;
}
user.client_kdf_parallelism = data.KdfMemory;
user.client_kdf_memory = data.KdfParallelism;
user.client_kdf_memory = data.KdfMemory;
user.client_kdf_parallelism = data.KdfParallelism;
user.set_password(&data.MasterPasswordHash, Some(data.Key), true, None);
user.password_hint = password_hint;
@@ -266,8 +277,8 @@ async fn put_avatar(data: JsonUpcase<AvatarData>, headers: Headers, mut conn: Db
}
#[get("/users/<uuid>/public-key")]
async fn get_public_keys(uuid: String, _headers: Headers, mut conn: DbConn) -> JsonResult {
let user = match User::find_by_uuid(&uuid, &mut conn).await {
async fn get_public_keys(uuid: &str, _headers: Headers, mut conn: DbConn) -> JsonResult {
let user = match User::find_by_uuid(uuid, &mut conn).await {
Some(user) => user,
None => err!("User doesn't exist"),
};
@@ -335,7 +346,7 @@ async fn post_password(
let save_result = user.save(&mut conn).await;
// Prevent loging out the client where the user requested this endpoint from.
// Prevent logging out the client where the user requested this endpoint from.
// If you do logout the user it will causes issues at the client side.
// Adding the device uuid will prevent this.
nt.send_logout(&user, Some(headers.device.uuid)).await;
@@ -389,6 +400,9 @@ async fn post_kdf(data: JsonUpcase<ChangeKdfData>, headers: Headers, mut conn: D
} else {
err!("Argon2 parallelism parameter is required.")
}
} else {
user.client_kdf_memory = None;
user.client_kdf_parallelism = None;
}
user.client_kdf_iter = data.KdfIterations;
user.client_kdf_type = data.Kdf;
@@ -479,7 +493,7 @@ async fn post_rotatekey(data: JsonUpcase<KeyData>, headers: Headers, mut conn: D
let save_result = user.save(&mut conn).await;
// Prevent loging out the client where the user requested this endpoint from.
// Prevent logging out the client where the user requested this endpoint from.
// If you do logout the user it will causes issues at the client side.
// Adding the device uuid will prevent this.
nt.send_logout(&user, Some(headers.device.uuid)).await;
@@ -489,17 +503,15 @@ async fn post_rotatekey(data: JsonUpcase<KeyData>, headers: Headers, mut conn: D
#[post("/accounts/security-stamp", data = "<data>")]
async fn post_sstamp(
data: JsonUpcase<PasswordData>,
data: JsonUpcase<PasswordOrOtpData>,
headers: Headers,
mut conn: DbConn,
nt: Notify<'_>,
) -> EmptyResult {
let data: PasswordData = data.into_inner().data;
let data: PasswordOrOtpData = data.into_inner().data;
let mut user = headers.user;
if !user.check_valid_password(&data.MasterPasswordHash) {
err!("Invalid password")
}
data.validate(&user, true, &mut conn).await?;
Device::delete_all_by_user(&user.uuid, &mut conn).await?;
user.reset_security_stamp();
@@ -519,6 +531,10 @@ struct EmailTokenData {
#[post("/accounts/email-token", data = "<data>")]
async fn post_email_token(data: JsonUpcase<EmailTokenData>, headers: Headers, mut conn: DbConn) -> EmptyResult {
if !CONFIG.email_change_allowed() {
err!("Email change is not allowed.");
}
let data: EmailTokenData = data.into_inner().data;
let mut user = headers.user;
@@ -565,6 +581,10 @@ async fn post_email(
mut conn: DbConn,
nt: Notify<'_>,
) -> EmptyResult {
if !CONFIG.email_change_allowed() {
err!("Email change is not allowed.");
}
let data: ChangeEmailData = data.into_inner().data;
let mut user = headers.user;
@@ -714,18 +734,16 @@ async fn post_delete_recover_token(data: JsonUpcase<DeleteRecoverTokenData>, mut
}
#[post("/accounts/delete", data = "<data>")]
async fn post_delete_account(data: JsonUpcase<PasswordData>, headers: Headers, conn: DbConn) -> EmptyResult {
async fn post_delete_account(data: JsonUpcase<PasswordOrOtpData>, headers: Headers, conn: DbConn) -> EmptyResult {
delete_account(data, headers, conn).await
}
#[delete("/accounts", data = "<data>")]
async fn delete_account(data: JsonUpcase<PasswordData>, headers: Headers, mut conn: DbConn) -> EmptyResult {
let data: PasswordData = data.into_inner().data;
async fn delete_account(data: JsonUpcase<PasswordOrOtpData>, headers: Headers, mut conn: DbConn) -> EmptyResult {
let data: PasswordOrOtpData = data.into_inner().data;
let user = headers.user;
if !user.check_valid_password(&data.MasterPasswordHash) {
err!("Invalid password")
}
data.validate(&user, true, &mut conn).await?;
user.delete(&mut conn).await
}
@@ -803,16 +821,13 @@ pub async fn _prelogin(data: JsonUpcase<PreloginData>, mut conn: DbConn) -> Json
None => (User::CLIENT_KDF_TYPE_DEFAULT, User::CLIENT_KDF_ITER_DEFAULT, None, None),
};
let mut result = json!({
let result = json!({
"Kdf": kdf_type,
"KdfIterations": kdf_iter,
"KdfMemory": kdf_mem,
"KdfParallelism": kdf_para,
});
if kdf_type == UserKdfType::Argon2id as i32 {
result["KdfMemory"] = Value::Number(kdf_mem.expect("Argon2 memory parameter is required.").into());
result["KdfParallelism"] = Value::Number(kdf_para.expect("Argon2 parallelism parameter is required.").into());
}
Json(result)
}
@@ -835,20 +850,13 @@ fn verify_password(data: JsonUpcase<SecretVerificationRequest>, headers: Headers
Ok(())
}
async fn _api_key(
data: JsonUpcase<SecretVerificationRequest>,
rotate: bool,
headers: Headers,
mut conn: DbConn,
) -> JsonResult {
async fn _api_key(data: JsonUpcase<PasswordOrOtpData>, rotate: bool, headers: Headers, mut conn: DbConn) -> JsonResult {
use crate::util::format_date;
let data: SecretVerificationRequest = data.into_inner().data;
let data: PasswordOrOtpData = data.into_inner().data;
let mut user = headers.user;
if !user.check_valid_password(&data.MasterPasswordHash) {
err!("Invalid password")
}
data.validate(&user, true, &mut conn).await?;
if rotate || user.api_key.is_none() {
user.api_key = Some(crypto::generate_api_key());
@@ -863,29 +871,29 @@ async fn _api_key(
}
#[post("/accounts/api-key", data = "<data>")]
async fn api_key(data: JsonUpcase<SecretVerificationRequest>, headers: Headers, conn: DbConn) -> JsonResult {
async fn api_key(data: JsonUpcase<PasswordOrOtpData>, headers: Headers, conn: DbConn) -> JsonResult {
_api_key(data, false, headers, conn).await
}
#[post("/accounts/rotate-api-key", data = "<data>")]
async fn rotate_api_key(data: JsonUpcase<SecretVerificationRequest>, headers: Headers, conn: DbConn) -> JsonResult {
async fn rotate_api_key(data: JsonUpcase<PasswordOrOtpData>, headers: Headers, conn: DbConn) -> JsonResult {
_api_key(data, true, headers, conn).await
}
// This variant is deprecated: https://github.com/bitwarden/server/pull/2682
#[get("/devices/knowndevice/<email>/<uuid>")]
async fn get_known_device_from_path(email: String, uuid: String, mut conn: DbConn) -> JsonResult {
async fn get_known_device_from_path(email: &str, uuid: &str, mut conn: DbConn) -> JsonResult {
// This endpoint doesn't have auth header
let mut result = false;
if let Some(user) = User::find_by_mail(&email, &mut conn).await {
result = Device::find_by_uuid_and_user(&uuid, &user.uuid, &mut conn).await.is_some();
if let Some(user) = User::find_by_mail(email, &mut conn).await {
result = Device::find_by_uuid_and_user(uuid, &user.uuid, &mut conn).await.is_some();
}
Ok(Json(json!(result)))
}
#[get("/devices/knowndevice")]
async fn get_known_device(device: KnownDevice, conn: DbConn) -> JsonResult {
get_known_device_from_path(device.email, device.uuid, conn).await
get_known_device_from_path(&device.email, &device.uuid, conn).await
}
struct KnownDevice {
@@ -899,29 +907,26 @@ impl<'r> FromRequest<'r> for KnownDevice {
async fn from_request(req: &'r Request<'_>) -> Outcome<Self, Self::Error> {
let email = if let Some(email_b64) = req.headers().get_one("X-Request-Email") {
let email_bytes = match data_encoding::BASE64URL.decode(email_b64.as_bytes()) {
let email_bytes = match data_encoding::BASE64URL_NOPAD.decode(email_b64.as_bytes()) {
Ok(bytes) => bytes,
Err(_) => {
return Outcome::Failure((
Status::BadRequest,
"X-Request-Email value failed to decode as base64url",
));
return Outcome::Error((Status::BadRequest, "X-Request-Email value failed to decode as base64url"));
}
};
match String::from_utf8(email_bytes) {
Ok(email) => email,
Err(_) => {
return Outcome::Failure((Status::BadRequest, "X-Request-Email value failed to decode as UTF-8"));
return Outcome::Error((Status::BadRequest, "X-Request-Email value failed to decode as UTF-8"));
}
}
} else {
return Outcome::Failure((Status::BadRequest, "X-Request-Email value is required"));
return Outcome::Error((Status::BadRequest, "X-Request-Email value is required"));
};
let uuid = if let Some(uuid) = req.headers().get_one("X-Device-Identifier") {
uuid.to_string()
} else {
return Outcome::Failure((Status::BadRequest, "X-Device-Identifier value is required"));
return Outcome::Error((Status::BadRequest, "X-Device-Identifier value is required"));
};
Outcome::Success(KnownDevice {
@@ -930,3 +935,272 @@ impl<'r> FromRequest<'r> for KnownDevice {
})
}
}
#[derive(Deserialize)]
#[allow(non_snake_case)]
struct PushToken {
PushToken: String,
}
#[post("/devices/identifier/<uuid>/token", data = "<data>")]
async fn post_device_token(uuid: &str, data: JsonUpcase<PushToken>, headers: Headers, conn: DbConn) -> EmptyResult {
put_device_token(uuid, data, headers, conn).await
}
#[put("/devices/identifier/<uuid>/token", data = "<data>")]
async fn put_device_token(uuid: &str, data: JsonUpcase<PushToken>, headers: Headers, mut conn: DbConn) -> EmptyResult {
if !CONFIG.push_enabled() {
return Ok(());
}
let data = data.into_inner().data;
let token = data.PushToken;
let mut device = match Device::find_by_uuid_and_user(&headers.device.uuid, &headers.user.uuid, &mut conn).await {
Some(device) => device,
None => err!(format!("Error: device {uuid} should be present before a token can be assigned")),
};
device.push_token = Some(token);
if device.push_uuid.is_none() {
device.push_uuid = Some(uuid::Uuid::new_v4().to_string());
}
if let Err(e) = device.save(&mut conn).await {
err!(format!("An error occurred while trying to save the device push token: {e}"));
}
if let Err(e) = register_push_device(headers.user.uuid, device).await {
err!(format!("An error occurred while proceeding registration of a device: {e}"));
}
Ok(())
}
#[put("/devices/identifier/<uuid>/clear-token")]
async fn put_clear_device_token(uuid: &str, mut conn: DbConn) -> EmptyResult {
// This only clears push token
// https://github.com/bitwarden/core/blob/master/src/Api/Controllers/DevicesController.cs#L109
// https://github.com/bitwarden/core/blob/master/src/Core/Services/Implementations/DeviceService.cs#L37
// This is somehow not implemented in any app, added it in case it is required
if !CONFIG.push_enabled() {
return Ok(());
}
if let Some(device) = Device::find_by_uuid(uuid, &mut conn).await {
Device::clear_push_token_by_uuid(uuid, &mut conn).await?;
unregister_push_device(device.uuid).await?;
}
Ok(())
}
// On upstream server, both PUT and POST are declared. Implementing the POST method in case it would be useful somewhere
#[post("/devices/identifier/<uuid>/clear-token")]
async fn post_clear_device_token(uuid: &str, conn: DbConn) -> EmptyResult {
put_clear_device_token(uuid, conn).await
}
#[derive(Debug, Deserialize)]
#[allow(non_snake_case)]
struct AuthRequestRequest {
accessCode: String,
deviceIdentifier: String,
email: String,
publicKey: String,
#[serde(alias = "type")]
_type: i32,
}
#[post("/auth-requests", data = "<data>")]
async fn post_auth_request(
data: Json<AuthRequestRequest>,
headers: ClientHeaders,
mut conn: DbConn,
nt: Notify<'_>,
) -> JsonResult {
let data = data.into_inner();
let user = match User::find_by_mail(&data.email, &mut conn).await {
Some(user) => user,
None => {
err!("AuthRequest doesn't exist")
}
};
let mut auth_request = AuthRequest::new(
user.uuid.clone(),
data.deviceIdentifier.clone(),
headers.device_type,
headers.ip.ip.to_string(),
data.accessCode,
data.publicKey,
);
auth_request.save(&mut conn).await?;
nt.send_auth_request(&user.uuid, &auth_request.uuid, &data.deviceIdentifier, &mut conn).await;
Ok(Json(json!({
"id": auth_request.uuid,
"publicKey": auth_request.public_key,
"requestDeviceType": DeviceType::from_i32(auth_request.device_type).to_string(),
"requestIpAddress": auth_request.request_ip,
"key": null,
"masterPasswordHash": null,
"creationDate": auth_request.creation_date.and_utc(),
"responseDate": null,
"requestApproved": false,
"origin": CONFIG.domain_origin(),
"object": "auth-request"
})))
}
#[get("/auth-requests/<uuid>")]
async fn get_auth_request(uuid: &str, mut conn: DbConn) -> JsonResult {
let auth_request = match AuthRequest::find_by_uuid(uuid, &mut conn).await {
Some(auth_request) => auth_request,
None => {
err!("AuthRequest doesn't exist")
}
};
let response_date_utc = auth_request.response_date.map(|response_date| response_date.and_utc());
Ok(Json(json!(
{
"id": uuid,
"publicKey": auth_request.public_key,
"requestDeviceType": DeviceType::from_i32(auth_request.device_type).to_string(),
"requestIpAddress": auth_request.request_ip,
"key": auth_request.enc_key,
"masterPasswordHash": auth_request.master_password_hash,
"creationDate": auth_request.creation_date.and_utc(),
"responseDate": response_date_utc,
"requestApproved": auth_request.approved,
"origin": CONFIG.domain_origin(),
"object":"auth-request"
}
)))
}
#[derive(Debug, Deserialize)]
#[allow(non_snake_case)]
struct AuthResponseRequest {
deviceIdentifier: String,
key: String,
masterPasswordHash: Option<String>,
requestApproved: bool,
}
#[put("/auth-requests/<uuid>", data = "<data>")]
async fn put_auth_request(
uuid: &str,
data: Json<AuthResponseRequest>,
mut conn: DbConn,
ant: AnonymousNotify<'_>,
nt: Notify<'_>,
) -> JsonResult {
let data = data.into_inner();
let mut auth_request: AuthRequest = match AuthRequest::find_by_uuid(uuid, &mut conn).await {
Some(auth_request) => auth_request,
None => {
err!("AuthRequest doesn't exist")
}
};
auth_request.approved = Some(data.requestApproved);
auth_request.enc_key = Some(data.key);
auth_request.master_password_hash = data.masterPasswordHash;
auth_request.response_device_id = Some(data.deviceIdentifier.clone());
auth_request.save(&mut conn).await?;
if auth_request.approved.unwrap_or(false) {
ant.send_auth_response(&auth_request.user_uuid, &auth_request.uuid).await;
nt.send_auth_response(&auth_request.user_uuid, &auth_request.uuid, data.deviceIdentifier, &mut conn).await;
}
let response_date_utc = auth_request.response_date.map(|response_date| response_date.and_utc());
Ok(Json(json!(
{
"id": uuid,
"publicKey": auth_request.public_key,
"requestDeviceType": DeviceType::from_i32(auth_request.device_type).to_string(),
"requestIpAddress": auth_request.request_ip,
"key": auth_request.enc_key,
"masterPasswordHash": auth_request.master_password_hash,
"creationDate": auth_request.creation_date.and_utc(),
"responseDate": response_date_utc,
"requestApproved": auth_request.approved,
"origin": CONFIG.domain_origin(),
"object":"auth-request"
}
)))
}
#[get("/auth-requests/<uuid>/response?<code>")]
async fn get_auth_request_response(uuid: &str, code: &str, mut conn: DbConn) -> JsonResult {
let auth_request = match AuthRequest::find_by_uuid(uuid, &mut conn).await {
Some(auth_request) => auth_request,
None => {
err!("AuthRequest doesn't exist")
}
};
if !auth_request.check_access_code(code) {
err!("Access code invalid doesn't exist")
}
let response_date_utc = auth_request.response_date.map(|response_date| response_date.and_utc());
Ok(Json(json!(
{
"id": uuid,
"publicKey": auth_request.public_key,
"requestDeviceType": DeviceType::from_i32(auth_request.device_type).to_string(),
"requestIpAddress": auth_request.request_ip,
"key": auth_request.enc_key,
"masterPasswordHash": auth_request.master_password_hash,
"creationDate": auth_request.creation_date.and_utc(),
"responseDate": response_date_utc,
"requestApproved": auth_request.approved,
"origin": CONFIG.domain_origin(),
"object":"auth-request"
}
)))
}
#[get("/auth-requests")]
async fn get_auth_requests(headers: Headers, mut conn: DbConn) -> JsonResult {
let auth_requests = AuthRequest::find_by_user(&headers.user.uuid, &mut conn).await;
Ok(Json(json!({
"data": auth_requests
.iter()
.filter(|request| request.approved.is_none())
.map(|request| {
let response_date_utc = request.response_date.map(|response_date| response_date.and_utc());
json!({
"id": request.uuid,
"publicKey": request.public_key,
"requestDeviceType": DeviceType::from_i32(request.device_type).to_string(),
"requestIpAddress": request.request_ip,
"key": request.enc_key,
"masterPasswordHash": request.master_password_hash,
"creationDate": request.creation_date.and_utc(),
"responseDate": response_date_utc,
"requestApproved": request.approved,
"origin": CONFIG.domain_origin(),
"object":"auth-request"
})
}).collect::<Vec<Value>>(),
"continuationToken": null,
"object": "list"
})))
}
pub async fn purge_auth_requests(pool: DbPool) {
debug!("Purging auth requests");
if let Ok(mut conn) = pool.get().await {
AuthRequest::purge_expired_auth_requests(&mut conn).await;
} else {
error!("Failed to get DB connection while purging trashed ciphers")
}
}

View File

@@ -10,7 +10,7 @@ use rocket::{
use serde_json::Value;
use crate::{
api::{self, core::log_event, EmptyResult, JsonResult, JsonUpcase, Notify, PasswordData, UpdateType},
api::{self, core::log_event, EmptyResult, JsonResult, JsonUpcase, Notify, PasswordOrOtpData, UpdateType},
auth::Headers,
crypto,
db::{models::*, DbConn, DbPool},
@@ -172,8 +172,8 @@ async fn get_ciphers(headers: Headers, mut conn: DbConn) -> Json<Value> {
}
#[get("/ciphers/<uuid>")]
async fn get_cipher(uuid: String, headers: Headers, mut conn: DbConn) -> JsonResult {
let cipher = match Cipher::find_by_uuid(&uuid, &mut conn).await {
async fn get_cipher(uuid: &str, headers: Headers, mut conn: DbConn) -> JsonResult {
let cipher = match Cipher::find_by_uuid(uuid, &mut conn).await {
Some(cipher) => cipher,
None => err!("Cipher doesn't exist"),
};
@@ -186,13 +186,13 @@ async fn get_cipher(uuid: String, headers: Headers, mut conn: DbConn) -> JsonRes
}
#[get("/ciphers/<uuid>/admin")]
async fn get_cipher_admin(uuid: String, headers: Headers, conn: DbConn) -> JsonResult {
async fn get_cipher_admin(uuid: &str, headers: Headers, conn: DbConn) -> JsonResult {
// TODO: Implement this correctly
get_cipher(uuid, headers, conn).await
}
#[get("/ciphers/<uuid>/details")]
async fn get_cipher_details(uuid: String, headers: Headers, conn: DbConn) -> JsonResult {
async fn get_cipher_details(uuid: &str, headers: Headers, conn: DbConn) -> JsonResult {
get_cipher(uuid, headers, conn).await
}
@@ -206,6 +206,8 @@ pub struct CipherData {
// TODO: Some of these might appear all the time, no need for Option
OrganizationId: Option<String>,
Key: Option<String>,
/*
Login = 1,
SecureNote = 2,
@@ -357,14 +359,17 @@ pub async fn update_cipher_from_data(
enforce_personal_ownership_policy(Some(&data), headers, conn).await?;
// Check that the client isn't updating an existing cipher with stale data.
if let Some(dt) = data.LastKnownRevisionDate {
match NaiveDateTime::parse_from_str(&dt, "%+") {
// ISO 8601 format
Err(err) => warn!("Error parsing LastKnownRevisionDate '{}': {}", dt, err),
Ok(dt) if cipher.updated_at.signed_duration_since(dt).num_seconds() > 1 => {
err!("The client copy of this cipher is out of date. Resync the client and try again.")
// And only perform this check when not importing ciphers, else the date/time check will fail.
if ut != UpdateType::None {
if let Some(dt) = data.LastKnownRevisionDate {
match NaiveDateTime::parse_from_str(&dt, "%+") {
// ISO 8601 format
Err(err) => warn!("Error parsing LastKnownRevisionDate '{}': {}", dt, err),
Ok(dt) if cipher.updated_at.signed_duration_since(dt).num_seconds() > 1 => {
err!("The client copy of this cipher is out of date. Resync the client and try again.")
}
Ok(_) => (),
}
Ok(_) => (),
}
}
@@ -480,6 +485,7 @@ pub async fn update_cipher_from_data(
None => err!("Data missing"),
};
cipher.key = data.Key;
cipher.name = data.Name;
cipher.notes = data.Notes;
cipher.fields = data.Fields.map(|f| _clean_cipher_data(f).to_string());
@@ -503,7 +509,7 @@ pub async fn update_cipher_from_data(
log_event(
event_type as i32,
&cipher.uuid,
String::from(org_uuid),
org_uuid,
headers.user.uuid.clone(),
headers.device.atype,
&headers.ip.ip,
@@ -511,10 +517,9 @@ pub async fn update_cipher_from_data(
)
.await;
}
nt.send_cipher_update(ut, cipher, &cipher.update_users_revision(conn).await, &headers.device.uuid).await;
nt.send_cipher_update(ut, cipher, &cipher.update_users_revision(conn).await, &headers.device.uuid, None, conn)
.await;
}
Ok(())
}
@@ -580,13 +585,14 @@ async fn post_ciphers_import(
let mut user = headers.user;
user.update_revision(&mut conn).await?;
nt.send_user_update(UpdateType::SyncVault, &user).await;
Ok(())
}
/// Called when an org admin modifies an existing org cipher.
#[put("/ciphers/<uuid>/admin", data = "<data>")]
async fn put_cipher_admin(
uuid: String,
uuid: &str,
data: JsonUpcase<CipherData>,
headers: Headers,
conn: DbConn,
@@ -597,7 +603,7 @@ async fn put_cipher_admin(
#[post("/ciphers/<uuid>/admin", data = "<data>")]
async fn post_cipher_admin(
uuid: String,
uuid: &str,
data: JsonUpcase<CipherData>,
headers: Headers,
conn: DbConn,
@@ -608,7 +614,7 @@ async fn post_cipher_admin(
#[post("/ciphers/<uuid>", data = "<data>")]
async fn post_cipher(
uuid: String,
uuid: &str,
data: JsonUpcase<CipherData>,
headers: Headers,
conn: DbConn,
@@ -619,7 +625,7 @@ async fn post_cipher(
#[put("/ciphers/<uuid>", data = "<data>")]
async fn put_cipher(
uuid: String,
uuid: &str,
data: JsonUpcase<CipherData>,
headers: Headers,
mut conn: DbConn,
@@ -627,7 +633,7 @@ async fn put_cipher(
) -> JsonResult {
let data: CipherData = data.into_inner().data;
let mut cipher = match Cipher::find_by_uuid(&uuid, &mut conn).await {
let mut cipher = match Cipher::find_by_uuid(uuid, &mut conn).await {
Some(cipher) => cipher,
None => err!("Cipher doesn't exist"),
};
@@ -648,7 +654,7 @@ async fn put_cipher(
#[post("/ciphers/<uuid>/partial", data = "<data>")]
async fn post_cipher_partial(
uuid: String,
uuid: &str,
data: JsonUpcase<PartialCipherData>,
headers: Headers,
conn: DbConn,
@@ -659,14 +665,14 @@ async fn post_cipher_partial(
// Only update the folder and favorite for the user, since this cipher is read-only
#[put("/ciphers/<uuid>/partial", data = "<data>")]
async fn put_cipher_partial(
uuid: String,
uuid: &str,
data: JsonUpcase<PartialCipherData>,
headers: Headers,
mut conn: DbConn,
) -> JsonResult {
let data: PartialCipherData = data.into_inner().data;
let cipher = match Cipher::find_by_uuid(&uuid, &mut conn).await {
let cipher = match Cipher::find_by_uuid(uuid, &mut conn).await {
Some(cipher) => cipher,
None => err!("Cipher doesn't exist"),
};
@@ -698,44 +704,48 @@ struct CollectionsAdminData {
#[put("/ciphers/<uuid>/collections", data = "<data>")]
async fn put_collections_update(
uuid: String,
uuid: &str,
data: JsonUpcase<CollectionsAdminData>,
headers: Headers,
conn: DbConn,
nt: Notify<'_>,
) -> EmptyResult {
post_collections_admin(uuid, data, headers, conn).await
post_collections_admin(uuid, data, headers, conn, nt).await
}
#[post("/ciphers/<uuid>/collections", data = "<data>")]
async fn post_collections_update(
uuid: String,
uuid: &str,
data: JsonUpcase<CollectionsAdminData>,
headers: Headers,
conn: DbConn,
nt: Notify<'_>,
) -> EmptyResult {
post_collections_admin(uuid, data, headers, conn).await
post_collections_admin(uuid, data, headers, conn, nt).await
}
#[put("/ciphers/<uuid>/collections-admin", data = "<data>")]
async fn put_collections_admin(
uuid: String,
uuid: &str,
data: JsonUpcase<CollectionsAdminData>,
headers: Headers,
conn: DbConn,
nt: Notify<'_>,
) -> EmptyResult {
post_collections_admin(uuid, data, headers, conn).await
post_collections_admin(uuid, data, headers, conn, nt).await
}
#[post("/ciphers/<uuid>/collections-admin", data = "<data>")]
async fn post_collections_admin(
uuid: String,
uuid: &str,
data: JsonUpcase<CollectionsAdminData>,
headers: Headers,
mut conn: DbConn,
nt: Notify<'_>,
) -> EmptyResult {
let data: CollectionsAdminData = data.into_inner().data;
let cipher = match Cipher::find_by_uuid(&uuid, &mut conn).await {
let cipher = match Cipher::find_by_uuid(uuid, &mut conn).await {
Some(cipher) => cipher,
None => err!("Cipher doesn't exist"),
};
@@ -767,10 +777,20 @@ async fn post_collections_admin(
}
}
nt.send_cipher_update(
UpdateType::SyncCipherUpdate,
&cipher,
&cipher.update_users_revision(&mut conn).await,
&headers.device.uuid,
Some(Vec::from_iter(posted_collections)),
&mut conn,
)
.await;
log_event(
EventType::CipherUpdatedCollections as i32,
&cipher.uuid,
cipher.organization_uuid.unwrap(),
&cipher.organization_uuid.unwrap(),
headers.user.uuid.clone(),
headers.device.atype,
&headers.ip.ip,
@@ -790,7 +810,7 @@ struct ShareCipherData {
#[post("/ciphers/<uuid>/share", data = "<data>")]
async fn post_cipher_share(
uuid: String,
uuid: &str,
data: JsonUpcase<ShareCipherData>,
headers: Headers,
mut conn: DbConn,
@@ -798,12 +818,12 @@ async fn post_cipher_share(
) -> JsonResult {
let data: ShareCipherData = data.into_inner().data;
share_cipher_by_uuid(&uuid, data, &headers, &mut conn, &nt).await
share_cipher_by_uuid(uuid, data, &headers, &mut conn, &nt).await
}
#[put("/ciphers/<uuid>/share", data = "<data>")]
async fn put_cipher_share(
uuid: String,
uuid: &str,
data: JsonUpcase<ShareCipherData>,
headers: Headers,
mut conn: DbConn,
@@ -811,7 +831,7 @@ async fn put_cipher_share(
) -> JsonResult {
let data: ShareCipherData = data.into_inner().data;
share_cipher_by_uuid(&uuid, data, &headers, &mut conn, &nt).await
share_cipher_by_uuid(uuid, data, &headers, &mut conn, &nt).await
}
#[derive(Deserialize)]
@@ -916,8 +936,17 @@ async fn share_cipher_by_uuid(
/// their object storage service. For self-hosted instances, it basically just
/// redirects to the same location as before the v2 API.
#[get("/ciphers/<uuid>/attachment/<attachment_id>")]
async fn get_attachment(uuid: String, attachment_id: String, headers: Headers, mut conn: DbConn) -> JsonResult {
match Attachment::find_by_id(&attachment_id, &mut conn).await {
async fn get_attachment(uuid: &str, attachment_id: &str, headers: Headers, mut conn: DbConn) -> JsonResult {
let cipher = match Cipher::find_by_uuid(uuid, &mut conn).await {
Some(cipher) => cipher,
None => err!("Cipher doesn't exist"),
};
if !cipher.is_accessible_to_user(&headers.user.uuid, &mut conn).await {
err!("Cipher is not accessible")
}
match Attachment::find_by_id(attachment_id, &mut conn).await {
Some(attachment) if uuid == attachment.cipher_uuid => Ok(Json(attachment.to_json(&headers.host))),
Some(_) => err!("Attachment doesn't belong to cipher"),
None => err!("Attachment doesn't exist"),
@@ -944,12 +973,12 @@ enum FileUploadType {
/// For self-hosted instances, it's another API on the local instance.
#[post("/ciphers/<uuid>/attachment/v2", data = "<data>")]
async fn post_attachment_v2(
uuid: String,
uuid: &str,
data: JsonUpcase<AttachmentRequestData>,
headers: Headers,
mut conn: DbConn,
) -> JsonResult {
let cipher = match Cipher::find_by_uuid(&uuid, &mut conn).await {
let cipher = match Cipher::find_by_uuid(uuid, &mut conn).await {
Some(cipher) => cipher,
None => err!("Cipher doesn't exist"),
};
@@ -995,13 +1024,13 @@ struct UploadData<'f> {
/// database record, which is passed in as `attachment`.
async fn save_attachment(
mut attachment: Option<Attachment>,
cipher_uuid: String,
cipher_uuid: &str,
data: Form<UploadData<'_>>,
headers: &Headers,
mut conn: DbConn,
nt: Notify<'_>,
) -> Result<(Cipher, DbConn), crate::error::Error> {
let cipher = match Cipher::find_by_uuid(&cipher_uuid, &mut conn).await {
let cipher = match Cipher::find_by_uuid(cipher_uuid, &mut conn).await {
Some(cipher) => cipher,
None => err!("Cipher doesn't exist"),
};
@@ -1058,7 +1087,7 @@ async fn save_attachment(
None => crypto::generate_attachment_id(), // Legacy API
};
let folder_path = tokio::fs::canonicalize(&CONFIG.attachments_folder()).await?.join(&cipher_uuid);
let folder_path = tokio::fs::canonicalize(&CONFIG.attachments_folder()).await?.join(cipher_uuid);
let file_path = folder_path.join(&file_id);
tokio::fs::create_dir_all(&folder_path).await?;
@@ -1094,7 +1123,8 @@ async fn save_attachment(
if data.key.is_none() {
err!("No attachment key provided")
}
let attachment = Attachment::new(file_id, cipher_uuid.clone(), encrypted_filename.unwrap(), size, data.key);
let attachment =
Attachment::new(file_id, String::from(cipher_uuid), encrypted_filename.unwrap(), size, data.key);
attachment.save(&mut conn).await.expect("Error saving attachment");
}
@@ -1107,6 +1137,8 @@ async fn save_attachment(
&cipher,
&cipher.update_users_revision(&mut conn).await,
&headers.device.uuid,
None,
&mut conn,
)
.await;
@@ -1114,7 +1146,7 @@ async fn save_attachment(
log_event(
EventType::CipherAttachmentCreated as i32,
&cipher.uuid,
String::from(org_uuid),
org_uuid,
headers.user.uuid.clone(),
headers.device.atype,
&headers.ip.ip,
@@ -1132,14 +1164,14 @@ async fn save_attachment(
/// with this one.
#[post("/ciphers/<uuid>/attachment/<attachment_id>", format = "multipart/form-data", data = "<data>", rank = 1)]
async fn post_attachment_v2_data(
uuid: String,
attachment_id: String,
uuid: &str,
attachment_id: &str,
data: Form<UploadData<'_>>,
headers: Headers,
mut conn: DbConn,
nt: Notify<'_>,
) -> EmptyResult {
let attachment = match Attachment::find_by_id(&attachment_id, &mut conn).await {
let attachment = match Attachment::find_by_id(attachment_id, &mut conn).await {
Some(attachment) if uuid == attachment.cipher_uuid => Some(attachment),
Some(_) => err!("Attachment doesn't belong to cipher"),
None => err!("Attachment doesn't exist"),
@@ -1153,7 +1185,7 @@ async fn post_attachment_v2_data(
/// Legacy API for creating an attachment associated with a cipher.
#[post("/ciphers/<uuid>/attachment", format = "multipart/form-data", data = "<data>")]
async fn post_attachment(
uuid: String,
uuid: &str,
data: Form<UploadData<'_>>,
headers: Headers,
conn: DbConn,
@@ -1170,7 +1202,7 @@ async fn post_attachment(
#[post("/ciphers/<uuid>/attachment-admin", format = "multipart/form-data", data = "<data>")]
async fn post_attachment_admin(
uuid: String,
uuid: &str,
data: Form<UploadData<'_>>,
headers: Headers,
conn: DbConn,
@@ -1181,21 +1213,21 @@ async fn post_attachment_admin(
#[post("/ciphers/<uuid>/attachment/<attachment_id>/share", format = "multipart/form-data", data = "<data>")]
async fn post_attachment_share(
uuid: String,
attachment_id: String,
uuid: &str,
attachment_id: &str,
data: Form<UploadData<'_>>,
headers: Headers,
mut conn: DbConn,
nt: Notify<'_>,
) -> JsonResult {
_delete_cipher_attachment_by_id(&uuid, &attachment_id, &headers, &mut conn, &nt).await?;
_delete_cipher_attachment_by_id(uuid, attachment_id, &headers, &mut conn, &nt).await?;
post_attachment(uuid, data, headers, conn, nt).await
}
#[post("/ciphers/<uuid>/attachment/<attachment_id>/delete-admin")]
async fn delete_attachment_post_admin(
uuid: String,
attachment_id: String,
uuid: &str,
attachment_id: &str,
headers: Headers,
conn: DbConn,
nt: Notify<'_>,
@@ -1205,8 +1237,8 @@ async fn delete_attachment_post_admin(
#[post("/ciphers/<uuid>/attachment/<attachment_id>/delete")]
async fn delete_attachment_post(
uuid: String,
attachment_id: String,
uuid: &str,
attachment_id: &str,
headers: Headers,
conn: DbConn,
nt: Notify<'_>,
@@ -1216,58 +1248,58 @@ async fn delete_attachment_post(
#[delete("/ciphers/<uuid>/attachment/<attachment_id>")]
async fn delete_attachment(
uuid: String,
attachment_id: String,
uuid: &str,
attachment_id: &str,
headers: Headers,
mut conn: DbConn,
nt: Notify<'_>,
) -> EmptyResult {
_delete_cipher_attachment_by_id(&uuid, &attachment_id, &headers, &mut conn, &nt).await
_delete_cipher_attachment_by_id(uuid, attachment_id, &headers, &mut conn, &nt).await
}
#[delete("/ciphers/<uuid>/attachment/<attachment_id>/admin")]
async fn delete_attachment_admin(
uuid: String,
attachment_id: String,
uuid: &str,
attachment_id: &str,
headers: Headers,
mut conn: DbConn,
nt: Notify<'_>,
) -> EmptyResult {
_delete_cipher_attachment_by_id(&uuid, &attachment_id, &headers, &mut conn, &nt).await
_delete_cipher_attachment_by_id(uuid, attachment_id, &headers, &mut conn, &nt).await
}
#[post("/ciphers/<uuid>/delete")]
async fn delete_cipher_post(uuid: String, headers: Headers, mut conn: DbConn, nt: Notify<'_>) -> EmptyResult {
_delete_cipher_by_uuid(&uuid, &headers, &mut conn, false, &nt).await
async fn delete_cipher_post(uuid: &str, headers: Headers, mut conn: DbConn, nt: Notify<'_>) -> EmptyResult {
_delete_cipher_by_uuid(uuid, &headers, &mut conn, false, &nt).await
// permanent delete
}
#[post("/ciphers/<uuid>/delete-admin")]
async fn delete_cipher_post_admin(uuid: String, headers: Headers, mut conn: DbConn, nt: Notify<'_>) -> EmptyResult {
_delete_cipher_by_uuid(&uuid, &headers, &mut conn, false, &nt).await
async fn delete_cipher_post_admin(uuid: &str, headers: Headers, mut conn: DbConn, nt: Notify<'_>) -> EmptyResult {
_delete_cipher_by_uuid(uuid, &headers, &mut conn, false, &nt).await
// permanent delete
}
#[put("/ciphers/<uuid>/delete")]
async fn delete_cipher_put(uuid: String, headers: Headers, mut conn: DbConn, nt: Notify<'_>) -> EmptyResult {
_delete_cipher_by_uuid(&uuid, &headers, &mut conn, true, &nt).await
async fn delete_cipher_put(uuid: &str, headers: Headers, mut conn: DbConn, nt: Notify<'_>) -> EmptyResult {
_delete_cipher_by_uuid(uuid, &headers, &mut conn, true, &nt).await
// soft delete
}
#[put("/ciphers/<uuid>/delete-admin")]
async fn delete_cipher_put_admin(uuid: String, headers: Headers, mut conn: DbConn, nt: Notify<'_>) -> EmptyResult {
_delete_cipher_by_uuid(&uuid, &headers, &mut conn, true, &nt).await
async fn delete_cipher_put_admin(uuid: &str, headers: Headers, mut conn: DbConn, nt: Notify<'_>) -> EmptyResult {
_delete_cipher_by_uuid(uuid, &headers, &mut conn, true, &nt).await
}
#[delete("/ciphers/<uuid>")]
async fn delete_cipher(uuid: String, headers: Headers, mut conn: DbConn, nt: Notify<'_>) -> EmptyResult {
_delete_cipher_by_uuid(&uuid, &headers, &mut conn, false, &nt).await
async fn delete_cipher(uuid: &str, headers: Headers, mut conn: DbConn, nt: Notify<'_>) -> EmptyResult {
_delete_cipher_by_uuid(uuid, &headers, &mut conn, false, &nt).await
// permanent delete
}
#[delete("/ciphers/<uuid>/admin")]
async fn delete_cipher_admin(uuid: String, headers: Headers, mut conn: DbConn, nt: Notify<'_>) -> EmptyResult {
_delete_cipher_by_uuid(&uuid, &headers, &mut conn, false, &nt).await
async fn delete_cipher_admin(uuid: &str, headers: Headers, mut conn: DbConn, nt: Notify<'_>) -> EmptyResult {
_delete_cipher_by_uuid(uuid, &headers, &mut conn, false, &nt).await
// permanent delete
}
@@ -1332,13 +1364,13 @@ async fn delete_cipher_selected_put_admin(
}
#[put("/ciphers/<uuid>/restore")]
async fn restore_cipher_put(uuid: String, headers: Headers, mut conn: DbConn, nt: Notify<'_>) -> JsonResult {
_restore_cipher_by_uuid(&uuid, &headers, &mut conn, &nt).await
async fn restore_cipher_put(uuid: &str, headers: Headers, mut conn: DbConn, nt: Notify<'_>) -> JsonResult {
_restore_cipher_by_uuid(uuid, &headers, &mut conn, &nt).await
}
#[put("/ciphers/<uuid>/restore-admin")]
async fn restore_cipher_put_admin(uuid: String, headers: Headers, mut conn: DbConn, nt: Notify<'_>) -> JsonResult {
_restore_cipher_by_uuid(&uuid, &headers, &mut conn, &nt).await
async fn restore_cipher_put_admin(uuid: &str, headers: Headers, mut conn: DbConn, nt: Notify<'_>) -> JsonResult {
_restore_cipher_by_uuid(uuid, &headers, &mut conn, &nt).await
}
#[put("/ciphers/restore", data = "<data>")]
@@ -1392,7 +1424,15 @@ async fn move_cipher_selected(
// Move cipher
cipher.move_to_folder(data.FolderId.clone(), &user_uuid, &mut conn).await?;
nt.send_cipher_update(UpdateType::SyncCipherUpdate, &cipher, &[user_uuid.clone()], &headers.device.uuid).await;
nt.send_cipher_update(
UpdateType::SyncCipherUpdate,
&cipher,
&[user_uuid.clone()],
&headers.device.uuid,
None,
&mut conn,
)
.await;
}
Ok(())
@@ -1417,19 +1457,15 @@ struct OrganizationId {
#[post("/ciphers/purge?<organization..>", data = "<data>")]
async fn delete_all(
organization: Option<OrganizationId>,
data: JsonUpcase<PasswordData>,
data: JsonUpcase<PasswordOrOtpData>,
headers: Headers,
mut conn: DbConn,
nt: Notify<'_>,
) -> EmptyResult {
let data: PasswordData = data.into_inner().data;
let password_hash = data.MasterPasswordHash;
let data: PasswordOrOtpData = data.into_inner().data;
let mut user = headers.user;
if !user.check_valid_password(&password_hash) {
err!("Invalid password")
}
data.validate(&user, true, &mut conn).await?;
match organization {
Some(org_data) => {
@@ -1444,7 +1480,7 @@ async fn delete_all(
log_event(
EventType::OrganizationPurgedVault as i32,
&org_data.org_id,
org_data.org_id.clone(),
&org_data.org_id,
user.uuid,
headers.device.atype,
&headers.ip.ip,
@@ -1473,6 +1509,7 @@ async fn delete_all(
user.update_revision(&mut conn).await?;
nt.send_user_update(UpdateType::SyncVault, &user).await;
Ok(())
}
}
@@ -1502,6 +1539,8 @@ async fn _delete_cipher_by_uuid(
&cipher,
&cipher.update_users_revision(conn).await,
&headers.device.uuid,
None,
conn,
)
.await;
} else {
@@ -1511,6 +1550,8 @@ async fn _delete_cipher_by_uuid(
&cipher,
&cipher.update_users_revision(conn).await,
&headers.device.uuid,
None,
conn,
)
.await;
}
@@ -1524,7 +1565,7 @@ async fn _delete_cipher_by_uuid(
log_event(
event_type,
&cipher.uuid,
org_uuid,
&org_uuid,
headers.user.uuid.clone(),
headers.device.atype,
&headers.ip.ip,
@@ -1580,13 +1621,16 @@ async fn _restore_cipher_by_uuid(uuid: &str, headers: &Headers, conn: &mut DbCon
&cipher,
&cipher.update_users_revision(conn).await,
&headers.device.uuid,
None,
conn,
)
.await;
if let Some(org_uuid) = &cipher.organization_uuid {
log_event(
EventType::CipherRestored as i32,
&cipher.uuid.clone(),
String::from(org_uuid),
org_uuid,
headers.user.uuid.clone(),
headers.device.atype,
&headers.ip.ip,
@@ -1661,13 +1705,16 @@ async fn _delete_cipher_attachment_by_id(
&cipher,
&cipher.update_users_revision(conn).await,
&headers.device.uuid,
None,
conn,
)
.await;
if let Some(org_uuid) = cipher.organization_uuid {
log_event(
EventType::CipherAttachmentDeleted as i32,
&cipher.uuid,
org_uuid,
&org_uuid,
headers.user.uuid.clone(),
headers.device.atype,
&headers.ip.ip,
@@ -1704,7 +1751,7 @@ impl CipherSyncData {
let cipher_folders: HashMap<String, String>;
let cipher_favorites: HashSet<String>;
match sync_type {
// User Sync supports Folders and Favorits
// User Sync supports Folders and Favorites
CipherSyncType::User => {
// Generate a HashMap with the Cipher UUID as key and the Folder UUID as value
cipher_folders = FolderCipher::find_by_user(user_uuid, conn).await.into_iter().collect();
@@ -1712,7 +1759,7 @@ impl CipherSyncData {
// Generate a HashSet of all the Cipher UUID's which are marked as favorite
cipher_favorites = Favorite::get_all_cipher_uuid_by_user(user_uuid, conn).await.into_iter().collect();
}
// Organization Sync does not support Folders and Favorits.
// Organization Sync does not support Folders and Favorites.
// If these are set, it will cause issues in the web-vault.
CipherSyncType::Organization => {
cipher_folders = HashMap::with_capacity(0);
@@ -1757,7 +1804,7 @@ impl CipherSyncData {
.map(|collection_group| (collection_group.collections_uuid.clone(), collection_group))
.collect();
// Get all organizations that the user has full access to via group assignement
// Get all organizations that the user has full access to via group assignment
let user_group_full_access_for_organizations: HashSet<String> =
Group::gather_user_organizations_full_access(user_uuid, conn).await.into_iter().collect();

View File

@@ -71,10 +71,10 @@ async fn get_grantees(headers: Headers, mut conn: DbConn) -> JsonResult {
}
#[get("/emergency-access/<emer_id>")]
async fn get_emergency_access(emer_id: String, mut conn: DbConn) -> JsonResult {
async fn get_emergency_access(emer_id: &str, mut conn: DbConn) -> JsonResult {
check_emergency_access_allowed()?;
match EmergencyAccess::find_by_uuid(&emer_id, &mut conn).await {
match EmergencyAccess::find_by_uuid(emer_id, &mut conn).await {
Some(emergency_access) => Ok(Json(emergency_access.to_json_grantee_details(&mut conn).await)),
None => err!("Emergency access not valid."),
}
@@ -93,17 +93,13 @@ struct EmergencyAccessUpdateData {
}
#[put("/emergency-access/<emer_id>", data = "<data>")]
async fn put_emergency_access(
emer_id: String,
data: JsonUpcase<EmergencyAccessUpdateData>,
conn: DbConn,
) -> JsonResult {
async fn put_emergency_access(emer_id: &str, data: JsonUpcase<EmergencyAccessUpdateData>, conn: DbConn) -> JsonResult {
post_emergency_access(emer_id, data, conn).await
}
#[post("/emergency-access/<emer_id>", data = "<data>")]
async fn post_emergency_access(
emer_id: String,
emer_id: &str,
data: JsonUpcase<EmergencyAccessUpdateData>,
mut conn: DbConn,
) -> JsonResult {
@@ -111,7 +107,7 @@ async fn post_emergency_access(
let data: EmergencyAccessUpdateData = data.into_inner().data;
let mut emergency_access = match EmergencyAccess::find_by_uuid(&emer_id, &mut conn).await {
let mut emergency_access = match EmergencyAccess::find_by_uuid(emer_id, &mut conn).await {
Some(emergency_access) => emergency_access,
None => err!("Emergency access not valid."),
};
@@ -136,12 +132,12 @@ async fn post_emergency_access(
// region delete
#[delete("/emergency-access/<emer_id>")]
async fn delete_emergency_access(emer_id: String, headers: Headers, mut conn: DbConn) -> EmptyResult {
async fn delete_emergency_access(emer_id: &str, headers: Headers, mut conn: DbConn) -> EmptyResult {
check_emergency_access_allowed()?;
let grantor_user = headers.user;
let emergency_access = match EmergencyAccess::find_by_uuid(&emer_id, &mut conn).await {
let emergency_access = match EmergencyAccess::find_by_uuid(emer_id, &mut conn).await {
Some(emer) => {
if emer.grantor_uuid != grantor_user.uuid && emer.grantee_uuid != Some(grantor_user.uuid) {
err!("Emergency access not valid.")
@@ -155,7 +151,7 @@ async fn delete_emergency_access(emer_id: String, headers: Headers, mut conn: Db
}
#[post("/emergency-access/<emer_id>/delete")]
async fn post_delete_emergency_access(emer_id: String, headers: Headers, conn: DbConn) -> EmptyResult {
async fn post_delete_emergency_access(emer_id: &str, headers: Headers, conn: DbConn) -> EmptyResult {
delete_emergency_access(emer_id, headers, conn).await
}
@@ -243,7 +239,7 @@ async fn send_invite(data: JsonUpcase<EmergencyAccessInviteData>, headers: Heade
} else {
// Automatically mark user as accepted if no email invites
match User::find_by_mail(&email, &mut conn).await {
Some(user) => match accept_invite_process(user.uuid, &mut new_emergency_access, &email, &mut conn).await {
Some(user) => match accept_invite_process(&user.uuid, &mut new_emergency_access, &email, &mut conn).await {
Ok(v) => v,
Err(e) => err!(e.to_string()),
},
@@ -255,10 +251,10 @@ async fn send_invite(data: JsonUpcase<EmergencyAccessInviteData>, headers: Heade
}
#[post("/emergency-access/<emer_id>/reinvite")]
async fn resend_invite(emer_id: String, headers: Headers, mut conn: DbConn) -> EmptyResult {
async fn resend_invite(emer_id: &str, headers: Headers, mut conn: DbConn) -> EmptyResult {
check_emergency_access_allowed()?;
let mut emergency_access = match EmergencyAccess::find_by_uuid(&emer_id, &mut conn).await {
let mut emergency_access = match EmergencyAccess::find_by_uuid(emer_id, &mut conn).await {
Some(emer) => emer,
None => err!("Emergency access not valid."),
};
@@ -299,7 +295,7 @@ async fn resend_invite(emer_id: String, headers: Headers, mut conn: DbConn) -> E
}
// Automatically mark user as accepted if no email invites
match accept_invite_process(grantee_user.uuid, &mut emergency_access, &email, &mut conn).await {
match accept_invite_process(&grantee_user.uuid, &mut emergency_access, &email, &mut conn).await {
Ok(v) => v,
Err(e) => err!(e.to_string()),
}
@@ -315,12 +311,7 @@ struct AcceptData {
}
#[post("/emergency-access/<emer_id>/accept", data = "<data>")]
async fn accept_invite(
emer_id: String,
data: JsonUpcase<AcceptData>,
headers: Headers,
mut conn: DbConn,
) -> EmptyResult {
async fn accept_invite(emer_id: &str, data: JsonUpcase<AcceptData>, headers: Headers, mut conn: DbConn) -> EmptyResult {
check_emergency_access_allowed()?;
let data: AcceptData = data.into_inner().data;
@@ -328,7 +319,7 @@ async fn accept_invite(
let claims = decode_emergency_access_invite(token)?;
// This can happen if the user who received the invite used a different email to signup.
// Since we do not know if this is intented, we error out here and do nothing with the invite.
// Since we do not know if this is intended, we error out here and do nothing with the invite.
if claims.email != headers.user.email {
err!("Claim email does not match current users email")
}
@@ -341,7 +332,7 @@ async fn accept_invite(
None => err!("Invited user not found"),
};
let mut emergency_access = match EmergencyAccess::find_by_uuid(&emer_id, &mut conn).await {
let mut emergency_access = match EmergencyAccess::find_by_uuid(emer_id, &mut conn).await {
Some(emer) => emer,
None => err!("Emergency access not valid."),
};
@@ -356,7 +347,7 @@ async fn accept_invite(
&& grantor_user.name == claims.grantor_name
&& grantor_user.email == claims.grantor_email
{
match accept_invite_process(grantee_user.uuid, &mut emergency_access, &grantee_user.email, &mut conn).await {
match accept_invite_process(&grantee_user.uuid, &mut emergency_access, &grantee_user.email, &mut conn).await {
Ok(v) => v,
Err(e) => err!(e.to_string()),
}
@@ -372,7 +363,7 @@ async fn accept_invite(
}
async fn accept_invite_process(
grantee_uuid: String,
grantee_uuid: &str,
emergency_access: &mut EmergencyAccess,
grantee_email: &str,
conn: &mut DbConn,
@@ -386,7 +377,7 @@ async fn accept_invite_process(
}
emergency_access.status = EmergencyAccessStatus::Accepted as i32;
emergency_access.grantee_uuid = Some(grantee_uuid);
emergency_access.grantee_uuid = Some(String::from(grantee_uuid));
emergency_access.email = None;
emergency_access.save(conn).await
}
@@ -399,7 +390,7 @@ struct ConfirmData {
#[post("/emergency-access/<emer_id>/confirm", data = "<data>")]
async fn confirm_emergency_access(
emer_id: String,
emer_id: &str,
data: JsonUpcase<ConfirmData>,
headers: Headers,
mut conn: DbConn,
@@ -410,7 +401,7 @@ async fn confirm_emergency_access(
let data: ConfirmData = data.into_inner().data;
let key = data.Key;
let mut emergency_access = match EmergencyAccess::find_by_uuid(&emer_id, &mut conn).await {
let mut emergency_access = match EmergencyAccess::find_by_uuid(emer_id, &mut conn).await {
Some(emer) => emer,
None => err!("Emergency access not valid."),
};
@@ -452,11 +443,11 @@ async fn confirm_emergency_access(
// region access emergency access
#[post("/emergency-access/<emer_id>/initiate")]
async fn initiate_emergency_access(emer_id: String, headers: Headers, mut conn: DbConn) -> JsonResult {
async fn initiate_emergency_access(emer_id: &str, headers: Headers, mut conn: DbConn) -> JsonResult {
check_emergency_access_allowed()?;
let initiating_user = headers.user;
let mut emergency_access = match EmergencyAccess::find_by_uuid(&emer_id, &mut conn).await {
let mut emergency_access = match EmergencyAccess::find_by_uuid(emer_id, &mut conn).await {
Some(emer) => emer,
None => err!("Emergency access not valid."),
};
@@ -492,10 +483,10 @@ async fn initiate_emergency_access(emer_id: String, headers: Headers, mut conn:
}
#[post("/emergency-access/<emer_id>/approve")]
async fn approve_emergency_access(emer_id: String, headers: Headers, mut conn: DbConn) -> JsonResult {
async fn approve_emergency_access(emer_id: &str, headers: Headers, mut conn: DbConn) -> JsonResult {
check_emergency_access_allowed()?;
let mut emergency_access = match EmergencyAccess::find_by_uuid(&emer_id, &mut conn).await {
let mut emergency_access = match EmergencyAccess::find_by_uuid(emer_id, &mut conn).await {
Some(emer) => emer,
None => err!("Emergency access not valid."),
};
@@ -530,10 +521,10 @@ async fn approve_emergency_access(emer_id: String, headers: Headers, mut conn: D
}
#[post("/emergency-access/<emer_id>/reject")]
async fn reject_emergency_access(emer_id: String, headers: Headers, mut conn: DbConn) -> JsonResult {
async fn reject_emergency_access(emer_id: &str, headers: Headers, mut conn: DbConn) -> JsonResult {
check_emergency_access_allowed()?;
let mut emergency_access = match EmergencyAccess::find_by_uuid(&emer_id, &mut conn).await {
let mut emergency_access = match EmergencyAccess::find_by_uuid(emer_id, &mut conn).await {
Some(emer) => emer,
None => err!("Emergency access not valid."),
};
@@ -573,15 +564,15 @@ async fn reject_emergency_access(emer_id: String, headers: Headers, mut conn: Db
// region action
#[post("/emergency-access/<emer_id>/view")]
async fn view_emergency_access(emer_id: String, headers: Headers, mut conn: DbConn) -> JsonResult {
async fn view_emergency_access(emer_id: &str, headers: Headers, mut conn: DbConn) -> JsonResult {
check_emergency_access_allowed()?;
let emergency_access = match EmergencyAccess::find_by_uuid(&emer_id, &mut conn).await {
let emergency_access = match EmergencyAccess::find_by_uuid(emer_id, &mut conn).await {
Some(emer) => emer,
None => err!("Emergency access not valid."),
};
if !is_valid_request(&emergency_access, headers.user.uuid, EmergencyAccessType::View) {
if !is_valid_request(&emergency_access, &headers.user.uuid, EmergencyAccessType::View) {
err!("Emergency access not valid.")
}
@@ -610,16 +601,16 @@ async fn view_emergency_access(emer_id: String, headers: Headers, mut conn: DbCo
}
#[post("/emergency-access/<emer_id>/takeover")]
async fn takeover_emergency_access(emer_id: String, headers: Headers, mut conn: DbConn) -> JsonResult {
async fn takeover_emergency_access(emer_id: &str, headers: Headers, mut conn: DbConn) -> JsonResult {
check_emergency_access_allowed()?;
let requesting_user = headers.user;
let emergency_access = match EmergencyAccess::find_by_uuid(&emer_id, &mut conn).await {
let emergency_access = match EmergencyAccess::find_by_uuid(emer_id, &mut conn).await {
Some(emer) => emer,
None => err!("Emergency access not valid."),
};
if !is_valid_request(&emergency_access, requesting_user.uuid, EmergencyAccessType::Takeover) {
if !is_valid_request(&emergency_access, &requesting_user.uuid, EmergencyAccessType::Takeover) {
err!("Emergency access not valid.")
}
@@ -628,21 +619,15 @@ async fn takeover_emergency_access(emer_id: String, headers: Headers, mut conn:
None => err!("Grantor user not found."),
};
let mut result = json!({
let result = json!({
"Kdf": grantor_user.client_kdf_type,
"KdfIterations": grantor_user.client_kdf_iter,
"KdfMemory": grantor_user.client_kdf_memory,
"KdfParallelism": grantor_user.client_kdf_parallelism,
"KeyEncrypted": &emergency_access.key_encrypted,
"Object": "emergencyAccessTakeover",
});
if grantor_user.client_kdf_type == UserKdfType::Argon2id as i32 {
result["KdfMemory"] =
Value::Number(grantor_user.client_kdf_memory.expect("Argon2 memory parameter is required.").into());
result["KdfParallelism"] = Value::Number(
grantor_user.client_kdf_parallelism.expect("Argon2 parallelism parameter is required.").into(),
);
}
Ok(Json(result))
}
@@ -655,7 +640,7 @@ struct EmergencyAccessPasswordData {
#[post("/emergency-access/<emer_id>/password", data = "<data>")]
async fn password_emergency_access(
emer_id: String,
emer_id: &str,
data: JsonUpcase<EmergencyAccessPasswordData>,
headers: Headers,
mut conn: DbConn,
@@ -667,12 +652,12 @@ async fn password_emergency_access(
//let key = &data.Key;
let requesting_user = headers.user;
let emergency_access = match EmergencyAccess::find_by_uuid(&emer_id, &mut conn).await {
let emergency_access = match EmergencyAccess::find_by_uuid(emer_id, &mut conn).await {
Some(emer) => emer,
None => err!("Emergency access not valid."),
};
if !is_valid_request(&emergency_access, requesting_user.uuid, EmergencyAccessType::Takeover) {
if !is_valid_request(&emergency_access, &requesting_user.uuid, EmergencyAccessType::Takeover) {
err!("Emergency access not valid.")
}
@@ -700,14 +685,14 @@ async fn password_emergency_access(
// endregion
#[get("/emergency-access/<emer_id>/policies")]
async fn policies_emergency_access(emer_id: String, headers: Headers, mut conn: DbConn) -> JsonResult {
async fn policies_emergency_access(emer_id: &str, headers: Headers, mut conn: DbConn) -> JsonResult {
let requesting_user = headers.user;
let emergency_access = match EmergencyAccess::find_by_uuid(&emer_id, &mut conn).await {
let emergency_access = match EmergencyAccess::find_by_uuid(emer_id, &mut conn).await {
Some(emer) => emer,
None => err!("Emergency access not valid."),
};
if !is_valid_request(&emergency_access, requesting_user.uuid, EmergencyAccessType::Takeover) {
if !is_valid_request(&emergency_access, &requesting_user.uuid, EmergencyAccessType::Takeover) {
err!("Emergency access not valid.")
}
@@ -728,10 +713,11 @@ async fn policies_emergency_access(emer_id: String, headers: Headers, mut conn:
fn is_valid_request(
emergency_access: &EmergencyAccess,
requesting_user_uuid: String,
requesting_user_uuid: &str,
requested_access_type: EmergencyAccessType,
) -> bool {
emergency_access.grantee_uuid == Some(requesting_user_uuid)
emergency_access.grantee_uuid.is_some()
&& emergency_access.grantee_uuid.as_ref().unwrap() == requesting_user_uuid
&& emergency_access.status == EmergencyAccessStatus::RecoveryApproved as i32
&& emergency_access.atype == requested_access_type as i32
}

View File

@@ -32,7 +32,7 @@ struct EventRange {
// Upstream: https://github.com/bitwarden/server/blob/9ecf69d9cabce732cf2c57976dd9afa5728578fb/src/Api/Controllers/EventsController.cs#LL84C35-L84C41
#[get("/organizations/<org_id>/events?<data..>")]
async fn get_org_events(org_id: String, data: EventRange, _headers: AdminHeaders, mut conn: DbConn) -> JsonResult {
async fn get_org_events(org_id: &str, data: EventRange, _headers: AdminHeaders, mut conn: DbConn) -> JsonResult {
// Return an empty vec when we org events are disabled.
// This prevents client errors
let events_json: Vec<Value> = if !CONFIG.org_events_enabled() {
@@ -45,7 +45,7 @@ async fn get_org_events(org_id: String, data: EventRange, _headers: AdminHeaders
parse_date(&data.end)
};
Event::find_by_organization_uuid(&org_id, &start_date, &end_date, &mut conn)
Event::find_by_organization_uuid(org_id, &start_date, &end_date, &mut conn)
.await
.iter()
.map(|e| e.to_json())
@@ -60,14 +60,14 @@ async fn get_org_events(org_id: String, data: EventRange, _headers: AdminHeaders
}
#[get("/ciphers/<cipher_id>/events?<data..>")]
async fn get_cipher_events(cipher_id: String, data: EventRange, headers: Headers, mut conn: DbConn) -> JsonResult {
async fn get_cipher_events(cipher_id: &str, data: EventRange, headers: Headers, mut conn: DbConn) -> JsonResult {
// Return an empty vec when we org events are disabled.
// This prevents client errors
let events_json: Vec<Value> = if !CONFIG.org_events_enabled() {
Vec::with_capacity(0)
} else {
let mut events_json = Vec::with_capacity(0);
if UserOrganization::user_has_ge_admin_access_to_cipher(&headers.user.uuid, &cipher_id, &mut conn).await {
if UserOrganization::user_has_ge_admin_access_to_cipher(&headers.user.uuid, cipher_id, &mut conn).await {
let start_date = parse_date(&data.start);
let end_date = if let Some(before_date) = &data.continuation_token {
parse_date(before_date)
@@ -75,7 +75,7 @@ async fn get_cipher_events(cipher_id: String, data: EventRange, headers: Headers
parse_date(&data.end)
};
events_json = Event::find_by_cipher_uuid(&cipher_id, &start_date, &end_date, &mut conn)
events_json = Event::find_by_cipher_uuid(cipher_id, &start_date, &end_date, &mut conn)
.await
.iter()
.map(|e| e.to_json())
@@ -93,8 +93,8 @@ async fn get_cipher_events(cipher_id: String, data: EventRange, headers: Headers
#[get("/organizations/<org_id>/users/<user_org_id>/events?<data..>")]
async fn get_user_events(
org_id: String,
user_org_id: String,
org_id: &str,
user_org_id: &str,
data: EventRange,
_headers: AdminHeaders,
mut conn: DbConn,
@@ -111,7 +111,7 @@ async fn get_user_events(
parse_date(&data.end)
};
Event::find_by_org_and_user_org(&org_id, &user_org_id, &start_date, &end_date, &mut conn)
Event::find_by_org_and_user_org(org_id, user_org_id, &start_date, &end_date, &mut conn)
.await
.iter()
.map(|e| e.to_json())
@@ -185,7 +185,7 @@ async fn post_events_collect(data: JsonUpcaseVec<EventCollection>, headers: Head
_log_event(
event.Type,
org_uuid,
String::from(org_uuid),
org_uuid,
&headers.user.uuid,
headers.device.atype,
Some(event_date),
@@ -202,7 +202,7 @@ async fn post_events_collect(data: JsonUpcaseVec<EventCollection>, headers: Head
_log_event(
event.Type,
cipher_uuid,
org_uuid,
&org_uuid,
&headers.user.uuid,
headers.device.atype,
Some(event_date),
@@ -262,7 +262,7 @@ async fn _log_user_event(
pub async fn log_event(
event_type: i32,
source_uuid: &str,
org_uuid: String,
org_uuid: &str,
act_user_uuid: String,
device_type: i32,
ip: &IpAddr,
@@ -278,7 +278,7 @@ pub async fn log_event(
async fn _log_event(
event_type: i32,
source_uuid: &str,
org_uuid: String,
org_uuid: &str,
act_user_uuid: &str,
device_type: i32,
event_date: Option<NaiveDateTime>,
@@ -314,7 +314,7 @@ async fn _log_event(
_ => {}
}
event.org_uuid = Some(org_uuid);
event.org_uuid = Some(String::from(org_uuid));
event.act_user_uuid = Some(String::from(act_user_uuid));
event.device_type = Some(device_type);
event.ip_address = Some(ip.to_string());

View File

@@ -24,8 +24,8 @@ async fn get_folders(headers: Headers, mut conn: DbConn) -> Json<Value> {
}
#[get("/folders/<uuid>")]
async fn get_folder(uuid: String, headers: Headers, mut conn: DbConn) -> JsonResult {
let folder = match Folder::find_by_uuid(&uuid, &mut conn).await {
async fn get_folder(uuid: &str, headers: Headers, mut conn: DbConn) -> JsonResult {
let folder = match Folder::find_by_uuid(uuid, &mut conn).await {
Some(folder) => folder,
_ => err!("Invalid folder"),
};
@@ -50,14 +50,14 @@ async fn post_folders(data: JsonUpcase<FolderData>, headers: Headers, mut conn:
let mut folder = Folder::new(headers.user.uuid, data.Name);
folder.save(&mut conn).await?;
nt.send_folder_update(UpdateType::SyncFolderCreate, &folder, &headers.device.uuid).await;
nt.send_folder_update(UpdateType::SyncFolderCreate, &folder, &headers.device.uuid, &mut conn).await;
Ok(Json(folder.to_json()))
}
#[post("/folders/<uuid>", data = "<data>")]
async fn post_folder(
uuid: String,
uuid: &str,
data: JsonUpcase<FolderData>,
headers: Headers,
conn: DbConn,
@@ -68,7 +68,7 @@ async fn post_folder(
#[put("/folders/<uuid>", data = "<data>")]
async fn put_folder(
uuid: String,
uuid: &str,
data: JsonUpcase<FolderData>,
headers: Headers,
mut conn: DbConn,
@@ -76,7 +76,7 @@ async fn put_folder(
) -> JsonResult {
let data: FolderData = data.into_inner().data;
let mut folder = match Folder::find_by_uuid(&uuid, &mut conn).await {
let mut folder = match Folder::find_by_uuid(uuid, &mut conn).await {
Some(folder) => folder,
_ => err!("Invalid folder"),
};
@@ -88,19 +88,19 @@ async fn put_folder(
folder.name = data.Name;
folder.save(&mut conn).await?;
nt.send_folder_update(UpdateType::SyncFolderUpdate, &folder, &headers.device.uuid).await;
nt.send_folder_update(UpdateType::SyncFolderUpdate, &folder, &headers.device.uuid, &mut conn).await;
Ok(Json(folder.to_json()))
}
#[post("/folders/<uuid>/delete")]
async fn delete_folder_post(uuid: String, headers: Headers, conn: DbConn, nt: Notify<'_>) -> EmptyResult {
async fn delete_folder_post(uuid: &str, headers: Headers, conn: DbConn, nt: Notify<'_>) -> EmptyResult {
delete_folder(uuid, headers, conn, nt).await
}
#[delete("/folders/<uuid>")]
async fn delete_folder(uuid: String, headers: Headers, mut conn: DbConn, nt: Notify<'_>) -> EmptyResult {
let folder = match Folder::find_by_uuid(&uuid, &mut conn).await {
async fn delete_folder(uuid: &str, headers: Headers, mut conn: DbConn, nt: Notify<'_>) -> EmptyResult {
let folder = match Folder::find_by_uuid(uuid, &mut conn).await {
Some(folder) => folder,
_ => err!("Invalid folder"),
};
@@ -112,6 +112,6 @@ async fn delete_folder(uuid: String, headers: Headers, mut conn: DbConn, nt: Not
// Delete the actual folder entry
folder.delete(&mut conn).await?;
nt.send_folder_update(UpdateType::SyncFolderDelete, &folder, &headers.device.uuid).await;
nt.send_folder_update(UpdateType::SyncFolderDelete, &folder, &headers.device.uuid, &mut conn).await;
Ok(())
}

View File

@@ -4,9 +4,11 @@ mod emergency_access;
mod events;
mod folders;
mod organizations;
mod public;
mod sends;
pub mod two_factor;
pub use accounts::purge_auth_requests;
pub use ciphers::{purge_trashed_ciphers, CipherData, CipherSyncData, CipherSyncType};
pub use emergency_access::{emergency_notification_reminder_job, emergency_request_timeout_job};
pub use events::{event_cleanup_job, log_event, log_user_event};
@@ -14,7 +16,6 @@ pub use sends::purge_sends;
pub use two_factor::send_incomplete_2fa_notifications;
pub fn routes() -> Vec<Route> {
let mut device_token_routes = routes![clear_device_token, put_device_token];
let mut eq_domains_routes = routes![get_eq_domains, post_eq_domains, put_eq_domains];
let mut hibp_routes = routes![hibp_breach];
let mut meta_routes = routes![alive, now, version, config];
@@ -28,7 +29,7 @@ pub fn routes() -> Vec<Route> {
routes.append(&mut organizations::routes());
routes.append(&mut two_factor::routes());
routes.append(&mut sends::routes());
routes.append(&mut device_token_routes);
routes.append(&mut public::routes());
routes.append(&mut eq_domains_routes);
routes.append(&mut hibp_routes);
routes.append(&mut meta_routes);
@@ -57,37 +58,6 @@ use crate::{
util::get_reqwest_client,
};
#[put("/devices/identifier/<uuid>/clear-token")]
fn clear_device_token(uuid: String) -> &'static str {
// This endpoint doesn't have auth header
let _ = uuid;
// uuid is not related to deviceId
// This only clears push token
// https://github.com/bitwarden/core/blob/master/src/Api/Controllers/DevicesController.cs#L109
// https://github.com/bitwarden/core/blob/master/src/Core/Services/Implementations/DeviceService.cs#L37
""
}
#[put("/devices/identifier/<uuid>/token", data = "<data>")]
fn put_device_token(uuid: String, data: JsonUpcase<Value>, headers: Headers) -> Json<Value> {
let _data: Value = data.into_inner().data;
// Data has a single string value "PushToken"
let _ = uuid;
// uuid is not related to deviceId
// TODO: This should save the push token, but we don't have push functionality
Json(json!({
"Id": headers.device.uuid,
"Name": headers.device.name,
"Type": headers.device.atype,
"Identifier": headers.device.uuid,
"CreationDate": crate::util::format_date(&headers.device.created_at),
}))
}
#[derive(Serialize, Deserialize, Debug)]
#[allow(non_snake_case)]
struct GlobalDomain {
@@ -170,7 +140,7 @@ async fn put_eq_domains(
}
#[get("/hibp/breach?<username>")]
async fn hibp_breach(username: String) -> JsonResult {
async fn hibp_breach(username: &str) -> JsonResult {
let url = format!(
"https://haveibeenpwned.com/api/v3/breachedaccount/{username}?truncateResponse=false&includeUnverified=false"
);
@@ -224,11 +194,17 @@ fn version() -> Json<&'static str> {
fn config() -> Json<Value> {
let domain = crate::CONFIG.domain();
Json(json!({
"version": crate::VERSION,
// Note: The clients use this version to handle backwards compatibility concerns
// This means they expect a version that closely matches the Bitwarden server version
// We should make sure that we keep this updated when we support the new server features
// Version history:
// - Individual cipher key encryption: 2023.9.1
"version": "2023.9.1",
"gitHash": option_env!("GIT_REV"),
"server": {
"name": "Vaultwarden",
"url": "https://github.com/dani-garcia/vaultwarden"
"url": "https://github.com/dani-garcia/vaultwarden",
"version": crate::VERSION
},
"environment": {
"vault": domain,
@@ -237,6 +213,13 @@ fn config() -> Json<Value> {
"notifications": format!("{domain}/notifications"),
"sso": "",
},
"featureStates": {
// Any feature flags that we want the clients to use
// Can check the enabled ones at:
// https://vault.bitwarden.com/api/config
"fido2-vault-credentials": true, // Passkey support
"autofill-v2": false, // Disabled because it is causing issues https://github.com/dani-garcia/vaultwarden/discussions/4052
},
"object": "config",
}))
}

File diff suppressed because it is too large Load Diff

252
src/api/core/public.rs Normal file
View File

@@ -0,0 +1,252 @@
use chrono::Utc;
use rocket::{
request::{self, FromRequest, Outcome},
Request, Route,
};
use std::collections::HashSet;
use crate::{
api::{EmptyResult, JsonUpcase},
auth,
db::{models::*, DbConn},
mail, CONFIG,
};
pub fn routes() -> Vec<Route> {
routes![ldap_import]
}
#[derive(Deserialize)]
#[allow(non_snake_case)]
struct OrgImportGroupData {
Name: String,
ExternalId: String,
MemberExternalIds: Vec<String>,
}
#[derive(Deserialize)]
#[allow(non_snake_case)]
struct OrgImportUserData {
Email: String,
ExternalId: String,
Deleted: bool,
}
#[derive(Deserialize)]
#[allow(non_snake_case)]
struct OrgImportData {
Groups: Vec<OrgImportGroupData>,
Members: Vec<OrgImportUserData>,
OverwriteExisting: bool,
// LargeImport: bool, // For now this will not be used, upstream uses this to prevent syncs of more then 2000 users or groups without the flag set.
}
#[post("/public/organization/import", data = "<data>")]
async fn ldap_import(data: JsonUpcase<OrgImportData>, token: PublicToken, mut conn: DbConn) -> EmptyResult {
// Most of the logic for this function can be found here
// https://github.com/bitwarden/server/blob/fd892b2ff4547648a276734fb2b14a8abae2c6f5/src/Core/Services/Implementations/OrganizationService.cs#L1797
let org_id = token.0;
let data = data.into_inner().data;
for user_data in &data.Members {
if user_data.Deleted {
// If user is marked for deletion and it exists, revoke it
if let Some(mut user_org) =
UserOrganization::find_by_email_and_org(&user_data.Email, &org_id, &mut conn).await
{
// Only revoke a user if it is not the last confirmed owner
let revoked = if user_org.atype == UserOrgType::Owner
&& user_org.status == UserOrgStatus::Confirmed as i32
{
if UserOrganization::count_confirmed_by_org_and_type(&org_id, UserOrgType::Owner, &mut conn).await
<= 1
{
warn!("Can't revoke the last owner");
false
} else {
user_org.revoke()
}
} else {
user_org.revoke()
};
let ext_modified = user_org.set_external_id(Some(user_data.ExternalId.clone()));
if revoked || ext_modified {
user_org.save(&mut conn).await?;
}
}
// If user is part of the organization, restore it
} else if let Some(mut user_org) =
UserOrganization::find_by_email_and_org(&user_data.Email, &org_id, &mut conn).await
{
let restored = user_org.restore();
let ext_modified = user_org.set_external_id(Some(user_data.ExternalId.clone()));
if restored || ext_modified {
user_org.save(&mut conn).await?;
}
} else {
// If user is not part of the organization
let user = match User::find_by_mail(&user_data.Email, &mut conn).await {
Some(user) => user, // exists in vaultwarden
None => {
// User does not exist yet
let mut new_user = User::new(user_data.Email.clone());
new_user.save(&mut conn).await?;
if !CONFIG.mail_enabled() {
let invitation = Invitation::new(&new_user.email);
invitation.save(&mut conn).await?;
}
new_user
}
};
let user_org_status = if CONFIG.mail_enabled() || user.password_hash.is_empty() {
UserOrgStatus::Invited as i32
} else {
UserOrgStatus::Accepted as i32 // Automatically mark user as accepted if no email invites
};
let mut new_org_user = UserOrganization::new(user.uuid.clone(), org_id.clone());
new_org_user.set_external_id(Some(user_data.ExternalId.clone()));
new_org_user.access_all = false;
new_org_user.atype = UserOrgType::User as i32;
new_org_user.status = user_org_status;
new_org_user.save(&mut conn).await?;
if CONFIG.mail_enabled() {
let (org_name, org_email) = match Organization::find_by_uuid(&org_id, &mut conn).await {
Some(org) => (org.name, org.billing_email),
None => err!("Error looking up organization"),
};
mail::send_invite(
&user_data.Email,
&user.uuid,
Some(org_id.clone()),
Some(new_org_user.uuid),
&org_name,
Some(org_email),
)
.await?;
}
}
}
if CONFIG.org_groups_enabled() {
for group_data in &data.Groups {
let group_uuid = match Group::find_by_external_id(&group_data.ExternalId, &mut conn).await {
Some(group) => group.uuid,
None => {
let mut group =
Group::new(org_id.clone(), group_data.Name.clone(), false, Some(group_data.ExternalId.clone()));
group.save(&mut conn).await?;
group.uuid
}
};
GroupUser::delete_all_by_group(&group_uuid, &mut conn).await?;
for ext_id in &group_data.MemberExternalIds {
if let Some(user_org) = UserOrganization::find_by_external_id_and_org(ext_id, &org_id, &mut conn).await
{
let mut group_user = GroupUser::new(group_uuid.clone(), user_org.uuid.clone());
group_user.save(&mut conn).await?;
}
}
}
} else {
warn!("Group support is disabled, groups will not be imported!");
}
// If this flag is enabled, any user that isn't provided in the Users list will be removed (by default they will be kept unless they have Deleted == true)
if data.OverwriteExisting {
// Generate a HashSet to quickly verify if a member is listed or not.
let sync_members: HashSet<String> = data.Members.into_iter().map(|m| m.ExternalId).collect();
for user_org in UserOrganization::find_by_org(&org_id, &mut conn).await {
if let Some(ref user_external_id) = user_org.external_id {
if !sync_members.contains(user_external_id) {
if user_org.atype == UserOrgType::Owner && user_org.status == UserOrgStatus::Confirmed as i32 {
// Removing owner, check that there is at least one other confirmed owner
if UserOrganization::count_confirmed_by_org_and_type(&org_id, UserOrgType::Owner, &mut conn)
.await
<= 1
{
warn!("Can't delete the last owner");
continue;
}
}
user_org.delete(&mut conn).await?;
}
}
}
}
Ok(())
}
pub struct PublicToken(String);
#[rocket::async_trait]
impl<'r> FromRequest<'r> for PublicToken {
type Error = &'static str;
async fn from_request(request: &'r Request<'_>) -> request::Outcome<Self, Self::Error> {
let headers = request.headers();
// Get access_token
let access_token: &str = match headers.get_one("Authorization") {
Some(a) => match a.rsplit("Bearer ").next() {
Some(split) => split,
None => err_handler!("No access token provided"),
},
None => err_handler!("No access token provided"),
};
// Check JWT token is valid and get device and user from it
let claims = match auth::decode_api_org(access_token) {
Ok(claims) => claims,
Err(_) => err_handler!("Invalid claim"),
};
// Check if time is between claims.nbf and claims.exp
let time_now = Utc::now().naive_utc().timestamp();
if time_now < claims.nbf {
err_handler!("Token issued in the future");
}
if time_now > claims.exp {
err_handler!("Token expired");
}
// Check if claims.iss is host|claims.scope[0]
let host = match auth::Host::from_request(request).await {
Outcome::Success(host) => host,
_ => err_handler!("Error getting Host"),
};
let complete_host = format!("{}|{}", host.host, claims.scope[0]);
if complete_host != claims.iss {
err_handler!("Token not issued by this server");
}
// Check if claims.sub is org_api_key.uuid
// Check if claims.client_sub is org_api_key.org_uuid
let conn = match DbConn::from_request(request).await {
Outcome::Success(conn) => conn,
_ => err_handler!("Error getting DB"),
};
let org_uuid = match claims.client_id.strip_prefix("organization.") {
Some(uuid) => uuid,
None => err_handler!("Malformed client_id"),
};
let org_api_key = match OrganizationApiKey::find_by_org_uuid(org_uuid, &conn).await {
Some(org_api_key) => org_api_key,
None => err_handler!("Invalid client_id"),
};
if org_api_key.org_uuid != claims.client_sub {
err_handler!("Token not issued for this org");
}
if org_api_key.uuid != claims.sub {
err_handler!("Token not issued for this client");
}
Outcome::Success(PublicToken(claims.client_sub))
}
}

View File

@@ -154,8 +154,8 @@ async fn get_sends(headers: Headers, mut conn: DbConn) -> Json<Value> {
}
#[get("/sends/<uuid>")]
async fn get_send(uuid: String, headers: Headers, mut conn: DbConn) -> JsonResult {
let send = match Send::find_by_uuid(&uuid, &mut conn).await {
async fn get_send(uuid: &str, headers: Headers, mut conn: DbConn) -> JsonResult {
let send = match Send::find_by_uuid(uuid, &mut conn).await {
Some(send) => send,
None => err!("Send not found"),
};
@@ -180,7 +180,14 @@ async fn post_send(data: JsonUpcase<SendData>, headers: Headers, mut conn: DbCon
let mut send = create_send(data, headers.user.uuid)?;
send.save(&mut conn).await?;
nt.send_send_update(UpdateType::SyncSendCreate, &send, &send.update_users_revision(&mut conn).await).await;
nt.send_send_update(
UpdateType::SyncSendCreate,
&send,
&send.update_users_revision(&mut conn).await,
&headers.device.uuid,
&mut conn,
)
.await;
Ok(Json(send.to_json()))
}
@@ -252,7 +259,14 @@ async fn post_send_file(data: Form<UploadData<'_>>, headers: Headers, mut conn:
// Save the changes in the database
send.save(&mut conn).await?;
nt.send_send_update(UpdateType::SyncSendCreate, &send, &send.update_users_revision(&mut conn).await).await;
nt.send_send_update(
UpdateType::SyncSendCreate,
&send,
&send.update_users_revision(&mut conn).await,
&headers.device.uuid,
&mut conn,
)
.await;
Ok(Json(send.to_json()))
}
@@ -315,8 +329,8 @@ async fn post_send_file_v2(data: JsonUpcase<SendData>, headers: Headers, mut con
// https://github.com/bitwarden/server/blob/d0c793c95181dfb1b447eb450f85ba0bfd7ef643/src/Api/Controllers/SendsController.cs#L243
#[post("/sends/<send_uuid>/file/<file_id>", format = "multipart/form-data", data = "<data>")]
async fn post_send_file_v2_data(
send_uuid: String,
file_id: String,
send_uuid: &str,
file_id: &str,
data: Form<UploadDataV2<'_>>,
headers: Headers,
mut conn: DbConn,
@@ -326,20 +340,34 @@ async fn post_send_file_v2_data(
let mut data = data.into_inner();
if let Some(send) = Send::find_by_uuid(&send_uuid, &mut conn).await {
let folder_path = tokio::fs::canonicalize(&CONFIG.sends_folder()).await?.join(&send_uuid);
let file_path = folder_path.join(&file_id);
tokio::fs::create_dir_all(&folder_path).await?;
let Some(send) = Send::find_by_uuid(send_uuid, &mut conn).await else {
err!("Send not found. Unable to save the file.")
};
if let Err(_err) = data.data.persist_to(&file_path).await {
data.data.move_copy_to(file_path).await?
}
nt.send_send_update(UpdateType::SyncSendCreate, &send, &send.update_users_revision(&mut conn).await).await;
} else {
err!("Send not found. Unable to save the file.");
let Some(send_user_id) = &send.user_uuid else {
err!("Sends are only supported for users at the moment")
};
if send_user_id != &headers.user.uuid {
err!("Send doesn't belong to user");
}
let folder_path = tokio::fs::canonicalize(&CONFIG.sends_folder()).await?.join(send_uuid);
let file_path = folder_path.join(file_id);
tokio::fs::create_dir_all(&folder_path).await?;
if let Err(_err) = data.data.persist_to(&file_path).await {
data.data.move_copy_to(file_path).await?
}
nt.send_send_update(
UpdateType::SyncSendCreate,
&send,
&send.update_users_revision(&mut conn).await,
&headers.device.uuid,
&mut conn,
)
.await;
Ok(())
}
@@ -351,13 +379,13 @@ pub struct SendAccessData {
#[post("/sends/access/<access_id>", data = "<data>")]
async fn post_access(
access_id: String,
access_id: &str,
data: JsonUpcase<SendAccessData>,
mut conn: DbConn,
ip: ClientIp,
nt: Notify<'_>,
) -> JsonResult {
let mut send = match Send::find_by_access_id(&access_id, &mut conn).await {
let mut send = match Send::find_by_access_id(access_id, &mut conn).await {
Some(s) => s,
None => err_code!(SEND_INACCESSIBLE_MSG, 404),
};
@@ -397,21 +425,28 @@ async fn post_access(
send.save(&mut conn).await?;
nt.send_send_update(UpdateType::SyncSendUpdate, &send, &send.update_users_revision(&mut conn).await).await;
nt.send_send_update(
UpdateType::SyncSendUpdate,
&send,
&send.update_users_revision(&mut conn).await,
&String::from("00000000-0000-0000-0000-000000000000"),
&mut conn,
)
.await;
Ok(Json(send.to_json_access(&mut conn).await))
}
#[post("/sends/<send_id>/access/file/<file_id>", data = "<data>")]
async fn post_access_file(
send_id: String,
file_id: String,
send_id: &str,
file_id: &str,
data: JsonUpcase<SendAccessData>,
host: Host,
mut conn: DbConn,
nt: Notify<'_>,
) -> JsonResult {
let mut send = match Send::find_by_uuid(&send_id, &mut conn).await {
let mut send = match Send::find_by_uuid(send_id, &mut conn).await {
Some(s) => s,
None => err_code!(SEND_INACCESSIBLE_MSG, 404),
};
@@ -448,9 +483,16 @@ async fn post_access_file(
send.save(&mut conn).await?;
nt.send_send_update(UpdateType::SyncSendUpdate, &send, &send.update_users_revision(&mut conn).await).await;
nt.send_send_update(
UpdateType::SyncSendUpdate,
&send,
&send.update_users_revision(&mut conn).await,
&String::from("00000000-0000-0000-0000-000000000000"),
&mut conn,
)
.await;
let token_claims = crate::auth::generate_send_claims(&send_id, &file_id);
let token_claims = crate::auth::generate_send_claims(send_id, file_id);
let token = crate::auth::encode_jwt(&token_claims);
Ok(Json(json!({
"Object": "send-fileDownload",
@@ -460,8 +502,8 @@ async fn post_access_file(
}
#[get("/sends/<send_id>/<file_id>?<t>")]
async fn download_send(send_id: SafeString, file_id: SafeString, t: String) -> Option<NamedFile> {
if let Ok(claims) = crate::auth::decode_send(&t) {
async fn download_send(send_id: SafeString, file_id: SafeString, t: &str) -> Option<NamedFile> {
if let Ok(claims) = crate::auth::decode_send(t) {
if claims.sub == format!("{send_id}/{file_id}") {
return NamedFile::open(Path::new(&CONFIG.sends_folder()).join(send_id).join(file_id)).await.ok();
}
@@ -471,7 +513,7 @@ async fn download_send(send_id: SafeString, file_id: SafeString, t: String) -> O
#[put("/sends/<id>", data = "<data>")]
async fn put_send(
id: String,
id: &str,
data: JsonUpcase<SendData>,
headers: Headers,
mut conn: DbConn,
@@ -482,7 +524,7 @@ async fn put_send(
let data: SendData = data.into_inner().data;
enforce_disable_hide_email_policy(&data, &headers, &mut conn).await?;
let mut send = match Send::find_by_uuid(&id, &mut conn).await {
let mut send = match Send::find_by_uuid(id, &mut conn).await {
Some(s) => s,
None => err!("Send not found"),
};
@@ -530,14 +572,21 @@ async fn put_send(
}
send.save(&mut conn).await?;
nt.send_send_update(UpdateType::SyncSendUpdate, &send, &send.update_users_revision(&mut conn).await).await;
nt.send_send_update(
UpdateType::SyncSendUpdate,
&send,
&send.update_users_revision(&mut conn).await,
&headers.device.uuid,
&mut conn,
)
.await;
Ok(Json(send.to_json()))
}
#[delete("/sends/<id>")]
async fn delete_send(id: String, headers: Headers, mut conn: DbConn, nt: Notify<'_>) -> EmptyResult {
let send = match Send::find_by_uuid(&id, &mut conn).await {
async fn delete_send(id: &str, headers: Headers, mut conn: DbConn, nt: Notify<'_>) -> EmptyResult {
let send = match Send::find_by_uuid(id, &mut conn).await {
Some(s) => s,
None => err!("Send not found"),
};
@@ -547,16 +596,23 @@ async fn delete_send(id: String, headers: Headers, mut conn: DbConn, nt: Notify<
}
send.delete(&mut conn).await?;
nt.send_send_update(UpdateType::SyncSendDelete, &send, &send.update_users_revision(&mut conn).await).await;
nt.send_send_update(
UpdateType::SyncSendDelete,
&send,
&send.update_users_revision(&mut conn).await,
&headers.device.uuid,
&mut conn,
)
.await;
Ok(())
}
#[put("/sends/<id>/remove-password")]
async fn put_remove_password(id: String, headers: Headers, mut conn: DbConn, nt: Notify<'_>) -> JsonResult {
async fn put_remove_password(id: &str, headers: Headers, mut conn: DbConn, nt: Notify<'_>) -> JsonResult {
enforce_disable_send_policy(&headers, &mut conn).await?;
let mut send = match Send::find_by_uuid(&id, &mut conn).await {
let mut send = match Send::find_by_uuid(id, &mut conn).await {
Some(s) => s,
None => err!("Send not found"),
};
@@ -567,7 +623,14 @@ async fn put_remove_password(id: String, headers: Headers, mut conn: DbConn, nt:
send.set_password(None);
send.save(&mut conn).await?;
nt.send_send_update(UpdateType::SyncSendUpdate, &send, &send.update_users_revision(&mut conn).await).await;
nt.send_send_update(
UpdateType::SyncSendUpdate,
&send,
&send.update_users_revision(&mut conn).await,
&headers.device.uuid,
&mut conn,
)
.await;
Ok(Json(send.to_json()))
}

Some files were not shown because too many files have changed in this diff Show More