阶段性提交

This commit is contained in:
GoEdgeLab
2020-09-09 18:53:53 +08:00
parent ea05b2f236
commit 5a941ec9d7
115 changed files with 7910 additions and 744 deletions

View File

@@ -10,6 +10,8 @@ type APIConfig struct {
RPC struct {
Endpoints []string `yaml:"endpoints"`
} `yaml:"rpc"`
NodeId string `yaml:"nodeId"`
Secret string `yaml:"secret"`
}
func LoadAPIConfig() (*APIConfig, error) {

View File

@@ -1,6 +1,7 @@
package configs
import (
"github.com/TeaOSLab/EdgeNode/internal/configs/serverconfigs"
"github.com/go-yaml/yaml"
"github.com/iwind/TeaGo/Tea"
"io/ioutil"
@@ -9,10 +10,13 @@ import (
var sharedNodeConfig *NodeConfig = nil
type NodeConfig struct {
Id string `yaml:"id"`
Servers []*ServerConfig `yaml:"servers"`
Id string `yaml:"id" json:"id"`
IsOn bool `yaml:"isOn" json:"isOn"`
Servers []*serverconfigs.ServerConfig `yaml:"servers" json:"servers"`
Version int `yaml:"version" json:"version"`
}
// 取得当前节点配置单例
func SharedNodeConfig() (*NodeConfig, error) {
sharedLocker.Lock()
defer sharedLocker.Unlock()
@@ -36,9 +40,19 @@ func SharedNodeConfig() (*NodeConfig, error) {
return config, nil
}
// 刷新当前节点配置
func ReloadNodeConfig() error {
sharedLocker.Lock()
sharedNodeConfig = nil
sharedLocker.Unlock()
_, err := SharedNodeConfig()
return err
}
// 根据网络地址和协议分组
func (this *NodeConfig) AvailableGroups() []*ServerGroup {
groupMapping := map[string]*ServerGroup{} // protocol://addr => Server Group
func (this *NodeConfig) AvailableGroups() []*serverconfigs.ServerGroup {
groupMapping := map[string]*serverconfigs.ServerGroup{} // protocol://addr => Server Group
for _, server := range this.Servers {
if !server.IsOn {
continue
@@ -48,15 +62,39 @@ func (this *NodeConfig) AvailableGroups() []*ServerGroup {
if ok {
group.Add(server)
} else {
group = NewServerGroup(addr)
group = serverconfigs.NewServerGroup(addr)
group.Add(server)
}
groupMapping[addr] = group
}
}
result := []*ServerGroup{}
result := []*serverconfigs.ServerGroup{}
for _, group := range groupMapping {
result = append(result, group)
}
return result
}
func (this *NodeConfig) Init() error {
for _, server := range this.Servers {
err := server.Init()
if err != nil {
return err
}
}
return nil
}
// 写入到文件
func (this *NodeConfig) Save() error {
sharedLocker.Lock()
defer sharedLocker.Unlock()
data, err := yaml.Marshal(this)
if err != nil {
return err
}
return ioutil.WriteFile(Tea.ConfigFile("node.yaml"), data, 0777)
}

View File

@@ -1,6 +1,7 @@
package configs
import (
"github.com/TeaOSLab/EdgeNode/internal/configs/serverconfigs"
_ "github.com/iwind/TeaGo/bootstrap"
"github.com/iwind/TeaGo/logs"
"testing"
@@ -27,18 +28,37 @@ func TestSharedNodeConfig(t *testing.T) {
func TestNodeConfig_Groups(t *testing.T) {
config := &NodeConfig{}
config.Servers = []*ServerConfig{
config.Servers = []*serverconfigs.ServerConfig{
{
IsOn: true,
HTTP: &HTTPProtocolConfig{
IsOn: true,
Listen: []string{"127.0.0.1:1234", ":8080"},
HTTP: &serverconfigs.HTTPProtocolConfig{
BaseProtocol: serverconfigs.BaseProtocol{
IsOn: true,
Listen: []*serverconfigs.NetworkAddressConfig{
{
Protocol: serverconfigs.ProtocolHTTP,
Host: "127.0.0.1",
PortRange: "1234",
},
{
Protocol: serverconfigs.ProtocolHTTP,
PortRange: "8080",
},
},
},
},
},
{
HTTP: &HTTPProtocolConfig{
IsOn: true,
Listen: []string{":8080"},
HTTP: &serverconfigs.HTTPProtocolConfig{
BaseProtocol: serverconfigs.BaseProtocol{
IsOn: true,
Listen: []*serverconfigs.NetworkAddressConfig{
{
Protocol: serverconfigs.ProtocolHTTP,
PortRange: "8080",
},
},
},
},
},
}

View File

@@ -1,4 +0,0 @@
package configs
type OriginServerConfig struct {
}

View File

@@ -1,22 +0,0 @@
package configs
type HTTPProtocolConfig struct {
IsOn bool `yaml:"isOn"` // 是否开启
IPVersion IPVersion `yaml:"ipVersion"` // 4, 6
Listen []string `yaml:"listen" json:"listen"` // 监听地址
}
func (this *HTTPProtocolConfig) Addresses() []string {
result := []string{}
for _, listen := range this.Listen {
switch this.IPVersion {
case IPv4:
result = append(result, ProtocolHTTP4+"://"+listen)
case IPv6:
result = append(result, ProtocolHTTP6+"://"+listen)
default:
result = append(result, ProtocolHTTP+"://"+listen)
}
}
return result
}

View File

@@ -1,22 +0,0 @@
package configs
type HTTPSProtocolConfig struct {
IsOn bool `yaml:"isOn"` // 是否开启
IPVersion string `yaml:"ipVersion"` // 4, 6
Listen []string `yaml:"listen" json:"listen"` // 监听地址
}
func (this *HTTPSProtocolConfig) Addresses() []string {
result := []string{}
for _, listen := range this.Listen {
switch this.IPVersion {
case IPv4:
result = append(result, ProtocolHTTPS4+"://"+listen)
case IPv6:
result = append(result, ProtocolHTTPS6+"://"+listen)
default:
result = append(result, ProtocolHTTPS+"://"+listen)
}
}
return result
}

View File

@@ -1,22 +0,0 @@
package configs
type TCPProtocolConfig struct {
IsOn bool `yaml:"isOn"` // 是否开启
IPVersion IPVersion `yaml:"ipVersion"` // 4, 6
Listen []string `yaml:"listen" json:"listen"` // 监听地址
}
func (this *TCPProtocolConfig) Addresses() []string {
result := []string{}
for _, listen := range this.Listen {
switch this.IPVersion {
case IPv4:
result = append(result, ProtocolTCP4+"://"+listen)
case IPv6:
result = append(result, ProtocolTCP6+"://"+listen)
default:
result = append(result, ProtocolTCP+"://"+listen)
}
}
return result
}

View File

@@ -1,22 +0,0 @@
package configs
type TLSProtocolConfig struct {
IsOn bool `yaml:"isOn"` // 是否开启
IPVersion string `yaml:"ipVersion"` // 4, 6
Listen []string `yaml:"listen" json:"listen"` // 监听地址
}
func (this *TLSProtocolConfig) Addresses() []string {
result := []string{}
for _, listen := range this.Listen {
switch this.IPVersion {
case IPv4:
result = append(result, ProtocolTLS4+"://"+listen)
case IPv6:
result = append(result, ProtocolTLS6+"://"+listen)
default:
result = append(result, ProtocolTLS+"://"+listen)
}
}
return result
}

View File

@@ -1,14 +0,0 @@
package configs
type UDPProtocolConfig struct {
IsOn bool `yaml:"isOn"` // 是否开启
Listen []string `yaml:"listen" json:"listen"` // 监听地址
}
func (this *UDPProtocolConfig) Addresses() []string {
result := []string{}
for _, listen := range this.Listen {
result = append(result, ProtocolUDP+"://"+listen)
}
return result
}

View File

@@ -1,14 +0,0 @@
package configs
type UnixProtocolConfig struct {
IsOn bool `yaml:"isOn"` // 是否开启
Listen []string `yaml:"listen" json:"listen"` // 监听地址
}
func (this *UnixProtocolConfig) Addresses() []string {
result := []string{}
for _, listen := range this.Listen {
result = append(result, ProtocolUnix+":"+listen)
}
return result
}

View File

@@ -1,54 +0,0 @@
package configs
type ServerConfig struct {
Id string `yaml:"id"` // ID
IsOn bool `yaml:"isOn"` // 是否开启
Components []*ComponentConfig `yaml:"components"` // 组件
Filters []*FilterConfig `yaml:"filters"` // 过滤器
Name string `yaml:"name"` // 名称
Description string `yaml:"description"` // 描述
ServerNames []string `yaml:"serverNames"` // 域名
// 协议
HTTP *HTTPProtocolConfig `yaml:"http"` // HTTP配置
HTTPS *HTTPSProtocolConfig `yaml:"https"` // HTTPS配置
TCP *TCPProtocolConfig `yaml:"tcp"` // TCP配置
TLS *TLSProtocolConfig `yaml:"tls"` // TLS配置
Unix *UnixProtocolConfig `yaml:"unix"` // Unix配置
UDP *UDPProtocolConfig `yaml:"udp"` // UDP配置
// Web配置
Web *WebConfig `yaml:"web"`
}
func NewServerConfig() *ServerConfig {
return &ServerConfig{}
}
func (this *ServerConfig) Init() error {
return nil
}
func (this *ServerConfig) FullAddresses() []string {
result := []Protocol{}
if this.HTTP != nil && this.HTTP.IsOn {
result = append(result, this.HTTP.Addresses()...)
}
if this.HTTPS != nil && this.HTTPS.IsOn {
result = append(result, this.HTTPS.Addresses()...)
}
if this.TCP != nil && this.TCP.IsOn {
result = append(result, this.TCP.Addresses()...)
}
if this.TLS != nil && this.TLS.IsOn {
result = append(result, this.TLS.Addresses()...)
}
if this.Unix != nil && this.Unix.IsOn {
result = append(result, this.Unix.Addresses()...)
}
if this.UDP != nil && this.UDP.IsOn {
result = append(result, this.UDP.Addresses()...)
}
return result
}

View File

@@ -1,21 +0,0 @@
package configs
import "testing"
func TestServerConfig_Protocols(t *testing.T) {
{
server := NewServerConfig()
t.Log(server.FullAddresses())
}
{
server := NewServerConfig()
server.HTTP = &HTTPProtocolConfig{IsOn: true, Listen: []string{"127.0.0.1:1234"}}
server.HTTPS = &HTTPSProtocolConfig{IsOn: true, Listen: []string{"127.0.0.1:1234"}}
server.TCP = &TCPProtocolConfig{IsOn: true, Listen: []string{"127.0.0.1:1234"}}
server.TLS = &TLSProtocolConfig{IsOn: true, Listen: []string{"127.0.0.1:1234"}}
server.Unix = &UnixProtocolConfig{IsOn: true, Listen: []string{"127.0.0.1:1234"}}
server.UDP = &UDPProtocolConfig{IsOn: true, Listen: []string{"127.0.0.1:1234"}}
t.Log(server.FullAddresses())
}
}

View File

@@ -1,41 +0,0 @@
package configs
import "strings"
type ServerGroup struct {
fullAddr string
Servers []*ServerConfig
}
func NewServerGroup(fullAddr string) *ServerGroup {
return &ServerGroup{fullAddr: fullAddr}
}
// 添加服务
func (this *ServerGroup) Add(server *ServerConfig) {
this.Servers = append(this.Servers, server)
}
// 获取完整的地址
func (this *ServerGroup) FullAddr() string {
return this.fullAddr
}
// 获取当前分组的协议
func (this *ServerGroup) Protocol() Protocol {
for _, p := range AllProtocols() {
if strings.HasPrefix(this.fullAddr, p+":") {
return p
}
}
return ProtocolHTTP
}
// 获取当前分组的地址
func (this *ServerGroup) Addr() string {
protocol := this.Protocol()
if protocol == ProtocolUnix {
return strings.TrimPrefix(this.fullAddr, protocol+":")
}
return strings.TrimPrefix(this.fullAddr, protocol+"://")
}

View File

@@ -1,4 +1,4 @@
package configs
package serverconfigs
type ComponentConfig struct {
}

View File

@@ -0,0 +1,20 @@
package configutils
import (
"reflect"
)
// 拷贝同类型struct指针对象中的字段
func CopyStructObject(destPtr, sourcePtr interface{}) {
value := reflect.ValueOf(destPtr)
value2 := reflect.ValueOf(sourcePtr)
countFields := value2.Elem().NumField()
for i := 0; i < countFields; i++ {
v := value2.Elem().Field(i)
if !v.IsValid() || !v.CanSet() {
continue
}
value.Elem().Field(i).Set(v)
}
}

View File

@@ -0,0 +1,28 @@
package configutils
import (
"github.com/iwind/TeaGo/logs"
"testing"
)
func TestCopyStructObject(t *testing.T) {
type Book struct {
Name string
Price int
Year int
Author string
press string
}
book1 := &Book{
Name: "Hello Golang",
Price: 100,
Year: 2020,
Author: "Liu",
press: "Beijing",
}
book2 := new(Book)
CopyStructObject(book2, book1)
logs.PrintAsJSON(book2, t)
logs.PrintAsJSON(book1, t)
}

View File

@@ -0,0 +1,59 @@
package configutils
import (
"github.com/iwind/TeaGo/logs"
"github.com/iwind/TeaGo/utils/string"
"strings"
)
// 从一组规则中匹配域名
// 支持的格式example.com, www.example.com, .example.com, *.example.com, ~(\d+).example.com
// 更多参考http://nginx.org/en/docs/http/ngx_http_core_module.html#server_name
func MatchDomains(patterns []string, domain string) (isMatched bool) {
if len(patterns) == 0 {
return
}
for _, pattern := range patterns {
if matchDomain(pattern, domain) {
return true
}
}
return
}
// 匹配单个域名规则
func matchDomain(pattern string, domain string) (isMatched bool) {
if len(pattern) == 0 {
return
}
// 正则表达式
if pattern[0] == '~' {
reg, err := stringutil.RegexpCompile(strings.TrimSpace(pattern[1:]))
if err != nil {
logs.Error(err)
return false
}
return reg.MatchString(domain)
}
if pattern[0] == '.' {
return strings.HasSuffix(domain, pattern)
}
// 其他匹配
patternPieces := strings.Split(pattern, ".")
domainPieces := strings.Split(domain, ".")
if len(patternPieces) != len(domainPieces) {
return
}
isMatched = true
for index, patternPiece := range patternPieces {
if patternPiece == "" || patternPiece == "*" || patternPiece == domainPieces[index] {
continue
}
isMatched = false
break
}
return isMatched
}

View File

@@ -0,0 +1,79 @@
package configutils
import (
"github.com/iwind/TeaGo/assert"
"testing"
)
func TestMatchDomain(t *testing.T) {
a := assert.NewAssertion(t)
{
ok := MatchDomains([]string{}, "example.com")
a.IsFalse(ok)
}
{
ok := MatchDomains([]string{"example.com"}, "example.com")
a.IsTrue(ok)
}
{
ok := MatchDomains([]string{"www.example.com"}, "example.com")
a.IsFalse(ok)
}
{
ok := MatchDomains([]string{".example.com"}, "www.example.com")
a.IsTrue(ok)
}
{
ok := MatchDomains([]string{".example.com"}, "a.www.example.com")
a.IsTrue(ok)
}
{
ok := MatchDomains([]string{".example.com"}, "a.www.example123.com")
a.IsFalse(ok)
}
{
ok := MatchDomains([]string{"*.example.com"}, "www.example.com")
a.IsTrue(ok)
}
{
ok := MatchDomains([]string{"*.*.com"}, "www.example.com")
a.IsTrue(ok)
}
{
ok := MatchDomains([]string{"www.*.com"}, "www.example.com")
a.IsTrue(ok)
}
{
ok := MatchDomains([]string{"gallery.*.com"}, "www.example.com")
a.IsFalse(ok)
}
{
ok := MatchDomains([]string{"~\\w+.example.com"}, "www.example.com")
a.IsTrue(ok)
}
{
ok := MatchDomains([]string{"~\\w+.example.com"}, "a.www.example.com")
a.IsTrue(ok)
}
{
ok := MatchDomains([]string{"~^\\d+.example.com$"}, "www.example.com")
a.IsFalse(ok)
}
{
ok := MatchDomains([]string{"~^\\d+.example.com$"}, "123.example.com")
a.IsTrue(ok)
}
}

View File

@@ -0,0 +1,11 @@
package configutils
import "github.com/iwind/TeaGo/logs"
// 记录错误
func LogError(arg ...interface{}) {
if len(arg) == 0 {
return
}
logs.Println(arg...)
}

View File

@@ -0,0 +1,25 @@
package configutils
import (
"regexp"
"strings"
)
var whitespaceReg = regexp.MustCompile(`\s+`)
// 关键词匹配
func MatchKeyword(source, keyword string) bool {
if len(keyword) == 0 {
return false
}
pieces := whitespaceReg.Split(keyword, -1)
source = strings.ToLower(source)
for _, piece := range pieces {
if strings.Index(source, strings.ToLower(piece)) > -1 {
return true
}
}
return false
}

View File

@@ -0,0 +1,13 @@
package configutils
import (
"github.com/iwind/TeaGo/assert"
"testing"
)
func TestMatchKeyword(t *testing.T) {
a := assert.NewAssertion(t)
a.IsTrue(MatchKeyword("a b c", "a"))
a.IsFalse(MatchKeyword("a b c", ""))
a.IsTrue(MatchKeyword("abc", "BC"))
}

View File

@@ -0,0 +1,14 @@
package configutils
import (
"github.com/go-yaml/yaml"
"io/ioutil"
)
func UnmarshalYamlFile(file string, ptr interface{}) error {
data, err := ioutil.ReadFile(file)
if err != nil {
return err
}
return yaml.Unmarshal(data, ptr)
}

View File

@@ -1,4 +1,4 @@
package configs
package serverconfigs
type FilterConfig struct {
}

View File

@@ -0,0 +1,43 @@
package serverconfigs
import (
"github.com/TeaOSLab/EdgeNode/internal/configs/serverconfigs/configutils"
"github.com/TeaOSLab/EdgeNode/internal/configs/serverconfigs/shared"
)
var globalConfig *GlobalConfig = nil
var globalConfigFile = "global.yaml"
// 全局设置
type GlobalConfig struct {
HTTPAll struct {
MatchDomainStrictly bool `yaml:"matchDomainStrictly"`
} `yaml:"httpAll"`
HTTP struct{} `yaml:"http"`
HTTPS struct{} `yaml:"https"`
TCPAll struct{} `yaml:"tcpAll"`
TCP struct{} `yaml:"tcp"`
TLS struct{} `yaml:"tls"`
Unix struct{} `yaml:"unix"`
UDP struct{} `yaml:"udp"`
}
func SharedGlobalConfig() *GlobalConfig {
shared.Locker.Lock()
defer shared.Locker.Unlock()
if globalConfig != nil {
return globalConfig
}
err := configutils.UnmarshalYamlFile(globalConfigFile, globalConfig)
if err != nil {
configutils.LogError("[SharedGlobalConfig]" + err.Error())
globalConfig = &GlobalConfig{}
}
return globalConfig
}
func (this *GlobalConfig) Init() error {
return nil
}

View File

@@ -1,4 +1,4 @@
package configs
package serverconfigs
type IPVersion = string

View File

@@ -1,4 +1,4 @@
package configs
package serverconfigs
type LocationConfig struct {
}

View File

@@ -0,0 +1,70 @@
package serverconfigs
import (
"github.com/iwind/TeaGo/types"
"regexp"
"strconv"
"strings"
)
var regexpSinglePort = regexp.MustCompile(`^\d+$`)
// 网络地址配置
type NetworkAddressConfig struct {
Protocol string `yaml:"protocol" json:"protocol"` // 协议http、tcp、tcp4、tcp6、unix、udp等
Host string `yaml:"host" json:"host"` // 主机地址或主机名
PortRange string `yaml:"portRange" json:"portRange"` // 端口范围,支持 8080、8080-8090、8080:8090
minPort int
maxPort int
}
func (this *NetworkAddressConfig) Init() error {
// 8080
if regexpSinglePort.MatchString(this.PortRange) {
this.minPort = types.Int(this.PortRange)
this.maxPort = this.minPort
return nil
}
// 8080:8090
if strings.Contains(this.PortRange, ":") {
pieces := strings.SplitN(this.PortRange, ":", 2)
minPort := types.Int(pieces[0])
maxPort := types.Int(pieces[1])
if minPort > maxPort {
minPort, maxPort = maxPort, minPort
}
this.minPort = minPort
this.maxPort = maxPort
return nil
}
// 8080-8090
if strings.Contains(this.PortRange, "-") {
pieces := strings.SplitN(this.PortRange, "-", 2)
minPort := types.Int(pieces[0])
maxPort := types.Int(pieces[1])
if minPort > maxPort {
minPort, maxPort = maxPort, minPort
}
this.minPort = minPort
this.maxPort = maxPort
return nil
}
return nil
}
func (this *NetworkAddressConfig) FullAddresses() []string {
if this.Protocol == ProtocolUnix {
return []string{this.Protocol + ":" + this.Host}
}
result := []string{}
for i := this.minPort; i <= this.maxPort; i++ {
host := this.Host
result = append(result, this.Protocol+"://"+host+":"+strconv.Itoa(i))
}
return result
}

View File

@@ -0,0 +1,57 @@
package serverconfigs
import "testing"
func TestNetworkAddressConfig_FullAddresses(t *testing.T) {
{
addr := &NetworkAddressConfig{
Protocol: "http",
Host: "127.0.0.1",
PortRange: "8080",
}
err := addr.Init()
if err != nil {
t.Fatal(err)
}
t.Log(addr.FullAddresses())
}
{
addr := &NetworkAddressConfig{
Protocol: "http",
Host: "127.0.0.1",
PortRange: "8080:8090",
}
err := addr.Init()
if err != nil {
t.Fatal(err)
}
t.Log(addr.FullAddresses())
}
{
addr := &NetworkAddressConfig{
Protocol: "http",
Host: "127.0.0.1",
PortRange: "8080-8090",
}
err := addr.Init()
if err != nil {
t.Fatal(err)
}
t.Log(addr.FullAddresses())
}
{
addr := &NetworkAddressConfig{
Protocol: "http",
Host: "127.0.0.1",
PortRange: "8080-8070",
}
err := addr.Init()
if err != nil {
t.Fatal(err)
}
t.Log(addr.FullAddresses())
}
}

View File

@@ -0,0 +1,10 @@
package serverconfigs
// 源站服务配置
type OriginServerConfig struct {
Id string `yaml:"id" json:"id"` // ID
IsOn bool `yaml:"isOn" json:"isOn"` // 是否启用
Name string `yaml:"name" json:"name"` // 名称 TODO
Addr *NetworkAddressConfig `yaml:"addr" json:"addr"` // 地址
Description string `yaml:"description" json:"description"` // 描述 TODO
}

View File

@@ -0,0 +1,6 @@
package serverconfigs
// TODO 需要实现
type OriginServerGroupConfig struct {
Origins []*OriginServerConfig `yaml:"origins" json:"origins"` // 源站列表
}

View File

@@ -1,4 +1,4 @@
package configs
package serverconfigs
type Protocol = string

View File

@@ -0,0 +1,32 @@
package serverconfigs
// 协议基础数据结构
type BaseProtocol struct {
IsOn bool `yaml:"isOn" json:"isOn"` // 是否开启
Listen []*NetworkAddressConfig `yaml:"listen" json:"listen"` // 绑定的网络地址
}
// 初始化
func (this *BaseProtocol) InitBase() error {
for _, addr := range this.Listen {
err := addr.Init()
if err != nil {
return err
}
}
return nil
}
// 获取完整的地址列表
func (this *BaseProtocol) FullAddresses() []string {
result := []string{}
for _, addr := range this.Listen {
result = append(result, addr.FullAddresses()...)
}
return result
}
// 添加地址
func (this *BaseProtocol) AddListen(addr ...*NetworkAddressConfig) {
this.Listen = append(this.Listen, addr...)
}

View File

@@ -0,0 +1,14 @@
package serverconfigs
type HTTPProtocolConfig struct {
BaseProtocol `yaml:",inline"`
}
func (this *HTTPProtocolConfig) Init() error {
err := this.InitBase()
if err != nil {
return err
}
return nil
}

View File

@@ -0,0 +1,24 @@
package serverconfigs
import "github.com/TeaOSLab/EdgeNode/internal/configs/serverconfigs/sslconfigs"
// TLS Version
type TLSVersion = string
// Cipher Suites
type TLSCipherSuite = string
type HTTPSProtocolConfig struct {
BaseProtocol `yaml:",inline"`
SSL *sslconfigs.SSLConfig `yaml:"ssl"`
}
func (this *HTTPSProtocolConfig) Init() error {
err := this.InitBase()
if err != nil {
return err
}
return nil
}

View File

@@ -0,0 +1,14 @@
package serverconfigs
type TCPProtocolConfig struct {
BaseProtocol `yaml:",inline"`
}
func (this *TCPProtocolConfig) Init() error {
err := this.InitBase()
if err != nil {
return err
}
return nil
}

View File

@@ -0,0 +1,18 @@
package serverconfigs
import "github.com/TeaOSLab/EdgeNode/internal/configs/serverconfigs/sslconfigs"
type TLSProtocolConfig struct {
BaseProtocol `yaml:",inline"`
SSL *sslconfigs.SSLConfig `yaml:"ssl"`
}
func (this *TLSProtocolConfig) Init() error {
err := this.InitBase()
if err != nil {
return err
}
return nil
}

View File

@@ -0,0 +1,14 @@
package serverconfigs
type UDPProtocolConfig struct {
BaseProtocol `yaml:",inline"`
}
func (this *UDPProtocolConfig) Init() error {
err := this.InitBase()
if err != nil {
return err
}
return nil
}

View File

@@ -0,0 +1,14 @@
package serverconfigs
type UnixProtocolConfig struct {
BaseProtocol `yaml:",inline"`
}
func (this *UnixProtocolConfig) Init() error {
err := this.InitBase()
if err != nil {
return err
}
return nil
}

View File

@@ -0,0 +1,6 @@
package serverconfigs
type ReverseProxyConfig struct {
IsOn bool `yaml:"isOn" json:"isOn"` // 是否启用
Origins []*OriginServerConfig `yaml:"origins" json:"origins"` // 源站列表
}

View File

@@ -0,0 +1,178 @@
package serverconfigs
import (
"encoding/json"
"github.com/TeaOSLab/EdgeNode/internal/configs/serverconfigs/sslconfigs"
)
type ServerConfig struct {
Id string `yaml:"id" json:"id"` // ID
IsOn bool `yaml:"isOn" json:"isOn"` // 是否开启
Components []*ComponentConfig `yaml:"components" json:"components"` // 组件
Filters []*FilterConfig `yaml:"filters" json:"filters"` // 过滤器
Name string `yaml:"name" json:"name"` // 名称
Description string `yaml:"description" json:"description"` // 描述
ServerNames []*ServerNameConfig `yaml:"serverNames" json:"serverNames"` // 域名
// 前端协议
HTTP *HTTPProtocolConfig `yaml:"http" json:"http"` // HTTP配置
HTTPS *HTTPSProtocolConfig `yaml:"https" json:"https"` // HTTPS配置
TCP *TCPProtocolConfig `yaml:"tcp" json:"tcp"` // TCP配置
TLS *TLSProtocolConfig `yaml:"tls" json:"tls"` // TLS配置
Unix *UnixProtocolConfig `yaml:"unix" json:"unix"` // Unix配置
UDP *UDPProtocolConfig `yaml:"udp" json:"udp"` // UDP配置
// Web配置
Web *WebConfig `yaml:"web" json:"web"`
// 反向代理配置
ReverseProxy *ReverseProxyConfig `yaml:"reverseProxy" json:"reverseProxy"`
}
func NewServerConfig() *ServerConfig {
return &ServerConfig{}
}
func (this *ServerConfig) Init() error {
if this.HTTP != nil {
err := this.HTTP.Init()
if err != nil {
return err
}
}
if this.HTTPS != nil {
err := this.HTTPS.Init()
if err != nil {
return err
}
}
if this.TCP != nil {
err := this.TCP.Init()
if err != nil {
return err
}
}
if this.TLS != nil {
err := this.TLS.Init()
if err != nil {
return err
}
}
if this.Unix != nil {
err := this.Unix.Init()
if err != nil {
return err
}
}
if this.UDP != nil {
err := this.UDP.Init()
if err != nil {
return err
}
}
return nil
}
func (this *ServerConfig) FullAddresses() []string {
result := []Protocol{}
if this.HTTP != nil && this.HTTP.IsOn {
result = append(result, this.HTTP.FullAddresses()...)
}
if this.HTTPS != nil && this.HTTPS.IsOn {
result = append(result, this.HTTPS.FullAddresses()...)
}
if this.TCP != nil && this.TCP.IsOn {
result = append(result, this.TCP.FullAddresses()...)
}
if this.TLS != nil && this.TLS.IsOn {
result = append(result, this.TLS.FullAddresses()...)
}
if this.Unix != nil && this.Unix.IsOn {
result = append(result, this.Unix.FullAddresses()...)
}
if this.UDP != nil && this.UDP.IsOn {
result = append(result, this.UDP.FullAddresses()...)
}
return result
}
func (this *ServerConfig) Listen() []*NetworkAddressConfig {
result := []*NetworkAddressConfig{}
if this.HTTP != nil {
result = append(result, this.HTTP.Listen...)
}
if this.HTTPS != nil {
result = append(result, this.HTTPS.Listen...)
}
if this.TCP != nil {
result = append(result, this.TCP.Listen...)
}
if this.TLS != nil {
result = append(result, this.TLS.Listen...)
}
if this.Unix != nil {
result = append(result, this.Unix.Listen...)
}
if this.UDP != nil {
result = append(result, this.UDP.Listen...)
}
return result
}
func (this *ServerConfig) AsJSON() ([]byte, error) {
return json.Marshal(this)
}
func (this *ServerConfig) IsHTTP() bool {
return this.HTTP != nil || this.HTTPS != nil
}
func (this *ServerConfig) IsTCP() bool {
return this.TCP != nil || this.TLS != nil
}
func (this *ServerConfig) IsUnix() bool {
return this.Unix != nil
}
func (this *ServerConfig) IsUDP() bool {
return this.UDP != nil
}
// 判断是否和域名匹配
func (this *ServerConfig) MatchName(name string) bool {
for _, serverName := range this.ServerNames {
if serverName.Match(name) {
return true
}
}
return false
}
// 判断是否严格匹配
func (this *ServerConfig) MatchNameStrictly(name string) bool {
for _, serverName := range this.ServerNames {
if serverName.Name == name {
return true
}
}
return false
}
// SSL信息
func (this *ServerConfig) SSLConfig() *sslconfigs.SSLConfig {
if this.HTTPS != nil {
return this.HTTPS.SSL
}
if this.TLS != nil {
return this.TLS.SSL
}
return nil
}

View File

@@ -0,0 +1,74 @@
package serverconfigs
import "testing"
func TestServerConfig_Protocols(t *testing.T) {
{
server := NewServerConfig()
t.Log(server.FullAddresses())
}
{
server := NewServerConfig()
server.HTTP = &HTTPProtocolConfig{BaseProtocol: BaseProtocol{
IsOn: true,
Listen: []*NetworkAddressConfig{
{
Protocol: ProtocolHTTP,
PortRange: "1234",
},
},
}}
server.HTTPS = &HTTPSProtocolConfig{BaseProtocol: BaseProtocol{
IsOn: true,
Listen: []*NetworkAddressConfig{
{
Protocol: ProtocolUnix,
Host: "/hello.sock",
PortRange: "1235",
},
},
}}
server.TCP = &TCPProtocolConfig{BaseProtocol: BaseProtocol{
IsOn: true,
Listen: []*NetworkAddressConfig{
{
Protocol: ProtocolHTTPS,
PortRange: "1236",
},
},
}}
server.TLS = &TLSProtocolConfig{BaseProtocol: BaseProtocol{
IsOn: true,
Listen: []*NetworkAddressConfig{
{
Protocol: ProtocolTCP,
PortRange: "1234",
},
},
}}
server.Unix = &UnixProtocolConfig{BaseProtocol: BaseProtocol{
IsOn: true,
Listen: []*NetworkAddressConfig{
{
Protocol: ProtocolTLS,
PortRange: "1234",
},
},
}}
server.UDP = &UDPProtocolConfig{BaseProtocol: BaseProtocol{
IsOn: true,
Listen: []*NetworkAddressConfig{
{
Protocol: ProtocolUDP,
PortRange: "1234",
},
},
}}
err := server.Init()
if err != nil {
t.Fatal(err)
}
t.Log(server.FullAddresses())
}
}

View File

@@ -0,0 +1,85 @@
package serverconfigs
import "strings"
type ServerGroup struct {
fullAddr string
Servers []*ServerConfig
}
func NewServerGroup(fullAddr string) *ServerGroup {
return &ServerGroup{fullAddr: fullAddr}
}
// 添加服务
func (this *ServerGroup) Add(server *ServerConfig) {
this.Servers = append(this.Servers, server)
}
// 获取完整的地址
func (this *ServerGroup) FullAddr() string {
return this.fullAddr
}
// 获取当前分组的协议
func (this *ServerGroup) Protocol() Protocol {
for _, p := range AllProtocols() {
if strings.HasPrefix(this.fullAddr, p+":") {
return p
}
}
return ProtocolHTTP
}
// 获取当前分组的地址
func (this *ServerGroup) Addr() string {
protocol := this.Protocol()
if protocol == ProtocolUnix {
return strings.TrimPrefix(this.fullAddr, protocol+":")
}
return strings.TrimPrefix(this.fullAddr, protocol+"://")
}
// 判断当前分组是否为HTTP
func (this *ServerGroup) IsHTTP() bool {
p := this.Protocol()
return p == ProtocolHTTP || p == ProtocolHTTP4 || p == ProtocolHTTP6
}
// 判断当前分组是否为HTTPS
func (this *ServerGroup) IsHTTPS() bool {
p := this.Protocol()
return p == ProtocolHTTPS || p == ProtocolHTTPS4 || p == ProtocolHTTPS6
}
// 判断当前分组是否为TCP
func (this *ServerGroup) IsTCP() bool {
p := this.Protocol()
return p == ProtocolTCP || p == ProtocolTCP4 || p == ProtocolTCP6
}
// 判断当前分组是否为TLS
func (this *ServerGroup) IsTLS() bool {
p := this.Protocol()
return p == ProtocolTLS || p == ProtocolTLS4 || p == ProtocolTLS6
}
// 判断当前分组是否为Unix
func (this *ServerGroup) IsUnix() bool {
p := this.Protocol()
return p == ProtocolUnix
}
// 判断当前分组是否为UDP
func (this *ServerGroup) IsUDP() bool {
p := this.Protocol()
return p == ProtocolUDP
}
// 获取第一个Server
func (this *ServerGroup) FirstServer() *ServerConfig {
if len(this.Servers) > 0 {
return this.Servers[0]
}
return nil
}

View File

@@ -1,4 +1,4 @@
package configs
package serverconfigs
import (
"github.com/iwind/TeaGo/assert"

View File

@@ -0,0 +1,23 @@
package serverconfigs
import "github.com/TeaOSLab/EdgeNode/internal/configs/serverconfigs/configutils"
type ServerNameType = string
const (
ServerNameTypeFull = "full" // 完整的域名,包含通配符等
ServerNameTypePrefix = "prefix" // 前缀
ServerNameTypeSuffix = "suffix" // 后缀
ServerNameTypeMatch = "match" // 正则匹配
)
// 主机名(域名)配置
type ServerNameConfig struct {
Name string `yaml:"name" json:"name"` // 名称
Type string `yaml:"type" json:"type"` // 类型
}
// 判断主机名是否匹配
func (this *ServerNameConfig) Match(name string) bool {
return configutils.MatchDomains([]string{this.Name}, name)
}

View File

@@ -0,0 +1,21 @@
package shared
import (
"sync"
)
var Locker = new(FileLocker)
// global file modify locker
type FileLocker struct {
locker sync.Mutex
}
// lock
func (this *FileLocker) Lock() {
this.locker.Lock()
}
func (this *FileLocker) Unlock() {
this.locker.Unlock()
}

View File

@@ -0,0 +1,207 @@
package sslconfigs
import (
"crypto/tls"
"crypto/x509"
"errors"
"github.com/iwind/TeaGo/types"
"io/ioutil"
"net"
"regexp"
"strconv"
"strings"
)
// TLS Version
type TLSVersion = string
// Cipher Suites
type TLSCipherSuite = string
// SSL配置
type SSLConfig struct {
IsOn bool `yaml:"isOn" json:"isOn"` // 是否开启
Certs []*SSLCertConfig `yaml:"certs" json:"certs"`
ClientAuthType SSLClientAuthType `yaml:"clientAuthType" json:"clientAuthType"` // 客户端认证类型
ClientCACertIds []string `yaml:"clientCACertIds" json:"clientCACertIds"` // 客户端认证CA
Listen []string `yaml:"listen" json:"listen"` // 网络地址
MinVersion TLSVersion `yaml:"minVersion" json:"minVersion"` // 支持的最小版本
CipherSuites []TLSCipherSuite `yaml:"cipherSuites" json:"cipherSuites"` // 加密算法套件
HSTS *HSTSConfig `yaml:"hsts2" json:"hsts"` // HSTS配置yaml之所以使用hsts2是因为要和以前的版本分开
HTTP2Disabled bool `yaml:"http2Disabled" json:"http2Disabled"` // 是否禁用HTTP2
nameMapping map[string]*tls.Certificate // dnsName => cert
minVersion uint16
cipherSuites []uint16
clientCAPool *x509.CertPool
}
// 获取新对象
func NewSSLConfig() *SSLConfig {
return &SSLConfig{}
}
// 校验配置
func (this *SSLConfig) Init() error {
if !this.IsOn {
return nil
}
if len(this.Certs) == 0 {
return errors.New("no certificates in https config")
}
for _, cert := range this.Certs {
err := cert.Init()
if err != nil {
return err
}
}
if this.Listen == nil {
this.Listen = []string{}
} else {
for index, addr := range this.Listen {
_, _, err := net.SplitHostPort(addr)
if err != nil {
this.Listen[index] = strings.TrimSuffix(addr, ":") + ":443"
}
}
}
// min version
this.convertMinVersion()
// cipher suite categories
this.initCipherSuites()
// hsts
if this.HSTS != nil {
err := this.HSTS.Init()
if err != nil {
return err
}
}
// CA证书
if len(this.ClientCACertIds) > 0 && this.ClientAuthType != SSLClientAuthTypeNoClientCert {
this.clientCAPool = x509.NewCertPool()
list := SharedSSLCertList()
for _, certId := range this.ClientCACertIds {
cert := list.FindCert(certId)
if cert == nil {
continue
}
if !cert.On {
continue
}
data, err := ioutil.ReadFile(cert.FullCertPath())
if err != nil {
return err
}
this.clientCAPool.AppendCertsFromPEM(data)
}
}
return nil
}
// 取得最小版本
func (this *SSLConfig) TLSMinVersion() uint16 {
return this.minVersion
}
// 套件
func (this *SSLConfig) TLSCipherSuites() []uint16 {
return this.cipherSuites
}
// 校验是否匹配某个域名
func (this *SSLConfig) MatchDomain(domain string) (cert *tls.Certificate, ok bool) {
for _, cert := range this.Certs {
if cert.MatchDomain(domain) {
return cert.CertObject(), true
}
}
return nil, false
}
// 取得第一个证书
func (this *SSLConfig) FirstCert() *tls.Certificate {
for _, cert := range this.Certs {
return cert.CertObject()
}
return nil
}
// 是否包含某个证书或密钥路径
func (this *SSLConfig) ContainsFile(file string) bool {
for _, cert := range this.Certs {
if cert.CertFile == file || cert.KeyFile == file {
return true
}
}
return false
}
// 删除证书文件
func (this *SSLConfig) DeleteFiles() error {
var resultErr error = nil
for _, cert := range this.Certs {
err := cert.DeleteFiles()
if err != nil {
resultErr = err
}
}
return resultErr
}
// 查找单个证书配置
func (this *SSLConfig) FindCert(certId string) *SSLCertConfig {
for _, cert := range this.Certs {
if cert.Id == certId {
return cert
}
}
return nil
}
// 添加证书
func (this *SSLConfig) AddCert(cert *SSLCertConfig) {
this.Certs = append(this.Certs, cert)
}
// CA证书Pool用于TLS对客户端进行认证
func (this *SSLConfig) CAPool() *x509.CertPool {
return this.clientCAPool
}
// 分解所有监听地址
func (this *SSLConfig) ParseListenAddresses() []string {
result := []string{}
var reg = regexp.MustCompile(`\[\s*(\d+)\s*[,:-]\s*(\d+)\s*]$`)
for _, addr := range this.Listen {
match := reg.FindStringSubmatch(addr)
if len(match) == 0 {
result = append(result, addr)
} else {
min := types.Int(match[1])
max := types.Int(match[2])
if min > max {
min, max = max, min
}
for i := min; i <= max; i++ {
newAddr := reg.ReplaceAllString(addr, ":"+strconv.Itoa(i))
result = append(result, newAddr)
}
}
}
return result
}

View File

@@ -0,0 +1,75 @@
package sslconfigs
import (
"crypto/tls"
"github.com/iwind/TeaGo/maps"
)
// 认证类型
type SSLClientAuthType = int
const (
SSLClientAuthTypeNoClientCert SSLClientAuthType = 0
SSLClientAuthTypeRequestClientCert SSLClientAuthType = 1
SSLClientAuthTypeRequireAnyClientCert SSLClientAuthType = 2
SSLClientAuthTypeVerifyClientCertIfGiven SSLClientAuthType = 3
SSLClientAuthTypeRequireAndVerifyClientCert SSLClientAuthType = 4
)
// 所有的客户端认证类型
func AllSSLClientAuthTypes() []maps.Map {
return []maps.Map{
{
"name": "不需要客户端证书",
"type": SSLClientAuthTypeNoClientCert,
"requireCA": false,
},
{
"name": "请求客户端证书",
"type": SSLClientAuthTypeRequestClientCert,
"requireCA": true,
},
{
"name": "需要客户端证书,但不校验",
"type": SSLClientAuthTypeRequireAnyClientCert,
"requireCA": true,
},
{
"name": "有客户端证书的时候才校验",
"type": SSLClientAuthTypeVerifyClientCertIfGiven,
"requireCA": true,
},
{
"name": "校验客户端提供的证书",
"type": SSLClientAuthTypeRequireAndVerifyClientCert,
"requireCA": true,
},
}
}
// 查找单个认证方式的名称
func FindSSLClientAuthTypeName(authType SSLClientAuthType) string {
for _, m := range AllSSLClientAuthTypes() {
if m.GetInt("type") == authType {
return m.GetString("name")
}
}
return ""
}
// 认证类型和tls包内类型的映射
func GoSSLClientAuthType(authType SSLClientAuthType) tls.ClientAuthType {
switch authType {
case SSLClientAuthTypeNoClientCert:
return tls.NoClientCert
case SSLClientAuthTypeRequestClientCert:
return tls.RequestClientCert
case SSLClientAuthTypeRequireAnyClientCert:
return tls.RequireAnyClientCert
case SSLClientAuthTypeVerifyClientCertIfGiven:
return tls.VerifyClientCertIfGiven
case SSLClientAuthTypeRequireAndVerifyClientCert:
return tls.RequireAndVerifyClientCert
}
return tls.NoClientCert
}

View File

@@ -0,0 +1,271 @@
package sslconfigs
import (
"crypto/tls"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"errors"
"github.com/TeaOSLab/EdgeNode/internal/configs/serverconfigs/configutils"
"github.com/iwind/TeaGo/Tea"
"github.com/iwind/TeaGo/files"
"github.com/iwind/TeaGo/lists"
"github.com/iwind/TeaGo/utils/string"
"io/ioutil"
"strings"
"time"
)
// SSL证书
type SSLCertConfig struct {
Id string `yaml:"id" json:"id"`
On bool `yaml:"on" json:"on"`
Description string `yaml:"description" json:"description"` // 说明
CertFile string `yaml:"certFile" json:"certFile"`
KeyFile string `yaml:"keyFile" json:"keyFile"`
IsLocal bool `yaml:"isLocal" json:"isLocal"` // 是否为本地文件
TaskId string `yaml:"taskId" json:"taskId"` // 生成证书任务ID
IsShared bool `yaml:"isShared" json:"isShared"` // 是否为公用组件
ServerName string `yaml:"serverName" json:"serverName"` // 证书使用的主机名在请求TLS服务器时需要
IsCA bool `yaml:"isCA" json:"isCA"` // 是否为CA证书
dnsNames []string
cert *tls.Certificate
timeBefore time.Time
timeAfter time.Time
issuer pkix.Name
}
// 获取新的SSL证书
func NewSSLCertConfig(certFile string, keyFile string) *SSLCertConfig {
return &SSLCertConfig{
On: true,
Id: stringutil.Rand(16),
CertFile: certFile,
KeyFile: keyFile,
}
}
// 校验
func (this *SSLCertConfig) Init() error {
if this.IsShared {
shared := this.FindShared()
if shared == nil {
return errors.New("the shared cert has been deleted")
}
// 拷贝之前需要保留的
serverName := this.ServerName
// copy
configutils.CopyStructObject(this, shared)
this.ServerName = serverName
}
this.dnsNames = []string{}
if len(this.CertFile) == 0 {
return errors.New("cert file should not be empty")
}
// 分析证书
if this.IsCA { // CA证书
data, err := ioutil.ReadFile(this.FullCertPath())
if err != nil {
return err
}
index := -1
this.cert = &tls.Certificate{
Certificate: [][]byte{},
}
for {
index++
block, rest := pem.Decode(data)
if block == nil {
break
}
if len(rest) == 0 {
break
}
this.cert.Certificate = append(this.cert.Certificate, block.Bytes)
data = rest
c, err := x509.ParseCertificate(block.Bytes)
if err != nil {
return err
}
if c == nil {
return errors.New("no available certificates in file")
}
dnsNames := c.DNSNames
if len(dnsNames) > 0 {
for _, dnsName := range dnsNames {
if !lists.ContainsString(this.dnsNames, dnsName) {
this.dnsNames = append(this.dnsNames, dnsName)
}
}
}
if index == 0 {
this.timeBefore = c.NotBefore
this.timeAfter = c.NotAfter
this.issuer = c.Issuer
}
}
} else { // 证书+私钥
if len(this.KeyFile) == 0 {
return errors.New("key file should not be empty")
}
cert, err := tls.LoadX509KeyPair(this.FullCertPath(), this.FullKeyPath())
if err != nil {
return errors.New("load certificate '" + this.CertFile + "', '" + this.KeyFile + "' failed:" + err.Error())
}
for index, data := range cert.Certificate {
c, err := x509.ParseCertificate(data)
if err != nil {
continue
}
dnsNames := c.DNSNames
if len(dnsNames) > 0 {
for _, dnsName := range dnsNames {
if !lists.ContainsString(this.dnsNames, dnsName) {
this.dnsNames = append(this.dnsNames, dnsName)
}
}
}
if index == 0 {
this.timeBefore = c.NotBefore
this.timeAfter = c.NotAfter
this.issuer = c.Issuer
}
}
this.cert = &cert
}
return nil
}
// 查找共享的证书
func (this *SSLCertConfig) FindShared() *SSLCertConfig {
if !this.IsShared {
return nil
}
return SharedSSLCertList().FindCert(this.Id)
}
// 证书文件路径
func (this *SSLCertConfig) FullCertPath() string {
if len(this.CertFile) == 0 {
return ""
}
if !strings.ContainsAny(this.CertFile, "/\\") {
return Tea.ConfigFile(this.CertFile)
}
return this.CertFile
}
// 密钥文件路径
func (this *SSLCertConfig) FullKeyPath() string {
if len(this.KeyFile) == 0 {
return ""
}
if !strings.ContainsAny(this.KeyFile, "/\\") {
return Tea.ConfigFile(this.KeyFile)
}
return this.KeyFile
}
// 校验是否匹配某个域名
func (this *SSLCertConfig) MatchDomain(domain string) bool {
if len(this.dnsNames) == 0 {
return false
}
return configutils.MatchDomains(this.dnsNames, domain)
}
// 证书中的域名
func (this *SSLCertConfig) DNSNames() []string {
return this.dnsNames
}
// 获取证书对象
func (this *SSLCertConfig) CertObject() *tls.Certificate {
return this.cert
}
// 开始时间
func (this *SSLCertConfig) TimeBefore() time.Time {
return this.timeBefore
}
// 结束时间
func (this *SSLCertConfig) TimeAfter() time.Time {
return this.timeAfter
}
// 发行信息
func (this *SSLCertConfig) Issuer() pkix.Name {
return this.issuer
}
// 删除文件
func (this *SSLCertConfig) DeleteFiles() error {
if this.IsLocal {
return nil
}
var resultErr error = nil
if len(this.CertFile) > 0 && !strings.ContainsAny(this.CertFile, "/\\") {
err := files.NewFile(this.FullCertPath()).Delete()
if err != nil {
resultErr = err
}
}
if len(this.KeyFile) > 0 && !strings.ContainsAny(this.KeyFile, "/\\") {
err := files.NewFile(this.FullKeyPath()).Delete()
if err != nil {
resultErr = err
}
}
return resultErr
}
// 读取证书文件
func (this *SSLCertConfig) ReadCert() ([]byte, error) {
if len(this.CertFile) == 0 {
return nil, errors.New("cert file should not be empty")
}
if this.IsLocal {
return ioutil.ReadFile(this.CertFile)
}
return ioutil.ReadFile(Tea.ConfigFile(this.CertFile))
}
// 读取密钥文件
func (this *SSLCertConfig) ReadKey() ([]byte, error) {
if len(this.KeyFile) == 0 {
return nil, errors.New("key file should not be empty")
}
if this.IsLocal {
return ioutil.ReadFile(this.KeyFile)
}
return ioutil.ReadFile(Tea.ConfigFile(this.KeyFile))
}
// 匹配关键词
func (this *SSLCertConfig) MatchKeyword(keyword string) (matched bool, name string, tags []string) {
if configutils.MatchKeyword(this.Description, keyword) {
matched = true
name = this.Description
}
return
}

View File

@@ -0,0 +1,86 @@
package sslconfigs
import (
"github.com/TeaOSLab/EdgeNode/internal/configs/serverconfigs/shared"
"github.com/iwind/TeaGo/Tea"
"github.com/iwind/TeaGo/logs"
"gopkg.in/yaml.v3"
"io/ioutil"
)
const (
sslCertListFilename = "ssl.certs.conf"
)
// 获取证书列表实例
// 一定会返回不为nil的值
func SharedSSLCertList() *SSLCertList {
data, err := ioutil.ReadFile(Tea.ConfigFile(sslCertListFilename))
if err != nil {
return NewSSLCertList()
}
list := &SSLCertList{}
err = yaml.Unmarshal(data, list)
if err != nil {
logs.Error(err)
return NewSSLCertList()
}
return list
}
// 公共的SSL证书列表
type SSLCertList struct {
Certs []*SSLCertConfig `yaml:"certs" json:"certs"` // 证书
}
// 获取新对象
func NewSSLCertList() *SSLCertList {
return &SSLCertList{
Certs: []*SSLCertConfig{},
}
}
// 添加证书
func (this *SSLCertList) AddCert(cert *SSLCertConfig) {
this.Certs = append(this.Certs, cert)
}
// 删除证书
func (this *SSLCertList) RemoveCert(certId string) {
result := []*SSLCertConfig{}
for _, cert := range this.Certs {
if cert.Id == certId {
continue
}
result = append(result, cert)
}
this.Certs = result
}
// 查找证书
func (this *SSLCertList) FindCert(certId string) *SSLCertConfig {
if len(certId) == 0 {
return nil
}
for _, cert := range this.Certs {
if cert.Id == certId {
return cert
}
}
return nil
}
// 保存
func (this *SSLCertList) Save() error {
shared.Locker.Lock()
defer shared.Locker.Unlock()
data, err := yaml.Marshal(this)
if err != nil {
return err
}
return ioutil.WriteFile(Tea.ConfigFile(sslCertListFilename), data, 0777)
}

View File

@@ -0,0 +1,124 @@
// +build !go1.12
package sslconfigs
import "crypto/tls"
var AllTlsVersions = []TLSVersion{"SSL 3.0", "TLS 1.0", "TLS 1.1", "TLS 1.2"}
var AllTLSCipherSuites = []TLSCipherSuite{
"TLS_RSA_WITH_RC4_128_SHA",
"TLS_RSA_WITH_3DES_EDE_CBC_SHA",
"TLS_RSA_WITH_AES_128_CBC_SHA",
"TLS_RSA_WITH_AES_256_CBC_SHA",
"TLS_RSA_WITH_AES_128_CBC_SHA256",
"TLS_RSA_WITH_AES_128_GCM_SHA256",
"TLS_RSA_WITH_AES_256_GCM_SHA384",
"TLS_ECDHE_ECDSA_WITH_RC4_128_SHA",
"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA",
"TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA",
"TLS_ECDHE_RSA_WITH_RC4_128_SHA",
"TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA",
"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA",
"TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA",
"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256",
"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256",
"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
"TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384",
"TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384",
"TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305",
"TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305",
}
var TLSModernCipherSuites = []string{
"TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305",
"TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305",
"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
"TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384",
"TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384",
}
var TLSIntermediateCipherSuites = []string{
"TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305",
"TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305",
"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
"TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384",
"TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384",
"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256",
"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256",
"TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA",
"TLS_RSA_WITH_3DES_EDE_CBC_SHA",
}
func (this *SSLConfig) convertMinVersion() {
switch this.MinVersion {
case "SSL 3.0":
this.minVersion = tls.VersionSSL30
case "TLS 1.0":
this.minVersion = tls.VersionTLS10
case "TLS 1.1":
this.minVersion = tls.VersionTLS11
case "TLS 1.2":
this.minVersion = tls.VersionTLS12
default:
this.minVersion = tls.VersionTLS10
}
}
func (this *SSLConfig) initCipherSuites() {
// cipher suites
suites := []uint16{}
for _, suite := range this.CipherSuites {
switch suite {
case "TLS_RSA_WITH_RC4_128_SHA":
suites = append(suites, tls.TLS_RSA_WITH_RC4_128_SHA)
case "TLS_RSA_WITH_3DES_EDE_CBC_SHA":
suites = append(suites, tls.TLS_RSA_WITH_3DES_EDE_CBC_SHA)
case "TLS_RSA_WITH_AES_128_CBC_SHA":
suites = append(suites, tls.TLS_RSA_WITH_AES_128_CBC_SHA)
case "TLS_RSA_WITH_AES_256_CBC_SHA":
suites = append(suites, tls.TLS_RSA_WITH_AES_256_CBC_SHA)
case "TLS_RSA_WITH_AES_128_CBC_SHA256":
suites = append(suites, tls.TLS_RSA_WITH_AES_128_CBC_SHA256)
case "TLS_RSA_WITH_AES_128_GCM_SHA256":
suites = append(suites, tls.TLS_RSA_WITH_AES_128_GCM_SHA256)
case "TLS_RSA_WITH_AES_256_GCM_SHA384":
suites = append(suites, tls.TLS_RSA_WITH_AES_256_GCM_SHA384)
case "TLS_ECDHE_ECDSA_WITH_RC4_128_SHA":
suites = append(suites, tls.TLS_ECDHE_ECDSA_WITH_RC4_128_SHA)
case "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA":
suites = append(suites, tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA)
case "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA":
suites = append(suites, tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA)
case "TLS_ECDHE_RSA_WITH_RC4_128_SHA":
suites = append(suites, tls.TLS_ECDHE_RSA_WITH_RC4_128_SHA)
case "TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA":
suites = append(suites, tls.TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA)
case "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA":
suites = append(suites, tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA)
case "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA":
suites = append(suites, tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA)
case "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256":
suites = append(suites, tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256)
case "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256":
suites = append(suites, tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256)
case "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256":
suites = append(suites, tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256)
case "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256":
suites = append(suites, tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256)
case "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384":
suites = append(suites, tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384)
case "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384":
suites = append(suites, tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384)
case "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305":
suites = append(suites, tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305)
case "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305":
suites = append(suites, tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305)
}
}
this.cipherSuites = suites
}

View File

@@ -0,0 +1,148 @@
// +build go1.12
package sslconfigs
import (
"crypto/tls"
"os"
)
var AllTlsVersions = []TLSVersion{"SSL 3.0", "TLS 1.0", "TLS 1.1", "TLS 1.2", "TLS 1.3"}
var AllTLSCipherSuites = []TLSCipherSuite{
"TLS_RSA_WITH_RC4_128_SHA",
"TLS_RSA_WITH_3DES_EDE_CBC_SHA",
"TLS_RSA_WITH_AES_128_CBC_SHA",
"TLS_RSA_WITH_AES_256_CBC_SHA",
"TLS_RSA_WITH_AES_128_CBC_SHA256",
"TLS_RSA_WITH_AES_128_GCM_SHA256",
"TLS_RSA_WITH_AES_256_GCM_SHA384",
"TLS_ECDHE_ECDSA_WITH_RC4_128_SHA",
"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA",
"TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA",
"TLS_ECDHE_RSA_WITH_RC4_128_SHA",
"TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA",
"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA",
"TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA",
"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256",
"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256",
"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
"TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384",
"TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384",
"TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305",
"TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305",
"TLS_AES_128_GCM_SHA256",
"TLS_AES_256_GCM_SHA384",
"TLS_CHACHA20_POLY1305_SHA256",
}
var TLSModernCipherSuites = []string{
"TLS_AES_128_GCM_SHA256",
"TLS_CHACHA20_POLY1305_SHA256",
"TLS_AES_256_GCM_SHA384",
"TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305",
"TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305",
"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
"TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384",
"TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384",
}
var TLSIntermediateCipherSuites = []string{
"TLS_AES_128_GCM_SHA256",
"TLS_CHACHA20_POLY1305_SHA256",
"TLS_AES_256_GCM_SHA384",
"TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305",
"TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305",
"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
"TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384",
"TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384",
"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256",
"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256",
"TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA",
"TLS_RSA_WITH_3DES_EDE_CBC_SHA",
}
func (this *SSLConfig) convertMinVersion() {
switch this.MinVersion {
case "SSL 3.0":
this.minVersion = tls.VersionSSL30
case "TLS 1.0":
this.minVersion = tls.VersionTLS10
case "TLS 1.1":
this.minVersion = tls.VersionTLS11
case "TLS 1.2":
this.minVersion = tls.VersionTLS12
case "TLS 1.3":
this.minVersion = tls.VersionTLS13
os.Setenv("GODEBUG", "tls13=1") // TODO should be removed in go 1.14, in go 1.12 tls IS NOT FULL IMPLEMENTED YET
default:
this.minVersion = tls.VersionTLS10
}
}
func (this *SSLConfig) initCipherSuites() {
// cipher suites
suites := []uint16{}
for _, suite := range this.CipherSuites {
switch suite {
case "TLS_RSA_WITH_RC4_128_SHA":
suites = append(suites, tls.TLS_RSA_WITH_RC4_128_SHA)
case "TLS_RSA_WITH_3DES_EDE_CBC_SHA":
suites = append(suites, tls.TLS_RSA_WITH_3DES_EDE_CBC_SHA)
case "TLS_RSA_WITH_AES_128_CBC_SHA":
suites = append(suites, tls.TLS_RSA_WITH_AES_128_CBC_SHA)
case "TLS_RSA_WITH_AES_256_CBC_SHA":
suites = append(suites, tls.TLS_RSA_WITH_AES_256_CBC_SHA)
case "TLS_RSA_WITH_AES_128_CBC_SHA256":
suites = append(suites, tls.TLS_RSA_WITH_AES_128_CBC_SHA256)
case "TLS_RSA_WITH_AES_128_GCM_SHA256":
suites = append(suites, tls.TLS_RSA_WITH_AES_128_GCM_SHA256)
case "TLS_RSA_WITH_AES_256_GCM_SHA384":
suites = append(suites, tls.TLS_RSA_WITH_AES_256_GCM_SHA384)
case "TLS_ECDHE_ECDSA_WITH_RC4_128_SHA":
suites = append(suites, tls.TLS_ECDHE_ECDSA_WITH_RC4_128_SHA)
case "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA":
suites = append(suites, tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA)
case "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA":
suites = append(suites, tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA)
case "TLS_ECDHE_RSA_WITH_RC4_128_SHA":
suites = append(suites, tls.TLS_ECDHE_RSA_WITH_RC4_128_SHA)
case "TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA":
suites = append(suites, tls.TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA)
case "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA":
suites = append(suites, tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA)
case "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA":
suites = append(suites, tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA)
case "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256":
suites = append(suites, tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256)
case "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256":
suites = append(suites, tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256)
case "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256":
suites = append(suites, tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256)
case "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256":
suites = append(suites, tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256)
case "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384":
suites = append(suites, tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384)
case "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384":
suites = append(suites, tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384)
case "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305":
suites = append(suites, tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305)
case "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305":
suites = append(suites, tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305)
case "TLS_AES_128_GCM_SHA256":
suites = append(suites, tls.TLS_AES_128_GCM_SHA256)
case "TLS_AES_256_GCM_SHA384":
suites = append(suites, tls.TLS_AES_256_GCM_SHA384)
case "TLS_CHACHA20_POLY1305_SHA256":
suites = append(suites, tls.TLS_CHACHA20_POLY1305_SHA256)
}
}
this.cipherSuites = suites
}

View File

@@ -0,0 +1,63 @@
package sslconfigs
import (
"github.com/TeaOSLab/EdgeNode/internal/configs/serverconfigs/configutils"
"strconv"
"strings"
)
// HSTS设置
// 参考: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Strict-Transport-Security
type HSTSConfig struct {
On bool `yaml:"on" json:"on"`
MaxAge int `yaml:"maxAge" json:"maxAge"` // 单位秒
IncludeSubDomains bool `yaml:"includeSubDomains" json:"includeSubDomains"`
Preload bool `yaml:"preload" json:"preload"`
Domains []string `yaml:"domains" json:"domains"`
hasDomains bool
headerValue string
}
// 校验
func (this *HSTSConfig) Init() error {
this.hasDomains = len(this.Domains) > 0
this.headerValue = this.asHeaderValue()
return nil
}
// 判断是否匹配域名
func (this *HSTSConfig) Match(domain string) bool {
if !this.hasDomains {
return true
}
return configutils.MatchDomains(this.Domains, domain)
}
// Header Key
func (this *HSTSConfig) HeaderKey() string {
return "Strict-Transport-Security"
}
// 取得当前的Header值
func (this *HSTSConfig) HeaderValue() string {
return this.headerValue
}
// 转换为Header值
func (this *HSTSConfig) asHeaderValue() string {
b := strings.Builder{}
b.WriteString("max-age=")
if this.MaxAge > 0 {
b.WriteString(strconv.Itoa(this.MaxAge))
} else {
b.WriteString("31536000") // 1 year
}
if this.IncludeSubDomains {
b.WriteString("; includeSubDomains")
}
if this.Preload {
b.WriteString("; preload")
}
return b.String()
}

View File

@@ -0,0 +1,39 @@
package sslconfigs
import (
"github.com/iwind/TeaGo/assert"
"testing"
)
func TestHSTSConfig(t *testing.T) {
h := &HSTSConfig{}
h.Init()
t.Log(h.HeaderValue())
h.IncludeSubDomains = true
h.Init()
t.Log(h.HeaderValue())
h.Preload = true
h.Init()
t.Log(h.HeaderValue())
h.IncludeSubDomains = false
h.Init()
t.Log(h.HeaderValue())
h.MaxAge = 86400
h.Init()
t.Log(h.HeaderValue())
a := assert.NewAssertion(t)
a.IsTrue(h.Match("abc.com"))
h.Domains = []string{"abc.com"}
h.Init()
a.IsTrue(h.Match("abc.com"))
h.Domains = []string{"1.abc.com"}
h.Init()
a.IsFalse(h.Match("abc.com"))
}

View File

@@ -0,0 +1,10 @@
package serverconfigs
type WebConfig struct {
IsOn bool `yaml:"isOn" json:"isOn"`
Locations []*LocationConfig `yaml:"locations" json:"locations"` // 路径规则 TODO
// 本地静态资源配置
Root string `yaml:"root" json:"root"` // 资源根目录 TODO
}

View File

@@ -1,8 +0,0 @@
package configs
type WebConfig struct {
Locations []*LocationConfig `yaml:"locations"` // 路径规则
// 本地静态资源配置
Root string `yaml:"root" json:"root"` // 资源根目录
}