diff --git a/.github/s3tests/s3tests.conf b/.github/s3tests/s3tests.conf index cc7b51274..945b7b0cd 100644 --- a/.github/s3tests/s3tests.conf +++ b/.github/s3tests/s3tests.conf @@ -55,6 +55,10 @@ secret_key = ${S3_SECRET_KEY} ## replace with key id obtained when secret is created, or delete if KMS not tested #kms_keyid = 01234567-89ab-cdef-0123-456789abcdef +#kms_keyid2 = fedcba98-7654-3210-fedc-ba9876543210 + +## Expected service default for SSE-KMS requests without a key id; empty means none +#kms_default_keyid = ## Storage classes #storage_classes = "LUKEWARM, FROZEN" diff --git a/scripts/s3-tests/README.md b/scripts/s3-tests/README.md index fe06d679c..c7bebde87 100644 --- a/scripts/s3-tests/README.md +++ b/scripts/s3-tests/README.md @@ -238,6 +238,28 @@ DEPLOY_MODE=existing \ ./scripts/s3-tests/run.sh ``` +### KMS Test Fixtures + +Managed deployments enable local KMS and provision two distinct public test keys. +`S3_KMS_KEY_ID` selects the primary key (default `rustfs-s3tests-default-key`), and +`S3_KMS_SECONDARY_KEY_ID` selects the second key (default +`rustfs-s3tests-secondary-key`). Their IDs and key material differ so encrypted +copy tests exercise a change of key. The generated config sets `kms_keyid` and +`kms_keyid2` explicitly. + +`S3_KMS_DEFAULT_KEY_ID` defaults to the primary key for managed local KMS. Set it +to an empty value to start without a default key. The generated +`kms_default_keyid` setting fixes the expectation for an SSE-KMS PUT without a +key ID: with a configured default, the patched upstream test requires a +successful PUT, HEAD and GET with that key ID and matching object contents; +without one, the request must fail. The test remains in the implemented gate. + +For `DEPLOY_MODE=existing`, the harness does not provision keys or infer the +server's default. Set both key IDs to keys that already exist, and set +`S3_KMS_DEFAULT_KEY_ID` only when the target has a default. Custom config +templates should include `kms_keyid`, `kms_keyid2`, and `kms_default_keyid`, or +the commented placeholders from the bundled template. + ### Custom Configuration Files ```bash diff --git a/scripts/s3-tests/patches/0004-sse-kms-default-key-expectation.patch b/scripts/s3-tests/patches/0004-sse-kms-default-key-expectation.patch new file mode 100644 index 000000000..729c9ebbd --- /dev/null +++ b/scripts/s3-tests/patches/0004-sse-kms-default-key-expectation.patch @@ -0,0 +1,61 @@ +diff --git a/s3tests/functional/__init__.py b/s3tests/functional/__init__.py +index 5fcb1f4..09af01d 100644 +--- a/s3tests/functional/__init__.py ++++ b/s3tests/functional/__init__.py +@@ -232,6 +232,8 @@ def configure(): + except (configparser.NoSectionError, configparser.NoOptionError): + config.main_kms_keyid2 = 'testkey-2' + ++ config.main_kms_default_keyid = cfg.get('s3 main', 'kms_default_keyid', fallback=None) ++ + try: + config.main_api_name = cfg.get('s3 main',"api_name") + except (configparser.NoSectionError, configparser.NoOptionError): +@@ -732,6 +734,9 @@ def get_main_kms_keyid(): + def get_secondary_kms_keyid(): + return config.main_kms_keyid2 + ++def get_default_kms_keyid(): ++ return config.main_kms_default_keyid or None ++ + def get_alt_aws_access_key(): + return config.alt_access_key + +diff --git a/s3tests/functional/test_s3.py b/s3tests/functional/test_s3.py +index aa8879c..7e0c381 100644 +--- a/s3tests/functional/test_s3.py ++++ b/s3tests/functional/test_s3.py +@@ -79,6 +79,7 @@ from . import ( + get_objects_list, + get_main_kms_keyid, + get_secondary_kms_keyid, ++ get_default_kms_keyid, + get_svc_client, + get_cloud_storage_class, + get_cloud_retain_head_object, +@@ -11282,7 +11283,24 @@ def test_sse_kms_no_key(): + lf = (lambda **kwargs: kwargs['params']['headers'].update(sse_kms_client_headers)) + client.meta.events.register('before-call.s3.PutObject', lf) + +- e = assert_raises(ClientError, client.put_object, Bucket=bucket_name, Key=key, Body=data) ++ default_keyid = get_default_kms_keyid() ++ if default_keyid is None: ++ assert_raises(ClientError, client.put_object, Bucket=bucket_name, Key=key, Body=data) ++ return ++ ++ # A configured default must encrypt the object, not silently store plaintext. ++ response = client.put_object(Bucket=bucket_name, Key=key, Body=data) ++ assert response['ServerSideEncryption'] == 'aws:kms' ++ assert response['SSEKMSKeyId'] == default_keyid ++ ++ response = client.head_object(Bucket=bucket_name, Key=key) ++ assert response['ServerSideEncryption'] == 'aws:kms' ++ assert response['SSEKMSKeyId'] == default_keyid ++ ++ response = client.get_object(Bucket=bucket_name, Key=key) ++ assert response['ServerSideEncryption'] == 'aws:kms' ++ assert response['SSEKMSKeyId'] == default_keyid ++ assert _get_body(response) == data + + + @pytest.mark.encryption diff --git a/scripts/s3-tests/run.sh b/scripts/s3-tests/run.sh index 593bec898..ead73e80b 100755 --- a/scripts/s3-tests/run.sh +++ b/scripts/s3-tests/run.sh @@ -241,6 +241,13 @@ NO_CACHE="${NO_CACHE:-false}" S3TESTS_LOCAL_SSE_MASTER_KEY_DEFAULT="MDEyMzQ1Njc4OWFiY2RlZjAxMjM0NTY3ODlhYmNkZWY=" S3TESTS_ENABLE_LOCAL_KMS="${S3TESTS_ENABLE_LOCAL_KMS:-true}" S3_KMS_KEY_ID="${S3_KMS_KEY_ID:-rustfs-s3tests-default-key}" +S3_KMS_SECONDARY_KEY_ID="${S3_KMS_SECONDARY_KEY_ID:-rustfs-s3tests-secondary-key}" +if [ "${S3TESTS_ENABLE_LOCAL_KMS}" = "true" ] && [ "${DEPLOY_MODE}" != "existing" ]; then + # An explicitly empty value exercises the missing-default-key path. + S3_KMS_DEFAULT_KEY_ID="${S3_KMS_DEFAULT_KEY_ID-${S3_KMS_KEY_ID}}" +else + S3_KMS_DEFAULT_KEY_ID="${S3_KMS_DEFAULT_KEY_ID:-}" +fi # Additional directories (SCRIPT_DIR and PROJECT_ROOT defined earlier) ARTIFACTS_DIR="${PROJECT_ROOT}/artifacts/s3tests-${TEST_MODE}" @@ -289,6 +296,8 @@ Environment Variables: RUSTFS_SSE_S3_MASTER_KEY - Optional base64 32-byte key for local managed SSE fallback S3TESTS_ENABLE_LOCAL_KMS - Enable local KMS for SSE-KMS cases (default: true) S3_KMS_KEY_ID - s3-tests KMS key id (default: rustfs-s3tests-default-key) + S3_KMS_SECONDARY_KEY_ID - Second KMS key id (default: rustfs-s3tests-secondary-key) + S3_KMS_DEFAULT_KEY_ID - Expected default key (managed local KMS: primary key; existing: none; empty disables it) S3TESTS_KMS_KEY_DIR - Host key directory for local KMS (default: DATA_ROOT/kms-keys) RUSTFS_SCANNER_ENABLED - Enable background scanner for harness service (default: false) MAXFAIL - Stop after N failures, 0 = never stop (default: 1) @@ -358,18 +367,30 @@ prepare_s3tests_local_kms() { return 0 fi if [ "${DEPLOY_MODE}" = "existing" ]; then - log_warn "Skipping local KMS setup for DEPLOY_MODE=existing; set S3_KMS_KEY_ID only when the target service is KMS-enabled" + log_warn "Skipping local KMS setup for DEPLOY_MODE=existing; configure both test key IDs and the expected default for the target service" return 0 fi + if [ "${S3_KMS_KEY_ID}" = "${S3_KMS_SECONDARY_KEY_ID}" ]; then + log_error "S3_KMS_KEY_ID and S3_KMS_SECONDARY_KEY_ID must differ for cross-key tests" + return 1 + fi if [ "${DEPLOY_MODE}" = "docker" ] && [ -z "${S3TESTS_KMS_KEY_DIR:-}" ]; then S3TESTS_KMS_HOST_KEY_DIR="/tmp/${CONTAINER_NAME}/kms-keys" S3TESTS_KMS_RUNTIME_KEY_DIR="/data/kms-keys" fi mkdir -p "${S3TESTS_KMS_HOST_KEY_DIR}" - cat > "${S3TESTS_KMS_HOST_KEY_DIR}/${S3_KMS_KEY_ID}.key" < "${S3TESTS_KMS_HOST_KEY_DIR}/${key_id}.key" < "${CONF_OUTPUT_PATH}" || { } if [ -n "${S3_KMS_KEY_ID:-}" ]; then tmp_conf="${CONF_OUTPUT_PATH}.tmp" - sed "s|^#kms_keyid = .*$|kms_keyid = ${S3_KMS_KEY_ID}|" "${CONF_OUTPUT_PATH}" > "${tmp_conf}" + sed \ + -e "s|^#kms_keyid = .*$|kms_keyid = ${S3_KMS_KEY_ID}|" \ + -e "s|^#kms_keyid2 = .*$|kms_keyid2 = ${S3_KMS_SECONDARY_KEY_ID}|" \ + -e "s|^#kms_default_keyid =.*$|kms_default_keyid = ${S3_KMS_DEFAULT_KEY_ID}|" \ + "${CONF_OUTPUT_PATH}" > "${tmp_conf}" mv "${tmp_conf}" "${CONF_OUTPUT_PATH}" fi