Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"github.com/jpillora/chisel/share/ccrypto"
"github.com/jpillora/chisel/share/cio"
"github.com/jpillora/chisel/share/cnet"
"github.com/jpillora/chisel/share/metrics"
"github.com/jpillora/chisel/share/settings"
"github.com/jpillora/chisel/share/tunnel"

Expand All @@ -43,6 +44,8 @@ type Config struct {
TLS TLSConfig
DialContext func(ctx context.Context, network, addr string) (net.Conn, error)
Verbose bool
MetricsAddr string
MetricsNamespace string
}

// TLSConfig for a Client
Expand All @@ -67,6 +70,7 @@ type Client struct {
stop func()
eg *errgroup.Group
tunnel *tunnel.Tunnel
metrics *metrics.Metrics
}

// NewClient creates a new client instance
Expand Down Expand Up @@ -179,13 +183,26 @@ func NewClient(c *Config) (*Client, error) {
HostKeyCallback: client.verifyServer,
Timeout: settings.EnvDuration("SSH_TIMEOUT", 30*time.Second),
}
//initialize metrics if enabled
if c.MetricsAddr != "" {
m, err := metrics.New(c.MetricsNamespace)
if err != nil {
return nil, err
}
client.metrics = m
if err := client.metrics.Start(c.MetricsAddr); err != nil {
return nil, err
}
client.Infof("Metrics server started on %s", c.MetricsAddr)
}
//prepare client tunnel
client.tunnel = tunnel.New(tunnel.Config{
Logger: client.Logger,
Inbound: true, //client always accepts inbound
Outbound: hasReverse,
Socks: hasReverse && hasSocks,
KeepAlive: client.config.KeepAlive,
Metrics: client.metrics,
})
return client, nil
}
Expand Down
55 changes: 55 additions & 0 deletions client/client_connect.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,13 @@ func (c *Client) connectionLoop(ctx context.Context) error {

// connectionOnce connects to the chisel server and blocks
func (c *Client) connectionOnce(ctx context.Context) (connected bool, err error) {
// Record connection attempt
if c.metrics != nil {
(*c.metrics.ClientConnectionAttempts).Inc()
if c.Debug {
c.Debugf("[metrics] client_connection_attempts_total incremented")
}
}
//already closed?
select {
case <-ctx.Done():
Expand All @@ -92,6 +99,12 @@ func (c *Client) connectionOnce(ctx context.Context) (connected bool, err error)
}
wsConn, _, err := d.DialContext(ctx, c.server, c.config.Headers)
if err != nil {
if c.metrics != nil {
c.metrics.ClientConnectionErrors.WithLabelValues("handshake_error").Inc()
if c.Debug {
c.Debugf("[metrics] client_connection_errors_total{cause=\"handshake_error\"} incremented")
}
}
return false, err
}
conn := cnet.NewWebSocketConn(wsConn)
Expand All @@ -103,8 +116,20 @@ func (c *Client) connectionOnce(ctx context.Context) (connected bool, err error)
if strings.Contains(e, "unable to authenticate") {
c.Infof("Authentication failed")
c.Debugf(e)
if c.metrics != nil {
c.metrics.ClientConnectionErrors.WithLabelValues("auth_failure").Inc()
if c.Debug {
c.Debugf("[metrics] client_connection_errors_total{cause=\"auth_failure\"} incremented")
}
}
} else {
c.Infof(e)
if c.metrics != nil {
c.metrics.ClientConnectionErrors.WithLabelValues("handshake_error").Inc()
if c.Debug {
c.Debugf("[metrics] client_connection_errors_total{cause=\"handshake_error\"} incremented")
}
}
}
return false, err
}
Expand All @@ -123,12 +148,42 @@ func (c *Client) connectionOnce(ctx context.Context) (connected bool, err error)
return false, err
}
if len(configerr) > 0 {
if c.metrics != nil {
c.metrics.ClientConnectionErrors.WithLabelValues("handshake_error").Inc()
if c.Debug {
c.Debugf("[metrics] client_connection_errors_total{cause=\"handshake_error\"} incremented")
}
}
return false, errors.New(string(configerr))
}
// Record handshake duration and set connected status
if c.metrics != nil {
duration := time.Since(t0).Seconds()
c.metrics.ClientHandshakeDuration.Observe(duration)
c.metrics.ClientConnected.Set(1)
if c.Debug {
c.Debugf("[metrics] client_handshake_duration_seconds observed: %.6fs", duration)
c.Debugf("[metrics] client_connected set to 1")
}
}
c.Infof("Connected (Latency %s)", time.Since(t0))
//connected, handover ssh connection for tunnel to use, and block
err = c.tunnel.BindSSH(ctx, sshConn, reqs, chans)
c.Infof("Disconnected")
// Set disconnected status
if c.metrics != nil {
c.metrics.ClientConnected.Set(0)
if c.Debug {
c.Debugf("[metrics] client_connected set to 0")
}
// Record transport error if it's not EOF
if err != nil && err != io.EOF && !strings.HasSuffix(err.Error(), "EOF") {
c.metrics.ClientConnectionErrors.WithLabelValues("transport_error").Inc()
if c.Debug {
c.Debugf("[metrics] client_connection_errors_total{cause=\"transport_error\"} incremented")
}
}
}
connected = time.Since(t0) > 5*time.Second
return connected, err
}
9 changes: 9 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,26 @@ require (
github.com/jpillora/backoff v1.0.0
github.com/jpillora/requestlog v1.0.0
github.com/jpillora/sizestr v1.0.0
github.com/prometheus/client_golang v1.20.5
golang.org/x/crypto v0.53.0
golang.org/x/net v0.56.0
golang.org/x/sync v0.21.0
)

