Merge branch 'sasha/corruption' into sasha/rbac

This commit is contained in:
Sasha Klizhentas 2016-12-20 11:06:16 -08:00
commit 41a4d2872c
20 changed files with 293 additions and 122 deletions

1
.gitignore vendored
View file

@ -19,6 +19,7 @@ build
# Folders
_obj
_test
tmp
# Architecture specific extensions/prefixes
*.[568vq]

View file

@ -2,7 +2,7 @@
# Naming convention:
# for stable releases we use "1.0.0" format
# for pre-releases, we use "1.0.0-beta.2" format
VERSION=1.2.6
VERSION=1.3.0
# These are standard autotools variables, don't change them please
BUILDDIR ?= build

View file

@ -0,0 +1,90 @@
# This Dockerfile makes the "build box": the container used to build
# official releases of Teleport and its documentation
FROM debian:jessie
ARG UID
ARG GID
ENV DEBIAN_FRONTEND noninteractive
ADD build.assets/locale.gen /etc/locale.gen
ADD build.assets/profile /etc/profile
RUN (apt-get clean \
&& apt-key update \
&& apt-get -q -y update --fix-missing \
&& apt-get -q -y update \
&& apt-get install -q -y apt-utils \
&& apt-get install -q -y less \
&& apt-get install -q -y locales) ;
# Set locale to en_US.UTF-8
RUN (locale-gen \
&& locale-gen en_US.UTF-8 \
&& dpkg-reconfigure locales)
RUN apt-get -y update && apt-get -y upgrade
RUN apt-get install -q -y \
libsqlite3-0 \
curl \
make \
git \
libc6-dev \
gcc \
tar \
gzip \
python \
python-pip \
libyaml-dev \
python-dev \
zip
RUN (pip install click==4.1 recommonmark mkdocs markdown-include ;\
apt-get -y autoclean; apt-get -y clean)
# Install Golang:
RUN (mkdir -p /opt && cd /opt && curl https://storage.googleapis.com/golang/go1.7.linux-amd64.tar.gz | tar xz;\
mkdir -p /gopath/src/github.com/gravitational/teleport;\
chmod a+w /gopath;\
chmod a+w /var/lib)
ENV LANGUAGE="en_US.UTF-8" \
LANG="en_US.UTF-8" \
LC_ALL="en_US.UTF-8" \
LC_CTYPE="en_US.UTF-8" \
GOPATH="/gopath" \
GOROOT="/opt/go" \
PATH="$PATH:/opt/go/bin:/gopath/bin"
RUN go get -u github.com/aws/aws-sdk-go
RUN pip install awscli
RUN curl -sL https://deb.nodesource.com/setup_6.x |bash -
RUN apt-get install -y nodejs
ADD . /gopath/src/github.com/gravitational/teleport
WORKDIR /gopath/src/github.com/gravitational/teleport/web
RUN npm install
RUN npm run build
RUN mkdir -p /gopath/src/github.com/gravitational/teleport/build
RUN cd dist && zip -qr /gopath/src/github.com/gravitational/teleport/build/webassets.zip .
WORKDIR /gopath/src/github.com/gravitational/teleport
RUN go build -o build/teleport -i -tags dynamodb -ldflags '-w -s -extldflags "-static"' ./tool/teleport
RUN go build -o build/tctl -i -tags dynamodb -ldflags '-w -s -extldflags "-static"' ./tool/tctl
RUN go build -o build/tsh -i -tags dynamodb -ldflags '-w -s -extldflags "-static"' ./tool/tsh
RUN cat build/webassets.zip >> build/teleport
RUN zip -q -A build/teleport
RUN cd build && tar -czvf ../teleport.tgz teleport tctl tsh
CMD [ "/gopath/src/github.com/gravitational/teleport/build.assets/publisher.sh" ]

View file

