diff --git a/.github/workflows/conformance-tests-gax-showcase.yaml b/.github/workflows/conformance-tests-gax-showcase.yaml index 7962003b4d2e..604ca4e515e1 100644 --- a/.github/workflows/conformance-tests-gax-showcase.yaml +++ b/.github/workflows/conformance-tests-gax-showcase.yaml @@ -10,24 +10,25 @@ jobs: gapic-showcase: runs-on: ubuntu-latest env: - GAPIC_SHOWCASE_VERSION: 0.36.2 + GAPIC_SHOWCASE_VERSION: 0.42.0 OS: linux ARCH: amd64 name: GAPIC Showcase Conformance Tests steps: - name: Checkout code - uses: actions/checkout@v7 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 #v7.0.1 - name: Setup PHP - uses: shivammathur/setup-php@v2 + uses: shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240 #2.37.2 with: - php-version: '8.1' - extensions: grpc + php-version: '8.2' + extensions: grpc-1.83.0 - name: Install and run GAPIC Showcase run: | curl -L https://github.com/googleapis/gapic-showcase/releases/download/v${GAPIC_SHOWCASE_VERSION}/gapic-showcase-${GAPIC_SHOWCASE_VERSION}-${OS}-${ARCH}.tar.gz | tar -zx - ./gapic-showcase run & + ./gapic-showcase run --port :7469 --tls --ca-cert-output-file Gax/tests/Conformance/showcase.pem & + sleep 2 - name: Install dependencies run: | @@ -35,4 +36,3 @@ jobs: - name: Run PHPUnit run: Gax/vendor/bin/phpunit -c Gax/phpunit-conformance.xml.dist - diff --git a/Gax/.gitignore b/Gax/.gitignore index 94e81fe0df3a..5af2b82268b3 100644 --- a/Gax/.gitignore +++ b/Gax/.gitignore @@ -20,3 +20,5 @@ src/Jison/Parser.js # VS Code .vscode + +*.pem diff --git a/Gax/README.md b/Gax/README.md index 2f5a1d9322c9..5c10c76dcaa9 100644 --- a/Gax/README.md +++ b/Gax/README.md @@ -80,12 +80,20 @@ be found for Mac or Windows. > vendor/bin/phpunit ``` -4. Run static analysis tools +4. Run conformance tests (requires [GAPIC Showcase](https://github.com/googleapis/gapic-showcase)). + + ```sh + > ./gapic-showcase run --port :7469 --tls --ca-cert-output-file tests/Conformance/showcase.pem & + > vendor/bin/phpunit -c phpunit-conformance.xml.dist + ``` + +5. Run static analysis tools ```sh > phpstan -c phpstan.neon.dist ``` + ## License BSD - See [LICENSE][] for more information. diff --git a/Gax/tests/Conformance/PqcShowcaseTest.php b/Gax/tests/Conformance/PqcShowcaseTest.php new file mode 100644 index 000000000000..9f2d4bb0b2dc --- /dev/null +++ b/Gax/tests/Conformance/PqcShowcaseTest.php @@ -0,0 +1,105 @@ +fail('Could not read showcase.pem'); + } + + $grpcTransport = GrpcTransport::build($host, [ + 'stubOpts' => [ + 'credentials' => ChannelCredentials::createSsl($pemContents) + ] + ]); + + // REST Configuration + $restConfigPath = __DIR__ . '/src/V1beta1/resources/echo_rest_client_config.php'; + + $requestBuilder = new RequestBuilder($host, $restConfigPath); + $guzzleClient = new Client([ + 'verify' => $pemPath + ]); + $httpHandler = HttpHandlerFactory::build($guzzleClient); + $restTransport = new RestTransport($requestBuilder, [$httpHandler, 'async']); + + return [[$grpcTransport], [$restTransport]]; + } + + /** @dataProvider provideTransport */ + public function testPqc(TransportInterface $transport): void + { + $expected = 'This is a test'; + $expectedGroup = 'X25519MLKEM768'; + $responseHeaders = null; + $metadataCallback = function (array $metadata) use (&$responseHeaders) { + $responseHeaders = $metadata; + }; + + $echoClient = new EchoClient([ + 'credentials' => new InsecureCredentialsWrapper(), + 'transport' => $transport + ]); + + $echoRequest = new EchoRequest(); + $echoRequest->setContent($expected); + $response = $echoClient->echo($echoRequest, [ + 'metadataCallback' => $metadataCallback + ]); + + $this->assertEquals($expected, $response->getContent()); + $this->assertNotNull($responseHeaders); + + /** @var array> $responseHeaders */ + $responseHeaders = array_change_key_case($responseHeaders, CASE_LOWER); + $negotiatedGroup = $responseHeaders[self::TLS_GROUP][0]; + + if ($transport instanceof GrpcTransport) { + // gRPC must ALWAYS negotiate PQC + $this->assertEquals($expectedGroup, $negotiatedGroup); + } else { + // REST uses host system OpenSSL (X25519MLKEM768 when PQC is available, or X25519 fallback) + $this->assertContains($negotiatedGroup, [$expectedGroup, 'X25519']); + } + } +} diff --git a/Gax/tests/Conformance/ShowcaseTest.php b/Gax/tests/Conformance/ShowcaseTest.php index e5f9b22baa3c..921d79d91c2b 100644 --- a/Gax/tests/Conformance/ShowcaseTest.php +++ b/Gax/tests/Conformance/ShowcaseTest.php @@ -22,7 +22,7 @@ use Google\ApiCore\ApiException; use Google\ApiCore\KnownTypes; use Google\ApiCore\InsecureCredentialsWrapper; -use Google\ApiCore\InsecureRequestBuilder; +use Google\ApiCore\RequestBuilder; use Google\ApiCore\Transport\GrpcTransport; use Google\ApiCore\Transport\TransportInterface; use Google\ApiCore\Transport\RestTransport; @@ -31,21 +31,37 @@ use Google\Showcase\V1beta1\Client\EchoClient; use Google\Showcase\V1beta1\FailEchoWithDetailsRequest; use Grpc\ChannelCredentials; +use GuzzleHttp\Client; final class ShowcaseTest extends TestCase { public function provideTransport() { + $pemPath = __DIR__ . '/showcase.pem'; + $host = 'localhost:7469'; + + // gRPC Configuration + $pemContents = file_get_contents($pemPath); + + if (!$pemContents) { + $this->fail('Could not read showcase.pem'); + } + // build gRPC transport $grpc = GrpcTransport::build( 'localhost:7469', - ['stubOpts' => ['credentials' => ChannelCredentials::createInsecure()]] + [ + 'stubOpts' => [ + 'credentials' => ChannelCredentials::createSsl($pemContents) + ] + ] ); // build REST transport $restConfigPath = __DIR__ . '/src/V1beta1/resources/echo_rest_client_config.php'; - $requestBuilder = new InsecureRequestBuilder('localhost:7469', $restConfigPath); - $httpHandler = HttpHandlerFactory::build(); + $requestBuilder = new RequestBuilder('localhost:7469', $restConfigPath); + $guzzleClient = new Client(['verify' => $pemPath]); + $httpHandler = HttpHandlerFactory::build($guzzleClient); $rest = new RestTransport($requestBuilder, [$httpHandler, 'async']); return [[$grpc], [$rest]];