Skip to content

Commit 66f6335

Browse files
committed
Manage targets and certificates based on their availability
The following two parameters are introduced to `icingacli x509 scan` command to manage targets and certificates: 1) `since-last-scan` - used to check when the target was last scanned (stored in column `last_scan` of table `x509_target`) 2) `since-last-seen` - used to check when the target or certificate was last seen (stored in column `last_seen of tables `x509_target` and `x509_certificate`).
1 parent a9816ff commit 66f6335

File tree

7 files changed

+141
-8
lines changed

7 files changed

+141
-8
lines changed

application/clicommands/CheckCommand.php

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
namespace Icinga\Module\X509\Clicommands;
66

7+
use DateTime;
78
use Icinga\Application\Logger;
89
use Icinga\Module\X509\Command;
910
use Icinga\Module\X509\DbTool;
@@ -119,9 +120,9 @@ public function hostAction()
119120
$state = 2;
120121
}
121122

122-
$now = new \DateTime();
123-
$validFrom = (new \DateTime())->setTimestamp($target['valid_from']);
124-
$validTo = (new \DateTime())->setTimestamp($target['valid_to']);
123+
$now = new DateTime();
124+
$validFrom = (new DateTime())->setTimestamp($target->valid_from);
125+
$validTo = (new DateTime())->setTimestamp($target->valid_to);
125126
$criticalAfter = $this->thresholdToDateTime($validFrom, $validTo, $criticalThreshold, $criticalUnit);
126127
$warningAfter = $this->thresholdToDateTime($validFrom, $validTo, $warningThreshold, $warningUnit);
127128

application/clicommands/ImportCommand.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
namespace Icinga\Module\X509\Clicommands;
66

7+
use DateTime;
78
use Icinga\Application\Logger;
89
use Icinga\Module\X509\CertificateUtils;
910
use Icinga\Module\X509\Command;
@@ -45,7 +46,7 @@ public function indexAction()
4546

4647
$db->update(
4748
'x509_certificate',
48-
['trusted' => 'y'],
49+
['trusted' => 'y', 'last_seen' => (new DateTime())->getTimestamp()],
4950
['id = ?' => $id]
5051
);
5152

application/clicommands/ScanCommand.php

Lines changed: 67 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,28 +4,36 @@
44

55
namespace Icinga\Module\X509\Clicommands;
66

7+
use DateTime;
78
use Icinga\Application\Logger;
89
use Icinga\Module\X509\CertificateUtils;
910
use Icinga\Module\X509\Command;
1011
use Icinga\Module\X509\Hook\SniHook;
1112
use Icinga\Module\X509\Job;
13+
use InvalidArgumentException;
14+
use ipl\Sql\Connection;
15+
use ipl\Sql\Expression;
1216

1317
class ScanCommand extends Command
1418
{
1519
/**
1620
* Scans IP and port ranges to find X.509 certificates.
1721
*
1822
* This command starts scanning the IP and port ranges which belong to the job that was specified with the
19-
* --job parameter.
23+
* --job parameter and optional --since-last-scan and --since-last-seen parameters.
2024
*
2125
* USAGE
2226
*
23-
* icingacli x509 scan --job <name>
27+
* icingacli x509 scan --job <name> --since-last-scan <duration> --since-last-seen <duration>
2428
*/
2529
public function indexAction()
2630
{
2731
$name = $this->params->shiftRequired('job');
2832

33+
$sinceLastSeen = $this->processThresholdToDateTiime($this->params->get('since-last-seen'));
34+
35+
$sinceLastScan = $this->processThresholdToDateTiime($this->params->get('since-last-scan'));
36+
2937
$parallel = (int) $this->Config()->get('scan', 'parallel', 256);
3038

3139
if ($parallel <= 0) {
@@ -58,9 +66,66 @@ public function indexAction()
5866
$name
5967
);
6068

69+
if ($sinceLastScan !== null) {
70+
$this->cleanupLastScanTargets($this->getDb(), $sinceLastScan);
71+
}
72+
6173
$verified = CertificateUtils::verifyCertificates($this->getDb());
6274

75+
if ($sinceLastSeen !== null) {
76+
$this->cleanupLastSeenTargets($this->getDb(), $sinceLastSeen);
77+
CertificateUtils::cleanupCertificates($this->getDb(), $sinceLastSeen);
78+
}
79+
80+
CertificateUtils::cleanupCertificateChains($this->getDb());
6381
Logger::info("Checked %d certificate chain%s.", $verified, $verified !== 1 ? 's' : '');
6482
}
6583
}
84+
85+
protected function cleanupLastScanTargets(Connection $db, DateTime $sincLastScan)
86+
{
87+
$db->delete(
88+
'x509_target',
89+
['last_scan < ?' => $sincLastScan->getTimestamp()]
90+
);
91+
}
92+
93+
protected function cleanupLastSeenTargets(Connection $db, DateTime $sincLastSeen)
94+
{
95+
$db->delete(
96+
'x509_target',
97+
[new Expression(sprintf(
98+
"last_seen < last_scan - %d",
99+
(new DateTime())->getTimestamp() - $sincLastSeen->getTimestamp()
100+
))]
101+
);
102+
}
103+
104+
/**
105+
* @param string|null $threshold
106+
* @return DateTime|null
107+
*/
108+
protected function processThresholdToDateTiime(?string $threshold)
109+
{
110+
$since = $threshold;
111+
if ($threshold !== null) {
112+
if ($since[0] !== '-') {
113+
// When the user specified "2 days" as a threshold strtotime() will compute the
114+
// timestamp NOW() + 2 days, but it has to be NOW() + (-2 days)
115+
$since = "-$since";
116+
}
117+
118+
$since = strtotime($since);
119+
if ($since === false) {
120+
throw new InvalidArgumentException(sprintf(
121+
"The specified last scan time is in an unknown format: '%s'",
122+
$threshold
123+
));
124+
}
125+
126+
$since = (new DateTime())->setTimestamp($since);
127+
}
128+
129+
return $since;
130+
}
66131
}

etc/schema/mysql.schema.sql

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ CREATE TABLE x509_certificate (
1818
fingerprint binary(32) NOT NULL COMMENT 'sha256 hash',
1919
`serial` blob NOT NULL,
2020
certificate blob NOT NULL COMMENT 'DER encoded certificate',
21+
last_seen bigint(20) NOT NULL,
2122
ctime timestamp NULL DEFAULT NULL,
2223
mtime timestamp NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
2324
PRIMARY KEY (id),
@@ -86,6 +87,8 @@ CREATE TABLE x509_target (
8687
hostname varchar(255) NULL DEFAULT NULL,
8788
latest_certificate_chain_id int(10) unsigned NULL DEFAULT NULL,
8889
ctime timestamp NULL DEFAULT NULL,
90+
last_scan bigint(20) NOT NULL DEFAULT (EXTRACT(EPOCH FROM NOW())),
91+
last_seen bigint(20) NOT NULL,
8992
mtime timestamp NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
9093
PRIMARY KEY (id),
9194
INDEX x509_idx_target_ip_port (ip, port)

etc/schema/postgresql.schema.sql

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ CREATE TABLE x509_certificate (
3535
valid_to bigint NOT NULL,
3636
fingerprint bytea NOT NULL,
3737
serial bytea NOT NULL,
38+
last_seen bigint NOT NULL,
3839
certificate bytea NOT NULL,
3940
ctime timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP,
4041
mtime timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP,
@@ -98,6 +99,8 @@ CREATE TABLE x509_target (
9899
port uint2 NOT NULL,
99100
hostname varchar(255) NULL DEFAULT NULL,
100101
latest_certificate_chain_id int NULL DEFAULT NULL,
102+
last_seen bigint NOT NULL,
103+
last_scan bigint NOT NULL,
101104
ctime timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP,
102105
mtime timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP
103106
);

library/X509/CertificateUtils.php

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
namespace Icinga\Module\X509;
66

7+
use DateTime;
78
use Exception;
89
use Icinga\Application\Logger;
910
use Icinga\File\Storage\TemporaryLocalFileStorage;
@@ -244,7 +245,8 @@ public static function findOrInsertCert(Connection $db, $cert)
244245
'valid_to' => $certInfo['validTo_time_t'],
245246
'fingerprint' => $dbTool->marshalBinary($fingerprint),
246247
'serial' => $dbTool->marshalBinary(gmp_export($certInfo['serialNumber'])),
247-
'certificate' => $dbTool->marshalBinary($der)
248+
'certificate' => $dbTool->marshalBinary($der),
249+
'last_seen' => (new DateTime())->getTimestamp()
248250
]
249251
);
250252

@@ -353,6 +355,27 @@ private static function findOrInsertDn($db, $certInfo, $type)
353355
return $hash;
354356
}
355357

358+
public static function cleanupCertificates(Connection $db, DateTime $lastSeen)
359+
{
360+
$db->delete(
361+
'x509_certificate',
362+
[
363+
'last_seen < ?' => $lastSeen->getTimestamp(),
364+
'trusted = ?' => 'no'
365+
]
366+
);
367+
}
368+
369+
public static function cleanupCertificateChains(Connection $db)
370+
{
371+
$db->delete(
372+
'x509_certificate_chain',
373+
['target_id NOT IN (?)' => (new Select())
374+
->from('x509_target')
375+
->columns(['id'])]
376+
);
377+
}
378+
356379
/**
357380
* Verify certificates
358381
*
@@ -414,6 +437,12 @@ public static function verifyCertificates(Connection $db)
414437
$collection = [];
415438

416439
foreach ($certs as $cert) {
440+
$db->update(
441+
'x509_certificate',
442+
['last_seen' => (new DateTime())->getTimestamp()],
443+
['id = ?' => $cert->id]
444+
);
445+
417446
$collection[] = CertificateUtils::der2pem(DbTool::unmarshalBinary($cert->certificate));
418447
}
419448

library/X509/Job.php

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
namespace Icinga\Module\X509;
66

7+
use DateTime;
78
use Icinga\Application\Config;
89
use Icinga\Application\Logger;
910
use Icinga\Data\ConfigObject;
@@ -41,6 +42,7 @@ class Job
4142
private $snimap;
4243
private $parallel;
4344
private $name;
45+
private $currentDateTime;
4446

4547
public function __construct($name, ConfigObject $jobDescription, array $snimap, $parallel)
4648
{
@@ -50,6 +52,7 @@ public function __construct($name, ConfigObject $jobDescription, array $snimap,
5052
$this->snimap = $snimap;
5153
$this->parallel = $parallel;
5254
$this->name = $name;
55+
$this->currentDateTime = (new DateTime())->getTimestamp();
5356
}
5457

5558
private function getConnector($peerName)
@@ -203,6 +206,16 @@ function (ConnectionInterface $conn) use ($target, $streamCapture) {
203206
// Close connection in order to capture stream context options
204207
$conn->close();
205208

209+
$this->db->update(
210+
'x509_target',
211+
['last_seen' => $this->currentDateTime],
212+
[
213+
'ip = ?' => $this->dbTool->marshalBinary(static::binary($target->ip)),
214+
'port = ?' => $target->port,
215+
'hostname = ?' => $target->hostname
216+
]
217+
);
218+
206219
$capturedStreamOptions = $streamCapture->getCapturedStreamOptions();
207220

208221
$this->processChain($target, $capturedStreamOptions['ssl']['peer_certificate_chain']);
@@ -240,6 +253,16 @@ function (\Exception $exception) use ($target, $streamCapture) {
240253
)->otherwise(function (\Exception $e) {
241254
echo $e->getMessage() . PHP_EOL;
242255
echo $e->getTraceAsString() . PHP_EOL;
256+
})->always(function () use ($target) {
257+
$this->db->update(
258+
'x509_target',
259+
['last_scan' => $this->currentDateTime],
260+
[
261+
'ip = ?' => $this->dbTool->marshalBinary(static::binary($target->ip)),
262+
'port = ?' => $target->port,
263+
'hostname = ?' => $target->hostname
264+
]
265+
);
243266
});
244267
}
245268

@@ -309,7 +332,9 @@ protected function processChain($target, $chain)
309332
[
310333
'ip' => $this->dbTool->marshalBinary(static::binary($target->ip)),
311334
'port' => $target->port,
312-
'hostname' => $target->hostname
335+
'hostname' => $target->hostname,
336+
'last_scan' => $this->currentDateTime,
337+
'last_seen' => $this->currentDateTime
313338
]
314339
);
315340
$targetId = $this->db->lastInsertId();
@@ -369,6 +394,12 @@ protected function processChain($target, $chain)
369394

370395
$certId = CertificateUtils::findOrInsertCert($this->db, $cert, $certInfo);
371396

397+
$this->db->update(
398+
'x509_certificate',
399+
['last_seen' => $this->currentDateTime],
400+
['id = ?' => $certId]
401+
);
402+
372403
$this->db->insert(
373404
'x509_certificate_chain_link',
374405
[

0 commit comments

Comments
 (0)