forked from gravitational/teleport
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
RDP client implementation (gravitational#7824)
* RDP client implementation This is a minimal RDP client on top of the rdp-rs Rust crate. The crate is wrapped with an FFI Rust adapter, statically compiled and called from Go via CGO. This PR also has toy web client to test the RDP code, and just enough of the desktop wire protocol implementation to make it work. I excluded the RDP client from the build via tags for now to avoid bloating the teleport binaries. Many more things missing that will come in later PRs, to keep this one reviewable. * flag tweaks and updated readme for macos * Switch to C-style strings between Go and Rust * Use regular top-level C function instead ofthe cgo jump function * Fix bitmap buffer memory leak * Consistent naming for CGO types * Pass rust object reference to Go * Clean up FFI error string management * Extract a thin C wrapper for read_rdp_output * Use the log crate in rust * Small rust cleanups * Fix shellcheck nit in run.sh * Fix RDP client memory release * Allow Rust code to compile on any unix * Force Alpha channel to 100% always For some reason, on Windows 10 with bitmap decompression the Alpha always ends up as 0. This makes everything transparent. * Implement screen size and credential negotiation * desktop protocol: remove password prompt It was decided that supporting passwords natively is a bad product decision. We will only support certificate-based authn and only in ActiveDirectory environments. Until we implement certificate support, passwords can be injected via an environment variable for testing. It will be removed before the beta release. * Address review feedback Co-authored-by: Isaiah Becker-Mayer <isaiah@goteleport.com>
- Loading branch information
Andrew Lytvynov
and
Isaiah Becker-Mayer
authored
Aug 23, 2021
1 parent
dc6e728
commit b9e9b53
Showing
14 changed files
with
2,569 additions
and
19 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,273 @@ | ||
/* | ||
Copyright 2021 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 deskproto implements the desktop protocol encoder/decoder. | ||
// See https://github.com/gravitational/teleport/blob/master/rfd/0037-desktop-access-protocol.md | ||
// | ||
// TODO(awly): complete the implementation of all messages, even if we don't | ||
// use them yet. | ||
package deskproto | ||
|
||
import ( | ||
"bytes" | ||
"encoding/binary" | ||
"image" | ||
"image/png" | ||
"io" | ||
|
||
"github.com/gravitational/trace" | ||
) | ||
|
||
// MessageType identifies the type of the message. | ||
type MessageType byte | ||
|
||
// For descriptions of each message type see: | ||
// https://github.com/gravitational/teleport/blob/master/rfd/0037-desktop-access-protocol.md#message-types | ||
const ( | ||
TypeClientScreenSpec = MessageType(1) | ||
TypePNGFrame = MessageType(2) | ||
TypeMouseMove = MessageType(3) | ||
TypeMouseButton = MessageType(4) | ||
TypeKeyboardButton = MessageType(5) | ||
TypeClipboardData = MessageType(6) | ||
TypeClientUsername = MessageType(7) | ||
) | ||
|
||
// Message is a Go representation of a desktop protocol message. | ||
type Message interface { | ||
Encode() ([]byte, error) | ||
} | ||
|
||
// Decode decodes the wire representation of a message. | ||
func Decode(buf []byte) (Message, error) { | ||
if len(buf) == 0 { | ||
return nil, trace.BadParameter("input desktop protocol message is empty") | ||
} | ||
switch MessageType(buf[0]) { | ||
case TypeClientScreenSpec: | ||
return decodeClientScreenSpec(buf) | ||
case TypePNGFrame: | ||
return decodePNGFrame(buf) | ||
case TypeMouseMove: | ||
return decodeMouseMove(buf) | ||
case TypeMouseButton: | ||
return decodeMouseButton(buf) | ||
case TypeKeyboardButton: | ||
return decodeKeyboardButton(buf) | ||
case TypeClientUsername: | ||
return decodeClientUsername(buf) | ||
default: | ||
return nil, trace.BadParameter("unsupported desktop protocol message type %d", buf[0]) | ||
} | ||
} | ||
|
||
// PNGFrame is the PNG frame message | ||
// https://github.com/gravitational/teleport/blob/master/rfd/0037-desktop-access-protocol.md#2---png-frame | ||
type PNGFrame struct { | ||
Img image.Image | ||
} | ||
|
||
func (f PNGFrame) Encode() ([]byte, error) { | ||
type header struct { | ||
Type byte | ||
Left, Top uint32 | ||
Right, Bottom uint32 | ||
} | ||
|
||
buf := new(bytes.Buffer) | ||
if err := binary.Write(buf, binary.BigEndian, header{ | ||
Type: byte(TypePNGFrame), | ||
Left: uint32(f.Img.Bounds().Min.X), | ||
Top: uint32(f.Img.Bounds().Min.Y), | ||
Right: uint32(f.Img.Bounds().Max.X), | ||
Bottom: uint32(f.Img.Bounds().Max.Y), | ||
}); err != nil { | ||
return nil, trace.Wrap(err) | ||
} | ||
if err := png.Encode(buf, f.Img); err != nil { | ||
return nil, trace.Wrap(err) | ||
} | ||
return buf.Bytes(), nil | ||
} | ||
|
||
func decodePNGFrame(buf []byte) (PNGFrame, error) { | ||
var header struct { | ||
Left, Top uint32 | ||
Right, Bottom uint32 | ||
} | ||
r := bytes.NewReader(buf[1:]) | ||
if err := binary.Read(r, binary.BigEndian, &header); err != nil { | ||
return PNGFrame{}, trace.Wrap(err) | ||
} | ||
img, err := png.Decode(r) | ||
if err != nil { | ||
return PNGFrame{}, trace.Wrap(err) | ||
} | ||
// PNG encoding does not preserve offset image bounds. | ||
// Opportunistically restore them based on the header. | ||
switch img := img.(type) { | ||
case *image.RGBA: | ||
img.Rect = image.Rect(int(header.Left), int(header.Top), int(header.Right), int(header.Bottom)) | ||
case *image.NRGBA: | ||
img.Rect = image.Rect(int(header.Left), int(header.Top), int(header.Right), int(header.Bottom)) | ||
} | ||
return PNGFrame{Img: img}, nil | ||
} | ||
|
||
// MouseMove is the mouse movement message. | ||
// https://github.com/gravitational/teleport/blob/master/rfd/0037-desktop-access-protocol.md#3---mouse-move | ||
type MouseMove struct { | ||
X, Y uint32 | ||
} | ||
|
||
func (m MouseMove) Encode() ([]byte, error) { | ||
buf := new(bytes.Buffer) | ||
buf.WriteByte(byte(TypeMouseMove)) | ||
if err := binary.Write(buf, binary.BigEndian, m); err != nil { | ||
return nil, trace.Wrap(err) | ||
} | ||
return buf.Bytes(), nil | ||
} | ||
|
||
func decodeMouseMove(buf []byte) (MouseMove, error) { | ||
var m MouseMove | ||
err := binary.Read(bytes.NewReader(buf[1:]), binary.BigEndian, &m) | ||
return m, trace.Wrap(err) | ||
} | ||
|
||
// MouseButtonType identifies a specific button on the mouse. | ||
type MouseButtonType byte | ||
|
||
const ( | ||
LeftMouseButton = MouseButtonType(0) | ||
MiddleMouseButton = MouseButtonType(1) | ||
RightMouseButton = MouseButtonType(2) | ||
) | ||
|
||
// ButtonState is the press state of a keyboard or mouse button. | ||
type ButtonState byte | ||
|
||
const ( | ||
ButtonNotPressed = ButtonState(0) | ||
ButtonPressed = ButtonState(1) | ||
) | ||
|
||
// MouseButton is the mouse button press message. | ||
// https://github.com/gravitational/teleport/blob/master/rfd/0037-desktop-access-protocol.md#4---mouse-button | ||
type MouseButton struct { | ||
Button MouseButtonType | ||
State ButtonState | ||
} | ||
|
||
func (m MouseButton) Encode() ([]byte, error) { | ||
return []byte{byte(TypeMouseButton), byte(m.Button), byte(m.State)}, nil | ||
} | ||
|
||
func decodeMouseButton(buf []byte) (MouseButton, error) { | ||
var m MouseButton | ||
err := binary.Read(bytes.NewReader(buf[1:]), binary.BigEndian, &m) | ||
return m, trace.Wrap(err) | ||
} | ||
|
||
// KeyboardButton is the keyboard button press message. | ||
// https://github.com/gravitational/teleport/blob/master/rfd/0037-desktop-access-protocol.md#4---keyboard-input | ||
type KeyboardButton struct { | ||
KeyCode uint32 | ||
State ButtonState | ||
} | ||
|
||
func (k KeyboardButton) Encode() ([]byte, error) { | ||
buf := new(bytes.Buffer) | ||
buf.WriteByte(byte(TypeKeyboardButton)) | ||
if err := binary.Write(buf, binary.BigEndian, k); err != nil { | ||
return nil, trace.Wrap(err) | ||
} | ||
return buf.Bytes(), nil | ||
} | ||
|
||
func decodeKeyboardButton(buf []byte) (KeyboardButton, error) { | ||
var k KeyboardButton | ||
err := binary.Read(bytes.NewReader(buf[1:]), binary.BigEndian, &k) | ||
return k, trace.Wrap(err) | ||
} | ||
|
||
// ClientScreenSpec is the client screen specification. | ||
// https://github.com/gravitational/teleport/blob/master/rfd/0037-desktop-access-protocol.md#1---client-screen-spec | ||
type ClientScreenSpec struct { | ||
Width uint32 | ||
Height uint32 | ||
} | ||
|
||
func (s ClientScreenSpec) Encode() ([]byte, error) { | ||
buf := new(bytes.Buffer) | ||
buf.WriteByte(byte(TypeClientScreenSpec)) | ||
if err := binary.Write(buf, binary.BigEndian, s); err != nil { | ||
return nil, trace.Wrap(err) | ||
} | ||
return buf.Bytes(), nil | ||
} | ||
|
||
func decodeClientScreenSpec(buf []byte) (ClientScreenSpec, error) { | ||
var s ClientScreenSpec | ||
err := binary.Read(bytes.NewReader(buf[1:]), binary.BigEndian, &s) | ||
return s, trace.Wrap(err) | ||
} | ||
|
||
// ClientUsername is the client username. | ||
// https://github.com/gravitational/teleport/blob/master/rfd/0037-desktop-access-protocol.md#7---client-username | ||
type ClientUsername struct { | ||
Username string | ||
} | ||
|
||
func (r ClientUsername) Encode() ([]byte, error) { | ||
buf := new(bytes.Buffer) | ||
buf.WriteByte(byte(TypeClientUsername)) | ||
if err := encodeString(buf, r.Username); err != nil { | ||
return nil, trace.Wrap(err) | ||
} | ||
return buf.Bytes(), nil | ||
} | ||
|
||
func decodeClientUsername(buf []byte) (ClientUsername, error) { | ||
r := bytes.NewReader(buf[1:]) | ||
username, err := decodeString(r) | ||
if err != nil { | ||
return ClientUsername{}, trace.Wrap(err) | ||
} | ||
return ClientUsername{Username: username}, nil | ||
} | ||
|
||
func encodeString(w io.Writer, s string) error { | ||
if err := binary.Write(w, binary.BigEndian, uint32(len(s))); err != nil { | ||
return trace.Wrap(err) | ||
} | ||
if _, err := w.Write([]byte(s)); err != nil { | ||
return trace.Wrap(err) | ||
} | ||
return nil | ||
} | ||
|
||
func decodeString(r io.Reader) (string, error) { | ||
var length uint32 | ||
if err := binary.Read(r, binary.BigEndian, &length); err != nil { | ||
return "", trace.Wrap(err) | ||
} | ||
s := make([]byte, int(length)) | ||
if _, err := io.ReadFull(r, s); err != nil { | ||
return "", trace.Wrap(err) | ||
} | ||
return string(s), nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
/* | ||
Copyright 2021 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 deskproto | ||
|
||
import ( | ||
"image" | ||
"image/color" | ||
"testing" | ||
|
||
"github.com/google/go-cmp/cmp" | ||
"github.com/stretchr/testify/require" | ||
) | ||
|
||
func TestEncodeDecode(t *testing.T) { | ||
for _, m := range []Message{ | ||
MouseMove{X: 1, Y: 2}, | ||
MouseButton{Button: MiddleMouseButton, State: ButtonPressed}, | ||
KeyboardButton{KeyCode: 1, State: ButtonPressed}, | ||
func() Message { | ||
img := image.NewNRGBA(image.Rect(5, 5, 10, 10)) | ||
for x := img.Rect.Min.X; x < img.Rect.Max.X; x++ { | ||
for y := img.Rect.Min.Y; y < img.Rect.Max.Y; y++ { | ||
img.Set(x, y, color.NRGBA{1, 2, 3, 4}) | ||
} | ||
} | ||
return PNGFrame{Img: img} | ||
}(), | ||
ClientScreenSpec{Width: 123, Height: 456}, | ||
ClientUsername{Username: "admin"}, | ||
} { | ||
|
||
buf, err := m.Encode() | ||
require.NoError(t, err) | ||
|
||
out, err := Decode(buf) | ||
require.NoError(t, err) | ||
|
||
require.Empty(t, cmp.Diff(m, out)) | ||
} | ||
} | ||
|
||
func TestBadDecode(t *testing.T) { | ||
// 254 is an unknown message type. | ||
_, err := Decode([]byte{254}) | ||
require.Error(t, err) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
target/ | ||
main |
Oops, something went wrong.