mirror of
https://github.com/gravitational/teleport
synced 2024-10-21 17:53:28 +00:00
Merge pull request #1224 from gravitational/rjones/rule-fix
Session access controls
This commit is contained in:
commit
52117abf6b
|
@ -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"`
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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{})
|
||||
|
|
|
@ -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()),
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
||||
|
|
|
@ -51,6 +51,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
|
||||
|
@ -931,7 +934,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(),
|
||||
|
|
|
@ -343,15 +343,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},
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -366,7 +366,7 @@ func (s *RoleSuite) TestCheckRuleAccess(c *C) {
|
|||
Allow: RoleConditions{
|
||||
Namespaces: []string{"system"},
|
||||
Rules: []Rule{
|
||||
NewRule(KindSession, []string{VerbRead}),
|
||||
NewRule(KindSSHSession, []string{VerbRead}),
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -380,16 +380,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},
|
||||
},
|
||||
},
|
||||
|
@ -405,20 +405,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},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
})
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue