This commit is contained in:
unknown
2026-02-04 20:27:13 +08:00
commit 3b042d1dad
9410 changed files with 1488147 additions and 0 deletions

5
EdgePlus/go.mod Normal file
View File

@@ -0,0 +1,5 @@
module github.com/TeaOSLab/EdgePlus
go 1.25
require github.com/iwind/TeaGo v0.0.0-20240312020455-6f20b5121caf

2
EdgePlus/go.sum Normal file
View File

@@ -0,0 +1,2 @@
github.com/iwind/TeaGo v0.0.0-20240312020455-6f20b5121caf h1:WA9qgiynESu/DDTnLH6npRI5AK6UL9qwJ2YZ5qhJX5E=
github.com/iwind/TeaGo v0.0.0-20240312020455-6f20b5121caf/go.mod h1:SfqVbWyIPdVflyA6lMgicZzsoGS8pyeLiTRe8/CIpGI=

View File

@@ -0,0 +1,6 @@
package teaconst
const (
PlusKey = "41100c93a65cfb71d5b0672c0d60d7ec"
PlusIV = "70ba69d67bf7e61e17ac565c6093a325"
)

View File

@@ -0,0 +1,41 @@
package encrypt
import (
"github.com/iwind/TeaGo/logs"
)
const (
MagicKey = "f1c8eafb543f03023e97b7be864a4e9b"
)
// 加密特殊信息
func MagicKeyEncode(data []byte) []byte {
method, err := NewMethodInstance("aes-256-cfb", MagicKey, MagicKey[:16])
if err != nil {
logs.Println("[MagicKeyEncode]" + err.Error())
return data
}
dst, err := method.Encrypt(data)
if err != nil {
logs.Println("[MagicKeyEncode]" + err.Error())
return data
}
return dst
}
// 解密特殊信息
func MagicKeyDecode(data []byte) []byte {
method, err := NewMethodInstance("aes-256-cfb", MagicKey, MagicKey[:16])
if err != nil {
logs.Println("[MagicKeyEncode]" + err.Error())
return data
}
src, err := method.Decrypt(data)
if err != nil {
logs.Println("[MagicKeyEncode]" + err.Error())
return data
}
return src
}

View File

@@ -0,0 +1,11 @@
package encrypt
import "testing"
func TestMagicKeyEncode(t *testing.T) {
dst := MagicKeyEncode([]byte("Hello,World"))
t.Log("dst:", string(dst))
src := MagicKeyDecode(dst)
t.Log("src:", string(src))
}

View File

@@ -0,0 +1,12 @@
package encrypt
type MethodInterface interface {
// Init 初始化
Init(key []byte, iv []byte) error
// Encrypt 加密
Encrypt(src []byte) (dst []byte, err error)
// Decrypt 解密
Decrypt(dst []byte) (src []byte, err error)
}

View File

@@ -0,0 +1,73 @@
package encrypt
import (
"bytes"
"crypto/aes"
"crypto/cipher"
)
type AES128CFBMethod struct {
iv []byte
block cipher.Block
}
func (this *AES128CFBMethod) Init(key, iv []byte) error {
// 判断key是否为32长度
l := len(key)
if l > 16 {
key = key[:16]
} else if l < 16 {
key = append(key, bytes.Repeat([]byte{' '}, 16-l)...)
}
// 判断iv长度
l2 := len(iv)
if l2 > aes.BlockSize {
iv = iv[:aes.BlockSize]
} else if l2 < aes.BlockSize {
iv = append(iv, bytes.Repeat([]byte{' '}, aes.BlockSize-l2)...)
}
this.iv = iv
// block
block, err := aes.NewCipher(key)
if err != nil {
return err
}
this.block = block
return nil
}
func (this *AES128CFBMethod) Encrypt(src []byte) (dst []byte, err error) {
if len(src) == 0 {
return
}
defer func() {
err = RecoverMethodPanic(recover())
}()
dst = make([]byte, len(src))
encrypter := cipher.NewCFBEncrypter(this.block, this.iv)
encrypter.XORKeyStream(dst, src)
return
}
func (this *AES128CFBMethod) Decrypt(dst []byte) (src []byte, err error) {
if len(dst) == 0 {
return
}
defer func() {
err = RecoverMethodPanic(recover())
}()
src = make([]byte, len(dst))
encrypter := cipher.NewCFBDecrypter(this.block, this.iv)
encrypter.XORKeyStream(src, dst)
return
}

View File

