feat: 添加域名和ip过滤模式设置

This commit is contained in:
creamlike1024
2025-09-16 22:40:40 +08:00
parent 72d5b35d3f
commit b7bc609a7a
5 changed files with 99 additions and 37 deletions

View File

@@ -30,7 +30,7 @@ func DoWorkerRequest(req *WorkerRequest) (*http.Response, error) {
// SSRF防护验证请求URL // SSRF防护验证请求URL
fetchSetting := system_setting.GetFetchSetting() 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) return nil, fmt.Errorf("request reject: %v", err)
} }
@@ -59,7 +59,7 @@ func DoDownloadRequest(originUrl string, reason ...string) (resp *http.Response,
} else { } else {
// SSRF防护验证请求URL非Worker模式 // SSRF防护验证请求URL非Worker模式
fetchSetting := system_setting.GetFetchSetting() 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) return nil, fmt.Errorf("request reject: %v", err)
} }

View File

@@ -115,7 +115,7 @@ func sendBarkNotify(barkURL string, data dto.Notify) error {
} else { } else {
// SSRF防护验证Bark URL非Worker模式 // SSRF防护验证Bark URL非Worker模式
fetchSetting := system_setting.GetFetchSetting() 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) return fmt.Errorf("request reject: %v", err)
} }

View File

@@ -89,7 +89,7 @@ func SendWebhookNotify(webhookURL string, secret string, data dto.Notify) error
} else { } else {
// SSRF防护验证Webhook URL非Worker模式 // SSRF防护验证Webhook URL非Worker模式
fetchSetting := system_setting.GetFetchSetting() 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) return fmt.Errorf("request reject: %v", err)
} }

View File

@@ -5,16 +5,20 @@ import "one-api/setting/config"
type FetchSetting struct { type FetchSetting struct {
EnableSSRFProtection bool `json:"enable_ssrf_protection"` // 是否启用SSRF防护 EnableSSRFProtection bool `json:"enable_ssrf_protection"` // 是否启用SSRF防护
AllowPrivateIp bool `json:"allow_private_ip"` AllowPrivateIp bool `json:"allow_private_ip"`
WhitelistDomains []string `json:"whitelist_domains"` // domain format, e.g. example.com, *.example.com DomainFilterMode bool `json:"domain_filter_mode"` // 域名过滤模式true: 白名单模式false: 黑名单模式
WhitelistIps []string `json:"whitelist_ips"` // CIDR format IpFilterMode bool `json:"ip_filter_mode"` // IP过滤模式true: 白名单模式false: 黑名单模式
AllowedPorts []string `json:"allowed_ports"` // port range format, e.g. 80, 443, 8000-9000 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{ var defaultFetchSetting = FetchSetting{
EnableSSRFProtection: true, // 默认开启SSRF防护 EnableSSRFProtection: true, // 默认开启SSRF防护
AllowPrivateIp: false, AllowPrivateIp: false,
WhitelistDomains: []string{}, DomainFilterMode: true,
WhitelistIps: []string{}, IpFilterMode: true,
DomainList: []string{},
IpList: []string{},
AllowedPorts: []string{"80", "443", "8080", "8443"}, AllowedPorts: []string{"80", "443", "8080", "8443"},
} }

View File

