Skip to content

Commit

Permalink
agent,api,gateway,pkg,ssh: add public url to access HTTP application …
Browse files Browse the repository at this point in the history
…on device
  • Loading branch information
henrybarreto committed Mar 9, 2023
1 parent b363870 commit 570a2c7
Show file tree
Hide file tree
Showing 16 changed files with 472 additions and 58 deletions.
60 changes: 57 additions & 3 deletions agent/main.go
Original file line number Diff line number Diff line change
@@ -1,22 +1,23 @@
package main

import (
"errors"
"fmt"
"io"
"net"
"net/http"
"os"
"runtime"
"strings"
"time"

"github.com/shellhub-io/shellhub/pkg/loglevel"

"github.com/Masterminds/semver"
"github.com/gorilla/mux"
"github.com/kelseyhightower/envconfig"
"github.com/shellhub-io/shellhub/agent/pkg/tunnel"
"github.com/shellhub-io/shellhub/agent/selfupdater"
"github.com/shellhub-io/shellhub/agent/server"
"github.com/shellhub-io/shellhub/pkg/loglevel"
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
)
Expand Down Expand Up @@ -65,7 +66,7 @@ type ConfigOptions struct {
}

// NewAgentServer creates a new agent server instance.
func NewAgentServer() *Agent {
func NewAgentServer() *Agent { // nolint:gocyclo
opts := ConfigOptions{}

// Process unprefixed env vars for backward compatibility
Expand Down Expand Up @@ -170,6 +171,59 @@ func NewAgentServer() *Agent {

conn.Close()
}
tun.HTTPHandler = func(w http.ResponseWriter, r *http.Request) {
replyError := func(err error, msg string, code int) {
log.WithError(err).WithFields(log.Fields{
"remote": r.RemoteAddr,
"namespace": r.Header.Get("X-Namespace"),
"path": r.Header.Get("X-Path"),
"version": AgentVersion,
}).Error(msg)

http.Error(w, msg, code)
}

in, err := net.Dial("tcp", ":8080")
if err != nil {
replyError(err, "failed to connect to HTTP the server on device", http.StatusInternalServerError)

return
}

defer in.Close()

url, err := r.URL.Parse(r.Header.Get("X-Path"))
if err != nil {
replyError(err, "failed to parse URL", http.StatusInternalServerError)

return
}

r.URL.Scheme = "http"
r.URL = url

if err := r.Write(in); err != nil {
replyError(err, "failed to write request to the server on device", http.StatusInternalServerError)

return
}

ctr := http.NewResponseController(w)
out, _, err := ctr.Hijack()
if err != nil {
replyError(err, "failed to hijack connection", http.StatusInternalServerError)

return
}

defer out.Close() // nolint:errcheck

if _, err := io.Copy(out, in); errors.Is(err, io.ErrUnexpectedEOF) {
replyError(err, "failed to copy response from device service to client", http.StatusInternalServerError)

return
}
}
tun.CloseHandler = func(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
serv.CloseSession(vars["id"])
Expand Down
7 changes: 7 additions & 0 deletions agent/pkg/tunnel/tunnel.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
type Tunnel struct {
router *mux.Router
srv *http.Server
HTTPHandler func(w http.ResponseWriter, r *http.Request)
ConnHandler func(w http.ResponseWriter, r *http.Request)
CloseHandler func(w http.ResponseWriter, r *http.Request)
}
Expand All @@ -27,13 +28,19 @@ func NewTunnel() *Tunnel {
return context.WithValue(ctx, "http-conn", c) //nolint:revive
},
},
HTTPHandler: func(w http.ResponseWriter, r *http.Request) {
panic("HTTPHandler can not be nil")
},
ConnHandler: func(w http.ResponseWriter, r *http.Request) {
panic("connHandler can not be nil")
},
CloseHandler: func(w http.ResponseWriter, r *http.Request) {
panic("closeHandler can not be nil")
},
}
t.router.HandleFunc("/ssh/http", func(w http.ResponseWriter, r *http.Request) {
t.HTTPHandler(w, r)
})
t.router.HandleFunc("/ssh/{id}", func(w http.ResponseWriter, r *http.Request) {
t.ConnHandler(w, r)
})
Expand Down
3 changes: 2 additions & 1 deletion api/pkg/guard/actions.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ type AllActions struct {
}

type DeviceActions struct {
Accept, Reject, Remove, Connect, Rename, CreateTag, UpdateTag, RemoveTag, RenameTag, DeleteTag int
Accept, Reject, Update, Remove, Connect, Rename, CreateTag, UpdateTag, RemoveTag, RenameTag, DeleteTag int
}

type SessionActions struct {
Expand Down Expand Up @@ -42,6 +42,7 @@ var Actions = AllActions{
Device: DeviceActions{
Accept: DeviceAccept,
Reject: DeviceReject,
Update: DeviceUpdate,
Remove: DeviceRemove,
Connect: DeviceConnect,
Rename: DeviceRename,
Expand Down
3 changes: 3 additions & 0 deletions api/pkg/guard/guard_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -331,6 +331,7 @@ func TestCheckPermission(t *testing.T) {
Actions.Device.Reject,
Actions.Device.Connect,
Actions.Device.Rename,
Actions.Device.Update,

Actions.Device.CreateTag,
Actions.Device.UpdateTag,
Expand All @@ -353,6 +354,7 @@ func TestCheckPermission(t *testing.T) {
Actions.Device.Remove,
Actions.Device.Connect,
Actions.Device.Rename,
Actions.Device.Update,

Actions.Device.CreateTag,
Actions.Device.UpdateTag,
Expand Down Expand Up @@ -392,6 +394,7 @@ func TestCheckPermission(t *testing.T) {
Actions.Device.Remove,
Actions.Device.Connect,
Actions.Device.Rename,
Actions.Device.Update,

Actions.Device.CreateTag,
Actions.Device.UpdateTag,
Expand Down
8 changes: 8 additions & 0 deletions api/pkg/guard/permissions.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ type Permissions []int
const (
DeviceAccept = iota + 1
DeviceReject
DeviceUpdate
DeviceRemove
DeviceConnect
DeviceRename
Expand Down Expand Up @@ -66,6 +67,7 @@ var operatorPermissions = Permissions{
DeviceConnect,
DeviceRename,
DeviceDetails,
DeviceUpdate,

DeviceCreateTag,
DeviceUpdateTag,
Expand All @@ -83,13 +85,16 @@ var adminPermissions = Permissions{
DeviceConnect,
DeviceRename,
DeviceDetails,
DeviceUpdate,

DeviceCreateTag,
DeviceUpdateTag,
DeviceRemoveTag,
DeviceRenameTag,
DeviceDeleteTag,

DeviceUpdate,

SessionPlay,
SessionClose,
SessionRemove,
Expand Down Expand Up @@ -123,13 +128,16 @@ var ownerPermissions = Permissions{
DeviceConnect,
DeviceRename,
DeviceDetails,
DeviceUpdate,

DeviceCreateTag,
DeviceUpdateTag,
DeviceRemoveTag,
DeviceRenameTag,
DeviceDeleteTag,

DeviceUpdate,

SessionPlay,
SessionClose,
SessionRemove,
Expand Down
25 changes: 25 additions & 0 deletions api/routes/device.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ const (
CreateTagURL = "/devices/:uid/tags" // Add a tag to a device.
UpdateTagURL = "/devices/:uid/tags" // Update device's tags with a new set.
RemoveTagURL = "/devices/:uid/tags/:tag" // Delete a tag from a device.
UpdateDevice = "/devices/:uid"
)

const (
Expand Down Expand Up @@ -281,3 +282,27 @@ func (h *Handler) UpdateDeviceTag(c gateway.Context) error {

return c.NoContent(http.StatusOK)
}

func (h *Handler) UpdateDevice(c gateway.Context) error {
var req request.DeviceUpdate
if err := c.Bind(&req); err != nil {
return err
}

if err := c.Validate(&req); err != nil {
return err
}

var tenant string
if c.Tenant() != nil {
tenant = c.Tenant().ID
}

if err := guard.EvaluatePermission(c.Role(), guard.Actions.Device.Update, func() error {
return h.service.UpdateDevice(c.Ctx(), tenant, models.UID(req.UID), req.Name, req.PublicURL)
}); err != nil {
return err
}

return c.NoContent(http.StatusOK)
}
1 change: 1 addition & 0 deletions api/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,7 @@ func startServer(cfg *config) error {
publicAPI.GET(routes.GetDeviceURL,
apiMiddleware.Authorize(gateway.Handler(handler.GetDevice)))
publicAPI.DELETE(routes.DeleteDeviceURL, gateway.Handler(handler.DeleteDevice))
publicAPI.PUT(routes.UpdateDevice, gateway.Handler(handler.UpdateDevice))
publicAPI.PATCH(routes.RenameDeviceURL, gateway.Handler(handler.RenameDevice))
internalAPI.POST(routes.OfflineDeviceURL, gateway.Handler(handler.OfflineDevice))
internalAPI.POST(routes.HeartbeatDeviceURL, gateway.Handler(handler.HeartbeatDevice))
Expand Down
28 changes: 28 additions & 0 deletions api/services/device.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package services

import (
"context"
"fmt"
"net"
"strings"

Expand All @@ -25,6 +26,7 @@ type DeviceService interface {
UpdatePendingStatus(ctx context.Context, uid models.UID, status, tenant string) error
SetDevicePosition(ctx context.Context, uid models.UID, ip string) error
DeviceHeartbeat(ctx context.Context, uid models.UID) error
UpdateDevice(ctx context.Context, tenant string, uid models.UID, name *string, publicURL *bool) error
}

func (s *service) ListDevices(ctx context.Context, pagination paginator.Query, filter []models.Filter, status string, sort string, order string) ([]models.Device, int, error) {
Expand Down Expand Up @@ -225,3 +227,29 @@ func (s *service) DeviceHeartbeat(ctx context.Context, uid models.UID) error {

return nil
}

func (s *service) UpdateDevice(ctx context.Context, tenant string, uid models.UID, name *string, publicURL *bool) error {
device, err := s.store.DeviceGetByUID(ctx, uid, tenant)
if err != nil {
return NewErrDeviceNotFound(uid, err)
}

if name != nil {
*name = strings.ToLower(*name)

if device.Name == *name {
return nil
}

otherDevice, err := s.store.DeviceGetByName(ctx, *name, tenant)
if err != nil && err != store.ErrNoDocuments {
return NewErrDeviceNotFound(models.UID(device.UID), fmt.Errorf("failed to get device by name: %w", err))
}

if otherDevice != nil {
return NewErrDeviceDuplicated(otherDevice.Name, err)
}
}

return s.store.DeviceUpdate(ctx, uid, name, publicURL)
}
14 changes: 14 additions & 0 deletions api/services/mocks/services.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions api/store/device_store.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
type DeviceStore interface {
DeviceList(ctx context.Context, pagination paginator.Query, filters []models.Filter, status string, sort string, order string) ([]models.Device, int, error)
DeviceGet(ctx context.Context, uid models.UID) (*models.Device, error)
DeviceUpdate(ctx context.Context, uid models.UID, name *string, publicURL *bool) error
DeviceDelete(ctx context.Context, uid models.UID) error
DeviceCreate(ctx context.Context, d models.Device, hostname string) error
DeviceRename(ctx context.Context, uid models.UID, hostname string) error
Expand Down
Loading

0 comments on commit 570a2c7

Please sign in to comment.