mirror of
https://github.com/openziti/ziti.git
synced 2026-09-24 19:32:22 +00:00
390 lines
11 KiB
Go
390 lines
11 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"
|
|
"net"
|
|
"reflect"
|
|
"strconv"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
const (
|
|
sessionTimeoutDefault = 10 * time.Minute
|
|
sessionTimeoutMin = 1 * time.Minute
|
|
|
|
enrollmentDurationMin = 5 * time.Minute
|
|
enrollmentDurationDefault = 5 * time.Minute
|
|
)
|
|
|
|
type Enrollment struct {
|
|
SigningCert identity.Identity
|
|
SigningCertConfig identity.Config
|
|
SigningCertCaPem []byte
|
|
EdgeIdentity EnrollmentOption
|
|
EdgeRouter EnrollmentOption
|
|
}
|
|
|
|
type EnrollmentOption struct {
|
|
Duration time.Duration
|
|
}
|
|
|
|
type Api struct {
|
|
SessionTimeout time.Duration
|
|
ActivityUpdateBatchSize int
|
|
ActivityUpdateInterval time.Duration
|
|
|
|
Listener string
|
|
Address string
|
|
IdentityCaPem []byte
|
|
HttpTimeouts HttpTimeouts
|
|
}
|
|
|
|
type Config struct {
|
|
Enabled bool
|
|
Api Api
|
|
Enrollment Enrollment
|
|
|
|
caPems *bytes.Buffer
|
|
caPemsOnce sync.Once
|
|
}
|
|
|
|
type HttpTimeouts struct {
|
|
ReadTimeoutDuration time.Duration
|
|
ReadHeaderTimeoutDuration time.Duration
|
|
WriteTimeoutDuration time.Duration
|
|
IdleTimeoutsDuration time.Duration
|
|
}
|
|
|
|
func NewConfig() *Config {
|
|
return &Config{
|
|
Enabled: false,
|
|
caPems: bytes.NewBuffer(nil),
|
|
}
|
|
}
|
|
|
|
func (c *Config) SessionTimeoutDuration() time.Duration {
|
|
return c.Api.SessionTimeout
|
|
}
|
|
|
|
func (c *Config) CaPems() []byte {
|
|
c.caPemsOnce.Do(func() {
|
|
c.RefreshCaPems()
|
|
})
|
|
|
|
return c.caPems.Bytes()
|
|
}
|
|
|
|
// AddCaPems adds a byte array of certificates to the current buffered list of CAs. The certificates
|
|
// should be in PEM format separated by new lines. RefreshCaPems should be called after all
|
|
// calls to AddCaPems are completed.
|
|
func (c *Config) AddCaPems(caPems []byte) {
|
|
c.caPems.WriteString("\n")
|
|
c.caPems.Write(caPems)
|
|
}
|
|
|
|
func (c *Config) RefreshCaPems() {
|
|
c.caPems = CalculateCaPems(c.caPems)
|
|
}
|
|
|
|
|
|
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 {
|
|
apiSubMap := value.(map[interface{}]interface{})
|
|
|
|
if val, ok := apiSubMap["address"]; ok {
|
|
if c.Api.Address, ok = val.(string); !ok {
|
|
return errors.Errorf("invalid type %t for [edge.api.address], must be string", val)
|
|
}
|
|
|
|
if c.Api.Address == "" {
|
|
return errors.Errorf("invalid type %t for [edge.api.address], must not be an empty string", val)
|
|
}
|
|
|
|
if err := validateHostPortString(c.Api.Address); err != nil {
|
|
return errors.Errorf("invalid value %s for [edge.api.address]: %v", c.Api.Address, err)
|
|
}
|
|
} else {
|
|
return errors.New("required value [edge.api.address] is required")
|
|
}
|
|
|
|
var durationValue = 0 * time.Second
|
|
if value, found := apiSubMap["sessionTimeout"]; found {
|
|
strValue := value.(string)
|
|
durationValue, err = time.ParseDuration(strValue)
|
|
if err != nil {
|
|
return errors.Errorf("error parsing [edge.api.sessionTimeout], invalid duration string %s, cannot parse as duration (e.g. 1m): %v", strValue, err)
|
|
}
|
|
}
|
|
|
|
if durationValue < sessionTimeoutMin {
|
|
durationValue = sessionTimeoutDefault
|
|
pfxlog.Logger().Warnf("[edge.api.sessionTimeout] defaulted to %v", durationValue)
|
|
}
|
|
|
|
c.Api.SessionTimeout = durationValue
|
|
|
|
if val, ok := apiSubMap["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 := apiSubMap["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")
|
|
}
|
|
}
|
|
|
|
func validateHostPortString(address string) error {
|
|
address = strings.TrimSpace(address)
|
|
|
|
if address == "" {
|
|
return errors.New("must not be an empty string or unspecified")
|
|
}
|
|
|
|
host, port, err := net.SplitHostPort(address)
|
|
|
|
if err != nil {
|
|
return errors.Errorf("could not split host and port: %v", err)
|
|
}
|
|
|
|
if host == "" {
|
|
return errors.New("host must be specified")
|
|
}
|
|
|
|
if port == "" {
|
|
return errors.New("port must be specified")
|
|
}
|
|
|
|
if port, err := strconv.ParseInt(port, 10, 32); err != nil {
|
|
return errors.New("invalid port, must be a integer")
|
|
} else if port < 1 || port > 65535 {
|
|
return errors.New("invalid port, must 1-65535")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (c *Config) loadEnrollmentSection(edgeConfigMap map[interface{}]interface{}) error {
|
|
c.Enrollment = Enrollment{}
|
|
var err error
|
|
|
|
if value, found := edgeConfigMap["enrollment"]; found {
|
|
enrollmentSubMap := value.(map[interface{}]interface{})
|
|
|
|
if value, found := enrollmentSubMap["signingCert"]; found {
|
|
signingCertSubMap := value.(map[interface{}]interface{})
|
|
c.Enrollment.SigningCertConfig = identity.Config{}
|
|
|
|
if value, found := signingCertSubMap["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.WriteString("\n")
|
|
_, _ = c.caPems.Write(certPem)
|
|
|
|
} else {
|
|
return fmt.Errorf("required configuration value [edge.enrollment.cert] is missing")
|
|
}
|
|
|
|
if value, found := signingCertSubMap["key"]; found {
|
|
c.Enrollment.SigningCertConfig.Key = value.(string)
|
|
} else {
|
|
return fmt.Errorf("required configuration value [edge.enrollment.key] is missing")
|
|
}
|
|
|
|
if value, found := signingCertSubMap["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.WriteString("\n")
|
|
_, _ = c.caPems.Write(c.Enrollment.SigningCertCaPem)
|
|
} //not an error if the signing certificate'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 := enrollmentSubMap["edgeIdentity"]; found {
|
|
edgeIdentitySubMap := value.(map[interface{}]interface{})
|
|
|
|
edgeIdentityDuration := 0 * time.Second
|
|
if value, found := edgeIdentitySubMap["duration"]; found {
|
|
strValue := value.(string)
|
|
var err error
|
|
edgeIdentityDuration, err = time.ParseDuration(strValue)
|
|
|
|
if err != nil {
|
|
return errors.Errorf("error parsing [edge.enrollment.edgeIdentity.duration], invalid duration string %s, cannot parse as duration (e.g. 1m): %v", strValue, err)
|
|
}
|
|
}
|
|
|
|
if edgeIdentityDuration < enrollmentDurationMin {
|
|
edgeIdentityDuration = enrollmentDurationDefault
|
|
}
|
|
|
|
c.Enrollment.EdgeIdentity = EnrollmentOption{Duration: edgeIdentityDuration}
|
|
|
|
} else {
|
|
return errors.New("required configuration section [edge.enrollment.edgeIdentity] missing")
|
|
}
|
|
|
|
if value, found := enrollmentSubMap["edgeRouter"]; found {
|
|
edgeRouterSubMap := value.(map[interface{}]interface{})
|
|
|
|
edgeRouterDuration := 0 * time.Second
|
|
if value, found := edgeRouterSubMap["duration"]; found {
|
|
strValue := value.(string)
|
|
var err error
|
|
edgeRouterDuration, err = time.ParseDuration(strValue)
|
|
|
|
if err != nil {
|
|
return errors.Errorf("error parsing [edge.enrollment.edgeRouter.duration], invalid duration string %s, cannot parse as duration (e.g. 1m): %v", strValue, err)
|
|
}
|
|
}
|
|
|
|
if edgeRouterDuration < enrollmentDurationMin {
|
|
edgeRouterDuration = enrollmentDurationDefault
|
|
}
|
|
|
|
c.Enrollment.EdgeRouter = EnrollmentOption{Duration: edgeRouterDuration}
|
|
|
|
} else {
|
|
return errors.New("required configuration section [edge.enrollment.edgeRouter] missing")
|
|
}
|
|
|
|
} else {
|
|
return errors.New("required configuration section [edge.enrollment] missing")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func LoadFromMap(configMap map[interface{}]interface{}) (*Config, error) {
|
|
edgeConfig := NewConfig()
|
|
|
|
var edgeConfigMap map[interface{}]interface{}
|
|
|
|
if val, ok := configMap["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 = configMap != nil
|
|
|
|
if !edgeConfig.Enabled {
|
|
return edgeConfig, nil
|
|
}
|
|
|
|
var err error
|
|
|
|
if err = edgeConfig.loadApiSection(edgeConfigMap); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if err = edgeConfig.loadEnrollmentSection(edgeConfigMap); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return edgeConfig, nil
|
|
}
|
|
|
|
// CalculateCaPems takes the supplied caPems buffer as a set of PEM Certificates separated by new lines. Duplicate
|
|
// certificates are removed and the result is returned as a bytes.Buffer of PEM Certificates separated by new lines.
|
|
func CalculateCaPems(caPems *bytes.Buffer) *bytes.Buffer {
|
|
caPemMap := map[string][]byte{}
|
|
|
|
newCaPems := bytes.Buffer{}
|
|
blocksToProcess := caPems.Bytes()
|
|
for len(blocksToProcess) != 0 {
|
|
var block *pem.Block
|
|
block, blocksToProcess = pem.Decode(blocksToProcess)
|
|
|
|
if block != nil {
|
|
hash := sha1.Sum(block.Bytes)
|
|
fingerprint := toHex(hash[:])
|
|
newPem := pem.EncodeToMemory(block)
|
|
caPemMap[fingerprint] = newPem
|
|
_, _ = newCaPems.WriteString("\n")
|
|
_, _ = newCaPems.Write(newPem)
|
|
}
|
|
}
|
|
|
|
return &newCaPems
|
|
}
|
|
|
|
// toHex takes a byte array returns a hex formatted fingerprint
|
|
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())
|
|
}
|