Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e15768b25b |
@@ -34,6 +34,16 @@ import {
|
|||||||
} from '@/queries/firewall'
|
} from '@/queries/firewall'
|
||||||
import type { BgpCommunity, FirewallClient } from '@/types/api'
|
import type { BgpCommunity, FirewallClient } from '@/types/api'
|
||||||
|
|
||||||
|
function httpsOrigin(origin: string): string {
|
||||||
|
try {
|
||||||
|
const u = new URL(origin)
|
||||||
|
u.protocol = 'https:'
|
||||||
|
return u.origin
|
||||||
|
} catch {
|
||||||
|
return origin.replace(/^http:/i, 'https:')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export const Route = createFileRoute('/_auth/firewall')({
|
export const Route = createFileRoute('/_auth/firewall')({
|
||||||
component: FirewallPage,
|
component: FirewallPage,
|
||||||
})
|
})
|
||||||
@@ -51,13 +61,13 @@ function FirewallPage() {
|
|||||||
|
|
||||||
const [clientName, setClientName] = useState('web-01')
|
const [clientName, setClientName] = useState('web-01')
|
||||||
const [cpUrl, setCpUrl] = useState(() =>
|
const [cpUrl, setCpUrl] = useState(() =>
|
||||||
typeof window !== 'undefined' ? window.location.origin : 'https://api.example.com',
|
typeof window !== 'undefined' ? httpsOrigin(window.location.origin) : 'https://api.example.com',
|
||||||
)
|
)
|
||||||
const [seed, setSeed] = useState('')
|
const [seed, setSeed] = useState('')
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (installCtx?.suggested_cp_url) {
|
if (installCtx?.suggested_cp_url) {
|
||||||
setCpUrl(installCtx.suggested_cp_url)
|
setCpUrl(httpsOrigin(installCtx.suggested_cp_url))
|
||||||
}
|
}
|
||||||
if (installCtx?.bundle_seed) {
|
if (installCtx?.bundle_seed) {
|
||||||
setSeed(installCtx.bundle_seed)
|
setSeed(installCtx.bundle_seed)
|
||||||
|
|||||||
@@ -159,10 +159,18 @@ services:
|
|||||||
condition: service_started
|
condition: service_started
|
||||||
labels:
|
labels:
|
||||||
- traefik.enable=true
|
- traefik.enable=true
|
||||||
|
# Публичные firewall-эндпоинты — без WEBUI_IP_WHITELIST (установка с произвольных серверов).
|
||||||
|
- traefik.http.routers.evobgp-firewall-public.rule=Host(`${WEBUI_DOMAIN}`) && (Path(`/v1/firewall/install.sh`) || Path(`/v1/firewall/sync-script`) || PathPrefix(`/v1/firewall/enroll`))
|
||||||
|
- traefik.http.routers.evobgp-firewall-public.entrypoints=websecure
|
||||||
|
- traefik.http.routers.evobgp-firewall-public.tls=true
|
||||||
|
- traefik.http.routers.evobgp-firewall-public.tls.certresolver=letsencrypt
|
||||||
|
- traefik.http.routers.evobgp-firewall-public.priority=100
|
||||||
|
- traefik.http.routers.evobgp-firewall-public.service=evobgp-web
|
||||||
- traefik.http.routers.evobgp-web.rule=Host(`${WEBUI_DOMAIN}`)
|
- traefik.http.routers.evobgp-web.rule=Host(`${WEBUI_DOMAIN}`)
|
||||||
- traefik.http.routers.evobgp-web.entrypoints=websecure
|
- traefik.http.routers.evobgp-web.entrypoints=websecure
|
||||||
- traefik.http.routers.evobgp-web.tls=true
|
- traefik.http.routers.evobgp-web.tls=true
|
||||||
- traefik.http.routers.evobgp-web.tls.certresolver=letsencrypt
|
- traefik.http.routers.evobgp-web.tls.certresolver=letsencrypt
|
||||||
|
- traefik.http.routers.evobgp-web.priority=10
|
||||||
- traefik.http.routers.evobgp-web.middlewares=webui-ipwhitelist@docker
|
- traefik.http.routers.evobgp-web.middlewares=webui-ipwhitelist@docker
|
||||||
- traefik.http.middlewares.webui-ipwhitelist.ipallowlist.sourcerange=${WEBUI_IP_WHITELIST}
|
- traefik.http.middlewares.webui-ipwhitelist.ipallowlist.sourcerange=${WEBUI_IP_WHITELIST}
|
||||||
- traefik.http.services.evobgp-web.loadbalancer.server.port=80
|
- traefik.http.services.evobgp-web.loadbalancer.server.port=80
|
||||||
|
|||||||
@@ -247,10 +247,18 @@ services:
|
|||||||
- evobgp-all
|
- evobgp-all
|
||||||
labels:
|
labels:
|
||||||
- traefik.enable=true
|
- traefik.enable=true
|
||||||
|
# Публичные firewall-эндпоинты — без WEBUI_IP_WHITELIST (установка с произвольных серверов).
|
||||||
|
- traefik.http.routers.evobgp-firewall-public.rule=Host(`${WEBUI_DOMAIN}`) && (Path(`/v1/firewall/install.sh`) || Path(`/v1/firewall/sync-script`) || PathPrefix(`/v1/firewall/enroll`))
|
||||||
|
- traefik.http.routers.evobgp-firewall-public.entrypoints=websecure
|
||||||
|
- traefik.http.routers.evobgp-firewall-public.tls=true
|
||||||
|
- traefik.http.routers.evobgp-firewall-public.tls.certresolver=letsencrypt
|
||||||
|
- traefik.http.routers.evobgp-firewall-public.priority=100
|
||||||
|
- traefik.http.routers.evobgp-firewall-public.service=evobgp-web
|
||||||
- traefik.http.routers.evobgp-web.rule=Host(`${WEBUI_DOMAIN}`)
|
- traefik.http.routers.evobgp-web.rule=Host(`${WEBUI_DOMAIN}`)
|
||||||
- traefik.http.routers.evobgp-web.entrypoints=websecure
|
- traefik.http.routers.evobgp-web.entrypoints=websecure
|
||||||
- traefik.http.routers.evobgp-web.tls=true
|
- traefik.http.routers.evobgp-web.tls=true
|
||||||
- traefik.http.routers.evobgp-web.tls.certresolver=letsencrypt
|
- traefik.http.routers.evobgp-web.tls.certresolver=letsencrypt
|
||||||
|
- traefik.http.routers.evobgp-web.priority=10
|
||||||
- traefik.http.routers.evobgp-web.middlewares=webui-ipwhitelist@docker
|
- traefik.http.routers.evobgp-web.middlewares=webui-ipwhitelist@docker
|
||||||
- traefik.http.middlewares.webui-ipwhitelist.ipallowlist.sourcerange=${WEBUI_IP_WHITELIST}
|
- traefik.http.middlewares.webui-ipwhitelist.ipallowlist.sourcerange=${WEBUI_IP_WHITELIST}
|
||||||
- traefik.http.services.evobgp-web.loadbalancer.server.port=80
|
- traefik.http.services.evobgp-web.loadbalancer.server.port=80
|
||||||
|
|||||||
@@ -162,10 +162,18 @@ services:
|
|||||||
condition: service_started
|
condition: service_started
|
||||||
labels:
|
labels:
|
||||||
- traefik.enable=true
|
- traefik.enable=true
|
||||||
|
# Публичные firewall-эндпоинты — без WEBUI_IP_WHITELIST (установка с произвольных серверов).
|
||||||
|
- traefik.http.routers.evobgp-firewall-public.rule=Host(`${WEBUI_DOMAIN}`) && (Path(`/v1/firewall/install.sh`) || Path(`/v1/firewall/sync-script`) || PathPrefix(`/v1/firewall/enroll`))
|
||||||
|
- traefik.http.routers.evobgp-firewall-public.entrypoints=websecure
|
||||||
|
- traefik.http.routers.evobgp-firewall-public.tls=true
|
||||||
|
- traefik.http.routers.evobgp-firewall-public.tls.certresolver=letsencrypt
|
||||||
|
- traefik.http.routers.evobgp-firewall-public.priority=100
|
||||||
|
- traefik.http.routers.evobgp-firewall-public.service=evobgp-web
|
||||||
- traefik.http.routers.evobgp-web.rule=Host(`${WEBUI_DOMAIN}`)
|
- traefik.http.routers.evobgp-web.rule=Host(`${WEBUI_DOMAIN}`)
|
||||||
- traefik.http.routers.evobgp-web.entrypoints=websecure
|
- traefik.http.routers.evobgp-web.entrypoints=websecure
|
||||||
- traefik.http.routers.evobgp-web.tls=true
|
- traefik.http.routers.evobgp-web.tls=true
|
||||||
- traefik.http.routers.evobgp-web.tls.certresolver=letsencrypt
|
- traefik.http.routers.evobgp-web.tls.certresolver=letsencrypt
|
||||||
|
- traefik.http.routers.evobgp-web.priority=10
|
||||||
- traefik.http.routers.evobgp-web.middlewares=webui-ipwhitelist@docker
|
- traefik.http.routers.evobgp-web.middlewares=webui-ipwhitelist@docker
|
||||||
- traefik.http.middlewares.webui-ipwhitelist.ipallowlist.sourcerange=${WEBUI_IP_WHITELIST}
|
- traefik.http.middlewares.webui-ipwhitelist.ipallowlist.sourcerange=${WEBUI_IP_WHITELIST}
|
||||||
- traefik.http.services.evobgp-web.loadbalancer.server.port=80
|
- traefik.http.services.evobgp-web.loadbalancer.server.port=80
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ server {
|
|||||||
proxy_set_header Host $host;
|
proxy_set_header Host $host;
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
proxy_set_header X-Forwarded-Proto $scheme;
|
proxy_set_header X-Forwarded-Proto $http_x_forwarded_proto;
|
||||||
}
|
}
|
||||||
|
|
||||||
location = /metrics {
|
location = /metrics {
|
||||||
|
|||||||
@@ -21,6 +21,8 @@
|
|||||||
|
|
||||||
## Установка на сервер
|
## Установка на сервер
|
||||||
|
|
||||||
|
Публичные URL (без API-ключа, вне `WEBUI_IP_WHITELIST` Traefik): `GET /v1/firewall/install.sh`, `GET /v1/firewall/sync-script`, `POST /v1/firewall/enroll`. Всегда **HTTPS**.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
curl -fsSL https://<api>/v1/firewall/install.sh | \
|
curl -fsSL https://<api>/v1/firewall/install.sh | \
|
||||||
EVOBGP_CP_URL=https://<api> \
|
EVOBGP_CP_URL=https://<api> \
|
||||||
|
|||||||
@@ -153,6 +153,7 @@ docker compose --env-file .env --env-file .env.web-sec --profile microvps-full u
|
|||||||
- `http://<WEBUI_DOMAIN>` должен редиректить на `https://<WEBUI_DOMAIN>`;
|
- `http://<WEBUI_DOMAIN>` должен редиректить на `https://<WEBUI_DOMAIN>`;
|
||||||
- с IP из `WEBUI_IP_WHITELIST` UI доступен по HTTPS;
|
- с IP из `WEBUI_IP_WHITELIST` UI доступен по HTTPS;
|
||||||
- с неразрешенного IP Traefik вернет `403`.
|
- с неразрешенного IP Traefik вернет `403`.
|
||||||
|
- исключение: `GET /v1/firewall/install.sh`, `GET /v1/firewall/sync-script`, `POST /v1/firewall/enroll` — публичные, без whitelist (см. [firewall.md](firewall.md)).
|
||||||
|
|
||||||
Health API: `http://<IP>:8080/v1/health`.
|
Health API: `http://<IP>:8080/v1/health`.
|
||||||
|
|
||||||
|
|||||||
@@ -50,20 +50,13 @@ func (s *Server) handleFirewallInstallContext(w http.ResponseWriter, r *http.Req
|
|||||||
writeJSON(w, http.StatusOK, map[string]any{
|
writeJSON(w, http.StatusOK, map[string]any{
|
||||||
"bundle_seed": seed,
|
"bundle_seed": seed,
|
||||||
"bundle_seed_configured": seed != "",
|
"bundle_seed_configured": seed != "",
|
||||||
"suggested_cp_url": requestBaseURL(r),
|
"suggested_cp_url": publicHTTPSBaseURL(r),
|
||||||
"install_sh_url": requestBaseURL(r) + "/v1/firewall/install.sh",
|
"install_sh_url": publicHTTPSBaseURL(r) + "/v1/firewall/install.sh",
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func requestBaseURL(r *http.Request) string {
|
// publicHTTPSBaseURL is the external HTTPS origin for firewall install/enroll links.
|
||||||
scheme := "https"
|
func publicHTTPSBaseURL(r *http.Request) string {
|
||||||
if r.TLS == nil {
|
|
||||||
if xf := strings.TrimSpace(r.Header.Get("X-Forwarded-Proto")); xf != "" {
|
|
||||||
scheme = strings.ToLower(strings.Split(xf, ",")[0])
|
|
||||||
} else if strings.EqualFold(r.URL.Scheme, "http") {
|
|
||||||
scheme = "http"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
host := strings.TrimSpace(r.Host)
|
host := strings.TrimSpace(r.Host)
|
||||||
if xf := strings.TrimSpace(r.Header.Get("X-Forwarded-Host")); xf != "" {
|
if xf := strings.TrimSpace(r.Header.Get("X-Forwarded-Host")); xf != "" {
|
||||||
host = strings.TrimSpace(strings.Split(xf, ",")[0])
|
host = strings.TrimSpace(strings.Split(xf, ",")[0])
|
||||||
@@ -71,7 +64,11 @@ func requestBaseURL(r *http.Request) string {
|
|||||||
if host == "" {
|
if host == "" {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
return scheme + "://" + host
|
return "https://" + host
|
||||||
|
}
|
||||||
|
|
||||||
|
func requestBaseURL(r *http.Request) string {
|
||||||
|
return publicHTTPSBaseURL(r)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) handleFirewallEnrollPublic(w http.ResponseWriter, r *http.Request) {
|
func (s *Server) handleFirewallEnrollPublic(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|||||||
@@ -186,7 +186,10 @@ func TestFirewallInstallContext(t *testing.T) {
|
|||||||
if configured, _ := ctx["bundle_seed_configured"].(bool); !configured {
|
if configured, _ := ctx["bundle_seed_configured"].(bool); !configured {
|
||||||
t.Fatal("bundle_seed_configured want true")
|
t.Fatal("bundle_seed_configured want true")
|
||||||
}
|
}
|
||||||
if url, _ := ctx["install_sh_url"].(string); !strings.HasSuffix(url, "/v1/firewall/install.sh") {
|
if url, _ := ctx["suggested_cp_url"].(string); !strings.HasPrefix(url, "https://") {
|
||||||
|
t.Fatalf("suggested_cp_url=%q want https", url)
|
||||||
|
}
|
||||||
|
if url, _ := ctx["install_sh_url"].(string); !strings.HasPrefix(url, "https://") || !strings.HasSuffix(url, "/v1/firewall/install.sh") {
|
||||||
t.Fatalf("install_sh_url=%q", url)
|
t.Fatalf("install_sh_url=%q", url)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user