mirror of https://github.com/hashicorp/boundary
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
559 lines
15 KiB
559 lines
15 KiB
// Copyright (c) HashiCorp, Inc.
|
|
// SPDX-License-Identifier: BUSL-1.1
|
|
|
|
package bsr
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"io"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/hashicorp/boundary/internal/bsr/internal/checksum"
|
|
"github.com/hashicorp/boundary/internal/bsr/internal/fstest"
|
|
"github.com/hashicorp/boundary/internal/bsr/kms"
|
|
"github.com/hashicorp/go-kms-wrapping/v2/extras/crypto"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestSyncBsrKeys(t *testing.T) {
|
|
ctx := context.Background()
|
|
|
|
keys, err := kms.CreateKeys(ctx, kms.TestWrapper(t), "session")
|
|
require.NoError(t, err)
|
|
f := &fstest.MemFS{}
|
|
|
|
fc, err := f.New(ctx, fmt.Sprintf(bsrFileNameTemplate, "session-id"))
|
|
require.NoError(t, err)
|
|
|
|
c, err := newContainer(ctx, SessionContainer, fc, keys)
|
|
require.NoError(t, err)
|
|
require.NotNil(t, c)
|
|
|
|
cases := []struct {
|
|
name string
|
|
fname string
|
|
data []byte
|
|
expErr bool
|
|
expErrMsg string
|
|
}{
|
|
{
|
|
name: "no filename",
|
|
data: []byte("got no name"),
|
|
expErr: true,
|
|
expErrMsg: "bsr.(container).syncBsrKey: missing file name invalid parameter",
|
|
},
|
|
{
|
|
name: "no data",
|
|
fname: "i have a name",
|
|
expErr: true,
|
|
expErrMsg: "bsr.(container).syncBsrKey: missing data payload invalid parameter",
|
|
},
|
|
{
|
|
name: "success",
|
|
fname: "i have a name",
|
|
data: []byte("payload coming thru"),
|
|
},
|
|
}
|
|
for _, tc := range cases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
err := c.syncBsrKey(ctx, tc.fname, tc.data)
|
|
|
|
if tc.expErr {
|
|
require.EqualError(t, err, tc.expErrMsg)
|
|
return
|
|
}
|
|
require.NoError(t, err)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGetFailedItems(t *testing.T) {
|
|
t.Parallel()
|
|
cases := []struct {
|
|
name string
|
|
checksumValidation ContainerChecksumValidation
|
|
expectedChecksums ContainerChecksumValidation
|
|
}{
|
|
{
|
|
name: "empty",
|
|
checksumValidation: ContainerChecksumValidation{},
|
|
expectedChecksums: ContainerChecksumValidation{},
|
|
},
|
|
{
|
|
name: "no failed items",
|
|
checksumValidation: ContainerChecksumValidation{
|
|
"test": &FileChecksumValidation{
|
|
Filename: "test",
|
|
Passed: true,
|
|
},
|
|
},
|
|
expectedChecksums: ContainerChecksumValidation{},
|
|
},
|
|
{
|
|
name: "failed item",
|
|
checksumValidation: ContainerChecksumValidation{
|
|
"test": &FileChecksumValidation{
|
|
Filename: "test",
|
|
Passed: true,
|
|
},
|
|
"capture": &FileChecksumValidation{
|
|
Filename: "capture",
|
|
},
|
|
},
|
|
expectedChecksums: ContainerChecksumValidation{
|
|
"capture": &FileChecksumValidation{
|
|
Filename: "capture",
|
|
},
|
|
},
|
|
},
|
|
}
|
|
for _, tc := range cases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
require.Equal(t, tc.expectedChecksums, tc.checksumValidation.GetFailedItems())
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestSessionValidateChecksums(t *testing.T) {
|
|
ctx := context.Background()
|
|
protocol := TestRegisterSummaryAllocFunc(t)
|
|
cases := []struct {
|
|
name string
|
|
c *container
|
|
expectedChecksums ContainerChecksumValidation
|
|
expectedErr string
|
|
}{
|
|
{
|
|
name: "missing checksums",
|
|
c: &container{},
|
|
expectedErr: "missing checksums",
|
|
},
|
|
{
|
|
name: "missing keys",
|
|
c: &container{
|
|
shaSums: checksum.Sha256Sums{
|
|
"test": []byte("test"),
|
|
},
|
|
},
|
|
expectedErr: "missing keys",
|
|
},
|
|
{
|
|
name: "file does not exist",
|
|
c: func() *container {
|
|
sessionId := "session_123456789"
|
|
keys, err := kms.CreateKeys(ctx, kms.TestWrapper(t), sessionId)
|
|
require.NoError(t, err)
|
|
|
|
fs := &fstest.MemFS{}
|
|
sc, err := fs.New(ctx, fmt.Sprintf(bsrFileNameTemplate, sessionId))
|
|
require.NoError(t, err)
|
|
|
|
c, err := newContainer(ctx, SessionContainer, sc, keys)
|
|
require.NoError(t, err)
|
|
|
|
c.shaSums = checksum.Sha256Sums{
|
|
"test": []byte("test"),
|
|
}
|
|
|
|
return c
|
|
}(),
|
|
expectedChecksums: ContainerChecksumValidation{
|
|
"test": &FileChecksumValidation{
|
|
Filename: "test",
|
|
Passed: false,
|
|
Error: fmt.Errorf("bsr.(container).computeFileChecksum: file test does not exist: does not exist"),
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "failed checksum match",
|
|
c: func() *container {
|
|
sessionId := "session_123456789"
|
|
keys, err := kms.CreateKeys(ctx, kms.TestWrapper(t), sessionId)
|
|
require.NoError(t, err)
|
|
|
|
fs := &fstest.MemFS{}
|
|
sc, err := fs.New(ctx, fmt.Sprintf(bsrFileNameTemplate, sessionId))
|
|
require.NoError(t, err)
|
|
|
|
c, err := newContainer(ctx, SessionContainer, sc, keys)
|
|
require.NoError(t, err)
|
|
|
|
c.shaSums = checksum.Sha256Sums{
|
|
"test": []byte("test"),
|
|
}
|
|
|
|
f, err := c.create(ctx, "test")
|
|
require.NoError(t, err)
|
|
_, err = f.Write([]byte("hello world"))
|
|
require.NoError(t, err)
|
|
require.NoError(t, f.Close())
|
|
|
|
return c
|
|
}(),
|
|
expectedChecksums: ContainerChecksumValidation{
|
|
"test": &FileChecksumValidation{
|
|
Filename: "test",
|
|
Passed: false,
|
|
Error: fmt.Errorf("checksum mismatch"),
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "valid session container",
|
|
c: func() *container {
|
|
sessionId := "session_123456789"
|
|
fs := &fstest.MemFS{}
|
|
keys, err := kms.CreateKeys(ctx, kms.TestWrapper(t), sessionId)
|
|
require.NoError(t, err)
|
|
keyFn := func(w kms.WrappedKeys) (kms.UnwrappedKeys, error) {
|
|
u := kms.UnwrappedKeys{
|
|
BsrKey: keys.BsrKey,
|
|
PrivKey: keys.PrivKey,
|
|
}
|
|
return u, nil
|
|
}
|
|
|
|
s, err := NewSession(ctx, TestSessionRecordingMeta(sessionId, protocol), TestSessionMeta(sessionId), fs, keys)
|
|
require.NoError(t, err)
|
|
require.NoError(t, s.EncodeSummary(ctx, &BaseSessionSummary{
|
|
Id: sessionId,
|
|
ConnectionCount: 1,
|
|
StartTime: time.Now(),
|
|
EndTime: time.Now(),
|
|
}))
|
|
require.NoError(t, s.Close(ctx))
|
|
|
|
s, err = OpenSession(ctx, sessionId, fs, keyFn)
|
|
require.NoError(t, err)
|
|
|
|
return s.container
|
|
}(),
|
|
expectedChecksums: ContainerChecksumValidation{
|
|
bsrPubKeyFileName: &FileChecksumValidation{
|
|
Filename: bsrPubKeyFileName,
|
|
Passed: true,
|
|
},
|
|
pubKeyBsrSignatureFileName: &FileChecksumValidation{
|
|
Filename: pubKeyBsrSignatureFileName,
|
|
Passed: true,
|
|
},
|
|
pubKeySelfSignatureFileName: &FileChecksumValidation{
|
|
Filename: pubKeySelfSignatureFileName,
|
|
Passed: true,
|
|
},
|
|
wrappedBsrKeyFileName: &FileChecksumValidation{
|
|
Filename: wrappedBsrKeyFileName,
|
|
Passed: true,
|
|
},
|
|
wrappedPrivKeyFileName: &FileChecksumValidation{
|
|
Filename: wrappedPrivKeyFileName,
|
|
Passed: true,
|
|
},
|
|
sessionMetaFileName: &FileChecksumValidation{
|
|
Filename: sessionMetaFileName,
|
|
Passed: true,
|
|
},
|
|
"session-recording.meta": &FileChecksumValidation{
|
|
Filename: "session-recording.meta",
|
|
Passed: true,
|
|
},
|
|
"session-recording-summary.json": &FileChecksumValidation{
|
|
Filename: "session-recording-summary.json",
|
|
Passed: true,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "valid connection container",
|
|
c: func() *container {
|
|
sessionId := "s_123456789"
|
|
connectionId := "cr_123456789"
|
|
fs := &fstest.MemFS{}
|
|
keys, err := kms.CreateKeys(ctx, kms.TestWrapper(t), sessionId)
|
|
require.NoError(t, err)
|
|
keyFn := func(w kms.WrappedKeys) (kms.UnwrappedKeys, error) {
|
|
u := kms.UnwrappedKeys{
|
|
BsrKey: keys.BsrKey,
|
|
PrivKey: keys.PrivKey,
|
|
}
|
|
return u, nil
|
|
}
|
|
|
|
s, err := NewSession(ctx, TestSessionRecordingMeta(sessionId, protocol), TestSessionMeta(sessionId), fs, keys)
|
|
require.NoError(t, err)
|
|
require.NoError(t, s.EncodeSummary(ctx, &BaseSessionSummary{
|
|
Id: sessionId,
|
|
ConnectionCount: 1,
|
|
StartTime: time.Now(),
|
|
EndTime: time.Now(),
|
|
}))
|
|
|
|
c, err := s.NewConnection(ctx, &ConnectionRecordingMeta{
|
|
Id: connectionId,
|
|
channels: map[string]bool{
|
|
"test": true,
|
|
},
|
|
})
|
|
require.NoError(t, err)
|
|
require.NoError(t, c.EncodeSummary(ctx, &BaseConnectionSummary{
|
|
Id: connectionId,
|
|
ChannelCount: 1,
|
|
StartTime: time.Now(),
|
|
EndTime: time.Now(),
|
|
BytesUp: 256,
|
|
BytesDown: 256,
|
|
}))
|
|
|
|
inbound, err := c.NewRequestsWriter(ctx, Inbound)
|
|
require.NoError(t, err)
|
|
n, err := inbound.Write([]byte("hello world"))
|
|
require.NoError(t, err)
|
|
require.Equal(t, len("hello world"), n)
|
|
closer := inbound.(io.Closer)
|
|
require.NoError(t, closer.Close())
|
|
|
|
outbound, err := c.NewRequestsWriter(ctx, Outbound)
|
|
require.NoError(t, err)
|
|
n, err = outbound.Write([]byte("hello world"))
|
|
require.NoError(t, err)
|
|
require.Equal(t, len("hello world"), n)
|
|
closer = outbound.(io.Closer)
|
|
require.NoError(t, closer.Close())
|
|
|
|
require.NoError(t, c.Close(ctx))
|
|
require.NoError(t, s.Close(ctx))
|
|
|
|
s, err = OpenSession(ctx, sessionId, fs, keyFn)
|
|
require.NoError(t, err)
|
|
|
|
c, err = s.OpenConnection(ctx, connectionId)
|
|
require.NoError(t, err)
|
|
|
|
return c.container
|
|
}(),
|
|
expectedChecksums: ContainerChecksumValidation{
|
|
"requests-inbound.data": &FileChecksumValidation{
|
|
Filename: "requests-inbound.data",
|
|
Passed: true,
|
|
},
|
|
"requests-outbound.data": &FileChecksumValidation{
|
|
Filename: "requests-outbound.data",
|
|
Passed: true,
|
|
},
|
|
"connection-recording.meta": &FileChecksumValidation{
|
|
Filename: "connection-recording.meta",
|
|
Passed: true,
|
|
},
|
|
"connection-recording-summary.json": &FileChecksumValidation{
|
|
Filename: "connection-recording-summary.json",
|
|
Passed: true,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "valid channel container",
|
|
c: func() *container {
|
|
sessionId := "s_123456789"
|
|
connectionId := "cr_123456789"
|
|
channelId := "chr_123456789"
|
|
fs := &fstest.MemFS{}
|
|
keys, err := kms.CreateKeys(ctx, kms.TestWrapper(t), sessionId)
|
|
require.NoError(t, err)
|
|
keyFn := func(w kms.WrappedKeys) (kms.UnwrappedKeys, error) {
|
|
u := kms.UnwrappedKeys{
|
|
BsrKey: keys.BsrKey,
|
|
PrivKey: keys.PrivKey,
|
|
}
|
|
return u, nil
|
|
}
|
|
|
|
s, err := NewSession(ctx, TestSessionRecordingMeta(sessionId, protocol), TestSessionMeta(sessionId), fs, keys, WithSupportsMultiplex(true))
|
|
require.NoError(t, err)
|
|
require.NoError(t, s.EncodeSummary(ctx, &BaseSessionSummary{
|
|
Id: sessionId,
|
|
ConnectionCount: 1,
|
|
StartTime: time.Now(),
|
|
EndTime: time.Now(),
|
|
}))
|
|
|
|
c, err := s.NewConnection(ctx, &ConnectionRecordingMeta{
|
|
Id: connectionId,
|
|
channels: map[string]bool{
|
|
"test": true,
|
|
},
|
|
})
|
|
require.NoError(t, err)
|
|
require.NoError(t, c.EncodeSummary(ctx, &BaseConnectionSummary{
|
|
Id: connectionId,
|
|
ChannelCount: 1,
|
|
StartTime: time.Now(),
|
|
EndTime: time.Now(),
|
|
BytesUp: 256,
|
|
BytesDown: 256,
|
|
}))
|
|
|
|
chr, err := c.NewChannel(ctx, &ChannelRecordingMeta{
|
|
Id: channelId,
|
|
Type: "chan",
|
|
})
|
|
require.NoError(t, err)
|
|
require.NoError(t, chr.EncodeSummary(ctx, &ChannelRecordingMeta{
|
|
Id: channelId,
|
|
Type: "chan",
|
|
}))
|
|
|
|
inboundReq, err := chr.NewRequestsWriter(ctx, Inbound)
|
|
require.NoError(t, err)
|
|
n, err := inboundReq.Write([]byte("hello world"))
|
|
require.NoError(t, err)
|
|
require.Equal(t, len("hello world"), n)
|
|
closer := inboundReq.(io.Closer)
|
|
require.NoError(t, closer.Close())
|
|
|
|
outboundReq, err := chr.NewRequestsWriter(ctx, Outbound)
|
|
require.NoError(t, err)
|
|
n, err = outboundReq.Write([]byte("hello world"))
|
|
require.NoError(t, err)
|
|
require.Equal(t, len("hello world"), n)
|
|
closer = outboundReq.(io.Closer)
|
|
require.NoError(t, closer.Close())
|
|
|
|
inboundMsg, err := chr.NewMessagesWriter(ctx, Inbound)
|
|
require.NoError(t, err)
|
|
n, err = inboundMsg.Write([]byte("hello world"))
|
|
require.NoError(t, err)
|
|
require.Equal(t, len("hello world"), n)
|
|
closer = inboundMsg.(io.Closer)
|
|
require.NoError(t, closer.Close())
|
|
|
|
outboundMsg, err := chr.NewMessagesWriter(ctx, Outbound)
|
|
require.NoError(t, err)
|
|
n, err = outboundMsg.Write([]byte("hello world"))
|
|
require.NoError(t, err)
|
|
require.Equal(t, len("hello world"), n)
|
|
closer = outboundMsg.(io.Closer)
|
|
require.NoError(t, closer.Close())
|
|
|
|
require.NoError(t, chr.Close(ctx))
|
|
require.NoError(t, c.Close(ctx))
|
|
require.NoError(t, s.Close(ctx))
|
|
|
|
s, err = OpenSession(ctx, sessionId, fs, keyFn)
|
|
require.NoError(t, err)
|
|
|
|
c, err = s.OpenConnection(ctx, connectionId)
|
|
require.NoError(t, err)
|
|
|
|
chr, err = c.OpenChannel(ctx, channelId)
|
|
require.NoError(t, err)
|
|
|
|
return chr.container
|
|
}(),
|
|
expectedChecksums: ContainerChecksumValidation{
|
|
"requests-inbound.data": &FileChecksumValidation{
|
|
Filename: "requests-inbound.data",
|
|
Passed: true,
|
|
},
|
|
"requests-outbound.data": &FileChecksumValidation{
|
|
Filename: "requests-outbound.data",
|
|
Passed: true,
|
|
},
|
|
"messages-inbound.data": &FileChecksumValidation{
|
|
Filename: "messages-inbound.data",
|
|
Passed: true,
|
|
},
|
|
"messages-outbound.data": &FileChecksumValidation{
|
|
Filename: "messages-outbound.data",
|
|
Passed: true,
|
|
},
|
|
"channel-recording.meta": &FileChecksumValidation{
|
|
Filename: "channel-recording.meta",
|
|
Passed: true,
|
|
},
|
|
"channel-recording-summary.json": &FileChecksumValidation{
|
|
Filename: "channel-recording-summary.json",
|
|
Passed: true,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
for _, tc := range cases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
validatedChecksums, err := tc.c.ValidateChecksums(ctx)
|
|
if tc.expectedErr != "" {
|
|
require.Error(t, err)
|
|
assert.Contains(t, err.Error(), tc.expectedErr)
|
|
return
|
|
}
|
|
require.NoError(t, err)
|
|
require.NotEmpty(t, validatedChecksums)
|
|
|
|
require.Equal(t, len(tc.expectedChecksums), len(validatedChecksums))
|
|
for fileName, expectedStatus := range tc.expectedChecksums {
|
|
actualStatus, ok := validatedChecksums[fileName]
|
|
require.True(t, ok, fmt.Sprintf("missing %s", fileName))
|
|
assert.Equal(t, expectedStatus.Passed, actualStatus.Passed)
|
|
assert.Equal(t, expectedStatus.Filename, actualStatus.Filename)
|
|
if expectedStatus.Error == nil {
|
|
assert.NoError(t, actualStatus.Error)
|
|
} else {
|
|
require.Error(t, actualStatus.Error)
|
|
assert.Equal(t, expectedStatus.Error.Error(), actualStatus.Error.Error())
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestComputeFileChecksum(t *testing.T) {
|
|
ctx := context.Background()
|
|
|
|
keys, err := kms.CreateKeys(ctx, kms.TestWrapper(t), "session")
|
|
require.NoError(t, err)
|
|
f := &fstest.MemFS{}
|
|
|
|
fc, err := f.New(ctx, fmt.Sprintf(bsrFileNameTemplate, "session-id"))
|
|
require.NoError(t, err)
|
|
|
|
c, err := newContainer(ctx, SessionContainer, fc, keys)
|
|
require.NoError(t, err)
|
|
require.NotNil(t, c)
|
|
|
|
cases := []struct {
|
|
name string
|
|
fileName string
|
|
expectedErr string
|
|
}{
|
|
{
|
|
name: "empty file name",
|
|
expectedErr: "file does not exist",
|
|
},
|
|
{
|
|
name: "file does not exist",
|
|
fileName: "dne",
|
|
expectedErr: "file dne does not exist",
|
|
},
|
|
{
|
|
name: "valid",
|
|
fileName: ".journal",
|
|
},
|
|
}
|
|
for _, tc := range cases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
checksum, err := c.computeFileChecksum(ctx, tc.fileName, crypto.WithHexEncoding(true))
|
|
if tc.expectedErr != "" {
|
|
require.Error(t, err)
|
|
assert.Contains(t, err.Error(), tc.expectedErr)
|
|
assert.Empty(t, checksum)
|
|
return
|
|
}
|
|
require.NoError(t, err)
|
|
require.NotEmpty(t, checksum)
|
|
})
|
|
}
|
|
}
|