diff --git a/app/controllers.php b/app/controllers.php index 12208bd..cb7e5e5 100644 --- a/app/controllers.php +++ b/app/controllers.php @@ -37,6 +37,7 @@ }); Http::post('/v1/runtimes/:runtimeId/commands') + ->groups(['api', 'runtimes']) ->desc('Execute a command inside an existing runtime') ->param('runtimeId', '', new Text(64), 'Unique runtime ID.') ->param('command', '', new Text(1024), 'Command to execute.') diff --git a/tests/e2e/ExecutorTest.php b/tests/e2e/ExecutorTest.php index 7db4b58..73b39f6 100644 --- a/tests/e2e/ExecutorTest.php +++ b/tests/e2e/ExecutorTest.php @@ -250,6 +250,17 @@ public function testGetRuntimesUnauthorized(): void $this->client->setKey($this->key); } + public function testCommandsUnauthorized(): void + { + $this->client->setKey(''); + $response = $this->client->call(Client::METHOD_POST, '/runtimes/test-commands-unauth/commands', [], [ + 'command' => 'true', + ]); + $this->assertEquals(401, $response['headers']['status-code']); + $this->assertEquals('Missing executor key', $response['body']['message']); + $this->client->setKey($this->key); + } + public function testBuild(): void { $output = ''; diff --git a/tests/unit/Http/RouteAuthTest.php b/tests/unit/Http/RouteAuthTest.php new file mode 100644 index 0000000..4cc69d9 --- /dev/null +++ b/tests/unit/Http/RouteAuthTest.php @@ -0,0 +1,54 @@ +fail('Unable to read app/controllers.php'); + } + + $matches = []; + \preg_match_all( + "/Http::(?:get|post|put|patch|delete)\\('([^']+)'\\)(.*?)(?=Http::(?:get|post|put|patch|delete|init|error)\\(|\\z)/s", + $source, + $matches, + PREG_SET_ORDER + ); + + $this->assertNotEmpty($matches, 'Expected HTTP routes in app/controllers.php'); + + $paths = []; + foreach ($matches as $match) { + $path = $match[1]; + $body = $match[2]; + $paths[] = $path; + + if ($path === '/v1/health') { + $this->assertDoesNotMatchRegularExpression( + "/->groups\\(\\[[^\\]]*?'api'/", + $body, + 'Health must stay public for Docker healthchecks' + ); + continue; + } + + $this->assertMatchesRegularExpression( + "/->groups\\(\\[[^\\]]*?'api'/", + $body, + \sprintf('Route %s must declare the api group so the executor secret check applies', $path) + ); + } + + $this->assertContains('/v1/runtimes/:runtimeId/commands', $paths); + $this->assertContains('/v1/health', $paths); + } +}