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.
boundary/internal/bsr/bsr_validate_test.go

591 lines
18 KiB

// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package bsr
import (
"context"
"errors"
"fmt"
"testing"
"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/boundary/internal/storage"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestBSR_Validate_ValidateBSR(t *testing.T) {
ctx := context.Background()
connectionId := "test_connection"
channelId := "test_channel"
sessionId := "s_01234567881"
fs := &fstest.MemFS{}
protocol := TestRegisterSummaryAllocFunc(t)
// Setup keys
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
}
cases := []struct {
name string
protocol Protocol
storage storage.FS
sessionId string
sessionRecordingId string
expectedSessionChecksums ContainerChecksumValidation
expectedConnectionChecksums ContainerChecksumValidation
expectedChannelChecksums ContainerChecksumValidation
expectedSessionContainerSize int
expectedConnectionContainerSize int
expectedChannelContainerSize int
wantErr error
}{
{
name: "valid BSR with multiple connections and channels",
sessionId: "s_01234567881",
sessionRecordingId: "sr_01234567881",
storage: func() storage.FS {
sessionRecordingId := "sr_01234567881"
// Set up session
srm := &SessionRecordingMeta{
Id: sessionRecordingId,
Protocol: protocol,
}
sessionMeta := TestSessionMeta(sessionId)
sesh, err := NewSession(ctx, srm, sessionMeta, fs, keys, WithSupportsMultiplex(true))
require.NoError(t, err)
require.NotNil(t, sesh)
// Encode session summary
sesh.EncodeSummary(ctx, &BaseSessionSummary{
Id: channelId,
})
// Set up connection 1
conn1, err := sesh.NewConnection(ctx, &ConnectionRecordingMeta{Id: connectionId + "_1"})
require.NoError(t, err)
require.NotNil(t, conn1)
// Encode connection 2 summary
err = conn1.EncodeSummary(ctx, &BaseConnectionSummary{
Id: connectionId + ".1",
ChannelCount: 1,
})
require.NoError(t, err)
// Set up connection 2
conn2, err := sesh.NewConnection(ctx, &ConnectionRecordingMeta{Id: connectionId + "_2"})
require.NoError(t, err)
require.NotNil(t, conn1)
// Encode connection 2 summary
err = conn2.EncodeSummary(ctx, &BaseConnectionSummary{
Id: connectionId + ".2",
ChannelCount: 1,
})
require.NoError(t, err)
// Setup Channels with Connection 1
ch1, err := conn1.NewChannel(ctx, &ChannelRecordingMeta{
Id: channelId + "_1",
Type: "chan",
})
require.NoError(t, err)
require.NotNil(t, ch1)
err = ch1.EncodeSummary(ctx, &BaseChannelSummary{
Id: channelId + "_1",
ConnectionRecordingId: connectionId + "_1",
})
require.NoError(t, err)
ch2, err := conn1.NewChannel(ctx, &ChannelRecordingMeta{
Id: channelId + "_2",
Type: "chan",
})
require.NoError(t, err)
require.NotNil(t, ch2)
err = ch2.EncodeSummary(ctx, &BaseChannelSummary{
Id: channelId + "_2",
ConnectionRecordingId: connectionId + "_1",
})
require.NoError(t, err)
// Setup Channels with Connection 2
ch3, err := conn2.NewChannel(ctx, &ChannelRecordingMeta{
Id: channelId + "_1",
Type: "chan",
})
require.NoError(t, err)
require.NotNil(t, ch3)
err = ch3.EncodeSummary(ctx, &BaseChannelSummary{
Id: channelId + "_1",
ConnectionRecordingId: connectionId + "_2",
})
require.NoError(t, err)
ch4, err := conn2.NewChannel(ctx, &ChannelRecordingMeta{
Id: channelId + "_2",
Type: "chan",
})
require.NoError(t, err)
require.NotNil(t, ch4)
// Encode channel summary
err = ch4.EncodeSummary(ctx, &BaseChannelSummary{
Id: channelId + "_2",
ConnectionRecordingId: connectionId + "_2",
})
require.NoError(t, err)
require.NoError(t, ch1.Close(ctx))
require.NoError(t, ch2.Close(ctx))
require.NoError(t, ch3.Close(ctx))
require.NoError(t, ch4.Close(ctx))
require.NoError(t, conn1.Close(ctx))
require.NoError(t, conn2.Close(ctx))
require.NoError(t, sesh.Close(ctx))
return fs
}(),
expectedSessionChecksums: 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,
},
},
expectedConnectionChecksums: ContainerChecksumValidation{
"connection-recording-summary.json": &FileChecksumValidation{
Filename: "connection-recording-summary.json",
Passed: true,
},
"connection-recording.meta": &FileChecksumValidation{
Filename: "connection-recording.meta",
Passed: true,
},
},
expectedChannelChecksums: ContainerChecksumValidation{
"channel-recording-summary.json": &FileChecksumValidation{
Filename: "channel-recording-summary.json",
Passed: true,
},
"channel-recording.meta": &FileChecksumValidation{
Filename: "channel-recording.meta",
Passed: true,
},
},
expectedSessionContainerSize: 2,
expectedConnectionContainerSize: 2,
expectedChannelContainerSize: 0,
},
{
name: "Failed checksum",
sessionRecordingId: "sr_21234567881",
storage: func() storage.FS {
tmpFileName := "Test"
// Set up session
srm := &SessionRecordingMeta{
Id: "sr_21234567881",
Protocol: protocol,
}
sessionMeta := TestSessionMeta(sessionId)
session, err := NewSession(ctx, srm, sessionMeta, fs, keys, WithSupportsMultiplex(true))
require.NoError(t, err)
require.NotNil(t, session)
// Encode session summary
session.EncodeSummary(ctx, &BaseSessionSummary{
Id: channelId,
})
testFile, err := session.container.container.Create(ctx, tmpFileName)
require.NoError(t, err)
_, err = testFile.Write([]byte("hello world"))
require.NoError(t, err)
tfc, err := checksum.NewFile(ctx, testFile, session.checksums)
require.NoError(t, err)
require.NotNil(t, tfc)
require.NoError(t, tfc.Close())
require.NoError(t, session.Close(ctx))
openSesh, err := OpenSession(ctx, srm.Id, fs, keyFn)
require.NoError(t, err)
require.NotNil(t, openSesh)
openedTestFile, err := openSesh.container.container.OpenFile(ctx, tmpFileName, storage.WithCreateFile(), storage.WithFileAccessMode(storage.ReadWrite))
require.NoError(t, err)
_, err = openedTestFile.Write([]byte("invalid text"))
require.NoError(t, err)
require.NoError(t, openedTestFile.Close())
return fs
}(),
expectedSessionChecksums: 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,
},
"Test": &FileChecksumValidation{
Filename: "Test",
Passed: false,
Error: errors.New("checksum mismatch"),
},
},
expectedConnectionChecksums: ContainerChecksumValidation{
"connection-recording-summary.json": &FileChecksumValidation{
Filename: "connection-recording-summary.json",
Passed: true,
},
"connection-recording.meta": &FileChecksumValidation{
Filename: "connection-recording.meta",
Passed: true,
},
},
expectedChannelChecksums: ContainerChecksumValidation{
"channel-recording-summary.json": &FileChecksumValidation{
Filename: "channel-recording-summary.json",
Passed: true,
},
"channel-recording.meta": &FileChecksumValidation{
Filename: "channel-recording.meta",
Passed: true,
},
},
expectedSessionContainerSize: 2,
expectedConnectionContainerSize: 2,
expectedChannelContainerSize: 0,
},
{
name: "missing session recording id parameter",
wantErr: errors.New("bsr.Validate: missing session recording id: invalid parameter"),
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
validation, err := Validate(ctx, tc.sessionRecordingId, tc.storage, keyFn)
if tc.wantErr != nil {
assert.EqualError(t, err, tc.wantErr.Error())
return
}
require.NoError(t, err)
require.NotNil(t, validation)
// Validate Session
assert.Equal(t, tc.sessionRecordingId, validation.SessionRecordingId)
assert.Equal(t, SessionContainer, validation.SessionRecordingValidation.ContainerType)
assert.Equal(t, len(tc.expectedSessionChecksums), len(validation.SessionRecordingValidation.FileChecksumValidations))
assert.Equal(t, tc.expectedSessionChecksums, validation.SessionRecordingValidation.FileChecksumValidations)
// Ensure session container is closed
sessionContainer := fs.Containers[fmt.Sprintf(bsrFileNameTemplate, validation.SessionRecordingId)]
require.NotNil(t, sessionContainer)
assert.True(t, sessionContainer.IsClosed())
// Validate Multiple Connections
for _, connection := range validation.SessionRecordingValidation.SubContainers {
require.NotNil(t, connection)
assert.Equal(t, ConnectionContainer, connection.ContainerType)
assert.Equal(t, tc.expectedConnectionContainerSize, len(connection.SubContainers))
assert.Equal(t, len(tc.expectedConnectionChecksums), len(connection.FileChecksumValidations))
assert.Equal(t, tc.expectedConnectionChecksums, connection.FileChecksumValidations)
// Ensure connection container is closed
connectionContainer := sessionContainer.Sub[fmt.Sprintf(connectionFileNameTemplate, connection.Name)]
require.NotNil(t, connectionContainer)
assert.True(t, connectionContainer.IsClosed())
// Validate Multiple Channels
for _, channel := range connection.SubContainers {
require.NotNil(t, channel)
assert.Equal(t, ChannelContainer, channel.ContainerType)
assert.Equal(t, tc.expectedChannelContainerSize, len(channel.SubContainers))
assert.Equal(t, len(tc.expectedChannelChecksums), len(channel.FileChecksumValidations))
assert.Equal(t, tc.expectedChannelChecksums, channel.FileChecksumValidations)
// Ensure channel container is closed
channelContainer := connectionContainer.Sub[fmt.Sprintf(channelFileNameTemplate, channel.Name)]
require.NotNil(t, channelContainer)
assert.True(t, channelContainer.IsClosed())
}
}
})
}
}
func TestBSR_Validate_ValidateContainer(t *testing.T) {
ctx := context.Background()
channelId := "chr_123456789"
sessionId := "s_01234567881"
protocol := TestRegisterSummaryAllocFunc(t)
cases := []struct {
name string
c *container
containerName string
containerType ContainerType
expectedValidation *Validation
expectedContainerValidation *ContainerValidation
wantErr error
}{
{
name: "valid container",
c: func() *container {
fs := &fstest.MemFS{}
// Setup keys
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
}
// Set up session
srm := &SessionRecordingMeta{
Id: sessionId,
Protocol: protocol,
}
sessionMeta := TestSessionMeta(sessionId)
session, err := NewSession(ctx, srm, sessionMeta, fs, keys, WithSupportsMultiplex(true))
require.NoError(t, err)
require.NotNil(t, session)
// Encode session summary
session.EncodeSummary(ctx, &BaseSessionSummary{
Id: channelId,
})
require.NoError(t, session.Close(ctx))
openSesh, err := OpenSession(ctx, srm.Id, fs, keyFn)
require.NoError(t, err)
require.NotNil(t, openSesh)
return openSesh.container
}(),
containerName: "sr_123456789",
containerType: SessionContainer,
expectedValidation: &Validation{
SessionRecordingId: "sr_123456789",
Valid: true,
},
expectedContainerValidation: &ContainerValidation{
Name: "sr_123456789",
ContainerType: SessionContainer,
FileChecksumValidations: 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: "invalid container",
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
}(),
containerName: "sr_123456789",
containerType: SessionContainer,
expectedValidation: &Validation{
SessionRecordingId: "sr_123456789",
Valid: false,
},
expectedContainerValidation: &ContainerValidation{
Name: "sr_123456789",
ContainerType: SessionContainer,
FileChecksumValidations: ContainerChecksumValidation{
"test": &FileChecksumValidation{
Filename: "test",
Passed: false,
Error: errors.New("checksum mismatch"),
},
},
},
},
{
name: "invalid container - without checksums",
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)
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
}(),
containerName: "sr_123456789",
containerType: SessionContainer,
expectedValidation: &Validation{
SessionRecordingId: "sr_123456789",
Valid: false,
},
expectedContainerValidation: &ContainerValidation{
Name: "sr_123456789",
ContainerType: SessionContainer,
},
wantErr: errors.New("bsr.(Validate).ValidateContainer: failed to validate sr_123456789: bsr.(container).Validate: missing checksums"),
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
v := &Validation{
SessionRecordingId: tc.containerName,
Valid: true,
}
containerValidation := validateContainer(ctx, v, tc.containerType, tc.c, tc.containerName)
if tc.wantErr != nil {
assert.EqualError(t, containerValidation.Error, tc.wantErr.Error())
return
}
require.NotNil(t, containerValidation)
assert.Equal(t, v, tc.expectedValidation)
assert.Equal(t, tc.expectedContainerValidation, containerValidation)
})
}
}