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 2, 2023
1 parent 1b14c39 commit 338f148
Show file tree
Hide file tree
Showing 17 changed files with 252 additions and 30 deletions.
50 changes: 50 additions & 0 deletions agent/main.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
package main

import (
"bufio"
"fmt"
"net"
"net/http"
"net/url"
"os"
"runtime"
"strings"
Expand Down Expand Up @@ -170,6 +172,54 @@ func NewAgentServer() *Agent {

conn.Close()
}
tun.HTTPHandler = func(w http.ResponseWriter, r *http.Request) {
ok, err := serv.CheckDevicePublic()
if err != nil {
http.Error(w, "failed to check if the device is allowing HTTP connections", http.StatusInternalServerError)

return
}

if !ok {
http.Error(w, "the device is not allowing HTTP connections", http.StatusForbidden)

return
}

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

return
}

req := r.Clone(r.Context())
req.URL.Scheme = "http"
req.URL.Path = r.Header.Get("X-Path")
req.URL.RawQuery = url.QueryEscape(r.Header.Get("X-Path"))
// TODO: check if the rest of url parameters are correct!

if err := req.Write(conn); err != nil {
http.Error(w, "failed to write HTTP request to the server on device", http.StatusInternalServerError)

return
}

res, err := http.ReadResponse(bufio.NewReader(conn), req)
if err != nil {
http.Error(w, "failed to read HTTP response from the server on device", http.StatusInternalServerError)

return
}

if err := res.Write(w); err != nil {
http.Error(w, "failed to write HTTP response to the client", http.StatusInternalServerError)

return
}

conn.Close()
}
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
11 changes: 11 additions & 0 deletions agent/server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,17 @@ type Server struct {
singleUserPassword string
}

func (s *Server) CheckDevicePublic() (bool, error) {
enabled, err := s.api.CheckDevicePublicURL(s.authData.UID, s.authData.Token)
if err != nil {
log.WithError(err).Error("Failed to check device public")

return false, err
}

return enabled, nil
}

