Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add FIPS integration tests #19302

Draft
wants to merge 2 commits into
base: david.kirov/fips-switch
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
228 changes: 228 additions & 0 deletions .github/workflows/experimental.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,228 @@
name: Test FIPS experimental

on:
workflow_dispatch:
inputs:
zip_url:
required: true
type: string
default: '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'
pull_request:
path:
- datadog_checks_base/datadog_checks/**
schedule:
- cron: '0 0,8,16 * * *'

defaults:
run:
shell: bash

jobs:
test:
strategy:
matrix:
include:
- platform: "Windows"
runner: "windows-2022"
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"
- platform: "Linux"
runner: "ubuntu-22.04"
zip_url: ""
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 || matrix.zip_url }} -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
run: |
./openssl version -a
./openssl list -providers

- name: Verify OpenSSL with FIPS ENV vars
if: runner.os == 'Windows'
working-directory: .\python_dir
run: |
if [[ "$RUNNER_OS" == "Windows" ]]; then
echo "OPENSSL_MODULES=$(pwd)\ossl-modules" >> $GITHUB_ENV
echo "OPENSSL_CONF=$(pwd)\openssl.cnf" >> $GITHUB_ENV
else
echo "OPENSSL_MODULES=$(pwd)/ossl-modules" >> $GITHUB_ENV
echo "OPENSSL_CONF=$(pwd)/openssl.cnf" >> $GITHUB_ENV
fi
./openssl list -providers

- name: Add Python to PATH
run: |
if [[ "$RUNNER_OS" == "Windows" ]]; then
echo "PATH=$(pwd)\python_dir;$(pwd)\python_dir\Scripts;$PATH" >> $GITHUB_ENV
else
echo "PATH=$(pwd)/python_dir:$PATH" >> $GITHUB_ENV
fi

- 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 -m pip install -e ./datadog_checks_dev[cli]
python -m pip install -e ./ddev

- name: Configure ddev
run: |
ddev config set repos.core .
ddev config set repo core

- name: Test
working-directory: ./python_dir
run: |
if [[ "$RUNNER_OS" == "Windows" ]]; then
echo "PATH_TO_OPENSSL_CONF=$(pwd)\openssl.cnf" >> $GITHUB_ENV
echo "PATH_TO_OPENSSL_MODULES=$(pwd)\ossl-modules" >> $GITHUB_ENV
echo "OPENSSL_CONF=$(pwd)\openssl.cnf" >> $GITHUB_ENV
echo "OPENSSL_MODULES=$(pwd)\ossl-modules" >> $GITHUB_ENV
else
echo "PATH_TO_OPENSSL_CONF=$(pwd)/openssl.cnf" >> $GITHUB_ENV
echo "PATH_TO_OPENSSL_MODULES=$(pwd)/ossl-modules" >> $GITHUB_ENV
echo "OPENSSL_CONF=$(pwd)/openssl.cnf" >> $GITHUB_ENV
echo "OPENSSL_MODULES=$(pwd)/ossl-modules" >> $GITHUB_ENV
fi
./openssl list -providers
ddev test datadog_checks_base -- -s -m fips_off
ddev test datadog_checks_base -- -s -m fips_on
72 changes: 72 additions & 0 deletions datadog_checks_base/tests/test_fips.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
# (C) Datadog, Inc. 2024-present
# All rights reserved
# Licensed under a 3-clause BSD style license (see LICENSE)
from typing import Any # noqa: F401

import os
import pytest
import sys

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(monkeypatch):
monkeypatch.setenv("GOFIPS", "0")
monkeypatch.setenv("OPENSSL_CONF", "")
monkeypatch.setenv("OPENSSL_MODULES", "")
yield


@pytest.mark.fips_off
def test_ssl_md5_before_fips(clean_environment):
"""
MD5 cipher should be available through ssl before enabling FIPS mode.
"""
import ssl

ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
ctx.set_ciphers("MD5")
assert True


@pytest.mark.fips_off
def test_cryptography_md5_before_fips(clean_environment):
"""
MD5 cipher should be available through cryptography before enabling FIPS mode.
"""
from cryptography.hazmat.primitives import hashes

assert hashes.Hash(hashes.MD5())


@pytest.mark.fips_on
def test_ssl_md5_after_fips(clean_environment):
"""
MD5 cipher should not be available through ssl after enabling FIPS mode.
"""
import ssl

print(f'\nPython Path: {sys.executable}')
print(f'\nEnv Vars: {os.environ}')

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")


@pytest.mark.fips_on
def test_cryptography_md5_after_fips(clean_environment):
"""
MD5 cipher should not be available through cryptography after enabling FIPS mode.
"""
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())
Loading