@@ -0,0 +1,92 @@
package encrypt
import (
"runtime"
"strings"
"testing"
)
func TestAES128CFBMethod_Encrypt(t *testing.T) {
method, err := NewMethodInstance("aes-128-cfb", "abc", "123")
if err != nil {
t.Fatal(err)
}
src := []byte("Hello, World")
dst, err := method.Encrypt(src)
if err != nil {
t.Fatal(err)
}
dst = dst[:len(src)]
t.Log("dst:", string(dst))
src = make([]byte, len(src))
src, err = method.Decrypt(dst)
if err != nil {
t.Fatal(err)
}
t.Log("src:", string(src))
}
func TestAES128CFBMethod_Encrypt2(t *testing.T) {
method, err := NewMethodInstance("aes-128-cfb", "abc", "123")
if err != nil {
t.Fatal(err)
}
sources := [][]byte{}
{
a := []byte{1}
_, err = method.Encrypt(a)
if err != nil {
t.Fatal(err)
}
}
for i := 0; i < 10; i++ {
src := []byte(strings.Repeat("Hello", 1))
dst, err := method.Encrypt(src)
if err != nil {
t.Fatal(err)
}
sources = append(sources, dst)
}
{
a := []byte{1}
_, err = method.Decrypt(a)
if err != nil {
t.Fatal(err)
}
}
for _, dst := range sources {
dst2 := append([]byte{}, dst...)
src2 := make([]byte, len(dst2))
src2, err := method.Decrypt(dst2)
if err != nil {
t.Fatal(err)
}
t.Log(string(src2))
}
}
func BenchmarkAES128CFBMethod_Encrypt(b *testing.B) {
runtime.GOMAXPROCS(1)
method, err := NewMethodInstance("aes-128-cfb", "abc", "123")
if err != nil {
b.Fatal(err)
}
src := []byte(strings.Repeat("Hello", 1024))
for i := 0; i < b.N; i++ {
dst, err := method.Encrypt(src)
if err != nil {
b.Fatal(err)
}
_ = dst
}
}

View File

@@ -0,0 +1,74 @@
package encrypt
import (
"bytes"
"crypto/aes"
"crypto/cipher"
)
type AES192CFBMethod struct {
block cipher.Block
iv []byte
}
func (this *AES192CFBMethod) Init(key, iv []byte) error {
// 判断key是否为24长度
l := len(key)
if l > 24 {
key = key[:24]
} else if l < 24 {
key = append(key, bytes.Repeat([]byte{' '}, 24-l)...)
}
block, err := aes.NewCipher(key)
if err != nil {
return err
}
this.block = block
// 判断iv长度
l2 := len(iv)
if l2 > aes.BlockSize {
iv = iv[:aes.BlockSize]
} else if l2 < aes.BlockSize {
iv = append(iv, bytes.Repeat([]byte{' '}, aes.BlockSize-l2)...)
}
this.iv = iv
return nil
}
func (this *AES192CFBMethod) Encrypt(src []byte) (dst []byte, err error) {
if len(src) == 0 {
return
}
defer func() {
err = RecoverMethodPanic(recover())
}()
dst = make([]byte, len(src))
encrypter := cipher.NewCFBEncrypter(this.block, this.iv)
encrypter.XORKeyStream(dst, src)
return
}
func (this *AES192CFBMethod) Decrypt(dst []byte) (src []byte, err error) {
if len(dst) == 0 {
return
}
defer func() {
err = RecoverMethodPanic(recover())
}()
src = make([]byte, len(dst))
decrypter := cipher.NewCFBDecrypter(this.block, this.iv)
decrypter.XORKeyStream(src, dst)
return
}

View File

@@ -0,0 +1,45 @@
package encrypt
import (
"runtime"
"strings"
"testing"
)
func TestAES192CFBMethod_Encrypt(t *testing.T) {
method, err := NewMethodInstance("aes-192-cfb", "abc", "123")
if err != nil {
t.Fatal(err)
}
src := []byte("Hello, World")
dst, err := method.Encrypt(src)
if err != nil {
t.Fatal(err)
}
dst = dst[:len(src)]
t.Log("dst:", string(dst))
src, err = method.Decrypt(dst)
if err != nil {
t.Fatal(err)
}
t.Log("src:", string(src))
}
func BenchmarkAES192CFBMethod_Encrypt(b *testing.B) {
runtime.GOMAXPROCS(1)
method, err := NewMethodInstance("aes-192-cfb", "abc", "123")
if err != nil {
b.Fatal(err)
}
src := []byte(strings.Repeat("Hello", 1024))
for i := 0; i < b.N; i++ {
dst, err := method.Encrypt(src)
if err != nil {
b.Fatal(err)
}
_ = dst
}
}

View File

