Odchozi IP adresa portalu

Kdyz cizi sluzba odmitne pristup, prvni otazka je, z jake adresy se vlastne
vola. Z containeru to videt neni, vidi to az protistrana, takze se zepta echo
sluzby podle EGRESS_IP_URL a vysledek se drzi v pameti po EGRESS_IP_TTL_MS.
Prazdna EGRESS_IP_URL funkci vypne, prepsat ji jde na vlastni echo pod svou
domenou.

Adresa je natvrdo na strance Konektory a u kazdeho odmitnuteho overeni v logu.
Pripojuje se jen u 401 a 403 - jinde nema co rict a nestoji za volani ven.

Neni to tajemstvi: kazda volana sluzba tuhle adresu stejne vidi.

Endpoint egress-ip je registrovany pred GET /:id, jinak by ho router vzal
jako id konektoru.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
JiriUhlir
2026-08-25 07:22:26 +02:00
co-authored by Claude Opus 5
parent ab88979627
commit 4545de8076
9 changed files with 283 additions and 0 deletions
+58
View File
@@ -2,6 +2,7 @@ import {
AlertTriangle,
CheckCircle2,
Eye,
Globe,
Info,
EyeOff,
Pencil,
@@ -28,6 +29,7 @@ import type {
Connector,
ConnectorCheck,
ConnectorTestResult,
EgressIp,
Service,
ServiceCredentialField,
ServiceOverview,
@@ -54,6 +56,11 @@ export default function Connectors() {
const [services, setServices] = useState<ServiceOverview | null>(null);
const [connectors, setConnectors] = useState<Connector[] | null>(null);
const [storage, setStorage] = useState<StorageStatus | null>(null);
/**
* Adresa, ze ktere portal vola ven. Cizi sluzby maji seznamy povolenych IP
* a bez teto informace se neda rict, jestli je na nich prave ta nase.
*/
const [egress, setEgress] = useState<EgressIp | null>(null);
const [error, setError] = useState<string | null>(null);
const [loading, setLoading] = useState(true);
@@ -83,6 +90,14 @@ export default function Connectors() {
useEffect(load, [load]);
// Zvlast a bez blokovani stranky: je to volani ven a kdyz nedobehne,
// konektory se tim zdrzet nesmi.
useEffect(() => {
apiFetch<EgressIp>('/api/dashboard/connectors/egress-ip')
.then(setEgress)
.catch(() => setEgress(null));
}, []);
const servicesById = useMemo(() => {
// ServiceWithUsage, ne Service: karta ukazuje i vychozi adresu sluzby.
const map = new Map<string, ServiceWithUsage>();
@@ -128,6 +143,8 @@ export default function Connectors() {
*/}
{storage && storage.mode !== 'postgres' && <StorageNotice storage={storage} />}
{egress && <EgressNotice egress={egress} />}
<DataState loading={loading} error={error} onRetry={load}>
{(connectors?.length ?? 0) === 0 ? (
<p className="py-10 text-center text-sm text-white/45">
@@ -203,6 +220,42 @@ function StorageNotice({ storage }: { storage: StorageStatus }) {
);
}
/**
* Ze ktere adresy portal vola ven.
*
* Duvod, proc to je na strance napevno a ne az u chyby: cizi sluzby maji
* seznamy povolenych IP adres a z containeru neni videt, jak ho protistrana
* vidi. Bez teto radky se pri odmitnutem pristupu hada, misto aby se porovnal
* jeden retezec se seznamem.
*/
function EgressNotice({ egress }: { egress: EgressIp }) {
return (
<div className="glass mb-4 flex items-start gap-3 rounded-card p-4">
<Globe className="mt-0.5 size-4 shrink-0 text-white/40" />
<div className="min-w-0">
{egress.ip ? (
<>
<p className="text-sm text-white/70">
Volání odcházejí z IP <span className="font-mono text-white">{egress.ip}</span>
</p>
<p className="mt-0.5 text-xs text-white/40">
Tuhle adresu vidí volaná služba, takže právě ona musí být na jejím seznamu
povolených. Zjištěno {formatDateTime(egress.checkedAt)} přes {egress.source}.
</p>
</>
) : (
<>
<p className="text-sm text-white/70">Odchozí IP adresu se nepodařilo zjistit.</p>
<p className="mt-0.5 text-xs text-white/40">
{egress.error ?? 'Bez důvodu.'} Adresu echo služby určuje EGRESS_IP_URL.
</p>
</>
)}
</div>
</div>
);
}
// ----------------------------------------------------------------- mala karta
function ConnectorCard({
@@ -422,6 +475,11 @@ function ConnectorLogs({
<span className="text-white/70">{formatDateTime(check.at)}</span>
{check.ok ? <Badge tone="ok">prošlo</Badge> : <Badge tone="danger">selhalo</Badge>}
<span>Ověřeno: {check.checked}</span>
{check.egressIp && (
<span>
voláno z IP <span className="font-mono text-white/60">{check.egressIp}</span>
</span>
)}
</div>
{check.ok ? (
+16
View File
@@ -397,6 +397,8 @@ export interface ConnectorCheck {
request: { method: string; path: string; url: string } | null;
/** Vybrane hlavicky odpovedi. Rikaji, kdo odpoved vydal. */
responseHeaders: Record<string, string> | null;
/** Odchozi IP portalu ve chvili volani. Jen u odmitnuteho pristupu. */
egressIp: string | null;
}
export interface ConnectorTestResult {
@@ -413,6 +415,20 @@ export interface ConnectorTestResult {
baseUrl?: string;
/** Vybrane hlavicky odpovedi. Rikaji, kdo odpoved vydal. */
responseHeaders?: Record<string, string>;
/** Odchozi IP portalu. Jen u odmitnuteho pristupu. */
egressIp?: string;
}
/**
* Odchozi IP adresa portalu.
* Adresa, kterou vidi volana sluzba - tedy ta, ktera musi byt na seznamu
* povolenych. Z containeru ji videt neni, zjistuje se echo sluzbou.
*/
export interface EgressIp {
ip: string | null;
source: string;
checkedAt: string;
error?: string;
}
export interface ServiceCatalog {