func (app *App) makeServer(addr string, handler *http.Handler) (*http.Server, error) {
	server := &http.Server{
		Addr:    addr,
		Handler: *handler,
	}

	if app.options.EnableTLSClientAuth {
		caFile := gotty.ExpandHomeDir(app.options.TLSCACrtFile)
		log.Printf("CA file: " + caFile)
		caCert, err := ioutil.ReadFile(caFile)
		if err != nil {
			return nil, errors.New("Could not open CA crt file " + caFile)
		}
		caCertPool := x509.NewCertPool()
		if !caCertPool.AppendCertsFromPEM(caCert) {
			return nil, errors.New("Could not parse CA crt file data in " + caFile)
		}
		tlsConfig := &tls.Config{
			ClientCAs:  caCertPool,
			ClientAuth: tls.RequireAndVerifyClientCert,
		}
		server.TLSConfig = tlsConfig
	}

	return server, nil
}
Example #2
0
func main() {
	cmd := cli.NewApp()
	cmd.Version = "0.0.10"
	cmd.Name = "gotty"
	cmd.Usage = "Share your terminal as a web application"
	cmd.HideHelp = true

	flags := []flag{
		flag{"address", "a", "IP address to listen"},
		flag{"port", "p", "Port number to listen"},
		flag{"permit-write", "w", "Permit clients to write to the TTY (BE CAREFUL)"},
		flag{"credential", "c", "Credential for Basic Authentication (ex: user:pass, default disabled)"},
		flag{"random-url", "r", "Add a random string to the URL"},
		flag{"random-url-length", "", "Random URL length"},
		flag{"tls", "t", "Enable TLS/SSL"},
		flag{"tls-crt", "", "TLS/SSL crt file path"},
		flag{"tls-key", "", "TLS/SSL key file path"},
		flag{"index", "", "Custom index.html file"},
		flag{"title-format", "", "Title format of browser window"},
		flag{"reconnect", "", "Enable reconnection"},
		flag{"reconnect-time", "", "Time to reconnect"},
		flag{"once", "", "Accept only one client and exit on disconnection"},
		flag{"permit-arguments", "", "Allow to send arguments"},
	}

	mappingHint := map[string]string{
		"index":      "IndexFile",
		"tls":        "EnableTLS",
		"tls-crt":    "TLSCrtFile",
		"tls-key":    "TLSKeyFile",
		"random-url": "EnableRandomUrl",
		"reconnect":  "EnableReconnect",
	}

	cliFlags, err := generateFlags(flags, mappingHint)
	if err != nil {
		exit(err, 3)
	}

	cmd.Flags = append(
		cliFlags,
		cli.StringFlag{
			Name:   "config",
			Value:  "~/.gotty",
			Usage:  "Config file path",
			EnvVar: "GOTTY_CONFIG",
		},
	)

	cmd.Action = func(c *cli.Context) {
		if len(c.Args()) == 0 {
			fmt.Println("Error: No command given.\n")
			cli.ShowAppHelp(c)
			exit(err, 1)
		}

		options := app.DefaultOptions

		configFile := c.String("config")
		_, err := os.Stat(app.ExpandHomeDir(configFile))
		if configFile != "~/.gotty" || !os.IsNotExist(err) {
			if err := app.ApplyConfigFile(&options, configFile); err != nil {
				exit(err, 2)
			}
		}

		applyFlags(&options, flags, mappingHint, c)

		if c.IsSet("credential") {
			options.EnableBasicAuth = true
		}

		app, err := app.New(c.Args(), &options)
		if err != nil {
			exit(err, 3)
		}

		registerSignals(app)

		err = app.Run()
		if err != nil {
			exit(err, 4)
		}
	}

	cli.AppHelpTemplate = helpTemplate

	cmd.Run(os.Args)
}
Example #3
0
func GoTTY() {

	flags := []struct {
		name        string
		shortName   string
		description string
	}{
		{"address", "a", "IP address to listen"},
		{"port", "p", "Port number to listen"},
		{"permit-write", "w", "Permit clients to write to the TTY (BE CAREFUL)"},
		{"credential", "c", "Credential for Basic Authentication (ex: user:pass, default disabled)"},
		{"random-url", "r", "Add a random string to the URL"},
		{"random-url-length", "", "Random URL length"},
		{"tls", "t", "Enable TLS/SSL"},
		{"tls-crt", "", "TLS/SSL certificate file path"},
		{"tls-key", "", "TLS/SSL key file path"},
		{"tls-ca-crt", "", "TLS/SSL CA certificate file for client certifications"},
		{"index", "", "Custom index.html file"},
		{"title-format", "", "Title format of browser window"},
		{"reconnect", "", "Enable reconnection"},
		{"reconnect-time", "", "Time to reconnect"},
		{"once", "", "Accept only one client and exit on disconnection"},
		{"permit-arguments", "", "Permit clients to send command line arguments in URL (e.g. http://example.com:8080/?arg=AAA&arg=BBB)"},
		{"close-signal", "", "Signal sent to the command process when gotty close it (default: SIGHUP)"},
	}

	mappingHint := map[string]string{
		"index":      "IndexFile",
		"tls":        "EnableTLS",
		"tls-crt":    "TLSCrtFile",
		"tls-key":    "TLSKeyFile",
		"tls-ca-crt": "TLSCACrtFile",
		"random-url": "EnableRandomUrl",
		"reconnect":  "EnableReconnect",
	}

	options := gotty.DefaultOptions

	var configFile string = "~/.gotty"
	if v, ok := os.LookupEnv("GOTTY_CONFIG"); ok {
		configFile = v
	}
	args := os.Args[1:]
	//si := SliceIndex(len(args), func(i int) bool { return strings.Contains(args[i], "-config") })

	flag.StringVar(&configFile, "config", configFile, "Config file path")

	//cliFlags, err := generateFlags(flags, mappingHint)
	//applyFlags(&options, flags, mappingHint, c)
	o := structs.New(&options)
	for _, f := range flags {
		fieldname := fieldName(f.name, mappingHint)

		field, ok := o.FieldOk(fieldname)
		if !ok {
			glog.Warningf("No such field: %s", fieldname)
			continue
		}

		//flagname := f.name
		//if f.shortName != "" {
		//	flagname += ", " + f.shortName
		//}
		envName := "GOTTY_" + strings.ToUpper(strings.Join(strings.Split(f.name, "-"), "_"))

		//field := o.Field(fieldname)
		field.Set(os.Getenv(envName))
		var val interface{}
		switch field.Kind() {
		case reflect.String:
			val = flag.String(f.name, field.Value().(string), f.description)
		case reflect.Bool:
			val = flag.Bool(f.name, false, f.description)
		case reflect.Int:
			val = flag.Int(f.name, field.Value().(int), f.description)
		}
		field.Set(val)
	}

	flag.Parse()

	_, err := os.Stat(gotty.ExpandHomeDir(configFile))
	if configFile != "~/.gotty" || !os.IsNotExist(err) {
		if err := gotty.ApplyConfigFile(&options, configFile); err != nil {
			exit(err, 2)
		}
	}

	if options.Credential != "" {
		options.EnableBasicAuth = true
	}
	if options.TLSCACrtFile != "" {
		options.EnableTLSClientAuth = true
	}

	if err := gotty.CheckConfig(&options); err != nil {
		exit(err, 6)
	}

	var app *App

	if len(args) == 0 {
		app, err = NewApp([]string{"kubectl", "exec", "-ti", "netcat-simple", "ash"}, &options)
	} else {
		clientConfig := directKClientConfig(_kubeconfig, _context, _apiserver)
		if clientConfig == nil {
			exit(fmt.Errorf("kubeclient required"), 1)
		}
		pty, tty, err := openPTYTTY()
		if err != nil {
			exit(err, 1)
		}
		app, err = NewCmdExec(clientConfig, _ns, _pod, _container, _stdin, _tty, pty, tty, tty, tty, _command, &options)
	}
	if err != nil {
		exit(err, 3)
	}

	registerSignals(app)

	err = app.Run()
	if err != nil {
		exit(err, 4)
	}
}
func (app *App) Run() error {
	if app.options.PermitWrite {
		log.Printf("Permitting clients to write input to the PTY.")
	}

	if app.options.Once {
		log.Printf("Once option is provided, accepting only one client")
	}

	path := ""
	if app.options.EnableRandomUrl {
		path += "/" + generateRandomString(app.options.RandomUrlLength)
	}

	endpoint := net.JoinHostPort(app.options.Address, app.options.Port)

	wsHandler := http.HandlerFunc(app.handleWS)
	customIndexHandler := http.HandlerFunc(app.handleCustomIndex)
	authTokenHandler := http.HandlerFunc(app.handleAuthToken)
	staticHandler := http.FileServer(
		&assetfs.AssetFS{Asset: gotty.Asset, AssetDir: gotty.AssetDir, Prefix: "static"},
	)

	var siteMux = http.NewServeMux()

	if app.options.IndexFile != "" {
		log.Printf("Using index file at " + app.options.IndexFile)
		siteMux.Handle(path+"/", customIndexHandler)
	} else {
		siteMux.Handle(path+"/", http.StripPrefix(path+"/", staticHandler))
	}
	siteMux.Handle(path+"/auth_token.js", authTokenHandler)
	siteMux.Handle(path+"/js/", http.StripPrefix(path+"/", staticHandler))
	siteMux.Handle(path+"/favicon.png", http.StripPrefix(path+"/", staticHandler))

	siteHandler := http.Handler(siteMux)

	if app.options.EnableBasicAuth {
		log.Printf("Using Basic Authentication")
		siteHandler = wrapBasicAuth(siteHandler, app.options.Credential)
	}

	siteHandler = wrapHeaders(siteHandler)

	wsMux := http.NewServeMux()
	wsMux.Handle("/", siteHandler)
	wsMux.Handle(path+"/ws", wsHandler)
	siteHandler = (http.Handler(wsMux))

	siteHandler = wrapLogger(siteHandler)

	scheme := "http"
	if app.options.EnableTLS {
		scheme = "https"
	}
	log.Printf(
		"Server is starting with command: %s",
		strings.Join(app.command, " "),
	)
	if app.options.Address != "" {
		log.Printf(
			"URL: %s",
			(&url.URL{Scheme: scheme, Host: endpoint, Path: path + "/"}).String(),
		)
	} else {
		for _, address := range listAddresses() {
			log.Printf(
				"URL: %s",
				(&url.URL{
					Scheme: scheme,
					Host:   net.JoinHostPort(address, app.options.Port),
					Path:   path + "/",
				}).String(),
			)
		}
	}

	server, err := app.makeServer(endpoint, &siteHandler)
	if err != nil {
		return errors.New("Failed to build server: " + err.Error())
	}
	app.server = manners.NewWithServer(
		server,
	)

	if app.options.EnableTLS {
		crtFile := gotty.ExpandHomeDir(app.options.TLSCrtFile)
		keyFile := gotty.ExpandHomeDir(app.options.TLSKeyFile)
		log.Printf("TLS crt file: " + crtFile)
		log.Printf("TLS key file: " + keyFile)

		err = app.server.ListenAndServeTLS(crtFile, keyFile)
	} else {
		err = app.server.ListenAndServe()
	}
	if err != nil {
		return err
	}

	log.Printf("Exiting...")

	return nil
}
func (app *App) handleCustomIndex(w http.ResponseWriter, r *http.Request) {
	http.ServeFile(w, r, gotty.ExpandHomeDir(app.options.IndexFile))
}