-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathserver.go
173 lines (152 loc) · 5.1 KB
/
server.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
package main
import (
"fmt"
"log"
"net"
"strings"
"time"
"github.com/rorycl/sshagentca/util"
"golang.org/x/crypto/ssh"
"golang.org/x/crypto/ssh/agent"
"golang.org/x/term"
)
// Serve the SSH Agent Forwarding Certificate Authority Server. The
// server requires connections to have public keys registered in the
// user_principals settings yaml file.
// The handleConnections goroutine prints information to the the client
// terminal and adds a certificate to the user's ssh forwarded agent.
// The ssh server is drawn from the example in the ssh server docs at
// https://godoc.org/golang.org/x/crypto/ssh#ServerConn and the Scalingo
// blog posting at
// https://scalingo.com/blog/writing-a-replacement-to-openssh-using-go-22.html
func Serve(options Options, privateKey ssh.Signer, caKey ssh.Signer, settings util.Settings) {
// configure server
sshConfig := &ssh.ServerConfig{
// public key callback taken directly from ssh.ServerConn example
PublicKeyCallback: func(c ssh.ConnMetadata, pubKey ssh.PublicKey) (*ssh.Permissions, error) {
_, err := settings.UserByFingerprint(ssh.FingerprintSHA256(pubKey))
if err == nil {
return &ssh.Permissions{
Extensions: map[string]string{
"pubkey-fp": ssh.FingerprintSHA256(pubKey),
},
}, nil
}
return nil, fmt.Errorf("unknown public key for %q", c.User())
},
}
sshConfig.AddHostKey(privateKey)
// setup net listener
log.Printf("\n\nStarting server connection for %s...", settings.Organisation)
addrPort := strings.Join([]string{options.IPAddress, options.Port}, ":")
listener, err := net.Listen("tcp", addrPort)
if err != nil {
log.Fatalf("Failed to listen on %s", addrPort)
} else {
log.Printf("Listening on %s", addrPort)
}
for {
// make tcp connection
tcpConn, err := listener.Accept()
if err != nil {
log.Printf("failed to accept incoming connection (%s)", err)
continue
}
// provide handshake
sshConn, chans, _, err := ssh.NewServerConn(tcpConn, sshConfig)
if err != nil {
log.Printf("failed to handshake (%s)", err)
continue
}
// extract user
user, err := settings.UserByFingerprint(sshConn.Permissions.Extensions["pubkey-fp"])
if err != nil {
log.Printf("verification error from unknown user %s", sshConn.Permissions.Extensions["pubkey-fp"])
continue
}
// report remote address, user and key
log.Printf("new ssh connection from %s (%s)", sshConn.RemoteAddr(), sshConn.ClientVersion())
log.Printf("user %s logged in with key %s", user.Name, user.Fingerprint)
// https://lists.gt.net/openssh/dev/72190
agentChan, reqs, err := sshConn.OpenChannel("auth-agent@openssh.com", nil)
if err != nil {
log.Printf("Could not open agent channel %s", err)
sshConn.Close()
continue
}
agentConn := agent.NewClient(agentChan)
// discard incoming out-of-band requests
go ssh.DiscardRequests(reqs)
// accept all channels
go handleChannels(chans, user, settings, sshConn, agentConn, caKey)
}
}
// write to the connection terminal, ignoring errors
func termWriter(t *term.Terminal, s string) {
_, _ = t.Write([]byte(s + "\n"))
}
// close the ssh client connection politely
func chanCloser(c ssh.Channel, isError bool) {
var status = struct {
Status uint32
}{uint32(0)}
if isError {
status.Status = 1
}
// https://godoc.org/golang.org/x/crypto/ssh#Channel
_, err := c.SendRequest("exit-status", false, ssh.Marshal(status))
if err != nil {
log.Printf("Could not close ssh client connection: %s", err)
}
}
// Service the incoming channel. The certErr channel indicates when the
// certificate has finished generation
func handleChannels(chans <-chan ssh.NewChannel, user *util.UserPrincipals,
settings util.Settings, sshConn *ssh.ServerConn, agentConn agent.ExtendedAgent,
caKey ssh.Signer) {
defer sshConn.Close()
for thisChan := range chans {
if thisChan.ChannelType() != "session" {
_ = thisChan.Reject(ssh.Prohibited, "channel type is not a session")
return
}
// accept channel
ch, reqs, err := thisChan.Accept()
if err != nil {
log.Println("did not accept channel request", err)
return
}
defer ch.Close()
// only respond to "exec" type requests
req := <-reqs
if req.Type != "auth-agent-req@openssh.com" {
_, err = ch.Write([]byte("request type not supported\n"))
if err != nil {
log.Printf("channel write error for invalid request type %v", err)
}
return
}
// terminal
term := term.NewTerminal(ch, "")
termWriter(term, settings.Banner)
termWriter(term, fmt.Sprintf("welcome, %s", user.Name))
// add certificate to agent, let the user know, then close the
// connection
err = addCertToAgent(agentConn, caKey, user, settings)
if err != nil {
log.Printf("certificate creation error %s\n", err)
termWriter(term, "certificate creation error")
termWriter(term, "goodbye\n")
chanCloser(ch, true)
} else {
log.Printf("certificate creation and insertion in agent done\n")
termWriter(term, "certificate generation complete")
termWriter(term, "run 'ssh-add -l' to view")
termWriter(term, "goodbye\n")
chanCloser(ch, false)
}
time.Sleep(500 * time.Millisecond)
log.Println("closing the connection")
sshConn.Close()
}
}