Compare commits
4 Commits
c9caf35596
...
e04bddd574
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e04bddd574 | ||
|
|
e37e472960 | ||
|
|
8c1963cac5 | ||
|
|
f33c1bfe4f |
@@ -60,7 +60,7 @@ import (
|
|||||||
usersvc "git.netcell-it.de/projekte/edgeguard-native/internal/services/users"
|
usersvc "git.netcell-it.de/projekte/edgeguard-native/internal/services/users"
|
||||||
)
|
)
|
||||||
|
|
||||||
var version = "1.1.77"
|
var version = "1.1.80"
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
addr := os.Getenv("EDGEGUARD_API_ADDR")
|
addr := os.Getenv("EDGEGUARD_API_ADDR")
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import (
|
|||||||
"git.netcell-it.de/projekte/edgeguard-native/internal/services/setup"
|
"git.netcell-it.de/projekte/edgeguard-native/internal/services/setup"
|
||||||
)
|
)
|
||||||
|
|
||||||
var version = "1.0.78"
|
var version = "1.1.80"
|
||||||
|
|
||||||
const usage = `edgeguard-ctl — EdgeGuard CLI
|
const usage = `edgeguard-ctl — EdgeGuard CLI
|
||||||
|
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ import (
|
|||||||
"git.netcell-it.de/projekte/edgeguard-native/internal/services/tlscerts"
|
"git.netcell-it.de/projekte/edgeguard-native/internal/services/tlscerts"
|
||||||
)
|
)
|
||||||
|
|
||||||
var version = "1.0.78"
|
var version = "1.1.80"
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// renewTickInterval — how often we re-evaluate expiring certs.
|
// renewTickInterval — how often we re-evaluate expiring certs.
|
||||||
|
|||||||
@@ -881,6 +881,7 @@
|
|||||||
"aclType": "Typ",
|
"aclType": "Typ",
|
||||||
"aclTypeExtra": "Was Squid prüft (Quelle, Domain, Port, …).",
|
"aclTypeExtra": "Was Squid prüft (Quelle, Domain, Port, …).",
|
||||||
"value": "Wert",
|
"value": "Wert",
|
||||||
|
"valuePlaceholder": ".example.com oder 10.10.20.0/24 oder 443",
|
||||||
"valueExtra": "Format hängt vom Typ ab — IPs/CIDRs für src/dst, Domain mit führendem . für dstdomain (.example.com matcht auch sub.example.com), Regex für *_regex-Typen.",
|
"valueExtra": "Format hängt vom Typ ab — IPs/CIDRs für src/dst, Domain mit führendem . für dstdomain (.example.com matcht auch sub.example.com), Regex für *_regex-Typen.",
|
||||||
"action": "Aktion",
|
"action": "Aktion",
|
||||||
"priority": "Priority",
|
"priority": "Priority",
|
||||||
@@ -890,7 +891,26 @@
|
|||||||
"edit": "ACL bearbeiten",
|
"edit": "ACL bearbeiten",
|
||||||
"deleteConfirm": "ACL {{name}} wirklich löschen?",
|
"deleteConfirm": "ACL {{name}} wirklich löschen?",
|
||||||
"emptyTitle": "Noch keine Forward-Proxy-ACLs.",
|
"emptyTitle": "Noch keine Forward-Proxy-ACLs.",
|
||||||
"emptyDesc": "Default ohne ACLs: nur localnet (10/8, 172.16/12, 192.168/16) darf raus. Lege eine ACL an, um spezifische Domains/IPs/Ports gezielt zu erlauben oder zu blocken."
|
"emptyDesc": "Default ohne ACLs: nur localnet (10/8, 172.16/12, 192.168/16) darf raus. Lege eine ACL an, um spezifische Domains/IPs/Ports gezielt zu erlauben oder zu blocken.",
|
||||||
|
"actions": {
|
||||||
|
"allow": "allow — Zugriff erlauben",
|
||||||
|
"deny": "deny — Zugriff blockieren"
|
||||||
|
},
|
||||||
|
"aclTypes": {
|
||||||
|
"src": "src — Quell-IP/CIDR",
|
||||||
|
"dst": "dst — Ziel-IP/CIDR",
|
||||||
|
"dstdomain": "dstdomain — Ziel-Domain (exact)",
|
||||||
|
"srcdomain": "srcdomain — Quell-Domain (rDNS)",
|
||||||
|
"port": "port — Ziel-Port",
|
||||||
|
"proto": "proto — http/https/ftp/...",
|
||||||
|
"method": "method — GET/POST/CONNECT/...",
|
||||||
|
"time": "time — Wochentag/Zeit",
|
||||||
|
"url_regex": "url_regex — kompletter URL-Match",
|
||||||
|
"urlpath_regex": "urlpath_regex — Path-Teil",
|
||||||
|
"dstdom_regex": "dstdom_regex — Domain-Regex",
|
||||||
|
"srcdom_regex": "srcdom_regex — Quell-Domain-Regex",
|
||||||
|
"browser": "browser — User-Agent-Regex"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"common": {
|
"common": {
|
||||||
"yes": "Ja",
|
"yes": "Ja",
|
||||||
@@ -950,7 +970,11 @@
|
|||||||
"confirmClearHint": "System fällt auf Trial-Modus zurück, sobald der Schlüssel gelöscht wird.",
|
"confirmClearHint": "System fällt auf Trial-Modus zurück, sobald der Schlüssel gelöscht wird.",
|
||||||
"lastVerifyFailed": "Letzte Server-Verifizierung fehlgeschlagen",
|
"lastVerifyFailed": "Letzte Server-Verifizierung fehlgeschlagen",
|
||||||
"trialExpiring": "Trial läuft in {{days}} Tag(en) ab",
|
"trialExpiring": "Trial läuft in {{days}} Tag(en) ab",
|
||||||
"trialExpiringHint": "Lizenz aktivieren, bevor die Trial-Periode endet."
|
"trialExpiringHint": "Lizenz aktivieren, bevor die Trial-Periode endet.",
|
||||||
|
"tagActive": "Aktiv",
|
||||||
|
"tagTrial": "Trial",
|
||||||
|
"tagExpired": "Abgelaufen",
|
||||||
|
"tagInvalid": "Ungültig"
|
||||||
},
|
},
|
||||||
"licenseBanner": {
|
"licenseBanner": {
|
||||||
"expired": "Lizenz abgelaufen oder ungültig.",
|
"expired": "Lizenz abgelaufen oder ungültig.",
|
||||||
@@ -1055,7 +1079,8 @@
|
|||||||
"curl": { "intro": "HTTPS-Probe mit -IsSv: TLS-Handshake-Details + Header. Folgt KEINE Redirects." },
|
"curl": { "intro": "HTTPS-Probe mit -IsSv: TLS-Handshake-Details + Header. Folgt KEINE Redirects." },
|
||||||
"tcp": { "intro": "Pure TCP-Connect ohne I/O. Ideal um zu prüfen ob ein Backend-Port erreichbar ist." },
|
"tcp": { "intro": "Pure TCP-Connect ohne I/O. Ideal um zu prüfen ob ein Backend-Port erreichbar ist." },
|
||||||
"securityTitle": "Sicherheits-Hinweis",
|
"securityTitle": "Sicherheits-Hinweis",
|
||||||
"securityDesc": "Endpoints sind hinter Admin-Auth. Targets werden gegen eine strikte Allow-Liste validiert (keine Shell-Metazeichen). curl ist auf http(s) beschränkt — kein file:// / smb:// / data://."
|
"securityDesc": "Endpoints sind hinter Admin-Auth. Targets werden gegen eine strikte Allow-Liste validiert (keine Shell-Metazeichen). curl ist auf http(s) beschränkt — kein file:// / smb:// / data://.",
|
||||||
|
"noOutput": "(kein Output)"
|
||||||
},
|
},
|
||||||
"backups": {
|
"backups": {
|
||||||
"title": "Backups",
|
"title": "Backups",
|
||||||
|
|||||||
@@ -881,6 +881,7 @@
|
|||||||
"aclType": "Type",
|
"aclType": "Type",
|
||||||
"aclTypeExtra": "What Squid matches (source, domain, port, …).",
|
"aclTypeExtra": "What Squid matches (source, domain, port, …).",
|
||||||
"value": "Value",
|
"value": "Value",
|
||||||
|
"valuePlaceholder": ".example.com or 10.10.20.0/24 or 443",
|
||||||
"valueExtra": "Format depends on type — IPs/CIDRs for src/dst, domain with leading dot for dstdomain (.example.com also matches sub.example.com), regex for *_regex types.",
|
"valueExtra": "Format depends on type — IPs/CIDRs for src/dst, domain with leading dot for dstdomain (.example.com also matches sub.example.com), regex for *_regex types.",
|
||||||
"action": "Action",
|
"action": "Action",
|
||||||
"priority": "Priority",
|
"priority": "Priority",
|
||||||
@@ -890,7 +891,26 @@
|
|||||||
"edit": "Edit ACL",
|
"edit": "Edit ACL",
|
||||||
"deleteConfirm": "Really delete ACL {{name}}?",
|
"deleteConfirm": "Really delete ACL {{name}}?",
|
||||||
"emptyTitle": "No forward-proxy ACLs yet.",
|
"emptyTitle": "No forward-proxy ACLs yet.",
|
||||||
"emptyDesc": "Default with no ACLs: only localnet (10/8, 172.16/12, 192.168/16) is allowed out. Add an ACL to selectively allow or block specific domains/IPs/ports."
|
"emptyDesc": "Default with no ACLs: only localnet (10/8, 172.16/12, 192.168/16) is allowed out. Add an ACL to selectively allow or block specific domains/IPs/ports.",
|
||||||
|
"actions": {
|
||||||
|
"allow": "allow — permit access",
|
||||||
|
"deny": "deny — block access"
|
||||||
|
},
|
||||||
|
"aclTypes": {
|
||||||
|
"src": "src — source IP/CIDR",
|
||||||
|
"dst": "dst — destination IP/CIDR",
|
||||||
|
"dstdomain": "dstdomain — destination domain (exact)",
|
||||||
|
"srcdomain": "srcdomain — source domain (rDNS)",
|
||||||
|
"port": "port — destination port",
|
||||||
|
"proto": "proto — http/https/ftp/...",
|
||||||
|
"method": "method — GET/POST/CONNECT/...",
|
||||||
|
"time": "time — weekday/time range",
|
||||||
|
"url_regex": "url_regex — full URL regex",
|
||||||
|
"urlpath_regex": "urlpath_regex — URL path regex",
|
||||||
|
"dstdom_regex": "dstdom_regex — destination domain regex",
|
||||||
|
"srcdom_regex": "srcdom_regex — source domain regex",
|
||||||
|
"browser": "browser — User-Agent regex"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"common": {
|
"common": {
|
||||||
"yes": "Yes",
|
"yes": "Yes",
|
||||||
@@ -950,7 +970,11 @@
|
|||||||
"confirmClearHint": "The system will fall back to trial-mode once the key is deleted.",
|
"confirmClearHint": "The system will fall back to trial-mode once the key is deleted.",
|
||||||
"lastVerifyFailed": "Last server verify failed",
|
"lastVerifyFailed": "Last server verify failed",
|
||||||
"trialExpiring": "Trial expires in {{days}} day(s)",
|
"trialExpiring": "Trial expires in {{days}} day(s)",
|
||||||
"trialExpiringHint": "Activate a license before the trial period ends."
|
"trialExpiringHint": "Activate a license before the trial period ends.",
|
||||||
|
"tagActive": "Active",
|
||||||
|
"tagTrial": "Trial",
|
||||||
|
"tagExpired": "Expired",
|
||||||
|
"tagInvalid": "Invalid"
|
||||||
},
|
},
|
||||||
"licenseBanner": {
|
"licenseBanner": {
|
||||||
"expired": "License expired or invalid.",
|
"expired": "License expired or invalid.",
|
||||||
@@ -1055,7 +1079,8 @@
|
|||||||
"curl": { "intro": "HTTPS probe with -IsSv: TLS handshake details + headers. Does NOT follow redirects." },
|
"curl": { "intro": "HTTPS probe with -IsSv: TLS handshake details + headers. Does NOT follow redirects." },
|
||||||
"tcp": { "intro": "Pure TCP connect (no I/O). Ideal to verify a backend port is reachable." },
|
"tcp": { "intro": "Pure TCP connect (no I/O). Ideal to verify a backend port is reachable." },
|
||||||
"securityTitle": "Security note",
|
"securityTitle": "Security note",
|
||||||
"securityDesc": "Endpoints are behind admin auth. Targets are validated against a strict allow-list (no shell metachars). curl is restricted to http(s) — no file:// / smb:// / data://."
|
"securityDesc": "Endpoints are behind admin auth. Targets are validated against a strict allow-list (no shell metachars). curl is restricted to http(s) — no file:// / smb:// / data://.",
|
||||||
|
"noOutput": "(no output)"
|
||||||
},
|
},
|
||||||
"backups": {
|
"backups": {
|
||||||
"title": "Backups",
|
"title": "Backups",
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ function ToolCard({ icon, title, intro, children, result, loading, run }: {
|
|||||||
borderRadius: 6, overflow: 'auto', maxHeight: 320,
|
borderRadius: 6, overflow: 'auto', maxHeight: 320,
|
||||||
whiteSpace: 'pre-wrap', wordBreak: 'break-all',
|
whiteSpace: 'pre-wrap', wordBreak: 'break-all',
|
||||||
}}>
|
}}>
|
||||||
{result.output || result.error || '(kein Output)'}
|
{result.output || result.error || t('diag.noOutput')}
|
||||||
</pre>
|
</pre>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -40,24 +40,10 @@ interface FormValues {
|
|||||||
comment?: string
|
comment?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
// ACL-Typen aus Squid's Vokabular — dieselbe Whitelist wie der
|
const ACL_TYPE_KEYS = [
|
||||||
// Backend-Validator. Mehr Typen kann Squid (browser, time-of-day,
|
'src', 'dst', 'dstdomain', 'srcdomain', 'port', 'proto', 'method',
|
||||||
// arp, ...) — die spielen wir später dazu.
|
'time', 'url_regex', 'urlpath_regex', 'dstdom_regex', 'srcdom_regex', 'browser',
|
||||||
const ACL_TYPES = [
|
] as const
|
||||||
{ value: 'src', label: 'src — Quell-IP/CIDR' },
|
|
||||||
{ value: 'dst', label: 'dst — Ziel-IP/CIDR' },
|
|
||||||
{ value: 'dstdomain', label: 'dstdomain — Ziel-Domain (exact)' },
|
|
||||||
{ value: 'srcdomain', label: 'srcdomain — Quell-Domain (rDNS)' },
|
|
||||||
{ value: 'port', label: 'port — Ziel-Port' },
|
|
||||||
{ value: 'proto', label: 'proto — http/https/ftp/...' },
|
|
||||||
{ value: 'method', label: 'method — GET/POST/CONNECT/...' },
|
|
||||||
{ value: 'time', label: 'time — Wochentag/Zeit' },
|
|
||||||
{ value: 'url_regex', label: 'url_regex — kompletter URL-Match' },
|
|
||||||
{ value: 'urlpath_regex', label: 'urlpath_regex — Path-Teil' },
|
|
||||||
{ value: 'dstdom_regex', label: 'dstdom_regex — Domain-Regex' },
|
|
||||||
{ value: 'srcdom_regex', label: 'srcdom_regex — Quell-Domain-Regex' },
|
|
||||||
{ value: 'browser', label: 'browser — User-Agent-Regex' },
|
|
||||||
]
|
|
||||||
|
|
||||||
async function listACLs(): Promise<ACL[]> {
|
async function listACLs(): Promise<ACL[]> {
|
||||||
const r = await apiClient.get('/forward-proxy/acls')
|
const r = await apiClient.get('/forward-proxy/acls')
|
||||||
@@ -222,17 +208,21 @@ export default function ForwardProxyPage() {
|
|||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item label={t('fwd.action')} name="action" rules={[{ required: true }]}>
|
<Form.Item label={t('fwd.action')} name="action" rules={[{ required: true }]}>
|
||||||
<Select options={[
|
<Select options={[
|
||||||
{ value: 'allow', label: 'allow — Zugriff erlauben' },
|
{ value: 'allow', label: t('fwd.actions.allow') },
|
||||||
{ value: 'deny', label: 'deny — Zugriff blockieren' },
|
{ value: 'deny', label: t('fwd.actions.deny') },
|
||||||
]} />
|
]} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item label={t('fwd.aclType')} name="acl_type" rules={[{ required: true }]}
|
<Form.Item label={t('fwd.aclType')} name="acl_type" rules={[{ required: true }]}
|
||||||
extra={t('fwd.aclTypeExtra')}>
|
extra={t('fwd.aclTypeExtra')}>
|
||||||
<Select options={ACL_TYPES} showSearch optionFilterProp="value" />
|
<Select
|
||||||
|
options={ACL_TYPE_KEYS.map(k => ({ value: k, label: t(`fwd.aclTypes.${k}`) }))}
|
||||||
|
showSearch
|
||||||
|
optionFilterProp="value"
|
||||||
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item label={t('fwd.value')} name="value" rules={[{ required: true }]}
|
<Form.Item label={t('fwd.value')} name="value" rules={[{ required: true }]}
|
||||||
extra={t('fwd.valueExtra')}>
|
extra={t('fwd.valueExtra')}>
|
||||||
<Input.TextArea rows={2} placeholder=".example.com oder 10.10.20.0/24 oder 443" />
|
<Input.TextArea rows={2} placeholder={t('fwd.valuePlaceholder')} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item label={t('fwd.priority')} name="priority" rules={[{ required: true }]}
|
<Form.Item label={t('fwd.priority')} name="priority" rules={[{ required: true }]}
|
||||||
extra={t('fwd.priorityExtra')}>
|
extra={t('fwd.priorityExtra')}>
|
||||||
|
|||||||
@@ -36,11 +36,11 @@ function daysUntil(iso?: string): number | null {
|
|||||||
return Math.ceil(ms / 86_400_000)
|
return Math.ceil(ms / 86_400_000)
|
||||||
}
|
}
|
||||||
|
|
||||||
function statusTag(s: LicenseStatus) {
|
function statusTag(s: LicenseStatus, t: (k: string) => string) {
|
||||||
if (s.status === 'active' && s.type === 'trial') return <Tag color="orange">Trial</Tag>
|
if (s.status === 'active' && s.type === 'trial') return <Tag color="orange">{t('license.tagTrial')}</Tag>
|
||||||
if (s.status === 'active') return <Tag color="green">Aktiv</Tag>
|
if (s.status === 'active') return <Tag color="green">{t('license.tagActive')}</Tag>
|
||||||
if (s.status === 'expired') return <Tag color="red">Abgelaufen</Tag>
|
if (s.status === 'expired') return <Tag color="red">{t('license.tagExpired')}</Tag>
|
||||||
if (s.status === 'invalid') return <Tag color="red">Ungültig</Tag>
|
if (s.status === 'invalid') return <Tag color="red">{t('license.tagInvalid')}</Tag>
|
||||||
return <Tag>{s.status}</Tag>
|
return <Tag>{s.status}</Tag>
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -143,7 +143,7 @@ export default function LicensePage() {
|
|||||||
<Card>
|
<Card>
|
||||||
<Descriptions column={1} bordered size="small">
|
<Descriptions column={1} bordered size="small">
|
||||||
<Descriptions.Item label={t('license.status')}>
|
<Descriptions.Item label={t('license.status')}>
|
||||||
{status ? statusTag(status) : '-'}
|
{status ? statusTag(status, t) : '-'}
|
||||||
</Descriptions.Item>
|
</Descriptions.Item>
|
||||||
<Descriptions.Item label={t('license.product')}>
|
<Descriptions.Item label={t('license.product')}>
|
||||||
{status?.payload?.product || (isTrial ? 'Trial' : '-')}
|
{status?.payload?.product || (isTrial ? 'Trial' : '-')}
|
||||||
|
|||||||
@@ -360,10 +360,17 @@ function PeerDrawer({ iface, onClose }: PeerDrawerProps) {
|
|||||||
if (editing) return (await apiClient.put(`/wireguard/peers/${editing.id}`, v)).data
|
if (editing) return (await apiClient.put(`/wireguard/peers/${editing.id}`, v)).data
|
||||||
return (await apiClient.post(`/wireguard/interfaces/${ifaceID}/peers`, v)).data
|
return (await apiClient.post(`/wireguard/interfaces/${ifaceID}/peers`, v)).data
|
||||||
},
|
},
|
||||||
onSuccess: () => {
|
onSuccess: (envelope, _, ctx) => {
|
||||||
|
void ctx
|
||||||
message.success(t('common.save'))
|
message.success(t('common.save'))
|
||||||
|
const wasCreating = !editing
|
||||||
setEditing(null); setCreating(false); form.resetFields()
|
setEditing(null); setCreating(false); form.resetFields()
|
||||||
void qc.invalidateQueries({ queryKey: ['wg', 'peers', ifaceID] })
|
void qc.invalidateQueries({ queryKey: ['wg', 'peers', ifaceID] })
|
||||||
|
// Auto-open QR when we just created a peer with a generated keypair.
|
||||||
|
if (wasCreating && isEnvelope(envelope)) {
|
||||||
|
const peer = envelope.data as WGPeer
|
||||||
|
if (peer?.has_private_key) setQrPeer(peer)
|
||||||
|
}
|
||||||
},
|
},
|
||||||
onError: (e: Error) => message.error(e.message),
|
onError: (e: Error) => message.error(e.message),
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user