refact(e2e): Reorganize test directory

Rearranges the directory structure to align better with the enos scenarios. Tests will now be added in the `tests` directory, with a folder in the directory corresponding to an enos scenario.
This commit is contained in:
Michael Li committed 2022-10-27 17:40:13 -04:00
1 parent 7abea7a4a9
commit 0424d379cf
16 files changed
+452 -302

No files matched your search

@@ -0,0 +1,372 @@
package aws_test
import (
"context"
"encoding/json"
"errors"
"fmt"
"strings"
"testing"
"time"
"github.com/cenkalti/backoff/v4"
"github.com/hashicorp/boundary/api/hostcatalogs"
"github.com/hashicorp/boundary/api/hosts"
"github.com/hashicorp/boundary/api/hostsets"
"github.com/hashicorp/boundary/testing/internal/e2e"
"github.com/hashicorp/boundary/testing/internal/e2e/boundary"
"github.com/kelseyhightower/envconfig"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
type config struct {
AwsAccessKeyId string `envconfig:"E2E_AWS_ACCESS_KEY_ID" required:"true"`
AwsSecretAccessKey string `envconfig:"E2E_AWS_SECRET_ACCESS_KEY" required:"true"`
AwsHostSetFilter1 string `envconfig:"E2E_AWS_HOST_SET_FILTER1" required:"true"` // e.g. "tag:testtag=true"
AwsHostSetIps1 string `envconfig:"E2E_AWS_HOST_SET_IPS1" required:"true"` // e.g. "[\"1.2.3.4\", \"2.3.4.5\"]"
AwsHostSetFilter2 string `envconfig:"E2E_AWS_HOST_SET_FILTER2" required:"true"` // e.g. "tag:testtagtwo=test"
AwsHostSetIps2 string `envconfig:"E2E_AWS_HOST_SET_IPS2" required:"true"` // e.g. "[\"1.2.3.4\"]"
TargetSshKeyPath string `envconfig:"E2E_SSH_KEY_PATH" required:"true"` // e.g. "/Users/username/key.pem"
TargetSshUser string `envconfig:"E2E_SSH_USER" required:"true"` // e.g. "ubuntu"
TargetPort string `envconfig:"E2E_SSH_PORT" default:"22"`
}
func loadConfig() (*config, error) {
var c config
err := envconfig.Process("", &c)
if err != nil {
return nil, err
}
return &c, err
}
// TestCliCreateAwsDynamicHostCatalog uses the boundary cli to create a host catalog with the AWS
// plugin. The test sets up an AWS dynamic host catalog, creates some host sets, sets up a target to
// one of the host sets, and attempts to connect to the target.
func TestCliCreateAwsDynamicHostCatalog(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)
// Create a dynamic host catalog
output := e2e.RunCommand(ctx, "boundary",
e2e.WithArgs(
"host-catalogs", "create", "plugin",
"-scope-id", newProjectId,
"-plugin-name", "aws",
"-attr", "disable_credential_rotation=true",
"-attr", "region=us-east-1",
"-secret", "access_key_id=env://E2E_AWS_ACCESS_KEY_ID",
"-secret", "secret_access_key=env://E2E_AWS_SECRET_ACCESS_KEY",
"-format", "json",
),
)
require.NoError(t, output.Err, string(output.Stderr))
var newHostCatalogResult hostcatalogs.HostCatalogCreateResult
err = json.Unmarshal(output.Stdout, &newHostCatalogResult)
require.NoError(t, err)
newHostCatalogId := newHostCatalogResult.Item.Id
t.Logf("Created Host Catalog: %s", newHostCatalogId)
// Create a host set
output = e2e.RunCommand(ctx, "boundary",
e2e.WithArgs(
"host-sets", "create", "plugin",
"-host-catalog-id", newHostCatalogId,
"-attr", "filters="+c.AwsHostSetFilter1,
"-name", "e2e Automated Test Host Set",
"-format", "json",
),
)
require.NoError(t, output.Err, string(output.Stderr))
var newHostSetResult hostsets.HostSetCreateResult
err = json.Unmarshal(output.Stdout, &newHostSetResult)
require.NoError(t, err)
newHostSetId1 := newHostSetResult.Item.Id
t.Logf("Created Host Set: %s", newHostSetId1)
// Get list of hosts in host set
// Retry is needed here since it can take a few tries before hosts start appearing
t.Logf("Looking for items in the host set...")
var actualHostSetCount1 int
err = backoff.RetryNotify(
func() error {
output = e2e.RunCommand(ctx, "boundary",
e2e.WithArgs(
"host-sets", "read",
"-id", newHostSetId1,
"-format", "json",
),
)
if output.Err != nil {
return backoff.Permanent(errors.New(string(output.Stderr)))
}
var hostSetsReadResult hostsets.HostSetReadResult
err = json.Unmarshal(output.Stdout, &hostSetsReadResult)
if err != nil {
return backoff.Permanent(err)
}
actualHostSetCount1 = len(hostSetsReadResult.Item.HostIds)
if actualHostSetCount1 == 0 {
return errors.New("No items are appearing in the host set")
}
t.Logf("Found %d hosts", actualHostSetCount1)
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)
var targetIps1 []string
err = json.Unmarshal([]byte(c.AwsHostSetIps1), &targetIps1)
expectedHostSetCount1 := len(targetIps1)
require.NoError(t, err)
assert.Equal(t, expectedHostSetCount1, actualHostSetCount1, "Numbers of hosts in host set did not match expected amount")
// Create another host set
output = e2e.RunCommand(ctx, "boundary",
e2e.WithArgs(
"host-sets", "create", "plugin",
"-host-catalog-id", newHostCatalogId,
"-attr", "filters="+c.AwsHostSetFilter2,
"-name", "e2e Automated Test Host Set2",
"-format", "json",
),
)
require.NoError(t, output.Err, string(output.Stderr))
var newHostSetResult2 hostsets.HostSetCreateResult
err = json.Unmarshal(output.Stdout, &newHostSetResult2)
require.NoError(t, err)
newHostSetId2 := newHostSetResult2.Item.Id
t.Logf("Created Host Set: %s", newHostSetId2)
// Get list of hosts in the second host set
t.Logf("Looking for items in the second host set...")
var actualHostSetCount2 int
err = backoff.RetryNotify(
func() error {
output = e2e.RunCommand(ctx, "boundary",
e2e.WithArgs("host-sets", "read", "-id", newHostSetId2, "-format", "json"),
)
if output.Err != nil {
return backoff.Permanent(errors.New(string(output.Stderr)))
}
var hostSetsReadResult hostsets.HostSetReadResult
err = json.Unmarshal(output.Stdout, &hostSetsReadResult)
if err != nil {
return backoff.Permanent(err)
}
actualHostSetCount2 = len(hostSetsReadResult.Item.HostIds)
if actualHostSetCount2 == 0 {
return errors.New("No items are appearing in the host set")
}
t.Logf("Found %d hosts", actualHostSetCount2)
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)
var targetIps2 []string
err = json.Unmarshal([]byte(c.AwsHostSetIps2), &targetIps2)
expectedHostSetCount2 := len(targetIps2)
require.NoError(t, err)
assert.Equal(t, expectedHostSetCount2, actualHostSetCount2, "Numbers of hosts in host set did not match expected amount")
// Get list of all hosts from host catalog
t.Logf("Looking for items in the host catalog...")
var actualHostCatalogCount int
err = backoff.RetryNotify(
func() error {
output = e2e.RunCommand(ctx, "boundary",
e2e.WithArgs("hosts", "list", "-host-catalog-id", newHostCatalogId, "-format", "json"),
)
if output.Err != nil {
return backoff.Permanent(errors.New(string(output.Stderr)))
}
var hostCatalogListResult hostcatalogs.HostCatalogListResult
err = json.Unmarshal(output.Stdout, &hostCatalogListResult)
if err != nil {
return backoff.Permanent(err)
}
actualHostCatalogCount = len(hostCatalogListResult.Items)
if actualHostCatalogCount == 0 {
return errors.New("No items are appearing in the host catalog")
}
t.Logf("Found %d host(s)", actualHostCatalogCount)
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)
expectedHostCatalogCount := expectedHostSetCount1 + expectedHostSetCount2
require.NoError(t, err)
assert.Equal(t, expectedHostCatalogCount, actualHostCatalogCount, "Numbers of hosts in host catalog did not match expected amount")
// Create target
newTargetId := boundary.CreateNewTargetCli(t, ctx, newProjectId, c.TargetPort)
boundary.AddHostSourceToTargetCli(t, ctx, newTargetId, newHostSetId1)
// Connect to target
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]
t.Log("Successfully connected to the target")
// Check if connected host exists in the host set
hostIpInList := false
for _, v := range targetIps1 {
if v == hostIp {
hostIpInList = true
}
}
require.True(t, hostIpInList, fmt.Sprintf("Connected host (%s) is not in expected list (%s)", hostIp, targetIps1))
}
// TestApiCreateAwsDynamicHostCatalog uses the Go api to create a host catalog with the AWS plugin.
// The test sets up an AWS dynamic host catalog, creates a host set, and sets up a target to the
// host set.
func TestApiCreateAwsDynamicHostCatalog(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)
// Create a dynamic host catalog
hcClient := hostcatalogs.NewClient(client)
newHostCatalogResult, err := hcClient.Create(ctx, "plugin", newProjectId,
hostcatalogs.WithName("e2e Automated Test Host Catalog"),
hostcatalogs.WithPluginName("aws"),
hostcatalogs.WithAttributes(map[string]any{
"disable_credential_rotation": true,
"region": "us-east-1",
}),
hostcatalogs.WithSecrets(map[string]any{
"access_key_id": c.AwsAccessKeyId,
"secret_access_key": c.AwsSecretAccessKey,
}),
)
require.NoError(t, err)
newHostCatalogId := newHostCatalogResult.Item.Id
t.Logf("Created Host Catalog: %s", newHostCatalogId)
// Create a host set and add to catalog
hsClient := hostsets.NewClient(client)
newHostSetResult, err := hsClient.Create(ctx, newHostCatalogId,
hostsets.WithAttributes(map[string]any{
"filters": c.AwsHostSetFilter1,
}),
hostsets.WithName("e2e Automated Test Host Set"),
)
require.NoError(t, err)
newHostSetId := newHostSetResult.Item.Id
t.Logf("Created Host Set: %s", newHostSetId)
// Get list of hosts in host set
// Retry is needed here since it can take a few tries before hosts start appearing
t.Logf("Looking for items in the host set...")
var actualHostSetCount int
err = backoff.RetryNotify(
func() error {
hostSetReadResult, err := hsClient.Read(ctx, newHostSetId)
if err != nil {
return backoff.Permanent(err)
}
actualHostSetCount = len(hostSetReadResult.Item.HostIds)
if actualHostSetCount == 0 {
return errors.New("No items are appearing in the host set")
}
t.Logf("Found %d hosts", actualHostSetCount)
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("Successfully found items in the host set")
var targetIps []string
err = json.Unmarshal([]byte(c.AwsHostSetIps1), &targetIps)
expectedHostSetCount := len(targetIps)
require.NoError(t, err)
assert.Equal(t, expectedHostSetCount, actualHostSetCount, "Numbers of hosts in host set did not match expected amount")
// Get list of all hosts from host catalog
// Retry is needed here since it can take a few tries before hosts start appearing
t.Logf("Looking for items in the host catalog...")
var actualHostCatalogCount int
hClient := hosts.NewClient(client)
err = backoff.RetryNotify(
func() error {
hostListResult, err := hClient.List(ctx, newHostCatalogId)
if err != nil {
return backoff.Permanent(err)
}
actualHostCatalogCount = len(hostListResult.Items)
if actualHostCatalogCount == 0 {
return errors.New("No items are appearing in the host catalog")
}
t.Logf("Found %d hosts", actualHostCatalogCount)
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("Successfully found items in the host catalog")
require.NoError(t, err)
assert.Equal(t, actualHostCatalogCount, expectedHostSetCount, "Numbers of hosts in host catalog did not match expected amount")
}
@@ -0,0 +1,118 @@
package static_test
import (
"context"
"encoding/json"
"fmt"
"os"
"strings"
"testing"
"github.com/hashicorp/boundary/api/credentials"
"github.com/hashicorp/boundary/api/targets"
"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"
)
// TestCliConnectTargetWithAuthzToken uses the boundary cli to connect to a target using the
// `authz_token` option
func TestCliConnectTargetWithAuthzToken(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)
newCredentialStoreId := boundary.CreateNewCredentialStoreStaticCli(t, ctx, newProjectId)
// Create credentials
output := e2e.RunCommand(ctx, "boundary",
e2e.WithArgs(
"credentials", "create", "ssh-private-key",
"-credential-store-id", newCredentialStoreId,
"-username", c.TargetSshUser,
"-private-key", "file://"+c.TargetSshKeyPath,
"-format", "json",
),
)
require.NoError(t, output.Err, string(output.Stderr))
var newCredentialsResult credentials.CredentialCreateResult
err = json.Unmarshal(output.Stdout, &newCredentialsResult)
require.NoError(t, err)
newCredentialsId := newCredentialsResult.Item.Id
t.Logf("Created Credentials: %s", newCredentialsId)
// Add credentials to target
output = e2e.RunCommand(ctx, "boundary",
e2e.WithArgs(
"targets", "add-credential-sources",
"-id", newTargetId,
"-brokered-credential-source", newCredentialsId,
),
)
require.NoError(t, output.Err, string(output.Stderr))
// Get credentials for target
output = e2e.RunCommand(ctx, "boundary",
e2e.WithArgs("targets", "authorize-session", "-id", newTargetId, "-format", "json"),
)
require.NoError(t, output.Err, string(output.Stderr))
var newSessionAuthorizationResult targets.SessionAuthorizationResult
err = json.Unmarshal(output.Stdout, &newSessionAuthorizationResult)
require.NoError(t, err)
newSessionAuthorization := newSessionAuthorizationResult.Item
retrievedUser := fmt.Sprintf("%s", newSessionAuthorization.Credentials[0].Credential["username"])
retrievedKey := fmt.Sprintf("%s\n", newSessionAuthorization.Credentials[0].Credential["private_key"])
assert.Equal(t, c.TargetSshUser, retrievedUser)
k, err := os.ReadFile(c.TargetSshKeyPath)
require.NoError(t, err)
require.Equal(t, string(k), retrievedKey)
t.Log("Successfully retrieved credentials for target")
// Get auth token for session
newAuthToken := newSessionAuthorizationResult.Item.AuthorizationToken
// Create key file
retrievedKeyPath := fmt.Sprintf("%s/%s", t.TempDir(), "target_private_key.pem")
f, err := os.Create(retrievedKeyPath)
require.NoError(t, err)
_, err = f.WriteString(retrievedKey)
require.NoError(t, err)
err = os.Chmod(retrievedKeyPath, 0o400)
require.NoError(t, err)
// Connect to target and print host's IP address using retrieved credentials
output = e2e.RunCommand(ctx, "boundary",
e2e.WithArgs(
"connect",
"-authz-token", newAuthToken,
"-exec", "/usr/bin/ssh", "--",
"-l", retrievedUser,
"-i", retrievedKeyPath,
"-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")
}
@@ -0,0 +1,96 @@
package static_test
import (
"context"
"encoding/json"
"fmt"
"os"
"testing"
"github.com/hashicorp/boundary/api/credentials"
"github.com/hashicorp/boundary/api/targets"
"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"
)
// TestCliConnectTargetWithSsh uses the boundary cli to create a credential using boundary's
// built-in credential store. The test attaches that credential to a target and attempts to connect
// to that target using those credentials.
func TestCliConnectTargetWithSsh(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)
newCredentialStoreId := boundary.CreateNewCredentialStoreStaticCli(t, ctx, newProjectId)
// Create credentials
output := e2e.RunCommand(ctx, "boundary",
e2e.WithArgs(
"credentials", "create", "ssh-private-key",
"-credential-store-id", newCredentialStoreId,
"-username", c.TargetSshUser,
"-private-key", "file://"+c.TargetSshKeyPath,
"-format", "json",
),
)
require.NoError(t, output.Err, string(output.Stderr))
var newCredentialsResult credentials.CredentialCreateResult
err = json.Unmarshal(output.Stdout, &newCredentialsResult)
require.NoError(t, err)
newCredentialsId := newCredentialsResult.Item.Id
t.Logf("Created Credentials: %s", newCredentialsId)
// Add credentials to target
output = e2e.RunCommand(ctx, "boundary",
e2e.WithArgs(
"targets", "add-credential-sources",
"-id", newTargetId,
"-brokered-credential-source", newCredentialsId,
),
)
require.NoError(t, output.Err, string(output.Stderr))
// Get credentials for target
output = e2e.RunCommand(ctx, "boundary",
e2e.WithArgs("targets", "authorize-session", "-id", newTargetId, "-format", "json"),
)
require.NoError(t, output.Err, string(output.Stderr))
var newSessionAuthorizationResult targets.SessionAuthorizationResult
err = json.Unmarshal(output.Stdout, &newSessionAuthorizationResult)
require.NoError(t, err)
newSessionAuthorization := newSessionAuthorizationResult.Item
retrievedUser := fmt.Sprintf("%s", newSessionAuthorization.Credentials[0].Credential["username"])
retrievedKey := fmt.Sprintf("%s\n", newSessionAuthorization.Credentials[0].Credential["private_key"])
assert.Equal(t, c.TargetSshUser, retrievedUser)
k, err := os.ReadFile(c.TargetSshKeyPath)
require.NoError(t, err)
require.Equal(t, string(k), retrievedKey)
t.Log("Successfully retrieved credentials for target")
// Connect to target using ssh option
output = e2e.RunCommand(ctx, "boundary",
e2e.WithArgs(
"connect", "ssh",
"-target-id", newTargetId, "--",
"-o", "UserKnownHostsFile=/dev/null",
"-o", "StrictHostKeyChecking=no",
"-o", "IdentitiesOnly=yes", // forces the use of the provided key
),
)
require.NoError(t, output.Err, string(output.Stderr))
t.Log("Successfully connected to target")
}
@@ -0,0 +1,54 @@
package static_test
import (
"context"
"strings"
"testing"
"github.com/hashicorp/boundary/testing/internal/e2e"
"github.com/hashicorp/boundary/testing/internal/e2e/boundary"
"github.com/stretchr/testify/require"
)
// TestCliConnectTarget uses the boundary cli to create a number of supporting objects
// to connect to a target. It then attempts to connect to that target and verifies that
// the connection was successful.
func TestCliConnectTarget(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)
// 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")
}
@@ -0,0 +1,152 @@
package static_test
import (
"context"
"encoding/json"
"fmt"
"os"
"testing"
"time"
"github.com/cenkalti/backoff/v4"
"github.com/hashicorp/boundary/api/credentials"
"github.com/hashicorp/boundary/api/targets"
"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"
)
// TestCliStaticCredentialStore validates various credential-store operations using the cli
func TestCliStaticCredentialStore(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)
newCredentialStoreId := boundary.CreateNewCredentialStoreStaticCli(t, ctx, newProjectId)
// Create ssh key credentials
output := e2e.RunCommand(ctx, "boundary",
e2e.WithArgs(
"credentials", "create", "ssh-private-key",
"-credential-store-id", newCredentialStoreId,
"-username", c.TargetSshUser,
"-private-key", "file://"+c.TargetSshKeyPath,
"-format", "json",
),
)
require.NoError(t, output.Err, string(output.Stderr))
var keyCredentialsResult credentials.CredentialCreateResult
err = json.Unmarshal(output.Stdout, &keyCredentialsResult)
require.NoError(t, err)
keyCredentialsId := keyCredentialsResult.Item.Id
t.Logf("Created SSH Private Key Credentials: %s", keyCredentialsId)
// Create username/password credentials
output = e2e.RunCommand(ctx, "boundary",
e2e.WithArgs(
"credentials", "create", "username-password",
"-credential-store-id", newCredentialStoreId,
"-username", c.TargetSshUser,
"-password", "env://E2E_CREDENTIALS_PASSWORD",
"-format", "json",
),
e2e.WithEnv("E2E_CREDENTIALS_PASSWORD", "password"),
)
require.NoError(t, output.Err, string(output.Stderr))
var pwCredentialsResult credentials.CredentialCreateResult
err = json.Unmarshal(output.Stdout, &pwCredentialsResult)
require.NoError(t, err)
pwCredentialsId := pwCredentialsResult.Item.Id
t.Logf("Created Username/Password Credentials: %s", pwCredentialsId)
// Delete credential store
output = e2e.RunCommand(ctx, "boundary",
e2e.WithArgs("credential-stores", "delete", "-id", newCredentialStoreId),
)
require.NoError(t, output.Err, string(output.Stderr))
err = backoff.RetryNotify(
func() error {
output = e2e.RunCommand(ctx, "boundary",
e2e.WithArgs("credential-stores", "read", "-id", newCredentialStoreId, "-format", "json"),
)
if output.Err == nil {
return fmt.Errorf("Deleted credential can still be read: '%s'", output.Stdout)
}
var response e2e.CliError
err = json.Unmarshal(output.Stderr, &response)
require.NoError(t, err)
statusCode := response.Status
if statusCode != 404 {
return backoff.Permanent(
fmt.Errorf("Command did not return expected status code. Expected: 404, Actual: %d", statusCode),
)
}
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.Logf("Successfully deleted credential store")
}
// TestApiStaticCredentialStore uses the Go api to create a credential using
// boundary's built-in credential store. The test then attaches that credential to a target.
func TestApiStaticCredentialStore(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)
newCredentialStoreId := boundary.CreateNewCredentialStoreStaticApi(t, ctx, client, newProjectId)
// Create credentials
cClient := credentials.NewClient(client)
k, err := os.ReadFile(c.TargetSshKeyPath)
require.NoError(t, err)
newCredentialsResult, err := cClient.Create(ctx, "ssh_private_key", newCredentialStoreId,
credentials.WithSshPrivateKeyCredentialUsername(c.TargetSshUser),
credentials.WithSshPrivateKeyCredentialPrivateKey(string(k)),
)
require.NoError(t, err)
newCredentialsId := newCredentialsResult.Item.Id
t.Logf("Created Credentials: %s", newCredentialsId)
// Add credentials to target
tClient := targets.NewClient(client)
_, err = tClient.AddCredentialSources(ctx, newTargetId, 0,
targets.WithAutomaticVersioning(true),
targets.WithBrokeredCredentialSourceIds([]string{newCredentialsId}),
)
require.NoError(t, err)
// Authorize Session
newSessionAuthorizationResult, err := tClient.AuthorizeSession(ctx, newTargetId)
require.NoError(t, err)
newSessionAuthorization := newSessionAuthorizationResult.Item
retrievedUser := fmt.Sprintf("%s", newSessionAuthorization.Credentials[0].Credential["username"])
retrievedKey := fmt.Sprintf("%s", newSessionAuthorization.Credentials[0].Credential["private_key"])
assert.Equal(t, c.TargetSshUser, retrievedUser)
require.Equal(t, string(k), retrievedKey)
t.Log("Successfully retrieved credentials for target")
}
@@ -0,0 +1,20 @@
package static_test
import "github.com/kelseyhightower/envconfig"
type config struct {
TargetIp string `envconfig:"E2E_TARGET_IP" required:"true"` // e.g. 192.168.0.1
TargetSshKeyPath string `envconfig:"E2E_SSH_KEY_PATH" required:"true"` // e.g. /Users/username/key.pem
TargetSshUser string `envconfig:"E2E_SSH_USER" required:"true"` // e.g. ubuntu
TargetPort string `envconfig:"E2E_SSH_PORT" default:"22"`
}
func loadConfig() (*config, error) {
var c config
err := envconfig.Process("", &c)
if err != nil {
return nil, err
}
return &c, nil
}
@@ -0,0 +1,125 @@
package static_test
import (
"context"
"encoding/json"
"errors"
"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"
)
// TestCliSessionCancelAdmin uses the boundary cli to start and then cancel a session
func TestCliSessionCancelAdmin(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)
// Connect to target to create a session
ctxCancel, cancel := context.WithCancel(context.Background())
errChan := make(chan *e2e.CommandResult)
go func() {
t.Log("Starting session...")
errChan <- e2e.RunCommand(ctxCancel, "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; sleep 60",
),
)
}()
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")
}
@@ -0,0 +1,238 @@
package static_test
import (
"context"
"encoding/json"
"errors"
"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"
"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"
)
// TestCliSessionCancelUser uses the cli to create a new user and sets up the right permissions for
// the user to connect to the created target. The test also confirms that an admin can cancel the
// user's session.
func TestCliSessionCancelUser(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 role
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))
// Connect to target to create a session
ctxCancel, cancel := context.WithCancel(context.Background())
errChan := make(chan *e2e.CommandResult)
go func() {
boundary.AuthenticateCli(t, ctx, acctName, acctPassword)
t.Log("Starting session as user...")
errChan <- e2e.RunCommand(ctxCancel, "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; sleep 60",
),
)
}()
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")
}
// TestApiCreateUser uses the Go api to create a new user and add some grants to the user
func TestApiCreateUser(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})
rClient := roles.NewClient(client)
newRoleResult, err := rClient.Create(ctx, newProjectId)
require.NoError(t, err)
newRoleId := newRoleResult.Item.Id
t.Logf("Create 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{newUserId},
roles.WithAutomaticVersioning(true),
)
require.NoError(t, err)
}
@@ -0,0 +1,154 @@
package static_with_vault_test
import (
"context"
"encoding/json"
"fmt"
"os"
"testing"
"github.com/hashicorp/boundary/api/credentiallibraries"
"github.com/hashicorp/boundary/api/credentialstores"
"github.com/hashicorp/boundary/api/targets"
"github.com/hashicorp/boundary/testing/internal/e2e"
"github.com/hashicorp/boundary/testing/internal/e2e/boundary"
"github.com/hashicorp/boundary/testing/internal/e2e/vault"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// TestCliVaultConnectTargetWithSsh uses the boundary and vault clis to add secrets management for a
// target. The test sets up vault as a credential store, creates a set of credentials in vault to be
// attached to a target, and attempts to connect to that target (with the ssh option) using those
// credentials.
func TestCliVaultConnectTargetWithSsh(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)
// Configure vault
vaultAddr, boundaryPolicyName := 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))
})
// Create credential in vault
secretName := "TestCreateVaultCredentialStoreCli"
credentialPolicyName := vault.CreateKvPrivateKeyCredential(t, secretName, c.VaultSecretPath, c.TargetSshUser, c.TargetSshKeyPath)
t.Log("Created Vault Credential")
// Create vault token for boundary
output = e2e.RunCommand(ctx, "vault",
e2e.WithArgs(
"token", "create",
"-no-default-policy=true",
"-policy="+boundaryPolicyName,
"-policy="+credentialPolicyName,
"-orphan=true",
"-period=20m",
"-renewable=true",
"-format=json",
),
)
require.NoError(t, output.Err, string(output.Stderr))
var tokenCreateResult createTokenResponse
err = json.Unmarshal(output.Stdout, &tokenCreateResult)
require.NoError(t, err)
credStoreToken := tokenCreateResult.Auth.Client_Token
t.Log("Created Vault Cred Store Token")
// Create a credential store
output = e2e.RunCommand(ctx, "boundary",
e2e.WithArgs(
"credential-stores", "create", "vault",
"-scope-id", newProjectId,
"-vault-address", vaultAddr,
"-vault-token", credStoreToken,
"-format", "json",
),
)
require.NoError(t, output.Err, string(output.Stderr))
var newCredentialStoreResult credentialstores.CredentialStoreCreateResult
err = json.Unmarshal(output.Stdout, &newCredentialStoreResult)
require.NoError(t, err)
newCredentialStoreId := newCredentialStoreResult.Item.Id
t.Logf("Created Credential Store: %s", newCredentialStoreId)
// Create a credential library
output = e2e.RunCommand(ctx, "boundary",
e2e.WithArgs(
"credential-libraries", "create", "vault",
"-credential-store-id", newCredentialStoreId,
"-vault-path", c.VaultSecretPath+"/data/"+secretName,
"-name", "e2e Automated Test Vault Credential Library",
"-credential-type", "ssh_private_key",
"-format", "json",
),
)
require.NoError(t, output.Err, string(output.Stderr))
var newCredentialLibraryResult credentiallibraries.CredentialLibraryCreateResult
err = json.Unmarshal(output.Stdout, &newCredentialLibraryResult)
require.NoError(t, err)
newCredentialLibraryId := newCredentialLibraryResult.Item.Id
t.Logf("Created Credential Library: %s", newCredentialLibraryId)
// Add brokered credentials to target
output = e2e.RunCommand(ctx, "boundary",
e2e.WithArgs(
"targets", "add-credential-sources",
"-id", newTargetId,
"-brokered-credential-source", newCredentialLibraryId,
),
)
require.NoError(t, output.Err, string(output.Stderr))
// Get credentials for target
output = e2e.RunCommand(ctx, "boundary",
e2e.WithArgs("targets", "authorize-session", "-id", newTargetId, "-format", "json"),
)
require.NoError(t, output.Err, string(output.Stderr))
var newSessionAuthorizationResult targets.SessionAuthorizationResult
err = json.Unmarshal(output.Stdout, &newSessionAuthorizationResult)
require.NoError(t, err)
newSessionAuthorization := newSessionAuthorizationResult.Item
retrievedUser := fmt.Sprintf("%s", newSessionAuthorization.Credentials[0].Credential["username"])
retrievedKey := fmt.Sprintf("%s", newSessionAuthorization.Credentials[0].Credential["private_key"])
assert.Equal(t, c.TargetSshUser, retrievedUser)
k, err := os.ReadFile(c.TargetSshKeyPath)
require.NoError(t, err)
require.Equal(t, string(k), retrievedKey)
t.Log("Successfully retrieved credentials for target")
// Connect to target using ssh option
output = e2e.RunCommand(ctx, "boundary",
e2e.WithArgs(
"connect", "ssh",
"-target-id", newTargetId, "--",
"-o", "UserKnownHostsFile=/dev/null",
"-o", "StrictHostKeyChecking=no",
"-o", "IdentitiesOnly=yes", // forces the use of the provided key
),
)
require.NoError(t, output.Err, string(output.Stderr))
t.Log("Successfully connected to target")
}
@@ -0,0 +1,199 @@
package static_with_vault_test
import (
"context"
"encoding/json"
"fmt"
"os"
"strings"
"testing"
"github.com/hashicorp/boundary/api/credentiallibraries"
"github.com/hashicorp/boundary/api/credentialstores"
"github.com/hashicorp/boundary/api/targets"
"github.com/hashicorp/boundary/testing/internal/e2e"
"github.com/hashicorp/boundary/testing/internal/e2e/boundary"
"github.com/hashicorp/boundary/testing/internal/e2e/vault"
"github.com/kelseyhightower/envconfig"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
type config struct {
TargetIp string `envconfig:"E2E_TARGET_IP" required:"true"` // e.g. 192.168.0.1
TargetSshUser string `envconfig:"E2E_SSH_USER" required:"true"` // e.g. ubuntu
TargetSshKeyPath string `envconfig:"E2E_SSH_KEY_PATH" required:"true"` // e.g. /Users/username/key.pem
TargetPort string `envconfig:"E2E_SSH_PORT" default:"22"`
VaultSecretPath string `envconfig:"E2E_VAULT_SECRET_PATH" default:"e2e_secrets"`
}
func loadConfig() (*config, error) {
var c config
err := envconfig.Process("", &c)
if err != nil {
return nil, err
}
return &c, err
}
type createTokenResponse struct {
Auth struct {
Client_Token string
}
}
// TestCliVaultConnectTarget uses the boundary and vault clis to add secrets management
// for a target. The test sets up vault as a credential store, creates a set of credentials
// in vault to be attached to a target, and attempts to connect to that target using those
// credentials.
func TestCliVaultConnectTarget(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)
// Configure vault
vaultAddr, boundaryPolicyName := 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))
})
// Create credential in vault
secretName := "TestCreateVaultCredentialStoreCli"
credentialPolicyName := vault.CreateKvPrivateKeyCredential(t, secretName, c.VaultSecretPath, c.TargetSshUser, c.TargetSshKeyPath)
t.Log("Created Vault Credential")
// Create vault token for boundary
output = e2e.RunCommand(ctx, "vault",
e2e.WithArgs(
"token", "create",
"-no-default-policy=true",
"-policy="+boundaryPolicyName,
"-policy="+credentialPolicyName,
"-orphan=true",
"-period=20m",
"-renewable=true",
"-format=json",
),
)
require.NoError(t, output.Err, string(output.Stderr))
var tokenCreateResult createTokenResponse
err = json.Unmarshal(output.Stdout, &tokenCreateResult)
require.NoError(t, err)
credStoreToken := tokenCreateResult.Auth.Client_Token
t.Log("Created Vault Cred Store Token")
// Create a credential store
output = e2e.RunCommand(ctx, "boundary",
e2e.WithArgs(
"credential-stores", "create", "vault",
"-scope-id", newProjectId,
"-vault-address", vaultAddr,
"-vault-token", credStoreToken,
"-format", "json",
),
)
require.NoError(t, output.Err, string(output.Stderr))
var newCredentialStoreResult credentialstores.CredentialStoreCreateResult
err = json.Unmarshal(output.Stdout, &newCredentialStoreResult)
require.NoError(t, err)
newCredentialStoreId := newCredentialStoreResult.Item.Id
t.Logf("Created Credential Store: %s", newCredentialStoreId)
// Create a credential library
output = e2e.RunCommand(ctx, "boundary",
e2e.WithArgs(
"credential-libraries", "create", "vault",
"-credential-store-id", newCredentialStoreId,
"-vault-path", c.VaultSecretPath+"/data/"+secretName,
"-name", "e2e Automated Test Vault Credential Library",
"-credential-type", "ssh_private_key",
"-format", "json",
),
)
require.NoError(t, output.Err, string(output.Stderr))
var newCredentialLibraryResult credentiallibraries.CredentialLibraryCreateResult
err = json.Unmarshal(output.Stdout, &newCredentialLibraryResult)
require.NoError(t, err)
newCredentialLibraryId := newCredentialLibraryResult.Item.Id
t.Logf("Created Credential Library: %s", newCredentialLibraryId)
// Add brokered credentials to target
output = e2e.RunCommand(ctx, "boundary",
e2e.WithArgs(
"targets", "add-credential-sources",
"-id", newTargetId,
"-brokered-credential-source", newCredentialLibraryId,
),
)
require.NoError(t, output.Err, string(output.Stderr))
// Get credentials for target
output = e2e.RunCommand(ctx, "boundary",
e2e.WithArgs("targets", "authorize-session", "-id", newTargetId, "-format", "json"),
)
require.NoError(t, output.Err, string(output.Stderr))
var newSessionAuthorizationResult targets.SessionAuthorizationResult
err = json.Unmarshal(output.Stdout, &newSessionAuthorizationResult)
require.NoError(t, err)
newSessionAuthorization := newSessionAuthorizationResult.Item
retrievedUser := fmt.Sprintf("%s", newSessionAuthorization.Credentials[0].Credential["username"])
retrievedKey := fmt.Sprintf("%s", newSessionAuthorization.Credentials[0].Credential["private_key"])
assert.Equal(t, c.TargetSshUser, retrievedUser)
k, err := os.ReadFile(c.TargetSshKeyPath)
require.NoError(t, err)
require.Equal(t, string(k), retrievedKey)
t.Log("Successfully retrieved credentials for target")
// Create key file
retrievedKeyPath := fmt.Sprintf("%s/%s", t.TempDir(), "target_private_key.pem")
f, err := os.Create(retrievedKeyPath)
require.NoError(t, err)
_, err = f.WriteString(retrievedKey)
require.NoError(t, err)
err = os.Chmod(retrievedKeyPath, 0o400)
require.NoError(t, err)
// Connect to target and print host's IP address using retrieved credentials
output = e2e.RunCommand(ctx, "boundary",
e2e.WithArgs(
"connect",
"-target-id", newTargetId,
"-exec", "/usr/bin/ssh", "--",
"-l", retrievedUser,
"-i", retrievedKeyPath,
"-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")
}
@@ -0,0 +1,122 @@
package static_with_vault_test
import (
"context"
"encoding/json"
"os"
"testing"
"github.com/hashicorp/boundary/api/credentiallibraries"
"github.com/hashicorp/boundary/api/credentialstores"
"github.com/hashicorp/boundary/api/targets"
"github.com/hashicorp/boundary/testing/internal/e2e"
"github.com/hashicorp/boundary/testing/internal/e2e/boundary"
"github.com/hashicorp/boundary/testing/internal/e2e/vault"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// TestApiVaultCredentialStore uses the Go api along with the vault cli to add secrets
// management for a target. The test sets up vault as a credential stores and creates a set of
// credentials in vault that is attached to a target.
func TestApiVaultCredentialStore(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)
// Configure vault
vaultAddr, boundaryPolicyName := 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))
})
// Create credential in vault
secretName := "TestCreateVaultCredentialStoreCli"
credentialPolicyName := vault.CreateKvPrivateKeyCredential(t, secretName, c.VaultSecretPath, c.TargetSshUser, c.TargetSshKeyPath)
t.Log("Created Vault Credential")
// Create vault token for boundary
output = e2e.RunCommand(ctx, "vault",
e2e.WithArgs(
"token", "create",
"-no-default-policy=true",
"-policy="+boundaryPolicyName,
"-policy="+credentialPolicyName,
"-orphan=true",
"-period=20m",
"-renewable=true",
"-format=json",
),
)
require.NoError(t, output.Err, string(output.Stderr))
var tokenCreateResult createTokenResponse
err = json.Unmarshal(output.Stdout, &tokenCreateResult)
require.NoError(t, err)
credStoreToken := tokenCreateResult.Auth.Client_Token
t.Log("Created Vault Cred Store Token")
// Create a credential store
csClient := credentialstores.NewClient(client)
newCredentialStoreResult, err := csClient.Create(ctx, "vault", newProjectId,
credentialstores.WithVaultCredentialStoreAddress(vaultAddr),
credentialstores.WithVaultCredentialStoreToken(credStoreToken),
)
require.NoError(t, err)
newCredentialStoreId := newCredentialStoreResult.Item.Id
t.Logf("Created Credential Store: %s", newCredentialStoreId)
// Create a credential library
clClient := credentiallibraries.NewClient(client)
newCredentialLibraryResult, err := clClient.Create(ctx, newCredentialStoreId,
credentiallibraries.WithVaultCredentialLibraryPath(c.VaultSecretPath+"/data/"+secretName),
credentiallibraries.WithCredentialType("ssh_private_key"),
)
require.NoError(t, err)
newCredentialLibraryId := newCredentialLibraryResult.Item.Id
t.Logf("Created Credential Library: %s", newCredentialLibraryId)
// Add brokered credentials to target
tClient := targets.NewClient(client)
_, err = tClient.AddCredentialSources(ctx, newTargetId, 0,
targets.WithBrokeredCredentialSourceIds([]string{newCredentialLibraryId}),
targets.WithAutomaticVersioning(true),
)
require.NoError(t, err)
// Get credentials for target
newSessionAuthorizationResult, err := tClient.AuthorizeSession(ctx, newTargetId)
require.NoError(t, err)
newSessionAuthorization := newSessionAuthorizationResult.Item
retrievedUser, ok := newSessionAuthorization.Credentials[0].Credential["username"].(string)
require.True(t, ok)
assert.Equal(t, c.TargetSshUser, retrievedUser)
retrievedKey, ok := newSessionAuthorization.Credentials[0].Credential["private_key"].(string)
require.True(t, ok)
k, err := os.ReadFile(c.TargetSshKeyPath)
require.NoError(t, err)
keysMatch := string(k) == retrievedKey // This is done to prevent printing out key info
require.True(t, keysMatch, "Key retrieved from vault does not match expected value")
t.Log("Successfully retrieved credentials for target")
}