Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e15768b25b |
@@ -34,6 +34,16 @@ import {
|
||||
} from '@/queries/firewall'
|
||||
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')({
|
||||
component: FirewallPage,
|
||||
})
|
||||
@@ -51,13 +61,13 @@ function FirewallPage() {
|
||||
|
||||
const [clientName, setClientName] = useState('web-01')
|
||||
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('')
|
||||
|
||||
useEffect(() => {
|
||||
if (installCtx?.suggested_cp_url) {
|
||||
setCpUrl(installCtx.suggested_cp_url)
|
||||
setCpUrl(httpsOrigin(installCtx.suggested_cp_url))
|
||||
}
|
||||
if (installCtx?.bundle_seed) {
|
||||
setSeed(installCtx.bundle_seed)
|
||||
|
||||
@@ -159,10 +159,18 @@ services:
|
||||
condition: service_started
|
||||
labels:
|
||||
- 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.entrypoints=websecure
|
||||
- traefik.http.routers.evobgp-web.tls=true
|
||||
- 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.middlewares.webui-ipwhitelist.ipallowlist.sourcerange=${WEBUI_IP_WHITELIST}
|
||||
- traefik.http.services.evobgp-web.loadbalancer.server.port=80
|
||||
|
||||
@@ -247,10 +247,18 @@ services:
|
||||
- evobgp-all
|
||||
labels:
|
||||
- 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.entrypoints=websecure
|
||||
- traefik.http.routers.evobgp-web.tls=true
|
||||
- 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.middlewares.webui-ipwhitelist.ipallowlist.sourcerange=${WEBUI_IP_WHITELIST}
|
||||
- traefik.http.services.evobgp-web.loadbalancer.server.port=80
|
||||
|
||||
@@ -162,10 +162,18 @@ services:
|
||||
condition: service_started
|
||||
labels:
|
||||
- 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.entrypoints=websecure
|
||||
- traefik.http.routers.evobgp-web.tls=true
|
||||
- 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.middlewares.webui-ipwhitelist.ipallowlist.sourcerange=${WEBUI_IP_WHITELIST}
|
||||
- traefik.http.services.evobgp-web.loadbalancer.server.port=80
|
||||
|
||||
@@ -17,7 +17,7 @@ server {
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
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 {
|
||||
|
||||
@@ -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
|
||||
curl -fsSL https://<api>/v1/firewall/install.sh | \
|
||||
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>`;
|
||||
- с IP из `WEBUI_IP_WHITELIST` UI доступен по HTTPS;
|
||||
- с неразрешенного 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`.
|
||||
|
||||
|
||||
@@ -50,20 +50,13 @@ func (s *Server) handleFirewallInstallContext(w http.ResponseWriter, r *http.Req
|
||||
writeJSON(w, http.StatusOK, map[string]any{
|
||||
"bundle_seed": seed,
|
||||
"bundle_seed_configured": seed != "",
|
||||
"suggested_cp_url": requestBaseURL(r),
|
||||
"install_sh_url": requestBaseURL(r) + "/v1/firewall/install.sh",
|
||||
"suggested_cp_url": publicHTTPSBaseURL(r),
|
||||
"install_sh_url": publicHTTPSBaseURL(r) + "/v1/firewall/install.sh",
|
||||
})
|
||||
}
|
||||
|
||||
func requestBaseURL(r *http.Request) string {
|
||||
scheme := "https"
|
||||
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"
|
||||
}
|
||||
}
|
||||
// publicHTTPSBaseURL is the external HTTPS origin for firewall install/enroll links.
|
||||
func publicHTTPSBaseURL(r *http.Request) string {
|
||||
host := strings.TrimSpace(r.Host)
|
||||
if xf := strings.TrimSpace(r.Header.Get("X-Forwarded-Host")); xf != "" {
|
||||
host = strings.TrimSpace(strings.Split(xf, ",")[0])
|
||||
@@ -71,7 +64,11 @@ func requestBaseURL(r *http.Request) string {
|
||||
if host == "" {
|
||||
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) {
|
||||
|
||||
@@ -186,7 +186,10 @@ func TestFirewallInstallContext(t *testing.T) {
|
||||
if configured, _ := ctx["bundle_seed_configured"].(bool); !configured {
|
||||
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)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user