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 41a5abe
Show file tree
Hide file tree
Showing 17 changed files with 245 additions and 23 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
17 changes: 14 additions & 3 deletions agent/server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ func (c *sshConn) Close() error {

type Server struct {
sshd *gliderssh.Server
api client.Client
Api client.Client
authData *models.DeviceAuthResponse
cmds map[string]*exec.Cmd
Sessions map[string]net.Conn
Expand All @@ -57,10 +57,21 @@ 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{
api: api,
Api: api,
authData: authData,
cmds: make(map[string]*exec.Cmd),
Sessions: make(map[string]net.Conn),
Expand Down Expand Up @@ -369,7 +380,7 @@ func (s *Server) publicKeyHandler(ctx gliderssh.Context, key gliderssh.PublicKey

sigHash := sha256.Sum256(sigBytes)

res, err := s.api.AuthPublicKey(&models.PublicKeyAuthRequest{
res, err := s.Api.AuthPublicKey(&models.PublicKeyAuthRequest{
Fingerprint: gossh.FingerprintLegacyMD5(key),
Data: string(sigBytes),
}, s.authData.Token)
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, Remove, Connect, Rename, CreateTag, UpdateTag, RemoveTag, RenameTag, DeleteTag, UpdatePublicURL int
}

type SessionActions struct {
Expand Down Expand Up @@ -50,6 +50,7 @@ var Actions = AllActions{
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
63 changes: 52 additions & 11 deletions api/routes/device.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,21 +10,23 @@ 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.
CheckDevicePublicURL = "/devices/:uid/public" // Check if a device allows public access.
)

const (
Expand Down Expand Up @@ -281,3 +283,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,
})
}
2 changes: 2 additions & 0 deletions api/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,8 @@ func startServer(cfg *config) error {
publicAPI.DELETE(routes.RemoveTagURL, gateway.Handler(handler.RemoveDeviceTag))
publicAPI.PUT(routes.UpdateTagURL, gateway.Handler(handler.UpdateDeviceTag))

publicAPI.GET(routes.CheckDevicePublicURL, 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
20 changes: 20 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,21 @@ 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
}
7 changes: 7 additions & 0 deletions api/store/mongo/device_store.go
Original file line number Diff line number Diff line change
Expand Up @@ -443,3 +443,10 @@ 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 41a5abe

Please sign in to comment.