ICU-745/Refactor internal/session to new domain errors (#877)

* Refactor internal/sessions to use domain errors
This commit is contained in:
Louis Ruch authored and GitHub committed 2021-02-03 16:25:56 -08:00
1 parent 81785e139f
commit 178d4efa64
24 files changed
+404 -329

No files matched your search

+5
View File
@@ -31,6 +31,9 @@ const (
TicketNotFound Code = 107 // TicketNotFound represents that the ticket was not found
Io Code = 108 // Io represents that an io error occurred in an underlying call (i.e binary.Write)
InvalidTimeStamp Code = 109 // InvalidTimeStamp represents an invalid time stamp for an operation
SessionNotFound Code = 110 // SessionNotFound represents that the session was not found
InvalidSessionState Code = 111 // InvalidSessionState represents that the session was in an invalid state
TokenMismatch Code = 112 // TokenMismatch represents that there was a token mismatch
// PasswordTooShort results from attempting to set a password which is to short.
PasswordTooShort Code = 200
@@ -51,6 +54,8 @@ const (
Decrypt Code = 301 // Decrypt represents an error occurred during the underlying decryption process
Encode Code = 302 // Encode represents an error occurred during the underlying encoding/marshaling process
Decode Code = 303 // Decode represents an error occurred during the underlying decoding/unmarshaling process
GenKey Code = 304 // GenKey represents an error occurred during the underlying key generation process
GenCert Code = 305 // GenCert represents an error occurred during the underlying certificate generation process
// DB errors are reserved Codes from 1000-1999
CheckConstraint Code = 1000 // CheckConstraint represents a check constraint error
+25
View File
@@ -77,6 +77,21 @@ func TestCode_Both_String_Info(t *testing.T) {
c: InvalidTimeStamp,
want: InvalidTimeStamp,
},
{
name: "SessionNotFound",
c: SessionNotFound,
want: SessionNotFound,
},
{
name: "InvalidSessionState",
c: InvalidSessionState,
want: InvalidSessionState,
},
{
name: "TokenMismatch",
c: TokenMismatch,
want: TokenMismatch,
},
{
name: "PasswordTooShort",
c: PasswordTooShort,
@@ -117,6 +132,16 @@ func TestCode_Both_String_Info(t *testing.T) {
c: Decode,
want: Decode,
},
{
name: "GenKey",
c: GenKey,
want: GenKey,
},
{
name: "GenCert",
c: GenCert,
want: GenCert,
},
{
name: "CheckConstraint",
c: CheckConstraint,
+20
View File
@@ -56,6 +56,18 @@ var errorCodeInfo = map[Code]Info{
Message: "invalid time stamp",
Kind: Integrity,
},
SessionNotFound: {
Message: "session not found",
Kind: Integrity,
},
InvalidSessionState: {
Message: "session state was not valid for the requested operation",
Kind: Integrity,
},
TokenMismatch: {
Message: "token mismatch",
Kind: Integrity,
},
PasswordTooShort: {
Message: "too short",
Kind: Password,
@@ -88,6 +100,14 @@ var errorCodeInfo = map[Code]Info{
Message: "error occurred during decode",
Kind: Encoding,
},
GenKey: {
Message: "error occurred during key generation",
Kind: Encryption,
},
GenCert: {
Message: "error occurred during certification generation",
Kind: Encryption,
},
CheckConstraint: {
Message: "constraint check failed",
Kind: Integrity,
+1 -1
View File
@@ -131,7 +131,7 @@ func (r *Repository) UpdateUser(ctx context.Context, user *User, version uint32,
}
if rowsUpdated > 1 {
// return err, which will result in a rollback of the update
return errors.New(errors.MultipleRecords, op, "error more than 1 resource would have been updated")
return errors.New(errors.MultipleRecords, op, "more than 1 resource would have been updated")
}
// we need a new repo, that's using the same reader/writer as this TxHandler
txRepo := &Repository{
+21 -19
View File
@@ -2,7 +2,6 @@ package session
import (
"context"
"fmt"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/db/timestamp"
@@ -55,7 +54,8 @@ var (
// New creates a new in memory session. No options
// are currently supported.
func NewConnection(sessionID, clientTcpAddress string, clientTcpPort uint32, endpointTcpAddr string, endpointTcpPort uint32, opt ...Option) (*Connection, error) {
func NewConnection(sessionID, clientTcpAddress string, clientTcpPort uint32, endpointTcpAddr string, endpointTcpPort uint32, _ ...Option) (*Connection, error) {
const op = "session.NewConnection"
c := Connection{
SessionId: sessionID,
ClientTcpAddress: clientTcpAddress,
@@ -63,8 +63,8 @@ func NewConnection(sessionID, clientTcpAddress string, clientTcpPort uint32, end
EndpointTcpAddress: endpointTcpAddr,
EndpointTcpPort: endpointTcpPort,
}
if err := c.validateNewConnection("new connection:"); err != nil {
return nil, err
if err := c.validateNewConnection(); err != nil {
return nil, errors.Wrap(err, op)
}
return &c, nil
}
@@ -109,29 +109,30 @@ func (c *Connection) Clone() interface{} {
// VetForWrite implements db.VetForWrite() interface and validates the connection
// before it's written.
func (c *Connection) VetForWrite(ctx context.Context, r db.Reader, opType db.OpType, opt ...db.Option) error {
func (c *Connection) VetForWrite(_ context.Context, _ db.Reader, opType db.OpType, opt ...db.Option) error {
const op = "session.(Connection).VetForWrite"
opts := db.GetOpts(opt...)
if c.PublicId == "" {
return fmt.Errorf("connection vet for write: missing public id: %w", errors.ErrInvalidParameter)
return errors.New(errors.InvalidParameter, op, "missing public id")
}
switch opType {
case db.CreateOp:
if err := c.validateNewConnection("connection vet for write:"); err != nil {
return err
if err := c.validateNewConnection(); err != nil {
return errors.Wrap(err, op)
}
case db.UpdateOp:
switch {
case contains(opts.WithFieldMaskPaths, "PublicId"):
return fmt.Errorf("connection vet for write: public id is immutable: %w", errors.ErrInvalidParameter)
return errors.New(errors.InvalidParameter, op, "public id is immutable")
case contains(opts.WithFieldMaskPaths, "SessionId"):
return fmt.Errorf("connection vet for write: session id is immutable: %w", errors.ErrInvalidParameter)
return errors.New(errors.InvalidParameter, op, "session id is immutable")
case contains(opts.WithFieldMaskPaths, "CreateTime"):
return fmt.Errorf("connection vet for write: create time is immutable: %w", errors.ErrInvalidParameter)
return errors.New(errors.InvalidParameter, op, "create time is immutable")
case contains(opts.WithFieldMaskPaths, "UpdateTime"):
return fmt.Errorf("connection vet for write: update time is immutable: %w", errors.ErrInvalidParameter)
return errors.New(errors.InvalidParameter, op, "update time is immutable")
case contains(opts.WithFieldMaskPaths, "ClosedReason"):
if _, err := convertToClosedReason(c.ClosedReason); err != nil {
return fmt.Errorf("connection vet for write: %w", errors.ErrInvalidParameter)
return errors.Wrap(err, op)
}
}
}
@@ -154,21 +155,22 @@ func (c *Connection) SetTableName(n string) {
}
// validateNewConnection checks everything but the connection's PublicId
func (c *Connection) validateNewConnection(errorPrefix string) error {
func (c *Connection) validateNewConnection() error {
const op = "session.(Connection).validateNewConnection"
if c.SessionId == "" {
return fmt.Errorf("%s missing session id: %w", errorPrefix, errors.ErrInvalidParameter)
return errors.New(errors.InvalidParameter, op, "missing session id")
}
if c.ClientTcpAddress == "" {
return fmt.Errorf("%s missing client address: %w", errorPrefix, errors.ErrInvalidParameter)
return errors.New(errors.InvalidParameter, op, "missing client address")
}
if c.ClientTcpPort == 0 {
return fmt.Errorf("%s missing client port: %w", errorPrefix, errors.ErrInvalidParameter)
return errors.New(errors.InvalidParameter, op, "missing client port")
}
if c.EndpointTcpAddress == "" {
return fmt.Errorf("%s missing endpoint address: %w", errorPrefix, errors.ErrInvalidParameter)
return errors.New(errors.InvalidParameter, op, "missing endpoint address")
}
if c.EndpointTcpPort == 0 {
return fmt.Errorf("%s missing endpoint port: %w", errorPrefix, errors.ErrInvalidParameter)
return errors.New(errors.InvalidParameter, op, "missing endpoint port")
}
return nil
}
+2 -1
View File
@@ -24,6 +24,7 @@ func (r ClosedReason) String() string {
}
func convertToClosedReason(s string) (ClosedReason, error) {
const op = "session.convertToClosedReason"
switch s {
case UnknownReason.String():
return UnknownReason, nil
@@ -38,6 +39,6 @@ func convertToClosedReason(s string) (ClosedReason, error) {
case ConnectionSystemError.String():
return ConnectionSystemError, nil
default:
return "", fmt.Errorf("closed reason: %s is not a valid reason: %w", s, errors.ErrInvalidParameter)
return "", errors.New(errors.InvalidParameter, op, fmt.Sprintf("%s is not a valid reason", s))
}
}
+3 -4
View File
@@ -1,8 +1,6 @@
package session
import (
"fmt"
"github.com/hashicorp/boundary/internal/errors"
)
@@ -16,11 +14,12 @@ type CloseWith struct {
}
func (c CloseWith) validate() error {
const op = "session.(CloseWith).validate"
if c.ConnectionId == "" {
return fmt.Errorf("missing connection id: %w", errors.ErrInvalidParameter)
return errors.New(errors.InvalidParameter, op, "missing connection id")
}
if c.ClosedReason.String() == "" {
return fmt.Errorf("missing closed reason: %w", errors.ErrInvalidParameter)
return errors.New(errors.InvalidParameter, op, "missing closed reason")
}
// 0 is valid for BytesUp and BytesDown
return nil
+15 -13
View File
@@ -2,7 +2,6 @@ package session
import (
"context"
"fmt"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/db/timestamp"
@@ -66,13 +65,14 @@ var (
// NewConnectionState creates a new in memory connection state. No options
// are currently supported.
func NewConnectionState(connectionId string, state ConnectionStatus, opt ...Option) (*ConnectionState, error) {
func NewConnectionState(connectionId string, state ConnectionStatus, _ ...Option) (*ConnectionState, error) {
const op = "session.NewConnectionState"
s := ConnectionState{
ConnectionId: connectionId,
Status: state,
}
if err := s.validate("new connection state:"); err != nil {
return nil, err
if err := s.validate(); err != nil {
return nil, errors.Wrap(err, op)
}
return &s, nil
}
@@ -118,9 +118,10 @@ func (s *ConnectionState) Clone() interface{} {
// VetForWrite implements db.VetForWrite() interface and validates the state
// before it's written.
func (s *ConnectionState) VetForWrite(ctx context.Context, r db.Reader, opType db.OpType, opt ...db.Option) error {
if err := s.validate("connection state vet for write:"); err != nil {
return err
func (s *ConnectionState) VetForWrite(_ context.Context, _ db.Reader, _ db.OpType, _ ...db.Option) error {
const op = "session.(ConnectionState).VetForWrite"
if err := s.validate(); err != nil {
return errors.Wrap(err, op)
}
return nil
}
@@ -141,21 +142,22 @@ func (s *ConnectionState) SetTableName(n string) {
}
// validate checks the session state
func (s *ConnectionState) validate(errorPrefix string) error {
func (s *ConnectionState) validate() error {
const op = "session.(ConnectionState).validate"
if s.Status == "" {
return fmt.Errorf("%s missing status: %w", errorPrefix, errors.ErrInvalidParameter)
return errors.New(errors.InvalidParameter, op, "missing status")
}
if s.ConnectionId == "" {
return fmt.Errorf("%s missing connection id: %w", errorPrefix, errors.ErrInvalidParameter)
return errors.New(errors.InvalidParameter, op, "missing connection id")
}
if s.StartTime != nil {
return fmt.Errorf("%s start time is not settable: %w", errorPrefix, errors.ErrInvalidParameter)
return errors.New(errors.InvalidParameter, op, "start time is not settable")
}
if s.EndTime != nil {
return fmt.Errorf("%s end time is not settable: %w", errorPrefix, errors.ErrInvalidParameter)
return errors.New(errors.InvalidParameter, op, "end time is not settable")
}
if s.PreviousEndTime != nil {
return fmt.Errorf("%s previous end time is not settable: %w", errorPrefix, errors.ErrInvalidParameter)
return errors.New(errors.InvalidParameter, op, "previous end time is not settable")
}
return nil
}
+4 -4
View File
@@ -28,7 +28,7 @@ func TestConnectionState_Create(t *testing.T) {
args args
want *ConnectionState
wantErr bool
wantIsErr error
wantIsErr errors.Code
create bool
wantCreateErr bool
}{
@@ -50,7 +50,7 @@ func TestConnectionState_Create(t *testing.T) {
status: StatusClosed,
},
wantErr: true,
wantIsErr: errors.ErrInvalidParameter,
wantIsErr: errors.InvalidParameter,
},
{
name: "empty-status",
@@ -58,7 +58,7 @@ func TestConnectionState_Create(t *testing.T) {
connectionId: connection.PublicId,
},
wantErr: true,
wantIsErr: errors.ErrInvalidParameter,
wantIsErr: errors.InvalidParameter,
},
}
for _, tt := range tests {
@@ -67,7 +67,7 @@ func TestConnectionState_Create(t *testing.T) {
got, err := NewConnectionState(tt.args.connectionId, tt.args.status)
if tt.wantErr {
require.Error(err)
assert.True(errors.Is(err, tt.wantIsErr))
assert.True(errors.Match(errors.T(tt.wantIsErr), err))
return
}
require.NoError(err)
+7 -7
View File
@@ -30,7 +30,7 @@ func TestConnection_Create(t *testing.T) {
args args
want *Connection
wantErr bool
wantIsErr error
wantIsErr errors.Code
create bool
wantCreateErr bool
}{
@@ -61,7 +61,7 @@ func TestConnection_Create(t *testing.T) {
endpointTcpPort: 2222,
},
wantErr: true,
wantIsErr: errors.ErrInvalidParameter,
wantIsErr: errors.InvalidParameter,
},
{
name: "empty-client-address",
@@ -72,7 +72,7 @@ func TestConnection_Create(t *testing.T) {
endpointTcpPort: 2222,
},
wantErr: true,
wantIsErr: errors.ErrInvalidParameter,
wantIsErr: errors.InvalidParameter,
},
{
name: "empty-client-port",
@@ -83,7 +83,7 @@ func TestConnection_Create(t *testing.T) {
endpointTcpPort: 2222,
},
wantErr: true,
wantIsErr: errors.ErrInvalidParameter,
wantIsErr: errors.InvalidParameter,
},
{
name: "empty-endpoint-address",
@@ -94,7 +94,7 @@ func TestConnection_Create(t *testing.T) {
endpointTcpPort: 2222,
},
wantErr: true,
wantIsErr: errors.ErrInvalidParameter,
wantIsErr: errors.InvalidParameter,
},
{
name: "empty-endpoint-port",
@@ -105,7 +105,7 @@ func TestConnection_Create(t *testing.T) {
endpointTcpAddress: "127.0.0.1",
},
wantErr: true,
wantIsErr: errors.ErrInvalidParameter,
wantIsErr: errors.InvalidParameter,
},
}
for _, tt := range tests {
@@ -120,7 +120,7 @@ func TestConnection_Create(t *testing.T) {
)
if tt.wantErr {
require.Error(err)
assert.True(errors.Is(err, tt.wantIsErr))
assert.True(errors.Match(errors.T(tt.wantIsErr), err))
return
}
require.NoError(err)
-8
View File
@@ -8,12 +8,4 @@ var (
// ErrInvalidStateForOperation indicates the session's state does not allow the
// operation.
ErrInvalidStateForOperation = errors.New("state is invalid for operation")
// ErrSessionNotPending indicates that a session cannot be activated
// because it's not in a pending state.
ErrSessionNotPending = errors.New("session is not in a pending state")
// ErrOpenConnection indicates that a session can not be terminated because
// it has open connections.
ErrOpenConnection = errors.New("session has open connections")
)
+9 -6
View File
@@ -1,9 +1,8 @@
package session
import (
"fmt"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/errors"
)
const (
@@ -21,33 +20,37 @@ const (
)
func newId() (string, error) {
const op = "session.newId"
id, err := db.NewPublicId(SessionPrefix)
if err != nil {
return "", fmt.Errorf("new session id: %w", err)
return "", errors.Wrap(err, op)
}
return id, nil
}
func newStateId() (string, error) {
const op = "session.newStateId"
id, err := db.NewPublicId(StatePrefix)
if err != nil {
return "", fmt.Errorf("new session state id: %w", err)
return "", errors.Wrap(err, op)
}
return id, nil
}
func newConnectionId() (string, error) {
const op = "session.newConnectionId"
id, err := db.NewPublicId(ConnectionPrefix)
if err != nil {
return "", fmt.Errorf("new session connection id: %w", err)
return "", errors.Wrap(err, op)
}
return id, nil
}
func newConnectionStateId() (string, error) {
const op = "session.newConnectionStateId"
id, err := db.NewPublicId(ConnectionStatePrefix)
if err != nil {
return "", fmt.Errorf("new session connection state id: %w", err)
return "", errors.Wrap(err, op)
}
return id, nil
}
+13 -8
View File
@@ -2,11 +2,10 @@ package session
import (
"context"
"errors"
"fmt"
"sort"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/errors"
"github.com/hashicorp/boundary/internal/kms"
)
@@ -28,14 +27,15 @@ type Repository struct {
// NewRepository creates a new session Repository. Supports the options: WithLimit
// which sets a default limit on results returned by repo operations.
func NewRepository(r db.Reader, w db.Writer, kms *kms.Kms, opt ...Option) (*Repository, error) {
const op = "session.NewRepository"
if r == nil {
return nil, errors.New("error creating db repository with nil reader")
return nil, errors.New(errors.InvalidParameter, op, "nil reader")
}
if w == nil {
return nil, errors.New("error creating db repository with nil writer")
return nil, errors.New(errors.InvalidParameter, op, "nil writer")
}
if kms == nil {
return nil, errors.New("error creating db repository with nil kms")
return nil, errors.New(errors.InvalidParameter, op, "nil kms")
}
opts := getOpts(opt...)
if opts.withLimit == 0 {
@@ -53,6 +53,7 @@ func NewRepository(r db.Reader, w db.Writer, kms *kms.Kms, opt ...Option) (*Repo
// list will return a listing of resources and honor the WithLimit option or the
// repo defaultLimit. Supports WithOrder option.
func (r *Repository) list(ctx context.Context, resources interface{}, where string, args []interface{}, opts options) error {
const op = "session.(Repository).list"
limit := r.defaultLimit
var dbOpts []db.Option
if opts.withLimit != 0 {
@@ -63,10 +64,14 @@ func (r *Repository) list(ctx context.Context, resources interface{}, where stri
if opts.withOrder != "" {
dbOpts = append(dbOpts, db.WithOrder(opts.withOrder))
}
return r.reader.SearchWhere(ctx, resources, where, args, dbOpts...)
if err := r.reader.SearchWhere(ctx, resources, where, args, dbOpts...); err != nil {
return errors.Wrap(err, op)
}
return nil
}
func (r *Repository) convertToSessions(ctx context.Context, sessionsWithState []*sessionView, opt ...Option) ([]*Session, error) {
const op = "session.(Repository).convertToSessions"
opts := getOpts(opt...)
if len(sessionsWithState) == 0 {
@@ -114,10 +119,10 @@ func (r *Repository) convertToSessions(ctx context.Context, sessionsWithState []
if len(workingSession.CtTofuToken) > 0 {
databaseWrapper, err := r.kms.GetWrapper(ctx, workingSession.ScopeId, kms.KeyPurposeDatabase, kms.WithKeyId(workingSession.KeyId))
if err != nil {
return nil, fmt.Errorf("convert session: unable to get database wrapper: %w", err)
return nil, errors.Wrap(err, op, errors.WithMsg("unable to get database wrapper"))
}
if err := workingSession.decrypt(ctx, databaseWrapper); err != nil {
return nil, fmt.Errorf("convert session: cannot decrypt session value: %w", err)
return nil, errors.Wrap(err, op, errors.WithMsg("cannot decrypt session value"))
}
} else {
workingSession.CtTofuToken = nil
+22 -16
View File
@@ -2,7 +2,6 @@ package session
import (
"context"
stderrors "errors"
"fmt"
"github.com/hashicorp/boundary/internal/db"
@@ -12,9 +11,10 @@ import (
// LookupConnection will look up a connection in the repository and return the connection
// with its states. If the connection is not found, it will return nil, nil, nil.
// No options are currently supported.
func (r *Repository) LookupConnection(ctx context.Context, connectionId string, opt ...Option) (*Connection, []*ConnectionState, error) {
func (r *Repository) LookupConnection(ctx context.Context, connectionId string, _ ...Option) (*Connection, []*ConnectionState, error) {
const op = "session.(Repository).LookupConnection"
if connectionId == "" {
return nil, nil, fmt.Errorf("lookup connection: missing connectionId id: %w", errors.ErrInvalidParameter)
return nil, nil, errors.New(errors.InvalidParameter, op, "missing connectionId id")
}
connection := AllocConnection()
connection.PublicId = connectionId
@@ -25,11 +25,11 @@ func (r *Repository) LookupConnection(ctx context.Context, connectionId string,
db.ExpBackoff{},
func(read db.Reader, w db.Writer) error {
if err := read.LookupById(ctx, &connection); err != nil {
return fmt.Errorf("lookup connection: failed %w for %s", err, connectionId)
return errors.Wrap(err, op, errors.WithMsg(fmt.Sprintf("failed for %s", connectionId)))
}
var err error
if states, err = fetchConnectionStates(ctx, read, connectionId, db.WithOrder("start_time desc")); err != nil {
return err
return errors.Wrap(err, op)
}
return nil
},
@@ -38,31 +38,33 @@ func (r *Repository) LookupConnection(ctx context.Context, connectionId string,
if errors.IsNotFoundError(err) {
return nil, nil, nil
}
return nil, nil, fmt.Errorf("lookup connection: %w", err)
return nil, nil, errors.Wrap(err, op)
}
return &connection, states, nil
}
// ListConnections will sessions. Supports the WithLimit and WithOrder options.
func (r *Repository) ListConnections(ctx context.Context, sessionId string, opt ...Option) ([]*Connection, error) {
const op = "session.(Repository).ListConnections"
opts := getOpts(opt...)
var connections []*Connection
err := r.list(ctx, &connections, "session_id = ?", []interface{}{sessionId}, opts) // pass options, so WithLimit and WithOrder are supported
if err != nil {
return nil, fmt.Errorf("list connections: %w", err)
return nil, errors.Wrap(err, op)
}
return connections, nil
}
// DeleteConnection will delete a connection from the repository.
func (r *Repository) DeleteConnection(ctx context.Context, publicId string, opt ...Option) (int, error) {
func (r *Repository) DeleteConnection(ctx context.Context, publicId string, _ ...Option) (int, error) {
const op = "session.(Repository).DeleteConnection"
if publicId == "" {
return db.NoRowsAffected, fmt.Errorf("delete connection: missing public id %w", errors.ErrInvalidParameter)
return db.NoRowsAffected, errors.New(errors.InvalidParameter, op, "missing public id")
}
connection := AllocConnection()
connection.PublicId = publicId
if err := r.reader.LookupByPublicId(ctx, &connection); err != nil {
return db.NoRowsAffected, fmt.Errorf("delete connection: failed %w for %s", err, publicId)
return db.NoRowsAffected, errors.Wrap(err, op, errors.WithMsg(fmt.Sprintf("failed for %s", publicId)))
}
var rowsDeleted int
@@ -77,23 +79,27 @@ func (r *Repository) DeleteConnection(ctx context.Context, publicId string, opt
ctx,
deleteConnection,
)
if err == nil && rowsDeleted > 1 {
// return err, which will result in a rollback of the delete
return stderrors.New("error more than 1 connection would have been deleted")
if err != nil {
return errors.Wrap(err, op)
}
return err
if rowsDeleted > 1 {
// return err, which will result in a rollback of the delete
return errors.New(errors.MultipleRecords, op, "more than 1 resource would have been deleted")
}
return nil
},
)
if err != nil {
return db.NoRowsAffected, fmt.Errorf("delete connection: failed %w for %s", err, publicId)
return db.NoRowsAffected, errors.Wrap(err, op, errors.WithMsg(fmt.Sprintf("failed for %s", publicId)))
}
return rowsDeleted, nil
}
func fetchConnectionStates(ctx context.Context, r db.Reader, connectionId string, opt ...db.Option) ([]*ConnectionState, error) {
const op = "session.fetchConnectionStates"
var states []*ConnectionState
if err := r.SearchWhere(ctx, &states, "connection_id = ?", []interface{}{connectionId}, opt...); err != nil {
return nil, fmt.Errorf("fetch connection states: %w", err)
return nil, errors.Wrap(err, op)
}
if len(states) == 0 {
return nil, nil
@@ -169,7 +169,7 @@ func TestRepository_DeleteConnection(t *testing.T) {
},
wantRowsDeleted: 0,
wantErr: true,
wantErrMsg: "delete connection: missing public id invalid parameter",
wantErrMsg: "session.(Repository).DeleteConnection: missing public id: parameter violation: error #100",
},
{
name: "not-found",
@@ -184,7 +184,7 @@ func TestRepository_DeleteConnection(t *testing.T) {
},
wantRowsDeleted: 0,
wantErr: true,
wantErrMsg: "delete connection: failed db.LookupById: record not found",
wantErrMsg: "db.LookupById: record not found",
},
}
for _, tt := range tests {
+124 -108
View File
@@ -4,7 +4,6 @@ import (
"context"
"crypto/ed25519"
"crypto/subtle"
stderrors "errors"
"fmt"
"strings"
@@ -21,58 +20,59 @@ import (
// its State of "Pending". The following fields must be empty when creating a
// session: ServerId, ServerType, and PublicId. No options are
// currently supported.
func (r *Repository) CreateSession(ctx context.Context, sessionWrapper wrapping.Wrapper, newSession *Session, opt ...Option) (*Session, ed25519.PrivateKey, error) {
func (r *Repository) CreateSession(ctx context.Context, sessionWrapper wrapping.Wrapper, newSession *Session, _ ...Option) (*Session, ed25519.PrivateKey, error) {
const op = "session.(Repository).CreateSession"
if newSession == nil {
return nil, nil, fmt.Errorf("create session: missing session: %w", errors.ErrInvalidParameter)
return nil, nil, errors.New(errors.InvalidParameter, op, "missing session")
}
if newSession.PublicId != "" {
return nil, nil, fmt.Errorf("create session: public id is not empty: %w", errors.ErrInvalidParameter)
return nil, nil, errors.New(errors.InvalidParameter, op, "public id is not empty")
}
if len(newSession.Certificate) != 0 {
return nil, nil, fmt.Errorf("create session: certificate is not empty: %w", errors.ErrInvalidParameter)
return nil, nil, errors.New(errors.InvalidParameter, op, "certificate is not empty")
}
if newSession.TargetId == "" {
return nil, nil, fmt.Errorf("create session: target id is empty: %w", errors.ErrInvalidParameter)
return nil, nil, errors.New(errors.InvalidParameter, op, "missing target id")
}
if newSession.HostId == "" {
return nil, nil, fmt.Errorf("create session: user id is empty: %w", errors.ErrInvalidParameter)
return nil, nil, errors.New(errors.InvalidParameter, op, "missing host id")
}
if newSession.UserId == "" {
return nil, nil, fmt.Errorf("create session: user id is empty: %w", errors.ErrInvalidParameter)
return nil, nil, errors.New(errors.InvalidParameter, op, "missing user id")
}
if newSession.HostSetId == "" {
return nil, nil, fmt.Errorf("create session: host set id is empty: %w", errors.ErrInvalidParameter)
return nil, nil, errors.New(errors.InvalidParameter, op, "missing host set id")
}
if newSession.AuthTokenId == "" {
return nil, nil, fmt.Errorf("create session: auth token id is empty: %w", errors.ErrInvalidParameter)
return nil, nil, errors.New(errors.InvalidParameter, op, "missing auth token id")
}
if newSession.ScopeId == "" {
return nil, nil, fmt.Errorf("create session: scope id is empty: %w", errors.ErrInvalidParameter)
return nil, nil, errors.New(errors.InvalidParameter, op, "missing scope id")
}
if newSession.ServerId != "" {
return nil, nil, fmt.Errorf("create session: server id must be empty: %w", errors.ErrInvalidParameter)
return nil, nil, errors.New(errors.InvalidParameter, op, "server id is not empty")
}
if newSession.ServerType != "" {
return nil, nil, fmt.Errorf("create session: server type must be empty: %w", errors.ErrInvalidParameter)
return nil, nil, errors.New(errors.InvalidParameter, op, "server type is not empty")
}
if newSession.CtTofuToken != nil {
return nil, nil, fmt.Errorf("create session: ct must be empty: %w", errors.ErrInvalidParameter)
return nil, nil, errors.New(errors.InvalidParameter, op, "ct is not empty")
}
if newSession.TofuToken != nil {
return nil, nil, fmt.Errorf("create session: tofu token must be empty: %w", errors.ErrInvalidParameter)
return nil, nil, errors.New(errors.InvalidParameter, op, "tofu token is not empty")
}
if newSession.ExpirationTime == nil || newSession.ExpirationTime.Timestamp.AsTime().IsZero() {
return nil, nil, fmt.Errorf("create session: expiration is empty: %w", errors.ErrInvalidParameter)
return nil, nil, errors.New(errors.InvalidParameter, op, "missing expiration time")
}
id, err := newId()
if err != nil {
return nil, nil, fmt.Errorf("create session: %w", err)
return nil, nil, errors.Wrap(err, op)
}
privKey, certBytes, err := newCert(sessionWrapper, newSession.UserId, id, newSession.ExpirationTime.Timestamp.AsTime())
if err != nil {
return nil, nil, fmt.Errorf("create session: %w", err)
return nil, nil, errors.Wrap(err, op)
}
newSession.Certificate = certBytes
newSession.PublicId = id
@@ -85,39 +85,40 @@ func (r *Repository) CreateSession(ctx context.Context, sessionWrapper wrapping.
func(read db.Reader, w db.Writer) error {
returnedSession = newSession.Clone().(*Session)
if err = w.Create(ctx, returnedSession); err != nil {
return err
return errors.Wrap(err, op)
}
var foundStates []*State
// trigger will create new "Pending" state
if foundStates, err = fetchStates(ctx, read, returnedSession.PublicId); err != nil {
return err
return errors.Wrap(err, op)
}
if len(foundStates) != 1 {
return fmt.Errorf("%d states found for new session %s", len(foundStates), returnedSession.PublicId)
return errors.New(errors.MultipleRecords, op, fmt.Sprintf("%d states found for new session %s", len(foundStates), returnedSession.PublicId))
}
if len(foundStates) == 0 {
return fmt.Errorf("no states found for new session %s", returnedSession.PublicId)
return errors.New(errors.SessionNotFound, op, fmt.Sprintf("no states found for new session %s", returnedSession.PublicId))
}
returnedSession.States = foundStates
if returnedSession.States[0].Status != StatusPending {
return fmt.Errorf("new session %s state is not valid: %s", returnedSession.PublicId, returnedSession.States[0].Status)
return errors.New(errors.InvalidSessionState, op, fmt.Sprintf("new session %s state is not valid: %s", returnedSession.PublicId, returnedSession.States[0].Status))
}
return nil
},
)
if err != nil {
return nil, nil, fmt.Errorf("create session: %w", err)
return nil, nil, errors.Wrap(err, op)
}
return returnedSession, privKey, err
return returnedSession, privKey, nil
}
// LookupSession will look up a session in the repository and return the session
// with its states. Returned States are ordered by start time descending. If the
// session is not found, it will return nil, nil, nil. No options are currently
// supported.
func (r *Repository) LookupSession(ctx context.Context, sessionId string, opt ...Option) (*Session, *ConnectionAuthzSummary, error) {
func (r *Repository) LookupSession(ctx context.Context, sessionId string, _ ...Option) (*Session, *ConnectionAuthzSummary, error) {
const op = "session.(Repository).LookupSession"
if sessionId == "" {
return nil, nil, fmt.Errorf("lookup session: missing sessionId id: %w", errors.ErrInvalidParameter)
return nil, nil, errors.New(errors.InvalidParameter, op, "missing session id")
}
session := AllocSession()
session.PublicId = sessionId
@@ -127,11 +128,11 @@ func (r *Repository) LookupSession(ctx context.Context, sessionId string, opt ..
db.ExpBackoff{},
func(read db.Reader, w db.Writer) error {
if err := read.LookupById(ctx, &session); err != nil {
return fmt.Errorf("lookup session: failed %w for %s", err, sessionId)
return errors.Wrap(err, op, errors.WithMsg(fmt.Sprintf("failed for %s", sessionId)))
}
states, err := fetchStates(ctx, read, sessionId, db.WithOrder("start_time desc"))
if err != nil {
return err
return errors.Wrap(err, op)
}
session.States = states
return nil
@@ -141,15 +142,15 @@ func (r *Repository) LookupSession(ctx context.Context, sessionId string, opt ..
if errors.IsNotFoundError(err) {
return nil, nil, nil
}
return nil, nil, fmt.Errorf("lookup session: %w", err)
return nil, nil, errors.Wrap(err, op)
}
if len(session.CtTofuToken) > 0 {
databaseWrapper, err := r.kms.GetWrapper(ctx, session.ScopeId, kms.KeyPurposeDatabase, kms.WithKeyId(session.KeyId))
if err != nil {
return nil, nil, fmt.Errorf("lookup session: unable to get database wrapper: %w", err)
return nil, nil, errors.Wrap(err, op, errors.WithMsg("unable to get database wrapper"))
}
if err := session.decrypt(ctx, databaseWrapper); err != nil {
return nil, nil, fmt.Errorf("lookup session: cannot decrypt session value: %w", err)
return nil, nil, errors.Wrap(err, op, errors.WithMsg("cannot decrypt session value"))
}
} else {
session.CtTofuToken = nil
@@ -157,7 +158,7 @@ func (r *Repository) LookupSession(ctx context.Context, sessionId string, opt ..
authzSummary, err := r.sessionAuthzSummary(ctx, sessionId)
if err != nil {
return nil, nil, fmt.Errorf("lookup session: failed to get authz summary: %w", err)
return nil, nil, errors.Wrap(err, op, errors.WithMsg("failed to get authz summary"))
}
return &session, authzSummary, nil
@@ -165,6 +166,7 @@ func (r *Repository) LookupSession(ctx context.Context, sessionId string, opt ..
// ListSessions will sessions. Supports the WithLimit, WithScopeId and WithSessionIds options.
func (r *Repository) ListSessions(ctx context.Context, opt ...Option) ([]*Session, error) {
const op = "session.(Repository).ListSessions"
opts := getOpts(opt...)
var where []string
var args []interface{}
@@ -220,7 +222,7 @@ func (r *Repository) ListSessions(ctx context.Context, opt ...Option) ([]*Sessio
rows, err := r.reader.Query(ctx, query, args)
if err != nil {
return nil, fmt.Errorf("changes: query failed: %w", err)
return nil, errors.Wrap(err, op)
}
defer rows.Close()
@@ -228,26 +230,27 @@ func (r *Repository) ListSessions(ctx context.Context, opt ...Option) ([]*Sessio
for rows.Next() {
var s sessionView
if err := r.reader.ScanRows(rows, &s); err != nil {
return nil, fmt.Errorf("changes: scan row failed: %w", err)
return nil, errors.Wrap(err, op, errors.WithMsg("scan row failed"))
}
sessionsWithState = append(sessionsWithState, &s)
}
sessions, err := r.convertToSessions(ctx, sessionsWithState, withListingConvert(true))
if err != nil {
return nil, fmt.Errorf("list sessions: %w", err)
return nil, errors.Wrap(err, op)
}
return sessions, nil
}
// DeleteSession will delete a session from the repository.
func (r *Repository) DeleteSession(ctx context.Context, publicId string, opt ...Option) (int, error) {
func (r *Repository) DeleteSession(ctx context.Context, publicId string, _ ...Option) (int, error) {
const op = "session.(Repository).DeleteSession"
if publicId == "" {
return db.NoRowsAffected, fmt.Errorf("delete session: missing public id %w", errors.ErrInvalidParameter)
return db.NoRowsAffected, errors.New(errors.InvalidParameter, op, "missing public id")
}
session := AllocSession()
session.PublicId = publicId
if err := r.reader.LookupByPublicId(ctx, &session); err != nil {
return db.NoRowsAffected, fmt.Errorf("delete session: failed %w for %s", err, publicId)
return db.NoRowsAffected, errors.Wrap(err, op, errors.WithMsg(fmt.Sprintf("failed for %s", publicId)))
}
var rowsDeleted int
@@ -262,15 +265,18 @@ func (r *Repository) DeleteSession(ctx context.Context, publicId string, opt ...
ctx,
deleteSession,
)
if err == nil && rowsDeleted > 1 {
// return err, which will result in a rollback of the delete
return stderrors.New("error more than 1 session would have been deleted")
if err != nil {
return errors.Wrap(err, op)
}
return err
if rowsDeleted > 1 {
// return err, which will result in a rollback of the delete
return errors.New(errors.MultipleRecords, op, "more than 1 resource would have been deleted")
}
return nil
},
)
if err != nil {
return db.NoRowsAffected, fmt.Errorf("delete session: failed %w for %s", err, publicId)
return db.NoRowsAffected, errors.Wrap(err, op, errors.WithMsg(fmt.Sprintf("failed for %s", publicId)))
}
return rowsDeleted, nil
}
@@ -281,15 +287,16 @@ func (r *Repository) DeleteSession(ctx context.Context, publicId string, opt ...
// "canceling signal" during their next status heartbeat. CancelSession is
// idempotent.
func (r *Repository) CancelSession(ctx context.Context, sessionId string, sessionVersion uint32) (*Session, error) {
const op = "session.(Repository).CancelSession"
if sessionId == "" {
return nil, fmt.Errorf("cancel session: missing session id: %w", errors.ErrInvalidParameter)
return nil, errors.New(errors.InvalidParameter, op, "missing session id")
}
if sessionVersion == 0 {
return nil, fmt.Errorf("cancel session: missing session version: %w", errors.ErrInvalidParameter)
return nil, errors.New(errors.InvalidParameter, op, "missing session version")
}
s, ss, err := r.updateState(ctx, sessionId, sessionVersion, StatusCanceling)
if err != nil {
return nil, fmt.Errorf("cancel session: %w", err)
return nil, errors.Wrap(err, op)
}
s.States = ss
return s, nil
@@ -299,11 +306,12 @@ func (r *Repository) CancelSession(ctx context.Context, sessionId string, sessio
// "terminated" Sessions cannot be terminated which still have connections that
// are not closed.
func (r *Repository) TerminateSession(ctx context.Context, sessionId string, sessionVersion uint32, reason TerminationReason) (*Session, error) {
const op = "session.(Repository).TerminateSession"
if sessionId == "" {
return nil, fmt.Errorf("terminate session: missing session id: %w", errors.ErrInvalidParameter)
return nil, errors.New(errors.InvalidParameter, op, "missing session id")
}
if sessionVersion == 0 {
return nil, fmt.Errorf("terminate session: version cannot be zero: %w", errors.ErrInvalidParameter)
return nil, errors.New(errors.InvalidParameter, op, "missing session version")
}
updatedSession := AllocSession()
@@ -316,28 +324,28 @@ func (r *Repository) TerminateSession(ctx context.Context, sessionId string, ses
func(reader db.Reader, w db.Writer) error {
rowsAffected, err := w.Exec(ctx, terminateSessionCte, []interface{}{sessionId, sessionVersion})
if err != nil {
return fmt.Errorf("unable to terminate session %s: %w", sessionId, err)
return errors.Wrap(err, op, errors.WithMsg(fmt.Sprintf("unable to terminate session %s", sessionId)))
}
if rowsAffected == 0 {
return fmt.Errorf("unable to terminate session %s", sessionId)
return errors.New(errors.InvalidSessionState, op, fmt.Sprintf("unable to terminate session %s", sessionId))
}
rowsUpdated, err := w.Update(ctx, &updatedSession, []string{"TerminationReason"}, nil, db.WithVersion(&sessionVersion))
if err != nil {
return fmt.Errorf("update session: failed %w for %s", err, sessionId)
return errors.Wrap(err, op, errors.WithMsg(fmt.Sprintf("failed for %s", sessionId)))
}
if rowsUpdated != 1 {
return fmt.Errorf("update to session %s would have updated %d session", updatedSession.PublicId, rowsUpdated)
return errors.New(errors.MultipleRecords, op, fmt.Sprintf("update to session %s would have updated %d session", updatedSession.PublicId, rowsUpdated))
}
states, err := fetchStates(ctx, reader, sessionId, db.WithOrder("start_time desc"))
if err != nil {
return err
return errors.Wrap(err, op)
}
updatedSession.States = states
return nil
},
)
if err != nil {
return nil, fmt.Errorf("terminate session: %w", err)
return nil, errors.Wrap(err, op)
}
return &updatedSession, nil
}
@@ -349,6 +357,7 @@ func (r *Repository) TerminateSession(ctx context.Context, sessionId string, ses
// This function should called on a periodic basis a Controllers via it's
// "ticker" pattern.
func (r *Repository) TerminateCompletedSessions(ctx context.Context) (int, error) {
const op = "session.(Repository).TerminateCompletedSessions"
var rowsAffected int
_, err := r.writer.DoTx(
ctx,
@@ -358,13 +367,13 @@ func (r *Repository) TerminateCompletedSessions(ctx context.Context) (int, error
var err error
rowsAffected, err = w.Exec(ctx, termSessionsUpdate, nil)
if err != nil {
return err
return errors.Wrap(err, op)
}
return nil
},
)
if err != nil {
return db.NoRowsAffected, fmt.Errorf("terminate completed sessions: %w", err)
return db.NoRowsAffected, errors.Wrap(err, op)
}
return rowsAffected, nil
}
@@ -377,6 +386,7 @@ func (r *Repository) TerminateCompletedSessions(ctx context.Context) (int, error
// and returns it, along with it's states. If the authorization fails, it
// an error of ErrInvalidStateForOperation.
func (r *Repository) AuthorizeConnection(ctx context.Context, sessionId string) (*Connection, []*ConnectionState, *ConnectionAuthzSummary, error) {
const op = "session.(Repository).AuthorizeConnection"
if sessionId == "" {
return nil, nil, nil, status.Errorf(codes.FailedPrecondition, "authorize connection: missing session id: %v", errors.ErrInvalidParameter)
}
@@ -405,17 +415,17 @@ func (r *Repository) AuthorizeConnection(ctx context.Context, sessionId string)
}
connectionStates, err = fetchConnectionStates(ctx, reader, connectionId, db.WithOrder("start_time desc"))
if err != nil {
return err
return errors.Wrap(err, op)
}
return nil
},
)
if err != nil {
return nil, nil, nil, err
return nil, nil, nil, errors.Wrap(err, op)
}
authzSummary, err := r.sessionAuthzSummary(ctx, connection.SessionId)
if err != nil {
return nil, nil, nil, fmt.Errorf("authorize connection: %w", err)
return nil, nil, nil, errors.Wrap(err, op)
}
return &connection, connectionStates, authzSummary, nil
}
@@ -427,20 +437,21 @@ type ConnectionAuthzSummary struct {
}
func (r *Repository) sessionAuthzSummary(ctx context.Context, sessionId string) (*ConnectionAuthzSummary, error) {
const op = "session.(Repository).sessionAuthzSummary"
rows, err := r.reader.Query(ctx, remainingConnectionsCte, []interface{}{sessionId})
if err != nil {
return nil, fmt.Errorf("session summary: query failed: %w", err)
return nil, errors.Wrap(err, op)
}
defer rows.Close()
var info *ConnectionAuthzSummary
for rows.Next() {
if info != nil {
return nil, fmt.Errorf("session summary: query returned more than one row")
return nil, errors.New(errors.MultipleRecords, op, "query returned more than one row")
}
info = &ConnectionAuthzSummary{}
if err := r.reader.ScanRows(rows, info); err != nil {
return nil, fmt.Errorf("session summary: scan row failed: %w", err)
return nil, errors.Wrap(err, op, errors.WithMsg("scan row failed"))
}
}
return info, nil
@@ -448,9 +459,10 @@ func (r *Repository) sessionAuthzSummary(ctx context.Context, sessionId string)
// ConnectConnection updates a connection in the repo with a state of "connected".
func (r *Repository) ConnectConnection(ctx context.Context, c ConnectWith) (*Connection, []*ConnectionState, error) {
const op = "session.(Repository).ConnectConnection"
// ConnectWith.validate will check all the fields...
if err := c.validate(); err != nil {
return nil, nil, fmt.Errorf("connect session: %w", err)
return nil, nil, errors.Wrap(err, op)
}
var connection Connection
var connectionStates []*ConnectionState
@@ -473,28 +485,28 @@ func (r *Repository) ConnectConnection(ctx context.Context, c ConnectWith) (*Con
}
rowsUpdated, err := w.Update(ctx, &connection, fieldMask, nil)
if err != nil {
return err
return errors.Wrap(err, op)
}
if err == nil && rowsUpdated > 1 {
// return err, which will result in a rollback of the update
return stderrors.New("error more than 1 connection would have been updated ")
return errors.New(errors.MultipleRecords, op, "more than 1 resource would have been updated")
}
newState, err := NewConnectionState(connection.PublicId, StatusConnected)
if err != nil {
return err
return errors.Wrap(err, op)
}
if err := w.Create(ctx, newState); err != nil {
return err
return errors.Wrap(err, op)
}
connectionStates, err = fetchConnectionStates(ctx, reader, c.ConnectionId, db.WithOrder("start_time desc"))
if err != nil {
return err
return errors.Wrap(err, op)
}
return nil
},
)
if err != nil {
return nil, nil, fmt.Errorf("connect session: %w", err)
return nil, nil, errors.Wrap(err, op)
}
return &connection, connectionStates, nil
}
@@ -509,13 +521,14 @@ type CloseConnectionResp struct {
// CloseConnections set's a connection's state to "closed" in the repo. It's
// called by a worker after it's closed a connection between the client and the
// endpoint
func (r *Repository) CloseConnections(ctx context.Context, closeWith []CloseWith, opt ...Option) ([]CloseConnectionResp, error) {
func (r *Repository) CloseConnections(ctx context.Context, closeWith []CloseWith, _ ...Option) ([]CloseConnectionResp, error) {
const op = "session.(Repository).CloseConnections"
if len(closeWith) == 0 {
return nil, fmt.Errorf("close connections: missing connections to close: %w", errors.ErrInvalidParameter)
return nil, errors.New(errors.InvalidParameter, op, "missing connections")
}
for _, cw := range closeWith {
if err := cw.validate(); err != nil {
return nil, fmt.Errorf("close connections: %s was invalid: %w", cw.ConnectionId, err)
return nil, errors.Wrap(err, op, errors.WithMsg(fmt.Sprintf("%s was invalid", cw.ConnectionId)))
}
}
var resp []CloseConnectionResp
@@ -539,14 +552,14 @@ func (r *Repository) CloseConnections(ctx context.Context, closeWith []CloseWith
nil,
)
if err != nil {
return fmt.Errorf("unable to update connection %s: %w", cw.ConnectionId, err)
return errors.Wrap(err, op, errors.WithMsg(fmt.Sprintf("unable to update connection %s", cw.ConnectionId)))
}
if rowsUpdated != 1 {
return fmt.Errorf("%d would have been updated for connection %s", rowsUpdated, cw.ConnectionId)
return errors.New(errors.MultipleRecords, op, fmt.Sprintf("%d would have been updated for connection %s", rowsUpdated, cw.ConnectionId))
}
states, err := fetchConnectionStates(ctx, reader, cw.ConnectionId, db.WithOrder("start_time desc"))
if err != nil {
return err
return errors.Wrap(err, op)
}
resp = append(resp, CloseConnectionResp{
Connection: &updateConnection,
@@ -558,7 +571,7 @@ func (r *Repository) CloseConnections(ctx context.Context, closeWith []CloseWith
},
)
if err != nil {
return nil, fmt.Errorf("close connections: %w", err)
return nil, errors.Wrap(err, op)
}
return resp, nil
}
@@ -566,23 +579,24 @@ func (r *Repository) CloseConnections(ctx context.Context, closeWith []CloseWith
// ActivateSession will activate the session and is called by a worker after
// authenticating the session. The session must be in a "pending" state to be
// activated. States are ordered by start time descending. Returns an
// ErrSessionNotPending error if a connection cannot be made because the session
// InvalidSessionState error code if a connection cannot be made because the session
// was canceled or terminated.
func (r *Repository) ActivateSession(ctx context.Context, sessionId string, sessionVersion uint32, serverId, serverType string, tofuToken []byte) (*Session, []*State, error) {
const op = "session.(Repository).ActivateSession"
if sessionId == "" {
return nil, nil, fmt.Errorf("activate session: missing session id: %w", errors.ErrInvalidParameter)
return nil, nil, errors.New(errors.InvalidParameter, op, "missing session id")
}
if sessionVersion == 0 {
return nil, nil, fmt.Errorf("activate session: version cannot be zero: %w", errors.ErrInvalidParameter)
return nil, nil, errors.New(errors.InvalidParameter, op, "missing version")
}
if serverId == "" {
return nil, nil, fmt.Errorf("activate session: missing server id: %w", errors.ErrInvalidParameter)
return nil, nil, errors.New(errors.InvalidParameter, op, "missing server id")
}
if serverType == "" {
return nil, nil, fmt.Errorf("activate session: missing server type: %w", errors.ErrInvalidParameter)
return nil, nil, errors.New(errors.InvalidParameter, op, "missing server type")
}
if len(tofuToken) == 0 {
return nil, nil, fmt.Errorf("activate session: missing tofu token: %w", errors.ErrInvalidParameter)
return nil, nil, errors.New(errors.InvalidParameter, op, "missing tofu token")
}
updatedSession := AllocSession()
@@ -595,48 +609,48 @@ func (r *Repository) ActivateSession(ctx context.Context, sessionId string, sess
func(reader db.Reader, w db.Writer) error {
rowsAffected, err := w.Exec(ctx, activateStateCte, []interface{}{sessionId, sessionVersion})
if err != nil {
return fmt.Errorf("unable to activate session %s: %w", sessionId, err)
return errors.Wrap(err, op, errors.WithMsg(fmt.Sprintf("unable to activate session %s", sessionId)))
}
if rowsAffected == 0 {
return fmt.Errorf("unable to activate session %s: %w", sessionId, ErrSessionNotPending)
return errors.New(errors.InvalidSessionState, op, "session is not in a pending state")
}
foundSession := AllocSession()
foundSession.PublicId = sessionId
if err := reader.LookupById(ctx, &foundSession); err != nil {
return fmt.Errorf("lookup session: failed for %s: %w", sessionId, err)
return errors.Wrap(err, op, errors.WithMsg(fmt.Sprintf("failed for %s", sessionId)))
}
databaseWrapper, err := r.kms.GetWrapper(ctx, foundSession.ScopeId, kms.KeyPurposeDatabase)
if err != nil {
return fmt.Errorf("unable to get database wrapper: %w", err)
return errors.Wrap(err, op, errors.WithMsg("unable to get database wrapper"))
}
if len(foundSession.TofuToken) > 0 && subtle.ConstantTimeCompare(foundSession.TofuToken, tofuToken) != 1 {
return fmt.Errorf("tofu token mismatch")
return errors.New(errors.TokenMismatch, op, "tofu token mismatch")
}
updatedSession.TofuToken = tofuToken
updatedSession.ServerId = serverId
updatedSession.ServerType = serverType
if err := updatedSession.encrypt(ctx, databaseWrapper); err != nil {
return err
return errors.Wrap(err, op)
}
rowsUpdated, err := w.Update(ctx, &updatedSession, []string{"CtTofuToken"}, nil)
if err != nil {
return err
return errors.Wrap(err, op)
}
if err == nil && rowsUpdated > 1 {
// return err, which will result in a rollback of the update
return stderrors.New("error more than 1 session would have been updated ")
return errors.New(errors.MultipleRecords, op, "more than 1 resource would have been updated")
}
returnedStates, err = fetchStates(ctx, reader, sessionId, db.WithOrder("start_time desc"))
if err != nil {
return err
return errors.Wrap(err, op)
}
return nil
},
)
if err != nil {
return nil, nil, fmt.Errorf("activate session: %w", err)
return nil, nil, errors.Wrap(err, op)
}
return &updatedSession, returnedStates, nil
}
@@ -644,18 +658,19 @@ func (r *Repository) ActivateSession(ctx context.Context, sessionId string, sess
// updateState will update the session's state using the session id and its
// version. updateState is idempotent. States are ordered by start time
// descending. No options are currently supported.
func (r *Repository) updateState(ctx context.Context, sessionId string, sessionVersion uint32, s Status, opt ...Option) (*Session, []*State, error) {
func (r *Repository) updateState(ctx context.Context, sessionId string, sessionVersion uint32, s Status, _ ...Option) (*Session, []*State, error) {
const op = "session.(Repository).updateState"
if sessionId == "" {
return nil, nil, fmt.Errorf("update session state: missing session id %w", errors.ErrInvalidParameter)
return nil, nil, errors.New(errors.InvalidParameter, op, "missing session id")
}
if sessionVersion == 0 {
return nil, nil, fmt.Errorf("update session state: version cannot be zero: %w", errors.ErrInvalidParameter)
return nil, nil, errors.New(errors.InvalidParameter, op, "missing version")
}
if s == "" {
return nil, nil, fmt.Errorf("update session state: missing session status: %w", errors.ErrInvalidParameter)
return nil, nil, errors.New(errors.InvalidParameter, op, "missing session status")
}
if s == StatusActive {
return nil, nil, fmt.Errorf("update session: you must call ActivateSession to update a session's state to active: %w", errors.ErrInvalidParameter)
return nil, nil, errors.New(errors.InvalidParameter, op, "you must call ActivateSession to update a session's state to active")
}
var rowsAffected int
@@ -672,18 +687,18 @@ func (r *Repository) updateState(ctx context.Context, sessionId string, sessionV
updatedSession.Version = uint32(sessionVersion) + 1
rowsUpdated, err := w.Update(ctx, &updatedSession, []string{"Version"}, nil, db.WithVersion(&sessionVersion))
if err != nil {
return err
return errors.Wrap(err, op)
}
if rowsUpdated != 1 {
return fmt.Errorf("updated session and %d rows updated", rowsUpdated)
return errors.New(errors.MultipleRecords, op, fmt.Sprintf("updated session and %d rows updated", rowsUpdated))
}
if len(updatedSession.CtTofuToken) > 0 {
databaseWrapper, err := r.kms.GetWrapper(ctx, updatedSession.ScopeId, kms.KeyPurposeDatabase, kms.WithKeyId(updatedSession.KeyId))
if err != nil {
return fmt.Errorf("lookup session: unable to get database wrapper: %w", err)
return errors.Wrap(err, op, errors.WithMsg("unable to get database wrapper"))
}
if err := updatedSession.decrypt(ctx, databaseWrapper); err != nil {
return fmt.Errorf("lookup session: cannot decrypt session value: %w", err)
return errors.Wrap(err, op, errors.WithMsg("cannot decrypt session value"))
}
} else {
updatedSession.CtTofuToken = nil
@@ -691,31 +706,32 @@ func (r *Repository) updateState(ctx context.Context, sessionId string, sessionV
rowsAffected, err = w.Exec(ctx, updateSessionState, []interface{}{sessionId, s.String()})
if err != nil {
return fmt.Errorf("unable to update session %s state to %s: %w", sessionId, s.String(), err)
return errors.Wrap(err, op, errors.WithMsg(fmt.Sprintf("unable to update session %s state to %s", sessionId, s.String())))
}
if rowsAffected != 0 && rowsAffected != 1 {
return fmt.Errorf("updated session %s to state %s and %d rows inserted (should be 0 or 1)", sessionId, s.String(), rowsAffected)
return errors.New(errors.MultipleRecords, op, fmt.Sprintf("updated session %s to state %s and %d rows inserted (should be 0 or 1)", sessionId, s.String(), rowsAffected))
}
returnedStates, err = fetchStates(ctx, reader, sessionId, db.WithOrder("start_time desc"))
if err != nil {
return err
return errors.Wrap(err, op)
}
if len(returnedStates) < 1 && returnedStates[0].Status != s {
return fmt.Errorf("failed to update %s to a state of %s", sessionId, s.String())
return errors.New(errors.InvalidSessionState, op, fmt.Sprintf("failed to update %s to a state of %s", sessionId, s.String()))
}
return nil
},
)
if err != nil {
return nil, nil, fmt.Errorf("update session state: error creating new state: %w", err)
return nil, nil, errors.Wrap(err, op, errors.WithMsg("error creating new state"))
}
return &updatedSession, returnedStates, nil
}
func fetchStates(ctx context.Context, r db.Reader, sessionId string, opt ...db.Option) ([]*State, error) {
const op = "session.fetchStates"
var states []*State
if err := r.SearchWhere(ctx, &states, "session_id = ?", []interface{}{sessionId}, opt...); err != nil {
return nil, fmt.Errorf("fetch session states: %w", err)
return nil, errors.Wrap(err, op)
}
if len(states) == 0 {
return nil, nil
+37 -51
View File
@@ -234,7 +234,7 @@ func TestRepository_CreateSession(t *testing.T) {
name string
args args
wantErr bool
wantIsError error
wantIsError errors.Code
}{
{
name: "valid",
@@ -253,7 +253,7 @@ func TestRepository_CreateSession(t *testing.T) {
}(),
},
wantErr: true,
wantIsError: errors.ErrInvalidParameter,
wantIsError: errors.InvalidParameter,
},
{
name: "empty-hostId",
@@ -265,7 +265,7 @@ func TestRepository_CreateSession(t *testing.T) {
}(),
},
wantErr: true,
wantIsError: errors.ErrInvalidParameter,
wantIsError: errors.InvalidParameter,
},
{
name: "empty-targetId",
@@ -277,7 +277,7 @@ func TestRepository_CreateSession(t *testing.T) {
}(),
},
wantErr: true,
wantIsError: errors.ErrInvalidParameter,
wantIsError: errors.InvalidParameter,
},
{
name: "empty-hostSetId",
@@ -289,7 +289,7 @@ func TestRepository_CreateSession(t *testing.T) {
}(),
},
wantErr: true,
wantIsError: errors.ErrInvalidParameter,
wantIsError: errors.InvalidParameter,
},
{
name: "empty-authTokenId",
@@ -301,7 +301,7 @@ func TestRepository_CreateSession(t *testing.T) {
}(),
},
wantErr: true,
wantIsError: errors.ErrInvalidParameter,
wantIsError: errors.InvalidParameter,
},
{
name: "empty-scopeId",
@@ -313,7 +313,7 @@ func TestRepository_CreateSession(t *testing.T) {
}(),
},
wantErr: true,
wantIsError: errors.ErrInvalidParameter,
wantIsError: errors.InvalidParameter,
},
}
for _, tt := range tests {
@@ -334,9 +334,7 @@ func TestRepository_CreateSession(t *testing.T) {
if tt.wantErr {
assert.Error(err)
assert.Nil(ses)
if tt.wantIsError != nil {
assert.True(errors.Is(err, tt.wantIsError))
}
assert.True(errors.Match(errors.T(tt.wantIsError), err))
return
}
require.NoError(err)
@@ -382,7 +380,7 @@ func TestRepository_updateState(t *testing.T) {
overrideSessionVersion *uint32
wantStateCnt int
wantErr bool
wantIsError error
wantIsError errors.Code
}{
{
name: "canceling",
@@ -421,7 +419,7 @@ func TestRepository_updateState(t *testing.T) {
return &v
}(),
wantErr: true,
wantIsError: errors.ErrInvalidParameter,
wantIsError: errors.InvalidParameter,
},
{
name: "bad-sessionId",
@@ -442,7 +440,7 @@ func TestRepository_updateState(t *testing.T) {
return &s
}(),
wantErr: true,
wantIsError: errors.ErrInvalidParameter,
wantIsError: errors.InvalidParameter,
},
}
for _, tt := range tests {
@@ -466,9 +464,7 @@ func TestRepository_updateState(t *testing.T) {
s, ss, err := repo.updateState(context.Background(), id, version, tt.newStatus)
if tt.wantErr {
require.Error(err)
if tt.wantIsError != nil {
assert.Truef(errors.Is(err, tt.wantIsError), "unexpected error %s", err.Error())
}
assert.Truef(errors.Match(errors.T(tt.wantIsError), err), "unexpected error %s", err.Error())
return
}
require.NoError(err)
@@ -601,7 +597,7 @@ func TestRepository_ConnectConnection(t *testing.T) {
name string
connectWith ConnectWith
wantErr bool
wantIsError error
wantIsError errors.Code
}{
{
name: "valid",
@@ -615,7 +611,7 @@ func TestRepository_ConnectConnection(t *testing.T) {
return cw
}(),
wantErr: true,
wantIsError: errors.ErrInvalidParameter,
wantIsError: errors.InvalidParameter,
},
{
name: "empty-ClientTcpAddress",
@@ -625,7 +621,7 @@ func TestRepository_ConnectConnection(t *testing.T) {
return cw
}(),
wantErr: true,
wantIsError: errors.ErrInvalidParameter,
wantIsError: errors.InvalidParameter,
},
{
name: "empty-ClientTcpPort",
@@ -635,7 +631,7 @@ func TestRepository_ConnectConnection(t *testing.T) {
return cw
}(),
wantErr: true,
wantIsError: errors.ErrInvalidParameter,
wantIsError: errors.InvalidParameter,
},
{
name: "empty-EndpointTcpAddress",
@@ -645,7 +641,7 @@ func TestRepository_ConnectConnection(t *testing.T) {
return cw
}(),
wantErr: true,
wantIsError: errors.ErrInvalidParameter,
wantIsError: errors.InvalidParameter,
},
{
name: "empty-EndpointTcpPort",
@@ -655,7 +651,7 @@ func TestRepository_ConnectConnection(t *testing.T) {
return cw
}(),
wantErr: true,
wantIsError: errors.ErrInvalidParameter,
wantIsError: errors.InvalidParameter,
},
}
for _, tt := range tests {
@@ -665,9 +661,7 @@ func TestRepository_ConnectConnection(t *testing.T) {
c, cs, err := repo.ConnectConnection(context.Background(), tt.connectWith)
if tt.wantErr {
require.Error(err)
if tt.wantIsError != nil {
assert.Truef(errors.Is(err, tt.wantIsError), "unexpected error %s", err.Error())
}
assert.Truef(errors.Match(errors.T(tt.wantIsError), err), "unexpected error %s", err.Error())
return
}
require.NoError(err)
@@ -701,7 +695,7 @@ func TestRepository_TerminateSession(t *testing.T) {
session *Session
reason TerminationReason
wantErr bool
wantIsError error
wantIsError errors.Code
}{
{
name: "valid-active-session",
@@ -722,7 +716,7 @@ func TestRepository_TerminateSession(t *testing.T) {
}(),
reason: ClosedByUser,
wantErr: true,
wantIsError: errors.ErrInvalidParameter,
wantIsError: errors.InvalidParameter,
},
{
name: "empty-session-version",
@@ -733,7 +727,7 @@ func TestRepository_TerminateSession(t *testing.T) {
}(),
reason: ClosedByUser,
wantErr: true,
wantIsError: errors.ErrInvalidParameter,
wantIsError: errors.InvalidParameter,
},
{
name: "open-connection",
@@ -752,9 +746,7 @@ func TestRepository_TerminateSession(t *testing.T) {
s, err := repo.TerminateSession(context.Background(), tt.session.PublicId, tt.session.Version, tt.reason)
if tt.wantErr {
require.Error(err)
if tt.wantIsError != nil {
assert.Truef(errors.Is(err, tt.wantIsError), "unexpected error %s", err.Error())
}
assert.Truef(errors.Match(errors.T(tt.wantIsError), err), "unexpected error %s", err.Error())
return
}
require.NoError(err)
@@ -1037,7 +1029,7 @@ func TestRepository_CloseConnections(t *testing.T) {
closeWith []CloseWith
reason TerminationReason
wantErr bool
wantIsError error
wantIsError errors.Code
}{
{
name: "valid",
@@ -1049,7 +1041,7 @@ func TestRepository_CloseConnections(t *testing.T) {
closeWith: []CloseWith{},
reason: ClosedByUser,
wantErr: true,
wantIsError: errors.ErrInvalidParameter,
wantIsError: errors.InvalidParameter,
},
{
name: "missing-ConnectionId",
@@ -1060,7 +1052,7 @@ func TestRepository_CloseConnections(t *testing.T) {
}(),
reason: ClosedByUser,
wantErr: true,
wantIsError: errors.ErrInvalidParameter,
wantIsError: errors.InvalidParameter,
},
}
for _, tt := range tests {
@@ -1069,9 +1061,7 @@ func TestRepository_CloseConnections(t *testing.T) {
resp, err := repo.CloseConnections(context.Background(), tt.closeWith)
if tt.wantErr {
require.Error(err)
if tt.wantIsError != nil {
assert.Truef(errors.Is(err, tt.wantIsError), "unexpected error %s", err.Error())
}
assert.Truef(errors.Match(errors.T(tt.wantIsError), err), "unexpected error %s", err.Error())
return
}
require.NoError(err)
@@ -1105,7 +1095,7 @@ func TestRepository_CancelSession(t *testing.T) {
overrideSessionId *string
overrideSessionVersion *uint32
wantErr bool
wantIsError error
wantIsError errors.Code
wantStatus Status
}{
{
@@ -1153,7 +1143,7 @@ func TestRepository_CancelSession(t *testing.T) {
}(),
wantStatus: StatusCanceling,
wantErr: true,
wantIsError: errors.ErrInvalidParameter,
wantIsError: errors.InvalidParameter,
},
{
name: "bad-version-id",
@@ -1174,7 +1164,7 @@ func TestRepository_CancelSession(t *testing.T) {
}(),
wantStatus: StatusCanceling,
wantErr: true,
wantIsError: errors.ErrInvalidParameter,
wantIsError: errors.InvalidParameter,
},
}
for _, tt := range tests {
@@ -1197,9 +1187,7 @@ func TestRepository_CancelSession(t *testing.T) {
s, err := repo.CancelSession(context.Background(), id, version)
if tt.wantErr {
require.Error(err)
if tt.wantIsError != nil {
assert.Truef(errors.Is(err, tt.wantIsError), "unexpected error %s", err.Error())
}
assert.Truef(errors.Match(errors.T(tt.wantIsError), err), "unexpected error %s", err.Error())
return
}
require.NoError(err)
@@ -1418,7 +1406,7 @@ func TestRepository_ActivateSession(t *testing.T) {
overrideSessionId *string
overrideSessionVersion *uint32
wantErr bool
wantIsError error
wantIsError errors.Code
}{
{
name: "valid",
@@ -1462,7 +1450,7 @@ func TestRepository_ActivateSession(t *testing.T) {
return &id
}(),
wantErr: true,
wantIsError: errors.ErrInvalidParameter,
wantIsError: errors.InvalidParameter,
},
{
name: "empty-session-version",
@@ -1472,7 +1460,7 @@ func TestRepository_ActivateSession(t *testing.T) {
return &v
}(),
wantErr: true,
wantIsError: errors.ErrInvalidParameter,
wantIsError: errors.InvalidParameter,
},
}
for _, tt := range tests {
@@ -1495,9 +1483,7 @@ func TestRepository_ActivateSession(t *testing.T) {
s, ss, err := repo.ActivateSession(context.Background(), id, version, worker.PrivateId, worker.Type, tofu)
if tt.wantErr {
require.Error(err)
if tt.wantIsError != nil {
assert.Truef(errors.Is(err, tt.wantIsError), "unexpected error %s", err.Error())
}
assert.Truef(errors.Match(errors.T(tt.wantIsError), err), "unexpected error %s", err.Error())
return
}
require.NoError(err)
@@ -1565,7 +1551,7 @@ func TestRepository_DeleteSession(t *testing.T) {
},
wantRowsDeleted: 0,
wantErr: true,
wantErrMsg: "delete session: missing public id invalid parameter",
wantErrMsg: "session.(Repository).DeleteSession: missing public id: parameter violation: error #100",
},
{
name: "not-found",
@@ -1580,7 +1566,7 @@ func TestRepository_DeleteSession(t *testing.T) {
},
wantRowsDeleted: 0,
wantErr: true,
wantErrMsg: "delete session: failed db.LookupById: record not found",
wantErrMsg: "db.LookupById: record not found",
},
}
for _, tt := range tests {
+49 -44
View File
@@ -5,7 +5,6 @@ import (
"crypto/ed25519"
"crypto/rand"
"crypto/x509"
"fmt"
"math/big"
mathrand "math/rand"
"strings"
@@ -119,7 +118,8 @@ var (
)
// New creates a new in memory session.
func New(c ComposedOf, opt ...Option) (*Session, error) {
func New(c ComposedOf, _ ...Option) (*Session, error) {
const op = "session.New"
s := Session{
UserId: c.UserId,
HostId: c.HostId,
@@ -132,8 +132,8 @@ func New(c ComposedOf, opt ...Option) (*Session, error) {
ConnectionLimit: c.ConnectionLimit,
WorkerFilter: c.WorkerFilter,
}
if err := s.validateNewSession("new session:"); err != nil {
return nil, err
if err := s.validateNewSession(); err != nil {
return nil, errors.Wrap(err, op)
}
return &s, nil
}
@@ -209,50 +209,51 @@ func (s *Session) Clone() interface{} {
// VetForWrite implements db.VetForWrite() interface and validates the session
// before it's written.
func (s *Session) VetForWrite(ctx context.Context, r db.Reader, opType db.OpType, opt ...db.Option) error {
func (s *Session) VetForWrite(_ context.Context, _ db.Reader, opType db.OpType, opt ...db.Option) error {
const op = "session.(Session).VetForWrite"
opts := db.GetOpts(opt...)
if s.PublicId == "" {
return fmt.Errorf("session vet for write: missing public id: %w", errors.ErrInvalidParameter)
return errors.New(errors.InvalidParameter, op, "missing public id")
}
switch opType {
case db.CreateOp:
if err := s.validateNewSession("session vet for write:"); err != nil {
return err
if err := s.validateNewSession(); err != nil {
return errors.Wrap(err, op)
}
if len(s.Certificate) == 0 {
return fmt.Errorf("session vet for write: certificate is missing: %w", errors.ErrInvalidParameter)
return errors.New(errors.InvalidParameter, op, "missing certificate")
}
case db.UpdateOp:
switch {
case contains(opts.WithFieldMaskPaths, "PublicId"):
return fmt.Errorf("session vet for write: public id is immutable: %w", errors.ErrInvalidParameter)
return errors.New(errors.InvalidParameter, op, "public id is immutable")
case contains(opts.WithFieldMaskPaths, "UserId"):
return fmt.Errorf("session vet for write: user id is immutable: %w", errors.ErrInvalidParameter)
return errors.New(errors.InvalidParameter, op, "user id is immutable")
case contains(opts.WithFieldMaskPaths, "HostId"):
return fmt.Errorf("session vet for write: host id is immutable: %w", errors.ErrInvalidParameter)
return errors.New(errors.InvalidParameter, op, "host id is immutable")
case contains(opts.WithFieldMaskPaths, "TargetId"):
return fmt.Errorf("session vet for write: target id is immutable: %w", errors.ErrInvalidParameter)
return errors.New(errors.InvalidParameter, op, "target id is immutable")
case contains(opts.WithFieldMaskPaths, "HostSetId"):
return fmt.Errorf("session vet for write: host set id is immutable: %w", errors.ErrInvalidParameter)
return errors.New(errors.InvalidParameter, op, "host set id is immutable")
case contains(opts.WithFieldMaskPaths, "AuthTokenId"):
return fmt.Errorf("session vet for write: auth token id is immutable: %w", errors.ErrInvalidParameter)
return errors.New(errors.InvalidParameter, op, "auth token id is immutable")
case contains(opts.WithFieldMaskPaths, "Certificate"):
return fmt.Errorf("session vet for write: certificate is immutable: %w", errors.ErrInvalidParameter)
return errors.New(errors.InvalidParameter, op, "certificate is immutable")
case contains(opts.WithFieldMaskPaths, "CreateTime"):
return fmt.Errorf("session vet for write: create time is immutable: %w", errors.ErrInvalidParameter)
return errors.New(errors.InvalidParameter, op, "create time is immutable")
case contains(opts.WithFieldMaskPaths, "UpdateTime"):
return fmt.Errorf("session vet for write: update time is immutable: %w", errors.ErrInvalidParameter)
return errors.New(errors.InvalidParameter, op, "update time is immutable")
case contains(opts.WithFieldMaskPaths, "Endpoint"):
return fmt.Errorf("session vet for write: endpoint is immutable: %w", errors.ErrInvalidParameter)
return errors.New(errors.InvalidParameter, op, "endpoint is immutable")
case contains(opts.WithFieldMaskPaths, "ExpirationTime"):
return fmt.Errorf("session vet for write: expiration time is immutable: %w", errors.ErrInvalidParameter)
return errors.New(errors.InvalidParameter, op, "expiration time is immutable")
case contains(opts.WithFieldMaskPaths, "ConnectionLimit"):
return fmt.Errorf("session vet for write: connection limit is immutable: %w", errors.ErrInvalidParameter)
return errors.New(errors.InvalidParameter, op, "connection limit is immutable")
case contains(opts.WithFieldMaskPaths, "WorkerFilter"):
return fmt.Errorf("session vet for write: worker filter is immutable: %w", errors.ErrInvalidParameter)
return errors.New(errors.InvalidParameter, op, "worker filter is immutable")
case contains(opts.WithFieldMaskPaths, "TerminationReason"):
if _, err := convertToReason(s.TerminationReason); err != nil {
return fmt.Errorf("session vet for write: termination reason '%s' is invalid: %w", s.TerminationReason, errors.ErrInvalidParameter)
return errors.Wrap(err, op)
}
}
}
@@ -275,45 +276,46 @@ func (s *Session) SetTableName(n string) {
}
// validateNewSession checks everything but the session's PublicId
func (s *Session) validateNewSession(errorPrefix string) error {
func (s *Session) validateNewSession() error {
const op = "session.(Session).validateNewSession"
if s.UserId == "" {
return fmt.Errorf("%s missing user id: %w", errorPrefix, errors.ErrInvalidParameter)
return errors.New(errors.InvalidParameter, op, "missing user id")
}
if s.HostId == "" {
return fmt.Errorf("%s missing host id: %w", errorPrefix, errors.ErrInvalidParameter)
return errors.New(errors.InvalidParameter, op, "missing host id")
}
if s.TargetId == "" {
return fmt.Errorf("%s missing target id: %w", errorPrefix, errors.ErrInvalidParameter)
return errors.New(errors.InvalidParameter, op, "missing target id")
}
if s.HostSetId == "" {
return fmt.Errorf("%s missing host set id: %w", errorPrefix, errors.ErrInvalidParameter)
return errors.New(errors.InvalidParameter, op, "missing host set id")
}
if s.AuthTokenId == "" {
return fmt.Errorf("%s missing auth token id: %w", errorPrefix, errors.ErrInvalidParameter)
return errors.New(errors.InvalidParameter, op, "missing auth token id")
}
if s.ScopeId == "" {
return fmt.Errorf("%s missing scope id: %w", errorPrefix, errors.ErrInvalidParameter)
return errors.New(errors.InvalidParameter, op, "missing scope id")
}
if s.Endpoint == "" {
return fmt.Errorf("%s missing endpoint: %w", errorPrefix, errors.ErrInvalidParameter)
return errors.New(errors.InvalidParameter, op, "missing endpoint")
}
if s.ExpirationTime.GetTimestamp().AsTime().IsZero() {
return fmt.Errorf("%s missing expiration time: %w", errorPrefix, errors.ErrInvalidParameter)
return errors.New(errors.InvalidParameter, op, "missing expiration time")
}
if s.TerminationReason != "" {
return fmt.Errorf("%s termination reason must be empty: %w", errorPrefix, errors.ErrInvalidParameter)
return errors.New(errors.InvalidParameter, op, "termination reason must be empty")
}
if s.ServerId != "" {
return fmt.Errorf("%s server id must be empty: %w", errorPrefix, errors.ErrInvalidParameter)
return errors.New(errors.InvalidParameter, op, "server id must be empty")
}
if s.ServerType != "" {
return fmt.Errorf("%s server type must be empty: %w", errorPrefix, errors.ErrInvalidParameter)
return errors.New(errors.InvalidParameter, op, "server type must be empty")
}
if s.TofuToken != nil {
return fmt.Errorf("%s tofu token must be empty: %w", errorPrefix, errors.ErrInvalidParameter)
return errors.New(errors.InvalidParameter, op, "tofu token must be empty")
}
if s.CtTofuToken != nil {
return fmt.Errorf("%s ct must be empty: %w", errorPrefix, errors.ErrInvalidParameter)
return errors.New(errors.InvalidParameter, op, "ct must be empty")
}
// It is okay for the worker filter to be empty, so it is not checked here.
return nil
@@ -329,18 +331,19 @@ func contains(ss []string, t string) bool {
}
func newCert(wrapper wrapping.Wrapper, userId, jobId string, exp time.Time) (ed25519.PrivateKey, []byte, error) {
const op = "session.newCert"
if wrapper == nil {
return nil, nil, fmt.Errorf("new session cert: missing wrapper: %w", errors.ErrInvalidParameter)
return nil, nil, errors.New(errors.InvalidParameter, op, "missing wrapper")
}
if userId == "" {
return nil, nil, fmt.Errorf("new session cert: missing user id: %w", errors.ErrInvalidParameter)
return nil, nil, errors.New(errors.InvalidParameter, op, "missing user id")
}
if jobId == "" {
return nil, nil, fmt.Errorf("new session cert: missing job id: %w", errors.ErrInvalidParameter)
return nil, nil, errors.New(errors.InvalidParameter, op, "missing job id")
}
pubKey, privKey, err := DeriveED25519Key(wrapper, userId, jobId)
if err != nil {
return nil, nil, fmt.Errorf("new session cert: ")
return nil, nil, errors.Wrap(err, op)
}
template := &x509.Certificate{
ExtKeyUsage: []x509.ExtKeyUsage{
@@ -358,22 +361,24 @@ func newCert(wrapper wrapping.Wrapper, userId, jobId string, exp time.Time) (ed2
certBytes, err := x509.CreateCertificate(rand.Reader, template, template, pubKey, privKey)
if err != nil {
return nil, nil, fmt.Errorf("new session cert: %w", err)
return nil, nil, errors.Wrap(err, op, errors.WithCode(errors.GenCert))
}
return privKey, certBytes, nil
}
func (s *Session) encrypt(ctx context.Context, cipher wrapping.Wrapper) error {
const op = "session.(Session).encrypt"
if err := structwrapping.WrapStruct(ctx, cipher, s, nil); err != nil {
return fmt.Errorf("error encrypting session: %w", err)
return errors.Wrap(err, op, errors.WithCode(errors.Encrypt))
}
s.KeyId = cipher.KeyID()
return nil
}
func (s *Session) decrypt(ctx context.Context, cipher wrapping.Wrapper) error {
const op = "session.(Session).decrypt"
if err := structwrapping.UnwrapStruct(ctx, cipher, s, nil); err != nil {
return fmt.Errorf("error decrypting session: %w", err)
return errors.Wrap(err, op, errors.WithCode(errors.Decrypt))
}
return nil
}
+6 -7
View File
@@ -1,8 +1,6 @@
package session
import (
"fmt"
"github.com/hashicorp/boundary/internal/errors"
)
@@ -17,20 +15,21 @@ type ConnectWith struct {
}
func (c ConnectWith) validate() error {
const op = "session.(ConnectWith).validate"
if c.ConnectionId == "" {
return fmt.Errorf("missing session id: %w", errors.ErrInvalidParameter)
return errors.New(errors.InvalidParameter, op, "missing session id")
}
if c.ClientTcpAddress == "" {
return fmt.Errorf("missing client tcp address: %w", errors.ErrInvalidParameter)
return errors.New(errors.InvalidParameter, op, "missing client tcp address")
}
if c.ClientTcpPort == 0 {
return fmt.Errorf("missing client ctp port: %w", errors.ErrInvalidParameter)
return errors.New(errors.InvalidParameter, op, "missing client ctp port")
}
if c.EndpointTcpAddress == "" {
return fmt.Errorf("missing endpoint tcp address: %w", errors.ErrInvalidParameter)
return errors.New(errors.InvalidParameter, op, "missing endpoint tcp address")
}
if c.EndpointTcpPort == 0 {
return fmt.Errorf("missing endpoint ctp port: %w", errors.ErrInvalidParameter)
return errors.New(errors.InvalidParameter, op, "missing endpoint ctp port")
}
return nil
}
+8 -8
View File
@@ -34,7 +34,7 @@ func TestSession_Create(t *testing.T) {
args args
want *Session
wantErr bool
wantIsErr error
wantIsErr errors.Code
create bool
wantCreateErr bool
}{
@@ -67,7 +67,7 @@ func TestSession_Create(t *testing.T) {
}(),
},
wantErr: true,
wantIsErr: errors.ErrInvalidParameter,
wantIsErr: errors.InvalidParameter,
},
{
name: "empty-hostId",
@@ -79,7 +79,7 @@ func TestSession_Create(t *testing.T) {
}(),
},
wantErr: true,
wantIsErr: errors.ErrInvalidParameter,
wantIsErr: errors.InvalidParameter,
},
{
name: "empty-targetId",
@@ -91,7 +91,7 @@ func TestSession_Create(t *testing.T) {
}(),
},
wantErr: true,
wantIsErr: errors.ErrInvalidParameter,
wantIsErr: errors.InvalidParameter,
},
{
name: "empty-hostSetId",
@@ -103,7 +103,7 @@ func TestSession_Create(t *testing.T) {
}(),
},
wantErr: true,
wantIsErr: errors.ErrInvalidParameter,
wantIsErr: errors.InvalidParameter,
},
{
name: "empty-authTokenId",
@@ -115,7 +115,7 @@ func TestSession_Create(t *testing.T) {
}(),
},
wantErr: true,
wantIsErr: errors.ErrInvalidParameter,
wantIsErr: errors.InvalidParameter,
},
{
name: "empty-scopeId",
@@ -127,7 +127,7 @@ func TestSession_Create(t *testing.T) {
}(),
},
wantErr: true,
wantIsErr: errors.ErrInvalidParameter,
wantIsErr: errors.InvalidParameter,
},
}
for _, tt := range tests {
@@ -136,7 +136,7 @@ func TestSession_Create(t *testing.T) {
got, err := New(tt.args.composedOf)
if tt.wantErr {
require.Error(err)
assert.True(errors.Is(err, tt.wantIsErr))
assert.True(errors.Match(errors.T(tt.wantIsErr), err))
return
}
require.NoError(err)
+15 -13
View File
@@ -2,7 +2,6 @@ package session
import (
"context"
"fmt"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/db/timestamp"
@@ -69,14 +68,15 @@ var (
// NewState creates a new in memory session state. No options
// are currently supported.
func NewState(session_id string, state Status, opt ...Option) (*State, error) {
func NewState(session_id string, state Status, _ ...Option) (*State, error) {
const op = "session.NewState"
s := State{
SessionId: session_id,
Status: state,
}
if err := s.validate("new session state:"); err != nil {
return nil, err
if err := s.validate(); err != nil {
return nil, errors.Wrap(err, op)
}
return &s, nil
}
@@ -122,9 +122,10 @@ func (s *State) Clone() interface{} {
// VetForWrite implements db.VetForWrite() interface and validates the state
// before it's written.
func (s *State) VetForWrite(ctx context.Context, r db.Reader, opType db.OpType, opt ...db.Option) error {
if err := s.validate("session state vet for write:"); err != nil {
return err
func (s *State) VetForWrite(_ context.Context, _ db.Reader, _ db.OpType, _ ...db.Option) error {
const op = "session.(State).VetForWrite"
if err := s.validate(); err != nil {
return errors.Wrap(err, op)
}
return nil
}
@@ -145,21 +146,22 @@ func (s *State) SetTableName(n string) {
}
// validate checks the session state
func (s *State) validate(errorPrefix string) error {
func (s *State) validate() error {
const op = "session.(State).validate"
if s.Status == "" {
return fmt.Errorf("%s missing status: %w", errorPrefix, errors.ErrInvalidParameter)
return errors.New(errors.InvalidParameter, op, "missing status")
}
if s.SessionId == "" {
return fmt.Errorf("%s missing session id: %w", errorPrefix, errors.ErrInvalidParameter)
return errors.New(errors.InvalidParameter, op, "missing session id")
}
if s.StartTime != nil {
return fmt.Errorf("%s start time is not settable: %w", errorPrefix, errors.ErrInvalidParameter)
return errors.New(errors.InvalidParameter, op, "start time is not settable")
}
if s.EndTime != nil {
return fmt.Errorf("%s end time is not settable: %w", errorPrefix, errors.ErrInvalidParameter)
return errors.New(errors.InvalidParameter, op, "end time is not settable")
}
if s.PreviousEndTime != nil {
return fmt.Errorf("%s previous end time is not settable: %w", errorPrefix, errors.ErrInvalidParameter)
return errors.New(errors.InvalidParameter, op, "previous end time is not settable")
}
return nil
}
+4 -4
View File
@@ -27,7 +27,7 @@ func TestState_Create(t *testing.T) {
args args
want *State
wantErr bool
wantIsErr error
wantIsErr errors.Code
create bool
wantCreateErr bool
}{
@@ -49,7 +49,7 @@ func TestState_Create(t *testing.T) {
status: StatusPending,
},
wantErr: true,
wantIsErr: errors.ErrInvalidParameter,
wantIsErr: errors.InvalidParameter,
},
{
name: "empty-status",
@@ -57,7 +57,7 @@ func TestState_Create(t *testing.T) {
sessionId: session.PublicId,
},
wantErr: true,
wantIsErr: errors.ErrInvalidParameter,
wantIsErr: errors.InvalidParameter,
},
}
for _, tt := range tests {
@@ -66,7 +66,7 @@ func TestState_Create(t *testing.T) {
got, err := NewState(tt.args.sessionId, tt.args.status)
if tt.wantErr {
require.Error(err)
assert.True(errors.Is(err, tt.wantIsErr))
assert.True(errors.Match(errors.T(tt.wantIsErr), err))
return
}
require.NoError(err)
+2 -1
View File
@@ -26,6 +26,7 @@ func (r TerminationReason) String() string {
}
func convertToReason(s string) (TerminationReason, error) {
const op = "session.convertToReason"
switch s {
case UnknownTermination.String():
return UnknownTermination, nil
@@ -42,6 +43,6 @@ func convertToReason(s string) (TerminationReason, error) {
case ConnectionLimit.String():
return ConnectionLimit, nil
default:
return "", fmt.Errorf("termination reason: %s is not a valid reason: %w", s, errors.ErrInvalidParameter)
return "", errors.New(errors.InvalidParameter, op, fmt.Sprintf("%s is not a valid reason", s))
}
}
+10 -4
View File
@@ -3,9 +3,10 @@ package session
import (
"crypto/ed25519"
"crypto/sha256"
"errors"
"fmt"
"io"
"github.com/hashicorp/boundary/internal/errors"
wrapping "github.com/hashicorp/go-kms-wrapping"
"github.com/hashicorp/go-kms-wrapping/wrappers/aead"
"github.com/hashicorp/go-kms-wrapping/wrappers/multiwrapper"
@@ -15,23 +16,28 @@ import (
// DeriveED25519Key generates a key based on the scope's session DEK, the
// requesting user, and the generated job ID.
func DeriveED25519Key(wrapper wrapping.Wrapper, userId, jobId string) (ed25519.PublicKey, ed25519.PrivateKey, error) {
const op = "session.DeriveED25519Key"
var aeadWrapper *aead.Wrapper
switch w := wrapper.(type) {
case *multiwrapper.MultiWrapper:
raw := w.WrapperForKeyID("__base__")
var ok bool
if aeadWrapper, ok = raw.(*aead.Wrapper); !ok {
return nil, nil, errors.New("unexpected wrapper type from multiwrapper base")
return nil, nil, errors.New(errors.InvalidParameter, op, "unexpected wrapper type from multiwrapper base")
}
case *aead.Wrapper:
aeadWrapper = w
default:
return nil, nil, errors.New("unknown wrapper type")
return nil, nil, errors.New(errors.InvalidParameter, op, fmt.Sprintf("unknown wrapper type %T", wrapper))
}
reader := hkdf.New(sha256.New, aeadWrapper.GetKeyBytes(), []byte(jobId), []byte(userId))
limitedReader := &io.LimitedReader{
R: reader,
N: 32,
}
return ed25519.GenerateKey(limitedReader)
pub, pvt, err := ed25519.GenerateKey(limitedReader)
if err != nil {
return nil, nil, errors.Wrap(err, op, errors.WithCode(errors.GenKey))
}
return pub, pvt, nil
}