From 06b1bf3a51a45415527b663ee31a1507f32b89d6 Mon Sep 17 00:00:00 2001 From: Jesse Hallam Date: Mon, 15 Sep 2025 10:53:28 -0300 Subject: [PATCH] MM-64878: FIPS Build (#33809) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * pin to ubuntu-24.04 * always use FIPS compatible Postgres settings * use sha256 for remote cluster IDs * use sha256 for client config hash * rework S3 backend to be FIPS compatible * skip setup-node during build, since already in container * support FIPS builds * Dockerfile for FIPS image, using glibc-openssl-fips * workaround entrypoint inconsistencies * authenticate to DockerHub * fix FIPS_ENABLED, add test-mmctl-fips * decouple check-mattermost-vet from test/build steps * fixup! decouple check-mattermost-vet from test/build steps * only build-linux-amd64 for fips * rm entrypoint workaround * tweak comment grammar * rm unused Dockerfile.fips (for now) * ignore gpg import errors, since would fail later anyway * for fips, only make package-linux-amd64 * set FIPS_ENABLED for build step * Add a FIPS-specific list of prepackaged plugins Note that the names are still temporary, since they are not uploaded to S3 yet. We may need to tweak them when that happens. * s/golangci-lint/check-style/ This ensures we run all the `check-style` checks: previously, `modernize` was missing. * pin go-vet to @v2, remove annoying comment * add -fips to linux-amd64.tz.gz package * rm unused setup-chainctl * use BUILD_TYPE_NAME instead * mv fips build to enterprise-only * fixup! use BUILD_TYPE_NAME instead * temporarily pre-package no plugins for FIPS * split package-cleanup * undo package-cleanup, just skip ARM, also test * skip arm for FIPS in second target too * fmt Makefile * Revert "rm unused Dockerfile.fips (for now)" This reverts commit 601e37e0fff7b7703540bb9e91961ad8bb83b2e7. * reintroduce Dockerfile.fips and align with existing Dockerfile * s/IMAGE/BUILD_IMAGE/ * bump the glibc-openssl-fips version * rm redundant comment * fix FIPS checks * set PLUGIN_PACKAGES empty until prepackaged plugins ready * upgrade glibc-openssl-fips, use non-dev version for final stage * another BUILD_IMAGE case * Prepackage the FIPS versions of plugins * relocate FIPS_ENABLED initialization before use * s/Config File MD5/Config File Hash/ * Update the FIPS plugin names and encode the + sign * add /var/tmp for local socket manipulation --------- Co-authored-by: Alejandro GarcĂ­a Montoro Co-authored-by: Mattermost Build --- .github/workflows/api.yml | 2 +- .github/workflows/claude.yml | 2 +- .github/workflows/codeql-analysis.yml | 2 +- .../dispatch-server-builder-image.yml | 4 +- .github/workflows/docker-push-mirrored.yml | 2 +- .github/workflows/e2e-fulltests-ci.yml | 4 +- .github/workflows/e2e-tests-ci-template.yml | 22 ++--- .github/workflows/e2e-tests-ci.yml | 2 +- .github/workflows/mmctl-test-template.yml | 32 ++++++- .github/workflows/scorecards-analysis.yml | 2 +- .github/workflows/server-ci.yml | 85 ++++++++---------- .github/workflows/server-test-template.yml | 37 ++++++-- server/Makefile | 24 ++++- server/build/Dockerfile | 2 +- server/build/Dockerfile.fips | 90 +++++++++++++++++++ server/build/docker-compose.common.yml | 1 + server/build/docker/postgres.conf | 1 + server/build/release.mk | 35 +++++++- server/channels/app/platform/config.go | 4 +- server/platform/shared/filestore/s3store.go | 46 ++++------ server/public/model/remote_cluster.go | 5 +- .../admin_console/cluster_table.tsx | 2 +- webapp/channels/src/i18n/en.json | 2 +- 23 files changed, 292 insertions(+), 116 deletions(-) create mode 100644 server/build/Dockerfile.fips diff --git a/.github/workflows/api.yml b/.github/workflows/api.yml index bc71a8144c6..1369ab4d1e6 100644 --- a/.github/workflows/api.yml +++ b/.github/workflows/api.yml @@ -11,7 +11,7 @@ permissions: jobs: build: - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 defaults: run: working-directory: ./api diff --git a/.github/workflows/claude.yml b/.github/workflows/claude.yml index 3484634a32b..d8567a60e35 100644 --- a/.github/workflows/claude.yml +++ b/.github/workflows/claude.yml @@ -17,7 +17,7 @@ jobs: (github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude')) || (github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude')) || (github.event_name == 'issues' && (contains(github.event.issue.body, '@claude') || contains(github.event.issue.title, '@claude'))) - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 permissions: contents: read pull-requests: read diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index c2574ec31cf..4fa61e07d0f 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -16,7 +16,7 @@ jobs: security-events: write # for github/codeql-action/autobuild to send a status report name: Analyze if: github.repository_owner == 'mattermost' - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 strategy: fail-fast: false diff --git a/.github/workflows/dispatch-server-builder-image.yml b/.github/workflows/dispatch-server-builder-image.yml index d390fba4fd8..9abc52a94c0 100644 --- a/.github/workflows/dispatch-server-builder-image.yml +++ b/.github/workflows/dispatch-server-builder-image.yml @@ -21,7 +21,7 @@ permissions: jobs: build-and-push: - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 env: IMAGE_TAG: ${{ github.event.inputs.tag }} @@ -66,7 +66,7 @@ jobs: -f server/build/Dockerfile.buildenv . build-and-push-fips: - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 steps: - uses: chainguard-dev/setup-chainctl@f4ed65b781b048c44d4f033ae854c025c5531c19 # v0.3.2 diff --git a/.github/workflows/docker-push-mirrored.yml b/.github/workflows/docker-push-mirrored.yml index 6fce41887b0..298c913d8dd 100644 --- a/.github/workflows/docker-push-mirrored.yml +++ b/.github/workflows/docker-push-mirrored.yml @@ -11,7 +11,7 @@ jobs: build-docker: name: cd/Push mirrored docker images if: github.repository_owner == 'mattermost' - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 steps: - name: Checkout mattermost project uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 diff --git a/.github/workflows/e2e-fulltests-ci.yml b/.github/workflows/e2e-fulltests-ci.yml index 6cfeabb838d..f2c633582af 100644 --- a/.github/workflows/e2e-fulltests-ci.yml +++ b/.github/workflows/e2e-fulltests-ci.yml @@ -53,7 +53,7 @@ concurrency: jobs: generate-test-variables: - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 permissions: issues: write pull-requests: write @@ -307,7 +307,7 @@ jobs: AWS_SECRET_ACCESS_KEY: "${{ secrets.CYPRESS_AWS_SECRET_ACCESS_KEY }}" notify-user: - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 if: always() needs: - generate-test-variables diff --git a/.github/workflows/e2e-tests-ci-template.yml b/.github/workflows/e2e-tests-ci-template.yml index e75841f4b62..7b953976a4b 100644 --- a/.github/workflows/e2e-tests-ci-template.yml +++ b/.github/workflows/e2e-tests-ci-template.yml @@ -105,7 +105,7 @@ on: jobs: update-initial-status: - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 steps: - uses: mattermost/actions/delivery/update-commit-status@main env: @@ -118,7 +118,7 @@ jobs: status: pending cypress-check: - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 needs: - update-initial-status defaults: @@ -149,7 +149,7 @@ jobs: npm run check playwright-check: - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 needs: - update-initial-status defaults: @@ -186,7 +186,7 @@ jobs: npm run check shell-check: - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 needs: - update-initial-status defaults: @@ -204,7 +204,7 @@ jobs: run: make check-shell generate-build-variables: - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 needs: - update-initial-status defaults: @@ -230,7 +230,7 @@ jobs: echo "node-cache-dependency-path=e2e-tests/${TEST}/package-lock.json" >> $GITHUB_OUTPUT generate-test-cycle: - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 needs: - generate-build-variables defaults: @@ -284,8 +284,8 @@ jobs: # - For MacOS: works on developer machines, but uses too many resources to be able to run on Github Actions # - for Windows: cannot currently run on Github Actions, since the runners do not support running linux containers, at the moment # - #os: [ubuntu-latest, windows-2022, macos-12-xl] - os: [ubuntu-latest] + #os: [ubuntu-24.04, windows-2022, macos-12-xl] + os: [ubuntu-24.04] worker_index: ${{ fromJSON(needs.generate-build-variables.outputs.workers) }} # https://docs.github.com/en/actions/learn-github-actions/expressions#example-returning-a-json-object runs-on: "${{ matrix.os }}" timeout-minutes: 120 @@ -376,7 +376,7 @@ jobs: retention-days: 1 report: - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 needs: - test - generate-build-variables @@ -527,7 +527,7 @@ jobs: [ "${{ steps.calculate-results.outputs.failed }}" = "0" ] update-failure-final-status: - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 if: failure() || cancelled() needs: - generate-test-cycle @@ -550,7 +550,7 @@ jobs: update-success-final-status: - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 if: success() needs: - generate-test-cycle diff --git a/.github/workflows/e2e-tests-ci.yml b/.github/workflows/e2e-tests-ci.yml index ca55849b42f..5d771bab498 100644 --- a/.github/workflows/e2e-tests-ci.yml +++ b/.github/workflows/e2e-tests-ci.yml @@ -11,7 +11,7 @@ on: jobs: generate-test-variables: - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 outputs: BRANCH: "${{ steps.generate.outputs.BRANCH }}" BUILD_ID: "${{ steps.generate.outputs.BUILD_ID }}" diff --git a/.github/workflows/mmctl-test-template.yml b/.github/workflows/mmctl-test-template.yml index 8c37f6e2b4c..1cde38dd92b 100644 --- a/.github/workflows/mmctl-test-template.yml +++ b/.github/workflows/mmctl-test-template.yml @@ -17,6 +17,10 @@ on: go-version: required: true type: string + fips-enabled: + required: false + default: false + type: boolean jobs: test: @@ -25,8 +29,25 @@ jobs: env: COMPOSE_PROJECT_NAME: ghactions steps: + - name: buildenv/docker-login + uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + - name: Checkout mattermost project uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - name: Setup BUILD_IMAGE + id: build + run: | + if [[ ${{ inputs.fips-enabled }} == 'true' ]]; then + echo "BUILD_IMAGE=mattermost/mattermost-build-server-fips:${{ inputs.go-version }}" >> "${GITHUB_OUTPUT}" + echo "LOG_ARTIFACT_NAME=${{ inputs.logsartifact }}-fips" >> "${GITHUB_OUTPUT}" + else + echo "BUILD_IMAGE=mattermostdevelopment/mattermost-build-server:${{ inputs.go-version }}" >> "${GITHUB_OUTPUT}" + echo "LOG_ARTIFACT_NAME=${{ inputs.logsartifact }}" >> "${GITHUB_OUTPUT}" + fi + - name: Store required variables for publishing results run: | echo "${{ inputs.name }}" > server/test-name @@ -35,6 +56,7 @@ jobs: run: | cd server make prepackaged-plugins PLUGIN_PACKAGES=mattermost-plugin-jira-v3.2.5 + - name: Run docker compose run: | cd server/build @@ -44,9 +66,10 @@ jobs: cat ../tests/test-data.ldif | docker compose --ansi never exec -T openldap bash -c 'ldapadd -x -D "cn=admin,dc=mm,dc=test,dc=com" -w mostest'; docker compose --ansi never exec -T minio sh -c 'mkdir -p /data/mattermost-test'; docker compose --ansi never ps + - name: Run mmctl Tests env: - BUILD_IMAGE: mattermostdevelopment/mattermost-build-server:${{ inputs.go-version }} + BUILD_IMAGE: ${{ steps.build.outputs.BUILD_IMAGE }} run: | if [[ ${{ github.ref_name }} == 'master' ]]; then export TESTFLAGS="-timeout 90m -race" @@ -58,21 +81,22 @@ jobs: --env-file=server/build/dotenv/test.env \ --env MM_SQLSETTINGS_DATASOURCE="${{ inputs.datasource }}" \ --env MMCTL_TESTFLAGS="$TESTFLAGS" \ - -v $(go env GOCACHE):/go/cache \ - -e GOCACHE=/go/cache \ + --env FIPS_ENABLED="${{ inputs.fips-enabled }}" \ -v $PWD:/mattermost \ -w /mattermost/server \ $BUILD_IMAGE \ make test-mmctl BUILD_NUMBER=$GITHUB_HEAD_REF-$GITHUB_RUN_ID + - name: Stop docker compose run: | cd server/build docker compose --ansi never stop + - name: Archive logs if: ${{ always() }} uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 with: - name: ${{ inputs.logsartifact }} + name: ${{ steps.build.outputs.LOG_ARTIFACT_NAME }} path: | server/gotestsum.json server/report.xml diff --git a/.github/workflows/scorecards-analysis.yml b/.github/workflows/scorecards-analysis.yml index 3ef52c213b6..9742b7019c2 100644 --- a/.github/workflows/scorecards-analysis.yml +++ b/.github/workflows/scorecards-analysis.yml @@ -12,7 +12,7 @@ jobs: analysis: name: Scorecard analysis if: github.repository_owner == 'mattermost' - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 permissions: # Needed to upload the results to code-scanning dashboard. security-events: write diff --git a/.github/workflows/server-ci.yml b/.github/workflows/server-ci.yml index 10d3340dbe6..a5aaa8f910f 100644 --- a/.github/workflows/server-ci.yml +++ b/.github/workflows/server-ci.yml @@ -63,8 +63,8 @@ jobs: run: make modules-tidy - name: Check modules run: if [[ -n $(git status --porcelain) ]]; then echo "Please tidy up the Go modules using make modules-tidy"; git diff; exit 1; fi - golangci: - name: golangci-lint + check-style: + name: check-style needs: go runs-on: ubuntu-22.04 container: mattermostdevelopment/mattermost-build-server:${{ needs.go.outputs.version }} @@ -79,7 +79,7 @@ jobs: - name: Run setup-go-work run: make setup-go-work - name: Run golangci - run: make golangci-lint + run: make check-style check-gen-serialized: name: Check serialization methods for hot structs needs: go @@ -97,25 +97,6 @@ jobs: run: make gen-serialized - name: Check serialized run: if [[ -n $(git status --porcelain) ]]; then echo "Please update the serialized files using 'make gen-serialized'"; exit 1; fi - check-mattermost-vet: - name: Check style - needs: go - runs-on: ubuntu-22.04 - container: mattermostdevelopment/mattermost-build-server:${{ needs.go.outputs.version }} - defaults: - run: - working-directory: server - steps: - - name: Checkout mattermost project - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - name: Run setup-go-work - run: make setup-go-work - - name: Reset config - run: make config-reset - - name: Run plugin-checker - run: make plugin-checker - - name: Run mattermost-vet - run: make vet BUILD_NUMBER='${GITHUB_HEAD_REF}' check-mattermost-vet-api: name: Vet API needs: go @@ -201,9 +182,7 @@ jobs: test-postgres-binary: if: github.event_name == 'push' # Only run postgres binary tests on master/release pushes: odds are low this regresses, so save the cycles for pull requests. name: Postgres with binary parameters - needs: - - go - - check-mattermost-vet + needs: go uses: ./.github/workflows/server-test-template.yml secrets: inherit with: @@ -212,11 +191,10 @@ jobs: drivername: postgres logsartifact: postgres-binary-server-test-logs go-version: ${{ needs.go.outputs.version }} + fips-enabled: false test-postgres-normal: name: Postgres - needs: - - go - - check-mattermost-vet + needs: go uses: ./.github/workflows/server-test-template.yml secrets: inherit with: @@ -225,13 +203,24 @@ jobs: drivername: postgres logsartifact: postgres-server-test-logs go-version: ${{ needs.go.outputs.version }} + fips-enabled: false + test-postgres-normal-fips: + name: Postgres (FIPS) + needs: go + uses: ./.github/workflows/server-test-template.yml + secrets: inherit + with: + name: Postgres + datasource: postgres://mmuser:mostest@postgres:5432/mattermost_test?sslmode=disable&connect_timeout=10 + drivername: postgres + logsartifact: postgres-server-test-logs + go-version: ${{ needs.go.outputs.version }} + fips-enabled: true test-coverage: # Skip coverage generation for cherry-pick PRs into release branches. if: ${{ github.event_name != 'pull_request' || !startsWith(github.event.pull_request.base.ref, 'release-') }} name: Generate Test Coverage - needs: - - go - - check-mattermost-vet + needs: go uses: ./.github/workflows/server-test-template.yml secrets: inherit with: @@ -244,9 +233,7 @@ jobs: go-version: ${{ needs.go.outputs.version }} test-mmctl: name: Run mmctl tests - needs: - - check-mattermost-vet - - go + needs: go uses: ./.github/workflows/mmctl-test-template.yml secrets: inherit with: @@ -255,11 +242,22 @@ jobs: drivername: postgres logsartifact: mmctl-test-logs go-version: ${{ needs.go.outputs.version }} + fips-enabled: false + test-mmctl-fips: + name: Run mmctl tests (FIPS) + needs: go + uses: ./.github/workflows/mmctl-test-template.yml + secrets: inherit + with: + name: mmctl + datasource: postgres://mmuser:mostest@postgres:5432/mattermost_test?sslmode=disable&connect_timeout=10 + drivername: postgres + logsartifact: mmctl-test-logs + go-version: ${{ needs.go.outputs.version }} + fips-enabled: true build-mattermost-server: name: Build mattermost server app - needs: - - go - - check-mattermost-vet + needs: go runs-on: ubuntu-22.04 container: mattermostdevelopment/mattermost-build-server:${{ needs.go.outputs.version }} defaults: @@ -267,23 +265,18 @@ jobs: working-directory: server env: GOFLAGS: -buildvcs=false # TODO: work around "error obtaining VCS status: exit status 128" in a container + BUILD_NUMBER: "${GITHUB_HEAD_REF}-${GITHUB_RUN_ID}" + FIPS_ENABLED: false steps: - name: Checkout mattermost project uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - name: ci/setup-node - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 - id: setup_node - with: - node-version-file: ".nvmrc" - cache: npm - cache-dependency-path: "webapp/package-lock.json" - name: Run setup-go-work run: make setup-go-work - name: Build run: | make config-reset - make build-cmd BUILD_NUMBER='${GITHUB_HEAD_REF}-${GITHUB_RUN_ID}' - make package BUILD_NUMBER='${GITHUB_HEAD_REF}-${GITHUB_RUN_ID}' + make build-cmd + make package - name: Persist dist artifacts uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 with: diff --git a/.github/workflows/server-test-template.yml b/.github/workflows/server-test-template.yml index 6827fe1120d..af696ab5081 100644 --- a/.github/workflows/server-test-template.yml +++ b/.github/workflows/server-test-template.yml @@ -25,6 +25,14 @@ on: go-version: required: true type: string + fips-enabled: + required: false + default: false + type: boolean + +permissions: + id-token: write + contents: read jobs: test: @@ -34,12 +42,30 @@ jobs: env: COMPOSE_PROJECT_NAME: ghactions steps: + - name: buildenv/docker-login + uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + - name: Checkout mattermost project uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - name: Setup BUILD_IMAGE + id: build + run: | + if [[ ${{ inputs.fips-enabled }} == 'true' ]]; then + echo "BUILD_IMAGE=mattermost/mattermost-build-server-fips:${{ inputs.go-version }}" >> "${GITHUB_OUTPUT}" + echo "LOG_ARTIFACT_NAME=${{ inputs.logsartifact }}-fips" >> "${GITHUB_OUTPUT}" + else + echo "BUILD_IMAGE=mattermostdevelopment/mattermost-build-server:${{ inputs.go-version }}" >> "${GITHUB_OUTPUT}" + echo "LOG_ARTIFACT_NAME=${{ inputs.logsartifact }}" >> "${GITHUB_OUTPUT}" + fi + - name: Store required variables for publishing results run: | echo "${{ inputs.name }}" > server/test-name echo "${{ github.event.pull_request.number }}" > server/pr-number + - name: Run docker compose run: | cd server/build @@ -49,9 +75,10 @@ jobs: cat ../tests/test-data.ldif | docker compose --ansi never exec -T openldap bash -c 'ldapadd -x -D "cn=admin,dc=mm,dc=test,dc=com" -w mostest'; docker compose --ansi never exec -T minio sh -c 'mkdir -p /data/mattermost-test'; docker compose --ansi never ps + - name: Run Tests env: - BUILD_IMAGE: mattermostdevelopment/mattermost-build-server:${{ inputs.go-version }} + BUILD_IMAGE: ${{ steps.build.outputs.BUILD_IMAGE }} run: | if [[ ${{ github.ref_name }} == 'master' && ${{ inputs.fullyparallel }} != true ]]; then export RACE_MODE="-race" @@ -61,12 +88,10 @@ jobs: --env-file=server/build/dotenv/test.env \ --env MM_SQLSETTINGS_DRIVERNAME="${{ inputs.drivername }}" \ --env MM_SQLSETTINGS_DATASOURCE="${{ inputs.datasource }}" \ - --env TEST_DATABASE_MYSQL_DSN="${{ inputs.datasource }}" \ --env TEST_DATABASE_POSTGRESQL_DSN="${{ inputs.datasource }}" \ --env ENABLE_FULLY_PARALLEL_TESTS="${{ inputs.fullyparallel }}" \ --env ENABLE_COVERAGE="${{ inputs.enablecoverage }}" \ - -v $(go env GOCACHE):/go/cache \ - -e GOCACHE=/go/cache \ + --env FIPS_ENABLED="${{ inputs.fips-enabled }}" \ -v $PWD:/mattermost \ -w /mattermost/server \ $BUILD_IMAGE \ @@ -78,15 +103,17 @@ jobs: token: ${{ secrets.CODECOV_TOKEN }} disable_search: true files: server/cover.out + - name: Stop docker compose run: | cd server/build docker compose --ansi never stop + - name: Archive logs if: ${{ always() }} uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 with: - name: ${{ inputs.logsartifact }} + name: ${{ steps.build.outputs.LOG_ARTIFACT_NAME }} path: | server/gotestsum.json server/report.xml diff --git a/server/Makefile b/server/Makefile index db7b734e60c..588fa185204 100644 --- a/server/Makefile +++ b/server/Makefile @@ -78,6 +78,11 @@ else BUILD_TYPE_NAME = team endif +FIPS_ENABLED ?= false +ifeq ($(FIPS_ENABLED),true) + BUILD_TYPE_NAME := $(BUILD_TYPE_NAME)-fips +endif + # Clean up the old means of importing enterprise source, if it exists ifneq ($(wildcard channels/imports/imports.go),) IGNORE := $(shell rm -f channels/imports/imports.go) @@ -106,6 +111,11 @@ GOFLAGS ?= $(GOFLAGS:) # for processes spawned from the Makefile export GOBIN ?= $(PWD)/bin GO ?= go + +ifeq ($(FIPS_ENABLED),true) + BUILD_TAGS += requirefips +endif + DELVE ?= dlv LDFLAGS += -X "github.com/mattermost/mattermost/server/public/model.BuildNumber=$(BUILD_NUMBER)" LDFLAGS += -X "github.com/mattermost/mattermost/server/public/model.BuildDate=$(BUILD_DATE)" @@ -158,6 +168,16 @@ PLUGIN_PACKAGES += mattermost-plugin-msteams-meetings-v2.2.0 PLUGIN_PACKAGES += mattermost-plugin-metrics-v0.7.0 PLUGIN_PACKAGES += mattermost-plugin-channel-export-v1.2.1 +# Overwrite the definition of PLUGIN_PACKAGES with the list of FIPS-ready plugins +# Note that the '+' in the file name is encoded as %2B for the URL we use to +# download the package from to work. This will no longer be needed when we unify +# the way we pre-package FIPS and non-FIPS plugins. +ifeq ($(FIPS_ENABLED),true) + PLUGIN_PACKAGES = mattermost-plugin-playbooks-v2.4.1%2Bd973eca-fips + PLUGIN_PACKAGES += mattermost-plugin-agents-v1.3.1%2B6e1b6eb-fips + PLUGIN_PACKAGES += mattermost-plugin-boards-v9.1.6%2B2b0e66a-fips +endif + EE_PACKAGES=$(shell $(GO) list $(BUILD_ENTERPRISE_DIR)/...) ifeq ($(BUILD_ENTERPRISE_READY),true) @@ -754,11 +774,9 @@ ifeq ($(BUILD_ENTERPRISE_READY),true) endif vet: ## Run mattermost go vet specific checks - ## Note that it is pinned to a specific commit, rather than a branch. This is to prevent - ## having to backport the fix to multiple release branches for any new change. $(GO) install github.com/mattermost/mattermost-govet/v2@7d8db289e508999dfcac47b97c9490a0fec12d66 $(GO) vet -vettool=$(GOBIN)/mattermost-govet -structuredLogging -inconsistentReceiverName -emptyStrCmp -tFatal -configtelemetry -errorAssertions -requestCtxNaming -license -inconsistentReceiverName.ignore=session_serial_gen.go,team_member_serial_gen.go,user_serial_gen.go,utils_serial_gen.go ./... - ifeq ($(BUILD_ENTERPRISE_READY),true) +ifeq ($(BUILD_ENTERPRISE_READY),true) ifneq ($(MM_NO_ENTERPRISE_LINT),true) $(GO) vet -vettool=$(GOBIN)/mattermost-govet -structuredLogging -inconsistentReceiverName -emptyStrCmp -tFatal -configtelemetry -errorAssertions -requestCtxNaming -enterpriseLicense $(BUILD_ENTERPRISE_DIR)/... endif diff --git a/server/build/Dockerfile b/server/build/Dockerfile index 488c7ac1c17..ab8e44e64d7 100644 --- a/server/build/Dockerfile +++ b/server/build/Dockerfile @@ -7,7 +7,7 @@ SHELL ["/bin/bash", "-o", "pipefail", "-c"] ARG PUID=2000 ARG PGID=2000 # MM_PACKAGE build arguments controls which version of mattermost to install, defaults to latest stable enterprise -# i.e. https://releases.mattermost.com/9.7.1/mattermost-9.7.1-linux-amd64.tar.gz +# e.g. https://releases.mattermost.com/9.7.1/mattermost-9.7.1-linux-amd64.tar.gz ARG MM_PACKAGE="https://latest.mattermost.com/mattermost-enterprise-linux" # Install needed packages and indirect dependencies diff --git a/server/build/Dockerfile.fips b/server/build/Dockerfile.fips new file mode 100644 index 00000000000..c2b392ef43a --- /dev/null +++ b/server/build/Dockerfile.fips @@ -0,0 +1,90 @@ +# First stage - FIPS dev image with dependencies for building +FROM cgr.dev/mattermost.com/glibc-openssl-fips:15-dev@sha256:9223f9245fb026a3c255ce9b7028a069fe11432aa7710713a331eaa36f44851c AS builder +# Setting bash as our shell, and enabling pipefail option +SHELL ["/bin/bash", "-o", "pipefail", "-c"] + +# Build Arguments +ARG PUID=2000 +ARG PGID=2000 +# MM_PACKAGE build arguments controls which version of mattermost to install, defaults to latest stable enterprise +# e.g. https://releases.mattermost.com/9.7.1/mattermost-9.7.1-linux-amd64.tar.gz +ARG MM_PACKAGE="https://latest.mattermost.com/mattermost-enterprise-linux" + +# Install needed packages and indirect dependencies +USER 0:0 +RUN apk add \ + curl \ + ca-certificates \ + mailcap \ + unrtf \ + wv \ + poppler-utils \ + tzdata + +# Set mattermost group/user and download Mattermost +RUN mkdir -p /mattermost/data /mattermost/plugins /mattermost/client/plugins \ + && addgroup -g ${PGID} mattermost \ + && adduser -D -u ${PUID} -G mattermost -g "" -s /bin/sh -h /mattermost mattermost \ + && curl -L $MM_PACKAGE | tar -xvz \ + && chown -R mattermost:mattermost /mattermost /mattermost/data /mattermost/plugins /mattermost/client/plugins + +# Create PostgreSQL client SSL directory structure for ssl_mode=require +RUN mkdir -p /mattermost/.postgresql \ + && chmod 700 /mattermost/.postgresql + +# Create /var/tmp directory needed for local socket files +RUN mkdir -p /var/tmp \ + && chmod 755 /var/tmp + +# Final stage using FIPS runtime image +FROM cgr.dev/mattermost.com/glibc-openssl-fips:15@sha256:7947eecc0d82fa3bc661aaca039bcd86d55fdf3ee581c8ecdef1b3c6f63fa83a + +# Some ENV variables +ENV PATH="/mattermost/bin:${PATH}" +ENV MM_SERVICESETTINGS_ENABLELOCALMODE="true" + +# Copy over metadata files needed by runtime +COPY --from=builder /etc/mime.types /etc + +# Copy CA certificates for SSL/TLS validation with proper ownership +COPY --from=builder --chown=2000:2000 /etc/ssl/certs /etc/ssl/certs + +# Copy document processing utilities and necessary support files +COPY --from=builder /usr/bin/pdftotext /usr/bin/pdftotext +COPY --from=builder /usr/bin/wvText /usr/bin/wvText +COPY --from=builder /usr/bin/wvWare /usr/bin/wvWare +COPY --from=builder /usr/bin/unrtf /usr/bin/unrtf +COPY --from=builder /usr/share/wv /usr/share/wv + +# Copy necessary libraries for document processing utilities +COPY --from=builder /usr/lib/libpoppler.so* /usr/lib/ +COPY --from=builder /usr/lib/libfreetype.so* /usr/lib/ +COPY --from=builder /usr/lib/libpng16.so* /usr/lib/ +COPY --from=builder /usr/lib/libwv.so* /usr/lib/ +COPY --from=builder /usr/lib/libfontconfig.so* /usr/lib/ + +# Copy mattermost from builder stage +COPY --from=builder --chown=2000:2000 /mattermost /mattermost + +# Copy group and passwd files including mattermost user +COPY --from=builder /etc/passwd /etc/passwd +COPY --from=builder /etc/group /etc/group + +# Copy /var/tmp directory needed for local socket files +COPY --from=builder /var/tmp /var/tmp + +# We should refrain from running as privileged user +USER mattermost + +# Healthcheck to make sure container is ready - using mmctl instead of curl for distroless compatibility +HEALTHCHECK --interval=30s --timeout=10s \ + CMD ["/mattermost/bin/mmctl", "system", "status", "--local"] + +# Configure entrypoint and command with proper permissions +WORKDIR /mattermost +CMD ["/mattermost/bin/mattermost"] + +EXPOSE 8065 8067 8074 8075 + +# Declare volumes for mount point directories +VOLUME ["/mattermost/data", "/mattermost/logs", "/mattermost/config", "/mattermost/plugins", "/mattermost/client/plugins"] diff --git a/server/build/docker-compose.common.yml b/server/build/docker-compose.common.yml index 66adeb20481..6103f61fb57 100644 --- a/server/build/docker-compose.common.yml +++ b/server/build/docker-compose.common.yml @@ -8,6 +8,7 @@ services: POSTGRES_USER: mmuser POSTGRES_PASSWORD: mostest POSTGRES_DB: mattermost_test + POSTGRES_INITDB_ARGS: "--auth-host=scram-sha-256 --auth-local=scram-sha-256" command: postgres -c 'config_file=/etc/postgresql/postgresql.conf' volumes: - "./docker/postgres.conf:/etc/postgresql/postgresql.conf" diff --git a/server/build/docker/postgres.conf b/server/build/docker/postgres.conf index ee7ae24f621..79959385a99 100644 --- a/server/build/docker/postgres.conf +++ b/server/build/docker/postgres.conf @@ -5,3 +5,4 @@ full_page_writes = off default_text_search_config = 'pg_catalog.english' commit_delay=1000 logging_collector=off +password_encryption = 'scram-sha-256' diff --git a/server/build/release.mk b/server/build/release.mk index 42f523525ca..bd3a50b5d31 100644 --- a/server/build/release.mk +++ b/server/build/release.mk @@ -10,8 +10,20 @@ else mkdir -p $(GOBIN)/linux_amd64 env GOOS=linux GOARCH=amd64 $(GO) build -o $(GOBIN)/linux_amd64 $(GOFLAGS) -trimpath -tags '$(BUILD_TAGS) production' -ldflags '$(LDFLAGS)' ./... endif +ifeq ($(FIPS_ENABLED),true) + @echo Verifying Build Linux amd64 for FIPS + $(GO) version -m $(GOBIN)/$(MM_BIN_NAME) | grep -q "GOEXPERIMENT=systemcrypto" || (echo "ERROR: FIPS mattermost binary missing GOEXPERIMENT=systemcrypto" && exit 1) + $(GO) version -m $(GOBIN)/$(MM_BIN_NAME) | grep "\-tags" | grep -q "requirefips" || (echo "ERROR: FIPS mattermost binary missing -tags=requirefips" && exit 1) + $(GO) tool nm $(GOBIN)/$(MM_BIN_NAME) | grep -q "func_go_openssl_OpenSSL_version" || (echo "ERROR: FIPS mattermost binary missing OpenSSL integration" && exit 1) + $(GO) version -m $(GOBIN)/$(MMCTL_BIN_NAME) | grep -q "GOEXPERIMENT=systemcrypto" || (echo "ERROR: FIPS mmctl binary missing GOEXPERIMENT=systemcrypto" && exit 1) + $(GO) version -m $(GOBIN)/$(MMCTL_BIN_NAME) | grep "\-tags" | grep -q "requirefips" || (echo "ERROR: FIPS mmctl binary missing -tags=requirefips" && exit 1) + $(GO) tool nm $(GOBIN)/$(MMCTL_BIN_NAME) | grep -q "func_go_openssl_OpenSSL_version" || (echo "ERROR: FIPS mmctl binary missing OpenSSL integration" && exit 1) +endif build-linux-arm64: +ifeq ($(FIPS_ENABLED),true) + @echo Skipping Build Linux arm64 for FIPS +else @echo Build Linux arm64 ifeq ($(BUILDER_GOOS_GOARCH),"linux_arm64") env GOOS=linux GOARCH=arm64 $(GO) build -o $(GOBIN) $(GOFLAGS) -trimpath -tags '$(BUILD_TAGS) production' -ldflags '$(LDFLAGS)' ./... @@ -19,6 +31,7 @@ else mkdir -p $(GOBIN)/linux_arm64 env GOOS=linux GOARCH=arm64 $(GO) build -o $(GOBIN)/linux_arm64 $(GOFLAGS) -trimpath -tags '$(BUILD_TAGS) production' -ldflags '$(LDFLAGS)' ./... endif +endif build-osx: @echo Build OSX amd64 @@ -53,6 +66,18 @@ else mkdir -p $(GOBIN)/linux_amd64 env GOOS=linux GOARCH=amd64 $(GO) build -o $(GOBIN)/linux_amd64 $(GOFLAGS) -trimpath -tags '$(BUILD_TAGS) production' -ldflags '$(LDFLAGS)' ./cmd/... endif +ifeq ($(FIPS_ENABLED),true) + @echo Verifying Build Linux amd64 for FIPS + $(GO) version -m $(GOBIN)/mattermost | grep -q "GOEXPERIMENT=systemcrypto" || (echo "ERROR: FIPS mattermost binary missing GOEXPERIMENT=systemcrypto" && exit 1) + $(GO) version -m $(GOBIN)/mattermost | grep "\-tags" | grep -q "requirefips" || (echo "ERROR: FIPS mattermost binary missing -tags=requirefips" && exit 1) + $(GO) tool nm $(GOBIN)/mattermost | grep -q "func_go_openssl_OpenSSL_version" || (echo "ERROR: FIPS mattermost binary missing OpenSSL integration" && exit 1) + $(GO) version -m $(GOBIN)/mmctl | grep -q "GOEXPERIMENT=systemcrypto" || (echo "ERROR: FIPS mmctl binary missing GOEXPERIMENT=systemcrypto" && exit 1) + $(GO) version -m $(GOBIN)/mmctl | grep "\-tags" | grep -q "requirefips" || (echo "ERROR: FIPS mmctl binary missing -tags=requirefips" && exit 1) + $(GO) tool nm $(GOBIN)/mmctl | grep -q "func_go_openssl_OpenSSL_version" || (echo "ERROR: FIPS mmctl binary missing OpenSSL integration" && exit 1) +endif +ifeq ($(FIPS_ENABLED),true) + @echo Skipping Build Linux arm64 for FIPS +else @echo Build CMD Linux arm64 ifeq ($(BUILDER_GOOS_GOARCH),"linux_arm64") env GOOS=linux GOARCH=arm64 $(GO) build -o $(GOBIN) $(GOFLAGS) -trimpath -tags '$(BUILD_TAGS) production' -ldflags '$(LDFLAGS)' ./cmd/... @@ -60,6 +85,7 @@ else mkdir -p $(GOBIN)/linux_arm64 env GOOS=linux GOARCH=arm64 $(GO) build -o $(GOBIN)/linux_arm64 $(GOFLAGS) -trimpath -tags '$(BUILD_TAGS) production' -ldflags '$(LDFLAGS)' ./cmd/... endif +endif build-cmd-osx: @echo Build CMD OSX amd64 @@ -141,8 +167,9 @@ endif fi fetch-prepackaged-plugins: - @# Import Mattermost plugin public key - gpg --import build/plugin-production-public-key.gpg + @# Import Mattermost plugin public key, ignoring errors. In FIPS mode, GPG fails to start + @# the gpg-agent, but still imports the key. If it really fails, it will fail validation later. + -gpg --import build/plugin-production-public-key.gpg @# Download prepackaged plugins mkdir -p tmpprepackaged @echo "Downloading prepackaged plugins ... " @@ -205,11 +232,15 @@ package-linux-amd64: package-prep rm -rf $(DIST_ROOT)/linux_amd64 package-linux-arm64: package-prep +ifeq ($(FIPS_ENABLED),true) + @echo Skipping package linux arm64 for FIPS +else DIST_PATH_GENERIC=$(DIST_PATH_LIN_ARM64) CURRENT_PACKAGE_ARCH=linux_arm64 MM_BIN_NAME=mattermost MMCTL_BIN_NAME=mmctl $(MAKE) package-general @# Package tar -C $(DIST_PATH_LIN_ARM64)/.. -czf $(DIST_PATH)-$(BUILD_TYPE_NAME)-linux-arm64.tar.gz mattermost ../mattermost @# Cleanup rm -rf $(DIST_ROOT)/linux_arm64 +endif package-linux: package-linux-amd64 package-linux-arm64 diff --git a/server/channels/app/platform/config.go b/server/channels/app/platform/config.go index 95aff5250c1..62a716a68ab 100644 --- a/server/channels/app/platform/config.go +++ b/server/channels/app/platform/config.go @@ -6,8 +6,8 @@ package platform import ( "crypto/ecdsa" "crypto/elliptic" - "crypto/md5" "crypto/rand" + "crypto/sha256" "crypto/x509" "encoding/base64" "encoding/json" @@ -237,7 +237,7 @@ func (ps *PlatformService) regenerateClientConfig() { clientConfigJSON, _ := json.Marshal(clientConfig) ps.clientConfig.Store(clientConfig) ps.limitedClientConfig.Store(limitedClientConfig) - ps.clientConfigHash.Store(fmt.Sprintf("%x", md5.Sum(clientConfigJSON))) + ps.clientConfigHash.Store(fmt.Sprintf("%x", sha256.Sum256(clientConfigJSON))) } // AsymmetricSigningKey will return a private key that can be used for asymmetric signing. diff --git a/server/platform/shared/filestore/s3store.go b/server/platform/shared/filestore/s3store.go index 9fabf59fb0a..94ac28cc228 100644 --- a/server/platform/shared/filestore/s3store.go +++ b/server/platform/shared/filestore/s3store.go @@ -188,6 +188,13 @@ func (b *S3FileBackend) s3New(isCloud bool) (*s3.Client, error) { s3Clnt.TraceOn(&s3Trace{}) } + if tr, ok := opts.Transport.(*http.Transport); ok { + if tr.TLSClientConfig == nil { + tr.TLSClientConfig = &tls.Config{} + } + tr.TLSClientConfig.MinVersion = tls.VersionTLS12 + } + return s3Clnt, nil } @@ -595,26 +602,6 @@ func (b *S3FileBackend) RemoveFile(path string) error { return nil } -func getPathsFromObjectInfos(in <-chan s3.ObjectInfo) <-chan s3.ObjectInfo { - out := make(chan s3.ObjectInfo, 1) - - go func() { - defer close(out) - - for { - info, done := <-in - - if !done { - break - } - - out <- info - } - }() - - return out -} - func (b *S3FileBackend) listDirectory(path string, recursion bool) ([]string, error) { path, err := b.prefixedPath(path) if err != nil { @@ -675,14 +662,19 @@ func (b *S3FileBackend) RemoveDirectory(path string) error { } ctx, cancel := context.WithTimeout(context.Background(), b.timeout) defer cancel() - list := b.client.ListObjects(ctx, b.bucket, opts) - ctx2, cancel2 := context.WithTimeout(context.Background(), b.timeout) - defer cancel2() - objectsCh := b.client.RemoveObjects(ctx2, b.bucket, getPathsFromObjectInfos(list), s3.RemoveObjectsOptions{}) - for err := range objectsCh { - if err.Err != nil { - return errors.Wrapf(err.Err, "unable to remove the directory %s", path) + // List all objects in the directory + for object := range b.client.ListObjects(ctx, b.bucket, opts) { + if object.Err != nil { + return errors.Wrapf(object.Err, "unable to list the directory %s", path) + } + + // Remove each object individually to avoid MD5 usage + ctx2, cancel2 := context.WithTimeout(context.Background(), b.timeout) + defer cancel2() + err := b.client.RemoveObject(ctx2, b.bucket, object.Key, s3.RemoveObjectOptions{}) + if err != nil { + return errors.Wrapf(err, "unable to remove object %s from directory %s", object.Key, path) } } diff --git a/server/public/model/remote_cluster.go b/server/public/model/remote_cluster.go index 22eb5fbabac..cbdbc6c2452 100644 --- a/server/public/model/remote_cluster.go +++ b/server/public/model/remote_cluster.go @@ -6,7 +6,6 @@ package model import ( "crypto/aes" "crypto/cipher" - "crypto/md5" "crypto/pbkdf2" "crypto/rand" "crypto/sha256" @@ -181,11 +180,11 @@ type RemoteClusterWithInvite struct { } func newIDFromBytes(b []byte) string { - hash := md5.New() + hash := sha256.New() _, _ = hash.Write(b) buf := hash.Sum(nil) - var encoding = base32.NewEncoding("ybndrfg8ejkmcpqxot1uwisza345h769").WithPadding(base32.NoPadding) + encoding := base32.NewEncoding("ybndrfg8ejkmcpqxot1uwisza345h769").WithPadding(base32.NoPadding) id := encoding.EncodeToString(buf) return id[:26] } diff --git a/webapp/channels/src/components/admin_console/cluster_table.tsx b/webapp/channels/src/components/admin_console/cluster_table.tsx index f859c2d02a3..46034d996d6 100644 --- a/webapp/channels/src/components/admin_console/cluster_table.tsx +++ b/webapp/channels/src/components/admin_console/cluster_table.tsx @@ -202,7 +202,7 @@ export default class ClusterTable extends PureComponent { diff --git a/webapp/channels/src/i18n/en.json b/webapp/channels/src/i18n/en.json index 0529d34ac69..f23b9b92d9e 100644 --- a/webapp/channels/src/i18n/en.json +++ b/webapp/channels/src/i18n/en.json @@ -653,7 +653,7 @@ "admin.cluster.OverrideHostnameDesc": "The default value of '' will attempt to get the Hostname from the OS or use the IP Address. You can override the hostname of this server with this property. It is not recommended to override the Hostname unless needed. This property can also be set to a specific IP Address if needed.", "admin.cluster.OverrideHostnameEx": "E.g.: \"app-server-01\"", "admin.cluster.should_not_change": "WARNING: These settings may not sync with the other servers in the cluster. High Availability inter-node communication will not start until you modify the config.json to be identical on all servers and restart Mattermost. Please see the documentation on how to add or remove a server from the cluster. If you are accessing the System Console through a load balancer and experiencing issues, please see the Troubleshooting Guide in our documentation.", - "admin.cluster.status_table.config_hash": "Config File MD5", + "admin.cluster.status_table.config_hash": "Config File Hash", "admin.cluster.status_table.hostname": "Hostname", "admin.cluster.status_table.reload": " Reload Cluster Status", "admin.cluster.status_table.schema_version": "DB Schema Version",