mirror of
https://github.com/maelgangloff/domain-watchdog.git
synced 2025-12-18 02:05:36 +00:00
feat: add dns_key table
This commit is contained in:
parent
e270a3d970
commit
5640e0d05e
35
migrations/Version20250812002458.php
Normal file
35
migrations/Version20250812002458.php
Normal file
@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
* Auto-generated Migration: Please modify to your needs!
|
||||
*/
|
||||
final class Version20250812002458 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'Add dns_key table';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
// this up() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('CREATE TABLE dns_key (algorithm INT NOT NULL, digest_type INT NOT NULL, key_tag BYTEA NOT NULL, digest BYTEA NOT NULL, domain_id VARCHAR(255) NOT NULL, PRIMARY KEY(algorithm, digest_type, key_tag, domain_id, digest))');
|
||||
$this->addSql('CREATE INDEX IDX_88A62EF2115F0EE5 ON dns_key (domain_id)');
|
||||
$this->addSql('ALTER TABLE dns_key ADD CONSTRAINT FK_88A62EF2115F0EE5 FOREIGN KEY (domain_id) REFERENCES domain (ldh_name) NOT DEFERRABLE INITIALLY IMMEDIATE');
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
// this down() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('CREATE SCHEMA public');
|
||||
$this->addSql('ALTER TABLE dns_key DROP CONSTRAINT FK_88A62EF2115F0EE5');
|
||||
$this->addSql('DROP TABLE dns_key');
|
||||
}
|
||||
}
|
||||
36
src/Config/DnsKey/Algorithm.php
Normal file
36
src/Config/DnsKey/Algorithm.php
Normal file
@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
namespace App\Config\DnsKey;
|
||||
|
||||
/**
|
||||
* @see https://www.iana.org/assignments/dns-sec-alg-numbers/dns-sec-alg-numbers.xhtml
|
||||
*/
|
||||
enum Algorithm: int
|
||||
{
|
||||
case RSAMD5 = 1;
|
||||
case DH = 2;
|
||||
case DSA = 3;
|
||||
// 4 RESERVED
|
||||
case RSASHA1 = 5;
|
||||
case DSA_NSEC3_SHA1 = 6;
|
||||
case RSASHA1_NSEC3_SHA1 = 7;
|
||||
case RSASHA256 = 8;
|
||||
// 9 RESERVED
|
||||
case RSASHA512 = 10;
|
||||
// 11 RESERVED
|
||||
case ECC_GOST = 12;
|
||||
case ECDSAP256SHA256 = 13;
|
||||
case ECDSAP384SHA384 = 14;
|
||||
case ED25519 = 15;
|
||||
case ED448 = 16;
|
||||
case SM2SM3 = 17;
|
||||
// 18-22 RESERVED
|
||||
case ECC_GOST12 = 23;
|
||||
// 24-122 UNASSIGNED
|
||||
// 123-251 RESERVED
|
||||
case INDIRECT = 252;
|
||||
case PRIVATEDNS = 253;
|
||||
case PRIVATEOID = 254;
|
||||
case RESERVED_255 = 255;
|
||||
// 255 RESERVED
|
||||
}
|
||||
22
src/Config/DnsKey/DigestType.php
Normal file
22
src/Config/DnsKey/DigestType.php
Normal file
@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
namespace App\Config\DnsKey;
|
||||
|
||||
/**
|
||||
* @see https://www.iana.org/assignments/ds-rr-types/ds-rr-types.xhtml
|
||||
*/
|
||||
enum DigestType: int
|
||||
{
|
||||
case RESERVED = 0;
|
||||
case SHA1 = 1;
|
||||
case SHA256 = 2;
|
||||
case GOST_R_34_11_94 = 3;
|
||||
case SHA384 = 4;
|
||||
case GOST_R_34_11_2012 = 5;
|
||||
case SM3 = 6;
|
||||
|
||||
// 7-127 UNASSIGNED
|
||||
// 128-252 RESERVED
|
||||
// 253-254 RESERVED PRIVATE USE
|
||||
// 254 UNASSIGNED
|
||||
}
|
||||
100
src/Entity/DnsKey.php
Normal file
100
src/Entity/DnsKey.php
Normal file
@ -0,0 +1,100 @@
|
||||
<?php
|
||||
|
||||
namespace App\Entity;
|
||||
|
||||
use App\Config\DnsKey\Algorithm;
|
||||
use App\Config\DnsKey\DigestType;
|
||||
use App\Repository\DnsKeyRepository;
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Symfony\Component\Serializer\Attribute\Groups;
|
||||
|
||||
#[ORM\Entity(repositoryClass: DnsKeyRepository::class)]
|
||||
class DnsKey
|
||||
{
|
||||
#[ORM\Column(nullable: true, enumType: Algorithm::class)]
|
||||
#[Groups(['ds:list'])]
|
||||
#[ORM\Id]
|
||||
private ?Algorithm $algorithm;
|
||||
|
||||
#[ORM\Column(enumType: DigestType::class)]
|
||||
#[Groups(['ds:list'])]
|
||||
#[ORM\Id]
|
||||
private ?DigestType $digestType;
|
||||
|
||||
#[ORM\Column(type: Types::BINARY)]
|
||||
#[Groups(['ds:list'])]
|
||||
#[ORM\Id]
|
||||
private $keyTag;
|
||||
|
||||
#[ORM\ManyToOne(inversedBy: 'dnsKey')]
|
||||
#[ORM\JoinColumn(referencedColumnName: 'ldh_name', nullable: false)]
|
||||
#[Groups(['ds:list', 'ds:item'])]
|
||||
#[ORM\Id]
|
||||
private ?Domain $domain = null;
|
||||
|
||||
#[ORM\Column(type: Types::BLOB)]
|
||||
#[Groups(['ds:list'])]
|
||||
#[ORM\Id]
|
||||
private $digest;
|
||||
|
||||
public function getAlgorithm(): ?Algorithm
|
||||
{
|
||||
return $this->algorithm;
|
||||
}
|
||||
|
||||
public function setAlgorithm(?Algorithm $algorithm): static
|
||||
{
|
||||
$this->algorithm = $algorithm;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getDigestType(): ?DigestType
|
||||
{
|
||||
return $this->digestType;
|
||||
}
|
||||
|
||||
public function setDigestType(DigestType $digestType): static
|
||||
{
|
||||
$this->digestType = $digestType;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getKeyTag()
|
||||
{
|
||||
return unpack('n', $this->keyTag)[1];
|
||||
}
|
||||
|
||||
public function setKeyTag($keyTag): static
|
||||
{
|
||||
$this->keyTag = $keyTag;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getDomain(): ?Domain
|
||||
{
|
||||
return $this->domain;
|
||||
}
|
||||
|
||||
public function setDomain(?Domain $domain): static
|
||||
{
|
||||
$this->domain = $domain;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getDigest()
|
||||
{
|
||||
return strtoupper(bin2hex($this->digest));
|
||||
}
|
||||
|
||||
public function setDigest($digest): static
|
||||
{
|
||||
$this->digest = $digest;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
@ -39,6 +39,7 @@ use Symfony\Component\Serializer\Attribute\SerializedName;
|
||||
'nameserver-entity:nameserver',
|
||||
'nameserver-entity:entity',
|
||||
'tld:item',
|
||||
'ds:list',
|
||||
],
|
||||
],
|
||||
read: false
|
||||
@ -124,6 +125,13 @@ class Domain
|
||||
#[Groups(['domain:item', 'domain:list'])]
|
||||
private ?bool $delegationSigned = null;
|
||||
|
||||
/**
|
||||
* @var Collection<int, DnsKey>
|
||||
*/
|
||||
#[ORM\OneToMany(targetEntity: DnsKey::class, mappedBy: 'domain', orphanRemoval: true)]
|
||||
#[Groups(['domain:item'])]
|
||||
private Collection $dnsKey;
|
||||
|
||||
private const IMPORTANT_EVENTS = [EventAction::Deletion->value, EventAction::Expiration->value];
|
||||
private const IMPORTANT_STATUS = [
|
||||
'redemption period',
|
||||
@ -146,6 +154,7 @@ class Domain
|
||||
$this->createdAt = $this->updatedAt;
|
||||
$this->deleted = false;
|
||||
$this->domainStatuses = new ArrayCollection();
|
||||
$this->dnsKey = new ArrayCollection();
|
||||
}
|
||||
|
||||
public function getLdhName(): ?string
|
||||
@ -594,4 +603,34 @@ class Domain
|
||||
$this->calculateDaysFromStatus($now),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Collection<int, DnsKey>
|
||||
*/
|
||||
public function getDnsKey(): Collection
|
||||
{
|
||||
return $this->dnsKey;
|
||||
}
|
||||
|
||||
public function addDnsKey(DnsKey $dnsKey): static
|
||||
{
|
||||
if (!$this->dnsKey->contains($dnsKey)) {
|
||||
$this->dnsKey->add($dnsKey);
|
||||
$dnsKey->setDomain($this);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function removeDnsKey(DnsKey $dnsKey): static
|
||||
{
|
||||
if ($this->dnsKey->removeElement($dnsKey)) {
|
||||
// set the owning side to null (unless already changed)
|
||||
if ($dnsKey->getDomain() === $this) {
|
||||
$dnsKey->setDomain(null);
|
||||
}
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
||||
43
src/Repository/DnsKeyRepository.php
Normal file
43
src/Repository/DnsKeyRepository.php
Normal file
@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
namespace App\Repository;
|
||||
|
||||
use App\Entity\DnsKey;
|
||||
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
|
||||
use Doctrine\Persistence\ManagerRegistry;
|
||||
|
||||
/**
|
||||
* @extends ServiceEntityRepository<DnsKey>
|
||||
*/
|
||||
class DnsKeyRepository extends ServiceEntityRepository
|
||||
{
|
||||
public function __construct(ManagerRegistry $registry)
|
||||
{
|
||||
parent::__construct($registry, DnsKey::class);
|
||||
}
|
||||
|
||||
// /**
|
||||
// * @return DnsKey[] Returns an array of DnsKey objects
|
||||
// */
|
||||
// public function findByExampleField($value): array
|
||||
// {
|
||||
// return $this->createQueryBuilder('d')
|
||||
// ->andWhere('d.exampleField = :val')
|
||||
// ->setParameter('val', $value)
|
||||
// ->orderBy('d.id', 'ASC')
|
||||
// ->setMaxResults(10)
|
||||
// ->getQuery()
|
||||
// ->getResult()
|
||||
// ;
|
||||
// }
|
||||
|
||||
// public function findOneBySomeField($value): ?DnsKey
|
||||
// {
|
||||
// return $this->createQueryBuilder('d')
|
||||
// ->andWhere('d.exampleField = :val')
|
||||
// ->setParameter('val', $value)
|
||||
// ->getQuery()
|
||||
// ->getOneOrNullResult()
|
||||
// ;
|
||||
// }
|
||||
}
|
||||
@ -2,8 +2,11 @@
|
||||
|
||||
namespace App\Service;
|
||||
|
||||
use App\Config\DnsKey\Algorithm;
|
||||
use App\Config\DnsKey\DigestType;
|
||||
use App\Config\EventAction;
|
||||
use App\Config\TldType;
|
||||
use App\Entity\DnsKey;
|
||||
use App\Entity\Domain;
|
||||
use App\Entity\DomainEntity;
|
||||
use App\Entity\DomainEvent;
|
||||
@ -31,6 +34,7 @@ use Symfony\Component\DependencyInjection\Attribute\Autowire;
|
||||
use Symfony\Component\HttpClient\Exception\ClientException;
|
||||
use Symfony\Component\HttpFoundation\Exception\BadRequestException;
|
||||
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||
use Symfony\Component\HttpKernel\Exception\ServiceUnavailableHttpException;
|
||||
use Symfony\Component\Yaml\Yaml;
|
||||
use Symfony\Contracts\HttpClient\Exception\ClientExceptionInterface;
|
||||
use Symfony\Contracts\HttpClient\Exception\DecodingExceptionInterface;
|
||||
@ -171,6 +175,7 @@ readonly class RDAPService
|
||||
$this->updateDomainEvents($domain, $rdapData);
|
||||
$this->updateDomainEntities($domain, $rdapData);
|
||||
$this->updateDomainNameservers($domain, $rdapData);
|
||||
$this->updateDomainDsData($domain, $rdapData);
|
||||
|
||||
$domain->setDeleted(false)->updateTimestamps();
|
||||
|
||||
@ -625,6 +630,42 @@ readonly class RDAPService
|
||||
return $entity;
|
||||
}
|
||||
|
||||
private function updateDomainDsData(Domain $domain, array $rdapData): void
|
||||
{
|
||||
$domain->getDnsKey()->clear();
|
||||
$this->em->persist($domain);
|
||||
$this->em->flush();
|
||||
|
||||
if (array_key_exists('secureDNS', $rdapData) && array_key_exists('dsData', $rdapData['secureDNS']) && is_array($rdapData['secureDNS']['dsData'])) {
|
||||
foreach ($rdapData['secureDNS']['dsData'] as $rdapDsData) {
|
||||
$dsData = new DnsKey();
|
||||
if (array_key_exists('keyTag', $rdapDsData)) {
|
||||
$dsData->setKeyTag(pack('n', $rdapDsData['keyTag']));
|
||||
}
|
||||
if (array_key_exists('algorithm', $rdapDsData)) {
|
||||
$dsData->setAlgorithm(Algorithm::from($rdapDsData['algorithm']));
|
||||
}
|
||||
if (array_key_exists('digest', $rdapDsData)) {
|
||||
$blob = hex2bin($rdapDsData['digest']);
|
||||
if (false === $blob) {
|
||||
throw new ServiceUnavailableHttpException('DNSSEC digest is not a valid hexadecimal value.');
|
||||
}
|
||||
$dsData->setDigest($blob);
|
||||
}
|
||||
if (array_key_exists('digestType', $rdapDsData)) {
|
||||
$dsData->setDigestType(DigestType::from($rdapDsData['digestType']));
|
||||
}
|
||||
|
||||
$domain->addDnsKey($dsData);
|
||||
$this->em->persist($dsData);
|
||||
}
|
||||
} else {
|
||||
$this->logger->warning('The domain name {idnDomain} has no DS record.', [
|
||||
'idnDomain' => $domain->getLdhName(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws TransportExceptionInterface
|
||||
* @throws ServerExceptionInterface
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user