Skip to content

Commit 82f3a41

Browse files
committed
ssh: convert to pan, misc fixes and cleanup
- update pty dependency (old version is broken for go >= 1.15) - redo the path policy and selector configuration, now based on pan. Make more useful selectors available. Drop related helper packages, merge ssh/sssh into ssh/client/ssh (the package organization here is still rather wacky). - Now uses quicutils.SingleStream, making this incompatible with previous versions. - when executing a command, don't set Uid/Gid if the selected user is already the current user. Only appears to work when sshd is run with root otherwise. - misc fixes and cleanup related to logging and error handling - clean up import order Replace the custom
1 parent aa18eb8 commit 82f3a41

File tree

18 files changed

+356
-798
lines changed

18 files changed

+356
-798
lines changed

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,10 @@ go 1.16
44

55
require (
66
github.com/bclicn/color v0.0.0-20180711051946-108f2023dc84
7+
github.com/creack/pty v1.1.17
78
github.com/gorilla/handlers v1.5.1
89
github.com/inconshreveable/log15 v0.0.0-20180818164646-67afb5ed74ec
910
github.com/kormat/fmt15 v0.0.0-20181112140556-ee69fecb2656
10-
github.com/kr/pty v1.1.8
1111
github.com/lucas-clemente/quic-go v0.23.0
1212
github.com/mattn/go-sqlite3 v1.14.4
1313
github.com/msteinert/pam v0.0.0-20190215180659-f29b9f28d6f9

go.sum

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -132,8 +132,9 @@ github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfc
132132
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
133133
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
134134
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
135-
github.com/creack/pty v1.1.9 h1:uDmaGzcdjhF4i/plgjmEsriH11Y0o7RKapEf/LDaM3w=
136135
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
136+
github.com/creack/pty v1.1.17 h1:QeVUsEDNrLBW4tMgZHvxy18sKtr6VI492kBhUfhDJNI=
137+
github.com/creack/pty v1.1.17/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
137138
github.com/cyberdelia/templates v0.0.0-20141128023046-ca7fffd4298c/go.mod h1:GyV+0YP4qX0UQ7r2MoYZ+AvYDp12OF5yg4q8rGnyNh4=
138139
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
139140
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
@@ -394,8 +395,6 @@ github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfn
394395
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
395396
github.com/kr/pty v1.1.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
396397
github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA=
397-
github.com/kr/pty v1.1.8 h1:AkaSdXYQOWeaO3neb8EM634ahkXXe3jYbVh/F9lq+GI=
398-
github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
399398
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
400399
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
401400
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=

ssh/client/clientconfig/clientconfig_test.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,9 @@ import (
1919
"strings"
2020
"testing"
2121

22-
"github.com/netsec-ethz/scion-apps/ssh/config"
2322
. "github.com/smartystreets/goconvey/convey"
23+
24+
"github.com/netsec-ethz/scion-apps/ssh/config"
2425
)
2526

2627
func TestDefaultConfig(t *testing.T) {

ssh/client/main.go

Lines changed: 18 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,8 @@
1515
package main
1616

1717
import (
18-
"encoding/json"
18+
"context"
1919
"fmt"
20-
"io/ioutil"
2120
golog "log"
2221
"net"
2322
"os"
@@ -28,13 +27,12 @@ import (
2827
log "github.com/inconshreveable/log15"
2928
"golang.org/x/term"
3029
"gopkg.in/alecthomas/kingpin.v2"
30+
"inet.af/netaddr"
3131

32-
"github.com/scionproto/scion/go/lib/pathpol"
33-
32+
"github.com/netsec-ethz/scion-apps/pkg/pan"
3433
"github.com/netsec-ethz/scion-apps/ssh/client/clientconfig"
3534
"github.com/netsec-ethz/scion-apps/ssh/client/ssh"
3635
"github.com/netsec-ethz/scion-apps/ssh/config"
37-
"github.com/netsec-ethz/scion-apps/ssh/scionutils"
3836
"github.com/netsec-ethz/scion-apps/ssh/utils"
3937
)
4038

@@ -46,9 +44,12 @@ var (
4644
localForward = kingpin.Flag("local-forward", "Forward remote address connections to listening port. Format: listening_port:remote_address").Short('L').String()
4745
options = kingpin.Flag("option", "Set an option").Short('o').Strings()
4846
configFiles = kingpin.Flag("config", "Configuration files").Short('c').Default("/etc/ssh/ssh_config", "~/.ssh/config").Strings()
49-
policyFile = kingpin.Flag("policy-file", "Path to the JSON policy file").Default("").String()
50-
policyName = kingpin.Flag("policy-name", "Name of policy to be applied.").Default("").String()
51-
pathSelection = kingpin.Flag("selection", "Path selection mode").Default("arbitrary").Enum("static", "arbitrary", "random", "round-robin")
47+
interactive = kingpin.Flag("interactive", "Prompt user for interactive path selection").Bool()
48+
sequence = kingpin.Flag("sequence", "Sequence of space separated hop predicates to specify path").Default("").String()
49+
preference = kingpin.Flag("preference", "Preference sorting order for paths. "+
50+
"Comma-separated list of available sorting options: "+
51+
strings.Join(pan.AvailablePreferencePolicies, "|")).Default("").String()
52+
pathSelector = kingpin.Flag("selector", "Path selection mode").Default("default").Enum(ssh.AvailablePathSelectors...)
5253

5354
// TODO: additional file paths
5455
knownHostsFile = kingpin.Flag("known-hosts", "File where known hosts are stored").ExistingFile()
@@ -145,37 +146,20 @@ func main() {
145146
if remoteUsername == "" {
146147
remoteUsername = localUser.Username
147148
}
148-
var policyMap pathpol.PolicyMap
149-
var policy *pathpol.Policy
150-
if *policyFile != "" {
151-
file, err := ioutil.ReadFile(*policyFile)
152-
if err != nil {
153-
golog.Panicf("Cannot read policy file: %v", err)
154-
}
155-
err = json.Unmarshal(file, &policyMap)
156-
if err != nil {
157-
golog.Panicf("Cannot unmarshal policy form file: %v", err)
158-
}
159-
extPolicy, policyExists := policyMap[*policyName]
160-
161-
if !policyExists {
162-
golog.Panicf("No policy with name %s exists", *policyName)
163-
}
164-
policy = extPolicy.Policy
165-
}
166-
appConf, err := scionutils.NewPathAppConf(policy, *pathSelection)
149+
sshClient, err := ssh.Create(remoteUsername, conf, PromptPassword, verifyNewKeyHandler)
167150
if err != nil {
168-
golog.Panicf("Invalid application config: %v", err)
151+
golog.Panicf("Error creating ssh client: %v", err)
169152
}
170153

171-
sshClient, err := ssh.Create(remoteUsername, conf, PromptPassword, verifyNewKeyHandler, appConf)
154+
policy, err := pan.PolicyFromCommandline(*sequence, *preference, *interactive)
172155
if err != nil {
173-
golog.Panicf("Error creating ssh client: %v", err)
156+
golog.Fatal(err)
174157
}
175158

176159
serverAddress := fmt.Sprintf("%s:%v", conf.HostAddress, conf.Port)
177160

178-
err = sshClient.Connect(serverAddress)
161+
ctx := context.Background()
162+
err = sshClient.Connect(ctx, serverAddress, policy, *pathSelector)
179163
if err != nil {
180164
golog.Panicf("Error connecting: %v", err)
181165
}
@@ -189,7 +173,8 @@ func main() {
189173
golog.Panicf("Error parsing forwarding port: %v", err)
190174
}
191175

192-
err = sshClient.StartTunnel(uint16(port), localForward[1])
176+
local := netaddr.IPPortFrom(netaddr.IP{}, uint16(port))
177+
err = sshClient.StartTunnel(local, localForward[1])
193178
if err != nil {
194179
golog.Panicf("Error starting tunnel: %v", err)
195180
}
@@ -204,7 +189,7 @@ func main() {
204189
golog.Panicf("Error starting shell: %v", err)
205190
}
206191
} else {
207-
log.Debug("Running command: %s", runCommand)
192+
log.Debug("Running command", "cmd", runCommand)
208193

209194
err = sshClient.ConnectPipes(os.Stdin, os.Stdout)
210195
if err != nil {

ssh/client/ssh/knownhosts/knownhosts.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ import (
2929

3030
"golang.org/x/crypto/ssh"
3131

32-
"github.com/netsec-ethz/scion-apps/pkg/appnet"
32+
"github.com/netsec-ethz/scion-apps/pkg/pan"
3333
)
3434

3535
// See the sshd manpage
@@ -151,7 +151,7 @@ func keyEq(a, b ssh.PublicKey) bool {
151151

152152
// IsAuthorityForHost can be used as a callback in ssh.CertChecker
153153
func (db *hostKeyDB) IsHostAuthority(remote ssh.PublicKey, address string) bool {
154-
h, p, err := appnet.SplitHostPort(address)
154+
h, p, err := pan.SplitHostPort(address)
155155
if err != nil {
156156
return false
157157
}
@@ -272,7 +272,7 @@ func newHostnameMatcher(pattern string) (matcher, error) {
272272
}
273273

274274
var err error
275-
a.host, a.port, err = appnet.SplitHostPort(p)
275+
a.host, a.port, err = pan.SplitHostPort(p)
276276
if err != nil {
277277
a.host = p
278278
a.port = "22"
@@ -331,7 +331,7 @@ func (db *hostKeyDB) check(address string, remote net.Addr, remoteKey ssh.Public
331331
return &RevokedError{Revoked: *revoked}
332332
}
333333

334-
host, port, err := appnet.SplitHostPort(remote.String())
334+
host, port, err := pan.SplitHostPort(remote.String())
335335
if err != nil {
336336
return fmt.Errorf("knownhosts: SplitHostPort(%s): %v", remote, err)
337337
}
@@ -341,7 +341,7 @@ func (db *hostKeyDB) check(address string, remote net.Addr, remoteKey ssh.Public
341341
}
342342

343343
if address != "" {
344-
host, port, err := appnet.SplitHostPort(address)
344+
host, port, err := pan.SplitHostPort(address)
345345
if err != nil {
346346
return fmt.Errorf("knownhosts: SplitHostPort(%s): %v", address, err)
347347
}
@@ -435,7 +435,7 @@ func New(files ...string) (ssh.HostKeyCallback, error) {
435435

436436
// Normalize normalizes an address into the form used in known_hosts
437437
func Normalize(address string) string {
438-
host, port, err := appnet.SplitHostPort(address)
438+
host, port, err := pan.SplitHostPort(address)
439439
if err != nil {
440440
host = address
441441
port = "22"

ssh/client/ssh/scion.go

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
// Copyright 2020 ETH Zurich
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package ssh
16+
17+
import (
18+
"context"
19+
"crypto/tls"
20+
21+
"github.com/lucas-clemente/quic-go"
22+
"golang.org/x/crypto/ssh"
23+
"inet.af/netaddr"
24+
25+
"github.com/netsec-ethz/scion-apps/pkg/pan"
26+
"github.com/netsec-ethz/scion-apps/pkg/quicutil"
27+
)
28+
29+
// dialSCION starts a client connection to the given SSH server over SCION using QUIC.
30+
func dialSCION(ctx context.Context,
31+
addr string,
32+
policy pan.Policy,
33+
selector string,
34+
config *ssh.ClientConfig) (*ssh.Client, error) {
35+
36+
remote, err := pan.ResolveUDPAddr(addr)
37+
if err != nil {
38+
return nil, err
39+
}
40+
sel, err := selectorByName(selector)
41+
if err != nil {
42+
return nil, err
43+
}
44+
tlsConf := &tls.Config{
45+
NextProtos: []string{quicutil.SingleStreamProto},
46+
InsecureSkipVerify: true,
47+
}
48+
quicConf := &quic.Config{
49+
KeepAlive: true,
50+
}
51+
sess, err := pan.DialQUIC(ctx, netaddr.IPPort{}, remote, policy, sel, "", tlsConf, quicConf)
52+
if err != nil {
53+
return nil, err
54+
}
55+
stream, err := quicutil.NewSingleStream(sess)
56+
if err != nil {
57+
return nil, err
58+
}
59+
conn, nc, rc, err := ssh.NewClientConn(stream, addr, config)
60+
if err != nil {
61+
return nil, err
62+
}
63+
return ssh.NewClient(conn, nc, rc), nil
64+
}
65+
66+
// tunnelDialSCION creates a tunnel using the given SSH client.
67+
func tunnelDialSCION(client *ssh.Client, addr string) (ssh.Channel, error) {
68+
openChannelData := directSCIONData{
69+
addr: addr,
70+
}
71+
72+
c, requests, err := client.OpenChannel("direct-scionquic", ssh.Marshal(&openChannelData))
73+
if err != nil {
74+
return nil, err
75+
}
76+
77+
go ssh.DiscardRequests(requests)
78+
return c, nil
79+
}
80+
81+
type directSCIONData struct {
82+
addr string
83+
}

0 commit comments

Comments
 (0)