Skip to content

Commit 89d2d72

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 9c9d26a commit 89d2d72

File tree

7 files changed

+117
-61
lines changed

7 files changed

+117
-61
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' => 'yes'],
49+
['trusted' => 'yes', 'last_seen' => (new DateTime())->getTimestamp()],
4950
['id = ?' => $id]
5051
);
5152

application/clicommands/ScanCommand.php

Lines changed: 58 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,13 @@
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;
1214
use ipl\Sql\Connection;
1315
use ipl\Sql\Expression;
1416

@@ -18,16 +20,20 @@ class ScanCommand extends Command
1820
* Scans IP and port ranges to find X.509 certificates.
1921
*
2022
* This command starts scanning the IP and port ranges which belong to the job that was specified with the
21-
* --job parameter.
23+
* --job parameter and optional --last-scan and --last-seen parameter.
2224
*
2325
* USAGE
2426
*
25-
* icingacli x509 scan --job <name>
27+
* icingacli x509 scan --job <name> --since-last-scan <days> --since-last-seen <days>
2628
*/
2729
public function indexAction()
2830
{
2931
$name = $this->params->shiftRequired('job');
3032

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

3339
if ($parallel <= 0) {
@@ -60,19 +66,66 @@ public function indexAction()
6066
$name
6167
);
6268

63-
// $this->cleanupTargets($this->getDb());
69+
if ($sinceLastScan !== null) {
70+
$this->cleanupLastScanTargets($this->getDb(), $sinceLastScan);
71+
}
6472

6573
$verified = CertificateUtils::verifyCertificates($this->getDb());
6674

75+
if ($sinceLastSeen !== null) {
76+
$this->cleanupLastSeenTargets($this->getDb(), $sinceLastSeen);
77+
CertificateUtils::cleanupCertificates($this->getDb(), $sinceLastSeen);
78+
}
79+
80+
CertificateUtils::cleanupCertificateChains($this->getDb());
6781
Logger::info("Checked %d certificate chain%s.", $verified, $verified !== 1 ? 's' : '');
6882
}
6983
}
7084

71-
protected function cleanupTargets(Connection $db)
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)
7294
{
7395
$db->delete(
7496
'x509_target',
75-
['last_seen = ?' => new Expression('NOW() - INTERVAL 1 DAY')]
97+
[new Expression(sprintf(
98+
"last_seen < last_scan - %d",
99+
(new DateTime())->getTimestamp() - $sincLastSeen->getTimestamp()
100+
))]
76101
);
77102
}
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+
}
78131
}

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: 17 additions & 14 deletions
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;
@@ -246,7 +247,7 @@ public static function findOrInsertCert(Connection $db, $cert)
246247
'fingerprint' => $dbTool->marshalBinary($fingerprint),
247248
'serial' => $dbTool->marshalBinary(gmp_export($certInfo['serialNumber'])),
248249
'certificate' => $dbTool->marshalBinary($der),
249-
'last_seen' => new Expression('NOW()')
250+
'last_seen' => (new DateTime())->getTimestamp()
250251
]
251252
);
252253

@@ -355,24 +356,25 @@ private static function findOrInsertDn($db, $certInfo, $type)
355356
return $hash;
356357
}
357358

358-
public static function cleanupCertificates(Connection $db)
359+
public static function cleanupCertificates(Connection $db, DateTime $lastSeen)
359360
{
360361
$db->delete(
361362
'x509_certificate',
362-
['last_seen = ?' => new Expression('NOW() - INTERVAL 7 DAY')]
363+
[
364+
'last_seen < ?' => $lastSeen->getTimestamp(),
365+
'trusted = ?' => 'no'
366+
]
363367
);
368+
}
364369

370+
public static function cleanupCertificateChains(Connection $db)
371+
{
365372
$db->delete(
366-
'x509_dn',
367-
['last_seen' => new Expression('NOW() - INTERVAL 7 DAY')]
373+
'x509_certificate_chain',
374+
['target_id NOT IN (?)' => (new Select())
375+
->from('x509_target')
376+
->columns(['id'])]
368377
);
369-
370-
// DELETE
371-
//FROM x509_dn
372-
//WHERE type='subject' AND HASH <> ALL (
373-
// SELECT DISTINCT subject_hash
374-
//FROM x509_certificate
375-
//);
376378
}
377379

378380
/**
@@ -438,9 +440,10 @@ public static function verifyCertificates(Connection $db)
438440
foreach ($certs as $cert) {
439441
$db->update(
440442
'x509_certificate',
441-
['last_seen' => new Expression('NOW()')],
442-
['id = ?' => $chain->id]
443+
['last_seen' => (new DateTime())->getTimestamp()],
444+
['id = ?' => $cert->id]
443445
);
446+
444447
$collection[] = CertificateUtils::der2pem(DbTool::unmarshalBinary($cert->certificate));
445448
}
446449

0 commit comments

Comments
 (0)