mirror of
https://github.com/openziti/ziti.git
synced 2026-10-11 15:10:59 +00:00
623681db87
- as updates to API Sessions can now happen later than the last time they were active, updatedAt is no longer the correct representation of the last activity an API Session had - move all logic that used updatedAt to lastActivityAt - add migration to set lastActivityAt
490 lines
14 KiB
Go
490 lines
14 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"
|
|
"encoding/pem"
|
|
"fmt"
|
|
"github.com/michaelquigley/pfxlog"
|
|
"github.com/openziti/foundation/identity/identity"
|
|
"github.com/pkg/errors"
|
|
"io/ioutil"
|
|
"reflect"
|
|
"strconv"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
const (
|
|
sessionTimeoutDefault = 10
|
|
sessionTimeoutMin = 1
|
|
|
|
enrollmentDurationMin = 5
|
|
enrollmentDurationDefault = 1440
|
|
)
|
|
|
|
type Enrollment struct {
|
|
SigningCert identity.Identity
|
|
SigningCertConfig identity.IdentityConfig
|
|
SigningCertCaPem []byte
|
|
EdgeIdentity EnrollmentOption
|
|
EdgeRouter EnrollmentOption
|
|
}
|
|
|
|
type EnrollmentOption struct {
|
|
DurationMinutes time.Duration
|
|
}
|
|
|
|
type Api struct {
|
|
SessionTimeoutSeconds time.Duration
|
|
ActivityUpdateBatchSize int
|
|
ActivityUpdateInterval time.Duration
|
|
|
|
Listener string
|
|
Advertise string
|
|
Identity identity.Identity
|
|
IdentityConfig identity.IdentityConfig
|
|
IdentityCaPem []byte
|
|
HttpTimeouts HttpTimeouts
|
|
|
|
}
|
|
|
|
type Config struct {
|
|
RootIdentityConfig identity.IdentityConfig
|
|
RootIdentity identity.Identity
|
|
RootIdentityCaPem []byte
|
|
Enabled bool
|
|
Api Api
|
|
Enrollment Enrollment
|
|
caPems [][]byte
|
|
caPemsBuf []byte
|
|
caPemsOnce sync.Once
|
|
}
|
|
|
|
type HttpTimeouts struct {
|
|
ReadTimeoutDuration time.Duration
|
|
ReadHeaderTimeoutDuration time.Duration
|
|
WriteTimeoutDuration time.Duration
|
|
IdleTimeoutsDuration time.Duration
|
|
}
|
|
|
|
func (c *Config) SessionTimeoutDuration() time.Duration {
|
|
return c.Api.SessionTimeoutSeconds
|
|
}
|
|
|
|
func toHex(data []byte) string {
|
|
var buf bytes.Buffer
|
|
for i, b := range data {
|
|
if i > 0 {
|
|
_, _ = fmt.Fprintf(&buf, ":")
|
|
}
|
|
_, _ = fmt.Fprintf(&buf, "%02x", b)
|
|
}
|
|
return strings.ToUpper(buf.String())
|
|
}
|
|
|
|
func (c *Config) CaPems() []byte {
|
|
c.caPemsOnce.Do(func() {
|
|
buf := bytes.Buffer{}
|
|
//dedupe chains
|
|
pemMap := map[string][]byte{}
|
|
for _, caChain := range c.caPems {
|
|
rest := caChain
|
|
for len(rest) != 0 {
|
|
var block *pem.Block
|
|
block, rest = pem.Decode(rest)
|
|
|
|
if block != nil {
|
|
hash := sha1.Sum(block.Bytes)
|
|
fingerprint := toHex(hash[:])
|
|
pemMap[fingerprint] = pem.EncodeToMemory(block)
|
|
}
|
|
}
|
|
}
|
|
|
|
i := 0
|
|
for _, pemBytes := range pemMap {
|
|
if i != 0 {
|
|
buf.Write([]byte("\n"))
|
|
}
|
|
buf.Write(pemBytes)
|
|
i++
|
|
}
|
|
c.caPemsBuf = buf.Bytes()
|
|
})
|
|
|
|
return c.caPemsBuf
|
|
}
|
|
|
|
func (c *Config) loadRootIdentity(fabricConfigMap map[interface{}]interface{}) error {
|
|
var fabricIdentitySubMap map[interface{}]interface{}
|
|
if value, found := fabricConfigMap["identity"]; found {
|
|
fabricIdentitySubMap = value.(map[interface{}]interface{})
|
|
} else {
|
|
return errors.New("required configuration value [identity] missing")
|
|
}
|
|
|
|
if value, found := fabricIdentitySubMap["cert"]; found {
|
|
c.RootIdentityConfig.Cert = value.(string)
|
|
} else {
|
|
return fmt.Errorf("required configuration value [identity.cert] is missing")
|
|
}
|
|
|
|
if value, found := fabricIdentitySubMap["server_cert"]; found {
|
|
c.RootIdentityConfig.ServerCert = value.(string)
|
|
} else {
|
|
return fmt.Errorf("required configuration value [identity.server_cert] is missing")
|
|
}
|
|
|
|
if value, found := fabricIdentitySubMap["key"]; found {
|
|
c.RootIdentityConfig.Key = value.(string)
|
|
} else {
|
|
return fmt.Errorf("required configuration value [identity.key] is missing")
|
|
}
|
|
|
|
if value, found := fabricIdentitySubMap["server_key"]; found {
|
|
c.RootIdentityConfig.ServerKey = value.(string)
|
|
} //allow "key" to be the default, this isn't an error
|
|
|
|
if value, found := fabricIdentitySubMap["ca"]; found {
|
|
c.RootIdentityConfig.CA = value.(string)
|
|
}
|
|
|
|
var err error
|
|
if c.RootIdentityCaPem, err = ioutil.ReadFile(c.RootIdentityConfig.CA); err != nil {
|
|
return fmt.Errorf("could not read file CA file from [identity.ca]")
|
|
}
|
|
|
|
c.caPems = append(c.caPems, c.RootIdentityCaPem)
|
|
|
|
c.RootIdentity, err = identity.LoadIdentity(c.RootIdentityConfig)
|
|
|
|
return err
|
|
}
|
|
|
|
func (c *Config) loadApiSection(edgeConfigMap map[interface{}]interface{}) error {
|
|
c.Api = Api{}
|
|
var err error
|
|
|
|
c.Api.ActivityUpdateBatchSize = 250
|
|
c.Api.ActivityUpdateInterval = 90 * time.Second
|
|
|
|
if value, found := edgeConfigMap["api"]; found {
|
|
submap := value.(map[interface{}]interface{})
|
|
|
|
if value, found := submap["listener"]; found {
|
|
c.Api.Listener = value.(string)
|
|
} else {
|
|
return errors.New("required configuration value [edge.api.listener] missing")
|
|
}
|
|
|
|
if value, found := submap["advertise"]; found {
|
|
c.Api.Advertise = value.(string)
|
|
} else {
|
|
return errors.New("required configuration value [edge.api.advertise] missing")
|
|
}
|
|
|
|
var intValue = 0
|
|
if value, found := submap["sessionTimeoutMinutes"]; found {
|
|
intValue = value.(int)
|
|
}
|
|
|
|
if intValue < sessionTimeoutMin {
|
|
intValue = sessionTimeoutDefault
|
|
pfxlog.Logger().Warn("[edge.api.sessionTimeout] defaulted to " + strconv.Itoa(intValue))
|
|
}
|
|
|
|
c.Api.SessionTimeoutSeconds = time.Duration(intValue) * time.Minute
|
|
|
|
var apiIdentitySubMap map[interface{}]interface{}
|
|
if value, found = submap["identity"]; found {
|
|
apiIdentitySubMap = value.(map[interface{}]interface{})
|
|
}
|
|
|
|
if err = c.loadIApiIdentity(apiIdentitySubMap); err != nil {
|
|
return fmt.Errorf("error loading Edge API Identity: %s", err)
|
|
}
|
|
|
|
if err = c.loadHttpTimeouts(submap); err != nil {
|
|
return fmt.Errorf("error loading Edge API Http Timeouts: %s", err)
|
|
}
|
|
|
|
if val, ok := submap["activityUpdateBatchSize"]; ok {
|
|
if c.Api.ActivityUpdateBatchSize, ok = val.(int); !ok {
|
|
return errors.Errorf("invalid type %v for apiSessions.activityUpdateBatchSize, must be int", reflect.TypeOf(val))
|
|
}
|
|
}
|
|
|
|
if val, ok := submap["activityUpdateInterval"]; ok {
|
|
if strVal, ok := val.(string); !ok {
|
|
return errors.Errorf("invalid type %v for apiSessions.activityUpdateInterval, must be string duration", reflect.TypeOf(val))
|
|
} else {
|
|
if c.Api.ActivityUpdateInterval, err = time.ParseDuration(strVal); err != nil {
|
|
return errors.Wrapf(err, "invalid value %v for apiSessions.activityUpdateInterval, must be string duration", val)
|
|
}
|
|
}
|
|
}
|
|
|
|
if c.Api.ActivityUpdateBatchSize < 1 || c.Api.ActivityUpdateBatchSize > 10000 {
|
|
return errors.Errorf("invalid value %v for apiSessions.activityUpdateBatchSize, must be between 1 and 10000", c.Api.ActivityUpdateBatchSize)
|
|
}
|
|
|
|
if c.Api.ActivityUpdateInterval < time.Millisecond || c.Api.ActivityUpdateInterval > 10*time.Minute {
|
|
return errors.Errorf("invalid value %v for apiSessions.activityUpdateInterval, must be between 1ms and 10m", c.Api.ActivityUpdateInterval.String())
|
|
}
|
|
|
|
return nil
|
|
|
|
} else {
|
|
return errors.New("required configuration section [edge.api] missing")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (c *Config) loadIApiIdentity(apiIdentitySubMap map[interface{}]interface{}) error {
|
|
//default to root identity value
|
|
c.Api.IdentityConfig = identity.IdentityConfig{
|
|
Key: c.RootIdentityConfig.Key,
|
|
Cert: c.RootIdentityConfig.Cert,
|
|
ServerCert: c.RootIdentityConfig.ServerCert,
|
|
ServerKey: c.RootIdentityConfig.ServerKey,
|
|
CA: c.RootIdentityConfig.CA,
|
|
}
|
|
|
|
if apiIdentitySubMap != nil {
|
|
if value, found := apiIdentitySubMap["server_cert"]; found {
|
|
c.Api.IdentityConfig.ServerCert = value.(string)
|
|
} else {
|
|
return fmt.Errorf("configuration value [edge.api.identity.server_cert] is required if [edge.api.identity] is specified")
|
|
}
|
|
|
|
if value, found := apiIdentitySubMap["server_key"]; found {
|
|
c.Api.IdentityConfig.ServerKey = value.(string)
|
|
} else {
|
|
return fmt.Errorf("configuration value [edge.api.identity.server_key] is required if [edge.api.identity] is specified")
|
|
}
|
|
|
|
if value, found := apiIdentitySubMap["ca"]; found {
|
|
c.Api.IdentityConfig.CA = value.(string)
|
|
var err error
|
|
if c.Api.IdentityCaPem, err = ioutil.ReadFile(c.Api.IdentityConfig.CA); err != nil {
|
|
return fmt.Errorf("could not read file CA file from [edge.api.identity.ca]")
|
|
}
|
|
c.caPems = append(c.caPems, c.Api.IdentityCaPem)
|
|
}
|
|
}
|
|
|
|
var err error
|
|
c.Api.Identity, err = identity.LoadIdentity(c.Api.IdentityConfig)
|
|
|
|
return err
|
|
}
|
|
|
|
func (c *Config) loadEnrollmentSection(edgeConfigMap map[interface{}]interface{}) error {
|
|
c.Enrollment = Enrollment{}
|
|
var err error
|
|
|
|
if value, found := edgeConfigMap["enrollment"]; found {
|
|
submap := value.(map[interface{}]interface{})
|
|
|
|
if value, found := submap["signingCert"]; found {
|
|
submap := value.(map[interface{}]interface{})
|
|
c.Enrollment.SigningCertConfig = identity.IdentityConfig{}
|
|
|
|
if value, found := submap["cert"]; found {
|
|
c.Enrollment.SigningCertConfig.Cert = value.(string)
|
|
certPem, err := ioutil.ReadFile(c.Enrollment.SigningCertConfig.Cert)
|
|
if err != nil {
|
|
pfxlog.Logger().WithError(err).Panic("unable to read [edge.enrollment.cert]")
|
|
}
|
|
//The signer is a valid trust anchor
|
|
c.caPems = append(c.caPems, certPem)
|
|
|
|
} else {
|
|
return fmt.Errorf("required configuration value [edge.enrollment.cert] is missing")
|
|
}
|
|
|
|
if value, found := submap["key"]; found {
|
|
c.Enrollment.SigningCertConfig.Key = value.(string)
|
|
} else {
|
|
return fmt.Errorf("required configuration value [edge.enrollment.key] is missing")
|
|
}
|
|
|
|
if value, found := submap["ca"]; found {
|
|
c.Enrollment.SigningCertConfig.CA = value.(string)
|
|
|
|
if c.Enrollment.SigningCertCaPem, err = ioutil.ReadFile(c.Enrollment.SigningCertConfig.CA); err != nil {
|
|
return fmt.Errorf("could not read file CA file from [edge.enrollment.signingCert.ca]")
|
|
}
|
|
|
|
c.caPems = append(c.caPems, c.Enrollment.SigningCertCaPem)
|
|
} //not an error if the signing cert's CA is already represented in the root [identity.ca]
|
|
|
|
if c.Enrollment.SigningCert, err = identity.LoadIdentity(c.Enrollment.SigningCertConfig); err != nil {
|
|
return fmt.Errorf("error loading [edge.enrollment.signingCert]: %s", err)
|
|
}
|
|
|
|
} else {
|
|
return errors.New("required configuration section [edge.enrollment.signingCert] missing")
|
|
}
|
|
|
|
if value, found := submap["edgeIdentity"]; found {
|
|
submap := value.(map[interface{}]interface{})
|
|
|
|
var edgeIdentityDurationInt = 0
|
|
if value, found := submap["durationMinutes"]; found {
|
|
edgeIdentityDurationInt = value.(int)
|
|
}
|
|
|
|
if edgeIdentityDurationInt < enrollmentDurationMin {
|
|
edgeIdentityDurationInt = enrollmentDurationDefault
|
|
}
|
|
|
|
c.Enrollment.EdgeIdentity = EnrollmentOption{DurationMinutes: time.Duration(edgeIdentityDurationInt) * time.Minute}
|
|
|
|
} else {
|
|
return errors.New("required configuration section [edge.enrollment.edgeIdentity] missing")
|
|
}
|
|
|
|
if value, found := submap["edgeRouter"]; found {
|
|
submap := value.(map[interface{}]interface{})
|
|
|
|
var edgeRouterDurationInt = 0
|
|
if value, found := submap["durationMinutes"]; found {
|
|
edgeRouterDurationInt = value.(int)
|
|
}
|
|
|
|
if edgeRouterDurationInt < enrollmentDurationMin {
|
|
edgeRouterDurationInt = enrollmentDurationDefault
|
|
}
|
|
|
|
c.Enrollment.EdgeRouter = EnrollmentOption{DurationMinutes: time.Duration(edgeRouterDurationInt) * time.Minute}
|
|
|
|
} else {
|
|
return errors.New("required configuration section [edge.enrollment.edgeRouter] missing")
|
|
}
|
|
|
|
} else {
|
|
return errors.New("required configuration section [edge.enrollment] missing")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (c *Config) loadHttpTimeouts(apiSubMap map[interface{}]interface{}) error {
|
|
c.Api.HttpTimeouts = HttpTimeouts{
|
|
ReadTimeoutDuration: 5 * time.Second,
|
|
ReadHeaderTimeoutDuration: 0,
|
|
WriteTimeoutDuration: 10 * time.Second,
|
|
IdleTimeoutsDuration: 5 * time.Second,
|
|
}
|
|
|
|
if value, found := apiSubMap["httpTimeouts"]; found && value != nil {
|
|
httpTimeoutsSubMap := value.(map[interface{}]interface{})
|
|
|
|
if value, found := httpTimeoutsSubMap["readTimeoutMs"]; found {
|
|
readTimeoutMs := value.(int)
|
|
if readTimeoutMs < 0 {
|
|
readTimeoutMs = 0
|
|
}
|
|
c.Api.HttpTimeouts.ReadTimeoutDuration = time.Duration(readTimeoutMs) * time.Millisecond
|
|
} else {
|
|
pfxlog.Logger().Warnf("[edge.api.httpTimeouts.readTimeoutMs] defaulted to %v", c.Api.HttpTimeouts.ReadTimeoutDuration.Milliseconds())
|
|
}
|
|
|
|
if value, found := httpTimeoutsSubMap["readHeaderTimeoutMs"]; found {
|
|
readHeaderTimeoutMs := value.(int)
|
|
if readHeaderTimeoutMs < 0 {
|
|
readHeaderTimeoutMs = 0
|
|
}
|
|
|
|
c.Api.HttpTimeouts.ReadHeaderTimeoutDuration = time.Duration(readHeaderTimeoutMs) * time.Millisecond
|
|
} else {
|
|
pfxlog.Logger().Warnf("[edge.api.httpTimeouts.readHeaderTimeoutMs] defaulted to %v", c.Api.HttpTimeouts.ReadHeaderTimeoutDuration.Milliseconds())
|
|
}
|
|
|
|
if value, found := httpTimeoutsSubMap["writeTimeoutMs"]; found {
|
|
writeTimeoutMs := value.(int)
|
|
if writeTimeoutMs < 0 {
|
|
writeTimeoutMs = 0
|
|
}
|
|
|
|
c.Api.HttpTimeouts.WriteTimeoutDuration = time.Duration(writeTimeoutMs) * time.Millisecond
|
|
} else {
|
|
pfxlog.Logger().Warnf("[edge.api.httpTimeouts.writeTimeoutMs] defaulted to %v", c.Api.HttpTimeouts.ReadHeaderTimeoutDuration.Milliseconds())
|
|
}
|
|
|
|
if value, found := httpTimeoutsSubMap["idleTimeoutMs"]; found {
|
|
idleTimeoutMs := value.(int)
|
|
if idleTimeoutMs < 0 {
|
|
idleTimeoutMs = 0
|
|
}
|
|
|
|
c.Api.HttpTimeouts.IdleTimeoutsDuration = time.Duration(idleTimeoutMs) * time.Millisecond
|
|
} else {
|
|
pfxlog.Logger().Warnf("[edge.api.httpTimeouts.idleTimeoutMs] defaulted to %v", c.Api.HttpTimeouts.ReadHeaderTimeoutDuration.Milliseconds())
|
|
}
|
|
|
|
} else {
|
|
pfxlog.Logger().Warn("using default edge.api.httpTimeouts, no config section found")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func LoadFromMap(cfgmap map[interface{}]interface{}) (*Config, error) {
|
|
edgeConfig := &Config{
|
|
Enabled: false,
|
|
}
|
|
|
|
var edgeConfigMap map[interface{}]interface{}
|
|
|
|
if val, ok := cfgmap["edge"]; ok && val != nil {
|
|
if edgeConfigMap, ok = val.(map[interface{}]interface{}); !ok {
|
|
return nil, fmt.Errorf("expected map as edge configuration")
|
|
}
|
|
} else {
|
|
return edgeConfig, nil
|
|
}
|
|
|
|
edgeConfig.Enabled = cfgmap != nil
|
|
|
|
if !edgeConfig.Enabled {
|
|
return edgeConfig, nil
|
|
}
|
|
|
|
var err error
|
|
|
|
if err = edgeConfig.loadRootIdentity(cfgmap); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if err = edgeConfig.loadApiSection(edgeConfigMap); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if err = edgeConfig.loadEnrollmentSection(edgeConfigMap); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return edgeConfig, nil
|
|
}
|