name: CI on: pull_request: {} push: branches: - main - '*.*.x' permissions: contents: read concurrency: group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }} cancel-in-progress: true env: CARGO_REGISTRIES_CRATES_IO_PROTOCOL: sparse CARGO_INCREMENTAL: 0 jobs: linux: runs-on: ubuntu-latest strategy: fail-fast: false matrix: PYTHON: - {VERSION: "3.12", NOXSESSION: "flake"} - {VERSION: "3.12", NOXSESSION: "rust"} - {VERSION: "3.12", NOXSESSION: "docs", OPENSSL: {TYPE: "openssl", VERSION: "3.2.3"}} - {VERSION: "3.13", NOXSESSION: "tests"} - {VERSION: "3.14-dev", NOXSESSION: "tests"} - {VERSION: "pypy-3.10", NOXSESSION: "tests-nocoverage"} - {VERSION: "3.12", NOXSESSION: "tests", OPENSSL: {TYPE: "openssl", VERSION: "3.0.15"}} - {VERSION: "3.12", NOXSESSION: "tests", OPENSSL: {TYPE: "openssl", VERSION: "3.1.7"}} - {VERSION: "3.12", NOXSESSION: "tests-ssh", OPENSSL: {TYPE: "openssl", VERSION: "3.2.3"}} - {VERSION: "3.12", NOXSESSION: "tests-ssh", OPENSSL: {TYPE: "openssl", VERSION: "3.3.2"}} - {VERSION: "3.12", NOXSESSION: "tests", OPENSSL: {TYPE: "openssl", VERSION: "3.2.3", CONFIG_FLAGS: "no-engine no-rc2 no-srtp no-ct no-psk"}} - {VERSION: "3.12", NOXSESSION: "tests", OPENSSL: {TYPE: "openssl", VERSION: "3.2.3", CONFIG_FLAGS: "no-legacy", NO_LEGACY: "1"}} - {VERSION: "3.12", NOXSESSION: "tests", NOXARGS: "--enable-fips=1", OPENSSL: {TYPE: "openssl", CONFIG_FLAGS: "enable-fips", VERSION: "3.1.7"}} - {VERSION: "3.12", NOXSESSION: "tests", NOXARGS: "--enable-fips=1", OPENSSL: {TYPE: "openssl", CONFIG_FLAGS: "enable-fips", VERSION: "3.2.3"}} - {VERSION: "3.12", NOXSESSION: "tests", OPENSSL: {TYPE: "openssl", VERSION: "3.4.0"}} - {VERSION: "3.12", NOXSESSION: "rust,tests", OPENSSL: {TYPE: "libressl", VERSION: "3.9.2"}} - {VERSION: "3.12", NOXSESSION: "rust,tests", OPENSSL: {TYPE: "libressl", VERSION: "4.0.0"}} - {VERSION: "3.12", NOXSESSION: "tests-randomorder"} # Latest commit on the BoringSSL master branch, as of Jan 09, 2025. - {VERSION: "3.12", NOXSESSION: "rust,tests", OPENSSL: {TYPE: "boringssl", VERSION: "803062a053a86cff4ef1ffac90d12a82885688f3"}} # Latest commit on the OpenSSL master branch, as of Jan 09, 2025. - {VERSION: "3.12", NOXSESSION: "tests", OPENSSL: {TYPE: "openssl", VERSION: "89e2c6f61ebbf2ee0b0b742eb66cba0583fc6813"}} # Builds with various Rust versions. Includes MSRV and next # potential future MSRV. # - 1.70: crates.io sparse protocol by default # - 1.77: offset_of! in std (for pyo3) # - 1.80: LazyLock in std - {VERSION: "3.12", NOXSESSION: "rust,tests", RUST: "1.65.0"} - {VERSION: "3.12", NOXSESSION: "rust,tests", RUST: "beta"} - {VERSION: "3.12", NOXSESSION: "rust,tests", RUST: "nightly"} - {VERSION: "3.12", NOXSESSION: "tests-rust-debug"} timeout-minutes: 15 steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 timeout-minutes: 3 with: persist-credentials: false - name: Setup python id: setup-python uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b # v5.3.0 with: python-version: ${{ matrix.PYTHON.VERSION }} cache: pip cache-dependency-path: ci-constraints-requirements.txt timeout-minutes: 3 - name: Setup rust uses: dtolnay/rust-toolchain@a54c7afa936fefeb4456b2dd8068152669aa8203 with: toolchain: ${{ matrix.PYTHON.RUST }} components: rustfmt,clippy if: matrix.PYTHON.RUST - run: rustup component add llvm-tools-preview if: matrix.PYTHON.NOXSESSION != 'flake' && matrix.PYTHON.NOXSESSION != 'docs' - name: Clone test vectors timeout-minutes: 2 uses: ./.github/actions/fetch-vectors if: matrix.PYTHON.NOXSESSION != 'flake' && matrix.PYTHON.NOXSESSION != 'docs' && matrix.PYTHON.NOXSESSION != 'rust' - name: Compute config hash and set config vars run: | DEFAULT_CONFIG_FLAGS="shared no-ssl2 no-ssl3" CONFIG_FLAGS="$DEFAULT_CONFIG_FLAGS $CONFIG_FLAGS" OPENSSL_HASH=$(echo "${{ matrix.PYTHON.OPENSSL.TYPE }}-${{ matrix.PYTHON.OPENSSL.VERSION }}-$CONFIG_FLAGS" | sha1sum | sed 's/ .*$//') echo "CONFIG_FLAGS=${CONFIG_FLAGS}" >> $GITHUB_ENV echo "OPENSSL_HASH=${OPENSSL_HASH}" >> $GITHUB_ENV echo "OSSL_INFO=${{ matrix.PYTHON.OPENSSL.TYPE }}-${{ matrix.PYTHON.OPENSSL.VERSION }}-${CONFIG_FLAGS}" >> $GITHUB_ENV echo "OSSL_PATH=${{ github.workspace }}/osslcache/${{ matrix.PYTHON.OPENSSL.TYPE }}-${{ matrix.PYTHON.OPENSSL.VERSION }}-${OPENSSL_HASH}" >> $GITHUB_ENV env: CONFIG_FLAGS: ${{ matrix.PYTHON.OPENSSL.CONFIG_FLAGS }} if: matrix.PYTHON.OPENSSL - name: Load OpenSSL cache uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0 id: ossl-cache timeout-minutes: 2 with: path: ${{ github.workspace }}/osslcache # When altering the openssl build process you may need to increment # the value on the end of this cache key so that you can prevent it # from fetching the cache and skipping the build step. key: ${{ matrix.PYTHON.OPENSSL.TYPE }}-${{ matrix.PYTHON.OPENSSL.VERSION }}-${{ env.OPENSSL_HASH }}-14 if: matrix.PYTHON.OPENSSL - name: Build custom OpenSSL/LibreSSL run: .github/workflows/build_openssl.sh env: TYPE: ${{ matrix.PYTHON.OPENSSL.TYPE }} VERSION: ${{ matrix.PYTHON.OPENSSL.VERSION }} if: matrix.PYTHON.OPENSSL && steps.ossl-cache.outputs.cache-hit != 'true' - name: Set CFLAGS/LDFLAGS run: | echo "OPENSSL_DIR=${OSSL_PATH}" >> $GITHUB_ENV echo "CFLAGS=${CFLAGS} -Werror=implicit-function-declaration" >> $GITHUB_ENV echo "RUSTFLAGS=-Clink-arg=-Wl,-rpath=${OSSL_PATH}/lib -Clink-arg=-Wl,-rpath=${OSSL_PATH}/lib64" >> $GITHUB_ENV if: matrix.PYTHON.OPENSSL - run: sudo apt-get install -y bindgen if: matrix.PYTHON.OPENSSL.TYPE == 'boringssl' - name: Cache rust and pip uses: ./.github/actions/cache timeout-minutes: 2 with: # We have both the Python version from the matrix and from the # setup-python step because the latter doesn't distinguish # pypy3-3.8 and pypy3-3.9 -- both of them show up as 7.3.11. key: ${{ matrix.PYTHON.VERSION }}-${{ steps.setup-python.outputs.python-version }}-${{ matrix.PYTHON.NOXSESSION }}-${{ env.OPENSSL_HASH }} - run: python -m pip install -c ci-constraints-requirements.txt 'nox' 'nox[uv]; python_version >= "3.8"' 'tomli; python_version < "3.11"' - name: Create nox environment run: | nox -v --install-only env: NOXSESSION: ${{ matrix.PYTHON.NOXSESSION }} CARGO_TARGET_DIR: ${{ format('{0}/src/rust/target/', github.workspace) }} - name: Tests run: | nox --no-install -- --color=yes --wycheproof-root=wycheproof --x509-limbo-root=x509-limbo ${{ matrix.PYTHON.NOXARGS }} env: NOXSESSION: ${{ matrix.PYTHON.NOXSESSION }} COLUMNS: 80 CRYPTOGRAPHY_OPENSSL_NO_LEGACY: ${{ matrix.PYTHON.OPENSSL.NO_LEGACY }} CARGO_TARGET_DIR: ${{ format('{0}/src/rust/target/', github.workspace) }} - uses: ./.github/actions/upload-coverage distros: runs-on: ${{ matrix.IMAGE.RUNNER }} container: ghcr.io/pyca/cryptography-runner-${{ matrix.IMAGE.IMAGE }} strategy: fail-fast: false matrix: IMAGE: - {IMAGE: "rhel8", NOXSESSION: "tests", RUNNER: "ubuntu-latest"} - {IMAGE: "rhel8-fips", NOXSESSION: "tests", RUNNER: "ubuntu-latest", FIPS: true} - {IMAGE: "bullseye", NOXSESSION: "tests", RUNNER: "ubuntu-latest"} - {IMAGE: "bookworm", NOXSESSION: "tests", RUNNER: "ubuntu-latest"} - {IMAGE: "trixie", NOXSESSION: "tests", RUNNER: "ubuntu-latest"} - {IMAGE: "sid", NOXSESSION: "tests", RUNNER: "ubuntu-latest"} - {IMAGE: "ubuntu-focal", NOXSESSION: "tests", RUNNER: "ubuntu-latest"} - {IMAGE: "ubuntu-jammy", NOXSESSION: "tests", RUNNER: "ubuntu-latest"} - {IMAGE: "ubuntu-noble", NOXSESSION: "tests", RUNNER: "ubuntu-latest"} - {IMAGE: "ubuntu-rolling", NOXSESSION: "tests", RUNNER: "ubuntu-latest"} - {IMAGE: "fedora", NOXSESSION: "tests", RUNNER: "ubuntu-latest"} - {IMAGE: "alpine", NOXSESSION: "tests", RUNNER: "ubuntu-latest"} - {IMAGE: "centos-stream9", NOXSESSION: "tests", RUNNER: "ubuntu-latest"} - {IMAGE: "centos-stream9-fips", NOXSESSION: "tests", RUNNER: "ubuntu-latest", FIPS: true} - {IMAGE: "centos-stream10", NOXSESSION: "tests", RUNNER: "ubuntu-latest"} - {IMAGE: "centos-stream10-fips", NOXSESSION: "tests", RUNNER: "ubuntu-latest", FIPS: true} - {IMAGE: "ubuntu-rolling:aarch64", NOXSESSION: "tests", RUNNER: [self-hosted, Linux, ARM64]} - {IMAGE: "alpine:aarch64", NOXSESSION: "tests", RUNNER: [self-hosted, Linux, ARM64]} timeout-minutes: 15 env: RUSTUP_HOME: /root/.rustup steps: - name: Ridiculous alpine workaround for actions support on arm64 run: | # This modifies /etc/os-release so the JS actions # from GH can't detect that it's on alpine:aarch64. It will # then use a glibc nodejs, which works fine when gcompat # is installed in the container (which it is) sed -i "s:ID=alpine:ID=NotpineForGHA:" /etc/os-release if: matrix.IMAGE.IMAGE == 'alpine:aarch64' - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 timeout-minutes: 3 with: persist-credentials: false - name: Cache rust and pip uses: ./.github/actions/cache timeout-minutes: 2 with: key: ${{ matrix.IMAGE.IMAGE }} - name: Clone test vectors timeout-minutes: 2 uses: ./.github/actions/fetch-vectors # When run in a docker container the home directory doesn't have the same owner as the # apparent user so pip refuses to create a cache dir - name: create pip cache dir run: mkdir -p "${HOME}/.cache/pip" - run: | echo "OPENSSL_FORCE_FIPS_MODE=1" >> $GITHUB_ENV if: matrix.IMAGE.FIPS - run: /venv/bin/python -m pip install -c ci-constraints-requirements.txt 'nox' 'nox[uv]; python_version >= "3.8"' 'tomli; python_version < "3.11"' - run: '/venv/bin/nox -v --install-only' env: CARGO_TARGET_DIR: ${{ format('{0}/src/rust/target/', github.workspace) }} # OPENSSL_ENABLE_SHA1_SIGNATURES is for CentOS 9 Stream OPENSSL_ENABLE_SHA1_SIGNATURES: 1 NOXSESSION: ${{ matrix.IMAGE.NOXSESSION }} - run: '/venv/bin/nox --no-install -- --color=yes --wycheproof-root="wycheproof" --x509-limbo-root="x509-limbo"' env: COLUMNS: 80 # OPENSSL_ENABLE_SHA1_SIGNATURES is for CentOS 9 Stream OPENSSL_ENABLE_SHA1_SIGNATURES: 1 NOXSESSION: ${{ matrix.IMAGE.NOXSESSION }} - uses: ./.github/actions/upload-coverage macos: runs-on: ${{ matrix.RUNNER.OS }} strategy: fail-fast: false matrix: RUNNER: - {OS: 'macos-13', ARCH: 'x86_64'} - {OS: 'macos-14', ARCH: 'arm64'} PYTHON: - {VERSION: "3.7", NOXSESSION: "tests"} - {VERSION: "3.13", NOXSESSION: "tests"} exclude: # We only test latest Python on arm64. py37 won't work since there's no universal2 binary - PYTHON: {VERSION: "3.7", NOXSESSION: "tests"} RUNNER: {OS: 'macos-14', ARCH: 'arm64'} timeout-minutes: 15 steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 timeout-minutes: 3 with: persist-credentials: false - name: Cache rust and pip uses: ./.github/actions/cache timeout-minutes: 2 with: key: ${{ matrix.PYTHON.NOXSESSION }}-${{ matrix.PYTHON.VERSION }} - name: Setup python uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b # v5.3.0 with: python-version: ${{ matrix.PYTHON.VERSION }} cache: pip cache-dependency-path: ci-constraints-requirements.txt timeout-minutes: 3 - run: rustup component add llvm-tools-preview - run: python -m pip install -c ci-constraints-requirements.txt 'nox' 'nox[uv]; python_version >= "3.8"' 'tomli; python_version < "3.11"' - name: Clone test vectors timeout-minutes: 2 uses: ./.github/actions/fetch-vectors - uses: dawidd6/action-download-artifact@80620a5d27ce0ae443b965134db88467fc607b43 # v7 with: repo: pyca/infra workflow: build-macos-openssl.yml branch: main workflow_conclusion: success name: openssl-macos-universal2 path: "../openssl-macos-universal2/" github_token: ${{ secrets.GITHUB_TOKEN }} - name: Build nox environment run: | OPENSSL_DIR=$(readlink -f ../openssl-macos-universal2/) \ OPENSSL_STATIC=1 \ CFLAGS="-Werror -Wno-error=deprecated-declarations -Wno-error=incompatible-pointer-types-discards-qualifiers -Wno-error=unused-function" \ nox -v --install-only env: NOXSESSION: ${{ matrix.PYTHON.NOXSESSION }} CARGO_TARGET_DIR: ${{ format('{0}/src/rust/target/', github.workspace) }} MACOSX_DEPLOYMENT_TARGET: "10.13" - name: Tests run: nox --no-install -- --color=yes --wycheproof-root=wycheproof --x509-limbo-root=x509-limbo env: NOXSESSION: ${{ matrix.PYTHON.NOXSESSION }} COLUMNS: 80 - uses: ./.github/actions/upload-coverage windows: runs-on: windows-latest strategy: fail-fast: false matrix: WINDOWS: - {ARCH: 'x86', WINDOWS: 'win32'} - {ARCH: 'x64', WINDOWS: 'win64'} PYTHON: - {VERSION: "3.7", NOXSESSION: "tests-nocoverage"} - {VERSION: "3.13", NOXSESSION: "tests"} timeout-minutes: 15 steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 timeout-minutes: 3 with: persist-credentials: false - name: Setup python id: setup-python uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b # v5.3.0 with: python-version: ${{ matrix.PYTHON.VERSION }} architecture: ${{ matrix.WINDOWS.ARCH }} cache: pip cache-dependency-path: ci-constraints-requirements.txt timeout-minutes: 3 - run: rustup component add llvm-tools-preview - name: Cache rust and pip uses: ./.github/actions/cache timeout-minutes: 2 with: key: ${{ matrix.PYTHON.NOXSESSION }}-${{ matrix.WINDOWS.ARCH }}-${{ steps.setup-python.outputs.python-version }} - run: python -m pip install -c ci-constraints-requirements.txt "nox" "nox[uv]; python_version >= '3.8'" "tomli; python_version < '3.11'" - uses: dawidd6/action-download-artifact@80620a5d27ce0ae443b965134db88467fc607b43 # v7 with: repo: pyca/infra workflow: build-windows-openssl.yml branch: main workflow_conclusion: success name: "openssl-${{ matrix.WINDOWS.WINDOWS }}" path: "C:/openssl-${{ matrix.WINDOWS.WINDOWS }}/" github_token: ${{ secrets.GITHUB_TOKEN }} - name: Configure run: | echo "OPENSSL_DIR=C:/openssl-${{ matrix.WINDOWS.WINDOWS }}" >> $GITHUB_ENV shell: bash - name: Clone test vectors timeout-minutes: 2 uses: ./.github/actions/fetch-vectors - name: Build nox environment run: nox -v --install-only env: NOXSESSION: ${{ matrix.PYTHON.NOXSESSION }} CARGO_TARGET_DIR: ${{ format('{0}/src/rust/target/', github.workspace) }} - name: Tests run: nox --no-install -- --color=yes --wycheproof-root=wycheproof --x509-limbo-root=x509-limbo env: NOXSESSION: ${{ matrix.PYTHON.NOXSESSION }} COLUMNS: 80 CARGO_TARGET_DIR: ${{ format('{0}/src/rust/target/', github.workspace) }} - uses: ./.github/actions/upload-coverage linux-downstream: runs-on: ubuntu-latest strategy: fail-fast: false matrix: DOWNSTREAM: - paramiko - pyopenssl - pyopenssl-release - twisted - aws-encryption-sdk - dynamodb-encryption-sdk - certbot - certbot-josepy - mitmproxy - scapy - sigstore PYTHON: - '3.12' name: "Downstream tests for ${{ matrix.DOWNSTREAM }}" timeout-minutes: 15 steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 timeout-minutes: 3 with: persist-credentials: false - name: Cache rust and pip uses: ./.github/actions/cache timeout-minutes: 2 - name: Setup python uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b # v5.3.0 with: python-version: ${{ matrix.PYTHON }} cache: pip cache-dependency-path: ci-constraints-requirements.txt timeout-minutes: 3 - run: ./.github/downstream.d/${{ matrix.DOWNSTREAM }}.sh install - run: pip install . env: CARGO_TARGET_DIR: ${{ format('{0}/src/rust/target/', github.workspace) }} # cryptography main has a version of "(X+1).0.0.dev1" where X is the # most recently released major version. A package used by a downstream # may depend on cryptography <=X. If you use entrypoints stuff, this can # lead to runtime errors due to version incompatibilities. Rename the # dist-info directory to pretend to be an older version to "solve" this. - run: | import json import importlib.metadata import shutil import urllib.request d = importlib.metadata.distribution("cryptography") with urllib.request.urlopen("https://pypi.org/pypi/cryptography/json") as r: latest_version = json.load(r)["info"]["version"] new_path = d.locate_file(f"cryptography-{latest_version}.dist-info") shutil.move(d.locate_file(f"cryptography-{d.version}.dist-info"), new_path) shell: python - run: ./.github/downstream.d/${{ matrix.DOWNSTREAM }}.sh run all-green: # https://github.community/t/is-it-possible-to-require-all-github-actions-tasks-to-pass-without-enumerating-them/117957/4?u=graingert runs-on: ubuntu-latest needs: [linux, distros, macos, windows, linux-downstream] if: ${{ always() }} timeout-minutes: 3 steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 timeout-minutes: 3 with: persist-credentials: false - name: Decide whether the needed jobs succeeded or failed uses: re-actors/alls-green@05ac9388f0aebcb5727afa17fcccfecd6f8ec5fe # v1.2.2 with: jobs: ${{ toJSON(needs) }} - name: Setup python if: ${{ always() }} uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b # v5.3.0 with: python-version: '3.12' cache: pip cache-dependency-path: ci-constraints-requirements.txt timeout-minutes: 3 - run: pip install -c ci-constraints-requirements.txt coverage[toml] if: ${{ always() }} - name: Download coverage data if: ${{ always() }} uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 with: pattern: coverage-data-* merge-multiple: true - name: Combine coverage and fail if it's <100%. if: ${{ always() }} id: combinecoverage run: | set +e python -m coverage combine echo "## Python Coverage" >> $GITHUB_STEP_SUMMARY python -m coverage report -m --fail-under=100 > COV_REPORT COV_EXIT_CODE=$? cat COV_REPORT if [ $COV_EXIT_CODE -ne 0 ]; then echo "🚨 Python Coverage failed. Under 100" | tee -a $GITHUB_STEP_SUMMARY fi echo '```' >> $GITHUB_STEP_SUMMARY cat COV_REPORT >> $GITHUB_STEP_SUMMARY echo '```' >> $GITHUB_STEP_SUMMARY exit $COV_EXIT_CODE - name: Combine rust coverage and fail if it's <100%. if: ${{ always() }} id: combinerustcoverage run: | set +e sudo apt-get install -y lcov RUST_COVERAGE_OUTPUT=$(lcov $(for f in *.lcov; do echo --add-tracefile "$f"; done) -o combined.lcov | grep lines) echo "## Rust Coverage" >> $GITHUB_STEP_SUMMARY echo '```' >> $GITHUB_STEP_SUMMARY echo $RUST_COVERAGE_OUTPUT >> $GITHUB_STEP_SUMMARY echo '```' >> $GITHUB_STEP_SUMMARY if ! echo "$RUST_COVERAGE_OUTPUT" | grep "100.0%"; then echo "🚨 Rust Coverage failed. Under 100" | tee -a $GITHUB_STEP_SUMMARY exit 1 fi - name: Create rust coverage HTML run: genhtml combined.lcov -o rust-coverage if: ${{ failure() && steps.combinerustcoverage.outcome == 'failure' }} - name: Create coverage HTML run: python -m coverage html if: ${{ failure() && steps.combinecoverage.outcome == 'failure' }} - name: Upload HTML report. uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 with: name: _html-report path: htmlcov if-no-files-found: ignore if: ${{ failure() && steps.combinecoverage.outcome == 'failure' }} - name: Upload rust HTML report. uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 with: name: _html-rust-report path: rust-coverage if-no-files-found: ignore if: ${{ failure() && steps.combinerustcoverage.outcome == 'failure' }}