diff --git a/README.md b/README.md index 008663cc..614b6d7e 100644 --- a/README.md +++ b/README.md @@ -546,6 +546,7 @@ Since WebSockets support is required: - client exits non-zero when `--max-retry-count` is exhausted; new `--min-retry-interval` (default 1s); `socks5://` accepted for `--proxy` - `go install` builds report their real version; sessions and failed logins are logged at info level - new `--disable-forward` server flag refuses connections through forward (non-reverse) remotes; this flag is specific to this fork and is not in upstream + - refused outbound connections are logged at info level with the requested target (`Denied outbound connection to ""`) - releases now ship goreleaser-built multi-arch Docker images to GHCR and Docker Hub with correctly stamped versions; releasing is two-stage — tagging builds a draft GitHub release plus version-tagged images, and publishing the draft promotes the Docker `latest` / `X` / `X.Y` tags ### Upgrading to 1.12 diff --git a/share/tunnel/tunnel_out_ssh.go b/share/tunnel/tunnel_out_ssh.go index a54f3e10..1c3fa192 100644 --- a/share/tunnel/tunnel_out_ssh.go +++ b/share/tunnel/tunnel_out_ssh.go @@ -34,7 +34,8 @@ func (t *Tunnel) handleSSHChannels(ctx context.Context, chans <-chan ssh.NewChan func (t *Tunnel) handleSSHChannel(ctx context.Context, ch ssh.NewChannel) { if !t.Config.Outbound { - t.Debugf("Denied outbound connection") + //info-level so refusals are visible without -v; the target is client-controlled, so quote it + t.Infof("Denied outbound connection to %q", string(ch.ExtraData())) ch.Reject(ssh.Prohibited, "Denied outbound connection") return } diff --git a/share/tunnel/tunnel_out_ssh_test.go b/share/tunnel/tunnel_out_ssh_test.go new file mode 100644 index 00000000..1b8ef9db --- /dev/null +++ b/share/tunnel/tunnel_out_ssh_test.go @@ -0,0 +1,46 @@ +package tunnel + +import ( + "bytes" + "testing" + + "github.com/jpillora/chisel/share/cio" + "golang.org/x/crypto/ssh" +) + +type rejectedChannel struct { + extra string + reason ssh.RejectionReason + message string +} + +func (c *rejectedChannel) Accept() (ssh.Channel, <-chan *ssh.Request, error) { + panic("channel should not be accepted") +} + +func (c *rejectedChannel) Reject(reason ssh.RejectionReason, message string) error { + c.reason, c.message = reason, message + return nil +} + +func (c *rejectedChannel) ChannelType() string { return "chisel" } +func (c *rejectedChannel) ExtraData() []byte { return []byte(c.extra) } + +func TestDeniedOutboundConnectionLoggedAtInfo(t *testing.T) { + var logs bytes.Buffer + logger := cio.NewLoggerFlag("server", 0) + logger.Info = true + logger.SetOutput(&logs) + tun := &Tunnel{Config: Config{Logger: logger, Outbound: false}} + + ch := &rejectedChannel{extra: "example.com:80\nserver: forged"} + tun.handleSSHChannel(nil, ch) + + if ch.reason != ssh.Prohibited || ch.message != "Denied outbound connection" { + t.Fatalf("unexpected rejection: %v %q", ch.reason, ch.message) + } + const want = "server: Denied outbound connection to \"example.com:80\\nserver: forged\"\n" + if got := logs.String(); got != want { + t.Fatalf("unexpected log:\n got: %q\nwant: %q", got, want) + } +}