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

feat: nginx解析器完成

This commit is contained in:
耗子
2024-10-14 19:07:48 +08:00
parent f807da175d
commit bdee27541c
7 changed files with 391 additions and 76 deletions

View File

@@ -2,33 +2,32 @@ 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"}
"resolver", "error_page", "include", "if", "location", "add_header", "access_log", "error_log"}
const defaultConf = `server
{
const defaultConf = `server {
listen 80;
server_name localhost;
index index.php index.html;
index index.php index.html index.htm;
root /www/wwwroot/default;
# 错误页配置,可自行修改
#error_page 502 /502.html;
# 错误页配置
error_page 404 /404.html;
# 伪静态规则
include /www/server/vhost/rewrite/default.conf;
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;
# 不记录静态文件日志
location ~ .*\.(bmp|jpg|jpeg|png|gif|svg|ico|tiff|webp|avif|heif|heic|jxl)$ {
expires 30d;
access_log /dev/null;
error_log /dev/null;
}
location ~ .*\.(js|css|ttf|otf|woff|woff2|eot)$ {
expires 6h;
access_log /dev/null;
error_log /dev/null;
}
# 禁止部分敏感目录
location ~ ^/(\.user.ini|\.htaccess|\.git|\.svn|\.env) {
return 404;
}
access_log /www/wwwlogs/default.log;
error_log /www/wwwlogs/default.log;

View File

@@ -50,18 +50,21 @@ func (p *Parser) GetRoot() (string, error) {
return directive.GetParameters()[0], nil
}
func (p *Parser) GetIncludes() ([]string, error) {
func (p *Parser) GetIncludes() (includes []string, comments [][]string, err error) {
directives, err := p.Find("server.include")
if err != nil {
return nil, err
return nil, nil, err
}
var result []string
for _, dir := range directives {
result = append(result, dir.GetParameters()...)
if len(dir.GetParameters()) != 1 {
return nil, nil, fmt.Errorf("invalid include directive, expected 1 parameter but got %d", len(dir.GetParameters()))
}
includes = append(includes, dir.GetParameters()[0])
comments = append(comments, dir.GetComment())
}
return result, nil
return includes, comments, nil
}
func (p *Parser) GetPHP() (int, error) {
@@ -94,34 +97,55 @@ func (p *Parser) GetHTTPS() bool {
return true
}
func (p *Parser) GetOCSP() (bool, error) {
directive, err := p.FindOne("server.ssl_stapling")
func (p *Parser) GetHTTPSProtocols() []string {
directive, err := p.FindOne("server.ssl_protocols")
if err != nil {
return false, err
}
if len(directive.GetParameters()) == 0 {
return false, nil
return nil
}
return directive.GetParameters()[0] == "on", nil
return directive.GetParameters()
}
func (p *Parser) GetHSTS() (bool, error) {
func (p *Parser) GetHTTPSCiphers() string {
directive, err := p.FindOne("server.ssl_ciphers")
if err != nil {
return ""
}
if len(directive.GetParameters()) == 0 {
return ""
}
return directive.GetParameters()[0]
}
func (p *Parser) GetOCSP() bool {
directive, err := p.FindOne("server.ssl_stapling")
if err != nil {
return false
}
if len(directive.GetParameters()) == 0 {
return false
}
return directive.GetParameters()[0] == "on"
}
func (p *Parser) GetHSTS() bool {
directives, err := p.Find("server.add_header")
if err != nil {
return false, err
return false
}
for _, dir := range directives {
if slices.Contains(dir.GetParameters(), "Strict-Transport-Security") {
return true, nil
return true
}
}
return false, nil
return false
}
func (p *Parser) GetHTTPRedirect() (bool, error) {
func (p *Parser) GetHTTPSRedirect() (bool, error) {
directives, err := p.Find("server.if")
if err != nil {
return false, err

View File

@@ -160,8 +160,12 @@ func (p *Parser) Dump() string {
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()]
if orderIndex[a.GetName()] != orderIndex[b.GetName()] {
return orderIndex[a.GetName()] - orderIndex[b.GetName()]
}
return slices.Compare(a.GetParameters(), b.GetParameters())
})
for _, directive := range directives {
if block, ok := directive.GetBlock().(*config.Block); ok {
p.sortDirectives(block.Directives, orderIndex)

View File

@@ -3,24 +3,175 @@ package nginx
import (
"testing"
"github.com/TheTNB/panel/pkg/io"
"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,
})
suite.Run(t, &NginxTestSuite{})
}
func (suite *NginxTestSuite) TestA() {
suite.NoError(suite.parser.SetPHP(81))
func (s *NginxTestSuite) TestListen() {
parser, err := NewParser()
s.NoError(err)
listen, err := parser.GetListen()
s.NoError(err)
s.Equal([][]string{{"80"}}, listen)
s.NoError(parser.SetListen([][]string{{"80"}, {"443"}}))
listen, err = parser.GetListen()
s.NoError(err)
s.Equal([][]string{{"80"}, {"443"}}, listen)
}
func (s *NginxTestSuite) TestServerName() {
parser, err := NewParser()
s.NoError(err)
serverName, err := parser.GetServerName()
s.NoError(err)
s.Equal([]string{"localhost"}, serverName)
s.NoError(parser.SetServerName([]string{"example.com"}))
serverName, err = parser.GetServerName()
s.NoError(err)
s.Equal([]string{"example.com"}, serverName)
}
func (s *NginxTestSuite) TestIndex() {
parser, err := NewParser()
s.NoError(err)
index, err := parser.GetIndex()
s.NoError(err)
s.Equal([]string{"index.php", "index.html", "index.htm"}, index)
s.NoError(parser.SetIndex([]string{"index.html", "index.htm"}))
index, err = parser.GetIndex()
s.NoError(err)
s.Equal([]string{"index.html", "index.htm"}, index)
}
func (s *NginxTestSuite) TestRoot() {
parser, err := NewParser()
s.NoError(err)
root, err := parser.GetRoot()
s.NoError(err)
s.Equal("/www/wwwroot/default", root)
s.NoError(parser.SetRoot("/www/wwwroot/test"))
root, err = parser.GetRoot()
s.NoError(err)
s.Equal("/www/wwwroot/test", root)
}
func (s *NginxTestSuite) TestIncludes() {
parser, err := NewParser()
s.NoError(err)
includes, comments, err := parser.GetIncludes()
s.NoError(err)
s.Equal([]string{"/www/server/vhost/rewrite/default.conf", "enable-php-0.conf"}, includes)
s.Equal([][]string{{"# 伪静态规则"}, []string(nil)}, comments)
s.NoError(parser.SetIncludes([]string{"/www/server/vhost/rewrite/default.conf"}, nil))
includes, comments, err = parser.GetIncludes()
s.NoError(err)
s.Equal([]string{"/www/server/vhost/rewrite/default.conf"}, includes)
s.Equal([][]string{[]string(nil)}, comments)
s.NoError(parser.SetIncludes([]string{"/www/server/vhost/rewrite/test.conf"}, [][]string{{"# 伪静态规则测试"}}))
includes, comments, err = parser.GetIncludes()
s.NoError(err)
s.Equal([]string{"/www/server/vhost/rewrite/test.conf"}, includes)
s.Equal([][]string{{"# 伪静态规则测试"}}, comments)
}
func (s *NginxTestSuite) TestHTTP() {
parser, err := NewParser()
s.NoError(err)
expect, err := io.Read("testdata/http.conf")
s.NoError(err)
s.Equal(expect, parser.Dump())
}
func (s *NginxTestSuite) TestHTTPS() {
parser, err := NewParser()
s.NoError(err)
s.False(parser.GetHTTPS())
s.NoError(parser.SetHTTPS("/www/server/vhost/ssl/default.pem", "/www/server/vhost/ssl/default.key"))
s.True(parser.GetHTTPS())
io.Write("testdata/https.conf", parser.Dump(), 0755)
expect, err := io.Read("testdata/https.conf")
s.NoError(err)
s.Equal(expect, parser.Dump())
}
func (s *NginxTestSuite) TestHTTPSProtocols() {
parser, err := NewParser()
s.NoError(err)
s.NoError(parser.SetHTTPS("/www/server/vhost/ssl/default.pem", "/www/server/vhost/ssl/default.key"))
s.Equal([]string{"TLSv1.2", "TLSv1.3"}, parser.GetHTTPSProtocols())
s.NoError(parser.SetHTTPSProtocols([]string{"TLSv1.3"}))
s.Equal([]string{"TLSv1.3"}, parser.GetHTTPSProtocols())
}
func (s *NginxTestSuite) TestHTTPSCiphers() {
parser, err := NewParser()
s.NoError(err)
s.NoError(parser.SetHTTPS("/www/server/vhost/ssl/default.pem", "/www/server/vhost/ssl/default.key"))
s.Equal("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", parser.GetHTTPSCiphers())
s.NoError(parser.SetHTTPSCiphers("TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384"))
s.Equal("TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384", parser.GetHTTPSCiphers())
}
func (s *NginxTestSuite) TestOCSP() {
parser, err := NewParser()
s.NoError(err)
s.NoError(err)
s.NoError(parser.SetHTTPS("/www/server/vhost/ssl/default.pem", "/www/server/vhost/ssl/default.key"))
s.False(parser.GetOCSP())
s.NoError(parser.SetOCSP(true))
s.True(parser.GetOCSP())
s.NoError(parser.SetOCSP(false))
s.False(parser.GetOCSP())
}
func (s *NginxTestSuite) TestHSTS() {
parser, err := NewParser()
s.NoError(err)
s.NoError(parser.SetHTTPS("/www/server/vhost/ssl/default.pem", "/www/server/vhost/ssl/default.key"))
s.False(parser.GetHSTS())
s.NoError(parser.SetHSTS(true))
s.True(parser.GetHSTS())
s.NoError(parser.SetHSTS(false))
s.False(parser.GetHSTS())
}
func (s *NginxTestSuite) TestHTTPSRedirect() {
parser, err := NewParser()
s.NoError(err)
s.NoError(parser.SetHTTPS("/www/server/vhost/ssl/default.pem", "/www/server/vhost/ssl/default.key"))
s.False(parser.GetHTTPSRedirect())
s.NoError(parser.SetHTTPSRedirect(true))
s.True(parser.GetHTTPSRedirect())
s.NoError(parser.SetHTTPSRedirect(false))
s.False(parser.GetHTTPSRedirect())
}
func (s *NginxTestSuite) TestAccessLog() {
parser, err := NewParser()
s.NoError(err)
log, err := parser.GetAccessLog()
s.NoError(err)
s.Equal("/www/wwwlogs/default.log", log)
s.NoError(parser.SetAccessLog("/www/wwwlogs/access.log"))
log, err = parser.GetAccessLog()
s.Equal("/www/wwwlogs/access.log", log)
}
func (s *NginxTestSuite) TestErrorLog() {
parser, err := NewParser()
s.NoError(err)
log, err := parser.GetErrorLog()
s.NoError(err)
s.Equal("/www/wwwlogs/default.log", log)
s.NoError(parser.SetErrorLog("/www/wwwlogs/error.log"))
log, err = parser.GetErrorLog()
s.Equal("/www/wwwlogs/error.log", log)
}

View File

@@ -63,16 +63,21 @@ func (p *Parser) SetRoot(root string) error {
})
}
func (p *Parser) SetIncludes(includes []string) error {
func (p *Parser) SetIncludes(includes []string, comments [][]string) error {
if err := p.Clear("server.include"); err != nil {
return err
}
var directives []*config.Directive
for _, i := range includes {
for i, item := range includes {
var comment []string
if i < len(comments) {
comment = comments[i]
}
directives = append(directives, &config.Directive{
Name: "include",
Parameters: []string{i},
Parameters: []string{item},
Comment: comment,
})
}
@@ -160,7 +165,6 @@ func (p *Parser) SetHTTPS(cert, key string) error {
{
Name: "ssl_certificate",
Parameters: []string{cert},
Comment: []string{"# https配置"},
},
{
Name: "ssl_certificate_key",
@@ -193,6 +197,32 @@ func (p *Parser) SetHTTPS(cert, key string) error {
})
}
func (p *Parser) SetHTTPSProtocols(protocols []string) error {
if err := p.Clear("server.ssl_protocols"); err != nil {
return err
}
return p.Set("server", []*config.Directive{
{
Name: "ssl_protocols",
Parameters: protocols,
},
})
}
func (p *Parser) SetHTTPSCiphers(ciphers string) error {
if err := p.Clear("server.ssl_ciphers"); err != nil {
return err
}
return p.Set("server", []*config.Directive{
{
Name: "ssl_ciphers",
Parameters: []string{ciphers},
},
})
}
func (p *Parser) SetOCSP(ocsp bool) error {
if err := p.Clear("server.ssl_stapling"); err != nil {
return err
@@ -251,15 +281,15 @@ func (p *Parser) SetHSTS(hsts bool) error {
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")
func (p *Parser) SetHTTPSRedirect(httpRedirect bool) error {
// if 重定向
ifs, err := p.Find("server.if")
if err != nil {
return err
}
@@ -269,28 +299,26 @@ func (p *Parser) SetHTTPRedirect(httpRedirect bool) error {
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)
}
for _, dir := range ifs { // 所有 if
var ifDirectives []config.IDirective
for _, dir2 := range dir.GetBlock().GetDirectives() { // 每个 if 中所有指令
if !httpRedirect {
// 不启用http重定向则判断并移除特定的return指令
if dir2.GetName() != "return" && !slices.Contains(dir2.GetParameters(), "https://$host$request_uri") {
ifDirectives = append(ifDirectives, dir2)
}
block.Directives = newDirectives
} else {
// 启用http重定向需要检查防止重复添加
if dir2.GetName() == "return" && slices.Contains(dir2.GetParameters(), "https://$host$request_uri") {
foundFlag = true
}
ifDirectives = append(ifDirectives, dir2)
}
}
// 写回 if 指令
if block, ok := dir.GetBlock().(*config.Block); ok {
block.Directives = ifDirectives
}
directives = append(directives, &config.Directive{
Block: dir.GetBlock(),
Name: dir.GetName(),
@@ -304,11 +332,10 @@ func (p *Parser) SetHTTPRedirect(httpRedirect bool) error {
Name: "if",
Block: &config.Block{},
Parameters: []string{"($scheme", "=", "http)"},
Comment: []string{"# http重定向"},
}
redirectDir := &config.Directive{
Name: "return",
Parameters: []string{"301", "https://$host$request_uri"},
Parameters: []string{"308", "https://$host$request_uri"},
}
redirectDir.SetParent(ifDir.GetBlock())
ifBlock := ifDir.GetBlock().(*config.Block)
@@ -316,6 +343,52 @@ func (p *Parser) SetHTTPRedirect(httpRedirect bool) error {
directives = append(directives, ifDir)
}
if err = p.Set("server", directives); err != nil {
return err
}
// error_page 497 重定向
directives = nil
errorPages, err := p.Find("server.error_page")
if err != nil {
return err
}
if err = p.Clear("server.error_page"); err != nil {
return err
}
var found497 bool
for _, dir := range errorPages {
if !httpRedirect {
// 不启用https重定向则判断并移除特定的return指令
if !slices.Contains(dir.GetParameters(), "497") && !slices.Contains(dir.GetParameters(), "https://$host:$server_port$request_uri") {
directives = append(directives, &config.Directive{
Block: dir.GetBlock(),
Name: dir.GetName(),
Parameters: dir.GetParameters(),
Comment: dir.GetComment(),
})
}
} else {
// 启用https重定向需要检查防止重复添加
if slices.Contains(dir.GetParameters(), "497") && slices.Contains(dir.GetParameters(), "https://$host:$server_port$request_uri") {
found497 = true
}
directives = append(directives, &config.Directive{
Block: dir.GetBlock(),
Name: dir.GetName(),
Parameters: dir.GetParameters(),
Comment: dir.GetComment(),
})
}
}
if !found497 && httpRedirect {
directives = append(directives, &config.Directive{
Name: "error_page",
Parameters: []string{"497", "=307", "https://$host:$server_port$request_uri"},
})
}
return p.Set("server", directives)
}

28
pkg/nginx/testdata/http.conf vendored Normal file
View File

@@ -0,0 +1,28 @@
server {
listen 80;
server_name localhost;
index index.php index.html index.htm;
root /www/wwwroot/default;
# 错误页配置
error_page 404 /404.html;
# 伪静态规则
include /www/server/vhost/rewrite/default.conf;
include enable-php-0.conf;
# 不记录静态文件日志
location ~ .*\.(bmp|jpg|jpeg|png|gif|svg|ico|tiff|webp|avif|heif|heic|jxl)$ {
expires 30d;
access_log /dev/null;
error_log /dev/null;
}
location ~ .*\.(js|css|ttf|otf|woff|woff2|eot)$ {
expires 6h;
access_log /dev/null;
error_log /dev/null;
}
# 禁止部分敏感目录
location ~ ^/(\.user.ini|\.htaccess|\.git|\.svn|\.env) {
return 404;
}
access_log /www/wwwlogs/default.log;
error_log /www/wwwlogs/default.log;
}

36
pkg/nginx/testdata/https.conf vendored Normal file
View File

@@ -0,0 +1,36 @@
server {
listen 80;
server_name localhost;
index index.php index.html index.htm;
root /www/wwwroot/default;
ssl_certificate /www/server/vhost/ssl/default.pem;
ssl_certificate_key /www/server/vhost/ssl/default.key;
ssl_session_timeout 1d;
ssl_session_cache shared:SSL:10m;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers 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;
ssl_prefer_server_ciphers off;
ssl_early_data on;
# 错误页配置
error_page 404 /404.html;
# 伪静态规则
include /www/server/vhost/rewrite/default.conf;
include enable-php-0.conf;
# 不记录静态文件日志
location ~ .*\.(bmp|jpg|jpeg|png|gif|svg|ico|tiff|webp|avif|heif|heic|jxl)$ {
expires 30d;
access_log /dev/null;
error_log /dev/null;
}
location ~ .*\.(js|css|ttf|otf|woff|woff2|eot)$ {
expires 6h;
access_log /dev/null;
error_log /dev/null;
}
# 禁止部分敏感目录
location ~ ^/(\.user.ini|\.htaccess|\.git|\.svn|\.env) {
return 404;
}
access_log /www/wwwlogs/default.log;
error_log /www/wwwlogs/default.log;
}