From 02dc9be85fd878440f7e1d064046ee80e0df27c7 Mon Sep 17 00:00:00 2001 From: Michael Li Date: Mon, 31 Oct 2022 10:36:18 -0400 Subject: [PATCH 1/2] refact(e2e): Create role functions --- testing/internal/e2e/boundary/role.go | 56 +++++++++++++++++++ .../tests/static/session_cancel_user_test.go | 39 ++----------- 2 files changed, 60 insertions(+), 35 deletions(-) create mode 100644 testing/internal/e2e/boundary/role.go diff --git a/testing/internal/e2e/boundary/role.go b/testing/internal/e2e/boundary/role.go new file mode 100644 index 0000000000..4debcdfa35 --- /dev/null +++ b/testing/internal/e2e/boundary/role.go @@ -0,0 +1,56 @@ +package boundary + +import ( + "context" + "encoding/json" + "testing" + + "github.com/hashicorp/boundary/api/roles" + "github.com/hashicorp/boundary/testing/internal/e2e" + "github.com/stretchr/testify/require" +) + +// CreateNewRoleCli creates a new role using the cli. +// Returns the id of the new role. +func CreateNewRoleCli(t testing.TB, ctx context.Context, scopeId string) string { + output := e2e.RunCommand(ctx, "boundary", + e2e.WithArgs( + "roles", "create", + "-scope-id", scopeId, + "-name", "e2e Role", + "-format", "json", + ), + ) + require.NoError(t, output.Err, string(output.Stderr)) + var newRoleResult roles.RoleCreateResult + err := json.Unmarshal(output.Stdout, &newRoleResult) + require.NoError(t, err) + + newRoleId := newRoleResult.Item.Id + t.Logf("Created Role: %s", newRoleId) + return newRoleId +} + +// AddGrantToRoleCli adds a grant/permission to a role using the cli +func AddGrantToRoleCli(t testing.TB, ctx context.Context, roleId string, grant string) { + output := e2e.RunCommand(ctx, "boundary", + e2e.WithArgs( + "roles", "add-grants", + "-id", roleId, + "-grant", grant, + ), + ) + require.NoError(t, output.Err, string(output.Stderr)) +} + +// AddPrincipalToRoleCli adds a user/group to a role using the cli +func AddPrincipalToRoleCli(t testing.TB, ctx context.Context, roleId string, principal string) { + output := e2e.RunCommand(ctx, "boundary", + e2e.WithArgs( + "roles", "add-principals", + "-id", roleId, + "-principal", principal, + ), + ) + require.NoError(t, output.Err, string(output.Stderr)) +} 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 40148db72d..e82ce441e8 100644 --- a/testing/internal/e2e/tests/static/session_cancel_user_test.go +++ b/testing/internal/e2e/tests/static/session_cancel_user_test.go @@ -65,42 +65,11 @@ func TestCliSessionCancelUser(t *testing.T) { require.Equal(t, 403, response.Status) t.Log("Successfully received an error when connecting to target as a user without permissions") - // Create a role + // Create a role for user boundary.AuthenticateAdminCli(t, ctx) - output = e2e.RunCommand(ctx, "boundary", - e2e.WithArgs( - "roles", "create", - "-scope-id", newProjectId, - "-name", "e2e Role", - "-format", "json", - ), - ) - require.NoError(t, output.Err, string(output.Stderr)) - var newRoleResult roles.RoleCreateResult - err = json.Unmarshal(output.Stdout, &newRoleResult) - require.NoError(t, err) - newRoleId := newRoleResult.Item.Id - t.Logf("Created Role: %s", newRoleId) - - // Add grant to role - output = e2e.RunCommand(ctx, "boundary", - e2e.WithArgs( - "roles", "add-grants", - "-id", newRoleId, - "-grant", "id=*;type=target;actions=authorize-session", - ), - ) - require.NoError(t, output.Err, string(output.Stderr)) - - // Add user to role - output = e2e.RunCommand(ctx, "boundary", - e2e.WithArgs( - "roles", "add-principals", - "-id", newRoleId, - "-principal", newUserId, - ), - ) - require.NoError(t, output.Err, string(output.Stderr)) + 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()) From 48e615642849450917f407c019ed291d8943a1d8 Mon Sep 17 00:00:00 2001 From: Michael Li Date: Mon, 31 Oct 2022 10:56:50 -0400 Subject: [PATCH 2/2] test(e2e): Add test that creates a group --- .../tests/static/session_cancel_group_test.go | 244 ++++++++++++++++++ .../tests/static/session_cancel_user_test.go | 2 +- 2 files changed, 245 insertions(+), 1 deletion(-) create mode 100644 testing/internal/e2e/tests/static/session_cancel_group_test.go diff --git a/testing/internal/e2e/tests/static/session_cancel_group_test.go b/testing/internal/e2e/tests/static/session_cancel_group_test.go new file mode 100644 index 0000000000..979e1d57f3 --- /dev/null +++ b/testing/internal/e2e/tests/static/session_cancel_group_test.go @@ -0,0 +1,244 @@ +package static_test + +import ( + "context" + "encoding/json" + "errors" + "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" + "github.com/hashicorp/boundary/api/users" + "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" +) + +// TestCliSessionCancelGroup uses the cli to create a new user and sets up the right permissions for +// the user to connect to the created target (via a group). The test also confirms that an admin can +// cancel the user's session. +func TestCliSessionCancelGroup(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) + + // Try to connect to the target as a user without permissions + boundary.AuthenticateCli(t, ctx, acctName, acctPassword) + output := e2e.RunCommand(ctx, "boundary", + e2e.WithArgs( + "connect", + "-target-id", newTargetId, + "-format", "json", + "-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", + ), + ) + require.Error(t, output.Err, string(output.Stdout), string(output.Stderr)) + var response e2e.CliError + err = json.Unmarshal(output.Stderr, &response) + require.NoError(t, err) + require.Equal(t, 403, response.Status) + t.Log("Successfully received an error when connecting to target as a user without permissions") + + // Create a group + boundary.AuthenticateAdminCli(t, ctx) + output = e2e.RunCommand(ctx, "boundary", + e2e.WithArgs( + "groups", "create", + "-scope-id", "global", + "-format", "json", + ), + ) + require.NoError(t, output.Err, string(output.Stderr)) + var newGroupResult groups.GroupCreateResult + err = json.Unmarshal(output.Stdout, &newGroupResult) + require.NoError(t, err) + newGroupId := newGroupResult.Item.Id + t.Logf("Created Group: %s", newGroupId) + + // Add user to group + output = e2e.RunCommand(ctx, "boundary", + e2e.WithArgs( + "groups", "add-members", + "-id", newGroupId, + "-member", newUserId, + ), + ) + require.NoError(t, output.Err, string(output.Stderr)) + + // Create a role for a group + newRoleId := boundary.CreateNewRoleCli(t, ctx, newProjectId) + boundary.AddGrantToRoleCli(t, ctx, newRoleId, "id=*;type=target;actions=authorize-session") + boundary.AddPrincipalToRoleCli(t, ctx, newRoleId, newGroupId) + + // 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) + + // Get list of sessions + var session *sessions.Session + err = backoff.RetryNotify( + func() error { + 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) + } + + 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")) + } + + session = sessionListResult.Items[0] + 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) + assert.Equal(t, newTargetId, session.TargetId) + assert.Equal(t, newHostId, session.HostId) + require.Equal(t, "active", session.Status) + + // Cancel session + output = e2e.RunCommand(ctx, "boundary", + e2e.WithArgs("sessions", "cancel", "-id", session.Id), + ) + require.NoError(t, output.Err, string(output.Stderr)) + + output = e2e.RunCommand(ctx, "boundary", + e2e.WithArgs("sessions", "read", "-id", session.Id, "-format", "json"), + ) + require.NoError(t, output.Err, string(output.Stderr)) + var newSessionReadResult sessions.SessionReadResult + err = json.Unmarshal(output.Stdout, &newSessionReadResult) + require.NoError(t, err) + require.Condition(t, func() bool { + return newSessionReadResult.Item.Status == "canceling" || newSessionReadResult.Item.Status == "terminated" + }) + + // Check output from session + select { + case output := <-errChan: + // `boundary connect` returns a 255 when cancelled + require.Equal(t, output.ExitCode, 255, string(output.Stdout), string(output.Stderr)) + case <-time.After(time.Second * 5): + t.Fatal("Timed out waiting for session command to exit") + } + + t.Log("Successfully cancelled session") +} + +// TestApiCreateGroup uses the Go api to create a new group and add some grants to the group +func TestApiCreateGroup(t *testing.T) { + e2e.MaybeSkipTest(t) + c, err := loadConfig() + require.NoError(t, err) + + client, err := boundary.NewApiClient() + require.NoError(t, err) + ctx := context.Background() + + newOrgId := boundary.CreateNewOrgApi(t, ctx, client) + newProjectId := boundary.CreateNewProjectApi(t, ctx, client, newOrgId) + newHostCatalogId := boundary.CreateNewHostCatalogApi(t, ctx, client, newProjectId) + newHostSetId := boundary.CreateNewHostSetApi(t, ctx, client, newHostCatalogId) + newHostId := boundary.CreateNewHostApi(t, ctx, client, newHostCatalogId, c.TargetIp) + boundary.AddHostToHostSetApi(t, ctx, client, newHostSetId, newHostId) + newTargetId := boundary.CreateNewTargetApi(t, ctx, client, newProjectId, c.TargetPort) + boundary.AddHostSourceToTargetApi(t, ctx, client, newTargetId, newHostSetId) + + acctName := "e2e-account" + newAcctId, _ := boundary.CreateNewAccountApi(t, ctx, client, acctName) + newUserId := boundary.CreateNewUserApi(t, ctx, client, "global") + uClient := users.NewClient(client) + uClient.SetAccounts(ctx, newUserId, 0, []string{newAcctId}) + + gClient := groups.NewClient(client) + newGroupResult, err := gClient.Create(ctx, "global") + require.NoError(t, err) + newGroupId := newGroupResult.Item.Id + t.Logf("Created Group: %s", newGroupId) + + _, err = gClient.AddMembers(ctx, newGroupId, 0, []string{newUserId}, groups.WithAutomaticVersioning(true)) + require.NoError(t, err) + + rClient := roles.NewClient(client) + newRoleResult, err := rClient.Create(ctx, newProjectId) + require.NoError(t, err) + newRoleId := newRoleResult.Item.Id + t.Logf("Created Role: %s", newRoleId) + + _, err = rClient.AddGrants(ctx, newRoleId, 0, []string{"id=*;type=target;actions=authorize-session"}, + roles.WithAutomaticVersioning(true), + ) + require.NoError(t, err) + + _, err = rClient.AddPrincipals(ctx, newRoleId, 0, []string{newGroupId}, + roles.WithAutomaticVersioning(true), + ) + require.NoError(t, err) +} 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 e82ce441e8..6e8cb81573 100644 --- a/testing/internal/e2e/tests/static/session_cancel_user_test.go +++ b/testing/internal/e2e/tests/static/session_cancel_user_test.go @@ -195,7 +195,7 @@ func TestApiCreateUser(t *testing.T) { newRoleResult, err := rClient.Create(ctx, newProjectId) require.NoError(t, err) newRoleId := newRoleResult.Item.Id - t.Logf("Create Role: %s", newRoleId) + t.Logf("Created Role: %s", newRoleId) _, err = rClient.AddGrants(ctx, newRoleId, 0, []string{"id=*;type=target;actions=authorize-session"}, roles.WithAutomaticVersioning(true),