diff --git a/.github/workflows/experimental.yml b/.github/workflows/experimental.yml new file mode 100644 index 0000000000000..65053c8dccc95 --- /dev/null +++ b/.github/workflows/experimental.yml @@ -0,0 +1,223 @@ +name: Test FIPS experimental + +on: + workflow_dispatch: + inputs: + zip_url: + required: true + type: string + pull_request: + path: + - datadog_checks_base/datadog_checks/** + schedule: + - cron: '0 0,8,16 * * *' + +defaults: + run: + shell: bash + +matrix: + include: + - platform: "Windows" + runner: '["windows-2022"]' + zip_url: "" + - platform: "Linux" + runner: '["ubuntu-22.04"]' + +jobs: + run: + name: FIPS test on "${{ matrix.platform }}" + runs-on: ${{ matrix.runner }} + + env: + FORCE_COLOR: "1" + DEBIAN_FRONTEND: "noninteractive" + OPENSSL_FIPS: 1 + PYTHON_VERSION: "3.12" + OPENSSL_VERSION: "3.0.15" + FIPS_MODULE_VERSION: "3.0.9" + + steps: + + - uses: actions/checkout@v4 + + - name: Install System Dependencies + if: runner.os == 'Linux' + run: | + sudo apt update + sudo apt install -y --no-install-recommends \ + wget \ + build-essential \ + gcc \ + make \ + perl \ + libc6-dev + + - name: Build FIPS Module + if: runner.os == 'Linux' + run: | + wget https://www.openssl.org/source/openssl-${{ env.FIPS_MODULE_VERSION }}.tar.gz \ + && tar -xvzf openssl-${{ env.FIPS_MODULE_VERSION }}.tar.gz \ + && cd openssl-${{ env.FIPS_MODULE_VERSION }} \ + && ./Configure enable-fips \ + && make \ + && sudo make install + + - name: Build OpenSSL + if: runner.os == 'Linux' + run: | + wget https://www.openssl.org/source/openssl-${{ env.OPENSSL_VERSION }}.tar.gz \ + && tar -xvzf openssl-${{ env.OPENSSL_VERSION }}.tar.gz \ + && cd openssl-${{ env.OPENSSL_VERSION }} \ + && ./Configure enable-fips \ + && make \ + && sudo make install + + - name: Build Python from Source with Custom OpenSSL + if: runner.os == 'Linux' + run: | + + # Install dependencies for building Python + sudo apt-get update && sudo apt-get install -y \ + build-essential \ + zlib1g-dev \ + libffi-dev \ + libssl-dev \ + libncurses5-dev \ + libsqlite3-dev \ + libreadline-dev \ + libbz2-dev \ + liblzma-dev \ + tk-dev \ + uuid-dev \ + libgdbm-dev \ + wget + + # Download and extract Python source + wget https://www.python.org/ftp/python/${{ env.PYTHON_VERSION }}/Python-${{ env.PYTHON_VERSION }}.tgz + tar -xvzf Python-${{ env.PYTHON_VERSION }}.tgz -C python_dir + cd python_dir + + # Configure and build Python with custom OpenSSL + ./configure --enable-optimizations --with-openssl=$(pwd)/../openssl-${{ env.OPENSSL_VERSION }} + make -j$(nproc) + sudo make altinstall + + - name: Download python-windows-combined + if: runner.os == 'Windows' + shell: powershell + run: | + Invoke-WebRequest -Uri '${{ inputs.zip_url || https://agent-ints-python-build-sandbox.s3.eu-north-1.amazonaws.com/python-windows-combined-v3.12.6-openssl-3.0.15-openssl-3.0.9-amd64.zip }}' -OutFile 'python_combined.zip' + + - name: Unzip python_combined.zip + if: runner.os == 'Windows' + shell: powershell + run: | + Expand-Archive -Path python_combined.zip -DestinationPath .\python_dir + + - name: Run fipsintall.exe + if: runner.os == 'Windows' + working-directory: .\python_dir + shell: powershell + run: | + .\openssl.exe fipsinstall -module .\ossl-modules\fips.dll -out fipsmodule.cnf + + - name: Configure OpenSSL for FIPS + if: runner.os == 'Windows' + working-directory: .\python_dir + shell: powershell + run: | + # Create openssl.cnf to enable FIPS mode + $OpenSSLConf = @" + config_diagnostics = 1 + openssl_conf = openssl_init + + .include fipsmodule.cnf + + [openssl_init] + providers = provider_sect + alg_section = algorithm_sect + + [provider_sect] + fips = fips_sect + base = base_sect + + [base_sect] + activate = 1 + + [algorithm_sect] + default_properties = fips=yes + "@ + $OpenSSLConf | Set-Content -Path ".\openssl.cnf" + + - name: Verify OpenSSL + if: runner.os == 'Windows' + working-directory: .\python_dir + shell: powershell + run: | + .\openssl.exe version -a + .\openssl.exe list -providers + + - name: Verify OpenSSL with FIPS ENV vars + if: runner.os == 'Windows' + working-directory: .\python_dir + shell: powershell + run: | + $env:OPENSSL_MODULES = ".\ossl-modules" + $env:OPENSSL_CONF = ".\openssl.cnf" + .\openssl.exe list -providers + + - name: Add Python to PATH Windows + if: runner.os == 'Windows' + shell: powershell + run: | + Add-Content -Path $env:GITHUB_ENV -Value "PATH=$(pwd)\python_dir;$(pwd)\python_dir\Scripts;$env:PATH" + + - name: Add Python to PATH Linux + if: runner.os == 'Linux' + run: | + echo "PATH=$(pwd)/python_dir:$PATH" >> $GITHUB_ENV + + - name: Install pip + run: | + python -m ensurepip + + - name: Restore cache + uses: actions/cache/restore@v4 + with: + path: ${{ runner.os == 'Windows' && '~\AppData\Local\pip\Cache' || '~/.cache/pip' }} + key: >- + ${{ format( + 'v01-python-{0}-{1}-{2}-{3}', + env.pythonLocation, + hashFiles('datadog_checks_base/pyproject.toml'), + hashFiles('datadog_checks_dev/pyproject.toml'), + hashFiles('ddev/pyproject.toml') + )}} + restore-keys: |- + v01-python-${{ env.pythonLocation }} + + - name: Install ddev from local folder + run: | + python.exe -m pip install -e ./datadog_checks_dev[cli] + python.exe -m pip install -e ./ddev + + - name: Configure ddev + run: | + ddev config set repos.core . + ddev config set repo core + + - name: Test + if: runner.os == 'Windows' + working-directory: .\python_dir + shell: powershell + run: | + $env:PATH_TO_OPENSSL_CONF = "$(pwd)\openssl.cnf" + $env:PATH_TO_OPENSSL_MODULES = "$(pwd)\ossl-modules" + $env:OPENSSL_CONF = "$(pwd)\openssl.cnf" + $env:OPENSSL_MODULES = "$(pwd)\ossl-modules" + .\openssl.exe list -providers + .\openssl.exe md5 + ddev test --cov --junit datadog_checks_base -- -k before_fips + ddev test --cov --junit datadog_checks_base -- -k after_fips + python -c "import ssl; ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT).set_ciphers('MD5')" diff --git a/datadog_checks_base/tests/fips/test_md5_after_fips.py b/datadog_checks_base/tests/fips/test_md5_after_fips.py new file mode 100644 index 0000000000000..be6d41f9ad358 --- /dev/null +++ b/datadog_checks_base/tests/fips/test_md5_after_fips.py @@ -0,0 +1,38 @@ +# (C) Datadog, Inc. 2024-present +# All rights reserved +# Licensed under a 3-clause BSD style license (see LICENSE) +import os +from typing import Any # noqa: F401 + +import pytest + +from datadog_checks.base.utils.fips import enable_fips + +PATH_TO_OPENSSL_CONF = os.getenv("PATH_TO_OPENSSL_CONF") +PATH_TO_OPENSSL_MODULES = os.getenv("PATH_TO_OPENSSL_MODULES") + + +@pytest.fixture(scope="function") +def clean_environment(): + os.environ["GOFIPS"] = "0" + os.environ["OPENSSL_CONF"] = "" + os.environ["OPENSSL_MODULES"] = "" + yield + + +def test_ssl_md5_after_fips(clean_environment): + import ssl + + enable_fips(path_to_openssl_conf=PATH_TO_OPENSSL_CONF, path_to_openssl_modules=PATH_TO_OPENSSL_MODULES) + with pytest.raises(ssl.SSLError, match='No cipher can be selected.'): + ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) + ctx.set_ciphers("MD5") + + +def test_cryptography_md5_after_fips(clean_environment): + from cryptography.exceptions import InternalError + from cryptography.hazmat.primitives import hashes + + enable_fips(path_to_openssl_conf=PATH_TO_OPENSSL_CONF, path_to_openssl_modules=PATH_TO_OPENSSL_MODULES) + with pytest.raises(InternalError, match='Unknown OpenSSL error.'): + hashes.Hash(hashes.MD5()) diff --git a/datadog_checks_base/tests/fips/test_md5_before_fips.py b/datadog_checks_base/tests/fips/test_md5_before_fips.py new file mode 100644 index 0000000000000..da35054d170b6 --- /dev/null +++ b/datadog_checks_base/tests/fips/test_md5_before_fips.py @@ -0,0 +1,30 @@ +# (C) Datadog, Inc. 2024-present +# All rights reserved +# Licensed under a 3-clause BSD style license (see LICENSE) +import os +from typing import Any # noqa: F401 + +import pytest + + +@pytest.fixture(scope="function") +def clean_environment(): + os.environ["GOFIPS"] = "0" + os.environ["OPENSSL_CONF"] = "" + os.environ["OPENSSL_MODULES"] = "" + yield + + +def test_ssl_md5_before_fips(clean_environment): + import ssl + + ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) + ctx.set_ciphers("MD5") + assert True + + +def test_cryptography_md5_before_fips(clean_environment): + from cryptography.hazmat.primitives import hashes + + hashes.Hash(hashes.MD5()) + assert True