diff --git a/internal/bsr/bsr.go b/internal/bsr/bsr.go index beed5b491..2e2417dc3 100644 --- a/internal/bsr/bsr.go +++ b/internal/bsr/bsr.go @@ -4,10 +4,13 @@ package bsr import ( + "bufio" "context" "errors" "fmt" "io" + "strconv" + "strings" "github.com/hashicorp/boundary/internal/bsr/internal/checksum" "github.com/hashicorp/boundary/internal/bsr/internal/is" @@ -30,6 +33,159 @@ const ( pubKeySelfSignatureFile = "pubKeySelfSignature.sign" ) +// decodeSessionMeta will populate a SessionMeta for an opened BSR Session +// TODO Unmarshal without brute force +func decodeSessionMeta(ctx context.Context, r io.Reader) (*SessionMeta, error) { + const op = "bsr.decodeSessionMeta" + + switch { + case r == nil: + return nil, fmt.Errorf("%s: missing session meta file: %w", op, ErrInvalidParameter) + } + + s := &SessionMeta{} + user := &User{Scope: Scope{}} + target := &Target{Scope: Scope{}} + sHost := &StaticHost{Catalog: StaticHostCatalog{}} + dHost := &DynamicHost{Catalog: DynamicHostCatalog{}} + + scanner := bufio.NewScanner(r) + for scanner.Scan() { + l := scanner.Text() + if len(l) == 0 { + continue + } + k, v, ok := strings.Cut(l, ":") + if !ok { + return nil, fmt.Errorf("%s: session meta file contains invalid entry; expecting k: v pair:%s", op, l) + } + v = strings.TrimSpace(v) + switch { + case k == "id": + s.Id = v + case k == "protocol": + s.Protocol = Protocol(v) + + // User + case k == "user_publicId": + user.PublicId = v + case k == "user_name": + user.Name = v + case k == "user_description": + user.Description = v + case k == "user_scope_publicId": + user.Scope.PublicId = v + case k == "user_scope_name": + user.Scope.Name = v + case k == "user_scope_description": + user.Scope.Description = v + case k == "user_scope_type": + user.Scope.Type = v + case k == "user_scope_parentId": + user.Scope.ParentId = v + case k == "user_scope_primaryAuthMethod": + user.Scope.PrimaryAuthMethodId = v + + // Target + case k == "target_publicId": + target.PublicId = v + case k == "target_projectId": + target.ProjectId = v + case k == "target_name": + target.Name = v + case k == "target_description": + target.Description = v + case k == "target_defaultPort": + dp, err := strconv.Atoi(v) + if err != nil { + return nil, err + } + target.DefaultPort = uint32(dp) + case k == "target_sessionMaxSeconds": + sms, err := strconv.Atoi(v) + if err != nil { + return nil, err + } + target.SessionMaxSeconds = uint32(sms) + case k == "target_sessionConnectionLimit": + scl, err := strconv.Atoi(v) + if err != nil { + return nil, err + } + target.SessionConnectionLimit = int32(scl) + case k == "target_workerFilter": + target.WorkerFilter = v + case k == "target_egressWorkerFilter": + target.EgressWorkerFilter = v + case k == "target_ingressWorkerFilter": + target.IngressWorkerFilter = v + case k == "target_scope_publicId": + target.Scope.PublicId = v + case k == "target_scope_name": + target.Scope.Name = v + case k == "target_scope_description": + target.Scope.Description = v + case k == "target_scope_type": + target.Scope.Type = v + case k == "target_scope_parentId": + target.Scope.ParentId = v + case k == "target_scope_primaryAuthMethod": + target.Scope.PrimaryAuthMethodId = v + + // Static Host + case k == "staticHost_publicId": + sHost.PublicId = v + case k == "staticHost_name": + sHost.Name = v + case k == "staticHost_description": + sHost.Description = v + case k == "staticHost_address": + sHost.Address = v + case k == "staticHostCatalog_publicId": + sHost.Catalog.PublicId = v + case k == "staticHostCatalog_projectId": + sHost.Catalog.ProjectId = v + case k == "staticHostCatalog_name": + sHost.Catalog.Name = v + case k == "staticHostCatalog_description": + sHost.Catalog.Description = v + + // Dynamic Host + case k == "dynamicHost_publicId": + dHost.PublicId = v + case k == "dynamicHost_name": + dHost.Name = v + case k == "dynamicHost_description": + dHost.Description = v + case k == "dynamicHost_externalId": + dHost.ExternalId = v + case k == "dynamicHostCatalog_publicId": + dHost.Catalog.PublicId = v + case k == "dynamicHostCatalog_projectId": + dHost.Catalog.ProjectId = v + case k == "dynamicHostCatalog_name": + dHost.Catalog.Name = v + case k == "dynamicHostCatalog_description": + dHost.Catalog.Description = v + case k == "dynamicHostCatalog_pluginId": + dHost.Catalog.PluginId = v + case k == "dynamicHostCatalog_attributes": + dHost.Catalog.Attributes = v + } + } + + s.Target = target + s.User = user + if sHost.PublicId != "" { + s.StaticHost = sHost + } + if dHost.PublicId != "" { + s.DynamicHost = dHost + } + + return s, nil +} + // Session is the top level container in a bsr that contains the files for // a recorded session. type Session struct { @@ -150,6 +306,51 @@ func OpenSession(ctx context.Context, sessionRecordingId string, f storage.FS, k panic("not implemented") } +// ConnectionMeta contains metadata about a connection in a BSR. +type ConnectionMeta struct { + Id string +} + +func (c ConnectionMeta) isValid() bool { + switch { + case c.Id == "": + return false + default: + return true + } +} + +// decodeConnectionMeta will populate the ConnectionMeta for a BSR Connection +// TODO Unmarshal without brute force +func decodeConnectionMeta(ctx context.Context, r io.Reader) (*ConnectionMeta, error) { + const op = "bsr.decodeConnectionMeta" + + switch { + case r == nil: + return nil, fmt.Errorf("%s: missing connection meta file: %w", op, ErrInvalidParameter) + } + + c := &ConnectionMeta{} + scanner := bufio.NewScanner(r) + for scanner.Scan() { + l := scanner.Text() + if len(l) == 0 { + continue + } + k, v, ok := strings.Cut(l, ":") + if !ok { + return nil, fmt.Errorf("%s: connection meta file contains invalid entry; expecting k: v pair:%s", op, l) + } + v = strings.TrimSpace(v) + switch { + case k == "id": + c.Id = v + } + } + + return c, nil +} + // NewConnection creates a Connection container for a given connection id. func (s *Session) NewConnection(ctx context.Context, meta *ConnectionMeta) (*Connection, error) { const op = "bsr.(Session).NewConnection" @@ -189,11 +390,6 @@ 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 -} - // 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 { @@ -203,6 +399,56 @@ type Connection struct { Meta *ConnectionMeta } +// ChannelMeta contains metadata about a channel in a BSR. +type ChannelMeta struct { + Id string + Type string +} + +func (c ChannelMeta) isValid() bool { + switch { + case c.Id == "": + return false + case c.Type == "": + return false + default: + return true + } +} + +// decodeChannelMeta will populate the ChannelMeta for a BSR Channel +// TODO Unmarshal without brute force +func decodeChannelMeta(ctx context.Context, r io.Reader) (*ChannelMeta, error) { + const op = "bsr.decodeChannelMeta" + + switch { + case r == nil: + return nil, fmt.Errorf("%s: missing channel meta file: %w", op, ErrInvalidParameter) + } + + c := &ChannelMeta{} + scanner := bufio.NewScanner(r) + for scanner.Scan() { + l := scanner.Text() + if len(l) == 0 { + continue + } + k, v, ok := strings.Cut(l, ":") + if !ok { + return nil, fmt.Errorf("%s: channel meta file contains invalid entry; expecting k: v pair:%s", op, l) + } + v = strings.TrimSpace(v) + switch { + case k == "id": + c.Id = v + case k == "channelType": + c.Type = v + } + } + + return c, nil +} + // NewChannel creates a Channel container for a given channel id. func (c *Connection) NewChannel(ctx context.Context, meta *ChannelMeta) (*Channel, error) { const op = "bsr.(Connection).NewChannel" @@ -233,6 +479,9 @@ func (c *Connection) NewChannel(ctx context.Context, meta *ChannelMeta) (*Channe if _, err := nc.WriteMeta(ctx, "id", meta.Id); err != nil { return nil, err } + if _, err := nc.WriteMeta(ctx, "channelType", meta.Type); err != nil { + return nil, err + } return &Channel{ container: nc, Meta: meta, @@ -288,12 +537,6 @@ func (c *Connection) Close(ctx context.Context) error { return c.container.close(ctx) } -// ChannelMeta contains metadata about a channel in a BSR. -type ChannelMeta struct { - Id string - Type string -} - // Channel is a container in a bsr for a specific channel in a session // container. It contains the files for the recorded channel. type Channel struct { diff --git a/internal/bsr/bsr_test.go b/internal/bsr/bsr_test.go index c3224c886..7a8683331 100644 --- a/internal/bsr/bsr_test.go +++ b/internal/bsr/bsr_test.go @@ -282,7 +282,7 @@ func TestBsr(t *testing.T) { createdChannels := make([]*channel, 0, len(conn.channels)) for _, chann := range conn.channels { - ch, err := c.NewChannel(ctx, &bsr.ChannelMeta{Id: chann.id}) + ch, err := c.NewChannel(ctx, &bsr.ChannelMeta{Id: chann.id, Type: "chan"}) require.NoError(t, err) require.NotNil(t, ch) diff --git a/internal/bsr/decode_meta_test.go b/internal/bsr/decode_meta_test.go new file mode 100644 index 000000000..e5af823ca --- /dev/null +++ b/internal/bsr/decode_meta_test.go @@ -0,0 +1,66 @@ +// 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) +} diff --git a/internal/bsr/testdata/TestBsr/session_multiplexed/closed/conn_1/chan_1/SHA256SUM b/internal/bsr/testdata/TestBsr/session_multiplexed/closed/conn_1/chan_1/SHA256SUM index 3019ed30e..e3eaa85aa 100644 --- a/internal/bsr/testdata/TestBsr/session_multiplexed/closed/conn_1/chan_1/SHA256SUM +++ b/internal/bsr/testdata/TestBsr/session_multiplexed/closed/conn_1/chan_1/SHA256SUM @@ -2,5 +2,5 @@ e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 messages-inbou e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 requests-inbound.data e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 messages-outbound.data e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 requests-outbound.data -e30a207b53f444c7bd2d1e8cc102abf910dfafd70aaef7f24db00298832b74d3 channel.meta +27a58452605f6e30ca048cc540e9d963c7270212fcb2c7b73fc69e314ffd1902 channel.meta e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 channel.summary diff --git a/internal/bsr/testdata/TestBsr/session_multiplexed/closed/conn_1/chan_1/channel.meta b/internal/bsr/testdata/TestBsr/session_multiplexed/closed/conn_1/chan_1/channel.meta index c63cfcef7..be0e2f221 100644 --- a/internal/bsr/testdata/TestBsr/session_multiplexed/closed/conn_1/chan_1/channel.meta +++ b/internal/bsr/testdata/TestBsr/session_multiplexed/closed/conn_1/chan_1/channel.meta @@ -1,4 +1,5 @@ id: chan_1 +channelType: chan messages: inbound requests: inbound messages: outbound diff --git a/internal/bsr/testdata/TestBsr/session_multiplexed/opened/conn_1/chan_1/channel.meta b/internal/bsr/testdata/TestBsr/session_multiplexed/opened/conn_1/chan_1/channel.meta index c49943d95..9f389c897 100644 --- a/internal/bsr/testdata/TestBsr/session_multiplexed/opened/conn_1/chan_1/channel.meta +++ b/internal/bsr/testdata/TestBsr/session_multiplexed/opened/conn_1/chan_1/channel.meta @@ -1 +1,2 @@ id: chan_1 +channelType: chan