From 29a8a6f74eb814372fcd899a7ef6a5544cd1eb23 Mon Sep 17 00:00:00 2001 From: Noah Stride Date: Fri, 26 Jul 2024 11:36:39 +0100 Subject: [PATCH 1/4] Add examples on using SSH multiplexer programtically --- .../machine-id/reference/configuration.mdx | 144 ++++++++++++++++++ 1 file changed, 144 insertions(+) diff --git a/docs/pages/enroll-resources/machine-id/reference/configuration.mdx b/docs/pages/enroll-resources/machine-id/reference/configuration.mdx index 4024a8a4f8f13..59df306a7c1ab 100644 --- a/docs/pages/enroll-resources/machine-id/reference/configuration.mdx +++ b/docs/pages/enroll-resources/machine-id/reference/configuration.mdx @@ -515,6 +515,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 will 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. + +Example in Python using Paramiko: + +```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()) +``` + +Example in Go: + +```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. From 381daa8f8746ff9a0f398cee122963c75538f020 Mon Sep 17 00:00:00 2001 From: Noah Stride Date: Mon, 29 Jul 2024 09:45:38 +0100 Subject: [PATCH 2/4] Update docs/pages/enroll-resources/machine-id/reference/configuration.mdx Co-authored-by: Paul Gottschling --- .../enroll-resources/machine-id/reference/configuration.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/pages/enroll-resources/machine-id/reference/configuration.mdx b/docs/pages/enroll-resources/machine-id/reference/configuration.mdx index 59df306a7c1ab..6914f9369137f 100644 --- a/docs/pages/enroll-resources/machine-id/reference/configuration.mdx +++ b/docs/pages/enroll-resources/machine-id/reference/configuration.mdx @@ -528,7 +528,7 @@ to support one of two things: 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 will a null byte. The multiplexer will +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. From 5939b969ac196d833e491721e5d31f0794b7186d Mon Sep 17 00:00:00 2001 From: Noah Stride Date: Mon, 29 Jul 2024 10:20:32 +0100 Subject: [PATCH 3/4] Add details blocks to collapse code --- .../machine-id/reference/configuration.mdx | 153 +++++++++--------- 1 file changed, 77 insertions(+), 76 deletions(-) diff --git a/docs/pages/enroll-resources/machine-id/reference/configuration.mdx b/docs/pages/enroll-resources/machine-id/reference/configuration.mdx index 6914f9369137f..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` @@ -532,8 +533,7 @@ 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. -Example in Python using Paramiko: - +
```python import os import paramiko @@ -573,91 +573,92 @@ ssh_client.connect( stdin, stdout, stderr = ssh_client.exec_command("hostname") print(stdout.read().decode()) ``` +
-Example in Go: - +
```go package main import ( - "fmt" - "net" - "path/filepath" + "fmt" + "net" + "path/filepath" - "golang.org/x/crypto/ssh" - "golang.org/x/crypto/ssh/agent" - "golang.org/x/crypto/ssh/knownhosts" + "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)) + 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 From 27e941e9403e9220c67b0ff35fb82b67371f07e0 Mon Sep 17 00:00:00 2001 From: Noah Stride Date: Mon, 29 Jul 2024 10:46:17 +0100 Subject: [PATCH 4/4] Update cpsell --- docs/cspell.json | 5 +++++ 1 file changed, 5 insertions(+) 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",