authors | state |
---|---|
Isaiah Becker-Mayer (isaiah@goteleport.com) |
implemented (v10.2.0) |
- Engineering: @zmb3 && (@probakowski || @LKozlowski)
At a high level, implementing drive (folder) redirection for Teleport is a matter of taking RDP's File System Virtual Channel Extension protocol (described in [MS-RDPEFS]), and converting it to the TDP protocol (described in this document).
Teleport Desktop Protocol RDP
------------------------------------------ ---------------------
| | | |
+----------------------+ +------------------+ +------------------+ +------------------+
| | | | | | | |
| | | | | Teleport | | |
| User's Web Browser ------| Teleport Proxy ----- Windows Desktop ------| Windows Desktop |
| | | | | Service | | |
+----------------------+ +------------------+ +------------------+ +------------------+
RDP is an old protocol, and is designed in some ways to be deeply compatible with Windows operating system, which means that it contains a lot of details that aren't necessarily relevant or available to our client, which is limited to sort of file information is available to us through the browser. While this reality creates its own set of implementation difficulties for us, it also creates an opportunity for us to simplify drive redirection (aka "directory sharing") in the TDP protocol.
Being such a longstanding, large, and complex protocol, it can sometimes be difficult to tell from it's documentation precisely how RDP is actually supposed to work. Adding to that difficulty is the File System Virtual Channel Extension designer's decision to leave some aspects of client and server dynamics unspecified:
"This protocol forwards server requests from the server-based application and returns replies from the client-file system. There are no specific rules implied by this protocol as to how and when a particular message is sent from the server and what the client is to reply." ([MS-RDPEFS] 3.1.5.1)
In cases of protocol ambiguity, the open source project FreeRDP is an invaluable resource as a canonical implementation, which will be used for reference. This document attempts to be exhaustive for core drive sharing functionality; for any cases that aren't accounted for in this document, it should be assumed that we follow FreeRDP's conventions and algorithms.
After the initial drive negotiation, RDP directs the remote machine's use of the drive by sending
Device I/O Request
s. These requests are only sent
from the server (the Windows Desktop) to the client (the Rust client running on Teleport's Windows Desktop Service). Each Device I/O Request
contains the fields
described below, and may contain an additional data structure determined by it's MajorFunction
/MinorFunction
.
The DeviceId
uniquely identifies a shared directory. The initial implementation will only support sharing a single directory, so we will use the same ID in all responses.
In order to support sharing multiple directories simultaneously, Teleport would need to maintain a mapping between the selected ID and the local directory.
Since it's trivial to add, we will include a corresponding field directory_id
in TDP that corresponds with this field, which will set us up add a multiple-directory-sharing
feature more easily in the future.
A FileId
is generated by the client upon receipt of a Device I/O Request
where MajorFunction = IRP_MJ_CREATE
and sent back to the server, which then uses it to denote that file in subsequent
Device I/O Request
s. The FileId
is valid until the client receives a Device I/O Request
with
MajorFunction = IRP_MJ_CLOSE
, at which point it becomes invalid and can be recycled.
The semantics of these MajorFunction
s and how this system works isn't obvious at first glance, and will be clarified in the
Typical Operation section below. TDP won't make use of FileId
, electing to use a file or directory's (unix-like) relative path in each
request. The relative path
combined with directory_id
gives a unique "id" to any shared file-like object for any number of root-level shared directories.
Therefore, the responsibility for mapping between FileId
and a file path will be the responsibility of the Teleport Desktop Service.
CompletionId
s are generated by the server and sent with each Device I/O Request
.
All Device I/O Request
s demand a
Device I/O Response
in response, and the server
matches Device I/O Response
s to
Device I/O Request
s by the CompletionId
field.
This design allows operations to take place asynchronously/concurrently, and we will mimic it in our TDP translation.
Device I/O Request
s are classified by their
MajorFunction
field, which can contain the following values/semantics:
Value | Meaning |
---|---|
IRP_MJ_CREATE | Create request |
IRP_MJ_CLOSE | Close request |
IRP_MJ_READ | Read request |
IRP_MJ_WRITE | Write request |
IRP_MJ_DEVICE_CONTROL | Device control request |
IRP_MJ_QUERY_VOLUME_INFORMATION | Query volume information request |
IRP_MJ_SET_VOLUME_INFORMATION | Set volume information request |
IRP_MJ_QUERY_INFORMATION | Query information request |
IRP_MJ_SET_INFORMATION | Set information request |
IRP_MJ_DIRECTORY_CONTROL | Directory control request |
IRP_MJ_LOCK_CONTROL | File lock control request |
Device I/O Request
s are always the headers of more
detailed request messages. For example, a Device I/O Request
with its MajorFunction
set to IRP_MJ_CREATE
denotes the beginning of a Device Create Request
,
which contains further fields that specify the details of the request.
The MinorFunction
further specifies IRP_MJ_DIRECTORY_CONTROL
requests.
We will need to handle each of these request types, though in some cases that simply means sending back an empty message to RDP. The details of how each of these will be handled is documented in the RDP --> TDP Translation
section below.
As mentioned previously, the semantics of the MajorFunction
s isn't necessarily obvious at first glance. For example, one might assume that an IRP_MJ_CREATE
means "create a new file or directory", however it turns out this is only the case given a specific CreateDisposition
in the attendant
Device Create Request
.
In fact, IRP_MJ_CREATE
is sent at the beginning of any operation the server wants to execute. It most commonly just tells the client "create a reference to a
file/directory and give it a FileId
". The client does so and sends that FileId
back to the server in response, and that FileId
is then used in subsequent
Device I/O Request
s that act on that file/directory, such as reading or writing. Once the operation is complete, the server sends a IRP_MJ_CLOSE
, which typically
means "remove the reference you created previously".
As an example, here is what happens when the client first announces a new folder for redirection:
RDP Server RDP Client
(Windows Machine) (Windows Desktop Service)
| |
| Client Device List Announce Request|
|<-----------------------------------+
| |
|Server Device Announce Response |
+----------------------------------->|
| |
|Device Create Request |Generates a FileId corresponding to
|(IRP_MJ_CREATE) |the requested Path (which is "", meaning
+----------------------------------->|the top level directory), internally creating some
| |sort of file handle.
|Device Create Response |
|<-----------------------------------+and responds with the FileId.
| |
|Drive Query Information Request |
Asks for metadata about|(IRP_MJ_QUERY_INFORMATION) |
the tld. +----------------------------------->|
| |
|Drive Query Information Response |
|<-----------------------------------+Replies with the metadata
| |
|Device Close Request |
|(IRP_MJ_CLOSE) |Operation complete, the previously
+----------------------------------->|generated FileId can now be recycled
| |and internal file handle can be deleted.
| |
| |
| |
A similar process would then take place for other operations, for example a read of a file in the shared directory named example.txt
would look like
- server sends
IRP_MJ_CREATE
withPath: "example.txt"
- client responds with a
FileId
- server sends
IRP_MJ_READ
forFileId
for bytes 0 - 1024 - client executes the read and responds with the data
- server sends
IRP_MJ_CLOSE
Our RDP client lives on the Windows Desktop Service, while the directory we're sharing is exposed to us via the user's browser. This means that in order to get information to and from the shared directory,
we must extend the TDP protocol (also see the diagram in the Introduction
section for reference).
Each * Request
(such as Shared Directory Info Request
, Shared Directory Create Request
, etc.) and * Response
(Shared Directory Info Response
, Shared Directory Create Response
, etc.) TDP message contains a completion_id
field, with * Request
s being responsible for generating the completion_id
s, and * Response
s being responsible for
including the correct completion_id
to signify which * Request
the response is intended for.
From here on out, the term "client" will refer to the browser-based TDP client, and the term "server" will refer to the Windows Desktop Service based TDP server, unless otherwise specified.
| message type (11) | directory_id uint32 | name_length uint32 | name []byte |
This message announces a new directory to be shared over TDP. directory_id
must be a unique identifier. Attempting to share multiple different directories using
the same directory_id
is undefined behavior.
name_length
is the length in bytes of the name
. The maximum allowed length is equivalent to
Windows' MAX_PATH
(260 characters).
name
is the name of the directory (without any path prefix).
| message type (12) | err_code uint32 | directory_id uint32 |
Acknowledges a Shared Directory Announce
was received.
err_code
is an error code. 0
("nil") means the Shared Directory Announce
was successfully processed, 1
("operation failed") means processing failed.
directory_id
is the directory_id
of the top level directory being shared, as specified in the Announce Shared Directory
message this message is acknowledging.
| message type (13) | completion_id uint32 | directory_id uint32 | path_length uint32 | path []byte |
This message is sent from the server to the client to request information about a file. The server will be expecting a Shared Directory Info Response
.
completion_id
is generated by the server and must be returned by the client in its corresponding Shared Directory Info Response
or Shared Directory Error
response.
directory_id
is the directory_id
of the top level directory being shared, as specified in a previous Announce Shared Directory
message.
path_length
is the length in bytes of the path
.
path
is the unix-style relative path (from the root-level directory specified by directory_id
) to the file or directory, excepting special path characters ".",
"..". Absolute paths, or those containing path elements with either of the special characters, will result in an error. Info can be requested for the root-level
directory by setting path_length: 0
and path: ""
.
| message type (14) | completion_id uint32 | err_code uint32 | file_system_object fso |
This message is sent by the client to the server in response to a Shared Directory Info Request
.
completion_id
must match the completion_id
of the Shared Directory Info Request
that this message is responding to.
err_code
is an error code. If a file or directory at path
does not exist, this should be set to 2
("resource does not exist").
file_system_object
is the file system object.
| message type (15) | completion_id uint32 | directory_id uint32 | file_type uint32 | path_length uint32 | path []byte |
This message is sent by the server to the client to request the creation of a new file or directory.
completion_id
is generated by the server and must be returned by the client in its corresponding Shared Directory Create Response
or Shared Directory Error
response.
directory_id
is the directory_id
of the top level directory being shared, as specified in a previous Announce Shared Directory
message.
file_type
matches the specification in Shared Directory Info Response
.
path_length
is the length in bytes of the path
.
path
is the unix-style relative path (from the root-level directory specified by directory_id
) to the file or directory, excepting special path characters ".",
"..". Absolute paths, or those containing path elements with either of the special characters, will result in an error.
| message type (16) | completion_id uint32 | err_code uint32 | file_system_object fso |
This message is sent by the client to the server to acknowledge a Shared Directory Create Request
was successfully executed. A Shared Directory Create Request
that fails should respond with an "operation failed" Shared Directory Error
.
completion_id
must match the completion_id
of the Shared Directory Create Request
that this message is responding to.
err_code
is an error code. If a filesystem object at path
already exists, this should be set to 3
("resource already exists").
file_system_object
is the file system object that was created.
| message type (17) | completion_id uint32 | directory_id uint32 | path_length uint32 | path []byte |
This message is sent by the server to the client to request the deletion of a file or directory at path
.
completion_id
is generated by the server and must be returned by the client in its corresponding Shared Directory Delete Response
or Shared Directory Error
response.
directory_id
is the directory_id
of the top level directory being shared, as specified in a previous Shared Directory Announce
message.
path_length
is the length in bytes of the path
.
path
is the unix-style relative path (from the root-level directory specified by directory_id
) to the file or directory, excepting special path characters ".",
"..". Absolute paths, or those containing path elements with either of the special characters, will result in an error.
| message type (18) | completion_id uint32 | err_code uint32 |
This message is sent by the client to the server to acknowledge a Shared Directory Delete Request
was successfully executed. A Shared Directory Create Request
that fails should respond with an appropriate Shared Directory Error
.
completion_id
must match the completion_id
of the Shared Directory Delete Request
that this message is responding to.
err_code
is an error code. If the delete fails, this should be set to 1
("operation failed"). If the file or directory does not exist, this should be set to 2
("resource does not exist").
| message type (19) | completion_id uint32 | directory_id uint32 | path_length uint32 | path []byte | offset uint64 | length uint32 |
This message is sent by the server to the client to request a maximum of length
bytes be read from the file at path
, starting at byte offset offset
.
completion_id
is generated by the server and must be returned by the client in its corresponding Shared Directory Read Response
or Shared Directory Error
response.
directory_id
is the directory_id
of the top level directory being shared, as specified in a previous Announce Shared Directory
message.
path_length
is the length in bytes of the path
.
path
is the unix-style relative path (from the root-level directory specified by directory_id
) to the file or directory, excepting special path characters ".",
"..". Absolute paths, or those containing path elements with either of the special characters, will result in an error.
offset
specifies the file offset where the read operation is performed.
length
specifies the maximum number of bytes to be read from the device.
| message type (20) | completion_id uint32 | err_code uint32 | read_data_length uint32 | read_data []byte |
This message is sent by the client to the server in response to a Shared Directory Read Request
.
completion_id
must match the completion_id
of the Shared Directory Read Request
that this message is responding to.
err_code
is an error code. If the file does not exist or the path is to a directory, this field should be set to 2
("resource does not exist"). For any other error, this field should be set to 1
("operation failed").
read_data_length
specifies the number of bytes in the read_data
field.
read_data
are the raw bytes that were read.
| message type (21) | completion_id uint32 | directory_id uint32 | path_length uint32 | path []byte | offset uint64 | write_data_length uint32 | write_data []byte |
This message is sent by the server to the client to request write_data
be written to the file specified by path
.
completion_id
is generated by the server and must be returned by the client in its corresponding Shared Directory Write Response
or Shared Directory Error
response.
directory_id
is the directory_id
of the top level directory being shared, as specified in a previous Announce Shared Directory
message.
path_length
is the length in bytes of the path
.
path
is the unix-style relative path (from the root-level directory specified by directory_id
) to the file or directory, excepting special path characters ".",
"..". Absolute paths, or those containing path elements with either of the special characters, will result in an error.
offset
specifies the file offset where the write operation should start from.
write_data_length
is the length of write_data
write_data
is the raw data to be written.
| message type (22) | completion_id uint32 | err_code uint32 | bytes_written uint32 |
This message is sent by the client to the server in response to a Shared Directory Write Request
.
completion_id
must match the completion_id
of the Shared Directory Write Request
that this message is responding to.
err_code
is an error code. If the file does not exist or the path is to a directory, this field should be set to 2
("resource does not exist"). For any other error, this field should be set to 1
("operation failed").
bytes_written
specifies the number of bytes that were written.
| message type (23) | completion_id uint32 | directory_id uint32 | original_path_length uint32 | original_path []byte | new_path_length uint32 | new_path []byte |
This message is sent by the server to the client to request a file or directory be moved (or renamed). It should be expected to work like the
linux mv
utility (with no flags).
completion_id
is generated by the server and must be returned by the client in its corresponding Shared Directory Move Response
or Shared Directory Error
response.
directory_id
is the directory_id
of the top level directory being shared, as specified in a previous Announce Shared Directory
message.
original_path_length
is the length of original_path
original_path
is the existing path of the file or directory being moved.
new_path_length
is the length of new_path
new_path
is the new path for the file or directory being moved.
| message type (24) | completion_id uint32 | err_code uint32 |
This message is sent by the client to the server in response to a Shared Directory Move Request
to alert the server of a successful move operation.
completion_id
must match the completion_id
of the Shared Directory Move Request
that this message is responding to.
err_code
is an error code. If the original_path
in the Shared Directory Move Request
does not exist, this field should be set to 2
("resource does not exist"). For any other error, this field should be set to 1
("operation failed").
| message type (25) | completion_id uint32 | directory_id uint32 | path_length uint32 | path []byte |
This message is sent by the server to the client to request the contents of a directory.
completion_id
is generated by the server and must be returned by the client in its corresponding Shared Directory List Response
or Shared Directory Error
response.
directory_id
is the directory_id
of the top level directory being shared, as specified in a previous Announce Shared Directory
message.
path_length
is the length in bytes of the path
.
path
is the unix-style relative path (from the root-level directory specified by directory_id
) to the file or directory, excepting special path characters ".",
"..". Absolute paths, or those containing path elements with either of the special characters, will result in an error. Info can be requested for the root-level
directory by setting path_length: 0
and path: ""
.
| message type (26) | completion_id uint32 | err_code uint32 | fso_list_length uint32 | fso_list fso[] |
This message is sent by the client to the server in response to a Shared Directory Info Request
.
completion_id
must match the completion_id
of the Shared Directory List Request
that this message is responding to.
err_code
is an error code. If the original_path
in the Shared Directory Move Request
does not exist or the path is to a file, this field should be set to 2
("resource does not exist"). For any other error, this field should be set to 1
("operation failed").
fso_list_length
is the number of entries in the fso_list
.
fso_list
is a list of fso
objects representing all the files and directories in the directory specified by the path
variable in the originating
Shared Directory List Request
.
| message type (33) | completion_id uint32 | directory_id uint32 | path_length uint32 | path []byte | end_of_file uint32 |
This message is sent by the server to the client to request a file be truncated to end_of_file
bytes.
completion_id
is generated by the server and must be returned by the client in its corresponding Shared Directory Truncate Response
or Shared Directory Error
directory_id
is the directory_id
of the top level directory being shared, as specified in a previous Announce Shared Directory
message.
path_length
is the length in bytes of the path
.
path
is the unix-style relative path (from the root-level directory specified by directory_id
) to the file which should be truncated.
end_of_file
is the new size of the file in bytes.
| message type (34) | completion_id uint32 | err_code uint32 |
This message is sent by the client to the server in response to a Shared Directory Truncate Request
to alert the server of a successful truncate operation.
completion_id
must match the completion_id
of the Shared Directory Truncate Request
that this message is responding to.
err_code
is an error code. If the file does not exist or the path is to a directory, this field should be set to 2
("resource does not exist"). For any other error, this field should be set to 1
("operation failed").
| last_modified uint64 | size uint64 | file_type uint32 | is_empty bool | path_length uint32 | path byte[] |
The design of this message is constrained by what information is made available to us by the browser and which File Information Classes are potentially requested by RDP.
For files, last_modified
is the last modified time of the file as specified by the mtime
, in milliseconds
since the UNIX epoch. For directories, last_modified
should also be set to the
directory's mtime
when such information is available. If such information is unavailable for a directory, such as
in a browser environment, this value should be assigned the UNIX epoch itself (0).
For files, size
is the size of the file in bytes. For directories, size
is not the total size of the contents of the
directory, but rather the size the directory itself takes up on disk. If such information is unavailable for a directory, such as in a browser environment,
this can be set to the contemporary de facto Unix default of 4096 bytes (see mke2fsc.onf
).
file_type
s currently represents only the simple file/directory distinction. Later it may be modified to support more types such as those corresponding to the
types available in RDP's File Attributes fields:
- file
- directory
is_empty
says whether or not a directory is empty. For objects where file_type = 0
(files), this field should be
ignored.
path_length
is the length in bytes of the path
.
path
is the unix-style relative path (from the root-level directory specified by directory_id
) to the file or directory, excluding special path characters ".",
"..". Absolute paths, or those containing path elements with either of the special characters, are considered an error. A root-level shared directory is specified
by setting path_length: 0
and path: ""
.
err_code
is a uint32
sized field specifying an error
- nil (no error, operation succeeded)
- operation failed
- resource does not exist
- resource already exists
For now, all errors will be unspecified, and translated into
NTSTATUS::STATUS_UNSUCCESSFUL
over RDP. Later, we
can add more specific error messages for more specific RDP error handling,
as they do in FreeRDP.
bool
is a uint8
specifying True or False:
- False
- True
It's beyond the scope of this document to detail the RDP --> TDP translation of every MajorFunction
, however I here is one example of the task at hand
for IRP_MJ_READ
, for clarity's sake.
RDP request: Device Read Request
RDP response: Device Read Response
FreeRDP: entry point
Our Windows Desktop Service receives the following Device Read Request
DeviceReadRequest {
DeviceIoRequest: DeviceIoRequest {
Header: omitted,
DeviceId: 2,
FileId: 3,
CompletionId: 4,
MajorFunction: IRP_MJ_READ,
MinorFunction: 0x00000000,
},
Length: 1024,
Offset: 2048,
}
First we consult our internal cache of pseudo "file handles" for an entry where FileId = 3
, which would have been created while fielding a previous successful IRP_MJ_CREATE
.
From that we'll grab the path
corresponding to this file (lets say "example/file.txt"). No we have all the information required to create a TDP Shared Directory Read Request
:
SharedDirectoryReadRequest: {
message_type: 19,
completion_id: 4,
directory_id: 2,
path_length: 16,
path: "example/file.txt",
offset: 2048,
length: 1024,
}
Presuming the request succeeds, we expect back a response like:
SharedDirectoryReadResponse {
completion_id: 4,
err: 0,
read_data_length: 1024,
read_data: [...],
}
Which we can then package into an RDP Device Read Response
DeviceReadResponse {
DeviceIoReply: DeviceIoResponse {
Header: omitted,
DeviceId: 2,
CompletionId: 4,
IoStatus: NTSTATUS.STATUS_SUCCESS,
}
Length: 1024,
ReadData: [...],
}
This is clearly the "happy path" for this process, error mode handling that been omitted here. Note that not all MajorFunctions
have such straightforward translations, and some will require multiple TDP messages to complete their translation.
Depending on customer demand, we may wish to add a "read-only" mode that allows users to share a local directory with the remote Windows machine for file transfer while disallowing any write operations for enhanced security. RDP doesn't offer such a feature natively, thus it would need to be implemented at the TDP layer only.
To do so, we would extend Shared Directory Announce
to include a read-only boolean field:
| message type (11) | directory_id uint32 | read_only bool | name_length uint32 | name []byte |
When set to true, any mutating TDP Requests such as Shared Directory Write Request
and Shared Directory Delete Request
will become invalid. An initial thought might be to have such messages always result in
a corresponding Response with a non-null err_code
, however this would make the read-only guarantee dependent on a friendly client implementation, which we can not rely on. Instead, this read-only setting will
be enforced by the Windows Desktop Service itself: whenever we receive an RDP message that in non-read-only mode would be translated into a mutating TDP Request, we will send back an RDP error.
For example, if we receive an IRP_MJ_WRITE
, at the point where would ordinarily send a Shared Directory Write Request
to our client (the browser), we will instead send the Windows machine an RDP Device I/O Response
with IoStatus
set to STATUS_ACCESS_DENIED
. Realistically this situation might happen if the user is sharing a directory in read-only mode, opens a file in Notepad, changes it and then tries to save it.
The UX for this feature is discussed in RFD 0058 and may be expounded upon further in a future RFD.
Security will be discussed in a future RFD.
Audit events would ideally be focused exclusively on directory sharing events that are germaine to system security such as:
- A file was transferred from the remote Windows machine onto the local machine (into the shared directory)
- A file was transferred from the local machine (from the shared directory) onto the remote Windows machine
- etc
However due to our limited visibility into the Windows side of the equation it is not possible for us to determine such events with precision. For example, if a user transfers a file
from the Windows box into the shared directory, we see that as a Shared Directory Create
sequence followed by a Shared Directory Write
sequence. But then that same sequence could
just have been a user creating a brand new file within the shared directory and then writing to it (which is not of particular note from a security perspective). Given this limitation,
we will instead simply log all events that indicate security-relevant information, which is to say events indicating a data transfer between the local and remote machines,
which is to say Shared Directory Read
("desktop.directory.read") and Shared Directory Write
("desktop.directory.write") events.
Shared Directory Announce/Acknowledge
("desktop.directory.share") is also included in the audit event log to make the sequence of events more easily comprehensible.
TDP Shared Directory Messages Logged
The following list includes the type of TDP messages that will be logged and their proposed corresponding event names.
Shared Directory Announce/Acknowledge
Shared Directory ReadRequest/ReadResponse
Shared Directory WriteRequest/WriteResponse
TDP Shared Directory Messages Skipped
Shared Directory Info
Shared Directory List
Shared Directory Create
Shared Directory Delete
Shared Directory Move
For Shared Directory Read
and Shared Directory Write
, which contain raw file data, the length (number of bytes) of the data transfer will be logged rather than the data itself. This
by default prevents the audit log from blowing up in size if large files are shared, and from becoming a source of potential data theft. That said, given that there is apparently already
consumer demand for complete file data logging in the audit log, it's worth considering making this configurable (out of scope for this RFD).
Emitted when a successful Shared Directory Acknowledge
is received. Due to technical limitations, we will for now just be logging this event on a successful directory sharing initialization.
We can attempt a more complex implementation that takes into account failed initialization attempts, pending user demand.
// DesktopSharedDirectoryStart is emitted when Teleport
// attempt to begin sharing a new directory to a remote desktop.
message DesktopSharedDirectoryStart {
// Metadata is common event metadata.
Metadata Metadata = 1 [
(gogoproto.nullable) = false,
(gogoproto.embed) = true,
(gogoproto.jsontag) = ""
];
// User is common user event metadata.
UserMetadata User = 2 [
(gogoproto.nullable) = false,
(gogoproto.embed) = true,
(gogoproto.jsontag) = ""
];
// Session is common event session metadata.
SessionMetadata Session = 3 [
(gogoproto.nullable) = false,
(gogoproto.embed) = true,
(gogoproto.jsontag) = ""
];
// Connection holds information about the connection.
ConnectionMetadata Connection = 4 [
(gogoproto.nullable) = false,
(gogoproto.embed) = true,
(gogoproto.jsontag) = ""
];
// DesktopAddr is the address of the desktop being accessed.
string DesktopAddr = 5 [(gogoproto.jsontag) = "desktop_addr"];
// DirectoryName is the name of the directory being shared.
string DirectoryName = 6 [(gogoproto.jsontag) = "directory_name"];
// DirectoryID is the ID of the directory being shared (unique to the Windows Desktop Session).
uint32 DirectoryID = 7 [(gogoproto.jsontag) = "directory_id"];
}
Note: the inclusion of DirectoryID
is looking forward to if/when we allow for multiple directories to be shared at once, at which point DirectoryName
will no longer necessarily be
a unique identifier.
// DesktopSharedDirectoryRead is emitted when Teleport
// attempts to read from a file in a shared directory at
// the behest of the remote desktop.
message DesktopSharedDirectoryRead {
// Metadata, UserMetadata, SessionMetadata, ConnectionMetadata omitted
// DesktopAddr is the address of the desktop being accessed.
string DesktopAddr = 5 [(gogoproto.jsontag) = "desktop_addr"];
// DirectoryName is the name of the directory being shared.
string DirectoryName = 6 [(gogoproto.jsontag) = "directory_name"];
// DirectoryID is the ID of the directory being shared (unique to the Windows Desktop Session).
uint32 DirectoryID = 7 [(gogoproto.jsontag) = "directory_id"];
// Path is the path within the shared directory where the file is located.
string Path = 8 [(gogoproto.jsontag) = "file_path"];
// Length is the number of bytes read.
uint32 Length = 9 [(gogoproto.jsontag) = "length"];
// Offset is the offset the bytes were read from.
uint32 Offset = 10 [(gogoproto.jsontag) = "offset"];
}
// DesktopSharedDirectoryWrite is emitted when Teleport
// attempts to write to a file in a shared directory at
// the behest of the remote desktop.
message DesktopSharedDirectoryWrite {
// Metadata, UserMetadata, SessionMetadata, ConnectionMetadata omitted
// DesktopAddr is the address of the desktop being accessed.
string DesktopAddr = 5 [(gogoproto.jsontag) = "desktop_addr"];
// DirectoryName is the name of the directory being shared.
string DirectoryName = 6 [(gogoproto.jsontag) = "directory_name"];
// DirectoryID is the ID of the directory being shared (unique to the Windows Desktop Session).
uint32 DirectoryID = 7 [(gogoproto.jsontag) = "directory_id"];
// Path is the path within the shared directory where the file is located.
string Path = 8 [(gogoproto.jsontag) = "file_path"];
// Length is the number of bytes written.
uint32 Length = 9 [(gogoproto.jsontag) = "length"];
// Offset is the offset the bytes were written to.
uint32 Offset = 10 [(gogoproto.jsontag) = "offset"];
}
Shared Directory Announce/Acknowledge |
Shared Directory ReadRequest/ReadResponse |
Shared Directory WriteRequest/WriteResponse |
|
---|---|---|---|
Name | "desktop.directory.share" |
"desktop.directory.read" |
"desktop.directory.write" |
Success | "TDP04I" |
"TDP05I" |
"TDP06I" |
Failure | "TDP04W" |
"TDP05W" |
"TDP06W" |
The failure codes in the table above will usually correspond to a Shared Directory Acknowledge
/ReadResponse
/WriteResponse
being returned with a
non-nil error code.
Because of the asynchronous nature of these request/response pairs, audit information will need to be cached, and there is
therefore a chance of some information being lost in the audit events due to a programmer error causing the cache to get out of sync. Such a possibility
is accounted for in the code, with the missing information being recorded as "unknown"
, and the event code being recorded as the failure code. Because
such a situation should in practice never happen, it won't be accounted for in the Audit Events UI, and all failure codes will show up in the UI as some
variation of "Start/Read/Write failed".
The read/write events that are sent to us are fundamentally determined by the program on the Windows machine that's reading or writing to a shared file. In the case of copying files in/out of the shared directory, that program is typically File Explorer, which empirically does read/writes at a maximum of 1MB (exactly 2^20 bytes) at a time. For files larger than 1MB, this manifests as several read/writes in a row showing up at time in such a case.
In order to avoid the logs getting spammed with multiple messages corresponding to one "operation" (such as a multi MB read of a large file), we can create an algorithm which upon receipt of a read or write event, sets a short timer (say 1s), and if it receives another non-overlapping read/write for the same file before the timer runs out, stops the timer and amalgamates the events together, repeating until the timer runs out at which point a single event is written into the log (credit to @zmb3 for the algo design).
This feature will be considered beyond the scope of the initial implementation, which will naively write all reads/writes as individual events.