mirror of
https://github.com/fatedier/frp.git
synced 2026-10-18 16:13:27 +08:00
transport.tls.trustedCaFile is installed as RootCAs, and omitting it sets InsecureSkipVerify, so system-root verification cannot be selected directly. Add an opt-in transport.tls.useSystemRoots that enables certificate verification with RootCAs nil when no trusted CA file is set, allowing Go to use system roots and native verification on macOS and Windows. Defaults are unchanged.
225 lines
7.2 KiB
Go
225 lines
7.2 KiB
Go
// Copyright 2023 The frp Authors
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package validation
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"slices"
|
|
|
|
"github.com/samber/lo"
|
|
|
|
v1 "github.com/fatedier/frp/pkg/config/v1"
|
|
"github.com/fatedier/frp/pkg/policy/featuregate"
|
|
"github.com/fatedier/frp/pkg/policy/security"
|
|
)
|
|
|
|
func (v *ConfigValidator) ValidateClientCommonConfig(c *v1.ClientCommonConfig) (Warning, error) {
|
|
var (
|
|
warnings Warning
|
|
errs error
|
|
)
|
|
|
|
validators := []func() (Warning, error){
|
|
func() (Warning, error) { return validateFeatureGates(c) },
|
|
func() (Warning, error) { return v.validateAuthConfig(&c.Auth) },
|
|
func() (Warning, error) { return nil, validateLogConfig(&c.Log) },
|
|
func() (Warning, error) { return nil, validateWebServerConfig(&c.WebServer) },
|
|
func() (Warning, error) { return validateTransportConfig(&c.Transport) },
|
|
func() (Warning, error) { return validateIncludeFiles(c.IncludeConfigFiles) },
|
|
}
|
|
|
|
for _, validator := range validators {
|
|
w, err := validator()
|
|
warnings = AppendError(warnings, w)
|
|
errs = AppendError(errs, err)
|
|
}
|
|
return warnings, errs
|
|
}
|
|
|
|
func validateFeatureGates(c *v1.ClientCommonConfig) (Warning, error) {
|
|
gates := featuregate.NewFeatureGate()
|
|
if err := gates.SetFromMap(c.FeatureGates); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if c.VirtualNet.Address != "" {
|
|
if !gates.Enabled(featuregate.VirtualNet) {
|
|
return nil, fmt.Errorf("VirtualNet feature is not enabled; enable it by setting the appropriate feature gate flag")
|
|
}
|
|
}
|
|
return nil, nil
|
|
}
|
|
|
|
// ClientConfigRequirements describes runtime capabilities needed by a client configuration.
|
|
type ClientConfigRequirements struct {
|
|
VirtualNet bool
|
|
}
|
|
|
|
// GetClientConfigRequirements returns the runtime capabilities needed by a client configuration.
|
|
func GetClientConfigRequirements(
|
|
common *v1.ClientCommonConfig,
|
|
proxyCfgs []v1.ProxyConfigurer,
|
|
visitorCfgs []v1.VisitorConfigurer,
|
|
) ClientConfigRequirements {
|
|
requirements := ClientConfigRequirements{}
|
|
if common != nil && common.VirtualNet.Address != "" {
|
|
requirements.VirtualNet = true
|
|
}
|
|
for _, cfg := range proxyCfgs {
|
|
if cfg.GetBaseConfig().Plugin.Type == v1.PluginVirtualNet {
|
|
requirements.VirtualNet = true
|
|
break
|
|
}
|
|
}
|
|
if !requirements.VirtualNet {
|
|
for _, cfg := range visitorCfgs {
|
|
if cfg.GetBaseConfig().Plugin.Type == v1.VisitorPluginVirtualNet {
|
|
requirements.VirtualNet = true
|
|
break
|
|
}
|
|
}
|
|
}
|
|
return requirements
|
|
}
|
|
|
|
func (v *ConfigValidator) validateAuthConfig(c *v1.AuthClientConfig) (Warning, error) {
|
|
var errs error
|
|
if !slices.Contains(SupportedAuthMethods, c.Method) {
|
|
errs = AppendError(errs, fmt.Errorf("invalid auth method, optional values are %v", SupportedAuthMethods))
|
|
}
|
|
if !lo.Every(SupportedAuthAdditionalScopes, c.AdditionalScopes) {
|
|
errs = AppendError(errs, fmt.Errorf("invalid auth additional scopes, optional values are %v", SupportedAuthAdditionalScopes))
|
|
}
|
|
|
|
errs = AppendError(errs, v.validateAuthTokenSource(c.Token, c.TokenSource))
|
|
|
|
if err := v.validateOIDCConfig(&c.OIDC); err != nil {
|
|
errs = AppendError(errs, err)
|
|
}
|
|
if c.Method == v1.AuthMethodOIDC && c.OIDC.TokenSource == nil {
|
|
if err := ValidateOIDCClientCredentialsConfig(&c.OIDC); err != nil {
|
|
errs = AppendError(errs, err)
|
|
}
|
|
}
|
|
return nil, errs
|
|
}
|
|
|
|
func (v *ConfigValidator) validateOIDCConfig(c *v1.AuthOIDCClientConfig) error {
|
|
if c.TokenSource == nil {
|
|
return nil
|
|
}
|
|
var errs error
|
|
// Validate oidc.tokenSource mutual exclusivity with other fields of oidc
|
|
if c.ClientID != "" || c.ClientSecret != "" || c.Audience != "" ||
|
|
c.Scope != "" || c.TokenEndpointURL != "" || len(c.AdditionalEndpointParams) > 0 ||
|
|
c.TrustedCaFile != "" || c.InsecureSkipVerify || c.ProxyURL != "" {
|
|
errs = AppendError(errs, fmt.Errorf("cannot specify both auth.oidc.tokenSource and any other field of auth.oidc"))
|
|
}
|
|
if c.TokenSource.Type == "exec" {
|
|
if err := v.ValidateUnsafeFeature(security.TokenSourceExec); err != nil {
|
|
errs = AppendError(errs, err)
|
|
}
|
|
}
|
|
if err := c.TokenSource.Validate(); err != nil {
|
|
errs = AppendError(errs, fmt.Errorf("invalid auth.oidc.tokenSource: %v", err))
|
|
}
|
|
return errs
|
|
}
|
|
|
|
func validateTransportConfig(c *v1.ClientTransportConfig) (Warning, error) {
|
|
var (
|
|
warnings Warning
|
|
errs error
|
|
)
|
|
|
|
if c.HeartbeatTimeout > 0 && c.HeartbeatInterval > 0 {
|
|
if c.HeartbeatTimeout < c.HeartbeatInterval {
|
|
errs = AppendError(errs, fmt.Errorf("invalid transport.heartbeatTimeout, heartbeat timeout should not less than heartbeat interval"))
|
|
}
|
|
}
|
|
|
|
if !lo.FromPtr(c.TLS.Enable) {
|
|
checkTLSConfig := func(name string, value string) Warning {
|
|
if value != "" {
|
|
return fmt.Errorf("%s is invalid when transport.tls.enable is false", name)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
warnings = AppendError(warnings, checkTLSConfig("transport.tls.certFile", c.TLS.CertFile))
|
|
warnings = AppendError(warnings, checkTLSConfig("transport.tls.keyFile", c.TLS.KeyFile))
|
|
warnings = AppendError(warnings, checkTLSConfig("transport.tls.trustedCaFile", c.TLS.TrustedCaFile))
|
|
// wss keeps TLS on regardless of transport.tls.enable.
|
|
if c.TLS.UseSystemRoots && c.Protocol != "wss" {
|
|
warnings = AppendError(warnings, fmt.Errorf("transport.tls.useSystemRoots is ignored when transport.tls.enable is false"))
|
|
}
|
|
}
|
|
|
|
if !slices.Contains(SupportedTransportProtocols, c.Protocol) {
|
|
errs = AppendError(errs, fmt.Errorf("invalid transport.protocol, optional values are %v", SupportedTransportProtocols))
|
|
}
|
|
if !slices.Contains(SupportedWireProtocols, c.WireProtocol) {
|
|
errs = AppendError(errs, fmt.Errorf("invalid transport.wireProtocol, optional values are %v", SupportedWireProtocols))
|
|
}
|
|
return warnings, errs
|
|
}
|
|
|
|
func validateIncludeFiles(files []string) (Warning, error) {
|
|
var errs error
|
|
for _, f := range files {
|
|
absDir, err := filepath.Abs(filepath.Dir(f))
|
|
if err != nil {
|
|
errs = AppendError(errs, fmt.Errorf("include: parse directory of %s failed: %v", f, err))
|
|
continue
|
|
}
|
|
if _, err := os.Stat(absDir); os.IsNotExist(err) {
|
|
errs = AppendError(errs, fmt.Errorf("include: directory of %s not exist", f))
|
|
}
|
|
}
|
|
return nil, errs
|
|
}
|
|
|
|
func ValidateAllClientConfig(
|
|
c *v1.ClientCommonConfig,
|
|
proxyCfgs []v1.ProxyConfigurer,
|
|
visitorCfgs []v1.VisitorConfigurer,
|
|
unsafeFeatures *security.UnsafeFeatures,
|
|
) (Warning, error) {
|
|
validator := NewConfigValidator(unsafeFeatures)
|
|
var warnings Warning
|
|
if c != nil {
|
|
warning, err := validator.ValidateClientCommonConfig(c)
|
|
warnings = AppendError(warnings, warning)
|
|
if err != nil {
|
|
return warnings, err
|
|
}
|
|
}
|
|
|
|
for _, c := range proxyCfgs {
|
|
if err := ValidateProxyConfigurerForClient(c); err != nil {
|
|
return warnings, fmt.Errorf("proxy %s: %v", c.GetBaseConfig().Name, err)
|
|
}
|
|
}
|
|
|
|
for _, c := range visitorCfgs {
|
|
if err := ValidateVisitorConfigurer(c); err != nil {
|
|
return warnings, fmt.Errorf("visitor %s: %v", c.GetBaseConfig().Name, err)
|
|
}
|
|
}
|
|
return warnings, nil
|
|
}
|