Project: Teleport (Provisioning / Security & Compliance)
File: 0067-desktop-access-file-system-sharing.md

authors: Isaiah Becker-Mayer (isaiah@goteleport.com) state: 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 ), 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." () In cases of protocol ambiguity, the open source project 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 . 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 = IRPMJCREATE` 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 = IRPMJCLOSE`, at which point it becomes invalid and can be

The semantics of these `MajorFunction`s and how this system works isn't obvious at first glance, and will be clarified in the 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 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 | | - | -- | | IRPMJCREATE | Create request | | IRPMJCLOSE | Close request | | IRPMJREAD | Read request | | IRPMJWRITE | Write request | | IRPMJDEVICE_CONTROL | Device control request | | IRPMJQUERYVOLUMEINFORMATION | Query volume information request | | IRPMJSETVOLUMEINFORMATION | Set volume information request | | IRPMJQUERY_INFORMATION | Query information request | | IRPMJSET_INFORMATION | Set information request | | IRPMJDIRECTORY_CONTROL | Directory control request | | IRPMJLOCK_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 `IRPMJCREATE` denotes the beginning of a , which contains further fields that specify the details of the request. The `MinorFunction` further specifies `IRPMJDIRECTORY_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 `IRPMJCREATE` 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, `IRPMJCREATE` 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 `IRPMJCLOSE`, 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 |(IRPMJCREATE) |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|(IRPMJQUERY_INFORMATION) | the

+-->| | | |Drive Query Information Response | |<--+Replies with the metadata | | |Device Close Request | |(IRPMJCLOSE) |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 `IRPMJCREATE` with `Path: "example.txt"` client responds with a `FileId` server sends `IRPMJREAD` for `FileId` for bytes 0 - 1024 client executes the read and responds with the data server sends `IRPMJCLOSE` 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 (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 `completionid` field, with `* Request`s being responsible for generating the `completionid`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) | directoryid uint32 | namelength 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 (260 characters). `name` is the name of the directory (without any path prefix). ``` | message type (12) | errcode uint32 | directoryid 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. `directoryid` is the `directoryid` of the top level directory being shared, as specified in the `Announce Shared Directory` message this message is acknowledging. ``` | message type (13) | completionid uint32 | directoryid 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. `directoryid` is the `directoryid` 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) | completionid uint32 | errcode uint32 | filesystemobject fso | ``` This message is sent by the client to the server in response to a `Shared Directory Info

`completionid` must match the `completionid` 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"). `filesystemobject` is the file system object. ``` | message type (15) | completionid uint32 | directoryid uint32 | filetype uint32 | pathlength 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. `directoryid` is the `directoryid` 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) | completionid uint32 | errcode uint32 | filesystemobject 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`. `completionid` must match the `completionid` 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"). `filesystemobject` is the file system object that was created. ``` | message type (17) | completionid uint32 | directoryid 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. `directoryid` is the `directoryid` 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) | completionid uint32 | errcode 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`. `completionid` must match the `completionid` 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

``` | message type (19) | completionid uint32 | directoryid 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. `directoryid` is the `directoryid` 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) | completionid uint32 | errcode uint32 | readdatalength uint32 | read_data []byte | ``` This message is sent by the client to the server in response to a `Shared Directory Read Request`. `completionid` must match the `completionid` 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"). `readdatalength` specifies the number of bytes in the `read_data` field. `read_data` are the raw bytes that were read. ``` | message type (21) | completionid uint32 | directoryid uint32 | pathlength uint32 | path []byte | offset uint64 | writedatalength uint32 | writedata []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. `directoryid` is the `directoryid` 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. `writedatalength` is the length of `write_data` `write_data` is the raw data to be written. ``` | message type (22) | completionid uint32 | errcode uint32 | bytes_written uint32 | ``` This message is sent by the client to the server in response to a `Shared Directory Write Request`. `completionid` must match the `completionid` 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

``` | message type (23) | completionid uint32 | directoryid uint32 | originalpathlength uint32 | originalpath []byte | newpathlength uint32 | newpath []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 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. `directoryid` is the `directoryid` of the top level directory being shared, as specified in a previous `Announce Shared Directory` message. `originalpathlength` is the length of `original_path` `original_path` is the existing path of the file or directory being moved. `newpathlength` is the length of `new_path` `new_path` is the new path for the file or directory being moved. ``` | message type (24) | completionid uint32 | errcode 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. `completionid` must match the `completionid` of the `Shared Directory Move Request` that this message is responding to. `errcode` is an error code. If the `originalpath` 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) | completionid uint32 | directoryid 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. `directoryid` is the `directoryid` 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) | completionid uint32 | errcode uint32 | fsolistlength uint32 | fso_list fso[] | ``` This message is sent by the client to the server in response to a `Shared Directory Info Request`. `completionid` must match the `completionid` of the `Shared Directory List Request` that this message is responding to. `errcode` is an error code. If the `originalpath` 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"). `fsolistlength` 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) | completionid uint32 | directoryid uint32 | pathlength uint32 | path []byte | endof_file uint32 | ``` This message is sent by the server to the client to request a file be truncated to `endoffile`

`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` `directoryid` is the `directoryid` 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. `endoffile` is the new size of the file in bytes. ``` | message type (34) | completionid uint32 | errcode 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. `completionid` must match the `completionid` 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"). ``` | lastmodified uint64 | size uint64 | filetype uint32 | isempty bool | pathlength uint32 | path byte[] | ``` The design of this message is constrained by and which are potentially requested by RDP. For files, `last_modified` is the last modified time of the file as specified by the , in milliseconds since the . For directories, `last_modified` should also be set to the when suchinformation 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 ). `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 fields: file directory `isempty` says whether or not a directory is empty. For objects where `filetype = 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 over RDP. Later, we can add more specific error messages for more specific RDP error handling, . `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 `IRPMJREAD`, for clarity's

RDP request: RDP response: FreeRDP: Our Windows Desktop Service receives the following `Device Read Request` ``` DeviceReadRequest { DeviceIoRequest: DeviceIoRequest { Header: omitted, DeviceId: 2, FileId: 3, CompletionId: 4, MajorFunction: IRPMJREAD, 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 `IRPMJCREATE`. 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, readdatalength: 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) | directoryid uint32 | readonly 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 `IRPMJWRITE`, 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 `STATUSACCESSDENIED`. 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 and may be expounded upon further in a future RFD. Security will be discussed in . 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`

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. ```proto // 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. ```proto // 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

= "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"]; } ``` ```proto // 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.