mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2026-10-27 08:00:14 +00:00
* feat(mcp): add 1Panel MCP and enhance MCP credential management * feat(i18n): implement MCP document localization and remove legacy code
97 lines
2.3 KiB
Go
97 lines
2.3 KiB
Go
package mcp
|
|
|
|
import (
|
|
"crypto/hmac"
|
|
"crypto/rand"
|
|
"crypto/sha256"
|
|
"encoding/base64"
|
|
"encoding/hex"
|
|
"encoding/json"
|
|
"errors"
|
|
"net/netip"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
const AccessProofHeader = "X-Panel-MCP-Access"
|
|
|
|
var accessProofKey struct {
|
|
sync.Once
|
|
value []byte
|
|
err error
|
|
}
|
|
|
|
type accessProof struct {
|
|
IP string `json:"ip"`
|
|
Token string `json:"token"`
|
|
Expires int64 `json:"expires"`
|
|
}
|
|
|
|
func proofKey() ([]byte, error) {
|
|
accessProofKey.Do(func() {
|
|
accessProofKey.value = make([]byte, 32)
|
|
_, accessProofKey.err = rand.Read(accessProofKey.value)
|
|
})
|
|
return accessProofKey.value, accessProofKey.err
|
|
}
|
|
|
|
func proofToken(token string) string {
|
|
value := sha256.Sum256([]byte(token))
|
|
return hex.EncodeToString(value[:])
|
|
}
|
|
|
|
func IssueAccessProof(token, source string, now time.Time) (string, error) {
|
|
address, err := netip.ParseAddr(source)
|
|
if err != nil || address.Zone() != "" || token == "" {
|
|
return "", errors.New("invalid MCP source proof")
|
|
}
|
|
key, err := proofKey()
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
data, err := json.Marshal(accessProof{IP: address.Unmap().String(), Token: proofToken(token), Expires: now.Add(400 * time.Second).Unix()})
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
mac := hmac.New(sha256.New, key)
|
|
mac.Write(data)
|
|
return base64.RawURLEncoding.EncodeToString(data) + "." + base64.RawURLEncoding.EncodeToString(mac.Sum(nil)), nil
|
|
}
|
|
|
|
func VerifyAccessProof(value, token string, now time.Time) (string, error) {
|
|
if len(value) > 512 {
|
|
return "", Forbidden()
|
|
}
|
|
parts := strings.Split(value, ".")
|
|
if len(parts) != 2 {
|
|
return "", Forbidden()
|
|
}
|
|
data, err := base64.RawURLEncoding.DecodeString(parts[0])
|
|
if err != nil {
|
|
return "", Forbidden()
|
|
}
|
|
signature, err := base64.RawURLEncoding.DecodeString(parts[1])
|
|
if err != nil {
|
|
return "", Forbidden()
|
|
}
|
|
key, err := proofKey()
|
|
if err != nil {
|
|
return "", Forbidden()
|
|
}
|
|
mac := hmac.New(sha256.New, key)
|
|
mac.Write(data)
|
|
if !hmac.Equal(signature, mac.Sum(nil)) {
|
|
return "", Forbidden()
|
|
}
|
|
var proof accessProof
|
|
if json.Unmarshal(data, &proof) != nil || !hmac.Equal([]byte(proof.Token), []byte(proofToken(token))) || now.Unix() >= proof.Expires || proof.Expires > now.Add(401*time.Second).Unix() {
|
|
return "", Forbidden()
|
|
}
|
|
address, err := netip.ParseAddr(proof.IP)
|
|
if err != nil || address.Zone() != "" {
|
|
return "", Forbidden()
|
|
}
|
|
return address.Unmap().String(), nil
|
|
}
|