// NewServer creates a new server SSH agent server.
func NewServer(api client.Client, authData *models.DeviceAuthResponse, privateKey string, keepAliveInterval int, singleUserPassword string) *Server {
server := &Server{
Expand Down
23 changes: 12 additions & 11 deletions 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, Remove, Connect, Rename, CreateTag, UpdateTag, RemoveTag, RenameTag, DeleteTag, UpdatePublicURL int
}

type SessionActions struct {
Expand All @@ -40,16 +40,17 @@ type BillingActions struct {
// You should use it to get the code's action.
var Actions = AllActions{
Device: DeviceActions{
Accept: DeviceAccept,
Reject: DeviceReject,
Remove: DeviceRemove,
Connect: DeviceConnect,
Rename: DeviceRename,
CreateTag: DeviceCreateTag,
UpdateTag: DeviceUpdateTag,
RemoveTag: DeviceRemoveTag,
RenameTag: DeviceRenameTag,
DeleteTag: DeviceDeleteTag,
Accept: DeviceAccept,
Reject: DeviceReject,
Remove: DeviceRemove,
Connect: DeviceConnect,
Rename: DeviceRename,
CreateTag: DeviceCreateTag,
UpdateTag: DeviceUpdateTag,
RemoveTag: DeviceRemoveTag,
RenameTag: DeviceRenameTag,
DeleteTag: DeviceDeleteTag,
UpdatePublicURL: DeviceUpdatePublicURL,
},
Session: SessionActions{
Play: SessionPlay,
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 @@ -16,6 +16,8 @@ const (
DeviceRenameTag
DeviceDeleteTag

DeviceUpdatePublicURL

SessionPlay
SessionClose
SessionRemove
Expand Down Expand Up @@ -73,6 +75,8 @@ var operatorPermissions = Permissions{
DeviceRenameTag,
DeviceDeleteTag,

DeviceUpdatePublicURL,

SessionDetails,
}

Expand All @@ -90,6 +94,8 @@ var adminPermissions = Permissions{
DeviceRenameTag,
DeviceDeleteTag,

DeviceUpdatePublicURL,

SessionPlay,
SessionClose,
SessionRemove,
Expand Down Expand Up @@ -130,6 +136,8 @@ var ownerPermissions = Permissions{
DeviceRenameTag,
DeviceDeleteTag,

DeviceUpdatePublicURL,

SessionPlay,
SessionClose,
SessionRemove,
Expand Down
64 changes: 53 additions & 11 deletions api/routes/device.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,21 +10,24 @@ import (
"github.com/shellhub-io/shellhub/api/pkg/guard"
"github.com/shellhub-io/shellhub/pkg/api/paginator"
"github.com/shellhub-io/shellhub/pkg/api/request"
"github.com/shellhub-io/shellhub/pkg/api/response"
"github.com/shellhub-io/shellhub/pkg/models"
)

const (
GetDeviceListURL = "/devices"
GetDeviceURL = "/devices/:uid"
DeleteDeviceURL = "/devices/:uid"
RenameDeviceURL = "/devices/:uid"
OfflineDeviceURL = "/devices/:uid/offline"
HeartbeatDeviceURL = "/devices/:uid/heartbeat"
LookupDeviceURL = "/lookup"
UpdateStatusURL = "/devices/:uid/:status"
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.
GetDeviceListURL = "/devices"
GetDeviceURL = "/devices/:uid"
DeleteDeviceURL = "/devices/:uid"
RenameDeviceURL = "/devices/:uid"
OfflineDeviceURL = "/devices/:uid/offline"
HeartbeatDeviceURL = "/devices/:uid/heartbeat"
LookupDeviceURL = "/lookup"
UpdateStatusURL = "/devices/:uid/:status"
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.
UpdateDevicePublicURL = "/devices/:uid/public" // Update permission for device public url access.
GetDevicePublicURL = "/devices/:uid/public" // Get if device allows public url access.
)

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

return c.NoContent(http.StatusOK)
}

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

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

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

return c.NoContent(http.StatusOK)
}

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

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

status, err := h.service.GetDevicePublicURL(c.Ctx(), models.UID(req.UID))
if err != nil {
return err
}

return c.JSON(http.StatusOK, response.DeviceGetPublicURL{
PublicURL: status,
})
}
3 changes: 3 additions & 0 deletions api/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,9 @@ func startServer(cfg *config) error {
publicAPI.DELETE(routes.RemoveTagURL, gateway.Handler(handler.RemoveDeviceTag))
publicAPI.PUT(routes.UpdateTagURL, gateway.Handler(handler.UpdateDeviceTag))

publicAPI.PATCH(routes.UpdateDevicePublicURL, gateway.Handler(handler.UpdateDevicePublicURL))
publicAPI.GET(routes.GetDevicePublicURL, gateway.Handler(handler.GetDevicePublicURL))

publicAPI.GET(routes.GetTagsURL, gateway.Handler(handler.GetTags))
publicAPI.PUT(routes.RenameTagURL, gateway.Handler(handler.RenameTag))
publicAPI.DELETE(routes.DeleteTagsURL, gateway.Handler(handler.DeleteTag))
Expand Down
19 changes: 19 additions & 0 deletions api/services/device.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ 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
GetDevicePublicURL(ctx context.Context, uid models.UID) (bool, error)
UpdateDevicePublicURL(ctx context.Context, uid models.UID, 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,20 @@ func (s *service) DeviceHeartbeat(ctx context.Context, uid models.UID) error {

return nil
}

func (s *service) GetDevicePublicURL(ctx context.Context, uid models.UID) (bool, error) {
device, err := s.store.DeviceGet(ctx, uid)
if err != nil {
return false, NewErrDeviceNotFound(uid, err)
}

return device.PublicURL, nil
}

func (s *service) UpdateDevicePublicURL(ctx context.Context, uid models.UID, publicURL bool) error {
if err := s.store.DeviceSetPublicURL(ctx, uid, publicURL); err != nil {
return NewErrDeviceNotFound(uid, err)
}

return nil
}
1 change: 1 addition & 0 deletions api/store/device_store.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,5 @@ type DeviceStore interface {
DeviceSetPosition(ctx context.Context, uid models.UID, position models.DevicePosition) error
DeviceListByUsage(ctx context.Context, tenantID string) ([]models.UID, error)
DeviceChooser(ctx context.Context, tenantID string, chosen []string) error
DeviceSetPublicURL(ctx context.Context, uid models.UID, publicURL bool) error
}
6 changes: 6 additions & 0 deletions api/store/mongo/device_store.go
Original file line number Diff line number Diff line change
Expand Up @@ -443,3 +443,9 @@ func (s *Store) DeviceChooser(ctx context.Context, tenantID string, chosen []str

return nil
}

func (s *Store) DeviceSetPublicURL(ctx context.Context, uid models.UID, publicURL bool) error {
_, err := s.db.Collection("devices").UpdateOne(ctx, bson.M{"uid": uid}, bson.M{"$set": bson.M{"public_url": publicURL}})

return FromMongoError(err)
}
13 changes: 13 additions & 0 deletions gateway/shellhub.conf
Original file line number Diff line number Diff line change
Expand Up @@ -391,3 +391,16 @@ server {
}
}
}

server {
listen 80;
server_name ~^(?<namespace>.+)\.(?<device>.+)\..+$;

location / { #~ ^/(.*)$ {
rewrite ^/(.*)$ /ssh/http break;
proxy_set_header X-Namespace $namespace;
proxy_set_header X-Device $device;
proxy_set_header X-Path /$1$is_args$args;
proxy_pass http://ssh:8080;
}
}
12 changes: 6 additions & 6 deletions pkg/api/client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,12 @@ type client struct {
logger *logrus.Logger
}

func buildURL(c *client, uri string) string {
u, _ := url.Parse(fmt.Sprintf("%s://%s:%d%s", c.scheme, c.host, c.port, uri))

return u.String()
}

func (c *client) ListDevices() ([]models.Device, error) {
list := []models.Device{}
_, err := c.http.R().
Expand Down Expand Up @@ -97,9 +103,3 @@ func (c *client) GetDevice(uid string) (*models.Device, error) {
return nil, ErrUnknown
}
}

func buildURL(c *client, uri string) string {
u, _ := url.Parse(fmt.Sprintf("%s://%s:%d%s", c.scheme, c.host, c.port, uri))

return u.String()
}
Loading

0 comments on commit 338f148

Please sign in to comment.