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 (
|
|
|
|
"net/http"
|
2016-02-26 22:57:51 +00:00
|
|
|
"sync"
|
|
|
|
"time"
|
2015-07-15 00:52:12 +00:00
|
|
|
|
2016-02-26 22:57:51 +00:00
|
|
|
"github.com/gravitational/teleport"
|
2015-10-05 17:36:55 +00:00
|
|
|
"github.com/gravitational/teleport/lib/auth"
|
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-02-03 01:53:21 +00:00
|
|
|
log "github.com/Sirupsen/logrus"
|
2016-01-20 15:52:25 +00:00
|
|
|
"github.com/gravitational/trace"
|
|
|
|
"github.com/mailgun/ttlmap"
|
|
|
|
"golang.org/x/crypto/ssh"
|
2015-07-15 00:52:12 +00:00
|
|
|
)
|
|
|
|
|
2016-02-25 01:58:22 +00:00
|
|
|
// sessionContext is a context associated with users'
|
|
|
|
// web session, it stores connected client that persists
|
|
|
|
// between requests for example to avoid connecting
|
|
|
|
// to the auth server on every page hit
|
|
|
|
type sessionContext struct {
|
2016-02-26 22:57:51 +00:00
|
|
|
*log.Entry
|
|
|
|
sess *auth.Session
|
|
|
|
user string
|
|
|
|
clt *auth.TunClient
|
|
|
|
parent *sessionCache
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *sessionContext) Invalidate() error {
|
|
|
|
return c.parent.InvalidateSession(c)
|
2015-07-15 00:52:12 +00:00
|
|
|
}
|
|
|
|
|
2016-02-25 01:58:22 +00:00
|
|
|
// GetClient returns the client connected to the auth server
|
|
|
|
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
|
|
|
}
|
|
|
|
|
2016-02-25 01:58:22 +00:00
|
|
|
// GetUser returns the authenticated teleport user
|
|
|
|
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
|
|
|
|
func (c *sessionContext) GetWebSession() *auth.Session {
|
2016-02-24 01:26:23 +00:00
|
|
|
return c.sess
|
2015-07-15 00:52:12 +00:00
|
|
|
}
|
|
|
|
|
2016-02-26 22:57:51 +00:00
|
|
|
// CreateNewWebSession creates a new web session for this user
|
|
|
|
// based on the previous session
|
|
|
|
func (c *sessionContext) CreateWebSession() (*auth.Session, error) {
|
|
|
|
sess, err := c.clt.CreateWebSession(c.user, c.sess.ID)
|
|
|
|
if err != nil {
|
|
|
|
return nil, trace.Wrap(err)
|
|
|
|
}
|
|
|
|
return sess, nil
|
|
|
|
}
|
|
|
|
|
2016-02-25 01:58:22 +00:00
|
|
|
// GetAuthMethods returns authentication methods (credentials) that proxy
|
|
|
|
// can use to connect to servers
|
|
|
|
func (c *sessionContext) GetAuthMethods() ([]ssh.AuthMethod, error) {
|
2015-07-15 00:52:12 +00:00
|
|
|
a, err := c.clt.GetAgent()
|
|
|
|
if err != nil {
|
2016-02-24 01:26:23 +00:00
|
|
|
return nil, trace.Wrap(err)
|
2015-07-15 00:52:12 +00:00
|
|
|
}
|
|
|
|
signers, err := a.Signers()
|
|
|
|
if err != nil {
|
2016-02-24 01:26:23 +00:00
|
|
|
return nil, trace.Wrap(err)
|
2015-07-15 00:52:12 +00:00
|
|
|
}
|
|
|
|
return []ssh.AuthMethod{ssh.PublicKeys(signers...)}, nil
|
|
|
|
}
|
|
|
|
|
2016-02-25 01:58:22 +00:00
|
|
|
// Close cleans up connections associated with requests
|
|
|
|
func (c *sessionContext) Close() error {
|
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-02-25 01:58:22 +00:00
|
|
|
// newSessionHandler returns new instance of the session handler
|
2016-02-26 22:57:51 +00:00
|
|
|
func newSessionHandler(secure bool, servers []utils.NetAddr) (*sessionCache, error) {
|
2016-02-25 01:58:22 +00:00
|
|
|
m, err := ttlmap.NewMap(1024, ttlmap.CallOnExpire(closeContext))
|
2015-07-15 00:52:12 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2016-02-26 22:57:51 +00:00
|
|
|
return &sessionCache{
|
2016-02-25 01:58:22 +00:00
|
|
|
contexts: m,
|
2015-07-15 00:52:12 +00:00
|
|
|
authServers: servers,
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
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
|
2016-02-24 01:26:23 +00:00
|
|
|
secure bool
|
2016-02-25 01:58:22 +00:00
|
|
|
contexts *ttlmap.TtlMap
|
2015-07-15 00:52:12 +00:00
|
|
|
authServers []utils.NetAddr
|
|
|
|
}
|
|
|
|
|
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{}) {
|
2015-08-16 20:55:00 +00:00
|
|
|
log.Infof("closing context %v", key)
|
2016-02-25 01:58:22 +00:00
|
|
|
ctx := val.(*sessionContext)
|
|
|
|
if err := ctx.Close(); err != nil {
|
|
|
|
log.Infof("failed to close context: %v", err)
|
2015-08-16 20:55:00 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-02-26 22:57:51 +00:00
|
|
|
func (s *sessionCache) Auth(user, pass string, hotpToken string) (*auth.Session, error) {
|
2015-10-23 00:45:51 +00:00
|
|
|
method, err := auth.NewWebPasswordAuth(user, []byte(pass), hotpToken)
|
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
|
|
|
}
|
|
|
|
clt, err := auth.NewTunClient(s.authServers[0], user, method)
|
|
|
|
if err != nil {
|
2016-02-24 01:26:23 +00:00
|
|
|
return nil, trace.Wrap(err)
|
2015-07-15 00:52:12 +00:00
|
|
|
}
|
2015-10-23 20:34:09 +00:00
|
|
|
return clt.SignIn(user, []byte(pass))
|
2015-07-15 00:52:12 +00:00
|
|
|
}
|
|
|
|
|
2016-02-26 22:57:51 +00:00
|
|
|
func (s *sessionCache) GetCertificate(c createSSHCertReq) (*SSHLoginResponse, error) {
|
2015-10-31 01:17:37 +00:00
|
|
|
method, err := auth.NewWebPasswordAuth(c.User, []byte(c.Password),
|
|
|
|
c.HOTPToken)
|
|
|
|
if err != nil {
|
2016-02-25 01:58:22 +00:00
|
|
|
return nil, trace.Wrap(err)
|
2015-10-31 01:17:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
clt, err := auth.NewTunClient(s.authServers[0], c.User, method)
|
|
|
|
if err != nil {
|
2016-02-25 01:58:22 +00:00
|
|
|
return nil, trace.Wrap(err)
|
2015-10-31 01:17:37 +00:00
|
|
|
}
|
2016-02-19 02:07:43 +00:00
|
|
|
cert, err := clt.GenerateUserCert(c.PubKey, c.User, c.TTL)
|
2015-10-31 01:17:37 +00:00
|
|
|
if err != nil {
|
2016-02-25 01:58:22 +00:00
|
|
|
return nil, trace.Wrap(err)
|
2016-02-12 15:25:54 +00:00
|
|
|
}
|
2016-02-18 19:10:34 +00:00
|
|
|
hostSigners, err := clt.GetCertAuthorities(services.HostCA)
|
2016-02-12 15:25:54 +00:00
|
|
|
if err != nil {
|
2016-02-25 01:58:22 +00:00
|
|
|
return nil, trace.Wrap(err)
|
2015-10-31 01:17:37 +00:00
|
|
|
}
|
|
|
|
|
2016-02-18 19:10:34 +00:00
|
|
|
signers := []services.CertAuthority{}
|
|
|
|
for _, hs := range hostSigners {
|
|
|
|
signers = append(signers, *hs)
|
|
|
|
}
|
|
|
|
|
2016-02-25 01:58:22 +00:00
|
|
|
return &SSHLoginResponse{
|
2016-02-12 15:25:54 +00:00
|
|
|
Cert: cert,
|
2016-02-18 19:10:34 +00:00
|
|
|
HostSigners: signers,
|
2016-02-12 15:25:54 +00:00
|
|
|
}, nil
|
2015-10-31 01:17:37 +00:00
|
|
|
}
|
|
|
|
|
2016-02-26 22:57:51 +00:00
|
|
|
func (s *sessionCache) GetUserInviteInfo(token string) (user string,
|
2016-01-22 19:05:46 +00:00
|
|
|
QRImg []byte, hotpFirstValues []string, e error) {
|
2016-01-21 18:18:59 +00:00
|
|
|
|
2016-01-21 22:08:41 +00:00
|
|
|
method, err := auth.NewSignupTokenAuth(token)
|
2016-01-21 18:18:59 +00:00
|
|
|
if err != nil {
|
2016-01-22 19:05:46 +00:00
|
|
|
return "", nil, nil, trace.Wrap(err)
|
2016-01-21 18:18:59 +00:00
|
|
|
}
|
|
|
|
clt, err := auth.NewTunClient(s.authServers[0], "tokenAuth", method)
|
|
|
|
if err != nil {
|
2016-01-22 19:05:46 +00:00
|
|
|
return "", nil, nil, trace.Wrap(err)
|
2016-01-21 18:18:59 +00:00
|
|
|
}
|
|
|
|
|
2016-01-21 19:41:04 +00:00
|
|
|
return clt.GetSignupTokenData(token)
|
2016-01-21 18:18:59 +00:00
|
|
|
}
|
|
|
|
|
2016-02-26 22:57:51 +00:00
|
|
|
func (s *sessionCache) CreateNewUser(token, password, hotpToken string) (*auth.Session, error) {
|
2016-01-21 22:08:41 +00:00
|
|
|
method, err := auth.NewSignupTokenAuth(token)
|
2016-01-21 18:18:59 +00:00
|
|
|
if err != nil {
|
2016-02-24 01:26:23 +00:00
|
|
|
return nil, trace.Wrap(err)
|
2016-01-21 18:18:59 +00:00
|
|
|
}
|
|
|
|
clt, err := auth.NewTunClient(s.authServers[0], "tokenAuth", method)
|
|
|
|
if err != nil {
|
2016-02-24 01:26:23 +00:00
|
|
|
return nil, trace.Wrap(err)
|
2016-01-21 18:18:59 +00:00
|
|
|
}
|
2016-02-24 01:26:23 +00:00
|
|
|
sess, err := clt.CreateUserWithToken(token, password, hotpToken)
|
|
|
|
return sess, trace.Wrap(err)
|
2016-01-21 18:18:59 +00:00
|
|
|
}
|
|
|
|
|
2016-02-26 22:57:51 +00:00
|
|
|
func (s *sessionCache) InvalidateSession(ctx *sessionContext) error {
|
|
|
|
defer ctx.Close()
|
|
|
|
if err := s.resetContext(ctx.GetUser(), ctx.GetWebSession().ID); err != nil {
|
|
|
|
return trace.Wrap(err)
|
|
|
|
}
|
|
|
|
clt, err := ctx.GetClient()
|
|
|
|
if err != nil {
|
|
|
|
return trace.Wrap(err)
|
|
|
|
}
|
|
|
|
err = clt.DeleteWebSession(ctx.GetUser(), ctx.GetWebSession().ID)
|
|
|
|
return trace.Wrap(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *sessionCache) getContext(user, sid string) (*sessionContext, error) {
|
|
|
|
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-02-25 01:58:22 +00:00
|
|
|
return val.(*sessionContext), nil
|
2015-07-15 00:52:12 +00:00
|
|
|
}
|
2016-02-26 22:57:51 +00:00
|
|
|
return nil, trace.Wrap(teleport.NotFound("sessionContext not found"))
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *sessionCache) insertContext(user, sid string, ctx *sessionContext, ttl time.Duration) (*sessionContext, error) {
|
|
|
|
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
|
|
|
|
return val.(*sessionContext), trace.Wrap(&teleport.AlreadyExistsError{})
|
|
|
|
}
|
|
|
|
if err := s.contexts.Set(user+sid, ctx, int(ttl/time.Second)); err != nil {
|
|
|
|
return nil, trace.Wrap(err)
|
|
|
|
}
|
|
|
|
return ctx, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *sessionCache) resetContext(user, sid string) error {
|
|
|
|
s.Lock()
|
|
|
|
defer s.Unlock()
|
|
|
|
return trace.Wrap(s.contexts.Set(user+sid, nil, 1))
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *sessionCache) ValidateSession(user, sid string) (*sessionContext, error) {
|
|
|
|
ctx, err := s.getContext(user, sid)
|
|
|
|
if err == nil {
|
|
|
|
ctx.Infof("got from cache")
|
|
|
|
return ctx, nil
|
|
|
|
}
|
2015-07-15 00:52:12 +00:00
|
|
|
method, err := auth.NewWebSessionAuth(user, []byte(sid))
|
|
|
|
if err != nil {
|
2016-02-24 01:26:23 +00:00
|
|
|
return nil, trace.Wrap(err)
|
2015-07-15 00:52:12 +00:00
|
|
|
}
|
|
|
|
clt, err := auth.NewTunClient(s.authServers[0], user, method)
|
|
|
|
if err != nil {
|
2016-02-24 01:26:23 +00:00
|
|
|
return nil, trace.Wrap(err)
|
2015-07-15 00:52:12 +00:00
|
|
|
}
|
2016-02-24 01:26:23 +00:00
|
|
|
sess, err := clt.GetWebSessionInfo(user, sid)
|
|
|
|
if err != nil {
|
|
|
|
return nil, trace.Wrap(err)
|
2015-07-15 00:52:12 +00:00
|
|
|
}
|
2016-02-25 01:58:22 +00:00
|
|
|
c := &sessionContext{
|
2016-02-26 22:57:51 +00:00
|
|
|
clt: clt,
|
|
|
|
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,
|
2016-02-26 23:24:38 +00:00
|
|
|
"sess": sess.ID[:4],
|
2016-02-26 22:57:51 +00:00
|
|
|
})
|
|
|
|
out, err := s.insertContext(user, sid, c, auth.WebSessionTTL)
|
|
|
|
if err != nil {
|
|
|
|
// this means that someone has just inserted the context, so
|
|
|
|
// close our extra context and return
|
|
|
|
if teleport.IsAlreadyExists(err) {
|
|
|
|
ctx.Infof("just created, returning the existing one")
|
|
|
|
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,
|
|
|
|
Secure: s.secure,
|
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,
|
|
|
|
Secure: s.secure,
|
2015-07-15 00:52:12 +00:00
|
|
|
})
|
|
|
|
}
|