This was originally introduced to ensure that the meta files only included ascii characters. However, this makes reading the protocol from meta file less reliable since it does not exactly match the protocol string. Instead protocol strings will be recorded in the meta files as is. Generally protocol strings should include readable characters. This also updates the ssh.Protocol to be 4 readable characters.
306 lines
8.2 KiB
Go
306 lines
8.2 KiB
Go
// 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"
|
|
"google.golang.org/protobuf/proto"
|
|
)
|
|
|
|
func init() {
|
|
for _, ct := range []bsr.ChunkType{
|
|
DataChunkType,
|
|
BreakReqChunkType,
|
|
CancelTCPIPForwardReqChunkType,
|
|
DirectTCPIPReqChunkType,
|
|
EnvReqChunkType,
|
|
ExecReqChunkType,
|
|
ExitSignalReqChunkType,
|
|
ExitStatusReqChunkType,
|
|
ForwardedTCPIPReqChunkType,
|
|
PtyReqChunkType,
|
|
SessionReqChunkType,
|
|
ShellReqChunkType,
|
|
SignalReqChunkType,
|
|
SubsystemReqChunkType,
|
|
TCPIPForwardReqChunkType,
|
|
WindowChangeReqChunkType,
|
|
X11ForwardingReqChunkType,
|
|
X11ReqChunkType,
|
|
XonXoffReqChunkType,
|
|
UnknownReqChunkType,
|
|
} {
|
|
if err := bsr.RegisterChunkType(Protocol, ct, DecodeChunk); err != nil {
|
|
panic(err)
|
|
}
|
|
}
|
|
}
|
|
|
|
const (
|
|
// Protocol is used to identify chunks that are recorded from SSH.
|
|
Protocol bsr.Protocol = "BSSH"
|
|
|
|
// MaxPacketSize is used by the DataWriter to determine if SSH data should
|
|
// be broken into multiple chunks.
|
|
MaxPacketSize = 256 * 1024
|
|
)
|
|
|
|
// Chunk types
|
|
const (
|
|
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
|
|
}
|
|
|
|
// DecodeChunk will decode any known SSH Chunk type. If the chunk type is
|
|
// not an ssh chunk type, and error is returned.
|
|
func DecodeChunk(_ context.Context, bc *bsr.BaseChunk, data []byte) (bsr.Chunk, error) {
|
|
const op = "ssh.DecodeChunk"
|
|
|
|
if is.Nil(bc) {
|
|
return nil, fmt.Errorf("%s: nil base chunk: %w", op, bsr.ErrInvalidParameter)
|
|
}
|
|
|
|
if bc.Protocol != Protocol {
|
|
return nil, fmt.Errorf("%s: invalid protocol %s", op, bc.Protocol)
|
|
}
|
|
|
|
switch bc.Type {
|
|
case DataChunkType:
|
|
return &DataChunk{
|
|
BaseChunk: bc,
|
|
Data: data,
|
|
}, nil
|
|
}
|
|
|
|
if len(data) <= 0 {
|
|
return nil, fmt.Errorf("%s: not enough data", op)
|
|
}
|
|
|
|
switch bc.Type {
|
|
case BreakReqChunkType:
|
|
mm := &pssh.BreakRequest{}
|
|
if err := proto.Unmarshal(data, mm); err != nil {
|
|
return nil, fmt.Errorf("%s: %w", op, err)
|
|
}
|
|
return &BreakRequest{
|
|
BaseChunk: bc,
|
|
BreakRequest: mm,
|
|
}, nil
|
|
case CancelTCPIPForwardReqChunkType:
|
|
mm := &pssh.CancelTCPIPForwardRequest{}
|
|
if err := proto.Unmarshal(data, mm); err != nil {
|
|
return nil, fmt.Errorf("%s: %w", op, err)
|
|
}
|
|
return &CancelTCPIPForwardRequest{
|
|
BaseChunk: bc,
|
|
CancelTCPIPForwardRequest: mm,
|
|
}, nil
|
|
case DirectTCPIPReqChunkType:
|
|
mm := &pssh.DirectTCPIPRequest{}
|
|
if err := proto.Unmarshal(data, mm); err != nil {
|
|
return nil, fmt.Errorf("%s: %w", op, err)
|
|
}
|
|
return &DirectTCPIPRequest{
|
|
BaseChunk: bc,
|
|
DirectTCPIPRequest: mm,
|
|
}, nil
|
|
case EnvReqChunkType:
|
|
mm := &pssh.EnvRequest{}
|
|
if err := proto.Unmarshal(data, mm); err != nil {
|
|
return nil, fmt.Errorf("%s: %w", op, err)
|
|
}
|
|
return &EnvRequest{
|
|
BaseChunk: bc,
|
|
EnvRequest: mm,
|
|
}, nil
|
|
case ExecReqChunkType:
|
|
mm := &pssh.ExecRequest{}
|
|
if err := proto.Unmarshal(data, mm); err != nil {
|
|
return nil, fmt.Errorf("%s: %w", op, err)
|
|
}
|
|
return &ExecRequest{
|
|
BaseChunk: bc,
|
|
ExecRequest: mm,
|
|
}, nil
|
|
case ExitSignalReqChunkType:
|
|
mm := &pssh.ExitSignalRequest{}
|
|
if err := proto.Unmarshal(data, mm); err != nil {
|
|
return nil, fmt.Errorf("%s: %w", op, err)
|
|
}
|
|
return &ExitSignalRequest{
|
|
BaseChunk: bc,
|
|
ExitSignalRequest: mm,
|
|
}, nil
|
|
case ExitStatusReqChunkType:
|
|
mm := &pssh.ExitStatusRequest{}
|
|
if err := proto.Unmarshal(data, mm); err != nil {
|
|
return nil, fmt.Errorf("%s: %w", op, err)
|
|
}
|
|
return &ExitStatusRequest{
|
|
BaseChunk: bc,
|
|
ExitStatusRequest: mm,
|
|
}, nil
|
|
case ForwardedTCPIPReqChunkType:
|
|
mm := &pssh.ForwardedTCPIPRequest{}
|
|
if err := proto.Unmarshal(data, mm); err != nil {
|
|
return nil, fmt.Errorf("%s: %w", op, err)
|
|
}
|
|
return &ForwardedTCPIPRequest{
|
|
BaseChunk: bc,
|
|
ForwardedTCPIPRequest: mm,
|
|
}, nil
|
|
case PtyReqChunkType:
|
|
mm := &pssh.PtyRequest{}
|
|
if err := proto.Unmarshal(data, mm); err != nil {
|
|
return nil, fmt.Errorf("%s: %w", op, err)
|
|
}
|
|
return &PtyRequest{
|
|
BaseChunk: bc,
|
|
PtyRequest: mm,
|
|
}, nil
|
|
case SessionReqChunkType:
|
|
mm := &pssh.SessionRequest{}
|
|
if err := proto.Unmarshal(data, mm); err != nil {
|
|
return nil, fmt.Errorf("%s: %w", op, err)
|
|
}
|
|
return &SessionRequest{
|
|
BaseChunk: bc,
|
|
SessionRequest: mm,
|
|
}, nil
|
|
case ShellReqChunkType:
|
|
mm := &pssh.ShellRequest{}
|
|
if err := proto.Unmarshal(data, mm); err != nil {
|
|
return nil, fmt.Errorf("%s: %w", op, err)
|
|
}
|
|
return &ShellRequest{
|
|
BaseChunk: bc,
|
|
ShellRequest: mm,
|
|
}, nil
|
|
case SignalReqChunkType:
|
|
mm := &pssh.SignalRequest{}
|
|
if err := proto.Unmarshal(data, mm); err != nil {
|
|
return nil, fmt.Errorf("%s: %w", op, err)
|
|
}
|
|
return &SignalRequest{
|
|
BaseChunk: bc,
|
|
SignalRequest: mm,
|
|
}, nil
|
|
case SubsystemReqChunkType:
|
|
mm := &pssh.SubsystemRequest{}
|
|
if err := proto.Unmarshal(data, mm); err != nil {
|
|
return nil, fmt.Errorf("%s: %w", op, err)
|
|
}
|
|
return &SubsystemRequest{
|
|
BaseChunk: bc,
|
|
SubsystemRequest: mm,
|
|
}, nil
|
|
case TCPIPForwardReqChunkType:
|
|
mm := &pssh.TCPIPForwardRequest{}
|
|
if err := proto.Unmarshal(data, mm); err != nil {
|
|
return nil, fmt.Errorf("%s: %w", op, err)
|
|
}
|
|
return &TCPIPForwardRequest{
|
|
BaseChunk: bc,
|
|
TCPIPForwardRequest: mm,
|
|
}, nil
|
|
case WindowChangeReqChunkType:
|
|
mm := &pssh.WindowChangeRequest{}
|
|
if err := proto.Unmarshal(data, mm); err != nil {
|
|
return nil, fmt.Errorf("%s: %w", op, err)
|
|
}
|
|
return &WindowChangeRequest{
|
|
BaseChunk: bc,
|
|
WindowChangeRequest: mm,
|
|
}, nil
|
|
case X11ForwardingReqChunkType:
|
|
mm := &pssh.X11ForwardingRequest{}
|
|
if err := proto.Unmarshal(data, mm); err != nil {
|
|
return nil, fmt.Errorf("%s: %w", op, err)
|
|
}
|
|
return &X11ForwardingRequest{
|
|
BaseChunk: bc,
|
|
X11ForwardingRequest: mm,
|
|
}, nil
|
|
case X11ReqChunkType:
|
|
mm := &pssh.X11Request{}
|
|
if err := proto.Unmarshal(data, mm); err != nil {
|
|
return nil, fmt.Errorf("%s: %w", op, err)
|
|
}
|
|
return &X11Request{
|
|
BaseChunk: bc,
|
|
X11Request: mm,
|
|
}, nil
|
|
case XonXoffReqChunkType:
|
|
mm := &pssh.XonXoffRequest{}
|
|
if err := proto.Unmarshal(data, mm); err != nil {
|
|
return nil, fmt.Errorf("%s: %w", op, err)
|
|
}
|
|
return &XonXoffRequest{
|
|
BaseChunk: bc,
|
|
XonXoffRequest: mm,
|
|
}, nil
|
|
case UnknownReqChunkType:
|
|
mm := &pssh.UnknownRequest{}
|
|
if err := proto.Unmarshal(data, mm); err != nil {
|
|
return nil, fmt.Errorf("%s: %w", op, err)
|
|
}
|
|
return &UnknownRequest{
|
|
BaseChunk: bc,
|
|
UnknownRequest: mm,
|
|
}, nil
|
|
|
|
default:
|
|
return nil, fmt.Errorf("%s: unsupported chunk type %s", op, bc.Type)
|
|
}
|
|
}
|