From 8ad861f426e9431a793bc1158b62b44fb76dd2e8 Mon Sep 17 00:00:00 2001 From: Michael Li Date: Tue, 7 Feb 2023 14:00:05 -0500 Subject: [PATCH] test(e2e): Update migration test to use vault docker container (#2909) * fix(enos): Make sure enos reports test as failure After updating this command to save off the output from running `go test`, enos would not report when tests failed. This was due to the extra pipe hiding the exit status of the prior commands. Adding this option should return the status of the last command to exit with a non-zero status. * doc(e2e): Fix comments * refact(e2e): Update AuthenticateCli to accept auth method id * refact(e2e/migration): Use docker to set up vault instead of enos * refact(e2e/migration): Adjust where environment variables are set --- enos/enos-scenario-e2e-database.hcl | 24 ------- enos/modules/test_e2e/main.tf | 2 +- testing/internal/e2e/boundary/authenticate.go | 6 +- testing/internal/e2e/infra/docker.go | 39 ++++++++++- .../e2e/tests/database/migration_test.go | 66 +++++++++++-------- .../tests/static/session_cancel_group_test.go | 4 +- .../tests/static/session_cancel_user_test.go | 4 +- 7 files changed, 83 insertions(+), 62 deletions(-) diff --git a/enos/enos-scenario-e2e-database.hcl b/enos/enos-scenario-e2e-database.hcl index d9b4d9cf3..364657cbe 100644 --- a/enos/enos-scenario-e2e-database.hcl +++ b/enos/enos-scenario-e2e-database.hcl @@ -88,33 +88,11 @@ scenario "e2e_database" { } } - step "create_vault_cluster" { - module = module.vault - depends_on = [ - step.create_base_infra, - ] - - variables { - ami_id = step.create_base_infra.ami_ids["ubuntu"]["amd64"] - instance_type = var.vault_instance_type - instance_count = 1 - kms_key_arn = step.create_base_infra.kms_key_arn - storage_backend = "raft" - unseal_method = "awskms" - vault_release = { - version = var.vault_version - edition = "oss" - } - vpc_id = step.create_base_infra.vpc_id - } - } - step "run_e2e_test" { module = module.test_e2e depends_on = [ step.create_targets_with_tag, step.iam_setup, - step.create_vault_cluster, ] variables { @@ -125,8 +103,6 @@ scenario "e2e_database" { aws_access_key_id = step.iam_setup.access_key_id aws_secret_access_key = step.iam_setup.secret_access_key aws_host_set_filter1 = step.create_tag_inputs.tag_string - vault_addr = step.create_vault_cluster.instance_public_ips[0] - vault_root_token = step.create_vault_cluster.vault_root_token } } diff --git a/enos/modules/test_e2e/main.tf b/enos/modules/test_e2e/main.tf index 2321114a7..f3651ca4a 100644 --- a/enos/modules/test_e2e/main.tf +++ b/enos/modules/test_e2e/main.tf @@ -126,7 +126,7 @@ resource "enos_local_exec" "run_e2e_test" { E2E_AWS_HOST_SET_IPS2 = local.aws_host_set_ips2 } - inline = ["PATH=\"${var.local_boundary_dir}:$PATH\" go test -v ${var.test_package} -count=1 -json | tparse -follow -format plain 2>&1 | tee ${path.module}/../../test-e2e-${local.package_name}.out"] + inline = ["set -o pipefail; PATH=\"${var.local_boundary_dir}:$PATH\" go test -v ${var.test_package} -count=1 -json | tparse -follow -format plain 2>&1 | tee ${path.module}/../../test-e2e-${local.package_name}.out"] } output "test_results" { diff --git a/testing/internal/e2e/boundary/authenticate.go b/testing/internal/e2e/boundary/authenticate.go index 19f4f570b..1b06914bc 100644 --- a/testing/internal/e2e/boundary/authenticate.go +++ b/testing/internal/e2e/boundary/authenticate.go @@ -49,11 +49,11 @@ func AuthenticateAdminCli(t testing.TB, ctx context.Context) { c, err := LoadConfig() require.NoError(t, err) - AuthenticateCli(t, ctx, c.AdminLoginName, c.AdminLoginPassword) + AuthenticateCli(t, ctx, c.AuthMethodId, c.AdminLoginName, c.AdminLoginPassword) } // AuthenticateCli uses the cli to authenticate the specified Boundary instance -func AuthenticateCli(t testing.TB, ctx context.Context, loginName string, password string) { +func AuthenticateCli(t testing.TB, ctx context.Context, authMethodId string, loginName string, password string) { c, err := LoadConfig() require.NoError(t, err) @@ -61,7 +61,7 @@ func AuthenticateCli(t testing.TB, ctx context.Context, loginName string, passwo e2e.WithArgs( "authenticate", "password", "-addr", c.Address, - "-auth-method-id", c.AuthMethodId, + "-auth-method-id", authMethodId, "-login-name", loginName, "-password", "env://E2E_TEST_BOUNDARY_PASSWORD", ), diff --git a/testing/internal/e2e/infra/docker.go b/testing/internal/e2e/infra/docker.go index 335b69643..9eee3dbc5 100644 --- a/testing/internal/e2e/infra/docker.go +++ b/testing/internal/e2e/infra/docker.go @@ -100,7 +100,8 @@ func InitBoundaryDatabase(t testing.TB, pool *dockertest.Pool, network *dockerte } // GetDbInitInfoFromContainer extracts info from calling `boundary database init` in the specified -// container. Returns a struct containing the generated info. +// container. +// Returns a struct containing the generated info. func GetDbInitInfoFromContainer(t testing.TB, pool *dockertest.Pool, container *Container) boundary.DbInitInfo { _, err := pool.Client.WaitContainer(container.Resource.Container.ID) require.NoError(t, err) @@ -125,8 +126,8 @@ func GetDbInitInfoFromContainer(t testing.TB, pool *dockertest.Pool, container * } // StartBoundary starts a boundary container and spins up an instance of boundary using the -// specified database at postgresURI. Returns information about the container. -// Returns information about the container +// specified database at postgresURI. +// Returns information about the container. func StartBoundary(t testing.TB, pool *dockertest.Pool, network *dockertest.Network, postgresURI string) *Container { t.Log("Starting Boundary...") @@ -163,6 +164,38 @@ func StartBoundary(t testing.TB, pool *dockertest.Pool, network *dockertest.Netw } } +// StartVault starts a vault container. +// Returns information about the container. +func StartVault(t testing.TB, pool *dockertest.Pool, network *dockertest.Network) (*Container, string) { + t.Log("Starting Vault...") + + vaultToken := "boundarytok" + resource, err := pool.RunWithOptions(&dockertest.RunOptions{ + Repository: "hashicorp/vault", + Tag: "latest", + Env: []string{ + "VAULT_DEV_ROOT_TOKEN_ID=" + vaultToken, + }, + Name: "vault", + Networks: []*dockertest.Network{network}, + ExposedPorts: []string{"8200"}, + PortBindings: map[docker.Port][]docker.PortBinding{ + "8200/tcp": {{HostIP: "localhost", HostPort: "8200/tcp"}}, + }, + CapAdd: []string{"IPC_LOCK"}, + }) + require.NoError(t, err) + + uriLocalhost := "http://localhost:8200" + + return &Container{ + Resource: resource, + UriLocalhost: uriLocalhost, + UriNetwork: "http://vault:8200", + }, + vaultToken +} + // ConnectToTarget starts a boundary container and attempts to connect to the specified target. The // goal of this method is to create a session entry in the database. // Returns information about the container. diff --git a/testing/internal/e2e/tests/database/migration_test.go b/testing/internal/e2e/tests/database/migration_test.go index a779f51ec..984c30536 100644 --- a/testing/internal/e2e/tests/database/migration_test.go +++ b/testing/internal/e2e/tests/database/migration_test.go @@ -32,6 +32,7 @@ type TestEnvironment struct { Boundary *infra.Container Database *infra.Container Target *infra.Container + Vault *infra.Container DbInitInfo boundary.DbInitInfo } @@ -70,13 +71,19 @@ func setupEnvironment(t testing.TB, ctx context.Context, c *config) TestEnvironm require.NoError(t, err) err = pool.Client.Ping() require.NoError(t, err) + pool.MaxWait = 10 * time.Second - // This ensures that the latest Boundary image is used + // This ensures that the latest images are used err = pool.Client.PullImage(docker.PullImageOptions{ Repository: "hashicorp/boundary", Tag: "latest", }, docker.AuthConfiguration{}) require.NoError(t, err) + err = pool.Client.PullImage(docker.PullImageOptions{ + Repository: "hashicorp/vault", + Tag: "latest", + }, docker.AuthConfiguration{}) + require.NoError(t, err) // Set up docker network network, err := pool.CreateNetwork(t.Name()) @@ -85,13 +92,34 @@ func setupEnvironment(t testing.TB, ctx context.Context, c *config) TestEnvironm require.NoError(t, pool.RemoveNetwork(network)) }) + // Start Vault + v, vaultToken := infra.StartVault(t, pool, network) + t.Cleanup(func() { + pool.Purge(v.Resource) + }) + os.Setenv("VAULT_ADDR", v.UriLocalhost) + os.Setenv("VAULT_TOKEN", vaultToken) + t.Log("Waiting for Vault to finish loading...") + err = pool.Retry(func() error { + response, err := http.Get(v.UriLocalhost) + if err != nil { + t.Logf("Could not access Vault URL: %s. Retrying...", err.Error()) + return err + } + + if response.StatusCode != http.StatusOK { + return fmt.Errorf("Could not connect to %s. Status Code: %d", v.UriLocalhost, response.StatusCode) + } + + return nil + }) + require.NoError(t, err) + // Start a Boundary database and wait until it's ready db := infra.StartBoundaryDatabase(t, pool, network) t.Cleanup(func() { pool.Purge(db.Resource) }) - - pool.MaxWait = 10 * time.Second t.Log("Waiting for database to load...") err = pool.Retry(func() error { db, err := sql.Open("pgx", db.UriLocalhost) @@ -120,6 +148,7 @@ func setupEnvironment(t testing.TB, ctx context.Context, c *config) TestEnvironm t.Cleanup(func() { pool.Purge(b.Resource) }) + os.Setenv("BOUNDARY_ADDR", b.UriLocalhost) buf := bytes.NewBuffer(nil) ebuf := bytes.NewBuffer(nil) @@ -153,20 +182,16 @@ func setupEnvironment(t testing.TB, ctx context.Context, c *config) TestEnvironm Boundary: b, Database: db, Target: target, + Vault: v, DbInitInfo: dbInitInfo, } } func populateBoundaryDatabase(t testing.TB, ctx context.Context, c *config, te TestEnvironment) { - os.Setenv("BOUNDARY_ADDR", te.Boundary.UriLocalhost) - os.Setenv("E2E_PASSWORD_AUTH_METHOD_ID", te.DbInitInfo.AuthMethod.AuthMethodId) - os.Setenv("E2E_PASSWORD_ADMIN_LOGIN_NAME", te.DbInitInfo.AuthMethod.LoginName) - os.Setenv("E2E_PASSWORD_ADMIN_PASSWORD", te.DbInitInfo.AuthMethod.Password) - // Create resources for target. Uses the local CLI so that these methods can be reused. // While the CLI version used won't necessarily match the controller version, it should be (and is // supposed to be) backwards ompatible - boundary.AuthenticateAdminCli(t, ctx) + boundary.AuthenticateCli(t, ctx, te.DbInitInfo.AuthMethod.AuthMethodId, te.DbInitInfo.AuthMethod.LoginName, te.DbInitInfo.AuthMethod.Password) newOrgId := boundary.CreateNewOrgCli(t, ctx) newProjectId := boundary.CreateNewProjectCli(t, ctx, newOrgId) newHostCatalogId := boundary.CreateNewHostCatalogCli(t, ctx, newProjectId) @@ -189,31 +214,14 @@ func populateBoundaryDatabase(t testing.TB, ctx context.Context, c *config, te T boundary.AddCredentialSourceToTargetCli(t, ctx, newTargetId, newCredentialsId) // Create vault credentials - vaultAddr, boundaryPolicyName, kvPolicyFilePath := vault.Setup(t) - t.Cleanup(func() { - output := e2e.RunCommand(ctx, "vault", - e2e.WithArgs("policy", "delete", boundaryPolicyName), - ) - require.NoError(t, output.Err, string(output.Stderr)) - }) + _, boundaryPolicyName, kvPolicyFilePath := vault.Setup(t) output := e2e.RunCommand(ctx, "vault", e2e.WithArgs("secrets", "enable", "-path="+c.VaultSecretPath, "kv-v2"), ) require.NoError(t, output.Err, string(output.Stderr)) - t.Cleanup(func() { - output := e2e.RunCommand(ctx, "vault", - e2e.WithArgs("secrets", "disable", c.VaultSecretPath), - ) - require.NoError(t, output.Err, string(output.Stderr)) - }) + privateKeySecretName := vault.CreateKvPrivateKeyCredential(t, c.VaultSecretPath, c.TargetSshUser, c.TargetSshKeyPath, kvPolicyFilePath) kvPolicyName := vault.WritePolicy(t, ctx, kvPolicyFilePath) - t.Cleanup(func() { - output := e2e.RunCommand(ctx, "vault", - e2e.WithArgs("policy", "delete", kvPolicyName), - ) - require.NoError(t, output.Err, string(output.Stderr)) - }) t.Log("Created Vault Credential") output = e2e.RunCommand(ctx, "vault", e2e.WithArgs( @@ -233,7 +241,7 @@ func populateBoundaryDatabase(t testing.TB, ctx context.Context, c *config, te T require.NoError(t, err) credStoreToken := tokenCreateResult.Auth.Client_Token t.Log("Created Vault Cred Store Token") - newVaultCredentialStoreId := boundary.CreateNewCredentialStoreVaultCli(t, ctx, newProjectId, vaultAddr, credStoreToken) + newVaultCredentialStoreId := boundary.CreateNewCredentialStoreVaultCli(t, ctx, newProjectId, te.Vault.UriNetwork, credStoreToken) output = e2e.RunCommand(ctx, "boundary", e2e.WithArgs( "credential-libraries", "create", "vault", 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 9f86fb6b3..c7a3c6b2c 100644 --- a/testing/internal/e2e/tests/static/session_cancel_group_test.go +++ b/testing/internal/e2e/tests/static/session_cancel_group_test.go @@ -29,6 +29,8 @@ func TestCliSessionCancelGroup(t *testing.T) { e2e.MaybeSkipTest(t) c, err := loadConfig() require.NoError(t, err) + bc, err := boundary.LoadConfig() + require.NoError(t, err) ctx := context.Background() boundary.AuthenticateAdminCli(t, ctx) @@ -66,7 +68,7 @@ func TestCliSessionCancelGroup(t *testing.T) { boundary.SetAccountToUserCli(t, ctx, newUserId, newAccountId) // Try to connect to the target as a user without permissions - boundary.AuthenticateCli(t, ctx, acctName, acctPassword) + boundary.AuthenticateCli(t, ctx, bc.AuthMethodId, acctName, acctPassword) output := e2e.RunCommand(ctx, "boundary", e2e.WithArgs( "connect", 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 ae381bed7..62868eae3 100644 --- a/testing/internal/e2e/tests/static/session_cancel_user_test.go +++ b/testing/internal/e2e/tests/static/session_cancel_user_test.go @@ -28,6 +28,8 @@ func TestCliSessionCancelUser(t *testing.T) { e2e.MaybeSkipTest(t) c, err := loadConfig() require.NoError(t, err) + bc, err := boundary.LoadConfig() + require.NoError(t, err) ctx := context.Background() boundary.AuthenticateAdminCli(t, ctx) @@ -65,7 +67,7 @@ func TestCliSessionCancelUser(t *testing.T) { boundary.SetAccountToUserCli(t, ctx, newUserId, newAccountId) // Try to connect to the target as a user without permissions - boundary.AuthenticateCli(t, ctx, acctName, acctPassword) + boundary.AuthenticateCli(t, ctx, bc.AuthMethodId, acctName, acctPassword) output := e2e.RunCommand(ctx, "boundary", e2e.WithArgs( "connect",