Strict configuration parsing (#3773)

* Test configuration loading more precisely

* Add strict configuration parsing
This commit is contained in:
Aarni Koskela
2023-11-16 09:42:49 +02:00
committed by GitHub
parent 184223cb2f
commit e8deb65c4b
11 changed files with 119 additions and 47 deletions

View File

@@ -27,7 +27,7 @@ import (
"github.com/samber/lo"
"gopkg.in/ini.v1"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/apimachinery/pkg/util/yaml"
yaml "k8s.io/apimachinery/pkg/util/yaml"
"github.com/fatedier/frp/pkg/config/legacy"
v1 "github.com/fatedier/frp/pkg/config/v1"
@@ -100,26 +100,39 @@ func LoadFileContentWithTemplate(path string, values *Values) ([]byte, error) {
return RenderWithTemplate(b, values)
}
func LoadConfigureFromFile(path string, c any) error {
func LoadConfigureFromFile(path string, c any, strict bool) error {
content, err := LoadFileContentWithTemplate(path, GetValues())
if err != nil {
return err
}
return LoadConfigure(content, c)
return LoadConfigure(content, c, strict)
}
// LoadConfigure loads configuration from bytes and unmarshal into c.
// Now it supports json, yaml and toml format.
func LoadConfigure(b []byte, c any) error {
func LoadConfigure(b []byte, c any, strict bool) error {
var tomlObj interface{}
// Try to unmarshal as TOML first; swallow errors from that (assume it's not valid TOML).
// TODO: caller should probably be able to specify the format, so we don't need to swallow errors.
if err := toml.Unmarshal(b, &tomlObj); err == nil {
b, err = json.Marshal(&tomlObj)
if err != nil {
return err
}
}
decoder := yaml.NewYAMLOrJSONDecoder(bytes.NewBuffer(b), 4096)
return decoder.Decode(c)
// If the buffer smells like JSON (first non-whitespace character is '{'), unmarshal as JSON directly.
if yaml.IsJSONBuffer(b) {
decoder := json.NewDecoder(bytes.NewBuffer(b))
if strict {
decoder.DisallowUnknownFields()
}
return decoder.Decode(c)
}
// It wasn't JSON. Unmarshal as YAML.
if strict {
return yaml.UnmarshalStrict(b, c)
}
return yaml.Unmarshal(b, c)
}
func NewProxyConfigurerFromMsg(m *msg.NewProxy, serverCfg *v1.ServerConfig) (v1.ProxyConfigurer, error) {
@@ -139,7 +152,7 @@ func NewProxyConfigurerFromMsg(m *msg.NewProxy, serverCfg *v1.ServerConfig) (v1.
return configurer, nil
}
func LoadServerConfig(path string) (*v1.ServerConfig, bool, error) {
func LoadServerConfig(path string, strict bool) (*v1.ServerConfig, bool, error) {
var (
svrCfg *v1.ServerConfig
isLegacyFormat bool
@@ -158,7 +171,7 @@ func LoadServerConfig(path string) (*v1.ServerConfig, bool, error) {
isLegacyFormat = true
} else {
svrCfg = &v1.ServerConfig{}
if err := LoadConfigureFromFile(path, svrCfg); err != nil {
if err := LoadConfigureFromFile(path, svrCfg, strict); err != nil {
return nil, false, err
}
}
@@ -168,7 +181,7 @@ func LoadServerConfig(path string) (*v1.ServerConfig, bool, error) {
return svrCfg, isLegacyFormat, nil
}
func LoadClientConfig(path string) (
func LoadClientConfig(path string, strict bool) (
*v1.ClientCommonConfig,
[]v1.ProxyConfigurer,
[]v1.VisitorConfigurer,
@@ -196,7 +209,7 @@ func LoadClientConfig(path string) (
isLegacyFormat = true
} else {
allCfg := v1.ClientConfig{}
if err := LoadConfigureFromFile(path, &allCfg); err != nil {
if err := LoadConfigureFromFile(path, &allCfg, strict); err != nil {
return nil, nil, nil, false, err
}
cliCfg = &allCfg.ClientCommonConfig
@@ -211,7 +224,7 @@ func LoadClientConfig(path string) (
// Load additional config from includes.
// legacy ini format alredy handle this in ParseClientConfig.
if len(cliCfg.IncludeConfigFiles) > 0 && !isLegacyFormat {
extPxyCfgs, extVisitorCfgs, err := LoadAdditionalClientConfigs(cliCfg.IncludeConfigFiles, isLegacyFormat)
extPxyCfgs, extVisitorCfgs, err := LoadAdditionalClientConfigs(cliCfg.IncludeConfigFiles, isLegacyFormat, strict)
if err != nil {
return nil, nil, nil, isLegacyFormat, err
}
@@ -242,7 +255,7 @@ func LoadClientConfig(path string) (
return cliCfg, pxyCfgs, visitorCfgs, isLegacyFormat, nil
}
func LoadAdditionalClientConfigs(paths []string, isLegacyFormat bool) ([]v1.ProxyConfigurer, []v1.VisitorConfigurer, error) {
func LoadAdditionalClientConfigs(paths []string, isLegacyFormat bool, strict bool) ([]v1.ProxyConfigurer, []v1.VisitorConfigurer, error) {
pxyCfgs := make([]v1.ProxyConfigurer, 0)
visitorCfgs := make([]v1.VisitorConfigurer, 0)
for _, path := range paths {
@@ -265,7 +278,7 @@ func LoadAdditionalClientConfigs(paths []string, isLegacyFormat bool) ([]v1.Prox
if matched, _ := filepath.Match(filepath.Join(absDir, filepath.Base(path)), absFile); matched {
// support yaml/json/toml
cfg := v1.ClientConfig{}
if err := LoadConfigureFromFile(absFile, &cfg); err != nil {
if err := LoadConfigureFromFile(absFile, &cfg, strict); err != nil {
return nil, nil, fmt.Errorf("load additional config from %s error: %v", absFile, err)
}
for _, c := range cfg.Proxies {

View File

@@ -15,6 +15,7 @@
package config
import (
"strings"
"testing"
"github.com/stretchr/testify/require"
@@ -22,9 +23,7 @@ import (
v1 "github.com/fatedier/frp/pkg/config/v1"
)
func TestLoadConfigure(t *testing.T) {
require := require.New(t)
content := `
const tomlServerContent = `
bindAddr = "127.0.0.1"
kcpBindPort = 7000
quicBindPort = 7001
@@ -33,13 +32,60 @@ custom404Page = "/abc.html"
transport.tcpKeepalive = 10
`
svrCfg := v1.ServerConfig{}
err := LoadConfigure([]byte(content), &svrCfg)
require.NoError(err)
require.EqualValues("127.0.0.1", svrCfg.BindAddr)
require.EqualValues(7000, svrCfg.KCPBindPort)
require.EqualValues(7001, svrCfg.QUICBindPort)
require.EqualValues(7005, svrCfg.TCPMuxHTTPConnectPort)
require.EqualValues("/abc.html", svrCfg.Custom404Page)
require.EqualValues(10, svrCfg.Transport.TCPKeepAlive)
const yamlServerContent = `
bindAddr: 127.0.0.1
kcpBindPort: 7000
quicBindPort: 7001
tcpmuxHTTPConnectPort: 7005
custom404Page: /abc.html
transport:
tcpKeepalive: 10
`
const jsonServerContent = `
{
"bindAddr": "127.0.0.1",
"kcpBindPort": 7000,
"quicBindPort": 7001,
"tcpmuxHTTPConnectPort": 7005,
"custom404Page": "/abc.html",
"transport": {
"tcpKeepalive": 10
}
}
`
func TestLoadServerConfig(t *testing.T) {
for _, content := range []string{tomlServerContent, yamlServerContent, jsonServerContent} {
svrCfg := v1.ServerConfig{}
err := LoadConfigure([]byte(content), &svrCfg, true)
require := require.New(t)
require.NoError(err)
require.EqualValues("127.0.0.1", svrCfg.BindAddr)
require.EqualValues(7000, svrCfg.KCPBindPort)
require.EqualValues(7001, svrCfg.QUICBindPort)
require.EqualValues(7005, svrCfg.TCPMuxHTTPConnectPort)
require.EqualValues("/abc.html", svrCfg.Custom404Page)
require.EqualValues(10, svrCfg.Transport.TCPKeepAlive)
}
}
// Test that loading in strict mode fails when the config is invalid.
func TestLoadServerConfigErrorMode(t *testing.T) {
for strict := range []bool{false, true} {
for _, content := range []string{tomlServerContent, yamlServerContent, jsonServerContent} {
// Break the content with an innocent typo
brokenContent := strings.Replace(content, "bindAddr", "bindAdur", 1)
svrCfg := v1.ServerConfig{}
err := LoadConfigure([]byte(brokenContent), &svrCfg, strict == 1)
require := require.New(t)
if strict == 1 {
require.ErrorContains(err, "bindAdur")
} else {
require.NoError(err)
// BindAddr didn't get parsed because of the typo.
require.EqualValues("", svrCfg.BindAddr)
}
}
}
}