@@ -0,0 +1,72 @@
package encrypt
import (
"bytes"
"crypto/aes"
"crypto/cipher"
)
type AES256CFBMethod struct {
block cipher.Block
iv []byte
}
func (this *AES256CFBMethod) Init(key, iv []byte) error {
// 判断key是否为32长度
l := len(key)
if l > 32 {
key = key[:32]
} else if l < 32 {
key = append(key, bytes.Repeat([]byte{' '}, 32-l)...)
}
block, err := aes.NewCipher(key)
if err != nil {
return err
}
this.block = block
// 判断iv长度
l2 := len(iv)
if l2 > aes.BlockSize {
iv = iv[:aes.BlockSize]
} else if l2 < aes.BlockSize {
iv = append(iv, bytes.Repeat([]byte{' '}, aes.BlockSize-l2)...)
}
this.iv = iv
return nil
}
func (this *AES256CFBMethod) Encrypt(src []byte) (dst []byte, err error) {
if len(src) == 0 {
return
}
defer func() {
err = RecoverMethodPanic(recover())
}()
dst = make([]byte, len(src))
encrypter := cipher.NewCFBEncrypter(this.block, this.iv)
encrypter.XORKeyStream(dst, src)
return
}
func (this *AES256CFBMethod) Decrypt(dst []byte) (src []byte, err error) {
if len(dst) == 0 {
return
}
defer func() {
err = RecoverMethodPanic(recover())
}()
src = make([]byte, len(dst))
decrypter := cipher.NewCFBDecrypter(this.block, this.iv)
decrypter.XORKeyStream(src, dst)
return
}

View File

@@ -0,0 +1,42 @@
package encrypt
import "testing"
func TestAES256CFBMethod_Encrypt(t *testing.T) {
method, err := NewMethodInstance("aes-256-cfb", "abc", "123")
if err != nil {
t.Fatal(err)
}
src := []byte("Hello, World")
dst, err := method.Encrypt(src)
if err != nil {
t.Fatal(err)
}
dst = dst[:len(src)]
t.Log("dst:", string(dst))
src, err = method.Decrypt(dst)
if err != nil {
t.Fatal(err)
}
t.Log("src:", string(src))
}
func TestAES256CFBMethod_Encrypt2(t *testing.T) {
method, err := NewMethodInstance("aes-256-cfb", "abc", "123")
if err != nil {
t.Fatal(err)
}
src := []byte("Hello, World")
dst, err := method.Encrypt(src)
if err != nil {
t.Fatal(err)
}
t.Log("dst:", string(dst))
src, err = method.Decrypt(dst)
if err != nil {
t.Fatal(err)
}
t.Log("src:", string(src))
}

View File

@@ -0,0 +1,26 @@
package encrypt
type RawMethod struct {
}
func (this *RawMethod) Init(key, iv []byte) error {
return nil
}
func (this *RawMethod) Encrypt(src []byte) (dst []byte, err error) {
if len(src) == 0 {
return
}
dst = make([]byte, len(src))
copy(dst, src)
return
}
func (this *RawMethod) Decrypt(dst []byte) (src []byte, err error) {
if len(dst) == 0 {
return
}
src = make([]byte, len(dst))
copy(src, dst)
return
}

View File

@@ -0,0 +1,23 @@
package encrypt
import "testing"
func TestRawMethod_Encrypt(t *testing.T) {
method, err := NewMethodInstance("raw", "abc", "123")
if err != nil {
t.Fatal(err)
}
src := []byte("Hello, World")
dst, err := method.Encrypt(src)
if err != nil {
t.Fatal(err)
}
dst = dst[:len(src)]
t.Log("dst:", string(dst))
src, err = method.Decrypt(dst)
if err != nil {
t.Fatal(err)
}
t.Log("src:", string(src))
}

View File

@@ -0,0 +1,43 @@
package encrypt
import (
"errors"
"reflect"
)
var methods = map[string]reflect.Type{
"raw": reflect.TypeOf(new(RawMethod)).Elem(),
"aes-128-cfb": reflect.TypeOf(new(AES128CFBMethod)).Elem(),
"aes-192-cfb": reflect.TypeOf(new(AES192CFBMethod)).Elem(),
"aes-256-cfb": reflect.TypeOf(new(AES256CFBMethod)).Elem(),
}
func NewMethodInstance(method string, key string, iv string) (MethodInterface, error) {
valueType, ok := methods[method]
if !ok {
return nil, errors.New("method '" + method + "' not found")
}
instance, ok := reflect.New(valueType).Interface().(MethodInterface)
if !ok {
return nil, errors.New("method '" + method + "' must implement MethodInterface")
}
err := instance.Init([]byte(key), []byte(iv))
return instance, err
}
func RecoverMethodPanic(err interface{}) error {
if err != nil {
s, ok := err.(string)
if ok {
return errors.New(s)
}
e, ok := err.(error)
if ok {
return e
}
return errors.New("unknown error")
}
return nil
}

View File

@@ -0,0 +1,8 @@
package encrypt
import "testing"
func TestFindMethodInstance(t *testing.T) {
t.Log(NewMethodInstance("a", "b", ""))
t.Log(NewMethodInstance("aes-256-cfb", "123456", ""))
}

