diff --git a/.github/workflows/ci_decrypt-oracle.yaml b/.github/workflows/ci_decrypt-oracle.yaml index baf01c571..c56e43a63 100644 --- a/.github/workflows/ci_decrypt-oracle.yaml +++ b/.github/workflows/ci_decrypt-oracle.yaml @@ -1,11 +1,10 @@ name: Continuous Integration tests for the decrypt oracle on: - pull_request: - push: - # Run once a day - schedule: - - cron: '0 0 * * *' + workflow_call: + +permissions: + contents: read jobs: tests: diff --git a/.github/workflows/ci_static-analysis.yaml b/.github/workflows/ci_static-analysis.yaml index 7f74e8fc3..37a5e0cf3 100644 --- a/.github/workflows/ci_static-analysis.yaml +++ b/.github/workflows/ci_static-analysis.yaml @@ -1,11 +1,10 @@ name: Static analysis checks on: - pull_request: - push: - # Run once a day - schedule: - - cron: '0 0 * * *' + workflow_call: + +permissions: + contents: read jobs: analysis: diff --git a/.github/workflows/ci_test-vector-handler.yaml b/.github/workflows/ci_test-vector-handler.yaml index 8a142096d..3d5aceaed 100644 --- a/.github/workflows/ci_test-vector-handler.yaml +++ b/.github/workflows/ci_test-vector-handler.yaml @@ -1,11 +1,13 @@ name: Continuous Integration tests for the test vector handler on: - pull_request: - push: - # Run once a day - schedule: - - cron: '0 0 * * *' + workflow_call: + # Define any secrets that need to be passed from the caller + secrets: + INTEG_AWS_ACCESS_KEY_ID: + required: true + INTEG_AWS_SECRET_ACCESS_KEY: + required: true jobs: tests: @@ -19,10 +21,10 @@ jobs: os: - ubuntu-latest - windows-latest - - macos-12 + - macos-latest python: - 3.8 - - 3.x + - "3.12" architecture: - x64 - x86 @@ -34,8 +36,10 @@ jobs: # x86 builds are only meaningful for Windows - os: ubuntu-latest architecture: x86 - - os: macos-12 + - os: macos-latest architecture: x86 + - os: macos-latest + python: 3.8 steps: - uses: aws-actions/configure-aws-credentials@v4 with: diff --git a/.github/workflows/ci_tests.yaml b/.github/workflows/ci_tests.yaml index e1a13c334..0a53cace4 100644 --- a/.github/workflows/ci_tests.yaml +++ b/.github/workflows/ci_tests.yaml @@ -1,11 +1,7 @@ name: Continuous Integration tests on: - pull_request: - push: - # Run once a day - schedule: - - cron: '0 0 * * *' + workflow_call: env: AWS_ENCRYPTION_SDK_PYTHON_INTEGRATION_TEST_AWS_KMS_KEY_ID: | @@ -26,14 +22,13 @@ jobs: os: - ubuntu-latest - windows-latest - - macos-12 + - macos-latest python: - 3.8 - 3.9 - "3.10" - "3.11" - "3.12" - - 3.x architecture: - x64 - x86 @@ -48,8 +43,15 @@ jobs: # x86 builds are only meaningful for Windows - os: ubuntu-latest architecture: x86 - - os: macos-12 + - os: macos-latest architecture: x86 + # Skip older Python versions on macOS + - os: macos-latest + python: 3.8 + - os: macos-latest + python: 3.9 + - os: macos-latest + python: "3.10" steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v4 diff --git a/.github/workflows/daily_ci.yml b/.github/workflows/daily_ci.yml new file mode 100644 index 000000000..09edf3c40 --- /dev/null +++ b/.github/workflows/daily_ci.yml @@ -0,0 +1,50 @@ +# This workflow runs every weekday at 15:00 UTC (8AM PDT) +name: Daily CI + +on: + schedule: + - cron: "00 15 * * 1-5" + pull_request: + paths: + .github/workflows/daily_ci.yml + +permissions: + contents: read + id-token: write + +jobs: + decrypt_oracle: + # Don't run the cron builds on forks + if: github.event_name != 'schedule' || github.repository_owner == 'aws' + uses: ./.github/workflows/ci_decrypt-oracle.yaml + static_analysis: + # Don't run the cron builds on forks + if: github.event_name != 'schedule' || github.repository_owner == 'aws' + uses: ./.github/workflows/ci_static-analysis.yaml + test_vector_handler: + # Don't run the cron builds on forks + if: github.event_name != 'schedule' || github.repository_owner == 'aws' + uses: ./.github/workflows/ci_test-vector-handler.yaml + secrets: + INTEG_AWS_ACCESS_KEY_ID: ${{ secrets.INTEG_AWS_ACCESS_KEY_ID }} + INTEG_AWS_SECRET_ACCESS_KEY: ${{ secrets.INTEG_AWS_SECRET_ACCESS_KEY }} + tests: + # Don't run the cron builds on forks + if: github.event_name != 'schedule' || github.repository_owner == 'aws' + uses: ./.github/workflows/ci_tests.yaml + + notify: + needs: + [ + decrypt_oracle, + static_analysis, + test_vector_handler, + tests + ] + if: ${{ failure() }} + uses: aws/aws-cryptographic-material-providers-library/.github/workflows/slack-notification.yml@main + with: + message: "Daily CI failed on `${{ github.repository }}`. View run: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" + secrets: + SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL_CI }} + diff --git a/.github/workflows/pull.yml b/.github/workflows/pull.yml new file mode 100644 index 000000000..ca5899359 --- /dev/null +++ b/.github/workflows/pull.yml @@ -0,0 +1,41 @@ +name: Pull Request Workflow + +on: + pull_request: + +# Concurrency control helps avoid CodeBuild throttling. +# When new commits are pushed, the previous workflow run is cancelled. +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +permissions: + id-token: write + contents: read + +jobs: + # Call each workflow with appropriate parameters + decrypt_oracle: + uses: ./.github/workflows/ci_decrypt-oracle.yaml + static_analysis: + uses: ./.github/workflows/ci_static-analysis.yaml + test_vector_handler: + uses: ./.github/workflows/ci_test-vector-handler.yaml + secrets: + INTEG_AWS_ACCESS_KEY_ID: ${{ secrets.INTEG_AWS_ACCESS_KEY_ID }} + INTEG_AWS_SECRET_ACCESS_KEY: ${{ secrets.INTEG_AWS_SECRET_ACCESS_KEY }} + tests: + uses: ./.github/workflows/ci_tests.yaml + pr-ci-all-required: + if: always() + needs: + - decrypt_oracle + - static_analysis + - test_vector_handler + - tests + runs-on: ubuntu-22.04 + steps: + - name: Verify all required jobs passed + uses: re-actors/alls-green@release/v1 + with: + jobs: ${{ toJSON(needs) }} \ No newline at end of file diff --git a/.github/workflows/push.yml b/.github/workflows/push.yml new file mode 100644 index 000000000..2832513e3 --- /dev/null +++ b/.github/workflows/push.yml @@ -0,0 +1,25 @@ +name: Push Workflow + +on: + push: + branches: master + +permissions: + id-token: write + contents: read + +jobs: + decrypt_oracle: + uses: ./.github/workflows/ci_decrypt-oracle.yaml + + static_analysis: + uses: ./.github/workflows/ci_static-analysis.yaml + + test_vector_handler: + uses: ./.github/workflows/ci_test-vector-handler.yaml + secrets: + INTEG_AWS_ACCESS_KEY_ID: ${{ secrets.INTEG_AWS_ACCESS_KEY_ID }} + INTEG_AWS_SECRET_ACCESS_KEY: ${{ secrets.INTEG_AWS_SECRET_ACCESS_KEY }} + + tests: + uses: ./.github/workflows/ci_tests.yaml \ No newline at end of file diff --git a/decrypt_oracle/src/pylintrc b/decrypt_oracle/src/pylintrc index 888ae1355..b191633be 100644 --- a/decrypt_oracle/src/pylintrc +++ b/decrypt_oracle/src/pylintrc @@ -1,6 +1,7 @@ [MESSAGES CONTROL] # Disabling messages that we either don't care about for tests or are necessary to break for tests. disable = + too-many-positional-arguments, # on 2026-04-17 aws_encryption_sdk_decrypt_oracle started failing because of this ungrouped-imports, # we let isort handle this consider-using-f-string # disable until 2022-05-05; 6 months after 3.5 deprecation diff --git a/dev_requirements/linter-requirements.txt b/dev_requirements/linter-requirements.txt index 1295e522d..0c06a6667 100644 --- a/dev_requirements/linter-requirements.txt +++ b/dev_requirements/linter-requirements.txt @@ -6,8 +6,9 @@ flake8-bugbear==22.9.11 flake8-docstrings==1.7.0 flake8-print==5.0.0 isort==5.11.4 +pbr>=5.5.0 pyflakes==2.4.0 pylint==2.13.5 readme_renderer==37.3 seed-isort-config==2.2.0 -vulture==2.9.1 +vulture==2.9.1 \ No newline at end of file diff --git a/dev_requirements/test-requirements.txt b/dev_requirements/test-requirements.txt index 01d7a2e2b..260c63636 100644 --- a/dev_requirements/test-requirements.txt +++ b/dev_requirements/test-requirements.txt @@ -1,4 +1,4 @@ mock==4.0.3 pytest==7.2.1 pytest-cov==4.0.0 -pytest-mock==3.6.1 +pytest-mock==3.6.1 \ No newline at end of file diff --git a/src/aws_encryption_sdk/streaming_client.py b/src/aws_encryption_sdk/streaming_client.py index 271b2ab70..d5df02068 100644 --- a/src/aws_encryption_sdk/streaming_client.py +++ b/src/aws_encryption_sdk/streaming_client.py @@ -453,6 +453,8 @@ def _prep_message(self): request=encryption_materials_request ) + validate_commitment_policy_on_encrypt(self.config.commitment_policy, self._encryption_materials.algorithm) + if self.config.algorithm is not None and self._encryption_materials.algorithm != self.config.algorithm: raise ActionNotAllowedError( ( diff --git a/test/functional/test_f_commitment.py b/test/functional/test_f_commitment.py index fdfe281ae..f6078197c 100644 --- a/test/functional/test_f_commitment.py +++ b/test/functional/test_f_commitment.py @@ -225,3 +225,59 @@ def test_encrypt_with_uncommitting_algorithm_require_decrypt(): with pytest.raises(ActionNotAllowedError) as excinfo: decrypting_client.decrypt(source=ciphertext, key_provider=key_provider) excinfo.match("Configuration conflict. Cannot decrypt due to .* requiring only committed messages") + + +def test_encrypt_with_require_policy_fail_when_retrieving_invalid_cmm_materials(): + """Tests that when a client with a require policy shares a cache with a client with a forbid policy + an error gets thrown due to invalid materials retrieved from cmm""" + forbid_encrypting_client = aws_encryption_sdk.EncryptionSDKClient( + commitment_policy=CommitmentPolicy.FORBID_ENCRYPT_ALLOW_DECRYPT + ) + required_encrypting_client = aws_encryption_sdk.EncryptionSDKClient( + commitment_policy=CommitmentPolicy.REQUIRE_ENCRYPT_REQUIRE_DECRYPT + ) + + provider = StaticRawMasterKeyProvider( + wrapping_algorithm=WrappingAlgorithm.AES_256_GCM_IV12_TAG16_NO_PADDING, + encryption_key_type=EncryptionKeyType.SYMMETRIC, + key_bytes=b"\00" * 32, + ) + provider.add_master_key("KeyId") + cache = aws_encryption_sdk.LocalCryptoMaterialsCache(capacity=10) + ccmm = aws_encryption_sdk.CachingCryptoMaterialsManager( + master_key_provider=provider, cache=cache, max_age=3600.0, max_messages_encrypted=5 + ) + plaintext = b"Yellow Submarine" + + _, _ = forbid_encrypting_client.encrypt(source=plaintext, materials_manager=ccmm) + with pytest.raises(ActionNotAllowedError) as excinfo: + required_encrypting_client.encrypt(source=plaintext, materials_manager=ccmm) + excinfo.match("Configuration conflict. Cannot encrypt due to .* requiring only committed messages") + + +def test_encrypt_with_forbid_policy_fail_when_retrieving_invalid_cmm_materials(): + """Tests that when a client with a forbid policy shares a cache with a client with a require policy + an error gets thrown due to invalid materials retrieved from cmm""" + forbid_encrypting_client = aws_encryption_sdk.EncryptionSDKClient( + commitment_policy=CommitmentPolicy.FORBID_ENCRYPT_ALLOW_DECRYPT + ) + required_encrypting_client = aws_encryption_sdk.EncryptionSDKClient( + commitment_policy=CommitmentPolicy.REQUIRE_ENCRYPT_REQUIRE_DECRYPT + ) + + provider = StaticRawMasterKeyProvider( + wrapping_algorithm=WrappingAlgorithm.AES_256_GCM_IV12_TAG16_NO_PADDING, + encryption_key_type=EncryptionKeyType.SYMMETRIC, + key_bytes=b"\00" * 32, + ) + provider.add_master_key("KeyId") + cache = aws_encryption_sdk.LocalCryptoMaterialsCache(capacity=10) + ccmm = aws_encryption_sdk.CachingCryptoMaterialsManager( + master_key_provider=provider, cache=cache, max_age=3600.0, max_messages_encrypted=5 + ) + plaintext = b"Yellow Submarine" + + _, _ = required_encrypting_client.encrypt(source=plaintext, materials_manager=ccmm) + with pytest.raises(ActionNotAllowedError) as excinfo: + forbid_encrypting_client.encrypt(source=plaintext, materials_manager=ccmm) + excinfo.match("Configuration conflict. Cannot encrypt due to .* requiring only non-committed messages.")