diff --git a/lib/tbot/config/config.go b/lib/tbot/config/config.go index 1b3435119e90f..94a6f588b784f 100644 --- a/lib/tbot/config/config.go +++ b/lib/tbot/config/config.go @@ -100,6 +100,7 @@ type CLIConf struct { // AuthServer is a Teleport auth server address. It may either point // directly to an auth server, or to a Teleport proxy server in which case // a tunneled auth connection will be established. + // Prefer using Address() to pick an address. AuthServer string // DataDir stores the bot's internal data. @@ -154,9 +155,10 @@ type CLIConf struct { // should be written to ConfigureOutput string - // Proxy is the teleport proxy address. Unlike `AuthServer` this must + // ProxyServer is the teleport proxy address. Unlike `AuthServer` this must // explicitly point to a Teleport proxy. - Proxy string + // Example: "example.teleport.sh:443" + ProxyServer string // Cluster is the name of the Teleport cluster on which resources should // be accessed. @@ -267,8 +269,12 @@ type BotConfig struct { Outputs Outputs `yaml:"outputs,omitempty"` Services ServiceConfigs `yaml:"services,omitempty"` - Debug bool `yaml:"debug"` - AuthServer string `yaml:"auth_server"` + Debug bool `yaml:"debug"` + AuthServer string `yaml:"auth_server,omitempty"` + // ProxyServer is the teleport proxy address. Unlike `AuthServer` this must + // explicitly point to a Teleport proxy. + // Example: "example.teleport.sh:443" + ProxyServer string `yaml:"proxy_server,omitempty"` CertificateTTL time.Duration `yaml:"certificate_ttl"` RenewalInterval time.Duration `yaml:"renewal_interval"` Oneshot bool `yaml:"oneshot"` @@ -292,6 +298,30 @@ type BotConfig struct { Insecure bool `yaml:"insecure,omitempty"` } +type AddressKind string + +const ( + AddressKindUnspecified AddressKind = "" + AddressKindProxy AddressKind = "proxy" + AddressKindAuth AddressKind = "auth" +) + +// Address returns the address to the auth server, either directly or via +// a proxy, and the kind of address it is. +func (conf *BotConfig) Address() (string, AddressKind) { + switch { + case conf.AuthServer != "" && conf.ProxyServer != "": + // This is an error case that should be prevented by the validation. + return "", AddressKindUnspecified + case conf.ProxyServer != "": + return conf.ProxyServer, AddressKindProxy + case conf.AuthServer != "": + return conf.AuthServer, AddressKindAuth + default: + return "", AddressKindUnspecified + } +} + func (conf *BotConfig) CipherSuites() []uint16 { if conf.FIPS { return defaults.FIPSCipherSuites @@ -641,6 +671,13 @@ func FromCLIConf(cf *CLIConf) (*BotConfig, error) { config.AuthServer = cf.AuthServer } + if cf.ProxyServer != "" { + if config.ProxyServer != "" { + log.Warnf("CLI parameters are overriding proxy configured in %s", cf.ConfigPath) + } + config.ProxyServer = cf.ProxyServer + } + if cf.CertificateTTL != 0 { if config.CertificateTTL != 0 { log.Warnf("CLI parameters are overriding certificate TTL configured in %s", cf.ConfigPath) diff --git a/lib/tbot/config/config_test.go b/lib/tbot/config/config_test.go index 47ff55df8c014..43293300c227b 100644 --- a/lib/tbot/config/config_test.go +++ b/lib/tbot/config/config_test.go @@ -284,6 +284,20 @@ func TestBotConfig_YAML(t *testing.T) { }, }, }, + { + name: "minimal config using proxy addr", + in: BotConfig{ + Version: V2, + ProxyServer: "example.teleport.sh:443", + CertificateTTL: time.Minute, + RenewalInterval: time.Second * 30, + Outputs: Outputs{ + &IdentityOutput{ + Destination: &DestinationMemory{}, + }, + }, + }, + }, } testYAML(t, tests) diff --git a/lib/tbot/config/template_ssh_client.go b/lib/tbot/config/template_ssh_client.go index a17768b3153e0..2cea0a1202012 100644 --- a/lib/tbot/config/template_ssh_client.go +++ b/lib/tbot/config/template_ssh_client.go @@ -103,14 +103,14 @@ func (c *templateSSHClient) render( ) defer span.End() - ping, err := bot.AuthPing(ctx) + ping, err := bot.ProxyPing(ctx) if err != nil { return trace.Wrap(err) } - proxyHost, proxyPort, err := utils.SplitHostPort(ping.ProxyPublicAddr) + proxyHost, proxyPort, err := utils.SplitHostPort(ping.Proxy.SSH.PublicAddr) if err != nil { - return trace.BadParameter("proxy %+v has no usable public address: %v", ping.ProxyPublicAddr, err) + return trace.BadParameter("proxy %+v has no usable public address: %v", ping.Proxy.SSH.PublicAddr, err) } clusterNames, err := getClusterNames(bot, ping.ClusterName) diff --git a/lib/tbot/config/testdata/TestBotConfig_YAML/minimal_config_using_proxy_addr.golden b/lib/tbot/config/testdata/TestBotConfig_YAML/minimal_config_using_proxy_addr.golden new file mode 100644 index 0000000000000..9328ff75099bb --- /dev/null +++ b/lib/tbot/config/testdata/TestBotConfig_YAML/minimal_config_using_proxy_addr.golden @@ -0,0 +1,11 @@ +version: v2 +outputs: + - type: identity + destination: + type: memory +debug: false +proxy_server: example.teleport.sh:443 +certificate_ttl: 1m0s +renewal_interval: 30s +oneshot: false +fips: false diff --git a/lib/tbot/service_bot_identity.go b/lib/tbot/service_bot_identity.go index cab80cba65dae..f7f9868603356 100644 --- a/lib/tbot/service_bot_identity.go +++ b/lib/tbot/service_bot_identity.go @@ -382,10 +382,6 @@ func botIdentityFromToken(ctx context.Context, log logrus.FieldLogger, cfg *conf defer span.End() log.Info("Fetching bot identity using token.") - addr, err := utils.ParseAddr(cfg.AuthServer) - if err != nil { - return nil, trace.Wrap(err, "invalid auth server address %+v", cfg.AuthServer) - } tlsPrivateKey, sshPublicKey, tlsPublicKey, err := generateKeys() if err != nil { @@ -403,7 +399,6 @@ func botIdentityFromToken(ctx context.Context, log logrus.FieldLogger, cfg *conf ID: auth.IdentityID{ Role: types.RoleBot, }, - AuthServers: []utils.NetAddr{*addr}, PublicTLSKey: tlsPublicKey, PublicSSHKey: sshPublicKey, CAPins: cfg.Onboarding.CAPins, @@ -416,6 +411,24 @@ func botIdentityFromToken(ctx context.Context, log logrus.FieldLogger, cfg *conf Insecure: cfg.Insecure, } + addr, addrKind := cfg.Address() + switch addrKind { + case config.AddressKindAuth: + parsed, err := utils.ParseAddr(addr) + if err != nil { + return nil, trace.Wrap(err, "failed to parse addr") + } + params.AuthServers = []utils.NetAddr{*parsed} + case config.AddressKindProxy: + parsed, err := utils.ParseAddr(addr) + if err != nil { + return nil, trace.Wrap(err, "failed to parse addr") + } + params.ProxyServer = *parsed + default: + return nil, trace.BadParameter("unsupported address kind: %v", addrKind) + } + if params.JoinMethod == types.JoinMethodAzure { params.AzureParams = auth.AzureParams{ ClientID: cfg.Onboarding.Azure.ClientID, diff --git a/lib/tbot/service_outputs.go b/lib/tbot/service_outputs.go index 6651a70417a48..ad17d7487050f 100644 --- a/lib/tbot/service_outputs.go +++ b/lib/tbot/service_outputs.go @@ -724,18 +724,27 @@ func (orc *outputRenewalCache) proxyPing(ctx context.Context) (*webclient.PingRe return orc._proxyPong, nil } - // Note: this relies on the auth server's proxy address. We could - // potentially support some manual parameter here in the future if desired. - authPong, err := orc.authPing(ctx) - if err != nil { - return nil, trace.Wrap(err) + // Determine the Proxy address to use. + addr, addrKind := orc.cfg.Address() + switch addrKind { + case config.AddressKindAuth: + // If the address is an auth address, ping auth to determine proxy addr. + authPong, err := orc.authPing(ctx) + if err != nil { + return nil, trace.Wrap(err) + } + addr = authPong.ProxyPublicAddr + case config.AddressKindProxy: + // If the address is a proxy address, use it directly. + default: + return nil, trace.BadParameter("unsupported address kind: %v", addrKind) } // We use find instead of Ping as it's less resource intense and we can // ping the AuthServer directly for its configuration if necessary. proxyPong, err := webclient.Find(&webclient.Config{ Context: ctx, - ProxyAddr: authPong.ProxyPublicAddr, + ProxyAddr: addr, Insecure: orc.cfg.Insecure, }) if err != nil { diff --git a/lib/tbot/tbot.go b/lib/tbot/tbot.go index 6b09685653a21..77013e29a6d1a 100644 --- a/lib/tbot/tbot.go +++ b/lib/tbot/tbot.go @@ -132,11 +132,12 @@ func (b *Bot) Run(ctx context.Context) error { return trace.Wrap(err) } + addr, _ := b.cfg.Address() resolver, err := reversetunnelclient.CachingResolver( ctx, reversetunnelclient.WebClientResolver(&webclient.Config{ Context: ctx, - ProxyAddr: b.cfg.AuthServer, + ProxyAddr: addr, Insecure: b.cfg.Insecure, }), nil /* clock */) @@ -300,11 +301,16 @@ func (b *Bot) preRunChecks(ctx context.Context) (func() error, error) { ctx, span := tracer.Start(ctx, "Bot/preRunChecks") defer span.End() - if b.cfg.AuthServer == "" { + switch _, addrKind := b.cfg.Address(); addrKind { + case config.AddressKindUnspecified: return nil, trace.BadParameter( - "an auth or proxy server must be set via --auth-server or configuration", + "either a proxy or auth address must be set using --proxy, --auth-server or configuration", ) + case config.AddressKindAuth: + // TODO(noah): DELETE IN V17.0.0 + b.log.Warn("We recently introduced the ability to explicitly configure the address of the Teleport Proxy using --proxy-server. We recommend switching to this if you currently provide the address of the Proxy to --auth-server.") } + // Ensure they have provided a join method. if b.cfg.Onboarding.JoinMethod == types.JoinMethodUnspecified { return nil, trace.BadParameter("join method must be provided") @@ -427,15 +433,18 @@ func clientForFacade( return nil, trace.Wrap(err) } - authAddr, err := utils.ParseAddr(cfg.AuthServer) + addr, _ := cfg.Address() + parsedAddr, err := utils.ParseAddr(addr) if err != nil { return nil, trace.Wrap(err) } authClientConfig := &authclient.Config{ - TLS: tlsConfig, - SSH: sshConfig, - AuthServers: []utils.NetAddr{*authAddr}, + TLS: tlsConfig, + SSH: sshConfig, + // TODO(noah): It'd be ideal to distinguish the proxy addr and auth addr + // here to avoid pointlessly hitting the address as an auth server. + AuthServers: []utils.NetAddr{*parsedAddr}, Log: log, Insecure: cfg.Insecure, Resolver: resolver, diff --git a/tool/tbot/db.go b/tool/tbot/db.go index cb9091fae63de..2b38fa401b493 100644 --- a/tool/tbot/db.go +++ b/tool/tbot/db.go @@ -54,7 +54,7 @@ func onDBCommand(botConfig *config.BotConfig, cf *config.CLIConf) error { return trace.Wrap(err) } - args := []string{"-i", identityPath, "db", "--proxy=" + cf.Proxy} + args := []string{"-i", identityPath, "db", "--proxy=" + cf.ProxyServer} if cf.Cluster != "" { // If we caught --cluster in our args, pass it through. args = append(args, "--cluster="+cf.Cluster) diff --git a/tool/tbot/main.go b/tool/tbot/main.go index f932ed83d5c5e..0a581c92c48fc 100644 --- a/tool/tbot/main.go +++ b/tool/tbot/main.go @@ -47,8 +47,9 @@ var log = logrus.WithFields(logrus.Fields{ }) const ( - authServerEnvVar = "TELEPORT_AUTH_SERVER" - tokenEnvVar = "TELEPORT_BOT_TOKEN" + authServerEnvVar = "TELEPORT_AUTH_SERVER" + tokenEnvVar = "TELEPORT_BOT_TOKEN" + proxyServerEnvVar = "TELEPORT_PROXY" ) func main() { @@ -83,7 +84,8 @@ func Run(args []string, stdout io.Writer) error { versionCmd := app.Command("version", "Print the version of your tbot binary.") startCmd := app.Command("start", "Starts the renewal bot, writing certificates to the data dir at a set interval.") - startCmd.Flag("auth-server", "Address of the Teleport Auth Server or Proxy Server.").Short('a').Envar(authServerEnvVar).StringVar(&cf.AuthServer) + startCmd.Flag("auth-server", "Address of the Teleport Auth Server. Prefer using --proxy-server where possible.").Short('a').Envar(authServerEnvVar).StringVar(&cf.AuthServer) + startCmd.Flag("proxy-server", "Address of the Teleport Proxy Server.").Envar(proxyServerEnvVar).StringVar(&cf.ProxyServer) startCmd.Flag("token", "A bot join token or path to file with token value, if attempting to onboard a new bot; used on first connect.").Envar(tokenEnvVar).StringVar(&cf.Token) startCmd.Flag("ca-pin", "CA pin to validate the Teleport Auth Server; used on first connect.").StringsVar(&cf.CAPins) startCmd.Flag("data-dir", "Directory to store internal bot data. Access to this directory should be limited.").StringVar(&cf.DataDir) @@ -110,7 +112,8 @@ func Run(args []string, stdout io.Writer) error { EnumVar(&cf.LogFormat, utils.LogFormatJSON, utils.LogFormatText) configureCmd := app.Command("configure", "Creates a config file based on flags provided, and writes it to stdout or a file (-c ).") - configureCmd.Flag("auth-server", "Address of the Teleport Auth Server (On-Prem installs) or Proxy Server (Cloud installs).").Short('a').Envar(authServerEnvVar).StringVar(&cf.AuthServer) + configureCmd.Flag("auth-server", "Address of the Teleport Auth Server. Prefer using --proxy-server where possible.").Short('a').Envar(authServerEnvVar).StringVar(&cf.AuthServer) + configureCmd.Flag("proxy-server", "Address of the Teleport Proxy Server.").Envar(proxyServerEnvVar).StringVar(&cf.ProxyServer) configureCmd.Flag("ca-pin", "CA pin to validate the Teleport Auth Server; used on first connect.").StringsVar(&cf.CAPins) configureCmd.Flag("certificate-ttl", "TTL of short-lived machine certificates.").Default("60m").DurationVar(&cf.CertificateTTL) configureCmd.Flag("data-dir", "Directory to store internal bot data. Access to this directory should be limited.").StringVar(&cf.DataDir) @@ -127,8 +130,14 @@ func Run(args []string, stdout io.Writer) error { migrateCmd := app.Command("migrate", "Migrates a config file from an older version to the newest version. Outputs to stdout by default.") migrateCmd.Flag("output", "Path to write the generated configuration file to rather than write to stdout.").Short('o').StringVar(&cf.ConfigureOutput) + legacyProxyFlag := "" + dbCmd := app.Command("db", "Execute database commands through tsh.") - dbCmd.Flag("proxy", "The Teleport proxy server to use, in host:port form.").Required().StringVar(&cf.Proxy) + dbCmd.Flag("proxy-server", "The Teleport proxy server to use, in host:port form.").StringVar(&cf.ProxyServer) + // We're migrating from --proxy to --proxy-server so this flag is hidden + // but still supported. + // TODO(strideynet): DELETE IN 17.0.0 + dbCmd.Flag("proxy", "The Teleport proxy server to use, in host:port form.").Hidden().Envar(proxyServerEnvVar).StringVar(&legacyProxyFlag) dbCmd.Flag("destination-dir", "The destination directory with which to authenticate tsh").StringVar(&cf.DestinationDir) dbCmd.Flag("cluster", "The cluster name. Extracted from the certificate if unset.").StringVar(&cf.Cluster) dbRemaining := config.RemainingArgs(dbCmd.Arg( @@ -137,7 +146,11 @@ func Run(args []string, stdout io.Writer) error { )) proxyCmd := app.Command("proxy", "Start a local TLS proxy via tsh to connect to Teleport in single-port mode.") - proxyCmd.Flag("proxy", "The Teleport proxy server to use, in host:port form.").Required().StringVar(&cf.Proxy) + proxyCmd.Flag("proxy-server", "The Teleport proxy server to use, in host:port form.").Envar(proxyServerEnvVar).StringVar(&cf.ProxyServer) + // We're migrating from --proxy to --proxy-server so this flag is hidden + // but still supported. + // TODO(strideynet): DELETE IN 17.0.0 + proxyCmd.Flag("proxy", "The Teleport proxy server to use, in host:port form.").Hidden().StringVar(&legacyProxyFlag) proxyCmd.Flag("destination-dir", "The destination directory with which to authenticate tsh").StringVar(&cf.DestinationDir) proxyCmd.Flag("cluster", "The cluster name. Extracted from the certificate if unset.").StringVar(&cf.Cluster) proxyRemaining := config.RemainingArgs(proxyCmd.Arg( @@ -156,6 +169,11 @@ func Run(args []string, stdout io.Writer) error { return trace.Wrap(err) } + if legacyProxyFlag != "" { + cf.ProxyServer = legacyProxyFlag + log.Warn("The --proxy flag is deprecated and will be removed in v17.0.0. Use --proxy-server instead.") + } + // Remaining args are stored directly to a []string rather than written to // a shared ref like most other kingpin args, so we'll need to manually // move them to the remaining args field. diff --git a/tool/tbot/main_test.go b/tool/tbot/main_test.go index 67967f0f8ff6e..d45118d95c298 100644 --- a/tool/tbot/main_test.go +++ b/tool/tbot/main_test.go @@ -71,6 +71,12 @@ func TestRun_Configure(t *testing.T) { "--fips", }...), }, + { + name: "all parameters provided", + args: append(baseArgs, []string{ + "--proxy-server", "proxy.example.com:443", + }...), + }, } for _, tt := range tests { diff --git a/tool/tbot/proxy.go b/tool/tbot/proxy.go index b84a54da39cd2..450d6bb182dce 100644 --- a/tool/tbot/proxy.go +++ b/tool/tbot/proxy.go @@ -56,7 +56,7 @@ func onProxyCommand(botConfig *config.BotConfig, cf *config.CLIConf) error { // TODO(timothyb89): We could consider supporting a --cluster passthrough // here as in `tbot db ...`. - args := []string{"-i", identityPath, "proxy", "--proxy=" + cf.Proxy} + args := []string{"-i", identityPath, "proxy", "--proxy=" + cf.ProxyServer} args = append(args, cf.RemainingArgs...) // Pass through the debug flag, and prepend to satisfy argument ordering diff --git a/tool/tbot/testdata/TestRun_Configure/all_parameters_provided#01/file.golden b/tool/tbot/testdata/TestRun_Configure/all_parameters_provided#01/file.golden new file mode 100644 index 0000000000000..91469b967aa8c --- /dev/null +++ b/tool/tbot/testdata/TestRun_Configure/all_parameters_provided#01/file.golden @@ -0,0 +1,15 @@ +# tbot config file generated by `configure` command +version: v2 +onboarding: + join_method: token +storage: + type: directory + path: /var/lib/teleport/bot + symlinks: secure + acls: try +debug: false +proxy_server: proxy.example.com:443 +certificate_ttl: 1h0m0s +renewal_interval: 20m0s +oneshot: false +fips: false diff --git a/tool/tbot/testdata/TestRun_Configure/all_parameters_provided#01/stdout.golden b/tool/tbot/testdata/TestRun_Configure/all_parameters_provided#01/stdout.golden new file mode 100644 index 0000000000000..91469b967aa8c --- /dev/null +++ b/tool/tbot/testdata/TestRun_Configure/all_parameters_provided#01/stdout.golden @@ -0,0 +1,15 @@ +# tbot config file generated by `configure` command +version: v2 +onboarding: + join_method: token +storage: + type: directory + path: /var/lib/teleport/bot + symlinks: secure + acls: try +debug: false +proxy_server: proxy.example.com:443 +certificate_ttl: 1h0m0s +renewal_interval: 20m0s +oneshot: false +fips: false diff --git a/tool/tbot/testdata/TestRun_Configure/no_parameters_provided/file.golden b/tool/tbot/testdata/TestRun_Configure/no_parameters_provided/file.golden index db43e046b280d..b14ed6c58f811 100644 --- a/tool/tbot/testdata/TestRun_Configure/no_parameters_provided/file.golden +++ b/tool/tbot/testdata/TestRun_Configure/no_parameters_provided/file.golden @@ -8,7 +8,6 @@ storage: symlinks: secure acls: try debug: false -auth_server: "" certificate_ttl: 1h0m0s renewal_interval: 20m0s oneshot: false diff --git a/tool/tbot/testdata/TestRun_Configure/no_parameters_provided/stdout.golden b/tool/tbot/testdata/TestRun_Configure/no_parameters_provided/stdout.golden index db43e046b280d..b14ed6c58f811 100644 --- a/tool/tbot/testdata/TestRun_Configure/no_parameters_provided/stdout.golden +++ b/tool/tbot/testdata/TestRun_Configure/no_parameters_provided/stdout.golden @@ -8,7 +8,6 @@ storage: symlinks: secure acls: try debug: false -auth_server: "" certificate_ttl: 1h0m0s renewal_interval: 20m0s oneshot: false diff --git a/tool/tctl/common/bots_command.go b/tool/tctl/common/bots_command.go index 9878b25dfff27..4d092843ed98a 100644 --- a/tool/tctl/common/bots_command.go +++ b/tool/tctl/common/bots_command.go @@ -231,7 +231,7 @@ certificates: > tbot start \ --destination-dir=./tbot-user \ --token={{.token}} \ - --auth-server={{.addr}}{{if .join_method}} \ + --proxy-server={{.addr}}{{if .join_method}} \ --join-method={{.join_method}}{{end}} Please note: