summaryrefslogtreecommitdiffstats
path: root/library/X509/Model
diff options
context:
space:
mode:
Diffstat (limited to 'library/X509/Model')
-rw-r--r--library/X509/Model/Behavior/DERBase64.php44
-rw-r--r--library/X509/Model/Behavior/ExpressionInjector.php62
-rw-r--r--library/X509/Model/Behavior/Ip.php39
-rw-r--r--library/X509/Model/Schema.php49
-rw-r--r--library/X509/Model/X509Certificate.php159
-rw-r--r--library/X509/Model/X509CertificateChain.php58
-rw-r--r--library/X509/Model/X509CertificateChainLink.php46
-rw-r--r--library/X509/Model/X509CertificateSubjectAltName.php50
-rw-r--r--library/X509/Model/X509Dn.php51
-rw-r--r--library/X509/Model/X509Job.php73
-rw-r--r--library/X509/Model/X509JobRun.php77
-rw-r--r--library/X509/Model/X509Schedule.php70
-rw-r--r--library/X509/Model/X509Target.php74
13 files changed, 852 insertions, 0 deletions
diff --git a/library/X509/Model/Behavior/DERBase64.php b/library/X509/Model/Behavior/DERBase64.php
new file mode 100644
index 0000000..f7b7215
--- /dev/null
+++ b/library/X509/Model/Behavior/DERBase64.php
@@ -0,0 +1,44 @@
+<?php
+
+/* Icinga Web 2 X.509 Module | (c) 2022 Icinga GmbH | GPLv2 */
+
+namespace Icinga\Module\X509\Model\Behavior;
+
+use ipl\Orm\Contract\PropertyBehavior;
+
+/**
+ * Support automatically transformation of DER-encoded certificates to PEM and vice versa.
+ */
+class DERBase64 extends PropertyBehavior
+{
+ public function fromDb($value, $key, $_)
+ {
+ if (! $value) {
+ return null;
+ }
+
+ $block = chunk_split(base64_encode($value), 64, "\n");
+
+ return "-----BEGIN CERTIFICATE-----\n{$block}-----END CERTIFICATE-----";
+ }
+
+ public function toDb($value, $key, $_)
+ {
+ if (! $value) {
+ return null;
+ }
+
+ $lines = explode("\n", $value);
+ $der = '';
+
+ foreach ($lines as $line) {
+ if (strpos($line, '-----') === 0) {
+ continue;
+ }
+
+ $der .= base64_decode($line);
+ }
+
+ return $der;
+ }
+}
diff --git a/library/X509/Model/Behavior/ExpressionInjector.php b/library/X509/Model/Behavior/ExpressionInjector.php
new file mode 100644
index 0000000..c3fa2cb
--- /dev/null
+++ b/library/X509/Model/Behavior/ExpressionInjector.php
@@ -0,0 +1,62 @@
+<?php
+
+/* Icinga Web 2 X.509 Module | (c) 2022 Icinga GmbH | GPLv2 */
+
+namespace Icinga\Module\X509\Model\Behavior;
+
+use ipl\Orm\Contract\QueryAwareBehavior;
+use ipl\Orm\Contract\RewriteFilterBehavior;
+use ipl\Orm\Query;
+use ipl\Sql\ExpressionInterface;
+use ipl\Stdlib\Filter;
+
+/**
+ * Support expression columns (which don't really exist in the database, but rather
+ * resulted e.g. from a `case..when` expression), being used as filter columns
+ */
+class ExpressionInjector implements RewriteFilterBehavior, QueryAwareBehavior
+{
+ /** @var array */
+ protected $columns;
+
+ /** @var Query */
+ protected $query;
+
+ public function __construct(...$columns)
+ {
+ $this->columns = $columns;
+ }
+
+ public function setQuery(Query $query)
+ {
+ $this->query = $query;
+
+ return $this;
+ }
+
+ public function rewriteCondition(Filter\Condition $condition, $relation = null)
+ {
+ $columnName = $condition->metaData()->get('columnName');
+ if (in_array($columnName, $this->columns, true)) {
+ $relationPath = $condition->metaData()->get('relationPath');
+ if ($relationPath && $relationPath !== $this->query->getModel()->getTableAlias()) {
+ $subject = $this->query->getResolver()->resolveRelation($relationPath)->getTarget();
+ } else {
+ $subject = $this->query->getModel();
+ }
+
+ /** @var ExpressionInterface $column */
+ $column = $subject->getColumns()[$columnName];
+ $expression = clone $column;
+ $expression->setColumns($this->query->getResolver()->qualifyColumns(
+ $this->query->getResolver()->requireAndResolveColumns(
+ $expression->getColumns(),
+ $subject
+ ),
+ $subject
+ ));
+
+ $condition->setColumn($this->query->getDb()->getQueryBuilder()->buildExpression($expression));
+ }
+ }
+}
diff --git a/library/X509/Model/Behavior/Ip.php b/library/X509/Model/Behavior/Ip.php
new file mode 100644
index 0000000..79c9e80
--- /dev/null
+++ b/library/X509/Model/Behavior/Ip.php
@@ -0,0 +1,39 @@
+<?php
+
+/* Icinga Web 2 X.509 Module | (c) 2022 Icinga GmbH | GPLv2 */
+
+namespace Icinga\Module\X509\Model\Behavior;
+
+use ipl\Orm\Behavior\Binary;
+use ipl\Orm\Contract\PropertyBehavior;
+
+/**
+ * Support automatically transformation of human-readable IP addresses into their respective packed
+ * binary representation and vice versa.
+ */
+class Ip extends Binary
+{
+ public function fromDb($value, $key, $_)
+ {
+ $value = parent::fromDb($value, $key, $_);
+ if ($value === null) {
+ return null;
+ }
+
+ $ipv4 = ltrim($value, "\0");
+ if (strlen($ipv4) === 4) {
+ $value = $ipv4;
+ }
+
+ return inet_ntop($value);
+ }
+
+ public function toDb($value, $key, $_)
+ {
+ if ($value === null || $value === '*' || ! ctype_print($value)) {
+ return $value;
+ }
+
+ return parent::toDb(str_pad(inet_pton($value), 16, "\0", STR_PAD_LEFT), $key, $_);
+ }
+}
diff --git a/library/X509/Model/Schema.php b/library/X509/Model/Schema.php
new file mode 100644
index 0000000..02ec0c0
--- /dev/null
+++ b/library/X509/Model/Schema.php
@@ -0,0 +1,49 @@
+<?php
+
+/* Icinga Web 2 X.509 Module | (c) 2023 Icinga GmbH | GPLv2 */
+
+namespace Icinga\Module\X509\Model;
+
+use DateTime;
+use ipl\Orm\Behavior\BoolCast;
+use ipl\Orm\Behavior\MillisecondTimestamp;
+use ipl\Orm\Behaviors;
+use ipl\Orm\Model;
+
+/**
+ * A database model for x509 schema version table
+ *
+ * @property int $id Unique identifier of the database schema entries
+ * @property string $version The current schema version of Icinga Web
+ * @property DateTime $timestamp The insert/modify time of the schema entry
+ * @property bool $success Whether the database migration of the current version was successful
+ * @property ?string $reason The reason why the database migration has failed
+ */
+class Schema extends Model
+{
+ public function getTableName(): string
+ {
+ return 'x509_schema';
+ }
+
+ public function getKeyName()
+ {
+ return 'id';
+ }
+
+ public function getColumns(): array
+ {
+ return [
+ 'version',
+ 'timestamp',
+ 'success',
+ 'reason'
+ ];
+ }
+
+ public function createBehaviors(Behaviors $behaviors): void
+ {
+ $behaviors->add(new BoolCast(['success']));
+ $behaviors->add(new MillisecondTimestamp(['timestamp']));
+ }
+}
diff --git a/library/X509/Model/X509Certificate.php b/library/X509/Model/X509Certificate.php
new file mode 100644
index 0000000..63bdf95
--- /dev/null
+++ b/library/X509/Model/X509Certificate.php
@@ -0,0 +1,159 @@
+<?php
+
+/* Icinga Web 2 X.509 Module | (c) 2022 Icinga GmbH | GPLv2 */
+
+namespace Icinga\Module\X509\Model;
+
+use Icinga\Module\X509\Model\Behavior\DERBase64;
+use Icinga\Module\X509\Model\Behavior\ExpressionInjector;
+use ipl\Orm\Behavior\Binary;
+use ipl\Orm\Behavior\BoolCast;
+use ipl\Orm\Behavior\MillisecondTimestamp;
+use ipl\Orm\Behaviors;
+use ipl\Orm\Model;
+use ipl\Orm\Relations;
+use ipl\Sql\Expression;
+
+class X509Certificate extends Model
+{
+ public function getTableName()
+ {
+ return 'x509_certificate';
+ }
+
+ public function getTableAlias(): string
+ {
+ return 'certificate';
+ }
+
+ public function getKeyName()
+ {
+ return 'id';
+ }
+
+ public function getColumns()
+ {
+ return [
+ 'subject',
+ 'subject_hash',
+ 'issuer',
+ 'issuer_hash',
+ 'issuer_certificate_id',
+ 'version',
+ 'self_signed',
+ 'ca',
+ 'trusted',
+ 'pubkey_algo',
+ 'pubkey_bits',
+ 'signature_algo',
+ 'signature_hash_algo',
+ 'valid_from',
+ 'valid_to',
+ 'fingerprint',
+ 'serial',
+ 'certificate',
+ 'ctime',
+ 'mtime',
+ 'duration' => new Expression('%s - %s', ['valid_to', 'valid_from'])
+ ];
+ }
+
+ public function getColumnDefinitions()
+ {
+ return [
+ 'subject' => t('Certificate'),
+ 'issuer' => t('Issuer'),
+ 'version' => t('Version'),
+ 'self_signed' => t('Is Self-Signed'),
+ 'ca' => t('Is Certificate Authority'),
+ 'trusted' => t('Is Trusted'),
+ 'pubkey_algo' => t('Public Key Algorithm'),
+ 'pubkey_bits' => t('Public Key Strength'),
+ 'signature_algo' => t('Signature Algorithm'),
+ 'signature_hash_algo' => t('Signature Hash Algorithm'),
+ 'valid_from' => t('Valid From'),
+ 'valid_to' => t('Valid To'),
+ 'duration' => t('Duration'),
+ 'subject_hash' => t('Subject Hash'),
+ 'issuer_hash' => t('Issuer Hash'),
+ ];
+ }
+
+ public function getSearchColumns()
+ {
+ return ['subject', 'issuer'];
+ }
+
+ /**
+ * Get list of allowed columns to be exported
+ *
+ * @return string[]
+ */
+ public function getExportableColumns(): array
+ {
+ return [
+ 'id',
+ 'subject',
+ 'issuer',
+ 'version',
+ 'self_signed',
+ 'ca',
+ 'trusted',
+ 'pubkey_algo',
+ 'pubkey_bits',
+ 'signature_algo',
+ 'signature_hash_algo',
+ 'valid_from',
+ 'valid_to'
+ ];
+ }
+
+ public function createBehaviors(Behaviors $behaviors)
+ {
+ $behaviors->add(new Binary([
+ 'subject_hash',
+ 'issuer_hash',
+ 'fingerprint',
+ 'serial',
+ 'certificate'
+ ]));
+
+ $behaviors->add(new DERBase64(['certificate']));
+
+ $behaviors->add(new BoolCast([
+ 'ca',
+ 'trusted',
+ 'self_signed'
+ ]));
+
+ $behaviors->add(new MillisecondTimestamp([
+ 'valid_from',
+ 'valid_to',
+ 'ctime',
+ 'mtime',
+ 'duration'
+ ]));
+
+ $behaviors->add(new ExpressionInjector('duration'));
+ }
+
+ public function createRelations(Relations $relations)
+ {
+ $relations->belongsTo('issuer_certificate', static::class)
+ ->setForeignKey('subject_hash')
+ ->setCandidateKey('issuer_hash');
+ $relations->belongsToMany('chain', X509CertificateChain::class)
+ ->through(X509CertificateChainLink::class)
+ ->setForeignKey('certificate_id');
+
+ $relations->hasMany('certificate', static::class)
+ ->setForeignKey('issuer_hash')
+ ->setCandidateKey('subject_hash');
+ $relations->hasMany('alt_name', X509CertificateSubjectAltName::class)
+ ->setJoinType('LEFT');
+ $relations->hasMany('dn', X509Dn::class)
+ ->setForeignKey('hash')
+ ->setCandidateKey('subject_hash')
+ ->setJoinType('LEFT');
+ }
+}
diff --git a/library/X509/Model/X509CertificateChain.php b/library/X509/Model/X509CertificateChain.php
new file mode 100644
index 0000000..189c38d
--- /dev/null
+++ b/library/X509/Model/X509CertificateChain.php
@@ -0,0 +1,58 @@
+<?php
+
+/* Icinga Web 2 X.509 Module | (c) 2022 Icinga GmbH | GPLv2 */
+
+namespace Icinga\Module\X509\Model;
+
+use ipl\Orm\Behavior\BoolCast;
+use ipl\Orm\Behavior\MillisecondTimestamp;
+use ipl\Orm\Behaviors;
+use ipl\Orm\Model;
+use ipl\Orm\Relations;
+
+class X509CertificateChain extends Model
+{
+ public function getTableName()
+ {
+ return 'x509_certificate_chain';
+ }
+
+ public function getTableAlias(): string
+ {
+ return 'chain';
+ }
+
+ public function getKeyName()
+ {
+ return 'id';
+ }
+
+ public function getColumns()
+ {
+ return [
+ 'target_id',
+ 'length',
+ 'valid',
+ 'invalid_reason',
+ 'ctime'
+ ];
+ }
+
+ public function createBehaviors(Behaviors $behaviors)
+ {
+ $behaviors->add(new BoolCast(['valid']));
+
+ $behaviors->add(new MillisecondTimestamp(['ctime']));
+ }
+
+ public function createRelations(Relations $relations)
+ {
+ $relations->belongsTo('target', X509Target::class)
+ ->setCandidateKey('id')
+ ->setForeignKey('latest_certificate_chain_id');
+
+ $relations->belongsToMany('certificate', X509Certificate::class)
+ ->through(X509CertificateChainLink::class)
+ ->setForeignKey('certificate_chain_id');
+ }
+}
diff --git a/library/X509/Model/X509CertificateChainLink.php b/library/X509/Model/X509CertificateChainLink.php
new file mode 100644
index 0000000..d093793
--- /dev/null
+++ b/library/X509/Model/X509CertificateChainLink.php
@@ -0,0 +1,46 @@
+<?php
+
+/* Icinga Web 2 X.509 Module | (c) 2022 Icinga GmbH | GPLv2 */
+
+namespace Icinga\Module\X509\Model;
+
+use ipl\Orm\Behavior\MillisecondTimestamp;
+use ipl\Orm\Behaviors;
+use ipl\Orm\Model;
+use ipl\Orm\Relations;
+
+class X509CertificateChainLink extends Model
+{
+ public function getTableName()
+ {
+ return 'x509_certificate_chain_link';
+ }
+
+ public function getTableAlias(): string
+ {
+ return 'link';
+ }
+
+ public function getKeyName()
+ {
+ return ['certificate_chain_id', 'certificate_id', 'order'];
+ }
+
+ public function getColumns()
+ {
+ return ['ctime'];
+ }
+
+ public function createBehaviors(Behaviors $behaviors)
+ {
+ $behaviors->add(new MillisecondTimestamp(['ctime']));
+ }
+
+ public function createRelations(Relations $relations)
+ {
+ $relations->belongsTo('certificate', X509Certificate::class)
+ ->setCandidateKey('certificate_id');
+ $relations->belongsTo('chain', X509CertificateChain::class)
+ ->setCandidateKey('certificate_chain_id');
+ }
+}
diff --git a/library/X509/Model/X509CertificateSubjectAltName.php b/library/X509/Model/X509CertificateSubjectAltName.php
new file mode 100644
index 0000000..62aac5c
--- /dev/null
+++ b/library/X509/Model/X509CertificateSubjectAltName.php
@@ -0,0 +1,50 @@
+<?php
+
+/* Icinga Web 2 X.509 Module | (c) 2022 Icinga GmbH | GPLv2 */
+
+namespace Icinga\Module\X509\Model;
+
+use ipl\Orm\Behavior\Binary;
+use ipl\Orm\Behavior\MillisecondTimestamp;
+use ipl\Orm\Behaviors;
+use ipl\Orm\Model;
+use ipl\Orm\Relations;
+
+class X509CertificateSubjectAltName extends Model
+{
+ public function getTableName()
+ {
+ return 'x509_certificate_subject_alt_name';
+ }
+
+ public function getTableAlias(): string
+ {
+ return 'alt_name';
+ }
+
+ public function getKeyName()
+ {
+ return ['certificate_id', 'hash'];
+ }
+
+ public function getColumns()
+ {
+ return [
+ 'type',
+ 'value',
+ 'ctime'
+ ];
+ }
+
+ public function createBehaviors(Behaviors $behaviors)
+ {
+ $behaviors->add(new Binary(['hash']));
+
+ $behaviors->add(new MillisecondTimestamp(['ctime']));
+ }
+
+ public function createRelations(Relations $relations)
+ {
+ $relations->belongsTo('certificate', X509Certificate::class);
+ }
+}
diff --git a/library/X509/Model/X509Dn.php b/library/X509/Model/X509Dn.php
new file mode 100644
index 0000000..fa0406f
--- /dev/null
+++ b/library/X509/Model/X509Dn.php
@@ -0,0 +1,51 @@
+<?php
+
+/* Icinga Web 2 X.509 Module | (c) 2022 Icinga GmbH | GPLv2 */
+
+namespace Icinga\Module\X509\Model;
+
+use ipl\Orm\Behavior\Binary;
+use ipl\Orm\Behavior\MillisecondTimestamp;
+use ipl\Orm\Behaviors;
+use ipl\Orm\Model;
+use ipl\Orm\Relations;
+
+class X509Dn extends Model
+{
+ public function getTableName()
+ {
+ return 'x509_dn';
+ }
+
+ public function getTableAlias(): string
+ {
+ return 'dn';
+ }
+
+ public function getKeyName()
+ {
+ return ['hash', 'type', 'order'];
+ }
+
+ public function getColumns()
+ {
+ return [
+ 'key',
+ 'value',
+ 'ctime'
+ ];
+ }
+
+ public function createBehaviors(Behaviors $behaviors)
+ {
+ $behaviors->add(new Binary(['hash']));
+
+ $behaviors->add(new MillisecondTimestamp(['ctime']));
+ }
+
+ public function createRelations(Relations $relations)
+ {
+ $relations->belongsTo('certificate', X509Certificate::class)
+ ->setForeignKey('subject_hash');
+ }
+}
diff --git a/library/X509/Model/X509Job.php b/library/X509/Model/X509Job.php
new file mode 100644
index 0000000..1b3a855
--- /dev/null
+++ b/library/X509/Model/X509Job.php
@@ -0,0 +1,73 @@
+<?php
+
+/* Icinga Web 2 X.509 Module | (c) 2023 Icinga GmbH | GPLv2 */
+
+namespace Icinga\Module\X509\Model;
+
+use DateTime;
+use ipl\Orm\Behavior\MillisecondTimestamp;
+use ipl\Orm\Behaviors;
+use ipl\Orm\Model;
+use ipl\Orm\Query;
+use ipl\Orm\Relations;
+
+/**
+ * A database model for all x509 jobs
+ *
+ * @property int $id Unique identifier of this job
+ * @property string $name The name of this job
+ * @property string $author The author of this job
+ * @property string $cidrs The configured cidrs of this job
+ * @property string $ports The configured ports of this job
+ * @property ?string $exclude_targets The configured excluded targets of this job
+ * @property DateTime $ctime The creation time of this job
+ * @property DateTime $mtime The modification time of this job
+ * @property Query|X509Schedule $schedule The configured schedules of this job
+ * @property Query|X509JobRun $job_run Job activities
+ */
+class X509Job extends Model
+{
+ public function getTableName(): string
+ {
+ return 'x509_job';
+ }
+
+ public function getTableAlias(): string
+ {
+ return 'job';
+ }
+
+ public function getKeyName()
+ {
+ return 'id';
+ }
+
+ public function getColumns(): array
+ {
+ return [
+ 'name',
+ 'author',
+ 'cidrs',
+ 'ports',
+ 'exclude_targets',
+ 'ctime',
+ 'mtime'
+ ];
+ }
+
+ public function createBehaviors(Behaviors $behaviors): void
+ {
+ $behaviors->add(new MillisecondTimestamp([
+ 'ctime',
+ 'mtime'
+ ]));
+ }
+
+ public function createRelations(Relations $relations): void
+ {
+ $relations->hasMany('schedule', X509Schedule::class)
+ ->setForeignKey('job_id');
+ $relations->hasMany('job_run', X509JobRun::class)
+ ->setForeignKey('job_id');
+ }
+}
diff --git a/library/X509/Model/X509JobRun.php b/library/X509/Model/X509JobRun.php
new file mode 100644
index 0000000..d776622
--- /dev/null
+++ b/library/X509/Model/X509JobRun.php
@@ -0,0 +1,77 @@
+<?php
+
+/* Icinga Web 2 X.509 Module | (c) 2022 Icinga GmbH | GPLv2 */
+
+namespace Icinga\Module\X509\Model;
+
+use DateTime;
+use ipl\Orm\Behavior\MillisecondTimestamp;
+use ipl\Orm\Behaviors;
+use ipl\Orm\Model;
+use ipl\Orm\Query;
+use ipl\Orm\Relations;
+
+/**
+ * A database model for all x509 job schedules
+ *
+ * @property int $id Unique identifier of this job
+ * @property ?int $job_id The id of the x509 job this job run belongs to
+ * @property ?int $schedule_id The id of the x509 job schedule this run belongs to
+ * @property int $total_targets All the x509 targets found by this job run
+ * @property int $finished_targets All the x509 targets scanned by this job run
+ * @property DateTime $start_time The start time of this job run
+ * @property DateTime $end_time The end time of this job run
+ * @property Query|X509Job $job The x509 job this job run belongs to
+ * @property Query|X509Schedule $schedule The x509 job schedule this job run belongs to
+ */
+class X509JobRun extends Model
+{
+ public function getTableName(): string
+ {
+ return 'x509_job_run';
+ }
+
+ public function getTableAlias(): string
+ {
+ return 'job_run';
+ }
+
+ public function getKeyName()
+ {
+ return 'id';
+ }
+
+ public function getColumns(): array
+ {
+ return [
+ 'job_id',
+ 'schedule_id',
+ 'total_targets',
+ 'finished_targets',
+ 'start_time',
+ 'end_time'
+ ];
+ }
+
+ public function getDefaultSort(): string
+ {
+ return 'start_time desc';
+ }
+
+ public function createBehaviors(Behaviors $behaviors): void
+ {
+ $behaviors->add(new MillisecondTimestamp([
+ 'start_time',
+ 'end_time',
+ ]));
+ }
+
+ public function createRelations(Relations $relations): void
+ {
+ $relations->belongsTo('job', X509Job::class)
+ ->setCandidateKey('job_id');
+ $relations->belongsTo('schedule', X509Schedule::class)
+ ->setJoinType('LEFT')
+ ->setCandidateKey('schedule_id');
+ }
+}
diff --git a/library/X509/Model/X509Schedule.php b/library/X509/Model/X509Schedule.php
new file mode 100644
index 0000000..476641a
--- /dev/null
+++ b/library/X509/Model/X509Schedule.php
@@ -0,0 +1,70 @@
+<?php
+
+/* Icinga Web 2 X.509 Module | (c) 2023 Icinga GmbH | GPLv2 */
+
+namespace Icinga\Module\X509\Model;
+
+use DateTime;
+use ipl\Orm\Behavior\MillisecondTimestamp;
+use ipl\Orm\Behaviors;
+use ipl\Orm\Model;
+use ipl\Orm\Relations;
+
+/**
+ * A database model for all x509 job schedules
+ *
+ * @property int $id Unique identifier of this job
+ * @property int $job_id The id of the x509 job this schedule belongs to
+ * @property string $name The name of this job schedule
+ * @property string $author The author of this job schedule
+ * @property string $config The config of this job schedule
+ * @property DateTime $ctime The creation time of this job
+ * @property DateTime $mtime The modification time of this job
+ * @property X509Job $job The x509 job this schedule belongs to
+ * @property X509JobRun $job_run Schedule activities
+ */
+class X509Schedule extends Model
+{
+ public function getTableName(): string
+ {
+ return 'x509_schedule';
+ }
+
+ public function getTableAlias(): string
+ {
+ return 'schedule';
+ }
+
+ public function getKeyName()
+ {
+ return 'id';
+ }
+
+ public function getColumns(): array
+ {
+ return [
+ 'job_id',
+ 'name',
+ 'author',
+ 'config',
+ 'ctime',
+ 'mtime'
+ ];
+ }
+
+ public function createBehaviors(Behaviors $behaviors): void
+ {
+ $behaviors->add(new MillisecondTimestamp([
+ 'ctime',
+ 'mtime'
+ ]));
+ }
+
+ public function createRelations(Relations $relations): void
+ {
+ $relations->belongsTo('job', X509Job::class)
+ ->setCandidateKey('job_id');
+ $relations->hasMany('job_run', X509JobRun::class)
+ ->setForeignKey('schedule_id');
+ }
+}
diff --git a/library/X509/Model/X509Target.php b/library/X509/Model/X509Target.php
new file mode 100644
index 0000000..7705d57
--- /dev/null
+++ b/library/X509/Model/X509Target.php
@@ -0,0 +1,74 @@
+<?php
+
+/* Icinga Web 2 X.509 Module | (c) 2022 Icinga GmbH | GPLv2 */
+
+namespace Icinga\Module\X509\Model;
+
+use Icinga\Module\X509\Model\Behavior\Ip;
+use ipl\Orm\Behavior\Binary;
+use ipl\Orm\Behavior\MillisecondTimestamp;
+use ipl\Orm\Behaviors;
+use ipl\Orm\Model;
+use ipl\Orm\Relations;
+
+class X509Target extends Model
+{
+ public function getTableName()
+ {
+ return 'x509_target';
+ }
+
+ public function getTableAlias(): string
+ {
+ return 'target';
+ }
+
+ public function getKeyName()
+ {
+ return 'id';
+ }
+
+ public function getColumns()
+ {
+ return [
+ 'ip',
+ 'port',
+ 'hostname',
+ 'latest_certificate_chain_id',
+ 'last_scan',
+ 'ctime',
+ 'mtime'
+ ];
+ }
+
+ public function getColumnDefinitions()
+ {
+ return [
+ 'hostname' => t('Host Name'),
+ 'ip' => t('IP'),
+ 'port' => t('Port')
+ ];
+ }
+
+ public function getSearchColumns()
+ {
+ return ['hostname'];
+ }
+
+ public function createBehaviors(Behaviors $behaviors)
+ {
+ $behaviors->add(new Ip(['ip']));
+
+ $behaviors->add(new MillisecondTimestamp([
+ 'ctime',
+ 'mtime',
+ 'last_scan'
+ ]));
+ }
+
+ public function createRelations(Relations $relations)
+ {
+ $relations->belongsTo('chain', X509CertificateChain::class)
+ ->setCandidateKey('latest_certificate_chain_id');
+ }
+}