162
EdgePlus/pkg/utils/cmd.go Normal file
View File

@@ -0,0 +1,162 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
package utils
import (
"bytes"
"context"
"os"
"os/exec"
"strings"
"time"
)
type Cmd struct {
name string
args []string
env []string
dir string
ctx context.Context
timeout time.Duration
cancelFunc func()
captureStdout bool
captureStderr bool
stdout *bytes.Buffer
stderr *bytes.Buffer
rawCmd *exec.Cmd
}
func NewCmd(name string, args ...string) *Cmd {
return &Cmd{
name: name,
args: args,
}
}
func NewTimeoutCmd(timeout time.Duration, name string, args ...string) *Cmd {
return (&Cmd{
name: name,
args: args,
}).WithTimeout(timeout)
}
func (this *Cmd) WithTimeout(timeout time.Duration) *Cmd {
this.timeout = timeout
ctx, cancelFunc := context.WithTimeout(context.Background(), timeout)
this.ctx = ctx
this.cancelFunc = cancelFunc
return this
}
func (this *Cmd) WithStdout() *Cmd {
this.captureStdout = true
return this
}
func (this *Cmd) WithStderr() *Cmd {
this.captureStderr = true
return this
}
func (this *Cmd) WithEnv(env []string) *Cmd {
this.env = env
return this
}
func (this *Cmd) WithDir(dir string) *Cmd {
this.dir = dir
return this
}
func (this *Cmd) Start() error {
var cmd = this.compose()
return cmd.Start()
}
func (this *Cmd) Wait() error {
var cmd = this.compose()
return cmd.Wait()
}
func (this *Cmd) Run() error {
if this.cancelFunc != nil {
defer this.cancelFunc()
}
var cmd = this.compose()
return cmd.Run()
}
func (this *Cmd) RawStdout() string {
if this.stdout != nil {
return this.stdout.String()
}
return ""
}
func (this *Cmd) Stdout() string {
return strings.TrimSpace(this.RawStdout())
}
func (this *Cmd) RawStderr() string {
if this.stderr != nil {
return this.stderr.String()
}
return ""
}
func (this *Cmd) Stderr() string {
return strings.TrimSpace(this.RawStderr())
}
func (this *Cmd) String() string {
if this.rawCmd != nil {
return this.rawCmd.String()
}
var newCmd = exec.Command(this.name, this.args...)
return newCmd.String()
}
func (this *Cmd) Process() *os.Process {
if this.rawCmd != nil {
return this.rawCmd.Process
}
return nil
}
func (this *Cmd) compose() *exec.Cmd {
if this.rawCmd != nil {
return this.rawCmd
}
if this.ctx != nil {
this.rawCmd = exec.CommandContext(this.ctx, this.name, this.args...)
} else {
this.rawCmd = exec.Command(this.name, this.args...)
}
if this.env != nil {
this.rawCmd.Env = this.env
}
if len(this.dir) > 0 {
this.rawCmd.Dir = this.dir
}
if this.captureStdout {
this.stdout = &bytes.Buffer{}
this.rawCmd.Stdout = this.stdout
}
if this.captureStderr {
this.stderr = &bytes.Buffer{}
this.rawCmd.Stderr = this.stderr
}
return this.rawCmd
}

View File

@@ -0,0 +1,98 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package utils
type ComponentCode = string
const (
ComponentCodeUser ComponentCode = "user"
ComponentCodeScheduling ComponentCode = "scheduling"
ComponentCodeMonitor ComponentCode = "monitor"
ComponentCodeLog ComponentCode = "log"
ComponentCodeReporter ComponentCode = "reporter"
ComponentCodePlan ComponentCode = "plan"
ComponentCodeFinance ComponentCode = "finance"
ComponentCodeNS ComponentCode = "ns"
ComponentCodeL2Node ComponentCode = "l2node"
ComponentCodeTicket ComponentCode = "ticket"
ComponentCodeAntiDDoS ComponentCode = "antiDDoS"
ComponentCodeCloudNative ComponentCode = "cloudNative"
)
type Edition = string
const (
EditionBasic Edition = "basic" // 个人商业版
EditionPro Edition = "pro" // 专业版
EditionEnt Edition = "ent" // 企业版
EditionMax Edition = "max" // [待命名]
EditionUltra Edition = "ultra" // 旗舰版
)
type ComponentDefinition struct {
Name string `json:"name"`
Code ComponentCode `json:"code"`
Description string `json:"description"`
}
func FindAllComponents() []*ComponentDefinition {
return []*ComponentDefinition{
{
Name: "多租户",
Code: ComponentCodeUser,
},
{
Name: "智能调度",
Code: ComponentCodeScheduling,
},
{
Name: "监控",
Code: ComponentCodeMonitor,
},
{
Name: "日志",
Code: ComponentCodeLog,
},
{
Name: "区域监控",
Code: ComponentCodeReporter,
},
{
Name: "套餐",
Code: ComponentCodePlan,
},
{
Name: "财务",
Code: ComponentCodeFinance,
},
{
Name: "智能DNS",
Code: ComponentCodeNS,
},
{
Name: "L2节点",
Code: ComponentCodeL2Node,
},
{
Name: "工单系统",
Code: ComponentCodeTicket,
},
{
Name: "高防IP",
Code: ComponentCodeAntiDDoS,
},
/**{
Name: "云原生部署",
Code: ComponentCodeCloudNative,
},**/
}
}
func CheckComponent(code string) bool {
for _, c := range FindAllComponents() {
if c.Code == code {
return true
}
}
return false
}

