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.