1415 lines
49 KiB
Go
1415 lines
49 KiB
Go
// Copyright (c) HashiCorp, Inc.
|
|
// SPDX-License-Identifier: BUSL-1.1
|
|
|
|
package handlers
|
|
|
|
import (
|
|
"context"
|
|
"crypto/rand"
|
|
"errors"
|
|
"fmt"
|
|
"sync"
|
|
"sync/atomic"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/google/go-cmp/cmp"
|
|
"github.com/hashicorp/boundary/internal/authtoken"
|
|
credstatic "github.com/hashicorp/boundary/internal/credential/static"
|
|
"github.com/hashicorp/boundary/internal/db"
|
|
pb "github.com/hashicorp/boundary/internal/gen/controller/servers"
|
|
pbs "github.com/hashicorp/boundary/internal/gen/controller/servers/services"
|
|
intglobals "github.com/hashicorp/boundary/internal/globals"
|
|
"github.com/hashicorp/boundary/internal/host/static"
|
|
"github.com/hashicorp/boundary/internal/iam"
|
|
"github.com/hashicorp/boundary/internal/kms"
|
|
"github.com/hashicorp/boundary/internal/server"
|
|
"github.com/hashicorp/boundary/internal/session"
|
|
"github.com/hashicorp/boundary/internal/target"
|
|
"github.com/hashicorp/boundary/internal/target/tcp"
|
|
"github.com/hashicorp/boundary/internal/types/scope"
|
|
"github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/targets"
|
|
"github.com/hashicorp/go-secure-stdlib/base62"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
"golang.org/x/crypto/ssh/testdata"
|
|
"google.golang.org/grpc/codes"
|
|
"google.golang.org/grpc/status"
|
|
"google.golang.org/protobuf/proto"
|
|
"google.golang.org/protobuf/testing/protocmp"
|
|
)
|
|
|
|
type fakeControllerExtension struct {
|
|
reader db.Reader
|
|
writer db.Writer
|
|
}
|
|
|
|
var _ intglobals.ControllerExtension = (*fakeControllerExtension)(nil)
|
|
|
|
func (f *fakeControllerExtension) Start(_ context.Context) error { return nil }
|
|
|
|
func TestLookupSession(t *testing.T) {
|
|
ctx := context.Background()
|
|
conn, _ := db.TestSetup(t, "postgres")
|
|
rw := db.New(conn)
|
|
wrapper := db.TestWrapper(t)
|
|
kms := kms.TestKms(t, conn, wrapper)
|
|
org, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
|
|
|
|
serversRepoFn := func() (*server.Repository, error) {
|
|
return server.NewRepository(ctx, rw, rw, kms)
|
|
}
|
|
workerAuthRepoFn := func() (*server.WorkerAuthRepositoryStorage, error) {
|
|
return server.NewRepositoryStorage(ctx, rw, rw, kms)
|
|
}
|
|
sessionRepoFn := func(opts ...session.Option) (*session.Repository, error) {
|
|
return session.NewRepository(ctx, rw, rw, kms, opts...)
|
|
}
|
|
connectionRepoFn := func() (*session.ConnectionRepository, error) {
|
|
return session.NewConnectionRepository(ctx, rw, rw, kms)
|
|
}
|
|
fce := &fakeControllerExtension{
|
|
reader: rw,
|
|
writer: rw,
|
|
}
|
|
|
|
at := authtoken.TestAuthToken(t, conn, kms, org.GetPublicId())
|
|
uId := at.GetIamUserId()
|
|
hc := static.TestCatalogs(t, conn, prj.GetPublicId(), 1)[0]
|
|
hs := static.TestSets(t, conn, hc.GetPublicId(), 1)[0]
|
|
h := static.TestHosts(t, conn, hc.GetPublicId(), 1)[0]
|
|
static.TestSetMembers(t, conn, hs.GetPublicId(), []*static.Host{h})
|
|
tar := tcp.TestTarget(ctx, t, conn, prj.GetPublicId(), "test", target.WithHostSources([]string{hs.GetPublicId()}))
|
|
|
|
sess := session.TestSession(t, conn, wrapper, session.ComposedOf{
|
|
UserId: uId,
|
|
HostId: h.GetPublicId(),
|
|
TargetId: tar.GetPublicId(),
|
|
HostSetId: hs.GetPublicId(),
|
|
AuthTokenId: at.GetPublicId(),
|
|
ProjectId: prj.GetPublicId(),
|
|
Endpoint: "tcp://127.0.0.1:22",
|
|
})
|
|
|
|
worker1 := server.TestKmsWorker(t, conn, wrapper, server.WithName("testworker"))
|
|
sessWithWorkerFilter := session.TestSession(t, conn, wrapper, session.ComposedOf{
|
|
UserId: uId,
|
|
HostId: h.GetPublicId(),
|
|
TargetId: tar.GetPublicId(),
|
|
HostSetId: hs.GetPublicId(),
|
|
AuthTokenId: at.GetPublicId(),
|
|
ProjectId: prj.GetPublicId(),
|
|
Endpoint: "tcp://127.0.0.1:22",
|
|
WorkerFilter: fmt.Sprintf("%q matches %q", "/name", worker1.GetName()),
|
|
})
|
|
|
|
sessWithCreds := session.TestSession(t, conn, wrapper, session.ComposedOf{
|
|
UserId: uId,
|
|
HostId: h.GetPublicId(),
|
|
TargetId: tar.GetPublicId(),
|
|
HostSetId: hs.GetPublicId(),
|
|
AuthTokenId: at.GetPublicId(),
|
|
ProjectId: prj.GetPublicId(),
|
|
Endpoint: "tcp://127.0.0.1:22",
|
|
})
|
|
|
|
repo, err := sessionRepoFn()
|
|
require.NoError(t, err)
|
|
|
|
creds := []*pbs.Credential{
|
|
{
|
|
Credential: &pbs.Credential_UsernamePassword{
|
|
UsernamePassword: &pbs.UsernamePassword{
|
|
Username: "username",
|
|
Password: "password",
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Credential: &pbs.Credential_SshPrivateKey{
|
|
SshPrivateKey: &pbs.SshPrivateKey{
|
|
Username: "another-username",
|
|
PrivateKey: credstatic.TestLargeSshPrivateKeyPem,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Credential: &pbs.Credential_SshPrivateKey{
|
|
SshPrivateKey: &pbs.SshPrivateKey{
|
|
Username: "another-username",
|
|
PrivateKey: string(testdata.PEMBytes["ed25519"]),
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Credential: &pbs.Credential_SshPrivateKey{
|
|
SshPrivateKey: &pbs.SshPrivateKey{
|
|
Username: "another-username",
|
|
PrivateKey: string(testdata.PEMEncryptedKeys[0].PEMBytes),
|
|
PrivateKeyPassphrase: testdata.PEMEncryptedKeys[0].EncryptionKey,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
workerCreds := make([]session.Credential, 0, len(creds))
|
|
for _, c := range creds {
|
|
data, err := proto.Marshal(c)
|
|
require.NoError(t, err)
|
|
workerCreds = append(workerCreds, data)
|
|
}
|
|
err = repo.AddSessionCredentials(ctx, sessWithCreds.ProjectId, sessWithCreds.GetPublicId(), workerCreds)
|
|
require.NoError(t, err)
|
|
|
|
s := NewWorkerServiceServer(serversRepoFn, workerAuthRepoFn, sessionRepoFn, connectionRepoFn, nil, new(sync.Map), kms, new(atomic.Int64), fce)
|
|
require.NotNil(t, s)
|
|
|
|
oldFn := connectionRouteFn
|
|
connectionRouteFn = singleHopConnectionRoute
|
|
t.Cleanup(func() {
|
|
connectionRouteFn = oldFn
|
|
})
|
|
|
|
cases := []struct {
|
|
name string
|
|
wantErr bool
|
|
wantErrMsg string
|
|
want *pbs.LookupSessionResponse
|
|
req *pbs.LookupSessionRequest
|
|
}{
|
|
{
|
|
name: "Invalid session id",
|
|
req: &pbs.LookupSessionRequest{
|
|
SessionId: "s_fakesession",
|
|
WorkerId: worker1.GetPublicId(),
|
|
},
|
|
wantErr: true,
|
|
wantErrMsg: "rpc error: code = PermissionDenied desc = Unknown session ID.",
|
|
},
|
|
{
|
|
name: "no worker id",
|
|
req: &pbs.LookupSessionRequest{
|
|
SessionId: sessWithWorkerFilter.PublicId,
|
|
},
|
|
wantErr: true,
|
|
wantErrMsg: "rpc error: code = InvalidArgument desc = Did not receive worker id when looking up session",
|
|
},
|
|
{
|
|
name: "nonexistent worker id",
|
|
req: &pbs.LookupSessionRequest{
|
|
SessionId: sessWithWorkerFilter.PublicId,
|
|
WorkerId: "w_nonexistingworker",
|
|
},
|
|
wantErr: true,
|
|
wantErrMsg: "rpc error: code = Internal desc = Worker not found",
|
|
},
|
|
{
|
|
name: "Valid",
|
|
req: &pbs.LookupSessionRequest{
|
|
SessionId: sess.PublicId,
|
|
WorkerId: worker1.GetPublicId(),
|
|
},
|
|
want: &pbs.LookupSessionResponse{
|
|
Authorization: &targets.SessionAuthorizationData{
|
|
SessionId: sess.PublicId,
|
|
Certificate: sess.Certificate,
|
|
PrivateKey: sess.CertificatePrivateKey,
|
|
},
|
|
ConnectionLimit: 1,
|
|
ConnectionsLeft: 1,
|
|
Version: 1,
|
|
Endpoint: sess.Endpoint,
|
|
HostId: sess.HostId,
|
|
HostSetId: sess.HostSetId,
|
|
TargetId: sess.TargetId,
|
|
UserId: sess.UserId,
|
|
Status: pbs.SESSIONSTATUS_SESSIONSTATUS_PENDING,
|
|
},
|
|
},
|
|
{
|
|
name: "Valid with worker filter",
|
|
req: &pbs.LookupSessionRequest{
|
|
SessionId: sess.PublicId,
|
|
WorkerId: worker1.GetPublicId(),
|
|
},
|
|
want: &pbs.LookupSessionResponse{
|
|
Authorization: &targets.SessionAuthorizationData{
|
|
SessionId: sess.PublicId,
|
|
Certificate: sess.Certificate,
|
|
PrivateKey: sess.CertificatePrivateKey,
|
|
},
|
|
ConnectionLimit: 1,
|
|
ConnectionsLeft: 1,
|
|
Version: 1,
|
|
Endpoint: sess.Endpoint,
|
|
HostId: sess.HostId,
|
|
HostSetId: sess.HostSetId,
|
|
TargetId: sess.TargetId,
|
|
UserId: sess.UserId,
|
|
Status: pbs.SESSIONSTATUS_SESSIONSTATUS_PENDING,
|
|
},
|
|
},
|
|
{
|
|
name: "Valid-with-worker-creds",
|
|
req: &pbs.LookupSessionRequest{
|
|
SessionId: sessWithCreds.PublicId,
|
|
WorkerId: worker1.GetPublicId(),
|
|
},
|
|
want: &pbs.LookupSessionResponse{
|
|
Authorization: &targets.SessionAuthorizationData{
|
|
SessionId: sessWithCreds.PublicId,
|
|
Certificate: sessWithCreds.Certificate,
|
|
PrivateKey: sessWithCreds.CertificatePrivateKey,
|
|
},
|
|
ConnectionLimit: 1,
|
|
ConnectionsLeft: 1,
|
|
Version: 1,
|
|
Endpoint: sessWithCreds.Endpoint,
|
|
HostId: sessWithCreds.HostId,
|
|
HostSetId: sessWithCreds.HostSetId,
|
|
TargetId: sessWithCreds.TargetId,
|
|
UserId: sessWithCreds.UserId,
|
|
Status: pbs.SESSIONSTATUS_SESSIONSTATUS_PENDING,
|
|
Credentials: creds,
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tc := range cases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
assert, require := assert.New(t), require.New(t)
|
|
|
|
got, err := s.LookupSession(ctx, tc.req)
|
|
if tc.wantErr {
|
|
require.Error(err)
|
|
assert.Nil(got)
|
|
assert.Equal(tc.wantErrMsg, err.Error())
|
|
return
|
|
}
|
|
require.NoError(err)
|
|
assert.Empty(
|
|
cmp.Diff(
|
|
tc.want,
|
|
got,
|
|
protocmp.Transform(),
|
|
protocmp.IgnoreFields(&pbs.LookupSessionResponse{}, "expiration"),
|
|
),
|
|
)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestAuthorizeConnection(t *testing.T) {
|
|
ctx := context.Background()
|
|
conn, _ := db.TestSetup(t, "postgres")
|
|
rw := db.New(conn)
|
|
wrapper := db.TestWrapper(t)
|
|
kmsCache := kms.TestKms(t, conn, wrapper)
|
|
require.NoError(t, kmsCache.CreateKeys(context.Background(), scope.Global.String(), kms.WithRandomReader(rand.Reader)))
|
|
|
|
currentConnFn := connectionRouteFn
|
|
t.Cleanup(func() {
|
|
connectionRouteFn = currentConnFn
|
|
})
|
|
connectionRouteFn = singleHopConnectionRoute
|
|
|
|
org, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
|
|
|
|
serversRepoFn := func() (*server.Repository, error) {
|
|
return server.NewRepository(ctx, rw, rw, kmsCache)
|
|
}
|
|
workerAuthRepoFn := func() (*server.WorkerAuthRepositoryStorage, error) {
|
|
return server.NewRepositoryStorage(ctx, rw, rw, kmsCache)
|
|
}
|
|
sessionRepoFn := func(opts ...session.Option) (*session.Repository, error) {
|
|
return session.NewRepository(ctx, rw, rw, kmsCache, opts...)
|
|
}
|
|
connectionRepoFn := func() (*session.ConnectionRepository, error) {
|
|
return session.NewConnectionRepository(ctx, rw, rw, kmsCache)
|
|
}
|
|
fce := &fakeControllerExtension{
|
|
reader: rw,
|
|
writer: rw,
|
|
}
|
|
|
|
var workerKeyId string
|
|
worker := server.TestPkiWorker(t, conn, wrapper, server.WithTestPkiWorkerAuthorizedKeyId(&workerKeyId))
|
|
serverRepo, err := serversRepoFn()
|
|
require.NoError(t, err)
|
|
|
|
at := authtoken.TestAuthToken(t, conn, kmsCache, org.GetPublicId())
|
|
uId := at.GetIamUserId()
|
|
hc := static.TestCatalogs(t, conn, prj.GetPublicId(), 1)[0]
|
|
hs := static.TestSets(t, conn, hc.GetPublicId(), 1)[0]
|
|
h := static.TestHosts(t, conn, hc.GetPublicId(), 1)[0]
|
|
static.TestSetMembers(t, conn, hs.GetPublicId(), []*static.Host{h})
|
|
tar := tcp.TestTarget(ctx, t, conn, prj.GetPublicId(), "test", target.WithHostSources([]string{hs.GetPublicId()}))
|
|
|
|
newTestSession := func(connLimit int32) *session.Session {
|
|
return session.TestSession(t, conn, wrapper, session.ComposedOf{
|
|
UserId: uId,
|
|
HostId: h.GetPublicId(),
|
|
TargetId: tar.GetPublicId(),
|
|
HostSetId: hs.GetPublicId(),
|
|
AuthTokenId: at.GetPublicId(),
|
|
ProjectId: prj.GetPublicId(),
|
|
Endpoint: "tcp://127.0.0.1:22",
|
|
ConnectionLimit: connLimit,
|
|
})
|
|
}
|
|
|
|
repo, err := sessionRepoFn()
|
|
require.NoError(t, err)
|
|
|
|
s := NewWorkerServiceServer(serversRepoFn, workerAuthRepoFn, sessionRepoFn, connectionRepoFn, nil, new(sync.Map), kmsCache, new(atomic.Int64), fce)
|
|
require.NotNil(t, s)
|
|
|
|
cases := []struct {
|
|
name string
|
|
sessionId string
|
|
want *pbs.AuthorizeConnectionResponse
|
|
wantErr bool
|
|
}{
|
|
{
|
|
name: "no-protocol-context",
|
|
sessionId: func() string {
|
|
sess := newTestSession(-1)
|
|
|
|
tofuToken, err := base62.Random(20)
|
|
require.NoError(t, err)
|
|
_, _, err = repo.ActivateSession(ctx, sess.GetPublicId(), sess.Version, []byte(tofuToken))
|
|
require.NoError(t, err)
|
|
|
|
return sess.GetPublicId()
|
|
}(),
|
|
want: &pbs.AuthorizeConnectionResponse{
|
|
Status: pbs.CONNECTIONSTATUS_CONNECTIONSTATUS_AUTHORIZED,
|
|
ConnectionsLeft: -1,
|
|
Route: []string{worker.PublicId},
|
|
},
|
|
},
|
|
{
|
|
name: "with-credentials",
|
|
sessionId: func() string {
|
|
sess := newTestSession(-1)
|
|
|
|
data, err := proto.Marshal(&pbs.Credential{Credential: &pbs.Credential_UsernamePassword{
|
|
UsernamePassword: &pbs.UsernamePassword{
|
|
Username: "username",
|
|
Password: "password",
|
|
},
|
|
}})
|
|
require.NoError(t, err)
|
|
err = repo.AddSessionCredentials(ctx, sess.ProjectId, sess.GetPublicId(), []session.Credential{data})
|
|
require.NoError(t, err)
|
|
|
|
tofuToken, err := base62.Random(20)
|
|
require.NoError(t, err)
|
|
_, _, err = repo.ActivateSession(ctx, sess.GetPublicId(), sess.Version, []byte(tofuToken))
|
|
require.NoError(t, err)
|
|
|
|
return sess.GetPublicId()
|
|
}(),
|
|
want: &pbs.AuthorizeConnectionResponse{
|
|
Status: pbs.CONNECTIONSTATUS_CONNECTIONSTATUS_AUTHORIZED,
|
|
ConnectionsLeft: -1,
|
|
Route: []string{worker.PublicId},
|
|
},
|
|
},
|
|
{
|
|
name: "connection limit reached",
|
|
sessionId: func() string {
|
|
sess := newTestSession(1)
|
|
tofuToken, err := base62.Random(20)
|
|
require.NoError(t, err)
|
|
_, _, err = repo.ActivateSession(ctx, sess.GetPublicId(), sess.Version, []byte(tofuToken))
|
|
require.NoError(t, err)
|
|
|
|
// Take up the only connection limit.
|
|
_, err = s.AuthorizeConnection(ctx,
|
|
&pbs.AuthorizeConnectionRequest{
|
|
SessionId: sess.GetPublicId(),
|
|
WorkerId: worker.GetPublicId(),
|
|
})
|
|
require.NoError(t, err)
|
|
return sess.GetPublicId()
|
|
}(),
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "non activated session",
|
|
sessionId: newTestSession(-1).GetPublicId(),
|
|
wantErr: true,
|
|
},
|
|
}
|
|
|
|
for _, tc := range cases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
serverRepo.UpsertWorkerStatus(ctx, worker, server.WithKeyId(workerKeyId))
|
|
|
|
resp, err := s.AuthorizeConnection(ctx, &pbs.AuthorizeConnectionRequest{
|
|
SessionId: tc.sessionId,
|
|
WorkerId: worker.GetPublicId(),
|
|
})
|
|
if tc.wantErr {
|
|
assert.Error(t, err)
|
|
return
|
|
}
|
|
require.NoError(t, err)
|
|
assert.NotEmpty(t, resp.GetConnectionId())
|
|
resp.ConnectionId = ""
|
|
assert.Empty(t, cmp.Diff(resp, tc.want, protocmp.Transform()))
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestCancelSession(t *testing.T) {
|
|
ctx := context.Background()
|
|
conn, _ := db.TestSetup(t, "postgres")
|
|
rw := db.New(conn)
|
|
wrapper := db.TestWrapper(t)
|
|
kms := kms.TestKms(t, conn, wrapper)
|
|
org, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
|
|
|
|
serversRepoFn := func() (*server.Repository, error) {
|
|
return server.NewRepository(ctx, rw, rw, kms)
|
|
}
|
|
workerAuthRepoFn := func() (*server.WorkerAuthRepositoryStorage, error) {
|
|
return server.NewRepositoryStorage(ctx, rw, rw, kms)
|
|
}
|
|
sessionRepoFn := func(opts ...session.Option) (*session.Repository, error) {
|
|
return session.NewRepository(ctx, rw, rw, kms, opts...)
|
|
}
|
|
connectionRepoFn := func() (*session.ConnectionRepository, error) {
|
|
return session.NewConnectionRepository(ctx, rw, rw, kms)
|
|
}
|
|
fce := &fakeControllerExtension{
|
|
reader: rw,
|
|
writer: rw,
|
|
}
|
|
|
|
at := authtoken.TestAuthToken(t, conn, kms, org.GetPublicId())
|
|
uId := at.GetIamUserId()
|
|
hc := static.TestCatalogs(t, conn, prj.GetPublicId(), 1)[0]
|
|
hs := static.TestSets(t, conn, hc.GetPublicId(), 1)[0]
|
|
h := static.TestHosts(t, conn, hc.GetPublicId(), 1)[0]
|
|
static.TestSetMembers(t, conn, hs.GetPublicId(), []*static.Host{h})
|
|
tar := tcp.TestTarget(ctx, t, conn, prj.GetPublicId(), "test", target.WithHostSources([]string{hs.GetPublicId()}))
|
|
|
|
sess := session.TestSession(t, conn, wrapper, session.ComposedOf{
|
|
UserId: uId,
|
|
HostId: h.GetPublicId(),
|
|
TargetId: tar.GetPublicId(),
|
|
HostSetId: hs.GetPublicId(),
|
|
AuthTokenId: at.GetPublicId(),
|
|
ProjectId: prj.GetPublicId(),
|
|
Endpoint: "tcp://127.0.0.1:22",
|
|
})
|
|
s := NewWorkerServiceServer(serversRepoFn, workerAuthRepoFn, sessionRepoFn, connectionRepoFn, nil, new(sync.Map), kms, new(atomic.Int64), fce)
|
|
require.NotNil(t, s)
|
|
cases := []struct {
|
|
name string
|
|
wantErr bool
|
|
wantErrMsg string
|
|
want *pbs.CancelSessionResponse
|
|
req *pbs.CancelSessionRequest
|
|
}{
|
|
{
|
|
name: "Invalid session id",
|
|
req: &pbs.CancelSessionRequest{
|
|
SessionId: "s_fakesession",
|
|
},
|
|
wantErr: true,
|
|
wantErrMsg: "rpc error: code = PermissionDenied desc = Unknown session ID.",
|
|
},
|
|
{
|
|
name: "Valid",
|
|
req: &pbs.CancelSessionRequest{
|
|
SessionId: sess.PublicId,
|
|
},
|
|
want: &pbs.CancelSessionResponse{
|
|
Status: pbs.SESSIONSTATUS_SESSIONSTATUS_CANCELING,
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tc := range cases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
assert, require := assert.New(t), require.New(t)
|
|
|
|
got, err := s.CancelSession(ctx, tc.req)
|
|
if tc.wantErr {
|
|
require.Error(err)
|
|
assert.Nil(got)
|
|
assert.Equal(tc.wantErrMsg, err.Error())
|
|
return
|
|
}
|
|
require.NoError(err)
|
|
require.NotNil(got)
|
|
assert.Empty(
|
|
cmp.Diff(
|
|
tc.want,
|
|
got,
|
|
protocmp.Transform(),
|
|
),
|
|
)
|
|
})
|
|
}
|
|
}
|
|
|
|
// This test creates workers of both kms and pki type and verifies that the only
|
|
// returned workers are those that are alive with the expected tag key/value
|
|
func TestHcpbWorkers(t *testing.T) {
|
|
t.Parallel()
|
|
require, assert := require.New(t), assert.New(t)
|
|
|
|
ctx := context.Background()
|
|
conn, _ := db.TestSetup(t, "postgres")
|
|
rw := db.New(conn)
|
|
wrapper := db.TestWrapper(t)
|
|
kmsCache := kms.TestKms(t, conn, wrapper)
|
|
require.NoError(kmsCache.CreateKeys(context.Background(), scope.Global.String(), kms.WithRandomReader(rand.Reader)))
|
|
|
|
serversRepoFn := func() (*server.Repository, error) {
|
|
return server.NewRepository(ctx, rw, rw, kmsCache)
|
|
}
|
|
workerAuthRepoFn := func() (*server.WorkerAuthRepositoryStorage, error) {
|
|
return server.NewRepositoryStorage(ctx, rw, rw, kmsCache)
|
|
}
|
|
sessionRepoFn := func(opts ...session.Option) (*session.Repository, error) {
|
|
return session.NewRepository(ctx, rw, rw, kmsCache, opts...)
|
|
}
|
|
connectionRepoFn := func() (*session.ConnectionRepository, error) {
|
|
return session.NewConnectionRepository(ctx, rw, rw, kmsCache)
|
|
}
|
|
var liveDur atomic.Int64
|
|
liveDur.Store(int64(1 * time.Second))
|
|
fce := &fakeControllerExtension{
|
|
reader: rw,
|
|
writer: rw,
|
|
}
|
|
|
|
// Stale/unalive kms worker aren't expected...
|
|
server.TestKmsWorker(t, conn, wrapper, server.WithWorkerTags(&server.Tag{Key: server.ManagedWorkerTag, Value: "true"}),
|
|
server.WithAddress("old.kms.1"))
|
|
// Sleep + 500ms longer than the liveness duration.
|
|
time.Sleep(time.Duration(liveDur.Load()) + time.Second)
|
|
|
|
server.TestKmsWorker(t, conn, wrapper, server.WithWorkerTags(&server.Tag{Key: server.ManagedWorkerTag, Value: "true"}),
|
|
server.WithAddress("kms.1"))
|
|
server.TestKmsWorker(t, conn, wrapper, server.WithWorkerTags(&server.Tag{Key: server.ManagedWorkerTag, Value: "true"}),
|
|
server.WithAddress("kms.2"))
|
|
server.TestKmsWorker(t, conn, wrapper, server.WithWorkerTags(&server.Tag{Key: "unrelated_tag", Value: "true"}),
|
|
server.WithAddress("unrelated_tag.kms.1"))
|
|
|
|
// Shutdown workers will be removed from routes and sessions, but still returned
|
|
// to downstream workers
|
|
server.TestKmsWorker(t, conn, wrapper, server.WithWorkerTags(&server.Tag{Key: server.ManagedWorkerTag, Value: "true"}),
|
|
server.WithAddress("shutdown.kms.3"), server.WithOperationalState(server.ShutdownOperationalState.String()))
|
|
|
|
// PKI workers are also expected, if they have the managed worker tag
|
|
serverRepo, err := serversRepoFn()
|
|
require.NoError(err)
|
|
var keyId string
|
|
server.TestPkiWorker(t, conn, wrapper, server.WithWorkerTags(&server.Tag{Key: server.ManagedWorkerTag, Value: "true"}),
|
|
server.WithTestPkiWorkerAuthorizedKeyId(&keyId))
|
|
_, err = serverRepo.UpsertWorkerStatus(ctx, server.NewWorker(scope.Global.String(), server.WithAddress("pki.1")), server.WithKeyId(keyId))
|
|
require.NoError(err)
|
|
server.TestPkiWorker(t, conn, wrapper, server.WithWorkerTags(&server.Tag{Key: "unrelated_tag", Value: "true"}),
|
|
server.WithTestPkiWorkerAuthorizedKeyId(&keyId))
|
|
_, err = serverRepo.UpsertWorkerStatus(ctx, server.NewWorker(scope.Global.String(), server.WithAddress("unrelated_tag.pki.1")), server.WithKeyId(keyId))
|
|
require.NoError(err)
|
|
|
|
s := NewWorkerServiceServer(serversRepoFn, workerAuthRepoFn, sessionRepoFn, connectionRepoFn, nil, new(sync.Map), kmsCache, &liveDur, fce)
|
|
require.NotNil(t, s)
|
|
|
|
res, err := s.ListHcpbWorkers(ctx, &pbs.ListHcpbWorkersRequest{})
|
|
require.NoError(err)
|
|
require.NotNil(res)
|
|
expValues := []string{"kms.1", "kms.2", "shutdown.kms.3", "pki.1"}
|
|
var gotValues []string
|
|
for _, worker := range res.Workers {
|
|
gotValues = append(gotValues, worker.Address)
|
|
}
|
|
assert.ElementsMatch(expValues, gotValues)
|
|
}
|
|
|
|
func TestStatistics(t *testing.T) {
|
|
t.Parallel()
|
|
ctx := context.Background()
|
|
conn, _ := db.TestSetup(t, "postgres")
|
|
rw := db.New(conn)
|
|
wrapper := db.TestWrapper(t)
|
|
kms := kms.TestKms(t, conn, wrapper)
|
|
serversRepoFn := func() (*server.Repository, error) {
|
|
return server.NewRepository(ctx, rw, rw, kms)
|
|
}
|
|
workerAuthRepoFn := func() (*server.WorkerAuthRepositoryStorage, error) {
|
|
return server.NewRepositoryStorage(ctx, rw, rw, kms)
|
|
}
|
|
sessionRepoFn := func(opts ...session.Option) (*session.Repository, error) {
|
|
return session.NewRepository(ctx, rw, rw, kms, opts...)
|
|
}
|
|
connectionRepoFn := func() (*session.ConnectionRepository, error) {
|
|
return session.NewConnectionRepository(ctx, rw, rw, kms)
|
|
}
|
|
connectionRepoErrFn := func() (*session.ConnectionRepository, error) {
|
|
return nil, fmt.Errorf("test error")
|
|
}
|
|
fce := &fakeControllerExtension{
|
|
reader: rw,
|
|
writer: rw,
|
|
}
|
|
cases := []struct {
|
|
name string
|
|
workerService *workerServiceServer
|
|
wantErrMsg string
|
|
expectedStatusCode codes.Code
|
|
req *pbs.StatisticsRequest
|
|
}{
|
|
{
|
|
name: "empty worker id",
|
|
workerService: NewWorkerServiceServer(serversRepoFn, workerAuthRepoFn, sessionRepoFn, connectionRepoFn, nil, new(sync.Map), kms, new(atomic.Int64), fce),
|
|
req: &pbs.StatisticsRequest{},
|
|
wantErrMsg: "rpc error: code = InvalidArgument desc = worker id is empty",
|
|
expectedStatusCode: codes.InvalidArgument,
|
|
},
|
|
{
|
|
name: "missing session connection repo",
|
|
workerService: NewWorkerServiceServer(serversRepoFn, workerAuthRepoFn, sessionRepoFn, connectionRepoErrFn, nil, new(sync.Map), kms, new(atomic.Int64), fce),
|
|
req: &pbs.StatisticsRequest{
|
|
WorkerId: "w_1234567890",
|
|
Sessions: []*pbs.SessionStatistics{
|
|
{},
|
|
},
|
|
},
|
|
wantErrMsg: "rpc error: code = Internal desc = Error acquiring connection repo: test error",
|
|
expectedStatusCode: codes.Internal,
|
|
},
|
|
{
|
|
name: "nil sessions",
|
|
workerService: NewWorkerServiceServer(serversRepoFn, workerAuthRepoFn, sessionRepoFn, connectionRepoFn, nil, new(sync.Map), kms, new(atomic.Int64), fce),
|
|
req: &pbs.StatisticsRequest{
|
|
WorkerId: "w_1234567890",
|
|
},
|
|
},
|
|
{
|
|
name: "empty sessions",
|
|
workerService: NewWorkerServiceServer(serversRepoFn, workerAuthRepoFn, sessionRepoFn, connectionRepoFn, nil, new(sync.Map), kms, new(atomic.Int64), fce),
|
|
req: &pbs.StatisticsRequest{
|
|
WorkerId: "w_1234567890",
|
|
Sessions: []*pbs.SessionStatistics{},
|
|
},
|
|
},
|
|
{
|
|
name: "empty session id",
|
|
workerService: NewWorkerServiceServer(serversRepoFn, workerAuthRepoFn, sessionRepoFn, connectionRepoFn, nil, new(sync.Map), kms, new(atomic.Int64), fce),
|
|
req: &pbs.StatisticsRequest{
|
|
WorkerId: "w_1234567890",
|
|
Sessions: []*pbs.SessionStatistics{
|
|
{},
|
|
},
|
|
},
|
|
wantErrMsg: "rpc error: code = InvalidArgument desc = session id is empty",
|
|
expectedStatusCode: codes.InvalidArgument,
|
|
},
|
|
{
|
|
name: "empty connection id",
|
|
workerService: NewWorkerServiceServer(serversRepoFn, workerAuthRepoFn, sessionRepoFn, connectionRepoFn, nil, new(sync.Map), kms, new(atomic.Int64), fce),
|
|
req: &pbs.StatisticsRequest{
|
|
WorkerId: "w_1234567890",
|
|
Sessions: []*pbs.SessionStatistics{
|
|
{
|
|
SessionId: "s_1234567890",
|
|
Connections: []*pbs.Connection{
|
|
{},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
wantErrMsg: "rpc error: code = InvalidArgument desc = connection id is empty",
|
|
expectedStatusCode: codes.InvalidArgument,
|
|
},
|
|
}
|
|
for _, tc := range cases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
assert, require := assert.New(t), require.New(t)
|
|
got, err := tc.workerService.Statistics(context.Background(), tc.req)
|
|
require.NotNil(got)
|
|
if tc.wantErrMsg != "" {
|
|
require.Error(err)
|
|
assert.ErrorContains(err, tc.wantErrMsg)
|
|
actualStatus, ok := status.FromError(err)
|
|
require.True(ok)
|
|
assert.Equal(tc.expectedStatusCode, actualStatus.Code())
|
|
return
|
|
}
|
|
require.NoError(err)
|
|
})
|
|
}
|
|
|
|
// update the bytes up and bytes down for an active session and close an orphaned session connection
|
|
t.Run("happy path", func(t *testing.T) {
|
|
assert, require := assert.New(t), require.New(t)
|
|
connectionRepoFn := func() (*session.ConnectionRepository, error) {
|
|
return session.NewConnectionRepository(ctx, rw, rw, kms, session.WithWorkerStateDelay(0))
|
|
}
|
|
sessRepo, err := sessionRepoFn()
|
|
require.NoError(err)
|
|
connRepo, err := connectionRepoFn()
|
|
require.NoError(err)
|
|
|
|
w := server.TestKmsWorker(t, conn, wrapper)
|
|
org, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
|
|
authToken := authtoken.TestAuthToken(t, conn, kms, org.GetPublicId())
|
|
userId := authToken.GetIamUserId()
|
|
hostCatalog := static.TestCatalogs(t, conn, prj.GetPublicId(), 1)[0]
|
|
hostSet := static.TestSets(t, conn, hostCatalog.GetPublicId(), 1)[0]
|
|
host := static.TestHosts(t, conn, hostCatalog.GetPublicId(), 1)[0]
|
|
static.TestSetMembers(t, conn, hostSet.GetPublicId(), []*static.Host{host})
|
|
tar := tcp.TestTarget(
|
|
context.Background(),
|
|
t, conn, prj.GetPublicId(), "test",
|
|
target.WithHostSources([]string{hostSet.GetPublicId()}),
|
|
target.WithSessionConnectionLimit(-1),
|
|
)
|
|
s1 := session.TestSession(t, conn, wrapper, session.ComposedOf{
|
|
UserId: userId,
|
|
HostId: host.GetPublicId(),
|
|
TargetId: tar.GetPublicId(),
|
|
HostSetId: hostSet.GetPublicId(),
|
|
AuthTokenId: authToken.GetPublicId(),
|
|
ProjectId: prj.GetPublicId(),
|
|
Endpoint: "tcp://127.0.0.1:22",
|
|
ConnectionLimit: 10,
|
|
})
|
|
s1, _, err = sessRepo.ActivateSession(context.Background(), s1.PublicId, s1.Version, session.TestTofu(t))
|
|
require.NoError(err)
|
|
c1, err := connRepo.AuthorizeConnection(context.Background(), s1.PublicId, w.GetPublicId())
|
|
require.NoError(err)
|
|
|
|
s2 := session.TestSession(t, conn, wrapper, session.ComposedOf{
|
|
UserId: userId,
|
|
HostId: host.GetPublicId(),
|
|
TargetId: tar.GetPublicId(),
|
|
HostSetId: hostSet.GetPublicId(),
|
|
AuthTokenId: authToken.GetPublicId(),
|
|
ProjectId: prj.GetPublicId(),
|
|
Endpoint: "tcp://127.0.0.1:22",
|
|
ConnectionLimit: 10,
|
|
})
|
|
s2, _, err = sessRepo.ActivateSession(context.Background(), s2.PublicId, s2.Version, session.TestTofu(t))
|
|
require.NoError(err)
|
|
c2, err := connRepo.AuthorizeConnection(context.Background(), s2.PublicId, w.PublicId)
|
|
require.NoError(err)
|
|
|
|
var expectedBytesUp int64 = 1024
|
|
var expectedBytesDown int64 = 2048
|
|
workerService := NewWorkerServiceServer(serversRepoFn, workerAuthRepoFn, sessionRepoFn, connectionRepoFn, nil, new(sync.Map), kms, new(atomic.Int64), fce)
|
|
got, err := workerService.Statistics(context.Background(), &pbs.StatisticsRequest{
|
|
WorkerId: w.GetPublicId(),
|
|
Sessions: []*pbs.SessionStatistics{
|
|
{
|
|
SessionId: s1.PublicId,
|
|
Connections: []*pbs.Connection{
|
|
{
|
|
ConnectionId: c1.PublicId,
|
|
BytesUp: expectedBytesUp,
|
|
BytesDown: expectedBytesDown,
|
|
Status: pbs.CONNECTIONSTATUS_CONNECTIONSTATUS_AUTHORIZED,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
})
|
|
require.NoError(err)
|
|
assert.NotNil(got)
|
|
|
|
conn, err := connRepo.LookupConnection(context.Background(), c1.PublicId)
|
|
require.NoError(err)
|
|
assert.Equal(expectedBytesUp, conn.BytesUp)
|
|
assert.Equal(expectedBytesDown, conn.BytesDown)
|
|
assert.Equal(session.StatusAuthorized, session.ConnectionStatusFromString(conn.Status))
|
|
|
|
conn, err = connRepo.LookupConnection(context.Background(), c2.PublicId)
|
|
require.NoError(err)
|
|
assert.Empty(conn.BytesUp)
|
|
assert.Empty(conn.BytesDown)
|
|
assert.Equal(session.StatusClosed, session.ConnectionStatusFromString(conn.Status))
|
|
})
|
|
}
|
|
|
|
func TestSessionInfo(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
ctx := context.Background()
|
|
conn, _ := db.TestSetup(t, "postgres")
|
|
rw := db.New(conn)
|
|
wrapper := db.TestWrapper(t)
|
|
testKms := kms.TestKms(t, conn, wrapper)
|
|
serversRepoFn := func() (*server.Repository, error) {
|
|
return server.NewRepository(ctx, rw, rw, testKms)
|
|
}
|
|
workerAuthRepoFn := func() (*server.WorkerAuthRepositoryStorage, error) {
|
|
return server.NewRepositoryStorage(ctx, rw, rw, testKms)
|
|
}
|
|
sessionRepoFn := func(opts ...session.Option) (*session.Repository, error) {
|
|
return session.NewRepository(ctx, rw, rw, testKms, opts...)
|
|
}
|
|
connectionRepoFn := func() (*session.ConnectionRepository, error) {
|
|
return session.NewConnectionRepository(ctx, rw, rw, testKms)
|
|
}
|
|
serversErrRepoFn := func() (*server.Repository, error) {
|
|
return nil, errors.New("unknown error")
|
|
}
|
|
sessionErrRepoFn := func(opt ...session.Option) (*session.Repository, error) {
|
|
return nil, errors.New("unknown error")
|
|
}
|
|
fce := &fakeControllerExtension{
|
|
reader: rw,
|
|
writer: rw,
|
|
}
|
|
iamRepo := iam.TestRepo(t, conn, wrapper)
|
|
iam.TestScopes(t, iamRepo)
|
|
workerId := server.TestPkiWorker(t, conn, wrapper).PublicId
|
|
cases := []struct {
|
|
name string
|
|
workerService *workerServiceServer
|
|
wantErrMsg string
|
|
expectedStatusCode codes.Code
|
|
req *pbs.SessionInfoRequest
|
|
expectedResponse *pbs.SessionInfoResponse
|
|
}{
|
|
{
|
|
name: "empty worker id",
|
|
workerService: NewWorkerServiceServer(serversRepoFn, workerAuthRepoFn, sessionRepoFn, connectionRepoFn, nil, new(sync.Map), testKms, new(atomic.Int64), fce),
|
|
req: &pbs.SessionInfoRequest{},
|
|
wantErrMsg: "rpc error: code = InvalidArgument desc = worker id is empty",
|
|
expectedStatusCode: codes.InvalidArgument,
|
|
},
|
|
{
|
|
name: "invalid unspecified session type",
|
|
workerService: NewWorkerServiceServer(serversRepoFn, workerAuthRepoFn, sessionRepoFn, connectionRepoFn, nil, new(sync.Map), testKms, new(atomic.Int64), fce),
|
|
req: &pbs.SessionInfoRequest{
|
|
WorkerId: "w_1234567890",
|
|
Sessions: []*pbs.Session{
|
|
{
|
|
SessionId: "s_A",
|
|
SessionType: pbs.SessionType_SESSION_TYPE_UNSPECIFIED,
|
|
SessionStatus: pbs.SESSIONSTATUS_SESSIONSTATUS_ACTIVE,
|
|
},
|
|
},
|
|
},
|
|
wantErrMsg: "rpc error: code = InvalidArgument desc = unspecified session type",
|
|
expectedStatusCode: codes.InvalidArgument,
|
|
},
|
|
{
|
|
name: "invalid unknown session type",
|
|
workerService: NewWorkerServiceServer(serversRepoFn, workerAuthRepoFn, sessionRepoFn, connectionRepoFn, nil, new(sync.Map), testKms, new(atomic.Int64), fce),
|
|
req: &pbs.SessionInfoRequest{
|
|
WorkerId: "w_1234567890",
|
|
Sessions: []*pbs.Session{
|
|
{
|
|
SessionId: "s_A",
|
|
SessionType: pbs.SessionType(-1),
|
|
SessionStatus: pbs.SESSIONSTATUS_SESSIONSTATUS_ACTIVE,
|
|
},
|
|
},
|
|
},
|
|
wantErrMsg: "rpc error: code = InvalidArgument desc = unknown session type",
|
|
expectedStatusCode: codes.InvalidArgument,
|
|
},
|
|
{
|
|
name: "session repository error",
|
|
workerService: NewWorkerServiceServer(serversRepoFn, workerAuthRepoFn, sessionErrRepoFn, connectionRepoFn, nil, new(sync.Map), testKms, new(atomic.Int64), fce),
|
|
req: &pbs.SessionInfoRequest{
|
|
WorkerId: "w_1234567890",
|
|
},
|
|
wantErrMsg: "rpc error: code = Internal desc = Error acquiring repo to query session status: unknown error",
|
|
expectedStatusCode: codes.Internal,
|
|
},
|
|
{
|
|
name: "server repository error",
|
|
workerService: NewWorkerServiceServer(serversErrRepoFn, workerAuthRepoFn, sessionRepoFn, connectionRepoFn, nil, new(sync.Map), testKms, new(atomic.Int64), fce),
|
|
req: &pbs.SessionInfoRequest{
|
|
WorkerId: "w_1234567890",
|
|
},
|
|
wantErrMsg: "rpc error: code = Internal desc = Error acquiring repo to upsert session info status time: unknown error",
|
|
expectedStatusCode: codes.Internal,
|
|
},
|
|
{
|
|
name: "ignore canceled and terminated sessions",
|
|
workerService: NewWorkerServiceServer(serversRepoFn, workerAuthRepoFn, sessionRepoFn, connectionRepoFn, nil, new(sync.Map), testKms, new(atomic.Int64), fce),
|
|
req: &pbs.SessionInfoRequest{
|
|
WorkerId: workerId,
|
|
Sessions: []*pbs.Session{
|
|
{
|
|
SessionId: "s_A",
|
|
SessionStatus: pbs.SESSIONSTATUS_SESSIONSTATUS_CANCELING,
|
|
SessionType: pbs.SessionType_SESSION_TYPE_INGRESSED,
|
|
},
|
|
{
|
|
SessionId: "s_B",
|
|
SessionStatus: pbs.SESSIONSTATUS_SESSIONSTATUS_TERMINATED,
|
|
SessionType: pbs.SessionType_SESSION_TYPE_INGRESSED,
|
|
},
|
|
{
|
|
SessionId: "s_C",
|
|
SessionStatus: pbs.SESSIONSTATUS_SESSIONSTATUS_CANCELING,
|
|
SessionType: pbs.SessionType_SESSION_TYPE_RECORDED,
|
|
},
|
|
{
|
|
SessionId: "s_D",
|
|
SessionStatus: pbs.SESSIONSTATUS_SESSIONSTATUS_TERMINATED,
|
|
SessionType: pbs.SessionType_SESSION_TYPE_RECORDED,
|
|
},
|
|
},
|
|
},
|
|
expectedResponse: &pbs.SessionInfoResponse{},
|
|
},
|
|
{
|
|
name: "empty sessions",
|
|
workerService: NewWorkerServiceServer(serversRepoFn, workerAuthRepoFn, sessionRepoFn, connectionRepoFn, nil, new(sync.Map), testKms, new(atomic.Int64), fce),
|
|
req: &pbs.SessionInfoRequest{
|
|
WorkerId: workerId,
|
|
},
|
|
expectedResponse: &pbs.SessionInfoResponse{},
|
|
},
|
|
}
|
|
for _, tc := range cases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
assert, require := assert.New(t), require.New(t)
|
|
got, err := tc.workerService.SessionInfo(context.Background(), tc.req)
|
|
require.NotNil(got)
|
|
if tc.wantErrMsg != "" {
|
|
require.Error(err)
|
|
assert.ErrorContains(err, tc.wantErrMsg)
|
|
actualStatus, ok := status.FromError(err)
|
|
require.True(ok)
|
|
assert.Equal(tc.expectedStatusCode, actualStatus.Code())
|
|
return
|
|
}
|
|
require.NoError(err)
|
|
require.NotNil(got)
|
|
assert.Len(got.NonActiveSessions, len(tc.expectedResponse.NonActiveSessions))
|
|
})
|
|
}
|
|
|
|
t.Run("session canceled with active connections", func(t *testing.T) {
|
|
t.Parallel()
|
|
assert, require := assert.New(t), require.New(t)
|
|
sessionTypes := []pbs.SessionType{
|
|
pbs.SessionType_SESSION_TYPE_INGRESSED,
|
|
pbs.SessionType_SESSION_TYPE_RECORDED,
|
|
}
|
|
for _, sessionType := range sessionTypes {
|
|
t.Run(sessionType.String(), func(t *testing.T) {
|
|
t.Parallel()
|
|
ctx := context.Background()
|
|
conn, _ := db.TestSetup(t, "postgres")
|
|
rw := db.New(conn)
|
|
wrapper := db.TestWrapper(t)
|
|
testKms := kms.TestKms(t, conn, wrapper)
|
|
org, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
|
|
|
|
serverRepo, err := server.NewRepository(ctx, rw, rw, testKms)
|
|
require.NoError(err)
|
|
c := server.NewController("test_controller1", server.WithAddress("127.0.0.1"))
|
|
_, err = serverRepo.UpsertController(ctx, c)
|
|
require.NoError(err)
|
|
|
|
serversRepoFn := func() (*server.Repository, error) {
|
|
return serverRepo, nil
|
|
}
|
|
workerAuthRepoFn := func() (*server.WorkerAuthRepositoryStorage, error) {
|
|
return server.NewRepositoryStorage(ctx, rw, rw, testKms)
|
|
}
|
|
sessionRepoFn := func(opts ...session.Option) (*session.Repository, error) {
|
|
return session.NewRepository(ctx, rw, rw, testKms, opts...)
|
|
}
|
|
connRepoFn := func() (*session.ConnectionRepository, error) {
|
|
return session.NewConnectionRepository(ctx, rw, rw, testKms)
|
|
}
|
|
fce := &fakeControllerExtension{
|
|
reader: rw,
|
|
writer: rw,
|
|
}
|
|
|
|
repo, err := sessionRepoFn()
|
|
require.NoError(err)
|
|
connRepo, err := connRepoFn()
|
|
require.NoError(err)
|
|
|
|
at := authtoken.TestAuthToken(t, conn, testKms, org.GetPublicId())
|
|
uId := at.GetIamUserId()
|
|
hc := static.TestCatalogs(t, conn, prj.GetPublicId(), 1)[0]
|
|
hs := static.TestSets(t, conn, hc.GetPublicId(), 1)[0]
|
|
h := static.TestHosts(t, conn, hc.GetPublicId(), 1)[0]
|
|
static.TestSetMembers(t, conn, hs.GetPublicId(), []*static.Host{h})
|
|
tar := tcp.TestTarget(
|
|
ctx,
|
|
t, conn, prj.GetPublicId(), "test",
|
|
target.WithHostSources([]string{hs.GetPublicId()}),
|
|
target.WithSessionConnectionLimit(-1),
|
|
)
|
|
|
|
worker1 := server.TestKmsWorker(t, conn, wrapper)
|
|
sess := session.TestSession(t, conn, wrapper, session.ComposedOf{
|
|
UserId: uId,
|
|
HostId: h.GetPublicId(),
|
|
TargetId: tar.GetPublicId(),
|
|
HostSetId: hs.GetPublicId(),
|
|
AuthTokenId: at.GetPublicId(),
|
|
ProjectId: prj.GetPublicId(),
|
|
Endpoint: "tcp://127.0.0.1:22",
|
|
ConnectionLimit: 10,
|
|
})
|
|
tofu := session.TestTofu(t)
|
|
sess, _, err = repo.ActivateSession(ctx, sess.PublicId, sess.Version, tofu)
|
|
require.NoError(err)
|
|
sess2 := session.TestSession(t, conn, wrapper, session.ComposedOf{
|
|
UserId: uId,
|
|
HostId: h.GetPublicId(),
|
|
TargetId: tar.GetPublicId(),
|
|
HostSetId: hs.GetPublicId(),
|
|
AuthTokenId: at.GetPublicId(),
|
|
ProjectId: prj.GetPublicId(),
|
|
Endpoint: "tcp://127.0.0.1:22",
|
|
ConnectionLimit: 10,
|
|
})
|
|
tofu2 := session.TestTofu(t)
|
|
sess2, _, err = repo.ActivateSession(ctx, sess2.PublicId, sess2.Version, tofu2)
|
|
require.NoError(err)
|
|
|
|
s := NewWorkerServiceServer(serversRepoFn, workerAuthRepoFn, sessionRepoFn, connRepoFn, nil, new(sync.Map), testKms, new(atomic.Int64), fce)
|
|
require.NotNil(t, s)
|
|
|
|
_, err = connRepo.AuthorizeConnection(ctx, sess.PublicId, worker1.PublicId)
|
|
require.NoError(err)
|
|
|
|
_, err = connRepo.AuthorizeConnection(ctx, sess2.PublicId, worker1.PublicId)
|
|
require.NoError(err)
|
|
|
|
_, err = repo.CancelSession(ctx, sess2.PublicId, sess.Version)
|
|
require.NoError(err)
|
|
|
|
got, err := s.SessionInfo(ctx, &pbs.SessionInfoRequest{
|
|
WorkerId: worker1.GetPublicId(),
|
|
Sessions: []*pbs.Session{
|
|
{
|
|
SessionId: sess2.PublicId,
|
|
SessionStatus: pbs.SESSIONSTATUS_SESSIONSTATUS_ACTIVE,
|
|
SessionType: sessionType,
|
|
},
|
|
},
|
|
})
|
|
require.NoError(err)
|
|
require.NotNil(got)
|
|
require.Len(got.NonActiveSessions, 1)
|
|
actualNonActiveSession := got.NonActiveSessions[0]
|
|
require.NotNil(actualNonActiveSession)
|
|
assert.Equal(sess2.PublicId, actualNonActiveSession.SessionId)
|
|
assert.Equal(pbs.SESSIONSTATUS_SESSIONSTATUS_CANCELING, actualNonActiveSession.SessionStatus)
|
|
assert.Equal(sessionType, actualNonActiveSession.SessionType)
|
|
assert.Empty(actualNonActiveSession.Connections)
|
|
})
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestRoutingInfo(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
ctx := context.Background()
|
|
conn, _ := db.TestSetup(t, "postgres")
|
|
rw := db.New(conn)
|
|
wrapper := db.TestWrapper(t)
|
|
kmsCache := kms.TestKms(t, conn, wrapper)
|
|
require.NoError(t, kmsCache.CreateKeys(context.Background(), scope.Global.String(), kms.WithRandomReader(rand.Reader)))
|
|
|
|
serversRepoFn := func() (*server.Repository, error) {
|
|
return server.NewRepository(ctx, rw, rw, kmsCache)
|
|
}
|
|
workerAuthRepoFn := func() (*server.WorkerAuthRepositoryStorage, error) {
|
|
return server.NewRepositoryStorage(ctx, rw, rw, kmsCache)
|
|
}
|
|
sessionRepoFn := func(opts ...session.Option) (*session.Repository, error) {
|
|
return session.NewRepository(ctx, rw, rw, kmsCache, opts...)
|
|
}
|
|
connectionRepoFn := func() (*session.ConnectionRepository, error) {
|
|
return session.NewConnectionRepository(ctx, rw, rw, kmsCache)
|
|
}
|
|
var liveDur atomic.Int64
|
|
liveDur.Store(int64(1 * time.Second))
|
|
fce := &fakeControllerExtension{
|
|
reader: rw,
|
|
writer: rw,
|
|
}
|
|
serverRepo, err := serversRepoFn()
|
|
require.NoError(t, err)
|
|
|
|
// Set up resources
|
|
var w1KeyId, w2KeyId string
|
|
w1 := server.TestPkiWorker(t, conn, wrapper, server.WithTestPkiWorkerAuthorizedKeyId(&w1KeyId))
|
|
_ = server.TestPkiWorker(t, conn, wrapper, server.WithTestPkiWorkerAuthorizedKeyId(&w2KeyId))
|
|
w3 := server.TestKmsWorker(t, conn, wrapper, server.WithName("testworker3"))
|
|
|
|
c := server.NewController("test_controller1", server.WithAddress("1.2.3.4"))
|
|
_, err = serverRepo.UpsertController(ctx, c)
|
|
require.NoError(t, err)
|
|
|
|
s := NewWorkerServiceServer(serversRepoFn, workerAuthRepoFn, sessionRepoFn, connectionRepoFn, nil, new(sync.Map), kmsCache, &liveDur, fce)
|
|
require.NotNil(t, s)
|
|
require.NoError(t, err)
|
|
|
|
t.Run("Missing worker status", func(t *testing.T) {
|
|
req := &pbs.RoutingInfoRequest{}
|
|
_, err := s.RoutingInfo(ctx, req)
|
|
require.ErrorContains(t, err, "worker status is required")
|
|
})
|
|
|
|
t.Run("Missing key ID and public Id", func(t *testing.T) {
|
|
req := &pbs.RoutingInfoRequest{
|
|
WorkerStatus: &pb.ServerWorkerStatus{
|
|
PublicId: "",
|
|
KeyId: "",
|
|
Address: "2.3.4.5:8080",
|
|
Tags: []*pb.TagPair{
|
|
{Key: "tag1", Value: "value1"},
|
|
{Key: "tag2", Value: "value2"},
|
|
},
|
|
ReleaseVersion: "Boundary v0.18.0",
|
|
OperationalState: server.ActiveOperationalState.String(),
|
|
LocalStorageState: server.AvailableLocalStorageState.String(),
|
|
},
|
|
UpdateTags: true,
|
|
ConnectedUnmappedWorkerKeyIdentifiers: []string{w2KeyId, "worker-key-2"},
|
|
ConnectedWorkerPublicIds: []string{w3.PublicId, "worker-3"},
|
|
}
|
|
_, err := s.RoutingInfo(ctx, req)
|
|
require.ErrorContains(t, err, "public id, key id and name are not set in the request; one is required")
|
|
})
|
|
|
|
t.Run("Missing address", func(t *testing.T) {
|
|
req := &pbs.RoutingInfoRequest{
|
|
WorkerStatus: &pb.ServerWorkerStatus{
|
|
PublicId: "",
|
|
KeyId: w1KeyId,
|
|
Address: "",
|
|
Tags: []*pb.TagPair{
|
|
{Key: "tag1", Value: "value1"},
|
|
{Key: "tag2", Value: "value2"},
|
|
},
|
|
ReleaseVersion: "Boundary v0.18.0",
|
|
OperationalState: server.ActiveOperationalState.String(),
|
|
LocalStorageState: server.AvailableLocalStorageState.String(),
|
|
},
|
|
UpdateTags: true,
|
|
ConnectedUnmappedWorkerKeyIdentifiers: []string{w2KeyId, "worker-key-2"},
|
|
ConnectedWorkerPublicIds: []string{w3.PublicId, "worker-3"},
|
|
}
|
|
_, err := s.RoutingInfo(ctx, req)
|
|
require.ErrorContains(t, err, "address is not set but is required")
|
|
})
|
|
|
|
t.Run("Missing release version", func(t *testing.T) {
|
|
req := &pbs.RoutingInfoRequest{
|
|
WorkerStatus: &pb.ServerWorkerStatus{
|
|
PublicId: "",
|
|
KeyId: w1KeyId,
|
|
Address: "2.3.4.5:8080",
|
|
Tags: []*pb.TagPair{
|
|
{Key: "tag1", Value: "value1"},
|
|
{Key: "tag2", Value: "value2"},
|
|
},
|
|
ReleaseVersion: "",
|
|
OperationalState: server.ActiveOperationalState.String(),
|
|
LocalStorageState: server.AvailableLocalStorageState.String(),
|
|
},
|
|
UpdateTags: true,
|
|
ConnectedUnmappedWorkerKeyIdentifiers: []string{w2KeyId, "worker-key-2"},
|
|
ConnectedWorkerPublicIds: []string{w3.PublicId, "worker-3"},
|
|
}
|
|
_, err := s.RoutingInfo(ctx, req)
|
|
require.ErrorContains(t, err, "release version is not set but is required")
|
|
})
|
|
|
|
t.Run("Missing operational state", func(t *testing.T) {
|
|
req := &pbs.RoutingInfoRequest{
|
|
WorkerStatus: &pb.ServerWorkerStatus{
|
|
PublicId: "",
|
|
KeyId: w1KeyId,
|
|
Address: "2.3.4.5:8080",
|
|
Tags: []*pb.TagPair{
|
|
{Key: "tag1", Value: "value1"},
|
|
{Key: "tag2", Value: "value2"},
|
|
},
|
|
ReleaseVersion: "Boundary v0.18.0",
|
|
OperationalState: "",
|
|
LocalStorageState: server.AvailableLocalStorageState.String(),
|
|
},
|
|
UpdateTags: true,
|
|
ConnectedUnmappedWorkerKeyIdentifiers: []string{w2KeyId, "worker-key-2"},
|
|
ConnectedWorkerPublicIds: []string{w3.PublicId, "worker-3"},
|
|
}
|
|
_, err := s.RoutingInfo(ctx, req)
|
|
require.ErrorContains(t, err, "operational state is not set but is required")
|
|
})
|
|
|
|
t.Run("Missing local storage state", func(t *testing.T) {
|
|
req := &pbs.RoutingInfoRequest{
|
|
WorkerStatus: &pb.ServerWorkerStatus{
|
|
PublicId: "",
|
|
KeyId: w1KeyId,
|
|
Address: "2.3.4.5:8080",
|
|
Tags: []*pb.TagPair{
|
|
{Key: "tag1", Value: "value1"},
|
|
{Key: "tag2", Value: "value2"},
|
|
},
|
|
ReleaseVersion: "Boundary v0.18.0",
|
|
OperationalState: server.ActiveOperationalState.String(),
|
|
LocalStorageState: "",
|
|
},
|
|
UpdateTags: true,
|
|
ConnectedUnmappedWorkerKeyIdentifiers: []string{w2KeyId, "worker-key-2"},
|
|
ConnectedWorkerPublicIds: []string{w3.PublicId, "worker-3"},
|
|
}
|
|
_, err := s.RoutingInfo(ctx, req)
|
|
require.ErrorContains(t, err, "local storage state is not set but is required")
|
|
})
|
|
|
|
t.Run("Successful first and second request (PKI worker)", func(t *testing.T) {
|
|
req := &pbs.RoutingInfoRequest{
|
|
WorkerStatus: &pb.ServerWorkerStatus{
|
|
PublicId: "", // Not set on the first request
|
|
KeyId: w1KeyId,
|
|
Address: "2.3.4.5:8080",
|
|
Tags: []*pb.TagPair{
|
|
{Key: "tag1", Value: "value1"},
|
|
{Key: "tag2", Value: "value2"},
|
|
},
|
|
ReleaseVersion: "Boundary v0.18.0",
|
|
OperationalState: server.ActiveOperationalState.String(),
|
|
LocalStorageState: server.AvailableLocalStorageState.String(),
|
|
},
|
|
UpdateTags: true,
|
|
ConnectedUnmappedWorkerKeyIdentifiers: []string{w2KeyId, "worker-key-2"},
|
|
ConnectedWorkerPublicIds: []string{w3.PublicId, "worker-3"},
|
|
}
|
|
resp, err := s.RoutingInfo(ctx, req)
|
|
require.NoError(t, err)
|
|
assert.NotNil(t, resp)
|
|
|
|
assert.Equal(t, resp.WorkerId, w1.PublicId)
|
|
assert.Equal(t, resp.CalculatedUpstreamAddresses, []string{c.Address})
|
|
assert.Equal(t, resp.AuthorizedDownstreamWorkers.UnmappedWorkerKeyIdentifiers, []string{w2KeyId})
|
|
assert.Equal(t, resp.AuthorizedDownstreamWorkers.WorkerPublicIds, []string{w3.PublicId})
|
|
|
|
w1, err := serverRepo.LookupWorker(ctx, resp.WorkerId)
|
|
require.NoError(t, err)
|
|
|
|
assert.Equal(t, w1.Address, "2.3.4.5:8080")
|
|
expTags := server.Tags{
|
|
"tag1": []string{"value1"},
|
|
"tag2": []string{"value2"},
|
|
}
|
|
assert.Equal(t, len(expTags), len(w1.ConfigTags))
|
|
for k, v := range expTags {
|
|
assert.ElementsMatch(t, v, w1.ConfigTags[k])
|
|
}
|
|
assert.Equal(t, w1.ReleaseVersion, "Boundary v0.18.0")
|
|
assert.EqualValues(t, w1.OperationalState, server.ActiveOperationalState)
|
|
assert.EqualValues(t, w1.LocalStorageState, server.AvailableLocalStorageState)
|
|
|
|
// Now send the subsequent routing info request
|
|
req.WorkerStatus.PublicId = w1.PublicId // Only set on subsequent requests
|
|
resp, err = s.RoutingInfo(ctx, req)
|
|
require.NoError(t, err)
|
|
assert.NotNil(t, resp)
|
|
|
|
assert.Equal(t, resp.WorkerId, w1.PublicId)
|
|
assert.Equal(t, resp.CalculatedUpstreamAddresses, []string{c.Address})
|
|
assert.Equal(t, resp.AuthorizedDownstreamWorkers.UnmappedWorkerKeyIdentifiers, []string{w2KeyId})
|
|
assert.Equal(t, resp.AuthorizedDownstreamWorkers.WorkerPublicIds, []string{w3.PublicId})
|
|
|
|
w1, err = serverRepo.LookupWorker(ctx, resp.WorkerId)
|
|
require.NoError(t, err)
|
|
|
|
assert.Equal(t, w1.Address, "2.3.4.5:8080")
|
|
assert.Equal(t, len(expTags), len(w1.ConfigTags))
|
|
for k, v := range expTags {
|
|
assert.ElementsMatch(t, v, w1.ConfigTags[k])
|
|
}
|
|
assert.Equal(t, w1.ReleaseVersion, "Boundary v0.18.0")
|
|
assert.EqualValues(t, w1.OperationalState, server.ActiveOperationalState)
|
|
assert.EqualValues(t, w1.LocalStorageState, server.AvailableLocalStorageState)
|
|
})
|
|
|
|
t.Run("Successful first and second request (KMS worker)", func(t *testing.T) {
|
|
req := &pbs.RoutingInfoRequest{
|
|
WorkerStatus: &pb.ServerWorkerStatus{
|
|
PublicId: "", // Not set on the first request
|
|
Name: "testworker3",
|
|
Address: "2.3.4.5:8080",
|
|
Tags: []*pb.TagPair{
|
|
{Key: "tag1", Value: "value1"},
|
|
{Key: "tag2", Value: "value2"},
|
|
},
|
|
ReleaseVersion: "Boundary v0.18.0",
|
|
OperationalState: server.ActiveOperationalState.String(),
|
|
LocalStorageState: server.AvailableLocalStorageState.String(),
|
|
},
|
|
UpdateTags: true,
|
|
ConnectedUnmappedWorkerKeyIdentifiers: []string{w2KeyId, "worker-key-2"},
|
|
ConnectedWorkerPublicIds: []string{w1.PublicId, "worker-4"},
|
|
}
|
|
resp, err := s.RoutingInfo(ctx, req)
|
|
require.NoError(t, err)
|
|
assert.NotNil(t, resp)
|
|
|
|
assert.Equal(t, resp.WorkerId, w3.PublicId)
|
|
assert.Equal(t, resp.CalculatedUpstreamAddresses, []string{c.Address})
|
|
assert.Equal(t, resp.AuthorizedDownstreamWorkers.UnmappedWorkerKeyIdentifiers, []string{w2KeyId})
|
|
assert.Equal(t, resp.AuthorizedDownstreamWorkers.WorkerPublicIds, []string{w1.PublicId})
|
|
|
|
w3, err := serverRepo.LookupWorker(ctx, resp.WorkerId)
|
|
require.NoError(t, err)
|
|
|
|
assert.Equal(t, w3.Address, "2.3.4.5:8080")
|
|
expTags := server.Tags{
|
|
"tag1": []string{"value1"},
|
|
"tag2": []string{"value2"},
|
|
}
|
|
assert.Equal(t, len(expTags), len(w3.ConfigTags))
|
|
for k, v := range expTags {
|
|
assert.ElementsMatch(t, v, w3.ConfigTags[k])
|
|
}
|
|
assert.Equal(t, w3.ReleaseVersion, "Boundary v0.18.0")
|
|
assert.EqualValues(t, w3.OperationalState, server.ActiveOperationalState)
|
|
assert.EqualValues(t, w3.LocalStorageState, server.AvailableLocalStorageState)
|
|
|
|
// Now send the subsequent routing info request
|
|
req.WorkerStatus.PublicId = w3.PublicId // Only set on subsequent requests
|
|
resp, err = s.RoutingInfo(ctx, req)
|
|
require.NoError(t, err)
|
|
assert.NotNil(t, resp)
|
|
|
|
assert.Equal(t, resp.WorkerId, w3.PublicId)
|
|
assert.Equal(t, resp.CalculatedUpstreamAddresses, []string{c.Address})
|
|
assert.Equal(t, resp.AuthorizedDownstreamWorkers.UnmappedWorkerKeyIdentifiers, []string{w2KeyId})
|
|
assert.Equal(t, resp.AuthorizedDownstreamWorkers.WorkerPublicIds, []string{w1.PublicId})
|
|
|
|
w1, err = serverRepo.LookupWorker(ctx, resp.WorkerId)
|
|
require.NoError(t, err)
|
|
|
|
assert.Equal(t, w3.Address, "2.3.4.5:8080")
|
|
assert.Equal(t, len(expTags), len(w3.ConfigTags))
|
|
for k, v := range expTags {
|
|
assert.ElementsMatch(t, v, w3.ConfigTags[k])
|
|
}
|
|
assert.Equal(t, w3.ReleaseVersion, "Boundary v0.18.0")
|
|
assert.EqualValues(t, w3.OperationalState, server.ActiveOperationalState)
|
|
assert.EqualValues(t, w3.LocalStorageState, server.AvailableLocalStorageState)
|
|
})
|
|
}
|