From 0eb7c04c1db16335c37126e153ceba886f1ebbc6 Mon Sep 17 00:00:00 2001 From: Michael Li Date: Thu, 8 Dec 2022 18:27:52 -0500 Subject: [PATCH] test(e2e): Add tests to validate that session ends after host source is deleted (#2689) --- .../session_end_delete_host_set_test.go | 132 ++++++++++++++++++ .../static/session_end_delete_host_test.go | 132 ++++++++++++++++++ .../static/session_end_delete_user_test.go | 11 +- 3 files changed, 274 insertions(+), 1 deletion(-) create mode 100644 testing/internal/e2e/tests/static/session_end_delete_host_set_test.go create mode 100644 testing/internal/e2e/tests/static/session_end_delete_host_test.go diff --git a/testing/internal/e2e/tests/static/session_end_delete_host_set_test.go b/testing/internal/e2e/tests/static/session_end_delete_host_set_test.go new file mode 100644 index 0000000000..5a04a66f93 --- /dev/null +++ b/testing/internal/e2e/tests/static/session_end_delete_host_set_test.go @@ -0,0 +1,132 @@ +package static_test + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "testing" + "time" + + "github.com/cenkalti/backoff/v4" + "github.com/hashicorp/boundary/api/sessions" + "github.com/hashicorp/boundary/testing/internal/e2e" + "github.com/hashicorp/boundary/testing/internal/e2e/boundary" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// TestCliSessionEndWhenHostSetIsDeleted tests that an active session is canceled when the respective +// host set for the session is deleted. +func TestCliSessionEndWhenHostSetIsDeleted(t *testing.T) { + e2e.MaybeSkipTest(t) + c, err := loadConfig() + require.NoError(t, err) + + ctx := context.Background() + boundary.AuthenticateAdminCli(t, ctx) + newOrgId := boundary.CreateNewOrgCli(t, ctx) + t.Cleanup(func() { + ctx := context.Background() + boundary.AuthenticateAdminCli(t, ctx) + output := e2e.RunCommand(ctx, "boundary", e2e.WithArgs("scopes", "delete", "-id", newOrgId)) + require.NoError(t, output.Err, string(output.Stderr)) + }) + newProjectId := boundary.CreateNewProjectCli(t, ctx, newOrgId) + newHostCatalogId := boundary.CreateNewHostCatalogCli(t, ctx, newProjectId) + newHostSetId := boundary.CreateNewHostSetCli(t, ctx, newHostCatalogId) + newHostId := boundary.CreateNewHostCli(t, ctx, newHostCatalogId, c.TargetIp) + boundary.AddHostToHostSetCli(t, ctx, newHostSetId, newHostId) + newTargetId := boundary.CreateNewTargetCli(t, ctx, newProjectId, c.TargetPort) + boundary.AddHostSourceToTargetCli(t, ctx, newTargetId, newHostSetId) + acctName := "e2e-account" + newAccountId, acctPassword := boundary.CreateNewAccountCli(t, ctx, acctName) + t.Cleanup(func() { + boundary.AuthenticateAdminCli(t, context.Background()) + output := e2e.RunCommand(ctx, "boundary", + e2e.WithArgs("accounts", "delete", "-id", newAccountId), + ) + require.NoError(t, output.Err, string(output.Stderr)) + }) + newUserId := boundary.CreateNewUserCli(t, ctx, "global") + t.Cleanup(func() { + boundary.AuthenticateAdminCli(t, context.Background()) + output := e2e.RunCommand(ctx, "boundary", + e2e.WithArgs("users", "delete", "-id", newUserId), + ) + require.NoError(t, output.Err, string(output.Stderr)) + }) + boundary.SetAccountToUserCli(t, ctx, newUserId, newAccountId) + newRoleId := boundary.CreateNewRoleCli(t, ctx, newProjectId) + boundary.AddGrantToRoleCli(t, ctx, newRoleId, "id=*;type=target;actions=authorize-session") + boundary.AddPrincipalToRoleCli(t, ctx, newRoleId, newUserId) + + // Connect to target to create a session + ctxCancel, cancel := context.WithCancel(context.Background()) + errChan := make(chan *e2e.CommandResult) + go func() { + token := boundary.GetAuthenticationTokenCli(t, ctx, acctName, acctPassword) + t.Log("Starting session as user...") + errChan <- e2e.RunCommand(ctxCancel, "boundary", + e2e.WithArgs( + "connect", + "-token", "env://E2E_AUTH_TOKEN", + "-target-id", newTargetId, + "-exec", "/usr/bin/ssh", "--", + "-l", c.TargetSshUser, + "-i", c.TargetSshKeyPath, + "-o", "UserKnownHostsFile=/dev/null", + "-o", "StrictHostKeyChecking=no", + "-o", "IdentitiesOnly=yes", // forces the use of the provided key + "-p", "{{boundary.port}}", // this is provided by boundary + "{{boundary.ip}}", + "hostname -i; sleep 60", + ), + e2e.WithEnv("E2E_AUTH_TOKEN", token), + ) + }() + t.Cleanup(cancel) + session := boundary.WaitForSessionToBeActiveCli(t, ctx, newProjectId) + assert.Equal(t, newTargetId, session.TargetId) + assert.Equal(t, newHostId, session.HostId) + + // Delete Host Set + t.Log("Deleting host set...") + output := e2e.RunCommand(ctx, "boundary", e2e.WithArgs("host-sets", "delete", "-id", newHostSetId)) + require.NoError(t, output.Err, string(output.Stderr)) + + // Check if session has terminated + t.Log("Waiting for session to be canceling/terminated...") + err = backoff.RetryNotify( + func() error { + // Check if session is active + output = e2e.RunCommand(ctx, "boundary", + e2e.WithArgs("sessions", "read", "-id", session.Id, "-format", "json"), + ) + if output.Err != nil { + return backoff.Permanent(errors.New(string(output.Stderr))) + } + + var sessionReadResult sessions.SessionReadResult + err = json.Unmarshal(output.Stdout, &sessionReadResult) + if err != nil { + return backoff.Permanent(err) + } + + if sessionReadResult.Item.Status != "canceling" && sessionReadResult.Item.Status != "terminated" { + return errors.New(fmt.Sprintf("Session has unexpected status. Expected: %s, Actual: %s", + "`canceling` or `terminated`", + sessionReadResult.Item.Status, + )) + } + + return nil + }, + backoff.WithMaxRetries(backoff.NewConstantBackOff(3*time.Second), 5), + func(err error, td time.Duration) { + t.Logf("%s. Retrying...", err.Error()) + }, + ) + require.NoError(t, err) + t.Log("Session successfully ended after host set was deleted") +} diff --git a/testing/internal/e2e/tests/static/session_end_delete_host_test.go b/testing/internal/e2e/tests/static/session_end_delete_host_test.go new file mode 100644 index 0000000000..472d339fe7 --- /dev/null +++ b/testing/internal/e2e/tests/static/session_end_delete_host_test.go @@ -0,0 +1,132 @@ +package static_test + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "testing" + "time" + + "github.com/cenkalti/backoff/v4" + "github.com/hashicorp/boundary/api/sessions" + "github.com/hashicorp/boundary/testing/internal/e2e" + "github.com/hashicorp/boundary/testing/internal/e2e/boundary" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// TestCliSessionEndWhenHostIsDeleted tests that an active session is canceled when the respective +// host set for the session is deleted. +func TestCliSessionEndWhenHostIsDeleted(t *testing.T) { + e2e.MaybeSkipTest(t) + c, err := loadConfig() + require.NoError(t, err) + + ctx := context.Background() + boundary.AuthenticateAdminCli(t, ctx) + newOrgId := boundary.CreateNewOrgCli(t, ctx) + t.Cleanup(func() { + ctx := context.Background() + boundary.AuthenticateAdminCli(t, ctx) + output := e2e.RunCommand(ctx, "boundary", e2e.WithArgs("scopes", "delete", "-id", newOrgId)) + require.NoError(t, output.Err, string(output.Stderr)) + }) + newProjectId := boundary.CreateNewProjectCli(t, ctx, newOrgId) + newHostCatalogId := boundary.CreateNewHostCatalogCli(t, ctx, newProjectId) + newHostSetId := boundary.CreateNewHostSetCli(t, ctx, newHostCatalogId) + newHostId := boundary.CreateNewHostCli(t, ctx, newHostCatalogId, c.TargetIp) + boundary.AddHostToHostSetCli(t, ctx, newHostSetId, newHostId) + newTargetId := boundary.CreateNewTargetCli(t, ctx, newProjectId, c.TargetPort) + boundary.AddHostSourceToTargetCli(t, ctx, newTargetId, newHostSetId) + acctName := "e2e-account" + newAccountId, acctPassword := boundary.CreateNewAccountCli(t, ctx, acctName) + t.Cleanup(func() { + boundary.AuthenticateAdminCli(t, context.Background()) + output := e2e.RunCommand(ctx, "boundary", + e2e.WithArgs("accounts", "delete", "-id", newAccountId), + ) + require.NoError(t, output.Err, string(output.Stderr)) + }) + newUserId := boundary.CreateNewUserCli(t, ctx, "global") + t.Cleanup(func() { + boundary.AuthenticateAdminCli(t, context.Background()) + output := e2e.RunCommand(ctx, "boundary", + e2e.WithArgs("users", "delete", "-id", newUserId), + ) + require.NoError(t, output.Err, string(output.Stderr)) + }) + boundary.SetAccountToUserCli(t, ctx, newUserId, newAccountId) + newRoleId := boundary.CreateNewRoleCli(t, ctx, newProjectId) + boundary.AddGrantToRoleCli(t, ctx, newRoleId, "id=*;type=target;actions=authorize-session") + boundary.AddPrincipalToRoleCli(t, ctx, newRoleId, newUserId) + + // Connect to target to create a session + ctxCancel, cancel := context.WithCancel(context.Background()) + errChan := make(chan *e2e.CommandResult) + go func() { + token := boundary.GetAuthenticationTokenCli(t, ctx, acctName, acctPassword) + t.Log("Starting session as user...") + errChan <- e2e.RunCommand(ctxCancel, "boundary", + e2e.WithArgs( + "connect", + "-token", "env://E2E_AUTH_TOKEN", + "-target-id", newTargetId, + "-exec", "/usr/bin/ssh", "--", + "-l", c.TargetSshUser, + "-i", c.TargetSshKeyPath, + "-o", "UserKnownHostsFile=/dev/null", + "-o", "StrictHostKeyChecking=no", + "-o", "IdentitiesOnly=yes", // forces the use of the provided key + "-p", "{{boundary.port}}", // this is provided by boundary + "{{boundary.ip}}", + "hostname -i; sleep 60", + ), + e2e.WithEnv("E2E_AUTH_TOKEN", token), + ) + }() + t.Cleanup(cancel) + session := boundary.WaitForSessionToBeActiveCli(t, ctx, newProjectId) + assert.Equal(t, newTargetId, session.TargetId) + assert.Equal(t, newHostId, session.HostId) + + // Delete Host + t.Log("Deleting host...") + output := e2e.RunCommand(ctx, "boundary", e2e.WithArgs("hosts", "delete", "-id", newHostId)) + require.NoError(t, output.Err, string(output.Stderr)) + + // Check if session has terminated + t.Log("Waiting for session to be canceling/terminated...") + err = backoff.RetryNotify( + func() error { + // Check if session is active + output = e2e.RunCommand(ctx, "boundary", + e2e.WithArgs("sessions", "read", "-id", session.Id, "-format", "json"), + ) + if output.Err != nil { + return backoff.Permanent(errors.New(string(output.Stderr))) + } + + var sessionReadResult sessions.SessionReadResult + err = json.Unmarshal(output.Stdout, &sessionReadResult) + if err != nil { + return backoff.Permanent(err) + } + + if sessionReadResult.Item.Status != "canceling" && sessionReadResult.Item.Status != "terminated" { + return errors.New(fmt.Sprintf("Session has unexpected status. Expected: %s, Actual: %s", + "`canceling` or `terminated`", + sessionReadResult.Item.Status, + )) + } + + return nil + }, + backoff.WithMaxRetries(backoff.NewConstantBackOff(3*time.Second), 5), + func(err error, td time.Duration) { + t.Logf("%s. Retrying...", err.Error()) + }, + ) + require.NoError(t, err) + t.Log("Session successfully ended after host was deleted") +} diff --git a/testing/internal/e2e/tests/static/session_end_delete_user_test.go b/testing/internal/e2e/tests/static/session_end_delete_user_test.go index 108e078ea8..faf1080db7 100644 --- a/testing/internal/e2e/tests/static/session_end_delete_user_test.go +++ b/testing/internal/e2e/tests/static/session_end_delete_user_test.go @@ -16,6 +16,8 @@ import ( "github.com/stretchr/testify/require" ) +// TestCliSessionEndWhenUserIsDeleted tests that an active session is canceled when the respective +// user who started the session is deleted. func TestCliSessionEndWhenUserIsDeleted(t *testing.T) { e2e.MaybeSkipTest(t) c, err := loadConfig() @@ -39,6 +41,13 @@ func TestCliSessionEndWhenUserIsDeleted(t *testing.T) { boundary.AddHostSourceToTargetCli(t, ctx, newTargetId, newHostSetId) acctName := "e2e-account" newAccountId, acctPassword := boundary.CreateNewAccountCli(t, ctx, acctName) + t.Cleanup(func() { + boundary.AuthenticateAdminCli(t, context.Background()) + output := e2e.RunCommand(ctx, "boundary", + e2e.WithArgs("accounts", "delete", "-id", newAccountId), + ) + require.NoError(t, output.Err, string(output.Stderr)) + }) newUserId := boundary.CreateNewUserCli(t, ctx, "global") boundary.SetAccountToUserCli(t, ctx, newUserId, newAccountId) newRoleId := boundary.CreateNewRoleCli(t, ctx, newProjectId) @@ -79,7 +88,7 @@ func TestCliSessionEndWhenUserIsDeleted(t *testing.T) { output := e2e.RunCommand(ctx, "boundary", e2e.WithArgs("users", "delete", "-id", newUserId)) require.NoError(t, output.Err, string(output.Stderr)) - // Check is session has terminated + // Check if session has terminated t.Log("Waiting for session to be canceling/terminated...") err = backoff.RetryNotify( func() error {