From 62119522bbcf814b6aac832a3da2449002e4fab5 Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Wed, 23 Mar 2022 14:41:31 -0700 Subject: [PATCH] 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. See https://github.com/golang/go/issues/51994 and https://go-review.googlesource.com/c/crypto/+/395314 Change-Id: I83ea80901d4977d8f78523e3d1e16e0a7df5b172 (cherry picked from commit 4a431fab27b09acb1458fbb8709e12b2760e58a2) --- ssh/server.go | 14 ++++++++++++- ssh/session_test.go | 51 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 64 insertions(+), 1 deletion(-) diff --git a/ssh/server.go b/ssh/server.go index 70045bdfd8..2260b20afc 100644 --- a/ssh/server.go +++ b/ssh/server.go @@ -68,8 +68,16 @@ type ServerConfig struct { // 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 @@ userAuthLoop: 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 a4e1a59420..154bbb9b24 100644 --- a/ssh/session_test.go +++ b/ssh/session_test.go @@ -780,3 +780,54 @@ func TestHostKeyAlgorithms(t *testing.T) { 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) + } +}