diff --git a/testing/internal/e2e/boundary/target.go b/testing/internal/e2e/boundary/target.go index f7226af15..536641052 100644 --- a/testing/internal/e2e/boundary/target.go +++ b/testing/internal/e2e/boundary/target.go @@ -61,14 +61,64 @@ func CreateNewTargetCli(t testing.TB, ctx context.Context, projectId string, def return newTargetId } -// AddHostSourceToTargetCli uses the cli to add a host source (host set or host) to a target -func AddHostSourceToTargetCli(t testing.TB, ctx context.Context, targetId string, hostSourceId string) { +// CreateNewAddressTargetCli uses the cli to create a new target using an +// address in boundary. +// Returns the id of the new target. +func CreateNewAddressTargetCli(t testing.TB, ctx context.Context, projectId string, defaultPort string, address string) string { + output := e2e.RunCommand(ctx, "boundary", + e2e.WithArgs( + "targets", "create", "tcp", + "-scope-id", projectId, + "-default-port", defaultPort, + "-name", "e2e Target", + "-address", address, + "-format", "json", + ), + ) + require.NoError(t, output.Err, string(output.Stderr)) + var newTargetResult targets.TargetCreateResult + err := json.Unmarshal(output.Stdout, &newTargetResult) + require.NoError(t, err) + newTargetId := newTargetResult.Item.Id + t.Logf("Created Target: %s", newTargetId) + + return newTargetId +} + +// AddHostSourceToTargetCli uses the cli to add a host source (host set or host) +// to a target. +// Boundary's `add-host-sources` functionality appends a new host source to the +// existing set of host sources in the target. +func AddHostSourceToTargetCli(t testing.TB, ctx context.Context, targetId, hostSourceId string) { output := e2e.RunCommand(ctx, "boundary", e2e.WithArgs("targets", "add-host-sources", "-id", targetId, "-host-source", hostSourceId), ) require.NoError(t, output.Err, string(output.Stderr)) } +// SetHostSourceToTargetCli uses the cli to set a host source (host set or host) +// to a target. +// Boundary's `set-host-sources` functionality replaces all existing host sets +// on a target with the provided one. +func SetHostSourceToTargetCli(t testing.TB, ctx context.Context, targetId, hostSourceId string) { + output := e2e.RunCommand(ctx, "boundary", + e2e.WithArgs("targets", "set-host-sources", "-id", targetId, "-host-source", hostSourceId), + ) + require.NoError(t, output.Err, string(output.Stderr)) +} + +// RemoveHostSourceFromTargetCli uses the cli to remove a host source (host set or host) to a target +func RemoveHostSourceFromTargetCli(t testing.TB, ctx context.Context, targetId, hostSourceId string) { + output := e2e.RunCommand(ctx, "boundary", + e2e.WithArgs( + "targets", "remove-host-sources", + "-id", targetId, + "-host-source", hostSourceId, + ), + ) + require.NoError(t, output.Err, string(output.Stderr)) +} + // AddCredentialSourceToTargetCli uses the cli to add a credential source (credential library or // credential) to a target func AddCredentialSourceToTargetCli(t testing.TB, ctx context.Context, targetId string, credentialSourceId string) { diff --git a/testing/internal/e2e/tests/static/target_address_test.go b/testing/internal/e2e/tests/static/target_address_test.go new file mode 100644 index 000000000..b183358b2 --- /dev/null +++ b/testing/internal/e2e/tests/static/target_address_test.go @@ -0,0 +1,340 @@ +package static_test + +import ( + "context" + "encoding/json" + "net/http" + "strings" + "testing" + + "github.com/hashicorp/boundary/testing/internal/e2e" + "github.com/hashicorp/boundary/testing/internal/e2e/boundary" + "github.com/stretchr/testify/require" +) + +// TestCliCreateUpdateTargetAddress asserts that creating and updating a target +// using a valid address is successful. +func TestCliCreateUpdateTargetAddress(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) + newTargetId := boundary.CreateNewAddressTargetCli(t, ctx, newProjectId, c.TargetPort, c.TargetIp) + + // Connect to target and print host's IP address + output := e2e.RunCommand(ctx, "boundary", + e2e.WithArgs( + "connect", + "-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", + ), + ) + require.NoError(t, output.Err, string(output.Stderr)) + + parts := strings.Fields(string(output.Stdout)) + hostIp := parts[len(parts)-1] + require.Equal(t, c.TargetIp, hostIp, "SSH session did not return expected output") + t.Log("Successfully connected to target") + + // Remove target address. We have now no address or host sources. + output = e2e.RunCommand(ctx, "boundary", + e2e.WithArgs( + "targets", "update", "tcp", + "-id", newTargetId, + "-address", "null", + "-format", "json", + ), + ) + require.NoError(t, output.Err, string(output.Stderr)) + + // Connect to target and print host's IP address - should error because + // there's no place to connect to. + 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.Stderr)) + + var response boundary.CliError + require.NoError(t, json.Unmarshal(output.Stderr, &response)) + require.Equal(t, http.StatusNotFound, response.Status, "Expected error when connecting to target with no host sources or address") + + // Add a valid address back in. + output = e2e.RunCommand(ctx, "boundary", + e2e.WithArgs( + "targets", "update", "tcp", + "-id", newTargetId, + "-address", c.TargetIp, + "-format", "json", + ), + ) + require.NoError(t, output.Err, string(output.Stderr)) + + // Connect to target and print host's IP address. + output = e2e.RunCommand(ctx, "boundary", + e2e.WithArgs( + "connect", + "-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", + ), + ) + require.NoError(t, output.Err, string(output.Stderr)) + + parts = strings.Fields(string(output.Stdout)) + hostIp = parts[len(parts)-1] + require.Equal(t, c.TargetIp, hostIp, "SSH session did not return expected output") + t.Log("Successfully connected to target") +} + +// TestCliTargetAddressToHostSource asserts that we can change a target using an +// address to a target using host sources only if we remove the address first. +// TestCliTargetAddressToHostSource and TestCliTargetHostSourceToAddress are separate, +// independent tests because switching between host sources and address is not a +// commutative operation (they use different codepaths). +func TestCliTargetAddressToHostSource(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.CreateNewAddressTargetCli(t, ctx, newProjectId, c.TargetPort, c.TargetIp) + + // Connect to target and print host's IP address + output := e2e.RunCommand(ctx, "boundary", + e2e.WithArgs( + "connect", + "-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", + ), + ) + require.NoError(t, output.Err, string(output.Stderr)) + + parts := strings.Fields(string(output.Stdout)) + hostIp := parts[len(parts)-1] + require.Equal(t, c.TargetIp, hostIp, "SSH session did not return expected output") + t.Log("Successfully connected to target") + + // Attempt add-host-sources. Should error because the target has an address. + output = e2e.RunCommand(ctx, "boundary", + e2e.WithArgs( + "targets", "add-host-sources", + "-id", newTargetId, + "-host-source", newHostSetId, + "-format", "json", + ), + ) + require.Error(t, output.Err, "Target add-host-sources did not return expected error") + + var response boundary.CliError + require.NoError(t, json.Unmarshal(output.Stderr, &response)) + require.Equal(t, http.StatusBadRequest, response.Status, "Expected error when adding host source to target with an address") + + // Attempt set-host-sources. Should error because the target has an address. + output = e2e.RunCommand(ctx, "boundary", + e2e.WithArgs( + "targets", "set-host-sources", + "-id", newTargetId, + "-host-source", newHostSetId, + "-format", "json", + ), + ) + require.Error(t, output.Err, "Target set-host-sources did not return expected error") + require.NoError(t, json.Unmarshal(output.Stderr, &response)) + require.Equal(t, http.StatusBadRequest, response.Status, "Expected error when setting host source to target with an address") + + // Remove the target's address. We should now be able to add/set host + // sources. + output = e2e.RunCommand(ctx, "boundary", + e2e.WithArgs( + "targets", "update", "tcp", + "-id", newTargetId, + "-address", "null", + "-format", "json", + ), + ) + require.NoError(t, output.Err, string(output.Stderr)) + + boundary.AddHostSourceToTargetCli(t, ctx, newTargetId, newHostSetId) + boundary.SetHostSourceToTargetCli(t, ctx, newTargetId, newHostSetId) + + // Connect to target and print host's IP address, now using the host source. + output = e2e.RunCommand(ctx, "boundary", + e2e.WithArgs( + "connect", + "-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", + ), + ) + require.NoError(t, output.Err, string(output.Stderr)) + + parts = strings.Fields(string(output.Stdout)) + hostIp = parts[len(parts)-1] + require.Equal(t, c.TargetIp, hostIp, "SSH session did not return expected output") + t.Log("Successfully connected to target") +} + +// TestCliTargetHostSourceToAddress asserts that we can change a target using host +// source(s) to a target using an address only if we remove the host sources +// first. +// TestCliTargetAddressToHostSource and TestCliTargetHostSourceToAddress are separate, +// independent tests because switching between host sources and address is not a +// commutative operation (they use different codepaths). +func TestCliTargetHostSourceToAddress(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) + + // Connect to target and print host's IP address + output := e2e.RunCommand(ctx, "boundary", + e2e.WithArgs( + "connect", + "-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", + ), + ) + require.NoError(t, output.Err, string(output.Stderr)) + + parts := strings.Fields(string(output.Stdout)) + hostIp := parts[len(parts)-1] + require.Equal(t, c.TargetIp, hostIp, "SSH session did not return expected output") + t.Log("Successfully connected to target") + + // Attempt to add an address to the target - should error because we have + // host sources set. + output = e2e.RunCommand(ctx, "boundary", + e2e.WithArgs( + "targets", "update", "tcp", + "-id", newTargetId, + "-address", c.TargetIp, + "-format", "json", + ), + ) + require.Error(t, output.Err, "Target update did not return expected error") + + var response boundary.CliError + require.NoError(t, json.Unmarshal(output.Stderr, &response)) + require.Equal(t, http.StatusBadRequest, response.Status, "Expected error when setting address to target with a host source") + + boundary.RemoveHostSourceFromTargetCli(t, ctx, newTargetId, newHostSetId) + + // Attempt to add an address to the target again - should work + output = e2e.RunCommand(ctx, "boundary", + e2e.WithArgs( + "targets", "update", "tcp", + "-id", newTargetId, + "-address", c.TargetIp, + "-format", "json", + ), + ) + require.NoError(t, output.Err, string(output.Stderr)) + + // Connect to target and print host's IP address + output = e2e.RunCommand(ctx, "boundary", + e2e.WithArgs( + "connect", + "-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", + ), + ) + require.NoError(t, output.Err, string(output.Stderr)) +}