feat(bsr): instantiate BSR session for playback
This commit is contained in:
1 parent
46e1752283
commit
e567bb97e6
7 files changed
+673
-83
No files matched your search
+156
-13
@@ -16,6 +16,7 @@ import (
|
||||
"github.com/hashicorp/boundary/internal/bsr/internal/is"
|
||||
"github.com/hashicorp/boundary/internal/bsr/kms"
|
||||
"github.com/hashicorp/boundary/internal/storage"
|
||||
"github.com/hashicorp/go-kms-wrapping/v2/extras/crypto"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
@@ -43,6 +44,7 @@ func decodeSessionMeta(ctx context.Context, r io.Reader) (*SessionMeta, error) {
|
||||
return nil, fmt.Errorf("%s: missing session meta file: %w", op, ErrInvalidParameter)
|
||||
}
|
||||
|
||||
connections := make(map[string]bool)
|
||||
s := &SessionMeta{}
|
||||
user := &User{Scope: Scope{}}
|
||||
target := &Target{Scope: Scope{}}
|
||||
@@ -171,6 +173,10 @@ func decodeSessionMeta(ctx context.Context, r io.Reader) (*SessionMeta, error) {
|
||||
dHost.Catalog.PluginId = v
|
||||
case k == "dynamicHostCatalog_attributes":
|
||||
dHost.Catalog.Attributes = v
|
||||
|
||||
// connections
|
||||
case k == "connection":
|
||||
connections[v] = true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -182,6 +188,7 @@ func decodeSessionMeta(ctx context.Context, r io.Reader) (*SessionMeta, error) {
|
||||
if dHost.PublicId != "" {
|
||||
s.DynamicHost = dHost
|
||||
}
|
||||
s.connections = connections
|
||||
|
||||
return s, nil
|
||||
}
|
||||
@@ -303,12 +310,60 @@ func persistBsrSessionKeys(ctx context.Context, keys *kms.Keys, c *container) er
|
||||
// Fields on the underlying container will be populated so that the returned Session can be used for BSR
|
||||
// playback and conversion to formats such as asciinema
|
||||
func OpenSession(ctx context.Context, sessionRecordingId string, f storage.FS, keyUnwrapFn kms.KeyUnwrapCallbackFunc) (*Session, error) {
|
||||
panic("not implemented")
|
||||
const op = "bsr.OpenSession"
|
||||
switch {
|
||||
case sessionRecordingId == "":
|
||||
return nil, fmt.Errorf("%s: missing session recording id: %w", op, ErrInvalidParameter)
|
||||
case f == nil:
|
||||
return nil, fmt.Errorf("%s: missing storage: %w", op, ErrInvalidParameter)
|
||||
case keyUnwrapFn == nil:
|
||||
return nil, fmt.Errorf("%s: missing key unwrap function: %w", op, ErrInvalidParameter)
|
||||
}
|
||||
|
||||
c, err := f.Open(ctx, fmt.Sprintf(bsrFile, sessionRecordingId))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
keyPopFn := func(c *container) (*kms.Keys, error) {
|
||||
return c.loadKeys(ctx, keyUnwrapFn)
|
||||
}
|
||||
cc, err := openContainer(ctx, sessionContainer, c, keyPopFn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Load and verify session metadata
|
||||
sha256Reader, err := crypto.NewSha256SumReader(ctx, cc.metaFile)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s: %w", op, err)
|
||||
}
|
||||
sm, err := decodeSessionMeta(ctx, sha256Reader)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = cc.verifyMetadata(ctx, sha256Reader)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
session := &Session{
|
||||
container: cc,
|
||||
Meta: sm,
|
||||
}
|
||||
|
||||
return session, nil
|
||||
}
|
||||
|
||||
// Close closes the Session container.
|
||||
func (s *Session) Close(ctx context.Context) error {
|
||||
return s.container.close(ctx)
|
||||
}
|
||||
|
||||
// ConnectionMeta contains metadata about a connection in a BSR.
|
||||
type ConnectionMeta struct {
|
||||
Id string
|
||||
Id string
|
||||
channels map[string]bool
|
||||
}
|
||||
|
||||
func (c ConnectionMeta) isValid() bool {
|
||||
@@ -320,6 +375,15 @@ func (c ConnectionMeta) isValid() bool {
|
||||
}
|
||||
}
|
||||
|
||||
// Connection is a container in a bsr for a specific connection in a session
|
||||
// container. It contains the files for the recorded connection.
|
||||
type Connection struct {
|
||||
*container
|
||||
multiplexed bool
|
||||
|
||||
Meta *ConnectionMeta
|
||||
}
|
||||
|
||||
// decodeConnectionMeta will populate the ConnectionMeta for a BSR Connection
|
||||
// TODO Unmarshal without brute force
|
||||
func decodeConnectionMeta(ctx context.Context, r io.Reader) (*ConnectionMeta, error) {
|
||||
@@ -329,8 +393,8 @@ func decodeConnectionMeta(ctx context.Context, r io.Reader) (*ConnectionMeta, er
|
||||
case r == nil:
|
||||
return nil, fmt.Errorf("%s: missing connection meta file: %w", op, ErrInvalidParameter)
|
||||
}
|
||||
|
||||
c := &ConnectionMeta{}
|
||||
channels := make(map[string]bool)
|
||||
scanner := bufio.NewScanner(r)
|
||||
for scanner.Scan() {
|
||||
l := scanner.Text()
|
||||
@@ -345,8 +409,11 @@ func decodeConnectionMeta(ctx context.Context, r io.Reader) (*ConnectionMeta, er
|
||||
switch {
|
||||
case k == "id":
|
||||
c.Id = v
|
||||
case k == "channel":
|
||||
channels[v] = true
|
||||
}
|
||||
}
|
||||
c.channels = channels
|
||||
|
||||
return c, nil
|
||||
}
|
||||
@@ -385,18 +452,49 @@ func (s *Session) NewConnection(ctx context.Context, meta *ConnectionMeta) (*Con
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Close closes the Session container.
|
||||
func (s *Session) Close(ctx context.Context) error {
|
||||
return s.container.close(ctx)
|
||||
}
|
||||
// OpenConnection will open and validate a BSR connection
|
||||
func (s *Session) OpenConnection(ctx context.Context, connId string) (*Connection, error) {
|
||||
const op = "bsr.OpenConnection"
|
||||
switch {
|
||||
case connId == "":
|
||||
return nil, fmt.Errorf("%s: missing connection id: %w", op, ErrInvalidParameter)
|
||||
case !s.Meta.connections[connId]:
|
||||
return nil, fmt.Errorf("%s: connection id does not exist within this session: %w", op, ErrInvalidParameter)
|
||||
}
|
||||
|
||||
// Connection is a container in a bsr for a specific connection in a session
|
||||
// container. It contains the files for the recorded connection.
|
||||
type Connection struct {
|
||||
*container
|
||||
multiplexed bool
|
||||
c, err := s.container.container.SubContainer(ctx, connId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
Meta *ConnectionMeta
|
||||
keyPopFn := func(c *container) (*kms.Keys, error) {
|
||||
return s.keys, nil
|
||||
}
|
||||
cc, err := openContainer(ctx, connectionContainer, c, keyPopFn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Load and verify connection metadata
|
||||
sha256Reader, err := crypto.NewSha256SumReader(ctx, cc.metaFile)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s: %w", op, err)
|
||||
}
|
||||
sm, err := decodeConnectionMeta(ctx, sha256Reader)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = cc.verifyMetadata(ctx, sha256Reader)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
connection := &Connection{
|
||||
container: cc,
|
||||
Meta: sm,
|
||||
}
|
||||
|
||||
return connection, nil
|
||||
}
|
||||
|
||||
// ChannelMeta contains metadata about a channel in a BSR.
|
||||
@@ -488,6 +586,51 @@ func (c *Connection) NewChannel(ctx context.Context, meta *ChannelMeta) (*Channe
|
||||
}, nil
|
||||
}
|
||||
|
||||
// OpenChannel will open and validate a BSR channel
|
||||
func (c *Connection) OpenChannel(ctx context.Context, chanId string) (*Channel, error) {
|
||||
const op = "bsr.OpenChannel"
|
||||
switch {
|
||||
case chanId == "":
|
||||
return nil, fmt.Errorf("%s: missing channel id: %w", op, ErrInvalidParameter)
|
||||
case !c.Meta.channels[chanId]:
|
||||
return nil, fmt.Errorf("%s: channel id does not exist within this connection: %w", op, ErrInvalidParameter)
|
||||
}
|
||||
|
||||
con, err := c.container.container.SubContainer(ctx, chanId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
keyPopFn := func(cn *container) (*kms.Keys, error) {
|
||||
return c.keys, nil
|
||||
}
|
||||
cc, err := openContainer(ctx, channelContainer, con, keyPopFn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Load and verify channel metadata
|
||||
sha256Reader, err := crypto.NewSha256SumReader(ctx, cc.metaFile)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s: %w", op, err)
|
||||
}
|
||||
sm, err := decodeChannelMeta(ctx, sha256Reader)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = cc.verifyMetadata(ctx, sha256Reader)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
channel := &Channel{
|
||||
container: cc,
|
||||
Meta: sm,
|
||||
}
|
||||
|
||||
return channel, nil
|
||||
}
|
||||
|
||||
// NewMessagesWriter creates a writer for recording channel messages.
|
||||
func (c *Connection) NewMessagesWriter(ctx context.Context, dir Direction) (io.Writer, error) {
|
||||
const op = "bsr.(Connection).NewMessagesWriter"
|
||||
|
||||
@@ -0,0 +1,276 @@
|
||||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package bsr
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"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/require"
|
||||
)
|
||||
|
||||
func TestPopulateMeta(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
keys, err := kms.CreateKeys(ctx, kms.TestWrapper(t), "session")
|
||||
require.NoError(t, err)
|
||||
|
||||
sessionId := "session"
|
||||
sessionProtocol := "TEST"
|
||||
|
||||
// Populate session meta
|
||||
sessionMeta := TestSessionMeta(sessionId, Protocol((sessionProtocol)))
|
||||
s, err := NewSession(ctx, sessionMeta, &fstest.MemFS{}, keys, WithSupportsMultiplex(true))
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, s)
|
||||
|
||||
// Reset meta and populate it from the meta file
|
||||
s.Meta = &SessionMeta{}
|
||||
sm, err := decodeSessionMeta(ctx, s.container.meta)
|
||||
require.NoError(t, err)
|
||||
s.Meta = sm
|
||||
require.Equal(t, s.Meta.Id, sessionId)
|
||||
require.Equal(t, s.Meta.Protocol.ToText(), sessionProtocol)
|
||||
require.Equal(t, s.Meta.Target, sessionMeta.Target)
|
||||
require.Equal(t, s.Meta.User, sessionMeta.User)
|
||||
require.Equal(t, s.Meta.StaticHost, sessionMeta.StaticHost)
|
||||
|
||||
// Populate connection meta
|
||||
connectionId := "connection"
|
||||
c, err := s.NewConnection(ctx, &ConnectionMeta{Id: connectionId})
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, c)
|
||||
|
||||
c.Meta = &ConnectionMeta{}
|
||||
cm, err := decodeConnectionMeta(ctx, c.container.meta)
|
||||
require.NoError(t, err)
|
||||
c.Meta = cm
|
||||
require.Equal(t, c.Meta.Id, connectionId)
|
||||
|
||||
// Populate channel meta
|
||||
channelId := "channel"
|
||||
channelType := "mythical"
|
||||
ch, err := c.NewChannel(ctx, &ChannelMeta{Id: channelId, Type: channelType})
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, ch)
|
||||
|
||||
ch.Meta = &ChannelMeta{}
|
||||
chM, err := decodeChannelMeta(ctx, ch.container.meta)
|
||||
require.NoError(t, err)
|
||||
ch.Meta = chM
|
||||
require.Equal(t, ch.Meta.Id, channelId)
|
||||
require.Equal(t, ch.Meta.Type, channelType)
|
||||
}
|
||||
|
||||
func TestOpenBSRMethods(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
keys, err := kms.CreateKeys(ctx, kms.TestWrapper(t), "session")
|
||||
require.NoError(t, err)
|
||||
|
||||
f := &fstest.MemFS{}
|
||||
sessionId := "session"
|
||||
sessionMeta := TestSessionMeta(sessionId, Protocol("test"))
|
||||
|
||||
sesh, err := NewSession(ctx, sessionMeta, f, keys, WithSupportsMultiplex(true))
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, sesh)
|
||||
|
||||
connectionId := "connection"
|
||||
connMeta := &ConnectionMeta{Id: connectionId}
|
||||
conn, err := sesh.NewConnection(ctx, connMeta)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, conn)
|
||||
|
||||
channelId := "channel"
|
||||
chanMeta := &ChannelMeta{
|
||||
Id: channelId,
|
||||
Type: "chan",
|
||||
}
|
||||
ch, err := conn.NewChannel(ctx, chanMeta)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, ch)
|
||||
|
||||
ch.Close(ctx)
|
||||
conn.Close(ctx)
|
||||
sesh.Close(ctx)
|
||||
|
||||
keyFn := func(w kms.WrappedKeys) (kms.UnwrappedKeys, error) {
|
||||
u := kms.UnwrappedKeys{
|
||||
BsrKey: keys.BsrKey,
|
||||
PrivKey: keys.PrivKey,
|
||||
}
|
||||
return u, nil
|
||||
}
|
||||
|
||||
opSesh, err := OpenSession(ctx, sessionId, f, keyFn)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, opSesh)
|
||||
sesh.Meta.connections = opSesh.Meta.connections
|
||||
require.Equal(t, opSesh.Meta, sesh.Meta)
|
||||
|
||||
opConn, err := opSesh.OpenConnection(ctx, fmt.Sprintf("connection.%s", connectionId))
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, opConn)
|
||||
conn.Meta.channels = opConn.Meta.channels
|
||||
require.Equal(t, opConn.Meta, conn.Meta)
|
||||
|
||||
opChan, err := opConn.OpenChannel(ctx, fmt.Sprintf("channel.%s", channelId))
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, opChan)
|
||||
require.Equal(t, opChan.Meta, ch.Meta)
|
||||
}
|
||||
|
||||
func TestOpenSession(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
keys, err := kms.CreateKeys(ctx, kms.TestWrapper(t), "session")
|
||||
require.NoError(t, err)
|
||||
|
||||
keyFn := func(w kms.WrappedKeys) (kms.UnwrappedKeys, error) {
|
||||
u := kms.UnwrappedKeys{
|
||||
BsrKey: keys.BsrKey,
|
||||
PrivKey: keys.PrivKey,
|
||||
}
|
||||
return u, nil
|
||||
}
|
||||
|
||||
f := &fstest.MemFS{}
|
||||
sessionId := "session"
|
||||
|
||||
cases := []struct {
|
||||
name string
|
||||
id string
|
||||
f storage.FS
|
||||
keyFn kms.KeyUnwrapCallbackFunc
|
||||
wantErr bool
|
||||
wantErrContains string
|
||||
}{
|
||||
{
|
||||
name: "no-session-id",
|
||||
f: f,
|
||||
keyFn: keyFn,
|
||||
wantErr: true,
|
||||
wantErrContains: "bsr.OpenSession: missing session recording id: invalid parameter",
|
||||
},
|
||||
{
|
||||
name: "no-key-fn",
|
||||
id: sessionId,
|
||||
f: f,
|
||||
wantErr: true,
|
||||
wantErrContains: "bsr.OpenSession: missing key unwrap function: invalid parameter",
|
||||
},
|
||||
{
|
||||
name: "no-storage",
|
||||
id: sessionId,
|
||||
keyFn: keyFn,
|
||||
wantErr: true,
|
||||
wantErrContains: "bsr.OpenSession: missing storage: invalid parameter",
|
||||
},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
got, err := OpenSession(ctx, tc.id, tc.f, tc.keyFn)
|
||||
if tc.wantErr {
|
||||
require.Error(t, err)
|
||||
require.ErrorContains(t, err, tc.wantErrContains)
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestOpenConnection(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
connMap := make(map[string]bool)
|
||||
connMap["connection"] = true
|
||||
meta := TestSessionMeta("sessionId", Protocol("test"))
|
||||
meta.connections = connMap
|
||||
|
||||
cases := []struct {
|
||||
name string
|
||||
id string
|
||||
wantErr bool
|
||||
wantErrContains string
|
||||
}{
|
||||
{
|
||||
name: "no-conn-id",
|
||||
wantErr: true,
|
||||
wantErrContains: "bsr.OpenConnection: missing connection id: invalid parameter",
|
||||
},
|
||||
|
||||
{
|
||||
name: "bad-conn-id",
|
||||
id: "bogus",
|
||||
wantErr: true,
|
||||
wantErrContains: "bsr.OpenConnection: connection id does not exist within this session: invalid parameter",
|
||||
},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
sesh := Session{
|
||||
Meta: meta,
|
||||
}
|
||||
got, err := sesh.OpenConnection(ctx, tc.id)
|
||||
if tc.wantErr {
|
||||
require.Error(t, err)
|
||||
require.ErrorContains(t, err, tc.wantErrContains)
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestOpenChannel(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
chanMap := make(map[string]bool)
|
||||
chanMap["channel"] = true
|
||||
|
||||
cases := []struct {
|
||||
name string
|
||||
id string
|
||||
wantErr bool
|
||||
wantErrContains string
|
||||
}{
|
||||
{
|
||||
name: "no-chan-id",
|
||||
wantErr: true,
|
||||
wantErrContains: "bsr.OpenChannel: missing channel id: invalid parameter",
|
||||
},
|
||||
|
||||
{
|
||||
name: "bad-chan-id",
|
||||
id: "bogus",
|
||||
wantErr: true,
|
||||
wantErrContains: "bsr.OpenChannel: channel id does not exist within this connection: invalid parameter",
|
||||
},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
conn := Connection{
|
||||
Meta: &ConnectionMeta{
|
||||
channels: chanMap,
|
||||
},
|
||||
}
|
||||
got, err := conn.OpenChannel(ctx, tc.id)
|
||||
if tc.wantErr {
|
||||
require.Error(t, err)
|
||||
require.ErrorContains(t, err, tc.wantErrContains)
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
+236
-3
@@ -4,15 +4,21 @@
|
||||
package bsr
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/hashicorp/boundary/internal/bsr/internal/checksum"
|
||||
"github.com/hashicorp/boundary/internal/bsr/internal/is"
|
||||
"github.com/hashicorp/boundary/internal/bsr/internal/journal"
|
||||
"github.com/hashicorp/boundary/internal/bsr/internal/sign"
|
||||
"github.com/hashicorp/boundary/internal/bsr/kms"
|
||||
"github.com/hashicorp/boundary/internal/storage"
|
||||
wrapping "github.com/hashicorp/go-kms-wrapping/v2"
|
||||
"github.com/hashicorp/go-kms-wrapping/v2/extras/crypto"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -37,16 +43,22 @@ const (
|
||||
// Each container has corresponding .meta, .summary, SHA256SUM, and SHA256SUM.sig files.
|
||||
type container struct {
|
||||
container storage.Container
|
||||
journal *journal.Journal
|
||||
|
||||
metaName string
|
||||
// Fields primarily used for writing
|
||||
journal *journal.Journal
|
||||
sumName string
|
||||
meta *checksum.File
|
||||
sum *checksum.File
|
||||
checksums *sign.File
|
||||
sigs storage.File
|
||||
|
||||
keys *kms.Keys
|
||||
// Field used for reading
|
||||
shaSums checksum.Sha256Sums
|
||||
metaFile storage.File
|
||||
|
||||
// Field used for reading and writing
|
||||
keys *kms.Keys
|
||||
metaName string
|
||||
}
|
||||
|
||||
// newContainer creates a container for the given type backed by the provide storage.Container.
|
||||
@@ -105,6 +117,227 @@ func newContainer(ctx context.Context, t containerType, c storage.Container, key
|
||||
return cc, nil
|
||||
}
|
||||
|
||||
type populateKeyFunc func(c *container) (*kms.Keys, error)
|
||||
|
||||
// openContainer will set keys and load and verify the checksums for this container
|
||||
func openContainer(ctx context.Context, t containerType, c storage.Container, keyGetFunc populateKeyFunc) (*container, error) {
|
||||
const op = "bsr.openContainer"
|
||||
switch {
|
||||
case t == "":
|
||||
return nil, fmt.Errorf("%s: missing container type: %w", op, ErrInvalidParameter)
|
||||
case is.Nil(c):
|
||||
return nil, fmt.Errorf("%s: missing container: %w", op, ErrInvalidParameter)
|
||||
case is.Nil(keyGetFunc):
|
||||
return nil, fmt.Errorf("%s: missing key function: %w", op, ErrInvalidParameter)
|
||||
}
|
||||
|
||||
cc := &container{
|
||||
container: c,
|
||||
}
|
||||
|
||||
keys, err := keyGetFunc(cc)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cc.keys = keys
|
||||
|
||||
err = cc.loadChecksums(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Load the meta file
|
||||
cc.metaName = fmt.Sprintf(metaFile, t)
|
||||
mFile, err := cc.container.OpenFile(ctx, cc.metaName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cc.metaFile = mFile
|
||||
|
||||
return cc, nil
|
||||
}
|
||||
|
||||
func (c *container) loadChecksums(ctx context.Context) error {
|
||||
const op = "bsr.(container).loadChecksums"
|
||||
|
||||
// Open and extract checksum signature
|
||||
checksumsSigFile, err := c.container.OpenFile(ctx, sigFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
checksumSigBytes := new(bytes.Buffer)
|
||||
_, err = checksumSigBytes.ReadFrom(checksumsSigFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
sig := new(wrapping.SigInfo)
|
||||
err = proto.Unmarshal(checksumSigBytes.Bytes(), sig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Open and extract checksum file bytes
|
||||
checksumsFile, err := c.container.OpenFile(ctx, checksumFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var checksumsBuffer bytes.Buffer
|
||||
cTee := io.TeeReader(checksumsFile, &checksumsBuffer)
|
||||
|
||||
checksumBytes := new(bytes.Buffer)
|
||||
_, err = checksumBytes.ReadFrom(cTee)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
verified, err := c.keys.VerifySignatureWithPubKey(ctx, sig, checksumBytes.Bytes())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !verified {
|
||||
return fmt.Errorf("%s: failed to verify checksums signature: %w", op, ErrSignatureVerification)
|
||||
}
|
||||
|
||||
// Load checksums
|
||||
sums, err := checksum.LoadSha256Sums(&checksumsBuffer)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.shaSums = sums
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *container) loadKey(ctx context.Context, keyFileName string) (*wrapping.KeyInfo, error) {
|
||||
keyFile, err := c.container.OpenFile(ctx, keyFileName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
keyBytes := new(bytes.Buffer)
|
||||
_, err = keyBytes.ReadFrom(keyFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
key := new(wrapping.KeyInfo)
|
||||
err = proto.Unmarshal(keyBytes.Bytes(), key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return key, nil
|
||||
}
|
||||
|
||||
func (c *container) loadSignature(ctx context.Context, sigFileName string) (*wrapping.SigInfo, error) {
|
||||
sigFile, err := c.container.OpenFile(ctx, sigFileName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sigBytes := new(bytes.Buffer)
|
||||
_, err = sigBytes.ReadFrom(sigFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
signature := new(wrapping.SigInfo)
|
||||
err = proto.Unmarshal(sigBytes.Bytes(), signature)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return signature, nil
|
||||
}
|
||||
|
||||
// loadKeys will load the BSR keys from storage, unmarshal and unwrap them.
|
||||
// After unwrapping, it will verify the key signature files before setting the keys
|
||||
// on the container
|
||||
func (c *container) loadKeys(ctx context.Context, keyUnwrapFn kms.KeyUnwrapCallbackFunc) (*kms.Keys, error) {
|
||||
const op = "bsr.(container).loadKeys"
|
||||
|
||||
switch {
|
||||
case keyUnwrapFn == nil:
|
||||
return nil, fmt.Errorf("%s: missing key unwrap function: %w", op, ErrInvalidParameter)
|
||||
}
|
||||
|
||||
bsrPubKey, err := c.loadKey(ctx, bsrPubKeyFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
wrappedBsrKey, err := c.loadKey(ctx, wrappedBsrKeyFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
wrappedPrivKey, err := c.loadKey(ctx, wrappedPrivKeyFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pubKeyBsrSignature, err := c.loadSignature(ctx, pubKeyBsrSignatureFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pubKeySelfSignature, err := c.loadSignature(ctx, pubKeySelfSignatureFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
unwrappedKeys, err := keyUnwrapFn(kms.WrappedKeys{
|
||||
WrappedBsrKey: wrappedBsrKey,
|
||||
WrappedPrivKey: wrappedPrivKey,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
keys := &kms.Keys{
|
||||
PubKey: bsrPubKey,
|
||||
BsrKey: unwrappedKeys.BsrKey,
|
||||
PrivKey: unwrappedKeys.PrivKey,
|
||||
PubKeyBsrSignature: pubKeyBsrSignature,
|
||||
PubKeySelfSignature: pubKeySelfSignature,
|
||||
}
|
||||
|
||||
verified, err := keys.VerifyPubKeySelfSignature(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !verified {
|
||||
return nil, fmt.Errorf("%s: failed to verify public self signed key: %w", op, ErrSignatureVerification)
|
||||
}
|
||||
|
||||
verified, err = keys.VerifyPubKeyBsrSignature(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !verified {
|
||||
return nil, fmt.Errorf("%s: failed to verify pub key signature: %w", op, ErrSignatureVerification)
|
||||
}
|
||||
|
||||
return keys, nil
|
||||
}
|
||||
|
||||
func (c *container) verifyMetadata(ctx context.Context, sha256Reader *crypto.Sha256SumReader) error {
|
||||
const op = "bsr.(container).verifyMetadata"
|
||||
metaSum, err := sha256Reader.Sum(ctx, crypto.WithHexEncoding(true))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
expectedMetaSum, err := c.shaSums.Sum(c.metaName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !bytes.Equal(expectedMetaSum, metaSum) {
|
||||
return fmt.Errorf("%s: meta checksum did not match expected value", op)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// create creates a new file in the container for writing.
|
||||
func (c *container) create(ctx context.Context, s string, options ...storage.Option) (storage.File, error) {
|
||||
const op = "bsr.(container).create"
|
||||
|
||||
@@ -1,66 +0,0 @@
|
||||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package bsr
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/boundary/internal/bsr/internal/fstest"
|
||||
"github.com/hashicorp/boundary/internal/bsr/kms"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestPopulateMeta(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
keys, err := kms.CreateKeys(ctx, kms.TestWrapper(t), "session")
|
||||
require.NoError(t, err)
|
||||
|
||||
sessionId := "session"
|
||||
sessionProtocol := "SSH"
|
||||
|
||||
// Populate session meta
|
||||
sessionMeta := TestSessionMeta(sessionId, Protocol((sessionProtocol)))
|
||||
s, err := NewSession(ctx, sessionMeta, &fstest.MemFS{}, keys, WithSupportsMultiplex(true))
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, s)
|
||||
|
||||
// Reset meta and populate it from the meta file
|
||||
s.Meta = &SessionMeta{}
|
||||
sm, err := decodeSessionMeta(ctx, s.container.meta)
|
||||
require.NoError(t, err)
|
||||
s.Meta = sm
|
||||
require.Equal(t, s.Meta.Id, sessionId)
|
||||
require.Equal(t, s.Meta.Protocol.ToText(), sessionProtocol)
|
||||
require.Equal(t, s.Meta.Target, sessionMeta.Target)
|
||||
require.Equal(t, s.Meta.User, sessionMeta.User)
|
||||
require.Equal(t, s.Meta.StaticHost, sessionMeta.StaticHost)
|
||||
|
||||
// Populate connection meta
|
||||
connectionId := "connection"
|
||||
c, err := s.NewConnection(ctx, &ConnectionMeta{Id: connectionId})
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, c)
|
||||
|
||||
c.Meta = &ConnectionMeta{}
|
||||
cm, err := decodeConnectionMeta(ctx, c.container.meta)
|
||||
require.NoError(t, err)
|
||||
c.Meta = cm
|
||||
require.Equal(t, c.Meta.Id, connectionId)
|
||||
|
||||
// Populate channel meta
|
||||
channelId := "channel"
|
||||
channelType := "mythical"
|
||||
ch, err := c.NewChannel(ctx, &ChannelMeta{Id: channelId, Type: channelType})
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, ch)
|
||||
|
||||
ch.Meta = &ChannelMeta{}
|
||||
chM, err := decodeChannelMeta(ctx, ch.container.meta)
|
||||
require.NoError(t, err)
|
||||
ch.Meta = chM
|
||||
require.Equal(t, ch.Meta.Id, channelId)
|
||||
require.Equal(t, ch.Meta.Type, channelType)
|
||||
}
|
||||
@@ -20,6 +20,9 @@ var (
|
||||
// ErrSummaryUnavailable indicates a BSR summary is unavailable
|
||||
ErrSummaryUnavailable = errors.New("summary not available")
|
||||
|
||||
// ErrSignatureVerification indicates a failure in verifying a signature
|
||||
ErrSignatureVerification = errors.New("could not verify signature")
|
||||
|
||||
// ErrNotSupported represents an operation that is not supported for a
|
||||
// particular protocol.
|
||||
ErrNotSupported = errors.New("not supported by protocol")
|
||||
|
||||
@@ -37,7 +37,6 @@ func LoadSha256Sums(r io.Reader) (Sha256Sums, error) {
|
||||
for scanner.Scan() {
|
||||
var sum, file string
|
||||
var ok bool
|
||||
|
||||
sum, file, ok = strings.Cut(scanner.Text(), sha256sumSep)
|
||||
if !ok {
|
||||
sum, file, ok = strings.Cut(scanner.Text(), sha256sumBinarySep)
|
||||
|
||||
@@ -727,6 +727,8 @@ type SessionMeta struct {
|
||||
|
||||
StaticCredentialStore []StaticCredentialStore
|
||||
VaultCredentialStore []VaultCredentialStore
|
||||
|
||||
connections map[string]bool
|
||||
}
|
||||
|
||||
func (s SessionMeta) writeMeta(ctx context.Context, c *container) error {
|
||||
|
||||
Reference in new issue
Block a user