mirror of
https://github.com/gravitational/teleport.git
synced 2026-10-11 22:49:54 +00:00
305 lines
10 KiB
Go
305 lines
10 KiB
Go
/*
|
|
Copyright 2015-2017 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 common
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"os"
|
|
"os/user"
|
|
"path/filepath"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/gravitational/teleport"
|
|
"github.com/gravitational/teleport/lib/config"
|
|
"github.com/gravitational/teleport/lib/defaults"
|
|
"github.com/gravitational/teleport/lib/service"
|
|
"github.com/gravitational/teleport/lib/srv"
|
|
"github.com/gravitational/teleport/lib/sshutils/scp"
|
|
"github.com/gravitational/teleport/lib/utils"
|
|
|
|
"github.com/gravitational/trace"
|
|
|
|
log "github.com/sirupsen/logrus"
|
|
)
|
|
|
|
// Options combines init/start teleport options
|
|
type Options struct {
|
|
// Args is a list of command-line args passed from main()
|
|
Args []string
|
|
// InitOnly when set to true, initializes config and aux
|
|
// endpoints but does not start the process
|
|
InitOnly bool
|
|
}
|
|
|
|
// Run inits/starts the process according to the provided options
|
|
func Run(options Options) (executedCommand string, conf *service.Config) {
|
|
var err error
|
|
|
|
// configure trace's errors to produce full stack traces
|
|
isDebug, _ := strconv.ParseBool(os.Getenv(teleport.VerboseLogsEnvVar))
|
|
if isDebug {
|
|
trace.SetDebug(true)
|
|
}
|
|
// configure logger for a typical CLI scenario until configuration file is
|
|
// parsed
|
|
utils.InitLogger(utils.LoggingForDaemon, log.ErrorLevel)
|
|
app := utils.InitCLIParser("teleport", "Clustered SSH service. Learn more at https://goteleport.com/teleport")
|
|
|
|
// define global flags:
|
|
var ccf config.CommandLineFlags
|
|
var scpFlags scp.Flags
|
|
|
|
// define commands:
|
|
start := app.Command("start", "Starts the Teleport service.")
|
|
status := app.Command("status", "Print the status of the current SSH session.")
|
|
dump := app.Command("configure", "Print the sample config file into stdout.")
|
|
ver := app.Command("version", "Print the version.")
|
|
scpc := app.Command("scp", "Server-side implementation of SCP.").Hidden()
|
|
exec := app.Command("exec", "Used internally by Teleport to re-exec itself to run a command.").Hidden()
|
|
forward := app.Command("forward", "Used internally by Teleport to re-exec itself to port forward.").Hidden()
|
|
app.HelpFlag.Short('h')
|
|
|
|
// define start flags:
|
|
start.Flag("debug", "Enable verbose logging to stderr").
|
|
Short('d').
|
|
BoolVar(&ccf.Debug)
|
|
start.Flag("insecure-no-tls", "Disable TLS for the web socket").
|
|
BoolVar(&ccf.DisableTLS)
|
|
start.Flag("roles",
|
|
fmt.Sprintf("Comma-separated list of roles to start with [%s]", strings.Join(defaults.StartRoles, ","))).
|
|
Short('r').
|
|
StringVar(&ccf.Roles)
|
|
start.Flag("pid-file",
|
|
"Full path to the PID file. By default no PID file will be created").StringVar(&ccf.PIDFile)
|
|
start.Flag("advertise-ip",
|
|
"IP to advertise to clients if running behind NAT").
|
|
StringVar(&ccf.AdvertiseIP)
|
|
start.Flag("listen-ip",
|
|
fmt.Sprintf("IP address to bind to [%s]", defaults.BindIP)).
|
|
Short('l').
|
|
IPVar(&ccf.ListenIP)
|
|
start.Flag("auth-server",
|
|
fmt.Sprintf("Address of the auth server [%s]", defaults.AuthConnectAddr().Addr)).
|
|
StringsVar(&ccf.AuthServerAddr)
|
|
start.Flag("token",
|
|
"Invitation token to register with an auth server [none]").
|
|
StringVar(&ccf.AuthToken)
|
|
start.Flag("ca-pin",
|
|
"CA pin to validate the Auth Server").
|
|
StringVar(&ccf.CAPin)
|
|
start.Flag("nodename",
|
|
"Name of this node, defaults to hostname").
|
|
StringVar(&ccf.NodeName)
|
|
start.Flag("config",
|
|
fmt.Sprintf("Path to a configuration file [%v]", defaults.ConfigFilePath)).
|
|
Short('c').ExistingFileVar(&ccf.ConfigFile)
|
|
start.Flag("bootstrap",
|
|
"Path to bootstrap file (ignored if already initialized)").ExistingFileVar(&ccf.BootstrapFile)
|
|
start.Flag("config-string",
|
|
"Base64 encoded configuration string").Hidden().Envar(defaults.ConfigEnvar).
|
|
StringVar(&ccf.ConfigString)
|
|
start.Flag("labels", "List of labels for this node").StringVar(&ccf.Labels)
|
|
start.Flag("diag-addr",
|
|
"Start diangonstic prometheus and healthz endpoint.").Hidden().StringVar(&ccf.DiagnosticAddr)
|
|
start.Flag("permit-user-env",
|
|
"Enables reading of ~/.tsh/environment when creating a session").BoolVar(&ccf.PermitUserEnvironment)
|
|
start.Flag("insecure",
|
|
"Insecure mode disables certificate validation").BoolVar(&ccf.InsecureMode)
|
|
start.Flag("fips",
|
|
"Start Teleport in FedRAMP/FIPS 140-2 mode.").
|
|
Default("false").
|
|
BoolVar(&ccf.FIPS)
|
|
start.Flag("app-name",
|
|
"Name of the application to start").
|
|
StringVar(&ccf.AppName)
|
|
start.Flag("app-uri",
|
|
"Internal address of the application to proxy.").
|
|
StringVar(&ccf.AppURI)
|
|
start.Flag("app-public-addr",
|
|
"Public address of the application to proxy.").
|
|
StringVar(&ccf.AppPublicAddr)
|
|
|
|
// define start's usage info (we use kingpin's "alias" field for this)
|
|
start.Alias(usageNotes + usageExamples)
|
|
|
|
// define a hidden 'scp' command (it implements server-side implementation of handling
|
|
// 'scp' requests)
|
|
scpc.Flag("t", "sink mode (data consumer)").Short('t').Default("false").BoolVar(&scpFlags.Sink)
|
|
scpc.Flag("f", "source mode (data producer)").Short('f').Default("false").BoolVar(&scpFlags.Source)
|
|
scpc.Flag("v", "verbose mode").Default("false").Short('v').BoolVar(&scpFlags.Verbose)
|
|
scpc.Flag("r", "recursive mode").Default("false").Short('r').BoolVar(&scpFlags.Recursive)
|
|
scpc.Flag("d", "directory mode").Short('d').Hidden().BoolVar(&scpFlags.DirectoryMode)
|
|
scpc.Flag("remote-addr", "address of the remote client").StringVar(&scpFlags.RemoteAddr)
|
|
scpc.Flag("local-addr", "local address which accepted the request").StringVar(&scpFlags.LocalAddr)
|
|
scpc.Arg("target", "").StringsVar(&scpFlags.Target)
|
|
|
|
// parse CLI commands+flags:
|
|
command, err := app.Parse(options.Args)
|
|
if err != nil {
|
|
utils.FatalError(err)
|
|
}
|
|
|
|
// Create default configuration.
|
|
conf = service.MakeDefaultConfig()
|
|
|
|
// If FIPS mode is specified update defaults to be FIPS appropriate.
|
|
if ccf.FIPS {
|
|
service.ApplyFIPSDefaults(conf)
|
|
}
|
|
|
|
// execute the selected command unless we're running tests
|
|
switch command {
|
|
case start.FullCommand():
|
|
// configuration merge: defaults -> file-based conf -> CLI conf
|
|
if err = config.Configure(&ccf, conf); err != nil {
|
|
utils.FatalError(err)
|
|
}
|
|
if !options.InitOnly {
|
|
err = OnStart(conf)
|
|
}
|
|
case scpc.FullCommand():
|
|
err = onSCP(&scpFlags)
|
|
case status.FullCommand():
|
|
err = onStatus()
|
|
case dump.FullCommand():
|
|
err = onConfigDump()
|
|
case exec.FullCommand():
|
|
err = onExec()
|
|
case forward.FullCommand():
|
|
err = onForward()
|
|
case ver.FullCommand():
|
|
utils.PrintVersion()
|
|
}
|
|
if err != nil {
|
|
utils.FatalError(err)
|
|
}
|
|
return command, conf
|
|
}
|
|
|
|
// OnStart is the handler for "start" CLI command
|
|
func OnStart(config *service.Config) error {
|
|
return service.Run(context.TODO(), *config, nil)
|
|
}
|
|
|
|
// onStatus is the handler for "status" CLI command
|
|
func onStatus() error {
|
|
sshClient := os.Getenv("SSH_CLIENT")
|
|
systemUser := os.Getenv("USER")
|
|
teleportUser := os.Getenv(teleport.SSHTeleportUser)
|
|
proxyHost := os.Getenv(teleport.SSHSessionWebproxyAddr)
|
|
clusterName := os.Getenv(teleport.SSHTeleportClusterName)
|
|
hostUUID := os.Getenv(teleport.SSHTeleportHostUUID)
|
|
sid := os.Getenv(teleport.SSHSessionID)
|
|
|
|
if sid == "" || proxyHost == "" {
|
|
fmt.Println("You are not inside of a Teleport SSH session")
|
|
return nil
|
|
}
|
|
|
|
fmt.Printf("User ID : %s, logged in as %s from %s\n", teleportUser, systemUser, sshClient)
|
|
fmt.Printf("Cluster Name: %s\n", clusterName)
|
|
fmt.Printf("Host UUID : %s\n", hostUUID)
|
|
fmt.Printf("Session ID : %s\n", sid)
|
|
fmt.Printf("Session URL : https://%s/web/cluster/%v/node/%v/%v/%v\n", proxyHost, clusterName, hostUUID, systemUser, sid)
|
|
|
|
return nil
|
|
}
|
|
|
|
// onConfigDump is the handler for "configure" CLI command
|
|
func onConfigDump() error {
|
|
sfc, err := config.MakeSampleFileConfig()
|
|
if err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
fmt.Printf("%s\n%s\n", sampleConfComment, sfc.DebugDumpToYAML())
|
|
return nil
|
|
}
|
|
|
|
// onSCP implements handling of 'scp' requests on the server side. When the teleport SSH daemon
|
|
// receives an SSH "scp" request, it launches itself with 'scp' flag under the requested
|
|
// user's privileges
|
|
//
|
|
// This is the entry point of "teleport scp" call (the parent process is the teleport daemon)
|
|
func onSCP(scpFlags *scp.Flags) (err error) {
|
|
// when 'teleport scp' is executed, it cannot write logs to stderr (because
|
|
// they're automatically replayed by the scp client)
|
|
utils.SwitchLoggingtoSyslog()
|
|
if len(scpFlags.Target) == 0 {
|
|
return trace.BadParameter("teleport scp: missing an argument")
|
|
}
|
|
|
|
// get user's home dir (it serves as a default destination)
|
|
user, err := user.Current()
|
|
if err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
// see if the target is absolute. if not, use user's homedir to make
|
|
// it absolute (and if the user doesn't have a homedir, use "/")
|
|
target := scpFlags.Target[0]
|
|
if !filepath.IsAbs(target) {
|
|
if !utils.IsDir(user.HomeDir) {
|
|
slash := string(filepath.Separator)
|
|
scpFlags.Target[0] = slash + target
|
|
} else {
|
|
scpFlags.Target[0] = filepath.Join(user.HomeDir, target)
|
|
}
|
|
}
|
|
if !scpFlags.Source && !scpFlags.Sink {
|
|
return trace.Errorf("remote mode is not supported")
|
|
}
|
|
|
|
scpCfg := scp.Config{
|
|
Flags: *scpFlags,
|
|
User: user.Username,
|
|
RunOnServer: true,
|
|
}
|
|
|
|
cmd, err := scp.CreateCommand(scpCfg)
|
|
if err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
|
|
return trace.Wrap(cmd.Execute(&StdReadWriter{}))
|
|
}
|
|
|
|
// onExec is a subcommand used to re-execute Teleport for execution. Used for
|
|
// "exec" or "shell" requests over a "session" channel on Teleport nodes.
|
|
func onExec() error {
|
|
srv.RunAndExit(teleport.ExecSubCommand)
|
|
return nil
|
|
}
|
|
|
|
// onForward is a subcommand used to re-execute Teleport for port forwarding.
|
|
// Used with "direct-tcpip" channel on Teleport nodes.
|
|
func onForward() error {
|
|
srv.RunAndExit(teleport.ForwardSubCommand)
|
|
return nil
|
|
}
|
|
|
|
type StdReadWriter struct {
|
|
}
|
|
|
|
func (rw *StdReadWriter) Read(b []byte) (int, error) {
|
|
return os.Stdin.Read(b)
|
|
}
|
|
|
|
func (rw *StdReadWriter) Write(b []byte) (int, error) {
|
|
return os.Stdout.Write(b)
|
|
}
|