package http import ( "errors" "net/http" "sync" "time" "github.com/gorilla/websocket" "github.com/labstack/echo/v5" "github.com/shellhub-io/shellhub/pkg/clock" "github.com/shellhub-io/shellhub/pkg/revdial" routesmiddleware "github.com/shellhub-io/shellhub/server/api/routes/middleware" "github.com/shellhub-io/shellhub/server/api/services" "github.com/shellhub-io/shellhub/server/ssh/pkg/dialer" "github.com/shellhub-io/shellhub/server/ssh/session" log "github.com/sirupsen/logrus" ) // Message is the JSON body returned to an agent whose tunnel request was refused. type Message struct { Message string `json:"message"` } // NewMessageFromError renders err as the body of a refusal. func NewMessageFromError(err error) Message { return Message{ Message: err.Error(), } } // Config controls optional features for the SSH HTTP sidecar server. type Config struct { // RequireAcceptedTunnel refuses the reverse tunnel for a device that is not accepted (pending or // rejected). Off by default; opt-in per instance. RequireAcceptedTunnel bool // Clock is what the tunnels the server accepts read the time from. Nil means the wall clock. Clock clock.Clock // TunnelPingInterval is how often a v1 tunnel is pinged, each answered ping a beat for its // device. A non-positive value leaves the adapter's default of 30 seconds. TunnelPingInterval time.Duration } func (c *Config) clock() clock.Clock { if c == nil || c.Clock == nil { return clock.New() } return c.Clock } // ErrDeviceTunnelDial is returned when the device holds a tunnel but the connection over it // could not be opened, which is distinct from the device being offline. var ErrDeviceTunnelDial = errors.New("failed to connect to device") var upgrader = websocket.Upgrader{ ReadBufferSize: 1024, WriteBufferSize: 1024, WriteBufferPool: new(sync.Pool), Subprotocols: []string{"binary"}, CheckOrigin: func(_ *http.Request) bool { return true }, } // Register adds the SSH routes to the API's router. The binder, validator and // error handler are the API's; they are a superset of what these handlers need. // // authn is the API's authenticator, used to declare which of these routes are // reachable without a credential and which accept a device token. It must not be // nil. func Register(router *echo.Echo, authn *routesmiddleware.Authenticator, d *dialer.Dialer, service services.Service, sessions *session.Registry, cfg *Config) { handlers := &Handlers{ Dialer: d, Tunnels: d.Manager, Service: service, Sessions: sessions, Config: cfg, } router.GET(HandleConnectionV1Path, handlers.HandleConnectionV1) authn.AllowDevice(http.MethodGet, HandleConnectionV1Path) router.GET(HandleConnectionV2Path, handlers.HandleConnectionV2) authn.AllowDevice(http.MethodGet, HandleConnectionV2Path) router.GET(HandleRevdialPath, echo.WrapHandler(revdial.ConnHandler(upgrader))) authn.AllowAnonymous(http.MethodGet, HandleRevdialPath) router.POST(HandleSSHClosePath, handlers.HandleSSHClose) if err := applyTunnelExtensions(router, authn, d); err != nil { log.WithError(err).Error("failed to register the tunnel extensions") } }