From 80cdbc305dfff43f8f53cb8e01ca1bebf9673b9a Mon Sep 17 00:00:00 2001 From: Michael Li Date: Wed, 2 Nov 2022 20:05:15 -0400 Subject: [PATCH] test(e2e): Check that an active session ends if the user is deleted (#2588) Adds a new test to the e2e test suite that checks that an active session ends once the user is deleted. --- testing/internal/e2e/boundary/session.go | 77 ++++++++++++ testing/internal/e2e/boundary/user.go | 12 +- .../tests/static/connect_localhost_test.go | 1 + .../e2e/tests/static/credential_store_test.go | 1 + .../tests/static/session_cancel_admin_test.go | 61 +--------- .../tests/static/session_cancel_group_test.go | 65 +---------- .../tests/static/session_cancel_user_test.go | 65 +---------- .../static/session_end_delete_user_test.go | 110 ++++++++++++++++++ 8 files changed, 207 insertions(+), 185 deletions(-) create mode 100644 testing/internal/e2e/boundary/session.go create mode 100644 testing/internal/e2e/tests/static/session_end_delete_user_test.go diff --git a/testing/internal/e2e/boundary/session.go b/testing/internal/e2e/boundary/session.go new file mode 100644 index 0000000000..e5720b8476 --- /dev/null +++ b/testing/internal/e2e/boundary/session.go @@ -0,0 +1,77 @@ +package boundary + +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/stretchr/testify/require" +) + +func WaitForSessionToBeActiveCli(t testing.TB, ctx context.Context, scopeId string) *sessions.Session { + t.Log("Waiting for session to be active...") + var session *sessions.Session + err := backoff.RetryNotify( + func() error { + // List sessions + output := e2e.RunCommand(ctx, "boundary", + e2e.WithArgs("sessions", "list", "-scope-id", scopeId, "-format", "json"), + ) + if output.Err != nil { + return backoff.Permanent(errors.New(string(output.Stderr))) + } + var sessionListResult sessions.SessionListResult + err := json.Unmarshal(output.Stdout, &sessionListResult) + if err != nil { + return backoff.Permanent(err) + } + + // Check if there is one session + sessionCount := len(sessionListResult.Items) + if sessionCount == 0 { + return errors.New("No items are appearing in the session list") + } + t.Logf("Found %d session(s)", sessionCount) + if sessionCount != 1 { + return backoff.Permanent(errors.New("Only one session was expected to be found")) + } + + // Check if session is active + session = sessionListResult.Items[0] + 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 != "active" { + return errors.New(fmt.Sprintf("Waiting for session to be active... Expected: %s, Actual: %s", + "active", + 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) + + return session +} diff --git a/testing/internal/e2e/boundary/user.go b/testing/internal/e2e/boundary/user.go index b018811d52..ff9ba748f5 100644 --- a/testing/internal/e2e/boundary/user.go +++ b/testing/internal/e2e/boundary/user.go @@ -49,9 +49,17 @@ func CreateNewUserCli(t testing.TB, ctx context.Context, scopeId string) string t.Cleanup(func() { AuthenticateAdminCli(t, context.Background()) output := e2e.RunCommand(ctx, "boundary", - e2e.WithArgs("users", "delete", "-id", newUserId), + e2e.WithArgs("users", "delete", "-id", newUserId, "-format", "json"), ) - require.NoError(t, output.Err, string(output.Stderr)) + // Only allow an error if it's due to a Resource Not Found (404) + // This was needed since there's a test that already deletes the user and would run into an + // error during cleanup + if output.Err != nil { + var response e2e.CliError + err := json.Unmarshal(output.Stderr, &response) + require.NoError(t, err) + require.Equal(t, 404, int(response.Status)) + } }) t.Logf("Created User: %s", newUserId) diff --git a/testing/internal/e2e/tests/static/connect_localhost_test.go b/testing/internal/e2e/tests/static/connect_localhost_test.go index 599e8ad9cb..a814dbbd8d 100644 --- a/testing/internal/e2e/tests/static/connect_localhost_test.go +++ b/testing/internal/e2e/tests/static/connect_localhost_test.go @@ -50,6 +50,7 @@ func TestCliConnectTargetWithLocalhost(t *testing.T) { t.Cleanup(cancel) // Wait for session to appear + t.Log("Waiting for session to appear...") err = backoff.RetryNotify( func() error { output := e2e.RunCommand(ctx, "boundary", diff --git a/testing/internal/e2e/tests/static/credential_store_test.go b/testing/internal/e2e/tests/static/credential_store_test.go index 8006341272..ddf144a4ae 100644 --- a/testing/internal/e2e/tests/static/credential_store_test.go +++ b/testing/internal/e2e/tests/static/credential_store_test.go @@ -109,6 +109,7 @@ func TestCliStaticCredentialStore(t *testing.T) { e2e.WithArgs("credential-stores", "delete", "-id", newCredentialStoreId), ) require.NoError(t, output.Err, string(output.Stderr)) + t.Log("Waiting for credential store to be deleted...") err = backoff.RetryNotify( func() error { output := e2e.RunCommand(ctx, "boundary", diff --git a/testing/internal/e2e/tests/static/session_cancel_admin_test.go b/testing/internal/e2e/tests/static/session_cancel_admin_test.go index 2e55a9563f..de801b0563 100644 --- a/testing/internal/e2e/tests/static/session_cancel_admin_test.go +++ b/testing/internal/e2e/tests/static/session_cancel_admin_test.go @@ -3,12 +3,9 @@ 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" @@ -55,66 +52,12 @@ func TestCliSessionCancelAdmin(t *testing.T) { ) }() t.Cleanup(cancel) - - // Wait for session to be active - var session *sessions.Session - err = backoff.RetryNotify( - func() error { - // List sessions - output := e2e.RunCommand(ctx, "boundary", - e2e.WithArgs("sessions", "list", "-scope-id", newProjectId, "-format", "json"), - ) - if output.Err != nil { - return backoff.Permanent(errors.New(string(output.Stderr))) - } - var sessionListResult sessions.SessionListResult - err := json.Unmarshal(output.Stdout, &sessionListResult) - if err != nil { - return backoff.Permanent(err) - } - - // Check if there is one session - sessionCount := len(sessionListResult.Items) - if sessionCount == 0 { - return errors.New("No items are appearing in the session list") - } - t.Logf("Found %d session(s)", sessionCount) - if sessionCount != 1 { - return backoff.Permanent(errors.New("Only one session was expected to be found")) - } - - // Check if session is active - session = sessionListResult.Items[0] - 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 != "active" { - return errors.New(fmt.Sprintf("Waiting for session to be active... Expected: %s, Actual: %s", - "active", - 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) + session := boundary.WaitForSessionToBeActiveCli(t, ctx, newProjectId) assert.Equal(t, newTargetId, session.TargetId) assert.Equal(t, newHostId, session.HostId) // Cancel session + t.Log("Canceling session...") output := e2e.RunCommand(ctx, "boundary", e2e.WithArgs("sessions", "cancel", "-id", session.Id), ) diff --git a/testing/internal/e2e/tests/static/session_cancel_group_test.go b/testing/internal/e2e/tests/static/session_cancel_group_test.go index 024889cb8f..41c70ce709 100644 --- a/testing/internal/e2e/tests/static/session_cancel_group_test.go +++ b/testing/internal/e2e/tests/static/session_cancel_group_test.go @@ -3,12 +3,9 @@ package static_test import ( "context" "encoding/json" - "errors" - "fmt" "testing" "time" - "github.com/cenkalti/backoff/v4" "github.com/hashicorp/boundary/api/groups" "github.com/hashicorp/boundary/api/roles" "github.com/hashicorp/boundary/api/sessions" @@ -64,7 +61,7 @@ func TestCliSessionCancelGroup(t *testing.T) { var response e2e.CliError err = json.Unmarshal(output.Stderr, &response) require.NoError(t, err) - require.Equal(t, 403, response.Status) + require.Equal(t, 403, int(response.Status)) t.Log("Successfully received an error when connecting to target as a user without permissions") // Create a group @@ -123,68 +120,12 @@ func TestCliSessionCancelGroup(t *testing.T) { ) }() t.Cleanup(cancel) - - // Wait for session to be active - var session *sessions.Session - err = backoff.RetryNotify( - func() error { - // List sessions - output := e2e.RunCommand(ctx, "boundary", - e2e.WithArgs("sessions", "list", "-scope-id", newProjectId, "-format", "json"), - ) - if output.Err != nil { - return backoff.Permanent(errors.New(string(output.Stderr))) - } - var sessionListResult sessions.SessionListResult - err := json.Unmarshal(output.Stdout, &sessionListResult) - if err != nil { - return backoff.Permanent(err) - } - - // Check if there is one session - sessionCount := len(sessionListResult.Items) - if sessionCount == 0 { - return errors.New("No items are appearing in the session list") - } - t.Logf("Found %d session(s)", sessionCount) - if sessionCount != 1 { - return backoff.Permanent(errors.New("Only one session was expected to be found")) - } - - // Check if session is active - session = sessionListResult.Items[0] - 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 != "active" { - return errors.New(fmt.Sprintf("Waiting for session to be active... Expected: %s, Actual: %s", - "active", - 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) + session := boundary.WaitForSessionToBeActiveCli(t, ctx, newProjectId) assert.Equal(t, newTargetId, session.TargetId) assert.Equal(t, newHostId, session.HostId) // Cancel session + t.Log("Canceling session...") output = e2e.RunCommand(ctx, "boundary", e2e.WithArgs("sessions", "cancel", "-id", session.Id), ) diff --git a/testing/internal/e2e/tests/static/session_cancel_user_test.go b/testing/internal/e2e/tests/static/session_cancel_user_test.go index fee9aa0081..90c213b0e9 100644 --- a/testing/internal/e2e/tests/static/session_cancel_user_test.go +++ b/testing/internal/e2e/tests/static/session_cancel_user_test.go @@ -3,12 +3,9 @@ package static_test import ( "context" "encoding/json" - "errors" - "fmt" "testing" "time" - "github.com/cenkalti/backoff/v4" "github.com/hashicorp/boundary/api/roles" "github.com/hashicorp/boundary/api/sessions" "github.com/hashicorp/boundary/api/users" @@ -63,7 +60,7 @@ func TestCliSessionCancelUser(t *testing.T) { var response e2e.CliError err = json.Unmarshal(output.Stderr, &response) require.NoError(t, err) - require.Equal(t, 403, response.Status) + require.Equal(t, 403, int(response.Status)) t.Log("Successfully received an error when connecting to target as a user without permissions") // Create a role for user @@ -97,68 +94,12 @@ func TestCliSessionCancelUser(t *testing.T) { ) }() t.Cleanup(cancel) - - // Wait for session to be active - var session *sessions.Session - err = backoff.RetryNotify( - func() error { - // List sessions - output := e2e.RunCommand(ctx, "boundary", - e2e.WithArgs("sessions", "list", "-scope-id", newProjectId, "-format", "json"), - ) - if output.Err != nil { - return backoff.Permanent(errors.New(string(output.Stderr))) - } - var sessionListResult sessions.SessionListResult - err := json.Unmarshal(output.Stdout, &sessionListResult) - if err != nil { - return backoff.Permanent(err) - } - - // Check if there is one session - sessionCount := len(sessionListResult.Items) - if sessionCount == 0 { - return errors.New("No items are appearing in the session list") - } - t.Logf("Found %d session(s)", sessionCount) - if sessionCount != 1 { - return backoff.Permanent(errors.New("Only one session was expected to be found")) - } - - // Check if session is active - session = sessionListResult.Items[0] - 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 != "active" { - return errors.New(fmt.Sprintf("Waiting for session to be active... Expected: %s, Actual: %s", - "active", - 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) + session := boundary.WaitForSessionToBeActiveCli(t, ctx, newProjectId) assert.Equal(t, newTargetId, session.TargetId) assert.Equal(t, newHostId, session.HostId) // Cancel session + t.Log("Canceling session...") output = e2e.RunCommand(ctx, "boundary", e2e.WithArgs("sessions", "cancel", "-id", session.Id), ) 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 new file mode 100644 index 0000000000..a71f45859b --- /dev/null +++ b/testing/internal/e2e/tests/static/session_end_delete_user_test.go @@ -0,0 +1,110 @@ +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" +) + +func TestCliSessionEndWhenUserIsDeleted(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) + 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) + newUserId := boundary.CreateNewUserCli(t, ctx, "global") + 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 User + t.Log("Deleting user...") + output := e2e.RunCommand(ctx, "boundary", e2e.WithArgs("users", "delete", "-id", newUserId)) + require.NoError(t, output.Err, string(output.Stderr)) + + // Check is 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 user was deleted") +}