2015-10-31 18:56:49 +00:00
|
|
|
/*
|
2019-05-02 01:56:42 +00:00
|
|
|
Copyright 2015-2019 Gravitational, Inc.
|
2015-10-31 18:56:49 +00:00
|
|
|
|
|
|
|
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"
|
2021-02-04 15:50:18 +00:00
|
|
|
"fmt"
|
2016-02-27 02:10:01 +00:00
|
|
|
"io"
|
2017-08-24 00:31:07 +00:00
|
|
|
"net"
|
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"
|
|
|
|
|
2021-02-04 15:50:18 +00:00
|
|
|
"github.com/gravitational/teleport"
|
2020-12-29 17:24:16 +00:00
|
|
|
apiclient "github.com/gravitational/teleport/api/client"
|
|
|
|
"github.com/gravitational/teleport/api/client/proto"
|
2021-02-04 15:50:18 +00:00
|
|
|
"github.com/gravitational/teleport/api/types"
|
2015-10-05 17:36:55 +00:00
|
|
|
"github.com/gravitational/teleport/lib/auth"
|
2021-01-29 19:30:15 +00:00
|
|
|
"github.com/gravitational/teleport/lib/auth/u2f"
|
2021-02-04 15:50:18 +00:00
|
|
|
"github.com/gravitational/teleport/lib/backend"
|
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/reversetunnel"
|
2016-02-12 15:25:54 +00:00
|
|
|
"github.com/gravitational/teleport/lib/services"
|
2021-02-04 15:50:18 +00:00
|
|
|
"github.com/gravitational/teleport/lib/services/local"
|
2021-03-04 22:18:30 +00:00
|
|
|
"github.com/gravitational/teleport/lib/sshutils"
|
2020-05-05 18:57:19 +00:00
|
|
|
"github.com/gravitational/teleport/lib/tlsca"
|
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"
|
2018-05-04 00:36:08 +00:00
|
|
|
|
2017-11-25 01:09:11 +00:00
|
|
|
"github.com/jonboulle/clockwork"
|
2020-11-05 19:46:54 +00:00
|
|
|
"github.com/sirupsen/logrus"
|
2021-03-02 03:47:03 +00:00
|
|
|
"google.golang.org/grpc"
|
2015-07-15 00:52:12 +00:00
|
|
|
)
|
|
|
|
|
2021-02-04 15:50:18 +00:00
|
|
|
// SessionContext is a context associated with a user's
|
|
|
|
// web session. An instance of the context is created for
|
|
|
|
// each web session generated for the user and provides
|
|
|
|
// a basic client cache for remote auth server connections.
|
2016-04-06 17:29:30 +00:00
|
|
|
type SessionContext struct {
|
2021-02-04 15:50:18 +00:00
|
|
|
log logrus.FieldLogger
|
|
|
|
user string
|
2021-03-02 03:47:03 +00:00
|
|
|
clt *auth.Client
|
2021-02-04 15:50:18 +00:00
|
|
|
parent *sessionCache
|
|
|
|
// resources is persistent resource store this context is bound to.
|
|
|
|
// The store maintains a list of resources between session renewals
|
|
|
|
resources *sessionResources
|
|
|
|
// session refers the web session created for the user.
|
|
|
|
session services.WebSession
|
|
|
|
|
|
|
|
mu sync.Mutex
|
2017-08-24 00:31:07 +00:00
|
|
|
remoteClt map[string]auth.ClientI
|
2016-03-01 21:19:43 +00:00
|
|
|
}
|
|
|
|
|
2021-02-04 15:50:18 +00:00
|
|
|
// String returns the text representation of this context
|
|
|
|
func (c *SessionContext) String() string {
|
|
|
|
return fmt.Sprintf("WebSession(user=%v,id=%v,expires=%v,bearer=%v,bearer_expires=%v)",
|
|
|
|
c.user,
|
|
|
|
c.session.GetName(),
|
|
|
|
c.session.GetExpiryTime(),
|
|
|
|
c.session.GetBearerToken(),
|
|
|
|
c.session.GetBearerTokenExpiryTime(),
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
// AddClosers adds the specified closers to this context
|
2016-04-06 17:29:30 +00:00
|
|
|
func (c *SessionContext) AddClosers(closers ...io.Closer) {
|
2021-02-04 15:50:18 +00:00
|
|
|
c.resources.addClosers(closers...)
|
2016-02-27 02:10:01 +00:00
|
|
|
}
|
|
|
|
|
2021-02-04 15:50:18 +00:00
|
|
|
// RemoveCloser removes the specified closer from this context
|
2017-02-04 07:12:29 +00:00
|
|
|
func (c *SessionContext) RemoveCloser(closer io.Closer) {
|
2021-02-04 15:50:18 +00:00
|
|
|
c.resources.removeCloser(closer)
|
2017-02-04 07:12:29 +00:00
|
|
|
}
|
|
|
|
|
2021-02-04 15:50:18 +00:00
|
|
|
// Invalidate invalidates this context by removing the underlying session
|
|
|
|
// and closing all underlying closers
|
|
|
|
func (c *SessionContext) Invalidate() error {
|
|
|
|
return c.parent.invalidateSession(c)
|
2016-02-26 22:57:51 +00:00
|
|
|
}
|
|
|
|
|
2021-02-04 15:50:18 +00:00
|
|
|
func (c *SessionContext) validateBearerToken(ctx context.Context, token string) error {
|
|
|
|
_, err := c.parent.readBearerToken(ctx, types.GetWebTokenRequest{
|
|
|
|
User: c.user,
|
|
|
|
Token: token,
|
|
|
|
})
|
|
|
|
return trace.Wrap(err)
|
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) {
|
2021-02-04 15:50:18 +00:00
|
|
|
c.mu.Lock()
|
|
|
|
defer c.mu.Unlock()
|
2017-08-24 00:31:07 +00:00
|
|
|
c.remoteClt[siteName] = remoteClient
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *SessionContext) getRemoteClient(siteName string) (auth.ClientI, bool) {
|
2021-02-04 15:50:18 +00:00
|
|
|
c.mu.Lock()
|
|
|
|
defer c.mu.Unlock()
|
2017-08-24 00:31:07 +00:00
|
|
|
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
|
|
|
}
|
|
|
|
|
2021-03-02 03:47:03 +00:00
|
|
|
// GetClientConnection returns a connection to Auth Service
|
|
|
|
func (c *SessionContext) GetClientConnection() *grpc.ClientConn {
|
|
|
|
return c.clt.GetConnection()
|
|
|
|
}
|
|
|
|
|
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
|
2021-02-04 15:50:18 +00:00
|
|
|
clusterName, err := c.clt.GetClusterName()
|
2017-08-24 00:31:07 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, trace.Wrap(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// if we're trying to access the local cluster, pass back the local client.
|
2021-02-04 15:50:18 +00:00
|
|
|
if clusterName.GetClusterName() == site.GetName() {
|
|
|
|
return c.clt, nil
|
2017-08-24 00:31:07 +00:00
|
|
|
}
|
|
|
|
|
2021-02-04 15:50:18 +00:00
|
|
|
// check if we already have a connection to this cluster
|
2017-08-24 00:31:07 +00:00
|
|
|
remoteClt, ok := c.getRemoteClient(site.GetName())
|
|
|
|
if !ok {
|
2021-02-04 15:50:18 +00:00
|
|
|
rClt, err := c.newRemoteClient(site)
|
2017-08-24 00:31:07 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, trace.Wrap(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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.
|
2021-02-04 15:50:18 +00:00
|
|
|
func (c *SessionContext) newRemoteClient(cluster reversetunnel.RemoteSite) (auth.ClientI, error) {
|
2017-11-25 01:09:11 +00:00
|
|
|
clt, err := c.tryRemoteTLSClient(cluster)
|
2019-05-02 01:56:42 +00:00
|
|
|
if err != nil {
|
2021-02-04 15:50:18 +00:00
|
|
|
return nil, trace.Wrap(err)
|
2017-11-25 01:09:11 +00:00
|
|
|
}
|
2021-02-04 15:50:18 +00:00
|
|
|
return clt, nil
|
2017-11-25 01:09:11 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// clusterDialer returns DialContext function using cluster's dial function
|
2019-05-03 04:34:15 +00:00
|
|
|
func clusterDialer(remoteCluster reversetunnel.RemoteSite) auth.ContextDialer {
|
|
|
|
return auth.ContextDialerFunc(func(in context.Context, network, _ string) (net.Conn, error) {
|
2017-11-25 01:09:11 +00:00
|
|
|
return remoteCluster.DialAuthServer()
|
2019-05-03 04:34:15 +00:00
|
|
|
})
|
2017-11-25 01:09:11 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// 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-08-20 23:22:51 +00:00
|
|
|
tlsConfig := utils.TLSConfig(c.parent.cipherSuites)
|
2021-02-04 15:50:18 +00:00
|
|
|
tlsCert, err := tls.X509KeyPair(c.session.GetTLSCert(), c.session.GetPriv())
|
2017-11-25 01:09:11 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, trace.Wrap(err, "failed to parse TLS cert and key")
|
|
|
|
}
|
|
|
|
tlsConfig.Certificates = []tls.Certificate{tlsCert}
|
|
|
|
tlsConfig.RootCAs = certPool
|
2018-09-21 20:07:48 +00:00
|
|
|
tlsConfig.ServerName = auth.EncodeClusterName(c.parent.clusterName)
|
2021-02-04 15:50:18 +00:00
|
|
|
tlsConfig.Time = c.parent.clock.Now
|
2017-11-25 01:09:11 +00:00
|
|
|
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)
|
|
|
|
}
|
2021-02-23 00:43:00 +00:00
|
|
|
return auth.NewClient(apiclient.Config{
|
|
|
|
Dialer: clusterDialer(cluster),
|
|
|
|
Credentials: []apiclient.Credentials{
|
|
|
|
apiclient.LoadTLS(tlsConfig),
|
|
|
|
},
|
|
|
|
})
|
2017-08-24 00:31:07 +00:00
|
|
|
}
|
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2021-02-04 15:50:18 +00:00
|
|
|
// extendWebSession creates a new web session for this user
|
2016-02-26 22:57:51 +00:00
|
|
|
// based on the previous session
|
2021-02-04 15:50:18 +00:00
|
|
|
func (c *SessionContext) extendWebSession(accessRequestID string) (services.WebSession, error) {
|
|
|
|
session, err := c.clt.ExtendWebSession(c.user, c.session.GetName(), accessRequestID)
|
2016-02-26 22:57:51 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, trace.Wrap(err)
|
|
|
|
}
|
2021-02-04 15:50:18 +00:00
|
|
|
return session, nil
|
2016-02-26 22:57:51 +00:00
|
|
|
}
|
|
|
|
|
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) {
|
2021-01-27 18:56:32 +00:00
|
|
|
cert, err := c.GetSSHCertificate()
|
2017-11-25 01:09:11 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, nil, trace.Wrap(err)
|
|
|
|
}
|
|
|
|
if len(cert.ValidPrincipals) == 0 {
|
|
|
|
return nil, nil, trace.BadParameter("expected at least valid principal in certificate")
|
|
|
|
}
|
2021-02-04 15:50:18 +00:00
|
|
|
privateKey, err := ssh.ParseRawPrivateKey(c.session.GetPriv())
|
2017-11-25 01:09:11 +00:00
|
|
|
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
|
|
|
}
|
|
|
|
|
2021-01-27 18:56:32 +00:00
|
|
|
// GetSSHCertificate returns the *ssh.Certificate associated with this session.
|
|
|
|
func (c *SessionContext) GetSSHCertificate() (*ssh.Certificate, error) {
|
2021-03-04 22:18:30 +00:00
|
|
|
return sshutils.ParseCertificate(c.session.GetPub())
|
2021-01-27 18:56:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// GetX509Certificate returns the *x509.Certificate associated with this session.
|
|
|
|
func (c *SessionContext) GetX509Certificate() (*x509.Certificate, error) {
|
2021-02-04 15:50:18 +00:00
|
|
|
tlsCert, err := tlsca.ParseCertificatePEM(c.session.GetTLSCert())
|
2019-08-02 00:19:49 +00:00
|
|
|
if err != nil {
|
2021-01-27 18:56:32 +00:00
|
|
|
return nil, trace.Wrap(err)
|
2019-08-02 00:19:49 +00:00
|
|
|
}
|
2021-01-27 18:56:32 +00:00
|
|
|
return tlsCert, nil
|
|
|
|
}
|
2021-02-04 15:50:18 +00:00
|
|
|
|
2021-01-27 18:56:32 +00:00
|
|
|
// GetCertRoles extracts roles from the *ssh.Certificate associated with this
|
|
|
|
// session.
|
|
|
|
func (c *SessionContext) GetCertRoles() (services.RoleSet, error) {
|
|
|
|
cert, err := c.GetSSHCertificate()
|
|
|
|
if err != nil {
|
|
|
|
return nil, trace.Wrap(err)
|
|
|
|
}
|
|
|
|
roles, traits, err := services.ExtractFromCertificate(c.clt, cert)
|
|
|
|
if err != nil {
|
|
|
|
return nil, trace.Wrap(err)
|
|
|
|
}
|
|
|
|
roleset, err := services.FetchRoles(roles, c.clt, traits)
|
|
|
|
if err != nil {
|
|
|
|
return nil, trace.Wrap(err)
|
|
|
|
}
|
|
|
|
return roleset, nil
|
2021-02-04 15:50:18 +00:00
|
|
|
}
|
2019-08-02 00:19:49 +00:00
|
|
|
|
2021-02-04 15:50:18 +00:00
|
|
|
// GetSessionID returns the ID of the underlying user web session.
|
|
|
|
func (c *SessionContext) GetSessionID() string {
|
|
|
|
return c.session.GetName()
|
2019-08-02 00:19:49 +00:00
|
|
|
}
|
|
|
|
|
2021-02-04 15:50:18 +00:00
|
|
|
// Close cleans up resources associated with this context and removes it
|
|
|
|
// from the user context
|
2016-04-06 17:29:30 +00:00
|
|
|
func (c *SessionContext) Close() error {
|
2021-02-04 15:50:18 +00:00
|
|
|
c.mu.Lock()
|
|
|
|
defer c.mu.Unlock()
|
|
|
|
var errors []error
|
|
|
|
for _, clt := range c.remoteClt {
|
|
|
|
if err := clt.Close(); err != nil {
|
|
|
|
errors = append(errors, err)
|
|
|
|
}
|
2016-02-27 02:10:01 +00:00
|
|
|
}
|
2021-02-04 15:50:18 +00:00
|
|
|
if err := c.clt.Close(); err != nil {
|
|
|
|
errors = append(errors, err)
|
2015-07-15 00:52:12 +00:00
|
|
|
}
|
2021-02-04 15:50:18 +00:00
|
|
|
return trace.NewAggregate(errors...)
|
|
|
|
}
|
|
|
|
|
|
|
|
// getToken returns the bearer token associated with the underlying
|
|
|
|
// session. Note that sessions are separate from bearer tokens and this
|
|
|
|
// is only useful immediately after a session has been created to query
|
|
|
|
// the token.
|
|
|
|
func (c *SessionContext) getToken() types.WebToken {
|
|
|
|
return types.NewWebToken(c.session.GetBearerTokenExpiryTime(), types.WebTokenSpecV3{
|
|
|
|
Token: c.session.GetBearerToken(),
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
// expired returns whether this context has expired.
|
|
|
|
// The context is considered expired when its bearer token TTL
|
|
|
|
// is in the past (subject to lingering threshold)
|
|
|
|
func (c *SessionContext) expired(ctx context.Context) bool {
|
|
|
|
_, err := c.parent.readSession(ctx, types.GetWebSessionRequest{
|
|
|
|
User: c.user,
|
|
|
|
SessionID: c.session.GetName(),
|
|
|
|
})
|
|
|
|
if err == nil {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
expiry := c.session.GetBearerTokenExpiryTime()
|
|
|
|
if expiry.IsZero() {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
if !trace.IsNotFound(err) {
|
|
|
|
c.log.WithError(err).Debug("Failed to query web session.")
|
|
|
|
}
|
|
|
|
// Give the session some time to linger so existing users of the context
|
|
|
|
// have successfully disposed of them.
|
|
|
|
// If we remove the session immediately, a stale copy might still use the
|
|
|
|
// cached site clients.
|
|
|
|
// This is a cheaper way to avoid race without introducing object
|
|
|
|
// reference counters.
|
|
|
|
return c.parent.clock.Since(expiry) > c.parent.sessionLingeringThreshold
|
|
|
|
}
|
|
|
|
|
|
|
|
// cachedSessionLingeringThreshold specifies the maximum amount of time the session cache
|
|
|
|
// will hold onto a session before removing it. This period allows all outstanding references
|
|
|
|
// to disappear without fear of racing with the removal
|
|
|
|
const cachedSessionLingeringThreshold = 2 * time.Minute
|
|
|
|
|
|
|
|
type sessionCacheOptions struct {
|
|
|
|
proxyClient auth.ClientI
|
|
|
|
accessPoint auth.ReadAccessPoint
|
|
|
|
servers []utils.NetAddr
|
|
|
|
cipherSuites []uint16
|
|
|
|
clock clockwork.Clock
|
|
|
|
// sessionLingeringThreshold specifies the time the session will linger
|
|
|
|
// in the cache before getting purged after it has expired
|
|
|
|
sessionLingeringThreshold time.Duration
|
2015-07-15 00:52:12 +00:00
|
|
|
}
|
|
|
|
|
2016-04-07 22:00:11 +00:00
|
|
|
// newSessionCache returns new instance of the session cache
|
2021-02-04 15:50:18 +00:00
|
|
|
func newSessionCache(config sessionCacheOptions) (*sessionCache, error) {
|
2021-01-12 11:10:00 +00:00
|
|
|
clusterName, err := config.proxyClient.GetClusterName()
|
2018-01-03 18:40:44 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, trace.Wrap(err)
|
|
|
|
}
|
2021-01-12 11:10:00 +00:00
|
|
|
if config.clock == nil {
|
|
|
|
config.clock = clockwork.NewRealClock()
|
|
|
|
}
|
2016-04-07 22:00:11 +00:00
|
|
|
cache := &sessionCache{
|
2021-02-04 15:50:18 +00:00
|
|
|
clusterName: clusterName.GetClusterName(),
|
|
|
|
proxyClient: config.proxyClient,
|
|
|
|
accessPoint: config.accessPoint,
|
|
|
|
sessions: make(map[string]*SessionContext),
|
|
|
|
resources: make(map[string]*sessionResources),
|
|
|
|
authServers: config.servers,
|
|
|
|
closer: utils.NewCloseBroadcaster(),
|
|
|
|
cipherSuites: config.cipherSuites,
|
|
|
|
log: newPackageLogger(),
|
|
|
|
clock: config.clock,
|
|
|
|
sessionLingeringThreshold: config.sessionLingeringThreshold,
|
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,
|
2021-02-04 15:50:18 +00:00
|
|
|
// and holds in-memory contexts associated with each session
|
2016-02-26 22:57:51 +00:00
|
|
|
type sessionCache struct {
|
2021-02-04 15:50:18 +00:00
|
|
|
log logrus.FieldLogger
|
2017-11-25 01:09:11 +00:00
|
|
|
proxyClient auth.ClientI
|
2015-07-15 00:52:12 +00:00
|
|
|
authServers []utils.NetAddr
|
2021-02-04 15:50:18 +00:00
|
|
|
accessPoint auth.ReadAccessPoint
|
2016-04-07 22:00:11 +00:00
|
|
|
closer *utils.CloseBroadcaster
|
2017-11-25 01:09:11 +00:00
|
|
|
clusterName string
|
2021-01-12 11:10:00 +00:00
|
|
|
clock clockwork.Clock
|
2021-02-04 15:50:18 +00:00
|
|
|
// sessionLingeringThreshold specifies the time the session will linger
|
|
|
|
// in the cache before getting purged after it has expired
|
|
|
|
sessionLingeringThreshold time.Duration
|
2018-08-20 23:22:51 +00:00
|
|
|
// cipherSuites is the list of supported TLS cipher suites.
|
|
|
|
cipherSuites []uint16
|
2021-02-04 15:50:18 +00:00
|
|
|
|
|
|
|
mu sync.Mutex
|
|
|
|
// sessions maps user/sessionID to an active web session value between renewals.
|
|
|
|
// This is the client-facing session handle
|
|
|
|
sessions map[string]*SessionContext
|
|
|
|
|
|
|
|
// session cache maintains a list of resources per-user as long
|
|
|
|
// as the user session is active even though individual session values
|
|
|
|
// are periodically recycled.
|
|
|
|
// Resources are disposed of when the corresponding session
|
|
|
|
// is either explicitly invalidated (e.g. during logout) or the
|
|
|
|
// resources are themselves closing
|
|
|
|
resources map[string]*sessionResources
|
2016-04-07 22:00:11 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Close closes all allocated resources and stops goroutines
|
|
|
|
func (s *sessionCache) Close() error {
|
2020-11-05 19:46:54 +00:00
|
|
|
s.log.Info("Closing session cache.")
|
2016-04-07 22:00:11 +00:00
|
|
|
return s.closer.Close()
|
2015-07-15 00:52:12 +00:00
|
|
|
}
|
|
|
|
|
2016-04-07 22:00:11 +00:00
|
|
|
func (s *sessionCache) expireSessions() {
|
2021-02-04 15:50:18 +00:00
|
|
|
ticker := s.clock.NewTicker(1 * time.Second)
|
2016-08-28 19:50:52 +00:00
|
|
|
defer ticker.Stop()
|
|
|
|
|
2016-10-12 20:40:08 +00:00
|
|
|
for {
|
|
|
|
select {
|
2021-02-04 15:50:18 +00:00
|
|
|
case <-ticker.Chan():
|
|
|
|
s.clearExpiredSessions(context.TODO())
|
2016-10-12 20:40:08 +00:00
|
|
|
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
|
|
|
}
|
|
|
|
|
2021-02-04 15:50:18 +00:00
|
|
|
func (s *sessionCache) clearExpiredSessions(ctx context.Context) {
|
|
|
|
s.mu.Lock()
|
|
|
|
defer s.mu.Unlock()
|
|
|
|
for _, c := range s.sessions {
|
|
|
|
if !c.expired(ctx) {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
s.removeSessionContextLocked(c.session.GetUser(), c.session.GetName())
|
|
|
|
s.log.WithField("ctx", c.String()).Debug("Context expired.")
|
2015-08-16 20:55:00 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-02-04 15:50:18 +00:00
|
|
|
// AuthWithOTP authenticates the specified user with the given password and OTP token.
|
|
|
|
// Returns a new web session if successful.
|
|
|
|
func (s *sessionCache) AuthWithOTP(user, pass, otpToken string) (services.WebSession, error) {
|
2017-11-25 01:09:11 +00:00
|
|
|
return s.proxyClient.AuthenticateWebUser(auth.AuthenticateUserRequest{
|
|
|
|
Username: user,
|
2021-02-17 00:24:23 +00:00
|
|
|
Pass: &auth.PassCreds{Password: []byte(pass)},
|
2017-11-25 01:09:11 +00:00
|
|
|
OTP: &auth.OTPCreds{
|
|
|
|
Password: []byte(pass),
|
|
|
|
Token: otpToken,
|
|
|
|
},
|
|
|
|
})
|
2017-03-22 02:08:27 +00:00
|
|
|
}
|
|
|
|
|
2021-02-04 15:50:18 +00:00
|
|
|
// AuthWithoutOTP authenticates the specified user with the given password.
|
|
|
|
// Returns a new web session if successful.
|
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
|
|
|
}
|
|
|
|
|
2021-02-17 00:24:23 +00:00
|
|
|
func (s *sessionCache) GetMFAAuthenticateChallenge(user, pass string) (*auth.MFAAuthenticateChallenge, error) {
|
|
|
|
return s.proxyClient.GetMFAAuthenticateChallenge(user, []byte(pass))
|
2016-10-14 06:51:16 +00:00
|
|
|
}
|
|
|
|
|
2021-01-29 19:30:15 +00:00
|
|
|
func (s *sessionCache) AuthWithU2FSignResponse(user string, response *u2f.AuthenticateChallengeResponse) (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
|
|
|
}
|
|
|
|
|
2021-02-04 15:50:18 +00:00
|
|
|
// GetCertificateWithoutOTP returns a new user certificate for the specified request.
|
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,
|
2020-05-19 01:01:00 +00:00
|
|
|
RouteToCluster: c.RouteToCluster,
|
2020-09-29 22:05:21 +00:00
|
|
|
KubernetesCluster: c.KubernetesCluster,
|
2017-11-25 01:09:11 +00:00
|
|
|
})
|
2016-11-29 07:03:29 +00:00
|
|
|
}
|
|
|
|
|
2021-02-04 15:50:18 +00:00
|
|
|
// GetCertificateWithOTP returns a new user certificate for the specified request.
|
|
|
|
// The request is used with the given OTP token.
|
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,
|
2020-05-19 01:01:00 +00:00
|
|
|
RouteToCluster: c.RouteToCluster,
|
2020-09-29 22:05:21 +00:00
|
|
|
KubernetesCluster: c.KubernetesCluster,
|
2017-11-25 01:09:11 +00:00
|
|
|
})
|
2015-10-31 01:17:37 +00:00
|
|
|
}
|
|
|
|
|
2021-02-17 00:24:23 +00:00
|
|
|
func (s *sessionCache) GetCertificateWithMFA(c client.CreateSSHCertWithMFAReq) (*auth.SSHLoginResponse, error) {
|
|
|
|
authReq := auth.AuthenticateUserRequest{
|
|
|
|
Username: c.User,
|
|
|
|
}
|
|
|
|
if c.Password != "" {
|
|
|
|
authReq.Pass = &auth.PassCreds{Password: []byte(c.Password)}
|
|
|
|
}
|
|
|
|
if c.U2FSignResponse != nil {
|
|
|
|
authReq.U2F = &auth.U2FSignResponseCreds{
|
|
|
|
SignResponse: *c.U2FSignResponse,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if c.TOTPCode != "" {
|
|
|
|
authReq.OTP = &auth.OTPCreds{
|
|
|
|
Password: []byte(c.Password),
|
|
|
|
Token: c.TOTPCode,
|
|
|
|
}
|
|
|
|
}
|
2017-11-25 01:09:11 +00:00
|
|
|
return s.proxyClient.AuthenticateSSHUser(auth.AuthenticateSSHRequest{
|
2021-02-17 00:24:23 +00:00
|
|
|
AuthenticateUserRequest: authReq,
|
|
|
|
PublicKey: c.PubKey,
|
|
|
|
CompatibilityMode: c.Compatibility,
|
|
|
|
TTL: c.TTL,
|
|
|
|
RouteToCluster: c.RouteToCluster,
|
|
|
|
KubernetesCluster: c.KubernetesCluster,
|
2017-11-25 01:09:11 +00:00
|
|
|
})
|
2015-10-31 01:17:37 +00:00
|
|
|
}
|
|
|
|
|
2020-03-27 15:53:04 +00:00
|
|
|
// Ping gets basic info about the auth server.
|
|
|
|
func (s *sessionCache) Ping(ctx context.Context) (proto.PingResponse, error) {
|
|
|
|
return s.proxyClient.Ping(ctx)
|
|
|
|
}
|
|
|
|
|
2021-01-29 19:30:15 +00:00
|
|
|
func (s *sessionCache) GetUserInviteU2FRegisterRequest(token string) (*u2f.RegisterChallenge, error) {
|
2017-11-25 01:09:11 +00:00
|
|
|
return s.proxyClient.GetSignupU2FRegisterRequest(token)
|
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
|
|
|
}
|
|
|
|
|
2021-02-04 15:50:18 +00:00
|
|
|
// validateSession validates the session given with user and session ID.
|
|
|
|
// Returns a new or existing session context.
|
|
|
|
func (s *sessionCache) validateSession(ctx context.Context, user, sessionID string) (*SessionContext, error) {
|
|
|
|
sessionCtx, err := s.getContext(user, sessionID)
|
|
|
|
if err == nil {
|
|
|
|
return sessionCtx, nil
|
2016-02-26 22:57:51 +00:00
|
|
|
}
|
2021-02-04 15:50:18 +00:00
|
|
|
if !trace.IsNotFound(err) {
|
|
|
|
return nil, trace.Wrap(err)
|
|
|
|
}
|
|
|
|
return s.newSessionContext(user, sessionID)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *sessionCache) invalidateSession(ctx *SessionContext) error {
|
|
|
|
defer ctx.Close()
|
2016-02-26 22:57:51 +00:00
|
|
|
clt, err := ctx.GetClient()
|
|
|
|
if err != nil {
|
|
|
|
return trace.Wrap(err)
|
|
|
|
}
|
2021-02-04 15:50:18 +00:00
|
|
|
// Delete just the session - leave the bearer token to linger to avoid
|
|
|
|
// failing a client query still using the old token.
|
|
|
|
err = clt.WebSessions().Delete(context.TODO(), types.DeleteWebSessionRequest{
|
|
|
|
User: ctx.user,
|
|
|
|
SessionID: ctx.session.GetName(),
|
|
|
|
})
|
|
|
|
if err != nil && !trace.IsNotFound(err) {
|
|
|
|
return trace.Wrap(err)
|
|
|
|
}
|
|
|
|
if err := s.releaseResources(ctx.GetUser(), ctx.session.GetName()); err != nil {
|
|
|
|
return trace.Wrap(err)
|
|
|
|
}
|
|
|
|
return nil
|
2016-02-26 22:57:51 +00:00
|
|
|
}
|
|
|
|
|
2021-02-04 15:50:18 +00:00
|
|
|
func (s *sessionCache) getContext(user, sessionID string) (*SessionContext, error) {
|
|
|
|
s.mu.Lock()
|
|
|
|
defer s.mu.Unlock()
|
|
|
|
ctx, ok := s.sessions[user+sessionID]
|
2015-07-15 00:52:12 +00:00
|
|
|
if ok {
|
2021-02-04 15:50:18 +00:00
|
|
|
return ctx, nil
|
2015-07-15 00:52:12 +00:00
|
|
|
}
|
2021-02-04 15:50:18 +00:00
|
|
|
return nil, trace.NotFound("no context for user %v and session %v",
|
|
|
|
user, sessionID)
|
2016-02-26 22:57:51 +00:00
|
|
|
}
|
|
|
|
|
2021-02-04 15:50:18 +00:00
|
|
|
func (s *sessionCache) insertContext(user string, ctx *SessionContext) (exists bool) {
|
|
|
|
s.mu.Lock()
|
|
|
|
defer s.mu.Unlock()
|
|
|
|
id := sessionKey(user, ctx.session.GetName())
|
|
|
|
if _, exists := s.sessions[id]; exists {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
s.sessions[id] = ctx
|
|
|
|
return false
|
|
|
|
}
|
2016-02-26 22:57:51 +00:00
|
|
|
|
2021-02-04 15:50:18 +00:00
|
|
|
func (s *sessionCache) releaseResources(user, sessionID string) error {
|
|
|
|
s.mu.Lock()
|
|
|
|
defer s.mu.Unlock()
|
|
|
|
return s.releaseResourcesLocked(user, sessionID)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *sessionCache) removeSessionContextLocked(user, sessionID string) error {
|
|
|
|
id := sessionKey(user, sessionID)
|
|
|
|
ctx, ok := s.sessions[id]
|
|
|
|
if !ok {
|
|
|
|
return nil
|
2016-02-26 22:57:51 +00:00
|
|
|
}
|
2021-02-04 15:50:18 +00:00
|
|
|
delete(s.sessions, id)
|
|
|
|
err := ctx.Close()
|
|
|
|
if err != nil {
|
|
|
|
s.log.WithFields(logrus.Fields{
|
|
|
|
"ctx": ctx.String(),
|
|
|
|
logrus.ErrorKey: err,
|
|
|
|
}).Warn("Failed to close session context.")
|
|
|
|
return trace.Wrap(err)
|
2016-02-26 22:57:51 +00:00
|
|
|
}
|
2021-02-04 15:50:18 +00:00
|
|
|
return nil
|
2016-02-26 22:57:51 +00:00
|
|
|
}
|
|
|
|
|
2021-02-04 15:50:18 +00:00
|
|
|
func (s *sessionCache) releaseResourcesLocked(user, sessionID string) error {
|
|
|
|
var errors []error
|
|
|
|
err := s.removeSessionContextLocked(user, sessionID)
|
|
|
|
if err != nil {
|
|
|
|
errors = append(errors, err)
|
2016-04-07 22:00:11 +00:00
|
|
|
}
|
2021-02-04 15:50:18 +00:00
|
|
|
if ctx, ok := s.resources[user]; ok {
|
|
|
|
delete(s.resources, user)
|
|
|
|
if err := ctx.Close(); err != nil {
|
|
|
|
s.log.WithError(err).Warn("Failed to clean up session context.")
|
|
|
|
errors = append(errors, err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return trace.NewAggregate(errors...)
|
2016-02-26 22:57:51 +00:00
|
|
|
}
|
|
|
|
|
2021-02-04 15:50:18 +00:00
|
|
|
func (s *sessionCache) upsertSessionContext(user string) *sessionResources {
|
|
|
|
s.mu.Lock()
|
|
|
|
defer s.mu.Unlock()
|
|
|
|
if ctx, exists := s.resources[user]; exists {
|
|
|
|
return ctx
|
2016-02-26 22:57:51 +00:00
|
|
|
}
|
2021-02-04 15:50:18 +00:00
|
|
|
ctx := &sessionResources{
|
|
|
|
log: s.log.WithFields(logrus.Fields{
|
|
|
|
trace.Component: "user-session",
|
|
|
|
"user": user,
|
|
|
|
}),
|
|
|
|
}
|
|
|
|
s.resources[user] = ctx
|
|
|
|
return ctx
|
|
|
|
}
|
2017-11-25 01:09:11 +00:00
|
|
|
|
2021-02-04 15:50:18 +00:00
|
|
|
// newSessionContext creates a new web session context for the specified user/session ID
|
|
|
|
func (s *sessionCache) newSessionContext(user, sessionID string) (*SessionContext, error) {
|
|
|
|
session, err := s.proxyClient.AuthenticateWebUser(auth.AuthenticateUserRequest{
|
2017-11-25 01:09:11 +00:00
|
|
|
Username: user,
|
|
|
|
Session: &auth.SessionCreds{
|
2021-02-04 15:50:18 +00:00
|
|
|
ID: sessionID,
|
2017-11-25 01:09:11 +00:00
|
|
|
},
|
|
|
|
})
|
|
|
|
if err != nil {
|
2021-02-04 15:50:18 +00:00
|
|
|
// This will fail if the session has expired and was removed
|
2017-11-25 01:09:11 +00:00
|
|
|
return nil, trace.Wrap(err)
|
|
|
|
}
|
2021-02-04 15:50:18 +00:00
|
|
|
return s.newSessionContextFromSession(session)
|
|
|
|
}
|
2017-11-25 01:09:11 +00:00
|
|
|
|
2021-02-04 15:50:18 +00:00
|
|
|
func (s *sessionCache) newSessionContextFromSession(session services.WebSession) (*SessionContext, error) {
|
|
|
|
tlsConfig, err := s.tlsConfig(session.GetTLSCert(), session.GetPriv())
|
|
|
|
if err != nil {
|
|
|
|
return nil, trace.Wrap(err)
|
|
|
|
}
|
|
|
|
userClient, err := auth.NewTLSClient(auth.ClientConfig{
|
|
|
|
Addrs: s.authServers,
|
|
|
|
TLS: tlsConfig,
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return nil, trace.Wrap(err)
|
|
|
|
}
|
2021-03-02 03:47:03 +00:00
|
|
|
|
2021-02-04 15:50:18 +00:00
|
|
|
ctx := &SessionContext{
|
|
|
|
clt: userClient,
|
|
|
|
remoteClt: make(map[string]auth.ClientI),
|
|
|
|
user: session.GetUser(),
|
|
|
|
session: session,
|
|
|
|
parent: s,
|
|
|
|
resources: s.upsertSessionContext(session.GetUser()),
|
|
|
|
log: s.log.WithFields(logrus.Fields{
|
|
|
|
"user": session.GetUser(),
|
|
|
|
"session": session.GetShortName(),
|
|
|
|
}),
|
|
|
|
}
|
|
|
|
if err != nil {
|
|
|
|
return nil, trace.Wrap(err)
|
|
|
|
}
|
|
|
|
if exists := s.insertContext(session.GetUser(), ctx); exists {
|
|
|
|
// this means that someone has just inserted the context, so
|
|
|
|
// close our extra context and return
|
|
|
|
ctx.Close()
|
|
|
|
}
|
2021-03-02 03:47:03 +00:00
|
|
|
|
2021-02-04 15:50:18 +00:00
|
|
|
return ctx, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *sessionCache) tlsConfig(cert, privKey []byte) (*tls.Config, error) {
|
2017-11-25 01:09:11 +00:00
|
|
|
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
|
|
|
}
|
2018-08-20 23:22:51 +00:00
|
|
|
tlsConfig := utils.TLSConfig(s.cipherSuites)
|
2021-02-04 15:50:18 +00:00
|
|
|
tlsCert, err := tls.X509KeyPair(cert, privKey)
|
2017-11-25 01:09:11 +00:00
|
|
|
if err != nil {
|
2021-02-04 15:50:18 +00:00
|
|
|
return nil, trace.Wrap(err, "failed to parse TLS certificate and key")
|
2017-11-25 01:09:11 +00:00
|
|
|
}
|
|
|
|
tlsConfig.Certificates = []tls.Certificate{tlsCert}
|
|
|
|
tlsConfig.RootCAs = certPool
|
2018-09-21 20:07:48 +00:00
|
|
|
tlsConfig.ServerName = auth.EncodeClusterName(s.clusterName)
|
2021-01-12 11:10:00 +00:00
|
|
|
tlsConfig.Time = s.clock.Now
|
2021-02-04 15:50:18 +00:00
|
|
|
return tlsConfig, nil
|
|
|
|
}
|
2017-11-25 01:09:11 +00:00
|
|
|
|
2021-02-04 15:50:18 +00:00
|
|
|
func (s *sessionCache) readSession(ctx context.Context, req types.GetWebSessionRequest) (types.WebSession, error) {
|
|
|
|
// Read session from the cache first
|
|
|
|
session, err := s.accessPoint.GetWebSession(ctx, req)
|
|
|
|
if err == nil {
|
|
|
|
return session, nil
|
2015-07-15 00:52:12 +00:00
|
|
|
}
|
2021-02-04 15:50:18 +00:00
|
|
|
// Fallback to proxy otherwise
|
|
|
|
return s.proxyClient.GetWebSession(ctx, req)
|
|
|
|
}
|
2017-11-25 01:09:11 +00:00
|
|
|
|
2021-02-04 15:50:18 +00:00
|
|
|
func (s *sessionCache) readBearerToken(ctx context.Context, req types.GetWebTokenRequest) (types.WebToken, error) {
|
|
|
|
// Read token from the cache first
|
|
|
|
token, err := s.accessPoint.GetWebToken(ctx, req)
|
|
|
|
if err == nil {
|
|
|
|
return token, nil
|
2015-07-15 00:52:12 +00:00
|
|
|
}
|
2021-02-04 15:50:18 +00:00
|
|
|
// Fallback to proxy otherwise
|
|
|
|
return s.proxyClient.GetWebToken(ctx, req)
|
|
|
|
}
|
2016-04-07 22:00:11 +00:00
|
|
|
|
2021-02-04 15:50:18 +00:00
|
|
|
// Close releases all underlying resources for the user session.
|
|
|
|
func (c *sessionResources) Close() error {
|
|
|
|
closers := c.transferClosers()
|
|
|
|
var errors []error
|
|
|
|
for _, closer := range closers {
|
|
|
|
c.log.Debugf("Closing %v.", closer)
|
|
|
|
if err := closer.Close(); err != nil {
|
|
|
|
errors = append(errors, err)
|
2016-02-26 22:57:51 +00:00
|
|
|
}
|
2015-07-15 00:52:12 +00:00
|
|
|
}
|
2021-02-04 15:50:18 +00:00
|
|
|
return trace.NewAggregate(errors...)
|
2015-07-15 00:52:12 +00:00
|
|
|
}
|
|
|
|
|
2021-02-04 15:50:18 +00:00
|
|
|
// sessionResources persists resources initiated by a web session
|
|
|
|
// but which might outlive the session.
|
|
|
|
type sessionResources struct {
|
|
|
|
log logrus.FieldLogger
|
|
|
|
|
|
|
|
mu sync.Mutex
|
|
|
|
closers []io.Closer
|
|
|
|
}
|
|
|
|
|
|
|
|
// addClosers adds the specified closers to this context
|
|
|
|
func (c *sessionResources) addClosers(closers ...io.Closer) {
|
|
|
|
c.mu.Lock()
|
|
|
|
defer c.mu.Unlock()
|
|
|
|
c.closers = append(c.closers, closers...)
|
|
|
|
}
|
|
|
|
|
|
|
|
// removeCloser removes the specified closer from this context
|
|
|
|
func (c *sessionResources) removeCloser(closer io.Closer) {
|
|
|
|
c.mu.Lock()
|
|
|
|
defer c.mu.Unlock()
|
|
|
|
for i, cls := range c.closers {
|
|
|
|
if cls == closer {
|
|
|
|
c.closers = append(c.closers[:i], c.closers[i+1:]...)
|
|
|
|
return
|
|
|
|
}
|
2015-07-15 00:52:12 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-02-04 15:50:18 +00:00
|
|
|
func (c *sessionResources) transferClosers() []io.Closer {
|
|
|
|
c.mu.Lock()
|
|
|
|
defer c.mu.Unlock()
|
|
|
|
closers := c.closers
|
|
|
|
c.closers = nil
|
|
|
|
return closers
|
|
|
|
}
|
|
|
|
|
|
|
|
func sessionKey(user, sessionID string) string {
|
|
|
|
return user + sessionID
|
|
|
|
}
|
|
|
|
|
|
|
|
// waitForWebSession will block until the requested web session shows up in the
|
|
|
|
// cache or a timeout occurs.
|
|
|
|
func (h *Handler) waitForWebSession(ctx context.Context, req types.GetWebSessionRequest) error {
|
|
|
|
_, err := h.cfg.AccessPoint.GetWebSession(ctx, req)
|
|
|
|
if err == nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
logger := h.log.WithField("req", req)
|
|
|
|
if !trace.IsNotFound(err) {
|
|
|
|
logger.WithError(err).Debug("Failed to query web session.")
|
|
|
|
}
|
|
|
|
// Establish a watch.
|
|
|
|
watcher, err := h.cfg.AccessPoint.NewWatcher(ctx, services.Watch{
|
|
|
|
Name: teleport.ComponentWebProxy,
|
|
|
|
Kinds: []services.WatchKind{
|
|
|
|
{
|
|
|
|
Kind: services.KindWebSession,
|
|
|
|
SubKind: services.KindWebSession,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
MetricComponent: teleport.ComponentWebProxy,
|
2015-07-15 00:52:12 +00:00
|
|
|
})
|
2021-02-04 15:50:18 +00:00
|
|
|
if err != nil {
|
|
|
|
return trace.Wrap(err)
|
|
|
|
}
|
|
|
|
defer watcher.Close()
|
|
|
|
matchEvent := func(event services.Event) (services.Resource, error) {
|
|
|
|
if event.Type == backend.OpPut &&
|
|
|
|
event.Resource.GetKind() == services.KindWebSession &&
|
|
|
|
event.Resource.GetSubKind() == services.KindWebSession &&
|
|
|
|
event.Resource.GetName() == req.SessionID {
|
|
|
|
return event.Resource, nil
|
|
|
|
}
|
|
|
|
return nil, trace.CompareFailed("no match")
|
|
|
|
}
|
|
|
|
_, err = local.WaitForEvent(ctx, watcher, local.EventMatcherFunc(matchEvent), h.clock)
|
|
|
|
if err != nil {
|
|
|
|
logger.WithError(err).Warn("Failed to wait for web session.")
|
|
|
|
}
|
|
|
|
return trace.Wrap(err)
|
2015-07-15 00:52:12 +00:00
|
|
|
}
|