From cf4e4300b3ef0b75d33f0b37c8c29ed6eaeda8d2 Mon Sep 17 00:00:00 2001 From: Matheus Cardoso Date: Wed, 4 Mar 2026 11:29:20 -0300 Subject: [PATCH 01/70] feat: conditional fips mode --- ee/apps/Dockerfile | 12 +++++++++++- ee/apps/ddp-streamer/src/fips.ts | 7 +++++++ 2 files changed, 18 insertions(+), 1 deletion(-) create mode 100644 ee/apps/ddp-streamer/src/fips.ts diff --git a/ee/apps/Dockerfile b/ee/apps/Dockerfile index 380aaee56b81e..9d0d6c756be01 100644 --- a/ee/apps/Dockerfile +++ b/ee/apps/Dockerfile @@ -50,7 +50,7 @@ RUN set -eu; cd /app; \ done; \ find packages ee/packages ee/apps \( -name '*.js.map' -o -name '*.d.ts' \) -delete -FROM node:22.22.3-alpine3.23 +FROM node:22.22.3-alpine3.23 AS release-standard ARG SERVICE @@ -76,3 +76,13 @@ USER rocketchat EXPOSE 3000 9458 CMD ["node", "src/service.js"] + +FROM rocketchatfips140/dhi-node:22-alpine3.23 AS release-fips +ARG SERVICE +ENV NODE_ENV=production \ + PORT=3000 +COPY --chown=node:node --from=builder /app /app +WORKDIR /app/ee/apps/${SERVICE} +USER node +EXPOSE 3000 9458 +CMD ["node", "--require", "./src/fips.js", "src/service.js"] diff --git a/ee/apps/ddp-streamer/src/fips.ts b/ee/apps/ddp-streamer/src/fips.ts new file mode 100644 index 0000000000000..db58817c919c3 --- /dev/null +++ b/ee/apps/ddp-streamer/src/fips.ts @@ -0,0 +1,7 @@ +import crypto from 'crypto'; + +crypto.setFips(true); + +console.log('========================================='); +console.log('FIPS COMPLIANCE CHECK: YES'); +console.log('========================================='); From 851ed25ac85991b6b13d631ede2bd7f57ca57c70 Mon Sep 17 00:00:00 2001 From: Matheus Cardoso Date: Thu, 5 Mar 2026 09:10:21 -0300 Subject: [PATCH 02/70] ci: add fips lane --- .github/workflows/ci-test-e2e.yml | 27 ++++++++-- .github/workflows/ci.yml | 84 ++++++++++++++++++++++++++++++- 2 files changed, 105 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci-test-e2e.yml b/.github/workflows/ci-test-e2e.yml index be682452ba934..2d8e533dd452c 100644 --- a/.github/workflows/ci-test-e2e.yml +++ b/.github/workflows/ci-test-e2e.yml @@ -49,6 +49,10 @@ on: required: true CR_PAT: required: true + TEMP_DOCKERHUB_FIPS_USER: + required: false + TEMP_DOCKERHUB_FIPS_PASS: + required: false QASE_API_TOKEN: required: false REPORTER_ROCKETCHAT_URL: @@ -119,6 +123,13 @@ jobs: username: ${{ secrets.CR_USER }} password: ${{ secrets.CR_PAT }} + - name: Login to DockerHub for FIPS base images + if: inputs.release == 'fips' + uses: docker/login-action@v3 + with: + username: ${{ secrets.TEMP_DOCKERHUB_FIPS_USER }} + password: ${{ secrets.TEMP_DOCKERHUB_FIPS_PASS }} + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - name: Setup NodeJS @@ -192,21 +203,27 @@ jobs: DEBUG_LOG_LEVEL=${DEBUG_LOG_LEVEL:-0} docker compose -f docker-compose-ci.yml up -d rocketchat --wait - name: Start containers for EE - if: inputs.release == 'ee' + if: inputs.release == 'ee' || inputs.release == 'fips' env: ENTERPRISE_LICENSE: ${{ inputs.enterprise-license }} TRANSPORTER: ${{ inputs.transporter }} COMPOSE_PROFILES: ${{ inputs.type == 'api' && 'api' || '' }} TEST_MODE: ${{ startsWith(inputs.type, 'api') && 'api' || 'true' }} APPS_ENGINE_RUNTIME_BACKEND: ${{ inputs.type == 'api-apps-node' && 'node' || '' }} + BUILD_TARGET: ${{ inputs.release == 'fips' && 'release-fips' || '' }} run: | + # Build only ddp-streamer locally when running FIPS to ensure the FIPS stage is used. + if [[ '${{ inputs.release }}' == 'fips' ]]; then + docker compose -f docker-compose-ci.yml build ddp-streamer-service + fi + DEBUG_LOG_LEVEL=${DEBUG_LOG_LEVEL:-0} docker compose -f docker-compose-ci.yml up -d --wait - uses: ./.github/actions/setup-playwright if: inputs.type == 'ui' - name: Wait services to start up - if: inputs.release == 'ee' + if: inputs.release == 'ee' || inputs.release == 'fips' run: | docker ps @@ -224,7 +241,7 @@ jobs: working-directory: ./apps/meteor env: WEBHOOK_TEST_URL: 'http://httpbin' - IS_EE: ${{ inputs.release == 'ee' && 'true' || '' }} + IS_EE: ${{ (inputs.release == 'ee' || inputs.release == 'fips') && 'true' || '' }} run: | set -o xtrace @@ -257,7 +274,7 @@ jobs: working-directory: ./apps/meteor env: WEBHOOK_TEST_URL: 'http://httpbin' - IS_EE: ${{ inputs.release == 'ee' && 'true' || '' }} + IS_EE: ${{ (inputs.release == 'ee' || inputs.release == 'fips') && 'true' || '' }} run: | set -o xtrace @@ -272,7 +289,7 @@ jobs: if: inputs.type == 'ui' env: E2E_COVERAGE: ${{ inputs.coverage == matrix.mongodb-version && 'true' || '' }} - IS_EE: ${{ inputs.release == 'ee' && 'true' || '' }} + IS_EE: ${{ (inputs.release == 'ee' || inputs.release == 'fips') && 'true' || '' }} REPORTER_ROCKETCHAT_API_KEY: ${{ secrets.REPORTER_ROCKETCHAT_API_KEY }} REPORTER_ROCKETCHAT_URL: ${{ secrets.REPORTER_ROCKETCHAT_URL }} REPORTER_JIRA_ROCKETCHAT_API_KEY: ${{ secrets.REPORTER_JIRA_ROCKETCHAT_API_KEY }} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f980f1a10f476..2d7fe4403aac5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -753,6 +753,76 @@ jobs: CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} REPORTER_JIRA_ROCKETCHAT_API_KEY: ${{ secrets.REPORTER_JIRA_ROCKETCHAT_API_KEY }} + test-api-fips: + name: 🔨 Test API (FIPS) + needs: [checks, build-gh-docker-publish, release-versions] + + uses: ./.github/workflows/ci-test-e2e.yml + with: + type: api + release: fips + transporter: 'nats://nats:4222' + enterprise-license: ${{ needs.release-versions.outputs.enterprise-license }} + mongodb-version: "['8.0']" + node-version: ${{ needs.release-versions.outputs.node-version }} + deno-version: ${{ needs.release-versions.outputs.deno-version }} + lowercase-repo: ${{ needs.release-versions.outputs.lowercase-repo }} + gh-docker-tag: ${{ needs.release-versions.outputs.gh-docker-tag }} + secrets: + CR_USER: ${{ secrets.CR_USER }} + CR_PAT: ${{ secrets.CR_PAT }} + TEMP_DOCKERHUB_FIPS_USER: ${{ secrets.TEMP_DOCKERHUB_FIPS_USER }} + TEMP_DOCKERHUB_FIPS_PASS: ${{ secrets.TEMP_DOCKERHUB_FIPS_PASS }} + + test-api-livechat-fips: + name: 🔨 Test API Livechat (FIPS) + needs: [checks, build-gh-docker-publish, release-versions] + + uses: ./.github/workflows/ci-test-e2e.yml + with: + type: api-livechat + release: fips + transporter: 'nats://nats:4222' + enterprise-license: ${{ needs.release-versions.outputs.enterprise-license }} + mongodb-version: "['8.0']" + node-version: ${{ needs.release-versions.outputs.node-version }} + deno-version: ${{ needs.release-versions.outputs.deno-version }} + lowercase-repo: ${{ needs.release-versions.outputs.lowercase-repo }} + gh-docker-tag: ${{ needs.release-versions.outputs.gh-docker-tag }} + secrets: + CR_USER: ${{ secrets.CR_USER }} + CR_PAT: ${{ secrets.CR_PAT }} + TEMP_DOCKERHUB_FIPS_USER: ${{ secrets.TEMP_DOCKERHUB_FIPS_USER }} + TEMP_DOCKERHUB_FIPS_PASS: ${{ secrets.TEMP_DOCKERHUB_FIPS_PASS }} + + test-ui-fips: + name: 🔨 Test UI (FIPS) + needs: [checks, build-gh-docker-publish, release-versions] + + uses: ./.github/workflows/ci-test-e2e.yml + with: + type: ui + release: fips + transporter: 'nats://nats:4222' + enterprise-license: ${{ needs.release-versions.outputs.enterprise-license }} + shard: '[1, 2, 3, 4, 5]' + total-shard: 5 + mongodb-version: "['8.0']" + node-version: ${{ needs.release-versions.outputs.node-version }} + deno-version: ${{ needs.release-versions.outputs.deno-version }} + lowercase-repo: ${{ needs.release-versions.outputs.lowercase-repo }} + gh-docker-tag: ${{ needs.release-versions.outputs.gh-docker-tag }} + retries: ${{ (github.event_name == 'release' || github.ref == 'refs/heads/develop' || github.ref == 'refs/heads/master') && 2 || 0 }} + secrets: + CR_USER: ${{ secrets.CR_USER }} + CR_PAT: ${{ secrets.CR_PAT }} + QASE_API_TOKEN: ${{ secrets.QASE_API_TOKEN }} + REPORTER_ROCKETCHAT_API_KEY: ${{ secrets.REPORTER_ROCKETCHAT_API_KEY }} + REPORTER_ROCKETCHAT_URL: ${{ secrets.REPORTER_ROCKETCHAT_URL }} + REPORTER_JIRA_ROCKETCHAT_API_KEY: ${{ secrets.REPORTER_JIRA_ROCKETCHAT_API_KEY }} + TEMP_DOCKERHUB_FIPS_USER: ${{ secrets.TEMP_DOCKERHUB_FIPS_USER }} + TEMP_DOCKERHUB_FIPS_PASS: ${{ secrets.TEMP_DOCKERHUB_FIPS_PASS }} + test-federation-matrix: name: 🔨 Test Federation Matrix needs: [checks, build-gh-docker-publish, packages-build, release-versions] @@ -907,7 +977,7 @@ jobs: tests-done: name: ✅ Tests Done runs-on: ubuntu-24.04-arm - needs: [test-guard, checks, test-unit, test-api, test-ui, test-api-ee, test-ui-ee, test-api-livechat, test-api-livechat-ee, test-api-apps-node-ee, test-federation-matrix] + needs: [test-guard, checks, test-unit, test-api, test-ui, test-api-ee, test-ui-ee, test-api-livechat, test-api-livechat-ee, test-api-apps-node-ee, test-api-fips, test-api-livechat-fips, test-ui-fips, test-federation-matrix] if: always() && needs.test-guard.outputs.skip-tests != 'true' steps: - name: Test finish aggregation @@ -948,6 +1018,18 @@ jobs: exit 1 fi + if [[ '${{ needs.test-api-fips.result }}' != 'success' ]]; then + exit 1 + fi + + if [[ '${{ needs.test-api-livechat-fips.result }}' != 'success' ]]; then + exit 1 + fi + + if [[ '${{ needs.test-ui-fips.result }}' != 'success' ]]; then + exit 1 + fi + if [[ '${{ needs.test-federation-matrix.result }}' != 'success' ]]; then exit 1 fi From 45176474a0be25f2150de19c27eda6e19ac36590 Mon Sep 17 00:00:00 2001 From: Matheus Cardoso Date: Thu, 5 Mar 2026 09:26:05 -0300 Subject: [PATCH 03/70] ci: build fips image in advance --- .github/actions/build-docker/action.yml | 17 ++++++++++++++- .github/workflows/ci-test-e2e.yml | 11 +--------- .github/workflows/ci.yml | 28 +++++++++++++++++++------ docker-compose-ci.yml | 2 +- 4 files changed, 40 insertions(+), 18 deletions(-) diff --git a/.github/actions/build-docker/action.yml b/.github/actions/build-docker/action.yml index 87389cbf52495..7cd637aad0784 100644 --- a/.github/actions/build-docker/action.yml +++ b/.github/actions/build-docker/action.yml @@ -8,6 +8,12 @@ inputs: CR_PAT: required: true description: 'GitHub Container Registry Personal Access Token' + TEMP_DOCKERHUB_FIPS_USER: + required: false + description: 'Temporary DockerHub user for FIPS base image pulls' + TEMP_DOCKERHUB_FIPS_PASS: + required: false + description: 'Temporary DockerHub password/token for FIPS base image pulls' deno-version: required: true description: 'Deno version' @@ -28,7 +34,7 @@ inputs: default: 'true' type: required: false - description: 'production or coverage' + description: 'production, coverage, or fips' default: 'coverage' runs: @@ -43,6 +49,13 @@ runs: username: ${{ inputs.CR_USER }} password: ${{ inputs.CR_PAT }} + - name: Login to DockerHub for FIPS base images + if: inputs.type == 'fips' + uses: docker/login-action@v3 + with: + username: ${{ inputs.TEMP_DOCKERHUB_FIPS_USER }} + password: ${{ inputs.TEMP_DOCKERHUB_FIPS_PASS }} + - name: Restore meteor build if: inputs.service == 'rocketchat' uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0 @@ -160,6 +173,8 @@ runs: SERVICE_SUFFIX='' if [[ "$INPUT_SERVICE" == 'rocketchat' && "$INPUT_TYPE" == 'coverage' ]] && [[ "$GITHUB_EVENT_NAME" == 'release' || "$GITHUB_REF" == 'refs/heads/develop' ]]; then SERVICE_SUFFIX='-cov' + elif [[ "$INPUT_SERVICE" == 'ddp-streamer-service' && "$INPUT_TYPE" == 'fips' ]]; then + SERVICE_SUFFIX='-fips' fi mkdir -p "/tmp/manifests/${INPUT_SERVICE}${SERVICE_SUFFIX}/${INPUT_ARCH}" diff --git a/.github/workflows/ci-test-e2e.yml b/.github/workflows/ci-test-e2e.yml index 2d8e533dd452c..1d5be8f179bd2 100644 --- a/.github/workflows/ci-test-e2e.yml +++ b/.github/workflows/ci-test-e2e.yml @@ -49,10 +49,6 @@ on: required: true CR_PAT: required: true - TEMP_DOCKERHUB_FIPS_USER: - required: false - TEMP_DOCKERHUB_FIPS_PASS: - required: false QASE_API_TOKEN: required: false REPORTER_ROCKETCHAT_URL: @@ -84,6 +80,7 @@ jobs: env: # if building for production on develop branch or release, add suffix for coverage images DOCKER_TAG_SUFFIX_ROCKETCHAT: ${{ inputs.coverage == matrix.mongodb-version && (github.event_name == 'release' || github.ref == 'refs/heads/develop') && '-cov' || '' }} + DOCKER_TAG_SUFFIX_DDP_STREAMER: ${{ inputs.release == 'fips' && '-fips' || '' }} MONGODB_VERSION: ${{ matrix.mongodb-version }} COVERAGE_DIR: '/tmp/coverage/${{ startsWith(inputs.type, ''api'') && ''api'' || inputs.type }}' COVERAGE_FILE_NAME: '${{ inputs.type }}-${{ matrix.shard }}.json' @@ -210,13 +207,7 @@ jobs: COMPOSE_PROFILES: ${{ inputs.type == 'api' && 'api' || '' }} TEST_MODE: ${{ startsWith(inputs.type, 'api') && 'api' || 'true' }} APPS_ENGINE_RUNTIME_BACKEND: ${{ inputs.type == 'api-apps-node' && 'node' || '' }} - BUILD_TARGET: ${{ inputs.release == 'fips' && 'release-fips' || '' }} run: | - # Build only ddp-streamer locally when running FIPS to ensure the FIPS stage is used. - if [[ '${{ inputs.release }}' == 'fips' ]]; then - docker compose -f docker-compose-ci.yml build ddp-streamer-service - fi - DEBUG_LOG_LEVEL=${DEBUG_LOG_LEVEL:-0} docker compose -f docker-compose-ci.yml up -d --wait - uses: ./.github/actions/setup-playwright diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2d7fe4403aac5..0a00ee0674e79 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -376,6 +376,10 @@ jobs: - arch: ${{ (github.event.pull_request.head.repo.full_name == github.repository || github.event_name == 'release' || github.ref == 'refs/heads/develop') && 'arm64' || 'amd64' }} service: [rocketchat] type: coverage + # build a dedicated FIPS ddp-streamer image for FIPS test lanes + - arch: amd64 + service: [ddp-streamer-service] + type: fips steps: - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 @@ -389,9 +393,13 @@ jobs: env: # add suffix for the extra images with coverage if building for production DOCKER_TAG_SUFFIX_ROCKETCHAT: ${{ matrix.type == 'coverage' && (github.event_name == 'release' || github.ref == 'refs/heads/develop') && '-cov' || '' }} + DOCKER_TAG_SUFFIX_DDP_STREAMER: ${{ matrix.service[0] == 'ddp-streamer-service' && matrix.type == 'fips' && '-fips' || '' }} + BUILD_TARGET: ${{ matrix.service[0] == 'ddp-streamer-service' && matrix.type == 'fips' && 'release-fips' || '' }} with: CR_USER: ${{ secrets.CR_USER }} CR_PAT: ${{ secrets.CR_PAT }} + TEMP_DOCKERHUB_FIPS_USER: ${{ secrets.TEMP_DOCKERHUB_FIPS_USER }} + TEMP_DOCKERHUB_FIPS_PASS: ${{ secrets.TEMP_DOCKERHUB_FIPS_PASS }} deno-version: ${{ needs.release-versions.outputs.deno-version }} arch: ${{ matrix.arch }} service: ${{ matrix.service[0] }} @@ -403,9 +411,13 @@ jobs: if: matrix.service[1] && github.actor != 'dependabot[bot]' env: DOCKER_TAG_SUFFIX_ROCKETCHAT: ${{ matrix.type == 'coverage' && '-cov' || '' }} + DOCKER_TAG_SUFFIX_DDP_STREAMER: ${{ matrix.service[1] == 'ddp-streamer-service' && matrix.type == 'fips' && '-fips' || '' }} + BUILD_TARGET: ${{ matrix.service[1] == 'ddp-streamer-service' && matrix.type == 'fips' && 'release-fips' || '' }} with: CR_USER: ${{ secrets.CR_USER }} CR_PAT: ${{ secrets.CR_PAT }} + TEMP_DOCKERHUB_FIPS_USER: ${{ secrets.TEMP_DOCKERHUB_FIPS_USER }} + TEMP_DOCKERHUB_FIPS_PASS: ${{ secrets.TEMP_DOCKERHUB_FIPS_PASS }} deno-version: ${{ needs.release-versions.outputs.deno-version }} arch: ${{ matrix.arch }} service: ${{ matrix.service[1] }} @@ -418,9 +430,13 @@ jobs: if: matrix.service[2] && github.actor != 'dependabot[bot]' env: DOCKER_TAG_SUFFIX_ROCKETCHAT: ${{ matrix.type == 'coverage' && '-cov' || '' }} + DOCKER_TAG_SUFFIX_DDP_STREAMER: ${{ matrix.service[2] == 'ddp-streamer-service' && matrix.type == 'fips' && '-fips' || '' }} + BUILD_TARGET: ${{ matrix.service[2] == 'ddp-streamer-service' && matrix.type == 'fips' && 'release-fips' || '' }} with: CR_USER: ${{ secrets.CR_USER }} CR_PAT: ${{ secrets.CR_PAT }} + TEMP_DOCKERHUB_FIPS_USER: ${{ secrets.TEMP_DOCKERHUB_FIPS_USER }} + TEMP_DOCKERHUB_FIPS_PASS: ${{ secrets.TEMP_DOCKERHUB_FIPS_PASS }} deno-version: ${{ needs.release-versions.outputs.deno-version }} arch: ${{ matrix.arch }} service: ${{ matrix.service[2] }} @@ -433,9 +449,13 @@ jobs: if: matrix.service[3] && github.actor != 'dependabot[bot]' env: DOCKER_TAG_SUFFIX_ROCKETCHAT: ${{ matrix.type == 'coverage' && '-cov' || '' }} + DOCKER_TAG_SUFFIX_DDP_STREAMER: ${{ matrix.service[3] == 'ddp-streamer-service' && matrix.type == 'fips' && '-fips' || '' }} + BUILD_TARGET: ${{ matrix.service[3] == 'ddp-streamer-service' && matrix.type == 'fips' && 'release-fips' || '' }} with: CR_USER: ${{ secrets.CR_USER }} CR_PAT: ${{ secrets.CR_PAT }} + TEMP_DOCKERHUB_FIPS_USER: ${{ secrets.TEMP_DOCKERHUB_FIPS_USER }} + TEMP_DOCKERHUB_FIPS_PASS: ${{ secrets.TEMP_DOCKERHUB_FIPS_PASS }} deno-version: ${{ needs.release-versions.outputs.deno-version }} arch: ${{ matrix.arch }} service: ${{ matrix.service[3] }} @@ -500,6 +520,8 @@ jobs: # Get image name from docker-compose-ci.yml since rocketchat image is different from service name (rocket.chat) if [ "$service" == "rocketchat-cov" ]; then IMAGE=$(docker compose -f docker-compose-ci.yml config --format json 2>/dev/null | jq -r --arg s "rocketchat" '.services[$s].image')-cov + elif [ "$service" == "ddp-streamer-service-fips" ]; then + IMAGE=$(docker compose -f docker-compose-ci.yml config --format json 2>/dev/null | jq -r --arg s "ddp-streamer-service" '.services[$s].image')-fips else IMAGE=$(docker compose -f docker-compose-ci.yml config --format json 2>/dev/null | jq -r --arg s "$service" '.services[$s].image') fi @@ -771,8 +793,6 @@ jobs: secrets: CR_USER: ${{ secrets.CR_USER }} CR_PAT: ${{ secrets.CR_PAT }} - TEMP_DOCKERHUB_FIPS_USER: ${{ secrets.TEMP_DOCKERHUB_FIPS_USER }} - TEMP_DOCKERHUB_FIPS_PASS: ${{ secrets.TEMP_DOCKERHUB_FIPS_PASS }} test-api-livechat-fips: name: 🔨 Test API Livechat (FIPS) @@ -792,8 +812,6 @@ jobs: secrets: CR_USER: ${{ secrets.CR_USER }} CR_PAT: ${{ secrets.CR_PAT }} - TEMP_DOCKERHUB_FIPS_USER: ${{ secrets.TEMP_DOCKERHUB_FIPS_USER }} - TEMP_DOCKERHUB_FIPS_PASS: ${{ secrets.TEMP_DOCKERHUB_FIPS_PASS }} test-ui-fips: name: 🔨 Test UI (FIPS) @@ -820,8 +838,6 @@ jobs: REPORTER_ROCKETCHAT_API_KEY: ${{ secrets.REPORTER_ROCKETCHAT_API_KEY }} REPORTER_ROCKETCHAT_URL: ${{ secrets.REPORTER_ROCKETCHAT_URL }} REPORTER_JIRA_ROCKETCHAT_API_KEY: ${{ secrets.REPORTER_JIRA_ROCKETCHAT_API_KEY }} - TEMP_DOCKERHUB_FIPS_USER: ${{ secrets.TEMP_DOCKERHUB_FIPS_USER }} - TEMP_DOCKERHUB_FIPS_PASS: ${{ secrets.TEMP_DOCKERHUB_FIPS_PASS }} test-federation-matrix: name: 🔨 Test Federation Matrix diff --git a/docker-compose-ci.yml b/docker-compose-ci.yml index cef50ced7cbf4..c7dcfffd19a45 100644 --- a/docker-compose-ci.yml +++ b/docker-compose-ci.yml @@ -128,7 +128,7 @@ services: - linux/arm64 args: SERVICE: ddp-streamer - image: ghcr.io/${LOWERCASE_REPOSITORY}/ddp-streamer-service:${DOCKER_TAG} + image: ghcr.io/${LOWERCASE_REPOSITORY}/ddp-streamer-service:${DOCKER_TAG}${DOCKER_TAG_SUFFIX_DDP_STREAMER:-} environment: - MONGO_URL=mongodb://mongo:27017/rocketchat?replicaSet=rs0 - 'TRANSPORTER=${TRANSPORTER:-}' From 38625c54936434cb40deb22c2921bd47bcac298f Mon Sep 17 00:00:00 2001 From: Matheus Cardoso Date: Thu, 5 Mar 2026 10:01:24 -0300 Subject: [PATCH 04/70] ci: allow selecting test lanes to run --- .github/workflows/ci.yml | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0a00ee0674e79..71fa12f253347 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,6 +1,16 @@ name: CI on: + workflow_dispatch: + inputs: + test-scope: + description: Select test lanes to run + required: false + default: all + type: choice + options: + - all + - fips release: types: [published] pull_request: @@ -540,7 +550,7 @@ jobs: name: 📦 Track Image Sizes needs: [build-gh-docker-publish, release-versions] runs-on: ubuntu-24.04-arm - if: github.event_name == 'pull_request' || github.ref == 'refs/heads/develop' + if: github.event_name != 'workflow_dispatch' && (github.event_name == 'pull_request' || github.ref == 'refs/heads/develop') permissions: pull-requests: write contents: write @@ -626,6 +636,7 @@ jobs: test-api: name: 🔨 Test API (CE) needs: [checks, build-gh-docker-publish, release-versions] + if: ${{ github.event_name != 'workflow_dispatch' || inputs.test-scope != 'fips' }} uses: ./.github/workflows/ci-test-e2e.yml with: @@ -643,6 +654,7 @@ jobs: test-api-livechat: name: 🔨 Test API Livechat (CE) needs: [checks, build-gh-docker-publish, release-versions] + if: ${{ github.event_name != 'workflow_dispatch' || inputs.test-scope != 'fips' }} uses: ./.github/workflows/ci-test-e2e.yml with: @@ -660,6 +672,7 @@ jobs: test-ui: name: 🔨 Test UI (CE) needs: [checks, build-gh-docker-publish, release-versions] + if: ${{ github.event_name != 'workflow_dispatch' || inputs.test-scope != 'fips' }} uses: ./.github/workflows/ci-test-e2e.yml with: @@ -686,6 +699,7 @@ jobs: test-api-ee: name: 🔨 Test API (EE) needs: [checks, build-gh-docker-publish, release-versions] + if: ${{ github.event_name != 'workflow_dispatch' || inputs.test-scope != 'fips' }} uses: ./.github/workflows/ci-test-e2e.yml with: @@ -707,6 +721,7 @@ jobs: test-api-livechat-ee: name: 🔨 Test API Livechat (EE) needs: [checks, build-gh-docker-publish, release-versions] + if: ${{ github.event_name != 'workflow_dispatch' || inputs.test-scope != 'fips' }} uses: ./.github/workflows/ci-test-e2e.yml with: @@ -749,6 +764,7 @@ jobs: test-ui-ee: name: 🔨 Test UI (EE) needs: [checks, build-gh-docker-publish, release-versions] + if: ${{ github.event_name != 'workflow_dispatch' || inputs.test-scope != 'fips' }} uses: ./.github/workflows/ci-test-e2e.yml with: @@ -842,6 +858,7 @@ jobs: test-federation-matrix: name: 🔨 Test Federation Matrix needs: [checks, build-gh-docker-publish, packages-build, release-versions] + if: ${{ github.event_name != 'workflow_dispatch' || inputs.test-scope != 'fips' }} runs-on: ubuntu-24.04 steps: @@ -938,6 +955,7 @@ jobs: name: 📊 Report Coverage runs-on: ubuntu-24.04 needs: [release-versions, test-api-ee, test-api-livechat-ee, test-ui-ee] + if: ${{ github.event_name != 'workflow_dispatch' || inputs.test-scope != 'fips' }} steps: - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 @@ -1055,7 +1073,7 @@ jobs: deploy: name: 🚀 Publish build assets runs-on: ubuntu-24.04-arm - if: github.event_name == 'release' || github.ref == 'refs/heads/develop' + if: github.event_name != 'workflow_dispatch' && (github.event_name == 'release' || github.ref == 'refs/heads/develop') needs: [build-gh-docker-publish, release-versions] steps: From f1884956a9aa37e76b51cab13b9fef14e1ea0dda Mon Sep 17 00:00:00 2001 From: Matheus Cardoso Date: Thu, 5 Mar 2026 11:18:35 -0300 Subject: [PATCH 05/70] fix: allow sha-1 for ws handshake --- ee/apps/Dockerfile | 7 ++++++- ee/apps/ddp-streamer/openssl-ddp-streamer-fips.cnf | 14 ++++++++++++++ ee/apps/ddp-streamer/src/fips.ts | 10 ++++++++-- 3 files changed, 28 insertions(+), 3 deletions(-) create mode 100644 ee/apps/ddp-streamer/openssl-ddp-streamer-fips.cnf diff --git a/ee/apps/Dockerfile b/ee/apps/Dockerfile index 9d0d6c756be01..6eb50dcbdef22 100644 --- a/ee/apps/Dockerfile +++ b/ee/apps/Dockerfile @@ -81,8 +81,13 @@ FROM rocketchatfips140/dhi-node:22-alpine3.23 AS release-fips ARG SERVICE ENV NODE_ENV=production \ PORT=3000 + +# Keep provider behavior explicit for auditing: enable FIPS provider while allowing +# fallback to default provider for legacy algorithms required by dependencies. +COPY ./ee/apps/ddp-streamer/openssl-ddp-streamer-fips.cnf /etc/ssl/openssl-ddp-streamer-fips.cnf + COPY --chown=node:node --from=builder /app /app WORKDIR /app/ee/apps/${SERVICE} USER node EXPOSE 3000 9458 -CMD ["node", "--require", "./src/fips.js", "src/service.js"] +CMD ["node", "--openssl-config=/etc/ssl/openssl-ddp-streamer-fips.cnf", "--openssl-shared-config", "--require", "./src/fips.js", "src/service.js"] diff --git a/ee/apps/ddp-streamer/openssl-ddp-streamer-fips.cnf b/ee/apps/ddp-streamer/openssl-ddp-streamer-fips.cnf new file mode 100644 index 0000000000000..f0d6c4f706647 --- /dev/null +++ b/ee/apps/ddp-streamer/openssl-ddp-streamer-fips.cnf @@ -0,0 +1,14 @@ +openssl_conf = openssl_init + +[openssl_init] +providers = provider_sect + +[provider_sect] +fips = fips_sect +default = default_sect + +[fips_sect] +activate = 1 + +[default_sect] +activate = 1 diff --git a/ee/apps/ddp-streamer/src/fips.ts b/ee/apps/ddp-streamer/src/fips.ts index db58817c919c3..8a31de51660f6 100644 --- a/ee/apps/ddp-streamer/src/fips.ts +++ b/ee/apps/ddp-streamer/src/fips.ts @@ -1,7 +1,13 @@ import crypto from 'crypto'; -crypto.setFips(true); +const OPENSSL_CONFIG_PATH = '/etc/ssl/openssl-ddp-streamer-fips.cnf'; +const hasOpenSSLConfigFlag = process.execArgv.some((arg) => arg.startsWith('--openssl-config=')); +const hasOpenSSLSharedConfigFlag = process.execArgv.includes('--openssl-shared-config'); console.log('========================================='); -console.log('FIPS COMPLIANCE CHECK: YES'); +console.log(`Node FIPS Mode Flag: ${crypto.getFips() === 1 ? 'ENABLED' : 'DISABLED'}`); +console.log(`OpenSSL Config Path: ${OPENSSL_CONFIG_PATH}`); +console.log(`OpenSSL Config Flag Present: ${hasOpenSSLConfigFlag ? 'YES' : 'NO'}`); +console.log(`OpenSSL Shared Config Flag Present: ${hasOpenSSLSharedConfigFlag ? 'YES' : 'NO'}`); +console.log('OpenSSL provider policy expected: fips + default fallback.'); console.log('========================================='); From 09e51bf4547af95d81f3b81bb2227d754eea30d1 Mon Sep 17 00:00:00 2001 From: Matheus Cardoso Date: Thu, 5 Mar 2026 11:57:43 -0300 Subject: [PATCH 06/70] Revert "ci: allow selecting test lanes to run" This reverts commit f2f6b9d75eb59ab677eb8c786e4b8e03bdcc19fa. --- .github/workflows/ci.yml | 22 ++-------------------- 1 file changed, 2 insertions(+), 20 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 71fa12f253347..0a00ee0674e79 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,16 +1,6 @@ name: CI on: - workflow_dispatch: - inputs: - test-scope: - description: Select test lanes to run - required: false - default: all - type: choice - options: - - all - - fips release: types: [published] pull_request: @@ -550,7 +540,7 @@ jobs: name: 📦 Track Image Sizes needs: [build-gh-docker-publish, release-versions] runs-on: ubuntu-24.04-arm - if: github.event_name != 'workflow_dispatch' && (github.event_name == 'pull_request' || github.ref == 'refs/heads/develop') + if: github.event_name == 'pull_request' || github.ref == 'refs/heads/develop' permissions: pull-requests: write contents: write @@ -636,7 +626,6 @@ jobs: test-api: name: 🔨 Test API (CE) needs: [checks, build-gh-docker-publish, release-versions] - if: ${{ github.event_name != 'workflow_dispatch' || inputs.test-scope != 'fips' }} uses: ./.github/workflows/ci-test-e2e.yml with: @@ -654,7 +643,6 @@ jobs: test-api-livechat: name: 🔨 Test API Livechat (CE) needs: [checks, build-gh-docker-publish, release-versions] - if: ${{ github.event_name != 'workflow_dispatch' || inputs.test-scope != 'fips' }} uses: ./.github/workflows/ci-test-e2e.yml with: @@ -672,7 +660,6 @@ jobs: test-ui: name: 🔨 Test UI (CE) needs: [checks, build-gh-docker-publish, release-versions] - if: ${{ github.event_name != 'workflow_dispatch' || inputs.test-scope != 'fips' }} uses: ./.github/workflows/ci-test-e2e.yml with: @@ -699,7 +686,6 @@ jobs: test-api-ee: name: 🔨 Test API (EE) needs: [checks, build-gh-docker-publish, release-versions] - if: ${{ github.event_name != 'workflow_dispatch' || inputs.test-scope != 'fips' }} uses: ./.github/workflows/ci-test-e2e.yml with: @@ -721,7 +707,6 @@ jobs: test-api-livechat-ee: name: 🔨 Test API Livechat (EE) needs: [checks, build-gh-docker-publish, release-versions] - if: ${{ github.event_name != 'workflow_dispatch' || inputs.test-scope != 'fips' }} uses: ./.github/workflows/ci-test-e2e.yml with: @@ -764,7 +749,6 @@ jobs: test-ui-ee: name: 🔨 Test UI (EE) needs: [checks, build-gh-docker-publish, release-versions] - if: ${{ github.event_name != 'workflow_dispatch' || inputs.test-scope != 'fips' }} uses: ./.github/workflows/ci-test-e2e.yml with: @@ -858,7 +842,6 @@ jobs: test-federation-matrix: name: 🔨 Test Federation Matrix needs: [checks, build-gh-docker-publish, packages-build, release-versions] - if: ${{ github.event_name != 'workflow_dispatch' || inputs.test-scope != 'fips' }} runs-on: ubuntu-24.04 steps: @@ -955,7 +938,6 @@ jobs: name: 📊 Report Coverage runs-on: ubuntu-24.04 needs: [release-versions, test-api-ee, test-api-livechat-ee, test-ui-ee] - if: ${{ github.event_name != 'workflow_dispatch' || inputs.test-scope != 'fips' }} steps: - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 @@ -1073,7 +1055,7 @@ jobs: deploy: name: 🚀 Publish build assets runs-on: ubuntu-24.04-arm - if: github.event_name != 'workflow_dispatch' && (github.event_name == 'release' || github.ref == 'refs/heads/develop') + if: github.event_name == 'release' || github.ref == 'refs/heads/develop' needs: [build-gh-docker-publish, release-versions] steps: From 743e5d87afc802705a40debced067d83f3cb7f20 Mon Sep 17 00:00:00 2001 From: Matheus Cardoso Date: Thu, 5 Mar 2026 12:55:49 -0300 Subject: [PATCH 07/70] chore: streamline fips env vars --- .github/workflows/ci-test-e2e.yml | 2 +- .github/workflows/ci.yml | 8 ++++---- docker-compose-ci.yml | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci-test-e2e.yml b/.github/workflows/ci-test-e2e.yml index 1d5be8f179bd2..9ffcacdda59ec 100644 --- a/.github/workflows/ci-test-e2e.yml +++ b/.github/workflows/ci-test-e2e.yml @@ -80,7 +80,7 @@ jobs: env: # if building for production on develop branch or release, add suffix for coverage images DOCKER_TAG_SUFFIX_ROCKETCHAT: ${{ inputs.coverage == matrix.mongodb-version && (github.event_name == 'release' || github.ref == 'refs/heads/develop') && '-cov' || '' }} - DOCKER_TAG_SUFFIX_DDP_STREAMER: ${{ inputs.release == 'fips' && '-fips' || '' }} + DOCKER_TAG_SUFFIX_FIPS: ${{ inputs.release == 'fips' && '-fips' || '' }} MONGODB_VERSION: ${{ matrix.mongodb-version }} COVERAGE_DIR: '/tmp/coverage/${{ startsWith(inputs.type, ''api'') && ''api'' || inputs.type }}' COVERAGE_FILE_NAME: '${{ inputs.type }}-${{ matrix.shard }}.json' diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0a00ee0674e79..563a4c19cfc8d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -393,7 +393,7 @@ jobs: env: # add suffix for the extra images with coverage if building for production DOCKER_TAG_SUFFIX_ROCKETCHAT: ${{ matrix.type == 'coverage' && (github.event_name == 'release' || github.ref == 'refs/heads/develop') && '-cov' || '' }} - DOCKER_TAG_SUFFIX_DDP_STREAMER: ${{ matrix.service[0] == 'ddp-streamer-service' && matrix.type == 'fips' && '-fips' || '' }} + DOCKER_TAG_SUFFIX_FIPS: ${{ matrix.service[0] == 'ddp-streamer-service' && matrix.type == 'fips' && '-fips' || '' }} BUILD_TARGET: ${{ matrix.service[0] == 'ddp-streamer-service' && matrix.type == 'fips' && 'release-fips' || '' }} with: CR_USER: ${{ secrets.CR_USER }} @@ -411,7 +411,7 @@ jobs: if: matrix.service[1] && github.actor != 'dependabot[bot]' env: DOCKER_TAG_SUFFIX_ROCKETCHAT: ${{ matrix.type == 'coverage' && '-cov' || '' }} - DOCKER_TAG_SUFFIX_DDP_STREAMER: ${{ matrix.service[1] == 'ddp-streamer-service' && matrix.type == 'fips' && '-fips' || '' }} + DOCKER_TAG_SUFFIX_FIPS: ${{ matrix.service[1] == 'ddp-streamer-service' && matrix.type == 'fips' && '-fips' || '' }} BUILD_TARGET: ${{ matrix.service[1] == 'ddp-streamer-service' && matrix.type == 'fips' && 'release-fips' || '' }} with: CR_USER: ${{ secrets.CR_USER }} @@ -430,7 +430,7 @@ jobs: if: matrix.service[2] && github.actor != 'dependabot[bot]' env: DOCKER_TAG_SUFFIX_ROCKETCHAT: ${{ matrix.type == 'coverage' && '-cov' || '' }} - DOCKER_TAG_SUFFIX_DDP_STREAMER: ${{ matrix.service[2] == 'ddp-streamer-service' && matrix.type == 'fips' && '-fips' || '' }} + DOCKER_TAG_SUFFIX_FIPS: ${{ matrix.service[2] == 'ddp-streamer-service' && matrix.type == 'fips' && '-fips' || '' }} BUILD_TARGET: ${{ matrix.service[2] == 'ddp-streamer-service' && matrix.type == 'fips' && 'release-fips' || '' }} with: CR_USER: ${{ secrets.CR_USER }} @@ -449,7 +449,7 @@ jobs: if: matrix.service[3] && github.actor != 'dependabot[bot]' env: DOCKER_TAG_SUFFIX_ROCKETCHAT: ${{ matrix.type == 'coverage' && '-cov' || '' }} - DOCKER_TAG_SUFFIX_DDP_STREAMER: ${{ matrix.service[3] == 'ddp-streamer-service' && matrix.type == 'fips' && '-fips' || '' }} + DOCKER_TAG_SUFFIX_FIPS: ${{ matrix.service[3] == 'ddp-streamer-service' && matrix.type == 'fips' && '-fips' || '' }} BUILD_TARGET: ${{ matrix.service[3] == 'ddp-streamer-service' && matrix.type == 'fips' && 'release-fips' || '' }} with: CR_USER: ${{ secrets.CR_USER }} diff --git a/docker-compose-ci.yml b/docker-compose-ci.yml index c7dcfffd19a45..c0fab0208011e 100644 --- a/docker-compose-ci.yml +++ b/docker-compose-ci.yml @@ -128,7 +128,7 @@ services: - linux/arm64 args: SERVICE: ddp-streamer - image: ghcr.io/${LOWERCASE_REPOSITORY}/ddp-streamer-service:${DOCKER_TAG}${DOCKER_TAG_SUFFIX_DDP_STREAMER:-} + image: ghcr.io/${LOWERCASE_REPOSITORY}/ddp-streamer-service:${DOCKER_TAG}${DOCKER_TAG_SUFFIX_FIPS:-} environment: - MONGO_URL=mongodb://mongo:27017/rocketchat?replicaSet=rs0 - 'TRANSPORTER=${TRANSPORTER:-}' From c7240fc13c92bf46203144e4fc2bd637901c8253 Mon Sep 17 00:00:00 2001 From: Matheus Cardoso Date: Fri, 6 Mar 2026 15:51:47 -0300 Subject: [PATCH 08/70] Revert "fix: allow sha-1 for ws handshake" This reverts commit 77fe51c04e63f4bb370c6e6e5859e7c035c7fb7b. --- ee/apps/Dockerfile | 7 +------ ee/apps/ddp-streamer/openssl-ddp-streamer-fips.cnf | 14 -------------- ee/apps/ddp-streamer/src/fips.ts | 10 ++-------- 3 files changed, 3 insertions(+), 28 deletions(-) delete mode 100644 ee/apps/ddp-streamer/openssl-ddp-streamer-fips.cnf diff --git a/ee/apps/Dockerfile b/ee/apps/Dockerfile index 6eb50dcbdef22..9d0d6c756be01 100644 --- a/ee/apps/Dockerfile +++ b/ee/apps/Dockerfile @@ -81,13 +81,8 @@ FROM rocketchatfips140/dhi-node:22-alpine3.23 AS release-fips ARG SERVICE ENV NODE_ENV=production \ PORT=3000 - -# Keep provider behavior explicit for auditing: enable FIPS provider while allowing -# fallback to default provider for legacy algorithms required by dependencies. -COPY ./ee/apps/ddp-streamer/openssl-ddp-streamer-fips.cnf /etc/ssl/openssl-ddp-streamer-fips.cnf - COPY --chown=node:node --from=builder /app /app WORKDIR /app/ee/apps/${SERVICE} USER node EXPOSE 3000 9458 -CMD ["node", "--openssl-config=/etc/ssl/openssl-ddp-streamer-fips.cnf", "--openssl-shared-config", "--require", "./src/fips.js", "src/service.js"] +CMD ["node", "--require", "./src/fips.js", "src/service.js"] diff --git a/ee/apps/ddp-streamer/openssl-ddp-streamer-fips.cnf b/ee/apps/ddp-streamer/openssl-ddp-streamer-fips.cnf deleted file mode 100644 index f0d6c4f706647..0000000000000 --- a/ee/apps/ddp-streamer/openssl-ddp-streamer-fips.cnf +++ /dev/null @@ -1,14 +0,0 @@ -openssl_conf = openssl_init - -[openssl_init] -providers = provider_sect - -[provider_sect] -fips = fips_sect -default = default_sect - -[fips_sect] -activate = 1 - -[default_sect] -activate = 1 diff --git a/ee/apps/ddp-streamer/src/fips.ts b/ee/apps/ddp-streamer/src/fips.ts index 8a31de51660f6..db58817c919c3 100644 --- a/ee/apps/ddp-streamer/src/fips.ts +++ b/ee/apps/ddp-streamer/src/fips.ts @@ -1,13 +1,7 @@ import crypto from 'crypto'; -const OPENSSL_CONFIG_PATH = '/etc/ssl/openssl-ddp-streamer-fips.cnf'; -const hasOpenSSLConfigFlag = process.execArgv.some((arg) => arg.startsWith('--openssl-config=')); -const hasOpenSSLSharedConfigFlag = process.execArgv.includes('--openssl-shared-config'); +crypto.setFips(true); console.log('========================================='); -console.log(`Node FIPS Mode Flag: ${crypto.getFips() === 1 ? 'ENABLED' : 'DISABLED'}`); -console.log(`OpenSSL Config Path: ${OPENSSL_CONFIG_PATH}`); -console.log(`OpenSSL Config Flag Present: ${hasOpenSSLConfigFlag ? 'YES' : 'NO'}`); -console.log(`OpenSSL Shared Config Flag Present: ${hasOpenSSLSharedConfigFlag ? 'YES' : 'NO'}`); -console.log('OpenSSL provider policy expected: fips + default fallback.'); +console.log('FIPS COMPLIANCE CHECK: YES'); console.log('========================================='); From 1fbd716be9b6e4fdc469d1b8139eb4687f6418e3 Mon Sep 17 00:00:00 2001 From: Matheus Cardoso Date: Fri, 6 Mar 2026 15:56:11 -0300 Subject: [PATCH 09/70] fix(fips): use js implementation for ws handshake --- ee/apps/ddp-streamer/src/fips.ts | 98 ++++++++++++++++++++++++++++++++ 1 file changed, 98 insertions(+) diff --git a/ee/apps/ddp-streamer/src/fips.ts b/ee/apps/ddp-streamer/src/fips.ts index db58817c919c3..b0e2a565607d0 100644 --- a/ee/apps/ddp-streamer/src/fips.ts +++ b/ee/apps/ddp-streamer/src/fips.ts @@ -5,3 +5,101 @@ crypto.setFips(true); console.log('========================================='); console.log('FIPS COMPLIANCE CHECK: YES'); console.log('========================================='); + +// ========================================================================= +// FIPS 140-3 SHA-1 WORKAROUND +// Bypasses OpenSSL FIPS restrictions for WebSocket handshakes. +// ========================================================================= + +const generateWebSocketAccept = (message: string): string => { + let h0 = 0x67452301; + let h1 = 0xefcdab89; + let h2 = 0x98badcfe; + let h3 = 0x10325476; + let h4 = 0xc3d2e1f0; + const blocks = new Uint32Array(32); + for (let i = 0; i < 60; i++) blocks[i >> 2] |= message.charCodeAt(i) << (24 - (i % 4) * 8); + blocks[15] = 0x80000000; + blocks[31] = 480; + + const rotl = (n: number, b: number) => (n << b) | (n >>> (32 - b)); + + for (let chunk = 0; chunk < 2; chunk++) { + const w = new Uint32Array(80); + const offset = chunk * 16; + for (let i = 0; i < 16; i++) w[i] = blocks[offset + i]; + for (let i = 16; i < 80; i++) w[i] = rotl(w[i - 3] ^ w[i - 8] ^ w[i - 14] ^ w[i - 16], 1); + + let a = h0; + let b = h1; + let c = h2; + let d = h3; + let e = h4; + + for (let i = 0; i < 80; i++) { + let f; + let k; + if (i < 20) { + f = (b & c) | (~b & d); + k = 0x5a827999; + } else if (i < 40) { + f = b ^ c ^ d; + k = 0x6ed9eba1; + } else if (i < 60) { + f = (b & c) | (b & d) | (c & d); + k = 0x8f1bbcdc; + } else { + f = b ^ c ^ d; + k = 0xca62c1d6; + } + + const temp = (rotl(a, 5) + f + e + k + w[i]) >>> 0; + e = d; + d = c; + c = rotl(b, 30); + b = a; + a = temp; + } + h0 = (h0 + a) >>> 0; + h1 = (h1 + b) >>> 0; + h2 = (h2 + c) >>> 0; + h3 = (h3 + d) >>> 0; + h4 = (h4 + e) >>> 0; + } + + const hashBuffer = Buffer.allocUnsafe(20); + hashBuffer.writeUInt32BE(h0, 0); + hashBuffer.writeUInt32BE(h1, 4); + hashBuffer.writeUInt32BE(h2, 8); + hashBuffer.writeUInt32BE(h3, 12); + hashBuffer.writeUInt32BE(h4, 16); + return hashBuffer.toString('base64'); +}; + +const originalCreateHash = crypto.createHash; + +crypto.createHash = function (algorithm: string, options?: crypto.HashOptions) { + if (algorithm.toLowerCase() === 'sha1') { + let payload = ''; + + return { + update(data: string | Buffer) { + payload += data.toString(); + return this; + }, + digest(encoding: crypto.BinaryToTextEncoding) { + // Route the exact 60-byte WebSocket handshake to our bypass + if (encoding === 'base64' && payload.length === 60) { + return generateWebSocketAccept(payload); + } + // Otherwise, fall back to native crypto + return originalCreateHash('sha1', options).update(payload).digest(encoding); + }, + } as crypto.Hash; + } + + return originalCreateHash(algorithm, options); +}; + +console.log('FIPS Workaround: WebSocket SHA-1 Monkey Patch applied successfully.'); +console.log('========================================='); From 8cb5bf3e5c3d299debe30dcbe57669b8671aa985 Mon Sep 17 00:00:00 2001 From: Matheus Cardoso Date: Mon, 9 Mar 2026 12:51:42 -0300 Subject: [PATCH 10/70] refactor: improve fips init script --- ee/apps/ddp-streamer/src/fips.ts | 226 ++++++++++++++++++------------- 1 file changed, 129 insertions(+), 97 deletions(-) diff --git a/ee/apps/ddp-streamer/src/fips.ts b/ee/apps/ddp-streamer/src/fips.ts index b0e2a565607d0..dbe93b5f6d9a5 100644 --- a/ee/apps/ddp-streamer/src/fips.ts +++ b/ee/apps/ddp-streamer/src/fips.ts @@ -1,105 +1,137 @@ +/** + * ============================================================================== + * SECURITY AUDIT EXEMPTION / FIPS 140-3 WORKAROUND + * ============================================================================== + * Context: + * Node.js running in FIPS 140-3 mode strictly disables native SHA-1 execution. + * However, RFC 6455 (WebSockets) strictly requires SHA-1 to generate the + * Sec-WebSocket-Accept handshake header. + * * Justification: + * The WebSocket protocol uses SHA-1 purely for framing/handshake validation, + * NOT for cryptographic security. To allow the 'ws' library to function without + * crashing the Node process, we intercept SHA-1 calls specifically for the + * 60-byte WebSocket handshake and process them using a highly-optimized, + * zero-allocation, pure-JS implementation. + * ============================================================================== + */ import crypto from 'crypto'; -crypto.setFips(true); +try { + crypto.createHash('sha1').update('test').digest('hex'); + console.log('🔓 Native SHA-1 allowed. Skipping FIPS WebSocket patch.'); +} catch (err) { + console.log('🔒 FIPS 140-3 mode detected. Applying WebSocket Handshake Patch...'); -console.log('========================================='); -console.log('FIPS COMPLIANCE CHECK: YES'); -console.log('========================================='); + const blocks = new Uint32Array(32); + const w = new Uint32Array(80); + const hashBuffer = Buffer.alloc(20); -// ========================================================================= -// FIPS 140-3 SHA-1 WORKAROUND -// Bypasses OpenSSL FIPS restrictions for WebSocket handshakes. -// ========================================================================= + const generateWebSocketAccept = (message: string): string => { + if (message.length !== 60) { + throw new Error(`Expected 60-byte input for WS Accept, got ${message.length}`); + } -const generateWebSocketAccept = (message: string): string => { - let h0 = 0x67452301; - let h1 = 0xefcdab89; - let h2 = 0x98badcfe; - let h3 = 0x10325476; - let h4 = 0xc3d2e1f0; - const blocks = new Uint32Array(32); - for (let i = 0; i < 60; i++) blocks[i >> 2] |= message.charCodeAt(i) << (24 - (i % 4) * 8); - blocks[15] = 0x80000000; - blocks[31] = 480; - - const rotl = (n: number, b: number) => (n << b) | (n >>> (32 - b)); - - for (let chunk = 0; chunk < 2; chunk++) { - const w = new Uint32Array(80); - const offset = chunk * 16; - for (let i = 0; i < 16; i++) w[i] = blocks[offset + i]; - for (let i = 16; i < 80; i++) w[i] = rotl(w[i - 3] ^ w[i - 8] ^ w[i - 14] ^ w[i - 16], 1); - - let a = h0; - let b = h1; - let c = h2; - let d = h3; - let e = h4; - - for (let i = 0; i < 80; i++) { - let f; - let k; - if (i < 20) { - f = (b & c) | (~b & d); - k = 0x5a827999; - } else if (i < 40) { - f = b ^ c ^ d; - k = 0x6ed9eba1; - } else if (i < 60) { - f = (b & c) | (b & d) | (c & d); - k = 0x8f1bbcdc; - } else { - f = b ^ c ^ d; - k = 0xca62c1d6; + blocks.fill(0); + + let h0 = 0x67452301; + let h1 = 0xefcdab89; + let h2 = 0x98badcfe; + let h3 = 0x10325476; + let h4 = 0xc3d2e1f0; + + for (let i = 0; i < 60; i++) blocks[i >> 2] |= message.charCodeAt(i) << (24 - (i % 4) * 8); + blocks[15] = 0x80000000; + blocks[31] = 480; + + const rotl = (n: number, b: number) => (n << b) | (n >>> (32 - b)); + + for (let chunk = 0; chunk < 2; chunk++) { + const offset = chunk * 16; + for (let i = 0; i < 16; i++) w[i] = blocks[offset + i]; + for (let i = 16; i < 80; i++) w[i] = rotl(w[i - 3] ^ w[i - 8] ^ w[i - 14] ^ w[i - 16], 1); + + let a = h0; + let b = h1; + let c = h2; + let d = h3; + let e = h4; + let temp; + + for (let i = 0; i < 20; i++) { + temp = (rotl(a, 5) + (d ^ (b & (c ^ d))) + e + 0x5a827999 + w[i]) >>> 0; + e = d; + d = c; + c = rotl(b, 30); + b = a; + a = temp; } + for (let i = 20; i < 40; i++) { + temp = (rotl(a, 5) + (b ^ c ^ d) + e + 0x6ed9eba1 + w[i]) >>> 0; + e = d; + d = c; + c = rotl(b, 30); + b = a; + a = temp; + } + for (let i = 40; i < 60; i++) { + temp = (rotl(a, 5) + ((b & c) | (b & d) | (c & d)) + e + 0x8f1bbcdc + w[i]) >>> 0; + e = d; + d = c; + c = rotl(b, 30); + b = a; + a = temp; + } + for (let i = 60; i < 80; i++) { + temp = (rotl(a, 5) + (b ^ c ^ d) + e + 0xca62c1d6 + w[i]) >>> 0; + e = d; + d = c; + c = rotl(b, 30); + b = a; + a = temp; + } + + h0 = (h0 + a) >>> 0; + h1 = (h1 + b) >>> 0; + h2 = (h2 + c) >>> 0; + h3 = (h3 + d) >>> 0; + h4 = (h4 + e) >>> 0; + } + + hashBuffer.writeUInt32BE(h0, 0); + hashBuffer.writeUInt32BE(h1, 4); + hashBuffer.writeUInt32BE(h2, 8); + hashBuffer.writeUInt32BE(h3, 12); + hashBuffer.writeUInt32BE(h4, 16); + + return hashBuffer.toString('base64'); + }; + + const originalCreateHash = crypto.createHash; + + crypto.createHash = function (algorithm: string, options?: crypto.HashOptions) { + if (algorithm.toLowerCase() === 'sha1') { + let inputData = ''; - const temp = (rotl(a, 5) + f + e + k + w[i]) >>> 0; - e = d; - d = c; - c = rotl(b, 30); - b = a; - a = temp; + return { + update(data) { + if (typeof data === 'string') { + inputData += data; + } else if (Buffer.isBuffer(data)) { + inputData += data.toString('utf8'); + } else { + inputData += Buffer.from(data.buffer, data.byteOffset, data.byteLength).toString('utf8'); + } + return this; + }, + digest(encoding) { + if (encoding === 'base64' && inputData.length === 60) { + return generateWebSocketAccept(inputData); + } + // If it's not the exact WS handshake, pass it back to native (which will throw FIPS error) + return originalCreateHash(algorithm, options).update(inputData).digest(encoding); + }, + } as crypto.Hash; } - h0 = (h0 + a) >>> 0; - h1 = (h1 + b) >>> 0; - h2 = (h2 + c) >>> 0; - h3 = (h3 + d) >>> 0; - h4 = (h4 + e) >>> 0; - } - - const hashBuffer = Buffer.allocUnsafe(20); - hashBuffer.writeUInt32BE(h0, 0); - hashBuffer.writeUInt32BE(h1, 4); - hashBuffer.writeUInt32BE(h2, 8); - hashBuffer.writeUInt32BE(h3, 12); - hashBuffer.writeUInt32BE(h4, 16); - return hashBuffer.toString('base64'); -}; - -const originalCreateHash = crypto.createHash; - -crypto.createHash = function (algorithm: string, options?: crypto.HashOptions) { - if (algorithm.toLowerCase() === 'sha1') { - let payload = ''; - - return { - update(data: string | Buffer) { - payload += data.toString(); - return this; - }, - digest(encoding: crypto.BinaryToTextEncoding) { - // Route the exact 60-byte WebSocket handshake to our bypass - if (encoding === 'base64' && payload.length === 60) { - return generateWebSocketAccept(payload); - } - // Otherwise, fall back to native crypto - return originalCreateHash('sha1', options).update(payload).digest(encoding); - }, - } as crypto.Hash; - } - - return originalCreateHash(algorithm, options); -}; - -console.log('FIPS Workaround: WebSocket SHA-1 Monkey Patch applied successfully.'); -console.log('========================================='); + return originalCreateHash(algorithm, options); + }; +} From 1d25e6010adb93d00581072c1310b6d5fb16366f Mon Sep 17 00:00:00 2001 From: Matheus Cardoso Date: Mon, 9 Mar 2026 13:03:22 -0300 Subject: [PATCH 11/70] ci: guard against missing creds --- .github/actions/build-docker/action.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/actions/build-docker/action.yml b/.github/actions/build-docker/action.yml index 7cd637aad0784..a658af4b06d4e 100644 --- a/.github/actions/build-docker/action.yml +++ b/.github/actions/build-docker/action.yml @@ -50,12 +50,11 @@ runs: password: ${{ inputs.CR_PAT }} - name: Login to DockerHub for FIPS base images - if: inputs.type == 'fips' + if: inputs.type == 'fips' && inputs.TEMP_DOCKERHUB_FIPS_USER != '' && inputs.TEMP_DOCKERHUB_FIPS_PASS != '' uses: docker/login-action@v3 with: username: ${{ inputs.TEMP_DOCKERHUB_FIPS_USER }} password: ${{ inputs.TEMP_DOCKERHUB_FIPS_PASS }} - - name: Restore meteor build if: inputs.service == 'rocketchat' uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0 From 303d1991a0f8c016a939f9e84ff195060bfc20fe Mon Sep 17 00:00:00 2001 From: Matheus Cardoso Date: Mon, 9 Mar 2026 13:13:36 -0300 Subject: [PATCH 12/70] feat(presence-service): fips mode --- .github/actions/build-docker/action.yml | 2 ++ .github/workflows/ci.yml | 22 ++++++++++++---------- docker-compose-ci.yml | 3 ++- ee/apps/presence-service/src/fips.ts | 11 +++++++++++ ee/apps/presence-service/tsconfig.json | 2 +- 5 files changed, 28 insertions(+), 12 deletions(-) create mode 100644 ee/apps/presence-service/src/fips.ts diff --git a/.github/actions/build-docker/action.yml b/.github/actions/build-docker/action.yml index a658af4b06d4e..ac399a84ae8bb 100644 --- a/.github/actions/build-docker/action.yml +++ b/.github/actions/build-docker/action.yml @@ -174,6 +174,8 @@ runs: SERVICE_SUFFIX='-cov' elif [[ "$INPUT_SERVICE" == 'ddp-streamer-service' && "$INPUT_TYPE" == 'fips' ]]; then SERVICE_SUFFIX='-fips' + elif [[ "$INPUT_SERVICE" == 'presence-service' && "$INPUT_TYPE" == 'fips' ]]; then + SERVICE_SUFFIX='-fips' fi mkdir -p "/tmp/manifests/${INPUT_SERVICE}${SERVICE_SUFFIX}/${INPUT_ARCH}" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 563a4c19cfc8d..e4538b38131c5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -376,9 +376,9 @@ jobs: - arch: ${{ (github.event.pull_request.head.repo.full_name == github.repository || github.event_name == 'release' || github.ref == 'refs/heads/develop') && 'arm64' || 'amd64' }} service: [rocketchat] type: coverage - # build a dedicated FIPS ddp-streamer image for FIPS test lanes + # build dedicated FIPS images for FIPS test lanes - arch: amd64 - service: [ddp-streamer-service] + service: [ddp-streamer-service, presence-service] type: fips steps: @@ -393,8 +393,8 @@ jobs: env: # add suffix for the extra images with coverage if building for production DOCKER_TAG_SUFFIX_ROCKETCHAT: ${{ matrix.type == 'coverage' && (github.event_name == 'release' || github.ref == 'refs/heads/develop') && '-cov' || '' }} - DOCKER_TAG_SUFFIX_FIPS: ${{ matrix.service[0] == 'ddp-streamer-service' && matrix.type == 'fips' && '-fips' || '' }} - BUILD_TARGET: ${{ matrix.service[0] == 'ddp-streamer-service' && matrix.type == 'fips' && 'release-fips' || '' }} + DOCKER_TAG_SUFFIX_FIPS: ${{ (matrix.service[0] == 'ddp-streamer-service' || matrix.service[0] == 'presence-service') && matrix.type == 'fips' && '-fips' || '' }} + BUILD_TARGET: ${{ (matrix.service[0] == 'ddp-streamer-service' || matrix.service[0] == 'presence-service') && matrix.type == 'fips' && 'release-fips' || '' }} with: CR_USER: ${{ secrets.CR_USER }} CR_PAT: ${{ secrets.CR_PAT }} @@ -411,8 +411,8 @@ jobs: if: matrix.service[1] && github.actor != 'dependabot[bot]' env: DOCKER_TAG_SUFFIX_ROCKETCHAT: ${{ matrix.type == 'coverage' && '-cov' || '' }} - DOCKER_TAG_SUFFIX_FIPS: ${{ matrix.service[1] == 'ddp-streamer-service' && matrix.type == 'fips' && '-fips' || '' }} - BUILD_TARGET: ${{ matrix.service[1] == 'ddp-streamer-service' && matrix.type == 'fips' && 'release-fips' || '' }} + DOCKER_TAG_SUFFIX_FIPS: ${{ (matrix.service[1] == 'ddp-streamer-service' || matrix.service[1] == 'presence-service') && matrix.type == 'fips' && '-fips' || '' }} + BUILD_TARGET: ${{ (matrix.service[1] == 'ddp-streamer-service' || matrix.service[1] == 'presence-service') && matrix.type == 'fips' && 'release-fips' || '' }} with: CR_USER: ${{ secrets.CR_USER }} CR_PAT: ${{ secrets.CR_PAT }} @@ -430,8 +430,8 @@ jobs: if: matrix.service[2] && github.actor != 'dependabot[bot]' env: DOCKER_TAG_SUFFIX_ROCKETCHAT: ${{ matrix.type == 'coverage' && '-cov' || '' }} - DOCKER_TAG_SUFFIX_FIPS: ${{ matrix.service[2] == 'ddp-streamer-service' && matrix.type == 'fips' && '-fips' || '' }} - BUILD_TARGET: ${{ matrix.service[2] == 'ddp-streamer-service' && matrix.type == 'fips' && 'release-fips' || '' }} + DOCKER_TAG_SUFFIX_FIPS: ${{ (matrix.service[2] == 'ddp-streamer-service' || matrix.service[2] == 'presence-service') && matrix.type == 'fips' && '-fips' || '' }} + BUILD_TARGET: ${{ (matrix.service[2] == 'ddp-streamer-service' || matrix.service[2] == 'presence-service') && matrix.type == 'fips' && 'release-fips' || '' }} with: CR_USER: ${{ secrets.CR_USER }} CR_PAT: ${{ secrets.CR_PAT }} @@ -449,8 +449,8 @@ jobs: if: matrix.service[3] && github.actor != 'dependabot[bot]' env: DOCKER_TAG_SUFFIX_ROCKETCHAT: ${{ matrix.type == 'coverage' && '-cov' || '' }} - DOCKER_TAG_SUFFIX_FIPS: ${{ matrix.service[3] == 'ddp-streamer-service' && matrix.type == 'fips' && '-fips' || '' }} - BUILD_TARGET: ${{ matrix.service[3] == 'ddp-streamer-service' && matrix.type == 'fips' && 'release-fips' || '' }} + DOCKER_TAG_SUFFIX_FIPS: ${{ (matrix.service[3] == 'ddp-streamer-service' || matrix.service[3] == 'presence-service') && matrix.type == 'fips' && '-fips' || '' }} + BUILD_TARGET: ${{ (matrix.service[3] == 'ddp-streamer-service' || matrix.service[3] == 'presence-service') && matrix.type == 'fips' && 'release-fips' || '' }} with: CR_USER: ${{ secrets.CR_USER }} CR_PAT: ${{ secrets.CR_PAT }} @@ -520,6 +520,8 @@ jobs: # Get image name from docker-compose-ci.yml since rocketchat image is different from service name (rocket.chat) if [ "$service" == "rocketchat-cov" ]; then IMAGE=$(docker compose -f docker-compose-ci.yml config --format json 2>/dev/null | jq -r --arg s "rocketchat" '.services[$s].image')-cov + elif [ "$service" == "presence-service-fips" ]; then + IMAGE=$(docker compose -f docker-compose-ci.yml config --format json 2>/dev/null | jq -r --arg s "presence-service" '.services[$s].image')-fips elif [ "$service" == "ddp-streamer-service-fips" ]; then IMAGE=$(docker compose -f docker-compose-ci.yml config --format json 2>/dev/null | jq -r --arg s "ddp-streamer-service" '.services[$s].image')-fips else diff --git a/docker-compose-ci.yml b/docker-compose-ci.yml index c0fab0208011e..318a2a54d632f 100644 --- a/docker-compose-ci.yml +++ b/docker-compose-ci.yml @@ -102,13 +102,14 @@ services: build: dockerfile: ${GITHUB_WORKSPACE:-}/ee/apps/Dockerfile context: out/presence-service + target: ${BUILD_TARGET:-release-standard} x-bake: platforms: - linux/amd64 - linux/arm64 args: SERVICE: presence-service - image: ghcr.io/${LOWERCASE_REPOSITORY}/presence-service:${DOCKER_TAG} + image: ghcr.io/${LOWERCASE_REPOSITORY}/presence-service:${DOCKER_TAG}${DOCKER_TAG_SUFFIX_FIPS:-} environment: - MONGO_URL=mongodb://mongo:27017/rocketchat?replicaSet=rs0 - 'TRANSPORTER=${TRANSPORTER:-}' diff --git a/ee/apps/presence-service/src/fips.ts b/ee/apps/presence-service/src/fips.ts new file mode 100644 index 0000000000000..c232a6e586011 --- /dev/null +++ b/ee/apps/presence-service/src/fips.ts @@ -0,0 +1,11 @@ +import crypto from 'crypto'; + +crypto.setFips(true); + +if (crypto.getFips() !== 1) { + throw new Error('FIPS mode was not enabled after crypto.setFips(true)'); +} + +console.log('========================================='); +console.log('FIPS COMPLIANCE CHECK: YES'); +console.log('========================================='); diff --git a/ee/apps/presence-service/tsconfig.json b/ee/apps/presence-service/tsconfig.json index ff29a55af231c..34dfe4e779780 100644 --- a/ee/apps/presence-service/tsconfig.json +++ b/ee/apps/presence-service/tsconfig.json @@ -4,7 +4,7 @@ "strictPropertyInitialization": false, // TODO: Remove this line "outDir": "./dist/ee/apps/presence-service/src", }, - "files": ["./src/service.ts"], + "files": ["./src/service.ts", "./src/fips.ts"], "include": ["../../../apps/meteor/definition/externals/meteor"], "exclude": ["./dist"] } From 5c960d3460826c7f4a20b04a26acec5efd9fa72a Mon Sep 17 00:00:00 2001 From: Matheus Cardoso Date: Mon, 9 Mar 2026 14:05:53 -0300 Subject: [PATCH 13/70] ci: reduce duplication of fips checks --- .github/actions/build-docker/action.yml | 4 +--- .github/workflows/ci.yml | 24 ++++++++++++------------ 2 files changed, 13 insertions(+), 15 deletions(-) diff --git a/.github/actions/build-docker/action.yml b/.github/actions/build-docker/action.yml index ac399a84ae8bb..ecc04e1e65203 100644 --- a/.github/actions/build-docker/action.yml +++ b/.github/actions/build-docker/action.yml @@ -172,9 +172,7 @@ runs: SERVICE_SUFFIX='' if [[ "$INPUT_SERVICE" == 'rocketchat' && "$INPUT_TYPE" == 'coverage' ]] && [[ "$GITHUB_EVENT_NAME" == 'release' || "$GITHUB_REF" == 'refs/heads/develop' ]]; then SERVICE_SUFFIX='-cov' - elif [[ "$INPUT_SERVICE" == 'ddp-streamer-service' && "$INPUT_TYPE" == 'fips' ]]; then - SERVICE_SUFFIX='-fips' - elif [[ "$INPUT_SERVICE" == 'presence-service' && "$INPUT_TYPE" == 'fips' ]]; then + elif [[ "$INPUT_TYPE" == 'fips' ]]; then SERVICE_SUFFIX='-fips' fi diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e4538b38131c5..4d43f07af9738 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -23,6 +23,7 @@ concurrency: env: TOOL_NODE_FLAGS: ${{ vars.TOOL_NODE_FLAGS }} + FIPS_ENABLED_SERVICES: '["ddp-streamer-service","presence-service"]' permissions: contents: read @@ -393,8 +394,8 @@ jobs: env: # add suffix for the extra images with coverage if building for production DOCKER_TAG_SUFFIX_ROCKETCHAT: ${{ matrix.type == 'coverage' && (github.event_name == 'release' || github.ref == 'refs/heads/develop') && '-cov' || '' }} - DOCKER_TAG_SUFFIX_FIPS: ${{ (matrix.service[0] == 'ddp-streamer-service' || matrix.service[0] == 'presence-service') && matrix.type == 'fips' && '-fips' || '' }} - BUILD_TARGET: ${{ (matrix.service[0] == 'ddp-streamer-service' || matrix.service[0] == 'presence-service') && matrix.type == 'fips' && 'release-fips' || '' }} + DOCKER_TAG_SUFFIX_FIPS: ${{ contains(fromJSON(env.FIPS_ENABLED_SERVICES), matrix.service[0]) && matrix.type == 'fips' && '-fips' || '' }} + BUILD_TARGET: ${{ contains(fromJSON(env.FIPS_ENABLED_SERVICES), matrix.service[0]) && matrix.type == 'fips' && 'release-fips' || '' }} with: CR_USER: ${{ secrets.CR_USER }} CR_PAT: ${{ secrets.CR_PAT }} @@ -411,8 +412,8 @@ jobs: if: matrix.service[1] && github.actor != 'dependabot[bot]' env: DOCKER_TAG_SUFFIX_ROCKETCHAT: ${{ matrix.type == 'coverage' && '-cov' || '' }} - DOCKER_TAG_SUFFIX_FIPS: ${{ (matrix.service[1] == 'ddp-streamer-service' || matrix.service[1] == 'presence-service') && matrix.type == 'fips' && '-fips' || '' }} - BUILD_TARGET: ${{ (matrix.service[1] == 'ddp-streamer-service' || matrix.service[1] == 'presence-service') && matrix.type == 'fips' && 'release-fips' || '' }} + DOCKER_TAG_SUFFIX_FIPS: ${{ contains(fromJSON(env.FIPS_ENABLED_SERVICES), matrix.service[1]) && matrix.type == 'fips' && '-fips' || '' }} + BUILD_TARGET: ${{ contains(fromJSON(env.FIPS_ENABLED_SERVICES), matrix.service[1]) && matrix.type == 'fips' && 'release-fips' || '' }} with: CR_USER: ${{ secrets.CR_USER }} CR_PAT: ${{ secrets.CR_PAT }} @@ -430,8 +431,8 @@ jobs: if: matrix.service[2] && github.actor != 'dependabot[bot]' env: DOCKER_TAG_SUFFIX_ROCKETCHAT: ${{ matrix.type == 'coverage' && '-cov' || '' }} - DOCKER_TAG_SUFFIX_FIPS: ${{ (matrix.service[2] == 'ddp-streamer-service' || matrix.service[2] == 'presence-service') && matrix.type == 'fips' && '-fips' || '' }} - BUILD_TARGET: ${{ (matrix.service[2] == 'ddp-streamer-service' || matrix.service[2] == 'presence-service') && matrix.type == 'fips' && 'release-fips' || '' }} + DOCKER_TAG_SUFFIX_FIPS: ${{ contains(fromJSON(env.FIPS_ENABLED_SERVICES), matrix.service[2]) && matrix.type == 'fips' && '-fips' || '' }} + BUILD_TARGET: ${{ contains(fromJSON(env.FIPS_ENABLED_SERVICES), matrix.service[2]) && matrix.type == 'fips' && 'release-fips' || '' }} with: CR_USER: ${{ secrets.CR_USER }} CR_PAT: ${{ secrets.CR_PAT }} @@ -449,8 +450,8 @@ jobs: if: matrix.service[3] && github.actor != 'dependabot[bot]' env: DOCKER_TAG_SUFFIX_ROCKETCHAT: ${{ matrix.type == 'coverage' && '-cov' || '' }} - DOCKER_TAG_SUFFIX_FIPS: ${{ (matrix.service[3] == 'ddp-streamer-service' || matrix.service[3] == 'presence-service') && matrix.type == 'fips' && '-fips' || '' }} - BUILD_TARGET: ${{ (matrix.service[3] == 'ddp-streamer-service' || matrix.service[3] == 'presence-service') && matrix.type == 'fips' && 'release-fips' || '' }} + DOCKER_TAG_SUFFIX_FIPS: ${{ contains(fromJSON(env.FIPS_ENABLED_SERVICES), matrix.service[3]) && matrix.type == 'fips' && '-fips' || '' }} + BUILD_TARGET: ${{ contains(fromJSON(env.FIPS_ENABLED_SERVICES), matrix.service[3]) && matrix.type == 'fips' && 'release-fips' || '' }} with: CR_USER: ${{ secrets.CR_USER }} CR_PAT: ${{ secrets.CR_PAT }} @@ -520,10 +521,9 @@ jobs: # Get image name from docker-compose-ci.yml since rocketchat image is different from service name (rocket.chat) if [ "$service" == "rocketchat-cov" ]; then IMAGE=$(docker compose -f docker-compose-ci.yml config --format json 2>/dev/null | jq -r --arg s "rocketchat" '.services[$s].image')-cov - elif [ "$service" == "presence-service-fips" ]; then - IMAGE=$(docker compose -f docker-compose-ci.yml config --format json 2>/dev/null | jq -r --arg s "presence-service" '.services[$s].image')-fips - elif [ "$service" == "ddp-streamer-service-fips" ]; then - IMAGE=$(docker compose -f docker-compose-ci.yml config --format json 2>/dev/null | jq -r --arg s "ddp-streamer-service" '.services[$s].image')-fips + elif [[ "$service" == *"-fips" ]]; then + base_service="${service%-fips}" + IMAGE=$(docker compose -f docker-compose-ci.yml config --format json 2>/dev/null | jq -r --arg s "$base_service" '.services[$s].image')-fips else IMAGE=$(docker compose -f docker-compose-ci.yml config --format json 2>/dev/null | jq -r --arg s "$service" '.services[$s].image') fi From 841fc12803fc08801b3ab60d57ca33c00e194c4b Mon Sep 17 00:00:00 2001 From: Matheus Cardoso Date: Mon, 9 Mar 2026 15:09:07 -0300 Subject: [PATCH 14/70] ci: fips docker compose overrides --- .github/actions/build-docker/action.yml | 18 ++++++++---- .github/workflows/ci-test-e2e.yml | 39 ++++++++++++++++++------- .github/workflows/ci.yml | 22 +++++++------- docker-compose-ci.fips.yml | 10 +++++++ docker-compose-ci.yml | 5 ++-- 5 files changed, 65 insertions(+), 29 deletions(-) create mode 100644 docker-compose-ci.fips.yml diff --git a/.github/actions/build-docker/action.yml b/.github/actions/build-docker/action.yml index ecc04e1e65203..d7e3fed420d8e 100644 --- a/.github/actions/build-docker/action.yml +++ b/.github/actions/build-docker/action.yml @@ -124,6 +124,10 @@ runs: GITHUB_REF: ${{ github.ref }} run: | set -o xtrace + compose_files=(-f docker-compose-ci.yml) + if [[ "$INPUT_TYPE" == 'fips' ]]; then + compose_files+=(-f docker-compose-ci.fips.yml) + fi # Removes unnecessary swc cores and sharp binaries to reduce image size swc_arch='x64' @@ -147,11 +151,11 @@ runs: LOAD_OR_PUSH="--load" fi - # Get image name from docker-compose-ci.yml since rocketchat image is different from service name (rocket.chat) - IMAGE=$(docker compose -f docker-compose-ci.yml config --format json 2>/dev/null | jq -r --arg s "$INPUT_SERVICE" '.services[$s].image') + # Get image name from compose config since rocketchat image is different from service name (rocket.chat) + IMAGE=$(docker compose "${compose_files[@]}" config --format json 2>/dev/null | jq -r --arg s "$INPUT_SERVICE" '.services[$s].image') docker buildx bake \ - -f docker-compose-ci.yml \ + "${compose_files[@]}" \ ${LOAD_OR_PUSH} \ --allow=fs.read=/tmp/build \ --set "*.tags+=${IMAGE}-gha-run-${GITHUB_RUN_ID}" \ @@ -201,9 +205,13 @@ runs: TYPE: ${{ inputs.type }} run: | set -o xtrace + compose_files=(-f docker-compose-ci.yml) + if [[ "$TYPE" == 'fips' ]]; then + compose_files+=(-f docker-compose-ci.fips.yml) + fi - # Get image name from docker-compose-ci.yml - IMAGE=$(docker compose -f docker-compose-ci.yml config --format json 2>/dev/null | jq -r --arg s "$SERVICE" '.services[$s].image') + # Get image name from compose config + IMAGE=$(docker compose "${compose_files[@]}" config --format json 2>/dev/null | jq -r --arg s "$SERVICE" '.services[$s].image') # Create directory for image archives mkdir -p /tmp/docker-images diff --git a/.github/workflows/ci-test-e2e.yml b/.github/workflows/ci-test-e2e.yml index 9ffcacdda59ec..9fb0de71a1aaf 100644 --- a/.github/workflows/ci-test-e2e.yml +++ b/.github/workflows/ci-test-e2e.yml @@ -80,7 +80,6 @@ jobs: env: # if building for production on develop branch or release, add suffix for coverage images DOCKER_TAG_SUFFIX_ROCKETCHAT: ${{ inputs.coverage == matrix.mongodb-version && (github.event_name == 'release' || github.ref == 'refs/heads/develop') && '-cov' || '' }} - DOCKER_TAG_SUFFIX_FIPS: ${{ inputs.release == 'fips' && '-fips' || '' }} MONGODB_VERSION: ${{ matrix.mongodb-version }} COVERAGE_DIR: '/tmp/coverage/${{ startsWith(inputs.type, ''api'') && ''api'' || inputs.type }}' COVERAGE_FILE_NAME: '${{ inputs.type }}-${{ matrix.shard }}.json' @@ -95,6 +94,16 @@ jobs: name: MongoDB ${{ matrix.mongodb-version }}${{ inputs.coverage == matrix.mongodb-version && ' coverage' || '' }} (${{ matrix.shard }}/${{ inputs.total-shard }}) steps: + - name: Set compose files + run: | + if [[ '${{ inputs.release }}' == 'fips' ]]; then + echo 'COMPOSE_FILES=-f docker-compose-ci.yml -f docker-compose-ci.fips.yml' >> "$GITHUB_ENV" + echo 'COMPOSE_FILES_METEOR=-f ../../docker-compose-ci.yml -f ../../docker-compose-ci.fips.yml' >> "$GITHUB_ENV" + else + echo 'COMPOSE_FILES=-f docker-compose-ci.yml' >> "$GITHUB_ENV" + echo 'COMPOSE_FILES_METEOR=-f ../../docker-compose-ci.yml' >> "$GITHUB_ENV" + fi + - name: Collect Workflow Telemetry if: inputs.type == 'perf' uses: catchpoint/workflow-telemetry-action@94c3c3d9567a0205de6da68a76c428ce4e769af1 # v2.0.0 @@ -177,7 +186,8 @@ jobs: - name: Start httpbin container and wait for it to be ready if: startsWith(inputs.type, 'api') run: | - docker compose -f docker-compose-ci.yml up -d httpbin + read -r -a compose_files <<< "$COMPOSE_FILES" + docker compose "${compose_files[@]}" up -d httpbin - name: Prepare code coverage directory run: | @@ -197,7 +207,8 @@ jobs: TEST_MODE: ${{ startsWith(inputs.type, 'api') && 'api' || 'true' }} run: | # when we are testing CE, we only need to start the rocketchat container - DEBUG_LOG_LEVEL=${DEBUG_LOG_LEVEL:-0} docker compose -f docker-compose-ci.yml up -d rocketchat --wait + read -r -a compose_files <<< "$COMPOSE_FILES" + DEBUG_LOG_LEVEL=${DEBUG_LOG_LEVEL:-0} docker compose "${compose_files[@]}" up -d rocketchat --wait - name: Start containers for EE if: inputs.release == 'ee' || inputs.release == 'fips' @@ -208,7 +219,8 @@ jobs: TEST_MODE: ${{ startsWith(inputs.type, 'api') && 'api' || 'true' }} APPS_ENGINE_RUNTIME_BACKEND: ${{ inputs.type == 'api-apps-node' && 'node' || '' }} run: | - DEBUG_LOG_LEVEL=${DEBUG_LOG_LEVEL:-0} docker compose -f docker-compose-ci.yml up -d --wait + read -r -a compose_files <<< "$COMPOSE_FILES" + DEBUG_LOG_LEVEL=${DEBUG_LOG_LEVEL:-0} docker compose "${compose_files[@]}" up -d --wait - uses: ./.github/actions/setup-playwright if: inputs.type == 'ui' @@ -216,11 +228,12 @@ jobs: - name: Wait services to start up if: inputs.release == 'ee' || inputs.release == 'fips' run: | + read -r -a compose_files <<< "$COMPOSE_FILES" docker ps - until docker compose -f docker-compose-ci.yml logs ddp-streamer-service | grep -q "NetworkBroker started successfully"; do + until docker compose "${compose_files[@]}" logs ddp-streamer-service | grep -q "NetworkBroker started successfully"; do echo "Waiting 'ddp-streamer' to start up" - ((c++)) && ((c==10)) && docker compose -f docker-compose-ci.yml logs ddp-streamer-service && exit 1 + ((c++)) && ((c==10)) && docker compose "${compose_files[@]}" logs ddp-streamer-service && exit 1 sleep 10 done; @@ -235,10 +248,11 @@ jobs: IS_EE: ${{ (inputs.release == 'ee' || inputs.release == 'fips') && 'true' || '' }} run: | set -o xtrace + read -r -a compose_files_meteor <<< "$COMPOSE_FILES_METEOR" npm run testapi || s=$? - docker compose -f ../../docker-compose-ci.yml stop + docker compose "${compose_files_meteor[@]}" stop ls -la "$COVERAGE_DIR" exit "${s:-0}" @@ -268,10 +282,11 @@ jobs: IS_EE: ${{ (inputs.release == 'ee' || inputs.release == 'fips') && 'true' || '' }} run: | set -o xtrace + read -r -a compose_files_meteor <<< "$COMPOSE_FILES_METEOR" npm run testapi:livechat || s=$? - docker compose -f ../../docker-compose-ci.yml stop + docker compose "${compose_files_meteor[@]}" stop ls -la "$COVERAGE_DIR" exit "${s:-0}" @@ -322,11 +337,15 @@ jobs: - name: Show server logs if E2E test failed if: failure() - run: docker compose -f docker-compose-ci.yml logs rocketchat authorization-service queue-worker-service ddp-streamer-service account-service presence-service omnichannel-transcript-service + run: | + read -r -a compose_files <<< "$COMPOSE_FILES" + docker compose "${compose_files[@]}" logs rocketchat authorization-service queue-worker-service ddp-streamer-service account-service presence-service omnichannel-transcript-service - name: Show mongo logs if E2E test failed if: failure() - run: docker compose -f docker-compose-ci.yml logs mongo + run: | + read -r -a compose_files <<< "$COMPOSE_FILES" + docker compose "${compose_files[@]}" logs mongo - name: Show traefik logs if E2E test failed if: failure() diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4d43f07af9738..c87b764d2d415 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -23,7 +23,6 @@ concurrency: env: TOOL_NODE_FLAGS: ${{ vars.TOOL_NODE_FLAGS }} - FIPS_ENABLED_SERVICES: '["ddp-streamer-service","presence-service"]' permissions: contents: read @@ -394,8 +393,6 @@ jobs: env: # add suffix for the extra images with coverage if building for production DOCKER_TAG_SUFFIX_ROCKETCHAT: ${{ matrix.type == 'coverage' && (github.event_name == 'release' || github.ref == 'refs/heads/develop') && '-cov' || '' }} - DOCKER_TAG_SUFFIX_FIPS: ${{ contains(fromJSON(env.FIPS_ENABLED_SERVICES), matrix.service[0]) && matrix.type == 'fips' && '-fips' || '' }} - BUILD_TARGET: ${{ contains(fromJSON(env.FIPS_ENABLED_SERVICES), matrix.service[0]) && matrix.type == 'fips' && 'release-fips' || '' }} with: CR_USER: ${{ secrets.CR_USER }} CR_PAT: ${{ secrets.CR_PAT }} @@ -412,8 +409,6 @@ jobs: if: matrix.service[1] && github.actor != 'dependabot[bot]' env: DOCKER_TAG_SUFFIX_ROCKETCHAT: ${{ matrix.type == 'coverage' && '-cov' || '' }} - DOCKER_TAG_SUFFIX_FIPS: ${{ contains(fromJSON(env.FIPS_ENABLED_SERVICES), matrix.service[1]) && matrix.type == 'fips' && '-fips' || '' }} - BUILD_TARGET: ${{ contains(fromJSON(env.FIPS_ENABLED_SERVICES), matrix.service[1]) && matrix.type == 'fips' && 'release-fips' || '' }} with: CR_USER: ${{ secrets.CR_USER }} CR_PAT: ${{ secrets.CR_PAT }} @@ -431,8 +426,6 @@ jobs: if: matrix.service[2] && github.actor != 'dependabot[bot]' env: DOCKER_TAG_SUFFIX_ROCKETCHAT: ${{ matrix.type == 'coverage' && '-cov' || '' }} - DOCKER_TAG_SUFFIX_FIPS: ${{ contains(fromJSON(env.FIPS_ENABLED_SERVICES), matrix.service[2]) && matrix.type == 'fips' && '-fips' || '' }} - BUILD_TARGET: ${{ contains(fromJSON(env.FIPS_ENABLED_SERVICES), matrix.service[2]) && matrix.type == 'fips' && 'release-fips' || '' }} with: CR_USER: ${{ secrets.CR_USER }} CR_PAT: ${{ secrets.CR_PAT }} @@ -450,8 +443,6 @@ jobs: if: matrix.service[3] && github.actor != 'dependabot[bot]' env: DOCKER_TAG_SUFFIX_ROCKETCHAT: ${{ matrix.type == 'coverage' && '-cov' || '' }} - DOCKER_TAG_SUFFIX_FIPS: ${{ contains(fromJSON(env.FIPS_ENABLED_SERVICES), matrix.service[3]) && matrix.type == 'fips' && '-fips' || '' }} - BUILD_TARGET: ${{ contains(fromJSON(env.FIPS_ENABLED_SERVICES), matrix.service[3]) && matrix.type == 'fips' && 'release-fips' || '' }} with: CR_USER: ${{ secrets.CR_USER }} CR_PAT: ${{ secrets.CR_PAT }} @@ -479,6 +470,7 @@ jobs: with: sparse-checkout: | docker-compose-ci.yml + docker-compose-ci.fips.yml sparse-checkout-cone-mode: false ref: ${{ github.ref }} @@ -1120,6 +1112,7 @@ jobs: with: sparse-checkout: | docker-compose-ci.yml + docker-compose-ci.fips.yml sparse-checkout-cone-mode: false ref: ${{ github.ref }} @@ -1202,8 +1195,15 @@ jobs: IMAGE_NAME="${{ needs.release-versions.outputs.lowercase-repo }}/${service}" fi - # Get image name from docker-compose-ci.yml since rocketchat image is different from service name (rocket.chat) - SRC=$(docker compose -f docker-compose-ci.yml config --format json 2>/dev/null | jq -r --arg s "${service}" '.services[$s].image') + # Get image name from compose config since rocketchat image is different from service name (rocket.chat) + if [ "$service" == "rocketchat-cov" ]; then + SRC=$(docker compose -f docker-compose-ci.yml config --format json 2>/dev/null | jq -r --arg s "rocketchat" '.services[$s].image')-cov + elif [[ "$service" == *"-fips" ]]; then + base_service="${service%-fips}" + SRC=$(docker compose -f docker-compose-ci.yml -f docker-compose-ci.fips.yml config --format json 2>/dev/null | jq -r --arg s "$base_service" '.services[$s].image') + else + SRC=$(docker compose -f docker-compose-ci.yml config --format json 2>/dev/null | jq -r --arg s "${service}" '.services[$s].image') + fi DEST_REPO="docker.io/${IMAGE_NAME}" echo "Copying $SRC to ${DEST_REPO}:${PRIMARY}" diff --git a/docker-compose-ci.fips.yml b/docker-compose-ci.fips.yml new file mode 100644 index 0000000000000..fc67bc966df93 --- /dev/null +++ b/docker-compose-ci.fips.yml @@ -0,0 +1,10 @@ +services: + presence-service: + build: + target: release-fips + image: ghcr.io/${LOWERCASE_REPOSITORY}/presence-service:${DOCKER_TAG}-fips + + ddp-streamer-service: + build: + target: release-fips + image: ghcr.io/${LOWERCASE_REPOSITORY}/ddp-streamer-service:${DOCKER_TAG}-fips diff --git a/docker-compose-ci.yml b/docker-compose-ci.yml index 318a2a54d632f..cef50ced7cbf4 100644 --- a/docker-compose-ci.yml +++ b/docker-compose-ci.yml @@ -102,14 +102,13 @@ services: build: dockerfile: ${GITHUB_WORKSPACE:-}/ee/apps/Dockerfile context: out/presence-service - target: ${BUILD_TARGET:-release-standard} x-bake: platforms: - linux/amd64 - linux/arm64 args: SERVICE: presence-service - image: ghcr.io/${LOWERCASE_REPOSITORY}/presence-service:${DOCKER_TAG}${DOCKER_TAG_SUFFIX_FIPS:-} + image: ghcr.io/${LOWERCASE_REPOSITORY}/presence-service:${DOCKER_TAG} environment: - MONGO_URL=mongodb://mongo:27017/rocketchat?replicaSet=rs0 - 'TRANSPORTER=${TRANSPORTER:-}' @@ -129,7 +128,7 @@ services: - linux/arm64 args: SERVICE: ddp-streamer - image: ghcr.io/${LOWERCASE_REPOSITORY}/ddp-streamer-service:${DOCKER_TAG}${DOCKER_TAG_SUFFIX_FIPS:-} + image: ghcr.io/${LOWERCASE_REPOSITORY}/ddp-streamer-service:${DOCKER_TAG} environment: - MONGO_URL=mongodb://mongo:27017/rocketchat?replicaSet=rs0 - 'TRANSPORTER=${TRANSPORTER:-}' From 98d9d83ceb29132a35cdd59ce82b9746f1a89c6b Mon Sep 17 00:00:00 2001 From: Matheus Cardoso Date: Mon, 9 Mar 2026 15:43:46 -0300 Subject: [PATCH 15/70] ci: specify targets --- docker-compose-ci.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docker-compose-ci.yml b/docker-compose-ci.yml index cef50ced7cbf4..9a7aed7feeed5 100644 --- a/docker-compose-ci.yml +++ b/docker-compose-ci.yml @@ -102,6 +102,7 @@ services: build: dockerfile: ${GITHUB_WORKSPACE:-}/ee/apps/Dockerfile context: out/presence-service + target: release-standard x-bake: platforms: - linux/amd64 @@ -122,6 +123,7 @@ services: build: dockerfile: ${GITHUB_WORKSPACE:-}/ee/apps/Dockerfile context: out/ddp-streamer + target: release-standard x-bake: platforms: - linux/amd64 From 511790016cc0eb63c825c6acb5ea053faf7f56cd Mon Sep 17 00:00:00 2001 From: Matheus Cardoso Date: Mon, 9 Mar 2026 16:16:28 -0300 Subject: [PATCH 16/70] ci: enhance naming and streamline service matrix --- .github/workflows/ci.yml | 71 +++++----------------------------------- 1 file changed, 9 insertions(+), 62 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c87b764d2d415..9f784d32b1be8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -341,7 +341,7 @@ jobs: type: ${{ matrix.type }} build-gh-docker: - name: 🚢 Build Docker + name: 🚢 Build Docker (${{ matrix.service }}-${{ matrix.type }}-${{ matrix.arch }}) needs: [build, release-versions] runs-on: ubuntu-24.04${{ matrix.arch == 'arm64' && '-arm' || '' }} @@ -353,12 +353,7 @@ jobs: fail-fast: false matrix: arch: [arm64, amd64] - service: - [ - [authorization-service, queue-worker-service, ddp-streamer-service], - [account-service, presence-service, omnichannel-transcript-service], - [rocketchat], - ] + service: [authorization-service, queue-worker-service, ddp-streamer-service, account-service, presence-service, omnichannel-transcript-service, rocketchat] type: # if running in a PR build with coverage - ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }} @@ -369,7 +364,7 @@ jobs: include: # if not, build with coverage for tests - arch: amd64 - service: [rocketchat] + service: rocketchat type: coverage # resolves to amd64 when arm64 is excluded above, matching the entry before this # one instead of re-adding an arm64 job (includes bypass exclude) @@ -378,7 +373,10 @@ jobs: type: coverage # build dedicated FIPS images for FIPS test lanes - arch: amd64 - service: [ddp-streamer-service, presence-service] + service: ddp-streamer-service + type: fips + - arch: amd64 + service: presence-service type: fips steps: @@ -387,7 +385,7 @@ jobs: - uses: ./.github/actions/restore-packages # we only build and publish the actual docker images if not a PR from a fork - - name: Image ${{ matrix.service[0] }} + - name: Image ${{ matrix.service }} uses: ./.github/actions/build-docker if: github.actor != 'dependabot[bot]' env: @@ -400,60 +398,9 @@ jobs: TEMP_DOCKERHUB_FIPS_PASS: ${{ secrets.TEMP_DOCKERHUB_FIPS_PASS }} deno-version: ${{ needs.release-versions.outputs.deno-version }} arch: ${{ matrix.arch }} - service: ${{ matrix.service[0] }} - type: ${{ matrix.type }} - publish-image: ${{ github.event.pull_request.head.repo.full_name == github.repository || github.event_name == 'release' || github.ref == 'refs/heads/develop' }} - - - name: Image ${{ matrix.service[1] || '"skipped"' }} - uses: ./.github/actions/build-docker - if: matrix.service[1] && github.actor != 'dependabot[bot]' - env: - DOCKER_TAG_SUFFIX_ROCKETCHAT: ${{ matrix.type == 'coverage' && '-cov' || '' }} - with: - CR_USER: ${{ secrets.CR_USER }} - CR_PAT: ${{ secrets.CR_PAT }} - TEMP_DOCKERHUB_FIPS_USER: ${{ secrets.TEMP_DOCKERHUB_FIPS_USER }} - TEMP_DOCKERHUB_FIPS_PASS: ${{ secrets.TEMP_DOCKERHUB_FIPS_PASS }} - deno-version: ${{ needs.release-versions.outputs.deno-version }} - arch: ${{ matrix.arch }} - service: ${{ matrix.service[1] }} - type: ${{ matrix.type }} - publish-image: ${{ github.event.pull_request.head.repo.full_name == github.repository || github.event_name == 'release' || github.ref == 'refs/heads/develop' }} - setup-docker: false - - - name: Image ${{ matrix.service[2] || '"skipped"' }} - uses: ./.github/actions/build-docker - if: matrix.service[2] && github.actor != 'dependabot[bot]' - env: - DOCKER_TAG_SUFFIX_ROCKETCHAT: ${{ matrix.type == 'coverage' && '-cov' || '' }} - with: - CR_USER: ${{ secrets.CR_USER }} - CR_PAT: ${{ secrets.CR_PAT }} - TEMP_DOCKERHUB_FIPS_USER: ${{ secrets.TEMP_DOCKERHUB_FIPS_USER }} - TEMP_DOCKERHUB_FIPS_PASS: ${{ secrets.TEMP_DOCKERHUB_FIPS_PASS }} - deno-version: ${{ needs.release-versions.outputs.deno-version }} - arch: ${{ matrix.arch }} - service: ${{ matrix.service[2] }} - type: ${{ matrix.type }} - publish-image: ${{ github.event.pull_request.head.repo.full_name == github.repository || github.event_name == 'release' || github.ref == 'refs/heads/develop' }} - setup-docker: false - - - name: Image ${{ matrix.service[3] || '"skipped"' }} - uses: ./.github/actions/build-docker - if: matrix.service[3] && github.actor != 'dependabot[bot]' - env: - DOCKER_TAG_SUFFIX_ROCKETCHAT: ${{ matrix.type == 'coverage' && '-cov' || '' }} - with: - CR_USER: ${{ secrets.CR_USER }} - CR_PAT: ${{ secrets.CR_PAT }} - TEMP_DOCKERHUB_FIPS_USER: ${{ secrets.TEMP_DOCKERHUB_FIPS_USER }} - TEMP_DOCKERHUB_FIPS_PASS: ${{ secrets.TEMP_DOCKERHUB_FIPS_PASS }} - deno-version: ${{ needs.release-versions.outputs.deno-version }} - arch: ${{ matrix.arch }} - service: ${{ matrix.service[3] }} + service: ${{ matrix.service }} type: ${{ matrix.type }} publish-image: ${{ github.event.pull_request.head.repo.full_name == github.repository || github.event_name == 'release' || github.ref == 'refs/heads/develop' }} - setup-docker: false build-gh-docker-publish: name: 🚢 Publish Docker Images (ghcr.io) From f6a12ace6de6730a347919832c50709cf8732ab1 Mon Sep 17 00:00:00 2001 From: Matheus Cardoso Date: Mon, 9 Mar 2026 17:08:24 -0300 Subject: [PATCH 17/70] fix: set fips mode on start --- ee/apps/ddp-streamer/src/fips.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/ee/apps/ddp-streamer/src/fips.ts b/ee/apps/ddp-streamer/src/fips.ts index dbe93b5f6d9a5..32f59060e314f 100644 --- a/ee/apps/ddp-streamer/src/fips.ts +++ b/ee/apps/ddp-streamer/src/fips.ts @@ -16,6 +16,16 @@ */ import crypto from 'crypto'; +crypto.setFips(true); + +if (crypto.getFips() !== 1) { + throw new Error('FIPS mode was not enabled after crypto.setFips(true)'); +} + +console.log('========================================='); +console.log('FIPS COMPLIANCE CHECK: YES'); +console.log('========================================='); + try { crypto.createHash('sha1').update('test').digest('hex'); console.log('🔓 Native SHA-1 allowed. Skipping FIPS WebSocket patch.'); From 80a89471c34b9040e9ca5a69467d1c28bc45bab2 Mon Sep 17 00:00:00 2001 From: Matheus Cardoso Date: Mon, 9 Mar 2026 17:09:36 -0300 Subject: [PATCH 18/70] fix: update artifact pattern --- .github/workflows/ci-test-e2e.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci-test-e2e.yml b/.github/workflows/ci-test-e2e.yml index 9fb0de71a1aaf..09476e8c5abb1 100644 --- a/.github/workflows/ci-test-e2e.yml +++ b/.github/workflows/ci-test-e2e.yml @@ -156,7 +156,7 @@ jobs: uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 if: github.event.pull_request.head.repo.full_name != github.repository && github.event_name != 'release' && github.ref != 'refs/heads/develop' with: - pattern: ${{ inputs.release == 'ce' && 'docker-image-rocketchat-amd64-coverage' || 'docker-image-*-amd64-coverage' }} + pattern: ${{ inputs.release == 'ce' && 'docker-image-rocketchat-amd64-coverage' || (inputs.release == 'fips' && 'docker-image-*-amd64-fips' || 'docker-image-*-amd64-coverage') }} path: /tmp/docker-images merge-multiple: true From c2b839e9668ce44ab5358fdc862321c61062a468 Mon Sep 17 00:00:00 2001 From: Matheus Cardoso Date: Mon, 9 Mar 2026 17:10:05 -0300 Subject: [PATCH 19/70] ci: matrix generation job --- .github/workflows/ci.yml | 75 ++++++++++++++++++++++++---------------- 1 file changed, 46 insertions(+), 29 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9f784d32b1be8..93bc9ed066e60 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -23,6 +23,10 @@ concurrency: env: TOOL_NODE_FLAGS: ${{ vars.TOOL_NODE_FLAGS }} + DOCKER_BUILD_ARCHES_JSON: '["arm64","amd64"]' + DOCKER_BUILD_SERVICES_JSON: '["authorization-service","queue-worker-service","ddp-streamer-service","account-service","presence-service","omnichannel-transcript-service","rocketchat"]' + DOCKER_BUILD_EXTRA_COVERAGE_JSON: '[{"arch":"amd64","service":"rocketchat","type":"coverage"},{"arch":"arm64","service":"rocketchat","type":"coverage"}]' + DOCKER_BUILD_FIPS_SERVICES_JSON: '["ddp-streamer-service","presence-service"]' permissions: contents: read @@ -340,9 +344,48 @@ jobs: source-hash: ${{ needs.release-versions.outputs.packages-build-cache-key }}-${{ needs.release-versions.outputs.meteor-rc-cache-key }} type: ${{ matrix.type }} + build-gh-docker-matrix: + name: ⚙️ Build Docker Matrix + needs: [build] + runs-on: ubuntu-24.04-arm + outputs: + matrix: ${{ steps.generate.outputs.matrix }} + steps: + - id: generate + env: + DOCKER_BUILD_ARCHES_JSON: ${{ env.DOCKER_BUILD_ARCHES_JSON }} + DOCKER_BUILD_SERVICES_JSON: ${{ env.DOCKER_BUILD_SERVICES_JSON }} + DOCKER_BUILD_EXTRA_COVERAGE_JSON: ${{ env.DOCKER_BUILD_EXTRA_COVERAGE_JSON }} + DOCKER_BUILD_FIPS_SERVICES_JSON: ${{ env.DOCKER_BUILD_FIPS_SERVICES_JSON }} + run: | + node <<'NODE' + const fs = require('node:fs'); + + const baseType = '${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}'; + const arches = JSON.parse(process.env.DOCKER_BUILD_ARCHES_JSON); + const services = JSON.parse(process.env.DOCKER_BUILD_SERVICES_JSON); + const extraCoverageRows = JSON.parse(process.env.DOCKER_BUILD_EXTRA_COVERAGE_JSON); + const fipsServices = JSON.parse(process.env.DOCKER_BUILD_FIPS_SERVICES_JSON); + + const include = []; + for (const arch of arches) { + for (const service of services) { + include.push({ arch, service, type: baseType }); + } + } + + if (baseType === 'production') { + include.push(...extraCoverageRows); + } + + include.push(...fipsServices.map((service) => ({ arch: 'amd64', service, type: 'fips' }))); + + fs.appendFileSync(process.env.GITHUB_OUTPUT, `matrix=${JSON.stringify({ include })}\n`); + NODE + build-gh-docker: - name: 🚢 Build Docker (${{ matrix.service }}-${{ matrix.type }}-${{ matrix.arch }}) - needs: [build, release-versions] + name: 🚢 Build Docker (${{ matrix.service }}-${{ matrix.arch }}-${{ matrix.type }}) + needs: [build, build-gh-docker-matrix, release-versions] runs-on: ubuntu-24.04${{ matrix.arch == 'arm64' && '-arm' || '' }} env: @@ -351,33 +394,7 @@ jobs: strategy: fail-fast: false - matrix: - arch: [arm64, amd64] - service: [authorization-service, queue-worker-service, ddp-streamer-service, account-service, presence-service, omnichannel-transcript-service, rocketchat] - type: - # if running in a PR build with coverage - - ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }} - exclude: - # when images aren't published (merge queue, fork PRs) only amd64 tars are uploaded - # as artifacts, so arm64 builds would be discarded — skip them - - arch: ${{ (github.event.pull_request.head.repo.full_name != github.repository && github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'arm64' || '' }} - include: - # if not, build with coverage for tests - - arch: amd64 - service: rocketchat - type: coverage - # resolves to amd64 when arm64 is excluded above, matching the entry before this - # one instead of re-adding an arm64 job (includes bypass exclude) - - arch: ${{ (github.event.pull_request.head.repo.full_name == github.repository || github.event_name == 'release' || github.ref == 'refs/heads/develop') && 'arm64' || 'amd64' }} - service: [rocketchat] - type: coverage - # build dedicated FIPS images for FIPS test lanes - - arch: amd64 - service: ddp-streamer-service - type: fips - - arch: amd64 - service: presence-service - type: fips + matrix: ${{ fromJSON(needs.build-gh-docker-matrix.outputs.matrix) }} steps: - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 From 9c7bc13d03a1db28de3cbb4a521ae559582f6285 Mon Sep 17 00:00:00 2001 From: Matheus Cardoso Date: Tue, 10 Mar 2026 09:06:59 -0300 Subject: [PATCH 20/70] feat(authorization-service): fips --- .github/workflows/ci.yml | 2 +- docker-compose-ci.fips.yml | 5 +++++ docker-compose-ci.yml | 1 + ee/apps/authorization-service/src/fips.ts | 11 +++++++++++ ee/apps/authorization-service/tsconfig.json | 2 +- 5 files changed, 19 insertions(+), 2 deletions(-) create mode 100644 ee/apps/authorization-service/src/fips.ts diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 93bc9ed066e60..f7935df0dee15 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -26,7 +26,7 @@ env: DOCKER_BUILD_ARCHES_JSON: '["arm64","amd64"]' DOCKER_BUILD_SERVICES_JSON: '["authorization-service","queue-worker-service","ddp-streamer-service","account-service","presence-service","omnichannel-transcript-service","rocketchat"]' DOCKER_BUILD_EXTRA_COVERAGE_JSON: '[{"arch":"amd64","service":"rocketchat","type":"coverage"},{"arch":"arm64","service":"rocketchat","type":"coverage"}]' - DOCKER_BUILD_FIPS_SERVICES_JSON: '["ddp-streamer-service","presence-service"]' + DOCKER_BUILD_FIPS_SERVICES_JSON: '["authorization-service","ddp-streamer-service","presence-service"]' permissions: contents: read diff --git a/docker-compose-ci.fips.yml b/docker-compose-ci.fips.yml index fc67bc966df93..4cfc6ec0964ca 100644 --- a/docker-compose-ci.fips.yml +++ b/docker-compose-ci.fips.yml @@ -1,4 +1,9 @@ services: + authorization-service: + build: + target: release-fips + image: ghcr.io/${LOWERCASE_REPOSITORY}/authorization-service:${DOCKER_TAG}-fips + presence-service: build: target: release-fips diff --git a/docker-compose-ci.yml b/docker-compose-ci.yml index 9a7aed7feeed5..dfa67a0f6817f 100644 --- a/docker-compose-ci.yml +++ b/docker-compose-ci.yml @@ -62,6 +62,7 @@ services: build: dockerfile: ${GITHUB_WORKSPACE:-}/ee/apps/Dockerfile context: out/authorization-service + target: release-standard x-bake: platforms: - linux/amd64 diff --git a/ee/apps/authorization-service/src/fips.ts b/ee/apps/authorization-service/src/fips.ts new file mode 100644 index 0000000000000..c232a6e586011 --- /dev/null +++ b/ee/apps/authorization-service/src/fips.ts @@ -0,0 +1,11 @@ +import crypto from 'crypto'; + +crypto.setFips(true); + +if (crypto.getFips() !== 1) { + throw new Error('FIPS mode was not enabled after crypto.setFips(true)'); +} + +console.log('========================================='); +console.log('FIPS COMPLIANCE CHECK: YES'); +console.log('========================================='); diff --git a/ee/apps/authorization-service/tsconfig.json b/ee/apps/authorization-service/tsconfig.json index 04f7fc0034d1c..7c04aabd6bb1b 100644 --- a/ee/apps/authorization-service/tsconfig.json +++ b/ee/apps/authorization-service/tsconfig.json @@ -4,7 +4,7 @@ "strictPropertyInitialization": false, // TODO: Remove this line "outDir": "./dist" }, - "files": ["./src/service.ts"], + "files": ["./src/service.ts", "./src/fips.ts"], "include": ["../../../apps/meteor/definition/externals/meteor"], "exclude": ["./dist"] } From e777a9f96fe691a83ef2802b1582a0f6faa8d37e Mon Sep 17 00:00:00 2001 From: Matheus Cardoso Date: Tue, 10 Mar 2026 09:28:23 -0300 Subject: [PATCH 21/70] fix(ddp-streamer): remove flaky check --- ee/apps/ddp-streamer/src/fips.ts | 227 +++++++++++++++---------------- 1 file changed, 111 insertions(+), 116 deletions(-) diff --git a/ee/apps/ddp-streamer/src/fips.ts b/ee/apps/ddp-streamer/src/fips.ts index 32f59060e314f..1767ed946a7e4 100644 --- a/ee/apps/ddp-streamer/src/fips.ts +++ b/ee/apps/ddp-streamer/src/fips.ts @@ -26,122 +26,117 @@ console.log('========================================='); console.log('FIPS COMPLIANCE CHECK: YES'); console.log('========================================='); -try { - crypto.createHash('sha1').update('test').digest('hex'); - console.log('🔓 Native SHA-1 allowed. Skipping FIPS WebSocket patch.'); -} catch (err) { - console.log('🔒 FIPS 140-3 mode detected. Applying WebSocket Handshake Patch...'); - - const blocks = new Uint32Array(32); - const w = new Uint32Array(80); - const hashBuffer = Buffer.alloc(20); - - const generateWebSocketAccept = (message: string): string => { - if (message.length !== 60) { - throw new Error(`Expected 60-byte input for WS Accept, got ${message.length}`); +console.log('🔒 FIPS 140-3 mode detected. Applying WebSocket Handshake Patch...'); + +const blocks = new Uint32Array(32); +const w = new Uint32Array(80); +const hashBuffer = Buffer.alloc(20); + +const generateWebSocketAccept = (message: string): string => { + if (message.length !== 60) { + throw new Error(`Expected 60-byte input for WS Accept, got ${message.length}`); + } + + blocks.fill(0); + + let h0 = 0x67452301; + let h1 = 0xefcdab89; + let h2 = 0x98badcfe; + let h3 = 0x10325476; + let h4 = 0xc3d2e1f0; + + for (let i = 0; i < 60; i++) blocks[i >> 2] |= message.charCodeAt(i) << (24 - (i % 4) * 8); + blocks[15] = 0x80000000; + blocks[31] = 480; + + const rotl = (n: number, b: number) => (n << b) | (n >>> (32 - b)); + + for (let chunk = 0; chunk < 2; chunk++) { + const offset = chunk * 16; + for (let i = 0; i < 16; i++) w[i] = blocks[offset + i]; + for (let i = 16; i < 80; i++) w[i] = rotl(w[i - 3] ^ w[i - 8] ^ w[i - 14] ^ w[i - 16], 1); + + let a = h0; + let b = h1; + let c = h2; + let d = h3; + let e = h4; + let temp; + + for (let i = 0; i < 20; i++) { + temp = (rotl(a, 5) + (d ^ (b & (c ^ d))) + e + 0x5a827999 + w[i]) >>> 0; + e = d; + d = c; + c = rotl(b, 30); + b = a; + a = temp; } - - blocks.fill(0); - - let h0 = 0x67452301; - let h1 = 0xefcdab89; - let h2 = 0x98badcfe; - let h3 = 0x10325476; - let h4 = 0xc3d2e1f0; - - for (let i = 0; i < 60; i++) blocks[i >> 2] |= message.charCodeAt(i) << (24 - (i % 4) * 8); - blocks[15] = 0x80000000; - blocks[31] = 480; - - const rotl = (n: number, b: number) => (n << b) | (n >>> (32 - b)); - - for (let chunk = 0; chunk < 2; chunk++) { - const offset = chunk * 16; - for (let i = 0; i < 16; i++) w[i] = blocks[offset + i]; - for (let i = 16; i < 80; i++) w[i] = rotl(w[i - 3] ^ w[i - 8] ^ w[i - 14] ^ w[i - 16], 1); - - let a = h0; - let b = h1; - let c = h2; - let d = h3; - let e = h4; - let temp; - - for (let i = 0; i < 20; i++) { - temp = (rotl(a, 5) + (d ^ (b & (c ^ d))) + e + 0x5a827999 + w[i]) >>> 0; - e = d; - d = c; - c = rotl(b, 30); - b = a; - a = temp; - } - for (let i = 20; i < 40; i++) { - temp = (rotl(a, 5) + (b ^ c ^ d) + e + 0x6ed9eba1 + w[i]) >>> 0; - e = d; - d = c; - c = rotl(b, 30); - b = a; - a = temp; - } - for (let i = 40; i < 60; i++) { - temp = (rotl(a, 5) + ((b & c) | (b & d) | (c & d)) + e + 0x8f1bbcdc + w[i]) >>> 0; - e = d; - d = c; - c = rotl(b, 30); - b = a; - a = temp; - } - for (let i = 60; i < 80; i++) { - temp = (rotl(a, 5) + (b ^ c ^ d) + e + 0xca62c1d6 + w[i]) >>> 0; - e = d; - d = c; - c = rotl(b, 30); - b = a; - a = temp; - } - - h0 = (h0 + a) >>> 0; - h1 = (h1 + b) >>> 0; - h2 = (h2 + c) >>> 0; - h3 = (h3 + d) >>> 0; - h4 = (h4 + e) >>> 0; + for (let i = 20; i < 40; i++) { + temp = (rotl(a, 5) + (b ^ c ^ d) + e + 0x6ed9eba1 + w[i]) >>> 0; + e = d; + d = c; + c = rotl(b, 30); + b = a; + a = temp; } - - hashBuffer.writeUInt32BE(h0, 0); - hashBuffer.writeUInt32BE(h1, 4); - hashBuffer.writeUInt32BE(h2, 8); - hashBuffer.writeUInt32BE(h3, 12); - hashBuffer.writeUInt32BE(h4, 16); - - return hashBuffer.toString('base64'); - }; - - const originalCreateHash = crypto.createHash; - - crypto.createHash = function (algorithm: string, options?: crypto.HashOptions) { - if (algorithm.toLowerCase() === 'sha1') { - let inputData = ''; - - return { - update(data) { - if (typeof data === 'string') { - inputData += data; - } else if (Buffer.isBuffer(data)) { - inputData += data.toString('utf8'); - } else { - inputData += Buffer.from(data.buffer, data.byteOffset, data.byteLength).toString('utf8'); - } - return this; - }, - digest(encoding) { - if (encoding === 'base64' && inputData.length === 60) { - return generateWebSocketAccept(inputData); - } - // If it's not the exact WS handshake, pass it back to native (which will throw FIPS error) - return originalCreateHash(algorithm, options).update(inputData).digest(encoding); - }, - } as crypto.Hash; + for (let i = 40; i < 60; i++) { + temp = (rotl(a, 5) + ((b & c) | (b & d) | (c & d)) + e + 0x8f1bbcdc + w[i]) >>> 0; + e = d; + d = c; + c = rotl(b, 30); + b = a; + a = temp; } - return originalCreateHash(algorithm, options); - }; -} + for (let i = 60; i < 80; i++) { + temp = (rotl(a, 5) + (b ^ c ^ d) + e + 0xca62c1d6 + w[i]) >>> 0; + e = d; + d = c; + c = rotl(b, 30); + b = a; + a = temp; + } + + h0 = (h0 + a) >>> 0; + h1 = (h1 + b) >>> 0; + h2 = (h2 + c) >>> 0; + h3 = (h3 + d) >>> 0; + h4 = (h4 + e) >>> 0; + } + + hashBuffer.writeUInt32BE(h0, 0); + hashBuffer.writeUInt32BE(h1, 4); + hashBuffer.writeUInt32BE(h2, 8); + hashBuffer.writeUInt32BE(h3, 12); + hashBuffer.writeUInt32BE(h4, 16); + + return hashBuffer.toString('base64'); +}; + +const originalCreateHash = crypto.createHash; + +crypto.createHash = function (algorithm: string, options?: crypto.HashOptions) { + if (algorithm.toLowerCase() === 'sha1') { + let inputData = ''; + + return { + update(data) { + if (typeof data === 'string') { + inputData += data; + } else if (Buffer.isBuffer(data)) { + inputData += data.toString('utf8'); + } else { + inputData += Buffer.from(data.buffer, data.byteOffset, data.byteLength).toString('utf8'); + } + return this; + }, + digest(encoding) { + if (encoding === 'base64' && inputData.length === 60) { + return generateWebSocketAccept(inputData); + } + // If it's not the exact WS handshake, pass it back to native (which will throw FIPS error) + return originalCreateHash(algorithm, options).update(inputData).digest(encoding); + }, + } as crypto.Hash; + } + return originalCreateHash(algorithm, options); +}; From 564e619be24e313dbb8fa9338842336082dd76df Mon Sep 17 00:00:00 2001 From: Matheus Cardoso Date: Tue, 10 Mar 2026 10:37:00 -0300 Subject: [PATCH 22/70] feat(queue-worker): fips --- .github/workflows/ci.yml | 2 +- docker-compose-ci.fips.yml | 5 +++++ docker-compose-ci.yml | 1 + ee/apps/queue-worker/src/fips.ts | 11 +++++++++++ ee/apps/queue-worker/tsconfig.json | 2 +- 5 files changed, 19 insertions(+), 2 deletions(-) create mode 100644 ee/apps/queue-worker/src/fips.ts diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f7935df0dee15..a92cf58260584 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -26,7 +26,7 @@ env: DOCKER_BUILD_ARCHES_JSON: '["arm64","amd64"]' DOCKER_BUILD_SERVICES_JSON: '["authorization-service","queue-worker-service","ddp-streamer-service","account-service","presence-service","omnichannel-transcript-service","rocketchat"]' DOCKER_BUILD_EXTRA_COVERAGE_JSON: '[{"arch":"amd64","service":"rocketchat","type":"coverage"},{"arch":"arm64","service":"rocketchat","type":"coverage"}]' - DOCKER_BUILD_FIPS_SERVICES_JSON: '["authorization-service","ddp-streamer-service","presence-service"]' + DOCKER_BUILD_FIPS_SERVICES_JSON: '["authorization-service","queue-worker-service","ddp-streamer-service","presence-service"]' permissions: contents: read diff --git a/docker-compose-ci.fips.yml b/docker-compose-ci.fips.yml index 4cfc6ec0964ca..995d074cf456c 100644 --- a/docker-compose-ci.fips.yml +++ b/docker-compose-ci.fips.yml @@ -13,3 +13,8 @@ services: build: target: release-fips image: ghcr.io/${LOWERCASE_REPOSITORY}/ddp-streamer-service:${DOCKER_TAG}-fips + + queue-worker-service: + build: + target: release-fips + image: ghcr.io/${LOWERCASE_REPOSITORY}/queue-worker-service:${DOCKER_TAG}-fips diff --git a/docker-compose-ci.yml b/docker-compose-ci.yml index dfa67a0f6817f..5c2a8ec1b7db8 100644 --- a/docker-compose-ci.yml +++ b/docker-compose-ci.yml @@ -151,6 +151,7 @@ services: build: dockerfile: ${GITHUB_WORKSPACE:-}/ee/apps/Dockerfile context: out/queue-worker + target: release-standard x-bake: platforms: - linux/amd64 diff --git a/ee/apps/queue-worker/src/fips.ts b/ee/apps/queue-worker/src/fips.ts new file mode 100644 index 0000000000000..c232a6e586011 --- /dev/null +++ b/ee/apps/queue-worker/src/fips.ts @@ -0,0 +1,11 @@ +import crypto from 'crypto'; + +crypto.setFips(true); + +if (crypto.getFips() !== 1) { + throw new Error('FIPS mode was not enabled after crypto.setFips(true)'); +} + +console.log('========================================='); +console.log('FIPS COMPLIANCE CHECK: YES'); +console.log('========================================='); diff --git a/ee/apps/queue-worker/tsconfig.json b/ee/apps/queue-worker/tsconfig.json index c12ebd48bcdc0..c36303f2794d1 100644 --- a/ee/apps/queue-worker/tsconfig.json +++ b/ee/apps/queue-worker/tsconfig.json @@ -4,7 +4,7 @@ "strictPropertyInitialization": false, "outDir": "./dist/ee/apps/queue-worker/src", }, - "files": ["./src/service.ts"], + "files": ["./src/service.ts", "./src/fips.ts"], "include": ["../../../apps/meteor/definition/externals/meteor"], "exclude": ["./dist"] } From d15ba31ab86c72c2e2054026ea76d21509acdb2b Mon Sep 17 00:00:00 2001 From: Matheus Cardoso Date: Tue, 10 Mar 2026 11:06:24 -0300 Subject: [PATCH 23/70] feat(account-service): fips --- .github/workflows/ci.yml | 2 +- docker-compose-ci.fips.yml | 5 +++++ docker-compose-ci.yml | 1 + ee/apps/account-service/src/fips.ts | 11 +++++++++++ 4 files changed, 18 insertions(+), 1 deletion(-) create mode 100644 ee/apps/account-service/src/fips.ts diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a92cf58260584..4d2516aadfb76 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -26,7 +26,7 @@ env: DOCKER_BUILD_ARCHES_JSON: '["arm64","amd64"]' DOCKER_BUILD_SERVICES_JSON: '["authorization-service","queue-worker-service","ddp-streamer-service","account-service","presence-service","omnichannel-transcript-service","rocketchat"]' DOCKER_BUILD_EXTRA_COVERAGE_JSON: '[{"arch":"amd64","service":"rocketchat","type":"coverage"},{"arch":"arm64","service":"rocketchat","type":"coverage"}]' - DOCKER_BUILD_FIPS_SERVICES_JSON: '["authorization-service","queue-worker-service","ddp-streamer-service","presence-service"]' + DOCKER_BUILD_FIPS_SERVICES_JSON: '["authorization-service","queue-worker-service","ddp-streamer-service","account-service","presence-service"]' permissions: contents: read diff --git a/docker-compose-ci.fips.yml b/docker-compose-ci.fips.yml index 995d074cf456c..9d49cf28af538 100644 --- a/docker-compose-ci.fips.yml +++ b/docker-compose-ci.fips.yml @@ -1,4 +1,9 @@ services: + account-service: + build: + target: release-fips + image: ghcr.io/${LOWERCASE_REPOSITORY}/account-service:${DOCKER_TAG}-fips + authorization-service: build: target: release-fips diff --git a/docker-compose-ci.yml b/docker-compose-ci.yml index 5c2a8ec1b7db8..f017504747715 100644 --- a/docker-compose-ci.yml +++ b/docker-compose-ci.yml @@ -83,6 +83,7 @@ services: build: dockerfile: ${GITHUB_WORKSPACE:-}/ee/apps/Dockerfile context: out/account-service + target: release-standard x-bake: platforms: - linux/amd64 diff --git a/ee/apps/account-service/src/fips.ts b/ee/apps/account-service/src/fips.ts new file mode 100644 index 0000000000000..60cf98ffbb3d4 --- /dev/null +++ b/ee/apps/account-service/src/fips.ts @@ -0,0 +1,11 @@ +import crypto from 'crypto'; + +crypto.setFips(true); + +if (!crypto.getFips()) { + throw new Error('FIPS mode was not enabled after crypto.setFips(true)'); +} + +console.log('========================================='); +console.log('FIPS COMPLIANCE CHECK: YES'); +console.log('========================================='); From 77c62f4839d224e0de9e6ffbaa1d1af8f4dc1be1 Mon Sep 17 00:00:00 2001 From: Matheus Cardoso Date: Tue, 10 Mar 2026 12:36:45 -0300 Subject: [PATCH 24/70] fix(ci): compose build fallback creds --- .github/workflows/ci-test-e2e.yml | 9 ++++++++- .github/workflows/ci.yml | 6 ++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci-test-e2e.yml b/.github/workflows/ci-test-e2e.yml index 09476e8c5abb1..6692733e27a45 100644 --- a/.github/workflows/ci-test-e2e.yml +++ b/.github/workflows/ci-test-e2e.yml @@ -49,6 +49,10 @@ on: required: true CR_PAT: required: true + TEMP_DOCKERHUB_FIPS_USER: + required: false + TEMP_DOCKERHUB_FIPS_PASS: + required: false QASE_API_TOKEN: required: false REPORTER_ROCKETCHAT_URL: @@ -130,8 +134,11 @@ jobs: password: ${{ secrets.CR_PAT }} - name: Login to DockerHub for FIPS base images - if: inputs.release == 'fips' + if: inputs.release == 'fips' && env.TEMP_DOCKERHUB_FIPS_USER != '' && env.TEMP_DOCKERHUB_FIPS_PASS != '' uses: docker/login-action@v3 + env: + TEMP_DOCKERHUB_FIPS_USER: ${{ secrets.TEMP_DOCKERHUB_FIPS_USER }} + TEMP_DOCKERHUB_FIPS_PASS: ${{ secrets.TEMP_DOCKERHUB_FIPS_PASS }} with: username: ${{ secrets.TEMP_DOCKERHUB_FIPS_USER }} password: ${{ secrets.TEMP_DOCKERHUB_FIPS_PASS }} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4d2516aadfb76..c2965916dccb9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -751,6 +751,8 @@ jobs: secrets: CR_USER: ${{ secrets.CR_USER }} CR_PAT: ${{ secrets.CR_PAT }} + TEMP_DOCKERHUB_FIPS_USER: ${{ secrets.TEMP_DOCKERHUB_FIPS_USER }} + TEMP_DOCKERHUB_FIPS_PASS: ${{ secrets.TEMP_DOCKERHUB_FIPS_PASS }} test-api-livechat-fips: name: 🔨 Test API Livechat (FIPS) @@ -770,6 +772,8 @@ jobs: secrets: CR_USER: ${{ secrets.CR_USER }} CR_PAT: ${{ secrets.CR_PAT }} + TEMP_DOCKERHUB_FIPS_USER: ${{ secrets.TEMP_DOCKERHUB_FIPS_USER }} + TEMP_DOCKERHUB_FIPS_PASS: ${{ secrets.TEMP_DOCKERHUB_FIPS_PASS }} test-ui-fips: name: 🔨 Test UI (FIPS) @@ -792,6 +796,8 @@ jobs: secrets: CR_USER: ${{ secrets.CR_USER }} CR_PAT: ${{ secrets.CR_PAT }} + TEMP_DOCKERHUB_FIPS_USER: ${{ secrets.TEMP_DOCKERHUB_FIPS_USER }} + TEMP_DOCKERHUB_FIPS_PASS: ${{ secrets.TEMP_DOCKERHUB_FIPS_PASS }} QASE_API_TOKEN: ${{ secrets.QASE_API_TOKEN }} REPORTER_ROCKETCHAT_API_KEY: ${{ secrets.REPORTER_ROCKETCHAT_API_KEY }} REPORTER_ROCKETCHAT_URL: ${{ secrets.REPORTER_ROCKETCHAT_URL }} From 34b0fe607431bef2452fad678e8c0831d77c91cc Mon Sep 17 00:00:00 2001 From: Matheus Cardoso Date: Tue, 10 Mar 2026 13:17:42 -0300 Subject: [PATCH 25/70] fix(ci): retry docker buildx with backoff --- .github/actions/build-docker/action.yml | 55 ++++++++++++++++++------- 1 file changed, 40 insertions(+), 15 deletions(-) diff --git a/.github/actions/build-docker/action.yml b/.github/actions/build-docker/action.yml index d7e3fed420d8e..81d618b6fe315 100644 --- a/.github/actions/build-docker/action.yml +++ b/.github/actions/build-docker/action.yml @@ -151,23 +151,48 @@ runs: LOAD_OR_PUSH="--load" fi + export DOCKER_CLIENT_TIMEOUT=300 + export COMPOSE_HTTP_TIMEOUT=300 + # Get image name from compose config since rocketchat image is different from service name (rocket.chat) - IMAGE=$(docker compose "${compose_files[@]}" config --format json 2>/dev/null | jq -r --arg s "$INPUT_SERVICE" '.services[$s].image') - - docker buildx bake \ - "${compose_files[@]}" \ - ${LOAD_OR_PUSH} \ - --allow=fs.read=/tmp/build \ - --set "*.tags+=${IMAGE}-gha-run-${GITHUB_RUN_ID}" \ - --set "*.labels.org.opencontainers.image.description=Build run: ${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}" \ - --set "*.labels.org.opencontainers.image.source=${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}" \ - --set "*.platform=linux/${INPUT_ARCH}" \ - --set *.cache-from=type=gha \ - --set *.cache-to=type=gha,mode=max \ - --provenance=false \ - --sbom=false \ - --metadata-file "/tmp/meta.json" \ + IMAGE=$(docker compose "${compose_files[@]}" config --format json 2>/dev/null | jq -r --arg s "$INPUT_SERVICE" '.services[$s].image') + + buildx_bake_cmd=( + docker buildx bake + "${compose_files[@]}" + "$LOAD_OR_PUSH" + "--allow=fs.read=/tmp/build" + "--set" + "*.tags+=${IMAGE}-gha-run-${GITHUB_RUN_ID}" + "--set" + "*.labels.org.opencontainers.image.description=Build run: ${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}" + "--set" + "*.labels.org.opencontainers.image.source=${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}" + "--set" + "*.platform=linux/${INPUT_ARCH}" + "--set" + "*.cache-from=type=gha" + "--set" + "*.cache-to=type=gha,mode=max" + "--provenance=false" + "--sbom=false" + --metadata-file "/tmp/meta.json" "$INPUT_SERVICE" + ) + + attempts=1 + max_attempts=3 + until "${buildx_bake_cmd[@]}"; do + if [[ "$INPUT_PUBLISH_IMAGE" != 'true' || $attempts -ge $max_attempts ]]; then + echo "docker buildx bake failed after ${attempts} attempt(s)." + exit 1 + fi + + attempts=$((attempts + 1)) + sleep_seconds=$((15 * attempts)) + echo "docker buildx bake failed (likely transient push error). Retrying in ${sleep_seconds}s... (attempt ${attempts}/${max_attempts})" + sleep "${sleep_seconds}" + done echo "Contents of /tmp/meta.json:" cat /tmp/meta.json From e8d28cb73453b2e477b0b461cfe7f75b51aa4603 Mon Sep 17 00:00:00 2001 From: Matheus Cardoso Date: Tue, 10 Mar 2026 14:13:28 -0300 Subject: [PATCH 26/70] feat(ci): fips image pull step service readiness checks --- .github/workflows/ci-test-e2e.yml | 42 ++++++++++++++++++++++++++----- 1 file changed, 36 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci-test-e2e.yml b/.github/workflows/ci-test-e2e.yml index 6692733e27a45..f4239000eef91 100644 --- a/.github/workflows/ci-test-e2e.yml +++ b/.github/workflows/ci-test-e2e.yml @@ -203,6 +203,12 @@ jobs: mkdir -p "$COVERAGE_DIR" chmod 777 "$COVERAGE_DIR" + - name: Pull FIPS images + if: inputs.release == 'fips' && (github.event.pull_request.head.repo.full_name == github.repository || github.event_name == 'release' || github.ref == 'refs/heads/develop') + run: | + read -r -a compose_files <<< "$COMPOSE_FILES" + docker compose "${compose_files[@]}" pull + - name: Start containers for CE if: inputs.release == 'ce' env: @@ -227,7 +233,11 @@ jobs: APPS_ENGINE_RUNTIME_BACKEND: ${{ inputs.type == 'api-apps-node' && 'node' || '' }} run: | read -r -a compose_files <<< "$COMPOSE_FILES" - DEBUG_LOG_LEVEL=${DEBUG_LOG_LEVEL:-0} docker compose "${compose_files[@]}" up -d --wait + if [[ '${{ inputs.release }}' == 'fips' ]]; then + DEBUG_LOG_LEVEL=${DEBUG_LOG_LEVEL:-0} docker compose "${compose_files[@]}" up -d --wait --no-build + else + DEBUG_LOG_LEVEL=${DEBUG_LOG_LEVEL:-0} docker compose "${compose_files[@]}" up -d --wait + fi - uses: ./.github/actions/setup-playwright if: inputs.type == 'ui' @@ -238,11 +248,31 @@ jobs: read -r -a compose_files <<< "$COMPOSE_FILES" docker ps - until docker compose "${compose_files[@]}" logs ddp-streamer-service | grep -q "NetworkBroker started successfully"; do - echo "Waiting 'ddp-streamer' to start up" - ((c++)) && ((c==10)) && docker compose "${compose_files[@]}" logs ddp-streamer-service && exit 1 - sleep 10 - done; + wait_for_service() { + local service="$1" + local retries=18 + local delay=10 + + for attempt in $(seq 1 "$retries"); do + if docker compose "${compose_files[@]}" logs "$service" | grep -q "NetworkBroker started successfully"; then + echo "Service '$service' is ready" + return 0 + fi + + echo "Waiting '$service' to start up (attempt ${attempt}/${retries})" + sleep "$delay" + done + + echo "Service '$service' did not become ready in time" + docker compose "${compose_files[@]}" logs "$service" + return 1 + } + + wait_for_service ddp-streamer-service + wait_for_service account-service + wait_for_service authorization-service + wait_for_service queue-worker-service + wait_for_service presence-service - name: Remove unused Docker images run: docker system prune -af From fe3235bc24c28a999ba2f32b0665b29d10f3b63c Mon Sep 17 00:00:00 2001 From: Matheus Cardoso Date: Wed, 11 Mar 2026 09:20:48 -0300 Subject: [PATCH 27/70] feat(omnichannel-transcript): fips --- .github/workflows/ci-test-e2e.yml | 1 + .github/workflows/ci.yml | 2 +- docker-compose-ci.fips.yml | 5 +++++ docker-compose-ci.yml | 1 + ee/apps/omnichannel-transcript/src/fips.ts | 11 +++++++++++ ee/apps/omnichannel-transcript/tsconfig.json | 2 +- 6 files changed, 20 insertions(+), 2 deletions(-) create mode 100644 ee/apps/omnichannel-transcript/src/fips.ts diff --git a/.github/workflows/ci-test-e2e.yml b/.github/workflows/ci-test-e2e.yml index f4239000eef91..a885b4c3473a3 100644 --- a/.github/workflows/ci-test-e2e.yml +++ b/.github/workflows/ci-test-e2e.yml @@ -273,6 +273,7 @@ jobs: wait_for_service authorization-service wait_for_service queue-worker-service wait_for_service presence-service + wait_for_service omnichannel-transcript-service - name: Remove unused Docker images run: docker system prune -af diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c2965916dccb9..7782b599cff34 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -26,7 +26,7 @@ env: DOCKER_BUILD_ARCHES_JSON: '["arm64","amd64"]' DOCKER_BUILD_SERVICES_JSON: '["authorization-service","queue-worker-service","ddp-streamer-service","account-service","presence-service","omnichannel-transcript-service","rocketchat"]' DOCKER_BUILD_EXTRA_COVERAGE_JSON: '[{"arch":"amd64","service":"rocketchat","type":"coverage"},{"arch":"arm64","service":"rocketchat","type":"coverage"}]' - DOCKER_BUILD_FIPS_SERVICES_JSON: '["authorization-service","queue-worker-service","ddp-streamer-service","account-service","presence-service"]' + DOCKER_BUILD_FIPS_SERVICES_JSON: '["authorization-service","queue-worker-service","ddp-streamer-service","account-service","presence-service","omnichannel-transcript-service"]' permissions: contents: read diff --git a/docker-compose-ci.fips.yml b/docker-compose-ci.fips.yml index 9d49cf28af538..69bafb63e434f 100644 --- a/docker-compose-ci.fips.yml +++ b/docker-compose-ci.fips.yml @@ -23,3 +23,8 @@ services: build: target: release-fips image: ghcr.io/${LOWERCASE_REPOSITORY}/queue-worker-service:${DOCKER_TAG}-fips + + omnichannel-transcript-service: + build: + target: release-fips + image: ghcr.io/${LOWERCASE_REPOSITORY}/omnichannel-transcript-service:${DOCKER_TAG}-fips diff --git a/docker-compose-ci.yml b/docker-compose-ci.yml index f017504747715..8ad3162d7a5aa 100644 --- a/docker-compose-ci.yml +++ b/docker-compose-ci.yml @@ -173,6 +173,7 @@ services: build: dockerfile: ${GITHUB_WORKSPACE:-}/ee/apps/Dockerfile context: out/omnichannel-transcript + target: release-standard x-bake: platforms: - linux/amd64 diff --git a/ee/apps/omnichannel-transcript/src/fips.ts b/ee/apps/omnichannel-transcript/src/fips.ts new file mode 100644 index 0000000000000..60cf98ffbb3d4 --- /dev/null +++ b/ee/apps/omnichannel-transcript/src/fips.ts @@ -0,0 +1,11 @@ +import crypto from 'crypto'; + +crypto.setFips(true); + +if (!crypto.getFips()) { + throw new Error('FIPS mode was not enabled after crypto.setFips(true)'); +} + +console.log('========================================='); +console.log('FIPS COMPLIANCE CHECK: YES'); +console.log('========================================='); diff --git a/ee/apps/omnichannel-transcript/tsconfig.json b/ee/apps/omnichannel-transcript/tsconfig.json index 6c7f2d916a732..253de00013a22 100644 --- a/ee/apps/omnichannel-transcript/tsconfig.json +++ b/ee/apps/omnichannel-transcript/tsconfig.json @@ -4,7 +4,7 @@ "strictPropertyInitialization": false, "outDir": "./dist/ee/apps/omnichannel-transcript/src", }, - "files": ["./src/service.ts"], + "files": ["./src/service.ts", "./src/fips.ts"], "include": ["../../../apps/meteor/definition/externals/meteor"], "exclude": ["./dist"] } From 888904947b044c89ca65230a5ef3b438308a7321 Mon Sep 17 00:00:00 2001 From: Matheus Cardoso Date: Wed, 11 Mar 2026 10:40:44 -0300 Subject: [PATCH 28/70] ci: improve readiness checks --- .github/workflows/ci-test-e2e.yml | 59 +++++++++++++++++++++++++++---- 1 file changed, 53 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci-test-e2e.yml b/.github/workflows/ci-test-e2e.yml index a885b4c3473a3..ef52c1693deb5 100644 --- a/.github/workflows/ci-test-e2e.yml +++ b/.github/workflows/ci-test-e2e.yml @@ -248,12 +248,52 @@ jobs: read -r -a compose_files <<< "$COMPOSE_FILES" docker ps + wait_for_mongo_primary() { + local retries=36 + local delay=5 + + for attempt in $(seq 1 "$retries"); do + local is_primary + is_primary=$(docker compose "${compose_files[@]}" exec -T mongo mongosh --quiet --eval "try { const hello = db.hello(); print((hello.isWritablePrimary || hello.ismaster) ? '1' : '0'); } catch (e) { print('0'); }" 2>/dev/null | tail -n1) + + if [[ "$is_primary" == '1' ]]; then + echo "Mongo replica set primary is ready" + return 0 + fi + + echo "Waiting for Mongo primary (attempt ${attempt}/${retries})" + sleep "$delay" + done + + echo "Mongo primary was not ready in time" + docker compose "${compose_files[@]}" logs mongo + return 1 + } + wait_for_service() { local service="$1" local retries=18 local delay=10 for attempt in $(seq 1 "$retries"); do + local container_id + container_id=$(docker compose "${compose_files[@]}" ps -q "$service") + + if [[ -z "$container_id" ]]; then + echo "Service '$service' has no container ID" + docker compose "${compose_files[@]}" ps + return 1 + fi + + local container_state + container_state=$(docker inspect -f '{{.State.Status}}' "$container_id" 2>/dev/null || echo "unknown") + + if [[ "$container_state" != 'running' ]]; then + echo "Service '$service' is not running (state=$container_state)" + docker compose "${compose_files[@]}" logs "$service" + return 1 + fi + if docker compose "${compose_files[@]}" logs "$service" | grep -q "NetworkBroker started successfully"; then echo "Service '$service' is ready" return 0 @@ -268,12 +308,19 @@ jobs: return 1 } - wait_for_service ddp-streamer-service - wait_for_service account-service - wait_for_service authorization-service - wait_for_service queue-worker-service - wait_for_service presence-service - wait_for_service omnichannel-transcript-service + mapfile -t services_to_wait < <( + docker compose "${compose_files[@]}" config --format json \ + | jq -r '.services | keys[] | select(endswith("-service"))' \ + | sort + ) + + wait_for_mongo_primary + + echo "Waiting for services: ${services_to_wait[*]}" + + for service in "${services_to_wait[@]}"; do + wait_for_service "$service" + done - name: Remove unused Docker images run: docker system prune -af From 599428fa885ed28129b7792a3bea9e80e0b71726 Mon Sep 17 00:00:00 2001 From: Matheus Cardoso Date: Wed, 11 Mar 2026 11:36:27 -0300 Subject: [PATCH 29/70] fix(ci): update readiness check for omnichannel-transcript service --- .github/workflows/ci-test-e2e.yml | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci-test-e2e.yml b/.github/workflows/ci-test-e2e.yml index ef52c1693deb5..e110e56d27d30 100644 --- a/.github/workflows/ci-test-e2e.yml +++ b/.github/workflows/ci-test-e2e.yml @@ -274,6 +274,13 @@ jobs: local service="$1" local retries=18 local delay=10 + local ready_pattern="NetworkBroker started successfully" + + case "$service" in + omnichannel-transcript-service) + ready_pattern="Service 'omnichannel-transcript' started." + ;; + esac for attempt in $(seq 1 "$retries"); do local container_id @@ -294,7 +301,7 @@ jobs: return 1 fi - if docker compose "${compose_files[@]}" logs "$service" | grep -q "NetworkBroker started successfully"; then + if docker compose "${compose_files[@]}" logs "$service" | grep -q "$ready_pattern"; then echo "Service '$service' is ready" return 0 fi From 01614ba01e9d6ce6388d898f1c628eab20b5997f Mon Sep 17 00:00:00 2001 From: Matheus Cardoso Date: Wed, 11 Mar 2026 12:21:06 -0300 Subject: [PATCH 30/70] fix(ci): update image pull step to support both EE and FIPS releases --- .github/workflows/ci-test-e2e.yml | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/.github/workflows/ci-test-e2e.yml b/.github/workflows/ci-test-e2e.yml index e110e56d27d30..61ae6a299f90a 100644 --- a/.github/workflows/ci-test-e2e.yml +++ b/.github/workflows/ci-test-e2e.yml @@ -203,8 +203,8 @@ jobs: mkdir -p "$COVERAGE_DIR" chmod 777 "$COVERAGE_DIR" - - name: Pull FIPS images - if: inputs.release == 'fips' && (github.event.pull_request.head.repo.full_name == github.repository || github.event_name == 'release' || github.ref == 'refs/heads/develop') + - name: Pull EE/FIPS images + if: (inputs.release == 'ee' || inputs.release == 'fips') && (github.event.pull_request.head.repo.full_name == github.repository || github.event_name == 'release' || github.ref == 'refs/heads/develop') run: | read -r -a compose_files <<< "$COMPOSE_FILES" docker compose "${compose_files[@]}" pull @@ -233,11 +233,7 @@ jobs: APPS_ENGINE_RUNTIME_BACKEND: ${{ inputs.type == 'api-apps-node' && 'node' || '' }} run: | read -r -a compose_files <<< "$COMPOSE_FILES" - if [[ '${{ inputs.release }}' == 'fips' ]]; then - DEBUG_LOG_LEVEL=${DEBUG_LOG_LEVEL:-0} docker compose "${compose_files[@]}" up -d --wait --no-build - else - DEBUG_LOG_LEVEL=${DEBUG_LOG_LEVEL:-0} docker compose "${compose_files[@]}" up -d --wait - fi + DEBUG_LOG_LEVEL=${DEBUG_LOG_LEVEL:-0} docker compose "${compose_files[@]}" up -d --wait --no-build - uses: ./.github/actions/setup-playwright if: inputs.type == 'ui' From 2d6b2f2c4b544d11560c5f034abb93b3fdddc979 Mon Sep 17 00:00:00 2001 From: Matheus Cardoso Date: Wed, 11 Mar 2026 13:45:31 -0300 Subject: [PATCH 31/70] ci: make readiness check more robust --- .github/workflows/ci-test-e2e.yml | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/.github/workflows/ci-test-e2e.yml b/.github/workflows/ci-test-e2e.yml index 61ae6a299f90a..b8ea9e5bcd1bf 100644 --- a/.github/workflows/ci-test-e2e.yml +++ b/.github/workflows/ci-test-e2e.yml @@ -270,13 +270,8 @@ jobs: local service="$1" local retries=18 local delay=10 - local ready_pattern="NetworkBroker started successfully" - - case "$service" in - omnichannel-transcript-service) - ready_pattern="Service 'omnichannel-transcript' started." - ;; - esac + local broker_service_name="${service%-service}" + local ready_pattern="NetworkBroker started successfully|ServiceBroker with [0-9]+ service\(s\) started successfully|Service '${broker_service_name}' started\." for attempt in $(seq 1 "$retries"); do local container_id @@ -290,6 +285,8 @@ jobs: local container_state container_state=$(docker inspect -f '{{.State.Status}}' "$container_id" 2>/dev/null || echo "unknown") + local health_state + health_state=$(docker inspect -f '{{if .State.Health}}{{.State.Health.Status}}{{else}}none{{end}}' "$container_id" 2>/dev/null || echo "unknown") if [[ "$container_state" != 'running' ]]; then echo "Service '$service' is not running (state=$container_state)" @@ -297,7 +294,18 @@ jobs: return 1 fi - if docker compose "${compose_files[@]}" logs "$service" | grep -q "$ready_pattern"; then + if [[ "$health_state" == 'unhealthy' ]]; then + echo "Service '$service' is unhealthy" + docker compose "${compose_files[@]}" logs "$service" + return 1 + fi + + if [[ "$health_state" == 'healthy' ]]; then + echo "Service '$service' is healthy" + return 0 + fi + + if docker compose "${compose_files[@]}" logs "$service" | grep -Eq "$ready_pattern"; then echo "Service '$service' is ready" return 0 fi From 9d6aa0919d3897dce0b3ae688689e76a27109266 Mon Sep 17 00:00:00 2001 From: Matheus Cardoso Date: Wed, 11 Mar 2026 13:46:51 -0300 Subject: [PATCH 32/70] ci: do not skip rocketchat image in fork runs --- .github/workflows/ci-test-e2e.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci-test-e2e.yml b/.github/workflows/ci-test-e2e.yml index b8ea9e5bcd1bf..47fcbcdaffea5 100644 --- a/.github/workflows/ci-test-e2e.yml +++ b/.github/workflows/ci-test-e2e.yml @@ -163,7 +163,7 @@ jobs: uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 if: github.event.pull_request.head.repo.full_name != github.repository && github.event_name != 'release' && github.ref != 'refs/heads/develop' with: - pattern: ${{ inputs.release == 'ce' && 'docker-image-rocketchat-amd64-coverage' || (inputs.release == 'fips' && 'docker-image-*-amd64-fips' || 'docker-image-*-amd64-coverage') }} + pattern: ${{ inputs.release == 'ce' && 'docker-image-rocketchat-amd64-coverage' || (inputs.release == 'fips' && 'docker-image-*-amd64-*' || 'docker-image-*-amd64-coverage') }} path: /tmp/docker-images merge-multiple: true From 1f59625becbafb625f543bfa3a913f7614db9141 Mon Sep 17 00:00:00 2001 From: Matheus Cardoso Date: Wed, 11 Mar 2026 13:50:08 -0300 Subject: [PATCH 33/70] fix(fips): enhance SHA-1 handling in FIPS mode for WebSocket handshake --- ee/apps/ddp-streamer/src/fips.ts | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/ee/apps/ddp-streamer/src/fips.ts b/ee/apps/ddp-streamer/src/fips.ts index 1767ed946a7e4..ec96ac44aedc6 100644 --- a/ee/apps/ddp-streamer/src/fips.ts +++ b/ee/apps/ddp-streamer/src/fips.ts @@ -119,22 +119,27 @@ crypto.createHash = function (algorithm: string, options?: crypto.HashOptions) { let inputData = ''; return { - update(data) { + update(data: string | Buffer | NodeJS.ArrayBufferView, inputEncoding?: crypto.Encoding) { if (typeof data === 'string') { - inputData += data; + if (inputEncoding) { + inputData += Buffer.from(data, inputEncoding as BufferEncoding).toString('latin1'); + } else { + inputData += data; + } } else if (Buffer.isBuffer(data)) { - inputData += data.toString('utf8'); + inputData += data.toString('latin1'); } else { - inputData += Buffer.from(data.buffer, data.byteOffset, data.byteLength).toString('utf8'); + inputData += Buffer.from(data.buffer, data.byteOffset, data.byteLength).toString('latin1'); } return this; }, - digest(encoding) { + digest(encoding?: crypto.BinaryToTextEncoding) { if (encoding === 'base64' && inputData.length === 60) { return generateWebSocketAccept(inputData); } // If it's not the exact WS handshake, pass it back to native (which will throw FIPS error) - return originalCreateHash(algorithm, options).update(inputData).digest(encoding); + const hash = originalCreateHash(algorithm, options).update(Buffer.from(inputData, 'latin1')); + return encoding ? hash.digest(encoding) : hash.digest(); }, } as crypto.Hash; } From 27d6c0d8d0f0bdebe698c4c2b6d2e658d2c950b4 Mon Sep 17 00:00:00 2001 From: Matheus Cardoso Date: Wed, 11 Mar 2026 13:50:19 -0300 Subject: [PATCH 34/70] fix(fips): enhance SHA-1 handling in FIPS mode with error handling for unsupported methods --- ee/apps/ddp-streamer/src/fips.ts | 32 ++++++++++++++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/ee/apps/ddp-streamer/src/fips.ts b/ee/apps/ddp-streamer/src/fips.ts index ec96ac44aedc6..d143875b0e575 100644 --- a/ee/apps/ddp-streamer/src/fips.ts +++ b/ee/apps/ddp-streamer/src/fips.ts @@ -114,11 +114,23 @@ const generateWebSocketAccept = (message: string): string => { const originalCreateHash = crypto.createHash; +const createUnsupportedSha1MethodError = (method: string): Error => + new Error( + `Unsupported SHA-1 hash API in FIPS mode: crypto.createHash('sha1').${method}. ` + + `Only update() and digest('base64') for the WebSocket handshake are supported.`, + ); + +const createUnsupportedSha1Method = (method: string) => { + return () => { + throw createUnsupportedSha1MethodError(method); + }; +}; + crypto.createHash = function (algorithm: string, options?: crypto.HashOptions) { if (algorithm.toLowerCase() === 'sha1') { let inputData = ''; - return { + const mockHash = { update(data: string | Buffer | NodeJS.ArrayBufferView, inputEncoding?: crypto.Encoding) { if (typeof data === 'string') { if (inputEncoding) { @@ -141,7 +153,23 @@ crypto.createHash = function (algorithm: string, options?: crypto.HashOptions) { const hash = originalCreateHash(algorithm, options).update(Buffer.from(inputData, 'latin1')); return encoding ? hash.digest(encoding) : hash.digest(); }, - } as crypto.Hash; + copy: createUnsupportedSha1Method('copy'), + on: createUnsupportedSha1Method('on'), + once: createUnsupportedSha1Method('once'), + emit: createUnsupportedSha1Method('emit'), + pipe: createUnsupportedSha1Method('pipe'), + } as Record; + + const guardedHash = new Proxy(mockHash, { + get(target, prop, receiver) { + if (typeof prop === 'string' && !(prop in target)) { + throw createUnsupportedSha1MethodError(prop); + } + return Reflect.get(target, prop, receiver); + }, + }); + + return guardedHash as unknown as crypto.Hash; } return originalCreateHash(algorithm, options); }; From 85cfd7395591e69716d06f7918bc5fbcac4a7b82 Mon Sep 17 00:00:00 2001 From: Matheus Cardoso Date: Wed, 11 Mar 2026 13:51:57 -0300 Subject: [PATCH 35/70] fix(ci): remove exclusion of rocketchat-cov service from promotion process --- .github/workflows/ci.yml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7782b599cff34..cd2083aca3ae8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1153,10 +1153,6 @@ jobs: [[ -d "$service_dir" ]] || continue service="$(basename "$service_dir")" - if [ "$service" == "rocketchat-cov" ]; then - continue - fi - echo "Promoting $service" if [[ "${service}" == 'rocketchat' ]]; then From 49b24e90a0ab7019f75a8087c2228465aed4a290 Mon Sep 17 00:00:00 2001 From: Matheus Cardoso Date: Wed, 11 Mar 2026 13:54:03 -0300 Subject: [PATCH 36/70] fix(fips): improve SHA-1 handling in FIPS mode by storing updates for WebSocket handshake --- ee/apps/ddp-streamer/src/fips.ts | 40 +++++++++++++++++++++++--------- 1 file changed, 29 insertions(+), 11 deletions(-) diff --git a/ee/apps/ddp-streamer/src/fips.ts b/ee/apps/ddp-streamer/src/fips.ts index d143875b0e575..87294d0c34d1b 100644 --- a/ee/apps/ddp-streamer/src/fips.ts +++ b/ee/apps/ddp-streamer/src/fips.ts @@ -126,31 +126,49 @@ const createUnsupportedSha1Method = (method: string) => { }; }; +type Sha1UpdateCall = { type: 'string'; chunk: string; encoding?: BufferEncoding } | { type: 'buffer'; chunk: Buffer }; + crypto.createHash = function (algorithm: string, options?: crypto.HashOptions) { if (algorithm.toLowerCase() === 'sha1') { - let inputData = ''; + const updates: Sha1UpdateCall[] = []; + let wsProbeInput = ''; const mockHash = { update(data: string | Buffer | NodeJS.ArrayBufferView, inputEncoding?: crypto.Encoding) { if (typeof data === 'string') { - if (inputEncoding) { - inputData += Buffer.from(data, inputEncoding as BufferEncoding).toString('latin1'); - } else { - inputData += data; - } + const encoding = inputEncoding as BufferEncoding | undefined; + updates.push({ type: 'string', chunk: data, encoding }); + wsProbeInput += Buffer.from(data, encoding ?? 'utf8').toString('latin1'); } else if (Buffer.isBuffer(data)) { - inputData += data.toString('latin1'); + const chunk = Buffer.from(data); + updates.push({ type: 'buffer', chunk }); + wsProbeInput += chunk.toString('latin1'); } else { - inputData += Buffer.from(data.buffer, data.byteOffset, data.byteLength).toString('latin1'); + const chunk = Buffer.from(data.buffer, data.byteOffset, data.byteLength); + updates.push({ type: 'buffer', chunk: Buffer.from(chunk) }); + wsProbeInput += chunk.toString('latin1'); } return this; }, digest(encoding?: crypto.BinaryToTextEncoding) { - if (encoding === 'base64' && inputData.length === 60) { - return generateWebSocketAccept(inputData); + if (encoding === 'base64' && wsProbeInput.length === 60) { + return generateWebSocketAccept(wsProbeInput); } // If it's not the exact WS handshake, pass it back to native (which will throw FIPS error) - const hash = originalCreateHash(algorithm, options).update(Buffer.from(inputData, 'latin1')); + const hash = originalCreateHash(algorithm, options); + + for (const updateCall of updates) { + if (updateCall.type === 'string') { + if (updateCall.encoding) { + hash.update(updateCall.chunk, updateCall.encoding); + } else { + hash.update(updateCall.chunk); + } + } else { + hash.update(updateCall.chunk); + } + } + return encoding ? hash.digest(encoding) : hash.digest(); }, copy: createUnsupportedSha1Method('copy'), From 536883205c342c88a7250fffc8e85c6db6086197 Mon Sep 17 00:00:00 2001 From: Matheus Cardoso Date: Wed, 11 Mar 2026 15:07:36 -0300 Subject: [PATCH 37/70] chore: consistent fips checks --- ee/apps/authorization-service/src/fips.ts | 2 +- ee/apps/ddp-streamer/src/fips.ts | 2 +- ee/apps/presence-service/src/fips.ts | 2 +- ee/apps/queue-worker/src/fips.ts | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/ee/apps/authorization-service/src/fips.ts b/ee/apps/authorization-service/src/fips.ts index c232a6e586011..60cf98ffbb3d4 100644 --- a/ee/apps/authorization-service/src/fips.ts +++ b/ee/apps/authorization-service/src/fips.ts @@ -2,7 +2,7 @@ import crypto from 'crypto'; crypto.setFips(true); -if (crypto.getFips() !== 1) { +if (!crypto.getFips()) { throw new Error('FIPS mode was not enabled after crypto.setFips(true)'); } diff --git a/ee/apps/ddp-streamer/src/fips.ts b/ee/apps/ddp-streamer/src/fips.ts index 87294d0c34d1b..fb645599eda15 100644 --- a/ee/apps/ddp-streamer/src/fips.ts +++ b/ee/apps/ddp-streamer/src/fips.ts @@ -18,7 +18,7 @@ import crypto from 'crypto'; crypto.setFips(true); -if (crypto.getFips() !== 1) { +if (!crypto.getFips()) { throw new Error('FIPS mode was not enabled after crypto.setFips(true)'); } diff --git a/ee/apps/presence-service/src/fips.ts b/ee/apps/presence-service/src/fips.ts index c232a6e586011..60cf98ffbb3d4 100644 --- a/ee/apps/presence-service/src/fips.ts +++ b/ee/apps/presence-service/src/fips.ts @@ -2,7 +2,7 @@ import crypto from 'crypto'; crypto.setFips(true); -if (crypto.getFips() !== 1) { +if (!crypto.getFips()) { throw new Error('FIPS mode was not enabled after crypto.setFips(true)'); } diff --git a/ee/apps/queue-worker/src/fips.ts b/ee/apps/queue-worker/src/fips.ts index c232a6e586011..60cf98ffbb3d4 100644 --- a/ee/apps/queue-worker/src/fips.ts +++ b/ee/apps/queue-worker/src/fips.ts @@ -2,7 +2,7 @@ import crypto from 'crypto'; crypto.setFips(true); -if (crypto.getFips() !== 1) { +if (!crypto.getFips()) { throw new Error('FIPS mode was not enabled after crypto.setFips(true)'); } From f4ec69771f8aa42795498e3113b87b2d337f0e9e Mon Sep 17 00:00:00 2001 From: Matheus Cardoso Date: Thu, 12 Mar 2026 11:24:26 -0300 Subject: [PATCH 38/70] fix(fips): add WebSocket handshake validation for SHA-1 input in FIPS mode --- ee/apps/ddp-streamer/src/fips.ts | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/ee/apps/ddp-streamer/src/fips.ts b/ee/apps/ddp-streamer/src/fips.ts index fb645599eda15..0188b3bd8f59d 100644 --- a/ee/apps/ddp-streamer/src/fips.ts +++ b/ee/apps/ddp-streamer/src/fips.ts @@ -31,6 +31,8 @@ console.log('🔒 FIPS 140-3 mode detected. Applying WebSocket Handshake Patch.. const blocks = new Uint32Array(32); const w = new Uint32Array(80); const hashBuffer = Buffer.alloc(20); +const WEBSOCKET_GUID = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'; +const SEC_WEBSOCKET_KEY_BASE64_PATTERN = /^[A-Za-z0-9+/]{22}==$/; const generateWebSocketAccept = (message: string): string => { if (message.length !== 60) { @@ -128,6 +130,17 @@ const createUnsupportedSha1Method = (method: string) => { type Sha1UpdateCall = { type: 'string'; chunk: string; encoding?: BufferEncoding } | { type: 'buffer'; chunk: Buffer }; +const isWebSocketHandshakeSha1Input = (input: string): boolean => { + if (input.length !== 60) { + return false; + } + + const secWebSocketKey = input.slice(0, 24); + const guid = input.slice(24); + + return guid === WEBSOCKET_GUID && SEC_WEBSOCKET_KEY_BASE64_PATTERN.test(secWebSocketKey); +}; + crypto.createHash = function (algorithm: string, options?: crypto.HashOptions) { if (algorithm.toLowerCase() === 'sha1') { const updates: Sha1UpdateCall[] = []; @@ -151,7 +164,7 @@ crypto.createHash = function (algorithm: string, options?: crypto.HashOptions) { return this; }, digest(encoding?: crypto.BinaryToTextEncoding) { - if (encoding === 'base64' && wsProbeInput.length === 60) { + if (encoding === 'base64' && isWebSocketHandshakeSha1Input(wsProbeInput)) { return generateWebSocketAccept(wsProbeInput); } // If it's not the exact WS handshake, pass it back to native (which will throw FIPS error) From 1a5623fa77df9af7bfc0b3fd751e63ba6576c6a9 Mon Sep 17 00:00:00 2001 From: Matheus Cardoso Date: Thu, 12 Mar 2026 11:24:47 -0300 Subject: [PATCH 39/70] fix(fips): prevent multiple calls to digest in SHA-1 mock implementation for WebSocket handshake --- ee/apps/ddp-streamer/src/fips.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/ee/apps/ddp-streamer/src/fips.ts b/ee/apps/ddp-streamer/src/fips.ts index 0188b3bd8f59d..087647ba557e9 100644 --- a/ee/apps/ddp-streamer/src/fips.ts +++ b/ee/apps/ddp-streamer/src/fips.ts @@ -145,9 +145,14 @@ crypto.createHash = function (algorithm: string, options?: crypto.HashOptions) { if (algorithm.toLowerCase() === 'sha1') { const updates: Sha1UpdateCall[] = []; let wsProbeInput = ''; + let finalized = false; const mockHash = { update(data: string | Buffer | NodeJS.ArrayBufferView, inputEncoding?: crypto.Encoding) { + if (finalized) { + throw new Error('Digest already called'); + } + if (typeof data === 'string') { const encoding = inputEncoding as BufferEncoding | undefined; updates.push({ type: 'string', chunk: data, encoding }); @@ -164,6 +169,12 @@ crypto.createHash = function (algorithm: string, options?: crypto.HashOptions) { return this; }, digest(encoding?: crypto.BinaryToTextEncoding) { + if (finalized) { + throw new Error('Digest already called'); + } + + finalized = true; + if (encoding === 'base64' && isWebSocketHandshakeSha1Input(wsProbeInput)) { return generateWebSocketAccept(wsProbeInput); } From d04ee51037b1c66dcacceae9102f894de28f61c0 Mon Sep 17 00:00:00 2001 From: Matheus Cardoso Date: Thu, 12 Mar 2026 11:25:55 -0300 Subject: [PATCH 40/70] fix(fips): add TEMP_DOCKERHUB_FIPS_USER and TEMP_DOCKERHUB_FIPS_PASS to environment variables for DockerHub login --- .github/workflows/ci-test-e2e.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci-test-e2e.yml b/.github/workflows/ci-test-e2e.yml index 47fcbcdaffea5..8377129d21e8e 100644 --- a/.github/workflows/ci-test-e2e.yml +++ b/.github/workflows/ci-test-e2e.yml @@ -88,6 +88,8 @@ jobs: COVERAGE_DIR: '/tmp/coverage/${{ startsWith(inputs.type, ''api'') && ''api'' || inputs.type }}' COVERAGE_FILE_NAME: '${{ inputs.type }}-${{ matrix.shard }}.json' COVERAGE_REPORTER: ${{ inputs.coverage == matrix.mongodb-version && 'json' || '' }} + TEMP_DOCKERHUB_FIPS_USER: ${{ secrets.TEMP_DOCKERHUB_FIPS_USER }} + TEMP_DOCKERHUB_FIPS_PASS: ${{ secrets.TEMP_DOCKERHUB_FIPS_PASS }} strategy: fail-fast: false @@ -136,9 +138,6 @@ jobs: - name: Login to DockerHub for FIPS base images if: inputs.release == 'fips' && env.TEMP_DOCKERHUB_FIPS_USER != '' && env.TEMP_DOCKERHUB_FIPS_PASS != '' uses: docker/login-action@v3 - env: - TEMP_DOCKERHUB_FIPS_USER: ${{ secrets.TEMP_DOCKERHUB_FIPS_USER }} - TEMP_DOCKERHUB_FIPS_PASS: ${{ secrets.TEMP_DOCKERHUB_FIPS_PASS }} with: username: ${{ secrets.TEMP_DOCKERHUB_FIPS_USER }} password: ${{ secrets.TEMP_DOCKERHUB_FIPS_PASS }} From ae52898e093c6edcd5d0e7acb042310a87da8681 Mon Sep 17 00:00:00 2001 From: Matheus Cardoso Date: Thu, 12 Mar 2026 15:26:30 -0300 Subject: [PATCH 41/70] fix(ci): improve service wait logic by checking for running containers before proceeding --- .github/workflows/ci-test-e2e.yml | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci-test-e2e.yml b/.github/workflows/ci-test-e2e.yml index 8377129d21e8e..e463a981c4150 100644 --- a/.github/workflows/ci-test-e2e.yml +++ b/.github/workflows/ci-test-e2e.yml @@ -319,13 +319,19 @@ jobs: } mapfile -t services_to_wait < <( - docker compose "${compose_files[@]}" config --format json \ - | jq -r '.services | keys[] | select(endswith("-service"))' \ + docker compose "${compose_files[@]}" ps --services --status running \ + | grep -- '-service$' \ | sort ) wait_for_mongo_primary + if (( ${#services_to_wait[@]} == 0 )); then + echo "No running -service containers found to wait for" + docker compose "${compose_files[@]}" ps + exit 1 + fi + echo "Waiting for services: ${services_to_wait[*]}" for service in "${services_to_wait[@]}"; do From d240962983f17bd5b9b3bf4f39761146bfa252c3 Mon Sep 17 00:00:00 2001 From: Matheus Cardoso Date: Fri, 13 Mar 2026 12:05:28 -0300 Subject: [PATCH 42/70] fix(ci): semgrep findings --- .github/actions/build-docker/action.yml | 64 +++++++++++-------------- .github/workflows/ci-test-e2e.yml | 8 ++-- 2 files changed, 34 insertions(+), 38 deletions(-) diff --git a/.github/actions/build-docker/action.yml b/.github/actions/build-docker/action.yml index 81d618b6fe315..8ecb0f11f6ab9 100644 --- a/.github/actions/build-docker/action.yml +++ b/.github/actions/build-docker/action.yml @@ -112,18 +112,20 @@ runs: - name: Build Docker images shell: bash env: - DENO_VERSION: ${{ inputs.deno-version }} + INPUT_DENO_VERSION: ${{ inputs.deno-version }} INPUT_ARCH: ${{ inputs.arch }} INPUT_SERVICE: ${{ inputs.service }} INPUT_PUBLISH_IMAGE: ${{ inputs.publish-image }} INPUT_TYPE: ${{ inputs.type }} - GITHUB_RUN_ID: ${{ github.run_id }} - GITHUB_SERVER_URL: ${{ github.server_url }} - GITHUB_REPOSITORY: ${{ github.repository }} - GITHUB_EVENT_NAME: ${{ github.event_name }} - GITHUB_REF: ${{ github.ref }} + GH_RUN_ID: ${{ github.run_id }} + GH_SERVER_URL: ${{ github.server_url }} + GH_REPOSITORY: ${{ github.repository }} + GH_EVENT_NAME: ${{ github.event_name }} + GH_REF: ${{ github.ref }} + SERVICE_SUFFIX_FROM_CONTEXT: ${{ (inputs.service == 'rocketchat' && inputs.type == 'coverage' && (github.event_name == 'release' || github.ref == 'refs/heads/develop')) && '-cov' || (inputs.type == 'fips' && '-fips' || '') }} run: | set -o xtrace + export DENO_VERSION="$INPUT_DENO_VERSION" compose_files=(-f docker-compose-ci.yml) if [[ "$INPUT_TYPE" == 'fips' ]]; then compose_files+=(-f docker-compose-ci.fips.yml) @@ -157,25 +159,22 @@ runs: # Get image name from compose config since rocketchat image is different from service name (rocket.chat) IMAGE=$(docker compose "${compose_files[@]}" config --format json 2>/dev/null | jq -r --arg s "$INPUT_SERVICE" '.services[$s].image') + BUILD_RUN_URL="${GH_SERVER_URL}/${GH_REPOSITORY}/actions/runs/${GH_RUN_ID}" + BUILD_SOURCE_URL="${GH_SERVER_URL}/${GH_REPOSITORY}" + buildx_bake_cmd=( docker buildx bake "${compose_files[@]}" - "$LOAD_OR_PUSH" - "--allow=fs.read=/tmp/build" - "--set" - "*.tags+=${IMAGE}-gha-run-${GITHUB_RUN_ID}" - "--set" - "*.labels.org.opencontainers.image.description=Build run: ${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}" - "--set" - "*.labels.org.opencontainers.image.source=${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}" - "--set" - "*.platform=linux/${INPUT_ARCH}" - "--set" - "*.cache-from=type=gha" - "--set" - "*.cache-to=type=gha,mode=max" - "--provenance=false" - "--sbom=false" + ${LOAD_OR_PUSH} + --allow=fs.read=/tmp/build + --set "*.tags+=${IMAGE}-gha-run-${GH_RUN_ID}" + --set "*.labels.org.opencontainers.image.description=Build run: ${BUILD_RUN_URL}" + --set "*.labels.org.opencontainers.image.source=${BUILD_SOURCE_URL}" + --set "*.platform=linux/${INPUT_ARCH}" + --set *.cache-from=type=gha + --set *.cache-to=type=gha,mode=max + --provenance=false + --sbom=false --metadata-file "/tmp/meta.json" "$INPUT_SERVICE" ) @@ -198,12 +197,7 @@ runs: cat /tmp/meta.json if [[ "$INPUT_PUBLISH_IMAGE" == 'true' ]]; then - SERVICE_SUFFIX='' - if [[ "$INPUT_SERVICE" == 'rocketchat' && "$INPUT_TYPE" == 'coverage' ]] && [[ "$GITHUB_EVENT_NAME" == 'release' || "$GITHUB_REF" == 'refs/heads/develop' ]]; then - SERVICE_SUFFIX='-cov' - elif [[ "$INPUT_TYPE" == 'fips' ]]; then - SERVICE_SUFFIX='-fips' - fi + SERVICE_SUFFIX="$SERVICE_SUFFIX_FROM_CONTEXT" mkdir -p "/tmp/manifests/${INPUT_SERVICE}${SERVICE_SUFFIX}/${INPUT_ARCH}" @@ -225,26 +219,26 @@ runs: if: inputs.publish-image == 'false' && inputs.arch == 'amd64' shell: bash env: - SERVICE: ${{ inputs.service }} - ARCH: ${{ inputs.arch }} - TYPE: ${{ inputs.type }} + INPUT_TYPE: ${{ inputs.type }} + INPUT_SERVICE: ${{ inputs.service }} + INPUT_ARCH: ${{ inputs.arch }} run: | set -o xtrace compose_files=(-f docker-compose-ci.yml) - if [[ "$TYPE" == 'fips' ]]; then + if [[ "$INPUT_TYPE" == 'fips' ]]; then compose_files+=(-f docker-compose-ci.fips.yml) fi # Get image name from compose config - IMAGE=$(docker compose "${compose_files[@]}" config --format json 2>/dev/null | jq -r --arg s "$SERVICE" '.services[$s].image') + IMAGE=$(docker compose "${compose_files[@]}" config --format json 2>/dev/null | jq -r --arg s "$INPUT_SERVICE" '.services[$s].image') # Create directory for image archives mkdir -p /tmp/docker-images # Save the image to a tar file - docker save "${IMAGE}" -o "/tmp/docker-images/${SERVICE}-${ARCH}-${TYPE}.tar" + docker save "${IMAGE}" -o "/tmp/docker-images/${INPUT_SERVICE}-${INPUT_ARCH}-${INPUT_TYPE}.tar" - echo "Saved image to /tmp/docker-images/${SERVICE}-${ARCH}-${TYPE}.tar" + echo "Saved image to /tmp/docker-images/${INPUT_SERVICE}-${INPUT_ARCH}-${INPUT_TYPE}.tar" ls -lh /tmp/docker-images/ - name: Upload Docker image artifact diff --git a/.github/workflows/ci-test-e2e.yml b/.github/workflows/ci-test-e2e.yml index e463a981c4150..7d3dddfb04ce6 100644 --- a/.github/workflows/ci-test-e2e.yml +++ b/.github/workflows/ci-test-e2e.yml @@ -101,8 +101,10 @@ jobs: steps: - name: Set compose files + env: + INPUT_RELEASE: ${{ inputs.release }} run: | - if [[ '${{ inputs.release }}' == 'fips' ]]; then + if [[ "$INPUT_RELEASE" == 'fips' ]]; then echo 'COMPOSE_FILES=-f docker-compose-ci.yml -f docker-compose-ci.fips.yml' >> "$GITHUB_ENV" echo 'COMPOSE_FILES_METEOR=-f ../../docker-compose-ci.yml -f ../../docker-compose-ci.fips.yml' >> "$GITHUB_ENV" else @@ -395,6 +397,8 @@ jobs: - name: E2E Test UI (${{ matrix.shard }}/${{ inputs.total-shard }}) if: inputs.type == 'ui' env: + E2E_SHARD: ${{ matrix.shard }} + E2E_TOTAL_SHARD: ${{ inputs.total-shard }} E2E_COVERAGE: ${{ inputs.coverage == matrix.mongodb-version && 'true' || '' }} IS_EE: ${{ (inputs.release == 'ee' || inputs.release == 'fips') && 'true' || '' }} REPORTER_ROCKETCHAT_API_KEY: ${{ secrets.REPORTER_ROCKETCHAT_API_KEY }} @@ -412,8 +416,6 @@ jobs: QASE_REPORT: ${{ github.ref == 'refs/heads/develop' && 'true' || '' }} CI: true PLAYWRIGHT_RETRIES: ${{ inputs.retries }} - E2E_SHARD: ${{ matrix.shard }} - E2E_TOTAL_SHARD: ${{ inputs.total-shard }} working-directory: ./apps/meteor run: | set -o xtrace From fa38620380e03ae1e28272ca3d27c8863265134d Mon Sep 17 00:00:00 2001 From: Matheus Cardoso Date: Mon, 16 Mar 2026 15:43:34 -0300 Subject: [PATCH 43/70] chore: reduce logs --- ee/apps/account-service/src/fips.ts | 2 -- ee/apps/authorization-service/src/fips.ts | 2 -- ee/apps/ddp-streamer/src/fips.ts | 4 ---- ee/apps/omnichannel-transcript/src/fips.ts | 2 -- ee/apps/presence-service/src/fips.ts | 2 -- ee/apps/queue-worker/src/fips.ts | 2 -- 6 files changed, 14 deletions(-) diff --git a/ee/apps/account-service/src/fips.ts b/ee/apps/account-service/src/fips.ts index 60cf98ffbb3d4..fab82191a20c8 100644 --- a/ee/apps/account-service/src/fips.ts +++ b/ee/apps/account-service/src/fips.ts @@ -6,6 +6,4 @@ if (!crypto.getFips()) { throw new Error('FIPS mode was not enabled after crypto.setFips(true)'); } -console.log('========================================='); console.log('FIPS COMPLIANCE CHECK: YES'); -console.log('========================================='); diff --git a/ee/apps/authorization-service/src/fips.ts b/ee/apps/authorization-service/src/fips.ts index 60cf98ffbb3d4..fab82191a20c8 100644 --- a/ee/apps/authorization-service/src/fips.ts +++ b/ee/apps/authorization-service/src/fips.ts @@ -6,6 +6,4 @@ if (!crypto.getFips()) { throw new Error('FIPS mode was not enabled after crypto.setFips(true)'); } -console.log('========================================='); console.log('FIPS COMPLIANCE CHECK: YES'); -console.log('========================================='); diff --git a/ee/apps/ddp-streamer/src/fips.ts b/ee/apps/ddp-streamer/src/fips.ts index 087647ba557e9..37651fc8bf1a6 100644 --- a/ee/apps/ddp-streamer/src/fips.ts +++ b/ee/apps/ddp-streamer/src/fips.ts @@ -22,11 +22,7 @@ if (!crypto.getFips()) { throw new Error('FIPS mode was not enabled after crypto.setFips(true)'); } -console.log('========================================='); console.log('FIPS COMPLIANCE CHECK: YES'); -console.log('========================================='); - -console.log('🔒 FIPS 140-3 mode detected. Applying WebSocket Handshake Patch...'); const blocks = new Uint32Array(32); const w = new Uint32Array(80); diff --git a/ee/apps/omnichannel-transcript/src/fips.ts b/ee/apps/omnichannel-transcript/src/fips.ts index 60cf98ffbb3d4..fab82191a20c8 100644 --- a/ee/apps/omnichannel-transcript/src/fips.ts +++ b/ee/apps/omnichannel-transcript/src/fips.ts @@ -6,6 +6,4 @@ if (!crypto.getFips()) { throw new Error('FIPS mode was not enabled after crypto.setFips(true)'); } -console.log('========================================='); console.log('FIPS COMPLIANCE CHECK: YES'); -console.log('========================================='); diff --git a/ee/apps/presence-service/src/fips.ts b/ee/apps/presence-service/src/fips.ts index 60cf98ffbb3d4..fab82191a20c8 100644 --- a/ee/apps/presence-service/src/fips.ts +++ b/ee/apps/presence-service/src/fips.ts @@ -6,6 +6,4 @@ if (!crypto.getFips()) { throw new Error('FIPS mode was not enabled after crypto.setFips(true)'); } -console.log('========================================='); console.log('FIPS COMPLIANCE CHECK: YES'); -console.log('========================================='); diff --git a/ee/apps/queue-worker/src/fips.ts b/ee/apps/queue-worker/src/fips.ts index 60cf98ffbb3d4..fab82191a20c8 100644 --- a/ee/apps/queue-worker/src/fips.ts +++ b/ee/apps/queue-worker/src/fips.ts @@ -6,6 +6,4 @@ if (!crypto.getFips()) { throw new Error('FIPS mode was not enabled after crypto.setFips(true)'); } -console.log('========================================='); console.log('FIPS COMPLIANCE CHECK: YES'); -console.log('========================================='); From 46ea026002b38adb5a31fead6b680fac11b2d1dc Mon Sep 17 00:00:00 2001 From: Matheus Cardoso Date: Mon, 16 Mar 2026 15:50:01 -0300 Subject: [PATCH 44/70] chore: use --force-fips --- ee/apps/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ee/apps/Dockerfile b/ee/apps/Dockerfile index 9d0d6c756be01..4b0e0a8603e9f 100644 --- a/ee/apps/Dockerfile +++ b/ee/apps/Dockerfile @@ -85,4 +85,4 @@ COPY --chown=node:node --from=builder /app /app WORKDIR /app/ee/apps/${SERVICE} USER node EXPOSE 3000 9458 -CMD ["node", "--require", "./src/fips.js", "src/service.js"] +CMD ["node", "--force-fips", "--require", "./src/fips.js", "src/service.js"] From 0d12e4a2f30b116db4dbecaa9908257e289c364e Mon Sep 17 00:00:00 2001 From: Matheus Cardoso Date: Tue, 17 Mar 2026 10:09:43 -0300 Subject: [PATCH 45/70] chore: reduce diff --- .github/workflows/ci-test-e2e.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci-test-e2e.yml b/.github/workflows/ci-test-e2e.yml index 7d3dddfb04ce6..a929c82dc4178 100644 --- a/.github/workflows/ci-test-e2e.yml +++ b/.github/workflows/ci-test-e2e.yml @@ -397,8 +397,6 @@ jobs: - name: E2E Test UI (${{ matrix.shard }}/${{ inputs.total-shard }}) if: inputs.type == 'ui' env: - E2E_SHARD: ${{ matrix.shard }} - E2E_TOTAL_SHARD: ${{ inputs.total-shard }} E2E_COVERAGE: ${{ inputs.coverage == matrix.mongodb-version && 'true' || '' }} IS_EE: ${{ (inputs.release == 'ee' || inputs.release == 'fips') && 'true' || '' }} REPORTER_ROCKETCHAT_API_KEY: ${{ secrets.REPORTER_ROCKETCHAT_API_KEY }} @@ -416,6 +414,8 @@ jobs: QASE_REPORT: ${{ github.ref == 'refs/heads/develop' && 'true' || '' }} CI: true PLAYWRIGHT_RETRIES: ${{ inputs.retries }} + E2E_SHARD: ${{ matrix.shard }} + E2E_TOTAL_SHARD: ${{ inputs.total-shard }} working-directory: ./apps/meteor run: | set -o xtrace From d65ca1b9e02a1625e3434585eec67aebd2edb76d Mon Sep 17 00:00:00 2001 From: Matheus Cardoso Date: Fri, 27 Mar 2026 10:47:13 -0300 Subject: [PATCH 46/70] feat(ddp-streamer): use alpine for fips --- ee/apps/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ee/apps/Dockerfile b/ee/apps/Dockerfile index 4b0e0a8603e9f..f0bc4862ee563 100644 --- a/ee/apps/Dockerfile +++ b/ee/apps/Dockerfile @@ -77,7 +77,7 @@ EXPOSE 3000 9458 CMD ["node", "src/service.js"] -FROM rocketchatfips140/dhi-node:22-alpine3.23 AS release-fips +FROM rocketchatfips140/dhi-node:22.22.2-alpine3.23-fips AS release-fips ARG SERVICE ENV NODE_ENV=production \ PORT=3000 From d41dedd256502afb2212216669e9f3df065808a4 Mon Sep 17 00:00:00 2001 From: Matheus Cardoso Date: Mon, 30 Mar 2026 12:43:19 -0300 Subject: [PATCH 47/70] fix(ci): skip publish of rocketchat-cov --- .github/workflows/ci.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cd2083aca3ae8..5b689c6f0cd24 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1153,6 +1153,11 @@ jobs: [[ -d "$service_dir" ]] || continue service="$(basename "$service_dir")" + if [[ "$service" == 'rocketchat-cov' ]]; then + echo "Skipping $service for DockerHub publish" + continue + fi + echo "Promoting $service" if [[ "${service}" == 'rocketchat' ]]; then From b6a6d939a43b3e6f91e551e0f445d177a5892cb6 Mon Sep 17 00:00:00 2001 From: Matheus Cardoso Date: Mon, 30 Mar 2026 12:45:01 -0300 Subject: [PATCH 48/70] ci: fix retry loop --- .github/workflows/ci-test-e2e.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci-test-e2e.yml b/.github/workflows/ci-test-e2e.yml index a929c82dc4178..695e762b53d85 100644 --- a/.github/workflows/ci-test-e2e.yml +++ b/.github/workflows/ci-test-e2e.yml @@ -251,7 +251,7 @@ jobs: for attempt in $(seq 1 "$retries"); do local is_primary - is_primary=$(docker compose "${compose_files[@]}" exec -T mongo mongosh --quiet --eval "try { const hello = db.hello(); print((hello.isWritablePrimary || hello.ismaster) ? '1' : '0'); } catch (e) { print('0'); }" 2>/dev/null | tail -n1) + is_primary=$(docker compose "${compose_files[@]}" exec -T mongo mongosh --quiet --eval "try { const hello = db.hello(); print((hello.isWritablePrimary || hello.ismaster) ? '1' : '0'); } catch (e) { print('0'); }" 2>/dev/null | tail -n1 || true) if [[ "$is_primary" == '1' ]]; then echo "Mongo replica set primary is ready" From dd8e2473dc627618fe5c235112825daf5b6e4596 Mon Sep 17 00:00:00 2001 From: Matheus Cardoso Date: Thu, 7 May 2026 14:43:07 -0300 Subject: [PATCH 49/70] ci(fips): build and publish rocketchat-fips image with temporary production artifact mapping --- .github/actions/build-docker/action.yml | 4 +++- .github/workflows/ci.yml | 2 +- apps/meteor/.docker/Dockerfile.alpine | 6 +++++- docker-compose-ci.fips.yml | 5 +++++ docker-compose-ci.yml | 1 + ee/packages/federation-matrix/docker-compose.test.yml | 1 + 6 files changed, 16 insertions(+), 3 deletions(-) diff --git a/.github/actions/build-docker/action.yml b/.github/actions/build-docker/action.yml index 8ecb0f11f6ab9..fcda118dabd5a 100644 --- a/.github/actions/build-docker/action.yml +++ b/.github/actions/build-docker/action.yml @@ -59,7 +59,9 @@ runs: if: inputs.service == 'rocketchat' uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0 with: - name: build-${{ inputs.type }} + # Temporary: rocketchat fips reuses existing Meteor artifacts until build-fips exists. + # PR/merge queue runs only produce build-coverage, while release/develop produce build-production. + name: build-${{ inputs.type == 'fips' && ((github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production') || inputs.type }} path: /tmp/build - name: Unpack meteor build diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5b689c6f0cd24..b947c764ca29c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -26,7 +26,7 @@ env: DOCKER_BUILD_ARCHES_JSON: '["arm64","amd64"]' DOCKER_BUILD_SERVICES_JSON: '["authorization-service","queue-worker-service","ddp-streamer-service","account-service","presence-service","omnichannel-transcript-service","rocketchat"]' DOCKER_BUILD_EXTRA_COVERAGE_JSON: '[{"arch":"amd64","service":"rocketchat","type":"coverage"},{"arch":"arm64","service":"rocketchat","type":"coverage"}]' - DOCKER_BUILD_FIPS_SERVICES_JSON: '["authorization-service","queue-worker-service","ddp-streamer-service","account-service","presence-service","omnichannel-transcript-service"]' + DOCKER_BUILD_FIPS_SERVICES_JSON: '["authorization-service","queue-worker-service","ddp-streamer-service","account-service","presence-service","omnichannel-transcript-service","rocketchat"]' permissions: contents: read diff --git a/apps/meteor/.docker/Dockerfile.alpine b/apps/meteor/.docker/Dockerfile.alpine index 2bee7b45f36e3..3f2c3a277b246 100644 --- a/apps/meteor/.docker/Dockerfile.alpine +++ b/apps/meteor/.docker/Dockerfile.alpine @@ -20,7 +20,7 @@ RUN cd /app/bundle/programs/server \ && find /app/bundle/programs/server/npm/node_modules -type f -name '*.map' -delete \ && find /app/bundle/programs/web.browser -type f -name '*.map' -delete -FROM node:22.22.3-alpine3.23 +FROM node:22.22.3-alpine3.23 AS release-standard LABEL maintainer="buildmaster@rocket.chat" @@ -65,3 +65,7 @@ WORKDIR /app/bundle EXPOSE 3000 CMD ["node", "main.js"] + +FROM release-standard AS release-fips + +CMD ["node", "main.js"] diff --git a/docker-compose-ci.fips.yml b/docker-compose-ci.fips.yml index 69bafb63e434f..bd3f55c510edb 100644 --- a/docker-compose-ci.fips.yml +++ b/docker-compose-ci.fips.yml @@ -1,4 +1,9 @@ services: + rocketchat: + build: + target: release-fips + image: ghcr.io/${LOWERCASE_REPOSITORY}/rocket.chat:${DOCKER_TAG}-fips + account-service: build: target: release-fips diff --git a/docker-compose-ci.yml b/docker-compose-ci.yml index 8ad3162d7a5aa..5882d9829f2bf 100644 --- a/docker-compose-ci.yml +++ b/docker-compose-ci.yml @@ -5,6 +5,7 @@ services: build: dockerfile: ${GITHUB_WORKSPACE:-}/apps/meteor/.docker/Dockerfile.alpine context: /tmp/build + target: release-standard x-bake: platforms: - linux/amd64 diff --git a/ee/packages/federation-matrix/docker-compose.test.yml b/ee/packages/federation-matrix/docker-compose.test.yml index ad06faeb0ef38..4c9fc6bf44390 100644 --- a/ee/packages/federation-matrix/docker-compose.test.yml +++ b/ee/packages/federation-matrix/docker-compose.test.yml @@ -82,6 +82,7 @@ services: build: context: ${ROCKETCHAT_BUILD_CONTEXT:-./test/dist} dockerfile: ${ROCKETCHAT_DOCKERFILE:-../../../apps/meteor/.docker/Dockerfile.alpine} + target: release-standard image: ${ROCKETCHAT_IMAGE:-rocketchat/rocket.chat:latest} profiles: - test From 75af69934904c016aaab2b49fa70ebae2135daa8 Mon Sep 17 00:00:00 2001 From: Matheus Cardoso Date: Mon, 11 May 2026 10:24:20 -0300 Subject: [PATCH 50/70] ci: hash pin docker/login-action --- .github/actions/build-docker/action.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/actions/build-docker/action.yml b/.github/actions/build-docker/action.yml index fcda118dabd5a..70f925c1fec20 100644 --- a/.github/actions/build-docker/action.yml +++ b/.github/actions/build-docker/action.yml @@ -51,7 +51,7 @@ runs: - name: Login to DockerHub for FIPS base images if: inputs.type == 'fips' && inputs.TEMP_DOCKERHUB_FIPS_USER != '' && inputs.TEMP_DOCKERHUB_FIPS_PASS != '' - uses: docker/login-action@v3 + uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0 with: username: ${{ inputs.TEMP_DOCKERHUB_FIPS_USER }} password: ${{ inputs.TEMP_DOCKERHUB_FIPS_PASS }} From cb34817dccb119903675f31b1b19b46dc6544d09 Mon Sep 17 00:00:00 2001 From: Matheus Cardoso Date: Mon, 11 May 2026 14:19:44 -0300 Subject: [PATCH 51/70] fix(fips): remove temporary DockerHub org --- .github/actions/build-docker/action.yml | 15 ++++++++------- .github/workflows/ci-test-e2e.yml | 13 ------------- .github/workflows/ci.yml | 10 ++-------- ee/apps/Dockerfile | 2 +- 4 files changed, 11 insertions(+), 29 deletions(-) diff --git a/.github/actions/build-docker/action.yml b/.github/actions/build-docker/action.yml index 70f925c1fec20..992c1d1f9d530 100644 --- a/.github/actions/build-docker/action.yml +++ b/.github/actions/build-docker/action.yml @@ -8,12 +8,12 @@ inputs: CR_PAT: required: true description: 'GitHub Container Registry Personal Access Token' - TEMP_DOCKERHUB_FIPS_USER: + DOCKER_USER: required: false - description: 'Temporary DockerHub user for FIPS base image pulls' - TEMP_DOCKERHUB_FIPS_PASS: + description: 'DockerHub username for private base image pulls' + DOCKER_PASS: required: false - description: 'Temporary DockerHub password/token for FIPS base image pulls' + description: 'DockerHub password/token for private base image pulls' deno-version: required: true description: 'Deno version' @@ -50,11 +50,12 @@ runs: password: ${{ inputs.CR_PAT }} - name: Login to DockerHub for FIPS base images - if: inputs.type == 'fips' && inputs.TEMP_DOCKERHUB_FIPS_USER != '' && inputs.TEMP_DOCKERHUB_FIPS_PASS != '' + if: inputs.type == 'fips' && inputs.DOCKER_USER != '' && inputs.DOCKER_PASS != '' uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0 with: - username: ${{ inputs.TEMP_DOCKERHUB_FIPS_USER }} - password: ${{ inputs.TEMP_DOCKERHUB_FIPS_PASS }} + username: ${{ inputs.DOCKER_USER }} + password: ${{ inputs.DOCKER_PASS }} + - name: Restore meteor build if: inputs.service == 'rocketchat' uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0 diff --git a/.github/workflows/ci-test-e2e.yml b/.github/workflows/ci-test-e2e.yml index 695e762b53d85..3ae539c77de7d 100644 --- a/.github/workflows/ci-test-e2e.yml +++ b/.github/workflows/ci-test-e2e.yml @@ -49,10 +49,6 @@ on: required: true CR_PAT: required: true - TEMP_DOCKERHUB_FIPS_USER: - required: false - TEMP_DOCKERHUB_FIPS_PASS: - required: false QASE_API_TOKEN: required: false REPORTER_ROCKETCHAT_URL: @@ -88,8 +84,6 @@ jobs: COVERAGE_DIR: '/tmp/coverage/${{ startsWith(inputs.type, ''api'') && ''api'' || inputs.type }}' COVERAGE_FILE_NAME: '${{ inputs.type }}-${{ matrix.shard }}.json' COVERAGE_REPORTER: ${{ inputs.coverage == matrix.mongodb-version && 'json' || '' }} - TEMP_DOCKERHUB_FIPS_USER: ${{ secrets.TEMP_DOCKERHUB_FIPS_USER }} - TEMP_DOCKERHUB_FIPS_PASS: ${{ secrets.TEMP_DOCKERHUB_FIPS_PASS }} strategy: fail-fast: false @@ -137,13 +131,6 @@ jobs: username: ${{ secrets.CR_USER }} password: ${{ secrets.CR_PAT }} - - name: Login to DockerHub for FIPS base images - if: inputs.release == 'fips' && env.TEMP_DOCKERHUB_FIPS_USER != '' && env.TEMP_DOCKERHUB_FIPS_PASS != '' - uses: docker/login-action@v3 - with: - username: ${{ secrets.TEMP_DOCKERHUB_FIPS_USER }} - password: ${{ secrets.TEMP_DOCKERHUB_FIPS_PASS }} - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - name: Setup NodeJS diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b947c764ca29c..6d3327b529c38 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -411,8 +411,8 @@ jobs: with: CR_USER: ${{ secrets.CR_USER }} CR_PAT: ${{ secrets.CR_PAT }} - TEMP_DOCKERHUB_FIPS_USER: ${{ secrets.TEMP_DOCKERHUB_FIPS_USER }} - TEMP_DOCKERHUB_FIPS_PASS: ${{ secrets.TEMP_DOCKERHUB_FIPS_PASS }} + DOCKER_USER: ${{ secrets.DOCKER_USER }} + DOCKER_PASS: ${{ secrets.DOCKER_PASS }} deno-version: ${{ needs.release-versions.outputs.deno-version }} arch: ${{ matrix.arch }} service: ${{ matrix.service }} @@ -751,8 +751,6 @@ jobs: secrets: CR_USER: ${{ secrets.CR_USER }} CR_PAT: ${{ secrets.CR_PAT }} - TEMP_DOCKERHUB_FIPS_USER: ${{ secrets.TEMP_DOCKERHUB_FIPS_USER }} - TEMP_DOCKERHUB_FIPS_PASS: ${{ secrets.TEMP_DOCKERHUB_FIPS_PASS }} test-api-livechat-fips: name: 🔨 Test API Livechat (FIPS) @@ -772,8 +770,6 @@ jobs: secrets: CR_USER: ${{ secrets.CR_USER }} CR_PAT: ${{ secrets.CR_PAT }} - TEMP_DOCKERHUB_FIPS_USER: ${{ secrets.TEMP_DOCKERHUB_FIPS_USER }} - TEMP_DOCKERHUB_FIPS_PASS: ${{ secrets.TEMP_DOCKERHUB_FIPS_PASS }} test-ui-fips: name: 🔨 Test UI (FIPS) @@ -796,8 +792,6 @@ jobs: secrets: CR_USER: ${{ secrets.CR_USER }} CR_PAT: ${{ secrets.CR_PAT }} - TEMP_DOCKERHUB_FIPS_USER: ${{ secrets.TEMP_DOCKERHUB_FIPS_USER }} - TEMP_DOCKERHUB_FIPS_PASS: ${{ secrets.TEMP_DOCKERHUB_FIPS_PASS }} QASE_API_TOKEN: ${{ secrets.QASE_API_TOKEN }} REPORTER_ROCKETCHAT_API_KEY: ${{ secrets.REPORTER_ROCKETCHAT_API_KEY }} REPORTER_ROCKETCHAT_URL: ${{ secrets.REPORTER_ROCKETCHAT_URL }} diff --git a/ee/apps/Dockerfile b/ee/apps/Dockerfile index f0bc4862ee563..a804e4376d3de 100644 --- a/ee/apps/Dockerfile +++ b/ee/apps/Dockerfile @@ -77,7 +77,7 @@ EXPOSE 3000 9458 CMD ["node", "src/service.js"] -FROM rocketchatfips140/dhi-node:22.22.2-alpine3.23-fips AS release-fips +FROM rocketchat/dhi-node:22.22.2-alpine3.23-fips AS release-fips ARG SERVICE ENV NODE_ENV=production \ PORT=3000 From 9cf662becd869984ce9ef645f09a044d448d0868 Mon Sep 17 00:00:00 2001 From: Matheus Cardoso Date: Tue, 12 May 2026 16:21:48 -0300 Subject: [PATCH 52/70] feat: use fips base image for rocketchat service --- apps/meteor/.docker/Dockerfile.alpine | 23 ++++++++++++++++++++++- docker-compose-ci.fips.yml | 2 ++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/apps/meteor/.docker/Dockerfile.alpine b/apps/meteor/.docker/Dockerfile.alpine index 3f2c3a277b246..5e4638e7ae202 100644 --- a/apps/meteor/.docker/Dockerfile.alpine +++ b/apps/meteor/.docker/Dockerfile.alpine @@ -66,6 +66,27 @@ EXPOSE 3000 CMD ["node", "main.js"] -FROM release-standard AS release-fips +FROM rocketchat/dhi-node:22.22.2-alpine3.23-fips AS release-fips + +LABEL maintainer="buildmaster@rocket.chat" + +# needs a mongo instance - defaults to container linking with alias 'mongo' +ENV DEPLOY_METHOD=docker \ + NODE_ENV=production \ + MONGO_URL=mongodb://mongo:27017/rocketchat \ + HOME=/tmp \ + PORT=3000 \ + ROOT_URL=http://localhost:3000 \ + Accounts_AvatarStorePath=/app/uploads + +USER node + +COPY --from=builder --chown=node:node /app /app + +VOLUME /app/uploads + +WORKDIR /app/bundle + +EXPOSE 3000 CMD ["node", "main.js"] diff --git a/docker-compose-ci.fips.yml b/docker-compose-ci.fips.yml index bd3f55c510edb..22781fac4ed3b 100644 --- a/docker-compose-ci.fips.yml +++ b/docker-compose-ci.fips.yml @@ -3,6 +3,8 @@ services: build: target: release-fips image: ghcr.io/${LOWERCASE_REPOSITORY}/rocket.chat:${DOCKER_TAG}-fips + healthcheck: + test: ["CMD", "node", "-e", "require('http').get('http://127.0.0.1:3000/livez', (res) => { process.exit(res.statusCode === 200 ? 0 : 1); }).on('error', () => process.exit(1));"] account-service: build: From 20514b3a56b7ae8d2fff606917b28c4b8854897c Mon Sep 17 00:00:00 2001 From: Matheus Cardoso Date: Wed, 13 May 2026 13:59:50 -0300 Subject: [PATCH 53/70] feat: force fips in monolith --- apps/meteor/.docker/Dockerfile.alpine | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/meteor/.docker/Dockerfile.alpine b/apps/meteor/.docker/Dockerfile.alpine index 5e4638e7ae202..5d368081dc534 100644 --- a/apps/meteor/.docker/Dockerfile.alpine +++ b/apps/meteor/.docker/Dockerfile.alpine @@ -89,4 +89,4 @@ WORKDIR /app/bundle EXPOSE 3000 -CMD ["node", "main.js"] +CMD ["node", "--force-fips", "main.js"] From 35fc40b42452e28e4ab4ec12710d70fd279827aa Mon Sep 17 00:00:00 2001 From: Matheus Cardoso Date: Thu, 14 May 2026 12:23:25 -0300 Subject: [PATCH 54/70] feat: show fips mode in logs --- apps/meteor/server/startup/serverRunning.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/apps/meteor/server/startup/serverRunning.ts b/apps/meteor/server/startup/serverRunning.ts index aee622af81793..8114f8b062d97 100644 --- a/apps/meteor/server/startup/serverRunning.ts +++ b/apps/meteor/server/startup/serverRunning.ts @@ -1,3 +1,4 @@ +import crypto from 'node:crypto'; import fs from 'node:fs'; import path from 'node:path'; @@ -51,6 +52,8 @@ Meteor.startup(async () => { ` Process Port: ${process.env.PORT}`, ` Site URL: ${settings.get('Site_Url')}`, ` Hashed Site URL: ${License.getHashedWorkspaceUrl()}`, + ` OpenSSL Version: ${process.versions.openssl}`, + ` FIPS Provider: ${crypto.getFips() ? 'Enabled' : 'Disabled'}`, ]; if (Info.commit?.hash) { From 5a92423fdac48190b73b0e5de36ea54b376a1d7d Mon Sep 17 00:00:00 2001 From: Matheus Cardoso Date: Thu, 14 May 2026 13:44:19 -0300 Subject: [PATCH 55/70] feat: show fips mode in statistics --- apps/meteor/server/lib/statistics/lib/statistics.ts | 3 +++ packages/core-typings/src/IStats.ts | 1 + 2 files changed, 4 insertions(+) diff --git a/apps/meteor/server/lib/statistics/lib/statistics.ts b/apps/meteor/server/lib/statistics/lib/statistics.ts index ec1f6757cc7ff..9375693a80fbe 100644 --- a/apps/meteor/server/lib/statistics/lib/statistics.ts +++ b/apps/meteor/server/lib/statistics/lib/statistics.ts @@ -1,4 +1,5 @@ import { log } from 'console'; +import crypto from 'node:crypto'; import os from 'node:os'; import { Analytics, Team, VideoConf, Presence } from '@rocket.chat/core-services'; @@ -345,6 +346,8 @@ export const statistics = { platform: process.env.DEPLOY_PLATFORM || 'selfinstall', }; + statistics.fips = !!crypto.getFips(); + statistics.readReceiptsEnabled = settings.get('Message_Read_Receipt_Enabled'); statistics.readReceiptsDetailed = settings.get('Message_Read_Receipt_Store_Users'); diff --git a/packages/core-typings/src/IStats.ts b/packages/core-typings/src/IStats.ts index 37280c86b5cf0..939b3558c8d72 100644 --- a/packages/core-typings/src/IStats.ts +++ b/packages/core-typings/src/IStats.ts @@ -273,4 +273,5 @@ export interface IStats extends IRocketChatRecord { abacTotalAttributeValues?: number; abacRoomsEnrolled?: number; allowUnsafeQueryAndFieldsApiParamsEnabled?: boolean; + fips?: boolean; } From 1bd99251fe16ebae66beb0162a48105fb93b2731 Mon Sep 17 00:00:00 2001 From: Matheus Cardoso Date: Tue, 19 May 2026 08:41:18 -0300 Subject: [PATCH 56/70] chore(deps): update fips base images for node 22.22.3 --- apps/meteor/.docker/Dockerfile.alpine | 2 +- ee/apps/Dockerfile | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/meteor/.docker/Dockerfile.alpine b/apps/meteor/.docker/Dockerfile.alpine index 5d368081dc534..cb002e9b3756d 100644 --- a/apps/meteor/.docker/Dockerfile.alpine +++ b/apps/meteor/.docker/Dockerfile.alpine @@ -66,7 +66,7 @@ EXPOSE 3000 CMD ["node", "main.js"] -FROM rocketchat/dhi-node:22.22.2-alpine3.23-fips AS release-fips +FROM rocketchat/dhi-node:22.22.3-alpine3.23-fips AS release-fips LABEL maintainer="buildmaster@rocket.chat" diff --git a/ee/apps/Dockerfile b/ee/apps/Dockerfile index a804e4376d3de..6031ba004f81a 100644 --- a/ee/apps/Dockerfile +++ b/ee/apps/Dockerfile @@ -77,7 +77,7 @@ EXPOSE 3000 9458 CMD ["node", "src/service.js"] -FROM rocketchat/dhi-node:22.22.2-alpine3.23-fips AS release-fips +FROM rocketchat/dhi-node:22.22.3-alpine3.23-fips AS release-fips ARG SERVICE ENV NODE_ENV=production \ PORT=3000 From f948770eda3a5031e2f9a077bcb6f776fa0d33f3 Mon Sep 17 00:00:00 2001 From: Matheus Cardoso Date: Thu, 21 May 2026 14:33:43 -0300 Subject: [PATCH 57/70] fix!(fips): use sha256 instead of md5 for 2fa fingerprint --- apps/meteor/server/lib/2fa/code/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/meteor/server/lib/2fa/code/index.ts b/apps/meteor/server/lib/2fa/code/index.ts index e86a77b3178cb..6d924fb8e8cfe 100644 --- a/apps/meteor/server/lib/2fa/code/index.ts +++ b/apps/meteor/server/lib/2fa/code/index.ts @@ -60,7 +60,7 @@ export function getFingerprintFromConnection(connection: IMethodConnection): str clientAddress: connection.clientAddress, }); - return crypto.createHash('md5').update(data).digest('hex'); + return crypto.createHash('sha256').update(data).digest('hex'); } export function getRememberDate(from: Date = new Date()): Date | undefined { From fcaf3a43ca3e84a49c273fbe605de66845a75cf9 Mon Sep 17 00:00:00 2001 From: Matheus Cardoso Date: Thu, 21 May 2026 14:35:09 -0300 Subject: [PATCH 58/70] fix(fips): use sha256 for meteor method rate limiting --- apps/meteor/server/api/v1/misc.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/meteor/server/api/v1/misc.ts b/apps/meteor/server/api/v1/misc.ts index c71a6efdb607f..cf762279f59b3 100644 --- a/apps/meteor/server/api/v1/misc.ts +++ b/apps/meteor/server/api/v1/misc.ts @@ -647,7 +647,7 @@ API.v1.post( const connectionId = this.token || crypto - .createHash('md5') + .createHash('sha256') .update((this.requestIp ?? '') + this.user._id) .digest('hex'); @@ -708,7 +708,7 @@ API.v1.post( const connectionId = this.token || crypto - .createHash('md5') + .createHash('sha256') .update(this.requestIp ?? '') .digest('hex'); From b57b539031a2765701fbdf1f008b09d67d279435 Mon Sep 17 00:00:00 2001 From: Matheus Cardoso Date: Fri, 22 May 2026 11:10:45 -0300 Subject: [PATCH 59/70] fix(fips): install deno in monolith image --- apps/meteor/.docker/Dockerfile.alpine | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/meteor/.docker/Dockerfile.alpine b/apps/meteor/.docker/Dockerfile.alpine index cb002e9b3756d..333f2f026fbf8 100644 --- a/apps/meteor/.docker/Dockerfile.alpine +++ b/apps/meteor/.docker/Dockerfile.alpine @@ -66,7 +66,9 @@ EXPOSE 3000 CMD ["node", "main.js"] -FROM rocketchat/dhi-node:22.22.3-alpine3.23-fips AS release-fips +FROM rocketchat/dhi-node:22.22.3-alpine3.23-fips-dev AS release-fips + +RUN apk add --no-cache deno LABEL maintainer="buildmaster@rocket.chat" From 952002afbec704864879507504a72611e6ab5e6d Mon Sep 17 00:00:00 2001 From: Matheus Cardoso Date: Fri, 22 May 2026 12:59:05 -0300 Subject: [PATCH 60/70] fix(federation-matrix): change from md5 to sha256 for ETag for FIPS compliance --- ee/packages/federation-matrix/src/api/.well-known/server.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ee/packages/federation-matrix/src/api/.well-known/server.ts b/ee/packages/federation-matrix/src/api/.well-known/server.ts index 9bb991ab0d7e9..262afa5d3249e 100644 --- a/ee/packages/federation-matrix/src/api/.well-known/server.ts +++ b/ee/packages/federation-matrix/src/api/.well-known/server.ts @@ -32,7 +32,7 @@ export const getWellKnownRoutes = () => { async (c) => { const responseData = federationSDK.getWellKnownHostData(); - const etag = createHash('md5').update(JSON.stringify(responseData)).digest('hex'); + const etag = createHash('sha256').update(JSON.stringify(responseData)).digest('hex'); c.header('ETag', etag); c.header('Content-Type', 'application/json'); From 7c6bcef8474ce1953b691309762ab4f381dfc270 Mon Sep 17 00:00:00 2001 From: Matheus Cardoso Date: Fri, 22 May 2026 13:31:46 -0300 Subject: [PATCH 61/70] chore(fips): reduce ci diff --- .github/actions/build-docker/action.yml | 108 +++++++--------- .github/workflows/ci-test-e2e.yml | 145 +++------------------- .github/workflows/ci.yml | 156 ++++++++++++++++-------- 3 files changed, 162 insertions(+), 247 deletions(-) diff --git a/.github/actions/build-docker/action.yml b/.github/actions/build-docker/action.yml index 992c1d1f9d530..cbbba6bbe761e 100644 --- a/.github/actions/build-docker/action.yml +++ b/.github/actions/build-docker/action.yml @@ -10,10 +10,10 @@ inputs: description: 'GitHub Container Registry Personal Access Token' DOCKER_USER: required: false - description: 'DockerHub username for private base image pulls' + description: 'DockerHub username (required for FIPS builds to pull private base image)' DOCKER_PASS: required: false - description: 'DockerHub password/token for private base image pulls' + description: 'DockerHub password (required for FIPS builds to pull private base image)' deno-version: required: true description: 'Deno version' @@ -49,8 +49,9 @@ runs: username: ${{ inputs.CR_USER }} password: ${{ inputs.CR_PAT }} - - name: Login to DockerHub for FIPS base images - if: inputs.type == 'fips' && inputs.DOCKER_USER != '' && inputs.DOCKER_PASS != '' + - name: Login to DockerHub + # FIPS base image (rocketchat/dhi-node) lives in a private DockerHub repo and requires auth to pull. + if: inputs.type == 'fips' && github.actor != 'dependabot[bot]' && (github.event.pull_request.head.repo.full_name == github.repository || github.event_name == 'release' || github.ref == 'refs/heads/develop') uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0 with: username: ${{ inputs.DOCKER_USER }} @@ -115,23 +116,21 @@ runs: - name: Build Docker images shell: bash env: - INPUT_DENO_VERSION: ${{ inputs.deno-version }} + DENO_VERSION: ${{ inputs.deno-version }} INPUT_ARCH: ${{ inputs.arch }} INPUT_SERVICE: ${{ inputs.service }} INPUT_PUBLISH_IMAGE: ${{ inputs.publish-image }} INPUT_TYPE: ${{ inputs.type }} - GH_RUN_ID: ${{ github.run_id }} - GH_SERVER_URL: ${{ github.server_url }} - GH_REPOSITORY: ${{ github.repository }} - GH_EVENT_NAME: ${{ github.event_name }} - GH_REF: ${{ github.ref }} - SERVICE_SUFFIX_FROM_CONTEXT: ${{ (inputs.service == 'rocketchat' && inputs.type == 'coverage' && (github.event_name == 'release' || github.ref == 'refs/heads/develop')) && '-cov' || (inputs.type == 'fips' && '-fips' || '') }} + GITHUB_RUN_ID: ${{ github.run_id }} + GITHUB_SERVER_URL: ${{ github.server_url }} + GITHUB_REPOSITORY: ${{ github.repository }} + GITHUB_EVENT_NAME: ${{ github.event_name }} + GITHUB_REF: ${{ github.ref }} run: | set -o xtrace - export DENO_VERSION="$INPUT_DENO_VERSION" - compose_files=(-f docker-compose-ci.yml) + compose_fips_override='' if [[ "$INPUT_TYPE" == 'fips' ]]; then - compose_files+=(-f docker-compose-ci.fips.yml) + compose_fips_override='-f docker-compose-ci.fips.yml' fi # Removes unnecessary swc cores and sharp binaries to reduce image size @@ -156,51 +155,34 @@ runs: LOAD_OR_PUSH="--load" fi - export DOCKER_CLIENT_TIMEOUT=300 - export COMPOSE_HTTP_TIMEOUT=300 - - # Get image name from compose config since rocketchat image is different from service name (rocket.chat) - IMAGE=$(docker compose "${compose_files[@]}" config --format json 2>/dev/null | jq -r --arg s "$INPUT_SERVICE" '.services[$s].image') - - BUILD_RUN_URL="${GH_SERVER_URL}/${GH_REPOSITORY}/actions/runs/${GH_RUN_ID}" - BUILD_SOURCE_URL="${GH_SERVER_URL}/${GH_REPOSITORY}" - - buildx_bake_cmd=( - docker buildx bake - "${compose_files[@]}" - ${LOAD_OR_PUSH} - --allow=fs.read=/tmp/build - --set "*.tags+=${IMAGE}-gha-run-${GH_RUN_ID}" - --set "*.labels.org.opencontainers.image.description=Build run: ${BUILD_RUN_URL}" - --set "*.labels.org.opencontainers.image.source=${BUILD_SOURCE_URL}" - --set "*.platform=linux/${INPUT_ARCH}" - --set *.cache-from=type=gha - --set *.cache-to=type=gha,mode=max - --provenance=false - --sbom=false - --metadata-file "/tmp/meta.json" + # Get image name from docker-compose-ci.yml since rocketchat image is different from service name (rocket.chat) + IMAGE=$(docker compose -f docker-compose-ci.yml $compose_fips_override config --format json 2>/dev/null | jq -r --arg s "$INPUT_SERVICE" '.services[$s].image') + + docker buildx bake \ + -f docker-compose-ci.yml $compose_fips_override \ + ${LOAD_OR_PUSH} \ + --allow=fs.read=/tmp/build \ + --set "*.tags+=${IMAGE}-gha-run-${GITHUB_RUN_ID}" \ + --set "*.labels.org.opencontainers.image.description=Build run: ${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}" \ + --set "*.labels.org.opencontainers.image.source=${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}" \ + --set "*.platform=linux/${INPUT_ARCH}" \ + --set *.cache-from=type=gha \ + --set *.cache-to=type=gha,mode=max \ + --provenance=false \ + --sbom=false \ + --metadata-file "/tmp/meta.json" \ "$INPUT_SERVICE" - ) - - attempts=1 - max_attempts=3 - until "${buildx_bake_cmd[@]}"; do - if [[ "$INPUT_PUBLISH_IMAGE" != 'true' || $attempts -ge $max_attempts ]]; then - echo "docker buildx bake failed after ${attempts} attempt(s)." - exit 1 - fi - - attempts=$((attempts + 1)) - sleep_seconds=$((15 * attempts)) - echo "docker buildx bake failed (likely transient push error). Retrying in ${sleep_seconds}s... (attempt ${attempts}/${max_attempts})" - sleep "${sleep_seconds}" - done echo "Contents of /tmp/meta.json:" cat /tmp/meta.json if [[ "$INPUT_PUBLISH_IMAGE" == 'true' ]]; then - SERVICE_SUFFIX="$SERVICE_SUFFIX_FROM_CONTEXT" + SERVICE_SUFFIX='' + if [[ "$INPUT_SERVICE" == 'rocketchat' && "$INPUT_TYPE" == 'coverage' ]] && [[ "$GITHUB_EVENT_NAME" == 'release' || "$GITHUB_REF" == 'refs/heads/develop' ]]; then + SERVICE_SUFFIX='-cov' + elif [[ "$INPUT_TYPE" == 'fips' ]]; then + SERVICE_SUFFIX='-fips' + fi mkdir -p "/tmp/manifests/${INPUT_SERVICE}${SERVICE_SUFFIX}/${INPUT_ARCH}" @@ -222,26 +204,26 @@ runs: if: inputs.publish-image == 'false' && inputs.arch == 'amd64' shell: bash env: - INPUT_TYPE: ${{ inputs.type }} - INPUT_SERVICE: ${{ inputs.service }} - INPUT_ARCH: ${{ inputs.arch }} + SERVICE: ${{ inputs.service }} + ARCH: ${{ inputs.arch }} + TYPE: ${{ inputs.type }} run: | set -o xtrace - compose_files=(-f docker-compose-ci.yml) - if [[ "$INPUT_TYPE" == 'fips' ]]; then - compose_files+=(-f docker-compose-ci.fips.yml) + compose_fips_override='' + if [[ "$TYPE" == 'fips' ]]; then + compose_fips_override='-f docker-compose-ci.fips.yml' fi - # Get image name from compose config - IMAGE=$(docker compose "${compose_files[@]}" config --format json 2>/dev/null | jq -r --arg s "$INPUT_SERVICE" '.services[$s].image') + # Get image name from docker-compose-ci.yml + IMAGE=$(docker compose -f docker-compose-ci.yml $compose_fips_override config --format json 2>/dev/null | jq -r --arg s "$SERVICE" '.services[$s].image') # Create directory for image archives mkdir -p /tmp/docker-images # Save the image to a tar file - docker save "${IMAGE}" -o "/tmp/docker-images/${INPUT_SERVICE}-${INPUT_ARCH}-${INPUT_TYPE}.tar" + docker save "${IMAGE}" -o "/tmp/docker-images/${SERVICE}-${ARCH}-${TYPE}.tar" - echo "Saved image to /tmp/docker-images/${INPUT_SERVICE}-${INPUT_ARCH}-${INPUT_TYPE}.tar" + echo "Saved image to /tmp/docker-images/${SERVICE}-${ARCH}-${TYPE}.tar" ls -lh /tmp/docker-images/ - name: Upload Docker image artifact diff --git a/.github/workflows/ci-test-e2e.yml b/.github/workflows/ci-test-e2e.yml index 3ae539c77de7d..484b049988c96 100644 --- a/.github/workflows/ci-test-e2e.yml +++ b/.github/workflows/ci-test-e2e.yml @@ -94,18 +94,6 @@ jobs: name: MongoDB ${{ matrix.mongodb-version }}${{ inputs.coverage == matrix.mongodb-version && ' coverage' || '' }} (${{ matrix.shard }}/${{ inputs.total-shard }}) steps: - - name: Set compose files - env: - INPUT_RELEASE: ${{ inputs.release }} - run: | - if [[ "$INPUT_RELEASE" == 'fips' ]]; then - echo 'COMPOSE_FILES=-f docker-compose-ci.yml -f docker-compose-ci.fips.yml' >> "$GITHUB_ENV" - echo 'COMPOSE_FILES_METEOR=-f ../../docker-compose-ci.yml -f ../../docker-compose-ci.fips.yml' >> "$GITHUB_ENV" - else - echo 'COMPOSE_FILES=-f docker-compose-ci.yml' >> "$GITHUB_ENV" - echo 'COMPOSE_FILES_METEOR=-f ../../docker-compose-ci.yml' >> "$GITHUB_ENV" - fi - - name: Collect Workflow Telemetry if: inputs.type == 'perf' uses: catchpoint/workflow-telemetry-action@94c3c3d9567a0205de6da68a76c428ce4e769af1 # v2.0.0 @@ -151,7 +139,7 @@ jobs: uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 if: github.event.pull_request.head.repo.full_name != github.repository && github.event_name != 'release' && github.ref != 'refs/heads/develop' with: - pattern: ${{ inputs.release == 'ce' && 'docker-image-rocketchat-amd64-coverage' || (inputs.release == 'fips' && 'docker-image-*-amd64-*' || 'docker-image-*-amd64-coverage') }} + pattern: ${{ inputs.release == 'ce' && 'docker-image-rocketchat-amd64-coverage' || (inputs.release == 'fips' && 'docker-image-*-amd64-fips' || 'docker-image-*-amd64-coverage') }} path: /tmp/docker-images merge-multiple: true @@ -181,8 +169,7 @@ jobs: - name: Start httpbin container and wait for it to be ready if: startsWith(inputs.type, 'api') run: | - read -r -a compose_files <<< "$COMPOSE_FILES" - docker compose "${compose_files[@]}" up -d httpbin + docker compose -f docker-compose-ci.yml up -d httpbin - name: Prepare code coverage directory run: | @@ -191,12 +178,6 @@ jobs: mkdir -p "$COVERAGE_DIR" chmod 777 "$COVERAGE_DIR" - - name: Pull EE/FIPS images - if: (inputs.release == 'ee' || inputs.release == 'fips') && (github.event.pull_request.head.repo.full_name == github.repository || github.event_name == 'release' || github.ref == 'refs/heads/develop') - run: | - read -r -a compose_files <<< "$COMPOSE_FILES" - docker compose "${compose_files[@]}" pull - - name: Start containers for CE if: inputs.release == 'ce' env: @@ -208,8 +189,7 @@ jobs: TEST_MODE: ${{ startsWith(inputs.type, 'api') && 'api' || 'true' }} run: | # when we are testing CE, we only need to start the rocketchat container - read -r -a compose_files <<< "$COMPOSE_FILES" - DEBUG_LOG_LEVEL=${DEBUG_LOG_LEVEL:-0} docker compose "${compose_files[@]}" up -d rocketchat --wait + DEBUG_LOG_LEVEL=${DEBUG_LOG_LEVEL:-0} docker compose -f docker-compose-ci.yml up -d rocketchat --wait - name: Start containers for EE if: inputs.release == 'ee' || inputs.release == 'fips' @@ -219,9 +199,10 @@ jobs: COMPOSE_PROFILES: ${{ inputs.type == 'api' && 'api' || '' }} TEST_MODE: ${{ startsWith(inputs.type, 'api') && 'api' || 'true' }} APPS_ENGINE_RUNTIME_BACKEND: ${{ inputs.type == 'api-apps-node' && 'node' || '' }} + FIPS_OVERRIDE: ${{ inputs.release == 'fips' && '-f docker-compose-ci.fips.yml' || '' }} run: | - read -r -a compose_files <<< "$COMPOSE_FILES" - DEBUG_LOG_LEVEL=${DEBUG_LOG_LEVEL:-0} docker compose "${compose_files[@]}" up -d --wait --no-build + read -r -a fips_override <<< "$FIPS_OVERRIDE" + DEBUG_LOG_LEVEL=${DEBUG_LOG_LEVEL:-0} docker compose -f docker-compose-ci.yml "${fips_override[@]}" up -d --wait - uses: ./.github/actions/setup-playwright if: inputs.type == 'ui' @@ -229,103 +210,13 @@ jobs: - name: Wait services to start up if: inputs.release == 'ee' || inputs.release == 'fips' run: | - read -r -a compose_files <<< "$COMPOSE_FILES" docker ps - wait_for_mongo_primary() { - local retries=36 - local delay=5 - - for attempt in $(seq 1 "$retries"); do - local is_primary - is_primary=$(docker compose "${compose_files[@]}" exec -T mongo mongosh --quiet --eval "try { const hello = db.hello(); print((hello.isWritablePrimary || hello.ismaster) ? '1' : '0'); } catch (e) { print('0'); }" 2>/dev/null | tail -n1 || true) - - if [[ "$is_primary" == '1' ]]; then - echo "Mongo replica set primary is ready" - return 0 - fi - - echo "Waiting for Mongo primary (attempt ${attempt}/${retries})" - sleep "$delay" - done - - echo "Mongo primary was not ready in time" - docker compose "${compose_files[@]}" logs mongo - return 1 - } - - wait_for_service() { - local service="$1" - local retries=18 - local delay=10 - local broker_service_name="${service%-service}" - local ready_pattern="NetworkBroker started successfully|ServiceBroker with [0-9]+ service\(s\) started successfully|Service '${broker_service_name}' started\." - - for attempt in $(seq 1 "$retries"); do - local container_id - container_id=$(docker compose "${compose_files[@]}" ps -q "$service") - - if [[ -z "$container_id" ]]; then - echo "Service '$service' has no container ID" - docker compose "${compose_files[@]}" ps - return 1 - fi - - local container_state - container_state=$(docker inspect -f '{{.State.Status}}' "$container_id" 2>/dev/null || echo "unknown") - local health_state - health_state=$(docker inspect -f '{{if .State.Health}}{{.State.Health.Status}}{{else}}none{{end}}' "$container_id" 2>/dev/null || echo "unknown") - - if [[ "$container_state" != 'running' ]]; then - echo "Service '$service' is not running (state=$container_state)" - docker compose "${compose_files[@]}" logs "$service" - return 1 - fi - - if [[ "$health_state" == 'unhealthy' ]]; then - echo "Service '$service' is unhealthy" - docker compose "${compose_files[@]}" logs "$service" - return 1 - fi - - if [[ "$health_state" == 'healthy' ]]; then - echo "Service '$service' is healthy" - return 0 - fi - - if docker compose "${compose_files[@]}" logs "$service" | grep -Eq "$ready_pattern"; then - echo "Service '$service' is ready" - return 0 - fi - - echo "Waiting '$service' to start up (attempt ${attempt}/${retries})" - sleep "$delay" - done - - echo "Service '$service' did not become ready in time" - docker compose "${compose_files[@]}" logs "$service" - return 1 - } - - mapfile -t services_to_wait < <( - docker compose "${compose_files[@]}" ps --services --status running \ - | grep -- '-service$' \ - | sort - ) - - wait_for_mongo_primary - - if (( ${#services_to_wait[@]} == 0 )); then - echo "No running -service containers found to wait for" - docker compose "${compose_files[@]}" ps - exit 1 - fi - - echo "Waiting for services: ${services_to_wait[*]}" - - for service in "${services_to_wait[@]}"; do - wait_for_service "$service" - done + until docker compose -f docker-compose-ci.yml logs ddp-streamer-service | grep -q "NetworkBroker started successfully"; do + echo "Waiting 'ddp-streamer' to start up" + ((c++)) && ((c==10)) && docker compose -f docker-compose-ci.yml logs ddp-streamer-service && exit 1 + sleep 10 + done; - name: Remove unused Docker images run: docker system prune -af @@ -338,11 +229,10 @@ jobs: IS_EE: ${{ (inputs.release == 'ee' || inputs.release == 'fips') && 'true' || '' }} run: | set -o xtrace - read -r -a compose_files_meteor <<< "$COMPOSE_FILES_METEOR" npm run testapi || s=$? - docker compose "${compose_files_meteor[@]}" stop + docker compose -f ../../docker-compose-ci.yml stop ls -la "$COVERAGE_DIR" exit "${s:-0}" @@ -372,11 +262,10 @@ jobs: IS_EE: ${{ (inputs.release == 'ee' || inputs.release == 'fips') && 'true' || '' }} run: | set -o xtrace - read -r -a compose_files_meteor <<< "$COMPOSE_FILES_METEOR" npm run testapi:livechat || s=$? - docker compose "${compose_files_meteor[@]}" stop + docker compose -f ../../docker-compose-ci.yml stop ls -la "$COVERAGE_DIR" exit "${s:-0}" @@ -427,15 +316,11 @@ jobs: - name: Show server logs if E2E test failed if: failure() - run: | - read -r -a compose_files <<< "$COMPOSE_FILES" - docker compose "${compose_files[@]}" logs rocketchat authorization-service queue-worker-service ddp-streamer-service account-service presence-service omnichannel-transcript-service + run: docker compose -f docker-compose-ci.yml logs rocketchat authorization-service queue-worker-service ddp-streamer-service account-service presence-service omnichannel-transcript-service - name: Show mongo logs if E2E test failed if: failure() - run: | - read -r -a compose_files <<< "$COMPOSE_FILES" - docker compose "${compose_files[@]}" logs mongo + run: docker compose -f docker-compose-ci.yml logs mongo - name: Show traefik logs if E2E test failed if: failure() diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6d3327b529c38..abd7a7f385e90 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -23,10 +23,6 @@ concurrency: env: TOOL_NODE_FLAGS: ${{ vars.TOOL_NODE_FLAGS }} - DOCKER_BUILD_ARCHES_JSON: '["arm64","amd64"]' - DOCKER_BUILD_SERVICES_JSON: '["authorization-service","queue-worker-service","ddp-streamer-service","account-service","presence-service","omnichannel-transcript-service","rocketchat"]' - DOCKER_BUILD_EXTRA_COVERAGE_JSON: '[{"arch":"amd64","service":"rocketchat","type":"coverage"},{"arch":"arm64","service":"rocketchat","type":"coverage"}]' - DOCKER_BUILD_FIPS_SERVICES_JSON: '["authorization-service","queue-worker-service","ddp-streamer-service","account-service","presence-service","omnichannel-transcript-service","rocketchat"]' permissions: contents: read @@ -344,48 +340,9 @@ jobs: source-hash: ${{ needs.release-versions.outputs.packages-build-cache-key }}-${{ needs.release-versions.outputs.meteor-rc-cache-key }} type: ${{ matrix.type }} - build-gh-docker-matrix: - name: ⚙️ Build Docker Matrix - needs: [build] - runs-on: ubuntu-24.04-arm - outputs: - matrix: ${{ steps.generate.outputs.matrix }} - steps: - - id: generate - env: - DOCKER_BUILD_ARCHES_JSON: ${{ env.DOCKER_BUILD_ARCHES_JSON }} - DOCKER_BUILD_SERVICES_JSON: ${{ env.DOCKER_BUILD_SERVICES_JSON }} - DOCKER_BUILD_EXTRA_COVERAGE_JSON: ${{ env.DOCKER_BUILD_EXTRA_COVERAGE_JSON }} - DOCKER_BUILD_FIPS_SERVICES_JSON: ${{ env.DOCKER_BUILD_FIPS_SERVICES_JSON }} - run: | - node <<'NODE' - const fs = require('node:fs'); - - const baseType = '${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}'; - const arches = JSON.parse(process.env.DOCKER_BUILD_ARCHES_JSON); - const services = JSON.parse(process.env.DOCKER_BUILD_SERVICES_JSON); - const extraCoverageRows = JSON.parse(process.env.DOCKER_BUILD_EXTRA_COVERAGE_JSON); - const fipsServices = JSON.parse(process.env.DOCKER_BUILD_FIPS_SERVICES_JSON); - - const include = []; - for (const arch of arches) { - for (const service of services) { - include.push({ arch, service, type: baseType }); - } - } - - if (baseType === 'production') { - include.push(...extraCoverageRows); - } - - include.push(...fipsServices.map((service) => ({ arch: 'amd64', service, type: 'fips' }))); - - fs.appendFileSync(process.env.GITHUB_OUTPUT, `matrix=${JSON.stringify({ include })}\n`); - NODE - build-gh-docker: - name: 🚢 Build Docker (${{ matrix.service }}-${{ matrix.arch }}-${{ matrix.type }}) - needs: [build, build-gh-docker-matrix, release-versions] + name: 🚢 Build Docker + needs: [build, release-versions] runs-on: ubuntu-24.04${{ matrix.arch == 'arm64' && '-arm' || '' }} env: @@ -394,7 +351,47 @@ jobs: strategy: fail-fast: false - matrix: ${{ fromJSON(needs.build-gh-docker-matrix.outputs.matrix) }} + matrix: + arch: [arm64, amd64] + service: + [ + [authorization-service, queue-worker-service, ddp-streamer-service], + [account-service, presence-service, omnichannel-transcript-service], + [rocketchat], + ] + type: + # if running in a PR build with coverage + - ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }} + include: + # if not, build with coverage for tests + - arch: amd64 + service: [rocketchat] + type: coverage + - arch: arm64 + service: [rocketchat] + type: coverage + # FIPS images: amd64 only, includes all microservices + - arch: amd64 + service: [authorization-service] + type: fips + - arch: amd64 + service: [queue-worker-service] + type: fips + - arch: amd64 + service: [ddp-streamer-service] + type: fips + - arch: amd64 + service: [account-service] + type: fips + - arch: amd64 + service: [presence-service] + type: fips + - arch: amd64 + service: [omnichannel-transcript-service] + type: fips + - arch: amd64 + service: [rocketchat] + type: fips steps: - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 @@ -402,7 +399,7 @@ jobs: - uses: ./.github/actions/restore-packages # we only build and publish the actual docker images if not a PR from a fork - - name: Image ${{ matrix.service }} + - name: Image ${{ matrix.service[0] }} uses: ./.github/actions/build-docker if: github.actor != 'dependabot[bot]' env: @@ -415,9 +412,60 @@ jobs: DOCKER_PASS: ${{ secrets.DOCKER_PASS }} deno-version: ${{ needs.release-versions.outputs.deno-version }} arch: ${{ matrix.arch }} - service: ${{ matrix.service }} + service: ${{ matrix.service[0] }} + type: ${{ matrix.type }} + publish-image: ${{ github.event.pull_request.head.repo.full_name == github.repository || github.event_name == 'release' || github.ref == 'refs/heads/develop' }} + + - name: Image ${{ matrix.service[1] || '"skipped"' }} + uses: ./.github/actions/build-docker + if: matrix.service[1] && github.actor != 'dependabot[bot]' + env: + DOCKER_TAG_SUFFIX_ROCKETCHAT: ${{ matrix.type == 'coverage' && '-cov' || '' }} + with: + CR_USER: ${{ secrets.CR_USER }} + CR_PAT: ${{ secrets.CR_PAT }} + DOCKER_USER: ${{ secrets.DOCKER_USER }} + DOCKER_PASS: ${{ secrets.DOCKER_PASS }} + deno-version: ${{ needs.release-versions.outputs.deno-version }} + arch: ${{ matrix.arch }} + service: ${{ matrix.service[1] }} + type: ${{ matrix.type }} + publish-image: ${{ github.event.pull_request.head.repo.full_name == github.repository || github.event_name == 'release' || github.ref == 'refs/heads/develop' }} + setup-docker: false + + - name: Image ${{ matrix.service[2] || '"skipped"' }} + uses: ./.github/actions/build-docker + if: matrix.service[2] && github.actor != 'dependabot[bot]' + env: + DOCKER_TAG_SUFFIX_ROCKETCHAT: ${{ matrix.type == 'coverage' && '-cov' || '' }} + with: + CR_USER: ${{ secrets.CR_USER }} + CR_PAT: ${{ secrets.CR_PAT }} + DOCKER_USER: ${{ secrets.DOCKER_USER }} + DOCKER_PASS: ${{ secrets.DOCKER_PASS }} + deno-version: ${{ needs.release-versions.outputs.deno-version }} + arch: ${{ matrix.arch }} + service: ${{ matrix.service[2] }} + type: ${{ matrix.type }} + publish-image: ${{ github.event.pull_request.head.repo.full_name == github.repository || github.event_name == 'release' || github.ref == 'refs/heads/develop' }} + setup-docker: false + + - name: Image ${{ matrix.service[3] || '"skipped"' }} + uses: ./.github/actions/build-docker + if: matrix.service[3] && github.actor != 'dependabot[bot]' + env: + DOCKER_TAG_SUFFIX_ROCKETCHAT: ${{ matrix.type == 'coverage' && '-cov' || '' }} + with: + CR_USER: ${{ secrets.CR_USER }} + CR_PAT: ${{ secrets.CR_PAT }} + DOCKER_USER: ${{ secrets.DOCKER_USER }} + DOCKER_PASS: ${{ secrets.DOCKER_PASS }} + deno-version: ${{ needs.release-versions.outputs.deno-version }} + arch: ${{ matrix.arch }} + service: ${{ matrix.service[3] }} type: ${{ matrix.type }} publish-image: ${{ github.event.pull_request.head.repo.full_name == github.repository || github.event_name == 'release' || github.ref == 'refs/heads/develop' }} + setup-docker: false build-gh-docker-publish: name: 🚢 Publish Docker Images (ghcr.io) @@ -751,6 +799,7 @@ jobs: secrets: CR_USER: ${{ secrets.CR_USER }} CR_PAT: ${{ secrets.CR_PAT }} + NPM_TOKEN: ${{ secrets.NPM_TOKEN }} test-api-livechat-fips: name: 🔨 Test API Livechat (FIPS) @@ -770,6 +819,7 @@ jobs: secrets: CR_USER: ${{ secrets.CR_USER }} CR_PAT: ${{ secrets.CR_PAT }} + NPM_TOKEN: ${{ secrets.NPM_TOKEN }} test-ui-fips: name: 🔨 Test UI (FIPS) @@ -792,6 +842,7 @@ jobs: secrets: CR_USER: ${{ secrets.CR_USER }} CR_PAT: ${{ secrets.CR_PAT }} + NPM_TOKEN: ${{ secrets.NPM_TOKEN }} QASE_API_TOKEN: ${{ secrets.QASE_API_TOKEN }} REPORTER_ROCKETCHAT_API_KEY: ${{ secrets.REPORTER_ROCKETCHAT_API_KEY }} REPORTER_ROCKETCHAT_URL: ${{ secrets.REPORTER_ROCKETCHAT_URL }} @@ -1147,8 +1198,7 @@ jobs: [[ -d "$service_dir" ]] || continue service="$(basename "$service_dir")" - if [[ "$service" == 'rocketchat-cov' ]]; then - echo "Skipping $service for DockerHub publish" + if [ "$service" == "rocketchat-cov" ]; then continue fi @@ -1160,10 +1210,8 @@ jobs: IMAGE_NAME="${{ needs.release-versions.outputs.lowercase-repo }}/${service}" fi - # Get image name from compose config since rocketchat image is different from service name (rocket.chat) - if [ "$service" == "rocketchat-cov" ]; then - SRC=$(docker compose -f docker-compose-ci.yml config --format json 2>/dev/null | jq -r --arg s "rocketchat" '.services[$s].image')-cov - elif [[ "$service" == *"-fips" ]]; then + # Get image name from docker-compose-ci.yml since rocketchat image is different from service name (rocket.chat) + if [[ "$service" == *"-fips" ]]; then base_service="${service%-fips}" SRC=$(docker compose -f docker-compose-ci.yml -f docker-compose-ci.fips.yml config --format json 2>/dev/null | jq -r --arg s "$base_service" '.services[$s].image') else From 184496d6e98cf45d967d85a4cefed4ee7495cf31 Mon Sep 17 00:00:00 2001 From: Matheus Cardoso Date: Tue, 16 Jun 2026 10:37:17 -0300 Subject: [PATCH 62/70] refactor(ddp-streamer/fips): remove sha1 fallback --- ee/apps/ddp-streamer/src/fips.ts | 204 ------------------------------- 1 file changed, 204 deletions(-) diff --git a/ee/apps/ddp-streamer/src/fips.ts b/ee/apps/ddp-streamer/src/fips.ts index 37651fc8bf1a6..fab82191a20c8 100644 --- a/ee/apps/ddp-streamer/src/fips.ts +++ b/ee/apps/ddp-streamer/src/fips.ts @@ -1,19 +1,3 @@ -/** - * ============================================================================== - * SECURITY AUDIT EXEMPTION / FIPS 140-3 WORKAROUND - * ============================================================================== - * Context: - * Node.js running in FIPS 140-3 mode strictly disables native SHA-1 execution. - * However, RFC 6455 (WebSockets) strictly requires SHA-1 to generate the - * Sec-WebSocket-Accept handshake header. - * * Justification: - * The WebSocket protocol uses SHA-1 purely for framing/handshake validation, - * NOT for cryptographic security. To allow the 'ws' library to function without - * crashing the Node process, we intercept SHA-1 calls specifically for the - * 60-byte WebSocket handshake and process them using a highly-optimized, - * zero-allocation, pure-JS implementation. - * ============================================================================== - */ import crypto from 'crypto'; crypto.setFips(true); @@ -23,191 +7,3 @@ if (!crypto.getFips()) { } console.log('FIPS COMPLIANCE CHECK: YES'); - -const blocks = new Uint32Array(32); -const w = new Uint32Array(80); -const hashBuffer = Buffer.alloc(20); -const WEBSOCKET_GUID = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'; -const SEC_WEBSOCKET_KEY_BASE64_PATTERN = /^[A-Za-z0-9+/]{22}==$/; - -const generateWebSocketAccept = (message: string): string => { - if (message.length !== 60) { - throw new Error(`Expected 60-byte input for WS Accept, got ${message.length}`); - } - - blocks.fill(0); - - let h0 = 0x67452301; - let h1 = 0xefcdab89; - let h2 = 0x98badcfe; - let h3 = 0x10325476; - let h4 = 0xc3d2e1f0; - - for (let i = 0; i < 60; i++) blocks[i >> 2] |= message.charCodeAt(i) << (24 - (i % 4) * 8); - blocks[15] = 0x80000000; - blocks[31] = 480; - - const rotl = (n: number, b: number) => (n << b) | (n >>> (32 - b)); - - for (let chunk = 0; chunk < 2; chunk++) { - const offset = chunk * 16; - for (let i = 0; i < 16; i++) w[i] = blocks[offset + i]; - for (let i = 16; i < 80; i++) w[i] = rotl(w[i - 3] ^ w[i - 8] ^ w[i - 14] ^ w[i - 16], 1); - - let a = h0; - let b = h1; - let c = h2; - let d = h3; - let e = h4; - let temp; - - for (let i = 0; i < 20; i++) { - temp = (rotl(a, 5) + (d ^ (b & (c ^ d))) + e + 0x5a827999 + w[i]) >>> 0; - e = d; - d = c; - c = rotl(b, 30); - b = a; - a = temp; - } - for (let i = 20; i < 40; i++) { - temp = (rotl(a, 5) + (b ^ c ^ d) + e + 0x6ed9eba1 + w[i]) >>> 0; - e = d; - d = c; - c = rotl(b, 30); - b = a; - a = temp; - } - for (let i = 40; i < 60; i++) { - temp = (rotl(a, 5) + ((b & c) | (b & d) | (c & d)) + e + 0x8f1bbcdc + w[i]) >>> 0; - e = d; - d = c; - c = rotl(b, 30); - b = a; - a = temp; - } - for (let i = 60; i < 80; i++) { - temp = (rotl(a, 5) + (b ^ c ^ d) + e + 0xca62c1d6 + w[i]) >>> 0; - e = d; - d = c; - c = rotl(b, 30); - b = a; - a = temp; - } - - h0 = (h0 + a) >>> 0; - h1 = (h1 + b) >>> 0; - h2 = (h2 + c) >>> 0; - h3 = (h3 + d) >>> 0; - h4 = (h4 + e) >>> 0; - } - - hashBuffer.writeUInt32BE(h0, 0); - hashBuffer.writeUInt32BE(h1, 4); - hashBuffer.writeUInt32BE(h2, 8); - hashBuffer.writeUInt32BE(h3, 12); - hashBuffer.writeUInt32BE(h4, 16); - - return hashBuffer.toString('base64'); -}; - -const originalCreateHash = crypto.createHash; - -const createUnsupportedSha1MethodError = (method: string): Error => - new Error( - `Unsupported SHA-1 hash API in FIPS mode: crypto.createHash('sha1').${method}. ` + - `Only update() and digest('base64') for the WebSocket handshake are supported.`, - ); - -const createUnsupportedSha1Method = (method: string) => { - return () => { - throw createUnsupportedSha1MethodError(method); - }; -}; - -type Sha1UpdateCall = { type: 'string'; chunk: string; encoding?: BufferEncoding } | { type: 'buffer'; chunk: Buffer }; - -const isWebSocketHandshakeSha1Input = (input: string): boolean => { - if (input.length !== 60) { - return false; - } - - const secWebSocketKey = input.slice(0, 24); - const guid = input.slice(24); - - return guid === WEBSOCKET_GUID && SEC_WEBSOCKET_KEY_BASE64_PATTERN.test(secWebSocketKey); -}; - -crypto.createHash = function (algorithm: string, options?: crypto.HashOptions) { - if (algorithm.toLowerCase() === 'sha1') { - const updates: Sha1UpdateCall[] = []; - let wsProbeInput = ''; - let finalized = false; - - const mockHash = { - update(data: string | Buffer | NodeJS.ArrayBufferView, inputEncoding?: crypto.Encoding) { - if (finalized) { - throw new Error('Digest already called'); - } - - if (typeof data === 'string') { - const encoding = inputEncoding as BufferEncoding | undefined; - updates.push({ type: 'string', chunk: data, encoding }); - wsProbeInput += Buffer.from(data, encoding ?? 'utf8').toString('latin1'); - } else if (Buffer.isBuffer(data)) { - const chunk = Buffer.from(data); - updates.push({ type: 'buffer', chunk }); - wsProbeInput += chunk.toString('latin1'); - } else { - const chunk = Buffer.from(data.buffer, data.byteOffset, data.byteLength); - updates.push({ type: 'buffer', chunk: Buffer.from(chunk) }); - wsProbeInput += chunk.toString('latin1'); - } - return this; - }, - digest(encoding?: crypto.BinaryToTextEncoding) { - if (finalized) { - throw new Error('Digest already called'); - } - - finalized = true; - - if (encoding === 'base64' && isWebSocketHandshakeSha1Input(wsProbeInput)) { - return generateWebSocketAccept(wsProbeInput); - } - // If it's not the exact WS handshake, pass it back to native (which will throw FIPS error) - const hash = originalCreateHash(algorithm, options); - - for (const updateCall of updates) { - if (updateCall.type === 'string') { - if (updateCall.encoding) { - hash.update(updateCall.chunk, updateCall.encoding); - } else { - hash.update(updateCall.chunk); - } - } else { - hash.update(updateCall.chunk); - } - } - - return encoding ? hash.digest(encoding) : hash.digest(); - }, - copy: createUnsupportedSha1Method('copy'), - on: createUnsupportedSha1Method('on'), - once: createUnsupportedSha1Method('once'), - emit: createUnsupportedSha1Method('emit'), - pipe: createUnsupportedSha1Method('pipe'), - } as Record; - - const guardedHash = new Proxy(mockHash, { - get(target, prop, receiver) { - if (typeof prop === 'string' && !(prop in target)) { - throw createUnsupportedSha1MethodError(prop); - } - return Reflect.get(target, prop, receiver); - }, - }); - - return guardedHash as unknown as crypto.Hash; - } - return originalCreateHash(algorithm, options); -}; From 6e1e197d2b561f0741fd9b5166e7da8e9689ecc0 Mon Sep 17 00:00:00 2001 From: Matheus Cardoso Date: Mon, 13 Jul 2026 09:59:55 -0300 Subject: [PATCH 63/70] fix(fips): use node for apps engine --- apps/meteor/.docker/Dockerfile.alpine | 7 +++---- docker-compose-ci.fips.yml | 2 ++ 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/apps/meteor/.docker/Dockerfile.alpine b/apps/meteor/.docker/Dockerfile.alpine index 333f2f026fbf8..307c801f261a0 100644 --- a/apps/meteor/.docker/Dockerfile.alpine +++ b/apps/meteor/.docker/Dockerfile.alpine @@ -66,9 +66,7 @@ EXPOSE 3000 CMD ["node", "main.js"] -FROM rocketchat/dhi-node:22.22.3-alpine3.23-fips-dev AS release-fips - -RUN apk add --no-cache deno +FROM rocketchat/dhi-node:22.22.3-alpine3.23-fips AS release-fips LABEL maintainer="buildmaster@rocket.chat" @@ -79,7 +77,8 @@ ENV DEPLOY_METHOD=docker \ HOME=/tmp \ PORT=3000 \ ROOT_URL=http://localhost:3000 \ - Accounts_AvatarStorePath=/app/uploads + Accounts_AvatarStorePath=/app/uploads \ + APPS_ENGINE_RUNTIME_BACKEND=node USER node diff --git a/docker-compose-ci.fips.yml b/docker-compose-ci.fips.yml index 22781fac4ed3b..9d6c163069de7 100644 --- a/docker-compose-ci.fips.yml +++ b/docker-compose-ci.fips.yml @@ -3,6 +3,8 @@ services: build: target: release-fips image: ghcr.io/${LOWERCASE_REPOSITORY}/rocket.chat:${DOCKER_TAG}-fips + environment: + - APPS_ENGINE_RUNTIME_BACKEND=node healthcheck: test: ["CMD", "node", "-e", "require('http').get('http://127.0.0.1:3000/livez', (res) => { process.exit(res.statusCode === 200 ? 0 : 1); }).on('error', () => process.exit(1));"] From 5397df0ec208bdc52276460c0c9b3fdfcf287a70 Mon Sep 17 00:00:00 2001 From: Matheus Cardoso Date: Tue, 14 Jul 2026 07:56:55 -0300 Subject: [PATCH 64/70] feat(fips): add license validation --- .../server/lib/license/enforceFipsLicense.ts | 35 +++++++++++++++++++ apps/meteor/startRocketChat.ts | 3 ++ .../core-typings/src/license/LicenseModule.ts | 1 + 3 files changed, 39 insertions(+) create mode 100644 apps/meteor/ee/server/lib/license/enforceFipsLicense.ts diff --git a/apps/meteor/ee/server/lib/license/enforceFipsLicense.ts b/apps/meteor/ee/server/lib/license/enforceFipsLicense.ts new file mode 100644 index 0000000000000..bd54339262d62 --- /dev/null +++ b/apps/meteor/ee/server/lib/license/enforceFipsLicense.ts @@ -0,0 +1,35 @@ +import crypto from 'node:crypto'; + +import { License } from '@rocket.chat/license'; + +import { showErrorBox } from '../../../../server/lib/logger/showBox'; + +export function enforceFipsLicense(): void { + if (!crypto.getFips()) { + return; + } + + if (License.hasModule('fips')) { + return; + } + + const reason = !License.encryptedLicense + ? 'No license was found in this workspace' + : (!License.hasValidLicense() && 'The license applied to this workspace is invalid or expired') || + "The license applied to this workspace does not include the 'fips' module"; + + showErrorBox( + 'FIPS LICENSE ERROR', + [ + `${reason}.`, + '', + 'This server is running in FIPS mode, which requires a valid license', + "including the 'fips' module.", + '', + 'Provide your license through the ROCKETCHAT_LICENSE environment variable,', + 'or contact Rocket.Chat to obtain one: https://www.rocket.chat/sales-contact', + ].join('\n'), + ); + + process.exit(1); +} diff --git a/apps/meteor/startRocketChat.ts b/apps/meteor/startRocketChat.ts index d69bf8d0ebc38..196c37d54151b 100644 --- a/apps/meteor/startRocketChat.ts +++ b/apps/meteor/startRocketChat.ts @@ -1,4 +1,5 @@ import { registerEEBroker } from './ee/server'; +import { enforceFipsLicense } from './ee/server/lib/license/enforceFipsLicense'; import { startLicense } from './ee/server/lib/license/startup'; import { startFederationService as startFederationMatrixService } from './ee/server/startup/federation'; @@ -15,5 +16,7 @@ export const startRocketChat = async () => { await startLicense(); + enforceFipsLicense(); + await loadAfterLicense(); }; diff --git a/packages/core-typings/src/license/LicenseModule.ts b/packages/core-typings/src/license/LicenseModule.ts index 5267817164db6..eeb8914d05238 100644 --- a/packages/core-typings/src/license/LicenseModule.ts +++ b/packages/core-typings/src/license/LicenseModule.ts @@ -24,6 +24,7 @@ export const CoreModules = [ 'teams-voip', 'outbound-messaging', 'abac', + 'fips', ] as const; export type InternalModuleName = (typeof CoreModules)[number]; From 264b2cfe34a8594db38a601ddfcf7df162478a7d Mon Sep 17 00:00:00 2001 From: Matheus Cardoso Date: Thu, 16 Jul 2026 10:56:23 -0300 Subject: [PATCH 65/70] chore: add changeset --- .changeset/fips-mode-support.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 .changeset/fips-mode-support.md diff --git a/.changeset/fips-mode-support.md b/.changeset/fips-mode-support.md new file mode 100644 index 0000000000000..2d3415cbe7c42 --- /dev/null +++ b/.changeset/fips-mode-support.md @@ -0,0 +1,18 @@ +--- +'@rocket.chat/meteor': minor +'@rocket.chat/core-typings': minor +'@rocket.chat/license': minor +'@rocket.chat/federation-matrix': minor +'@rocket.chat/account-service': minor +'@rocket.chat/authorization-service': minor +'@rocket.chat/ddp-streamer': minor +'@rocket.chat/omnichannel-transcript': minor +'@rocket.chat/presence-service': minor +'@rocket.chat/queue-worker': minor +--- + +Adds support for running Rocket.Chat in FIPS mode. The monolith and all microservices (ddp-streamer, account-service, authorization-service, presence-service, queue-worker, omnichannel-transcript) can now enforce FIPS-compliant cryptography via Node.js/OpenSSL FIPS, with dedicated FIPS Docker images. Running in FIPS mode requires a license including the new `fips` module, and FIPS status is now reported in server logs and statistics. + +To be FIPS-compliant, some internal usages of MD5 were replaced with SHA-256: the 2FA remember-device fingerprint (existing remembered devices will be prompted for 2FA once more), rate-limiting connection identifiers, and the federation `.well-known` ETag. + +Additionally fixes the license provided via the `ROCKETCHAT_LICENSE` environment variable not being applied when it is newer than the one persisted in the workspace. From 7414b093298cab3ac7d86d3a0e92f37d68dd4732 Mon Sep 17 00:00:00 2001 From: Douglas Gubert Date: Thu, 16 Jul 2026 15:58:03 -0300 Subject: [PATCH 66/70] chore: refactor CI matrix strategy --- .github/workflows/ci.yml | 28 ++++------------------------ 1 file changed, 4 insertions(+), 24 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index abd7a7f385e90..26916f5cb3a02 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -360,38 +360,18 @@ jobs: [rocketchat], ] type: - # if running in a PR build with coverage - ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }} + - fips + exclude: + - arch: arm64 + type: fips include: - # if not, build with coverage for tests - arch: amd64 service: [rocketchat] type: coverage - arch: arm64 service: [rocketchat] type: coverage - # FIPS images: amd64 only, includes all microservices - - arch: amd64 - service: [authorization-service] - type: fips - - arch: amd64 - service: [queue-worker-service] - type: fips - - arch: amd64 - service: [ddp-streamer-service] - type: fips - - arch: amd64 - service: [account-service] - type: fips - - arch: amd64 - service: [presence-service] - type: fips - - arch: amd64 - service: [omnichannel-transcript-service] - type: fips - - arch: amd64 - service: [rocketchat] - type: fips steps: - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 From b0afcd9d4c77b4cb84d22c1ca667a65c1934eb38 Mon Sep 17 00:00:00 2001 From: Matheus Cardoso Date: Mon, 20 Jul 2026 07:14:19 -0300 Subject: [PATCH 67/70] chore: address some review comments --- .changeset/fips-mode-support.md | 2 -- .github/actions/build-docker/action.yml | 4 +--- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/.changeset/fips-mode-support.md b/.changeset/fips-mode-support.md index 2d3415cbe7c42..b1b6f29e63f0a 100644 --- a/.changeset/fips-mode-support.md +++ b/.changeset/fips-mode-support.md @@ -13,6 +13,4 @@ Adds support for running Rocket.Chat in FIPS mode. The monolith and all microservices (ddp-streamer, account-service, authorization-service, presence-service, queue-worker, omnichannel-transcript) can now enforce FIPS-compliant cryptography via Node.js/OpenSSL FIPS, with dedicated FIPS Docker images. Running in FIPS mode requires a license including the new `fips` module, and FIPS status is now reported in server logs and statistics. -To be FIPS-compliant, some internal usages of MD5 were replaced with SHA-256: the 2FA remember-device fingerprint (existing remembered devices will be prompted for 2FA once more), rate-limiting connection identifiers, and the federation `.well-known` ETag. - Additionally fixes the license provided via the `ROCKETCHAT_LICENSE` environment variable not being applied when it is newer than the one persisted in the workspace. diff --git a/.github/actions/build-docker/action.yml b/.github/actions/build-docker/action.yml index cbbba6bbe761e..fb3b5adf2ae70 100644 --- a/.github/actions/build-docker/action.yml +++ b/.github/actions/build-docker/action.yml @@ -51,7 +51,7 @@ runs: - name: Login to DockerHub # FIPS base image (rocketchat/dhi-node) lives in a private DockerHub repo and requires auth to pull. - if: inputs.type == 'fips' && github.actor != 'dependabot[bot]' && (github.event.pull_request.head.repo.full_name == github.repository || github.event_name == 'release' || github.ref == 'refs/heads/develop') + if: inputs.type == 'fips' && (github.event.pull_request.head.repo.full_name == github.repository || github.event_name == 'release' || github.ref == 'refs/heads/develop') uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0 with: username: ${{ inputs.DOCKER_USER }} @@ -61,8 +61,6 @@ runs: if: inputs.service == 'rocketchat' uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0 with: - # Temporary: rocketchat fips reuses existing Meteor artifacts until build-fips exists. - # PR/merge queue runs only produce build-coverage, while release/develop produce build-production. name: build-${{ inputs.type == 'fips' && ((github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production') || inputs.type }} path: /tmp/build From 5138ba09bf097722333092e68059d81992d2f9fe Mon Sep 17 00:00:00 2001 From: Matheus Cardoso Date: Mon, 20 Jul 2026 07:37:16 -0300 Subject: [PATCH 68/70] ci: use separate prop for fips --- .github/workflows/ci-test-e2e.yml | 20 ++++++++++++-------- .github/workflows/ci.yml | 9 ++++++--- 2 files changed, 18 insertions(+), 11 deletions(-) diff --git a/.github/workflows/ci-test-e2e.yml b/.github/workflows/ci-test-e2e.yml index 484b049988c96..1fec4a979a109 100644 --- a/.github/workflows/ci-test-e2e.yml +++ b/.github/workflows/ci-test-e2e.yml @@ -26,6 +26,10 @@ on: release: required: true type: string + fips: + default: false + required: false + type: boolean shard: default: '[1]' required: false @@ -139,7 +143,7 @@ jobs: uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 if: github.event.pull_request.head.repo.full_name != github.repository && github.event_name != 'release' && github.ref != 'refs/heads/develop' with: - pattern: ${{ inputs.release == 'ce' && 'docker-image-rocketchat-amd64-coverage' || (inputs.release == 'fips' && 'docker-image-*-amd64-fips' || 'docker-image-*-amd64-coverage') }} + pattern: ${{ inputs.release == 'ce' && 'docker-image-rocketchat-amd64-coverage' || (inputs.fips && 'docker-image-*-amd64-fips' || 'docker-image-*-amd64-coverage') }} path: /tmp/docker-images merge-multiple: true @@ -192,14 +196,14 @@ jobs: DEBUG_LOG_LEVEL=${DEBUG_LOG_LEVEL:-0} docker compose -f docker-compose-ci.yml up -d rocketchat --wait - name: Start containers for EE - if: inputs.release == 'ee' || inputs.release == 'fips' + if: inputs.release == 'ee' env: ENTERPRISE_LICENSE: ${{ inputs.enterprise-license }} TRANSPORTER: ${{ inputs.transporter }} COMPOSE_PROFILES: ${{ inputs.type == 'api' && 'api' || '' }} TEST_MODE: ${{ startsWith(inputs.type, 'api') && 'api' || 'true' }} APPS_ENGINE_RUNTIME_BACKEND: ${{ inputs.type == 'api-apps-node' && 'node' || '' }} - FIPS_OVERRIDE: ${{ inputs.release == 'fips' && '-f docker-compose-ci.fips.yml' || '' }} + FIPS_OVERRIDE: ${{ inputs.fips && '-f docker-compose-ci.fips.yml' || '' }} run: | read -r -a fips_override <<< "$FIPS_OVERRIDE" DEBUG_LOG_LEVEL=${DEBUG_LOG_LEVEL:-0} docker compose -f docker-compose-ci.yml "${fips_override[@]}" up -d --wait @@ -208,7 +212,7 @@ jobs: if: inputs.type == 'ui' - name: Wait services to start up - if: inputs.release == 'ee' || inputs.release == 'fips' + if: inputs.release == 'ee' run: | docker ps @@ -226,7 +230,7 @@ jobs: working-directory: ./apps/meteor env: WEBHOOK_TEST_URL: 'http://httpbin' - IS_EE: ${{ (inputs.release == 'ee' || inputs.release == 'fips') && 'true' || '' }} + IS_EE: ${{ inputs.release == 'ee' && 'true' || '' }} run: | set -o xtrace @@ -259,7 +263,7 @@ jobs: working-directory: ./apps/meteor env: WEBHOOK_TEST_URL: 'http://httpbin' - IS_EE: ${{ (inputs.release == 'ee' || inputs.release == 'fips') && 'true' || '' }} + IS_EE: ${{ inputs.release == 'ee' && 'true' || '' }} run: | set -o xtrace @@ -274,7 +278,7 @@ jobs: if: inputs.type == 'ui' env: E2E_COVERAGE: ${{ inputs.coverage == matrix.mongodb-version && 'true' || '' }} - IS_EE: ${{ (inputs.release == 'ee' || inputs.release == 'fips') && 'true' || '' }} + IS_EE: ${{ inputs.release == 'ee' && 'true' || '' }} REPORTER_ROCKETCHAT_API_KEY: ${{ secrets.REPORTER_ROCKETCHAT_API_KEY }} REPORTER_ROCKETCHAT_URL: ${{ secrets.REPORTER_ROCKETCHAT_URL }} REPORTER_JIRA_ROCKETCHAT_API_KEY: ${{ secrets.REPORTER_JIRA_ROCKETCHAT_API_KEY }} @@ -310,7 +314,7 @@ jobs: if: inputs.type == 'ui' && always() uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: - name: playwright-test-trace-${{ inputs.release }}-${{ matrix.mongodb-version }}-${{ matrix.shard }} + name: playwright-test-trace-${{ inputs.release }}${{ inputs.fips && '-fips' || '' }}-${{ matrix.mongodb-version }}-${{ matrix.shard }} path: ./apps/meteor/tests/e2e/.playwright* include-hidden-files: true diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 26916f5cb3a02..89509ff88c38a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -768,7 +768,8 @@ jobs: uses: ./.github/workflows/ci-test-e2e.yml with: type: api - release: fips + release: ee + fips: true transporter: 'nats://nats:4222' enterprise-license: ${{ needs.release-versions.outputs.enterprise-license }} mongodb-version: "['8.0']" @@ -788,7 +789,8 @@ jobs: uses: ./.github/workflows/ci-test-e2e.yml with: type: api-livechat - release: fips + release: ee + fips: true transporter: 'nats://nats:4222' enterprise-license: ${{ needs.release-versions.outputs.enterprise-license }} mongodb-version: "['8.0']" @@ -808,7 +810,8 @@ jobs: uses: ./.github/workflows/ci-test-e2e.yml with: type: ui - release: fips + release: ee + fips: true transporter: 'nats://nats:4222' enterprise-license: ${{ needs.release-versions.outputs.enterprise-license }} shard: '[1, 2, 3, 4, 5]' From 7415d286ccb47859dde56f85193e49cabf004ae4 Mon Sep 17 00:00:00 2001 From: Matheus Cardoso Date: Mon, 20 Jul 2026 11:07:30 -0300 Subject: [PATCH 69/70] chore: update changeset --- .changeset/fips-mode-support.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/.changeset/fips-mode-support.md b/.changeset/fips-mode-support.md index b1b6f29e63f0a..56c45fc870e88 100644 --- a/.changeset/fips-mode-support.md +++ b/.changeset/fips-mode-support.md @@ -1,7 +1,6 @@ --- '@rocket.chat/meteor': minor '@rocket.chat/core-typings': minor -'@rocket.chat/license': minor '@rocket.chat/federation-matrix': minor '@rocket.chat/account-service': minor '@rocket.chat/authorization-service': minor @@ -12,5 +11,3 @@ --- Adds support for running Rocket.Chat in FIPS mode. The monolith and all microservices (ddp-streamer, account-service, authorization-service, presence-service, queue-worker, omnichannel-transcript) can now enforce FIPS-compliant cryptography via Node.js/OpenSSL FIPS, with dedicated FIPS Docker images. Running in FIPS mode requires a license including the new `fips` module, and FIPS status is now reported in server logs and statistics. - -Additionally fixes the license provided via the `ROCKETCHAT_LICENSE` environment variable not being applied when it is newer than the one persisted in the workspace. From 703adc510604ecb5610e0b2f6e45af3f31eeae1f Mon Sep 17 00:00:00 2001 From: Matheus Cardoso Date: Mon, 20 Jul 2026 12:16:34 -0300 Subject: [PATCH 70/70] ci(fips): temporarily disable ui tests (#41477) --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 89509ff88c38a..1656c435591dd 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -805,8 +805,8 @@ jobs: test-ui-fips: name: 🔨 Test UI (FIPS) + if: contains(github.event.pull_request.labels.*.name, 'fips') needs: [checks, build-gh-docker-publish, release-versions] - uses: ./.github/workflows/ci-test-e2e.yml with: type: ui @@ -1034,7 +1034,7 @@ jobs: exit 1 fi - if [[ '${{ needs.test-ui-fips.result }}' != 'success' ]]; then + if [[ '${{ needs.test-ui-fips.result }}' != 'success' && '${{ needs.test-ui-fips.result }}' != 'skipped' ]]; then exit 1 fi