# RDP

> Octelium documentation. Canonical page: <https://octelium.com/docs/octelium/latest/management/core/service/rdp>.

Octelium provides two Remote Desktop Protocol (RDP) modes:

- `RDP` serves ordinary RDP clients over the [client-based mode](https://octelium.com/docs/octelium/latest/user/cli/connect.md). It is not available through clientless access.
- `RDP_WEB` renders the remote desktop in a browser for clientless `HUMAN` access. It does not require a native RDP client.

Both modes can authenticate to an upstream Windows host over Network Level Authentication (NLA), inject upstream credentials from a *Secret*, verify the upstream TLS certificate and apply identity-based access control.

## RDP

Setting the *Service* mode to `RDP` enables native RDP access. The *User* first connects to the *Cluster* with the `octelium` client and then opens the *Service* private FQDN with Microsoft Remote Desktop, `mstsc`, FreeRDP or another RDP client.

Here is a minimal *Service* that passes the credentials entered in the downstream RDP client to the upstream:

```yaml
kind: Service
metadata:
  name: desktop
spec:
  # !mark
  mode: RDP
  port: 3389
  config:
    upstream:
      url: rdp://windows.internal:3389
```

After connecting to the *Cluster*, a *User* authorized to access the *Service* can open `desktop.local.example.com` in their RDP client. Replace `example.com` with the *Cluster* domain. The default port for `RDP` is `3389`, so the `port` field can be omitted when the default is suitable.

> **Note:**
>
> The `RDP` mode is available only through client-based access. Do not set `isPublic: true` to expose it. Use `RDP_WEB` when the remote desktop must be available through a browser without the `octelium` client.

> **Note:**
>
> For internal RDP upstreams behind NAT, remotely serve the *Service* through a connected `octelium` client or container as discussed [here](https://octelium.com/docs/octelium/latest/management/core/service/overview.md#remotely-via-a-connected-user).

## RDP Web

Setting the *Service* mode to `RDP_WEB` enables browser-based RDP. A `HUMAN` *User* opens the public *Service* URL and uses the remote desktop directly from the browser.

```yaml
kind: Service
metadata:
  name: desktop
spec:
  # !mark(1:2)
  mode: RDP_WEB
  isPublic: true
  config:
    upstream:
      url: rdp://windows.internal:3389
```

[View the documentation video](https://octelium.com/docs/octelium/latest/management/core/service/rdp).

> **Note:**
>
> The `RDP_WEB` mode is a managed *Service* intended for clientless `HUMAN` access and is therefore typically combined with the public BeyondCorp mode through `isPublic` (read more [here](https://octelium.com/docs/octelium/latest/management/core/service/clientless.md)).

> **Note:**
>
> `RDP_WEB` was added in version v0.37.0. It is still experimental and might contain bugs. Please report issues at the GitHub [repository](https://github.com/octelium/octelium/issues).

## Secretless Access

Both `RDP` and `RDP_WEB` support secretless upstream authentication. Octelium performs NLA authentication to the upstream using a username, optional Windows domain and password stored in a *Secret*. The downstream *User* never needs a valid upstream credential.

First, create a *Secret* containing the password:

```bash
octeliumctl create secret rdp-password
# OR via a --value flag
octeliumctl create secret --value <PASSWORD> rdp-password
# OR via a --file flag
octeliumctl create secret --file /PATH/TO/PASSWORD rdp-password
```

Add the injected credential to the *Service*:

```yaml
kind: Service
metadata:
  name: desktop
spec:
  mode: RDP
  config:
    upstream:
      url: rdp://windows.internal:3389
    # !mark(1:6)
    rdp:
      auth:
        user: Administrator
        domain: WORKGROUP
        password:
          fromSecret: rdp-password
```

The *Service* authenticates to the upstream as `Administrator` in the `WORKGROUP` domain. In native `RDP` mode, the configured credential overrides credentials supplied by the downstream RDP client. For browser access, change `mode` to `RDP_WEB` and set `isPublic: true`; the `rdp.auth` configuration remains the same.

> **Note:**
>
> The `domain` field is optional for local accounts. The `user` field is required when `password.fromSecret` is set.

## Upstream TLS

Both RDP modes use a TLS-protected enhanced RDP security channel to the upstream. Windows RDP servers commonly present self-signed certificates. For production use, pin the accepted upstream certificate fingerprints with `pinnedCertSHA256`:

```yaml
kind: Service
metadata:
  name: desktop
spec:
  mode: RDP
  config:
    upstream:
      url: rdp://windows.internal:3389
    rdp:
      upstreamTLS:
        # !mark(1:2)
        pinnedCertSHA256:
          - "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08"
```

More than one fingerprint can be provided during certificate rotation. Colons in a fingerprint and the `sha256/` prefix are accepted. The connection is rejected when the upstream certificate matches none of the configured fingerprints.

You can disable upstream certificate verification with `allowAnyCert`:

```yaml
rdp:
  upstreamTLS:
    # !mark
    allowAnyCert: true
```

> **Warning:**
>
> Use `allowAnyCert` only for testing or troubleshooting. It accepts any certificate presented by the upstream and does not authenticate the upstream host.

## Access Control

Access to both `RDP` and `RDP_WEB` *Services* is authorized when the connection starts. Conditions can use the *User*, *Groups*, *Session*, *Device* and other request context. This example restricts access to members of the `ops` *Group*:

```yaml
kind: Service
metadata:
  name: desktop
spec:
  mode: RDP
  config:
    upstream:
      url: rdp://windows.internal:3389
  authorization:
    inlinePolicies:
      - spec:
          rules:
            - effect: ALLOW
              condition:
                # !mark
                match: '"ops" in ctx.user.spec.groups'
```

RDP does not expose desktop activity as individual application-layer requests. Authorization therefore applies to the connection rather than to actions performed inside the remote desktop.

## Dynamic Configuration

Dynamic configuration can select a different upstream or injected credential from identity and context (read more [here](https://octelium.com/docs/octelium/latest/management/core/service/dynamic-config.md)). In this example, members of the `ops` *Group* receive the administrator credential while other authorized *Users* receive an unprivileged credential:

```yaml
kind: Service
metadata:
  name: desktop
spec:
  mode: RDP
  dynamicConfig:
    configs:
      - name: administrator
        upstream:
          url: rdp://windows.internal:3389
        rdp:
          auth:
            user: Administrator
            password:
              fromSecret: admin-password
          upstreamTLS:
            pinnedCertSHA256:
              - "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08"
      - name: user
        upstream:
          url: rdp://windows.internal:3389
        rdp:
          auth:
            user: rdp-user
            password:
              fromSecret: user-password
          upstreamTLS:
            pinnedCertSHA256:
              - "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08"
    rules:
      - condition:
          match: '"ops" in ctx.user.spec.groups'
        configName: administrator
      - condition:
          matchAny: true
        configName: user
```

The same dynamic configuration structure is supported by `RDP_WEB`.
