Files
ziti/xweb/config.go
T
Andrew Martinez c6b54e8606 updates to new foundation version
- updated identity module
- centralizes identity references
- removes old parsing logic
2021-10-18 15:25:07 -04:00

318 lines
10 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/openziti/foundation/identity/identity"
"time"
)
// Config is the root configuration options necessary to start numerous http.Server instances via WebListener's.
type Config struct {
SourceConfig map[interface{}]interface{}
WebListeners []*WebListener
WebSection string
DefaultIdentity identity.Identity
DefaultIdentitySection string
//used for loading/validation logic, use DefaultIdentity.Config() for runtime
defaultIdentityConfig *identity.Config
enabled bool
}
// Parse parses a configuration map, looking for sections that define an identity.Config and an array of WebListener's.
func (config *Config) Parse(configMap map[interface{}]interface{}) error {
config.SourceConfig = configMap
if config.DefaultIdentity == nil && config.DefaultIdentitySection == "" {
return errors.New("identity section not specified for configuration, must be specified if a default identity is not provided")
}
if config.WebSection == "" {
return errors.New("web section not specified for configuration")
}
//default identity config is the root identity
if config.DefaultIdentity == nil {
if identityInterface, ok := configMap[config.DefaultIdentitySection]; ok {
if identityMap, ok := identityInterface.(map[interface{}]interface{}); ok {
if identityConfig, err := parseIdentityConfig(identityMap, config.DefaultIdentitySection); err == nil {
config.defaultIdentityConfig = identityConfig
} else {
return fmt.Errorf("error parsing root identity section [%s] : %v", config.DefaultIdentitySection, err)
}
} else {
return fmt.Errorf("root identity section [%s] must be a map", config.DefaultIdentitySection)
}
} else {
return fmt.Errorf("root identity section [%s] must be defined", config.DefaultIdentitySection)
}
} else {
config.defaultIdentityConfig = config.DefaultIdentity.GetConfig()
}
if webInterface, ok := configMap[config.WebSection]; ok {
//treat section like an array of maps
if webArrayInterface, ok := webInterface.([]interface{}); ok {
for i, webInterface := range webArrayInterface {
if webMap, ok := webInterface.(map[interface{}]interface{}); ok {
webListener := &WebListener{
DefaultIdentity: config.DefaultIdentity,
}
if err := webListener.Parse(webMap, config.WebSection); err != nil {
return fmt.Errorf("error parsing web configuration [%s] at index [%d]: %v", config.WebSection, i, err)
}
config.WebListeners = append(config.WebListeners, webListener)
} else {
return fmt.Errorf("error parsing web configuration [%s] at index [%d]: not a map", config.WebSection, i)
}
}
} else {
return fmt.Errorf("%s identity section [%s] must be a map", config.WebSection, config.DefaultIdentitySection)
}
}
return nil
}
// Validate uses a WebHandlerFactoryRegistry to validate that all API bindings may be fulfilled. All other relevant
// Config values are also validated.
func (config *Config) Validate(registry WebHandlerFactoryRegistry) error {
if config.DefaultIdentity == nil {
//validate default identity by loading
if defaultIdentity, err := identity.LoadIdentity(*config.defaultIdentityConfig); err == nil {
config.DefaultIdentity = defaultIdentity
} else {
return fmt.Errorf("could not load default identity: %v", err)
}
//add default loaded identity to each web
for _, webListener := range config.WebListeners {
webListener.DefaultIdentity = config.DefaultIdentity
}
}
presentApis := map[string]WebHandlerFactory{}
for i, webListener := range config.WebListeners {
//validate attributes
if err := webListener.Validate(registry); err != nil {
return fmt.Errorf("could not validate web listener at %s[%d]: %v", config.WebSection, i, err)
}
for _, api := range webListener.APIs {
presentApis[api.Binding()] = registry.Get(api.Binding())
}
}
for presentApiBinding, presentApiFactory := range presentApis {
if err := presentApiFactory.Validate(config); err != nil {
return fmt.Errorf("error validating API 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 *Config) Enabled() bool {
return config.enabled
}
// Options is the shared options for a WebListener.
type Options struct {
TimeoutOptions
TlsVersionOptions
}
// Default provides defaults for all necessary values
func (options *Options) Default() {
options.TimeoutOptions.Default()
options.TlsVersionOptions.Default()
}
// Parse parses a configuration map
func (options *Options) 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 = time.Second * 10
timeoutOptions.ReadTimeout = time.Second * 5
timeoutOptions.IdleTimeout = time.Second * 5
}
// 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
}
// 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,
}
// Default defaults TLS versions
func (tlsVersionOptions *TlsVersionOptions) Default() {
tlsVersionOptions.MinTLSVersion = tls.VersionTLS12
tlsVersionOptions.MaxTLSVersion = tls.VersionTLS13
}
// 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
}