-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Use ssh via vpn from metal-lib (#200)
- Loading branch information
Showing
5 changed files
with
121 additions
and
273 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,139 +1,71 @@ | ||
package cmd | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"net" | ||
"os" | ||
"time" | ||
|
||
"github.com/tailscale/golang-x-crypto/ssh" | ||
"golang.org/x/term" | ||
"path/filepath" | ||
"strings" | ||
|
||
"github.com/metal-stack/metal-go/api/client/vpn" | ||
"github.com/metal-stack/metal-go/api/models" | ||
"github.com/metal-stack/metal-lib/pkg/pointer" | ||
metalssh "github.com/metal-stack/metal-lib/pkg/ssh" | ||
metalvpn "github.com/metal-stack/metal-lib/pkg/vpn" | ||
"github.com/spf13/viper" | ||
) | ||
|
||
// SSHClient opens an interactive ssh session to the host on port with user, authenticated by the key. | ||
func SSHClient(user, keyfile, host string, port int) error { | ||
sshConfig, err := getSSHConfig(user, keyfile) | ||
func (c *firewallCmd) firewallSSHViaVPN(firewall *models.V1FirewallResponse) (err error) { | ||
if firewall.Allocation == nil || firewall.Allocation.Project == nil { | ||
return fmt.Errorf("firewall allocation or allocation.project is nil") | ||
} | ||
projectID := firewall.Allocation.Project | ||
fmt.Fprintf(c.out, "accessing firewall through vpn ") | ||
authKeyResp, err := c.client.VPN().GetVPNAuthKey(vpn.NewGetVPNAuthKeyParams().WithBody(&models.V1VPNRequest{ | ||
Pid: projectID, | ||
Ephemeral: pointer.Pointer(true), | ||
}), nil) | ||
if err != nil { | ||
return fmt.Errorf("failed to create SSH config: %w", err) | ||
return fmt.Errorf("failed to get VPN auth key: %w", err) | ||
} | ||
|
||
client, err := ssh.Dial("tcp", fmt.Sprintf("%s:%d", host, port), sshConfig) | ||
ctx := context.Background() | ||
v, err := metalvpn.Connect(ctx, *firewall.ID, *authKeyResp.Payload.Address, *authKeyResp.Payload.AuthKey) | ||
if err != nil { | ||
return err | ||
} | ||
defer client.Close() | ||
|
||
return createSSHSession(client) | ||
} | ||
defer v.Close() | ||
|
||
// sshClient opens an interactive ssh session to the host on port with user, authenticated by the key. | ||
func sshClientWithConn(user, host, privateKey string, conn net.Conn) error { | ||
sshConfig, err := getSSHConfig(user, privateKey) | ||
if err != nil { | ||
return fmt.Errorf("failed to create SSH config: %w", err) | ||
privateKeyFile := viper.GetString("identity") | ||
if strings.HasPrefix(privateKeyFile, "~/") { | ||
home, _ := os.UserHomeDir() | ||
privateKeyFile = filepath.Join(home, privateKeyFile[2:]) | ||
} | ||
|
||
sshConn, sshChan, req, err := ssh.NewClientConn(conn, host, sshConfig) | ||
privateKey, err := os.ReadFile(privateKeyFile) | ||
if err != nil { | ||
return err | ||
} | ||
client := ssh.NewClient(sshConn, sshChan, req) | ||
s, err := metalssh.NewClientWithConnection("metal", v.TargetIP, privateKey, v.Conn) | ||
if err != nil { | ||
return err | ||
} | ||
defer client.Close() | ||
|
||
return createSSHSession(client) | ||
return s.Connect(nil) | ||
} | ||
|
||
func createSSHSession(client *ssh.Client) error { | ||
session, err := client.NewSession() | ||
// sshClient opens an interactive ssh session to the host on port with user, authenticated by the key. | ||
func sshClient(user, keyfile, host string, port int, idToken *string) error { | ||
privateKey, err := os.ReadFile(keyfile) | ||
if err != nil { | ||
return err | ||
} | ||
defer session.Close() | ||
|
||
// Set IO | ||
session.Stdout = os.Stdout | ||
session.Stderr = os.Stderr | ||
session.Stdin = os.Stdin | ||
// Set up terminal modes | ||
// https://net-ssh.github.io/net-ssh/classes/Net/SSH/Connection/Term.html | ||
// https://www.ietf.org/rfc/rfc4254.txt | ||
// https://godoc.org/golang.org/x/crypto/ssh | ||
// THIS IS THE TITLE | ||
// https://pythonhosted.org/ANSIColors-balises/ANSIColors.html | ||
modes := ssh.TerminalModes{ | ||
ssh.ECHO: 1, // enable echoing | ||
ssh.TTY_OP_ISPEED: 115200, // input speed = 14.4kbaud | ||
ssh.TTY_OP_OSPEED: 115200, // output speed = 14.4kbaud | ||
} | ||
|
||
fileDescriptor := int(os.Stdin.Fd()) | ||
|
||
if term.IsTerminal(fileDescriptor) { | ||
originalState, err := term.MakeRaw(fileDescriptor) | ||
if err != nil { | ||
return err | ||
} | ||
defer func() { | ||
err = term.Restore(fileDescriptor, originalState) | ||
if err != nil { | ||
fmt.Printf("error restoring ssh terminal:%v\n", err) | ||
} | ||
}() | ||
|
||
termWidth, termHeight, err := term.GetSize(fileDescriptor) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
err = session.RequestPty("xterm-256color", termHeight, termWidth, modes) | ||
if err != nil { | ||
return err | ||
} | ||
} | ||
|
||
err = session.Shell() | ||
s, err := metalssh.NewClient(user, host, privateKey, port) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
// You should now be connected via SSH with a fully-interactive terminal | ||
// This call blocks until the user exits the session (e.g. via CTRL + D) | ||
return session.Wait() | ||
} | ||
|
||
func getSSHConfig(user, keyfile string) (*ssh.ClientConfig, error) { | ||
keyfile, err := expandFilepath(keyfile) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
publicKeyAuthMethod, err := publicKey(keyfile) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
return &ssh.ClientConfig{ | ||
User: user, | ||
Auth: []ssh.AuthMethod{ | ||
publicKeyAuthMethod, | ||
}, | ||
//nolint:gosec | ||
HostKeyCallback: ssh.InsecureIgnoreHostKey(), | ||
Timeout: 10 * time.Second, | ||
}, nil | ||
} | ||
|
||
func publicKey(path string) (ssh.AuthMethod, error) { | ||
key, err := os.ReadFile(path) | ||
if err != nil { | ||
return nil, err | ||
} | ||
signer, err := ssh.ParsePrivateKey(key) | ||
if err != nil { | ||
return nil, err | ||
var env *metalssh.Env | ||
if idToken != nil { | ||
env = &metalssh.Env{"LC_METAL_STACK_OIDC_TOKEN": *idToken} | ||
} | ||
return ssh.PublicKeys(signer), nil | ||
return s.Connect(env) | ||
} |
Oops, something went wrong.