mirror of
https://github.com/gravitational/teleport.git
synced 2026-10-13 00:34:45 +08:00
161 lines
4.0 KiB
Go
161 lines
4.0 KiB
Go
package client
|
|
|
|
import (
|
|
"fmt"
|
|
"io/ioutil"
|
|
"os"
|
|
"os/user"
|
|
"path/filepath"
|
|
|
|
"github.com/gravitational/trace"
|
|
|
|
"gopkg.in/yaml.v2"
|
|
)
|
|
|
|
type ProfileOptions int
|
|
|
|
const (
|
|
ProfileMakeCurrent = 1 << iota
|
|
)
|
|
|
|
// CurrentProfileSymlink is a filename which is a symlink to the
|
|
// current profile, usually something like this:
|
|
//
|
|
// ~/.tsh/profile -> ~/.tsh/staging.yaml
|
|
//
|
|
const CurrentProfileSymlink = "profile"
|
|
|
|
// ClientProfile is a collection of most frequently used CLI flags
|
|
// for "tsh".
|
|
//
|
|
// Profiles can be stored in a profile file, allowing TSH users to
|
|
// type fewer CLI args.
|
|
//
|
|
type ClientProfile struct {
|
|
//
|
|
// proxy configuration
|
|
//
|
|
ProxyHost string `yaml:"proxy_host,omitempty"`
|
|
ProxySSHPort int `yaml:"proxy_port,omitempty"`
|
|
ProxyWebPort int `yaml:"proxy_web_port,omitempty"`
|
|
|
|
//
|
|
// auth/identity
|
|
//
|
|
Username string `yaml:"user,omitempty"`
|
|
|
|
// AuthType (like "google")
|
|
AuthType string `yaml:"auth_type,omitempty"`
|
|
|
|
// SiteName is equivalient to --cluster argument
|
|
SiteName string `yaml:"cluster,omitempty"`
|
|
|
|
//
|
|
// other stuff
|
|
//
|
|
ForwardedPorts []string `yaml:"forward_ports,omitempty"`
|
|
}
|
|
|
|
// FullProfilePath returns the full path to the user profile directory.
|
|
// If the parameter is empty, it returns expanded "~/.tsh", otherwise
|
|
// returns its unmodified parameter
|
|
func FullProfilePath(pDir string) string {
|
|
if pDir != "" {
|
|
return pDir
|
|
}
|
|
// get user home dir:
|
|
home := os.TempDir()
|
|
u, err := user.Current()
|
|
if err == nil {
|
|
home = u.HomeDir
|
|
}
|
|
return filepath.Join(home, ProfileDir)
|
|
|
|
}
|
|
|
|
// If there's a current profile symlink, remove it
|
|
func UnlinkCurrentProfile() error {
|
|
return trace.Wrap(os.Remove(filepath.Join(FullProfilePath(""), CurrentProfileSymlink)))
|
|
}
|
|
|
|
// ProfileFromDir reads the user (yaml) profile from a given directory. The
|
|
// default is to use the ~/<dir-path>/profile symlink unless another profile
|
|
// is explicitly asked for. It works by looking for a "profile" symlink in
|
|
// that directory pointing to the profile's YAML file first.
|
|
func ProfileFromDir(dirPath string, proxyName string) (*ClientProfile, error) {
|
|
profilePath := filepath.Join(dirPath, CurrentProfileSymlink)
|
|
if proxyName != "" {
|
|
profilePath = filepath.Join(dirPath, proxyName+".yaml")
|
|
}
|
|
|
|
return ProfileFromFile(profilePath)
|
|
}
|
|
|
|
// ProfileFromFile loads the profile from a YAML file
|
|
func ProfileFromFile(filePath string) (*ClientProfile, error) {
|
|
bytes, err := ioutil.ReadFile(filePath)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
var cp *ClientProfile
|
|
if err = yaml.Unmarshal(bytes, &cp); err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
return cp, nil
|
|
}
|
|
|
|
// SaveTo saves the profile into a given filename, optionally overwriting it.
|
|
func (cp *ClientProfile) SaveTo(filePath string, opts ProfileOptions) error {
|
|
bytes, err := yaml.Marshal(&cp)
|
|
if err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
if err = ioutil.WriteFile(filePath, bytes, 0660); err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
// set 'current' symlink:
|
|
if opts&ProfileMakeCurrent != 0 {
|
|
symlink := filepath.Join(filepath.Dir(filePath), CurrentProfileSymlink)
|
|
os.Remove(symlink)
|
|
err = os.Symlink(filepath.Base(filePath), symlink)
|
|
}
|
|
return trace.Wrap(err)
|
|
}
|
|
|
|
// LogoutFromEverywhere looks at the list of proxy servers tsh is currently logged into
|
|
// by examining ~/.tsh and logs him out of them all
|
|
func LogoutFromEverywhere(username string) error {
|
|
// if no --user flag was passed, get the current OS user:
|
|
if username == "" {
|
|
me, err := user.Current()
|
|
if err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
username = me.Username
|
|
}
|
|
// load all current keys:
|
|
agent, err := NewLocalAgent("", username)
|
|
if err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
keys, err := agent.GetKeys(username)
|
|
if err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
if len(keys) == 0 {
|
|
fmt.Printf("%s is not logged in\n", username)
|
|
return nil
|
|
}
|
|
// ... and delete them:
|
|
for _, key := range keys {
|
|
err = agent.DeleteKey(key.ProxyHost, username)
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "error logging %s out of %s: %s\n",
|
|
username, key.ProxyHost, err)
|
|
} else {
|
|
fmt.Printf("logged %s out of %s\n", username, key.ProxyHost)
|
|
}
|
|
}
|
|
return nil
|
|
}
|