mirror of
https://hubproxy.babadafafafafa.cn/https://github.com/1Panel-dev/1Panel.git
synced 2026-09-20 08:03:55 +08:00
137 lines
4.3 KiB
Go
137 lines
4.3 KiB
Go
package alert_webhook
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"net/url"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/1Panel-dev/1Panel/core/app/model"
|
|
"github.com/1Panel-dev/1Panel/core/constant"
|
|
"github.com/1Panel-dev/1Panel/core/global"
|
|
"github.com/1Panel-dev/1Panel/core/utils/encrypt"
|
|
"github.com/google/uuid"
|
|
)
|
|
|
|
const (
|
|
agentKeyPrefix = "agent:v1:"
|
|
secretSchemaV1 = 1
|
|
maxSecretLength = 256 * 1024
|
|
maxSecretHeaders = 64
|
|
maxSecretValueSize = 16 * 1024
|
|
)
|
|
|
|
type secretConfig struct {
|
|
SchemaVersion int `json:"schemaVersion"`
|
|
URL string `json:"url"`
|
|
Headers map[string]string `json:"headers,omitempty"`
|
|
}
|
|
|
|
func ExportSecretPlain(config model.AlertConfig) (string, error) {
|
|
if config.Type != constant.Custom {
|
|
return "", fmt.Errorf("alert config %d is not a custom webhook", config.ID)
|
|
}
|
|
if !strings.HasPrefix(config.SecretConfig, agentKeyPrefix) {
|
|
return "", fmt.Errorf("custom webhook secret is not encrypted with the agent key")
|
|
}
|
|
payload := strings.TrimPrefix(config.SecretConfig, agentKeyPrefix)
|
|
if payload == "" {
|
|
return "", fmt.Errorf("custom webhook secret ciphertext is empty")
|
|
}
|
|
key, err := loadEncryptKey()
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
plainText, err := encrypt.StringDecryptWithKey(payload, key)
|
|
if err != nil {
|
|
return "", fmt.Errorf("decrypt custom webhook secret: %w", err)
|
|
}
|
|
secret, err := decodeAndValidateSecret(plainText)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
canonical, err := json.Marshal(secret)
|
|
if err != nil {
|
|
return "", fmt.Errorf("encode custom webhook secret: %w", err)
|
|
}
|
|
return string(canonical), nil
|
|
}
|
|
|
|
// Read the source Agent key, never the Core configuration or Core database key.
|
|
func loadEncryptKey() (string, error) {
|
|
if global.AgentDB == nil {
|
|
return "", fmt.Errorf("source Agent database is unavailable")
|
|
}
|
|
var setting model.Setting
|
|
if err := global.AgentDB.Where("key = ?", "EncryptKey").First(&setting).Error; err != nil {
|
|
return "", fmt.Errorf("read source Agent encrypt key: %w", err)
|
|
}
|
|
key := strings.TrimSpace(setting.Value)
|
|
if key == "" {
|
|
return "", fmt.Errorf("source Agent encrypt key is empty")
|
|
}
|
|
return key, nil
|
|
}
|
|
|
|
func decodeAndValidateSecret(plainText string) (secretConfig, error) {
|
|
if len(plainText) == 0 || len(plainText) > maxSecretLength {
|
|
return secretConfig{}, fmt.Errorf("custom webhook secret has an invalid size")
|
|
}
|
|
var secret secretConfig
|
|
decoder := json.NewDecoder(strings.NewReader(plainText))
|
|
decoder.DisallowUnknownFields()
|
|
if err := decoder.Decode(&secret); err != nil {
|
|
return secretConfig{}, fmt.Errorf("decode custom webhook secret: %w", err)
|
|
}
|
|
if err := decoder.Decode(&struct{}{}); err != io.EOF {
|
|
return secretConfig{}, fmt.Errorf("decode custom webhook secret: multiple JSON values are not allowed")
|
|
}
|
|
if secret.SchemaVersion != secretSchemaV1 {
|
|
return secretConfig{}, fmt.Errorf("unsupported custom webhook secret schemaVersion: %d", secret.SchemaVersion)
|
|
}
|
|
if err := validateSecretURL(secret.URL); err != nil {
|
|
return secretConfig{}, err
|
|
}
|
|
if len(secret.Headers) > maxSecretHeaders {
|
|
return secretConfig{}, fmt.Errorf("custom webhook secret headers exceed the limit")
|
|
}
|
|
if secret.Headers == nil {
|
|
secret.Headers = map[string]string{}
|
|
}
|
|
for uid, value := range secret.Headers {
|
|
if _, err := uuid.Parse(strings.TrimSpace(uid)); err != nil {
|
|
return secretConfig{}, fmt.Errorf("custom webhook secret header uid must be a UUID")
|
|
}
|
|
if value == "" || len(value) > maxSecretValueSize || strings.ContainsAny(value, "\r\n") {
|
|
return secretConfig{}, fmt.Errorf("custom webhook secret header is invalid")
|
|
}
|
|
}
|
|
return secret, nil
|
|
}
|
|
|
|
func validateSecretURL(rawURL string) error {
|
|
if rawURL == "" {
|
|
return nil
|
|
}
|
|
parsed, err := url.Parse(rawURL)
|
|
if err != nil {
|
|
return fmt.Errorf("invalid custom webhook URL")
|
|
}
|
|
hostname := strings.TrimSuffix(strings.TrimSpace(parsed.Hostname()), ".")
|
|
if (parsed.Scheme != "http" && parsed.Scheme != "https") || parsed.Host == "" || hostname == "" || strings.Contains(hostname, "%") || parsed.User != nil || parsed.Fragment != "" {
|
|
return fmt.Errorf("invalid custom webhook URL")
|
|
}
|
|
if strings.HasSuffix(parsed.Host, ":") {
|
|
return fmt.Errorf("invalid custom webhook URL")
|
|
}
|
|
if port := parsed.Port(); port != "" {
|
|
value, err := strconv.Atoi(port)
|
|
if err != nil || value < 1 || value > 65535 {
|
|
return fmt.Errorf("invalid custom webhook URL")
|
|
}
|
|
}
|
|
return nil
|
|
}
|