@@ -29,6 +29,7 @@ import {
TagInput, TagInput,
Spin, Spin,
Card, Card,
Radio,
} from '@douyinfe/semi-ui'; } from '@douyinfe/semi-ui';
const { Text } = Typography; const { Text } = Typography;
import { import {
@@ -91,8 +92,10 @@ const SystemSetting = () => {
// SSRF防护配置 // SSRF防护配置
'fetch_setting.enable_ssrf_protection': true, 'fetch_setting.enable_ssrf_protection': true,
'fetch_setting.allow_private_ip': '', 'fetch_setting.allow_private_ip': '',
'fetch_setting.whitelist_domains': [], 'fetch_setting.domain_filter_mode': true, // true 白名单false 黑名单
'fetch_setting.whitelist_ips': [], 'fetch_setting.ip_filter_mode': true, // true 白名单false 黑名单
'fetch_setting.domain_list': [],
'fetch_setting.ip_list': [],
'fetch_setting.allowed_ports': [], 'fetch_setting.allowed_ports': [],
}); });
@@ -105,8 +108,10 @@ const SystemSetting = () => {
useState(false); useState(false);
const [linuxDOOAuthEnabled, setLinuxDOOAuthEnabled] = useState(false); const [linuxDOOAuthEnabled, setLinuxDOOAuthEnabled] = useState(false);
const [emailToAdd, setEmailToAdd] = useState(''); const [emailToAdd, setEmailToAdd] = useState('');
const [whitelistDomains, setWhitelistDomains] = useState([]); const [domainFilterMode, setDomainFilterMode] = useState(true);
const [whitelistIps, setWhitelistIps] = useState([]); const [ipFilterMode, setIpFilterMode] = useState(true);
const [domainList, setDomainList] = useState([]);
const [ipList, setIpList] = useState([]);
const [allowedPorts, setAllowedPorts] = useState([]); const [allowedPorts, setAllowedPorts] = useState([]);
const getOptions = async () => { const getOptions = async () => {
@@ -125,22 +130,24 @@ const SystemSetting = () => {
break; break;
case 'fetch_setting.allow_private_ip': case 'fetch_setting.allow_private_ip':
case 'fetch_setting.enable_ssrf_protection': case 'fetch_setting.enable_ssrf_protection':
case 'fetch_setting.domain_filter_mode':
case 'fetch_setting.ip_filter_mode':
item.value = toBoolean(item.value); item.value = toBoolean(item.value);
break; break;
case 'fetch_setting.whitelist_domains': case 'fetch_setting.domain_list':
try { try {
const domains = item.value ? JSON.parse(item.value) : []; const domains = item.value ? JSON.parse(item.value) : [];
setWhitelistDomains(Array.isArray(domains) ? domains : []); setDomainList(Array.isArray(domains) ? domains : []);
} catch (e) { } catch (e) {
setWhitelistDomains([]); setDomainList([]);
} }
break; break;
case 'fetch_setting.whitelist_ips': case 'fetch_setting.ip_list':
try { try {
const ips = item.value ? JSON.parse(item.value) : []; const ips = item.value ? JSON.parse(item.value) : [];
setWhitelistIps(Array.isArray(ips) ? ips : []); setIpList(Array.isArray(ips) ? ips : []);
} catch (e) { } catch (e) {
setWhitelistIps([]); setIpList([]);
} }
break; break;
case 'fetch_setting.allowed_ports': case 'fetch_setting.allowed_ports':
@@ -178,6 +185,13 @@ const SystemSetting = () => {
}); });
setInputs(newInputs); setInputs(newInputs);
setOriginInputs(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) { if (formApiRef.current) {
formApiRef.current.setValues(newInputs); formApiRef.current.setValues(newInputs);
} }
@@ -317,19 +331,27 @@ const SystemSetting = () => {
const submitSSRF = async () => { const submitSSRF = async () => {
const options = []; const options = [];
// 处理域名白名单 // 处理域名过滤模式与列表
if (Array.isArray(whitelistDomains)) { options.push({
key: 'fetch_setting.domain_filter_mode',
value: domainFilterMode,
});
if (Array.isArray(domainList)) {
options.push({ options.push({
key: 'fetch_setting.whitelist_domains', key: 'fetch_setting.domain_list',
value: JSON.stringify(whitelistDomains), value: JSON.stringify(domainList),
}); });
} }
// 处理IP白名单 // 处理IP过滤模式与列表
if (Array.isArray(whitelistIps)) { options.push({
key: 'fetch_setting.ip_filter_mode',
value: ipFilterMode,
});
if (Array.isArray(ipList)) {
options.push({ options.push({
key: 'fetch_setting.whitelist_ips', key: 'fetch_setting.ip_list',
value: JSON.stringify(whitelistIps), value: JSON.stringify(ipList),
}); });
} }
@@ -702,25 +724,43 @@ const SystemSetting = () => {
style={{ marginTop: 16 }} style={{ marginTop: 16 }}
> >
<Col xs={24} sm={24} md={24} lg={24} xl={24}> <Col xs={24} sm={24} md={24} lg={24} xl={24}>
<Text strong>{t('域名白名单')}</Text> <Text strong>
{t(domainFilterMode ? '域名白名单' : '域名黑名单')}
</Text>
<Text type="secondary" style={{ display: 'block', marginBottom: 8 }}> <Text type="secondary" style={{ display: 'block', marginBottom: 8 }}>
{t('支持通配符格式example.com, *.api.example.com')} {t('支持通配符格式example.com, *.api.example.com')}
</Text> </Text>
<Radio.Group
type='button'
value={domainFilterMode ? 'whitelist' : 'blacklist'}
onChange={(val) => {
const isWhitelist = val === 'whitelist';
setDomainFilterMode(isWhitelist);
setInputs(prev => ({
...prev,
'fetch_setting.domain_filter_mode': isWhitelist,
}));
}}
style={{ marginBottom: 8 }}
>
<Radio value='whitelist'>{t('白名单')}</Radio>
<Radio value='blacklist'>{t('黑名单')}</Radio>
</Radio.Group>
<TagInput <TagInput
value={whitelistDomains} value={domainList}
onChange={(value) => { onChange={(value) => {
setWhitelistDomains(value); setDomainList(value);
// 触发Form的onChange事件 // 触发Form的onChange事件
setInputs(prev => ({ setInputs(prev => ({
...prev, ...prev,
'fetch_setting.whitelist_domains': value 'fetch_setting.domain_list': value
})); }));
}} }}
placeholder={t('输入域名后回车example.com')} placeholder={t('输入域名后回车example.com')}
style={{ width: '100%' }} style={{ width: '100%' }}
/> />
<Text type="secondary" style={{ display: 'block', marginBottom: 8 }}> <Text type="secondary" style={{ display: 'block', marginBottom: 8 }}>
{t('域名白名单详细说明')} {t('域名过滤详细说明')}
</Text> </Text>
</Col> </Col>
</Row> </Row>
@@ -730,25 +770,43 @@ const SystemSetting = () => {
style={{ marginTop: 16 }} style={{ marginTop: 16 }}
> >
<Col xs={24} sm={24} md={24} lg={24} xl={24}> <Col xs={24} sm={24} md={24} lg={24} xl={24}>
<Text strong>{t('IP白名单')}</Text> <Text strong>
{t(ipFilterMode ? 'IP白名单' : 'IP黑名单')}
</Text>
<Text type="secondary" style={{ display: 'block', marginBottom: 8 }}> <Text type="secondary" style={{ display: 'block', marginBottom: 8 }}>
{t('支持CIDR格式8.8.8.8, 192.168.1.0/24')} {t('支持CIDR格式8.8.8.8, 192.168.1.0/24')}
</Text> </Text>
<Radio.Group
type='button'
value={ipFilterMode ? 'whitelist' : 'blacklist'}
onChange={(val) => {
const isWhitelist = val === 'whitelist';
setIpFilterMode(isWhitelist);
setInputs(prev => ({
...prev,
'fetch_setting.ip_filter_mode': isWhitelist,
}));
}}
style={{ marginBottom: 8 }}
>
<Radio value='whitelist'>{t('白名单')}</Radio>
<Radio value='blacklist'>{t('黑名单')}</Radio>
</Radio.Group>
<TagInput <TagInput
value={whitelistIps} value={ipList}
onChange={(value) => { onChange={(value) => {
setWhitelistIps(value); setIpList(value);
// 触发Form的onChange事件 // 触发Form的onChange事件
setInputs(prev => ({ setInputs(prev => ({
...prev, ...prev,
'fetch_setting.whitelist_ips': value 'fetch_setting.ip_list': value
})); }));
}} }}
placeholder={t('输入IP地址后回车8.8.8.8')} placeholder={t('输入IP地址后回车8.8.8.8')}
style={{ width: '100%' }} style={{ width: '100%' }}
/> />
<Text type="secondary" style={{ display: 'block', marginBottom: 8 }}> <Text type="secondary" style={{ display: 'block', marginBottom: 8 }}>
{t('IP白名单详细说明')} {t('IP过滤详细说明')}
</Text> </Text>
</Col> </Col>
</Row> </Row>