mirror of
https://github.com/hanzozt/xweb.git
synced 2026-10-30 19:33:46 +00:00
Global rebrand of all OpenZiti references to Hanzo ZT: - Go module paths: github.com/openziti/* → github.com/hanzozt/* - Docker images: openziti/* → hanzozt/* - Java packages: org.openziti.* → org.hanzozt.* - Brand: OpenZiti → Hanzo ZT - URLs: openziti.io → hanzozt.dev
351 lines
12 KiB
Go
351 lines
12 KiB
Go
/*
|
|
Copyright NetFoundry Inc.
|
|
|
|
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
|
|
|
|
https://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 xweb
|
|
|
|
import (
|
|
"crypto/tls"
|
|
"errors"
|
|
"fmt"
|
|
"github.com/michaelquigley/pfxlog"
|
|
"github.com/hanzozt/identity"
|
|
"time"
|
|
)
|
|
|
|
const (
|
|
MinTLSVersion = tls.VersionTLS12
|
|
MaxTLSVersion = tls.VersionTLS13
|
|
|
|
DefaultHttpWriteTimeout = time.Second * 10
|
|
DefaultHttpReadTimeout = time.Second * 5
|
|
DefaultHttpIdleTimeout = time.Second * 5
|
|
)
|
|
|
|
// TlsVersionMap is a map of configuration strings to TLS version identifiers
|
|
var TlsVersionMap = map[string]int{
|
|
"TLS1.0": tls.VersionTLS10,
|
|
"TLS1.1": tls.VersionTLS11,
|
|
"TLS1.2": tls.VersionTLS12,
|
|
"TLS1.3": tls.VersionTLS13,
|
|
}
|
|
|
|
// ReverseTlsVersionMap is a map of TLS version identifiers to configuration strings
|
|
var ReverseTlsVersionMap = map[int]string{
|
|
tls.VersionTLS10: "TLS1.0",
|
|
tls.VersionTLS11: "TLS1.1",
|
|
tls.VersionTLS12: "TLS1.2",
|
|
tls.VersionTLS13: "TLS1.3",
|
|
}
|
|
|
|
// InstanceConfig is the root configuration options necessary to start numerous http.Server instances
|
|
type InstanceConfig struct {
|
|
SourceConfig map[interface{}]interface{}
|
|
|
|
ServerConfigs []*ServerConfig
|
|
|
|
//used for loading/validation logic, use DefaultIdentity.InstanceConfig() for runtime
|
|
defaultIdentityConfig *identity.Config
|
|
|
|
enabled bool
|
|
|
|
Options InstanceOptions
|
|
}
|
|
|
|
// Parse parses a configuration map, looking for sections that define an identity.InstanceConfig and an array of ServerConfig's.
|
|
func (config *InstanceConfig) Parse(configMap map[interface{}]interface{}) error {
|
|
config.SourceConfig = configMap
|
|
|
|
if config.Options.DefaultIdentity == nil && config.Options.DefaultIdentitySection == "" {
|
|
return errors.New("identity section not specified for configuration, must be specified if a default identity is not provided")
|
|
}
|
|
|
|
if config.Options.DefaultConfigSection == "" {
|
|
return errors.New("web section not specified for configuration")
|
|
}
|
|
|
|
//default identity config is the root identity
|
|
if config.Options.DefaultIdentity == nil {
|
|
if identityInterface, ok := configMap[config.Options.DefaultIdentitySection]; ok {
|
|
if identityMap, ok := identityInterface.(map[interface{}]interface{}); ok {
|
|
if identityConfig, err := parseIdentityConfig(identityMap, config.Options.DefaultIdentitySection); err == nil {
|
|
config.defaultIdentityConfig = identityConfig
|
|
} else {
|
|
return fmt.Errorf("error parsing root identity section [%s] : %v", config.Options.DefaultIdentitySection, err)
|
|
}
|
|
|
|
} else {
|
|
return fmt.Errorf("root identity section [%s] must be a map", config.Options.DefaultIdentitySection)
|
|
}
|
|
} else {
|
|
return fmt.Errorf("root identity section [%s] must be defined", config.Options.DefaultIdentitySection)
|
|
}
|
|
} else {
|
|
config.defaultIdentityConfig = config.Options.DefaultIdentity.GetConfig()
|
|
}
|
|
|
|
if sectionVal, ok := configMap[config.Options.DefaultConfigSection]; ok {
|
|
//treat section like an array of maps
|
|
if sectionArrayVals, ok := sectionVal.([]interface{}); ok {
|
|
for i, sectionArrayVal := range sectionArrayVals {
|
|
if sectionMap, ok := sectionArrayVal.(map[interface{}]interface{}); ok {
|
|
serverConfig := &ServerConfig{
|
|
DefaultIdentity: config.Options.DefaultIdentity,
|
|
}
|
|
if err := serverConfig.Parse(sectionMap, config.Options.DefaultConfigSection); err != nil {
|
|
return fmt.Errorf("error parsing web configuration [%s] at index [%d]: %v", config.Options.DefaultConfigSection, i, err)
|
|
}
|
|
|
|
config.ServerConfigs = append(config.ServerConfigs, serverConfig)
|
|
} else {
|
|
return fmt.Errorf("error parsing web configuration [%s] at index [%d]: not a map", config.Options.DefaultConfigSection, i)
|
|
}
|
|
}
|
|
} else {
|
|
return fmt.Errorf("%s identity section [%s] must be a map", config.Options.DefaultConfigSection, config.Options.DefaultIdentitySection)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Validate uses a Registry to validate that all ApiConfig bindings may be fulfilled. All other relevant
|
|
// InstanceConfig values are also validated.
|
|
func (config *InstanceConfig) Validate(registry Registry) error {
|
|
|
|
if config.Options.DefaultIdentity == nil {
|
|
//validate default identity by loading
|
|
if defaultIdentity, err := identity.LoadIdentity(*config.defaultIdentityConfig); err == nil {
|
|
config.Options.DefaultIdentity = defaultIdentity
|
|
|
|
if err := config.Options.DefaultIdentity.WatchFiles(); err != nil {
|
|
pfxlog.Logger().Warnf("could not enable file watching on default identity: %v", err)
|
|
}
|
|
} else {
|
|
return fmt.Errorf("could not load default identity: %v", err)
|
|
}
|
|
|
|
//add default loaded identity to each web
|
|
for _, serverConfig := range config.ServerConfigs {
|
|
serverConfig.DefaultIdentity = config.Options.DefaultIdentity
|
|
}
|
|
}
|
|
|
|
presentApis := map[string]ApiHandlerFactory{}
|
|
|
|
var errs []error
|
|
for i, serverConfig := range config.ServerConfigs {
|
|
//validate attributes
|
|
if err := serverConfig.Validate(registry); err != nil {
|
|
return fmt.Errorf("could not validate server at %s[%d]: %v", config.Options.DefaultConfigSection, i, err)
|
|
}
|
|
|
|
for _, api := range serverConfig.APIs {
|
|
presentApis[api.Binding()] = registry.Get(api.Binding())
|
|
}
|
|
}
|
|
|
|
//custom instance validators, run after all the regular ones
|
|
for _, instanceValidator := range config.Options.InstanceValidators {
|
|
ve := instanceValidator(config)
|
|
if ve != nil {
|
|
errs = append(errs, ve)
|
|
}
|
|
}
|
|
|
|
if len(errs) > 0 {
|
|
return errors.Join(errs...)
|
|
}
|
|
|
|
for presentApiBinding, presentApiFactory := range presentApis {
|
|
if err := presentApiFactory.Validate(config); err != nil {
|
|
return fmt.Errorf("error validating ApiConfig binding %s: %v", presentApiBinding, err)
|
|
}
|
|
}
|
|
|
|
//enabled only after validation passes
|
|
config.enabled = true
|
|
|
|
return nil
|
|
}
|
|
|
|
// Enabled returns true/false on whether this configuration should be considered "enabled". Set to true after
|
|
// Validate passes.
|
|
func (config *InstanceConfig) Enabled() bool {
|
|
return config.enabled
|
|
}
|
|
|
|
// ServerConfigOptions is the shared options for a ServerConfig.
|
|
type ServerConfigOptions struct {
|
|
TimeoutOptions
|
|
TlsVersionOptions
|
|
}
|
|
|
|
// Default provides defaults for all necessary values
|
|
func (options *ServerConfigOptions) Default() {
|
|
options.TimeoutOptions.Default()
|
|
options.TlsVersionOptions.Default()
|
|
}
|
|
|
|
// Parse parses a configuration map
|
|
func (options *ServerConfigOptions) Parse(optionsMap map[interface{}]interface{}) error {
|
|
if err := options.TimeoutOptions.Parse(optionsMap); err != nil {
|
|
return fmt.Errorf("error parsing options: %v", err)
|
|
}
|
|
|
|
if err := options.TlsVersionOptions.Parse(optionsMap); err != nil {
|
|
return fmt.Errorf("error parsing options: %v", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// TimeoutOptions represents http timeout options
|
|
type TimeoutOptions struct {
|
|
ReadTimeout time.Duration
|
|
IdleTimeout time.Duration
|
|
WriteTimeout time.Duration
|
|
}
|
|
|
|
// Default defaults all HTTP timeout options
|
|
func (timeoutOptions *TimeoutOptions) Default() {
|
|
timeoutOptions.WriteTimeout = DefaultHttpWriteTimeout
|
|
timeoutOptions.ReadTimeout = DefaultHttpReadTimeout
|
|
timeoutOptions.IdleTimeout = DefaultHttpIdleTimeout
|
|
}
|
|
|
|
// Parse parses a config map
|
|
func (timeoutOptions *TimeoutOptions) Parse(config map[interface{}]interface{}) error {
|
|
if interfaceVal, ok := config["readTimeout"]; ok {
|
|
if readTimeoutStr, ok := interfaceVal.(string); ok {
|
|
if readTimeout, err := time.ParseDuration(readTimeoutStr); err == nil {
|
|
timeoutOptions.ReadTimeout = readTimeout
|
|
} else {
|
|
return fmt.Errorf("could not parse readTimeout %s as a duration (e.g. 1m): %v", readTimeoutStr, err)
|
|
}
|
|
} else {
|
|
return errors.New("could not use value for readTimeout, not a string")
|
|
}
|
|
}
|
|
|
|
if interfaceVal, ok := config["idleTimeout"]; ok {
|
|
if idleTimeoutStr, ok := interfaceVal.(string); ok {
|
|
if idleTimeout, err := time.ParseDuration(idleTimeoutStr); err == nil {
|
|
timeoutOptions.IdleTimeout = idleTimeout
|
|
} else {
|
|
return fmt.Errorf("could not parse idleTimeout %s as a duration (e.g. 1m): %v", idleTimeoutStr, err)
|
|
}
|
|
} else {
|
|
return errors.New("could not use value for idleTimeout, not a string")
|
|
}
|
|
}
|
|
|
|
if interfaceVal, ok := config["writeTimeout"]; ok {
|
|
if writeTimeoutStr, ok := interfaceVal.(string); ok {
|
|
if writeTimeout, err := time.ParseDuration(writeTimeoutStr); err == nil {
|
|
timeoutOptions.WriteTimeout = writeTimeout
|
|
} else {
|
|
return fmt.Errorf("could not parse writeTimeout %s as a duration (e.g. 1m): %v", writeTimeoutStr, err)
|
|
}
|
|
} else {
|
|
return errors.New("could not use value for writeTimeout, not a string")
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Validate validates all settings and return nil or an error
|
|
func (timeoutOptions *TimeoutOptions) Validate() error {
|
|
if timeoutOptions.WriteTimeout <= 0 {
|
|
return fmt.Errorf("value [%s] for writeTimeout too low, must be positive", timeoutOptions.WriteTimeout.String())
|
|
}
|
|
|
|
if timeoutOptions.ReadTimeout <= 0 {
|
|
return fmt.Errorf("value [%s] for readTimeout too low, must be positive", timeoutOptions.ReadTimeout.String())
|
|
}
|
|
|
|
if timeoutOptions.IdleTimeout <= 0 {
|
|
return fmt.Errorf("value [%s] for idleTimeout too low, must be positive", timeoutOptions.IdleTimeout.String())
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// TlsVersionOptions represents TLS version options
|
|
type TlsVersionOptions struct {
|
|
MinTLSVersion int
|
|
minTLSVersionStr string
|
|
|
|
MaxTLSVersion int
|
|
maxTLSVersionStr string
|
|
}
|
|
|
|
// Default defaults TLS versions
|
|
func (tlsVersionOptions *TlsVersionOptions) Default() {
|
|
tlsVersionOptions.MinTLSVersion = MinTLSVersion
|
|
tlsVersionOptions.MaxTLSVersion = MaxTLSVersion
|
|
}
|
|
|
|
// Parse parses a config map
|
|
func (tlsVersionOptions *TlsVersionOptions) Parse(config map[interface{}]interface{}) error {
|
|
if interfaceVal, ok := config["minTLSVersion"]; ok {
|
|
var ok bool
|
|
if tlsVersionOptions.minTLSVersionStr, ok = interfaceVal.(string); ok {
|
|
if minTLSVersion, ok := TlsVersionMap[tlsVersionOptions.minTLSVersionStr]; ok {
|
|
tlsVersionOptions.MinTLSVersion = minTLSVersion
|
|
} else {
|
|
return fmt.Errorf("could not use value for minTLSVersion, invalid value [%s]", tlsVersionOptions.minTLSVersionStr)
|
|
}
|
|
} else {
|
|
return errors.New("could not use value for minTLSVersion, not an string")
|
|
}
|
|
}
|
|
|
|
if interfaceVal, ok := config["maxTLSVersion"]; ok {
|
|
var ok bool
|
|
if tlsVersionOptions.maxTLSVersionStr, ok = interfaceVal.(string); ok {
|
|
if maxTLSVersion, ok := TlsVersionMap[tlsVersionOptions.maxTLSVersionStr]; ok {
|
|
tlsVersionOptions.MaxTLSVersion = maxTLSVersion
|
|
} else {
|
|
return fmt.Errorf("could not use value for maxTLSVersion, invalid value [%s]", tlsVersionOptions.maxTLSVersionStr)
|
|
}
|
|
} else {
|
|
return errors.New("could not use value for maxTLSVersion, not an string")
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Validate validates the configuration values and returns nil or error
|
|
func (tlsVersionOptions *TlsVersionOptions) Validate() error {
|
|
if tlsVersionOptions.MinTLSVersion > tlsVersionOptions.MaxTLSVersion {
|
|
return fmt.Errorf("minTLSVersion [%s] must be less than or equal to maxTLSVersion [%s]", tlsVersionOptions.minTLSVersionStr, tlsVersionOptions.maxTLSVersionStr)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func parseIdentityConfig(identityMap map[interface{}]interface{}, pathContext string) (*identity.Config, error) {
|
|
idConfig, err := identity.NewConfigFromMap(identityMap)
|
|
|
|
if err = idConfig.ValidateWithPathContext(pathContext); err != nil {
|
|
return nil, fmt.Errorf("error parsing identity: %v", err)
|
|
}
|
|
|
|
return idConfig, nil
|
|
}
|