require (
github.com/andrew-d/go-termutil v0.0.0-20150726205930-009166a695a2 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/jpillora/ansi v1.0.3 // indirect
github.com/klauspost/compress v1.17.9 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/prometheus/client_model v0.6.1 // indirect
github.com/prometheus/common v0.55.0 // indirect
github.com/prometheus/procfs v0.15.1 // indirect
github.com/tomasen/realip v0.0.0-20180522021738-f0c99a92ddce // indirect
golang.org/x/sys v0.46.0 // indirect
golang.org/x/text v0.38.0 // indirect
google.golang.org/protobuf v1.34.2 // indirect
)

replace github.com/jpillora/chisel => ../chisel
22 changes: 22 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,14 @@ github.com/andrew-d/go-termutil v0.0.0-20150726205930-009166a695a2 h1:axBiC50cNZ
github.com/andrew-d/go-termutil v0.0.0-20150726205930-009166a695a2/go.mod h1:jnzFpU88PccN/tPPhCpnNU8mZphvKxYM9lLNkd8e+os=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/fsnotify/fsnotify v1.10.1 h1:b0/UzAf9yR5rhf3RPm9gf3ehBPpf0oZKIjtpKrx59Ho=
github.com/fsnotify/fsnotify v1.10.1/go.mod h1:TLheqan6HD6GBK6PrDWyDPBaEV8LspOxvPSjC+bVfgo=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/jpillora/ansi v1.0.3 h1:nn4Jzti0EmRfDxm7JtEs5LzCbNwd5sv+0aE+LdS9/ZQ=
Expand All @@ -14,6 +20,20 @@ github.com/jpillora/requestlog v1.0.0 h1:bg++eJ74T7DYL3DlIpiwknrtfdUA9oP/M4fL+Pp
github.com/jpillora/requestlog v1.0.0/go.mod h1:HTWQb7QfDc2jtHnWe2XEIEeJB7gJPnVdpNn52HXPvy8=
github.com/jpillora/sizestr v1.0.0 h1:4tr0FLxs1Mtq3TnsLDV+GYUWG7Q26a6s+tV5Zfw2ygw=
github.com/jpillora/sizestr v1.0.0/go.mod h1:bUhLv4ctkknatr6gR42qPxirmd5+ds1u7mzD+MZ33f0=
github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA=
github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/prometheus/client_golang v1.20.5 h1:cxppBPuYhUnsO6yo/aoRol4L7q7UFfdm+bR9r+8l63Y=
github.com/prometheus/client_golang v1.20.5/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE=
github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E=
github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY=
github.com/prometheus/common v0.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G1dc=
github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8=
github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
github.com/tomasen/realip v0.0.0-20180522021738-f0c99a92ddce h1:fb190+cK2Xz/dvi9Hv8eCYJYvIGUTN2/KLq1pT6CjEc=
github.com/tomasen/realip v0.0.0-20180522021738-f0c99a92ddce/go.mod h1:o8v6yHRoik09Xen7gje4m9ERNah1d1PPsVq1VEx9vE4=
golang.org/x/crypto v0.53.0 h1:QZ4Muo8THX6CizN2vPPd5fBGHyogrdK9fG4wLPFUsto=
Expand All @@ -28,3 +48,5 @@ golang.org/x/term v0.44.0 h1:0rLvDRCtNj0gZkyIXhCyOb2OAzEhLVqc4B+hrsBhrmc=
golang.org/x/term v0.44.0/go.mod h1:7ze4MdzUzLXpSAoFP1H0bOI9aXDqveSvatT5vKcFh2Y=
golang.org/x/text v0.38.0 h1:sXmwo9DwP3OK9EZ7PqAdaooSGozfl/3a6/xJcbzPRhE=
golang.org/x/text v0.38.0/go.mod h1:YXZt3QhHUKYT53r2lLKFIVi6Ao1jdzrTR/KQ09qyxF4=
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
23 changes: 19 additions & 4 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,15 @@ var help = `
server - runs chisel in server mode
client - runs chisel in client mode

Global Options (must precede the command):

--metrics, An optional "host:port" to serve Prometheus metrics on
at /metrics. When unset, no metrics are collected or served.

--metrics-namespace, An optional prefix applied to every metric name
(defaults to "chisel"). Must match [a-zA-Z_][a-zA-Z0-9_]*. Only used
when --metrics is set.

Read more:
https://github.com/jpillora/chisel

Expand All @@ -37,6 +46,8 @@ func main() {

version := flag.Bool("version", false, "")
v := flag.Bool("v", false, "")
metricsAddr := flag.String("metrics", "", "")
metricsNamespace := flag.String("metrics-namespace", "", "")
flag.Bool("help", false, "")
flag.Bool("h", false, "")
flag.Usage = func() {}
Expand All @@ -57,9 +68,9 @@ func main() {

switch subcmd {
case "server":
server(args)
server(args, *metricsAddr, *metricsNamespace)
case "client":
client(args)
client(args, *metricsAddr, *metricsNamespace)
default:
fmt.Print(help)
os.Exit(0)
Expand Down Expand Up @@ -176,11 +187,13 @@ var serverHelp = `
instead of the system roots. This is commonly used to implement mutual-TLS.
` + commonHelp

func server(args []string) {
func server(args []string, metricsAddr, metricsNamespace string) {

flags := flag.NewFlagSet("server", flag.ContinueOnError)

config := &chserver.Config{}
config.MetricsAddr = metricsAddr
config.MetricsNamespace = metricsNamespace
flags.StringVar(&config.KeySeed, "key", "", "")
flags.StringVar(&config.KeyFile, "keyfile", "", "")
flags.StringVar(&config.AuthFile, "authfile", "", "")
Expand Down Expand Up @@ -421,9 +434,11 @@ var clientHelp = `
enabled (mutual-TLS).
` + commonHelp

func client(args []string) {
func client(args []string, metricsAddr, metricsNamespace string) {
flags := flag.NewFlagSet("client", flag.ContinueOnError)
config := chclient.Config{Headers: http.Header{}}
config.MetricsAddr = metricsAddr
config.MetricsNamespace = metricsNamespace
flags.StringVar(&config.Fingerprint, "fingerprint", "", "")
flags.StringVar(&config.Auth, "auth", "", "")
flags.DurationVar(&config.KeepAlive, "keepalive", 25*time.Second, "")
Expand Down
52 changes: 43 additions & 9 deletions server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,22 +16,25 @@ import (
"github.com/jpillora/chisel/share/ccrypto"
"github.com/jpillora/chisel/share/cio"
"github.com/jpillora/chisel/share/cnet"
"github.com/jpillora/chisel/share/metrics"
"github.com/jpillora/chisel/share/settings"
"github.com/jpillora/requestlog"
"golang.org/x/crypto/ssh"
)

// Config is the configuration for the chisel service
type Config struct {
KeySeed string
KeyFile string
AuthFile string
Auth string
Proxy string
Socks5 bool
Reverse bool
KeepAlive time.Duration
TLS TLSConfig
KeySeed string
KeyFile string
AuthFile string
Auth string
Proxy string
Socks5 bool
Reverse bool
KeepAlive time.Duration
TLS TLSConfig
MetricsAddr string
MetricsNamespace string
}

// Server respresent a chisel service
Expand All @@ -45,6 +48,7 @@ type Server struct {
sessions *settings.Users
sshConfig *ssh.ServerConfig
users *settings.UserIndex
metrics *metrics.Metrics
}

var upgrader = websocket.Upgrader{
Expand Down Expand Up @@ -140,6 +144,18 @@ func NewServer(c *Config) (*Server, error) {
if c.Reverse {
server.Infof("Reverse tunnelling enabled")
}
//initialize metrics if enabled
if c.MetricsAddr != "" {
m, err := metrics.New(c.MetricsNamespace)
if err != nil {
return nil, err
}
server.metrics = m
if err := server.metrics.Start(c.MetricsAddr); err != nil {
return nil, err
}
server.Infof("Metrics server started on %s", c.MetricsAddr)
}
return server, nil
}

Expand Down Expand Up @@ -199,18 +215,36 @@ func (s *Server) GetFingerprint() string {
func (s *Server) authUser(c ssh.ConnMetadata, password []byte) (*ssh.Permissions, error) {
// check if user authentication is enabled and if not, allow all
if s.users.Len() == 0 {
if s.metrics != nil {
s.metrics.ServerAuthAttempts.WithLabelValues("success").Inc()
if s.Debug {
s.Debugf("[metrics] server_auth_attempts_total{outcome=\"success\"} incremented")
}
}
return nil, nil
}
// check the user exists and has matching password
n := c.User()
user, found := s.users.Get(n)
if !found || user.Pass != string(password) {
s.Debugf("Login failed for user: %s", n)
if s.metrics != nil {
s.metrics.ServerAuthAttempts.WithLabelValues("failure").Inc()
if s.Debug {
s.Debugf("[metrics] server_auth_attempts_total{outcome=\"failure\"} incremented")
}
}
return nil, errors.New("Invalid authentication for username: %s")
}
// insert the user session map
// TODO this should probably have a lock on it given the map isn't thread-safe
s.sessions.Set(string(c.SessionID()), user)
if s.metrics != nil {
s.metrics.ServerAuthAttempts.WithLabelValues("success").Inc()
if s.Debug {
s.Debugf("[metrics] server_auth_attempts_total{outcome=\"success\"} incremented")
}
}
return nil, nil
}

Expand Down
Loading