mirror of
https://github.com/hanzozt/zt.git
synced 2026-10-30 15:04:55 +00:00
- Repo names: ziti-* → zt-* - Go modules: github.com/hanzozt/ziti → github.com/hanzozt/zt - Docker images: hanzozt/ziti-* → hanzozt/zt-* - C/Java/JS symbols: ziti_* → zt_* - File and directory names updated
1152 lines
39 KiB
Go
1152 lines
39 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 config
|
|
|
|
import (
|
|
"bytes"
|
|
"crypto/sha1"
|
|
"crypto/tls"
|
|
"crypto/x509"
|
|
"encoding/json"
|
|
"fmt"
|
|
"math"
|
|
"net/url"
|
|
"os"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/hashicorp/go-hclog"
|
|
"github.com/michaelquigley/pfxlog"
|
|
"github.com/hanzozt/channel/v4"
|
|
"github.com/hanzozt/identity"
|
|
"github.com/hanzozt/sdk-golang/xgress"
|
|
"github.com/hanzozt/storage/boltz"
|
|
"github.com/hanzozt/transport/v2"
|
|
transporttls "github.com/hanzozt/transport/v2/tls"
|
|
"github.com/hanzozt/zt/v2/common"
|
|
"github.com/hanzozt/zt/v2/common/config"
|
|
"github.com/hanzozt/zt/v2/common/pb/ctrl_pb"
|
|
"github.com/hanzozt/zt/v2/common/pb/mgmt_pb"
|
|
"github.com/hanzozt/zt/v2/controller/command"
|
|
"github.com/hanzozt/zt/v2/controller/db"
|
|
"github.com/pkg/errors"
|
|
"gopkg.in/yaml.v2"
|
|
)
|
|
|
|
const (
|
|
DefaultProfileMemoryInterval = 15 * time.Second
|
|
DefaultHealthChecksBoltCheckInterval = 30 * time.Second
|
|
DefaultHealthChecksBoltCheckTimeout = 20 * time.Second
|
|
DefaultHealthChecksBoltCheckInitialDelay = 30 * time.Second
|
|
|
|
DefaultRaftCommandHandlerMaxQueueSize = 250
|
|
|
|
// DefaultTlsHandshakeRateLimiterEnabled is whether the tls handshake rate limiter is enabled by default
|
|
DefaultTlsHandshakeRateLimiterEnabled = false
|
|
|
|
// TlsHandshakeRateLimiterMinSizeValue is the minimum size that can be configured for the tls handshake rate limiter
|
|
// window range
|
|
TlsHandshakeRateLimiterMinSizeValue = 5
|
|
|
|
// TlsHandshakeRateLimiterMaxSizeValue is the maximum size that can be configured for the tls handshake rate limiter
|
|
// window range
|
|
TlsHandshakeRateLimiterMaxSizeValue = 10000
|
|
|
|
// TlsHandshakeRateLimiterMetricOutstandingCount is the name of the metric tracking how many tasks are in process
|
|
TlsHandshakeRateLimiterMetricOutstandingCount = "tls_handshake_limiter.in_process"
|
|
|
|
// TlsHandshakeRateLimiterMetricCurrentWindowSize is the name of the metric tracking the current window size
|
|
TlsHandshakeRateLimiterMetricCurrentWindowSize = "tls_handshake_limiter.window_size"
|
|
|
|
// TlsHandshakeRateLimiterMetricWorkTimer is the name of the metric tracking how long successful tasks are taking to complete
|
|
TlsHandshakeRateLimiterMetricWorkTimer = "tls_handshake_limiter.work_timer"
|
|
|
|
// DefaultTlsHandshakeRateLimiterMaxWindow is the default max size for the tls handshake rate limiter
|
|
DefaultTlsHandshakeRateLimiterMaxWindow = 2500
|
|
|
|
MinRouterDataModelLogSize = 10
|
|
DefaultRouterDataModelLogSize = 10000
|
|
DefaultRouterDataModelListenerBufferSize = 1000
|
|
|
|
DefaultRaftSnapshotInterval = 2 * time.Minute
|
|
DefaultRaftSnapshotThreshold = 500
|
|
DefaultRaftTrailingLogs = 500
|
|
|
|
RaftRateLimiterQueueSizeMetricName = "raft.rate_limiter.queue_size"
|
|
RaftRateLimiterWorkTimerMetricName = "raft.rate_limiter.work_timer"
|
|
RaftRateLimiterWindowSizeMetricName = "raft.rate_limiter.window_size"
|
|
|
|
BackgroundQueueMinSize = 10
|
|
BackgroundQueueMaxSize = math.MaxUint32 >> 2
|
|
|
|
DefaultBackgroundQueueEnabled = true
|
|
DefaultBackgroundQueueSize = 1000
|
|
DefaultBackgroundQueueDropWhenFull = false
|
|
DefaultBackgroundQueueThreshold = 50 * time.Millisecond
|
|
)
|
|
|
|
type Config struct {
|
|
Id *identity.TokenId
|
|
SpiffeIdTrustDomain *url.URL
|
|
AdditionalTrustDomains []*url.URL
|
|
|
|
Raft *RaftConfig
|
|
Network *NetworkConfig
|
|
Edge *EdgeConfig
|
|
Db boltz.Db
|
|
Trace struct {
|
|
Handler *channel.TraceHandler
|
|
}
|
|
Profile struct {
|
|
Memory struct {
|
|
Path string
|
|
Interval time.Duration
|
|
}
|
|
CPU struct {
|
|
Path string
|
|
}
|
|
}
|
|
Ctrl struct {
|
|
Listener transport.Address
|
|
Options *CtrlOptions
|
|
}
|
|
HealthChecks struct {
|
|
BoltCheck struct {
|
|
Interval time.Duration
|
|
Timeout time.Duration
|
|
InitialDelay time.Duration
|
|
}
|
|
}
|
|
RouterDataModel common.RouterDataModelConfig
|
|
|
|
Command struct {
|
|
RateLimiter command.RateLimiterConfig
|
|
Background struct {
|
|
Enabled bool
|
|
QueueSize uint32
|
|
DropWhenFull bool
|
|
DelayThreshold time.Duration
|
|
}
|
|
}
|
|
|
|
TlsHandshakeRateLimiter command.AdaptiveRateLimiterConfig
|
|
Src map[interface{}]interface{}
|
|
}
|
|
|
|
func (self *Config) ToJson() (string, error) {
|
|
jsonMap, err := config.ToJsonCompatibleMap(self.Src)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
b, err := json.Marshal(jsonMap)
|
|
return string(b), err
|
|
}
|
|
|
|
func (self *Config) IsRaftEnabled() bool {
|
|
return self.Raft != nil
|
|
}
|
|
|
|
// CtrlOptions extends channel.Options to include support for additional, non-channel specific options
|
|
// (e.g. NewListener)
|
|
type CtrlOptions struct {
|
|
*channel.Options
|
|
NewListener *transport.Address
|
|
AdvertiseAddress *transport.Address
|
|
RouterHeartbeatOptions *channel.HeartbeatOptions
|
|
PeerHeartbeatOptions *channel.HeartbeatOptions
|
|
}
|
|
|
|
func (config *Config) Configure(sub config.Subconfig) error {
|
|
return sub.LoadConfig(config.Src)
|
|
}
|
|
|
|
func LoadConfig(path string) (*Config, error) {
|
|
cfgBytes, err := os.ReadFile(path)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
cfgmap := make(map[interface{}]interface{})
|
|
if err = yaml.NewDecoder(bytes.NewReader(cfgBytes)).Decode(&cfgmap); err != nil {
|
|
return nil, err
|
|
}
|
|
config.InjectEnv(cfgmap)
|
|
if value, found := cfgmap["v"]; found {
|
|
if value.(int) != 3 {
|
|
panic("config version mismatch: see docs for information on config updates")
|
|
}
|
|
} else {
|
|
panic("no config version: see docs for information on config")
|
|
}
|
|
|
|
var identityConfig *identity.Config
|
|
|
|
if value, found := cfgmap["identity"]; found {
|
|
subMap := value.(map[interface{}]interface{})
|
|
identityConfig, err = identity.NewConfigFromMapWithPathContext(subMap, "identity")
|
|
|
|
if err != nil {
|
|
return nil, fmt.Errorf("could not parse root identity: %v", err)
|
|
}
|
|
|
|
if identityConfig.ServerCert == "" && identityConfig.ServerKey == "" {
|
|
identityConfig.ServerCert = identityConfig.Cert
|
|
identityConfig.ServerKey = identityConfig.Key
|
|
}
|
|
} else {
|
|
return nil, fmt.Errorf("identity section not found")
|
|
}
|
|
|
|
controllerConfig := &Config{
|
|
Network: DefaultNetworkConfig(),
|
|
Src: cfgmap,
|
|
}
|
|
|
|
if id, err := identity.LoadIdentity(*identityConfig); err != nil {
|
|
return nil, fmt.Errorf("unable to load identity (%s)", err)
|
|
} else {
|
|
controllerConfig.Id = identity.NewIdentity(id)
|
|
|
|
if err := controllerConfig.Id.WatchFiles(); err != nil {
|
|
pfxlog.Logger().Warn("could not enable file watching on identity: %w", err)
|
|
}
|
|
}
|
|
|
|
if value, found := cfgmap["network"]; found {
|
|
if submap, ok := value.(map[interface{}]interface{}); ok {
|
|
if options, err := LoadNetworkConfig(submap); err == nil {
|
|
controllerConfig.Network = options
|
|
} else {
|
|
return nil, fmt.Errorf("invalid 'network' stanza (%s)", err)
|
|
}
|
|
} else {
|
|
pfxlog.Logger().Warn("invalid or empty 'network' stanza")
|
|
}
|
|
}
|
|
|
|
if value, found := cfgmap["cluster"]; found {
|
|
if submap, ok := value.(map[interface{}]interface{}); ok {
|
|
controllerConfig.Raft = &RaftConfig{}
|
|
|
|
controllerConfig.Raft.ApplyTimeout = 5 * time.Second
|
|
controllerConfig.Raft.ElectionTimeout = 5 * time.Second
|
|
controllerConfig.Raft.HeartbeatTimeout = 3 * time.Second
|
|
controllerConfig.Raft.LeaderLeaseTimeout = 3 * time.Second
|
|
controllerConfig.Raft.CommandHandlerOptions.MaxQueueSize = DefaultRaftCommandHandlerMaxQueueSize
|
|
controllerConfig.Raft.WarnWhenLeaderlessFor = time.Minute
|
|
|
|
controllerConfig.Raft.SnapshotInterval = DefaultRaftSnapshotInterval
|
|
controllerConfig.Raft.SnapshotThreshold = DefaultRaftSnapshotThreshold
|
|
controllerConfig.Raft.TrailingLogs = DefaultRaftTrailingLogs
|
|
|
|
controllerConfig.Raft.RateLimiter.QueueSizeMetric = RaftRateLimiterQueueSizeMetricName
|
|
controllerConfig.Raft.RateLimiter.WorkTimerMetric = RaftRateLimiterWorkTimerMetricName
|
|
controllerConfig.Raft.RateLimiter.WindowSizeMetric = RaftRateLimiterWindowSizeMetricName
|
|
|
|
if value, found := submap["dataDir"]; found {
|
|
controllerConfig.Raft.DataDir = value.(string)
|
|
} else {
|
|
return nil, errors.Errorf("cluster dataDir configuration missing")
|
|
}
|
|
|
|
if value, found := submap["restartSelfOnSnapshot"]; found {
|
|
controllerConfig.Raft.RestartSelf = strings.EqualFold("true", fmt.Sprintf("%v", value))
|
|
}
|
|
|
|
if value, found := submap["snapshotInterval"]; found {
|
|
if val, err := time.ParseDuration(fmt.Sprintf("%v", value)); err == nil {
|
|
controllerConfig.Raft.SnapshotInterval = val
|
|
} else {
|
|
return nil, errors.Wrapf(err, "failed to parse raft.snapshotInterval value '%v", value)
|
|
}
|
|
}
|
|
|
|
if value, found := submap["commitTimeout"]; found {
|
|
if val, err := time.ParseDuration(fmt.Sprintf("%v", value)); err == nil {
|
|
controllerConfig.Raft.CommitTimeout = &val
|
|
} else {
|
|
return nil, errors.Wrapf(err, "failed to parse raft.commitTimeout value '%v", value)
|
|
}
|
|
}
|
|
|
|
if value, found := submap["electionTimeout"]; found {
|
|
if val, err := time.ParseDuration(fmt.Sprintf("%v", value)); err == nil {
|
|
controllerConfig.Raft.ElectionTimeout = val
|
|
} else {
|
|
return nil, errors.Wrapf(err, "failed to parse raft.electionTimeout value '%v", value)
|
|
}
|
|
}
|
|
|
|
if value, found := submap["heartbeatTimeout"]; found {
|
|
if val, err := time.ParseDuration(fmt.Sprintf("%v", value)); err == nil {
|
|
controllerConfig.Raft.HeartbeatTimeout = val
|
|
} else {
|
|
return nil, errors.Wrapf(err, "failed to parse raft.heartbeatTimeout value '%v", value)
|
|
}
|
|
}
|
|
|
|
if value, found := submap["leaderLeaseTimeout"]; found {
|
|
if val, err := time.ParseDuration(fmt.Sprintf("%v", value)); err == nil {
|
|
controllerConfig.Raft.LeaderLeaseTimeout = val
|
|
} else {
|
|
return nil, errors.Wrapf(err, "failed to parse raft.leaderLeaseTimeout value '%v", value)
|
|
}
|
|
}
|
|
|
|
if value, found := submap["snapshotThreshold"]; found {
|
|
val := uint32(value.(int))
|
|
controllerConfig.Raft.SnapshotThreshold = val
|
|
}
|
|
|
|
if value, found := submap["maxAppendEntries"]; found {
|
|
val := uint32(value.(int))
|
|
controllerConfig.Raft.MaxAppendEntries = &val
|
|
}
|
|
|
|
if value, found := submap["trailingLogs"]; found {
|
|
val := uint32(value.(int))
|
|
controllerConfig.Raft.TrailingLogs = val
|
|
}
|
|
|
|
if value, found := submap["logLevel"]; found {
|
|
val := fmt.Sprintf("%v", value)
|
|
if hclog.LevelFromString(val) == hclog.NoLevel {
|
|
return nil, errors.Errorf("invalid value for raft.logLevel [%v]", val)
|
|
}
|
|
controllerConfig.Raft.LogLevel = &val
|
|
}
|
|
|
|
if value, found := submap["warnWhenLeaderlessFor"]; found {
|
|
if val, err := time.ParseDuration(fmt.Sprintf("%v", value)); err == nil {
|
|
if val < 10*time.Second {
|
|
pfxlog.Logger().Infof("invalid value %s for raft.warnWhenLeaderlessFor, must be >= 10s", val)
|
|
} else {
|
|
controllerConfig.Raft.WarnWhenLeaderlessFor = val
|
|
}
|
|
} else {
|
|
return nil, errors.Wrapf(err, "failed to parse raft.warnWhenLeaderlessFor value '%v", value)
|
|
}
|
|
}
|
|
|
|
if value, found := submap["logFile"]; found {
|
|
val := fmt.Sprintf("%v", value)
|
|
options := *hclog.DefaultOptions
|
|
f, err := os.Create(val)
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "unable to open raft log file [%v]", val)
|
|
}
|
|
options.Output = f
|
|
if controllerConfig.Raft.LogLevel != nil {
|
|
options.Level = hclog.LevelFromString(*controllerConfig.Raft.LogLevel)
|
|
}
|
|
controllerConfig.Raft.Logger = hclog.New(&options)
|
|
}
|
|
|
|
if value, found := cfgmap["commandHandler"]; found {
|
|
if chSubMap, ok := value.(map[interface{}]interface{}); ok {
|
|
if value, found := chSubMap["maxQueueSize"]; found {
|
|
controllerConfig.Raft.CommandHandlerOptions.MaxQueueSize = uint16(value.(int))
|
|
}
|
|
} else {
|
|
return nil, errors.New("invalid commandHandler value, should be map")
|
|
}
|
|
}
|
|
|
|
if value, found := submap["applyTimeout"]; found {
|
|
if val, err := time.ParseDuration(fmt.Sprintf("%v", value)); err == nil {
|
|
controllerConfig.Raft.ApplyTimeout = val
|
|
} else {
|
|
return nil, errors.Wrapf(err, "failed to parse raft.applyTimeout value '%v", value)
|
|
}
|
|
}
|
|
|
|
controllerConfig.Raft.RateLimiter.SetDefaults()
|
|
|
|
if value, found := submap["rateLimiter"]; found {
|
|
if rateLimitterConfig, ok := value.(map[interface{}]interface{}); ok {
|
|
if err = command.LoadAdaptiveRateLimiterConfig(&controllerConfig.Raft.RateLimiter, rateLimitterConfig); err != nil {
|
|
return nil, fmt.Errorf("error loading cluster rate limiting configuration (%w)", err)
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
return nil, errors.Errorf("invalid cluster configuration")
|
|
}
|
|
} else if value, found := cfgmap["db"]; found {
|
|
str, err := db.Open(value.(string))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
controllerConfig.Db = str
|
|
} else {
|
|
panic("controllerConfig must provide [db] or [cluster]")
|
|
}
|
|
|
|
//SPIFFE Trust Domain
|
|
var spiffeId *url.URL
|
|
if controllerConfig.Raft != nil {
|
|
//HA setup, SPIFFE ID must come from certs
|
|
var err error
|
|
spiffeId, err = GetSpiffeIdFromIdentity(controllerConfig.Id.Identity)
|
|
if err != nil {
|
|
panic("error determining a trust domain from a SPIFFE id in the root identity for HA configuration, must have a spiffe:// URI SANs in the server certificate or along the signing CAs chain: " + err.Error())
|
|
}
|
|
|
|
if spiffeId == nil {
|
|
panic("unable to determine a trust domain from a SPIFFE id in the root identity for HA configuration, must have a spiffe:// URI SANs in the server certificate or along the signing CAs chain")
|
|
}
|
|
} else {
|
|
// Non-HA/legacy system, prefer SPIFFE id from certs, but fall back to configuration if necessary
|
|
spiffeId, _ = GetSpiffeIdFromIdentity(controllerConfig.Id.Identity)
|
|
|
|
if spiffeId == nil {
|
|
//for non HA setups allow the trust domain to come from the configuration root value `trustDomain`
|
|
if value, found := cfgmap["trustDomain"]; found {
|
|
trustDomain, ok := value.(string)
|
|
|
|
if !ok {
|
|
panic(fmt.Sprintf("could not parse [trustDomain], expected a string got [%T]", value))
|
|
}
|
|
|
|
if trustDomain != "" {
|
|
if !strings.HasPrefix("spiffe://", trustDomain) {
|
|
trustDomain = "spiffe://" + trustDomain
|
|
}
|
|
|
|
spiffeId, err = url.Parse(trustDomain)
|
|
|
|
if err != nil {
|
|
panic("could not parse [trustDomain] when used in a SPIFFE id URI [" + trustDomain + "], please make sure it is a valid URI hostname: " + err.Error())
|
|
}
|
|
|
|
if spiffeId == nil {
|
|
panic("could not parse [trustDomain] when used in a SPIFFE id URI [" + trustDomain + "]: spiffeId is nil and no error returned")
|
|
}
|
|
|
|
if spiffeId.Scheme != "spiffe" {
|
|
panic("[trustDomain] does not have a spiffe scheme (spiffe://) has: " + spiffeId.Scheme)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//default a generated trust domain and spiffe id from the sha1 of the root ca
|
|
if spiffeId == nil {
|
|
spiffeId, err = generateDefaultSpiffeId(controllerConfig.Id.Identity)
|
|
|
|
if err != nil {
|
|
panic("could not generate default trust domain: " + err.Error())
|
|
}
|
|
|
|
pfxlog.Logger().Warnf("this environment is using a default generated trust domain [%s], it is recommended that a trust domain is specified in configuration via URI SANs or the 'trustDomain' field", spiffeId.String())
|
|
pfxlog.Logger().Warnf("this environment is using a default generated trust domain [%s], it is recommended that if network components have enrolled that the generated trust domain be added to the configuration field 'additionalTrustDomains' array when configuring a explicit trust domain", spiffeId.String())
|
|
}
|
|
}
|
|
|
|
if spiffeId == nil {
|
|
panic("unable to determine trust domain from SPIFFE id (spiffe:// URI SANs in server cert or signing CAs) or from configuration [trustDomain], controllers must have a trust domain")
|
|
}
|
|
|
|
if spiffeId.Hostname() == "" {
|
|
panic("unable to determine trust domain from SPIFFE id: hostname was empty")
|
|
}
|
|
|
|
if controllerConfig.Raft != nil {
|
|
if err = ValidateSpiffeId(controllerConfig.Id, spiffeId); err != nil {
|
|
panic(err)
|
|
}
|
|
}
|
|
|
|
//only preserve trust domain
|
|
spiffeId.Path = ""
|
|
controllerConfig.SpiffeIdTrustDomain = spiffeId
|
|
|
|
if value, found := cfgmap["additionalTrustDomains"]; found {
|
|
if valArr, ok := value.([]any); ok {
|
|
var trustDomains []*url.URL
|
|
for _, trustDomain := range valArr {
|
|
if strTrustDomain, ok := trustDomain.(string); ok {
|
|
|
|
if !strings.HasPrefix("spiffe://", strTrustDomain) {
|
|
strTrustDomain = "spiffe://" + strTrustDomain
|
|
}
|
|
|
|
spiffeId, err = url.Parse(strTrustDomain)
|
|
|
|
if err != nil {
|
|
panic(fmt.Sprintf("invalid entry in 'additionalTrustDomains', could not be parsed as a URI: %v", trustDomain))
|
|
}
|
|
//only preserve trust domain
|
|
spiffeId.Path = ""
|
|
|
|
trustDomains = append(trustDomains, spiffeId)
|
|
} else {
|
|
panic(fmt.Sprintf("invalid entry in 'additionalTrustDomains' expected a string: %v", trustDomain))
|
|
}
|
|
}
|
|
|
|
controllerConfig.AdditionalTrustDomains = trustDomains
|
|
}
|
|
}
|
|
|
|
if value, found := cfgmap["trace"]; found {
|
|
if submap, ok := value.(map[interface{}]interface{}); ok {
|
|
if value, found := submap["path"]; found {
|
|
handler, err := channel.NewTraceHandler(value.(string), controllerConfig.Id.Token)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
handler.AddDecoder(&channel.Decoder{})
|
|
handler.AddDecoder(&ctrl_pb.Decoder{})
|
|
handler.AddDecoder(&xgress.Decoder{})
|
|
handler.AddDecoder(&mgmt_pb.Decoder{})
|
|
controllerConfig.Trace.Handler = handler
|
|
}
|
|
}
|
|
}
|
|
|
|
if value, found := cfgmap["profile"]; found {
|
|
if submap, ok := value.(map[interface{}]interface{}); ok {
|
|
if value, found := submap["memory"]; found {
|
|
if submap, ok := value.(map[interface{}]interface{}); ok {
|
|
if value, found := submap["path"]; found {
|
|
controllerConfig.Profile.Memory.Path = value.(string)
|
|
}
|
|
if value, found := submap["intervalMs"]; found {
|
|
controllerConfig.Profile.Memory.Interval = time.Duration(value.(int)) * time.Millisecond
|
|
} else {
|
|
controllerConfig.Profile.Memory.Interval = DefaultProfileMemoryInterval
|
|
}
|
|
}
|
|
}
|
|
if value, found := submap["cpu"]; found {
|
|
if submap, ok := value.(map[interface{}]interface{}); ok {
|
|
if value, found := submap["path"]; found {
|
|
controllerConfig.Profile.CPU.Path = value.(string)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if value, found := cfgmap["ctrl"]; found {
|
|
if submap, ok := value.(map[interface{}]interface{}); ok {
|
|
if value, found := submap["listener"]; found {
|
|
listener, err := transport.ParseAddress(value.(string))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
controllerConfig.Ctrl.Listener = listener
|
|
} else {
|
|
panic("controllerConfig must provide [ctrl/listener]")
|
|
}
|
|
|
|
controllerConfig.Ctrl.Options = &CtrlOptions{
|
|
Options: channel.DefaultOptions(),
|
|
PeerHeartbeatOptions: channel.DefaultHeartbeatOptions(),
|
|
RouterHeartbeatOptions: channel.DefaultHeartbeatOptions(),
|
|
}
|
|
|
|
if value, found := submap["options"]; found {
|
|
if submap, ok := value.(map[interface{}]interface{}); ok {
|
|
options, err := channel.LoadOptions(submap)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
controllerConfig.Ctrl.Options.Options = options
|
|
|
|
if val, found := submap["newListener"]; found {
|
|
if newListener, ok := val.(string); ok {
|
|
if newListener != "" {
|
|
if addr, err := transport.ParseAddress(newListener); err == nil {
|
|
controllerConfig.Ctrl.Options.NewListener = &addr
|
|
|
|
if err := verifyNewListenerInServerCert(controllerConfig, addr); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
} else {
|
|
return nil, fmt.Errorf("error loading newListener for [ctrl/options] (%v)", err)
|
|
}
|
|
}
|
|
} else {
|
|
return nil, errors.New("error loading newAddress for [ctrl/options] (must be a string)")
|
|
}
|
|
}
|
|
|
|
if val, found := submap["advertiseAddress"]; found {
|
|
if advertiseAddr, ok := val.(string); ok {
|
|
if advertiseAddr != "" {
|
|
addr, err := transport.ParseAddress(advertiseAddr)
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "error parsing value '%v' for [ctrl/options/advertiseAddress]", advertiseAddr)
|
|
}
|
|
controllerConfig.Ctrl.Options.AdvertiseAddress = &addr
|
|
if controllerConfig.Raft != nil {
|
|
controllerConfig.Raft.AdvertiseAddress = addr
|
|
}
|
|
}
|
|
} else {
|
|
return nil, errors.New("error loading advertiseAddress for [ctrl/options] (must be a string)")
|
|
}
|
|
}
|
|
|
|
if value, found := submap["routerHeartbeats"]; found {
|
|
if submap, ok := value.(map[interface{}]interface{}); ok {
|
|
options, err := channel.LoadHeartbeatOptions(submap)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
controllerConfig.Ctrl.Options.RouterHeartbeatOptions = options
|
|
}
|
|
}
|
|
|
|
if value, found := submap["peerHeartbeats"]; found {
|
|
if submap, ok := value.(map[interface{}]interface{}); ok {
|
|
options, err := channel.LoadHeartbeatOptions(submap)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
controllerConfig.Ctrl.Options.PeerHeartbeatOptions = options
|
|
}
|
|
}
|
|
|
|
if err := controllerConfig.Ctrl.Options.Validate(); err != nil {
|
|
return nil, fmt.Errorf("error loading channel options for [ctrl/options] (%v)", err)
|
|
}
|
|
}
|
|
if value != nil {
|
|
m := value.(map[interface{}]interface{})
|
|
a := strings.TrimPrefix(m["advertiseAddress"].(string), "tls:")
|
|
v := controllerConfig.Id.ValidFor(strings.Split(a, ":")[0])
|
|
if v != nil {
|
|
pfxlog.Logger().Fatalf("provided value for ctrl/options/advertiseAddress is invalid (%v)", v)
|
|
}
|
|
}
|
|
}
|
|
if controllerConfig.Raft != nil && controllerConfig.Raft.AdvertiseAddress == nil {
|
|
return nil, errors.New("[ctrl/options/advertiseAddress] is required when raft is enabled")
|
|
}
|
|
} else {
|
|
panic("controllerConfig [ctrl] section in unexpected format")
|
|
}
|
|
} else {
|
|
panic("controllerConfig must provide [ctrl]")
|
|
}
|
|
|
|
controllerConfig.HealthChecks.BoltCheck.Interval = DefaultHealthChecksBoltCheckInterval
|
|
controllerConfig.HealthChecks.BoltCheck.Timeout = DefaultHealthChecksBoltCheckTimeout
|
|
controllerConfig.HealthChecks.BoltCheck.InitialDelay = DefaultHealthChecksBoltCheckInitialDelay
|
|
|
|
if value, found := cfgmap["healthChecks"]; found {
|
|
if healthChecksMap, ok := value.(map[interface{}]interface{}); ok {
|
|
if value, found := healthChecksMap["boltCheck"]; found {
|
|
if boltMap, ok := value.(map[interface{}]interface{}); ok {
|
|
if value, found := boltMap["interval"]; found {
|
|
if val, err := time.ParseDuration(fmt.Sprintf("%v", value)); err == nil {
|
|
controllerConfig.HealthChecks.BoltCheck.Interval = val
|
|
} else {
|
|
return nil, errors.Wrapf(err, "failed to parse healthChecks.bolt.interval value '%v", value)
|
|
}
|
|
}
|
|
|
|
if value, found := boltMap["timeout"]; found {
|
|
if val, err := time.ParseDuration(fmt.Sprintf("%v", value)); err == nil {
|
|
controllerConfig.HealthChecks.BoltCheck.Timeout = val
|
|
} else {
|
|
return nil, errors.Wrapf(err, "failed to parse healthChecks.bolt.timeout value '%v", value)
|
|
}
|
|
}
|
|
|
|
if value, found := boltMap["initialDelay"]; found {
|
|
if val, err := time.ParseDuration(fmt.Sprintf("%v", value)); err == nil {
|
|
controllerConfig.HealthChecks.BoltCheck.InitialDelay = val
|
|
} else {
|
|
return nil, errors.Wrapf(err, "failed to parse healthChecks.bolt.initialDelay value '%v", value)
|
|
}
|
|
}
|
|
} else {
|
|
pfxlog.Logger().Warn("invalid [healthChecks.bolt] stanza")
|
|
}
|
|
}
|
|
} else {
|
|
pfxlog.Logger().Warn("invalid [healthChecks] stanza")
|
|
}
|
|
}
|
|
|
|
controllerConfig.Command.RateLimiter.Enabled = true
|
|
controllerConfig.Command.RateLimiter.QueueSize = command.DefaultLimiterSize
|
|
controllerConfig.Command.Background.Enabled = DefaultBackgroundQueueEnabled
|
|
controllerConfig.Command.Background.QueueSize = DefaultBackgroundQueueSize
|
|
controllerConfig.Command.Background.DropWhenFull = DefaultBackgroundQueueDropWhenFull
|
|
controllerConfig.Command.Background.DelayThreshold = DefaultBackgroundQueueThreshold
|
|
|
|
commandRateLimiterHandled := false
|
|
if cmdValue, found := cfgmap["command"]; found {
|
|
if cmdMap, ok := cmdValue.(map[interface{}]interface{}); ok {
|
|
if backgroundValue, found := cmdMap["background"]; found {
|
|
if submap, ok := backgroundValue.(map[interface{}]interface{}); ok {
|
|
if value, found := submap["enabled"]; found {
|
|
controllerConfig.Command.Background.Enabled = strings.EqualFold("true", fmt.Sprintf("%v", value))
|
|
}
|
|
|
|
if value, found := submap["dropWhenFull"]; found {
|
|
controllerConfig.Command.Background.DropWhenFull = strings.EqualFold("true", fmt.Sprintf("%v", value))
|
|
}
|
|
|
|
if value, found := submap["queueSize"]; found {
|
|
if intVal, ok := value.(int); ok {
|
|
v := int64(intVal)
|
|
if v < BackgroundQueueMinSize {
|
|
return nil, errors.Errorf("invalid value %v for command.background.queueSize, must be at least %v", value, BackgroundQueueMinSize)
|
|
}
|
|
if v > BackgroundQueueMaxSize {
|
|
return nil, errors.Errorf("invalid value %v for command.background.queueSize, must be at most %v", value, BackgroundQueueMaxSize)
|
|
}
|
|
controllerConfig.Command.Background.QueueSize = uint32(v)
|
|
} else {
|
|
return nil, errors.Errorf("invalid value type %T for command.background.queueSize, must be integer value", value)
|
|
}
|
|
}
|
|
|
|
if value, found := submap["delayThreshold"]; found {
|
|
if val, err := time.ParseDuration(fmt.Sprintf("%v", value)); err == nil {
|
|
controllerConfig.Command.Background.DelayThreshold = val
|
|
} else {
|
|
return nil, errors.Wrapf(err, "failed to parse command.background.delayThreshold value '%v", value)
|
|
}
|
|
}
|
|
|
|
} else {
|
|
return nil, errors.Errorf("invalid command.background configuration section, should be map, not %T", backgroundValue)
|
|
}
|
|
}
|
|
} else {
|
|
return nil, errors.Errorf("invalid command configuration section, should be map, not %T", cmdValue)
|
|
}
|
|
|
|
if value, found := cfgmap["rateLimiter"]; found {
|
|
if err = parseCommandRateLimiter(controllerConfig, value, "command.rateLimiter"); err != nil {
|
|
return nil, err
|
|
}
|
|
commandRateLimiterHandled = true
|
|
}
|
|
}
|
|
|
|
if value, found := cfgmap["commandRateLimiter"]; found && !commandRateLimiterHandled {
|
|
if err = parseCommandRateLimiter(controllerConfig, value, "commandRateLimiter"); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
controllerConfig.TlsHandshakeRateLimiter.SetDefaults()
|
|
controllerConfig.TlsHandshakeRateLimiter.Enabled = DefaultTlsHandshakeRateLimiterEnabled
|
|
controllerConfig.TlsHandshakeRateLimiter.MaxSize = DefaultTlsHandshakeRateLimiterMaxWindow
|
|
controllerConfig.TlsHandshakeRateLimiter.QueueSizeMetric = TlsHandshakeRateLimiterMetricOutstandingCount
|
|
controllerConfig.TlsHandshakeRateLimiter.WindowSizeMetric = TlsHandshakeRateLimiterMetricCurrentWindowSize
|
|
controllerConfig.TlsHandshakeRateLimiter.WorkTimerMetric = TlsHandshakeRateLimiterMetricWorkTimer
|
|
|
|
if value, found := cfgmap["tls"]; found {
|
|
if tlsMap, ok := value.(map[interface{}]interface{}); ok {
|
|
if value, found := tlsMap["handshakeTimeout"]; found {
|
|
if val, err := time.ParseDuration(fmt.Sprintf("%v", value)); err == nil {
|
|
transporttls.SetSharedListenerHandshakeTimeout(val)
|
|
} else {
|
|
return nil, errors.Wrapf(err, "failed to parse tls.handshakeTimeout value '%v", value)
|
|
}
|
|
}
|
|
if err = loadTlsHandshakeRateLimiterConfig(&controllerConfig.TlsHandshakeRateLimiter, tlsMap); err != nil {
|
|
return nil, err
|
|
}
|
|
} else {
|
|
return nil, errors.Errorf("invalid tls configuration section, should be map not %T", value)
|
|
}
|
|
}
|
|
|
|
controllerConfig.RouterDataModel.LogSize = DefaultRouterDataModelLogSize
|
|
controllerConfig.RouterDataModel.ListenerBufferSize = DefaultRouterDataModelListenerBufferSize
|
|
|
|
if value, found := cfgmap["routerDataModel"]; found {
|
|
if submap, ok := value.(map[interface{}]interface{}); ok {
|
|
if value, found := submap["logSize"]; found {
|
|
if val, ok := value.(int); ok {
|
|
if val < MinRouterDataModelLogSize {
|
|
return nil, fmt.Errorf("invalid routerDataModel.logSize '%d', must be >= %d", val, MinRouterDataModelLogSize)
|
|
}
|
|
controllerConfig.RouterDataModel.LogSize = uint64(val)
|
|
} else {
|
|
return nil, fmt.Errorf("failed to parse routerDataModel.logSize, must be type int, not type %T", value)
|
|
}
|
|
}
|
|
|
|
if value, found := submap["listenerBufferSize"]; found {
|
|
if val, ok := value.(int); ok {
|
|
if val < 0 {
|
|
return nil, errors.Wrapf(err, "failed to parse routerDataModel.listenerBufferSize, must be >= 0 %v", value)
|
|
}
|
|
controllerConfig.RouterDataModel.ListenerBufferSize = uint(val)
|
|
} else {
|
|
return nil, errors.Wrapf(err, "failed to parse routerDataModel.listenerBufferSize, should be int not value %T", value)
|
|
}
|
|
}
|
|
} else {
|
|
return nil, errors.Errorf("invalid raft configuration, should be map, not %T", value)
|
|
}
|
|
}
|
|
|
|
edgeConfig, err := LoadEdgeConfigFromMap(cfgmap)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
controllerConfig.Edge = edgeConfig
|
|
|
|
return controllerConfig, nil
|
|
}
|
|
|
|
func parseCommandRateLimiter(controllerConfig *Config, rateLimiterValue interface{}, sectionName string) error {
|
|
submap, ok := rateLimiterValue.(map[interface{}]interface{})
|
|
if !ok {
|
|
return errors.Errorf("invalid commandRateLimiter configuration section, should be map, not %T", rateLimiterValue)
|
|
}
|
|
|
|
if value, found := submap["enabled"]; found {
|
|
controllerConfig.Command.RateLimiter.Enabled = strings.EqualFold("true", fmt.Sprintf("%v", value))
|
|
}
|
|
|
|
if value, found := submap["maxQueued"]; found {
|
|
if intVal, ok := value.(int); ok {
|
|
v := int64(intVal)
|
|
if v < command.MinLimiterSize {
|
|
return errors.Errorf("invalid value %v for commandRateLimiter, must be at least %v", value, command.MinLimiterSize)
|
|
}
|
|
if v > math.MaxUint32 {
|
|
return errors.Errorf("invalid value %v for commandRateLimiter, must be at most %v", value, int64(math.MaxUint32))
|
|
}
|
|
controllerConfig.Command.RateLimiter.QueueSize = uint32(v)
|
|
} else {
|
|
return errors.Errorf("invalid value type %T for %s, must be integer value", value, sectionName)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// isSelfSigned checks if the given certificate is self-signed.
|
|
func isSelfSigned(cert *x509.Certificate) (bool, error) {
|
|
// Check if the Issuer and Subject fields are equal
|
|
if cert.Issuer.String() != cert.Subject.String() {
|
|
return false, nil
|
|
}
|
|
|
|
// Attempt to verify the certificate's signature with its own public key
|
|
err := cert.CheckSignatureFrom(cert)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
|
|
return true, nil
|
|
}
|
|
|
|
func generateDefaultSpiffeId(id identity.Identity) (*url.URL, error) {
|
|
rawCerts := id.Cert().Certificate
|
|
certs := make([]*x509.Certificate, len(rawCerts))
|
|
|
|
for i := range rawCerts {
|
|
cert, err := x509.ParseCertificate(rawCerts[i])
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to parse certificate at index [%d]: %w", i, err)
|
|
}
|
|
certs[i] = cert
|
|
}
|
|
|
|
chain := id.CaPool().GetChain(id.Cert().Leaf, certs...)
|
|
|
|
// chain is 0 or 1, no root possible
|
|
if len(chain) <= 1 {
|
|
return nil, fmt.Errorf("error generating default trust domain from root CA: no root CA detected after chain assembly from the root identity server cert and ca bundle")
|
|
}
|
|
|
|
candidateRoot := chain[len(chain)-1]
|
|
|
|
if candidateRoot == nil {
|
|
return nil, fmt.Errorf("encountered nil candidate root ca during default trust domain generation")
|
|
}
|
|
|
|
if !candidateRoot.IsCA {
|
|
return nil, fmt.Errorf("candidate root CA is not flagged with the x509 CA flag")
|
|
}
|
|
|
|
if selfSigned, _ := isSelfSigned(candidateRoot); !selfSigned {
|
|
return nil, errors.New("candidate root CA is not self signed")
|
|
}
|
|
|
|
rawHash := sha1.Sum(candidateRoot.Raw)
|
|
|
|
fingerprint := fmt.Sprintf("%x", rawHash)
|
|
idStr := "spiffe://" + fingerprint
|
|
|
|
spiffeId, err := url.Parse(idStr)
|
|
|
|
if err != nil {
|
|
return nil, fmt.Errorf("could not parse generated SPIFFE id [%s] as a URI: %w", idStr, err)
|
|
}
|
|
|
|
return spiffeId, nil
|
|
}
|
|
|
|
// GetSpiffeIdFromIdentity will search an Identity for a trust domain encoded as a spiffe:// URI SAN starting
|
|
// from the server cert and up its signing chain. Each certificate must contain 0 or 1 spiffe:// URI SAN. The first
|
|
// SPIFFE id looking up the chain back to the root CA is returned. If no SPIFFE id is encountered, nil is returned.
|
|
// Errors are returned for parsing and processing errors only.
|
|
func GetSpiffeIdFromIdentity(id identity.Identity) (*url.URL, error) {
|
|
tlsCerts := id.ServerCert()
|
|
|
|
spiffeId, err := GetSpiffeIdFromTlsCertChain(tlsCerts)
|
|
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to acquire SPIFFE id from server certs: %w", err)
|
|
}
|
|
|
|
if spiffeId != nil {
|
|
return spiffeId, nil
|
|
}
|
|
|
|
if len(tlsCerts) > 0 {
|
|
chain := id.CaPool().GetChain(tlsCerts[0].Leaf)
|
|
|
|
if len(chain) > 0 {
|
|
spiffeId, _ = GetSpiffeIdFromCertChain(chain)
|
|
}
|
|
}
|
|
|
|
if spiffeId == nil {
|
|
return nil, errors.Errorf("SPIFFE id not found in identity")
|
|
}
|
|
|
|
return spiffeId, nil
|
|
}
|
|
|
|
func ValidateSpiffeId(id *identity.TokenId, spiffeId *url.URL) error {
|
|
if !strings.HasPrefix(spiffeId.Path, "/controller/") {
|
|
return fmt.Errorf("invalid SPIFFE id path: %s, should have /controller/ prefix", spiffeId.Path)
|
|
}
|
|
idInSpiffeId := strings.TrimPrefix(spiffeId.Path, "/controller/")
|
|
if idInSpiffeId != id.Token {
|
|
return fmt.Errorf("spiffe id '%s', does not match subject identifier '%s'", id.Token, idInSpiffeId)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// GetSpiffeIdFromCertChain cycles through a slice of certificates that goes from leaf up CAs. Each certificate
|
|
// must contain 0 or 1 spiffe:// URI SAN. The first encountered SPIFFE id looking up the chain back to the root CA is returned.
|
|
// If no SPIFFE id is encountered, nil is returned. Errors are returned for parsing and processing errors only.
|
|
func GetSpiffeIdFromCertChain(certs []*x509.Certificate) (*url.URL, error) {
|
|
var spiffeId *url.URL
|
|
for _, cert := range certs {
|
|
var err error
|
|
spiffeId, err = GetSpiffeIdFromCert(cert)
|
|
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to determine SPIFFE ID from x509 certificate chain: %w", err)
|
|
}
|
|
|
|
if spiffeId != nil {
|
|
return spiffeId, nil
|
|
}
|
|
}
|
|
|
|
return nil, errors.New("failed to determine SPIFFE ID, no spiffe:// URI SANs found in x509 certificate chain")
|
|
}
|
|
|
|
// GetSpiffeIdFromTlsCertChain will search a tls certificate chain for a trust domain encoded as a spiffe:// URI SAN.
|
|
// Each certificate must contain 0 or 1 spiffe:// URI SAN. The first SPIFFE id looking up the chain is returned. If
|
|
// no SPIFFE id is encountered, nil is returned. Errors are returned for parsing and processing errors only.
|
|
func GetSpiffeIdFromTlsCertChain(tlsCerts []*tls.Certificate) (*url.URL, error) {
|
|
for _, tlsCert := range tlsCerts {
|
|
for i, rawCert := range tlsCert.Certificate {
|
|
cert, err := x509.ParseCertificate(rawCert)
|
|
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to parse TLS cert at index [%d]: %w", i, err)
|
|
}
|
|
|
|
spiffeId, err := GetSpiffeIdFromCert(cert)
|
|
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to determine SPIFFE ID from TLS cert at index [%d]: %w", i, err)
|
|
}
|
|
|
|
if spiffeId != nil {
|
|
return spiffeId, nil
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil, nil
|
|
}
|
|
|
|
// GetSpiffeIdFromCert will search a x509 certificate for a trust domain encoded as a spiffe:// URI SAN.
|
|
// Each certificate must contain 0 or 1 spiffe:// URI SAN. The first SPIFFE id looking up the chain is returned. If
|
|
// no SPIFFE id is encountered, nil is returned. Errors are returned for parsing and processing errors only.
|
|
func GetSpiffeIdFromCert(cert *x509.Certificate) (*url.URL, error) {
|
|
var spiffeId *url.URL
|
|
for _, uriSan := range cert.URIs {
|
|
if uriSan.Scheme == "spiffe" {
|
|
if spiffeId != nil {
|
|
return nil, fmt.Errorf("multiple URI SAN spiffe:// ids encountered, must only have one, encountered at least two: [%s] and [%s]", spiffeId.String(), uriSan.String())
|
|
}
|
|
spiffeId = uriSan
|
|
}
|
|
}
|
|
|
|
return spiffeId, nil
|
|
}
|
|
|
|
func loadTlsHandshakeRateLimiterConfig(rateLimitConfig *command.AdaptiveRateLimiterConfig, cfgmap map[interface{}]interface{}) error {
|
|
timeoutSpecified := false
|
|
|
|
if value, found := cfgmap["rateLimiter"]; found {
|
|
if submap, ok := value.(map[interface{}]interface{}); ok {
|
|
if err := command.LoadAdaptiveRateLimiterConfig(rateLimitConfig, submap); err != nil {
|
|
return err
|
|
}
|
|
if rateLimitConfig.MaxSize < TlsHandshakeRateLimiterMinSizeValue {
|
|
return errors.Errorf("invalid value %v for tls.rateLimiter.maxSize, must be at least %v",
|
|
rateLimitConfig.MaxSize, TlsHandshakeRateLimiterMinSizeValue)
|
|
}
|
|
if rateLimitConfig.MaxSize > TlsHandshakeRateLimiterMaxSizeValue {
|
|
return errors.Errorf("invalid value %v for tls.rateLimiter.maxSize, must be at most %v",
|
|
rateLimitConfig.MaxSize, TlsHandshakeRateLimiterMaxSizeValue)
|
|
}
|
|
|
|
if rateLimitConfig.MinSize < TlsHandshakeRateLimiterMinSizeValue {
|
|
return errors.Errorf("invalid value %v for tls.rateLimiter.minSize, must be at least %v",
|
|
rateLimitConfig.MinSize, TlsHandshakeRateLimiterMinSizeValue)
|
|
}
|
|
if rateLimitConfig.MinSize > TlsHandshakeRateLimiterMaxSizeValue {
|
|
return errors.Errorf("invalid value %v for tls.rateLimiter.minSize, must be at most %v",
|
|
rateLimitConfig.MinSize, TlsHandshakeRateLimiterMaxSizeValue)
|
|
}
|
|
|
|
_, timeoutSpecified = cfgmap["timeout"]
|
|
} else {
|
|
return errors.Errorf("invalid type for tls.rateLimiter, should be map instead of %T", value)
|
|
}
|
|
}
|
|
|
|
if !timeoutSpecified {
|
|
rateLimitConfig.Timeout = transporttls.GetSharedListenerHandshakeTimeout() + (2 * time.Second)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// verifyNewListenerInServerCert verifies that the hostname (ip/dns) for addr is present as an IP/DNS SAN in the first
|
|
// certificate provided in the controller's identity server certificates. This is to avoid scenarios where
|
|
// newListener propagated to routers who will never be able to verify the controller's certificates due to SAN issues.
|
|
func verifyNewListenerInServerCert(controllerConfig *Config, addr transport.Address) error {
|
|
addrSplits := strings.Split(addr.String(), ":")
|
|
if len(addrSplits) < 3 {
|
|
return errors.New("could not determine newListener's host value, expected at least three segments")
|
|
}
|
|
|
|
host := addrSplits[1]
|
|
|
|
serverCerts := controllerConfig.Id.Identity.ServerCert()
|
|
|
|
if len(serverCerts) == 0 {
|
|
return errors.New("could not verify newListener value, server certificate for identity contains no certificates")
|
|
}
|
|
|
|
hostFound := false
|
|
for _, serverCert := range serverCerts {
|
|
for _, dnsName := range serverCert.Leaf.DNSNames {
|
|
if dnsName == host {
|
|
hostFound = true
|
|
break
|
|
}
|
|
}
|
|
|
|
if hostFound {
|
|
break
|
|
}
|
|
|
|
for _, ipAddresses := range serverCert.Leaf.IPAddresses {
|
|
if host == ipAddresses.String() {
|
|
hostFound = true
|
|
break
|
|
}
|
|
}
|
|
|
|
if hostFound {
|
|
break
|
|
}
|
|
}
|
|
|
|
if !hostFound {
|
|
return fmt.Errorf("could not find newListener [%s] host value [%s] in first certificate for controller identity", addr.String(), host)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
type CertValidatingIdentity struct {
|
|
identity.Identity
|
|
}
|
|
|
|
func (self *CertValidatingIdentity) ClientTLSConfig() *tls.Config {
|
|
cfg := self.Identity.ClientTLSConfig()
|
|
cfg.VerifyConnection = self.VerifyConnection
|
|
return cfg
|
|
}
|
|
|
|
func (self *CertValidatingIdentity) ServerTLSConfig() *tls.Config {
|
|
cfg := self.Identity.ServerTLSConfig()
|
|
cfg.VerifyConnection = self.VerifyConnection
|
|
return cfg
|
|
}
|
|
|
|
func (self *CertValidatingIdentity) VerifyConnection(state tls.ConnectionState) error {
|
|
if len(state.PeerCertificates) == 0 {
|
|
return errors.New("no peer certificates provided")
|
|
}
|
|
log := pfxlog.Logger()
|
|
for _, cert := range state.PeerCertificates {
|
|
log.Infof("cert provided: CN: %v IsCA: %v", cert.Subject.CommonName, cert.IsCA)
|
|
}
|
|
|
|
options := x509.VerifyOptions{
|
|
Roots: self.Identity.CA(),
|
|
Intermediates: x509.NewCertPool(),
|
|
}
|
|
|
|
for _, cert := range state.PeerCertificates[1:] {
|
|
options.Intermediates.AddCert(cert)
|
|
}
|
|
|
|
result, err := state.PeerCertificates[0].Verify(options)
|
|
|
|
if err != nil {
|
|
pfxlog.Logger().WithError(err).Error("got error validating cert")
|
|
return err
|
|
}
|
|
|
|
log.Infof("got result: %v", result)
|
|
return nil
|
|
}
|