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 8, 2023
1 parent 4856cd1 commit 2c4b397
Show file tree
Hide file tree
Showing 15 changed files with 599 additions and 115 deletions.
67 changes: 64 additions & 3 deletions agent/main.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package main

import (
"bytes"
"fmt"
"net"
"net/http"
Expand All @@ -9,14 +10,13 @@ import (
"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 +65,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 +170,67 @@ func NewAgentServer() *Agent {

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

http.Error(w, msg, code)
}

in, err := net.Dial("tcp", ":8080")
if err != nil {
replyError("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("failed to parse URL", http.StatusInternalServerError)

return
}

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

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

return
}

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

return
}

defer out.Close()

buf := bytes.NewBuffer(nil)
if _, err := buf.ReadFrom(in); err != nil {
replyError("failed to read response from the server on device", http.StatusInternalServerError)

return
}

if _, err := buf.WriteTo(out); err != nil {
replyError("failed to write response from the server on device to the server", 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
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
42 changes: 31 additions & 11 deletions api/routes/device.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,18 @@ import (
)

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.
)

const (
Expand Down Expand Up @@ -281,3 +282,22 @@ 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)
}
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.PATCH(routes.UpdateDevicePublicURL, gateway.Handler(handler.UpdateDevicePublicURL))

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
9 changes: 9 additions & 0 deletions api/services/device.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,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
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 +226,11 @@ func (s *service) DeviceHeartbeat(ctx context.Context, uid models.UID) error {

return nil
}

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

return nil
}
Loading

0 comments on commit 2c4b397

Please sign in to comment.