proxyjump.go

v0.5.0
Doc Versions Source
1
package transport
2
3
import (
4
	"strconv"
5
	"strings"
6
7
	"go.bigb.es/auxilia/culpa"
8
)
9
10
// jumpHost is one hop in a ProxyJump chain, as parsed from a `[user@]host[:port]`
11
// token. Empty user/port mean "resolve from ssh_config or default".
12
type jumpHost struct {
13
	user string
14
	host string
15
	port int
16
}
17
18
// parseJumpSpec parses an ssh ProxyJump value — a comma-separated list of
19
// `[user@]host[:port]` hops, applied left to right. An empty spec or the literal
20
// "none" disables jumping (ssh semantics) and returns a nil chain. A malformed
21
// hop (empty host, non-numeric port) is a hard error rather than a silent skip.
22
func parseJumpSpec(spec string) ([]jumpHost, error) {
23
	spec = strings.TrimSpace(spec)
24
	if spec == "" || spec == "none" {
25
		return nil, nil
26
	}
27
	var hops []jumpHost
28
	for _, part := range strings.Split(spec, ",") {
29
		part = strings.TrimSpace(part)
30
		if part == "" {
31
			return nil, culpa.WithCode(culpa.Errorf("proxyjump: empty hop in %q", spec), "SSH_PROXYJUMP")
32
		}
33
		var jh jumpHost
34
		if at := strings.LastIndex(part, "@"); at >= 0 {
35
			jh.user = part[:at]
36
			part = part[at+1:]
37
		}
38
		if colon := strings.LastIndex(part, ":"); colon >= 0 {
39
			portStr := part[colon+1:]
40
			p, err := strconv.Atoi(portStr)
41
			if err != nil {
42
				return nil, culpa.WithCode(
43
					culpa.Errorf("proxyjump: invalid port %q in hop %q", portStr, spec), "SSH_PROXYJUMP")
44
			}
45
			jh.host = part[:colon]
46
			jh.port = p
47
		} else {
48
			jh.host = part
49
		}
50
		if jh.host == "" {
51
			return nil, culpa.WithCode(culpa.Errorf("proxyjump: empty host in hop %q", spec), "SSH_PROXYJUMP")
52
		}
53
		hops = append(hops, jh)
54
	}
55
	return hops, nil
56
}
57

Source Files