2
0
mirror of https://github.com/acepanel/panel.git synced 2026-02-04 06:47:20 +08:00

feat: 初步实现nginx配置解析器

This commit is contained in:
耗子
2024-10-14 03:32:51 +08:00
parent a7a68f6b34
commit 1bea44146f
8 changed files with 745 additions and 2 deletions

2
go.mod
View File

@@ -39,6 +39,7 @@ require (
github.com/shirou/gopsutil v2.21.11+incompatible
github.com/spf13/cast v1.7.0
github.com/stretchr/testify v1.9.0
github.com/tufanbarisyildirim/gonginx v0.0.0-20240907135031-d38eb71142ac
github.com/urfave/cli/v3 v3.0.0-alpha9
go.uber.org/zap v1.27.0
golang.org/x/crypto v0.28.0
@@ -125,7 +126,6 @@ require (
google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 // indirect
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
gotest.tools/v3 v3.5.1 // indirect
modernc.org/libc v1.60.1 // indirect
modernc.org/mathutil v1.6.0 // indirect
modernc.org/memory v1.8.0 // indirect

2
go.sum
View File

@@ -297,6 +297,8 @@ github.com/tklauser/go-sysconf v0.3.14 h1:g5vzr9iPFFz24v2KZXs/pvpvh8/V9Fw6vQK5ZZ
github.com/tklauser/go-sysconf v0.3.14/go.mod h1:1ym4lWMLUOhuBOPGtRcJm7tEGX4SCYNEEEtghGG/8uY=
github.com/tklauser/numcpus v0.8.0 h1:Mx4Wwe/FjZLeQsK/6kt2EOepwwSl7SmJrK5bV/dXYgY=
github.com/tklauser/numcpus v0.8.0/go.mod h1:ZJZlAY+dmR4eut8epnzf0u/VwodKmryxR8txiloSqBE=
github.com/tufanbarisyildirim/gonginx v0.0.0-20240907135031-d38eb71142ac h1:IXccMEFcB+UqGWae8OF9EoA0/8GCLlDj6s84LCU7y58=
github.com/tufanbarisyildirim/gonginx v0.0.0-20240907135031-d38eb71142ac/go.mod h1:itu4KWRgrfEwGcfNka+rV4houuirUau53i0diN4lG5g=
github.com/ulikunitz/xz v0.5.8/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
github.com/ulikunitz/xz v0.5.12 h1:37Nm15o69RwBkXM0J6A5OlE67RZTfzUxTj8fB3dfcsc=
github.com/ulikunitz/xz v0.5.12/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=

View File

@@ -27,7 +27,7 @@ type WebsiteDelete struct {
type WebsiteUpdate struct {
ID uint `form:"id" json:"id" validate:"required"`
Domains []string `form:"domains" json:"domains" validate:"required"`
Ports []uint `form:"ports" json:"ports" validate:"required"`
Listens []string `form:"listens" json:"listens" validate:"required"`
SSLPorts []uint `form:"ssl_ports" json:"ssl_ports" validate:"required"`
QUICPorts []uint `form:"quic_ports" json:"quic_ports" validate:"required"`
OCSP bool `form:"ocsp" json:"ocsp"`

36
pkg/nginx/data.go Normal file
View File

@@ -0,0 +1,36 @@
package nginx
var order = []string{"listen", "server_name", "index", "root",
"ssl_certificate", "ssl_certificate_key", "ssl_session_timeout", "ssl_session_cache", "ssl_protocols", "ssl_ciphers", "ssl_prefer_server_ciphers", "ssl_early_data", "ssl_stapling", "ssl_stapling_verify", "ssl_trusted_certificate",
"resolver", "error_page", "include", "if", "location", "access_log", "error_log"}
const defaultConf = `server
{
listen 80;
server_name localhost;
index index.php index.html;
root /www/wwwroot/default;
# 错误页配置,可自行修改
#error_page 502 /502.html;
error_page 404 /404.html;
include enable-php-0.conf;
# acme证书签发配置不可修改
include /www/server/vhost/acme/test.conf;
# 伪静态规则引入,修改后将导致面板设置的伪静态规则失效
include /www/server/vhost/rewrite/test.conf;
# 禁止访问部分敏感目录,可自行修改
location ~ ^/(\.user.ini|\.htaccess|\.git|\.svn)
{
return 404;
}
# 不记录静态资源的访问日志,可自行修改
location ~ .*\.(js|css|ttf|otf|woff|woff2|eot)$
{
expires 1h;
error_log /dev/null;
access_log /dev/null;
}
access_log /www/wwwlogs/default.log;
error_log /www/wwwlogs/default.log;
}
`

163
pkg/nginx/getter.go Normal file
View File

@@ -0,0 +1,163 @@
package nginx
import (
"fmt"
"slices"
"strings"
)
func (p *Parser) GetListen() ([][]string, error) {
directives, err := p.Find("server.listen")
if err != nil {
return nil, err
}
var result [][]string
for _, dir := range directives {
result = append(result, dir.GetParameters())
}
return result, nil
}
func (p *Parser) GetServerName() ([]string, error) {
directive, err := p.FindOne("server.server_name")
if err != nil {
return nil, err
}
return directive.GetParameters(), nil
}
func (p *Parser) GetIndex() ([]string, error) {
directive, err := p.FindOne("server.index")
if err != nil {
return nil, err
}
return directive.GetParameters(), nil
}
func (p *Parser) GetRoot() (string, error) {
directive, err := p.FindOne("server.root")
if err != nil {
return "", err
}
if len(directive.GetParameters()) == 0 {
return "", nil
}
return directive.GetParameters()[0], nil
}
func (p *Parser) GetIncludes() ([]string, error) {
directives, err := p.Find("server.include")
if err != nil {
return nil, err
}
var result []string
for _, dir := range directives {
result = append(result, dir.GetParameters()...)
}
return result, nil
}
func (p *Parser) GetPHP() (int, error) {
directives, err := p.Find("server.include")
if err != nil {
return 0, err
}
var result int
for _, dir := range directives {
if slices.ContainsFunc(dir.GetParameters(), func(s string) bool {
return strings.HasPrefix(s, "enable-php-") && strings.HasSuffix(s, ".conf")
}) {
_, err = fmt.Sscanf(dir.GetParameters()[0], "enable-php-%d.conf", &result)
}
}
return result, err
}
func (p *Parser) GetHTTPS() bool {
directive, err := p.FindOne("server.ssl_certificate")
if err != nil {
return false
}
if len(directive.GetParameters()) == 0 {
return false
}
return true
}
func (p *Parser) GetOCSP() (bool, error) {
directive, err := p.FindOne("server.ssl_stapling")
if err != nil {
return false, err
}
if len(directive.GetParameters()) == 0 {
return false, nil
}
return directive.GetParameters()[0] == "on", nil
}
func (p *Parser) GetHSTS() (bool, error) {
directives, err := p.Find("server.add_header")
if err != nil {
return false, err
}
for _, dir := range directives {
if slices.Contains(dir.GetParameters(), "Strict-Transport-Security") {
return true, nil
}
}
return false, nil
}
func (p *Parser) GetHTTPRedirect() (bool, error) {
directives, err := p.Find("server.if")
if err != nil {
return false, err
}
for _, dir := range directives {
for _, dir2 := range dir.GetBlock().GetDirectives() {
if dir2.GetName() == "return" && slices.Contains(dir2.GetParameters(), "https://$host$request_uri") {
return true, nil
}
}
}
return false, nil
}
func (p *Parser) GetAccessLog() (string, error) {
directive, err := p.FindOne("server.access_log")
if err != nil {
return "", err
}
if len(directive.GetParameters()) == 0 {
return "", nil
}
return directive.GetParameters()[0], nil
}
func (p *Parser) GetErrorLog() (string, error) {
directive, err := p.FindOne("server.error_log")
if err != nil {
return "", err
}
if len(directive.GetParameters()) == 0 {
return "", nil
}
return directive.GetParameters()[0], nil
}

170
pkg/nginx/parser.go Normal file
View File

@@ -0,0 +1,170 @@
package nginx
import (
"errors"
"slices"
"strings"
"github.com/tufanbarisyildirim/gonginx/config"
"github.com/tufanbarisyildirim/gonginx/dumper"
"github.com/tufanbarisyildirim/gonginx/parser"
)
// Parser Nginx vhost 配置解析器
type Parser struct {
c *config.Config
orderIndex map[string]int
}
func NewParser(str ...string) (*Parser, error) {
if len(str) == 0 {
str = append(str, defaultConf)
}
p := parser.NewStringParser(str[0], parser.WithSkipIncludeParsingErr(), parser.WithSkipValidDirectivesErr())
c, err := p.Parse()
if err != nil {
return nil, err
}
orderIndex := make(map[string]int)
for i, name := range order {
orderIndex[name] = i
}
return &Parser{c: c, orderIndex: orderIndex}, nil
}
func (p *Parser) Config() *config.Config {
return p.c
}
// Find 通过表达式查找配置
// eg: Find("server.listen")
func (p *Parser) Find(key string) ([]config.IDirective, error) {
parts := strings.Split(key, ".")
var block *config.Block
var ok bool
block = p.c.Block
for i := 0; i < len(parts)-1; i++ {
key = parts[i]
directives := block.FindDirectives(key)
if len(directives) == 0 {
return nil, errors.New("given key not found")
}
if len(directives) > 1 {
return nil, errors.New("multiple directives found")
}
block, ok = directives[0].GetBlock().(*config.Block)
if !ok {
return nil, errors.New("block is not *config.Block")
}
}
var result []config.IDirective
for _, dir := range block.GetDirectives() {
if dir.GetName() == parts[len(parts)-1] {
result = append(result, dir)
}
}
return result, nil
}
// FindOne 通过表达式查找一个配置
// eg: FindOne("server.server_name")
func (p *Parser) FindOne(key string) (config.IDirective, error) {
directives, err := p.Find(key)
if err != nil {
return nil, err
}
if len(directives) == 0 {
return nil, errors.New("given key not found")
}
return directives[0], nil
}
// Clear 通过表达式移除配置
// eg: Clear("server.server_name")
func (p *Parser) Clear(key string) error {
parts := strings.Split(key, ".")
last := parts[len(parts)-1]
parts = parts[:len(parts)-1]
var block *config.Block
var ok bool
block = p.c.Block
for i := 0; i < len(parts); i++ {
directives := block.FindDirectives(parts[i])
if len(directives) == 0 {
return errors.New("given key not found")
}
if len(directives) > 1 {
return errors.New("multiple directives found")
}
block, ok = directives[0].GetBlock().(*config.Block)
if !ok {
return errors.New("block is not *config.Block")
}
}
var newDirectives []config.IDirective
for _, directive := range block.GetDirectives() {
if directive.GetName() != last {
newDirectives = append(newDirectives, directive)
}
}
block.Directives = newDirectives
return nil
}
// Set 通过表达式设置配置
// eg: Set("server.server_name", []directive)
func (p *Parser) Set(key string, directives []*config.Directive) error {
parts := strings.Split(key, ".")
var block *config.Block
var ok bool
block = p.c.Block
for i := 0; i < len(parts); i++ {
sub := block.FindDirectives(parts[i])
if len(sub) == 0 {
return errors.New("given key not found")
}
if len(sub) > 1 {
return errors.New("multiple directives found")
}
block, ok = sub[0].GetBlock().(*config.Block)
if !ok {
return errors.New("block is not *config.Block")
}
}
for _, directive := range directives {
directive.SetParent(block)
block.Directives = append(block.Directives, directive)
}
return nil
}
func (p *Parser) Sort() {
p.sortDirectives(p.c.Directives, p.orderIndex)
}
func (p *Parser) Dump() string {
p.Sort()
return dumper.DumpConfig(p.c, dumper.IndentedStyle)
}
func (p *Parser) sortDirectives(directives []config.IDirective, orderIndex map[string]int) {
slices.SortFunc(directives, func(a config.IDirective, b config.IDirective) int {
return orderIndex[a.GetName()] - orderIndex[b.GetName()]
})
for _, directive := range directives {
if block, ok := directive.GetBlock().(*config.Block); ok {
p.sortDirectives(block.Directives, orderIndex)
}
}
}

26
pkg/nginx/parser_test.go Normal file
View File

@@ -0,0 +1,26 @@
package nginx
import (
"testing"
"github.com/stretchr/testify/suite"
)
type NginxTestSuite struct {
parser *Parser
suite.Suite
}
func TestNginxTestSuite(t *testing.T) {
parser, err := NewParser()
if err != nil {
t.Errorf("parse error %v", err)
}
suite.Run(t, &NginxTestSuite{
parser: parser,
})
}
func (suite *NginxTestSuite) TestA() {
suite.NoError(suite.parser.SetPHP(81))
}

346
pkg/nginx/setter.go Normal file
View File

@@ -0,0 +1,346 @@
package nginx
import (
"fmt"
"slices"
"strings"
"github.com/tufanbarisyildirim/gonginx/config"
)
func (p *Parser) SetListen(listen [][]string) error {
var directives []*config.Directive
for _, l := range listen {
directives = append(directives, &config.Directive{
Name: "listen",
Parameters: l,
})
}
if err := p.Clear("server.listen"); err != nil {
return err
}
return p.Set("server", directives)
}
func (p *Parser) SetServerName(serverName []string) error {
if err := p.Clear("server.server_name"); err != nil {
return err
}
return p.Set("server", []*config.Directive{
{
Name: "server_name",
Parameters: serverName,
},
})
}
func (p *Parser) SetIndex(index []string) error {
if err := p.Clear("server.index"); err != nil {
return err
}
return p.Set("server", []*config.Directive{
{
Name: "index",
Parameters: index,
},
})
}
func (p *Parser) SetRoot(root string) error {
if err := p.Clear("server.root"); err != nil {
return err
}
return p.Set("server", []*config.Directive{
{
Name: "root",
Parameters: []string{root},
},
})
}
func (p *Parser) SetIncludes(includes []string) error {
if err := p.Clear("server.include"); err != nil {
return err
}
var directives []*config.Directive
for _, i := range includes {
directives = append(directives, &config.Directive{
Name: "include",
Parameters: []string{i},
})
}
return p.Set("server", directives)
}
func (p *Parser) SetPHP(php int) error {
old, err := p.Find("server.include")
if err != nil {
return err
}
if err = p.Clear("server.include"); err != nil {
return err
}
var directives []*config.Directive
var foundFlag bool
for _, item := range old {
// 查找enable-php的配置
if slices.ContainsFunc(item.GetParameters(), func(s string) bool {
return strings.HasPrefix(s, "enable-php-") && strings.HasSuffix(s, ".conf")
}) {
foundFlag = true
directives = append(directives, &config.Directive{
Name: item.GetName(),
Parameters: []string{fmt.Sprintf("enable-php-%d.conf", php)},
Comment: item.GetComment(),
})
} else {
// 其余的原样保留
directives = append(directives, &config.Directive{
Name: item.GetName(),
Parameters: item.GetParameters(),
Comment: item.GetComment(),
})
}
}
// 如果没有找到enable-php的配置直接添加一个
if !foundFlag {
directives = append(directives, &config.Directive{
Name: "include",
Parameters: []string{fmt.Sprintf("enable-php-%d.conf", php)},
})
}
return p.Set("server", directives)
}
func (p *Parser) UnSetHTTPS() error {
if err := p.Clear("server.ssl_certificate"); err != nil {
return err
}
if err := p.Clear("server.ssl_certificate_key"); err != nil {
return err
}
if err := p.Clear("server.ssl_session_timeout"); err != nil {
return err
}
if err := p.Clear("server.ssl_session_cache"); err != nil {
return err
}
if err := p.Clear("server.ssl_protocols"); err != nil {
return err
}
if err := p.Clear("server.ssl_ciphers"); err != nil {
return err
}
if err := p.Clear("server.ssl_prefer_server_ciphers"); err != nil {
return err
}
if err := p.Clear("server.ssl_early_data"); err != nil {
return err
}
return nil
}
func (p *Parser) SetHTTPS(cert, key string) error {
if err := p.UnSetHTTPS(); err != nil {
return err
}
return p.Set("server", []*config.Directive{
{
Name: "ssl_certificate",
Parameters: []string{cert},
Comment: []string{"# https配置"},
},
{
Name: "ssl_certificate_key",
Parameters: []string{key},
},
{
Name: "ssl_session_timeout",
Parameters: []string{"1d"},
},
{
Name: "ssl_session_cache",
Parameters: []string{"shared:SSL:10m"},
},
{
Name: "ssl_protocols",
Parameters: []string{"TLSv1.2", "TLSv1.3"},
},
{
Name: "ssl_ciphers",
Parameters: []string{"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"},
},
{
Name: "ssl_prefer_server_ciphers",
Parameters: []string{"off"},
},
{
Name: "ssl_early_data",
Parameters: []string{"on"},
},
})
}
func (p *Parser) SetOCSP(ocsp bool) error {
if err := p.Clear("server.ssl_stapling"); err != nil {
return err
}
if err := p.Clear("server.ssl_stapling_verify"); err != nil {
return err
}
if ocsp {
return p.Set("server", []*config.Directive{
{
Name: "ssl_stapling",
Parameters: []string{"on"},
},
{
Name: "ssl_stapling_verify",
Parameters: []string{"on"},
},
})
}
return nil
}
func (p *Parser) SetHSTS(hsts bool) error {
old, err := p.Find("server.add_header")
if err != nil {
return err
}
if err = p.Clear("server.add_header"); err != nil {
return err
}
var directives []*config.Directive
var foundFlag bool
for _, dir := range old {
if slices.Contains(dir.GetParameters(), "Strict-Transport-Security") {
foundFlag = true
if hsts {
directives = append(directives, &config.Directive{
Name: dir.GetName(),
Parameters: []string{"Strict-Transport-Security", "max-age=31536000"},
Comment: dir.GetComment(),
})
}
} else {
directives = append(directives, &config.Directive{
Name: dir.GetName(),
Parameters: dir.GetParameters(),
Comment: dir.GetComment(),
})
}
}
if !foundFlag && hsts {
directives = append(directives, &config.Directive{
Name: "add_header",
Parameters: []string{"Strict-Transport-Security", "max-age=31536000"},
Comment: []string{"# hsts配置"},
})
}
return p.Set("server", directives)
}
func (p *Parser) SetHTTPRedirect(httpRedirect bool) error {
old, err := p.Find("server.if")
if err != nil {
return err
}
if err = p.Clear("server.if"); err != nil {
return err
}
var directives []*config.Directive
var foundFlag bool
for _, dir := range old {
for _, dir2 := range dir.GetBlock().GetDirectives() { // if 中所有指令
if block, ok := dir2.GetBlock().(*config.Block); ok {
var newDirectives []config.IDirective
for _, directive := range block.GetDirectives() {
if !httpRedirect {
// 不启用http重定向则判断并移除特定的return指令
if directive.GetName() != "return" && !slices.Contains(directive.GetParameters(), "https://$host$request_uri") {
newDirectives = append(newDirectives, directive)
}
} else {
// 启用http重定向需要检查防止重复添加
if directive.GetName() == "return" && slices.Contains(directive.GetParameters(), "https://$host$request_uri") {
foundFlag = true
}
newDirectives = append(newDirectives, directive)
}
}
block.Directives = newDirectives
}
}
directives = append(directives, &config.Directive{
Block: dir.GetBlock(),
Name: dir.GetName(),
Parameters: dir.GetParameters(),
Comment: dir.GetComment(),
})
}
if !foundFlag && httpRedirect {
ifDir := &config.Directive{
Name: "if",
Block: &config.Block{},
Parameters: []string{"($scheme", "=", "http)"},
Comment: []string{"# http重定向"},
}
redirectDir := &config.Directive{
Name: "return",
Parameters: []string{"301", "https://$host$request_uri"},
}
redirectDir.SetParent(ifDir.GetBlock())
ifBlock := ifDir.GetBlock().(*config.Block)
ifBlock.Directives = append(ifBlock.Directives, redirectDir)
directives = append(directives, ifDir)
}
return p.Set("server", directives)
}
func (p *Parser) SetAccessLog(accessLog string) error {
if err := p.Clear("server.access_log"); err != nil {
return err
}
return p.Set("server", []*config.Directive{
{
Name: "access_log",
Parameters: []string{accessLog},
},
})
}
func (p *Parser) SetErrorLog(errorLog string) error {
if err := p.Clear("server.error_log"); err != nil {
return err
}
return p.Set("server", []*config.Directive{
{
Name: "error_log",
Parameters: []string{errorLog},
},
})
}