View File

@@ -0,0 +1,82 @@
// Copyright 2023 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
package utils
type EditionDefinition struct {
Name string `json:"name"`
Code ComponentCode `json:"code"`
MaxNodes int `json:"maxNodes"`
Description string `json:"description"`
}
func FindAllEditions() []*EditionDefinition {
return []*EditionDefinition{
{
Name: "个人商业版",
Code: EditionBasic,
MaxNodes: 20,
},
{
Name: "专业版",
Code: EditionPro,
MaxNodes: 100,
},
{
Name: "企业版",
Code: EditionEnt,
MaxNodes: 500,
},
{
Name: "豪华版",
Code: EditionMax,
MaxNodes: 1000, // TODO 未定
},
{
Name: "旗舰版",
Code: EditionUltra,
MaxNodes: 1000,
},
}
}
func CheckEdition(edition Edition) bool {
for _, e := range FindAllEditions() {
if e.Code == edition {
return true
}
}
return false
}
func CompareEdition(edition1 Edition, edition2 Edition) int {
var index1 = -1
var index2 = -1
for index, edition := range FindAllEditions() {
if edition.Code == edition1 {
index1 = index
}
if edition.Code == edition2 {
index2 = index
}
}
if index2 > index1 {
return -1
}
if index2 == index1 {
return 0
}
return 1
}
func EditionName(edition Edition) string {
if len(edition) == 0 {
return ""
}
for _, e := range FindAllEditions() {
if e.Code == edition {
return e.Name
}
}
return ""
}

View File

@@ -0,0 +1,14 @@
// Copyright 2023 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
package utils
import "testing"
func TestCompareEdition(t *testing.T) {
t.Log(CompareEdition("", ""))
t.Log(CompareEdition("", "pro"))
t.Log(CompareEdition("basic", "pro"))
t.Log(CompareEdition("pro", "pro"))
t.Log(CompareEdition("ent", "pro"))
t.Log(CompareEdition("pro", "basic"))
}

View File

@@ -0,0 +1,130 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package utils
import (
"bytes"
"encoding/base64"
"encoding/json"
"errors"
teaconst "github.com/TeaOSLab/EdgePlus/pkg/const"
"github.com/TeaOSLab/EdgePlus/pkg/encrypt"
"github.com/iwind/TeaGo/maps"
"time"
)
// Encode 加密
func Encode(data []byte) (string, error) {
instance, err := encrypt.NewMethodInstance("aes-256-cfb", teaconst.PlusKey, teaconst.PlusIV)
if err != nil {
return "", errors.New("不支持选择的加密方式")
}
dist, err := instance.Encrypt(data)
if err != nil {
return "", errors.New("加密失败:" + err.Error())
}
return base64.StdEncoding.EncodeToString(dist), nil
}
// EncodeMap 加密Map
func EncodeMap(m maps.Map) (string, error) {
m["updatedAt"] = time.Now().Unix() // 用来校验Authority服务是否已经更新
data, err := json.Marshal(m)
if err != nil {
return "", err
}
return Encode(data)
}
// DecodeData 解密
func DecodeData(data []byte) (maps.Map, error) {
instance, err := encrypt.NewMethodInstance("aes-256-cfb", teaconst.PlusKey, teaconst.PlusIV)
if err != nil {
return nil, errors.New("encrypt method not supported")
}
source, err := base64.StdEncoding.DecodeString(string(bytes.TrimSpace(data)))
if err != nil {
return nil, errors.New("decode key failed: base64 decode failed: " + err.Error())
}
dist, err := instance.Decrypt(source)
if err != nil {
return nil, errors.New("decode key failed: decrypt failed: " + err.Error())
}
var m = maps.Map{}
err = json.Unmarshal(dist, &m)
if err != nil {
return nil, errors.New("decode key failed: decode json failed: " + err.Error())
}
return m, nil
}
func Decode(data []byte) (maps.Map, error) {
m, err := DecodeData(data)
if err != nil {
return nil, err
}
// 控制 STILL 用户权限
if m.GetString("company") == "STILL" {
m["components"] = []ComponentCode{
ComponentCodeLog,
ComponentCodeNS,
ComponentCodeUser,
}
}
if len(m.GetString("dayFrom")) == 0 || len(m.GetString("dayTo")) == 0 || m.GetInt("nodes") <= 0 {
return nil, errors.New("invalid key")
}
return m, nil
}
// EncodeKey 加密Key
func EncodeKey(key *Key) (string, error) {
key.UpdatedAt = time.Now().Unix() // 用来校验Authority服务是否已经更新
data, err := json.Marshal(key)
if err != nil {
return "", err
}
return Encode(data)
}
// DecodeKey 解密Key
func DecodeKey(data []byte) (*Key, error) {
instance, err := encrypt.NewMethodInstance("aes-256-cfb", teaconst.PlusKey, teaconst.PlusIV)
if err != nil {
return nil, errors.New("encrypt method not supported")
}
source, err := base64.StdEncoding.DecodeString(string(bytes.TrimSpace(data)))
if err != nil {
return nil, errors.New("decode key failed: base64 decode failed: " + err.Error())
}
dist, err := instance.Decrypt(source)
if err != nil {
return nil, errors.New("decode key failed: decrypt failed: " + err.Error())
}
var result = &Key{}
err = json.Unmarshal(dist, result)
if err != nil {
return nil, errors.New("decode key failed: " + err.Error())
}
// 这里不能限制节点,因为以往有不限节点的授权
if len(result.DayFrom) == 0 || len(result.DayTo) == 0 {
return nil, errors.New("invalid key")
}
// 控制 STILL 用户权限
if result.Company == "STILL" {
result.Components = []ComponentCode{
ComponentCodeLog,
ComponentCodeNS,
ComponentCodeUser,
}
}
return result, nil
}

