Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

agent,api,gateway,pkg,ssh: add public url to access HTTP application on device #2534

Merged
merged 1 commit into from
Mar 9, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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