fix(tls): o painel de seguranca estava vazio em qualquer modo menos um
/admin/security mostrava "Nao foi possivel carregar os dados" porque /api/v1/admin/tls respondia 503. A rota so era ligada quando o certificado vinha do watcher do acme.json do Traefik, o unico provider que tinha metodos de estado proprios. Nesta instalacao o TRAEFIK_ACME_DIR nao esta definido e o STATIC_CERT_PATH tambem nao, portanto o servidor cai no certificado autoassinado efemero -- ou seja, o painel calava-se exactamente no estado em que o operador mais precisa de ser avisado. - ProviderStatus deriva o estado de qualquer port.CertProvider, lendo a folha do certificado que ele realmente serve - o painel passa a ser ligado sempre que ha JWT_SECRET - passa a dizer quem emitiu e se e autoassinado, com um aviso proprio: nas portas de correio um autoassinado e recusado por qualquer cliente que verifique, o que pesa mais do que uma validade ainda longe CheckSignatureFrom nao serve para detectar isto: tambem exige que o emissor seja uma CA, coisa que uma folha descartavel nunca declara, e por isso dava a fallback degradada como correctamente emitida. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
9689f26dda
commit
136f9f339a
9 files changed
+194
-7
No files matched your search
@@ -95,7 +95,8 @@
|
||||
"reconcileConflicts": "Left untouched — another value is already published:",
|
||||
"reconcileErrors": "The provider refused these:",
|
||||
"selectDomain": "Select",
|
||||
"domainSelected": "Selected"
|
||||
"domainSelected": "Selected",
|
||||
"tlsSelfSigned": "This certificate is self-signed (issuer: {issuer}). Clients that verify certificates will refuse the connection on the mail ports. Set TRAEFIK_ACME_DIR or STATIC_CERT_PATH to serve an issued certificate."
|
||||
},
|
||||
"auth": {
|
||||
"adminLoginTitle": "Admin console",
|
||||
|
||||
@@ -95,7 +95,8 @@
|
||||
"reconcileConflicts": "Sin tocar — ya hay otro valor publicado:",
|
||||
"reconcileErrors": "El proveedor rechazó estos:",
|
||||
"selectDomain": "Seleccionar",
|
||||
"domainSelected": "Seleccionado"
|
||||
"domainSelected": "Seleccionado",
|
||||
"tlsSelfSigned": "Este certificado es autofirmado (emisor: {issuer}). Los clientes que verifican el certificado rechazaran la conexion en los puertos de correo. Defina TRAEFIK_ACME_DIR o STATIC_CERT_PATH para servir un certificado emitido."
|
||||
},
|
||||
"auth": {
|
||||
"adminLoginTitle": "Consola de administración",
|
||||
|
||||
@@ -95,7 +95,8 @@
|
||||
"reconcileConflicts": "Não alterados — já existe outro valor publicado:",
|
||||
"reconcileErrors": "O provedor recusou estes:",
|
||||
"selectDomain": "Selecionar",
|
||||
"domainSelected": "Selecionado"
|
||||
"domainSelected": "Selecionado",
|
||||
"tlsSelfSigned": "Este certificado e autoassinado (emissor: {issuer}). Clientes que verificam o certificado vao recusar a ligacao nas portas de correio. Defina TRAEFIK_ACME_DIR ou STATIC_CERT_PATH para servir um certificado emitido."
|
||||
},
|
||||
"auth": {
|
||||
"adminLoginTitle": "Console de administração",
|
||||
|
||||
@@ -156,8 +156,8 @@ export default function AdminQueuePage() {
|
||||
{jobs.map((j) => (
|
||||
<tr key={j.id}>
|
||||
<td className="pl-0 font-mono text-[12.5px] text-slate-100">{j.id.slice(0, 8)}</td>
|
||||
<td className="max-w-[180px] break-words text-[12.5px] text-slate-300">{j.envelope_from}</td>
|
||||
<td className="max-w-[180px] break-words text-[12.5px] text-slate-300">{j.envelope_to}</td>
|
||||
<td className="max-w-[220px] break-words text-[12.5px] text-slate-300">{j.envelope_from}</td>
|
||||
<td className="max-w-[220px] break-words text-[12.5px] text-slate-300">{j.envelope_to}</td>
|
||||
<td className="break-words text-[12.5px] text-slate-300">{j.destination_domain}</td>
|
||||
<td className="whitespace-nowrap text-[12.5px] tabular-nums text-slate-300">{j.attempt}</td>
|
||||
{/* The diagnostic wraps instead of being truncated: a queue
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
CheckCircle2,
|
||||
Search,
|
||||
Shield,
|
||||
AlertTriangle,
|
||||
} from "lucide-react";
|
||||
import { useTranslations } from "../../../../i18n/provider";
|
||||
import { Badge } from "../../../../components/ui/Badge";
|
||||
@@ -275,6 +276,18 @@ export default function AdminSecurityPage() {
|
||||
{t("admin.tlsTitle")}
|
||||
</h2>
|
||||
|
||||
{/* A self-signed certificate on the mail ports is refused by every
|
||||
client that verifies, so it is stated outright rather than left
|
||||
to be inferred from a status word. */}
|
||||
{tlsStatus?.self_signed ? (
|
||||
<div className="flex items-start gap-2.5 rounded-xl bg-amber-500/10 px-3.5 py-3 shadow-edge">
|
||||
<AlertTriangle className="mt-px h-4 w-4 flex-none text-amber-400" />
|
||||
<span className="text-[12.5px] leading-relaxed text-amber-200">
|
||||
{t("admin.tlsSelfSigned", { issuer: String(tlsStatus.issuer ?? "-") })}
|
||||
</span>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{tlsStatus ? (
|
||||
<div className="grid grid-cols-1 gap-3 md:grid-cols-3">
|
||||
<div className="flex flex-col gap-1 rounded-xl bg-dark-card p-3.5 shadow-edge">
|
||||
|
||||
Reference in new issue
Block a user