View File

@@ -0,0 +1,46 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package utils
import (
"github.com/iwind/TeaGo/logs"
"github.com/iwind/TeaGo/maps"
"testing"
)
func TestEncodeMap(t *testing.T) {
{
t.Log(Encode([]byte("123")))
}
{
s, err := EncodeMap(maps.Map{"a": 1})
if err != nil {
t.Fatal(err)
}
t.Log(s)
t.Log(Decode([]byte(s)))
}
}
func TestEncodeKey(t *testing.T) {
var key = &Key{
DayFrom: "2020-10-10",
DayTo: "2022-10-10",
MacAddresses: []string{"*"},
Hostname: "web001",
Company: "STILL",
Nodes: 10,
}
encodedString, err := EncodeKey(key)
if err != nil {
t.Fatal(err)
}
t.Log("encoded:", encodedString)
key, err = DecodeKey([]byte(encodedString))
if err != nil {
t.Fatal(err)
}
logs.PrintAsJSON(key, t)
}

25
EdgePlus/pkg/utils/key.go Normal file
View File

@@ -0,0 +1,25 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package utils
import timeutil "github.com/iwind/TeaGo/utils/time"
type Key struct {
Id string `json:"id"` // 用户ID
DayFrom string `json:"dayFrom"` // 开始日期
DayTo string `json:"dayTo"` // 结束日期
MacAddresses []string `json:"macAddresses"` // MAC地址老的授权方式
RequestCode string `json:"requestCode"` // 授权请求码
Hostname string `json:"hostname"` // 主机名
Company string `json:"company"` // 公司名
Nodes int `json:"nodes"` // 节点数
UpdatedAt int64 `json:"updatedAt"` // 更新时间
Components []ComponentCode `json:"components"` // 组件
Edition Edition `json:"edition"` // 授权版本
Email string `json:"email"` // 联系人邮箱
Method Method `json:"method"` // 验证方法
}
func (this *Key) IsValid() bool {
return this.DayTo >= timeutil.Format("Y-m-d")
}

View File

@@ -0,0 +1,24 @@
// Copyright 2023 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
package utils
type Method = string
const (
MethodLocal Method = "local"
MethodRemote Method = "remote"
)
func IsValidMethod(method string) bool {
return method == MethodLocal || method == MethodRemote
}
func MethodName(method Method) string {
switch method {
case MethodLocal:
return "离线"
case MethodRemote:
return "远程"
}
return "离线"
}

View File

