96 lines
3.1 KiB
PHP
96 lines
3.1 KiB
PHP
<?php
|
||
|
||
namespace App\Console\Commands;
|
||
|
||
use App\Models\Domain;
|
||
use App\Models\TlsaRecord;
|
||
use App\Support\Hostnames;
|
||
use Illuminate\Console\Command;
|
||
use Symfony\Component\Process\Process;
|
||
|
||
class GenerateTlsaRecord extends Command
|
||
{
|
||
protected $signature = 'dns:tlsa
|
||
{domainId : ID der Domain in eurer domains-Tabelle}
|
||
{--host= : FQDN des MTA-Hosts (z.B. mailsrv012.domain.com)}
|
||
{--service=_25._tcp : TLSA Service-Präfix, Standard _25._tcp}
|
||
{--usage=3 : 3 = DANE-EE}
|
||
{--selector=1 : 1 = SPKI}
|
||
{--matching=1 : 1 = SHA-256}';
|
||
|
||
protected $description = 'Erzeugt/aktualisiert einen TLSA-Record und speichert ihn in tlsa_records.';
|
||
|
||
public function handle(): int
|
||
{
|
||
$domain = Domain::find($this->argument('domainId'));
|
||
if (!$domain) {
|
||
$this->error('Domain nicht gefunden.');
|
||
return self::FAILURE;
|
||
}
|
||
|
||
// Host bestimmen
|
||
$host = trim($this->option('host') ?: Hostnames::mta());
|
||
if (!str_contains($host, '.')) {
|
||
$this->error("Ungültiger Host: {$host}");
|
||
return self::FAILURE;
|
||
}
|
||
|
||
$service = $this->option('service') ?: '_25._tcp';
|
||
$usage = (int) $this->option('usage');
|
||
$selector = (int) $this->option('selector');
|
||
$matching = (int) $this->option('matching');
|
||
|
||
// Let’s Encrypt Pfad (ggf. anpassen, falls anderes CA/Verzeichnis)
|
||
$certPath = "/etc/letsencrypt/live/{$host}/fullchain.pem";
|
||
|
||
if (!is_file($certPath)) {
|
||
$this->error("Zertifikat nicht gefunden: {$certPath}");
|
||
$this->line('Tipp: LE deploy hook/renewal erst durchlaufen lassen oder Pfad anpassen.');
|
||
return self::FAILURE;
|
||
}
|
||
|
||
// Hash über SPKI (selector=1) + SHA-256 (matching=1)
|
||
$cmd = "openssl x509 -in ".escapeshellarg($certPath)." -noout -pubkey"
|
||
. " | openssl pkey -pubin -outform DER"
|
||
. " | openssl dgst -sha256";
|
||
$proc = Process::fromShellCommandline($cmd);
|
||
$proc->run();
|
||
|
||
if (!$proc->isSuccessful()) {
|
||
$this->error('Fehler bei der Hash-Erzeugung (openssl).');
|
||
$this->line($proc->getErrorOutput());
|
||
return self::FAILURE;
|
||
}
|
||
|
||
$hash = preg_replace('/^SHA256\(stdin\)=\s*/', '', trim($proc->getOutput()));
|
||
|
||
$record = TlsaRecord::updateOrCreate(
|
||
[
|
||
'domain_id' => $domain->id,
|
||
'host' => $host,
|
||
'service' => $service,
|
||
],
|
||
[
|
||
'usage' => $usage,
|
||
'selector' => $selector,
|
||
'matching' => $matching,
|
||
'hash' => $hash,
|
||
'cert_path' => $certPath,
|
||
]
|
||
);
|
||
|
||
$this->info('✅ TLSA gespeichert');
|
||
$this->line(sprintf(
|
||
'%s.%s IN TLSA %d %d %d %s',
|
||
$record->service,
|
||
$record->host,
|
||
$record->usage,
|
||
$record->selector,
|
||
$record->matching,
|
||
$record->hash
|
||
));
|
||
|
||
return self::SUCCESS;
|
||
}
|
||
}
|