mirror of
https://github.com/acepanel/acepanel.github.io.git
synced 2026-02-04 06:47:16 +08:00
l10n: update translations (#3)
* New translations api.md (Chinese Simplified) [skip ci] Update translations (Chinese Simplified) api.md * New translations api.md (Chinese Traditional) [skip ci] Update translations (Chinese Traditional) api.md * New translations safe.md (Chinese Simplified) [skip ci] Update translations (Chinese Simplified) safe.md * New translations safe.md (Chinese Traditional) [skip ci] Update translations (Chinese Traditional) safe.md * New translations panel.md (Chinese Simplified) [skip ci] Update translations (Chinese Simplified) panel.md * New translations panel.md (Chinese Traditional) [skip ci] Update translations (Chinese Traditional) panel.md * New translations en.ts (Chinese Simplified) [skip ci] Update translations (Chinese Simplified) en.ts * New translations en.ts (Chinese Traditional) [skip ci] Update translations (Chinese Traditional) en.ts * New translations faq.md (Chinese Simplified) [skip ci] Update translations (Chinese Simplified) faq.md * New translations faq.md (Chinese Traditional) [skip ci] Update translations (Chinese Traditional) faq.md * New translations panel.md (Chinese Simplified) [skip ci] Update translations (Chinese Simplified) panel.md * New translations en.ts (Chinese Simplified) [skip ci] Update translations (Chinese Simplified) en.ts * New translations faq.md (Chinese Simplified) [skip ci] Update translations (Chinese Simplified) faq.md
This commit is contained in:
@@ -95,23 +95,11 @@ function sidebarAdvanced(): DefaultTheme.SidebarItem[] {
|
||||
return [{
|
||||
text: "面板 API",
|
||||
link: '/api'
|
||||
}, {
|
||||
text: "配置容器镜像加速",
|
||||
link: '/hub-mirror'
|
||||
}, {
|
||||
text: "配置反向代理",
|
||||
link: '/proxy'
|
||||
}, {
|
||||
text: "配置进程守护",
|
||||
link: '/supervisor'
|
||||
}, {
|
||||
text: "配置 QUIC(HTTP3)",
|
||||
link: '/quic'
|
||||
}, {
|
||||
text: "配置 TLSv1.1 TLSv1",
|
||||
link: '/tls'
|
||||
}, {
|
||||
text: "安全性建议",
|
||||
link: '/safe'
|
||||
}, {
|
||||
text: "常见问题",
|
||||
link: '/faq'
|
||||
}];
|
||||
}
|
||||
@@ -95,23 +95,11 @@ function sidebarAdvanced(): DefaultTheme.SidebarItem[] {
|
||||
return [{
|
||||
text: 'Panel API',
|
||||
link: '/api'
|
||||
}, {
|
||||
text: 'Configure container image acceleration',
|
||||
link: '/hub-mirror'
|
||||
}, {
|
||||
text: 'Configure reverse proxy',
|
||||
link: '/proxy'
|
||||
}, {
|
||||
text: 'Configure process daemon',
|
||||
link: '/supervisor'
|
||||
}, {
|
||||
text: 'Configure QUIC (HTTP3)',
|
||||
link: '/quic'
|
||||
}, {
|
||||
text: 'Configure TLSv1.1 TLSv1',
|
||||
link: '/tls'
|
||||
}, {
|
||||
text: 'Security recommendations',
|
||||
link: "/安全"
|
||||
}, {
|
||||
text: 'FAQ',
|
||||
link: '/faq'
|
||||
}];
|
||||
}
|
||||
661
zh_CN/advanced/api.md
Normal file
661
zh_CN/advanced/api.md
Normal file
@@ -0,0 +1,661 @@
|
||||
# API 参考文档
|
||||
|
||||
## 概述
|
||||
|
||||
耗子面板提供了一套安全的 RESTful 接口,用于与面板系统进行交互。 所有 API 请求都需要进行 HMAC-SHA256 签名认证以确保通信的安全性和完整性。
|
||||
|
||||
## 基础信息
|
||||
|
||||
- **基础 URL**: `http(s)://your-panel-domain/{entry}/api/`
|
||||
- **内容类型**: 所有请求和响应均使用 `application/json`
|
||||
- **字符编码**: UTF-8
|
||||
|
||||
## 认证机制
|
||||
|
||||
API 使用 HMAC-SHA256 签名算法进行认证。每个请求必须包含以下 HTTP 头: 每个请求必须包含以下 HTTP 头:
|
||||
|
||||
| 头部名称 | 描述 |
|
||||
| --------------- | --------------------------------------------------------------- |
|
||||
| `Content-Type` | 设置为 `application/json` |
|
||||
| `X-Timestamp` | 当前 UNIX 时间戳(秒) |
|
||||
| `Authorization` | 身份验证信息,格式为 `HMAC-SHA256 Credential={id}, Signature={signature}` |
|
||||
|
||||
## 签名算法
|
||||
|
||||
签名过程包含四个主要步骤:
|
||||
|
||||
### 1. 构造规范化请求
|
||||
|
||||
规范化请求字符串由以下部分组成,各部分之间使用换行符(\n)分隔:
|
||||
|
||||
```
|
||||
HTTP方法
|
||||
规范化路径
|
||||
规范化查询字符串
|
||||
请求体的SHA256哈希值
|
||||
```
|
||||
|
||||
**注意**:规范化路径应始终使用 `/api/` 开头的路径部分,忽略入口前缀。
|
||||
|
||||
### 2. 构造待签名字符串
|
||||
|
||||
待签名字符串包含以下部分,各部分使用换行符(\n)分隔:
|
||||
|
||||
```
|
||||
"HMAC-SHA256"
|
||||
时间戳
|
||||
规范化请求的SHA256哈希值
|
||||
```
|
||||
|
||||
### 3. 计算签名
|
||||
|
||||
使用您的令牌(token)对待签名字符串进行 HMAC-SHA256 计算,然后将结果转换为十六进制字符串。
|
||||
|
||||
### 4. 构造授权头
|
||||
|
||||
将计算得到的签名添加到 `Authorization` 头:
|
||||
|
||||
```
|
||||
Authorization: HMAC-SHA256 Credential={id}, Signature={signature}
|
||||
```
|
||||
|
||||
## Go 示例
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/hmac"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// 创建一个获取用户信息的请求
|
||||
req, err := http.NewRequest("GET", "http://example.com/entrance/api/user/info", nil)
|
||||
if err != nil {
|
||||
fmt.Println("Error creating request:", err)
|
||||
return
|
||||
}
|
||||
|
||||
// 设置内容类型
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
// 签名请求 - 传入您的用户ID和API令牌
|
||||
if err = SignReq(req, uint(16), "YourSecretToken"); err != nil {
|
||||
fmt.Println("Error signing request:", err)
|
||||
return
|
||||
}
|
||||
|
||||
// 发送请求
|
||||
client := &http.Client{}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
fmt.Println("Error sending request:", err)
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// 处理响应
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
fmt.Println("Error reading response:", err)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Println("Response Status:", resp.Status)
|
||||
fmt.Println("Response Body:", string(body))
|
||||
}
|
||||
|
||||
// SignReq 对HTTP请求进行签名
|
||||
func SignReq(req *http.Request, id uint, token string) error {
|
||||
// 步骤一:构造规范化请求
|
||||
var body []byte
|
||||
var err error
|
||||
|
||||
if req.Body != nil {
|
||||
// 读取并保存请求体
|
||||
body, err = io.ReadAll(req.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// 恢复请求体以便后续使用
|
||||
req.Body = io.NopCloser(bytes.NewReader(body))
|
||||
}
|
||||
|
||||
// 规范化路径
|
||||
canonicalPath := req.URL.Path
|
||||
if !strings.HasPrefix(canonicalPath, "/api") {
|
||||
index := strings.Index(canonicalPath, "/api")
|
||||
if index != -1 {
|
||||
canonicalPath = canonicalPath[index:]
|
||||
}
|
||||
}
|
||||
|
||||
canonicalRequest := fmt.Sprintf("%s\n%s\n%s\n%s",
|
||||
req.Method,
|
||||
canonicalPath,
|
||||
req.URL.Query().Encode(),
|
||||
SHA256(string(body)))
|
||||
|
||||
// 步骤二:设置时间戳和构造待签名字符串
|
||||
timestamp := time.Now().Unix()
|
||||
req.Header.Set("X-Timestamp", fmt.Sprintf("%d", timestamp))
|
||||
|
||||
stringToSign := fmt.Sprintf("%s\n%d\n%s",
|
||||
"HMAC-SHA256",
|
||||
timestamp,
|
||||
SHA256(canonicalRequest))
|
||||
|
||||
// 步骤三:计算签名
|
||||
signature := HMACSHA256(stringToSign, token)
|
||||
|
||||
// 步骤四:设置Authorization头
|
||||
authHeader := fmt.Sprintf("HMAC-SHA256 Credential=%d, Signature=%s", id, signature)
|
||||
req.Header.Set("Authorization", authHeader)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func SHA256(str string) string {
|
||||
sum := sha256.Sum256([]byte(str))
|
||||
dst := make([]byte, hex.EncodedLen(len(sum)))
|
||||
hex.Encode(dst, sum[:])
|
||||
return string(dst)
|
||||
}
|
||||
|
||||
func HMACSHA256(data string, secret string) string {
|
||||
h := hmac.New(sha256.New, []byte(secret))
|
||||
h.Write([]byte(data))
|
||||
return hex.EncodeToString(h.Sum(nil))
|
||||
}
|
||||
```
|
||||
|
||||
## PHP 示例
|
||||
|
||||
```php
|
||||
<?php
|
||||
/**
|
||||
* 耗子面板 API 请求示例 (PHP)
|
||||
*/
|
||||
|
||||
function signRequest($method, $url, $body, $id, $token) {
|
||||
// 解析URL并获取路径
|
||||
$parsedUrl = parse_url($url);
|
||||
$path = $parsedUrl['path'];
|
||||
$query = isset($parsedUrl['query']) ? $parsedUrl['query'] : '';
|
||||
|
||||
// 规范化路径
|
||||
$canonicalPath = $path;
|
||||
if (strpos($path, '/api') !== 0) {
|
||||
$apiPos = strpos($path, '/api');
|
||||
if ($apiPos !== false) {
|
||||
$canonicalPath = substr($path, $apiPos);
|
||||
}
|
||||
}
|
||||
|
||||
// 计算请求体的SHA256哈希值
|
||||
$bodySha256 = hash('sha256', $body ?: '');
|
||||
|
||||
// 构造规范化请求
|
||||
$canonicalRequest = implode("\n", [
|
||||
$method,
|
||||
$canonicalPath,
|
||||
$query,
|
||||
$bodySha256
|
||||
]);
|
||||
|
||||
// 获取当前时间戳
|
||||
$timestamp = time();
|
||||
|
||||
// 构造待签名字符串
|
||||
$stringToSign = implode("\n", [
|
||||
'HMAC-SHA256',
|
||||
$timestamp,
|
||||
hash('sha256', $canonicalRequest)
|
||||
]);
|
||||
|
||||
// 计算签名
|
||||
$signature = hash_hmac('sha256', $stringToSign, $token);
|
||||
|
||||
// 返回签名和时间戳
|
||||
return [
|
||||
'timestamp' => $timestamp,
|
||||
'signature' => $signature,
|
||||
'id' => $id
|
||||
];
|
||||
}
|
||||
|
||||
// 示例请求
|
||||
$apiUrl = 'http://example.com/entrance/api/user/info';
|
||||
$method = 'GET';
|
||||
$body = ''; // 对于GET请求,通常没有请求体
|
||||
$id = 16;
|
||||
$token = 'YourSecretToken';
|
||||
|
||||
// 生成签名信息
|
||||
$signingData = signRequest($method, $apiUrl, $body, $id, $token);
|
||||
|
||||
// 准备HTTP请求头
|
||||
$headers = [
|
||||
'Content-Type: application/json',
|
||||
'X-Timestamp: ' . $signingData['timestamp'],
|
||||
'Authorization: HMAC-SHA256 Credential=' . $signingData['id'] . ', Signature=' . $signingData['signature']
|
||||
];
|
||||
|
||||
// 使用cURL发送请求
|
||||
$ch = curl_init($apiUrl);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
|
||||
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method);
|
||||
|
||||
if (!empty($body)) {
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, $body);
|
||||
}
|
||||
|
||||
// 执行请求并获取响应
|
||||
$response = curl_exec($ch);
|
||||
$statusCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
curl_close($ch);
|
||||
|
||||
// 输出结果
|
||||
echo "响应状态码: " . $statusCode . PHP_EOL;
|
||||
echo "响应内容: " . $response . PHP_EOL;
|
||||
```
|
||||
|
||||
## Python 示例
|
||||
|
||||
```python
|
||||
import hashlib
|
||||
import hmac
|
||||
import json
|
||||
import requests
|
||||
import time
|
||||
from urllib.parse import urlparse, parse_qs
|
||||
|
||||
def sha256_hash(text):
|
||||
"""计算字符串的SHA256哈希值"""
|
||||
return hashlib.sha256(text.encode('utf-8')).hexdigest()
|
||||
|
||||
def hmac_sha256(key, message):
|
||||
"""使用HMAC-SHA256算法计算签名"""
|
||||
return hmac.new(key.encode('utf-8'), message.encode('utf-8'), hashlib.sha256).hexdigest()
|
||||
|
||||
def sign_request(method, url, body, user_id, token):
|
||||
"""为API请求生成签名"""
|
||||
# 解析URL
|
||||
parsed_url = urlparse(url)
|
||||
path = parsed_url.path
|
||||
query = parsed_url.query
|
||||
|
||||
# 规范化路径
|
||||
canonical_path = path
|
||||
if not path.startswith('/api'):
|
||||
api_pos = path.find('/api')
|
||||
if api_pos != -1:
|
||||
canonical_path = path[api_pos:]
|
||||
|
||||
# 构造规范化请求
|
||||
body_str = body if body else ""
|
||||
canonical_request = "\n".join([
|
||||
method,
|
||||
canonical_path,
|
||||
query,
|
||||
sha256_hash(body_str)
|
||||
])
|
||||
|
||||
# 获取当前时间戳
|
||||
timestamp = int(time.time())
|
||||
|
||||
# 构造待签名字符串
|
||||
string_to_sign = "\n".join([
|
||||
"HMAC-SHA256",
|
||||
str(timestamp),
|
||||
sha256_hash(canonical_request)
|
||||
])
|
||||
|
||||
# 计算签名
|
||||
signature = hmac_sha256(token, string_to_sign)
|
||||
|
||||
return {
|
||||
"timestamp": timestamp,
|
||||
"signature": signature,
|
||||
"id": user_id
|
||||
}
|
||||
|
||||
# 示例请求
|
||||
api_url = "http://example.com/entrance/api/user/info"
|
||||
method = "GET"
|
||||
body = "" # GET请求通常没有请求体
|
||||
user_id = 16
|
||||
token = "YourSecretToken"
|
||||
|
||||
# 生成签名信息
|
||||
signing_data = sign_request(method, api_url, body, user_id, token)
|
||||
|
||||
# 准备HTTP请求头
|
||||
headers = {
|
||||
"Content-Type": "application/json",
|
||||
"X-Timestamp": str(signing_data["timestamp"]),
|
||||
"Authorization": f"HMAC-SHA256 Credential={signing_data['id']}, Signature={signing_data['signature']}"
|
||||
}
|
||||
|
||||
# 发送请求
|
||||
response = requests.request(
|
||||
method=method,
|
||||
url=api_url,
|
||||
headers=headers,
|
||||
data=body
|
||||
)
|
||||
|
||||
# 输出结果
|
||||
print(f"响应状态码: {response.status_code}")
|
||||
print(f"响应内容: {response.text}")
|
||||
```
|
||||
|
||||
## Java 示例
|
||||
|
||||
```java
|
||||
import java.net.URI;
|
||||
import java.net.http.HttpClient;
|
||||
import java.net.http.HttpRequest;
|
||||
import java.net.http.HttpResponse;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.MessageDigest;
|
||||
import java.time.Instant;
|
||||
import javax.crypto.Mac;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import java.util.Base64;
|
||||
|
||||
/**
|
||||
* 耗子面板 API 请求示例 (Java)
|
||||
*/
|
||||
public class RatPanelApiExample {
|
||||
|
||||
public static void main(String[] args) {
|
||||
try {
|
||||
// 示例请求
|
||||
String apiUrl = "http://example.com/entrance/api/user/info";
|
||||
String method = "GET";
|
||||
String body = ""; // 对于GET请求,通常没有请求体
|
||||
int id = 16;
|
||||
String token = "YourSecretToken";
|
||||
|
||||
// 生成签名信息
|
||||
SigningData signingData = signRequest(method, apiUrl, body, id, token);
|
||||
|
||||
// 准备HTTP请求
|
||||
HttpClient client = HttpClient.newHttpClient();
|
||||
HttpRequest.Builder requestBuilder = HttpRequest.newBuilder()
|
||||
.uri(URI.create(apiUrl))
|
||||
.header("Content-Type", "application/json")
|
||||
.header("X-Timestamp", String.valueOf(signingData.timestamp))
|
||||
.header("Authorization", "HMAC-SHA256 Credential=" + signingData.id +
|
||||
", Signature=" + signingData.signature);
|
||||
|
||||
// 设置请求方法和请求体
|
||||
if (method.equals("GET")) {
|
||||
requestBuilder.GET();
|
||||
} else {
|
||||
requestBuilder.method(method, HttpRequest.BodyPublishers.ofString(body));
|
||||
}
|
||||
|
||||
HttpRequest request = requestBuilder.build();
|
||||
|
||||
// 发送请求
|
||||
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
|
||||
|
||||
// 输出结果
|
||||
System.out.println("响应状态码: " + response.statusCode());
|
||||
System.out.println("响应内容: " + response.body());
|
||||
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
static class SigningData {
|
||||
long timestamp;
|
||||
String signature;
|
||||
int id;
|
||||
|
||||
SigningData(long timestamp, String signature, int id) {
|
||||
this.timestamp = timestamp;
|
||||
this.signature = signature;
|
||||
this.id = id;
|
||||
}
|
||||
}
|
||||
|
||||
public static SigningData signRequest(String method, String url, String body, int id, String token) throws Exception {
|
||||
// 解析URL
|
||||
URI uri = new URI(url);
|
||||
String path = uri.getPath();
|
||||
String query = uri.getQuery() != null ? uri.getQuery() : "";
|
||||
|
||||
// 规范化路径
|
||||
String canonicalPath = path;
|
||||
if (!path.startsWith("/api")) {
|
||||
int apiPos = path.indexOf("/api");
|
||||
if (apiPos != -1) {
|
||||
canonicalPath = path.substring(apiPos);
|
||||
}
|
||||
}
|
||||
|
||||
// 计算请求体的SHA256哈希值
|
||||
String bodySha256 = sha256Hash(body != null ? body : "");
|
||||
|
||||
// 构造规范化请求
|
||||
String canonicalRequest = String.join("\n",
|
||||
method,
|
||||
canonicalPath,
|
||||
query,
|
||||
bodySha256);
|
||||
|
||||
// 获取当前时间戳
|
||||
long timestamp = Instant.now().getEpochSecond();
|
||||
|
||||
// 构造待签名字符串
|
||||
String stringToSign = String.join("\n",
|
||||
"HMAC-SHA256",
|
||||
String.valueOf(timestamp),
|
||||
sha256Hash(canonicalRequest));
|
||||
|
||||
// 计算签名
|
||||
String signature = hmacSha256(token, stringToSign);
|
||||
|
||||
// 返回签名和时间戳
|
||||
return new SigningData(timestamp, signature, id);
|
||||
}
|
||||
|
||||
private static String sha256Hash(String text) throws Exception {
|
||||
MessageDigest digest = MessageDigest.getInstance("SHA-256");
|
||||
byte[] hash = digest.digest(text.getBytes(StandardCharsets.UTF_8));
|
||||
return bytesToHex(hash);
|
||||
}
|
||||
|
||||
private static String hmacSha256(String key, String message) throws Exception {
|
||||
Mac mac = Mac.getInstance("HmacSHA256");
|
||||
SecretKeySpec secretKeySpec = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), "HmacSHA256");
|
||||
mac.init(secretKeySpec);
|
||||
byte[] hash = mac.doFinal(message.getBytes(StandardCharsets.UTF_8));
|
||||
return bytesToHex(hash);
|
||||
}
|
||||
|
||||
private static String bytesToHex(byte[] bytes) {
|
||||
StringBuilder hexString = new StringBuilder();
|
||||
for (byte b : bytes) {
|
||||
String hex = Integer.toHexString(0xff & b);
|
||||
if (hex.length() == 1) {
|
||||
hexString.append('0');
|
||||
}
|
||||
hexString.append(hex);
|
||||
}
|
||||
return hexString.toString();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Node.js 示例
|
||||
|
||||
```javascript
|
||||
const crypto = require('crypto');
|
||||
const axios = require('axios');
|
||||
const url = require('url');
|
||||
|
||||
/**
|
||||
* 计算字符串的SHA256哈希值
|
||||
* @param {string} text 待哈希的字符串
|
||||
* @returns {string} 哈希结果(十六进制)
|
||||
*/
|
||||
function sha256Hash(text) {
|
||||
return crypto.createHash('sha256').update(text || '').digest('hex');
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用HMAC-SHA256算法计算签名
|
||||
* @param {string} key 密钥
|
||||
* @param {string} message 待签名的消息
|
||||
* @returns {string} 签名结果(十六进制)
|
||||
*/
|
||||
function hmacSha256(key, message) {
|
||||
return crypto.createHmac('sha256', key).update(message).digest('hex');
|
||||
}
|
||||
|
||||
/**
|
||||
* 为API请求生成签名
|
||||
* @param {string} method HTTP方法
|
||||
* @param {string} apiUrl API地址
|
||||
* @param {string} body 请求体
|
||||
* @param {number} id 用户ID
|
||||
* @param {string} token 密钥
|
||||
* @returns {object} 包含签名、时间戳和ID的对象
|
||||
*/
|
||||
function signRequest(method, apiUrl, body, id, token) {
|
||||
// 解析URL
|
||||
const parsedUrl = new url.URL(apiUrl);
|
||||
const path = parsedUrl.pathname;
|
||||
const query = parsedUrl.search.slice(1); // 移除开头的'?'
|
||||
|
||||
// 规范化路径
|
||||
let canonicalPath = path;
|
||||
if (!path.startsWith('/api')) {
|
||||
const apiPos = path.indexOf('/api');
|
||||
if (apiPos !== -1) {
|
||||
canonicalPath = path.slice(apiPos);
|
||||
}
|
||||
}
|
||||
|
||||
// 构造规范化请求
|
||||
const canonicalRequest = [
|
||||
method,
|
||||
canonicalPath,
|
||||
query,
|
||||
sha256Hash(body || '')
|
||||
].join('\n');
|
||||
|
||||
// 获取当前时间戳
|
||||
const timestamp = Math.floor(Date.now() / 1000);
|
||||
|
||||
// 构造待签名字符串
|
||||
const stringToSign = [
|
||||
'HMAC-SHA256',
|
||||
timestamp,
|
||||
sha256Hash(canonicalRequest)
|
||||
].join('\n');
|
||||
|
||||
// 计算签名
|
||||
const signature = hmacSha256(token, stringToSign);
|
||||
|
||||
return {
|
||||
timestamp,
|
||||
signature,
|
||||
id
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送API请求
|
||||
*/
|
||||
async function sendApiRequest() {
|
||||
// 示例请求参数
|
||||
const apiUrl = 'http://example.com/entrance/api/user/info';
|
||||
const method = 'GET';
|
||||
const body = ''; // GET请求通常没有请求体
|
||||
const id = 16;
|
||||
const token = 'YourSecretToken';
|
||||
|
||||
try {
|
||||
// 生成签名信息
|
||||
const signingData = signRequest(method, apiUrl, body, id, token);
|
||||
|
||||
// 准备HTTP请求头
|
||||
const headers = {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Timestamp': signingData.timestamp,
|
||||
'Authorization': `HMAC-SHA256 Credential=${signingData.id}, Signature=${signingData.signature}`
|
||||
};
|
||||
|
||||
// 发送请求
|
||||
const response = await axios({
|
||||
method,
|
||||
url: apiUrl,
|
||||
headers,
|
||||
data: body || undefined
|
||||
});
|
||||
|
||||
// 输出结果
|
||||
console.log(`响应状态码: ${response.status}`);
|
||||
console.log(`响应内容: ${JSON.stringify(response.data)}`);
|
||||
|
||||
} catch (error) {
|
||||
console.error('请求出错:', error.message);
|
||||
if (error.response) {
|
||||
console.error(`响应状态码: ${error.response.status}`);
|
||||
console.error(`响应内容: ${JSON.stringify(error.response.data)}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 执行请求
|
||||
sendApiRequest();
|
||||
```
|
||||
|
||||
## 常见响应码
|
||||
|
||||
| HTTP 状态码 | 描述 |
|
||||
| -------- | ------- |
|
||||
| 200 | 请求成功 |
|
||||
| 401 | 身份验证失败 |
|
||||
| 403 | 权限不足 |
|
||||
| 404 | 资源不存在 |
|
||||
| 422 | 请求参数错误 |
|
||||
| 500 | 服务器内部错误 |
|
||||
|
||||
## 安全建议
|
||||
|
||||
1. **保护您的 API 令牌**:不要在客户端代码中硬编码或公开您的 API 令牌
|
||||
2. **定期轮换令牌**:定期更改您的 API 令牌以提高安全性
|
||||
3. **配置 IP 白名单**:在生产环境中使用 IP 白名单限制访问
|
||||
|
||||
## 常见问题解答
|
||||
|
||||
### 签名验证失败
|
||||
|
||||
如果遇到签名验证失败,请检查:
|
||||
|
||||
- 确保使用了正确的 API 令牌和 ID
|
||||
- 检查客户端与服务器的时间是否准确,时间戳偏差大于 300 秒会导致验证失败
|
||||
- 确保请求体在计算签名前后没有被修改
|
||||
- 确保 URL 路径处理正确,注意规范化路径时需要移除入口前缀
|
||||
|
||||
### 请求超时
|
||||
|
||||
- 检查网络连接
|
||||
- 确认服务器状态
|
||||
- 考虑增加客户端的超时设置
|
||||
76
zh_CN/advanced/faq.md
Normal file
76
zh_CN/advanced/faq.md
Normal file
@@ -0,0 +1,76 @@
|
||||
# 配置 QUIC(HTTP3)
|
||||
|
||||
面板目前已支持自动 QUIC 配置,但是出于兼容性考虑,默认未添加 `Alt-Svc` 标头。 浏览器在未检测到 `Alt-Svc` 标头时不会尝试使用 QUIC 连接。
|
||||
|
||||
如果你不使用 CDN,可添加下述配置到网站伪静态中即可让浏览器知晓网站支持并使用 QUIC 连接。
|
||||
|
||||
```nginx
|
||||
add_header Alt-Svc 'h3=":$server_port"; ma=2592000';
|
||||
```
|
||||
|
||||
如果你使用 CDN 或者前端还存在代理服务器,则 QUIC 需要在 CDN / 前端开启。
|
||||
|
||||
如果配置后仍不生效,请检查浏览器版本和 UDP 443 端口的可用性。
|
||||
|
||||
- 根据 Nginx 的 git 提交记录,1.25 版本下所有 QUIC 草案版本已经移除,因此 `Alt-Svc` 无需添加草案版本号。
|
||||
|
||||
# 配置 TLSv1.1 TLSv1
|
||||
|
||||
当前面板 OpenResty 使用 OpenSSL 3.5 版本编译,默认禁用已弃用的 TLSv1.1 TLSv1 协议。
|
||||
|
||||
当然,如果你的业务必须要使用这两个协议的话,可以使用下述 SSL 配置启用。
|
||||
|
||||
```nginx
|
||||
ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3;
|
||||
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES256-SHA256:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA:@SECLEVEL=0;
|
||||
ssl_prefer_server_ciphers on;
|
||||
```
|
||||
|
||||
# 配置反向代理
|
||||
|
||||
面板 v2.4.10+ 自带反向代理配置生成器,你可以通过站点伪静态配置页面的右上角打开使用。
|
||||
|
||||
注意:如果设置反向代理后出现 CSS/JS 等静态资源无法正常加载的问题,请移除站点主配置文件中的**不记录静态文件日志**部分。
|
||||
|
||||
# 配置进程守护
|
||||
|
||||
1. 安装 Supervisor 管理器并打开。
|
||||
2. Supervisor 管理器中创建需要守护的进程(运行用户不建议使用 root)。
|
||||
3. 常见问题:[https://tom.moe/t/supervisor/3112](https://tom.moe/t/supervisor/3112)
|
||||
|
||||
# 配置 IPv6
|
||||
|
||||
如果您想要启用 IPv6 支持,您需要将 `[::]:80` 和 `[:]:443` 添加到网站的监听地址配置。
|
||||
|
||||
# 配置容器镜像加速
|
||||
|
||||
由于一些原因国内可能无法连接到 Docker Hub 拉取容器镜像,因此需要配置镜像加速。
|
||||
|
||||
## 对于 Podman
|
||||
|
||||
在面板打开 Podman 设置页面,导航到注册表配置选项卡。
|
||||
|
||||
滚动到配置文件底部,添加如下配置并保存:
|
||||
|
||||
```
|
||||
[[registry]]
|
||||
location = "docker.io"
|
||||
[[registry.mirror]]
|
||||
location = "docker.1ms.run"
|
||||
```
|
||||
|
||||
其中 docker.1ms.run 为配置的镜像加速地址。 可自行参考其他教程搭建使用。
|
||||
|
||||
## 对于 Docker
|
||||
|
||||
在面板打开 Docker 设置页面,导航到配置选项卡。
|
||||
|
||||
添加如下配置并保存:
|
||||
|
||||
```json
|
||||
{
|
||||
"registry-mirrors": ["https://docker.1ms.run"]
|
||||
}
|
||||
```
|
||||
|
||||
其中 docker.1ms.run 为配置的镜像加速地址。 可自行参考其他教程搭建使用。
|
||||
32
zh_CN/advanced/safe.md
Normal file
32
zh_CN/advanced/safe.md
Normal file
@@ -0,0 +1,32 @@
|
||||
# 安全性建议
|
||||
|
||||
通过以下安全措施,几乎可以杜绝一切被黑/挂马问题。
|
||||
|
||||
### 网站方面
|
||||
|
||||
根据以往经验大多数被黑挂马都是程序漏洞造成的,与面板等环境无关。 为了网站安全,你应该做到:
|
||||
|
||||
1. 不要使用盗版程序、软件,特别是在你无法确定有没有被人加过料的情况下。
|
||||
2. 定期更新网站程序和软件环境,不要怕麻烦使用过时的软件程序,它们的安全性无法保证。
|
||||
3. 网站后台禁止使用弱密码。 密码强烈建议使用随机生成器生成大于 20 位的混合密码并保存在安全位置。 有条件建议开启程序的两步验证(2FA)。
|
||||
4. 设置定时备份全站数据,不要裸奔。
|
||||
5. PHP 默认禁用了部分高危函数,非必要请勿删除。
|
||||
|
||||
### 系统方面
|
||||
|
||||
现代系统出现严重安全漏洞的概率是很低的,但是你仍应该做到:
|
||||
|
||||
1. 定期更新系统软件。 (使用 `yum update` 或 `apt upgrade`)。
|
||||
2. SSH 禁止使用弱密码和默认 22 端口。 密码强烈建议使用随机生成器生成大于 20 位的混合密码并保存在安全位置。 如果可以建议安装 Fail2ban 针对性保护。
|
||||
3. 不要随意给 777 权限和 www 用户的执行权限,可能造成极大安全隐患。
|
||||
4. 如果运营商提供 VNC 管理服务器,也可以考虑关闭 SSH,从源头上解决问题。
|
||||
|
||||
### 面板方面
|
||||
|
||||
面板拥有和 root 一样的权限,管理不当亦会造成严重安全问题。 你应该做到:
|
||||
|
||||
1. 定期更新面板及面板安装的应用。 同时推荐关注我们的频道或者群,以第一时间接收各类更新消息。
|
||||
2. 面板禁止使用弱密码和默认 8888 端口。 密码强烈建议使用随机生成器生成大于 20 位的混合密码并保存在安全位置。
|
||||
3. 建议修改添加面板入口和开启面板 HTTPS,防止被扫描器扫描和中间人攻击。
|
||||
4. 防火墙无必要请不要放行内部服务的端口(Redis 6379、MySQL 3306、PostgreSQL 5432等),可能造成严重安全隐患。 (网站本地连接不需要放行,连不上是程序的问题)。
|
||||
5. 对安全性要求较高的情况下,可以考虑日常停止面板的运行,按需启动(面板停止运行不会影响网站、计划任务等的运行)。
|
||||
@@ -14,6 +14,14 @@ panel-cli
|
||||
|
||||
可根据提示补全需要的命令进行操作。
|
||||
|
||||
例如,要更改用户的密码,您可以使用:
|
||||
|
||||
```bash
|
||||
panel-cli user password haozi 123456
|
||||
```
|
||||
|
||||
这将把用户 `haozi` 的密码更改为 `123456`。
|
||||
|
||||
## 卸载面板
|
||||
|
||||
优先建议备份数据重装系统,这样可以保证系统纯净。
|
||||
|
||||
661
zh_TW/advanced/api.md
Normal file
661
zh_TW/advanced/api.md
Normal file
@@ -0,0 +1,661 @@
|
||||
# API Reference Documentation
|
||||
|
||||
## Overview
|
||||
|
||||
RatPanel provides a secure RESTful interface for interacting with the panel system. All API requests require HMAC-SHA256 signature authentication to ensure the security and integrity of communications.
|
||||
|
||||
## Basic Information
|
||||
|
||||
- **Base URL**: `http(s)://your-panel-domain/{entry}/api/`
|
||||
- **Content Type**: All requests and responses use `application/json`
|
||||
- **Character Encoding**: UTF-8
|
||||
|
||||
## Authentication Mechanism
|
||||
|
||||
The API uses the HMAC-SHA256 signature algorithm for authentication. Each request must include the following HTTP headers:
|
||||
|
||||
| Header Name | 描述 |
|
||||
| --------------- | -------------------------------------------------------------------------------------------------------- |
|
||||
| `Content-Type` | Set to `application/json` |
|
||||
| `X-Timestamp` | Current UNIX timestamp (seconds) |
|
||||
| `Authorization` | Authentication information, format: `HMAC-SHA256 Credential={id}, Signature={signature}` |
|
||||
|
||||
## Signature Algorithm
|
||||
|
||||
The signature process consists of four main steps:
|
||||
|
||||
### 1. Construct Canonical Request
|
||||
|
||||
The canonical request string consists of the following parts, separated by newline characters (\n):
|
||||
|
||||
```
|
||||
HTTP Method
|
||||
Canonical Path
|
||||
Canonical Query String
|
||||
SHA256 Hash of Request Body
|
||||
```
|
||||
|
||||
**Note**: The canonical path should always use the path part starting with `/api/`, ignoring the entry prefix.
|
||||
|
||||
### 2. Construct String to Sign
|
||||
|
||||
The string to sign consists of the following parts, separated by newline characters (\n):
|
||||
|
||||
```
|
||||
"HMAC-SHA256"
|
||||
Timestamp
|
||||
SHA256 Hash of Canonical Request
|
||||
```
|
||||
|
||||
### 3. Calculate Signature
|
||||
|
||||
Calculate HMAC-SHA256 on the string to sign using your token, then convert the result to a hexadecimal string.
|
||||
|
||||
### 4. Construct Authorization Header
|
||||
|
||||
Add the calculated signature to the `Authorization` header:
|
||||
|
||||
```
|
||||
Authorization: HMAC-SHA256 Credential={id}, Signature={signature}
|
||||
```
|
||||
|
||||
## Go Example
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/hmac"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Create a request to get user information
|
||||
req, err := http.NewRequest("GET", "http://example.com/entrance/api/user/info", nil)
|
||||
if err != nil {
|
||||
fmt.Println("Error creating request:", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Set content type
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
// Sign request - pass your user ID and API token
|
||||
if err = SignReq(req, uint(16), "YourSecretToken"); err != nil {
|
||||
fmt.Println("Error signing request:", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Send request
|
||||
client := &http.Client{}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
fmt.Println("Error sending request:", err)
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// Handle response
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
fmt.Println("Error reading response:", err)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Println("Response Status:", resp.Status)
|
||||
fmt.Println("Response Body:", string(body))
|
||||
}
|
||||
|
||||
// SignReq signs an HTTP request
|
||||
func SignReq(req *http.Request, id uint, token string) error {
|
||||
// Step 1: Construct canonical request
|
||||
var body []byte
|
||||
var err error
|
||||
|
||||
if req.Body != nil {
|
||||
// Read and save request body
|
||||
body, err = io.ReadAll(req.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Restore request body for subsequent use
|
||||
req.Body = io.NopCloser(bytes.NewReader(body))
|
||||
}
|
||||
|
||||
// Canonical path
|
||||
canonicalPath := req.URL.Path
|
||||
if !strings.HasPrefix(canonicalPath, "/api") {
|
||||
index := strings.Index(canonicalPath, "/api")
|
||||
if index != -1 {
|
||||
canonicalPath = canonicalPath[index:]
|
||||
}
|
||||
}
|
||||
|
||||
canonicalRequest := fmt.Sprintf("%s\n%s\n%s\n%s",
|
||||
req.Method,
|
||||
canonicalPath,
|
||||
req.URL.Query().Encode(),
|
||||
SHA256(string(body)))
|
||||
|
||||
// Step 2: Set timestamp and construct string to sign
|
||||
timestamp := time.Now().Unix()
|
||||
req.Header.Set("X-Timestamp", fmt.Sprintf("%d", timestamp))
|
||||
|
||||
stringToSign := fmt.Sprintf("%s\n%d\n%s",
|
||||
"HMAC-SHA256",
|
||||
timestamp,
|
||||
SHA256(canonicalRequest))
|
||||
|
||||
// Step 3: Calculate signature
|
||||
signature := HMACSHA256(stringToSign, token)
|
||||
|
||||
// Step 4: Set Authorization header
|
||||
authHeader := fmt.Sprintf("HMAC-SHA256 Credential=%d, Signature=%s", id, signature)
|
||||
req.Header.Set("Authorization", authHeader)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func SHA256(str string) string {
|
||||
sum := sha256.Sum256([]byte(str))
|
||||
dst := make([]byte, hex.EncodedLen(len(sum)))
|
||||
hex.Encode(dst, sum[:])
|
||||
return string(dst)
|
||||
}
|
||||
|
||||
func HMACSHA256(data string, secret string) string {
|
||||
h := hmac.New(sha256.New, []byte(secret))
|
||||
h.Write([]byte(data))
|
||||
return hex.EncodeToString(h.Sum(nil))
|
||||
}
|
||||
```
|
||||
|
||||
## PHP Example
|
||||
|
||||
```php
|
||||
<?php
|
||||
/**
|
||||
* RatPanel API Request Example (PHP)
|
||||
*/
|
||||
|
||||
function signRequest($method, $url, $body, $id, $token) {
|
||||
// Parse URL and get path
|
||||
$parsedUrl = parse_url($url);
|
||||
$path = $parsedUrl['path'];
|
||||
$query = isset($parsedUrl['query']) ? $parsedUrl['query'] : '';
|
||||
|
||||
// Canonical path
|
||||
$canonicalPath = $path;
|
||||
if (strpos($path, '/api') !== 0) {
|
||||
$apiPos = strpos($path, '/api');
|
||||
if ($apiPos !== false) {
|
||||
$canonicalPath = substr($path, $apiPos);
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate SHA256 hash of request body
|
||||
$bodySha256 = hash('sha256', $body ?: '');
|
||||
|
||||
// Construct canonical request
|
||||
$canonicalRequest = implode("\n", [
|
||||
$method,
|
||||
$canonicalPath,
|
||||
$query,
|
||||
$bodySha256
|
||||
]);
|
||||
|
||||
// Get current timestamp
|
||||
$timestamp = time();
|
||||
|
||||
// Construct string to sign
|
||||
$stringToSign = implode("\n", [
|
||||
'HMAC-SHA256',
|
||||
$timestamp,
|
||||
hash('sha256', $canonicalRequest)
|
||||
]);
|
||||
|
||||
// Calculate signature
|
||||
$signature = hash_hmac('sha256', $stringToSign, $token);
|
||||
|
||||
// Return signature and timestamp
|
||||
return [
|
||||
'timestamp' => $timestamp,
|
||||
'signature' => $signature,
|
||||
'id' => $id
|
||||
];
|
||||
}
|
||||
|
||||
// Example request
|
||||
$apiUrl = 'http://example.com/entrance/api/user/info';
|
||||
$method = 'GET';
|
||||
$body = ''; // For GET requests, usually no request body
|
||||
$id = 16;
|
||||
$token = 'YourSecretToken';
|
||||
|
||||
// Generate signature information
|
||||
$signingData = signRequest($method, $apiUrl, $body, $id, $token);
|
||||
|
||||
// Prepare HTTP headers
|
||||
$headers = [
|
||||
'Content-Type: application/json',
|
||||
'X-Timestamp: ' . $signingData['timestamp'],
|
||||
'Authorization: HMAC-SHA256 Credential=' . $signingData['id'] . ', Signature=' . $signingData['signature']
|
||||
];
|
||||
|
||||
// Use cURL to send request
|
||||
$ch = curl_init($apiUrl);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
|
||||
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method);
|
||||
|
||||
if (!empty($body)) {
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, $body);
|
||||
}
|
||||
|
||||
// Execute request and get response
|
||||
$response = curl_exec($ch);
|
||||
$statusCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
curl_close($ch);
|
||||
|
||||
// Output results
|
||||
echo "Response Status Code: " . $statusCode . PHP_EOL;
|
||||
echo "Response Content: " . $response . PHP_EOL;
|
||||
```
|
||||
|
||||
## Python Example
|
||||
|
||||
```python
|
||||
import hashlib
|
||||
import hmac
|
||||
import json
|
||||
import requests
|
||||
import time
|
||||
from urllib.parse import urlparse, parse_qs
|
||||
|
||||
def sha256_hash(text):
|
||||
"""Calculate SHA256 hash of a string"""
|
||||
return hashlib.sha256(text.encode('utf-8')).hexdigest()
|
||||
|
||||
def hmac_sha256(key, message):
|
||||
"""Calculate signature using HMAC-SHA256 algorithm"""
|
||||
return hmac.new(key.encode('utf-8'), message.encode('utf-8'), hashlib.sha256).hexdigest()
|
||||
|
||||
def sign_request(method, url, body, user_id, token):
|
||||
"""Generate signature for API request"""
|
||||
# Parse URL
|
||||
parsed_url = urlparse(url)
|
||||
path = parsed_url.path
|
||||
query = parsed_url.query
|
||||
|
||||
# Canonical path
|
||||
canonical_path = path
|
||||
if not path.startswith('/api'):
|
||||
api_pos = path.find('/api')
|
||||
if api_pos != -1:
|
||||
canonical_path = path[api_pos:]
|
||||
|
||||
# Construct canonical request
|
||||
body_str = body if body else ""
|
||||
canonical_request = "\n".join([
|
||||
method,
|
||||
canonical_path,
|
||||
query,
|
||||
sha256_hash(body_str)
|
||||
])
|
||||
|
||||
# Get current timestamp
|
||||
timestamp = int(time.time())
|
||||
|
||||
# Construct string to sign
|
||||
string_to_sign = "\n".join([
|
||||
"HMAC-SHA256",
|
||||
str(timestamp),
|
||||
sha256_hash(canonical_request)
|
||||
])
|
||||
|
||||
# Calculate signature
|
||||
signature = hmac_sha256(token, string_to_sign)
|
||||
|
||||
return {
|
||||
"timestamp": timestamp,
|
||||
"signature": signature,
|
||||
"id": user_id
|
||||
}
|
||||
|
||||
# Example request
|
||||
api_url = "http://example.com/entrance/api/user/info"
|
||||
method = "GET"
|
||||
body = "" # GET requests typically have no body
|
||||
user_id = 16
|
||||
token = "YourSecretToken"
|
||||
|
||||
# Generate signature information
|
||||
signing_data = sign_request(method, api_url, body, user_id, token)
|
||||
|
||||
# Prepare HTTP headers
|
||||
headers = {
|
||||
"Content-Type": "application/json",
|
||||
"X-Timestamp": str(signing_data["timestamp"]),
|
||||
"Authorization": f"HMAC-SHA256 Credential={signing_data['id']}, Signature={signing_data['signature']}"
|
||||
}
|
||||
|
||||
# Send request
|
||||
response = requests.request(
|
||||
method=method,
|
||||
url=api_url,
|
||||
headers=headers,
|
||||
data=body
|
||||
)
|
||||
|
||||
# Output results
|
||||
print(f"Response Status Code: {response.status_code}")
|
||||
print(f"Response Content: {response.text}")
|
||||
```
|
||||
|
||||
## Java Example
|
||||
|
||||
```java
|
||||
import java.net.URI;
|
||||
import java.net.http.HttpClient;
|
||||
import java.net.http.HttpRequest;
|
||||
import java.net.http.HttpResponse;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.MessageDigest;
|
||||
import java.time.Instant;
|
||||
import javax.crypto.Mac;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import java.util.Base64;
|
||||
|
||||
/**
|
||||
* RatPanel API Request Example (Java)
|
||||
*/
|
||||
public class RatPanelApiExample {
|
||||
|
||||
public static void main(String[] args) {
|
||||
try {
|
||||
// Example request
|
||||
String apiUrl = "http://example.com/entrance/api/user/info";
|
||||
String method = "GET";
|
||||
String body = ""; // For GET requests, usually no request body
|
||||
int id = 16;
|
||||
String token = "YourSecretToken";
|
||||
|
||||
// Generate signature information
|
||||
SigningData signingData = signRequest(method, apiUrl, body, id, token);
|
||||
|
||||
// Prepare HTTP request
|
||||
HttpClient client = HttpClient.newHttpClient();
|
||||
HttpRequest.Builder requestBuilder = HttpRequest.newBuilder()
|
||||
.uri(URI.create(apiUrl))
|
||||
.header("Content-Type", "application/json")
|
||||
.header("X-Timestamp", String.valueOf(signingData.timestamp))
|
||||
.header("Authorization", "HMAC-SHA256 Credential=" + signingData.id +
|
||||
", Signature=" + signingData.signature);
|
||||
|
||||
// Set request method and body
|
||||
if (method.equals("GET")) {
|
||||
requestBuilder.GET();
|
||||
} else {
|
||||
requestBuilder.method(method, HttpRequest.BodyPublishers.ofString(body));
|
||||
}
|
||||
|
||||
HttpRequest request = requestBuilder.build();
|
||||
|
||||
// Send request
|
||||
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
|
||||
|
||||
// Output results
|
||||
System.out.println("Response Status Code: " + response.statusCode());
|
||||
System.out.println("Response Content: " + response.body());
|
||||
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
static class SigningData {
|
||||
long timestamp;
|
||||
String signature;
|
||||
int id;
|
||||
|
||||
SigningData(long timestamp, String signature, int id) {
|
||||
this.timestamp = timestamp;
|
||||
this.signature = signature;
|
||||
this.id = id;
|
||||
}
|
||||
}
|
||||
|
||||
public static SigningData signRequest(String method, String url, String body, int id, String token) throws Exception {
|
||||
// Parse URL
|
||||
URI uri = new URI(url);
|
||||
String path = uri.getPath();
|
||||
String query = uri.getQuery() != null ? uri.getQuery() : "";
|
||||
|
||||
// Canonical path
|
||||
String canonicalPath = path;
|
||||
if (!path.startsWith("/api")) {
|
||||
int apiPos = path.indexOf("/api");
|
||||
if (apiPos != -1) {
|
||||
canonicalPath = path.substring(apiPos);
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate SHA256 hash of request body
|
||||
String bodySha256 = sha256Hash(body != null ? body : "");
|
||||
|
||||
// Construct canonical request
|
||||
String canonicalRequest = String.join("\n",
|
||||
method,
|
||||
canonicalPath,
|
||||
query,
|
||||
bodySha256);
|
||||
|
||||
// Get current timestamp
|
||||
long timestamp = Instant.now().getEpochSecond();
|
||||
|
||||
// Construct string to sign
|
||||
String stringToSign = String.join("\n",
|
||||
"HMAC-SHA256",
|
||||
String.valueOf(timestamp),
|
||||
sha256Hash(canonicalRequest));
|
||||
|
||||
// Calculate signature
|
||||
String signature = hmacSha256(token, stringToSign);
|
||||
|
||||
// Return signature and timestamp
|
||||
return new SigningData(timestamp, signature, id);
|
||||
}
|
||||
|
||||
private static String sha256Hash(String text) throws Exception {
|
||||
MessageDigest digest = MessageDigest.getInstance("SHA-256");
|
||||
byte[] hash = digest.digest(text.getBytes(StandardCharsets.UTF_8));
|
||||
return bytesToHex(hash);
|
||||
}
|
||||
|
||||
private static String hmacSha256(String key, String message) throws Exception {
|
||||
Mac mac = Mac.getInstance("HmacSHA256");
|
||||
SecretKeySpec secretKeySpec = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), "HmacSHA256");
|
||||
mac.init(secretKeySpec);
|
||||
byte[] hash = mac.doFinal(message.getBytes(StandardCharsets.UTF_8));
|
||||
return bytesToHex(hash);
|
||||
}
|
||||
|
||||
private static String bytesToHex(byte[] bytes) {
|
||||
StringBuilder hexString = new StringBuilder();
|
||||
for (byte b : bytes) {
|
||||
String hex = Integer.toHexString(0xff & b);
|
||||
if (hex.length() == 1) {
|
||||
hexString.append('0');
|
||||
}
|
||||
hexString.append(hex);
|
||||
}
|
||||
return hexString.toString();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Node.js Example
|
||||
|
||||
```javascript
|
||||
const crypto = require('crypto');
|
||||
const axios = require('axios');
|
||||
const url = require('url');
|
||||
|
||||
/**
|
||||
* Calculate SHA256 hash of a string
|
||||
* @param {string} text The string to hash
|
||||
* @returns {string} Hash result (hexadecimal)
|
||||
*/
|
||||
function sha256Hash(text) {
|
||||
return crypto.createHash('sha256').update(text || '').digest('hex');
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate signature using HMAC-SHA256 algorithm
|
||||
* @param {string} key The key
|
||||
* @param {string} message The message to sign
|
||||
* @returns {string} Signature result (hexadecimal)
|
||||
*/
|
||||
function hmacSha256(key, message) {
|
||||
return crypto.createHmac('sha256', key).update(message).digest('hex');
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate signature for API request
|
||||
* @param {string} method HTTP method
|
||||
* @param {string} apiUrl API URL
|
||||
* @param {string} body Request body
|
||||
* @param {number} id User ID
|
||||
* @param {string} token Secret key
|
||||
* @returns {object} Object containing signature, timestamp and ID
|
||||
*/
|
||||
function signRequest(method, apiUrl, body, id, token) {
|
||||
// Parse URL
|
||||
const parsedUrl = new url.URL(apiUrl);
|
||||
const path = parsedUrl.pathname;
|
||||
const query = parsedUrl.search.slice(1); // Remove leading '?'
|
||||
|
||||
// Canonical path
|
||||
let canonicalPath = path;
|
||||
if (!path.startsWith('/api')) {
|
||||
const apiPos = path.indexOf('/api');
|
||||
if (apiPos !== -1) {
|
||||
canonicalPath = path.slice(apiPos);
|
||||
}
|
||||
}
|
||||
|
||||
// Construct canonical request
|
||||
const canonicalRequest = [
|
||||
method,
|
||||
canonicalPath,
|
||||
query,
|
||||
sha256Hash(body || '')
|
||||
].join('\n');
|
||||
|
||||
// Get current timestamp
|
||||
const timestamp = Math.floor(Date.now() / 1000);
|
||||
|
||||
// Construct string to sign
|
||||
const stringToSign = [
|
||||
'HMAC-SHA256',
|
||||
timestamp,
|
||||
sha256Hash(canonicalRequest)
|
||||
].join('\n');
|
||||
|
||||
// Calculate signature
|
||||
const signature = hmacSha256(token, stringToSign);
|
||||
|
||||
return {
|
||||
timestamp,
|
||||
signature,
|
||||
id
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Send API request
|
||||
*/
|
||||
async function sendApiRequest() {
|
||||
// Example request parameters
|
||||
const apiUrl = 'http://example.com/entrance/api/user/info';
|
||||
const method = 'GET';
|
||||
const body = ''; // GET requests typically have no body
|
||||
const id = 16;
|
||||
const token = 'YourSecretToken';
|
||||
|
||||
try {
|
||||
// Generate signature information
|
||||
const signingData = signRequest(method, apiUrl, body, id, token);
|
||||
|
||||
// Prepare HTTP headers
|
||||
const headers = {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Timestamp': signingData.timestamp,
|
||||
'Authorization': `HMAC-SHA256 Credential=${signingData.id}, Signature=${signingData.signature}`
|
||||
};
|
||||
|
||||
// Send request
|
||||
const response = await axios({
|
||||
method,
|
||||
url: apiUrl,
|
||||
headers,
|
||||
data: body || undefined
|
||||
});
|
||||
|
||||
// Output results
|
||||
console.log(`Response Status Code: ${response.status}`);
|
||||
console.log(`Response Content: ${JSON.stringify(response.data)}`);
|
||||
|
||||
} catch (error) {
|
||||
console.error('Request Error:', error.message);
|
||||
if (error.response) {
|
||||
console.error(`Response Status Code: ${error.response.status}`);
|
||||
console.error(`Response Content: ${JSON.stringify(error.response.data)}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Execute request
|
||||
sendApiRequest();
|
||||
```
|
||||
|
||||
## Common Response Codes
|
||||
|
||||
| HTTP Status Code | 描述 |
|
||||
| ---------------- | ----------------------- |
|
||||
| 200 | Request successful |
|
||||
| 401 | Authentication failed |
|
||||
| 403 | Permission denied |
|
||||
| 404 | Resource not found |
|
||||
| 422 | Request parameter error |
|
||||
| 500 | Internal server error |
|
||||
|
||||
## Security Recommendations
|
||||
|
||||
1. **Protect Your API Token**: Do not hardcode or expose your API token in client-side code
|
||||
2. **Rotate Tokens Regularly**: Change your API token regularly to enhance security
|
||||
3. **Configure IP Whitelisting**: Use IP whitelisting to restrict access in production environments
|
||||
|
||||
## Frequently Asked Questions
|
||||
|
||||
### Signature Verification Failed
|
||||
|
||||
If you encounter signature verification failures, check:
|
||||
|
||||
- Ensure you are using the correct API token and ID
|
||||
- Check that the client and server times are accurate; timestamp differences greater than 300 seconds will cause verification to fail
|
||||
- Ensure the request body hasn't been modified before or after signature calculation
|
||||
- Ensure the URL path is handled correctly; remember to remove the entry prefix when normalizing the path
|
||||
|
||||
### Request Timeout
|
||||
|
||||
- Check network connection
|
||||
- Confirm server status
|
||||
- Consider increasing the client timeout settings
|
||||
76
zh_TW/advanced/faq.md
Normal file
76
zh_TW/advanced/faq.md
Normal file
@@ -0,0 +1,76 @@
|
||||
# Configure QUIC (HTTP3)
|
||||
|
||||
RatPanel currently supports automatic QUIC configuration, but for compatibility reasons, the `Alt-Svc` header is not added by default. Browsers will not attempt to use QUIC connections without detecting the `Alt-Svc` header.
|
||||
|
||||
If you are not using a CDN, you can add the configuration below to your website's rewrite rules to let browsers know that the website supports and uses QUIC connections.
|
||||
|
||||
```nginx
|
||||
add_header Alt-Svc 'h3=":$server_port"; ma=2592000';
|
||||
```
|
||||
|
||||
If you are using a CDN or there are proxy servers in front, then QUIC needs to be enabled on the CDN / frontend.
|
||||
|
||||
If the configuration still doesn't work, please check your browser version and the availability of UDP port 443.
|
||||
|
||||
- According to Nginx's git commit history, all QUIC draft versions have been removed in version 1.25, so there's no need to add draft version numbers to `Alt-Svc`.
|
||||
|
||||
# Configure TLSv1.1 TLSv1
|
||||
|
||||
The current Panel OpenResty is compiled with OpenSSL 3.5, which by default disables the deprecated TLSv1.1 and TLSv1 protocols.
|
||||
|
||||
Of course, if your business must use these two protocols, you can enable them using the SSL configuration below.
|
||||
|
||||
```nginx
|
||||
ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3;
|
||||
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES256-SHA256:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA:@SECLEVEL=0;
|
||||
ssl_prefer_server_ciphers on;
|
||||
```
|
||||
|
||||
# Configure Reverse Proxy
|
||||
|
||||
RatPanel v2.4.10+ comes with a built-in reverse proxy configuration generator, which you can access through the top right corner of the site rewrite configuration page.
|
||||
|
||||
Note: If you encounter issues with static resources like CSS/JS not loading properly after setting up a reverse proxy, please remove the **Do not log static files** section from the site's main configuration file.
|
||||
|
||||
# Configure Process Monitoring
|
||||
|
||||
1. Install Supervisor manager and open it.
|
||||
2. Create processes that need to be monitored in the Supervisor manager (it's not recommended to use root as the running user).
|
||||
3. Common issues: [https://tom.moe/t/supervisor/3112](https://tom.moe/t/supervisor/3112)
|
||||
|
||||
# Configure IPv6
|
||||
|
||||
If you want to enable IPv6 support, you need to add `[::]:80` and `[::]:443` to the website's listening address configuration.
|
||||
|
||||
# Configure Container Image Acceleration
|
||||
|
||||
Due to certain reasons, domestic users in China may be unable to connect to Docker Hub to pull container images, thus requiring image acceleration configuration.
|
||||
|
||||
## For Podman
|
||||
|
||||
Open the Podman settings page in the Panel, and navigate to the Registry Configuration tab.
|
||||
|
||||
Scroll to the bottom of the configuration file, add the following configuration and save:
|
||||
|
||||
```
|
||||
[[registry]]
|
||||
location = "docker.io"
|
||||
[[registry.mirror]]
|
||||
location = "docker.1ms.run"
|
||||
```
|
||||
|
||||
Where docker.1ms.run is the configured image acceleration address. You can refer to other tutorials to set up and use it.
|
||||
|
||||
## For Docker
|
||||
|
||||
Open the Docker settings page in the Panel, and navigate to the Configuration tab.
|
||||
|
||||
Add the following configuration and save:
|
||||
|
||||
```json
|
||||
{
|
||||
"registry-mirrors": ["https://docker.1ms.run"]
|
||||
}
|
||||
```
|
||||
|
||||
Where https://docker.1ms.run is the configured image acceleration address. You can refer to other tutorials to set up and use it.
|
||||
32
zh_TW/advanced/safe.md
Normal file
32
zh_TW/advanced/safe.md
Normal file
@@ -0,0 +1,32 @@
|
||||
# Security Recommendations
|
||||
|
||||
With the following security measures, almost all hacking/malware issues can be prevented.
|
||||
|
||||
### Website Aspects
|
||||
|
||||
Based on experience, most hacking and malware incidents are caused by program vulnerabilities, unrelated to the Panel or environment. For website security, you should:
|
||||
|
||||
1. Avoid using pirated programs or software, especially when you cannot determine if they have been tampered with.
|
||||
2. Regularly update website programs and software environments; don't use outdated software due to inconvenience, as their security cannot be guaranteed.
|
||||
3. Never use weak passwords for website admin areas. Passwords are strongly recommended to be generated using a random generator with more than 20 mixed characters and stored in a secure location. If possible, enable 2FA for your programs.
|
||||
4. Set up scheduled backups of all site data; don't operate without backups.
|
||||
5. PHP has disabled some high-risk functions by default; don't remove these restrictions unless absolutely necessary.
|
||||
|
||||
### System Aspects
|
||||
|
||||
The probability of serious security vulnerabilities in modern systems is low, but you should still:
|
||||
|
||||
1. Regularly update system software. (Use `yum update` or `apt upgrade`).
|
||||
2. Prohibit weak passwords and the default port 22 for SSH. Passwords are strongly recommended to be generated using a random generator with more than 20 mixed characters and stored in a secure location. If possible, consider installing Fail2ban for targeted protection.
|
||||
3. Don't arbitrarily assign 777 permissions or execution permissions to the www user, as this may cause major security risks.
|
||||
4. If your service provider offers VNC server management, consider disabling SSH to solve the problem at the source.
|
||||
|
||||
### Panel Aspects
|
||||
|
||||
RatPanel has the same privileges as root, and improper management can cause serious security problems. You should:
|
||||
|
||||
1. Regularly update the Panel and applications installed through it. We recommend following our channel or group to receive various update messages promptly.
|
||||
2. Prohibit weak passwords and the default 8888 port for the Panel. Passwords are strongly recommended to be generated using a random generator with more than 20 mixed characters and stored in a secure location.
|
||||
3. Consider modifying the Panel entry point and enabling HTTPS for the Panel to prevent scanner detection and man-in-the-middle attacks.
|
||||
4. Unless necessary, do not allow firewall access to internal service ports (Redis 6379, MySQL 3306, PostgreSQL 5432, etc.), as this may cause serious security risks. (Local website connections don't require firewall access; connection issues are program problems).
|
||||
5. For high-security requirements, consider stopping the Panel operation routinely and starting it only when needed (stopping the Panel will not affect websites, scheduled tasks, etc.).
|
||||
@@ -14,6 +14,14 @@ panel-cli
|
||||
|
||||
Follow the prompts to complete the necessary commands for operation.
|
||||
|
||||
For example, to change a user's password, you can use:
|
||||
|
||||
```bash
|
||||
panel-cli user password haozi 123456
|
||||
```
|
||||
|
||||
This will change the password for the user `haozi` to `123456`.
|
||||
|
||||
## Uninstall Panel
|
||||
|
||||
It is primarily recommended to back up data and reinstall the system, as this ensures a clean system.
|
||||
|
||||
Reference in New Issue
Block a user