diff --git a/xtest/README.md b/xtest/README.md index 6bdfcc40..77dabb4a 100644 --- a/xtest/README.md +++ b/xtest/README.md @@ -116,6 +116,19 @@ uv sync --extra dev pytest ``` +### Running xtests against a platform feature branch (CI) + +To validate platform changes against the xtest suite without merging: + +1. Go to the **Actions** tab in [opentdf/tests](https://github.com/opentdf/tests/actions) +2. Find the **xtest** workflow +3. Click the **"Run workflow"** dropdown on the right +4. Set **"Use workflow from"** to the xtest branch you want to run (e.g. `main`, or a companion xtest branch) +5. Set **"platform ref branch"** to the HEAD commit SHA of your platform feature branch +6. Click **Run workflow** + +This builds the platform from your commit and runs the full xtest suite against it. Use this to validate SDK or KAS changes before merging. + #### Run TDF Tests ```shell diff --git a/xtest/sdk/go/cli.sh b/xtest/sdk/go/cli.sh index 172aa5b5..48c8ffea 100755 --- a/xtest/sdk/go/cli.sh +++ b/xtest/sdk/go/cli.sh @@ -94,6 +94,13 @@ if [ "$1" == "supports" ]; then "${cmd[@]}" --version --json | jq -re .sdk_version | awk -F. '{ if ($1 > 0 || ($1 == 0 && $2 > 3) || ($1 == 0 && $2 == 3 && $3 >= 18)) exit 0; else exit 1; }' exit $? ;; + tamper-error-split) + # KAS 400 split: generic "bad request" = tamper, descriptive = misconfiguration + # Introduced in go sdk 0.14.0 + set -o pipefail + "${cmd[@]}" --version --json | jq -re .sdk_version | awk -F. '{ if ($1 > 0 || ($1 == 0 && $2 >= 14)) exit 0; else exit 1; }' + exit $? + ;; *) echo "Unknown feature: $2" exit 2 diff --git a/xtest/tdfs.py b/xtest/tdfs.py index 56e4c11b..f8c743b5 100644 --- a/xtest/tdfs.py +++ b/xtest/tdfs.py @@ -53,6 +53,9 @@ "mechanism-ec-curves-384-521", "ns_grants", "obligations", + # KAS 400 errors are split: generic "bad request" = tamper (ErrTampered), + # descriptive messages = misconfiguration (ErrKASRequestError). + "tamper-error-split", ] container_version = Literal["4.2.2", "4.3.0"] diff --git a/xtest/test_tdfs.py b/xtest/test_tdfs.py index d20c18a8..5d6083d9 100644 --- a/xtest/test_tdfs.py +++ b/xtest/test_tdfs.py @@ -565,30 +565,38 @@ def assert_tamper_error( ) -def assert_kas_request_error( +def assert_policy_tamper_error( exc: subprocess.CalledProcessError, decrypt_sdk: tdfs.SDK ) -> None: - """Assert that a KAS request error was returned. + """Assert that a policy binding tamper error was returned. - Used for policy binding failures where KAS rejects the request (400). - Accepts both the new error classification (KAS request error) and the - legacy classification (tamper) for backward compatibility with older - SDK versions. + Policy binding failures (unbound or altered policy) are integrity + failures. KAS intentionally returns a generic "bad request" to avoid + leaking information about secret key computations, and the SDK + classifies this as a tamper error. """ + if decrypt_sdk.supports("tamper-error-split"): + # SDK distinguishes tamper from misconfiguration — assert tamper specifically + assert re.search(b"tamper", exc.output, re.IGNORECASE), ( + f"Expected tamper error, got: [{exc.output}]" + ) + assert not re.search(b"KAS request error", exc.output, re.IGNORECASE), ( + f"Policy binding failure must not be classified as KAS request error: [{exc.output}]" + ) + return + + # Older SDKs: accept any plausible error output expected_patterns = [ - # New classification: KAS request error - b"KAS request error", - b"rewrap request 400", + b"tamper", b"bad request", b"InvalidArgument", - # Legacy classification: tamper (older SDK versions) - b"tamper", + b"rewrap request 400", b"InvalidFileError", b"could not find policy in rewrap response", ] pattern = b"|".join(re.escape(p) for p in expected_patterns) assert re.search(pattern, exc.output, re.IGNORECASE), ( - f"Expected KAS request or tamper error, got: [{exc.output}]" + f"Expected policy tamper error, got: [{exc.output}]" ) @@ -628,7 +636,7 @@ def test_tdf_with_unbound_policy( decrypt_sdk.decrypt(b_file, rt_file, "ztdf", expect_error=True) assert False, "decrypt succeeded unexpectedly" except subprocess.CalledProcessError as exc: - assert_kas_request_error(exc, decrypt_sdk) + assert_policy_tamper_error(exc, decrypt_sdk) # Verify rewrap failure was logged (policy binding mismatch) # FIXME: Audit logs are not present on failed bindings @@ -669,7 +677,7 @@ def test_tdf_with_altered_policy_binding( decrypt_sdk.decrypt(b_file, rt_file, "ztdf", expect_error=True) assert False, "decrypt succeeded unexpectedly" except subprocess.CalledProcessError as exc: - assert_kas_request_error(exc, decrypt_sdk) + assert_policy_tamper_error(exc, decrypt_sdk) # Verify rewrap failure was logged (policy binding mismatch) # FIXME: Audit logs are not present on failed bindings