diff --git a/Makefile b/Makefile index e47627587..ec96a5b4e 100644 --- a/Makefile +++ b/Makefile @@ -142,12 +142,13 @@ proto: protolint protobuild .PHONY: protobuild protobuild: - @buf generate -o "${TMP_DIR}" + @buf generate -o "${TMP_DIR}" internal/proto # Move the generated files from the tmp file subdirectories into the current repo. cp -R ${TMP_DIR}/${REPO_PATH}/* ${THIS_DIR} @buf generate --template buf.openapiv2.gen.yaml --path internal/proto/controller/api/services/v1/ + cd internal/bsr/ && buf generate proto/ @protoc-go-inject-tag -input=./internal/oplog/store/oplog.pb.go @protoc-go-inject-tag -input=./internal/oplog/oplog_test/oplog_test.pb.go diff --git a/buf.work.yaml b/buf.work.yaml index e171a2670..54d070dc2 100644 --- a/buf.work.yaml +++ b/buf.work.yaml @@ -4,3 +4,4 @@ version: v1 directories: - internal/proto + - internal/bsr/proto diff --git a/internal/bsr/buf.gen.yaml b/internal/bsr/buf.gen.yaml new file mode 100644 index 000000000..58d642fee --- /dev/null +++ b/internal/bsr/buf.gen.yaml @@ -0,0 +1,13 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +version: v1 +managed: + enabled: true + go_package_prefix: + default: github.com/hashicorp/boundary/internal/bsr/gen +plugins: + - name: go + out: gen + opt: + - paths=source_relative \ No newline at end of file diff --git a/internal/bsr/gen/ssh/v1/ssh_chunks.pb.go b/internal/bsr/gen/ssh/v1/ssh_chunks.pb.go new file mode 100644 index 000000000..490ad9ec2 --- /dev/null +++ b/internal/bsr/gen/ssh/v1/ssh_chunks.pb.go @@ -0,0 +1,2152 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.30.0 +// protoc (unknown) +// source: ssh/v1/ssh_chunks.proto + +package sshv1 + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +// An UnknownRequest is an unrecognized SSH request. We'll record the type, want_reply, and payload +type UnknownRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The request type field + RequestType string `protobuf:"bytes,1,opt,name=request_type,json=requestType,proto3" json:"request_type,omitempty"` + // The want_reply field on the SSH request + WantReply bool `protobuf:"varint,2,opt,name=want_reply,json=wantReply,proto3" json:"want_reply,omitempty"` + // The Payload field on the SSH request + Data []byte `protobuf:"bytes,3,opt,name=data,proto3" json:"data,omitempty"` +} + +func (x *UnknownRequest) Reset() { + *x = UnknownRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_ssh_v1_ssh_chunks_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *UnknownRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UnknownRequest) ProtoMessage() {} + +func (x *UnknownRequest) ProtoReflect() protoreflect.Message { + mi := &file_ssh_v1_ssh_chunks_proto_msgTypes[0] + 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 UnknownRequest.ProtoReflect.Descriptor instead. +func (*UnknownRequest) Descriptor() ([]byte, []int) { + return file_ssh_v1_ssh_chunks_proto_rawDescGZIP(), []int{0} +} + +func (x *UnknownRequest) GetRequestType() string { + if x != nil { + return x.RequestType + } + return "" +} + +func (x *UnknownRequest) GetWantReply() bool { + if x != nil { + return x.WantReply + } + return false +} + +func (x *UnknownRequest) GetData() []byte { + if x != nil { + return x.Data + } + return nil +} + +// A session is started by sending the following message. +type SessionRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The request type field. This should always be "session" + RequestType string `protobuf:"bytes,1,opt,name=request_type,json=requestType,proto3" json:"request_type,omitempty"` + // The sender channel + SenderChannel uint32 `protobuf:"varint,2,opt,name=sender_channel,json=senderChannel,proto3" json:"sender_channel,omitempty"` + // The initial window size + InitialWindowSize uint32 `protobuf:"varint,3,opt,name=initial_window_size,json=initialWindowSize,proto3" json:"initial_window_size,omitempty"` + // The maximum packet size + MaximumPacketSize uint32 `protobuf:"varint,4,opt,name=maximum_packet_size,json=maximumPacketSize,proto3" json:"maximum_packet_size,omitempty"` +} + +func (x *SessionRequest) Reset() { + *x = SessionRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_ssh_v1_ssh_chunks_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *SessionRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SessionRequest) ProtoMessage() {} + +func (x *SessionRequest) ProtoReflect() protoreflect.Message { + mi := &file_ssh_v1_ssh_chunks_proto_msgTypes[1] + 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 SessionRequest.ProtoReflect.Descriptor instead. +func (*SessionRequest) Descriptor() ([]byte, []int) { + return file_ssh_v1_ssh_chunks_proto_rawDescGZIP(), []int{1} +} + +func (x *SessionRequest) GetRequestType() string { + if x != nil { + return x.RequestType + } + return "" +} + +func (x *SessionRequest) GetSenderChannel() uint32 { + if x != nil { + return x.SenderChannel + } + return 0 +} + +func (x *SessionRequest) GetInitialWindowSize() uint32 { + if x != nil { + return x.InitialWindowSize + } + return 0 +} + +func (x *SessionRequest) GetMaximumPacketSize() uint32 { + if x != nil { + return x.MaximumPacketSize + } + return 0 +} + +// X11 channels are opened with a channel open request. The resulting +// channels are independent of the session, and closing the session +// channel does not close the forwarded X11 channels. +type X11Request struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The request type field. This should always be "x11" + RequestType string `protobuf:"bytes,1,opt,name=request_type,json=requestType,proto3" json:"request_type,omitempty"` + // The sender channel + SenderChannel uint32 `protobuf:"varint,2,opt,name=sender_channel,json=senderChannel,proto3" json:"sender_channel,omitempty"` + // The initial window size + InitialWindowSize uint32 `protobuf:"varint,3,opt,name=initial_window_size,json=initialWindowSize,proto3" json:"initial_window_size,omitempty"` + // The maximum packet size + MaximumPacketSize uint32 `protobuf:"varint,4,opt,name=maximum_packet_size,json=maximumPacketSize,proto3" json:"maximum_packet_size,omitempty"` + // The originator address (e.g., "192.168.7.38") + OriginatorAddress string `protobuf:"bytes,5,opt,name=originator_address,json=originatorAddress,proto3" json:"originator_address,omitempty"` + // The originator port + OriginatorPort uint32 `protobuf:"varint,6,opt,name=originator_port,json=originatorPort,proto3" json:"originator_port,omitempty"` +} + +func (x *X11Request) Reset() { + *x = X11Request{} + if protoimpl.UnsafeEnabled { + mi := &file_ssh_v1_ssh_chunks_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *X11Request) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*X11Request) ProtoMessage() {} + +func (x *X11Request) ProtoReflect() protoreflect.Message { + mi := &file_ssh_v1_ssh_chunks_proto_msgTypes[2] + 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 X11Request.ProtoReflect.Descriptor instead. +func (*X11Request) Descriptor() ([]byte, []int) { + return file_ssh_v1_ssh_chunks_proto_rawDescGZIP(), []int{2} +} + +func (x *X11Request) GetRequestType() string { + if x != nil { + return x.RequestType + } + return "" +} + +func (x *X11Request) GetSenderChannel() uint32 { + if x != nil { + return x.SenderChannel + } + return 0 +} + +func (x *X11Request) GetInitialWindowSize() uint32 { + if x != nil { + return x.InitialWindowSize + } + return 0 +} + +func (x *X11Request) GetMaximumPacketSize() uint32 { + if x != nil { + return x.MaximumPacketSize + } + return 0 +} + +func (x *X11Request) GetOriginatorAddress() string { + if x != nil { + return x.OriginatorAddress + } + return "" +} + +func (x *X11Request) GetOriginatorPort() uint32 { + if x != nil { + return x.OriginatorPort + } + return 0 +} + +// When a connection comes to a port for which remote forwarding has +// been requested, a channel is opened to forward the port to the other +// side. +type ForwardedTCPIPRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The request type field. This should always be "forwarded-tcpip" + RequestType string `protobuf:"bytes,1,opt,name=request_type,json=requestType,proto3" json:"request_type,omitempty"` + // The sender channel + SenderChannel uint32 `protobuf:"varint,2,opt,name=sender_channel,json=senderChannel,proto3" json:"sender_channel,omitempty"` + // The initial window size + InitialWindowSize uint32 `protobuf:"varint,3,opt,name=initial_window_size,json=initialWindowSize,proto3" json:"initial_window_size,omitempty"` + // The maximum packet size + MaximumPacketSize uint32 `protobuf:"varint,4,opt,name=maximum_packet_size,json=maximumPacketSize,proto3" json:"maximum_packet_size,omitempty"` + // The address that was connected + Address string `protobuf:"bytes,5,opt,name=address,proto3" json:"address,omitempty"` + // The port that was connected + Port uint32 `protobuf:"varint,6,opt,name=port,proto3" json:"port,omitempty"` + // The originator ip address (e.g., "192.168.7.38") + OriginatorIpAddress string `protobuf:"bytes,7,opt,name=originator_ip_address,json=originatorIpAddress,proto3" json:"originator_ip_address,omitempty"` + // The originator port + OriginatorPort uint32 `protobuf:"varint,8,opt,name=originator_port,json=originatorPort,proto3" json:"originator_port,omitempty"` +} + +func (x *ForwardedTCPIPRequest) Reset() { + *x = ForwardedTCPIPRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_ssh_v1_ssh_chunks_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ForwardedTCPIPRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ForwardedTCPIPRequest) ProtoMessage() {} + +func (x *ForwardedTCPIPRequest) ProtoReflect() protoreflect.Message { + mi := &file_ssh_v1_ssh_chunks_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 ForwardedTCPIPRequest.ProtoReflect.Descriptor instead. +func (*ForwardedTCPIPRequest) Descriptor() ([]byte, []int) { + return file_ssh_v1_ssh_chunks_proto_rawDescGZIP(), []int{3} +} + +func (x *ForwardedTCPIPRequest) GetRequestType() string { + if x != nil { + return x.RequestType + } + return "" +} + +func (x *ForwardedTCPIPRequest) GetSenderChannel() uint32 { + if x != nil { + return x.SenderChannel + } + return 0 +} + +func (x *ForwardedTCPIPRequest) GetInitialWindowSize() uint32 { + if x != nil { + return x.InitialWindowSize + } + return 0 +} + +func (x *ForwardedTCPIPRequest) GetMaximumPacketSize() uint32 { + if x != nil { + return x.MaximumPacketSize + } + return 0 +} + +func (x *ForwardedTCPIPRequest) GetAddress() string { + if x != nil { + return x.Address + } + return "" +} + +func (x *ForwardedTCPIPRequest) GetPort() uint32 { + if x != nil { + return x.Port + } + return 0 +} + +func (x *ForwardedTCPIPRequest) GetOriginatorIpAddress() string { + if x != nil { + return x.OriginatorIpAddress + } + return "" +} + +func (x *ForwardedTCPIPRequest) GetOriginatorPort() uint32 { + if x != nil { + return x.OriginatorPort + } + return 0 +} + +// When a connection comes to a locally forwarded TCP/IP port, the +// following packet is sent to the other side. +type DirectTCPIPRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The request type field. This should always be "direct-tcpip" + RequestType string `protobuf:"bytes,1,opt,name=request_type,json=requestType,proto3" json:"request_type,omitempty"` + // The sender channel + SenderChannel uint32 `protobuf:"varint,2,opt,name=sender_channel,json=senderChannel,proto3" json:"sender_channel,omitempty"` + // The initial window size + InitialWindowSize uint32 `protobuf:"varint,3,opt,name=initial_window_size,json=initialWindowSize,proto3" json:"initial_window_size,omitempty"` + // The maximum packet size + MaximumPacketSize uint32 `protobuf:"varint,4,opt,name=maximum_packet_size,json=maximumPacketSize,proto3" json:"maximum_packet_size,omitempty"` + // The host to connect + Host string `protobuf:"bytes,5,opt,name=host,proto3" json:"host,omitempty"` + // The port to connect + Port uint32 `protobuf:"varint,6,opt,name=port,proto3" json:"port,omitempty"` + // The 'originator IP address' is the numeric IP address of the machine + // from where the connection request originates + OriginatorIpAddress string `protobuf:"bytes,7,opt,name=originator_ip_address,json=originatorIpAddress,proto3" json:"originator_ip_address,omitempty"` + // The port on the host from where the connection originated. + OriginatorPort uint32 `protobuf:"varint,8,opt,name=originator_port,json=originatorPort,proto3" json:"originator_port,omitempty"` +} + +func (x *DirectTCPIPRequest) Reset() { + *x = DirectTCPIPRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_ssh_v1_ssh_chunks_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *DirectTCPIPRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DirectTCPIPRequest) ProtoMessage() {} + +func (x *DirectTCPIPRequest) ProtoReflect() protoreflect.Message { + mi := &file_ssh_v1_ssh_chunks_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 DirectTCPIPRequest.ProtoReflect.Descriptor instead. +func (*DirectTCPIPRequest) Descriptor() ([]byte, []int) { + return file_ssh_v1_ssh_chunks_proto_rawDescGZIP(), []int{4} +} + +func (x *DirectTCPIPRequest) GetRequestType() string { + if x != nil { + return x.RequestType + } + return "" +} + +func (x *DirectTCPIPRequest) GetSenderChannel() uint32 { + if x != nil { + return x.SenderChannel + } + return 0 +} + +func (x *DirectTCPIPRequest) GetInitialWindowSize() uint32 { + if x != nil { + return x.InitialWindowSize + } + return 0 +} + +func (x *DirectTCPIPRequest) GetMaximumPacketSize() uint32 { + if x != nil { + return x.MaximumPacketSize + } + return 0 +} + +func (x *DirectTCPIPRequest) GetHost() string { + if x != nil { + return x.Host + } + return "" +} + +func (x *DirectTCPIPRequest) GetPort() uint32 { + if x != nil { + return x.Port + } + return 0 +} + +func (x *DirectTCPIPRequest) GetOriginatorIpAddress() string { + if x != nil { + return x.OriginatorIpAddress + } + return "" +} + +func (x *DirectTCPIPRequest) GetOriginatorPort() uint32 { + if x != nil { + return x.OriginatorPort + } + return 0 +} + +// A pseudo-terminal can be allocated for the session by sending the +// following message. +type PtyRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The request type field. This should always be "pty-req" + RequestType string `protobuf:"bytes,1,opt,name=request_type,json=requestType,proto3" json:"request_type,omitempty"` + // The want_reply field on the SSH request + WantReply bool `protobuf:"varint,2,opt,name=want_reply,json=wantReply,proto3" json:"want_reply,omitempty"` + // TERM environment variable value (e.g., vt100) + TermEnvVar string `protobuf:"bytes,3,opt,name=term_env_var,json=termEnvVar,proto3" json:"term_env_var,omitempty"` + // Terminal width, characters (e.g., 80) + TerminalWidthCharacters uint32 `protobuf:"varint,4,opt,name=terminal_width_characters,json=terminalWidthCharacters,proto3" json:"terminal_width_characters,omitempty"` + // Terminal height, rows (e.g., 24) + TerminalHeightRows uint32 `protobuf:"varint,5,opt,name=terminal_height_rows,json=terminalHeightRows,proto3" json:"terminal_height_rows,omitempty"` + // Terminal width, pixels (e.g., 640) + TerminalWidthPixels uint32 `protobuf:"varint,6,opt,name=terminal_width_pixels,json=terminalWidthPixels,proto3" json:"terminal_width_pixels,omitempty"` + // Terminal height, pixels (e.g., 480) + TerminalHeightPixels uint32 `protobuf:"varint,7,opt,name=terminal_height_pixels,json=terminalHeightPixels,proto3" json:"terminal_height_pixels,omitempty"` + // Encoded terminal modes + EncodedTerminalMode []byte `protobuf:"bytes,8,opt,name=encoded_terminal_mode,json=encodedTerminalMode,proto3" json:"encoded_terminal_mode,omitempty"` +} + +func (x *PtyRequest) Reset() { + *x = PtyRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_ssh_v1_ssh_chunks_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *PtyRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PtyRequest) ProtoMessage() {} + +func (x *PtyRequest) ProtoReflect() protoreflect.Message { + mi := &file_ssh_v1_ssh_chunks_proto_msgTypes[5] + 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 PtyRequest.ProtoReflect.Descriptor instead. +func (*PtyRequest) Descriptor() ([]byte, []int) { + return file_ssh_v1_ssh_chunks_proto_rawDescGZIP(), []int{5} +} + +func (x *PtyRequest) GetRequestType() string { + if x != nil { + return x.RequestType + } + return "" +} + +func (x *PtyRequest) GetWantReply() bool { + if x != nil { + return x.WantReply + } + return false +} + +func (x *PtyRequest) GetTermEnvVar() string { + if x != nil { + return x.TermEnvVar + } + return "" +} + +func (x *PtyRequest) GetTerminalWidthCharacters() uint32 { + if x != nil { + return x.TerminalWidthCharacters + } + return 0 +} + +func (x *PtyRequest) GetTerminalHeightRows() uint32 { + if x != nil { + return x.TerminalHeightRows + } + return 0 +} + +func (x *PtyRequest) GetTerminalWidthPixels() uint32 { + if x != nil { + return x.TerminalWidthPixels + } + return 0 +} + +func (x *PtyRequest) GetTerminalHeightPixels() uint32 { + if x != nil { + return x.TerminalHeightPixels + } + return 0 +} + +func (x *PtyRequest) GetEncodedTerminalMode() []byte { + if x != nil { + return x.EncodedTerminalMode + } + return nil +} + +// X11 forwarding may be requested for a session using the following message +type X11ForwardingRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The request type field. This should always be "x11-req" + RequestType string `protobuf:"bytes,1,opt,name=request_type,json=requestType,proto3" json:"request_type,omitempty"` + // The want_reply field on the SSH request + WantReply bool `protobuf:"varint,2,opt,name=want_reply,json=wantReply,proto3" json:"want_reply,omitempty"` + // Single connection + SingleConnection bool `protobuf:"varint,3,opt,name=single_connection,json=singleConnection,proto3" json:"single_connection,omitempty"` + // X11 authentication protocol + X11AuthenticationProtocol string `protobuf:"bytes,4,opt,name=x11_authentication_protocol,json=x11AuthenticationProtocol,proto3" json:"x11_authentication_protocol,omitempty"` + // X11 authentication cookie + X11AuthenticationCookie string `protobuf:"bytes,5,opt,name=x11_authentication_cookie,json=x11AuthenticationCookie,proto3" json:"x11_authentication_cookie,omitempty"` + // X11 screen number + X11ScreenNumber uint32 `protobuf:"varint,6,opt,name=x11_screen_number,json=x11ScreenNumber,proto3" json:"x11_screen_number,omitempty"` +} + +func (x *X11ForwardingRequest) Reset() { + *x = X11ForwardingRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_ssh_v1_ssh_chunks_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *X11ForwardingRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*X11ForwardingRequest) ProtoMessage() {} + +func (x *X11ForwardingRequest) ProtoReflect() protoreflect.Message { + mi := &file_ssh_v1_ssh_chunks_proto_msgTypes[6] + 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 X11ForwardingRequest.ProtoReflect.Descriptor instead. +func (*X11ForwardingRequest) Descriptor() ([]byte, []int) { + return file_ssh_v1_ssh_chunks_proto_rawDescGZIP(), []int{6} +} + +func (x *X11ForwardingRequest) GetRequestType() string { + if x != nil { + return x.RequestType + } + return "" +} + +func (x *X11ForwardingRequest) GetWantReply() bool { + if x != nil { + return x.WantReply + } + return false +} + +func (x *X11ForwardingRequest) GetSingleConnection() bool { + if x != nil { + return x.SingleConnection + } + return false +} + +func (x *X11ForwardingRequest) GetX11AuthenticationProtocol() string { + if x != nil { + return x.X11AuthenticationProtocol + } + return "" +} + +func (x *X11ForwardingRequest) GetX11AuthenticationCookie() string { + if x != nil { + return x.X11AuthenticationCookie + } + return "" +} + +func (x *X11ForwardingRequest) GetX11ScreenNumber() uint32 { + if x != nil { + return x.X11ScreenNumber + } + return 0 +} + +// Environment variables may be passed to the shell/command to be +// started later. +type EnvRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The request type field. This should always be "env" + RequestType string `protobuf:"bytes,1,opt,name=request_type,json=requestType,proto3" json:"request_type,omitempty"` + // The want_reply field on the SSH request + WantReply bool `protobuf:"varint,2,opt,name=want_reply,json=wantReply,proto3" json:"want_reply,omitempty"` + // Variable name + VariableName string `protobuf:"bytes,3,opt,name=variable_name,json=variableName,proto3" json:"variable_name,omitempty"` + // Variable value + VariableValue string `protobuf:"bytes,4,opt,name=variable_value,json=variableValue,proto3" json:"variable_value,omitempty"` +} + +func (x *EnvRequest) Reset() { + *x = EnvRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_ssh_v1_ssh_chunks_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *EnvRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*EnvRequest) ProtoMessage() {} + +func (x *EnvRequest) ProtoReflect() protoreflect.Message { + mi := &file_ssh_v1_ssh_chunks_proto_msgTypes[7] + 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 EnvRequest.ProtoReflect.Descriptor instead. +func (*EnvRequest) Descriptor() ([]byte, []int) { + return file_ssh_v1_ssh_chunks_proto_rawDescGZIP(), []int{7} +} + +func (x *EnvRequest) GetRequestType() string { + if x != nil { + return x.RequestType + } + return "" +} + +func (x *EnvRequest) GetWantReply() bool { + if x != nil { + return x.WantReply + } + return false +} + +func (x *EnvRequest) GetVariableName() string { + if x != nil { + return x.VariableName + } + return "" +} + +func (x *EnvRequest) GetVariableValue() string { + if x != nil { + return x.VariableValue + } + return "" +} + +// This message will request that the user's default shell (typically +// defined in /etc/passwd in UNIX systems) be started at the other end. +type ShellRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The request type field. This should always be "shell" + RequestType string `protobuf:"bytes,1,opt,name=request_type,json=requestType,proto3" json:"request_type,omitempty"` + // The want_reply field on the SSH request + WantReply bool `protobuf:"varint,2,opt,name=want_reply,json=wantReply,proto3" json:"want_reply,omitempty"` +} + +func (x *ShellRequest) Reset() { + *x = ShellRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_ssh_v1_ssh_chunks_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ShellRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ShellRequest) ProtoMessage() {} + +func (x *ShellRequest) ProtoReflect() protoreflect.Message { + mi := &file_ssh_v1_ssh_chunks_proto_msgTypes[8] + 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 ShellRequest.ProtoReflect.Descriptor instead. +func (*ShellRequest) Descriptor() ([]byte, []int) { + return file_ssh_v1_ssh_chunks_proto_rawDescGZIP(), []int{8} +} + +func (x *ShellRequest) GetRequestType() string { + if x != nil { + return x.RequestType + } + return "" +} + +func (x *ShellRequest) GetWantReply() bool { + if x != nil { + return x.WantReply + } + return false +} + +// This message will request that the server start the execution of the +// given command. The 'command' string may contain a path. +type ExecRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The request type field. This should always be "exec" + RequestType string `protobuf:"bytes,1,opt,name=request_type,json=requestType,proto3" json:"request_type,omitempty"` + // The want_reply field on the SSH request + WantReply bool `protobuf:"varint,2,opt,name=want_reply,json=wantReply,proto3" json:"want_reply,omitempty"` + // The command to be executed + Command string `protobuf:"bytes,3,opt,name=command,proto3" json:"command,omitempty"` +} + +func (x *ExecRequest) Reset() { + *x = ExecRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_ssh_v1_ssh_chunks_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ExecRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ExecRequest) ProtoMessage() {} + +func (x *ExecRequest) ProtoReflect() protoreflect.Message { + mi := &file_ssh_v1_ssh_chunks_proto_msgTypes[9] + 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 ExecRequest.ProtoReflect.Descriptor instead. +func (*ExecRequest) Descriptor() ([]byte, []int) { + return file_ssh_v1_ssh_chunks_proto_rawDescGZIP(), []int{9} +} + +func (x *ExecRequest) GetRequestType() string { + if x != nil { + return x.RequestType + } + return "" +} + +func (x *ExecRequest) GetWantReply() bool { + if x != nil { + return x.WantReply + } + return false +} + +func (x *ExecRequest) GetCommand() string { + if x != nil { + return x.Command + } + return "" +} + +// Execute a predefined subsystem. It is expected that +// these will include a general file transfer mechanism, and possibly +// other features. +type SubsystemRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The request type field. This should always be "subsystem" + RequestType string `protobuf:"bytes,1,opt,name=request_type,json=requestType,proto3" json:"request_type,omitempty"` + // The want_reply field on the SSH request + WantReply bool `protobuf:"varint,2,opt,name=want_reply,json=wantReply,proto3" json:"want_reply,omitempty"` + // The predefined subsystem to execute + SubsystemName string `protobuf:"bytes,3,opt,name=subsystem_name,json=subsystemName,proto3" json:"subsystem_name,omitempty"` +} + +func (x *SubsystemRequest) Reset() { + *x = SubsystemRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_ssh_v1_ssh_chunks_proto_msgTypes[10] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *SubsystemRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SubsystemRequest) ProtoMessage() {} + +func (x *SubsystemRequest) ProtoReflect() protoreflect.Message { + mi := &file_ssh_v1_ssh_chunks_proto_msgTypes[10] + 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 SubsystemRequest.ProtoReflect.Descriptor instead. +func (*SubsystemRequest) Descriptor() ([]byte, []int) { + return file_ssh_v1_ssh_chunks_proto_rawDescGZIP(), []int{10} +} + +func (x *SubsystemRequest) GetRequestType() string { + if x != nil { + return x.RequestType + } + return "" +} + +func (x *SubsystemRequest) GetWantReply() bool { + if x != nil { + return x.WantReply + } + return false +} + +func (x *SubsystemRequest) GetSubsystemName() string { + if x != nil { + return x.SubsystemName + } + return "" +} + +// When the window (terminal) size changes on the client side, it MAY +// send a message to the other side to inform it of the new dimensions. +type WindowChangeRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The request type field. This should always be "window-change" + RequestType string `protobuf:"bytes,1,opt,name=request_type,json=requestType,proto3" json:"request_type,omitempty"` + // The want_reply field on the SSH request. This should always be false. + WantReply bool `protobuf:"varint,2,opt,name=want_reply,json=wantReply,proto3" json:"want_reply,omitempty"` + // Terminal width, columns + TerminalWidthColumns uint32 `protobuf:"varint,3,opt,name=terminal_width_columns,json=terminalWidthColumns,proto3" json:"terminal_width_columns,omitempty"` + // Terminal height, rows + TerminalHeightRows uint32 `protobuf:"varint,4,opt,name=terminal_height_rows,json=terminalHeightRows,proto3" json:"terminal_height_rows,omitempty"` + // Terminal width, pixels + TerminalWidthPixels uint32 `protobuf:"varint,5,opt,name=terminal_width_pixels,json=terminalWidthPixels,proto3" json:"terminal_width_pixels,omitempty"` + // Terminal height, pixels + TerminalHeightPixels uint32 `protobuf:"varint,6,opt,name=terminal_height_pixels,json=terminalHeightPixels,proto3" json:"terminal_height_pixels,omitempty"` +} + +func (x *WindowChangeRequest) Reset() { + *x = WindowChangeRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_ssh_v1_ssh_chunks_proto_msgTypes[11] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *WindowChangeRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*WindowChangeRequest) ProtoMessage() {} + +func (x *WindowChangeRequest) ProtoReflect() protoreflect.Message { + mi := &file_ssh_v1_ssh_chunks_proto_msgTypes[11] + 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 WindowChangeRequest.ProtoReflect.Descriptor instead. +func (*WindowChangeRequest) Descriptor() ([]byte, []int) { + return file_ssh_v1_ssh_chunks_proto_rawDescGZIP(), []int{11} +} + +func (x *WindowChangeRequest) GetRequestType() string { + if x != nil { + return x.RequestType + } + return "" +} + +func (x *WindowChangeRequest) GetWantReply() bool { + if x != nil { + return x.WantReply + } + return false +} + +func (x *WindowChangeRequest) GetTerminalWidthColumns() uint32 { + if x != nil { + return x.TerminalWidthColumns + } + return 0 +} + +func (x *WindowChangeRequest) GetTerminalHeightRows() uint32 { + if x != nil { + return x.TerminalHeightRows + } + return 0 +} + +func (x *WindowChangeRequest) GetTerminalWidthPixels() uint32 { + if x != nil { + return x.TerminalWidthPixels + } + return 0 +} + +func (x *WindowChangeRequest) GetTerminalHeightPixels() uint32 { + if x != nil { + return x.TerminalHeightPixels + } + return 0 +} + +// The message below is used by the server to inform the client when it +// can or cannot perform flow control (control-S/control-Q processing). +// If 'client can do' is TRUE, the client is allowed to do flow control +// using control-S and control-Q +type XonXoffRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The request type field. This should always be "xon-xoff" + RequestType string `protobuf:"bytes,1,opt,name=request_type,json=requestType,proto3" json:"request_type,omitempty"` + // The want_reply field on the SSH request. This should always be false. + WantReply bool `protobuf:"varint,2,opt,name=want_reply,json=wantReply,proto3" json:"want_reply,omitempty"` + // Client can do flow control + ClientCanDo bool `protobuf:"varint,3,opt,name=client_can_do,json=clientCanDo,proto3" json:"client_can_do,omitempty"` +} + +func (x *XonXoffRequest) Reset() { + *x = XonXoffRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_ssh_v1_ssh_chunks_proto_msgTypes[12] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *XonXoffRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*XonXoffRequest) ProtoMessage() {} + +func (x *XonXoffRequest) ProtoReflect() protoreflect.Message { + mi := &file_ssh_v1_ssh_chunks_proto_msgTypes[12] + 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 XonXoffRequest.ProtoReflect.Descriptor instead. +func (*XonXoffRequest) Descriptor() ([]byte, []int) { + return file_ssh_v1_ssh_chunks_proto_rawDescGZIP(), []int{12} +} + +func (x *XonXoffRequest) GetRequestType() string { + if x != nil { + return x.RequestType + } + return "" +} + +func (x *XonXoffRequest) GetWantReply() bool { + if x != nil { + return x.WantReply + } + return false +} + +func (x *XonXoffRequest) GetClientCanDo() bool { + if x != nil { + return x.ClientCanDo + } + return false +} + +// A signal can be delivered to the remote process/service using the +// following message. +type SignalRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The request type field. This should always be "signal" + RequestType string `protobuf:"bytes,1,opt,name=request_type,json=requestType,proto3" json:"request_type,omitempty"` + // The want_reply field on the SSH request. This should always be false + WantReply bool `protobuf:"varint,2,opt,name=want_reply,json=wantReply,proto3" json:"want_reply,omitempty"` + // Signal name, without the SIG prefix. The 'signal name' is one of the following (these are from [POSIX]). + // + // ABRT ALRM FPE HUP ILL INT KILL + // PIPE QUIT SEGV TERM USR1 USR2 + SignalName string `protobuf:"bytes,3,opt,name=signal_name,json=signalName,proto3" json:"signal_name,omitempty"` +} + +func (x *SignalRequest) Reset() { + *x = SignalRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_ssh_v1_ssh_chunks_proto_msgTypes[13] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *SignalRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SignalRequest) ProtoMessage() {} + +func (x *SignalRequest) ProtoReflect() protoreflect.Message { + mi := &file_ssh_v1_ssh_chunks_proto_msgTypes[13] + 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 SignalRequest.ProtoReflect.Descriptor instead. +func (*SignalRequest) Descriptor() ([]byte, []int) { + return file_ssh_v1_ssh_chunks_proto_rawDescGZIP(), []int{13} +} + +func (x *SignalRequest) GetRequestType() string { + if x != nil { + return x.RequestType + } + return "" +} + +func (x *SignalRequest) GetWantReply() bool { + if x != nil { + return x.WantReply + } + return false +} + +func (x *SignalRequest) GetSignalName() string { + if x != nil { + return x.SignalName + } + return "" +} + +// When the command running at the other end terminates, the following +// +// message can be sent to return the exit status of the command. +type ExitStatusRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The request type field. This should always be "exit-status" + RequestType string `protobuf:"bytes,1,opt,name=request_type,json=requestType,proto3" json:"request_type,omitempty"` + // The want_reply field on the SSH request. This should always be false. + WantReply bool `protobuf:"varint,2,opt,name=want_reply,json=wantReply,proto3" json:"want_reply,omitempty"` + // Exit status of the command + ExitStatus uint32 `protobuf:"varint,3,opt,name=exit_status,json=exitStatus,proto3" json:"exit_status,omitempty"` +} + +func (x *ExitStatusRequest) Reset() { + *x = ExitStatusRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_ssh_v1_ssh_chunks_proto_msgTypes[14] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ExitStatusRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ExitStatusRequest) ProtoMessage() {} + +func (x *ExitStatusRequest) ProtoReflect() protoreflect.Message { + mi := &file_ssh_v1_ssh_chunks_proto_msgTypes[14] + 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 ExitStatusRequest.ProtoReflect.Descriptor instead. +func (*ExitStatusRequest) Descriptor() ([]byte, []int) { + return file_ssh_v1_ssh_chunks_proto_rawDescGZIP(), []int{14} +} + +func (x *ExitStatusRequest) GetRequestType() string { + if x != nil { + return x.RequestType + } + return "" +} + +func (x *ExitStatusRequest) GetWantReply() bool { + if x != nil { + return x.WantReply + } + return false +} + +func (x *ExitStatusRequest) GetExitStatus() uint32 { + if x != nil { + return x.ExitStatus + } + return 0 +} + +// The remote command may terminate violently due to a signal. +// Such a condition can be indicated by the following message. A zero +// 'exit_status' usually means that the command terminated successfully. +type ExitSignalRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The request type field. This should always be "exit-signal" + RequestType string `protobuf:"bytes,1,opt,name=request_type,json=requestType,proto3" json:"request_type,omitempty"` + // The want_reply field on the SSH request. This should always be false. + WantReply bool `protobuf:"varint,2,opt,name=want_reply,json=wantReply,proto3" json:"want_reply,omitempty"` + // Signal name, without the SIG prefix. The 'signal name' is one of the following (these are from [POSIX]). + // + // ABRT ALRM FPE HUP ILL INT KILL + // PIPE QUIT SEGV TERM USR1 USR2 + SignalName string `protobuf:"bytes,3,opt,name=signal_name,json=signalName,proto3" json:"signal_name,omitempty"` + // Core dumped + CoreDumped bool `protobuf:"varint,4,opt,name=core_dumped,json=coreDumped,proto3" json:"core_dumped,omitempty"` + // Error message in ISO-10646 UTF-8 encoding + ErrorMessage string `protobuf:"bytes,5,opt,name=error_message,json=errorMessage,proto3" json:"error_message,omitempty"` + // Language tag from https://www.rfc-editor.org/rfc/rfc3066 + LanguageTag string `protobuf:"bytes,6,opt,name=language_tag,json=languageTag,proto3" json:"language_tag,omitempty"` +} + +func (x *ExitSignalRequest) Reset() { + *x = ExitSignalRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_ssh_v1_ssh_chunks_proto_msgTypes[15] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ExitSignalRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ExitSignalRequest) ProtoMessage() {} + +func (x *ExitSignalRequest) ProtoReflect() protoreflect.Message { + mi := &file_ssh_v1_ssh_chunks_proto_msgTypes[15] + 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 ExitSignalRequest.ProtoReflect.Descriptor instead. +func (*ExitSignalRequest) Descriptor() ([]byte, []int) { + return file_ssh_v1_ssh_chunks_proto_rawDescGZIP(), []int{15} +} + +func (x *ExitSignalRequest) GetRequestType() string { + if x != nil { + return x.RequestType + } + return "" +} + +func (x *ExitSignalRequest) GetWantReply() bool { + if x != nil { + return x.WantReply + } + return false +} + +func (x *ExitSignalRequest) GetSignalName() string { + if x != nil { + return x.SignalName + } + return "" +} + +func (x *ExitSignalRequest) GetCoreDumped() bool { + if x != nil { + return x.CoreDumped + } + return false +} + +func (x *ExitSignalRequest) GetErrorMessage() string { + if x != nil { + return x.ErrorMessage + } + return "" +} + +func (x *ExitSignalRequest) GetLanguageTag() string { + if x != nil { + return x.LanguageTag + } + return "" +} + +// The following channel-specific request can be sent over a session +// channel to request that the remote host perform +// a BREAK operation. +type BreakRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The request type field. This should always be "break" + RequestType string `protobuf:"bytes,1,opt,name=request_type,json=requestType,proto3" json:"request_type,omitempty"` + // The want_reply field on the SSH request. This should always be false. + WantReply bool `protobuf:"varint,2,opt,name=want_reply,json=wantReply,proto3" json:"want_reply,omitempty"` + // Break-length in milliseconds + BreakLengthMs uint32 `protobuf:"varint,3,opt,name=break_length_ms,json=breakLengthMs,proto3" json:"break_length_ms,omitempty"` +} + +func (x *BreakRequest) Reset() { + *x = BreakRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_ssh_v1_ssh_chunks_proto_msgTypes[16] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *BreakRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*BreakRequest) ProtoMessage() {} + +func (x *BreakRequest) ProtoReflect() protoreflect.Message { + mi := &file_ssh_v1_ssh_chunks_proto_msgTypes[16] + 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 BreakRequest.ProtoReflect.Descriptor instead. +func (*BreakRequest) Descriptor() ([]byte, []int) { + return file_ssh_v1_ssh_chunks_proto_rawDescGZIP(), []int{16} +} + +func (x *BreakRequest) GetRequestType() string { + if x != nil { + return x.RequestType + } + return "" +} + +func (x *BreakRequest) GetWantReply() bool { + if x != nil { + return x.WantReply + } + return false +} + +func (x *BreakRequest) GetBreakLengthMs() uint32 { + if x != nil { + return x.BreakLengthMs + } + return 0 +} + +// A party need not explicitly request forwardings from its own end to +// the other direction. However, if it wishes that connections to a +// port on the other side be forwarded to the local side, it must +// explicitly request this. +type TCPIPForwardRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The request type field. This should always be "tcpip-forward" + RequestType string `protobuf:"bytes,1,opt,name=request_type,json=requestType,proto3" json:"request_type,omitempty"` + // The want_reply field on the SSH request. This should always be false. + WantReply bool `protobuf:"varint,2,opt,name=want_reply,json=wantReply,proto3" json:"want_reply,omitempty"` + // Address to bind (e.g., "0.0.0.0") on which connections for forwarding + // are to be accepted + AddressToBind string `protobuf:"bytes,3,opt,name=address_to_bind,json=addressToBind,proto3" json:"address_to_bind,omitempty"` + // Port number to bind on which connections for forwarding + // are to be accepted + PortToBind uint32 `protobuf:"varint,4,opt,name=port_to_bind,json=portToBind,proto3" json:"port_to_bind,omitempty"` +} + +func (x *TCPIPForwardRequest) Reset() { + *x = TCPIPForwardRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_ssh_v1_ssh_chunks_proto_msgTypes[17] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *TCPIPForwardRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TCPIPForwardRequest) ProtoMessage() {} + +func (x *TCPIPForwardRequest) ProtoReflect() protoreflect.Message { + mi := &file_ssh_v1_ssh_chunks_proto_msgTypes[17] + 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 TCPIPForwardRequest.ProtoReflect.Descriptor instead. +func (*TCPIPForwardRequest) Descriptor() ([]byte, []int) { + return file_ssh_v1_ssh_chunks_proto_rawDescGZIP(), []int{17} +} + +func (x *TCPIPForwardRequest) GetRequestType() string { + if x != nil { + return x.RequestType + } + return "" +} + +func (x *TCPIPForwardRequest) GetWantReply() bool { + if x != nil { + return x.WantReply + } + return false +} + +func (x *TCPIPForwardRequest) GetAddressToBind() string { + if x != nil { + return x.AddressToBind + } + return "" +} + +func (x *TCPIPForwardRequest) GetPortToBind() uint32 { + if x != nil { + return x.PortToBind + } + return 0 +} + +// A port forwarding can be canceled with the following message. Note +// that channel open requests may be received until a reply to this +// message is received. +type CancelTCPIPForwardRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The request type field. This should always be "cancel-tcpip-forward" + RequestType string `protobuf:"bytes,1,opt,name=request_type,json=requestType,proto3" json:"request_type,omitempty"` + // The want_reply field on the SSH request. This should always be false. + WantReply bool `protobuf:"varint,2,opt,name=want_reply,json=wantReply,proto3" json:"want_reply,omitempty"` + // Address to bind (e.g., "0.0.0.0") on which connections for forwarding + // are to be accepted + AddressToBind string `protobuf:"bytes,3,opt,name=address_to_bind,json=addressToBind,proto3" json:"address_to_bind,omitempty"` + // Port number to bind on which connections for forwarding + // are to be accepted + PortToBind uint32 `protobuf:"varint,4,opt,name=port_to_bind,json=portToBind,proto3" json:"port_to_bind,omitempty"` +} + +func (x *CancelTCPIPForwardRequest) Reset() { + *x = CancelTCPIPForwardRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_ssh_v1_ssh_chunks_proto_msgTypes[18] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *CancelTCPIPForwardRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CancelTCPIPForwardRequest) ProtoMessage() {} + +func (x *CancelTCPIPForwardRequest) ProtoReflect() protoreflect.Message { + mi := &file_ssh_v1_ssh_chunks_proto_msgTypes[18] + 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 CancelTCPIPForwardRequest.ProtoReflect.Descriptor instead. +func (*CancelTCPIPForwardRequest) Descriptor() ([]byte, []int) { + return file_ssh_v1_ssh_chunks_proto_rawDescGZIP(), []int{18} +} + +func (x *CancelTCPIPForwardRequest) GetRequestType() string { + if x != nil { + return x.RequestType + } + return "" +} + +func (x *CancelTCPIPForwardRequest) GetWantReply() bool { + if x != nil { + return x.WantReply + } + return false +} + +func (x *CancelTCPIPForwardRequest) GetAddressToBind() string { + if x != nil { + return x.AddressToBind + } + return "" +} + +func (x *CancelTCPIPForwardRequest) GetPortToBind() uint32 { + if x != nil { + return x.PortToBind + } + return 0 +} + +var File_ssh_v1_ssh_chunks_proto protoreflect.FileDescriptor + +var file_ssh_v1_ssh_chunks_proto_rawDesc = []byte{ + 0x0a, 0x17, 0x73, 0x73, 0x68, 0x2f, 0x76, 0x31, 0x2f, 0x73, 0x73, 0x68, 0x5f, 0x63, 0x68, 0x75, + 0x6e, 0x6b, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x06, 0x73, 0x73, 0x68, 0x2e, 0x76, + 0x31, 0x22, 0x66, 0x0a, 0x0e, 0x55, 0x6e, 0x6b, 0x6e, 0x6f, 0x77, 0x6e, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x74, + 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x72, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x77, 0x61, 0x6e, 0x74, 0x5f, 0x72, + 0x65, 0x70, 0x6c, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x77, 0x61, 0x6e, 0x74, + 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x12, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x0c, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x22, 0xba, 0x01, 0x0a, 0x0e, 0x53, 0x65, + 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x21, 0x0a, 0x0c, + 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x0b, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, + 0x25, 0x0a, 0x0e, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, + 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0d, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x43, + 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x12, 0x2e, 0x0a, 0x13, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x61, + 0x6c, 0x5f, 0x77, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x0d, 0x52, 0x11, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x6c, 0x57, 0x69, 0x6e, 0x64, + 0x6f, 0x77, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x2e, 0x0a, 0x13, 0x6d, 0x61, 0x78, 0x69, 0x6d, 0x75, + 0x6d, 0x5f, 0x70, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x04, 0x20, + 0x01, 0x28, 0x0d, 0x52, 0x11, 0x6d, 0x61, 0x78, 0x69, 0x6d, 0x75, 0x6d, 0x50, 0x61, 0x63, 0x6b, + 0x65, 0x74, 0x53, 0x69, 0x7a, 0x65, 0x22, 0x8e, 0x02, 0x0a, 0x0a, 0x58, 0x31, 0x31, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x72, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x73, 0x65, 0x6e, 0x64, + 0x65, 0x72, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, + 0x52, 0x0d, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x12, + 0x2e, 0x0a, 0x13, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x6c, 0x5f, 0x77, 0x69, 0x6e, 0x64, 0x6f, + 0x77, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x11, 0x69, 0x6e, + 0x69, 0x74, 0x69, 0x61, 0x6c, 0x57, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x53, 0x69, 0x7a, 0x65, 0x12, + 0x2e, 0x0a, 0x13, 0x6d, 0x61, 0x78, 0x69, 0x6d, 0x75, 0x6d, 0x5f, 0x70, 0x61, 0x63, 0x6b, 0x65, + 0x74, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x11, 0x6d, 0x61, + 0x78, 0x69, 0x6d, 0x75, 0x6d, 0x50, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x53, 0x69, 0x7a, 0x65, 0x12, + 0x2d, 0x0a, 0x12, 0x6f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x61, 0x74, 0x6f, 0x72, 0x5f, 0x61, 0x64, + 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x11, 0x6f, 0x72, 0x69, + 0x67, 0x69, 0x6e, 0x61, 0x74, 0x6f, 0x72, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x27, + 0x0a, 0x0f, 0x6f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x61, 0x74, 0x6f, 0x72, 0x5f, 0x70, 0x6f, 0x72, + 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0e, 0x6f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x61, + 0x74, 0x6f, 0x72, 0x50, 0x6f, 0x72, 0x74, 0x22, 0xcc, 0x02, 0x0a, 0x15, 0x46, 0x6f, 0x72, 0x77, + 0x61, 0x72, 0x64, 0x65, 0x64, 0x54, 0x43, 0x50, 0x49, 0x50, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x74, 0x79, 0x70, + 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x54, 0x79, 0x70, 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x5f, 0x63, + 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0d, 0x73, 0x65, + 0x6e, 0x64, 0x65, 0x72, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x12, 0x2e, 0x0a, 0x13, 0x69, + 0x6e, 0x69, 0x74, 0x69, 0x61, 0x6c, 0x5f, 0x77, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x5f, 0x73, 0x69, + 0x7a, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x11, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x61, + 0x6c, 0x57, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x2e, 0x0a, 0x13, 0x6d, + 0x61, 0x78, 0x69, 0x6d, 0x75, 0x6d, 0x5f, 0x70, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x5f, 0x73, 0x69, + 0x7a, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x11, 0x6d, 0x61, 0x78, 0x69, 0x6d, 0x75, + 0x6d, 0x50, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x61, + 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x64, + 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x6f, 0x72, 0x74, 0x18, 0x06, 0x20, + 0x01, 0x28, 0x0d, 0x52, 0x04, 0x70, 0x6f, 0x72, 0x74, 0x12, 0x32, 0x0a, 0x15, 0x6f, 0x72, 0x69, + 0x67, 0x69, 0x6e, 0x61, 0x74, 0x6f, 0x72, 0x5f, 0x69, 0x70, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, + 0x73, 0x73, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x13, 0x6f, 0x72, 0x69, 0x67, 0x69, 0x6e, + 0x61, 0x74, 0x6f, 0x72, 0x49, 0x70, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x27, 0x0a, + 0x0f, 0x6f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x61, 0x74, 0x6f, 0x72, 0x5f, 0x70, 0x6f, 0x72, 0x74, + 0x18, 0x08, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0e, 0x6f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x61, 0x74, + 0x6f, 0x72, 0x50, 0x6f, 0x72, 0x74, 0x22, 0xc3, 0x02, 0x0a, 0x12, 0x44, 0x69, 0x72, 0x65, 0x63, + 0x74, 0x54, 0x43, 0x50, 0x49, 0x50, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x21, 0x0a, + 0x0c, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x0b, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x54, 0x79, 0x70, 0x65, + 0x12, 0x25, 0x0a, 0x0e, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x6e, + 0x65, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0d, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, + 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x12, 0x2e, 0x0a, 0x13, 0x69, 0x6e, 0x69, 0x74, 0x69, + 0x61, 0x6c, 0x5f, 0x77, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x0d, 0x52, 0x11, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x6c, 0x57, 0x69, 0x6e, + 0x64, 0x6f, 0x77, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x2e, 0x0a, 0x13, 0x6d, 0x61, 0x78, 0x69, 0x6d, + 0x75, 0x6d, 0x5f, 0x70, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x04, + 0x20, 0x01, 0x28, 0x0d, 0x52, 0x11, 0x6d, 0x61, 0x78, 0x69, 0x6d, 0x75, 0x6d, 0x50, 0x61, 0x63, + 0x6b, 0x65, 0x74, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x6f, 0x73, 0x74, 0x18, + 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x68, 0x6f, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x70, + 0x6f, 0x72, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x04, 0x70, 0x6f, 0x72, 0x74, 0x12, + 0x32, 0x0a, 0x15, 0x6f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x61, 0x74, 0x6f, 0x72, 0x5f, 0x69, 0x70, + 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x13, + 0x6f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x61, 0x74, 0x6f, 0x72, 0x49, 0x70, 0x41, 0x64, 0x64, 0x72, + 0x65, 0x73, 0x73, 0x12, 0x27, 0x0a, 0x0f, 0x6f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x61, 0x74, 0x6f, + 0x72, 0x5f, 0x70, 0x6f, 0x72, 0x74, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0e, 0x6f, 0x72, + 0x69, 0x67, 0x69, 0x6e, 0x61, 0x74, 0x6f, 0x72, 0x50, 0x6f, 0x72, 0x74, 0x22, 0xfc, 0x02, 0x0a, + 0x0a, 0x50, 0x74, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x72, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x0b, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1d, + 0x0a, 0x0a, 0x77, 0x61, 0x6e, 0x74, 0x5f, 0x72, 0x65, 0x70, 0x6c, 0x79, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x08, 0x52, 0x09, 0x77, 0x61, 0x6e, 0x74, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x20, 0x0a, + 0x0c, 0x74, 0x65, 0x72, 0x6d, 0x5f, 0x65, 0x6e, 0x76, 0x5f, 0x76, 0x61, 0x72, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x0a, 0x74, 0x65, 0x72, 0x6d, 0x45, 0x6e, 0x76, 0x56, 0x61, 0x72, 0x12, + 0x3a, 0x0a, 0x19, 0x74, 0x65, 0x72, 0x6d, 0x69, 0x6e, 0x61, 0x6c, 0x5f, 0x77, 0x69, 0x64, 0x74, + 0x68, 0x5f, 0x63, 0x68, 0x61, 0x72, 0x61, 0x63, 0x74, 0x65, 0x72, 0x73, 0x18, 0x04, 0x20, 0x01, + 0x28, 0x0d, 0x52, 0x17, 0x74, 0x65, 0x72, 0x6d, 0x69, 0x6e, 0x61, 0x6c, 0x57, 0x69, 0x64, 0x74, + 0x68, 0x43, 0x68, 0x61, 0x72, 0x61, 0x63, 0x74, 0x65, 0x72, 0x73, 0x12, 0x30, 0x0a, 0x14, 0x74, + 0x65, 0x72, 0x6d, 0x69, 0x6e, 0x61, 0x6c, 0x5f, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x5f, 0x72, + 0x6f, 0x77, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x12, 0x74, 0x65, 0x72, 0x6d, 0x69, + 0x6e, 0x61, 0x6c, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x52, 0x6f, 0x77, 0x73, 0x12, 0x32, 0x0a, + 0x15, 0x74, 0x65, 0x72, 0x6d, 0x69, 0x6e, 0x61, 0x6c, 0x5f, 0x77, 0x69, 0x64, 0x74, 0x68, 0x5f, + 0x70, 0x69, 0x78, 0x65, 0x6c, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x13, 0x74, 0x65, + 0x72, 0x6d, 0x69, 0x6e, 0x61, 0x6c, 0x57, 0x69, 0x64, 0x74, 0x68, 0x50, 0x69, 0x78, 0x65, 0x6c, + 0x73, 0x12, 0x34, 0x0a, 0x16, 0x74, 0x65, 0x72, 0x6d, 0x69, 0x6e, 0x61, 0x6c, 0x5f, 0x68, 0x65, + 0x69, 0x67, 0x68, 0x74, 0x5f, 0x70, 0x69, 0x78, 0x65, 0x6c, 0x73, 0x18, 0x07, 0x20, 0x01, 0x28, + 0x0d, 0x52, 0x14, 0x74, 0x65, 0x72, 0x6d, 0x69, 0x6e, 0x61, 0x6c, 0x48, 0x65, 0x69, 0x67, 0x68, + 0x74, 0x50, 0x69, 0x78, 0x65, 0x6c, 0x73, 0x12, 0x32, 0x0a, 0x15, 0x65, 0x6e, 0x63, 0x6f, 0x64, + 0x65, 0x64, 0x5f, 0x74, 0x65, 0x72, 0x6d, 0x69, 0x6e, 0x61, 0x6c, 0x5f, 0x6d, 0x6f, 0x64, 0x65, + 0x18, 0x08, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x13, 0x65, 0x6e, 0x63, 0x6f, 0x64, 0x65, 0x64, 0x54, + 0x65, 0x72, 0x6d, 0x69, 0x6e, 0x61, 0x6c, 0x4d, 0x6f, 0x64, 0x65, 0x22, 0xad, 0x02, 0x0a, 0x14, + 0x58, 0x31, 0x31, 0x46, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, + 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x72, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x77, 0x61, 0x6e, 0x74, 0x5f, + 0x72, 0x65, 0x70, 0x6c, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x77, 0x61, 0x6e, + 0x74, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x2b, 0x0a, 0x11, 0x73, 0x69, 0x6e, 0x67, 0x6c, 0x65, + 0x5f, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x08, 0x52, 0x10, 0x73, 0x69, 0x6e, 0x67, 0x6c, 0x65, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, + 0x69, 0x6f, 0x6e, 0x12, 0x3e, 0x0a, 0x1b, 0x78, 0x31, 0x31, 0x5f, 0x61, 0x75, 0x74, 0x68, 0x65, + 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, + 0x6f, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x19, 0x78, 0x31, 0x31, 0x41, 0x75, 0x74, + 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x72, 0x6f, 0x74, 0x6f, + 0x63, 0x6f, 0x6c, 0x12, 0x3a, 0x0a, 0x19, 0x78, 0x31, 0x31, 0x5f, 0x61, 0x75, 0x74, 0x68, 0x65, + 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x63, 0x6f, 0x6f, 0x6b, 0x69, 0x65, + 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x17, 0x78, 0x31, 0x31, 0x41, 0x75, 0x74, 0x68, 0x65, + 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6f, 0x6b, 0x69, 0x65, 0x12, + 0x2a, 0x0a, 0x11, 0x78, 0x31, 0x31, 0x5f, 0x73, 0x63, 0x72, 0x65, 0x65, 0x6e, 0x5f, 0x6e, 0x75, + 0x6d, 0x62, 0x65, 0x72, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0f, 0x78, 0x31, 0x31, 0x53, + 0x63, 0x72, 0x65, 0x65, 0x6e, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x22, 0x9a, 0x01, 0x0a, 0x0a, + 0x45, 0x6e, 0x76, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x72, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x0b, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1d, 0x0a, + 0x0a, 0x77, 0x61, 0x6e, 0x74, 0x5f, 0x72, 0x65, 0x70, 0x6c, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x08, 0x52, 0x09, 0x77, 0x61, 0x6e, 0x74, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x23, 0x0a, 0x0d, + 0x76, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x0c, 0x76, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x4e, 0x61, 0x6d, + 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x76, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x76, 0x61, + 0x6c, 0x75, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x76, 0x61, 0x72, 0x69, 0x61, + 0x62, 0x6c, 0x65, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x50, 0x0a, 0x0c, 0x53, 0x68, 0x65, 0x6c, + 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x72, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, + 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x77, + 0x61, 0x6e, 0x74, 0x5f, 0x72, 0x65, 0x70, 0x6c, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, + 0x09, 0x77, 0x61, 0x6e, 0x74, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x69, 0x0a, 0x0b, 0x45, 0x78, + 0x65, 0x63, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x72, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x0b, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1d, 0x0a, 0x0a, + 0x77, 0x61, 0x6e, 0x74, 0x5f, 0x72, 0x65, 0x70, 0x6c, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, + 0x52, 0x09, 0x77, 0x61, 0x6e, 0x74, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x18, 0x0a, 0x07, 0x63, + 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x63, 0x6f, + 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x22, 0x7b, 0x0a, 0x10, 0x53, 0x75, 0x62, 0x73, 0x79, 0x73, 0x74, + 0x65, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x72, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x0b, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1d, 0x0a, 0x0a, + 0x77, 0x61, 0x6e, 0x74, 0x5f, 0x72, 0x65, 0x70, 0x6c, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, + 0x52, 0x09, 0x77, 0x61, 0x6e, 0x74, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x25, 0x0a, 0x0e, 0x73, + 0x75, 0x62, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x0d, 0x73, 0x75, 0x62, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x4e, 0x61, + 0x6d, 0x65, 0x22, 0xa9, 0x02, 0x0a, 0x13, 0x57, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x43, 0x68, 0x61, + 0x6e, 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x72, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x0b, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1d, 0x0a, + 0x0a, 0x77, 0x61, 0x6e, 0x74, 0x5f, 0x72, 0x65, 0x70, 0x6c, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x08, 0x52, 0x09, 0x77, 0x61, 0x6e, 0x74, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x34, 0x0a, 0x16, + 0x74, 0x65, 0x72, 0x6d, 0x69, 0x6e, 0x61, 0x6c, 0x5f, 0x77, 0x69, 0x64, 0x74, 0x68, 0x5f, 0x63, + 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x14, 0x74, 0x65, + 0x72, 0x6d, 0x69, 0x6e, 0x61, 0x6c, 0x57, 0x69, 0x64, 0x74, 0x68, 0x43, 0x6f, 0x6c, 0x75, 0x6d, + 0x6e, 0x73, 0x12, 0x30, 0x0a, 0x14, 0x74, 0x65, 0x72, 0x6d, 0x69, 0x6e, 0x61, 0x6c, 0x5f, 0x68, + 0x65, 0x69, 0x67, 0x68, 0x74, 0x5f, 0x72, 0x6f, 0x77, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, + 0x52, 0x12, 0x74, 0x65, 0x72, 0x6d, 0x69, 0x6e, 0x61, 0x6c, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, + 0x52, 0x6f, 0x77, 0x73, 0x12, 0x32, 0x0a, 0x15, 0x74, 0x65, 0x72, 0x6d, 0x69, 0x6e, 0x61, 0x6c, + 0x5f, 0x77, 0x69, 0x64, 0x74, 0x68, 0x5f, 0x70, 0x69, 0x78, 0x65, 0x6c, 0x73, 0x18, 0x05, 0x20, + 0x01, 0x28, 0x0d, 0x52, 0x13, 0x74, 0x65, 0x72, 0x6d, 0x69, 0x6e, 0x61, 0x6c, 0x57, 0x69, 0x64, + 0x74, 0x68, 0x50, 0x69, 0x78, 0x65, 0x6c, 0x73, 0x12, 0x34, 0x0a, 0x16, 0x74, 0x65, 0x72, 0x6d, + 0x69, 0x6e, 0x61, 0x6c, 0x5f, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x5f, 0x70, 0x69, 0x78, 0x65, + 0x6c, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x14, 0x74, 0x65, 0x72, 0x6d, 0x69, 0x6e, + 0x61, 0x6c, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x50, 0x69, 0x78, 0x65, 0x6c, 0x73, 0x22, 0x76, + 0x0a, 0x0e, 0x58, 0x6f, 0x6e, 0x58, 0x6f, 0x66, 0x66, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x12, 0x21, 0x0a, 0x0c, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x54, + 0x79, 0x70, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x77, 0x61, 0x6e, 0x74, 0x5f, 0x72, 0x65, 0x70, 0x6c, + 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x77, 0x61, 0x6e, 0x74, 0x52, 0x65, 0x70, + 0x6c, 0x79, 0x12, 0x22, 0x0a, 0x0d, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x63, 0x61, 0x6e, + 0x5f, 0x64, 0x6f, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0b, 0x63, 0x6c, 0x69, 0x65, 0x6e, + 0x74, 0x43, 0x61, 0x6e, 0x44, 0x6f, 0x22, 0x72, 0x0a, 0x0d, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x6c, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x72, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x72, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x77, 0x61, + 0x6e, 0x74, 0x5f, 0x72, 0x65, 0x70, 0x6c, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, + 0x77, 0x61, 0x6e, 0x74, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x69, 0x67, + 0x6e, 0x61, 0x6c, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, + 0x73, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x4e, 0x61, 0x6d, 0x65, 0x22, 0x76, 0x0a, 0x11, 0x45, 0x78, + 0x69, 0x74, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, + 0x21, 0x0a, 0x0c, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x54, 0x79, + 0x70, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x77, 0x61, 0x6e, 0x74, 0x5f, 0x72, 0x65, 0x70, 0x6c, 0x79, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x77, 0x61, 0x6e, 0x74, 0x52, 0x65, 0x70, 0x6c, + 0x79, 0x12, 0x1f, 0x0a, 0x0b, 0x65, 0x78, 0x69, 0x74, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0a, 0x65, 0x78, 0x69, 0x74, 0x53, 0x74, 0x61, 0x74, + 0x75, 0x73, 0x22, 0xdf, 0x01, 0x0a, 0x11, 0x45, 0x78, 0x69, 0x74, 0x53, 0x69, 0x67, 0x6e, 0x61, + 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x72, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, + 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x77, + 0x61, 0x6e, 0x74, 0x5f, 0x72, 0x65, 0x70, 0x6c, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, + 0x09, 0x77, 0x61, 0x6e, 0x74, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x69, + 0x67, 0x6e, 0x61, 0x6c, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x0a, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x63, + 0x6f, 0x72, 0x65, 0x5f, 0x64, 0x75, 0x6d, 0x70, 0x65, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, + 0x52, 0x0a, 0x63, 0x6f, 0x72, 0x65, 0x44, 0x75, 0x6d, 0x70, 0x65, 0x64, 0x12, 0x23, 0x0a, 0x0d, + 0x65, 0x72, 0x72, 0x6f, 0x72, 0x5f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x05, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x0c, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, + 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x6c, 0x61, 0x6e, 0x67, 0x75, 0x61, 0x67, 0x65, 0x5f, 0x74, 0x61, + 0x67, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x6c, 0x61, 0x6e, 0x67, 0x75, 0x61, 0x67, + 0x65, 0x54, 0x61, 0x67, 0x22, 0x78, 0x0a, 0x0c, 0x42, 0x72, 0x65, 0x61, 0x6b, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, + 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x72, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x77, 0x61, 0x6e, 0x74, 0x5f, + 0x72, 0x65, 0x70, 0x6c, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x77, 0x61, 0x6e, + 0x74, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x26, 0x0a, 0x0f, 0x62, 0x72, 0x65, 0x61, 0x6b, 0x5f, + 0x6c, 0x65, 0x6e, 0x67, 0x74, 0x68, 0x5f, 0x6d, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, + 0x0d, 0x62, 0x72, 0x65, 0x61, 0x6b, 0x4c, 0x65, 0x6e, 0x67, 0x74, 0x68, 0x4d, 0x73, 0x22, 0xa1, + 0x01, 0x0a, 0x13, 0x54, 0x43, 0x50, 0x49, 0x50, 0x46, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x72, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x77, 0x61, 0x6e, + 0x74, 0x5f, 0x72, 0x65, 0x70, 0x6c, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x77, + 0x61, 0x6e, 0x74, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x26, 0x0a, 0x0f, 0x61, 0x64, 0x64, 0x72, + 0x65, 0x73, 0x73, 0x5f, 0x74, 0x6f, 0x5f, 0x62, 0x69, 0x6e, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x0d, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x54, 0x6f, 0x42, 0x69, 0x6e, 0x64, + 0x12, 0x20, 0x0a, 0x0c, 0x70, 0x6f, 0x72, 0x74, 0x5f, 0x74, 0x6f, 0x5f, 0x62, 0x69, 0x6e, 0x64, + 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0a, 0x70, 0x6f, 0x72, 0x74, 0x54, 0x6f, 0x42, 0x69, + 0x6e, 0x64, 0x22, 0xa7, 0x01, 0x0a, 0x19, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x54, 0x43, 0x50, + 0x49, 0x50, 0x46, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x12, 0x21, 0x0a, 0x0c, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x54, + 0x79, 0x70, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x77, 0x61, 0x6e, 0x74, 0x5f, 0x72, 0x65, 0x70, 0x6c, + 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x77, 0x61, 0x6e, 0x74, 0x52, 0x65, 0x70, + 0x6c, 0x79, 0x12, 0x26, 0x0a, 0x0f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x5f, 0x74, 0x6f, + 0x5f, 0x62, 0x69, 0x6e, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x61, 0x64, 0x64, + 0x72, 0x65, 0x73, 0x73, 0x54, 0x6f, 0x42, 0x69, 0x6e, 0x64, 0x12, 0x20, 0x0a, 0x0c, 0x70, 0x6f, + 0x72, 0x74, 0x5f, 0x74, 0x6f, 0x5f, 0x62, 0x69, 0x6e, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, + 0x52, 0x0a, 0x70, 0x6f, 0x72, 0x74, 0x54, 0x6f, 0x42, 0x69, 0x6e, 0x64, 0x42, 0x92, 0x01, 0x0a, + 0x0a, 0x63, 0x6f, 0x6d, 0x2e, 0x73, 0x73, 0x68, 0x2e, 0x76, 0x31, 0x42, 0x0e, 0x53, 0x73, 0x68, + 0x43, 0x68, 0x75, 0x6e, 0x6b, 0x73, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x3b, 0x67, + 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, + 0x6f, 0x72, 0x70, 0x2f, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x61, 0x72, 0x79, 0x2f, 0x69, 0x6e, 0x74, + 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x62, 0x73, 0x72, 0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x73, 0x73, + 0x68, 0x2f, 0x76, 0x31, 0x3b, 0x73, 0x73, 0x68, 0x76, 0x31, 0xa2, 0x02, 0x03, 0x53, 0x58, 0x58, + 0xaa, 0x02, 0x06, 0x53, 0x73, 0x68, 0x2e, 0x56, 0x31, 0xca, 0x02, 0x06, 0x53, 0x73, 0x68, 0x5c, + 0x56, 0x31, 0xe2, 0x02, 0x12, 0x53, 0x73, 0x68, 0x5c, 0x56, 0x31, 0x5c, 0x47, 0x50, 0x42, 0x4d, + 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, 0x07, 0x53, 0x73, 0x68, 0x3a, 0x3a, 0x56, + 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_ssh_v1_ssh_chunks_proto_rawDescOnce sync.Once + file_ssh_v1_ssh_chunks_proto_rawDescData = file_ssh_v1_ssh_chunks_proto_rawDesc +) + +func file_ssh_v1_ssh_chunks_proto_rawDescGZIP() []byte { + file_ssh_v1_ssh_chunks_proto_rawDescOnce.Do(func() { + file_ssh_v1_ssh_chunks_proto_rawDescData = protoimpl.X.CompressGZIP(file_ssh_v1_ssh_chunks_proto_rawDescData) + }) + return file_ssh_v1_ssh_chunks_proto_rawDescData +} + +var file_ssh_v1_ssh_chunks_proto_msgTypes = make([]protoimpl.MessageInfo, 19) +var file_ssh_v1_ssh_chunks_proto_goTypes = []interface{}{ + (*UnknownRequest)(nil), // 0: ssh.v1.UnknownRequest + (*SessionRequest)(nil), // 1: ssh.v1.SessionRequest + (*X11Request)(nil), // 2: ssh.v1.X11Request + (*ForwardedTCPIPRequest)(nil), // 3: ssh.v1.ForwardedTCPIPRequest + (*DirectTCPIPRequest)(nil), // 4: ssh.v1.DirectTCPIPRequest + (*PtyRequest)(nil), // 5: ssh.v1.PtyRequest + (*X11ForwardingRequest)(nil), // 6: ssh.v1.X11ForwardingRequest + (*EnvRequest)(nil), // 7: ssh.v1.EnvRequest + (*ShellRequest)(nil), // 8: ssh.v1.ShellRequest + (*ExecRequest)(nil), // 9: ssh.v1.ExecRequest + (*SubsystemRequest)(nil), // 10: ssh.v1.SubsystemRequest + (*WindowChangeRequest)(nil), // 11: ssh.v1.WindowChangeRequest + (*XonXoffRequest)(nil), // 12: ssh.v1.XonXoffRequest + (*SignalRequest)(nil), // 13: ssh.v1.SignalRequest + (*ExitStatusRequest)(nil), // 14: ssh.v1.ExitStatusRequest + (*ExitSignalRequest)(nil), // 15: ssh.v1.ExitSignalRequest + (*BreakRequest)(nil), // 16: ssh.v1.BreakRequest + (*TCPIPForwardRequest)(nil), // 17: ssh.v1.TCPIPForwardRequest + (*CancelTCPIPForwardRequest)(nil), // 18: ssh.v1.CancelTCPIPForwardRequest +} +var file_ssh_v1_ssh_chunks_proto_depIdxs = []int32{ + 0, // [0:0] is the sub-list for method output_type + 0, // [0:0] is the sub-list for method input_type + 0, // [0:0] is the sub-list for extension type_name + 0, // [0:0] is the sub-list for extension extendee + 0, // [0:0] is the sub-list for field type_name +} + +func init() { file_ssh_v1_ssh_chunks_proto_init() } +func file_ssh_v1_ssh_chunks_proto_init() { + if File_ssh_v1_ssh_chunks_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_ssh_v1_ssh_chunks_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*UnknownRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_ssh_v1_ssh_chunks_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SessionRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_ssh_v1_ssh_chunks_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*X11Request); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_ssh_v1_ssh_chunks_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ForwardedTCPIPRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_ssh_v1_ssh_chunks_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*DirectTCPIPRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_ssh_v1_ssh_chunks_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*PtyRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_ssh_v1_ssh_chunks_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*X11ForwardingRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_ssh_v1_ssh_chunks_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*EnvRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_ssh_v1_ssh_chunks_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ShellRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_ssh_v1_ssh_chunks_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ExecRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_ssh_v1_ssh_chunks_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SubsystemRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_ssh_v1_ssh_chunks_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*WindowChangeRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_ssh_v1_ssh_chunks_proto_msgTypes[12].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*XonXoffRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_ssh_v1_ssh_chunks_proto_msgTypes[13].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SignalRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_ssh_v1_ssh_chunks_proto_msgTypes[14].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ExitStatusRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_ssh_v1_ssh_chunks_proto_msgTypes[15].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ExitSignalRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_ssh_v1_ssh_chunks_proto_msgTypes[16].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*BreakRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_ssh_v1_ssh_chunks_proto_msgTypes[17].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*TCPIPForwardRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_ssh_v1_ssh_chunks_proto_msgTypes[18].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*CancelTCPIPForwardRequest); 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{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_ssh_v1_ssh_chunks_proto_rawDesc, + NumEnums: 0, + NumMessages: 19, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_ssh_v1_ssh_chunks_proto_goTypes, + DependencyIndexes: file_ssh_v1_ssh_chunks_proto_depIdxs, + MessageInfos: file_ssh_v1_ssh_chunks_proto_msgTypes, + }.Build() + File_ssh_v1_ssh_chunks_proto = out.File + file_ssh_v1_ssh_chunks_proto_rawDesc = nil + file_ssh_v1_ssh_chunks_proto_goTypes = nil + file_ssh_v1_ssh_chunks_proto_depIdxs = nil +} diff --git a/internal/bsr/proto/buf.yaml b/internal/bsr/proto/buf.yaml new file mode 100644 index 000000000..57bd73975 --- /dev/null +++ b/internal/bsr/proto/buf.yaml @@ -0,0 +1,7 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +version: v1 +lint: + use: + - DEFAULT \ No newline at end of file diff --git a/internal/bsr/proto/ssh/v1/ssh_chunks.proto b/internal/bsr/proto/ssh/v1/ssh_chunks.proto new file mode 100644 index 000000000..d94c88d43 --- /dev/null +++ b/internal/bsr/proto/ssh/v1/ssh_chunks.proto @@ -0,0 +1,368 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +syntax = "proto3"; + +package ssh.v1; + +// An UnknownRequest is an unrecognized SSH request. We'll record the type, want_reply, and payload +message UnknownRequest { + // The request type field + string request_type = 1; + + // The want_reply field on the SSH request + bool want_reply = 2; + + // The Payload field on the SSH request + bytes data = 3; +} + +// Fields for the following SSH request protos were taken from +// https://www.iana.org/assignments/ssh-parameters/ssh-parameters.xhtml#ssh-parameters-11 + +// A session is started by sending the following message. +message SessionRequest { + // The request type field. This should always be "session" + string request_type = 1; + + // The sender channel + uint32 sender_channel = 2; + + // The initial window size + uint32 initial_window_size = 3; + + // The maximum packet size + uint32 maximum_packet_size = 4; +} + +// X11 channels are opened with a channel open request. The resulting +// channels are independent of the session, and closing the session +// channel does not close the forwarded X11 channels. +message X11Request { + // The request type field. This should always be "x11" + string request_type = 1; + + // The sender channel + uint32 sender_channel = 2; + + // The initial window size + uint32 initial_window_size = 3; + + // The maximum packet size + uint32 maximum_packet_size = 4; + + // The originator address (e.g., "192.168.7.38") + string originator_address = 5; + + // The originator port + uint32 originator_port = 6; +} + +// When a connection comes to a port for which remote forwarding has +// been requested, a channel is opened to forward the port to the other +// side. +message ForwardedTCPIPRequest { + // The request type field. This should always be "forwarded-tcpip" + string request_type = 1; + + // The sender channel + uint32 sender_channel = 2; + + // The initial window size + uint32 initial_window_size = 3; + + // The maximum packet size + uint32 maximum_packet_size = 4; + + // The address that was connected + string address = 5; + + // The port that was connected + uint32 port = 6; + + // The originator ip address (e.g., "192.168.7.38") + string originator_ip_address = 7; + + // The originator port + uint32 originator_port = 8; +} + +// When a connection comes to a locally forwarded TCP/IP port, the +// following packet is sent to the other side. +message DirectTCPIPRequest { + // The request type field. This should always be "direct-tcpip" + string request_type = 1; + + // The sender channel + uint32 sender_channel = 2; + + // The initial window size + uint32 initial_window_size = 3; + + // The maximum packet size + uint32 maximum_packet_size = 4; + + // The host to connect + string host = 5; + + // The port to connect + uint32 port = 6; + + // The 'originator IP address' is the numeric IP address of the machine + // from where the connection request originates + string originator_ip_address = 7; + + // The port on the host from where the connection originated. + uint32 originator_port = 8; +} + +// Fields for the following SSH request protos were taken from +// https://www.iana.org/assignments/ssh-parameters/ssh-parameters.xhtml#ssh-parameters-13 + +// A pseudo-terminal can be allocated for the session by sending the +// following message. +message PtyRequest { + // The request type field. This should always be "pty-req" + string request_type = 1; + + // The want_reply field on the SSH request + bool want_reply = 2; + + // TERM environment variable value (e.g., vt100) + string term_env_var = 3; + + // Terminal width, characters (e.g., 80) + uint32 terminal_width_characters = 4; + + // Terminal height, rows (e.g., 24) + uint32 terminal_height_rows = 5; + + // Terminal width, pixels (e.g., 640) + uint32 terminal_width_pixels = 6; + + // Terminal height, pixels (e.g., 480) + uint32 terminal_height_pixels = 7; + + // Encoded terminal modes + bytes encoded_terminal_mode = 8; +} + +// X11 forwarding may be requested for a session using the following message +message X11ForwardingRequest { + // The request type field. This should always be "x11-req" + string request_type = 1; + + // The want_reply field on the SSH request + bool want_reply = 2; + + // Single connection + bool single_connection = 3; + + // X11 authentication protocol + string x11_authentication_protocol = 4; + + // X11 authentication cookie + string x11_authentication_cookie = 5; + + // X11 screen number + uint32 x11_screen_number = 6; +} + +// Environment variables may be passed to the shell/command to be +// started later. +message EnvRequest { + // The request type field. This should always be "env" + string request_type = 1; + + // The want_reply field on the SSH request + bool want_reply = 2; + + // Variable name + string variable_name = 3; + + // Variable value + string variable_value = 4; +} + +// This message will request that the user's default shell (typically +// defined in /etc/passwd in UNIX systems) be started at the other end. +message ShellRequest { + // The request type field. This should always be "shell" + string request_type = 1; + + // The want_reply field on the SSH request + bool want_reply = 2; +} + +// This message will request that the server start the execution of the +// given command. The 'command' string may contain a path. +message ExecRequest { + // The request type field. This should always be "exec" + string request_type = 1; + + // The want_reply field on the SSH request + bool want_reply = 2; + + // The command to be executed + string command = 3; +} + +// Execute a predefined subsystem. It is expected that +// these will include a general file transfer mechanism, and possibly +// other features. +message SubsystemRequest { + // The request type field. This should always be "subsystem" + string request_type = 1; + + // The want_reply field on the SSH request + bool want_reply = 2; + + // The predefined subsystem to execute + string subsystem_name = 3; +} + +// When the window (terminal) size changes on the client side, it MAY +// send a message to the other side to inform it of the new dimensions. +message WindowChangeRequest { + // The request type field. This should always be "window-change" + string request_type = 1; + + // The want_reply field on the SSH request. This should always be false. + bool want_reply = 2; + + // Terminal width, columns + uint32 terminal_width_columns = 3; + + // Terminal height, rows + uint32 terminal_height_rows = 4; + + // Terminal width, pixels + uint32 terminal_width_pixels = 5; + + // Terminal height, pixels + uint32 terminal_height_pixels = 6; +} + +// The message below is used by the server to inform the client when it +// can or cannot perform flow control (control-S/control-Q processing). +// If 'client can do' is TRUE, the client is allowed to do flow control +// using control-S and control-Q +message XonXoffRequest { + // The request type field. This should always be "xon-xoff" + string request_type = 1; + + // The want_reply field on the SSH request. This should always be false. + bool want_reply = 2; + + // Client can do flow control + bool client_can_do = 3; +} + +// A signal can be delivered to the remote process/service using the +// following message. +message SignalRequest { + // The request type field. This should always be "signal" + string request_type = 1; + + // The want_reply field on the SSH request. This should always be false + bool want_reply = 2; + + // Signal name, without the SIG prefix. The 'signal name' is one of the following (these are from [POSIX]). + // ABRT ALRM FPE HUP ILL INT KILL + // PIPE QUIT SEGV TERM USR1 USR2 + string signal_name = 3; +} + +// When the command running at the other end terminates, the following +// message can be sent to return the exit status of the command. +message ExitStatusRequest { + // The request type field. This should always be "exit-status" + string request_type = 1; + + // The want_reply field on the SSH request. This should always be false. + bool want_reply = 2; + + // Exit status of the command + uint32 exit_status = 3; +} + +// The remote command may terminate violently due to a signal. +// Such a condition can be indicated by the following message. A zero +// 'exit_status' usually means that the command terminated successfully. +message ExitSignalRequest { + // The request type field. This should always be "exit-signal" + string request_type = 1; + + // The want_reply field on the SSH request. This should always be false. + bool want_reply = 2; + + // Signal name, without the SIG prefix. The 'signal name' is one of the following (these are from [POSIX]). + // ABRT ALRM FPE HUP ILL INT KILL + // PIPE QUIT SEGV TERM USR1 USR2 + string signal_name = 3; + + // Core dumped + bool core_dumped = 4; + + // Error message in ISO-10646 UTF-8 encoding + string error_message = 5; + + // Language tag from https://www.rfc-editor.org/rfc/rfc3066 + string language_tag = 6; +} + +// The following channel-specific request can be sent over a session +// channel to request that the remote host perform +// a BREAK operation. +message BreakRequest { + // The request type field. This should always be "break" + string request_type = 1; + + // The want_reply field on the SSH request. This should always be false. + bool want_reply = 2; + + // Break-length in milliseconds + uint32 break_length_ms = 3; +} + +// Fields for the following SSH request protos were taken from +// https://www.iana.org/assignments/ssh-parameters/ssh-parameters.xhtml#ssh-parameters-12 + +// A party need not explicitly request forwardings from its own end to +// the other direction. However, if it wishes that connections to a +// port on the other side be forwarded to the local side, it must +// explicitly request this. +message TCPIPForwardRequest { + // The request type field. This should always be "tcpip-forward" + string request_type = 1; + + // The want_reply field on the SSH request. This should always be false. + bool want_reply = 2; + + // Address to bind (e.g., "0.0.0.0") on which connections for forwarding + // are to be accepted + string address_to_bind = 3; + + // Port number to bind on which connections for forwarding + // are to be accepted + uint32 port_to_bind = 4; +} + +// A port forwarding can be canceled with the following message. Note +// that channel open requests may be received until a reply to this +// message is received. +message CancelTCPIPForwardRequest { + // The request type field. This should always be "cancel-tcpip-forward" + string request_type = 1; + + // The want_reply field on the SSH request. This should always be false. + bool want_reply = 2; + + // Address to bind (e.g., "0.0.0.0") on which connections for forwarding + // are to be accepted + string address_to_bind = 3; + + // Port number to bind on which connections for forwarding + // are to be accepted + uint32 port_to_bind = 4; +} diff --git a/internal/bsr/ssh/chunk.go b/internal/bsr/ssh/chunk.go new file mode 100644 index 000000000..21896c841 --- /dev/null +++ b/internal/bsr/ssh/chunk.go @@ -0,0 +1,65 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package ssh + +import ( + "context" + "fmt" + + "github.com/hashicorp/boundary/internal/bsr" +) + +const ( + Protocol bsr.Protocol = "SSH\x87" + + MaxPacketSize = 256 * 1024 + + // Chunk types + DataChunkType bsr.ChunkType = "DATA" + BreakReqChunkType bsr.ChunkType = "BREK" + CancelTCPIPForwardReqChunkType bsr.ChunkType = "CTPF" + DirectTCPIPReqChunkType bsr.ChunkType = "DTCP" + EnvReqChunkType bsr.ChunkType = "ENVR" + ExecReqChunkType bsr.ChunkType = "EXEC" + ExitSignalReqChunkType bsr.ChunkType = "EXSG" + ExitStatusReqChunkType bsr.ChunkType = "EXST" + ForwardedTCPIPReqChunkType bsr.ChunkType = "FTCP" + PtyReqChunkType bsr.ChunkType = "PTYR" + SessionReqChunkType bsr.ChunkType = "SESS" + ShellReqChunkType bsr.ChunkType = "SHLL" + SignalReqChunkType bsr.ChunkType = "SGNL" + SubsystemReqChunkType bsr.ChunkType = "SUBS" + TCPIPForwardReqChunkType bsr.ChunkType = "TCPF" + UnknownReqChunkType bsr.ChunkType = "UNKR" + WindowChangeReqChunkType bsr.ChunkType = "WCHG" + X11ForwardingReqChunkType bsr.ChunkType = "X11F" + X11ReqChunkType bsr.ChunkType = "X11R" + XonXoffReqChunkType bsr.ChunkType = "XOXO" +) + +// DataChunk contains the raw byte data from an SSH session +type DataChunk struct { + bsr.BaseChunk + Data []byte +} + +// NewDataChunk constructs a DataChunk +func NewDataChunk(ctx context.Context, d bsr.Direction, t *bsr.Timestamp, data []byte) (*DataChunk, error) { + const op = "ssh.NewDataChunk" + + baseChunk, err := bsr.NewBaseChunk(ctx, Protocol, d, t, DataChunkType) + if err != nil { + return nil, fmt.Errorf("%s: unable to create base chunk: %w", op, err) + } + + return &DataChunk{ + BaseChunk: *baseChunk, + Data: data, + }, nil +} + +// MarshalData returns the data for a DataChunk +func (c *DataChunk) MarshalData(_ context.Context) ([]byte, error) { + return c.Data, nil +} diff --git a/internal/bsr/ssh/chunk_break_request.go b/internal/bsr/ssh/chunk_break_request.go new file mode 100644 index 000000000..2bf2ec63b --- /dev/null +++ b/internal/bsr/ssh/chunk_break_request.go @@ -0,0 +1,78 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package ssh + +import ( + "context" + "fmt" + + "github.com/hashicorp/boundary/internal/bsr" + pssh "github.com/hashicorp/boundary/internal/bsr/gen/ssh/v1" + "github.com/hashicorp/boundary/internal/bsr/internal/is" + gssh "golang.org/x/crypto/ssh" + "google.golang.org/protobuf/proto" +) + +const BreakRequestType = "break" + +// BreakRequest is a chunk to contain data for an SSH Break request +type BreakRequest struct { + bsr.BaseChunk + *pssh.BreakRequest +} + +// MarshalData serializes a BreakRequest chunk +func (r *BreakRequest) MarshalData(ctx context.Context) ([]byte, error) { + const op = "ssh.(BreakRequest).MarshalData" + dataBytes, err := proto.Marshal(r.BreakRequest) + if err != nil { + return nil, fmt.Errorf("%s: failed to marshal data: %w", op, err) + } + + d := make([]byte, 0, len(dataBytes)) + d = append(d, dataBytes...) + + return d, nil +} + +// NewBreakRequest creates a BreakRequest chunk +func NewBreakRequest(ctx context.Context, d bsr.Direction, t *bsr.Timestamp, r *gssh.Request) (*BreakRequest, error) { + const op = "ssh.NewBreakRequest" + + if is.Nil(r) { + return nil, fmt.Errorf("%s: request cannot be nil: %w", op, bsr.ErrInvalidParameter) + } + if r.Type != BreakRequestType { + return nil, fmt.Errorf("%s: request type must be %q: %w", op, BreakRequestType, bsr.ErrInvalidParameter) + } + if !bsr.ValidDirection(d) { + return nil, fmt.Errorf("%s: invalid direction: %w", op, bsr.ErrInvalidParameter) + } + if is.Nil(t) { + return nil, fmt.Errorf("%s: timestamp cannot be nil: %w", op, bsr.ErrInvalidParameter) + } + + baseChunk, err := bsr.NewBaseChunk(ctx, Protocol, d, t, BreakReqChunkType) + if err != nil { + return nil, fmt.Errorf("%s: unable to create base chunk: %w", op, err) + } + + var sigval breakSigval + if err := gssh.Unmarshal(r.Payload, &sigval); err != nil { + return nil, fmt.Errorf("%s: unable to unmarshal payload: %w", op, err) + } + reqData := &BreakRequest{ + BaseChunk: *baseChunk, + BreakRequest: &pssh.BreakRequest{ + RequestType: r.Type, + WantReply: r.WantReply, + BreakLengthMs: sigval.BreakLengthMs, + }, + } + return reqData, nil +} + +type breakSigval struct { + BreakLengthMs uint32 +} diff --git a/internal/bsr/ssh/chunk_break_request_test.go b/internal/bsr/ssh/chunk_break_request_test.go new file mode 100644 index 000000000..d84ee36bb --- /dev/null +++ b/internal/bsr/ssh/chunk_break_request_test.go @@ -0,0 +1,107 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package ssh + +import ( + "context" + "testing" + "time" + + "github.com/hashicorp/boundary/internal/bsr" + pssh "github.com/hashicorp/boundary/internal/bsr/gen/ssh/v1" + "github.com/stretchr/testify/require" + gssh "golang.org/x/crypto/ssh" + "google.golang.org/protobuf/proto" +) + +func Test_NewBreakRequest(t *testing.T) { + ctx := context.Background() + now := bsr.NewTimestamp(time.Now()) + message := breakSigval{BreakLengthMs: 123} + payload := gssh.Marshal(message) + + tests := []struct { + name string + direction bsr.Direction + time *bsr.Timestamp + request *gssh.Request + expErr bool + expErrMsg string + }{ + { + name: "nil request", + direction: bsr.Inbound, + time: now, + expErr: true, + expErrMsg: "ssh.NewBreakRequest: request cannot be nil: invalid parameter", + }, + { + name: "empty direction", + time: now, + request: &gssh.Request{ + Type: BreakRequestType, + WantReply: false, + Payload: payload, + }, + expErr: true, + expErrMsg: "ssh.NewBreakRequest: invalid direction: invalid parameter", + }, + { + name: "empty time", + direction: bsr.Inbound, + request: &gssh.Request{ + Type: BreakRequestType, + WantReply: false, + Payload: payload, + }, + expErr: true, + expErrMsg: "ssh.NewBreakRequest: timestamp cannot be nil: invalid parameter", + }, + { + name: "bad type", + direction: bsr.Inbound, + time: now, + request: &gssh.Request{ + Type: "muahaha", + WantReply: false, + Payload: payload, + }, + expErr: true, + expErrMsg: `ssh.NewBreakRequest: request type must be "break": invalid parameter`, + }, + { + name: "happy path", + direction: bsr.Inbound, + time: now, + request: &gssh.Request{ + Type: BreakRequestType, + WantReply: false, + Payload: payload, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + rc, err := NewBreakRequest(ctx, tt.direction, tt.time, tt.request) + if tt.expErr { + require.EqualError(t, err, tt.expErrMsg) + require.Nil(t, rc) + return + } + + require.NoError(t, err) + require.NotNil(t, rc) + + data, err := rc.MarshalData(ctx) + require.NoError(t, err) + dataPayload := &pssh.BreakRequest{} + err = proto.Unmarshal(data, dataPayload) + require.NoError(t, err) + require.Equal(t, dataPayload.BreakLengthMs, message.BreakLengthMs) + require.Equal(t, dataPayload.RequestType, BreakRequestType) + require.Equal(t, dataPayload.WantReply, false) + }) + } +} diff --git a/internal/bsr/ssh/chunk_cancel_tcpip_forward_request.go b/internal/bsr/ssh/chunk_cancel_tcpip_forward_request.go new file mode 100644 index 000000000..048c44b47 --- /dev/null +++ b/internal/bsr/ssh/chunk_cancel_tcpip_forward_request.go @@ -0,0 +1,80 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package ssh + +import ( + "context" + "fmt" + + "github.com/hashicorp/boundary/internal/bsr" + pssh "github.com/hashicorp/boundary/internal/bsr/gen/ssh/v1" + "github.com/hashicorp/boundary/internal/bsr/internal/is" + gssh "golang.org/x/crypto/ssh" + "google.golang.org/protobuf/proto" +) + +const CancelTCPIPForwardRequestType = "cancel-tcpip-forward" + +// CancelTCPIPForwardRequest is a chunk to contain data for an SSH Cancel TCIPIP Forward request +type CancelTCPIPForwardRequest struct { + bsr.BaseChunk + *pssh.CancelTCPIPForwardRequest +} + +// MarshalData serializes a CancelTCPIPForwardRequest chunk +func (r *CancelTCPIPForwardRequest) MarshalData(ctx context.Context) ([]byte, error) { + const op = "ssh.(CancelTCPIPForwardRequest).MarshalData" + dataBytes, err := proto.Marshal(r.CancelTCPIPForwardRequest) + if err != nil { + return nil, fmt.Errorf("%s: failed to marshal data: %w", op, err) + } + + d := make([]byte, 0, len(dataBytes)) + d = append(d, dataBytes...) + + return d, nil +} + +// NewCancelTCPIPForwardRequest creates a CancelTCPIPForwardRequest chunk +func NewCancelTCPIPForwardRequest(ctx context.Context, d bsr.Direction, t *bsr.Timestamp, r *gssh.Request) (*CancelTCPIPForwardRequest, error) { + const op = "ssh.NewCancelTCPIPForwardRequest" + + if is.Nil(r) { + return nil, fmt.Errorf("%s: request cannot be nil: %w", op, bsr.ErrInvalidParameter) + } + if r.Type != CancelTCPIPForwardRequestType { + return nil, fmt.Errorf("%s: request type must be %q: %w", op, CancelTCPIPForwardRequestType, bsr.ErrInvalidParameter) + } + if !bsr.ValidDirection(d) { + return nil, fmt.Errorf("%s: invalid direction: %w", op, bsr.ErrInvalidParameter) + } + if is.Nil(t) { + return nil, fmt.Errorf("%s: timestamp cannot be nil: %w", op, bsr.ErrInvalidParameter) + } + + baseChunk, err := bsr.NewBaseChunk(ctx, Protocol, d, t, CancelTCPIPForwardReqChunkType) + if err != nil { + return nil, fmt.Errorf("%s: unable to create base chunk: %w", op, err) + } + + var sigval cancelTCPIPForwardSigval + if err := gssh.Unmarshal(r.Payload, &sigval); err != nil { + return nil, fmt.Errorf("%s: unable to unmarshal payload: %w", op, err) + } + reqData := &CancelTCPIPForwardRequest{ + BaseChunk: *baseChunk, + CancelTCPIPForwardRequest: &pssh.CancelTCPIPForwardRequest{ + RequestType: r.Type, + WantReply: r.WantReply, + AddressToBind: sigval.AddressToBind, + PortToBind: sigval.PortToBind, + }, + } + return reqData, nil +} + +type cancelTCPIPForwardSigval struct { + AddressToBind string + PortToBind uint32 +} diff --git a/internal/bsr/ssh/chunk_cancel_tcpip_forward_request_test.go b/internal/bsr/ssh/chunk_cancel_tcpip_forward_request_test.go new file mode 100644 index 000000000..5a059b06d --- /dev/null +++ b/internal/bsr/ssh/chunk_cancel_tcpip_forward_request_test.go @@ -0,0 +1,108 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package ssh + +import ( + "context" + "testing" + "time" + + "github.com/hashicorp/boundary/internal/bsr" + pssh "github.com/hashicorp/boundary/internal/bsr/gen/ssh/v1" + "github.com/stretchr/testify/require" + gssh "golang.org/x/crypto/ssh" + "google.golang.org/protobuf/proto" +) + +func Test_NewCancelTCPIPForwardRequest(t *testing.T) { + ctx := context.Background() + now := bsr.NewTimestamp(time.Now()) + message := cancelTCPIPForwardSigval{AddressToBind: "127.0.0.1", PortToBind: 123} + payload := gssh.Marshal(message) + + tests := []struct { + name string + direction bsr.Direction + time *bsr.Timestamp + request *gssh.Request + expErr bool + expErrMsg string + }{ + { + name: "nil request", + direction: bsr.Inbound, + time: now, + expErr: true, + expErrMsg: "ssh.NewCancelTCPIPForwardRequest: request cannot be nil: invalid parameter", + }, + { + name: "empty direction", + time: now, + request: &gssh.Request{ + Type: CancelTCPIPForwardRequestType, + WantReply: false, + Payload: payload, + }, + expErr: true, + expErrMsg: "ssh.NewCancelTCPIPForwardRequest: invalid direction: invalid parameter", + }, + { + name: "empty time", + direction: bsr.Inbound, + request: &gssh.Request{ + Type: CancelTCPIPForwardRequestType, + WantReply: false, + Payload: payload, + }, + expErr: true, + expErrMsg: "ssh.NewCancelTCPIPForwardRequest: timestamp cannot be nil: invalid parameter", + }, + { + name: "bad type", + direction: bsr.Inbound, + time: now, + request: &gssh.Request{ + Type: "muahaha", + WantReply: false, + Payload: payload, + }, + expErr: true, + expErrMsg: `ssh.NewCancelTCPIPForwardRequest: request type must be "cancel-tcpip-forward": invalid parameter`, + }, + { + name: "happy path", + direction: bsr.Inbound, + time: now, + request: &gssh.Request{ + Type: CancelTCPIPForwardRequestType, + WantReply: false, + Payload: payload, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + rc, err := NewCancelTCPIPForwardRequest(ctx, tt.direction, tt.time, tt.request) + if tt.expErr { + require.EqualError(t, err, tt.expErrMsg) + require.Nil(t, rc) + return + } + + require.NoError(t, err) + require.NotNil(t, rc) + + data, err := rc.MarshalData(ctx) + require.NoError(t, err) + dataPayload := &pssh.CancelTCPIPForwardRequest{} + err = proto.Unmarshal(data, dataPayload) + require.NoError(t, err) + require.Equal(t, dataPayload.AddressToBind, message.AddressToBind) + require.Equal(t, dataPayload.PortToBind, message.PortToBind) + require.Equal(t, dataPayload.RequestType, CancelTCPIPForwardRequestType) + require.Equal(t, dataPayload.WantReply, false) + }) + } +} diff --git a/internal/bsr/ssh/chunk_direct_tcpip_request.go b/internal/bsr/ssh/chunk_direct_tcpip_request.go new file mode 100644 index 000000000..a5f0d37d9 --- /dev/null +++ b/internal/bsr/ssh/chunk_direct_tcpip_request.go @@ -0,0 +1,89 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package ssh + +import ( + "context" + "fmt" + + "github.com/hashicorp/boundary/internal/bsr" + pssh "github.com/hashicorp/boundary/internal/bsr/gen/ssh/v1" + "github.com/hashicorp/boundary/internal/bsr/internal/is" + gssh "golang.org/x/crypto/ssh" + "google.golang.org/protobuf/proto" +) + +const DirectTCPIPRequestType = "direct-tcpip" + +// DirectTCPIPRequest is a chunk to contain data for an SSH Direct TCPIP request +type DirectTCPIPRequest struct { + bsr.BaseChunk + *pssh.DirectTCPIPRequest +} + +// MarshalData serializes a DirectTCPIPRequest chunk +func (r *DirectTCPIPRequest) MarshalData(ctx context.Context) ([]byte, error) { + const op = "ssh.(DirectTCPIPRequest).MarshalData" + dataBytes, err := proto.Marshal(r.DirectTCPIPRequest) + if err != nil { + return nil, fmt.Errorf("%s: failed to marshal data: %w", op, err) + } + + d := make([]byte, 0, len(dataBytes)) + d = append(d, dataBytes...) + + return d, nil +} + +// NewDirectTCPIPRequest creates a DirectTCPIPRequest chunk +func NewDirectTCPIPRequest(ctx context.Context, d bsr.Direction, t *bsr.Timestamp, r *gssh.Request) (*DirectTCPIPRequest, error) { + const op = "ssh.NewDirectTCPIPRequest" + + if is.Nil(r) { + return nil, fmt.Errorf("%s: request cannot be nil: %w", op, bsr.ErrInvalidParameter) + } + if r.Type != DirectTCPIPRequestType { + return nil, fmt.Errorf("%s: request type must be %q: %w", op, DirectTCPIPRequestType, bsr.ErrInvalidParameter) + } + if !bsr.ValidDirection(d) { + return nil, fmt.Errorf("%s: invalid direction: %w", op, bsr.ErrInvalidParameter) + } + if is.Nil(t) { + return nil, fmt.Errorf("%s: timestamp cannot be nil: %w", op, bsr.ErrInvalidParameter) + } + + baseChunk, err := bsr.NewBaseChunk(ctx, Protocol, d, t, DirectTCPIPReqChunkType) + if err != nil { + return nil, fmt.Errorf("%s: unable to create base chunk: %w", op, err) + } + + var sigval directTCPIPSigval + if err := gssh.Unmarshal(r.Payload, &sigval); err != nil { + return nil, fmt.Errorf("%s: unable to unmarshal payload: %w", op, err) + } + reqData := &DirectTCPIPRequest{ + BaseChunk: *baseChunk, + DirectTCPIPRequest: &pssh.DirectTCPIPRequest{ + RequestType: r.Type, + SenderChannel: sigval.SenderChannel, + InitialWindowSize: sigval.InitialWindowSize, + MaximumPacketSize: sigval.MaximumPacketSize, + Host: sigval.Host, + Port: sigval.Port, + OriginatorIpAddress: sigval.OriginatorIpAddress, + OriginatorPort: sigval.OriginatorPort, + }, + } + return reqData, nil +} + +type directTCPIPSigval struct { + SenderChannel uint32 + InitialWindowSize uint32 + MaximumPacketSize uint32 + Host string + Port uint32 + OriginatorIpAddress string + OriginatorPort uint32 +} diff --git a/internal/bsr/ssh/chunk_direct_tcpip_request_test.go b/internal/bsr/ssh/chunk_direct_tcpip_request_test.go new file mode 100644 index 000000000..d0aa4576b --- /dev/null +++ b/internal/bsr/ssh/chunk_direct_tcpip_request_test.go @@ -0,0 +1,120 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package ssh + +import ( + "context" + "testing" + "time" + + "github.com/hashicorp/boundary/internal/bsr" + pssh "github.com/hashicorp/boundary/internal/bsr/gen/ssh/v1" + "github.com/stretchr/testify/require" + gssh "golang.org/x/crypto/ssh" + "google.golang.org/protobuf/proto" +) + +func Test_NewDirectTCPIPRequest(t *testing.T) { + ctx := context.Background() + now := bsr.NewTimestamp(time.Now()) + message := directTCPIPSigval{ + SenderChannel: 123, + InitialWindowSize: 500, + MaximumPacketSize: 456, + Host: "hostarooni", + Port: 789, + OriginatorIpAddress: "original", + OriginatorPort: 99, + } + payload := gssh.Marshal(message) + + tests := []struct { + name string + direction bsr.Direction + time *bsr.Timestamp + request *gssh.Request + expErr bool + expErrMsg string + }{ + { + name: "nil request", + direction: bsr.Inbound, + time: now, + expErr: true, + expErrMsg: "ssh.NewDirectTCPIPRequest: request cannot be nil: invalid parameter", + }, + { + name: "empty direction", + time: now, + request: &gssh.Request{ + Type: DirectTCPIPRequestType, + WantReply: false, + Payload: payload, + }, + expErr: true, + expErrMsg: "ssh.NewDirectTCPIPRequest: invalid direction: invalid parameter", + }, + { + name: "empty time", + direction: bsr.Inbound, + request: &gssh.Request{ + Type: DirectTCPIPRequestType, + WantReply: false, + Payload: payload, + }, + expErr: true, + expErrMsg: "ssh.NewDirectTCPIPRequest: timestamp cannot be nil: invalid parameter", + }, + { + name: "bad type", + direction: bsr.Inbound, + time: now, + request: &gssh.Request{ + Type: "muahaha", + WantReply: false, + Payload: payload, + }, + expErr: true, + expErrMsg: `ssh.NewDirectTCPIPRequest: request type must be "direct-tcpip": invalid parameter`, + }, + { + name: "happy path", + direction: bsr.Inbound, + time: now, + request: &gssh.Request{ + Type: DirectTCPIPRequestType, + WantReply: false, + Payload: payload, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + rc, err := NewDirectTCPIPRequest(ctx, tt.direction, tt.time, tt.request) + if tt.expErr { + require.EqualError(t, err, tt.expErrMsg) + require.Nil(t, rc) + return + } + + require.NoError(t, err) + require.NotNil(t, rc) + + data, err := rc.MarshalData(ctx) + require.NoError(t, err) + dataPayload := &pssh.DirectTCPIPRequest{} + err = proto.Unmarshal(data, dataPayload) + require.NoError(t, err) + require.Equal(t, dataPayload.SenderChannel, message.SenderChannel) + require.Equal(t, dataPayload.InitialWindowSize, message.InitialWindowSize) + require.Equal(t, dataPayload.MaximumPacketSize, message.MaximumPacketSize) + require.Equal(t, dataPayload.Host, message.Host) + require.Equal(t, dataPayload.Port, message.Port) + require.Equal(t, dataPayload.OriginatorPort, message.OriginatorPort) + require.Equal(t, dataPayload.OriginatorIpAddress, message.OriginatorIpAddress) + require.Equal(t, dataPayload.RequestType, DirectTCPIPRequestType) + }) + } +} diff --git a/internal/bsr/ssh/chunk_env_request.go b/internal/bsr/ssh/chunk_env_request.go new file mode 100644 index 000000000..7e1c8f1d3 --- /dev/null +++ b/internal/bsr/ssh/chunk_env_request.go @@ -0,0 +1,80 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package ssh + +import ( + "context" + "fmt" + + "github.com/hashicorp/boundary/internal/bsr" + pssh "github.com/hashicorp/boundary/internal/bsr/gen/ssh/v1" + "github.com/hashicorp/boundary/internal/bsr/internal/is" + gssh "golang.org/x/crypto/ssh" + "google.golang.org/protobuf/proto" +) + +const EnvRequestType = "env" + +// EnvRequest is a chunk to contain data for an SSH Env request +type EnvRequest struct { + bsr.BaseChunk + *pssh.EnvRequest +} + +// MarshalData serializes an EnvRequest chunk +func (r *EnvRequest) MarshalData(ctx context.Context) ([]byte, error) { + const op = "ssh.(EnvRequest).MarshalData" + dataBytes, err := proto.Marshal(r.EnvRequest) + if err != nil { + return nil, fmt.Errorf("%s: failed to marshal data: %w", op, err) + } + + d := make([]byte, 0, len(dataBytes)) + d = append(d, dataBytes...) + + return d, nil +} + +// NewEnvRequest creates an EnvRequest chunk +func NewEnvRequest(ctx context.Context, d bsr.Direction, t *bsr.Timestamp, r *gssh.Request) (*EnvRequest, error) { + const op = "ssh.NewEnvRequest" + + if is.Nil(r) { + return nil, fmt.Errorf("%s: request cannot be nil: %w", op, bsr.ErrInvalidParameter) + } + if r.Type != EnvRequestType { + return nil, fmt.Errorf("%s: request type must be %q: %w", op, EnvRequestType, bsr.ErrInvalidParameter) + } + if !bsr.ValidDirection(d) { + return nil, fmt.Errorf("%s: invalid direction: %w", op, bsr.ErrInvalidParameter) + } + if is.Nil(t) { + return nil, fmt.Errorf("%s: timestamp cannot be nil: %w", op, bsr.ErrInvalidParameter) + } + + baseChunk, err := bsr.NewBaseChunk(ctx, Protocol, d, t, EnvReqChunkType) + if err != nil { + return nil, fmt.Errorf("%s: unable to create base chunk: %w", op, err) + } + + var sigval envSigval + if err := gssh.Unmarshal(r.Payload, &sigval); err != nil { + return nil, fmt.Errorf("%s: unable to unmarshal payload: %w", op, err) + } + reqData := &EnvRequest{ + BaseChunk: *baseChunk, + EnvRequest: &pssh.EnvRequest{ + RequestType: r.Type, + WantReply: r.WantReply, + VariableName: sigval.Name, + VariableValue: sigval.Value, + }, + } + return reqData, nil +} + +type envSigval struct { + Name string + Value string +} diff --git a/internal/bsr/ssh/chunk_env_request_test.go b/internal/bsr/ssh/chunk_env_request_test.go new file mode 100644 index 000000000..c31ca96fa --- /dev/null +++ b/internal/bsr/ssh/chunk_env_request_test.go @@ -0,0 +1,108 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package ssh + +import ( + "context" + "testing" + "time" + + "github.com/hashicorp/boundary/internal/bsr" + pssh "github.com/hashicorp/boundary/internal/bsr/gen/ssh/v1" + "github.com/stretchr/testify/require" + gssh "golang.org/x/crypto/ssh" + "google.golang.org/protobuf/proto" +) + +func Test_NewEnvRequest(t *testing.T) { + ctx := context.Background() + now := bsr.NewTimestamp(time.Now()) + message := envSigval{Name: "my baloney has a first name", Value: "it's h-o-m-e-r"} + payload := gssh.Marshal(message) + + tests := []struct { + name string + direction bsr.Direction + time *bsr.Timestamp + request *gssh.Request + expErr bool + expErrMsg string + }{ + { + name: "nil request", + direction: bsr.Inbound, + time: now, + expErr: true, + expErrMsg: "ssh.NewEnvRequest: request cannot be nil: invalid parameter", + }, + { + name: "empty direction", + time: now, + request: &gssh.Request{ + Type: EnvRequestType, + WantReply: false, + Payload: payload, + }, + expErr: true, + expErrMsg: "ssh.NewEnvRequest: invalid direction: invalid parameter", + }, + { + name: "empty time", + direction: bsr.Inbound, + request: &gssh.Request{ + Type: EnvRequestType, + WantReply: false, + Payload: payload, + }, + expErr: true, + expErrMsg: "ssh.NewEnvRequest: timestamp cannot be nil: invalid parameter", + }, + { + name: "bad type", + direction: bsr.Inbound, + time: now, + request: &gssh.Request{ + Type: "muahaha", + WantReply: false, + Payload: payload, + }, + expErr: true, + expErrMsg: `ssh.NewEnvRequest: request type must be "env": invalid parameter`, + }, + { + name: "happy path", + direction: bsr.Inbound, + time: now, + request: &gssh.Request{ + Type: EnvRequestType, + WantReply: false, + Payload: payload, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + rc, err := NewEnvRequest(ctx, tt.direction, tt.time, tt.request) + if tt.expErr { + require.EqualError(t, err, tt.expErrMsg) + require.Nil(t, rc) + return + } + + require.NoError(t, err) + require.NotNil(t, rc) + + data, err := rc.MarshalData(ctx) + require.NoError(t, err) + dataPayload := &pssh.EnvRequest{} + err = proto.Unmarshal(data, dataPayload) + require.NoError(t, err) + require.Equal(t, dataPayload.VariableName, message.Name) + require.Equal(t, dataPayload.VariableValue, message.Value) + require.Equal(t, dataPayload.RequestType, EnvRequestType) + require.Equal(t, dataPayload.WantReply, false) + }) + } +} diff --git a/internal/bsr/ssh/chunk_exec_request.go b/internal/bsr/ssh/chunk_exec_request.go new file mode 100644 index 000000000..419ce4131 --- /dev/null +++ b/internal/bsr/ssh/chunk_exec_request.go @@ -0,0 +1,78 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package ssh + +import ( + "context" + "fmt" + + "github.com/hashicorp/boundary/internal/bsr" + pssh "github.com/hashicorp/boundary/internal/bsr/gen/ssh/v1" + "github.com/hashicorp/boundary/internal/bsr/internal/is" + gssh "golang.org/x/crypto/ssh" + "google.golang.org/protobuf/proto" +) + +const ExecRequestType = "exec" + +// ExecRequest is a chunk to contain data for an SSH Exec request +type ExecRequest struct { + bsr.BaseChunk + *pssh.ExecRequest +} + +// MarshalData serializes an ExecRequest chunk +func (r *ExecRequest) MarshalData(ctx context.Context) ([]byte, error) { + const op = "ssh.(ExecRequest).MarshalData" + dataBytes, err := proto.Marshal(r.ExecRequest) + if err != nil { + return nil, fmt.Errorf("%s: failed to marshal data: %w", op, err) + } + + d := make([]byte, 0, len(dataBytes)) + d = append(d, dataBytes...) + + return d, nil +} + +// NewExecRequest creates an ExecRequest chunk +func NewExecRequest(ctx context.Context, d bsr.Direction, t *bsr.Timestamp, r *gssh.Request) (*ExecRequest, error) { + const op = "ssh.NewExecRequest" + + if is.Nil(r) { + return nil, fmt.Errorf("%s: request cannot be nil: %w", op, bsr.ErrInvalidParameter) + } + if r.Type != ExecRequestType { + return nil, fmt.Errorf("%s: request type must be %q: %w", op, ExecRequestType, bsr.ErrInvalidParameter) + } + if !bsr.ValidDirection(d) { + return nil, fmt.Errorf("%s: invalid direction: %w", op, bsr.ErrInvalidParameter) + } + if is.Nil(t) { + return nil, fmt.Errorf("%s: timestamp cannot be nil: %w", op, bsr.ErrInvalidParameter) + } + + baseChunk, err := bsr.NewBaseChunk(ctx, Protocol, d, t, ExecReqChunkType) + if err != nil { + return nil, fmt.Errorf("%s: unable to create base chunk: %w", op, err) + } + + var sigval execSigval + if err := gssh.Unmarshal(r.Payload, &sigval); err != nil { + return nil, fmt.Errorf("%s: unable to unmarshal payload: %w", op, err) + } + reqData := &ExecRequest{ + BaseChunk: *baseChunk, + ExecRequest: &pssh.ExecRequest{ + RequestType: r.Type, + WantReply: r.WantReply, + Command: sigval.Command, + }, + } + return reqData, nil +} + +type execSigval struct { + Command string +} diff --git a/internal/bsr/ssh/chunk_exec_request_test.go b/internal/bsr/ssh/chunk_exec_request_test.go new file mode 100644 index 000000000..d0a2d33aa --- /dev/null +++ b/internal/bsr/ssh/chunk_exec_request_test.go @@ -0,0 +1,107 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package ssh + +import ( + "context" + "testing" + "time" + + "github.com/hashicorp/boundary/internal/bsr" + pssh "github.com/hashicorp/boundary/internal/bsr/gen/ssh/v1" + "github.com/stretchr/testify/require" + gssh "golang.org/x/crypto/ssh" + "google.golang.org/protobuf/proto" +) + +func Test_NewExecRequest(t *testing.T) { + ctx := context.Background() + now := bsr.NewTimestamp(time.Now()) + message := execSigval{Command: "recycle!"} + payload := gssh.Marshal(message) + + tests := []struct { + name string + direction bsr.Direction + time *bsr.Timestamp + request *gssh.Request + expErr bool + expErrMsg string + }{ + { + name: "nil request", + direction: bsr.Inbound, + time: now, + expErr: true, + expErrMsg: "ssh.NewExecRequest: request cannot be nil: invalid parameter", + }, + { + name: "empty direction", + time: now, + request: &gssh.Request{ + Type: ExecRequestType, + WantReply: false, + Payload: payload, + }, + expErr: true, + expErrMsg: "ssh.NewExecRequest: invalid direction: invalid parameter", + }, + { + name: "empty time", + direction: bsr.Inbound, + request: &gssh.Request{ + Type: ExecRequestType, + WantReply: false, + Payload: payload, + }, + expErr: true, + expErrMsg: "ssh.NewExecRequest: timestamp cannot be nil: invalid parameter", + }, + { + name: "bad type", + direction: bsr.Inbound, + time: now, + request: &gssh.Request{ + Type: "muahaha", + WantReply: false, + Payload: payload, + }, + expErr: true, + expErrMsg: `ssh.NewExecRequest: request type must be "exec": invalid parameter`, + }, + { + name: "happy path", + direction: bsr.Inbound, + time: now, + request: &gssh.Request{ + Type: ExecRequestType, + WantReply: false, + Payload: payload, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + rc, err := NewExecRequest(ctx, tt.direction, tt.time, tt.request) + if tt.expErr { + require.EqualError(t, err, tt.expErrMsg) + require.Nil(t, rc) + return + } + + require.NoError(t, err) + require.NotNil(t, rc) + + data, err := rc.MarshalData(ctx) + require.NoError(t, err) + dataPayload := &pssh.ExecRequest{} + err = proto.Unmarshal(data, dataPayload) + require.NoError(t, err) + require.Equal(t, dataPayload.Command, message.Command) + require.Equal(t, dataPayload.RequestType, ExecRequestType) + require.Equal(t, dataPayload.WantReply, false) + }) + } +} diff --git a/internal/bsr/ssh/chunk_exit_signal_request.go b/internal/bsr/ssh/chunk_exit_signal_request.go new file mode 100644 index 000000000..88b72df48 --- /dev/null +++ b/internal/bsr/ssh/chunk_exit_signal_request.go @@ -0,0 +1,84 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package ssh + +import ( + "context" + "fmt" + + "github.com/hashicorp/boundary/internal/bsr" + pssh "github.com/hashicorp/boundary/internal/bsr/gen/ssh/v1" + "github.com/hashicorp/boundary/internal/bsr/internal/is" + gssh "golang.org/x/crypto/ssh" + "google.golang.org/protobuf/proto" +) + +const ExitSignalRequestType = "exit-signal" + +// ExitSignalRequest is a chunk to contain data for an SSH Exit Signal request +type ExitSignalRequest struct { + bsr.BaseChunk + *pssh.ExitSignalRequest +} + +// MarshalData serializes an ExitSignalRequest chunk +func (r *ExitSignalRequest) MarshalData(ctx context.Context) ([]byte, error) { + const op = "ssh.(ExitSignalRequest).MarshalData" + dataBytes, err := proto.Marshal(r.ExitSignalRequest) + if err != nil { + return nil, fmt.Errorf("%s: failed to marshal data: %w", op, err) + } + + d := make([]byte, 0, len(dataBytes)) + d = append(d, dataBytes...) + + return d, nil +} + +// NewExitSignalRequest creates an ExitSignalRequest chunk +func NewExitSignalRequest(ctx context.Context, d bsr.Direction, t *bsr.Timestamp, r *gssh.Request) (*ExitSignalRequest, error) { + const op = "ssh.NewExitSignalRequest" + + if is.Nil(r) { + return nil, fmt.Errorf("%s: request cannot be nil: %w", op, bsr.ErrInvalidParameter) + } + if r.Type != ExitSignalRequestType { + return nil, fmt.Errorf("%s: request type must be %q: %w", op, ExitSignalRequestType, bsr.ErrInvalidParameter) + } + if !bsr.ValidDirection(d) { + return nil, fmt.Errorf("%s: invalid direction: %w", op, bsr.ErrInvalidParameter) + } + if is.Nil(t) { + return nil, fmt.Errorf("%s: timestamp cannot be nil: %w", op, bsr.ErrInvalidParameter) + } + + baseChunk, err := bsr.NewBaseChunk(ctx, Protocol, d, t, ExitSignalReqChunkType) + if err != nil { + return nil, fmt.Errorf("%s: unable to create base chunk: %w", op, err) + } + + var sigval exitSignalSigval + if err := gssh.Unmarshal(r.Payload, &sigval); err != nil { + return nil, fmt.Errorf("%s: unable to unmarshal payload: %w", op, err) + } + reqData := &ExitSignalRequest{ + BaseChunk: *baseChunk, + ExitSignalRequest: &pssh.ExitSignalRequest{ + RequestType: r.Type, + WantReply: r.WantReply, + SignalName: sigval.SignalName, + CoreDumped: sigval.CoreDumped, + ErrorMessage: sigval.ErrorMessage, + LanguageTag: sigval.LanguageTag, + }, + } + return reqData, nil +} + +type exitSignalSigval struct { + SignalName string + CoreDumped bool + ErrorMessage string + LanguageTag string +} diff --git a/internal/bsr/ssh/chunk_exit_signal_request_test.go b/internal/bsr/ssh/chunk_exit_signal_request_test.go new file mode 100644 index 000000000..0029b65a2 --- /dev/null +++ b/internal/bsr/ssh/chunk_exit_signal_request_test.go @@ -0,0 +1,115 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package ssh + +import ( + "context" + "testing" + "time" + + "github.com/hashicorp/boundary/internal/bsr" + pssh "github.com/hashicorp/boundary/internal/bsr/gen/ssh/v1" + "github.com/stretchr/testify/require" + gssh "golang.org/x/crypto/ssh" + "google.golang.org/protobuf/proto" +) + +func Test_NewExitSignalRequest(t *testing.T) { + ctx := context.Background() + now := bsr.NewTimestamp(time.Now()) + message := exitSignalSigval{ + SignalName: "HALT", + CoreDumped: true, + ErrorMessage: "things did not go as expected", + LanguageTag: "EN", + } + payload := gssh.Marshal(message) + + tests := []struct { + name string + direction bsr.Direction + time *bsr.Timestamp + request *gssh.Request + expErr bool + expErrMsg string + }{ + { + name: "nil request", + direction: bsr.Inbound, + time: now, + expErr: true, + expErrMsg: "ssh.NewExitSignalRequest: request cannot be nil: invalid parameter", + }, + { + name: "empty direction", + time: now, + request: &gssh.Request{ + Type: ExitSignalRequestType, + WantReply: false, + Payload: payload, + }, + expErr: true, + expErrMsg: "ssh.NewExitSignalRequest: invalid direction: invalid parameter", + }, + { + name: "empty time", + direction: bsr.Inbound, + request: &gssh.Request{ + Type: ExitSignalRequestType, + WantReply: false, + Payload: payload, + }, + expErr: true, + expErrMsg: "ssh.NewExitSignalRequest: timestamp cannot be nil: invalid parameter", + }, + { + name: "bad type", + direction: bsr.Inbound, + time: now, + request: &gssh.Request{ + Type: "muahaha", + WantReply: false, + Payload: payload, + }, + expErr: true, + expErrMsg: `ssh.NewExitSignalRequest: request type must be "exit-signal": invalid parameter`, + }, + { + name: "happy path", + direction: bsr.Inbound, + time: now, + request: &gssh.Request{ + Type: ExitSignalRequestType, + WantReply: false, + Payload: payload, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + rc, err := NewExitSignalRequest(ctx, tt.direction, tt.time, tt.request) + if tt.expErr { + require.EqualError(t, err, tt.expErrMsg) + require.Nil(t, rc) + return + } + + require.NoError(t, err) + require.NotNil(t, rc) + + data, err := rc.MarshalData(ctx) + require.NoError(t, err) + dataPayload := &pssh.ExitSignalRequest{} + err = proto.Unmarshal(data, dataPayload) + require.NoError(t, err) + require.Equal(t, dataPayload.SignalName, message.SignalName) + require.Equal(t, dataPayload.CoreDumped, message.CoreDumped) + require.Equal(t, dataPayload.ErrorMessage, message.ErrorMessage) + require.Equal(t, dataPayload.LanguageTag, message.LanguageTag) + require.Equal(t, dataPayload.RequestType, ExitSignalRequestType) + require.Equal(t, dataPayload.WantReply, false) + }) + } +} diff --git a/internal/bsr/ssh/chunk_exit_status_request.go b/internal/bsr/ssh/chunk_exit_status_request.go new file mode 100644 index 000000000..9db2a33dd --- /dev/null +++ b/internal/bsr/ssh/chunk_exit_status_request.go @@ -0,0 +1,78 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package ssh + +import ( + "context" + "fmt" + + "github.com/hashicorp/boundary/internal/bsr" + pssh "github.com/hashicorp/boundary/internal/bsr/gen/ssh/v1" + "github.com/hashicorp/boundary/internal/bsr/internal/is" + gssh "golang.org/x/crypto/ssh" + "google.golang.org/protobuf/proto" +) + +const ExitStatusRequestType = "exit-status" + +// ExitStatusRequest is a chunk to contain data for an SSH Exit Status request +type ExitStatusRequest struct { + bsr.BaseChunk + *pssh.ExitStatusRequest +} + +// MarshalData serializes an ExitStatusRequest chunk +func (r *ExitStatusRequest) MarshalData(ctx context.Context) ([]byte, error) { + const op = "ssh.(ExitStatusRequest).MarshalData" + dataBytes, err := proto.Marshal(r.ExitStatusRequest) + if err != nil { + return nil, fmt.Errorf("%s: failed to marshal data: %w", op, err) + } + + d := make([]byte, 0, len(dataBytes)) + d = append(d, dataBytes...) + + return d, nil +} + +// NewExitSignalRequest creates an ExitSignalRequest chunk +func NewExitStatusRequest(ctx context.Context, d bsr.Direction, t *bsr.Timestamp, r *gssh.Request) (*ExitStatusRequest, error) { + const op = "ssh.NewExitStatusRequest" + + if is.Nil(r) { + return nil, fmt.Errorf("%s: request cannot be nil: %w", op, bsr.ErrInvalidParameter) + } + if r.Type != ExitStatusRequestType { + return nil, fmt.Errorf("%s: request type must be %q: %w", op, ExitStatusRequestType, bsr.ErrInvalidParameter) + } + if !bsr.ValidDirection(d) { + return nil, fmt.Errorf("%s: invalid direction: %w", op, bsr.ErrInvalidParameter) + } + if is.Nil(t) { + return nil, fmt.Errorf("%s: timestamp cannot be nil: %w", op, bsr.ErrInvalidParameter) + } + + baseChunk, err := bsr.NewBaseChunk(ctx, Protocol, d, t, ExitStatusReqChunkType) + if err != nil { + return nil, fmt.Errorf("%s: unable to create base chunk: %w", op, err) + } + + var sigval exitStatusSigval + if err := gssh.Unmarshal(r.Payload, &sigval); err != nil { + return nil, fmt.Errorf("%s: unable to unmarshal payload: %w", op, err) + } + reqData := &ExitStatusRequest{ + BaseChunk: *baseChunk, + ExitStatusRequest: &pssh.ExitStatusRequest{ + RequestType: r.Type, + WantReply: r.WantReply, + ExitStatus: sigval.ExitStatus, + }, + } + return reqData, nil +} + +type exitStatusSigval struct { + ExitStatus uint32 +} diff --git a/internal/bsr/ssh/chunk_exit_status_request_test.go b/internal/bsr/ssh/chunk_exit_status_request_test.go new file mode 100644 index 000000000..d9232eceb --- /dev/null +++ b/internal/bsr/ssh/chunk_exit_status_request_test.go @@ -0,0 +1,109 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package ssh + +import ( + "context" + "testing" + "time" + + "github.com/hashicorp/boundary/internal/bsr" + pssh "github.com/hashicorp/boundary/internal/bsr/gen/ssh/v1" + "github.com/stretchr/testify/require" + gssh "golang.org/x/crypto/ssh" + "google.golang.org/protobuf/proto" +) + +func Test_NewExitStatusRequest(t *testing.T) { + ctx := context.Background() + now := bsr.NewTimestamp(time.Now()) + message := exitStatusSigval{ + ExitStatus: 99, + } + payload := gssh.Marshal(message) + + tests := []struct { + name string + direction bsr.Direction + time *bsr.Timestamp + request *gssh.Request + expErr bool + expErrMsg string + }{ + { + name: "nil request", + direction: bsr.Inbound, + time: now, + expErr: true, + expErrMsg: "ssh.NewExitStatusRequest: request cannot be nil: invalid parameter", + }, + { + name: "empty direction", + time: now, + request: &gssh.Request{ + Type: ExitStatusRequestType, + WantReply: false, + Payload: payload, + }, + expErr: true, + expErrMsg: "ssh.NewExitStatusRequest: invalid direction: invalid parameter", + }, + { + name: "empty time", + direction: bsr.Inbound, + request: &gssh.Request{ + Type: ExitStatusRequestType, + WantReply: false, + Payload: payload, + }, + expErr: true, + expErrMsg: "ssh.NewExitStatusRequest: timestamp cannot be nil: invalid parameter", + }, + { + name: "bad type", + direction: bsr.Inbound, + time: now, + request: &gssh.Request{ + Type: "muahaha", + WantReply: false, + Payload: payload, + }, + expErr: true, + expErrMsg: `ssh.NewExitStatusRequest: request type must be "exit-status": invalid parameter`, + }, + { + name: "happy path", + direction: bsr.Inbound, + time: now, + request: &gssh.Request{ + Type: ExitStatusRequestType, + WantReply: false, + Payload: payload, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + rc, err := NewExitStatusRequest(ctx, tt.direction, tt.time, tt.request) + if tt.expErr { + require.EqualError(t, err, tt.expErrMsg) + require.Nil(t, rc) + return + } + + require.NoError(t, err) + require.NotNil(t, rc) + + data, err := rc.MarshalData(ctx) + require.NoError(t, err) + dataPayload := &pssh.ExitStatusRequest{} + err = proto.Unmarshal(data, dataPayload) + require.NoError(t, err) + require.Equal(t, dataPayload.ExitStatus, message.ExitStatus) + require.Equal(t, dataPayload.RequestType, ExitStatusRequestType) + require.Equal(t, dataPayload.WantReply, false) + }) + } +} diff --git a/internal/bsr/ssh/chunk_forwarded_tcpip_request.go b/internal/bsr/ssh/chunk_forwarded_tcpip_request.go new file mode 100644 index 000000000..f5d4eecab --- /dev/null +++ b/internal/bsr/ssh/chunk_forwarded_tcpip_request.go @@ -0,0 +1,89 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package ssh + +import ( + "context" + "fmt" + + "github.com/hashicorp/boundary/internal/bsr" + pssh "github.com/hashicorp/boundary/internal/bsr/gen/ssh/v1" + "github.com/hashicorp/boundary/internal/bsr/internal/is" + gssh "golang.org/x/crypto/ssh" + "google.golang.org/protobuf/proto" +) + +const ForwardedTCPIPRequestType = "forwarded-tcpip" + +// ForwardedTCPIPRequest is a chunk to contain data for an SSH Forwarded TCPIP request +type ForwardedTCPIPRequest struct { + bsr.BaseChunk + *pssh.ForwardedTCPIPRequest +} + +// MarshalData serializes an ForwardedTCPIPRequest chunk +func (r *ForwardedTCPIPRequest) MarshalData(ctx context.Context) ([]byte, error) { + const op = "ssh.(ForwardedTCPIPRequest).MarshalData" + dataBytes, err := proto.Marshal(r.ForwardedTCPIPRequest) + if err != nil { + return nil, fmt.Errorf("%s: failed to marshal data: %w", op, err) + } + + d := make([]byte, 0, len(dataBytes)) + d = append(d, dataBytes...) + + return d, nil +} + +// NewForwardedTCPIPRequest creates an ForwardedTCPIPRequest chunk +func NewForwardedTCPIPRequest(ctx context.Context, d bsr.Direction, t *bsr.Timestamp, r *gssh.Request) (*ForwardedTCPIPRequest, error) { + const op = "ssh.NewForwardedTCPIPRequest" + + if is.Nil(r) { + return nil, fmt.Errorf("%s: request cannot be nil: %w", op, bsr.ErrInvalidParameter) + } + if r.Type != ForwardedTCPIPRequestType { + return nil, fmt.Errorf("%s: request type must be %q: %w", op, ForwardedTCPIPRequestType, bsr.ErrInvalidParameter) + } + if !bsr.ValidDirection(d) { + return nil, fmt.Errorf("%s: invalid direction: %w", op, bsr.ErrInvalidParameter) + } + if is.Nil(t) { + return nil, fmt.Errorf("%s: timestamp cannot be nil: %w", op, bsr.ErrInvalidParameter) + } + + baseChunk, err := bsr.NewBaseChunk(ctx, Protocol, d, t, ForwardedTCPIPReqChunkType) + if err != nil { + return nil, fmt.Errorf("%s: unable to create base chunk: %w", op, err) + } + + var sigval forwardedTCPIPRequestSigval + if err := gssh.Unmarshal(r.Payload, &sigval); err != nil { + return nil, fmt.Errorf("%s: unable to unmarshal payload: %w", op, err) + } + reqData := &ForwardedTCPIPRequest{ + BaseChunk: *baseChunk, + ForwardedTCPIPRequest: &pssh.ForwardedTCPIPRequest{ + RequestType: r.Type, + SenderChannel: sigval.SenderChannel, + InitialWindowSize: sigval.InitialWindowSize, + MaximumPacketSize: sigval.MaximumPacketSize, + Address: sigval.Address, + Port: sigval.Port, + OriginatorIpAddress: sigval.OriginatorIpAddress, + OriginatorPort: sigval.OriginatorPort, + }, + } + return reqData, nil +} + +type forwardedTCPIPRequestSigval struct { + SenderChannel uint32 + InitialWindowSize uint32 + MaximumPacketSize uint32 + Address string + Port uint32 + OriginatorIpAddress string + OriginatorPort uint32 +} diff --git a/internal/bsr/ssh/chunk_forwarded_tcpip_request_test.go b/internal/bsr/ssh/chunk_forwarded_tcpip_request_test.go new file mode 100644 index 000000000..d9c23602c --- /dev/null +++ b/internal/bsr/ssh/chunk_forwarded_tcpip_request_test.go @@ -0,0 +1,120 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package ssh + +import ( + "context" + "testing" + "time" + + "github.com/hashicorp/boundary/internal/bsr" + pssh "github.com/hashicorp/boundary/internal/bsr/gen/ssh/v1" + "github.com/stretchr/testify/require" + gssh "golang.org/x/crypto/ssh" + "google.golang.org/protobuf/proto" +) + +func Test_NewForwardedTCPIPRequest(t *testing.T) { + ctx := context.Background() + now := bsr.NewTimestamp(time.Now()) + message := forwardedTCPIPRequestSigval{ + SenderChannel: 123, + InitialWindowSize: 456, + MaximumPacketSize: 789, + Address: "addy", + Port: 99, + OriginatorIpAddress: "original addy", + OriginatorPort: 22, + } + payload := gssh.Marshal(message) + + tests := []struct { + name string + direction bsr.Direction + time *bsr.Timestamp + request *gssh.Request + expErr bool + expErrMsg string + }{ + { + name: "nil request", + direction: bsr.Inbound, + time: now, + expErr: true, + expErrMsg: "ssh.NewForwardedTCPIPRequest: request cannot be nil: invalid parameter", + }, + { + name: "empty direction", + time: now, + request: &gssh.Request{ + Type: ForwardedTCPIPRequestType, + WantReply: false, + Payload: payload, + }, + expErr: true, + expErrMsg: "ssh.NewForwardedTCPIPRequest: invalid direction: invalid parameter", + }, + { + name: "empty time", + direction: bsr.Inbound, + request: &gssh.Request{ + Type: ForwardedTCPIPRequestType, + WantReply: false, + Payload: payload, + }, + expErr: true, + expErrMsg: "ssh.NewForwardedTCPIPRequest: timestamp cannot be nil: invalid parameter", + }, + { + name: "bad type", + direction: bsr.Inbound, + time: now, + request: &gssh.Request{ + Type: "muahaha", + WantReply: false, + Payload: payload, + }, + expErr: true, + expErrMsg: `ssh.NewForwardedTCPIPRequest: request type must be "forwarded-tcpip": invalid parameter`, + }, + { + name: "happy path", + direction: bsr.Inbound, + time: now, + request: &gssh.Request{ + Type: ForwardedTCPIPRequestType, + WantReply: false, + Payload: payload, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + rc, err := NewForwardedTCPIPRequest(ctx, tt.direction, tt.time, tt.request) + if tt.expErr { + require.EqualError(t, err, tt.expErrMsg) + require.Nil(t, rc) + return + } + + require.NoError(t, err) + require.NotNil(t, rc) + + data, err := rc.MarshalData(ctx) + require.NoError(t, err) + dataPayload := &pssh.ForwardedTCPIPRequest{} + err = proto.Unmarshal(data, dataPayload) + require.NoError(t, err) + require.Equal(t, dataPayload.SenderChannel, message.SenderChannel) + require.Equal(t, dataPayload.InitialWindowSize, message.InitialWindowSize) + require.Equal(t, dataPayload.MaximumPacketSize, message.MaximumPacketSize) + require.Equal(t, dataPayload.Address, message.Address) + require.Equal(t, dataPayload.Port, message.Port) + require.Equal(t, dataPayload.OriginatorPort, message.OriginatorPort) + require.Equal(t, dataPayload.OriginatorIpAddress, message.OriginatorIpAddress) + require.Equal(t, dataPayload.RequestType, ForwardedTCPIPRequestType) + }) + } +} diff --git a/internal/bsr/ssh/chunk_pty_request.go b/internal/bsr/ssh/chunk_pty_request.go new file mode 100644 index 000000000..0fd88d7cb --- /dev/null +++ b/internal/bsr/ssh/chunk_pty_request.go @@ -0,0 +1,88 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package ssh + +import ( + "context" + "fmt" + + "github.com/hashicorp/boundary/internal/bsr" + pssh "github.com/hashicorp/boundary/internal/bsr/gen/ssh/v1" + "github.com/hashicorp/boundary/internal/bsr/internal/is" + gssh "golang.org/x/crypto/ssh" + "google.golang.org/protobuf/proto" +) + +const PtyRequestType = "pty-req" + +// PtyPRequest is a chunk to contain data for an SSH Pty request +type PtyRequest struct { + bsr.BaseChunk + *pssh.PtyRequest +} + +// MarshalData serializes an PtyRequest chunk +func (r *PtyRequest) MarshalData(ctx context.Context) ([]byte, error) { + const op = "ssh.(PtyRequest).MarshalData" + dataBytes, err := proto.Marshal(r.PtyRequest) + if err != nil { + return nil, fmt.Errorf("%s: failed to marshal data: %w", op, err) + } + + d := make([]byte, 0, len(dataBytes)) + d = append(d, dataBytes...) + + return d, nil +} + +// NewPtyRequest creates an PtyRequest chunk +func NewPtyRequest(ctx context.Context, d bsr.Direction, t *bsr.Timestamp, r *gssh.Request) (*PtyRequest, error) { + const op = "ssh.NewPtyRequest" + + if is.Nil(r) { + return nil, fmt.Errorf("%s: request cannot be nil: %w", op, bsr.ErrInvalidParameter) + } + if r.Type != PtyRequestType { + return nil, fmt.Errorf("%s: request type must be %q: %w", op, PtyRequestType, bsr.ErrInvalidParameter) + } + if !bsr.ValidDirection(d) { + return nil, fmt.Errorf("%s: invalid direction: %w", op, bsr.ErrInvalidParameter) + } + if is.Nil(t) { + return nil, fmt.Errorf("%s: timestamp cannot be nil: %w", op, bsr.ErrInvalidParameter) + } + + baseChunk, err := bsr.NewBaseChunk(ctx, Protocol, d, t, PtyReqChunkType) + if err != nil { + return nil, fmt.Errorf("%s: unable to create base chunk: %w", op, err) + } + + var sigval ptySigval + if err := gssh.Unmarshal(r.Payload, &sigval); err != nil { + return nil, fmt.Errorf("%s: unable to unmarshal payload: %w", op, err) + } + reqData := &PtyRequest{ + BaseChunk: *baseChunk, + PtyRequest: &pssh.PtyRequest{ + RequestType: r.Type, + WantReply: r.WantReply, + TermEnvVar: sigval.TermEnvVar, + TerminalWidthCharacters: sigval.TerminalWidthCharacters, + TerminalHeightRows: sigval.TerminalHeightRows, + TerminalWidthPixels: sigval.TerminalWidthPixels, + TerminalHeightPixels: sigval.TerminalHeightPixels, + EncodedTerminalMode: sigval.EncodedTerminalMode, + }, + } + return reqData, nil +} + +type ptySigval struct { + TermEnvVar string + TerminalWidthCharacters uint32 + TerminalHeightRows uint32 + TerminalWidthPixels uint32 + TerminalHeightPixels uint32 + EncodedTerminalMode []byte +} diff --git a/internal/bsr/ssh/chunk_pty_request_test.go b/internal/bsr/ssh/chunk_pty_request_test.go new file mode 100644 index 000000000..acdb018d2 --- /dev/null +++ b/internal/bsr/ssh/chunk_pty_request_test.go @@ -0,0 +1,138 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package ssh + +import ( + "context" + "testing" + "time" + + "github.com/hashicorp/boundary/internal/bsr" + pssh "github.com/hashicorp/boundary/internal/bsr/gen/ssh/v1" + "github.com/stretchr/testify/require" + gssh "golang.org/x/crypto/ssh" + "google.golang.org/protobuf/proto" +) + +func Test_NewPtyRequest(t *testing.T) { + ctx := context.Background() + now := bsr.NewTimestamp(time.Now()) + + // emulate how go ssh encodes the termmode + termmodes := gssh.TerminalModes{ + gssh.ECHO: 0, + gssh.TTY_OP_ISPEED: 14400, + gssh.TTY_OP_OSPEED: 14400, + } + + var tm []byte + for k, v := range termmodes { + kv := struct { + Key byte + Val uint32 + }{k, v} + + tm = append(tm, gssh.Marshal(&kv)...) + } + tm = append(tm, 0) + + message := ptySigval{ + TermEnvVar: "var", + TerminalWidthCharacters: 123, + TerminalHeightRows: 456, + TerminalWidthPixels: 789, + TerminalHeightPixels: 234, + EncodedTerminalMode: tm, + } + payload := gssh.Marshal(message) + + tests := []struct { + name string + direction bsr.Direction + time *bsr.Timestamp + request *gssh.Request + expErr bool + expErrMsg string + }{ + { + name: "nil request", + direction: bsr.Inbound, + time: now, + expErr: true, + expErrMsg: "ssh.NewPtyRequest: request cannot be nil: invalid parameter", + }, + { + name: "empty direction", + time: now, + request: &gssh.Request{ + Type: PtyRequestType, + WantReply: false, + Payload: payload, + }, + expErr: true, + expErrMsg: "ssh.NewPtyRequest: invalid direction: invalid parameter", + }, + { + name: "empty time", + direction: bsr.Inbound, + request: &gssh.Request{ + Type: PtyRequestType, + WantReply: false, + Payload: payload, + }, + expErr: true, + expErrMsg: "ssh.NewPtyRequest: timestamp cannot be nil: invalid parameter", + }, + { + name: "bad type", + direction: bsr.Inbound, + time: now, + request: &gssh.Request{ + Type: "muahaha", + WantReply: false, + Payload: payload, + }, + expErr: true, + expErrMsg: `ssh.NewPtyRequest: request type must be "pty-req": invalid parameter`, + }, + { + name: "happy path", + direction: bsr.Inbound, + time: now, + request: &gssh.Request{ + Type: PtyRequestType, + WantReply: false, + Payload: payload, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + rc, err := NewPtyRequest(ctx, tt.direction, tt.time, tt.request) + if tt.expErr { + require.EqualError(t, err, tt.expErrMsg) + require.Nil(t, rc) + return + } + + require.NoError(t, err) + require.NotNil(t, rc) + + data, err := rc.MarshalData(ctx) + require.NoError(t, err) + dataPayload := &pssh.PtyRequest{} + err = proto.Unmarshal(data, dataPayload) + require.NoError(t, err) + require.Equal(t, dataPayload.TermEnvVar, message.TermEnvVar) + require.Equal(t, dataPayload.TerminalWidthCharacters, message.TerminalWidthCharacters) + require.Equal(t, dataPayload.TerminalHeightRows, message.TerminalHeightRows) + require.Equal(t, dataPayload.TerminalWidthPixels, message.TerminalWidthPixels) + require.Equal(t, dataPayload.TerminalHeightPixels, message.TerminalHeightPixels) + require.Equal(t, dataPayload.EncodedTerminalMode, message.EncodedTerminalMode) + require.Equal(t, dataPayload.RequestType, PtyRequestType) + require.Equal(t, dataPayload.WantReply, false) + }) + } +} diff --git a/internal/bsr/ssh/chunk_session_request.go b/internal/bsr/ssh/chunk_session_request.go new file mode 100644 index 000000000..6e0731274 --- /dev/null +++ b/internal/bsr/ssh/chunk_session_request.go @@ -0,0 +1,81 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package ssh + +import ( + "context" + "fmt" + + "github.com/hashicorp/boundary/internal/bsr" + pssh "github.com/hashicorp/boundary/internal/bsr/gen/ssh/v1" + "github.com/hashicorp/boundary/internal/bsr/internal/is" + gssh "golang.org/x/crypto/ssh" + "google.golang.org/protobuf/proto" +) + +const SessionRequestType = "session" + +// SessionRequest is a chunk to contain data for an SSH Session request +type SessionRequest struct { + bsr.BaseChunk + *pssh.SessionRequest +} + +// MarshalData serializes an SessionRequest chunk +func (r *SessionRequest) MarshalData(ctx context.Context) ([]byte, error) { + const op = "ssh.(SessionRequest).MarshalData" + dataBytes, err := proto.Marshal(r.SessionRequest) + if err != nil { + return nil, fmt.Errorf("%s: failed to marshal data: %w", op, err) + } + + d := make([]byte, 0, len(dataBytes)) + d = append(d, dataBytes...) + + return d, nil +} + +// NewSessionRequest creates an SessionRequest chunk +func NewSessionRequest(ctx context.Context, d bsr.Direction, t *bsr.Timestamp, r *gssh.Request) (*SessionRequest, error) { + const op = "ssh.NewSessionRequest" + + if is.Nil(r) { + return nil, fmt.Errorf("%s: request cannot be nil: %w", op, bsr.ErrInvalidParameter) + } + if r.Type != SessionRequestType { + return nil, fmt.Errorf("%s: request type must be %q: %w", op, SessionRequestType, bsr.ErrInvalidParameter) + } + if !bsr.ValidDirection(d) { + return nil, fmt.Errorf("%s: invalid direction: %w", op, bsr.ErrInvalidParameter) + } + if is.Nil(t) { + return nil, fmt.Errorf("%s: timestamp cannot be nil: %w", op, bsr.ErrInvalidParameter) + } + + baseChunk, err := bsr.NewBaseChunk(ctx, Protocol, d, t, SessionReqChunkType) + if err != nil { + return nil, fmt.Errorf("%s: unable to create base chunk: %w", op, err) + } + + var sigval sessionSigval + if err := gssh.Unmarshal(r.Payload, &sigval); err != nil { + return nil, fmt.Errorf("%s: unable to unmarshal payload: %w", op, err) + } + reqData := &SessionRequest{ + BaseChunk: *baseChunk, + SessionRequest: &pssh.SessionRequest{ + RequestType: r.Type, + SenderChannel: sigval.SenderChannel, + InitialWindowSize: sigval.InitialWindowSize, + MaximumPacketSize: sigval.MaximumPacketSize, + }, + } + return reqData, nil +} + +type sessionSigval struct { + SenderChannel uint32 + InitialWindowSize uint32 + MaximumPacketSize uint32 +} diff --git a/internal/bsr/ssh/chunk_session_request_test.go b/internal/bsr/ssh/chunk_session_request_test.go new file mode 100644 index 000000000..a2a152dfe --- /dev/null +++ b/internal/bsr/ssh/chunk_session_request_test.go @@ -0,0 +1,112 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package ssh + +import ( + "context" + "testing" + "time" + + "github.com/hashicorp/boundary/internal/bsr" + pssh "github.com/hashicorp/boundary/internal/bsr/gen/ssh/v1" + "github.com/stretchr/testify/require" + gssh "golang.org/x/crypto/ssh" + "google.golang.org/protobuf/proto" +) + +func Test_NewSessionRequest(t *testing.T) { + ctx := context.Background() + now := bsr.NewTimestamp(time.Now()) + message := sessionSigval{ + SenderChannel: 123, + InitialWindowSize: 456, + MaximumPacketSize: 789, + } + payload := gssh.Marshal(message) + + tests := []struct { + name string + direction bsr.Direction + time *bsr.Timestamp + request *gssh.Request + expErr bool + expErrMsg string + }{ + { + name: "nil request", + direction: bsr.Inbound, + time: now, + expErr: true, + expErrMsg: "ssh.NewSessionRequest: request cannot be nil: invalid parameter", + }, + { + name: "empty direction", + time: now, + request: &gssh.Request{ + Type: SessionRequestType, + WantReply: false, + Payload: payload, + }, + expErr: true, + expErrMsg: "ssh.NewSessionRequest: invalid direction: invalid parameter", + }, + { + name: "empty time", + direction: bsr.Inbound, + request: &gssh.Request{ + Type: SessionRequestType, + WantReply: false, + Payload: payload, + }, + expErr: true, + expErrMsg: "ssh.NewSessionRequest: timestamp cannot be nil: invalid parameter", + }, + { + name: "bad type", + direction: bsr.Inbound, + time: now, + request: &gssh.Request{ + Type: "muahaha", + WantReply: false, + Payload: payload, + }, + expErr: true, + expErrMsg: `ssh.NewSessionRequest: request type must be "session": invalid parameter`, + }, + { + name: "happy path", + direction: bsr.Inbound, + time: now, + request: &gssh.Request{ + Type: SessionRequestType, + WantReply: false, + Payload: payload, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + rc, err := NewSessionRequest(ctx, tt.direction, tt.time, tt.request) + if tt.expErr { + require.EqualError(t, err, tt.expErrMsg) + require.Nil(t, rc) + return + } + + require.NoError(t, err) + require.NotNil(t, rc) + + data, err := rc.MarshalData(ctx) + require.NoError(t, err) + dataPayload := &pssh.SessionRequest{} + err = proto.Unmarshal(data, dataPayload) + require.NoError(t, err) + require.Equal(t, dataPayload.SenderChannel, message.SenderChannel) + require.Equal(t, dataPayload.InitialWindowSize, message.InitialWindowSize) + require.Equal(t, dataPayload.MaximumPacketSize, message.MaximumPacketSize) + require.Equal(t, dataPayload.RequestType, SessionRequestType) + }) + } +} diff --git a/internal/bsr/ssh/chunk_shell_request.go b/internal/bsr/ssh/chunk_shell_request.go new file mode 100644 index 000000000..278c10d13 --- /dev/null +++ b/internal/bsr/ssh/chunk_shell_request.go @@ -0,0 +1,69 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package ssh + +import ( + "context" + "fmt" + + "github.com/hashicorp/boundary/internal/bsr" + pssh "github.com/hashicorp/boundary/internal/bsr/gen/ssh/v1" + "github.com/hashicorp/boundary/internal/bsr/internal/is" + gssh "golang.org/x/crypto/ssh" + "google.golang.org/protobuf/proto" +) + +const ShellRequestType = "shell" + +// ShellRequest is a chunk to contain data for an SSH Shell request +type ShellRequest struct { + bsr.BaseChunk + *pssh.ShellRequest +} + +// MarshalData serializes an ShellRequest chunk +func (r *ShellRequest) MarshalData(ctx context.Context) ([]byte, error) { + const op = "ssh.(SessionRequest).MarshalData" + dataBytes, err := proto.Marshal(r.ShellRequest) + if err != nil { + return nil, fmt.Errorf("%s: failed to marshal data: %w", op, err) + } + + d := make([]byte, 0, len(dataBytes)) + d = append(d, dataBytes...) + + return d, nil +} + +// NewShellRequest creates an ShellRequest chunk +func NewShellRequest(ctx context.Context, d bsr.Direction, t *bsr.Timestamp, r *gssh.Request) (*ShellRequest, error) { + const op = "ssh.NewShellRequest" + + if is.Nil(r) { + return nil, fmt.Errorf("%s: request cannot be nil: %w", op, bsr.ErrInvalidParameter) + } + if r.Type != ShellRequestType { + return nil, fmt.Errorf("%s: request type must be %q: %w", op, ShellRequestType, bsr.ErrInvalidParameter) + } + if !bsr.ValidDirection(d) { + return nil, fmt.Errorf("%s: invalid direction: %w", op, bsr.ErrInvalidParameter) + } + if is.Nil(t) { + return nil, fmt.Errorf("%s: timestamp cannot be nil: %w", op, bsr.ErrInvalidParameter) + } + + baseChunk, err := bsr.NewBaseChunk(ctx, Protocol, d, t, ShellReqChunkType) + if err != nil { + return nil, fmt.Errorf("%s: unable to create base chunk: %w", op, err) + } + + reqData := &ShellRequest{ + BaseChunk: *baseChunk, + ShellRequest: &pssh.ShellRequest{ + RequestType: r.Type, + WantReply: r.WantReply, + }, + } + return reqData, nil +} diff --git a/internal/bsr/ssh/chunk_shell_request_test.go b/internal/bsr/ssh/chunk_shell_request_test.go new file mode 100644 index 000000000..ee9548762 --- /dev/null +++ b/internal/bsr/ssh/chunk_shell_request_test.go @@ -0,0 +1,105 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package ssh + +import ( + "context" + "testing" + "time" + + "github.com/hashicorp/boundary/internal/bsr" + pssh "github.com/hashicorp/boundary/internal/bsr/gen/ssh/v1" + "github.com/stretchr/testify/require" + gssh "golang.org/x/crypto/ssh" + "google.golang.org/protobuf/proto" +) + +func Test_NewShellRequest(t *testing.T) { + ctx := context.Background() + now := bsr.NewTimestamp(time.Now()) + payload := []byte{} + + tests := []struct { + name string + direction bsr.Direction + time *bsr.Timestamp + request *gssh.Request + expErr bool + expErrMsg string + }{ + { + name: "nil request", + direction: bsr.Inbound, + time: now, + expErr: true, + expErrMsg: "ssh.NewShellRequest: request cannot be nil: invalid parameter", + }, + { + name: "empty direction", + time: now, + request: &gssh.Request{ + Type: ShellRequestType, + WantReply: false, + Payload: payload, + }, + expErr: true, + expErrMsg: "ssh.NewShellRequest: invalid direction: invalid parameter", + }, + { + name: "empty time", + direction: bsr.Inbound, + request: &gssh.Request{ + Type: ShellRequestType, + WantReply: false, + Payload: payload, + }, + expErr: true, + expErrMsg: "ssh.NewShellRequest: timestamp cannot be nil: invalid parameter", + }, + { + name: "bad type", + direction: bsr.Inbound, + time: now, + request: &gssh.Request{ + Type: "muahaha", + WantReply: false, + Payload: payload, + }, + expErr: true, + expErrMsg: `ssh.NewShellRequest: request type must be "shell": invalid parameter`, + }, + { + name: "happy path", + direction: bsr.Inbound, + time: now, + request: &gssh.Request{ + Type: ShellRequestType, + WantReply: false, + Payload: payload, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + rc, err := NewShellRequest(ctx, tt.direction, tt.time, tt.request) + if tt.expErr { + require.EqualError(t, err, tt.expErrMsg) + require.Nil(t, rc) + return + } + + require.NoError(t, err) + require.NotNil(t, rc) + + data, err := rc.MarshalData(ctx) + require.NoError(t, err) + dataPayload := &pssh.ShellRequest{} + err = proto.Unmarshal(data, dataPayload) + require.NoError(t, err) + require.Equal(t, dataPayload.RequestType, ShellRequestType) + require.Equal(t, dataPayload.WantReply, false) + }) + } +} diff --git a/internal/bsr/ssh/chunk_signal_request.go b/internal/bsr/ssh/chunk_signal_request.go new file mode 100644 index 000000000..a19afcab8 --- /dev/null +++ b/internal/bsr/ssh/chunk_signal_request.go @@ -0,0 +1,78 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package ssh + +import ( + "context" + "fmt" + + "github.com/hashicorp/boundary/internal/bsr" + pssh "github.com/hashicorp/boundary/internal/bsr/gen/ssh/v1" + "github.com/hashicorp/boundary/internal/bsr/internal/is" + gssh "golang.org/x/crypto/ssh" + "google.golang.org/protobuf/proto" +) + +const SignalRequestType = "signal" + +// SignalRequest is a chunk to contain data for an SSH Signal request +type SignalRequest struct { + bsr.BaseChunk + *pssh.SignalRequest +} + +// MarshalData serializes an SignalRequest chunk +func (r *SignalRequest) MarshalData(ctx context.Context) ([]byte, error) { + const op = "ssh.(SignalRequest).MarshalData" + dataBytes, err := proto.Marshal(r.SignalRequest) + if err != nil { + return nil, fmt.Errorf("%s: failed to marshal data: %w", op, err) + } + + d := make([]byte, 0, len(dataBytes)) + d = append(d, dataBytes...) + + return d, nil +} + +// NewSignalRequest creates an SignalRequest chunk +func NewSignalRequest(ctx context.Context, d bsr.Direction, t *bsr.Timestamp, r *gssh.Request) (*SignalRequest, error) { + const op = "ssh.NewSignalRequest" + + if is.Nil(r) { + return nil, fmt.Errorf("%s: request cannot be nil: %w", op, bsr.ErrInvalidParameter) + } + if r.Type != SignalRequestType { + return nil, fmt.Errorf("%s: request type must be %q: %w", op, SignalRequestType, bsr.ErrInvalidParameter) + } + if !bsr.ValidDirection(d) { + return nil, fmt.Errorf("%s: invalid direction: %w", op, bsr.ErrInvalidParameter) + } + if is.Nil(t) { + return nil, fmt.Errorf("%s: timestamp cannot be nil: %w", op, bsr.ErrInvalidParameter) + } + + baseChunk, err := bsr.NewBaseChunk(ctx, Protocol, d, t, SignalReqChunkType) + if err != nil { + return nil, fmt.Errorf("%s: unable to create base chunk: %w", op, err) + } + + var sigval signalSigval + if err := gssh.Unmarshal(r.Payload, &sigval); err != nil { + return nil, fmt.Errorf("%s: unable to unmarshal payload: %w", op, err) + } + reqData := &SignalRequest{ + BaseChunk: *baseChunk, + SignalRequest: &pssh.SignalRequest{ + RequestType: r.Type, + WantReply: r.WantReply, + SignalName: sigval.SignalName, + }, + } + return reqData, nil +} + +type signalSigval struct { + SignalName string +} diff --git a/internal/bsr/ssh/chunk_signal_request_test.go b/internal/bsr/ssh/chunk_signal_request_test.go new file mode 100644 index 000000000..659040797 --- /dev/null +++ b/internal/bsr/ssh/chunk_signal_request_test.go @@ -0,0 +1,109 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package ssh + +import ( + "context" + "testing" + "time" + + "github.com/hashicorp/boundary/internal/bsr" + pssh "github.com/hashicorp/boundary/internal/bsr/gen/ssh/v1" + "github.com/stretchr/testify/require" + gssh "golang.org/x/crypto/ssh" + "google.golang.org/protobuf/proto" +) + +func Test_NewSignalRequest(t *testing.T) { + ctx := context.Background() + now := bsr.NewTimestamp(time.Now()) + message := signalSigval{ + SignalName: "HALT", + } + payload := gssh.Marshal(message) + + tests := []struct { + name string + direction bsr.Direction + time *bsr.Timestamp + request *gssh.Request + expErr bool + expErrMsg string + }{ + { + name: "nil request", + direction: bsr.Inbound, + time: now, + expErr: true, + expErrMsg: "ssh.NewSignalRequest: request cannot be nil: invalid parameter", + }, + { + name: "empty direction", + time: now, + request: &gssh.Request{ + Type: SignalRequestType, + WantReply: false, + Payload: payload, + }, + expErr: true, + expErrMsg: "ssh.NewSignalRequest: invalid direction: invalid parameter", + }, + { + name: "empty time", + direction: bsr.Inbound, + request: &gssh.Request{ + Type: SignalRequestType, + WantReply: false, + Payload: payload, + }, + expErr: true, + expErrMsg: "ssh.NewSignalRequest: timestamp cannot be nil: invalid parameter", + }, + { + name: "bad type", + direction: bsr.Inbound, + time: now, + request: &gssh.Request{ + Type: "muahaha", + WantReply: false, + Payload: payload, + }, + expErr: true, + expErrMsg: `ssh.NewSignalRequest: request type must be "signal": invalid parameter`, + }, + { + name: "happy path", + direction: bsr.Inbound, + time: now, + request: &gssh.Request{ + Type: SignalRequestType, + WantReply: false, + Payload: payload, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + rc, err := NewSignalRequest(ctx, tt.direction, tt.time, tt.request) + if tt.expErr { + require.EqualError(t, err, tt.expErrMsg) + require.Nil(t, rc) + return + } + + require.NoError(t, err) + require.NotNil(t, rc) + + data, err := rc.MarshalData(ctx) + require.NoError(t, err) + dataPayload := &pssh.SignalRequest{} + err = proto.Unmarshal(data, dataPayload) + require.NoError(t, err) + require.Equal(t, dataPayload.SignalName, message.SignalName) + require.Equal(t, dataPayload.RequestType, SignalRequestType) + require.Equal(t, dataPayload.WantReply, false) + }) + } +} diff --git a/internal/bsr/ssh/chunk_subsystem_request.go b/internal/bsr/ssh/chunk_subsystem_request.go new file mode 100644 index 000000000..fab88566c --- /dev/null +++ b/internal/bsr/ssh/chunk_subsystem_request.go @@ -0,0 +1,78 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package ssh + +import ( + "context" + "fmt" + + "github.com/hashicorp/boundary/internal/bsr" + pssh "github.com/hashicorp/boundary/internal/bsr/gen/ssh/v1" + "github.com/hashicorp/boundary/internal/bsr/internal/is" + gssh "golang.org/x/crypto/ssh" + "google.golang.org/protobuf/proto" +) + +const SubsystemRequestType = "subsystem" + +// SubsystemRequest is a chunk to contain data for an SSH Subsystem request +type SubsystemRequest struct { + bsr.BaseChunk + *pssh.SubsystemRequest +} + +// MarshalData serializes an SubsystemRequest chunk +func (r *SubsystemRequest) MarshalData(ctx context.Context) ([]byte, error) { + const op = "ssh.(SubsystemRequest).MarshalData" + dataBytes, err := proto.Marshal(r.SubsystemRequest) + if err != nil { + return nil, fmt.Errorf("%s: failed to marshal data: %w", op, err) + } + + d := make([]byte, 0, len(dataBytes)) + d = append(d, dataBytes...) + + return d, nil +} + +// NewSubsystemRequest creates an SubsystemRequest chunk +func NewSubsystemRequest(ctx context.Context, d bsr.Direction, t *bsr.Timestamp, r *gssh.Request) (*SubsystemRequest, error) { + const op = "ssh.NewSubsystemRequest" + + if is.Nil(r) { + return nil, fmt.Errorf("%s: request cannot be nil: %w", op, bsr.ErrInvalidParameter) + } + if r.Type != SubsystemRequestType { + return nil, fmt.Errorf("%s: request type must be %q: %w", op, SubsystemRequestType, bsr.ErrInvalidParameter) + } + if !bsr.ValidDirection(d) { + return nil, fmt.Errorf("%s: invalid direction: %w", op, bsr.ErrInvalidParameter) + } + if is.Nil(t) { + return nil, fmt.Errorf("%s: timestamp cannot be nil: %w", op, bsr.ErrInvalidParameter) + } + + baseChunk, err := bsr.NewBaseChunk(ctx, Protocol, d, t, SubsystemReqChunkType) + if err != nil { + return nil, fmt.Errorf("%s: unable to create base chunk: %w", op, err) + } + + var sigval subsystemSigval + if err := gssh.Unmarshal(r.Payload, &sigval); err != nil { + return nil, fmt.Errorf("%s: unable to unmarshal payload: %w", op, err) + } + reqData := &SubsystemRequest{ + BaseChunk: *baseChunk, + SubsystemRequest: &pssh.SubsystemRequest{ + RequestType: r.Type, + WantReply: r.WantReply, + SubsystemName: sigval.SubsystemName, + }, + } + return reqData, nil +} + +type subsystemSigval struct { + SubsystemName string +} diff --git a/internal/bsr/ssh/chunk_subsystem_request_test.go b/internal/bsr/ssh/chunk_subsystem_request_test.go new file mode 100644 index 000000000..eadea0889 --- /dev/null +++ b/internal/bsr/ssh/chunk_subsystem_request_test.go @@ -0,0 +1,109 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package ssh + +import ( + "context" + "testing" + "time" + + "github.com/hashicorp/boundary/internal/bsr" + pssh "github.com/hashicorp/boundary/internal/bsr/gen/ssh/v1" + "github.com/stretchr/testify/require" + gssh "golang.org/x/crypto/ssh" + "google.golang.org/protobuf/proto" +) + +func Test_NewSubsystemRequest(t *testing.T) { + ctx := context.Background() + now := bsr.NewTimestamp(time.Now()) + message := subsystemSigval{ + SubsystemName: "submarine", + } + payload := gssh.Marshal(message) + + tests := []struct { + name string + direction bsr.Direction + time *bsr.Timestamp + request *gssh.Request + expErr bool + expErrMsg string + }{ + { + name: "nil request", + direction: bsr.Inbound, + time: now, + expErr: true, + expErrMsg: "ssh.NewSubsystemRequest: request cannot be nil: invalid parameter", + }, + { + name: "empty direction", + time: now, + request: &gssh.Request{ + Type: SubsystemRequestType, + WantReply: false, + Payload: payload, + }, + expErr: true, + expErrMsg: "ssh.NewSubsystemRequest: invalid direction: invalid parameter", + }, + { + name: "empty time", + direction: bsr.Inbound, + request: &gssh.Request{ + Type: SubsystemRequestType, + WantReply: false, + Payload: payload, + }, + expErr: true, + expErrMsg: "ssh.NewSubsystemRequest: timestamp cannot be nil: invalid parameter", + }, + { + name: "bad type", + direction: bsr.Inbound, + time: now, + request: &gssh.Request{ + Type: "muahaha", + WantReply: false, + Payload: payload, + }, + expErr: true, + expErrMsg: `ssh.NewSubsystemRequest: request type must be "subsystem": invalid parameter`, + }, + { + name: "happy path", + direction: bsr.Inbound, + time: now, + request: &gssh.Request{ + Type: SubsystemRequestType, + WantReply: false, + Payload: payload, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + rc, err := NewSubsystemRequest(ctx, tt.direction, tt.time, tt.request) + if tt.expErr { + require.EqualError(t, err, tt.expErrMsg) + require.Nil(t, rc) + return + } + + require.NoError(t, err) + require.NotNil(t, rc) + + data, err := rc.MarshalData(ctx) + require.NoError(t, err) + dataPayload := &pssh.SubsystemRequest{} + err = proto.Unmarshal(data, dataPayload) + require.NoError(t, err) + require.Equal(t, dataPayload.SubsystemName, message.SubsystemName) + require.Equal(t, dataPayload.RequestType, SubsystemRequestType) + require.Equal(t, dataPayload.WantReply, false) + }) + } +} diff --git a/internal/bsr/ssh/chunk_tcpip_forward_request.go b/internal/bsr/ssh/chunk_tcpip_forward_request.go new file mode 100644 index 000000000..8f6ca7083 --- /dev/null +++ b/internal/bsr/ssh/chunk_tcpip_forward_request.go @@ -0,0 +1,80 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package ssh + +import ( + "context" + "fmt" + + "github.com/hashicorp/boundary/internal/bsr" + pssh "github.com/hashicorp/boundary/internal/bsr/gen/ssh/v1" + "github.com/hashicorp/boundary/internal/bsr/internal/is" + gssh "golang.org/x/crypto/ssh" + "google.golang.org/protobuf/proto" +) + +const TCPIPForwardRequestType = "tcpip-forward" + +// TCPIPForwardRequest is a chunk to contain data for an SSH TCPIP Forward request +type TCPIPForwardRequest struct { + bsr.BaseChunk + *pssh.TCPIPForwardRequest +} + +// MarshalData serializes an TCPIPForwardRequest chunk +func (r *TCPIPForwardRequest) MarshalData(ctx context.Context) ([]byte, error) { + const op = "ssh.(TCPIPForwardRequest).MarshalData" + dataBytes, err := proto.Marshal(r.TCPIPForwardRequest) + if err != nil { + return nil, fmt.Errorf("%s: failed to marshal data: %w", op, err) + } + + d := make([]byte, 0, len(dataBytes)) + d = append(d, dataBytes...) + + return d, nil +} + +// NewTCPIPForwardRequest creates an TCPIPForwardRequest chunk +func NewTCPIPForwardRequest(ctx context.Context, d bsr.Direction, t *bsr.Timestamp, r *gssh.Request) (*TCPIPForwardRequest, error) { + const op = "ssh.NewTCPIPForwardRequest" + + if is.Nil(r) { + return nil, fmt.Errorf("%s: request cannot be nil: %w", op, bsr.ErrInvalidParameter) + } + if r.Type != TCPIPForwardRequestType { + return nil, fmt.Errorf("%s: request type must be %q: %w", op, TCPIPForwardRequestType, bsr.ErrInvalidParameter) + } + if !bsr.ValidDirection(d) { + return nil, fmt.Errorf("%s: invalid direction: %w", op, bsr.ErrInvalidParameter) + } + if is.Nil(t) { + return nil, fmt.Errorf("%s: timestamp cannot be nil: %w", op, bsr.ErrInvalidParameter) + } + + baseChunk, err := bsr.NewBaseChunk(ctx, Protocol, d, t, TCPIPForwardReqChunkType) + if err != nil { + return nil, fmt.Errorf("%s: unable to create base chunk: %w", op, err) + } + + var sigval tCPIPForwardSigval + if err := gssh.Unmarshal(r.Payload, &sigval); err != nil { + return nil, fmt.Errorf("%s: unable to unmarshal payload: %w", op, err) + } + reqData := &TCPIPForwardRequest{ + BaseChunk: *baseChunk, + TCPIPForwardRequest: &pssh.TCPIPForwardRequest{ + RequestType: r.Type, + WantReply: r.WantReply, + AddressToBind: sigval.AddressToBind, + PortToBind: sigval.PortToBind, + }, + } + return reqData, nil +} + +type tCPIPForwardSigval struct { + AddressToBind string + PortToBind uint32 +} diff --git a/internal/bsr/ssh/chunk_tcpip_forward_request_test.go b/internal/bsr/ssh/chunk_tcpip_forward_request_test.go new file mode 100644 index 000000000..b90e170d1 --- /dev/null +++ b/internal/bsr/ssh/chunk_tcpip_forward_request_test.go @@ -0,0 +1,111 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package ssh + +import ( + "context" + "testing" + "time" + + "github.com/hashicorp/boundary/internal/bsr" + pssh "github.com/hashicorp/boundary/internal/bsr/gen/ssh/v1" + "github.com/stretchr/testify/require" + gssh "golang.org/x/crypto/ssh" + "google.golang.org/protobuf/proto" +) + +func Test_NewTCPIPForwardRequest(t *testing.T) { + ctx := context.Background() + now := bsr.NewTimestamp(time.Now()) + message := tCPIPForwardSigval{ + AddressToBind: "addy", + PortToBind: 99, + } + payload := gssh.Marshal(message) + + tests := []struct { + name string + direction bsr.Direction + time *bsr.Timestamp + request *gssh.Request + expErr bool + expErrMsg string + }{ + { + name: "nil request", + direction: bsr.Inbound, + time: now, + expErr: true, + expErrMsg: "ssh.NewTCPIPForwardRequest: request cannot be nil: invalid parameter", + }, + { + name: "empty direction", + time: now, + request: &gssh.Request{ + Type: TCPIPForwardRequestType, + WantReply: false, + Payload: payload, + }, + expErr: true, + expErrMsg: "ssh.NewTCPIPForwardRequest: invalid direction: invalid parameter", + }, + { + name: "empty time", + direction: bsr.Inbound, + request: &gssh.Request{ + Type: TCPIPForwardRequestType, + WantReply: false, + Payload: payload, + }, + expErr: true, + expErrMsg: "ssh.NewTCPIPForwardRequest: timestamp cannot be nil: invalid parameter", + }, + { + name: "bad type", + direction: bsr.Inbound, + time: now, + request: &gssh.Request{ + Type: "muahaha", + WantReply: false, + Payload: payload, + }, + expErr: true, + expErrMsg: `ssh.NewTCPIPForwardRequest: request type must be "tcpip-forward": invalid parameter`, + }, + { + name: "happy path", + direction: bsr.Inbound, + time: now, + request: &gssh.Request{ + Type: TCPIPForwardRequestType, + WantReply: false, + Payload: payload, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + rc, err := NewTCPIPForwardRequest(ctx, tt.direction, tt.time, tt.request) + if tt.expErr { + require.EqualError(t, err, tt.expErrMsg) + require.Nil(t, rc) + return + } + + require.NoError(t, err) + require.NotNil(t, rc) + + data, err := rc.MarshalData(ctx) + require.NoError(t, err) + dataPayload := &pssh.TCPIPForwardRequest{} + err = proto.Unmarshal(data, dataPayload) + require.NoError(t, err) + require.Equal(t, dataPayload.AddressToBind, message.AddressToBind) + require.Equal(t, dataPayload.PortToBind, message.PortToBind) + require.Equal(t, dataPayload.RequestType, TCPIPForwardRequestType) + require.Equal(t, dataPayload.WantReply, false) + }) + } +} diff --git a/internal/bsr/ssh/chunk_unknown_request.go b/internal/bsr/ssh/chunk_unknown_request.go new file mode 100644 index 000000000..32aeb8f1e --- /dev/null +++ b/internal/bsr/ssh/chunk_unknown_request.go @@ -0,0 +1,64 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package ssh + +import ( + "context" + "fmt" + + "github.com/hashicorp/boundary/internal/bsr" + pssh "github.com/hashicorp/boundary/internal/bsr/gen/ssh/v1" + "github.com/hashicorp/boundary/internal/bsr/internal/is" + gssh "golang.org/x/crypto/ssh" + "google.golang.org/protobuf/proto" +) + +// UnknownRequest is a chunk to contain data for any unrecognized SSH request +type UnknownRequest struct { + bsr.BaseChunk + *pssh.UnknownRequest +} + +// MarshalData serializes an UnknownRequest chunk +func (r *UnknownRequest) MarshalData(ctx context.Context) ([]byte, error) { + const op = "ssh.(UnknownRequest).MarshalData" + dataBytes, err := proto.Marshal(r.UnknownRequest) + if err != nil { + return nil, fmt.Errorf("%s: failed to marshal data: %w", op, err) + } + + d := make([]byte, 0, len(dataBytes)) + d = append(d, dataBytes...) + + return d, nil +} + +// NewUnknownRequest creates an UnknownRequest chunk +func NewUnknownRequest(ctx context.Context, d bsr.Direction, t *bsr.Timestamp, r *gssh.Request) (*UnknownRequest, error) { + const op = "ssh.NewUnknownRequest" + + if is.Nil(r) { + return nil, fmt.Errorf("%s: request cannot be nil: %w", op, bsr.ErrInvalidParameter) + } + if !bsr.ValidDirection(d) { + return nil, fmt.Errorf("%s: invalid direction: %w", op, bsr.ErrInvalidParameter) + } + if is.Nil(t) { + return nil, fmt.Errorf("%s: timestamp cannot be nil: %w", op, bsr.ErrInvalidParameter) + } + + baseChunk, err := bsr.NewBaseChunk(ctx, Protocol, d, t, UnknownReqChunkType) + if err != nil { + return nil, fmt.Errorf("%s: unable to create base chunk: %w", op, err) + } + + return &UnknownRequest{ + BaseChunk: *baseChunk, + UnknownRequest: &pssh.UnknownRequest{ + RequestType: r.Type, + WantReply: r.WantReply, + Data: r.Payload, + }, + }, nil +} diff --git a/internal/bsr/ssh/chunk_unknown_request_test.go b/internal/bsr/ssh/chunk_unknown_request_test.go new file mode 100644 index 000000000..c7b8537d4 --- /dev/null +++ b/internal/bsr/ssh/chunk_unknown_request_test.go @@ -0,0 +1,95 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package ssh + +import ( + "context" + "testing" + "time" + + "github.com/hashicorp/boundary/internal/bsr" + pssh "github.com/hashicorp/boundary/internal/bsr/gen/ssh/v1" + "github.com/stretchr/testify/require" + gssh "golang.org/x/crypto/ssh" + "google.golang.org/protobuf/proto" +) + +func Test_NewUnknownRequest(t *testing.T) { + ctx := context.Background() + now := bsr.NewTimestamp(time.Now()) + payload := []byte("session request data comin' thru") + reqType := "you-dont-know-me" + + tests := []struct { + name string + direction bsr.Direction + time *bsr.Timestamp + request *gssh.Request + expErr bool + expErrMsg string + }{ + { + name: "nil request", + direction: bsr.Inbound, + time: now, + expErr: true, + expErrMsg: "ssh.NewUnknownRequest: request cannot be nil: invalid parameter", + }, + { + name: "empty direction", + time: now, + request: &gssh.Request{ + Type: reqType, + WantReply: false, + Payload: payload, + }, + expErr: true, + expErrMsg: "ssh.NewUnknownRequest: invalid direction: invalid parameter", + }, + { + name: "empty time", + direction: bsr.Inbound, + request: &gssh.Request{ + Type: reqType, + WantReply: false, + Payload: payload, + }, + expErr: true, + expErrMsg: "ssh.NewUnknownRequest: timestamp cannot be nil: invalid parameter", + }, + { + name: "happy path", + direction: bsr.Inbound, + time: now, + request: &gssh.Request{ + Type: reqType, + WantReply: false, + Payload: payload, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + rc, err := NewUnknownRequest(ctx, tt.direction, tt.time, tt.request) + if tt.expErr { + require.EqualError(t, err, tt.expErrMsg) + require.Nil(t, rc) + return + } + + require.NoError(t, err) + require.NotNil(t, rc) + + data, err := rc.MarshalData(ctx) + require.NoError(t, err) + dataPayload := &pssh.UnknownRequest{} + err = proto.Unmarshal(data, dataPayload) + require.NoError(t, err) + require.Equal(t, dataPayload.Data, payload) + require.Equal(t, dataPayload.RequestType, reqType) + require.Equal(t, dataPayload.WantReply, false) + }) + } +} diff --git a/internal/bsr/ssh/chunk_window_change_request.go b/internal/bsr/ssh/chunk_window_change_request.go new file mode 100644 index 000000000..eb0c55ec9 --- /dev/null +++ b/internal/bsr/ssh/chunk_window_change_request.go @@ -0,0 +1,84 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package ssh + +import ( + "context" + "fmt" + + "github.com/hashicorp/boundary/internal/bsr" + pssh "github.com/hashicorp/boundary/internal/bsr/gen/ssh/v1" + "github.com/hashicorp/boundary/internal/bsr/internal/is" + gssh "golang.org/x/crypto/ssh" + "google.golang.org/protobuf/proto" +) + +const WindowChangeRequestType = "window-change" + +// WindowChangeRequest is a chunk to contain data for an SSH Window Change request +type WindowChangeRequest struct { + bsr.BaseChunk + *pssh.WindowChangeRequest +} + +// MarshalData serializes an WindowChangeRequest chunk +func (r *WindowChangeRequest) MarshalData(ctx context.Context) ([]byte, error) { + const op = "ssh.(WindowChangeRequest).MarshalData" + dataBytes, err := proto.Marshal(r.WindowChangeRequest) + if err != nil { + return nil, fmt.Errorf("%s: failed to marshal data: %w", op, err) + } + + d := make([]byte, 0, len(dataBytes)) + d = append(d, dataBytes...) + + return d, nil +} + +// NewWindowChangeRequest creates a WindowChangeRequest chunk +func NewWindowChangeRequest(ctx context.Context, d bsr.Direction, t *bsr.Timestamp, r *gssh.Request) (*WindowChangeRequest, error) { + const op = "ssh.NewWindowChangeRequest" + + if is.Nil(r) { + return nil, fmt.Errorf("%s: request cannot be nil: %w", op, bsr.ErrInvalidParameter) + } + if r.Type != WindowChangeRequestType { + return nil, fmt.Errorf("%s: request type must be %q: %w", op, WindowChangeRequestType, bsr.ErrInvalidParameter) + } + if !bsr.ValidDirection(d) { + return nil, fmt.Errorf("%s: invalid direction: %w", op, bsr.ErrInvalidParameter) + } + if is.Nil(t) { + return nil, fmt.Errorf("%s: timestamp cannot be nil: %w", op, bsr.ErrInvalidParameter) + } + + baseChunk, err := bsr.NewBaseChunk(ctx, Protocol, d, t, WindowChangeReqChunkType) + if err != nil { + return nil, fmt.Errorf("%s: unable to create base chunk: %w", op, err) + } + + var sigval windowChangeSigval + if err := gssh.Unmarshal(r.Payload, &sigval); err != nil { + return nil, fmt.Errorf("%s: unable to unmarshal payload: %w", op, err) + } + reqData := &WindowChangeRequest{ + BaseChunk: *baseChunk, + WindowChangeRequest: &pssh.WindowChangeRequest{ + RequestType: r.Type, + WantReply: r.WantReply, + TerminalWidthColumns: sigval.TerminalWidthColumns, + TerminalHeightRows: sigval.TerminalHeightRows, + TerminalWidthPixels: sigval.TerminalWidthPixels, + TerminalHeightPixels: sigval.TerminalHeightPixels, + }, + } + return reqData, nil +} + +type windowChangeSigval struct { + TerminalWidthColumns uint32 + TerminalHeightRows uint32 + TerminalWidthPixels uint32 + TerminalHeightPixels uint32 +} diff --git a/internal/bsr/ssh/chunk_window_change_request_test.go b/internal/bsr/ssh/chunk_window_change_request_test.go new file mode 100644 index 000000000..aa86562c9 --- /dev/null +++ b/internal/bsr/ssh/chunk_window_change_request_test.go @@ -0,0 +1,115 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package ssh + +import ( + "context" + "testing" + "time" + + "github.com/hashicorp/boundary/internal/bsr" + pssh "github.com/hashicorp/boundary/internal/bsr/gen/ssh/v1" + "github.com/stretchr/testify/require" + gssh "golang.org/x/crypto/ssh" + "google.golang.org/protobuf/proto" +) + +func Test_NewWindowChangeRequest(t *testing.T) { + ctx := context.Background() + now := bsr.NewTimestamp(time.Now()) + message := windowChangeSigval{ + TerminalWidthColumns: 12, + TerminalHeightRows: 34, + TerminalWidthPixels: 56, + TerminalHeightPixels: 78, + } + payload := gssh.Marshal(message) + + tests := []struct { + name string + direction bsr.Direction + time *bsr.Timestamp + request *gssh.Request + expErr bool + expErrMsg string + }{ + { + name: "nil request", + direction: bsr.Inbound, + time: now, + expErr: true, + expErrMsg: "ssh.NewWindowChangeRequest: request cannot be nil: invalid parameter", + }, + { + name: "empty direction", + time: now, + request: &gssh.Request{ + Type: WindowChangeRequestType, + WantReply: false, + Payload: payload, + }, + expErr: true, + expErrMsg: "ssh.NewWindowChangeRequest: invalid direction: invalid parameter", + }, + { + name: "empty time", + direction: bsr.Inbound, + request: &gssh.Request{ + Type: WindowChangeRequestType, + WantReply: false, + Payload: payload, + }, + expErr: true, + expErrMsg: "ssh.NewWindowChangeRequest: timestamp cannot be nil: invalid parameter", + }, + { + name: "bad type", + direction: bsr.Inbound, + time: now, + request: &gssh.Request{ + Type: "muahaha", + WantReply: false, + Payload: payload, + }, + expErr: true, + expErrMsg: `ssh.NewWindowChangeRequest: request type must be "window-change": invalid parameter`, + }, + { + name: "happy path", + direction: bsr.Inbound, + time: now, + request: &gssh.Request{ + Type: WindowChangeRequestType, + WantReply: false, + Payload: payload, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + rc, err := NewWindowChangeRequest(ctx, tt.direction, tt.time, tt.request) + if tt.expErr { + require.EqualError(t, err, tt.expErrMsg) + require.Nil(t, rc) + return + } + + require.NoError(t, err) + require.NotNil(t, rc) + + data, err := rc.MarshalData(ctx) + require.NoError(t, err) + dataPayload := &pssh.WindowChangeRequest{} + err = proto.Unmarshal(data, dataPayload) + require.NoError(t, err) + require.Equal(t, dataPayload.TerminalWidthColumns, message.TerminalWidthColumns) + require.Equal(t, dataPayload.TerminalWidthPixels, message.TerminalWidthPixels) + require.Equal(t, dataPayload.TerminalHeightPixels, message.TerminalHeightPixels) + require.Equal(t, dataPayload.TerminalHeightRows, message.TerminalHeightRows) + require.Equal(t, dataPayload.RequestType, WindowChangeRequestType) + require.Equal(t, dataPayload.WantReply, false) + }) + } +} diff --git a/internal/bsr/ssh/chunk_x11_forwarding_request.go b/internal/bsr/ssh/chunk_x11_forwarding_request.go new file mode 100644 index 000000000..7ec6dfa00 --- /dev/null +++ b/internal/bsr/ssh/chunk_x11_forwarding_request.go @@ -0,0 +1,84 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package ssh + +import ( + "context" + "fmt" + + "github.com/hashicorp/boundary/internal/bsr" + pssh "github.com/hashicorp/boundary/internal/bsr/gen/ssh/v1" + "github.com/hashicorp/boundary/internal/bsr/internal/is" + gssh "golang.org/x/crypto/ssh" + "google.golang.org/protobuf/proto" +) + +const X11ForwardingRequestType = "x11-req" + +// X11ForwardingRequest is a chunk to contain data for an SSH X11 Forwarding request +type X11ForwardingRequest struct { + bsr.BaseChunk + *pssh.X11ForwardingRequest +} + +// MarshalData serializes an X11ForwardingRequest chunk +func (r *X11ForwardingRequest) MarshalData(ctx context.Context) ([]byte, error) { + const op = "ssh.(X11ForwardingRequest).MarshalData" + dataBytes, err := proto.Marshal(r.X11ForwardingRequest) + if err != nil { + return nil, fmt.Errorf("%s: failed to marshal data: %w", op, err) + } + + d := make([]byte, 0, len(dataBytes)) + d = append(d, dataBytes...) + + return d, nil +} + +// NewX11ForwardingRequest creates an X11ForwardingRequest chunk +func NewX11ForwardingRequest(ctx context.Context, d bsr.Direction, t *bsr.Timestamp, r *gssh.Request) (*X11ForwardingRequest, error) { + const op = "ssh.NewX11ForwardingRequest" + + if is.Nil(r) { + return nil, fmt.Errorf("%s: request cannot be nil: %w", op, bsr.ErrInvalidParameter) + } + if r.Type != X11ForwardingRequestType { + return nil, fmt.Errorf("%s: request type must be %q: %w", op, X11ForwardingRequestType, bsr.ErrInvalidParameter) + } + if !bsr.ValidDirection(d) { + return nil, fmt.Errorf("%s: invalid direction: %w", op, bsr.ErrInvalidParameter) + } + if is.Nil(t) { + return nil, fmt.Errorf("%s: timestamp cannot be nil: %w", op, bsr.ErrInvalidParameter) + } + + baseChunk, err := bsr.NewBaseChunk(ctx, Protocol, d, t, X11ForwardingReqChunkType) + if err != nil { + return nil, fmt.Errorf("%s: unable to create base chunk: %w", op, err) + } + + var sigval x11ForwardingSigval + if err := gssh.Unmarshal(r.Payload, &sigval); err != nil { + return nil, fmt.Errorf("%s: unable to unmarshal payload: %w", op, err) + } + reqData := &X11ForwardingRequest{ + BaseChunk: *baseChunk, + X11ForwardingRequest: &pssh.X11ForwardingRequest{ + RequestType: r.Type, + WantReply: r.WantReply, + SingleConnection: sigval.SingleConnection, + X11AuthenticationProtocol: sigval.X11AuthenticationProtocol, + X11AuthenticationCookie: sigval.X11AuthenticationCookie, + X11ScreenNumber: sigval.X11ScreenNumber, + }, + } + return reqData, nil +} + +type x11ForwardingSigval struct { + SingleConnection bool + X11AuthenticationProtocol string + X11AuthenticationCookie string + X11ScreenNumber uint32 +} diff --git a/internal/bsr/ssh/chunk_x11_forwarding_request_test.go b/internal/bsr/ssh/chunk_x11_forwarding_request_test.go new file mode 100644 index 000000000..2b38ce95b --- /dev/null +++ b/internal/bsr/ssh/chunk_x11_forwarding_request_test.go @@ -0,0 +1,115 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package ssh + +import ( + "context" + "testing" + "time" + + "github.com/hashicorp/boundary/internal/bsr" + pssh "github.com/hashicorp/boundary/internal/bsr/gen/ssh/v1" + "github.com/stretchr/testify/require" + gssh "golang.org/x/crypto/ssh" + "google.golang.org/protobuf/proto" +) + +func Test_NewX11ForwardingRequest(t *testing.T) { + ctx := context.Background() + now := bsr.NewTimestamp(time.Now()) + message := x11ForwardingSigval{ + SingleConnection: true, + X11AuthenticationProtocol: "auth", + X11AuthenticationCookie: "chocolate chip", + X11ScreenNumber: 123, + } + payload := gssh.Marshal(message) + + tests := []struct { + name string + direction bsr.Direction + time *bsr.Timestamp + request *gssh.Request + expErr bool + expErrMsg string + }{ + { + name: "nil request", + direction: bsr.Inbound, + time: now, + expErr: true, + expErrMsg: "ssh.NewX11ForwardingRequest: request cannot be nil: invalid parameter", + }, + { + name: "empty direction", + time: now, + request: &gssh.Request{ + Type: X11ForwardingRequestType, + WantReply: false, + Payload: payload, + }, + expErr: true, + expErrMsg: "ssh.NewX11ForwardingRequest: invalid direction: invalid parameter", + }, + { + name: "empty time", + direction: bsr.Inbound, + request: &gssh.Request{ + Type: X11ForwardingRequestType, + WantReply: false, + Payload: payload, + }, + expErr: true, + expErrMsg: "ssh.NewX11ForwardingRequest: timestamp cannot be nil: invalid parameter", + }, + { + name: "bad type", + direction: bsr.Inbound, + time: now, + request: &gssh.Request{ + Type: "muahaha", + WantReply: false, + Payload: payload, + }, + expErr: true, + expErrMsg: `ssh.NewX11ForwardingRequest: request type must be "x11-req": invalid parameter`, + }, + { + name: "happy path", + direction: bsr.Inbound, + time: now, + request: &gssh.Request{ + Type: X11ForwardingRequestType, + WantReply: false, + Payload: payload, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + rc, err := NewX11ForwardingRequest(ctx, tt.direction, tt.time, tt.request) + if tt.expErr { + require.EqualError(t, err, tt.expErrMsg) + require.Nil(t, rc) + return + } + + require.NoError(t, err) + require.NotNil(t, rc) + + data, err := rc.MarshalData(ctx) + require.NoError(t, err) + dataPayload := &pssh.X11ForwardingRequest{} + err = proto.Unmarshal(data, dataPayload) + require.NoError(t, err) + require.Equal(t, dataPayload.SingleConnection, message.SingleConnection) + require.Equal(t, dataPayload.X11AuthenticationProtocol, message.X11AuthenticationProtocol) + require.Equal(t, dataPayload.X11AuthenticationCookie, message.X11AuthenticationCookie) + require.Equal(t, dataPayload.X11ScreenNumber, message.X11ScreenNumber) + require.Equal(t, dataPayload.RequestType, X11ForwardingRequestType) + require.Equal(t, dataPayload.WantReply, false) + }) + } +} diff --git a/internal/bsr/ssh/chunk_x11_request.go b/internal/bsr/ssh/chunk_x11_request.go new file mode 100644 index 000000000..b3e2a1c89 --- /dev/null +++ b/internal/bsr/ssh/chunk_x11_request.go @@ -0,0 +1,85 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package ssh + +import ( + "context" + "fmt" + + "github.com/hashicorp/boundary/internal/bsr" + pssh "github.com/hashicorp/boundary/internal/bsr/gen/ssh/v1" + "github.com/hashicorp/boundary/internal/bsr/internal/is" + gssh "golang.org/x/crypto/ssh" + "google.golang.org/protobuf/proto" +) + +const X11RequestType = "x11" + +// X11Request is a chunk to contain data for an SSH X11 request +type X11Request struct { + bsr.BaseChunk + *pssh.X11Request +} + +// MarshalData serializes an X11Request chunk +func (r *X11Request) MarshalData(ctx context.Context) ([]byte, error) { + const op = "ssh.(X11Request).MarshalData" + dataBytes, err := proto.Marshal(r.X11Request) + if err != nil { + return nil, fmt.Errorf("%s: failed to marshal data: %w", op, err) + } + + d := make([]byte, 0, len(dataBytes)) + d = append(d, dataBytes...) + + return d, nil +} + +// NewX11Request creates an X11Request chunk +func NewX11Request(ctx context.Context, d bsr.Direction, t *bsr.Timestamp, r *gssh.Request) (*X11Request, error) { + const op = "ssh.NewX11Request" + + if is.Nil(r) { + return nil, fmt.Errorf("%s: request cannot be nil: %w", op, bsr.ErrInvalidParameter) + } + if r.Type != X11RequestType { + return nil, fmt.Errorf("%s: request type must be %q: %w", op, X11RequestType, bsr.ErrInvalidParameter) + } + if !bsr.ValidDirection(d) { + return nil, fmt.Errorf("%s: invalid direction: %w", op, bsr.ErrInvalidParameter) + } + if is.Nil(t) { + return nil, fmt.Errorf("%s: timestamp cannot be nil: %w", op, bsr.ErrInvalidParameter) + } + + baseChunk, err := bsr.NewBaseChunk(ctx, Protocol, d, t, X11ReqChunkType) + if err != nil { + return nil, fmt.Errorf("%s: unable to create base chunk: %w", op, err) + } + + var sigval x11Sigval + if err := gssh.Unmarshal(r.Payload, &sigval); err != nil { + return nil, fmt.Errorf("%s: unable to unmarshal payload: %w", op, err) + } + reqData := &X11Request{ + BaseChunk: *baseChunk, + X11Request: &pssh.X11Request{ + RequestType: r.Type, + SenderChannel: sigval.SenderChannel, + InitialWindowSize: sigval.InitialWindowSize, + MaximumPacketSize: sigval.MaximumPacketSize, + OriginatorAddress: sigval.OriginatorAddress, + OriginatorPort: sigval.OriginatorPort, + }, + } + return reqData, nil +} + +type x11Sigval struct { + SenderChannel uint32 + InitialWindowSize uint32 + MaximumPacketSize uint32 + OriginatorAddress string + OriginatorPort uint32 +} diff --git a/internal/bsr/ssh/chunk_x11_request_test.go b/internal/bsr/ssh/chunk_x11_request_test.go new file mode 100644 index 000000000..f5249a24a --- /dev/null +++ b/internal/bsr/ssh/chunk_x11_request_test.go @@ -0,0 +1,115 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package ssh + +import ( + "context" + "testing" + "time" + + "github.com/hashicorp/boundary/internal/bsr" + pssh "github.com/hashicorp/boundary/internal/bsr/gen/ssh/v1" + "github.com/stretchr/testify/require" + gssh "golang.org/x/crypto/ssh" + "google.golang.org/protobuf/proto" +) + +func Test_NewX11Request(t *testing.T) { + ctx := context.Background() + now := bsr.NewTimestamp(time.Now()) + message := x11Sigval{ + SenderChannel: 12, + InitialWindowSize: 34, + MaximumPacketSize: 56, + OriginatorPort: 78, + OriginatorAddress: "addy", + } + payload := gssh.Marshal(message) + + tests := []struct { + name string + direction bsr.Direction + time *bsr.Timestamp + request *gssh.Request + expErr bool + expErrMsg string + }{ + { + name: "nil request", + direction: bsr.Inbound, + time: now, + expErr: true, + expErrMsg: "ssh.NewX11Request: request cannot be nil: invalid parameter", + }, + { + name: "empty direction", + time: now, + request: &gssh.Request{ + Type: X11RequestType, + WantReply: false, + Payload: payload, + }, + expErr: true, + expErrMsg: "ssh.NewX11Request: invalid direction: invalid parameter", + }, + { + name: "empty time", + direction: bsr.Inbound, + request: &gssh.Request{ + Type: X11RequestType, + WantReply: false, + Payload: payload, + }, + expErr: true, + expErrMsg: "ssh.NewX11Request: timestamp cannot be nil: invalid parameter", + }, + { + name: "bad type", + direction: bsr.Inbound, + time: now, + request: &gssh.Request{ + Type: "muahaha", + WantReply: false, + Payload: payload, + }, + expErr: true, + expErrMsg: `ssh.NewX11Request: request type must be "x11": invalid parameter`, + }, + { + name: "happy path", + direction: bsr.Inbound, + time: now, + request: &gssh.Request{ + Type: X11RequestType, + WantReply: false, + Payload: payload, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + rc, err := NewX11Request(ctx, tt.direction, tt.time, tt.request) + if tt.expErr { + require.EqualError(t, err, tt.expErrMsg) + require.Nil(t, rc) + return + } + + require.NoError(t, err) + require.NotNil(t, rc) + + data, err := rc.MarshalData(ctx) + require.NoError(t, err) + dataPayload := &pssh.X11Request{} + err = proto.Unmarshal(data, dataPayload) + require.NoError(t, err) + require.Equal(t, dataPayload.SenderChannel, message.SenderChannel) + require.Equal(t, dataPayload.InitialWindowSize, message.InitialWindowSize) + require.Equal(t, dataPayload.MaximumPacketSize, message.MaximumPacketSize) + require.Equal(t, dataPayload.SenderChannel, message.SenderChannel) + require.Equal(t, dataPayload.RequestType, X11RequestType) + }) + } +} diff --git a/internal/bsr/ssh/chunk_xonn_xoff_request.go b/internal/bsr/ssh/chunk_xonn_xoff_request.go new file mode 100644 index 000000000..63d0afdd7 --- /dev/null +++ b/internal/bsr/ssh/chunk_xonn_xoff_request.go @@ -0,0 +1,78 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package ssh + +import ( + "context" + "fmt" + + "github.com/hashicorp/boundary/internal/bsr" + pssh "github.com/hashicorp/boundary/internal/bsr/gen/ssh/v1" + "github.com/hashicorp/boundary/internal/bsr/internal/is" + gssh "golang.org/x/crypto/ssh" + "google.golang.org/protobuf/proto" +) + +const XonXoffRequestType = "xon-xoff" + +// XonXoffRequest is a chunk to contain data for an SSH Xon Xoff request +type XonXoffRequest struct { + bsr.BaseChunk + *pssh.XonXoffRequest +} + +// MarshalData serializes an XonXoffRequest chunk +func (r *XonXoffRequest) MarshalData(ctx context.Context) ([]byte, error) { + const op = "ssh.(XonXoffRequest).MarshalData" + dataBytes, err := proto.Marshal(r.XonXoffRequest) + if err != nil { + return nil, fmt.Errorf("%s: failed to marshal data: %w", op, err) + } + + d := make([]byte, 0, len(dataBytes)) + d = append(d, dataBytes...) + + return d, nil +} + +// NewXonXoffRequest creates an XonXoffRequest chunk +func NewXonXoffRequest(ctx context.Context, d bsr.Direction, t *bsr.Timestamp, r *gssh.Request) (*XonXoffRequest, error) { + const op = "ssh.NewXonXoffRequest" + + if is.Nil(r) { + return nil, fmt.Errorf("%s: request cannot be nil: %w", op, bsr.ErrInvalidParameter) + } + if r.Type != XonXoffRequestType { + return nil, fmt.Errorf("%s: request type must be %q: %w", op, XonXoffRequestType, bsr.ErrInvalidParameter) + } + if !bsr.ValidDirection(d) { + return nil, fmt.Errorf("%s: invalid direction: %w", op, bsr.ErrInvalidParameter) + } + if is.Nil(t) { + return nil, fmt.Errorf("%s: timestamp cannot be nil: %w", op, bsr.ErrInvalidParameter) + } + + baseChunk, err := bsr.NewBaseChunk(ctx, Protocol, d, t, XonXoffReqChunkType) + if err != nil { + return nil, fmt.Errorf("%s: unable to create base chunk: %w", op, err) + } + + var sigval xonXoffSigval + if err := gssh.Unmarshal(r.Payload, &sigval); err != nil { + return nil, fmt.Errorf("%s: unable to unmarshal payload: %w", op, err) + } + reqData := &XonXoffRequest{ + BaseChunk: *baseChunk, + XonXoffRequest: &pssh.XonXoffRequest{ + RequestType: r.Type, + WantReply: r.WantReply, + ClientCanDo: sigval.ClientCanDo, + }, + } + return reqData, nil +} + +type xonXoffSigval struct { + ClientCanDo bool +} diff --git a/internal/bsr/ssh/chunk_xonn_xoff_request_test.go b/internal/bsr/ssh/chunk_xonn_xoff_request_test.go new file mode 100644 index 000000000..0e99a6505 --- /dev/null +++ b/internal/bsr/ssh/chunk_xonn_xoff_request_test.go @@ -0,0 +1,109 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package ssh + +import ( + "context" + "testing" + "time" + + "github.com/hashicorp/boundary/internal/bsr" + pssh "github.com/hashicorp/boundary/internal/bsr/gen/ssh/v1" + "github.com/stretchr/testify/require" + gssh "golang.org/x/crypto/ssh" + "google.golang.org/protobuf/proto" +) + +func Test_NewXonXoffRequest(t *testing.T) { + ctx := context.Background() + now := bsr.NewTimestamp(time.Now()) + message := xonXoffSigval{ + ClientCanDo: true, + } + payload := gssh.Marshal(message) + + tests := []struct { + name string + direction bsr.Direction + time *bsr.Timestamp + request *gssh.Request + expErr bool + expErrMsg string + }{ + { + name: "nil request", + direction: bsr.Inbound, + time: now, + expErr: true, + expErrMsg: "ssh.NewXonXoffRequest: request cannot be nil: invalid parameter", + }, + { + name: "empty direction", + time: now, + request: &gssh.Request{ + Type: XonXoffRequestType, + WantReply: false, + Payload: payload, + }, + expErr: true, + expErrMsg: "ssh.NewXonXoffRequest: invalid direction: invalid parameter", + }, + { + name: "empty time", + direction: bsr.Inbound, + request: &gssh.Request{ + Type: XonXoffRequestType, + WantReply: false, + Payload: payload, + }, + expErr: true, + expErrMsg: "ssh.NewXonXoffRequest: timestamp cannot be nil: invalid parameter", + }, + { + name: "bad type", + direction: bsr.Inbound, + time: now, + request: &gssh.Request{ + Type: "muahaha", + WantReply: false, + Payload: payload, + }, + expErr: true, + expErrMsg: `ssh.NewXonXoffRequest: request type must be "xon-xoff": invalid parameter`, + }, + { + name: "happy path", + direction: bsr.Inbound, + time: now, + request: &gssh.Request{ + Type: XonXoffRequestType, + WantReply: false, + Payload: payload, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + rc, err := NewXonXoffRequest(ctx, tt.direction, tt.time, tt.request) + if tt.expErr { + require.EqualError(t, err, tt.expErrMsg) + require.Nil(t, rc) + return + } + + require.NoError(t, err) + require.NotNil(t, rc) + + data, err := rc.MarshalData(ctx) + require.NoError(t, err) + dataPayload := &pssh.XonXoffRequest{} + err = proto.Unmarshal(data, dataPayload) + require.NoError(t, err) + require.Equal(t, dataPayload.ClientCanDo, message.ClientCanDo) + require.Equal(t, dataPayload.RequestType, XonXoffRequestType) + require.Equal(t, dataPayload.WantReply, false) + }) + } +} diff --git a/internal/bsr/ssh/doc.go b/internal/bsr/ssh/doc.go new file mode 100644 index 000000000..fe223c79c --- /dev/null +++ b/internal/bsr/ssh/doc.go @@ -0,0 +1,7 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +/* +Package ssh defines chunk types for recordings of the ssh protocol. +*/ +package ssh