Compare commits
2 Commits
d013124c12
...
d3f2533565
| Author | SHA1 | Date |
|---|---|---|
|
|
d3f2533565 | |
|
|
272cfe45e8 |
|
|
@ -22,18 +22,17 @@ class CertificateSweep
|
||||||
/**
|
/**
|
||||||
* Die Namen aus der Umgebung in die Liste übernehmen.
|
* Die Namen aus der Umgebung in die Liste übernehmen.
|
||||||
*
|
*
|
||||||
* Anlegen und aktualisieren, nie löschen: ein Name, der aus der Umgebung
|
|
||||||
* verschwindet, bleibt als Zeile stehen. Sein Zertifikat läuft ja weiter,
|
|
||||||
* und ein Eintrag, der still verschwindet, ist genau das Gegenteil einer
|
|
||||||
* Übersicht.
|
|
||||||
*
|
|
||||||
* @return int wie viele Zeilen neu hinzugekommen sind
|
* @return int wie viele Zeilen neu hinzugekommen sind
|
||||||
*/
|
*/
|
||||||
public function sync(): int
|
public function sync(): int
|
||||||
{
|
{
|
||||||
|
$bekannt = KnownHostnames::all();
|
||||||
|
|
||||||
|
$this->pruneVanished($bekannt);
|
||||||
|
|
||||||
$added = 0;
|
$added = 0;
|
||||||
|
|
||||||
foreach (KnownHostnames::all() as $hostname => $purpose) {
|
foreach ($bekannt as $hostname => $purpose) {
|
||||||
$existing = ProxyHost::query()->where('hostname', $hostname)->first();
|
$existing = ProxyHost::query()->where('hostname', $hostname)->first();
|
||||||
|
|
||||||
if ($existing === null) {
|
if ($existing === null) {
|
||||||
|
|
@ -59,6 +58,41 @@ class CertificateSweep
|
||||||
return $added;
|
return $added;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Zeilen, die aus der Umgebung kamen und dort nicht mehr stehen.
|
||||||
|
*
|
||||||
|
* Zwei Fälle, und sie brauchen zwei Antworten:
|
||||||
|
*
|
||||||
|
* - Die Zeile hatte NIE ein Zertifikat. Dann war sie ein Fehlgriff — so
|
||||||
|
* kam `127.0.0.1` aus `ADMIN_HOSTS` in die Liste, wo eine nackte IP als
|
||||||
|
* Wiederzugang stehen darf, für die es aber nie ein Zertifikat geben
|
||||||
|
* wird. Sie geht weg; ein dauerhaft roter Eintrag, den niemand beheben
|
||||||
|
* kann, macht die Übersicht unlesbar.
|
||||||
|
* - Die Zeile HAT ein Zertifikat. Dann wird sie zu einem Eintrag von Hand,
|
||||||
|
* statt zu verschwinden. Das Zertifikat läuft ja weiter, und etwas mit
|
||||||
|
* einer laufenden Frist still aus einer Übersicht zu nehmen ist das
|
||||||
|
* Gegenteil einer Übersicht — der Betreiber entfernt sie selbst.
|
||||||
|
*
|
||||||
|
* @param array<string, string> $bekannt
|
||||||
|
*/
|
||||||
|
private function pruneVanished(array $bekannt): void
|
||||||
|
{
|
||||||
|
$verschwunden = ProxyHost::query()
|
||||||
|
->where('source', 'config')
|
||||||
|
->whereNotIn('hostname', array_keys($bekannt))
|
||||||
|
->get();
|
||||||
|
|
||||||
|
foreach ($verschwunden as $host) {
|
||||||
|
if ($host->hasCertificate()) {
|
||||||
|
$host->forceFill(['source' => 'manual'])->save();
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$host->delete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/** @return array{checked: int, failing: int} */
|
/** @return array{checked: int, failing: int} */
|
||||||
public function check(): array
|
public function check(): array
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -59,6 +59,27 @@ final class KnownHostnames
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// KEINE IP-Adressen.
|
||||||
|
//
|
||||||
|
// `ADMIN_HOSTS` darf ausdrücklich eine nackte IP führen — sie ist der
|
||||||
|
// Wiederzugang zur Konsole, wenn ein Name einmal nicht auflöst. Aber für
|
||||||
|
// eine IP stellt Let's Encrypt kein Zertifikat aus, also steht sie in
|
||||||
|
// einer Zertifikatsübersicht nur als dauerhaft roter Eintrag, den
|
||||||
|
// niemand beheben kann.
|
||||||
|
//
|
||||||
|
// Der erste Filter verlangte bloß einen Punkt — `127.0.0.1` hat drei.
|
||||||
|
// Deshalb hier beides: die Adressprüfung UND die Bedingung, dass die
|
||||||
|
// letzte Marke keine Zahl ist. Eine Endung ist nie numerisch, und das
|
||||||
|
// fängt auch die Formen ab, die `FILTER_VALIDATE_IP` durchlässt.
|
||||||
|
if (filter_var($host, FILTER_VALIDATE_IP) !== false) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$letzteMarke = substr($host, strrpos($host, '.') + 1);
|
||||||
|
if ($letzteMarke === '' || ctype_digit($letzteMarke)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Konsole gewinnt gegen öffentlich, wenn ein Name in beiden Listen
|
// Konsole gewinnt gegen öffentlich, wenn ein Name in beiden Listen
|
||||||
// steht: die engere Aussage ist die richtige.
|
// steht: die engere Aussage ist die richtige.
|
||||||
if (($found[$host] ?? null) !== 'console') {
|
if (($found[$host] ?? null) !== 'console') {
|
||||||
|
|
|
||||||
|
|
@ -198,3 +198,51 @@ it('counts what is valid, what expires soon and what is broken', function () {
|
||||||
->and($summary['soon'])->toBe(1)
|
->and($summary['soon'])->toBe(1)
|
||||||
->and($summary['failing'])->toBe(1);
|
->and($summary['failing'])->toBe(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Der Fund vom Live-Server: `127.0.0.1` stand in der Übersicht, als Konsole,
|
||||||
|
* mit „Kein gültiges Zertifikat: Connection refused".
|
||||||
|
*
|
||||||
|
* Er kam aus `ADMIN_HOSTS`, wo eine nackte IP als Wiederzugang ausdrücklich
|
||||||
|
* stehen darf. Der erste Filter verlangte nur einen Punkt — davon hat
|
||||||
|
* `127.0.0.1` drei. Für eine IP stellt Let's Encrypt kein Zertifikat aus, also
|
||||||
|
* war das ein dauerhaft roter Eintrag, den niemand beheben kann.
|
||||||
|
*/
|
||||||
|
it('keeps IP addresses out of a certificate overview', function () {
|
||||||
|
config()->set('admin_access.hosts', ['admin.clupilot.com', '127.0.0.1', '2001:db8::1', '198.51.100.7']);
|
||||||
|
|
||||||
|
$gefunden = array_keys(App\Support\KnownHostnames::all());
|
||||||
|
|
||||||
|
expect($gefunden)->toContain('admin.clupilot.com')
|
||||||
|
->and($gefunden)->not->toContain('127.0.0.1')
|
||||||
|
->and($gefunden)->not->toContain('198.51.100.7')
|
||||||
|
->and($gefunden)->not->toContain('2001:db8::1');
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Und die Zeile, die schon in der Datenbank steht, geht beim nächsten Abgleich
|
||||||
|
* weg — sonst bliebe mein Fehler auf jeder Installation stehen, die einmal
|
||||||
|
* aktualisiert hat.
|
||||||
|
*/
|
||||||
|
it('clears out a config row that should never have been there', function () {
|
||||||
|
$ip = ProxyHost::create(['hostname' => '127.0.0.1', 'purpose' => 'console', 'source' => 'config']);
|
||||||
|
|
||||||
|
proxyPage();
|
||||||
|
|
||||||
|
expect(ProxyHost::query()->whereKey($ip->id)->exists())->toBeFalse();
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Aber nichts mit laufendem Zertifikat verschwindet still. Es wird zu einem
|
||||||
|
* Eintrag von Hand, den der Betreiber selbst entfernt — eine Frist, die weiter
|
||||||
|
* läuft, gehört nicht wortlos aus einer Übersicht genommen.
|
||||||
|
*/
|
||||||
|
it('turns a vanished name with a live certificate into a manual entry', function () {
|
||||||
|
$alt = ProxyHost::create(['hostname' => 'frueher.clupilot.com', 'purpose' => 'public', 'source' => 'config']);
|
||||||
|
$alt->forceFill(['certificate_expires_at' => now()->addDays(40), 'certificate_checked_at' => now()])->save();
|
||||||
|
|
||||||
|
proxyPage();
|
||||||
|
|
||||||
|
expect($alt->fresh()->isFromConfig())->toBeFalse()
|
||||||
|
->and(ProxyHost::query()->whereKey($alt->id)->exists())->toBeTrue();
|
||||||
|
});
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue