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

feature(agent): add VPN capability to agent #4021

Draft
wants to merge 3 commits into
base: master
Choose a base branch
from
Draft
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
3 changes: 3 additions & 0 deletions agent/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,10 @@ require (
github.com/pkg/errors v0.9.1 // indirect
github.com/pkg/sftp v1.13.5 // indirect
github.com/sethvargo/go-envconfig v0.9.0 // indirect
github.com/songgao/water v0.0.0-20200317203138-2b4b6d7c09d8 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/vishvananda/netlink v1.2.1-beta.2 // indirect
github.com/vishvananda/netns v0.0.0-20210104183010-2eb08e3e575f // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.51.0 // indirect
go.opentelemetry.io/otel v1.26.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.26.0 // indirect
Expand Down
9 changes: 9 additions & 0 deletions agent/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,8 @@ github.com/shellhub-io/ssh v0.0.0-20230224143412-edd48dfd6eea h1:7tEI9nukSYZViCj
github.com/shellhub-io/ssh v0.0.0-20230224143412-edd48dfd6eea/go.mod h1:8XB4KraRrX39qHhT6yxPsHedjA08I/uBVwj4xC+/+z4=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/songgao/water v0.0.0-20200317203138-2b4b6d7c09d8 h1:TG/diQgUe0pntT/2D9tmUCz4VNwm9MfrtPr0SU2qSX8=
github.com/songgao/water v0.0.0-20200317203138-2b4b6d7c09d8/go.mod h1:P5HUIBuIWKbyjl083/loAegFkfbFNx5i2qEP4CNbm7E=
github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM=
github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
Expand All @@ -118,6 +120,11 @@ github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyC
github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo=
github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
github.com/vishvananda/netlink v1.2.1-beta.2 h1:Llsql0lnQEbHj0I1OuKyp8otXp0r3q0mPkuhwHfStVs=
github.com/vishvananda/netlink v1.2.1-beta.2/go.mod h1:twkDnbuQxJYemMlGd4JFIcuhgX83tXhKS2B/PRMpOho=
github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0=
github.com/vishvananda/netns v0.0.0-20210104183010-2eb08e3e575f h1:p4VB7kIXpOQvVn1ZaTIVp+3vuYAXFe3OJEvjbUYJLaA=
github.com/vishvananda/netns v0.0.0-20210104183010-2eb08e3e575f/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.51.0 h1:Xs2Ncz0gNihqu9iosIZ5SkBbWo5T8JhhLJFMQL1qmLI=
Expand Down Expand Up @@ -159,6 +166,8 @@ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
Expand Down
53 changes: 39 additions & 14 deletions agent/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,10 @@ import (

"github.com/Masterminds/semver"
"github.com/shellhub-io/shellhub/pkg/agent"
"github.com/shellhub-io/shellhub/pkg/agent/connector"
"github.com/shellhub-io/shellhub/pkg/agent/pkg/selfupdater"
"github.com/shellhub-io/shellhub/pkg/agent/server/modes/host/command"
"github.com/shellhub-io/shellhub/pkg/agent/ssh"
"github.com/shellhub-io/shellhub/pkg/agent/ssh/connector"
"github.com/shellhub-io/shellhub/pkg/agent/ssh/modes/host/command"
"github.com/shellhub-io/shellhub/pkg/envs"
"github.com/shellhub-io/shellhub/pkg/loglevel"
log "github.com/sirupsen/logrus"
Expand Down Expand Up @@ -108,7 +109,7 @@ func main() {
"tenant_id": cfg.TenantID,
"server_address": cfg.ServerAddress,
"preferred_hostname": cfg.PreferredHostname,
}).Info("Listening for connections")
}).Info("Listening for SSH connections")

// Disable check update in development mode
if AgentVersion != "latest" {
Expand Down Expand Up @@ -162,23 +163,47 @@ func main() {
}()
}

