From 8155c100d4f90f0f2ed25090de6000bcaef5ae8a Mon Sep 17 00:00:00 2001 From: Anton Kosyakov Date: Mon, 26 Jul 2021 08:09:49 +0000 Subject: [PATCH 1/4] =?UTF-8?q?[local-app-api]=C2=A0support=20Node.js?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/local-app-api/typescript-grpcweb/webpack.config.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/components/local-app-api/typescript-grpcweb/webpack.config.js b/components/local-app-api/typescript-grpcweb/webpack.config.js index 5f207d32007e62..45753c706c2543 100644 --- a/components/local-app-api/typescript-grpcweb/webpack.config.js +++ b/components/local-app-api/typescript-grpcweb/webpack.config.js @@ -27,7 +27,8 @@ module.exports = { output: { filename: 'localapp.js', path: path.resolve('./lib'), - libraryTarget: 'umd' + libraryTarget: 'umd', + globalObject: 'typeof self !== \'undefined\' ? self : this' }, externals: { '@improbable-eng/grpc-web': 'commonjs2 @improbable-eng/grpc-web', From b26d8a8a96271b76b3a7a4b4d40e78d4252a0a5f Mon Sep 17 00:00:00 2001 From: Anton Kosyakov Date: Mon, 26 Jul 2021 06:46:13 +0000 Subject: [PATCH 2/4] [local-app] control auto tunneling api --- components/local-app-api/go/localapp.pb.go | 168 ++++++++++++++++-- .../local-app-api/go/localapp_grpc.pb.go | 39 +++- components/local-app-api/localapp.proto | 9 +- components/local-app/pkg/auth/auth.go | 1 + components/local-app/pkg/bastion/bastion.go | 79 ++++++-- components/local-app/pkg/bastion/service.go | 7 + components/server/src/oauth-server/db.ts | 25 +-- 7 files changed, 280 insertions(+), 48 deletions(-) diff --git a/components/local-app-api/go/localapp.pb.go b/components/local-app-api/go/localapp.pb.go index fb3440abea3ae2..43aaf8681471fb 100644 --- a/components/local-app-api/go/localapp.pb.go +++ b/components/local-app-api/go/localapp.pb.go @@ -192,6 +192,99 @@ func (x *TunnelStatus) GetVisibility() api.TunnelVisiblity { return api.TunnelVisiblity(0) } +type AutoTunnelRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + InstanceId string `protobuf:"bytes,1,opt,name=instance_id,json=instanceId,proto3" json:"instance_id,omitempty"` + Enabled bool `protobuf:"varint,2,opt,name=enabled,proto3" json:"enabled,omitempty"` +} + +func (x *AutoTunnelRequest) Reset() { + *x = AutoTunnelRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_localapp_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *AutoTunnelRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AutoTunnelRequest) ProtoMessage() {} + +func (x *AutoTunnelRequest) ProtoReflect() protoreflect.Message { + mi := &file_localapp_proto_msgTypes[3] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AutoTunnelRequest.ProtoReflect.Descriptor instead. +func (*AutoTunnelRequest) Descriptor() ([]byte, []int) { + return file_localapp_proto_rawDescGZIP(), []int{3} +} + +func (x *AutoTunnelRequest) GetInstanceId() string { + if x != nil { + return x.InstanceId + } + return "" +} + +func (x *AutoTunnelRequest) GetEnabled() bool { + if x != nil { + return x.Enabled + } + return false +} + +type AutoTunnelResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *AutoTunnelResponse) Reset() { + *x = AutoTunnelResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_localapp_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *AutoTunnelResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AutoTunnelResponse) ProtoMessage() {} + +func (x *AutoTunnelResponse) ProtoReflect() protoreflect.Message { + mi := &file_localapp_proto_msgTypes[4] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AutoTunnelResponse.ProtoReflect.Descriptor instead. +func (*AutoTunnelResponse) Descriptor() ([]byte, []int) { + return file_localapp_proto_rawDescGZIP(), []int{4} +} + var File_localapp_proto protoreflect.FileDescriptor var file_localapp_proto_rawDesc = []byte{ @@ -216,17 +309,28 @@ var file_localapp_proto_rawDesc = []byte{ 0x72, 0x74, 0x12, 0x3b, 0x0a, 0x0a, 0x76, 0x69, 0x73, 0x69, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1b, 0x2e, 0x73, 0x75, 0x70, 0x65, 0x72, 0x76, 0x69, 0x73, 0x6f, 0x72, 0x2e, 0x54, 0x75, 0x6e, 0x6e, 0x65, 0x6c, 0x56, 0x69, 0x73, 0x69, 0x62, 0x6c, - 0x69, 0x74, 0x79, 0x52, 0x0a, 0x76, 0x69, 0x73, 0x69, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x32, - 0x5d, 0x0a, 0x08, 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x41, 0x70, 0x70, 0x12, 0x51, 0x0a, 0x0c, 0x54, - 0x75, 0x6e, 0x6e, 0x65, 0x6c, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x1d, 0x2e, 0x6c, 0x6f, - 0x63, 0x61, 0x6c, 0x61, 0x70, 0x70, 0x2e, 0x54, 0x75, 0x6e, 0x6e, 0x65, 0x6c, 0x53, 0x74, 0x61, - 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x6c, 0x6f, 0x63, - 0x61, 0x6c, 0x61, 0x70, 0x70, 0x2e, 0x54, 0x75, 0x6e, 0x6e, 0x65, 0x6c, 0x53, 0x74, 0x61, 0x74, - 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x30, 0x01, 0x42, 0x2b, - 0x5a, 0x29, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x69, 0x74, - 0x70, 0x6f, 0x64, 0x2d, 0x69, 0x6f, 0x2f, 0x67, 0x69, 0x74, 0x70, 0x6f, 0x64, 0x2f, 0x6c, 0x6f, - 0x63, 0x61, 0x6c, 0x2d, 0x61, 0x70, 0x70, 0x2f, 0x61, 0x70, 0x69, 0x62, 0x06, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x33, + 0x69, 0x74, 0x79, 0x52, 0x0a, 0x76, 0x69, 0x73, 0x69, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x22, + 0x4e, 0x0a, 0x11, 0x41, 0x75, 0x74, 0x6f, 0x54, 0x75, 0x6e, 0x6e, 0x65, 0x6c, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, + 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x69, 0x6e, 0x73, 0x74, 0x61, + 0x6e, 0x63, 0x65, 0x49, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x22, + 0x14, 0x0a, 0x12, 0x41, 0x75, 0x74, 0x6f, 0x54, 0x75, 0x6e, 0x6e, 0x65, 0x6c, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0xa8, 0x01, 0x0a, 0x08, 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x41, + 0x70, 0x70, 0x12, 0x51, 0x0a, 0x0c, 0x54, 0x75, 0x6e, 0x6e, 0x65, 0x6c, 0x53, 0x74, 0x61, 0x74, + 0x75, 0x73, 0x12, 0x1d, 0x2e, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x61, 0x70, 0x70, 0x2e, 0x54, 0x75, + 0x6e, 0x6e, 0x65, 0x6c, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x1e, 0x2e, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x61, 0x70, 0x70, 0x2e, 0x54, 0x75, 0x6e, + 0x6e, 0x65, 0x6c, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x22, 0x00, 0x30, 0x01, 0x12, 0x49, 0x0a, 0x0a, 0x41, 0x75, 0x74, 0x6f, 0x54, 0x75, 0x6e, + 0x6e, 0x65, 0x6c, 0x12, 0x1b, 0x2e, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x61, 0x70, 0x70, 0x2e, 0x41, + 0x75, 0x74, 0x6f, 0x54, 0x75, 0x6e, 0x6e, 0x65, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x1c, 0x2e, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x61, 0x70, 0x70, 0x2e, 0x41, 0x75, 0x74, 0x6f, + 0x54, 0x75, 0x6e, 0x6e, 0x65, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, + 0x42, 0x2b, 0x5a, 0x29, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, + 0x69, 0x74, 0x70, 0x6f, 0x64, 0x2d, 0x69, 0x6f, 0x2f, 0x67, 0x69, 0x74, 0x70, 0x6f, 0x64, 0x2f, + 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x2d, 0x61, 0x70, 0x70, 0x2f, 0x61, 0x70, 0x69, 0x62, 0x06, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -241,20 +345,24 @@ func file_localapp_proto_rawDescGZIP() []byte { return file_localapp_proto_rawDescData } -var file_localapp_proto_msgTypes = make([]protoimpl.MessageInfo, 3) +var file_localapp_proto_msgTypes = make([]protoimpl.MessageInfo, 5) var file_localapp_proto_goTypes = []interface{}{ (*TunnelStatusRequest)(nil), // 0: localapp.TunnelStatusRequest (*TunnelStatusResponse)(nil), // 1: localapp.TunnelStatusResponse (*TunnelStatus)(nil), // 2: localapp.TunnelStatus - (api.TunnelVisiblity)(0), // 3: supervisor.TunnelVisiblity + (*AutoTunnelRequest)(nil), // 3: localapp.AutoTunnelRequest + (*AutoTunnelResponse)(nil), // 4: localapp.AutoTunnelResponse + (api.TunnelVisiblity)(0), // 5: supervisor.TunnelVisiblity } var file_localapp_proto_depIdxs = []int32{ 2, // 0: localapp.TunnelStatusResponse.tunnels:type_name -> localapp.TunnelStatus - 3, // 1: localapp.TunnelStatus.visibility:type_name -> supervisor.TunnelVisiblity + 5, // 1: localapp.TunnelStatus.visibility:type_name -> supervisor.TunnelVisiblity 0, // 2: localapp.LocalApp.TunnelStatus:input_type -> localapp.TunnelStatusRequest - 1, // 3: localapp.LocalApp.TunnelStatus:output_type -> localapp.TunnelStatusResponse - 3, // [3:4] is the sub-list for method output_type - 2, // [2:3] is the sub-list for method input_type + 3, // 3: localapp.LocalApp.AutoTunnel:input_type -> localapp.AutoTunnelRequest + 1, // 4: localapp.LocalApp.TunnelStatus:output_type -> localapp.TunnelStatusResponse + 4, // 5: localapp.LocalApp.AutoTunnel:output_type -> localapp.AutoTunnelResponse + 4, // [4:6] is the sub-list for method output_type + 2, // [2:4] is the sub-list for method input_type 2, // [2:2] is the sub-list for extension type_name 2, // [2:2] is the sub-list for extension extendee 0, // [0:2] is the sub-list for field type_name @@ -302,6 +410,30 @@ func file_localapp_proto_init() { return nil } } + file_localapp_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*AutoTunnelRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_localapp_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*AutoTunnelResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } } type x struct{} out := protoimpl.TypeBuilder{ @@ -309,7 +441,7 @@ func file_localapp_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_localapp_proto_rawDesc, NumEnums: 0, - NumMessages: 3, + NumMessages: 5, NumExtensions: 0, NumServices: 1, }, diff --git a/components/local-app-api/go/localapp_grpc.pb.go b/components/local-app-api/go/localapp_grpc.pb.go index ab2a81c0e4a55d..21e5984222b35c 100644 --- a/components/local-app-api/go/localapp_grpc.pb.go +++ b/components/local-app-api/go/localapp_grpc.pb.go @@ -23,6 +23,7 @@ const _ = grpc.SupportPackageIsVersion7 // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. type LocalAppClient interface { TunnelStatus(ctx context.Context, in *TunnelStatusRequest, opts ...grpc.CallOption) (LocalApp_TunnelStatusClient, error) + AutoTunnel(ctx context.Context, in *AutoTunnelRequest, opts ...grpc.CallOption) (*AutoTunnelResponse, error) } type localAppClient struct { @@ -65,11 +66,21 @@ func (x *localAppTunnelStatusClient) Recv() (*TunnelStatusResponse, error) { return m, nil } +func (c *localAppClient) AutoTunnel(ctx context.Context, in *AutoTunnelRequest, opts ...grpc.CallOption) (*AutoTunnelResponse, error) { + out := new(AutoTunnelResponse) + err := c.cc.Invoke(ctx, "/localapp.LocalApp/AutoTunnel", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + // LocalAppServer is the server API for LocalApp service. // All implementations must embed UnimplementedLocalAppServer // for forward compatibility type LocalAppServer interface { TunnelStatus(*TunnelStatusRequest, LocalApp_TunnelStatusServer) error + AutoTunnel(context.Context, *AutoTunnelRequest) (*AutoTunnelResponse, error) mustEmbedUnimplementedLocalAppServer() } @@ -80,6 +91,9 @@ type UnimplementedLocalAppServer struct { func (UnimplementedLocalAppServer) TunnelStatus(*TunnelStatusRequest, LocalApp_TunnelStatusServer) error { return status.Errorf(codes.Unimplemented, "method TunnelStatus not implemented") } +func (UnimplementedLocalAppServer) AutoTunnel(context.Context, *AutoTunnelRequest) (*AutoTunnelResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method AutoTunnel not implemented") +} func (UnimplementedLocalAppServer) mustEmbedUnimplementedLocalAppServer() {} // UnsafeLocalAppServer may be embedded to opt out of forward compatibility for this service. @@ -114,13 +128,36 @@ func (x *localAppTunnelStatusServer) Send(m *TunnelStatusResponse) error { return x.ServerStream.SendMsg(m) } +func _LocalApp_AutoTunnel_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(AutoTunnelRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(LocalAppServer).AutoTunnel(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/localapp.LocalApp/AutoTunnel", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(LocalAppServer).AutoTunnel(ctx, req.(*AutoTunnelRequest)) + } + return interceptor(ctx, in, info, handler) +} + // LocalApp_ServiceDesc is the grpc.ServiceDesc for LocalApp service. // It's only intended for direct use with grpc.RegisterService, // and not to be introspected or modified (even as a copy) var LocalApp_ServiceDesc = grpc.ServiceDesc{ ServiceName: "localapp.LocalApp", HandlerType: (*LocalAppServer)(nil), - Methods: []grpc.MethodDesc{}, + Methods: []grpc.MethodDesc{ + { + MethodName: "AutoTunnel", + Handler: _LocalApp_AutoTunnel_Handler, + }, + }, Streams: []grpc.StreamDesc{ { StreamName: "TunnelStatus", diff --git a/components/local-app-api/localapp.proto b/components/local-app-api/localapp.proto index 706e0df44535e1..90fd706c03af39 100644 --- a/components/local-app-api/localapp.proto +++ b/components/local-app-api/localapp.proto @@ -11,6 +11,7 @@ import "supervisor-api/port.proto"; service LocalApp { rpc TunnelStatus(TunnelStatusRequest) returns (stream TunnelStatusResponse) {} + rpc AutoTunnel(AutoTunnelRequest) returns (AutoTunnelResponse) {} } message TunnelStatusRequest { string instance_id = 1; @@ -23,4 +24,10 @@ message TunnelStatus { uint32 remote_port = 1; uint32 local_port = 2; supervisor.TunnelVisiblity visibility = 3; -} \ No newline at end of file +} + +message AutoTunnelRequest { + string instance_id = 1; + bool enabled = 2; +} +message AutoTunnelResponse {} diff --git a/components/local-app/pkg/auth/auth.go b/components/local-app/pkg/auth/auth.go index b0a26be5e475e0..65d3b0e13baae2 100644 --- a/components/local-app/pkg/auth/auth.go +++ b/components/local-app/pkg/auth/auth.go @@ -118,6 +118,7 @@ func Login(ctx context.Context, opts LoginOpts) (token string, err error) { ClientID: "gplctl-1.0", ClientSecret: "gplctl-1.0-secret", // Required (even though it is marked as optional?!) Scopes: []string{ + "function:getWorkspace", "function:getWorkspaces", "function:listenForWorkspaceInstanceUpdates", "resource:workspace::*::get", diff --git a/components/local-app/pkg/bastion/bastion.go b/components/local-app/pkg/bastion/bastion.go index 766a7a5d4ce9a8..b9ba1eb57dcb54 100644 --- a/components/local-app/pkg/bastion/bastion.go +++ b/components/local-app/pkg/bastion/bastion.go @@ -77,8 +77,10 @@ type Workspace struct { supervisorListener *TunnelListener supervisorClient *grpc.ClientConn - tunnelListenersMu sync.RWMutex - tunnelListeners map[uint32]*TunnelListener + tunnelMu sync.RWMutex + tunnelListeners map[uint32]*TunnelListener + tunnelEnabled bool + cancelTunnel context.CancelFunc localSSHListener *TunnelListener SSHPrivateFN string @@ -89,12 +91,11 @@ type Workspace struct { tunnelClient chan chan *TunnelClient tunnelClientConnected bool - portsTunneled bool } func (ws *Workspace) Status() []*app.TunnelStatus { - ws.tunnelListenersMu.RLock() - defer ws.tunnelListenersMu.RUnlock() + ws.tunnelMu.RLock() + defer ws.tunnelMu.RUnlock() res := make([]*app.TunnelStatus, 0, len(ws.tunnelListeners)) for _, listener := range ws.tunnelListeners { res = append(res, &app.TunnelStatus{ @@ -239,6 +240,17 @@ func (b *Bastion) FullUpdate() { } } +func (b *Bastion) Update(workspaceID string) { + ws, err := b.Client.GetWorkspace(b.ctx, workspaceID) + if err != nil { + logrus.WithError(err).WithField("WorkspaceID", workspaceID).Warn("cannot get workspace") + } + if ws.LatestInstance == nil { + return + } + b.updates <- ws.LatestInstance +} + func (b *Bastion) handleUpdate(u *gitpod.WorkspaceInstance) { b.workspacesMu.Lock() defer b.workspacesMu.Unlock() @@ -258,6 +270,7 @@ func (b *Bastion) handleUpdate(u *gitpod.WorkspaceInstance) { tunnelClient: make(chan chan *TunnelClient, 1), tunnelListeners: make(map[uint32]*TunnelListener), + tunnelEnabled: true, } } ws.Phase = u.Status.Phase @@ -297,8 +310,7 @@ func (b *Bastion) handleUpdate(u *gitpod.WorkspaceInstance) { } } - if ws.supervisorClient != nil && !ws.portsTunneled { - ws.portsTunneled = true + if ws.supervisorClient != nil { go b.tunnelPorts(ws) } @@ -648,13 +660,27 @@ func installSSHAuthorizedKey(ws *Workspace, key string) error { } func (b *Bastion) tunnelPorts(ws *Workspace) { + ws.tunnelMu.Lock() + if !ws.tunnelEnabled || ws.cancelTunnel != nil { + ws.tunnelMu.Unlock() + return + } + ctx, cancel := context.WithCancel(ws.ctx) + ws.cancelTunnel = cancel + ws.tunnelMu.Unlock() + defer func() { - ws.portsTunneled = false + ws.tunnelMu.Lock() + defer ws.tunnelMu.Unlock() + + ws.cancelTunnel = nil logrus.WithField("workspace", ws.WorkspaceID).Info("ports tunneling finished") }() + for { logrus.WithField("workspace", ws.WorkspaceID).Info("tunneling ports...") - err := b.doTunnelPorts(ws) + + err := b.doTunnelPorts(ctx, ws) if ws.ctx.Err() != nil { return } @@ -662,15 +688,16 @@ func (b *Bastion) tunnelPorts(ws *Workspace) { logrus.WithError(err).WithField("workspace", ws.WorkspaceID).Warn("ports tunneling failed, retrying...") } select { - case <-ws.ctx.Done(): + case <-ctx.Done(): return case <-time.After(1 * time.Second): } } } -func (b *Bastion) doTunnelPorts(ws *Workspace) error { + +func (b *Bastion) doTunnelPorts(ctx context.Context, ws *Workspace) error { statusService := supervisor.NewStatusServiceClient(ws.supervisorClient) - status, err := statusService.PortsStatus(ws.ctx, &supervisor.PortsStatusRequest{ + status, err := statusService.PortsStatus(ctx, &supervisor.PortsStatusRequest{ Observe: true, }) if err != nil { @@ -678,8 +705,8 @@ func (b *Bastion) doTunnelPorts(ws *Workspace) error { } defer b.notify(ws) defer func() { - ws.tunnelListenersMu.Lock() - defer ws.tunnelListenersMu.Unlock() + ws.tunnelMu.Lock() + defer ws.tunnelMu.Unlock() for port, t := range ws.tunnelListeners { delete(ws.tunnelListeners, port) t.Cancel() @@ -690,7 +717,7 @@ func (b *Bastion) doTunnelPorts(ws *Workspace) error { if err != nil { return err } - ws.tunnelListenersMu.Lock() + ws.tunnelMu.Lock() currentTunneled := make(map[uint32]struct{}) for _, port := range resp.Ports { visibility := supervisor.TunnelVisiblity_none @@ -730,7 +757,7 @@ func (b *Bastion) doTunnelPorts(ws *Workspace) error { listener.Cancel() } } - ws.tunnelListenersMu.Unlock() + ws.tunnelMu.Unlock() b.notify(ws) } } @@ -806,3 +833,23 @@ func (b *Bastion) Subscribe(instanceID string) (*StatusSubscription, error) { sub.updates <- b.Status(instanceID) return sub, nil } + +func (b *Bastion) AutoTunnel(instanceID string, enabled bool) { + ws, ok := b.getWorkspace(instanceID) + if !ok { + return + } + ws.tunnelMu.Lock() + defer ws.tunnelMu.Unlock() + if ws.tunnelEnabled == enabled { + return + } + ws.tunnelEnabled = enabled + if enabled { + if ws.cancelTunnel == nil { + b.Update(ws.WorkspaceID) + } + } else if ws.cancelTunnel != nil { + ws.cancelTunnel() + } +} diff --git a/components/local-app/pkg/bastion/service.go b/components/local-app/pkg/bastion/service.go index d6934f6739e731..f796d5154bdbeb 100644 --- a/components/local-app/pkg/bastion/service.go +++ b/components/local-app/pkg/bastion/service.go @@ -5,6 +5,8 @@ package bastion import ( + "context" + "github.com/gitpod-io/gitpod/local-app/api" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" @@ -54,3 +56,8 @@ func (s *LocalAppService) TunnelStatus(req *api.TunnelStatusRequest, srv api.Loc } } } + +func (s *LocalAppService) AutoTunnel(ctx context.Context, req *api.AutoTunnelRequest) (*api.AutoTunnelResponse, error) { + s.b.AutoTunnel(req.InstanceId, req.Enabled) + return &api.AutoTunnelResponse{}, nil +} diff --git a/components/server/src/oauth-server/db.ts b/components/server/src/oauth-server/db.ts index c92b29db38f04c..196fe2b7dda874 100644 --- a/components/server/src/oauth-server/db.ts +++ b/components/server/src/oauth-server/db.ts @@ -18,10 +18,13 @@ export interface InMemory { } // Scopes -const getWorkspacesScope: OAuthScope = { name: "function:getWorkspaces" }; -const listenForWorkspaceInstanceUpdatesScope: OAuthScope = { name: "function:listenForWorkspaceInstanceUpdates" }; -const getWorkspaceResourceScope: OAuthScope = { name: "resource:" + ScopedResourceGuard.marshalResourceScope({ kind: "workspace", subjectID: "*", operations: ["get"] }) }; -const getWorkspaceInstanceResourceScope: OAuthScope = { name: "resource:" + ScopedResourceGuard.marshalResourceScope({ kind: "workspaceInstance", subjectID: "*", operations: ["get"] }) }; +const scopes: OAuthScope[] = [ + { name: "function:getWorkspace" }, + { name: "function:getWorkspaces" }, + { name: "function:listenForWorkspaceInstanceUpdates" }, + { name: "resource:" + ScopedResourceGuard.marshalResourceScope({ kind: "workspace", subjectID: "*", operations: ["get"] }) }, + { name: "resource:" + ScopedResourceGuard.marshalResourceScope({ kind: "workspaceInstance", subjectID: "*", operations: ["get"] }) } +]; // Clients const localAppClientID = 'gplctl-1.0'; @@ -31,9 +34,9 @@ const localClient: OAuthClient = { name: 'Gitpod local control client', // Set of valid redirect URIs // NOTE: these need to be kept in sync with the port range in the local app - redirectUris: Array.from({length: 10}, (_, i) => 'http://127.0.0.1:' + (63110 + i)), + redirectUris: Array.from({ length: 10 }, (_, i) => 'http://127.0.0.1:' + (63110 + i)), allowedGrants: ['authorization_code'], - scopes: [getWorkspacesScope, listenForWorkspaceInstanceUpdatesScope, getWorkspaceResourceScope, getWorkspaceInstanceResourceScope], + scopes, } export const inMemoryDatabase: InMemory = { @@ -41,10 +44,8 @@ export const inMemoryDatabase: InMemory = { [localClient.id]: localClient, }, tokens: {}, - scopes: { - [getWorkspacesScope.name]: getWorkspacesScope, - [listenForWorkspaceInstanceUpdatesScope.name]: listenForWorkspaceInstanceUpdatesScope, - [getWorkspaceResourceScope.name]: getWorkspaceResourceScope, - [getWorkspaceInstanceResourceScope.name]: getWorkspaceInstanceResourceScope, - }, + scopes: {}, }; +for (const scope of scopes) { + inMemoryDatabase.scopes[scope.name] = scope; +} From 07b141e92498897433f817a99275c2e377c3509b Mon Sep 17 00:00:00 2001 From: Anton Kosyakov Date: Sun, 8 Aug 2021 07:13:18 +0000 Subject: [PATCH 3/4] [supervisor] pass env vars to ssh sessions --- components/supervisor/BUILD.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/supervisor/BUILD.yaml b/components/supervisor/BUILD.yaml index 0f18c33b7f6a98..28edb57af40eb6 100644 --- a/components/supervisor/BUILD.yaml +++ b/components/supervisor/BUILD.yaml @@ -41,7 +41,7 @@ packages: commands: - ["curl", "-OL", "https://matt.ucc.asn.au/dropbear/dropbear-2020.81.tar.bz2"] - ["tar", "xjf", "dropbear-2020.81.tar.bz2"] - - ["sh", "-c", "cd dropbear-2020.81; ./configure --enable-static && make"] + - ["sh", "-c", "cd dropbear-2020.81; ./configure --enable-static&& sed -i '/clearenv();/d' svr-chansession.c && make"] - ["mv", "dropbear-2020.81/dropbear", "dropbear"] - ["mv", "dropbear-2020.81/dropbearkey", "dropbearkey"] - ["rm", "-rf", "dropbear-2020.81*"] From 7a4aa182a79ab77452d7aeb0ad0cdf0b18096f8a Mon Sep 17 00:00:00 2001 From: Anton Kosyakov Date: Sun, 8 Aug 2021 05:41:41 +0000 Subject: [PATCH 4/4] [code] fix #4780: desktop support --- components/ide/code/leeway.Dockerfile | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/components/ide/code/leeway.Dockerfile b/components/ide/code/leeway.Dockerfile index 8d2daf1f170e8a..38663de001f713 100644 --- a/components/ide/code/leeway.Dockerfile +++ b/components/ide/code/leeway.Dockerfile @@ -42,7 +42,7 @@ RUN curl -fsSL https://raw.githubusercontent.com/nvm-sh/nvm/v0.38.0/install.sh | && npm install -g yarn node-gyp ENV PATH $NVM_DIR/versions/node/v$NODE_VERSION/bin:$PATH -ENV GP_CODE_COMMIT 06711200f6a3a19af94f91102fd49d921b1145db +ENV GP_CODE_COMMIT 9bc86ad1d6f3f7116ef96003983738963fd70a6f RUN mkdir gp-code \ && cd gp-code \ && git init \ @@ -51,6 +51,7 @@ RUN mkdir gp-code \ && git reset --hard FETCH_HEAD WORKDIR /gp-code RUN yarn +RUN yarn gitpod:link RUN yarn gulp gitpod-min # grant write permissions for built-in extensions @@ -77,5 +78,5 @@ ENV GITPOD_ENV_SET_EDITOR /ide/bin/code ENV GITPOD_ENV_SET_VISUAL "$GITPOD_ENV_SET_EDITOR" ENV GITPOD_ENV_SET_GP_OPEN_EDITOR "$GITPOD_ENV_SET_EDITOR" ENV GITPOD_ENV_SET_GIT_EDITOR "$GITPOD_ENV_SET_EDITOR --wait" -ENV GITPOD_ENV_SET_GP_PREVIEW_BROWSER "/ide/bin/code --command gitpod.api.preview" -ENV GITPOD_ENV_SET_GP_EXTERNAL_BROWSER "/ide/bin/code --open-external" +ENV GITPOD_ENV_SET_GP_PREVIEW_BROWSER "/ide/bin/code --preview" +ENV GITPOD_ENV_SET_GP_EXTERNAL_BROWSER "/ide/bin/code --openExternal"