From e2fe1ae8f9acae66464ed07e498bd95fe1ed3a02 Mon Sep 17 00:00:00 2001 From: Kess Plasmeier Date: Wed, 21 Jan 2026 15:47:19 -0800 Subject: [PATCH 01/38] update .NET submodules --- .gitmodules | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/.gitmodules b/.gitmodules index a88a83f9..4c3e19e8 100644 --- a/.gitmodules +++ b/.gitmodules @@ -28,17 +28,17 @@ url = git@github.com:awslabs/private-aws-encryption-sdk-specification-staging.git branch = fire-egg-staging [submodule "test-server/net-v2-v3-server/s3ec-net-v2"] - path = test-server/net-v2-v3-server/s3ec-net-v2 - url = https://github.com/aws/private-amazon-s3-encryption-client-dotnet-staging.git - branch = v3sdk-development + path = test-server/net-v2-v3-server/s3ec-net-v2 + url = https://github.com/aws/amazon-s3-encryption-client-dotnet.git + branch = v3sdk-development [submodule "test-server/net-v2-v3-server/s3ec-net-v3"] - path = test-server/net-v2-v3-server/s3ec-net-v3 - url = https://github.com/aws/private-amazon-s3-encryption-client-dotnet-staging.git - branch = s3ec-v3 + path = test-server/net-v2-v3-server/s3ec-net-v3 + url = https://github.com/aws/amazon-s3-encryption-client-dotnet.git + branch = v4sdk-development [submodule "test-server/net-v4-server/s3ec-net-v4-improved"] - path = test-server/net-v4-server/s3ec-net-v4-improved - url = https://github.com/aws/private-amazon-s3-encryption-client-dotnet-staging.git - branch = s3ec-v4-WIP + path = test-server/net-v4-server/s3ec-net-v4-improved + url = https://github.com/aws/amazon-s3-encryption-client-dotnet.git + branch = main [submodule "test-server/go-v3-transition-server/local-go-s3ec"] path = test-server/go-v3-transition-server/local-go-s3ec url = https://github.com/aws/private-amazon-s3-encryption-client-go-staging From 9ad08cd7640305a056cacf3af1911581bded84bc Mon Sep 17 00:00:00 2001 From: Kess Plasmeier Date: Wed, 21 Jan 2026 15:52:23 -0800 Subject: [PATCH 02/38] fix lint --- src/s3_encryption/__init__.py | 1 + src/s3_encryption/materials/encrypted_data_key.py | 1 + src/s3_encryption/materials/materials.py | 1 + src/s3_encryption/metadata.py | 1 + src/s3_encryption/pipelines.py | 1 + 5 files changed, 5 insertions(+) diff --git a/src/s3_encryption/__init__.py b/src/s3_encryption/__init__.py index adfb6886..46cdbdd1 100644 --- a/src/s3_encryption/__init__.py +++ b/src/s3_encryption/__init__.py @@ -1,6 +1,7 @@ # Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. # SPDX-License-Identifier: Apache-2.0 """Top-level S3 Encryption Client v3 for Python package.""" + import io from attrs import define, field diff --git a/src/s3_encryption/materials/encrypted_data_key.py b/src/s3_encryption/materials/encrypted_data_key.py index 28401b40..b2c2359a 100644 --- a/src/s3_encryption/materials/encrypted_data_key.py +++ b/src/s3_encryption/materials/encrypted_data_key.py @@ -5,6 +5,7 @@ This module provides the EncryptedDataKey class which represents an encrypted data key used in the S3 encryption process. """ + from attrs import define diff --git a/src/s3_encryption/materials/materials.py b/src/s3_encryption/materials/materials.py index 9f72ab91..3966e17c 100644 --- a/src/s3_encryption/materials/materials.py +++ b/src/s3_encryption/materials/materials.py @@ -6,6 +6,7 @@ which contain the cryptographic materials needed for S3 object encryption and decryption operations. """ + from typing import Any from attrs import define, field diff --git a/src/s3_encryption/metadata.py b/src/s3_encryption/metadata.py index b4378990..f42feadb 100644 --- a/src/s3_encryption/metadata.py +++ b/src/s3_encryption/metadata.py @@ -5,6 +5,7 @@ This module provides classes and utilities for managing encryption metadata for S3 objects, including serialization and deserialization of metadata. """ + import json from typing import Any diff --git a/src/s3_encryption/pipelines.py b/src/s3_encryption/pipelines.py index 6046fb3a..37093803 100644 --- a/src/s3_encryption/pipelines.py +++ b/src/s3_encryption/pipelines.py @@ -5,6 +5,7 @@ This module provides pipelines for encrypting objects before they are put into S3 and decrypting objects after they are retrieved from S3. """ + import base64 import os From efbb48b7a82fd49413341e447db73a3d4ed031aa Mon Sep 17 00:00:00 2001 From: Kess Plasmeier Date: Wed, 21 Jan 2026 16:08:14 -0800 Subject: [PATCH 03/38] update NET submodule commits --- test-server/net-v2-v3-server/s3ec-net-v2 | 2 +- test-server/net-v2-v3-server/s3ec-net-v3 | 2 +- test-server/net-v3-transition-server/s3ec-v3-transition-branch | 2 +- test-server/net-v4-server/s3ec-net-v4-improved | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/test-server/net-v2-v3-server/s3ec-net-v2 b/test-server/net-v2-v3-server/s3ec-net-v2 index ba85c07e..ed27648c 160000 --- a/test-server/net-v2-v3-server/s3ec-net-v2 +++ b/test-server/net-v2-v3-server/s3ec-net-v2 @@ -1 +1 @@ -Subproject commit ba85c07e0706bae8df242fb7bbfa7e53a264bafa +Subproject commit ed27648c36a2b290b52f94586fce107af7c51fe5 diff --git a/test-server/net-v2-v3-server/s3ec-net-v3 b/test-server/net-v2-v3-server/s3ec-net-v3 index cc942cb5..8c51dd6e 160000 --- a/test-server/net-v2-v3-server/s3ec-net-v3 +++ b/test-server/net-v2-v3-server/s3ec-net-v3 @@ -1 +1 @@ -Subproject commit cc942cb541a733a4340f46bd3e4a1d29a9cbb9a3 +Subproject commit 8c51dd6e2aa2119a2fc8213df34626fbe58fd1c9 diff --git a/test-server/net-v3-transition-server/s3ec-v3-transition-branch b/test-server/net-v3-transition-server/s3ec-v3-transition-branch index c3bf38b9..273e0890 160000 --- a/test-server/net-v3-transition-server/s3ec-v3-transition-branch +++ b/test-server/net-v3-transition-server/s3ec-v3-transition-branch @@ -1 +1 @@ -Subproject commit c3bf38b93c25f7169982073b1ffd1f3d00f59073 +Subproject commit 273e08904275d6357e3a0dad649bc945dce9b98c diff --git a/test-server/net-v4-server/s3ec-net-v4-improved b/test-server/net-v4-server/s3ec-net-v4-improved index 04f70c8b..de0fcf75 160000 --- a/test-server/net-v4-server/s3ec-net-v4-improved +++ b/test-server/net-v4-server/s3ec-net-v4-improved @@ -1 +1 @@ -Subproject commit 04f70c8b70e25c7a1a36fcd5a420c40806157c66 +Subproject commit de0fcf75c671dbaf2dfefd1fc5ed171c23b05fac From 865e31cc0c996137bf3cf2e0c0e7d48184bf4aee Mon Sep 17 00:00:00 2001 From: Kess Plasmeier Date: Wed, 21 Jan 2026 16:31:52 -0800 Subject: [PATCH 04/38] move Ruby to public repos --- .gitmodules | 10 ++++++---- test-server/ruby-v2-server/local-ruby-sdk | 2 +- test-server/ruby-v3-server/local-ruby-sdk | 2 +- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/.gitmodules b/.gitmodules index 4c3e19e8..59dd8854 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,9 +1,11 @@ [submodule "test-server/ruby-v2-server/local-ruby-sdk"] path = test-server/ruby-v2-server/local-ruby-sdk - url = git@github.com:aws/aws-sdk-ruby-staging.git + url = git@github.com:aws/aws-sdk-ruby.git + branch = version-3 [submodule "test-server/ruby-v3-server/local-ruby-sdk"] path = test-server/ruby-v3-server/local-ruby-sdk - url = git@github.com:aws/aws-sdk-ruby-staging.git + url = git@github.com:aws/aws-sdk-ruby.git + branch = version-3 [submodule "test-server/php-v2-server/local-php-sdk"] path = test-server/php-v2-server/local-php-sdk url = git@github.com:aws/private-aws-sdk-php-staging.git @@ -45,8 +47,8 @@ branch = v3-strip [submodule "test-server/net-v3-transition-server/s3ec-v3-transition-branch"] path = test-server/net-v3-transition-server/s3ec-v3-transition-branch - url = https://github.com/aws/private-amazon-s3-encryption-client-dotnet-staging.git - branch = rishav/key-commitment + url = https://github.com/aws/amazon-s3-encryption-client-dotnet.git + branch = v4sdk-development [submodule "test-server/cpp-v2-transition-server/aws-sdk-cpp"] path = test-server/cpp-v2-transition-server/aws-sdk-cpp url = git@github.com:awslabs/aws-sdk-cpp-staging.git diff --git a/test-server/ruby-v2-server/local-ruby-sdk b/test-server/ruby-v2-server/local-ruby-sdk index 1f32f5b9..93985e94 160000 --- a/test-server/ruby-v2-server/local-ruby-sdk +++ b/test-server/ruby-v2-server/local-ruby-sdk @@ -1 +1 @@ -Subproject commit 1f32f5b9ade757b6f2bce0650af43eefe9581d01 +Subproject commit 93985e94bbe8345cc7d615d1cdbcd7516ac16bcd diff --git a/test-server/ruby-v3-server/local-ruby-sdk b/test-server/ruby-v3-server/local-ruby-sdk index 1f32f5b9..93985e94 160000 --- a/test-server/ruby-v3-server/local-ruby-sdk +++ b/test-server/ruby-v3-server/local-ruby-sdk @@ -1 +1 @@ -Subproject commit 1f32f5b9ade757b6f2bce0650af43eefe9581d01 +Subproject commit 93985e94bbe8345cc7d615d1cdbcd7516ac16bcd From d1a707ca9619b6209e4679e0c9890a0cfbaaadc7 Mon Sep 17 00:00:00 2001 From: Kess Plasmeier Date: Wed, 21 Jan 2026 17:07:11 -0800 Subject: [PATCH 05/38] specify bundler version to match lockfile --- .github/workflows/test.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 182b7f47..dd6131a0 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -65,6 +65,8 @@ jobs: uses: ruby/setup-ruby@v1 with: ruby-version: "3.4" + bundler: "2.7.2" + bundler-cache: true - name: Set up PHP with Composer uses: shivammathur/setup-php@verbose From 63c5af17080d59b05d8303307dfb476afbb2496f Mon Sep 17 00:00:00 2001 From: Kess Plasmeier Date: Wed, 21 Jan 2026 17:21:21 -0800 Subject: [PATCH 06/38] install xcode tools --- .github/workflows/test.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index dd6131a0..04e8cbd7 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -61,6 +61,13 @@ jobs: test-server/cpp-v3-server/aws-sdk-cpp \ test-server/cpp-v2-server/aws-sdk-cpp + - name: Setup build environment + run: | + # Verify Xcode CLI tools are available + xcode-select -p || xcode-select --install + # Accept Xcode license if needed + sudo xcodebuild -license accept || true + - name: Set up Ruby uses: ruby/setup-ruby@v1 with: From 14e3fb1b069a1bd9118443205c0864bb36320139 Mon Sep 17 00:00:00 2001 From: Kess Plasmeier Date: Wed, 21 Jan 2026 18:03:29 -0800 Subject: [PATCH 07/38] try ruby 3.4.7 --- .github/workflows/examples.yml | 2 +- .github/workflows/test.yml | 10 +--------- 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/.github/workflows/examples.yml b/.github/workflows/examples.yml index 4b1cde5a..8cb32ab2 100644 --- a/.github/workflows/examples.yml +++ b/.github/workflows/examples.yml @@ -34,7 +34,7 @@ jobs: - name: Set up Ruby uses: ruby/setup-ruby@v1 with: - ruby-version: "3.4" + ruby-version: "3.4.7" - name: Set up PHP with Composer uses: shivammathur/setup-php@verbose diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 04e8cbd7..d3d101b5 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -61,18 +61,10 @@ jobs: test-server/cpp-v3-server/aws-sdk-cpp \ test-server/cpp-v2-server/aws-sdk-cpp - - name: Setup build environment - run: | - # Verify Xcode CLI tools are available - xcode-select -p || xcode-select --install - # Accept Xcode license if needed - sudo xcodebuild -license accept || true - - name: Set up Ruby uses: ruby/setup-ruby@v1 with: - ruby-version: "3.4" - bundler: "2.7.2" + ruby-version: "3.4.7" bundler-cache: true - name: Set up PHP with Composer From cc4d1cf91bd073d5c7c6075f2145009a7851fff9 Mon Sep 17 00:00:00 2001 From: Kess Plasmeier Date: Wed, 21 Jan 2026 18:37:21 -0800 Subject: [PATCH 08/38] supress CVE --- test-server/net-v2-v3-server/NetV2V3Server.csproj | 4 ++++ .../net-v3-transition-server/NetV3TransitionServer.csproj | 4 ++++ test-server/net-v4-server/NetV4Server.csproj | 4 ++++ 3 files changed, 12 insertions(+) diff --git a/test-server/net-v2-v3-server/NetV2V3Server.csproj b/test-server/net-v2-v3-server/NetV2V3Server.csproj index 8d664eff..91e1dfa1 100644 --- a/test-server/net-v2-v3-server/NetV2V3Server.csproj +++ b/test-server/net-v2-v3-server/NetV2V3Server.csproj @@ -36,4 +36,8 @@ + + + + diff --git a/test-server/net-v3-transition-server/NetV3TransitionServer.csproj b/test-server/net-v3-transition-server/NetV3TransitionServer.csproj index 269f555f..3a092af8 100644 --- a/test-server/net-v3-transition-server/NetV3TransitionServer.csproj +++ b/test-server/net-v3-transition-server/NetV3TransitionServer.csproj @@ -24,4 +24,8 @@ + + + + diff --git a/test-server/net-v4-server/NetV4Server.csproj b/test-server/net-v4-server/NetV4Server.csproj index 28ddba06..45ed1a1a 100644 --- a/test-server/net-v4-server/NetV4Server.csproj +++ b/test-server/net-v4-server/NetV4Server.csproj @@ -25,4 +25,8 @@ + + + + From 9b9f4c578cfc7f4020ef222081c9e57e02f8dfb7 Mon Sep 17 00:00:00 2001 From: Kess Plasmeier Date: Wed, 21 Jan 2026 18:59:52 -0800 Subject: [PATCH 09/38] try again to disable nuget audit errors --- .../net-v3-transition-server/NetV3TransitionServer.csproj | 1 + test-server/net-v4-server/NetV4Server.csproj | 1 + 2 files changed, 2 insertions(+) diff --git a/test-server/net-v3-transition-server/NetV3TransitionServer.csproj b/test-server/net-v3-transition-server/NetV3TransitionServer.csproj index 3a092af8..e9ca3798 100644 --- a/test-server/net-v3-transition-server/NetV3TransitionServer.csproj +++ b/test-server/net-v3-transition-server/NetV3TransitionServer.csproj @@ -2,6 +2,7 @@ net8.0 + moderate enable enable false diff --git a/test-server/net-v4-server/NetV4Server.csproj b/test-server/net-v4-server/NetV4Server.csproj index 45ed1a1a..71e818e5 100644 --- a/test-server/net-v4-server/NetV4Server.csproj +++ b/test-server/net-v4-server/NetV4Server.csproj @@ -6,6 +6,7 @@ enable false NetV2V3Server + moderate From 88e832061a3ede9b694063e72106757ec6477c67 Mon Sep 17 00:00:00 2001 From: Kess Plasmeier Date: Mon, 26 Jan 2026 11:54:04 -0800 Subject: [PATCH 10/38] fixup NET --- test-server/net-v2-v3-server/NetV2V3Server.csproj | 4 ---- .../net-v3-transition-server/NetV3TransitionServer.csproj | 5 ----- .../net-v3-transition-server/s3ec-v3-transition-branch | 2 +- test-server/net-v4-server/NetV4Server.csproj | 5 ----- test-server/net-v4-server/s3ec-net-v4-improved | 2 +- 5 files changed, 2 insertions(+), 16 deletions(-) diff --git a/test-server/net-v2-v3-server/NetV2V3Server.csproj b/test-server/net-v2-v3-server/NetV2V3Server.csproj index 91e1dfa1..8d664eff 100644 --- a/test-server/net-v2-v3-server/NetV2V3Server.csproj +++ b/test-server/net-v2-v3-server/NetV2V3Server.csproj @@ -36,8 +36,4 @@ - - - - diff --git a/test-server/net-v3-transition-server/NetV3TransitionServer.csproj b/test-server/net-v3-transition-server/NetV3TransitionServer.csproj index e9ca3798..269f555f 100644 --- a/test-server/net-v3-transition-server/NetV3TransitionServer.csproj +++ b/test-server/net-v3-transition-server/NetV3TransitionServer.csproj @@ -2,7 +2,6 @@ net8.0 - moderate enable enable false @@ -25,8 +24,4 @@ - - - - diff --git a/test-server/net-v3-transition-server/s3ec-v3-transition-branch b/test-server/net-v3-transition-server/s3ec-v3-transition-branch index 273e0890..7a552940 160000 --- a/test-server/net-v3-transition-server/s3ec-v3-transition-branch +++ b/test-server/net-v3-transition-server/s3ec-v3-transition-branch @@ -1 +1 @@ -Subproject commit 273e08904275d6357e3a0dad649bc945dce9b98c +Subproject commit 7a55294068bb3bb7f96226efd6d9edcd1057184b diff --git a/test-server/net-v4-server/NetV4Server.csproj b/test-server/net-v4-server/NetV4Server.csproj index 71e818e5..28ddba06 100644 --- a/test-server/net-v4-server/NetV4Server.csproj +++ b/test-server/net-v4-server/NetV4Server.csproj @@ -6,7 +6,6 @@ enable false NetV2V3Server - moderate @@ -26,8 +25,4 @@ - - - - diff --git a/test-server/net-v4-server/s3ec-net-v4-improved b/test-server/net-v4-server/s3ec-net-v4-improved index de0fcf75..9b628b06 160000 --- a/test-server/net-v4-server/s3ec-net-v4-improved +++ b/test-server/net-v4-server/s3ec-net-v4-improved @@ -1 +1 @@ -Subproject commit de0fcf75c671dbaf2dfefd1fc5ed171c23b05fac +Subproject commit 9b628b06e5c1bf12696c752afb2631c38cae11f9 From b9b2470fcc8a9da26fbf40d66ed1cd34f6d7c46a Mon Sep 17 00:00:00 2001 From: Kess Plasmeier Date: Mon, 26 Jan 2026 12:19:19 -0800 Subject: [PATCH 11/38] NET again --- test-server/net-v2-v3-server/s3ec-net-v3 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test-server/net-v2-v3-server/s3ec-net-v3 b/test-server/net-v2-v3-server/s3ec-net-v3 index 8c51dd6e..7a552940 160000 --- a/test-server/net-v2-v3-server/s3ec-net-v3 +++ b/test-server/net-v2-v3-server/s3ec-net-v3 @@ -1 +1 @@ -Subproject commit 8c51dd6e2aa2119a2fc8213df34626fbe58fd1c9 +Subproject commit 7a55294068bb3bb7f96226efd6d9edcd1057184b From d396df770f08db6807cc452f13286ec787067ed0 Mon Sep 17 00:00:00 2001 From: Kess Plasmeier Date: Mon, 26 Jan 2026 14:45:31 -0800 Subject: [PATCH 12/38] move PHP, CPP over to public repos --- .gitmodules | 17 +++++++++-------- test-server/cpp-v2-server/aws-sdk-cpp | 2 +- .../cpp-v2-transition-server/aws-sdk-cpp | 2 +- test-server/cpp-v3-server/aws-sdk-cpp | 2 +- test-server/php-v2-server/local-php-sdk | 2 +- test-server/php-v3-server/local-php-sdk | 2 +- 6 files changed, 14 insertions(+), 13 deletions(-) diff --git a/.gitmodules b/.gitmodules index 59dd8854..b3f0127c 100644 --- a/.gitmodules +++ b/.gitmodules @@ -8,12 +8,12 @@ branch = version-3 [submodule "test-server/php-v2-server/local-php-sdk"] path = test-server/php-v2-server/local-php-sdk - url = git@github.com:aws/private-aws-sdk-php-staging.git + url = git@github.com:aws/aws-sdk-php.git branch = master [submodule "test-server/php-v3-server/local-php-sdk"] path = test-server/php-v3-server/local-php-sdk - url = git@github.com:aws/private-aws-sdk-php-staging.git - branch = s3ec/improved + url = git@github.com:aws/aws-sdk-php.git + branch = master [submodule "test-server/go-v4-server/local-go-s3ec"] path = test-server/go-v4-server/local-go-s3ec url = https://github.com/aws/private-amazon-s3-encryption-client-go-staging @@ -51,12 +51,13 @@ branch = v4sdk-development [submodule "test-server/cpp-v2-transition-server/aws-sdk-cpp"] path = test-server/cpp-v2-transition-server/aws-sdk-cpp - url = git@github.com:awslabs/aws-sdk-cpp-staging.git - branch = fire-egg-dev + url = git@github.com:aws/aws-sdk-cpp.git + branch = main [submodule "test-server/cpp-v3-server/aws-sdk-cpp"] path = test-server/cpp-v3-server/aws-sdk-cpp - url = git@github.com:awslabs/aws-sdk-cpp-staging.git - branch = fire-egg-dev + url = git@github.com:aws/aws-sdk-cpp.git + branch = main [submodule "test-server/cpp-v2-server/aws-sdk-cpp"] path = test-server/cpp-v2-server/aws-sdk-cpp - url = git@github.com:awslabs/aws-sdk-cpp-staging.git + url = git@github.com:aws/aws-sdk-cpp.git + branch = main diff --git a/test-server/cpp-v2-server/aws-sdk-cpp b/test-server/cpp-v2-server/aws-sdk-cpp index 994384ca..9110b0ff 160000 --- a/test-server/cpp-v2-server/aws-sdk-cpp +++ b/test-server/cpp-v2-server/aws-sdk-cpp @@ -1 +1 @@ -Subproject commit 994384ca8b9defe2ae60b5d3447ec5f47f7ec19f +Subproject commit 9110b0ff85094134a4f78316485fd7fe754a2a9c diff --git a/test-server/cpp-v2-transition-server/aws-sdk-cpp b/test-server/cpp-v2-transition-server/aws-sdk-cpp index cec1f193..9110b0ff 160000 --- a/test-server/cpp-v2-transition-server/aws-sdk-cpp +++ b/test-server/cpp-v2-transition-server/aws-sdk-cpp @@ -1 +1 @@ -Subproject commit cec1f1933be672f65627c11ff2d853e07c5b3ff4 +Subproject commit 9110b0ff85094134a4f78316485fd7fe754a2a9c diff --git a/test-server/cpp-v3-server/aws-sdk-cpp b/test-server/cpp-v3-server/aws-sdk-cpp index cec1f193..9110b0ff 160000 --- a/test-server/cpp-v3-server/aws-sdk-cpp +++ b/test-server/cpp-v3-server/aws-sdk-cpp @@ -1 +1 @@ -Subproject commit cec1f1933be672f65627c11ff2d853e07c5b3ff4 +Subproject commit 9110b0ff85094134a4f78316485fd7fe754a2a9c diff --git a/test-server/php-v2-server/local-php-sdk b/test-server/php-v2-server/local-php-sdk index ab8aee74..f53d8fc6 160000 --- a/test-server/php-v2-server/local-php-sdk +++ b/test-server/php-v2-server/local-php-sdk @@ -1 +1 @@ -Subproject commit ab8aee74db1141da07c9c979cf313418fddae256 +Subproject commit f53d8fc6cdbc1e64e7d14e72d1e315d05003b2b4 diff --git a/test-server/php-v3-server/local-php-sdk b/test-server/php-v3-server/local-php-sdk index 163fe386..f53d8fc6 160000 --- a/test-server/php-v3-server/local-php-sdk +++ b/test-server/php-v3-server/local-php-sdk @@ -1 +1 @@ -Subproject commit 163fe3866e7122d6cd1dbff6f121302db8d98aae +Subproject commit f53d8fc6cdbc1e64e7d14e72d1e315d05003b2b4 From 3c5b79dd1e28d9b2e7681e33e92e859549774978 Mon Sep 17 00:00:00 2001 From: Kess Plasmeier Date: Mon, 26 Jan 2026 15:18:50 -0800 Subject: [PATCH 13/38] make C++ examples use public repo --- .github/workflows/examples.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/examples.yml b/.github/workflows/examples.yml index 8cb32ab2..2a8c6aca 100644 --- a/.github/workflows/examples.yml +++ b/.github/workflows/examples.yml @@ -22,8 +22,8 @@ jobs: with: submodules: recursive token: ${{ secrets.PAT_FOR_CPP }} - repository: awslabs/aws-sdk-cpp-staging - ref: fire-egg-dev + repository: aws/aws-sdk-cpp + ref: main path: all-examples/cpp/aws-sdk-cpp/ - name: Set up Python From 5c5767a985c404a6d147943ac8f4d8496285eb1b Mon Sep 17 00:00:00 2001 From: Kess Plasmeier Date: Mon, 26 Jan 2026 15:35:23 -0800 Subject: [PATCH 14/38] no token for C++ --- .github/workflows/examples.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/examples.yml b/.github/workflows/examples.yml index 2a8c6aca..b442be5a 100644 --- a/.github/workflows/examples.yml +++ b/.github/workflows/examples.yml @@ -21,7 +21,6 @@ jobs: uses: actions/checkout@v5 with: submodules: recursive - token: ${{ secrets.PAT_FOR_CPP }} repository: aws/aws-sdk-cpp ref: main path: all-examples/cpp/aws-sdk-cpp/ From d5a4233dc0a29ae0d9a2fbc979b5939c0137c14f Mon Sep 17 00:00:00 2001 From: Kess Plasmeier Date: Mon, 26 Jan 2026 15:59:29 -0800 Subject: [PATCH 15/38] fix Duvet --- .github/workflows/duvet.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/duvet.yml b/.github/workflows/duvet.yml index 529be19d..8b277ec0 100644 --- a/.github/workflows/duvet.yml +++ b/.github/workflows/duvet.yml @@ -23,9 +23,8 @@ jobs: uses: actions/checkout@v5 with: submodules: recursive - token: ${{ secrets.PAT_FOR_CPP }} - repository: awslabs/aws-sdk-cpp-staging - ref: fire-egg-dev + repository: aws/aws-sdk-cpp + ref: main path: test-server/cpp-v3-server/aws-sdk-cpp/ - name: Setup Rust toolchain From 7bcf5eb8bfd757a86241eb1c55d1642e9f210952 Mon Sep 17 00:00:00 2001 From: Kess Plasmeier Date: Tue, 27 Jan 2026 14:13:26 -0800 Subject: [PATCH 16/38] disable CURRENT tests --- .../amazon/encryption/s3/TestUtils.java | 159 +++++++++--------- 1 file changed, 79 insertions(+), 80 deletions(-) diff --git a/test-server/java-tests/src/it/java/software/amazon/encryption/s3/TestUtils.java b/test-server/java-tests/src/it/java/software/amazon/encryption/s3/TestUtils.java index f2065115..f06f5cd2 100644 --- a/test-server/java-tests/src/it/java/software/amazon/encryption/s3/TestUtils.java +++ b/test-server/java-tests/src/it/java/software/amazon/encryption/s3/TestUtils.java @@ -59,32 +59,32 @@ public class TestUtils { // vN-Transition: Proposed feature release version. Supports reading messages encrypted with key commitment. // vN+1: Proposed breaking release version. Supports reading/writing messages encrypted with key commitment. - public static final String JAVA_V3_CURRENT = "Java-V3-Current"; +// public static final String JAVA_V3_CURRENT = "Java-V3-Current"; public static final String JAVA_V3_TRANSITION = "Java-V3-Transition"; public static final String JAVA_V4 = "Java-V4"; // No Python S3EC versions are released. Only test V3 as the "vN+1" version. public static final String PYTHON_V3 = "Python-V3"; - public static final String GO_V3_CURRENT = "Go-V3-Current"; +// public static final String GO_V3_CURRENT = "Go-V3-Current"; public static final String GO_V3_TRANSITION = "Go-V3-Transition"; public static final String GO_V4 = "Go-V4"; - public static final String NET_V2_CURRENT = "NET-V2-Current"; - public static final String NET_V3_CURRENT = "NET-V3-Current"; +// public static final String NET_V2_CURRENT = "NET-V2-Current"; +// public static final String NET_V3_CURRENT = "NET-V3-Current"; public static final String NET_V2_TRANSITION = "NET-V2-Transition"; public static final String NET_V3_TRANSITION = "NET-V3-Transition"; public static final String NET_V4 = "NET-V4"; - public static final String CPP_V2_CURRENT = "CPP-V2-Current"; +// public static final String CPP_V2_CURRENT = "CPP-V2-Current"; public static final String CPP_V2_TRANSITION = "CPP-V2-Transition"; public static final String CPP_V3 = "CPP-V3"; - public static final String RUBY_V2_CURRENT = "Ruby-V2-Current"; +// public static final String RUBY_V2_CURRENT = "Ruby-V2-Current"; public static final String RUBY_V2_TRANSITION = "Ruby-V2-Transition"; public static final String RUBY_V3 = "Ruby-V3"; - public static final String PHP_V2_CURRENT = "PHP-V2-Current"; +// public static final String PHP_V2_CURRENT = "PHP-V2-Current"; public static final String PHP_V2_TRANSITION = "PHP-V2-Transition"; public static final String PHP_V3 = "PHP-V3"; @@ -97,33 +97,31 @@ public class TestUtils { // Sets of unsupported features by language public static final Set ENCRYPTION_CONTEXT_ON_DECRYPT_UNSUPPORTED = - Set.of(GO_V3_CURRENT, PHP_V2_CURRENT, PHP_V2_TRANSITION, PHP_V3, NET_V2_CURRENT, NET_V3_CURRENT, NET_V3_TRANSITION, NET_V4); + Set.of(PHP_V2_TRANSITION, PHP_V3, NET_V3_TRANSITION, NET_V4); +// Set.of(GO_V3_CURRENT, PHP_V2_CURRENT, PHP_V2_TRANSITION, PHP_V3, NET_V2_CURRENT, NET_V3_CURRENT, NET_V3_TRANSITION, NET_V4); public static final Set ENCRYPTION_CONTEXT_ON_ENCRYPT_UNSUPPORTED = - Set.of(NET_V2_CURRENT, NET_V3_CURRENT, NET_V3_TRANSITION, NET_V4); + Set.of(NET_V3_TRANSITION, NET_V4); +// Set.of(NET_V2_CURRENT, NET_V3_CURRENT, NET_V3_TRANSITION, NET_V4); public static final Set RE_ENCRYPT_SUPPORTED = - Set.of(JAVA_V3_CURRENT, JAVA_V3_TRANSITION, JAVA_V4); + Set.of(JAVA_V3_TRANSITION, JAVA_V4); +// Set.of(JAVA_V3_CURRENT, JAVA_V3_TRANSITION, JAVA_V4); public static final Set RANGED_GETS_SUPPORTED = Set.of( - JAVA_V3_CURRENT, JAVA_V3_TRANSITION, JAVA_V4 - , CPP_V2_CURRENT, CPP_V2_TRANSITION, CPP_V3 + JAVA_V3_TRANSITION, JAVA_V4, CPP_V2_TRANSITION, CPP_V3 +// JAVA_V3_CURRENT, JAVA_V3_TRANSITION, JAVA_V4, CPP_V2_CURRENT, CPP_V2_TRANSITION, CPP_V3 ); // Cpp only supports Raw AES public static final Set RAW_AES_SUPPORTED = - Set.of(JAVA_V3_CURRENT, JAVA_V3_TRANSITION, JAVA_V4 - , NET_V2_CURRENT, NET_V3_CURRENT, NET_V3_TRANSITION, NET_V4 - , RUBY_V2_TRANSITION, RUBY_V3 - , CPP_V2_CURRENT, CPP_V2_TRANSITION, CPP_V3 - ); + Set.of(JAVA_V3_TRANSITION, JAVA_V4, NET_V3_TRANSITION, NET_V4, RUBY_V2_TRANSITION, RUBY_V3, CPP_V2_TRANSITION, CPP_V3); +// Set.of(JAVA_V3_CURRENT, JAVA_V3_TRANSITION, JAVA_V4, NET_V2_CURRENT, NET_V3_CURRENT, NET_V3_TRANSITION, NET_V4, RUBY_V2_TRANSITION, RUBY_V3, CPP_V2_CURRENT, CPP_V2_TRANSITION, CPP_V3); public static final Set RAW_RSA_SUPPORTED = - Set.of(JAVA_V3_CURRENT, JAVA_V3_TRANSITION, JAVA_V4 - , NET_V2_CURRENT, NET_V3_CURRENT, NET_V3_TRANSITION, NET_V4 - , RUBY_V2_TRANSITION, RUBY_V3 - ); + Set.of(JAVA_V3_TRANSITION, JAVA_V4, NET_V3_TRANSITION, NET_V4, RUBY_V2_TRANSITION, RUBY_V3); +// Set.of(JAVA_V3_CURRENT, JAVA_V3_TRANSITION, JAVA_V4, NET_V2_CURRENT, NET_V3_CURRENT, NET_V3_TRANSITION, NET_V4, RUBY_V2_TRANSITION, RUBY_V3); // Intersection of RAW_AES_SUPPORTED and RAW_RSA_SUPPORTED public static final Set RAW_SUPPORTED = @@ -134,13 +132,14 @@ public class TestUtils { // .NET only supports decrypting instruction files using AES and RSA. // Python MUST support decrypting KMS instruction files, but does not yet. public static final Set KMS_INSTRUCTION_FILE_UNSUPPORTED = - Set.of(NET_V2_CURRENT, NET_V2_TRANSITION, NET_V3_CURRENT, NET_V3_TRANSITION, NET_V4); + Set.of(NET_V2_TRANSITION, NET_V3_TRANSITION, NET_V4); +// Set.of(NET_V2_CURRENT, NET_V2_TRANSITION, NET_V3_CURRENT, NET_V3_TRANSITION, NET_V4); // Go does not write with instruction files public static final Set INSTRUCTION_FILE_PUT_UNSUPPORTED = - Set.of(GO_V3_CURRENT, GO_V3_TRANSITION, GO_V4, PYTHON_V3 + Set.of(GO_V3_TRANSITION, GO_V4, PYTHON_V3); +// Set.of(GO_V3_CURRENT, GO_V3_TRANSITION, GO_V4, PYTHON_V3, CPP_V2_CURRENT); // Apparently C++ V2 Current does not work, even though it should - , CPP_V2_CURRENT); // Not implemented yet in Python. public static final Set INSTRUCTION_FILE_GET_UNSUPPORTED = @@ -159,17 +158,17 @@ public class TestUtils { PHP_V3 ); - public static final Set CURRENT_VERSIONS = - Set.of( - JAVA_V3_CURRENT, - GO_V3_CURRENT, - NET_V2_CURRENT, - NET_V3_CURRENT, - CPP_V2_CURRENT, - RUBY_V2_CURRENT, - PHP_V2_CURRENT - ); - +// public static final Set CURRENT_VERSIONS = +// Set.of( +// JAVA_V3_CURRENT, +// GO_V3_CURRENT, +// NET_V2_CURRENT, +// NET_V3_CURRENT, +// CPP_V2_CURRENT, +// RUBY_V2_CURRENT, +// PHP_V2_CURRENT +// ); +// public static final Set TRANSITION_VERSIONS = Set.of( JAVA_V3_TRANSITION, @@ -196,16 +195,16 @@ public class TestUtils { static { final Map servers = new LinkedHashMap<>(); - servers.put(JAVA_V3_CURRENT, new LanguageServerTarget(JAVA_V3_CURRENT, "8080")); +// servers.put(JAVA_V3_CURRENT, new LanguageServerTarget(JAVA_V3_CURRENT, "8080")); servers.put(PYTHON_V3, new LanguageServerTarget(PYTHON_V3, "8081")); - servers.put(GO_V3_CURRENT, new LanguageServerTarget(GO_V3_CURRENT, "8082")); - servers.put(NET_V2_CURRENT, new LanguageServerTarget(NET_V2_CURRENT, "8083")); - servers.put(NET_V3_CURRENT, new LanguageServerTarget(NET_V3_CURRENT, "8084")); - servers.put(CPP_V2_CURRENT, new LanguageServerTarget(CPP_V2_CURRENT, "8085")); +// servers.put(GO_V3_CURRENT, new LanguageServerTarget(GO_V3_CURRENT, "8082")); +// servers.put(NET_V2_CURRENT, new LanguageServerTarget(NET_V2_CURRENT, "8083")); +// servers.put(NET_V3_CURRENT, new LanguageServerTarget(NET_V3_CURRENT, "8084")); +// servers.put(CPP_V2_CURRENT, new LanguageServerTarget(CPP_V2_CURRENT, "8085")); servers.put(CPP_V2_TRANSITION, new LanguageServerTarget(CPP_V2_TRANSITION, "8097")); servers.put(CPP_V3, new LanguageServerTarget(CPP_V3, "8091")); // servers.put(RUBY_V2_CURRENT, new LanguageServerTarget(RUBY_V2_CURRENT, "8086")); - servers.put(PHP_V2_CURRENT, new LanguageServerTarget(PHP_V2_CURRENT, "8087")); +// servers.put(PHP_V2_CURRENT, new LanguageServerTarget(PHP_V2_CURRENT, "8087")); servers.put(GO_V4, new LanguageServerTarget(GO_V4, "8089")); servers.put(NET_V4, new LanguageServerTarget(NET_V4, "8090")); servers.put(RUBY_V3, new LanguageServerTarget(RUBY_V3, "8092")); @@ -377,14 +376,14 @@ public static Stream clientsForTest() { .map(Arguments::of); } - /** - * Get stream of arguments for current version clients for testing. - */ - public static Stream currentClientsForTest() { - return serverMap.values().stream() - .filter(target -> CURRENT_VERSIONS.contains(target.getLanguageName())) - .map(Arguments::of); - } +// /** +// * Get stream of arguments for current version clients for testing. +// */ +// public static Stream currentClientsForTest() { +// return serverMap.values().stream() +// .filter(target -> CURRENT_VERSIONS.contains(target.getLanguageName())) +// .map(Arguments::of); +// } /** * Get stream of arguments for transition version clients for testing. @@ -454,37 +453,37 @@ public static Stream encryptTransitionDecryptImproved() { ))); } - public static Stream encryptImprovedDecryptCurrent() { - return improvedClientsForTest() - .flatMap(encrypt -> currentClientsForTest() - .flatMap(decrypt -> Stream.of( - Arguments.of(encrypt.get()[0], decrypt.get()[0]) - ))); - } - - public static Stream encryptCurrentDecryptImproved() { - return currentClientsForTest() - .flatMap(encrypt -> improvedClientsForTest() - .flatMap(decrypt -> Stream.of( - Arguments.of(encrypt.get()[0], decrypt.get()[0]) - ))); - } - - public static Stream encryptTransitionDecryptCurrent() { - return transitionClientsForTest() - .flatMap(encrypt -> currentClientsForTest() - .flatMap(decrypt -> Stream.of( - Arguments.of(encrypt.get()[0], decrypt.get()[0]) - ))); - } - - public static Stream encryptCurrentDecryptTransition() { - return currentClientsForTest() - .flatMap(encrypt -> transitionClientsForTest() - .flatMap(decrypt -> Stream.of( - Arguments.of(encrypt.get()[0], decrypt.get()[0]) - ))); - } +// public static Stream encryptImprovedDecryptCurrent() { +// return improvedClientsForTest() +// .flatMap(encrypt -> currentClientsForTest() +// .flatMap(decrypt -> Stream.of( +// Arguments.of(encrypt.get()[0], decrypt.get()[0]) +// ))); +// } + +// public static Stream encryptCurrentDecryptImproved() { +// return currentClientsForTest() +// .flatMap(encrypt -> improvedClientsForTest() +// .flatMap(decrypt -> Stream.of( +// Arguments.of(encrypt.get()[0], decrypt.get()[0]) +// ))); +// } + +// public static Stream encryptTransitionDecryptCurrent() { +// return transitionClientsForTest() +// .flatMap(encrypt -> currentClientsForTest() +// .flatMap(decrypt -> Stream.of( +// Arguments.of(encrypt.get()[0], decrypt.get()[0]) +// ))); +// } + +// public static Stream encryptCurrentDecryptTransition() { +// return currentClientsForTest() +// .flatMap(encrypt -> transitionClientsForTest() +// .flatMap(decrypt -> Stream.of( +// Arguments.of(encrypt.get()[0], decrypt.get()[0]) +// ))); +// } /** * Provides a stream of arguments for parameterized tests that test cross-language compatibility From 2a79383a089fb321e641e0b473bc53dc32b7bf66 Mon Sep 17 00:00:00 2001 From: Tony Knapp <5892063+texastony@users.noreply.github.com> Date: Tue, 27 Jan 2026 16:21:51 -0600 Subject: [PATCH 17/38] chore[dev]: replace poetry with UV in README (#121) * chore[dev]: replace poetry with UV in README * fix[ci]: run-tests in main.yml needs permissions * chore[dev]: Add PR helper to instruction (#122) * chore: resolve PR feedback * chore: suppress docstring and line length linting rules for test files --- .github/workflows/main.yml | 5 ++++- README.md | 12 +++++++++++- pyproject.toml | 6 +++++- 3 files changed, 20 insertions(+), 3 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index e10b7d0d..675a0099 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -2,7 +2,7 @@ name: Main Workflow on: push: - branches: [ main ] + branches: [ staging ] pull_request: workflow_dispatch: inputs: @@ -22,4 +22,7 @@ jobs: uses: ./.github/workflows/test.yml with: python-version: ${{ inputs.python-version || '3.11' }} + permissions: + id-token: write + contents: read secrets: inherit diff --git a/README.md b/README.md index c24d1796..edcfd4ff 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ This library provides an S3 client that supports client-side encryption. ### Prerequisites - Python 3.11 or higher -- [Poetry](https://python-poetry.org/) for dependency management +- [uv](https://github.com/astral-sh/uv) for package and project management ### Setup @@ -73,3 +73,13 @@ Common Flake8 issues in the codebase include: - **Code complexity** (C901): Refactor complex functions When contributing to this project, please try to fix linting issues in the files you modify. + +### Pull Request Command +While this project is in development, +it is useful to use `gh pr` to create the pull-requests, +so they can be associated with the GitHub project. + +```sh +gh pr create -B staging -p "S3EC-Python" -f +``` + diff --git a/pyproject.toml b/pyproject.toml index 883b6914..187b99a2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -19,7 +19,8 @@ test = [ "pytest>=8.4.1", ] dev = [ - "black>=24.3.0", + # black >= 26 requires a reformat + "black>=24.3.0,<26.0.0", "ruff>=0.3.0", ] @@ -52,3 +53,6 @@ max-complexity = 10 [tool.ruff.lint.isort] known-first-party = ["s3_encryption"] + +[tool.ruff.lint.per-file-ignores] +"test/**/*.py" = ["D100", "D101", "D102", "D103", "D104", "E501"] From 894aeb9b63ad0cdf23ceafcc90315c82cac18d58 Mon Sep 17 00:00:00 2001 From: Tony Knapp <5892063+texastony@users.noreply.github.com> Date: Tue, 27 Jan 2026 16:41:31 -0600 Subject: [PATCH 18/38] fix(exceptions): properly initialize BotoCoreError subclasses with message formatting (#123) * feat: Exceptions * some duvet stuff we may not need * fix(exceptions): properly initialize BotoCoreError subclasses with message formatting Add BotoCoreError import and implement proper exception initialization: - Add fmt class attribute with {msg} placeholder for message formatting - Override __init__ to accept message parameter with sensible defaults - Add docstrings to __init__ methods - Add unit tests for default, custom exception messages, and inherentance. kiro-clie Cost: ~110,565 tokens Fixes syntax errors and TypeError when raising exceptions with messages. * chore: remove incomplete duvet work * Trigger CI build --- src/s3_encryption/exceptions.py | 22 ++++++++++++-- test/test_exceptions.py | 51 +++++++++++++++++++++++++++++++++ 2 files changed, 71 insertions(+), 2 deletions(-) create mode 100644 test/test_exceptions.py diff --git a/src/s3_encryption/exceptions.py b/src/s3_encryption/exceptions.py index 748075fc..463a180d 100644 --- a/src/s3_encryption/exceptions.py +++ b/src/s3_encryption/exceptions.py @@ -5,6 +5,24 @@ This module contains custom exception classes used throughout the S3 Encryption Client. """ +from botocore.exceptions import BotoCoreError -class S3EncryptionClientError(Exception): - """Exception class for S3 Encryption Client errors.""" + +class S3EncryptionClientError(BotoCoreError): + """Exception class for non-Security S3 Encryption Client errors.""" + + fmt = "{msg}" + + def __init__(self, message="An unspecified S3 Encryption Client error occurred"): + """Initialize the exception with a message.""" + super().__init__(msg=message) + + +class S3EncryptionClientSecurityError(BotoCoreError): + """Security Exceptions for S3 Encryption Client errors.""" + + fmt = "{msg}" + + def __init__(self, message="An unspecified S3 Encryption Client Security error occurred"): + """Initialize the exception with a message.""" + super().__init__(msg=message) diff --git a/test/test_exceptions.py b/test/test_exceptions.py new file mode 100644 index 00000000..f93e3d9d --- /dev/null +++ b/test/test_exceptions.py @@ -0,0 +1,51 @@ +import pytest +from botocore.exceptions import BotoCoreError + +from s3_encryption.exceptions import ( + S3EncryptionClientError, + S3EncryptionClientSecurityError, +) + + +class TestS3EncryptionClientError: + def test_default_message(self): + error = S3EncryptionClientError() + assert str(error) == "An unspecified S3 Encryption Client error occurred" + + def test_custom_message(self): + error = S3EncryptionClientError("Custom error message") + assert str(error) == "Custom error message" + + def test_empty_message(self): + error = S3EncryptionClientError("") + assert str(error) == "" + + def test_inherits_from_botocore_error(self): + error = S3EncryptionClientError("test") + assert isinstance(error, BotoCoreError) + + def test_can_be_caught_as_botocore_error(self): + with pytest.raises(BotoCoreError): + raise S3EncryptionClientError("test error") + + +class TestS3EncryptionClientSecurityError: + def test_default_message(self): + error = S3EncryptionClientSecurityError() + assert str(error) == "An unspecified S3 Encryption Client Security error occurred" + + def test_custom_message(self): + error = S3EncryptionClientSecurityError("Custom security error") + assert str(error) == "Custom security error" + + def test_empty_message(self): + error = S3EncryptionClientSecurityError("") + assert str(error) == "" + + def test_inherits_from_botocore_error(self): + error = S3EncryptionClientSecurityError("test") + assert isinstance(error, BotoCoreError) + + def test_can_be_caught_as_botocore_error(self): + with pytest.raises(BotoCoreError): + raise S3EncryptionClientSecurityError("test security error") From a11430c84b40f25850e07bc2bc131dd11cae73ee Mon Sep 17 00:00:00 2001 From: Kess Plasmeier Date: Tue, 27 Jan 2026 14:52:58 -0800 Subject: [PATCH 19/38] fix compile --- .../software/amazon/encryption/s3/RoundTripTests.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/test-server/java-tests/src/it/java/software/amazon/encryption/s3/RoundTripTests.java b/test-server/java-tests/src/it/java/software/amazon/encryption/s3/RoundTripTests.java index cf45006e..4821aec1 100644 --- a/test-server/java-tests/src/it/java/software/amazon/encryption/s3/RoundTripTests.java +++ b/test-server/java-tests/src/it/java/software/amazon/encryption/s3/RoundTripTests.java @@ -213,7 +213,7 @@ public void crossLanguageTestKmsWithSubsetEncCtxFails(LanguageServerTarget encLa .build()); fail("Expected exception!"); } catch (S3EncryptionClientError e) { - if (decLang.getLanguageName().equals(RUBY_V3) || decLang.getLanguageName().equals(RUBY_V2_CURRENT) || decLang.getLanguageName().equals(RUBY_V2_TRANSITION)) { + if (decLang.getLanguageName().equals(RUBY_V3) || decLang.getLanguageName().equals(RUBY_V2_TRANSITION)) { assertTrue(e.getMessage().contains("Value of encryption context from envelope does not match the provided encryption context"), "Actual error: " + e.getMessage()); } else { assertTrue(e.getMessage().contains("Provided encryption context does not match information retrieved from S3"), "Actual error: " + e.getMessage()); @@ -277,7 +277,7 @@ public void crossLanguageTestKmsWithIncorrectEncCtxFails(LanguageServerTarget en .build()); fail("Expected exception!"); } catch (S3EncryptionClientError e) { - if (decLang.getLanguageName().equals(RUBY_V3) || decLang.getLanguageName().equals(RUBY_V2_CURRENT) || decLang.getLanguageName().equals(RUBY_V2_TRANSITION)) { + if (decLang.getLanguageName().equals(RUBY_V3) || decLang.getLanguageName().equals(RUBY_V2_TRANSITION)) { assertTrue(e.getMessage().contains("Value of encryption context from envelope does not match the provided encryption context"), "Actual error: " + e.getMessage()); } else { assertTrue(e.getMessage().contains("Provided encryption context does not match information retrieved from S3"), "Actual error: " + e.getMessage()); @@ -423,12 +423,12 @@ public void kmsV1LegacyFailsWhenLegacyDisabled(TestUtils.LanguageServerTarget la .build()); fail("Expected Exception"); } catch (S3EncryptionClientError e) { - if (language.getLanguageName().equals(NET_V3_CURRENT) || language.getLanguageName().equals(NET_V2_CURRENT) || language.getLanguageName().equals(NET_V3_TRANSITION) || language.getLanguageName().equals(NET_V4) - || language.getLanguageName().equals(CPP_V2_CURRENT) || language.getLanguageName().equals(CPP_V2_TRANSITION) || language.getLanguageName().equals(CPP_V3)) { + if (language.getLanguageName().equals(NET_V3_TRANSITION) || language.getLanguageName().equals(NET_V4) + || language.getLanguageName().equals(CPP_V2_TRANSITION) || language.getLanguageName().equals(CPP_V3)) { assertTrue(e.getMessage().contains( "The requested object is encrypted with V1 encryption schemas that have been disabled by client configuration" ), "Actual error:" + e.getMessage()); - } else if (language.getLanguageName().equals(RUBY_V3) || language.getLanguageName().equals(RUBY_V2_CURRENT) || language.getLanguageName().equals(RUBY_V2_TRANSITION)) { + } else if (language.getLanguageName().equals(RUBY_V3) || language.getLanguageName().equals(RUBY_V2_TRANSITION)) { assertTrue(e.getMessage().contains( "The requested object is encrypted with V1 encryption schemas that have been disabled by client configuration security_profile = :v2. Retry with :v2_and_legacy or re-encrypt the object." ), "Actual error:" + e.getMessage()); From 0fde32f9af47e54f61aa9dff0d7e2cbd86f00638 Mon Sep 17 00:00:00 2001 From: Tony Knapp <5892063+texastony@users.noreply.github.com> Date: Wed, 28 Jan 2026 14:58:39 -0600 Subject: [PATCH 20/38] chore(deps): bump black, reformat for black bump * chore(deps): bump black * chore: reformat for black bump --- pyproject.toml | 3 +-- src/s3_encryption/__init__.py | 1 + src/s3_encryption/materials/encrypted_data_key.py | 1 + src/s3_encryption/materials/materials.py | 1 + src/s3_encryption/metadata.py | 1 + src/s3_encryption/pipelines.py | 1 + 6 files changed, 6 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 187b99a2..b05f22aa 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -19,8 +19,7 @@ test = [ "pytest>=8.4.1", ] dev = [ - # black >= 26 requires a reformat - "black>=24.3.0,<26.0.0", + "black>=24.3.0,<27.0.0", "ruff>=0.3.0", ] diff --git a/src/s3_encryption/__init__.py b/src/s3_encryption/__init__.py index adfb6886..46cdbdd1 100644 --- a/src/s3_encryption/__init__.py +++ b/src/s3_encryption/__init__.py @@ -1,6 +1,7 @@ # Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. # SPDX-License-Identifier: Apache-2.0 """Top-level S3 Encryption Client v3 for Python package.""" + import io from attrs import define, field diff --git a/src/s3_encryption/materials/encrypted_data_key.py b/src/s3_encryption/materials/encrypted_data_key.py index 28401b40..b2c2359a 100644 --- a/src/s3_encryption/materials/encrypted_data_key.py +++ b/src/s3_encryption/materials/encrypted_data_key.py @@ -5,6 +5,7 @@ This module provides the EncryptedDataKey class which represents an encrypted data key used in the S3 encryption process. """ + from attrs import define diff --git a/src/s3_encryption/materials/materials.py b/src/s3_encryption/materials/materials.py index 9f72ab91..3966e17c 100644 --- a/src/s3_encryption/materials/materials.py +++ b/src/s3_encryption/materials/materials.py @@ -6,6 +6,7 @@ which contain the cryptographic materials needed for S3 object encryption and decryption operations. """ + from typing import Any from attrs import define, field diff --git a/src/s3_encryption/metadata.py b/src/s3_encryption/metadata.py index b4378990..f42feadb 100644 --- a/src/s3_encryption/metadata.py +++ b/src/s3_encryption/metadata.py @@ -5,6 +5,7 @@ This module provides classes and utilities for managing encryption metadata for S3 objects, including serialization and deserialization of metadata. """ + import json from typing import Any diff --git a/src/s3_encryption/pipelines.py b/src/s3_encryption/pipelines.py index 6046fb3a..37093803 100644 --- a/src/s3_encryption/pipelines.py +++ b/src/s3_encryption/pipelines.py @@ -5,6 +5,7 @@ This module provides pipelines for encrypting objects before they are put into S3 and decrypting objects after they are retrieved from S3. """ + import base64 import os From 9626325a6c6817404b8ab9da86b9431757fd9974 Mon Sep 17 00:00:00 2001 From: Kess Plasmeier Date: Wed, 28 Jan 2026 15:52:55 -0800 Subject: [PATCH 21/38] delete cpp-v2-server --- .github/workflows/test.yml | 3 +- .gitmodules | 4 - test-server/cpp-v2-server/.duvet/.gitignore | 3 - test-server/cpp-v2-server/CMakeLists.txt | 39 - test-server/cpp-v2-server/Makefile | 38 - test-server/cpp-v2-server/README.md | 37 - test-server/cpp-v2-server/aws-sdk-cpp | 1 - test-server/cpp-v2-server/main.cpp | 668 ------------------ .../amazon/encryption/s3/TestUtils.java | 2 - 9 files changed, 1 insertion(+), 794 deletions(-) delete mode 100644 test-server/cpp-v2-server/.duvet/.gitignore delete mode 100644 test-server/cpp-v2-server/CMakeLists.txt delete mode 100644 test-server/cpp-v2-server/Makefile delete mode 100644 test-server/cpp-v2-server/README.md delete mode 160000 test-server/cpp-v2-server/aws-sdk-cpp delete mode 100644 test-server/cpp-v2-server/main.cpp diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index d3d101b5..b934bc20 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -58,8 +58,7 @@ jobs: --jobs ${{ steps.cpu-count.outputs.count }} \ --force \ test-server/cpp-v2-transition-server/aws-sdk-cpp \ - test-server/cpp-v3-server/aws-sdk-cpp \ - test-server/cpp-v2-server/aws-sdk-cpp + test-server/cpp-v3-server/aws-sdk-cpp - name: Set up Ruby uses: ruby/setup-ruby@v1 diff --git a/.gitmodules b/.gitmodules index b3f0127c..cc14cae1 100644 --- a/.gitmodules +++ b/.gitmodules @@ -57,7 +57,3 @@ path = test-server/cpp-v3-server/aws-sdk-cpp url = git@github.com:aws/aws-sdk-cpp.git branch = main -[submodule "test-server/cpp-v2-server/aws-sdk-cpp"] - path = test-server/cpp-v2-server/aws-sdk-cpp - url = git@github.com:aws/aws-sdk-cpp.git - branch = main diff --git a/test-server/cpp-v2-server/.duvet/.gitignore b/test-server/cpp-v2-server/.duvet/.gitignore deleted file mode 100644 index 93956e36..00000000 --- a/test-server/cpp-v2-server/.duvet/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -reports/ -requirements/ -specification/ \ No newline at end of file diff --git a/test-server/cpp-v2-server/CMakeLists.txt b/test-server/cpp-v2-server/CMakeLists.txt deleted file mode 100644 index b282dbc4..00000000 --- a/test-server/cpp-v2-server/CMakeLists.txt +++ /dev/null @@ -1,39 +0,0 @@ -cmake_minimum_required(VERSION 3.16) -project(s3ec-cpp-v2-server) - -set(CMAKE_CXX_STANDARD 17) - -# Configure AWS SDK build options -set(BUILD_ONLY "kms;s3;s3-encryption" CACHE STRING "Build only KMS, S3, and S3-encryption components") -set(ENABLE_TESTING OFF CACHE BOOL "Disable testing") -set(BUILD_SHARED_LIBS OFF CACHE BOOL "Build static libraries") - -# Add AWS SDK as subdirectory -add_subdirectory(aws-sdk-cpp) - -find_package(PkgConfig REQUIRED) -pkg_check_modules(LIBMICROHTTPD REQUIRED libmicrohttpd) - -find_package(nlohmann_json REQUIRED) - -add_executable(s3ec-server main.cpp) - -target_include_directories(s3ec-server PRIVATE - ${LIBMICROHTTPD_INCLUDE_DIRS} - /opt/homebrew/include -) - -target_link_directories(s3ec-server PRIVATE - ${LIBMICROHTTPD_LIBRARY_DIRS} - /opt/homebrew/lib -) - -target_link_libraries(s3ec-server - ${LIBMICROHTTPD_LIBRARIES} - aws-cpp-sdk-core - aws-cpp-sdk-kms - aws-cpp-sdk-s3 - aws-cpp-sdk-s3-encryption - nlohmann_json::nlohmann_json - uuid -) \ No newline at end of file diff --git a/test-server/cpp-v2-server/Makefile b/test-server/cpp-v2-server/Makefile deleted file mode 100644 index 2d0a4b55..00000000 --- a/test-server/cpp-v2-server/Makefile +++ /dev/null @@ -1,38 +0,0 @@ -# Makefile for S3 Encryption Client Testing - -.PHONY: build-server start-server stop-server wait-for-server - -PID_FILE := server.pid -PORT := 8085 - -build/s3ec-server: - cd aws-sdk-cpp - mkdir -p build && cd build && cmake .. - -build-server: | build/s3ec-server - @echo "Building Cpp V2 server..." - cd build && $(MAKE) - -start-server: - @echo "Starting Cpp V2 server..." - cd build && \ - AWS_ACCESS_KEY_ID="$$AWS_ACCESS_KEY_ID" \ - AWS_SECRET_ACCESS_KEY="$$AWS_SECRET_ACCESS_KEY" \ - AWS_SESSION_TOKEN="$$AWS_SESSION_TOKEN" \ - AWS_REGION="us-west-2" \ - ./s3ec-server > ../server.log 2>&1 & echo $$! > ../$(PID_FILE) - @echo "Cpp V2 server starting..." - -stop-server: - @echo "Stopping server on port $(PORT)..." - @lsof -ti:$(PORT) | xargs kill -9 2>/dev/null || true - @if [ -f $(PID_FILE) ]; then \ - pkill -P $$(cat $(PID_FILE)) 2>/dev/null || true; \ - kill -9 $$(cat $(PID_FILE)) 2>/dev/null || true; \ - rm -f $(PID_FILE); \ - fi - @rm -f server.log - @echo "Server stopped" - -wait-for-server: - $(MAKE) -C .. wait-for-port PORT=$(PORT) diff --git a/test-server/cpp-v2-server/README.md b/test-server/cpp-v2-server/README.md deleted file mode 100644 index 8e77feda..00000000 --- a/test-server/cpp-v2-server/README.md +++ /dev/null @@ -1,37 +0,0 @@ -# C++ S3 Encryption Test Server - -Minimal C++ implementation of the S3 Encryption test server. - -## Dependencies - -- libmicrohttpd -- AWS SDK for C++ -- nlohmann/json -- uuid - -On MacOS you can -```bash -brew install libmicrohttpd nlohmann-json ossp-uuid -``` - -## Build - -```bash -mkdir build && cd build -cmake .. -make -``` - -## Run - -```bash -./s3ec-server -``` - -Server runs on localhost:8085 - -## API Endpoints - -- `POST /client` - Create S3 encryption client -- `GET /object/{bucket}/{key}` - Get encrypted object -- `PUT /object/{bucket}/{key}` - Put encrypted object \ No newline at end of file diff --git a/test-server/cpp-v2-server/aws-sdk-cpp b/test-server/cpp-v2-server/aws-sdk-cpp deleted file mode 160000 index 9110b0ff..00000000 --- a/test-server/cpp-v2-server/aws-sdk-cpp +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 9110b0ff85094134a4f78316485fd7fe754a2a9c diff --git a/test-server/cpp-v2-server/main.cpp b/test-server/cpp-v2-server/main.cpp deleted file mode 100644 index e8ffe770..00000000 --- a/test-server/cpp-v2-server/main.cpp +++ /dev/null @@ -1,668 +0,0 @@ -/* - * S3 Encryption Test Server - C++ V2 - * - * CONCURRENCY AND SYNCHRONIZATION DESIGN: - * - * 1. Threading Model: - * - Uses MHD_USE_POLL_INTERNALLY with fixed thread pool - * - Thread pool size = CPU cores * 2 (auto-detected at startup) - * - Threads are reused across connections for efficiency - * - I/O multiplexing (poll) distributes connections across thread pool - * - All S3 operations are SYNCHRONOUS - server waits for S3 completion before responding - * - POLL mechanism avoids FD_SETSIZE=1024 limitation of select() - * - * 2. Resource Scaling: - * - All limits automatically scale with detected CPU count: - * * Thread pool size = num_cores * 2 - * * Connection limit = num_cores * 2 - * * S3 client maxConnections = num_cores * 2 - * - Multiplier of 2 accounts for I/O blocking without starving throughput - * - Ensures optimal resource usage on any hardware configuration - * - * 3. Client Cache (client_cache_secret): - * - Protected by std::shared_mutex for efficient concurrent access - * - get_client() uses shared_lock (multiple threads can read simultaneously) - * - set_client() uses unique_lock (exclusive write access) - * - This allows concurrent GET/PUT operations without serialization - * - UUID-based keys guarantee uniqueness (always insert, never update) - * - * 4. Memory Management: - * - Request body allocated in request_handler (*con_cls = new std::string()) - * - Body lifetime managed by libmicrohttpd - valid until request_completed() - * - All handler functions complete synchronously before returning - * - request_completed() safely deletes body after response sent - * - No memory leaks under sustained concurrent load - * - * 5. Synchronous Operation Guarantees: - * - GetObject: Waits for S3, reads full response stream, then returns - * - PutObject: Waits for S3 operation to complete, then returns - * - No async callbacks or background operations - * - Client receives response only after S3 operation completes - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -using json = nlohmann::json; -using namespace Aws::S3Encryption; -using Aws::S3Encryption::Materials::KMSWithContextEncryptionMaterials; - -// LRU cache for S3 encryption clients -// Limits memory and connection pool growth by evicting least recently used clients -const size_t MAX_CACHED_CLIENTS = 100; // Reasonable limit for concurrent test operations - -struct ClientCacheEntry { - std::shared_ptr client; - std::list::iterator lru_iter; -}; - -std::unordered_map client_cache_secret; -std::list lru_order; // Most recently used at front -std::shared_timed_mutex client_mutex; // Using shared_timed_mutex (C++14 compatible) for concurrent reads - -// Threading configuration - set at startup based on CPU cores -unsigned int g_thread_pool_size = 8; // Default, will be overwritten in main() - -std::string generate_uuid() { - uuid_t uuid; - uuid_generate(uuid); - char uuid_str[37]; - uuid_unparse(uuid, uuid_str); - return std::string(uuid_str); -} - -std::shared_ptr get_client(const std::string &client_id) -{ - // Need unique_lock to update LRU order even on reads - std::unique_lock lock(client_mutex); - auto it = client_cache_secret.find(client_id); - if (it == client_cache_secret.end()) { - return std::shared_ptr(); - } else { - // Move to front of LRU list (mark as most recently used) - lru_order.erase(it->second.lru_iter); - lru_order.push_front(client_id); - it->second.lru_iter = lru_order.begin(); - - return it->second.client; - } -} - -void set_client(const std::string &client_id, std::shared_ptr client) -{ - // UUID guarantees unique keys - always insert, never update - // Still need exclusive lock because std::unordered_map isn't thread-safe for concurrent inserts - std::unique_lock lock(client_mutex); - - // Add to front of LRU list (most recently used) - lru_order.push_front(client_id); - - ClientCacheEntry entry; - entry.client = client; - entry.lru_iter = lru_order.begin(); - - client_cache_secret.emplace(client_id, entry); - - // Evict least recently used clients if we exceed the limit - while (client_cache_secret.size() > MAX_CACHED_CLIENTS) { - std::string lru_client_id = lru_order.back(); - lru_order.pop_back(); - - auto evict_it = client_cache_secret.find(lru_client_id); - if (evict_it != client_cache_secret.end()) { - fprintf(stderr, "[CPP-V2] [CACHE-EVICT] Evicting client %s (cache size was %zu)\n", - lru_client_id.c_str(), client_cache_secret.size()); - client_cache_secret.erase(evict_it); - } - } - - fprintf(stderr, "[CPP-V2] [CACHE-ADD] Added client %s (cache size now %zu)\n", - client_id.c_str(), client_cache_secret.size()); -} - -std::string get_header_value(struct MHD_Connection *connection, - const char *key) { - const char *value = - MHD_lookup_connection_value(connection, MHD_HEADER_KIND, key); - return value ? std::string(value) : ""; -} - -MHD_Result send_response(struct MHD_Connection *connection, int status_code, - const std::string &content) { - struct MHD_Response *response = MHD_create_response_from_buffer( - content.length(), (void *)content.data(), MHD_RESPMEM_MUST_COPY); - MHD_Result ret = MHD_queue_response(connection, status_code, response); - MHD_destroy_response(response); - return ret; -} - -std::string make_error(const std::string &message, int status_code) { - return "{\"__type\": " - "\"software.amazon.encryption.s3#S3EncryptionClientError\", " - "\"message\": \"" + - message + "\"}"; -} - -MHD_Result handle_create_client(struct MHD_Connection *connection, - const std::string &body) { - // Body is kept alive by *con_cls until request_completed fires, so it's safe to use directly - // All operations here are synchronous and complete before returning to caller - - try { - json request = json::parse(body); - std::string kms_key_id = request["config"]["keyMaterial"]["kmsKeyId"]; - bool legacy1 = request["config"]["enableLegacyWrappingAlgorithms"]; - bool legacy2 = request["config"]["enableLegacyUnauthenticatedModes"]; - bool inst_put = false; - if (request["config"].contains("instructionFileConfig") && - request["config"]["instructionFileConfig"].contains("enableInstructionFilePutObject")) { - inst_put = request["config"]["instructionFileConfig"]["enableInstructionFilePutObject"]; - } - - auto materials = - std::make_shared(kms_key_id); - CryptoConfigurationV2 config(materials); - if (legacy1 || legacy2) - config.SetSecurityProfile(SecurityProfile::V2_AND_LEGACY); - if (legacy2) - config.SetUnAuthenticatedRangeGet(RangeGetMode::ALL); - if (inst_put) - config.SetStorageMethod(StorageMethod::INSTRUCTION_FILE); - - // Each client gets a large connection pool since we cannot share HTTP clients - Aws::Client::ClientConfiguration clientConfig; - clientConfig.maxConnections = 512; // Large pool per client - clientConfig.retryStrategy = Aws::Client::InitRetryStrategy("standard"); - - // Disable automatic checksum calculation for encrypted streams - // The ChecksumInterceptor cannot handle non-seekable SymmetricCryptoStream - // which causes intermittent "BadDigest: CRC64NVME you specified did not match" errors - // when the stream gets consumed during checksum calculation and can't be rewound - clientConfig.checksumConfig.requestChecksumCalculation = - Aws::Client::RequestChecksumCalculation::WHEN_REQUIRED; - - auto encryption_client = std::make_shared(config, clientConfig); - - std::string client_id = generate_uuid(); - set_client(client_id, encryption_client); - - json response = {{"clientId", client_id}}; - return send_response(connection, 200, response.dump()); - } catch (const std::exception &e) { - fprintf(stderr, "[CPP-V2] handle_create_client exception: %s\n", e.what()); - return send_response(connection, 500, - "{\"error\":\"An exception was thrown.\"}"); - } catch (...) { - return send_response(connection, 500, "{\"error\":\"Unknown error\"}"); - } -} - -void fill_context(Aws::Map &map, - const std::string &metadata) { - if (metadata.empty()) { - fprintf(stderr, "[CPP-V2] [DEBUG] fill_context: metadata is empty\n"); - return; - } - - fprintf(stderr, "[CPP-V2] [DEBUG] fill_context: raw metadata='%s' (length=%zu)\n", - metadata.c_str(), metadata.length()); - - // Parse metadata format: [key1]:[value1],[key2]:[value2],... - // or single pair: [key]:[value] - std::string current = metadata; - size_t pos = 0; - int pair_count = 0; - - while (pos < current.length()) { - // Find opening bracket for key - size_t key_start = current.find('[', pos); - if (key_start == std::string::npos) - break; - - // Find closing bracket for key - size_t key_end = current.find(']', key_start); - if (key_end == std::string::npos) - break; - - // Find colon separator - size_t colon = current.find(':', key_end); - if (colon == std::string::npos) - break; - - // Find opening bracket for value - size_t value_start = current.find('[', colon); - if (value_start == std::string::npos) - break; - - // Find closing bracket for value - size_t value_end = current.find(']', value_start); - if (value_end == std::string::npos) - break; - - // Extract key and value - std::string key = current.substr(key_start + 1, key_end - key_start - 1); - std::string value = - current.substr(value_start + 1, value_end - value_start - 1); - - fprintf(stderr, "[CPP-V2] [DEBUG] fill_context: parsed pair #%d: key='%s', value='%s'\n", - ++pair_count, key.c_str(), value.c_str()); - - // Add to map - map.emplace(key, value); - - // Move to next pair (look for comma or next opening bracket) - pos = value_end + 1; - size_t comma = current.find(',', pos); - if (comma != std::string::npos) { - pos = comma + 1; - } - } - - fprintf(stderr, "[CPP-V2] [DEBUG] fill_context: completed, parsed %d pairs into map\n", pair_count); -} - -MHD_Result handle_get_object(struct MHD_Connection *connection, - std::string bucket, - std::string key, - std::string client_id, - std::string metadata, - std::string range) { - // Get thread ID for debugging concurrent operations - std::thread::id thread_id = std::this_thread::get_id(); - - fprintf(stderr, "[CPP-V2] [DEBUG] GetObject START: thread=%lu, bucket=%s, key=%s, client_id=%s, metadata_length=%zu, range=%s\n", - (unsigned long)std::hash{}(thread_id), bucket.c_str(), key.c_str(), client_id.c_str(), metadata.length(), range.c_str()); - - auto client = get_client(client_id); - if (!client) { - fprintf(stderr, "[CPP-V2] GetObject error: Client not found for client_id=%s\n", client_id.c_str()); - return send_response(connection, 404, "{\"error\":\"Client not found\"}"); - } - - try { - Aws::S3::Model::GetObjectRequest request; - request.SetBucket(bucket); - request.SetKey(key); - - // Add range header if provided - if (!range.empty()) { - request.SetRange(range); - fprintf(stderr, "[CPP-V2] [DEBUG] GetObject: Setting range=%s\n", range.c_str()); - } - - Aws::Map kmsContextMap; - fill_context(kmsContextMap, metadata); - - // Log the encryption context map size and contents - fprintf(stderr, "[CPP-V2] [DEBUG] GetObject: encryption context map size=%zu\n", kmsContextMap.size()); - for (const auto& pair : kmsContextMap) { - fprintf(stderr, "[CPP-V2] [DEBUG] GetObject: context['%s']='%s'\n", - pair.first.c_str(), pair.second.c_str()); - } - - fprintf(stderr, "[CPP-V2] [DEBUG] GetObject: calling client->GetObject() for key=%s\n", key.c_str()); - - // Keep outcome alive to ensure stream remains valid - auto outcome = client->GetObject(request, kmsContextMap); - - fprintf(stderr, "[CPP-V2] [DEBUG] GetObject: client->GetObject() returned for key=%s\n", key.c_str()); - - if (outcome.IsSuccess()) { - // Read the stream completely before outcome goes out of scope - auto &stream = outcome.GetResult().GetBody(); - std::stringstream buffer; - buffer << stream.rdbuf(); - std::string content = buffer.str(); - - // Validate we read something - if (content.empty() && stream.fail()) { - fprintf(stderr, "[CPP-V2] GetObject error: Failed to read stream for bucket=%s, key=%s\n", - bucket.c_str(), key.c_str()); - auto msg = make_error("Failed to read response stream", 500); - return send_response(connection, 500, msg); - } - - fprintf(stderr, "[CPP-V2] GetObject success: bucket=%s, key=%s, size=%zu bytes\n", - bucket.c_str(), key.c_str(), content.length()); - - // Create and send response - struct MHD_Response *response = MHD_create_response_from_buffer( - content.length(), (void *)content.data(), MHD_RESPMEM_MUST_COPY); - - // Add keep-alive header - MHD_add_response_header(response, "Connection", "keep-alive"); - MHD_add_response_header(response, "Keep-Alive", "timeout=30, max=100"); - - MHD_Result ret = MHD_queue_response(connection, 200, response); - MHD_destroy_response(response); - - return ret; - } else { - // Enhanced error logging with thread info - auto error = outcome.GetError(); - fprintf(stderr, "[CPP-V2] [DEBUG] GetObject FAILED: thread=%lu, key=%s\n", - (unsigned long)std::hash{}(thread_id), key.c_str()); - fprintf(stderr, "[CPP-V2] [DEBUG] GetObject error details:\n"); - fprintf(stderr, "[CPP-V2] [DEBUG] - Message: %s\n", error.GetMessage().c_str()); - fprintf(stderr, "[CPP-V2] [DEBUG] - ExceptionName: %s\n", error.GetExceptionName().c_str()); - fprintf(stderr, "[CPP-V2] [DEBUG] - ResponseCode: %d\n", (int)error.GetResponseCode()); - fprintf(stderr, "[CPP-V2] [DEBUG] - ShouldRetry: %s\n", error.ShouldRetry() ? "true" : "false"); - - auto msg = make_error(outcome.GetError().GetMessage(), 500); - fprintf(stderr, "[CPP-V2] GetObject AWS error: %s\n", msg.c_str()); - return send_response(connection, 500, msg); - } - } catch (const std::exception &e) { - fprintf(stderr, "[CPP-V2] [DEBUG] GetObject EXCEPTION: thread=%lu, key=%s, what=%s\n", - (unsigned long)std::hash{}(thread_id), key.c_str(), e.what()); - auto msg = make_error(e.what(), 500); - return send_response(connection, 500, msg); - } catch (...) { - fprintf(stderr, "[CPP-V2] [DEBUG] GetObject UNKNOWN EXCEPTION: thread=%lu, key=%s\n", - (unsigned long)std::hash{}(thread_id), key.c_str()); - auto msg = make_error("Unknown error in GetObject", 500); - return send_response(connection, 500, msg); - } -} - -MHD_Result handle_put_object(struct MHD_Connection *connection, - std::string bucket, - std::string key, - std::string client_id, - std::string body, - std::string metadata) { - fprintf(stderr, "[CPP-V2] PutObject request: bucket=%s, key=%s, client_id=%s, body_size=%zu\n", - bucket.c_str(), key.c_str(), client_id.c_str(), body.length()); - - auto client = get_client(client_id); - if (!client) { - fprintf(stderr, "[CPP-V2] PutObject error: Client not found for client_id=%s\n", client_id.c_str()); - return send_response(connection, 404, "{\"error\":\"Client not found\"}"); - } - - try { - // Create owned copy of body data to ensure it lives through the S3 operation - auto body_ptr = std::make_shared(body); - - Aws::Map kmsContextMap; - fill_context(kmsContextMap, metadata); - - Aws::S3::Model::PutObjectRequest request; - request.SetBucket(bucket); - request.SetKey(key); - - // Create stream from owned body data - auto stream = std::make_shared(*body_ptr); - request.SetBody(stream); - - // Synchronous call - waits for S3 operation to complete - // body_ptr keeps the data alive through this entire operation - auto outcome = client->PutObject(request, kmsContextMap); - if (outcome.IsSuccess()) { - fprintf(stderr, "[CPP-V2] PutObject success: bucket=%s, key=%s\n", bucket.c_str(), key.c_str()); - json response = {{"bucket", bucket}, {"key", key}}; - return send_response(connection, 200, response.dump()); - } else { - auto msg = make_error(outcome.GetError().GetMessage(), 500); - fprintf(stderr, "[CPP-V2] PutObject AWS error: %s\n", msg.c_str()); - return send_response(connection, 500, msg); - } - } catch (const std::exception &e) { - fprintf(stderr, "[CPP-V2] PutObject exception: %s\n", e.what()); - auto msg = make_error(e.what(), 500); - return send_response(connection, 500, msg); - } -} - -void request_completed(void *cls, struct MHD_Connection *connection, - void **con_cls, enum MHD_RequestTerminationCode toe) { - // Clean up the request-specific context when request is truly complete - // This is called AFTER all handlers have returned and the response has been sent - - // Log why the request was terminated - const char* reason = "UNKNOWN"; - switch (toe) { - case MHD_REQUEST_TERMINATED_COMPLETED_OK: - reason = "COMPLETED_OK"; - break; - case MHD_REQUEST_TERMINATED_WITH_ERROR: - reason = "WITH_ERROR"; - break; - case MHD_REQUEST_TERMINATED_TIMEOUT_REACHED: - reason = "TIMEOUT_REACHED"; - break; - case MHD_REQUEST_TERMINATED_DAEMON_SHUTDOWN: - reason = "DAEMON_SHUTDOWN"; - break; - case MHD_REQUEST_TERMINATED_READ_ERROR: - reason = "READ_ERROR"; - break; - case MHD_REQUEST_TERMINATED_CLIENT_ABORT: - reason = "CLIENT_ABORT"; - break; - } - fprintf(stderr, "[CPP-V2] request_completed called, reason=%s, con_cls=%p\n", - reason, *con_cls); - - if (*con_cls != nullptr) { - std::string *body = static_cast(*con_cls); - delete body; // Safe to delete now - all synchronous operations are complete - *con_cls = nullptr; - } -} - -MHD_Result request_handler(void *cls, struct MHD_Connection *connection, - const char *url, const char *method, - const char *version, const char *upload_data, - size_t *upload_data_size, void **con_cls) { - try { - std::string method_str(method); - std::string url_str(url); - bool is_push = method_str == "POST" || method_str == "PUT"; - - // LOG: Every request entry (even first-time calls) - if (*con_cls == nullptr) { - fprintf(stderr, "[CPP-V2] REQUEST START: method=%s, url=%s, version=%s, con_cls=NULL, upload_data_size=%zu\n", - method, url, version, *upload_data_size); - } - - // Initialize request context on first call - if (*con_cls == nullptr) { - // Allocate unique state for each request to avoid race conditions - *con_cls = new std::string(); - fprintf(stderr, "[CPP-V2] REQUEST INIT: allocated new request context for %s %s\n", method, url); - return MHD_YES; - } - - // LOG: Subsequent calls - if (is_push && *upload_data_size > 0) { - fprintf(stderr, "[CPP-V2] REQUEST DATA: %s %s receiving %zu bytes\n", method, url, *upload_data_size); - } else if (*upload_data_size == 0) { - fprintf(stderr, "[CPP-V2] REQUEST COMPLETE: %s %s ready for processing\n", method, url); - } - - // Accumulate request body data for POST/PUT requests - if (is_push && *upload_data_size > 0) { - std::string *body = static_cast(*con_cls); - body->append(upload_data, *upload_data_size); - *upload_data_size = 0; - return MHD_YES; - } - - // At this point, *upload_data_size == 0, meaning we have all the data - // Now we can safely process the request - - // LOG: About to process request - fprintf(stderr, "[CPP-V2] PROCESSING: %s %s\n", method, url); - - // Handle client creation endpoint - if (is_push && url_str == "/client") { - fprintf(stderr, "[CPP-V2] Handling /client endpoint\n"); - std::string *body = static_cast(*con_cls); - MHD_Result result = handle_create_client(connection, *body); - fprintf(stderr, "[CPP-V2] /client handler returned: %d\n", result); - return result; - } - - // Handle object operations - if (url_str.find("/object/") == 0) { - fprintf(stderr, "[CPP-V2] Handling /object/ endpoint\n"); - std::string path = url_str.substr(8); // Remove "/object/" - size_t slash_pos = path.find('/'); - if (slash_pos != std::string::npos) { - std::string bucket = path.substr(0, slash_pos); - std::string key = path.substr(slash_pos + 1); - std::string client_id = get_header_value(connection, "clientid"); - std::string metadata = get_header_value(connection, "content-metadata"); - - fprintf(stderr, "[CPP-V2] Object operation: bucket=%s, key=%s, client_id=%s, method=%s\n", - bucket.c_str(), key.c_str(), client_id.c_str(), method); - - if (method_str == "GET") { - fprintf(stderr, "[CPP-V2] Dispatching to handle_get_object\n"); - std::string range = get_header_value(connection, "Range"); - MHD_Result result = handle_get_object(connection, bucket, key, client_id, metadata, range); - fprintf(stderr, "[CPP-V2] handle_get_object returned: %d\n", result); - return result; - } else if (method_str == "PUT") { - fprintf(stderr, "[CPP-V2] Dispatching to handle_put_object\n"); - std::string *body = static_cast(*con_cls); - MHD_Result result = handle_put_object(connection, bucket, key, client_id, *body, metadata); - fprintf(stderr, "[CPP-V2] handle_put_object returned: %d\n", result); - return result; - } else { - fprintf(stderr, "[CPP-V2] Method not allowed: %s\n", method); - return send_response(connection, 405, "{\"error\":\"Method not allowed\"}"); - } - } - } - - // Return error for unrecognized endpoints - fprintf(stderr, "[CPP-V2] ERROR: Unrecognized endpoint: %s %s\n", method, url); - return send_response(connection, 404, - "{\"error\":\"Not idea what is happening\"}"); - } catch (const std::exception &e) { - fprintf(stderr, "[CPP-V2] FATAL: Unhandled exception in request_handler: %s (method=%s, url=%s)\n", - e.what(), method, url); - // Try to send error response, but connection might already be broken - try { - return send_response(connection, 500, - "{\"error\":\"Internal server error: unhandled exception\"}"); - } catch (...) { - fprintf(stderr, "[CPP-V2] FATAL: Failed to send error response\n"); - return MHD_NO; - } - } catch (...) { - fprintf(stderr, "[CPP-V2] FATAL: Unknown exception in request_handler (method=%s, url=%s)\n", - method, url); - // Try to send error response, but connection might already be broken - try { - return send_response(connection, 500, - "{\"error\":\"Internal server error: unknown exception\"}"); - } catch (...) { - fprintf(stderr, "[CPP-V2] FATAL: Failed to send error response\n"); - return MHD_NO; - } - } -} - -// Error log callback for libmicrohttpd -void log_mhd_error(void* cls, const char* fmt, va_list ap) { - fprintf(stderr, "[CPP-V2] [MHD-ERROR] "); - vfprintf(stderr, fmt, ap); - fprintf(stderr, "\n"); -} - -// Connection notification callback - called when a client connects -MHD_Result notify_connection(void *cls, - struct MHD_Connection *connection, - void **socket_context, - enum MHD_ConnectionNotificationCode toe) { - if (toe == MHD_CONNECTION_NOTIFY_STARTED) { - fprintf(stderr, "[CPP-V2] [MHD-CONNECT] New connection started\n"); - } else if (toe == MHD_CONNECTION_NOTIFY_CLOSED) { - fprintf(stderr, "[CPP-V2] [MHD-DISCONNECT] Connection closed\n"); - } - return MHD_YES; -} - -int main() { - Aws::SDKOptions options; - - // Configure AWS SDK logging to output to stderr (which goes to server.log) - // Using Debug level to capture all SDK activity including CryptoModule errors - options.loggingOptions.logLevel = Aws::Utils::Logging::LogLevel::Debug; - options.loggingOptions.logger_create_fn = []() { - return std::make_shared( - Aws::Utils::Logging::LogLevel::Debug - ); - }; - - fprintf(stderr, "[CONFIG] AWS SDK logging enabled at Debug level\n"); - - Aws::InitAPI(options); - - // Detect CPU core count and configure threading - unsigned int num_cores = std::thread::hardware_concurrency(); - if (num_cores == 0) { - num_cores = 4; - fprintf(stderr, "[CPP-V2] [WARNING] CPU core detection failed, defaulting to %u cores\n", num_cores); - } - - g_thread_pool_size = num_cores * 2; - unsigned int connection_limit = g_thread_pool_size; - - // Log configuration - fprintf(stderr, "[CONFIG] Detected CPU cores: %u\n", num_cores); - fprintf(stderr, "[CONFIG] Thread pool size: %u\n", g_thread_pool_size); - fprintf(stderr, "[CONFIG] Connection limit: %u\n", connection_limit); - fprintf(stderr, "[CONFIG] Each S3 client will use 512 max connections\n"); - - int port = 8085; - - struct MHD_Daemon *daemon = - MHD_start_daemon(MHD_USE_POLL_INTERNALLY | MHD_USE_INTERNAL_POLLING_THREAD | MHD_USE_ERROR_LOG, - port, NULL, NULL, - &request_handler, NULL, - MHD_OPTION_EXTERNAL_LOGGER, log_mhd_error, NULL, - MHD_OPTION_NOTIFY_CONNECTION, notify_connection, NULL, - MHD_OPTION_NOTIFY_COMPLETED, request_completed, NULL, - MHD_OPTION_THREAD_POOL_SIZE, g_thread_pool_size, - MHD_OPTION_CONNECTION_LIMIT, connection_limit, - MHD_OPTION_CONNECTION_TIMEOUT, 10, - MHD_OPTION_END); - - if (!daemon) { - fprintf(stderr, "[CPP-V2] Failed to start server on port %d\n", port); - Aws::ShutdownAPI(options); - return 1; - } - - fprintf(stderr, "Server running on port %d\n", port); - sleep(10000); - - MHD_stop_daemon(daemon); - Aws::ShutdownAPI(options); - fprintf(stderr, "Ending server\n"); - return 0; -} diff --git a/test-server/java-tests/src/it/java/software/amazon/encryption/s3/TestUtils.java b/test-server/java-tests/src/it/java/software/amazon/encryption/s3/TestUtils.java index f06f5cd2..510d57b6 100644 --- a/test-server/java-tests/src/it/java/software/amazon/encryption/s3/TestUtils.java +++ b/test-server/java-tests/src/it/java/software/amazon/encryption/s3/TestUtils.java @@ -76,7 +76,6 @@ public class TestUtils { public static final String NET_V3_TRANSITION = "NET-V3-Transition"; public static final String NET_V4 = "NET-V4"; -// public static final String CPP_V2_CURRENT = "CPP-V2-Current"; public static final String CPP_V2_TRANSITION = "CPP-V2-Transition"; public static final String CPP_V3 = "CPP-V3"; @@ -200,7 +199,6 @@ public class TestUtils { // servers.put(GO_V3_CURRENT, new LanguageServerTarget(GO_V3_CURRENT, "8082")); // servers.put(NET_V2_CURRENT, new LanguageServerTarget(NET_V2_CURRENT, "8083")); // servers.put(NET_V3_CURRENT, new LanguageServerTarget(NET_V3_CURRENT, "8084")); -// servers.put(CPP_V2_CURRENT, new LanguageServerTarget(CPP_V2_CURRENT, "8085")); servers.put(CPP_V2_TRANSITION, new LanguageServerTarget(CPP_V2_TRANSITION, "8097")); servers.put(CPP_V3, new LanguageServerTarget(CPP_V3, "8091")); // servers.put(RUBY_V2_CURRENT, new LanguageServerTarget(RUBY_V2_CURRENT, "8086")); From 51e707168e5e3726e5920bd583add79a713f1994 Mon Sep 17 00:00:00 2001 From: Kess Plasmeier Date: Fri, 30 Jan 2026 11:49:44 -0800 Subject: [PATCH 22/38] delete Java, Go, PHP current servers --- .github/workflows/examples.yml | 3 +- .github/workflows/test.yml | 8 +- .gitmodules | 4 - test-server/README.md | 7 - test-server/go-v3-server/Makefile | 39 -- test-server/go-v3-server/README.md | 23 -- test-server/go-v3-server/go.mod | 31 -- test-server/go-v3-server/go.sum | 46 --- test-server/go-v3-server/main.go | 370 ------------------ .../amazon/encryption/s3/RoundTripTests.java | 4 - .../amazon/encryption/s3/TestUtils.java | 3 - test-server/java-v3-server/.duvet/.gitignore | 3 - test-server/java-v3-server/.duvet/config.toml | 21 - test-server/java-v3-server/.gitignore | 1 - test-server/java-v3-server/Makefile | 39 -- test-server/java-v3-server/README.md | 23 -- test-server/java-v3-server/build.gradle.kts | 56 --- test-server/java-v3-server/gradle.properties | 24 -- .../gradle/wrapper/gradle-wrapper.jar | Bin 43453 -> 0 bytes .../gradle/wrapper/gradle-wrapper.properties | 7 - test-server/java-v3-server/gradlew | 249 ------------ test-server/java-v3-server/gradlew.bat | 92 ----- test-server/java-v3-server/license.txt | 4 - .../java-v3-server/settings.gradle.kts | 19 - test-server/java-v3-server/smithy-build.json | 11 - test-server/java-v3-server/specification | 1 - .../s3/CreateClientOperationImpl.java | 155 -------- .../encryption/s3/GetObjectOperationImpl.java | 78 ---- .../amazon/encryption/s3/MetadataUtils.java | 43 -- .../encryption/s3/PutObjectOperationImpl.java | 55 --- .../encryption/s3/ReEncryptOperationImpl.java | 185 --------- .../encryption/s3/S3ECJavaTestServer.java | 55 --- test-server/php-v2-server/.duvet/.gitignore | 3 - test-server/php-v2-server/.duvet/config.toml | 24 -- test-server/php-v2-server/.gitignore | 4 - test-server/php-v2-server/Makefile | 39 -- test-server/php-v2-server/README.md | 69 ---- test-server/php-v2-server/composer.json | 36 -- test-server/php-v2-server/local-php-sdk | 1 - test-server/php-v2-server/src/client.php | 74 ---- test-server/php-v2-server/src/errors.php | 42 -- test-server/php-v2-server/src/get_object.php | 85 ---- test-server/php-v2-server/src/index.php | 295 -------------- test-server/php-v2-server/src/put_object.php | 79 ---- 44 files changed, 2 insertions(+), 2408 deletions(-) delete mode 100644 test-server/go-v3-server/Makefile delete mode 100644 test-server/go-v3-server/README.md delete mode 100644 test-server/go-v3-server/go.mod delete mode 100644 test-server/go-v3-server/go.sum delete mode 100644 test-server/go-v3-server/main.go delete mode 100644 test-server/java-v3-server/.duvet/.gitignore delete mode 100644 test-server/java-v3-server/.duvet/config.toml delete mode 100644 test-server/java-v3-server/.gitignore delete mode 100644 test-server/java-v3-server/Makefile delete mode 100644 test-server/java-v3-server/README.md delete mode 100644 test-server/java-v3-server/build.gradle.kts delete mode 100644 test-server/java-v3-server/gradle.properties delete mode 100644 test-server/java-v3-server/gradle/wrapper/gradle-wrapper.jar delete mode 100644 test-server/java-v3-server/gradle/wrapper/gradle-wrapper.properties delete mode 100755 test-server/java-v3-server/gradlew delete mode 100644 test-server/java-v3-server/gradlew.bat delete mode 100644 test-server/java-v3-server/license.txt delete mode 100644 test-server/java-v3-server/settings.gradle.kts delete mode 100644 test-server/java-v3-server/smithy-build.json delete mode 120000 test-server/java-v3-server/specification delete mode 100644 test-server/java-v3-server/src/main/java/software/amazon/encryption/s3/CreateClientOperationImpl.java delete mode 100644 test-server/java-v3-server/src/main/java/software/amazon/encryption/s3/GetObjectOperationImpl.java delete mode 100644 test-server/java-v3-server/src/main/java/software/amazon/encryption/s3/MetadataUtils.java delete mode 100644 test-server/java-v3-server/src/main/java/software/amazon/encryption/s3/PutObjectOperationImpl.java delete mode 100644 test-server/java-v3-server/src/main/java/software/amazon/encryption/s3/ReEncryptOperationImpl.java delete mode 100644 test-server/java-v3-server/src/main/java/software/amazon/encryption/s3/S3ECJavaTestServer.java delete mode 100644 test-server/php-v2-server/.duvet/.gitignore delete mode 100644 test-server/php-v2-server/.duvet/config.toml delete mode 100644 test-server/php-v2-server/.gitignore delete mode 100644 test-server/php-v2-server/Makefile delete mode 100644 test-server/php-v2-server/README.md delete mode 100644 test-server/php-v2-server/composer.json delete mode 160000 test-server/php-v2-server/local-php-sdk delete mode 100644 test-server/php-v2-server/src/client.php delete mode 100644 test-server/php-v2-server/src/errors.php delete mode 100644 test-server/php-v2-server/src/get_object.php delete mode 100644 test-server/php-v2-server/src/index.php delete mode 100644 test-server/php-v2-server/src/put_object.php diff --git a/.github/workflows/examples.yml b/.github/workflows/examples.yml index b442be5a..1ef01de4 100644 --- a/.github/workflows/examples.yml +++ b/.github/workflows/examples.yml @@ -64,9 +64,8 @@ jobs: path: | ~/.gradle/caches ~/.gradle/wrapper - test-server/java-v3-server/.gradle test-server/java-tests/.gradle - key: ${{ runner.os }}-gradle-${{ hashFiles('test-server/java-v3-server/**/*.gradle*', 'test-server/java-tests/**/gradle-wrapper.properties', 'test-server/java-tests/**/*.gradle*', 'test-server/java-v3-server/**/gradle-wrapper.properties') }} + key: ${{ runner.os }}-gradle-${{ hashFiles('test-server/java-tests/**/gradle-wrapper.properties', 'test-server/java-tests/**/*.gradle*') }} restore-keys: | ${{ runner.os }}-gradle- diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index b934bc20..e5b5b8a2 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -71,11 +71,6 @@ jobs: with: php-version: "8.1" - - name: Install PHP V2 dependencies - working-directory: ./test-server/php-v2-server - shell: bash - run: composer install - - name: Install PHP V2 Transition dependencies working-directory: ./test-server/php-v2-transition-server shell: bash @@ -132,9 +127,8 @@ jobs: path: | ~/.gradle/caches ~/.gradle/wrapper - test-server/java-v3-server/.gradle test-server/java-tests/.gradle - key: ${{ runner.os }}-gradle-${{ hashFiles('test-server/java-v3-server/**/*.gradle*', 'test-server/java-tests/**/gradle-wrapper.properties', 'test-server/java-tests/**/*.gradle*', 'test-server/java-v3-server/**/gradle-wrapper.properties') }} + key: ${{ runner.os }}-gradle-${{ hashFiles('test-server/java-tests/**/gradle-wrapper.properties', 'test-server/java-tests/**/*.gradle*') }} restore-keys: | ${{ runner.os }}-gradle- diff --git a/.gitmodules b/.gitmodules index cc14cae1..6f0577f6 100644 --- a/.gitmodules +++ b/.gitmodules @@ -6,10 +6,6 @@ path = test-server/ruby-v3-server/local-ruby-sdk url = git@github.com:aws/aws-sdk-ruby.git branch = version-3 -[submodule "test-server/php-v2-server/local-php-sdk"] - path = test-server/php-v2-server/local-php-sdk - url = git@github.com:aws/aws-sdk-php.git - branch = master [submodule "test-server/php-v3-server/local-php-sdk"] path = test-server/php-v3-server/local-php-sdk url = git@github.com:aws/aws-sdk-php.git diff --git a/test-server/README.md b/test-server/README.md index 818e8ded..48187fc3 100644 --- a/test-server/README.md +++ b/test-server/README.md @@ -31,12 +31,6 @@ make start-servers # Start only the Python S3EC V3 server make start-python-v3-server -# Start only the Java S3EC V3 server -make start-java-v3-server - -# Start only the Go S3EC V3 server -make start-go-v3-server - # Run Java tests make run-tests @@ -83,7 +77,6 @@ You can adjust the source pattern or comment style as needed. Examples: - `ruby-v2-server/.duvet/config.toml` -- `php-v2-server/.duvet/config.toml` There are Makefile targets, but you can just run `make duvet` or `duvet report` inside a server directory to run the report. diff --git a/test-server/go-v3-server/Makefile b/test-server/go-v3-server/Makefile deleted file mode 100644 index 80928dbd..00000000 --- a/test-server/go-v3-server/Makefile +++ /dev/null @@ -1,39 +0,0 @@ -# Makefile for S3 Encryption Client Testing - -.PHONY: build-server start-server stop-server wait-for-server - -PID_FILE := server.pid -PORT := 8082 - -build-server: - @echo "Building Go V3 server..." - go mod tidy - -start-server: - @echo "Starting Go V3 server..." - AWS_ACCESS_KEY_ID="$$AWS_ACCESS_KEY_ID" \ - AWS_SECRET_ACCESS_KEY="$$AWS_SECRET_ACCESS_KEY" \ - AWS_SESSION_TOKEN="$$AWS_SESSION_TOKEN" \ - AWS_REGION="us-west-2" \ - go run . > server.log 2>&1 & echo $$! > $(PID_FILE) - @echo "Go V3 server starting..." - -stop-server: - @echo "Stopping server on port $(PORT)..." - @lsof -ti:$(PORT) | xargs kill -9 2>/dev/null || true - @if [ -f $(PID_FILE) ]; then \ - pkill -P $$(cat $(PID_FILE)) 2>/dev/null || true; \ - kill -9 $$(cat $(PID_FILE)) 2>/dev/null || true; \ - rm -f $(PID_FILE); \ - fi - @rm -f server.log - @echo "Server stopped" - -wait-for-server: - $(MAKE) -C .. wait-for-port PORT=$(PORT) - -duvet: - duvet report - -view-report-mac: - open .duvet/reports/report.html diff --git a/test-server/go-v3-server/README.md b/test-server/go-v3-server/README.md deleted file mode 100644 index cf1692b6..00000000 --- a/test-server/go-v3-server/README.md +++ /dev/null @@ -1,23 +0,0 @@ -# S3EC Go V3 Test Server - -This is the Go implementation of the S3ECTestServer framework for S3EC Go V3. It provides a server implementation for testing Go S3 Encryption Client V3 functionality. - -## Overview - -The S3EC Go test server implements the S3ECTestServer service defined in the shared Smithy model. It provides endpoints for: - -- Creating S3 Encryption Clients -- Putting objects with encryption -- Getting and decrypting objects - -## Usage - -To run the server: - -```console -go run . -``` - -This will start the server running on port `8082`. - -The server is used as part of the testing framework to verify cross-language compatibility of the S3 Encryption Client implementations. diff --git a/test-server/go-v3-server/go.mod b/test-server/go-v3-server/go.mod deleted file mode 100644 index 014a64da..00000000 --- a/test-server/go-v3-server/go.mod +++ /dev/null @@ -1,31 +0,0 @@ -module github.com/aws/amazon-s3-encryption-client-python/test-server/go-server - -go 1.21 - -require ( - github.com/aws/amazon-s3-encryption-client-go/v3 v3.1.0 - github.com/aws/aws-sdk-go-v2 v1.24.0 - github.com/aws/aws-sdk-go-v2/config v1.26.1 - github.com/aws/aws-sdk-go-v2/service/kms v1.27.4 - github.com/aws/aws-sdk-go-v2/service/s3 v1.47.5 - github.com/google/uuid v1.5.0 - github.com/gorilla/mux v1.8.1 -) - -require ( - github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.5.4 // indirect - github.com/aws/aws-sdk-go-v2/credentials v1.16.12 // indirect - github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.10 // indirect - github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.9 // indirect - github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.9 // indirect - github.com/aws/aws-sdk-go-v2/internal/ini v1.7.2 // indirect - github.com/aws/aws-sdk-go-v2/internal/v4a v1.2.9 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.10.4 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.2.9 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.10.9 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.16.9 // indirect - github.com/aws/aws-sdk-go-v2/service/sso v1.18.5 // indirect - github.com/aws/aws-sdk-go-v2/service/ssooidc v1.21.5 // indirect - github.com/aws/aws-sdk-go-v2/service/sts v1.26.5 // indirect - github.com/aws/smithy-go v1.19.0 // indirect -) diff --git a/test-server/go-v3-server/go.sum b/test-server/go-v3-server/go.sum deleted file mode 100644 index 4fc073e0..00000000 --- a/test-server/go-v3-server/go.sum +++ /dev/null @@ -1,46 +0,0 @@ -github.com/aws/amazon-s3-encryption-client-go/v3 v3.1.0 h1:P4dOTmTkEb8Dj/LuAoA4bqRZZrDq4DqZQI88vdMaj18= -github.com/aws/amazon-s3-encryption-client-go/v3 v3.1.0/go.mod h1:olnwkBTbWjaJCaGOHohvJu98q40GiJZuDHLXj751mII= -github.com/aws/aws-sdk-go-v2 v1.24.0 h1:890+mqQ+hTpNuw0gGP6/4akolQkSToDJgHfQE7AwGuk= -github.com/aws/aws-sdk-go-v2 v1.24.0/go.mod h1:LNh45Br1YAkEKaAqvmE1m8FUx6a5b/V0oAKV7of29b4= -github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.5.4 h1:OCs21ST2LrepDfD3lwlQiOqIGp6JiEUqG84GzTDoyJs= -github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.5.4/go.mod h1:usURWEKSNNAcAZuzRn/9ZYPT8aZQkR7xcCtunK/LkJo= -github.com/aws/aws-sdk-go-v2/config v1.26.1 h1:z6DqMxclFGL3Zfo+4Q0rLnAZ6yVkzCRxhRMsiRQnD1o= -github.com/aws/aws-sdk-go-v2/config v1.26.1/go.mod h1:ZB+CuKHRbb5v5F0oJtGdhFTelmrxd4iWO1lf0rQwSAg= -github.com/aws/aws-sdk-go-v2/credentials v1.16.12 h1:v/WgB8NxprNvr5inKIiVVrXPuuTegM+K8nncFkr1usU= -github.com/aws/aws-sdk-go-v2/credentials v1.16.12/go.mod h1:X21k0FjEJe+/pauud82HYiQbEr9jRKY3kXEIQ4hXeTQ= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.10 h1:w98BT5w+ao1/r5sUuiH6JkVzjowOKeOJRHERyy1vh58= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.10/go.mod h1:K2WGI7vUvkIv1HoNbfBA1bvIZ+9kL3YVmWxeKuLQsiw= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.9 h1:v+HbZaCGmOwnTTVS86Fleq0vPzOd7tnJGbFhP0stNLs= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.9/go.mod h1:Xjqy+Nyj7VDLBtCMkQYOw1QYfAEZCVLrfI0ezve8wd4= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.9 h1:N94sVhRACtXyVcjXxrwK1SKFIJrA9pOJ5yu2eSHnmls= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.9/go.mod h1:hqamLz7g1/4EJP+GH5NBhcUMLjW+gKLQabgyz6/7WAU= -github.com/aws/aws-sdk-go-v2/internal/ini v1.7.2 h1:GrSw8s0Gs/5zZ0SX+gX4zQjRnRsMJDJ2sLur1gRBhEM= -github.com/aws/aws-sdk-go-v2/internal/ini v1.7.2/go.mod h1:6fQQgfuGmw8Al/3M2IgIllycxV7ZW7WCdVSqfBeUiCY= -github.com/aws/aws-sdk-go-v2/internal/v4a v1.2.9 h1:ugD6qzjYtB7zM5PN/ZIeaAIyefPaD82G8+SJopgvUpw= -github.com/aws/aws-sdk-go-v2/internal/v4a v1.2.9/go.mod h1:YD0aYBWCrPENpHolhKw2XDlTIWae2GKXT1T4o6N6hiM= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.10.4 h1:/b31bi3YVNlkzkBrm9LfpaKoaYZUxIAj4sHfOTmLfqw= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.10.4/go.mod h1:2aGXHFmbInwgP9ZfpmdIfOELL79zhdNYNmReK8qDfdQ= -github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.2.9 h1:/90OR2XbSYfXucBMJ4U14wrjlfleq/0SB6dZDPncgmo= -github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.2.9/go.mod h1:dN/Of9/fNZet7UrQQ6kTDo/VSwKPIq94vjlU16bRARc= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.10.9 h1:Nf2sHxjMJR8CSImIVCONRi4g0Su3J+TSTbS7G0pUeMU= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.10.9/go.mod h1:idky4TER38YIjr2cADF1/ugFMKvZV7p//pVeV5LZbF0= -github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.16.9 h1:iEAeF6YC3l4FzlJPP9H3Ko1TXpdjdqWffxXjp8SY6uk= -github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.16.9/go.mod h1:kjsXoK23q9Z/tLBrckZLLyvjhZoS+AGrzqzUfEClvMM= -github.com/aws/aws-sdk-go-v2/service/kms v1.27.4 h1:c75pHGBV3h6WOsIjbJhLyOnlCPXzap45nbiP2Z5jk5M= -github.com/aws/aws-sdk-go-v2/service/kms v1.27.4/go.mod h1:D9FVDkZjkZnnFHymJ3fPVz0zOUlNSd0xcIIVmmrAac8= -github.com/aws/aws-sdk-go-v2/service/s3 v1.47.5 h1:Keso8lIOS+IzI2MkPZyK6G0LYcK3My2LQ+T5bxghEAY= -github.com/aws/aws-sdk-go-v2/service/s3 v1.47.5/go.mod h1:vADO6Jn+Rq4nDtfwNjhgR84qkZwiC6FqCaXdw/kYwjA= -github.com/aws/aws-sdk-go-v2/service/sso v1.18.5 h1:ldSFWz9tEHAwHNmjx2Cvy1MjP5/L9kNoR0skc6wyOOM= -github.com/aws/aws-sdk-go-v2/service/sso v1.18.5/go.mod h1:CaFfXLYL376jgbP7VKC96uFcU8Rlavak0UlAwk1Dlhc= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.21.5 h1:2k9KmFawS63euAkY4/ixVNsYYwrwnd5fIvgEKkfZFNM= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.21.5/go.mod h1:W+nd4wWDVkSUIox9bacmkBP5NMFQeTJ/xqNabpzSR38= -github.com/aws/aws-sdk-go-v2/service/sts v1.26.5 h1:5UYvv8JUvllZsRnfrcMQ+hJ9jNICmcgKPAO1CER25Wg= -github.com/aws/aws-sdk-go-v2/service/sts v1.26.5/go.mod h1:XX5gh4CB7wAs4KhcF46G6C8a2i7eupU19dcAAE+EydU= -github.com/aws/smithy-go v1.19.0 h1:KWFKQV80DpP3vJrrA9sVAHQ5gc2z8i4EzrLhLlWXcBM= -github.com/aws/smithy-go v1.19.0/go.mod h1:NukqUGpCZIILqqiV0NIjeFh24kd/FAa4beRb6nbIUPE= -github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= -github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU= -github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= -github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= diff --git a/test-server/go-v3-server/main.go b/test-server/go-v3-server/main.go deleted file mode 100644 index 0384c5ff..00000000 --- a/test-server/go-v3-server/main.go +++ /dev/null @@ -1,370 +0,0 @@ -package main - -import ( - "context" - "encoding/json" - "fmt" - "io" - "log" - "net/http" - "strings" - "sync" - - "github.com/aws/amazon-s3-encryption-client-go/v3/client" - "github.com/aws/amazon-s3-encryption-client-go/v3/materials" - "github.com/aws/aws-sdk-go-v2/aws" - "github.com/aws/aws-sdk-go-v2/config" - "github.com/aws/aws-sdk-go-v2/service/kms" - "github.com/aws/aws-sdk-go-v2/service/s3" - "github.com/google/uuid" - "github.com/gorilla/mux" -) - -// Server represents the Go test server -type Server struct { - clientCache map[string]*client.S3EncryptionClientV3 - kmsClient *kms.Client - mu sync.RWMutex -} - -// CreateClientInput represents the input for creating a client -type CreateClientInput struct { - Config S3ECConfig `json:"config"` -} - -// CreateClientOutput represents the output for creating a client -type CreateClientOutput struct { - ClientID string `json:"clientId"` -} - -// S3ECConfig represents the S3 encryption client configuration -type S3ECConfig struct { - EnableLegacyUnauthenticatedModes bool `json:"enableLegacyUnauthenticatedModes"` - EnableDelayedAuthenticationMode bool `json:"enableDelayedAuthenticationMode"` - EnableLegacyWrappingAlgorithms bool `json:"enableLegacyWrappingAlgorithms"` - SetBufferSize int64 `json:"setBufferSize"` - KeyMaterial KeyMaterial `json:"keyMaterial"` -} - -// KeyMaterial represents the key material for encryption -type KeyMaterial struct { - RSAKey []byte `json:"rsaKey"` - AESKey []byte `json:"aesKey"` - KMSKeyID string `json:"kmsKeyId"` -} - -// PutObjectOutput represents the output for put object operation -type PutObjectOutput struct { - Bucket string `json:"bucket"` - Key string `json:"key"` - Metadata []string `json:"metadata"` -} - -// ErrorResponse represents an error response -type ErrorResponse struct { - Type string `json:"__type"` - Message string `json:"message"` -} - -// NewServer creates a new server instance -func NewServer() (*Server, error) { - cfg, err := config.LoadDefaultConfig( - context.TODO(), - config.WithRegion("us-west-2"), - config.WithRetryMaxAttempts(5), - config.WithRetryMode(aws.RetryModeAdaptive), - ) - if err != nil { - return nil, fmt.Errorf("failed to load AWS config: %w", err) - } - - return &Server{ - clientCache: make(map[string]*client.S3EncryptionClientV3), - kmsClient: kms.NewFromConfig(cfg), - }, nil -} - -// createGenericServerError creates a generic server error response -func (s *Server) createGenericServerError(w http.ResponseWriter, message string, statusCode int) { - // Echo error to console - log.Printf("[Go V3] GenericServerError: %s (Status: %d)", message, statusCode) - - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(statusCode) - json.NewEncoder(w).Encode(ErrorResponse{ - Type: "software.amazon.encryption.s3#GenericServerError", - Message: message, - }) -} - -// createS3EncryptionClientError creates an S3 encryption client error response -func (s *Server) createS3EncryptionClientError(w http.ResponseWriter, message string, statusCode int) { - // Echo error to console - log.Printf("[Go V3] S3EncryptionClientError: %s (Status: %d)", message, statusCode) - - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(statusCode) - json.NewEncoder(w).Encode(ErrorResponse{ - Type: "software.amazon.encryption.s3#S3EncryptionClientError", - Message: message, - }) -} - -// metadataStringToMap converts metadata string to map -func metadataStringToMap(mdString string) (map[string]string, error) { - md := make(map[string]string) - if mdString == "" { - return md, nil - } - - mdList := strings.Split(mdString, ",") - for _, entry := range mdList { - // Split on "]:[" to separate key and value - parts := strings.Split(entry, "]:[") - if len(parts) == 2 { - // Remove remaining brackets from start and end - key := parts[0][1:] // Remove first character - value := parts[1][:len(parts[1])-1] // Remove last character - md[key] = value - } else { - return nil, fmt.Errorf("malformed metadata list entry: %s", entry) - } - } - return md, nil -} - -// createClient handles POST /client -func (s *Server) createClient(w http.ResponseWriter, r *http.Request) { - // Read body - body, err := io.ReadAll(r.Body) - if err != nil { - s.createGenericServerError(w, "Failed to read request body", http.StatusBadRequest) - return - } - - var input CreateClientInput - if err := json.Unmarshal(body, &input); err != nil { - s.createGenericServerError(w, "Invalid JSON in request body", http.StatusBadRequest) - return - } - - cfg, err := config.LoadDefaultConfig( - context.TODO(), - config.WithRegion("us-west-2"), - config.WithRetryMaxAttempts(5), - config.WithRetryMode(aws.RetryModeAdaptive), - ) - if err != nil { - s.createS3EncryptionClientError(w, fmt.Sprintf("Failed to load AWS config: %v", err), http.StatusInternalServerError) - return - } - - // Create KMS keyring - kmsClient := kms.NewFromConfig(cfg) - keyring := materials.NewKmsKeyring(kmsClient, input.Config.KeyMaterial.KMSKeyID, func(options *materials.KeyringOptions) { - options.EnableLegacyWrappingAlgorithms = input.Config.EnableLegacyWrappingAlgorithms - }) - cmm, err := materials.NewCryptographicMaterialsManager(keyring) - - if err != nil { - s.createS3EncryptionClientError(w, fmt.Sprintf("Failed to create CMM: %v", err), http.StatusInternalServerError) - return - } - - // Create S3 encryption client - var s3EncryptionClient *client.S3EncryptionClientV3 - s3PlaintextClient := s3.NewFromConfig(cfg) - s3EncryptionClient, err = client.New(s3PlaintextClient, cmm) - - if err != nil { - s.createS3EncryptionClientError(w, fmt.Sprintf("Failed to create S3EC: %v", err), http.StatusInternalServerError) - return - } - - // Generate client ID - clientID := uuid.New().String() - - // Store client in cache (protected by mutex) - s.mu.Lock() - s.clientCache[clientID] = s3EncryptionClient - s.mu.Unlock() - - // Return response - w.Header().Set("Content-Type", "application/json") - json.NewEncoder(w).Encode(CreateClientOutput{ - ClientID: clientID, - }) -} - -// putObject handles PUT /object/{bucket}/{key} -func (s *Server) putObject(w http.ResponseWriter, r *http.Request) { - vars := mux.Vars(r) - bucket := vars["bucket"] - key := vars["key"] - - clientID := r.Header.Get("ClientID") - if clientID == "" { - s.createGenericServerError(w, "ClientID header is required", http.StatusBadRequest) - return - } - - // Get client from cache (protected by mutex) - s.mu.RLock() - client, exists := s.clientCache[clientID] - s.mu.RUnlock() - - if !exists { - s.createGenericServerError(w, fmt.Sprintf("No client found for ClientID: %s", clientID), http.StatusNotFound) - return - } - - // Read body - body, err := io.ReadAll(r.Body) - if err != nil { - s.createGenericServerError(w, "Failed to read request body", http.StatusBadRequest) - return - } - - // Get metadata from header - metadataHeader := r.Header.Get("Content-Metadata") - encCtx, err := metadataStringToMap(metadataHeader) - - // Create context with encryption context - ctx := context.Background() - encryptionContext := context.WithValue(ctx, "EncryptionContext", encCtx) - if err != nil { - s.createS3EncryptionClientError(w, fmt.Sprintf("Failed to parse metadata: %v", err), http.StatusBadRequest) - return - } - - // Create put object input - putInput := &s3.PutObjectInput{ - Bucket: aws.String(bucket), - Key: aws.String(key), - Body: strings.NewReader(string(body)), - } - - // Add metadata if present - if len(encCtx) > 0 { - putInput.Metadata = encCtx - } - - // Make the put object request using the encryption client - _, err = client.PutObject(encryptionContext, putInput) - if err != nil { - s.createS3EncryptionClientError(w, fmt.Sprintf("Failed to put object: %v", err), http.StatusInternalServerError) - return - } - - log.Printf("[Go V3] PutObject SUCCESS: Bucket=%s, Key=%s", bucket, key) - - // Return response - w.Header().Set("Content-Type", "application/json") - response := PutObjectOutput{ - Bucket: bucket, - Key: key, - Metadata: []string{}, // Return empty metadata list as per the model - } - json.NewEncoder(w).Encode(response) -} - -// getObject handles GET /object/{bucket}/{key} -func (s *Server) getObject(w http.ResponseWriter, r *http.Request) { - vars := mux.Vars(r) - bucket := vars["bucket"] - key := vars["key"] - - clientID := r.Header.Get("ClientID") - if clientID == "" { - s.createGenericServerError(w, "ClientID header is required", http.StatusBadRequest) - return - } - - // Get client from cache (protected by mutex) - s.mu.RLock() - client, exists := s.clientCache[clientID] - s.mu.RUnlock() - - if !exists { - s.createGenericServerError(w, fmt.Sprintf("No client found for ClientID: %s", clientID), http.StatusNotFound) - return - } - - // Get metadata from header - metadataHeader := r.Header.Get("Content-Metadata") - encCtx, err := metadataStringToMap(metadataHeader) - - // Create context with encryption context - // Note: S3EC Go V3 does not validate encryption context on decrypt, so the value provided here - // will not be validated against the encryption context stored on the object. - ctx := context.Background() - encryptionContext := context.WithValue(ctx, "EncryptionContext", encCtx) - if err != nil { - s.createS3EncryptionClientError(w, fmt.Sprintf("Failed to parse metadata: %v", err), http.StatusBadRequest) - return - } - - // Create get object input - getInput := &s3.GetObjectInput{ - Bucket: aws.String(bucket), - Key: aws.String(key), - } - - // Make the get object request using the encryption client - result, err := client.GetObject(encryptionContext, getInput) - if err != nil { - errMsg := err.Error() - // Shim the S3EC error message to the error message expected by the test server. - // We don't want to change the S3EC error message but the test server expects a specific error message; - // This is the appropriate place to rewrite the error message. - if strings.Contains(errMsg, "to decrypt x-amz-cek-alg value `kms` you must enable legacyWrappingAlgorithms on the keyring") { - s.createS3EncryptionClientError(w, "Enable legacy wrapping algorithms to use legacy key wrapping algorithm: kms", http.StatusInternalServerError) - return - } - s.createS3EncryptionClientError(w, fmt.Sprintf("Failed to get object: %v", err), http.StatusInternalServerError) - return - } - defer result.Body.Close() - - // Read the body - body, err := io.ReadAll(result.Body) - if err != nil { - s.createS3EncryptionClientError(w, fmt.Sprintf("Failed to read object body: %v", err), http.StatusInternalServerError) - return - } - - // Convert metadata to string format - var metadataList []string - if result.Metadata != nil { - for k, v := range result.Metadata { - metadataList = append(metadataList, fmt.Sprintf("%s=%s", k, v)) - } - } - - metadataStr := strings.Join(metadataList, ",") - - log.Printf("[Go V3] GetObject SUCCESS: Bucket=%s, Key=%s", bucket, key) - - // Set response headers - w.Header().Set("Content-Metadata", metadataStr) - - // Return the body as response - w.Write(body) -} - -func main() { - server, err := NewServer() - if err != nil { - log.Fatalf("[Go V3] Failed to create Go V3 server: %v", err) - } - - r := mux.NewRouter() - - // Register routes - r.HandleFunc("/client", server.createClient).Methods("POST") - r.HandleFunc("/object/{bucket}/{key}", server.putObject).Methods("PUT") - r.HandleFunc("/object/{bucket}/{key}", server.getObject).Methods("GET") - - fmt.Println("[Go V3] Starting Go V3 server on :8082...") - log.Fatal(http.ListenAndServe(":8082", r)) -} diff --git a/test-server/java-tests/src/it/java/software/amazon/encryption/s3/RoundTripTests.java b/test-server/java-tests/src/it/java/software/amazon/encryption/s3/RoundTripTests.java index 4821aec1..e6cfae84 100644 --- a/test-server/java-tests/src/it/java/software/amazon/encryption/s3/RoundTripTests.java +++ b/test-server/java-tests/src/it/java/software/amazon/encryption/s3/RoundTripTests.java @@ -549,10 +549,6 @@ public void instructionFileWriteAndRead(LanguageServerTarget encLang, LanguageSe if (KMS_INSTRUCTION_FILE_UNSUPPORTED.contains(decLang.getLanguageName())) { throw new TestAbortedException("not testing " + encLang.getLanguageName()); } - // We skip PHP-V2-Current because it writes an instruction file that other languages may not read. - if (encLang.getLanguageName().equals("PHP-V2-Current")) { - throw new TestAbortedException("not testing " + encLang.getLanguageName()); - } S3ECTestServerClient encClient = testServerClientFor(encLang); S3ECTestServerClient decClient = testServerClientFor(decLang); final String objectKey = appendTestSuffix(String.format("write-%s-read-%s-instruction-file", encLang.getLanguageName(), decLang.getLanguageName())); diff --git a/test-server/java-tests/src/it/java/software/amazon/encryption/s3/TestUtils.java b/test-server/java-tests/src/it/java/software/amazon/encryption/s3/TestUtils.java index 510d57b6..4084c2a1 100644 --- a/test-server/java-tests/src/it/java/software/amazon/encryption/s3/TestUtils.java +++ b/test-server/java-tests/src/it/java/software/amazon/encryption/s3/TestUtils.java @@ -59,14 +59,12 @@ public class TestUtils { // vN-Transition: Proposed feature release version. Supports reading messages encrypted with key commitment. // vN+1: Proposed breaking release version. Supports reading/writing messages encrypted with key commitment. -// public static final String JAVA_V3_CURRENT = "Java-V3-Current"; public static final String JAVA_V3_TRANSITION = "Java-V3-Transition"; public static final String JAVA_V4 = "Java-V4"; // No Python S3EC versions are released. Only test V3 as the "vN+1" version. public static final String PYTHON_V3 = "Python-V3"; -// public static final String GO_V3_CURRENT = "Go-V3-Current"; public static final String GO_V3_TRANSITION = "Go-V3-Transition"; public static final String GO_V4 = "Go-V4"; @@ -83,7 +81,6 @@ public class TestUtils { public static final String RUBY_V2_TRANSITION = "Ruby-V2-Transition"; public static final String RUBY_V3 = "Ruby-V3"; -// public static final String PHP_V2_CURRENT = "PHP-V2-Current"; public static final String PHP_V2_TRANSITION = "PHP-V2-Transition"; public static final String PHP_V3 = "PHP-V3"; diff --git a/test-server/java-v3-server/.duvet/.gitignore b/test-server/java-v3-server/.duvet/.gitignore deleted file mode 100644 index 93956e36..00000000 --- a/test-server/java-v3-server/.duvet/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -reports/ -requirements/ -specification/ \ No newline at end of file diff --git a/test-server/java-v3-server/.duvet/config.toml b/test-server/java-v3-server/.duvet/config.toml deleted file mode 100644 index 988fb5fa..00000000 --- a/test-server/java-v3-server/.duvet/config.toml +++ /dev/null @@ -1,21 +0,0 @@ -'$schema' = "https://awslabs.github.io/duvet/config/v0.4.0.json" - -[[source]] -pattern = "s3ec-staging/**/*.java" - -# Include required specifications here -[[specification]] -source = "specification/s3-encryption/data-format/content-metadata.md" -[[specification]] -source = "specification/s3-encryption/data-format/metadata-strategy.md" -[[specification]] -source = "specification/s3-encryption/encryption.md" -[[specification]] -source = "specification/s3-encryption/key-derivation.md" - -[report.html] -enabled = true - -# Enable snapshots to prevent requirement coverage regressions -[report.snapshot] -enabled = false diff --git a/test-server/java-v3-server/.gitignore b/test-server/java-v3-server/.gitignore deleted file mode 100644 index e660fd93..00000000 --- a/test-server/java-v3-server/.gitignore +++ /dev/null @@ -1 +0,0 @@ -bin/ diff --git a/test-server/java-v3-server/Makefile b/test-server/java-v3-server/Makefile deleted file mode 100644 index 59dcdff5..00000000 --- a/test-server/java-v3-server/Makefile +++ /dev/null @@ -1,39 +0,0 @@ -# Makefile for S3 Encryption Client Testing - -.PHONY: build-server start-server stop-server wait-for-server - -PID_FILE := server.pid -PORT := 8080 - -build-server: - @echo "Building Java V3 server..." - ./gradlew --build-cache --parallel --no-daemon build - -start-server: - @echo "Starting Java V3 server..." - AWS_ACCESS_KEY_ID="$$AWS_ACCESS_KEY_ID" \ - AWS_SECRET_ACCESS_KEY="$$AWS_SECRET_ACCESS_KEY" \ - AWS_SESSION_TOKEN="$$AWS_SESSION_TOKEN" \ - AWS_REGION="us-west-2" \ - ./gradlew --build-cache --parallel run > server.log 2>&1 & echo $$! > $(PID_FILE) - @echo "Java V3 server starting..." - -stop-server: - @echo "Stopping server on port $(PORT)..." - @lsof -ti:$(PORT) | xargs kill -9 2>/dev/null || true - @if [ -f $(PID_FILE) ]; then \ - pkill -P $$(cat $(PID_FILE)) 2>/dev/null || true; \ - kill -9 $$(cat $(PID_FILE)) 2>/dev/null || true; \ - rm -f $(PID_FILE); \ - fi - @rm -f server.log - @echo "Server stopped" - -wait-for-server: - $(MAKE) -C .. wait-for-port PORT=$(PORT) - -duvet: - duvet report - -view-report-mac: - open .duvet/reports/report.html diff --git a/test-server/java-v3-server/README.md b/test-server/java-v3-server/README.md deleted file mode 100644 index e00eb496..00000000 --- a/test-server/java-v3-server/README.md +++ /dev/null @@ -1,23 +0,0 @@ -# S3EC Java V3 Test Server - -This is the Java implementation of the S3ECTestServer framework for S3EC Java V3. It provides a server implementation for testing Java S3 Encryption Client V3 functionality. - -## Overview - -The S3ECJavaTestServer implements the S3ECTestServer service defined in the shared Smithy model. It provides endpoints for: - -- Creating S3 Encryption Clients -- Putting objects with encryption -- Getting and decrypting objects - -## Usage - -To run the server: - -```console -gradle run -``` - -This will start the server running on port `8080`. - -The server is used as part of the testing framework to verify cross-language compatibility of the S3 Encryption Client implementations. diff --git a/test-server/java-v3-server/build.gradle.kts b/test-server/java-v3-server/build.gradle.kts deleted file mode 100644 index baae4947..00000000 --- a/test-server/java-v3-server/build.gradle.kts +++ /dev/null @@ -1,56 +0,0 @@ -plugins { - `java-library` - id("software.amazon.smithy.gradle.smithy-base") - application -} - -dependencies { - val smithyJavaVersion: String by project - - smithyBuild("software.amazon.smithy.java:plugins:$smithyJavaVersion") - - implementation("software.amazon.smithy:smithy-rules-engine:1.59.0") - implementation("software.amazon.smithy.java:server-netty:$smithyJavaVersion") - implementation("software.amazon.smithy.java:aws-server-restjson:$smithyJavaVersion") - - compileOnly("software.amazon.awssdk:aws-sdk-java:2.31.66") - // This MUST stay at 3.5.0 - implementation("software.amazon.encryption.s3:amazon-s3-encryption-client-java:3.5.0") -} - -// Use that application plugin to start the service via the `run` task. -application { - mainClass = "software.amazon.encryption.s3.S3ECJavaTestServer" -} - -// Add generated Java files to the main sourceSet -afterEvaluate { - val serverPath = smithy.getPluginProjectionPath(smithy.sourceProjection.get(), "java-server-codegen") - sourceSets { - main { - java { - srcDir(serverPath) - } - } - } -} - -tasks { - compileJava { - dependsOn(smithyBuild) - } -} - -// Helps Intellij IDE's discover smithy models -sourceSets { - main { - java { - srcDir("../model") - } - } -} - -repositories { - mavenLocal() - mavenCentral() -} diff --git a/test-server/java-v3-server/gradle.properties b/test-server/java-v3-server/gradle.properties deleted file mode 100644 index 483cd315..00000000 --- a/test-server/java-v3-server/gradle.properties +++ /dev/null @@ -1,24 +0,0 @@ -# Smithy versions -smithyJavaVersion=[0,1] -smithyGradleVersion=1.1.0 -smithyVersion=[1,2] - -# Performance optimization settings - -# Force no-daemon mode - ensures Gradle doesn't try to keep a daemon alive -org.gradle.daemon=false - -# Set minimal idle timeout for any daemon-like behavior (1 second) -org.gradle.daemon.idletimeout=1000 - -# JVM arguments to prevent forking a separate JVM process -# By matching the JVM args here with what Gradle expects, we avoid the -# "single-use Daemon process will be forked" behavior -org.gradle.jvmargs=-Xmx2g -XX:MaxMetaspaceSize=512m -XX:+HeapDumpOnOutOfMemoryError -XX:+UseParallelGC - -# Keep builds fast with parallel execution and caching -org.gradle.parallel=true -org.gradle.caching=true - -# Configure on demand to reduce startup time -org.gradle.configureondemand=true diff --git a/test-server/java-v3-server/gradle/wrapper/gradle-wrapper.jar b/test-server/java-v3-server/gradle/wrapper/gradle-wrapper.jar deleted file mode 100644 index e6441136f3d4ba8a0da8d277868979cfbc8ad796..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 43453 zcma&N1CXTcmMvW9vTb(Rwr$&4wr$(C?dmSu>@vG-+vuvg^_??!{yS%8zW-#zn-LkA z5&1^$^{lnmUON?}LBF8_K|(?T0Ra(xUH{($5eN!MR#ZihR#HxkUPe+_R8Cn`RRs(P z_^*#_XlXmGv7!4;*Y%p4nw?{bNp@UZHv1?Um8r6)Fei3p@ClJn0ECfg1hkeuUU@Or zDaPa;U3fE=3L}DooL;8f;P0ipPt0Z~9P0)lbStMS)ag54=uL9ia-Lm3nh|@(Y?B`; zx_#arJIpXH!U{fbCbI^17}6Ri*H<>OLR%c|^mh8+)*h~K8Z!9)DPf zR2h?lbDZQ`p9P;&DQ4F0sur@TMa!Y}S8irn(%d-gi0*WxxCSk*A?3lGh=gcYN?FGl z7D=Js!i~0=u3rox^eO3i@$0=n{K1lPNU zwmfjRVmLOCRfe=seV&P*1Iq=^i`502keY8Uy-WNPwVNNtJFx?IwAyRPZo2Wo1+S(xF37LJZ~%i)kpFQ3Fw=mXfd@>%+)RpYQLnr}B~~zoof(JVm^^&f zxKV^+3D3$A1G;qh4gPVjhrC8e(VYUHv#dy^)(RoUFM?o%W-EHxufuWf(l*@-l+7vt z=l`qmR56K~F|v<^Pd*p~1_y^P0P^aPC##d8+HqX4IR1gu+7w#~TBFphJxF)T$2WEa zxa?H&6=Qe7d(#tha?_1uQys2KtHQ{)Qco)qwGjrdNL7thd^G5i8Os)CHqc>iOidS} z%nFEDdm=GXBw=yXe1W-ShHHFb?Cc70+$W~z_+}nAoHFYI1MV1wZegw*0y^tC*s%3h zhD3tN8b=Gv&rj}!SUM6|ajSPp*58KR7MPpI{oAJCtY~JECm)*m_x>AZEu>DFgUcby z1Qaw8lU4jZpQ_$;*7RME+gq1KySGG#Wql>aL~k9tLrSO()LWn*q&YxHEuzmwd1?aAtI zBJ>P=&$=l1efe1CDU;`Fd+_;&wI07?V0aAIgc(!{a z0Jg6Y=inXc3^n!U0Atk`iCFIQooHqcWhO(qrieUOW8X(x?(RD}iYDLMjSwffH2~tB z)oDgNBLB^AJBM1M^c5HdRx6fBfka`(LD-qrlh5jqH~);#nw|iyp)()xVYak3;Ybik z0j`(+69aK*B>)e_p%=wu8XC&9e{AO4c~O1U`5X9}?0mrd*m$_EUek{R?DNSh(=br# z#Q61gBzEpmy`$pA*6!87 zSDD+=@fTY7<4A?GLqpA?Pb2z$pbCc4B4zL{BeZ?F-8`s$?>*lXXtn*NC61>|*w7J* z$?!iB{6R-0=KFmyp1nnEmLsA-H0a6l+1uaH^g%c(p{iT&YFrbQ$&PRb8Up#X3@Zsk zD^^&LK~111%cqlP%!_gFNa^dTYT?rhkGl}5=fL{a`UViaXWI$k-UcHJwmaH1s=S$4 z%4)PdWJX;hh5UoK?6aWoyLxX&NhNRqKam7tcOkLh{%j3K^4Mgx1@i|Pi&}<^5>hs5 zm8?uOS>%)NzT(%PjVPGa?X%`N2TQCKbeH2l;cTnHiHppPSJ<7y-yEIiC!P*ikl&!B z%+?>VttCOQM@ShFguHVjxX^?mHX^hSaO_;pnyh^v9EumqSZTi+#f&_Vaija0Q-e*| z7ulQj6Fs*bbmsWp{`auM04gGwsYYdNNZcg|ph0OgD>7O}Asn7^Z=eI>`$2*v78;sj-}oMoEj&@)9+ycEOo92xSyY344^ z11Hb8^kdOvbf^GNAK++bYioknrpdN>+u8R?JxG=!2Kd9r=YWCOJYXYuM0cOq^FhEd zBg2puKy__7VT3-r*dG4c62Wgxi52EMCQ`bKgf*#*ou(D4-ZN$+mg&7$u!! z-^+Z%;-3IDwqZ|K=ah85OLwkO zKxNBh+4QHh)u9D?MFtpbl)us}9+V!D%w9jfAMYEb>%$A;u)rrI zuBudh;5PN}_6J_}l55P3l_)&RMlH{m!)ai-i$g)&*M`eN$XQMw{v^r@-125^RRCF0 z^2>|DxhQw(mtNEI2Kj(;KblC7x=JlK$@78`O~>V!`|1Lm-^JR$-5pUANAnb(5}B}JGjBsliK4& zk6y(;$e&h)lh2)L=bvZKbvh@>vLlreBdH8No2>$#%_Wp1U0N7Ank!6$dFSi#xzh|( zRi{Uw%-4W!{IXZ)fWx@XX6;&(m_F%c6~X8hx=BN1&q}*( zoaNjWabE{oUPb!Bt$eyd#$5j9rItB-h*5JiNi(v^e|XKAj*8(k<5-2$&ZBR5fF|JA z9&m4fbzNQnAU}r8ab>fFV%J0z5awe#UZ|bz?Ur)U9bCIKWEzi2%A+5CLqh?}K4JHi z4vtM;+uPsVz{Lfr;78W78gC;z*yTch~4YkLr&m-7%-xc ztw6Mh2d>_iO*$Rd8(-Cr1_V8EO1f*^@wRoSozS) zy1UoC@pruAaC8Z_7~_w4Q6n*&B0AjOmMWa;sIav&gu z|J5&|{=a@vR!~k-OjKEgPFCzcJ>#A1uL&7xTDn;{XBdeM}V=l3B8fE1--DHjSaxoSjNKEM9|U9#m2<3>n{Iuo`r3UZp;>GkT2YBNAh|b z^jTq-hJp(ebZh#Lk8hVBP%qXwv-@vbvoREX$TqRGTgEi$%_F9tZES@z8Bx}$#5eeG zk^UsLBH{bc2VBW)*EdS({yw=?qmevwi?BL6*=12k9zM5gJv1>y#ML4!)iiPzVaH9% zgSImetD@dam~e>{LvVh!phhzpW+iFvWpGT#CVE5TQ40n%F|p(sP5mXxna+Ev7PDwA zamaV4m*^~*xV+&p;W749xhb_X=$|LD;FHuB&JL5?*Y2-oIT(wYY2;73<^#46S~Gx| z^cez%V7x$81}UWqS13Gz80379Rj;6~WdiXWOSsdmzY39L;Hg3MH43o*y8ibNBBH`(av4|u;YPq%{R;IuYow<+GEsf@R?=@tT@!}?#>zIIn0CoyV!hq3mw zHj>OOjfJM3F{RG#6ujzo?y32m^tgSXf@v=J$ELdJ+=5j|=F-~hP$G&}tDZsZE?5rX ztGj`!S>)CFmdkccxM9eGIcGnS2AfK#gXwj%esuIBNJQP1WV~b~+D7PJTmWGTSDrR` zEAu4B8l>NPuhsk5a`rReSya2nfV1EK01+G!x8aBdTs3Io$u5!6n6KX%uv@DxAp3F@{4UYg4SWJtQ-W~0MDb|j-$lwVn znAm*Pl!?Ps&3wO=R115RWKb*JKoexo*)uhhHBncEDMSVa_PyA>k{Zm2(wMQ(5NM3# z)jkza|GoWEQo4^s*wE(gHz?Xsg4`}HUAcs42cM1-qq_=+=!Gk^y710j=66(cSWqUe zklbm8+zB_syQv5A2rj!Vbw8;|$@C!vfNmNV!yJIWDQ>{+2x zKjuFX`~~HKG~^6h5FntRpnnHt=D&rq0>IJ9#F0eM)Y-)GpRjiN7gkA8wvnG#K=q{q z9dBn8_~wm4J<3J_vl|9H{7q6u2A!cW{bp#r*-f{gOV^e=8S{nc1DxMHFwuM$;aVI^ zz6A*}m8N-&x8;aunp1w7_vtB*pa+OYBw=TMc6QK=mbA-|Cf* zvyh8D4LRJImooUaSb7t*fVfih<97Gf@VE0|z>NcBwBQze);Rh!k3K_sfunToZY;f2 z^HmC4KjHRVg+eKYj;PRN^|E0>Gj_zagfRbrki68I^#~6-HaHg3BUW%+clM1xQEdPYt_g<2K+z!$>*$9nQ>; zf9Bei{?zY^-e{q_*|W#2rJG`2fy@{%6u0i_VEWTq$*(ZN37|8lFFFt)nCG({r!q#9 z5VK_kkSJ3?zOH)OezMT{!YkCuSSn!K#-Rhl$uUM(bq*jY? zi1xbMVthJ`E>d>(f3)~fozjg^@eheMF6<)I`oeJYx4*+M&%c9VArn(OM-wp%M<-`x z7sLP1&3^%Nld9Dhm@$3f2}87!quhI@nwd@3~fZl_3LYW-B?Ia>ui`ELg z&Qfe!7m6ze=mZ`Ia9$z|ARSw|IdMpooY4YiPN8K z4B(ts3p%2i(Td=tgEHX z0UQ_>URBtG+-?0E;E7Ld^dyZ;jjw0}XZ(}-QzC6+NN=40oDb2^v!L1g9xRvE#@IBR zO!b-2N7wVfLV;mhEaXQ9XAU+>=XVA6f&T4Z-@AX!leJ8obP^P^wP0aICND?~w&NykJ#54x3_@r7IDMdRNy4Hh;h*!u(Ol(#0bJdwEo$5437-UBjQ+j=Ic>Q2z` zJNDf0yO6@mr6y1#n3)s(W|$iE_i8r@Gd@!DWDqZ7J&~gAm1#~maIGJ1sls^gxL9LLG_NhU!pTGty!TbhzQnu)I*S^54U6Yu%ZeCg`R>Q zhBv$n5j0v%O_j{QYWG!R9W?5_b&67KB$t}&e2LdMvd(PxN6Ir!H4>PNlerpBL>Zvyy!yw z-SOo8caEpDt(}|gKPBd$qND5#a5nju^O>V&;f890?yEOfkSG^HQVmEbM3Ugzu+UtH zC(INPDdraBN?P%kE;*Ae%Wto&sgw(crfZ#Qy(<4nk;S|hD3j{IQRI6Yq|f^basLY; z-HB&Je%Gg}Jt@={_C{L$!RM;$$|iD6vu#3w?v?*;&()uB|I-XqEKqZPS!reW9JkLewLb!70T7n`i!gNtb1%vN- zySZj{8-1>6E%H&=V}LM#xmt`J3XQoaD|@XygXjdZ1+P77-=;=eYpoEQ01B@L*a(uW zrZeZz?HJsw_4g0vhUgkg@VF8<-X$B8pOqCuWAl28uB|@r`19DTUQQsb^pfqB6QtiT z*`_UZ`fT}vtUY#%sq2{rchyfu*pCg;uec2$-$N_xgjZcoumE5vSI{+s@iLWoz^Mf; zuI8kDP{!XY6OP~q5}%1&L}CtfH^N<3o4L@J@zg1-mt{9L`s^z$Vgb|mr{@WiwAqKg zp#t-lhrU>F8o0s1q_9y`gQNf~Vb!F%70f}$>i7o4ho$`uciNf=xgJ>&!gSt0g;M>*x4-`U)ysFW&Vs^Vk6m%?iuWU+o&m(2Jm26Y(3%TL; zA7T)BP{WS!&xmxNw%J=$MPfn(9*^*TV;$JwRy8Zl*yUZi8jWYF>==j~&S|Xinsb%c z2?B+kpet*muEW7@AzjBA^wAJBY8i|#C{WtO_or&Nj2{=6JTTX05}|H>N2B|Wf!*3_ z7hW*j6p3TvpghEc6-wufFiY!%-GvOx*bZrhZu+7?iSrZL5q9}igiF^*R3%DE4aCHZ zqu>xS8LkW+Auv%z-<1Xs92u23R$nk@Pk}MU5!gT|c7vGlEA%G^2th&Q*zfg%-D^=f z&J_}jskj|Q;73NP4<4k*Y%pXPU2Thoqr+5uH1yEYM|VtBPW6lXaetokD0u z9qVek6Q&wk)tFbQ8(^HGf3Wp16gKmr>G;#G(HRBx?F`9AIRboK+;OfHaLJ(P>IP0w zyTbTkx_THEOs%Q&aPrxbZrJlio+hCC_HK<4%f3ZoSAyG7Dn`=X=&h@m*|UYO-4Hq0 z-Bq&+Ie!S##4A6OGoC~>ZW`Y5J)*ouaFl_e9GA*VSL!O_@xGiBw!AF}1{tB)z(w%c zS1Hmrb9OC8>0a_$BzeiN?rkPLc9%&;1CZW*4}CDDNr2gcl_3z+WC15&H1Zc2{o~i) z)LLW=WQ{?ricmC`G1GfJ0Yp4Dy~Ba;j6ZV4r{8xRs`13{dD!xXmr^Aga|C=iSmor% z8hi|pTXH)5Yf&v~exp3o+sY4B^^b*eYkkCYl*T{*=-0HniSA_1F53eCb{x~1k3*`W zr~};p1A`k{1DV9=UPnLDgz{aJH=-LQo<5%+Em!DNN252xwIf*wF_zS^!(XSm(9eoj z=*dXG&n0>)_)N5oc6v!>-bd(2ragD8O=M|wGW z!xJQS<)u70m&6OmrF0WSsr@I%T*c#Qo#Ha4d3COcX+9}hM5!7JIGF>7<~C(Ear^Sn zm^ZFkV6~Ula6+8S?oOROOA6$C&q&dp`>oR-2Ym3(HT@O7Sd5c~+kjrmM)YmgPH*tL zX+znN>`tv;5eOfX?h{AuX^LK~V#gPCu=)Tigtq9&?7Xh$qN|%A$?V*v=&-2F$zTUv z`C#WyIrChS5|Kgm_GeudCFf;)!WH7FI60j^0o#65o6`w*S7R@)88n$1nrgU(oU0M9 zx+EuMkC>(4j1;m6NoGqEkpJYJ?vc|B zOlwT3t&UgL!pX_P*6g36`ZXQ; z9~Cv}ANFnJGp(;ZhS(@FT;3e)0)Kp;h^x;$*xZn*k0U6-&FwI=uOGaODdrsp-!K$Ac32^c{+FhI-HkYd5v=`PGsg%6I`4d9Jy)uW0y%) zm&j^9WBAp*P8#kGJUhB!L?a%h$hJgQrx!6KCB_TRo%9{t0J7KW8!o1B!NC)VGLM5! zpZy5Jc{`r{1e(jd%jsG7k%I+m#CGS*BPA65ZVW~fLYw0dA-H_}O zrkGFL&P1PG9p2(%QiEWm6x;U-U&I#;Em$nx-_I^wtgw3xUPVVu zqSuKnx&dIT-XT+T10p;yjo1Y)z(x1fb8Dzfn8e yu?e%!_ptzGB|8GrCfu%p?(_ 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
NnX)EH+Nua)3Y(c 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 '} - case $link in #( - /*) app_path=$link ;; #( - *) app_path=$APP_HOME$link ;; - esac -done - -# This is normally unused -# shellcheck disable=SC2034 -APP_BASE_NAME=${0##*/} -# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit - -# Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD=maximum - -warn () { - echo "$*" -} >&2 - -die () { - echo - echo "$*" - echo - exit 1 -} >&2 - -# OS specific support (must be 'true' or 'false'). -cygwin=false -msys=false -darwin=false -nonstop=false -case "$( uname )" in #( - CYGWIN* ) cygwin=true ;; #( - Darwin* ) darwin=true ;; #( - MSYS* | MINGW* ) msys=true ;; #( - NONSTOP* ) nonstop=true ;; -esac - -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar - - -# Determine the Java command to use to start the JVM. -if [ -n "$JAVA_HOME" ] ; then - if [ -x "$JAVA_HOME/jre/sh/java" ] ; then - # IBM's JDK on AIX uses strange locations for the executables - JAVACMD=$JAVA_HOME/jre/sh/java - else - JAVACMD=$JAVA_HOME/bin/java - fi - if [ ! -x "$JAVACMD" ] ; then - die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME - -Please set the JAVA_HOME variable in your environment to match the -location of your Java installation." - fi -else - JAVACMD=java - if ! command -v java >/dev/null 2>&1 - then - die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. - -Please set the JAVA_HOME variable in your environment to match the -location of your Java installation." - fi -fi - -# Increase the maximum file descriptors if we can. -if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then - case $MAX_FD in #( - max*) - # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC2039,SC3045 - MAX_FD=$( ulimit -H -n ) || - warn "Could not query maximum file descriptor limit" - esac - case $MAX_FD in #( - '' | soft) :;; #( - *) - # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC2039,SC3045 - ulimit -n "$MAX_FD" || - warn "Could not set maximum file descriptor limit to $MAX_FD" - esac -fi - -# Collect all arguments for the java command, stacking in reverse order: -# * args from the command line -# * the main class name -# * -classpath -# * -D...appname settings -# * --module-path (only if needed) -# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. - -# For Cygwin or MSYS, switch paths to Windows format before running java -if "$cygwin" || "$msys" ; then - APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) - CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) - - JAVACMD=$( cygpath --unix "$JAVACMD" ) - - # Now convert the arguments - kludge to limit ourselves to /bin/sh - for arg do - if - case $arg in #( - -*) false ;; # don't mess with options #( - /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath - [ -e "$t" ] ;; #( - *) false ;; - esac - then - arg=$( cygpath --path --ignore --mixed "$arg" ) - fi - # Roll the args list around exactly as many times as the number of - # args, so each arg winds up back in the position where it started, but - # possibly modified. - # - # NB: a `for` loop captures its iteration list before it begins, so - # changing the positional parameters here affects neither the number of - # iterations, nor the values presented in `arg`. - shift # remove old arg - set -- "$@" "$arg" # push replacement arg - done -fi - - -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' - -# Collect all arguments for the java command: -# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, -# and any embedded shellness will be escaped. -# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be -# treated as '${Hostname}' itself on the command line. - -set -- \ - "-Dorg.gradle.appname=$APP_BASE_NAME" \ - -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ - "$@" - -# Stop when "xargs" is not available. -if ! command -v xargs >/dev/null 2>&1 -then - die "xargs is not available" -fi - -# Use "xargs" to parse quoted args. -# -# With -n1 it outputs one arg per line, with the quotes and backslashes removed. -# -# In Bash we could simply go: -# -# readarray ARGS < <( xargs -n1 <<<"$var" ) && -# set -- "${ARGS[@]}" "$@" -# -# but POSIX shell has neither arrays nor command substitution, so instead we -# post-process each arg (as a line of input to sed) to backslash-escape any -# character that might be a shell metacharacter, then use eval to reverse -# that process (while maintaining the separation between arguments), and wrap -# the whole thing up as a single "set" statement. -# -# This will of course break if any of these variables contains a newline or -# an unmatched quote. -# - -eval "set -- $( - printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | - xargs -n1 | - sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | - tr '\n' ' ' - )" '"$@"' - -exec "$JAVACMD" "$@" diff --git a/test-server/java-v3-server/gradlew.bat b/test-server/java-v3-server/gradlew.bat deleted file mode 100644 index 7101f8e4..00000000 --- a/test-server/java-v3-server/gradlew.bat +++ /dev/null @@ -1,92 +0,0 @@ -@rem -@rem Copyright 2015 the original author or authors. -@rem -@rem Licensed under the Apache License, Version 2.0 (the "License"); -@rem you may not use this file except in compliance with the License. -@rem You may obtain a copy of the License at -@rem -@rem https://www.apache.org/licenses/LICENSE-2.0 -@rem -@rem Unless required by applicable law or agreed to in writing, software -@rem distributed under the License is distributed on an "AS IS" BASIS, -@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -@rem See the License for the specific language governing permissions and -@rem limitations under the License. -@rem - -@if "%DEBUG%"=="" @echo off -@rem ########################################################################## -@rem -@rem Gradle startup script for Windows -@rem -@rem ########################################################################## - -@rem Set local scope for the variables with windows NT shell -if "%OS%"=="Windows_NT" setlocal - -set DIRNAME=%~dp0 -if "%DIRNAME%"=="" set DIRNAME=. -@rem This is normally unused -set APP_BASE_NAME=%~n0 -set APP_HOME=%DIRNAME% - -@rem Resolve any "." and ".." in APP_HOME to make it shorter. -for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi - -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" - -@rem Find java.exe -if defined JAVA_HOME goto findJavaFromJavaHome - -set JAVA_EXE=java.exe -%JAVA_EXE% -version >NUL 2>&1 -if %ERRORLEVEL% equ 0 goto execute - -echo. 1>&2 -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 -echo. 1>&2 -echo Please set the JAVA_HOME variable in your environment to match the 1>&2 -echo location of your Java installation. 1>&2 - -goto fail - -:findJavaFromJavaHome -set JAVA_HOME=%JAVA_HOME:"=% -set JAVA_EXE=%JAVA_HOME%/bin/java.exe - -if exist "%JAVA_EXE%" goto execute - -echo. 1>&2 -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 -echo. 1>&2 -echo Please set the JAVA_HOME variable in your environment to match the 1>&2 -echo location of your Java installation. 1>&2 - -goto fail - -:execute -@rem Setup the command line - -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar - - -@rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* - -:end -@rem End local scope for the variables with windows NT shell -if %ERRORLEVEL% equ 0 goto mainEnd - -:fail -rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of -rem the _cmd.exe /c_ return code! -set EXIT_CODE=%ERRORLEVEL% -if %EXIT_CODE% equ 0 set EXIT_CODE=1 -if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% -exit /b %EXIT_CODE% - -:mainEnd -if "%OS%"=="Windows_NT" endlocal - -:omega diff --git a/test-server/java-v3-server/license.txt b/test-server/java-v3-server/license.txt deleted file mode 100644 index 2dd564b3..00000000 --- a/test-server/java-v3-server/license.txt +++ /dev/null @@ -1,4 +0,0 @@ -/* - * Example file license header. - * File header line two - */ \ No newline at end of file diff --git a/test-server/java-v3-server/settings.gradle.kts b/test-server/java-v3-server/settings.gradle.kts deleted file mode 100644 index e7c41714..00000000 --- a/test-server/java-v3-server/settings.gradle.kts +++ /dev/null @@ -1,19 +0,0 @@ -/** - * Basic usage of generated server stubs. - */ - -pluginManagement { - val smithyGradleVersion: String by settings - - plugins { - id("software.amazon.smithy.gradle.smithy-base").version(smithyGradleVersion) - } - - repositories { - mavenLocal() - mavenCentral() - gradlePluginPortal() - } -} - -rootProject.name = "S3ECJavaTestServer" diff --git a/test-server/java-v3-server/smithy-build.json b/test-server/java-v3-server/smithy-build.json deleted file mode 100644 index a0fcb8e5..00000000 --- a/test-server/java-v3-server/smithy-build.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "version": "2.0", - "plugins": { - "java-server-codegen": { - "service": "software.amazon.encryption.s3#S3ECTestServer", - "namespace": "software.amazon.encryption.s3", - "headerFile": "license.txt" - } - }, - "sources": ["../model"] -} diff --git a/test-server/java-v3-server/specification b/test-server/java-v3-server/specification deleted file mode 120000 index b173f708..00000000 --- a/test-server/java-v3-server/specification +++ /dev/null @@ -1 +0,0 @@ -../specification \ No newline at end of file diff --git a/test-server/java-v3-server/src/main/java/software/amazon/encryption/s3/CreateClientOperationImpl.java b/test-server/java-v3-server/src/main/java/software/amazon/encryption/s3/CreateClientOperationImpl.java deleted file mode 100644 index 6a3da066..00000000 --- a/test-server/java-v3-server/src/main/java/software/amazon/encryption/s3/CreateClientOperationImpl.java +++ /dev/null @@ -1,155 +0,0 @@ -package software.amazon.encryption.s3; - -import software.amazon.awssdk.core.client.config.ClientOverrideConfiguration; -import software.amazon.awssdk.core.retry.RetryPolicy; -import software.amazon.awssdk.core.retry.backoff.BackoffStrategy; -import software.amazon.awssdk.core.traits.Trait; -import software.amazon.awssdk.services.s3.S3Client; -import software.amazon.encryption.s3.S3EncryptionClient; -import software.amazon.encryption.s3.internal.InstructionFileConfig; -import software.amazon.encryption.s3.materials.AesKeyring; -import software.amazon.encryption.s3.materials.Keyring; -import software.amazon.encryption.s3.materials.KmsKeyring; -import software.amazon.encryption.s3.materials.PartialRsaKeyPair; -import software.amazon.encryption.s3.materials.RsaKeyring; -import software.amazon.smithy.java.core.schema.Schema; -import software.amazon.smithy.java.server.RequestContext; -import software.amazon.encryption.s3.model.CreateClientInput; -import software.amazon.encryption.s3.model.CreateClientOutput; -import software.amazon.encryption.s3.model.GenericServerError; -import software.amazon.encryption.s3.model.KeyMaterial; -import software.amazon.encryption.s3.service.CreateClientOperation; - -import javax.crypto.spec.SecretKeySpec; -import java.io.PrintWriter; -import java.io.StringWriter; -import java.security.KeyFactory; -import java.security.NoSuchAlgorithmException; -import java.security.PublicKey; -import java.security.interfaces.RSAPrivateCrtKey; -import java.security.spec.InvalidKeySpecException; -import java.security.spec.PKCS8EncodedKeySpec; -import java.security.spec.RSAPublicKeySpec; -import java.util.Arrays; -import java.util.Map; -import java.util.Optional; -import java.util.UUID; - -public class CreateClientOperationImpl implements CreateClientOperation { - private Map clientCache_; - private Map keyringCache_; - - public CreateClientOperationImpl(Map clientCache, Map keyringCache) { - clientCache_ = clientCache; - keyringCache_ = keyringCache; - } - - // Copied from S3EC. - private boolean onlyOneNonNull(Object... values) { - boolean haveOneNonNull = false; - for (Object o : values) { - if (o != null) { - if (haveOneNonNull) { - return false; - } - - haveOneNonNull = true; - } - } - - return haveOneNonNull; - } - - @Override - public CreateClientOutput createClient(CreateClientInput input, RequestContext context) { - try { - // Key Material / Keyring Creation - KeyMaterial key = input.getConfig().getKeyMaterial(); - if (!onlyOneNonNull(key.getAesKey(), key.getKmsKeyId(), key.getRsaKey())) { - throw new RuntimeException("KeyMaterial must be only one, non-null input!"); - } - Keyring keyring; - if (key.getAesKey() != null) { - byte[] keyBytes = new byte[key.getAesKey().remaining()]; - key.getAesKey().get(keyBytes); - keyring = AesKeyring.builder() - .wrappingKey(new SecretKeySpec(keyBytes, "AES")) - .enableLegacyWrappingAlgorithms(input.getConfig().isEnableLegacyWrappingAlgorithms()) - .build(); - } else if (key.getRsaKey() != null) { - try { - byte[] keyBytes = new byte[key.getRsaKey().remaining()]; - key.getRsaKey().get(keyBytes); - PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes); - KeyFactory keyFactory = KeyFactory.getInstance("RSA"); - RSAPrivateCrtKey privateKey = (RSAPrivateCrtKey) keyFactory.generatePrivate(keySpec); - RSAPublicKeySpec publicKeySpec = new RSAPublicKeySpec( - privateKey.getModulus(), - privateKey.getPublicExponent() - ); - - // Generate public key - PublicKey publicKey = keyFactory.generatePublic(publicKeySpec); - - keyring = RsaKeyring.builder() - .enableLegacyWrappingAlgorithms(input.getConfig().isEnableLegacyWrappingAlgorithms()) - .wrappingKeyPair(PartialRsaKeyPair.builder() - .publicKey(publicKey) - .privateKey(privateKey).build()) - .build(); - } catch (NoSuchAlgorithmException | InvalidKeySpecException nse) { - throw GenericServerError.builder() - .message(nse.getMessage()) - .build(); - } - } else if (key.getKmsKeyId() != null) { - keyring = KmsKeyring.builder() - .enableLegacyWrappingAlgorithms(input.getConfig().isEnableLegacyWrappingAlgorithms()) - .wrappingKeyId(key.getKmsKeyId()) - .build(); - } else { - throw new RuntimeException("No KeyMaterial found!"); - } - - // Configure S3 client with adaptive retry for throttling - RetryPolicy retryPolicy = RetryPolicy.builder() - .numRetries(5) - .throttlingBackoffStrategy(BackoffStrategy.defaultThrottlingStrategy()) - .build(); - - S3Client wrappedClient = S3Client.builder() - .overrideConfiguration(ClientOverrideConfiguration.builder() - .retryPolicy(retryPolicy) - .build()) - .build(); - - // Client Creation - boolean instFilePut = false; - if (input.getConfig().getInstructionFileConfig() != null) { - instFilePut = input.getConfig().getInstructionFileConfig().isEnableInstructionFilePutObject(); - } - S3Client s3Client = S3EncryptionClient.builder() - .wrappedClient(wrappedClient) - .instructionFileConfig(InstructionFileConfig.builder() - .instructionFileClient(S3Client.create()) - .enableInstructionFilePutObject(instFilePut) - .build()) - .keyring(keyring) - .build(); - UUID uuid = UUID.randomUUID(); - String uuidString = uuid.toString(); - clientCache_.put(uuidString, s3Client); - keyringCache_.put(uuidString, keyring); - return CreateClientOutput.builder() - .clientId(uuidString) - .build(); - } catch (Exception e) { - StringWriter sw = new StringWriter(); - e.printStackTrace(new PrintWriter(sw)); - String stackTrace = sw.toString(); - throw GenericServerError.builder() - .message(stackTrace) - .build(); - } - } -} diff --git a/test-server/java-v3-server/src/main/java/software/amazon/encryption/s3/GetObjectOperationImpl.java b/test-server/java-v3-server/src/main/java/software/amazon/encryption/s3/GetObjectOperationImpl.java deleted file mode 100644 index fbccd458..00000000 --- a/test-server/java-v3-server/src/main/java/software/amazon/encryption/s3/GetObjectOperationImpl.java +++ /dev/null @@ -1,78 +0,0 @@ -package software.amazon.encryption.s3; - -import software.amazon.awssdk.core.ResponseBytes; -import software.amazon.awssdk.services.s3.S3Client; -import software.amazon.awssdk.services.s3.model.GetObjectResponse; -import software.amazon.encryption.s3.S3EncryptionClientException; -import software.amazon.smithy.java.server.RequestContext; -import software.amazon.encryption.s3.model.GenericServerError; -import software.amazon.encryption.s3.model.GetObjectInput; -import software.amazon.encryption.s3.model.GetObjectOutput; -import software.amazon.encryption.s3.model.S3EncryptionClientError; -import software.amazon.encryption.s3.service.GetObjectOperation; - -import java.io.PrintWriter; -import java.io.StringWriter; -import java.nio.ByteBuffer; -import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; - -import static software.amazon.encryption.s3.S3EncryptionClient.withAdditionalConfiguration; -import static software.amazon.encryption.s3.MetadataUtils.metadataListToMap; -import static software.amazon.encryption.s3.MetadataUtils.metadataMapToList; - -public class GetObjectOperationImpl implements GetObjectOperation { - private Map clientCache_; - public GetObjectOperationImpl(Map clientCache) { - clientCache_ = clientCache; - } - @Override - public GetObjectOutput getObject(GetObjectInput input, RequestContext context) { - try { - S3Client s3Client = clientCache_.get(input.getClientID()); - Map ecMap = metadataListToMap(input.getMetadata()); - - try { - ResponseBytes resp = s3Client.getObjectAsBytes(builder -> { - builder.bucket(input.getBucket()) - .key(input.getKey()) - .overrideConfiguration(withAdditionalConfiguration(ecMap)); - - // Add range header if provided - if (input.getRange() != null && !input.getRange().isEmpty()) { - builder.range(input.getRange()); - } - }); - - List mdAsList = metadataMapToList(resp.response().metadata()); - // Can't use asBB else it gets mad bc cant access backing array - ByteBuffer bb = ByteBuffer.wrap(resp.asByteArray()); - GetObjectOutput output = GetObjectOutput.builder() - .body(bb) - .metadata(mdAsList) - .build(); - return output; - } catch (S3EncryptionClientException s3EncryptionClientException) { - // Modeled exceptions MUST be returned as such - StringWriter sw = new StringWriter(); - s3EncryptionClientException.printStackTrace(new PrintWriter(sw)); - String stackTrace = sw.toString(); - throw S3EncryptionClientError.builder() - .message(stackTrace) - .build(); - } - } catch (Exception e) { - // Don't wrap modeled errors - if (e instanceof S3EncryptionClientError) { - throw e; - } - StringWriter sw = new StringWriter(); - e.printStackTrace(new PrintWriter(sw)); - String stackTrace = sw.toString(); - throw GenericServerError.builder() - .message(stackTrace) - .build(); - } - } -} diff --git a/test-server/java-v3-server/src/main/java/software/amazon/encryption/s3/MetadataUtils.java b/test-server/java-v3-server/src/main/java/software/amazon/encryption/s3/MetadataUtils.java deleted file mode 100644 index 036289ec..00000000 --- a/test-server/java-v3-server/src/main/java/software/amazon/encryption/s3/MetadataUtils.java +++ /dev/null @@ -1,43 +0,0 @@ -package software.amazon.encryption.s3; - -import software.amazon.encryption.s3.model.GenericServerError; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -public class MetadataUtils { - - /** - * Annoyingly, Smithy doesn't provide an interface for map types - * in HTTP headers, so we have to do the serde ourselves - */ - public static List metadataMapToList(Map md) { - List mdAsList = new ArrayList<>(md.size()); - for (Map.Entry keyValue : md.entrySet()) { - mdAsList.add("[" + keyValue.getKey() + "]:[" + keyValue.getValue() + "]"); - } - return mdAsList; - } - - public static Map metadataListToMap(List mdList) { - Map md = new HashMap<>(); - for (String entry : mdList) { - // Split on "]:[" to separate key and value - String[] parts = entry.split("]:\\["); - if (parts.length == 2) { - // Remove remaining brackets from start and end - String key = parts[0].substring(1); - String value = parts[1].substring(0, parts[1].length() - 1); - md.put(key, value); - } else { - throw GenericServerError.builder() - .message("Malformed metadata list entry: " + entry) - .build(); - } - } - return md; - } - -} diff --git a/test-server/java-v3-server/src/main/java/software/amazon/encryption/s3/PutObjectOperationImpl.java b/test-server/java-v3-server/src/main/java/software/amazon/encryption/s3/PutObjectOperationImpl.java deleted file mode 100644 index 4c772673..00000000 --- a/test-server/java-v3-server/src/main/java/software/amazon/encryption/s3/PutObjectOperationImpl.java +++ /dev/null @@ -1,55 +0,0 @@ -package software.amazon.encryption.s3; - -import software.amazon.awssdk.core.sync.RequestBody; -import software.amazon.awssdk.services.s3.S3Client; -import software.amazon.awssdk.services.s3.model.PutObjectResponse; -import software.amazon.smithy.java.server.RequestContext; -import software.amazon.encryption.s3.model.GenericServerError; -import software.amazon.encryption.s3.model.PutObjectInput; -import software.amazon.encryption.s3.model.PutObjectOutput; -import software.amazon.encryption.s3.service.PutObjectOperation; - -import java.io.PrintWriter; -import java.io.StringWriter; -import java.util.ArrayList; -import java.util.Map; -import java.util.stream.Collectors; - -import static software.amazon.encryption.s3.S3EncryptionClient.withAdditionalConfiguration; -import static software.amazon.encryption.s3.MetadataUtils.metadataListToMap; - -public class PutObjectOperationImpl implements PutObjectOperation { - - private Map clientCache_; - - public PutObjectOperationImpl(Map clientCache) { - clientCache_ = clientCache; - } - - @Override - public PutObjectOutput putObject(PutObjectInput input, RequestContext context) { - try { - final Map metadata = metadataListToMap(input.getMetadata()); - S3Client s3Client = clientCache_.get(input.getClientID()); - s3Client.putObject(builder -> builder - .bucket(input.getBucket()) - .key(input.getKey()) - .overrideConfiguration(withAdditionalConfiguration(metadata)), - RequestBody.fromByteBuffer(input.getBody()) - ); - // The real S3 doesn't provide bucket/key/metadata, so Test doesn't need to either, but we do anyway - return PutObjectOutput.builder() - .bucket(input.getBucket()) - .key(input.getKey()) - .metadata(input.getMetadata()) - .build(); - } catch (Exception e) { - StringWriter sw = new StringWriter(); - e.printStackTrace(new PrintWriter(sw)); - String stackTrace = sw.toString(); - throw GenericServerError.builder() - .message(stackTrace) - .build(); - } - } -} diff --git a/test-server/java-v3-server/src/main/java/software/amazon/encryption/s3/ReEncryptOperationImpl.java b/test-server/java-v3-server/src/main/java/software/amazon/encryption/s3/ReEncryptOperationImpl.java deleted file mode 100644 index dd376429..00000000 --- a/test-server/java-v3-server/src/main/java/software/amazon/encryption/s3/ReEncryptOperationImpl.java +++ /dev/null @@ -1,185 +0,0 @@ -package software.amazon.encryption.s3; - -import software.amazon.awssdk.services.s3.S3Client; -import software.amazon.encryption.s3.internal.ReEncryptInstructionFileRequest; -import software.amazon.encryption.s3.internal.ReEncryptInstructionFileResponse; -import software.amazon.encryption.s3.materials.AesKeyring; -import software.amazon.encryption.s3.materials.MaterialsDescription; -import software.amazon.encryption.s3.materials.PartialRsaKeyPair; -import software.amazon.encryption.s3.materials.RawKeyring; -import software.amazon.encryption.s3.materials.RsaKeyring; -import software.amazon.encryption.s3.model.GenericServerError; -import software.amazon.encryption.s3.model.KeyMaterial; -import software.amazon.encryption.s3.model.ReEncryptInput; -import software.amazon.encryption.s3.model.ReEncryptOutput; -import software.amazon.encryption.s3.model.S3EncryptionClientError; -import software.amazon.encryption.s3.service.ReEncryptOperation; -import software.amazon.smithy.java.server.RequestContext; - -import javax.crypto.SecretKey; -import javax.crypto.spec.SecretKeySpec; -import java.io.PrintWriter; -import java.io.StringWriter; -import java.security.KeyFactory; -import java.security.PublicKey; -import java.security.interfaces.RSAPrivateCrtKey; -import java.security.spec.PKCS8EncodedKeySpec; -import java.security.spec.RSAPublicKeySpec; -import java.util.HashMap; -import java.util.Map; - -public class ReEncryptOperationImpl implements ReEncryptOperation { - private final Map clientCache_; - private final Map keyringCache_; - - public ReEncryptOperationImpl(Map clientCache, Map keyringCache) { - clientCache_ = clientCache; - keyringCache_ = keyringCache; - } - - @Override - public ReEncryptOutput reEncrypt(ReEncryptInput input, RequestContext context) { - try { - S3Client s3Client = clientCache_.get(input.getClientID()); - - // Ensure we have an S3EncryptionClient, not just a plain S3Client - if (!(s3Client instanceof S3EncryptionClient)) { - throw new IllegalStateException( - "Client " + input.getClientID() + " is not an S3EncryptionClient"); - } - - S3EncryptionClient s3EncryptionClient = (S3EncryptionClient) s3Client; - - // Create a new keyring from the provided newKeyMaterial - KeyMaterial newKeyMaterial = input.getNewKeyMaterial(); - if (newKeyMaterial == null) { - throw new IllegalStateException( - "newKeyMaterial is required for ReEncrypt operation"); - } - - RawKeyring newKeyring = createKeyringFromMaterial(newKeyMaterial); - - try { - // Build the ReEncryptInstructionFileRequest - ReEncryptInstructionFileRequest.Builder requestBuilder = - ReEncryptInstructionFileRequest.builder() - .bucket(input.getBucket()) - .key(input.getKey()) - .newKeyring(newKeyring); - - // Add optional instruction file suffix if provided - if (input.getInstructionFileSuffix() != null && !input.getInstructionFileSuffix().isEmpty()) { - requestBuilder.instructionFileSuffix(input.getInstructionFileSuffix()); - } - - // Add optional enforceRotation if provided - if (input.isEnforceRotation() != null) { - requestBuilder.enforceRotation(input.isEnforceRotation()); - } - - ReEncryptInstructionFileRequest reEncryptRequest = requestBuilder.build(); - - // Perform the re-encryption - ReEncryptInstructionFileResponse response = - s3EncryptionClient.reEncryptInstructionFile(reEncryptRequest); - - // Build and return the output - return ReEncryptOutput.builder() - .bucket(response.bucket()) - .key(response.key()) - .instructionFileSuffix(response.instructionFileSuffix()) - .enforceRotation(response.enforceRotation()) - .build(); - - } catch (S3EncryptionClientException s3EncryptionClientException) { - // Modeled exceptions MUST be returned as such - StringWriter sw = new StringWriter(); - s3EncryptionClientException.printStackTrace(new PrintWriter(sw)); - String stackTrace = sw.toString(); - throw S3EncryptionClientError.builder() - .message(stackTrace) - .build(); - } - } catch (Exception e) { - // Don't wrap modeled errors - if (e instanceof S3EncryptionClientError) { - throw e; - } - StringWriter sw = new StringWriter(); - e.printStackTrace(new PrintWriter(sw)); - String stackTrace = sw.toString(); - throw GenericServerError.builder() - .message(stackTrace) - .build(); - } - } - - /** - * Creates a RawKeyring from KeyMaterial. - * The KeyMaterial should have exactly one of: aesKey, rsaKey, or kmsKeyId set. - */ - private RawKeyring createKeyringFromMaterial(KeyMaterial keyMaterial) { - try { - // Get materials description from KeyMaterial if provided - MaterialsDescription materialsDescription = null; - if (keyMaterial.getMaterialsDescription() != null && !keyMaterial.getMaterialsDescription().isEmpty()) { - MaterialsDescription.Builder builder = MaterialsDescription.builder(); - for (Map.Entry entry : keyMaterial.getMaterialsDescription().entrySet()) { - builder.put(entry.getKey(), entry.getValue()); - } - materialsDescription = builder.build(); - } - - // Check for AES key - if (keyMaterial.getAesKey() != null) { - byte[] aesKeyBytes = new byte[keyMaterial.getAesKey().remaining()]; - keyMaterial.getAesKey().get(aesKeyBytes); - SecretKey secretKey = new SecretKeySpec(aesKeyBytes, "AES"); - - AesKeyring.Builder keyringBuilder = AesKeyring.builder() - .wrappingKey(secretKey); - - if (materialsDescription != null) { - keyringBuilder.materialsDescription(materialsDescription); - } - - return keyringBuilder.build(); - } - - // Check for RSA key - if (keyMaterial.getRsaKey() != null) { - byte[] rsaKeyBytes = new byte[keyMaterial.getRsaKey().remaining()]; - keyMaterial.getRsaKey().get(rsaKeyBytes); - PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(rsaKeyBytes); - KeyFactory keyFactory = KeyFactory.getInstance("RSA"); - RSAPrivateCrtKey privateKey = (RSAPrivateCrtKey) keyFactory.generatePrivate(keySpec); - - // Derive the public key from the private key - RSAPublicKeySpec publicKeySpec = new RSAPublicKeySpec( - privateKey.getModulus(), - privateKey.getPublicExponent() - ); - PublicKey publicKey = keyFactory.generatePublic(publicKeySpec); - - PartialRsaKeyPair keyPair = PartialRsaKeyPair.builder() - .privateKey(privateKey) - .publicKey(publicKey) - .build(); - - RsaKeyring.Builder keyringBuilder = RsaKeyring.builder() - .wrappingKeyPair(keyPair); - - if (materialsDescription != null) { - keyringBuilder.materialsDescription(materialsDescription); - } - - return keyringBuilder.build(); - } - - throw new IllegalStateException( - "KeyMaterial must have either aesKey or rsaKey set"); - } catch (Exception e) { - throw new IllegalStateException("Failed to create keyring from KeyMaterial: " + e.getMessage(), e); - } - } -} diff --git a/test-server/java-v3-server/src/main/java/software/amazon/encryption/s3/S3ECJavaTestServer.java b/test-server/java-v3-server/src/main/java/software/amazon/encryption/s3/S3ECJavaTestServer.java deleted file mode 100644 index be53f20c..00000000 --- a/test-server/java-v3-server/src/main/java/software/amazon/encryption/s3/S3ECJavaTestServer.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -package software.amazon.encryption.s3; - -import software.amazon.awssdk.services.s3.S3Client; - -import java.net.URI; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ExecutionException; -import software.amazon.smithy.java.server.Server; -import software.amazon.encryption.s3.service.S3ECTestServer; - -public class S3ECJavaTestServer implements Runnable { - static final URI endpoint = URI.create("http://localhost:8080"); - - public static void main(String[] args) { - new S3ECJavaTestServer().run(); - } - - @Override - public void run() { - // All the S3EC instances live here. - // Obviously this can get messy in a real service. - // Assume that the tests behave and don't induce weird race conditions. - Map clientCache = new ConcurrentHashMap<>(); - Map keyringCache = new ConcurrentHashMap<>(); - - Server server = Server.builder() - .endpoints(endpoint) - .addService( - S3ECTestServer.builder() - .addCreateClientOperation(new CreateClientOperationImpl(clientCache, keyringCache)) - .addGetObjectOperation(new GetObjectOperationImpl(clientCache)) - .addPutObjectOperation(new PutObjectOperationImpl(clientCache)) - .addReEncryptOperation(new ReEncryptOperationImpl(clientCache, keyringCache)) - .build()) - .build(); - System.out.println("Starting server..."); - server.start(); - try { - Thread.currentThread().join(); - } catch (InterruptedException e) { - System.out.println("Stopping server..."); - try { - server.shutdown().get(); - } catch (InterruptedException | ExecutionException ex) { - throw new RuntimeException(ex); - } - } - } -} diff --git a/test-server/php-v2-server/.duvet/.gitignore b/test-server/php-v2-server/.duvet/.gitignore deleted file mode 100644 index 93956e36..00000000 --- a/test-server/php-v2-server/.duvet/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -reports/ -requirements/ -specification/ \ No newline at end of file diff --git a/test-server/php-v2-server/.duvet/config.toml b/test-server/php-v2-server/.duvet/config.toml deleted file mode 100644 index 64b00927..00000000 --- a/test-server/php-v2-server/.duvet/config.toml +++ /dev/null @@ -1,24 +0,0 @@ -'$schema' = "https://awslabs.github.io/duvet/config/v0.4.0.json" - -[[source]] -pattern = "local-php-sdk/src/S3/**/*.php" - -[[source]] -pattern = "local-php-sdk/src/Crypto/**/*.php" - -# Include required specifications here -[[specification]] -source = "../specification/s3-encryption/data-format/content-metadata.md" -[[specification]] -source = "../specification/s3-encryption/data-format/metadata-strategy.md" -[[specification]] -source = "../specification/s3-encryption/encryption.md" -[[specification]] -source = "../specification/s3-encryption/key-derivation.md" - -[report.html] -enabled = true - -# Enable snapshots to prevent requirement coverage regressions -[report.snapshot] -enabled = false diff --git a/test-server/php-v2-server/.gitignore b/test-server/php-v2-server/.gitignore deleted file mode 100644 index 07108589..00000000 --- a/test-server/php-v2-server/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -vendor/* -cookies.txt -server.pid -composer.lock \ No newline at end of file diff --git a/test-server/php-v2-server/Makefile b/test-server/php-v2-server/Makefile deleted file mode 100644 index 719ea238..00000000 --- a/test-server/php-v2-server/Makefile +++ /dev/null @@ -1,39 +0,0 @@ -# Makefile for S3 Encryption Client Testing - -.PHONY: build-server start-server stop-server wait-for-server - -PID_FILE := server.pid -PORT := 8087 - -build-server: - @echo "Building PHP V2 server..." - composer install - -start-server: - @echo "Starting PHP V2 server..." - AWS_ACCESS_KEY_ID="$$AWS_ACCESS_KEY_ID" \ - AWS_SECRET_ACCESS_KEY="$$AWS_SECRET_ACCESS_KEY" \ - AWS_SESSION_TOKEN="$$AWS_SESSION_TOKEN" \ - AWS_REGION="us-west-2" \ - composer run start --timeout=0 > server.log 2>&1 & echo $$! > $(PID_FILE) - @echo "PHP V2 server starting..." - -stop-server: - @echo "Stopping server on port $(PORT)..." - @lsof -ti:$(PORT) | xargs kill -9 2>/dev/null || true - @if [ -f $(PID_FILE) ]; then \ - pkill -P $$(cat $(PID_FILE)) 2>/dev/null || true; \ - kill -9 $$(cat $(PID_FILE)) 2>/dev/null || true; \ - rm -f $(PID_FILE); \ - fi - @rm -f server.log - @echo "Server stopped" - -wait-for-server: - $(MAKE) -C .. wait-for-port PORT=$(PORT) - -duvet: - duvet report - -view-report-mac: - open .duvet/reports/report.html diff --git a/test-server/php-v2-server/README.md b/test-server/php-v2-server/README.md deleted file mode 100644 index c4ba49fe..00000000 --- a/test-server/php-v2-server/README.md +++ /dev/null @@ -1,69 +0,0 @@ -# S3EC PHP v2 Test Server - -This is the PHP V2 implementation of the S3ECTestServer framework. It provides a server implementation for testing S3 Encryption Client functionality. - -## Overview - -The S3ECPhpV2TestServer implements the S3ECTestServer service defined in the shared Smithy model. It provides endpoints for: - -- Creating S3 Encryption Clients with session-based caching -- Putting objects with encryption -- Getting and decrypting objects - -## Starting the Server - -### Method 1: Using Composer (Recommended) -```bash -composer run start -``` - -The server will start on port `8087`. - -## Available Endpoints - -### Server Status -- **GET /** - Returns server status and available endpoints - -### Client Management -- **POST /client** - Creates an S3EncryptionClient and caches it with session persistence -- **GET /cache** - Shows current session state and cached clients (for debugging) - -### Object Operations -- **GET /object/{bucket}/{key}** - Handle GET requests using the S3EncryptionClient -- **PUT /object/{bucket}/{key}** - Handle PUT requests using the S3EncryptionClient - -## Testing with curl - -### Important: Session Cookie Management - -To properly test the server and maintain session persistence, you **must** use cookies with curl: - -#### First Request (creates session cookie): -```bash -curl -X POST http://localhost:8087/client \ - -H "Content-Type: application/json" \ - -c cookies.txt \ - -v -``` - -#### Subsequent Requests (reuses session cookie): -```bash -curl -X POST http://localhost:8087/client \ - -H "Content-Type: application/json" \ - -b cookies.txt \ - -c cookies.txt \ - -v -``` - -#### Check Cache Status: -```bash -curl http://localhost:8087/cache \ - -b cookies.txt -``` - -#### Helpful Notes -- **Session Storage**: Client configurations are stored in `$_SESSION['s3ecCache']` -- **Object Recreation**: AWS SDK objects are recreated from stored configuration (they cannot be serialized) -AWS SDK obbjects cannot be serialized due to internal resources and closures. -- **Helper Function**: `getCachedClient($clientId)` retrieves and recreates clients from cache -- **Debugging**: Enhanced logging and `/cache` endpoint for troubleshooting diff --git a/test-server/php-v2-server/composer.json b/test-server/php-v2-server/composer.json deleted file mode 100644 index d5177951..00000000 --- a/test-server/php-v2-server/composer.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "name": "aws/s3ec-php-v2-test-server", - "description": "PHP v2 implementation of the S3EC Test Server framework", - "type": "project", - "license": "Apache-2.0", - "repositories": [ - { - "type": "path", - "url": "./local-php-sdk", - "options": { - "symlink": true - } - } - ], - "require": { - "php": ">=7.4", - "aws/aws-sdk-php": "@dev", - "ramsey/uuid": "^4.9" - }, - "autoload": { - "psr-4": { - "S3EC\\PhpV2Server\\": "src/" - } - }, - "scripts": { - "start": [ - "php -S 0.0.0.0:8087 src/index.php" - ] - }, - "config": { - "optimize-autoloader": true, - "platform": { - "php": "8.1" - } - } -} \ No newline at end of file diff --git a/test-server/php-v2-server/local-php-sdk b/test-server/php-v2-server/local-php-sdk deleted file mode 160000 index f53d8fc6..00000000 --- a/test-server/php-v2-server/local-php-sdk +++ /dev/null @@ -1 +0,0 @@ -Subproject commit f53d8fc6cdbc1e64e7d14e72d1e315d05003b2b4 diff --git a/test-server/php-v2-server/src/client.php b/test-server/php-v2-server/src/client.php deleted file mode 100644 index 9c39d540..00000000 --- a/test-server/php-v2-server/src/client.php +++ /dev/null @@ -1,74 +0,0 @@ -toString(); - $kmsKeyId = $keyMaterial["kmsKeyId"] ?? null; - $instFileConfig = $configData['instructionFileConfig'] ?? null; - $instFilePut = false; - if ($instFileConfig != null) { - $instFilePut = $instFileConfig['enableInstructionFilePutObject'] ?? false; - } - - if ($configData == []) { - return GenericServerError("Invalid config in request body", 400); - } - if (($keyMaterial || $kmsKeyId) === null) { - return GenericServerError("Invalid keyMaterial in config", 400); - } - - // Store client configuration instead of objects (AWS objects can't be serialized) - $_SESSION['s3ecCache'][$clientId] = [ - 's3Config' => [ - 'region' => 'us-west-2', - 'version' => 'latest', - 'http' => [ - 'debug' => false, - 'verify' => true, - 'curl' => [ - CURLOPT_VERBOSE => false, - CURLOPT_NOPROGRESS => true - ] - ] - ], - 'kmsConfig' => [ - 'region' => 'us-west-2', - 'version' => 'latest', - 'http' => [ - 'debug' => false, - 'verify' => true, - 'curl' => [ - CURLOPT_VERBOSE => false, - CURLOPT_NOPROGRESS => true - ] - ] - ], - 'kmsKeyId' => $kmsKeyId, - 'legacy' => $legacyAlgorithms, - 'instFilePut' => $instFilePut, - 'created' => time() - ]; - - // Auto-update cookies.txt with current session ID so tests can access cached clients - writeSessionIdToCookiesFile(session_id()); - - header("Content-Type: application/json"); - return json_encode([ - 'clientId' => $clientId, - ]); -} diff --git a/test-server/php-v2-server/src/errors.php b/test-server/php-v2-server/src/errors.php deleted file mode 100644 index 2b59861d..00000000 --- a/test-server/php-v2-server/src/errors.php +++ /dev/null @@ -1,42 +0,0 @@ - 'GenericServerError', - 'message' => $message - ]; - - return json_encode($errorResponse); -} - -/** - * Used for modeled errors, e.g. errors thrown by the S3EC - * Tests SHOULD expect this error in negative tests. - * - * @param string $message The error message to include in the response - * @return string JSON-encoded error response - */ -function S3EncryptionClientError($message) -{ - http_response_code(500); - header('Content-Type: application/json'); - - $errorResponse = [ - "__type" => "software.amazon.encryption.s3#S3EncryptionClientError", - 'message' => $message - ]; - - return json_encode($errorResponse); -} diff --git a/test-server/php-v2-server/src/get_object.php b/test-server/php-v2-server/src/get_object.php deleted file mode 100644 index 61bacb5b..00000000 --- a/test-server/php-v2-server/src/get_object.php +++ /dev/null @@ -1,85 +0,0 @@ -getObject([ - '@SecurityProfile' => $legacy, - '@MaterialsProvider' => $materialProvider, - '@KmsEncryptionContext' => $encryptionContext, - 'Bucket' => $bucket, - 'Key' => $key, - ]); - - // Capture and discard any unwanted output from AWS SDK - $unwantedOutput = ob_get_clean(); - if (!empty($unwantedOutput)) { - error_log("AWS SDK produced unexpected output: " . strlen($unwantedOutput) . " bytes"); - } - - $body = $result['Body']->getContents(); - $formattedMetadata = formatMetadataForResponse($result["Metadata"]); - - // Now set headers safely - header("Content-Metadata: " . $formattedMetadata); - header("Content-Type: application/octet-stream"); - header("Content-Length: " . strlen($body)); - return $body; - } catch (InvalidArgumentException $e) { - // Clean up output buffer if still active - if (ob_get_level()) { - ob_end_clean(); - } - return GenericServerError("Invalid argument: " . $e->getMessage(), 400); - } catch (Exception $e) { - // Clean up output buffer if still active - if (ob_get_level()) { - ob_end_clean(); - } - if (strpos($e->getMessage(), "@SecurityProfile=V2") !== false) { - return S3EncryptionClientError($e->getMessage() . " " . "Enable legacy wrapping algorithms to use legacy key wrapping algorithm: kms"); - } else { - return GenericServerError("Server argument: " . $e->getMessage(), 500); - } - } -} diff --git a/test-server/php-v2-server/src/index.php b/test-server/php-v2-server/src/index.php deleted file mode 100644 index 167834e0..00000000 --- a/test-server/php-v2-server/src/index.php +++ /dev/null @@ -1,295 +0,0 @@ -= 7 && $parts[5] === 'PHPSESSID') { - error_log("Found session ID in cookies.txt: " . $parts[6]); - return $parts[6]; // Return the session ID value - } - } - - error_log("No PHPSESSID found in cookies.txt file"); - return null; -} - -// Function to write session ID to cookies.txt file -function writeSessionIdToCookiesFile($sessionId) -{ - $cookiesFile = __DIR__ . '/../cookies.txt'; - - // Create Netscape cookie format entry - $cookieLine = "localhost\tFALSE\t/\tFALSE\t0\tPHPSESSID\t$sessionId"; - - // Write header and cookie entry - $content = "# Netscape HTTP Cookie File\n"; - $content .= "# https://curl.se/docs/http-cookies.html\n"; - $content .= "# This file was generated by libcurl! Edit at your own risk.\n\n"; - $content .= $cookieLine . "\n"; - - $result = file_put_contents($cookiesFile, $content); - - if ($result === false) { - error_log("Failed to write session ID to cookies.txt file: $cookiesFile"); - return false; - } - - error_log("Successfully wrote session ID to cookies.txt: $sessionId"); - return true; -} - -set_time_limit(600); -// Start session to persist cache across requests -// First try to use session ID from cookies.txt if available -$sessionId = getSessionIdFromCookiesFile(); -if ($sessionId) { - session_id($sessionId); -} -session_start(); - -// Initialize session cache if it doesn't exist -if (!isset($_SESSION['s3ecCache'])) { - $_SESSION['s3ecCache'] = []; -} - -// Simple router class -class SimpleRouter -{ - private $routes = []; - - public function addRoute($method, $path, $handler) - { - $this->routes[] = [ - 'method' => strtoupper($method), - 'path' => $path, - 'handler' => $handler - ]; - } - - public function handleRequest() - { - $method = $_SERVER['REQUEST_METHOD']; - $path = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH); - - foreach ($this->routes as $route) { - if ($route['method'] === $method) { - $params = $this->matchPathWithParams($route['path'], $path); - if ($params !== false) { - return call_user_func($route['handler'], $params); - } - } - } - - // Default 404 response - http_response_code(404); - return json_encode(['error' => 'Not Found']); - } - - private function matchPathWithParams($routePath, $requestPath) - { - // Handle exact matches first (for routes without parameters) - if ($routePath === $requestPath) { - return []; - } - - // Convert route path like '/object/{bucket}/{key}' to regex - $pattern = preg_replace('/\{([^}]+)\}/', '([^/]+)', $routePath); - $pattern = '/^' . str_replace('/', '\/', $pattern) . '$/'; - - if (preg_match($pattern, $requestPath, $matches)) { - array_shift($matches); // Remove full match - - // Extract parameter names - preg_match_all('/\{([^}]+)\}/', $routePath, $paramNames); - $params = []; - - foreach ($paramNames[1] as $index => $paramName) { - $params[$paramName] = $matches[$index] ?? null; - } - - return $params; - } - - return false; - } -} - -// Helper function to get cached client by ID -function getCachedClient($clientId) -{ - if (!isset($_SESSION['s3ecCache'][$clientId])) { - return null; - } - - $config = $_SESSION['s3ecCache'][$clientId]; - - // Recreate the AWS clients from stored configuration - $s3Client = new S3Client($config['s3Config']); - $encryptionClient = new S3EncryptionClientV2($s3Client); - - $kmsClient = new KmsClient($config['kmsConfig']); - $materialsProvider = new KmsMaterialsProviderV2($kmsClient, $config['kmsKeyId']); - - return [ - 's3Client' => $s3Client, - 'encryptionClient' => $encryptionClient, - 'materialsProvider' => $materialsProvider, - 'config' => $config - ]; -} - -function createDefaultClientTuple(): array -{ - $s3Client = new S3Client([ - 'region' => 'us-west-2', - 'version' => 'latest', - 'http' => [ - 'debug' => false, - 'verify' => true, - 'curl' => [ - CURLOPT_VERBOSE => false, - CURLOPT_NOPROGRESS => true - ] - ] - ]); - $encryptionClient = new S3EncryptionClientV2($s3Client); - - $kmsClient = new KmsClient([ - 'region' => 'us-west-2', - 'version' => 'latest', - 'http' => [ - 'debug' => false, - 'verify' => true, - 'curl' => [ - CURLOPT_VERBOSE => false, - CURLOPT_NOPROGRESS => true - ] - ] - ]); - $materialsProvider = new KmsMaterialsProviderV2($kmsClient, 'arn:aws:kms:us-west-2:370957321024:alias/S3EC-Test-Server-Github-KMS-Key'); - - return [ - 'encryptionClient' => $encryptionClient, - 'materialsProvider' => $materialsProvider - ]; -} - -function metadataStringToMap($metadata): array -{ - $md = []; - - if (empty($metadata)) { - return $md; - } - - $mdList = explode(',', $metadata); - - foreach ($mdList as $entry) { - $parts = explode(']:[', $entry); - - if (count($parts) === 2) { - $key = substr($parts[0], 1); - $value = substr($parts[1], 0, -1); - $md[$key] = $value; - } else { - throw new InvalidArgumentException("Malformed metadata list entry: " . $entry); - } - } - - return $md; -} -function formatMetadataForResponse($metadata) -{ - $metadataList = []; - // Handle different metadata input types - if (is_array($metadata)) { - // If it's an associative array (like Python dict) - foreach ($metadata as $key => $value) { - $metadataList[] = $key . '=' . $value; - } - } elseif (is_string($metadata) && !empty($metadata)) { - // If it's already a string, assume it's in the correct format - return $metadata; - } - - // Convert array to comma-separated string - return implode(',', $metadataList); -} - -// Initialize router -$router = new SimpleRouter(); - -// Add basic routes -$router->addRoute('GET', '/', function () { - return json_encode([ - 'service' => 'S3EC PHP v2 Test Server', - 'status' => 'running', - 'port' => 8087, - 'endpoints' => [ - 'GET /' => 'Server status', - 'POST /client' => 'Create an S3EncryptionClient and cache it.', - 'GET /object/{bucket}/{key}' => 'Handle GET requests to /object/{bucket}/{key} by using the S3EncryptionClient to make a GetObject request to S3.', - 'PUT /object/{bucket}/{key}' => 'Handle PUT requests to /object/{bucket}/{key} by using the S3EncryptionClient to make a PutObject request to S3.', - ] - ]); -}); - -$router->addRoute('GET', '/cache', function () { - return json_encode([ - 'sessionId' => session_id(), - 'sessionStatus' => session_status(), - 'totalCachedClients' => count($_SESSION['s3ecCache'] ?? []), - 'allClientIds' => array_keys($_SESSION['s3ecCache'] ?? []), - 'cacheDetails' => $_SESSION['s3ecCache'] ?? [] - ]); -}); - -$router->addRoute('GET', '/object/{bucket}/{key}', function ($params) { - return handleGetObject($params); -}); - -$router->addRoute('PUT', '/object/{bucket}/{key}', function ($params) { - return handlePutObject($params); -}); - -$router->addRoute('POST', '/client', function () { - return handleCreateClient(); -}); - -// Handle the request and output response -$result = $router->handleRequest(); -if ($result !== false) { - echo $result; -} diff --git a/test-server/php-v2-server/src/put_object.php b/test-server/php-v2-server/src/put_object.php deleted file mode 100644 index c6c592fe..00000000 --- a/test-server/php-v2-server/src/put_object.php +++ /dev/null @@ -1,79 +0,0 @@ - 'gcm', - 'KeySize' => 256, - ]; - $legacyConfig = $s3ecClientTuple["legacy"] ?? false; - $legacy = null; - if ($legacyConfig === false) { - $legacy = "V2"; - } else { - $legacy = "V2_AND_LEGACY"; - } - $strategy = $s3ecClientTuple["config"]["instFilePut"] ? - new InstructionFileMetadataStrategy($s3Client) : - new HeadersMetadataStrategy(); - try { - $result = $s3ec->putObject([ - '@SecurityProfile' => $legacy, - '@MaterialsProvider' => $materialProvider, - '@KmsEncryptionContext' => $encryptionContext, - '@MetadataStrategy' => $strategy, - '@CipherOptions' => $cipherOptions, - 'Bucket' => $bucket, - 'Key' => $key, - 'Body' => $rawBody, - ]); - - header("Content-Type: application/json"); - return json_encode([ - "bucket" => $bucket, - "key" => $key, - // php for some reason blows java's heap if we pass the metadata - // "metadata" => $encryptionContext - ]); - - } catch (InvalidArgumentException $e) { - return S3EncryptionClientError("Invalid arguement: " . $e->getMessage()); - } catch (Exception $e) { - return GenericServerError("Server error: " . $e->getMessage()); - } -} From bc446111fab58a6077812eb4f8e02a047bba3945 Mon Sep 17 00:00:00 2001 From: Kess Plasmeier Date: Fri, 30 Jan 2026 13:35:53 -0800 Subject: [PATCH 23/38] remove NET V2 --- .github/workflows/test.yml | 2 - .gitmodules | 8 -- .../amazon/encryption/s3/TestUtils.java | 9 -- .../net-v2-v3-server/.duvet/.gitignore | 3 - .../net-v2-v3-server/.duvet/config.toml | 21 ---- test-server/net-v2-v3-server/.gitignore | 44 -------- .../Controllers/ClientController.cs | 106 ------------------ .../Controllers/ObjectController.cs | 105 ----------------- test-server/net-v2-v3-server/Makefile | 68 ----------- .../net-v2-v3-server/Models/ClientRequest.cs | 34 ------ .../net-v2-v3-server/Models/ClientResponse.cs | 8 -- .../net-v2-v3-server/Models/ErrorModels.cs | 17 --- .../net-v2-v3-server/NetV2V3Server.csproj | 39 ------- test-server/net-v2-v3-server/Program.cs | 21 ---- test-server/net-v2-v3-server/README.md | 72 ------------ .../Services/ClientCacheService.cs | 28 ----- test-server/net-v2-v3-server/s3ec-net-v2 | 1 - test-server/net-v2-v3-server/s3ec-net-v3 | 1 - .../net-v3-transition-server/README.md | 4 +- test-server/net-v4-server/README.md | 6 +- 20 files changed, 5 insertions(+), 592 deletions(-) delete mode 100644 test-server/net-v2-v3-server/.duvet/.gitignore delete mode 100644 test-server/net-v2-v3-server/.duvet/config.toml delete mode 100644 test-server/net-v2-v3-server/.gitignore delete mode 100644 test-server/net-v2-v3-server/Controllers/ClientController.cs delete mode 100644 test-server/net-v2-v3-server/Controllers/ObjectController.cs delete mode 100644 test-server/net-v2-v3-server/Makefile delete mode 100644 test-server/net-v2-v3-server/Models/ClientRequest.cs delete mode 100644 test-server/net-v2-v3-server/Models/ClientResponse.cs delete mode 100644 test-server/net-v2-v3-server/Models/ErrorModels.cs delete mode 100644 test-server/net-v2-v3-server/NetV2V3Server.csproj delete mode 100644 test-server/net-v2-v3-server/Program.cs delete mode 100644 test-server/net-v2-v3-server/README.md delete mode 100644 test-server/net-v2-v3-server/Services/ClientCacheService.cs delete mode 160000 test-server/net-v2-v3-server/s3ec-net-v2 delete mode 160000 test-server/net-v2-v3-server/s3ec-net-v3 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index e5b5b8a2..5ea728af 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -171,8 +171,6 @@ jobs: name: server-logs path: | test-server/*/server.log - test-server/*/net-v2-server.log - test-server/*/net-v3-server.log - name: Stop the servers run: cd test-server && make stop-servers diff --git a/.gitmodules b/.gitmodules index 6f0577f6..df9a207f 100644 --- a/.gitmodules +++ b/.gitmodules @@ -25,14 +25,6 @@ path = test-server/specification url = git@github.com:awslabs/private-aws-encryption-sdk-specification-staging.git branch = fire-egg-staging -[submodule "test-server/net-v2-v3-server/s3ec-net-v2"] - path = test-server/net-v2-v3-server/s3ec-net-v2 - url = https://github.com/aws/amazon-s3-encryption-client-dotnet.git - branch = v3sdk-development -[submodule "test-server/net-v2-v3-server/s3ec-net-v3"] - path = test-server/net-v2-v3-server/s3ec-net-v3 - url = https://github.com/aws/amazon-s3-encryption-client-dotnet.git - branch = v4sdk-development [submodule "test-server/net-v4-server/s3ec-net-v4-improved"] path = test-server/net-v4-server/s3ec-net-v4-improved url = https://github.com/aws/amazon-s3-encryption-client-dotnet.git diff --git a/test-server/java-tests/src/it/java/software/amazon/encryption/s3/TestUtils.java b/test-server/java-tests/src/it/java/software/amazon/encryption/s3/TestUtils.java index 4084c2a1..a9334eed 100644 --- a/test-server/java-tests/src/it/java/software/amazon/encryption/s3/TestUtils.java +++ b/test-server/java-tests/src/it/java/software/amazon/encryption/s3/TestUtils.java @@ -68,8 +68,6 @@ public class TestUtils { public static final String GO_V3_TRANSITION = "Go-V3-Transition"; public static final String GO_V4 = "Go-V4"; -// public static final String NET_V2_CURRENT = "NET-V2-Current"; -// public static final String NET_V3_CURRENT = "NET-V3-Current"; public static final String NET_V2_TRANSITION = "NET-V2-Transition"; public static final String NET_V3_TRANSITION = "NET-V3-Transition"; public static final String NET_V4 = "NET-V4"; @@ -191,22 +189,15 @@ public class TestUtils { static { final Map servers = new LinkedHashMap<>(); -// servers.put(JAVA_V3_CURRENT, new LanguageServerTarget(JAVA_V3_CURRENT, "8080")); servers.put(PYTHON_V3, new LanguageServerTarget(PYTHON_V3, "8081")); -// servers.put(GO_V3_CURRENT, new LanguageServerTarget(GO_V3_CURRENT, "8082")); -// servers.put(NET_V2_CURRENT, new LanguageServerTarget(NET_V2_CURRENT, "8083")); -// servers.put(NET_V3_CURRENT, new LanguageServerTarget(NET_V3_CURRENT, "8084")); servers.put(CPP_V2_TRANSITION, new LanguageServerTarget(CPP_V2_TRANSITION, "8097")); servers.put(CPP_V3, new LanguageServerTarget(CPP_V3, "8091")); - // servers.put(RUBY_V2_CURRENT, new LanguageServerTarget(RUBY_V2_CURRENT, "8086")); -// servers.put(PHP_V2_CURRENT, new LanguageServerTarget(PHP_V2_CURRENT, "8087")); servers.put(GO_V4, new LanguageServerTarget(GO_V4, "8089")); servers.put(NET_V4, new LanguageServerTarget(NET_V4, "8090")); servers.put(RUBY_V3, new LanguageServerTarget(RUBY_V3, "8092")); servers.put(PHP_V3, new LanguageServerTarget(PHP_V3, "8093")); servers.put(JAVA_V3_TRANSITION, new LanguageServerTarget(JAVA_V3_TRANSITION, "8094")); servers.put(GO_V3_TRANSITION, new LanguageServerTarget(GO_V3_TRANSITION, "8095")); - // servers.put(NET_V2_TRANSITION, new LanguageServerTarget(NET_V2_TRANSITION, "8096")); servers.put(RUBY_V2_TRANSITION, new LanguageServerTarget(RUBY_V2_TRANSITION, "8098")); servers.put(PHP_V2_TRANSITION, new LanguageServerTarget(PHP_V2_TRANSITION, "8099")); servers.put(JAVA_V4, new LanguageServerTarget(JAVA_V4, "8088")); diff --git a/test-server/net-v2-v3-server/.duvet/.gitignore b/test-server/net-v2-v3-server/.duvet/.gitignore deleted file mode 100644 index 93956e36..00000000 --- a/test-server/net-v2-v3-server/.duvet/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -reports/ -requirements/ -specification/ \ No newline at end of file diff --git a/test-server/net-v2-v3-server/.duvet/config.toml b/test-server/net-v2-v3-server/.duvet/config.toml deleted file mode 100644 index 04d2e812..00000000 --- a/test-server/net-v2-v3-server/.duvet/config.toml +++ /dev/null @@ -1,21 +0,0 @@ -'$schema' = "https://awslabs.github.io/duvet/config/v0.4.0.json" - -[[source]] -pattern = "**/*.cs" - -# Include required specifications here -[[specification]] -source = "../specification/s3-encryption/data-format/content-metadata.md" -[[specification]] -source = "../specification/s3-encryption/data-format/metadata-strategy.md" -[[specification]] -source = "../specification/s3-encryption/encryption.md" -[[specification]] -source = "../specification/s3-encryption/key-derivation.md" - -[report.html] -enabled = true - -# Enable snapshots to prevent requirement coverage regressions -[report.snapshot] -enabled = false diff --git a/test-server/net-v2-v3-server/.gitignore b/test-server/net-v2-v3-server/.gitignore deleted file mode 100644 index 4c20cbc8..00000000 --- a/test-server/net-v2-v3-server/.gitignore +++ /dev/null @@ -1,44 +0,0 @@ -# Build results -[Dd]ebug/ -[Dd]ebugPublic/ -[Rr]elease/ -[Rr]eleases/ -x64/ -x86/ -[Ww][Ii][Nn]32/ -[Aa][Rr][Mm]/ -[Aa][Rr][Mm]64/ -bld/ -[Bb]in/ -[Oo]bj/ -[Ll]og/ -[Ll]ogs/ - -# Visual Studio 2015/2017 cache/options directory -.vs/ - -# User-specific files -*.rsuser -*.suo -*.user -*.userosscache -*.sln.docstates - -# NuGet Packages -*.nupkg -*.snupkg -packages/ - -# JetBrains Rider -.idea/ -*.sln.iml - -# VS Code -.vscode/ - -# macOS -.DS_Store - -# Temporary files -*.tmp -*.temp diff --git a/test-server/net-v2-v3-server/Controllers/ClientController.cs b/test-server/net-v2-v3-server/Controllers/ClientController.cs deleted file mode 100644 index 437233a8..00000000 --- a/test-server/net-v2-v3-server/Controllers/ClientController.cs +++ /dev/null @@ -1,106 +0,0 @@ -using System.Net; -using System.Security.Cryptography; -using System.Text.Json; -using Amazon.Extensions.S3.Encryption; -using Amazon.Extensions.S3.Encryption.Primitives; -using Microsoft.AspNetCore.Mvc; -using NetV2V3Server.Models; -using NetV2V3Server.Services; - -namespace NetV2V3Server.Controllers; - -[ApiController] -[Route("[controller]")] -public class ClientController(IClientCacheService clientCacheService, ILogger logger) : ControllerBase -{ - [HttpPost] - public IActionResult CreateClient([FromBody] ClientRequest request) - { - // Return 501 for not implemented features by the server - if (request.Config.EnableDelayedAuthenticationMode) - return StatusCode(501, new GenericServerError { Message = "[NET-current] EnableDelayedAuthenticationMode not supported" }); - if (request.Config.SetBufferSize.HasValue) - return StatusCode(501, new GenericServerError { Message = "[NET-current] SetBufferSize not supported" }); - - try - { - EncryptionMaterialsV2 encryptionMaterial; - if (request.Config.KeyMaterial.KmsKeyId != null) - { - // The POST request does not contain encryption context. - // However, encryption context is a required field when using KMS. - // So, we are passing empty dictionary. - var encryptionContext = new Dictionary(); - var kmsKeyId = request.Config.KeyMaterial.KmsKeyId; - encryptionMaterial = new EncryptionMaterialsV2(kmsKeyId, KmsType.KmsContext, encryptionContext); - logger.LogInformation( - "[NET-current] Created EncryptionMaterialsV2: KMS={KmsKeyId}", - kmsKeyId); - } - else if (request.Config.KeyMaterial.RsaKey != null) - { - var rsaKeyBytes = request.Config.KeyMaterial.RsaKey; - var rsaKey = RSA.Create(); - rsaKey.ImportPkcs8PrivateKey(new ReadOnlySpan(rsaKeyBytes), out _); - encryptionMaterial = new EncryptionMaterialsV2(rsaKey, AsymmetricAlgorithmType.RsaOaepSha1); - logger.LogInformation( - "Created EncryptionMaterialsV2: RSA"); - } - else if (request.Config.KeyMaterial.AesKey != null) - { - var aesKeyBytes = request.Config.KeyMaterial.AesKey; - var aes = Aes.Create(); - aes.Key = aesKeyBytes; - encryptionMaterial = new EncryptionMaterialsV2(aes, SymmetricAlgorithmType.AesGcm); - logger.LogInformation( - "[NET-current] Created EncryptionMaterialsV2: AES"); - } else - { - return StatusCode(501, new GenericServerError { Message = "[NET-current] Unknown or missing key material!" }); - } - - var enableLegacyUnauthenticatedModes = request.Config.EnableLegacyUnauthenticatedModes; - var enableLegacyWrappingAlgorithms = request.Config.EnableLegacyWrappingAlgorithms; - - // SecurityProfile V2AndLegacy can decrypt from legacy S3EC but V2 cannot - var enableLegacyMode = enableLegacyUnauthenticatedModes || enableLegacyWrappingAlgorithms; - var securityProfile = enableLegacyMode ? SecurityProfile.V2AndLegacy : SecurityProfile.V2; - - logger.LogInformation("[NET-current] Created securityProfile= {securityProfile}", securityProfile.ToString()); - - var configuration = new AmazonS3CryptoConfigurationV2(securityProfile); - - // Add retry configuration for throttling - configuration.RetryMode = Amazon.Runtime.RequestRetryMode.Adaptive; - configuration.MaxErrorRetry = 5; - - if (request.Config.InstructionFileConfig?.EnableInstructionFilePutObject == true) - { - configuration.StorageMode = CryptoStorageMode.InstructionFile; - logger.LogInformation("[NET-current] Created StorageMode= InstructionFile"); - } - // Create S3 encryption client - var encryptionClient = new AmazonS3EncryptionClientV2(configuration, encryptionMaterial); - // Add to cache and return client ID - var clientId = clientCacheService.AddClient(encryptionClient); - var response = new ClientResponse { ClientId = clientId }; - - logger.LogInformation("[NET-current] Created S3EC client with ID: {clientId}", clientId); - - return new ContentResult - { - Content = JsonSerializer.Serialize(response), - ContentType = "application/json", - StatusCode = 200 - }; - } - catch (Exception ex) - { - logger.LogError(ex, "[NET-current] Failed to create S3EC client"); - return StatusCode(500, new S3EncryptionClientError - { - Message = $"[NET-current] Failed to create client: {ex.Message}" - }); - } - } -} diff --git a/test-server/net-v2-v3-server/Controllers/ObjectController.cs b/test-server/net-v2-v3-server/Controllers/ObjectController.cs deleted file mode 100644 index bf1842ae..00000000 --- a/test-server/net-v2-v3-server/Controllers/ObjectController.cs +++ /dev/null @@ -1,105 +0,0 @@ -using System.Text.Json; -using Amazon.S3.Model; -using Microsoft.AspNetCore.Mvc; -using NetV2V3Server.Models; -using NetV2V3Server.Services; - -namespace NetV2V3Server.Controllers; - -[ApiController] -[Route("[controller]")] -public class ObjectController(IClientCacheService clientCacheService, ILogger logger) : ControllerBase -{ - [HttpPut("{bucket}/{key}")] - public async Task PutObject(string bucket, string key) - { - logger.LogInformation("Starting PutObject"); - var clientId = Request.Headers["clientId"].FirstOrDefault(); - if (string.IsNullOrEmpty(clientId)) - return BadRequest(new GenericServerError { Message = "ClientID header is required" }); - - var client = clientCacheService.GetClient(clientId); - if (client == null) - return NotFound(new GenericServerError { Message = $"No client found for ClientID: {clientId}" }); - - try - { - // Read raw body data - using var memoryStream = new MemoryStream(); - // Request is the HTTP request this method is currently handling - await Request.Body.CopyToAsync(memoryStream); - var bodyBytes = memoryStream.ToArray(); - - // Create put request - var putRequest = new PutObjectRequest - { - BucketName = bucket, - Key = key, - InputStream = new MemoryStream(bodyBytes) - }; - - await client.PutObjectAsync(putRequest); - - var response = new { bucket, key }; - - logger.LogInformation( - "Put object succeeded for bucket={bucket}, key={key} and clientId = {clientId}", - bucket, key, clientId); - return new ContentResult - { - Content = JsonSerializer.Serialize(response), - ContentType = "application/json", - StatusCode = 200 - }; - } - catch (Exception ex) - { - logger.LogError(ex, "Failed to put object from S3 for bucket={bucket}, key={key}", bucket, key); - return StatusCode(500, new S3EncryptionClientError { Message = $"Failed to put object: {ex.Message}" }); - } - } - - [HttpGet("{bucket}/{key}")] - public async Task GetObject(string bucket, string key) - { - logger.LogInformation("Starting GetObject"); - var clientId = Request.Headers["clientId"].FirstOrDefault(); - if (string.IsNullOrEmpty(clientId)) - return BadRequest(new GenericServerError { Message = "ClientID header is required" }); - - var client = clientCacheService.GetClient(clientId); - if (client == null) - return NotFound(new GenericServerError { Message = $"No client found for ClientID: {clientId}" }); - - try - { - var getRequest = new GetObjectRequest - { - BucketName = bucket, - Key = key - }; - var response = await client.GetObjectAsync(getRequest); - logger.LogInformation("Got object from S3 for bucket={bucket}, key={key}", bucket, key); - // Read response body - using var memoryStream = new MemoryStream(); - await response.ResponseStream.CopyToAsync(memoryStream); - var bodyBytes = memoryStream.ToArray(); - - // Convert metadata to content-metadata header format - var metadataList = response.Metadata.Keys - .Select(metaDataKey => $"{metaDataKey}={response.Metadata[metaDataKey]}") - .ToList(); - var metadataStr = string.Join(",", metadataList); - - // Set response headers - Response.Headers["Content-Metadata"] = metadataStr; - - return File(bodyBytes, "application/octet-stream"); - } - catch (Exception ex) - { - logger.LogError(ex, "Failed to get object from S3 for bucket={bucket}, key={key}", bucket, key); - return StatusCode(500, new S3EncryptionClientError { Message = ex.Message }); - } - } -} \ No newline at end of file diff --git a/test-server/net-v2-v3-server/Makefile b/test-server/net-v2-v3-server/Makefile deleted file mode 100644 index 9881ee38..00000000 --- a/test-server/net-v2-v3-server/Makefile +++ /dev/null @@ -1,68 +0,0 @@ -# Makefile for S3 Encryption Client .NET Testing - -.PHONY: build-server start-server stop-server wait-for-server - -PID_FILE_NET_V2 := net-v2-server.pid -PID_FILE_NET_V3 := net-v3-server.pid -PORT_NET_V2 := 8083 -PORT_NET_V3 := 8084 - -build-server: - @echo "Building .NET V2 and V3 servers..." - rm -rf obj/v2 bin/v2 obj/v3 bin/v3 - dotnet build -p:S3EncryptionVersion=v2 -o bin/v2 -p:BaseIntermediateOutputPath=obj/v2/ - rm -rf obj - dotnet build -p:S3EncryptionVersion=v3 -o bin/v3 -p:BaseIntermediateOutputPath=obj/v3/ - -start-server: - $(MAKE) start-net-v2-server; \ - $(MAKE) start-net-v3-server; - -stop-server: - @echo "Stopping .NET V2 server on port $(PORT_NET_V2)..." - @lsof -ti:$(PORT_NET_V2) | xargs kill -9 2>/dev/null || true - @if [ -f $(PID_FILE_NET_V2) ]; then \ - pkill -P $$(cat $(PID_FILE_NET_V2)) 2>/dev/null || true; \ - kill -9 $$(cat $(PID_FILE_NET_V2)) 2>/dev/null || true; \ - rm -f $(PID_FILE_NET_V2); \ - fi - @rm -f net-v2-server.log - @echo "Stopping .NET V3 server on port $(PORT_NET_V3)..." - @lsof -ti:$(PORT_NET_V3) | xargs kill -9 2>/dev/null || true - @if [ -f $(PID_FILE_NET_V3) ]; then \ - pkill -P $$(cat $(PID_FILE_NET_V3)) 2>/dev/null || true; \ - kill -9 $$(cat $(PID_FILE_NET_V3)) 2>/dev/null || true; \ - rm -f $(PID_FILE_NET_V3); \ - fi - @rm -f net-v3-server.log - @echo "Servers stopped" - -# Start .NET V2 server in background -start-net-v2-server: - @echo "Starting .NET V2 server..." - AWS_ACCESS_KEY_ID="$$AWS_ACCESS_KEY_ID" \ - AWS_SECRET_ACCESS_KEY="$$AWS_SECRET_ACCESS_KEY" \ - AWS_SESSION_TOKEN="$$AWS_SESSION_TOKEN" \ - AWS_REGION="us-west-2" \ - dotnet bin/v2/NetV2V3Server.dll > net-v2-server.log 2>&1 & echo $$! > net-v2-server.pid - @echo ".NET V2 server starting..." - -# Start .NET V3 server in background -start-net-v3-server: - @echo "Starting .NET V3 server..." - AWS_ACCESS_KEY_ID="$$AWS_ACCESS_KEY_ID" \ - AWS_SECRET_ACCESS_KEY="$$AWS_SECRET_ACCESS_KEY" \ - AWS_SESSION_TOKEN="$$AWS_SESSION_TOKEN" \ - AWS_REGION="us-west-2" \ - dotnet bin/v3/NetV2V3Server.dll > net-v3-server.log 2>&1 & echo $$! > net-v3-server.pid - @echo ".NET V3 server starting..." - -wait-for-server: - $(MAKE) -C .. wait-for-port PORT=$(PORT_NET_V2) - $(MAKE) -C .. wait-for-port PORT=$(PORT_NET_V3) - -duvet: - duvet report - -view-report-mac: - open .duvet/reports/report.html diff --git a/test-server/net-v2-v3-server/Models/ClientRequest.cs b/test-server/net-v2-v3-server/Models/ClientRequest.cs deleted file mode 100644 index e51a9c25..00000000 --- a/test-server/net-v2-v3-server/Models/ClientRequest.cs +++ /dev/null @@ -1,34 +0,0 @@ -using System.ComponentModel.DataAnnotations; - -namespace NetV2V3Server.Models; - -public class ClientRequest -{ - [Required] - public ClientConfig Config { get; set; } = new(); -} - -public class ClientConfig -{ - public bool EnableLegacyUnauthenticatedModes { get; set; } = false; - public bool EnableLegacyWrappingAlgorithms { get; set; } = false; - public bool EnableDelayedAuthenticationMode { get; set; } = false; - public long? SetBufferSize { get; set; } - [Required] - public KeyMaterial KeyMaterial { get; set; } = new(); - public InstructionFileConfig? InstructionFileConfig { get; set; } -} - -public class KeyMaterial -{ - public byte[]? RsaKey { get; set; } - public byte[]? AesKey { get; set; } - public string? KmsKeyId { get; set; } -} - -public class InstructionFileConfig -{ - public string? ClientId { get; set; } - public bool EnableInstructionFilePutObject { get; set; } = false; - public bool DisableInstructionFile { get; set; } = false; -} \ No newline at end of file diff --git a/test-server/net-v2-v3-server/Models/ClientResponse.cs b/test-server/net-v2-v3-server/Models/ClientResponse.cs deleted file mode 100644 index 1e029ef7..00000000 --- a/test-server/net-v2-v3-server/Models/ClientResponse.cs +++ /dev/null @@ -1,8 +0,0 @@ -using System.Text.Json.Serialization; - -namespace NetV2V3Server.Models; - -public class ClientResponse -{ - [JsonPropertyName("clientId")] public string ClientId { get; set; } = string.Empty; -} \ No newline at end of file diff --git a/test-server/net-v2-v3-server/Models/ErrorModels.cs b/test-server/net-v2-v3-server/Models/ErrorModels.cs deleted file mode 100644 index af1646e7..00000000 --- a/test-server/net-v2-v3-server/Models/ErrorModels.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System.Text.Json.Serialization; - -namespace NetV2V3Server.Models; - -public class GenericServerError -{ - [JsonPropertyName("__type")] - public string Type { get; set; } = "software.amazon.encryption.s3#GenericServerError"; - public string Message { get; set; } = string.Empty; -} - -public class S3EncryptionClientError -{ - [JsonPropertyName("__type")] - public string Type { get; set; } = "software.amazon.encryption.s3#S3EncryptionClientError"; - public string Message { get; set; } = string.Empty; -} diff --git a/test-server/net-v2-v3-server/NetV2V3Server.csproj b/test-server/net-v2-v3-server/NetV2V3Server.csproj deleted file mode 100644 index 8d664eff..00000000 --- a/test-server/net-v2-v3-server/NetV2V3Server.csproj +++ /dev/null @@ -1,39 +0,0 @@ - - - - net8.0 - enable - enable - false - - - - false - - - - S3EC_V2 - - - - S3EC_V3 - - - - - - - - - - - - - - - - - - - - diff --git a/test-server/net-v2-v3-server/Program.cs b/test-server/net-v2-v3-server/Program.cs deleted file mode 100644 index 1b77de5d..00000000 --- a/test-server/net-v2-v3-server/Program.cs +++ /dev/null @@ -1,21 +0,0 @@ -using NetV2V3Server.Services; - -var builder = WebApplication.CreateBuilder(args); - -builder.Services.AddControllers(); -builder.Services.AddSingleton(); - -#if S3EC_V2 -const int port = 8083; -#else -const int port = 8084; -#endif - -builder.WebHost.UseUrls($"http://localhost:{port}"); - -var app = builder.Build(); - -app.MapControllers(); - -Console.WriteLine($"Starting server on port {port}"); -app.Run(); diff --git a/test-server/net-v2-v3-server/README.md b/test-server/net-v2-v3-server/README.md deleted file mode 100644 index 9463d8b5..00000000 --- a/test-server/net-v2-v3-server/README.md +++ /dev/null @@ -1,72 +0,0 @@ -# Net-V2-V3-Server - -A .NET test server for Amazon S3 encryption client .NET v2 and v3. - -## Project Structure - -``` -net-v2-v3-server/ -├── Controllers/ # API controllers -├── Models/ # Data models -├── Services/ # Business logic services -├── Program.cs # Application entry point -├── NetV2V3Server.csproj # Project file -└── README.md # This file -``` - -## Running the Server - -For S3 Encryption Client v2 (runs on port 8083): - -```bash -dotnet run -p:S3EncryptionVersion=v2 -``` - -For S3 Encryption Client v3 (runs on port 8084): - -```bash -dotnet run -p:S3EncryptionVersion=v3 -``` - -## API Endpoints - -### Client Management - -- `POST /Client` - Create a new S3 encryption client - -### Object Operations - -- `PUT /{bucket}/{key}` - Upload an encrypted object to S3 -- `GET /{bucket}/{key}` - Download and decrypt an object from S3 - -All object operations require a `clientId` header to specify which client to use. - -## Example Usage - -### Create a Client - -```bash -curl -i -X POST \ - -H "Content-Type: application/json" \ - -H "User-Agent: smithy-java/0.0.3 ua/2.1 os/macos#15.5 lang/java#23.0.2" \ - -d '{"config":{"enableLegacyUnauthenticatedModes":true,"enableLegacyWrappingAlgorithms":true,"keyMaterial":{"kmsKeyId":"arn:aws:kms:us-west-2:370957321024:alias/S3EC-Test-Server-Github-KMS-Key"}, "encryptionContext": {"abc": "b"}}}' \ - http://localhost:8083/client -``` - -### Upload an Object - -```bash -curl -X PUT \ - -H "clientid: 7978763a-a02b-4dea-a5d4-78ef11d13d12" \ - -H "content-type: application/octet-stream" \ - -d "simple-test-input-net" \ - http://localhost:8083/object/s3ec-test-server-github-bucket/cross-lang-test-key-kms-ec-dotnet -``` - -### Download an Object - -```bash -curl -X GET \ - -H "clientid: 7978763a-a02b-4dea-a5d4-78ef11d13d12" \ - http://localhost:8083/object/s3ec-test-server-github-bucket/cross-lang-test-key-kms-ec-dotnet -``` diff --git a/test-server/net-v2-v3-server/Services/ClientCacheService.cs b/test-server/net-v2-v3-server/Services/ClientCacheService.cs deleted file mode 100644 index d8239c9b..00000000 --- a/test-server/net-v2-v3-server/Services/ClientCacheService.cs +++ /dev/null @@ -1,28 +0,0 @@ -using Amazon.Extensions.S3.Encryption; -using System.Collections.Concurrent; - -namespace NetV2V3Server.Services; - -public interface IClientCacheService -{ - string AddClient(AmazonS3EncryptionClientV2 client); - AmazonS3EncryptionClientV2? GetClient(string clientId); -} - -public class ClientCacheService : IClientCacheService -{ - private readonly ConcurrentDictionary _clients = new(); - - public string AddClient(AmazonS3EncryptionClientV2 client) - { - var clientId = Guid.NewGuid().ToString(); - _clients[clientId] = client; - return clientId; - } - - public AmazonS3EncryptionClientV2? GetClient(string clientId) - { - _clients.TryGetValue(clientId, out var client); - return client; - } -} diff --git a/test-server/net-v2-v3-server/s3ec-net-v2 b/test-server/net-v2-v3-server/s3ec-net-v2 deleted file mode 160000 index ed27648c..00000000 --- a/test-server/net-v2-v3-server/s3ec-net-v2 +++ /dev/null @@ -1 +0,0 @@ -Subproject commit ed27648c36a2b290b52f94586fce107af7c51fe5 diff --git a/test-server/net-v2-v3-server/s3ec-net-v3 b/test-server/net-v2-v3-server/s3ec-net-v3 deleted file mode 160000 index 7a552940..00000000 --- a/test-server/net-v2-v3-server/s3ec-net-v3 +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 7a55294068bb3bb7f96226efd6d9edcd1057184b diff --git a/test-server/net-v3-transition-server/README.md b/test-server/net-v3-transition-server/README.md index 7634a4e7..ea925c73 100644 --- a/test-server/net-v3-transition-server/README.md +++ b/test-server/net-v3-transition-server/README.md @@ -1,11 +1,11 @@ -# Net-V2-V3-Server +# Net-V3-Transition-Server A .NET test server for Amazon S3 encryption client .NET v3 transition. ## Project Structure ``` -net-v2-v3-server/ +net-v3-transition-server/ ├── Controllers/ # API controllers ├── Models/ # Data models ├── Services/ # Business logic services diff --git a/test-server/net-v4-server/README.md b/test-server/net-v4-server/README.md index dd5a6753..487d8471 100644 --- a/test-server/net-v4-server/README.md +++ b/test-server/net-v4-server/README.md @@ -1,11 +1,11 @@ -# Net-V2-V3-Server +# Net-V4-Server -A .NET test server for Amazon S3 encryption client .NET v2 and v3. +A .NET test server for Amazon S3 encryption client .NET v4. ## Project Structure ``` -net-v2-v3-server/ +net-v4-server/ ├── Controllers/ # API controllers ├── Models/ # Data models ├── Services/ # Business logic services From 56cba03551c3cf88163b488ca47ea95e43b254b2 Mon Sep 17 00:00:00 2001 From: Kess Plasmeier Date: Fri, 30 Jan 2026 13:36:32 -0800 Subject: [PATCH 24/38] delete examples --- .github/workflows/examples.yml | 88 --- all-examples/Makefile | 110 ---- all-examples/README.md | 74 --- all-examples/cpp/CMakeLists.txt | 23 - all-examples/cpp/Makefile | 60 --- all-examples/cpp/README.md | 15 - all-examples/cpp/main.cpp | 245 --------- all-examples/go/v3/Makefile | 69 --- all-examples/go/v3/README.md | 55 -- all-examples/go/v3/go.mod | 32 -- all-examples/go/v3/go.sum | 40 -- all-examples/go/v3/local-go-s3ec | 1 - all-examples/go/v3/main.go | 171 ------ all-examples/go/v4/Makefile | 69 --- all-examples/go/v4/README.md | 55 -- all-examples/go/v4/go.mod | 32 -- all-examples/go/v4/go.sum | 40 -- all-examples/go/v4/local-go-s3ec | 1 - all-examples/go/v4/main.go | 171 ------ all-examples/java/v3/Makefile | 75 --- all-examples/java/v3/README.md | 57 -- all-examples/java/v3/build.gradle.kts | 47 -- .../java/v3/gradle/wrapper/gradle-wrapper.jar | Bin 43453 -> 0 bytes .../gradle/wrapper/gradle-wrapper.properties | 7 - all-examples/java/v3/gradlew | 249 --------- all-examples/java/v3/gradlew.bat | 92 ---- all-examples/java/v3/s3ec-staging | 1 - all-examples/java/v3/settings.gradle.kts | 1 - .../amazon/encryption/s3/example/Main.java | 161 ------ all-examples/java/v4/Makefile | 75 --- all-examples/java/v4/README.md | 57 -- all-examples/java/v4/build.gradle.kts | 47 -- .../java/v4/gradle/wrapper/gradle-wrapper.jar | Bin 43453 -> 0 bytes .../gradle/wrapper/gradle-wrapper.properties | 7 - all-examples/java/v4/gradlew | 249 --------- all-examples/java/v4/gradlew.bat | 92 ---- all-examples/java/v4/s3ec-staging | 1 - all-examples/java/v4/settings.gradle.kts | 1 - .../amazon/encryption/s3/example/Main.java | 160 ------ all-examples/net/.gitignore | 19 - all-examples/net/v3/Makefile | 66 --- all-examples/net/v3/Program.cs | 76 --- all-examples/net/v3/s3ec-v3-local | 1 - all-examples/net/v3/v3.csproj | 19 - all-examples/net/v4/Makefile | 66 --- all-examples/net/v4/Program.cs | 76 --- all-examples/net/v4/s3ec-v4-local | 1 - all-examples/net/v4/v4.csproj | 19 - all-examples/php/v2/.gitignore | 4 - all-examples/php/v2/Makefile | 71 --- all-examples/php/v2/composer.json | 33 -- all-examples/php/v2/local-php-sdk | 1 - all-examples/php/v2/main.php | 161 ------ all-examples/php/v3/.gitignore | 4 - all-examples/php/v3/Makefile | 71 --- all-examples/php/v3/composer.json | 33 -- all-examples/php/v3/local-php-sdk | 1 - all-examples/php/v3/main.php | 508 ------------------ all-examples/ruby/v2/Gemfile | 12 - all-examples/ruby/v2/Gemfile.lock | 82 --- all-examples/ruby/v2/Makefile | 70 --- all-examples/ruby/v2/local-ruby-sdk | 1 - all-examples/ruby/v2/main.rb | 150 ------ all-examples/ruby/v3/Gemfile | 12 - all-examples/ruby/v3/Makefile | 70 --- all-examples/ruby/v3/local-ruby-sdk | 1 - all-examples/ruby/v3/main.rb | 148 ----- 67 files changed, 4506 deletions(-) delete mode 100644 .github/workflows/examples.yml delete mode 100644 all-examples/Makefile delete mode 100644 all-examples/README.md delete mode 100644 all-examples/cpp/CMakeLists.txt delete mode 100644 all-examples/cpp/Makefile delete mode 100644 all-examples/cpp/README.md delete mode 100644 all-examples/cpp/main.cpp delete mode 100644 all-examples/go/v3/Makefile delete mode 100644 all-examples/go/v3/README.md delete mode 100644 all-examples/go/v3/go.mod delete mode 100644 all-examples/go/v3/go.sum delete mode 120000 all-examples/go/v3/local-go-s3ec delete mode 100644 all-examples/go/v3/main.go delete mode 100644 all-examples/go/v4/Makefile delete mode 100644 all-examples/go/v4/README.md delete mode 100644 all-examples/go/v4/go.mod delete mode 100644 all-examples/go/v4/go.sum delete mode 120000 all-examples/go/v4/local-go-s3ec delete mode 100644 all-examples/go/v4/main.go delete mode 100644 all-examples/java/v3/Makefile delete mode 100644 all-examples/java/v3/README.md delete mode 100644 all-examples/java/v3/build.gradle.kts delete mode 100644 all-examples/java/v3/gradle/wrapper/gradle-wrapper.jar delete mode 100644 all-examples/java/v3/gradle/wrapper/gradle-wrapper.properties delete mode 100755 all-examples/java/v3/gradlew delete mode 100644 all-examples/java/v3/gradlew.bat delete mode 120000 all-examples/java/v3/s3ec-staging delete mode 100644 all-examples/java/v3/settings.gradle.kts delete mode 100644 all-examples/java/v3/src/main/java/software/amazon/encryption/s3/example/Main.java delete mode 100644 all-examples/java/v4/Makefile delete mode 100644 all-examples/java/v4/README.md delete mode 100644 all-examples/java/v4/build.gradle.kts delete mode 100644 all-examples/java/v4/gradle/wrapper/gradle-wrapper.jar delete mode 100644 all-examples/java/v4/gradle/wrapper/gradle-wrapper.properties delete mode 100755 all-examples/java/v4/gradlew delete mode 100644 all-examples/java/v4/gradlew.bat delete mode 120000 all-examples/java/v4/s3ec-staging delete mode 100644 all-examples/java/v4/settings.gradle.kts delete mode 100644 all-examples/java/v4/src/main/java/software/amazon/encryption/s3/example/Main.java delete mode 100644 all-examples/net/.gitignore delete mode 100644 all-examples/net/v3/Makefile delete mode 100644 all-examples/net/v3/Program.cs delete mode 120000 all-examples/net/v3/s3ec-v3-local delete mode 100644 all-examples/net/v3/v3.csproj delete mode 100644 all-examples/net/v4/Makefile delete mode 100644 all-examples/net/v4/Program.cs delete mode 120000 all-examples/net/v4/s3ec-v4-local delete mode 100644 all-examples/net/v4/v4.csproj delete mode 100644 all-examples/php/v2/.gitignore delete mode 100644 all-examples/php/v2/Makefile delete mode 100644 all-examples/php/v2/composer.json delete mode 120000 all-examples/php/v2/local-php-sdk delete mode 100755 all-examples/php/v2/main.php delete mode 100644 all-examples/php/v3/.gitignore delete mode 100644 all-examples/php/v3/Makefile delete mode 100644 all-examples/php/v3/composer.json delete mode 120000 all-examples/php/v3/local-php-sdk delete mode 100755 all-examples/php/v3/main.php delete mode 100644 all-examples/ruby/v2/Gemfile delete mode 100644 all-examples/ruby/v2/Gemfile.lock delete mode 100644 all-examples/ruby/v2/Makefile delete mode 120000 all-examples/ruby/v2/local-ruby-sdk delete mode 100644 all-examples/ruby/v2/main.rb delete mode 100644 all-examples/ruby/v3/Gemfile delete mode 100644 all-examples/ruby/v3/Makefile delete mode 120000 all-examples/ruby/v3/local-ruby-sdk delete mode 100644 all-examples/ruby/v3/main.rb diff --git a/.github/workflows/examples.yml b/.github/workflows/examples.yml deleted file mode 100644 index 1ef01de4..00000000 --- a/.github/workflows/examples.yml +++ /dev/null @@ -1,88 +0,0 @@ -name: Run Examples - -on: - workflow_call: - -jobs: - run-examples: - runs-on: macos-14-large - permissions: - id-token: write - contents: read - - steps: - - name: Checkout code - uses: actions/checkout@v5 - with: - submodules: true - token: ${{ secrets.PAT_FOR_PRIVATE_RUBY }} - - - name: Checkout CPP code cpp-examples - uses: actions/checkout@v5 - with: - submodules: recursive - repository: aws/aws-sdk-cpp - ref: main - path: all-examples/cpp/aws-sdk-cpp/ - - - name: Set up Python - uses: actions/setup-python@v5 - with: - python-version: "3.11" - - - name: Set up Ruby - uses: ruby/setup-ruby@v1 - with: - ruby-version: "3.4.7" - - - name: Set up PHP with Composer - uses: shivammathur/setup-php@verbose - with: - php-version: "8.1" - - - name: Set up Go - uses: actions/setup-go@v5 - with: - go-version: 1.24 - - # Cache uv dependencies - - name: Cache uv dependencies - uses: actions/cache@v3 - with: - path: ~/.cache/uv - key: ${{ runner.os }}-uv-${{ hashFiles('./test-server/python-v3-server/**/pyproject.toml') }} - restore-keys: | - ${{ runner.os }}-uv- - - - name: Install Uv - run: pip install uv - - # Cache Gradle dependencies and build outputs - - name: Cache Gradle packages - uses: actions/cache@v4 - with: - path: | - ~/.gradle/caches - ~/.gradle/wrapper - test-server/java-tests/.gradle - key: ${{ runner.os }}-gradle-${{ hashFiles('test-server/java-tests/**/gradle-wrapper.properties', 'test-server/java-tests/**/*.gradle*') }} - restore-keys: | - ${{ runner.os }}-gradle- - - - name: Configure AWS credentials - uses: aws-actions/configure-aws-credentials@v4 - with: - role-to-assume: arn:aws:iam::370957321024:role/S3EC-Python-Github-test-role - aws-region: us-west-2 - - - name: Install dependencies for all examples - working-directory: ./all-examples - run: make install - - - name: Run all examples - working-directory: ./all-examples - run: make run - env: - AWS_REGION: us-west-2 - BUCKET_NAME: ${{ vars.TEST_SERVER_S3_BUCKET }} - KMS_KEY_ID: ${{ vars.TEST_SERVER_KMS_KEY_ARN }} diff --git a/all-examples/Makefile b/all-examples/Makefile deleted file mode 100644 index eeb0949b..00000000 --- a/all-examples/Makefile +++ /dev/null @@ -1,110 +0,0 @@ -# Makefile for S3 Encryption Client Examples -# Runs make commands across all language/version directories - -# Default target -.PHONY: all install clean run help list-examples - -# Find all directories with Makefiles -EXAMPLE_DIRS := $(shell find . -name Makefile -not -path "./Makefile" -not -path "./cpp/aws-sdk-cpp/**" -not -path "./cpp/build/**" | xargs dirname | sed 's|^\./||' | $(if $(FILTER),grep -E "$$(echo '$(FILTER)' | sed 's/,/|/g')",cat) | sort) - -all: install - -# Install dependencies for all examples -install: - @echo "Installing dependencies for all examples..." - @failed=0; \ - for dir in $(EXAMPLE_DIRS); do \ - echo ""; \ - echo "=== Installing dependencies in $$dir ==="; \ - if (cd $$dir && $(MAKE) install); then \ - echo "✓ Successfully installed dependencies in $$dir"; \ - else \ - echo "✗ Failed to install dependencies in $$dir"; \ - failed=$$((failed + 1)); \ - fi; \ - done; \ - echo ""; \ - if [ $$failed -eq 0 ]; then \ - echo "All dependencies installed successfully!"; \ - else \ - echo "$$failed example(s) failed to install dependencies"; \ - exit 1; \ - fi - -# Clean all examples -clean: - @echo "Cleaning all examples..." - @failed=0; \ - for dir in $(EXAMPLE_DIRS); do \ - echo ""; \ - echo "=== Cleaning $$dir ==="; \ - if (cd $$dir && $(MAKE) clean); then \ - echo "✓ Successfully cleaned $$dir"; \ - else \ - echo "✗ Failed to clean $$dir"; \ - failed=$$((failed + 1)); \ - fi; \ - done; \ - echo ""; \ - if [ $$failed -eq 0 ]; then \ - echo "All examples cleaned successfully!"; \ - else \ - echo "$$failed example(s) failed to clean"; \ - exit 1; \ - fi - -# Run all examples with default parameters -run: - @echo "Running all examples with default parameters..." - @failed=0; \ - for dir in $(EXAMPLE_DIRS); do \ - echo ""; \ - echo "=== Running example in $$dir ==="; \ - if (cd $$dir && $(MAKE) run); then \ - echo "✓ Successfully ran example in $$dir"; \ - else \ - echo "✗ Failed to run example in $$dir"; \ - failed=$$((failed + 1)); \ - fi; \ - done; \ - echo ""; \ - if [ $$failed -eq 0 ]; then \ - echo "All examples completed successfully!"; \ - else \ - echo "$$failed example(s) failed to run"; \ - exit 1; \ - fi - -# List all available examples -list-examples: - @echo "Available S3 Encryption Client examples:" - @for dir in $(EXAMPLE_DIRS); do \ - echo " $$dir"; \ - done - -# Show help -help: - @echo "S3 Encryption Client Examples Makefile" - @echo "" - @echo "Available targets:" - @echo " install - Install dependencies for all examples" - @echo " run - Run all examples with default parameters" - @echo " clean - Clean all examples" - @echo " list-examples - List all available example directories" - @echo " help - Show this help message" - @echo "" - @echo "Filtering examples:" - @echo " Use FILTER to run commands on specific examples:" - @echo " make install FILTER=go # Only Go examples" - @echo " make run FILTER=v4 # Only v4 examples" - @echo " make clean FILTER=go/v3,ruby # Go v3 and Ruby examples" - @echo "" - @echo "Individual example usage:" - @echo " To work with a specific example, cd into its directory and use its Makefile:" - @echo " cd go/v4 && make run" - @echo " cd ruby/v2 && make install" - @echo "" - @echo "Available examples:" - @for dir in $(EXAMPLE_DIRS); do \ - echo " $$dir"; \ - done diff --git a/all-examples/README.md b/all-examples/README.md deleted file mode 100644 index 8472de78..00000000 --- a/all-examples/README.md +++ /dev/null @@ -1,74 +0,0 @@ -# S3 Encryption Client Examples - -This directory contains example projects for the Amazon S3 Encryption Client across different programming languages and major versions. - -## Directory Structure - -Each language has subdirectories for different major versions of the S3 Encryption Client: - -- `cpp/` - C++ examples - - `v2/` - S3EC C++ v2 example (transitional) - - `v3/` - S3EC C++ v3 example (improved) -- `net/` - .NET examples - - `v3/` - S3EC .NET v3 example (transitional) - - `v4/` - S3EC .NET v4 example (improved) -- `go/` - Go examples - - `v3/` - S3EC Go v3 example (transitional) - - `v4/` - S3EC Go v4 example (improved) -- `java/` - Java examples - - `v3/` - S3EC Java v3 example (transitional) - - `v4/` - S3EC Java v4 example (improved) -- `php/` - PHP examples - - `v2/` - S3EC PHP v2 example (transitional) - - `v3/` - S3EC PHP v3 example (improved) -- `ruby/` - Ruby examples - - `v2/` - S3EC Ruby v2 example (transitional) - - `v3/` - S3EC Ruby v3 example (improved) - -## Setup Instructions - -### Prerequisites - -1. **Git Submodules**: Some examples depend on staging versions of the S3EC libraries that are included as git submodules. Initialize and update submodules: - - ```bash - git submodule update --init --recursive - ``` - -2. **AWS Credentials**: Configure your AWS credentials using one of the following methods: - - AWS CLI: `aws configure` - - Environment variables: `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY` - - IAM roles (for EC2 instances) - -3. **KMS Key**: Use "arn:aws:kms:us-east-2:648638458147:key/a47079da-17e4-45a5-b82e-2bac101cad01" by default, or create a KMS key in your AWS account and note the key ID for use in examples. - -### Language-Specific Setup - -Each language directory contains specific setup instructions in its README file. Generally: - -- **Java**: Requires JDK 11+ and Gradle -- **Go**: Requires Go 1.21+ -- **.NET**: Requires .NET 8.0+ -- **PHP**: Requires PHP 7.4+ and Composer -- **Ruby**: Requires Ruby 3.0+ and Bundler -- **C++**: Requires CMake 3.16+ and C++17 compiler - -## Usage - -Each example directory contains: - -- Build configuration files (e.g., `build.gradle.kts`, `go.mod`, `composer.json`) -- Source code demonstrating basic S3EC usage -- README with specific setup and run instructions - -## Dependencies - -Examples use different dependency sources based on version: - -- **Released versions**: Use public package repositories (Maven Central, npm, etc.) -- **Staging versions**: Use git submodules pointing to staging repositories -- **Local versions**: Reference locally built libraries - -## Support - -For issues with specific examples, refer to the individual README files in each language/version directory. diff --git a/all-examples/cpp/CMakeLists.txt b/all-examples/cpp/CMakeLists.txt deleted file mode 100644 index 906951b8..00000000 --- a/all-examples/cpp/CMakeLists.txt +++ /dev/null @@ -1,23 +0,0 @@ -cmake_minimum_required(VERSION 3.16) -project(s3ec-test) - -set(CMAKE_CXX_STANDARD 14) - -# Configure AWS SDK build options -set(BUILD_ONLY "kms;s3;s3-encryption" CACHE STRING "Build only KMS, S3, and S3-encryption components") -set(ENABLE_TESTING OFF CACHE BOOL "Disable testing") -set(BUILD_SHARED_LIBS OFF CACHE BOOL "Build static libraries") - -# Add AWS SDK as subdirectory -add_subdirectory(aws-sdk-cpp) - -find_package(PkgConfig REQUIRED) - -add_executable(s3ec-test main.cpp) - -target_link_libraries(s3ec-test - aws-cpp-sdk-core - aws-cpp-sdk-kms - aws-cpp-sdk-s3 - aws-cpp-sdk-s3-encryption -) diff --git a/all-examples/cpp/Makefile b/all-examples/cpp/Makefile deleted file mode 100644 index 64550528..00000000 --- a/all-examples/cpp/Makefile +++ /dev/null @@ -1,60 +0,0 @@ -# Makefile for S3 Encryption Client C++ Example - -.PHONY: all install clean run help - -# Default arguments for running the example -# Override these when calling make run -VERSION ?= V3 -BUCKET_NAME ?= avp-21638 -OBJECT_KEY ?= s3ec-cpp-test -KMS_KEY_ID ?= arn:aws:kms:us-east-2:648638458147:key/a47079da-17e4-45a5-b82e-2bac101cad01 -AWS_REGION ?= us-east-2 - -all: run - -install: build/s3ec-test - -aws-sdk-cpp: - git clone --recurse-submodules -b fire-egg-dev https://github.com/awslabs/aws-sdk-cpp-staging.git aws-sdk-cpp - -build/s3ec-test: aws-sdk-cpp - mkdir -p build && cd build && cmake .. && make - -clean: - rm -rf build - -# Run the example with default arguments -run: build/s3ec-test - @echo "Running S3 Encryption Client C++ example..." - @echo "Version: $(VERSION)" - @echo "Bucket: $(BUCKET_NAME)" - @echo "Object Key: $(OBJECT_KEY)" - @echo "KMS Key ID: $(KMS_KEY_ID)" - @echo "Region: $(AWS_REGION)" - @echo "" - ./build/s3ec-test $(VERSION) $(BUCKET_NAME) $(OBJECT_KEY) $(KMS_KEY_ID) $(AWS_REGION) - -# Show help -help: - @echo "S3 Encryption Client C++ Example Makefile" - @echo "" - @echo "Available targets:" - @echo " install - Install Go dependencies using Go modules" - @echo " run - Install dependencies and run the example" - @echo " clean - Remove C++ artifacts" - @echo " help - Show this help message" - @echo "" - @echo "Default parameters:" - @echo " VERSION = $(VERSION) (must be V2 or V3)" - @echo " BUCKET_NAME = $(BUCKET_NAME)" - @echo " OBJECT_KEY = $(OBJECT_KEY)" - @echo " KMS_KEY_ID = $(KMS_KEY_ID)" - @echo " AWS_REGION = $(AWS_REGION)" - @echo "" - @echo "To run with custom parameters:" - @echo " make run VERSION=your-version BUCKET_NAME=your-bucket OBJECT_KEY=your-key KMS_KEY_ID=your-kms-key AWS_REGION=your-region" - @echo "" - @echo "Prerequisites:" - @echo " - Read access to https://github.com/awslabs/aws-sdk-cpp-staging.git" - @echo " - AWS credentials configured (AWS CLI, environment variables, or IAM role)" - @echo " - Valid S3 bucket and KMS key with appropriate permissions" diff --git a/all-examples/cpp/README.md b/all-examples/cpp/README.md deleted file mode 100644 index 8875fb07..00000000 --- a/all-examples/cpp/README.md +++ /dev/null @@ -1,15 +0,0 @@ -# C++ S3 Encryption Test - -Minimal C++ use of S3 Encryption - -## Build - -```bash -make install -``` - -## Run - -```bash -make run -``` diff --git a/all-examples/cpp/main.cpp b/all-examples/cpp/main.cpp deleted file mode 100644 index 30cb2b8d..00000000 --- a/all-examples/cpp/main.cpp +++ /dev/null @@ -1,245 +0,0 @@ -#include -#include -#include -#include - -using namespace Aws::S3Encryption; -using Aws::S3Encryption::Materials::KMSWithContextEncryptionMaterials; - -static Aws::Map get_encryption_context(const char * version) -{ - return { - {"purpose", "example"}, - {"version", version}, - {"language", "c++"} - }; -} - -static int test_migration(const char *bucket, const char *object, const char *kms_key_id, const char *region) -{ - Aws::Client::ClientConfiguration s3ClientConfig; - s3ClientConfig.region = region; - - auto materials = std::make_shared(kms_key_id, s3ClientConfig); - CryptoConfigurationV3 config(materials); - - // STEP 1: Upgrade to V3 client to prepare to read messages with commitment. - // You want to update your readers before you update your writers - config.SetCommitmentPolicy(CommitmentPolicy::FORBID_ENCRYPT_ALLOW_DECRYPT); - auto client = std::make_shared(config, s3ClientConfig); - - auto encryption_context = get_encryption_context("V3"); - - // Put Object - writes objects WITHOUT commitment - Aws::S3::Model::PutObjectRequest put_request; - put_request.SetBucket(bucket); - put_request.SetKey(object); - - auto data = std::string("This is the sample content."); - - auto stream = std::make_shared(data); - put_request.SetBody(stream); - - // Put Object - writes objects WITHOUT commitment - auto put_outcome = client->PutObject(put_request, encryption_context); - assert(put_outcome.IsSuccess()); - - Aws::S3::Model::GetObjectRequest get_request; - get_request.SetBucket(bucket); - get_request.SetKey(object); - - // Get Object - can read objects with or without commitment - auto get_outcome = client->GetObject(get_request, encryption_context); - assert(get_outcome.IsSuccess()); - - // STEP 2: If all of the readers can read with or without commitment - // you can upgrade the commitment policy to write objects with commitment - config.SetCommitmentPolicy(CommitmentPolicy::REQUIRE_ENCRYPT_ALLOW_DECRYPT); - client = std::make_shared(config, s3ClientConfig); - - stream = std::make_shared(data); - put_request.SetBody(stream); - - // Put Object - writes objects WITH commitment - put_outcome = client->PutObject(put_request, encryption_context); - assert(put_outcome.IsSuccess()); - - // Get Object - can read objects with or without commitment - get_outcome = client->GetObject(get_request, encryption_context); - assert(get_outcome.IsSuccess()); - - // STEP 3: Once your system no longer has to read messages without commitment, - // you may update your client to only read messages written with key commitment - config.SetCommitmentPolicy(CommitmentPolicy::REQUIRE_ENCRYPT_REQUIRE_DECRYPT); - client = std::make_shared(config, s3ClientConfig); - - stream = std::make_shared(data); - put_request.SetBody(stream); - - // Put Object - writes objects WITH commitment - put_outcome = client->PutObject(put_request, encryption_context); - assert(put_outcome.IsSuccess()); - - // Get Object - can only read objects with commitment - get_outcome = client->GetObject(get_request, encryption_context); - assert(get_outcome.IsSuccess()); - - return 0; -} - -static int test_v3(const char *bucket, const char *object, const char *kms_key_id, const char *region) -{ - Aws::Client::ClientConfiguration s3ClientConfig; - s3ClientConfig.region = region; - - auto materials = std::make_shared(kms_key_id, s3ClientConfig); - CryptoConfigurationV3 config(materials); - // config.AllowLegacy(); - // config.SetStorageMethod(StorageMethod::INSTRUCTION_FILE); - // config.SetCommitmentPolicy(CommitmentPolicy::FORBID_ENCRYPT_ALLOW_DECRYPT); - - - auto client = std::make_shared(config, s3ClientConfig); - - auto encryption_context = get_encryption_context("V3"); - - Aws::S3::Model::PutObjectRequest put_request; - put_request.SetBucket(bucket); - put_request.SetKey(object); - - auto data = std::string("This is the sample content."); - - auto stream = std::make_shared(data); - put_request.SetBody(stream); - - auto put_outcome = client->PutObject(put_request, encryption_context); - if (put_outcome.IsSuccess()) - { - fprintf(stderr, "PutObject V3 Successful.\n"); - } - else - { - fprintf(stderr, "PutObject V3 Failed : %s\n", put_outcome.GetError().GetMessage().c_str()); - return 1; - } - - Aws::S3::Model::GetObjectRequest get_request; - get_request.SetBucket(bucket); - get_request.SetKey(object); - auto get_outcome = client->GetObject(get_request, encryption_context); - if (get_outcome.IsSuccess()) - { - fprintf(stderr, "GetObject V3 Successful.\n"); - Aws::StringStream response_stream; - response_stream << get_outcome.GetResult().GetBody().rdbuf(); - if (response_stream.str() != data) - { - fprintf(stderr, "GetObject V3 returned the wrong data.\n"); - return 1; - } - } - else - { - fprintf(stderr, "GetObject V3 Failed : %s\n", put_outcome.GetError().GetMessage().c_str()); - return 1; - } - return 0; -} - -static int test_v2(const char *bucket, const char *object, const char *kms_key_id, const char *region) -{ - Aws::Client::ClientConfiguration s3ClientConfig; - s3ClientConfig.region = region; - - auto materials = std::make_shared(kms_key_id, s3ClientConfig); - CryptoConfigurationV2 config(materials); - // config.SetSecurityProfile(SecurityProfile::V2_AND_LEGACY); - // config.SetStorageMethod(StorageMethod::INSTRUCTION_FILE); - - - auto client = std::make_shared(config, s3ClientConfig); - - auto encryption_context = get_encryption_context("V2"); - - Aws::S3::Model::PutObjectRequest put_request; - put_request.SetBucket(bucket); - put_request.SetKey(object); - - auto data = std::string("This is the sample content."); - - auto stream = std::make_shared(data); - put_request.SetBody(stream); - - auto put_outcome = client->PutObject(put_request, encryption_context); - if (put_outcome.IsSuccess()) - { - fprintf(stderr, "PutObject V2 Successful.\n"); - } - else - { - fprintf(stderr, "PutObject V2 Failed : %s\n", put_outcome.GetError().GetMessage().c_str()); - return 1; - } - - Aws::S3::Model::GetObjectRequest get_request; - get_request.SetBucket(bucket); - get_request.SetKey(object); - auto get_outcome = client->GetObject(get_request, encryption_context); - if (get_outcome.IsSuccess()) - { - fprintf(stderr, "GetObject V2 Successful.\n"); - Aws::StringStream response_stream; - response_stream << get_outcome.GetResult().GetBody().rdbuf(); - if (response_stream.str() != data) - { - fprintf(stderr, "GetObject V2 returned the wrong data.\n"); - return 1; - } - } - else - { - fprintf(stderr, "GetObject V2 Failed : %s\n", put_outcome.GetError().GetMessage().c_str()); - return 1; - } - return 0; -} - -int main(int argc, char **argv) -{ - if (argc != 6) - { - fprintf(stderr, "USAGE : s3ec-test version bucket object key_id region"); - return 1; - } - - auto version_str = argv[1]; - auto bucket = argv[2]; - auto object = argv[3]; - auto kms_key_id = argv[4]; - auto region = argv[5]; - - bool is_v3; - if (strcasecmp(version_str, "v3") == 0) - { - is_v3 = true; - } - else if (strcasecmp(version_str, "v2") == 0) - { - is_v3 = false; - } - else - { - fprintf(stderr, "Version was <%s> must be V2 or V3\n", version_str); - return 1; - } - - Aws::SDKOptions options; - Aws::InitAPI(options); - - if (is_v3) - test_v3(bucket, object, kms_key_id, region); - else - test_v2(bucket, object, kms_key_id, region); - - Aws::ShutdownAPI(options); -} diff --git a/all-examples/go/v3/Makefile b/all-examples/go/v3/Makefile deleted file mode 100644 index d7285fe9..00000000 --- a/all-examples/go/v3/Makefile +++ /dev/null @@ -1,69 +0,0 @@ -# Makefile for S3 Encryption Client Go v3 Example - -# Default target -.PHONY: all install clean run help - -# Variables -SCRIPT = main.go - -# Default arguments for running the example -# Override these when calling make run -BUCKET_NAME ?= avp-21638 -OBJECT_KEY ?= s3ec-go-v3 -KMS_KEY_ID ?= arn:aws:kms:us-east-2:648638458147:key/a47079da-17e4-45a5-b82e-2bac101cad01 -AWS_REGION ?= us-east-2 - -all: install - -# Install dependencies using Go modules -install: - @echo "Installing Go dependencies..." - @go mod tidy - @echo "Dependencies installed successfully!" - -# Clean Go artifacts -clean: - @echo "Cleaning Go artifacts..." - @go clean - @echo "Clean completed!" - -# Run the example with default arguments -run: install - @echo "Running S3 Encryption Client v3 Go example..." - @echo "Bucket: $(BUCKET_NAME)" - @echo "Object Key: $(OBJECT_KEY)" - @echo "KMS Key ID: $(KMS_KEY_ID)" - @echo "Region: $(AWS_REGION)" - @echo "" - @go run $(SCRIPT) $(BUCKET_NAME) $(OBJECT_KEY) $(KMS_KEY_ID) $(AWS_REGION) - -# Run with custom arguments -# Usage: make run-custom BUCKET_NAME=my-bucket OBJECT_KEY=my-key KMS_KEY_ID=my-kms-key AWS_REGION=my-region -run-custom: install - @go run $(SCRIPT) $(BUCKET_NAME) $(OBJECT_KEY) $(KMS_KEY_ID) $(AWS_REGION) - -# Show help -help: - @echo "S3 Encryption Client Go v3 Example Makefile" - @echo "" - @echo "Available targets:" - @echo " install - Install Go dependencies using Go modules" - @echo " run - Install dependencies and run the example with default parameters" - @echo " run-custom - Install dependencies and run with custom parameters" - @echo " clean - Remove Go artifacts" - @echo " help - Show this help message" - @echo "" - @echo "Default parameters:" - @echo " BUCKET_NAME = $(BUCKET_NAME)" - @echo " OBJECT_KEY = $(OBJECT_KEY)" - @echo " KMS_KEY_ID = $(KMS_KEY_ID)" - @echo " AWS_REGION = $(AWS_REGION)" - @echo "" - @echo "To run with custom parameters:" - @echo " make run BUCKET_NAME=your-bucket OBJECT_KEY=your-key KMS_KEY_ID=your-kms-key AWS_REGION=your-region" - @echo "" - @echo "Prerequisites:" - @echo " - Go 1.24+ installed on the system" - @echo " - AWS credentials configured (AWS CLI, environment variables, or IAM role)" - @echo " - Valid S3 bucket and KMS key with appropriate permissions" - @echo " - S3 Encryption Client v3 Go SDK (included in local-go-s3ec)" diff --git a/all-examples/go/v3/README.md b/all-examples/go/v3/README.md deleted file mode 100644 index 519dfc36..00000000 --- a/all-examples/go/v3/README.md +++ /dev/null @@ -1,55 +0,0 @@ -# S3 Encryption Client Go v3 Example - -This example demonstrates how to use the Amazon S3 Encryption Client v3 for Go to perform client-side encryption and decryption of objects. - -## Prerequisites - -1. **Go**: Requires Go 1.24 or later -2. **AWS Credentials**: Configure your AWS credentials using one of the following methods: - - AWS CLI: `aws configure` - - Environment variables: `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY` - - IAM roles (for EC2 instances) -3. **KMS Key**: You'll need a KMS key ID or ARN. You can use the default example key: `arn:aws:kms:us-east-2:648638458147:key/a47079da-17e4-45a5-b82e-2bac101cad01` -4. **S3 Bucket**: An existing S3 bucket where you have read/write permissions - -## Setup - -1. Initialize submodules and download dependencies: - ```bash - make install - ``` - - Or manually: - ```bash - go mod tidy - ``` - - **Note**: This example uses a local submodule for the S3EC Go v3 library via the `replace` directive in `go.mod`. - -## Usage - -### Using Make (Recommended) - -Run the example with default parameters: -```bash -make run -``` - -Run with custom parameters: -```bash -make run BUCKET_NAME=my-bucket OBJECT_KEY=my-key KMS_KEY_ID=my-kms-key AWS_REGION=my-region -``` - -### Manual Usage - -Run the example with the following command: - -```bash -go run main.go -``` - -### Example: - -```bash -go run main.go my-test-bucket s3ec-go-v3-test arn:aws:kms:us-east-2:648638458147:key/a47079da-17e4-45a5-b82e-2bac101cad01 us-east-2 -``` diff --git a/all-examples/go/v3/go.mod b/all-examples/go/v3/go.mod deleted file mode 100644 index 1821569a..00000000 --- a/all-examples/go/v3/go.mod +++ /dev/null @@ -1,32 +0,0 @@ -module github.com/aws/amazon-s3-encryption-client-python/all-examples/go/v3 - -go 1.24 - -require ( - github.com/aws/amazon-s3-encryption-client-go/v3 v3.1.0 - github.com/aws/aws-sdk-go-v2 v1.24.0 - github.com/aws/aws-sdk-go-v2/config v1.26.1 - github.com/aws/aws-sdk-go-v2/service/kms v1.27.4 - github.com/aws/aws-sdk-go-v2/service/s3 v1.47.5 -) - -require ( - github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.5.4 // indirect - github.com/aws/aws-sdk-go-v2/credentials v1.16.12 // indirect - github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.10 // indirect - github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.9 // indirect - github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.9 // indirect - github.com/aws/aws-sdk-go-v2/internal/ini v1.7.2 // indirect - github.com/aws/aws-sdk-go-v2/internal/v4a v1.2.9 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.10.4 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.2.9 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.10.9 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.16.9 // indirect - github.com/aws/aws-sdk-go-v2/service/sso v1.18.5 // indirect - github.com/aws/aws-sdk-go-v2/service/ssooidc v1.21.5 // indirect - github.com/aws/aws-sdk-go-v2/service/sts v1.26.5 // indirect - github.com/aws/smithy-go v1.19.0 // indirect -) - -// S3EC Go V3 uses a local submodule for development -replace github.com/aws/amazon-s3-encryption-client-go/v3 => ./local-go-s3ec/v3 diff --git a/all-examples/go/v3/go.sum b/all-examples/go/v3/go.sum deleted file mode 100644 index 244c8814..00000000 --- a/all-examples/go/v3/go.sum +++ /dev/null @@ -1,40 +0,0 @@ -github.com/aws/aws-sdk-go-v2 v1.24.0 h1:890+mqQ+hTpNuw0gGP6/4akolQkSToDJgHfQE7AwGuk= -github.com/aws/aws-sdk-go-v2 v1.24.0/go.mod h1:LNh45Br1YAkEKaAqvmE1m8FUx6a5b/V0oAKV7of29b4= -github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.5.4 h1:OCs21ST2LrepDfD3lwlQiOqIGp6JiEUqG84GzTDoyJs= -github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.5.4/go.mod h1:usURWEKSNNAcAZuzRn/9ZYPT8aZQkR7xcCtunK/LkJo= -github.com/aws/aws-sdk-go-v2/config v1.26.1 h1:z6DqMxclFGL3Zfo+4Q0rLnAZ6yVkzCRxhRMsiRQnD1o= -github.com/aws/aws-sdk-go-v2/config v1.26.1/go.mod h1:ZB+CuKHRbb5v5F0oJtGdhFTelmrxd4iWO1lf0rQwSAg= -github.com/aws/aws-sdk-go-v2/credentials v1.16.12 h1:v/WgB8NxprNvr5inKIiVVrXPuuTegM+K8nncFkr1usU= -github.com/aws/aws-sdk-go-v2/credentials v1.16.12/go.mod h1:X21k0FjEJe+/pauud82HYiQbEr9jRKY3kXEIQ4hXeTQ= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.10 h1:w98BT5w+ao1/r5sUuiH6JkVzjowOKeOJRHERyy1vh58= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.10/go.mod h1:K2WGI7vUvkIv1HoNbfBA1bvIZ+9kL3YVmWxeKuLQsiw= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.9 h1:v+HbZaCGmOwnTTVS86Fleq0vPzOd7tnJGbFhP0stNLs= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.9/go.mod h1:Xjqy+Nyj7VDLBtCMkQYOw1QYfAEZCVLrfI0ezve8wd4= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.9 h1:N94sVhRACtXyVcjXxrwK1SKFIJrA9pOJ5yu2eSHnmls= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.9/go.mod h1:hqamLz7g1/4EJP+GH5NBhcUMLjW+gKLQabgyz6/7WAU= -github.com/aws/aws-sdk-go-v2/internal/ini v1.7.2 h1:GrSw8s0Gs/5zZ0SX+gX4zQjRnRsMJDJ2sLur1gRBhEM= -github.com/aws/aws-sdk-go-v2/internal/ini v1.7.2/go.mod h1:6fQQgfuGmw8Al/3M2IgIllycxV7ZW7WCdVSqfBeUiCY= -github.com/aws/aws-sdk-go-v2/internal/v4a v1.2.9 h1:ugD6qzjYtB7zM5PN/ZIeaAIyefPaD82G8+SJopgvUpw= -github.com/aws/aws-sdk-go-v2/internal/v4a v1.2.9/go.mod h1:YD0aYBWCrPENpHolhKw2XDlTIWae2GKXT1T4o6N6hiM= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.10.4 h1:/b31bi3YVNlkzkBrm9LfpaKoaYZUxIAj4sHfOTmLfqw= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.10.4/go.mod h1:2aGXHFmbInwgP9ZfpmdIfOELL79zhdNYNmReK8qDfdQ= -github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.2.9 h1:/90OR2XbSYfXucBMJ4U14wrjlfleq/0SB6dZDPncgmo= -github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.2.9/go.mod h1:dN/Of9/fNZet7UrQQ6kTDo/VSwKPIq94vjlU16bRARc= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.10.9 h1:Nf2sHxjMJR8CSImIVCONRi4g0Su3J+TSTbS7G0pUeMU= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.10.9/go.mod h1:idky4TER38YIjr2cADF1/ugFMKvZV7p//pVeV5LZbF0= -github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.16.9 h1:iEAeF6YC3l4FzlJPP9H3Ko1TXpdjdqWffxXjp8SY6uk= -github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.16.9/go.mod h1:kjsXoK23q9Z/tLBrckZLLyvjhZoS+AGrzqzUfEClvMM= -github.com/aws/aws-sdk-go-v2/service/kms v1.27.4 h1:c75pHGBV3h6WOsIjbJhLyOnlCPXzap45nbiP2Z5jk5M= -github.com/aws/aws-sdk-go-v2/service/kms v1.27.4/go.mod h1:D9FVDkZjkZnnFHymJ3fPVz0zOUlNSd0xcIIVmmrAac8= -github.com/aws/aws-sdk-go-v2/service/s3 v1.47.5 h1:Keso8lIOS+IzI2MkPZyK6G0LYcK3My2LQ+T5bxghEAY= -github.com/aws/aws-sdk-go-v2/service/s3 v1.47.5/go.mod h1:vADO6Jn+Rq4nDtfwNjhgR84qkZwiC6FqCaXdw/kYwjA= -github.com/aws/aws-sdk-go-v2/service/sso v1.18.5 h1:ldSFWz9tEHAwHNmjx2Cvy1MjP5/L9kNoR0skc6wyOOM= -github.com/aws/aws-sdk-go-v2/service/sso v1.18.5/go.mod h1:CaFfXLYL376jgbP7VKC96uFcU8Rlavak0UlAwk1Dlhc= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.21.5 h1:2k9KmFawS63euAkY4/ixVNsYYwrwnd5fIvgEKkfZFNM= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.21.5/go.mod h1:W+nd4wWDVkSUIox9bacmkBP5NMFQeTJ/xqNabpzSR38= -github.com/aws/aws-sdk-go-v2/service/sts v1.26.5 h1:5UYvv8JUvllZsRnfrcMQ+hJ9jNICmcgKPAO1CER25Wg= -github.com/aws/aws-sdk-go-v2/service/sts v1.26.5/go.mod h1:XX5gh4CB7wAs4KhcF46G6C8a2i7eupU19dcAAE+EydU= -github.com/aws/smithy-go v1.19.0 h1:KWFKQV80DpP3vJrrA9sVAHQ5gc2z8i4EzrLhLlWXcBM= -github.com/aws/smithy-go v1.19.0/go.mod h1:NukqUGpCZIILqqiV0NIjeFh24kd/FAa4beRb6nbIUPE= -github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= -github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= diff --git a/all-examples/go/v3/local-go-s3ec b/all-examples/go/v3/local-go-s3ec deleted file mode 120000 index 7e5e770c..00000000 --- a/all-examples/go/v3/local-go-s3ec +++ /dev/null @@ -1 +0,0 @@ -../../../test-server/go-v3-transition-server/local-go-s3ec \ No newline at end of file diff --git a/all-examples/go/v3/main.go b/all-examples/go/v3/main.go deleted file mode 100644 index 22732bc8..00000000 --- a/all-examples/go/v3/main.go +++ /dev/null @@ -1,171 +0,0 @@ -package main - -import ( - "context" - "fmt" - "io" - "os" - "strings" - - "github.com/aws/amazon-s3-encryption-client-go/v3/client" - "github.com/aws/amazon-s3-encryption-client-go/v3/commitment" - "github.com/aws/amazon-s3-encryption-client-go/v3/materials" - "github.com/aws/aws-sdk-go-v2/aws" - "github.com/aws/aws-sdk-go-v2/config" - "github.com/aws/aws-sdk-go-v2/service/kms" - "github.com/aws/aws-sdk-go-v2/service/s3" -) - -func main() { - // Check command line arguments - if len(os.Args) != 5 { - fmt.Printf("Usage: %s \n", os.Args[0]) - fmt.Printf("Example: %s avp-21638 s3ec-go-v3 arn:aws:kms:us-east-2:648638458147:key/a47079da-17e4-45a5-b82e-2bac101cad01 us-east-2\n", os.Args[0]) - os.Exit(1) - } - - bucketName := os.Args[1] - objectKey := os.Args[2] - kmsKeyID := os.Args[3] - region := os.Args[4] - - fmt.Println("=== S3 Encryption Client v3 Example (Go) ===") - fmt.Printf("Bucket: %s\n", bucketName) - fmt.Printf("Object Key: %s\n", objectKey) - fmt.Printf("KMS Key ID: %s\n", kmsKeyID) - fmt.Printf("Region: %s\n", region) - fmt.Println() - - // Test data for encryption - testData := "Hello, World! This is a test message for S3 encryption client v3 in Go." - fmt.Printf("Original data: %s\n", testData) - fmt.Printf("Data length: %d bytes\n", len(testData)) - fmt.Println() - - fmt.Println("--- Initialize S3 Encryption Client v3 ---") - - // Create regular S3 client - cfg, err := config.LoadDefaultConfig(context.TODO(), config.WithRegion(region)) - if err != nil { - fmt.Printf("Error loading AWS config: %v\n", err) - os.Exit(1) - } - s3Client := s3.NewFromConfig(cfg) - - // Create KMS client - kmsClient := kms.NewFromConfig(cfg) - - // Create KMS keyring - keyring := materials.NewKmsKeyring(kmsClient, kmsKeyID) - - // Create Cryptographic Materials Manager - cmm, err := materials.NewCryptographicMaterialsManager(keyring) - if err != nil { - fmt.Printf("Error creating CMM: %v\n", err) - os.Exit(1) - } - - // Create S3 Encryption Client v3 - encryptionClient, err := client.New(s3Client, cmm, func(options *client.EncryptionClientOptions) { - options.CommitmentPolicy = commitment.FORBID_ENCRYPT_ALLOW_DECRYPT - }) - if err != nil { - fmt.Printf("Error creating S3 Encryption Client: %v\n", err) - os.Exit(1) - } - - fmt.Println("Successfully initialized S3 Encryption Client v3") - fmt.Println("--- Encrypt and Upload Object to S3 ---") - - // Add encryption context - encryptionContext := map[string]string{ - "purpose": "example", - "version": "v3", - "language": "go", - } - - // Create context with encryption context - ctx := context.WithValue(context.Background(), "EncryptionContext", encryptionContext) - - // Upload encrypted object using S3 Encryption Client - putInput := &s3.PutObjectInput{ - Bucket: aws.String(bucketName), - Key: aws.String(objectKey), - Body: strings.NewReader(testData), - } - - _, err = encryptionClient.PutObject(ctx, putInput) - if err != nil { - if strings.Contains(err.Error(), "NoSuchBucket") { - fmt.Printf("Error: S3 bucket '%s' does not exist or is not accessible\n", bucketName) - } else if strings.Contains(err.Error(), "NotFoundException") { - fmt.Printf("Error: KMS key '%s' not found or not accessible\n", kmsKeyID) - } else { - fmt.Printf("Error uploading encrypted object: %v\n", err) - } - os.Exit(1) - } - - fmt.Println("Successfully uploaded encrypted object to S3!") - fmt.Printf(" Bucket: %s\n", bucketName) - fmt.Printf(" Key: %s\n", objectKey) - fmt.Printf(" Encryption Context: %v\n", encryptionContext) - fmt.Println() - - fmt.Println("--- Download and Decrypt Object from S3 ---") - - // Download and decrypt object using S3 Encryption Client - getInput := &s3.GetObjectInput{ - Bucket: aws.String(bucketName), - Key: aws.String(objectKey), - } - - getResponse, err := encryptionClient.GetObject(ctx, getInput) - if err != nil { - fmt.Printf("Error downloading and decrypting object: %v\n", err) - os.Exit(1) - } - defer getResponse.Body.Close() - - // Read the decrypted data - decryptedData, err := io.ReadAll(getResponse.Body) - if err != nil { - fmt.Printf("Error reading decrypted data: %v\n", err) - os.Exit(1) - } - - fmt.Println("Successfully downloaded and decrypted object from S3!") - fmt.Printf(" Object size: %d bytes\n", len(decryptedData)) - fmt.Printf(" Decrypted data: %s\n", string(decryptedData)) - fmt.Println() - - fmt.Println("--- Verify Roundtrip Success ---") - - // Verify the roundtrip was successful - if string(decryptedData) == testData { - fmt.Println("SUCCESS: Roundtrip encryption/decryption completed successfully!") - fmt.Println(" Original data matches decrypted data") - fmt.Println(" Data integrity verified") - } else { - fmt.Println("ERROR: Roundtrip failed - data mismatch") - fmt.Printf(" Original: %s\n", testData) - fmt.Printf(" Decrypted: %s\n", string(decryptedData)) - os.Exit(1) - } - - // Optionally Delete the Object - //fmt.Println("--- Cleanup ---") - // Clean up the test object using regular S3 client - // _, err = s3Client.DeleteObject(context.TODO(), &s3.DeleteObjectInput{ - // Bucket: aws.String(bucketName), - // Key: aws.String(objectKey), - // }) - // if err != nil { - // fmt.Printf("Error deleting test object: %v\n", err) - // } else { - // fmt.Println("Test object deleted from S3") - // } - - fmt.Println() - fmt.Println("=== Example completed successfully! ===") -} diff --git a/all-examples/go/v4/Makefile b/all-examples/go/v4/Makefile deleted file mode 100644 index 1e8307e7..00000000 --- a/all-examples/go/v4/Makefile +++ /dev/null @@ -1,69 +0,0 @@ -# Makefile for S3 Encryption Client Go v4 Example - -# Default target -.PHONY: all install clean run help - -# Variables -SCRIPT = main.go - -# Default arguments for running the example -# Override these when calling make run -BUCKET_NAME ?= avp-21638 -OBJECT_KEY ?= s3ec-go-v4 -KMS_KEY_ID ?= arn:aws:kms:us-east-2:648638458147:key/a47079da-17e4-45a5-b82e-2bac101cad01 -AWS_REGION ?= us-east-2 - -all: install - -# Install dependencies using Go modules -install: - @echo "Installing Go dependencies..." - @go mod tidy - @echo "Dependencies installed successfully!" - -# Clean Go artifacts -clean: - @echo "Cleaning Go artifacts..." - @go clean - @echo "Clean completed!" - -# Run the example with default arguments -run: install - @echo "Running S3 Encryption Client v4 Go example..." - @echo "Bucket: $(BUCKET_NAME)" - @echo "Object Key: $(OBJECT_KEY)" - @echo "KMS Key ID: $(KMS_KEY_ID)" - @echo "Region: $(AWS_REGION)" - @echo "" - @go run $(SCRIPT) $(BUCKET_NAME) $(OBJECT_KEY) $(KMS_KEY_ID) $(AWS_REGION) - -# Run with custom arguments -# Usage: make run-custom BUCKET_NAME=my-bucket OBJECT_KEY=my-key KMS_KEY_ID=my-kms-key AWS_REGION=my-region -run-custom: install - @go run $(SCRIPT) $(BUCKET_NAME) $(OBJECT_KEY) $(KMS_KEY_ID) $(AWS_REGION) - -# Show help -help: - @echo "S3 Encryption Client Go v4 Example Makefile" - @echo "" - @echo "Available targets:" - @echo " install - Install Go dependencies using Go modules" - @echo " run - Install dependencies and run the example with default parameters" - @echo " run-custom - Install dependencies and run with custom parameters" - @echo " clean - Remove Go artifacts" - @echo " help - Show this help message" - @echo "" - @echo "Default parameters:" - @echo " BUCKET_NAME = $(BUCKET_NAME)" - @echo " OBJECT_KEY = $(OBJECT_KEY)" - @echo " KMS_KEY_ID = $(KMS_KEY_ID)" - @echo " AWS_REGION = $(AWS_REGION)" - @echo "" - @echo "To run with custom parameters:" - @echo " make run BUCKET_NAME=your-bucket OBJECT_KEY=your-key KMS_KEY_ID=your-kms-key AWS_REGION=your-region" - @echo "" - @echo "Prerequisites:" - @echo " - Go 1.24+ installed on the system" - @echo " - AWS credentials configured (AWS CLI, environment variables, or IAM role)" - @echo " - Valid S3 bucket and KMS key with appropriate permissions" - @echo " - S3 Encryption Client v4 Go SDK (included in local-go-s3ec)" diff --git a/all-examples/go/v4/README.md b/all-examples/go/v4/README.md deleted file mode 100644 index b6972f26..00000000 --- a/all-examples/go/v4/README.md +++ /dev/null @@ -1,55 +0,0 @@ -# S3 Encryption Client Go v4 Example - -This example demonstrates how to use the Amazon S3 Encryption Client v4 for Go to perform client-side encryption and decryption of objects. - -## Prerequisites - -1. **Go**: Requires Go 1.24 or later -2. **AWS Credentials**: Configure your AWS credentials using one of the following methods: - - AWS CLI: `aws configure` - - Environment variables: `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY` - - IAM roles (for EC2 instances) -3. **KMS Key**: You'll need a KMS key ID or ARN. You can use the default example key: `arn:aws:kms:us-east-2:648638458147:key/a47079da-17e4-45a5-b82e-2bac101cad01` -4. **S3 Bucket**: An existing S3 bucket where you have read/write permissions - -## Setup - -1. Initialize submodules and download dependencies: - ```bash - make install - ``` - - Or manually: - ```bash - go mod tidy - ``` - - **Note**: This example uses a local submodule for the S3EC Go v4 library via the `replace` directive in `go.mod`. - -## Usage - -### Using Make (Recommended) - -Run the example with default parameters: -```bash -make run -``` - -Run with custom parameters: -```bash -make run BUCKET_NAME=my-bucket OBJECT_KEY=my-key KMS_KEY_ID=my-kms-key AWS_REGION=my-region -``` - -### Manual Usage - -Run the example with the following command: - -```bash -go run main.go -``` - -### Example: - -```bash -go run main.go my-test-bucket s3ec-go-v4-test arn:aws:kms:us-east-2:648638458147:key/a47079da-17e4-45a5-b82e-2bac101cad01 us-east-2 -``` diff --git a/all-examples/go/v4/go.mod b/all-examples/go/v4/go.mod deleted file mode 100644 index 48bea56e..00000000 --- a/all-examples/go/v4/go.mod +++ /dev/null @@ -1,32 +0,0 @@ -module github.com/aws/amazon-s3-encryption-client-python/all-examples/go/v4 - -go 1.24 - -require ( - github.com/aws/amazon-s3-encryption-client-go/v4 v4.0.0 - github.com/aws/aws-sdk-go-v2 v1.24.0 - github.com/aws/aws-sdk-go-v2/config v1.26.1 - github.com/aws/aws-sdk-go-v2/service/kms v1.27.4 - github.com/aws/aws-sdk-go-v2/service/s3 v1.47.5 -) - -require ( - github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.5.4 // indirect - github.com/aws/aws-sdk-go-v2/credentials v1.16.12 // indirect - github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.10 // indirect - github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.9 // indirect - github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.9 // indirect - github.com/aws/aws-sdk-go-v2/internal/ini v1.7.2 // indirect - github.com/aws/aws-sdk-go-v2/internal/v4a v1.2.9 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.10.4 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.2.9 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.10.9 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.16.9 // indirect - github.com/aws/aws-sdk-go-v2/service/sso v1.18.5 // indirect - github.com/aws/aws-sdk-go-v2/service/ssooidc v1.21.5 // indirect - github.com/aws/aws-sdk-go-v2/service/sts v1.26.5 // indirect - github.com/aws/smithy-go v1.19.0 // indirect -) - -// S3EC Go V4 uses a local submodule for development -replace github.com/aws/amazon-s3-encryption-client-go/v4 => ./local-go-s3ec/v4 diff --git a/all-examples/go/v4/go.sum b/all-examples/go/v4/go.sum deleted file mode 100644 index 244c8814..00000000 --- a/all-examples/go/v4/go.sum +++ /dev/null @@ -1,40 +0,0 @@ -github.com/aws/aws-sdk-go-v2 v1.24.0 h1:890+mqQ+hTpNuw0gGP6/4akolQkSToDJgHfQE7AwGuk= -github.com/aws/aws-sdk-go-v2 v1.24.0/go.mod h1:LNh45Br1YAkEKaAqvmE1m8FUx6a5b/V0oAKV7of29b4= -github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.5.4 h1:OCs21ST2LrepDfD3lwlQiOqIGp6JiEUqG84GzTDoyJs= -github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.5.4/go.mod h1:usURWEKSNNAcAZuzRn/9ZYPT8aZQkR7xcCtunK/LkJo= -github.com/aws/aws-sdk-go-v2/config v1.26.1 h1:z6DqMxclFGL3Zfo+4Q0rLnAZ6yVkzCRxhRMsiRQnD1o= -github.com/aws/aws-sdk-go-v2/config v1.26.1/go.mod h1:ZB+CuKHRbb5v5F0oJtGdhFTelmrxd4iWO1lf0rQwSAg= -github.com/aws/aws-sdk-go-v2/credentials v1.16.12 h1:v/WgB8NxprNvr5inKIiVVrXPuuTegM+K8nncFkr1usU= -github.com/aws/aws-sdk-go-v2/credentials v1.16.12/go.mod h1:X21k0FjEJe+/pauud82HYiQbEr9jRKY3kXEIQ4hXeTQ= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.10 h1:w98BT5w+ao1/r5sUuiH6JkVzjowOKeOJRHERyy1vh58= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.10/go.mod h1:K2WGI7vUvkIv1HoNbfBA1bvIZ+9kL3YVmWxeKuLQsiw= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.9 h1:v+HbZaCGmOwnTTVS86Fleq0vPzOd7tnJGbFhP0stNLs= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.9/go.mod h1:Xjqy+Nyj7VDLBtCMkQYOw1QYfAEZCVLrfI0ezve8wd4= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.9 h1:N94sVhRACtXyVcjXxrwK1SKFIJrA9pOJ5yu2eSHnmls= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.9/go.mod h1:hqamLz7g1/4EJP+GH5NBhcUMLjW+gKLQabgyz6/7WAU= -github.com/aws/aws-sdk-go-v2/internal/ini v1.7.2 h1:GrSw8s0Gs/5zZ0SX+gX4zQjRnRsMJDJ2sLur1gRBhEM= -github.com/aws/aws-sdk-go-v2/internal/ini v1.7.2/go.mod h1:6fQQgfuGmw8Al/3M2IgIllycxV7ZW7WCdVSqfBeUiCY= -github.com/aws/aws-sdk-go-v2/internal/v4a v1.2.9 h1:ugD6qzjYtB7zM5PN/ZIeaAIyefPaD82G8+SJopgvUpw= -github.com/aws/aws-sdk-go-v2/internal/v4a v1.2.9/go.mod h1:YD0aYBWCrPENpHolhKw2XDlTIWae2GKXT1T4o6N6hiM= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.10.4 h1:/b31bi3YVNlkzkBrm9LfpaKoaYZUxIAj4sHfOTmLfqw= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.10.4/go.mod h1:2aGXHFmbInwgP9ZfpmdIfOELL79zhdNYNmReK8qDfdQ= -github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.2.9 h1:/90OR2XbSYfXucBMJ4U14wrjlfleq/0SB6dZDPncgmo= -github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.2.9/go.mod h1:dN/Of9/fNZet7UrQQ6kTDo/VSwKPIq94vjlU16bRARc= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.10.9 h1:Nf2sHxjMJR8CSImIVCONRi4g0Su3J+TSTbS7G0pUeMU= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.10.9/go.mod h1:idky4TER38YIjr2cADF1/ugFMKvZV7p//pVeV5LZbF0= -github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.16.9 h1:iEAeF6YC3l4FzlJPP9H3Ko1TXpdjdqWffxXjp8SY6uk= -github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.16.9/go.mod h1:kjsXoK23q9Z/tLBrckZLLyvjhZoS+AGrzqzUfEClvMM= -github.com/aws/aws-sdk-go-v2/service/kms v1.27.4 h1:c75pHGBV3h6WOsIjbJhLyOnlCPXzap45nbiP2Z5jk5M= -github.com/aws/aws-sdk-go-v2/service/kms v1.27.4/go.mod h1:D9FVDkZjkZnnFHymJ3fPVz0zOUlNSd0xcIIVmmrAac8= -github.com/aws/aws-sdk-go-v2/service/s3 v1.47.5 h1:Keso8lIOS+IzI2MkPZyK6G0LYcK3My2LQ+T5bxghEAY= -github.com/aws/aws-sdk-go-v2/service/s3 v1.47.5/go.mod h1:vADO6Jn+Rq4nDtfwNjhgR84qkZwiC6FqCaXdw/kYwjA= -github.com/aws/aws-sdk-go-v2/service/sso v1.18.5 h1:ldSFWz9tEHAwHNmjx2Cvy1MjP5/L9kNoR0skc6wyOOM= -github.com/aws/aws-sdk-go-v2/service/sso v1.18.5/go.mod h1:CaFfXLYL376jgbP7VKC96uFcU8Rlavak0UlAwk1Dlhc= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.21.5 h1:2k9KmFawS63euAkY4/ixVNsYYwrwnd5fIvgEKkfZFNM= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.21.5/go.mod h1:W+nd4wWDVkSUIox9bacmkBP5NMFQeTJ/xqNabpzSR38= -github.com/aws/aws-sdk-go-v2/service/sts v1.26.5 h1:5UYvv8JUvllZsRnfrcMQ+hJ9jNICmcgKPAO1CER25Wg= -github.com/aws/aws-sdk-go-v2/service/sts v1.26.5/go.mod h1:XX5gh4CB7wAs4KhcF46G6C8a2i7eupU19dcAAE+EydU= -github.com/aws/smithy-go v1.19.0 h1:KWFKQV80DpP3vJrrA9sVAHQ5gc2z8i4EzrLhLlWXcBM= -github.com/aws/smithy-go v1.19.0/go.mod h1:NukqUGpCZIILqqiV0NIjeFh24kd/FAa4beRb6nbIUPE= -github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= -github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= diff --git a/all-examples/go/v4/local-go-s3ec b/all-examples/go/v4/local-go-s3ec deleted file mode 120000 index d06737d9..00000000 --- a/all-examples/go/v4/local-go-s3ec +++ /dev/null @@ -1 +0,0 @@ -../../../test-server/go-v4-server/local-go-s3ec \ No newline at end of file diff --git a/all-examples/go/v4/main.go b/all-examples/go/v4/main.go deleted file mode 100644 index a1227790..00000000 --- a/all-examples/go/v4/main.go +++ /dev/null @@ -1,171 +0,0 @@ -package main - -import ( - "context" - "fmt" - "io" - "os" - "strings" - - "github.com/aws/amazon-s3-encryption-client-go/v4/client" - "github.com/aws/amazon-s3-encryption-client-go/v4/commitment" - "github.com/aws/amazon-s3-encryption-client-go/v4/materials" - "github.com/aws/aws-sdk-go-v2/aws" - "github.com/aws/aws-sdk-go-v2/config" - "github.com/aws/aws-sdk-go-v2/service/kms" - "github.com/aws/aws-sdk-go-v2/service/s3" -) - -func main() { - // Check command line arguments - if len(os.Args) != 5 { - fmt.Printf("Usage: %s \n", os.Args[0]) - fmt.Printf("Example: %s avp-21638 s3ec-go-v4 arn:aws:kms:us-east-2:648638458147:key/a47079da-17e4-45a5-b82e-2bac101cad01 us-east-2\n", os.Args[0]) - os.Exit(1) - } - - bucketName := os.Args[1] - objectKey := os.Args[2] - kmsKeyID := os.Args[3] - region := os.Args[4] - - fmt.Println("=== S3 Encryption Client v4 Example (Go) ===") - fmt.Printf("Bucket: %s\n", bucketName) - fmt.Printf("Object Key: %s\n", objectKey) - fmt.Printf("KMS Key ID: %s\n", kmsKeyID) - fmt.Printf("Region: %s\n", region) - fmt.Println() - - // Test data for encryption - testData := "Hello, World! This is a test message for S3 encryption client v4 in Go." - fmt.Printf("Original data: %s\n", testData) - fmt.Printf("Data length: %d bytes\n", len(testData)) - fmt.Println() - - fmt.Println("--- Initialize S3 Encryption Client v4 ---") - - // Create regular S3 client - cfg, err := config.LoadDefaultConfig(context.TODO(), config.WithRegion(region)) - if err != nil { - fmt.Printf("Error loading AWS config: %v\n", err) - os.Exit(1) - } - s3Client := s3.NewFromConfig(cfg) - - // Create KMS client - kmsClient := kms.NewFromConfig(cfg) - - // Create KMS keyring - keyring := materials.NewKmsKeyring(kmsClient, kmsKeyID) - - // Create Cryptographic Materials Manager - cmm, err := materials.NewCryptographicMaterialsManager(keyring) - if err != nil { - fmt.Printf("Error creating CMM: %v\n", err) - os.Exit(1) - } - - // Create S3 Encryption Client v4 - encryptionClient, err := client.New(s3Client, cmm, func(options *client.EncryptionClientOptions) { - options.CommitmentPolicy = commitment.REQUIRE_ENCRYPT_ALLOW_DECRYPT - }) - if err != nil { - fmt.Printf("Error creating S3 Encryption Client: %v\n", err) - os.Exit(1) - } - - fmt.Println("Successfully initialized S3 Encryption Client v4") - fmt.Println("--- Encrypt and Upload Object to S3 ---") - - // Add encryption context - encryptionContext := map[string]string{ - "purpose": "example", - "version": "v4", - "language": "go", - } - - // Create context with encryption context - ctx := context.WithValue(context.Background(), "EncryptionContext", encryptionContext) - - // Upload encrypted object using S3 Encryption Client - putInput := &s3.PutObjectInput{ - Bucket: aws.String(bucketName), - Key: aws.String(objectKey), - Body: strings.NewReader(testData), - } - - _, err = encryptionClient.PutObject(ctx, putInput) - if err != nil { - if strings.Contains(err.Error(), "NoSuchBucket") { - fmt.Printf("Error: S3 bucket '%s' does not exist or is not accessible\n", bucketName) - } else if strings.Contains(err.Error(), "NotFoundException") { - fmt.Printf("Error: KMS key '%s' not found or not accessible\n", kmsKeyID) - } else { - fmt.Printf("Error uploading encrypted object: %v\n", err) - } - os.Exit(1) - } - - fmt.Println("Successfully uploaded encrypted object to S3!") - fmt.Printf(" Bucket: %s\n", bucketName) - fmt.Printf(" Key: %s\n", objectKey) - fmt.Printf(" Encryption Context: %v\n", encryptionContext) - fmt.Println() - - fmt.Println("--- Download and Decrypt Object from S3 ---") - - // Download and decrypt object using S3 Encryption Client - getInput := &s3.GetObjectInput{ - Bucket: aws.String(bucketName), - Key: aws.String(objectKey), - } - - getResponse, err := encryptionClient.GetObject(ctx, getInput) - if err != nil { - fmt.Printf("Error downloading and decrypting object: %v\n", err) - os.Exit(1) - } - defer getResponse.Body.Close() - - // Read the decrypted data - decryptedData, err := io.ReadAll(getResponse.Body) - if err != nil { - fmt.Printf("Error reading decrypted data: %v\n", err) - os.Exit(1) - } - - fmt.Println("Successfully downloaded and decrypted object from S3!") - fmt.Printf(" Object size: %d bytes\n", len(decryptedData)) - fmt.Printf(" Decrypted data: %s\n", string(decryptedData)) - fmt.Println() - - fmt.Println("--- Verify Roundtrip Success ---") - - // Verify the roundtrip was successful - if string(decryptedData) == testData { - fmt.Println("SUCCESS: Roundtrip encryption/decryption completed successfully!") - fmt.Println(" Original data matches decrypted data") - fmt.Println(" Data integrity verified") - } else { - fmt.Println("ERROR: Roundtrip failed - data mismatch") - fmt.Printf(" Original: %s\n", testData) - fmt.Printf(" Decrypted: %s\n", string(decryptedData)) - os.Exit(1) - } - - // Optionally Delete the Object - //fmt.Println("--- Cleanup ---") - // Clean up the test object using regular S3 client - // _, err = s3Client.DeleteObject(context.TODO(), &s3.DeleteObjectInput{ - // Bucket: aws.String(bucketName), - // Key: aws.String(objectKey), - // }) - // if err != nil { - // fmt.Printf("Error deleting test object: %v\n", err) - // } else { - // fmt.Println("Test object deleted from S3") - // } - - fmt.Println() - fmt.Println("=== Example completed successfully! ===") -} diff --git a/all-examples/java/v3/Makefile b/all-examples/java/v3/Makefile deleted file mode 100644 index 81e4228d..00000000 --- a/all-examples/java/v3/Makefile +++ /dev/null @@ -1,75 +0,0 @@ -# Makefile for S3 Encryption Client Java v3 Example - -# Default target -.PHONY: all install clean run help s3ec-staging - -# Default arguments for running the example -# Override these when calling make run -BUCKET_NAME ?= avp-21638 -OBJECT_KEY ?= s3ec-java-v3 -KMS_KEY_ID ?= arn:aws:kms:us-east-2:648638458147:key/a47079da-17e4-45a5-b82e-2bac101cad01 -AWS_REGION ?= us-east-2 - -all: install - -# Install S3 Encryption Client library from source -s3ec-staging: - @echo "[JAVA V3] Installing S3 Encryption Client library from source..." - @cd s3ec-staging && mvn -B -ntp install -DskipTests - @echo "[JAVA V3] S3 Encryption Client library installed successfully!" - -# Install dependencies using Gradle -install: s3ec-staging - @echo "[JAVA V3] Installing Java dependencies..." - @chmod +x ./gradlew - @./gradlew build - @echo "[JAVA V3] Dependencies installed successfully!" - -# Clean Gradle artifacts -clean: - @echo "[JAVA V3] Cleaning Gradle artifacts..." - @./gradlew clean - @echo "[JAVA V3] Clean completed!" - -# Run the example with default arguments -run: install - @echo "[JAVA V3] Running S3 Encryption Client v3 Java example..." - @echo "[JAVA V3] Bucket: $(BUCKET_NAME)" - @echo "[JAVA V3] Object Key: $(OBJECT_KEY)" - @echo "[JAVA V3] KMS Key ID: $(KMS_KEY_ID)" - @echo "[JAVA V3] Region: $(AWS_REGION)" - @echo "" - @./gradlew run --args="$(BUCKET_NAME) $(OBJECT_KEY) $(KMS_KEY_ID) $(AWS_REGION)" - -# Run with custom arguments -# Usage: make run-custom BUCKET_NAME=my-bucket OBJECT_KEY=my-key KMS_KEY_ID=my-kms-key AWS_REGION=my-region -run-custom: install - @./gradlew run --args="$(BUCKET_NAME) $(OBJECT_KEY) $(KMS_KEY_ID) $(AWS_REGION)" - -# Show help -help: - @echo "S3 Encryption Client Java v3 Example Makefile" - @echo "" - @echo "Available targets:" - @echo " s3ec-staging - Install S3 Encryption Client library from source" - @echo " install - Install Java dependencies using Gradle" - @echo " run - Install dependencies and run the example with default parameters" - @echo " run-custom - Install dependencies and run with custom parameters" - @echo " clean - Remove Gradle artifacts" - @echo " help - Show this help message" - @echo "" - @echo "Default parameters:" - @echo " BUCKET_NAME = $(BUCKET_NAME)" - @echo " OBJECT_KEY = $(OBJECT_KEY)" - @echo " KMS_KEY_ID = $(KMS_KEY_ID)" - @echo " AWS_REGION = $(AWS_REGION)" - @echo "" - @echo "To run with custom parameters:" - @echo " make run BUCKET_NAME=your-bucket OBJECT_KEY=your-key KMS_KEY_ID=your-kms-key AWS_REGION=your-region" - @echo "" - @echo "Prerequisites:" - @echo " - Java 11+ installed on the system" - @echo " - AWS credentials configured (AWS CLI, environment variables, or IAM role)" - @echo " - Valid S3 bucket and KMS key with appropriate permissions" - @echo " - S3 Encryption Client v3 library installed in local Maven repository" - @echo " (Install by running: cd s3ec-staging && mvn install)" diff --git a/all-examples/java/v3/README.md b/all-examples/java/v3/README.md deleted file mode 100644 index 50e67684..00000000 --- a/all-examples/java/v3/README.md +++ /dev/null @@ -1,57 +0,0 @@ -# S3 Encryption Client Java v3 Example - -This example demonstrates how to use the Amazon S3 Encryption Client v3 for Java to perform client-side encryption and decryption of objects. - -## Prerequisites - -1. **Java**: Requires Java 11 or later -2. **Gradle**: The project uses Gradle wrapper (included - `./gradlew`) -3. **Maven**: Required to install the S3 Encryption Client library from source -4. **AWS Credentials**: Configure your AWS credentials using one of the following methods: - - AWS CLI: `aws configure` - - Environment variables: `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY` - - IAM roles (for EC2 instances) -5. **KMS Key**: You'll need a KMS key ID or ARN. You can use the default example key: `arn:aws:kms:us-east-2:648638458147:key/a47079da-17e4-45a5-b82e-2bac101cad01` -6. **S3 Bucket**: An existing S3 bucket where you have read/write permissions - -## Setup - -Install dependencies and build (this automatically installs the S3 Encryption Client library from source): -```bash -make install -``` - -Or manually: -```bash -cd s3ec-staging && mvn clean install && cd - -./gradlew build -``` - -**Note**: This example uses a local library installed in Maven local repository via the symbolic link `s3ec-staging`. - -## Usage - -### Using Make (Recommended) - -Run the example with default parameters: -```bash -make run -``` - -Run with custom parameters: -```bash -make run BUCKET_NAME=my-bucket OBJECT_KEY=my-key KMS_KEY_ID=my-kms-key AWS_REGION=my-region -``` - -### Manual Usage - -Run the example with the following command: - -```bash -./gradlew run --args=" " -``` - -### Example: - -```bash -./gradlew run --args="my-test-bucket s3ec-java-v3-test arn:aws:kms:us-east-2:648638458147:key/a47079da-17e4-45a5-b82e-2bac101cad01 us-east-2" diff --git a/all-examples/java/v3/build.gradle.kts b/all-examples/java/v3/build.gradle.kts deleted file mode 100644 index f0748625..00000000 --- a/all-examples/java/v3/build.gradle.kts +++ /dev/null @@ -1,47 +0,0 @@ -plugins { - java - application -} - -group = "software.amazon.encryption.s3.example" -version = "1.0.0" - -repositories { - mavenLocal() - mavenCentral() -} - -dependencies { - // AWS SDK v2 dependencies - implementation(platform("software.amazon.awssdk:bom:2.20.0")) - implementation("software.amazon.awssdk:s3") - implementation("software.amazon.awssdk:kms") - implementation("software.amazon.awssdk:auth") - - // S3 Encryption Client v3 from local Maven repository - implementation("software.amazon.encryption.s3:amazon-s3-encryption-client-java:3.4.0-read-kc") -} - -application { - mainClass.set("software.amazon.encryption.s3.example.Main") -} - -java { - sourceCompatibility = JavaVersion.VERSION_11 - targetCompatibility = JavaVersion.VERSION_11 -} - -tasks.jar { - manifest { - attributes["Main-Class"] = "software.amazon.encryption.s3.example.Main" - } - - // Create a fat jar with all dependencies - from(configurations.runtimeClasspath.get().map { if (it.isDirectory) it else zipTree(it) }) - duplicatesStrategy = DuplicatesStrategy.EXCLUDE - archiveBaseName.set("s3ec-java-v3-example") -} - -tasks.named("run") { - standardInput = System.`in` -} diff --git a/all-examples/java/v3/gradle/wrapper/gradle-wrapper.jar b/all-examples/java/v3/gradle/wrapper/gradle-wrapper.jar deleted file mode 100644 index e6441136f3d4ba8a0da8d277868979cfbc8ad796..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 43453 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 yu?e%!_ptzGB|8GrCfu%p?(_ zQccdaaVK$5bz;*rnyK{_SQYM>;aES6Qs^lj9lEs6_J+%nIiuQC*fN;z8md>r_~Mfl zU%p5Dt_YT>gQqfr@`cR!$NWr~+`CZb%dn;WtzrAOI>P_JtsB76PYe*<%H(y>qx-`Kq!X_; z<{RpAqYhE=L1r*M)gNF3B8r(<%8mo*SR2hu zccLRZwGARt)Hlo1euqTyM>^!HK*!Q2P;4UYrysje@;(<|$&%vQekbn|0Ruu_Io(w4#%p6ld2Yp7tlA`Y$cciThP zKzNGIMPXX%&Ud0uQh!uQZz|FB`4KGD?3!ND?wQt6!n*f4EmCoJUh&b?;B{|lxs#F- z31~HQ`SF4x$&v00@(P+j1pAaj5!s`)b2RDBp*PB=2IB>oBF!*6vwr7Dp%zpAx*dPr zb@Zjq^XjN?O4QcZ*O+8>)|HlrR>oD*?WQl5ri3R#2?*W6iJ>>kH%KnnME&TT@ZzrHS$Q%LC?n|e>V+D+8D zYc4)QddFz7I8#}y#Wj6>4P%34dZH~OUDb?uP%-E zwjXM(?Sg~1!|wI(RVuxbu)-rH+O=igSho_pDCw(c6b=P zKk4ATlB?bj9+HHlh<_!&z0rx13K3ZrAR8W)!@Y}o`?a*JJsD+twZIv`W)@Y?Amu_u zz``@-e2X}27$i(2=9rvIu5uTUOVhzwu%mNazS|lZb&PT;XE2|B&W1>=B58#*!~D&) zfVmJGg8UdP*fx(>Cj^?yS^zH#o-$Q-*$SnK(ZVFkw+er=>N^7!)FtP3y~Xxnu^nzY zikgB>Nj0%;WOltWIob|}%lo?_C7<``a5hEkx&1ku$|)i>Rh6@3h*`slY=9U}(Ql_< zaNG*J8vb&@zpdhAvv`?{=zDedJ23TD&Zg__snRAH4eh~^oawdYi6A3w8<Ozh@Kw)#bdktM^GVb zrG08?0bG?|NG+w^&JvD*7LAbjED{_Zkc`3H!My>0u5Q}m!+6VokMLXxl`Mkd=g&Xx z-a>m*#G3SLlhbKB!)tnzfWOBV;u;ftU}S!NdD5+YtOjLg?X}dl>7m^gOpihrf1;PY zvll&>dIuUGs{Qnd- zwIR3oIrct8Va^Tm0t#(bJD7c$Z7DO9*7NnRZorrSm`b`cxz>OIC;jSE3DO8`hX955ui`s%||YQtt2 z5DNA&pG-V+4oI2s*x^>-$6J?p=I>C|9wZF8z;VjR??Icg?1w2v5Me+FgAeGGa8(3S z4vg*$>zC-WIVZtJ7}o9{D-7d>zCe|z#<9>CFve-OPAYsneTb^JH!Enaza#j}^mXy1 z+ULn^10+rWLF6j2>Ya@@Kq?26>AqK{A_| zQKb*~F1>sE*=d?A?W7N2j?L09_7n+HGi{VY;MoTGr_)G9)ot$p!-UY5zZ2Xtbm=t z@dpPSGwgH=QtIcEulQNI>S-#ifbnO5EWkI;$A|pxJd885oM+ zGZ0_0gDvG8q2xebj+fbCHYfAXuZStH2j~|d^sBAzo46(K8n59+T6rzBwK)^rfPT+B zyIFw)9YC-V^rhtK`!3jrhmW-sTmM+tPH+;nwjL#-SjQPUZ53L@A>y*rt(#M(qsiB2 zx6B)dI}6Wlsw%bJ8h|(lhkJVogQZA&n{?Vgs6gNSXzuZpEyu*xySy8ro07QZ7Vk1!3tJphN_5V7qOiyK8p z#@jcDD8nmtYi1^l8ml;AF<#IPK?!pqf9D4moYk>d99Im}Jtwj6c#+A;f)CQ*f-hZ< z=p_T86jog%!p)D&5g9taSwYi&eP z#JuEK%+NULWus;0w32-SYFku#i}d~+{Pkho&^{;RxzP&0!RCm3-9K6`>KZpnzS6?L z^H^V*s!8<>x8bomvD%rh>Zp3>Db%kyin;qtl+jAv8Oo~1g~mqGAC&Qi_wy|xEt2iz zWAJEfTV%cl2Cs<1L&DLRVVH05EDq`pH7Oh7sR`NNkL%wi}8n>IXcO40hp+J+sC!W?!krJf!GJNE8uj zg-y~Ns-<~D?yqbzVRB}G>0A^f0!^N7l=$m0OdZuqAOQqLc zX?AEGr1Ht+inZ-Qiwnl@Z0qukd__a!C*CKuGdy5#nD7VUBM^6OCpxCa2A(X;e0&V4 zM&WR8+wErQ7UIc6LY~Q9x%Sn*Tn>>P`^t&idaOEnOd(Ufw#>NoR^1QdhJ8s`h^|R_ zXX`c5*O~Xdvh%q;7L!_!ohf$NfEBmCde|#uVZvEo>OfEq%+Ns7&_f$OR9xsihRpBb z+cjk8LyDm@U{YN>+r46?nn{7Gh(;WhFw6GAxtcKD+YWV?uge>;+q#Xx4!GpRkVZYu zzsF}1)7$?%s9g9CH=Zs+B%M_)+~*j3L0&Q9u7!|+T`^O{xE6qvAP?XWv9_MrZKdo& z%IyU)$Q95AB4!#hT!_dA>4e@zjOBD*Y=XjtMm)V|+IXzjuM;(l+8aA5#Kaz_$rR6! zj>#&^DidYD$nUY(D$mH`9eb|dtV0b{S>H6FBfq>t5`;OxA4Nn{J(+XihF(stSche7$es&~N$epi&PDM_N`As;*9D^L==2Q7Z2zD+CiU(|+-kL*VG+&9!Yb3LgPy?A zm7Z&^qRG_JIxK7-FBzZI3Q<;{`DIxtc48k> zc|0dmX;Z=W$+)qE)~`yn6MdoJ4co;%!`ddy+FV538Y)j(vg}5*k(WK)KWZ3WaOG!8 z!syGn=s{H$odtpqFrT#JGM*utN7B((abXnpDM6w56nhw}OY}0TiTG1#f*VFZr+^-g zbP10`$LPq_;PvrA1XXlyx2uM^mrjTzX}w{yuLo-cOClE8MMk47T25G8M!9Z5ypOSV zAJUBGEg5L2fY)ZGJb^E34R2zJ?}Vf>{~gB!8=5Z) z9y$>5c)=;o0HeHHSuE4U)#vG&KF|I%-cF6f$~pdYJWk_dD}iOA>iA$O$+4%@>JU08 zS`ep)$XLPJ+n0_i@PkF#ri6T8?ZeAot$6JIYHm&P6EB=BiaNY|aA$W0I+nz*zkz_z zkEru!tj!QUffq%)8y0y`T&`fuus-1p>=^hnBiBqD^hXrPs`PY9tU3m0np~rISY09> z`P3s=-kt_cYcxWd{de@}TwSqg*xVhp;E9zCsnXo6z z?f&Sv^U7n4`xr=mXle94HzOdN!2kB~4=%)u&N!+2;z6UYKUDqi-s6AZ!haB;@&B`? z_TRX0%@suz^TRdCb?!vNJYPY8L_}&07uySH9%W^Tc&1pia6y1q#?*Drf}GjGbPjBS zbOPcUY#*$3sL2x4v_i*Y=N7E$mR}J%|GUI(>WEr+28+V z%v5{#e!UF*6~G&%;l*q*$V?&r$Pp^sE^i-0$+RH3ERUUdQ0>rAq2(2QAbG}$y{de( z>{qD~GGuOk559Y@%$?N^1ApVL_a704>8OD%8Y%8B;FCt%AoPu8*D1 zLB5X>b}Syz81pn;xnB}%0FnwazlWfUV)Z-~rZg6~b z6!9J$EcE&sEbzcy?CI~=boWA&eeIa%z(7SE^qgVLz??1Vbc1*aRvc%Mri)AJaAG!p z$X!_9Ds;Zz)f+;%s&dRcJt2==P{^j3bf0M=nJd&xwUGlUFn?H=2W(*2I2Gdu zv!gYCwM10aeus)`RIZSrCK=&oKaO_Ry~D1B5!y0R=%!i2*KfXGYX&gNv_u+n9wiR5 z*e$Zjju&ODRW3phN925%S(jL+bCHv6rZtc?!*`1TyYXT6%Ju=|X;6D@lq$8T zW{Y|e39ioPez(pBH%k)HzFITXHvnD6hw^lIoUMA;qAJ^CU?top1fo@s7xT13Fvn1H z6JWa-6+FJF#x>~+A;D~;VDs26>^oH0EI`IYT2iagy23?nyJ==i{g4%HrAf1-*v zK1)~@&(KkwR7TL}L(A@C_S0G;-GMDy=MJn2$FP5s<%wC)4jC5PXoxrQBFZ_k0P{{s@sz+gX`-!=T8rcB(=7vW}^K6oLWMmp(rwDh}b zwaGGd>yEy6fHv%jM$yJXo5oMAQ>c9j`**}F?MCry;T@47@r?&sKHgVe$MCqk#Z_3S z1GZI~nOEN*P~+UaFGnj{{Jo@16`(qVNtbU>O0Hf57-P>x8Jikp=`s8xWs^dAJ9lCQ z)GFm+=OV%AMVqVATtN@|vp61VVAHRn87}%PC^RAzJ%JngmZTasWBAWsoAqBU+8L8u z4A&Pe?fmTm0?mK-BL9t+{y7o(7jm+RpOhL9KnY#E&qu^}B6=K_dB}*VlSEiC9fn)+V=J;OnN)Ta5v66ic1rG+dGAJ1 z1%Zb_+!$=tQ~lxQrzv3x#CPb?CekEkA}0MYSgx$Jdd}q8+R=ma$|&1a#)TQ=l$1tQ z=tL9&_^vJ)Pk}EDO-va`UCT1m#Uty1{v^A3P~83_#v^ozH}6*9mIjIr;t3Uv%@VeW zGL6(CwCUp)Jq%G0bIG%?{_*Y#5IHf*5M@wPo6A{$Um++Co$wLC=J1aoG93&T7Ho}P z=mGEPP7GbvoG!uD$k(H3A$Z))+i{Hy?QHdk>3xSBXR0j!11O^mEe9RHmw!pvzv?Ua~2_l2Yh~_!s1qS`|0~0)YsbHSz8!mG)WiJE| z2f($6TQtt6L_f~ApQYQKSb=`053LgrQq7G@98#igV>y#i==-nEjQ!XNu9 z~;mE+gtj4IDDNQJ~JVk5Ux6&LCSFL!y=>79kE9=V}J7tD==Ga+IW zX)r7>VZ9dY=V&}DR))xUoV!u(Z|%3ciQi_2jl}3=$Agc(`RPb z8kEBpvY>1FGQ9W$n>Cq=DIpski};nE)`p3IUw1Oz0|wxll^)4dq3;CCY@RyJgFgc# zKouFh!`?Xuo{IMz^xi-h=StCis_M7yq$u) z?XHvw*HP0VgR+KR6wI)jEMX|ssqYvSf*_3W8zVTQzD?3>H!#>InzpSO)@SC8q*ii- z%%h}_#0{4JG;Jm`4zg};BPTGkYamx$Xo#O~lBirRY)q=5M45n{GCfV7h9qwyu1NxOMoP4)jjZMxmT|IQQh0U7C$EbnMN<3)Kk?fFHYq$d|ICu>KbY_hO zTZM+uKHe(cIZfEqyzyYSUBZa8;Fcut-GN!HSA9ius`ltNebF46ZX_BbZNU}}ZOm{M2&nANL9@0qvih15(|`S~z}m&h!u4x~(%MAO$jHRWNfuxWF#B)E&g3ghSQ9|> z(MFaLQj)NE0lowyjvg8z0#m6FIuKE9lDO~Glg}nSb7`~^&#(Lw{}GVOS>U)m8bF}x zVjbXljBm34Cs-yM6TVusr+3kYFjr28STT3g056y3cH5Tmge~ASxBj z%|yb>$eF;WgrcOZf569sDZOVwoo%8>XO>XQOX1OyN9I-SQgrm;U;+#3OI(zrWyow3 zk==|{lt2xrQ%FIXOTejR>;wv(Pb8u8}BUpx?yd(Abh6? zsoO3VYWkeLnF43&@*#MQ9-i-d0t*xN-UEyNKeyNMHw|A(k(_6QKO=nKMCxD(W(Yop zsRQ)QeL4X3Lxp^L%wzi2-WVSsf61dqliPUM7srDB?Wm6Lzn0&{*}|IsKQW;02(Y&| zaTKv|`U(pSzuvR6Rduu$wzK_W-Y-7>7s?G$)U}&uK;<>vU}^^ns@Z!p+9?St1s)dG zK%y6xkPyyS1$~&6v{kl?Md6gwM|>mt6Upm>oa8RLD^8T{0?HC!Z>;(Bob7el(DV6x zi`I)$&E&ngwFS@bi4^xFLAn`=fzTC;aimE^!cMI2n@Vo%Ae-ne`RF((&5y6xsjjAZ zVguVoQ?Z9uk$2ON;ersE%PU*xGO@T*;j1BO5#TuZKEf(mB7|g7pcEA=nYJ{s3vlbg zd4-DUlD{*6o%Gc^N!Nptgay>j6E5;3psI+C3Q!1ZIbeCubW%w4pq9)MSDyB{HLm|k zxv-{$$A*pS@csolri$Ge<4VZ}e~78JOL-EVyrbxKra^d{?|NnPp86!q>t<&IP07?Z z^>~IK^k#OEKgRH+LjllZXk7iA>2cfH6+(e&9ku5poo~6y{GC5>(bRK7hwjiurqAiZ zg*DmtgY}v83IjE&AbiWgMyFbaRUPZ{lYiz$U^&Zt2YjG<%m((&_JUbZcfJ22(>bi5 z!J?<7AySj0JZ&<-qXX;mcV!f~>G=sB0KnjWca4}vrtunD^1TrpfeS^4dvFr!65knK zZh`d;*VOkPs4*-9kL>$GP0`(M!j~B;#x?Ba~&s6CopvO86oM?-? zOw#dIRc;6A6T?B`Qp%^<U5 z19x(ywSH$_N+Io!6;e?`tWaM$`=Db!gzx|lQ${DG!zb1Zl&|{kX0y6xvO1o z220r<-oaS^^R2pEyY;=Qllqpmue|5yI~D|iI!IGt@iod{Opz@*ml^w2bNs)p`M(Io z|E;;m*Xpjd9l)4G#KaWfV(t8YUn@A;nK^#xgv=LtnArX|vWQVuw3}B${h+frU2>9^ z!l6)!Uo4`5k`<<;E(ido7M6lKTgWezNLq>U*=uz&s=cc$1%>VrAeOoUtA|T6gO4>UNqsdK=NF*8|~*sl&wI=x9-EGiq*aqV!(VVXA57 zw9*o6Ir8Lj1npUXvlevtn(_+^X5rzdR>#(}4YcB9O50q97%rW2me5_L=%ffYPUSRc z!vv?Kv>dH994Qi>U(a<0KF6NH5b16enCp+mw^Hb3Xs1^tThFpz!3QuN#}KBbww`(h z7GO)1olDqy6?T$()R7y%NYx*B0k_2IBiZ14&8|JPFxeMF{vSTxF-Vi3+ZOI=Thq2} zyQgjYY1_7^ZQHh{?P))4+qUiQJLi1&{yE>h?~jU%tjdV0h|FENbM3X(KnJdPKc?~k zh=^Ixv*+smUll!DTWH!jrV*wSh*(mx0o6}1@JExzF(#9FXgmTXVoU+>kDe68N)dkQ zH#_98Zv$}lQwjKL@yBd;U(UD0UCl322=pav<=6g>03{O_3oKTq;9bLFX1ia*lw;#K zOiYDcBJf)82->83N_Y(J7Kr_3lE)hAu;)Q(nUVydv+l+nQ$?|%MWTy`t>{havFSQloHwiIkGK9YZ79^9?AZo0ZyQlVR#}lF%dn5n%xYksXf8gnBm=wO7g_^! zauQ-bH1Dc@3ItZ-9D_*pH}p!IG7j8A_o94#~>$LR|TFq zZ-b00*nuw|-5C2lJDCw&8p5N~Z1J&TrcyErds&!l3$eSz%`(*izc;-?HAFD9AHb-| z>)id`QCrzRws^9(#&=pIx9OEf2rmlob8sK&xPCWS+nD~qzU|qG6KwA{zbikcfQrdH z+ zQg>O<`K4L8rN7`GJB0*3<3`z({lWe#K!4AZLsI{%z#ja^OpfjU{!{)x0ZH~RB0W5X zTwN^w=|nA!4PEU2=LR05x~}|B&ZP?#pNgDMwD*ajI6oJqv!L81gu=KpqH22avXf0w zX3HjbCI!n9>l046)5rr5&v5ja!xkKK42zmqHzPx$9Nn_MZk`gLeSLgC=LFf;H1O#B zn=8|^1iRrujHfbgA+8i<9jaXc;CQBAmQvMGQPhFec2H1knCK2x!T`e6soyrqCamX% zTQ4dX_E*8so)E*TB$*io{$c6X)~{aWfaqdTh=xEeGvOAN9H&-t5tEE-qso<+C!2>+ zskX51H-H}#X{A75wqFe-J{?o8Bx|>fTBtl&tcbdR|132Ztqu5X0i-pisB-z8n71%q%>EF}yy5?z=Ve`}hVh{Drv1YWL zW=%ug_&chF11gDv3D6B)Tz5g54H0mDHNjuKZ+)CKFk4Z|$RD zfRuKLW`1B>B?*RUfVd0+u8h3r-{@fZ{k)c!93t1b0+Q9vOaRnEn1*IL>5Z4E4dZ!7 ztp4GP-^1d>8~LMeb}bW!(aAnB1tM_*la=Xx)q(I0Y@__Zd$!KYb8T2VBRw%e$iSdZ zkwdMwd}eV9q*;YvrBFTv1>1+}{H!JK2M*C|TNe$ZSA>UHKk);wz$(F$rXVc|sI^lD zV^?_J!3cLM;GJuBMbftbaRUs$;F}HDEDtIeHQ)^EJJ1F9FKJTGH<(Jj`phE6OuvE) zqK^K`;3S{Y#1M@8yRQwH`?kHMq4tHX#rJ>5lY3DM#o@or4&^_xtBC(|JpGTfrbGkA z2Tu+AyT^pHannww!4^!$5?@5v`LYy~T`qs7SYt$JgrY(w%C+IWA;ZkwEF)u5sDvOK zGk;G>Mh&elvXDcV69J_h02l&O;!{$({fng9Rlc3ID#tmB^FIG^w{HLUpF+iB`|
NnX)EH+Nua)3Y(c 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 '} - case $link in #( - /*) app_path=$link ;; #( - *) app_path=$APP_HOME$link ;; - esac -done - -# This is normally unused -# shellcheck disable=SC2034 -APP_BASE_NAME=${0##*/} -# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit - -# Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD=maximum - -warn () { - echo "$*" -} >&2 - -die () { - echo - echo "$*" - echo - exit 1 -} >&2 - -# OS specific support (must be 'true' or 'false'). -cygwin=false -msys=false -darwin=false -nonstop=false -case "$( uname )" in #( - CYGWIN* ) cygwin=true ;; #( - Darwin* ) darwin=true ;; #( - MSYS* | MINGW* ) msys=true ;; #( - NONSTOP* ) nonstop=true ;; -esac - -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar - - -# Determine the Java command to use to start the JVM. -if [ -n "$JAVA_HOME" ] ; then - if [ -x "$JAVA_HOME/jre/sh/java" ] ; then - # IBM's JDK on AIX uses strange locations for the executables - JAVACMD=$JAVA_HOME/jre/sh/java - else - JAVACMD=$JAVA_HOME/bin/java - fi - if [ ! -x "$JAVACMD" ] ; then - die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME - -Please set the JAVA_HOME variable in your environment to match the -location of your Java installation." - fi -else - JAVACMD=java - if ! command -v java >/dev/null 2>&1 - then - die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. - -Please set the JAVA_HOME variable in your environment to match the -location of your Java installation." - fi -fi - -# Increase the maximum file descriptors if we can. -if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then - case $MAX_FD in #( - max*) - # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC2039,SC3045 - MAX_FD=$( ulimit -H -n ) || - warn "Could not query maximum file descriptor limit" - esac - case $MAX_FD in #( - '' | soft) :;; #( - *) - # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC2039,SC3045 - ulimit -n "$MAX_FD" || - warn "Could not set maximum file descriptor limit to $MAX_FD" - esac -fi - -# Collect all arguments for the java command, stacking in reverse order: -# * args from the command line -# * the main class name -# * -classpath -# * -D...appname settings -# * --module-path (only if needed) -# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. - -# For Cygwin or MSYS, switch paths to Windows format before running java -if "$cygwin" || "$msys" ; then - APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) - CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) - - JAVACMD=$( cygpath --unix "$JAVACMD" ) - - # Now convert the arguments - kludge to limit ourselves to /bin/sh - for arg do - if - case $arg in #( - -*) false ;; # don't mess with options #( - /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath - [ -e "$t" ] ;; #( - *) false ;; - esac - then - arg=$( cygpath --path --ignore --mixed "$arg" ) - fi - # Roll the args list around exactly as many times as the number of - # args, so each arg winds up back in the position where it started, but - # possibly modified. - # - # NB: a `for` loop captures its iteration list before it begins, so - # changing the positional parameters here affects neither the number of - # iterations, nor the values presented in `arg`. - shift # remove old arg - set -- "$@" "$arg" # push replacement arg - done -fi - - -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' - -# Collect all arguments for the java command: -# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, -# and any embedded shellness will be escaped. -# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be -# treated as '${Hostname}' itself on the command line. - -set -- \ - "-Dorg.gradle.appname=$APP_BASE_NAME" \ - -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ - "$@" - -# Stop when "xargs" is not available. -if ! command -v xargs >/dev/null 2>&1 -then - die "xargs is not available" -fi - -# Use "xargs" to parse quoted args. -# -# With -n1 it outputs one arg per line, with the quotes and backslashes removed. -# -# In Bash we could simply go: -# -# readarray ARGS < <( xargs -n1 <<<"$var" ) && -# set -- "${ARGS[@]}" "$@" -# -# but POSIX shell has neither arrays nor command substitution, so instead we -# post-process each arg (as a line of input to sed) to backslash-escape any -# character that might be a shell metacharacter, then use eval to reverse -# that process (while maintaining the separation between arguments), and wrap -# the whole thing up as a single "set" statement. -# -# This will of course break if any of these variables contains a newline or -# an unmatched quote. -# - -eval "set -- $( - printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | - xargs -n1 | - sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | - tr '\n' ' ' - )" '"$@"' - -exec "$JAVACMD" "$@" diff --git a/all-examples/java/v3/gradlew.bat b/all-examples/java/v3/gradlew.bat deleted file mode 100644 index 25da30db..00000000 --- a/all-examples/java/v3/gradlew.bat +++ /dev/null @@ -1,92 +0,0 @@ -@rem -@rem Copyright 2015 the original author or authors. -@rem -@rem Licensed under the Apache License, Version 2.0 (the "License"); -@rem you may not use this file except in compliance with the License. -@rem You may obtain a copy of the License at -@rem -@rem https://www.apache.org/licenses/LICENSE-2.0 -@rem -@rem Unless required by applicable law or agreed to in writing, software -@rem distributed under the License is distributed on an "AS IS" BASIS, -@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -@rem See the License for the specific language governing permissions and -@rem limitations under the License. -@rem - -@if "%DEBUG%"=="" @echo off -@rem ########################################################################## -@rem -@rem Gradle startup script for Windows -@rem -@rem ########################################################################## - -@rem Set local scope for the variables with windows NT shell -if "%OS%"=="Windows_NT" setlocal - -set DIRNAME=%~dp0 -if "%DIRNAME%"=="" set DIRNAME=. -@rem This is normally unused -set APP_BASE_NAME=%~n0 -set APP_HOME=%DIRNAME% - -@rem Resolve any "." and ".." in APP_HOME to make it shorter. -for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi - -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" - -@rem Find java.exe -if defined JAVA_HOME goto findJavaFromJavaHome - -set JAVA_EXE=java.exe -%JAVA_EXE% -version >NUL 2>&1 -if %ERRORLEVEL% equ 0 goto execute - -echo. 1>&2 -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 -echo. 1>&2 -echo Please set the JAVA_HOME variable in your environment to match the 1>&2 -echo location of your Java installation. 1>&2 - -goto fail - -:findJavaFromJavaHome -set JAVA_HOME=%JAVA_HOME:"=% -set JAVA_EXE=%JAVA_HOME%/bin/java.exe - -if exist "%JAVA_EXE%" goto execute - -echo. 1>&2 -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 -echo. 1>&2 -echo Please set the JAVA_HOME variable in your environment to match the 1>&2 -echo location of your Java installation. 1>&2 - -goto fail - -:execute -@rem Setup the command line - -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar - - -@rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* - -:end -@rem End local scope for the variables with windows NT shell -if %ERRORLEVEL% equ 0 goto mainEnd - -:fail -rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of -rem the _cmd.exe /c_ return code! -set EXIT_CODE=%ERRORLEVEL% -if %EXIT_CODE% equ 0 set EXIT_CODE=1 -if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% -exit /b %EXIT_CODE% - -:mainEnd -if "%OS%"=="Windows_NT" endlocal - -:omega diff --git a/all-examples/java/v3/s3ec-staging b/all-examples/java/v3/s3ec-staging deleted file mode 120000 index 1a52a7b9..00000000 --- a/all-examples/java/v3/s3ec-staging +++ /dev/null @@ -1 +0,0 @@ -../../../test-server/java-v3-transition-server/s3ec-staging \ No newline at end of file diff --git a/all-examples/java/v3/settings.gradle.kts b/all-examples/java/v3/settings.gradle.kts deleted file mode 100644 index a4dcbbae..00000000 --- a/all-examples/java/v3/settings.gradle.kts +++ /dev/null @@ -1 +0,0 @@ -rootProject.name = "s3ec-java-v3-example" diff --git a/all-examples/java/v3/src/main/java/software/amazon/encryption/s3/example/Main.java b/all-examples/java/v3/src/main/java/software/amazon/encryption/s3/example/Main.java deleted file mode 100644 index b0d9c112..00000000 --- a/all-examples/java/v3/src/main/java/software/amazon/encryption/s3/example/Main.java +++ /dev/null @@ -1,161 +0,0 @@ -package software.amazon.encryption.s3.example; - -import java.nio.charset.StandardCharsets; -import java.util.HashMap; -import java.util.Map; - -import software.amazon.awssdk.auth.credentials.DefaultCredentialsProvider; -import software.amazon.awssdk.core.sync.RequestBody; -import software.amazon.awssdk.regions.Region; -import software.amazon.awssdk.services.kms.KmsClient; -import software.amazon.awssdk.services.s3.S3Client; -import software.amazon.awssdk.services.s3.model.GetObjectRequest; -import software.amazon.awssdk.services.s3.model.PutObjectRequest; -import software.amazon.encryption.s3.CommitmentPolicy; -import software.amazon.encryption.s3.S3EncryptionClient; -import software.amazon.encryption.s3.algorithms.AlgorithmSuite; -import software.amazon.encryption.s3.materials.CryptographicMaterialsManager; -import software.amazon.encryption.s3.materials.DefaultCryptoMaterialsManager; -import software.amazon.encryption.s3.materials.KmsKeyring; - -/** - * Example demonstrating the use of Amazon S3 Encryption Client v3 for Java. - * - * This example shows how to: - * 1. Initialize the S3 Encryption Client with KMS keyring - * 2. Encrypt and upload an object to S3 - * 3. Download and decrypt the object - * 4. Verify the roundtrip encryption/decryption - */ -public class Main { - - public static void main(String[] args) { - // Check command line arguments - if (args.length != 4) { - System.out.println("Usage: ./gradlew run --args=\" \""); - System.out.println("Example: ./gradlew run --args=\"avp-21638 s3ec-java-v3 arn:aws:kms:us-east-2:648638458147:key/a47079da-17e4-45a5-b82e-2bac101cad01 us-east-2\""); - System.exit(1); - } - - String bucketName = args[0]; - String objectKey = args[1]; - String kmsKeyId = args[2]; - String region = args[3]; - - System.out.println("=== S3 Encryption Client v3 Example (Java) ==="); - System.out.println("Bucket: " + bucketName); - System.out.println("Object Key: " + objectKey); - System.out.println("KMS Key ID: " + kmsKeyId); - System.out.println("Region: " + region); - System.out.println(); - - // Test data for encryption - String testData = "Hello, World! This is a test message for S3 encryption client v3 in Java."; - System.out.println("Original data: " + testData); - System.out.println("Data length: " + testData.length() + " bytes"); - System.out.println(); - - try { - System.out.println("--- Initialize S3 Encryption Client v3 ---"); - - // Create standard S3 client - S3Client s3Client = S3Client.builder() - .region(Region.of(region)) - .credentialsProvider(DefaultCredentialsProvider.create()) - .build(); - - // Create KMS client - KmsClient kmsClient = KmsClient.builder() - .region(Region.of(region)) - .credentialsProvider(DefaultCredentialsProvider.create()) - .build(); - - // Create KMS keyring - KmsKeyring keyring = KmsKeyring.builder() - .kmsClient(kmsClient) - .wrappingKeyId(kmsKeyId) - .build(); - - // Create Cryptographic Materials Manager - CryptographicMaterialsManager cmm = DefaultCryptoMaterialsManager.builder() - .keyring(keyring) - .build(); - - // Create S3 Encryption Client v3 - S3EncryptionClient encryptionClient = S3EncryptionClient.builder() - .commitmentPolicy(CommitmentPolicy.FORBID_ENCRYPT_ALLOW_DECRYPT) - .encryptionAlgorithm(AlgorithmSuite.ALG_AES_256_GCM_IV12_TAG16_NO_KDF) - .wrappedClient(s3Client) - .cryptoMaterialsManager(cmm) - .enableLegacyUnauthenticatedModes(true) - .enableLegacyWrappingAlgorithms(true) - .build(); - - System.out.println("Successfully initialized S3 Encryption Client v3"); - System.out.println("--- Encrypt and Upload Object to S3 ---"); - - // Add encryption context - Map encryptionContext = new HashMap<>(); - encryptionContext.put("purpose", "example"); - encryptionContext.put("version", "v3"); - encryptionContext.put("language", "java"); - - // Upload encrypted object using S3 Encryption Client - PutObjectRequest putRequest = PutObjectRequest.builder() - .bucket(bucketName) - .key(objectKey) - .build(); - - encryptionClient.putObject(putRequest, RequestBody.fromString(testData)); - - System.out.println("Successfully uploaded encrypted object to S3!"); - System.out.println(" Bucket: " + bucketName); - System.out.println(" Key: " + objectKey); - System.out.println(" Encryption Context: " + encryptionContext); - System.out.println(); - - System.out.println("--- Download and Decrypt Object from S3 ---"); - - // Download and decrypt object using S3 Encryption Client - GetObjectRequest getRequest = GetObjectRequest.builder() - .bucket(bucketName) - .key(objectKey) - .build(); - - String decryptedData = encryptionClient.getObjectAsBytes(getRequest) - .asString(StandardCharsets.UTF_8); - - System.out.println("Successfully downloaded and decrypted object from S3!"); - System.out.println(" Object size: " + decryptedData.length() + " bytes"); - System.out.println(" Decrypted data: " + decryptedData); - System.out.println(); - - System.out.println("--- Verify Roundtrip Success ---"); - - // Verify the roundtrip was successful - if (decryptedData.equals(testData)) { - System.out.println("SUCCESS: Roundtrip encryption/decryption completed successfully!"); - System.out.println(" Original data matches decrypted data"); - System.out.println(" Data integrity verified"); - } else { - System.out.println("ERROR: Roundtrip failed - data mismatch"); - System.out.println(" Original: " + testData); - System.out.println(" Decrypted: " + decryptedData); - System.exit(1); - } - - System.out.println(); - System.out.println("=== Example completed successfully! ==="); - - // Clean up clients - encryptionClient.close(); - s3Client.close(); - kmsClient.close(); - - } catch (Exception e) { - System.err.println("Error: " + e.getMessage()); - e.printStackTrace(); - System.exit(1); - } - } -} diff --git a/all-examples/java/v4/Makefile b/all-examples/java/v4/Makefile deleted file mode 100644 index 3390c5e4..00000000 --- a/all-examples/java/v4/Makefile +++ /dev/null @@ -1,75 +0,0 @@ -# Makefile for S3 Encryption Client Java v4 Example - -# Default target -.PHONY: all install clean run help s3ec-staging - -# Default arguments for running the example -# Override these when calling make run -BUCKET_NAME ?= avp-21638 -OBJECT_KEY ?= s3ec-java-v4 -KMS_KEY_ID ?= arn:aws:kms:us-east-2:648638458147:key/a47079da-17e4-45a5-b82e-2bac101cad01 -AWS_REGION ?= us-east-2 - -all: install - -# Install S3 Encryption Client library from source -s3ec-staging: - @echo "[JAVA V4] Installing S3 Encryption Client library from source..." - @cd s3ec-staging && mvn -B -ntp install -DskipTests - @echo "[JAVA V4] S3 Encryption Client library installed successfully!" - -# Install dependencies using Gradle -install: s3ec-staging - @echo "[JAVA V4] Installing Java dependencies..." - @chmod +x ./gradlew - @./gradlew build - @echo "[JAVA V4] Dependencies installed successfully!" - -# Clean Gradle artifacts -clean: - @echo "[JAVA V4] Cleaning Gradle artifacts..." - @./gradlew clean - @echo "[JAVA V4] Clean completed!" - -# Run the example with default arguments -run: install - @echo "[JAVA V4] Running S3 Encryption Client v4 Java example..." - @echo "[JAVA V4] Bucket: $(BUCKET_NAME)" - @echo "[JAVA V4] Object Key: $(OBJECT_KEY)" - @echo "[JAVA V4] KMS Key ID: $(KMS_KEY_ID)" - @echo "[JAVA V4] Region: $(AWS_REGION)" - @echo "" - @./gradlew run --args="$(BUCKET_NAME) $(OBJECT_KEY) $(KMS_KEY_ID) $(AWS_REGION)" - -# Run with custom arguments -# Usage: make run-custom BUCKET_NAME=my-bucket OBJECT_KEY=my-key KMS_KEY_ID=my-kms-key AWS_REGION=my-region -run-custom: install - @./gradlew run --args="$(BUCKET_NAME) $(OBJECT_KEY) $(KMS_KEY_ID) $(AWS_REGION)" - -# Show help -help: - @echo "S3 Encryption Client Java v4 Example Makefile" - @echo "" - @echo "Available targets:" - @echo " s3ec-staging - Install S3 Encryption Client library from source" - @echo " install - Install Java dependencies using Gradle" - @echo " run - Install dependencies and run the example with default parameters" - @echo " run-custom - Install dependencies and run with custom parameters" - @echo " clean - Remove Gradle artifacts" - @echo " help - Show this help message" - @echo "" - @echo "Default parameters:" - @echo " BUCKET_NAME = $(BUCKET_NAME)" - @echo " OBJECT_KEY = $(OBJECT_KEY)" - @echo " KMS_KEY_ID = $(KMS_KEY_ID)" - @echo " AWS_REGION = $(AWS_REGION)" - @echo "" - @echo "To run with custom parameters:" - @echo " make run BUCKET_NAME=your-bucket OBJECT_KEY=your-key KMS_KEY_ID=your-kms-key AWS_REGION=your-region" - @echo "" - @echo "Prerequisites:" - @echo " - Java 11+ installed on the system" - @echo " - AWS credentials configured (AWS CLI, environment variables, or IAM role)" - @echo " - Valid S3 bucket and KMS key with appropriate permissions" - @echo " - S3 Encryption Client v4 library installed." - @echo " (Install by running: cd s3ec-staging && mvn install)" diff --git a/all-examples/java/v4/README.md b/all-examples/java/v4/README.md deleted file mode 100644 index deedec94..00000000 --- a/all-examples/java/v4/README.md +++ /dev/null @@ -1,57 +0,0 @@ -# S3 Encryption Client Java v4 Example - -This example demonstrates how to use the Amazon S3 Encryption Client v4 for Java to perform client-side encryption and decryption of objects with **key commitment** enabled for enhanced security. - -## Prerequisites - -1. **Java**: Requires Java 11 or later -2. **Gradle**: The example project uses Gradle wrapper (included - `./gradlew`) -3. **Maven**: Required to install the S3 Encryption Client library from source -4. **AWS Credentials**: Configure your AWS credentials using one of the following methods: - - AWS CLI: `aws configure` - - Environment variables: `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY` - - IAM roles (for EC2 instances) -5. **KMS Key**: You'll need a KMS key ID or ARN. You can use the default example key: `arn:aws:kms:us-east-2:648638458147:key/a47079da-17e4-45a5-b82e-2bac101cad01` -6. **S3 Bucket**: An existing S3 bucket where you have read/write permissions - -## Setup - -Install dependencies and build (this automatically installs the S3 Encryption Client library from source): -```bash -make install -``` - -Or manually: -```bash -cd s3ec-staging && mvn clean install && cd .. -./gradlew build -``` - -**Note**: This example uses a local library installed in Maven local repository via the symbolic link `s3ec-staging`. - -## Usage - -### Using Make (Recommended) - -Run the example with default parameters: -```bash -make run -``` - -Run with custom parameters: -```bash -make run BUCKET_NAME=my-bucket OBJECT_KEY=my-key KMS_KEY_ID=my-kms-key AWS_REGION=my-region -``` - -### Manual Usage - -Run the example with the following command: - -```bash -./gradlew run --args=" " -``` - -### Example: - -```bash -./gradlew run --args="my-test-bucket s3ec-java-v4-test arn:aws:kms:us-east-2:648638458147:key/a47079da-17e4-45a5-b82e-2bac101cad01 us-east-2" diff --git a/all-examples/java/v4/build.gradle.kts b/all-examples/java/v4/build.gradle.kts deleted file mode 100644 index 56b02b97..00000000 --- a/all-examples/java/v4/build.gradle.kts +++ /dev/null @@ -1,47 +0,0 @@ -plugins { - java - application -} - -group = "software.amazon.encryption.s3.example" -version = "1.0.0" - -repositories { - mavenLocal() - mavenCentral() -} - -dependencies { - // AWS SDK v2 dependencies - implementation(platform("software.amazon.awssdk:bom:2.20.0")) - implementation("software.amazon.awssdk:s3") - implementation("software.amazon.awssdk:kms") - implementation("software.amazon.awssdk:auth") - - // S3 Encryption Client v4 from local Maven repository - implementation("software.amazon.encryption.s3:amazon-s3-encryption-client-java:3.4.0-add-kc") -} - -application { - mainClass.set("software.amazon.encryption.s3.example.Main") -} - -java { - sourceCompatibility = JavaVersion.VERSION_11 - targetCompatibility = JavaVersion.VERSION_11 -} - -tasks.jar { - manifest { - attributes["Main-Class"] = "software.amazon.encryption.s3.example.Main" - } - - // Create a fat jar with all dependencies - from(configurations.runtimeClasspath.get().map { if (it.isDirectory) it else zipTree(it) }) - duplicatesStrategy = DuplicatesStrategy.EXCLUDE - archiveBaseName.set("s3ec-java-v4-example") -} - -tasks.named("run") { - standardInput = System.`in` -} diff --git a/all-examples/java/v4/gradle/wrapper/gradle-wrapper.jar b/all-examples/java/v4/gradle/wrapper/gradle-wrapper.jar deleted file mode 100644 index e6441136f3d4ba8a0da8d277868979cfbc8ad796..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 43453 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 yu?e%!_ptzGB|8GrCfu%p?(_ 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
NnX)EH+Nua)3Y(c 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 '} - case $link in #( - /*) app_path=$link ;; #( - *) app_path=$APP_HOME$link ;; - esac -done - -# This is normally unused -# shellcheck disable=SC2034 -APP_BASE_NAME=${0##*/} -# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit - -# Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD=maximum - -warn () { - echo "$*" -} >&2 - -die () { - echo - echo "$*" - echo - exit 1 -} >&2 - -# OS specific support (must be 'true' or 'false'). -cygwin=false -msys=false -darwin=false -nonstop=false -case "$( uname )" in #( - CYGWIN* ) cygwin=true ;; #( - Darwin* ) darwin=true ;; #( - MSYS* | MINGW* ) msys=true ;; #( - NONSTOP* ) nonstop=true ;; -esac - -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar - - -# Determine the Java command to use to start the JVM. -if [ -n "$JAVA_HOME" ] ; then - if [ -x "$JAVA_HOME/jre/sh/java" ] ; then - # IBM's JDK on AIX uses strange locations for the executables - JAVACMD=$JAVA_HOME/jre/sh/java - else - JAVACMD=$JAVA_HOME/bin/java - fi - if [ ! -x "$JAVACMD" ] ; then - die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME - -Please set the JAVA_HOME variable in your environment to match the -location of your Java installation." - fi -else - JAVACMD=java - if ! command -v java >/dev/null 2>&1 - then - die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. - -Please set the JAVA_HOME variable in your environment to match the -location of your Java installation." - fi -fi - -# Increase the maximum file descriptors if we can. -if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then - case $MAX_FD in #( - max*) - # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC2039,SC3045 - MAX_FD=$( ulimit -H -n ) || - warn "Could not query maximum file descriptor limit" - esac - case $MAX_FD in #( - '' | soft) :;; #( - *) - # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC2039,SC3045 - ulimit -n "$MAX_FD" || - warn "Could not set maximum file descriptor limit to $MAX_FD" - esac -fi - -# Collect all arguments for the java command, stacking in reverse order: -# * args from the command line -# * the main class name -# * -classpath -# * -D...appname settings -# * --module-path (only if needed) -# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. - -# For Cygwin or MSYS, switch paths to Windows format before running java -if "$cygwin" || "$msys" ; then - APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) - CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) - - JAVACMD=$( cygpath --unix "$JAVACMD" ) - - # Now convert the arguments - kludge to limit ourselves to /bin/sh - for arg do - if - case $arg in #( - -*) false ;; # don't mess with options #( - /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath - [ -e "$t" ] ;; #( - *) false ;; - esac - then - arg=$( cygpath --path --ignore --mixed "$arg" ) - fi - # Roll the args list around exactly as many times as the number of - # args, so each arg winds up back in the position where it started, but - # possibly modified. - # - # NB: a `for` loop captures its iteration list before it begins, so - # changing the positional parameters here affects neither the number of - # iterations, nor the values presented in `arg`. - shift # remove old arg - set -- "$@" "$arg" # push replacement arg - done -fi - - -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' - -# Collect all arguments for the java command: -# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, -# and any embedded shellness will be escaped. -# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be -# treated as '${Hostname}' itself on the command line. - -set -- \ - "-Dorg.gradle.appname=$APP_BASE_NAME" \ - -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ - "$@" - -# Stop when "xargs" is not available. -if ! command -v xargs >/dev/null 2>&1 -then - die "xargs is not available" -fi - -# Use "xargs" to parse quoted args. -# -# With -n1 it outputs one arg per line, with the quotes and backslashes removed. -# -# In Bash we could simply go: -# -# readarray ARGS < <( xargs -n1 <<<"$var" ) && -# set -- "${ARGS[@]}" "$@" -# -# but POSIX shell has neither arrays nor command substitution, so instead we -# post-process each arg (as a line of input to sed) to backslash-escape any -# character that might be a shell metacharacter, then use eval to reverse -# that process (while maintaining the separation between arguments), and wrap -# the whole thing up as a single "set" statement. -# -# This will of course break if any of these variables contains a newline or -# an unmatched quote. -# - -eval "set -- $( - printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | - xargs -n1 | - sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | - tr '\n' ' ' - )" '"$@"' - -exec "$JAVACMD" "$@" diff --git a/all-examples/java/v4/gradlew.bat b/all-examples/java/v4/gradlew.bat deleted file mode 100644 index 25da30db..00000000 --- a/all-examples/java/v4/gradlew.bat +++ /dev/null @@ -1,92 +0,0 @@ -@rem -@rem Copyright 2015 the original author or authors. -@rem -@rem Licensed under the Apache License, Version 2.0 (the "License"); -@rem you may not use this file except in compliance with the License. -@rem You may obtain a copy of the License at -@rem -@rem https://www.apache.org/licenses/LICENSE-2.0 -@rem -@rem Unless required by applicable law or agreed to in writing, software -@rem distributed under the License is distributed on an "AS IS" BASIS, -@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -@rem See the License for the specific language governing permissions and -@rem limitations under the License. -@rem - -@if "%DEBUG%"=="" @echo off -@rem ########################################################################## -@rem -@rem Gradle startup script for Windows -@rem -@rem ########################################################################## - -@rem Set local scope for the variables with windows NT shell -if "%OS%"=="Windows_NT" setlocal - -set DIRNAME=%~dp0 -if "%DIRNAME%"=="" set DIRNAME=. -@rem This is normally unused -set APP_BASE_NAME=%~n0 -set APP_HOME=%DIRNAME% - -@rem Resolve any "." and ".." in APP_HOME to make it shorter. -for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi - -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" - -@rem Find java.exe -if defined JAVA_HOME goto findJavaFromJavaHome - -set JAVA_EXE=java.exe -%JAVA_EXE% -version >NUL 2>&1 -if %ERRORLEVEL% equ 0 goto execute - -echo. 1>&2 -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 -echo. 1>&2 -echo Please set the JAVA_HOME variable in your environment to match the 1>&2 -echo location of your Java installation. 1>&2 - -goto fail - -:findJavaFromJavaHome -set JAVA_HOME=%JAVA_HOME:"=% -set JAVA_EXE=%JAVA_HOME%/bin/java.exe - -if exist "%JAVA_EXE%" goto execute - -echo. 1>&2 -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 -echo. 1>&2 -echo Please set the JAVA_HOME variable in your environment to match the 1>&2 -echo location of your Java installation. 1>&2 - -goto fail - -:execute -@rem Setup the command line - -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar - - -@rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* - -:end -@rem End local scope for the variables with windows NT shell -if %ERRORLEVEL% equ 0 goto mainEnd - -:fail -rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of -rem the _cmd.exe /c_ return code! -set EXIT_CODE=%ERRORLEVEL% -if %EXIT_CODE% equ 0 set EXIT_CODE=1 -if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% -exit /b %EXIT_CODE% - -:mainEnd -if "%OS%"=="Windows_NT" endlocal - -:omega diff --git a/all-examples/java/v4/s3ec-staging b/all-examples/java/v4/s3ec-staging deleted file mode 120000 index 970f5de7..00000000 --- a/all-examples/java/v4/s3ec-staging +++ /dev/null @@ -1 +0,0 @@ -../../../test-server/java-v4-server/s3ec-staging \ No newline at end of file diff --git a/all-examples/java/v4/settings.gradle.kts b/all-examples/java/v4/settings.gradle.kts deleted file mode 100644 index e20b5a12..00000000 --- a/all-examples/java/v4/settings.gradle.kts +++ /dev/null @@ -1 +0,0 @@ -rootProject.name = "s3ec-java-v4-example" diff --git a/all-examples/java/v4/src/main/java/software/amazon/encryption/s3/example/Main.java b/all-examples/java/v4/src/main/java/software/amazon/encryption/s3/example/Main.java deleted file mode 100644 index 4ec8fd9b..00000000 --- a/all-examples/java/v4/src/main/java/software/amazon/encryption/s3/example/Main.java +++ /dev/null @@ -1,160 +0,0 @@ -package software.amazon.encryption.s3.example; - -import java.nio.charset.StandardCharsets; -import java.util.HashMap; -import java.util.Map; - -import software.amazon.awssdk.auth.credentials.DefaultCredentialsProvider; -import software.amazon.awssdk.core.sync.RequestBody; -import software.amazon.awssdk.regions.Region; -import software.amazon.awssdk.services.kms.KmsClient; -import software.amazon.awssdk.services.s3.S3Client; -import software.amazon.awssdk.services.s3.model.GetObjectRequest; -import software.amazon.awssdk.services.s3.model.PutObjectRequest; -import software.amazon.encryption.s3.S3EncryptionClient; -import software.amazon.encryption.s3.materials.CryptographicMaterialsManager; -import software.amazon.encryption.s3.materials.DefaultCryptoMaterialsManager; -import software.amazon.encryption.s3.materials.KmsKeyring; - -/** - * Example demonstrating the use of Amazon S3 Encryption Client v4 for Java. - * - * This example shows how to: - * 1. Initialize the S3 Encryption Client with KMS keyring and key commitment - * 2. Encrypt and upload an object to S3 with key commitment - * 3. Download and decrypt the object - * 4. Verify the roundtrip encryption/decryption - */ -public class Main { - - public static void main(String[] args) { - // Check command line arguments - if (args.length != 4) { - System.out.println("Usage: ./gradlew run --args=\" \""); - System.out.println("Example: ./gradlew run --args=\"avp-21638 s3ec-java-v4 arn:aws:kms:us-east-2:648638458147:key/a47079da-17e4-45a5-b82e-2bac101cad01 us-east-2\""); - System.exit(1); - } - - String bucketName = args[0]; - String objectKey = args[1]; - String kmsKeyId = args[2]; - String region = args[3]; - - System.out.println("=== S3 Encryption Client v4 Example (Java) ==="); - System.out.println("Bucket: " + bucketName); - System.out.println("Object Key: " + objectKey); - System.out.println("KMS Key ID: " + kmsKeyId); - System.out.println("Region: " + region); - System.out.println(); - - // Test data for encryption - String testData = "Hello, World! This is a test message for S3 encryption client v4 in Java."; - System.out.println("Original data: " + testData); - System.out.println("Data length: " + testData.length() + " bytes"); - System.out.println(); - - try { - System.out.println("--- Initialize S3 Encryption Client v4 ---"); - - // Create standard S3 client - S3Client s3Client = S3Client.builder() - .region(Region.of(region)) - .credentialsProvider(DefaultCredentialsProvider.create()) - .build(); - - // Create KMS client - KmsClient kmsClient = KmsClient.builder() - .region(Region.of(region)) - .credentialsProvider(DefaultCredentialsProvider.create()) - .build(); - - // Create KMS keyring - KmsKeyring keyring = KmsKeyring.builder() - .kmsClient(kmsClient) - .wrappingKeyId(kmsKeyId) - .build(); - - // Create Cryptographic Materials Manager - CryptographicMaterialsManager cmm = DefaultCryptoMaterialsManager.builder() - .keyring(keyring) - .build(); - - // Create S3 Encryption Client v4 with key commitment enabled (Defaults to REQUIRE_ENCRYPT_REQUIRE_DECRYPT) - S3EncryptionClient encryptionClient = S3EncryptionClient.builderV4() - .wrappedClient(s3Client) - .cryptoMaterialsManager(cmm) - .enableLegacyUnauthenticatedModes(false) - .enableLegacyWrappingAlgorithms(false) - .build(); - - System.out.println("Successfully initialized S3 Encryption Client v4"); - System.out.println("Key commitment: ENABLED"); - System.out.println("--- Encrypt and Upload Object to S3 ---"); - - // Add encryption context - Map encryptionContext = new HashMap<>(); - encryptionContext.put("purpose", "example"); - encryptionContext.put("version", "v4"); - encryptionContext.put("language", "java"); - - // Upload encrypted object using S3 Encryption Client - PutObjectRequest putRequest = PutObjectRequest.builder() - .bucket(bucketName) - .key(objectKey) - .build(); - - encryptionClient.putObject(putRequest, RequestBody.fromString(testData)); - - System.out.println("Successfully uploaded encrypted object to S3!"); - System.out.println(" Bucket: " + bucketName); - System.out.println(" Key: " + objectKey); - System.out.println(" Encryption Context: " + encryptionContext); - System.out.println(" Key Commitment: ENABLED"); - System.out.println(); - - System.out.println("--- Download and Decrypt Object from S3 ---"); - - // Download and decrypt object using S3 Encryption Client - GetObjectRequest getRequest = GetObjectRequest.builder() - .bucket(bucketName) - .key(objectKey) - .build(); - - String decryptedData = encryptionClient.getObjectAsBytes(getRequest) - .asString(StandardCharsets.UTF_8); - - System.out.println("Successfully downloaded and decrypted object from S3!"); - System.out.println(" Object size: " + decryptedData.length() + " bytes"); - System.out.println(" Decrypted data: " + decryptedData); - System.out.println(); - - System.out.println("--- Verify Roundtrip Success ---"); - - // Verify the roundtrip was successful - if (decryptedData.equals(testData)) { - System.out.println("SUCCESS: Roundtrip encryption/decryption completed successfully!"); - System.out.println(" Original data matches decrypted data"); - System.out.println(" Data integrity verified"); - System.out.println(" Key commitment verified"); - } else { - System.out.println("ERROR: Roundtrip failed - data mismatch"); - System.out.println(" Original: " + testData); - System.out.println(" Decrypted: " + decryptedData); - System.exit(1); - } - - System.out.println(); - System.out.println("=== Example completed successfully! ==="); - - // Clean up clients - encryptionClient.close(); - s3Client.close(); - kmsClient.close(); - - } catch (Exception e) { - System.err.println("Error: " + e.getMessage()); - e.printStackTrace(); - System.exit(1); - } - } -} diff --git a/all-examples/net/.gitignore b/all-examples/net/.gitignore deleted file mode 100644 index c6a52ab1..00000000 --- a/all-examples/net/.gitignore +++ /dev/null @@ -1,19 +0,0 @@ -# Build results -bin/ -obj/ - -# User-specific files -*.user -*.suo -*.userosscache -*.sln.docstates - -# Visual Studio -.vs/ - -# Rider -.idea/ - -# NuGet packages -packages/ -*.nupkg diff --git a/all-examples/net/v3/Makefile b/all-examples/net/v3/Makefile deleted file mode 100644 index c375acc7..00000000 --- a/all-examples/net/v3/Makefile +++ /dev/null @@ -1,66 +0,0 @@ -# Makefile for S3 Encryption Client .NET v3 Example - -# Default target -.PHONY: all install clean run help - -# Default arguments for running the example -# Override these when calling make run -BUCKET_NAME ?= avp-21638 -OBJECT_KEY ?= s3ec-dotnet-v3 -KMS_KEY_ID ?= arn:aws:kms:us-east-2:648638458147:key/a47079da-17e4-45a5-b82e-2bac101cad01 -AWS_REGION ?= us-east-2 - -all: install - -# Install dependencies using .NET modules -install: - @echo "[NET V3] Installing .NET dependencies..." - dotnet restore - @echo "[NET V3] Dependencies installed successfully!" - -# Clean .NET artifacts -clean: - @echo "[NET V3] Cleaning .NET artifacts..." - dotnet clean - @echo "[NET V3] Clean completed!" - -# Run the example with default arguments -run: install - @echo "[NET V3] Running S3 Encryption Client v3 .NET example..." - @echo "[NET V3] Bucket: $(BUCKET_NAME)" - @echo "[NET V3] Object Key: $(OBJECT_KEY)" - @echo "[NET V3] KMS Key ID: $(KMS_KEY_ID)" - @echo "[NET V3] Region: $(AWS_REGION)" - @echo "" - @dotnet run -- $(BUCKET_NAME) $(OBJECT_KEY) $(KMS_KEY_ID) $(AWS_REGION) - -# Run with custom arguments -# Usage: make run-custom BUCKET_NAME=my-bucket OBJECT_KEY=my-key KMS_KEY_ID=my-kms-key AWS_REGION=my-region -run-custom: install - @dotnet run -- $(BUCKET_NAME) $(OBJECT_KEY) $(KMS_KEY_ID) $(AWS_REGION) - -# Show help -help: - @echo "S3 Encryption Client .NET v3 Example Makefile" - @echo "" - @echo "Available targets:" - @echo " install - Install .NET dependencies using .NET modules" - @echo " run - Install dependencies and run the example with default parameters" - @echo " run-custom - Install dependencies and run with custom parameters" - @echo " clean - Remove .NET artifacts" - @echo " help - Show this help message" - @echo "" - @echo "Default parameters:" - @echo " BUCKET_NAME = $(BUCKET_NAME)" - @echo " OBJECT_KEY = $(OBJECT_KEY)" - @echo " KMS_KEY_ID = $(KMS_KEY_ID)" - @echo " AWS_REGION = $(AWS_REGION)" - @echo "" - @echo "To run with custom parameters:" - @echo " make run BUCKET_NAME=your-bucket OBJECT_KEY=your-key KMS_KEY_ID=your-kms-key AWS_REGION=your-region" - @echo "" - @echo "Prerequisites:" - @echo " - Supported .NET framework installed on the system. See https://www.nuget.org/packages/Amazon.Extensions.S3.Encryption for supported one." - @echo " - AWS credentials configured (AWS CLI, environment variables, or IAM role)" - @echo " - Valid S3 bucket and KMS key with appropriate permissions" - @echo " - S3 Encryption Client v3 .NET SDK (included in s3ec-v3-local)" \ No newline at end of file diff --git a/all-examples/net/v3/Program.cs b/all-examples/net/v3/Program.cs deleted file mode 100644 index 6c1336f6..00000000 --- a/all-examples/net/v3/Program.cs +++ /dev/null @@ -1,76 +0,0 @@ -using Amazon; -using Amazon.Extensions.S3.Encryption; -using Amazon.Extensions.S3.Encryption.Primitives; -using Amazon.S3; -using Amazon.S3.Model; - -using Amazon.Extensions.S3.Encryption; -using Amazon.Extensions.S3.Encryption.Primitives; -using Amazon.S3; -using Amazon.S3.Model; - -namespace S3EncryptionClientV3Example -{ - class Program - { - static async Task Main(string[] args) - { - if (args.Length != 4) - { - Console.WriteLine("[NET V3] Usage: dotnet run "); - Environment.Exit(1); - } - - var (bucketName, objectKey, kmsKeyId, region) = (args[0], args[1], args[2], args[3]); - var testData = "Hello, World! This is a test message for S3 encryption client v3 in .NET."; - - Console.WriteLine("=== S3 Encryption Client v3 Example (.NET) ==="); - - try - { - var s3Client = CreateS3ECWithKms(kmsKeyId, region); - - await s3Client.PutObjectAsync(new PutObjectRequest - { - BucketName = bucketName, - Key = objectKey, - ContentBody = testData - }); - - var getResponse = await s3Client.GetObjectAsync(bucketName, objectKey); - using var reader = new StreamReader(getResponse.ResponseStream); - var decryptedData = await reader.ReadToEndAsync(); - - if (decryptedData != testData) - { - Console.WriteLine("[NET V3] ERROR: Roundtrip failed - data mismatch"); - Environment.Exit(1); - } - - Console.WriteLine("[NET V3] SUCCESS: Roundtrip encryption/decryption completed successfully!"); - } - catch (Exception ex) - { - Console.WriteLine($"[NET V3] Error: {ex.Message}"); - Environment.Exit(1); - } - } - - private static AmazonS3Client CreateS3ECWithKms(string kmsKeyId, string region) - { - var encryptionContextPerClient = new Dictionary - { - ["purpose"] = "example", - ["version"] = "v3", - ["language"] = "dotnet" - }; - - var encryptionMaterial = new EncryptionMaterialsV2(kmsKeyId, KmsType.KmsContext, encryptionContextPerClient); - var configuration = new AmazonS3CryptoConfigurationV2(SecurityProfile.V2, CommitmentPolicy.ForbidEncryptAllowDecrypt, ContentEncryptionAlgorithm.AesGcm) - { - RegionEndpoint = RegionEndpoint.GetBySystemName(region) - }; - return new AmazonS3EncryptionClientV2(configuration, encryptionMaterial); - } - } -} diff --git a/all-examples/net/v3/s3ec-v3-local b/all-examples/net/v3/s3ec-v3-local deleted file mode 120000 index a2d8df44..00000000 --- a/all-examples/net/v3/s3ec-v3-local +++ /dev/null @@ -1 +0,0 @@ -../../../test-server/net-v3-transition-server/s3ec-v3-transition-branch \ No newline at end of file diff --git a/all-examples/net/v3/v3.csproj b/all-examples/net/v3/v3.csproj deleted file mode 100644 index cfb74fad..00000000 --- a/all-examples/net/v3/v3.csproj +++ /dev/null @@ -1,19 +0,0 @@ - - - - Exe - net8.0 - enable - enable - false - - - - - - - - - - - diff --git a/all-examples/net/v4/Makefile b/all-examples/net/v4/Makefile deleted file mode 100644 index f45fbdfd..00000000 --- a/all-examples/net/v4/Makefile +++ /dev/null @@ -1,66 +0,0 @@ -# Makefile for S3 Encryption Client .NET v4 Example - -# Default target -.PHONY: all install clean run help - -# Default arguments for running the example -# Override these when calling make run -BUCKET_NAME ?= avp-21638 -OBJECT_KEY ?= s3ec-dotnet-v4 -KMS_KEY_ID ?= arn:aws:kms:us-east-2:648638458147:key/a47079da-17e4-45a5-b82e-2bac101cad01 -AWS_REGION ?= us-east-2 - -all: install - -# Install dependencies using .NET modules -install: - @echo "[NET V4] Installing .NET dependencies..." - dotnet restore - @echo "[NET V4] Dependencies installed successfully!" - -# Clean .NET artifacts -clean: - @echo "[NET V4] Cleaning .NET artifacts..." - dotnet clean - @echo "[NET V4] Clean completed!" - -# Run the example with default arguments -run: install - @echo "[NET V4] Running S3 Encryption Client v4 .NET example..." - @echo "[NET V4] Bucket: $(BUCKET_NAME)" - @echo "[NET V4] Object Key: $(OBJECT_KEY)" - @echo "[NET V4] KMS Key ID: $(KMS_KEY_ID)" - @echo "[NET V4] Region: $(AWS_REGION)" - @echo "" - @dotnet run -- $(BUCKET_NAME) $(OBJECT_KEY) $(KMS_KEY_ID) $(AWS_REGION) - -# Run with custom arguments -# Usage: make run-custom BUCKET_NAME=my-bucket OBJECT_KEY=my-key KMS_KEY_ID=my-kms-key AWS_REGION=my-region -run-custom: install - @dotnet run -- $(BUCKET_NAME) $(OBJECT_KEY) $(KMS_KEY_ID) $(AWS_REGION) - -# Show help -help: - @echo "S3 Encryption Client .NET v4 Example Makefile" - @echo "" - @echo "Available targets:" - @echo " install - Install .NET dependencies using .NET modules" - @echo " run - Install dependencies and run the example with default parameters" - @echo " run-custom - Install dependencies and run with custom parameters" - @echo " clean - Remove .NET artifacts" - @echo " help - Show this help message" - @echo "" - @echo "Default parameters:" - @echo " BUCKET_NAME = $(BUCKET_NAME)" - @echo " OBJECT_KEY = $(OBJECT_KEY)" - @echo " KMS_KEY_ID = $(KMS_KEY_ID)" - @echo " AWS_REGION = $(AWS_REGION)" - @echo "" - @echo "To run with custom parameters:" - @echo " make run BUCKET_NAME=your-bucket OBJECT_KEY=your-key KMS_KEY_ID=your-kms-key AWS_REGION=your-region" - @echo "" - @echo "Prerequisites:" - @echo " - Supported .NET framework installed on the system. See https://www.nuget.org/packages/Amazon.Extensions.S3.Encryption for supported one." - @echo " - AWS credentials configured (AWS CLI, environment variables, or IAM role)" - @echo " - Valid S3 bucket and KMS key with appropriate permissions" - @echo " - S3 Encryption Client v4 .NET SDK (included in s3ec-v4-local)" \ No newline at end of file diff --git a/all-examples/net/v4/Program.cs b/all-examples/net/v4/Program.cs deleted file mode 100644 index a8c799a6..00000000 --- a/all-examples/net/v4/Program.cs +++ /dev/null @@ -1,76 +0,0 @@ -using Amazon; -using Amazon.Extensions.S3.Encryption; -using Amazon.Extensions.S3.Encryption.Primitives; -using Amazon.S3; -using Amazon.S3.Model; - -using Amazon.Extensions.S3.Encryption; -using Amazon.Extensions.S3.Encryption.Primitives; -using Amazon.S3; -using Amazon.S3.Model; - -namespace S3EncryptionClientV4Example -{ - class Program - { - static async Task Main(string[] args) - { - if (args.Length != 4) - { - Console.WriteLine("[NET V4] Usage: dotnet run "); - Environment.Exit(1); - } - - var (bucketName, objectKey, kmsKeyId, region) = (args[0], args[1], args[2], args[3]); - var testData = "Hello, World! This is a test message for S3 encryption client v4 in .NET."; - - Console.WriteLine("=== S3 Encryption Client v4 Example (.NET) ==="); - - try - { - var s3Client = CreateS3ECWithKms(kmsKeyId, region); - - await s3Client.PutObjectAsync(new PutObjectRequest - { - BucketName = bucketName, - Key = objectKey, - ContentBody = testData - }); - - var getResponse = await s3Client.GetObjectAsync(bucketName, objectKey); - using var reader = new StreamReader(getResponse.ResponseStream); - var decryptedData = await reader.ReadToEndAsync(); - - if (decryptedData != testData) - { - Console.WriteLine("[NET V4] ERROR: Roundtrip failed - data mismatch"); - Environment.Exit(1); - } - - Console.WriteLine("[NET V4] SUCCESS: Roundtrip encryption/decryption completed successfully!"); - } - catch (Exception ex) - { - Console.WriteLine($"[NET V4] Error: {ex.Message}"); - Environment.Exit(1); - } - } - - private static AmazonS3Client CreateS3ECWithKms(string kmsKeyId, string region) - { - var encryptionContextPerClient = new Dictionary - { - ["purpose"] = "example", - ["version"] = "v4", - ["language"] = "dotnet" - }; - - var encryptionMaterial = new EncryptionMaterialsV4(kmsKeyId, KmsType.KmsContext, encryptionContextPerClient); - var configuration = new AmazonS3CryptoConfigurationV4(SecurityProfile.V4, CommitmentPolicy.RequireEncryptRequireDecrypt, ContentEncryptionAlgorithm.AesGcmWithCommitment) - { - RegionEndpoint = RegionEndpoint.GetBySystemName(region) - }; - return new AmazonS3EncryptionClientV4(configuration, encryptionMaterial); - } - } -} diff --git a/all-examples/net/v4/s3ec-v4-local b/all-examples/net/v4/s3ec-v4-local deleted file mode 120000 index 371b1a90..00000000 --- a/all-examples/net/v4/s3ec-v4-local +++ /dev/null @@ -1 +0,0 @@ -../../../test-server/net-v4-server/s3ec-net-v4-improved \ No newline at end of file diff --git a/all-examples/net/v4/v4.csproj b/all-examples/net/v4/v4.csproj deleted file mode 100644 index 6d223a92..00000000 --- a/all-examples/net/v4/v4.csproj +++ /dev/null @@ -1,19 +0,0 @@ - - - - Exe - net8.0 - enable - enable - false - - - - - - - - - - - diff --git a/all-examples/php/v2/.gitignore b/all-examples/php/v2/.gitignore deleted file mode 100644 index 07108589..00000000 --- a/all-examples/php/v2/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -vendor/* -cookies.txt -server.pid -composer.lock \ No newline at end of file diff --git a/all-examples/php/v2/Makefile b/all-examples/php/v2/Makefile deleted file mode 100644 index 0747d7b8..00000000 --- a/all-examples/php/v2/Makefile +++ /dev/null @@ -1,71 +0,0 @@ -# Makefile for S3 Encryption Client PHP v2 Example - -# Default target -.PHONY: all install clean run help - -# Variables -SCRIPT = main.php - -# Default arguments for running the example -# Override these when calling make run -BUCKET_NAME ?= avp-21638 -OBJECT_KEY ?= s3ec-php-v2 -KMS_KEY_ID ?= arn:aws:kms:us-east-2:648638458147:key/a47079da-17e4-45a5-b82e-2bac101cad01 -AWS_REGION ?= us-east-2 - -all: install - -# Install dependencies using Composer -install: - @echo "Installing PHP dependencies..." - @composer install --no-dev --optimize-autoloader - @echo "Dependencies installed successfully!" - -# Clean composer artifacts -clean: - @echo "Cleaning composer artifacts..." - @rm -rf vendor/ - @rm -f composer.lock - @echo "Clean completed!" - -# Run the example with default arguments -run: install - @echo "Running S3 Encryption Client v2 PHP example..." - @echo "Bucket: $(BUCKET_NAME)" - @echo "Object Key: $(OBJECT_KEY)" - @echo "KMS Key ID: $(KMS_KEY_ID)" - @echo "Region: $(AWS_REGION)" - @echo "" - @php $(SCRIPT) $(BUCKET_NAME) $(OBJECT_KEY) $(KMS_KEY_ID) $(AWS_REGION) - -# Run with custom arguments -# Usage: make run-custom BUCKET_NAME=my-bucket OBJECT_KEY=my-key KMS_KEY_ID=my-kms-key AWS_REGION=my-region -run-custom: install - @php $(SCRIPT) $(BUCKET_NAME) $(OBJECT_KEY) $(KMS_KEY_ID) $(AWS_REGION) - -# Show help -help: - @echo "S3 Encryption Client PHP v2 Example Makefile" - @echo "" - @echo "Available targets:" - @echo " install - Install PHP dependencies using Composer" - @echo " run - Install dependencies and run the example with default parameters" - @echo " run-custom - Install dependencies and run with custom parameters" - @echo " clean - Remove composer artifacts (vendor/, composer.lock)" - @echo " help - Show this help message" - @echo "" - @echo "Default parameters:" - @echo " BUCKET_NAME = $(BUCKET_NAME)" - @echo " OBJECT_KEY = $(OBJECT_KEY)" - @echo " KMS_KEY_ID = $(KMS_KEY_ID)" - @echo " AWS_REGION = $(AWS_REGION)" - @echo "" - @echo "To run with custom parameters:" - @echo " make run BUCKET_NAME=your-bucket OBJECT_KEY=your-key KMS_KEY_ID=your-kms-key AWS_REGION=your-region" - @echo "" - @echo "Prerequisites:" - @echo " - PHP 7.4+ installed on the system" - @echo " - Composer installed (https://getcomposer.org/)" - @echo " - AWS credentials configured (AWS CLI, environment variables, or IAM role)" - @echo " - Valid S3 bucket and KMS key with appropriate permissions" - @echo " - S3 Encryption Client v2 PHP SDK (included in local-php-sdk)" diff --git a/all-examples/php/v2/composer.json b/all-examples/php/v2/composer.json deleted file mode 100644 index 914bf900..00000000 --- a/all-examples/php/v2/composer.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "name": "aws/s3ec-php-v2-example", - "description": "PHP v2 example for Amazon S3 Encryption Client", - "type": "project", - "license": "Apache-2.0", - "repositories": [ - { - "type": "path", - "url": "./local-php-sdk", - "options": { - "symlink": true - } - } - ], - "require": { - "php": ">=7.4", - "aws/aws-sdk-php": "@dev", - "ramsey/uuid": "^4.9" - }, - "autoload": { - "psr-4": { - "AWS\\S3EC\\Example\\": "src/" - } - }, - "config": { - "optimize-autoloader": true, - "platform": { - "php": "8.1" - } - }, - "minimum-stability": "dev", - "prefer-stable": true -} diff --git a/all-examples/php/v2/local-php-sdk b/all-examples/php/v2/local-php-sdk deleted file mode 120000 index 04ad0cf7..00000000 --- a/all-examples/php/v2/local-php-sdk +++ /dev/null @@ -1 +0,0 @@ -../../../test-server/php-v2-transition-server/local-php-sdk \ No newline at end of file diff --git a/all-examples/php/v2/main.php b/all-examples/php/v2/main.php deleted file mode 100755 index df2fdd32..00000000 --- a/all-examples/php/v2/main.php +++ /dev/null @@ -1,161 +0,0 @@ -#!/usr/bin/env php - \n"; - echo "Example: {$GLOBALS['argv'][0]} avp-21638 s3ec-php-v2 arn:aws:kms:us-east-2:648638458147:key/a47079da-17e4-45a5-b82e-2bac101cad01 us-east-2\n"; - exit(1); - } - - $bucketName = $GLOBALS['argv'][1]; - $objectKey = $GLOBALS['argv'][2]; - $kmsKeyId = $GLOBALS['argv'][3]; - $region = $GLOBALS['argv'][4]; - - echo "=== S3 Encryption Client v2 Example (PHP) ===\n"; - echo "Bucket: {$bucketName}\n"; - echo "Object Key: {$objectKey}\n"; - echo "KMS Key ID: {$kmsKeyId}\n"; - echo "Region: {$region}\n"; - echo "\n"; - - try { - // Test data for encryption - $testData = "Hello, World! This is a test message for S3 encryption client v2 in PHP."; - echo "Original data: {$testData}\n"; - echo "Data length: " . strlen($testData) . " bytes\n"; - echo "\n"; - - echo "--- Initialize S3 Encryption Client v2 ---\n"; - - // Create regular S3 client - $s3Client = new S3Client([ - 'region' => $region, - 'version' => 'latest' - ]); - - // Create KMS client - $kmsClient = new KmsClient([ - 'region' => $region, - 'version' => 'latest' - ]); - - // Create S3 Encryption Client v2 - $encryptionClient = new S3EncryptionClientV2($s3Client); - $materialsProvider = new KmsMaterialsProviderV2($kmsClient, $kmsKeyId); - - echo "Successfully initialized S3 Encryption Client v2\n"; - echo "--- Encrypt and Upload Object to S3 ---\n"; - - // Add encryption context - $encryptionContext = [ - 'purpose' => 'example', - 'version' => 'v2', - 'language' => 'php' - ]; - - $cipherOptions = [ - 'Cipher' => 'gcm', - 'KeySize' => 256, - ]; - - // Upload encrypted object using S3 Encryption Client - $putResponse = $encryptionClient->putObject([ - 'Bucket' => $bucketName, - 'Key' => $objectKey, - 'Body' => $testData, - '@MaterialsProvider' => $materialsProvider, - '@KmsEncryptionContext' => $encryptionContext, - '@CipherOptions' => $cipherOptions, - ]); - - echo "Successfully uploaded encrypted object to S3!\n"; - echo " Bucket: {$bucketName}\n"; - echo " Key: {$objectKey}\n"; - echo " Encryption Context: " . json_encode($encryptionContext) . "\n"; - echo "\n"; - - echo "--- Download and Decrypt Object from S3 ---\n"; - - // Download and decrypt object using S3 Encryption Client - $getResponse = $encryptionClient->getObject([ - 'Bucket' => $bucketName, - 'Key' => $objectKey, - '@KmsEncryptionContext' => $encryptionContext, - '@MaterialsProvider' => $materialsProvider, - '@CommitmentPolicy' => 'FORBID_ENCRYPT_ALLOW_DECRYPT', - '@SecurityProfile' => 'V2' - ]); - - // Read the decrypted data - $decryptedData = (string) $getResponse['Body']; - - echo "Successfully downloaded and decrypted object from S3!\n"; - echo " Object size: " . strlen($decryptedData) . " bytes\n"; - echo " Decrypted data: {$decryptedData}\n"; - echo "\n"; - - echo "--- Verify Roundtrip Success ---\n"; - - // Verify the roundtrip was successful - if ($decryptedData === $testData) { - echo "SUCCESS: Roundtrip encryption/decryption completed successfully!\n"; - echo " Original data matches decrypted data\n"; - echo " Data integrity verified\n"; - } else { - echo "ERROR: Roundtrip failed - data mismatch\n"; - echo " Original: {$testData}\n"; - echo " Decrypted: {$decryptedData}\n"; - exit(1); - } - - // Optionally Delete the Object - // echo "--- Cleanup ---\n"; - // Clean up the test object using regular S3 client - // $s3Client->deleteObject([ - // 'Bucket' => $bucketName, - // 'Key' => $objectKey - // ]); - // echo "Test object deleted from S3\n"; - - echo "\n"; - echo "=== Example completed successfully! ===\n"; - - } catch (AwsException $e) { - $errorCode = $e->getAwsErrorCode(); - $errorMessage = $e->getMessage(); - - if (strpos($errorCode, 'NoSuchBucket') !== false) { - echo "Error: S3 bucket '{$bucketName}' does not exist or is not accessible\n"; - echo " {$errorMessage}\n"; - } elseif (strpos($errorCode, 'NotFoundException') !== false) { - echo "Error: KMS key '{$kmsKeyId}' not found or not accessible\n"; - echo " {$errorMessage}\n"; - } elseif (strpos($errorMessage, 'encryption') !== false) { - echo "S3 Encryption Error: {$errorMessage}\n"; - } else { - echo "AWS Service Error: {$errorMessage}\n"; - echo " Error Code: {$errorCode}\n"; - } - exit(1); - } catch (Exception $e) { - echo "Unexpected error: {$e->getMessage()}\n"; - echo " File: {$e->getFile()}:{$e->getLine()}\n"; - exit(1); - } -} - -// Run the main function if this script is executed directly -if (php_sapi_name() === 'cli' && isset($GLOBALS['argv']) && basename($GLOBALS['argv'][0]) === basename(__FILE__)) { - main(); -} diff --git a/all-examples/php/v3/.gitignore b/all-examples/php/v3/.gitignore deleted file mode 100644 index 07108589..00000000 --- a/all-examples/php/v3/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -vendor/* -cookies.txt -server.pid -composer.lock \ No newline at end of file diff --git a/all-examples/php/v3/Makefile b/all-examples/php/v3/Makefile deleted file mode 100644 index 328a901a..00000000 --- a/all-examples/php/v3/Makefile +++ /dev/null @@ -1,71 +0,0 @@ -# Makefile for S3 Encryption Client PHP v3 Example - -# Default target -.PHONY: all install clean run help - -# Variables -SCRIPT = main.php - -# Default arguments for running the example -# Override these when calling make run -BUCKET_NAME ?= avp-21638 -OBJECT_KEY ?= s3ec-php-v3 -KMS_KEY_ID ?= arn:aws:kms:us-east-2:648638458147:key/a47079da-17e4-45a5-b82e-2bac101cad01 -AWS_REGION ?= us-east-2 - -all: install - -# Install dependencies using Composer -install: - @echo "Installing PHP dependencies..." - @composer install --no-dev --optimize-autoloader - @echo "Dependencies installed successfully!" - -# Clean composer artifacts -clean: - @echo "Cleaning composer artifacts..." - @rm -rf vendor/ - @rm -f composer.lock - @echo "Clean completed!" - -# Run the example with default arguments -run: install - @echo "Running S3 Encryption Client v3 PHP example..." - @echo "Bucket: $(BUCKET_NAME)" - @echo "Object Key: $(OBJECT_KEY)" - @echo "KMS Key ID: $(KMS_KEY_ID)" - @echo "Region: $(AWS_REGION)" - @echo "" - @php $(SCRIPT) $(BUCKET_NAME) $(OBJECT_KEY) $(KMS_KEY_ID) $(AWS_REGION) - -# Run with custom arguments -# Usage: make run-custom BUCKET_NAME=my-bucket OBJECT_KEY=my-key KMS_KEY_ID=my-kms-key AWS_REGION=my-region -run-custom: install - @php $(SCRIPT) $(BUCKET_NAME) $(OBJECT_KEY) $(KMS_KEY_ID) $(AWS_REGION) - -# Show help -help: - @echo "S3 Encryption Client PHP v3 Example Makefile" - @echo "" - @echo "Available targets:" - @echo " install - Install PHP dependencies using Composer" - @echo " run - Install dependencies and run the example with default parameters" - @echo " run-custom - Install dependencies and run with custom parameters" - @echo " clean - Remove composer artifacts (vendor/, composer.lock)" - @echo " help - Show this help message" - @echo "" - @echo "Default parameters:" - @echo " BUCKET_NAME = $(BUCKET_NAME)" - @echo " OBJECT_KEY = $(OBJECT_KEY)" - @echo " KMS_KEY_ID = $(KMS_KEY_ID)" - @echo " AWS_REGION = $(AWS_REGION)" - @echo "" - @echo "To run with custom parameters:" - @echo " make run BUCKET_NAME=your-bucket OBJECT_KEY=your-key KMS_KEY_ID=your-kms-key AWS_REGION=your-region" - @echo "" - @echo "Prerequisites:" - @echo " - PHP 7.4+ installed on the system" - @echo " - Composer installed (https://getcomposer.org/)" - @echo " - AWS credentials configured (AWS CLI, environment variables, or IAM role)" - @echo " - Valid S3 bucket and KMS key with appropriate permissions" - @echo " - S3 Encryption Client v3 PHP SDK (included in local-php-sdk)" diff --git a/all-examples/php/v3/composer.json b/all-examples/php/v3/composer.json deleted file mode 100644 index 2ad2469a..00000000 --- a/all-examples/php/v3/composer.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "name": "aws/s3ec-php-v3-example", - "description": "PHP v3 example for Amazon S3 Encryption Client", - "type": "project", - "license": "Apache-2.0", - "repositories": [ - { - "type": "path", - "url": "./local-php-sdk", - "options": { - "symlink": true - } - } - ], - "require": { - "php": ">=7.4", - "aws/aws-sdk-php": "@dev", - "ramsey/uuid": "^4.9" - }, - "autoload": { - "psr-4": { - "AWS\\S3EC\\Example\\": "src/" - } - }, - "config": { - "optimize-autoloader": true, - "platform": { - "php": "8.1" - } - }, - "minimum-stability": "dev", - "prefer-stable": true -} diff --git a/all-examples/php/v3/local-php-sdk b/all-examples/php/v3/local-php-sdk deleted file mode 120000 index 3b9b4cd7..00000000 --- a/all-examples/php/v3/local-php-sdk +++ /dev/null @@ -1 +0,0 @@ -../../../test-server/php-v3-server/local-php-sdk \ No newline at end of file diff --git a/all-examples/php/v3/main.php b/all-examples/php/v3/main.php deleted file mode 100755 index 949a0ce1..00000000 --- a/all-examples/php/v3/main.php +++ /dev/null @@ -1,508 +0,0 @@ -#!/usr/bin/env php - \n"; - echo "Example: {$GLOBALS['argv'][0]} avp-21638 s3ec-php-v3 arn:aws:kms:us-east-2:648638458147:key/a47079da-17e4-45a5-b82e-2bac101cad01 us-east-2\n"; - exit(1); - } - - $bucketName = $GLOBALS['argv'][1]; - $objectKey = $GLOBALS['argv'][2]; - $kmsKeyId = $GLOBALS['argv'][3]; - $region = $GLOBALS['argv'][4]; - - echo "=== S3 Encryption Client v3 Example (PHP) ===\n"; - echo "Bucket: {$bucketName}\n"; - echo "Object Key: {$objectKey}\n"; - echo "KMS Key ID: {$kmsKeyId}\n"; - echo "Region: {$region}\n"; - echo "\n"; - - try { - // Test data for encryption - $testData = "Hello, World! This is a test message for S3 encryption client v3 in PHP."; - echo "Original data: {$testData}\n"; - echo "Data length: " . strlen($testData) . " bytes\n"; - echo "\n"; - - echo "--- Initialize S3 Encryption Client v3 ---\n"; - - // Create regular S3 client - $s3Client = new S3Client([ - 'region' => $region, - 'version' => 'latest' - ]); - - // Create KMS client - $kmsClient = new KmsClient([ - 'region' => $region, - 'version' => 'latest' - ]); - - // Create S3 Encryption Client v3 - // Create S3 Encryption Client v2 - $encryptionClient = new S3EncryptionClientV3($s3Client); - $materialsProvider = new KmsMaterialsProviderV3($kmsClient, $kmsKeyId); - - echo "Successfully initialized S3 Encryption Client v3\n"; - echo "--- Encrypt and Upload Object to S3 ---\n"; - - // Add encryption context - $encryptionContext = [ - 'purpose' => 'example', - 'version' => 'v3', - 'language' => 'php' - ]; - - $cipherOptions = [ - 'Cipher' => 'gcm', - 'KeySize' => 256, - ]; - - // Upload encrypted object using S3 Encryption Client - $putResponse = $encryptionClient->putObject([ - 'Bucket' => $bucketName, - 'Key' => $objectKey, - 'Body' => $testData, - '@MaterialsProvider' => $materialsProvider, - '@KmsEncryptionContext' => $encryptionContext, - '@CommitmentPolicy' => "REQUIRE_ENCRYPT_REQUIRE_DECRYPT", - '@CipherOptions' => $cipherOptions, - ]); - - echo "Successfully uploaded encrypted object to S3!\n"; - echo " Bucket: {$bucketName}\n"; - echo " Key: {$objectKey}\n"; - echo " Encryption Context: " . json_encode($encryptionContext) . "\n"; - echo "\n"; - - echo "--- Download and Decrypt Object from S3 ---\n"; - - // Download and decrypt object using S3 Encryption Client - $getResponse = $encryptionClient->getObject([ - 'Bucket' => $bucketName, - 'Key' => $objectKey, - '@KmsEncryptionContext' => $encryptionContext, - '@MaterialsProvider' => $materialsProvider, - '@CommitmentPolicy' => "REQUIRE_ENCRYPT_REQUIRE_DECRYPT", - '@SecurityProfile' => 'V3' - ]); - - // Read the decrypted data - $decryptedData = (string) $getResponse['Body']; - - echo "Successfully downloaded and decrypted object from S3!\n"; - echo " Object size: " . strlen($decryptedData) . " bytes\n"; - echo " Decrypted data: {$decryptedData}\n"; - echo "\n"; - - echo "--- Verify Roundtrip Success ---\n"; - - // Verify the roundtrip was successful - if ($decryptedData === $testData) { - echo "SUCCESS: Roundtrip encryption/decryption completed successfully!\n"; - echo " Original data matches decrypted data\n"; - echo " Data integrity verified\n"; - } else { - echo "ERROR: Roundtrip failed - data mismatch\n"; - echo " Original: {$testData}\n"; - echo " Decrypted: {$decryptedData}\n"; - exit(1); - } - - // Optionally Delete the Object - // echo "--- Cleanup ---\n"; - // Clean up the test object using regular S3 client - // $s3Client->deleteObject([ - // 'Bucket' => $bucketName, - // 'Key' => $objectKey - // ]); - // echo "Test object deleted from S3\n"; - - echo "\n"; - echo "=== Example completed successfully! ===\n"; - - } catch (AwsException $e) { - $errorCode = $e->getAwsErrorCode(); - $errorMessage = $e->getMessage(); - - if (strpos($errorCode, 'NoSuchBucket') !== false) { - echo "Error: S3 bucket '{$bucketName}' does not exist or is not accessible\n"; - echo " {$errorMessage}\n"; - } elseif (strpos($errorCode, 'NotFoundException') !== false) { - echo "Error: KMS key '{$kmsKeyId}' not found or not accessible\n"; - echo " {$errorMessage}\n"; - } elseif (strpos($errorMessage, 'encryption') !== false) { - echo "S3 Encryption Error: {$errorMessage}\n"; - } else { - echo "AWS Service Error: {$errorMessage}\n"; - echo " Error Code: {$errorCode}\n"; - } - exit(1); - } catch (Exception $e) { - echo "Unexpected error: {$e->getMessage()}\n"; - echo " File: {$e->getFile()}:{$e->getLine()}\n"; - exit(1); - } -} - -function testMigration(): void { - if (count($GLOBALS['argv']) !== 5) { - echo "Usage: {$GLOBALS['argv'][0]} \n"; - echo "Example: {$GLOBALS['argv'][0]} avp-21638 s3ec-php-v3 arn:aws:kms:us-east-2:648638458147:key/a47079da-17e4-45a5-b82e-2bac101cad01 us-east-2\n"; - exit(1); - } - - $bucketName = $GLOBALS['argv'][1]; - $objectKey = $GLOBALS['argv'][2]; - $kmsKeyId = $GLOBALS['argv'][3]; - $region = $GLOBALS['argv'][4]; - - echo "=== S3 Encryption Client Pre-migration (V2) Example ===\n"; - echo "Bucket: {$bucketName}\n"; - echo "Object Key: {$objectKey}\n"; - echo "KMS Key ID: {$kmsKeyId}\n"; - echo "Region: {$region}\n"; - echo "\n"; - - try { - $testData = "Hello, World! This is a test message for S3 encryption client Pre-migration (V2) in PHP."; - echo "Original data: {$testData}\n"; - echo "Data length: " . strlen($testData) . " bytes\n"; - echo "\n"; - - $v2EncryptionClient = new S3EncryptionClientV2( - new S3Client([ - 'region' => $region, - 'version' => 'latest', - ]) - ); - - $materialsProviderV2 = new KmsMaterialsProviderV2( - new KmsClient([ - 'region' => $region, - 'version' => 'latest', - ]), - $kmsKeyId - ); - - $cipherOptions = [ - 'Cipher' => 'gcm', - 'KeySize' => 256, - ]; - - $v2EncryptionClient->putObject([ - '@MaterialsProvider' => $materialsProviderV2, - '@CipherOptions' => $cipherOptions, - '@KmsEncryptionContext' => ['context-key' => 'context-value'], - 'Bucket' => $bucketName, - 'Key' => $objectKey, - 'Body' => $testData, - ]); - - $getResponse = $v2EncryptionClient->getObject([ - '@KmsAllowDecryptWithAnyCmk' => true, - '@SecurityProfile' => 'V2_AND_LEGACY', - '@CommitmentPolicy' => 'FORBID_ENCRYPT_ALLOW_DECRYPT', - '@MaterialsProvider' => $materialsProviderV2, - '@CipherOptions' => $cipherOptions, - 'Bucket' => $bucketName, - 'Key' => $objectKey, - ]); - - // Read the decrypted data - $decryptedData = (string) $getResponse['Body']; - - echo "Successfully downloaded and decrypted object from S3!\n"; - echo " Object size: " . strlen($decryptedData) . " bytes\n"; - echo " Decrypted data: {$decryptedData}\n"; - echo "\n"; - - echo "--- Verify Roundtrip Success ---\n"; - - // Verify the roundtrip was successful - if ($decryptedData === $testData) { - echo "SUCCESS: Roundtrip encryption/decryption completed successfully!\n"; - echo " Original data matches decrypted data\n"; - echo " Data integrity verified\n"; - } else { - echo "ERROR: Roundtrip failed - data mismatch\n"; - echo " Original: {$testData}\n"; - echo " Decrypted: {$decryptedData}\n"; - exit(1); - } - - // Optionally Delete the Object - // echo "--- Cleanup ---\n"; - // Clean up the test object using regular S3 client - // $s3Client->deleteObject([ - // 'Bucket' => $bucketName, - // 'Key' => $objectKey - // ]); - // echo "Test object deleted from S3\n"; - - echo "\n"; - echo "=== Example completed successfully! ===\n"; - - } catch (AwsException $e) { - $errorCode = $e->getAwsErrorCode(); - $errorMessage = $e->getMessage(); - - if (strpos($errorCode, 'NoSuchBucket') !== false) { - echo "Error: S3 bucket '{$bucketName}' does not exist or is not accessible\n"; - echo " {$errorMessage}\n"; - } elseif (strpos($errorCode, 'NotFoundException') !== false) { - echo "Error: KMS key '{$kmsKeyId}' not found or not accessible\n"; - echo " {$errorMessage}\n"; - } elseif (strpos($errorMessage, 'encryption') !== false) { - echo "S3 Encryption Error: {$errorMessage}\n"; - } else { - echo "AWS Service Error: {$errorMessage}\n"; - echo " Error Code: {$errorCode}\n"; - } - exit(1); - } catch (Exception $e) { - echo "Unexpected error: {$e->getMessage()}\n"; - echo " File: {$e->getFile()}:{$e->getLine()}\n"; - exit(1); - } - - echo "=== S3 Encryption Client during migration (V3 with backward compatibility) Example ===\n"; - echo "Bucket: {$bucketName}\n"; - echo "Object Key: {$objectKey}\n"; - echo "KMS Key ID: {$kmsKeyId}\n"; - echo "Region: {$region}\n"; - echo "\n"; - - try { - $testData = "Hello, World! This is a test message for S3 encryption client during migration (V3 with backward compatibility) in PHP."; - echo "Original data: {$testData}\n"; - echo "Data length: " . strlen($testData) . " bytes\n"; - echo "\n"; - - $v3EncryptionClient = new S3EncryptionClientV3( - new S3Client([ - 'region' => $region, - 'version' => 'latest', - ]) - ); - - $materialsProviderV3 = new KmsMaterialsProviderV3( - new KmsClient([ - 'region' => $region, - 'version' => 'latest', - ]), - $kmsKeyId - ); - - $cipherOptions = [ - 'Cipher' => 'gcm', - 'KeySize' => 256, - ]; - - $v3EncryptionClient->putObject([ - '@MaterialsProvider' => $materialsProviderV3, - '@CipherOptions' => $cipherOptions, - '@CommitmentPolicy' => 'REQUIRE_ENCRYPT_ALLOW_DECRYPT', - '@KmsEncryptionContext' => ['context-key' => 'context-value'], - 'Bucket' => $bucketName, - 'Key' => $objectKey, - 'Body' => $testData, - ]); - - $getResponse = $v3EncryptionClient->getObject([ - '@KmsAllowDecryptWithAnyCmk' => true, - '@SecurityProfile' => 'V3_AND_LEGACY', - '@CommitmentPolicy' => 'REQUIRE_ENCRYPT_ALLOW_DECRYPT', - '@MaterialsProvider' => $materialsProviderV3, - '@CipherOptions' => $cipherOptions, - 'Bucket' => $bucketName, - 'Key' => $objectKey, - ]); - - // Read the decrypted data - $decryptedData = (string) $getResponse['Body']; - - echo "Successfully downloaded and decrypted object from S3!\n"; - echo " Object size: " . strlen($decryptedData) . " bytes\n"; - echo " Decrypted data: {$decryptedData}\n"; - echo "\n"; - - echo "--- Verify Roundtrip Success ---\n"; - - // Verify the roundtrip was successful - if ($decryptedData === $testData) { - echo "SUCCESS: Roundtrip encryption/decryption completed successfully!\n"; - echo " Original data matches decrypted data\n"; - echo " Data integrity verified\n"; - } else { - echo "ERROR: Roundtrip failed - data mismatch\n"; - echo " Original: {$testData}\n"; - echo " Decrypted: {$decryptedData}\n"; - exit(1); - } - - // Optionally Delete the Object - // echo "--- Cleanup ---\n"; - // Clean up the test object using regular S3 client - // $s3Client->deleteObject([ - // 'Bucket' => $bucketName, - // 'Key' => $objectKey - // ]); - // echo "Test object deleted from S3\n"; - - echo "\n"; - echo "=== Example completed successfully! ===\n"; - - } catch (AwsException $e) { - $errorCode = $e->getAwsErrorCode(); - $errorMessage = $e->getMessage(); - - if (strpos($errorCode, 'NoSuchBucket') !== false) { - echo "Error: S3 bucket '{$bucketName}' does not exist or is not accessible\n"; - echo " {$errorMessage}\n"; - } elseif (strpos($errorCode, 'NotFoundException') !== false) { - echo "Error: KMS key '{$kmsKeyId}' not found or not accessible\n"; - echo " {$errorMessage}\n"; - } elseif (strpos($errorMessage, 'encryption') !== false) { - echo "S3 Encryption Error: {$errorMessage}\n"; - } else { - echo "AWS Service Error: {$errorMessage}\n"; - echo " Error Code: {$errorCode}\n"; - } - exit(1); - } catch (Exception $e) { - echo "Unexpected error: {$e->getMessage()}\n"; - echo " File: {$e->getFile()}:{$e->getLine()}\n"; - exit(1); - } - - echo "=== S3 Encryption Client post-migration (V3 with key commitment) Example ===\n"; - echo "Bucket: {$bucketName}\n"; - echo "Object Key: {$objectKey}\n"; - echo "KMS Key ID: {$kmsKeyId}\n"; - echo "Region: {$region}\n"; - echo "\n"; - - try { - $testData = "Hello, World! This is a test message for S3 encryption client post-migration (V3 with key commitment) in PHP."; - echo "Original data: {$testData}\n"; - echo "Data length: " . strlen($testData) . " bytes\n"; - echo "\n"; - - $v3EncryptionClient = new S3EncryptionClientV3( - new S3Client([ - 'region' => $region, - 'version' => 'latest', - ]) - ); - - $materialsProviderV3 = new KmsMaterialsProviderV3( - new KmsClient([ - 'region' => $region, - 'version' => 'latest', - ]), - $kmsKeyId - ); - - $cipherOptions = [ - 'Cipher' => 'gcm', - 'KeySize' => 256, - ]; - - $v3EncryptionClient->putObject([ - '@MaterialsProvider' => $materialsProviderV3, - '@CipherOptions' => $cipherOptions, - '@CommitmentPolicy' => 'REQUIRE_ENCRYPT_REQUIRE_DECRYPT', - '@KmsEncryptionContext' => ['context-key' => 'context-value'], - 'Bucket' => $bucketName, - 'Key' => $objectKey, - 'Body' => $testData, - ]); - - $getResponse = $v3EncryptionClient->getObject([ - '@KmsAllowDecryptWithAnyCmk' => true, - '@SecurityProfile' => 'V3', - '@CommitmentPolicy' => 'REQUIRE_ENCRYPT_REQUIRE_DECRYPT', - '@MaterialsProvider' => $materialsProviderV3, - '@CipherOptions' => $cipherOptions, - 'Bucket' => $bucketName, - 'Key' => $objectKey, - ]); - - // Read the decrypted data - $decryptedData = (string) $getResponse['Body']; - - echo "Successfully downloaded and decrypted object from S3!\n"; - echo " Object size: " . strlen($decryptedData) . " bytes\n"; - echo " Decrypted data: {$decryptedData}\n"; - echo "\n"; - - echo "--- Verify Roundtrip Success ---\n"; - - // Verify the roundtrip was successful - if ($decryptedData === $testData) { - echo "SUCCESS: Roundtrip encryption/decryption completed successfully!\n"; - echo " Original data matches decrypted data\n"; - echo " Data integrity verified\n"; - } else { - echo "ERROR: Roundtrip failed - data mismatch\n"; - echo " Original: {$testData}\n"; - echo " Decrypted: {$decryptedData}\n"; - exit(1); - } - - // Optionally Delete the Object - // echo "--- Cleanup ---\n"; - // Clean up the test object using regular S3 client - // $s3Client->deleteObject([ - // 'Bucket' => $bucketName, - // 'Key' => $objectKey - // ]); - // echo "Test object deleted from S3\n"; - - echo "\n"; - echo "=== Example completed successfully! ===\n"; - - } catch (AwsException $e) { - $errorCode = $e->getAwsErrorCode(); - $errorMessage = $e->getMessage(); - - if (strpos($errorCode, 'NoSuchBucket') !== false) { - echo "Error: S3 bucket '{$bucketName}' does not exist or is not accessible\n"; - echo " {$errorMessage}\n"; - } elseif (strpos($errorCode, 'NotFoundException') !== false) { - echo "Error: KMS key '{$kmsKeyId}' not found or not accessible\n"; - echo " {$errorMessage}\n"; - } elseif (strpos($errorMessage, 'encryption') !== false) { - echo "S3 Encryption Error: {$errorMessage}\n"; - } else { - echo "AWS Service Error: {$errorMessage}\n"; - echo " Error Code: {$errorCode}\n"; - } - exit(1); - } catch (Exception $e) { - echo "Unexpected error: {$e->getMessage()}\n"; - echo " File: {$e->getFile()}:{$e->getLine()}\n"; - exit(1); - } -} - -// Run the main function if this script is executed directly -if (php_sapi_name() === 'cli' && isset($GLOBALS['argv']) && basename($GLOBALS['argv'][0]) === basename(__FILE__)) { - main(); - testMigration(); -} diff --git a/all-examples/ruby/v2/Gemfile b/all-examples/ruby/v2/Gemfile deleted file mode 100644 index 5f51bf18..00000000 --- a/all-examples/ruby/v2/Gemfile +++ /dev/null @@ -1,12 +0,0 @@ -source 'https://rubygems.org' - -ruby '>= 2.7.0' - -gem 'aws-sdk-s3', path: 'local-ruby-sdk/gems/aws-sdk-s3' -gem 'aws-sdk-kms', path: 'local-ruby-sdk/gems/aws-sdk-kms' -gem 'json', '~> 2.0' -gem 'rexml', '~> 3.0' - -group :development do - gem 'rubocop', '~> 1.0' -end diff --git a/all-examples/ruby/v2/Gemfile.lock b/all-examples/ruby/v2/Gemfile.lock deleted file mode 100644 index 7c4a32c4..00000000 --- a/all-examples/ruby/v2/Gemfile.lock +++ /dev/null @@ -1,82 +0,0 @@ -PATH - remote: local-ruby-sdk/gems/aws-sdk-kms - specs: - aws-sdk-kms (1.115.0) - aws-sdk-core (~> 3, >= 3.234.0) - aws-sigv4 (~> 1.5) - -PATH - remote: local-ruby-sdk/gems/aws-sdk-s3 - specs: - aws-sdk-s3 (1.201.0) - aws-sdk-core (~> 3, >= 3.234.0) - aws-sdk-kms (~> 1) - aws-sigv4 (~> 1.5) - -GEM - remote: https://rubygems.org/ - specs: - ast (2.4.3) - aws-eventstream (1.4.0) - aws-partitions (1.1177.0) - aws-sdk-core (3.235.0) - aws-eventstream (~> 1, >= 1.3.0) - aws-partitions (~> 1, >= 1.992.0) - aws-sigv4 (~> 1.9) - base64 - bigdecimal - jmespath (~> 1, >= 1.6.1) - logger - aws-sigv4 (1.12.1) - aws-eventstream (~> 1, >= 1.0.2) - base64 (0.3.0) - bigdecimal (3.3.1) - jmespath (1.6.2) - json (2.15.2) - language_server-protocol (3.17.0.5) - lint_roller (1.1.0) - logger (1.7.0) - parallel (1.27.0) - parser (3.3.10.0) - ast (~> 2.4.1) - racc - prism (1.6.0) - racc (1.8.1) - rainbow (3.1.1) - regexp_parser (2.11.3) - rexml (3.4.4) - rubocop (1.81.6) - json (~> 2.3) - language_server-protocol (~> 3.17.0.2) - lint_roller (~> 1.1.0) - parallel (~> 1.10) - parser (>= 3.3.0.2) - rainbow (>= 2.2.2, < 4.0) - regexp_parser (>= 2.9.3, < 3.0) - rubocop-ast (>= 1.47.1, < 2.0) - ruby-progressbar (~> 1.7) - unicode-display_width (>= 2.4.0, < 4.0) - rubocop-ast (1.47.1) - parser (>= 3.3.7.2) - prism (~> 1.4) - ruby-progressbar (1.13.0) - unicode-display_width (3.2.0) - unicode-emoji (~> 4.1) - unicode-emoji (4.1.0) - -PLATFORMS - arm64-darwin-24 - ruby - -DEPENDENCIES - aws-sdk-kms! - aws-sdk-s3! - json (~> 2.0) - rexml (~> 3.0) - rubocop (~> 1.0) - -RUBY VERSION - ruby 3.4.7p58 - -BUNDLED WITH - 2.7.2 diff --git a/all-examples/ruby/v2/Makefile b/all-examples/ruby/v2/Makefile deleted file mode 100644 index cfa1adef..00000000 --- a/all-examples/ruby/v2/Makefile +++ /dev/null @@ -1,70 +0,0 @@ -# Makefile for S3 Encryption Client Ruby v2 Example - -# Default target -.PHONY: all install clean run help - -# Variables -SCRIPT = main.rb - -# Default arguments for running the example -# Override these when calling make run -BUCKET_NAME ?= avp-21638 -OBJECT_KEY ?= s3ec-ruby-v2 -KMS_KEY_ID ?= arn:aws:kms:us-east-2:648638458147:key/a47079da-17e4-45a5-b82e-2bac101cad01 -AWS_REGION ?= us-east-2 - -all: install - -# Install dependencies using Bundler -install: - @echo "Installing Ruby dependencies..." - @bundle install - @echo "Dependencies installed successfully!" - -# Clean bundle artifacts -clean: - @echo "Cleaning bundle artifacts..." - @bundle clean --force - @echo "Clean completed!" - -# Run the example with default arguments -run: install - @echo "Running S3 Encryption Client v2 Ruby example..." - @echo "Bucket: $(BUCKET_NAME)" - @echo "Object Key: $(OBJECT_KEY)" - @echo "KMS Key ID: $(KMS_KEY_ID)" - @echo "Region: $(AWS_REGION)" - @echo "" - @bundle exec ruby $(SCRIPT) $(BUCKET_NAME) $(OBJECT_KEY) $(KMS_KEY_ID) $(AWS_REGION) - -# Run with custom arguments -# Usage: make run-custom BUCKET_NAME=my-bucket OBJECT_KEY=my-key KMS_KEY_ID=my-kms-key AWS_REGION=my-region -run-custom: install - @bundle exec ruby $(SCRIPT) $(BUCKET_NAME) $(OBJECT_KEY) $(KMS_KEY_ID) $(AWS_REGION) - -# Show help -help: - @echo "S3 Encryption Client Ruby v2 Example Makefile" - @echo "" - @echo "Available targets:" - @echo " install - Install Ruby dependencies using Bundler" - @echo " run - Install dependencies and run the example with default parameters" - @echo " run-custom - Install dependencies and run with custom parameters" - @echo " clean - Remove bundle artifacts" - @echo " help - Show this help message" - @echo "" - @echo "Default parameters:" - @echo " BUCKET_NAME = $(BUCKET_NAME)" - @echo " OBJECT_KEY = $(OBJECT_KEY)" - @echo " KMS_KEY_ID = $(KMS_KEY_ID)" - @echo " AWS_REGION = $(AWS_REGION)" - @echo "" - @echo "To run with custom parameters:" - @echo " make run BUCKET_NAME=your-bucket OBJECT_KEY=your-key KMS_KEY_ID=your-kms-key AWS_REGION=your-region" - @echo "" - @echo "Prerequisites:" - @echo " - Ruby 3.0+ installed on the system" - @echo " - Bundler gem installed (gem install bundler)" - @echo " - AWS credentials configured (AWS CLI, environment variables, or IAM role)" - @echo " - Valid S3 bucket and KMS key with appropriate permissions" - @echo " - S3 Encryption Client v2 Ruby SDK (included in local-ruby-sdk)" diff --git a/all-examples/ruby/v2/local-ruby-sdk b/all-examples/ruby/v2/local-ruby-sdk deleted file mode 120000 index 7abd378c..00000000 --- a/all-examples/ruby/v2/local-ruby-sdk +++ /dev/null @@ -1 +0,0 @@ -../../../test-server/ruby-v2-server/local-ruby-sdk \ No newline at end of file diff --git a/all-examples/ruby/v2/main.rb b/all-examples/ruby/v2/main.rb deleted file mode 100644 index 3fc86f7c..00000000 --- a/all-examples/ruby/v2/main.rb +++ /dev/null @@ -1,150 +0,0 @@ -#!/usr/bin/env ruby - -require 'aws-sdk-s3' -require 'aws-sdk-kms' -require 'json' - -# See: https://github.com/ruby/openssl/issues/949 -Aws.use_bundled_cert! - -def main - # Check command line arguments - if ARGV.length != 4 - puts "Usage: #{$0} " - puts "Example: #{$0} avp-21638 s3ec-ruby-v2 arn:aws:kms:us-east-2:648638458147:key/a47079da-17e4-45a5-b82e-2bac101cad01 us-east-2" - exit 1 - end - - bucket_name = ARGV[0] - object_key = ARGV[1] - kms_key_id = ARGV[2] - region = ARGV[3] - - puts "=== S3 Encryption Client v2 Example (Ruby) ===" - puts "Bucket: #{bucket_name}" - puts "Object Key: #{object_key}" - puts "KMS Key ID: #{kms_key_id}" - puts "Region: #{region}" - puts - - begin - # Test data for encryption - test_data = "Hello, World! This is a test message for S3 encryption client v2 in Ruby." - puts "Original data: #{test_data}" - puts "Data length: #{test_data.length} bytes" - puts - - puts "--- Initialize S3 Encryption Client v2 ---" - - # Create regular S3 client - s3_client = Aws::S3::Client.new(region: region) - - # Create KMS client - kms_client = Aws::KMS::Client.new(region: region) - - # Create S3 Encryption Client v2 - encryption_client = Aws::S3::EncryptionV2::Client.new( - client: s3_client, - kms_key_id: kms_key_id, - kms_client: kms_client, - key_wrap_schema: :kms_context, - content_encryption_schema: :aes_gcm_no_padding, - security_profile: :v2 - ) - - puts "Successfully initialized S3 Encryption Client v2" - puts "--- Encrypt and Upload Object to S3 ---" - - # Add encryption context - encryption_context = { - 'purpose' => 'example', - 'version' => 'v2', - 'language' => 'ruby' - } - - # Upload encrypted object using S3 Encryption Client - put_response = encryption_client.put_object({ - bucket: bucket_name, - key: object_key, - body: test_data, - kms_encryption_context: encryption_context - }) - - puts "Successfully uploaded encrypted object to S3!" - puts " Bucket: #{bucket_name}" - puts " Key: #{object_key}" - puts " Encryption Context: #{encryption_context}" - puts - - puts "--- Download and Decrypt Object from S3 ---" - - # Download and decrypt object using S3 Encryption Client - get_response = encryption_client.get_object({ - bucket: bucket_name, - key: object_key, - kms_encryption_context: encryption_context - }) - - # Read the decrypted data - decrypted_data = get_response.body.read - - puts "Successfully downloaded and decrypted object from S3!" - puts " Object size: #{decrypted_data.length} bytes" - puts " Decrypted data: #{decrypted_data}" - puts - - puts "--- Verify Roundtrip Success ---" - - # Verify the roundtrip was successful - if decrypted_data == test_data - puts "SUCCESS: Roundtrip encryption/decryption completed successfully!" - puts " Original data matches decrypted data" - puts " Data integrity verified" - else - puts "ERROR: Roundtrip failed - data mismatch" - puts " Original: #{test_data}" - puts " Decrypted: #{decrypted_data}" - exit 1 - end - - # Optionally Delete the Object - #puts "--- Cleanup ---" - # Clean up the test object using regular S3 client - # s3_client.delete_object({ - # bucket: bucket_name, - # key: object_key - # }) - # puts "Test object deleted from S3" - - puts - puts "=== Example completed successfully! ===" - - rescue Aws::S3::Errors::NoSuchBucket => e - puts "Error: S3 bucket '#{bucket_name}' does not exist or is not accessible" - puts " #{e.message}" - exit 1 - rescue Aws::KMS::Errors::NotFoundException => e - puts "Error: KMS key '#{kms_key_id}' not found or not accessible" - puts " #{e.message}" - exit 1 - rescue Aws::S3::EncryptionV2::Errors::EncryptionError => e - puts "S3 Encryption Error: #{e.message}" - exit 1 - rescue Aws::S3::EncryptionV2::Errors::DecryptionError => e - puts "S3 Decryption Error: #{e.message}" - exit 1 - rescue Aws::Errors::ServiceError => e - puts "AWS Service Error: #{e.message}" - puts " Error Code: #{e.code}" if e.respond_to?(:code) - exit 1 - rescue StandardError => e - puts "Unexpected error: #{e.message}" - puts e.backtrace.first(5) - exit 1 - end -end - -# Run the main function if this script is executed directly -if __FILE__ == $0 - main -end diff --git a/all-examples/ruby/v3/Gemfile b/all-examples/ruby/v3/Gemfile deleted file mode 100644 index 5f51bf18..00000000 --- a/all-examples/ruby/v3/Gemfile +++ /dev/null @@ -1,12 +0,0 @@ -source 'https://rubygems.org' - -ruby '>= 2.7.0' - -gem 'aws-sdk-s3', path: 'local-ruby-sdk/gems/aws-sdk-s3' -gem 'aws-sdk-kms', path: 'local-ruby-sdk/gems/aws-sdk-kms' -gem 'json', '~> 2.0' -gem 'rexml', '~> 3.0' - -group :development do - gem 'rubocop', '~> 1.0' -end diff --git a/all-examples/ruby/v3/Makefile b/all-examples/ruby/v3/Makefile deleted file mode 100644 index b27bf29f..00000000 --- a/all-examples/ruby/v3/Makefile +++ /dev/null @@ -1,70 +0,0 @@ -# Makefile for S3 Encryption Client Ruby v3 Example - -# Default target -.PHONY: all install clean run help - -# Variables -SCRIPT = main.rb - -# Default arguments for running the example -# Override these when calling make run -BUCKET_NAME ?= avp-21638 -OBJECT_KEY ?= s3ec-ruby-v3 -KMS_KEY_ID ?= arn:aws:kms:us-east-2:648638458147:key/a47079da-17e4-45a5-b82e-2bac101cad01 -AWS_REGION ?= us-east-2 - -all: install - -# Install dependencies using Bundler -install: - @echo "Installing Ruby dependencies..." - @bundle install - @echo "Dependencies installed successfully!" - -# Clean bundle artifacts -clean: - @echo "Cleaning bundle artifacts..." - @bundle clean --force - @echo "Clean completed!" - -# Run the example with default arguments -run: install - @echo "Running S3 Encryption Client v3 Ruby example..." - @echo "Bucket: $(BUCKET_NAME)" - @echo "Object Key: $(OBJECT_KEY)" - @echo "KMS Key ID: $(KMS_KEY_ID)" - @echo "Region: $(AWS_REGION)" - @echo "" - @bundle exec ruby $(SCRIPT) $(BUCKET_NAME) $(OBJECT_KEY) $(KMS_KEY_ID) $(AWS_REGION) - -# Run with custom arguments -# Usage: make run-custom BUCKET_NAME=my-bucket OBJECT_KEY=my-key KMS_KEY_ID=my-kms-key AWS_REGION=my-region -run-custom: install - @bundle exec ruby $(SCRIPT) $(BUCKET_NAME) $(OBJECT_KEY) $(KMS_KEY_ID) $(AWS_REGION) - -# Show help -help: - @echo "S3 Encryption Client Ruby v3 Example Makefile" - @echo "" - @echo "Available targets:" - @echo " install - Install Ruby dependencies using Bundler" - @echo " run - Install dependencies and run the example with default parameters" - @echo " run-custom - Install dependencies and run with custom parameters" - @echo " clean - Remove bundle artifacts" - @echo " help - Show this help message" - @echo "" - @echo "Default parameters:" - @echo " BUCKET_NAME = $(BUCKET_NAME)" - @echo " OBJECT_KEY = $(OBJECT_KEY)" - @echo " KMS_KEY_ID = $(KMS_KEY_ID)" - @echo " AWS_REGION = $(AWS_REGION)" - @echo "" - @echo "To run with custom parameters:" - @echo " make run BUCKET_NAME=your-bucket OBJECT_KEY=your-key KMS_KEY_ID=your-kms-key AWS_REGION=your-region" - @echo "" - @echo "Prerequisites:" - @echo " - Ruby 3.0+ installed on the system" - @echo " - Bundler gem installed (gem install bundler)" - @echo " - AWS credentials configured (AWS CLI, environment variables, or IAM role)" - @echo " - Valid S3 bucket and KMS key with appropriate permissions" - @echo " - S3 Encryption Client v3 Ruby SDK (included in local-ruby-sdk)" diff --git a/all-examples/ruby/v3/local-ruby-sdk b/all-examples/ruby/v3/local-ruby-sdk deleted file mode 120000 index 49be2657..00000000 --- a/all-examples/ruby/v3/local-ruby-sdk +++ /dev/null @@ -1 +0,0 @@ -../../../test-server/ruby-v3-server/local-ruby-sdk \ No newline at end of file diff --git a/all-examples/ruby/v3/main.rb b/all-examples/ruby/v3/main.rb deleted file mode 100644 index 59743515..00000000 --- a/all-examples/ruby/v3/main.rb +++ /dev/null @@ -1,148 +0,0 @@ -#!/usr/bin/env ruby - -require 'aws-sdk-s3' -require 'aws-sdk-kms' -require 'json' - -# See: https://github.com/ruby/openssl/issues/949 -Aws.use_bundled_cert! - -def main - # Check command line arguments - if ARGV.length != 4 - puts "Usage: #{$0} " - puts "Example: #{$0} avp-21638 s3ec-ruby-v3 arn:aws:kms:us-east-2:648638458147:key/a47079da-17e4-45a5-b82e-2bac101cad01 us-east-2" - exit 1 - end - - bucket_name = ARGV[0] - object_key = ARGV[1] - kms_key_id = ARGV[2] - region = ARGV[3] - - puts "=== S3 Encryption Client v3 Example (Ruby) ===" - puts "Bucket: #{bucket_name}" - puts "Object Key: #{object_key}" - puts "KMS Key ID: #{kms_key_id}" - puts "Region: #{region}" - puts - - begin - # Test data for encryption - test_data = "Hello, World! This is a test message for S3 encryption client v3 in Ruby." - puts "Original data: #{test_data}" - puts "Data length: #{test_data.length} bytes" - puts - - puts "--- Initialize S3 Encryption Client v3 ---" - - # Create regular S3 client - s3_client = Aws::S3::Client.new(region: region) - - # Create KMS client - kms_client = Aws::KMS::Client.new(region: region) - - # Create S3 Encryption Client v3 - encryption_client = Aws::S3::EncryptionV3::Client.new( - client: s3_client, - kms_key_id: kms_key_id, - kms_client: kms_client, - key_wrap_schema: :kms_context - ) - - puts "Successfully initialized S3 Encryption Client v3" - puts "--- Encrypt and Upload Object to S3 ---" - - # Add encryption context - encryption_context = { - 'purpose' => 'example', - 'version' => 'v3', - 'language' => 'ruby' - } - - # Upload encrypted object using S3 Encryption Client - put_response = encryption_client.put_object({ - bucket: bucket_name, - key: object_key, - body: test_data, - kms_encryption_context: encryption_context - }) - - puts "Successfully uploaded encrypted object to S3!" - puts " Bucket: #{bucket_name}" - puts " Key: #{object_key}" - puts " Encryption Context: #{encryption_context}" - puts - - puts "--- Download and Decrypt Object from S3 ---" - - # Download and decrypt object using S3 Encryption Client - get_response = encryption_client.get_object({ - bucket: bucket_name, - key: object_key, - kms_encryption_context: encryption_context - }) - - # Read the decrypted data - decrypted_data = get_response.body.read - - puts "Successfully downloaded and decrypted object from S3!" - puts " Object size: #{decrypted_data.length} bytes" - puts " Decrypted data: #{decrypted_data}" - puts - - puts "--- Verify Roundtrip Success ---" - - # Verify the roundtrip was successful - if decrypted_data == test_data - puts "SUCCESS: Roundtrip encryption/decryption completed successfully!" - puts " Original data matches decrypted data" - puts " Data integrity verified" - else - puts "ERROR: Roundtrip failed - data mismatch" - puts " Original: #{test_data}" - puts " Decrypted: #{decrypted_data}" - exit 1 - end - - # Optionally Delete the Object - #puts "--- Cleanup ---" - # Clean up the test object using regular S3 client - # s3_client.delete_object({ - # bucket: bucket_name, - # key: object_key - # }) - # puts "Test object deleted from S3" - - puts - puts "=== Example completed successfully! ===" - - rescue Aws::S3::Errors::NoSuchBucket => e - puts "Error: S3 bucket '#{bucket_name}' does not exist or is not accessible" - puts " #{e.message}" - exit 1 - rescue Aws::KMS::Errors::NotFoundException => e - puts "Error: KMS key '#{kms_key_id}' not found or not accessible" - puts " #{e.message}" - exit 1 - rescue Aws::S3::EncryptionV3::Errors::EncryptionError => e - puts "S3 Encryption Error: #{e.message}" - exit 1 - rescue Aws::S3::EncryptionV3::Errors::DecryptionError => e - puts "S3 Decryption Error: #{e.message}" - exit 1 - rescue Aws::Errors::ServiceError => e - puts "AWS Service Error: #{e.message}" - puts " Error Code: #{e.code}" if e.respond_to?(:code) - exit 1 - rescue StandardError => e - puts "Unexpected error: #{e.message}" - puts e.backtrace.first(5) - exit 1 - end -end - -# Run the main function if this script is executed directly -if __FILE__ == $0 - main -end From a277b60b51761ed4aca753dccfa32e70f0d14ef2 Mon Sep 17 00:00:00 2001 From: Kess Plasmeier Date: Fri, 30 Jan 2026 13:45:15 -0800 Subject: [PATCH 25/38] fix CI --- .github/workflows/main.yml | 8 -------- 1 file changed, 8 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index d53b9dca..52c3e465 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -35,11 +35,3 @@ jobs: name: Run Duvet uses: ./.github/workflows/duvet.yml secrets: inherit - - run-examples: - permissions: - id-token: write - contents: read - name: Run Examples - uses: ./.github/workflows/examples.yml - secrets: inherit From 1d9fac0743c7dba7caf707642c7c777a535f72fd Mon Sep 17 00:00:00 2001 From: Kess Plasmeier Date: Fri, 30 Jan 2026 14:05:07 -0800 Subject: [PATCH 26/38] remove commented out code from java-tests --- .../amazon/encryption/s3/TestUtils.java | 63 ------------------- 1 file changed, 63 deletions(-) diff --git a/test-server/java-tests/src/it/java/software/amazon/encryption/s3/TestUtils.java b/test-server/java-tests/src/it/java/software/amazon/encryption/s3/TestUtils.java index a9334eed..2b9cd062 100644 --- a/test-server/java-tests/src/it/java/software/amazon/encryption/s3/TestUtils.java +++ b/test-server/java-tests/src/it/java/software/amazon/encryption/s3/TestUtils.java @@ -75,7 +75,6 @@ public class TestUtils { public static final String CPP_V2_TRANSITION = "CPP-V2-Transition"; public static final String CPP_V3 = "CPP-V3"; -// public static final String RUBY_V2_CURRENT = "Ruby-V2-Current"; public static final String RUBY_V2_TRANSITION = "Ruby-V2-Transition"; public static final String RUBY_V3 = "Ruby-V3"; @@ -92,30 +91,24 @@ public class TestUtils { // Sets of unsupported features by language public static final Set ENCRYPTION_CONTEXT_ON_DECRYPT_UNSUPPORTED = Set.of(PHP_V2_TRANSITION, PHP_V3, NET_V3_TRANSITION, NET_V4); -// Set.of(GO_V3_CURRENT, PHP_V2_CURRENT, PHP_V2_TRANSITION, PHP_V3, NET_V2_CURRENT, NET_V3_CURRENT, NET_V3_TRANSITION, NET_V4); public static final Set ENCRYPTION_CONTEXT_ON_ENCRYPT_UNSUPPORTED = Set.of(NET_V3_TRANSITION, NET_V4); -// Set.of(NET_V2_CURRENT, NET_V3_CURRENT, NET_V3_TRANSITION, NET_V4); public static final Set RE_ENCRYPT_SUPPORTED = Set.of(JAVA_V3_TRANSITION, JAVA_V4); -// Set.of(JAVA_V3_CURRENT, JAVA_V3_TRANSITION, JAVA_V4); public static final Set RANGED_GETS_SUPPORTED = Set.of( JAVA_V3_TRANSITION, JAVA_V4, CPP_V2_TRANSITION, CPP_V3 -// JAVA_V3_CURRENT, JAVA_V3_TRANSITION, JAVA_V4, CPP_V2_CURRENT, CPP_V2_TRANSITION, CPP_V3 ); // Cpp only supports Raw AES public static final Set RAW_AES_SUPPORTED = Set.of(JAVA_V3_TRANSITION, JAVA_V4, NET_V3_TRANSITION, NET_V4, RUBY_V2_TRANSITION, RUBY_V3, CPP_V2_TRANSITION, CPP_V3); -// Set.of(JAVA_V3_CURRENT, JAVA_V3_TRANSITION, JAVA_V4, NET_V2_CURRENT, NET_V3_CURRENT, NET_V3_TRANSITION, NET_V4, RUBY_V2_TRANSITION, RUBY_V3, CPP_V2_CURRENT, CPP_V2_TRANSITION, CPP_V3); public static final Set RAW_RSA_SUPPORTED = Set.of(JAVA_V3_TRANSITION, JAVA_V4, NET_V3_TRANSITION, NET_V4, RUBY_V2_TRANSITION, RUBY_V3); -// Set.of(JAVA_V3_CURRENT, JAVA_V3_TRANSITION, JAVA_V4, NET_V2_CURRENT, NET_V3_CURRENT, NET_V3_TRANSITION, NET_V4, RUBY_V2_TRANSITION, RUBY_V3); // Intersection of RAW_AES_SUPPORTED and RAW_RSA_SUPPORTED public static final Set RAW_SUPPORTED = @@ -127,13 +120,10 @@ public class TestUtils { // Python MUST support decrypting KMS instruction files, but does not yet. public static final Set KMS_INSTRUCTION_FILE_UNSUPPORTED = Set.of(NET_V2_TRANSITION, NET_V3_TRANSITION, NET_V4); -// Set.of(NET_V2_CURRENT, NET_V2_TRANSITION, NET_V3_CURRENT, NET_V3_TRANSITION, NET_V4); // Go does not write with instruction files public static final Set INSTRUCTION_FILE_PUT_UNSUPPORTED = Set.of(GO_V3_TRANSITION, GO_V4, PYTHON_V3); -// Set.of(GO_V3_CURRENT, GO_V3_TRANSITION, GO_V4, PYTHON_V3, CPP_V2_CURRENT); - // Apparently C++ V2 Current does not work, even though it should // Not implemented yet in Python. public static final Set INSTRUCTION_FILE_GET_UNSUPPORTED = @@ -152,22 +142,10 @@ public class TestUtils { PHP_V3 ); -// public static final Set CURRENT_VERSIONS = -// Set.of( -// JAVA_V3_CURRENT, -// GO_V3_CURRENT, -// NET_V2_CURRENT, -// NET_V3_CURRENT, -// CPP_V2_CURRENT, -// RUBY_V2_CURRENT, -// PHP_V2_CURRENT -// ); -// public static final Set TRANSITION_VERSIONS = Set.of( JAVA_V3_TRANSITION, GO_V3_TRANSITION, - // NET_V2_TRANSITION, NET_V3_TRANSITION, CPP_V2_TRANSITION, PHP_V2_TRANSITION, @@ -362,15 +340,6 @@ public static Stream clientsForTest() { .map(Arguments::of); } -// /** -// * Get stream of arguments for current version clients for testing. -// */ -// public static Stream currentClientsForTest() { -// return serverMap.values().stream() -// .filter(target -> CURRENT_VERSIONS.contains(target.getLanguageName())) -// .map(Arguments::of); -// } - /** * Get stream of arguments for transition version clients for testing. */ @@ -439,38 +408,6 @@ public static Stream encryptTransitionDecryptImproved() { ))); } -// public static Stream encryptImprovedDecryptCurrent() { -// return improvedClientsForTest() -// .flatMap(encrypt -> currentClientsForTest() -// .flatMap(decrypt -> Stream.of( -// Arguments.of(encrypt.get()[0], decrypt.get()[0]) -// ))); -// } - -// public static Stream encryptCurrentDecryptImproved() { -// return currentClientsForTest() -// .flatMap(encrypt -> improvedClientsForTest() -// .flatMap(decrypt -> Stream.of( -// Arguments.of(encrypt.get()[0], decrypt.get()[0]) -// ))); -// } - -// public static Stream encryptTransitionDecryptCurrent() { -// return transitionClientsForTest() -// .flatMap(encrypt -> currentClientsForTest() -// .flatMap(decrypt -> Stream.of( -// Arguments.of(encrypt.get()[0], decrypt.get()[0]) -// ))); -// } - -// public static Stream encryptCurrentDecryptTransition() { -// return currentClientsForTest() -// .flatMap(encrypt -> transitionClientsForTest() -// .flatMap(decrypt -> Stream.of( -// Arguments.of(encrypt.get()[0], decrypt.get()[0]) -// ))); -// } - /** * Provides a stream of arguments for parameterized tests that test cross-language compatibility * @return Stream of Arguments containing pairs of LanguageServerTarget for encryption and decryption From 7e5547dbbb83a2a173fc7eab9eb6e6914adc8ddc Mon Sep 17 00:00:00 2001 From: Kess Plasmeier Date: Fri, 30 Jan 2026 14:08:56 -0800 Subject: [PATCH 27/38] tweak cpp client to maybe have fewer timeouts --- test-server/cpp-v3-server/main.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/test-server/cpp-v3-server/main.cpp b/test-server/cpp-v3-server/main.cpp index 4e7227df..169fa517 100644 --- a/test-server/cpp-v3-server/main.cpp +++ b/test-server/cpp-v3-server/main.cpp @@ -296,6 +296,11 @@ MHD_Result handle_create_client(struct MHD_Connection *connection, clientConfig.maxConnections = 512; // Large pool per client clientConfig.retryStrategy = Aws::Client::InitRetryStrategy("standard"); + // Increase timeouts for CI environments where SSL handshakes can be slow + // Default connectTimeoutMs is 1000ms, which is too short for busy CI runners + clientConfig.connectTimeoutMs = 10000; // 10 seconds for SSL connection establishment + clientConfig.requestTimeoutMs = 30000; // 30 seconds for complete request/response + // Disable automatic checksum calculation for encrypted streams // The ChecksumInterceptor cannot handle non-seekable SymmetricCryptoStream // which causes intermittent "BadDigest: CRC64NVME you specified did not match" errors From 8ddb8f4f49f36358e60c503c9b59295be08bb6ae Mon Sep 17 00:00:00 2001 From: Kess Plasmeier Date: Fri, 30 Jan 2026 14:44:06 -0800 Subject: [PATCH 28/38] point to public Java, Go repos --- .gitmodules | 15 ++++++++------- test-server/go-v3-transition-server/local-go-s3ec | 2 +- test-server/go-v4-server/local-go-s3ec | 2 +- .../java-v3-transition-server/s3ec-staging | 2 +- test-server/java-v4-server/s3ec-staging | 2 +- 5 files changed, 12 insertions(+), 11 deletions(-) diff --git a/.gitmodules b/.gitmodules index df9a207f..75e91f99 100644 --- a/.gitmodules +++ b/.gitmodules @@ -12,15 +12,16 @@ branch = master [submodule "test-server/go-v4-server/local-go-s3ec"] path = test-server/go-v4-server/local-go-s3ec - url = https://github.com/aws/private-amazon-s3-encryption-client-go-staging + url = https://github.com/aws/amazon-s3-encryption-client-go + branch = main [submodule "test-server/java-v3-transition-server/s3ec-staging"] path = test-server/java-v3-transition-server/s3ec-staging - url = git@github.com:aws/private-amazon-s3-encryption-client-java-staging.git - branch = imabhichow/transition-read-kc + url = git@github.com:aws/amazon-s3-encryption-client-java.git + branch = main-3.x [submodule "test-server/java-v4-server/s3ec-staging"] path = test-server/java-v4-server/s3ec-staging - url = git@github.com:aws/private-amazon-s3-encryption-client-java-staging.git - branch = imabhichow/add-kc + url = git@github.com:aws/amazon-s3-encryption-client-java.git + branch = main [submodule "test-server/specification"] path = test-server/specification url = git@github.com:awslabs/private-aws-encryption-sdk-specification-staging.git @@ -31,8 +32,8 @@ branch = main [submodule "test-server/go-v3-transition-server/local-go-s3ec"] path = test-server/go-v3-transition-server/local-go-s3ec - url = https://github.com/aws/private-amazon-s3-encryption-client-go-staging - branch = v3-strip + url = https://github.com/aws/amazon-s3-encryption-client-go + branch = main [submodule "test-server/net-v3-transition-server/s3ec-v3-transition-branch"] path = test-server/net-v3-transition-server/s3ec-v3-transition-branch url = https://github.com/aws/amazon-s3-encryption-client-dotnet.git diff --git a/test-server/go-v3-transition-server/local-go-s3ec b/test-server/go-v3-transition-server/local-go-s3ec index 912914ad..bf8a12f6 160000 --- a/test-server/go-v3-transition-server/local-go-s3ec +++ b/test-server/go-v3-transition-server/local-go-s3ec @@ -1 +1 @@ -Subproject commit 912914ad1c14942c3ea8638fb37f9e2c46445a84 +Subproject commit bf8a12f61694d750a13a44f0a691dd7ced0ff904 diff --git a/test-server/go-v4-server/local-go-s3ec b/test-server/go-v4-server/local-go-s3ec index 912914ad..bf8a12f6 160000 --- a/test-server/go-v4-server/local-go-s3ec +++ b/test-server/go-v4-server/local-go-s3ec @@ -1 +1 @@ -Subproject commit 912914ad1c14942c3ea8638fb37f9e2c46445a84 +Subproject commit bf8a12f61694d750a13a44f0a691dd7ced0ff904 diff --git a/test-server/java-v3-transition-server/s3ec-staging b/test-server/java-v3-transition-server/s3ec-staging index 183f1984..d829a235 160000 --- a/test-server/java-v3-transition-server/s3ec-staging +++ b/test-server/java-v3-transition-server/s3ec-staging @@ -1 +1 @@ -Subproject commit 183f1984ed1679e8aa4cb368aeda66f2131a2061 +Subproject commit d829a235854996e0f25736662510c2aa25e61fae diff --git a/test-server/java-v4-server/s3ec-staging b/test-server/java-v4-server/s3ec-staging index 7a1899bb..a95aa3fd 160000 --- a/test-server/java-v4-server/s3ec-staging +++ b/test-server/java-v4-server/s3ec-staging @@ -1 +1 @@ -Subproject commit 7a1899bb8be6f137a3031ff76f2a1bf3f278e98d +Subproject commit a95aa3fddb5abf4e17551c0ef3c247c7a43edf40 From 45c5de00afe3745fb02ea746177f7c71ae8d9942 Mon Sep 17 00:00:00 2001 From: Kess Plasmeier Date: Fri, 30 Jan 2026 14:54:59 -0800 Subject: [PATCH 29/38] fix ci branch name --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 52c3e465..50f3abcb 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -2,7 +2,7 @@ name: Main Workflow on: push: - branches: [ main, fireegg-test-servers ] + branches: [ main, staging ] pull_request: workflow_dispatch: inputs: From fef65e45791980e5ff9b6ea80fc34b348f2ab8d8 Mon Sep 17 00:00:00 2001 From: Kess Plasmeier Date: Fri, 30 Jan 2026 14:56:55 -0800 Subject: [PATCH 30/38] fix merge error --- .github/workflows/main.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index a7f97be3..50f3abcb 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -25,9 +25,6 @@ jobs: uses: ./.github/workflows/test.yml with: python-version: ${{ inputs.python-version || '3.11' }} - permissions: - id-token: write - contents: read secrets: inherit run-duvet: From da904a56bb3db48dc2c68b25710a05cf995d1bc2 Mon Sep 17 00:00:00 2001 From: Kess Plasmeier Date: Fri, 30 Jan 2026 15:14:42 -0800 Subject: [PATCH 31/38] remove PAT --- .github/workflows/duvet.yml | 1 - .github/workflows/test.yml | 10 ---------- 2 files changed, 11 deletions(-) diff --git a/.github/workflows/duvet.yml b/.github/workflows/duvet.yml index 8b277ec0..e6549030 100644 --- a/.github/workflows/duvet.yml +++ b/.github/workflows/duvet.yml @@ -17,7 +17,6 @@ jobs: uses: actions/checkout@v5 with: submodules: true - token: ${{ secrets.PAT_FOR_PRIVATE_RUBY }} - name: Checkout CPP code cpp-v3 uses: actions/checkout@v5 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 5ea728af..39b2b322 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -22,10 +22,6 @@ jobs: uses: actions/checkout@v5 with: submodules: false - # This is Ryan Emery's (seebees) PAT. - # To grant this workflow access to a new private repo, - # ask Ryan to edit this PAT's permissions to add access to a new private repo. - token: ${{ secrets.PAT_FOR_PRIVATE_RUBY }} # There are a lot of submodules here # This initializes the checkouts in parallel (--jobs) @@ -35,12 +31,6 @@ jobs: id: cpu-count run: echo "count=$(node -p 'require("os").cpus().length')" >> $GITHUB_OUTPUT - - name: Setup git submodules with PAT - run: | - git config --global url."https://github.com/".insteadOf "git@github.com:" - git config --global credential.helper store - echo "https://x-token-auth:${{ secrets.PAT_FOR_PRIVATE_RUBY }}@github.com" > ~/.git-credentials - - name: Optimize git for performance run: | git config --global fetch.parallel ${{ steps.cpu-count.outputs.count }} From 76d559f76626c41247aad06f8ce56dcdfb374792 Mon Sep 17 00:00:00 2001 From: Kess Plasmeier Date: Fri, 30 Jan 2026 15:26:40 -0800 Subject: [PATCH 32/38] dynamically load version from submodule pom --- test-server/java-v3-transition-server/build.gradle.kts | 7 ++++++- test-server/java-v4-server/build.gradle.kts | 7 ++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/test-server/java-v3-transition-server/build.gradle.kts b/test-server/java-v3-transition-server/build.gradle.kts index 7f249d65..7f474fa0 100644 --- a/test-server/java-v3-transition-server/build.gradle.kts +++ b/test-server/java-v3-transition-server/build.gradle.kts @@ -4,6 +4,10 @@ plugins { application } +// Dynamically read S3EC version from submodule's pom.xml +val s3ecVersion = file("s3ec-staging/pom.xml").readText() + .let { Regex("(.*?)").find(it)?.groupValues?.get(1) ?: "3.6.0" } + dependencies { val smithyJavaVersion: String by project @@ -14,7 +18,8 @@ dependencies { implementation("software.amazon.smithy.java:aws-server-restjson:$smithyJavaVersion") // S3EC from local Maven repository (installed by mvn install) - implementation("software.amazon.encryption.s3:amazon-s3-encryption-client-java:3.4.0-read-kc") + // Version is dynamically read from s3ec-staging/pom.xml + implementation("software.amazon.encryption.s3:amazon-s3-encryption-client-java:$s3ecVersion") } // Use that application plugin to start the service via the `run` task. diff --git a/test-server/java-v4-server/build.gradle.kts b/test-server/java-v4-server/build.gradle.kts index fcb3c3ca..d55d93d7 100644 --- a/test-server/java-v4-server/build.gradle.kts +++ b/test-server/java-v4-server/build.gradle.kts @@ -4,6 +4,10 @@ plugins { application } +// Dynamically read S3EC version from submodule's pom.xml +val s3ecVersion = file("s3ec-staging/pom.xml").readText() + .let { Regex("(.*?)").find(it)?.groupValues?.get(1) ?: "4.0.0" } + dependencies { val smithyJavaVersion: String by project @@ -14,7 +18,8 @@ dependencies { implementation("software.amazon.smithy.java:aws-server-restjson:$smithyJavaVersion") // S3EC from local Maven repository (installed by mvn install) - implementation("software.amazon.encryption.s3:amazon-s3-encryption-client-java:3.4.0-add-kc") + // Version is dynamically read from s3ec-staging/pom.xml + implementation("software.amazon.encryption.s3:amazon-s3-encryption-client-java:$s3ecVersion") } // Use that application plugin to start the service via the `run` task. From d8a2e21dfc4417de70ee3f6a203e99d6b0c7da3f Mon Sep 17 00:00:00 2001 From: Kess Plasmeier Date: Fri, 30 Jan 2026 15:29:39 -0800 Subject: [PATCH 33/38] use HTTPS --- .github/workflows/test.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 39b2b322..4d5570ca 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -31,6 +31,10 @@ jobs: id: cpu-count run: echo "count=$(node -p 'require("os").cpus().length')" >> $GITHUB_OUTPUT + - name: Configure git to use HTTPS instead of SSH for public repos + run: | + git config --global url."https://github.com/".insteadOf "git@github.com:" + - name: Optimize git for performance run: | git config --global fetch.parallel ${{ steps.cpu-count.outputs.count }} From 06b3a757586ecfc3e618717d4b3303f0adf9fda7 Mon Sep 17 00:00:00 2001 From: Kess Plasmeier Date: Fri, 30 Jan 2026 15:34:51 -0800 Subject: [PATCH 34/38] restore PAT --- .github/workflows/duvet.yml | 1 + .github/workflows/test.yml | 5 ++++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/duvet.yml b/.github/workflows/duvet.yml index e6549030..8b277ec0 100644 --- a/.github/workflows/duvet.yml +++ b/.github/workflows/duvet.yml @@ -17,6 +17,7 @@ jobs: uses: actions/checkout@v5 with: submodules: true + token: ${{ secrets.PAT_FOR_PRIVATE_RUBY }} - name: Checkout CPP code cpp-v3 uses: actions/checkout@v5 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 4d5570ca..201862c8 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -22,6 +22,7 @@ jobs: uses: actions/checkout@v5 with: submodules: false + token: ${{ secrets.PAT_FOR_PRIVATE_RUBY }} # There are a lot of submodules here # This initializes the checkouts in parallel (--jobs) @@ -31,9 +32,11 @@ jobs: id: cpu-count run: echo "count=$(node -p 'require("os").cpus().length')" >> $GITHUB_OUTPUT - - name: Configure git to use HTTPS instead of SSH for public repos + - name: Setup git submodules with PAT run: | git config --global url."https://github.com/".insteadOf "git@github.com:" + git config --global credential.helper store + echo "https://x-token-auth:${{ secrets.PAT_FOR_PRIVATE_RUBY }}@github.com" > ~/.git-credentials - name: Optimize git for performance run: | From 6cfa131ea8f2e51da6bfed73efda1fae863e5031 Mon Sep 17 00:00:00 2001 From: Kess Plasmeier Date: Fri, 30 Jan 2026 15:52:31 -0800 Subject: [PATCH 35/38] restore Python integ tests --- .github/workflows/main.yml | 10 ++++++ .github/workflows/python-integ.yml | 57 ++++++++++++++++++++++++++++++ .github/workflows/test.yml | 30 ---------------- 3 files changed, 67 insertions(+), 30 deletions(-) create mode 100644 .github/workflows/python-integ.yml diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 50f3abcb..50f817fe 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -27,6 +27,16 @@ jobs: python-version: ${{ inputs.python-version || '3.11' }} secrets: inherit + python-integ: + permissions: + id-token: write + contents: read + name: Python Integration Tests + uses: ./.github/workflows/python-integ.yml + with: + python-version: ${{ inputs.python-version || '3.11' }} + secrets: inherit + run-duvet: permissions: id-token: write diff --git a/.github/workflows/python-integ.yml b/.github/workflows/python-integ.yml new file mode 100644 index 00000000..9e5ae818 --- /dev/null +++ b/.github/workflows/python-integ.yml @@ -0,0 +1,57 @@ +name: Python Integration Tests + +on: + workflow_call: + inputs: + python-version: + description: "Python version to use" + default: "3.11" + required: false + type: string + +jobs: + python-integ: + runs-on: macos-14-large + permissions: + id-token: write + contents: read + + steps: + - name: Checkout code + uses: actions/checkout@v5 + with: + submodules: false + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: ${{ inputs.python-version || '3.11' }} + + - name: Cache uv dependencies + uses: actions/cache@v4 + with: + path: ~/.cache/uv + key: ${{ runner.os }}-uv-${{ hashFiles('./test-server/python-v3-server/**/pyproject.toml') }} + restore-keys: | + ${{ runner.os }}-uv- + + - name: Install Uv + run: pip install uv + + - name: Install dependencies + run: make install + + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@v4 + with: + role-to-assume: arn:aws:iam::370957321024:role/S3EC-Python-Github-test-role + aws-region: us-west-2 + + - name: Run unit tests + run: make test-unit + + - name: Run integration tests + run: make test-integration + env: + CI_S3_BUCKET: ${{ vars.CI_S3_BUCKET }} + CI_KMS_KEY_ALIAS: ${{ vars.CI_KMS_KEY_ALIAS }} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 201862c8..a6311eeb 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -87,36 +87,6 @@ jobs: run: | brew install libmicrohttpd nlohmann-json ossp-uuid - # Legacy Python tests: - # - name: Set up Python - # uses: actions/setup-python@v5 - # with: - # python-version: ${{ inputs.python-version || '3.11' }} - # - # # Cache uv dependencies - # - name: Cache uv dependencies - # uses: actions/cache@v3 - # with: - # path: ~/.cache/uv - # key: ${{ runner.os }}-uv-${{ hashFiles('./test-server/python-v3-server/**/pyproject.toml') }} - # restore-keys: | - # ${{ runner.os }}-uv- - - # - name: Install Uv - # run: pip install uv - - # - name: Install dependencies - # run: make install - - # - name: Run unit tests - # run: make test-unit - - # - name: Run integration tests - # run: make test-integration - # env: - # CI_S3_BUCKET: ${{ vars.CI_S3_BUCKET }} - # CI_KMS_KEY_ALIAS: ${{ vars.CI_KMS_KEY_ALIAS }} - # Cache Gradle dependencies and build outputs - name: Cache Gradle packages uses: actions/cache@v4 From 727f05d8a00820370b9c9f789d8101edd5a9ac23 Mon Sep 17 00:00:00 2001 From: Kess Plasmeier Date: Fri, 30 Jan 2026 15:55:00 -0800 Subject: [PATCH 36/38] rename tests to testserver tests --- .github/workflows/main.yml | 6 +++--- .github/workflows/{test.yml => test-server.yml} | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) rename .github/workflows/{test.yml => test-server.yml} (99%) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 50f817fe..c3be058a 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -17,12 +17,12 @@ jobs: name: Lint uses: ./.github/workflows/lint.yml - run-tests: + run-test-server: permissions: id-token: write contents: read - name: Run Tests - uses: ./.github/workflows/test.yml + name: Run TestServer Tests + uses: ./.github/workflows/test-server.yml with: python-version: ${{ inputs.python-version || '3.11' }} secrets: inherit diff --git a/.github/workflows/test.yml b/.github/workflows/test-server.yml similarity index 99% rename from .github/workflows/test.yml rename to .github/workflows/test-server.yml index a6311eeb..6e8d3fa5 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test-server.yml @@ -1,4 +1,4 @@ -name: Run Tests +name: Run TestServer Tests on: workflow_call: @@ -11,7 +11,7 @@ on: type: string jobs: - test: + test-server: runs-on: macos-14-large permissions: id-token: write From d9e7a35e32971499e8c81eb9f541716d0d78bed0 Mon Sep 17 00:00:00 2001 From: Kess Plasmeier Date: Mon, 2 Feb 2026 14:41:00 -0800 Subject: [PATCH 37/38] apply PR feedback --- .github/workflows/{main.yml => all-ci.yml} | 8 ++--- .../{duvet.yml => duvet-test-server.yml} | 10 +++--- .github/workflows/test-server.yml | 4 +-- test-server/Makefile | 32 +++++++++---------- test-server/model/client.smithy | 9 ++++-- 5 files changed, 34 insertions(+), 29 deletions(-) rename .github/workflows/{main.yml => all-ci.yml} (89%) rename .github/workflows/{duvet.yml => duvet-test-server.yml} (85%) diff --git a/.github/workflows/main.yml b/.github/workflows/all-ci.yml similarity index 89% rename from .github/workflows/main.yml rename to .github/workflows/all-ci.yml index c3be058a..c65364b7 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/all-ci.yml @@ -1,4 +1,4 @@ -name: Main Workflow +name: All CI on: push: @@ -13,7 +13,7 @@ on: type: string jobs: - lint: + python-lint: name: Lint uses: ./.github/workflows/lint.yml @@ -37,11 +37,11 @@ jobs: python-version: ${{ inputs.python-version || '3.11' }} secrets: inherit - run-duvet: + run-duvet-test-server: permissions: id-token: write contents: read pages: write name: Run Duvet - uses: ./.github/workflows/duvet.yml + uses: ./.github/workflows/duvet-test-server.yml secrets: inherit diff --git a/.github/workflows/duvet.yml b/.github/workflows/duvet-test-server.yml similarity index 85% rename from .github/workflows/duvet.yml rename to .github/workflows/duvet-test-server.yml index 8b277ec0..5f8da5f0 100644 --- a/.github/workflows/duvet.yml +++ b/.github/workflows/duvet-test-server.yml @@ -1,11 +1,11 @@ -name: Run Tests +name: Generate Duvet Report for TestServer on: workflow_call: # Optional inputs that can be provided when calling this workflow jobs: - test: + duvet: runs-on: macos-latest permissions: id-token: write @@ -90,15 +90,15 @@ jobs: test-server/index.html - name: Setup Pages - if: always() && github.ref == 'refs/heads/fireegg-test-servers' && github.event_name == 'push' + if: always() && (github.ref == 'refs/heads/staging' || github.ref == 'refs/heads/fireegg-test-servers') && github.event_name == 'push' uses: actions/configure-pages@v5 - name: Upload Pages artifact - if: always() && github.ref == 'refs/heads/fireegg-test-servers' && github.event_name == 'push' + if: always() && (github.ref == 'refs/heads/staging' || github.ref == 'refs/heads/fireegg-test-servers') && github.event_name == 'push' uses: actions/upload-pages-artifact@v3 with: path: test-server/ - name: Deploy to GitHub Pages - if: always() && github.ref == 'refs/heads/fireegg-test-servers' && github.event_name == 'push' + if: always() && (github.ref == 'refs/heads/staging' || github.ref == 'refs/heads/fireegg-test-servers') && github.event_name == 'push' uses: actions/deploy-pages@v4 diff --git a/.github/workflows/test-server.yml b/.github/workflows/test-server.yml index 6e8d3fa5..d2b542ee 100644 --- a/.github/workflows/test-server.yml +++ b/.github/workflows/test-server.yml @@ -124,7 +124,7 @@ jobs: MAKEFLAGS: -j${{ steps.cpu-count.outputs.count }} - name: Run run-tests - run: cd test-server && make run-tests + run: cd test-server && make test-servers-run-tests env: AWS_REGION: us-west-2 TEST_SERVER_S3_BUCKET: ${{ vars.TEST_SERVER_S3_BUCKET }} @@ -140,7 +140,7 @@ jobs: test-server/*/server.log - name: Stop the servers - run: cd test-server && make stop-servers + run: cd test-server && make test-servers-stop - name: Upload results if: always() diff --git a/test-server/Makefile b/test-server/Makefile index 94a76b3f..21b5c98b 100644 --- a/test-server/Makefile +++ b/test-server/Makefile @@ -1,14 +1,14 @@ # Makefile for S3 Encryption Client Testing -.PHONY: all start-servers run-tests stop-servers clean ci check-env help +.PHONY: test-servers-all test-servers-start test-servers-run-tests test-servers-stop test-servers-clean test-servers-ci test-servers-check-env test-servers-help # CI target for GitHub Actions -ci: +test-servers-ci: $(MAKE) build-all-servers $(MAKE) start-all-servers $(MAKE) wait-all-servers - $(MAKE) run-tests - $(MAKE) stop-servers + $(MAKE) test-servers-run-tests + $(MAKE) test-servers-stop SERVER_DIRS := $(shell find . -maxdepth 1 -type d -name '*-server' | sed 's|^\./||' | $(if $(FILTER),grep -E "$$(echo '$(FILTER)' | sed 's/,/|/g')",cat) | sort) @@ -36,7 +36,7 @@ $(BUILD_SERVER_TARGETS): build-%: fi # Build and start all servers -start-servers: +test-servers-start: @echo "Building all servers..." $(MAKE) build-all-servers @echo "Starting all servers..." @@ -75,7 +75,7 @@ $(WAIT_SERVER_TARGETS): wait-%: # Run the Java tests -run-tests: +test-servers-run-tests: @echo "Running Java tests..." @echo "Exporting environment variables from servers to tests..." @# Extract AWS environment variables from the current shell and pass them to the tests @@ -90,7 +90,7 @@ run-tests: @echo "Tests completed successfully" # Stop the servers -stop-servers: +test-servers-stop: @echo "Stopping servers..." @for dir in $(SERVER_DIRS); do \ echo "Stopping server in $$dir..."; \ @@ -99,18 +99,18 @@ stop-servers: @echo "Servers stopped" # Help target -help: +test-servers-help: @echo "Available targets:" - @echo " all : Start servers and run tests (default, output to stdout)" - @echo " ci : Run in CI mode (start servers, run tests, stop servers)" - @echo " start-servers : Start all servers in parallel" - @echo " run-tests : Run Java tests" - @echo " stop-servers : Stop running servers" - @echo " check-env : Check if required environment variables are set" - @echo " help : Show this help message" + @echo " test-servers-all : Start servers and run tests (default, output to stdout)" + @echo " test-servers-ci : Run in CI mode (start servers, run tests, stop servers)" + @echo " test-servers-start : Start all servers in parallel" + @echo " test-servers-run-tests : Run Java tests" + @echo " test-servers-stop : Stop running servers" + @echo " test-servers-check-env : Check if required environment variables are set" + @echo " test-servers-help : Show this help message" # Check if required environment variables are set -check-env: +test-servers-check-env: @echo "Checking required environment variables..." @if [ -z "$$AWS_ACCESS_KEY_ID" ]; then echo "AWS_ACCESS_KEY_ID is not set"; else echo "AWS_ACCESS_KEY_ID is set"; fi @if [ -z "$$AWS_SECRET_ACCESS_KEY" ]; then echo "AWS_SECRET_ACCESS_KEY is not set"; else echo "AWS_SECRET_ACCESS_KEY is set"; fi diff --git a/test-server/model/client.smithy b/test-server/model/client.smithy index 5772e8c2..11f65f57 100644 --- a/test-server/model/client.smithy +++ b/test-server/model/client.smithy @@ -52,9 +52,14 @@ enum EncryptionAlgorithm { structure InstructionFileConfig { /// This allows specifying a (non-encrypted) client for languages which /// support this for instruction files. - /// In general, languages should not require specifying it, - /// so it is best to leave it null until there's a good reason not to. + /// In general, languages do not require specifying a client; + /// they use the usual wrapped client for instruction file operations, + /// so it is fine to leave it null for now. /// This also requires a way to create non-encrypted clients which we don't have yet. + /// Some languages (Java) do allow a client to be passed specifically for instruction files, + /// so this should be implemented eventually for full coverage, + /// especially if other languages add this feature. Until then, + /// the Java integ tests are sufficient. clientId: String, enableInstructionFilePutObject: Boolean = false, disableInstructionFile: Boolean = false From 9f65202c414a0af29665f9678f412e299bdb0053 Mon Sep 17 00:00:00 2001 From: Kess Plasmeier Date: Tue, 3 Feb 2026 15:40:08 -0800 Subject: [PATCH 38/38] new PAT --- .github/workflows/duvet-test-server.yml | 2 +- .github/workflows/test-server.yml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/duvet-test-server.yml b/.github/workflows/duvet-test-server.yml index 5f8da5f0..f4bac5a8 100644 --- a/.github/workflows/duvet-test-server.yml +++ b/.github/workflows/duvet-test-server.yml @@ -17,7 +17,7 @@ jobs: uses: actions/checkout@v5 with: submodules: true - token: ${{ secrets.PAT_FOR_PRIVATE_RUBY }} + token: ${{ secrets.PAT_FOR_SPEC }} - name: Checkout CPP code cpp-v3 uses: actions/checkout@v5 diff --git a/.github/workflows/test-server.yml b/.github/workflows/test-server.yml index d2b542ee..4fa10666 100644 --- a/.github/workflows/test-server.yml +++ b/.github/workflows/test-server.yml @@ -22,7 +22,7 @@ jobs: uses: actions/checkout@v5 with: submodules: false - token: ${{ secrets.PAT_FOR_PRIVATE_RUBY }} + token: ${{ secrets.PAT_FOR_SPEC }} # There are a lot of submodules here # This initializes the checkouts in parallel (--jobs) @@ -36,7 +36,7 @@ jobs: run: | git config --global url."https://github.com/".insteadOf "git@github.com:" git config --global credential.helper store - echo "https://x-token-auth:${{ secrets.PAT_FOR_PRIVATE_RUBY }}@github.com" > ~/.git-credentials + echo "https://x-token-auth:${{ secrets.PAT_FOR_SPEC }}@github.com" > ~/.git-credentials - name: Optimize git for performance run: |