if err := ag.Listen(ctx); err != nil {
log.WithError(err).WithFields(log.Fields{
"version": AgentVersion,
"mode": mode,
"tenant_id": cfg.TenantID,
"server_address": cfg.ServerAddress,
"preferred_hostname": cfg.PreferredHostname,
}).Fatal("Failed to listen for connections")
}
go func() {
if err := ag.ListenSSH(ctx); err != nil {
log.WithError(err).WithFields(log.Fields{
"version": AgentVersion,
"mode": mode,
"tenant_id": cfg.TenantID,
"server_address": cfg.ServerAddress,
"preferred_hostname": cfg.PreferredHostname,
}).Fatal("Failed to listen for SSH connections")
}
}()

go func() {
if !cfg.VPN {
log.Info("VPN is disable")

return
}

log.Debug("VPN enabled")

for {
log.Info("VPN connected")

if err := ag.ConnectVPN(ctx); err != nil {
log.WithError(err).Error("Connect to VPN lost. Retrying in 10 seconds.")
}

time.Sleep(10 * time.Second)
}
}()

<-ctx.Done()

log.WithFields(log.Fields{
"version": AgentVersion,
"mode": mode,
"tenant_id": cfg.TenantID,
"server_address": cfg.ServerAddress,
"preferred_hostname": cfg.PreferredHostname,
}).Info("Stopped listening for connections")
}).Info("Agent Stopped")
},
}

Expand Down Expand Up @@ -266,7 +291,7 @@ func main() {
Long: `Starts the SFTP server. This command is used internally by the agent and should not be used directly.
It is initialized by the agent when a new SFTP session is created.`,
Run: func(cmd *cobra.Command, args []string) {
agent.NewSFTPServer(command.SFTPServerMode(args[0]))
ssh.NewSFTPServer(command.SFTPServerMode(args[0]))
},
})

Expand Down
10 changes: 10 additions & 0 deletions api/services/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,8 @@ var (
ErrAPIKeyDuplicated = errors.New("APIKey duplicated", ErrLayer, ErrCodeDuplicated)
ErrAuthForbidden = errors.New("user is authenticated but cannot access this resource", ErrLayer, ErrCodeForbidden)
ErrRoleInvalid = errors.New("role is invalid", ErrLayer, ErrCodeForbidden)
ErrNamespaceIPInvalid = errors.New("ip is invalid", ErrLayer, ErrCodeForbidden)
ErrNamespaceIPNotPrivate = errors.New("ip is not a private address", ErrLayer, ErrCodeForbidden)
)

