diff --git a/docs/cspell.json b/docs/cspell.json
index 73aa77b8167be..34e99df55ff2e 100644
--- a/docs/cspell.json
+++ b/docs/cspell.json
@@ -120,6 +120,7 @@
"Kanban",
"Keycloak",
"Keyspaces",
+ "knownhosts",
"Kubernetes",
"Kubes",
"LDAPS",
@@ -187,6 +188,7 @@
"Relogin",
"SAMLIDP",
"SCIM",
+ "sendall",
"SECURITYADMIN",
"SIEM",
"SIGINT",
@@ -479,6 +481,7 @@
"fluxcd",
"ftmg",
"fullchain",
+ "fprint",
"gacc",
"gcloud",
"gcpproj",
@@ -714,6 +717,7 @@
"parquetlog",
"pastable",
"pasteable",
+ "paramiko",
"persistentvolume",
"persistentvolumeclaim",
"pgaadauth",
@@ -814,6 +818,7 @@
"serviceacct",
"servicecfg",
"serviceid",
+ "sess",
"session",
"setspn",
"sharded",
diff --git a/docs/pages/enroll-resources/machine-id/reference/configuration.mdx b/docs/pages/enroll-resources/machine-id/reference/configuration.mdx
index 4024a8a4f8f13..3ba11f0878b01 100644
--- a/docs/pages/enroll-resources/machine-id/reference/configuration.mdx
+++ b/docs/pages/enroll-resources/machine-id/reference/configuration.mdx
@@ -1,6 +1,7 @@
---
title: Machine ID Configuration Reference
description: Configuration reference for Teleport Machine ID.
+tocDepth: 4
---
This reference documents the various options that can be configured in the `tbot`
@@ -515,6 +516,150 @@ destination:
- `v1.sock`: the Unix socket that the multiplexer listens on.
- `agent.sock`: the Unix socket that the SSH agent listens on.
+##### Using the SSH multiplexer programmatically
+
+To use the SSH multiplexer programmatically, your SSH client library will need
+to support one of two things:
+
+- The ability to use a ProxyCommand with FDPass. If so, you can use the
+ `ssh_config` file generated by `tbot` to configure the SSH client.
+- The ability to accept an open socket to use as the connection to the SSH
+ server. You will then need to manually connect to the socket and send the
+ multiplexer request.
+
+The `v1.sock` Unix Domain Socket implements the V1 Teleport SSH multiplexer
+protocol. The client must first send a short request message to indicate the
+desired target host and port, terminated with a null byte. The multiplexer will
+then begin to forward traffic to the target host and port. The client can then
+make an SSH connection.
+
+
+```python
+import os
+import paramiko
+import socket
+
+host = "ubuntu.example.teleport.sh"
+username = "root"
+port = 3022
+directory_destination = "/opt/machine-id"
+
+# Connect to Mux Unix Domain Socket
+sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
+sock.connect(os.path.join(directory_destination, "v1.sock"))
+# Send the connection request specifying the server you wish to connect to
+sock.sendall(f"{host}:{port}\x00".encode("utf-8"))
+
+# We must set the env var as Paramiko does not make this configurable...
+os.environ["SSH_AUTH_SOCK"] = os.path.join(directory_destination, "agent.sock")
+
+ssh_config = paramiko.SSHConfig()
+with open(os.path.join(directory_destination, "ssh_config")) as f:
+ ssh_config.parse(f)
+
+ssh_client = paramiko.SSHClient()
+
+# Paramiko does not support known_hosts with CAs: https://github.com/paramiko/paramiko/issues/771
+# Therefore, we must disable host key checking
+ssh_client.set_missing_host_key_policy(paramiko.WarningPolicy())
+
+ssh_client.connect(
+ hostname=host,
+ port=port,
+ username=username,
+ sock=sock
+)
+
+stdin, stdout, stderr = ssh_client.exec_command("hostname")
+print(stdout.read().decode())
+```
+
+
+
+```go
+package main
+
+import (
+ "fmt"
+ "net"
+ "path/filepath"
+
+ "golang.org/x/crypto/ssh"
+ "golang.org/x/crypto/ssh/agent"
+ "golang.org/x/crypto/ssh/knownhosts"
+)
+
+func main() {
+ host := "ubuntu.example.teleport.sh"
+ username := "root"
+ directoryDestination := "/opt/machine-id"
+
+ // Setup Agent and Known Hosts
+ agentConn, err := net.Dial(
+ "unix", filepath.Join(directoryDestination, "agent.sock"),
+ )
+ if err != nil {
+ panic(err)
+ }
+ defer agentConn.Close()
+ agentClient := agent.NewClient(agentConn)
+ hostKeyCallback, err := knownhosts.New(
+ filepath.Join(directoryDestination, "known_hosts"),
+ )
+ if err != nil {
+ panic(err)
+ }
+
+ // Create SSH Config
+ sshConfig := &ssh.ClientConfig{
+ Auth: []ssh.AuthMethod{
+ ssh.PublicKeysCallback(agentClient.Signers),
+ },
+ User: username,
+ HostKeyCallback: hostKeyCallback,
+ }
+
+ // Dial Unix Domain Socket and send multiplexing request
+ conn, err := net.Dial(
+ "unix", filepath.Join(directoryDestination, "v1.sock"),
+ )
+ if err != nil {
+ panic(err)
+ }
+ defer conn.Close()
+ _, err = fmt.Fprint(conn, fmt.Sprintf("%s:0\x00", host))
+ if err != nil {
+ panic(err)
+ }
+
+ sshConn, sshChan, sshReq, err := ssh.NewClientConn(
+ conn,
+ // Port here doesn't matter because Multiplexer has already established
+ // connection.
+ fmt.Sprintf("%s:22", host),
+ sshConfig,
+ )
+ if err != nil {
+ panic(err)
+ }
+ sshClient := ssh.NewClient(sshConn, sshChan, sshReq)
+ defer sshClient.Close()
+
+ sshSess, err := sshClient.NewSession()
+ if err != nil {
+ panic(err)
+ }
+ defer sshSess.Close()
+
+ out, err := sshSess.CombinedOutput("hostname")
+ if err != nil {
+ panic(err)
+ }
+ fmt.Println(string(out))
+}
+```
+
+
### Destinations
A destination is somewhere that `tbot` can read and write artifacts.