2015-10-31 18:56:49 +00:00
|
|
|
/*
|
|
|
|
Copyright 2015 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.
|
|
|
|
*/
|
2016-02-24 21:19:36 +00:00
|
|
|
|
2015-10-24 23:04:13 +00:00
|
|
|
package web
|
2015-07-15 00:52:12 +00:00
|
|
|
|
|
|
|
import (
|
2017-11-25 01:09:11 +00:00
|
|
|
"context"
|
|
|
|
"crypto/tls"
|
|
|
|
"crypto/x509"
|
2016-02-27 02:10:01 +00:00
|
|
|
"io"
|
2017-08-24 00:31:07 +00:00
|
|
|
"net"
|
2015-07-15 00:52:12 +00:00
|
|
|
"net/http"
|
2016-02-26 22:57:51 +00:00
|
|
|
"sync"
|
|
|
|
"time"
|
2015-07-15 00:52:12 +00:00
|
|
|
|
2018-05-04 00:36:08 +00:00
|
|
|
"golang.org/x/crypto/ssh"
|
|
|
|
"golang.org/x/crypto/ssh/agent"
|
|
|
|
|
2017-03-22 02:08:27 +00:00
|
|
|
"github.com/gravitational/teleport"
|
2015-10-05 17:36:55 +00:00
|
|
|
"github.com/gravitational/teleport/lib/auth"
|
2017-02-03 02:14:42 +00:00
|
|
|
"github.com/gravitational/teleport/lib/client"
|
2017-08-24 00:31:07 +00:00
|
|
|
"github.com/gravitational/teleport/lib/defaults"
|
|
|
|
"github.com/gravitational/teleport/lib/reversetunnel"
|
2016-02-12 15:25:54 +00:00
|
|
|
"github.com/gravitational/teleport/lib/services"
|
2015-10-05 17:36:55 +00:00
|
|
|
"github.com/gravitational/teleport/lib/utils"
|
2015-07-15 00:52:12 +00:00
|
|
|
|
2016-01-20 15:52:25 +00:00
|
|
|
"github.com/gravitational/trace"
|
2016-04-07 22:00:11 +00:00
|
|
|
"github.com/gravitational/ttlmap"
|
2018-05-04 00:36:08 +00:00
|
|
|
|
2017-11-25 01:09:11 +00:00
|
|
|
"github.com/jonboulle/clockwork"
|
2017-08-22 22:30:30 +00:00
|
|
|
log "github.com/sirupsen/logrus"
|
2016-10-14 06:51:16 +00:00
|
|
|
"github.com/tstranex/u2f"
|
2015-07-15 00:52:12 +00:00
|
|
|
)
|
|
|
|
|
2016-04-06 17:29:30 +00:00
|
|
|
// SessionContext is a context associated with users'
|
2016-02-25 01:58:22 +00:00
|
|
|
// web session, it stores connected client that persists
|
|
|
|
// between requests for example to avoid connecting
|
|
|
|
// to the auth server on every page hit
|
2016-04-06 17:29:30 +00:00
|
|
|
type SessionContext struct {
|
2016-02-27 02:10:01 +00:00
|
|
|
sync.Mutex
|
2016-02-26 22:57:51 +00:00
|
|
|
*log.Entry
|
2017-08-24 00:31:07 +00:00
|
|
|
sess services.WebSession
|
|
|
|
user string
|
2017-11-25 01:09:11 +00:00
|
|
|
clt auth.ClientI
|
2017-08-24 00:31:07 +00:00
|
|
|
remoteClt map[string]auth.ClientI
|
|
|
|
parent *sessionCache
|
|
|
|
closers []io.Closer
|
2018-05-04 00:36:08 +00:00
|
|
|
tc *client.TeleportClient
|
2016-03-01 21:19:43 +00:00
|
|
|
}
|
|
|
|
|
2016-04-06 17:29:30 +00:00
|
|
|
func (c *SessionContext) AddClosers(closers ...io.Closer) {
|
2016-02-27 02:10:01 +00:00
|
|
|
c.Lock()
|
|
|
|
defer c.Unlock()
|
|
|
|
c.closers = append(c.closers, closers...)
|
|
|
|
}
|
|
|
|
|
2017-02-04 07:12:29 +00:00
|
|
|
func (c *SessionContext) RemoveCloser(closer io.Closer) {
|
|
|
|
c.Lock()
|
|
|
|
defer c.Unlock()
|
|
|
|
for i := range c.closers {
|
|
|
|
if c.closers[i] == closer {
|
|
|
|
c.closers = append(c.closers[:i], c.closers[i+1:]...)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-04-06 17:29:30 +00:00
|
|
|
func (c *SessionContext) TransferClosers() []io.Closer {
|
2016-02-27 02:10:01 +00:00
|
|
|
c.Lock()
|
|
|
|
defer c.Unlock()
|
|
|
|
closers := c.closers
|
|
|
|
c.closers = nil
|
|
|
|
return closers
|
2016-02-26 22:57:51 +00:00
|
|
|
}
|
|
|
|
|
2016-04-06 17:29:30 +00:00
|
|
|
func (c *SessionContext) Invalidate() error {
|
2016-02-26 22:57:51 +00:00
|
|
|
return c.parent.InvalidateSession(c)
|
2015-07-15 00:52:12 +00:00
|
|
|
}
|
|
|
|
|
2017-08-24 00:31:07 +00:00
|
|
|
func (c *SessionContext) addRemoteClient(siteName string, remoteClient auth.ClientI) {
|
|
|
|
c.Lock()
|
|
|
|
defer c.Unlock()
|
|
|
|
|
|
|
|
c.remoteClt[siteName] = remoteClient
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *SessionContext) getRemoteClient(siteName string) (auth.ClientI, bool) {
|
|
|
|
c.Lock()
|
|
|
|
defer c.Unlock()
|
|
|
|
|
|
|
|
remoteClt, ok := c.remoteClt[siteName]
|
|
|
|
return remoteClt, ok
|
|
|
|
}
|
|
|
|
|
2016-02-25 01:58:22 +00:00
|
|
|
// GetClient returns the client connected to the auth server
|
2017-11-25 01:09:11 +00:00
|
|
|
func (c *SessionContext) GetClient() (auth.ClientI, error) {
|
2016-02-20 04:56:25 +00:00
|
|
|
return c.clt, nil
|
2015-07-15 00:52:12 +00:00
|
|
|
}
|
|
|
|
|
2017-08-24 00:31:07 +00:00
|
|
|
// GetUserClient will return an auth.ClientI with the role of the user at
|
|
|
|
// the requested site. If the site is local a client with the users local role
|
|
|
|
// is returned. If the site is remote a client with the users remote role is
|
|
|
|
// returned.
|
|
|
|
func (c *SessionContext) GetUserClient(site reversetunnel.RemoteSite) (auth.ClientI, error) {
|
|
|
|
// get the name of the current cluster
|
|
|
|
clt, err := c.GetClient()
|
|
|
|
if err != nil {
|
|
|
|
return nil, trace.Wrap(err)
|
|
|
|
}
|
|
|
|
cn, err := clt.GetClusterName()
|
|
|
|
if err != nil {
|
|
|
|
return nil, trace.Wrap(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// if we're trying to access the local cluster, pass back the local client.
|
|
|
|
if cn.GetClusterName() == site.GetName() {
|
|
|
|
return clt, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// look to see if we already have a connection to this cluster
|
|
|
|
remoteClt, ok := c.getRemoteClient(site.GetName())
|
|
|
|
if !ok {
|
|
|
|
rClt, rConn, err := c.newRemoteClient(site)
|
|
|
|
if err != nil {
|
|
|
|
return nil, trace.Wrap(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// add a closer for the underlying connection
|
2017-09-05 18:29:59 +00:00
|
|
|
if rConn != nil {
|
|
|
|
c.AddClosers(rConn)
|
|
|
|
}
|
2017-08-24 00:31:07 +00:00
|
|
|
|
|
|
|
// we'll save the remote client in our session context so we don't have to
|
|
|
|
// build a new connection next time. all remote clients will be closed when
|
|
|
|
// the session context is closed.
|
|
|
|
c.addRemoteClient(site.GetName(), rClt)
|
|
|
|
|
|
|
|
return rClt, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
return remoteClt, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// newRemoteClient returns a client to a remote cluster with the role of
|
|
|
|
// the logged in user.
|
2017-11-25 01:09:11 +00:00
|
|
|
func (c *SessionContext) newRemoteClient(cluster reversetunnel.RemoteSite) (auth.ClientI, net.Conn, error) {
|
|
|
|
clt, err := c.tryRemoteTLSClient(cluster)
|
|
|
|
if err == nil {
|
|
|
|
return clt, nil, nil
|
|
|
|
}
|
|
|
|
log.Debugf("could not use TLS client to %v, error: %v", cluster.GetName(), err)
|
|
|
|
return c.newRemoteTunClient(cluster)
|
|
|
|
}
|
|
|
|
|
|
|
|
// clusterDialer returns DialContext function using cluster's dial function
|
|
|
|
func clusterDialer(remoteCluster reversetunnel.RemoteSite) auth.DialContext {
|
|
|
|
return func(in context.Context, network, _ string) (net.Conn, error) {
|
|
|
|
return remoteCluster.DialAuthServer()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// tryRemoteTLSClient tries creating TLS client and using it (the client may not be available
|
|
|
|
// due to older clusters), returns client if it is working properly
|
|
|
|
func (c *SessionContext) tryRemoteTLSClient(cluster reversetunnel.RemoteSite) (auth.ClientI, error) {
|
|
|
|
clt, err := c.newRemoteTLSClient(cluster)
|
|
|
|
if err != nil {
|
|
|
|
return nil, trace.Wrap(err)
|
|
|
|
}
|
|
|
|
_, err = clt.GetDomainName()
|
|
|
|
if err != nil {
|
|
|
|
return clt, trace.Wrap(err)
|
|
|
|
}
|
|
|
|
return clt, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// ClientTLSConfig returns client TLS authentication associated
|
|
|
|
// with the web session context
|
|
|
|
func (c *SessionContext) ClientTLSConfig(clusterName ...string) (*tls.Config, error) {
|
|
|
|
var certPool *x509.CertPool
|
|
|
|
if len(clusterName) == 0 {
|
|
|
|
certAuthorities, err := c.parent.proxyClient.GetCertAuthorities(services.HostCA, false)
|
|
|
|
if err != nil {
|
|
|
|
return nil, trace.Wrap(err)
|
|
|
|
}
|
|
|
|
certPool, err = services.CertPoolFromCertAuthorities(certAuthorities)
|
|
|
|
if err != nil {
|
|
|
|
return nil, trace.Wrap(err)
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
certAuthority, err := c.parent.proxyClient.GetCertAuthority(services.CertAuthID{
|
|
|
|
Type: services.HostCA,
|
|
|
|
DomainName: clusterName[0],
|
|
|
|
}, false)
|
|
|
|
if err != nil {
|
|
|
|
return nil, trace.Wrap(err)
|
|
|
|
}
|
|
|
|
certPool, err = services.CertPool(certAuthority)
|
|
|
|
if err != nil {
|
|
|
|
return nil, trace.Wrap(err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-06-08 23:50:43 +00:00
|
|
|
tlsConfig := c.parent.clientTLSConfig.Clone()
|
2017-11-25 01:09:11 +00:00
|
|
|
tlsCert, err := tls.X509KeyPair(c.sess.GetTLSCert(), c.sess.GetPriv())
|
|
|
|
if err != nil {
|
|
|
|
return nil, trace.Wrap(err, "failed to parse TLS cert and key")
|
|
|
|
}
|
|
|
|
tlsConfig.Certificates = []tls.Certificate{tlsCert}
|
|
|
|
tlsConfig.RootCAs = certPool
|
|
|
|
return tlsConfig, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *SessionContext) newRemoteTLSClient(cluster reversetunnel.RemoteSite) (auth.ClientI, error) {
|
|
|
|
tlsConfig, err := c.ClientTLSConfig(cluster.GetName())
|
|
|
|
if err != nil {
|
|
|
|
return nil, trace.Wrap(err)
|
|
|
|
}
|
|
|
|
return auth.NewTLSClientWithDialer(clusterDialer(cluster), tlsConfig)
|
|
|
|
}
|
|
|
|
|
|
|
|
// newRemoteTunClient returns a client using legacy SSH tunnel authentication to a remote cluster with the role of
|
|
|
|
// the logged in user
|
|
|
|
func (c *SessionContext) newRemoteTunClient(site reversetunnel.RemoteSite) (auth.ClientI, net.Conn, error) {
|
2017-08-24 00:31:07 +00:00
|
|
|
var err error
|
|
|
|
var netConn net.Conn
|
|
|
|
|
|
|
|
sshDialer := func(network, addr string) (net.Conn, error) {
|
|
|
|
// first get a net.Conn (tcp connection) to the remote auth server. no
|
2017-10-20 08:20:26 +00:00
|
|
|
// authentication has occurred.
|
2017-12-05 01:51:59 +00:00
|
|
|
netConn, err = site.DialAuthServer()
|
2017-08-24 00:31:07 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, trace.Wrap(err)
|
|
|
|
}
|
|
|
|
// get the principal and authMethods we will use to authenticate on the
|
|
|
|
// remote cluster from our local cluster
|
2017-11-25 01:09:11 +00:00
|
|
|
agent, cert, err := c.GetAgent()
|
2017-08-24 00:31:07 +00:00
|
|
|
if err != nil {
|
2017-11-25 01:09:11 +00:00
|
|
|
netConn.Close()
|
2017-08-24 00:31:07 +00:00
|
|
|
return nil, trace.Wrap(err)
|
|
|
|
}
|
2017-11-25 01:09:11 +00:00
|
|
|
signers, err := agent.Signers()
|
2017-08-24 00:31:07 +00:00
|
|
|
if err != nil {
|
2017-11-25 01:09:11 +00:00
|
|
|
netConn.Close()
|
2017-08-24 00:31:07 +00:00
|
|
|
return nil, trace.Wrap(err)
|
|
|
|
}
|
|
|
|
// build a ssh connection to the remote auth server
|
|
|
|
config := &ssh.ClientConfig{
|
2017-11-25 01:09:11 +00:00
|
|
|
User: cert.ValidPrincipals[0],
|
|
|
|
Auth: []ssh.AuthMethod{ssh.PublicKeys(signers...)},
|
2017-08-24 00:31:07 +00:00
|
|
|
Timeout: defaults.DefaultDialTimeout,
|
|
|
|
}
|
|
|
|
sshConn, sshChans, sshReqs, err := ssh.NewClientConn(netConn, reversetunnel.RemoteAuthServer, config)
|
|
|
|
if err != nil {
|
|
|
|
err = netConn.Close()
|
|
|
|
if err != nil {
|
|
|
|
return nil, trace.Wrap(err)
|
|
|
|
}
|
|
|
|
return nil, trace.Wrap(err)
|
|
|
|
}
|
|
|
|
client := ssh.NewClient(sshConn, sshChans, sshReqs)
|
|
|
|
|
|
|
|
// dial again to the HTTP server
|
|
|
|
return client.Dial(network, addr)
|
|
|
|
}
|
|
|
|
|
|
|
|
clt, err := auth.NewClient("http://stub:0", sshDialer)
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, trace.Wrap(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return clt, netConn, nil
|
|
|
|
}
|
|
|
|
|
2016-02-25 01:58:22 +00:00
|
|
|
// GetUser returns the authenticated teleport user
|
2016-04-06 17:29:30 +00:00
|
|
|
func (c *SessionContext) GetUser() string {
|
2015-07-15 00:52:12 +00:00
|
|
|
return c.user
|
|
|
|
}
|
|
|
|
|
2016-02-25 01:58:22 +00:00
|
|
|
// GetWebSession returns a web session
|
2017-02-11 02:55:51 +00:00
|
|
|
func (c *SessionContext) GetWebSession() services.WebSession {
|
2016-02-24 01:26:23 +00:00
|
|
|
return c.sess
|
2015-07-15 00:52:12 +00:00
|
|
|
}
|
|
|
|
|
2016-04-10 20:29:32 +00:00
|
|
|
// ExtendWebSession creates a new web session for this user
|
2016-02-26 22:57:51 +00:00
|
|
|
// based on the previous session
|
2017-02-11 02:55:51 +00:00
|
|
|
func (c *SessionContext) ExtendWebSession() (services.WebSession, error) {
|
2017-02-11 18:48:29 +00:00
|
|
|
sess, err := c.clt.ExtendWebSession(c.user, c.sess.GetName())
|
2016-02-26 22:57:51 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, trace.Wrap(err)
|
|
|
|
}
|
|
|
|
return sess, nil
|
|
|
|
}
|
|
|
|
|
2017-11-25 01:09:11 +00:00
|
|
|
// GetAgent returns agent that can be used to answer challenges
|
|
|
|
// for the web to ssh connection as well as certificate
|
|
|
|
func (c *SessionContext) GetAgent() (agent.Agent, *ssh.Certificate, error) {
|
|
|
|
pub, _, _, _, err := ssh.ParseAuthorizedKey(c.sess.GetPub())
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, trace.Wrap(err)
|
|
|
|
}
|
|
|
|
cert, ok := pub.(*ssh.Certificate)
|
|
|
|
if !ok {
|
|
|
|
return nil, nil, trace.BadParameter("expected certificate, got %T", pub)
|
|
|
|
}
|
|
|
|
if len(cert.ValidPrincipals) == 0 {
|
|
|
|
return nil, nil, trace.BadParameter("expected at least valid principal in certificate")
|
|
|
|
}
|
|
|
|
privateKey, err := ssh.ParseRawPrivateKey(c.sess.GetPriv())
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, trace.Wrap(err, "failed to parse SSH private key")
|
|
|
|
}
|
|
|
|
|
|
|
|
keyring := agent.NewKeyring()
|
|
|
|
err = keyring.Add(agent.AddedKey{
|
|
|
|
PrivateKey: privateKey,
|
|
|
|
Certificate: cert,
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, trace.Wrap(err)
|
|
|
|
}
|
|
|
|
return keyring, cert, nil
|
2015-07-15 00:52:12 +00:00
|
|
|
}
|
|
|
|
|
2016-02-25 01:58:22 +00:00
|
|
|
// Close cleans up connections associated with requests
|
2016-04-06 17:29:30 +00:00
|
|
|
func (c *SessionContext) Close() error {
|
2016-02-27 02:10:01 +00:00
|
|
|
closers := c.TransferClosers()
|
|
|
|
for _, closer := range closers {
|
2017-11-25 01:09:11 +00:00
|
|
|
c.Debugf("Closing %v.", closer)
|
2016-02-27 02:10:01 +00:00
|
|
|
closer.Close()
|
|
|
|
}
|
2015-07-15 00:52:12 +00:00
|
|
|
if c.clt != nil {
|
2016-02-24 01:26:23 +00:00
|
|
|
return trace.Wrap(c.clt.Close())
|
2015-07-15 00:52:12 +00:00
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2016-04-07 22:00:11 +00:00
|
|
|
// newSessionCache returns new instance of the session cache
|
2018-06-08 23:50:43 +00:00
|
|
|
func newSessionCache(proxyClient auth.ClientI, servers []utils.NetAddr, clientTLSConfig *tls.Config) (*sessionCache, error) {
|
2018-01-03 18:40:44 +00:00
|
|
|
clusterName, err := proxyClient.GetClusterName()
|
|
|
|
if err != nil {
|
|
|
|
return nil, trace.Wrap(err)
|
|
|
|
}
|
2016-04-07 22:00:11 +00:00
|
|
|
m, err := ttlmap.New(1024, ttlmap.CallOnExpire(closeContext))
|
2015-07-15 00:52:12 +00:00
|
|
|
if err != nil {
|
2017-11-25 01:09:11 +00:00
|
|
|
return nil, trace.Wrap(err)
|
2015-07-15 00:52:12 +00:00
|
|
|
}
|
2016-04-07 22:00:11 +00:00
|
|
|
cache := &sessionCache{
|
2018-06-08 23:50:43 +00:00
|
|
|
clusterName: clusterName.GetClusterName(),
|
|
|
|
proxyClient: proxyClient,
|
|
|
|
contexts: m,
|
|
|
|
authServers: servers,
|
|
|
|
closer: utils.NewCloseBroadcaster(),
|
|
|
|
clientTLSConfig: clientTLSConfig,
|
2016-04-07 22:00:11 +00:00
|
|
|
}
|
|
|
|
// periodically close expired and unused sessions
|
|
|
|
go cache.expireSessions()
|
|
|
|
return cache, nil
|
2015-07-15 00:52:12 +00:00
|
|
|
}
|
|
|
|
|
2016-02-26 22:57:51 +00:00
|
|
|
// sessionCache handles web session authentication,
|
2016-02-25 01:58:22 +00:00
|
|
|
// and holds in memory contexts associated with each session
|
2016-02-26 22:57:51 +00:00
|
|
|
type sessionCache struct {
|
|
|
|
sync.Mutex
|
2017-11-25 01:09:11 +00:00
|
|
|
proxyClient auth.ClientI
|
2016-04-07 22:00:11 +00:00
|
|
|
contexts *ttlmap.TTLMap
|
2015-07-15 00:52:12 +00:00
|
|
|
authServers []utils.NetAddr
|
2016-04-07 22:00:11 +00:00
|
|
|
closer *utils.CloseBroadcaster
|
2017-11-25 01:09:11 +00:00
|
|
|
clusterName string
|
2018-06-08 23:50:43 +00:00
|
|
|
|
|
|
|
// clientTLSConfig is the TLS configuration the client uses.
|
|
|
|
clientTLSConfig *tls.Config
|
2016-04-07 22:00:11 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Close closes all allocated resources and stops goroutines
|
|
|
|
func (s *sessionCache) Close() error {
|
2016-08-22 06:16:04 +00:00
|
|
|
log.Infof("[WEB] closing session cache")
|
2016-04-07 22:00:11 +00:00
|
|
|
return s.closer.Close()
|
2015-07-15 00:52:12 +00:00
|
|
|
}
|
|
|
|
|
2016-02-25 01:58:22 +00:00
|
|
|
// closeContext is called when session context expires from
|
|
|
|
// cache and will clean up connections
|
|
|
|
func closeContext(key string, val interface{}) {
|
2016-04-07 22:00:11 +00:00
|
|
|
go func() {
|
|
|
|
log.Infof("[WEB] closing context %v", key)
|
|
|
|
ctx, ok := val.(*SessionContext)
|
|
|
|
if !ok {
|
|
|
|
log.Warningf("warning, not valid value type %T", val)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if err := ctx.Close(); err != nil {
|
|
|
|
log.Infof("failed to close context: %v", err)
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *sessionCache) expireSessions() {
|
|
|
|
ticker := time.NewTicker(time.Second)
|
2016-08-28 19:50:52 +00:00
|
|
|
defer ticker.Stop()
|
|
|
|
|
2016-10-12 20:40:08 +00:00
|
|
|
for {
|
|
|
|
select {
|
|
|
|
case <-ticker.C:
|
|
|
|
s.clearExpiredSessions()
|
|
|
|
case <-s.closer.C:
|
|
|
|
return
|
2016-04-07 22:00:11 +00:00
|
|
|
}
|
2016-10-12 20:40:08 +00:00
|
|
|
}
|
2016-04-07 22:00:11 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (s *sessionCache) clearExpiredSessions() {
|
|
|
|
s.Lock()
|
|
|
|
defer s.Unlock()
|
|
|
|
expired := s.contexts.RemoveExpired(10)
|
|
|
|
if expired != 0 {
|
|
|
|
log.Infof("[WEB] removed %v expired sessions", expired)
|
2015-08-16 20:55:00 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-03-22 02:08:27 +00:00
|
|
|
func (s *sessionCache) AuthWithOTP(user, pass string, otpToken string) (services.WebSession, error) {
|
2017-11-25 01:09:11 +00:00
|
|
|
return s.proxyClient.AuthenticateWebUser(auth.AuthenticateUserRequest{
|
|
|
|
Username: user,
|
|
|
|
OTP: &auth.OTPCreds{
|
|
|
|
Password: []byte(pass),
|
|
|
|
Token: otpToken,
|
|
|
|
},
|
|
|
|
})
|
2017-03-22 02:08:27 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (s *sessionCache) AuthWithoutOTP(user, pass string) (services.WebSession, error) {
|
2017-11-25 01:09:11 +00:00
|
|
|
return s.proxyClient.AuthenticateWebUser(auth.AuthenticateUserRequest{
|
|
|
|
Username: user,
|
|
|
|
Pass: &auth.PassCreds{
|
|
|
|
Password: []byte(pass),
|
|
|
|
},
|
|
|
|
})
|
2015-07-15 00:52:12 +00:00
|
|
|
}
|
|
|
|
|
2016-12-08 10:23:51 +00:00
|
|
|
func (s *sessionCache) GetU2FSignRequest(user, pass string) (*u2f.SignRequest, error) {
|
2017-11-25 01:09:11 +00:00
|
|
|
return s.proxyClient.GetU2FSignRequest(user, []byte(pass))
|
2016-10-14 06:51:16 +00:00
|
|
|
}
|
|
|
|
|
2017-02-11 02:55:51 +00:00
|
|
|
func (s *sessionCache) AuthWithU2FSignResponse(user string, response *u2f.SignResponse) (services.WebSession, error) {
|
2017-11-25 01:09:11 +00:00
|
|
|
return s.proxyClient.AuthenticateWebUser(auth.AuthenticateUserRequest{
|
|
|
|
Username: user,
|
|
|
|
U2F: &auth.U2FSignResponseCreds{
|
|
|
|
SignResponse: *response,
|
|
|
|
},
|
|
|
|
})
|
2017-03-22 02:08:27 +00:00
|
|
|
}
|
|
|
|
|
2017-11-25 01:09:11 +00:00
|
|
|
func (s *sessionCache) GetCertificateWithoutOTP(c client.CreateSSHCertReq) (*auth.SSHLoginResponse, error) {
|
|
|
|
return s.proxyClient.AuthenticateSSHUser(auth.AuthenticateSSHRequest{
|
|
|
|
AuthenticateUserRequest: auth.AuthenticateUserRequest{
|
|
|
|
Username: c.User,
|
|
|
|
Pass: &auth.PassCreds{
|
|
|
|
Password: []byte(c.Password),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
PublicKey: c.PubKey,
|
|
|
|
CompatibilityMode: c.Compatibility,
|
|
|
|
TTL: c.TTL,
|
|
|
|
})
|
2016-11-29 07:03:29 +00:00
|
|
|
}
|
|
|
|
|
2017-11-25 01:09:11 +00:00
|
|
|
func (s *sessionCache) GetCertificateWithOTP(c client.CreateSSHCertReq) (*auth.SSHLoginResponse, error) {
|
|
|
|
return s.proxyClient.AuthenticateSSHUser(auth.AuthenticateSSHRequest{
|
|
|
|
AuthenticateUserRequest: auth.AuthenticateUserRequest{
|
|
|
|
Username: c.User,
|
|
|
|
OTP: &auth.OTPCreds{
|
|
|
|
Password: []byte(c.Password),
|
|
|
|
Token: c.OTPToken,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
PublicKey: c.PubKey,
|
|
|
|
CompatibilityMode: c.Compatibility,
|
|
|
|
TTL: c.TTL,
|
|
|
|
})
|
2016-02-18 19:10:34 +00:00
|
|
|
|
2015-10-31 01:17:37 +00:00
|
|
|
}
|
|
|
|
|
2017-11-25 01:09:11 +00:00
|
|
|
func (s *sessionCache) GetCertificateWithU2F(c client.CreateSSHCertWithU2FReq) (*auth.SSHLoginResponse, error) {
|
|
|
|
return s.proxyClient.AuthenticateSSHUser(auth.AuthenticateSSHRequest{
|
|
|
|
AuthenticateUserRequest: auth.AuthenticateUserRequest{
|
|
|
|
Username: c.User,
|
|
|
|
U2F: &auth.U2FSignResponseCreds{
|
|
|
|
SignResponse: c.U2FSignResponse,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
PublicKey: c.PubKey,
|
|
|
|
CompatibilityMode: c.Compatibility,
|
|
|
|
TTL: c.TTL,
|
|
|
|
})
|
2015-10-31 01:17:37 +00:00
|
|
|
}
|
|
|
|
|
2017-01-17 19:24:17 +00:00
|
|
|
func (s *sessionCache) GetUserInviteInfo(token string) (user string, otpQRCode []byte, err error) {
|
2017-11-25 01:09:11 +00:00
|
|
|
return s.proxyClient.GetSignupTokenData(token)
|
2016-01-21 18:18:59 +00:00
|
|
|
}
|
|
|
|
|
2017-11-25 01:09:11 +00:00
|
|
|
func (s *sessionCache) GetUserInviteU2FRegisterRequest(token string) (*u2f.RegisterRequest, error) {
|
|
|
|
return s.proxyClient.GetSignupU2FRegisterRequest(token)
|
2016-10-14 06:51:16 +00:00
|
|
|
}
|
|
|
|
|
2017-02-11 02:55:51 +00:00
|
|
|
func (s *sessionCache) CreateNewUser(token, password, otpToken string) (services.WebSession, error) {
|
2017-11-25 01:09:11 +00:00
|
|
|
cap, err := s.proxyClient.GetAuthPreference()
|
2017-03-22 02:08:27 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, trace.Wrap(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
var webSession services.WebSession
|
|
|
|
|
|
|
|
switch cap.GetSecondFactor() {
|
|
|
|
case teleport.OFF:
|
2017-11-25 01:09:11 +00:00
|
|
|
webSession, err = s.proxyClient.CreateUserWithoutOTP(token, password)
|
2017-03-22 02:08:27 +00:00
|
|
|
case teleport.OTP, teleport.TOTP, teleport.HOTP:
|
2017-11-25 01:09:11 +00:00
|
|
|
webSession, err = s.proxyClient.CreateUserWithOTP(token, password, otpToken)
|
2017-03-22 02:08:27 +00:00
|
|
|
}
|
|
|
|
if err != nil {
|
|
|
|
return nil, trace.Wrap(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return webSession, nil
|
2016-01-21 18:18:59 +00:00
|
|
|
}
|
|
|
|
|
2017-02-11 02:55:51 +00:00
|
|
|
func (s *sessionCache) CreateNewU2FUser(token string, password string, u2fRegisterResponse u2f.RegisterResponse) (services.WebSession, error) {
|
2017-11-25 01:09:11 +00:00
|
|
|
return s.proxyClient.CreateUserWithU2FToken(token, password, u2fRegisterResponse)
|
2016-10-14 06:51:16 +00:00
|
|
|
}
|
|
|
|
|
2017-03-02 19:50:35 +00:00
|
|
|
func (s *sessionCache) ValidateTrustedCluster(validateRequest *auth.ValidateTrustedClusterRequest) (*auth.ValidateTrustedClusterResponse, error) {
|
2017-11-25 01:09:11 +00:00
|
|
|
return s.proxyClient.ValidateTrustedCluster(validateRequest)
|
2017-03-02 19:50:35 +00:00
|
|
|
}
|
|
|
|
|
2016-04-06 17:29:30 +00:00
|
|
|
func (s *sessionCache) InvalidateSession(ctx *SessionContext) error {
|
2016-02-26 22:57:51 +00:00
|
|
|
defer ctx.Close()
|
2017-02-11 18:48:29 +00:00
|
|
|
if err := s.resetContext(ctx.GetUser(), ctx.GetWebSession().GetName()); err != nil {
|
2016-02-26 22:57:51 +00:00
|
|
|
return trace.Wrap(err)
|
|
|
|
}
|
|
|
|
clt, err := ctx.GetClient()
|
|
|
|
if err != nil {
|
|
|
|
return trace.Wrap(err)
|
|
|
|
}
|
2017-02-11 18:48:29 +00:00
|
|
|
err = clt.DeleteWebSession(ctx.GetUser(), ctx.GetWebSession().GetName())
|
2016-02-26 22:57:51 +00:00
|
|
|
return trace.Wrap(err)
|
|
|
|
}
|
|
|
|
|
2016-04-06 17:29:30 +00:00
|
|
|
func (s *sessionCache) getContext(user, sid string) (*SessionContext, error) {
|
2016-02-26 22:57:51 +00:00
|
|
|
s.Lock()
|
|
|
|
defer s.Unlock()
|
|
|
|
|
2016-02-25 01:58:22 +00:00
|
|
|
val, ok := s.contexts.Get(user + sid)
|
2015-07-15 00:52:12 +00:00
|
|
|
if ok {
|
2016-04-06 17:29:30 +00:00
|
|
|
return val.(*SessionContext), nil
|
2015-07-15 00:52:12 +00:00
|
|
|
}
|
2016-04-12 17:54:24 +00:00
|
|
|
return nil, trace.NotFound("sessionContext not found")
|
2016-02-26 22:57:51 +00:00
|
|
|
}
|
|
|
|
|
2016-04-06 17:29:30 +00:00
|
|
|
func (s *sessionCache) insertContext(user, sid string, ctx *SessionContext, ttl time.Duration) (*SessionContext, error) {
|
2016-02-26 22:57:51 +00:00
|
|
|
s.Lock()
|
|
|
|
defer s.Unlock()
|
|
|
|
|
|
|
|
val, ok := s.contexts.Get(user + sid)
|
|
|
|
if ok && val != nil { // nil means that we've just invalidated the context now and set it to nil in the cache
|
2016-04-12 17:54:24 +00:00
|
|
|
return val.(*SessionContext), trace.AlreadyExists("exists")
|
2016-02-26 22:57:51 +00:00
|
|
|
}
|
2016-04-07 22:00:11 +00:00
|
|
|
if err := s.contexts.Set(user+sid, ctx, ttl); err != nil {
|
2016-02-26 22:57:51 +00:00
|
|
|
return nil, trace.Wrap(err)
|
|
|
|
}
|
|
|
|
return ctx, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *sessionCache) resetContext(user, sid string) error {
|
|
|
|
s.Lock()
|
|
|
|
defer s.Unlock()
|
2016-04-07 22:00:11 +00:00
|
|
|
context, ok := s.contexts.Remove(user + sid)
|
|
|
|
if ok {
|
|
|
|
closeContext(user+sid, context)
|
|
|
|
}
|
|
|
|
return nil
|
2016-02-26 22:57:51 +00:00
|
|
|
}
|
|
|
|
|
2016-04-06 17:29:30 +00:00
|
|
|
func (s *sessionCache) ValidateSession(user, sid string) (*SessionContext, error) {
|
2016-02-26 22:57:51 +00:00
|
|
|
ctx, err := s.getContext(user, sid)
|
|
|
|
if err == nil {
|
|
|
|
return ctx, nil
|
|
|
|
}
|
2017-11-25 01:09:11 +00:00
|
|
|
|
|
|
|
sess, err := s.proxyClient.AuthenticateWebUser(auth.AuthenticateUserRequest{
|
|
|
|
Username: user,
|
|
|
|
Session: &auth.SessionCreds{
|
|
|
|
ID: sid,
|
|
|
|
},
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return nil, trace.Wrap(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
ca, err := s.proxyClient.GetCertAuthority(services.CertAuthID{
|
|
|
|
Type: services.HostCA,
|
|
|
|
DomainName: s.clusterName,
|
|
|
|
}, false)
|
2015-07-15 00:52:12 +00:00
|
|
|
if err != nil {
|
2016-02-24 01:26:23 +00:00
|
|
|
return nil, trace.Wrap(err)
|
2015-07-15 00:52:12 +00:00
|
|
|
}
|
2017-11-25 01:09:11 +00:00
|
|
|
|
|
|
|
certPool, err := services.CertPool(ca)
|
2015-07-15 00:52:12 +00:00
|
|
|
if err != nil {
|
2016-02-24 01:26:23 +00:00
|
|
|
return nil, trace.Wrap(err)
|
2015-07-15 00:52:12 +00:00
|
|
|
}
|
2017-11-25 01:09:11 +00:00
|
|
|
|
2018-06-08 23:50:43 +00:00
|
|
|
tlsConfig := s.clientTLSConfig.Clone()
|
2017-11-25 01:09:11 +00:00
|
|
|
tlsCert, err := tls.X509KeyPair(sess.GetTLSCert(), sess.GetPriv())
|
|
|
|
if err != nil {
|
|
|
|
return nil, trace.Wrap(err, "failed to parse TLS cert and key")
|
|
|
|
}
|
|
|
|
tlsConfig.Certificates = []tls.Certificate{tlsCert}
|
|
|
|
tlsConfig.RootCAs = certPool
|
|
|
|
|
|
|
|
userClient, err := auth.NewTLSClient(s.authServers, tlsConfig)
|
2016-02-24 01:26:23 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, trace.Wrap(err)
|
2015-07-15 00:52:12 +00:00
|
|
|
}
|
2017-11-25 01:09:11 +00:00
|
|
|
|
2016-04-06 17:29:30 +00:00
|
|
|
c := &SessionContext{
|
2017-11-25 01:09:11 +00:00
|
|
|
clt: userClient,
|
2017-08-24 00:31:07 +00:00
|
|
|
remoteClt: make(map[string]auth.ClientI),
|
|
|
|
user: user,
|
|
|
|
sess: sess,
|
|
|
|
parent: s,
|
2015-07-15 00:52:12 +00:00
|
|
|
}
|
2016-02-26 22:57:51 +00:00
|
|
|
c.Entry = log.WithFields(log.Fields{
|
|
|
|
"user": user,
|
2017-02-18 00:16:44 +00:00
|
|
|
"sess": sess.GetShortName(),
|
2016-02-26 22:57:51 +00:00
|
|
|
})
|
2016-04-07 22:00:11 +00:00
|
|
|
|
2017-02-18 00:16:44 +00:00
|
|
|
ttl := utils.ToTTL(clockwork.NewRealClock(), sess.GetBearerTokenExpiryTime())
|
|
|
|
out, err := s.insertContext(user, sid, c, ttl)
|
2016-02-26 22:57:51 +00:00
|
|
|
if err != nil {
|
|
|
|
// this means that someone has just inserted the context, so
|
|
|
|
// close our extra context and return
|
2016-04-12 17:54:24 +00:00
|
|
|
if trace.IsAlreadyExists(err) {
|
2016-02-26 22:57:51 +00:00
|
|
|
defer c.Close()
|
|
|
|
return out, nil
|
|
|
|
}
|
2016-02-24 01:26:23 +00:00
|
|
|
return nil, trace.Wrap(err)
|
2015-07-15 00:52:12 +00:00
|
|
|
}
|
2016-02-26 22:57:51 +00:00
|
|
|
return out, nil
|
2015-07-15 00:52:12 +00:00
|
|
|
}
|
|
|
|
|
2016-02-26 22:57:51 +00:00
|
|
|
func (s *sessionCache) SetSession(w http.ResponseWriter, user, sid string) error {
|
2015-07-15 00:52:12 +00:00
|
|
|
d, err := EncodeCookie(user, sid)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
c := &http.Cookie{
|
2016-02-24 01:26:23 +00:00
|
|
|
Name: "session",
|
|
|
|
Value: d,
|
|
|
|
Path: "/",
|
|
|
|
HttpOnly: true,
|
2016-04-07 22:00:11 +00:00
|
|
|
Secure: true,
|
2015-07-15 00:52:12 +00:00
|
|
|
}
|
|
|
|
http.SetCookie(w, c)
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2016-02-26 22:57:51 +00:00
|
|
|
func (s *sessionCache) ClearSession(w http.ResponseWriter) {
|
2015-07-15 00:52:12 +00:00
|
|
|
http.SetCookie(w, &http.Cookie{
|
2016-02-24 01:26:23 +00:00
|
|
|
Name: "session",
|
|
|
|
Value: "",
|
|
|
|
Path: "/",
|
|
|
|
HttpOnly: true,
|
2016-04-07 22:00:11 +00:00
|
|
|
Secure: true,
|
2015-07-15 00:52:12 +00:00
|
|
|
})
|
|
|
|
}
|