Files
ziti/controller/config/config.go
T
Andrew Martinez 623681db87 add lastActivityAt
- 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
2021-03-17 13:49:12 -04:00

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
}