diff --git a/aws/README.md b/aws/README.md index 46117949..f9248838 100644 --- a/aws/README.md +++ b/aws/README.md @@ -10,6 +10,11 @@ CloudFront CDN └─ Web-vault static assets S3 Bucket ``` +For upgrades of an existing deployment, follow the +[serverless release runbook](RELEASE_RUNBOOK.md). It records the required +branch order, build and changeset workflow, web-vault compatibility rule, +verification gates, and rollback precautions. + ## A Note On AWS Accounts and Security It is common to have one AWS account host multiple services. But it's easy, and doesn't cost any additional amount, to separate workloads into their own accounts. Doing so makes it easier to control for security concerns and monitor costs. AWS Identity and Access Management (IAM) enforces additional controls for cross-account access than for within-account access, for example, making it harder for security attacks to hop from workload to workload when they are in separate accounts. diff --git a/aws/RELEASE_RUNBOOK.md b/aws/RELEASE_RUNBOOK.md new file mode 100644 index 00000000..172283a2 --- /dev/null +++ b/aws/RELEASE_RUNBOOK.md @@ -0,0 +1,300 @@ +# Serverless release runbook + +Use this runbook to upgrade this fork to a tagged Vaultwarden release and +deploy it to the existing AWS serverless stack. It is written for an agent or +operator starting from any branch in a possibly dirty worktree. + +Loading this runbook does not authorize a deployment, history rewrite, or +other external mutation. Confirm that the user's request includes each action +before doing it. + +## Deployment shape + +The branch and runtime layers are ordered as follows: + +```text +upstream release tag + -> aws-ses (AWS HTTP integration and SES) + -> main (Aurora DSQL and Lambda deployment) +``` + +Production uses: + +- AWS profile `vaultwarden`. +- The stack name and region in `aws/samconfig.toml`. +- An ARM64 Lambda package built by `aws/build-lambda.sh`. +- A static web vault in the `WebVaultAssetsBucket` stack output. +- The web-vault version pinned by `docker/DockerSettings.yaml` at the release + tag. Do not substitute the newest web-vault release. + +Never infer account IDs, bucket names, function names, or CloudFront IDs from +old output. Resolve them from CloudFormation on every run. + +## 1. Inventory and protect local state + +From the repository root, record: + +```sh +git status --short --branch +git remote -v +git branch -vv +git log --graph --decorate --oneline --all -30 +``` + +The expected remotes are `upstream` for `dani-garcia/vaultwarden` and `origin` +for this fork. Stop if either URL is unexpected. + +Preserve all existing changes before switching branches. Prefer a named stash +that includes untracked files, then record its object ID. Do not discard, +overwrite, or accidentally commit operator files such as +`aws/samconfig.toml` or database dumps. + +```sh +git stash push --include-untracked --message "operator state before release" +git stash list --format='%gd %H %s' -1 +``` + +If the tree was already clean, no stash is created. Do not assume an older +`stash@{0}` belongs to this run. + +## 2. Authenticate the release + +Set the requested release, fetch only from the upstream repository, and read +the immutable GitHub release notes: + +```sh +release_version=1.37.2 +git fetch upstream "refs/tags/${release_version}:refs/tags/${release_version}" \ + refs/heads/main:refs/remotes/upstream/main +gh release view "${release_version}" --repo dani-garcia/vaultwarden +git show --no-patch --format=fuller "${release_version}" +``` + +Confirm that the tag is annotated, identifies the expected release commit, +and is the immutable non-prerelease requested by the user. Run +`git tag -v "$release_version"` when GPG and the maintainer key are available. +If local signature verification is unavailable, explicitly record that fact; +do not describe the signature as locally verified. + +Read the pinned web-vault version before changing branches: + +```sh +git show "${release_version}:docker/DockerSettings.yaml" +``` + +## 3. Rebase the branch stack + +Fetch `origin` immediately before rewriting and record its branch tips. These +object IDs become the force-with-lease expectations. + +```sh +git fetch origin aws-ses main +old_aws_ses=$(git rev-parse aws-ses) +old_main=$(git rev-parse main) +origin_aws_ses=$(git rev-parse origin/aws-ses) +origin_main=$(git rev-parse origin/main) +``` + +Create uniquely named local recovery branches. Never overwrite an existing +backup branch. + +```sh +git branch "backup-aws-ses-pre-${release_version}" "${old_aws_ses}" +git branch "backup-main-pre-${release_version}" "${old_main}" +``` + +Rebase the AWS integration layer first: + +```sh +git switch aws-ses +git rebase "${release_version}" +``` + +Resolve conflicts by preserving the upstream behavior and reapplying only the +OpenDAL transport, shared AWS HTTP client integration, and SES transport. +Review each complete resulting commit, not just conflict markers. + +Replay only the commits that were above the old `aws-ses` tip onto its new tip: + +```sh +git switch main +git rebase --onto aws-ses "${old_aws_ses}" +``` + +This form avoids replaying the old AWS integration commits as part of `main`. +Restore the operator stash only after both rebases finish and the worktree is +back on `main`. Resolve a stash conflict without losing either version. + +Review these invariants before building: + +```sh +git merge-base --is-ancestor "${release_version}" aws-ses +git merge-base --is-ancestor aws-ses main +git log --oneline "${release_version}..aws-ses" +git log --oneline "aws-ses..main" +git diff --check "${release_version}..main" +git status --short --branch +``` + +The release-to-`aws-ses` range should contain the upstreamable OpenDAL HTTP +transport, the separate AWS connector fix, and the SES layer unless the +release already includes any of them. The `aws-ses`-to-`main` range should +contain the DSQL, Lambda deployment, and release documentation commits. + +## 4. Validate and publish source + +Run formatting and an AWS-feature compile check before the packaging build: + +```sh +cargo fmt --all -- --check +cargo check --locked --features aws +``` + +The S3 feature deliberately injects Vaultwarden's HTTP client into OpenDAL's +reqwest transport. If the rebase changes OpenDAL, confirm that each S3 +operator still receives that transport in an `OperationContext`. A compile +check covers the trait API, but an S3 smoke test is still needed to detect +runtime routing failures. Both the OpenDAL and AWS SDK transports should +continue to use `get_reqwest_client_builder(false)` so they inherit the shared +HTTP policy and standard ten-second deadline. + +Build from `main`, test the archive, and record its checksum: + +```sh +./aws/build-lambda.sh +unzip -t aws/vaultwarden-lambda.zip +shasum -a 256 aws/vaultwarden-lambda.zip +``` + +The build script and its GitHub workflow must invoke `cargo lambda build` with +the `aws` feature. Stop and fix the build configuration if they do not. + +Fetch `origin` again immediately before pushing. Publish rebased history only +with explicit leases recorded above; a lease failure means someone else +updated the branch, so stop and reconcile rather than retrying blindly. + +```sh +git fetch origin aws-ses main +git push --force-with-lease="aws-ses:${origin_aws_ses}" \ + origin aws-ses:aws-ses +git push --force-with-lease="main:${origin_main}" origin main:main +``` + +## 5. Create and review the deployment changeset + +Confirm AWS identity, stack health, and live versions before mutation: + +```sh +aws sts get-caller-identity --profile vaultwarden +aws cloudformation describe-stacks --profile vaultwarden \ + --stack-name vaultwarden +curl -fsS https://vault.chasedouglas.net/api/config +curl -fsS https://vault.chasedouglas.net/vw-version.json +``` + +Resolve the actual stack name and region from `aws/samconfig.toml` if they are +not `vaultwarden` and `us-east-2`. Do not commit that file merely to perform a +release. + +Build the SAM staging directory and create, but do not yet execute, a +changeset. Explicitly override the local `disable_rollback` setting: + +```sh +cd aws +sam build +sam deploy --profile vaultwarden --no-execute-changeset \ + --no-confirm-changeset --no-disable-rollback +``` + +Capture the exact changeset ARN printed by SAM. Inspect it with +`aws cloudformation describe-change-set` and confirm: + +- The stack name, account, and region are correct. +- There are no replacements or deletions. +- The expected Lambda code/configuration is the only material runtime change, + unless the upstream rebase intentionally changed the template. +- Parameter values match the existing stack. No blank local value silently + replaces a production setting. + +Stop if the changeset differs from those expectations. + +## 6. Execute and verify the backend + +Execute the exact reviewed changeset, wait for completion, and inspect stack +events if it fails: + +```sh +aws cloudformation execute-change-set --profile vaultwarden \ + --change-set-name CHANGESET_ARN +aws cloudformation wait stack-update-complete --profile vaultwarden \ + --stack-name vaultwarden +``` + +Verify that `/api/config` reports the new `main` commit hash and expected +compatibility version. Query recent Lambda logs for startup panics or request +errors. Do not rely on a successful CloudFormation status alone. + +## 7. Update the compatible web vault + +Extract `vault_version` from `docker/DockerSettings.yaml` on the deployed +`main` branch. Remove its leading `v` only for the JSON version comparison. + +Inspect the immutable release and download its tarball, checksum manifest, and +detached signature from `dani-garcia/bw_web_builds`. Verify the entire checksum +manifest before extraction. Reject archives containing absolute paths, +parent-directory traversal, or symlinks. + +Resolve current resource IDs: + +```sh +aws cloudformation describe-stacks --profile vaultwarden \ + --stack-name vaultwarden +aws cloudformation describe-stack-resource --profile vaultwarden \ + --stack-name vaultwarden --logical-resource-id CDN +``` + +If the live `vw-version.json` already matches the pinned version, do not +rewrite the bucket. Otherwise: + +1. Download the current bucket into a newly created temporary rollback + directory. +2. Compare the rollback copy with the extracted release by content. Do not + trust `aws s3 sync` as a byte comparison: a changed file with the same size + is skipped when the release archive's timestamp is older than S3. Normalize + timestamps in the temporary extracted copy, or otherwise force every + content difference to appear in the dry run. +3. Run `aws s3 sync ... --delete --dryrun` and inspect uploads and deletions. +4. Sync the extracted `web-vault/` directory with `--delete`. +5. Repeat the dry run with `--size-only`; it must report no changes. Download + the resulting bucket into a separate temporary directory and compare the + complete directory with the extracted release byte for byte. +6. Invalidate `/*` on the resolved CloudFront distribution and wait for it. +7. Verify the public version marker, `index.html`, primary JavaScript bundle, + and WASM module against the release artifact byte for byte. + +Keep the rollback directory until all live checks succeed. Never use a bucket +name or distribution ID copied from an earlier release. + +## 8. Finish and clean up + +Record: + +- Release tag and commit. +- New `aws-ses` and `main` tips. +- Lambda package SHA-256. +- CloudFormation changeset ARN and final stack status. +- Backend compatibility version and live `gitHash`. +- Pinned and live web-vault versions. +- CloudFront invalidation ID, if one was needed. +- Any verification that could not be performed. + +After successful verification, remove only generated artifacts and temporary +directories created by this run, including `target/`, `aws/.aws-sam/`, the +Lambda ZIP, downloaded web-vault files, and rollback copies. Do not delete +operator files or prune shared Docker caches unless the user separately asks +for that cleanup. + +If production verification fails, retain rollback material, stop cleanup, and +report the exact failure. Use the backup branches for source recovery. Restore +the previous Lambda package or web-vault bucket contents only with explicit +authorization for that rollback.