feat(bsr/ssh): Add SSH Request type chunks

This commit is contained in:
irenarindos authored and Timothy Messier committed 2023-05-31 12:18:11 +00:00
1 parent 26bd86fb07
commit ba476ca73b
46 files changed
+6267 -1

No files matched your search

+2 -1
View File
@@ -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
+1
View File
@@ -4,3 +4,4 @@
version: v1
directories:
- internal/proto
- internal/bsr/proto
+13
View File
@@ -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
File diff suppressed because it is too large. Load diff
+7
View File
@@ -0,0 +1,7 @@
# Copyright (c) HashiCorp, Inc.
# SPDX-License-Identifier: MPL-2.0
version: v1
lint:
use:
- DEFAULT
+368
View File
@@ -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;
}
+65
View File
@@ -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
}
+78
View File
@@ -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
}
@@ -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)
})
}
}
@@ -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
}
@@ -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)
})
}
}
@@ -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
}
@@ -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)
})
}
}
+80
View File
@@ -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
}
+108
View File
@@ -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)
})
}
}
+78
View File
@@ -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
}
+107
View File
@@ -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)
})
}
}
@@ -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
}
@@ -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)
})
}
}
@@ -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
}
@@ -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)
})
}
}
@@ -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
}
@@ -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)
})
}
}
+88
View File
@@ -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
}
+138
View File
@@ -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)
})
}
}
+81
View File
@@ -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
}
@@ -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)
})
}
}
+69
View File
@@ -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
}
@@ -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)
})
}
}
+78
View File
@@ -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
}
@@ -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)
})
}
}
@@ -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
}
@@ -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)
})
}
}
@@ -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
}
@@ -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)
})
}
}
+64
View File
@@ -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
}
@@ -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)
})
}
}
@@ -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
}
@@ -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)
})
}
}
@@ -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
}
@@ -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)
})
}
}
+85
View File
@@ -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
}
+115
View File
@@ -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)
})
}
}
@@ -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
}
@@ -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)
})
}
}
+7
View File
@@ -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