ssh: add ServerConfig.NoClientAuthCallback
It was possible to accept auth type "none" before, but not dynamically
at runtime as a function of the ConnMetadata like the other auth types'
callback hooks.
Fixes golang/go#51994
Change-Id: I83ea80901d4977d8f78523e3d1e16e0a7df5b172
Reviewed-on: https://go-review.googlesource.com/c/crypto/+/395314
Reviewed-by: Roland Shoemaker <roland@golang.org>
Run-TryBot: Brad Fitzpatrick <bradfitz@golang.org>
TryBot-Result: Gopher Robot <gobot@golang.org>
Reviewed-by: Julie Qiu <julieqiu@google.com>
diff --git a/ssh/server.go b/ssh/server.go
index 70045bd..2260b20 100644
--- a/ssh/server.go
+++ b/ssh/server.go
@@ -68,8 +68,16 @@
// NoClientAuth is true if clients are allowed to connect without
// authenticating.
+ // To determine NoClientAuth at runtime, set NoClientAuth to true
+ // and the optional NoClientAuthCallback to a non-nil value.
NoClientAuth bool
+ // NoClientAuthCallback, if non-nil, is called when a user
+ // attempts to authenticate with auth method "none".
+ // NoClientAuth must also be set to true for this be used, or
+ // this func is unused.
+ NoClientAuthCallback func(ConnMetadata) (*Permissions, error)
+
// MaxAuthTries specifies the maximum number of authentication attempts
// permitted per connection. If set to a negative number, the number of
// attempts are unlimited. If set to zero, the number of attempts are limited
@@ -455,7 +463,11 @@
switch userAuthReq.Method {
case "none":
if config.NoClientAuth {
- authErr = nil
+ if config.NoClientAuthCallback != nil {
+ perms, authErr = config.NoClientAuthCallback(s)
+ } else {
+ authErr = nil
+ }
}
// allow initial attempt of 'none' without penalty
diff --git a/ssh/session_test.go b/ssh/session_test.go
index 2568a88..c4b9f0e 100644
--- a/ssh/session_test.go
+++ b/ssh/session_test.go
@@ -779,3 +779,54 @@
t.Fatal("succeeded connecting with unknown hostkey algorithm")
}
}
+
+func TestServerClientAuthCallback(t *testing.T) {
+ c1, c2, err := netPipe()
+ if err != nil {
+ t.Fatalf("netPipe: %v", err)
+ }
+ defer c1.Close()
+ defer c2.Close()
+
+ userCh := make(chan string, 1)
+
+ serverConf := &ServerConfig{
+ NoClientAuth: true,
+ NoClientAuthCallback: func(conn ConnMetadata) (*Permissions, error) {
+ userCh <- conn.User()
+ return nil, nil
+ },
+ }
+ const someUsername = "some-username"
+
+ serverConf.AddHostKey(testSigners["ecdsa"])
+ clientConf := &ClientConfig{
+ HostKeyCallback: InsecureIgnoreHostKey(),
+ User: someUsername,
+ }
+
+ go func() {
+ _, chans, reqs, err := NewServerConn(c1, serverConf)
+ if err != nil {
+ t.Errorf("server handshake: %v", err)
+ userCh <- "error"
+ return
+ }
+ go DiscardRequests(reqs)
+ for ch := range chans {
+ ch.Reject(Prohibited, "")
+ }
+ }()
+
+ conn, _, _, err := NewClientConn(c2, "", clientConf)
+ if err != nil {
+ t.Fatalf("client handshake: %v", err)
+ return
+ }
+ conn.Close()
+
+ got := <-userCh
+ if got != someUsername {
+ t.Errorf("username = %q; want %q", got, someUsername)
+ }
+}