Merge branch 'master' into sasha/where2

This commit is contained in:
Sasha Klizhentas 2017-08-27 13:14:27 -07:00
commit 1537fa2254
16 changed files with 336 additions and 83 deletions

View file

@ -120,7 +120,7 @@ func NewAPIServer(config *APIConfig) http.Handler {
srv.POST("/:version/tokens/register", srv.withAuth(srv.registerUsingToken))
srv.POST("/:version/tokens/register/auth", srv.withAuth(srv.registerNewAuthServer))
// Sesssions
// active sesssions
srv.POST("/:version/namespaces/:namespace/sessions", srv.withAuth(srv.createSession))
srv.PUT("/:version/namespaces/:namespace/sessions/:id", srv.withAuth(srv.updateSession))
srv.GET("/:version/namespaces/:namespace/sessions", srv.withAuth(srv.getSessions))
@ -181,6 +181,7 @@ func NewAPIServer(config *APIConfig) http.Handler {
// Audit logs AKA events
srv.POST("/:version/events", srv.withAuth(srv.emitAuditEvent))
srv.GET("/:version/events", srv.withAuth(srv.searchEvents))
srv.GET("/:version/events/session", srv.withAuth(srv.searchSessionEvents))
return httplib.RewritePaths(&srv.Router,
httplib.Rewrite("/v1/nodes", "/v1/namespaces/default/nodes"),
@ -1389,6 +1390,40 @@ func (s *APIServer) searchEvents(auth ClientI, w http.ResponseWriter, r *http.Re
return eventsList, nil
}
// searchSessionEvents only allows searching audit log for events related to session playback.
func (s *APIServer) searchSessionEvents(auth ClientI, w http.ResponseWriter, r *http.Request, p httprouter.Params, version string) (interface{}, error) {
var err error
// default values for "to" and "from" fields
to := time.Now().In(time.UTC) // now
from := to.AddDate(0, -1, 0) // one month ago
// parse query for "to" and "from"
query := r.URL.Query()
fromStr := query.Get("from")
if fromStr != "" {
from, err = time.Parse(time.RFC3339, fromStr)
if err != nil {
return nil, trace.BadParameter("from")
}
}
toStr := query.Get("to")
if toStr != "" {
to, err = time.Parse(time.RFC3339, toStr)
if err != nil {
return nil, trace.BadParameter("to")
}
}
// only pull back start and end events to build list of completed sessions
eventsList, err := auth.SearchSessionEvents(from, to)
if err != nil {
return nil, trace.Wrap(err)
}
return eventsList, nil
}
type auditEventReq struct {
Type string `json:"type"`
Fields events.EventFields `json:"fields"`

View file

@ -57,10 +57,7 @@ func (a *AuthWithRoles) currentUserAction(username string) error {
}
func (a *AuthWithRoles) GetSessions(namespace string) ([]session.Session, error) {
if err := a.action(namespace, services.KindSession, services.VerbList); err != nil {
return nil, trace.Wrap(err)
}
if err := a.action(namespace, services.KindSession, services.VerbRead); err != nil {
if err := a.action(namespace, services.KindSSHSession, services.VerbList); err != nil {
return nil, trace.Wrap(err)
}
@ -68,21 +65,21 @@ func (a *AuthWithRoles) GetSessions(namespace string) ([]session.Session, error)
}
func (a *AuthWithRoles) GetSession(namespace string, id session.ID) (*session.Session, error) {
if err := a.action(namespace, services.KindSession, services.VerbRead); err != nil {
if err := a.action(namespace, services.KindSSHSession, services.VerbRead); err != nil {
return nil, trace.Wrap(err)
}
return a.sessions.GetSession(namespace, id)
}
func (a *AuthWithRoles) CreateSession(s session.Session) error {
if err := a.action(s.Namespace, services.KindSession, services.VerbCreate); err != nil {
if err := a.action(s.Namespace, services.KindSSHSession, services.VerbCreate); err != nil {
return trace.Wrap(err)
}
return a.sessions.CreateSession(s)
}
func (a *AuthWithRoles) UpdateSession(req session.UpdateRequest) error {
if err := a.action(req.Namespace, services.KindSession, services.VerbUpdate); err != nil {
if err := a.action(req.Namespace, services.KindSSHSession, services.VerbUpdate); err != nil {
return trace.Wrap(err)
}
return a.sessions.UpdateSession(req)
@ -191,9 +188,6 @@ func (a *AuthWithRoles) GetNodes(namespace string) ([]services.Server, error) {
if err := a.action(namespace, services.KindNode, services.VerbList); err != nil {
return nil, trace.Wrap(err)
}
if err := a.action(namespace, services.KindNode, services.VerbRead); err != nil {
return nil, trace.Wrap(err)
}
return a.authServer.GetNodes(namespace)
}
@ -636,20 +630,20 @@ func (a *AuthWithRoles) EmitAuditEvent(eventType string, fields events.EventFiel
}
func (a *AuthWithRoles) PostSessionSlice(slice events.SessionSlice) error {
if err := a.action(slice.Namespace, services.KindSession, services.VerbCreate); err != nil {
if err := a.action(slice.Namespace, services.KindEvent, services.VerbCreate); err != nil {
return trace.Wrap(err)
}
if err := a.action(slice.Namespace, services.KindSession, services.VerbUpdate); err != nil {
if err := a.action(slice.Namespace, services.KindEvent, services.VerbUpdate); err != nil {
return trace.Wrap(err)
}
return a.alog.PostSessionSlice(slice)
}
func (a *AuthWithRoles) PostSessionChunk(namespace string, sid session.ID, reader io.Reader) error {
if err := a.action(namespace, services.KindSession, services.VerbCreate); err != nil {
if err := a.action(namespace, services.KindEvent, services.VerbCreate); err != nil {
return trace.Wrap(err)
}
if err := a.action(namespace, services.KindSession, services.VerbUpdate); err != nil {
if err := a.action(namespace, services.KindEvent, services.VerbUpdate); err != nil {
return trace.Wrap(err)
}
return a.alog.PostSessionChunk(namespace, sid, reader)
@ -672,15 +666,21 @@ func (a *AuthWithRoles) GetSessionEvents(namespace string, sid session.ID, after
}
func (a *AuthWithRoles) SearchEvents(from, to time.Time, query string) ([]events.EventFields, error) {
if err := a.action(defaults.Namespace, services.KindEvent, services.VerbRead); err != nil {
return nil, trace.Wrap(err)
}
if err := a.action(defaults.Namespace, services.KindEvent, services.VerbList); err != nil {
return nil, trace.Wrap(err)
}
return a.alog.SearchEvents(from, to, query)
}
func (a *AuthWithRoles) SearchSessionEvents(from, to time.Time) ([]events.EventFields, error) {
if err := a.action(defaults.Namespace, services.KindSession, services.VerbList); err != nil {
return nil, trace.Wrap(err)
}
return a.alog.SearchSessionEvents(from, to)
}
// GetNamespaces returns a list of namespaces
func (a *AuthWithRoles) GetNamespaces() ([]services.Namespace, error) {
if err := a.action(defaults.Namespace, services.KindNamespace, services.VerbList); err != nil {

View file

@ -1227,6 +1227,26 @@ func (c *Client) SearchEvents(from, to time.Time, query string) ([]events.EventF
return retval, nil
}
// SearchSessionEvents returns session related events to find completed sessions.
func (c *Client) SearchSessionEvents(from, to time.Time) ([]events.EventFields, error) {
query := url.Values{
"to": []string{to.Format(time.RFC3339)},
"from": []string{from.Format(time.RFC3339)},
}
response, err := c.Get(c.Endpoint("events", "session"), query)
if err != nil {
return nil, trace.Wrap(err)
}
retval := make([]events.EventFields, 0)
if err := json.Unmarshal(response.Bytes(), &retval); err != nil {
return nil, trace.Wrap(err)
}
return retval, nil
}
// GetNamespaces returns a list of namespaces
func (c *Client) GetNamespaces() ([]services.Namespace, error) {
out, err := c.Get(c.Endpoint("namespaces"), url.Values{})

View file

@ -167,7 +167,7 @@ func GetCheckerForBuiltinRole(role teleport.Role) (services.AccessChecker, error
Namespaces: []string{services.Wildcard},
Rules: []services.Rule{
services.NewRule(services.KindNode, services.RW()),
services.NewRule(services.KindSession, services.RW()),
services.NewRule(services.KindSSHSession, services.RW()),
services.NewRule(services.KindEvent, services.RW()),
services.NewRule(services.KindProxy, services.RO()),
services.NewRule(services.KindCertAuthority, services.RO()),
@ -188,7 +188,8 @@ func GetCheckerForBuiltinRole(role teleport.Role) (services.AccessChecker, error
Rules: []services.Rule{
services.NewRule(services.KindProxy, services.RW()),
services.NewRule(services.KindOIDCRequest, services.RW()),
services.NewRule(services.KindSession, services.RW()),
services.NewRule(services.KindSSHSession, services.RW()),
services.NewRule(services.KindSession, services.RO()),
services.NewRule(services.KindEvent, services.RW()),
services.NewRule(services.KindSAMLRequest, services.RW()),
services.NewRule(services.KindOIDC, services.RO()),
@ -214,7 +215,7 @@ func GetCheckerForBuiltinRole(role teleport.Role) (services.AccessChecker, error
Namespaces: []string{services.Wildcard},
Rules: []services.Rule{
services.NewRule(services.KindWebSession, services.RW()),
services.NewRule(services.KindSession, services.RW()),
services.NewRule(services.KindSSHSession, services.RW()),
services.NewRule(services.KindAuthServer, services.RO()),
services.NewRule(services.KindUser, services.RO()),
services.NewRule(services.KindRole, services.RO()),

View file

@ -141,6 +141,10 @@ type IAuditLog interface {
// The only mandatory requirement is a date range (UTC). Results must always
// show up sorted by date (newest first)
SearchEvents(fromUTC, toUTC time.Time, query string) ([]EventFields, error)
// SearchSessionEvents returns session related events only. This is used to
// find completed session.
SearchSessionEvents(fromUTC time.Time, toUTC time.Time) ([]EventFields, error)
}
// EventFields instance is attached to every logged event

View file

@ -428,7 +428,7 @@ func (l *AuditLog) EmitAuditEvent(eventType string, fields EventFields) error {
return nil
}
// Search finds events. Results show up sorted by date (newest first)
// SearchEvents finds events. Results show up sorted by date (newest first)
func (l *AuditLog) SearchEvents(fromUTC, toUTC time.Time, query string) ([]EventFields, error) {
log.Infof("auditLog.SearchEvents(%v, %v, query=%v)", fromUTC, toUTC, query)
queryVals, err := url.ParseQuery(query)
@ -477,6 +477,20 @@ func (l *AuditLog) SearchEvents(fromUTC, toUTC time.Time, query string) ([]Event
return events, nil
}
// SearchSessionEvents searches for session related events. Used to find completed sessions.
func (l *AuditLog) SearchSessionEvents(fromUTC, toUTC time.Time) ([]EventFields, error) {
log.Infof("auditLog.SearchSessionEvents(%v, %v)", fromUTC, toUTC)
// only search for specific event types
query := url.Values{}
query[EventType] = []string{
SessionStartEvent,
SessionEndEvent,
}
return l.SearchEvents(fromUTC, toUTC, query.Encode())
}
// byDate implements sort.Interface.
type byDate []os.FileInfo

View file

@ -34,3 +34,6 @@ func (d *DiscardAuditLog) GetSessionEvents(namespace string, sid session.ID, aft
func (d *DiscardAuditLog) SearchEvents(fromUTC, toUTC time.Time, query string) ([]EventFields, error) {
return make([]EventFields, 0), nil
}
func (d *DiscardAuditLog) SearchSessionEvents(fromUTC time.Time, toUTC time.Time) ([]EventFields, error) {
return make([]EventFields, 0), nil
}

View file

@ -85,3 +85,7 @@ func (d *MockAuditLog) GetSessionEvents(namespace string, sid session.ID, after
func (d *MockAuditLog) SearchEvents(fromUTC, toUTC time.Time, query string) ([]EventFields, error) {
return make([]EventFields, 0), nil
}
func (d *MockAuditLog) SearchSessionEvents(fromUTC, toUTC time.Time) ([]EventFields, error) {
return make([]EventFields, 0), nil
}

View file

@ -235,20 +235,51 @@ func (a *Agent) proxyTransport(ch ssh.Channel, reqC <-chan *ssh.Request) {
}
server := string(req.Payload)
log.Infof("got out of band request %v", server)
var servers []string
conn, err := net.Dial("tcp", server)
// if the request is for the special string @remote-auth-server, then get the
// list of auth servers and return that. otherwise try and connect to the
// passed in server.
if server == RemoteAuthServer {
authServers, err := a.clt.GetAuthServers()
if err != nil {
a.log.Errorf("unable to find auth servers: %v", err)
return
}
for _, as := range authServers {
servers = append(servers, as.GetAddr())
}
} else {
servers = append(servers, server)
}
log.Debugf("got out of band request %v", servers)
var conn net.Conn
var err error
// loop over all servers and try and connect to one of them
for _, s := range servers {
conn, err = net.Dial("tcp", s)
if err == nil {
break
}
// log the reason we were not able to connect
log.Debugf(trace.DebugReport(err))
}
// if we were not able to connect to any server, write the last connection
// error to stderr of the caller (via SSH channel) so the error will be
// propagated all the way back to the client (most likely tsh)
if err != nil {
log.Error(trace.DebugReport(err))
// write the connection error to stderr of the caller (via SSH channel)
// so the error will be propagated all the way back to the
// client (most likely tsh)
fmt.Fprint(ch.Stderr(), err.Error())
req.Reply(false, []byte(err.Error()))
return
}
req.Reply(true, []byte("connected"))
// successfully dialed
req.Reply(true, []byte("connected"))
a.log.Infof("successfully dialed to %v, start proxying", server)
wg := sync.WaitGroup{}
@ -378,3 +409,7 @@ const (
// at expected interval
RemoteSiteStatusOnline = "online"
)
// RemoteAuthServer is a special non-resolvable address that indicates we want
// a connection to the remote auth server.
const RemoteAuthServer = "@remote-auth-server"

View file

@ -70,9 +70,12 @@ const (
// KindOIDCReques is saml auth request resource
KindSAMLRequest = "saml_request"
// KindSession is a recorded session resource
// KindSession is a recorded SSH session.
KindSession = "session"
// KindSSHSession is an active SSH session.
KindSSHSession = "ssh_session"
// KindWebSession is a web session resource
KindWebSession = "web_session"

View file

@ -52,6 +52,9 @@ var DefaultImplicitRules = []Rule{
NewRule(KindAuthServer, RO()),
NewRule(KindReverseTunnel, RO()),
NewRule(KindCertAuthority, RO()),
NewRule(KindClusterAuthPreference, RO()),
NewRule(KindClusterName, RO()),
NewRule(KindSSHSession, RO()),
}
// DefaultCertAuthorityRules provides access the minimal set of resources
@ -981,7 +984,7 @@ func (r *RoleV2) CheckAndSetDefaults() error {
}
if r.Spec.Resources == nil {
r.Spec.Resources = map[string][]string{
KindSession: RO(),
KindSSHSession: RO(),
KindRole: RO(),
KindNode: RO(),
KindAuthServer: RO(),

View file

@ -356,15 +356,15 @@ func (s *RoleSuite) TestCheckRuleAccess(c *C) {
Allow: RoleConditions{
Namespaces: []string{defaults.Namespace},
Rules: []Rule{
NewRule(KindSession, []string{VerbRead}),
NewRule(KindSSHSession, []string{VerbRead}),
},
},
},
},
},
checks: []check{
{rule: KindSession, verb: VerbRead, namespace: defaults.Namespace, hasAccess: true},
{rule: KindSession, verb: VerbList, namespace: defaults.Namespace, hasAccess: false},
{rule: KindSSHSession, verb: VerbRead, namespace: defaults.Namespace, hasAccess: true},
{rule: KindSSHSession, verb: VerbList, namespace: defaults.Namespace, hasAccess: false},
},
},
{
@ -379,7 +379,7 @@ func (s *RoleSuite) TestCheckRuleAccess(c *C) {
Allow: RoleConditions{
Namespaces: []string{"system"},
Rules: []Rule{
NewRule(KindSession, []string{VerbRead}),
NewRule(KindSSHSession, []string{VerbRead}),
},
},
},
@ -393,16 +393,16 @@ func (s *RoleSuite) TestCheckRuleAccess(c *C) {
Allow: RoleConditions{
Namespaces: []string{defaults.Namespace},
Rules: []Rule{
NewRule(KindSession, []string{VerbCreate, VerbRead}),
NewRule(KindSSHSession, []string{VerbCreate, VerbRead}),
},
},
},
},
},
checks: []check{
{rule: KindSession, verb: VerbRead, namespace: defaults.Namespace, hasAccess: true},
{rule: KindSession, verb: VerbCreate, namespace: defaults.Namespace, hasAccess: true},
{rule: KindSession, verb: VerbCreate, namespace: "system", hasAccess: false},
{rule: KindSSHSession, verb: VerbRead, namespace: defaults.Namespace, hasAccess: true},
{rule: KindSSHSession, verb: VerbCreate, namespace: defaults.Namespace, hasAccess: true},
{rule: KindSSHSession, verb: VerbCreate, namespace: "system", hasAccess: false},
{rule: KindRole, verb: VerbRead, namespace: defaults.Namespace, hasAccess: false},
},
},
@ -418,20 +418,20 @@ func (s *RoleSuite) TestCheckRuleAccess(c *C) {
Deny: RoleConditions{
Namespaces: []string{defaults.Namespace},
Rules: []Rule{
NewRule(KindSession, []string{VerbCreate}),
NewRule(KindSSHSession, []string{VerbCreate}),
},
},
Allow: RoleConditions{
Namespaces: []string{defaults.Namespace},
Rules: []Rule{
NewRule(KindSession, []string{VerbCreate}),
NewRule(KindSSHSession, []string{VerbCreate}),
},
},
},
},
},
checks: []check{
{rule: KindSession, verb: VerbCreate, namespace: defaults.Namespace, hasAccess: false},
{rule: KindSSHSession, verb: VerbCreate, namespace: defaults.Namespace, hasAccess: false},
},
},
{

View file

@ -413,3 +413,6 @@ func (ll *CachingAuditLog) GetSessionEvents(string, session.ID, int) ([]events.E
func (ll *CachingAuditLog) SearchEvents(time.Time, time.Time, string) ([]events.EventFields, error) {
return nil, errNotSupported
}
func (ll *CachingAuditLog) SearchSessionEvents(time.Time, time.Time) ([]events.EventFields, error) {
return nil, errNotSupported
}

View file

@ -171,24 +171,19 @@ func NewHandler(cfg Config, opts ...HandlerOption) (*RewritingHandler, error) {
// get nodes
h.GET("/webapi/sites/:site/namespaces/:namespace/nodes", h.WithClusterAuth(h.getSiteNodes))
// connect to node via websocket (that's why it's a GET method)
h.GET("/webapi/sites/:site/namespaces/:namespace/connect", h.WithClusterAuth(h.siteNodeConnect))
// get session event stream
h.GET("/webapi/sites/:site/namespaces/:namespace/sessions/:sid/events/stream", h.WithClusterAuth(h.siteSessionStream))
// generate a new session
h.POST("/webapi/sites/:site/namespaces/:namespace/sessions", h.WithClusterAuth(h.siteSessionGenerate))
// update session parameters
h.PUT("/webapi/sites/:site/namespaces/:namespace/sessions/:sid", h.WithClusterAuth(h.siteSessionUpdate))
// get the session list
h.GET("/webapi/sites/:site/namespaces/:namespace/sessions", h.WithClusterAuth(h.siteSessionsGet))
// get a session
h.GET("/webapi/sites/:site/namespaces/:namespace/sessions/:sid", h.WithClusterAuth(h.siteSessionGet))
// get session's events
h.GET("/webapi/sites/:site/namespaces/:namespace/sessions/:sid/events", h.WithClusterAuth(h.siteSessionEventsGet))
// get session's bytestream
h.GET("/webapi/sites/:site/namespaces/:namespace/sessions/:sid/stream", h.siteSessionStreamGet)
// search site events
h.GET("/webapi/sites/:site/events", h.WithClusterAuth(h.siteEventsGet))
// active sessions handlers
h.GET("/webapi/sites/:site/namespaces/:namespace/connect", h.WithClusterAuth(h.siteNodeConnect)) // connect to an active session (via websocket)
h.GET("/webapi/sites/:site/namespaces/:namespace/sessions", h.WithClusterAuth(h.siteSessionsGet)) // get active list of sessions
h.POST("/webapi/sites/:site/namespaces/:namespace/sessions", h.WithClusterAuth(h.siteSessionGenerate)) // create active session metadata
h.GET("/webapi/sites/:site/namespaces/:namespace/sessions/:sid", h.WithClusterAuth(h.siteSessionGet)) // get active session metadata
h.PUT("/webapi/sites/:site/namespaces/:namespace/sessions/:sid", h.WithClusterAuth(h.siteSessionUpdate)) // update active session metadata (parameters)
h.GET("/webapi/sites/:site/namespaces/:namespace/sessions/:sid/events/stream", h.WithClusterAuth(h.siteSessionStream)) // get active session's byte stream (from events)
// recorded sessions handlers
h.GET("/webapi/sites/:site/events", h.WithClusterAuth(h.siteEventsGet)) // get recorded list of sessions (from events)
h.GET("/webapi/sites/:site/namespaces/:namespace/sessions/:sid/events", h.WithClusterAuth(h.siteSessionEventsGet)) // get recorded session's timing information (from events)
h.GET("/webapi/sites/:site/namespaces/:namespace/sessions/:sid/stream", h.siteSessionStreamGet) // get recorded session's bytes (from events)
// OIDC related callback handlers
h.GET("/webapi/oidc/login/web", httplib.MakeHandler(h.oidcLoginWeb))
@ -1208,7 +1203,7 @@ func (m *Handler) siteNodeConnect(
req.ProxyHostPort = m.ProxyHostPort()
req.Cluster = site.GetName()
clt, err := ctx.GetClient()
clt, err := ctx.GetUserClient(site)
if err != nil {
return nil, trace.Wrap(err)
}
@ -1366,7 +1361,7 @@ type siteSessionsGetResponse struct {
//
// {"sessions": [{"id": "sid", "terminal_params": {"w": 100, "h": 100}, "parties": [], "login": "bob"}, ...] }
func (m *Handler) siteSessionsGet(w http.ResponseWriter, r *http.Request, p httprouter.Params, ctx *SessionContext, site reversetunnel.RemoteSite) (interface{}, error) {
clt, err := site.GetClient()
clt, err := ctx.GetUserClient(site)
if err != nil {
return nil, trace.Wrap(err)
}
@ -1397,7 +1392,8 @@ func (m *Handler) siteSessionGet(w http.ResponseWriter, r *http.Request, p httpr
return nil, trace.Wrap(err)
}
log.Infof("web.getSetssion(%v)", sessionID)
clt, err := site.GetClient()
clt, err := ctx.GetUserClient(site)
if err != nil {
return nil, trace.Wrap(err)
}
@ -1431,11 +1427,13 @@ func (m *Handler) siteEventsGet(w http.ResponseWriter, r *http.Request, p httpro
query := r.URL.Query()
log.Infof("web.getEvents(%v)", r.URL.RawQuery)
clt, err := site.GetClient()
clt, err := ctx.GetUserClient(site)
if err != nil {
log.Error(err)
return nil, trace.Wrap(err)
}
// default values
to := time.Now().In(time.UTC)
from := to.AddDate(0, -1, 0) // one month ago
@ -1454,11 +1452,8 @@ func (m *Handler) siteEventsGet(w http.ResponseWriter, r *http.Request, p httpro
return nil, trace.BadParameter("to")
}
}
// remove to & from fields, and pass the rest of it directly to the back-end:
query.Del("to")
query.Del("from")
el, err := clt.SearchEvents(from, to, query.Encode())
el, err := clt.SearchSessionEvents(from, to)
if err != nil {
return nil, trace.Wrap(err)
}
@ -1488,7 +1483,7 @@ func (m *Handler) siteSessionStreamGet(w http.ResponseWriter, r *http.Request, p
trace.WriteError(w, err)
}
// authenticate first:
_, err := m.AuthenticateRequest(w, r, true)
ctx, err := m.AuthenticateRequest(w, r, true)
if err != nil {
log.Info(err)
// clear session just in case if the authentication request is not valid
@ -1517,11 +1512,13 @@ func (m *Handler) siteSessionStreamGet(w http.ResponseWriter, r *http.Request, p
onError(trace.Wrap(err))
return
}
clt, err := site.GetClient()
clt, err := ctx.GetUserClient(site)
if err != nil {
onError(trace.Wrap(err))
return
}
// look at 'offset' parameter
query := r.URL.Query()
offset, _ := strconv.Atoi(query.Get("offset"))
@ -1541,6 +1538,7 @@ func (m *Handler) siteSessionStreamGet(w http.ResponseWriter, r *http.Request, p
onError(trace.BadParameter("invalid namespace %q", namespace))
return
}
// call the site API to get the chunk:
bytes, err := clt.GetSessionChunk(namespace, *sid, offset, max)
if err != nil {
@ -1586,7 +1584,8 @@ func (m *Handler) siteSessionEventsGet(w http.ResponseWriter, r *http.Request, p
if err != nil {
return nil, trace.Wrap(err)
}
clt, err := site.GetClient()
clt, err := ctx.GetUserClient(site)
if err != nil {
return nil, trace.Wrap(err)
}
@ -1751,6 +1750,7 @@ func (h *Handler) WithClusterAuth(fn ClusterHandler) httprouter.Handle {
log.Warn(err)
return nil, trace.Wrap(err)
}
return fn(w, r, p, ctx, site)
})
}

View file

@ -18,6 +18,7 @@ package web
import (
"io"
"net"
"net/http"
"sync"
"time"
@ -25,6 +26,8 @@ import (
"github.com/gravitational/teleport"
"github.com/gravitational/teleport/lib/auth"
"github.com/gravitational/teleport/lib/client"
"github.com/gravitational/teleport/lib/defaults"
"github.com/gravitational/teleport/lib/reversetunnel"
"github.com/gravitational/teleport/lib/services"
"github.com/gravitational/teleport/lib/session"
"github.com/gravitational/teleport/lib/utils"
@ -35,6 +38,7 @@ import (
log "github.com/sirupsen/logrus"
"github.com/tstranex/u2f"
"golang.org/x/crypto/ssh"
)
// SessionContext is a context associated with users'
@ -44,11 +48,12 @@ import (
type SessionContext struct {
sync.Mutex
*log.Entry
sess services.WebSession
user string
clt *auth.TunClient
parent *sessionCache
closers []io.Closer
sess services.WebSession
user string
clt *auth.TunClient
remoteClt map[string]auth.ClientI
parent *sessionCache
closers []io.Closer
}
// getTerminal finds and returns an active web terminal for a given session:
@ -117,11 +122,134 @@ func (c *SessionContext) Invalidate() error {
return c.parent.InvalidateSession(c)
}
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
}
// GetClient returns the client connected to the auth server
func (c *SessionContext) GetClient() (auth.ClientI, error) {
return c.clt, nil
}
// 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
c.AddClosers(rConn)
// 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.
func (c *SessionContext) newRemoteClient(site reversetunnel.RemoteSite) (auth.ClientI, net.Conn, error) {
var err error
var netConn net.Conn
sshDialer := func(network, addr string) (net.Conn, error) {
// we tell the remote site we want a connection to the auth server by using
// a non-resolvable string @remote-auth-server as the destination.
srcAddr := utils.NetAddr{Addr: "web.get-remote-client-with-user-role", AddrNetwork: "tcp"}
dstAddr := utils.NetAddr{Addr: reversetunnel.RemoteAuthServer, AddrNetwork: "tcp"}
// first get a net.Conn (tcp connection) to the remote auth server. no
// authentication has occured.
netConn, err = site.Dial(&srcAddr, &dstAddr)
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
agent, err := c.GetAgent()
if err != nil {
err = netConn.Close()
if err != nil {
return nil, trace.Wrap(err)
}
return nil, trace.Wrap(err)
}
principal, authMethods, err := getUserCredentials(agent)
if err != nil {
err = netConn.Close()
if err != nil {
return nil, trace.Wrap(err)
}
return nil, trace.Wrap(err)
}
// build a ssh connection to the remote auth server
config := &ssh.ClientConfig{
User: principal,
Auth: []ssh.AuthMethod{authMethods},
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
}
// GetUser returns the authenticated teleport user
func (c *SessionContext) GetUser() string {
return c.user
@ -541,10 +669,11 @@ func (s *sessionCache) ValidateSession(user, sid string) (*SessionContext, error
return nil, trace.Wrap(err)
}
c := &SessionContext{
clt: clt,
user: user,
sess: sess,
parent: s,
clt: clt,
remoteClt: make(map[string]auth.ClientI),
user: user,
sess: sess,
parent: s,
}
c.Entry = log.WithFields(log.Fields{
"user": user,

View file

@ -186,7 +186,7 @@ func (t *terminalHandler) Run(w http.ResponseWriter, r *http.Request) {
return
}
defer agent.Close()
principal, auth, err := t.getUserCredentials(agent)
principal, auth, err := getUserCredentials(agent)
if err != nil {
errToTerm(err, ws)
return
@ -242,8 +242,7 @@ func (t *terminalHandler) Run(w http.ResponseWriter, r *http.Request) {
// getUserCredentials retreives the SSH credentials (certificate) for the currently logged in user
// from the auth server API.
//
func (t *terminalHandler) getUserCredentials(agent auth.AgentCloser) (string, ssh.AuthMethod, error) {
func getUserCredentials(agent auth.AgentCloser) (string, ssh.AuthMethod, error) {
var (
cert *ssh.Certificate
pub ssh.PublicKey