func NewErrRoleInvalid() error {
Expand Down Expand Up @@ -471,3 +473,11 @@ func NewErrDeviceMaxDevicesReached(count int) error {
func NewErrAuthForbidden() error {
return NewErrForbidden(ErrAuthForbidden, nil)
}

func NewErrNamespaceIPInvalid() error {
return NewErrInvalid(ErrNamespaceIPInvalid, nil, nil)
}

func NewErrNamespaceIPNotPrivate() error {
return NewErrInvalid(ErrNamespaceIPNotPrivate, nil, nil)
}
31 changes: 31 additions & 0 deletions api/services/namespace.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@ package services
import (
"context"
"errors"
"net"
"strings"

"github.com/shellhub-io/shellhub/api/store"
"github.com/shellhub-io/shellhub/api/store/mongo"
"github.com/shellhub-io/shellhub/pkg/api/authorizer"
"github.com/shellhub-io/shellhub/pkg/api/internalclient"
"github.com/shellhub-io/shellhub/pkg/api/requests"
"github.com/shellhub-io/shellhub/pkg/clock"
"github.com/shellhub-io/shellhub/pkg/envs"
Expand Down Expand Up @@ -204,6 +206,27 @@ func (s *service) EditNamespace(ctx context.Context, req *requests.NamespaceEdit
ConnectionAnnouncement: req.Settings.ConnectionAnnouncement,
}

if envs.IsEnterprise() {
changes.VPNEnable = req.VPN.Enable

if req.VPN.Address != nil {
address := *req.VPN.Address
ip := net.IPv4(address[0], address[1], address[2], address[3])

if ip.IsLoopback() || ip.IsUnspecified() {
return nil, NewErrNamespaceIPInvalid()
}

if !ip.IsPrivate() {
return nil, NewErrNamespaceIPNotPrivate()
}

changes.VPNAddress = &address
}

changes.VPNMask = req.VPN.Mask
}

if err := s.store.NamespaceEdit(ctx, req.Tenant, changes); err != nil {
switch {
case errors.Is(err, store.ErrNoDocuments):
Expand All @@ -213,6 +236,14 @@ func (s *service) EditNamespace(ctx context.Context, req *requests.NamespaceEdit
}
}

if envs.IsEnterprise() {
cli := s.client.(internalclient.Client)

if err := cli.VPNStopRouter(req.Tenant); err != nil {
return nil, err
}
}

return s.store.NamespaceGet(ctx, req.Tenant, true)
}

Expand Down
5 changes: 5 additions & 0 deletions api/services/namespace_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -937,6 +937,7 @@ func TestEditNamespace(t *testing.T) {
tenantID: "xxxxx",
namespaceName: "newname",
requiredMocks: func() {
envMock.On("Get", "SHELLHUB_ENTERPRISE").Return("false").Once()
mock.On("NamespaceEdit", ctx, "xxxxx", &models.NamespaceChanges{Name: "newname"}).
Return(store.ErrNoDocuments).
Once()
Expand All @@ -951,6 +952,7 @@ func TestEditNamespace(t *testing.T) {
tenantID: "xxxxx",
namespaceName: "newname",
requiredMocks: func() {
envMock.On("Get", "SHELLHUB_ENTERPRISE").Return("false").Once()
mock.On("NamespaceEdit", ctx, "xxxxx", &models.NamespaceChanges{Name: "newname"}).
Return(errors.New("error")).
Once()
Expand All @@ -965,6 +967,7 @@ func TestEditNamespace(t *testing.T) {
namespaceName: "newName",
tenantID: "xxxxx",
requiredMocks: func() {
envMock.On("Get", "SHELLHUB_ENTERPRISE").Return("false").Once()
mock.On("NamespaceEdit", ctx, "xxxxx", &models.NamespaceChanges{Name: "newname"}).
Return(nil).
Once()
Expand All @@ -991,6 +994,7 @@ func TestEditNamespace(t *testing.T) {
namespaceName: "newname",
tenantID: "xxxxx",
requiredMocks: func() {
envMock.On("Get", "SHELLHUB_ENTERPRISE").Return("false").Once()
mock.On("NamespaceEdit", ctx, "xxxxx", &models.NamespaceChanges{Name: "newname"}).
Return(nil).
Once()
Expand All @@ -1000,6 +1004,7 @@ func TestEditNamespace(t *testing.T) {
Name: "newname",
}

envMock.On("Get", "SHELLHUB_ENTERPRISE").Return("false").Once()
mock.On("NamespaceGet", ctx, "xxxxx", true).
Return(namespace, nil).
Once()
Expand Down
1 change: 1 addition & 0 deletions api/store/mongo/migrations/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ func GenerateMigrations() []migrate.Migration {
migration74,
migration75,
migration76,
migration77,
}
}

Expand Down
64 changes: 64 additions & 0 deletions api/store/mongo/migrations/migration_77.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package migrations

import (
"context"

"github.com/shellhub-io/shellhub/pkg/envs"
"github.com/sirupsen/logrus"
migrate "github.com/xakep666/mongo-migrate"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo"
)

var migration77 = migrate.Migration{
Version: 77,
Description: "Adding VPN settings to namespace",
Up: migrate.MigrationFunc(func(ctx context.Context, db *mongo.Database) error {
logrus.WithFields(logrus.Fields{
"component": "migration",
"version": 77,
"action": "Up",
}).Info("Applying migration")

if envs.IsEnterprise() {
update := bson.M{
"$set": bson.M{
"vpn": bson.M{
"enable": false,
"address": bson.A{10, 0, 0, 0},
"mask": 16,
},
},
}

_, err := db.
Collection("namespaces").
UpdateMany(ctx, bson.M{}, update)

return err
}

return nil
}),
Down: migrate.MigrationFunc(func(ctx context.Context, db *mongo.Database) error {
logrus.WithFields(logrus.Fields{
"component": "migration",
"version": 77,
"action": "Down",
}).Info("Reverting migration")

if envs.IsEnterprise() {
update := bson.M{
"$unset": bson.M{"vpn": ""},
}

_, err := db.
Collection("namespaces").
UpdateMany(ctx, bson.M{}, update)

return err
}

return nil
}),
}
Loading