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.
591 lines
18 KiB
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)
|
|
})
|
|
}
|
|
}
|