Files
teleport/lib/web/command_utils.go
T
rosstimothy 553eb02bdf Fix moderated session presence checking (#25988)
* Fix moderated session presence checking

Addresses all of the issues that were preventing presence checking during
moderated sessions from working as described in
[#18092](https://github.com/gravitational/teleport/issues/18092#issuecomment-1540900859).

Closes #18092

* make presence test clearer

* fix presence checking on the web ui

* Refactor web socket message handling

A single message processing loop handles retrieving the envelope
and passing it off to individual message handlers. This allows all
messages to be processed outside of `Read` which was dependent on
the terminal being active to process any messages.

The webauthn challenge response was also moved from a raw message
to a webauthn message. By sending it as a raw message it made
presence checking fail because the response has a `t` in it which
caused the session to be killed during moderated sessions.

* enforce mfa ceremony when joining and cluster wide mfa is enabled

* fix conflicts with master

* moderated tests

* Add moderated session tests for the UI

* Add moderated session integration test

* fix lints

* clean up presence test

* refactor envelope handling

* fix build

* fix: revert test debug timeout

* fix: use local context in tests

* simplify closing streams

* generalize waitForOutput to work with an io.Reader

* fix error handling in stream close

* unexport PresenceOptions

* Improve waitForOutput to match against output in successive reads
2023-06-08 20:27:13 +00:00

108 lines
3.2 KiB
Go

/*
Copyright 2023 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.
*/
package web
import (
"encoding/json"
"io"
"net"
"time"
"github.com/gorilla/websocket"
"github.com/gravitational/trace"
)
// WSConn is a gorilla/websocket minimal interface used by our web implementation.
// This interface exists to override the default websocket.Conn implementation,
// currently used by noopCloserWS to prevent WS being closed by wrapping stream.
type WSConn interface {
Close() error
LocalAddr() net.Addr
RemoteAddr() net.Addr
WriteControl(messageType int, data []byte, deadline time.Time) error
WriteMessage(messageType int, data []byte) error
NextReader() (messageType int, r io.Reader, err error)
ReadMessage() (messageType int, p []byte, err error)
SetReadLimit(limit int64)
SetReadDeadline(t time.Time) error
PingHandler() func(appData string) error
SetPingHandler(h func(appData string) error)
PongHandler() func(appData string) error
SetPongHandler(h func(appData string) error)
}
// outEnvelope is an envelope used to wrap messages send back to the client connected over WS.
type outEnvelope struct {
NodeID string `json:"node_id"`
Type string `json:"type"`
Payload []byte `json:"payload"`
}
// payloadWriter is a wrapper around io.Writer, which wraps the given bytes into
// outEnvelope and writes it to the underlying stream.
type payloadWriter struct {
nodeID string
// output name, can be stdout, stderr or teleport-error.
outputName string
// stream is the underlying stream.
stream io.Writer
}
// Write writes the given bytes to the underlying stream.
func (p *payloadWriter) Write(b []byte) (n int, err error) {
out := &outEnvelope{
NodeID: p.nodeID,
Type: p.outputName,
Payload: b,
}
data, err := json.Marshal(out)
if err != nil {
return 0, trace.Wrap(err)
}
_, err = p.stream.Write(data)
// return the size of the original message as a message send over stream
// is larger due to json marshaling and envelope.
return len(b), trace.Wrap(err)
}
func newPayloadWriter(nodeID, outputName string, stream io.Writer) *payloadWriter {
return &payloadWriter{
nodeID: nodeID,
outputName: outputName,
stream: stream,
}
}
// noopCloserWS is a wrapper around websocket.Conn, which does nothing on Close().
// This struct is used to prevent WS being closed by wrapping stream.
// Currently, it is being used in Command web handler to prevent WS being closed
// by any underlying code as we want to keep the connection open until the command
// is executed on all nodes and a single failure should not close the connection.
type noopCloserWS struct {
*websocket.Conn
}
// Close does nothing.
func (ws *noopCloserWS) Close() error {
return nil
}