@@ -0,0 +1,288 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
package utils
import (
"bytes"
"encoding/json"
"errors"
"github.com/iwind/TeaGo/lists"
stringutil "github.com/iwind/TeaGo/utils/string"
"net"
"os"
"regexp"
"runtime"
"sort"
"strings"
"time"
)
// RequestKey 申请码
type RequestKey struct {
MacAddresses []string `json:"macAddresses"` // MAC 排序后内容
MachineId string `json:"machineId"` // /etc/machine-id
HardwareUUID string `json:"hardwareUUID"` // hardware disk uuid
}
// GenerateRequestKey 生成请求Key
func GenerateRequestKey() (*RequestKey, error) {
// mac addresses
netInterfaces, err := findAllNetInterfaces()
if err != nil {
return nil, errors.New("could not generate request key (code: 001)")
}
var macAddrs = []string{}
for _, netInterface := range netInterfaces {
var macAddr = strings.TrimSpace(netInterface.HardwareAddr.String())
if len(macAddr) == 0 {
continue
}
if !lists.ContainsString(macAddrs, macAddr) {
macAddrs = append(macAddrs, macAddr)
}
}
if len(macAddrs) == 0 {
return nil, errors.New("could not generate request key (code: 002)")
}
sort.Strings(macAddrs)
// machine id
var machineId = ""
var machineIdFile = "/etc/machine-id"
stat, err := os.Stat(machineIdFile)
if err == nil && !stat.IsDir() {
data, err := os.ReadFile(machineIdFile)
data = bytes.TrimSpace(data)
if err == nil && len(data) <= 32 {
machineId = string(data)
}
}
return &RequestKey{
MacAddresses: macAddrs,
MachineId: machineId,
HardwareUUID: generateHardwareUUID(),
}, nil
}
// GenerateRequestCode 生成请求Key代码
func GenerateRequestCode() (string, error) {
key, err := GenerateRequestKey()
if err != nil {
return "", err
}
keyJSON, err := json.Marshal(key)
if err != nil {
return "", errors.New("could not generate request code (code: 001)")
}
return Encode(keyJSON)
}
// DecodeRequestCode 解析请求Key代码
func DecodeRequestCode(requestCode string) (*RequestKey, error) {
requestCode = regexp.MustCompile(`\s+`).ReplaceAllString(requestCode, "")
if requestCode == "*" {
return &RequestKey{
MacAddresses: nil,
MachineId: "",
}, nil
}
m, err := DecodeData([]byte(requestCode))
if err != nil {
return nil, err
}
jsonData, err := json.Marshal(m)
if err != nil {
return nil, err
}
var key = &RequestKey{}
err = json.Unmarshal(jsonData, key)
return key, err
}
// ValidateRequestCode 校验请求Key代码
func ValidateRequestCode(requestCode string) (ok bool, errorCode string) {
requestCode = regexp.MustCompile(`\s+`).ReplaceAllString(requestCode, "")
if requestCode == "*" {
return true, ""
}
key, err := DecodeRequestCode(requestCode)
if err != nil {
return false, "001"
}
// check machine id
if len(key.MachineId) > 0 {
// machine id
var machineId = ""
var machineIdFile = "/etc/machine-id"
stat, err := os.Stat(machineIdFile)
if err == nil && !stat.IsDir() {
data, err := os.ReadFile(machineIdFile)
data = bytes.TrimSpace(data)
if err == nil && len(data) <= 32 {
machineId = string(data)
}
}
if machineId != key.MachineId {
return false, "004"
}
}
// hardware uuid
if len(key.HardwareUUID) > 0 && key.HardwareUUID == generateHardwareUUID() {
return true, ""
}
// mac addresses
netInterfaces, err := findAllNetInterfaces()
if err != nil {
return false, "002"
}
// remove net interfaces related to docker
{
var cmd = NewTimeoutCmd(5*time.Second, "docker", "network", "ls")
cmd.WithStdout()
err = cmd.Run()
if err == nil {
var dockerIdRegexp = regexp.MustCompile(`^[0-9a-f]{12,}$`)
var spaceRegexp = regexp.MustCompile(`\s+`)
var stdoutLines = strings.Split(cmd.Stdout(), "\n")
var dockerInterfaceIds []string
var dockerInterfaceNames []string
for _, line := range stdoutLines {
var pieces = spaceRegexp.Split(strings.TrimSpace(line), -1)
if len(pieces) <= 3 {
continue
}
var piece0 = strings.TrimSpace(pieces[0])
if !dockerIdRegexp.MatchString(piece0) {
continue
}
dockerInterfaceIds = append(dockerInterfaceIds, piece0)
var piece1 = strings.TrimSpace(pieces[1])
if len(piece1) > 0 {
dockerInterfaceNames = append(dockerInterfaceNames, piece1)
}
}
var newInterfaces []net.Interface
for _, i := range netInterfaces {
var skip bool
for _, dockerInterfaceId := range dockerInterfaceIds {
if strings.HasSuffix(i.Name, dockerInterfaceId) {
skip = true
break
}
}
if !skip {
skip = lists.ContainsString(dockerInterfaceNames, i.Name)
}
if skip {
continue
}
newInterfaces = append(newInterfaces, i)
}
netInterfaces = newInterfaces
}
}
var allMACAddresses = []string{}
for _, netInterface := range netInterfaces {
var macAddr = strings.TrimSpace(netInterface.HardwareAddr.String())
if len(macAddr) == 0 {
continue
}
allMACAddresses = append(allMACAddresses, macAddr)
}
// check mac addresses
for _, macAddress := range allMACAddresses {
if !lists.ContainsString(key.MacAddresses, macAddress) {
return false, "003"
}
}
return true, ""
}
func findAllNetInterfaces() ([]net.Interface, error) {
interfaces, err := net.Interfaces()
if err != nil {
return nil, err
}
var dockerReg = regexp.MustCompile(`^docker\d+$`)
var resultInterfaces []net.Interface
for _, i := range interfaces {
if i.Flags&net.FlagLoopback == net.FlagLoopback {
continue
}
// ignore docker
if dockerReg.MatchString(i.Name) {
continue
}
if i.Flags&net.FlagUp == net.FlagUp {
resultInterfaces = append(resultInterfaces, i)
}
}
return resultInterfaces, nil
}
func generateHardwareUUID() string {
if runtime.GOOS != "linux" {
return ""
}
var diskUUID string
{
var cmd = NewCmd("ls", "/dev/disk/by-uuid")
cmd.WithStdout()
err := cmd.Run()
if err != nil {
return ""
}
var stdout = strings.TrimSpace(cmd.Stdout())
if len(stdout) == 0 {
return ""
}
var pieces = regexp.MustCompile(`\s+`).Split(stdout, -1)
sort.Strings(pieces)
diskUUID = stringutil.Md5(strings.Join(pieces, "\n"))
}
var hardwareUUID string
{
var cmd = NewCmd("dmidecode")
cmd.WithStdout()
err := cmd.Run()
if err != nil {
return ""
}
var stdout = strings.TrimSpace(cmd.Stdout())
if len(stdout) == 0 {
return ""
}
hardwareUUID = stringutil.Md5(stdout)
}
return stringutil.Md5(diskUUID + hardwareUUID)
}

