Files
teleport/lib/config/configuration.go
T
Alan Parra bcf99aaedc Drop local_auth/second_factor warning (#22851)
Drop warning about second_factor configured in clusters with local_auth:false.
This is a valid configuration for clusters with session MFA, regardless if their
authn method.
2023-03-09 21:16:38 +00:00

2270 lines
69 KiB
Go

/*
Copyright 2015 Gravitational, 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
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 config provides facilities for configuring Teleport daemons
// including
// - parsing YAML configuration
// - parsing CLI flags
package config
import (
"crypto/x509"
"io"
stdlog "log"
"net"
"os"
"path/filepath"
"regexp"
"runtime"
"strings"
"time"
"unicode"
"github.com/go-ldap/ldap/v3"
"github.com/gravitational/trace"
log "github.com/sirupsen/logrus"
"golang.org/x/exp/slices"
kyaml "k8s.io/apimachinery/pkg/util/yaml"
"github.com/gravitational/teleport"
"github.com/gravitational/teleport/api/client/webclient"
"github.com/gravitational/teleport/api/constants"
"github.com/gravitational/teleport/api/types"
"github.com/gravitational/teleport/lib"
"github.com/gravitational/teleport/lib/backend"
"github.com/gravitational/teleport/lib/backend/lite"
"github.com/gravitational/teleport/lib/backend/memory"
"github.com/gravitational/teleport/lib/client"
"github.com/gravitational/teleport/lib/defaults"
dtconfig "github.com/gravitational/teleport/lib/devicetrust/config"
"github.com/gravitational/teleport/lib/limiter"
"github.com/gravitational/teleport/lib/pam"
"github.com/gravitational/teleport/lib/service/servicecfg"
"github.com/gravitational/teleport/lib/services"
"github.com/gravitational/teleport/lib/tlsca"
"github.com/gravitational/teleport/lib/utils"
)
// CommandLineFlags stores command line flag values, it's a much simplified subset
// of Teleport configuration (which is fully expressed via YAML config file)
type CommandLineFlags struct {
// --name flag
NodeName string
// --auth-server flag
AuthServerAddr []string
// --token flag
AuthToken string
// --join-method flag
JoinMethod string
// CAPins are the SKPI hashes of the CAs used to verify the Auth Server.
CAPins []string
// --listen-ip flag
ListenIP net.IP
// --advertise-ip flag
AdvertiseIP string
// --config flag
ConfigFile string
// --apply-on-startup contains the path of a YAML manifest whose resources should be
// applied on startup. Unlike the bootstrap flag, the resources are always applied,
// even if the cluster is already initialized. Existing resources will be updated.
ApplyOnStartupFile string
// Bootstrap flag contains a YAML file that defines a set of resources to bootstrap
// a cluster.
BootstrapFile string
// ConfigString is a base64 encoded configuration string
// set by --config-string or TELEPORT_CONFIG environment variable
ConfigString string
// --roles flag
Roles string
// -d flag
Debug bool
// --insecure-no-tls flag
DisableTLS bool
// --labels flag
Labels string
// --pid-file flag
PIDFile string
// DiagnosticAddr is listen address for diagnostic endpoint
DiagnosticAddr string
// PermitUserEnvironment enables reading of ~/.tsh/environment
// when creating a new session.
PermitUserEnvironment bool
// Insecure mode is controlled by --insecure flag and in this mode
// Teleport won't check certificates when connecting to trusted clusters
// It's useful for learning Teleport (following quick starts, etc).
InsecureMode bool
// FIPS mode means Teleport starts in a FedRAMP/FIPS 140-2 compliant
// configuration.
FIPS bool
// SkipVersionCheck allows Teleport to connect to auth servers that
// have an earlier major version number.
SkipVersionCheck bool
// AppName is the name of the application to proxy.
AppName string
// AppURI is the internal address of the application to proxy.
AppURI string
// AppCloud is set if application is proxying Cloud API
AppCloud string
// AppPublicAddr is the public address of the application to proxy.
AppPublicAddr string
// DatabaseName is the name of the database to proxy.
DatabaseName string
// DatabaseDescription is a free-form database description.
DatabaseDescription string
// DatabaseProtocol is the type of the proxied database e.g. postgres or mysql.
DatabaseProtocol string
// DatabaseURI is the address to connect to the proxied database.
DatabaseURI string
// DatabaseCACertFile is the database CA cert path.
DatabaseCACertFile string
// DatabaseAWSRegion is an optional database cloud region e.g. when using AWS RDS.
DatabaseAWSRegion string
// DatabaseAWSAccountID is an optional AWS account ID e.g. when using Keyspaces.
DatabaseAWSAccountID string
// DatabaseAWSExternalID is an optional AWS external ID used to enable assuming an AWS role across accounts.
DatabaseAWSExternalID string
// DatabaseAWSRedshiftClusterID is Redshift cluster identifier.
DatabaseAWSRedshiftClusterID string
// DatabaseAWSRDSInstanceID is RDS instance identifier.
DatabaseAWSRDSInstanceID string
// DatabaseAWSRDSClusterID is RDS cluster (Aurora) cluster identifier.
DatabaseAWSRDSClusterID string
// DatabaseAWSElastiCacheGroupID is the ElastiCache replication group identifier.
DatabaseAWSElastiCacheGroupID string
// DatabaseAWSMemoryDBClusterName is the MemoryDB cluster name.
DatabaseAWSMemoryDBClusterName string
// DatabaseGCPProjectID is GCP Cloud SQL project identifier.
DatabaseGCPProjectID string
// DatabaseGCPInstanceID is GCP Cloud SQL instance identifier.
DatabaseGCPInstanceID string
// DatabaseADKeytabFile is the path to Kerberos keytab file.
DatabaseADKeytabFile string
// DatabaseADKrb5File is the path to krb5.conf file.
DatabaseADKrb5File string
// DatabaseADDomain is the Active Directory domain for authentication.
DatabaseADDomain string
// DatabaseADSPN is the database Service Principal Name.
DatabaseADSPN string
// DatabaseMySQLServerVersion is the MySQL server version reported to a client
// if the value cannot be obtained from the database.
DatabaseMySQLServerVersion string
// ProxyServer is the url of the proxy server to connect to
ProxyServer string
// OpenSSHConfigPath is the path of the file to write agentless configuration to
OpenSSHConfigPath string
// OpenSSHKeysPath is the path to write teleport keys and certs into
OpenSSHKeysPath string
// AdditionalPrincipals are a list of extra principals to include when generating host keys.
AdditionalPrincipals string
// RestartOpenSSH indicates whether openssh should be restarted or not.
RestartOpenSSH bool
}
// ReadConfigFile reads /etc/teleport.yaml (or whatever is passed via --config flag)
// and overrides values in 'cfg' structure
func ReadConfigFile(cliConfigPath string) (*FileConfig, error) {
configFilePath := defaults.ConfigFilePath
// --config tells us to use a specific conf. file:
if cliConfigPath != "" {
configFilePath = cliConfigPath
if !utils.FileExists(configFilePath) {
return nil, trace.NotFound("file %s is not found", configFilePath)
}
}
// default config doesn't exist? quietly return:
if !utils.FileExists(configFilePath) {
log.Info("not using a config file")
return nil, nil
}
log.Debug("reading config file: ", configFilePath)
return ReadFromFile(configFilePath)
}
// ReadResources loads a set of resources from a file.
func ReadResources(filePath string) ([]types.Resource, error) {
reader, err := utils.OpenFile(filePath)
if err != nil {
return nil, trace.Wrap(err)
}
defer reader.Close()
decoder := kyaml.NewYAMLOrJSONDecoder(reader, defaults.LookaheadBufSize)
var resources []types.Resource
for {
var raw services.UnknownResource
err := decoder.Decode(&raw)
if err != nil {
if err == io.EOF {
break
}
return nil, trace.Wrap(err)
}
rsc, err := services.UnmarshalResource(raw.Kind, raw.Raw)
if err != nil {
return nil, trace.Wrap(err)
}
resources = append(resources, rsc)
}
return resources, nil
}
// ApplyFileConfig applies configuration from a YAML file to Teleport
// runtime config
func ApplyFileConfig(fc *FileConfig, cfg *servicecfg.Config) error {
var err error
// no config file? no problem
if fc == nil {
return nil
}
applyConfigVersion(fc, cfg)
// merge file-based config with defaults in 'cfg'
if fc.Auth.Disabled() {
cfg.Auth.Enabled = false
}
if fc.SSH.Disabled() {
cfg.SSH.Enabled = false
}
if fc.Proxy.Disabled() {
cfg.Proxy.Enabled = false
}
if fc.Kube.Enabled() {
cfg.Kube.Enabled = true
}
if fc.Apps.Disabled() {
cfg.Apps.Enabled = false
}
if fc.Databases.Disabled() {
cfg.Databases.Enabled = false
}
if fc.Metrics.Disabled() {
cfg.Metrics.Enabled = false
}
if fc.WindowsDesktop.Disabled() {
cfg.WindowsDesktop.Enabled = false
}
applyString(fc.NodeName, &cfg.Hostname)
// apply "advertise_ip" setting:
advertiseIP := fc.AdvertiseIP
if advertiseIP != "" {
if _, _, err := utils.ParseAdvertiseAddr(advertiseIP); err != nil {
return trace.Wrap(err)
}
cfg.AdvertiseIP = advertiseIP
}
cfg.PIDFile = fc.PIDFile
if err := applyAuthOrProxyAddress(fc, cfg); err != nil {
return trace.Wrap(err)
}
if err := applyTokenConfig(fc, cfg); err != nil {
return trace.Wrap(err)
}
if fc.Global.DataDir != "" {
cfg.DataDir = fc.Global.DataDir
cfg.Auth.StorageConfig.Params["path"] = cfg.DataDir
}
// If a backend is specified, override the defaults.
if fc.Storage.Type != "" {
// If the alternative name "dir" is given, update it to "lite".
if fc.Storage.Type == lite.AlternativeName {
fc.Storage.Type = lite.GetName()
}
cfg.Auth.StorageConfig = fc.Storage
// backend is specified, but no path is set, set a reasonable default
_, pathSet := cfg.Auth.StorageConfig.Params[defaults.BackendPath]
if cfg.Auth.StorageConfig.Type == lite.GetName() && !pathSet {
if cfg.Auth.StorageConfig.Params == nil {
cfg.Auth.StorageConfig.Params = make(backend.Params)
}
cfg.Auth.StorageConfig.Params[defaults.BackendPath] = filepath.Join(cfg.DataDir, defaults.BackendDir)
}
} else {
// Set a reasonable default.
cfg.Auth.StorageConfig.Params[defaults.BackendPath] = filepath.Join(cfg.DataDir, defaults.BackendDir)
}
// apply logger settings
err = applyLogConfig(fc.Logger, cfg)
if err != nil {
return trace.Wrap(err)
}
if fc.CachePolicy.TTL != "" {
log.Warn("cache.ttl config option is deprecated and will be ignored, caches no longer attempt to anticipate resource expiration.")
}
if fc.CachePolicy.Type == memory.GetName() {
log.Debugf("cache.type config option is explicitly set to %v.", memory.GetName())
} else if fc.CachePolicy.Type != "" {
log.Warn("cache.type config option is deprecated and will be ignored, caches are always in memory in this version.")
}
// apply cache policy for node and proxy
cachePolicy, err := fc.CachePolicy.Parse()
if err != nil {
return trace.Wrap(err)
}
cfg.CachePolicy = *cachePolicy
// Apply (TLS) cipher suites and (SSH) ciphers, KEX algorithms, and MAC
// algorithms.
if len(fc.CipherSuites) > 0 {
cipherSuites, err := utils.CipherSuiteMapping(fc.CipherSuites)
if err != nil {
return trace.Wrap(err)
}
cfg.CipherSuites = cipherSuites
}
if fc.Ciphers != nil {
cfg.Ciphers = fc.Ciphers
}
if fc.KEXAlgorithms != nil {
cfg.KEXAlgorithms = fc.KEXAlgorithms
}
if fc.MACAlgorithms != nil {
cfg.MACAlgorithms = fc.MACAlgorithms
}
if fc.CASignatureAlgorithm != nil {
log.Warn("ca_signing_algo config option is deprecated and will be removed in a future release, Teleport defaults to rsa-sha2-512.")
}
// Read in how nodes will validate the CA. A single empty string in the file
// conf should indicate no pins.
if err = cfg.ApplyCAPins(fc.CAPin); err != nil {
return trace.Wrap(err)
}
// Set diagnostic address
if fc.DiagAddr != "" {
// Validate address
parsed, err := utils.ParseAddr(fc.DiagAddr)
if err != nil {
return trace.Wrap(err)
}
cfg.DiagnosticAddr = *parsed
}
// apply connection throttling:
limiters := []*limiter.Config{
&cfg.SSH.Limiter,
&cfg.Auth.Limiter,
&cfg.Proxy.Limiter,
&cfg.Databases.Limiter,
&cfg.Kube.Limiter,
&cfg.WindowsDesktop.ConnLimiter,
}
for _, l := range limiters {
if fc.Limits.MaxConnections > 0 {
l.MaxConnections = fc.Limits.MaxConnections
}
if fc.Limits.MaxUsers > 0 {
l.MaxNumberOfUsers = fc.Limits.MaxUsers
}
for _, rate := range fc.Limits.Rates {
l.Rates = append(l.Rates, limiter.Rate{
Period: rate.Period,
Average: rate.Average,
Burst: rate.Burst,
})
}
}
// Apply configuration for "auth_service", "proxy_service", "ssh_service",
// and "app_service" if they are enabled.
if fc.Auth.Enabled() {
err = applyAuthConfig(fc, cfg)
if err != nil {
return trace.Wrap(err)
}
}
if fc.Proxy.Enabled() {
err = applyProxyConfig(fc, cfg)
if err != nil {
return trace.Wrap(err)
}
}
if fc.SSH.Enabled() {
err = applySSHConfig(fc, cfg)
if err != nil {
return trace.Wrap(err)
}
}
if fc.Kube.Enabled() {
if err := applyKubeConfig(fc, cfg); err != nil {
return trace.Wrap(err)
}
}
if fc.Apps.Enabled() {
if err := applyAppsConfig(fc, cfg); err != nil {
return trace.Wrap(err)
}
}
if fc.Databases.Enabled() {
if err := applyDatabasesConfig(fc, cfg); err != nil {
return trace.Wrap(err)
}
}
if fc.Metrics.Enabled() {
if err := applyMetricsConfig(fc, cfg); err != nil {
return trace.Wrap(err)
}
}
if fc.WindowsDesktop.Enabled() {
if err := applyWindowsDesktopConfig(fc, cfg); err != nil {
return trace.Wrap(err)
}
}
if fc.Tracing.Enabled() {
if err := applyTracingConfig(fc, cfg); err != nil {
return trace.Wrap(err)
}
}
if fc.Discovery.Enabled() {
if err := applyDiscoveryConfig(fc, cfg); err != nil {
return trace.Wrap(err)
}
}
return nil
}
func applyAuthOrProxyAddress(fc *FileConfig, cfg *servicecfg.Config) error {
switch cfg.Version {
// For config versions v1 and v2, the auth_servers field can point to an auth
// server or a proxy server
case defaults.TeleportConfigVersionV1, defaults.TeleportConfigVersionV2:
// config file has auth servers in there?
if len(fc.AuthServers) > 0 {
var parsedAddresses []utils.NetAddr
for _, as := range fc.AuthServers {
addr, err := utils.ParseHostPortAddr(as, defaults.AuthListenPort)
if err != nil {
return trace.Wrap(err)
}
parsedAddresses = append(parsedAddresses, *addr)
}
if err := cfg.SetAuthServerAddresses(parsedAddresses); err != nil {
return trace.Wrap(err)
}
}
if fc.AuthServer != "" {
return trace.BadParameter("auth_server is supported from config version v3 onwards")
}
if fc.ProxyServer != "" {
return trace.BadParameter("proxy_server is supported from config version v3 onwards")
}
// From v3 onwards, either auth_server or proxy_server should be set
case defaults.TeleportConfigVersionV3:
if len(fc.AuthServers) > 0 {
return trace.BadParameter("config v3 has replaced auth_servers with either auth_server or proxy_server")
}
haveAuthServer := fc.AuthServer != ""
haveProxyServer := fc.ProxyServer != ""
if haveProxyServer && haveAuthServer {
return trace.BadParameter("only one of auth_server or proxy_server should be set")
}
if haveAuthServer {
addr, err := utils.ParseHostPortAddr(fc.AuthServer, defaults.AuthListenPort)
if err != nil {
return trace.Wrap(err)
}
cfg.SetAuthServerAddress(*addr)
}
if haveProxyServer {
addr, err := utils.ParseHostPortAddr(fc.ProxyServer, defaults.HTTPListenPort)
if err != nil {
return trace.Wrap(err)
}
cfg.ProxyServer = *addr
}
}
return nil
}
func applyLogConfig(loggerConfig Log, cfg *servicecfg.Config) error {
logger := log.StandardLogger()
switch loggerConfig.Output {
case "":
break // not set
case "stderr", "error", "2":
logger.SetOutput(os.Stderr)
cfg.Console = io.Discard // disable console printing
case "stdout", "out", "1":
logger.SetOutput(os.Stdout)
cfg.Console = io.Discard // disable console printing
case teleport.Syslog:
err := utils.SwitchLoggerToSyslog(logger)
if err != nil {
// this error will go to stderr
log.Errorf("Failed to switch logging to syslog: %v.", err)
}
default:
// assume it's a file path:
logFile, err := os.Create(loggerConfig.Output)
if err != nil {
return trace.Wrap(err, "failed to create the log file")
}
logger.SetOutput(logFile)
}
switch strings.ToLower(loggerConfig.Severity) {
case "", "info":
logger.SetLevel(log.InfoLevel)
case "err", "error":
logger.SetLevel(log.ErrorLevel)
case teleport.DebugLevel:
logger.SetLevel(log.DebugLevel)
case "warn", "warning":
logger.SetLevel(log.WarnLevel)
default:
return trace.BadParameter("unsupported logger severity: %q", loggerConfig.Severity)
}
switch strings.ToLower(loggerConfig.Format.Output) {
case "":
fallthrough // not set. defaults to 'text'
case "text":
formatter := &utils.TextFormatter{
ExtraFields: loggerConfig.Format.ExtraFields,
EnableColors: trace.IsTerminal(os.Stderr),
}
if err := formatter.CheckAndSetDefaults(); err != nil {
return trace.Wrap(err)
}
logger.SetFormatter(formatter)
case "json":
formatter := &utils.JSONFormatter{
ExtraFields: loggerConfig.Format.ExtraFields,
}
if err := formatter.CheckAndSetDefaults(); err != nil {
return trace.Wrap(err)
}
logger.SetFormatter(formatter)
stdlog.SetOutput(io.Discard) // disable the standard logger used by external dependencies
stdlog.SetFlags(0)
default:
return trace.BadParameter("unsupported log output format : %q", loggerConfig.Format.Output)
}
cfg.Log = logger
return nil
}
// applyAuthConfig applies file configuration for the "auth_service" section.
func applyAuthConfig(fc *FileConfig, cfg *servicecfg.Config) error {
var err error
if fc.Auth.KubeconfigFile != "" {
warningMessage := "The auth_service no longer needs kubeconfig_file. It has " +
"been moved to proxy_service section. This setting is ignored."
log.Warning(warningMessage)
}
cfg.Auth.EnableProxyProtocol, err = utils.ParseOnOff("proxy_protocol", fc.Auth.ProxyProtocol, true)
if err != nil {
return trace.Wrap(err)
}
if fc.Auth.ListenAddress != "" {
addr, err := utils.ParseHostPortAddr(fc.Auth.ListenAddress, int(defaults.AuthListenPort))
if err != nil {
return trace.Wrap(err)
}
cfg.Auth.ListenAddr = *addr
if len(cfg.AuthServerAddresses()) == 0 {
cfg.SetAuthServerAddress(*addr)
}
}
for _, t := range fc.Auth.ReverseTunnels {
tun, err := t.ConvertAndValidate()
if err != nil {
return trace.Wrap(err)
}
cfg.ReverseTunnels = append(cfg.ReverseTunnels, tun)
}
if len(fc.Auth.PublicAddr) != 0 {
addrs, err := utils.AddrsFromStrings(fc.Auth.PublicAddr, defaults.AuthListenPort)
if err != nil {
return trace.Wrap(err)
}
cfg.Auth.PublicAddrs = addrs
}
// read in cluster name from file configuration and create services.ClusterName
cfg.Auth.ClusterName, err = fc.Auth.ClusterName.Parse()
if err != nil {
return trace.Wrap(err)
}
// read in static tokens from file configuration and create services.StaticTokens
if fc.Auth.StaticTokens != nil {
cfg.Auth.StaticTokens, err = fc.Auth.StaticTokens.Parse()
if err != nil {
return trace.Wrap(err)
}
}
// read in and set authentication preferences
if fc.Auth.Authentication != nil {
cfg.Auth.Preference, err = fc.Auth.Authentication.Parse()
if err != nil {
return trace.Wrap(err)
}
if err := dtconfig.ValidateConfigAgainstModules(cfg.Auth.Preference.GetDeviceTrust()); err != nil {
return trace.Wrap(err)
}
}
if fc.Auth.MessageOfTheDay != "" {
cfg.Auth.Preference.SetMessageOfTheDay(fc.Auth.MessageOfTheDay)
}
if fc.Auth.DisconnectExpiredCert != nil {
cfg.Auth.Preference.SetOrigin(types.OriginConfigFile)
cfg.Auth.Preference.SetDisconnectExpiredCert(fc.Auth.DisconnectExpiredCert.Value)
}
// Set cluster audit configuration from file configuration.
auditConfigSpec, err := services.ClusterAuditConfigSpecFromObject(fc.Storage.Params)
if err != nil {
return trace.Wrap(err)
}
auditConfigSpec.Type = fc.Storage.Type
cfg.Auth.AuditConfig, err = types.NewClusterAuditConfig(*auditConfigSpec)
if err != nil {
return trace.Wrap(err)
}
// Only override networking configuration if some of its fields are
// specified in file configuration.
if fc.Auth.hasCustomNetworkingConfig() {
cfg.Auth.NetworkingConfig, err = types.NewClusterNetworkingConfigFromConfigFile(types.ClusterNetworkingConfigSpecV2{
ClientIdleTimeout: fc.Auth.ClientIdleTimeout,
ClientIdleTimeoutMessage: fc.Auth.ClientIdleTimeoutMessage,
WebIdleTimeout: fc.Auth.WebIdleTimeout,
KeepAliveInterval: fc.Auth.KeepAliveInterval,
KeepAliveCountMax: fc.Auth.KeepAliveCountMax,
SessionControlTimeout: fc.Auth.SessionControlTimeout,
ProxyListenerMode: fc.Auth.ProxyListenerMode,
RoutingStrategy: fc.Auth.RoutingStrategy,
TunnelStrategy: fc.Auth.TunnelStrategy,
ProxyPingInterval: fc.Auth.ProxyPingInterval,
})
if err != nil {
return trace.Wrap(err)
}
}
// Only override session recording configuration if either field is
// specified in file configuration.
if fc.Auth.hasCustomSessionRecording() {
cfg.Auth.SessionRecordingConfig, err = types.NewSessionRecordingConfigFromConfigFile(types.SessionRecordingConfigSpecV2{
Mode: fc.Auth.SessionRecording,
ProxyChecksHostKeys: fc.Auth.ProxyChecksHostKeys,
})
if err != nil {
return trace.Wrap(err)
}
}
if err := applyKeyStoreConfig(fc, cfg); err != nil {
return trace.Wrap(err)
}
// read in and set the license file path (not used in open-source version)
licenseFile := fc.Auth.LicenseFile
if licenseFile != "" {
if filepath.IsAbs(licenseFile) {
cfg.Auth.LicenseFile = licenseFile
} else {
cfg.Auth.LicenseFile = filepath.Join(cfg.DataDir, licenseFile)
}
}
cfg.Auth.LoadAllCAs = fc.Auth.LoadAllCAs
if fc.Auth.HostedPlugins.Enabled {
cfg.Auth.HostedPlugins.Enabled = true
cfg.Auth.HostedPlugins.OAuthProviders, err = fc.Auth.HostedPlugins.OAuthProviders.Parse()
if err != nil {
return trace.Wrap(err)
}
}
return nil
}
func applyKeyStoreConfig(fc *FileConfig, cfg *servicecfg.Config) error {
if fc.Auth.CAKeyParams == nil {
return nil
}
if fc.Auth.CAKeyParams.PKCS11 != nil {
if fc.Auth.CAKeyParams.GoogleCloudKMS != nil {
return trace.BadParameter("cannot set both pkcs11 and gcp_kms in file config")
}
return trace.Wrap(applyPKCS11Config(fc.Auth.CAKeyParams.PKCS11, cfg))
}
if fc.Auth.CAKeyParams.GoogleCloudKMS != nil {
return trace.Wrap(applyGoogleCloudKMSConfig(fc.Auth.CAKeyParams.GoogleCloudKMS, cfg))
}
return nil
}
func applyPKCS11Config(pkcs11Config *PKCS11, cfg *servicecfg.Config) error {
if pkcs11Config.ModulePath != "" {
fi, err := utils.StatFile(pkcs11Config.ModulePath)
if err != nil {
return trace.Wrap(err)
}
const worldWritableBits = 0o002
if fi.Mode().Perm()&worldWritableBits != 0 {
return trace.Errorf(
"PKCS11 library (%s) must not be world-writable",
pkcs11Config.ModulePath,
)
}
cfg.Auth.KeyStore.PKCS11.Path = pkcs11Config.ModulePath
}
cfg.Auth.KeyStore.PKCS11.TokenLabel = pkcs11Config.TokenLabel
cfg.Auth.KeyStore.PKCS11.SlotNumber = pkcs11Config.SlotNumber
cfg.Auth.KeyStore.PKCS11.Pin = pkcs11Config.Pin
if pkcs11Config.PinPath != "" {
if pkcs11Config.Pin != "" {
return trace.BadParameter("can not set both pin and pin_path")
}
fi, err := utils.StatFile(pkcs11Config.PinPath)
if err != nil {
return trace.Wrap(err)
}
const worldReadableBits = 0o004
if fi.Mode().Perm()&worldReadableBits != 0 {
return trace.Errorf(
"HSM pin file (%s) must not be world-readable",
pkcs11Config.PinPath,
)
}
pinBytes, err := os.ReadFile(pkcs11Config.PinPath)
if err != nil {
return trace.Wrap(err)
}
pin := strings.TrimRight(string(pinBytes), "\r\n")
cfg.Auth.KeyStore.PKCS11.Pin = pin
}
return nil
}
func applyGoogleCloudKMSConfig(kmsConfig *GoogleCloudKMS, cfg *servicecfg.Config) error {
if kmsConfig.KeyRing == "" {
return trace.BadParameter("must set keyring in ca_key_params.gcp_kms")
}
cfg.Auth.KeyStore.GCPKMS.KeyRing = kmsConfig.KeyRing
if kmsConfig.ProtectionLevel == "" {
return trace.BadParameter("must set protection_level in ca_key_params.gcp_kms")
}
cfg.Auth.KeyStore.GCPKMS.ProtectionLevel = kmsConfig.ProtectionLevel
return nil
}
// applyProxyConfig applies file configuration for the "proxy_service" section.
func applyProxyConfig(fc *FileConfig, cfg *servicecfg.Config) error {
var err error
cfg.Proxy.EnableProxyProtocol, err = utils.ParseOnOff("proxy_protocol", fc.Proxy.ProxyProtocol, true)
if err != nil {
return trace.Wrap(err)
}
if fc.Proxy.ListenAddress != "" {
addr, err := utils.ParseHostPortAddr(fc.Proxy.ListenAddress, defaults.SSHProxyListenPort)
if err != nil {
return trace.Wrap(err)
}
cfg.Proxy.SSHAddr = *addr
}
if fc.Proxy.WebAddr != "" {
addr, err := utils.ParseHostPortAddr(fc.Proxy.WebAddr, defaults.HTTPListenPort)
if err != nil {
return trace.Wrap(err)
}
cfg.Proxy.WebAddr = *addr
}
if fc.Proxy.TunAddr != "" {
addr, err := utils.ParseHostPortAddr(fc.Proxy.TunAddr, defaults.SSHProxyTunnelListenPort)
if err != nil {
return trace.Wrap(err)
}
cfg.Proxy.ReverseTunnelListenAddr = *addr
}
if fc.Proxy.MySQLAddr != "" {
addr, err := utils.ParseHostPortAddr(fc.Proxy.MySQLAddr, defaults.MySQLListenPort)
if err != nil {
return trace.Wrap(err)
}
cfg.Proxy.MySQLAddr = *addr
}
if fc.Proxy.PostgresAddr != "" {
addr, err := utils.ParseHostPortAddr(fc.Proxy.PostgresAddr, defaults.PostgresListenPort)
if err != nil {
return trace.Wrap(err)
}
cfg.Proxy.PostgresAddr = *addr
}
if fc.Proxy.MongoAddr != "" {
addr, err := utils.ParseHostPortAddr(fc.Proxy.MongoAddr, defaults.MongoListenPort)
if err != nil {
return trace.Wrap(err)
}
cfg.Proxy.MongoAddr = *addr
}
if fc.Proxy.PeerAddr != "" {
addr, err := utils.ParseHostPortAddr(fc.Proxy.PeerAddr, int(defaults.ProxyPeeringListenPort))
if err != nil {
return trace.Wrap(err)
}
cfg.Proxy.PeerAddress = *addr
}
if fc.Proxy.UI != nil {
cfg.Proxy.UI = webclient.UIConfig(*fc.Proxy.UI)
}
// This is the legacy format. Continue to support it forever, but ideally
// users now use the list format below.
if fc.Proxy.KeyFile != "" || fc.Proxy.CertFile != "" {
cfg.Proxy.KeyPairs = append(cfg.Proxy.KeyPairs, servicecfg.KeyPairPath{
PrivateKey: fc.Proxy.KeyFile,
Certificate: fc.Proxy.CertFile,
})
}
for _, p := range fc.Proxy.KeyPairs {
// Check that the certificate exists on disk. This exists to provide the
// user a sensible error message.
if !utils.FileExists(p.PrivateKey) {
return trace.Errorf("https private key does not exist: %s", p.PrivateKey)
}
if !utils.FileExists(p.Certificate) {
return trace.Errorf("https cert does not exist: %s", p.Certificate)
}
// Read in certificate from disk. If Teleport finds a self-signed
// certificate chain, log a warning, and then accept whatever certificate
// was passed. If the certificate is not self-signed, verify the certificate
// chain from leaf to root with the trust store on the computer so browsers
// don't complain.
certificateChain, err := utils.ReadCertificatesFromPath(p.Certificate)
if err != nil {
return trace.Wrap(err)
}
if utils.IsSelfSigned(certificateChain) {
warningMessage := "Starting Teleport with a self-signed TLS certificate, this is " +
"not safe for production clusters. Using a self-signed certificate opens " +
"Teleport users to Man-in-the-Middle attacks."
log.Warnf(warningMessage)
} else {
if err := utils.VerifyCertificateChain(certificateChain); err != nil {
return trace.BadParameter("unable to verify HTTPS certificate chain in %v: %s",
fc.Proxy.CertFile, utils.UserMessageFromError(err))
}
}
cfg.Proxy.KeyPairs = append(cfg.Proxy.KeyPairs, servicecfg.KeyPairPath{
PrivateKey: p.PrivateKey,
Certificate: p.Certificate,
})
}
cfg.Proxy.KeyPairsReloadInterval = fc.Proxy.KeyPairsReloadInterval
// apply kubernetes proxy config, by default kube proxy is disabled
legacyKube := fc.Proxy.Kube.Configured() && fc.Proxy.Kube.Enabled()
newKube := fc.Proxy.KubeAddr != "" || len(fc.Proxy.KubePublicAddr) > 0
switch {
case legacyKube && !newKube:
cfg.Proxy.Kube.Enabled = true
cfg.Proxy.Kube.LegacyKubeProxy = true
if fc.Proxy.Kube.KubeconfigFile != "" {
cfg.Proxy.Kube.KubeconfigPath = fc.Proxy.Kube.KubeconfigFile
}
if fc.Proxy.Kube.ListenAddress != "" {
addr, err := utils.ParseHostPortAddr(fc.Proxy.Kube.ListenAddress, int(defaults.KubeListenPort))
if err != nil {
return trace.Wrap(err)
}
cfg.Proxy.Kube.ListenAddr = *addr
}
if len(fc.Proxy.Kube.PublicAddr) != 0 {
addrs, err := utils.AddrsFromStrings(fc.Proxy.Kube.PublicAddr, defaults.KubeListenPort)
if err != nil {
return trace.Wrap(err)
}
cfg.Proxy.Kube.PublicAddrs = addrs
}
case !legacyKube && newKube:
// New kubernetes format (kubernetes_service +
// proxy_service.kube_listen_addr) is only relevant in the config file
// format. Under the hood, we use the same cfg.Proxy.Kube field to
// enable it.
if len(fc.Proxy.KubePublicAddr) > 0 && fc.Proxy.KubeAddr == "" {
return trace.BadParameter("kube_listen_addr must be set when kube_public_addr is set")
}
cfg.Proxy.Kube.Enabled = true
addr, err := utils.ParseHostPortAddr(fc.Proxy.KubeAddr, int(defaults.KubeListenPort))
if err != nil {
return trace.Wrap(err)
}
cfg.Proxy.Kube.ListenAddr = *addr
publicAddrs, err := utils.AddrsFromStrings(fc.Proxy.KubePublicAddr, defaults.KubeListenPort)
if err != nil {
return trace.Wrap(err)
}
cfg.Proxy.Kube.PublicAddrs = publicAddrs
case legacyKube && newKube:
return trace.BadParameter("proxy_service should either set kube_listen_addr/kube_public_addr or kubernetes.enabled, not both; keep kubernetes.enabled if you don't enable kubernetes_service, or keep kube_listen_addr otherwise")
case !legacyKube && !newKube:
if fc.Version != "" && fc.Version != defaults.TeleportConfigVersionV1 {
cfg.Proxy.Kube.Enabled = true
}
}
if len(fc.Proxy.PublicAddr) != 0 {
addrs, err := utils.AddrsFromStrings(fc.Proxy.PublicAddr, cfg.Proxy.WebAddr.Port(defaults.HTTPListenPort))
if err != nil {
return trace.Wrap(err)
}
cfg.Proxy.PublicAddrs = addrs
}
if len(fc.Proxy.SSHPublicAddr) != 0 {
addrs, err := utils.AddrsFromStrings(fc.Proxy.SSHPublicAddr, defaults.SSHProxyListenPort)
if err != nil {
return trace.Wrap(err)
}
cfg.Proxy.SSHPublicAddrs = addrs
}
if len(fc.Proxy.TunnelPublicAddr) != 0 {
addrs, err := utils.AddrsFromStrings(fc.Proxy.TunnelPublicAddr, defaults.SSHProxyTunnelListenPort)
if err != nil {
return trace.Wrap(err)
}
cfg.Proxy.TunnelPublicAddrs = addrs
}
if len(fc.Proxy.PostgresPublicAddr) != 0 {
defaultPort := getPostgresDefaultPort(cfg)
addrs, err := utils.AddrsFromStrings(fc.Proxy.PostgresPublicAddr, defaultPort)
if err != nil {
return trace.Wrap(err)
}
cfg.Proxy.PostgresPublicAddrs = addrs
}
if len(fc.Proxy.MySQLPublicAddr) != 0 {
if fc.Proxy.MySQLAddr == "" {
return trace.BadParameter("mysql_listen_addr must be set when mysql_public_addr is set")
}
// MySQL proxy is listening on a separate port.
addrs, err := utils.AddrsFromStrings(fc.Proxy.MySQLPublicAddr, defaults.MySQLListenPort)
if err != nil {
return trace.Wrap(err)
}
cfg.Proxy.MySQLPublicAddrs = addrs
}
if len(fc.Proxy.MongoPublicAddr) != 0 {
if fc.Proxy.MongoAddr == "" {
return trace.BadParameter("mongo_listen_addr must be set when mongo_public_addr is set")
}
addrs, err := utils.AddrsFromStrings(fc.Proxy.MongoPublicAddr, defaults.MongoListenPort)
if err != nil {
return trace.Wrap(err)
}
cfg.Proxy.MongoPublicAddrs = addrs
}
if fc.Proxy.PeerPublicAddr != "" {
if fc.Proxy.PeerAddr == "" {
return trace.BadParameter("peer_listen_addr must be set when peer_public_addr is set")
}
addr, err := utils.ParseHostPortAddr(fc.Proxy.PeerPublicAddr, int(defaults.ProxyPeeringListenPort))
if err != nil {
return trace.Wrap(err)
}
cfg.Proxy.PeerPublicAddr = *addr
}
if fc.Proxy.IdP.SAMLIdP.Enabled() {
cfg.Proxy.IdP.SAMLIdP.Enabled = true
}
cfg.Proxy.IdP.SAMLIdP.BaseURL = fc.Proxy.IdP.SAMLIdP.BaseURL
acme, err := fc.Proxy.ACME.Parse()
if err != nil {
return trace.Wrap(err)
}
cfg.Proxy.ACME = *acme
return nil
}
func getPostgresDefaultPort(cfg *servicecfg.Config) int {
if !cfg.Proxy.PostgresAddr.IsEmpty() {
// If the proxy.PostgresAddr flag was provided return port
// from PostgresAddr address or default PostgresListenPort.
return cfg.Proxy.PostgresAddr.Port(defaults.PostgresListenPort)
}
// Postgres proxy is multiplexed on the web proxy port. If the proxy is
// not specified here explicitly, prefer defaults in the following
// order, depending on what's set:
// 1. Web proxy public port
// 2. Web proxy listen port
// 3. Web proxy default listen port
if len(cfg.Proxy.PublicAddrs) != 0 {
return cfg.Proxy.PublicAddrs[0].Port(defaults.HTTPListenPort)
}
return cfg.Proxy.WebAddr.Port(defaults.HTTPListenPort)
}
func applyDefaultProxyListenerAddresses(cfg *servicecfg.Config) {
// From v2 onwards if an address is not provided don't fall back to the default values.
if cfg.Version != "" && cfg.Version != defaults.TeleportConfigVersionV1 {
return
}
// For v1 configuration check if address was set in config file if
// not fallback to the default listener address.
if cfg.Proxy.WebAddr.IsEmpty() {
cfg.Proxy.WebAddr = *defaults.ProxyWebListenAddr()
}
if cfg.Proxy.SSHAddr.IsEmpty() {
cfg.Proxy.SSHAddr = *defaults.ProxyListenAddr()
}
if cfg.Proxy.ReverseTunnelListenAddr.IsEmpty() {
cfg.Proxy.ReverseTunnelListenAddr = *defaults.ReverseTunnelListenAddr()
}
if cfg.Proxy.Kube.ListenAddr.IsEmpty() && cfg.Proxy.Kube.Enabled {
cfg.Proxy.Kube.ListenAddr = *defaults.KubeProxyListenAddr()
}
}
// applySSHConfig applies file configuration for the "ssh_service" section.
func applySSHConfig(fc *FileConfig, cfg *servicecfg.Config) (err error) {
if fc.SSH.ListenAddress != "" {
addr, err := utils.ParseHostPortAddr(fc.SSH.ListenAddress, int(defaults.SSHServerListenPort))
if err != nil {
return trace.Wrap(err)
}
cfg.SSH.Addr = *addr
}
if fc.SSH.Labels != nil {
cfg.SSH.Labels = make(map[string]string)
for k, v := range fc.SSH.Labels {
cfg.SSH.Labels[k] = v
}
}
if fc.SSH.Commands != nil {
cfg.SSH.CmdLabels = make(services.CommandLabels)
for _, cmdLabel := range fc.SSH.Commands {
cfg.SSH.CmdLabels[cmdLabel.Name] = &types.CommandLabelV2{
Period: types.NewDuration(cmdLabel.Period),
Command: cmdLabel.Command,
Result: "",
}
}
}
if fc.SSH.Namespace != "" {
cfg.SSH.Namespace = fc.SSH.Namespace
}
if fc.SSH.PermitUserEnvironment {
cfg.SSH.PermitUserEnvironment = true
}
if fc.SSH.DisableCreateHostUser || runtime.GOOS != constants.LinuxOS {
cfg.SSH.DisableCreateHostUser = true
if runtime.GOOS != constants.LinuxOS {
log.Debugln("Disabling host user creation as this feature is only available on Linux")
}
}
if fc.SSH.PAM != nil {
cfg.SSH.PAM = fc.SSH.PAM.Parse()
// If PAM is enabled, make sure that Teleport was built with PAM support
// and the PAM library was found at runtime.
if cfg.SSH.PAM.Enabled {
if !pam.BuildHasPAM() {
errorMessage := "Unable to start Teleport: PAM was enabled in file configuration but this \n" +
"Teleport binary was built without PAM support. To continue either download a \n" +
"Teleport binary build with PAM support from https://goteleport.com/teleport \n" +
"or disable PAM in file configuration."
return trace.BadParameter(errorMessage)
}
if !pam.SystemHasPAM() {
errorMessage := "Unable to start Teleport: PAM was enabled in file configuration but this \n" +
"system does not have the needed PAM library installed. To continue either \n" +
"install libpam or disable PAM in file configuration."
return trace.BadParameter(errorMessage)
}
}
}
if len(fc.SSH.PublicAddr) != 0 {
addrs, err := utils.AddrsFromStrings(fc.SSH.PublicAddr, defaults.SSHServerListenPort)
if err != nil {
return trace.Wrap(err)
}
cfg.SSH.PublicAddrs = addrs
}
if fc.SSH.BPF != nil {
cfg.SSH.BPF = fc.SSH.BPF.Parse()
}
if fc.SSH.RestrictedSession != nil {
rs, err := fc.SSH.RestrictedSession.Parse()
if err != nil {
return trace.Wrap(err)
}
cfg.SSH.RestrictedSession = rs
}
cfg.SSH.AllowTCPForwarding = fc.SSH.AllowTCPForwarding()
cfg.SSH.X11, err = fc.SSH.X11ServerConfig()
if err != nil {
return trace.Wrap(err)
}
cfg.SSH.AllowFileCopying = fc.SSH.SSHFileCopy()
return nil
}
// getInstallerProxyAddr determines the address of the proxy for discovered
// nodes to connect to.
func getInstallerProxyAddr(matcher AzureMatcher, fc *FileConfig) string {
if matcher.InstallParams.PublicProxyAddr != "" {
return matcher.InstallParams.PublicProxyAddr
}
if fc.ProxyServer != "" {
return fc.ProxyServer
}
if fc.Proxy.Enabled() && len(fc.Proxy.PublicAddr) > 0 {
return fc.Proxy.PublicAddr[0]
}
return ""
}
func applyDiscoveryConfig(fc *FileConfig, cfg *servicecfg.Config) error {
cfg.Discovery.Enabled = fc.Discovery.Enabled()
for _, matcher := range fc.Discovery.AWSMatchers {
installParams, err := matcher.InstallParams.Parse()
if err != nil {
return trace.Wrap(err)
}
cfg.Discovery.AWSMatchers = append(cfg.Discovery.AWSMatchers,
services.AWSMatcher{
Types: matcher.Types,
Regions: matcher.Regions,
Tags: matcher.Tags,
Params: installParams,
SSM: &services.AWSSSM{DocumentName: matcher.SSM.DocumentName},
})
}
for _, matcher := range fc.Discovery.AzureMatchers {
m := services.AzureMatcher{
Subscriptions: matcher.Subscriptions,
ResourceGroups: matcher.ResourceGroups,
Types: matcher.Types,
Regions: matcher.Regions,
ResourceTags: matcher.ResourceTags,
}
if matcher.InstallParams != nil {
m.Params = services.InstallerParams{
JoinMethod: matcher.InstallParams.JoinParams.Method,
JoinToken: matcher.InstallParams.JoinParams.TokenName,
ScriptName: matcher.InstallParams.ScriptName,
PublicProxyAddr: getInstallerProxyAddr(matcher, fc),
}
}
cfg.Discovery.AzureMatchers = append(cfg.Discovery.AzureMatchers, m)
}
for _, matcher := range fc.Discovery.GCPMatchers {
cfg.Discovery.GCPMatchers = append(
cfg.Discovery.GCPMatchers,
services.GCPMatcher{
Types: matcher.Types,
Locations: matcher.Locations,
Tags: matcher.Tags,
ProjectIDs: matcher.ProjectIDs,
},
)
}
return nil
}
// applyKubeConfig applies file configuration for the "kubernetes_service" section.
func applyKubeConfig(fc *FileConfig, cfg *servicecfg.Config) error {
if fc.Kube.ListenAddress != "" {
addr, err := utils.ParseHostPortAddr(fc.Kube.ListenAddress, int(defaults.SSHProxyListenPort))
if err != nil {
return trace.Wrap(err)
}
cfg.Kube.ListenAddr = addr
}
if len(fc.Kube.PublicAddr) != 0 {
addrs, err := utils.AddrsFromStrings(fc.Kube.PublicAddr, defaults.KubeListenPort)
if err != nil {
return trace.Wrap(err)
}
cfg.Kube.PublicAddrs = addrs
}
for _, matcher := range fc.Kube.ResourceMatchers {
cfg.Kube.ResourceMatchers = append(cfg.Kube.ResourceMatchers,
services.ResourceMatcher{
Labels: matcher.Labels,
})
}
if fc.Kube.KubeconfigFile != "" {
cfg.Kube.KubeconfigPath = fc.Kube.KubeconfigFile
}
if fc.Kube.KubeClusterName != "" {
cfg.Kube.KubeClusterName = fc.Kube.KubeClusterName
}
if fc.Kube.StaticLabels != nil {
cfg.Kube.StaticLabels = make(map[string]string)
for k, v := range fc.Kube.StaticLabels {
cfg.Kube.StaticLabels[k] = v
}
}
if fc.Kube.DynamicLabels != nil {
cfg.Kube.DynamicLabels = make(services.CommandLabels)
for _, cmdLabel := range fc.Kube.DynamicLabels {
cfg.Kube.DynamicLabels[cmdLabel.Name] = &types.CommandLabelV2{
Period: types.NewDuration(cmdLabel.Period),
Command: cmdLabel.Command,
Result: "",
}
}
}
return nil
}
// applyDatabasesConfig applies file configuration for the "db_service" section.
func applyDatabasesConfig(fc *FileConfig, cfg *servicecfg.Config) error {
cfg.Databases.Enabled = true
for _, matcher := range fc.Databases.ResourceMatchers {
cfg.Databases.ResourceMatchers = append(cfg.Databases.ResourceMatchers,
services.ResourceMatcher{
Labels: matcher.Labels,
})
}
for _, matcher := range fc.Databases.AWSMatchers {
cfg.Databases.AWSMatchers = append(cfg.Databases.AWSMatchers,
services.AWSMatcher{
Types: matcher.Types,
Regions: matcher.Regions,
Tags: matcher.Tags,
})
}
for _, matcher := range fc.Databases.AzureMatchers {
cfg.Databases.AzureMatchers = append(cfg.Databases.AzureMatchers,
services.AzureMatcher{
Subscriptions: matcher.Subscriptions,
ResourceGroups: matcher.ResourceGroups,
Types: matcher.Types,
Regions: matcher.Regions,
ResourceTags: matcher.ResourceTags,
})
}
for _, database := range fc.Databases.Databases {
staticLabels := make(map[string]string)
if database.StaticLabels != nil {
staticLabels = database.StaticLabels
}
dynamicLabels := make(services.CommandLabels)
if database.DynamicLabels != nil {
for _, v := range database.DynamicLabels {
dynamicLabels[v.Name] = &types.CommandLabelV2{
Period: types.NewDuration(v.Period),
Command: v.Command,
Result: "",
}
}
}
caBytes, err := readCACert(database)
if err != nil {
return trace.Wrap(err)
}
db := servicecfg.Database{
Name: database.Name,
Description: database.Description,
Protocol: database.Protocol,
URI: database.URI,
StaticLabels: staticLabels,
DynamicLabels: dynamicLabels,
MySQL: servicecfg.MySQLOptions{
ServerVersion: database.MySQL.ServerVersion,
},
TLS: servicecfg.DatabaseTLS{
CACert: caBytes,
ServerName: database.TLS.ServerName,
Mode: servicecfg.TLSMode(database.TLS.Mode),
},
AWS: servicecfg.DatabaseAWS{
AccountID: database.AWS.AccountID,
ExternalID: database.AWS.ExternalID,
Region: database.AWS.Region,
Redshift: servicecfg.DatabaseAWSRedshift{
ClusterID: database.AWS.Redshift.ClusterID,
},
RedshiftServerless: servicecfg.DatabaseAWSRedshiftServerless{
WorkgroupName: database.AWS.RedshiftServerless.WorkgroupName,
EndpointName: database.AWS.RedshiftServerless.EndpointName,
},
RDS: servicecfg.DatabaseAWSRDS{
InstanceID: database.AWS.RDS.InstanceID,
ClusterID: database.AWS.RDS.ClusterID,
},
ElastiCache: servicecfg.DatabaseAWSElastiCache{
ReplicationGroupID: database.AWS.ElastiCache.ReplicationGroupID,
},
MemoryDB: servicecfg.DatabaseAWSMemoryDB{
ClusterName: database.AWS.MemoryDB.ClusterName,
},
SecretStore: servicecfg.DatabaseAWSSecretStore{
KeyPrefix: database.AWS.SecretStore.KeyPrefix,
KMSKeyID: database.AWS.SecretStore.KMSKeyID,
},
},
GCP: servicecfg.DatabaseGCP{
ProjectID: database.GCP.ProjectID,
InstanceID: database.GCP.InstanceID,
},
AD: servicecfg.DatabaseAD{
KeytabFile: database.AD.KeytabFile,
Krb5File: database.AD.Krb5File,
Domain: database.AD.Domain,
SPN: database.AD.SPN,
},
Azure: servicecfg.DatabaseAzure{
ResourceID: database.Azure.ResourceID,
IsFlexiServer: database.Azure.IsFlexiServer,
},
}
if err := db.CheckAndSetDefaults(); err != nil {
return trace.Wrap(err)
}
cfg.Databases.Databases = append(cfg.Databases.Databases, db)
}
return nil
}
// readCACert reads database CA certificate from the config file.
// First 'tls.ca_cert_file` is being read, then deprecated 'ca_cert_file' if
// the first one is not set.
func readCACert(database *Database) ([]byte, error) {
var (
caBytes []byte
err error
)
if database.TLS.CACertFile != "" {
caBytes, err = os.ReadFile(database.TLS.CACertFile)
if err != nil {
return nil, trace.ConvertSystemError(err)
}
}
// ca_cert_file is deprecated, but we still support it.
// Print a warning if the old field is still being used.
if database.CACertFile != "" {
if database.TLS.CACertFile != "" {
// New and old fields are set. Ignore the old field.
log.Warnf("Ignoring deprecated ca_cert_file in %s configuration; using tls.ca_cert_file.", database.Name)
} else {
// Only old field is set, inform about deprecation.
log.Warnf("ca_cert_file is deprecated, please use tls.ca_cert_file instead for %s.", database.Name)
caBytes, err = os.ReadFile(database.CACertFile)
if err != nil {
return nil, trace.ConvertSystemError(err)
}
}
}
return caBytes, nil
}
// applyAppsConfig applies file configuration for the "app_service" section.
func applyAppsConfig(fc *FileConfig, cfg *servicecfg.Config) error {
// Apps are enabled.
cfg.Apps.Enabled = true
// Enable debugging application if requested.
cfg.Apps.DebugApp = fc.Apps.DebugApp
// Configure resource watcher selectors if present.
for _, matcher := range fc.Apps.ResourceMatchers {
cfg.Apps.ResourceMatchers = append(cfg.Apps.ResourceMatchers,
services.ResourceMatcher{
Labels: matcher.Labels,
})
}
// Loop over all apps and load app configuration.
for _, application := range fc.Apps.Apps {
// Parse the static labels of the application.
staticLabels := make(map[string]string)
if application.StaticLabels != nil {
staticLabels = application.StaticLabels
}
// Parse the dynamic labels of the application.
dynamicLabels := make(services.CommandLabels)
if application.DynamicLabels != nil {
for _, v := range application.DynamicLabels {
dynamicLabels[v.Name] = &types.CommandLabelV2{
Period: types.NewDuration(v.Period),
Command: v.Command,
}
}
}
// Add the application to the list of proxied applications.
app := servicecfg.App{
Name: application.Name,
Description: application.Description,
URI: application.URI,
PublicAddr: application.PublicAddr,
StaticLabels: staticLabels,
DynamicLabels: dynamicLabels,
InsecureSkipVerify: application.InsecureSkipVerify,
Cloud: application.Cloud,
}
if application.Rewrite != nil {
// Parse http rewrite headers if there are any.
headers, err := servicecfg.ParseHeaders(application.Rewrite.Headers)
if err != nil {
return trace.Wrap(err, "failed to parse headers rewrite configuration for app %q",
application.Name)
}
app.Rewrite = &servicecfg.Rewrite{
Redirect: application.Rewrite.Redirect,
Headers: headers,
}
}
if application.AWS != nil {
app.AWS = &servicecfg.AppAWS{
ExternalID: application.AWS.ExternalID,
}
}
if err := app.CheckAndSetDefaults(); err != nil {
return trace.Wrap(err)
}
cfg.Apps.Apps = append(cfg.Apps.Apps, app)
}
return nil
}
// applyMetricsConfig applies file configuration for the "metrics_service" section.
func applyMetricsConfig(fc *FileConfig, cfg *servicecfg.Config) error {
// Metrics is enabled.
cfg.Metrics.Enabled = true
addr, err := utils.ParseHostPortAddr(fc.Metrics.ListenAddress, int(defaults.MetricsListenPort))
if err != nil {
return trace.Wrap(err)
}
cfg.Metrics.ListenAddr = addr
cfg.Metrics.GRPCServerLatency = fc.Metrics.GRPCServerLatency
cfg.Metrics.GRPCClientLatency = fc.Metrics.GRPCClientLatency
if !fc.Metrics.MTLSEnabled() {
return nil
}
cfg.Metrics.MTLS = true
if len(fc.Metrics.KeyPairs) == 0 {
return trace.BadParameter("at least one keypair should be provided when mtls is enabled in the metrics config")
}
if len(fc.Metrics.CACerts) == 0 {
return trace.BadParameter("at least one CA cert should be provided when mtls is enabled in the metrics config")
}
for _, p := range fc.Metrics.KeyPairs {
// Check that the certificate exists on disk. This exists to provide the
// user a sensible error message.
if !utils.FileExists(p.PrivateKey) {
return trace.NotFound("metrics service private key does not exist: %s", p.PrivateKey)
}
if !utils.FileExists(p.Certificate) {
return trace.NotFound("metrics service cert does not exist: %s", p.Certificate)
}
certificateChain, err := utils.ReadCertificatesFromPath(p.Certificate)
if err != nil {
return trace.Wrap(err)
}
if !utils.IsSelfSigned(certificateChain) {
if err := utils.VerifyCertificateChain(certificateChain); err != nil {
return trace.BadParameter("unable to verify the metrics service certificate chain in %v: %s",
p.Certificate, utils.UserMessageFromError(err))
}
}
cfg.Metrics.KeyPairs = append(cfg.Metrics.KeyPairs, servicecfg.KeyPairPath{
PrivateKey: p.PrivateKey,
Certificate: p.Certificate,
})
}
for _, caCert := range fc.Metrics.CACerts {
// Check that the certificate exists on disk. This exists to provide the
// user a sensible error message.
if !utils.FileExists(caCert) {
return trace.NotFound("metrics service ca cert does not exist: %s", caCert)
}
cfg.Metrics.CACerts = append(cfg.Metrics.CACerts, caCert)
}
return nil
}
// applyWindowsDesktopConfig applies file configuration for the "windows_desktop_service" section.
func applyWindowsDesktopConfig(fc *FileConfig, cfg *servicecfg.Config) error {
if err := fc.WindowsDesktop.Check(); err != nil {
return trace.Wrap(err)
}
cfg.WindowsDesktop.Enabled = true
if fc.WindowsDesktop.ListenAddress != "" {
listenAddr, err := utils.ParseHostPortAddr(fc.WindowsDesktop.ListenAddress, int(defaults.WindowsDesktopListenPort))
if err != nil {
return trace.Wrap(err)
}
cfg.WindowsDesktop.ListenAddr = *listenAddr
}
for _, filter := range fc.WindowsDesktop.Discovery.Filters {
if _, err := ldap.CompileFilter(filter); err != nil {
return trace.BadParameter("WindowsDesktopService specifies invalid LDAP filter %q", filter)
}
}
for _, attributeName := range fc.WindowsDesktop.Discovery.LabelAttributes {
if !types.IsValidLabelKey(attributeName) {
return trace.BadParameter("WindowsDesktopService specifies label_attribute %q which is not a valid label key", attributeName)
}
}
cfg.WindowsDesktop.Discovery = servicecfg.LDAPDiscoveryConfig{
BaseDN: fc.WindowsDesktop.Discovery.BaseDN,
Filters: fc.WindowsDesktop.Discovery.Filters,
LabelAttributes: fc.WindowsDesktop.Discovery.LabelAttributes,
}
var err error
cfg.WindowsDesktop.PublicAddrs, err = utils.AddrsFromStrings(fc.WindowsDesktop.PublicAddr, defaults.WindowsDesktopListenPort)
if err != nil {
return trace.Wrap(err)
}
cfg.WindowsDesktop.ShowDesktopWallpaper = fc.WindowsDesktop.ShowDesktopWallpaper
cfg.WindowsDesktop.Hosts, err = utils.AddrsFromStrings(fc.WindowsDesktop.Hosts, defaults.RDPListenPort)
if err != nil {
return trace.Wrap(err)
}
cfg.WindowsDesktop.NonADHosts, err = utils.AddrsFromStrings(fc.WindowsDesktop.NonADHosts, defaults.RDPListenPort)
if err != nil {
return trace.Wrap(err)
}
if fc.WindowsDesktop.LDAP.DEREncodedCAFile != "" && fc.WindowsDesktop.LDAP.PEMEncodedCACert != "" {
return trace.BadParameter("WindowsDesktopService can not use both der_ca_file and ldap_ca_cert")
}
var cert *x509.Certificate
if fc.WindowsDesktop.LDAP.DEREncodedCAFile != "" {
rawCert, err := os.ReadFile(fc.WindowsDesktop.LDAP.DEREncodedCAFile)
if err != nil {
return trace.WrapWithMessage(err, "loading the LDAP CA from file %v", fc.WindowsDesktop.LDAP.DEREncodedCAFile)
}
cert, err = x509.ParseCertificate(rawCert)
if err != nil {
return trace.WrapWithMessage(err, "parsing the LDAP root CA file %v", fc.WindowsDesktop.LDAP.DEREncodedCAFile)
}
}
if fc.WindowsDesktop.LDAP.PEMEncodedCACert != "" {
cert, err = tlsca.ParseCertificatePEM([]byte(fc.WindowsDesktop.LDAP.PEMEncodedCACert))
if err != nil {
return trace.WrapWithMessage(err, "parsing the LDAP root CA PEM cert")
}
}
cfg.WindowsDesktop.LDAP = servicecfg.LDAPConfig{
Addr: fc.WindowsDesktop.LDAP.Addr,
Username: fc.WindowsDesktop.LDAP.Username,
SID: fc.WindowsDesktop.LDAP.SID,
Domain: fc.WindowsDesktop.LDAP.Domain,
InsecureSkipVerify: fc.WindowsDesktop.LDAP.InsecureSkipVerify,
ServerName: fc.WindowsDesktop.LDAP.ServerName,
CA: cert,
}
var hlrs []servicecfg.HostLabelRule
for _, rule := range fc.WindowsDesktop.HostLabels {
r, err := regexp.Compile(rule.Match)
if err != nil {
return trace.BadParameter("WindowsDesktopService specifies invalid regexp %q", rule.Match)
}
if len(rule.Labels) == 0 {
return trace.BadParameter("WindowsDesktopService host regex %q has no labels", rule.Match)
}
for k := range rule.Labels {
if !types.IsValidLabelKey(k) {
return trace.BadParameter("WindowsDesktopService specifies invalid label %q", k)
}
}
hlrs = append(hlrs, servicecfg.HostLabelRule{
Regexp: r,
Labels: rule.Labels,
})
}
cfg.WindowsDesktop.HostLabels = servicecfg.NewHostLabelRules(hlrs...)
if fc.WindowsDesktop.Labels != nil {
cfg.WindowsDesktop.Labels = make(map[string]string)
for k, v := range fc.WindowsDesktop.Labels {
cfg.WindowsDesktop.Labels[k] = v
}
}
return nil
}
// applyTracingConfig applies file configuration for the "tracing_service" section.
func applyTracingConfig(fc *FileConfig, cfg *servicecfg.Config) error {
// Tracing is enabled.
cfg.Tracing.Enabled = true
if fc.Tracing.ExporterURL == "" {
return trace.BadParameter("tracing_service is enabled but no exporter_url is specified")
}
cfg.Tracing.ExporterURL = fc.Tracing.ExporterURL
cfg.Tracing.SamplingRate = float64(fc.Tracing.SamplingRatePerMillion) / 1_000_000.0
for _, p := range fc.Tracing.KeyPairs {
// Check that the certificate exists on disk. This exists to provide the
// user a sensible error message.
if !utils.FileExists(p.PrivateKey) {
return trace.NotFound("tracing_service private key does not exist: %s", p.PrivateKey)
}
if !utils.FileExists(p.Certificate) {
return trace.NotFound("tracing_service cert does not exist: %s", p.Certificate)
}
cfg.Tracing.KeyPairs = append(cfg.Tracing.KeyPairs, servicecfg.KeyPairPath{
PrivateKey: p.PrivateKey,
Certificate: p.Certificate,
})
}
for _, caCert := range fc.Tracing.CACerts {
// Check that the certificate exists on disk. This exists to provide the
// user a sensible error message.
if !utils.FileExists(caCert) {
return trace.NotFound("tracing_service ca cert does not exist: %s", caCert)
}
cfg.Tracing.CACerts = append(cfg.Tracing.CACerts, caCert)
}
return nil
}
// applyString takes 'src' and overwrites target with it, unless 'src' is empty
// returns 'True' if 'src' was not empty
func applyString(src string, target *string) bool {
if src != "" {
*target = src
return true
}
return false
}
// applyConfigVersion applies config version from parsed file. If config version is not
// present the v1 version will be used as default.
func applyConfigVersion(fc *FileConfig, cfg *servicecfg.Config) {
cfg.Version = defaults.TeleportConfigVersionV1
if fc.Version != "" {
cfg.Version = fc.Version
}
}
// Configure merges command line arguments with what's in a configuration file
// with CLI commands taking precedence
func Configure(clf *CommandLineFlags, cfg *servicecfg.Config, legacyAppFlags bool) error {
// pass the value of --insecure flag to the runtime
lib.SetInsecureDevMode(clf.InsecureMode)
// load /etc/teleport.yaml and apply it's values:
fileConf, err := ReadConfigFile(clf.ConfigFile)
if err != nil {
return trace.Wrap(err)
}
// if configuration is passed as an environment variable,
// try to decode it and override the config file
if clf.ConfigString != "" {
fileConf, err = ReadFromString(clf.ConfigString)
if err != nil {
return trace.Wrap(err)
}
}
if clf.BootstrapFile != "" {
resources, err := ReadResources(clf.BootstrapFile)
if err != nil {
return trace.Wrap(err)
}
if len(resources) < 1 {
return trace.BadParameter("no resources found: %q", clf.BootstrapFile)
}
cfg.Auth.BootstrapResources = resources
}
if clf.ApplyOnStartupFile != "" {
resources, err := ReadResources(clf.ApplyOnStartupFile)
if err != nil {
return trace.Wrap(err)
}
if len(resources) < 1 {
return trace.BadParameter("no resources found: %q", clf.ApplyOnStartupFile)
}
cfg.Auth.ApplyOnStartupResources = resources
}
// Apply command line --debug flag to override logger severity.
if clf.Debug {
// If debug logging is requested and no file configuration exists, set the
// log level right away. Otherwise allow the command line flag to override
// logger severity in file configuration.
if fileConf == nil {
log.SetLevel(log.DebugLevel)
cfg.Log.SetLevel(log.DebugLevel)
} else {
fileConf.Logger.Severity = teleport.DebugLevel
}
}
// If this process is trying to join a cluster as an application service,
// make sure application name and URI are provided.
if slices.Contains(splitRoles(clf.Roles), defaults.RoleApp) {
if (clf.AppName == "") && (clf.AppURI == "" && clf.AppCloud == "") {
// TODO: remove legacyAppFlags once `teleport start --app-name` is removed.
if legacyAppFlags {
return trace.BadParameter("application name (--app-name) and URI (--app-uri) flags are both required to join application proxy to the cluster")
}
return trace.BadParameter("to join application proxy to the cluster provide application name (--name) and either URI (--uri) or Cloud type (--cloud)")
}
if clf.AppName == "" {
if legacyAppFlags {
return trace.BadParameter("application name (--app-name) is required to join application proxy to the cluster")
}
return trace.BadParameter("to join application proxy to the cluster provide application name (--name)")
}
if clf.AppURI == "" && clf.AppCloud == "" {
if legacyAppFlags {
return trace.BadParameter("URI (--app-uri) flag is required to join application proxy to the cluster")
}
return trace.BadParameter("to join application proxy to the cluster provide URI (--uri) or Cloud type (--cloud)")
}
}
// If application name was specified on command line, add to file
// configuration where it will be validated.
if clf.AppName != "" {
cfg.Apps.Enabled = true
// Parse static and dynamic labels.
static, dynamic, err := parseLabels(clf.Labels)
if err != nil {
return trace.BadParameter("labels invalid: %v", err)
}
// Create and validate application. If valid, add to list of applications.
app := servicecfg.App{
Name: clf.AppName,
URI: clf.AppURI,
Cloud: clf.AppCloud,
PublicAddr: clf.AppPublicAddr,
StaticLabels: static,
DynamicLabels: dynamic,
}
if err := app.CheckAndSetDefaults(); err != nil {
return trace.Wrap(err)
}
cfg.Apps.Apps = append(cfg.Apps.Apps, app)
}
// If database name was specified on the command line, add to configuration.
if clf.DatabaseName != "" {
cfg.Databases.Enabled = true
staticLabels, dynamicLabels, err := parseLabels(clf.Labels)
if err != nil {
return trace.Wrap(err)
}
var caBytes []byte
if clf.DatabaseCACertFile != "" {
caBytes, err = os.ReadFile(clf.DatabaseCACertFile)
if err != nil {
return trace.Wrap(err)
}
}
db := servicecfg.Database{
Name: clf.DatabaseName,
Description: clf.DatabaseDescription,
Protocol: clf.DatabaseProtocol,
URI: clf.DatabaseURI,
StaticLabels: staticLabels,
MySQL: servicecfg.MySQLOptions{
ServerVersion: clf.DatabaseMySQLServerVersion,
},
DynamicLabels: dynamicLabels,
TLS: servicecfg.DatabaseTLS{
CACert: caBytes,
},
AWS: servicecfg.DatabaseAWS{
Region: clf.DatabaseAWSRegion,
AccountID: clf.DatabaseAWSAccountID,
ExternalID: clf.DatabaseAWSExternalID,
Redshift: servicecfg.DatabaseAWSRedshift{
ClusterID: clf.DatabaseAWSRedshiftClusterID,
},
RDS: servicecfg.DatabaseAWSRDS{
InstanceID: clf.DatabaseAWSRDSInstanceID,
ClusterID: clf.DatabaseAWSRDSClusterID,
},
ElastiCache: servicecfg.DatabaseAWSElastiCache{
ReplicationGroupID: clf.DatabaseAWSElastiCacheGroupID,
},
MemoryDB: servicecfg.DatabaseAWSMemoryDB{
ClusterName: clf.DatabaseAWSMemoryDBClusterName,
},
},
GCP: servicecfg.DatabaseGCP{
ProjectID: clf.DatabaseGCPProjectID,
InstanceID: clf.DatabaseGCPInstanceID,
},
AD: servicecfg.DatabaseAD{
KeytabFile: clf.DatabaseADKeytabFile,
Krb5File: clf.DatabaseADKrb5File,
Domain: clf.DatabaseADDomain,
SPN: clf.DatabaseADSPN,
},
}
if err := db.CheckAndSetDefaults(); err != nil {
return trace.Wrap(err)
}
cfg.Databases.Databases = append(cfg.Databases.Databases, db)
}
if err = ApplyFileConfig(fileConf, cfg); err != nil {
return trace.Wrap(err)
}
// If FIPS mode is specified, validate Teleport configuration is FedRAMP/FIPS
// 140-2 compliant.
if clf.FIPS {
// Make sure all cryptographic primitives are FIPS compliant.
err = utils.UintSliceSubset(defaults.FIPSCipherSuites, cfg.CipherSuites)
if err != nil {
return trace.BadParameter("non-FIPS compliant TLS cipher suite selected: %v", err)
}
err = utils.StringSliceSubset(defaults.FIPSCiphers, cfg.Ciphers)
if err != nil {
return trace.BadParameter("non-FIPS compliant SSH cipher selected: %v", err)
}
err = utils.StringSliceSubset(defaults.FIPSKEXAlgorithms, cfg.KEXAlgorithms)
if err != nil {
return trace.BadParameter("non-FIPS compliant SSH kex algorithm selected: %v", err)
}
err = utils.StringSliceSubset(defaults.FIPSMACAlgorithms, cfg.MACAlgorithms)
if err != nil {
return trace.BadParameter("non-FIPS compliant SSH mac algorithm selected: %v", err)
}
// Make sure cluster settings are also FedRAMP/FIPS 140-2 compliant.
if cfg.Auth.Enabled {
// Only SSO based authentication is supported. The SSO provider is where
// any FedRAMP/FIPS 140-2 compliance (like password complexity) should be
// enforced.
if cfg.Auth.Preference.GetAllowLocalAuth() {
return trace.BadParameter("non-FIPS compliant authentication setting: \"local_auth\" must be false")
}
// If sessions are being recorded at the proxy host key checking must be
// enabled. This make sure the host certificate key algorithm is FIPS
// compliant.
if services.IsRecordAtProxy(cfg.Auth.SessionRecordingConfig.GetMode()) &&
!cfg.Auth.SessionRecordingConfig.GetProxyChecksHostKeys() {
return trace.BadParameter("non-FIPS compliant proxy settings: \"proxy_checks_host_keys\" must be true")
}
}
}
// apply --skip-version-check flag.
if clf.SkipVersionCheck {
cfg.SkipVersionCheck = clf.SkipVersionCheck
}
// Apply diagnostic address flag.
if clf.DiagnosticAddr != "" {
addr, err := utils.ParseAddr(clf.DiagnosticAddr)
if err != nil {
return trace.Wrap(err, "failed to parse diag-addr")
}
cfg.DiagnosticAddr = *addr
}
// apply --insecure-no-tls flag:
if clf.DisableTLS {
cfg.Proxy.DisableTLS = clf.DisableTLS
}
// apply --debug flag to config:
if clf.Debug {
cfg.Console = io.Discard
cfg.Debug = clf.Debug
}
// apply --roles flag:
if clf.Roles != "" {
if err := validateRoles(clf.Roles); err != nil {
return trace.Wrap(err)
}
cfg.SSH.Enabled = strings.Contains(clf.Roles, defaults.RoleNode)
cfg.Auth.Enabled = strings.Contains(clf.Roles, defaults.RoleAuthService)
cfg.Proxy.Enabled = strings.Contains(clf.Roles, defaults.RoleProxy)
cfg.Apps.Enabled = strings.Contains(clf.Roles, defaults.RoleApp)
cfg.Databases.Enabled = strings.Contains(clf.Roles, defaults.RoleDatabase)
}
// apply --auth-server flag:
if len(clf.AuthServerAddr) > 0 {
if cfg.Auth.Enabled {
log.Warnf("not starting the local auth service. --auth-server flag tells to connect to another auth server")
cfg.Auth.Enabled = false
}
authServerAddresses := make([]utils.NetAddr, 0, len(clf.AuthServerAddr))
for _, as := range clf.AuthServerAddr {
addr, err := utils.ParseHostPortAddr(as, defaults.AuthListenPort)
if err != nil {
return trace.BadParameter("cannot parse auth server address: '%v'", as)
}
authServerAddresses = append(authServerAddresses, *addr)
}
if err := cfg.SetAuthServerAddresses(authServerAddresses); err != nil {
return trace.Wrap(err)
}
}
// apply --name flag:
if clf.NodeName != "" {
cfg.Hostname = clf.NodeName
}
// apply --pid-file flag
if clf.PIDFile != "" {
cfg.PIDFile = clf.PIDFile
}
if clf.AuthToken != "" {
// store the value of the --token flag:
cfg.SetToken(clf.AuthToken)
}
// Apply flags used for the node to validate the Auth Server.
if err = cfg.ApplyCAPins(clf.CAPins); err != nil {
return trace.Wrap(err)
}
// apply --listen-ip flag:
if clf.ListenIP != nil {
applyListenIP(clf.ListenIP, cfg)
}
// --advertise-ip flag
if clf.AdvertiseIP != "" {
if _, _, err := utils.ParseAdvertiseAddr(clf.AdvertiseIP); err != nil {
return trace.Wrap(err)
}
cfg.AdvertiseIP = clf.AdvertiseIP
}
// apply --labels flag
if err := parseLabelsApply(clf.Labels, &cfg.SSH); err != nil {
return trace.Wrap(err)
}
// --pid-file:
if clf.PIDFile != "" {
cfg.PIDFile = clf.PIDFile
}
// auth_servers not configured, but the 'auth' is enabled (auth is on localhost)?
if len(cfg.AuthServerAddresses()) == 0 && cfg.Auth.Enabled {
cfg.SetAuthServerAddress(cfg.Auth.ListenAddr)
}
// add data_dir to the backend config:
if cfg.Auth.StorageConfig.Params == nil {
cfg.Auth.StorageConfig.Params = backend.Params{}
}
cfg.Auth.StorageConfig.Params["data_dir"] = cfg.DataDir
// command line flag takes precedence over file config
if clf.PermitUserEnvironment {
cfg.SSH.PermitUserEnvironment = true
}
// set the default proxy listener addresses for config v1, if not already set
applyDefaultProxyListenerAddresses(cfg)
return nil
}
// parseLabels parses the labels command line flag and returns static and
// dynamic labels.
func parseLabels(spec string) (map[string]string, services.CommandLabels, error) {
// Base syntax parsing, the spec must be in the form of 'key=value,more="better"'.
lmap, err := client.ParseLabelSpec(spec)
if err != nil {
return nil, nil, trace.Wrap(err)
}
static := make(map[string]string)
dynamic := make(services.CommandLabels)
if len(lmap) == 0 {
return static, dynamic, nil
}
// Loop over all parsed labels and set either static or dynamic labels.
for key, value := range lmap {
dynamicLabel, err := isCmdLabelSpec(value)
if err != nil {
return nil, nil, trace.Wrap(err)
}
if dynamicLabel != nil {
dynamic[key] = dynamicLabel
} else {
static[key] = value
}
}
return static, dynamic, nil
}
// parseLabelsApply reads in the labels command line flag and tries to
// correctly populate static and dynamic labels for the SSH service.
func parseLabelsApply(spec string, sshConf *servicecfg.SSHConfig) error {
if spec == "" {
return nil
}
var err error
sshConf.Labels, sshConf.CmdLabels, err = parseLabels(spec)
if err != nil {
return trace.Wrap(err)
}
return nil
}
// isCmdLabelSpec tries to interpret a given string as a "command label" spec.
// A command label spec looks like [time_duration:command param1 param2 ...] where
// time_duration is in "1h2m1s" form.
//
// Example of a valid spec: "[1h:/bin/uname -m]"
func isCmdLabelSpec(spec string) (types.CommandLabel, error) {
// command spec? (surrounded by brackets?)
if len(spec) > 5 && spec[0] == '[' && spec[len(spec)-1] == ']' {
invalidSpecError := trace.BadParameter(
"invalid command label spec: '%s'", spec)
spec = strings.Trim(spec, "[]")
idx := strings.IndexRune(spec, ':')
if idx < 0 {
return nil, trace.Wrap(invalidSpecError)
}
periodSpec := spec[:idx]
period, err := time.ParseDuration(periodSpec)
if err != nil {
return nil, trace.Wrap(invalidSpecError)
}
cmdSpec := spec[idx+1:]
if len(cmdSpec) < 1 {
return nil, trace.Wrap(invalidSpecError)
}
openQuote := false
return &types.CommandLabelV2{
Period: types.NewDuration(period),
Command: strings.FieldsFunc(cmdSpec, func(c rune) bool {
if c == '"' {
openQuote = !openQuote
}
return unicode.IsSpace(c) && !openQuote
}),
}, nil
}
// not a valid spec
return nil, nil
}
// applyListenIP replaces all 'listen addr' settings for all services with
// a given IP
func applyListenIP(ip net.IP, cfg *servicecfg.Config) {
listeningAddresses := []*utils.NetAddr{
&cfg.Auth.ListenAddr,
&cfg.Auth.ListenAddr,
&cfg.Proxy.SSHAddr,
&cfg.Proxy.WebAddr,
&cfg.SSH.Addr,
&cfg.Proxy.ReverseTunnelListenAddr,
}
for _, addr := range listeningAddresses {
replaceHost(addr, ip.String())
}
}
// replaceHost takes utils.NetAddr and replaces the hostname in it, preserving
// the original port
func replaceHost(addr *utils.NetAddr, newHost string) {
_, port, err := net.SplitHostPort(addr.Addr)
if err != nil {
log.Errorf("failed parsing address: '%v'", addr.Addr)
}
addr.Addr = net.JoinHostPort(newHost, port)
}
// validateRoles makes sure that value passed to the --roles flag is valid
func validateRoles(roles string) error {
for _, role := range splitRoles(roles) {
switch role {
case defaults.RoleAuthService,
defaults.RoleNode,
defaults.RoleProxy,
defaults.RoleApp,
defaults.RoleDatabase,
defaults.RoleWindowsDesktop,
defaults.RoleDiscovery:
default:
return trace.Errorf("unknown role: '%s'", role)
}
}
return nil
}
// splitRoles splits in the format roles expects.
func splitRoles(roles string) []string {
return strings.Split(roles, ",")
}
// applyTokenConfig applies the auth_token and join_params to the config
func applyTokenConfig(fc *FileConfig, cfg *servicecfg.Config) error {
if fc.AuthToken != "" {
if fc.JoinParams != (JoinParams{}) {
return trace.BadParameter("only one of auth_token or join_params should be set")
}
cfg.JoinMethod = types.JoinMethodToken
cfg.SetToken(fc.AuthToken)
return nil
}
if fc.JoinParams != (JoinParams{}) {
cfg.SetToken(fc.JoinParams.TokenName)
if err := types.ValidateJoinMethod(fc.JoinParams.Method); err != nil {
return trace.Wrap(err)
}
cfg.JoinMethod = fc.JoinParams.Method
if fc.JoinParams.Azure != (AzureJoinParams{}) {
cfg.JoinParams = servicecfg.JoinParams{
Azure: servicecfg.AzureJoinParams{
ClientID: fc.JoinParams.Azure.ClientID,
},
}
}
}
return nil
}