// Copyright (c) 2022 Gitpod GmbH. All rights reserved. // Licensed under the GNU Affero General Public License (AGPL). // See License.AGPL.txt in the project root for license information. package sshtunnel import ( "context" "encoding/json" "fmt" "io" "net" "os" "github.com/caddyserver/caddy/v2" "github.com/caddyserver/caddy/v2/caddyconfig" "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile" "github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile" "go.uber.org/zap" ) func init() { caddy.RegisterModule(SSHTunnel{}) caddyconfig.RegisterAdapter("inject-ssh-tunnel", InjectSSHTunnelAdaper{}) } func (a InjectSSHTunnelAdaper) Adapt(body []byte, options map[string]interface{}) (result []byte, warnings []caddyconfig.Warning, err error) { c := caddyfile.Adapter{ServerType: httpcaddyfile.ServerType{}} result, warnings, err = c.Adapt(body, options) if err != nil { return } var r map[string]interface{} err = json.Unmarshal(result, &r) if err != nil { return } if apps, ok := r["apps"].(map[string]interface{}); ok { apps["ssh-tunnel"] = make(map[string]interface{}) result, err = json.Marshal(r) } return } type InjectSSHTunnelAdaper struct { } type SSHTunnel struct { listener net.Listener logger *zap.Logger } func (SSHTunnel) CaddyModule() caddy.ModuleInfo { return caddy.ModuleInfo{ ID: "ssh-tunnel", New: func() caddy.Module { return new(SSHTunnel) }, } } func (s *SSHTunnel) Provision(ctx caddy.Context) error { s.logger = ctx.Logger(s) return nil } func (s *SSHTunnel) Start() error { ln, err := caddy.Listen("tcp", "0.0.0.0:22") if err != nil { return err } s.listener = ln go s.serve() s.logger.Info("SSH Tunnel is running") return nil } func (s SSHTunnel) Stop() error { err := s.listener.Close() if err != nil { return err } return nil } func (s *SSHTunnel) serve() { for { conn, err := s.listener.Accept() if nerr, ok := err.(net.Error); ok && nerr.Temporary() { // ignore temporary network error continue } if err != nil { return } go s.handle(conn) } } func (s *SSHTunnel) handle(conn net.Conn) { defer conn.Close() addr := fmt.Sprintf("ws-proxy.%s.%s:22", os.Getenv("KUBE_NAMESPACE"), os.Getenv("KUBE_DOMAIN")) tconn, err := net.Dial("tcp", addr) if err != nil { fmt.Printf("dial %s failed with:%v\n", addr, err) return } defer tconn.Close() ctx, cancel := context.WithCancel(context.Background()) go func() { io.Copy(conn, tconn) cancel() }() go func() { io.Copy(tconn, conn) cancel() }() <-ctx.Done() } var _ caddy.App = (*SSHTunnel)(nil)