diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 3c066bd4837..2a89a565d6f 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -9,55 +9,4 @@ # This file uses an fnmatch-style matching pattern. # Team Core -* @saltstack/team-core - -# Team Boto -salt/*/*boto* @saltstack/team-core - -# Team Cloud -salt/cloud/* @saltstack/team-core -salt/utils/openstack/* @saltstack/team-core -salt/utils/aws.py @saltstack/team-core -salt/*/*cloud* @saltstack/team-core - -# Team NetAPI -salt/cli/api.py @saltstack/team-core -salt/client/netapi.py @saltstack/team-core -salt/netapi/* @saltstack/team-core - -# Team Network -salt/proxy/* @saltstack/team-core - -# Team SPM -salt/cli/spm.py @saltstack/team-core -salt/spm/* @saltstack/team-core - -# Team SSH -salt/cli/ssh.py @saltstack/team-core -salt/client/ssh/* @saltstack/team-core -salt/roster/* @saltstack/team-core -salt/runners/ssh.py @saltstack/team-core -salt/*/thin.py @saltstack/team-core - -# Team State -salt/state.py @saltstack/team-core - -# Team SUSE -salt/*/*btrfs* @saltstack/team-core -salt/*/*kubernetes* @saltstack/team-core -salt/*/*pkg* @saltstack/team-core -salt/*/*snapper* @saltstack/team-core -salt/*/*xfs* @saltstack/team-core -salt/*/*zypper* @saltstack/team-core - -# Team Transport -salt/transport/* @saltstack/team-core -salt/utils/zeromq.py @saltstack/team-core - -# Team Windows -salt/*/*win* @saltstack/team-core -salt/modules/reg.py @saltstack/team-core -salt/states/reg.py @saltstack/team-core -tests/*/*win* @saltstack/team-core -tests/*/test_reg.py @saltstack/team-core -tests/pytests/* @saltstack/team-core @s0undt3ch +* @saltstack/salt-core-maintainers diff --git a/.github/workflows/build-deps-ci-action.yml b/.github/workflows/build-deps-ci-action.yml index 73c328a97f7..a7d2d3da5fa 100644 --- a/.github/workflows/build-deps-ci-action.yml +++ b/.github/workflows/build-deps-ci-action.yml @@ -40,8 +40,9 @@ env: COLUMNS: 190 AWS_MAX_ATTEMPTS: "10" AWS_RETRY_MODE: "adaptive" - PIP_INDEX_URL: https://pypi-proxy.saltstack.net/root/local/+simple/ - PIP_EXTRA_INDEX_URL: https://pypi.org/simple + PIP_INDEX_URL: ${{ vars.PIP_INDEX_URL }} + PIP_TRUSTED_HOST: ${{ vars.PIP_TRUSTED_HOST }} + PIP_EXTRA_INDEX_URL: ${{ vars.PIP_EXTRA_INDEX_URL }} PIP_DISABLE_PIP_VERSION_CHECK: "1" RAISE_DEPRECATIONS_RUNTIME_ERRORS: "1" @@ -52,6 +53,8 @@ jobs: runs-on: ubuntu-latest outputs: matrix-include: ${{ steps.generate-matrix.outputs.matrix }} + env: + PIP_INDEX_URL: https://pypi.org/simple steps: - name: "Throttle Builds" @@ -66,6 +69,8 @@ jobs: uses: ./.github/actions/setup-python-tools-scripts with: cache-prefix: ${{ inputs.cache-prefix }} + env: + PIP_INDEX_URL: https://pypi.org/simple - name: Generate Test Matrix id: generate-matrix @@ -123,7 +128,7 @@ jobs: - name: PyPi Proxy if: steps.nox-dependencies-cache.outputs.cache-hit != 'true' run: | - sed -i '7s;^;--index-url=https://pypi-proxy.saltstack.net/root/local/+simple/ --extra-index-url=https://pypi.org/simple\n;' requirements/static/ci/*/*.txt + sed -i '7s;^;--index-url=${{ vars.PIP_INDEX_URL }} --trusted-host ${{ vars.PIP_TRUSTED_HOST }} --extra-index-url=${{ vars.PIP_EXTRA_INDEX_URL }}\n;' requirements/static/ci/*/*.txt - name: Setup Python Tools Scripts if: steps.nox-dependencies-cache.outputs.cache-hit != 'true' @@ -195,6 +200,8 @@ jobs: fail-fast: false matrix: include: ${{ fromJSON(needs.generate-matrix.outputs.matrix-include)['macos'] }} + env: + PIP_INDEX_URL: https://pypi.org/simple steps: - name: "Throttle Builds" @@ -321,7 +328,7 @@ jobs: - name: PyPi Proxy if: steps.nox-dependencies-cache.outputs.cache-hit != 'true' run: | - sed -i '7s;^;--index-url=https://pypi-proxy.saltstack.net/root/local/+simple/ --extra-index-url=https://pypi.org/simple\n;' requirements/static/ci/*/*.txt + sed -i '7s;^;--index-url=${{ vars.PIP_INDEX_URL }} --trusted-host ${{ vars.PIP_TRUSTED_HOST }} --extra-index-url=${{ vars.PIP_EXTRA_INDEX_URL }}\n;' requirements/static/ci/*/*.txt - name: Setup Python Tools Scripts if: steps.nox-dependencies-cache.outputs.cache-hit != 'true' diff --git a/.github/workflows/build-deps-onedir.yml b/.github/workflows/build-deps-onedir.yml index 007ff52e6fd..f687306a023 100644 --- a/.github/workflows/build-deps-onedir.yml +++ b/.github/workflows/build-deps-onedir.yml @@ -32,8 +32,9 @@ env: COLUMNS: 190 AWS_MAX_ATTEMPTS: "10" AWS_RETRY_MODE: "adaptive" - PIP_INDEX_URL: https://pypi-proxy.saltstack.net/root/local/+simple/ - PIP_EXTRA_INDEX_URL: https://pypi.org/simple + PIP_INDEX_URL: ${{ vars.PIP_INDEX_URL }} + PIP_TRUSTED_HOST: ${{ vars.PIP_TRUSTED_HOST }} + PIP_EXTRA_INDEX_URL: ${{ vars.PIP_EXTRA_INDEX_URL }} PIP_DISABLE_PIP_VERSION_CHECK: "1" jobs: @@ -98,6 +99,7 @@ jobs: - ${{ matrix.arch == 'arm64' && 'macos-13-xlarge' || 'macos-12' }} env: USE_S3_CACHE: 'false' + PIP_INDEX_URL: https://pypi.org/simple steps: - name: "Throttle Builds" @@ -148,6 +150,7 @@ jobs: runs-on: windows-latest env: USE_S3_CACHE: 'false' + PIP_INDEX_URL: https://pypi.org/simple steps: - name: "Throttle Builds" diff --git a/.github/workflows/build-docs.yml b/.github/workflows/build-docs.yml index 64c19ccb8a5..adeeb2fff67 100644 --- a/.github/workflows/build-docs.yml +++ b/.github/workflows/build-docs.yml @@ -17,8 +17,7 @@ env: COLUMNS: 190 AWS_MAX_ATTEMPTS: "10" AWS_RETRY_MODE: "adaptive" - PIP_INDEX_URL: https://pypi-proxy.saltstack.net/root/local/+simple/ - PIP_EXTRA_INDEX_URL: https://pypi.org/simple + PIP_INDEX_URL: https://pypi.org/simple PIP_DISABLE_PIP_VERSION_CHECK: "1" jobs: diff --git a/.github/workflows/build-packages.yml b/.github/workflows/build-packages.yml index 2abd67415a4..47a24bf2975 100644 --- a/.github/workflows/build-packages.yml +++ b/.github/workflows/build-packages.yml @@ -39,8 +39,9 @@ on: env: COLUMNS: 190 - PIP_INDEX_URL: https://pypi-proxy.saltstack.net/root/local/+simple/ - PIP_EXTRA_INDEX_URL: https://pypi.org/simple + PIP_INDEX_URL: ${{ vars.PIP_INDEX_URL }} + PIP_TRUSTED_HOST: ${{ vars.PIP_TRUSTED_HOST }} + PIP_EXTRA_INDEX_URL: ${{ vars.PIP_EXTRA_INDEX_URL }} PIP_DISABLE_PIP_VERSION_CHECK: "1" jobs: @@ -54,7 +55,8 @@ jobs: arch: ${{ github.event.repository.fork && fromJSON('["x86_64"]') || fromJSON('["x86_64", "arm64"]') }} source: - ${{ inputs.source }} - + env: + PIP_INDEX_URL: https://pypi.org/simple runs-on: - ${{ matrix.arch == 'arm64' && 'macos-13-xlarge' || 'macos-12' }} @@ -289,7 +291,7 @@ jobs: - ${{ inputs.source }} container: - image: ghcr.io/saltstack/salt-ci-containers/packaging:centosstream-9 + image: ghcr.io/saltstack/salt-ci-containers/packaging:rockylinux-9 steps: - uses: actions/checkout@v4 @@ -375,6 +377,7 @@ jobs: SM_CLIENT_CERT_PASSWORD: "${{ secrets.WIN_SIGN_CERT_PASSWORD }}" SM_CLIENT_CERT_FILE_B64: "${{ secrets.WIN_SIGN_CERT_FILE_B64 }}" WIN_SIGN_CERT_SHA1_HASH: "${{ secrets.WIN_SIGN_CERT_SHA1_HASH }}" + PIP_INDEX_URL: https://pypi.org/simple steps: - name: Check Package Signing Enabled diff --git a/.github/workflows/build-salt-onedir.yml b/.github/workflows/build-salt-onedir.yml index 73f9533fb51..5913038bbd2 100644 --- a/.github/workflows/build-salt-onedir.yml +++ b/.github/workflows/build-salt-onedir.yml @@ -32,8 +32,9 @@ env: COLUMNS: 190 AWS_MAX_ATTEMPTS: "10" AWS_RETRY_MODE: "adaptive" - PIP_INDEX_URL: https://pypi-proxy.saltstack.net/root/local/+simple/ - PIP_EXTRA_INDEX_URL: https://pypi.org/simple + PIP_INDEX_URL: ${{ vars.PIP_INDEX_URL }} + PIP_TRUSTED_HOST: ${{ vars.PIP_TRUSTED_HOST }} + PIP_EXTRA_INDEX_URL: ${{ vars.PIP_EXTRA_INDEX_URL }} PIP_DISABLE_PIP_VERSION_CHECK: "1" jobs: @@ -102,6 +103,8 @@ jobs: arch: ${{ github.event.repository.fork && fromJSON('["x86_64"]') || fromJSON('["x86_64", "arm64"]') }} runs-on: - ${{ matrix.arch == 'arm64' && 'macos-13-xlarge' || 'macos-12' }} + env: + PIP_INDEX_URL: https://pypi.org/simple steps: - name: "Throttle Builds" @@ -156,6 +159,9 @@ jobs: - x86 - amd64 runs-on: windows-latest + env: + PIP_INDEX_URL: https://pypi.org/simple + steps: - name: "Throttle Builds" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0a33d117b35..a64c29957c8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -437,7 +437,7 @@ jobs: salt-version: "${{ needs.prepare-workflow.outputs.salt-version }}" self-hosted-runners: ${{ fromJSON(needs.prepare-workflow.outputs.runners)['self-hosted'] }} github-hosted-runners: ${{ fromJSON(needs.prepare-workflow.outputs.runners)['github-hosted'] }} - relenv-version: "0.16.0" + relenv-version: "0.17.0" python-version: "3.10.14" build-salt-onedir: @@ -453,7 +453,7 @@ jobs: salt-version: "${{ needs.prepare-workflow.outputs.salt-version }}" self-hosted-runners: ${{ fromJSON(needs.prepare-workflow.outputs.runners)['self-hosted'] }} github-hosted-runners: ${{ fromJSON(needs.prepare-workflow.outputs.runners)['github-hosted'] }} - relenv-version: "0.16.0" + relenv-version: "0.17.0" python-version: "3.10.14" build-pkgs-onedir: @@ -466,7 +466,7 @@ jobs: with: salt-version: "${{ needs.prepare-workflow.outputs.salt-version }}" cache-prefix: ${{ needs.prepare-workflow.outputs.cache-seed }} - relenv-version: "0.16.0" + relenv-version: "0.17.0" python-version: "3.10.14" source: "onedir" @@ -480,7 +480,7 @@ jobs: with: salt-version: "${{ needs.prepare-workflow.outputs.salt-version }}" cache-prefix: ${{ needs.prepare-workflow.outputs.cache-seed }} - relenv-version: "0.16.0" + relenv-version: "0.17.0" python-version: "3.10.14" source: "src" build-ci-deps: @@ -666,27 +666,6 @@ jobs: skip-code-coverage: ${{ fromJSON(needs.prepare-workflow.outputs.testrun)['skip_code_coverage'] }} testing-releases: ${{ needs.prepare-workflow.outputs.testing-releases }} - centos-7-pkg-tests: - name: CentOS 7 Package Test - if: ${{ fromJSON(needs.prepare-workflow.outputs.jobs)['test-pkg'] && fromJSON(needs.prepare-workflow.outputs.runners)['self-hosted'] && contains(fromJSON(needs.prepare-workflow.outputs.os-labels), 'centos-7') }} - needs: - - prepare-workflow - - build-pkgs-onedir - - build-ci-deps - uses: ./.github/workflows/test-packages-action-linux.yml - with: - distro-slug: centos-7 - nox-session: ci-test-onedir - platform: linux - arch: x86_64 - salt-version: "${{ needs.prepare-workflow.outputs.salt-version }}" - pkg-type: rpm - nox-version: 2022.8.7 - python-version: "3.10" - cache-prefix: ${{ needs.prepare-workflow.outputs.cache-seed }}|3.10.14 - skip-code-coverage: ${{ fromJSON(needs.prepare-workflow.outputs.testrun)['skip_code_coverage'] }} - testing-releases: ${{ needs.prepare-workflow.outputs.testing-releases }} - debian-11-pkg-tests: name: Debian 11 Package Test if: ${{ fromJSON(needs.prepare-workflow.outputs.jobs)['test-pkg'] && fromJSON(needs.prepare-workflow.outputs.runners)['self-hosted'] && contains(fromJSON(needs.prepare-workflow.outputs.os-labels), 'debian-11') }} @@ -1579,27 +1558,6 @@ jobs: workflow-slug: ci timeout-minutes: ${{ fromJSON(needs.prepare-workflow.outputs.testrun)['type'] == 'full' && 180 || 360 }} - centos-7: - name: CentOS 7 Test - if: ${{ fromJSON(needs.prepare-workflow.outputs.jobs)['test'] && fromJSON(needs.prepare-workflow.outputs.runners)['self-hosted'] && contains(fromJSON(needs.prepare-workflow.outputs.os-labels), 'centos-7') }} - needs: - - prepare-workflow - - build-ci-deps - uses: ./.github/workflows/test-action-linux.yml - with: - distro-slug: centos-7 - nox-session: ci-test-onedir - platform: linux - arch: x86_64 - nox-version: 2022.8.7 - gh-actions-python-version: "3.10" - testrun: ${{ needs.prepare-workflow.outputs.testrun }} - salt-version: "${{ needs.prepare-workflow.outputs.salt-version }}" - cache-prefix: ${{ needs.prepare-workflow.outputs.cache-seed }}|3.10.14 - skip-code-coverage: ${{ fromJSON(needs.prepare-workflow.outputs.testrun)['skip_code_coverage'] }} - workflow-slug: ci - timeout-minutes: ${{ fromJSON(needs.prepare-workflow.outputs.testrun)['type'] == 'full' && 180 || 360 }} - debian-11: name: Debian 11 Test if: ${{ fromJSON(needs.prepare-workflow.outputs.jobs)['test'] && fromJSON(needs.prepare-workflow.outputs.runners)['self-hosted'] && contains(fromJSON(needs.prepare-workflow.outputs.os-labels), 'debian-11') }} @@ -2028,6 +1986,8 @@ jobs: name: Combine Code Coverage if: ${{ fromJSON(needs.prepare-workflow.outputs.testrun)['skip_code_coverage'] == false }} runs-on: ubuntu-latest + env: + PIP_INDEX_URL: https://pypi.org/simple needs: - prepare-workflow - build-ci-deps @@ -2046,7 +2006,6 @@ jobs: - amazonlinux-2023 - amazonlinux-2023-arm64 - archlinux-lts - - centos-7 - debian-11 - debian-11-arm64 - debian-12 @@ -2214,7 +2173,6 @@ jobs: - amazonlinux-2023 - amazonlinux-2023-arm64 - archlinux-lts - - centos-7 - debian-11 - debian-11-arm64 - debian-12 @@ -2243,7 +2201,6 @@ jobs: - amazonlinux-2-arm64-pkg-tests - amazonlinux-2023-pkg-tests - amazonlinux-2023-arm64-pkg-tests - - centos-7-pkg-tests - debian-11-pkg-tests - debian-11-arm64-pkg-tests - debian-12-pkg-tests diff --git a/.github/workflows/lint-action.yml b/.github/workflows/lint-action.yml index 9dc4be360e3..3c3df9cfb89 100644 --- a/.github/workflows/lint-action.yml +++ b/.github/workflows/lint-action.yml @@ -11,8 +11,7 @@ on: env: - PIP_INDEX_URL: https://pypi-proxy.saltstack.net/root/local/+simple/ - PIP_EXTRA_INDEX_URL: https://pypi.org/simple + PIP_INDEX_URL: https://pypi.org/simple PIP_DISABLE_PIP_VERSION_CHECK: "1" @@ -23,7 +22,7 @@ jobs: if: ${{ contains(fromJSON('["push", "schedule", "workflow_dispatch"]'), github.event_name) || fromJSON(inputs.changed-files)['salt'] || fromJSON(inputs.changed-files)['lint'] }} container: - image: ghcr.io/saltstack/salt-ci-containers/python:3.9 + image: ghcr.io/saltstack/salt-ci-containers/python:3.10 steps: - name: Install System Deps @@ -67,7 +66,7 @@ jobs: if: ${{ contains(fromJSON('["push", "schedule", "workflow_dispatch"]'), github.event_name) || fromJSON(inputs.changed-files)['tests'] || fromJSON(inputs.changed-files)['lint'] }} container: - image: ghcr.io/saltstack/salt-ci-containers/python:3.8 + image: ghcr.io/saltstack/salt-ci-containers/python:3.10 steps: - name: Install System Deps diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index 2a4d7fd73fa..ed9f9c70740 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -494,7 +494,7 @@ jobs: salt-version: "${{ needs.prepare-workflow.outputs.salt-version }}" self-hosted-runners: ${{ fromJSON(needs.prepare-workflow.outputs.runners)['self-hosted'] }} github-hosted-runners: ${{ fromJSON(needs.prepare-workflow.outputs.runners)['github-hosted'] }} - relenv-version: "0.16.0" + relenv-version: "0.17.0" python-version: "3.10.14" build-salt-onedir: @@ -510,7 +510,7 @@ jobs: salt-version: "${{ needs.prepare-workflow.outputs.salt-version }}" self-hosted-runners: ${{ fromJSON(needs.prepare-workflow.outputs.runners)['self-hosted'] }} github-hosted-runners: ${{ fromJSON(needs.prepare-workflow.outputs.runners)['github-hosted'] }} - relenv-version: "0.16.0" + relenv-version: "0.17.0" python-version: "3.10.14" build-pkgs-onedir: @@ -523,7 +523,7 @@ jobs: with: salt-version: "${{ needs.prepare-workflow.outputs.salt-version }}" cache-prefix: ${{ needs.prepare-workflow.outputs.cache-seed }} - relenv-version: "0.16.0" + relenv-version: "0.17.0" python-version: "3.10.14" source: "onedir" environment: nightly @@ -541,7 +541,7 @@ jobs: with: salt-version: "${{ needs.prepare-workflow.outputs.salt-version }}" cache-prefix: ${{ needs.prepare-workflow.outputs.cache-seed }} - relenv-version: "0.16.0" + relenv-version: "0.17.0" python-version: "3.10.14" source: "src" environment: nightly @@ -731,27 +731,6 @@ jobs: skip-code-coverage: false testing-releases: ${{ needs.prepare-workflow.outputs.testing-releases }} - centos-7-pkg-tests: - name: CentOS 7 Package Test - if: ${{ fromJSON(needs.prepare-workflow.outputs.jobs)['test-pkg'] && fromJSON(needs.prepare-workflow.outputs.runners)['self-hosted'] }} - needs: - - prepare-workflow - - build-pkgs-onedir - - build-ci-deps - uses: ./.github/workflows/test-packages-action-linux.yml - with: - distro-slug: centos-7 - nox-session: ci-test-onedir - platform: linux - arch: x86_64 - salt-version: "${{ needs.prepare-workflow.outputs.salt-version }}" - pkg-type: rpm - nox-version: 2022.8.7 - python-version: "3.10" - cache-prefix: ${{ needs.prepare-workflow.outputs.cache-seed }}|3.10.14 - skip-code-coverage: false - testing-releases: ${{ needs.prepare-workflow.outputs.testing-releases }} - debian-11-pkg-tests: name: Debian 11 Package Test if: ${{ fromJSON(needs.prepare-workflow.outputs.jobs)['test-pkg'] && fromJSON(needs.prepare-workflow.outputs.runners)['self-hosted'] }} @@ -1644,27 +1623,6 @@ jobs: workflow-slug: nightly timeout-minutes: ${{ fromJSON(needs.prepare-workflow.outputs.testrun)['type'] == 'full' && 180 || 360 }} - centos-7: - name: CentOS 7 Test - if: ${{ fromJSON(needs.prepare-workflow.outputs.jobs)['test'] && fromJSON(needs.prepare-workflow.outputs.runners)['self-hosted'] }} - needs: - - prepare-workflow - - build-ci-deps - uses: ./.github/workflows/test-action-linux.yml - with: - distro-slug: centos-7 - nox-session: ci-test-onedir - platform: linux - arch: x86_64 - nox-version: 2022.8.7 - gh-actions-python-version: "3.10" - testrun: ${{ needs.prepare-workflow.outputs.testrun }} - salt-version: "${{ needs.prepare-workflow.outputs.salt-version }}" - cache-prefix: ${{ needs.prepare-workflow.outputs.cache-seed }}|3.10.14 - skip-code-coverage: false - workflow-slug: nightly - timeout-minutes: ${{ fromJSON(needs.prepare-workflow.outputs.testrun)['type'] == 'full' && 180 || 360 }} - debian-11: name: Debian 11 Test if: ${{ fromJSON(needs.prepare-workflow.outputs.jobs)['test'] && fromJSON(needs.prepare-workflow.outputs.runners)['self-hosted'] }} @@ -2093,6 +2051,8 @@ jobs: name: Combine Code Coverage if: ${{ fromJSON(needs.prepare-workflow.outputs.testrun)['skip_code_coverage'] == false }} runs-on: ubuntu-latest + env: + PIP_INDEX_URL: https://pypi.org/simple needs: - prepare-workflow - build-ci-deps @@ -2111,7 +2071,6 @@ jobs: - amazonlinux-2023 - amazonlinux-2023-arm64 - archlinux-lts - - centos-7 - debian-11 - debian-11-arm64 - debian-12 @@ -2558,18 +2517,6 @@ jobs: distro: photon version: "5" arch: aarch64 - - pkg-type: rpm - distro: redhat - version: "7" - arch: x86_64 - - pkg-type: rpm - distro: redhat - version: "7" - arch: arm64 - - pkg-type: rpm - distro: redhat - version: "7" - arch: aarch64 - pkg-type: rpm distro: redhat version: "8" @@ -3031,7 +2978,6 @@ jobs: - amazonlinux-2023 - amazonlinux-2023-arm64 - archlinux-lts - - centos-7 - debian-11 - debian-11-arm64 - debian-12 @@ -3118,7 +3064,6 @@ jobs: - amazonlinux-2-arm64-pkg-tests - amazonlinux-2023-pkg-tests - amazonlinux-2023-arm64-pkg-tests - - centos-7-pkg-tests - debian-11-pkg-tests - debian-11-arm64-pkg-tests - debian-12-pkg-tests diff --git a/.github/workflows/release-tag.yml b/.github/workflows/release-tag.yml index 66c16da7f97..bc4624ef086 100644 --- a/.github/workflows/release-tag.yml +++ b/.github/workflows/release-tag.yml @@ -19,8 +19,9 @@ on: env: - PIP_INDEX_URL: https://pypi-proxy.saltstack.net/root/local/+simple/ - PIP_EXTRA_INDEX_URL: https://pypi.org/simple + PIP_INDEX_URL: ${{ vars.PIP_INDEX_URL }} + PIP_TRUSTED_HOST: ${{ vars.PIP_TRUSTED_HOST }} + PIP_EXTRA_INDEX_URL: ${{ vars.PIP_EXTRA_INDEX_URL }} permissions: diff --git a/.github/workflows/release-upload-virustotal.yml b/.github/workflows/release-upload-virustotal.yml index 50e71594d50..431ea00039a 100644 --- a/.github/workflows/release-upload-virustotal.yml +++ b/.github/workflows/release-upload-virustotal.yml @@ -20,8 +20,9 @@ env: COLUMNS: 190 AWS_MAX_ATTEMPTS: "10" AWS_RETRY_MODE: "adaptive" - PIP_INDEX_URL: https://pypi-proxy.saltstack.net/root/local/+simple/ - PIP_EXTRA_INDEX_URL: https://pypi.org/simple + PIP_INDEX_URL: ${{ vars.PIP_INDEX_URL }} + PIP_TRUSTED_HOST: ${{ vars.PIP_TRUSTED_HOST }} + PIP_EXTRA_INDEX_URL: ${{ vars.PIP_EXTRA_INDEX_URL }} jobs: upload-virustotal: diff --git a/.github/workflows/scheduled.yml b/.github/workflows/scheduled.yml index 6641cdb1f61..37e50f5d98d 100644 --- a/.github/workflows/scheduled.yml +++ b/.github/workflows/scheduled.yml @@ -484,7 +484,7 @@ jobs: salt-version: "${{ needs.prepare-workflow.outputs.salt-version }}" self-hosted-runners: ${{ fromJSON(needs.prepare-workflow.outputs.runners)['self-hosted'] }} github-hosted-runners: ${{ fromJSON(needs.prepare-workflow.outputs.runners)['github-hosted'] }} - relenv-version: "0.16.0" + relenv-version: "0.17.0" python-version: "3.10.14" build-salt-onedir: @@ -500,7 +500,7 @@ jobs: salt-version: "${{ needs.prepare-workflow.outputs.salt-version }}" self-hosted-runners: ${{ fromJSON(needs.prepare-workflow.outputs.runners)['self-hosted'] }} github-hosted-runners: ${{ fromJSON(needs.prepare-workflow.outputs.runners)['github-hosted'] }} - relenv-version: "0.16.0" + relenv-version: "0.17.0" python-version: "3.10.14" build-pkgs-onedir: @@ -513,7 +513,7 @@ jobs: with: salt-version: "${{ needs.prepare-workflow.outputs.salt-version }}" cache-prefix: ${{ needs.prepare-workflow.outputs.cache-seed }} - relenv-version: "0.16.0" + relenv-version: "0.17.0" python-version: "3.10.14" source: "onedir" @@ -527,7 +527,7 @@ jobs: with: salt-version: "${{ needs.prepare-workflow.outputs.salt-version }}" cache-prefix: ${{ needs.prepare-workflow.outputs.cache-seed }} - relenv-version: "0.16.0" + relenv-version: "0.17.0" python-version: "3.10.14" source: "src" build-ci-deps: @@ -713,27 +713,6 @@ jobs: skip-code-coverage: false testing-releases: ${{ needs.prepare-workflow.outputs.testing-releases }} - centos-7-pkg-tests: - name: CentOS 7 Package Test - if: ${{ fromJSON(needs.prepare-workflow.outputs.jobs)['test-pkg'] && fromJSON(needs.prepare-workflow.outputs.runners)['self-hosted'] }} - needs: - - prepare-workflow - - build-pkgs-onedir - - build-ci-deps - uses: ./.github/workflows/test-packages-action-linux.yml - with: - distro-slug: centos-7 - nox-session: ci-test-onedir - platform: linux - arch: x86_64 - salt-version: "${{ needs.prepare-workflow.outputs.salt-version }}" - pkg-type: rpm - nox-version: 2022.8.7 - python-version: "3.10" - cache-prefix: ${{ needs.prepare-workflow.outputs.cache-seed }}|3.10.14 - skip-code-coverage: false - testing-releases: ${{ needs.prepare-workflow.outputs.testing-releases }} - debian-11-pkg-tests: name: Debian 11 Package Test if: ${{ fromJSON(needs.prepare-workflow.outputs.jobs)['test-pkg'] && fromJSON(needs.prepare-workflow.outputs.runners)['self-hosted'] }} @@ -1626,27 +1605,6 @@ jobs: workflow-slug: scheduled timeout-minutes: ${{ fromJSON(needs.prepare-workflow.outputs.testrun)['type'] == 'full' && 180 || 360 }} - centos-7: - name: CentOS 7 Test - if: ${{ fromJSON(needs.prepare-workflow.outputs.jobs)['test'] && fromJSON(needs.prepare-workflow.outputs.runners)['self-hosted'] }} - needs: - - prepare-workflow - - build-ci-deps - uses: ./.github/workflows/test-action-linux.yml - with: - distro-slug: centos-7 - nox-session: ci-test-onedir - platform: linux - arch: x86_64 - nox-version: 2022.8.7 - gh-actions-python-version: "3.10" - testrun: ${{ needs.prepare-workflow.outputs.testrun }} - salt-version: "${{ needs.prepare-workflow.outputs.salt-version }}" - cache-prefix: ${{ needs.prepare-workflow.outputs.cache-seed }}|3.10.14 - skip-code-coverage: false - workflow-slug: scheduled - timeout-minutes: ${{ fromJSON(needs.prepare-workflow.outputs.testrun)['type'] == 'full' && 180 || 360 }} - debian-11: name: Debian 11 Test if: ${{ fromJSON(needs.prepare-workflow.outputs.jobs)['test'] && fromJSON(needs.prepare-workflow.outputs.runners)['self-hosted'] }} @@ -2075,6 +2033,8 @@ jobs: name: Combine Code Coverage if: ${{ fromJSON(needs.prepare-workflow.outputs.testrun)['skip_code_coverage'] == false }} runs-on: ubuntu-latest + env: + PIP_INDEX_URL: https://pypi.org/simple needs: - prepare-workflow - build-ci-deps @@ -2093,7 +2053,6 @@ jobs: - amazonlinux-2023 - amazonlinux-2023-arm64 - archlinux-lts - - centos-7 - debian-11 - debian-11-arm64 - debian-12 @@ -2263,7 +2222,6 @@ jobs: - amazonlinux-2023 - amazonlinux-2023-arm64 - archlinux-lts - - centos-7 - debian-11 - debian-11-arm64 - debian-12 @@ -2292,7 +2250,6 @@ jobs: - amazonlinux-2-arm64-pkg-tests - amazonlinux-2023-pkg-tests - amazonlinux-2023-arm64-pkg-tests - - centos-7-pkg-tests - debian-11-pkg-tests - debian-11-arm64-pkg-tests - debian-12-pkg-tests diff --git a/.github/workflows/staging.yml b/.github/workflows/staging.yml index ec32bd547de..1a1160f7521 100644 --- a/.github/workflows/staging.yml +++ b/.github/workflows/staging.yml @@ -476,7 +476,7 @@ jobs: salt-version: "${{ needs.prepare-workflow.outputs.salt-version }}" self-hosted-runners: ${{ fromJSON(needs.prepare-workflow.outputs.runners)['self-hosted'] }} github-hosted-runners: ${{ fromJSON(needs.prepare-workflow.outputs.runners)['github-hosted'] }} - relenv-version: "0.16.0" + relenv-version: "0.17.0" python-version: "3.10.14" build-salt-onedir: @@ -492,7 +492,7 @@ jobs: salt-version: "${{ needs.prepare-workflow.outputs.salt-version }}" self-hosted-runners: ${{ fromJSON(needs.prepare-workflow.outputs.runners)['self-hosted'] }} github-hosted-runners: ${{ fromJSON(needs.prepare-workflow.outputs.runners)['github-hosted'] }} - relenv-version: "0.16.0" + relenv-version: "0.17.0" python-version: "3.10.14" build-pkgs-onedir: @@ -505,7 +505,7 @@ jobs: with: salt-version: "${{ needs.prepare-workflow.outputs.salt-version }}" cache-prefix: ${{ needs.prepare-workflow.outputs.cache-seed }} - relenv-version: "0.16.0" + relenv-version: "0.17.0" python-version: "3.10.14" source: "onedir" environment: staging @@ -523,7 +523,7 @@ jobs: with: salt-version: "${{ needs.prepare-workflow.outputs.salt-version }}" cache-prefix: ${{ needs.prepare-workflow.outputs.cache-seed }} - relenv-version: "0.16.0" + relenv-version: "0.17.0" python-version: "3.10.14" source: "src" environment: staging @@ -713,27 +713,6 @@ jobs: skip-code-coverage: true testing-releases: ${{ needs.prepare-workflow.outputs.testing-releases }} - centos-7-pkg-tests: - name: CentOS 7 Package Test - if: ${{ fromJSON(needs.prepare-workflow.outputs.jobs)['test-pkg'] && fromJSON(needs.prepare-workflow.outputs.runners)['self-hosted'] }} - needs: - - prepare-workflow - - build-pkgs-onedir - - build-ci-deps - uses: ./.github/workflows/test-packages-action-linux.yml - with: - distro-slug: centos-7 - nox-session: ci-test-onedir - platform: linux - arch: x86_64 - salt-version: "${{ needs.prepare-workflow.outputs.salt-version }}" - pkg-type: rpm - nox-version: 2022.8.7 - python-version: "3.10" - cache-prefix: ${{ needs.prepare-workflow.outputs.cache-seed }}|3.10.14 - skip-code-coverage: true - testing-releases: ${{ needs.prepare-workflow.outputs.testing-releases }} - debian-11-pkg-tests: name: Debian 11 Package Test if: ${{ fromJSON(needs.prepare-workflow.outputs.jobs)['test-pkg'] && fromJSON(needs.prepare-workflow.outputs.runners)['self-hosted'] }} @@ -1626,27 +1605,6 @@ jobs: workflow-slug: staging timeout-minutes: ${{ fromJSON(needs.prepare-workflow.outputs.testrun)['type'] == 'full' && 180 || 360 }} - centos-7: - name: CentOS 7 Test - if: ${{ fromJSON(needs.prepare-workflow.outputs.jobs)['test'] && fromJSON(needs.prepare-workflow.outputs.runners)['self-hosted'] }} - needs: - - prepare-workflow - - build-ci-deps - uses: ./.github/workflows/test-action-linux.yml - with: - distro-slug: centos-7 - nox-session: ci-test-onedir - platform: linux - arch: x86_64 - nox-version: 2022.8.7 - gh-actions-python-version: "3.10" - testrun: ${{ needs.prepare-workflow.outputs.testrun }} - salt-version: "${{ needs.prepare-workflow.outputs.salt-version }}" - cache-prefix: ${{ needs.prepare-workflow.outputs.cache-seed }}|3.10.14 - skip-code-coverage: true - workflow-slug: staging - timeout-minutes: ${{ fromJSON(needs.prepare-workflow.outputs.testrun)['type'] == 'full' && 180 || 360 }} - debian-11: name: Debian 11 Test if: ${{ fromJSON(needs.prepare-workflow.outputs.jobs)['test'] && fromJSON(needs.prepare-workflow.outputs.runners)['self-hosted'] }} @@ -2381,18 +2339,6 @@ jobs: distro: photon version: "5" arch: aarch64 - - pkg-type: rpm - distro: redhat - version: "7" - arch: x86_64 - - pkg-type: rpm - distro: redhat - version: "7" - arch: arm64 - - pkg-type: rpm - distro: redhat - version: "7" - arch: aarch64 - pkg-type: rpm distro: redhat version: "8" @@ -2988,7 +2934,6 @@ jobs: - amazonlinux-2023 - amazonlinux-2023-arm64 - archlinux-lts - - centos-7 - debian-11 - debian-11-arm64 - debian-12 @@ -3017,7 +2962,6 @@ jobs: - amazonlinux-2-arm64-pkg-tests - amazonlinux-2023-pkg-tests - amazonlinux-2023-arm64-pkg-tests - - centos-7-pkg-tests - debian-11-pkg-tests - debian-11-arm64-pkg-tests - debian-12-pkg-tests diff --git a/.github/workflows/templates/ci.yml.jinja b/.github/workflows/templates/ci.yml.jinja index 400b3211cd4..a259e6dd43d 100644 --- a/.github/workflows/templates/ci.yml.jinja +++ b/.github/workflows/templates/ci.yml.jinja @@ -311,6 +311,8 @@ name: Combine Code Coverage if: ${{ fromJSON(needs.prepare-workflow.outputs.testrun)['skip_code_coverage'] == false }} runs-on: ubuntu-latest + env: + PIP_INDEX_URL: https://pypi.org/simple needs: - prepare-workflow <%- for need in test_salt_needs.iter(consume=False) %> diff --git a/.github/workflows/test-action-linux.yml b/.github/workflows/test-action-linux.yml index 95e5d3d4964..3a25fe5f57f 100644 --- a/.github/workflows/test-action-linux.yml +++ b/.github/workflows/test-action-linux.yml @@ -70,8 +70,9 @@ env: COLUMNS: 190 AWS_MAX_ATTEMPTS: "10" AWS_RETRY_MODE: "adaptive" - PIP_INDEX_URL: https://pypi-proxy.saltstack.net/root/local/+simple/ - PIP_EXTRA_INDEX_URL: https://pypi.org/simple + PIP_INDEX_URL: ${{ vars.PIP_INDEX_URL }} + PIP_TRUSTED_HOST: ${{ vars.PIP_TRUSTED_HOST }} + PIP_EXTRA_INDEX_URL: ${{ vars.PIP_EXTRA_INDEX_URL }} PIP_DISABLE_PIP_VERSION_CHECK: "1" RAISE_DEPRECATIONS_RUNTIME_ERRORS: "1" @@ -97,6 +98,8 @@ jobs: uses: ./.github/actions/setup-python-tools-scripts with: cache-prefix: ${{ inputs.cache-prefix }} + env: + PIP_INDEX_URL: https://pypi.org/simple - name: Generate Test Matrix id: generate-matrix @@ -159,7 +162,7 @@ jobs: - name: PyPi Proxy run: | - sed -i '7s;^;--index-url=https://pypi-proxy.saltstack.net/root/local/+simple/ --extra-index-url=https://pypi.org/simple\n;' requirements/static/ci/*/*.txt + sed -i '7s;^;--index-url=${{ vars.PIP_INDEX_URL }} --trusted-host ${{ vars.PIP_TRUSTED_HOST }} --extra-index-url=${{ vars.PIP_EXTRA_INDEX_URL }}\n;' requirements/static/ci/*/*.txt - name: Setup Python Tools Scripts uses: ./.github/actions/setup-python-tools-scripts @@ -307,6 +310,8 @@ jobs: needs: - test - generate-matrix + env: + PIP_INDEX_URL: https://pypi.org/simple steps: - name: Checkout Source Code diff --git a/.github/workflows/test-action-macos.yml b/.github/workflows/test-action-macos.yml index b0d3423b00c..38bf4204a9b 100644 --- a/.github/workflows/test-action-macos.yml +++ b/.github/workflows/test-action-macos.yml @@ -67,8 +67,9 @@ on: env: COLUMNS: 190 - PIP_INDEX_URL: "https://pypi-proxy.saltstack.net/root/local/+simple/" - PIP_EXTRA_INDEX_URL: "https://pypi.org/simple" + PIP_INDEX_URL: ${{ vars.PIP_INDEX_URL }} + PIP_TRUSTED_HOST: ${{ vars.PIP_TRUSTED_HOST }} + PIP_EXTRA_INDEX_URL: ${{ vars.PIP_EXTRA_INDEX_URL }} PIP_DISABLE_PIP_VERSION_CHECK: "1" RAISE_DEPRECATIONS_RUNTIME_ERRORS: "1" @@ -94,6 +95,8 @@ jobs: uses: ./.github/actions/setup-python-tools-scripts with: cache-prefix: ${{ inputs.cache-prefix }} + env: + PIP_INDEX_URL: https://pypi.org/simple - name: Generate Test Matrix id: generate-matrix @@ -162,6 +165,8 @@ jobs: - name: Install Nox run: | python3 -m pip install 'nox==${{ inputs.nox-version }}' + env: + PIP_INDEX_URL: https://pypi.org/simple - name: Decompress .nox Directory run: | @@ -335,6 +340,8 @@ jobs: needs: - test - generate-matrix + env: + PIP_INDEX_URL: https://pypi.org/simple steps: - name: Checkout Source Code diff --git a/.github/workflows/test-action-windows.yml b/.github/workflows/test-action-windows.yml index fae0db5ca99..4110a3a644d 100644 --- a/.github/workflows/test-action-windows.yml +++ b/.github/workflows/test-action-windows.yml @@ -70,8 +70,9 @@ env: COLUMNS: 190 AWS_MAX_ATTEMPTS: "10" AWS_RETRY_MODE: "adaptive" - PIP_INDEX_URL: https://pypi-proxy.saltstack.net/root/local/+simple/ - PIP_EXTRA_INDEX_URL: https://pypi.org/simple + PIP_INDEX_URL: ${{ vars.PIP_INDEX_URL }} + PIP_TRUSTED_HOST: ${{ vars.PIP_TRUSTED_HOST }} + PIP_EXTRA_INDEX_URL: ${{ vars.PIP_EXTRA_INDEX_URL }} PIP_DISABLE_PIP_VERSION_CHECK: "1" RAISE_DEPRECATIONS_RUNTIME_ERRORS: "1" @@ -97,6 +98,8 @@ jobs: uses: ./.github/actions/setup-python-tools-scripts with: cache-prefix: ${{ inputs.cache-prefix }} + env: + PIP_INDEX_URL: https://pypi.org/simple - name: Generate Test Matrix id: generate-matrix @@ -159,7 +162,7 @@ jobs: - name: PyPi Proxy run: | - sed -i '7s;^;--index-url=https://pypi-proxy.saltstack.net/root/local/+simple/ --extra-index-url=https://pypi.org/simple\n;' requirements/static/ci/*/*.txt + sed -i '7s;^;--index-url=${{ vars.PIP_INDEX_URL }} --trusted-host ${{ vars.PIP_TRUSTED_HOST }} --extra-index-url=${{ vars.PIP_EXTRA_INDEX_URL }}\n;' requirements/static/ci/*/*.txt - name: Setup Python Tools Scripts uses: ./.github/actions/setup-python-tools-scripts @@ -308,6 +311,8 @@ jobs: needs: - test - generate-matrix + env: + PIP_INDEX_URL: https://pypi.org/simple steps: - name: Checkout Source Code diff --git a/.github/workflows/test-package-downloads-action.yml b/.github/workflows/test-package-downloads-action.yml index 7532813999a..22e3e58bcfb 100644 --- a/.github/workflows/test-package-downloads-action.yml +++ b/.github/workflows/test-package-downloads-action.yml @@ -48,8 +48,9 @@ env: COLUMNS: 190 AWS_MAX_ATTEMPTS: "10" AWS_RETRY_MODE: "adaptive" - PIP_INDEX_URL: https://pypi-proxy.saltstack.net/root/local/+simple/ - PIP_EXTRA_INDEX_URL: https://pypi.org/simple + PIP_INDEX_URL: ${{ vars.PIP_INDEX_URL }} + PIP_TRUSTED_HOST: ${{ vars.PIP_TRUSTED_HOST }} + PIP_EXTRA_INDEX_URL: ${{ vars.PIP_EXTRA_INDEX_URL }} PIP_DISABLE_PIP_VERSION_CHECK: "1" RAISE_DEPRECATIONS_RUNTIME_ERRORS: "1" @@ -74,6 +75,8 @@ jobs: uses: ./.github/actions/setup-python-tools-scripts with: cache-prefix: ${{ inputs.cache-prefix }} + env: + PIP_INDEX_URL: https://pypi.org/simple - name: Generate Test Matrix id: generate-matrix @@ -296,6 +299,7 @@ jobs: runs-on: ${{ matrix.distro-slug == 'macos-13-arm64' && 'macos-13-xlarge' || matrix.distro-slug }} env: USE_S3_CACHE: 'false' + PIP_INDEX_URL: https://pypi.org/simple environment: ${{ inputs.environment }} timeout-minutes: 120 # 2 Hours - More than this and something is wrong strategy: diff --git a/.github/workflows/test-packages-action-linux.yml b/.github/workflows/test-packages-action-linux.yml index a5c00db88ea..432b8e04bb4 100644 --- a/.github/workflows/test-packages-action-linux.yml +++ b/.github/workflows/test-packages-action-linux.yml @@ -65,8 +65,9 @@ env: COLUMNS: 190 AWS_MAX_ATTEMPTS: "10" AWS_RETRY_MODE: "adaptive" - PIP_INDEX_URL: https://pypi-proxy.saltstack.net/root/local/+simple/ - PIP_EXTRA_INDEX_URL: https://pypi.org/simple + PIP_INDEX_URL: ${{ vars.PIP_INDEX_URL }} + PIP_TRUSTED_HOST: ${{ vars.PIP_TRUSTED_HOST }} + PIP_EXTRA_INDEX_URL: ${{ vars.PIP_EXTRA_INDEX_URL }} PIP_DISABLE_PIP_VERSION_CHECK: "1" RAISE_DEPRECATIONS_RUNTIME_ERRORS: "1" USE_S3_CACHE: 'true' diff --git a/.github/workflows/test-packages-action-macos.yml b/.github/workflows/test-packages-action-macos.yml index 2e27f1e9849..5e8c3069178 100644 --- a/.github/workflows/test-packages-action-macos.yml +++ b/.github/workflows/test-packages-action-macos.yml @@ -62,8 +62,9 @@ on: env: COLUMNS: 190 - PIP_INDEX_URL: https://pypi-proxy.saltstack.net/root/local/+simple/ - PIP_EXTRA_INDEX_URL: https://pypi.org/simple + PIP_INDEX_URL: ${{ vars.PIP_INDEX_URL }} + PIP_TRUSTED_HOST: ${{ vars.PIP_TRUSTED_HOST }} + PIP_EXTRA_INDEX_URL: ${{ vars.PIP_EXTRA_INDEX_URL }} PIP_DISABLE_PIP_VERSION_CHECK: "1" RAISE_DEPRECATIONS_RUNTIME_ERRORS: "1" @@ -162,6 +163,8 @@ jobs: - name: Install Nox run: | python3 -m pip install 'nox==${{ inputs.nox-version }}' + env: + PIP_INDEX_URL: https://pypi.org/simple - name: Download nox.macos.${{ inputs.arch }}.tar.* artifact for session ${{ inputs.nox-session }} uses: actions/download-artifact@v4 @@ -263,3 +266,5 @@ jobs: - name: Install Nox run: | python3 -m pip install 'nox==${{ inputs.nox-version }}' + env: + PIP_INDEX_URL: https://pypi.org/simple diff --git a/.github/workflows/test-packages-action-windows.yml b/.github/workflows/test-packages-action-windows.yml index 967482ac204..c21100f4e69 100644 --- a/.github/workflows/test-packages-action-windows.yml +++ b/.github/workflows/test-packages-action-windows.yml @@ -65,8 +65,9 @@ env: COLUMNS: 190 AWS_MAX_ATTEMPTS: "10" AWS_RETRY_MODE: "adaptive" - PIP_INDEX_URL: https://pypi-proxy.saltstack.net/root/local/+simple/ - PIP_EXTRA_INDEX_URL: https://pypi.org/simple + PIP_INDEX_URL: ${{ vars.PIP_INDEX_URL }} + PIP_TRUSTED_HOST: ${{ vars.PIP_TRUSTED_HOST }} + PIP_EXTRA_INDEX_URL: ${{ vars.PIP_EXTRA_INDEX_URL }} PIP_DISABLE_PIP_VERSION_CHECK: "1" RAISE_DEPRECATIONS_RUNTIME_ERRORS: "1" diff --git a/SECURITY.md b/SECURITY.md index 97afd202de6..eaf22ee8b5e 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -4,77 +4,65 @@ - saltproject-security.pdl@broadcom.com **GPG key ID:** -- 4EA0793D +- 37654A06 **GPG key fingerprint:** -- `8ABE 4EFC F0F4 B24B FF2A AF90 D570 F2D3 4EA0 793D` +- `99EF 26F2 6469 2D24 973A 7007 E8BF 76A7 3765 4A06` **GPG Public Key** ``` -----BEGIN PGP PUBLIC KEY BLOCK----- -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 -vtBIWO4LPeGEvb2Gs65PL2eouOqU6yvBr5Y= -=F/97 +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 +=pPmt -----END PGP PUBLIC KEY BLOCK----- ``` diff --git a/changelog/50196.fixed.md b/changelog/50196.fixed.md new file mode 100644 index 00000000000..979411a640d --- /dev/null +++ b/changelog/50196.fixed.md @@ -0,0 +1 @@ +Made slsutil.renderer work with salt-ssh diff --git a/changelog/51605.fixed.md b/changelog/51605.fixed.md new file mode 100644 index 00000000000..990b34413d9 --- /dev/null +++ b/changelog/51605.fixed.md @@ -0,0 +1 @@ +Fixed defaults.merge is not available when using salt-ssh diff --git a/changelog/56441.fixed.md b/changelog/56441.fixed.md new file mode 100644 index 00000000000..489ad80f770 --- /dev/null +++ b/changelog/56441.fixed.md @@ -0,0 +1 @@ +Fixed config.get does not support merge option with salt-ssh diff --git a/changelog/57649.fixed.md b/changelog/57649.fixed.md new file mode 100644 index 00000000000..12d22a0531c --- /dev/null +++ b/changelog/57649.fixed.md @@ -0,0 +1 @@ + Update to include croniter in pkg requirements diff --git a/changelog/61100.fixed.md b/changelog/61100.fixed.md new file mode 100644 index 00000000000..d7ac2b6bc3f --- /dev/null +++ b/changelog/61100.fixed.md @@ -0,0 +1 @@ +Fixed state.test does not work with salt-ssh diff --git a/changelog/61143.fixed.md b/changelog/61143.fixed.md new file mode 100644 index 00000000000..08a62c9d8b1 --- /dev/null +++ b/changelog/61143.fixed.md @@ -0,0 +1 @@ +Made slsutil.findup work with salt-ssh diff --git a/changelog/64300.fixed.md b/changelog/64300.fixed.md new file mode 100644 index 00000000000..4418db1d04c --- /dev/null +++ b/changelog/64300.fixed.md @@ -0,0 +1 @@ +Fix utf8 handling in 'pass' renderer diff --git a/changelog/64728.fixed.md b/changelog/64728.fixed.md new file mode 100644 index 00000000000..afe36f42316 --- /dev/null +++ b/changelog/64728.fixed.md @@ -0,0 +1 @@ +salt-cloud honors root_dir config setting for log_file location and fixes for root_dir locations on windows. diff --git a/changelog/65067.fixed.md b/changelog/65067.fixed.md new file mode 100644 index 00000000000..d6de87b5bc1 --- /dev/null +++ b/changelog/65067.fixed.md @@ -0,0 +1 @@ +Fixed slsutil.update with salt-ssh during template rendering diff --git a/changelog/65251.fixed.md b/changelog/65251.fixed.md new file mode 100644 index 00000000000..e8abd5af327 --- /dev/null +++ b/changelog/65251.fixed.md @@ -0,0 +1 @@ +Fix config.items when called on minion diff --git a/changelog/65304.fixed.md b/changelog/65304.fixed.md new file mode 100644 index 00000000000..dd162cee714 --- /dev/null +++ b/changelog/65304.fixed.md @@ -0,0 +1 @@ +pkg.installed state aggregate does not honors requires requisite diff --git a/changelog/65630.fixed.md b/changelog/65630.fixed.md new file mode 100644 index 00000000000..e8650abcdc1 --- /dev/null +++ b/changelog/65630.fixed.md @@ -0,0 +1 @@ +Added SSH wrapper for logmod diff --git a/changelog/65816.fixed.md b/changelog/65816.fixed.md new file mode 100644 index 00000000000..23aaa1e5e8e --- /dev/null +++ b/changelog/65816.fixed.md @@ -0,0 +1 @@ +Fix for GitFS failure to unlock lock file, and resource cleanup for process SIGTERM diff --git a/changelog/66330.fixed.md b/changelog/66330.fixed.md new file mode 100644 index 00000000000..25d7caf9923 --- /dev/null +++ b/changelog/66330.fixed.md @@ -0,0 +1 @@ +fix #66194: Exchange HTTPClient by AsyncHTTPClient in salt.utils.http diff --git a/changelog/66347.fixed.md b/changelog/66347.fixed.md new file mode 100644 index 00000000000..e61e5ce64a9 --- /dev/null +++ b/changelog/66347.fixed.md @@ -0,0 +1 @@ +Fix win_task ExecutionTimeLimit and result/error code interpretation diff --git a/changelog/66414.fixed.md b/changelog/66414.fixed.md new file mode 100644 index 00000000000..e777d18226d --- /dev/null +++ b/changelog/66414.fixed.md @@ -0,0 +1 @@ +Fixed x509_v2 certificate.managed crash for locally signed certificates if the signing policy defines signing_private_key diff --git a/changelog/66441.fixed.md b/changelog/66441.fixed.md new file mode 100644 index 00000000000..e61e5ce64a9 --- /dev/null +++ b/changelog/66441.fixed.md @@ -0,0 +1 @@ +Fix win_task ExecutionTimeLimit and result/error code interpretation diff --git a/changelog/66579.fixed.md b/changelog/66579.fixed.md new file mode 100644 index 00000000000..ccef663b846 --- /dev/null +++ b/changelog/66579.fixed.md @@ -0,0 +1 @@ +Fix support for FIPS approved encryption and signing algorithms. diff --git a/changelog/66604.fixed.md b/changelog/66604.fixed.md new file mode 100644 index 00000000000..4d1a771ca54 --- /dev/null +++ b/changelog/66604.fixed.md @@ -0,0 +1 @@ +Fix RPM package provides diff --git a/changelog/66623.deprecated.md b/changelog/66623.deprecated.md new file mode 100644 index 00000000000..8d829eadec9 --- /dev/null +++ b/changelog/66623.deprecated.md @@ -0,0 +1 @@ +Drop CentOS 7 support diff --git a/changelog/66624.added.md b/changelog/66624.added.md new file mode 100644 index 00000000000..fbc4adf84c7 --- /dev/null +++ b/changelog/66624.added.md @@ -0,0 +1 @@ +Build RPM packages with Rocky Linux 9 (instead of CentOS Stream 9) diff --git a/changelog/66624.deprecated.md b/changelog/66624.deprecated.md new file mode 100644 index 00000000000..10b397bae85 --- /dev/null +++ b/changelog/66624.deprecated.md @@ -0,0 +1 @@ +No longer build RPM packages with CentOS Stream 9 diff --git a/changelog/66632.fixed.md b/changelog/66632.fixed.md new file mode 100644 index 00000000000..c50213867ca --- /dev/null +++ b/changelog/66632.fixed.md @@ -0,0 +1 @@ +Upgrade relAenv to 0.16.1. This release fixes several package installs for salt-pip diff --git a/changelog/66663.fixed.md b/changelog/66663.fixed.md new file mode 100644 index 00000000000..14a40b4730e --- /dev/null +++ b/changelog/66663.fixed.md @@ -0,0 +1 @@ +Upgrade relenv to 0.17.0 (https://github.com/saltstack/relenv/blob/v0.17.0/CHANGELOG.md) diff --git a/changelog/66666.fixed.md b/changelog/66666.fixed.md new file mode 100644 index 00000000000..076088f4d0c --- /dev/null +++ b/changelog/66666.fixed.md @@ -0,0 +1,4 @@ +Upgrade dependencies due to security issues: +- pymysql>=1.1.1 +- requests>=2.32.0 +- docker>=7.1.0 diff --git a/changelog/66683.fixed.md b/changelog/66683.fixed.md new file mode 100644 index 00000000000..2917188fa63 --- /dev/null +++ b/changelog/66683.fixed.md @@ -0,0 +1 @@ +Corrected missed line in branch 3006.x when backporting from PR 61620 and 65044 diff --git a/cicd/golden-images.json b/cicd/golden-images.json index ee7a8acc80e..ca7818fdd6b 100644 --- a/cicd/golden-images.json +++ b/cicd/golden-images.json @@ -49,26 +49,6 @@ "is_windows": "false", "ssh_username": "arch" }, - "centos-7-arm64": { - "ami": "ami-0ef52419c91cb0169", - "ami_description": "CI Image of CentOS 7 arm64", - "ami_name": "salt-project/ci/centos/7/arm64/20240509.1530", - "arch": "arm64", - "cloudwatch-agent-available": "true", - "instance_type": "m6g.large", - "is_windows": "false", - "ssh_username": "centos" - }, - "centos-7": { - "ami": "ami-0973c8d1b91dcba5c", - "ami_description": "CI Image of CentOS 7 x86_64", - "ami_name": "salt-project/ci/centos/7/x86_64/20240509.1530", - "arch": "x86_64", - "cloudwatch-agent-available": "true", - "instance_type": "t3a.large", - "is_windows": "false", - "ssh_username": "centos" - }, "debian-11-arm64": { "ami": "ami-0eff227d9a94d8692", "ami_description": "CI Image of Debian 11 arm64", diff --git a/cicd/shared-gh-workflows-context.yml b/cicd/shared-gh-workflows-context.yml index 3cf74f2766b..68a5dd25541 100644 --- a/cicd/shared-gh-workflows-context.yml +++ b/cicd/shared-gh-workflows-context.yml @@ -1,6 +1,6 @@ nox_version: "2022.8.7" python_version: "3.10.14" -relenv_version: "0.16.0" +relenv_version: "0.17.0" release_branches: - "3006.x" - "3007.x" diff --git a/doc/ref/configuration/master.rst b/doc/ref/configuration/master.rst index ef1d0e7a93d..d5093e0c471 100644 --- a/doc/ref/configuration/master.rst +++ b/doc/ref/configuration/master.rst @@ -96,6 +96,14 @@ The user to run the Salt processes user: root +.. note:: + + Starting with version `3006.0`, Salt's offical packages ship with a default + configuration which runs the Master as a non-priviledged user. The Master's + configuration file has the `user` option set to `user: salt`. Unless you + are absolutly sure want to run salt as some other user, care should be + taken to preserve this setting in your Master configuration file.. + .. conf_master:: ret_port ``enable_ssh_minions`` @@ -2123,6 +2131,20 @@ The number of seconds between AES key rotations on the master. .. conf_master:: ssl + +``publish_signing_algorithm`` +----------------------------- + +.. versionadded:: 3006.9 + +Default: PKCS1v15-SHA1 + +The RSA signing algorithm used by this minion when connecting to the +master's request channel. Valid values are ``PKCS1v15-SHA1`` and +``PKCS1v15-SHA224``. Minions must be at version ``3006.9`` or greater if this +is changed from the default setting. + + ``ssl`` ------- diff --git a/doc/ref/configuration/minion.rst b/doc/ref/configuration/minion.rst index 46a357c611f..a69c315a735 100644 --- a/doc/ref/configuration/minion.rst +++ b/doc/ref/configuration/minion.rst @@ -3170,6 +3170,28 @@ constant names without ssl module prefix: ``CERT_REQUIRED`` or ``PROTOCOL_SSLv23 certfile: ssl_version: PROTOCOL_TLSv1_2 +``encryption_algorithm`` +------------------------ + +.. versionadded:: 3006.9 + +Default: OAEP-SHA1 + +The RSA encryption algorithm used by this minion when connecting to the +master's request channel. Valid values are ``OAEP-SHA1`` and ``OAEP-SHA224`` + + +``signing_algorithm`` +------------------------ + +.. versionadded:: 3006.9 + +Default: PKCS1v15-SHA1 + +The RSA signing algorithm used by this minion when connecting to the +master's request channel. Valid values are ``PKCS1v15-SHA1`` and +``PKCS1v15-SHA224`` + Reactor Settings ================ diff --git a/doc/security/index.rst b/doc/security/index.rst index e5a36381e4e..13a6b71f665 100644 --- a/doc/security/index.rst +++ b/doc/security/index.rst @@ -5,8 +5,8 @@ Security disclosure policy ========================== :email: saltproject-security.pdl@broadcom.com -:gpg key ID: 4EA0793D -:gpg key fingerprint: ``8ABE 4EFC F0F4 B24B FF2A AF90 D570 F2D3 4EA0 793D`` +:gpg key ID: 37654A06 +:gpg key fingerprint: ``99EF 26F2 6469 2D24 973A 7007 E8BF 76A7 3765 4A06`` **gpg public key:** @@ -14,104 +14,55 @@ Security disclosure policy -----BEGIN PGP PUBLIC KEY BLOCK----- - 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 - fwPKmQ2cKnCBs5ASj1DkgUcz2c8DTUPVqg== - =i1Tf + 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 + =pPmt -----END PGP PUBLIC KEY BLOCK----- The SaltStack Security Team is available at saltproject-security.pdl@broadcom.com for diff --git a/pkg/rpm/salt.spec b/pkg/rpm/salt.spec index 2471ad81da2..637b3f65079 100644 --- a/pkg/rpm/salt.spec +++ b/pkg/rpm/salt.spec @@ -10,8 +10,9 @@ %define __brp_python_hardlink /usr/bin/true # Disable private libraries from showing in provides -%global __provides_exclude_from ^lib/.*\\.so.*$ -%global __requires_exclude_from ^lib/.*\\.so.*$ +%global __to_exclude .*\\.so.* +%global __provides_exclude_from ^.*$ +%global __requires_exclude_from ^.*$ %define _source_payload w2.gzdio %define _binary_payload w2.gzdio %define _SALT_GROUP salt diff --git a/pkg/windows/install_vs_buildtools.ps1 b/pkg/windows/install_vs_buildtools.ps1 index 1d51058d2f1..5988ae5a1ae 100644 --- a/pkg/windows/install_vs_buildtools.ps1 +++ b/pkg/windows/install_vs_buildtools.ps1 @@ -25,6 +25,8 @@ param( [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]::Tls12 $ProgressPreference = "SilentlyContinue" $ErrorActionPreference = "Stop" +# https://stackoverflow.com/a/67201331/4581998 +$env:PSModulePath = [Environment]::GetEnvironmentVariable('PSModulePath', 'Machine') #------------------------------------------------------------------------------- # Script Functions @@ -39,6 +41,43 @@ function Write-Result($result, $ForegroundColor="Green") { } } +function Add-Certificate { + [CmdletBinding()] + param( + + [Parameter(Mandatory=$true)] + # The path in the certstore (CERT:/LocalMachine/Root/) + [String] $Path, + + [Parameter(Mandatory=$true)] + # The path to the cert file for importing + [String] $File, + + [Parameter(Mandatory=$true)] + # The name of the cert file for importing + [String] $Name + + ) + + # Validation + if ( ! (Test-Path -Path $File)) { + Write-Host "Invalid path to certificate file" + exit 1 + } + + if (! (Test-Path -Path $Path) ) { + + Write-Host "Installing Certificate $Name`: " -NoNewLine + $output = Import-Certificate -FilePath $File -CertStoreLocation "Cert:\LocalMachine\Root" + if ( Test-Path -Path $Path ) { + Write-Result "Success" + } else { + Write-Result "Failed" -ForegroundColor Yellow + Write-Host $output + } + } +} + #------------------------------------------------------------------------------- # Start the Script #------------------------------------------------------------------------------- @@ -53,25 +92,32 @@ Write-Host $("-" * 80) # Dependency Variables $VS_BLD_TOOLS = "https://aka.ms/vs/15/release/vs_buildtools.exe" -$VS_CL_BIN = "${env:ProgramFiles(x86)}\Microsoft Visual Studio 14.0\VC\bin\cl.exe" -$MSBUILD_BIN = "${env:ProgramFiles(x86)}\Microsoft Visual Studio\2017\BuildTools\MSBuild\15.0\Bin\msbuild.exe" -$WIN10_SDK_RC = "${env:ProgramFiles(x86)}\Windows Kits\10\bin\10.0.17763.0\x64\rc.exe" +try { + # If VS is installed, you will be able to get the WMI Object MSFT_VSInstance + $VS_INST_LOC = $(Get-CimInstance MSFT_VSInstance -Namespace root/cimv2/vs).InstallLocation + $MSBUILD_BIN = $(Get-ChildItem "$VS_INST_LOC\MSBuild\*\Bin\msbuild.exe").FullName +} catch { + # If VS is not installed, this is the fallback for this installation + $MSBUILD_BIN = "${env:ProgramFiles(x86)}\Microsoft Visual Studio\2017\BuildTools\MSBuild\15.0\Bin\msbuild.exe" +} #------------------------------------------------------------------------------- # Visual Studio #------------------------------------------------------------------------------- -$install_build_tools = $false Write-Host "Confirming Presence of Visual Studio Build Tools: " -NoNewline -@($VS_CL_BIN, $MSBUILD_BIN, $WIN10_SDK_RC) | ForEach-Object { - if ( ! (Test-Path -Path $_) ) { - $install_build_tools = $true - } -} - -if ( $install_build_tools ) { +# We're only gonna look for msbuild.exe +if ( Test-Path -Path $MSBUILD_BIN ) { + Write-Result "Success" -ForegroundColor Green +} else { Write-Result "Missing" -ForegroundColor Yellow + try { + # If VS is installed, you will be able to get the WMI Object MSFT_VSInstance + Write-Host "Get VS Instance Information" + Get-CimInstance MSFT_VSInstance -Namespace root/cimv2/vs + } catch {} + Write-Host "Checking available disk space: " -NoNewLine $available = (Get-PSDrive $env:SystemDrive.Trim(":")).Free if ( $available -gt (1024 * 1024 * 1024 * 9.1) ) { @@ -101,7 +147,6 @@ if ( $install_build_tools ) { "--add Microsoft.VisualStudio.Workload.MSBuildTools", ` "--add Microsoft.VisualStudio.Workload.VCTools", ` "--add Microsoft.VisualStudio.Component.Windows81SDK", ` - "--add Microsoft.VisualStudio.Component.Windows10SDK.17763", ` "--add Microsoft.VisualStudio.Component.VC.140", ` "--lang en-US", ` "--includeRecommended", ` @@ -115,51 +160,35 @@ if ( $install_build_tools ) { exit 1 } - # Serial: 28cc3a25bfba44ac449a9b586b4339a + # Serial: 28cc3a25bfba44ac449a9b586b4339aa # Hash: 3b1efd3a66ea28b16697394703a72ca340a05bd5 - if (! (Test-Path -Path Cert:\LocalMachine\Root\3b1efd3a66ea28b16697394703a72ca340a05bd5) ) { - Write-Host "Installing Certificate Sign Root Certificate: " -NoNewLine - Start-Process -FilePath "certutil" ` - -ArgumentList "-addstore", ` - "Root", ` - "$($env:TEMP)\build_tools\certificates\manifestCounterSignRootCertificate.cer" ` - -Wait -WindowStyle Hidden - if ( Test-Path -Path Cert:\LocalMachine\Root\3b1efd3a66ea28b16697394703a72ca340a05bd5 ) { - Write-Result "Success" -ForegroundColor Green - } else { - Write-Result "Failed" -ForegroundColor Yellow - } - } + $cert_name = "Sign Root Certificate" + $cert_path = "Cert:\LocalMachine\Root\3b1efd3a66ea28b16697394703a72ca340a05bd5" + $cert_file = "$env:TEMP\build_tools\certificates\manifestCounterSignRootCertificate.cer" + Add-Certificate -Name $cert_name -Path $cert_path -File $cert_file # Serial: 3f8bc8b5fc9fb29643b569d66c42e144 # Hash: 8f43288ad272f3103b6fb1428485ea3014c0bcfe - if (! (Test-Path -Path Cert:\LocalMachine\Root\8f43288ad272f3103b6fb1428485ea3014c0bcfe) ) { - Write-Host "Installing Certificate Root Certificate: " -NoNewLine - Start-Process -FilePath "certutil" ` - -ArgumentList "-addstore", ` - "Root", ` - "$($env:TEMP)\build_tools\certificates\manifestRootCertificate.cer" ` - -Wait -WindowStyle Hidden - if ( Test-Path -Path Cert:\LocalMachine\Root\8f43288ad272f3103b6fb1428485ea3014c0bcfe ) { - Write-Result "Success" -ForegroundColor Green - } else { - Write-Result "Failed" -ForegroundColor Yellow - } - } + $cert_name = "Root Certificate" + $cert_path = "Cert:\LocalMachine\Root\8f43288ad272f3103b6fb1428485ea3014c0bcfe" + $cert_file = "$env:TEMP\build_tools\certificates\manifestRootCertificate.cer" + Add-Certificate -Name $cert_name -Path $cert_path -File $cert_file Write-Host "Installing Visual Studio 2017 build tools: " -NoNewline - Start-Process -FilePath "$env:TEMP\build_tools\vs_setup.exe" ` - -ArgumentList "--wait", "--noweb", "--quiet" ` - -Wait - @($VS_CL_BIN, $MSBUILD_BIN, $WIN10_SDK_RC) | ForEach-Object { - if ( ! (Test-Path -Path $_) ) { - Write-Result "Failed" -ForegroundColor Red - exit 1 - } + $proc = Start-Process ` + -FilePath "$env:TEMP\build_tools\vs_setup.exe" ` + -ArgumentList "--wait", "--noweb", "--quiet" ` + -PassThru -Wait ` + -RedirectStandardOutput "$env:TEMP\stdout.txt" + if ( Test-Path -Path $MSBUILD_BIN ) { + Write-Result "Failed" -ForegroundColor Red + Write-Host "Missing: $_" + Write-Host "ExitCode: $($proc.ExitCode)" + Write-Host "STDOUT:" + Get-Content "$env:TEMP\stdout.txt" + exit 1 } Write-Result "Success" -ForegroundColor Green -} else { - Write-Result "Success" -ForegroundColor Green } #------------------------------------------------------------------------------- diff --git a/requirements/base.txt b/requirements/base.txt index 406754b8ed7..e6ab6f051c4 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -5,13 +5,15 @@ jmespath msgpack>=1.0.0 PyYAML MarkupSafe -requests>=2.25.1 +requests>=2.31.0 ; python_version < '3.8' +requests>=2.32.0 ; python_version >= '3.8' distro>=1.0.1 psutil>=5.0.0 packaging>=21.3 looseversion tornado>=6.3.3 aiohttp>=3.9.0 +croniter>=0.3.0,!=0.3.22; sys_platform != 'win32' # We need contextvars for salt-ssh. # Even on python versions which ships with contextvars in the standard library! diff --git a/requirements/pytest.txt b/requirements/pytest.txt index d53137d6601..ce8b9569125 100644 --- a/requirements/pytest.txt +++ b/requirements/pytest.txt @@ -1,6 +1,7 @@ mock >= 3.0.0 # PyTest -docker +docker >= 7.1.0; python_version >= '3.8' +docker < 7.1.0; python_version < '3.8' pytest >= 7.2.0 pytest-salt-factories >= 1.0.0 pytest-helpers-namespace >= 2019.1.8 diff --git a/requirements/static/ci/common.in b/requirements/static/ci/common.in index 8f111bb4af6..19b0305ba7d 100644 --- a/requirements/static/ci/common.in +++ b/requirements/static/ci/common.in @@ -12,7 +12,6 @@ certifi>=2022.12.07 cffi>=1.14.6 cherrypy>=17.4.1 clustershell -croniter>=0.3.0,!=0.3.22"; sys_platform != 'win32' dnspython etcd3-py==0.1.6 gitpython>=3.1.37 diff --git a/requirements/static/ci/lint.in b/requirements/static/ci/lint.in index 615bf80187e..d98d16af7ad 100644 --- a/requirements/static/ci/lint.in +++ b/requirements/static/ci/lint.in @@ -2,7 +2,8 @@ --constraint=./py{py_version}/{platform}.txt -docker +docker >= 7.1.0; python_version >= '3.8' +docker < 7.1.0; python_version < '3.8' pylint~=3.1.0 SaltPyLint>=2024.2.2 toml diff --git a/requirements/static/ci/linux.in b/requirements/static/ci/linux.in index 8d247386d61..c52c53dc58b 100644 --- a/requirements/static/ci/linux.in +++ b/requirements/static/ci/linux.in @@ -3,7 +3,7 @@ pyiface pygit2>=1.10.1 -pymysql>=1.0.2 +pymysql>=1.1.1 ansible>=9.1.0; python_version >= '3.10' twilio python-telegram-bot>=13.7 diff --git a/requirements/static/ci/py3.10/cloud.txt b/requirements/static/ci/py3.10/cloud.txt index 91ba901e5d6..e439ead8583 100644 --- a/requirements/static/ci/py3.10/cloud.txt +++ b/requirements/static/ci/py3.10/cloud.txt @@ -51,7 +51,7 @@ pywinrm==0.4.3 # via -r requirements/static/ci/cloud.in requests-ntlm==1.2.0 # via pywinrm -requests==2.31.0 +requests==2.32.3 ; python_version >= "3.8" # via # -c requirements/static/ci/py3.10/linux.txt # apache-libcloud diff --git a/requirements/static/ci/py3.10/darwin.txt b/requirements/static/ci/py3.10/darwin.txt index 1aa190e6918..cbf72ae9d3a 100644 --- a/requirements/static/ci/py3.10/darwin.txt +++ b/requirements/static/ci/py3.10/darwin.txt @@ -99,8 +99,10 @@ contextvars==2.4 # via # -c requirements/static/ci/../pkg/py3.10/darwin.txt # -r requirements/base.txt -croniter==1.3.15 ; sys_platform != "win32" - # via -r requirements/static/ci/common.in +croniter==2.0.5 ; sys_platform != "win32" + # via + # -c requirements/static/ci/../pkg/py3.10/darwin.txt + # -r requirements/base.txt cryptography==42.0.5 # via # -c requirements/static/ci/../pkg/py3.10/darwin.txt @@ -122,7 +124,7 @@ dnspython==2.6.1 # via # -r requirements/static/ci/common.in # python-etcd -docker==6.1.3 +docker==7.1.0 ; python_version >= "3.8" # via -r requirements/pytest.txt etcd3-py==0.1.6 # via -r requirements/static/ci/common.in @@ -288,7 +290,6 @@ packaging==23.1 # via # -c requirements/static/ci/../pkg/py3.10/darwin.txt # -r requirements/base.txt - # docker # pytest paramiko==3.4.0 # via @@ -414,6 +415,7 @@ python-gnupg==0.5.2 pytz==2024.1 # via # -c requirements/static/ci/../pkg/py3.10/darwin.txt + # croniter # tempora pyvmomi==8.0.1.0.1 # via -r requirements/static/ci/common.in @@ -435,7 +437,7 @@ pyzmq==25.1.2 # -c requirements/static/ci/../pkg/py3.10/darwin.txt # -r requirements/zeromq.txt # pytest-salt-factories -requests==2.31.0 +requests==2.32.3 ; python_version >= "3.8" # via # -c requirements/static/ci/../pkg/py3.10/darwin.txt # -r requirements/base.txt @@ -552,9 +554,7 @@ virtualenv==20.24.7 watchdog==3.0.0 # via -r requirements/static/ci/common.in websocket-client==0.40.0 - # via - # docker - # kubernetes + # via kubernetes wempy==0.2.1 # via -r requirements/static/ci/common.in werkzeug==3.0.3 diff --git a/requirements/static/ci/py3.10/docs.txt b/requirements/static/ci/py3.10/docs.txt index aede008af76..3eef9acc60d 100644 --- a/requirements/static/ci/py3.10/docs.txt +++ b/requirements/static/ci/py3.10/docs.txt @@ -119,7 +119,7 @@ pyyaml==6.0.1 # via # -c requirements/static/ci/py3.10/linux.txt # myst-docutils -requests==2.31.0 +requests==2.32.3 ; python_version >= "3.8" # via # -c requirements/static/ci/py3.10/linux.txt # sphinx diff --git a/requirements/static/ci/py3.10/freebsd.txt b/requirements/static/ci/py3.10/freebsd.txt index 1892f931799..e6a7f7cc26a 100644 --- a/requirements/static/ci/py3.10/freebsd.txt +++ b/requirements/static/ci/py3.10/freebsd.txt @@ -98,8 +98,10 @@ contextvars==2.4 # via # -c requirements/static/ci/../pkg/py3.10/freebsd.txt # -r requirements/base.txt -croniter==1.3.15 ; sys_platform != "win32" - # via -r requirements/static/ci/common.in +croniter==2.0.5 ; sys_platform != "win32" + # via + # -c requirements/static/ci/../pkg/py3.10/freebsd.txt + # -r requirements/base.txt cryptography==42.0.5 # via # -c requirements/static/ci/../pkg/py3.10/freebsd.txt @@ -121,7 +123,7 @@ dnspython==2.6.1 # via # -r requirements/static/ci/common.in # python-etcd -docker==6.1.3 +docker==7.1.0 ; python_version >= "3.8" # via -r requirements/pytest.txt etcd3-py==0.1.6 # via -r requirements/static/ci/common.in @@ -291,7 +293,6 @@ packaging==23.1 # via # -c requirements/static/ci/../pkg/py3.10/freebsd.txt # -r requirements/base.txt - # docker # pytest paramiko==3.4.0 ; sys_platform != "win32" and sys_platform != "darwin" # via @@ -418,6 +419,7 @@ python-gnupg==0.5.2 pytz==2024.1 # via # -c requirements/static/ci/../pkg/py3.10/freebsd.txt + # croniter # tempora pyvmomi==8.0.1.0.1 # via -r requirements/static/ci/common.in @@ -439,7 +441,7 @@ pyzmq==25.1.2 # -c requirements/static/ci/../pkg/py3.10/freebsd.txt # -r requirements/zeromq.txt # pytest-salt-factories -requests==2.31.0 +requests==2.32.3 ; python_version >= "3.8" # via # -c requirements/static/ci/../pkg/py3.10/freebsd.txt # -r requirements/base.txt @@ -557,9 +559,7 @@ virtualenv==20.24.7 watchdog==3.0.0 # via -r requirements/static/ci/common.in websocket-client==0.40.0 - # via - # docker - # kubernetes + # via kubernetes wempy==0.2.1 # via -r requirements/static/ci/common.in werkzeug==3.0.3 diff --git a/requirements/static/ci/py3.10/lint.txt b/requirements/static/ci/py3.10/lint.txt index b6f98be4fbc..6be00f6c5bc 100644 --- a/requirements/static/ci/py3.10/lint.txt +++ b/requirements/static/ci/py3.10/lint.txt @@ -16,7 +16,7 @@ charset-normalizer==3.2.0 # requests dill==0.3.8 # via pylint -docker==6.1.3 +docker==7.1.0 ; python_version >= "3.8" # via # -c requirements/static/ci/py3.10/linux.txt # -r requirements/static/ci/lint.in @@ -28,10 +28,6 @@ isort==4.3.21 # via pylint mccabe==0.6.1 # via pylint -packaging==23.1 - # via - # -c requirements/static/ci/py3.10/linux.txt - # docker platformdirs==4.0.0 # via # -c requirements/static/ci/py3.10/linux.txt @@ -40,16 +36,12 @@ pylint==3.1.0 # via # -r requirements/static/ci/lint.in # saltpylint -requests==2.31.0 +requests==2.32.3 ; python_version >= "3.8" # via # -c requirements/static/ci/py3.10/linux.txt # docker saltpylint==2024.2.5 # via -r requirements/static/ci/lint.in -six==1.16.0 - # via - # -c requirements/static/ci/py3.10/linux.txt - # websocket-client toml==0.10.2 # via # -c requirements/static/ci/py3.10/linux.txt @@ -69,7 +61,3 @@ urllib3==1.26.18 # -c requirements/static/ci/py3.10/linux.txt # docker # requests -websocket-client==0.40.0 - # via - # -c requirements/static/ci/py3.10/linux.txt - # docker diff --git a/requirements/static/ci/py3.10/linux.txt b/requirements/static/ci/py3.10/linux.txt index 6c7d41537fc..34906f5148c 100644 --- a/requirements/static/ci/py3.10/linux.txt +++ b/requirements/static/ci/py3.10/linux.txt @@ -111,8 +111,10 @@ contextvars==2.4 # via # -c requirements/static/ci/../pkg/py3.10/linux.txt # -r requirements/base.txt -croniter==1.3.15 ; sys_platform != "win32" - # via -r requirements/static/ci/common.in +croniter==2.0.5 ; sys_platform != "win32" + # via + # -c requirements/static/ci/../pkg/py3.10/linux.txt + # -r requirements/base.txt cryptography==42.0.5 # via # -c requirements/static/ci/../pkg/py3.10/linux.txt @@ -135,7 +137,7 @@ dnspython==2.6.1 # via # -r requirements/static/ci/common.in # python-etcd -docker==6.1.3 +docker==7.1.0 ; python_version >= "3.8" # via -r requirements/pytest.txt etcd3-py==0.1.6 # via -r requirements/static/ci/common.in @@ -317,7 +319,6 @@ packaging==23.1 # -c requirements/static/ci/../pkg/py3.10/linux.txt # -r requirements/base.txt # ansible-core - # docker # pytest paramiko==3.4.0 ; sys_platform != "win32" and sys_platform != "darwin" # via @@ -380,7 +381,7 @@ pyinotify==0.9.6 ; sys_platform != "win32" and sys_platform != "darwin" and plat # via -r requirements/static/ci/common.in pyjwt==2.7.0 # via twilio -pymysql==1.1.0 +pymysql==1.1.1 # via -r requirements/static/ci/linux.in pynacl==1.5.0 # via @@ -456,6 +457,7 @@ python-telegram-bot==20.3 pytz==2024.1 # via # -c requirements/static/ci/../pkg/py3.10/linux.txt + # croniter # tempora # twilio pyvmomi==8.0.1.0.1 @@ -483,7 +485,7 @@ redis-py-cluster==2.1.3 # via -r requirements/static/ci/linux.in redis==3.5.3 # via redis-py-cluster -requests==2.31.0 +requests==2.32.3 ; python_version >= "3.8" # via # -c requirements/static/ci/../pkg/py3.10/linux.txt # -r requirements/base.txt @@ -621,9 +623,7 @@ virtualenv==20.24.7 watchdog==3.0.0 # via -r requirements/static/ci/common.in websocket-client==0.40.0 - # via - # docker - # kubernetes + # via kubernetes wempy==0.2.1 # via -r requirements/static/ci/common.in werkzeug==3.0.3 diff --git a/requirements/static/ci/py3.10/windows.txt b/requirements/static/ci/py3.10/windows.txt index b44b18c7c33..496f104398f 100644 --- a/requirements/static/ci/py3.10/windows.txt +++ b/requirements/static/ci/py3.10/windows.txt @@ -120,7 +120,7 @@ dnspython==2.6.1 # via # -r requirements/static/ci/common.in # python-etcd -docker==6.1.3 +docker==7.1.0 ; python_version >= "3.8" # via -r requirements/pytest.txt etcd3-py==0.1.6 # via -r requirements/static/ci/common.in @@ -257,7 +257,6 @@ packaging==23.1 # via # -c requirements/static/ci/../pkg/py3.10/windows.txt # -r requirements/base.txt - # docker # pytest passlib==1.7.4 # via -r requirements/static/ci/common.in @@ -408,7 +407,7 @@ pyzmq==25.1.2 # pytest-salt-factories requests-ntlm==1.2.0 # via pywinrm -requests==2.31.0 +requests==2.32.3 ; python_version >= "3.8" # via # -c requirements/static/ci/../pkg/py3.10/windows.txt # -r requirements/base.txt @@ -505,9 +504,7 @@ virtualenv==20.24.7 watchdog==3.0.0 # via -r requirements/static/ci/common.in websocket-client==0.40.0 - # via - # docker - # kubernetes + # via kubernetes wempy==0.2.1 # via -r requirements/static/ci/common.in werkzeug==3.0.3 diff --git a/requirements/static/ci/py3.11/cloud.txt b/requirements/static/ci/py3.11/cloud.txt index 8a67c085503..5045e69e9b5 100644 --- a/requirements/static/ci/py3.11/cloud.txt +++ b/requirements/static/ci/py3.11/cloud.txt @@ -51,7 +51,7 @@ pywinrm==0.4.3 # via -r requirements/static/ci/cloud.in requests-ntlm==1.2.0 # via pywinrm -requests==2.31.0 +requests==2.32.3 ; python_version >= "3.8" # via # -c requirements/static/ci/py3.11/linux.txt # apache-libcloud diff --git a/requirements/static/ci/py3.11/darwin.txt b/requirements/static/ci/py3.11/darwin.txt index deeccf1f169..ff4f6b7e032 100644 --- a/requirements/static/ci/py3.11/darwin.txt +++ b/requirements/static/ci/py3.11/darwin.txt @@ -94,8 +94,10 @@ contextvars==2.4 # via # -c requirements/static/ci/../pkg/py3.11/darwin.txt # -r requirements/base.txt -croniter==1.3.15 ; sys_platform != "win32" - # via -r requirements/static/ci/common.in +croniter==2.0.5 ; sys_platform != "win32" + # via + # -c requirements/static/ci/../pkg/py3.11/darwin.txt + # -r requirements/base.txt cryptography==42.0.5 # via # -c requirements/static/ci/../pkg/py3.11/darwin.txt @@ -117,7 +119,7 @@ dnspython==2.6.1 # via # -r requirements/static/ci/common.in # python-etcd -docker==6.1.3 +docker==7.1.0 ; python_version >= "3.8" # via -r requirements/pytest.txt etcd3-py==0.1.6 # via -r requirements/static/ci/common.in @@ -281,7 +283,6 @@ packaging==23.1 # via # -c requirements/static/ci/../pkg/py3.11/darwin.txt # -r requirements/base.txt - # docker # pytest paramiko==3.4.0 # via @@ -407,6 +408,7 @@ python-gnupg==0.5.2 pytz==2024.1 # via # -c requirements/static/ci/../pkg/py3.11/darwin.txt + # croniter # tempora pyvmomi==8.0.1.0.1 # via -r requirements/static/ci/common.in @@ -428,7 +430,7 @@ pyzmq==25.1.2 # -c requirements/static/ci/../pkg/py3.11/darwin.txt # -r requirements/zeromq.txt # pytest-salt-factories -requests==2.31.0 +requests==2.32.3 ; python_version >= "3.8" # via # -c requirements/static/ci/../pkg/py3.11/darwin.txt # -r requirements/base.txt @@ -543,9 +545,7 @@ virtualenv==20.24.7 watchdog==3.0.0 # via -r requirements/static/ci/common.in websocket-client==0.40.0 - # via - # docker - # kubernetes + # via kubernetes wempy==0.2.1 # via -r requirements/static/ci/common.in werkzeug==3.0.3 diff --git a/requirements/static/ci/py3.11/docs.txt b/requirements/static/ci/py3.11/docs.txt index 977ff2ab4ab..ffa33dbcf7e 100644 --- a/requirements/static/ci/py3.11/docs.txt +++ b/requirements/static/ci/py3.11/docs.txt @@ -119,7 +119,7 @@ pyyaml==6.0.1 # via # -c requirements/static/ci/py3.11/linux.txt # myst-docutils -requests==2.31.0 +requests==2.32.3 ; python_version >= "3.8" # via # -c requirements/static/ci/py3.11/linux.txt # sphinx diff --git a/requirements/static/ci/py3.11/freebsd.txt b/requirements/static/ci/py3.11/freebsd.txt index ab86eae4fed..6e324cc5f4c 100644 --- a/requirements/static/ci/py3.11/freebsd.txt +++ b/requirements/static/ci/py3.11/freebsd.txt @@ -93,8 +93,10 @@ contextvars==2.4 # via # -c requirements/static/ci/../pkg/py3.11/freebsd.txt # -r requirements/base.txt -croniter==1.3.15 ; sys_platform != "win32" - # via -r requirements/static/ci/common.in +croniter==2.0.5 ; sys_platform != "win32" + # via + # -c requirements/static/ci/../pkg/py3.11/freebsd.txt + # -r requirements/base.txt cryptography==42.0.5 # via # -c requirements/static/ci/../pkg/py3.11/freebsd.txt @@ -116,7 +118,7 @@ dnspython==2.6.1 # via # -r requirements/static/ci/common.in # python-etcd -docker==6.1.3 +docker==7.1.0 ; python_version >= "3.8" # via -r requirements/pytest.txt etcd3-py==0.1.6 # via -r requirements/static/ci/common.in @@ -284,7 +286,6 @@ packaging==23.1 # via # -c requirements/static/ci/../pkg/py3.11/freebsd.txt # -r requirements/base.txt - # docker # pytest paramiko==3.4.0 ; sys_platform != "win32" and sys_platform != "darwin" # via @@ -411,6 +412,7 @@ python-gnupg==0.5.2 pytz==2024.1 # via # -c requirements/static/ci/../pkg/py3.11/freebsd.txt + # croniter # tempora pyvmomi==8.0.1.0.1 # via -r requirements/static/ci/common.in @@ -432,7 +434,7 @@ pyzmq==25.1.2 # -c requirements/static/ci/../pkg/py3.11/freebsd.txt # -r requirements/zeromq.txt # pytest-salt-factories -requests==2.31.0 +requests==2.32.3 ; python_version >= "3.8" # via # -c requirements/static/ci/../pkg/py3.11/freebsd.txt # -r requirements/base.txt @@ -549,9 +551,7 @@ virtualenv==20.24.7 watchdog==3.0.0 # via -r requirements/static/ci/common.in websocket-client==0.40.0 - # via - # docker - # kubernetes + # via kubernetes wempy==0.2.1 # via -r requirements/static/ci/common.in werkzeug==3.0.3 diff --git a/requirements/static/ci/py3.11/lint.txt b/requirements/static/ci/py3.11/lint.txt index e62650519db..150c35069c6 100644 --- a/requirements/static/ci/py3.11/lint.txt +++ b/requirements/static/ci/py3.11/lint.txt @@ -16,7 +16,7 @@ charset-normalizer==3.2.0 # requests dill==0.3.8 # via pylint -docker==6.1.3 +docker==7.1.0 ; python_version >= "3.8" # via # -c requirements/static/ci/py3.11/linux.txt # -r requirements/static/ci/lint.in @@ -28,10 +28,6 @@ isort==4.3.21 # via pylint mccabe==0.6.1 # via pylint -packaging==23.1 - # via - # -c requirements/static/ci/py3.11/linux.txt - # docker platformdirs==4.0.0 # via # -c requirements/static/ci/py3.11/linux.txt @@ -40,16 +36,12 @@ pylint==3.1.0 # via # -r requirements/static/ci/lint.in # saltpylint -requests==2.31.0 +requests==2.32.3 ; python_version >= "3.8" # via # -c requirements/static/ci/py3.11/linux.txt # docker saltpylint==2024.2.5 # via -r requirements/static/ci/lint.in -six==1.16.0 - # via - # -c requirements/static/ci/py3.11/linux.txt - # websocket-client toml==0.10.2 # via # -c requirements/static/ci/py3.11/linux.txt @@ -61,7 +53,3 @@ urllib3==1.26.18 # -c requirements/static/ci/py3.11/linux.txt # docker # requests -websocket-client==0.40.0 - # via - # -c requirements/static/ci/py3.11/linux.txt - # docker diff --git a/requirements/static/ci/py3.11/linux.txt b/requirements/static/ci/py3.11/linux.txt index 5a17dee8d48..5a2f15856d0 100644 --- a/requirements/static/ci/py3.11/linux.txt +++ b/requirements/static/ci/py3.11/linux.txt @@ -106,8 +106,10 @@ contextvars==2.4 # via # -c requirements/static/ci/../pkg/py3.11/linux.txt # -r requirements/base.txt -croniter==1.3.15 ; sys_platform != "win32" - # via -r requirements/static/ci/common.in +croniter==2.0.5 ; sys_platform != "win32" + # via + # -c requirements/static/ci/../pkg/py3.11/linux.txt + # -r requirements/base.txt cryptography==42.0.5 # via # -c requirements/static/ci/../pkg/py3.11/linux.txt @@ -130,7 +132,7 @@ dnspython==2.6.1 # via # -r requirements/static/ci/common.in # python-etcd -docker==6.1.3 +docker==7.1.0 ; python_version >= "3.8" # via -r requirements/pytest.txt etcd3-py==0.1.6 # via -r requirements/static/ci/common.in @@ -308,7 +310,6 @@ packaging==23.1 # -c requirements/static/ci/../pkg/py3.11/linux.txt # -r requirements/base.txt # ansible-core - # docker # pytest paramiko==3.4.0 ; sys_platform != "win32" and sys_platform != "darwin" # via @@ -371,7 +372,7 @@ pyinotify==0.9.6 ; sys_platform != "win32" and sys_platform != "darwin" and plat # via -r requirements/static/ci/common.in pyjwt==2.4.0 # via twilio -pymysql==1.1.0 +pymysql==1.1.1 # via -r requirements/static/ci/linux.in pynacl==1.5.0 # via @@ -447,6 +448,7 @@ python-telegram-bot==20.3 pytz==2024.1 # via # -c requirements/static/ci/../pkg/py3.11/linux.txt + # croniter # tempora # twilio pyvmomi==8.0.1.0.1 @@ -474,7 +476,7 @@ redis-py-cluster==2.1.3 # via -r requirements/static/ci/linux.in redis==3.5.3 # via redis-py-cluster -requests==2.31.0 +requests==2.32.3 ; python_version >= "3.8" # via # -c requirements/static/ci/../pkg/py3.11/linux.txt # -r requirements/base.txt @@ -611,9 +613,7 @@ virtualenv==20.24.7 watchdog==3.0.0 # via -r requirements/static/ci/common.in websocket-client==0.40.0 - # via - # docker - # kubernetes + # via kubernetes wempy==0.2.1 # via -r requirements/static/ci/common.in werkzeug==3.0.3 diff --git a/requirements/static/ci/py3.11/windows.txt b/requirements/static/ci/py3.11/windows.txt index 7a81cb86731..2b77f8cb01a 100644 --- a/requirements/static/ci/py3.11/windows.txt +++ b/requirements/static/ci/py3.11/windows.txt @@ -115,7 +115,7 @@ dnspython==2.6.1 # via # -r requirements/static/ci/common.in # python-etcd -docker==6.1.3 +docker==7.1.0 ; python_version >= "3.8" # via -r requirements/pytest.txt etcd3-py==0.1.6 # via -r requirements/static/ci/common.in @@ -250,7 +250,6 @@ packaging==23.1 # via # -c requirements/static/ci/../pkg/py3.11/windows.txt # -r requirements/base.txt - # docker # pytest passlib==1.7.4 # via -r requirements/static/ci/common.in @@ -401,7 +400,7 @@ pyzmq==25.1.2 # pytest-salt-factories requests-ntlm==1.2.0 # via pywinrm -requests==2.31.0 +requests==2.32.3 ; python_version >= "3.8" # via # -c requirements/static/ci/../pkg/py3.11/windows.txt # -r requirements/base.txt @@ -496,9 +495,7 @@ virtualenv==20.24.7 watchdog==3.0.0 # via -r requirements/static/ci/common.in websocket-client==0.40.0 - # via - # docker - # kubernetes + # via kubernetes wempy==0.2.1 # via -r requirements/static/ci/common.in werkzeug==3.0.3 diff --git a/requirements/static/ci/py3.12/cloud.txt b/requirements/static/ci/py3.12/cloud.txt index d3f1c19d8b6..41f14034d18 100644 --- a/requirements/static/ci/py3.12/cloud.txt +++ b/requirements/static/ci/py3.12/cloud.txt @@ -125,10 +125,11 @@ contextvars==2.4 # -c requirements/static/ci/../pkg/py3.12/linux.txt # -c requirements/static/ci/py3.12/linux.txt # -r requirements/base.txt -croniter==1.3.15 ; sys_platform != "win32" +croniter==2.0.5 ; sys_platform != "win32" # via + # -c requirements/static/ci/../pkg/py3.12/linux.txt # -c requirements/static/ci/py3.12/linux.txt - # -r requirements/static/ci/common.in + # -r requirements/base.txt cryptography==42.0.5 # via # -c requirements/static/ci/../pkg/py3.12/linux.txt @@ -158,7 +159,7 @@ dnspython==2.6.1 # -c requirements/static/ci/py3.12/linux.txt # -r requirements/static/ci/common.in # python-etcd -docker==6.1.3 +docker==7.1.0 ; python_version >= "3.8" # via # -c requirements/static/ci/py3.12/linux.txt # -r requirements/pytest.txt @@ -397,7 +398,6 @@ packaging==23.1 # -c requirements/static/ci/../pkg/py3.12/linux.txt # -c requirements/static/ci/py3.12/linux.txt # -r requirements/base.txt - # docker # pytest paramiko==3.4.0 ; sys_platform != "win32" and sys_platform != "darwin" # via @@ -582,6 +582,7 @@ pytz==2024.1 # via # -c requirements/static/ci/../pkg/py3.12/linux.txt # -c requirements/static/ci/py3.12/linux.txt + # croniter # tempora pyvmomi==8.0.1.0.1 # via @@ -610,7 +611,7 @@ pyzmq==25.1.2 # pytest-salt-factories requests-ntlm==1.2.0 # via pywinrm -requests==2.31.0 +requests==2.32.3 ; python_version >= "3.8" # via # -c requirements/static/ci/../pkg/py3.12/linux.txt # -c requirements/static/ci/py3.12/linux.txt @@ -785,7 +786,6 @@ watchdog==3.0.0 websocket-client==0.40.0 # via # -c requirements/static/ci/py3.12/linux.txt - # docker # kubernetes wempy==0.2.1 # via diff --git a/requirements/static/ci/py3.12/darwin.txt b/requirements/static/ci/py3.12/darwin.txt index d02f5edbba0..f77e05e5ddf 100644 --- a/requirements/static/ci/py3.12/darwin.txt +++ b/requirements/static/ci/py3.12/darwin.txt @@ -94,8 +94,10 @@ contextvars==2.4 # via # -c requirements/static/ci/../pkg/py3.12/darwin.txt # -r requirements/base.txt -croniter==1.3.15 ; sys_platform != "win32" - # via -r requirements/static/ci/common.in +croniter==2.0.5 ; sys_platform != "win32" + # via + # -c requirements/static/ci/../pkg/py3.12/darwin.txt + # -r requirements/base.txt cryptography==42.0.5 # via # -c requirements/static/ci/../pkg/py3.12/darwin.txt @@ -117,7 +119,7 @@ dnspython==2.6.1 # via # -r requirements/static/ci/common.in # python-etcd -docker==6.1.3 +docker==7.1.0 ; python_version >= "3.8" # via -r requirements/pytest.txt etcd3-py==0.1.6 # via -r requirements/static/ci/common.in @@ -281,7 +283,6 @@ packaging==23.1 # via # -c requirements/static/ci/../pkg/py3.12/darwin.txt # -r requirements/base.txt - # docker # pytest paramiko==3.4.0 # via @@ -407,6 +408,7 @@ python-gnupg==0.5.2 pytz==2024.1 # via # -c requirements/static/ci/../pkg/py3.12/darwin.txt + # croniter # tempora pyvmomi==8.0.1.0.1 # via -r requirements/static/ci/common.in @@ -428,7 +430,7 @@ pyzmq==25.1.2 # -c requirements/static/ci/../pkg/py3.12/darwin.txt # -r requirements/zeromq.txt # pytest-salt-factories -requests==2.31.0 +requests==2.32.3 ; python_version >= "3.8" # via # -c requirements/static/ci/../pkg/py3.12/darwin.txt # -r requirements/base.txt @@ -543,9 +545,7 @@ virtualenv==20.24.7 watchdog==3.0.0 # via -r requirements/static/ci/common.in websocket-client==0.40.0 - # via - # docker - # kubernetes + # via kubernetes wempy==0.2.1 # via -r requirements/static/ci/common.in werkzeug==3.0.3 diff --git a/requirements/static/ci/py3.12/docs.txt b/requirements/static/ci/py3.12/docs.txt index 83a0b436db3..acfaab6194d 100644 --- a/requirements/static/ci/py3.12/docs.txt +++ b/requirements/static/ci/py3.12/docs.txt @@ -53,6 +53,10 @@ contextvars==2.4 # via # -c requirements/static/ci/py3.12/linux.txt # -r requirements/base.txt +croniter==2.0.5 ; sys_platform != "win32" + # via + # -c requirements/static/ci/py3.12/linux.txt + # -r requirements/base.txt cryptography==42.0.5 # via # -c requirements/static/ci/py3.12/linux.txt @@ -195,6 +199,7 @@ python-dateutil==2.8.2 # via # -c requirements/static/ci/py3.12/linux.txt # -r requirements/base.txt + # croniter python-gnupg==0.5.2 # via # -c requirements/static/ci/py3.12/linux.txt @@ -202,6 +207,7 @@ python-gnupg==0.5.2 pytz==2024.1 # via # -c requirements/static/ci/py3.12/linux.txt + # croniter # tempora pyyaml==6.0.1 # via @@ -212,7 +218,7 @@ pyzmq==25.1.2 # via # -c requirements/static/ci/py3.12/linux.txt # -r requirements/zeromq.txt -requests==2.31.0 +requests==2.32.3 ; python_version >= "3.8" # via # -c requirements/static/ci/py3.12/linux.txt # -r requirements/base.txt diff --git a/requirements/static/ci/py3.12/freebsd.txt b/requirements/static/ci/py3.12/freebsd.txt index 50503909a55..a293d6e0945 100644 --- a/requirements/static/ci/py3.12/freebsd.txt +++ b/requirements/static/ci/py3.12/freebsd.txt @@ -93,8 +93,10 @@ contextvars==2.4 # via # -c requirements/static/ci/../pkg/py3.12/freebsd.txt # -r requirements/base.txt -croniter==1.3.15 ; sys_platform != "win32" - # via -r requirements/static/ci/common.in +croniter==2.0.5 ; sys_platform != "win32" + # via + # -c requirements/static/ci/../pkg/py3.12/freebsd.txt + # -r requirements/base.txt cryptography==42.0.5 # via # -c requirements/static/ci/../pkg/py3.12/freebsd.txt @@ -116,7 +118,7 @@ dnspython==2.6.1 # via # -r requirements/static/ci/common.in # python-etcd -docker==6.1.3 +docker==7.1.0 ; python_version >= "3.8" # via -r requirements/pytest.txt etcd3-py==0.1.6 # via -r requirements/static/ci/common.in @@ -284,7 +286,6 @@ packaging==23.1 # via # -c requirements/static/ci/../pkg/py3.12/freebsd.txt # -r requirements/base.txt - # docker # pytest paramiko==3.4.0 ; sys_platform != "win32" and sys_platform != "darwin" # via @@ -411,6 +412,7 @@ python-gnupg==0.5.2 pytz==2024.1 # via # -c requirements/static/ci/../pkg/py3.12/freebsd.txt + # croniter # tempora pyvmomi==8.0.1.0.1 # via -r requirements/static/ci/common.in @@ -432,7 +434,7 @@ pyzmq==25.1.2 # -c requirements/static/ci/../pkg/py3.12/freebsd.txt # -r requirements/zeromq.txt # pytest-salt-factories -requests==2.31.0 +requests==2.32.3 ; python_version >= "3.8" # via # -c requirements/static/ci/../pkg/py3.12/freebsd.txt # -r requirements/base.txt @@ -549,9 +551,7 @@ virtualenv==20.24.7 watchdog==3.0.0 # via -r requirements/static/ci/common.in websocket-client==0.40.0 - # via - # docker - # kubernetes + # via kubernetes wempy==0.2.1 # via -r requirements/static/ci/common.in werkzeug==3.0.3 diff --git a/requirements/static/ci/py3.12/lint.txt b/requirements/static/ci/py3.12/lint.txt index 5f417d45778..2196c275bf3 100644 --- a/requirements/static/ci/py3.12/lint.txt +++ b/requirements/static/ci/py3.12/lint.txt @@ -143,10 +143,11 @@ contextvars==2.4 # -c requirements/static/ci/../pkg/py3.12/linux.txt # -c requirements/static/ci/py3.12/linux.txt # -r requirements/base.txt -croniter==1.3.15 ; sys_platform != "win32" +croniter==2.0.5 ; sys_platform != "win32" # via + # -c requirements/static/ci/../pkg/py3.12/linux.txt # -c requirements/static/ci/py3.12/linux.txt - # -r requirements/static/ci/common.in + # -r requirements/base.txt cryptography==42.0.5 # via # -c requirements/static/ci/../pkg/py3.12/linux.txt @@ -174,7 +175,7 @@ dnspython==2.6.1 # -c requirements/static/ci/py3.12/linux.txt # -r requirements/static/ci/common.in # python-etcd -docker==6.1.3 +docker==7.1.0 ; python_version >= "3.8" # via # -c requirements/static/ci/py3.12/linux.txt # -r requirements/static/ci/lint.in @@ -425,7 +426,6 @@ packaging==23.1 # -c requirements/static/ci/py3.12/linux.txt # -r requirements/base.txt # ansible-core - # docker paramiko==3.4.0 ; sys_platform != "win32" and sys_platform != "darwin" # via # -c requirements/static/ci/py3.12/linux.txt @@ -511,7 +511,7 @@ pylint==3.1.0 # via # -r requirements/static/ci/lint.in # saltpylint -pymysql==1.1.0 +pymysql==1.1.1 # via # -c requirements/static/ci/py3.12/linux.txt # -r requirements/static/ci/linux.in @@ -570,6 +570,7 @@ pytz==2024.1 # via # -c requirements/static/ci/../pkg/py3.12/linux.txt # -c requirements/static/ci/py3.12/linux.txt + # croniter # tempora # twilio pyvmomi==8.0.1.0.1 @@ -603,7 +604,7 @@ redis==3.5.3 # via # -c requirements/static/ci/py3.12/linux.txt # redis-py-cluster -requests==2.31.0 +requests==2.32.3 ; python_version >= "3.8" # via # -c requirements/static/ci/../pkg/py3.12/linux.txt # -c requirements/static/ci/py3.12/linux.txt @@ -792,7 +793,6 @@ watchdog==3.0.0 websocket-client==0.40.0 # via # -c requirements/static/ci/py3.12/linux.txt - # docker # kubernetes wempy==0.2.1 # via diff --git a/requirements/static/ci/py3.12/linux.txt b/requirements/static/ci/py3.12/linux.txt index f19bcf974b8..6e9d2f93e15 100644 --- a/requirements/static/ci/py3.12/linux.txt +++ b/requirements/static/ci/py3.12/linux.txt @@ -106,8 +106,10 @@ contextvars==2.4 # via # -c requirements/static/ci/../pkg/py3.12/linux.txt # -r requirements/base.txt -croniter==1.3.15 ; sys_platform != "win32" - # via -r requirements/static/ci/common.in +croniter==2.0.5 ; sys_platform != "win32" + # via + # -c requirements/static/ci/../pkg/py3.12/linux.txt + # -r requirements/base.txt cryptography==42.0.5 # via # -c requirements/static/ci/../pkg/py3.12/linux.txt @@ -130,7 +132,7 @@ dnspython==2.6.1 # via # -r requirements/static/ci/common.in # python-etcd -docker==6.1.3 +docker==7.1.0 ; python_version >= "3.8" # via -r requirements/pytest.txt etcd3-py==0.1.6 # via -r requirements/static/ci/common.in @@ -308,7 +310,6 @@ packaging==23.1 # -c requirements/static/ci/../pkg/py3.12/linux.txt # -r requirements/base.txt # ansible-core - # docker # pytest paramiko==3.4.0 ; sys_platform != "win32" and sys_platform != "darwin" # via @@ -371,7 +372,7 @@ pyinotify==0.9.6 ; sys_platform != "win32" and sys_platform != "darwin" and plat # via -r requirements/static/ci/common.in pyjwt==2.4.0 # via twilio -pymysql==1.1.0 +pymysql==1.1.1 # via -r requirements/static/ci/linux.in pynacl==1.5.0 # via @@ -447,6 +448,7 @@ python-telegram-bot==20.3 pytz==2024.1 # via # -c requirements/static/ci/../pkg/py3.12/linux.txt + # croniter # tempora # twilio pyvmomi==8.0.1.0.1 @@ -474,7 +476,7 @@ redis-py-cluster==2.1.3 # via -r requirements/static/ci/linux.in redis==3.5.3 # via redis-py-cluster -requests==2.31.0 +requests==2.32.3 ; python_version >= "3.8" # via # -c requirements/static/ci/../pkg/py3.12/linux.txt # -r requirements/base.txt @@ -611,9 +613,7 @@ virtualenv==20.24.7 watchdog==3.0.0 # via -r requirements/static/ci/common.in websocket-client==0.40.0 - # via - # docker - # kubernetes + # via kubernetes wempy==0.2.1 # via -r requirements/static/ci/common.in werkzeug==3.0.3 diff --git a/requirements/static/ci/py3.12/windows.txt b/requirements/static/ci/py3.12/windows.txt index b72ee62f583..e4380533d61 100644 --- a/requirements/static/ci/py3.12/windows.txt +++ b/requirements/static/ci/py3.12/windows.txt @@ -115,7 +115,7 @@ dnspython==2.6.1 # via # -r requirements/static/ci/common.in # python-etcd -docker==6.1.3 +docker==7.1.0 ; python_version >= "3.8" # via -r requirements/pytest.txt etcd3-py==0.1.6 # via -r requirements/static/ci/common.in @@ -250,7 +250,6 @@ packaging==23.1 # via # -c requirements/static/ci/../pkg/py3.12/windows.txt # -r requirements/base.txt - # docker # pytest passlib==1.7.4 # via -r requirements/static/ci/common.in @@ -401,7 +400,7 @@ pyzmq==25.1.2 # pytest-salt-factories requests-ntlm==1.2.0 # via pywinrm -requests==2.31.0 +requests==2.32.3 ; python_version >= "3.8" # via # -c requirements/static/ci/../pkg/py3.12/windows.txt # -r requirements/base.txt @@ -496,9 +495,7 @@ virtualenv==20.24.7 watchdog==3.0.0 # via -r requirements/static/ci/common.in websocket-client==0.40.0 - # via - # docker - # kubernetes + # via kubernetes wempy==0.2.1 # via -r requirements/static/ci/common.in werkzeug==3.0.3 diff --git a/requirements/static/ci/py3.8/cloud.txt b/requirements/static/ci/py3.8/cloud.txt index e530f8b033d..6dc77976817 100644 --- a/requirements/static/ci/py3.8/cloud.txt +++ b/requirements/static/ci/py3.8/cloud.txt @@ -51,7 +51,7 @@ pywinrm==0.4.3 # via -r requirements/static/ci/cloud.in requests-ntlm==1.2.0 # via pywinrm -requests==2.31.0 +requests==2.32.3 ; python_version >= "3.8" # via # -c requirements/static/ci/py3.8/linux.txt # apache-libcloud diff --git a/requirements/static/ci/py3.8/docs.txt b/requirements/static/ci/py3.8/docs.txt index 355d8672e67..ff2a746c40a 100644 --- a/requirements/static/ci/py3.8/docs.txt +++ b/requirements/static/ci/py3.8/docs.txt @@ -128,7 +128,7 @@ pyyaml==6.0.1 # via # -c requirements/static/ci/py3.8/linux.txt # myst-docutils -requests==2.31.0 +requests==2.32.3 ; python_version >= "3.8" # via # -c requirements/static/ci/py3.8/linux.txt # sphinx diff --git a/requirements/static/ci/py3.8/freebsd.txt b/requirements/static/ci/py3.8/freebsd.txt index a818d9b340d..94c0f247d1a 100644 --- a/requirements/static/ci/py3.8/freebsd.txt +++ b/requirements/static/ci/py3.8/freebsd.txt @@ -98,8 +98,10 @@ contextvars==2.4 # via # -c requirements/static/ci/../pkg/py3.8/freebsd.txt # -r requirements/base.txt -croniter==1.3.15 ; sys_platform != "win32" - # via -r requirements/static/ci/common.in +croniter==2.0.5 ; sys_platform != "win32" + # via + # -c requirements/static/ci/../pkg/py3.8/freebsd.txt + # -r requirements/base.txt cryptography==42.0.5 # via # -c requirements/static/ci/../pkg/py3.8/freebsd.txt @@ -121,7 +123,7 @@ dnspython==2.6.1 # via # -r requirements/static/ci/common.in # python-etcd -docker==6.1.3 +docker==7.1.0 ; python_version >= "3.8" # via -r requirements/pytest.txt etcd3-py==0.1.6 # via -r requirements/static/ci/common.in @@ -295,7 +297,6 @@ packaging==23.1 # via # -c requirements/static/ci/../pkg/py3.8/freebsd.txt # -r requirements/base.txt - # docker # pytest paramiko==3.4.0 ; sys_platform != "win32" and sys_platform != "darwin" # via @@ -422,6 +423,7 @@ python-gnupg==0.5.2 pytz==2024.1 # via # -c requirements/static/ci/../pkg/py3.8/freebsd.txt + # croniter # tempora pyvmomi==8.0.1.0.1 # via -r requirements/static/ci/common.in @@ -443,7 +445,7 @@ pyzmq==25.1.2 # -c requirements/static/ci/../pkg/py3.8/freebsd.txt # -r requirements/zeromq.txt # pytest-salt-factories -requests==2.31.0 +requests==2.32.3 ; python_version >= "3.8" # via # -c requirements/static/ci/../pkg/py3.8/freebsd.txt # -r requirements/base.txt @@ -562,9 +564,7 @@ virtualenv==20.24.7 watchdog==3.0.0 # via -r requirements/static/ci/common.in websocket-client==0.40.0 - # via - # docker - # kubernetes + # via kubernetes wempy==0.2.1 # via -r requirements/static/ci/common.in werkzeug==3.0.3 diff --git a/requirements/static/ci/py3.8/lint.txt b/requirements/static/ci/py3.8/lint.txt index e2031243152..085b21ecd1d 100644 --- a/requirements/static/ci/py3.8/lint.txt +++ b/requirements/static/ci/py3.8/lint.txt @@ -16,7 +16,7 @@ charset-normalizer==3.2.0 # requests dill==0.3.8 # via pylint -docker==6.1.3 +docker==7.1.0 ; python_version >= "3.8" # via # -c requirements/static/ci/py3.8/linux.txt # -r requirements/static/ci/lint.in @@ -28,10 +28,6 @@ isort==4.3.21 # via pylint mccabe==0.6.1 # via pylint -packaging==23.1 - # via - # -c requirements/static/ci/py3.8/linux.txt - # docker platformdirs==4.0.0 # via # -c requirements/static/ci/py3.8/linux.txt @@ -40,16 +36,12 @@ pylint==3.1.0 # via # -r requirements/static/ci/lint.in # saltpylint -requests==2.31.0 +requests==2.32.3 ; python_version >= "3.8" # via # -c requirements/static/ci/py3.8/linux.txt # docker saltpylint==2024.2.5 # via -r requirements/static/ci/lint.in -six==1.16.0 - # via - # -c requirements/static/ci/py3.8/linux.txt - # websocket-client toml==0.10.2 # via # -c requirements/static/ci/py3.8/linux.txt @@ -70,7 +62,3 @@ urllib3==1.26.18 # -c requirements/static/ci/py3.8/linux.txt # docker # requests -websocket-client==0.40.0 - # via - # -c requirements/static/ci/py3.8/linux.txt - # docker diff --git a/requirements/static/ci/py3.8/linux.txt b/requirements/static/ci/py3.8/linux.txt index 6f3970d24c4..55faa78e308 100644 --- a/requirements/static/ci/py3.8/linux.txt +++ b/requirements/static/ci/py3.8/linux.txt @@ -107,8 +107,10 @@ contextvars==2.4 # via # -c requirements/static/ci/../pkg/py3.8/linux.txt # -r requirements/base.txt -croniter==1.3.15 ; sys_platform != "win32" - # via -r requirements/static/ci/common.in +croniter==2.0.5 ; sys_platform != "win32" + # via + # -c requirements/static/ci/../pkg/py3.8/linux.txt + # -r requirements/base.txt cryptography==42.0.5 # via # -c requirements/static/ci/../pkg/py3.8/linux.txt @@ -130,7 +132,7 @@ dnspython==2.6.1 # via # -r requirements/static/ci/common.in # python-etcd -docker==6.1.3 +docker==7.1.0 ; python_version >= "3.8" # via -r requirements/pytest.txt etcd3-py==0.1.6 # via -r requirements/static/ci/common.in @@ -314,7 +316,6 @@ packaging==23.1 # via # -c requirements/static/ci/../pkg/py3.8/linux.txt # -r requirements/base.txt - # docker # pytest paramiko==3.4.0 ; sys_platform != "win32" and sys_platform != "darwin" # via @@ -377,7 +378,7 @@ pyinotify==0.9.6 ; sys_platform != "win32" and sys_platform != "darwin" and plat # via -r requirements/static/ci/common.in pyjwt==2.7.0 # via twilio -pymysql==1.1.0 +pymysql==1.1.1 # via -r requirements/static/ci/linux.in pynacl==1.5.0 # via @@ -453,6 +454,7 @@ python-telegram-bot==20.3 pytz==2024.1 # via # -c requirements/static/ci/../pkg/py3.8/linux.txt + # croniter # tempora # twilio pyvmomi==8.0.1.0.1 @@ -479,7 +481,7 @@ redis-py-cluster==2.1.3 # via -r requirements/static/ci/linux.in redis==3.5.3 # via redis-py-cluster -requests==2.31.0 +requests==2.32.3 ; python_version >= "3.8" # via # -c requirements/static/ci/../pkg/py3.8/linux.txt # -r requirements/base.txt @@ -614,9 +616,7 @@ virtualenv==20.24.7 watchdog==3.0.0 # via -r requirements/static/ci/common.in websocket-client==0.40.0 - # via - # docker - # kubernetes + # via kubernetes wempy==0.2.1 # via -r requirements/static/ci/common.in werkzeug==3.0.3 diff --git a/requirements/static/ci/py3.8/windows.txt b/requirements/static/ci/py3.8/windows.txt index b8ececd507d..2099586ca2c 100644 --- a/requirements/static/ci/py3.8/windows.txt +++ b/requirements/static/ci/py3.8/windows.txt @@ -120,7 +120,7 @@ dnspython==2.6.1 # via # -r requirements/static/ci/common.in # python-etcd -docker==6.1.3 +docker==7.1.0 ; python_version >= "3.8" # via -r requirements/pytest.txt etcd3-py==0.1.6 # via -r requirements/static/ci/common.in @@ -261,7 +261,6 @@ packaging==23.1 # via # -c requirements/static/ci/../pkg/py3.8/windows.txt # -r requirements/base.txt - # docker # pytest passlib==1.7.4 # via -r requirements/static/ci/common.in @@ -413,7 +412,7 @@ pyzmq==25.1.2 # pytest-salt-factories requests-ntlm==1.2.0 # via pywinrm -requests==2.31.0 +requests==2.32.3 ; python_version >= "3.8" # via # -c requirements/static/ci/../pkg/py3.8/windows.txt # -r requirements/base.txt @@ -511,9 +510,7 @@ virtualenv==20.24.7 watchdog==3.0.0 # via -r requirements/static/ci/common.in websocket-client==0.40.0 - # via - # docker - # kubernetes + # via kubernetes wempy==0.2.1 # via -r requirements/static/ci/common.in werkzeug==3.0.3 diff --git a/requirements/static/ci/py3.9/cloud.txt b/requirements/static/ci/py3.9/cloud.txt index d43e5af5786..f3c308bb055 100644 --- a/requirements/static/ci/py3.9/cloud.txt +++ b/requirements/static/ci/py3.9/cloud.txt @@ -51,7 +51,7 @@ pywinrm==0.4.3 # via -r requirements/static/ci/cloud.in requests-ntlm==1.2.0 # via pywinrm -requests==2.31.0 +requests==2.32.3 ; python_version >= "3.8" # via # -c requirements/static/ci/py3.9/linux.txt # apache-libcloud diff --git a/requirements/static/ci/py3.9/darwin.txt b/requirements/static/ci/py3.9/darwin.txt index 6c04ee10f52..a364e46cc2a 100644 --- a/requirements/static/ci/py3.9/darwin.txt +++ b/requirements/static/ci/py3.9/darwin.txt @@ -99,8 +99,10 @@ contextvars==2.4 # via # -c requirements/static/ci/../pkg/py3.9/darwin.txt # -r requirements/base.txt -croniter==1.3.15 ; sys_platform != "win32" - # via -r requirements/static/ci/common.in +croniter==2.0.5 ; sys_platform != "win32" + # via + # -c requirements/static/ci/../pkg/py3.9/darwin.txt + # -r requirements/base.txt cryptography==42.0.5 # via # -c requirements/static/ci/../pkg/py3.9/darwin.txt @@ -122,7 +124,7 @@ dnspython==2.6.1 # via # -r requirements/static/ci/common.in # python-etcd -docker==6.1.3 +docker==7.1.0 ; python_version >= "3.8" # via -r requirements/pytest.txt etcd3-py==0.1.6 # via -r requirements/static/ci/common.in @@ -288,7 +290,6 @@ packaging==23.1 # via # -c requirements/static/ci/../pkg/py3.9/darwin.txt # -r requirements/base.txt - # docker # pytest paramiko==3.4.0 # via @@ -414,6 +415,7 @@ python-gnupg==0.5.2 pytz==2024.1 # via # -c requirements/static/ci/../pkg/py3.9/darwin.txt + # croniter # tempora pyvmomi==8.0.1.0.1 # via -r requirements/static/ci/common.in @@ -435,7 +437,7 @@ pyzmq==25.1.2 # -c requirements/static/ci/../pkg/py3.9/darwin.txt # -r requirements/zeromq.txt # pytest-salt-factories -requests==2.31.0 +requests==2.32.3 ; python_version >= "3.8" # via # -c requirements/static/ci/../pkg/py3.9/darwin.txt # -r requirements/base.txt @@ -552,9 +554,7 @@ virtualenv==20.24.7 watchdog==3.0.0 # via -r requirements/static/ci/common.in websocket-client==0.40.0 - # via - # docker - # kubernetes + # via kubernetes wempy==0.2.1 # via -r requirements/static/ci/common.in werkzeug==3.0.3 diff --git a/requirements/static/ci/py3.9/docs.txt b/requirements/static/ci/py3.9/docs.txt index 5aeec7e9253..320123d7592 100644 --- a/requirements/static/ci/py3.9/docs.txt +++ b/requirements/static/ci/py3.9/docs.txt @@ -123,7 +123,7 @@ pyyaml==6.0.1 # via # -c requirements/static/ci/py3.9/linux.txt # myst-docutils -requests==2.31.0 +requests==2.32.3 ; python_version >= "3.8" # via # -c requirements/static/ci/py3.9/linux.txt # sphinx diff --git a/requirements/static/ci/py3.9/freebsd.txt b/requirements/static/ci/py3.9/freebsd.txt index 72898aad5e0..d4a44882aea 100644 --- a/requirements/static/ci/py3.9/freebsd.txt +++ b/requirements/static/ci/py3.9/freebsd.txt @@ -98,8 +98,10 @@ contextvars==2.4 # via # -c requirements/static/ci/../pkg/py3.9/freebsd.txt # -r requirements/base.txt -croniter==1.3.15 ; sys_platform != "win32" - # via -r requirements/static/ci/common.in +croniter==2.0.5 ; sys_platform != "win32" + # via + # -c requirements/static/ci/../pkg/py3.9/freebsd.txt + # -r requirements/base.txt cryptography==42.0.5 # via # -c requirements/static/ci/../pkg/py3.9/freebsd.txt @@ -121,7 +123,7 @@ dnspython==2.6.1 # via # -r requirements/static/ci/common.in # python-etcd -docker==6.1.3 +docker==7.1.0 ; python_version >= "3.8" # via -r requirements/pytest.txt etcd3-py==0.1.6 # via -r requirements/static/ci/common.in @@ -291,7 +293,6 @@ packaging==23.1 # via # -c requirements/static/ci/../pkg/py3.9/freebsd.txt # -r requirements/base.txt - # docker # pytest paramiko==3.4.0 ; sys_platform != "win32" and sys_platform != "darwin" # via @@ -418,6 +419,7 @@ python-gnupg==0.5.2 pytz==2024.1 # via # -c requirements/static/ci/../pkg/py3.9/freebsd.txt + # croniter # tempora pyvmomi==8.0.1.0.1 # via -r requirements/static/ci/common.in @@ -439,7 +441,7 @@ pyzmq==25.1.2 # -c requirements/static/ci/../pkg/py3.9/freebsd.txt # -r requirements/zeromq.txt # pytest-salt-factories -requests==2.31.0 +requests==2.32.3 ; python_version >= "3.8" # via # -c requirements/static/ci/../pkg/py3.9/freebsd.txt # -r requirements/base.txt @@ -557,9 +559,7 @@ virtualenv==20.24.7 watchdog==3.0.0 # via -r requirements/static/ci/common.in websocket-client==0.40.0 - # via - # docker - # kubernetes + # via kubernetes wempy==0.2.1 # via -r requirements/static/ci/common.in werkzeug==3.0.3 diff --git a/requirements/static/ci/py3.9/lint.txt b/requirements/static/ci/py3.9/lint.txt index 9dfbc27a332..92d500abf59 100644 --- a/requirements/static/ci/py3.9/lint.txt +++ b/requirements/static/ci/py3.9/lint.txt @@ -16,7 +16,7 @@ charset-normalizer==3.2.0 # requests dill==0.3.8 # via pylint -docker==6.1.3 +docker==7.1.0 ; python_version >= "3.8" # via # -c requirements/static/ci/py3.9/linux.txt # -r requirements/static/ci/lint.in @@ -28,10 +28,6 @@ isort==4.3.21 # via pylint mccabe==0.6.1 # via pylint -packaging==23.1 - # via - # -c requirements/static/ci/py3.9/linux.txt - # docker platformdirs==4.0.0 # via # -c requirements/static/ci/py3.9/linux.txt @@ -40,16 +36,12 @@ pylint==3.1.0 # via # -r requirements/static/ci/lint.in # saltpylint -requests==2.31.0 +requests==2.32.3 ; python_version >= "3.8" # via # -c requirements/static/ci/py3.9/linux.txt # docker saltpylint==2024.2.5 # via -r requirements/static/ci/lint.in -six==1.16.0 - # via - # -c requirements/static/ci/py3.9/linux.txt - # websocket-client toml==0.10.2 # via # -c requirements/static/ci/py3.9/linux.txt @@ -70,7 +62,3 @@ urllib3==1.26.18 # -c requirements/static/ci/py3.9/linux.txt # docker # requests -websocket-client==0.40.0 - # via - # -c requirements/static/ci/py3.9/linux.txt - # docker diff --git a/requirements/static/ci/py3.9/linux.txt b/requirements/static/ci/py3.9/linux.txt index 5fffedddf63..dd4d0c1630a 100644 --- a/requirements/static/ci/py3.9/linux.txt +++ b/requirements/static/ci/py3.9/linux.txt @@ -107,8 +107,10 @@ contextvars==2.4 # via # -c requirements/static/ci/../pkg/py3.9/linux.txt # -r requirements/base.txt -croniter==1.3.15 ; sys_platform != "win32" - # via -r requirements/static/ci/common.in +croniter==2.0.5 ; sys_platform != "win32" + # via + # -c requirements/static/ci/../pkg/py3.9/linux.txt + # -r requirements/base.txt cryptography==42.0.5 # via # -c requirements/static/ci/../pkg/py3.9/linux.txt @@ -130,7 +132,7 @@ dnspython==2.6.1 # via # -r requirements/static/ci/common.in # python-etcd -docker==6.1.3 +docker==7.1.0 ; python_version >= "3.8" # via -r requirements/pytest.txt etcd3-py==0.1.6 # via -r requirements/static/ci/common.in @@ -310,7 +312,6 @@ packaging==23.1 # via # -c requirements/static/ci/../pkg/py3.9/linux.txt # -r requirements/base.txt - # docker # pytest paramiko==3.4.0 ; sys_platform != "win32" and sys_platform != "darwin" # via @@ -373,7 +374,7 @@ pyinotify==0.9.6 ; sys_platform != "win32" and sys_platform != "darwin" and plat # via -r requirements/static/ci/common.in pyjwt==2.7.0 # via twilio -pymysql==1.1.0 +pymysql==1.1.1 # via -r requirements/static/ci/linux.in pynacl==1.5.0 # via @@ -449,6 +450,7 @@ python-telegram-bot==20.3 pytz==2024.1 # via # -c requirements/static/ci/../pkg/py3.9/linux.txt + # croniter # tempora # twilio pyvmomi==8.0.1.0.1 @@ -475,7 +477,7 @@ redis-py-cluster==2.1.3 # via -r requirements/static/ci/linux.in redis==3.5.3 # via redis-py-cluster -requests==2.31.0 +requests==2.32.3 ; python_version >= "3.8" # via # -c requirements/static/ci/../pkg/py3.9/linux.txt # -r requirements/base.txt @@ -609,9 +611,7 @@ virtualenv==20.24.7 watchdog==3.0.0 # via -r requirements/static/ci/common.in websocket-client==0.40.0 - # via - # docker - # kubernetes + # via kubernetes wempy==0.2.1 # via -r requirements/static/ci/common.in werkzeug==3.0.3 diff --git a/requirements/static/ci/py3.9/windows.txt b/requirements/static/ci/py3.9/windows.txt index 7cbe9e15ad1..5c610222c6b 100644 --- a/requirements/static/ci/py3.9/windows.txt +++ b/requirements/static/ci/py3.9/windows.txt @@ -120,7 +120,7 @@ dnspython==2.6.1 # via # -r requirements/static/ci/common.in # python-etcd -docker==6.1.3 +docker==7.1.0 ; python_version >= "3.8" # via -r requirements/pytest.txt etcd3-py==0.1.6 # via -r requirements/static/ci/common.in @@ -257,7 +257,6 @@ packaging==23.1 # via # -c requirements/static/ci/../pkg/py3.9/windows.txt # -r requirements/base.txt - # docker # pytest passlib==1.7.4 # via -r requirements/static/ci/common.in @@ -409,7 +408,7 @@ pyzmq==25.1.2 # pytest-salt-factories requests-ntlm==1.2.0 # via pywinrm -requests==2.31.0 +requests==2.32.3 ; python_version >= "3.8" # via # -c requirements/static/ci/../pkg/py3.9/windows.txt # -r requirements/base.txt @@ -506,9 +505,7 @@ virtualenv==20.24.7 watchdog==3.0.0 # via -r requirements/static/ci/common.in websocket-client==0.40.0 - # via - # docker - # kubernetes + # via kubernetes wempy==0.2.1 # via -r requirements/static/ci/common.in werkzeug==3.0.3 diff --git a/requirements/static/pkg/py3.10/darwin.txt b/requirements/static/pkg/py3.10/darwin.txt index affadfae9c0..d5b06ea94c7 100644 --- a/requirements/static/pkg/py3.10/darwin.txt +++ b/requirements/static/pkg/py3.10/darwin.txt @@ -28,6 +28,8 @@ cherrypy==18.8.0 # via -r requirements/base.txt contextvars==2.4 # via -r requirements/base.txt +croniter==2.0.5 ; sys_platform != "win32" + # via -r requirements/base.txt cryptography==42.0.5 # via # -r requirements/base.txt @@ -98,16 +100,20 @@ pydantic==2.6.4 pyopenssl==24.0.0 # via -r requirements/base.txt python-dateutil==2.8.2 - # via -r requirements/base.txt + # via + # -r requirements/base.txt + # croniter python-gnupg==0.5.2 # via -r requirements/base.txt pytz==2024.1 - # via tempora + # via + # croniter + # tempora pyyaml==6.0.1 # via -r requirements/base.txt pyzmq==25.1.2 # via -r requirements/zeromq.txt -requests==2.31.0 +requests==2.32.3 ; python_version >= "3.8" # via -r requirements/base.txt setproctitle==1.3.2 # via -r requirements/base.txt diff --git a/requirements/static/pkg/py3.10/freebsd.txt b/requirements/static/pkg/py3.10/freebsd.txt index 9009d3af758..3fd122e574d 100644 --- a/requirements/static/pkg/py3.10/freebsd.txt +++ b/requirements/static/pkg/py3.10/freebsd.txt @@ -28,6 +28,8 @@ cherrypy==18.8.0 # via -r requirements/base.txt contextvars==2.4 # via -r requirements/base.txt +croniter==2.0.5 ; sys_platform != "win32" + # via -r requirements/base.txt cryptography==42.0.5 # via # -r requirements/base.txt @@ -98,16 +100,20 @@ pydantic==2.6.4 pyopenssl==24.0.0 # via -r requirements/base.txt python-dateutil==2.8.2 - # via -r requirements/base.txt + # via + # -r requirements/base.txt + # croniter python-gnupg==0.5.2 # via -r requirements/base.txt pytz==2024.1 - # via tempora + # via + # croniter + # tempora pyyaml==6.0.1 # via -r requirements/base.txt pyzmq==25.1.2 # via -r requirements/zeromq.txt -requests==2.31.0 +requests==2.32.3 ; python_version >= "3.8" # via -r requirements/base.txt setproctitle==1.3.2 # via -r requirements/base.txt diff --git a/requirements/static/pkg/py3.10/linux.txt b/requirements/static/pkg/py3.10/linux.txt index a63d2ef6985..7f7f059baf3 100644 --- a/requirements/static/pkg/py3.10/linux.txt +++ b/requirements/static/pkg/py3.10/linux.txt @@ -28,6 +28,8 @@ cherrypy==18.8.0 # via -r requirements/base.txt contextvars==2.4 # via -r requirements/base.txt +croniter==2.0.5 ; sys_platform != "win32" + # via -r requirements/base.txt cryptography==42.0.5 # via # -r requirements/base.txt @@ -98,16 +100,20 @@ pydantic==2.6.4 pyopenssl==24.0.0 # via -r requirements/base.txt python-dateutil==2.8.2 - # via -r requirements/base.txt + # via + # -r requirements/base.txt + # croniter python-gnupg==0.5.2 # via -r requirements/base.txt pytz==2024.1 - # via tempora + # via + # croniter + # tempora pyyaml==6.0.1 # via -r requirements/base.txt pyzmq==25.1.2 # via -r requirements/zeromq.txt -requests==2.31.0 +requests==2.32.3 ; python_version >= "3.8" # via -r requirements/base.txt rpm-vercmp==0.1.2 ; sys_platform == "linux" # via -r requirements/base.txt diff --git a/requirements/static/pkg/py3.10/windows.txt b/requirements/static/pkg/py3.10/windows.txt index e394963e9f2..b44be8971f8 100644 --- a/requirements/static/pkg/py3.10/windows.txt +++ b/requirements/static/pkg/py3.10/windows.txt @@ -127,7 +127,7 @@ pyyaml==6.0.1 # via -r requirements/base.txt pyzmq==25.1.2 # via -r requirements/zeromq.txt -requests==2.31.0 +requests==2.32.3 ; python_version >= "3.8" # via -r requirements/base.txt setproctitle==1.3.2 # via -r requirements/base.txt diff --git a/requirements/static/pkg/py3.11/darwin.txt b/requirements/static/pkg/py3.11/darwin.txt index c970bfae004..db6c4d08a5a 100644 --- a/requirements/static/pkg/py3.11/darwin.txt +++ b/requirements/static/pkg/py3.11/darwin.txt @@ -26,6 +26,8 @@ cherrypy==18.8.0 # via -r requirements/base.txt contextvars==2.4 # via -r requirements/base.txt +croniter==2.0.5 ; sys_platform != "win32" + # via -r requirements/base.txt cryptography==42.0.5 # via # -r requirements/base.txt @@ -96,16 +98,20 @@ pydantic==2.6.4 pyopenssl==24.0.0 # via -r requirements/base.txt python-dateutil==2.8.2 - # via -r requirements/base.txt + # via + # -r requirements/base.txt + # croniter python-gnupg==0.5.2 # via -r requirements/base.txt pytz==2024.1 - # via tempora + # via + # croniter + # tempora pyyaml==6.0.1 # via -r requirements/base.txt pyzmq==25.1.2 # via -r requirements/zeromq.txt -requests==2.31.0 +requests==2.32.3 ; python_version >= "3.8" # via -r requirements/base.txt setproctitle==1.3.2 # via -r requirements/base.txt diff --git a/requirements/static/pkg/py3.11/freebsd.txt b/requirements/static/pkg/py3.11/freebsd.txt index c310a5c98e8..99d25ff7113 100644 --- a/requirements/static/pkg/py3.11/freebsd.txt +++ b/requirements/static/pkg/py3.11/freebsd.txt @@ -26,6 +26,8 @@ cherrypy==18.8.0 # via -r requirements/base.txt contextvars==2.4 # via -r requirements/base.txt +croniter==2.0.5 ; sys_platform != "win32" + # via -r requirements/base.txt cryptography==42.0.5 # via # -r requirements/base.txt @@ -96,16 +98,20 @@ pydantic==2.6.4 pyopenssl==24.0.0 # via -r requirements/base.txt python-dateutil==2.8.2 - # via -r requirements/base.txt + # via + # -r requirements/base.txt + # croniter python-gnupg==0.5.2 # via -r requirements/base.txt pytz==2024.1 - # via tempora + # via + # croniter + # tempora pyyaml==6.0.1 # via -r requirements/base.txt pyzmq==25.1.2 # via -r requirements/zeromq.txt -requests==2.31.0 +requests==2.32.3 ; python_version >= "3.8" # via -r requirements/base.txt setproctitle==1.3.2 # via -r requirements/base.txt diff --git a/requirements/static/pkg/py3.11/linux.txt b/requirements/static/pkg/py3.11/linux.txt index b5e0cf7d992..4f25b2b1030 100644 --- a/requirements/static/pkg/py3.11/linux.txt +++ b/requirements/static/pkg/py3.11/linux.txt @@ -26,6 +26,8 @@ cherrypy==18.8.0 # via -r requirements/base.txt contextvars==2.4 # via -r requirements/base.txt +croniter==2.0.5 ; sys_platform != "win32" + # via -r requirements/base.txt cryptography==42.0.5 # via # -r requirements/base.txt @@ -96,16 +98,20 @@ pydantic==2.6.4 pyopenssl==24.0.0 # via -r requirements/base.txt python-dateutil==2.8.2 - # via -r requirements/base.txt + # via + # -r requirements/base.txt + # croniter python-gnupg==0.5.2 # via -r requirements/base.txt pytz==2024.1 - # via tempora + # via + # croniter + # tempora pyyaml==6.0.1 # via -r requirements/base.txt pyzmq==25.1.2 # via -r requirements/zeromq.txt -requests==2.31.0 +requests==2.32.3 ; python_version >= "3.8" # via -r requirements/base.txt rpm-vercmp==0.1.2 ; sys_platform == "linux" # via -r requirements/base.txt diff --git a/requirements/static/pkg/py3.11/windows.txt b/requirements/static/pkg/py3.11/windows.txt index c2ff838f42a..6815187d2a4 100644 --- a/requirements/static/pkg/py3.11/windows.txt +++ b/requirements/static/pkg/py3.11/windows.txt @@ -125,7 +125,7 @@ pyyaml==6.0.1 # via -r requirements/base.txt pyzmq==25.1.2 # via -r requirements/zeromq.txt -requests==2.31.0 +requests==2.32.3 ; python_version >= "3.8" # via -r requirements/base.txt setproctitle==1.3.2 # via -r requirements/base.txt diff --git a/requirements/static/pkg/py3.12/darwin.txt b/requirements/static/pkg/py3.12/darwin.txt index c7ab2a38fd4..51020f0170a 100644 --- a/requirements/static/pkg/py3.12/darwin.txt +++ b/requirements/static/pkg/py3.12/darwin.txt @@ -26,6 +26,8 @@ cherrypy==18.8.0 # via -r requirements/base.txt contextvars==2.4 # via -r requirements/base.txt +croniter==2.0.5 ; sys_platform != "win32" + # via -r requirements/base.txt cryptography==42.0.5 # via # -r requirements/base.txt @@ -96,16 +98,20 @@ pydantic==2.6.4 pyopenssl==24.0.0 # via -r requirements/base.txt python-dateutil==2.8.2 - # via -r requirements/base.txt + # via + # -r requirements/base.txt + # croniter python-gnupg==0.5.2 # via -r requirements/base.txt pytz==2024.1 - # via tempora + # via + # croniter + # tempora pyyaml==6.0.1 # via -r requirements/base.txt pyzmq==25.1.2 # via -r requirements/zeromq.txt -requests==2.31.0 +requests==2.32.3 ; python_version >= "3.8" # via -r requirements/base.txt setproctitle==1.3.2 # via -r requirements/base.txt diff --git a/requirements/static/pkg/py3.12/freebsd.txt b/requirements/static/pkg/py3.12/freebsd.txt index 78c3b94de28..95bedeebbe5 100644 --- a/requirements/static/pkg/py3.12/freebsd.txt +++ b/requirements/static/pkg/py3.12/freebsd.txt @@ -26,6 +26,8 @@ cherrypy==18.8.0 # via -r requirements/base.txt contextvars==2.4 # via -r requirements/base.txt +croniter==2.0.5 ; sys_platform != "win32" + # via -r requirements/base.txt cryptography==42.0.5 # via # -r requirements/base.txt @@ -96,16 +98,20 @@ pydantic==2.6.4 pyopenssl==24.0.0 # via -r requirements/base.txt python-dateutil==2.8.2 - # via -r requirements/base.txt + # via + # -r requirements/base.txt + # croniter python-gnupg==0.5.2 # via -r requirements/base.txt pytz==2024.1 - # via tempora + # via + # croniter + # tempora pyyaml==6.0.1 # via -r requirements/base.txt pyzmq==25.1.2 # via -r requirements/zeromq.txt -requests==2.31.0 +requests==2.32.3 ; python_version >= "3.8" # via -r requirements/base.txt setproctitle==1.3.2 # via -r requirements/base.txt diff --git a/requirements/static/pkg/py3.12/linux.txt b/requirements/static/pkg/py3.12/linux.txt index 8f9dfbddd2f..1da1d6f25a0 100644 --- a/requirements/static/pkg/py3.12/linux.txt +++ b/requirements/static/pkg/py3.12/linux.txt @@ -26,6 +26,8 @@ cherrypy==18.8.0 # via -r requirements/base.txt contextvars==2.4 # via -r requirements/base.txt +croniter==2.0.5 ; sys_platform != "win32" + # via -r requirements/base.txt cryptography==42.0.5 # via # -r requirements/base.txt @@ -96,16 +98,20 @@ pydantic==2.6.4 pyopenssl==24.0.0 # via -r requirements/base.txt python-dateutil==2.8.2 - # via -r requirements/base.txt + # via + # -r requirements/base.txt + # croniter python-gnupg==0.5.2 # via -r requirements/base.txt pytz==2024.1 - # via tempora + # via + # croniter + # tempora pyyaml==6.0.1 # via -r requirements/base.txt pyzmq==25.1.2 # via -r requirements/zeromq.txt -requests==2.31.0 +requests==2.32.3 ; python_version >= "3.8" # via -r requirements/base.txt rpm-vercmp==0.1.2 ; sys_platform == "linux" # via -r requirements/base.txt diff --git a/requirements/static/pkg/py3.12/windows.txt b/requirements/static/pkg/py3.12/windows.txt index 9221269e9a8..3b5b7009c04 100644 --- a/requirements/static/pkg/py3.12/windows.txt +++ b/requirements/static/pkg/py3.12/windows.txt @@ -125,7 +125,7 @@ pyyaml==6.0.1 # via -r requirements/base.txt pyzmq==25.1.2 # via -r requirements/zeromq.txt -requests==2.31.0 +requests==2.32.3 ; python_version >= "3.8" # via -r requirements/base.txt setproctitle==1.3.2 # via -r requirements/base.txt diff --git a/requirements/static/pkg/py3.8/freebsd.txt b/requirements/static/pkg/py3.8/freebsd.txt index 1644cc7c608..1097495bedd 100644 --- a/requirements/static/pkg/py3.8/freebsd.txt +++ b/requirements/static/pkg/py3.8/freebsd.txt @@ -28,6 +28,8 @@ cherrypy==18.8.0 # via -r requirements/base.txt contextvars==2.4 # via -r requirements/base.txt +croniter==2.0.5 ; sys_platform != "win32" + # via -r requirements/base.txt cryptography==42.0.5 # via # -r requirements/base.txt @@ -100,16 +102,20 @@ pydantic==2.6.4 pyopenssl==24.0.0 # via -r requirements/base.txt python-dateutil==2.8.2 - # via -r requirements/base.txt + # via + # -r requirements/base.txt + # croniter python-gnupg==0.5.2 # via -r requirements/base.txt pytz==2024.1 - # via tempora + # via + # croniter + # tempora pyyaml==6.0.1 # via -r requirements/base.txt pyzmq==25.1.2 # via -r requirements/zeromq.txt -requests==2.31.0 +requests==2.32.3 ; python_version >= "3.8" # via -r requirements/base.txt setproctitle==1.3.2 # via -r requirements/base.txt diff --git a/requirements/static/pkg/py3.8/linux.txt b/requirements/static/pkg/py3.8/linux.txt index f2fa6d50298..e144ef6b5a0 100644 --- a/requirements/static/pkg/py3.8/linux.txt +++ b/requirements/static/pkg/py3.8/linux.txt @@ -28,6 +28,8 @@ cherrypy==18.8.0 # via -r requirements/base.txt contextvars==2.4 # via -r requirements/base.txt +croniter==2.0.5 ; sys_platform != "win32" + # via -r requirements/base.txt cryptography==42.0.5 # via # -r requirements/base.txt @@ -100,16 +102,20 @@ pydantic==2.6.4 pyopenssl==24.0.0 # via -r requirements/base.txt python-dateutil==2.8.2 - # via -r requirements/base.txt + # via + # -r requirements/base.txt + # croniter python-gnupg==0.5.2 # via -r requirements/base.txt pytz==2024.1 - # via tempora + # via + # croniter + # tempora pyyaml==6.0.1 # via -r requirements/base.txt pyzmq==25.1.2 # via -r requirements/zeromq.txt -requests==2.31.0 +requests==2.32.3 ; python_version >= "3.8" # via -r requirements/base.txt rpm-vercmp==0.1.2 ; sys_platform == "linux" # via -r requirements/base.txt diff --git a/requirements/static/pkg/py3.8/windows.txt b/requirements/static/pkg/py3.8/windows.txt index 0c12c435da2..11d7cf6d112 100644 --- a/requirements/static/pkg/py3.8/windows.txt +++ b/requirements/static/pkg/py3.8/windows.txt @@ -130,7 +130,7 @@ pyyaml==6.0.1 # via -r requirements/base.txt pyzmq==25.1.2 # via -r requirements/zeromq.txt -requests==2.31.0 +requests==2.32.3 ; python_version >= "3.8" # via -r requirements/base.txt setproctitle==1.3.2 # via -r requirements/base.txt diff --git a/requirements/static/pkg/py3.9/darwin.txt b/requirements/static/pkg/py3.9/darwin.txt index 77895391f14..47ef9bfda36 100644 --- a/requirements/static/pkg/py3.9/darwin.txt +++ b/requirements/static/pkg/py3.9/darwin.txt @@ -28,6 +28,8 @@ cherrypy==18.8.0 # via -r requirements/base.txt contextvars==2.4 # via -r requirements/base.txt +croniter==2.0.5 ; sys_platform != "win32" + # via -r requirements/base.txt cryptography==42.0.5 # via # -r requirements/base.txt @@ -98,16 +100,20 @@ pydantic==2.6.4 pyopenssl==24.0.0 # via -r requirements/base.txt python-dateutil==2.8.2 - # via -r requirements/base.txt + # via + # -r requirements/base.txt + # croniter python-gnupg==0.5.2 # via -r requirements/base.txt pytz==2024.1 - # via tempora + # via + # croniter + # tempora pyyaml==6.0.1 # via -r requirements/base.txt pyzmq==25.1.2 # via -r requirements/zeromq.txt -requests==2.31.0 +requests==2.32.3 ; python_version >= "3.8" # via -r requirements/base.txt setproctitle==1.3.2 # via -r requirements/base.txt diff --git a/requirements/static/pkg/py3.9/freebsd.txt b/requirements/static/pkg/py3.9/freebsd.txt index e6d6d2d1f13..bde4cdef69b 100644 --- a/requirements/static/pkg/py3.9/freebsd.txt +++ b/requirements/static/pkg/py3.9/freebsd.txt @@ -28,6 +28,8 @@ cherrypy==18.8.0 # via -r requirements/base.txt contextvars==2.4 # via -r requirements/base.txt +croniter==2.0.5 ; sys_platform != "win32" + # via -r requirements/base.txt cryptography==42.0.5 # via # -r requirements/base.txt @@ -98,16 +100,20 @@ pydantic==2.6.4 pyopenssl==24.0.0 # via -r requirements/base.txt python-dateutil==2.8.2 - # via -r requirements/base.txt + # via + # -r requirements/base.txt + # croniter python-gnupg==0.5.2 # via -r requirements/base.txt pytz==2024.1 - # via tempora + # via + # croniter + # tempora pyyaml==6.0.1 # via -r requirements/base.txt pyzmq==25.1.2 # via -r requirements/zeromq.txt -requests==2.31.0 +requests==2.32.3 ; python_version >= "3.8" # via -r requirements/base.txt setproctitle==1.3.2 # via -r requirements/base.txt diff --git a/requirements/static/pkg/py3.9/linux.txt b/requirements/static/pkg/py3.9/linux.txt index bbe9f02990d..44e4bb6ef18 100644 --- a/requirements/static/pkg/py3.9/linux.txt +++ b/requirements/static/pkg/py3.9/linux.txt @@ -28,6 +28,8 @@ cherrypy==18.8.0 # via -r requirements/base.txt contextvars==2.4 # via -r requirements/base.txt +croniter==2.0.5 ; sys_platform != "win32" + # via -r requirements/base.txt cryptography==42.0.5 # via # -r requirements/base.txt @@ -98,16 +100,20 @@ pydantic==2.6.4 pyopenssl==24.0.0 # via -r requirements/base.txt python-dateutil==2.8.2 - # via -r requirements/base.txt + # via + # -r requirements/base.txt + # croniter python-gnupg==0.5.2 # via -r requirements/base.txt pytz==2024.1 - # via tempora + # via + # croniter + # tempora pyyaml==6.0.1 # via -r requirements/base.txt pyzmq==25.1.2 # via -r requirements/zeromq.txt -requests==2.31.0 +requests==2.32.3 ; python_version >= "3.8" # via -r requirements/base.txt rpm-vercmp==0.1.2 ; sys_platform == "linux" # via -r requirements/base.txt diff --git a/requirements/static/pkg/py3.9/windows.txt b/requirements/static/pkg/py3.9/windows.txt index a9a4aa82d64..b85fe898451 100644 --- a/requirements/static/pkg/py3.9/windows.txt +++ b/requirements/static/pkg/py3.9/windows.txt @@ -128,7 +128,7 @@ pyyaml==6.0.1 # via -r requirements/base.txt pyzmq==25.1.2 # via -r requirements/zeromq.txt -requests==2.31.0 +requests==2.32.3 ; python_version >= "3.8" # via -r requirements/base.txt setproctitle==1.3.2 # via -r requirements/base.txt diff --git a/salt/channel/client.py b/salt/channel/client.py index cb85ce024e2..3483e7237e4 100644 --- a/salt/channel/client.py +++ b/salt/channel/client.py @@ -24,21 +24,6 @@ import salt.utils.verify import salt.utils.versions from salt.utils.asynchronous import SyncWrapper -try: - from M2Crypto import RSA - - HAS_M2 = True -except ImportError: - HAS_M2 = False - try: - from Cryptodome.Cipher import PKCS1_OAEP - except ImportError: - try: - from Crypto.Cipher import PKCS1_OAEP # nosec - except ImportError: - pass - - log = logging.getLogger(__name__) REQUEST_CHANNEL_TIMEOUT = 60 @@ -168,11 +153,15 @@ class AsyncReqChannel: return self.transport.ttype def _package_load(self, load): - return { + ret = { "enc": self.crypt, "load": load, "version": 2, } + if self.crypt == "aes": + ret["enc_algo"] = self.opts["encryption_algorithm"] + ret["sig_algo"] = self.opts["signing_algorithm"] + return ret @tornado.gen.coroutine def _send_with_retry(self, load, tries, timeout): @@ -223,11 +212,7 @@ class AsyncReqChannel: tries, timeout, ) - if HAS_M2: - aes = key.private_decrypt(ret["key"], RSA.pkcs1_oaep_padding) - else: - cipher = PKCS1_OAEP.new(key) # pylint: disable=used-before-assignment - aes = cipher.decrypt(ret["key"]) + aes = key.decrypt(ret["key"], self.opts["encryption_algorithm"]) # Decrypt using the public key. pcrypt = salt.crypt.Crypticle(self.opts, aes) @@ -250,7 +235,9 @@ class AsyncReqChannel: raise tornado.gen.Return(data["pillar"]) def verify_signature(self, data, sig): - return salt.crypt.verify_signature(self.master_pubkey_path, data, sig) + return salt.crypt.PublicKey(self.master_pubkey_path).verify( + data, sig, self.opts["signing_algorithm"] + ) @tornado.gen.coroutine def _crypted_transfer(self, load, timeout, raw=False): @@ -594,7 +581,10 @@ class AsyncPubChannel: # Verify that the signature is valid if not salt.crypt.verify_signature( - self.master_pubkey_path, payload["load"], payload.get("sig") + self.master_pubkey_path, + payload["load"], + payload.get("sig"), + algorithm=payload["sig_algo"], ): raise salt.crypt.AuthenticationError( "Message signature failed to validate." diff --git a/salt/channel/server.py b/salt/channel/server.py index 610dfe2b9cc..5fcca7fd59f 100644 --- a/salt/channel/server.py +++ b/salt/channel/server.py @@ -26,21 +26,9 @@ import salt.utils.minions import salt.utils.platform import salt.utils.stringutils import salt.utils.verify -from salt.exceptions import SaltDeserializationError +from salt.exceptions import SaltDeserializationError, UnsupportedAlgorithm from salt.utils.cache import CacheCli -try: - from M2Crypto import RSA - - HAS_M2 = True -except ImportError: - HAS_M2 = False - try: - from Cryptodome.Cipher import PKCS1_OAEP - except ImportError: - from Crypto.Cipher import PKCS1_OAEP # nosec - - log = logging.getLogger(__name__) @@ -197,13 +185,24 @@ class ReqServerChannel: req_opts["tgt"], nonce, sign_messages, + payload.get("enc_algo", salt.crypt.OAEP_SHA1), + payload.get("sig_algo", salt.crypt.PKCS1v15_SHA1), ), ) log.error("Unknown req_fun %s", req_fun) # always attempt to return an error to the minion raise tornado.gen.Return("Server-side exception handling payload") - def _encrypt_private(self, ret, dictkey, target, nonce=None, sign_messages=True): + def _encrypt_private( + self, + ret, + dictkey, + target, + nonce=None, + sign_messages=True, + encryption_algorithm=salt.crypt.OAEP_SHA1, + signing_algorithm=salt.crypt.PKCS1v15_SHA1, + ): """ The server equivalent of ReqChannel.crypted_transfer_decode_dictentry """ @@ -222,7 +221,7 @@ class ReqServerChannel: log.error("AES key not found") return {"error": "AES key not found"} pret = {} - pret["key"] = pub.encrypt(key) + pret["key"] = pub.encrypt(key, encryption_algorithm) if ret is False: ret = {} if sign_messages: @@ -233,20 +232,31 @@ class ReqServerChannel: ) signed_msg = { "data": tosign, - "sig": salt.crypt.PrivateKey(self.master_key.rsa_path).sign(tosign), + "sig": salt.crypt.PrivateKey(self.master_key.rsa_path).sign( + tosign, algorithm=signing_algorithm + ), } pret[dictkey] = pcrypt.dumps(signed_msg) else: pret[dictkey] = pcrypt.dumps(ret) return pret - def _clear_signed(self, load): - tosign = salt.payload.dumps(load) - return { - "enc": "clear", - "load": tosign, - "sig": salt.crypt.sign_message(self.master_key.rsa_path, tosign), - } + def _clear_signed(self, load, algorithm): + try: + tosign = salt.payload.dumps(load) + return { + "enc": "clear", + "load": tosign, + "sig": salt.crypt.PrivateKey(self.master_key.rsa_path).sign( + tosign, algorithm=algorithm + ), + } + except UnsupportedAlgorithm: + log.info( + "Minion tried to authenticate with unsupported signing algorithm: %s", + algorithm, + ) + return {"enc": "clear", "load": {"ret": "bad sig algo"}} def _update_aes(self): """ @@ -306,10 +316,15 @@ class ReqServerChannel: """ import salt.master + enc_algo = load.get("enc_algo", salt.crypt.OAEP_SHA1) + sig_algo = load.get("sig_algo", salt.crypt.PKCS1v15_SHA1) + if not salt.utils.verify.valid_id(self.opts, load["id"]): log.info("Authentication request from invalid id %s", load["id"]) if sign_messages: - return self._clear_signed({"ret": False, "nonce": load["nonce"]}) + return self._clear_signed( + {"ret": False, "nonce": load["nonce"]}, sig_algo + ) else: return {"enc": "clear", "load": {"ret": False}} log.info("Authentication request from %s", load["id"]) @@ -351,7 +366,7 @@ class ReqServerChannel: ) if sign_messages: return self._clear_signed( - {"ret": "full", "nonce": load["nonce"]} + {"ret": "full", "nonce": load["nonce"]}, sig_algo ) else: return {"enc": "clear", "load": {"ret": "full"}} @@ -385,7 +400,9 @@ class ReqServerChannel: if self.opts.get("auth_events") is True: self.event.fire_event(eload, salt.utils.event.tagify(prefix="auth")) if sign_messages: - return self._clear_signed({"ret": False, "nonce": load["nonce"]}) + return self._clear_signed( + {"ret": False, "nonce": load["nonce"]}, sig_algo + ) else: return {"enc": "clear", "load": {"ret": False}} elif os.path.isfile(pubfn): @@ -413,7 +430,7 @@ class ReqServerChannel: ) if sign_messages: return self._clear_signed( - {"ret": False, "nonce": load["nonce"]} + {"ret": False, "nonce": load["nonce"]}, sig_algo ) else: return {"enc": "clear", "load": {"ret": False}} @@ -427,7 +444,9 @@ class ReqServerChannel: if self.opts.get("auth_events") is True: self.event.fire_event(eload, salt.utils.event.tagify(prefix="auth")) if sign_messages: - return self._clear_signed({"ret": False, "nonce": load["nonce"]}) + return self._clear_signed( + {"ret": False, "nonce": load["nonce"]}, sig_algo + ) else: return {"enc": "clear", "load": {"ret": False}} @@ -462,7 +481,8 @@ class ReqServerChannel: self.event.fire_event(eload, salt.utils.event.tagify(prefix="auth")) if sign_messages: return self._clear_signed( - {"ret": key_result, "nonce": load["nonce"]} + {"ret": key_result, "nonce": load["nonce"]}, + sig_algo, ) else: return {"enc": "clear", "load": {"ret": key_result}} @@ -490,7 +510,9 @@ class ReqServerChannel: if self.opts.get("auth_events") is True: self.event.fire_event(eload, salt.utils.event.tagify(prefix="auth")) if sign_messages: - return self._clear_signed({"ret": False, "nonce": load["nonce"]}) + return self._clear_signed( + {"ret": False, "nonce": load["nonce"]}, sig_algo + ) else: return {"enc": "clear", "load": {"ret": False}} @@ -522,7 +544,7 @@ class ReqServerChannel: ) if sign_messages: return self._clear_signed( - {"ret": False, "nonce": load["nonce"]} + {"ret": False, "nonce": load["nonce"]}, sig_algo ) else: return {"enc": "clear", "load": {"ret": False}} @@ -546,7 +568,7 @@ class ReqServerChannel: ) if sign_messages: return self._clear_signed( - {"ret": True, "nonce": load["nonce"]} + {"ret": True, "nonce": load["nonce"]}, sig_algo ) else: return {"enc": "clear", "load": {"ret": True}} @@ -573,7 +595,7 @@ class ReqServerChannel: ) if sign_messages: return self._clear_signed( - {"ret": False, "nonce": load["nonce"]} + {"ret": False, "nonce": load["nonce"]}, sig_algo ) else: return {"enc": "clear", "load": {"ret": False}} @@ -587,7 +609,9 @@ class ReqServerChannel: if self.opts.get("auth_events") is True: self.event.fire_event(eload, salt.utils.event.tagify(prefix="auth")) if sign_messages: - return self._clear_signed({"ret": False, "nonce": load["nonce"]}) + return self._clear_signed( + {"ret": False, "nonce": load["nonce"]}, sig_algo + ) else: return {"enc": "clear", "load": {"ret": False}} @@ -609,7 +633,9 @@ class ReqServerChannel: elif not load["pub"]: log.error("Public key is empty: %s", load["id"]) if sign_messages: - return self._clear_signed({"ret": False, "nonce": load["nonce"]}) + return self._clear_signed( + {"ret": False, "nonce": load["nonce"]}, sig_algo + ) else: return {"enc": "clear", "load": {"ret": False}} @@ -622,16 +648,16 @@ class ReqServerChannel: # The key payload may sometimes be corrupt when using auto-accept # and an empty request comes in try: - pub = salt.crypt.get_rsa_pub_key(pubfn) + pub = salt.crypt.PublicKey(pubfn) except salt.crypt.InvalidKeyError as err: log.error('Corrupt public key "%s": %s', pubfn, err) if sign_messages: - return self._clear_signed({"ret": False, "nonce": load["nonce"]}) + return self._clear_signed( + {"ret": False, "nonce": load["nonce"]}, sig_algo + ) else: return {"enc": "clear", "load": {"ret": False}} - if not HAS_M2: - cipher = PKCS1_OAEP.new(pub) # pylint: disable=used-before-assignment ret = { "enc": "pub", "pub_key": self.master_key.get_pub_str(), @@ -654,69 +680,66 @@ class ReqServerChannel: key_pass = salt.utils.sdb.sdb_get( self.opts["signing_key_pass"], self.opts ) - log.debug("Signing master public key before sending") pub_sign = salt.crypt.sign_message( - self.master_key.get_sign_paths()[1], ret["pub_key"], key_pass + self.master_key.get_sign_paths()[1], + ret["pub_key"], + key_pass, + algorithm=sig_algo, ) ret.update({"pub_sig": binascii.b2a_base64(pub_sign)}) - if not HAS_M2: - mcipher = PKCS1_OAEP.new(self.master_key.key) if self.opts["auth_mode"] >= 2: if "token" in load: try: - if HAS_M2: - mtoken = self.master_key.key.private_decrypt( - load["token"], RSA.pkcs1_oaep_padding - ) - else: - mtoken = mcipher.decrypt(load["token"]) - aes = f"{self.aes_key}_|-{mtoken}" - except Exception: # pylint: disable=broad-except + mtoken = self.master_key.key.decrypt(load["token"], enc_algo) + aes = "{}_|-{}".format( + salt.master.SMaster.secrets["aes"]["secret"].value, mtoken + ) + except UnsupportedAlgorithm as exc: + log.info( + "Minion %s tried to authenticate with unsupported encryption algorithm: %s", + load["id"], + enc_algo, + ) + return {"enc": "clear", "load": {"ret": "bad enc algo"}} + except Exception as exc: # pylint: disable=broad-except + log.warning("Token failed to decrypt %s", exc) # Token failed to decrypt, send back the salty bacon to # support older minions - pass else: aes = self.aes_key - if HAS_M2: - ret["aes"] = pub.public_encrypt(aes, RSA.pkcs1_oaep_padding) - else: - ret["aes"] = cipher.encrypt(aes) + ret["aes"] = pub.encrypt(aes, enc_algo) else: if "token" in load: try: - if HAS_M2: - mtoken = self.master_key.key.private_decrypt( - load["token"], RSA.pkcs1_oaep_padding - ) - ret["token"] = pub.public_encrypt( - mtoken, RSA.pkcs1_oaep_padding - ) - else: - mtoken = mcipher.decrypt(load["token"]) - ret["token"] = cipher.encrypt(mtoken) - except Exception: # pylint: disable=broad-except + mtoken = self.master_key.key.decrypt(load["token"], enc_algo) + ret["token"] = pub.encrypt(mtoken, enc_algo) + except UnsupportedAlgorithm as exc: + log.info( + "Minion %s tried to authenticate with unsupported encryption algorithm: %s", + load["id"], + enc_algo, + ) + return {"enc": "clear", "load": {"ret": "bad enc algo"}} + except Exception as exc: # pylint: disable=broad-except # Token failed to decrypt, send back the salty bacon to # support older minions - pass + log.warning("Token failed to decrypt: %r", exc) aes = self.aes_key - if HAS_M2: - ret["aes"] = pub.public_encrypt(aes, RSA.pkcs1_oaep_padding) - else: - ret["aes"] = cipher.encrypt(aes) + ret["aes"] = pub.encrypt(aes, enc_algo) # Be aggressive about the signature digest = salt.utils.stringutils.to_bytes(hashlib.sha256(aes).hexdigest()) - ret["sig"] = salt.crypt.private_encrypt(self.master_key.key, digest) + ret["sig"] = self.master_key.key.encrypt(digest) eload = {"result": True, "act": "accept", "id": load["id"], "pub": load["pub"]} if self.opts.get("auth_events") is True: self.event.fire_event(eload, salt.utils.event.tagify(prefix="auth")) if sign_messages: ret["nonce"] = load["nonce"] - return self._clear_signed(ret) + return self._clear_signed(ret, sig_algo) return ret def close(self): @@ -898,9 +921,11 @@ class PubServerChannel: payload["load"] = crypticle.dumps(load) if self.opts["sign_pub_messages"]: log.debug("Signing data packet") + payload["sig_algo"] = self.opts["publish_signing_algorithm"] payload["sig"] = salt.crypt.PrivateKey( self.master_key.rsa_path, - ).sign(payload["load"]) + ).sign(payload["load"], self.opts["publish_signing_algorithm"]) + int_payload = {"payload": salt.payload.dumps(payload)} # If topics are upported, target matching has to happen master side @@ -964,10 +989,8 @@ class MasterPubServerChannel: hashlib.sha256(aes).hexdigest() ) data["peers"][peer] = { - "aes": pub.encrypt(aes), - "sig": salt.crypt.private_encrypt( - self.master_key.master_key, digest - ), + "aes": pub.encrypt(aes, algorithm="OAEP-SHA224"), + "sig": self.master_key.master_key.encrypt(digest), } else: log.warning("Peer key missing %r", peer_pub) @@ -1024,7 +1047,7 @@ class MasterPubServerChannel: self.pushers = [] self.auth_errors = {} for peer in self.opts.get("cluster_peers", []): - pusher = salt.transport.tcp.TCPPublishServer( + pusher = salt.transport.tcp.PublishServer( self.opts, pull_host=peer, pull_port=tcp_master_pool_port, @@ -1062,7 +1085,9 @@ class MasterPubServerChannel: peer = data["peer_id"] aes = data["peers"][self.opts["id"]]["aes"] sig = data["peers"][self.opts["id"]]["sig"] - key_str = self.master_key.master_private_decrypt(aes) + key_str = self.master_key.master_key.decrypt( + aes, algorithm="OAEP-SHA224" + ) digest = salt.utils.stringutils.to_bytes( hashlib.sha256(key_str).hexdigest() ) diff --git a/salt/cloud/__init__.py b/salt/cloud/__init__.py index 61daa8db521..db657d097fd 100644 --- a/salt/cloud/__init__.py +++ b/salt/cloud/__init__.py @@ -35,14 +35,6 @@ from salt.exceptions import ( ) from salt.template import compile_template -try: - import Cryptodome.Random -except ImportError: - try: - import Crypto.Random # nosec - except ImportError: - pass # pycrypto < 2.1 - log = logging.getLogger(__name__) @@ -2288,8 +2280,6 @@ def create_multiprocessing(parallel_data, queue=None): This function will be called from another process when running a map in parallel mode. The result from the create is always a json object. """ - salt.utils.crypt.reinit_crypto() - parallel_data["opts"]["output"] = "json" cloud = Cloud(parallel_data["opts"]) try: @@ -2318,8 +2308,6 @@ def destroy_multiprocessing(parallel_data, queue=None): This function will be called from another process when running a map in parallel mode. The result from the destroy is always a json object. """ - salt.utils.crypt.reinit_crypto() - parallel_data["opts"]["output"] = "json" clouds = salt.loader.clouds(parallel_data["opts"]) @@ -2350,8 +2338,6 @@ def run_parallel_map_providers_query(data, queue=None): This function will be called from another process when building the providers map. """ - salt.utils.crypt.reinit_crypto() - cloud = Cloud(data["opts"]) try: with salt.utils.context.func_globals_inject( diff --git a/salt/config/__init__.py b/salt/config/__init__.py index 27a3d3d8000..5919f92ddfc 100644 --- a/salt/config/__init__.py +++ b/salt/config/__init__.py @@ -13,6 +13,7 @@ import types import urllib.parse from copy import deepcopy +import salt.crypt import salt.defaults.exitcodes import salt.exceptions import salt.features @@ -1007,6 +1008,12 @@ VALID_OPTS = immutabletypes.freeze( "fileserver_interval": int, "request_channel_timeout": int, "request_channel_tries": int, + # RSA encryption for minion + "encryption_algorithm": str, + # RSA signing for minion + "signing_algorithm": str, + # Master publish channel signing + "publish_signing_algorithm": str, } ) @@ -1314,6 +1321,8 @@ DEFAULT_MINION_OPTS = immutabletypes.freeze( "reactor_niceness": None, "fips_mode": False, "features": {}, + "encryption_algorithm": "OAEP-SHA1", + "signing_algorithm": "PKCS1v15-SHA1", } ) @@ -1666,6 +1675,7 @@ DEFAULT_MASTER_OPTS = immutabletypes.freeze( "cluster_peers": [], "cluster_pki_dir": None, "features": {}, + "publish_signing_algorithm": "PKCS1v15-SHA1", } ) @@ -2222,6 +2232,18 @@ def include_config(include, orig_path, verbose, exit_on_config_errors=False): return configuration +def should_prepend_root_dir(key, opts): + """ + Prepend root dir only when the key exists, has a value, and that value is + not a URI. + """ + return ( + key in opts + and opts[key] is not None + and urllib.parse.urlparse(os.path.splitdrive(opts[key])[1]).scheme == "" + ) + + def prepend_root_dir(opts, path_options): """ Prepends the options that represent filesystem paths with value of the @@ -2518,8 +2540,7 @@ def syndic_config( "autosign_grains_dir", ] for config_key in ("log_file", "key_logfile", "syndic_log_file"): - # If this is not a URI and instead a local path - if urllib.parse.urlparse(opts.get(config_key, "")).scheme == "": + if should_prepend_root_dir(config_key, opts): prepend_root_dirs.append(config_key) prepend_root_dir(opts, prepend_root_dirs) salt.features.setup_features(opts) @@ -2771,8 +2792,8 @@ def cloud_config( # prepend root_dir prepend_root_dirs = ["cachedir"] - if "log_file" in opts and urllib.parse.urlparse(opts["log_file"]).scheme == "": - prepend_root_dirs.append(opts["log_file"]) + if should_prepend_root_dir("log_file", opts): + prepend_root_dirs.append("log_file") prepend_root_dir(opts, prepend_root_dirs) salt.features.setup_features(opts) @@ -3846,7 +3867,7 @@ def apply_minion_config( # These can be set to syslog, so, not actual paths on the system for config_key in ("log_file", "key_logfile"): - if urllib.parse.urlparse(opts.get(config_key, "")).scheme == "": + if should_prepend_root_dir(config_key, opts): prepend_root_dirs.append(config_key) prepend_root_dir(opts, prepend_root_dirs) @@ -3867,6 +3888,17 @@ def apply_minion_config( _update_ssl_config(opts) _update_discovery_config(opts) + if opts["encryption_algorithm"] not in salt.crypt.VALID_ENCRYPTION_ALGORITHMS: + raise salt.exceptions.SaltConfigurationError( + f"The encryption algorithm '{opts['encryption_algorithm']}' is not valid. " + f"Please specify one of {','.join(salt.crypt.VALID_ENCRYPTION_ALGORITHMS)}." + ) + if opts["signing_algorithm"] not in salt.crypt.VALID_SIGNING_ALGORITHMS: + raise salt.exceptions.SaltConfigurationError( + f"The signging algorithm '{opts['signing_algorithm']}' is not valid. " + f"Please specify one of {','.join(salt.crypt.VALID_SIGNING_ALGORITHMS)}." + ) + return opts @@ -4073,11 +4105,7 @@ def apply_master_config(overrides=None, defaults=None): # These can be set to syslog, so, not actual paths on the system for config_key in ("log_file", "key_logfile", "ssh_log_file"): - log_setting = opts.get(config_key, "") - if log_setting is None: - continue - - if urllib.parse.urlparse(log_setting).scheme == "": + if should_prepend_root_dir(config_key, opts): prepend_root_dirs.append(config_key) prepend_root_dir(opts, prepend_root_dirs) @@ -4152,6 +4180,12 @@ def apply_master_config(overrides=None, defaults=None): _update_ssl_config(opts) _update_discovery_config(opts) + if opts["publish_signing_algorithm"] not in salt.crypt.VALID_SIGNING_ALGORITHMS: + raise salt.exceptions.SaltConfigurationError( + f"The publish signging algorithm '{opts['publish_signing_algorithm']}' is not valid. " + f"Please specify one of {','.join(salt.crypt.VALID_SIGNING_ALGORITHMS)}." + ) + return opts @@ -4299,11 +4333,7 @@ def apply_spm_config(overrides, defaults): # These can be set to syslog, so, not actual paths on the system for config_key in ("spm_logfile",): - log_setting = opts.get(config_key, "") - if log_setting is None: - continue - - if urllib.parse.urlparse(log_setting).scheme == "": + if should_prepend_root_dir(config_key, opts): prepend_root_dirs.append(config_key) prepend_root_dir(opts, prepend_root_dirs) diff --git a/salt/crypt.py b/salt/crypt.py index ca03c5ecadc..d0a8d232a9f 100644 --- a/salt/crypt.py +++ b/salt/crypt.py @@ -43,45 +43,58 @@ from salt.exceptions import ( MasterExit, SaltClientError, SaltReqTimeoutError, + UnsupportedAlgorithm, ) try: - from M2Crypto import BIO, EVP, RSA + import cryptography.exceptions + from cryptography.hazmat.primitives import hashes, serialization + from cryptography.hazmat.primitives.asymmetric import padding, rsa + from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes - HAS_M2 = True + HAS_CRYPTOGRAPHY = True except ImportError: - HAS_M2 = False - -if not HAS_M2: - try: - from Cryptodome import Random - from Cryptodome.Cipher import AES, PKCS1_OAEP - from Cryptodome.Cipher import PKCS1_v1_5 as PKCS1_v1_5_CIPHER - from Cryptodome.Hash import SHA - from Cryptodome.PublicKey import RSA - from Cryptodome.Signature import PKCS1_v1_5 - - HAS_CRYPTO = True - except ImportError: - HAS_CRYPTO = False - -if not HAS_M2 and not HAS_CRYPTO: - try: - # let this be imported, if possible - from Crypto import Random # nosec - from Crypto.Cipher import AES, PKCS1_OAEP # nosec - from Crypto.Cipher import PKCS1_v1_5 as PKCS1_v1_5_CIPHER # nosec - from Crypto.Hash import SHA # nosec - from Crypto.PublicKey import RSA # nosec - from Crypto.Signature import PKCS1_v1_5 # nosec - - HAS_CRYPTO = True - except ImportError: - HAS_CRYPTO = False + HAS_CRYPTOGRAPHY = False log = logging.getLogger(__name__) +OAEP = "OAEP" +PKCS1v15 = "PKCS1v15" + +SHA1 = "SHA1" +SHA224 = "SHA224" + +OAEP_SHA1 = f"{OAEP}-{SHA1}" +OAEP_SHA224 = f"{OAEP}-{SHA224}" + +PKCS1v15_SHA1 = f"{PKCS1v15}-{SHA1}" +PKCS1v15_SHA224 = f"{PKCS1v15}-{SHA224}" + + +VALID_HASHES = ( + SHA1, + SHA224, +) + +VALID_PADDING_FOR_SIGNING = (PKCS1v15,) +VALID_PADDING_FOR_ENCRYPTION = (OAEP,) +VALID_ENCRYPTION_ALGORITHMS = ( + OAEP_SHA1, + OAEP_SHA224, +) +VALID_SIGNING_ALGORITHMS = ( + PKCS1v15_SHA1, + PKCS1v15_SHA224, +) + + +def fips_enabled(): + if HAS_CRYPTOGRAPHY: + import cryptography.hazmat.backends.openssl.backend + + return cryptography.hazmat.backends.openssl.backend._fips_enabled + def clean_key(key): """ @@ -128,7 +141,7 @@ def dropfile(cachedir, user=None, master_id=""): os.rename(dfn_next, dfn) -def gen_keys(keydir, keyname, keysize, user=None, passphrase=None): +def gen_keys(keydir, keyname, keysize, user=None, passphrase=None, e=65537): """ Generate a RSA public keypair for use with salt @@ -145,11 +158,8 @@ def gen_keys(keydir, keyname, keysize, user=None, passphrase=None): priv = f"{base}.pem" pub = f"{base}.pub" - if HAS_M2: - gen = RSA.gen_key(keysize, 65537, lambda: None) - else: - salt.utils.crypt.reinit_crypto() - gen = RSA.generate(bits=keysize, e=65537) + gen = rsa.generate_private_key(e, keysize) + if os.path.isfile(priv): # Between first checking and the generation another process has made # a key! Use the winner's key @@ -164,24 +174,30 @@ def gen_keys(keydir, keyname, keysize, user=None, passphrase=None): ) with salt.utils.files.set_umask(0o277): - if HAS_M2: - # if passphrase is empty or None use no cipher - if not passphrase: - gen.save_pem(priv, cipher=None) + with salt.utils.files.fopen(priv, "wb+") as f: + if passphrase: + enc = serialization.BestAvailableEncryption(passphrase.encode()) + _format = serialization.PrivateFormat.TraditionalOpenSSL + if fips_enabled(): + _format = serialization.PrivateFormat.PKCS8 else: - gen.save_pem( - priv, - cipher="des_ede3_cbc", - callback=lambda x: salt.utils.stringutils.to_bytes(passphrase), - ) - else: - with salt.utils.files.fopen(priv, "wb+") as f: - f.write(gen.exportKey("PEM", passphrase)) - if HAS_M2: - gen.save_pub_key(pub) - else: - with salt.utils.files.fopen(pub, "wb+") as f: - f.write(gen.publickey().exportKey("PEM")) + enc = serialization.NoEncryption() + _format = serialization.PrivateFormat.TraditionalOpenSSL + pem = gen.private_bytes( + encoding=serialization.Encoding.PEM, + format=_format, + encryption_algorithm=enc, + ) + f.write(pem) + + pubkey = gen.public_key() + with salt.utils.files.fopen(pub, "wb+") as f: + pem = pubkey.public_bytes( + encoding=serialization.Encoding.PEM, + format=serialization.PublicFormat.SubjectPublicKeyInfo, + ) + f.write(pem) + os.chmod(priv, 0o400) if user: try: @@ -197,81 +213,120 @@ def gen_keys(keydir, keyname, keysize, user=None, passphrase=None): return priv -class PrivateKey: +class BaseKey: + + @staticmethod + def parse_padding_for_signing(algorithm): + if algorithm not in VALID_SIGNING_ALGORITHMS: + raise UnsupportedAlgorithm(f"Invalid signing algorithm: {algorithm}") + _pad, _hash = algorithm.split("-", 1) + if _pad not in VALID_PADDING_FOR_SIGNING: + raise UnsupportedAlgorithm(f"Invalid padding algorithm: {_pad}") + return getattr(padding, _pad) + + @staticmethod + def parse_padding_for_encryption(algorithm): + if algorithm not in VALID_ENCRYPTION_ALGORITHMS: + raise UnsupportedAlgorithm(f"Invalid encryption algorithm: {algorithm}") + _pad, _hash = algorithm.split("-", 1) + if _pad not in VALID_PADDING_FOR_ENCRYPTION: + raise UnsupportedAlgorithm(f"Invalid padding algorithm: {_pad}") + return getattr(padding, _pad) + + @staticmethod + def parse_hash(algorithm): + if "-" not in algorithm: + raise UnsupportedAlgorithm(f"Invalid encryption algorithm: {algorithm}") + _pad, _hash = algorithm.split("-", 1) + if _hash not in VALID_HASHES: + raise Exception("Invalid hashing algorithm") + return getattr(hashes, _hash) + + +class PrivateKey(BaseKey): + def __init__(self, path, passphrase=None): - if HAS_M2: - self.key = RSA.load_key(path, lambda x: bytes(passphrase)) - else: - with salt.utils.files.fopen(path) as f: - self.key = RSA.importKey(f.read(), passphrase) + self.key = get_rsa_key(path, passphrase) def encrypt(self, data): - if HAS_M2: - return self.key.private_encrypt(data, salt.utils.rsax931.RSA_X931_PADDING) - else: - return salt.utils.rsax931.RSAX931Signer(self.key.exportKey("PEM")).sign( - data + pem = self.key.private_bytes( + encoding=serialization.Encoding.PEM, + format=serialization.PrivateFormat.TraditionalOpenSSL, + encryption_algorithm=serialization.NoEncryption(), + ) + return salt.utils.rsax931.RSAX931Signer(pem).sign(data) + + def sign(self, data, algorithm=PKCS1v15_SHA1): + _padding = self.parse_padding_for_signing(algorithm) + _hash = self.parse_hash(algorithm) + try: + return self.key.sign( + salt.utils.stringutils.to_bytes(data), _padding(), _hash() ) + except cryptography.exceptions.UnsupportedAlgorithm: + raise UnsupportedAlgorithm(f"Unsupported algorithm: {algorithm}") - def sign(self, data): - if HAS_M2: - md = EVP.MessageDigest("sha1") - md.update(salt.utils.stringutils.to_bytes(data)) - digest = md.final() - return self.key.sign(digest) - else: - signer = PKCS1_v1_5.new(self.key) - return signer.sign(SHA.new(salt.utils.stringutils.to_bytes(data))) + def decrypt(self, data, algorithm=OAEP_SHA1): + _padding = self.parse_padding_for_encryption(algorithm) + _hash = self.parse_hash(algorithm) + try: + return self.key.decrypt( + data, + _padding( + mgf=padding.MGF1(algorithm=_hash()), + algorithm=_hash(), + label=None, + ), + ) + except cryptography.exceptions.UnsupportedAlgorithm: + raise UnsupportedAlgorithm(f"Unsupported algorithm: {algorithm}") -class PublicKey: - def __init__(self, path, _HAS_M2=HAS_M2): - self._HAS_M2 = _HAS_M2 - if self._HAS_M2: - with salt.utils.files.fopen(path, "rb") as f: - data = f.read().replace(b"RSA ", b"") - bio = BIO.MemoryBuffer(data) +class PublicKey(BaseKey): + def __init__(self, path): + with salt.utils.files.fopen(path, "rb") as fp: try: - self.key = RSA.load_pub_key_bio(bio) - except RSA.RSAError: - raise InvalidKeyError("Encountered bad RSA public key") - else: - with salt.utils.files.fopen(path) as f: - try: - self.key = RSA.importKey(f.read()) - except (ValueError, IndexError, TypeError): - raise InvalidKeyError("Encountered bad RSA public key") + self.key = serialization.load_pem_public_key(fp.read()) + except ValueError as exc: + raise InvalidKeyError("Invalid key") - def encrypt(self, data): + def encrypt(self, data, algorithm=OAEP_SHA1): + _padding = self.parse_padding_for_encryption(algorithm) + _hash = self.parse_hash(algorithm) bdata = salt.utils.stringutils.to_bytes(data) - if self._HAS_M2: - return self.key.public_encrypt(bdata, salt.crypt.RSA.pkcs1_oaep_padding) - else: - return salt.crypt.PKCS1_OAEP.new(self.key).encrypt(bdata) - - def verify(self, data, signature): - if self._HAS_M2: - md = EVP.MessageDigest("sha1") - md.update(salt.utils.stringutils.to_bytes(data)) - digest = md.final() - try: - return self.key.verify(digest, signature) - except RSA.RSAError as exc: - log.debug("Signature verification failed: %s", exc.args[0]) - return False - else: - verifier = PKCS1_v1_5.new(self.key) - return verifier.verify( - SHA.new(salt.utils.stringutils.to_bytes(data)), signature + try: + return self.key.encrypt( + bdata, + _padding( + mgf=padding.MGF1(algorithm=_hash()), + algorithm=_hash(), + label=None, + ), ) + except cryptography.exceptions.UnsupportedAlgorithm: + raise UnsupportedAlgorithm(f"Unsupported algorithm: {algorithm}") + + def verify(self, data, signature, algorithm=PKCS1v15_SHA1): + _padding = self.parse_padding_for_signing(algorithm) + _hash = self.parse_hash(algorithm) + try: + self.key.verify( + salt.utils.stringutils.to_bytes(signature), + salt.utils.stringutils.to_bytes(data), + _padding(), + _hash(), + ) + except cryptography.exceptions.InvalidSignature: + return False + return True def decrypt(self, data): - data = salt.utils.stringutils.to_bytes(data) - if HAS_M2: - return self.key.public_decrypt(data, salt.utils.rsax931.RSA_X931_PADDING) - else: - verifier = salt.utils.rsax931.RSAX931Verifier(self.key.exportKey("PEM")) - return verifier.verify(data) + pem = self.key.public_bytes( + encoding=serialization.Encoding.PEM, + format=serialization.PublicFormat.SubjectPublicKeyInfo, + ) + verifier = salt.utils.rsax931.RSAX931Verifier(pem) + return verifier.verify(data) @salt.utils.decorators.memoize @@ -284,12 +339,15 @@ def _get_key_with_evict(path, timestamp, passphrase): modified then the params are different and the key is loaded from disk. """ log.debug("salt.crypt._get_key_with_evict: Loading private key") - if HAS_M2: - key = RSA.load_key(path, lambda x: bytes(passphrase)) + if passphrase: + password = passphrase.encode() else: - with salt.utils.files.fopen(path) as f: - key = RSA.importKey(f.read(), passphrase) - return key + password = None + with salt.utils.files.fopen(path, "rb") as f: + return serialization.load_pem_private_key( + f.read(), + password=password, + ) def get_rsa_key(path, passphrase): @@ -312,61 +370,29 @@ def get_rsa_pub_key(path): Read a public key off the disk. """ log.debug("salt.crypt.get_rsa_pub_key: Loading public key") - if HAS_M2: - with salt.utils.files.fopen(path, "rb") as f: - data = f.read().replace(b"RSA ", b"") - bio = BIO.MemoryBuffer(data) - try: - key = RSA.load_pub_key_bio(bio) - except RSA.RSAError: - raise InvalidKeyError("Encountered bad RSA public key") - else: - with salt.utils.files.fopen(path) as f: - try: - key = RSA.importKey(f.read()) - except (ValueError, IndexError, TypeError): - raise InvalidKeyError("Encountered bad RSA public key") - return key + try: + with salt.utils.files.fopen(path, "rb") as fp: + return serialization.load_pem_public_key(fp.read()) + except ValueError: + raise InvalidKeyError("Encountered bad RSA public key") + except cryptography.exceptions.UnsupportedAlgorithm: + raise InvalidKeyError("Unsupported key algorithm") -def sign_message(privkey_path, message, passphrase=None): +def sign_message(privkey_path, message, passphrase=None, algorithm=PKCS1v15_SHA1): """ Use Crypto.Signature.PKCS1_v1_5 to sign a message. Returns the signature. """ - key = get_rsa_key(privkey_path, passphrase) - log.debug("salt.crypt.sign_message: Signing message.") - if HAS_M2: - md = EVP.MessageDigest("sha1") - md.update(salt.utils.stringutils.to_bytes(message)) - digest = md.final() - return key.sign(digest) - else: - signer = PKCS1_v1_5.new(key) - return signer.sign(SHA.new(salt.utils.stringutils.to_bytes(message))) + return PrivateKey(privkey_path, passphrase).sign(message, algorithm) -def verify_signature(pubkey_path, message, signature): +def verify_signature(pubkey_path, message, signature, algorithm=PKCS1v15_SHA1): """ Use Crypto.Signature.PKCS1_v1_5 to verify the signature on a message. Returns True for valid signature. """ log.debug("salt.crypt.verify_signature: Loading public key") - pubkey = get_rsa_pub_key(pubkey_path) - log.debug("salt.crypt.verify_signature: Verifying signature") - if HAS_M2: - md = EVP.MessageDigest("sha1") - md.update(salt.utils.stringutils.to_bytes(message)) - digest = md.final() - try: - return pubkey.verify(digest, signature) - except RSA.RSAError as exc: - log.debug("Signature verification failed: %s", exc.args[0]) - return False - else: - verifier = PKCS1_v1_5.new(pubkey) - return verifier.verify( - SHA.new(salt.utils.stringutils.to_bytes(message)), signature - ) + return PublicKey(pubkey_path).verify(message, signature, algorithm) def gen_signature(priv_path, pub_path, sign_path, passphrase=None): @@ -400,49 +426,12 @@ def gen_signature(priv_path, pub_path, sign_path, passphrase=None): return True -def private_encrypt(key, message): - """ - Generate an M2Crypto-compatible signature - - :param Crypto.PublicKey.RSA._RSAobj key: The RSA key object - :param str message: The message to sign - :rtype: str - :return: The signature, or an empty string if the signature operation failed - """ - if HAS_M2: - return key.private_encrypt(message, salt.utils.rsax931.RSA_X931_PADDING) - else: - signer = salt.utils.rsax931.RSAX931Signer(key.exportKey("PEM")) - return signer.sign(message) - - -def public_decrypt(pub, message): - """ - Verify an M2Crypto-compatible signature - - :param Crypto.PublicKey.RSA._RSAobj key: The RSA public key object - :param str message: The signed message to verify - :rtype: str - :return: The message (or digest) recovered from the signature, or an - empty string if the verification failed - """ - if HAS_M2: - return pub.public_decrypt(message, salt.utils.rsax931.RSA_X931_PADDING) - else: - verifier = salt.utils.rsax931.RSAX931Verifier(pub.exportKey("PEM")) - return verifier.verify(message) - - def pwdata_decrypt(rsa_key, pwdata): - if HAS_M2: - key = RSA.load_key_string(salt.utils.stringutils.to_bytes(rsa_key, "ascii")) - password = key.private_decrypt(pwdata, RSA.pkcs1_padding) - else: - dsize = SHA.digest_size - sentinel = Random.new().read(15 + dsize) - key_obj = RSA.importKey(rsa_key) - key_obj = PKCS1_v1_5_CIPHER.new(key_obj) - password = key_obj.decrypt(pwdata, sentinel) + key = serialization.load_pem_private_key(rsa_key.encode(), password=None) + password = key.decrypt( + pwdata, + padding.PKCS1v15(), + ) return salt.utils.stringutils.to_unicode(password) @@ -580,18 +569,21 @@ class MasterKeys(dict): self.opts.get("user"), passphrase, ) - if HAS_M2: - key_error = RSA.RSAError - else: - key_error = ValueError try: - key = get_rsa_key(path, passphrase) - except key_error as e: + key = PrivateKey(path, passphrase) + except ValueError as e: + message = f"Unable to read key: {path}; file may be corrupt" + except TypeError as e: message = f"Unable to read key: {path}; passphrase may be incorrect" - log.error(message) - raise MasterExit(message) - log.debug("Loaded %s key: %s", name, path) - return key + except InvalidKeyError as e: + message = f"Unable to read key: {path}; key contains unsupported algorithm" + except cryptography.exceptions.UnsupportedAlgorithm as e: + message = f"Unable to read key: {path}; key contains unsupported algorithm" + else: + log.debug("Loaded %s key: %s", name, path) + return key + log.error(message) + raise MasterExit(message) def get_pub_str(self, name="master"): """ @@ -607,12 +599,14 @@ class MasterKeys(dict): # if not os.path.isfile(path): # raise RuntimeError(f"The key {path} does not exist.") if not os.path.isfile(path): - key = self.__get_keys() - if HAS_M2: - key.save_pub_key(path) - else: - with salt.utils.files.fopen(path, "wb+") as wfh: - wfh.write(key.publickey().exportKey("PEM")) + pubkey = self.key.public_key() + with salt.utils.files.fopen(path, "wb+") as f: + f.write( + pubkey.public_bytes( + encoding=serialization.Encoding.PEM, + format=serialization.PublicFormat.SubjectPublicKeyInfo, + ) + ) with salt.utils.files.fopen(path) as rfh: return clean_key(rfh.read()) @@ -654,13 +648,6 @@ class MasterKeys(dict): log.debug("Writing shared key %s", shared_pub) shared_pub.write_bytes(master_pub.read_bytes()) - def master_private_decrypt(self, data): - if HAS_M2: - return self.master_key.private_decrypt(data, RSA.pkcs1_oaep_padding) - else: - cipher = PKCS1_OAEP.new(self.master_key) - return cipher.decrypt(data) - class AsyncAuth: """ @@ -729,10 +716,7 @@ class AsyncAuth: self.mpub = "minion_master.pub" if not os.path.isfile(self.pub_path): self.get_keys() - self.io_loop = io_loop or tornado.ioloop.IOLoop.current() - - salt.utils.crypt.reinit_crypto() key = self.__key(self.opts) # TODO: if we already have creds for this key, lets just re-use if key in AsyncAuth.creds_map: @@ -864,6 +848,18 @@ class AsyncAuth: "Authentication wait time is %s", acceptance_wait_time ) continue + elif creds == "bad enc algo": + log.error( + "This minion is using a encryption algorithm that is " + "not supported by it's Master. Please check your minion configutation." + ) + break + elif creds == "bad sig algo": + log.error( + "This minion is using a signing algorithm that is " + "not supported by it's Master. Please check your minion configutation." + ) + break break if not isinstance(creds, dict) or "aes" not in creds: if self.opts.get("detect_mode") is True: @@ -965,6 +961,13 @@ class AsyncAuth: if not isinstance(payload, dict) or "load" not in payload: log.error("Sign-in attempt failed: %s", payload) return False + elif isinstance(payload["load"], dict) and "ret" in payload["load"]: + if payload["load"]["ret"] == "bad enc algo": + log.error("Sign-in attempt failed: %s", payload) + return "bad enc algo" + elif payload["load"]["ret"] == "bad sig algo": + log.error("Sign-in attempt failed: %s", payload) + return "bad sig algo" clear_signed_data = payload["load"] clear_signature = payload["sig"] @@ -991,7 +994,11 @@ class AsyncAuth: master_pubkey_path = os.path.join(self.opts["pki_dir"], self.mpub) if os.path.exists(master_pubkey_path) and not PublicKey( master_pubkey_path - ).verify(clear_signed_data, clear_signature): + ).verify( + clear_signed_data, + clear_signature, + algorithm=self.opts["signing_algorithm"], + ): log.critical("The payload signature did not validate.") raise SaltClientError("Invalid signature") @@ -1078,7 +1085,7 @@ class AsyncAuth: self.opts["keysize"], self.opts.get("user"), ) - key = get_rsa_key(self.rsa_path, None) + key = PrivateKey(self.rsa_path, None) log.debug("Loaded minion key: %s", self.rsa_path) return key @@ -1091,7 +1098,7 @@ class AsyncAuth: :return: Encrypted token :rtype: str """ - return private_encrypt(self.get_keys(), clear_tok) + return self.get_keys().encrypt(clear_tok) def minion_sign_in_payload(self): """ @@ -1106,6 +1113,8 @@ class AsyncAuth: payload["cmd"] = "_auth" payload["id"] = self.opts["id"] payload["nonce"] = uuid.uuid4().hex + payload["enc_algo"] = self.opts["encryption_algorithm"] + payload["sig_algo"] = self.opts["signing_algorithm"] if "autosign_grains" in self.opts: autosign_grains = {} for grain in self.opts["autosign_grains"]: @@ -1113,16 +1122,14 @@ class AsyncAuth: payload["autosign_grains"] = autosign_grains try: pubkey_path = os.path.join(self.opts["pki_dir"], self.mpub) - pub = get_rsa_pub_key(pubkey_path) - if HAS_M2: - payload["token"] = pub.public_encrypt( - self.token, RSA.pkcs1_oaep_padding - ) - else: - cipher = PKCS1_OAEP.new(pub) - payload["token"] = cipher.encrypt(self.token) - except Exception: # pylint: disable=broad-except - pass + pub = PublicKey(pubkey_path) + payload["token"] = pub.encrypt( + self.token, self.opts["encryption_algorithm"] + ) + except FileNotFoundError: + log.debug("Master public key not found") + except Exception as exc: # pylint: disable=broad-except + log.debug("Exception while encrypting token %s", exc) with salt.utils.files.fopen(self.pub_path) as f: payload["pub"] = clean_key(f.read()) return payload @@ -1154,25 +1161,19 @@ class AsyncAuth: log.warning("Auth Called: %s", "".join(traceback.format_stack())) else: log.debug("Decrypting the current master AES key") + key = self.get_keys() - if HAS_M2: - key_str = key.private_decrypt(payload["aes"], RSA.pkcs1_oaep_padding) - else: - cipher = PKCS1_OAEP.new(key) - key_str = cipher.decrypt(payload["aes"]) + key_str = key.decrypt(payload["aes"], self.opts["encryption_algorithm"]) if "sig" in payload: m_path = os.path.join(self.opts["pki_dir"], self.mpub) if os.path.exists(m_path): try: - mkey = get_rsa_pub_key(m_path) + mkey = PublicKey(m_path) except Exception: # pylint: disable=broad-except return "", "" digest = hashlib.sha256(key_str).hexdigest() digest = salt.utils.stringutils.to_bytes(digest) - if HAS_M2: - m_digest = public_decrypt(mkey, payload["sig"]) - else: - m_digest = public_decrypt(mkey.publickey(), payload["sig"]) + m_digest = mkey.decrypt(payload["sig"]) if m_digest != digest: return "", "" else: @@ -1184,12 +1185,7 @@ class AsyncAuth: return key_str.split("_|-") else: if "token" in payload: - if HAS_M2: - token = key.private_decrypt( - payload["token"], RSA.pkcs1_oaep_padding - ) - else: - token = cipher.decrypt(payload["token"]) + token = key.decrypt(payload["token"], self.opts["encryption_algorithm"]) return key_str, token elif not master_pub: return key_str, "" @@ -1209,7 +1205,12 @@ class AsyncAuth: ) if os.path.isfile(path): - res = verify_signature(path, message, binascii.a2b_base64(sig)) + res = verify_signature( + path, + message, + binascii.a2b_base64(sig), + algorithm=self.opts["signing_algorithm"], + ) else: log.error( "Verification public key %s does not exist. You need to " @@ -1657,15 +1658,10 @@ class Crypticle: pad = self.AES_BLOCK_SIZE - len(data) % self.AES_BLOCK_SIZE data = data + salt.utils.stringutils.to_bytes(pad * chr(pad)) iv_bytes = os.urandom(self.AES_BLOCK_SIZE) - if HAS_M2: - cypher = EVP.Cipher( - alg="aes_192_cbc", key=aes_key, iv=iv_bytes, op=1, padding=False - ) - encr = cypher.update(data) - encr += cypher.final() - else: - cypher = AES.new(aes_key, AES.MODE_CBC, iv_bytes) - encr = cypher.encrypt(data) + cipher = Cipher(algorithms.AES(aes_key), modes.CBC(iv_bytes)) + encryptor = cipher.encryptor() + encr = encryptor.update(data) + encr += encryptor.finalize() data = iv_bytes + encr sig = hmac.new(hmac_key, data, hashlib.sha256).digest() return data + sig @@ -1684,7 +1680,6 @@ class Crypticle: log.debug("Failed to authenticate message") raise AuthenticationError("message authentication failed") result = 0 - for zipped_x, zipped_y in zip(mac_bytes, sig): result |= zipped_x ^ zipped_y if result != 0: @@ -1692,15 +1687,9 @@ class Crypticle: raise AuthenticationError("message authentication failed") iv_bytes = data[: self.AES_BLOCK_SIZE] data = data[self.AES_BLOCK_SIZE :] - if HAS_M2: - cypher = EVP.Cipher( - alg="aes_192_cbc", key=aes_key, iv=iv_bytes, op=0, padding=False - ) - encr = cypher.update(data) - data = encr + cypher.final() - else: - cypher = AES.new(aes_key, AES.MODE_CBC, iv_bytes) - data = cypher.decrypt(data) + cipher = Cipher(algorithms.AES(aes_key), modes.CBC(iv_bytes)) + decryptor = cipher.decryptor() + data = decryptor.update(data) + decryptor.finalize() return data[: -data[-1]] def dumps(self, obj, nonce=None): diff --git a/salt/exceptions.py b/salt/exceptions.py index 57a6175de2d..9dd9175281a 100644 --- a/salt/exceptions.py +++ b/salt/exceptions.py @@ -362,6 +362,12 @@ class AuthorizationError(SaltException): """ +class UnsupportedAlgorithm(SaltException): + """ + Thrown when a requested encryption or signing algorithm is un-supported. + """ + + class SaltDaemonNotRunning(SaltException): """ Throw when a running master/minion/syndic is not running but is needed to diff --git a/salt/grains/core.py b/salt/grains/core.py index 529c3dff2f8..7afcbd5cbae 100644 --- a/salt/grains/core.py +++ b/salt/grains/core.py @@ -40,6 +40,7 @@ import salt.utils.pkg.rpm import salt.utils.platform import salt.utils.stringutils from salt.utils.network import _clear_interfaces, _get_interfaces +from salt.utils.platform import get_machine_identifier as _get_machine_identifier from salt.utils.platform import linux_distribution as _linux_distribution try: @@ -3051,13 +3052,7 @@ def get_machine_id(): if platform.system() == "AIX": return _aix_get_machine_id() - locations = ["/etc/machine-id", "/var/lib/dbus/machine-id"] - existing_locations = [loc for loc in locations if os.path.exists(loc)] - if not existing_locations: - return {} - else: - with salt.utils.files.fopen(existing_locations[0]) as machineid: - return {"machine_id": machineid.read().strip()} + return _get_machine_identifier() def cwd(): diff --git a/salt/loader/lazy.py b/salt/loader/lazy.py index 6118b62bf91..98fd741232a 100644 --- a/salt/loader/lazy.py +++ b/salt/loader/lazy.py @@ -1281,7 +1281,10 @@ class LazyLoader(salt.utils.lazy.LazyDict): self.parent_loader = current_loader token = salt.loader.context.loader_ctxvar.set(self) try: - return _func_or_method(*args, **kwargs) + ret = _func_or_method(*args, **kwargs) + if isinstance(ret, salt.loader.context.NamedLoaderContext): + ret = ret.value() + return ret finally: self.parent_loader = None salt.loader.context.loader_ctxvar.reset(token) diff --git a/salt/master.py b/salt/master.py index 55b19e040d5..9c80fd24284 100644 --- a/salt/master.py +++ b/salt/master.py @@ -38,7 +38,6 @@ import salt.serializers.msgpack import salt.state import salt.utils.args import salt.utils.atomicfile -import salt.utils.crypt import salt.utils.ctx import salt.utils.event import salt.utils.files @@ -1311,7 +1310,6 @@ class MWorker(salt.utils.process.SignalHandlingProcess): ) self.clear_funcs.connect() self.aes_funcs = AESFuncs(self.opts) - salt.utils.crypt.reinit_crypto() self.__bind() @@ -1433,7 +1431,7 @@ class AESFuncs(TransportMethods): return False pub_path = os.path.join(self.pki_dir, "minions", id_) try: - pub = salt.crypt.get_rsa_pub_key(pub_path) + pub = salt.crypt.PublicKey(pub_path) except OSError: log.warning( "Salt minion claiming to be %s attempted to communicate with " @@ -1444,7 +1442,7 @@ class AESFuncs(TransportMethods): except (ValueError, IndexError, TypeError) as err: log.error('Unable to load public key "%s": %s', pub_path, err) try: - if salt.crypt.public_decrypt(pub, token) == b"salt": + if pub.decrypt(token) == b"salt": return True except ValueError as err: log.error("Unable to decrypt token: %s", err) diff --git a/salt/minion.py b/salt/minion.py index b4ad9d6956d..d2cf7c7fb96 100644 --- a/salt/minion.py +++ b/salt/minion.py @@ -40,7 +40,6 @@ import salt.syspaths import salt.transport import salt.utils.args import salt.utils.context -import salt.utils.crypt import salt.utils.ctx import salt.utils.data import salt.utils.dictdiffer @@ -1808,7 +1807,6 @@ class Minion(MinionBase): name=name, args=(instance, self.opts, data, self.connected, creds_map), ) - process.register_after_fork_method(salt.utils.crypt.reinit_crypto) else: process = threading.Thread( target=self._target, diff --git a/salt/modules/win_task.py b/salt/modules/win_task.py index 2b23e381bc0..e763e420fcb 100644 --- a/salt/modules/win_task.py +++ b/salt/modules/win_task.py @@ -182,6 +182,15 @@ def __virtual__(): return False, "Module win_task: module only works on Windows systems" +def _signed_to_unsigned_int32(code): + """ + Convert negative result and error codes from win32com + """ + if code < 0: + code = code + 2**32 + return code + + def _get_date_time_format(dt_string): """ Copied from win_system.py (_get_date_time_format) @@ -253,6 +262,8 @@ def _reverse_lookup(dictionary, value): value_index = idx break + if value_index < 0: + return "invalid value" return list(dictionary)[value_index] @@ -311,19 +322,20 @@ def _save_task_definition( except pythoncom.com_error as error: hr, msg, exc, arg = error.args # pylint: disable=W0633 + error_code = _signed_to_unsigned_int32(exc[5]) fc = { - -2147024773: ( + 0x8007007B: ( "The filename, directory name, or volume label syntax is incorrect" ), - -2147024894: "The system cannot find the file specified", - -2147216615: "Required element or attribute missing", - -2147216616: "Value incorrectly formatted or out of range", - -2147352571: "Access denied", + 0x80070002: "The system cannot find the file specified", + 0x80041319: "Required element or attribute missing", + 0x80041318: "Value incorrectly formatted or out of range", + 0x80020005: "Access denied", } try: - failure_code = fc[exc[5]] + failure_code = fc[error_code] except KeyError: - failure_code = f"Unknown Failure: {error}" + failure_code = f"Unknown Failure: {hex(error_code)}" log.debug("Failed to modify task: %s", failure_code) @@ -683,7 +695,7 @@ def create_task_from_xml( except pythoncom.com_error as error: hr, msg, exc, arg = error.args # pylint: disable=W0633 - error_code = hex(exc[5] + 2**32) + error_code = _signed_to_unsigned_int32(exc[5]) fc = { 0x80041319: "Required element or attribute missing", 0x80041318: "Value incorrectly formatted or out of range", @@ -731,7 +743,7 @@ def create_task_from_xml( try: failure_code = fc[error_code] except KeyError: - failure_code = f"Unknown Failure: {error_code}" + failure_code = f"Unknown Failure: {hex(error_code)}" finally: log.debug("Failed to create task: %s", failure_code) raise CommandExecutionError(failure_code) @@ -1469,10 +1481,16 @@ def info(name, location="\\"): task_folder = task_service.GetFolder(location) task = task_folder.GetTask(name) + last_task_result_code = _signed_to_unsigned_int32(task.LastTaskResult) + try: + last_task_result = results[last_task_result_code] + except KeyError: + last_task_result = f"Unknown Task Result: {hex(last_task_result_code)}" + properties = { "enabled": task.Enabled, "last_run": _get_date_value(task.LastRunTime), - "last_run_result": results[task.LastTaskResult], + "last_run_result": last_task_result, "missed_runs": task.NumberOfMissedRuns, "next_run": _get_date_value(task.NextRunTime), "status": states[task.State], @@ -1494,7 +1512,7 @@ def info(name, location="\\"): duration, def_set.DeleteExpiredTaskAfter ) - if def_set.ExecutionTimeLimit == "": + if def_set.ExecutionTimeLimit == "" or def_set.ExecutionTimeLimit == "PT0S": settings["execution_time_limit"] = False else: settings["execution_time_limit"] = _reverse_lookup( diff --git a/salt/state.py b/salt/state.py index b5006807ead..f8821c49809 100644 --- a/salt/state.py +++ b/salt/state.py @@ -976,8 +976,9 @@ class State: self.state_con["loader_cache"][agg_fun] = True try: low["__agg__"] = True - low = self._aggregate_requisites(low, chunks) + # Aggregate the states *before* aggregating requisites otherwise there will never be requisites to aggregate low = self.states[agg_fun](low, chunks, running) + low = self._aggregate_requisites(low, chunks) except TypeError: log.error("Failed to execute aggregate for state %s", low["state"]) else: diff --git a/salt/states/virtualenv_mod.py b/salt/states/virtualenv_mod.py index 957f44265bc..7dadfa23fd5 100644 --- a/salt/states/virtualenv_mod.py +++ b/salt/states/virtualenv_mod.py @@ -77,7 +77,9 @@ def managed( Prefer wheel archives (requires pip >= 1.4). python: None - Python executable used to build the virtualenv + Python executable used to build the virtualenv. When Salt is installed + from a onedir package. You will likely want to specify which python + interperter should be used. user: None The user under which to run virtualenv and pip. @@ -131,6 +133,12 @@ def managed( - requirements: salt://REQUIREMENTS.txt - env_vars: PATH_VAR: '/usr/local/bin/' + + Current versions of Salt use onedir packages and will use onedir python + interpreter by default. If you've installed Salt via out package + repository. You will likely want to provide the path to the interpreter + with wich you would like to be used to create the virtual envrionment. The + interperter can be specified by providing the `python` option. """ ret = {"name": name, "result": True, "comment": "", "changes": {}} diff --git a/salt/states/x509_v2.py b/salt/states/x509_v2.py index 379fa3debe8..3fcc9bffad6 100644 --- a/salt/states/x509_v2.py +++ b/salt/states/x509_v2.py @@ -1602,10 +1602,12 @@ def _build_cert( ca_server=None, signing_policy=None, signing_private_key=None, **kwargs ): final_kwargs = copy.deepcopy(kwargs) + final_kwargs["signing_private_key"] = signing_private_key x509util.merge_signing_policy( __salt__["x509.get_signing_policy"](signing_policy, ca_server=ca_server), final_kwargs, ) + signing_private_key = final_kwargs.pop("signing_private_key") builder, _, private_key_loaded, signing_cert = x509util.build_crt( signing_private_key, diff --git a/salt/transport/zeromq.py b/salt/transport/zeromq.py index 8bc4d0dff26..478057232fa 100644 --- a/salt/transport/zeromq.py +++ b/salt/transport/zeromq.py @@ -956,7 +956,10 @@ class PublishServer(salt.transport.base.DaemonizedPublishServer): await publish_payload(package) except Exception as exc: # pylint: disable=broad-except log.error( - "Exception in publisher %s %s", self.pull_uri, exc, exc_info=True + "Exception in publisher %s %s", + self.pull_uri, + exc, + exc_info_on_loglevel=logging.DEBUG, ) async def publish_payload(self, payload, topic_list=None): diff --git a/salt/utils/crypt.py b/salt/utils/crypt.py index 044eebe7a77..5505c0eacf0 100644 --- a/salt/utils/crypt.py +++ b/salt/utils/crypt.py @@ -12,35 +12,6 @@ from salt.exceptions import SaltInvocationError log = logging.getLogger(__name__) -try: - import M2Crypto # pylint: disable=unused-import - - Random = None - HAS_M2CRYPTO = True -except ImportError: - HAS_M2CRYPTO = False - -if not HAS_M2CRYPTO: - try: - from Cryptodome import Random - - HAS_CRYPTODOME = True - except ImportError: - HAS_CRYPTODOME = False -else: - HAS_CRYPTODOME = False - -if not HAS_M2CRYPTO and not HAS_CRYPTODOME: - try: - from Crypto import Random # nosec - - HAS_CRYPTO = True - except ImportError: - HAS_CRYPTO = False -else: - HAS_CRYPTO = False - - def decrypt( data, rend, translate_newlines=False, renderers=None, opts=None, valid_rend=None ): @@ -117,20 +88,6 @@ def decrypt( return rend_func(data, translate_newlines=translate_newlines) -def reinit_crypto(): - """ - When a fork arises, pycrypto needs to reinit - From its doc:: - - Caveat: For the random number generator to work correctly, - you must call Random.atfork() in both the parent and - child processes after using os.fork() - - """ - if HAS_CRYPTODOME or HAS_CRYPTO: - Random.atfork() - - def pem_finger(path=None, key=None, sum_type="sha256"): """ Pass in either a raw pem string, or the path on disk to the location of a diff --git a/salt/utils/files.py b/salt/utils/files.py index e5494911c28..acfe70f41a5 100644 --- a/salt/utils/files.py +++ b/salt/utils/files.py @@ -381,7 +381,8 @@ def fopen(*args, **kwargs): # Workaround callers with bad buffering setting for binary files if kwargs.get("buffering") == 1 and "b" in kwargs.get("mode", ""): log.debug( - "Line buffering (buffering=1) isn't supported in binary mode, the default buffer size will be used" + "Line buffering (buffering=1) isn't supported in binary mode, " + "the default buffer size will be used" ) kwargs["buffering"] = io.DEFAULT_BUFFER_SIZE diff --git a/salt/utils/gitfs.py b/salt/utils/gitfs.py index a047c60d33d..9feda7a1677 100644 --- a/salt/utils/gitfs.py +++ b/salt/utils/gitfs.py @@ -13,6 +13,7 @@ import io import logging import multiprocessing import os +import pathlib import shlex import shutil import stat @@ -33,6 +34,7 @@ import salt.utils.hashutils import salt.utils.itertools import salt.utils.path import salt.utils.platform +import salt.utils.process import salt.utils.stringutils import salt.utils.url import salt.utils.user @@ -42,7 +44,7 @@ from salt.config import DEFAULT_MASTER_OPTS as _DEFAULT_MASTER_OPTS from salt.exceptions import FileserverConfigError, GitLockError, get_error_message from salt.utils.event import tagify from salt.utils.odict import OrderedDict -from salt.utils.process import os_is_running as pid_exists +from salt.utils.platform import get_machine_identifier as _get_machine_identifier from salt.utils.versions import Version VALID_REF_TYPES = _DEFAULT_MASTER_OPTS["gitfs_ref_types"] @@ -82,6 +84,14 @@ _INVALID_REPO = ( log = logging.getLogger(__name__) +HAS_PSUTIL = False +try: + import psutil + + HAS_PSUTIL = True +except ImportError: + pass + # pylint: disable=import-error try: if ( @@ -249,6 +259,11 @@ class GitProvider: def _val_cb(x, y): return str(y) + # get machine_identifier + self.mach_id = _get_machine_identifier().get( + "machine_id", "no_machine_id_available" + ) + self.global_saltenv = salt.utils.data.repack_dictlist( self.opts.get(f"{self.role}_saltenv", []), strict=True, @@ -511,6 +526,17 @@ class GitProvider: os.makedirs(self._salt_working_dir) self.fetch_request_check() + if HAS_PSUTIL: + cur_pid = os.getpid() + process = psutil.Process(cur_pid) + dgm_process_dir = dir(process) + cache_dir = self.opts.get("cachedir", None) + gitfs_active = self.opts.get("gitfs_remotes", None) + if cache_dir and gitfs_active: + salt.utils.process.register_cleanup_finalize_function( + gitfs_finalize_cleanup, cache_dir + ) + def get_cache_basehash(self): return self._cache_basehash @@ -752,7 +778,12 @@ class GitProvider: except OSError as exc: if exc.errno == errno.ENOENT: # No lock file present - pass + msg = ( + f"Attempt to remove lock {self.url} for file ({lock_file}) " + f"which does not exist, exception : {exc} " + ) + log.debug(msg) + elif exc.errno == errno.EISDIR: # Somehow this path is a directory. Should never happen # unless some wiseguy manually creates a directory at this @@ -764,8 +795,9 @@ class GitProvider: else: _add_error(failed, exc) else: - msg = "Removed {} lock for {} remote '{}'".format( - lock_type, self.role, self.id + msg = ( + f"Removed {lock_type} lock for {self.role} remote '{self.id}' " + f"on machine_id '{self.mach_id}'" ) log.debug(msg) success.append(msg) @@ -904,7 +936,19 @@ class GitProvider: self._get_lock_file(lock_type="update"), self.role, ) + else: + log.warning( + "Update lock file generated an unexpected exception for %s remote '%s', " + "The lock file %s for %s type=update operation, exception: %s .", + self.role, + self.id, + self._get_lock_file(lock_type="update"), + self.role, + str(exc), + ) return False + except NotImplementedError as exc: + log.warning("fetch got NotImplementedError exception %s", exc) def _lock(self, lock_type="update", failhard=False): """ @@ -930,7 +974,11 @@ class GitProvider: ) with os.fdopen(fh_, "wb"): # Write the lock file and close the filehandle - os.write(fh_, salt.utils.stringutils.to_bytes(str(os.getpid()))) + os.write( + fh_, + salt.utils.stringutils.to_bytes(f"{os.getpid()}\n{self.mach_id}\n"), + ) + except OSError as exc: if exc.errno == errno.EEXIST: with salt.utils.files.fopen(self._get_lock_file(lock_type), "r") as fd_: @@ -942,40 +990,66 @@ class GitProvider: # Lock file is empty, set pid to 0 so it evaluates as # False. pid = 0 + try: + mach_id = salt.utils.stringutils.to_unicode( + fd_.readline() + ).rstrip() + except ValueError as exc: + # Lock file is empty, set machine id to 0 so it evaluates as + # False. + mach_id = 0 + global_lock_key = self.role + "_global_lock" lock_file = self._get_lock_file(lock_type=lock_type) if self.opts[global_lock_key]: msg = ( - "{} is enabled and {} lockfile {} is present for " - "{} remote '{}'.".format( - global_lock_key, - lock_type, - lock_file, - self.role, - self.id, - ) + f"{global_lock_key} is enabled and {lock_type} lockfile {lock_file} " + f"is present for {self.role} remote '{self.id}' on machine_id " + f"{self.mach_id} with pid '{pid}'." ) if pid: msg += f" Process {pid} obtained the lock" - if not pid_exists(pid): - msg += ( - " but this process is not running. The " - "update may have been interrupted. If " - "using multi-master with shared gitfs " - "cache, the lock may have been obtained " - "by another master." - ) + if self.mach_id or mach_id: + msg += f" for machine_id {mach_id}, current machine_id {self.mach_id}" + + if not salt.utils.process.os_is_running(pid): + if self.mach_id != mach_id: + msg += ( + " but this process is not running. The " + "update may have been interrupted. If " + "using multi-master with shared gitfs " + "cache, the lock may have been obtained " + f"by another master, with machine_id {mach_id}" + ) + else: + msg += ( + " but this process is not running. The " + "update may have been interrupted. " + " Given this process is for the same machine" + " the lock will be reallocated to new process " + ) + log.warning(msg) + success, fail = self._clear_lock() + if success: + return self.__lock( + lock_type="update", failhard=failhard + ) + elif failhard: + raise + return + log.warning(msg) if failhard: raise return - elif pid and pid_exists(pid): + elif pid and salt.utils.process.os_is_running(pid): log.warning( - "Process %d has a %s %s lock (%s)", + "Process %d has a %s %s lock (%s) on machine_id %s", pid, self.role, lock_type, lock_file, + self.mach_id, ) if failhard: raise @@ -983,12 +1057,13 @@ class GitProvider: else: if pid: log.warning( - "Process %d has a %s %s lock (%s), but this " + "Process %d has a %s %s lock (%s) on machine_id %s, but this " "process is not running. Cleaning up lock file.", pid, self.role, lock_type, lock_file, + self.mach_id, ) success, fail = self._clear_lock() if success: @@ -997,12 +1072,14 @@ class GitProvider: raise return else: - msg = "Unable to set {} lock for {} ({}): {} ".format( - lock_type, self.id, self._get_lock_file(lock_type), exc + msg = ( + f"Unable to set {lock_type} lock for {self.id} " + f"({self._get_lock_file(lock_type)}) on machine_id {self.mach_id}: {exc}" ) log.error(msg, exc_info=True) raise GitLockError(exc.errno, msg) - msg = f"Set {lock_type} lock for {self.role} remote '{self.id}'" + + msg = f"Set {lock_type} lock for {self.role} remote '{self.id}' on machine_id '{self.mach_id}'" log.debug(msg) return msg @@ -1019,6 +1096,15 @@ class GitProvider: try: result = self._lock(lock_type="update") except GitLockError as exc: + log.warning( + "Update lock file generated an unexpected exception for %s remote '%s', " + "The lock file %s for %s type=update operation, exception: %s .", + self.role, + self.id, + self._get_lock_file(lock_type="update"), + self.role, + str(exc), + ) failed.append(exc.strerror) else: if result is not None: @@ -1028,7 +1114,8 @@ class GitProvider: @contextlib.contextmanager def gen_lock(self, lock_type="update", timeout=0, poll_interval=0.5): """ - Set and automatically clear a lock + Set and automatically clear a lock, + should be called from a context, for example: with self.gen_lock() """ if not isinstance(lock_type, str): raise GitLockError(errno.EINVAL, f"Invalid lock_type '{lock_type}'") @@ -1049,17 +1136,23 @@ class GitProvider: if poll_interval > timeout: poll_interval = timeout - lock_set = False + lock_set1 = False + lock_set2 = False try: time_start = time.time() while True: try: self._lock(lock_type=lock_type, failhard=True) - lock_set = True - yield + lock_set1 = True + # docs state need to yield a single value, lock_set will do + yield lock_set1 + # Break out of his loop once we've yielded the lock, to # avoid continued attempts to iterate and establish lock + # just ensuring lock_set is true (belts and braces) + lock_set2 = True break + except (OSError, GitLockError) as exc: if not timeout or time.time() - time_start > timeout: raise GitLockError(exc.errno, exc.strerror) @@ -1075,7 +1168,13 @@ class GitProvider: time.sleep(poll_interval) continue finally: - if lock_set: + if lock_set1 or lock_set2: + msg = ( + f"Attempting to remove '{lock_type}' lock for " + f"'{self.role}' remote '{self.id}' due to lock_set1 " + f"'{lock_set1}' or lock_set2 '{lock_set2}'" + ) + log.debug(msg) self.clear_lock(lock_type=lock_type) def init_remote(self): @@ -1365,9 +1464,7 @@ class GitPython(GitProvider): # function. raise GitLockError( exc.errno, - "Checkout lock exists for {} remote '{}'".format( - self.role, self.id - ), + f"Checkout lock exists for {self.role} remote '{self.id}'", ) else: log.error( @@ -1716,9 +1813,7 @@ class Pygit2(GitProvider): # function. raise GitLockError( exc.errno, - "Checkout lock exists for {} remote '{}'".format( - self.role, self.id - ), + f"Checkout lock exists for {self.role} remote '{self.id}'", ) else: log.error( @@ -2233,10 +2328,8 @@ class Pygit2(GitProvider): if not self.ssl_verify: warnings.warn( "pygit2 does not support disabling the SSL certificate " - "check in versions prior to 0.23.2 (installed: {}). " - "Fetches for self-signed certificates will fail.".format( - PYGIT2_VERSION - ) + f"check in versions prior to 0.23.2 (installed: {PYGIT2_VERSION}). " + "Fetches for self-signed certificates will fail." ) def verify_auth(self): @@ -2489,11 +2582,12 @@ class GitBase: if self.provider in AUTH_PROVIDERS: override_params += AUTH_PARAMS elif global_auth_params: + msg_auth_providers = "{}".format(", ".join(AUTH_PROVIDERS)) msg = ( - "{0} authentication was configured, but the '{1}' " - "{0}_provider does not support authentication. The " - "providers for which authentication is supported in {0} " - "are: {2}.".format(self.role, self.provider, ", ".join(AUTH_PROVIDERS)) + f"{self.role} authentication was configured, but the '{self.provider}' " + f"{self.role}_provider does not support authentication. The " + f"providers for which authentication is supported in {self.role} " + f"are: {msg_auth_providers}." ) if self.role == "gitfs": msg += ( @@ -2665,6 +2759,7 @@ class GitBase: success, failed = repo.clear_lock(lock_type=lock_type) cleared.extend(success) errors.extend(failed) + return cleared, errors def fetch_remotes(self, remotes=None): @@ -2876,15 +2971,13 @@ class GitBase: errors = [] if GITPYTHON_VERSION < GITPYTHON_MINVER: errors.append( - "{} is configured, but the GitPython version is earlier than " - "{}. Version {} detected.".format( - self.role, GITPYTHON_MINVER, GITPYTHON_VERSION - ) + f"{self.role} is configured, but the GitPython version is earlier than " + f"{GITPYTHON_MINVER}. Version {GITPYTHON_VERSION} detected." ) if not salt.utils.path.which("git"): errors.append( "The git command line utility is required when using the " - "'gitpython' {}_provider.".format(self.role) + f"'gitpython' {self.role}_provider." ) if errors: @@ -2923,24 +3016,20 @@ class GitBase: errors = [] if PYGIT2_VERSION < PYGIT2_MINVER: errors.append( - "{} is configured, but the pygit2 version is earlier than " - "{}. Version {} detected.".format( - self.role, PYGIT2_MINVER, PYGIT2_VERSION - ) + f"{self.role} is configured, but the pygit2 version is earlier than " + f"{PYGIT2_MINVER}. Version {PYGIT2_VERSION} detected." ) if LIBGIT2_VERSION < LIBGIT2_MINVER: errors.append( - "{} is configured, but the libgit2 version is earlier than " - "{}. Version {} detected.".format( - self.role, LIBGIT2_MINVER, LIBGIT2_VERSION - ) + f"{self.role} is configured, but the libgit2 version is earlier than " + f"{LIBGIT2_MINVER}. Version {LIBGIT2_VERSION} detected." ) if not getattr(pygit2, "GIT_FETCH_PRUNE", False) and not salt.utils.path.which( "git" ): errors.append( "The git command line utility is required when using the " - "'pygit2' {}_provider.".format(self.role) + f"'pygit2' {self.role}_provider." ) if errors: @@ -3253,10 +3342,11 @@ class GitFS(GitBase): ret = {"hash_type": self.opts["hash_type"]} relpath = fnd["rel"] path = fnd["path"] + lc_hash_type = self.opts["hash_type"] hashdest = salt.utils.path.join( self.hash_cachedir, load["saltenv"], - "{}.hash.{}".format(relpath, self.opts["hash_type"]), + f"{relpath}.hash.{lc_hash_type}", ) try: with salt.utils.files.fopen(hashdest, "rb") as fp_: @@ -3291,13 +3381,14 @@ class GitFS(GitBase): except OSError: log.error("Unable to make cachedir %s", self.file_list_cachedir) return [] + lc_path_adj = load["saltenv"].replace(os.path.sep, "_|-") list_cache = salt.utils.path.join( self.file_list_cachedir, - "{}.p".format(load["saltenv"].replace(os.path.sep, "_|-")), + f"{lc_path_adj}.p", ) w_lock = salt.utils.path.join( self.file_list_cachedir, - ".{}.w".format(load["saltenv"].replace(os.path.sep, "_|-")), + f".{lc_path_adj}.w", ) cache_match, refresh_cache, save_cache = salt.fileserver.check_file_list_cache( self.opts, form, list_cache, w_lock @@ -3561,3 +3652,100 @@ class WinRepo(GitBase): cachedir = self.do_checkout(repo, fetch_on_fail=fetch_on_fail) if cachedir is not None: self.winrepo_dirs[repo.id] = cachedir + + +def gitfs_finalize_cleanup(cache_dir): + """ + Clean up finalize processes that used gitfs + """ + cur_pid = os.getpid() + mach_id = _get_machine_identifier().get("machine_id", "no_machine_id_available") + + # need to clean up any resources left around like lock files if using gitfs + # example: lockfile + # /var/cache/salt/master/gitfs/work/NlJQs6Pss_07AugikCrmqfmqEFrfPbCDBqGLBiCd3oU=/_/update.lk + # check for gitfs file locks to ensure no resource leaks + # last chance to clean up any missed unlock droppings + cache_dir = pathlib.Path(cache_dir + "/gitfs/work") + if cache_dir.exists and cache_dir.is_dir(): + file_list = list(cache_dir.glob("**/*.lk")) + file_del_list = [] + file_pid = 0 + file_mach_id = 0 + try: + for file_name in file_list: + with salt.utils.files.fopen(file_name, "r") as fd_: + try: + file_pid = int( + salt.utils.stringutils.to_unicode(fd_.readline()).rstrip() + ) + except ValueError: + # Lock file is empty, set pid to 0 so it evaluates as False. + file_pid = 0 + try: + file_mach_id = salt.utils.stringutils.to_unicode( + fd_.readline() + ).rstrip() + except ValueError: + # Lock file is empty, set mach_id to 0 so it evaluates False. + file_mach_id = 0 + + if cur_pid == file_pid: + if mach_id != file_mach_id: + if not file_mach_id: + msg = ( + f"gitfs lock file for pid '{file_pid}' does not " + "contain a machine id, deleting lock file which may " + "affect if using multi-master with shared gitfs cache, " + "the lock may have been obtained by another master " + "recommend updating Salt version on other masters to a " + "version which insert machine identification in lock a file." + ) + log.debug(msg) + file_del_list.append((file_name, file_pid, file_mach_id)) + else: + file_del_list.append((file_name, file_pid, file_mach_id)) + + except FileNotFoundError: + log.debug("gitfs lock file: %s not found", file_name) + + for file_name, file_pid, file_mach_id in file_del_list: + try: + os.remove(file_name) + except OSError as exc: + if exc.errno == errno.ENOENT: + # No lock file present + msg = ( + "SIGTERM clean up of resources attempted to remove lock " + f"file {file_name}, pid '{file_pid}', machine identifier " + f"'{mach_id}' but it did not exist, exception : {exc} " + ) + log.debug(msg) + + elif exc.errno == errno.EISDIR: + # Somehow this path is a directory. Should never happen + # unless some wiseguy manually creates a directory at this + # path, but just in case, handle it. + try: + shutil.rmtree(file_name) + except OSError as exc: + msg = ( + f"SIGTERM clean up of resources, lock file '{file_name}'" + f", pid '{file_pid}', machine identifier '{file_mach_id}'" + f"was a directory, removed directory, exception : '{exc}'" + ) + log.debug(msg) + else: + msg = ( + "SIGTERM clean up of resources, unable to remove lock file " + f"'{file_name}', pid '{file_pid}', machine identifier " + f"'{file_mach_id}', exception : '{exc}'" + ) + log.debug(msg) + else: + msg = ( + "SIGTERM clean up of resources, removed lock file " + f"'{file_name}', pid '{file_pid}', machine identifier " + f"'{file_mach_id}'" + ) + log.debug(msg) diff --git a/salt/utils/http.py b/salt/utils/http.py index 4d6c53faf6e..67fc05ce469 100644 --- a/salt/utils/http.py +++ b/salt/utils/http.py @@ -25,7 +25,7 @@ import zlib import tornado.httpclient import tornado.httputil import tornado.simple_httpclient -from tornado.httpclient import HTTPClient +from tornado.httpclient import AsyncHTTPClient import salt.config import salt.loader @@ -43,6 +43,7 @@ import salt.utils.xmlutil as xml import salt.utils.yaml import salt.version from salt.template import compile_template +from salt.utils.asynchronous import SyncWrapper from salt.utils.decorators.jinja import jinja_filter try: @@ -598,7 +599,7 @@ def query( salt.config.DEFAULT_MINION_OPTS["http_request_timeout"], ) - tornado.httpclient.AsyncHTTPClient.configure(None) + AsyncHTTPClient.configure(None) client_argspec = salt.utils.args.get_function_argspec( tornado.simple_httpclient.SimpleAsyncHTTPClient.initialize ) @@ -629,10 +630,10 @@ def query( req_kwargs = salt.utils.data.decode(req_kwargs, to_str=True) try: - download_client = ( - HTTPClient(max_body_size=max_body) - if supports_max_body_size - else HTTPClient() + download_client = SyncWrapper( + AsyncHTTPClient, + kwargs={"max_body_size": max_body} if supports_max_body_size else {}, + async_methods=["fetch"], ) result = download_client.fetch(url_full, **req_kwargs) except tornado.httpclient.HTTPError as exc: diff --git a/salt/utils/jinja.py b/salt/utils/jinja.py index f802156ddb8..6c65976ea3b 100644 --- a/salt/utils/jinja.py +++ b/salt/utils/jinja.py @@ -22,7 +22,6 @@ from jinja2.environment import TemplateModule from jinja2.exceptions import TemplateRuntimeError from jinja2.ext import Extension -import salt.fileclient import salt.utils.data import salt.utils.files import salt.utils.json @@ -93,6 +92,8 @@ class SaltCacheLoader(BaseLoader): or not hasattr(self._file_client, "opts") or self._file_client.opts["file_roots"] != self.opts["file_roots"] ): + import salt.fileclient + self._file_client = salt.fileclient.get_file_client( self.opts, self.pillar_rend ) diff --git a/salt/utils/platform.py b/salt/utils/platform.py index c6ca7fe8cae..100918b72d5 100644 --- a/salt/utils/platform.py +++ b/salt/utils/platform.py @@ -239,3 +239,22 @@ def spawning_platform(): Salt, however, will force macOS to spawning by default on all python versions """ return multiprocessing.get_start_method(allow_none=False) == "spawn" + + +def get_machine_identifier(): + """ + Provide the machine-id for machine/virtualization combination + """ + # pylint: disable=resource-leakage + # Provides: + # machine-id + locations = ["/etc/machine-id", "/var/lib/dbus/machine-id"] + existing_locations = [loc for loc in locations if os.path.exists(loc)] + if not existing_locations: + return {} + else: + # cannot use salt.utils.files.fopen due to circular dependency + with open( + existing_locations[0], encoding=__salt_system_encoding__ + ) as machineid: + return {"machine_id": machineid.read().strip()} diff --git a/salt/utils/process.py b/salt/utils/process.py index 51f2b91b38f..cdda5df02a5 100644 --- a/salt/utils/process.py +++ b/salt/utils/process.py @@ -47,6 +47,9 @@ try: except ImportError: HAS_SETPROCTITLE = False +# Process finalization function list +_INTERNAL_PROCESS_FINALIZE_FUNCTION_LIST = [] + def appendproctitle(name): """ @@ -70,7 +73,6 @@ def daemonize(redirect_out=True): pid = os.fork() if pid > 0: # exit first parent - salt.utils.crypt.reinit_crypto() os._exit(salt.defaults.exitcodes.EX_OK) except OSError as exc: log.error("fork #1 failed: %s (%s)", exc.errno, exc) @@ -86,14 +88,11 @@ def daemonize(redirect_out=True): try: pid = os.fork() if pid > 0: - salt.utils.crypt.reinit_crypto() sys.exit(salt.defaults.exitcodes.EX_OK) except OSError as exc: log.error("fork #2 failed: %s (%s)", exc.errno, exc) sys.exit(salt.defaults.exitcodes.EX_GENERIC) - salt.utils.crypt.reinit_crypto() - # A normal daemonization redirects the process output to /dev/null. # Unfortunately when a python multiprocess is called the output is # not cleanly redirected and the parent process dies when the @@ -526,11 +525,14 @@ class ProcessManager: target=tgt, args=args, kwargs=kwargs, name=name or tgt.__qualname__ ) + process.register_finalize_method(cleanup_finalize_process, args, kwargs) + if isinstance(process, SignalHandlingProcess): with default_signals(signal.SIGINT, signal.SIGTERM): process.start() else: process.start() + log.debug("Started '%s' with pid %s", process.name, process.pid) self._process_map[process.pid] = { "tgt": tgt, @@ -538,6 +540,7 @@ class ProcessManager: "kwargs": kwargs, "Process": process, } + return process def restart_process(self, pid): @@ -686,6 +689,7 @@ class ProcessManager: pass try: p_map["Process"].terminate() + except OSError as exc: if exc.errno not in (errno.ESRCH, errno.EACCES): raise @@ -1070,6 +1074,21 @@ class SignalHandlingProcess(Process): msg += "SIGTERM" msg += ". Exiting" log.debug(msg) + + # Run any registered process finalization routines + for method, args, kwargs in self._finalize_methods: + try: + method(*args, **kwargs) + except Exception: # pylint: disable=broad-except + log.exception( + "Failed to run finalize callback on %s; method=%r; args=%r; and kwargs=%r", + self, + method, + args, + kwargs, + ) + continue + if HAS_PSUTIL: try: process = psutil.Process(os.getpid()) @@ -1085,6 +1104,7 @@ class SignalHandlingProcess(Process): self.pid, os.getpid(), ) + except psutil.NoSuchProcess: log.warning( "Unable to kill children of process %d, it does not exist." @@ -1156,3 +1176,57 @@ class SubprocessList: self.processes.remove(proc) self.count -= 1 log.debug("Subprocess %s cleaned up", proc.name) + + +def cleanup_finalize_process(*args, **kwargs): + """ + Generic process to allow for any registered process cleanup routines to execute. + + While class Process has a register_finalize_method, when a process is looked up by pid + using psutil.Process, there is no method available to register a cleanup process. + + Hence, this function is added as part of the add_process to allow usage of other cleanup processes + which cannot be added by the register_finalize_method. + """ + + # Run any registered process cleanup routines + for method, args, kwargs in _INTERNAL_PROCESS_FINALIZE_FUNCTION_LIST: + log.debug( + "cleanup_finalize_process, method=%r, args=%r, kwargs=%r", + method, + args, + kwargs, + ) + try: + method(*args, **kwargs) + except Exception: # pylint: disable=broad-except + log.exception( + "Failed to run registered function finalize callback; method=%r; args=%r; and kwargs=%r", + method, + args, + kwargs, + ) + continue + + +def register_cleanup_finalize_function(function, *args, **kwargs): + """ + Register a function to run as process terminates + + While class Process has a register_finalize_method, when a process is looked up by pid + using psutil.Process, there is no method available to register a cleanup process. + + Hence, this function can be used to register a function to allow cleanup processes + which cannot be added by class Process register_finalize_method. + + Note: there is no deletion, since it is assummed that if something is registered, it will continue to be used + """ + log.debug( + "register_cleanup_finalize_function entry, function=%r, args=%r, kwargs=%r", + function, + args, + kwargs, + ) + finalize_function_tuple = (function, args, kwargs) + if finalize_function_tuple not in _INTERNAL_PROCESS_FINALIZE_FUNCTION_LIST: + _INTERNAL_PROCESS_FINALIZE_FUNCTION_LIST.append(finalize_function_tuple) diff --git a/salt/utils/vt.py b/salt/utils/vt.py index 3ffe45af2bf..06867861201 100644 --- a/salt/utils/vt.py +++ b/salt/utils/vt.py @@ -461,8 +461,6 @@ class Terminal: else: os.close(tty_fd) - salt.utils.crypt.reinit_crypto() - if preexec_fn is not None: preexec_fn() diff --git a/salt/utils/win_functions.py b/salt/utils/win_functions.py index 66327a88007..24c319749f8 100644 --- a/salt/utils/win_functions.py +++ b/salt/utils/win_functions.py @@ -11,15 +11,21 @@ from salt.exceptions import CommandExecutionError try: import psutil - import pywintypes import win32api import win32net import win32security from win32con import HWND_BROADCAST, SMTO_ABORTIFHUNG, WM_SETTINGCHANGE + import pywintypes # isort:skip + HAS_WIN32 = True except ImportError: - HAS_WIN32 = False + try: + import psutil + from win32 import pywintypes, win32api, win32net, win32security + from win32con import HWND_BROADCAST, SMTO_ABORTIFHUNG, WM_SETTINGCHANGE + except ImportError: + HAS_WIN32 = False # Although utils are often directly imported, it is also possible to use the diff --git a/salt/version.py b/salt/version.py index 92e096566fb..137383a704a 100644 --- a/salt/version.py +++ b/salt/version.py @@ -706,6 +706,7 @@ def dependency_information(include_salt_cloud=False): ("msgpack-pure", "msgpack_pure", "version"), ("pycrypto", "Crypto", "__version__"), ("pycryptodome", "Cryptodome", "version_info"), + ("cryptography", "cryptography", "__version__"), ("PyYAML", "yaml", "__version__"), ("PyZMQ", "zmq", "__version__"), ("ZMQ", "zmq", "zmq_version"), diff --git a/tests/conftest.py b/tests/conftest.py index 218dfdab6ac..1d283a8bbc9 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -991,6 +991,9 @@ def salt_syndic_master_factory( config_overrides = { "log_level_logfile": "quiet", "fips_mode": FIPS_TESTRUN, + "publish_signing_algorithm": ( + "PKCS1v15-SHA224" if FIPS_TESTRUN else "PKCS1v15-SHA1" + ), } ext_pillar = [] if salt.utils.platform.is_windows(): @@ -1107,6 +1110,9 @@ def salt_master_factory( config_overrides = { "log_level_logfile": "quiet", "fips_mode": FIPS_TESTRUN, + "publish_signing_algorithm": ( + "PKCS1v15-SHA224" if FIPS_TESTRUN else "PKCS1v15-SHA1" + ), } ext_pillar = [] if salt.utils.platform.is_windows(): @@ -1216,6 +1222,8 @@ def salt_minion_factory(salt_master_factory): "file_roots": salt_master_factory.config["file_roots"].copy(), "pillar_roots": salt_master_factory.config["pillar_roots"].copy(), "fips_mode": FIPS_TESTRUN, + "encryption_algorithm": "OAEP-SHA224" if FIPS_TESTRUN else "OAEP-SHA1", + "signing_algorithm": "PKCS1v15-SHA224" if FIPS_TESTRUN else "PKCS1v15-SHA1", } virtualenv_binary = get_virtualenv_binary_path() @@ -1248,6 +1256,8 @@ def salt_sub_minion_factory(salt_master_factory): "file_roots": salt_master_factory.config["file_roots"].copy(), "pillar_roots": salt_master_factory.config["pillar_roots"].copy(), "fips_mode": FIPS_TESTRUN, + "encryption_algorithm": "OAEP-SHA224" if FIPS_TESTRUN else "OAEP-SHA1", + "signing_algorithm": "PKCS1v15-SHA224" if FIPS_TESTRUN else "PKCS1v15-SHA1", } virtualenv_binary = get_virtualenv_binary_path() diff --git a/tests/integration/loader/test_ext_grains.py b/tests/integration/loader/test_ext_grains.py index 242519aa3f4..b1c36ace68e 100644 --- a/tests/integration/loader/test_ext_grains.py +++ b/tests/integration/loader/test_ext_grains.py @@ -16,6 +16,9 @@ from tests.support.case import ModuleCase from tests.support.runtests import RUNTIME_VARS +@pytest.mark.skip_on_photonos( + reason="Consistant failures on photon, test needs refactoring" +) @pytest.mark.windows_whitelisted class LoaderGrainsTest(ModuleCase): """ diff --git a/tests/pytests/conftest.py b/tests/pytests/conftest.py index c325168b8d5..975e8e254d7 100644 --- a/tests/pytests/conftest.py +++ b/tests/pytests/conftest.py @@ -181,6 +181,9 @@ def salt_master_factory( config_overrides = { "pytest-master": {"log": {"level": "DEBUG"}}, "fips_mode": FIPS_TESTRUN, + "publish_signing_algorithm": ( + "PKCS1v15-SHA224" if FIPS_TESTRUN else "PKCS1v15-SHA1" + ), } ext_pillar = [] if salt.utils.platform.is_windows(): @@ -306,6 +309,8 @@ def salt_minion_factory(salt_master_factory, salt_minion_id, sdb_etcd_port): "file_roots": salt_master_factory.config["file_roots"].copy(), "pillar_roots": salt_master_factory.config["pillar_roots"].copy(), "fips_mode": FIPS_TESTRUN, + "encryption_algorithm": "OAEP-SHA224" if FIPS_TESTRUN else "OAEP-SHA1", + "signing_algorithm": "PKCS1v15-SHA224" if FIPS_TESTRUN else "PKCS1v15-SHA1", } virtualenv_binary = get_virtualenv_binary_path() @@ -337,6 +342,8 @@ def salt_sub_minion_factory(salt_master_factory, salt_sub_minion_id): "file_roots": salt_master_factory.config["file_roots"].copy(), "pillar_roots": salt_master_factory.config["pillar_roots"].copy(), "fips_mode": FIPS_TESTRUN, + "encryption_algorithm": "OAEP-SHA224" if FIPS_TESTRUN else "OAEP-SHA1", + "signing_algorithm": "PKCS1v15-SHA224" if FIPS_TESTRUN else "PKCS1v15-SHA1", } virtualenv_binary = get_virtualenv_binary_path() @@ -361,6 +368,9 @@ def salt_proxy_factory(salt_master_factory): config_overrides = { "file_roots": salt_master_factory.config["file_roots"].copy(), "pillar_roots": salt_master_factory.config["pillar_roots"].copy(), + "fips_mode": FIPS_TESTRUN, + "encryption_algorithm": "OAEP-SHA224" if FIPS_TESTRUN else "OAEP-SHA1", + "signing_algorithm": "PKCS1v15-SHA224" if FIPS_TESTRUN else "PKCS1v15-SHA1", } factory = salt_master_factory.salt_proxy_minion_daemon( @@ -399,9 +409,15 @@ def salt_delta_proxy_factory(salt_factories, salt_master_factory): "metaproxy": "deltaproxy", "master": "127.0.0.1", } + config_overrides = { + "fips_mode": FIPS_TESTRUN, + "encryption_algorithm": "OAEP-SHA224" if FIPS_TESTRUN else "OAEP-SHA1", + "signing_algorithm": "PKCS1v15-SHA224" if FIPS_TESTRUN else "PKCS1v15-SHA1", + } factory = salt_master_factory.salt_proxy_minion_daemon( proxy_minion_id, defaults=config_defaults, + overrides=config_overrides, extra_cli_arguments_after_first_start_failure=["--log-level=info"], start_timeout=240, ) @@ -428,9 +444,16 @@ def temp_salt_master( "open_mode": True, "transport": request.config.getoption("--transport"), } + config_overrides = { + "fips_mode": FIPS_TESTRUN, + "publish_signing_algorithm": ( + "PKCS1v15-SHA224" if FIPS_TESTRUN else "PKCS1v15-SHA1" + ), + } factory = salt_factories.salt_master_daemon( random_string("temp-master-"), defaults=config_defaults, + overrides=config_overrides, extra_cli_arguments_after_first_start_failure=["--log-level=info"], ) return factory @@ -442,9 +465,15 @@ def temp_salt_minion(temp_salt_master): "open_mode": True, "transport": temp_salt_master.config["transport"], } + config_overrides = { + "fips_mode": FIPS_TESTRUN, + "encryption_algorithm": "OAEP-SHA224" if FIPS_TESTRUN else "OAEP-SHA1", + "signing_algorithm": "PKCS1v15-SHA224" if FIPS_TESTRUN else "PKCS1v15-SHA1", + } factory = temp_salt_master.salt_minion_daemon( random_string("temp-minion-"), defaults=config_defaults, + overrides=config_overrides, extra_cli_arguments_after_first_start_failure=["--log-level=info"], ) factory.after_terminate( diff --git a/tests/pytests/functional/channel/conftest.py b/tests/pytests/functional/channel/conftest.py index 387e3bcf4e5..e7ec5a6e32e 100644 --- a/tests/pytests/functional/channel/conftest.py +++ b/tests/pytests/functional/channel/conftest.py @@ -7,6 +7,7 @@ from saltfactories.utils import random_string import salt.crypt import salt.master import salt.utils.stringutils +from tests.conftest import FIPS_TESTRUN @pytest.fixture(autouse=True) @@ -42,6 +43,10 @@ def salt_master(salt_factories, transport): "transport": transport, "auto_accept": True, "sign_pub_messages": False, + "fips_mode": FIPS_TESTRUN, + "publish_signing_algorithm": ( + "PKCS1v15-SHA224" if FIPS_TESTRUN else "PKCS1v15-SHA1" + ), } factory = salt_factories.salt_master_daemon( random_string(f"server-{transport}-master-"), @@ -59,6 +64,9 @@ def salt_minion(salt_master, transport): "auth_timeout": 5, "auth_tries": 1, "master_uri": f"tcp://127.0.0.1:{salt_master.config['ret_port']}", + "fips_mode": FIPS_TESTRUN, + "encryption_algorithm": "OAEP-SHA224" if FIPS_TESTRUN else "OAEP-SHA1", + "signing_algorithm": "PKCS1v15-SHA224" if FIPS_TESTRUN else "PKCS1v15-SHA1", } factory = salt_master.salt_minion_daemon( random_string("server-{transport}-minion-"), diff --git a/tests/pytests/functional/channel/test_req_channel.py b/tests/pytests/functional/channel/test_req_channel.py index 89d171617f6..20d08d6266e 100644 --- a/tests/pytests/functional/channel/test_req_channel.py +++ b/tests/pytests/functional/channel/test_req_channel.py @@ -31,6 +31,7 @@ pytestmark = [ class ReqServerChannelProcess(salt.utils.process.SignalHandlingProcess): + def __init__(self, config, req_channel_crypt): super().__init__() self._closing = False diff --git a/tests/pytests/functional/channel/test_server.py b/tests/pytests/functional/channel/test_server.py index 05204c16d91..cd2a828e41e 100644 --- a/tests/pytests/functional/channel/test_server.py +++ b/tests/pytests/functional/channel/test_server.py @@ -20,6 +20,7 @@ import salt.master import salt.utils.platform import salt.utils.process import salt.utils.stringutils +from tests.conftest import FIPS_TESTRUN log = logging.getLogger(__name__) @@ -68,14 +69,20 @@ def transport(request): @pytest.fixture def master_config(root_dir, transport): master_conf = salt.config.master_config("") - master_conf["transport"] = transport - master_conf["id"] = "master" - master_conf["root_dir"] = str(root_dir) - master_conf["sock_dir"] = str(root_dir) - master_conf["interface"] = "127.0.0.1" - master_conf["publish_port"] = ports.get_unused_localhost_port() - master_conf["ret_port"] = ports.get_unused_localhost_port() - master_conf["pki_dir"] = str(root_dir / "pki") + master_conf.update( + transport=transport, + id="master", + root_dir=str(root_dir), + sock_dir=str(root_dir), + interface="127.0.0.1", + publish_port=ports.get_unused_localhost_port(), + ret_port=ports.get_unused_localhost_port(), + pki_dir=str(root_dir / "pki"), + fips_mode=FIPS_TESTRUN, + publish_signing_algorithm=( + "PKCS1v15-SHA224" if FIPS_TESTRUN else "PKCS1v15-SHA1" + ), + ) os.makedirs(master_conf["pki_dir"]) salt.crypt.gen_keys(master_conf["pki_dir"], "master", 4096) minions_keys = os.path.join(master_conf["pki_dir"], "minions") @@ -88,17 +95,22 @@ def minion_config(master_config, channel_minion_id): minion_conf = salt.config.minion_config( "", minion_id=channel_minion_id, cache_minion_id=False ) - minion_conf["transport"] = master_config["transport"] - minion_conf["root_dir"] = master_config["root_dir"] - minion_conf["id"] = channel_minion_id - minion_conf["sock_dir"] = master_config["sock_dir"] - minion_conf["ret_port"] = master_config["ret_port"] - minion_conf["interface"] = "127.0.0.1" - minion_conf["pki_dir"] = os.path.join(master_config["root_dir"], "pki_minion") + minion_conf.update( + transport=master_config["transport"], + root_dir=master_config["root_dir"], + id=channel_minion_id, + sock_dir=master_config["sock_dir"], + ret_port=master_config["ret_port"], + interface="127.0.0.1", + pki_dir=os.path.join(master_config["root_dir"], "pki_minion"), + master_port=master_config["ret_port"], + master_ip="127.0.0.1", + master_uri="tcp://127.0.0.1:{}".format(master_config["ret_port"]), + fips_mode=FIPS_TESTRUN, + encryption_algorithm="OAEP-SHA224" if FIPS_TESTRUN else "OAEP-SHA1", + signing_algorithm="PKCS1v15-SHA224" if FIPS_TESTRUN else "PKCS1v15-SHA1", + ) os.makedirs(minion_conf["pki_dir"]) - minion_conf["master_port"] = master_config["ret_port"] - minion_conf["master_ip"] = "127.0.0.1" - minion_conf["master_uri"] = "tcp://127.0.0.1:{}".format(master_config["ret_port"]) salt.crypt.gen_keys(minion_conf["pki_dir"], "minion", 4096) minion_pub = os.path.join(minion_conf["pki_dir"], "minion.pub") pub_on_master = os.path.join(master_config["pki_dir"], "minions", channel_minion_id) diff --git a/tests/pytests/functional/cli/test_salt_deltaproxy.py b/tests/pytests/functional/cli/test_salt_deltaproxy.py index 022aa77efcf..27faf8ed3d0 100644 --- a/tests/pytests/functional/cli/test_salt_deltaproxy.py +++ b/tests/pytests/functional/cli/test_salt_deltaproxy.py @@ -9,6 +9,7 @@ import pytest from saltfactories.utils import random_string import salt.defaults.exitcodes +from tests.conftest import FIPS_TESTRUN from tests.support.helpers import PRE_PYTEST_SKIP_REASON log = logging.getLogger(__name__) @@ -29,7 +30,14 @@ def salt_master(salt_factories): "open_mode": True, } salt_master = salt_factories.salt_master_daemon( - "deltaproxy-functional-master", defaults=config_defaults + "deltaproxy-functional-master", + defaults=config_defaults, + overrides={ + "fips_mode": FIPS_TESTRUN, + "publish_signing_algorithm": ( + "PKCS1v15-SHA224" if FIPS_TESTRUN else "PKCS1v15-SHA1" + ), + }, ) with salt_master.started(): yield salt_master @@ -172,6 +180,15 @@ def test_exit_status_correct_usage_large_number_of_minions( factory = salt_master.salt_proxy_minion_daemon( proxy_minion_id, defaults=config_defaults, + overrides={ + "fips_mode": FIPS_TESTRUN, + "encryption_algorithm": ( + "OAEP-SHA224" if FIPS_TESTRUN else "OAEP-SHA1" + ), + "signing_algorithm": ( + "PKCS1v15-SHA224" if FIPS_TESTRUN else "PKCS1v15-SHA1" + ), + }, extra_cli_arguments_after_first_start_failure=["--log-level=info"], start_timeout=240, ) diff --git a/tests/pytests/functional/states/test_x509_v2.py b/tests/pytests/functional/states/test_x509_v2.py index 613275bc3d5..3abda4f5fd9 100644 --- a/tests/pytests/functional/states/test_x509_v2.py +++ b/tests/pytests/functional/states/test_x509_v2.py @@ -1,5 +1,6 @@ import base64 import pathlib +import shutil import pytest @@ -31,7 +32,28 @@ pytestmark = [ @pytest.fixture(scope="module") -def minion_config_overrides(): +def ca_dir(tmp_path_factory): + ca_dir = tmp_path_factory.mktemp("ca") + try: + yield ca_dir + finally: + shutil.rmtree(str(ca_dir), ignore_errors=True) + + +@pytest.fixture(scope="module") +def ca_key_file(ca_dir, ca_key): + with pytest.helpers.temp_file("ca.key", ca_key, ca_dir) as key: + yield key + + +@pytest.fixture(scope="module") +def ca_cert_file(ca_dir, ca_cert): + with pytest.helpers.temp_file("ca.crt", ca_cert, ca_dir) as crt: + yield crt + + +@pytest.fixture(scope="module") +def minion_config_overrides(ca_key_file, ca_cert_file): return { "x509_signing_policies": { "testpolicy": { @@ -47,6 +69,11 @@ def minion_config_overrides(): "testnosubjectpolicy": { "CN": "from_signing_policy", }, + "test_fixed_signing_private_key": { + "subject": "CN=from_signing_policy", + "signing_cert": str(ca_cert_file), + "signing_private_key": str(ca_key_file), + }, }, } @@ -56,7 +83,7 @@ def x509(loaders, states, tmp_path): yield states.x509 -@pytest.fixture +@pytest.fixture(scope="module") def ca_cert(): return """\ -----BEGIN CERTIFICATE----- @@ -82,7 +109,7 @@ LN1w5sybsYwIw6QN """ -@pytest.fixture +@pytest.fixture(scope="module") def ca_key(): return """\ -----BEGIN RSA PRIVATE KEY----- @@ -382,11 +409,11 @@ O68= @pytest.fixture -def cert_args(tmp_path, ca_cert, ca_key): +def cert_args(tmp_path, ca_cert_file, ca_key_file): return { "name": f"{tmp_path}/cert", - "signing_private_key": ca_key, - "signing_cert": ca_cert, + "signing_private_key": str(ca_key_file), + "signing_cert": str(ca_cert_file), "CN": "success", } @@ -413,11 +440,11 @@ def cert_args_exts(): @pytest.fixture -def crl_args(tmp_path, ca_cert, ca_key): +def crl_args(tmp_path, ca_cert_file, ca_key_file): return { "name": f"{tmp_path}/crl", - "signing_private_key": ca_key, - "signing_cert": ca_cert, + "signing_private_key": str(ca_key_file), + "signing_cert": str(ca_cert_file), "revoked": [], } @@ -835,6 +862,20 @@ def test_certificate_managed_with_signing_policy(x509, cert_args, rsa_privkey, c assert _signed_by(cert, ca_key) +def test_certificate_managed_with_fixed_signing_key_in_signing_policy( + x509, rsa_privkey, ca_key, cert_args +): + cert_args["signing_policy"] = "test_fixed_signing_private_key" + cert_args["private_key"] = rsa_privkey + ret = x509.certificate_managed(**cert_args) + assert ret.result is True + assert ret.changes + assert ret.changes.get("created") + cert = _get_cert(cert_args["name"]) + assert _belongs_to(cert, rsa_privkey) + assert _signed_by(cert, ca_key) + + def test_certificate_managed_with_distinguished_name_kwargs( x509, cert_args, rsa_privkey, ca_key ): @@ -917,6 +958,25 @@ def test_certificate_managed_existing_with_signing_policy(x509, cert_args): _assert_not_changed(ret) +@pytest.mark.usefixtures("existing_cert") +@pytest.mark.parametrize( + "existing_cert", + [{"signing_policy": "test_fixed_signing_private_key"}], + indirect=True, +) +def test_certificate_managed_existing_with_fixed_signing_key_in_signing_policy( + x509, rsa_privkey, ca_key, cert_args +): + """ + If the policy defines a fixed signing_private_key and a certificate + is managed locally (without ca_server), the state module should not crash + when checking for changes. + Issue #66414 + """ + ret = x509.certificate_managed(**cert_args) + _assert_not_changed(ret) + + @pytest.mark.usefixtures("existing_cert") @pytest.mark.parametrize( "existing_cert", diff --git a/tests/pytests/functional/test_config.py b/tests/pytests/functional/test_config.py index 76d25118f4a..9417c1b6751 100644 --- a/tests/pytests/functional/test_config.py +++ b/tests/pytests/functional/test_config.py @@ -5,6 +5,7 @@ import tempfile import pytest import salt.config +import salt.utils.platform pytestmark = [ pytest.mark.windows_whitelisted, @@ -25,3 +26,128 @@ def test_minion_config_type_check(caplog): assert msg not in caplog.text finally: os.remove(path) + + +def test_cloud_config_relative_to_root_dir(tmp_path): + root_path = tmp_path + config_path = tmp_path / "conf" + config_path.mkdir() + cloud_config = config_path / "cloud" + cloud_config.write_text("") + master_config = config_path / "master" + master_config.write_text(f"root_dir: {root_path}") + opts = salt.config.cloud_config(cloud_config) + assert opts["log_file"] == str(root_path / "var" / "log" / "salt" / "cloud") + assert opts["cachedir"] == str(root_path / "var" / "cache" / "salt" / "cloud") + + +def test_master_config_relative_to_root_dir(tmp_path): + root_path = tmp_path + config_path = tmp_path / "conf" + config_path.mkdir() + master_config = config_path / "master" + master_config.write_text(f"root_dir: {root_path}") + opts = salt.config.master_config(master_config) + if salt.utils.platform.is_windows(): + assert opts["pki_dir"] == str(root_path / "conf" / "pki" / "master") + else: + assert opts["pki_dir"] == str(root_path / "etc" / "salt" / "pki" / "master") + assert opts["cachedir"] == str(root_path / "var" / "cache" / "salt" / "master") + assert opts["pidfile"] == str(root_path / "var" / "run" / "salt-master.pid") + assert opts["sock_dir"] == str(root_path / "var" / "run" / "salt" / "master") + assert opts["extension_modules"] == str( + root_path / "var" / "cache" / "salt" / "master" / "extmods" + ) + assert opts["token_dir"] == str( + root_path / "var" / "cache" / "salt" / "master" / "tokens" + ) + assert opts["syndic_dir"] == str( + root_path / "var" / "cache" / "salt" / "master" / "syndics" + ) + assert opts["sqlite_queue_dir"] == str( + root_path / "var" / "cache" / "salt" / "master" / "queues" + ) + assert opts["log_file"] == str(root_path / "var" / "log" / "salt" / "master") + assert opts["key_logfile"] == str(root_path / "var" / "log" / "salt" / "key") + assert opts["ssh_log_file"] == str(root_path / "var" / "log" / "salt" / "ssh") + + # These are not tested because we didn't define them in the master config. + # assert opts["autosign_file"] == str(root_path / "var" / "run" / "salt"/ "master") + # assert opts["autoreject_file"] == str(root_path / "var" / "run" / "salt"/ "master") + # assert opts["autosign_grains_dir"] == str(root_path / "var" / "run" / "salt"/ "master") + + +def test_minion_config_relative_to_root_dir(tmp_path): + root_path = tmp_path + config_path = tmp_path / "conf" + config_path.mkdir() + minion_config = config_path / "minion" + minion_config.write_text(f"root_dir: {root_path}") + opts = salt.config.minion_config(minion_config) + if salt.utils.platform.is_windows(): + assert opts["pki_dir"] == str(root_path / "conf" / "pki" / "minion") + else: + assert opts["pki_dir"] == str(root_path / "etc" / "salt" / "pki" / "minion") + assert opts["cachedir"] == str(root_path / "var" / "cache" / "salt" / "minion") + assert opts["pidfile"] == str(root_path / "var" / "run" / "salt-minion.pid") + assert opts["sock_dir"] == str(root_path / "var" / "run" / "salt" / "minion") + assert opts["extension_modules"] == str( + root_path / "var" / "cache" / "salt" / "minion" / "extmods" + ) + assert opts["log_file"] == str(root_path / "var" / "log" / "salt" / "minion") + + +def test_api_config_relative_to_root_dir(tmp_path): + root_path = tmp_path + config_path = tmp_path / "conf" + config_path.mkdir() + master_config = config_path / "master" + master_config.write_text(f"root_dir: {root_path}") + opts = salt.config.api_config(master_config) + assert opts["pidfile"] == str(root_path / "var" / "run" / "salt-api.pid") + assert opts["log_file"] == str(root_path / "var" / "log" / "salt" / "api") + assert opts["api_pidfile"] == str(root_path / "var" / "run" / "salt-api.pid") + assert opts["api_logfile"] == str(root_path / "var" / "log" / "salt" / "api") + + +def test_spm_config_relative_to_root_dir(tmp_path): + root_path = tmp_path + config_path = tmp_path / "conf" + config_path.mkdir() + spm_config = config_path / "spm" + spm_config.write_text(f"root_dir: {root_path}") + opts = salt.config.spm_config(spm_config) + + assert opts["formula_path"] == str(root_path / "srv" / "spm" / "salt") + assert opts["pillar_path"] == str(root_path / "srv" / "spm" / "pillar") + assert opts["reactor_path"] == str(root_path / "srv" / "spm" / "reactor") + assert opts["spm_cache_dir"] == str(root_path / "var" / "cache" / "salt" / "spm") + assert opts["spm_build_dir"] == str(root_path / "srv" / "spm_build") + assert opts["spm_logfile"] == str(root_path / "var" / "log" / "salt" / "spm") + + +def test_syndic_config_relative_to_root_dir(tmp_path): + root_path = tmp_path + config_path = tmp_path / "conf" + config_path.mkdir() + master_config = config_path / "master" + master_config.write_text(f"root_dir: {root_path}") + minion_config = config_path / "master" + minion_config.write_text(f"root_dir: {root_path}") + opts = salt.config.syndic_config(master_config, minion_config) + if salt.utils.platform.is_windows(): + assert opts["pki_dir"] == str(root_path / "conf" / "pki" / "minion") + else: + assert opts["pki_dir"] == str(root_path / "etc" / "salt" / "pki" / "minion") + assert opts["cachedir"] == str(root_path / "var" / "cache" / "salt" / "master") + assert opts["pidfile"] == str(root_path / "var" / "run" / "salt-syndic.pid") + assert opts["sock_dir"] == str(root_path / "var" / "run" / "salt" / "minion") + assert opts["extension_modules"] == str( + root_path / "var" / "cache" / "salt" / "minion" / "extmods" + ) + assert opts["token_dir"] == str( + root_path / "var" / "cache" / "salt" / "master" / "tokens" + ) + assert opts["log_file"] == str(root_path / "var" / "log" / "salt" / "syndic") + assert opts["key_logfile"] == str(root_path / "var" / "log" / "salt" / "key") + assert opts["syndic_log_file"] == str(root_path / "var" / "log" / "salt" / "syndic") diff --git a/tests/pytests/functional/transport/server/conftest.py b/tests/pytests/functional/transport/server/conftest.py index 80ad4c55531..636d2e63d02 100644 --- a/tests/pytests/functional/transport/server/conftest.py +++ b/tests/pytests/functional/transport/server/conftest.py @@ -1,6 +1,8 @@ import pytest +from saltfactories.utils import random_string import salt.utils.process +from tests.conftest import FIPS_TESTRUN def transport_ids(value): @@ -19,3 +21,40 @@ def process_manager(): yield pm finally: pm.terminate() + + +@pytest.fixture +def salt_master(salt_factories, transport): + config_defaults = { + "transport": transport, + "auto_accept": True, + "sign_pub_messages": False, + "publish_signing_algorithm": ( + "PKCS1v15-SHA224" if FIPS_TESTRUN else "PKCS1v15-SHA1" + ), + } + factory = salt_factories.salt_master_daemon( + random_string(f"server-{transport}-master-"), + defaults=config_defaults, + ) + return factory + + +@pytest.fixture +def salt_minion(salt_master, transport): + config_defaults = { + "transport": transport, + "master_ip": "127.0.0.1", + "master_port": salt_master.config["ret_port"], + "auth_timeout": 5, + "auth_tries": 1, + "master_uri": "tcp://127.0.0.1:{}".format(salt_master.config["ret_port"]), + "fips_mode": FIPS_TESTRUN, + "encryption_algorithm": "OAEP-SHA224" if FIPS_TESTRUN else "OAEP-SHA1", + "signing_algorithm": "PKCS1v15-SHA224" if FIPS_TESTRUN else "PKCS1v15-SHA1", + } + factory = salt_master.salt_minion_daemon( + random_string(f"server-{transport}-minion-"), + defaults=config_defaults, + ) + return factory diff --git a/tests/pytests/functional/transport/zeromq/conftest.py b/tests/pytests/functional/transport/zeromq/conftest.py index ea6dbd66472..9e8f059e889 100644 --- a/tests/pytests/functional/transport/zeromq/conftest.py +++ b/tests/pytests/functional/transport/zeromq/conftest.py @@ -1,6 +1,8 @@ import pytest from saltfactories.utils import random_string +from tests.conftest import FIPS_TESTRUN + @pytest.fixture def salt_master(salt_factories): @@ -10,7 +12,14 @@ def salt_master(salt_factories): "sign_pub_messages": False, } factory = salt_factories.salt_master_daemon( - random_string("zeromq-master-"), defaults=config_defaults + random_string("zeromq-master-"), + defaults=config_defaults, + overrides={ + "fips_mode": FIPS_TESTRUN, + "publish_signing_algorithm": ( + "PKCS1v15-SHA224" if FIPS_TESTRUN else "PKCS1v15-SHA1" + ), + }, ) return factory @@ -26,6 +35,12 @@ def salt_minion(salt_master): "master_uri": "tcp://127.0.0.1:{}".format(salt_master.config["ret_port"]), } factory = salt_master.salt_minion_daemon( - random_string("zeromq-minion-"), defaults=config_defaults + random_string("zeromq-minion-"), + defaults=config_defaults, + overrides={ + "fips_mode": FIPS_TESTRUN, + "encryption_algorithm": "OAEP-SHA224" if FIPS_TESTRUN else "OAEP-SHA1", + "signing_algorithm": "PKCS1v15-SHA224" if FIPS_TESTRUN else "PKCS1v15-SHA1", + }, ) return factory diff --git a/tests/pytests/functional/utils/gitfs/test_gitfs.py b/tests/pytests/functional/utils/gitfs/test_gitfs.py index 30a5f147faa..e31e3afac4d 100644 --- a/tests/pytests/functional/utils/gitfs/test_gitfs.py +++ b/tests/pytests/functional/utils/gitfs/test_gitfs.py @@ -5,6 +5,7 @@ import pytest from salt.fileserver.gitfs import PER_REMOTE_ONLY, PER_REMOTE_OVERRIDES from salt.utils.gitfs import GitFS, GitPython, Pygit2 from salt.utils.immutabletypes import ImmutableDict, ImmutableList +from salt.utils.platform import get_machine_identifier as _get_machine_identifier pytestmark = [ pytest.mark.slow_test, @@ -248,17 +249,24 @@ def _test_lock(opts): g.fetch_remotes() assert len(g.remotes) == 1 repo = g.remotes[0] + mach_id = _get_machine_identifier().get("machine_id", "no_machine_id_available") assert repo.get_salt_working_dir() in repo._get_lock_file() assert repo.lock() == ( [ - "Set update lock for gitfs remote 'https://github.com/saltstack/salt-test-pillar-gitfs.git'" + ( + f"Set update lock for gitfs remote " + f"'https://github.com/saltstack/salt-test-pillar-gitfs.git' on machine_id '{mach_id}'" + ) ], [], ) assert os.path.isfile(repo._get_lock_file()) assert repo.clear_lock() == ( [ - "Removed update lock for gitfs remote 'https://github.com/saltstack/salt-test-pillar-gitfs.git'" + ( + f"Removed update lock for gitfs remote " + f"'https://github.com/saltstack/salt-test-pillar-gitfs.git' on machine_id '{mach_id}'" + ) ], [], ) diff --git a/tests/pytests/functional/utils/gitfs/test_pillar.py b/tests/pytests/functional/utils/gitfs/test_pillar.py index 5d6729dc5f7..8e5a1aa52ca 100644 --- a/tests/pytests/functional/utils/gitfs/test_pillar.py +++ b/tests/pytests/functional/utils/gitfs/test_pillar.py @@ -5,6 +5,7 @@ import pytest from salt.pillar.git_pillar import GLOBAL_ONLY, PER_REMOTE_ONLY, PER_REMOTE_OVERRIDES from salt.utils.gitfs import GitPillar, GitPython, Pygit2 from salt.utils.immutabletypes import ImmutableDict, ImmutableList +from salt.utils.platform import get_machine_identifier as _get_machine_identifier pytestmark = [ pytest.mark.windows_whitelisted, @@ -339,17 +340,24 @@ def _test_lock(opts): p.fetch_remotes() assert len(p.remotes) == 1 repo = p.remotes[0] + mach_id = _get_machine_identifier().get("machine_id", "no_machine_id_available") assert repo.get_salt_working_dir() in repo._get_lock_file() assert repo.lock() == ( [ - "Set update lock for git_pillar remote 'https://github.com/saltstack/salt-test-pillar-gitfs.git'" + ( + f"Set update lock for git_pillar remote " + f"'https://github.com/saltstack/salt-test-pillar-gitfs.git' on machine_id '{mach_id}'" + ) ], [], ) assert os.path.isfile(repo._get_lock_file()) assert repo.clear_lock() == ( [ - "Removed update lock for git_pillar remote 'https://github.com/saltstack/salt-test-pillar-gitfs.git'" + ( + f"Removed update lock for git_pillar remote " + f"'https://github.com/saltstack/salt-test-pillar-gitfs.git' on machine_id '{mach_id}'" + ) ], [], ) diff --git a/tests/pytests/functional/utils/test_winrepo.py b/tests/pytests/functional/utils/test_winrepo.py index 117d995bba6..35269f36f5e 100644 --- a/tests/pytests/functional/utils/test_winrepo.py +++ b/tests/pytests/functional/utils/test_winrepo.py @@ -5,6 +5,7 @@ import pytest from salt.runners.winrepo import GLOBAL_ONLY, PER_REMOTE_ONLY, PER_REMOTE_OVERRIDES from salt.utils.gitfs import GitPython, Pygit2, WinRepo from salt.utils.immutabletypes import ImmutableDict, ImmutableList +from salt.utils.platform import get_machine_identifier as _get_machine_identifier pytestmark = [ pytest.mark.slow_test, @@ -130,6 +131,7 @@ def test_pygit2_remote_map(pygit2_winrepo_opts): def _test_lock(opts): + mach_id = _get_machine_identifier().get("machine_id", "no_machine_id_available") w = _get_winrepo( opts, "https://github.com/saltstack/salt-test-pillar-gitfs.git", @@ -140,14 +142,18 @@ def _test_lock(opts): assert repo.get_salt_working_dir() in repo._get_lock_file() assert repo.lock() == ( [ - "Set update lock for winrepo remote 'https://github.com/saltstack/salt-test-pillar-gitfs.git'" + ( + f"Set update lock for winrepo remote 'https://github.com/saltstack/salt-test-pillar-gitfs.git' on machine_id '{mach_id}'" + ) ], [], ) assert os.path.isfile(repo._get_lock_file()) assert repo.clear_lock() == ( [ - "Removed update lock for winrepo remote 'https://github.com/saltstack/salt-test-pillar-gitfs.git'" + ( + f"Removed update lock for winrepo remote 'https://github.com/saltstack/salt-test-pillar-gitfs.git' on machine_id '{mach_id}'" + ) ], [], ) diff --git a/tests/pytests/integration/cli/test_salt.py b/tests/pytests/integration/cli/test_salt.py index 035c93dd427..37925160ca6 100644 --- a/tests/pytests/integration/cli/test_salt.py +++ b/tests/pytests/integration/cli/test_salt.py @@ -16,6 +16,7 @@ from pytestshellutils.utils.processes import ProcessResult, terminate_process import salt.defaults.exitcodes import salt.utils.path +from tests.conftest import FIPS_TESTRUN log = logging.getLogger(__name__) @@ -32,6 +33,11 @@ def salt_minion_2(salt_master): """ factory = salt_master.salt_minion_daemon( "minion-2", + overrides={ + "fips_mode": FIPS_TESTRUN, + "encryption_algorithm": "OAEP-SHA224" if FIPS_TESTRUN else "OAEP-SHA1", + "signing_algorithm": "PKCS1v15-SHA224" if FIPS_TESTRUN else "PKCS1v15-SHA1", + }, extra_cli_arguments_after_first_start_failure=["--log-level=info"], ) with factory.started(start_timeout=120): diff --git a/tests/pytests/integration/cli/test_salt_deltaproxy.py b/tests/pytests/integration/cli/test_salt_deltaproxy.py index 1d8eea0e997..84d64dacaec 100644 --- a/tests/pytests/integration/cli/test_salt_deltaproxy.py +++ b/tests/pytests/integration/cli/test_salt_deltaproxy.py @@ -10,6 +10,7 @@ from pytestshellutils.exceptions import FactoryNotStarted from saltfactories.utils import random_string import salt.defaults.exitcodes +from tests.conftest import FIPS_TESTRUN from tests.support.helpers import PRE_PYTEST_SKIP_REASON log = logging.getLogger(__name__) @@ -51,7 +52,15 @@ def test_exit_status_no_proxyid(salt_master, proxy_minion_id): with pytest.raises(FactoryNotStarted) as exc: factory = salt_master.salt_proxy_minion_daemon( - proxy_minion_id, include_proxyid_cli_flag=False, defaults=config_defaults + proxy_minion_id, + include_proxyid_cli_flag=False, + defaults=config_defaults, + overrides={ + "fips_mode": FIPS_TESTRUN, + "publish_signing_algorithm": ( + "PKCS1v15-SHA224" if FIPS_TESTRUN else "PKCS1v15-SHA1" + ), + }, ) factory.start(start_timeout=10, max_start_attempts=1) @@ -73,8 +82,15 @@ def test_exit_status_unknown_user(salt_master, proxy_minion_id): with pytest.raises(FactoryNotStarted) as exc: factory = salt_master.salt_proxy_minion_daemon( proxy_minion_id, - overrides={"user": "unknown-user"}, defaults=config_defaults, + overrides={ + "user": "unknown-user", + "fips_mode": FIPS_TESTRUN, + "encryption_algorithm": "OAEP-SHA224" if FIPS_TESTRUN else "OAEP-SHA1", + "signing_algorithm": ( + "PKCS1v15-SHA224" if FIPS_TESTRUN else "PKCS1v15-SHA1" + ), + }, ) factory.start(start_timeout=10, max_start_attempts=1) @@ -93,7 +109,15 @@ def test_exit_status_unknown_argument(salt_master, proxy_minion_id): with pytest.raises(FactoryNotStarted) as exc: factory = salt_master.salt_proxy_minion_daemon( - proxy_minion_id, defaults=config_defaults + proxy_minion_id, + defaults=config_defaults, + overrides={ + "fips_mode": FIPS_TESTRUN, + "encryption_algorithm": "OAEP-SHA224" if FIPS_TESTRUN else "OAEP-SHA1", + "signing_algorithm": ( + "PKCS1v15-SHA224" if FIPS_TESTRUN else "PKCS1v15-SHA1" + ), + }, ) factory.start("--unknown-argument", start_timeout=10, max_start_attempts=1) @@ -129,6 +153,11 @@ def test_exit_status_correct_usage( config_defaults = { "metaproxy": "deltaproxy", } + config_overrides = { + "fips_mode": FIPS_TESTRUN, + "encryption_algorithm": "OAEP-SHA224" if FIPS_TESTRUN else "OAEP-SHA1", + "signing_algorithm": "PKCS1v15-SHA224" if FIPS_TESTRUN else "PKCS1v15-SHA1", + } proxy_one = "dummy_proxy_one" proxy_two = "dummy_proxy_two" @@ -184,6 +213,7 @@ def test_exit_status_correct_usage( factory = salt_master.salt_proxy_minion_daemon( proxy_minion_id, defaults=config_defaults, + overrides=config_overrides, extra_cli_arguments_after_first_start_failure=["--log-level=info"], start_timeout=320, ) @@ -246,6 +276,11 @@ def test_missing_pillar_file( config_defaults = { "metaproxy": "deltaproxy", } + config_overrides = { + "fips_mode": FIPS_TESTRUN, + "encryption_algorithm": "OAEP-SHA224" if FIPS_TESTRUN else "OAEP-SHA1", + "signing_algorithm": "PKCS1v15-SHA224" if FIPS_TESTRUN else "PKCS1v15-SHA1", + } proxy_one = "dummy_proxy_one" proxy_two = "dummy_proxy_two" @@ -287,6 +322,7 @@ def test_missing_pillar_file( factory = salt_master.salt_proxy_minion_daemon( proxy_minion_id, defaults=config_defaults, + overrides=config_overrides, extra_cli_arguments_after_first_start_failure=["--log-level=info"], start_timeout=320, ) @@ -344,6 +380,11 @@ def test_invalid_connection( config_defaults = { "metaproxy": "deltaproxy", } + config_overrides = { + "fips_mode": FIPS_TESTRUN, + "encryption_algorithm": "OAEP-SHA224" if FIPS_TESTRUN else "OAEP-SHA1", + "signing_algorithm": "PKCS1v15-SHA224" if FIPS_TESTRUN else "PKCS1v15-SHA1", + } proxy_one = "dummy_proxy_one" broken_proxy_one = "broken_proxy_one" broken_proxy_two = "broken_proxy_two" @@ -415,6 +456,7 @@ def test_invalid_connection( factory = salt_master.salt_proxy_minion_daemon( proxy_minion_id, defaults=config_defaults, + overrides=config_overrides, extra_cli_arguments_after_first_start_failure=["--log-level=info"], start_timeout=320, ) @@ -474,6 +516,11 @@ def test_custom_proxy_module( config_defaults = { "metaproxy": "deltaproxy", } + config_overrides = { + "fips_mode": FIPS_TESTRUN, + "encryption_algorithm": "OAEP-SHA224" if FIPS_TESTRUN else "OAEP-SHA1", + "signing_algorithm": "PKCS1v15-SHA224" if FIPS_TESTRUN else "PKCS1v15-SHA1", + } proxy_one = "custom_dummy_proxy_one" proxy_two = "custom_dummy_proxy_two" @@ -548,6 +595,7 @@ def ping(): factory = salt_master.salt_proxy_minion_daemon( proxy_minion_id, defaults=config_defaults, + overrides=config_overrides, extra_cli_arguments_after_first_start_failure=["--log-level=info"], start_timeout=320, ) @@ -611,6 +659,11 @@ def test_custom_proxy_module_raise_exception( config_defaults = { "metaproxy": "deltaproxy", } + config_overrides = { + "fips_mode": FIPS_TESTRUN, + "encryption_algorithm": "OAEP-SHA224" if FIPS_TESTRUN else "OAEP-SHA1", + "signing_algorithm": "PKCS1v15-SHA224" if FIPS_TESTRUN else "PKCS1v15-SHA1", + } proxy_one = "custom_dummy_proxy_one" proxy_two = "custom_dummy_proxy_two" @@ -685,6 +738,7 @@ def ping(): factory = salt_master.salt_proxy_minion_daemon( proxy_minion_id, defaults=config_defaults, + overrides=config_overrides, extra_cli_arguments_after_first_start_failure=["--log-level=info"], start_timeout=320, ) @@ -750,6 +804,11 @@ def test_exit_status_correct_usage_large_number_of_minions( config_defaults = { "metaproxy": "deltaproxy", } + config_overrides = { + "fips_mode": FIPS_TESTRUN, + "encryption_algorithm": "OAEP-SHA224" if FIPS_TESTRUN else "OAEP-SHA1", + "signing_algorithm": "PKCS1v15-SHA224" if FIPS_TESTRUN else "PKCS1v15-SHA1", + } sub_proxies = [ "proxy_one", "proxy_two", @@ -826,6 +885,7 @@ def test_exit_status_correct_usage_large_number_of_minions( factory = salt_master.salt_proxy_minion_daemon( proxy_minion_id, defaults=config_defaults, + overrides=config_overrides, extra_cli_arguments_after_first_start_failure=["--log-level=info"], start_timeout=320, ) diff --git a/tests/pytests/integration/cli/test_salt_minion.py b/tests/pytests/integration/cli/test_salt_minion.py index c0d60134746..ad623bd30f5 100644 --- a/tests/pytests/integration/cli/test_salt_minion.py +++ b/tests/pytests/integration/cli/test_salt_minion.py @@ -6,6 +6,7 @@ from pytestshellutils.exceptions import FactoryNotStarted from saltfactories.utils import random_string import salt.defaults.exitcodes +from tests.conftest import FIPS_TESTRUN from tests.support.helpers import PRE_PYTEST_SKIP_REASON pytestmark = [ @@ -39,7 +40,15 @@ def test_exit_status_unknown_user(salt_master, minion_id): """ with pytest.raises(FactoryNotStarted) as exc: factory = salt_master.salt_minion_daemon( - minion_id, overrides={"user": "unknown-user"} + minion_id, + overrides={ + "user": "unknown-user", + "fips_mode": FIPS_TESTRUN, + "encryption_algorithm": "OAEP-SHA224" if FIPS_TESTRUN else "OAEP-SHA1", + "signing_algorithm": ( + "PKCS1v15-SHA224" if FIPS_TESTRUN else "PKCS1v15-SHA1" + ), + }, ) factory.start(start_timeout=10, max_start_attempts=1) @@ -52,7 +61,16 @@ def test_exit_status_unknown_argument(salt_master, minion_id): Ensure correct exit status when an unknown argument is passed to salt-minion. """ with pytest.raises(FactoryNotStarted) as exc: - factory = salt_master.salt_minion_daemon(minion_id) + factory = salt_master.salt_minion_daemon( + minion_id, + overrides={ + "fips_mode": FIPS_TESTRUN, + "encryption_algorithm": "OAEP-SHA224" if FIPS_TESTRUN else "OAEP-SHA1", + "signing_algorithm": ( + "PKCS1v15-SHA224" if FIPS_TESTRUN else "PKCS1v15-SHA1" + ), + }, + ) factory.start("--unknown-argument", start_timeout=10, max_start_attempts=1) assert exc.value.process_result.returncode == salt.defaults.exitcodes.EX_USAGE @@ -66,6 +84,11 @@ def test_exit_status_correct_usage(salt_master, minion_id, salt_cli): minion_id, extra_cli_arguments_after_first_start_failure=["--log-level=info"], defaults={"transport": salt_master.config["transport"]}, + overrides={ + "fips_mode": FIPS_TESTRUN, + "encryption_algorithm": "OAEP-SHA224" if FIPS_TESTRUN else "OAEP-SHA1", + "signing_algorithm": "PKCS1v15-SHA224" if FIPS_TESTRUN else "PKCS1v15-SHA1", + }, ) factory.start() assert factory.is_running() diff --git a/tests/pytests/integration/cli/test_salt_proxy.py b/tests/pytests/integration/cli/test_salt_proxy.py index fbf39e30438..37024d2d76a 100644 --- a/tests/pytests/integration/cli/test_salt_proxy.py +++ b/tests/pytests/integration/cli/test_salt_proxy.py @@ -9,6 +9,7 @@ from pytestshellutils.exceptions import FactoryNotStarted from saltfactories.utils import random_string import salt.defaults.exitcodes +from tests.conftest import FIPS_TESTRUN from tests.support.helpers import PRE_PYTEST_SKIP_REASON log = logging.getLogger(__name__) @@ -32,7 +33,15 @@ def test_exit_status_no_proxyid(salt_master, proxy_minion_id): """ with pytest.raises(FactoryNotStarted) as exc: factory = salt_master.salt_proxy_minion_daemon( - proxy_minion_id, include_proxyid_cli_flag=False + proxy_minion_id, + include_proxyid_cli_flag=False, + overrides={ + "fips_mode": FIPS_TESTRUN, + "encryption_algorithm": "OAEP-SHA224" if FIPS_TESTRUN else "OAEP-SHA1", + "signing_algorithm": ( + "PKCS1v15-SHA224" if FIPS_TESTRUN else "PKCS1v15-SHA1" + ), + }, ) factory.start(start_timeout=10, max_start_attempts=1) @@ -50,7 +59,15 @@ def test_exit_status_unknown_user(salt_master, proxy_minion_id): """ with pytest.raises(FactoryNotStarted) as exc: factory = salt_master.salt_proxy_minion_daemon( - proxy_minion_id, overrides={"user": "unknown-user"} + proxy_minion_id, + overrides={ + "user": "unknown-user", + "fips_mode": FIPS_TESTRUN, + "encryption_algorithm": "OAEP-SHA224" if FIPS_TESTRUN else "OAEP-SHA1", + "signing_algorithm": ( + "PKCS1v15-SHA224" if FIPS_TESTRUN else "PKCS1v15-SHA1" + ), + }, ) factory.start(start_timeout=10, max_start_attempts=1) @@ -65,7 +82,16 @@ def test_exit_status_unknown_argument(salt_master, proxy_minion_id): salt-proxy. """ with pytest.raises(FactoryNotStarted) as exc: - factory = salt_master.salt_proxy_minion_daemon(proxy_minion_id) + factory = salt_master.salt_proxy_minion_daemon( + proxy_minion_id, + overrides={ + "fips_mode": FIPS_TESTRUN, + "encryption_algorithm": "OAEP-SHA224" if FIPS_TESTRUN else "OAEP-SHA1", + "signing_algorithm": ( + "PKCS1v15-SHA224" if FIPS_TESTRUN else "PKCS1v15-SHA1" + ), + }, + ) factory.start("--unknown-argument", start_timeout=10, max_start_attempts=1) assert exc.value.process_result.returncode == salt.defaults.exitcodes.EX_USAGE @@ -86,6 +112,11 @@ def test_exit_status_correct_usage(salt_master, proxy_minion_id, salt_cli): proxy_minion_id, extra_cli_arguments_after_first_start_failure=["--log-level=info"], defaults={"transport": salt_master.config["transport"]}, + overrides={ + "fips_mode": FIPS_TESTRUN, + "encryption_algorithm": "OAEP-SHA224" if FIPS_TESTRUN else "OAEP-SHA1", + "signing_algorithm": "PKCS1v15-SHA224" if FIPS_TESTRUN else "PKCS1v15-SHA1", + }, ) factory.start() assert factory.is_running() diff --git a/tests/pytests/integration/cluster/conftest.py b/tests/pytests/integration/cluster/conftest.py index c62e034426e..4520ad55403 100644 --- a/tests/pytests/integration/cluster/conftest.py +++ b/tests/pytests/integration/cluster/conftest.py @@ -4,6 +4,7 @@ import subprocess import pytest import salt.utils.platform +from tests.conftest import FIPS_TESTRUN log = logging.getLogger(__name__) @@ -51,6 +52,10 @@ def cluster_master_1(request, salt_factories, cluster_pki_path, cluster_cache_pa "salt.channel": "debug", "salt.utils.event": "debug", }, + "fips_mode": FIPS_TESTRUN, + "publish_signing_algorithm": ( + "PKCS1v15-SHA224" if FIPS_TESTRUN else "PKCS1v15-SHA1" + ), } factory = salt_factories.salt_master_daemon( "127.0.0.1", @@ -86,6 +91,10 @@ def cluster_master_2(salt_factories, cluster_master_1): "salt.channel": "debug", "salt.utils.event": "debug", }, + "fips_mode": FIPS_TESTRUN, + "publish_signing_algorithm": ( + "PKCS1v15-SHA224" if FIPS_TESTRUN else "PKCS1v15-SHA1" + ), } # Use the same ports for both masters, they are binding to different interfaces @@ -128,6 +137,10 @@ def cluster_master_3(salt_factories, cluster_master_1): "salt.channel": "debug", "salt.utils.event": "debug", }, + "fips_mode": FIPS_TESTRUN, + "publish_signing_algorithm": ( + "PKCS1v15-SHA224" if FIPS_TESTRUN else "PKCS1v15-SHA1" + ), } # Use the same ports for both masters, they are binding to different interfaces @@ -163,6 +176,9 @@ def cluster_minion_1(cluster_master_1): "salt.channel": "debug", "salt.utils.event": "debug", }, + "fips_mode": FIPS_TESTRUN, + "encryption_algorithm": "OAEP-SHA224" if FIPS_TESTRUN else "OAEP-SHA1", + "signing_algorithm": "PKCS1v15-SHA224" if FIPS_TESTRUN else "PKCS1v15-SHA1", } factory = cluster_master_1.salt_minion_daemon( "cluster-minion-1", diff --git a/tests/pytests/integration/daemons/test_memory_leak.py b/tests/pytests/integration/daemons/test_memory_leak.py index 908f1f1e4f2..8461edeb558 100644 --- a/tests/pytests/integration/daemons/test_memory_leak.py +++ b/tests/pytests/integration/daemons/test_memory_leak.py @@ -48,7 +48,9 @@ def file_add_delete_sls(tmp_path, salt_master): yield sls_name -@pytest.mark.flaky(max_runs=4) +# This test is fundimentally flawed. Needs to be re-factored to test the memory +# consuption of the minoin process not system wide memory. +@pytest.mark.skip(reason="Flawed test") def test_memory_leak(salt_cli, salt_minion, file_add_delete_sls): max_usg = None diff --git a/tests/pytests/integration/master/test_peer.py b/tests/pytests/integration/master/test_peer.py index bec438f27a5..a9552060f78 100644 --- a/tests/pytests/integration/master/test_peer.py +++ b/tests/pytests/integration/master/test_peer.py @@ -3,6 +3,8 @@ import shutil import pytest from saltfactories.utils import random_string +from tests.conftest import FIPS_TESTRUN + @pytest.fixture(scope="module") def pillar_state_tree(tmp_path_factory): @@ -34,6 +36,12 @@ def peer_salt_master(salt_factories, pillar_state_tree, peer_salt_master_config) factory = salt_factories.salt_master_daemon( random_string("peer-comm-master", uppercase=False), defaults=peer_salt_master_config, + overrides={ + "fips_mode": FIPS_TESTRUN, + "publish_signing_algorithm": ( + "PKCS1v15-SHA224" if FIPS_TESTRUN else "PKCS1v15-SHA1" + ), + }, ) with factory.started(): yield factory @@ -45,6 +53,11 @@ def peer_salt_minion_1(peer_salt_master): factory = peer_salt_master.salt_minion_daemon( random_string("peer-comm-minion-1", uppercase=False), defaults={"open_mode": True, "grains": {"hello_peer": "beer"}}, + overrides={ + "fips_mode": FIPS_TESTRUN, + "encryption_algorithm": "OAEP-SHA224" if FIPS_TESTRUN else "OAEP-SHA1", + "signing_algorithm": "PKCS1v15-SHA224" if FIPS_TESTRUN else "PKCS1v15-SHA1", + }, ) with factory.started(): # Sync All @@ -60,6 +73,11 @@ def peer_salt_minion_2(peer_salt_master): factory = peer_salt_master.salt_minion_daemon( random_string("peer-comm-minion-2", uppercase=False), defaults={"open_mode": True}, + overrides={ + "fips_mode": FIPS_TESTRUN, + "encryption_algorithm": "OAEP-SHA224" if FIPS_TESTRUN else "OAEP-SHA1", + "signing_algorithm": "PKCS1v15-SHA224" if FIPS_TESTRUN else "PKCS1v15-SHA1", + }, ) with factory.started(): # Sync All @@ -75,6 +93,11 @@ def peer_salt_minion_3(peer_salt_master): factory = peer_salt_master.salt_minion_daemon( random_string("peer-comm-minion-3", uppercase=False), defaults={"open_mode": True}, + overrides={ + "fips_mode": FIPS_TESTRUN, + "encryption_algorithm": "OAEP-SHA224" if FIPS_TESTRUN else "OAEP-SHA1", + "signing_algorithm": "PKCS1v15-SHA224" if FIPS_TESTRUN else "PKCS1v15-SHA1", + }, ) with factory.started(): # Sync All diff --git a/tests/pytests/integration/minion/test_job_return.py b/tests/pytests/integration/minion/test_job_return.py index dc345eb2771..c91748597dc 100644 --- a/tests/pytests/integration/minion/test_job_return.py +++ b/tests/pytests/integration/minion/test_job_return.py @@ -5,6 +5,7 @@ import subprocess import pytest import salt.utils.platform +from tests.conftest import FIPS_TESTRUN @pytest.fixture @@ -15,6 +16,10 @@ def salt_master_1(request, salt_factories): } config_overrides = { "interface": "127.0.0.1", + "fips_mode": FIPS_TESTRUN, + "publish_signing_algorithm": ( + "PKCS1v15-SHA224" if FIPS_TESTRUN else "PKCS1v15-SHA1" + ), } factory = salt_factories.salt_master_daemon( @@ -38,6 +43,10 @@ def salt_master_2(salt_factories, salt_master_1): } config_overrides = { "interface": "127.0.0.2", + "fips_mode": FIPS_TESTRUN, + "publish_signing_algorithm": ( + "PKCS1v15-SHA224" if FIPS_TESTRUN else "PKCS1v15-SHA1" + ), } # Use the same ports for both masters, they are binding to different interfaces @@ -80,6 +89,9 @@ def salt_minion_1(salt_master_1, salt_master_2): f"{master_2_addr}:{master_2_port}", ], "test.foo": "baz", + "fips_mode": FIPS_TESTRUN, + "encryption_algorithm": "OAEP-SHA224" if FIPS_TESTRUN else "OAEP-SHA1", + "signing_algorithm": "PKCS1v15-SHA224" if FIPS_TESTRUN else "PKCS1v15-SHA1", } factory = salt_master_1.salt_minion_daemon( "minion-1", diff --git a/tests/pytests/integration/minion/test_reauth.py b/tests/pytests/integration/minion/test_reauth.py index 24b256502e8..2e9962a087c 100644 --- a/tests/pytests/integration/minion/test_reauth.py +++ b/tests/pytests/integration/minion/test_reauth.py @@ -1,5 +1,7 @@ import time +from tests.conftest import FIPS_TESTRUN + def test_reauth(salt_master_factory, event_listener): """ @@ -23,12 +25,23 @@ def test_reauth(salt_master_factory, event_listener): event_listener.register_auth_event_handler("test_reauth-master", handler) master = salt_master_factory.salt_master_daemon( "test_reauth-master", - overrides={"log_level": "info"}, + overrides={ + "log_level": "info", + "fips_mode": FIPS_TESTRUN, + "publish_signing_algorithm": ( + "PKCS1v15-SHA224" if FIPS_TESTRUN else "PKCS1v15-SHA1" + ), + }, ) sls_tempfile = master.state_tree.base.temp_file(f"{sls_name}.sls", sls_contents) minion = master.salt_minion_daemon( "test_reauth-minion", - overrides={"log_level": "info"}, + overrides={ + "log_level": "info", + "fips_mode": FIPS_TESTRUN, + "encryption_algorithm": "OAEP-SHA224" if FIPS_TESTRUN else "OAEP-SHA1", + "signing_algorithm": "PKCS1v15-SHA224" if FIPS_TESTRUN else "PKCS1v15-SHA1", + }, ) cli = master.salt_cli() start_time = time.time() diff --git a/tests/pytests/integration/minion/test_return_retries.py b/tests/pytests/integration/minion/test_return_retries.py index be7885e7b44..fcd73b0a46e 100644 --- a/tests/pytests/integration/minion/test_return_retries.py +++ b/tests/pytests/integration/minion/test_return_retries.py @@ -4,6 +4,7 @@ import pytest from saltfactories.utils import random_string import salt.utils.files +from tests.conftest import FIPS_TESTRUN @pytest.fixture(scope="function") @@ -13,6 +14,9 @@ def salt_minion_retry(salt_master, salt_minion_id): "return_retry_timer_max": 0, "return_retry_timer": 5, "return_retry_tries": 30, + "fips_mode": FIPS_TESTRUN, + "encryption_algorithm": "OAEP-SHA224" if FIPS_TESTRUN else "OAEP-SHA1", + "signing_algorithm": "PKCS1v15-SHA224" if FIPS_TESTRUN else "PKCS1v15-SHA1", } factory = salt_master.salt_minion_daemon( random_string("retry-minion-"), @@ -71,11 +75,18 @@ def test_pillar_timeout(salt_master_factory, tmp_path): "worker_threads": 2, "peer": True, "minion_data_cache": False, + "fips_mode": FIPS_TESTRUN, + "publish_signing_algorithm": ( + "PKCS1v15-SHA224" if FIPS_TESTRUN else "PKCS1v15-SHA1" + ), } minion_overrides = { "auth_timeout": 20, "request_channel_timeout": 5, "request_channel_tries": 1, + "fips_mode": FIPS_TESTRUN, + "encryption_algorithm": "OAEP-SHA224" if FIPS_TESTRUN else "OAEP-SHA1", + "signing_algorithm": "PKCS1v15-SHA224" if FIPS_TESTRUN else "PKCS1v15-SHA1", } sls_name = "issue-50221" sls_contents = """ diff --git a/tests/pytests/integration/modules/test_config.py b/tests/pytests/integration/modules/test_config.py new file mode 100644 index 00000000000..afdf4706050 --- /dev/null +++ b/tests/pytests/integration/modules/test_config.py @@ -0,0 +1,8 @@ +import pytest + + +@pytest.mark.slow_test +def test_config_items(salt_cli, salt_minion): + ret = salt_cli.run("config.items", minion_tgt=salt_minion.id) + assert ret.returncode == 0 + assert isinstance(ret.data, dict) diff --git a/tests/pytests/integration/modules/test_x509_v2.py b/tests/pytests/integration/modules/test_x509_v2.py index d0febc10cf1..09448135b8d 100644 --- a/tests/pytests/integration/modules/test_x509_v2.py +++ b/tests/pytests/integration/modules/test_x509_v2.py @@ -12,6 +12,7 @@ import pytest from saltfactories.utils import random_string import salt.utils.x509 as x509util +from tests.conftest import FIPS_TESTRUN try: import cryptography @@ -60,7 +61,14 @@ def x509_data( @pytest.fixture(scope="module") def x509_salt_master(salt_factories, ca_minion_id, x509_master_config): factory = salt_factories.salt_master_daemon( - "x509-master", defaults=x509_master_config + "x509-master", + defaults=x509_master_config, + overrides={ + "fips_mode": FIPS_TESTRUN, + "publish_signing_algorithm": ( + "PKCS1v15-SHA224" if FIPS_TESTRUN else "PKCS1v15-SHA1" + ), + }, ) with factory.started(): yield factory @@ -169,6 +177,11 @@ def x509ca_salt_minion(x509_salt_master, ca_minion_id, ca_minion_config): factory = x509_salt_master.salt_minion_daemon( ca_minion_id, defaults=ca_minion_config, + overrides={ + "fips_mode": FIPS_TESTRUN, + "encryption_algorithm": "OAEP-SHA224" if FIPS_TESTRUN else "OAEP-SHA1", + "signing_algorithm": "PKCS1v15-SHA224" if FIPS_TESTRUN else "PKCS1v15-SHA1", + }, ) with factory.started(): # Sync All @@ -187,6 +200,11 @@ def x509_salt_minion(x509_salt_master, x509_minion_id): "open_mode": True, "grains": {"testgrain": "foo"}, }, + overrides={ + "fips_mode": FIPS_TESTRUN, + "encryption_algorithm": "OAEP-SHA224" if FIPS_TESTRUN else "OAEP-SHA1", + "signing_algorithm": "PKCS1v15-SHA224" if FIPS_TESTRUN else "PKCS1v15-SHA1", + }, ) with factory.started(): # Sync All diff --git a/tests/pytests/integration/pillar/cache/conftest.py b/tests/pytests/integration/pillar/cache/conftest.py index 54850985575..ca4ba16b2ba 100644 --- a/tests/pytests/integration/pillar/cache/conftest.py +++ b/tests/pytests/integration/pillar/cache/conftest.py @@ -2,6 +2,8 @@ import shutil import pytest +from tests.conftest import FIPS_TESTRUN + @pytest.fixture(scope="package") def pillar_state_tree(tmp_path_factory): @@ -22,8 +24,16 @@ def pillar_salt_master(salt_factories, pillar_state_tree): {"extra_minion_data_in_pillar": "*"}, ], } + config_overrides = { + "fips_mode": FIPS_TESTRUN, + "publish_signing_algorithm": ( + "PKCS1v15-SHA224" if FIPS_TESTRUN else "PKCS1v15-SHA1" + ), + } factory = salt_factories.salt_master_daemon( - "pillar-cache-functional-master", defaults=config_defaults + "pillar-cache-functional-master", + defaults=config_defaults, + overrides=config_overrides, ) with factory.started(): yield factory @@ -32,9 +42,15 @@ def pillar_salt_master(salt_factories, pillar_state_tree): @pytest.fixture(scope="package") def pillar_salt_minion(pillar_salt_master): assert pillar_salt_master.is_running() + config_overrides = { + "fips_mode": FIPS_TESTRUN, + "encryption_algorithm": "OAEP-SHA224" if FIPS_TESTRUN else "OAEP-SHA1", + "signing_algorithm": "PKCS1v15-SHA224" if FIPS_TESTRUN else "PKCS1v15-SHA1", + } factory = pillar_salt_master.salt_minion_daemon( "pillar-cache-functional-minion-1", defaults={"open_mode": True, "hi": "there", "pass_to_ext_pillars": ["hi"]}, + overrides=config_overrides, ) with factory.started(): # Sync All diff --git a/tests/pytests/integration/pillar/test_httpclient_in_pillar.py b/tests/pytests/integration/pillar/test_httpclient_in_pillar.py new file mode 100644 index 00000000000..905a8f51cb3 --- /dev/null +++ b/tests/pytests/integration/pillar/test_httpclient_in_pillar.py @@ -0,0 +1,29 @@ +def test_pillar_using_http_query(salt_master, salt_minion, salt_cli, tmp_path): + pillar_top = """ + base: + "*": + - http_pillar_test + """ + my_pillar = """ + {%- set something = salt['http.query']('https://raw.githubusercontent.com/saltstack/salt/master/.pre-commit-config.yaml', raise_error=False, verify_ssl=False, status=True, timeout=5).status %} + http_query_test: {{ something }} + """ + + with salt_master.pillar_tree.base.temp_file("top.sls", pillar_top): + with salt_master.pillar_tree.base.temp_file("http_pillar_test.sls", my_pillar): + with salt_master.pillar_tree.base.temp_file( + "http_pillar_test.sls", my_pillar + ): + ret = salt_cli.run("state.apply", minion_tgt=salt_minion.id) + assert ret.returncode == 1 + assert ( + ret.data["no_|-states_|-states_|-None"]["comment"] + == "No states found for this minion" + ) + + pillar_ret = salt_cli.run( + "pillar.item", "http_query_test", minion_tgt=salt_minion.id + ) + assert pillar_ret.returncode == 0 + + assert '"http_query_test": 200' in pillar_ret.stdout diff --git a/tests/pytests/integration/runners/test_match.py b/tests/pytests/integration/runners/test_match.py index 7a76ff59efe..2ae7409c0a6 100644 --- a/tests/pytests/integration/runners/test_match.py +++ b/tests/pytests/integration/runners/test_match.py @@ -6,6 +6,8 @@ import logging import pytest +from tests.conftest import FIPS_TESTRUN + log = logging.getLogger(__name__) pytestmark = [ @@ -70,7 +72,14 @@ def pillar_tree(match_salt_master, match_salt_minion_alice, match_salt_minion_ev @pytest.fixture(scope="class") def match_salt_master(salt_factories, match_master_config): factory = salt_factories.salt_master_daemon( - "match-master", defaults=match_master_config + "match-master", + defaults=match_master_config, + overrides={ + "fips_mode": FIPS_TESTRUN, + "publish_signing_algorithm": ( + "PKCS1v15-SHA224" if FIPS_TESTRUN else "PKCS1v15-SHA1" + ), + }, ) with factory.started(): yield factory @@ -82,6 +91,11 @@ def match_salt_minion_alice(match_salt_master): factory = match_salt_master.salt_minion_daemon( "match-minion-alice", defaults={"open_mode": True, "grains": {"role": "alice"}}, + overrides={ + "fips_mode": FIPS_TESTRUN, + "encryption_algorithm": "OAEP-SHA224" if FIPS_TESTRUN else "OAEP-SHA1", + "signing_algorithm": "PKCS1v15-SHA224" if FIPS_TESTRUN else "PKCS1v15-SHA1", + }, ) with factory.started(): # Sync All @@ -97,6 +111,11 @@ def match_salt_minion_eve(match_salt_master): factory = match_salt_master.salt_minion_daemon( "match-minion-eve", defaults={"open_mode": True, "grains": {"role": "eve"}}, + overrides={ + "fips_mode": FIPS_TESTRUN, + "encryption_algorithm": "OAEP-SHA224" if FIPS_TESTRUN else "OAEP-SHA1", + "signing_algorithm": "PKCS1v15-SHA224" if FIPS_TESTRUN else "PKCS1v15-SHA1", + }, ) with factory.started(): # Sync All @@ -112,6 +131,11 @@ def match_salt_minion_bob(match_salt_master): factory = match_salt_master.salt_minion_daemon( "match-minion-bob", defaults={"open_mode": True}, + overrides={ + "fips_mode": FIPS_TESTRUN, + "encryption_algorithm": "OAEP-SHA224" if FIPS_TESTRUN else "OAEP-SHA1", + "signing_algorithm": "PKCS1v15-SHA224" if FIPS_TESTRUN else "PKCS1v15-SHA1", + }, ) with factory.started(): # Sync All diff --git a/tests/pytests/integration/states/test_file.py b/tests/pytests/integration/states/test_file.py index 5966f778208..555f2a6116c 100644 --- a/tests/pytests/integration/states/test_file.py +++ b/tests/pytests/integration/states/test_file.py @@ -17,6 +17,7 @@ import salt.utils.files import salt.utils.path import salt.utils.platform from salt.utils.versions import Version +from tests.conftest import FIPS_TESTRUN log = logging.getLogger(__name__) @@ -236,6 +237,10 @@ def salt_secondary_master(request, salt_factories): "fileserver_followsymlinks": False, "publish_port": publish_port, "ret_port": ret_port, + "fips_mode": FIPS_TESTRUN, + "publish_signing_algorithm": ( + "PKCS1v15-SHA224" if FIPS_TESTRUN else "PKCS1v15-SHA1" + ), } factory = salt_factories.salt_master_daemon( @@ -258,6 +263,9 @@ def salt_secondary_minion(salt_secondary_master): config_overrides = { "master": salt_secondary_master.config["interface"], "master_port": salt_secondary_master.config["ret_port"], + "fips_mode": FIPS_TESTRUN, + "encryption_algorithm": "OAEP-SHA224" if FIPS_TESTRUN else "OAEP-SHA1", + "signing_algorithm": "PKCS1v15-SHA224" if FIPS_TESTRUN else "PKCS1v15-SHA1", } factory = salt_secondary_master.salt_minion_daemon( diff --git a/tests/pytests/integration/states/test_x509_v2.py b/tests/pytests/integration/states/test_x509_v2.py index 15c9771e709..05889c08a58 100644 --- a/tests/pytests/integration/states/test_x509_v2.py +++ b/tests/pytests/integration/states/test_x509_v2.py @@ -11,6 +11,7 @@ import pytest from saltfactories.utils import random_string import salt.utils.x509 as x509util +from tests.conftest import FIPS_TESTRUN try: import cryptography @@ -123,8 +124,14 @@ def x509_data( @pytest.fixture(scope="module") def x509_salt_master(salt_factories, ca_minion_id, x509_master_config): + config_overrides = { + "fips_mode": FIPS_TESTRUN, + "publish_signing_algorithm": ( + "PKCS1v15-SHA224" if FIPS_TESTRUN else "PKCS1v15-SHA1" + ), + } factory = salt_factories.salt_master_daemon( - "x509-master", defaults=x509_master_config + "x509-master", defaults=x509_master_config, overrides=config_overrides ) with factory.started(): yield factory @@ -181,9 +188,15 @@ def ca_minion_config(x509_minion_id, ca_cert, ca_key_enc, rsa_privkey, ca_new_ce @pytest.fixture(scope="module", autouse=True) def x509ca_salt_minion(x509_salt_master, ca_minion_id, ca_minion_config): assert x509_salt_master.is_running() + config_overrides = { + "fips_mode": FIPS_TESTRUN, + "encryption_algorithm": "OAEP-SHA224" if FIPS_TESTRUN else "OAEP-SHA1", + "signing_algorithm": "PKCS1v15-SHA224" if FIPS_TESTRUN else "PKCS1v15-SHA1", + } factory = x509_salt_master.salt_minion_daemon( ca_minion_id, defaults=ca_minion_config, + overrides=config_overrides, ) with factory.started(): # Sync All @@ -196,12 +209,18 @@ def x509ca_salt_minion(x509_salt_master, ca_minion_id, ca_minion_config): @pytest.fixture(scope="module") def x509_salt_minion(x509_salt_master, x509_minion_id): assert x509_salt_master.is_running() + config_overrides = { + "fips_mode": FIPS_TESTRUN, + "encryption_algorithm": "OAEP-SHA224" if FIPS_TESTRUN else "OAEP-SHA1", + "signing_algorithm": "PKCS1v15-SHA224" if FIPS_TESTRUN else "PKCS1v15-SHA1", + } factory = x509_salt_master.salt_minion_daemon( x509_minion_id, defaults={ "open_mode": True, "grains": {"testgrain": "foo"}, }, + overrides=config_overrides, ) with factory.started(): # Sync All diff --git a/tests/pytests/pkg/conftest.py b/tests/pytests/pkg/conftest.py index 5bcd544c119..59d02c3a60d 100644 --- a/tests/pytests/pkg/conftest.py +++ b/tests/pytests/pkg/conftest.py @@ -12,7 +12,7 @@ from saltfactories.utils import random_string import salt.config import salt.utils.files -from tests.conftest import CODE_DIR +from tests.conftest import CODE_DIR, FIPS_TESTRUN from tests.support.pkg import ApiRequest, SaltMaster, SaltMasterWindows, SaltPkgInstall log = logging.getLogger(__name__) @@ -298,6 +298,9 @@ def salt_master(salt_factories, install_salt, pkg_tests_account): }, }, "fips_mode": FIPS_TESTRUN, + "publish_signing_algorithm": ( + "PKCS1v15-SHA224" if FIPS_TESTRUN else "PKCS1v15-SHA1" + ), "open_mode": True, } salt_user_in_config_file = False @@ -449,6 +452,8 @@ def salt_minion(salt_factories, salt_master, install_salt): "file_roots": salt_master.config["file_roots"].copy(), "pillar_roots": salt_master.config["pillar_roots"].copy(), "fips_mode": FIPS_TESTRUN, + "encryption_algorithm": "OAEP-SHA224" if FIPS_TESTRUN else "OAEP-SHA1", + "signing_algorithm": "PKCS1v15-SHA224" if FIPS_TESTRUN else "PKCS1v15-SHA1", "open_mode": True, } if platform.is_windows(): diff --git a/tests/pytests/pkg/integration/test_pkg_meta.py b/tests/pytests/pkg/integration/test_pkg_meta.py new file mode 100644 index 00000000000..078b07f6518 --- /dev/null +++ b/tests/pytests/pkg/integration/test_pkg_meta.py @@ -0,0 +1,129 @@ +import subprocess + +import packaging +import pytest +from pytestskipmarkers.utils import platform + +import salt.utils.path +from tests.support.pkg import ARTIFACTS_DIR + + +@pytest.fixture +def pkg_arch(): + if platform.is_aarch64(): + return "aarch64" + else: + return "x86_64" + + +@pytest.fixture +def provides_arch(): + if platform.is_aarch64(): + return "aarch-64" + else: + return "x86-64" + + +@pytest.fixture +def rpm_version(): + proc = subprocess.run(["rpm", "--version"], capture_output=True, check=True) + return packaging.version.Version(proc.stdout.decode().rsplit(" ", 1)[-1]) + + +@pytest.fixture +def required_version(): + return packaging.version.Version("4.12") + + +@pytest.fixture +def artifact_version(install_salt): + return install_salt.artifact_version + + +@pytest.fixture +def package(artifact_version, pkg_arch): + name = f"salt-{artifact_version}-0.{pkg_arch}.rpm" + return ARTIFACTS_DIR / name + + +@pytest.mark.skipif(not salt.utils.path.which("rpm"), reason="rpm is not installed") +def test_provides( + install_salt, + package, + artifact_version, + provides_arch, + rpm_version, + required_version, +): + if install_salt.distro_id not in ( + "almalinux", + "rocky", + "centos", + "redhat", + "amzn", + "fedora", + "photon", + ): + pytest.skip("Only tests rpm packages") + if rpm_version < required_version: + pytest.skip(f"Test requires rpm version {required_version}") + + assert package.exists() + valid_provides = [ + f"config: config(salt) = {artifact_version}-0", + f"manual: salt = {artifact_version}", + f"manual: salt = {artifact_version}-0", + f"manual: salt({provides_arch}) = {artifact_version}-0", + ] + proc = subprocess.run( + ["rpm", "-q", "-v", "-provides", package], capture_output=True, check=True + ) + for line in proc.stdout.decode().splitlines(): + # If we have a provide that does not contain the word "salt" we should + # fail. + assert "salt" in line + # Check sepecific provide lines. + assert line in valid_provides + + +@pytest.mark.skipif(not salt.utils.path.which("rpm"), reason="rpm is not installed") +def test_requires( + install_salt, package, artifact_version, rpm_version, required_version +): + if install_salt.distro_id not in ( + "almalinux", + "rocky", + "centos", + "redhat", + "amzn", + "fedora", + "photon", + ): + pytest.skip("Only tests rpm packages") + if rpm_version < required_version: + pytest.skip(f"Test requires rpm version {required_version}") + assert package.exists() + valid_requires = [ + "manual: /bin/sh", + "pre,interp: /bin/sh", + "post,interp: /bin/sh", + "preun,interp: /bin/sh", + "manual: /usr/sbin/groupadd", + "manual: /usr/sbin/useradd", + "manual: /usr/sbin/usermod", + f"config: config(salt) = {artifact_version}-0", + "manual: dmidecode", + "manual: openssl", + "manual: pciutils", + # Not sure how often these will change, if this check causes things to + # break often we'll want to re-factor. + "rpmlib: rpmlib(CompressedFileNames) <= 3.0.4-1", + "rpmlib: rpmlib(FileDigests) <= 4.6.0-1", + "rpmlib: rpmlib(PayloadFilesHavePrefix) <= 4.0-1", + "manual: which", + ] + proc = subprocess.run( + ["rpm", "-q", "-v", "-requires", package], capture_output=True, check=True + ) + for line in proc.stdout.decode().splitlines(): + assert line in valid_requires diff --git a/tests/pytests/scenarios/blackout/conftest.py b/tests/pytests/scenarios/blackout/conftest.py index a75c20f30ed..52e21b79504 100644 --- a/tests/pytests/scenarios/blackout/conftest.py +++ b/tests/pytests/scenarios/blackout/conftest.py @@ -5,6 +5,8 @@ import time import attr import pytest +from tests.conftest import FIPS_TESTRUN + @attr.s class BlackoutPillar: @@ -126,9 +128,17 @@ def salt_master(salt_factories, pillar_state_tree): "pillar_roots": {"base": [str(pillar_state_tree)]}, "open_mode": True, } + config_overrides = { + "interface": "127.0.0.1", + "fips_mode": FIPS_TESTRUN, + "publish_signing_algorithm": ( + "PKCS1v15-SHA224" if FIPS_TESTRUN else "PKCS1v15-SHA1" + ), + } factory = salt_factories.salt_master_daemon( "blackout-master", defaults=config_defaults, + overrides=config_overrides, extra_cli_arguments_after_first_start_failure=["--log-level=info"], ) with factory.started(): @@ -138,7 +148,13 @@ def salt_master(salt_factories, pillar_state_tree): @pytest.fixture(scope="package") def salt_minion_1(salt_master): factory = salt_master.salt_minion_daemon( - "blackout-minion-1", defaults={"open_mode": True} + "blackout-minion-1", + defaults={"open_mode": True}, + overrides={ + "fips_mode": FIPS_TESTRUN, + "encryption_algorithm": "OAEP-SHA224" if FIPS_TESTRUN else "OAEP-SHA1", + "signing_algorithm": "PKCS1v15-SHA224" if FIPS_TESTRUN else "PKCS1v15-SHA1", + }, ) with factory.started(): yield factory @@ -147,7 +163,13 @@ def salt_minion_1(salt_master): @pytest.fixture(scope="package") def salt_minion_2(salt_master): factory = salt_master.salt_minion_daemon( - "blackout-minion-2", defaults={"open_mode": True} + "blackout-minion-2", + defaults={"open_mode": True}, + overrides={ + "fips_mode": FIPS_TESTRUN, + "encryption_algorithm": "OAEP-SHA224" if FIPS_TESTRUN else "OAEP-SHA1", + "signing_algorithm": "PKCS1v15-SHA224" if FIPS_TESTRUN else "PKCS1v15-SHA1", + }, ) with factory.started(): yield factory diff --git a/tests/pytests/scenarios/compat/conftest.py b/tests/pytests/scenarios/compat/conftest.py index e42c4c9259a..46e11d40bdf 100644 --- a/tests/pytests/scenarios/compat/conftest.py +++ b/tests/pytests/scenarios/compat/conftest.py @@ -14,6 +14,7 @@ from saltfactories.daemons.container import Container from saltfactories.utils import random_string import salt.utils.path +from tests.conftest import FIPS_TESTRUN from tests.support.runtests import RUNTIME_VARS from tests.support.sminion import create_sminion @@ -135,6 +136,10 @@ def salt_master( "log_level_logfile": "quiet", # We also want to scrutinize the key acceptance "open_mode": False, + "fips_mode": FIPS_TESTRUN, + "publish_signing_algorithm": ( + "PKCS1v15-SHA224" if FIPS_TESTRUN else "PKCS1v15-SHA1" + ), } # We need to copy the extension modules into the new master root_dir or diff --git a/tests/pytests/scenarios/compat/test_with_versions.py b/tests/pytests/scenarios/compat/test_with_versions.py index d9ba733839f..60c486864a8 100644 --- a/tests/pytests/scenarios/compat/test_with_versions.py +++ b/tests/pytests/scenarios/compat/test_with_versions.py @@ -13,6 +13,7 @@ from saltfactories.daemons.container import SaltMinion from saltfactories.utils import random_string import salt.utils.platform +from tests.conftest import FIPS_TESTRUN from tests.support.runtests import RUNTIME_VARS docker = pytest.importorskip("docker") @@ -78,6 +79,9 @@ def salt_minion( }, # We also want to scrutinize the key acceptance "open_mode": False, + "fips_mode": FIPS_TESTRUN, + "encryption_algorithm": "OAEP-SHA224" if FIPS_TESTRUN else "OAEP-SHA1", + "signing_algorithm": "PKCS1v15-SHA224" if FIPS_TESTRUN else "PKCS1v15-SHA1", } factory = salt_master.salt_minion_daemon( minion_id, @@ -149,12 +153,14 @@ def populated_state_tree(minion_id, package_name, state_tree): yield +@pytest.mark.skip_on_fips_enabled_platform def test_ping(salt_cli, salt_minion): ret = salt_cli.run("test.ping", minion_tgt=salt_minion.id) assert ret.returncode == 0, ret assert ret.data is True +@pytest.mark.skip_on_fips_enabled_platform @pytest.mark.usefixtures("populated_state_tree") def test_highstate(salt_cli, salt_minion, package_name): """ @@ -168,6 +174,7 @@ def test_highstate(salt_cli, salt_minion, package_name): assert package_name in state_return["changes"], state_return +@pytest.mark.skip_on_fips_enabled_platform @pytest.fixture def cp_file_source(): source = pathlib.Path(RUNTIME_VARS.BASE_FILES) / "cheese" @@ -176,6 +183,7 @@ def cp_file_source(): yield pathlib.Path(temp_file) +@pytest.mark.skip_on_fips_enabled_platform def test_cp(salt_cp_cli, salt_minion, artifacts_path, cp_file_source): """ Assert proper behaviour for salt-cp with a newer master and older minions. diff --git a/tests/pytests/scenarios/daemons/conftest.py b/tests/pytests/scenarios/daemons/conftest.py index 2433376d34c..634314a2a9d 100644 --- a/tests/pytests/scenarios/daemons/conftest.py +++ b/tests/pytests/scenarios/daemons/conftest.py @@ -1,6 +1,8 @@ import pytest from saltfactories.utils import random_string +from tests.conftest import FIPS_TESTRUN + @pytest.fixture(scope="package") def salt_master_factory(request, salt_factories): @@ -10,6 +12,10 @@ def salt_master_factory(request, salt_factories): } config_overrides = { "interface": "127.0.0.1", + "fips_mode": FIPS_TESTRUN, + "publish_signing_algorithm": ( + "PKCS1v15-SHA224" if FIPS_TESTRUN else "PKCS1v15-SHA1" + ), } return salt_factories.salt_master_daemon( diff --git a/tests/pytests/scenarios/dns/conftest.py b/tests/pytests/scenarios/dns/conftest.py index 254e8ee9a28..cfa4efda125 100644 --- a/tests/pytests/scenarios/dns/conftest.py +++ b/tests/pytests/scenarios/dns/conftest.py @@ -4,6 +4,8 @@ import subprocess import pytest +from tests.conftest import FIPS_TESTRUN + log = logging.getLogger(__name__) @@ -53,6 +55,10 @@ def master(request, salt_factories): } config_overrides = { "interface": "0.0.0.0", + "fips_mode": FIPS_TESTRUN, + "publish_signing_algorithm": ( + "PKCS1v15-SHA224" if FIPS_TESTRUN else "PKCS1v15-SHA1" + ), } factory = salt_factories.salt_master_daemon( "master", @@ -84,6 +90,9 @@ def minion(master, master_alive_interval): "master": f"master.local:{port}", "publish_port": master.config["publish_port"], "master_alive_interval": master_alive_interval, + "fips_mode": FIPS_TESTRUN, + "encryption_algorithm": "OAEP-SHA224" if FIPS_TESTRUN else "OAEP-SHA1", + "signing_algorithm": "PKCS1v15-SHA224" if FIPS_TESTRUN else "PKCS1v15-SHA1", } factory = master.salt_minion_daemon( "minion", diff --git a/tests/pytests/scenarios/dns/multimaster/conftest.py b/tests/pytests/scenarios/dns/multimaster/conftest.py index 3b50ed65c60..cda109d5a16 100644 --- a/tests/pytests/scenarios/dns/multimaster/conftest.py +++ b/tests/pytests/scenarios/dns/multimaster/conftest.py @@ -5,6 +5,8 @@ import subprocess import pytest +from tests.conftest import FIPS_TESTRUN + log = logging.getLogger(__name__) @@ -20,6 +22,10 @@ def salt_mm_master_1(request, salt_factories): config_overrides = { "interface": "0.0.0.0", "master_sign_pubkey": True, + "fips_mode": FIPS_TESTRUN, + "publish_signing_algorithm": ( + "PKCS1v15-SHA224" if FIPS_TESTRUN else "PKCS1v15-SHA1" + ), } factory = salt_factories.salt_master_daemon( "mm-master-1", @@ -55,6 +61,10 @@ def salt_mm_master_2(salt_factories, salt_mm_master_1): config_overrides = { "interface": "0.0.0.0", "master_sign_pubkey": True, + "fips_mode": FIPS_TESTRUN, + "publish_signing_algorithm": ( + "PKCS1v15-SHA224" if FIPS_TESTRUN else "PKCS1v15-SHA1" + ), } # Use the same ports for both masters, they are binding to different interfaces @@ -103,6 +113,9 @@ def salt_mm_minion_1(salt_mm_master_1, salt_mm_master_2, master_alive_interval): "master_tries": -1, "verify_master_pubkey_sign": True, "retry_dns": True, + "fips_mode": FIPS_TESTRUN, + "encryption_algorithm": "OAEP-SHA224" if FIPS_TESTRUN else "OAEP-SHA1", + "signing_algorithm": "PKCS1v15-SHA224" if FIPS_TESTRUN else "PKCS1v15-SHA1", } factory = salt_mm_master_1.salt_minion_daemon( "mm-minion-1", diff --git a/tests/pytests/scenarios/failover/multimaster/conftest.py b/tests/pytests/scenarios/failover/multimaster/conftest.py index 970c1e59137..3c8f89a8ba7 100644 --- a/tests/pytests/scenarios/failover/multimaster/conftest.py +++ b/tests/pytests/scenarios/failover/multimaster/conftest.py @@ -8,6 +8,7 @@ import pytest from pytestshellutils.exceptions import FactoryNotStarted, FactoryTimeout import salt.utils.platform +from tests.conftest import FIPS_TESTRUN log = logging.getLogger(__name__) @@ -21,6 +22,10 @@ def salt_mm_failover_master_1(request, salt_factories): config_overrides = { "interface": "127.0.0.1", "master_sign_pubkey": True, + "fips_mode": FIPS_TESTRUN, + "publish_signing_algorithm": ( + "PKCS1v15-SHA224" if FIPS_TESTRUN else "PKCS1v15-SHA1" + ), } factory = salt_factories.salt_master_daemon( "mm-failover-master-1", @@ -49,6 +54,10 @@ def salt_mm_failover_master_2(salt_factories, salt_mm_failover_master_1): config_overrides = { "interface": "127.0.0.2", "master_sign_pubkey": True, + "fips_mode": FIPS_TESTRUN, + "publish_signing_algorithm": ( + "PKCS1v15-SHA224" if FIPS_TESTRUN else "PKCS1v15-SHA1" + ), } # Use the same ports for both masters, they are binding to different interfaces @@ -100,6 +109,9 @@ def salt_mm_failover_minion_1(salt_mm_failover_master_1, salt_mm_failover_master "master_tries": -1, "verify_master_pubkey_sign": True, "retry_dns": 1, + "fips_mode": FIPS_TESTRUN, + "encryption_algorithm": "OAEP-SHA224" if FIPS_TESTRUN else "OAEP-SHA1", + "signing_algorithm": "PKCS1v15-SHA224" if FIPS_TESTRUN else "PKCS1v15-SHA1", } factory = salt_mm_failover_master_1.salt_minion_daemon( "mm-failover-minion-1", @@ -138,6 +150,9 @@ def salt_mm_failover_minion_2(salt_mm_failover_master_1, salt_mm_failover_master "master_tries": -1, "verify_master_pubkey_sign": True, "retry_dns": 1, + "fips_mode": FIPS_TESTRUN, + "encryption_algorithm": "OAEP-SHA224" if FIPS_TESTRUN else "OAEP-SHA1", + "signing_algorithm": "PKCS1v15-SHA224" if FIPS_TESTRUN else "PKCS1v15-SHA1", } factory = salt_mm_failover_master_2.salt_minion_daemon( "mm-failover-minion-2", diff --git a/tests/pytests/scenarios/failover/multimaster/test_failover_master.py b/tests/pytests/scenarios/failover/multimaster/test_failover_master.py index 84ab7548ff4..e996469789c 100644 --- a/tests/pytests/scenarios/failover/multimaster/test_failover_master.py +++ b/tests/pytests/scenarios/failover/multimaster/test_failover_master.py @@ -5,6 +5,8 @@ import time import pytest +from tests.conftest import FIPS_TESTRUN + pytestmark = [ pytest.mark.core_test, pytest.mark.skip_on_freebsd(reason="Processes are not properly killed on FreeBSD"), @@ -36,6 +38,9 @@ def test_pki(salt_mm_failover_master_1, salt_mm_failover_master_2, caplog): "master_alive_interval": 5, "master_tries": -1, "verify_master_pubkey_sign": True, + "fips_mode": FIPS_TESTRUN, + "encryption_algorithm": "OAEP-SHA224" if FIPS_TESTRUN else "OAEP-SHA1", + "signing_algorithm": "PKCS1v15-SHA224" if FIPS_TESTRUN else "PKCS1v15-SHA1", } factory = salt_mm_failover_master_1.salt_minion_daemon( "mm-failover-pki-minion-1", diff --git a/tests/pytests/scenarios/multimaster/conftest.py b/tests/pytests/scenarios/multimaster/conftest.py index 1bfc830cb00..84e7a9a3ceb 100644 --- a/tests/pytests/scenarios/multimaster/conftest.py +++ b/tests/pytests/scenarios/multimaster/conftest.py @@ -8,6 +8,7 @@ import pytest from pytestshellutils.exceptions import FactoryTimeout import salt.utils.platform +from tests.conftest import FIPS_TESTRUN log = logging.getLogger(__name__) @@ -20,8 +21,11 @@ def salt_mm_master_1(request, salt_factories): } config_overrides = { "interface": "127.0.0.1", + "fips_mode": FIPS_TESTRUN, + "publish_signing_algorithm": ( + "PKCS1v15-SHA224" if FIPS_TESTRUN else "PKCS1v15-SHA1" + ), } - factory = salt_factories.salt_master_daemon( "mm-master-1", defaults=config_defaults, @@ -48,6 +52,10 @@ def salt_mm_master_2(salt_factories, salt_mm_master_1): } config_overrides = { "interface": "127.0.0.2", + "fips_mode": FIPS_TESTRUN, + "publish_signing_algorithm": ( + "PKCS1v15-SHA224" if FIPS_TESTRUN else "PKCS1v15-SHA1" + ), } # Use the same ports for both masters, they are binding to different interfaces @@ -95,6 +103,9 @@ def salt_mm_minion_1(salt_mm_master_1, salt_mm_master_2): f"{mm_master_2_addr}:{mm_master_2_port}", ], "test.foo": "baz", + "fips_mode": FIPS_TESTRUN, + "encryption_algorithm": "OAEP-SHA224" if FIPS_TESTRUN else "OAEP-SHA1", + "signing_algorithm": "PKCS1v15-SHA224" if FIPS_TESTRUN else "PKCS1v15-SHA1", } factory = salt_mm_master_1.salt_minion_daemon( "mm-minion-1", @@ -122,6 +133,9 @@ def salt_mm_minion_2(salt_mm_master_1, salt_mm_master_2): f"{mm_master_2_addr}:{mm_master_2_port}", ], "test.foo": "baz", + "fips_mode": FIPS_TESTRUN, + "encryption_algorithm": "OAEP-SHA224" if FIPS_TESTRUN else "OAEP-SHA1", + "signing_algorithm": "PKCS1v15-SHA224" if FIPS_TESTRUN else "PKCS1v15-SHA1", } factory = salt_mm_master_2.salt_minion_daemon( "mm-minion-2", diff --git a/tests/pytests/scenarios/reauth/conftest.py b/tests/pytests/scenarios/reauth/conftest.py index bbefa71e181..c91760bc8ca 100644 --- a/tests/pytests/scenarios/reauth/conftest.py +++ b/tests/pytests/scenarios/reauth/conftest.py @@ -1,12 +1,20 @@ import pytest from saltfactories.utils import random_string +from tests.conftest import FIPS_TESTRUN + @pytest.fixture(scope="package") def salt_master_factory(salt_factories): factory = salt_factories.salt_master_daemon( random_string("reauth-master-"), extra_cli_arguments_after_first_start_failure=["--log-level=info"], + overrides={ + "fips_mode": FIPS_TESTRUN, + "publish_signing_algorithm": ( + "PKCS1v15-SHA224" if FIPS_TESTRUN else "PKCS1v15-SHA1" + ), + }, ) return factory @@ -22,6 +30,11 @@ def salt_minion_factory(salt_master): factory = salt_master.salt_minion_daemon( random_string("reauth-minion-"), extra_cli_arguments_after_first_start_failure=["--log-level=info"], + overrides={ + "fips_mode": FIPS_TESTRUN, + "encryption_algorithm": "OAEP-SHA224" if FIPS_TESTRUN else "OAEP-SHA1", + "signing_algorithm": "PKCS1v15-SHA224" if FIPS_TESTRUN else "PKCS1v15-SHA1", + }, ) return factory diff --git a/tests/pytests/scenarios/swarm/conftest.py b/tests/pytests/scenarios/swarm/conftest.py index f1d9154e9e1..abd2a8ef66d 100644 --- a/tests/pytests/scenarios/swarm/conftest.py +++ b/tests/pytests/scenarios/swarm/conftest.py @@ -4,12 +4,21 @@ from contextlib import ExitStack import pytest from saltfactories.utils import random_string +from tests.conftest import FIPS_TESTRUN + @pytest.fixture(scope="package") def salt_master_factory(salt_factories): + config_overrides = { + "fips_mode": FIPS_TESTRUN, + "publish_signing_algorithm": ( + "PKCS1v15-SHA224" if FIPS_TESTRUN else "PKCS1v15-SHA1" + ), + } factory = salt_factories.salt_master_daemon( random_string("swarm-master-"), extra_cli_arguments_after_first_start_failure=["--log-level=info"], + overrides=config_overrides, ) return factory @@ -68,6 +77,15 @@ def minion_swarm(salt_master, _minion_count): minion_factory = salt_master.salt_minion_daemon( random_string(f"swarm-minion-{idx}-"), extra_cli_arguments_after_first_start_failure=["--log-level=info"], + overrides={ + "fips_mode": FIPS_TESTRUN, + "encryption_algorithm": ( + "OAEP-SHA224" if FIPS_TESTRUN else "OAEP-SHA1" + ), + "signing_algorithm": ( + "PKCS1v15-SHA224" if FIPS_TESTRUN else "PKCS1v15-SHA1" + ), + }, ) stack.enter_context(minion_factory.started()) minions.append(minion_factory) diff --git a/tests/pytests/scenarios/syndic/conftest.py b/tests/pytests/scenarios/syndic/conftest.py index b014aa2bf0e..5492bc23946 100644 --- a/tests/pytests/scenarios/syndic/conftest.py +++ b/tests/pytests/scenarios/syndic/conftest.py @@ -2,6 +2,8 @@ import logging import pytest +from tests.conftest import FIPS_TESTRUN + log = logging.getLogger(__name__) @@ -14,6 +16,10 @@ def master(request, salt_factories): "interface": "127.0.0.1", "auto_accept": True, "order_masters": True, + "fips_mode": FIPS_TESTRUN, + "publish_signing_algorithm": ( + "PKCS1v15-SHA224" if FIPS_TESTRUN else "PKCS1v15-SHA1" + ), } factory = salt_factories.salt_master_daemon( "master", @@ -49,10 +55,17 @@ def syndic(master, salt_factories): "auto_accept": True, "syndic_master": f"{addr}", "syndic_master_port": f"{ret_port}", + "fips_mode": FIPS_TESTRUN, + "publish_signing_algorithm": ( + "PKCS1v15-SHA224" if FIPS_TESTRUN else "PKCS1v15-SHA1" + ), } minion_overrides = { "master": "127.0.0.2", "publish_port": f"{port}", + "fips_mode": FIPS_TESTRUN, + "encryption_algorithm": "OAEP-SHA224" if FIPS_TESTRUN else "OAEP-SHA1", + "signing_algorithm": "PKCS1v15-SHA224" if FIPS_TESTRUN else "PKCS1v15-SHA1", } factory = master.salt_syndic_daemon( "syndic", @@ -74,6 +87,9 @@ def minion(syndic, salt_factories): addr = syndic.master.config["interface"] config_overrides = { "master": f"{addr}:{port}", + "fips_mode": FIPS_TESTRUN, + "encryption_algorithm": "OAEP-SHA224" if FIPS_TESTRUN else "OAEP-SHA1", + "signing_algorithm": "PKCS1v15-SHA224" if FIPS_TESTRUN else "PKCS1v15-SHA1", } factory = syndic.master.salt_minion_daemon( "minion", diff --git a/tests/pytests/unit/conftest.py b/tests/pytests/unit/conftest.py index b3dc4bb32eb..163cae17326 100644 --- a/tests/pytests/unit/conftest.py +++ b/tests/pytests/unit/conftest.py @@ -5,6 +5,7 @@ import pytest import salt.config import salt.transport.tcp +from tests.conftest import FIPS_TESTRUN from tests.support.mock import MagicMock, patch @@ -26,6 +27,9 @@ def minion_opts(tmp_path): opts[name] = str(dirpath) opts["log_file"] = "logs/minion.log" opts["conf_file"] = os.path.join(opts["conf_dir"], "minion") + opts["fips_mode"] = FIPS_TESTRUN + opts["encryption_algorithm"] = "OAEP-SHA224" if FIPS_TESTRUN else "OAEP-SHA1" + opts["signing_algorithm"] = "PKCS1v15-SHA224" if FIPS_TESTRUN else "PKCS1v15-SHA1" return opts @@ -44,6 +48,10 @@ def master_opts(tmp_path): opts[name] = str(dirpath) opts["log_file"] = "logs/master.log" opts["conf_file"] = os.path.join(opts["conf_dir"], "master") + opts["fips_mode"] = FIPS_TESTRUN + opts["publish_signing_algorithm"] = ( + "PKCS1v15-SHA224" if FIPS_TESTRUN else "PKCS1v15-SHA1" + ) return opts diff --git a/tests/pytests/unit/crypt/__init__.py b/tests/pytests/unit/crypt/__init__.py index 7e3944aea08..0f08bb7c6c1 100644 --- a/tests/pytests/unit/crypt/__init__.py +++ b/tests/pytests/unit/crypt/__init__.py @@ -1,3 +1,196 @@ +import getpass +import logging +import os + +import salt.utils.files +import salt.utils.stringutils +from salt.exceptions import InvalidKeyError + +try: + from M2Crypto import BIO, EVP, RSA + + HAS_M2 = True +except ImportError: + HAS_M2 = False + +if not HAS_M2: + try: + from Cryptodome import Random + from Cryptodome.Cipher import AES, PKCS1_OAEP + from Cryptodome.Cipher import PKCS1_v1_5 as PKCS1_v1_5_CIPHER + from Cryptodome.Hash import SHA + from Cryptodome.PublicKey import RSA + from Cryptodome.Signature import PKCS1_v1_5 + + HAS_CRYPTO = True + except ImportError: + HAS_CRYPTO = False + +if not HAS_M2 and not HAS_CRYPTO: + try: + # let this be imported, if possible + from Crypto import Random # nosec + from Crypto.Cipher import AES, PKCS1_OAEP # nosec + from Crypto.Cipher import PKCS1_v1_5 as PKCS1_v1_5_CIPHER # nosec + from Crypto.Hash import SHA # nosec + from Crypto.PublicKey import RSA # nosec + from Crypto.Signature import PKCS1_v1_5 # nosec + + HAS_CRYPTO = True + except ImportError: + HAS_CRYPTO = False + +log = logging.getLogger(__name__) + + +def legacy_gen_keys(keydir, keyname, keysize, user=None, passphrase=None): + """ + Generate a RSA public keypair for use with salt + + :param str keydir: The directory to write the keypair to + :param str keyname: The type of salt server for whom this key should be written. (i.e. 'master' or 'minion') + :param int keysize: The number of bits in the key + :param str user: The user on the system who should own this keypair + :param str passphrase: The passphrase which should be used to encrypt the private key + + :rtype: str + :return: Path on the filesystem to the RSA private key + """ + base = os.path.join(keydir, keyname) + priv = f"{base}.pem" + pub = f"{base}.pub" + + # gen = rsa.generate_private_key(e, keysize) + if HAS_M2: + gen = RSA.gen_key(keysize, 65537, lambda: None) + else: + gen = RSA.generate(bits=keysize, e=65537) + + if os.path.isfile(priv): + # Between first checking and the generation another process has made + # a key! Use the winner's key + return priv + + # Do not try writing anything, if directory has no permissions. + if not os.access(keydir, os.W_OK): + raise OSError( + 'Write access denied to "{}" for user "{}".'.format( + os.path.abspath(keydir), getpass.getuser() + ) + ) + + with salt.utils.files.set_umask(0o277): + if HAS_M2: + # if passphrase is empty or None use no cipher + if not passphrase: + gen.save_pem(priv, cipher=None) + else: + gen.save_pem( + priv, + cipher="des_ede3_cbc", + callback=lambda x: salt.utils.stringutils.to_bytes(passphrase), + ) + else: + with salt.utils.files.fopen(priv, "wb+") as f: + f.write(gen.exportKey("PEM", passphrase)) + if HAS_M2: + gen.save_pub_key(pub) + else: + with salt.utils.files.fopen(pub, "wb+") as f: + f.write(gen.publickey().exportKey("PEM")) + os.chmod(priv, 0o400) + if user: + try: + import pwd + + uid = pwd.getpwnam(user).pw_uid + os.chown(priv, uid, -1) + os.chown(pub, uid, -1) + except (KeyError, ImportError, OSError): + # The specified user was not found, allow the backup systems to + # report the error + pass + return priv + + +class LegacyPrivateKey: + + def __init__(self, path, passphrase=None): + if HAS_M2: + self.key = RSA.load_key(path, lambda x: bytes(passphrase)) + else: + with salt.utils.files.fopen(path) as f: + self.key = RSA.importKey(f.read(), passphrase) + + def encrypt(self, data): + if HAS_M2: + return self.key.private_encrypt(data, salt.utils.rsax931.RSA_X931_PADDING) + else: + return salt.utils.rsax931.RSAX931Signer(self.key.exportKey("PEM")).sign( + data + ) + + def sign(self, data): + if HAS_M2: + md = EVP.MessageDigest("sha1") + md.update(salt.utils.stringutils.to_bytes(data)) + digest = md.final() + return self.key.sign(digest) + else: + signer = PKCS1_v1_5.new(self.key) + return signer.sign(SHA.new(salt.utils.stringutils.to_bytes(data))) + + +class LegacyPublicKey: + def __init__(self, path, _HAS_M2=HAS_M2): + self._HAS_M2 = _HAS_M2 + if self._HAS_M2: + with salt.utils.files.fopen(path, "rb") as f: + data = f.read().replace(b"RSA ", b"") + bio = BIO.MemoryBuffer(data) + try: + self.key = RSA.load_pub_key_bio(bio) + except RSA.RSAError: + raise InvalidKeyError("Encountered bad RSA public key") + else: + with salt.utils.files.fopen(path) as f: + try: + self.key = RSA.importKey(f.read()) + except (ValueError, IndexError, TypeError): + raise InvalidKeyError("Encountered bad RSA public key") + + def encrypt(self, data): + bdata = salt.utils.stringutils.to_bytes(data) + if self._HAS_M2: + return self.key.public_encrypt(bdata, RSA.pkcs1_oaep_padding) + else: + return PKCS1_OAEP.new(self.key).encrypt(bdata) + + def verify(self, data, signature): + if self._HAS_M2: + md = EVP.MessageDigest("sha1") + md.update(salt.utils.stringutils.to_bytes(data)) + digest = md.final() + try: + return self.key.verify(digest, signature) + except RSA.RSAError as exc: + log.debug("Signature verification failed: %s", exc.args[0]) + return False + else: + verifier = PKCS1_v1_5.new(self.key) + return verifier.verify( + SHA.new(salt.utils.stringutils.to_bytes(data)), signature + ) + + def decrypt(self, data): + data = salt.utils.stringutils.to_bytes(data) + if HAS_M2: + return self.key.public_decrypt(data, salt.utils.rsax931.RSA_X931_PADDING) + else: + verifier = salt.utils.rsax931.RSAX931Verifier(self.key.exportKey("PEM")) + return verifier.verify(data) + + PRIVKEY_DATA = ( "-----BEGIN RSA PRIVATE KEY-----\n" "MIIEpAIBAAKCAQEA75GR6ZTv5JOv90Vq8tKhKC7YQnhDIo2hM0HVziTEk5R4UQBW\n" diff --git a/tests/pytests/unit/crypt/test_crypt.py b/tests/pytests/unit/crypt/test_crypt.py index a9475d05b75..349e820701c 100644 --- a/tests/pytests/unit/crypt/test_crypt.py +++ b/tests/pytests/unit/crypt/test_crypt.py @@ -13,6 +13,7 @@ import salt.crypt import salt.master import salt.payload import salt.utils.files +from tests.conftest import FIPS_TESTRUN from tests.support.helpers import dedent from . import PRIV_KEY, PRIV_KEY2, PUB_KEY, PUB_KEY2 @@ -70,6 +71,7 @@ def test_cryptical_dumps_invalid_nonce(): assert master_crypt.loads(ret, nonce="abcde") +@pytest.mark.skipif(FIPS_TESTRUN, reason="Legacy key can not be loaded in FIPS mode") def test_verify_signature(tmp_path): tmp_path.joinpath("foo.pem").write_text(PRIV_KEY.strip()) tmp_path.joinpath("foo.pub").write_text(PUB_KEY.strip()) @@ -80,6 +82,7 @@ def test_verify_signature(tmp_path): assert salt.crypt.verify_signature(str(tmp_path.joinpath("foo.pub")), msg, sig) +@pytest.mark.skipif(FIPS_TESTRUN, reason="Legacy key can not be loaded in FIPS mode") def test_verify_signature_bad_sig(tmp_path): tmp_path.joinpath("foo.pem").write_text(PRIV_KEY.strip()) tmp_path.joinpath("foo.pub").write_text(PUB_KEY.strip()) @@ -152,7 +155,8 @@ def test_master_keys_with_cluster_id(tmp_path, master_opts): def test_pwdata_decrypt(): key_string = dedent( - """-----BEGIN RSA PRIVATE KEY----- + """ + -----BEGIN RSA PRIVATE KEY----- MIIEpQIBAAKCAQEAzhBRyyHa7b63RLE71uKMKgrpulcAJjaIaN68ltXcCvy4w9pi Kj+4I3Qp6RvUaHOEmymqyjOMjQc6iwpe0scCFqh3nUk5YYaLZ3WAW0htQVlnesgB ZiBg9PBeTQY/LzqtudL6RCng/AX+fbnCsddlIysRxnUoNVMvz0gAmCY2mnTDjcTt diff --git a/tests/pytests/unit/crypt/test_crypt_cryptodome.py b/tests/pytests/unit/crypt/test_crypt_cryptodome.py index 0944b87777c..8a27e36a1d8 100644 --- a/tests/pytests/unit/crypt/test_crypt_cryptodome.py +++ b/tests/pytests/unit/crypt/test_crypt_cryptodome.py @@ -6,8 +6,6 @@ import pytest import salt.crypt from tests.support.mock import MagicMock, MockCall, mock_open, patch -from . import MSG, PRIVKEY_DATA, PUBKEY_DATA, SIG - RSA = pytest.importorskip("Cryptodome.PublicKey.RSA") try: @@ -57,68 +55,3 @@ def test_gen_keys(tmp_path): salt.crypt.gen_keys(key_path, "keyname", 2048) assert open_priv_wb in m_open.calls assert open_pub_wb in m_open.calls - - -@pytest.mark.slow_test -def test_gen_keys_with_passphrase(tmp_path): - - key_path = str(tmp_path / "keydir") - open_priv_wb = MockCall(os.path.join(key_path, "keyname.pem"), "wb+") - open_pub_wb = MockCall(os.path.join(key_path, "keyname.pub"), "wb+") - - real_is_file = os.path.isfile - - def is_file(path): - if path.startswith(str(tmp_path)): - return False - return real_is_file(path) - - with patch.multiple( - os, - umask=MagicMock(), - chmod=MagicMock(), - access=MagicMock(return_value=True), - ): - with patch("salt.utils.files.fopen", mock_open()) as m_open, patch( - "os.path.isfile", return_value=True - ): - result = salt.crypt.gen_keys( - key_path, "keyname", 2048, passphrase="password" - ) - assert result == os.path.join(key_path, "keyname.pem") - assert open_priv_wb not in m_open.calls - assert open_pub_wb not in m_open.calls - - with patch("salt.utils.files.fopen", mock_open()) as m_open, patch( - "salt.crypt.os.path.isfile", is_file - ): - salt.crypt.gen_keys(key_path, "keyname", 2048) - assert open_priv_wb in m_open.calls - assert open_pub_wb in m_open.calls - - -def test_sign_message(): - key = RSA.importKey(PRIVKEY_DATA) - with patch("salt.crypt.get_rsa_key", return_value=key): - assert SIG == salt.crypt.sign_message("/keydir/keyname.pem", MSG) - - -def test_sign_message_with_passphrase(): - key = RSA.importKey(PRIVKEY_DATA) - with patch("salt.crypt.get_rsa_key", return_value=key): - assert SIG == salt.crypt.sign_message( - "/keydir/keyname.pem", MSG, passphrase="password" - ) - - -def test_verify_signature(): - with patch("salt.utils.files.fopen", mock_open(read_data=PUBKEY_DATA)): - assert salt.crypt.verify_signature("/keydir/keyname.pub", MSG, SIG) - - -def test_bad_key(key_to_test): - """ - Load public key with an invalid header and validate it without m2crypto - """ - key = salt.crypt.get_rsa_pub_key(key_to_test) - assert key.can_encrypt() diff --git a/tests/pytests/unit/crypt/test_crypt_cryptography.py b/tests/pytests/unit/crypt/test_crypt_cryptography.py new file mode 100644 index 00000000000..9a641b292d5 --- /dev/null +++ b/tests/pytests/unit/crypt/test_crypt_cryptography.py @@ -0,0 +1,373 @@ +import hashlib +import hmac +import os + +import pytest +from cryptography.hazmat.backends.openssl import backend +from cryptography.hazmat.primitives import serialization + +import salt.crypt as crypt +import salt.utils.files +import salt.utils.stringutils +from tests.conftest import FIPS_TESTRUN +from tests.support.mock import mock_open, patch + +from . import ( + HAS_M2, + MSG, + PRIVKEY_DATA, + PUBKEY_DATA, + SIG, + LegacyPrivateKey, + LegacyPublicKey, + legacy_gen_keys, +) + +if HAS_M2: + from . import EVP, RSA +else: + from . import AES, PKCS1_OAEP + + +OPENSSL_ENCRYPTED_KEY = """ +-----BEGIN RSA PRIVATE KEY----- +Proc-Type: 4,ENCRYPTED +DEK-Info: AES-256-CBC,12E2FE8CA93672B629477073867DE7E4 + +3wgKQlgjT3G+8rIQt97AXMDByh5u9JMZPYOB9/wg3iC3qoJXfoFAsCNUjODJBnkI +j9Zgj/bOCaSM3UshMQXmYY+2Rfi1SVQPnETlqEH/plMZS38tB8mN5pBdthgGTw6c +rhpj/S23eZT5d+z5ODeVYlWCVhx8CtE8OQEzOQk8dxLWbVHhvgC3uJGWOPR3P7VM +BlxH5LxWRCrC8vzbnwwaJnX8BTQ7fc4qeGwHlXBjpnxQhxO27pEj08NQ0/lfKh1b +seX5uiCjuQhHFKNGTuA16rQIe6BkYRHIWCDhySftl/lqSfLQif0OAXaHEdL2EdIS +ySD12RYyNUDotEzYFF+qzJ5OAtraqvc8kYror7oN52bHKCjJyrp4+DWA4/N7FjTV ++FqUyJNKqw1DAQAxlZlq+GgNyi8+g/Zs2TKTc/ZXaPLjtWYOQEQkYaNBoaD3ydY3 +c7x+uQtJLVW9BF9FSl9A7BItpqZQWKiHiGtUdhYOkemlR+zMatjBe/eTq8LrnEDa +IyI+rRo1PSDAz3n1pdEAzGAOeqwT+j7YG9O8+dybMY5FcAtiiPX21nIpmP+Rtx4X +GqzHsT7nM6QG4O8GPKuK6TniG+Q0doNWomwuau/cjQgL4C+yFiX3kIPpHz9kA/aS +NL1SJqSsvc3D/KlRbHXaJZJyhmzDuEbQynkaAdvejiajlJWAwA3BZWw1RUK7Wn8m +XcNPJL3g02uKq8SUDgVQl/cx4QawuWri2Xh8/xakNYWzNU2feoWBmV+gN2qDSxyz +Qi+xu3CzdJVrPs71lW0rEAIQvU3K3Umava9M4CUF6R7N9+Zv+m1EuMQs0IGt8VCY +Wo1KY5PAb/V718d1C3I6kXvLSDXG8xqyEleilPLhKCRGPK+2g0nGYu562EV1i6by +gr+PLnFJTfgHEzwIfsqfNoR8ReQ6AJKJoniQr4vqex9xtifuhes0odpqmUB4/B2C +UfY/SpJR6tzdrGndpB/vb1vjHumHklHHWrLONtz70BhR8Zaisc7SCmL5bFgWqzMC +MJKPulRRGQCPAzy5OI/ZULY8+dzlva1MyoCYlWjeUtcUAy+9dyA8GZv75ez9g71b +10nNINDcvGG7zWShSYrAKrvLlsoE7eZ+flG+XhI2CfiC9/zHBzy/slbaH9H+1tlO +VWKiw6iBb2TEvBk4Wpk2nUFlWKtkkBVAlgbShbE2K8pTHrJeIRv5J897k693NFZE +DjVVJirzMv/OiZTami0qBQ4nDtUZpH8FsFZ8DtREkhROsDmrjq9PGkOVaxEyF/ke +avJT34gp4OoNWj7/Rd1YNbGiWjMEB3zi9y1Q6Ufiod9ZlK3RQb4tNrpzDn/msdJo +pIkuByWjXjF4YQRKtAoeCn+CWiY7L/Qi8X7jmX27JLILlZPtTJ+aNp3eCr6ZX0dW +y0uhN89sgMewlvDA/pduL/VJRHUBZC2eD8FbD7p6K+yRKhdciS9A8F6aIhx615s6 +mngRBfwzh8ST6yQgLwCgle/XaRYTWJKjzAe3lkaIBBhHpeuq1UMAjunoS8JnLNiy +xQJ0PznzY57sYKpIiClwMjfpnX47nTU2DFWuPEXvBtG1xMjacGPbVrUslesY5bii +-----END RSA PRIVATE KEY----- +""" + + +@pytest.fixture +def openssl_encrypted_key(): + return OPENSSL_ENCRYPTED_KEY + + +@pytest.fixture +def passphrase(): + return "pass1234" + + +@pytest.fixture +def signing_algorithm(): + if FIPS_TESTRUN: + return salt.crypt.PKCS1v15_SHA224 + return salt.crypt.PKCS1v15_SHA1 + + +@pytest.fixture +def encryption_algorithm(): + if FIPS_TESTRUN: + return salt.crypt.OAEP_SHA224 + return salt.crypt.OAEP_SHA1 + + +SIG_SHA224 = ( + b'\x18)\xc3E|\x15\xebF\x0f\xe6\xc0\x10\xca\xd9~\x1d\xf14t\xc7\x14}\xda6Fk"#' + b'Hl\x06\x13\xa9\xe3QlL\\\xf4`r\x88\x85\xc6#s\xcb"6\x1c\xdd\x07t\xd4\x84g' + b"n\x0f\xcc\x1c\xee\xe7\x84T\xb7\xd1\xc80~\xdd\xf7+\x972b6\xf1\xe1\x00P" + b"E\xb8\x86\xb3i\xa6*\xd2\xac\xb5\xcbStg\xfb*E9+\xf7\xc5\xc6X\x1e\xb9vY\xb7" + b"kT[a\xe8\xe1\xd8\xdf'u\x00k\x13\xff\xe2\xd1\x91M\xa7U\xc9\x90z\xf0" + b"\x03\xb2\xf3\x1bR\xbd\xc8\xe4B\xadJ\x91\x1e\x98\xea\x17\xa8;\x01\xcb" + b"1\x07\x7f\xa2\xf3\xe6\x83\xed\x03m\xad\t&\x95\xc2Q\xfcs\xcbV\xd4\xa4\xc9n" + b"\x8a\xbe\xcc3?.N\x1f8d{B\x8cp\xf8\xc8\x17\x90\x0e\x0c\x1a\x8dF\xb8" + b'\x18\xf7\x97\xf0\x04L\xe6\xfb\xc1\xb0}\xa9\xb6?\xc0\xbd\x8a<\xac"5\xee@x' + b"\xea\x1d\xa3\xffB\xa5\xbdt`\xa5\xe8p\xa3/\x18+\xec5\xb3]\x92\xaa\xd7\x9c" + b"\x0b\x03`~\x00\r%\xc8" +) + + +@pytest.fixture +def signature(): + if FIPS_TESTRUN: + return SIG_SHA224 + return SIG + + +@pytest.fixture +def private_key(passphrase, tmp_path): + keypath = tmp_path / "keys" + keypath.mkdir() + keyname = "test" + keysize = 2048 + return crypt.gen_keys(str(keypath), keyname, keysize, passphrase=passphrase) + + +def test_fips_mode(): + assert backend._fips_enabled == FIPS_TESTRUN + + +@pytest.mark.skipif(FIPS_TESTRUN, reason="Legacy key can not be loaded in FIPS mode") +def test_gen_keys_legacy(tmp_path): + keypath = tmp_path / "keys" + keypath.mkdir() + passphrase = "pass1234" + keyname = "test" + keysize = 2048 + ret = legacy_gen_keys(str(keypath), keyname, keysize, passphrase=passphrase) + with salt.utils.files.fopen(ret, "rb") as fp: + keybytes = fp.read() + assert keybytes.startswith(b"-----BEGIN RSA PRIVATE KEY-----\n") + priv = serialization.load_pem_private_key(keybytes, passphrase.encode()) + with salt.utils.files.fopen(ret.replace(".pem", ".pub"), "rb") as fp: + keybytes = fp.read() + assert keybytes.startswith(b"-----BEGIN PUBLIC KEY-----\n") + + +def test_gen_keys(tmp_path): + keypath = tmp_path / "keys" + keypath.mkdir() + passphrase = "pass1234" + keyname = "test" + keysize = 2048 + ret = crypt.gen_keys(str(keypath), keyname, keysize, passphrase=passphrase) + with salt.utils.files.fopen(ret, "rb") as fp: + keybytes = fp.read() + if FIPS_TESTRUN: + assert keybytes.startswith(b"-----BEGIN ENCRYPTED PRIVATE KEY-----\n") + else: + assert keybytes.startswith(b"-----BEGIN RSA PRIVATE KEY-----\n") + priv = serialization.load_pem_private_key(keybytes, passphrase.encode()) + with salt.utils.files.fopen(ret.replace(".pem", ".pub"), "rb") as fp: + keybytes = fp.read() + assert keybytes.startswith(b"-----BEGIN PUBLIC KEY-----\n") + + +def test_legacy_private_key_loading(private_key, passphrase): + priv = LegacyPrivateKey(private_key.encode(), passphrase.encode()) + assert priv.key + + +def test_private_key_loading(private_key, passphrase): + priv = crypt.PrivateKey(private_key, passphrase) + assert priv.key + + +@pytest.mark.skipif(FIPS_TESTRUN, reason="Legacy key can not be loaded in FIPS mode") +def test_private_key_signing(private_key, passphrase): + lpriv = LegacyPrivateKey(private_key.encode(), passphrase.encode()) + priv = crypt.PrivateKey(private_key, passphrase) + data = b"meh" + signature = priv.sign(data) + lsignature = lpriv.sign(data) + assert lsignature == signature + + +@pytest.mark.skipif(FIPS_TESTRUN, reason="Legacy key can not be loaded in FIPS mode") +def test_legacy_public_key_verify(private_key, passphrase): + lpriv = crypt.PrivateKey(private_key, passphrase) + data = b"meh" + signature = lpriv.sign(data) + pubkey = LegacyPublicKey(private_key.replace(".pem", ".pub")) + assert pubkey.verify(data, signature) + + +@pytest.mark.skipif(FIPS_TESTRUN, reason="Legacy key can not be loaded in FIPS mode") +def test_public_key_verify(private_key, passphrase): + lpriv = LegacyPrivateKey(private_key.encode(), passphrase.encode()) + data = b"meh" + signature = lpriv.sign(data) + pubkey = crypt.PublicKey(private_key.replace(".pem", ".pub")) + assert pubkey.verify(data, signature) + + +@pytest.mark.skipif(FIPS_TESTRUN, reason="Legacy key can not be loaded in FIPS mode") +def test_public_key_encrypt(private_key, passphrase): + pubkey = crypt.PublicKey(private_key.replace(".pem", ".pub")) + data = b"meh" + enc = pubkey.encrypt(data) + + lpriv = LegacyPrivateKey(private_key.encode(), passphrase.encode()) + if HAS_M2: + dec = lpriv.key.private_decrypt(enc, RSA.pkcs1_oaep_padding) + else: + cipher = PKCS1_OAEP.new(lpriv.key) + dec = cipher.decrypt(enc) + + assert data == dec + + +@pytest.mark.skipif(FIPS_TESTRUN, reason="Legacy key can not be loaded in FIPS mode") +def test_private_key_decrypt(private_key, passphrase): + lpubkey = LegacyPublicKey(private_key.replace(".pem", ".pub")) + data = b"meh" + enc = lpubkey.encrypt(data) + priv = crypt.PrivateKey(private_key, passphrase) + dec = priv.key.decrypt( + enc, + crypt.padding.OAEP( + mgf=crypt.padding.MGF1(algorithm=crypt.hashes.SHA1()), + algorithm=crypt.hashes.SHA1(), + label=None, + ), + ) + + assert data == dec + + +def test_legacy_aes_encrypt(): + """ + Test that the legacy aes encryption can be decrypted by cryptography + """ + orig_data = b"meh" + crypticle = salt.crypt.Crypticle({}, salt.crypt.Crypticle.generate_key_string()) + aes_key, hmac_key = crypticle.keys + pad = crypticle.AES_BLOCK_SIZE - len(orig_data) % crypticle.AES_BLOCK_SIZE + data = orig_data + salt.utils.stringutils.to_bytes(pad * chr(pad)) + iv_bytes = os.urandom(crypticle.AES_BLOCK_SIZE) + iv_bytes = data[: crypticle.AES_BLOCK_SIZE] + if HAS_M2: + cypher = EVP.Cipher( + alg="aes_192_cbc", key=aes_key, iv=iv_bytes, op=1, padding=False + ) + encr = cypher.update(data) + encr += cypher.final() + else: + cypher = AES.new(aes_key, AES.MODE_CBC, iv_bytes) + encr = cypher.encrypt(data) + + data = iv_bytes + encr + sig = hmac.new(hmac_key, data, hashlib.sha256).digest() + assert orig_data == crypticle.decrypt(data + sig) + + +def test_aes_encrypt(): + """ + Test that cryptography aes encryption can be decrypted by the legacy libraries + """ + orig_data = b"meh" + crypticle = salt.crypt.Crypticle({}, salt.crypt.Crypticle.generate_key_string()) + + data = crypticle.encrypt(orig_data) + aes_key, hmac_key = crypticle.keys + sig = data[-crypticle.SIG_SIZE :] + data = data[: -crypticle.SIG_SIZE] + if not isinstance(data, bytes): + data = salt.utils.stringutils.to_bytes(data) + mac_bytes = hmac.new(hmac_key, data, hashlib.sha256).digest() + result = 0 + for zipped_x, zipped_y in zip(mac_bytes, sig): + result |= zipped_x ^ zipped_y + iv_bytes = data[: crypticle.AES_BLOCK_SIZE] + data = data[crypticle.AES_BLOCK_SIZE :] + if HAS_M2: + cypher = EVP.Cipher( + alg="aes_192_cbc", key=aes_key, iv=iv_bytes, op=0, padding=False + ) + encr = cypher.update(data) + data = encr + cypher.final() + else: + cypher = AES.new(aes_key, AES.MODE_CBC, iv_bytes) + data = cypher.decrypt(data) + data = data[: -data[-1]] + assert orig_data == data + + +def test_encrypt_decrypt(private_key, passphrase, encryption_algorithm): + pubkey = crypt.PublicKey(private_key.replace(".pem", ".pub")) + enc = pubkey.encrypt(b"meh", algorithm=encryption_algorithm) + privkey = crypt.PrivateKey(private_key, passphrase) + assert privkey.decrypt(enc, algorithm=encryption_algorithm) == b"meh" + + +def test_sign_message(signature, signing_algorithm): + key = salt.crypt.serialization.load_pem_private_key(PRIVKEY_DATA.encode(), None) + with patch("salt.crypt.get_rsa_key", return_value=key): + assert ( + salt.crypt.sign_message( + "/keydir/keyname.pem", MSG, algorithm=signing_algorithm + ) + == signature + ) + + +def test_sign_message_with_passphrase(signature, signing_algorithm): + key = salt.crypt.serialization.load_pem_private_key(PRIVKEY_DATA.encode(), None) + with patch("salt.crypt.get_rsa_key", return_value=key): + assert ( + salt.crypt.sign_message( + "/keydir/keyname.pem", + MSG, + passphrase="password", + algorithm=signing_algorithm, + ) + == signature + ) + + +def test_verify_signature(): + with patch("salt.utils.files.fopen", mock_open(read_data=PUBKEY_DATA.encode())): + assert salt.crypt.verify_signature("/keydir/keyname.pub", MSG, SIG) + + +def test_loading_encrypted_openssl_format(openssl_encrypted_key, passphrase, tmp_path): + path = tmp_path / "key" + path.write_text(openssl_encrypted_key) + if FIPS_TESTRUN: + with pytest.raises(ValueError): + salt.crypt.get_rsa_key(path, passphrase) + else: + try: + salt.crypt.get_rsa_key(path, passphrase) + # BaseException to catch errors bubbling up from the cryptogrphy's + # rust layer. + except BaseException as exc: # pylint: disable=broad-except + pytest.fail(f"Unexpected exception: {exc}") + + +@pytest.mark.skipif(not FIPS_TESTRUN, reason="Only valid when in FIPS mode") +def test_fips_bad_signing_algo(private_key, passphrase): + key = salt.crypt.PrivateKey(private_key, passphrase) + with pytest.raises(salt.exceptions.UnsupportedAlgorithm): + key.sign("meh", salt.crypt.PKCS1v15_SHA1) + + +@pytest.mark.skipif(not FIPS_TESTRUN, reason="Only valid when in FIPS mode") +def test_fips_bad_signing_algo_verification(private_key, passphrase): + lpriv = LegacyPrivateKey(private_key.encode(), passphrase.encode()) + data = b"meh" + signature = lpriv.sign(data) + pubkey = salt.crypt.PublicKey(private_key.replace(".pem", ".pub")) + # cryptogrpahy silently returns False on unsuppoted algorithm + assert pubkey.verify(signature, salt.crypt.PKCS1v15_SHA1) is False + + +@pytest.mark.skipif(not FIPS_TESTRUN, reason="Only valid when in FIPS mode") +def test_fips_bad_encryption_algo(private_key, passphrase): + key = salt.crypt.PublicKey(private_key.replace(".pem", ".pub")) + with pytest.raises(salt.exceptions.UnsupportedAlgorithm): + key.encrypt("meh", salt.crypt.OAEP_SHA1) + + +@pytest.mark.skipif(not FIPS_TESTRUN, reason="Only valid when in FIPS mode") +def test_fips_bad_decryption_algo(private_key, passphrase): + pubkey = LegacyPublicKey(private_key.replace(".pem", ".pub")) + data = pubkey.encrypt("meh") + key = salt.crypt.PrivateKey(private_key, passphrase) + with pytest.raises(salt.exceptions.UnsupportedAlgorithm): + key.decrypt(data) diff --git a/tests/pytests/unit/loader/test_loader.py b/tests/pytests/unit/loader/test_loader.py index e7359c6a74a..ea0883b9388 100644 --- a/tests/pytests/unit/loader/test_loader.py +++ b/tests/pytests/unit/loader/test_loader.py @@ -83,3 +83,16 @@ def test_named_loader_context_name_not_packed(tmp_path): match="LazyLoader does not have a packed value for: __not_packed__", ): loader["mymod.foobar"]() + + +def test_return_named_context_from_loaded_func(tmp_path): + opts = { + "optimization_order": [0], + } + contents = """ + def foobar(): + return __test__ + """ + with pytest.helpers.temp_file("mymod.py", contents, directory=tmp_path): + loader = salt.loader.LazyLoader([tmp_path], opts, pack={"__test__": "meh"}) + assert loader["mymod.foobar"]() == "meh" diff --git a/tests/pytests/unit/modules/test_win_task.py b/tests/pytests/unit/modules/test_win_task.py index ee61d739b99..fa405125d11 100644 --- a/tests/pytests/unit/modules/test_win_task.py +++ b/tests/pytests/unit/modules/test_win_task.py @@ -7,6 +7,7 @@ from datetime import datetime import pytest import salt.modules.win_task as win_task +from salt.exceptions import CommandExecutionError pytestmark = [ pytest.mark.skip_unless_on_windows, @@ -85,3 +86,93 @@ def test_edit_task_delete_after(base_task): result = win_task.info(base_task) assert result["settings"]["delete_after"] is False + + +def test_execution_time_limit(base_task): + result = win_task.add_trigger( + base_task, + trigger_type="Daily", + trigger_enabled=True, + end_date=datetime.today().strftime("%Y-%m-%d"), + end_time="23:59:59", + ) + assert result is True + + result = win_task.edit_task(base_task, execution_time_limit="1 hour") + assert result is True + + result = win_task.info(base_task) + assert result["settings"]["execution_time_limit"] == "1 hour" + + result = win_task.edit_task(base_task, execution_time_limit=False) + assert result is True + + result = win_task.info(base_task) + assert result["settings"]["execution_time_limit"] is False + + +@pytest.mark.parametrize( + "exitcode, expect", + [ + (0, "The operation completed successfully"), + (3221225786, "The application terminated as a result of CTRL+C"), + (4289449455, "Unknown Task Result: 0xffabcdef"), + ], +) +def test_run_result_code(exitcode, expect): + task_name = "SaltTest" + try: + result = win_task.create_task( + task_name, + user_name="System", + force=True, + action_type="Execute", + cmd="cmd.exe", + arguments=f"/c exit {exitcode}", + ) + assert result is True + + result = win_task.info(task_name) + assert result["last_run_result"] == "Task has not yet run" + + result = win_task.run_wait(task_name) + assert result is True + + result = win_task.info(task_name) + assert result["last_run_result"] == expect + finally: + result = win_task.delete_task(task_name) + assert result is True + + +def test_create_task_from_xml(): + task_name = "SaltTest" + task_xml = 'cmd.exe/c exit' + try: + result = win_task.create_task_from_xml( + task_name, user_name="System", xml_text=task_xml + ) + assert result is True + + result = win_task.info(task_name) + assert result["actions"][0]["action_type"] == "Execute" + assert result["actions"][0]["cmd"] == "cmd.exe" + assert result["actions"][0]["arguments"] == "/c exit" + + finally: + result = win_task.delete_task(task_name) + assert result is True + + +def test_create_task_from_xml_error(): + task_name = "SaltTest" + try: + with pytest.raises(CommandExecutionError) as excinfo: + result = win_task.create_task_from_xml( + task_name, user_name="System", xml_text="test" + ) + assert result is False + assert "The task XML is malformed" in str(excinfo.value) + finally: + result = win_task.delete_task(task_name) + assert result is not True diff --git a/tests/pytests/unit/state/test_state_compiler.py b/tests/pytests/unit/state/test_state_compiler.py index db241797326..4a546ce1dfe 100644 --- a/tests/pytests/unit/state/test_state_compiler.py +++ b/tests/pytests/unit/state/test_state_compiler.py @@ -1064,13 +1064,68 @@ def test_mod_aggregate(minion_opts): ] # Ensure that the require requisite from the - # figlet state doesn't find its way into this state - assert "require" not in low_ret + # figlet state finds its way into this state + assert "require" in low_ret # Ensure pkgs were aggregated assert low_ret["pkgs"] == ["figlet", "sl"] +def test_mod_aggregate_order(minion_opts): + """ + Test to ensure that the state_aggregate setting correctly aggregates package installations + while respecting the 'require' requisite to enforce execution order. + """ + # Setup the chunks based on the provided scenario + chunks = [ + { + "state": "pkg", + "name": "first packages", + "__id__": "first packages", + "pkgs": ["drpm"], + "fun": "installed", + "order": 1, + "__env__": "base", + "__sls__": "base", + }, + { + "state": "test", + "name": "requirement", + "__id__": "requirement", + "fun": "nop", + "order": 2, + "__env__": "base", + "__sls__": "base", + }, + { + "state": "pkg", + "name": "second packages", + "__id__": "second packages", + "pkgs": ["gc"], + "fun": "installed", + "order": 3, + "require": [{"test": "requirement"}], + "provider": "yumpkg", + "__env__": "base", + "__sls__": "base", + }, + ] + + # Setup the State object + with patch("salt.state.State._gather_pillar"): + state_obj = salt.state.State(minion_opts) + state_obj.load_modules(chunks[-1]) + state_obj.opts["state_aggregate"] = True # Ensure state aggregation is enabled + + # Process each chunk with _mod_aggregate to simulate state execution + state_obj.call_chunks(chunks) + + first_state_low = chunks[0] + last_state_low = chunks[-1] + # Verify that the requisites got aggregated as well + assert first_state_low["require"] == last_state_low["require"] + + def test_verify_onlyif_cmd_opts_exclude(minion_opts): """ Verify cmd.run state arguments are properly excluded from cmd.retcode diff --git a/tests/pytests/unit/test_config.py b/tests/pytests/unit/test_config.py index 7437c8214ed..313c3cb0b0e 100644 --- a/tests/pytests/unit/test_config.py +++ b/tests/pytests/unit/test_config.py @@ -5,7 +5,10 @@ tests.pytests.unit.test_config Unit tests for salt's config modulet """ +import pathlib + import salt.config +import salt.syspaths def test_call_id_function(tmp_path): @@ -21,3 +24,13 @@ def test_call_id_function(tmp_path): } ret = salt.config.call_id_function(opts) assert ret == "meh" + + +def test_prepend_root_dir(tmp_path): + root = tmp_path / "root" + opts = { + "root_dir": root, + "foo": str(pathlib.Path(salt.syspaths.ROOT_DIR) / "var" / "foo"), + } + salt.config.prepend_root_dir(opts, ["foo"]) + assert opts["foo"] == str(root / "var" / "foo") diff --git a/tests/pytests/unit/test_minion.py b/tests/pytests/unit/test_minion.py index 19e0934999b..dfbf0b1d8bd 100644 --- a/tests/pytests/unit/test_minion.py +++ b/tests/pytests/unit/test_minion.py @@ -740,38 +740,6 @@ def test_gen_modules_executors(minion_opts): minion.destroy() -def test_reinit_crypto_on_fork(minion_opts): - """ - Ensure salt.utils.crypt.reinit_crypto() is executed when forking for new job - """ - minion_opts["multiprocessing"] = True - with patch("salt.utils.process.default_signals"): - - io_loop = tornado.ioloop.IOLoop() - minion = salt.minion.Minion(minion_opts, io_loop=io_loop) - - job_data = {"jid": "test-jid", "fun": "test.ping"} - - def mock_start(self): - # pylint: disable=comparison-with-callable - assert ( - len( - [ - x - for x in self._after_fork_methods - if x[0] == salt.utils.crypt.reinit_crypto - ] - ) - == 1 - ) - # pylint: enable=comparison-with-callable - - with patch.object( - salt.utils.process.SignalHandlingProcess, "start", mock_start - ): - io_loop.run_sync(lambda: minion._handle_decoded_payload(job_data)) - - def test_minion_manage_schedule(minion_opts): """ Tests that the manage_schedule will call the add function, adding diff --git a/tests/pytests/unit/test_request_channel.py b/tests/pytests/unit/test_request_channel.py index 26254708fc6..300ee9c2332 100644 --- a/tests/pytests/unit/test_request_channel.py +++ b/tests/pytests/unit/test_request_channel.py @@ -22,19 +22,9 @@ import salt.transport.zeromq import salt.utils.process import salt.utils.stringutils from salt.master import SMaster +from tests.conftest import FIPS_TESTRUN from tests.support.mock import MagicMock, patch -try: - from M2Crypto import RSA - - HAS_M2 = True -except ImportError: - HAS_M2 = False - try: - from Cryptodome.Cipher import PKCS1_OAEP - except ImportError: - from Crypto.Cipher import PKCS1_OAEP # nosec - log = logging.getLogger(__name__) @@ -218,6 +208,20 @@ oQIDAQAB AES_KEY = "8wxWlOaMMQ4d3yT74LL4+hGrGTf65w8VgrcNjLJeLRQ2Q6zMa8ItY2EQUgMKKDb7JY+RnPUxbB0=" +@pytest.fixture +def signing_algorithm(): + if FIPS_TESTRUN: + return salt.crypt.PKCS1v15_SHA224 + return salt.crypt.PKCS1v15_SHA1 + + +@pytest.fixture +def encryption_algorithm(): + if FIPS_TESTRUN: + return salt.crypt.OAEP_SHA224 + return salt.crypt.OAEP_SHA1 + + @pytest.fixture def pki_dir(tmp_path): _pki_dir = tmp_path / "pki" @@ -478,58 +482,9 @@ def test_serverside_exception(temp_salt_minion, temp_salt_master): assert ret == "Server-side exception handling payload" -def test_req_server_chan_encrypt_v2(master_opts, pki_dir): - loop = tornado.ioloop.IOLoop.current() - master_opts.update( - { - "worker_threads": 1, - "master_uri": "tcp://127.0.0.1:4506", - "interface": "127.0.0.1", - "ret_port": 4506, - "ipv6": False, - "zmq_monitor": False, - "mworker_queue_niceness": False, - "sock_dir": ".", - "pki_dir": str(pki_dir.joinpath("master")), - "id": "minion", - "__role": "minion", - "keysize": 4096, - } - ) - server = salt.channel.server.ReqServerChannel.factory(master_opts) - dictkey = "pillar" - nonce = "abcdefg" - pillar_data = {"pillar1": "meh"} - try: - ret = server._encrypt_private(pillar_data, dictkey, "minion", nonce) - assert "key" in ret - assert dictkey in ret - - key = salt.crypt.get_rsa_key( - str(pki_dir.joinpath("minion", "minion.pem")), None - ) - if HAS_M2: - aes = key.private_decrypt(ret["key"], RSA.pkcs1_oaep_padding) - else: - cipher = PKCS1_OAEP.new(key) # pylint: disable=used-before-assignment - aes = cipher.decrypt(ret["key"]) - pcrypt = salt.crypt.Crypticle(master_opts, aes) - signed_msg = pcrypt.loads(ret[dictkey]) - - assert "sig" in signed_msg - assert "data" in signed_msg - data = salt.payload.loads(signed_msg["data"]) - assert "key" in data - assert data["key"] == ret["key"] - assert "key" in data - assert data["nonce"] == nonce - assert "pillar" in data - assert data["pillar"] == pillar_data - finally: - server.close() - - -def test_req_server_chan_encrypt_v1(master_opts, pki_dir): +def test_req_server_chan_encrypt_v2( + pki_dir, encryption_algorithm, signing_algorithm, master_opts +): loop = tornado.ioloop.IOLoop.current() master_opts.update( { @@ -553,20 +508,70 @@ def test_req_server_chan_encrypt_v1(master_opts, pki_dir): pillar_data = {"pillar1": "meh"} try: ret = server._encrypt_private( - pillar_data, dictkey, "minion", sign_messages=False + pillar_data, + dictkey, + "minion", + nonce, + encryption_algorithm=encryption_algorithm, + signing_algorithm=signing_algorithm, + ) + assert "key" in ret + assert dictkey in ret + + key = salt.crypt.PrivateKey(str(pki_dir.joinpath("minion", "minion.pem"))) + aes = key.decrypt(ret["key"], encryption_algorithm) + pcrypt = salt.crypt.Crypticle(master_opts, aes) + signed_msg = pcrypt.loads(ret[dictkey]) + + assert "sig" in signed_msg + assert "data" in signed_msg + data = salt.payload.loads(signed_msg["data"]) + assert "key" in data + assert data["key"] == ret["key"] + assert "key" in data + assert data["nonce"] == nonce + assert "pillar" in data + assert data["pillar"] == pillar_data + finally: + server.close() + + +def test_req_server_chan_encrypt_v1(pki_dir, encryption_algorithm, master_opts): + loop = tornado.ioloop.IOLoop.current() + master_opts.update( + { + "worker_threads": 1, + "master_uri": "tcp://127.0.0.1:4506", + "interface": "127.0.0.1", + "ret_port": 4506, + "ipv6": False, + "zmq_monitor": False, + "mworker_queue_niceness": False, + "sock_dir": ".", + "pki_dir": str(pki_dir.joinpath("master")), + "id": "minion", + "__role": "minion", + "keysize": 4096, + } + ) + server = salt.channel.server.ReqServerChannel.factory(master_opts) + dictkey = "pillar" + nonce = "abcdefg" + pillar_data = {"pillar1": "meh"} + try: + ret = server._encrypt_private( + pillar_data, + dictkey, + "minion", + sign_messages=False, + encryption_algorithm=encryption_algorithm, ) assert "key" in ret assert dictkey in ret - key = salt.crypt.get_rsa_key( - str(pki_dir.joinpath("minion", "minion.pem")), None - ) - if HAS_M2: - aes = key.private_decrypt(ret["key"], RSA.pkcs1_oaep_padding) - else: - cipher = PKCS1_OAEP.new(key) - aes = cipher.decrypt(ret["key"]) + key = salt.crypt.PrivateKey(str(pki_dir.joinpath("minion", "minion.pem"))) + aes = key.decrypt(ret["key"], encryption_algorithm) pcrypt = salt.crypt.Crypticle(master_opts, aes) data = pcrypt.loads(ret[dictkey]) assert data == pillar_data @@ -574,7 +579,9 @@ def test_req_server_chan_encrypt_v1(master_opts, pki_dir): server.close() -def test_req_chan_decode_data_dict_entry_v1(minion_opts, master_opts, pki_dir): +def test_req_chan_decode_data_dict_entry_v1( + pki_dir, encryption_algorithm, minion_opts, master_opts +): mockloop = MagicMock() minion_opts.update( { @@ -591,20 +598,22 @@ def test_req_chan_decode_data_dict_entry_v1(minion_opts, master_opts, pki_dir): "acceptance_wait_time_max": 3, } ) - master_opts.update(pki_dir=str(pki_dir.joinpath("master"))) + master_opts = dict(master_opts, pki_dir=str(pki_dir.joinpath("master"))) server = salt.channel.server.ReqServerChannel.factory(master_opts) client = salt.channel.client.ReqChannel.factory(minion_opts, io_loop=mockloop) try: dictkey = "pillar" target = "minion" pillar_data = {"pillar1": "meh"} - ret = server._encrypt_private(pillar_data, dictkey, target, sign_messages=False) + ret = server._encrypt_private( + pillar_data, + dictkey, + target, + sign_messages=False, + encryption_algorithm=encryption_algorithm, + ) key = client.auth.get_keys() - if HAS_M2: - aes = key.private_decrypt(ret["key"], RSA.pkcs1_oaep_padding) - else: - cipher = PKCS1_OAEP.new(key) - aes = cipher.decrypt(ret["key"]) + aes = key.decrypt(ret["key"], encryption_algorithm) pcrypt = salt.crypt.Crypticle(client.opts, aes) ret_pillar_data = pcrypt.loads(ret[dictkey]) assert ret_pillar_data == pillar_data @@ -647,16 +656,22 @@ async def test_req_chan_decode_data_dict_entry_v2(minion_opts, master_opts, pki_ client.auth.get_keys = auth.get_keys client.auth.crypticle.dumps = auth.crypticle.dumps client.auth.crypticle.loads = auth.crypticle.loads - real_transport = client.transport client.transport = MagicMock() - real_transport.close() + + print(minion_opts["encryption_algorithm"]) @tornado.gen.coroutine def mocksend(msg, timeout=60, tries=3): client.transport.msg = msg load = client.auth.crypticle.loads(msg["load"]) ret = server._encrypt_private( - pillar_data, dictkey, target, nonce=load["nonce"], sign_messages=True + pillar_data, + dictkey, + target, + nonce=load["nonce"], + sign_messages=True, + encryption_algorithm=minion_opts["encryption_algorithm"], + signing_algorithm=minion_opts["signing_algorithm"], ) raise tornado.gen.Return(ret) @@ -728,7 +743,13 @@ async def test_req_chan_decode_data_dict_entry_v2_bad_nonce( client.transport = MagicMock() real_transport.close() ret = server._encrypt_private( - pillar_data, dictkey, target, nonce=badnonce, sign_messages=True + pillar_data, + dictkey, + target, + nonce=badnonce, + sign_messages=True, + encryption_algorithm=minion_opts["encryption_algorithm"], + signing_algorithm=minion_opts["signing_algorithm"], ) @tornado.gen.coroutine @@ -764,7 +785,7 @@ async def test_req_chan_decode_data_dict_entry_v2_bad_nonce( async def test_req_chan_decode_data_dict_entry_v2_bad_signature( - minion_opts, master_opts, pki_dir + pki_dir, minion_opts, master_opts ): mockloop = MagicMock() minion_opts.update( @@ -800,24 +821,24 @@ async def test_req_chan_decode_data_dict_entry_v2_bad_signature( client.auth.get_keys = auth.get_keys client.auth.crypticle.dumps = auth.crypticle.dumps client.auth.crypticle.loads = auth.crypticle.loads - real_transport = client.transport client.transport = MagicMock() - real_transport.close() @tornado.gen.coroutine def mocksend(msg, timeout=60, tries=3): client.transport.msg = msg load = client.auth.crypticle.loads(msg["load"]) ret = server._encrypt_private( - pillar_data, dictkey, target, nonce=load["nonce"], sign_messages=True + pillar_data, + dictkey, + target, + nonce=load["nonce"], + sign_messages=True, + encryption_algorithm=minion_opts["encryption_algorithm"], + signing_algorithm=minion_opts["signing_algorithm"], ) key = client.auth.get_keys() - if HAS_M2: - aes = key.private_decrypt(ret["key"], RSA.pkcs1_oaep_padding) - else: - cipher = PKCS1_OAEP.new(key) - aes = cipher.decrypt(ret["key"]) + aes = key.decrypt(ret["key"], minion_opts["encryption_algorithm"]) pcrypt = salt.crypt.Crypticle(client.opts, aes) signed_msg = pcrypt.loads(ret[dictkey]) # Changing the pillar data will cause the signature verification to @@ -856,7 +877,7 @@ async def test_req_chan_decode_data_dict_entry_v2_bad_signature( async def test_req_chan_decode_data_dict_entry_v2_bad_key( - minion_opts, master_opts, pki_dir + pki_dir, minion_opts, master_opts ): mockloop = MagicMock() minion_opts.update( @@ -885,46 +906,42 @@ async def test_req_chan_decode_data_dict_entry_v2_bad_key( # Mock auth and message client. auth = client.auth - auth._crypticle = salt.crypt.Crypticle(minion_opts, AES_KEY) + auth._crypticle = salt.crypt.Crypticle(master_opts, AES_KEY) client.auth = MagicMock() client.auth.mpub = auth.mpub client.auth.authenticated = True client.auth.get_keys = auth.get_keys client.auth.crypticle.dumps = auth.crypticle.dumps client.auth.crypticle.loads = auth.crypticle.loads - real_transport = client.transport client.transport = MagicMock() - real_transport.close() @tornado.gen.coroutine def mocksend(msg, timeout=60, tries=3): client.transport.msg = msg load = client.auth.crypticle.loads(msg["load"]) ret = server._encrypt_private( - pillar_data, dictkey, target, nonce=load["nonce"], sign_messages=True + pillar_data, + dictkey, + target, + nonce=load["nonce"], + sign_messages=True, + encryption_algorithm=minion_opts["encryption_algorithm"], + signing_algorithm=minion_opts["signing_algorithm"], ) - key = client.auth.get_keys() - if HAS_M2: - aes = key.private_decrypt(ret["key"], RSA.pkcs1_oaep_padding) - else: - cipher = PKCS1_OAEP.new(key) - aes = cipher.decrypt(ret["key"]) + mkey = client.auth.get_keys() + aes = mkey.decrypt(ret["key"], minion_opts["encryption_algorithm"]) pcrypt = salt.crypt.Crypticle(client.opts, aes) signed_msg = pcrypt.loads(ret[dictkey]) # Now encrypt with a different key key = salt.crypt.Crypticle.generate_key_string() - pcrypt = salt.crypt.Crypticle(minion_opts, key) + pcrypt = salt.crypt.Crypticle(master_opts, key) pubfn = os.path.join(master_opts["pki_dir"], "minions", "minion") - pub = salt.crypt.get_rsa_pub_key(pubfn) + pub = salt.crypt.PublicKey(pubfn) ret[dictkey] = pcrypt.dumps(signed_msg) key = salt.utils.stringutils.to_bytes(key) - if HAS_M2: - ret["key"] = pub.public_encrypt(key, RSA.pkcs1_oaep_padding) - else: - cipher = PKCS1_OAEP.new(pub) - ret["key"] = cipher.encrypt(key) + ret["key"] = pub.encrypt(key, minion_opts["encryption_algorithm"]) raise tornado.gen.Return(ret) client.transport.send = mocksend @@ -941,7 +958,6 @@ async def test_req_chan_decode_data_dict_entry_v2_bad_key( "ver": "2", "cmd": "_pillar", } - try: with pytest.raises(salt.crypt.AuthenticationError) as excinfo: await client.crypted_transfer_decode_dictentry( @@ -1004,6 +1020,8 @@ async def test_req_serv_auth_v1(minion_opts, master_opts, pki_dir): "id": "minion", "token": token, "pub": pub_key, + "enc_algo": minion_opts["encryption_algorithm"], + "sig_algo": minion_opts["signing_algorithm"], } try: ret = server._auth(load, sign_messages=False) @@ -1063,6 +1081,8 @@ async def test_req_serv_auth_v2(minion_opts, master_opts, pki_dir): "nonce": nonce, "token": token, "pub": pub_key, + "enc_algo": minion_opts["encryption_algorithm"], + "sig_algo": minion_opts["signing_algorithm"], } try: ret = server._auth(load, sign_messages=True) @@ -1128,7 +1148,7 @@ async def test_req_chan_auth_v2(minion_opts, master_opts, pki_dir, io_loop): async def test_req_chan_auth_v2_with_master_signing( - minion_opts, master_opts, pki_dir, io_loop + pki_dir, io_loop, minion_opts, master_opts ): minion_opts.update( { @@ -1158,14 +1178,17 @@ async def test_req_chan_auth_v2_with_master_signing( ), "reload": salt.crypt.Crypticle.generate_key_string, } - master_opts.update(pki_dir=str(pki_dir.joinpath("master"))) + master_opts = dict(master_opts, pki_dir=str(pki_dir.joinpath("master"))) master_opts["master_sign_pubkey"] = True master_opts["master_use_pubkey_signature"] = False - master_opts["signing_key_pass"] = True + master_opts["signing_key_pass"] = "" master_opts["master_sign_key_name"] = "master_sign" server = salt.channel.server.ReqServerChannel.factory(master_opts) server.auto_key = salt.daemons.masterapi.AutoKey(server.opts) server.cache_cli = False + server.event = salt.utils.event.get_master_event( + master_opts, master_opts["sock_dir"], listen=False + ) server.master_key = salt.crypt.MasterKeys(server.opts) minion_opts["verify_master_pubkey_sign"] = True minion_opts["always_verify_signature"] = True @@ -1205,6 +1228,9 @@ async def test_req_chan_auth_v2_with_master_signing( server = salt.channel.server.ReqServerChannel.factory(master_opts) server.auto_key = salt.daemons.masterapi.AutoKey(server.opts) server.cache_cli = False + server.event = salt.utils.event.get_master_event( + master_opts, master_opts["sock_dir"], listen=False + ) server.master_key = salt.crypt.MasterKeys(server.opts) signin_payload = client.auth.minion_sign_in_payload() @@ -1448,3 +1474,228 @@ async def test_req_chan_bad_payload_to_decode( server._decode_payload({}) with pytest.raises(salt.exceptions.SaltDeserializationError): server._decode_payload(12345) + + +def test_req_server_auth_garbage_sig_algo(pki_dir, minion_opts, master_opts, caplog): + minion_opts.update( + { + "master_uri": "tcp://127.0.0.1:4506", + "interface": "127.0.0.1", + "ret_port": 4506, + "ipv6": False, + "sock_dir": ".", + "pki_dir": str(pki_dir.joinpath("minion")), + "id": "minion", + "__role": "minion", + "keysize": 4096, + "max_minions": 0, + "auto_accept": False, + "open_mode": False, + "key_pass": None, + "master_sign_pubkey": False, + "publish_port": 4505, + "auth_mode": 1, + } + ) + SMaster.secrets["aes"] = { + "secret": multiprocessing.Array( + ctypes.c_char, + salt.utils.stringutils.to_bytes(salt.crypt.Crypticle.generate_key_string()), + ), + "reload": salt.crypt.Crypticle.generate_key_string, + } + master_opts.update(pki_dir=str(pki_dir.joinpath("master"))) + server = salt.channel.server.ReqServerChannel.factory(master_opts) + + server.auto_key = salt.daemons.masterapi.AutoKey(server.opts) + server.cache_cli = False + server.event = salt.utils.event.get_master_event( + master_opts, master_opts["sock_dir"], listen=False + ) + server.master_key = salt.crypt.MasterKeys(server.opts) + pub = salt.crypt.PublicKey(str(pki_dir.joinpath("master", "master.pub"))) + token = pub.encrypt( + salt.utils.stringutils.to_bytes(salt.crypt.Crypticle.generate_key_string()), + algorithm=minion_opts["encryption_algorithm"], + ) + nonce = uuid.uuid4().hex + + # We need to read the public key with fopen otherwise the newlines might + # not match on windows. + with salt.utils.files.fopen( + str(pki_dir.joinpath("minion", "minion.pub")), "r" + ) as fp: + pub_key = salt.crypt.clean_key(fp.read()) + + load = { + "version": 2, + "cmd": "_auth", + "id": "minion", + "token": token, + "pub": pub_key, + "nonce": "asdfse", + "enc_algo": minion_opts["encryption_algorithm"], + "sig_algo": "IAMNOTANALGO", + } + with caplog.at_level(logging.INFO): + ret = server._auth(load, sign_messages=True) + assert ( + "Minion tried to authenticate with unsupported signing algorithm: IAMNOTANALGO" + in caplog.text + ) + assert "load" in ret + assert "ret" in ret["load"] + assert ret["load"]["ret"] == "bad sig algo" + + +@pytest.mark.skipif(not FIPS_TESTRUN, reason="Only run on fips enabled platforms") +def test_req_server_auth_unsupported_enc_algo( + pki_dir, minion_opts, master_opts, caplog +): + minion_opts.update( + { + "master_uri": "tcp://127.0.0.1:4506", + "interface": "127.0.0.1", + "ret_port": 4506, + "ipv6": False, + "sock_dir": ".", + "pki_dir": str(pki_dir.joinpath("minion")), + "id": "minion", + "__role": "minion", + "keysize": 4096, + "max_minions": 0, + "auto_accept": False, + "open_mode": False, + "key_pass": None, + "master_sign_pubkey": False, + "publish_port": 4505, + "auth_mode": 1, + } + ) + SMaster.secrets["aes"] = { + "secret": multiprocessing.Array( + ctypes.c_char, + salt.utils.stringutils.to_bytes(salt.crypt.Crypticle.generate_key_string()), + ), + "reload": salt.crypt.Crypticle.generate_key_string, + } + master_opts.update(pki_dir=str(pki_dir.joinpath("master"))) + server = salt.channel.server.ReqServerChannel.factory(master_opts) + + server.auto_key = salt.daemons.masterapi.AutoKey(server.opts) + server.cache_cli = False + server.event = salt.utils.event.get_master_event( + master_opts, master_opts["sock_dir"], listen=False + ) + server.master_key = salt.crypt.MasterKeys(server.opts) + import tests.pytests.unit.crypt + + pub = tests.pytests.unit.crypt.LegacyPublicKey( + str(pki_dir.joinpath("master", "master.pub")) + ) + token = pub.encrypt( + salt.utils.stringutils.to_bytes(salt.crypt.Crypticle.generate_key_string()), + ) + nonce = uuid.uuid4().hex + + # We need to read the public key with fopen otherwise the newlines might + # not match on windows. + with salt.utils.files.fopen( + str(pki_dir.joinpath("minion", "minion.pub")), "r" + ) as fp: + pub_key = salt.crypt.clean_key(fp.read()) + + load = { + "version": 2, + "cmd": "_auth", + "id": "minion", + "token": token, + "pub": pub_key, + "nonce": "asdfse", + "enc_algo": "OAEP-SHA1", + "sig_algo": minion_opts["signing_algorithm"], + } + with caplog.at_level(logging.INFO): + ret = server._auth(load, sign_messages=True) + assert ( + "Minion minion tried to authenticate with unsupported encryption algorithm: OAEP-SHA1" + in caplog.text + ) + assert "load" in ret + assert "ret" in ret["load"] + assert ret["load"]["ret"] == "bad enc algo" + + +def test_req_server_auth_garbage_enc_algo(pki_dir, minion_opts, master_opts, caplog): + minion_opts.update( + { + "master_uri": "tcp://127.0.0.1:4506", + "interface": "127.0.0.1", + "ret_port": 4506, + "ipv6": False, + "sock_dir": ".", + "pki_dir": str(pki_dir.joinpath("minion")), + "id": "minion", + "__role": "minion", + "keysize": 4096, + "max_minions": 0, + "auto_accept": False, + "open_mode": False, + "key_pass": None, + "master_sign_pubkey": False, + "publish_port": 4505, + "auth_mode": 1, + } + ) + SMaster.secrets["aes"] = { + "secret": multiprocessing.Array( + ctypes.c_char, + salt.utils.stringutils.to_bytes(salt.crypt.Crypticle.generate_key_string()), + ), + "reload": salt.crypt.Crypticle.generate_key_string, + } + master_opts.update(pki_dir=str(pki_dir.joinpath("master"))) + server = salt.channel.server.ReqServerChannel.factory(master_opts) + + server.auto_key = salt.daemons.masterapi.AutoKey(server.opts) + server.cache_cli = False + server.event = salt.utils.event.get_master_event( + master_opts, master_opts["sock_dir"], listen=False + ) + server.master_key = salt.crypt.MasterKeys(server.opts) + import tests.pytests.unit.crypt + + pub = tests.pytests.unit.crypt.LegacyPublicKey( + str(pki_dir.joinpath("master", "master.pub")) + ) + token = pub.encrypt( + salt.utils.stringutils.to_bytes(salt.crypt.Crypticle.generate_key_string()), + ) + nonce = uuid.uuid4().hex + + # We need to read the public key with fopen otherwise the newlines might + # not match on windows. + with salt.utils.files.fopen( + str(pki_dir.joinpath("minion", "minion.pub")), "r" + ) as fp: + pub_key = salt.crypt.clean_key(fp.read()) + + load = { + "version": 2, + "cmd": "_auth", + "id": "minion", + "token": token, + "pub": pub_key, + "nonce": "asdfse", + "enc_algo": "IAMNOTAENCALGO", + "sig_algo": minion_opts["signing_algorithm"], + } + with caplog.at_level(logging.INFO): + ret = server._auth(load, sign_messages=True) + assert ( + "Minion minion tried to authenticate with unsupported encryption algorithm: IAMNOTAENCALGO" + in caplog.text + ) + assert "load" in ret + assert "ret" in ret["load"] + assert ret["load"]["ret"] == "bad enc algo" diff --git a/tests/pytests/unit/transport/test_tcp.py b/tests/pytests/unit/transport/test_tcp.py index b7466742ae6..d064b561c11 100644 --- a/tests/pytests/unit/transport/test_tcp.py +++ b/tests/pytests/unit/transport/test_tcp.py @@ -417,10 +417,11 @@ async def test_when_async_req_channel_with_syndic_role_should_use_syndic_master_ "transport": "tcp", "acceptance_wait_time": 30, "acceptance_wait_time_max": 30, + "signing_algorithm": "MOCK", } client = salt.channel.client.ReqChannel.factory(opts, io_loop=mockloop) assert client.master_pubkey_path == expected_pubkey_path - with patch("salt.crypt.verify_signature") as mock: + with patch("salt.crypt.PublicKey", return_value=MagicMock()) as mock: client.verify_signature("mockdata", "mocksig") assert mock.call_args_list[0][0][0] == expected_pubkey_path @@ -444,7 +445,11 @@ async def test_mixin_should_use_correct_path_when_syndic(): } client = salt.channel.client.AsyncPubChannel.factory(opts, io_loop=mockloop) client.master_pubkey_path = expected_pubkey_path - payload = {"sig": "abc", "load": {"foo": "bar"}} + payload = { + "sig": "abc", + "load": {"foo": "bar"}, + "sig_algo": salt.crypt.PKCS1v15_SHA224, + } with patch("salt.crypt.verify_signature") as mock: client._verify_master_signature(payload) assert mock.call_args_list[0][0][0] == expected_pubkey_path diff --git a/tests/pytests/unit/transport/test_zeromq.py b/tests/pytests/unit/transport/test_zeromq.py index beccfd3eea1..609b70ab48a 100644 --- a/tests/pytests/unit/transport/test_zeromq.py +++ b/tests/pytests/unit/transport/test_zeromq.py @@ -1,7 +1,12 @@ +import ctypes import logging +import multiprocessing +import threading +import time import msgpack import pytest +import tornado.gen import zmq.eventloop.future import salt.config @@ -10,6 +15,8 @@ import salt.transport.zeromq import salt.utils.platform import salt.utils.process import salt.utils.stringutils +from salt.master import SMaster +from tests.conftest import FIPS_TESTRUN from tests.support.mock import AsyncMock, MagicMock log = logging.getLogger(__name__) @@ -20,6 +27,333 @@ pytestmark = [ ] +MASTER_PRIV_KEY = """ +-----BEGIN RSA PRIVATE KEY----- +MIIEogIBAAKCAQEAoAsMPt+4kuIG6vKyw9r3+OuZrVBee/2vDdVetW+Js5dTlgrJ +aghWWn3doGmKlEjqh7E4UTa+t2Jd6w8RSLnyHNJ/HpVhMG0M07MF6FMfILtDrrt8 +ZX7eDVt8sx5gCEpYI+XG8Y07Ga9i3Hiczt+fu6HYwu96HggmG2pqkOrn3iGfqBvV +YVFJzSZYe7e4c1PeEs0xYcrA4k+apyGsMtpef8vRUrNicRLc7dAcvfhtgt2DXEZ2 +d72t/CR4ygtUvPXzisaTPW0G7OWAheCloqvTIIPQIjR8htFxGTz02STVXfnhnJ0Z +k8KhqKF2v1SQvIYxsZU7jaDgl5i3zpeh58cYOwIDAQABAoIBABZUJEO7Y91+UnfC +H6XKrZEZkcnH7j6/UIaOD9YhdyVKxhsnax1zh1S9vceNIgv5NltzIsfV6vrb6v2K +Dx/F7Z0O0zR5o+MlO8ZncjoNKskex10gBEWG00Uqz/WPlddiQ/TSMJTv3uCBAzp+ +S2Zjdb4wYPUlgzSgb2ygxrhsRahMcSMG9PoX6klxMXFKMD1JxiY8QfAHahPzQXy9 +F7COZ0fCVo6BE+MqNuQ8tZeIxu8mOULQCCkLFwXmkz1FpfK/kNRmhIyhxwvCS+z4 +JuErW3uXfE64RLERiLp1bSxlDdpvRO2R41HAoNELTsKXJOEt4JANRHm/CeyA5wsh +NpscufUCgYEAxhgPfcMDy2v3nL6KtkgYjdcOyRvsAF50QRbEa8ldO+87IoMDD/Oe +osFERJ5hhyyEO78QnaLVegnykiw5DWEF02RKMhD/4XU+1UYVhY0wJjKQIBadsufB +2dnaKjvwzUhPh5BrBqNHl/FXwNCRDiYqXa79eWCPC9OFbZcUWWq70s8CgYEAztOI +61zRfmXJ7f70GgYbHg+GA7IrsAcsGRITsFR82Ho0lqdFFCxz7oK8QfL6bwMCGKyk +nzk+twh6hhj5UNp18KN8wktlo02zTgzgemHwaLa2cd6xKgmAyuPiTgcgnzt5LVNG +FOjIWkLwSlpkDTl7ZzY2QSy7t+mq5d750fpIrtUCgYBWXZUbcpPL88WgDB7z/Bjg +dlvW6JqLSqMK4b8/cyp4AARbNp12LfQC55o5BIhm48y/M70tzRmfvIiKnEc/gwaE +NJx4mZrGFFURrR2i/Xx5mt/lbZbRsmN89JM+iKWjCpzJ8PgIi9Wh9DIbOZOUhKVB +9RJEAgo70LvCnPTdS0CaVwKBgDJW3BllAvw/rBFIH4OB/vGnF5gosmdqp3oGo1Ik +jipmPAx6895AH4tquIVYrUl9svHsezjhxvjnkGK5C115foEuWXw0u60uiTiy+6Pt +2IS0C93VNMulenpnUrppE7CN2iWFAiaura0CY9fE/lsVpYpucHAWgi32Kok+ZxGL +WEttAoGAN9Ehsz4LeQxEj3x8wVeEMHF6OsznpwYsI2oVh6VxpS4AjgKYqeLVcnNi +TlZFsuQcqgod8OgzA91tdB+Rp86NygmWD5WzeKXpCOg9uA+y/YL+0sgZZHsuvbK6 +PllUgXdYxqClk/hdBFB7v9AQoaj7K9Ga22v32msftYDQRJ94xOI= +-----END RSA PRIVATE KEY----- +""" + + +MASTER_PUB_KEY = """ +-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAoAsMPt+4kuIG6vKyw9r3 ++OuZrVBee/2vDdVetW+Js5dTlgrJaghWWn3doGmKlEjqh7E4UTa+t2Jd6w8RSLny +HNJ/HpVhMG0M07MF6FMfILtDrrt8ZX7eDVt8sx5gCEpYI+XG8Y07Ga9i3Hiczt+f +u6HYwu96HggmG2pqkOrn3iGfqBvVYVFJzSZYe7e4c1PeEs0xYcrA4k+apyGsMtpe +f8vRUrNicRLc7dAcvfhtgt2DXEZ2d72t/CR4ygtUvPXzisaTPW0G7OWAheCloqvT +IIPQIjR8htFxGTz02STVXfnhnJ0Zk8KhqKF2v1SQvIYxsZU7jaDgl5i3zpeh58cY +OwIDAQAB +-----END PUBLIC KEY----- +""" + +MASTER2_PRIV_KEY = """ +-----BEGIN RSA PRIVATE KEY----- +MIIEogIBAAKCAQEAp+8cTxguO6Vg+YO92VfHgNld3Zy8aM3JbZvpJcjTnis+YFJ7 +Zlkcc647yPRRwY9nYBNywahnt5kIeuT1rTvTsMBZWvmUoEVUj1Xg8XXQkBvb9Ozy +Gqy/G/p8KDDpzMP/U+XCnUeHiXTZrgnqgBIc2cKeCVvWFqDi0GRFGzyaXLaX3PPm +M7DJ0MIPL1qgmcDq6+7Ze0gJ9SrDYFAeLmbuT1OqDfufXWQl/82JXeiwU2cOpqWq +7n5fvPOWim7l1tzQ+dSiMRRm0xa6uNexCJww3oJSwvMbAmgzvOhqqhlqv+K7u0u7 +FrFFojESsL36Gq4GBrISnvu2tk7u4GGNTYYQbQIDAQABAoIBAADrqWDQnd5DVZEA +lR+WINiWuHJAy/KaIC7K4kAMBgbxrz2ZbiY9Ok/zBk5fcnxIZDVtXd1sZicmPlro +GuWodIxdPZAnWpZ3UtOXUayZK/vCP1YsH1agmEqXuKsCu6Fc+K8VzReOHxLUkmXn +FYM+tixGahXcjEOi/aNNTWitEB6OemRM1UeLJFzRcfyXiqzHpHCIZwBpTUAsmzcG +QiVDkMTKubwo/m+PVXburX2CGibUydctgbrYIc7EJvyx/cpRiPZXo1PhHQWdu4Y1 +SOaC66WLsP/wqvtHo58JQ6EN/gjSsbAgGGVkZ1xMo66nR+pLpR27coS7o03xCks6 +DY/0mukCgYEAuLIGgBnqoh7YsOBLd/Bc1UTfDMxJhNseo+hZemtkSXz2Jn51322F +Zw/FVN4ArXgluH+XsOhvG/MFFpojwZSrb0Qq5b1MRdo9qycq8lGqNtlN1WHqosDQ +zW29kpL0tlRrSDpww3wRESsN9rH5XIrJ1b3ZXuO7asR+KBVQMy/+NcUCgYEA6MSC +c+fywltKPgmPl5j0DPoDe5SXE/6JQy7w/vVGrGfWGf/zEJmhzS2R+CcfTTEqaT0T +Yw8+XbFgKAqsxwtE9MUXLTVLI3sSUyE4g7blCYscOqhZ8ItCUKDXWkSpt++rG0Um +1+cEJP/0oCazG6MWqvBC4NpQ1nzh46QpjWqMwokCgYAKDLXJ1p8rvx3vUeUJW6zR +dfPlEGCXuAyMwqHLxXgpf4EtSwhC5gSyPOtx2LqUtcrnpRmt6JfTH4ARYMW9TMef +QEhNQ+WYj213mKP/l235mg1gJPnNbUxvQR9lkFV8bk+AGJ32JRQQqRUTbU+yN2MQ +HEptnVqfTp3GtJIultfwOQKBgG+RyYmu8wBP650izg33BXu21raEeYne5oIqXN+I +R5DZ0JjzwtkBGroTDrVoYyuH1nFNEh7YLqeQHqvyufBKKYo9cid8NQDTu+vWr5UK +tGvHnwdKrJmM1oN5JOAiq0r7+QMAOWchVy449VNSWWV03aeftB685iR5BXkstbIQ +EVopAoGAfcGBTAhmceK/4Q83H/FXBWy0PAa1kZGg/q8+Z0KY76AqyxOVl0/CU/rB +3tO3sKhaMTHPME/MiQjQQGoaK1JgPY6JHYvly2KomrJ8QTugqNGyMzdVJkXAK2AM +GAwC8ivAkHf8CHrHa1W7l8t2IqBjW1aRt7mOW92nfG88Hck0Mbo= +-----END RSA PRIVATE KEY----- +""" + + +MASTER2_PUB_KEY = """ +-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAp+8cTxguO6Vg+YO92VfH +gNld3Zy8aM3JbZvpJcjTnis+YFJ7Zlkcc647yPRRwY9nYBNywahnt5kIeuT1rTvT +sMBZWvmUoEVUj1Xg8XXQkBvb9OzyGqy/G/p8KDDpzMP/U+XCnUeHiXTZrgnqgBIc +2cKeCVvWFqDi0GRFGzyaXLaX3PPmM7DJ0MIPL1qgmcDq6+7Ze0gJ9SrDYFAeLmbu +T1OqDfufXWQl/82JXeiwU2cOpqWq7n5fvPOWim7l1tzQ+dSiMRRm0xa6uNexCJww +3oJSwvMbAmgzvOhqqhlqv+K7u0u7FrFFojESsL36Gq4GBrISnvu2tk7u4GGNTYYQ +bQIDAQAB +-----END PUBLIC KEY----- +""" + + +MASTER_SIGNING_PRIV = """ +-----BEGIN RSA PRIVATE KEY----- +MIIEpAIBAAKCAQEAtieqrBMTM0MSIbhPKkDcozHqyXKyL/+bXYYw+iVPsns7c7bJ +zBqenLQlWoRVyrVyBFrrwQSrKu/0Mqn3l639iOGPlUoR3I7aZKIpyEdDkqd3xGIC +e+BtNNDqhUai67L63hEdG+iYAchi8UZw3LZGtcGpJ3FkBH4cYFX9EOam2QjbD7WY +EO7m1+j6XEYIOTCmAP9dGAvBbU0Jblc+wYxG3qNr+2dBWsK76QXWEqib2VSOGP+z +gjJa8tqY7PXXdOJpalQXNphmD/4o4pHKR4Euy0yL/1oMkpacmrV61LWB8Trnx9nS +9gdVrUteQF/cL1KAGwOsdVmiLpHfvqLLRqSAAQIDAQABAoIBABjB+HEN4Kixf4fk +wKHKEhL+SF6b/7sFX00NXZ/KLXRhSnnWSMQ8g/1hgMg2P2DfW4FbCDsCUu9xkLvI +HTZY+CJAIh9U42uaYPWXkt09TmJi76TZ+2Nx4/XvRUjbCm7Fs1I2ekHeUbbAUS5g ++BsPjTnL+h05zLHNoDa5yT0gVGIgFsQcX/w38arZCe8Rjp9le7PXUB5IIqASsDiw +t8zJvdyWToeXd0WswCHTQu5coHvKo5MCjIZZ1Ink1yJcCCc3rKDc+q3jB2z9T9oW +cUsKzJ4VuleiYj1eRxFITBmXbjKrb/GPRRUkeqCQbs68Hyj2d3UtOFDPeF4vng/3 +jGsHPq8CgYEA0AHAbwykVC6NMa37BTvEqcKoxbjTtErxR+yczlmVDfma9vkwtZvx +FJdbS/+WGA/ucDby5x5b2T5k1J9ueMR86xukb+HnyS0WKsZ94Ie8WnJAcbp+38M6 +7LD0u74Cgk93oagDAzUHqdLq9cXxv/ppBpxVB1Uvu8DfVMHj+wt6ie8CgYEA4C7u +u+6b8EmbGqEdtlPpScKG0WFstJEDGXRARDCRiVP2w6wm25v8UssCPvWcwf8U1Hoq +lhMY+H6a5dnRRiNYql1MGQAsqMi7VeJNYb0B1uxi7X8MPM+SvXoAglX7wm1z0cVy +O4CE5sEKbBg6aQabx1x9tzdrm80SKuSsLc5HRQ8CgYEAp/mCKSuQWNru8ruJBwTp +IB4upN1JOUN77ZVKW+lD0XFMjz1U9JPl77b65ziTQQM8jioRpkqB6cHVM088qxIh +vssn06Iex/s893YrmPKETJYPLMhqRNEn+JQ+To53ADykY0uGg0SD18SYMbmULHBP ++CKvF6jXT0vGDnA1ZzoxzskCgYEA2nQhYrRS9EVlhP93KpJ+A8gxA5tCCHo+YPFt +JoWFbCKLlYUNoHZR3IPCPoOsK0Zbj+kz0mXtsUf9vPkR+py669haLQqEejyQgFIz +QYiiYEKc6/0feapzvXtDP751w7JQaBtVAzJrT0jQ1SCO2oT8C7rPLlgs3fdpOq72 +MPSPcnUCgYBWHm6bn4HvaoUSr0v2hyD9fHZS/wDTnlXVe5c1XXgyKlJemo5dvycf +HUCmN/xIuO6AsiMdqIzv+arNJdboz+O+bNtS43LkTJfEH3xj2/DdUogdvOgG/iPM +u9KBT1h+euws7PqC5qt4vqLwCTTCZXmUS8Riv+62RCC3kZ5AbpT3ZA== +-----END RSA PRIVATE KEY----- +""" + +MASTER_SIGNING_PUB = """ +-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtieqrBMTM0MSIbhPKkDc +ozHqyXKyL/+bXYYw+iVPsns7c7bJzBqenLQlWoRVyrVyBFrrwQSrKu/0Mqn3l639 +iOGPlUoR3I7aZKIpyEdDkqd3xGICe+BtNNDqhUai67L63hEdG+iYAchi8UZw3LZG +tcGpJ3FkBH4cYFX9EOam2QjbD7WYEO7m1+j6XEYIOTCmAP9dGAvBbU0Jblc+wYxG +3qNr+2dBWsK76QXWEqib2VSOGP+zgjJa8tqY7PXXdOJpalQXNphmD/4o4pHKR4Eu +y0yL/1oMkpacmrV61LWB8Trnx9nS9gdVrUteQF/cL1KAGwOsdVmiLpHfvqLLRqSA +AQIDAQAB +-----END PUBLIC KEY----- +""" + +MINION_PRIV_KEY = """ +-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEAsT6TwnlI0L7urjXu6D5E11tFJ/NglQ45jW/WN9tAUNvphq6Q +cjJCd/aWmdqlqe7ix8y9M/8rgwghRQsnPXblVBvPwFcUEXhMRnOGzqbq/0zyQX01 +KecT0plBhlDt2lTyCLU6E4XCqyLbPfOxgXzsVqM0/TnzRtpVvGNy+5N4eFGylrjb +cJhPxKt2G9TDOCM/hYacDs5RVIYQQmcYb8LJq7G3++FfWpYRDaxdKoHNFDspEynd +jzr67hgThnwzc388OKNJx/7B2atwPTunPb3YBjgwDyRO/01OKK4gUHdw5KoctFgp +kDCDjwjemlyXV+MYODRTIdtOlAP83ZkntEuLoQIDAQABAoIBAAJOKNtvFGfF2l9H +S4CXZSUGU0a+JaCkR+wmnjsPwPn/dXDpAe8nGpidpNicPWqRm6WABjeQHaxda+fB +lpSrRtEdo3zoi2957xQJ5wddDtI1pmXJQrdbm0H/K39oIg/Xtv/IZT769TM6OtVg +paUxG/aftmeGXDtGfIL8w1jkuPABRBLOakWQA9uVdeG19KTU0Ag8ilpJdEX64uFJ +W75bpVjT+KO/6aV1inuCntQSP097aYvUWajRwuiYVJOxoBZHme3IObcE6mdnYXeQ +wblyWBpJUHrOS4MP4HCODV2pHKZ2rr7Nwhh8lMNw/eY9OP0ifz2AcAqe3sUMQOKP +T0qRC6ECgYEAyeU5JvUPOpxXvvChYh6gJ8pYTIh1ueDP0O5e4t3vhz6lfy9DKtRN +ROJLUorHvw/yVXMR72nT07a0z2VswcrUSw8ov3sI53F0NkLGEafQ35lVhTGs4vTl +CFoQCuAKPsxeUl4AIbfbpkDsLGQqzW1diFArK7YeQkpGuGaGodXl480CgYEA4L40 +x5cUXnAhTPsybo7sbcpiwFHoGblmdkvpYvHA2QxtNSi2iHHdqGo8qP1YsZjKQn58 +371NhtqidrJ6i/8EBFP1dy+y/jr9qYlZNNGcQeBi+lshrEOIf1ct56KePG79s8lm +DmD1OY8tO2R37+Py46Nq1n6viT/ST4NjLQI3GyUCgYEAiOswSDA3ZLs0cqRD/gPg +/zsliLmehTFmHj4aEWcLkz+0Ar3tojUaNdX12QOPFQ7efH6uMhwl8NVeZ6xUBlTk +hgbAzqLE1hjGBCpiowSZDZqyOcMHiV8ll/VkHcv0hsQYT2m6UyOaDXTH9g70TB6Y +KOKddGZsvO4cad/1+/jQkB0CgYAzDEEkzLY9tS57M9uCrUgasAu6L2CO50PUvu1m +Ig9xvZbYqkS7vVFhva/FmrYYsOHQNLbcgz0m0mZwm52mSuh4qzFoPxdjE7cmWSJA +ExRxCiyxPR3q6PQKKJ0urgtPIs7RlX9u6KsKxfC6OtnbTWWQO0A7NE9e13ZHxUoz +oPsvWQKBgCa0+Fb2lzUeiQz9bV1CBkWneDZUXuZHmabAZomokX+h/bq+GcJFzZjW +3kAHwYkIy9IAy3SyO/6CP0V3vAye1p+XbotiwsQ/XZnr0pflSQL3J1l1CyN3aopg +Niv7k/zBn15B72aK73R/CpUSk9W/eJGqk1NcNwf8hJHsboRYx6BR +-----END RSA PRIVATE KEY----- +""" + + +MINION_PUB_KEY = """ +-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsT6TwnlI0L7urjXu6D5E +11tFJ/NglQ45jW/WN9tAUNvphq6QcjJCd/aWmdqlqe7ix8y9M/8rgwghRQsnPXbl +VBvPwFcUEXhMRnOGzqbq/0zyQX01KecT0plBhlDt2lTyCLU6E4XCqyLbPfOxgXzs +VqM0/TnzRtpVvGNy+5N4eFGylrjbcJhPxKt2G9TDOCM/hYacDs5RVIYQQmcYb8LJ +q7G3++FfWpYRDaxdKoHNFDspEyndjzr67hgThnwzc388OKNJx/7B2atwPTunPb3Y +BjgwDyRO/01OKK4gUHdw5KoctFgpkDCDjwjemlyXV+MYODRTIdtOlAP83ZkntEuL +oQIDAQAB +-----END PUBLIC KEY----- +""" + +AES_KEY = "8wxWlOaMMQ4d3yT74LL4+hGrGTf65w8VgrcNjLJeLRQ2Q6zMa8ItY2EQUgMKKDb7JY+RnPUxbB0=" + + +@pytest.fixture +def signing_algorithm(): + if FIPS_TESTRUN: + return salt.crypt.PKCS1v15_SHA224 + return salt.crypt.PKCS1v15_SHA1 + + +@pytest.fixture +def encryption_algorithm(): + if FIPS_TESTRUN: + return salt.crypt.OAEP_SHA224 + return salt.crypt.OAEP_SHA1 + + +@pytest.fixture +def pki_dir(tmp_path): + _pki_dir = tmp_path / "pki" + _pki_dir.mkdir() + madir = _pki_dir / "master" + madir.mkdir() + + mapriv = madir / "master.pem" + mapriv.write_text(MASTER_PRIV_KEY.strip()) + mapub = madir / "master.pub" + mapub.write_text(MASTER_PUB_KEY.strip()) + + maspriv = madir / "master_sign.pem" + maspriv.write_text(MASTER_SIGNING_PRIV.strip()) + maspub = madir / "master_sign.pub" + maspub.write_text(MASTER_SIGNING_PUB.strip()) + + misdir = madir / "minions" + misdir.mkdir() + misdir.joinpath("minion").write_text(MINION_PUB_KEY.strip()) + for sdir in [ + "minions_autosign", + "minions_denied", + "minions_pre", + "minions_rejected", + ]: + madir.joinpath(sdir).mkdir() + + midir = _pki_dir / "minion" + midir.mkdir() + mipub = midir / "minion.pub" + mipub.write_text(MINION_PUB_KEY.strip()) + mipriv = midir / "minion.pem" + mipriv.write_text(MINION_PRIV_KEY.strip()) + mimapriv = midir / "minion_master.pub" + mimapriv.write_text(MASTER_PUB_KEY.strip()) + mimaspriv = midir / "master_sign.pub" + mimaspriv.write_text(MASTER_SIGNING_PUB.strip()) + yield _pki_dir + + +def run_loop_in_thread(loop, evt): + """ + Run the provided loop until an event is set + """ + loop.make_current() + + @tornado.gen.coroutine + def stopper(): + yield tornado.gen.sleep(0.1) + while True: + if not evt.is_set(): + loop.stop() + break + yield tornado.gen.sleep(0.3) + + loop.add_callback(evt.set) + loop.add_callback(stopper) + try: + loop.start() + finally: + loop.close() + + +class MockSaltMinionMaster: + mock = MagicMock() + + def __init__(self, temp_salt_minion, temp_salt_master): + SMaster.secrets["aes"] = { + "secret": multiprocessing.Array( + ctypes.c_char, + salt.utils.stringutils.to_bytes( + salt.crypt.Crypticle.generate_key_string() + ), + ), + "reload": salt.crypt.Crypticle.generate_key_string, + } + self.process_manager = salt.utils.process.ProcessManager( + name="ReqServer_ProcessManager" + ) + + master_opts = temp_salt_master.config.copy() + master_opts.update({"transport": "zeromq"}) + self.server_channel = salt.channel.server.ReqServerChannel.factory(master_opts) + self.server_channel.pre_fork(self.process_manager) + + self.io_loop = tornado.ioloop.IOLoop() + self.evt = threading.Event() + self.server_channel.post_fork(self._handle_payload, io_loop=self.io_loop) + self.server_thread = threading.Thread( + target=run_loop_in_thread, args=(self.io_loop, self.evt) + ) + self.server_thread.start() + minion_opts = temp_salt_minion.config.copy() + minion_opts.update( + { + "master_ip": "127.0.0.1", + "transport": "zeromq", + } + ) + self.channel = salt.channel.client.ReqChannel.factory( + minion_opts, crypt="clear" + ) + + def __enter__(self): + self.channel.__enter__() + self.evt.wait() + return self + + def __exit__(self, *args, **kwargs): + self.channel.__exit__(*args, **kwargs) + del self.channel + # Attempting to kill the children hangs the test suite. + # Let the test suite handle this instead. + self.process_manager.stop_restarting() + self.process_manager.kill_children() + self.evt.clear() + self.server_thread.join() + # Give the procs a chance to fully close before we stop the io_loop + time.sleep(2) + self.server_channel.close() + SMaster.secrets.pop("aes") + del self.server_channel + del self.io_loop + del self.process_manager + del self.server_thread + + # pylint: enable=W1701 + @classmethod + @tornado.gen.coroutine + def _handle_payload(cls, payload): + """ + TODO: something besides echo + """ + cls.mock._handle_payload_hook() + raise tornado.gen.Return((payload, {"fun": "send_clear"})) + + async def test_req_server_garbage_request(io_loop): """ Validate invalid msgpack messages will not raise exceptions in the diff --git a/tests/pytests/unit/utils/test_crypt.py b/tests/pytests/unit/utils/test_crypt.py index ccf2cfbf46e..3aa1c409751 100644 --- a/tests/pytests/unit/utils/test_crypt.py +++ b/tests/pytests/unit/utils/test_crypt.py @@ -5,29 +5,6 @@ Unit tests for salt.utils.crypt.py import pytest import salt.utils.crypt -from tests.support.mock import patch - -try: - import M2Crypto # pylint: disable=unused-import - - HAS_M2CRYPTO = True -except ImportError: - HAS_M2CRYPTO = False - -try: - from Cryptodome import Random as CryptodomeRandom - - HAS_CYPTODOME = True -except ImportError: - HAS_CYPTODOME = False - - -try: - from Crypto import Random as CryptoRandom # nosec - - HAS_CRYPTO = True -except ImportError: - HAS_CRYPTO = False @pytest.fixture @@ -45,28 +22,6 @@ def pub_key_data(): ] -def test_random(): - # make sure the right library is used for random - if HAS_M2CRYPTO: - assert None is salt.utils.crypt.Random - elif HAS_CYPTODOME: - assert CryptodomeRandom is salt.utils.crypt.Random - elif HAS_CRYPTO: - assert CryptoRandom is salt.utils.crypt.Random - - -def test_reinit_crypto(): - # make sure reinit crypto does not crash - salt.utils.crypt.reinit_crypto() - - # make sure reinit does not crash when no crypt is found - with patch("salt.utils.crypt.HAS_M2CRYPTO", False): - with patch("salt.utils.crypt.HAS_CRYPTODOME", False): - with patch("salt.utils.crypt.HAS_CRYPTO", False): - with patch("salt.utils.crypt.Random", None): - salt.utils.crypt.reinit_crypto() - - @pytest.mark.parametrize("line_ending", ["\n", "\r\n"]) def test_pem_finger_file_line_endings(tmp_path, pub_key_data, line_ending): key_file = tmp_path / "master_crlf.pub" diff --git a/tests/pytests/unit/utils/test_gitfs_locks.py b/tests/pytests/unit/utils/test_gitfs_locks.py new file mode 100644 index 00000000000..b599a0733b0 --- /dev/null +++ b/tests/pytests/unit/utils/test_gitfs_locks.py @@ -0,0 +1,594 @@ +""" +These only test the provider selection and verification logic, they do not init +any remotes. +""" + +import logging +import os +import pathlib +import signal +import time + +import pytest +import tornado.ioloop +from saltfactories.utils import random_string + +import salt.fileserver.gitfs +import salt.utils.files +import salt.utils.gitfs +import salt.utils.path +import salt.utils.platform +import salt.utils.process +from salt.utils.immutabletypes import freeze +from salt.utils.platform import get_machine_identifier as _get_machine_identifier +from salt.utils.verify import verify_env + +try: + import pwd +except ImportError: + import salt.utils.win_functions + +pytestmark = [pytest.mark.skip_on_windows] + +log = logging.getLogger(__name__) + + +def _get_user(): + """ + Get the user associated with the current process. + """ + if salt.utils.platform.is_windows(): + return salt.utils.win_functions.get_current_user(with_domain=False) + return pwd.getpwuid(os.getuid())[0] + + +def _clear_instance_map(): + try: + del salt.utils.gitfs.GitFS.instance_map[tornado.ioloop.IOLoop.current()] + except KeyError: + pass + + +class MyMockedGitProvider: + """ + mocked GitFS provider leveraging tmp_path + """ + + def __init__( + self, + salt_factories_default_root_dir, + temp_salt_master, + temp_salt_minion, + tmp_path, + ): + self._tmp_name = str(tmp_path) + + self._root_dir = str(salt_factories_default_root_dir) + self._master_cfg = str(temp_salt_master.config["conf_file"]) + self._minion_cfg = str(temp_salt_minion.config["conf_file"]) + self._user = _get_user() + + tmp_name = self._tmp_name.join("/git_test") + pathlib.Path(tmp_name).mkdir(exist_ok=True, parents=True) + + class MockedProvider( + salt.utils.gitfs.GitProvider + ): # pylint: disable=abstract-method + def __init__( + self, + opts, + remote, + per_remote_defaults, + per_remote_only, + override_params, + cache_root, + role="gitfs", + ): + self.provider = "mocked" + self.fetched = False + super().__init__( + opts, + remote, + per_remote_defaults, + per_remote_only, + override_params, + cache_root, + role, + ) + + def init_remote(self): + self.gitdir = salt.utils.path.join(tmp_name, ".git") + self.repo = True + new = False + return new + + def envs(self): + return ["base"] + + def _fetch(self): + self.fetched = True + + # Clear the instance map so that we make sure to create a new instance + # for this test class. + _clear_instance_map() + + git_providers = { + "mocked": MockedProvider, + } + gitfs_remotes = ["file://repo1.git", {"file://repo2.git": [{"name": "repo2"}]}] + + self.opts = self.get_temp_config( + "master", + gitfs_remotes=gitfs_remotes, + verified_gitfs_provider="mocked", + ) + self.main_class = salt.utils.gitfs.GitFS( + self.opts, + self.opts["gitfs_remotes"], + per_remote_overrides=salt.fileserver.gitfs.PER_REMOTE_OVERRIDES, + per_remote_only=salt.fileserver.gitfs.PER_REMOTE_ONLY, + git_providers=git_providers, + ) + + def cleanup(self): + # Providers are preserved with GitFS's instance_map + for remote in self.main_class.remotes: + remote.fetched = False + del self.main_class + + def get_temp_config(self, config_for, **config_overrides): + + rootdir = config_overrides.get("root_dir", self._root_dir) + + if not pathlib.Path(rootdir).exists(): + pathlib.Path(rootdir).mkdir(exist_ok=True, parents=True) + + conf_dir = config_overrides.pop( + "conf_dir", str(pathlib.PurePath(rootdir).joinpath("conf")) + ) + + for key in ("cachedir", "pki_dir", "sock_dir"): + if key not in config_overrides: + config_overrides[key] = key + if "log_file" not in config_overrides: + config_overrides["log_file"] = f"logs/{config_for}.log".format() + if "user" not in config_overrides: + config_overrides["user"] = self._user + config_overrides["root_dir"] = rootdir + + cdict = self.get_config( + config_for, + from_scratch=True, + ) + + if config_for in ("master", "client_config"): + rdict = salt.config.apply_master_config(config_overrides, cdict) + if config_for == "minion": + minion_id = ( + config_overrides.get("id") + or config_overrides.get("minion_id") + or cdict.get("id") + or cdict.get("minion_id") + or random_string("temp-minion-") + ) + config_overrides["minion_id"] = config_overrides["id"] = minion_id + rdict = salt.config.apply_minion_config( + config_overrides, cdict, cache_minion_id=False, minion_id=minion_id + ) + + verify_env( + [ + pathlib.PurePath(rdict["pki_dir"]).joinpath("minions"), + pathlib.PurePath(rdict["pki_dir"]).joinpath("minions_pre"), + pathlib.PurePath(rdict["pki_dir"]).joinpath("minions_rejected"), + pathlib.PurePath(rdict["pki_dir"]).joinpath("minions_denied"), + pathlib.PurePath(rdict["cachedir"]).joinpath("jobs"), + pathlib.PurePath(rdict["cachedir"]).joinpath("tokens"), + pathlib.PurePath(rdict["root_dir"]).joinpath("cache", "tokens"), + pathlib.PurePath(rdict["pki_dir"]).joinpath("accepted"), + pathlib.PurePath(rdict["pki_dir"]).joinpath("rejected"), + pathlib.PurePath(rdict["pki_dir"]).joinpath("pending"), + pathlib.PurePath(rdict["log_file"]).parent, + rdict["sock_dir"], + conf_dir, + ], + self._user, + root_dir=rdict["root_dir"], + ) + + rdict["conf_file"] = pathlib.PurePath(conf_dir).joinpath(config_for) + with salt.utils.files.fopen(rdict["conf_file"], "w") as wfh: + salt.utils.yaml.safe_dump(rdict, wfh, default_flow_style=False) + return rdict + + def get_config( + self, + config_for, + from_scratch=False, + ): + if from_scratch: + if config_for in ("master"): + return salt.config.master_config(self._master_cfg) + elif config_for in ("minion"): + return salt.config.minion_config(self._minion_cfg) + elif config_for == "client_config": + return salt.config_client_config(self._master_cfg) + if config_for not in ("master", "minion", "client_config"): + if config_for in ("master"): + return freeze(salt.config.master_config(self._master_cfg)) + elif config_for in ("minion"): + return freeze(salt.config.minion_config(self._minion_cfg)) + elif config_for == "client_config": + return freeze(salt.config.client_config(self._master_cfg)) + + log.error( + "Should not reach this section of code for get_config, missing support for input config_for %s", + config_for, + ) + + # at least return master's config + return freeze(salt.config.master_config(self._master_cfg)) + + @property + def config_dir(self): + return str(pathlib.PurePath(self._master_cfg).parent) + + def get_config_dir(self): + log.warning("Use the config_dir attribute instead of calling get_config_dir()") + return self.config_dir + + def get_config_file_path(self, filename): + if filename == "master": + return str(self._master_cfg) + + if filename == "minion": + return str(self._minion_cfg) + + return str(self._master_cfg) + + @property + def master_opts(self): + """ + Return the options used for the master + """ + return self.get_config("master") + + @property + def minion_opts(self): + """ + Return the options used for the minion + """ + return self.get_config("minion") + + +@pytest.fixture +def main_class( + salt_factories_default_root_dir, + temp_salt_master, + temp_salt_minion, + tmp_path, +): + my_git_base = MyMockedGitProvider( + salt_factories_default_root_dir, + temp_salt_master, + temp_salt_minion, + tmp_path, + ) + yield my_git_base.main_class + + my_git_base.cleanup() + + +def test_update_all(main_class): + main_class.update() + assert len(main_class.remotes) == 2, "Wrong number of remotes" + assert main_class.remotes[0].fetched + assert main_class.remotes[1].fetched + + +def test_update_by_name(main_class): + main_class.update("repo2") + assert len(main_class.remotes) == 2, "Wrong number of remotes" + assert not main_class.remotes[0].fetched + assert main_class.remotes[1].fetched + + +def test_update_by_id_and_name(main_class): + main_class.update([("file://repo1.git", None)]) + assert len(main_class.remotes) == 2, "Wrong number of remotes" + assert main_class.remotes[0].fetched + assert not main_class.remotes[1].fetched + + +def test_get_cachedir_basename(main_class): + assert main_class.remotes[0].get_cache_basename() == "_" + assert main_class.remotes[1].get_cache_basename() == "_" + + +def test_git_provider_mp_lock_and_clear_lock(main_class): + """ + Check that lock is released after provider.lock() + and that lock is released after provider.clear_lock() + """ + provider = main_class.remotes[0] + provider.lock() + # check that lock has been released + assert provider._master_lock.acquire(timeout=5) + provider._master_lock.release() + + provider.clear_lock() + # check that lock has been released + assert provider._master_lock.acquire(timeout=5) + provider._master_lock.release() + + +@pytest.mark.slow_test +@pytest.mark.timeout_unless_on_windows(120) +def test_git_provider_mp_lock_timeout(main_class): + """ + Check that lock will time out if master lock is locked. + """ + provider = main_class.remotes[0] + # Hijack the lock so git provider is fooled into thinking another instance is doing somthing. + assert provider._master_lock.acquire(timeout=5) + try: + # git provider should raise timeout error to avoid lock race conditions + pytest.raises(TimeoutError, provider.lock) + finally: + provider._master_lock.release() + + +@pytest.mark.slow_test +@pytest.mark.timeout_unless_on_windows(120) +def test_git_provider_mp_clear_lock_timeout(main_class): + """ + Check that clear lock will time out if master lock is locked. + """ + provider = main_class.remotes[0] + # Hijack the lock so git provider is fooled into thinking another instance is doing somthing. + assert provider._master_lock.acquire(timeout=5) + try: + # git provider should raise timeout error to avoid lock race conditions + pytest.raises(TimeoutError, provider.clear_lock) + finally: + provider._master_lock.release() + + +@pytest.mark.slow_test +@pytest.mark.timeout_unless_on_windows(120) +def test_git_provider_mp_gen_lock(main_class, caplog): + """ + Check that gen_lock is obtains lock, and then releases, provider.lock() + """ + # get machine_identifier + mach_id = _get_machine_identifier().get("machine_id", "no_machine_id_available") + cur_pid = os.getpid() + + test_msg1 = ( + f"Set update lock for gitfs remote 'file://repo1.git' on machine_id '{mach_id}'" + ) + test_msg2 = ( + "Attempting to remove 'update' lock for 'gitfs' remote 'file://repo1.git' " + "due to lock_set1 'True' or lock_set2" + ) + test_msg3 = f"Removed update lock for gitfs remote 'file://repo1.git' on machine_id '{mach_id}'" + + provider = main_class.remotes[0] + + # loop seeing if the test can be made to mess up a lock/unlock sequence + max_count = 10000 + count = 0 + while count < max_count: + count = count + 1 + caplog.clear() + with caplog.at_level(logging.DEBUG): + provider.fetch() + + assert test_msg1 in caplog.text + assert test_msg2 in caplog.text + assert test_msg3 in caplog.text + + caplog.clear() + + +@pytest.mark.slow_test +@pytest.mark.timeout_unless_on_windows(120) +def test_git_provider_mp_lock_dead_pid(main_class, caplog): + """ + Check that lock obtains lock, if previous pid in lock file doesn't exist for same machine id + """ + # get machine_identifier + mach_id = _get_machine_identifier().get("machine_id", "no_machine_id_available") + cur_pid = os.getpid() + + test_msg1 = ( + f"Set update lock for gitfs remote 'file://repo1.git' on machine_id '{mach_id}'" + ) + test_msg3 = f"Removed update lock for gitfs remote 'file://repo1.git' on machine_id '{mach_id}'" + + provider = main_class.remotes[0] + provider.lock() + # check that lock has been released + assert provider._master_lock.acquire(timeout=5) + + # get lock file and manipulate it for a dead pid + file_name = provider._get_lock_file("update") + dead_pid = 1234 # give it non-existant pid + test_msg2 = ( + f"gitfs_global_lock is enabled and update lockfile {file_name} " + "is present for gitfs remote 'file://repo1.git' on machine_id " + f"{mach_id} with pid '{dead_pid}'. Process {dead_pid} obtained " + f"the lock for machine_id {mach_id}, current machine_id {mach_id} " + "but this process is not running. The update may have been " + "interrupted. Given this process is for the same machine the " + "lock will be reallocated to new process" + ) + + # remove existing lock file and write fake lock file with bad pid + assert pathlib.Path(file_name).is_file() + pathlib.Path(file_name).unlink() + + try: + # write lock file similar to salt/utils/gitfs.py + fh_ = os.open(file_name, os.O_CREAT | os.O_EXCL | os.O_WRONLY) + with os.fdopen(fh_, "wb"): + # Write the lock file and close the filehandle + os.write(fh_, salt.utils.stringutils.to_bytes(str(dead_pid))) + os.write(fh_, salt.utils.stringutils.to_bytes("\n")) + os.write(fh_, salt.utils.stringutils.to_bytes(str(mach_id))) + os.write(fh_, salt.utils.stringutils.to_bytes("\n")) + + except OSError as exc: + log.error( + "Failed to write fake dead pid lock file %s, exception %s", file_name, exc + ) + + finally: + provider._master_lock.release() + + caplog.clear() + with caplog.at_level(logging.DEBUG): + provider.lock() + # check that lock has been released + assert provider._master_lock.acquire(timeout=5) + provider._master_lock.release() + + provider.clear_lock() + # check that lock has been released + assert provider._master_lock.acquire(timeout=5) + provider._master_lock.release() + + assert test_msg1 in caplog.text + assert test_msg2 in caplog.text + assert test_msg3 in caplog.text + caplog.clear() + + +@pytest.mark.slow_test +@pytest.mark.timeout_unless_on_windows(120) +def test_git_provider_mp_lock_bad_machine(main_class, caplog): + """ + Check that lock obtains lock, if previous pid in lock file doesn't exist for same machine id + """ + # get machine_identifier + mach_id = _get_machine_identifier().get("machine_id", "no_machine_id_available") + cur_pid = os.getpid() + + provider = main_class.remotes[0] + provider.lock() + # check that lock has been released + assert provider._master_lock.acquire(timeout=5) + + # get lock file and manipulate it for a dead pid + file_name = provider._get_lock_file("update") + bad_mach_id = "abcedf0123456789" # give it non-existant pid + + test_msg1 = ( + f"gitfs_global_lock is enabled and update lockfile {file_name} " + "is present for gitfs remote 'file://repo1.git' on machine_id " + f"{mach_id} with pid '{cur_pid}'. Process {cur_pid} obtained " + f"the lock for machine_id {bad_mach_id}, current machine_id {mach_id}" + ) + test_msg2 = f"Removed update lock for gitfs remote 'file://repo1.git' on machine_id '{mach_id}'" + + # remove existing lock file and write fake lock file with bad pid + assert pathlib.Path(file_name).is_file() + pathlib.Path(file_name).unlink() + + try: + # write lock file similar to salt/utils/gitfs.py + fh_ = os.open(file_name, os.O_CREAT | os.O_EXCL | os.O_WRONLY) + with os.fdopen(fh_, "wb"): + # Write the lock file and close the filehandle + os.write(fh_, salt.utils.stringutils.to_bytes(str(cur_pid))) + os.write(fh_, salt.utils.stringutils.to_bytes("\n")) + os.write(fh_, salt.utils.stringutils.to_bytes(str(bad_mach_id))) + os.write(fh_, salt.utils.stringutils.to_bytes("\n")) + + except OSError as exc: + log.error( + "Failed to write fake dead pid lock file %s, exception %s", file_name, exc + ) + + finally: + provider._master_lock.release() + + caplog.clear() + with caplog.at_level(logging.DEBUG): + provider.lock() + # check that lock has been released + assert provider._master_lock.acquire(timeout=5) + provider._master_lock.release() + + provider.clear_lock() + # check that lock has been released + assert provider._master_lock.acquire(timeout=5) + provider._master_lock.release() + + assert test_msg1 in caplog.text + assert test_msg2 in caplog.text + caplog.clear() + + +class KillProcessTest(salt.utils.process.SignalHandlingProcess): + """ + Test process for which to kill and check lock resources are cleaned up + """ + + def __init__(self, provider, **kwargs): + super().__init__(**kwargs) + self.provider = provider + self.opts = provider.opts + self.threads = {} + + def run(self): + """ + Start the test process to kill + """ + self.provider.lock() + lockfile = self.provider._get_lock_file() + log.debug("KillProcessTest acquried lock file %s", lockfile) + + killtest_pid = os.getpid() + + # check that lock has been released + assert self.provider._master_lock.acquire(timeout=5) + + while True: + tsleep = 1 + time.sleep(tsleep) # give time for kill by sigterm + + +@pytest.mark.slow_test +@pytest.mark.skip_unless_on_linux +@pytest.mark.timeout_unless_on_windows(120) +def test_git_provider_sigterm_cleanup(main_class): + """ + Start process which will obtain lock, and leave it locked + then kill the process via SIGTERM and ensure locked resources are cleaned up + """ + provider = main_class.remotes[0] + + with salt.utils.process.default_signals(signal.SIGINT, signal.SIGTERM): + procmgr = salt.utils.process.ProcessManager(wait_for_kill=1) + proc = procmgr.add_process(KillProcessTest, args=(provider,), name="test_kill") + + while not proc.is_alive(): + time.sleep(1) # give some time for it to be started + + procmgr.run(asynchronous=True) + + time.sleep(2) # give some time for it to terminate + + # child process should be alive + file_name = provider._get_lock_file("update") + + assert pathlib.Path(file_name).exists() + assert pathlib.Path(file_name).is_file() + + procmgr.terminate() # sends a SIGTERM + + time.sleep(2) # give some time for it to terminate + + assert not proc.is_alive() + assert not pathlib.Path(file_name).exists() diff --git a/tests/support/gitfs.py b/tests/support/gitfs.py index 82a5d50d7c0..6ab90f4c8a4 100644 --- a/tests/support/gitfs.py +++ b/tests/support/gitfs.py @@ -6,6 +6,7 @@ import errno import logging import os import shutil +import subprocess import tempfile import textwrap @@ -595,6 +596,12 @@ class GitPillarSSHTestBase(GitPillarTestBase): ) self.make_repo(root_dir, user=self.username) self.make_extra_repo(root_dir, user=self.username) + # Force git repo ownership to prevent "fatal: detected dubious + # ownership in repository" errors. + subprocess.run( + ["chown", "-R", f"{self.username}:users", f"/home/{self.username}"], + check=True, + ) log.info("%s.setUp() complete.", self.__class__.__name__) def get_pillar(self, ext_pillar_conf): diff --git a/tests/unit/test_config.py b/tests/unit/test_config.py index c5196372b32..a67ae52bbb9 100644 --- a/tests/unit/test_config.py +++ b/tests/unit/test_config.py @@ -9,6 +9,7 @@ import textwrap import pytest import salt.config +import salt.crypt import salt.minion import salt.syspaths import salt.utils.files @@ -1797,6 +1798,11 @@ class ConfigTestCase(TestCase, AdaptedConfigurationTestCaseMixin): "worker_threads": 5, "hash_type": "sha256", "log_file": "foo.log", + # Crypto config for minion + "encryption_algorithm": salt.crypt.OAEP_SHA1, + "signing_algorithm": salt.crypt.PKCS1v15_SHA1, + # Crypto config for master + "publish_signing_algorithm": salt.crypt.PKCS1v15_SHA1, } ret.update(kwargs) return ret diff --git a/tools/ci.py b/tools/ci.py index f44ba38c066..1eaf77fe2a9 100644 --- a/tools/ci.py +++ b/tools/ci.py @@ -683,8 +683,8 @@ def matrix( for transport in ("zeromq", "tcp"): if transport == "tcp": if distro_slug not in ( - "centosstream-9", - "centosstream-9-arm64", + "rockylinux-9", + "rockylinux-9-arm64", "photonos-5", "photonos-5-arm64", "ubuntu-22.04", @@ -832,7 +832,7 @@ def pkg_matrix( if name == "amazonlinux": name = "amazon" - elif "centos" in name or name == "rockylinux": + elif name == "rockylinux": name = "redhat" elif "photon" in name: name = "photon" @@ -977,8 +977,8 @@ def get_ci_deps_matrix(ctx: Context): _matrix = { "linux": [ - {"distro-slug": "centos-7", "arch": "x86_64"}, - {"distro-slug": "centos-7-arm64", "arch": "arm64"}, + {"distro-slug": "amazonlinux-2", "arch": "x86_64"}, + {"distro-slug": "amazonlinux-2-arm64", "arch": "arm64"}, ], "macos": [ {"distro-slug": "macos-12", "arch": "x86_64"}, @@ -1041,7 +1041,6 @@ def get_pkg_downloads_matrix(ctx: Context): rpm_slugs = ( "rockylinux", "amazonlinux", - "centos", "fedora", "photon", ) diff --git a/tools/pkg/repo/create.py b/tools/pkg/repo/create.py index b62daf294f1..ee494430111 100644 --- a/tools/pkg/repo/create.py +++ b/tools/pkg/repo/create.py @@ -435,7 +435,7 @@ def rpm( createrepo = shutil.which("createrepo") if createrepo is None: - container = "ghcr.io/saltstack/salt-ci-containers/packaging:centosstream-9" + container = "ghcr.io/saltstack/salt-ci-containers/packaging:rockylinux-9" ctx.info(f"Using docker container '{container}' to call 'createrepo'...") uid = ctx.run("id", "-u", capture=True).stdout.strip().decode() gid = ctx.run("id", "-g", capture=True).stdout.strip().decode() @@ -493,7 +493,7 @@ def rpm( if distro == "amazon": distro_name = "Amazon Linux" elif distro == "redhat": - distro_name = "RHEL/CentOS" + distro_name = "RHEL" else: distro_name = distro.capitalize() diff --git a/tools/precommit/workflows.py b/tools/precommit/workflows.py index 56ba756ebb7..bd1fdca76df 100644 --- a/tools/precommit/workflows.py +++ b/tools/precommit/workflows.py @@ -52,7 +52,6 @@ TEST_SALT_LISTING = PlatformDefinitions( arch="arm64", ), Linux(slug="archlinux-lts", display_name="Arch Linux LTS", arch="x86_64"), - Linux(slug="centos-7", display_name="CentOS 7", arch="x86_64"), Linux(slug="debian-11", display_name="Debian 11", arch="x86_64"), Linux(slug="debian-11-arm64", display_name="Debian 11 Arm64", arch="arm64"), Linux(slug="debian-12", display_name="Debian 12", arch="x86_64"), @@ -245,12 +244,6 @@ def generate_workflows(ctx: Context): arch="arm64", pkg_type="rpm", ), - Linux( - slug="centos-7", - display_name="CentOS 7", - arch="x86_64", - pkg_type="rpm", - ), Linux( slug="debian-11", display_name="Debian 11", @@ -425,9 +418,7 @@ def generate_workflows(ctx: Context): for slug in sorted(tools.utils.get_golden_images()): if slug.endswith("-arm64"): continue - if not slug.startswith( - ("amazonlinux", "rockylinux", "centos", "fedora", "photonos") - ): + if not slug.startswith(("amazonlinux", "rockylinux", "fedora", "photonos")): continue os_name, os_version = slug.split("-") if os_name == "amazonlinux": diff --git a/tools/utils/gh.py b/tools/utils/gh.py index 9360a799fe5..8d030dbc07b 100644 --- a/tools/utils/gh.py +++ b/tools/utils/gh.py @@ -219,7 +219,7 @@ def download_pkgs_artifact( if slug.startswith(("debian", "ubuntu")): artifact_name += f"{arch}-deb" elif slug.startswith( - ("rockylinux", "amazonlinux", "centos", "fedora", "opensuse", "photonos") + ("rockylinux", "amazonlinux", "fedora", "opensuse", "photonos") ): artifact_name += f"{arch}-rpm" else: diff --git a/tools/vm.py b/tools/vm.py index d9e9c1e6e5b..13103eda912 100644 --- a/tools/vm.py +++ b/tools/vm.py @@ -1469,7 +1469,7 @@ class VM: cmd += ["--"] + session_args if env is None: env = {} - for key in ("CI", "PIP_INDEX_URL", "PIP_EXTRA_INDEX_URL"): + for key in ("CI", "PIP_INDEX_URL", "PIP_TRUSTED_HOST", "PIP_EXTRA_INDEX_URL"): if key in os.environ: env[key] = os.environ[key] env["PYTHONUTF8"] = "1"