mirror of
https://github.com/gogrlx/nats-server.git
synced 2026-04-02 03:38:42 -07:00
2030 lines
56 KiB
Go
2030 lines
56 KiB
Go
// Copyright 2012-2018 The NATS Authors
|
|
// 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
|
|
//
|
|
// http://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 server
|
|
|
|
import (
|
|
"crypto/tls"
|
|
"crypto/x509"
|
|
"errors"
|
|
"flag"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"net"
|
|
"net/url"
|
|
"os"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/nats-io/gnatsd/conf"
|
|
"github.com/nats-io/nkeys"
|
|
)
|
|
|
|
// ClusterOpts are options for clusters.
|
|
type ClusterOpts struct {
|
|
Host string `json:"addr,omitempty"`
|
|
Port int `json:"cluster_port,omitempty"`
|
|
Username string `json:"-"`
|
|
Password string `json:"-"`
|
|
AuthTimeout float64 `json:"auth_timeout,omitempty"`
|
|
Permissions *RoutePermissions `json:"-"`
|
|
TLSTimeout float64 `json:"-"`
|
|
TLSConfig *tls.Config `json:"-"`
|
|
ListenStr string `json:"-"`
|
|
Advertise string `json:"-"`
|
|
NoAdvertise bool `json:"-"`
|
|
ConnectRetries int `json:"-"`
|
|
}
|
|
|
|
// Options block for gnatsd server.
|
|
type Options struct {
|
|
ConfigFile string `json:"-"`
|
|
Host string `json:"addr"`
|
|
Port int `json:"port"`
|
|
ClientAdvertise string `json:"-"`
|
|
Trace bool `json:"-"`
|
|
Debug bool `json:"-"`
|
|
NoLog bool `json:"-"`
|
|
NoSigs bool `json:"-"`
|
|
Logtime bool `json:"-"`
|
|
MaxConn int `json:"max_connections"`
|
|
MaxSubs int `json:"max_subscriptions,omitempty"`
|
|
Nkeys []*NkeyUser `json:"-"`
|
|
Users []*User `json:"-"`
|
|
Accounts []*Account `json:"-"`
|
|
AllowNewAccounts bool `json:"-"`
|
|
Username string `json:"-"`
|
|
Password string `json:"-"`
|
|
Authorization string `json:"-"`
|
|
PingInterval time.Duration `json:"ping_interval"`
|
|
MaxPingsOut int `json:"ping_max"`
|
|
HTTPHost string `json:"http_host"`
|
|
HTTPPort int `json:"http_port"`
|
|
HTTPSPort int `json:"https_port"`
|
|
AuthTimeout float64 `json:"auth_timeout"`
|
|
MaxControlLine int `json:"max_control_line"`
|
|
MaxPayload int `json:"max_payload"`
|
|
MaxPending int64 `json:"max_pending"`
|
|
Cluster ClusterOpts `json:"cluster,omitempty"`
|
|
ProfPort int `json:"-"`
|
|
PidFile string `json:"-"`
|
|
PortsFileDir string `json:"-"`
|
|
LogFile string `json:"-"`
|
|
Syslog bool `json:"-"`
|
|
RemoteSyslog string `json:"-"`
|
|
Routes []*url.URL `json:"-"`
|
|
RoutesStr string `json:"-"`
|
|
TLSTimeout float64 `json:"tls_timeout"`
|
|
TLS bool `json:"-"`
|
|
TLSVerify bool `json:"-"`
|
|
TLSCert string `json:"-"`
|
|
TLSKey string `json:"-"`
|
|
TLSCaCert string `json:"-"`
|
|
TLSConfig *tls.Config `json:"-"`
|
|
WriteDeadline time.Duration `json:"-"`
|
|
RQSubsSweep time.Duration `json:"-"`
|
|
MaxClosedClients int `json:"-"`
|
|
|
|
CustomClientAuthentication Authentication `json:"-"`
|
|
CustomRouterAuthentication Authentication `json:"-"`
|
|
|
|
// CheckConfig enables pedantic configuration file syntax checks.
|
|
CheckConfig bool `json:"-"`
|
|
}
|
|
|
|
// Clone performs a deep copy of the Options struct, returning a new clone
|
|
// with all values copied.
|
|
func (o *Options) Clone() *Options {
|
|
if o == nil {
|
|
return nil
|
|
}
|
|
clone := &Options{}
|
|
*clone = *o
|
|
if o.Users != nil {
|
|
clone.Users = make([]*User, len(o.Users))
|
|
for i, user := range o.Users {
|
|
clone.Users[i] = user.clone()
|
|
}
|
|
}
|
|
if o.Nkeys != nil {
|
|
clone.Nkeys = make([]*NkeyUser, len(o.Nkeys))
|
|
for i, nkey := range o.Nkeys {
|
|
clone.Nkeys[i] = nkey.clone()
|
|
}
|
|
}
|
|
|
|
if o.Routes != nil {
|
|
clone.Routes = make([]*url.URL, len(o.Routes))
|
|
for i, route := range o.Routes {
|
|
routeCopy := &url.URL{}
|
|
*routeCopy = *route
|
|
clone.Routes[i] = routeCopy
|
|
}
|
|
}
|
|
if o.TLSConfig != nil {
|
|
clone.TLSConfig = o.TLSConfig.Clone()
|
|
}
|
|
if o.Cluster.TLSConfig != nil {
|
|
clone.Cluster.TLSConfig = o.Cluster.TLSConfig.Clone()
|
|
}
|
|
return clone
|
|
}
|
|
|
|
// Configuration file authorization section.
|
|
type authorization struct {
|
|
// Singles
|
|
user string
|
|
pass string
|
|
token string
|
|
// Multiple Nkeys/Users
|
|
nkeys []*NkeyUser
|
|
users []*User
|
|
timeout float64
|
|
defaultPermissions *Permissions
|
|
}
|
|
|
|
// TLSConfigOpts holds the parsed tls config information,
|
|
// used with flag parsing
|
|
type TLSConfigOpts struct {
|
|
CertFile string
|
|
KeyFile string
|
|
CaFile string
|
|
Verify bool
|
|
Timeout float64
|
|
Ciphers []uint16
|
|
CurvePreferences []tls.CurveID
|
|
}
|
|
|
|
var tlsUsage = `
|
|
TLS configuration is specified in the tls section of a configuration file:
|
|
|
|
e.g.
|
|
|
|
tls {
|
|
cert_file: "./certs/server-cert.pem"
|
|
key_file: "./certs/server-key.pem"
|
|
ca_file: "./certs/ca.pem"
|
|
verify: true
|
|
|
|
cipher_suites: [
|
|
"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
|
|
"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256"
|
|
]
|
|
curve_preferences: [
|
|
"CurveP256",
|
|
"CurveP384",
|
|
"CurveP521"
|
|
]
|
|
}
|
|
|
|
Available cipher suites include:
|
|
`
|
|
|
|
type token interface {
|
|
Value() interface{}
|
|
Line() int
|
|
IsUsedVariable() bool
|
|
SourceFile() string
|
|
}
|
|
|
|
type unknownConfigFieldErr struct {
|
|
field string
|
|
token token
|
|
configFile string
|
|
}
|
|
|
|
func (e *unknownConfigFieldErr) Error() string {
|
|
msg := fmt.Sprintf("unknown field %q", e.field)
|
|
if e.token != nil {
|
|
return msg + fmt.Sprintf(" in %s:%d", e.configFile, e.token.Line())
|
|
}
|
|
return msg
|
|
}
|
|
|
|
type configWarningErr struct {
|
|
field string
|
|
token token
|
|
configFile string
|
|
reason string
|
|
}
|
|
|
|
func (e *configWarningErr) Error() string {
|
|
msg := fmt.Sprintf("invalid use of field %q", e.field)
|
|
if e.token != nil {
|
|
msg += fmt.Sprintf(" in %s:%d", e.configFile, e.token.Line())
|
|
}
|
|
msg += ": " + e.reason
|
|
return msg
|
|
}
|
|
|
|
// ProcessConfigFile processes a configuration file.
|
|
// FIXME(dlc): A bit hacky
|
|
func ProcessConfigFile(configFile string) (*Options, error) {
|
|
opts := &Options{}
|
|
if err := opts.ProcessConfigFile(configFile); err != nil {
|
|
return nil, err
|
|
}
|
|
return opts, nil
|
|
}
|
|
|
|
// unwrapValue can be used to get the token and value from an item
|
|
// to be able to report the line number in case of an incorrect
|
|
// configuration.
|
|
func unwrapValue(v interface{}) (token, interface{}) {
|
|
switch tk := v.(type) {
|
|
case token:
|
|
return tk, tk.Value()
|
|
default:
|
|
return nil, v
|
|
}
|
|
}
|
|
|
|
// ProcessConfigFile updates the Options structure with options
|
|
// present in the given configuration file.
|
|
// This version is convenient if one wants to set some default
|
|
// options and then override them with what is in the config file.
|
|
// For instance, this version allows you to do something such as:
|
|
//
|
|
// opts := &Options{Debug: true}
|
|
// opts.ProcessConfigFile(myConfigFile)
|
|
//
|
|
// If the config file contains "debug: false", after this call,
|
|
// opts.Debug would really be false. It would be impossible to
|
|
// achieve that with the non receiver ProcessConfigFile() version,
|
|
// since one would not know after the call if "debug" was not present
|
|
// or was present but set to false.
|
|
func (o *Options) ProcessConfigFile(configFile string) error {
|
|
o.ConfigFile = configFile
|
|
if configFile == "" {
|
|
return nil
|
|
}
|
|
|
|
var (
|
|
m map[string]interface{}
|
|
tk token
|
|
pedantic bool = o.CheckConfig
|
|
err error
|
|
)
|
|
if pedantic {
|
|
m, err = conf.ParseFileWithChecks(configFile)
|
|
} else {
|
|
m, err = conf.ParseFile(configFile)
|
|
}
|
|
if err != nil {
|
|
return err
|
|
}
|
|
for k, v := range m {
|
|
// When pedantic checks are enabled then need to unwrap
|
|
// to get the value along with reported error line.
|
|
tk, v = unwrapValue(v)
|
|
switch strings.ToLower(k) {
|
|
case "listen":
|
|
hp, err := parseListen(v)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
o.Host = hp.host
|
|
o.Port = hp.port
|
|
case "client_advertise":
|
|
o.ClientAdvertise = v.(string)
|
|
case "port":
|
|
o.Port = int(v.(int64))
|
|
case "host", "net":
|
|
o.Host = v.(string)
|
|
case "debug":
|
|
o.Debug = v.(bool)
|
|
case "trace":
|
|
o.Trace = v.(bool)
|
|
case "logtime":
|
|
o.Logtime = v.(bool)
|
|
case "accounts":
|
|
err = parseAccounts(v, o)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
case "authorization":
|
|
var auth *authorization
|
|
if pedantic {
|
|
auth, err = parseAuthorization(tk, o)
|
|
} else {
|
|
auth, err = parseAuthorization(v, o)
|
|
}
|
|
if err != nil {
|
|
return err
|
|
}
|
|
o.Username = auth.user
|
|
o.Password = auth.pass
|
|
o.Authorization = auth.token
|
|
if (auth.user != "" || auth.pass != "") && auth.token != "" {
|
|
return fmt.Errorf("Cannot have a user/pass and token")
|
|
}
|
|
o.AuthTimeout = auth.timeout
|
|
// Check for multiple users defined
|
|
if auth.users != nil {
|
|
if auth.user != "" {
|
|
return fmt.Errorf("Can not have a single user/pass and a users array")
|
|
}
|
|
if auth.token != "" {
|
|
return fmt.Errorf("Can not have a token and a users array")
|
|
}
|
|
o.Users = auth.users
|
|
}
|
|
// Check for nkeys
|
|
if auth.nkeys != nil {
|
|
o.Nkeys = auth.nkeys
|
|
}
|
|
case "http":
|
|
hp, err := parseListen(v)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
o.HTTPHost = hp.host
|
|
o.HTTPPort = hp.port
|
|
case "https":
|
|
hp, err := parseListen(v)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
o.HTTPHost = hp.host
|
|
o.HTTPSPort = hp.port
|
|
case "http_port", "monitor_port":
|
|
o.HTTPPort = int(v.(int64))
|
|
case "https_port":
|
|
o.HTTPSPort = int(v.(int64))
|
|
case "cluster":
|
|
var err error
|
|
if pedantic {
|
|
err = parseCluster(tk, o)
|
|
} else {
|
|
err = parseCluster(v, o)
|
|
}
|
|
if err != nil {
|
|
return err
|
|
}
|
|
case "logfile", "log_file":
|
|
o.LogFile = v.(string)
|
|
case "syslog":
|
|
o.Syslog = v.(bool)
|
|
case "remote_syslog":
|
|
o.RemoteSyslog = v.(string)
|
|
case "pidfile", "pid_file":
|
|
o.PidFile = v.(string)
|
|
case "ports_file_dir":
|
|
o.PortsFileDir = v.(string)
|
|
case "prof_port":
|
|
o.ProfPort = int(v.(int64))
|
|
case "max_control_line":
|
|
o.MaxControlLine = int(v.(int64))
|
|
case "max_payload":
|
|
o.MaxPayload = int(v.(int64))
|
|
case "max_pending":
|
|
o.MaxPending = v.(int64)
|
|
case "max_connections", "max_conn":
|
|
o.MaxConn = int(v.(int64))
|
|
case "max_subscriptions", "max_subs":
|
|
o.MaxSubs = int(v.(int64))
|
|
case "ping_interval":
|
|
o.PingInterval = time.Duration(int(v.(int64))) * time.Second
|
|
case "ping_max":
|
|
o.MaxPingsOut = int(v.(int64))
|
|
case "tls":
|
|
var (
|
|
tc *TLSConfigOpts
|
|
err error
|
|
)
|
|
if pedantic {
|
|
tc, err = parseTLS(tk, o)
|
|
} else {
|
|
tc, err = parseTLS(v, o)
|
|
}
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if o.TLSConfig, err = GenTLSConfig(tc); err != nil {
|
|
return err
|
|
}
|
|
o.TLSTimeout = tc.Timeout
|
|
case "write_deadline":
|
|
wd, ok := v.(string)
|
|
if ok {
|
|
dur, err := time.ParseDuration(wd)
|
|
if err != nil {
|
|
return fmt.Errorf("error parsing write_deadline: %v", err)
|
|
}
|
|
o.WriteDeadline = dur
|
|
} else {
|
|
// Backward compatible with old type, assume this is the
|
|
// number of seconds.
|
|
o.WriteDeadline = time.Duration(v.(int64)) * time.Second
|
|
fmt.Printf("WARNING: write_deadline should be converted to a duration\n")
|
|
}
|
|
default:
|
|
if pedantic && tk != nil && !tk.IsUsedVariable() {
|
|
return &unknownConfigFieldErr{
|
|
field: k,
|
|
token: tk,
|
|
configFile: tk.SourceFile(),
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// hostPort is simple struct to hold parsed listen/addr strings.
|
|
type hostPort struct {
|
|
host string
|
|
port int
|
|
}
|
|
|
|
// parseListen will parse listen option which is replacing host/net and port
|
|
func parseListen(v interface{}) (*hostPort, error) {
|
|
hp := &hostPort{}
|
|
switch v.(type) {
|
|
// Only a port
|
|
case int64:
|
|
hp.port = int(v.(int64))
|
|
case string:
|
|
host, port, err := net.SplitHostPort(v.(string))
|
|
if err != nil {
|
|
return nil, fmt.Errorf("Could not parse address string %q", v)
|
|
}
|
|
hp.port, err = strconv.Atoi(port)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("Could not parse port %q", port)
|
|
}
|
|
hp.host = host
|
|
}
|
|
return hp, nil
|
|
}
|
|
|
|
// parseCluster will parse the cluster config.
|
|
func parseCluster(v interface{}, opts *Options) error {
|
|
var (
|
|
cm map[string]interface{}
|
|
tk token
|
|
pedantic bool = opts.CheckConfig
|
|
)
|
|
_, v = unwrapValue(v)
|
|
cm = v.(map[string]interface{})
|
|
for mk, mv := range cm {
|
|
// Again, unwrap token value if line check is required.
|
|
tk, mv = unwrapValue(mv)
|
|
switch strings.ToLower(mk) {
|
|
case "listen":
|
|
hp, err := parseListen(mv)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
opts.Cluster.Host = hp.host
|
|
opts.Cluster.Port = hp.port
|
|
case "port":
|
|
opts.Cluster.Port = int(mv.(int64))
|
|
case "host", "net":
|
|
opts.Cluster.Host = mv.(string)
|
|
case "authorization":
|
|
var (
|
|
auth *authorization
|
|
err error
|
|
)
|
|
if pedantic {
|
|
auth, err = parseAuthorization(tk, opts)
|
|
} else {
|
|
auth, err = parseAuthorization(mv, opts)
|
|
}
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if auth.users != nil {
|
|
return fmt.Errorf("Cluster authorization does not allow multiple users")
|
|
}
|
|
opts.Cluster.Username = auth.user
|
|
opts.Cluster.Password = auth.pass
|
|
opts.Cluster.AuthTimeout = auth.timeout
|
|
|
|
if auth.defaultPermissions != nil {
|
|
if pedantic {
|
|
return &configWarningErr{
|
|
field: mk,
|
|
token: tk,
|
|
reason: `setting "permissions" within cluster authorization block is deprecated`,
|
|
configFile: tk.SourceFile(),
|
|
}
|
|
}
|
|
|
|
// Do not set permissions if they were specified in top-level cluster block.
|
|
if opts.Cluster.Permissions == nil {
|
|
setClusterPermissions(&opts.Cluster, auth.defaultPermissions)
|
|
}
|
|
}
|
|
case "routes":
|
|
ra := mv.([]interface{})
|
|
opts.Routes = make([]*url.URL, 0, len(ra))
|
|
for _, r := range ra {
|
|
_, r = unwrapValue(r)
|
|
routeURL := r.(string)
|
|
url, err := url.Parse(routeURL)
|
|
if err != nil {
|
|
return fmt.Errorf("error parsing route url [%q]", routeURL)
|
|
}
|
|
opts.Routes = append(opts.Routes, url)
|
|
}
|
|
case "tls":
|
|
var (
|
|
tc *TLSConfigOpts
|
|
err error
|
|
)
|
|
if pedantic {
|
|
tc, err = parseTLS(tk, opts)
|
|
} else {
|
|
tc, err = parseTLS(mv, opts)
|
|
}
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if opts.Cluster.TLSConfig, err = GenTLSConfig(tc); err != nil {
|
|
return err
|
|
}
|
|
// For clusters, we will force strict verification. We also act
|
|
// as both client and server, so will mirror the rootCA to the
|
|
// clientCA pool.
|
|
opts.Cluster.TLSConfig.ClientAuth = tls.RequireAndVerifyClientCert
|
|
opts.Cluster.TLSConfig.RootCAs = opts.Cluster.TLSConfig.ClientCAs
|
|
opts.Cluster.TLSTimeout = tc.Timeout
|
|
case "cluster_advertise", "advertise":
|
|
opts.Cluster.Advertise = mv.(string)
|
|
case "no_advertise":
|
|
opts.Cluster.NoAdvertise = mv.(bool)
|
|
case "connect_retries":
|
|
opts.Cluster.ConnectRetries = int(mv.(int64))
|
|
case "permissions":
|
|
perms, err := parseUserPermissions(mv, opts)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
// This will possibly override permissions that were define in auth block
|
|
setClusterPermissions(&opts.Cluster, perms)
|
|
default:
|
|
if pedantic && tk != nil && !tk.IsUsedVariable() {
|
|
return &unknownConfigFieldErr{
|
|
field: mk,
|
|
token: tk,
|
|
configFile: tk.SourceFile(),
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Sets cluster's permissions based on given pub/sub permissions,
|
|
// doing the appropriate translation.
|
|
func setClusterPermissions(opts *ClusterOpts, perms *Permissions) {
|
|
// Import is whether or not we will send a SUB for interest to the other side.
|
|
// Export is whether or not we will accept a SUB from the remote for a given subject.
|
|
// Both only effect interest registration.
|
|
// The parsing sets Import into Publish and Export into Subscribe, convert
|
|
// accordingly.
|
|
opts.Permissions = &RoutePermissions{
|
|
Import: perms.Publish,
|
|
Export: perms.Subscribe,
|
|
}
|
|
}
|
|
|
|
// Temp structures to hold account import and export defintions since they need
|
|
// to be processed after being parsed.
|
|
type export struct {
|
|
acc *Account
|
|
sub string
|
|
accs []string
|
|
}
|
|
|
|
type importStream struct {
|
|
acc *Account
|
|
an string
|
|
sub string
|
|
pre string
|
|
}
|
|
|
|
type importService struct {
|
|
acc *Account
|
|
an string
|
|
sub string
|
|
to string
|
|
}
|
|
|
|
// parseAccounts will parse the different accounts syntax.
|
|
func parseAccounts(v interface{}, opts *Options) error {
|
|
var (
|
|
pedantic = opts.CheckConfig
|
|
importStreams []*importStream
|
|
importServices []*importService
|
|
exportStreams []*export
|
|
exportServices []*export
|
|
)
|
|
_, v = unwrapValue(v)
|
|
switch v.(type) {
|
|
// Simple array of account names.
|
|
case []interface{}, []string:
|
|
m := make(map[string]struct{}, len(v.([]interface{})))
|
|
for _, name := range v.([]interface{}) {
|
|
ns := name.(string)
|
|
if _, ok := m[ns]; ok {
|
|
return fmt.Errorf("Duplicate Account Entry: %s", ns)
|
|
}
|
|
opts.Accounts = append(opts.Accounts, &Account{Name: ns})
|
|
m[ns] = struct{}{}
|
|
}
|
|
// More common map entry
|
|
case map[string]interface{}:
|
|
// Track users across accounts, must be unique across
|
|
// accounts and nkeys vs users.
|
|
uorn := make(map[string]struct{})
|
|
for aname, mv := range v.(map[string]interface{}) {
|
|
_, amv := unwrapValue(mv)
|
|
// These should be maps.
|
|
mv, ok := amv.(map[string]interface{})
|
|
if !ok {
|
|
return fmt.Errorf("Expected map entries for accounts")
|
|
}
|
|
acc := &Account{Name: aname}
|
|
opts.Accounts = append(opts.Accounts, acc)
|
|
|
|
for k, v := range mv {
|
|
tk, mv := unwrapValue(v)
|
|
switch strings.ToLower(k) {
|
|
case "nkey":
|
|
nk, ok := mv.(string)
|
|
if !ok || !nkeys.IsValidPublicAccountKey(nk) {
|
|
return fmt.Errorf("Not a valid public nkey for an account: %q", v)
|
|
}
|
|
acc.Nkey = nk
|
|
case "imports":
|
|
streams, services, err := parseAccountImports(mv, acc, pedantic)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
importStreams = append(importStreams, streams...)
|
|
importServices = append(importServices, services...)
|
|
case "exports":
|
|
streams, services, err := parseAccountExports(mv, acc, pedantic)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
exportStreams = append(exportStreams, streams...)
|
|
exportServices = append(exportServices, services...)
|
|
case "users":
|
|
var (
|
|
users []*User
|
|
err error
|
|
nkeys []*NkeyUser
|
|
)
|
|
if pedantic {
|
|
nkeys, users, err = parseUsers(tk, opts)
|
|
} else {
|
|
nkeys, users, err = parseUsers(mv, opts)
|
|
}
|
|
if err != nil {
|
|
return err
|
|
}
|
|
for _, u := range users {
|
|
if _, ok := uorn[u.Username]; ok {
|
|
return fmt.Errorf("Duplicate user %q detected", u.Username)
|
|
}
|
|
uorn[u.Username] = struct{}{}
|
|
u.Account = acc
|
|
}
|
|
opts.Users = append(opts.Users, users...)
|
|
|
|
for _, u := range nkeys {
|
|
if _, ok := uorn[u.Nkey]; ok {
|
|
return fmt.Errorf("Duplicate nkey %q detected", u.Nkey)
|
|
}
|
|
uorn[u.Nkey] = struct{}{}
|
|
u.Account = acc
|
|
}
|
|
opts.Nkeys = append(opts.Nkeys, nkeys...)
|
|
default:
|
|
if pedantic && tk != nil && !tk.IsUsedVariable() {
|
|
return &unknownConfigFieldErr{
|
|
field: k,
|
|
token: tk,
|
|
configFile: tk.SourceFile(),
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Parse Imports and Exports here after all accounts defined.
|
|
// Do exports first since they need to be defined for imports to succeed
|
|
// since we do permissions checks.
|
|
|
|
// Create a lookup map for accounts lookups.
|
|
am := make(map[string]*Account, len(opts.Accounts))
|
|
for _, a := range opts.Accounts {
|
|
am[a.Name] = a
|
|
}
|
|
// Do stream exports
|
|
for _, stream := range exportStreams {
|
|
// Make array of accounts if applicable.
|
|
var accounts []*Account
|
|
for _, an := range stream.accs {
|
|
ta := am[an]
|
|
if ta == nil {
|
|
return fmt.Errorf("%q account not defined for stream export", an)
|
|
}
|
|
accounts = append(accounts, ta)
|
|
}
|
|
if err := stream.acc.addStreamExport(stream.sub, accounts); err != nil {
|
|
return fmt.Errorf("Error adding stream export %q: %v", stream.sub, err)
|
|
}
|
|
}
|
|
for _, service := range exportServices {
|
|
// Make array of accounts if applicable.
|
|
var accounts []*Account
|
|
for _, an := range service.accs {
|
|
ta := am[an]
|
|
if ta == nil {
|
|
return fmt.Errorf("%q account not defined for service export", an)
|
|
}
|
|
accounts = append(accounts, ta)
|
|
}
|
|
if err := service.acc.addServiceExport(service.sub, accounts); err != nil {
|
|
return fmt.Errorf("Error adding service export %q: %v", service.sub, err)
|
|
}
|
|
}
|
|
for _, stream := range importStreams {
|
|
ta := am[stream.an]
|
|
if ta == nil {
|
|
return fmt.Errorf("%q account not defined for stream import", stream.an)
|
|
}
|
|
if err := stream.acc.addStreamImport(ta, stream.sub, stream.pre); err != nil {
|
|
return fmt.Errorf("Error adding stream import %q: %v", stream.sub, err)
|
|
}
|
|
}
|
|
for _, service := range importServices {
|
|
ta := am[service.an]
|
|
if ta == nil {
|
|
return fmt.Errorf("%q account not defined for service import", service.an)
|
|
}
|
|
if service.to == "" {
|
|
service.to = service.sub
|
|
}
|
|
if err := service.acc.addServiceImport(ta, service.to, service.sub); err != nil {
|
|
return fmt.Errorf("Error adding service import %q: %v", service.sub, err)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Parse the account imports
|
|
func parseAccountExports(v interface{}, acc *Account, pedantic bool) ([]*export, []*export, error) {
|
|
// This should be an array of objects/maps.
|
|
_, v = unwrapValue(v)
|
|
ims, ok := v.([]interface{})
|
|
if !ok {
|
|
return nil, nil, fmt.Errorf("Exports should be an array, got %T", v)
|
|
}
|
|
|
|
var services []*export
|
|
var streams []*export
|
|
|
|
for _, v := range ims {
|
|
_, mv := unwrapValue(v)
|
|
io, ok := mv.(map[string]interface{})
|
|
if !ok {
|
|
return nil, nil, fmt.Errorf("Export Items should be a map with type entry, got %T", mv)
|
|
}
|
|
// Should have stream or service
|
|
stream, service, err := parseExportStreamOrService(io, pedantic)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
if service != nil {
|
|
service.acc = acc
|
|
services = append(services, service)
|
|
}
|
|
if stream != nil {
|
|
stream.acc = acc
|
|
streams = append(streams, stream)
|
|
}
|
|
}
|
|
return streams, services, nil
|
|
}
|
|
|
|
// Parse the account imports
|
|
func parseAccountImports(v interface{}, acc *Account, pedantic bool) ([]*importStream, []*importService, error) {
|
|
// This should be an array of objects/maps.
|
|
_, v = unwrapValue(v)
|
|
ims, ok := v.([]interface{})
|
|
if !ok {
|
|
return nil, nil, fmt.Errorf("Imports should be an array, got %T", v)
|
|
}
|
|
|
|
var services []*importService
|
|
var streams []*importStream
|
|
|
|
for _, v := range ims {
|
|
_, mv := unwrapValue(v)
|
|
io, ok := mv.(map[string]interface{})
|
|
if !ok {
|
|
return nil, nil, fmt.Errorf("Import Items should be a map with type entry, got %T", mv)
|
|
}
|
|
// Should have stream or service
|
|
stream, service, err := parseImportStreamOrService(io, pedantic)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
if service != nil {
|
|
service.acc = acc
|
|
services = append(services, service)
|
|
}
|
|
if stream != nil {
|
|
stream.acc = acc
|
|
streams = append(streams, stream)
|
|
}
|
|
}
|
|
return streams, services, nil
|
|
}
|
|
|
|
// Helper to parse an embedded account description for imported services or streams.
|
|
func parseAccount(v map[string]interface{}, pedantic bool) (string, string, error) {
|
|
var accountName, subject string
|
|
for mk, mv := range v {
|
|
tk, mv := unwrapValue(mv)
|
|
switch strings.ToLower(mk) {
|
|
case "account":
|
|
accountName = mv.(string)
|
|
case "subject":
|
|
subject = mv.(string)
|
|
default:
|
|
if pedantic && tk != nil && !tk.IsUsedVariable() {
|
|
return "", "", &unknownConfigFieldErr{
|
|
field: mk,
|
|
token: tk,
|
|
configFile: tk.SourceFile(),
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if accountName == "" || subject == "" {
|
|
return "", "", fmt.Errorf("Expect an account name and a subject")
|
|
}
|
|
return accountName, subject, nil
|
|
}
|
|
|
|
// Parse an import stream or service.
|
|
// e.g.
|
|
// {stream: "public.>"} # No accounts means public.
|
|
// {stream: "synadia.private.>", accounts: [cncf, natsio]}
|
|
// {service: "pub.request"} # No accounts means public.
|
|
// {service: "pub.special.request", accounts: [nats.io]}
|
|
func parseExportStreamOrService(v map[string]interface{}, pedantic bool) (*export, *export, error) {
|
|
var (
|
|
curStream *export
|
|
curService *export
|
|
accounts []string
|
|
)
|
|
|
|
for mk, mv := range v {
|
|
tk, mv := unwrapValue(mv)
|
|
switch strings.ToLower(mk) {
|
|
case "stream":
|
|
if curService != nil {
|
|
return nil, nil, fmt.Errorf("Detected stream but already saw a service: %+v", mv)
|
|
}
|
|
curStream = &export{sub: mv.(string)}
|
|
if accounts != nil {
|
|
curStream.accs = accounts
|
|
}
|
|
case "service":
|
|
if curStream != nil {
|
|
return nil, nil, fmt.Errorf("Detected service but already saw a stream: %+v", mv)
|
|
}
|
|
curService = &export{sub: mv.(string)}
|
|
if accounts != nil {
|
|
curService.accs = accounts
|
|
}
|
|
case "accounts":
|
|
for _, iv := range mv.([]interface{}) {
|
|
_, mv := unwrapValue(iv)
|
|
accounts = append(accounts, mv.(string))
|
|
}
|
|
if curStream != nil {
|
|
curStream.accs = accounts
|
|
} else if curService != nil {
|
|
curService.accs = accounts
|
|
}
|
|
default:
|
|
if pedantic && tk != nil && !tk.IsUsedVariable() {
|
|
return nil, nil, &unknownConfigFieldErr{
|
|
field: mk,
|
|
token: tk,
|
|
configFile: tk.SourceFile(),
|
|
}
|
|
}
|
|
return nil, nil, fmt.Errorf("Unknown field %q parsing export service or stream", mk)
|
|
}
|
|
|
|
}
|
|
return curStream, curService, nil
|
|
}
|
|
|
|
// Parse an import stream or service.
|
|
// e.g.
|
|
// {stream: {account: "synadia", subject:"public.synadia"}, prefix: "imports.synadia"}
|
|
// {stream: {account: "synadia", subject:"synadia.private.*"}}
|
|
// {service: {account: "synadia", subject: "pub.special.request"}, subject: "synadia.request"}
|
|
func parseImportStreamOrService(v map[string]interface{}, pedantic bool) (*importStream, *importService, error) {
|
|
var (
|
|
curStream *importStream
|
|
curService *importService
|
|
pre, to string
|
|
)
|
|
|
|
for mk, mv := range v {
|
|
tk, mv := unwrapValue(mv)
|
|
switch strings.ToLower(mk) {
|
|
case "stream":
|
|
if curService != nil {
|
|
return nil, nil, fmt.Errorf("Detected stream but already saw a service: %+v", mv)
|
|
}
|
|
ac, ok := mv.(map[string]interface{})
|
|
if !ok {
|
|
return nil, nil, fmt.Errorf("Stream entry should be an account map, got %T", mv)
|
|
}
|
|
// Make sure this is a map with account and subject
|
|
accountName, subject, err := parseAccount(ac, pedantic)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
curStream = &importStream{an: accountName, sub: subject}
|
|
if pre != "" {
|
|
curStream.pre = pre
|
|
}
|
|
case "service":
|
|
if curStream != nil {
|
|
return nil, nil, fmt.Errorf("Detected service but already saw a stream: %+v", mv)
|
|
}
|
|
ac, ok := mv.(map[string]interface{})
|
|
if !ok {
|
|
return nil, nil, fmt.Errorf("Service entry should be an account map, got %T", mv)
|
|
}
|
|
// Make sure this is a map with account and subject
|
|
accountName, subject, err := parseAccount(ac, pedantic)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
curService = &importService{an: accountName, sub: subject}
|
|
if to != "" {
|
|
curService.to = to
|
|
}
|
|
case "prefix":
|
|
pre = mv.(string)
|
|
if curStream != nil {
|
|
curStream.pre = pre
|
|
}
|
|
case "to":
|
|
to = mv.(string)
|
|
if curService != nil {
|
|
curService.to = to
|
|
}
|
|
default:
|
|
if pedantic && tk != nil && !tk.IsUsedVariable() {
|
|
return nil, nil, &unknownConfigFieldErr{
|
|
field: mk,
|
|
token: tk,
|
|
configFile: tk.SourceFile(),
|
|
}
|
|
}
|
|
return nil, nil, fmt.Errorf("Unknown field %q parsing import service or stream", mk)
|
|
}
|
|
|
|
}
|
|
return curStream, curService, nil
|
|
}
|
|
|
|
// Helper function to parse Authorization configs.
|
|
func parseAuthorization(v interface{}, opts *Options) (*authorization, error) {
|
|
var (
|
|
am map[string]interface{}
|
|
tk token
|
|
pedantic bool = opts.CheckConfig
|
|
auth *authorization = &authorization{}
|
|
)
|
|
|
|
// Unwrap value first if pedantic config check enabled.
|
|
_, v = unwrapValue(v)
|
|
am = v.(map[string]interface{})
|
|
for mk, mv := range am {
|
|
tk, mv = unwrapValue(mv)
|
|
switch strings.ToLower(mk) {
|
|
case "user", "username":
|
|
auth.user = mv.(string)
|
|
case "pass", "password":
|
|
auth.pass = mv.(string)
|
|
case "token":
|
|
auth.token = mv.(string)
|
|
case "timeout":
|
|
at := float64(1)
|
|
switch mv.(type) {
|
|
case int64:
|
|
at = float64(mv.(int64))
|
|
case float64:
|
|
at = mv.(float64)
|
|
}
|
|
auth.timeout = at
|
|
case "users":
|
|
var (
|
|
users []*User
|
|
err error
|
|
nkeys []*NkeyUser
|
|
)
|
|
if pedantic {
|
|
nkeys, users, err = parseUsers(tk, opts)
|
|
} else {
|
|
nkeys, users, err = parseUsers(mv, opts)
|
|
}
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
auth.users = users
|
|
auth.nkeys = nkeys
|
|
case "default_permission", "default_permissions", "permissions":
|
|
var (
|
|
permissions *Permissions
|
|
err error
|
|
)
|
|
if pedantic {
|
|
permissions, err = parseUserPermissions(tk, opts)
|
|
} else {
|
|
permissions, err = parseUserPermissions(mv, opts)
|
|
}
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
auth.defaultPermissions = permissions
|
|
default:
|
|
if pedantic && tk != nil && !tk.IsUsedVariable() {
|
|
return nil, &unknownConfigFieldErr{
|
|
field: mk,
|
|
token: tk,
|
|
configFile: tk.SourceFile(),
|
|
}
|
|
}
|
|
}
|
|
|
|
// Now check for permission defaults with multiple users, etc.
|
|
if auth.users != nil && auth.defaultPermissions != nil {
|
|
for _, user := range auth.users {
|
|
if user.Permissions == nil {
|
|
user.Permissions = auth.defaultPermissions
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
return auth, nil
|
|
}
|
|
|
|
// Helper function to parse multiple users array with optional permissions.
|
|
func parseUsers(mv interface{}, opts *Options) ([]*NkeyUser, []*User, error) {
|
|
var (
|
|
tk token
|
|
pedantic bool = opts.CheckConfig
|
|
users []*User = []*User{}
|
|
keys []*NkeyUser
|
|
)
|
|
_, mv = unwrapValue(mv)
|
|
|
|
// Make sure we have an array
|
|
uv, ok := mv.([]interface{})
|
|
if !ok {
|
|
return nil, nil, fmt.Errorf("Expected users field to be an array, got %v", mv)
|
|
}
|
|
for _, u := range uv {
|
|
_, u = unwrapValue(u)
|
|
|
|
// Check its a map/struct
|
|
um, ok := u.(map[string]interface{})
|
|
if !ok {
|
|
return nil, nil, fmt.Errorf("Expected user entry to be a map/struct, got %v", u)
|
|
}
|
|
|
|
var (
|
|
user *User = &User{}
|
|
nkey *NkeyUser = &NkeyUser{}
|
|
perms *Permissions
|
|
err error
|
|
)
|
|
for k, v := range um {
|
|
// Also needs to unwrap first
|
|
tk, v = unwrapValue(v)
|
|
|
|
switch strings.ToLower(k) {
|
|
case "nkey":
|
|
nkey.Nkey = v.(string)
|
|
case "user", "username":
|
|
user.Username = v.(string)
|
|
case "pass", "password":
|
|
user.Password = v.(string)
|
|
case "permission", "permissions", "authorization":
|
|
if pedantic {
|
|
perms, err = parseUserPermissions(tk, opts)
|
|
} else {
|
|
perms, err = parseUserPermissions(v, opts)
|
|
}
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
default:
|
|
if pedantic && tk != nil && !tk.IsUsedVariable() {
|
|
return nil, nil, &unknownConfigFieldErr{
|
|
field: k,
|
|
token: tk,
|
|
configFile: tk.SourceFile(),
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// Place perms if we have them.
|
|
if perms != nil {
|
|
// nkey takes precedent.
|
|
if nkey.Nkey != "" {
|
|
nkey.Permissions = perms
|
|
} else {
|
|
user.Permissions = perms
|
|
}
|
|
}
|
|
|
|
// Check to make sure we have at least username and password if defined.
|
|
if nkey.Nkey == "" && (user.Username == "" || user.Password == "") {
|
|
return nil, nil, fmt.Errorf("User entry requires a user and a password")
|
|
} else if nkey.Nkey != "" {
|
|
// Make sure the nkey a proper public nkey for a user..
|
|
if !nkeys.IsValidPublicUserKey(nkey.Nkey) {
|
|
return nil, nil, fmt.Errorf("Not a valid public nkey for a user")
|
|
}
|
|
// If we have user or password defined here that is an error.
|
|
if user.Username != "" || user.Password != "" {
|
|
return nil, nil, fmt.Errorf("Nkey users do not take usernames or passwords")
|
|
}
|
|
keys = append(keys, nkey)
|
|
} else {
|
|
users = append(users, user)
|
|
}
|
|
}
|
|
return keys, users, nil
|
|
}
|
|
|
|
// Helper function to parse user/account permissions
|
|
func parseUserPermissions(mv interface{}, opts *Options) (*Permissions, error) {
|
|
var (
|
|
tk token
|
|
pedantic bool = opts.CheckConfig
|
|
p *Permissions = &Permissions{}
|
|
)
|
|
_, mv = unwrapValue(mv)
|
|
pm, ok := mv.(map[string]interface{})
|
|
if !ok {
|
|
return nil, fmt.Errorf("Expected permissions to be a map/struct, got %+v", mv)
|
|
}
|
|
for k, v := range pm {
|
|
tk, v = unwrapValue(v)
|
|
|
|
switch strings.ToLower(k) {
|
|
// For routes:
|
|
// Import is Publish
|
|
// Export is Subscribe
|
|
case "pub", "publish", "import":
|
|
perms, err := parseVariablePermissions(v, opts)
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
p.Publish = perms
|
|
case "sub", "subscribe", "export":
|
|
perms, err := parseVariablePermissions(v, opts)
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
p.Subscribe = perms
|
|
default:
|
|
if pedantic && tk != nil && !tk.IsUsedVariable() {
|
|
return nil, &unknownConfigFieldErr{
|
|
field: k,
|
|
token: tk,
|
|
configFile: tk.SourceFile(),
|
|
}
|
|
}
|
|
return nil, fmt.Errorf("Unknown field %s parsing permissions", k)
|
|
}
|
|
}
|
|
return p, nil
|
|
}
|
|
|
|
// Top level parser for authorization configurations.
|
|
func parseVariablePermissions(v interface{}, opts *Options) (*SubjectPermission, error) {
|
|
switch vv := v.(type) {
|
|
case map[string]interface{}:
|
|
// New style with allow and/or deny properties.
|
|
return parseSubjectPermission(vv, opts)
|
|
default:
|
|
// Old style
|
|
return parseOldPermissionStyle(v)
|
|
}
|
|
}
|
|
|
|
// Helper function to parse subject singeltons and/or arrays
|
|
func parseSubjects(v interface{}) ([]string, error) {
|
|
var subjects []string
|
|
switch vv := v.(type) {
|
|
case string:
|
|
subjects = append(subjects, vv)
|
|
case []string:
|
|
subjects = vv
|
|
case []interface{}:
|
|
for _, i := range vv {
|
|
_, i = unwrapValue(i)
|
|
|
|
subject, ok := i.(string)
|
|
if !ok {
|
|
return nil, fmt.Errorf("Subject in permissions array cannot be cast to string")
|
|
}
|
|
subjects = append(subjects, subject)
|
|
}
|
|
default:
|
|
return nil, fmt.Errorf("Expected subject permissions to be a subject, or array of subjects, got %T", v)
|
|
}
|
|
if err := checkSubjectArray(subjects); err != nil {
|
|
return nil, err
|
|
}
|
|
return subjects, nil
|
|
}
|
|
|
|
// Helper function to parse old style authorization configs.
|
|
func parseOldPermissionStyle(v interface{}) (*SubjectPermission, error) {
|
|
subjects, err := parseSubjects(v)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &SubjectPermission{Allow: subjects}, nil
|
|
}
|
|
|
|
// Helper function to parse new style authorization into a SubjectPermission with Allow and Deny.
|
|
func parseSubjectPermission(v interface{}, opts *Options) (*SubjectPermission, error) {
|
|
m := v.(map[string]interface{})
|
|
if len(m) == 0 {
|
|
return nil, nil
|
|
}
|
|
p := &SubjectPermission{}
|
|
pedantic := opts.CheckConfig
|
|
for k, v := range m {
|
|
tk, v := unwrapValue(v)
|
|
switch strings.ToLower(k) {
|
|
case "allow":
|
|
subjects, err := parseSubjects(v)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
p.Allow = subjects
|
|
case "deny":
|
|
subjects, err := parseSubjects(v)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
p.Deny = subjects
|
|
default:
|
|
if pedantic && tk != nil && !tk.IsUsedVariable() {
|
|
return nil, &unknownConfigFieldErr{
|
|
field: k,
|
|
token: tk,
|
|
configFile: tk.SourceFile(),
|
|
}
|
|
}
|
|
return nil, fmt.Errorf("Unknown field name %q parsing subject permissions, only 'allow' or 'deny' are permitted", k)
|
|
}
|
|
}
|
|
return p, nil
|
|
}
|
|
|
|
// Helper function to validate subjects, etc for account permissioning.
|
|
func checkSubjectArray(sa []string) error {
|
|
for _, s := range sa {
|
|
if !IsValidSubject(s) {
|
|
return fmt.Errorf("Subject %q is not a valid subject", s)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// PrintTLSHelpAndDie prints TLS usage and exits.
|
|
func PrintTLSHelpAndDie() {
|
|
fmt.Printf("%s", tlsUsage)
|
|
for k := range cipherMap {
|
|
fmt.Printf(" %s\n", k)
|
|
}
|
|
fmt.Printf("\nAvailable curve preferences include:\n")
|
|
for k := range curvePreferenceMap {
|
|
fmt.Printf(" %s\n", k)
|
|
}
|
|
os.Exit(0)
|
|
}
|
|
|
|
func parseCipher(cipherName string) (uint16, error) {
|
|
cipher, exists := cipherMap[cipherName]
|
|
if !exists {
|
|
return 0, fmt.Errorf("Unrecognized cipher %s", cipherName)
|
|
}
|
|
|
|
return cipher, nil
|
|
}
|
|
|
|
func parseCurvePreferences(curveName string) (tls.CurveID, error) {
|
|
curve, exists := curvePreferenceMap[curveName]
|
|
if !exists {
|
|
return 0, fmt.Errorf("Unrecognized curve preference %s", curveName)
|
|
}
|
|
return curve, nil
|
|
}
|
|
|
|
// Helper function to parse TLS configs.
|
|
func parseTLS(v interface{}, opts *Options) (*TLSConfigOpts, error) {
|
|
var (
|
|
tlsm map[string]interface{}
|
|
tk token
|
|
tc TLSConfigOpts = TLSConfigOpts{}
|
|
pedantic bool = opts.CheckConfig
|
|
)
|
|
_, v = unwrapValue(v)
|
|
tlsm = v.(map[string]interface{})
|
|
for mk, mv := range tlsm {
|
|
tk, mv = unwrapValue(mv)
|
|
switch strings.ToLower(mk) {
|
|
case "cert_file":
|
|
certFile, ok := mv.(string)
|
|
if !ok {
|
|
return nil, fmt.Errorf("error parsing tls config, expected 'cert_file' to be filename")
|
|
}
|
|
tc.CertFile = certFile
|
|
case "key_file":
|
|
keyFile, ok := mv.(string)
|
|
if !ok {
|
|
return nil, fmt.Errorf("error parsing tls config, expected 'key_file' to be filename")
|
|
}
|
|
tc.KeyFile = keyFile
|
|
case "ca_file":
|
|
caFile, ok := mv.(string)
|
|
if !ok {
|
|
return nil, fmt.Errorf("error parsing tls config, expected 'ca_file' to be filename")
|
|
}
|
|
tc.CaFile = caFile
|
|
case "verify":
|
|
verify, ok := mv.(bool)
|
|
if !ok {
|
|
return nil, fmt.Errorf("error parsing tls config, expected 'verify' to be a boolean")
|
|
}
|
|
tc.Verify = verify
|
|
case "cipher_suites":
|
|
ra := mv.([]interface{})
|
|
if len(ra) == 0 {
|
|
return nil, fmt.Errorf("error parsing tls config, 'cipher_suites' cannot be empty")
|
|
}
|
|
tc.Ciphers = make([]uint16, 0, len(ra))
|
|
for _, r := range ra {
|
|
_, r = unwrapValue(r)
|
|
cipher, err := parseCipher(r.(string))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
tc.Ciphers = append(tc.Ciphers, cipher)
|
|
}
|
|
case "curve_preferences":
|
|
ra := mv.([]interface{})
|
|
if len(ra) == 0 {
|
|
return nil, fmt.Errorf("error parsing tls config, 'curve_preferences' cannot be empty")
|
|
}
|
|
tc.CurvePreferences = make([]tls.CurveID, 0, len(ra))
|
|
for _, r := range ra {
|
|
_, r = unwrapValue(r)
|
|
cps, err := parseCurvePreferences(r.(string))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
tc.CurvePreferences = append(tc.CurvePreferences, cps)
|
|
}
|
|
case "timeout":
|
|
at := float64(0)
|
|
switch mv.(type) {
|
|
case int64:
|
|
at = float64(mv.(int64))
|
|
case float64:
|
|
at = mv.(float64)
|
|
}
|
|
tc.Timeout = at
|
|
default:
|
|
if pedantic && tk != nil && !tk.IsUsedVariable() {
|
|
return nil, &unknownConfigFieldErr{
|
|
field: mk,
|
|
token: tk,
|
|
configFile: tk.SourceFile(),
|
|
}
|
|
}
|
|
|
|
return nil, fmt.Errorf("error parsing tls config, unknown field [%q]", mk)
|
|
}
|
|
}
|
|
|
|
// If cipher suites were not specified then use the defaults
|
|
if tc.Ciphers == nil {
|
|
tc.Ciphers = defaultCipherSuites()
|
|
}
|
|
|
|
// If curve preferences were not specified, then use the defaults
|
|
if tc.CurvePreferences == nil {
|
|
tc.CurvePreferences = defaultCurvePreferences()
|
|
}
|
|
|
|
return &tc, nil
|
|
}
|
|
|
|
// GenTLSConfig loads TLS related configuration parameters.
|
|
func GenTLSConfig(tc *TLSConfigOpts) (*tls.Config, error) {
|
|
|
|
// Now load in cert and private key
|
|
cert, err := tls.LoadX509KeyPair(tc.CertFile, tc.KeyFile)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error parsing X509 certificate/key pair: %v", err)
|
|
}
|
|
cert.Leaf, err = x509.ParseCertificate(cert.Certificate[0])
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error parsing certificate: %v", err)
|
|
}
|
|
|
|
// Create the tls.Config from our options.
|
|
// We will determine the cipher suites that we prefer.
|
|
// FIXME(dlc) change if ARM based.
|
|
config := tls.Config{
|
|
MinVersion: tls.VersionTLS12,
|
|
CipherSuites: tc.Ciphers,
|
|
PreferServerCipherSuites: true,
|
|
CurvePreferences: tc.CurvePreferences,
|
|
Certificates: []tls.Certificate{cert},
|
|
}
|
|
|
|
// Require client certificates as needed
|
|
if tc.Verify {
|
|
config.ClientAuth = tls.RequireAndVerifyClientCert
|
|
}
|
|
// Add in CAs if applicable.
|
|
if tc.CaFile != "" {
|
|
rootPEM, err := ioutil.ReadFile(tc.CaFile)
|
|
if err != nil || rootPEM == nil {
|
|
return nil, err
|
|
}
|
|
pool := x509.NewCertPool()
|
|
ok := pool.AppendCertsFromPEM(rootPEM)
|
|
if !ok {
|
|
return nil, fmt.Errorf("failed to parse root ca certificate")
|
|
}
|
|
config.ClientCAs = pool
|
|
}
|
|
|
|
return &config, nil
|
|
}
|
|
|
|
// MergeOptions will merge two options giving preference to the flagOpts
|
|
// if the item is present.
|
|
func MergeOptions(fileOpts, flagOpts *Options) *Options {
|
|
if fileOpts == nil {
|
|
return flagOpts
|
|
}
|
|
if flagOpts == nil {
|
|
return fileOpts
|
|
}
|
|
// Merge the two, flagOpts override
|
|
opts := *fileOpts
|
|
|
|
if flagOpts.Port != 0 {
|
|
opts.Port = flagOpts.Port
|
|
}
|
|
if flagOpts.Host != "" {
|
|
opts.Host = flagOpts.Host
|
|
}
|
|
if flagOpts.ClientAdvertise != "" {
|
|
opts.ClientAdvertise = flagOpts.ClientAdvertise
|
|
}
|
|
if flagOpts.Username != "" {
|
|
opts.Username = flagOpts.Username
|
|
}
|
|
if flagOpts.Password != "" {
|
|
opts.Password = flagOpts.Password
|
|
}
|
|
if flagOpts.Authorization != "" {
|
|
opts.Authorization = flagOpts.Authorization
|
|
}
|
|
if flagOpts.HTTPPort != 0 {
|
|
opts.HTTPPort = flagOpts.HTTPPort
|
|
}
|
|
if flagOpts.Debug {
|
|
opts.Debug = true
|
|
}
|
|
if flagOpts.Trace {
|
|
opts.Trace = true
|
|
}
|
|
if flagOpts.Logtime {
|
|
opts.Logtime = true
|
|
}
|
|
if flagOpts.LogFile != "" {
|
|
opts.LogFile = flagOpts.LogFile
|
|
}
|
|
if flagOpts.PidFile != "" {
|
|
opts.PidFile = flagOpts.PidFile
|
|
}
|
|
if flagOpts.PortsFileDir != "" {
|
|
opts.PortsFileDir = flagOpts.PortsFileDir
|
|
}
|
|
if flagOpts.ProfPort != 0 {
|
|
opts.ProfPort = flagOpts.ProfPort
|
|
}
|
|
if flagOpts.Cluster.ListenStr != "" {
|
|
opts.Cluster.ListenStr = flagOpts.Cluster.ListenStr
|
|
}
|
|
if flagOpts.Cluster.NoAdvertise {
|
|
opts.Cluster.NoAdvertise = true
|
|
}
|
|
if flagOpts.Cluster.ConnectRetries != 0 {
|
|
opts.Cluster.ConnectRetries = flagOpts.Cluster.ConnectRetries
|
|
}
|
|
if flagOpts.Cluster.Advertise != "" {
|
|
opts.Cluster.Advertise = flagOpts.Cluster.Advertise
|
|
}
|
|
if flagOpts.RoutesStr != "" {
|
|
mergeRoutes(&opts, flagOpts)
|
|
}
|
|
return &opts
|
|
}
|
|
|
|
// RoutesFromStr parses route URLs from a string
|
|
func RoutesFromStr(routesStr string) []*url.URL {
|
|
routes := strings.Split(routesStr, ",")
|
|
if len(routes) == 0 {
|
|
return nil
|
|
}
|
|
routeUrls := []*url.URL{}
|
|
for _, r := range routes {
|
|
r = strings.TrimSpace(r)
|
|
u, _ := url.Parse(r)
|
|
routeUrls = append(routeUrls, u)
|
|
}
|
|
return routeUrls
|
|
}
|
|
|
|
// This will merge the flag routes and override anything that was present.
|
|
func mergeRoutes(opts, flagOpts *Options) {
|
|
routeUrls := RoutesFromStr(flagOpts.RoutesStr)
|
|
if routeUrls == nil {
|
|
return
|
|
}
|
|
opts.Routes = routeUrls
|
|
opts.RoutesStr = flagOpts.RoutesStr
|
|
}
|
|
|
|
// RemoveSelfReference removes this server from an array of routes
|
|
func RemoveSelfReference(clusterPort int, routes []*url.URL) ([]*url.URL, error) {
|
|
var cleanRoutes []*url.URL
|
|
cport := strconv.Itoa(clusterPort)
|
|
|
|
selfIPs, err := getInterfaceIPs()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
for _, r := range routes {
|
|
host, port, err := net.SplitHostPort(r.Host)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
ipList, err := getURLIP(host)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if cport == port && isIPInList(selfIPs, ipList) {
|
|
continue
|
|
}
|
|
cleanRoutes = append(cleanRoutes, r)
|
|
}
|
|
|
|
return cleanRoutes, nil
|
|
}
|
|
|
|
func isIPInList(list1 []net.IP, list2 []net.IP) bool {
|
|
for _, ip1 := range list1 {
|
|
for _, ip2 := range list2 {
|
|
if ip1.Equal(ip2) {
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func getURLIP(ipStr string) ([]net.IP, error) {
|
|
ipList := []net.IP{}
|
|
|
|
ip := net.ParseIP(ipStr)
|
|
if ip != nil {
|
|
ipList = append(ipList, ip)
|
|
return ipList, nil
|
|
}
|
|
|
|
hostAddr, err := net.LookupHost(ipStr)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("Error looking up host with route hostname: %v", err)
|
|
}
|
|
for _, addr := range hostAddr {
|
|
ip = net.ParseIP(addr)
|
|
if ip != nil {
|
|
ipList = append(ipList, ip)
|
|
}
|
|
}
|
|
return ipList, nil
|
|
}
|
|
|
|
func getInterfaceIPs() ([]net.IP, error) {
|
|
var localIPs []net.IP
|
|
|
|
interfaceAddr, err := net.InterfaceAddrs()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("Error getting self referencing address: %v", err)
|
|
}
|
|
|
|
for i := 0; i < len(interfaceAddr); i++ {
|
|
interfaceIP, _, _ := net.ParseCIDR(interfaceAddr[i].String())
|
|
if net.ParseIP(interfaceIP.String()) != nil {
|
|
localIPs = append(localIPs, interfaceIP)
|
|
} else {
|
|
return nil, fmt.Errorf("Error parsing self referencing address: %v", err)
|
|
}
|
|
}
|
|
return localIPs, nil
|
|
}
|
|
|
|
func processOptions(opts *Options) {
|
|
// Setup non-standard Go defaults
|
|
if opts.Host == "" {
|
|
opts.Host = DEFAULT_HOST
|
|
}
|
|
if opts.HTTPHost == "" {
|
|
// Default to same bind from server if left undefined
|
|
opts.HTTPHost = opts.Host
|
|
}
|
|
if opts.Port == 0 {
|
|
opts.Port = DEFAULT_PORT
|
|
} else if opts.Port == RANDOM_PORT {
|
|
// Choose randomly inside of net.Listen
|
|
opts.Port = 0
|
|
}
|
|
if opts.MaxConn == 0 {
|
|
opts.MaxConn = DEFAULT_MAX_CONNECTIONS
|
|
}
|
|
if opts.PingInterval == 0 {
|
|
opts.PingInterval = DEFAULT_PING_INTERVAL
|
|
}
|
|
if opts.MaxPingsOut == 0 {
|
|
opts.MaxPingsOut = DEFAULT_PING_MAX_OUT
|
|
}
|
|
if opts.TLSTimeout == 0 {
|
|
opts.TLSTimeout = float64(TLS_TIMEOUT) / float64(time.Second)
|
|
}
|
|
if opts.AuthTimeout == 0 {
|
|
opts.AuthTimeout = float64(AUTH_TIMEOUT) / float64(time.Second)
|
|
}
|
|
if opts.Cluster.Port != 0 {
|
|
if opts.Cluster.Host == "" {
|
|
opts.Cluster.Host = DEFAULT_HOST
|
|
}
|
|
if opts.Cluster.TLSTimeout == 0 {
|
|
opts.Cluster.TLSTimeout = float64(TLS_TIMEOUT) / float64(time.Second)
|
|
}
|
|
if opts.Cluster.AuthTimeout == 0 {
|
|
opts.Cluster.AuthTimeout = float64(AUTH_TIMEOUT) / float64(time.Second)
|
|
}
|
|
}
|
|
if opts.MaxControlLine == 0 {
|
|
opts.MaxControlLine = MAX_CONTROL_LINE_SIZE
|
|
}
|
|
if opts.MaxPayload == 0 {
|
|
opts.MaxPayload = MAX_PAYLOAD_SIZE
|
|
}
|
|
if opts.MaxPending == 0 {
|
|
opts.MaxPending = MAX_PENDING_SIZE
|
|
}
|
|
if opts.WriteDeadline == time.Duration(0) {
|
|
opts.WriteDeadline = DEFAULT_FLUSH_DEADLINE
|
|
}
|
|
if opts.RQSubsSweep == time.Duration(0) {
|
|
opts.RQSubsSweep = DEFAULT_REMOTE_QSUBS_SWEEPER
|
|
}
|
|
if opts.MaxClosedClients == 0 {
|
|
opts.MaxClosedClients = DEFAULT_MAX_CLOSED_CLIENTS
|
|
}
|
|
}
|
|
|
|
// ConfigureOptions accepts a flag set and augment it with NATS Server
|
|
// specific flags. On success, an options structure is returned configured
|
|
// based on the selected flags and/or configuration file.
|
|
// The command line options take precedence to the ones in the configuration file.
|
|
func ConfigureOptions(fs *flag.FlagSet, args []string, printVersion, printHelp, printTLSHelp func()) (*Options, error) {
|
|
opts := &Options{}
|
|
var (
|
|
showVersion bool
|
|
showHelp bool
|
|
showTLSHelp bool
|
|
signal string
|
|
configFile string
|
|
err error
|
|
)
|
|
|
|
fs.BoolVar(&showHelp, "h", false, "Show this message.")
|
|
fs.BoolVar(&showHelp, "help", false, "Show this message.")
|
|
fs.IntVar(&opts.Port, "port", 0, "Port to listen on.")
|
|
fs.IntVar(&opts.Port, "p", 0, "Port to listen on.")
|
|
fs.StringVar(&opts.Host, "addr", "", "Network host to listen on.")
|
|
fs.StringVar(&opts.Host, "a", "", "Network host to listen on.")
|
|
fs.StringVar(&opts.Host, "net", "", "Network host to listen on.")
|
|
fs.StringVar(&opts.ClientAdvertise, "client_advertise", "", "Client URL to advertise to other servers.")
|
|
fs.BoolVar(&opts.Debug, "D", false, "Enable Debug logging.")
|
|
fs.BoolVar(&opts.Debug, "debug", false, "Enable Debug logging.")
|
|
fs.BoolVar(&opts.Trace, "V", false, "Enable Trace logging.")
|
|
fs.BoolVar(&opts.Trace, "trace", false, "Enable Trace logging.")
|
|
fs.Bool("DV", false, "Enable Debug and Trace logging.")
|
|
fs.BoolVar(&opts.Logtime, "T", true, "Timestamp log entries.")
|
|
fs.BoolVar(&opts.Logtime, "logtime", true, "Timestamp log entries.")
|
|
fs.StringVar(&opts.Username, "user", "", "Username required for connection.")
|
|
fs.StringVar(&opts.Password, "pass", "", "Password required for connection.")
|
|
fs.StringVar(&opts.Authorization, "auth", "", "Authorization token required for connection.")
|
|
fs.IntVar(&opts.HTTPPort, "m", 0, "HTTP Port for /varz, /connz endpoints.")
|
|
fs.IntVar(&opts.HTTPPort, "http_port", 0, "HTTP Port for /varz, /connz endpoints.")
|
|
fs.IntVar(&opts.HTTPSPort, "ms", 0, "HTTPS Port for /varz, /connz endpoints.")
|
|
fs.IntVar(&opts.HTTPSPort, "https_port", 0, "HTTPS Port for /varz, /connz endpoints.")
|
|
fs.StringVar(&configFile, "c", "", "Configuration file.")
|
|
fs.StringVar(&configFile, "config", "", "Configuration file.")
|
|
fs.BoolVar(&opts.CheckConfig, "t", false, "Check configuration and exit.")
|
|
fs.StringVar(&signal, "sl", "", "Send signal to gnatsd process (stop, quit, reopen, reload)")
|
|
fs.StringVar(&signal, "signal", "", "Send signal to gnatsd process (stop, quit, reopen, reload)")
|
|
fs.StringVar(&opts.PidFile, "P", "", "File to store process pid.")
|
|
fs.StringVar(&opts.PidFile, "pid", "", "File to store process pid.")
|
|
fs.StringVar(&opts.PortsFileDir, "ports_file_dir", "", "Creates a ports file in the specified directory (<executable_name>_<pid>.ports)")
|
|
fs.StringVar(&opts.LogFile, "l", "", "File to store logging output.")
|
|
fs.StringVar(&opts.LogFile, "log", "", "File to store logging output.")
|
|
fs.BoolVar(&opts.Syslog, "s", false, "Enable syslog as log method.")
|
|
fs.BoolVar(&opts.Syslog, "syslog", false, "Enable syslog as log method..")
|
|
fs.StringVar(&opts.RemoteSyslog, "r", "", "Syslog server addr (udp://127.0.0.1:514).")
|
|
fs.StringVar(&opts.RemoteSyslog, "remote_syslog", "", "Syslog server addr (udp://127.0.0.1:514).")
|
|
fs.BoolVar(&showVersion, "version", false, "Print version information.")
|
|
fs.BoolVar(&showVersion, "v", false, "Print version information.")
|
|
fs.IntVar(&opts.ProfPort, "profile", 0, "Profiling HTTP port")
|
|
fs.StringVar(&opts.RoutesStr, "routes", "", "Routes to actively solicit a connection.")
|
|
fs.StringVar(&opts.Cluster.ListenStr, "cluster", "", "Cluster url from which members can solicit routes.")
|
|
fs.StringVar(&opts.Cluster.ListenStr, "cluster_listen", "", "Cluster url from which members can solicit routes.")
|
|
fs.StringVar(&opts.Cluster.Advertise, "cluster_advertise", "", "Cluster URL to advertise to other servers.")
|
|
fs.BoolVar(&opts.Cluster.NoAdvertise, "no_advertise", false, "Advertise known cluster IPs to clients.")
|
|
fs.IntVar(&opts.Cluster.ConnectRetries, "connect_retries", 0, "For implicit routes, number of connect retries")
|
|
fs.BoolVar(&showTLSHelp, "help_tls", false, "TLS help.")
|
|
fs.BoolVar(&opts.TLS, "tls", false, "Enable TLS.")
|
|
fs.BoolVar(&opts.TLSVerify, "tlsverify", false, "Enable TLS with client verification.")
|
|
fs.StringVar(&opts.TLSCert, "tlscert", "", "Server certificate file.")
|
|
fs.StringVar(&opts.TLSKey, "tlskey", "", "Private key for server certificate.")
|
|
fs.StringVar(&opts.TLSCaCert, "tlscacert", "", "Client certificate CA for verification.")
|
|
|
|
// The flags definition above set "default" values to some of the options.
|
|
// Calling Parse() here will override the default options with any value
|
|
// specified from the command line. This is ok. We will then update the
|
|
// options with the content of the configuration file (if present), and then,
|
|
// call Parse() again to override the default+config with command line values.
|
|
// Calling Parse() before processing config file is necessary since configFile
|
|
// itself is a command line argument, and also Parse() is required in order
|
|
// to know if user wants simply to show "help" or "version", etc...
|
|
if err := fs.Parse(args); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if showVersion {
|
|
printVersion()
|
|
return nil, nil
|
|
}
|
|
|
|
if showHelp {
|
|
printHelp()
|
|
return nil, nil
|
|
}
|
|
|
|
if showTLSHelp {
|
|
printTLSHelp()
|
|
return nil, nil
|
|
}
|
|
|
|
// Process args looking for non-flag options,
|
|
// 'version' and 'help' only for now
|
|
showVersion, showHelp, err = ProcessCommandLineArgs(fs)
|
|
if err != nil {
|
|
return nil, err
|
|
} else if showVersion {
|
|
printVersion()
|
|
return nil, nil
|
|
} else if showHelp {
|
|
printHelp()
|
|
return nil, nil
|
|
}
|
|
|
|
// Snapshot flag options.
|
|
FlagSnapshot = opts.Clone()
|
|
|
|
// Process signal control.
|
|
if signal != "" {
|
|
if err := processSignal(signal); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
// Parse config if given
|
|
if configFile != "" {
|
|
// This will update the options with values from the config file.
|
|
if err := opts.ProcessConfigFile(configFile); err != nil {
|
|
return nil, err
|
|
} else if opts.CheckConfig {
|
|
// Report configuration file syntax test was successful and exit.
|
|
return opts, nil
|
|
}
|
|
|
|
// Call this again to override config file options with options from command line.
|
|
// Note: We don't need to check error here since if there was an error, it would
|
|
// have been caught the first time this function was called (after setting up the
|
|
// flags).
|
|
fs.Parse(args)
|
|
} else if opts.CheckConfig {
|
|
return nil, fmt.Errorf("must specify [-c, --config] option to check configuration file syntax")
|
|
}
|
|
|
|
// Special handling of some flags
|
|
var (
|
|
flagErr error
|
|
tlsDisabled bool
|
|
tlsOverride bool
|
|
)
|
|
fs.Visit(func(f *flag.Flag) {
|
|
// short-circuit if an error was encountered
|
|
if flagErr != nil {
|
|
return
|
|
}
|
|
if strings.HasPrefix(f.Name, "tls") {
|
|
if f.Name == "tls" {
|
|
if !opts.TLS {
|
|
// User has specified "-tls=false", we need to disable TLS
|
|
opts.TLSConfig = nil
|
|
tlsDisabled = true
|
|
tlsOverride = false
|
|
return
|
|
}
|
|
tlsOverride = true
|
|
} else if !tlsDisabled {
|
|
tlsOverride = true
|
|
}
|
|
} else {
|
|
switch f.Name {
|
|
case "DV":
|
|
// Check value to support -DV=false
|
|
boolValue, _ := strconv.ParseBool(f.Value.String())
|
|
opts.Trace, opts.Debug = boolValue, boolValue
|
|
case "cluster", "cluster_listen":
|
|
// Override cluster config if explicitly set via flags.
|
|
flagErr = overrideCluster(opts)
|
|
case "routes":
|
|
// Keep in mind that the flag has updated opts.RoutesStr at this point.
|
|
if opts.RoutesStr == "" {
|
|
// Set routes array to nil since routes string is empty
|
|
opts.Routes = nil
|
|
return
|
|
}
|
|
routeUrls := RoutesFromStr(opts.RoutesStr)
|
|
opts.Routes = routeUrls
|
|
}
|
|
}
|
|
})
|
|
if flagErr != nil {
|
|
return nil, flagErr
|
|
}
|
|
|
|
// This will be true if some of the `-tls` params have been set and
|
|
// `-tls=false` has not been set.
|
|
if tlsOverride {
|
|
if err := overrideTLS(opts); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
// If we don't have cluster defined in the configuration
|
|
// file and no cluster listen string override, but we do
|
|
// have a routes override, we need to report misconfiguration.
|
|
if opts.RoutesStr != "" && opts.Cluster.ListenStr == "" && opts.Cluster.Host == "" && opts.Cluster.Port == 0 {
|
|
return nil, errors.New("solicited routes require cluster capabilities, e.g. --cluster")
|
|
}
|
|
|
|
return opts, nil
|
|
}
|
|
|
|
// overrideTLS is called when at least "-tls=true" has been set.
|
|
func overrideTLS(opts *Options) error {
|
|
if opts.TLSCert == "" {
|
|
return errors.New("TLS Server certificate must be present and valid")
|
|
}
|
|
if opts.TLSKey == "" {
|
|
return errors.New("TLS Server private key must be present and valid")
|
|
}
|
|
|
|
tc := TLSConfigOpts{}
|
|
tc.CertFile = opts.TLSCert
|
|
tc.KeyFile = opts.TLSKey
|
|
tc.CaFile = opts.TLSCaCert
|
|
tc.Verify = opts.TLSVerify
|
|
|
|
var err error
|
|
opts.TLSConfig, err = GenTLSConfig(&tc)
|
|
return err
|
|
}
|
|
|
|
// overrideCluster updates Options.Cluster if that flag "cluster" (or "cluster_listen")
|
|
// has explicitly be set in the command line. If it is set to empty string, it will
|
|
// clear the Cluster options.
|
|
func overrideCluster(opts *Options) error {
|
|
if opts.Cluster.ListenStr == "" {
|
|
// This one is enough to disable clustering.
|
|
opts.Cluster.Port = 0
|
|
return nil
|
|
}
|
|
clusterURL, err := url.Parse(opts.Cluster.ListenStr)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
h, p, err := net.SplitHostPort(clusterURL.Host)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
opts.Cluster.Host = h
|
|
_, err = fmt.Sscan(p, &opts.Cluster.Port)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if clusterURL.User != nil {
|
|
pass, hasPassword := clusterURL.User.Password()
|
|
if !hasPassword {
|
|
return errors.New("expected cluster password to be set")
|
|
}
|
|
opts.Cluster.Password = pass
|
|
|
|
user := clusterURL.User.Username()
|
|
opts.Cluster.Username = user
|
|
} else {
|
|
// Since we override from flag and there is no user/pwd, make
|
|
// sure we clear what we may have gotten from config file.
|
|
opts.Cluster.Username = ""
|
|
opts.Cluster.Password = ""
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func processSignal(signal string) error {
|
|
var (
|
|
pid string
|
|
commandAndPid = strings.Split(signal, "=")
|
|
)
|
|
if l := len(commandAndPid); l == 2 {
|
|
pid = commandAndPid[1]
|
|
} else if l > 2 {
|
|
return fmt.Errorf("invalid signal parameters: %v", commandAndPid[2:])
|
|
}
|
|
if err := ProcessSignal(Command(commandAndPid[0]), pid); err != nil {
|
|
return err
|
|
}
|
|
os.Exit(0)
|
|
return nil
|
|
}
|