chg: [internal] Faster fetching event index

pull/8720/head
Jakub Onderka 2022-10-29 21:03:29 +02:00
parent bd0dde5e37
commit a27e036c5b
2 changed files with 111 additions and 106 deletions

View File

@ -768,26 +768,25 @@ class EventsController extends AppController
*/ */
private function __indexRestResponse(array $passedArgs) private function __indexRestResponse(array $passedArgs)
{ {
$isSync = $skipProtected = false; // We do not want to allow instances to pull our data that can't make sense of protected mode events
if (!empty($this->request->header('misp-version'))) { $skipProtected = (
$isSync = true; !empty($this->request->header('misp-version')) &&
if (version_compare($this->request->header('misp-version'), '2.4.156') < 0) { version_compare($this->request->header('misp-version'), '2.4.156') < 0
$skipProtected = true; );
}
}
$fieldNames = $this->Event->schema(); $fieldNames = $this->Event->schema();
$minimal = !empty($passedArgs['searchminimal']) || !empty($passedArgs['minimal']); $minimal = !empty($passedArgs['searchminimal']) || !empty($passedArgs['minimal']);
if ($minimal) { if ($minimal) {
$rules = [ $rules = [
'recursive' => -1, 'recursive' => -1,
'fields' => array('id', 'timestamp', 'sighting_timestamp', 'published', 'uuid', 'protected'), 'fields' => array('id', 'timestamp', 'sighting_timestamp', 'published', 'uuid', 'protected'),
'contain' => array('Orgc.uuid', 'CryptographicKey.fingerprint'), 'contain' => array('Orgc.uuid'),
]; ];
} else { } else {
// Remove user ID from fetched fields // Remove user ID from fetched fields
unset($fieldNames['user_id']); unset($fieldNames['user_id']);
$rules = [ $rules = [
'contain' => ['EventTag', 'CryptographicKey.fingerprint'], 'contain' => ['EventTag'],
'fields' => array_keys($fieldNames), 'fields' => array_keys($fieldNames),
]; ];
} }
@ -801,6 +800,9 @@ class EventsController extends AppController
if (isset($this->paginate['conditions'])) { if (isset($this->paginate['conditions'])) {
$rules['conditions'] = $this->paginate['conditions']; $rules['conditions'] = $this->paginate['conditions'];
} }
if ($skipProtected) {
$rules['conditions']['Event.protected'] = 0;
}
$paginationRules = array('page', 'limit', 'sort', 'direction', 'order'); $paginationRules = array('page', 'limit', 'sort', 'direction', 'order');
foreach ($paginationRules as $paginationRule) { foreach ($paginationRules as $paginationRule) {
if (isset($passedArgs[$paginationRule])) { if (isset($passedArgs[$paginationRule])) {
@ -837,12 +839,12 @@ class EventsController extends AppController
$events = $absolute_total === 0 ? [] : $this->Event->find('all', $rules); $events = $absolute_total === 0 ? [] : $this->Event->find('all', $rules);
} }
$isCsvResponse = $this->response->type() === 'text/csv'; $isCsvResponse = $this->response->type() === 'text/csv';
try {
$instanceFingerprint = $this->Event->CryptographicKey->ingestInstanceKey(); $protectedEventsByInstanceKey = $this->Event->CryptographicKey->protectedEventsByInstanceKey($events);
} catch (Exception $e) { $protectedEventsByInstanceKey = array_flip($protectedEventsByInstanceKey);
$instanceFingerprint = null;
}
if (!$minimal) { if (!$minimal) {
// Collect all tag IDs that are events // Collect all tag IDs that are events
$tagIds = []; $tagIds = [];
@ -889,19 +891,8 @@ class EventsController extends AppController
$orgIds[$event['Event']['org_id']] = true; $orgIds[$event['Event']['org_id']] = true;
$orgIds[$event['Event']['orgc_id']] = true; $orgIds[$event['Event']['orgc_id']] = true;
$sharingGroupIds[$event['Event']['sharing_group_id']] = true; $sharingGroupIds[$event['Event']['sharing_group_id']] = true;
if ($event['Event']['protected']) { if ($event['Event']['protected'] && !isset($protectedEventsByInstanceKey[$event['Event']['id']])) {
if ($skipProtected) {
unset($events[$k]);
continue;
}
foreach ($event['CryptographicKey'] as $cryptoKey) {
if ($instanceFingerprint === $cryptoKey['fingerprint']) {
continue 2;
}
}
unset($events[$k]); unset($events[$k]);
continue;
} }
} }
$events = array_values($events); $events = array_values($events);
@ -946,26 +937,9 @@ class EventsController extends AppController
if ($this->response->type() === 'application/xml') { if ($this->response->type() === 'application/xml') {
$events = array('Event' => $events); $events = array('Event' => $events);
} }
} else { } else { // minimal
// We do not want to allow instances to pull our data that can't make sense of protected mode events
$skipProtected = (
!empty($this->request->header('misp-version')) &&
version_compare($this->request->header('misp-version'), '2.4.156') < 0
);
foreach ($events as $key => $event) { foreach ($events as $key => $event) {
if ($event['Event']['protected']) { if ($event['Event']['protected'] && !isset($protectedEventsByInstanceKey[$event['Event']['id']])) {
if ($skipProtected) {
unset($events[$key]);
continue;
}
foreach ($event['CryptographicKey'] as $cryptoKey) {
if ($instanceFingerprint === $cryptoKey['fingerprint']) {
$event['Event']['orgc_uuid'] = $event['Orgc']['uuid'];
unset($event['Event']['protected']);
$events[$key] = $event['Event'];
continue 2;
}
}
unset($events[$key]); unset($events[$key]);
continue; continue;
} }

View File

@ -27,57 +27,46 @@ class CryptographicKey extends AppModel
ERROR_WRONG_KEY = 'Wrong key', ERROR_WRONG_KEY = 'Wrong key',
ERROR_INVALID_KEY = 'Invalid key'; ERROR_INVALID_KEY = 'Invalid key';
public $validTypes = [ const VALID_TYPES = [
'pgp' 'pgp'
]; ];
public $error = false; public $error = false;
public $validate = []; public $validate = [
'uuid' => [
'uuid' => [
'rule' => 'uuid',
'message' => 'Please provide a valid RFC 4122 UUID',
],
],
'type' => [
'rule' => ['inList', self::VALID_TYPES],
'message' => 'Invalid key type',
'required' => 'create'
],
'key_data' => [
'notBlankKey' => [
'rule' => 'notBlank',
'message' => 'No key data received.',
'required' => 'create'
],
'validKey' => [
'rule' => 'validateKey',
'message' => 'Invalid key.',
'required' => 'create'
],
'uniqueKeyForElement' => [
'rule' => 'uniqueKeyForElement',
'message' => 'This key is already assigned to the target.',
'required' => 'create'
]
]
];
/** @var CryptGpgExtended|null */ /** @var CryptGpgExtended|null */
private $gpg; private $gpg;
public function __construct($id = false, $table = null, $ds = null)
{
parent::__construct($id, $table, $ds);
try {
$this->gpg = GpgTool::initializeGpg();
} catch (Exception $e) {
$this->gpg = null;
}
$this->validate = [
'uuid' => [
'uuid' => [
'rule' => 'uuid',
'message' => 'Please provide a valid RFC 4122 UUID',
],
],
'type' => [
'rule' => ['inList', $this->validTypes],
'message' => __('Invalid key type'),
'required' => 'create'
],
'key_data' => [
'notBlankKey' => [
'rule' => 'notBlank',
'message' => __('No key data received.'),
'required' => 'create'
],
'validKey' => [
'rule' => 'validateKey',
'message' => __('Invalid key.'),
'required' => 'create'
],
'uniqueKeyForElement' => [
'rule' => 'uniqueKeyForElement',
'message' => __('This key is already assigned to the target.'),
'required' => 'create'
]
]
];
}
public function beforeSave($options = array()) public function beforeSave($options = array())
{ {
$this->data['CryptographicKey']['timestamp'] = time(); $this->data['CryptographicKey']['timestamp'] = time();
@ -97,20 +86,17 @@ class CryptographicKey extends AppModel
{ {
// If instance key is stored just in GPG homedir, use that key. // If instance key is stored just in GPG homedir, use that key.
if (Configure::read('MISP.download_gpg_from_homedir')) { if (Configure::read('MISP.download_gpg_from_homedir')) {
if (!$this->gpg) {
throw new Exception("Could not initiate GPG");
}
/** @var Crypt_GPG_Key[] $keys */ /** @var Crypt_GPG_Key[] $keys */
$keys = $this->gpg->getKeys(Configure::read('GnuPG.email')); $keys = $this->getGpg()->getKeys(Configure::read('GnuPG.email'));
if (empty($keys)) { if (empty($keys)) {
return false; return false;
} }
$this->gpg->addSignKey($keys[0], Configure::read('GnuPG.password')); $this->getGpg()->addSignKey($keys[0], Configure::read('GnuPG.password'));
return $keys[0]->getPrimaryKey()->getFingerprint(); return $keys[0]->getPrimaryKey()->getFingerprint();
} }
try { try {
$redis = $this->setupRedisWithException(); $redis = RedisTool::init();
} catch (Exception $e) { } catch (Exception $e) {
$redis = false; $redis = false;
} }
@ -124,33 +110,63 @@ class CryptographicKey extends AppModel
if (empty($fingerprint)) { if (empty($fingerprint)) {
$file = new File(APP . '/webroot/gpg.asc'); $file = new File(APP . '/webroot/gpg.asc');
$instanceKey = $file->read(); $instanceKey = $file->read();
if (!$this->gpg) {
throw new MethodNotAllowedException("Could not initiate GPG");
}
try { try {
$this->gpg->importKey($instanceKey); $this->getGpg()->importKey($instanceKey);
} catch (Crypt_GPG_NoDataException $e) { } catch (Crypt_GPG_NoDataException $e) {
throw new MethodNotAllowedException("Could not import the instance key."); throw new MethodNotAllowedException("Could not import the instance key.");
} }
$fingerprint = $this->gpg->getFingerprint(Configure::read('GnuPG.email')); $fingerprint = $this->getGpg()->getFingerprint(Configure::read('GnuPG.email'));
if ($redis) { if ($redis) {
$redis->setEx($redisKey, 300, $fingerprint); $redis->setEx($redisKey, 300, $fingerprint);
} }
} }
if (!$this->gpg) {
throw new MethodNotAllowedException("Could not initiate GPG");
}
try { try {
$this->gpg->addSignKey(Configure::read('GnuPG.email'), Configure::read('GnuPG.password')); $this->getGpg()->addSignKey(Configure::read('GnuPG.email'), Configure::read('GnuPG.password'));
} catch (Exception $e) { } catch (Exception $e) {
throw new NotFoundException('Could not add signing key.'); throw new NotFoundException('Could not add signing key.');
} }
return $fingerprint; return $fingerprint;
} }
/**
* Check if given events are protected by instance key, returns array of Event IDs
* @param array $events
* @return array Event ID that is protected in key
*/
public function protectedEventsByInstanceKey(array $events)
{
$eventIds = [];
foreach ($events as $event) {
if ($event['Event']['protected']) {
$eventIds[] = $event['Event']['id'];
}
}
if (empty($eventIds)) {
return [];
}
try {
$instanceKey = $this->ingestInstanceKey();
} catch (Exception $e) {
// could not fetch instance key
return [];
}
return $this->find('column', [
'conditions' => [
'CryptographicKey.parent_type' => 'Event',
'CryptographicKey.parent_id' => $eventIds,
'CryptographicKey.fingerprint' => $instanceKey,
],
'fields' => ['CryptographicKey.parent_id'],
'recursive' => -1,
]);
}
/** /**
* @param string $data * @param string $data
* @return false|string * @return false|string Signature
* @throws Crypt_GPG_BadPassphraseException * @throws Crypt_GPG_BadPassphraseException
* @throws Crypt_GPG_Exception * @throws Crypt_GPG_Exception
* @throws Crypt_GPG_KeyNotFoundException * @throws Crypt_GPG_KeyNotFoundException
@ -161,7 +177,7 @@ class CryptographicKey extends AppModel
return false; return false;
} }
$data = preg_replace("/\s+/", "", $data); $data = preg_replace("/\s+/", "", $data);
$signature = $this->gpg->sign($data, Crypt_GPG::SIGN_MODE_DETACHED, Crypt_GPG::ARMOR_BINARY); $signature = $this->getGpg()->sign($data, Crypt_GPG::SIGN_MODE_DETACHED, Crypt_GPG::ARMOR_BINARY);
return $signature; return $signature;
} }
@ -181,7 +197,7 @@ class CryptographicKey extends AppModel
} }
$data = preg_replace("/\s+/", "", $data); $data = preg_replace("/\s+/", "", $data);
try { try {
$verifiedSignature = $this->gpg->verify($data, $signature); $verifiedSignature = $this->getGpg()->verify($data, $signature);
} catch (Exception $e) { } catch (Exception $e) {
$this->error = self::ERROR_WRONG_KEY; $this->error = self::ERROR_WRONG_KEY;
return false; return false;
@ -219,7 +235,7 @@ class CryptographicKey extends AppModel
private function __extractPGPKeyData($data) private function __extractPGPKeyData($data)
{ {
try { try {
$gpgTool = new GpgTool($this->gpg); $gpgTool = new GpgTool($this->getGpg());
} catch (Exception $e) { } catch (Exception $e) {
$this->logException("GPG couldn't be initialized, GPG encryption and signing will be not available.", $e, LOG_NOTICE); $this->logException("GPG couldn't be initialized, GPG encryption and signing will be not available.", $e, LOG_NOTICE);
return false; return false;
@ -345,4 +361,19 @@ class CryptographicKey extends AppModel
$this->deleteAll(['CryptographicKey.id' => $toRemove]); $this->deleteAll(['CryptographicKey.id' => $toRemove]);
$this->loadLog()->createLogEntry($user, 'updateCryptoKeys', $cryptographicKey['parent_type'], $cryptographicKey['parent_id'], $message); $this->loadLog()->createLogEntry($user, 'updateCryptoKeys', $cryptographicKey['parent_type'], $cryptographicKey['parent_id'], $message);
} }
/**
* Lazy load GPG
* @return CryptGpgExtended|null
* @throws Exception
*/
private function getGpg()
{
if ($this->gpg) {
return $this->gpg;
}
$this->gpg = GpgTool::initializeGpg();
return $this->gpg;
}
} }