@ -9,3 +9,20 @@ It is a part of Gravitational CI/CD pipeline. To build Teleport type:
make
```
### DynamoDB static binary docker build
The static binary will be built along with all nodejs assets inside the container.
From the root directory of the source checkout run:
```
docker build -f build.assets/Dockerfile.dynamodb -t teleportbuilder .
```
Then you can upload the result to an S3 bucket for release.
```
docker run -it -e AWS_ACL=public-read -e S3_BUCKET=my-teleport-releases -e AWS_ACCESS_KEY_ID -e AWS_SECRET_ACCESS_KEY teleportbuilder
```
Or simply copy the binary out of the image using a volume (it will be copied to current directory/build/teleport.
```
docker run -v $(pwd)/build:/builds -it teleportbuilder cp /gopath/src/github.com/gravitational/teleport/teleport.tgz /builds
```

12
build.assets/publisher.sh Executable file
View file

@ -0,0 +1,12 @@
#!/bin/bash
# required to set AWS_ACL eg. public-read
# required to set S3_BUCKET eg. my-bucket
GIT_REV=$(git rev-parse --verify HEAD)
BINARY_NAME=teleport-${GIT_REV}.tgz
CHECKSUM_NAME=checksums-${GIT_REV}.txt
sha512sum teleport.tgz > $CHECKSUM_NAME
aws s3 cp --acl $AWS_ACL teleport.tgz s3://$S3_BUCKET/$BINARY_NAME
aws s3 cp --acl $AWS_ACL $CHECKSUM_NAME s3://$S3_BUCKET/$CHECKSUM_NAME

View file

@ -1,23 +1,21 @@
# This file is distributed with every binary Teleport tarball
BINDIR=/usr/local/bin
VARDIR=/var/lib/teleport
WEBDIR=/usr/local/share/teleport
#
# sudo make install: installs Teleport into a UNIX-like OS
#
.PHONY: install
install: sudo
mkdir -p $(VARDIR) $(WEBDIR) $(BINDIR)
mkdir -p $(VARDIR) $(BINDIR)
cp -f teleport tctl tsh $(BINDIR)/
cp -fr app index.html $(WEBDIR)/
#
# sudo make uninstall: removes Teleport
#
.PHONY: uninstall
uninstall: sudo
rm -rf $(VARDIR) $(WEBDIR) $(BINDIR)/tctl $(BINDIR)/teleport $(BINDIR)/tsh
rm -rf $(VARDIR) $(BINDIR)/tctl $(BINDIR)/teleport $(BINDIR)/tsh
# helper: makes sure it runs as root

View file

@ -194,7 +194,9 @@ teleport:
# This section configures the 'auth service':
auth_service:
# Turns 'auth' role on. Default is 'yes'
enabled: yes
# IP and the port to bind to. Other Teleport nodes will be connecting to
# this port (AKA "Auth API" or "Cluster API") to validate client
# certificates
@ -234,7 +236,9 @@ auth_service:
# This section configures the 'node service':
ssh_service:
# Turns 'ssh' role on. Default is 'yes'
enabled: yes
# IP and the port for SSH service to bind to.
listen_addr: 0.0.0.0:3022
# See explanation of labels in "Labeling Nodes" section below
@ -254,7 +258,9 @@ ssh_service:
# This section configures the 'proxy servie'
proxy_service:
# Turns 'proxy' role on. Default is 'yes'
enabled: yes
# SSH forwarding/proxy address. Command line (CLI) clients always begin their
# SSH sessions by connecting to this port
listen_addr: 0.0.0.0:3023
@ -766,64 +772,78 @@ your Google credentials. Teleport will keep you logged in for the next 23 hours.
## FIDO U2F
Teleport supports [FIDO U2F](https://www.yubico.com/about/background/fido/) hardware keys as a second authentication factor.
Teleport supports [FIDO U2F](https://www.yubico.com/about/background/fido/)
hardware keys as a second authentication factor.
### Enabling/Disabling U2F
To start using U2F:
U2F is enabled in the demo configuration.
To enable U2F, add the following to the auth service configuration.
* Purchase a U2F hardware key: looks like a tiny USB drive.
* Enable U2F in Teleport configuration.
* Use `--u2f` CLI flag when connecting via `tsh ssh`.
````
Lets look into each of these steps in detail.
### Getting U2F Keys
Visit [this page](https://www.yubico.com/about/background/fido/) to order
a physical U2F keys for Teleport users in your organization.
### Enabling U2F
By default U2F is disabled. To enable U2F, add the following to the auth
service configuration in `teleport.yaml`:
```bash
auth_service:
u2f:
# Must be set to 'yes' to enable and 'no' to disable:
enabled: yes
# Only matters when multiple proxy servers are used:
app_id: https://mycorp.com/appid.js
# U2F facets must be set to Teleport proxy servers:
facets:
- https://proxy1.mycorp.com:3080
- https://proxy2.mycorp.com:3080
````
In single-server setups, `app_id` and `facets` can be omitted.
To disable U2F, set `enabled` to `no`.
`app_id` should be the App ID of your cluster. `app_id` defaults to the auth server's hostname if not set.
`facets` should include proxies (if any).
If a proxy is on a port that is not standard for HTTPS (i.e. 3080), then the port must be specified in the facets list.
`facets` defaults to having `app_id` as the only entry if not set.
### Using U2F
Once U2F is enabled, users can select U2F as their second factor during registration and log in with their U2F keys.
#### Web UI
On the signup page, a set of radio buttons should appear that allow the user to select Google Authenticator or U2F as their second factor.
On the login page, a "Login with U2F" button should appear below the "Login" button.
Leave the two factor token field blank when logging in with U2F.
#### CLI
You have to tell `tsh` to authenticate using U2F with the `--u2f` switch:
```
tsh --proxy <proxy-addr> ssh --u2f
```
NOTE: You will need the [`u2f-host`](https://developers.yubico.com/libu2f-host/) binary in order to use U2F with the CLI.
#### Additional considerations
If your Teleport is deployed with the same `teleport` process running as a proxy
and as an auth server, you can omit `app_id` and `facets` settings.
The App ID identifies the web application to the U2F keys and should not change in the lifetime of the cluster.
If the App ID changes, all existing U2F key registrations will become invalid and all users who use U2F as the second factor will need to re-register.
`app_id` should be the U2F App ID of your cluster.
`app_id` defaults to the auth server's hostname if not set.
For single-proxy setups, the App ID can be equal to the domain name of the proxy, but this will prevent you from adding more proxies without changing the App ID.
For multi-proxy setups, the App ID should be an HTTPS URL pointing to a JSON file that mirrors `facets` in the auth config.
If your Teleport deployment includes multiple Teleport proxy servers, you
must list them all in the `facets` section. If the TCP port of a facet/proxy
is not specified, the default Teleport proxy port (3080) will be used.
For single-proxy setups, the App ID can be equal to the domain name of the
proxy, but this will prevent you from adding more proxies without changing the
App ID. For multi-proxy setups, the App ID should be an HTTPS URL pointing to
a JSON file that mirrors `facets` in the auth config.
The JSON file should be hosted on a domain you control and it should be accessible anonymously.
See the [official U2F specification](https://fidoalliance.org/specs/fido-u2f-v1.0-ps-20141009/fido-appid-and-facets-ps-20141009.html#processing-rules-for-appid-and-facetid-assertions)
for the exact format of the JSON file.
!!! warning "Warning":
The App ID must never change in the lifetime of the cluster. If the App ID
changes, all existing U2F key registrations will become invalid and all users
who use U2F as the second factor will need to re-register.
When adding a new proxy server, make sure to add it to the list of "facets"
in the configuration file, but also to the JSON file referenced by `app_id`
### Logging in with U2F
For logging in via the CLI, you must first install [`u2f-host`](https://developers.yubico.com/libu2f-host/)
Then invoke `tsh ssh` to authenticate using U2F with the `--u2f` switch:
```
tsh --proxy <proxy-addr> ssh --u2f <hostname>
```
## High Availability and Clustering
Teleport can use [etcd](https://coreos.com/etcd/) as a storage backend to

View file

@ -293,7 +293,7 @@ a:visited{
div.wy-nav-content { max-width: 1000px; }
.rst-content table.docutils td { overflow: hidden; white-space: normal }
h3 { margin: 0 auto 0 auto; width: 100% }
h3 { margin: 0 0; color: #3d73ba; width: 100% }
.wy-menu-vertical li ul li a { font-size: 90% }
.wy-nav-content { padding-left: 20px; padding-top: 10px; padding-right: 20px; }

View file

@ -434,6 +434,7 @@ func (tc *TeleportClient) SSH(command []string, runLocally bool) error {
tc.Config.HostLogin,
false)
if err != nil {
tc.ExitStatus = 1
return trace.Wrap(err)
}
// proxy local ports (forward incoming connections to remote host ports)
@ -764,7 +765,9 @@ func (tc *TeleportClient) ListNodes() ([]services.Server, error) {
}
// runCommand executes a given bash command on a bunch of remote nodes
func (tc *TeleportClient) runCommand(siteName string, nodeAddresses []string, proxyClient *ProxyClient, command []string) error {
func (tc *TeleportClient) runCommand(
siteName string, nodeAddresses []string, proxyClient *ProxyClient, command []string) error {
resultsC := make(chan error, len(nodeAddresses))
for _, address := range nodeAddresses {
go func(address string) {
@ -793,9 +796,17 @@ func (tc *TeleportClient) runCommand(siteName string, nodeAddresses []string, pr
return
}
if err = nodeSession.runCommand(command, tc.OnShellCreated, tc.Config.Interactive); err != nil {
exitErr, ok := err.(*ssh.ExitError)
originErr := trace.Unwrap(err)
exitErr, ok := originErr.(*ssh.ExitError)
if ok {
tc.ExitStatus = exitErr.ExitStatus()
} else {
// if an error occurs, but no exit status is passed back, GoSSH returns
// a generic error like this. in this case the error message is printed
// to stderr by the remote process so we have to quietly return 1:
if strings.Contains(originErr.Error(), "exited without exit status") {
tc.ExitStatus = 1
}
}
}
}(address)

View file

@ -72,6 +72,11 @@ func FullProfilePath(pDir string) string {
}
// If there's a current profile symlink, remove it
func UnlinkCurrentProfile() error {
return trace.Wrap(os.Remove(path.Join(FullProfilePath(""), CurrentProfileSymlink)))
}
// ProfileFromDir reads the user profile from a given directory. It works
// by looking for a "profile" symlink in that directory pointing to the
// profile's YAML file.

View file

@ -179,7 +179,9 @@ func ApplyFileConfig(fc *FileConfig, cfg *service.Config) error {
a := &cfg.Auth
a.KeysBackend.Type = dynamo.BackendType
a.KeysBackend.Params, err = dynamo.ConfigureBackend(&fc.Storage)
return trace.Wrap(err)
if err != nil {
return trace.Wrap(err)
}
case "":
break // not set
default:

View file

@ -195,9 +195,21 @@ func (a *Agent) proxyAccessPoint(ch ssh.Channel, req <-chan *ssh.Request) {
wg.Wait()
}
// proxyTransport runs as a goroutine running inside a reverse tunnel client
// and it establishes and maintains the following remote connection:
//
// tsh -> proxy(also reverse-tunnel-server) -> reverse-tunnel-agent
//
// ch : SSH channel which received "teleport-transport" out-of-band request
// reqC : request payload
func (a *Agent) proxyTransport(ch ssh.Channel, reqC <-chan *ssh.Request) {
defer ch.Close()
// always push space into stderr to make sure the caller can always
// safely call read(stderr) without blocking. this stderr is only used
// to request proxying of TCP/IP via reverse tunnel.
fmt.Fprint(ch.Stderr(), " ")
var req *ssh.Request
select {
case <-a.broadcastClose.C:
@ -218,7 +230,12 @@ func (a *Agent) proxyTransport(ch ssh.Channel, reqC <-chan *ssh.Request) {
conn, err := net.Dial("tcp", server)
if err != nil {
log.Errorf("failed to dial: %v, err: %v", server, err)
log.Error(trace.DebugReport(err))
// write the connection error to stderr of the caller (via SSH channel)
// so the error will be propagated all the way back to the
// client (most likely tsh)
fmt.Fprint(ch.Stderr(), err.Error())
req.Reply(false, []byte(err.Error()))
return
}
req.Reply(true, []byte("connected"))

View file

@ -18,6 +18,7 @@ package reversetunnel
import (
"fmt"
"io/ioutil"
"net"
"net/http"
"strings"
@ -514,6 +515,12 @@ func (s *tunnelSite) String() string {
return fmt.Sprintf("remoteSite(%v)", s.domainName)
}
func (s *tunnelSite) connectionCount() int {
s.Lock()
defer s.Unlock()
return len(s.connections)
}
func (s *tunnelSite) nextConn() (*remoteConn, error) {
s.Lock()
defer s.Unlock()
@ -668,8 +675,9 @@ func (s *tunnelSite) dialAccessPoint(network, addr string) (net.Conn, error) {
// Dial is used to connect a requesting client (say, tsh) to an SSH server
// located in a remote connected site, the connection goes through the
// reverse proxy tunnel.
func (s *tunnelSite) Dial(network string, addr string) (net.Conn, error) {
func (s *tunnelSite) Dial(network string, addr string) (conn net.Conn, err error) {
s.log.Infof("[TUNNEL] dialing %v@%v through the tunnel", addr, s.domainName)
stop := false
try := func() (net.Conn, error) {
remoteConn, err := s.nextConn()
@ -682,34 +690,44 @@ func (s *tunnelSite) Dial(network string, addr string) (net.Conn, error) {
remoteConn.markInvalid(err)
return nil, trace.Wrap(err)
}
// we're creating a new SSH connection inside reverse SSH connection
// as a new SSH channel:
stop = true
// send a special SSH out-of-band request called "teleport-transport"
// the agent on the other side will create a new TCP/IP connection to
// 'addr' on its network and will start proxying that connection over
// this SSH channel:
var dialed bool
dialed, err = ch.SendRequest(chanTransportDialReq, true, []byte(addr))
if err != nil {
remoteConn.markInvalid(err)
return nil, trace.Wrap(err)
}
if !dialed {
remoteConn.markInvalid(err)
return nil, trace.ConnectionProblem(
nil, "remote server %v is not available", addr)
defer ch.Close()
// pull the error message from the tunnel client (remote cluster)
// passed to us via stderr:
errMessage, _ := ioutil.ReadAll(ch.Stderr())
if errMessage == nil {
errMessage = []byte("failed connecting to " + addr)
}
return nil, trace.Errorf(strings.TrimSpace(string(errMessage)))
}
return utils.NewChConn(remoteConn.sshConn, ch), nil
}
for {
conn, err := try()
if err != nil {
s.log.Errorf("[TUNNEL] Dial(addr=%v) failed: %v", addr, err)
// we interpret it as a "out of connections and will try again"
if trace.IsNotFound(err) {
return nil, trace.Wrap(err)
}
continue
// loop through existing TCP/IP connections (reverse tunnels) and try
// to establish an inbound connection-over-ssh-channel to the remote
// cluster (AKA "remotetunnel agent"):
for i := 0; i < s.connectionCount() && !stop; i++ {
conn, err = try()
if err == nil {
return conn, nil
}
return conn, nil
s.log.Errorf("[TUNNEL] Dial(addr=%v) failed: %v", addr, err)
}
// didn't connect and no error? this means we didn't have any connected
// tunnels to try
if err == nil {
err = trace.Errorf("%v is offline", s.GetName())
}
return nil, err
}
func (s *tunnelSite) DialServer(addr string) (net.Conn, error) {

View file

@ -451,37 +451,18 @@ func newSessionRecorder(alog events.IAuditLog, namespace string, sid rsession.ID
// Write takes a chunk and writes it into the audit log
func (r *sessionRecorder) Write(data []byte) (int, error) {
const (
minChunkLen = 4
maxDelay = time.Millisecond * 20
requiredDelay = time.Millisecond * 5
)
// terminal recording is a tricky business. the TTY subsystem expects a certain
// delay when it writes to a virtual console. In our case recording takes no time
// so we have to emulate TTY delay here:
var start time.Time
// the delay depends on the chunk size, but shouldn't be higher than a certain
// ceiling (maxDelay)
dataLen := len(data)
if dataLen > minChunkLen {
start = time.Now()
defer func() {
postingDuration := time.Now().Sub(start)
if postingDuration < requiredDelay {
delay := time.Millisecond * time.Duration(dataLen)
if delay > maxDelay {
delay = maxDelay
}
time.Sleep(delay)
}
}()
}
// we are copying buffer to prevent data corruption:
// io.Copy allocates single buffer and calls multiple writes in a loop
// our PostSessionChunk is async and sends reader wrapping buffer
// to the channel. This can lead to cases when the buffer is re-used
// and data is corrupted unless we copy the data buffer in the first place
dataCopy := make([]byte, len(data))
copy(dataCopy, data)
// post the chunk of bytes to the audit log:
if err := r.alog.PostSessionChunk(r.namespace, r.sid, bytes.NewReader(data)); err != nil {
log.Error(err)
if err := r.alog.PostSessionChunk(r.namespace, r.sid, bytes.NewReader(dataCopy)); err != nil {
log.Error(trace.DebugReport(err))
}
return dataLen, nil
return len(data), nil
}
// Close() does nothing for session recorder (audit log cannot be closed)

View file

@ -621,7 +621,7 @@ func (s *Server) handleDirectTCPIPRequest(sconn *ssh.ServerConn, ch ssh.Channel,
ctx.Infof("direct-tcpip channel: %#v to --> %v", req, addr)
conn, err := net.Dial("tcp", addr)
if err != nil {
ctx.Infof("failed to connect to: %v, err: %v", addr, err)
ctx.Infof("failed connecting to: %v, err: %v", addr, err)
return
}
defer conn.Close()
@ -832,7 +832,7 @@ func (s *Server) handleSubsystem(ch ssh.Channel, req *ssh.Request, ctx *ctx) err
// starting subsystem is blocking to the client,
// while collecting its result and waiting is not blocking
if err := sb.start(ctx.conn, ch, req, ctx); err != nil {
ctx.Warnf("[SSH] failed to execute request, err: %v", err)
ctx.Warnf("[SSH] failed executing request: %v", err)
ctx.sendSubsystemResult(trace.Wrap(err))
return trace.Wrap(err)
}

View file

@ -49,17 +49,15 @@ func ObeyTimeouts(conn net.Conn, timeout time.Duration, name string) net.Conn {
}
func (tc *TimeoutConn) Read(p []byte) (n int, err error) {
err = tc.Conn.SetReadDeadline(time.Now().Add(tc.TimeoutDuration))
if err != nil {
return 0, err
}
// note: checking for errors here does not buy anything: some net.Conn interface
// implementations (sshConn, pipe) simply return "not supported" error
tc.Conn.SetReadDeadline(time.Now().Add(tc.TimeoutDuration))
return tc.Conn.Read(p)
}
func (tc *TimeoutConn) Write(p []byte) (n int, err error) {
err = tc.Conn.SetWriteDeadline(time.Now().Add(tc.TimeoutDuration))
if err != nil {
return 0, err
}
// note: checking for errors here does not buy anything: some net.Conn interface
// implementations (sshConn, pipe) simply return "not supported" error
tc.Conn.SetWriteDeadline(time.Now().Add(tc.TimeoutDuration))
return tc.Conn.Write(p)
}

View file

@ -241,7 +241,7 @@ func SSHAgentU2FLogin(proxyAddr, user, password string, pubKey []byte, ttl time.
// The origin URL is passed back base64-encoded and the keyHandle is passed back as is.
// A very long proxy hostname or keyHandle can overflow a fixed-size buffer.
signResponseLen := 500 + len(u2fSignRequest.Bytes()) + len(proxyAddr) * 4 / 3
signResponseLen := 500 + len(u2fSignRequest.Bytes()) + len(proxyAddr)*4/3
signResponseBuf := make([]byte, signResponseLen)
signResponseLen, err = io.ReadFull(stdout, signResponseBuf)
// unexpected EOF means we have read the data completely.
@ -288,7 +288,11 @@ func SSHAgentU2FLogin(proxyAddr, user, password string, pubKey []byte, ttl time.
return out, nil
}
// initClient creates and initializes HTTPS client for talking to teleport proxy HTTPS
// endpoint.
func initClient(proxyAddr string, insecure bool, pool *x509.CertPool) (*webClient, *url.URL, error) {
log.Debugf("HTTPS client init(insecure=%v)", insecure)
// validate proxyAddr:
host, port, err := net.SplitHostPort(proxyAddr)
if err != nil || host == "" || port == "" {
@ -305,13 +309,13 @@ func initClient(proxyAddr string, insecure bool, pool *x509.CertPool) (*webClien
var opts []roundtrip.ClientParam
if pool != nil {
// use custom set of trusted CAs
opts = append(opts, roundtrip.HTTPClient(newClientWithPool(pool)))
} else if insecure {
if insecure {
// skip https cert verification, oh no!
fmt.Printf("WARNING: You are using insecure connection to SSH proxy %v\n", proxyAddr)
opts = append(opts, roundtrip.HTTPClient(newInsecureClient()))
} else if pool != nil {
// use custom set of trusted CAs
opts = append(opts, roundtrip.HTTPClient(newClientWithPool(pool)))
}
clt, err := newWebClient(proxyAddr, opts...)

View file

@ -199,6 +199,8 @@ func onLogin(cf *CLIConf) {
if err := tc.Login(); err != nil {
utils.FatalError(err)
}
tc.SaveProfile("")
if tc.SiteName != "" {
fmt.Printf("\nYou are now logged into %s as %s\n", tc.SiteName, tc.Username)
} else {
@ -208,6 +210,7 @@ func onLogin(cf *CLIConf) {
// onLogout deletes a "session certificate" from ~/.tsh for a given proxy
func onLogout(cf *CLIConf) {
client.UnlinkCurrentProfile()
tc, err := makeClient(cf, true)
if err != nil {
utils.FatalError(err)
@ -301,9 +304,6 @@ func onSSH(cf *CLIConf) {
} else {
utils.FatalError(err)
}
} else {
// successful session? update the profile then:
tc.SaveProfile("")
}
}
@ -319,9 +319,6 @@ func onJoin(cf *CLIConf) {
}
if err = tc.Join(cf.Namespace, *sid, nil); err != nil {
utils.FatalError(err)
} else {
// successful session? update the profile then:
tc.SaveProfile("")
}
}
@ -338,9 +335,6 @@ func onSCP(cf *CLIConf) {
} else {
utils.FatalError(err)
}
} else {
// successful session? update the profile then:
tc.SaveProfile("")
}
}

View file

@ -268,7 +268,10 @@ type ConnectionProblemError struct {
// Error is debug - friendly error message
func (c *ConnectionProblemError) Error() string {
return fmt.Sprintf("%v: %v", c.Message, c.Err)
if c.Err == nil {
return c.Message
}
return c.Err.Error()
}
// IsConnectionProblemError indicates that this error is of ConnectionProblemError type

View file

@ -2,7 +2,7 @@
package teleport
const (
Version = "1.2.6"
Version = "1.3.0"
)
var Gitref string