diff --git a/service/download.go b/service/download.go index 2f30870d4..43b6fe7df 100644 --- a/service/download.go +++ b/service/download.go @@ -30,7 +30,7 @@ func DoWorkerRequest(req *WorkerRequest) (*http.Response, error) { // SSRF防护:验证请求URL fetchSetting := system_setting.GetFetchSetting() - if err := common.ValidateURLWithFetchSetting(req.URL, fetchSetting.EnableSSRFProtection, fetchSetting.AllowPrivateIp, fetchSetting.WhitelistDomains, fetchSetting.WhitelistIps, fetchSetting.AllowedPorts); err != nil { + if err := common.ValidateURLWithFetchSetting(req.URL, fetchSetting.EnableSSRFProtection, fetchSetting.AllowPrivateIp, fetchSetting.DomainList, fetchSetting.IpList, fetchSetting.AllowedPorts); err != nil { return nil, fmt.Errorf("request reject: %v", err) } @@ -59,7 +59,7 @@ func DoDownloadRequest(originUrl string, reason ...string) (resp *http.Response, } else { // SSRF防护:验证请求URL(非Worker模式) fetchSetting := system_setting.GetFetchSetting() - if err := common.ValidateURLWithFetchSetting(originUrl, fetchSetting.EnableSSRFProtection, fetchSetting.AllowPrivateIp, fetchSetting.WhitelistDomains, fetchSetting.WhitelistIps, fetchSetting.AllowedPorts); err != nil { + if err := common.ValidateURLWithFetchSetting(originUrl, fetchSetting.EnableSSRFProtection, fetchSetting.AllowPrivateIp, fetchSetting.DomainList, fetchSetting.IpList, fetchSetting.AllowedPorts); err != nil { return nil, fmt.Errorf("request reject: %v", err) } diff --git a/service/user_notify.go b/service/user_notify.go index f9d7b6691..1e9e8947c 100644 --- a/service/user_notify.go +++ b/service/user_notify.go @@ -115,7 +115,7 @@ func sendBarkNotify(barkURL string, data dto.Notify) error { } else { // SSRF防护:验证Bark URL(非Worker模式) fetchSetting := system_setting.GetFetchSetting() - if err := common.ValidateURLWithFetchSetting(finalURL, fetchSetting.EnableSSRFProtection, fetchSetting.AllowPrivateIp, fetchSetting.WhitelistDomains, fetchSetting.WhitelistIps, fetchSetting.AllowedPorts); err != nil { + if err := common.ValidateURLWithFetchSetting(finalURL, fetchSetting.EnableSSRFProtection, fetchSetting.AllowPrivateIp, fetchSetting.DomainList, fetchSetting.IpList, fetchSetting.AllowedPorts); err != nil { return fmt.Errorf("request reject: %v", err) } diff --git a/service/webhook.go b/service/webhook.go index 1f159eb4b..5d9ce400a 100644 --- a/service/webhook.go +++ b/service/webhook.go @@ -89,7 +89,7 @@ func SendWebhookNotify(webhookURL string, secret string, data dto.Notify) error } else { // SSRF防护:验证Webhook URL(非Worker模式) fetchSetting := system_setting.GetFetchSetting() - if err := common.ValidateURLWithFetchSetting(webhookURL, fetchSetting.EnableSSRFProtection, fetchSetting.AllowPrivateIp, fetchSetting.WhitelistDomains, fetchSetting.WhitelistIps, fetchSetting.AllowedPorts); err != nil { + if err := common.ValidateURLWithFetchSetting(webhookURL, fetchSetting.EnableSSRFProtection, fetchSetting.AllowPrivateIp, fetchSetting.DomainList, fetchSetting.IpList, fetchSetting.AllowedPorts); err != nil { return fmt.Errorf("request reject: %v", err) } diff --git a/setting/system_setting/fetch_setting.go b/setting/system_setting/fetch_setting.go index 6e47c3f06..5277e1033 100644 --- a/setting/system_setting/fetch_setting.go +++ b/setting/system_setting/fetch_setting.go @@ -5,16 +5,20 @@ import "one-api/setting/config" type FetchSetting struct { EnableSSRFProtection bool `json:"enable_ssrf_protection"` // 是否启用SSRF防护 AllowPrivateIp bool `json:"allow_private_ip"` - WhitelistDomains []string `json:"whitelist_domains"` // domain format, e.g. example.com, *.example.com - WhitelistIps []string `json:"whitelist_ips"` // CIDR format - AllowedPorts []string `json:"allowed_ports"` // port range format, e.g. 80, 443, 8000-9000 + DomainFilterMode bool `json:"domain_filter_mode"` // 域名过滤模式,true: 白名单模式,false: 黑名单模式 + IpFilterMode bool `json:"ip_filter_mode"` // IP过滤模式,true: 白名单模式,false: 黑名单模式 + DomainList []string `json:"domain_list"` // domain format, e.g. example.com, *.example.com + IpList []string `json:"ip_list"` // CIDR format + AllowedPorts []string `json:"allowed_ports"` // port range format, e.g. 80, 443, 8000-9000 } var defaultFetchSetting = FetchSetting{ EnableSSRFProtection: true, // 默认开启SSRF防护 AllowPrivateIp: false, - WhitelistDomains: []string{}, - WhitelistIps: []string{}, + DomainFilterMode: true, + IpFilterMode: true, + DomainList: []string{}, + IpList: []string{}, AllowedPorts: []string{"80", "443", "8080", "8443"}, } diff --git a/web/src/components/settings/SystemSetting.jsx b/web/src/components/settings/SystemSetting.jsx index 71dfaac8d..ebe4084be 100644 --- a/web/src/components/settings/SystemSetting.jsx +++ b/web/src/components/settings/SystemSetting.jsx @@ -29,6 +29,7 @@ import { TagInput, Spin, Card, + Radio, } from '@douyinfe/semi-ui'; const { Text } = Typography; import { @@ -91,8 +92,10 @@ const SystemSetting = () => { // SSRF防护配置 'fetch_setting.enable_ssrf_protection': true, 'fetch_setting.allow_private_ip': '', - 'fetch_setting.whitelist_domains': [], - 'fetch_setting.whitelist_ips': [], + 'fetch_setting.domain_filter_mode': true, // true 白名单,false 黑名单 + 'fetch_setting.ip_filter_mode': true, // true 白名单,false 黑名单 + 'fetch_setting.domain_list': [], + 'fetch_setting.ip_list': [], 'fetch_setting.allowed_ports': [], }); @@ -105,8 +108,10 @@ const SystemSetting = () => { useState(false); const [linuxDOOAuthEnabled, setLinuxDOOAuthEnabled] = useState(false); const [emailToAdd, setEmailToAdd] = useState(''); - const [whitelistDomains, setWhitelistDomains] = useState([]); - const [whitelistIps, setWhitelistIps] = useState([]); + const [domainFilterMode, setDomainFilterMode] = useState(true); + const [ipFilterMode, setIpFilterMode] = useState(true); + const [domainList, setDomainList] = useState([]); + const [ipList, setIpList] = useState([]); const [allowedPorts, setAllowedPorts] = useState([]); const getOptions = async () => { @@ -125,22 +130,24 @@ const SystemSetting = () => { break; case 'fetch_setting.allow_private_ip': case 'fetch_setting.enable_ssrf_protection': + case 'fetch_setting.domain_filter_mode': + case 'fetch_setting.ip_filter_mode': item.value = toBoolean(item.value); break; - case 'fetch_setting.whitelist_domains': + case 'fetch_setting.domain_list': try { const domains = item.value ? JSON.parse(item.value) : []; - setWhitelistDomains(Array.isArray(domains) ? domains : []); + setDomainList(Array.isArray(domains) ? domains : []); } catch (e) { - setWhitelistDomains([]); + setDomainList([]); } break; - case 'fetch_setting.whitelist_ips': + case 'fetch_setting.ip_list': try { const ips = item.value ? JSON.parse(item.value) : []; - setWhitelistIps(Array.isArray(ips) ? ips : []); + setIpList(Array.isArray(ips) ? ips : []); } catch (e) { - setWhitelistIps([]); + setIpList([]); } break; case 'fetch_setting.allowed_ports': @@ -178,6 +185,13 @@ const SystemSetting = () => { }); setInputs(newInputs); setOriginInputs(newInputs); + // 同步模式布尔到本地状态 + if (typeof newInputs['fetch_setting.domain_filter_mode'] !== 'undefined') { + setDomainFilterMode(!!newInputs['fetch_setting.domain_filter_mode']); + } + if (typeof newInputs['fetch_setting.ip_filter_mode'] !== 'undefined') { + setIpFilterMode(!!newInputs['fetch_setting.ip_filter_mode']); + } if (formApiRef.current) { formApiRef.current.setValues(newInputs); } @@ -317,19 +331,27 @@ const SystemSetting = () => { const submitSSRF = async () => { const options = []; - // 处理域名白名单 - if (Array.isArray(whitelistDomains)) { + // 处理域名过滤模式与列表 + options.push({ + key: 'fetch_setting.domain_filter_mode', + value: domainFilterMode, + }); + if (Array.isArray(domainList)) { options.push({ - key: 'fetch_setting.whitelist_domains', - value: JSON.stringify(whitelistDomains), + key: 'fetch_setting.domain_list', + value: JSON.stringify(domainList), }); } - // 处理IP白名单 - if (Array.isArray(whitelistIps)) { + // 处理IP过滤模式与列表 + options.push({ + key: 'fetch_setting.ip_filter_mode', + value: ipFilterMode, + }); + if (Array.isArray(ipList)) { options.push({ - key: 'fetch_setting.whitelist_ips', - value: JSON.stringify(whitelistIps), + key: 'fetch_setting.ip_list', + value: JSON.stringify(ipList), }); } @@ -702,25 +724,43 @@ const SystemSetting = () => { style={{ marginTop: 16 }} > - {t('域名白名单')} + + {t(domainFilterMode ? '域名白名单' : '域名黑名单')} + {t('支持通配符格式,如:example.com, *.api.example.com')} + { + const isWhitelist = val === 'whitelist'; + setDomainFilterMode(isWhitelist); + setInputs(prev => ({ + ...prev, + 'fetch_setting.domain_filter_mode': isWhitelist, + })); + }} + style={{ marginBottom: 8 }} + > + {t('白名单')} + {t('黑名单')} + { - setWhitelistDomains(value); + setDomainList(value); // 触发Form的onChange事件 setInputs(prev => ({ ...prev, - 'fetch_setting.whitelist_domains': value + 'fetch_setting.domain_list': value })); }} placeholder={t('输入域名后回车,如:example.com')} style={{ width: '100%' }} /> - {t('域名白名单详细说明')} + {t('域名过滤详细说明')} @@ -730,25 +770,43 @@ const SystemSetting = () => { style={{ marginTop: 16 }} > - {t('IP白名单')} + + {t(ipFilterMode ? 'IP白名单' : 'IP黑名单')} + {t('支持CIDR格式,如:8.8.8.8, 192.168.1.0/24')} + { + const isWhitelist = val === 'whitelist'; + setIpFilterMode(isWhitelist); + setInputs(prev => ({ + ...prev, + 'fetch_setting.ip_filter_mode': isWhitelist, + })); + }} + style={{ marginBottom: 8 }} + > + {t('白名单')} + {t('黑名单')} + { - setWhitelistIps(value); + setIpList(value); // 触发Form的onChange事件 setInputs(prev => ({ ...prev, - 'fetch_setting.whitelist_ips': value + 'fetch_setting.ip_list': value })); }} placeholder={t('输入IP地址后回车,如:8.8.8.8')} style={{ width: '100%' }} /> - {t('IP白名单详细说明')} + {t('IP过滤详细说明')}