下载地址增加 key 认证

agent二进制下载地址增加 key 认证,防止刷流量
This commit is contained in:
榆木 committed 2026-01-11 19:19:58 +08:00
1 parent fd7a083139
commit 9ae1fea7d8
6 files changed
+57 -46

No files matched your search

+2
View File
@@ -58,6 +58,8 @@ github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs= github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs=
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA= github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
github.com/google/subcommands v1.2.0 h1:vWQspBTo2nEqTUFita5/KeEWlUL8kQObDFbub/EN9oE=
github.com/google/subcommands v1.2.0/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/wire v0.7.0 h1:JxUKI6+CVBgCO2WToKy/nQk0sS+amI9z9EjVmdaocj4= github.com/google/wire v0.7.0 h1:JxUKI6+CVBgCO2WToKy/nQk0sS+amI9z9EjVmdaocj4=
+3 -1
View File
@@ -16,13 +16,14 @@ type AgentHandler struct {
tamperService *service.TamperService tamperService *service.TamperService
ddnsService *service.DDNSService ddnsService *service.DDNSService
sshLoginService *service.SSHLoginService sshLoginService *service.SSHLoginService
apiKeyService *service.ApiKeyService
wsManager *ws.Manager wsManager *ws.Manager
upgrader websocket.Upgrader upgrader websocket.Upgrader
} }
func NewAgentHandler(logger *zap.Logger, agentService *service.AgentService, trafficService *service.TrafficService, func NewAgentHandler(logger *zap.Logger, agentService *service.AgentService, trafficService *service.TrafficService,
metricService *service.MetricService, monitorService *service.MonitorService, tamperService *service.TamperService, metricService *service.MetricService, monitorService *service.MonitorService, tamperService *service.TamperService,
ddnsService *service.DDNSService, sshLoginService *service.SSHLoginService, wsManager *ws.Manager) *AgentHandler { ddnsService *service.DDNSService, sshLoginService *service.SSHLoginService, apiKeyService *service.ApiKeyService, wsManager *ws.Manager) *AgentHandler {
h := &AgentHandler{ h := &AgentHandler{
logger: logger, logger: logger,
@@ -33,6 +34,7 @@ func NewAgentHandler(logger *zap.Logger, agentService *service.AgentService, tra
tamperService: tamperService, tamperService: tamperService,
ddnsService: ddnsService, ddnsService: ddnsService,
sshLoginService: sshLoginService, sshLoginService: sshLoginService,
apiKeyService: apiKeyService,
wsManager: wsManager, wsManager: wsManager,
} }
+8 -1
View File
@@ -22,6 +22,13 @@ func (h *AgentHandler) GetAgentVersion(c echo.Context) error {
func (h *AgentHandler) DownloadAgent(c echo.Context) error { func (h *AgentHandler) DownloadAgent(c echo.Context) error {
filename := c.Param("filename") filename := c.Param("filename")
// 校验 API Key
apiKey := c.QueryParam("key")
if _, err := h.apiKeyService.ValidateApiKey(c.Request().Context(), apiKey); err != nil {
h.logger.Warn("download agent failed: invalid api key", zap.String("key", apiKey))
return orz.NewError(401, "无效的 API 密钥")
}
// 从嵌入的文件系统读取 // 从嵌入的文件系统读取
agentFile, err := pika.AgentFS().Open(fmt.Sprintf("pika-%s", filename)) agentFile, err := pika.AgentFS().Open(fmt.Sprintf("pika-%s", filename))
if err != nil { if err != nil {
@@ -135,7 +142,7 @@ detect_platform() {
# 下载探针 # 下载探针
download_agent() { download_agent() {
local download_url="` + serverUrl + `/api/agent/downloads/agent-$PLATFORM" local download_url="` + serverUrl + `/api/agent/downloads/agent-$PLATFORM?key=` + token + `"
local temp_file="/tmp/pika-agent-download" local temp_file="/tmp/pika-agent-download"
echo_info "正在下载探针..." echo_info "正在下载探针..."
+1 -1
View File
@@ -43,7 +43,7 @@ func InitializeApp(logger *zap.Logger, db *gorm.DB, cfg *config.AppConfig) (*App
tamperService := service.NewTamperService(logger, db, manager, notificationService) tamperService := service.NewTamperService(logger, db, manager, notificationService)
ddnsService := service.NewDDNSService(logger, db, propertyService, manager) ddnsService := service.NewDDNSService(logger, db, propertyService, manager)
sshLoginService := service.NewSSHLoginService(logger, db, manager, geoIPService, notificationService) sshLoginService := service.NewSSHLoginService(logger, db, manager, geoIPService, notificationService)
agentHandler := handler.NewAgentHandler(logger, agentService, trafficService, metricService, monitorService, tamperService, ddnsService, sshLoginService, manager) agentHandler := handler.NewAgentHandler(logger, agentService, trafficService, metricService, monitorService, tamperService, ddnsService, sshLoginService, apiKeyService, manager)
apiKeyHandler := handler.NewApiKeyHandler(logger, apiKeyService) apiKeyHandler := handler.NewApiKeyHandler(logger, apiKeyService)
alertService := service.NewAlertService(logger, db, propertyService, monitorService, notifier) alertService := service.NewAlertService(logger, db, propertyService, monitorService, notifier)
alertHandler := handler.NewAlertHandler(logger, alertService) alertHandler := handler.NewAlertHandler(logger, alertService)
+1 -1
View File
@@ -275,7 +275,7 @@ func (c *Config) GetDownloadURL() string {
if runtime.GOOS == "windows" { if runtime.GOOS == "windows" {
filename += ".exe" filename += ".exe"
} }
return c.Endpoint() + "/api/agent/downloads/" + filename return c.Endpoint() + "/api/agent/downloads/" + filename + "?key=" + c.Server.APIKey
} }
func (c *Config) Endpoint() string { func (c *Config) Endpoint() string {
+42 -42
View File
@@ -1,17 +1,17 @@
import React, {type ReactElement, useCallback, useEffect, useMemo, useState} from 'react'; import React, { type ReactElement, useCallback, useEffect, useMemo, useState } from 'react';
import {Alert, App, Button, Card, Select, Space, Tabs, Typography} from 'antd'; import { Alert, App, Button, Card, Select, Space, Tabs, Typography } from 'antd';
import {CopyIcon} from 'lucide-react'; import { CopyIcon } from 'lucide-react';
import {listApiKeys} from '@/api/apiKey.ts'; import { listApiKeys } from '@/api/apiKey.ts';
import {getServerUrl} from '@/api/agent.ts'; import { getServerUrl } from '@/api/agent.ts';
import type {ApiKey} from '@/types'; import type { ApiKey } from '@/types';
import linuxPng from '../../assets/os/linux.png'; import linuxPng from '../../assets/os/linux.png';
import applePng from '../../assets/os/apple.png'; import applePng from '../../assets/os/apple.png';
import windowsPng from '../../assets/os/win11.png'; import windowsPng from '../../assets/os/win11.png';
import {useNavigate} from "react-router-dom"; import { useNavigate } from "react-router-dom";
import copy from 'copy-to-clipboard'; import copy from 'copy-to-clipboard';
const {Paragraph, Text} = Typography; const { Paragraph, Text } = Typography;
const {TabPane} = Tabs; const { TabPane } = Tabs;
// 常量定义 // 常量定义
const AGENT_NAME = 'pika-agent'; const AGENT_NAME = 'pika-agent';
@@ -42,7 +42,7 @@ const AgentInstall = () => {
// 服务器地址相关状态 // 服务器地址相关状态
const [backendServerUrl, setBackendServerUrl] = useState<string>(''); const [backendServerUrl, setBackendServerUrl] = useState<string>('');
const {message} = App.useApp(); const { message } = App.useApp();
const navigate = useNavigate(); const navigate = useNavigate();
const frontendUrl = useMemo(() => window.location.origin, []); const frontendUrl = useMemo(() => window.location.origin, []);
@@ -86,37 +86,37 @@ const AgentInstall = () => {
const osConfigs: Record<OSType, OSConfig> = useMemo(() => ({ const osConfigs: Record<OSType, OSConfig> = useMemo(() => ({
'linux-amd64': { 'linux-amd64': {
name: 'Linux (amd64)', name: 'Linux (amd64)',
icon: <img src={linuxPng} alt="Linux" className="h-4 w-4"/>, icon: <img src={linuxPng} alt="Linux" className="h-4 w-4" />,
downloadUrl: '/api/agent/downloads/agent-linux-amd64', downloadUrl: '/api/agent/downloads/agent-linux-amd64',
}, },
'linux-arm64': { 'linux-arm64': {
name: 'Linux (arm64)', name: 'Linux (arm64)',
icon: <img src={linuxPng} alt="Linux" className="h-4 w-4"/>, icon: <img src={linuxPng} alt="Linux" className="h-4 w-4" />,
downloadUrl: '/api/agent/downloads/agent-linux-arm64', downloadUrl: '/api/agent/downloads/agent-linux-arm64',
}, },
'linux-loong64': { 'linux-loong64': {
name: 'Linux (loongarch64)', name: 'Linux (loongarch64)',
icon: <img src={linuxPng} alt="Linux" className={'h-4 w-4'}/>, icon: <img src={linuxPng} alt="Linux" className={'h-4 w-4'} />,
downloadUrl: '/api/agent/downloads/agent-linux-loong64', downloadUrl: '/api/agent/downloads/agent-linux-loong64',
}, },
'darwin-amd64': { 'darwin-amd64': {
name: 'macOS (amd64)', name: 'macOS (amd64)',
icon: <img src={applePng} alt="macOS" className="h-4 w-4"/>, icon: <img src={applePng} alt="macOS" className="h-4 w-4" />,
downloadUrl: '/api/agent/downloads/agent-darwin-amd64', downloadUrl: '/api/agent/downloads/agent-darwin-amd64',
}, },
'darwin-arm64': { 'darwin-arm64': {
name: 'macOS (arm64)', name: 'macOS (arm64)',
icon: <img src={applePng} alt="macOS" className="h-4 w-4"/>, icon: <img src={applePng} alt="macOS" className="h-4 w-4" />,
downloadUrl: '/api/agent/downloads/agent-darwin-arm64', downloadUrl: '/api/agent/downloads/agent-darwin-arm64',
}, },
'windows-amd64': { 'windows-amd64': {
name: 'Windows (amd64)', name: 'Windows (amd64)',
icon: <img src={windowsPng} alt="Windows" className="h-4 w-4"/>, icon: <img src={windowsPng} alt="Windows" className="h-4 w-4" />,
downloadUrl: '/api/agent/downloads/agent-windows-amd64.exe', downloadUrl: '/api/agent/downloads/agent-windows-amd64.exe',
}, },
'windows-arm64': { 'windows-arm64': {
name: 'Windows (arm64)', name: 'Windows (arm64)',
icon: <img src={windowsPng} alt="Windows" className="h-4 w-4"/>, icon: <img src={windowsPng} alt="Windows" className="h-4 w-4" />,
downloadUrl: '/api/agent/downloads/agent-windows-arm64.exe', downloadUrl: '/api/agent/downloads/agent-windows-arm64.exe',
}, },
}), []); }), []);
@@ -158,7 +158,7 @@ const AgentInstall = () => {
<Space direction="vertical" className="w-full"> <Space direction="vertical" className="w-full">
<div> <div>
当前访问地址: <Text code>{frontendUrl}</Text> 当前访问地址: <Text code>{frontendUrl}</Text>
<br/> <br />
后端检测地址: <Text code>{backendServerUrl}</Text> 后端检测地址: <Text code>{backendServerUrl}</Text>
</div> </div>
<div> <div>
@@ -170,7 +170,7 @@ const AgentInstall = () => {
<div> <div>
<Text strong>Nginx 配置示例:</Text> <Text strong>Nginx 配置示例:</Text>
<pre className="m-0 mt-2 overflow-auto text-xs bg-gray-100 dark:bg-slate-900 p-2 rounded"> <pre className="m-0 mt-2 overflow-auto text-xs bg-gray-100 dark:bg-slate-900 p-2 rounded">
{`location / { {`location / {
proxy_pass http://backend; proxy_pass http://backend;
proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Host $host; proxy_set_header X-Forwarded-Host $host;
@@ -182,7 +182,7 @@ const AgentInstall = () => {
<div> <div>
<Text strong>Caddy 配置示例:</Text> <Text strong>Caddy 配置示例:</Text>
<pre className="m-0 mt-2 overflow-auto text-xs bg-gray-100 dark:bg-slate-900 p-2 rounded"> <pre className="m-0 mt-2 overflow-auto text-xs bg-gray-100 dark:bg-slate-900 p-2 rounded">
{`reverse_proxy backend:8080 { {`reverse_proxy backend:8080 {
header_up X-Forwarded-Proto {scheme} header_up X-Forwarded-Proto {scheme}
header_up X-Forwarded-Host {host} header_up X-Forwarded-Host {host}
}`} }`}
@@ -191,7 +191,7 @@ const AgentInstall = () => {
<div> <div>
<Text strong>Traefik 配置说明:</Text> <Text strong>Traefik 配置说明:</Text>
<pre className="m-0 mt-2 overflow-auto text-xs bg-gray-100 dark:bg-slate-900 p-2 rounded"> <pre className="m-0 mt-2 overflow-auto text-xs bg-gray-100 dark:bg-slate-900 p-2 rounded">
{`# Traefik 默认会自动添加 X-Forwarded-* 头部 {`# Traefik 默认会自动添加 X-Forwarded-* 头部
# 无需额外配置`} # 无需额外配置`}
</pre> </pre>
</div> </div>
@@ -240,8 +240,8 @@ const AgentInstall = () => {
// 一键安装组件 // 一键安装组件
const InstallByOneClick = useCallback(() => ( const InstallByOneClick = useCallback(() => (
<Space direction="vertical" className="w-full"> <Space direction="vertical" className="w-full">
<ServerUrlChecker/> <ServerUrlChecker />
<ApiChooser/> <ApiChooser />
<Alert <Alert
description="一键安装脚本仅支持 Linux/macOS 系统。" description="一键安装脚本仅支持 Linux/macOS 系统。"
type="info" type="info"
@@ -259,16 +259,16 @@ const AgentInstall = () => {
<Button <Button
type="link" type="link"
onClick={() => void copyToClipboard(installCommand)} onClick={() => void copyToClipboard(installCommand)}
icon={<CopyIcon className="h-4 w-4"/>} icon={<CopyIcon className="h-4 w-4" />}
style={{margin: 0, padding: 0}} style={{ margin: 0, padding: 0 }}
disabled={!selectedApiKey} disabled={!selectedApiKey}
> >
复制命令 复制命令
</Button> </Button>
</Card> </Card>
<ServiceHelper os={AGENT_NAME}/> <ServiceHelper os={AGENT_NAME} />
<ConfigHelper/> <ConfigHelper />
</Space> </Space>
), [ServerUrlChecker, ApiChooser, installCommand, copyToClipboard, selectedApiKey]); ), [ServerUrlChecker, ApiChooser, installCommand, copyToClipboard, selectedApiKey]);
@@ -297,7 +297,7 @@ ${agentCmd} version`;
}, []); }, []);
// 服务管理命令组件 // 服务管理命令组件
const ServiceHelper = useCallback(({os}: { os: string }) => ( const ServiceHelper = useCallback(({ os }: { os: string }) => (
<Card type="inner" title="服务管理命令"> <Card type="inner" title="服务管理命令">
<Paragraph type="secondary" className="mb-3 text-gray-600 dark:text-slate-400"> <Paragraph type="secondary" className="mb-3 text-gray-600 dark:text-slate-400">
注册完成后,您可以使用以下命令管理探针服务: 注册完成后,您可以使用以下命令管理探针服务:
@@ -335,10 +335,10 @@ ${agentCmd} version`;
{ {
title: '1. 下载探针', title: '1. 下载探针',
command: `# 使用 PowerShell 下载 command: `# 使用 PowerShell 下载
Invoke-WebRequest -Uri "${backendServerUrl}${config.downloadUrl}" -OutFile "${AGENT_NAME_EXE}" Invoke-WebRequest -Uri "${backendServerUrl}${config.downloadUrl}?key=${selectedApiKey}" -OutFile "${AGENT_NAME_EXE}"
# 或者使用浏览器直接下载 # 或者使用浏览器直接下载
# ${backendServerUrl}${config.downloadUrl}` # ${backendServerUrl}${config.downloadUrl}?key=${selectedApiKey}`
}, },
{ {
title: '2. 注册探针', title: '2. 注册探针',
@@ -355,10 +355,10 @@ Invoke-WebRequest -Uri "${backendServerUrl}${config.downloadUrl}" -OutFile "${AG
{ {
title: '1. 下载探针', title: '1. 下载探针',
command: `# 使用 wget 下载 command: `# 使用 wget 下载
wget ${backendServerUrl}${config.downloadUrl} -O ${AGENT_NAME} wget "${backendServerUrl}${config.downloadUrl}?key=${selectedApiKey}" -O ${AGENT_NAME}
# 或使用 curl 下载 # 或使用 curl 下载
curl -L ${backendServerUrl}${config.downloadUrl} -o ${AGENT_NAME}` curl -L "${backendServerUrl}${config.downloadUrl}?key=${selectedApiKey}" -o ${AGENT_NAME}`
}, },
{ {
title: '2. 赋予执行权限', title: '2. 赋予执行权限',
@@ -382,8 +382,8 @@ curl -L ${backendServerUrl}${config.downloadUrl} -o ${AGENT_NAME}`
// 手动安装组件 // 手动安装组件
const InstallByManual = useCallback(() => ( const InstallByManual = useCallback(() => (
<Space direction="vertical" className="w-full"> <Space direction="vertical" className="w-full">
<ServerUrlChecker/> <ServerUrlChecker />
<ApiChooser/> <ApiChooser />
<Tabs <Tabs
activeKey={selectedOS} activeKey={selectedOS}
onChange={(key) => setSelectedOS(key as OSType)} onChange={(key) => setSelectedOS(key as OSType)}
@@ -404,7 +404,7 @@ curl -L ${backendServerUrl}${config.downloadUrl} -o ${AGENT_NAME}`
{getManualInstallSteps(key as OSType).map((step, index) => ( {getManualInstallSteps(key as OSType).map((step, index) => (
<div key={index}> <div key={index}>
<Text strong <Text strong
className="block mb-2 text-gray-900 dark:text-slate-100">{step.title}</Text> className="block mb-2 text-gray-900 dark:text-slate-100">{step.title}</Text>
<pre <pre
className="m-0 overflow-auto text-sm bg-gray-50 dark:bg-slate-800 p-3 rounded text-gray-900 dark:text-slate-100"> className="m-0 overflow-auto text-sm bg-gray-50 dark:bg-slate-800 p-3 rounded text-gray-900 dark:text-slate-100">
<code>{step.command}</code> <code>{step.command}</code>
@@ -412,9 +412,9 @@ curl -L ${backendServerUrl}${config.downloadUrl} -o ${AGENT_NAME}`
<Button <Button
type="link" type="link"
onClick={() => void copyToClipboard(step.command)} onClick={() => void copyToClipboard(step.command)}
icon={<CopyIcon className="h-4 w-4"/>} icon={<CopyIcon className="h-4 w-4" />}
size="small" size="small"
style={{margin: 0, padding: 0}} style={{ margin: 0, padding: 0 }}
disabled={!selectedApiKey} disabled={!selectedApiKey}
> >
复制 复制
@@ -424,8 +424,8 @@ curl -L ${backendServerUrl}${config.downloadUrl} -o ${AGENT_NAME}`
</Space> </Space>
</Card> </Card>
<ServiceHelper os={key}/> <ServiceHelper os={key} />
<ConfigHelper/> <ConfigHelper />
</Space> </Space>
</TabPane> </TabPane>
))} ))}
@@ -438,12 +438,12 @@ curl -L ${backendServerUrl}${config.downloadUrl} -o ${AGENT_NAME}`
{ {
label: '一键安装', label: '一键安装',
key: 'one-click', key: 'one-click',
children: <InstallByOneClick/> children: <InstallByOneClick />
}, },
{ {
label: '手动安装', label: '手动安装',
key: 'manual', key: 'manual',
children: <InstallByManual/> children: <InstallByManual />
}, },
], [InstallByOneClick, InstallByManual]); ], [InstallByOneClick, InstallByManual]);
@@ -463,7 +463,7 @@ curl -L ${backendServerUrl}${config.downloadUrl} -o ${AGENT_NAME}`
</div> </div>
<Tabs <Tabs
style={{marginTop: 24}} style={{ marginTop: 24 }}
tabPosition="left" tabPosition="left"
items={tabItems} items={tabItems}
/> />