From b8acc7989182a9e7184ad133b624b749cad512cb Mon Sep 17 00:00:00 2001 From: Sasha Klizhentas Date: Wed, 10 Apr 2019 09:19:35 -0700 Subject: [PATCH] Add --bind-addr, fixes #2620 This commit adds `--bind-addr` flag to tsh login and TELEPORT_LOGIN_BIND_ADDR environment variable to set up login bind address for SSO redirect flows. Usage examples: ``` tsh login --bind-addr=localhost:3333 tsh login --bind-addr=:3333 tsh login --bind-addr=[::1]:3333 TELEPORT_LOGIN_BIND_ADDR=localhost:7777 tsh login ``` --- lib/client/api.go | 25 ++++++++++------- lib/client/weblogin.go | 63 ++++++++++++++++++++++++++++++++++-------- tool/tsh/tsh.go | 12 ++++++-- 3 files changed, 76 insertions(+), 24 deletions(-) diff --git a/lib/client/api.go b/lib/client/api.go index a68292421becd..8e80464aeb018 100644 --- a/lib/client/api.go +++ b/lib/client/api.go @@ -240,6 +240,9 @@ type Config struct { // CheckVersions will check that client version is compatible // with auth server version when connecting. CheckVersions bool + + // BindAddr is an optional host:port to bind to for SSO redirect flows + BindAddr string } // CachePolicy defines cache policy for local clients @@ -1821,16 +1824,18 @@ func (tc *TeleportClient) directLogin(ctx context.Context, secondFactorType stri func (tc *TeleportClient) ssoLogin(ctx context.Context, connectorID string, pub []byte, protocol string) (*auth.SSHLoginResponse, error) { log.Debugf("samlLogin start") // ask the CA (via proxy) to sign our public key: - response, err := SSHAgentSSOLogin( - ctx, - tc.Config.WebProxyAddr, - connectorID, - pub, - tc.KeyTTL, - tc.InsecureSkipVerify, - loopbackPool(tc.Config.WebProxyAddr), - protocol, - tc.CertificateFormat) + response, err := SSHAgentSSOLogin(SSHLogin{ + Context: ctx, + ProxyAddr: tc.Config.WebProxyAddr, + ConnectorID: connectorID, + PubKey: pub, + TTL: tc.KeyTTL, + Insecure: tc.InsecureSkipVerify, + Pool: loopbackPool(tc.Config.WebProxyAddr), + Protocol: protocol, + Compatibility: tc.CertificateFormat, + BindAddr: tc.BindAddr, + }) return response, trace.Wrap(err) } diff --git a/lib/client/weblogin.go b/lib/client/weblogin.go index 5e4f7c1db4ac9..11e7bf09ce229 100644 --- a/lib/client/weblogin.go +++ b/lib/client/weblogin.go @@ -129,9 +129,34 @@ type sealData struct { Nonce []byte `json:"nonce"` } +// SSHLogin contains SSH login parameters +type SSHLogin struct { + // Context is an external context + Context context.Context + // ProxyAddr is the target proxy address + ProxyAddr string + // ConnectorID is the OIDC or SAML connector ID to use + ConnectorID string + // PubKey is SSH public key to sign + PubKey []byte + // TTL is requested TTL of the client certificates + TTL time.Duration + // Insecure turns off verification for x509 target proxy + Insecure bool + // Pool is x509 cert pool to use for server certifcate verification + Pool *x509.CertPool + // Protocol is an optional protocol selection + Protocol string + // Compatibility sets compatibility mode for SSH certificates + Compatibility string + // BindAddr is an optional host:port address to bind + // to for SSO login flows + BindAddr string +} + // SSHAgentSSOLogin is used by SSH Agent (tsh) to login using OpenID connect -func SSHAgentSSOLogin(ctx context.Context, proxyAddr, connectorID string, pubKey []byte, ttl time.Duration, insecure bool, pool *x509.CertPool, protocol string, compatibility string) (*auth.SSHLoginResponse, error) { - clt, proxyURL, err := initClient(proxyAddr, insecure, pool) +func SSHAgentSSOLogin(login SSHLogin) (*auth.SSHLoginResponse, error) { + clt, proxyURL, err := initClient(login.ProxyAddr, login.Insecure, login.Pool) if err != nil { return nil, trace.Wrap(err) } @@ -172,7 +197,7 @@ func SSHAgentSSOLogin(ctx context.Context, proxyAddr, connectorID string, pubKey }) } - server := httptest.NewServer(makeHandler(func(w http.ResponseWriter, r *http.Request) (*auth.SSHLoginResponse, error) { + handler := makeHandler(func(w http.ResponseWriter, r *http.Request) (*auth.SSHLoginResponse, error) { if r.URL.Path != "/callback" { return nil, trace.NotFound("path not found") } @@ -198,7 +223,23 @@ func SSHAgentSSOLogin(ctx context.Context, proxyAddr, connectorID string, pubKey return nil, trace.BadParameter("failed to decode response: in %v, err: %v", r.URL.String(), err) } return re, nil - })) + }) + + var server *httptest.Server + if login.BindAddr != "" { + log.Debugf("Binding to %v.", login.BindAddr) + listener, err := net.Listen("tcp", login.BindAddr) + if err != nil { + return nil, trace.Wrap(err, "%v: could not bind to %v, make sure the address is host:port format for ipv4 and [ipv6]:port format for ipv6, and the address is not in use", err, login.BindAddr) + } + server = &httptest.Server{ + Listener: listener, + Config: &http.Server{Handler: handler}, + } + server.Start() + } else { + server = httptest.NewServer(handler) + } defer server.Close() u, err := url.Parse(server.URL + "/callback") @@ -209,12 +250,12 @@ func SSHAgentSSOLogin(ctx context.Context, proxyAddr, connectorID string, pubKey query.Set("secret", secret.KeyToEncodedString(keyBytes)) u.RawQuery = query.Encode() - out, err := clt.PostJSON(ctx, clt.Endpoint("webapi", protocol, "login", "console"), SSOLoginConsoleReq{ + out, err := clt.PostJSON(login.Context, clt.Endpoint("webapi", login.Protocol, "login", "console"), SSOLoginConsoleReq{ RedirectURL: u.String(), - PublicKey: pubKey, - CertTTL: ttl, - ConnectorID: connectorID, - Compatibility: compatibility, + PublicKey: login.PubKey, + CertTTL: login.TTL, + ConnectorID: login.ConnectorID, + Compatibility: login.Compatibility, }) if err != nil { return nil, trace.Wrap(err) @@ -280,9 +321,9 @@ func SSHAgentSSOLogin(ctx context.Context, proxyAddr, connectorID string, pubKey case <-time.After(defaults.CallbackTimeout): log.Debugf("Timed out waiting for callback after %v.", defaults.CallbackTimeout) return nil, trace.Wrap(trace.Errorf("timed out waiting for callback")) - case <-ctx.Done(): + case <-login.Context.Done(): log.Debugf("Canceled by user.") - return nil, trace.Wrap(ctx.Err()) + return nil, trace.Wrap(login.Context.Err()) } } diff --git a/tool/tsh/tsh.go b/tool/tsh/tsh.go index ae811c46bd5ed..3c9fbf5949560 100644 --- a/tool/tsh/tsh.go +++ b/tool/tsh/tsh.go @@ -132,6 +132,10 @@ type CLIConf struct { // format to use with --out to store a fershly retreived certificate IdentityFormat client.IdentityFileFormat + // BindAddr is an address in the form of host:port to bind to + // during `tsh login` command + BindAddr string + // AuthConnector is the name of the connector to use. AuthConnector string @@ -164,8 +168,9 @@ func main() { } const ( - clusterEnvVar = "TELEPORT_SITE" - clusterHelp = "Specify the cluster to connect" + clusterEnvVar = "TELEPORT_SITE" + clusterHelp = "Specify the cluster to connect" + bindAddrEnvVar = "TELEPORT_LOGIN_BIND_ADDR" ) // Run executes TSH client. same as main() but easier to test @@ -235,6 +240,7 @@ func Run(args []string, underTest bool) { // login logs in with remote proxy and obtains a "session certificate" which gets // stored in ~/.tsh directory login := app.Command("login", "Log in to a cluster and retrieve the session certificate") + login.Flag("bind-addr", "Address in the form of host:port to bind to for login command webhook").Envar(bindAddrEnvVar).StringVar(&cf.BindAddr) login.Flag("out", "Identity output").Short('o').StringVar(&cf.IdentityFileOut) login.Flag("format", fmt.Sprintf("Identity format [%s] or %s (for OpenSSH compatibility)", client.DefaultIdentityFormat, @@ -912,7 +918,7 @@ func makeClient(cf *CLIConf, useProfileLogin bool) (tc *client.TeleportClient, e if options.StrictHostKeyChecking == false { c.HostKeyCallback = client.InsecureSkipHostKeyChecking } - + c.BindAddr = cf.BindAddr return client.NewClient(c) }