-
-
Notifications
You must be signed in to change notification settings - Fork 315
/
Copy pathauthentication_key_request_test.go
229 lines (209 loc) · 6.18 KB
/
authentication_key_request_test.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
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
package utils
import (
"bytes"
"crypto/rand"
"crypto/rsa"
"encoding/json"
"io"
"log"
"net"
"net/http"
"net/http/httptest"
"os"
"testing"
"github.com/spf13/viper"
"golang.org/x/crypto/ssh"
)
// MakeTestKeys returns a slice of randomly generated private keys.
func MakeTestKeys(numKeys int) []*rsa.PrivateKey {
testKeys := make([]*rsa.PrivateKey, numKeys)
for i := 0; i < numKeys; i++ {
key, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
log.Fatal(err)
}
testKeys[i] = key
}
return testKeys
}
type AuthRequestBody struct {
PubKey string `json:"auth_key"`
UserName string `json:"user"`
RemoteAddr string `json:"remote_addr"`
}
// PubKeyHttpHandler returns a http handler function which validates an
// OpenSSH authorized-keys formatted public key against a slice of
// slice authorized keys.
func PubKeyHttpHandler(validPublicKeys *[]rsa.PublicKey, validUsernames *[]string) func(w http.ResponseWriter, r *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
pubKey, err := io.ReadAll(r.Body)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
var reqBody AuthRequestBody
err = json.Unmarshal(pubKey, &reqBody)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
parsedKey, _, _, _, err := ssh.ParseAuthorizedKey([]byte(reqBody.PubKey))
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
marshalled := parsedKey.Marshal()
keyMatch := false
usernameMatch := false
for _, key := range *validPublicKeys {
authorizedKey, err := ssh.NewPublicKey(&key)
if err != nil {
log.Print("Error parsing authorized public key", err)
continue
}
if bytes.Equal(authorizedKey.Marshal(), marshalled) {
keyMatch = true
break
}
}
for _, username := range *validUsernames {
if reqBody.UserName == username {
usernameMatch = true
}
}
if keyMatch && usernameMatch {
w.WriteHeader(http.StatusOK)
} else {
w.WriteHeader(http.StatusUnauthorized)
}
}
}
// HandleSSHConn accepts an incoming client connection, performs the
// auth handshake to test the GetSSHConfig method using the
// authentication-key-request-url flag.
func HandleSSHConn(sshListener net.Listener, successAuth *chan bool) {
conn, err := sshListener.Accept()
if err != nil {
log.Fatal(err)
}
defer conn.Close()
// GetSSHConfig is the method we are testing to validate that it
// can use an http request to validate client public key auth
connection, _, _, err := ssh.NewServerConn(conn, GetSSHConfig())
if err != nil {
*successAuth <- false
return
}
connection.Close()
*successAuth <- true
}
// TestAuthenticationKeyRequest validates that the utils.GetSSHConfig
// PublicKey auth works with the authentication-key-request-url parameter.
func TestAuthenticationKeyRequest(t *testing.T) {
testKeys := MakeTestKeys(3)
// Give sish a temp directory to generate a server ssh host key
dir, err := os.MkdirTemp("", "sish_keys")
if err != nil {
log.Fatal(err)
}
defer os.RemoveAll(dir)
viper.Set("private-keys-directory", dir)
viper.Set("authentication", true)
testCases := []struct {
clientPrivateKey *rsa.PrivateKey
clientUser string
validPublicKeys []rsa.PublicKey
validUsernames []string
expectSuccessAuth bool
overrideHttpUrl string
}{
// valid key, should succeed auth
{
clientPrivateKey: testKeys[0],
clientUser: "ubuntu",
validPublicKeys: []rsa.PublicKey{testKeys[0].PublicKey},
validUsernames: []string{"ubuntu"},
expectSuccessAuth: true,
overrideHttpUrl: "",
},
// invalid key, should be rejected
{
clientPrivateKey: testKeys[0],
clientUser: "ubuntu",
validPublicKeys: []rsa.PublicKey{testKeys[1].PublicKey, testKeys[2].PublicKey},
validUsernames: []string{"ubuntu"},
expectSuccessAuth: false,
overrideHttpUrl: "",
},
// invalid username, should be rejected
{
clientPrivateKey: testKeys[0],
clientUser: "windows",
validPublicKeys: []rsa.PublicKey{testKeys[0].PublicKey},
validUsernames: []string{"ubuntu"},
expectSuccessAuth: false,
overrideHttpUrl: "",
},
// no http service listening on server url, should be rejected
{
clientPrivateKey: testKeys[0],
clientUser: "ubuntu",
validPublicKeys: []rsa.PublicKey{testKeys[0].PublicKey},
validUsernames: []string{"ubuntu"},
expectSuccessAuth: false,
overrideHttpUrl: "http://localhost:61234",
},
// invalid http url, should be rejected
{
clientPrivateKey: testKeys[0],
clientUser: "ubuntu",
validPublicKeys: []rsa.PublicKey{testKeys[0].PublicKey},
validUsernames: []string{"ubuntu"},
expectSuccessAuth: false,
overrideHttpUrl: "notarealurl",
},
}
for caseIdx, c := range testCases {
if c.overrideHttpUrl == "" {
// start an http server that will validate against the specified public keys
httpSrv := httptest.NewServer(http.HandlerFunc(PubKeyHttpHandler(&c.validPublicKeys, &c.validUsernames)))
defer httpSrv.Close()
// set viper to this http server URL as the auth request url it will
// send public keys to for auth validation
viper.Set("authentication-key-request-url", httpSrv.URL)
} else {
viper.Set("authentication-key-request-url", c.overrideHttpUrl)
}
sshListener, err := net.Listen("tcp", "localhost:0")
if err != nil {
t.Error(err)
}
defer sshListener.Close()
successAuth := make(chan bool)
go HandleSSHConn(sshListener, &successAuth)
// attempt to connect to the ssh server using the specified private key
signer, err := ssh.NewSignerFromKey(c.clientPrivateKey)
if err != nil {
t.Error(err)
}
clientConfig := &ssh.ClientConfig{
Auth: []ssh.AuthMethod{
ssh.PublicKeys(signer),
},
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
User: c.clientUser,
}
t.Log(clientConfig)
client, err := ssh.Dial("tcp", sshListener.Addr().String(), clientConfig)
if err != nil {
t.Log("ssh client rejected", err)
} else {
t.Log("ssh client connected")
client.Close()
}
didAuth := <-successAuth
if didAuth != c.expectSuccessAuth {
t.Errorf("Auth %t when should have been %t for case %d", didAuth, c.expectSuccessAuth, caseIdx)
}
}
}