View File

@@ -0,0 +1,62 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
package utils_test
import (
"github.com/TeaOSLab/EdgePlus/pkg/utils"
"github.com/iwind/TeaGo/types"
"testing"
)
func TestGenerateRequestKey(t *testing.T) {
requestKey, err := utils.GenerateRequestKey()
if err != nil {
t.Fatal(err)
}
t.Logf("request key: %+v", requestKey)
}
func TestGenerateRequestCode(t *testing.T) {
requestCode, err := utils.GenerateRequestCode()
if err != nil {
t.Fatal(err)
}
t.Log("request code:", "["+types.String(len(requestCode))+"]", requestCode)
requestKey, err := utils.DecodeRequestCode(requestCode)
if err != nil {
t.Fatal(err)
}
t.Logf("%+v", requestKey)
t.Log("mac addresses:", len(requestKey.MacAddresses))
}
func TestDecodeRequestCode(t *testing.T) {
var requestCode = `F4BqUMBxDHPFsd4mIDUiSfiRor473+ctxycygBwxZUyqDZppJrlAjnT5E6qyH7Yb64icvlkCqiEPYbOkxh9TUhWHuoqsGAKcO+6vFaelBeojnlVXkg==`
requestKey, err := utils.DecodeRequestCode(requestCode)
if err != nil {
t.Fatal(err)
}
t.Logf("%+v", requestKey)
}
func TestValidateRequestCode(t *testing.T) {
{
ok, errorCode := utils.ValidateRequestCode("123456")
t.Log("ok:", ok, "errorCode:", errorCode)
}
{
requestCode, err := utils.GenerateRequestCode()
if err != nil {
t.Fatal(err)
}
ok, errorCode := utils.ValidateRequestCode(requestCode)
t.Log("ok:", ok, "errorCode:", errorCode)
}
{
var requestCode = "F4BqUMBxDHPFsd4mIDUiSfiRpr471egtmnMyhx0xbxQeuEsuqXRiFtveCJGaELzffDATN5ULoP+Q/Y3NxsXNsvtxl9VkTA4VFq1s7b83BJVy6h3hKgwvhVw9H2upOf9aouD26JFZwr0ncM+cQGda3z64wOg3TFj8KhoM+ixaFY9SO0o3fg+0R8tKxA6rjGn/Do/CgKJTb4fF/tGGZ6QFY3UbO4KObaDmJrAQWag9IGKE5/GGOyBYWI9S45Auf6ee39X5JToDJHVJt3BV1fNNu3D9OrS+mg2SKLHhQdps7E5zor+K7Shhx8KV85qkdEImR+BA2rrxEDfcJz6+lQ=="
ok, errorCode := utils.ValidateRequestCode(requestCode)
t.Log("ok:", ok, "errorCode:", errorCode)
}
}