From 0d9525328bc0cae582241be71e37d209c58aa66c Mon Sep 17 00:00:00 2001 From: Devon Hudson Date: Fri, 30 Jan 2026 17:08:07 -0700 Subject: [PATCH 01/26] Push MAS docker images to Element OCI Registry --- .github/workflows/build.yaml | 61 ++++++++++++++++++++++++++++++------ 1 file changed, 52 insertions(+), 9 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index fbbf4fb2d..087edb118 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -27,7 +27,6 @@ env: CARGO_NET_GIT_FETCH_WITH_CLI: "true" SCCACHE_GHA_ENABLED: "true" RUSTC_WRAPPER: "sccache" - IMAGE: ghcr.io/element-hq/matrix-authentication-service BUILDCACHE: ghcr.io/element-hq/matrix-authentication-service/buildcache DOCKER_METADATA_ANNOTATIONS_LEVELS: manifest,index @@ -228,7 +227,9 @@ jobs: id: meta uses: docker/metadata-action@v5.10.0 with: - images: "${{ env.IMAGE }}" + images: | + ghcr.io/element-hq/matrix-authentication-service + oci-push.vpn.infra.element.io/matrix-authentication-service bake-target: docker-metadata-action flavor: | latest=auto @@ -244,7 +245,9 @@ jobs: id: meta-debug uses: docker/metadata-action@v5.10.0 with: - images: "${{ env.IMAGE }}" + images: | + ghcr.io/element-hq/matrix-authentication-service + oci-push.vpn.infra.element.io/matrix-authentication-service bake-target: docker-metadata-action-debug flavor: | latest=auto @@ -274,6 +277,38 @@ jobs: username: ${{ github.repository_owner }} password: ${{ secrets.GITHUB_TOKEN }} + - name: Tailscale + uses: tailscale/github-action@53acf823325fe9ca47f4cdaa951f90b4b0de5bb9 # v4.1.1 + with: + oauth-client-id: ${{ secrets.TS_OAUTH_CLIENT_ID }} + audience: ${{ secrets.TS_AUDIENCE }} + tags: tag:github-actions + + - name: Compute vault jwt role name + id: vault-jwt-role + run: | + echo "role_name=github_service_management_$( echo "${{ github.repository }}" | sed -r 's|[/-]|_|g')" | tee -a "$GITHUB_OUTPUT" + + - name: Get team registry token + id: import-secrets + uses: hashicorp/vault-action@4c06c5ccf5c0761b6029f56cfb1dcf5565918a3b # v3.4.0 + with: + url: https://vault.infra.ci.i.element.dev + role: ${{ steps.vault-jwt-role.outputs.role_name }} + path: service-management/github-actions + jwtGithubAudience: https://vault.infra.ci.i.element.dev + method: jwt + secrets: | + services/backend-repositories/secret/data/oci.element.io username | OCI_USERNAME ; + services/backend-repositories/secret/data/oci.element.io password | OCI_PASSWORD ; + + - name: Login to Element OCI Registry + uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0 + with: + registry: oci-push.vpn.infra.element.io + username: ${{ steps.import-secrets.outputs.OCI_USERNAME }} + password: ${{ steps.import-secrets.outputs.OCI_PASSWORD }} + - name: Build and push id: bake uses: docker/bake-action@v6.10.0 @@ -308,8 +343,11 @@ jobs: run: |- cosign sign --yes \ - "$IMAGE@$REGULAR_DIGEST" \ - "$IMAGE@$DEBUG_DIGEST" \ + "ghcr.io/element-hq/matrix-authentication-service@$REGULAR_DIGEST" \ + "ghcr.io/element-hq/matrix-authentication-service@$DEBUG_DIGEST" + cosign sign --yes \ + "oci-push.vpn.infra.element.io/matrix-authentication-service@$REGULAR_DIGEST" \ + "oci-push.vpn.infra.element.io/matrix-authentication-service@$DEBUG_DIGEST" release: name: Release @@ -318,6 +356,7 @@ jobs: needs: - assemble-archives - build-image + steps: - name: Download the artifacts from the previous job uses: actions/download-artifact@v7 @@ -337,7 +376,8 @@ jobs: - Digest: ``` - ${{ env.IMAGE }}@${{ fromJSON(needs.build-image.outputs.metadata).regular.digest }} + ghcr.io/element-hq/matrix-authentication-service@${{ fromJSON(needs.build-image.outputs.metadata).regular.digest }} + oci-push.vpn.infra.element.io/matrix-authentication-service@${{ fromJSON(needs.build-image.outputs.metadata).regular.digest }} ``` - Tags: ``` @@ -349,7 +389,8 @@ jobs: - Digest: ``` - ${{ env.IMAGE }}@${{ fromJSON(needs.build-image.outputs.metadata).debug.digest }} + ghcr.io/element-hq/matrix-authentication-service@${{ fromJSON(needs.build-image.outputs.metadata).debug.digest }} + oci-push.vpn.infra.element.io/matrix-authentication-service@${{ fromJSON(needs.build-image.outputs.metadata).debug.digest }} ``` - Tags: ``` @@ -414,7 +455,8 @@ jobs: - Digest: ``` - ${{ env.IMAGE }}@${{ fromJSON(needs.build-image.outputs.metadata).regular.digest }} + ghcr.io/element-hq/matrix-authentication-service@${{ fromJSON(needs.build-image.outputs.metadata).regular.digest }} + oci-push.vpn.infra.element.io/matrix-authentication-service@${{ fromJSON(needs.build-image.outputs.metadata).regular.digest }} ``` - Tags: ``` @@ -426,7 +468,8 @@ jobs: - Digest: ``` - ${{ env.IMAGE }}@${{ fromJSON(needs.build-image.outputs.metadata).debug.digest }} + ghcr.io/element-hq/matrix-authentication-service@${{ fromJSON(needs.build-image.outputs.metadata).debug.digest }} + oci-push.vpn.infra.element.io/matrix-authentication-service@${{ fromJSON(needs.build-image.outputs.metadata).debug.digest }} ``` - Tags: ``` From 9290c46ea85070a71478c3c7b9fa433ba033a7bc Mon Sep 17 00:00:00 2001 From: Eric Eastwood Date: Wed, 13 May 2026 16:39:33 -0500 Subject: [PATCH 02/26] Add reasoning for why upsert device immediately when logging in From https://github.com/element-hq/matrix-authentication-service/pull/5607#discussion_r3232971115 --- crates/handlers/src/compat/login.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/crates/handlers/src/compat/login.rs b/crates/handlers/src/compat/login.rs index e36e57a5b..7b13c6552 100644 --- a/crates/handlers/src/compat/login.rs +++ b/crates/handlers/src/compat/login.rs @@ -457,6 +457,12 @@ pub(crate) async fn post( // Now we can create the device on the homeserver, without holding the // transaction + // + // Normally, devices get synced to the homeserver in a `SyncDevicesJob` but we want + // the device to be created synchronously on the homeserver, so that when we + // respond, the device already exists (TODO: Why important?). We're using an upsert + // so if the device already exists for some reason (like when we're replacing it, or + // a concurrent device sync happening) it won't have any effect. if let Err(err) = homeserver .upsert_device( &user.username, From 25f6b219901dd212e4f677cddcccc2e60be18ee2 Mon Sep 17 00:00:00 2001 From: Eric Eastwood Date: Wed, 13 May 2026 16:54:44 -0500 Subject: [PATCH 03/26] Schedule `SyncDevicesJob` after `dangerous_hard_limit_eviction` --- crates/handlers/src/compat/login.rs | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/crates/handlers/src/compat/login.rs b/crates/handlers/src/compat/login.rs index 7b13c6552..854b82279 100644 --- a/crates/handlers/src/compat/login.rs +++ b/crates/handlers/src/compat/login.rs @@ -508,6 +508,7 @@ pub(crate) async fn post( /// Given the violations from [`Policy::evaluate_compat_login`], return the /// appropriate `RouteError` response. async fn process_violations_for_compat_login( + rng: &mut (dyn RngCore + Send), clock: &dyn Clock, repo: &mut BoxRepository, session_limit_config: Option<&SessionLimitConfig>, @@ -602,6 +603,11 @@ async fn process_violations_for_compat_login( .finish(clock, compat_session.to_owned()) .await?; } + + // Schedule a device sync with the homeserver + repo.queue_job() + .schedule_job(rng, clock, SyncDevicesJob::new_for_id(user.id)) + .await?; } else { // Tell the user about the limit return Err(RouteError::PolicyHardSessionLimitReached); @@ -846,6 +852,7 @@ async fn token_login( }) .await?; process_violations_for_compat_login( + rng, clock, repo, session_limit_config, @@ -972,8 +979,15 @@ async fn user_password_login( requester: policy_requester, }) .await?; - process_violations_for_compat_login(clock, repo, session_limit_config, &user, res.violations) - .await?; + process_violations_for_compat_login( + rng, + clock, + repo, + session_limit_config, + &user, + res.violations, + ) + .await?; let session = repo .compat_session() From 09139721c071a3edf3cb24c4907346c7f53bee73 Mon Sep 17 00:00:00 2001 From: Eric Eastwood Date: Wed, 13 May 2026 17:10:02 -0500 Subject: [PATCH 04/26] Add logout reasoning --- crates/handlers/src/compat/logout.rs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/crates/handlers/src/compat/logout.rs b/crates/handlers/src/compat/logout.rs index 4642cc54b..68f8f5d96 100644 --- a/crates/handlers/src/compat/logout.rs +++ b/crates/handlers/src/compat/logout.rs @@ -117,13 +117,19 @@ pub(crate) async fn post( // XXX: this is probably not the right error .ok_or(RouteError::InvalidAuthorization)?; + // This will make the access token invalid + repo.compat_session().finish(&clock, session).await?; + // Schedule a job to sync the devices of the user with the homeserver + // + // Doing this in a background job is ok as the access token will be invalid right + // away (from the session being finished above) and we do actually want to do a full + // device list sync, because we're not sure whether we want to delete the device (if + // there is for example a concurrent logout and login with the same device ID). repo.queue_job() .schedule_job(&mut rng, &clock, SyncDevicesJob::new(&user)) .await?; - repo.compat_session().finish(&clock, session).await?; - repo.save().await?; LOGOUT_COUNTER.add(1, &[KeyValue::new(RESULT, "success")]); From 5eadefa729bdb357ef077eee8e62b44346a88213 Mon Sep 17 00:00:00 2001 From: Eric Eastwood Date: Wed, 13 May 2026 17:15:38 -0500 Subject: [PATCH 05/26] Fix lints --- crates/handlers/src/compat/login.rs | 11 ++++++----- crates/handlers/src/compat/logout.rs | 9 +++++---- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/crates/handlers/src/compat/login.rs b/crates/handlers/src/compat/login.rs index 854b82279..7a7d85f28 100644 --- a/crates/handlers/src/compat/login.rs +++ b/crates/handlers/src/compat/login.rs @@ -458,11 +458,12 @@ pub(crate) async fn post( // Now we can create the device on the homeserver, without holding the // transaction // - // Normally, devices get synced to the homeserver in a `SyncDevicesJob` but we want - // the device to be created synchronously on the homeserver, so that when we - // respond, the device already exists (TODO: Why important?). We're using an upsert - // so if the device already exists for some reason (like when we're replacing it, or - // a concurrent device sync happening) it won't have any effect. + // Normally, devices get synced to the homeserver in a `SyncDevicesJob` but we + // want the device to be created synchronously on the homeserver, so that + // when we respond, the device already exists (TODO: Why important?). We're + // using an upsert so if the device already exists for some reason (like + // when we're replacing it, or a concurrent device sync happening) it won't + // have any effect. if let Err(err) = homeserver .upsert_device( &user.username, diff --git a/crates/handlers/src/compat/logout.rs b/crates/handlers/src/compat/logout.rs index 68f8f5d96..b73e3b268 100644 --- a/crates/handlers/src/compat/logout.rs +++ b/crates/handlers/src/compat/logout.rs @@ -122,10 +122,11 @@ pub(crate) async fn post( // Schedule a job to sync the devices of the user with the homeserver // - // Doing this in a background job is ok as the access token will be invalid right - // away (from the session being finished above) and we do actually want to do a full - // device list sync, because we're not sure whether we want to delete the device (if - // there is for example a concurrent logout and login with the same device ID). + // Doing this in a background job is ok as the access token will be invalid + // right away (from the session being finished above) and we do actually + // want to do a full device list sync, because we're not sure whether we + // want to delete the device (if there is for example a concurrent logout + // and login with the same device ID). repo.queue_job() .schedule_job(&mut rng, &clock, SyncDevicesJob::new(&user)) .await?; From f21e59b50d37a4c94eefabb87cc3689ff7f91538 Mon Sep 17 00:00:00 2001 From: Eric Eastwood Date: Wed, 13 May 2026 17:27:07 -0500 Subject: [PATCH 06/26] Explain as opposed to --- crates/handlers/src/compat/logout.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/crates/handlers/src/compat/logout.rs b/crates/handlers/src/compat/logout.rs index b73e3b268..8800fcf44 100644 --- a/crates/handlers/src/compat/logout.rs +++ b/crates/handlers/src/compat/logout.rs @@ -124,9 +124,10 @@ pub(crate) async fn post( // // Doing this in a background job is ok as the access token will be invalid // right away (from the session being finished above) and we do actually - // want to do a full device list sync, because we're not sure whether we - // want to delete the device (if there is for example a concurrent logout - // and login with the same device ID). + // want to do a full device list sync (as opposed to + // `homeserver.delete_device(...)`), because we're not sure whether we want + // to delete the device (if there is for example a concurrent logout and + // login with the same device ID). repo.queue_job() .schedule_job(&mut rng, &clock, SyncDevicesJob::new(&user)) .await?; From 4437b3b8d03e1afa6cc93b9992a4cd7b4f3ebc15 Mon Sep 17 00:00:00 2001 From: Eric Eastwood Date: Thu, 14 May 2026 16:42:10 -0500 Subject: [PATCH 07/26] Check whether policy evaluation result is valid --- crates/handlers/src/compat/login.rs | 32 +++++++++++++++-------------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/crates/handlers/src/compat/login.rs b/crates/handlers/src/compat/login.rs index d4c22b550..6be5c7f3b 100644 --- a/crates/handlers/src/compat/login.rs +++ b/crates/handlers/src/compat/login.rs @@ -499,28 +499,31 @@ pub(crate) async fn post( })) } -/// Given the violations from [`Policy::evaluate_compat_login`], return the -/// appropriate `RouteError` response. +/// Given the evaluation result/violations from [`Policy::evaluate_compat_login`], +/// return the appropriate `RouteError` response. async fn process_violations_for_compat_login( clock: &dyn Clock, repo: &mut BoxRepository, session_limit_config: Option<&SessionLimitConfig>, user: &User, - violations: Vec, + res: mas_policy::EvaluationResult, ) -> Result<(), RouteError> { // We're using slice syntax here so we can match easily - match &violations[..] { + match (res.valid(), &res.violations[..]) { // If the only violation is having reached the session limit, we might be // able to resolve the situation. // // We don't trigger this if there was some other violation anyway, since // that means that removing a session wouldn't actually unblock the login. - [ - Violation { - variant: Some(ViolationVariant::TooManySessions { need_to_remove }), - .. - }, - ] => { + ( + false, + [ + Violation { + variant: Some(ViolationVariant::TooManySessions { need_to_remove }), + .. + }, + ], + ) => { // Normally, if we are seeing a `TooManySessions` violation, we would // expect `session_limit_config` to be filled in but if someone created // their own policies which emit a `TooManySessions` violation that isn't @@ -613,9 +616,9 @@ async fn process_violations_for_compat_login( } } // Nothing is wrong - [] => return Ok(()), + (true, _) => return Ok(()), // Just throw an error for any other violation - _violations => { + (false, _violations) => { // FIXME: We should be exposing the violations to the user return Err(RouteError::PolicyRejected); } @@ -844,7 +847,7 @@ async fn token_login( repo, session_limit_config, &browser_session.user, - res.violations, + res, ) .await?; @@ -966,8 +969,7 @@ async fn user_password_login( requester: policy_requester, }) .await?; - process_violations_for_compat_login(clock, repo, session_limit_config, &user, res.violations) - .await?; + process_violations_for_compat_login(clock, repo, session_limit_config, &user, res).await?; let session = repo .compat_session() From 941f3e50d50abe2b44772807198de72fc59e10c3 Mon Sep 17 00:00:00 2001 From: Eric Eastwood Date: Thu, 14 May 2026 16:50:55 -0500 Subject: [PATCH 08/26] Fix lints --- crates/handlers/src/compat/login.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/crates/handlers/src/compat/login.rs b/crates/handlers/src/compat/login.rs index 6be5c7f3b..e85d67b08 100644 --- a/crates/handlers/src/compat/login.rs +++ b/crates/handlers/src/compat/login.rs @@ -499,8 +499,9 @@ pub(crate) async fn post( })) } -/// Given the evaluation result/violations from [`Policy::evaluate_compat_login`], -/// return the appropriate `RouteError` response. +/// Given the evaluation result/violations from +/// [`Policy::evaluate_compat_login`], return the appropriate `RouteError` +/// response. async fn process_violations_for_compat_login( clock: &dyn Clock, repo: &mut BoxRepository, From f99f4f5fbaaea1804b305334e52033e9890fa92c Mon Sep 17 00:00:00 2001 From: Quentin Gliech Date: Fri, 15 May 2026 13:22:50 +0200 Subject: [PATCH 09/26] Fix the transformation of the Docker build metadata in CI This broke in #5664 due to STEPS_BAKE_OUTPUTS_METADATA being too large to be passed as an argument to a shell script. This replaces the `jq` call with a javascript action which transforms the output. --- .github/workflows/build.yaml | 32 ++++++++++++++++++++++---------- 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 6b25b3f6b..255771d3e 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -214,7 +214,7 @@ jobs: runs-on: ubuntu-24.04 outputs: - metadata: ${{ steps.output.outputs.metadata }} + metadata: ${{ steps.metadata.outputs.result }} permissions: contents: read @@ -293,15 +293,27 @@ jobs: base.cache-to=type=registry,ref=${{ env.BUILDCACHE }}:buildcache,mode=max - name: Transform bake output - # This transforms the ouput to an object which looks like this: - # { reguar: { digest: "…", tags: ["…", "…"] }, debug: { digest: "…", tags: ["…"] }, … } - id: output - run: | - echo 'metadata<> $GITHUB_OUTPUT - echo "$STEPS_BAKE_OUTPUTS_METADATA" | jq -c 'with_entries(select(.value | (type == "object" and has("containerimage.digest")))) | map_values({ digest: .["containerimage.digest"], tags: (.["image.name"] | split(",")) })' >> $GITHUB_OUTPUT - echo 'EOF' >> $GITHUB_OUTPUT + # This transforms the output to an object which looks like this: + # { regular: { digest: "…", tags: ["…", "…"] }, debug: { digest: "…", tags: ["…"] }, … } + # We use github-script rather than shelling out to jq because the bake + # metadata can exceed the shell ARG_MAX limit when expanded. + id: metadata + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 env: STEPS_BAKE_OUTPUTS_METADATA: ${{ steps.bake.outputs.metadata }} + with: + script: | + const bakeOutput = JSON.parse(process.env.STEPS_BAKE_OUTPUTS_METADATA); + const metadata = {}; + for (const [key, value] of Object.entries(bakeOutput)) { + if (value && typeof value === 'object' && ('containerimage.digest' in value)) { + metadata[key] = { + digest: value['containerimage.digest'], + tags: value['image.name'].split(','), + }; + } + } + return metadata; - name: Sign the images with GitHub Actions provided token # Only sign on tags and on commits on main branch @@ -310,8 +322,8 @@ jobs: && (startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/main') env: - REGULAR_DIGEST: ${{ steps.output.outputs.metadata && fromJSON(steps.output.outputs.metadata).regular.digest }} - DEBUG_DIGEST: ${{ steps.output.outputs.metadata && fromJSON(steps.output.outputs.metadata).debug.digest }} + REGULAR_DIGEST: ${{ steps.metadata.outputs.result && fromJSON(steps.metadata.outputs.result).regular.digest }} + DEBUG_DIGEST: ${{ steps.metadata.outputs.result && fromJSON(steps.metadata.outputs.result).debug.digest }} run: |- cosign sign --yes \ From 59764300705bd87cd0d3ed075aae8a340e4541c7 Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Fri, 15 May 2026 14:00:53 +0100 Subject: [PATCH 10/26] Remove unused apalis dependabot config --- .github/dependabot.yml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index e210db33d..415b4eb49 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -12,10 +12,6 @@ updates: - "Z-Deps-Backend" schedule: interval: "daily" - ignore: - # We plan to remove apalis soon, let's ignore it for now - - dependency-name: "apalis" - - dependency-name: "apalis-*" groups: axum: patterns: From 676e2fc75f7edd9dea0da06c6dd8613aa820c534 Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Fri, 15 May 2026 14:04:43 +0100 Subject: [PATCH 11/26] Increase dependabot interval from daily to monthly --- .github/dependabot.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index e210db33d..376b57208 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -11,7 +11,7 @@ updates: - "A-Dependencies" - "Z-Deps-Backend" schedule: - interval: "daily" + interval: "monthly" ignore: # We plan to remove apalis soon, let's ignore it for now - dependency-name: "apalis" @@ -53,7 +53,7 @@ updates: - "A-Dependencies" - "Z-Deps-CI" schedule: - interval: "daily" + interval: "monthly" cooldown: default-days: 14 @@ -63,7 +63,7 @@ updates: - "A-Dependencies" - "Z-Deps-Frontend" schedule: - interval: "daily" + interval: "monthly" groups: storybook: patterns: From eb58397b05e2cea8513f03090dae112f20f9b2a3 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 19 May 2026 13:46:32 +0000 Subject: [PATCH 12/26] Translations updates --- frontend/.storybook/locales.ts | 78 +++++++++++++++++----------------- 1 file changed, 39 insertions(+), 39 deletions(-) diff --git a/frontend/.storybook/locales.ts b/frontend/.storybook/locales.ts index ffcc627ed..bb379022a 100644 --- a/frontend/.storybook/locales.ts +++ b/frontend/.storybook/locales.ts @@ -27,7 +27,7 @@ export type LocalazyMetadata = { }; const localazyMetadata: LocalazyMetadata = { - projectUrl: "https://localazy.com/p/matrix-authentication-service", + projectUrl: "https://localazy.com/p/matrix-authentication-service!v1.17", baseLocale: "en", languages: [ { @@ -208,25 +208,25 @@ const localazyMetadata: LocalazyMetadata = { file: "frontend.json", path: "", cdnFiles: { - "cs": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/cs/frontend.json", - "da": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/da/frontend.json", - "de": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/de/frontend.json", - "en": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/en/frontend.json", - "et": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/et/frontend.json", - "fi": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/fi/frontend.json", - "fr": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/fr/frontend.json", - "hu": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/hu/frontend.json", - "nb_NO": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/nb-NO/frontend.json", - "nl": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/nl/frontend.json", - "pl": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/pl/frontend.json", - "pt": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/pt/frontend.json", - "pt_BR": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/pt-BR/frontend.json", - "ru": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/ru/frontend.json", - "sk": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/sk/frontend.json", - "sv": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/sv/frontend.json", - "uk": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/uk/frontend.json", - "uz": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/uz/frontend.json", - "zh#Hans": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/zh-Hans/frontend.json" + "cs": "https://delivery.localazy.com/_a65347067752521597762935bd1b/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/cs/frontend.json", + "da": "https://delivery.localazy.com/_a65347067752521597762935bd1b/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/da/frontend.json", + "de": "https://delivery.localazy.com/_a65347067752521597762935bd1b/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/de/frontend.json", + "en": "https://delivery.localazy.com/_a65347067752521597762935bd1b/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/en/frontend.json", + "et": "https://delivery.localazy.com/_a65347067752521597762935bd1b/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/et/frontend.json", + "fi": "https://delivery.localazy.com/_a65347067752521597762935bd1b/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/fi/frontend.json", + "fr": "https://delivery.localazy.com/_a65347067752521597762935bd1b/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/fr/frontend.json", + "hu": "https://delivery.localazy.com/_a65347067752521597762935bd1b/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/hu/frontend.json", + "nb_NO": "https://delivery.localazy.com/_a65347067752521597762935bd1b/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/nb-NO/frontend.json", + "nl": "https://delivery.localazy.com/_a65347067752521597762935bd1b/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/nl/frontend.json", + "pl": "https://delivery.localazy.com/_a65347067752521597762935bd1b/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/pl/frontend.json", + "pt": "https://delivery.localazy.com/_a65347067752521597762935bd1b/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/pt/frontend.json", + "pt_BR": "https://delivery.localazy.com/_a65347067752521597762935bd1b/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/pt-BR/frontend.json", + "ru": "https://delivery.localazy.com/_a65347067752521597762935bd1b/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/ru/frontend.json", + "sk": "https://delivery.localazy.com/_a65347067752521597762935bd1b/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/sk/frontend.json", + "sv": "https://delivery.localazy.com/_a65347067752521597762935bd1b/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/sv/frontend.json", + "uk": "https://delivery.localazy.com/_a65347067752521597762935bd1b/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/uk/frontend.json", + "uz": "https://delivery.localazy.com/_a65347067752521597762935bd1b/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/uz/frontend.json", + "zh#Hans": "https://delivery.localazy.com/_a65347067752521597762935bd1b/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/zh-Hans/frontend.json" } }, { @@ -234,25 +234,25 @@ const localazyMetadata: LocalazyMetadata = { file: "file.json", path: "", cdnFiles: { - "cs": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/cs/file.json", - "da": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/da/file.json", - "de": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/de/file.json", - "en": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/en/file.json", - "et": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/et/file.json", - "fi": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/fi/file.json", - "fr": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/fr/file.json", - "hu": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/hu/file.json", - "nb_NO": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/nb-NO/file.json", - "nl": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/nl/file.json", - "pl": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/pl/file.json", - "pt": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/pt/file.json", - "pt_BR": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/pt-BR/file.json", - "ru": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/ru/file.json", - "sk": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/sk/file.json", - "sv": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/sv/file.json", - "uk": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/uk/file.json", - "uz": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/uz/file.json", - "zh#Hans": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/zh-Hans/file.json" + "cs": "https://delivery.localazy.com/_a65347067752521597762935bd1b/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/cs/file.json", + "da": "https://delivery.localazy.com/_a65347067752521597762935bd1b/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/da/file.json", + "de": "https://delivery.localazy.com/_a65347067752521597762935bd1b/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/de/file.json", + "en": "https://delivery.localazy.com/_a65347067752521597762935bd1b/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/en/file.json", + "et": "https://delivery.localazy.com/_a65347067752521597762935bd1b/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/et/file.json", + "fi": "https://delivery.localazy.com/_a65347067752521597762935bd1b/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/fi/file.json", + "fr": "https://delivery.localazy.com/_a65347067752521597762935bd1b/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/fr/file.json", + "hu": "https://delivery.localazy.com/_a65347067752521597762935bd1b/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/hu/file.json", + "nb_NO": "https://delivery.localazy.com/_a65347067752521597762935bd1b/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/nb-NO/file.json", + "nl": "https://delivery.localazy.com/_a65347067752521597762935bd1b/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/nl/file.json", + "pl": "https://delivery.localazy.com/_a65347067752521597762935bd1b/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/pl/file.json", + "pt": "https://delivery.localazy.com/_a65347067752521597762935bd1b/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/pt/file.json", + "pt_BR": "https://delivery.localazy.com/_a65347067752521597762935bd1b/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/pt-BR/file.json", + "ru": "https://delivery.localazy.com/_a65347067752521597762935bd1b/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/ru/file.json", + "sk": "https://delivery.localazy.com/_a65347067752521597762935bd1b/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/sk/file.json", + "sv": "https://delivery.localazy.com/_a65347067752521597762935bd1b/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/sv/file.json", + "uk": "https://delivery.localazy.com/_a65347067752521597762935bd1b/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/uk/file.json", + "uz": "https://delivery.localazy.com/_a65347067752521597762935bd1b/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/uz/file.json", + "zh#Hans": "https://delivery.localazy.com/_a65347067752521597762935bd1b/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/zh-Hans/file.json" } } ] From e2771abd55289591a4bfe6e828f97e4ed4e6a298 Mon Sep 17 00:00:00 2001 From: Quentin Gliech Date: Fri, 15 May 2026 13:36:08 +0200 Subject: [PATCH 13/26] Bump lettre to mitigate RUSTSEC-2026-0141 This also bumps rustls-platform-verifier to avoid duplicated dependencies in the tree --- Cargo.lock | 204 +++++++++++++++++------------------------------------ Cargo.toml | 4 +- 2 files changed, 65 insertions(+), 143 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 95b40d8d9..575a51699 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -71,18 +71,6 @@ dependencies = [ "subtle", ] -[[package]] -name = "ahash" -version = "0.8.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" -dependencies = [ - "cfg-if", - "once_cell", - "version_check", - "zerocopy", -] - [[package]] name = "aho-corasick" version = "1.1.3" @@ -852,12 +840,6 @@ dependencies = [ "shlex", ] -[[package]] -name = "cesu8" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" - [[package]] name = "cfg-if" version = "1.0.3" @@ -927,16 +909,6 @@ dependencies = [ "chrono", ] -[[package]] -name = "chumsky" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8eebd66744a15ded14960ab4ccdbfb51ad3b81f51f3f04a80adac98c985396c9" -dependencies = [ - "hashbrown 0.14.5", - "stacker", -] - [[package]] name = "cipher" version = "0.4.4" @@ -1727,7 +1699,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -2136,10 +2108,6 @@ name = "hashbrown" version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" -dependencies = [ - "ahash", - "allocator-api2", -] [[package]] name = "hashbrown" @@ -2857,25 +2825,52 @@ checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" [[package]] name = "jni" -version = "0.21.1" +version = "0.22.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" +checksum = "5efd9a482cf3a427f00d6b35f14332adc7902ce91efb778580e180ff90fa3498" dependencies = [ - "cesu8", "cfg-if", "combine", + "jni-macros", "jni-sys", "log", - "thiserror 1.0.69", + "simd_cesu8", + "thiserror 2.0.17", "walkdir", - "windows-sys 0.45.0", + "windows-link 0.2.1", +] + +[[package]] +name = "jni-macros" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a00109accc170f0bdb141fed3e393c565b6f5e072365c3bd58f5b062591560a3" +dependencies = [ + "proc-macro2", + "quote", + "rustc_version", + "simd_cesu8", + "syn", ] [[package]] name = "jni-sys" -version = "0.3.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" +checksum = "c6377a88cb3910bee9b0fa88d4f42e1d2da8e79915598f65fb0c7ee14c878af2" +dependencies = [ + "jni-sys-macros", +] + +[[package]] +name = "jni-sys-macros" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264" +dependencies = [ + "quote", + "syn", +] [[package]] name = "jobserver" @@ -2977,14 +2972,13 @@ checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" [[package]] name = "lettre" -version = "0.11.19" +version = "0.11.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e13e10e8818f8b2a60f52cb127041d388b89f3a96a62be9ceaffa22262fef7f" +checksum = "0da65617f6cb926332d039cb578aad56178da86e128db6a1b09f4c94fa5b3349" dependencies = [ "async-std", "async-trait", "base64", - "chumsky", "email-encoding", "email_address", "fastrand", @@ -4080,7 +4074,7 @@ dependencies = [ "sha1", "sha2", "sprintf", - "thiserror 1.0.69", + "thiserror 2.0.17", "tokio", "tracing", "urlencoding", @@ -4693,7 +4687,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9120690fafc389a67ba3803df527d0ec9cbbc9cc45e4cc20b332996dfb672425" dependencies = [ "anyhow", - "itertools 0.13.0", + "itertools 0.14.0", "proc-macro2", "quote", "syn", @@ -4714,15 +4708,6 @@ version = "2.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33cb294fe86a74cbcf50d4445b37da762029549ebeea341421c7c70370f86cac" -[[package]] -name = "psm" -version = "0.1.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e944464ec8536cd1beb0bbfd96987eb5e3b72f2ecdafdc5c769a37f1fa2ae1f" -dependencies = [ - "cc", -] - [[package]] name = "pulley-interpreter" version = "43.0.2" @@ -5131,7 +5116,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -5172,9 +5157,9 @@ dependencies = [ [[package]] name = "rustls-platform-verifier" -version = "0.6.1" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be59af91596cac372a6942530653ad0c3a246cdd491aaa9dcaee47f88d67d5a0" +checksum = "26d1e2536ce4f35f4846aa13bff16bd0ff40157cdb14cc056c7b14ba41233ba0" dependencies = [ "core-foundation 0.10.1", "core-foundation-sys", @@ -5188,7 +5173,7 @@ dependencies = [ "security-framework", "security-framework-sys", "webpki-root-certs", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -5368,9 +5353,9 @@ dependencies = [ [[package]] name = "security-framework" -version = "3.4.0" +version = "3.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60b369d18893388b345804dc0007963c99b7d665ae71d275812d828c6f089640" +checksum = "b3297343eaf830f66ede390ea39da1d462b6b0c1b000f420d0a83f898bbbe6ef" dependencies = [ "bitflags", "core-foundation 0.10.1", @@ -5732,6 +5717,22 @@ dependencies = [ "rand_core 0.6.4", ] +[[package]] +name = "simd_cesu8" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94f90157bb87cddf702797c5dadfa0be7d266cdf49e22da2fcaa32eff75b2c33" +dependencies = [ + "rustc_version", + "simdutf8", +] + +[[package]] +name = "simdutf8" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" + [[package]] name = "similar" version = "2.7.0" @@ -6023,19 +6024,6 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" -[[package]] -name = "stacker" -version = "0.1.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cddb07e32ddb770749da91081d8d0ac3a16f1a569a18b20348cd371f5dead06b" -dependencies = [ - "cc", - "cfg-if", - "libc", - "psm", - "windows-sys 0.59.0", -] - [[package]] name = "static_assertions" version = "1.1.0" @@ -6198,7 +6186,7 @@ dependencies = [ "getrandom 0.3.3", "once_cell", "rustix", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -7321,7 +7309,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -7406,15 +7394,6 @@ dependencies = [ "windows-link 0.2.1", ] -[[package]] -name = "windows-sys" -version = "0.45.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" -dependencies = [ - "windows-targets 0.42.2", -] - [[package]] name = "windows-sys" version = "0.48.0" @@ -7460,21 +7439,6 @@ dependencies = [ "windows-link 0.2.1", ] -[[package]] -name = "windows-targets" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" -dependencies = [ - "windows_aarch64_gnullvm 0.42.2", - "windows_aarch64_msvc 0.42.2", - "windows_i686_gnu 0.42.2", - "windows_i686_msvc 0.42.2", - "windows_x86_64_gnu 0.42.2", - "windows_x86_64_gnullvm 0.42.2", - "windows_x86_64_msvc 0.42.2", -] - [[package]] name = "windows-targets" version = "0.48.5" @@ -7523,12 +7487,6 @@ dependencies = [ "windows_x86_64_msvc 0.53.0", ] -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" - [[package]] name = "windows_aarch64_gnullvm" version = "0.48.5" @@ -7547,12 +7505,6 @@ version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" -[[package]] -name = "windows_aarch64_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" - [[package]] name = "windows_aarch64_msvc" version = "0.48.5" @@ -7571,12 +7523,6 @@ version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" -[[package]] -name = "windows_i686_gnu" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" - [[package]] name = "windows_i686_gnu" version = "0.48.5" @@ -7607,12 +7553,6 @@ version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" -[[package]] -name = "windows_i686_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" - [[package]] name = "windows_i686_msvc" version = "0.48.5" @@ -7631,12 +7571,6 @@ version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" -[[package]] -name = "windows_x86_64_gnu" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" - [[package]] name = "windows_x86_64_gnu" version = "0.48.5" @@ -7655,12 +7589,6 @@ version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" - [[package]] name = "windows_x86_64_gnullvm" version = "0.48.5" @@ -7679,12 +7607,6 @@ version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" -[[package]] -name = "windows_x86_64_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" - [[package]] name = "windows_x86_64_msvc" version = "0.48.5" diff --git a/Cargo.toml b/Cargo.toml index a81b62eff..a095184c4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -359,7 +359,7 @@ features = ["serde"] # Email sending [workspace.dependencies.lettre] -version = "0.11.19" +version = "0.11.22" default-features = false features = [ "tokio1-rustls", @@ -539,7 +539,7 @@ version = "1.13.0" # Use platform-specific verifier for TLS [workspace.dependencies.rustls-platform-verifier] -version = "0.6.1" +version = "0.7.0" # systemd service status notification [workspace.dependencies.sd-notify] From 3ab421191ea5b95ea1d97089979a052cfb5d1de6 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 19 May 2026 14:19:56 +0000 Subject: [PATCH 14/26] 1.17.0 --- Cargo.lock | 56 +++++++++++++++++++++++++------------------------- Cargo.toml | 60 +++++++++++++++++++++++++++--------------------------- 2 files changed, 58 insertions(+), 58 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 575a51699..5ca447c0b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3092,7 +3092,7 @@ dependencies = [ [[package]] name = "mas-axum-utils" -version = "1.17.0-rc.0" +version = "1.17.0" dependencies = [ "anyhow", "axum", @@ -3126,7 +3126,7 @@ dependencies = [ [[package]] name = "mas-cli" -version = "1.17.0-rc.0" +version = "1.17.0" dependencies = [ "anyhow", "axum", @@ -3201,7 +3201,7 @@ dependencies = [ [[package]] name = "mas-config" -version = "1.17.0-rc.0" +version = "1.17.0" dependencies = [ "anyhow", "camino", @@ -3232,7 +3232,7 @@ dependencies = [ [[package]] name = "mas-context" -version = "1.17.0-rc.0" +version = "1.17.0" dependencies = [ "console", "opentelemetry", @@ -3248,7 +3248,7 @@ dependencies = [ [[package]] name = "mas-data-model" -version = "1.17.0-rc.0" +version = "1.17.0" dependencies = [ "base64ct", "chrono", @@ -3270,7 +3270,7 @@ dependencies = [ [[package]] name = "mas-email" -version = "1.17.0-rc.0" +version = "1.17.0" dependencies = [ "async-trait", "lettre", @@ -3281,7 +3281,7 @@ dependencies = [ [[package]] name = "mas-handlers" -version = "1.17.0-rc.0" +version = "1.17.0" dependencies = [ "aide", "anyhow", @@ -3362,7 +3362,7 @@ dependencies = [ [[package]] name = "mas-http" -version = "1.17.0-rc.0" +version = "1.17.0" dependencies = [ "futures-util", "headers", @@ -3383,7 +3383,7 @@ dependencies = [ [[package]] name = "mas-i18n" -version = "1.17.0-rc.0" +version = "1.17.0" dependencies = [ "camino", "icu_calendar", @@ -3405,7 +3405,7 @@ dependencies = [ [[package]] name = "mas-i18n-scan" -version = "1.17.0-rc.0" +version = "1.17.0" dependencies = [ "camino", "clap", @@ -3419,7 +3419,7 @@ dependencies = [ [[package]] name = "mas-iana" -version = "1.17.0-rc.0" +version = "1.17.0" dependencies = [ "schemars 0.9.0", "serde", @@ -3427,7 +3427,7 @@ dependencies = [ [[package]] name = "mas-iana-codegen" -version = "1.17.0-rc.0" +version = "1.17.0" dependencies = [ "anyhow", "async-trait", @@ -3444,7 +3444,7 @@ dependencies = [ [[package]] name = "mas-jose" -version = "1.17.0-rc.0" +version = "1.17.0" dependencies = [ "base64ct", "chrono", @@ -3474,7 +3474,7 @@ dependencies = [ [[package]] name = "mas-keystore" -version = "1.17.0-rc.0" +version = "1.17.0" dependencies = [ "aead", "base64ct", @@ -3502,7 +3502,7 @@ dependencies = [ [[package]] name = "mas-listener" -version = "1.17.0-rc.0" +version = "1.17.0" dependencies = [ "anyhow", "bytes", @@ -3526,7 +3526,7 @@ dependencies = [ [[package]] name = "mas-matrix" -version = "1.17.0-rc.0" +version = "1.17.0" dependencies = [ "anyhow", "async-trait", @@ -3536,7 +3536,7 @@ dependencies = [ [[package]] name = "mas-matrix-synapse" -version = "1.17.0-rc.0" +version = "1.17.0" dependencies = [ "anyhow", "async-trait", @@ -3553,7 +3553,7 @@ dependencies = [ [[package]] name = "mas-oidc-client" -version = "1.17.0-rc.0" +version = "1.17.0" dependencies = [ "assert_matches", "async-trait", @@ -3589,7 +3589,7 @@ dependencies = [ [[package]] name = "mas-policy" -version = "1.17.0-rc.0" +version = "1.17.0" dependencies = [ "anyhow", "arc-swap", @@ -3606,7 +3606,7 @@ dependencies = [ [[package]] name = "mas-router" -version = "1.17.0-rc.0" +version = "1.17.0" dependencies = [ "axum", "serde", @@ -3617,7 +3617,7 @@ dependencies = [ [[package]] name = "mas-spa" -version = "1.17.0-rc.0" +version = "1.17.0" dependencies = [ "camino", "serde", @@ -3626,7 +3626,7 @@ dependencies = [ [[package]] name = "mas-storage" -version = "1.17.0-rc.0" +version = "1.17.0" dependencies = [ "async-trait", "chrono", @@ -3648,7 +3648,7 @@ dependencies = [ [[package]] name = "mas-storage-pg" -version = "1.17.0-rc.0" +version = "1.17.0" dependencies = [ "async-trait", "chrono", @@ -3678,7 +3678,7 @@ dependencies = [ [[package]] name = "mas-tasks" -version = "1.17.0-rc.0" +version = "1.17.0" dependencies = [ "anyhow", "async-trait", @@ -3710,7 +3710,7 @@ dependencies = [ [[package]] name = "mas-templates" -version = "1.17.0-rc.0" +version = "1.17.0" dependencies = [ "anyhow", "arc-swap", @@ -3742,7 +3742,7 @@ dependencies = [ [[package]] name = "mas-tower" -version = "1.17.0-rc.0" +version = "1.17.0" dependencies = [ "http", "opentelemetry", @@ -3995,7 +3995,7 @@ dependencies = [ [[package]] name = "oauth2-types" -version = "1.17.0-rc.0" +version = "1.17.0" dependencies = [ "assert_matches", "base64ct", @@ -6094,7 +6094,7 @@ dependencies = [ [[package]] name = "syn2mas" -version = "1.17.0-rc.0" +version = "1.17.0" dependencies = [ "anyhow", "arc-swap", diff --git a/Cargo.toml b/Cargo.toml index a095184c4..5348f2b47 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,7 +9,7 @@ members = ["crates/*"] resolver = "2" # Updated in the CI with a `sed` command -package.version = "1.17.0-rc.0" +package.version = "1.17.0" package.license = "AGPL-3.0-only OR LicenseRef-Element-Commercial" package.authors = ["Element Backend Team"] package.edition = "2024" @@ -39,35 +39,35 @@ broken_intra_doc_links = "deny" [workspace.dependencies] # Workspace crates -mas-axum-utils = { path = "./crates/axum-utils/", version = "=1.17.0-rc.0" } -mas-cli = { path = "./crates/cli/", version = "=1.17.0-rc.0" } -mas-config = { path = "./crates/config/", version = "=1.17.0-rc.0" } -mas-context = { path = "./crates/context/", version = "=1.17.0-rc.0" } -mas-data-model = { path = "./crates/data-model/", version = "=1.17.0-rc.0" } -mas-email = { path = "./crates/email/", version = "=1.17.0-rc.0" } -mas-graphql = { path = "./crates/graphql/", version = "=1.17.0-rc.0" } -mas-handlers = { path = "./crates/handlers/", version = "=1.17.0-rc.0" } -mas-http = { path = "./crates/http/", version = "=1.17.0-rc.0" } -mas-i18n = { path = "./crates/i18n/", version = "=1.17.0-rc.0" } -mas-i18n-scan = { path = "./crates/i18n-scan/", version = "=1.17.0-rc.0" } -mas-iana = { path = "./crates/iana/", version = "=1.17.0-rc.0" } -mas-iana-codegen = { path = "./crates/iana-codegen/", version = "=1.17.0-rc.0" } -mas-jose = { path = "./crates/jose/", version = "=1.17.0-rc.0" } -mas-keystore = { path = "./crates/keystore/", version = "=1.17.0-rc.0" } -mas-listener = { path = "./crates/listener/", version = "=1.17.0-rc.0" } -mas-matrix = { path = "./crates/matrix/", version = "=1.17.0-rc.0" } -mas-matrix-synapse = { path = "./crates/matrix-synapse/", version = "=1.17.0-rc.0" } -mas-oidc-client = { path = "./crates/oidc-client/", version = "=1.17.0-rc.0" } -mas-policy = { path = "./crates/policy/", version = "=1.17.0-rc.0" } -mas-router = { path = "./crates/router/", version = "=1.17.0-rc.0" } -mas-spa = { path = "./crates/spa/", version = "=1.17.0-rc.0" } -mas-storage = { path = "./crates/storage/", version = "=1.17.0-rc.0" } -mas-storage-pg = { path = "./crates/storage-pg/", version = "=1.17.0-rc.0" } -mas-tasks = { path = "./crates/tasks/", version = "=1.17.0-rc.0" } -mas-templates = { path = "./crates/templates/", version = "=1.17.0-rc.0" } -mas-tower = { path = "./crates/tower/", version = "=1.17.0-rc.0" } -oauth2-types = { path = "./crates/oauth2-types/", version = "=1.17.0-rc.0" } -syn2mas = { path = "./crates/syn2mas", version = "=1.17.0-rc.0" } +mas-axum-utils = { path = "./crates/axum-utils/", version = "=1.17.0" } +mas-cli = { path = "./crates/cli/", version = "=1.17.0" } +mas-config = { path = "./crates/config/", version = "=1.17.0" } +mas-context = { path = "./crates/context/", version = "=1.17.0" } +mas-data-model = { path = "./crates/data-model/", version = "=1.17.0" } +mas-email = { path = "./crates/email/", version = "=1.17.0" } +mas-graphql = { path = "./crates/graphql/", version = "=1.17.0" } +mas-handlers = { path = "./crates/handlers/", version = "=1.17.0" } +mas-http = { path = "./crates/http/", version = "=1.17.0" } +mas-i18n = { path = "./crates/i18n/", version = "=1.17.0" } +mas-i18n-scan = { path = "./crates/i18n-scan/", version = "=1.17.0" } +mas-iana = { path = "./crates/iana/", version = "=1.17.0" } +mas-iana-codegen = { path = "./crates/iana-codegen/", version = "=1.17.0" } +mas-jose = { path = "./crates/jose/", version = "=1.17.0" } +mas-keystore = { path = "./crates/keystore/", version = "=1.17.0" } +mas-listener = { path = "./crates/listener/", version = "=1.17.0" } +mas-matrix = { path = "./crates/matrix/", version = "=1.17.0" } +mas-matrix-synapse = { path = "./crates/matrix-synapse/", version = "=1.17.0" } +mas-oidc-client = { path = "./crates/oidc-client/", version = "=1.17.0" } +mas-policy = { path = "./crates/policy/", version = "=1.17.0" } +mas-router = { path = "./crates/router/", version = "=1.17.0" } +mas-spa = { path = "./crates/spa/", version = "=1.17.0" } +mas-storage = { path = "./crates/storage/", version = "=1.17.0" } +mas-storage-pg = { path = "./crates/storage-pg/", version = "=1.17.0" } +mas-tasks = { path = "./crates/tasks/", version = "=1.17.0" } +mas-templates = { path = "./crates/templates/", version = "=1.17.0" } +mas-tower = { path = "./crates/tower/", version = "=1.17.0" } +oauth2-types = { path = "./crates/oauth2-types/", version = "=1.17.0" } +syn2mas = { path = "./crates/syn2mas", version = "=1.17.0" } # OpenAPI schema generation and validation [workspace.dependencies.aide] From f0100c4fa891d72a9e7dd90332d87c1a2f835de9 Mon Sep 17 00:00:00 2001 From: Quentin Gliech Date: Wed, 20 May 2026 09:34:26 +0200 Subject: [PATCH 15/26] Disable provenance in the metadata output --- .github/workflows/build.yaml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 255771d3e..79cf89b33 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -282,6 +282,14 @@ jobs: - name: Build and push id: bake uses: docker/bake-action@a66e1c87e2eca0503c343edf1d208c716d54b8a8 # v7.1.0 + env: + # By default, docker bake will add provenance information to the + # metadata output. This makes the output larger and may exceed the + # shell ARG_MAX limit. Disabling through this environment variable + # disables provenance in the metadata while still attaching provenance + # attestations to the image we push. + # https://github.com/docker/bake-action/issues/239#issuecomment-3828170326 + BUILDX_METADATA_PROVENANCE: disabled with: files: | ./docker-bake.hcl From 815e9ef19ac29b45710373cad3621c71af60c131 Mon Sep 17 00:00:00 2001 From: Quentin Gliech Date: Wed, 20 May 2026 09:50:18 +0200 Subject: [PATCH 16/26] Skip oci.element.io push on PR-labelled builds Tailscale + Vault JWT auth needs a `push`-event OIDC token, so gate the oci-push registry image and its login steps on `github.event_name == 'push'`. PR-labelled builds (`Z-Build-Workflow`) push only to ghcr.io. --- .github/workflows/build.yaml | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index cc8d2a5bf..7a5840ceb 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -227,9 +227,11 @@ jobs: id: meta uses: docker/metadata-action@v5.10.0 with: + # The oci-push registry login requires Tailscale + Vault, which only + # works for `push` events (PR-labelled runs lack the right OIDC token). images: | ghcr.io/element-hq/matrix-authentication-service - oci-push.vpn.infra.element.io/matrix-authentication-service + ${{ github.event_name == 'push' && 'oci-push.vpn.infra.element.io/matrix-authentication-service' || '' }} bake-target: docker-metadata-action flavor: | latest=auto @@ -247,7 +249,7 @@ jobs: with: images: | ghcr.io/element-hq/matrix-authentication-service - oci-push.vpn.infra.element.io/matrix-authentication-service + ${{ github.event_name == 'push' && 'oci-push.vpn.infra.element.io/matrix-authentication-service' || '' }} bake-target: docker-metadata-action-debug flavor: | latest=auto @@ -277,7 +279,11 @@ jobs: username: ${{ github.repository_owner }} password: ${{ secrets.GITHUB_TOKEN }} + # The Element OCI Registry is only reachable via Tailscale, and the Vault + # JWT exchange relies on a GitHub OIDC token issued from a `push` event. + # PR-labelled builds (`Z-Build-Workflow`) skip this and push only to ghcr. - name: Tailscale + if: github.event_name == 'push' uses: tailscale/github-action@53acf823325fe9ca47f4cdaa951f90b4b0de5bb9 # v4.1.1 with: oauth-client-id: ${{ secrets.TS_OAUTH_CLIENT_ID }} @@ -286,11 +292,13 @@ jobs: - name: Compute vault jwt role name id: vault-jwt-role + if: github.event_name == 'push' run: | echo "role_name=github_service_management_$( echo "${{ github.repository }}" | sed -r 's|[/-]|_|g')" | tee -a "$GITHUB_OUTPUT" - name: Get team registry token id: import-secrets + if: github.event_name == 'push' uses: hashicorp/vault-action@4c06c5ccf5c0761b6029f56cfb1dcf5565918a3b # v3.4.0 with: url: https://vault.infra.ci.i.element.dev @@ -303,6 +311,7 @@ jobs: services/backend-repositories/secret/data/oci.element.io password | OCI_PASSWORD ; - name: Login to Element OCI Registry + if: github.event_name == 'push' uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0 with: registry: oci-push.vpn.infra.element.io From e833483070f27081e6262ee36c6a377cfc9503af Mon Sep 17 00:00:00 2001 From: Quentin Gliech Date: Wed, 20 May 2026 10:44:20 +0200 Subject: [PATCH 17/26] Bump OCI login action to v4.1.0 to match the GHCR login --- .github/workflows/build.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index f3f8851a3..2077a5d3e 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -317,7 +317,7 @@ jobs: - name: Login to Element OCI Registry if: github.event_name == 'push' - uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0 + uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0 with: registry: oci-push.vpn.infra.element.io username: ${{ steps.import-secrets.outputs.OCI_USERNAME }} From 6946e57ffdbd93c402f20d8b4eef3e5ccbe1185d Mon Sep 17 00:00:00 2001 From: Quentin Gliech Date: Wed, 20 May 2026 10:58:01 +0200 Subject: [PATCH 18/26] Fix the release notes reference to the image --- .github/workflows/build.yaml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 2077a5d3e..2a7425221 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -299,7 +299,7 @@ jobs: id: vault-jwt-role if: github.event_name == 'push' run: | - echo "role_name=github_service_management_$( echo "${{ github.repository }}" | sed -r 's|[/-]|_|g')" | tee -a "$GITHUB_OUTPUT" + echo "role_name=github_service_management_$( echo "${{ github.repository }}" | sed -r 's|[/-]|_|g')" | tee -a "$GITHUB_OUTPUT" - name: Get team registry token id: import-secrets @@ -312,8 +312,8 @@ jobs: jwtGithubAudience: https://vault.infra.ci.i.element.dev method: jwt secrets: | - services/backend-repositories/secret/data/oci.element.io username | OCI_USERNAME ; - services/backend-repositories/secret/data/oci.element.io password | OCI_PASSWORD ; + services/backend-repositories/secret/data/oci.element.io username | OCI_USERNAME ; + services/backend-repositories/secret/data/oci.element.io password | OCI_PASSWORD ; - name: Login to Element OCI Registry if: github.event_name == 'push' @@ -413,7 +413,7 @@ jobs: - Digest: ``` ghcr.io/element-hq/matrix-authentication-service@${{ fromJSON(needs.build-image.outputs.metadata).regular.digest }} - oci-push.vpn.infra.element.io/matrix-authentication-service@${{ fromJSON(needs.build-image.outputs.metadata).regular.digest }} + oci.element.io/matrix-authentication-service@${{ fromJSON(needs.build-image.outputs.metadata).regular.digest }} ``` - Tags: ``` @@ -426,7 +426,7 @@ jobs: - Digest: ``` ghcr.io/element-hq/matrix-authentication-service@${{ fromJSON(needs.build-image.outputs.metadata).debug.digest }} - oci-push.vpn.infra.element.io/matrix-authentication-service@${{ fromJSON(needs.build-image.outputs.metadata).debug.digest }} + oci.element.io/matrix-authentication-service@${{ fromJSON(needs.build-image.outputs.metadata).debug.digest }} ``` - Tags: ``` @@ -493,7 +493,7 @@ jobs: - Digest: ``` ghcr.io/element-hq/matrix-authentication-service@${{ fromJSON(needs.build-image.outputs.metadata).regular.digest }} - oci-push.vpn.infra.element.io/matrix-authentication-service@${{ fromJSON(needs.build-image.outputs.metadata).regular.digest }} + oci.element.io/matrix-authentication-service@${{ fromJSON(needs.build-image.outputs.metadata).regular.digest }} ``` - Tags: ``` @@ -506,7 +506,7 @@ jobs: - Digest: ``` ghcr.io/element-hq/matrix-authentication-service@${{ fromJSON(needs.build-image.outputs.metadata).debug.digest }} - oci-push.vpn.infra.element.io/matrix-authentication-service@${{ fromJSON(needs.build-image.outputs.metadata).debug.digest }} + oci.element.io/matrix-authentication-service@${{ fromJSON(needs.build-image.outputs.metadata).debug.digest }} ``` - Tags: ``` From c2dc7c11a961ee61fcfe35cd75562da08dc8bfb5 Mon Sep 17 00:00:00 2001 From: Quentin Gliech Date: Thu, 5 Feb 2026 12:02:45 +0100 Subject: [PATCH 19/26] Split multi-arch Docker build into parallel jobs - Modify Dockerfile to build single architecture based on TARGETARCH instead of cross-compiling both targets in one run - Replace single build-image job with matrix job (amd64, arm64) - Add finalize-image job that creates multi-arch manifests using `docker buildx imagetools create` and signs the final images - Each architecture gets its own build cache This enables parallel builds of each architecture, reducing total build time by running both simultaneously rather than sequentially. --- .github/workflows/build.yaml | 333 +++++++++++++++++++++++++++++------ Dockerfile | 23 +-- docker-bake.hcl | 10 +- 3 files changed, 296 insertions(+), 70 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 2a7425221..b5b1c8e5b 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -207,25 +207,17 @@ jobs: name: mas-cli-x86_64-linux path: mas-cli-x86_64-linux.tar.gz - build-image: - name: Build and push Docker image + compute-image-meta: + name: Compute Docker image metadata if: github.event_name == 'push' || github.event.label.name == 'Z-Build-Workflow' runs-on: ubuntu-24.04 - outputs: - metadata: ${{ steps.metadata.outputs.result }} - permissions: contents: read - packages: write - id-token: write - needs: - - compute-version - - env: - VERGEN_GIT_DESCRIBE: ${{ needs.compute-version.outputs.describe }} - SOURCE_DATE_EPOCH: ${{ needs.compute-version.outputs.timestamp }} + outputs: + regular-json: ${{ steps.meta.outputs.json }} + debug-json: ${{ steps.meta-debug.outputs.json }} steps: - name: Docker meta @@ -247,6 +239,15 @@ jobs: type=semver,pattern={{major}}.{{minor}} type=semver,pattern={{major}} type=sha + # GitHub's license detection (via Licensee) can only report a single + # SPDX identifier and still emits the legacy `AGPL-3.0` form, which + # `metadata-action` would otherwise propagate as-is. Override it with + # the project's actual dual-license SPDX expression so the image + # advertises both halves of the dual licensing. + labels: | + org.opencontainers.image.licenses=AGPL-3.0-only OR LicenseRef-Element-Commercial + annotations: | + org.opencontainers.image.licenses=AGPL-3.0-only OR LicenseRef-Element-Commercial - name: Docker meta (debug variant) id: meta-debug @@ -266,10 +267,63 @@ jobs: type=semver,pattern={{major}}.{{minor}} type=semver,pattern={{major}} type=sha + labels: | + org.opencontainers.image.licenses=AGPL-3.0-only OR LicenseRef-Element-Commercial + annotations: | + org.opencontainers.image.licenses=AGPL-3.0-only OR LicenseRef-Element-Commercial - - name: Setup Cosign - uses: sigstore/cosign-installer@cad07c2e89fa2edd6e2d7bab4c1aa38e53f76003 # v4.1.1 + # Stage the labels bake files under predictable names (the metadata-action + # writes them under a random temp dir with the same base name) and ship + # them to `build-image` as an artifact. We deliberately only pass through + # `bake-file-labels` (not `bake-file-annotations`): the per-arch images + # then carry the same config labels as today, while annotations are only + # applied at the index level in `finalize-image` — matching `:latest`'s + # shape and sidestepping `metadata-action`'s empty-value annotations + # which would otherwise trip `docker buildx imagetools create`. + - name: Stage bake files + env: + REGULAR_FILE: ${{ steps.meta.outputs.bake-file-labels }} + DEBUG_FILE: ${{ steps.meta-debug.outputs.bake-file-labels }} + run: | + mkdir -p /tmp/bake + cp "$REGULAR_FILE" /tmp/bake/regular.json + cp "$DEBUG_FILE" /tmp/bake/debug.json + - name: Upload bake files + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 + with: + name: bake-files + path: /tmp/bake/ + retention-days: 1 + + build-image: + name: Build Docker image (${{ matrix.arch }}) + if: github.event_name == 'push' || github.event.label.name == 'Z-Build-Workflow' + runs-on: ubuntu-24.04 + + permissions: + contents: read + packages: write + id-token: write + + needs: + - compute-version + - compute-image-meta + + strategy: + fail-fast: false + matrix: + arch: [amd64, arm64] + + env: + VERGEN_GIT_DESCRIBE: ${{ needs.compute-version.outputs.describe }} + SOURCE_DATE_EPOCH: ${{ needs.compute-version.outputs.timestamp }} + # Comma-separated list of registries to push each per-arch image to. + # The oci-push registry is only included on `push` events because the + # Tailscale + Vault login below requires the right OIDC token. + IMAGES: ghcr.io/element-hq/matrix-authentication-service${{ github.event_name == 'push' && ',oci-push.vpn.infra.element.io/matrix-authentication-service' || '' }} + + steps: - name: Set up Docker Buildx uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0 with: @@ -323,7 +377,13 @@ jobs: username: ${{ steps.import-secrets.outputs.OCI_USERNAME }} password: ${{ steps.import-secrets.outputs.OCI_PASSWORD }} - - name: Build and push + - name: Download bake files + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8 + with: + name: bake-files + path: /tmp/bake + + - name: Build and push by digest id: bake uses: docker/bake-action@a66e1c87e2eca0503c343edf1d208c716d54b8a8 # v7.1.0 env: @@ -337,35 +397,185 @@ jobs: with: files: | ./docker-bake.hcl - cwd://${{ steps.meta.outputs.bake-file }} - cwd://${{ steps.meta-debug.outputs.bake-file }} + cwd:///tmp/bake/regular.json + cwd:///tmp/bake/debug.json set: | - base.output=type=image,push=true - base.cache-from=type=registry,ref=${{ env.BUILDCACHE }}:buildcache - base.cache-to=type=registry,ref=${{ env.BUILDCACHE }}:buildcache,mode=max + *.platform=linux/${{ matrix.arch }} + *.output=type=image,"name=${{ env.IMAGES }}",push-by-digest=true,name-canonical=true,push=true + *.cache-from=type=registry,ref=${{ env.BUILDCACHE }}:buildcache-${{ matrix.arch }} + *.cache-to=type=registry,ref=${{ env.BUILDCACHE }}:buildcache-${{ matrix.arch }},mode=max - - name: Transform bake output - # This transforms the output to an object which looks like this: - # { regular: { digest: "…", tags: ["…", "…"] }, debug: { digest: "…", tags: ["…"] }, … } - # We use github-script rather than shelling out to jq because the bake - # metadata can exceed the shell ARG_MAX limit when expanded. - id: metadata + # We use github-script rather than shelling out to jq because the bake + # metadata can exceed the shell ARG_MAX limit when inherited as an env + # var by an exec'd jq. + - name: Export digests uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 env: STEPS_BAKE_OUTPUTS_METADATA: ${{ steps.bake.outputs.metadata }} + ARCH: ${{ matrix.arch }} with: script: | + const fs = require('node:fs'); + const path = require('node:path'); const bakeOutput = JSON.parse(process.env.STEPS_BAKE_OUTPUTS_METADATA); - const metadata = {}; - for (const [key, value] of Object.entries(bakeOutput)) { - if (value && typeof value === 'object' && ('containerimage.digest' in value)) { - metadata[key] = { - digest: value['containerimage.digest'], - tags: value['image.name'].split(','), - }; + const arch = process.env.ARCH; + fs.mkdirSync('/tmp/digests', { recursive: true }); + for (const target of ['regular', 'debug']) { + const digest = bakeOutput[target]?.['containerimage.digest']; + if (!digest) { + throw new Error(`Missing containerimage.digest for target ${target}`); } + fs.writeFileSync(path.join('/tmp/digests', `${target}-${arch}`), digest); } - return metadata; + + - name: Upload digests + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 + with: + name: digests-${{ matrix.arch }} + path: /tmp/digests/* + retention-days: 1 + + finalize-image: + name: Create multi-arch manifests + if: github.event_name == 'push' || github.event.label.name == 'Z-Build-Workflow' + runs-on: ubuntu-24.04 + + needs: + - build-image + - compute-image-meta + + outputs: + metadata: ${{ steps.output.outputs.metadata }} + + permissions: + contents: read + packages: write + id-token: write + + steps: + - name: Download digests + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8 + with: + pattern: digests-* + path: /tmp/digests + merge-multiple: true + + - name: Setup Cosign + uses: sigstore/cosign-installer@cad07c2e89fa2edd6e2d7bab4c1aa38e53f76003 # v4.1.1 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0 + + - name: Login to GitHub Container Registry + uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0 + with: + registry: ghcr.io + username: ${{ github.repository_owner }} + password: ${{ secrets.GITHUB_TOKEN }} + + # See `build-image` for why the Element OCI Registry login is gated on + # `push` events. + - name: Tailscale + if: github.event_name == 'push' + uses: tailscale/github-action@53acf823325fe9ca47f4cdaa951f90b4b0de5bb9 # v4.1.1 + with: + oauth-client-id: ${{ secrets.TS_OAUTH_CLIENT_ID }} + audience: ${{ secrets.TS_AUDIENCE }} + tags: tag:github-actions + + - name: Compute vault jwt role name + id: vault-jwt-role + if: github.event_name == 'push' + run: | + echo "role_name=github_service_management_$( echo "${{ github.repository }}" | sed -r 's|[/-]|_|g')" | tee -a "$GITHUB_OUTPUT" + + - name: Get team registry token + id: import-secrets + if: github.event_name == 'push' + uses: hashicorp/vault-action@4c06c5ccf5c0761b6029f56cfb1dcf5565918a3b # v3.4.0 + with: + url: https://vault.infra.ci.i.element.dev + role: ${{ steps.vault-jwt-role.outputs.role_name }} + path: service-management/github-actions + jwtGithubAudience: https://vault.infra.ci.i.element.dev + method: jwt + secrets: | + services/backend-repositories/secret/data/oci.element.io username | OCI_USERNAME ; + services/backend-repositories/secret/data/oci.element.io password | OCI_PASSWORD ; + + - name: Login to Element OCI Registry + if: github.event_name == 'push' + uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0 + with: + registry: oci-push.vpn.infra.element.io + username: ${{ steps.import-secrets.outputs.OCI_USERNAME }} + password: ${{ steps.import-secrets.outputs.OCI_PASSWORD }} + + - name: Create regular manifest + env: + META_JSON: ${{ needs.compute-image-meta.outputs.regular-json }} + run: | + REGULAR_AMD64=$(cat /tmp/digests/regular-amd64) + REGULAR_ARM64=$(cat /tmp/digests/regular-arm64) + # Build `-t TAG` and `--annotation index:NAME=VALUE` args into a bash + # array so that values containing spaces survive shell word-splitting. + # We keep the `index:` prefix so the annotations land on the index + # manifest itself rather than getting applied at the default manifest + # level. Empty-valued entries are filtered as a safety net (the bake + # step's annotation file is already pre-filtered upstream). + declare -a ARGS=() + while IFS= read -r tag; do + ARGS+=(-t "$tag") + done < <(jq -r '.tags[]' <<< "$META_JSON") + while IFS= read -r annotation; do + ARGS+=(--annotation "$annotation") + done < <(jq -r ' + .annotations + | map(select(startswith("index:"))) + | map(select(endswith("=") | not)) + | .[] + ' <<< "$META_JSON") + docker buildx imagetools create \ + "${ARGS[@]}" \ + "ghcr.io/element-hq/matrix-authentication-service@$REGULAR_AMD64" \ + "ghcr.io/element-hq/matrix-authentication-service@$REGULAR_ARM64" + + - name: Create debug manifest + env: + META_DEBUG_JSON: ${{ needs.compute-image-meta.outputs.debug-json }} + run: | + DEBUG_AMD64=$(cat /tmp/digests/debug-amd64) + DEBUG_ARM64=$(cat /tmp/digests/debug-arm64) + declare -a ARGS=() + while IFS= read -r tag; do + ARGS+=(-t "$tag") + done < <(jq -r '.tags[]' <<< "$META_DEBUG_JSON") + while IFS= read -r annotation; do + ARGS+=(--annotation "$annotation") + done < <(jq -r ' + .annotations + | map(select(startswith("index:"))) + | map(select(endswith("=") | not)) + | .[] + ' <<< "$META_DEBUG_JSON") + docker buildx imagetools create \ + "${ARGS[@]}" \ + "ghcr.io/element-hq/matrix-authentication-service@$DEBUG_AMD64" \ + "ghcr.io/element-hq/matrix-authentication-service@$DEBUG_ARM64" + + - name: Get manifest digests + id: manifests + env: + META_JSON: ${{ needs.compute-image-meta.outputs.regular-json }} + META_DEBUG_JSON: ${{ needs.compute-image-meta.outputs.debug-json }} + run: | + # Inspect the manifest list under the first tag to retrieve its digest + REGULAR_TAG=$(jq -r '.tags[0]' <<< "$META_JSON") + DEBUG_TAG=$(jq -r '.tags[0]' <<< "$META_DEBUG_JSON") + REGULAR_DIGEST=$(docker buildx imagetools inspect "$REGULAR_TAG" --format '{{ json . }}' | jq -r '.manifest.digest') + DEBUG_DIGEST=$(docker buildx imagetools inspect "$DEBUG_TAG" --format '{{ json . }}' | jq -r '.manifest.digest') + echo "regular=$REGULAR_DIGEST" >> $GITHUB_OUTPUT + echo "debug=$DEBUG_DIGEST" >> $GITHUB_OUTPUT - name: Sign the images with GitHub Actions provided token # Only sign on tags and on commits on main branch @@ -374,8 +584,8 @@ jobs: && (startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/main') env: - REGULAR_DIGEST: ${{ steps.metadata.outputs.result && fromJSON(steps.metadata.outputs.result).regular.digest }} - DEBUG_DIGEST: ${{ steps.metadata.outputs.result && fromJSON(steps.metadata.outputs.result).debug.digest }} + REGULAR_DIGEST: ${{ steps.manifests.outputs.regular }} + DEBUG_DIGEST: ${{ steps.manifests.outputs.debug }} run: |- cosign sign --yes \ @@ -385,13 +595,30 @@ jobs: "oci-push.vpn.infra.element.io/matrix-authentication-service@$REGULAR_DIGEST" \ "oci-push.vpn.infra.element.io/matrix-authentication-service@$DEBUG_DIGEST" + - name: Output metadata + id: output + env: + REGULAR_DIGEST: ${{ steps.manifests.outputs.regular }} + DEBUG_DIGEST: ${{ steps.manifests.outputs.debug }} + META_JSON: ${{ needs.compute-image-meta.outputs.regular-json }} + META_DEBUG_JSON: ${{ needs.compute-image-meta.outputs.debug-json }} + run: | + echo 'metadata<> $GITHUB_OUTPUT + jq -nc \ + --arg regular_digest "$REGULAR_DIGEST" \ + --arg debug_digest "$DEBUG_DIGEST" \ + --argjson regular_tags "$(jq '.tags' <<< "$META_JSON")" \ + --argjson debug_tags "$(jq '.tags' <<< "$META_DEBUG_JSON")" \ + '{regular: {digest: $regular_digest, tags: $regular_tags}, debug: {digest: $debug_digest, tags: $debug_tags}}' >> $GITHUB_OUTPUT + echo 'EOF' >> $GITHUB_OUTPUT + release: name: Release if: startsWith(github.ref, 'refs/tags/') runs-on: ubuntu-24.04 needs: - assemble-archives - - build-image + - finalize-image steps: - name: Download the artifacts from the previous job @@ -412,12 +639,12 @@ jobs: - Digest: ``` - ghcr.io/element-hq/matrix-authentication-service@${{ fromJSON(needs.build-image.outputs.metadata).regular.digest }} - oci.element.io/matrix-authentication-service@${{ fromJSON(needs.build-image.outputs.metadata).regular.digest }} + ghcr.io/element-hq/matrix-authentication-service@${{ fromJSON(needs.finalize-image.outputs.metadata).regular.digest }} + oci.element.io/matrix-authentication-service@${{ fromJSON(needs.finalize-image.outputs.metadata).regular.digest }} ``` - Tags: ``` - ${{ join(fromJSON(needs.build-image.outputs.metadata).regular.tags, ' + ${{ join(fromJSON(needs.finalize-image.outputs.metadata).regular.tags, ' ') }} ``` @@ -425,12 +652,12 @@ jobs: - Digest: ``` - ghcr.io/element-hq/matrix-authentication-service@${{ fromJSON(needs.build-image.outputs.metadata).debug.digest }} - oci.element.io/matrix-authentication-service@${{ fromJSON(needs.build-image.outputs.metadata).debug.digest }} + ghcr.io/element-hq/matrix-authentication-service@${{ fromJSON(needs.finalize-image.outputs.metadata).debug.digest }} + oci.element.io/matrix-authentication-service@${{ fromJSON(needs.finalize-image.outputs.metadata).debug.digest }} ``` - Tags: ``` - ${{ join(fromJSON(needs.build-image.outputs.metadata).debug.tags, ' + ${{ join(fromJSON(needs.finalize-image.outputs.metadata).debug.tags, ' ') }} ``` @@ -446,7 +673,7 @@ jobs: needs: - assemble-archives - - build-image + - finalize-image permissions: contents: write @@ -492,12 +719,12 @@ jobs: - Digest: ``` - ghcr.io/element-hq/matrix-authentication-service@${{ fromJSON(needs.build-image.outputs.metadata).regular.digest }} - oci.element.io/matrix-authentication-service@${{ fromJSON(needs.build-image.outputs.metadata).regular.digest }} + ghcr.io/element-hq/matrix-authentication-service@${{ fromJSON(needs.finalize-image.outputs.metadata).regular.digest }} + oci.element.io/matrix-authentication-service@${{ fromJSON(needs.finalize-image.outputs.metadata).regular.digest }} ``` - Tags: ``` - ${{ join(fromJSON(needs.build-image.outputs.metadata).regular.tags, ' + ${{ join(fromJSON(needs.finalize-image.outputs.metadata).regular.tags, ' ') }} ``` @@ -505,12 +732,12 @@ jobs: - Digest: ``` - ghcr.io/element-hq/matrix-authentication-service@${{ fromJSON(needs.build-image.outputs.metadata).debug.digest }} - oci.element.io/matrix-authentication-service@${{ fromJSON(needs.build-image.outputs.metadata).debug.digest }} + ghcr.io/element-hq/matrix-authentication-service@${{ fromJSON(needs.finalize-image.outputs.metadata).debug.digest }} + oci.element.io/matrix-authentication-service@${{ fromJSON(needs.finalize-image.outputs.metadata).debug.digest }} ``` - Tags: ``` - ${{ join(fromJSON(needs.build-image.outputs.metadata).debug.tags, ' + ${{ join(fromJSON(needs.finalize-image.outputs.metadata).debug.tags, ' ') }} ``` @@ -526,7 +753,7 @@ jobs: if: github.event_name == 'pull_request' && github.event.label.name == 'Z-Build-Workflow' needs: - - build-image + - finalize-image permissions: contents: read @@ -543,7 +770,7 @@ jobs: - name: Remove label and comment uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 env: - BUILD_IMAGE_MANIFEST: ${{ needs.build-image.outputs.metadata }} + BUILD_IMAGE_MANIFEST: ${{ needs.finalize-image.outputs.metadata }} with: script: | const script = require('./.github/scripts/cleanup-pr.cjs'); diff --git a/Dockerfile b/Dockerfile index ba2ed16c8..1998779da 100644 --- a/Dockerfile +++ b/Dockerfile @@ -6,9 +6,9 @@ # Please see LICENSE files in the repository root for full details. # Builds a minimal image with the binary only. It is multi-arch capable, -# cross-building to aarch64 and x86_64. When cross-compiling, Docker sets two +# cross-building to aarch64 or x86_64. When cross-compiling, Docker sets two # implicit BUILDARG: BUILDPLATFORM being the host platform and TARGETPLATFORM -# being the platform being built. +# being the platform being built. Each architecture is built separately. # The Debian version and version name must be in sync ARG DEBIAN_VERSION=13 @@ -119,20 +119,25 @@ ENV SQLX_OFFLINE=true ARG VERGEN_GIT_DESCRIBE ENV VERGEN_GIT_DESCRIBE=${VERGEN_GIT_DESCRIBE} +ARG TARGETARCH + # Network access: cargo auditable needs it RUN --network=default \ --mount=type=cache,target=/root/.cargo/registry \ --mount=type=cache,target=/app/target \ + RUST_TARGET=$(case "${TARGETARCH}" in \ + amd64) echo "x86_64-unknown-linux-gnu" ;; \ + arm64) echo "aarch64-unknown-linux-gnu" ;; \ + *) echo "unsupported architecture: ${TARGETARCH}" >&2; exit 1 ;; \ + esac) && \ cargo auditable build \ --locked \ --release \ --bin mas-cli \ --no-default-features \ --features docker \ - --target x86_64-unknown-linux-gnu \ - --target aarch64-unknown-linux-gnu \ - && mv "target/x86_64-unknown-linux-gnu/release/mas-cli" /usr/local/bin/mas-cli-amd64 \ - && mv "target/aarch64-unknown-linux-gnu/release/mas-cli" /usr/local/bin/mas-cli-arm64 + --target "${RUST_TARGET}" \ + && mv "target/${RUST_TARGET}/release/mas-cli" /usr/local/bin/mas-cli ####################################### ## Prepare /usr/local/share/mas-cli/ ## @@ -149,8 +154,7 @@ COPY ./translations/ /share/translations ################################## FROM gcr.io/distroless/cc-debian${DEBIAN_VERSION}:debug-nonroot AS debug -ARG TARGETARCH -COPY --from=builder /usr/local/bin/mas-cli-${TARGETARCH} /usr/local/bin/mas-cli +COPY --from=builder /usr/local/bin/mas-cli /usr/local/bin/mas-cli COPY --from=share /share /usr/local/share/mas-cli WORKDIR / @@ -161,8 +165,7 @@ ENTRYPOINT ["/usr/local/bin/mas-cli"] ################### FROM gcr.io/distroless/cc-debian${DEBIAN_VERSION}:nonroot -ARG TARGETARCH -COPY --from=builder /usr/local/bin/mas-cli-${TARGETARCH} /usr/local/bin/mas-cli +COPY --from=builder /usr/local/bin/mas-cli /usr/local/bin/mas-cli COPY --from=share /share /usr/local/share/mas-cli WORKDIR / diff --git a/docker-bake.hcl b/docker-bake.hcl index 3c3cac3af..6f6879e1a 100644 --- a/docker-bake.hcl +++ b/docker-bake.hcl @@ -1,3 +1,4 @@ +# Copyright 2025, 2026 Element Creations Ltd. # Copyright 2025 New Vector Ltd. # # SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial @@ -15,8 +16,8 @@ group "default" { targets = ["regular", "debug"] } target "docker-metadata-action" {} target "docker-metadata-action-debug" {} -// This sets the platforms and is further extended by GitHub Actions to set the -// output and the cache locations +// This is extended by GitHub Actions to set the output, cache locations, +// and platforms (one architecture per job for parallel builds) target "base" { args = { // This is set so that when we use a git context, the .git directory is @@ -26,11 +27,6 @@ target "base" { // Pass down the version from an external git describe source VERGEN_GIT_DESCRIBE = "${VERGEN_GIT_DESCRIBE}" } - - platforms = [ - "linux/amd64", - "linux/arm64", - ] } target "regular" { From d88db7deff22d31ae7e356bb5138983eaf8b51a4 Mon Sep 17 00:00:00 2001 From: Quentin Gliech Date: Wed, 20 May 2026 14:41:53 +0200 Subject: [PATCH 20/26] Simplify the injection of annotations in the final manifest --- .github/workflows/build.yaml | 132 +++++++++++++++-------------------- 1 file changed, 57 insertions(+), 75 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index b5b1c8e5b..bbb617a0d 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -28,7 +28,9 @@ env: SCCACHE_GHA_ENABLED: "true" RUSTC_WRAPPER: "sccache" BUILDCACHE: ghcr.io/element-hq/matrix-authentication-service/buildcache - DOCKER_METADATA_ANNOTATIONS_LEVELS: manifest,index + # We only ever apply annotations to the image index (manifest list) during + # `finalize-image`; per-arch manifests don't get any. + DOCKER_METADATA_ANNOTATIONS_LEVELS: index jobs: compute-version: @@ -216,8 +218,10 @@ jobs: contents: read outputs: - regular-json: ${{ steps.meta.outputs.json }} - debug-json: ${{ steps.meta-debug.outputs.json }} + regular-tags: ${{ steps.meta.outputs.tags }} + regular-annotations: ${{ steps.meta.outputs.annotations }} + debug-tags: ${{ steps.meta-debug.outputs.tags }} + debug-annotations: ${{ steps.meta-debug.outputs.annotations }} steps: - name: Docker meta @@ -512,70 +516,43 @@ jobs: password: ${{ steps.import-secrets.outputs.OCI_PASSWORD }} - name: Create regular manifest + id: regular env: - META_JSON: ${{ needs.compute-image-meta.outputs.regular-json }} + TAGS: ${{ needs.compute-image-meta.outputs.regular-tags }} + ANNOTATIONS: ${{ needs.compute-image-meta.outputs.regular-annotations }} run: | - REGULAR_AMD64=$(cat /tmp/digests/regular-amd64) - REGULAR_ARM64=$(cat /tmp/digests/regular-arm64) - # Build `-t TAG` and `--annotation index:NAME=VALUE` args into a bash - # array so that values containing spaces survive shell word-splitting. - # We keep the `index:` prefix so the annotations land on the index - # manifest itself rather than getting applied at the default manifest - # level. Empty-valued entries are filtered as a safety net (the bake - # step's annotation file is already pre-filtered upstream). - declare -a ARGS=() - while IFS= read -r tag; do - ARGS+=(-t "$tag") - done < <(jq -r '.tags[]' <<< "$META_JSON") - while IFS= read -r annotation; do - ARGS+=(--annotation "$annotation") - done < <(jq -r ' - .annotations - | map(select(startswith("index:"))) - | map(select(endswith("=") | not)) - | .[] - ' <<< "$META_JSON") - docker buildx imagetools create \ - "${ARGS[@]}" \ - "ghcr.io/element-hq/matrix-authentication-service@$REGULAR_AMD64" \ - "ghcr.io/element-hq/matrix-authentication-service@$REGULAR_ARM64" + # Construct the `imagetools create` command line from the tag and annotation inputs. + args=() + + # Add a `-t ` argument for each non-empty tag. + while IFS= read -r t; do [[ -n $t ]] && args+=(-t "$t"); done <<< "$TAGS" + + # Add a `--annotation =` argument for each non-empty annotation + while IFS= read -r a; do [[ -n $a && $a != *= ]] && args+=(--annotation "$a"); done <<< "$ANNOTATIONS" + + docker buildx imagetools create "${args[@]}" \ + "ghcr.io/element-hq/matrix-authentication-service@$(cat /tmp/digests/regular-amd64)" \ + "ghcr.io/element-hq/matrix-authentication-service@$(cat /tmp/digests/regular-arm64)" \ + --metadata-file regular-metadata.json + + # `imagetools create` wrote the digest to regular-metadata.json + echo "digest=$(jq -r '.["containerimage.descriptor"].digest' regular-metadata.json)" >> "$GITHUB_OUTPUT" - name: Create debug manifest + id: debug env: - META_DEBUG_JSON: ${{ needs.compute-image-meta.outputs.debug-json }} + TAGS: ${{ needs.compute-image-meta.outputs.debug-tags }} + ANNOTATIONS: ${{ needs.compute-image-meta.outputs.debug-annotations }} run: | - DEBUG_AMD64=$(cat /tmp/digests/debug-amd64) - DEBUG_ARM64=$(cat /tmp/digests/debug-arm64) - declare -a ARGS=() - while IFS= read -r tag; do - ARGS+=(-t "$tag") - done < <(jq -r '.tags[]' <<< "$META_DEBUG_JSON") - while IFS= read -r annotation; do - ARGS+=(--annotation "$annotation") - done < <(jq -r ' - .annotations - | map(select(startswith("index:"))) - | map(select(endswith("=") | not)) - | .[] - ' <<< "$META_DEBUG_JSON") - docker buildx imagetools create \ - "${ARGS[@]}" \ - "ghcr.io/element-hq/matrix-authentication-service@$DEBUG_AMD64" \ - "ghcr.io/element-hq/matrix-authentication-service@$DEBUG_ARM64" - - - name: Get manifest digests - id: manifests - env: - META_JSON: ${{ needs.compute-image-meta.outputs.regular-json }} - META_DEBUG_JSON: ${{ needs.compute-image-meta.outputs.debug-json }} - run: | - # Inspect the manifest list under the first tag to retrieve its digest - REGULAR_TAG=$(jq -r '.tags[0]' <<< "$META_JSON") - DEBUG_TAG=$(jq -r '.tags[0]' <<< "$META_DEBUG_JSON") - REGULAR_DIGEST=$(docker buildx imagetools inspect "$REGULAR_TAG" --format '{{ json . }}' | jq -r '.manifest.digest') - DEBUG_DIGEST=$(docker buildx imagetools inspect "$DEBUG_TAG" --format '{{ json . }}' | jq -r '.manifest.digest') - echo "regular=$REGULAR_DIGEST" >> $GITHUB_OUTPUT - echo "debug=$DEBUG_DIGEST" >> $GITHUB_OUTPUT + # See comments in regular manifest creation for argument construction. + args=() + while IFS= read -r t; do [[ -n $t ]] && args+=(-t "$t"); done <<< "$TAGS" + while IFS= read -r a; do [[ -n $a && $a != *= ]] && args+=(--annotation "$a"); done <<< "$ANNOTATIONS" + docker buildx imagetools create "${args[@]}" \ + "ghcr.io/element-hq/matrix-authentication-service@$(cat /tmp/digests/debug-amd64)" \ + "ghcr.io/element-hq/matrix-authentication-service@$(cat /tmp/digests/debug-arm64)" \ + --metadata-file debug-metadata.json + echo "digest=$(jq -r '.["containerimage.descriptor"].digest' debug-metadata.json)" >> "$GITHUB_OUTPUT" - name: Sign the images with GitHub Actions provided token # Only sign on tags and on commits on main branch @@ -584,8 +561,8 @@ jobs: && (startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/main') env: - REGULAR_DIGEST: ${{ steps.manifests.outputs.regular }} - DEBUG_DIGEST: ${{ steps.manifests.outputs.debug }} + REGULAR_DIGEST: ${{ steps.regular.outputs.digest }} + DEBUG_DIGEST: ${{ steps.debug.outputs.digest }} run: |- cosign sign --yes \ @@ -598,19 +575,24 @@ jobs: - name: Output metadata id: output env: - REGULAR_DIGEST: ${{ steps.manifests.outputs.regular }} - DEBUG_DIGEST: ${{ steps.manifests.outputs.debug }} - META_JSON: ${{ needs.compute-image-meta.outputs.regular-json }} - META_DEBUG_JSON: ${{ needs.compute-image-meta.outputs.debug-json }} + REGULAR_DIGEST: ${{ steps.regular.outputs.digest }} + DEBUG_DIGEST: ${{ steps.debug.outputs.digest }} + REGULAR_TAGS: ${{ needs.compute-image-meta.outputs.regular-tags }} + DEBUG_TAGS: ${{ needs.compute-image-meta.outputs.debug-tags }} run: | - echo 'metadata<> $GITHUB_OUTPUT - jq -nc \ - --arg regular_digest "$REGULAR_DIGEST" \ - --arg debug_digest "$DEBUG_DIGEST" \ - --argjson regular_tags "$(jq '.tags' <<< "$META_JSON")" \ - --argjson debug_tags "$(jq '.tags' <<< "$META_DEBUG_JSON")" \ - '{regular: {digest: $regular_digest, tags: $regular_tags}, debug: {digest: $debug_digest, tags: $debug_tags}}' >> $GITHUB_OUTPUT - echo 'EOF' >> $GITHUB_OUTPUT + # Convert the newline-separated tag lists into JSON arrays. + regular_tags=$(jq -Rnc '[inputs | select(length > 0)]' <<< "$REGULAR_TAGS") + debug_tags=$(jq -Rnc '[inputs | select(length > 0)]' <<< "$DEBUG_TAGS") + { + echo 'metadata<> "$GITHUB_OUTPUT" release: name: Release From 63deb0b1fdba9cfeee9e19455b06cef175810388 Mon Sep 17 00:00:00 2001 From: Quentin Gliech Date: Wed, 20 May 2026 14:42:17 +0200 Subject: [PATCH 21/26] Don't specify DOCKER_METADATA_ANNOTATIONS_LEVELS We're injecting annotations manually anyway --- .github/workflows/build.yaml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index bbb617a0d..d1121a3c5 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -28,9 +28,6 @@ env: SCCACHE_GHA_ENABLED: "true" RUSTC_WRAPPER: "sccache" BUILDCACHE: ghcr.io/element-hq/matrix-authentication-service/buildcache - # We only ever apply annotations to the image index (manifest list) during - # `finalize-image`; per-arch manifests don't get any. - DOCKER_METADATA_ANNOTATIONS_LEVELS: index jobs: compute-version: From 7834229784abceee2fd02bec725ddc16d5a5bc4f Mon Sep 17 00:00:00 2001 From: Quentin Gliech Date: Wed, 20 May 2026 15:22:56 +0200 Subject: [PATCH 22/26] Re-add DOCKER_METADATA_ANNOTATIONS_LEVELS to narrow annotations to the index `docker buildx imagetools create --annotation manifest:KEY=VALUE` errors out with "manifest annotations are not supported yet". metadata-action defaults to emitting `manifest:` prefixed entries, so without an explicit `DOCKER_METADATA_ANNOTATIONS_LEVELS: index` the finalize step blows up the first time it sees a non-empty annotations list. --- .github/workflows/build.yaml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index d1121a3c5..1dea54dcb 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -28,6 +28,11 @@ env: SCCACHE_GHA_ENABLED: "true" RUSTC_WRAPPER: "sccache" BUILDCACHE: ghcr.io/element-hq/matrix-authentication-service/buildcache + # metadata-action defaults to `manifest`, which `docker buildx imagetools + # create --annotation` refuses with "manifest annotations are not supported + # yet". We only want annotations on the manifest list anyway, so narrow it + # to `index`. + DOCKER_METADATA_ANNOTATIONS_LEVELS: index jobs: compute-version: From eeea952b55c2cc9e518529bafa43a48fc76e8ab5 Mon Sep 17 00:00:00 2001 From: Quentin Gliech Date: Wed, 20 May 2026 15:24:19 +0200 Subject: [PATCH 23/26] Add a comment about the artefact collection --- .github/workflows/build.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 1dea54dcb..2991a558e 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -464,6 +464,7 @@ jobs: with: pattern: digests-* path: /tmp/digests + # Collect digests from both amd64 and arm64 builds merge-multiple: true - name: Setup Cosign From 2aba54c4ba11c5aa5dc38213695f1c228c65d11f Mon Sep 17 00:00:00 2001 From: Eric Eastwood Date: Wed, 20 May 2026 11:47:18 -0500 Subject: [PATCH 24/26] Update reason for why synchronous device creation See https://github.com/element-hq/matrix-authentication-service/pull/5679#discussion_r3237767718 --- crates/handlers/src/compat/login.rs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/crates/handlers/src/compat/login.rs b/crates/handlers/src/compat/login.rs index 61eee7f4a..858b7afcd 100644 --- a/crates/handlers/src/compat/login.rs +++ b/crates/handlers/src/compat/login.rs @@ -458,12 +458,13 @@ pub(crate) async fn post( // Now we can create the device on the homeserver, without holding the // transaction // - // Normally, devices get synced to the homeserver in a `SyncDevicesJob` but we - // want the device to be created synchronously on the homeserver, so that - // when we respond, the device already exists (TODO: Why important?). We're - // using an upsert so if the device already exists for some reason (like - // when we're replacing it, or a concurrent device sync happening) it won't - // have any effect. + // Normally, devices get synced to the homeserver in a `SyncDevicesJob` but we want + // the device to be created synchronously on the homeserver, so that when we + // respond, the device already exists. If the device doesn't exist on Synapse, token + // introspection from Synapse to MAS will work, but then the device won't exist, so + // Synapse will return a 401. We're using an upsert so if the device already exists + // for some reason (like when we're replacing it, or a concurrent device sync + // happening) it won't have any effect. if let Err(err) = homeserver .upsert_device( &user.username, From 7764e9e2961c4eefe892583f7b8276065ae626f5 Mon Sep 17 00:00:00 2001 From: Eric Eastwood Date: Wed, 20 May 2026 11:48:37 -0500 Subject: [PATCH 25/26] Formatting --- crates/handlers/src/compat/login.rs | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/crates/handlers/src/compat/login.rs b/crates/handlers/src/compat/login.rs index 858b7afcd..01ae4192f 100644 --- a/crates/handlers/src/compat/login.rs +++ b/crates/handlers/src/compat/login.rs @@ -458,13 +458,14 @@ pub(crate) async fn post( // Now we can create the device on the homeserver, without holding the // transaction // - // Normally, devices get synced to the homeserver in a `SyncDevicesJob` but we want - // the device to be created synchronously on the homeserver, so that when we - // respond, the device already exists. If the device doesn't exist on Synapse, token - // introspection from Synapse to MAS will work, but then the device won't exist, so - // Synapse will return a 401. We're using an upsert so if the device already exists - // for some reason (like when we're replacing it, or a concurrent device sync - // happening) it won't have any effect. + // Normally, devices get synced to the homeserver in a `SyncDevicesJob` but we + // want the device to be created synchronously on the homeserver, so that + // when we respond, the device already exists. If the device doesn't exist + // on Synapse, token introspection from Synapse to MAS will work, but then + // the device won't exist, so Synapse will return a 401. We're using an + // upsert so if the device already exists for some reason (like when we're + // replacing it, or a concurrent device sync happening) it won't have any + // effect. if let Err(err) = homeserver .upsert_device( &user.username, From b73e06aded47c63c3baf88f52f66c53ddd1f6608 Mon Sep 17 00:00:00 2001 From: Eric Eastwood Date: Wed, 20 May 2026 11:58:36 -0500 Subject: [PATCH 26/26] Update comment langauge and add it to other login spots --- crates/handlers/src/compat/login.rs | 14 ++++++++------ crates/handlers/src/oauth2/token.rs | 18 ++++++++++++++++++ 2 files changed, 26 insertions(+), 6 deletions(-) diff --git a/crates/handlers/src/compat/login.rs b/crates/handlers/src/compat/login.rs index 01ae4192f..37bcaa0c6 100644 --- a/crates/handlers/src/compat/login.rs +++ b/crates/handlers/src/compat/login.rs @@ -460,12 +460,14 @@ pub(crate) async fn post( // // Normally, devices get synced to the homeserver in a `SyncDevicesJob` but we // want the device to be created synchronously on the homeserver, so that - // when we respond, the device already exists. If the device doesn't exist - // on Synapse, token introspection from Synapse to MAS will work, but then - // the device won't exist, so Synapse will return a 401. We're using an - // upsert so if the device already exists for some reason (like when we're - // replacing it, or a concurrent device sync happening) it won't have any - // effect. + // when we respond, the access token works completely. If the device doesn't + // exist on the homeserver side, token introspection from Synapse to MAS + // will work but Synapse will return a 401 because it doesn't see the + // device. + // + // We're using an upsert so if the device already exists for some reason (like + // when we're replacing it, or a concurrent device sync happening) it won't + // have any effect. if let Err(err) = homeserver .upsert_device( &user.username, diff --git a/crates/handlers/src/oauth2/token.rs b/crates/handlers/src/oauth2/token.rs index e070b640b..925f4209d 100644 --- a/crates/handlers/src/oauth2/token.rs +++ b/crates/handlers/src/oauth2/token.rs @@ -573,6 +573,15 @@ async fn authorization_code_grant( // Look for device to provision for scope in &*session.scope { if let Some(device) = Device::from_scope_token(scope) { + // Normally, devices get synced to the homeserver in a `SyncDevicesJob` but we + // want the device to be created synchronously on the homeserver, so + // that when we respond, the access token works completely. If the + // device doesn't exist on the homeserver side, token introspection + // from Synapse to MAS will work but Synapse will return a 401 + // because it doesn't see the device. + // + // We're using an upsert so if the device already exists for some reason + // (like when a concurrent device sync happening) it won't have any effect. homeserver .upsert_device( &browser_session.user.username, @@ -977,6 +986,15 @@ async fn device_code_grant( // Look for device to provision for scope in &*session.scope { if let Some(device) = Device::from_scope_token(scope) { + // Normally, devices get synced to the homeserver in a `SyncDevicesJob` but we + // want the device to be created synchronously on the homeserver, so + // that when we respond, the access token works completely. If the + // device doesn't exist on the homeserver side, token introspection + // from Synapse to MAS will work but Synapse will return a 401 + // because it doesn't see the device. + // + // We're using an upsert so if the device already exists for some reason + // (like when a concurrent device sync happening) it won't have any effect. homeserver .upsert_device(&browser_session.user.username, device.as_str(), None) .await