From 5da61f15dd9b399a9f464bb6858e05ec2569800f Mon Sep 17 00:00:00 2001 From: Luciano Righetti Date: Tue, 25 Jan 2022 18:01:51 +0100 Subject: [PATCH] add: initial version of cerebrate->cerebrate misp interconnection --- tests/Fixture/LocalToolsFixture.php | 20 ++ tests/Fixture/OrganisationsFixture.php | 7 +- .../Fixture/RemoteToolConnectionsFixture.php | 20 ++ tests/Helper/ApiTestTrait.php | 5 +- .../Api/Broods/TestBroodConnectionApiTest.php | 20 +- .../LocalTools/MispInterConnectionTest.php | 312 ++++++++++++++++++ 6 files changed, 372 insertions(+), 12 deletions(-) create mode 100644 tests/Fixture/LocalToolsFixture.php create mode 100644 tests/Fixture/RemoteToolConnectionsFixture.php create mode 100644 tests/TestCase/Api/LocalTools/MispInterConnectionTest.php diff --git a/tests/Fixture/LocalToolsFixture.php b/tests/Fixture/LocalToolsFixture.php new file mode 100644 index 0000000..bdc8334 --- /dev/null +++ b/tests/Fixture/LocalToolsFixture.php @@ -0,0 +1,20 @@ +records = []; + parent::init(); + } +} diff --git a/tests/Fixture/OrganisationsFixture.php b/tests/Fixture/OrganisationsFixture.php index 8531d0c..9e62495 100644 --- a/tests/Fixture/OrganisationsFixture.php +++ b/tests/Fixture/OrganisationsFixture.php @@ -11,7 +11,10 @@ class OrganisationsFixture extends TestFixture public $connection = 'test'; public const ORGANISATION_A_ID = 1; + public const ORGANISATION_A_UUID = 'dce5017e-b6a5-4d0d-a0d7-81e9af56c82c'; + public const ORGANISATION_B_ID = 2; + public const ORGANISATION_B_UUID = '36d22d9a-851e-4838-a655-9999c1d19497'; public function init(): void { @@ -20,7 +23,7 @@ class OrganisationsFixture extends TestFixture $this->records = [ [ 'id' => self::ORGANISATION_A_ID, - 'uuid' => $faker->uuid(), + 'uuid' => self::ORGANISATION_A_UUID, 'name' => 'Organisation A', 'url' => $faker->url, 'nationality' => $faker->countryCode, @@ -33,7 +36,7 @@ class OrganisationsFixture extends TestFixture [ 'id' => self::ORGANISATION_B_ID, 'uuid' => $faker->uuid(), - 'name' => 'Organisation B', + 'name' => self::ORGANISATION_B_UUID, 'url' => $faker->url, 'nationality' => $faker->countryCode, 'sector' => 'IT', diff --git a/tests/Fixture/RemoteToolConnectionsFixture.php b/tests/Fixture/RemoteToolConnectionsFixture.php new file mode 100644 index 0000000..9246836 --- /dev/null +++ b/tests/Fixture/RemoteToolConnectionsFixture.php @@ -0,0 +1,20 @@ +records = []; + parent::init(); + } +} diff --git a/tests/Helper/ApiTestTrait.php b/tests/Helper/ApiTestTrait.php index a55268c..4b3986f 100644 --- a/tests/Helper/ApiTestTrait.php +++ b/tests/Helper/ApiTestTrait.php @@ -240,7 +240,10 @@ trait ApiTestTrait protected function _sendRequest($url, $method, $data = []): void { // Adding Content-Type: application/json $this->configRequest() prevents this from happening somehow - if (in_array($method, ['POST', 'PATCH', 'PUT']) && $this->_request['headers']['Content-Type'] === 'application/json') { + if ( + in_array($method, ['POST', 'PATCH', 'PUT']) + && $this->_request['headers']['Content-Type'] === 'application/json' + ) { $data = json_encode($data); } diff --git a/tests/TestCase/Api/Broods/TestBroodConnectionApiTest.php b/tests/TestCase/Api/Broods/TestBroodConnectionApiTest.php index ee1117f..abc8be8 100644 --- a/tests/TestCase/Api/Broods/TestBroodConnectionApiTest.php +++ b/tests/TestCase/Api/Broods/TestBroodConnectionApiTest.php @@ -52,17 +52,19 @@ class TestBroodConnectionApiTest extends TestCase WireMock::get(WireMock::urlEqualTo('/instance/status.json')) ->willReturn(WireMock::aResponse() ->withHeader('Content-Type', 'application/json') - ->withBody((string)json_encode([ - "version" => "0.1", - "application" => "Cerebrate", - "user" => [ - "id" => 1, - "username" => "wiremock", - "role" => [ - "id" => 1 + ->withBody((string)json_encode( + [ + "version" => "0.1", + "application" => "Cerebrate", + "user" => [ + "id" => 1, + "username" => "wiremock", + "role" => [ + "id" => 1 + ] ] ] - ]))) + ))) ); } } diff --git a/tests/TestCase/Api/LocalTools/MispInterConnectionTest.php b/tests/TestCase/Api/LocalTools/MispInterConnectionTest.php new file mode 100644 index 0000000..e79431b --- /dev/null +++ b/tests/TestCase/Api/LocalTools/MispInterConnectionTest.php @@ -0,0 +1,312 @@ +skipOpenApiValidations(); + $this->initializeWireMock(); + $this->setAuthToken(AuthKeysFixture::ADMIN_API_KEY); + + $faker = \Faker\Factory::create(); + + // 1. Create LocalTool connection to `MISP LOCAL` (local MISP instance) + $this->post( + sprintf('%s/localTools/add', self::LOCAL_CEREBRATE_URL), + [ + 'name' => 'MISP_LOCAL', + 'connector' => 'MispConnector', + 'settings' => json_encode([ + 'url' => self::LOCAL_MISP_INSTANCE_URL, + 'authkey' => self::LOCAL_MISP_ADMIN_USER_AUTHKEY, + 'skip_ssl' => true, + ]), + 'description' => 'MISP local instance', + 'exposed' => true + ] + ); + $this->assertResponseOk(); + $this->assertDbRecordExists('LocalTools', ['name' => 'MISP_LOCAL']); + $connection = $this->getJsonResponseAsArray(); + // print_r($connection); + + // 2. Create a new Brood (connect to a remote Cerebrate instance) + $this->setAuthToken(AuthKeysFixture::ADMIN_API_KEY); + $LOCAL_BROOD_UUID = $faker->uuid; + + $this->post( + '/broods/add', + [ + 'uuid' => $LOCAL_BROOD_UUID, + 'name' => 'Local Brood', + 'url' => self::REMOTE_CEREBRATE_URL, + 'description' => $faker->text, + 'organisation_id' => OrganisationsFixture::ORGANISATION_A_ID, + 'trusted' => true, + 'pull' => true, + 'skip_proxy' => true, + 'authkey' => self::REMOTE_CEREBRATE_AUTHKEY, + ] + ); + $this->assertResponseOk(); + $this->assertDbRecordExists('Broods', ['uuid' => $LOCAL_BROOD_UUID]); + $brood = $this->getJsonResponseAsArray(); + // print_r($brood); + + // 3. Get remote Cerebrate exposed tools + $this->setAuthToken(AuthKeysFixture::ADMIN_API_KEY); + $this->mockCerebrateGetExposedToolsResponse('CEREBRATE_REMOTE', self::REMOTE_CEREBRATE_AUTHKEY); + $this->get(sprintf('/localTools/broodTools/%s', $brood['id'])); + $this->assertResponseOk(); + $tools = $this->getJsonResponseAsArray(); + // print_r($tools); + + // 4. Issue a connection request to the remote MISP instance + $this->setAuthToken(AuthKeysFixture::ADMIN_API_KEY); + $this->mockCerebrateGetExposedToolsResponse('CEREBRATE_REMOTE', self::REMOTE_CEREBRATE_AUTHKEY); + $this->mockMispViewOrganisationByUuid('MISP_LOCAL', OrganisationsFixture::ORGANISATION_A_UUID); + $this->mockMispCreateSyncUser( + 'MISP_LOCAL', + self::LOCAL_MISP_ADMIN_USER_AUTHKEY, + self::REMOTE_MISP_SYNC_USER_ID, + self::REMOTE_MISP_SYNC_USER_EMAIL, + self::REMOTE_MISP_SYNC_USER_AUTHKEY + ); + $this->mockCerebrateCreateMispIncommingConnectionRequest( + 'CEREBRATE_REMOTE', + UsersFixture::USER_ADMIN_ID, + self::LOCAL_CEREBRATE_URL, + self::REMOTE_CEREBRATE_AUTHKEY, + self::LOCAL_MISP_INSTANCE_URL + ); + $this->post( + sprintf('/localTools/connectionRequest/%s/%s', $brood['id'], $tools[0]['id']), + [ + 'local_tool_id' => 1 + ] + ); + $this->assertResponseOk(); + // $connectionRequest = $this->getJsonResponseAsArray(); + // print_r($connectionRequest); + + // 5. Remote Cerebrate accepts the connection request + $this->setAuthToken(AuthKeysFixture::ADMIN_API_KEY); // TODO: use the Cerebrate admin authkey + $this->post( + '/inbox/createEntry/LocalTool/AcceptedRequest', + [ + 'email' => self::REMOTE_MISP_SYNC_USER_EMAIL, + 'authkey' => self::REMOTE_MISP_SYNC_USER_AUTHKEY, + 'url' => self::LOCAL_MISP_INSTANCE_URL, + 'reflected_user_id' => self::REMOTE_MISP_SYNC_USER_ID, + 'connectorName' => 'MispConnector', + 'cerebrateURL' => self::REMOTE_CEREBRATE_URL, + 'local_tool_id' => 1, + 'remote_tool_id' => 1, + 'tool_name' => 'MISP_LOCAL' + ] + ); + $this->assertResponseOk(); + $acceptRequest = $this->getJsonResponseAsArray(); + // print_r($acceptRequest); + + // 6. Finalize the connection + $this->setAuthToken(AuthKeysFixture::ADMIN_API_KEY); + $this->mockEnableMispSyncUser('MISP_LOCAL', self::LOCAL_MISP_ADMIN_USER_AUTHKEY, self::REMOTE_MISP_SYNC_USER_ID); + $stub = $this->mockAddMispServer( + 'MISP_LOCAL', + self::LOCAL_MISP_ADMIN_USER_AUTHKEY, + [ + 'authkey' => self::REMOTE_MISP_SYNC_USER_AUTHKEY, + 'url' => self::LOCAL_MISP_INSTANCE_URL, + 'name' => 'MISP_LOCAL', + 'remote_org_id' => 1 + ] + ); + $this->post(sprintf('/inbox/process/%s', $acceptRequest['data']['id'])); + // $finalizeConnection = $this->getJsonResponseAsArray(); + // print_r($finalizeConnection); + $this->assertResponseOk(); + $this->assertResponseContains('"success": true'); + } + + private function mockCerebrateGetExposedToolsResponse(string $instance, string $cerebrateAuthkey): \WireMock\Stubbing\StubMapping + { + return $this->getWireMock()->stubFor( + WireMock::get(WireMock::urlEqualTo("/$instance/localTools/exposedTools")) + ->withHeader('Authorization', WireMock::equalTo($cerebrateAuthkey)) + ->willReturn(WireMock::aResponse() + ->withHeader('Content-Type', 'application/json') + ->withBody((string)json_encode( + [ + [ + "id" => 1, + "name" => "MISP_REMOTE", + "connector" => "MispConnector", + "description" => "Remote MISP instance" + ] + ] + ))) + ); + } + + private function mockMispViewOrganisationByUuid(string $instance, string $orgUuid): \WireMock\Stubbing\StubMapping + { + return $this->getWireMock()->stubFor( + WireMock::get(WireMock::urlEqualTo("/$instance/organisations/view/$orgUuid/limit:50")) + ->willReturn(WireMock::aResponse() + ->withHeader('Content-Type', 'application/json') + ->withBody((string)json_encode( + [ + "Organisation" => [ + "id" => 1, + "name" => "Local Organisation", + "uuid" => $orgUuid, + "local" => true + ] + ] + ))) + ); + } + + private function mockMispCreateSyncUser(string $instance, string $mispAuthkey, int $userId, string $email, string $authkey): \WireMock\Stubbing\StubMapping + { + return $this->getWireMock()->stubFor( + WireMock::post(WireMock::urlEqualTo("/$instance/admin/users/add")) + ->withHeader('Authorization', WireMock::equalTo($mispAuthkey)) + ->willReturn(WireMock::aResponse() + ->withHeader('Content-Type', 'application/json') + ->withBody((string)json_encode( + [ + "User" => [ + "id" => $userId, + "authkey" => $authkey, + "email" => $email + ] + ] + ))) + ); + } + + private function mockCerebrateCreateMispIncommingConnectionRequest( + string $instance, + int $userId, + string $cerebrateUrl, + string $cerebrateAuthkey, + string $mispUrl + ): \WireMock\Stubbing\StubMapping { + $faker = \Faker\Factory::create(); + + return $this->getWireMock()->stubFor( + WireMock::post(WireMock::urlEqualTo("/$instance/inbox/createEntry/LocalTool/IncomingConnectionRequest")) + ->withHeader('Authorization', WireMock::equalTo($cerebrateAuthkey)) + ->willReturn(WireMock::aResponse() + ->withHeader('Content-Type', 'application/json') + ->withBody((string)json_encode( + [ + 'data' => [ + 'id' => $faker->randomNumber(), + 'uuid' => $faker->uuid, + 'origin' => $cerebrateUrl, + 'user_id' => $userId, + 'data' => [ + 'connectorName' => 'MispConnector', + 'cerebrateURL' => $cerebrateUrl, + 'url' => $mispUrl, + 'tool_connector' => 'MispConnector', + 'local_tool_id' => 1, + 'remote_tool_id' => 1, + ], + 'title' => 'Request for MISP Inter-connection', + 'scope' => 'LocalTool', + 'action' => 'IncomingConnectionRequest', + 'description' => 'Handle Phase I of inter-connection when another cerebrate instance performs the request.', + 'local_tool_connector_name' => 'MispConnector', + 'created' => date('c'), + 'modified' => date('c') + ], + 'success' => true, + 'message' => 'LocalTool request for IncomingConnectionRequest created', + 'errors' => [], + ] + ))) + ); + } + + private function mockEnableMispSyncUser(string $instance, string $mispAuthkey, int $userId): \WireMock\Stubbing\StubMapping + { + return $this->getWireMock()->stubFor( + WireMock::post(WireMock::urlEqualTo("/$instance/admin/users/edit/$userId")) + ->withHeader('Authorization', WireMock::equalTo($mispAuthkey)) + ->withRequestBody(WireMock::equalToJson(json_encode(['disabled' => false]))) + ->willReturn(WireMock::aResponse() + ->withHeader('Content-Type', 'application/json') + ->withBody((string)json_encode( + [ + "User" => [ + "id" => $userId, + ] + ] + ))) + ); + } + + private function mockAddMispServer(string $instance, string $mispAuthkey, array $body): \WireMock\Stubbing\StubMapping + { + $faker = \Faker\Factory::create(); + + return $this->getWireMock()->stubFor( + WireMock::post(WireMock::urlEqualTo("/$instance/servers/add")) + ->withHeader('Authorization', WireMock::equalTo($mispAuthkey)) + ->withRequestBody(WireMock::equalToJson(json_encode($body))) + ->willReturn(WireMock::aResponse() + ->withHeader('Content-Type', 'application/json') + ->withBody((string)json_encode( + [ + 'Server' => [ + 'id' => $faker->randomNumber() + ] + ] + ))) + ); + } +}