test(e2e): Add vault tests

Adds the first set of vault tests. This test sets up vault as a credential store and creates a set
of credentials for a target.
This commit is contained in:
Michael Li committed 2022-09-27 12:21:10 -04:00
1 parent 6e53747b97
commit 8fe0d13caa
13 files changed
+922 -92

No files matched your search

+19
View File
@@ -2,6 +2,10 @@ module "az_finder" {
source = "./modules/az_finder"
}
module "binary_finder" {
source = "./modules/binary_finder"
}
module "boundary" {
source = "app.terraform.io/hashicorp-qti/aws-boundary/enos"
version = ">= 0.2.6"
@@ -58,6 +62,21 @@ module "target" {
enos_user = var.enos_user
}
module "vault" {
source = "app.terraform.io/hashicorp-qti/aws-vault/enos"
project_name = "qti-enos-boundary"
environment = var.environment
common_tags = {
"Project" : "Enos",
"Project Name" : "qti-enos-boundary",
"Enos User" : var.enos_user,
"Environment" : var.environment
}
ssh_aws_keypair = var.aws_ssh_keypair_name
}
module "test_smoke" {
source = "./modules/test_smoke"
}
+7 -5
View File
@@ -79,7 +79,7 @@ scenario "e2e_target" {
}
}
step "launch_targets" {
step "create_target" {
module = module.target
depends_on = [step.create_base_infra]
@@ -89,14 +89,15 @@ scenario "e2e_target" {
enos_user = var.enos_user
instance_type = var.target_instance_type
vpc_id = step.create_base_infra.vpc_id
target_count = 1
}
}
step "run_e2e_targets_test" {
step "run_e2e_test" {
module = module.test_e2e
depends_on = [
step.create_boundary_cluster,
step.launch_targets
step.create_target
]
variables {
@@ -107,11 +108,12 @@ scenario "e2e_target" {
auth_password = step.create_boundary_cluster.auth_password
local_boundary_dir = local.local_boundary_dir
aws_ssh_private_key_path = local.aws_ssh_private_key_path
target_ips = step.launch_targets.target_ips
target_ips = step.create_target.target_ips
target_user = "ubuntu"
}
}
output "test_results" {
value = step.run_e2e_targets_test.test_results
value = step.run_e2e_test.test_results
}
}
+145
View File
@@ -0,0 +1,145 @@
scenario "e2e_vault" {
terraform_cli = terraform_cli.default
terraform = terraform.default
providers = [
provider.aws.default,
provider.enos.default
]
matrix {
builder = ["local", "crt"]
}
locals {
aws_ssh_private_key_path = abspath(var.aws_ssh_private_key_path)
boundary_install_dir = abspath(var.boundary_install_dir)
local_boundary_dir = abspath(var.local_boundary_dir)
build_path = {
"local" = "/tmp",
"crt" = var.crt_bundle_path == null ? null : abspath(var.crt_bundle_path)
}
}
step "check_jq_is_installed" {
module = module.binary_finder
variables {
name = "jq"
}
}
step "find_azs" {
module = module.az_finder
variables {
instance_type = [
var.worker_instance_type,
var.controller_instance_type
]
}
}
step "create_db_password" {
module = module.random_stringifier
}
step "build_boundary" {
module = matrix.builder == "crt" ? module.build_crt : module.build_local
variables {
path = local.build_path[matrix.builder]
}
}
step "create_base_infra" {
module = module.infra
variables {
availability_zones = step.find_azs.availability_zones
}
}
step "create_boundary_cluster" {
module = module.boundary
depends_on = [
step.create_base_infra,
step.build_boundary
]
variables {
boundary_install_dir = local.boundary_install_dir
controller_instance_type = var.controller_instance_type
controller_count = var.controller_count
db_pass = step.create_db_password.string
kms_key_arn = step.create_base_infra.kms_key_arn
local_artifact_path = step.build_boundary.artifact_path
ubuntu_ami_id = step.create_base_infra.ami_ids["ubuntu"]["amd64"]
vpc_id = step.create_base_infra.vpc_id
worker_count = var.worker_count
worker_instance_type = var.worker_instance_type
}
}
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"
sg_additional_ips = step.create_boundary_cluster.controller_ips
unseal_method = "awskms"
vault_release = {
version = "1.11.0"
edition = "oss"
}
vpc_id = step.create_base_infra.vpc_id
}
}
step "create_target" {
module = module.target
depends_on = [step.create_base_infra]
variables {
ami_id = step.create_base_infra.ami_ids["ubuntu"]["amd64"]
aws_ssh_keypair_name = var.aws_ssh_keypair_name
enos_user = var.enos_user
instance_type = var.target_instance_type
vpc_id = step.create_base_infra.vpc_id
target_count = 1
}
}
step "run_e2e_test" {
module = module.test_e2e
depends_on = [
step.create_boundary_cluster,
step.create_target,
step.create_vault_cluster
]
variables {
test_package = "github.com/hashicorp/boundary/testing/internal/e2e/credential/vault"
alb_boundary_api_addr = step.create_boundary_cluster.alb_boundary_api_addr
auth_method_id = step.create_boundary_cluster.auth_method_id
auth_login_name = step.create_boundary_cluster.auth_login_name
auth_password = step.create_boundary_cluster.auth_password
local_boundary_dir = local.local_boundary_dir
aws_ssh_private_key_path = local.aws_ssh_private_key_path
target_ips = step.create_target.target_ips
target_user = "ubuntu"
vault_addr = step.create_vault_cluster.instance_public_ips[0]
vault_root_token = step.create_vault_cluster.vault_root_token
}
}
output "test_results" {
value = step.run_e2e_test.test_results
}
}
+6
View File
@@ -103,3 +103,9 @@ variable "skip_failing_bats_tests" {
type = string
default = "false"
}
variable "vault_instance_type" {
description = "Instance type for test target nodes"
type = string
default = "t3a.small"
}
+18 -1
View File
@@ -30,6 +30,10 @@ variable "local_boundary_dir" {
description = "Local Path to boundary executable"
type = string
}
variable "target_user" {
description = "SSH username for target"
type = string
}
variable "aws_ssh_private_key_path" {
description = "Local Path to key used to SSH onto created hosts"
type = string
@@ -38,9 +42,20 @@ variable "target_ips" {
description = "List of IP Addresses of created hosts"
type = list(string)
}
variable "vault_addr" {
description = "URL of Vault instance"
type = string
default = ""
}
variable "vault_root_token" {
description = "Root token for vault instance"
type = string
default = ""
}
locals {
aws_ssh_private_key_path = abspath(var.aws_ssh_private_key_path)
vault_addr = var.vault_addr != "" ? "http://${var.vault_addr}:8200" : ""
}
resource "enos_local_exec" "run_e2e_test" {
@@ -50,8 +65,10 @@ resource "enos_local_exec" "run_e2e_test" {
E2E_PASSWORD_ADMIN_LOGIN_NAME = var.auth_login_name,
E2E_PASSWORD_ADMIN_PASSWORD = var.auth_password,
E2E_TARGET_IP = var.target_ips[0],
E2E_SSH_USER = "ubuntu"
E2E_SSH_USER = var.target_user,
E2E_SSH_KEY_PATH = local.aws_ssh_private_key_path,
VAULT_ADDR = local.vault_addr,
VAULT_TOKEN = var.vault_root_token
}
inline = ["PATH=\"${var.local_boundary_dir}:$PATH\" go test -v ${var.test_package}"]
+7 -1
View File
@@ -30,7 +30,13 @@ export E2E_PASSWORD_ADMIN_PASSWORD= # e.g. "password"
export E2E_TARGET_IP= # e.g. 192.168.0.1
export E2E_SSH_KEY_PATH= # e.g. /Users/username/key.pem
export E2E_SSH_USER= # e.g. ubuntu
export E2E_SSH_PORT= # e.g. 22
# For e2e/credential/vault
export VAULT_ADDR= # e.g. http://127.0.0.1:8200
export VAULT_TOKEN=
export E2E_TARGET_IP= # e.g. 192.168.0.1
export E2E_SSH_KEY_PATH= # e.g. /Users/username/key.pem
export E2E_SSH_USER= # e.g. ubuntu
```
Then, run...
+8 -11
View File
@@ -5,11 +5,13 @@ import (
"context"
"errors"
"fmt"
"testing"
"github.com/hashicorp/boundary/api"
"github.com/hashicorp/boundary/api/authmethods"
"github.com/hashicorp/boundary/testing/internal/e2e"
"github.com/kelseyhightower/envconfig"
"github.com/stretchr/testify/require"
)
type config struct {
@@ -80,22 +82,17 @@ func NewApiClient() (*api.Client, error) {
}
// AuthenticateCli uses the cli to authenticate the specified Boundary instance.
// Returns the result of the command.
func AuthenticateCli() *e2e.CommandResult {
func AuthenticateCli(t testing.TB) {
c, err := loadConfig()
if err != nil {
return &e2e.CommandResult{Err: err}
}
require.NoError(t, err)
err = c.validate()
if err != nil {
return &e2e.CommandResult{Err: err}
}
require.NoError(t, err)
return e2e.RunCommand(
"boundary", "authenticate", "password",
output := e2e.RunCommand("boundary", e2e.WithArgs("authenticate", "password",
"-addr", c.Address,
"-auth-method-id", c.AuthMethodId,
"-login-name", c.AdminLoginName,
"-password", "env://E2E_PASSWORD_ADMIN_PASSWORD",
)
))
require.NoError(t, output.Err, string(output.Stderr))
}
+58
View File
@@ -0,0 +1,58 @@
package boundary
import (
"context"
"encoding/json"
"testing"
"github.com/hashicorp/boundary/api"
"github.com/hashicorp/boundary/api/scopes"
"github.com/hashicorp/boundary/testing/internal/e2e"
"github.com/stretchr/testify/require"
)
// CreateNewOrgApi creates a new organization in boundary using the go api.
// Returns the id of the new org.
func CreateNewOrgApi(t testing.TB, ctx context.Context, client *api.Client) string {
scopeClient := scopes.NewClient(client)
newOrgResult, err := scopeClient.Create(ctx, "global", scopes.WithName("e2e Automated Test Org"))
require.NoError(t, err)
t.Cleanup(func() {
_, err := scopeClient.Delete(ctx, newOrgResult.Item.Id)
require.NoError(t, err)
})
return newOrgResult.Item.Id
}
// CreateNewOrgCli creates a new organization in boundary using the cli.
// Returns the id of the new org.
func CreateNewOrgCli(t testing.TB) string {
return createNewScopeCli(t, "e2e Automated Test Org", "global")
}
// CreateNewProjectCli creates a new project in boundary using the cli.
// Returns the id of the new project.
func CreateNewProjectCli(t testing.TB, scopeId string) string {
return createNewScopeCli(t, "e2e Automated Test Project", scopeId)
}
func createNewScopeCli(t testing.TB, name string, scopeId string) string {
output := e2e.RunCommand("boundary", e2e.WithArgs("scopes", "create",
"-name", name,
"-scope-id", scopeId,
"-format", "json",
))
require.NoError(t, output.Err, string(output.Stderr))
var newOrgResult scopes.ScopeCreateResult
err := json.Unmarshal(output.Stdout, &newOrgResult)
require.NoError(t, err)
t.Cleanup(func() {
output := e2e.RunCommand("boundary", e2e.WithArgs("scopes", "delete", "-id", newOrgResult.Item.Id))
require.NoError(t, output.Err, string(output.Stderr))
})
return newOrgResult.Item.Id
}
@@ -0,0 +1,419 @@
package vaultcredential_test
import (
"context"
"encoding/json"
"errors"
"fmt"
"os"
"strconv"
"strings"
"testing"
"github.com/hashicorp/boundary/api/credentiallibraries"
"github.com/hashicorp/boundary/api/credentialstores"
"github.com/hashicorp/boundary/api/hostcatalogs"
"github.com/hashicorp/boundary/api/hosts"
"github.com/hashicorp/boundary/api/hostsets"
"github.com/hashicorp/boundary/api/scopes"
"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"` // e.g. 192.168.0.1
TargetSshUser string `envconfig:"E2E_SSH_USER"` // e.g. ubuntu
TargetSshKeyPath string `envconfig:"E2E_SSH_KEY_PATH"` // 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 (c *config) validate() error {
if c.TargetIp == "" {
return errors.New("TargetIp is empty. Set environment variable: E2E_TARGET_IP")
}
if c.TargetSshUser == "" {
return errors.New("TargetSshUser is empty. Set environment variable: E2E_SSH_USER")
}
if c.TargetSshKeyPath == "" {
return errors.New("TargetSshKeyPath is empty. Set environment variable: E2E_SSH_KEY_PATH")
}
if c.TargetPort == "" {
return errors.New("TargetPort is empty. Set environment variable: E2E_SSH_PORT")
}
return nil
}
func loadConfig() (*config, error) {
var c config
err := envconfig.Process("", &c)
if err != nil {
return nil, err
}
return &c, err
}
// TestCreateVaultCredentialStoreCli 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 TestCreateVaultCredentialStoreCli(t *testing.T) {
e2e.MaybeSkipTest(t)
c, err := loadConfig()
require.NoError(t, err)
err = c.validate()
require.NoError(t, err)
// Configure vault
vaultAddr, boundaryPolicyName := vault.Setup(t)
output := e2e.RunCommand("vault", e2e.WithArgs("secrets", "enable", "-path="+c.VaultSecretPath, "kv-v2"))
require.NoError(t, output.Err, string(output.Stderr))
t.Cleanup(func() {
output := e2e.RunCommand("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("vault", e2e.WithArgs("token", "create",
"-no-default-policy=true",
"-policy="+boundaryPolicyName,
"-policy="+credentialPolicyName,
"-orphan=true",
"-period=20m",
"-renewable=true",
"-format=json",
),
e2e.WithPipe("jq", "-r", ".auth.client_token"),
)
require.NoError(t, output.Err, string(output.Stderr))
credStoreToken := strings.TrimSpace(string(output.Stdout))
t.Log("Created Vault Cred Store Token")
// Authenticate boundary cli
boundary.AuthenticateCli(t)
// Create an org
newOrgId := boundary.CreateNewOrgCli(t)
t.Logf("Created Org Id: %s", newOrgId)
// Create a project
newProjectId := boundary.CreateNewProjectCli(t, newOrgId)
t.Logf("Created Project Id: %s", newProjectId)
// Create a credential store
output = e2e.RunCommand("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)
newCredentialStore := newCredentialStoreResult.Item
t.Logf("Created Credential Store: %s", newCredentialStore.Id)
// Create a credential library
output = e2e.RunCommand("boundary", e2e.WithArgs("credential-libraries", "create", "vault",
"-credential-store-id", newCredentialStore.Id,
"-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)
newCredentialLibrary := newCredentialLibraryResult.Item
t.Logf("Created Credential Library: %s", newCredentialLibrary.Id)
// Create a host catalog
output = e2e.RunCommand("boundary", e2e.WithArgs("host-catalogs", "create", "static",
"-scope-id", newProjectId,
"-name", "e2e Automated Test Host Catalog",
"-format", "json",
))
require.NoError(t, output.Err, string(output.Stderr))
var newHostCatalogResult hostcatalogs.HostCatalogCreateResult
err = json.Unmarshal(output.Stdout, &newHostCatalogResult)
require.NoError(t, err)
newHostCatalog := newHostCatalogResult.Item
t.Logf("Created Host Catalog: %s", newHostCatalog.Id)
// Create a host set and add to catalog
output = e2e.RunCommand("boundary", e2e.WithArgs("host-sets", "create", "static",
"-host-catalog-id", newHostCatalog.Id,
"-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)
newHostSet := newHostSetResult.Item
t.Logf("Created Host Set: %s", newHostSet.Id)
// Create a host
output = e2e.RunCommand("boundary", e2e.WithArgs("hosts", "create", "static",
"-host-catalog-id", newHostCatalog.Id,
"-name", c.TargetIp,
"-address", c.TargetIp,
"-format", "json"),
)
require.NoError(t, output.Err, string(output.Stderr))
var newHostResult hosts.HostCreateResult
err = json.Unmarshal(output.Stdout, &newHostResult)
require.NoError(t, err)
newHost := newHostResult.Item
t.Logf("Created Host: %s", newHost.Id)
// Add host to host set
output = e2e.RunCommand("boundary", e2e.WithArgs("host-sets", "add-hosts",
"-id", newHostSet.Id,
"-host", newHost.Id,
))
require.NoError(t, output.Err, string(output.Stderr))
// Create Target
output = e2e.RunCommand("boundary", e2e.WithArgs("targets", "create", "tcp",
"-scope-id", newProjectId,
"-default-port", c.TargetPort,
"-name", "e2e Automated Test Target",
"-format", "json",
))
require.NoError(t, output.Err, string(output.Stderr))
var newTargetResult targets.TargetCreateResult
err = json.Unmarshal(output.Stdout, &newTargetResult)
require.NoError(t, err)
newTarget := newTargetResult.Item
t.Logf("Created Target: %s", newTarget.Id)
// Add host set to target
output = e2e.RunCommand("boundary", e2e.WithArgs("targets", "add-host-sources",
"-id", newTarget.Id,
"-host-source", newHostSet.Id,
))
require.NoError(t, output.Err, string(output.Stderr))
// Add brokered credentials to target
output = e2e.RunCommand("boundary", e2e.WithArgs("targets", "add-credential-sources",
"-id", newTarget.Id,
"-brokered-credential-source", newCredentialLibrary.Id,
))
require.NoError(t, output.Err, string(output.Stderr))
// Get credentials for target
output = e2e.RunCommand("boundary", e2e.WithArgs("targets", "authorize-session", "-id", newTarget.Id, "-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("boundary", e2e.WithArgs("connect",
"-target-id", newTarget.Id,
"-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")
}
// TestCreateVaultCredentialStoreApi uses the boundary go api along with the vault cli to
// add secrets management for a target. The test sets up vault as a creential stores and creates
// a set of credentials in vault that is attached to a target.
func TestCreateVaultCredentialStoreApi(t *testing.T) {
e2e.MaybeSkipTest(t)
c, err := loadConfig()
require.NoError(t, err)
err = c.validate()
require.NoError(t, err)
// Configure vault
vaultAddr, boundaryPolicyName := vault.Setup(t)
output := e2e.RunCommand("vault", e2e.WithArgs("secrets", "enable", "-path="+c.VaultSecretPath, "kv-v2"))
require.NoError(t, output.Err, string(output.Stderr))
t.Cleanup(func() {
output := e2e.RunCommand("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("vault", e2e.WithArgs("token", "create",
"-no-default-policy=true",
"-policy="+boundaryPolicyName,
"-policy="+credentialPolicyName,
"-orphan=true",
"-period=20m",
"-renewable=true",
"-format=json",
),
e2e.WithPipe("jq", "-r", ".auth.client_token"),
)
require.NoError(t, output.Err, string(output.Stderr))
credStoreToken := strings.TrimSpace(string(output.Stdout))
t.Log("Created Vault Cred Store Token")
// Create boundary api client
client, err := boundary.NewApiClient()
require.NoError(t, err)
ctx := context.Background()
// Create an org
scopeClient := scopes.NewClient(client)
newOrgResult, err := scopeClient.Create(ctx, "global", scopes.WithName("e2e Automated Test Org"))
require.NoError(t, err)
newOrg := newOrgResult.Item
t.Cleanup(func() {
_, err := scopeClient.Delete(ctx, newOrg.Id)
require.NoError(t, err)
})
t.Logf("Created Org Id: %s", newOrg.Id)
// Create a project
newProjectResult, err := scopeClient.Create(ctx, newOrg.Id, scopes.WithName("e2e Automated Test Project"))
require.NoError(t, err)
newProject := newProjectResult.Item
t.Logf("Created Project Id: %s", newProject.Id)
// Create a credential store
csClient := credentialstores.NewClient(client)
newCredentialStoreResult, err := csClient.Create(ctx, "vault", newProject.Id,
credentialstores.WithVaultCredentialStoreAddress(vaultAddr),
credentialstores.WithVaultCredentialStoreToken(credStoreToken),
)
require.NoError(t, err)
newCredentialStore := newCredentialStoreResult.Item
t.Logf("Created Credential Store: %s", newCredentialStore.Id)
// Create a credential library
clClient := credentiallibraries.NewClient(client)
newCredentialLibraryResult, err := clClient.Create(ctx, newCredentialStore.Id,
credentiallibraries.WithVaultCredentialLibraryPath(c.VaultSecretPath+"/data/"+secretName),
credentiallibraries.WithCredentialType("ssh_private_key"),
)
require.NoError(t, err)
newCredentialLibrary := newCredentialLibraryResult.Item
t.Logf("Created Credential Library: %s", newCredentialLibrary.Id)
// Create a host catalog
hcClient := hostcatalogs.NewClient(client)
newHostCatalogResult, err := hcClient.Create(ctx, "static", newProject.Id,
hostcatalogs.WithName("e2e Automated Test Host Catalog"),
)
require.NoError(t, err)
newHostCatalog := newHostCatalogResult.Item
t.Logf("Created Host Catalog: %s", newHostCatalog.Id)
// Create a host set and add to catalog
hsClient := hostsets.NewClient(client)
newHostSetResult, err := hsClient.Create(ctx, newHostCatalog.Id)
require.NoError(t, err)
newHostSet := newHostSetResult.Item
t.Logf("Created Host Set: %s", newHostSet.Id)
// Create a host
hClient := hosts.NewClient(client)
newHostResult, err := hClient.Create(ctx, newHostCatalog.Id,
hosts.WithName(c.TargetIp),
hosts.WithStaticHostAddress(c.TargetIp),
)
require.NoError(t, err)
newHost := newHostResult.Item
t.Logf("Created Host: %s", newHost.Id)
// Add host to host set
_, err = hsClient.AddHosts(ctx, newHostSet.Id, 0, []string{newHost.Id}, hostsets.WithAutomaticVersioning(true))
require.NoError(t, err)
// Create a target
tClient := targets.NewClient(client)
targetPort, err := strconv.ParseInt(c.TargetPort, 10, 32)
newTargetResult, err := tClient.Create(ctx, "tcp", newProject.Id,
targets.WithName("e2e Automated Test Target"),
targets.WithTcpTargetDefaultPort(uint32(targetPort)),
)
require.NoError(t, err)
newTarget := newTargetResult.Item
t.Logf("Created Target: %s", newTarget.Id)
// Add host set to target
_, err = tClient.AddHostSources(ctx, newTarget.Id, 0,
[]string{newHostSet.Id},
targets.WithAutomaticVersioning(true),
)
require.NoError(t, err)
// Add brokered credentials to target
_, err = tClient.AddCredentialSources(ctx, newTarget.Id, 0,
targets.WithBrokeredCredentialSourceIds([]string{newCredentialLibrary.Id}),
targets.WithAutomaticVersioning(true),
)
require.NoError(t, err)
// Get credentials for target
newSessionAuthorizationResult, err := tClient.AuthorizeSession(ctx, newTarget.Id)
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")
}
+83 -8
View File
@@ -4,11 +4,37 @@ import (
"bytes"
"errors"
"fmt"
"io"
"os"
"os/exec"
"testing"
)
// Option is a func that sets optional attributes for a call. This does not need
// to be used directly, but instead option arguments are built from the
// functions in this package. WithX options set a value to that given in the
// argument; DefaultX options indicate that the value should be set to its
// default. When an API call is made options are processed in ther order they
// appear in the function call, so for a given argument X, a succession of WithX
// or DefaultX calls will result in the last call taking effect.
type Option func(*options)
type options struct {
withArgs []string
withPipe []string
}
func getOpts(opt ...Option) options {
opts := options{}
for _, o := range opt {
if o != nil {
o(&opts)
}
}
return opts
}
// CommandResult encapsulates the output from running an external command
type CommandResult struct {
Stdout []byte
@@ -20,16 +46,48 @@ type CommandResult struct {
const EnvToCheckSkip = "E2E_PASSWORD_AUTH_METHOD_ID"
// RunCommand executes external commands on the system. Returns the results
// of running the provided command. CommandResult is always valid even if there is
// an error.
func RunCommand(name string, args ...string) *CommandResult {
// of running the provided command.
//
// RunCommand("ls")
// RunCommand("ls", WithArgs("-al", "/path"))
// RunCommand("ls", WithArgs("-al", "/path"), WithPipe("grep", "file"))
//
// CommandResult is always valid even if there is an error.
func RunCommand(command string, opt ...Option) *CommandResult {
var outbuf, errbuf bytes.Buffer
var err error
var c1, c2 *exec.Cmd
cmd := exec.Command(name, args...)
cmd.Stdout = &outbuf
cmd.Stderr = &errbuf
opts := getOpts(opt...)
err := cmd.Run()
if opts.withArgs == nil {
c1 = exec.Command(command)
} else {
c1 = exec.Command(command, opts.withArgs...)
}
if opts.withPipe == nil {
c1.Stdout = &outbuf
c1.Stderr = &errbuf
err = c1.Run()
} else {
pipeCommand := opts.withPipe[0]
pipeArgs := opts.withPipe[1:]
c2 = exec.Command(pipeCommand, pipeArgs...)
r, w := io.Pipe()
c1.Stdout = w
c2.Stdin = r
c2.Stdout = &outbuf
c2.Stderr = &errbuf
c1.Start()
c2.Start()
c1.Wait()
w.Close()
c2.Wait()
}
var ee *exec.ExitError
var exitCode int
@@ -45,7 +103,24 @@ func RunCommand(name string, args ...string) *CommandResult {
}
}
func MaybeSkipTest(t *testing.T) {
// WithArgs is an option to RunCommand that allows the user to specify arguments
// for the provided command
func WithArgs(args ...string) Option {
return func(o *options) {
o.withArgs = args
}
}
// WithPipe is an option to RunCommand that allows the user to specify a command+arguments
// to pipe to
func WithPipe(command ...string) Option {
return func(o *options) {
o.withPipe = command
}
}
// MaybeSkipTest is a check used at the start of the test to determine if the test should run
func MaybeSkipTest(t testing.TB) {
if _, ok := os.LookupEnv(EnvToCheckSkip); !ok {
t.Skip(fmt.Sprintf(
"Skipping test because environment variable '%s' is not set. This is needed for e2e tests.",
+29 -66
View File
@@ -4,6 +4,7 @@ import (
"context"
"encoding/json"
"errors"
"strconv"
"strings"
"testing"
@@ -15,7 +16,6 @@ import (
"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"
)
@@ -64,137 +64,99 @@ func TestConnectTargetCli(t *testing.T) {
err = c.validate()
require.NoError(t, err)
output := boundary.AuthenticateCli()
require.NoError(t, output.Err, string(output.Stderr))
boundary.AuthenticateCli(t)
// Create an org
output = e2e.RunCommand(
"boundary", "scopes", "create",
"-name", "e2e Automated Test Org",
"-format", "json",
)
require.NoError(t, output.Err, string(output.Stderr))
var newOrgResult scopes.ScopeCreateResult
err = json.Unmarshal(output.Stdout, &newOrgResult)
require.NoError(t, err)
newOrg := newOrgResult.Item
t.Cleanup(func() {
output := e2e.RunCommand("boundary", "scopes", "delete", "-id", newOrg.Id)
require.NoError(t, output.Err, string(output.Stderr))
})
t.Logf("Created Org Id: %s", newOrg.Id)
newOrgId := boundary.CreateNewOrgCli(t)
t.Logf("Created Org Id: %s", newOrgId)
// Create a project
output = e2e.RunCommand(
"boundary", "scopes", "create",
"-scope-id", newOrg.Id,
"-name", "e2e Automated Test Project",
"-format", "json",
)
require.NoError(t, output.Err, string(output.Stderr))
var newProjectResult scopes.ScopeCreateResult
err = json.Unmarshal(output.Stdout, &newProjectResult)
require.NoError(t, err)
newProject := newProjectResult.Item
t.Cleanup(func() {
output := e2e.RunCommand("boundary", "scopes", "delete", "-id", newProject.Id)
require.NoError(t, output.Err, string(output.Stderr))
})
t.Logf("Created Project Id: %s", newProject.Id)
newProjectId := boundary.CreateNewProjectCli(t, newOrgId)
t.Logf("Created Project Id: %s", newProjectId)
// Create a host catalog
output = e2e.RunCommand(
"boundary", "host-catalogs", "create", "static",
"-scope-id", newProject.Id,
output := e2e.RunCommand("boundary", e2e.WithArgs("host-catalogs", "create", "static",
"-scope-id", newProjectId,
"-name", "e2e Automated Test Host Catalog",
"-format", "json",
)
))
require.NoError(t, output.Err, string(output.Stderr))
var newHostCatalogResult hostcatalogs.HostCatalogCreateResult
err = json.Unmarshal(output.Stdout, &newHostCatalogResult)
require.NoError(t, err)
newHostCatalog := newHostCatalogResult.Item
t.Cleanup(func() {
output := e2e.RunCommand("boundary", "host-catalogs", "delete", "-id", newHostCatalog.Id)
output := e2e.RunCommand("boundary", e2e.WithArgs("host-catalogs", "delete", "-id", newHostCatalog.Id))
require.NoError(t, output.Err, string(output.Stderr))
})
t.Logf("Created Host Catalog: %s", newHostCatalog.Id)
// Create a host set and add to catalog
output = e2e.RunCommand(
"boundary", "host-sets", "create", "static",
output = e2e.RunCommand("boundary", e2e.WithArgs("host-sets", "create", "static",
"-host-catalog-id", newHostCatalog.Id,
"-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)
newHostSet := newHostSetResult.Item
t.Cleanup(func() {
output := e2e.RunCommand("boundary", "host-sets", "delete", "-id", newHostSet.Id)
output := e2e.RunCommand("boundary", e2e.WithArgs("host-sets", "delete", "-id", newHostSet.Id))
require.NoError(t, output.Err, string(output.Stderr))
})
t.Logf("Created Host Set: %s", newHostSet.Id)
// Create a host
output = e2e.RunCommand(
"boundary", "hosts", "create", "static",
output = e2e.RunCommand("boundary", e2e.WithArgs("hosts", "create", "static",
"-host-catalog-id", newHostCatalog.Id,
"-name", c.TargetIp,
"-address", c.TargetIp,
"-format", "json",
)
))
require.NoError(t, output.Err, string(output.Stderr))
var newHostResult hosts.HostCreateResult
err = json.Unmarshal(output.Stdout, &newHostResult)
require.NoError(t, err)
newHost := newHostResult.Item
t.Cleanup(func() {
output := e2e.RunCommand("boundary", "hosts", "delete", "-id", newHost.Id)
output := e2e.RunCommand("boundary", e2e.WithArgs("hosts", "delete", "-id", newHost.Id))
require.NoError(t, output.Err, string(output.Stderr))
})
t.Logf("Created Host: %s", newHost.Id)
// Add host to host set
output = e2e.RunCommand(
"boundary", "host-sets", "add-hosts",
"-id", newHostSet.Id,
"-host", newHost.Id,
)
output = e2e.RunCommand("boundary", e2e.WithArgs("host-sets", "add-hosts", "-id", newHostSet.Id, "-host", newHost.Id))
require.NoError(t, output.Err, string(output.Stderr))
// Create a target
output = e2e.RunCommand(
"boundary", "targets", "create", "tcp",
"-scope-id", newProject.Id,
output = e2e.RunCommand("boundary", e2e.WithArgs("targets", "create", "tcp",
"-scope-id", newProjectId,
"-default-port", c.TargetPort,
"-name", "e2e Automated Test Target",
"-format", "json",
)
))
require.NoError(t, output.Err, string(output.Stderr))
var newTargetResult targets.TargetCreateResult
err = json.Unmarshal(output.Stdout, &newTargetResult)
require.NoError(t, err)
newTarget := newTargetResult.Item
t.Cleanup(func() {
output := e2e.RunCommand("boundary", "targets", "delete", "-id", newTarget.Id)
output := e2e.RunCommand("boundary", e2e.WithArgs("targets", "delete", "-id", newTarget.Id))
require.NoError(t, output.Err, string(output.Stderr))
})
t.Logf("Created Target: %s", newTarget.Id)
// Add host set to target
output = e2e.RunCommand(
"boundary", "targets", "add-host-sources",
output = e2e.RunCommand("boundary", e2e.WithArgs("targets", "add-host-sources",
"-id", newTarget.Id,
"-host-source", newHostSet.Id,
"-host-source", newHostSet.Id),
)
require.NoError(t, output.Err, string(output.Stderr))
// Connect to target and print host's IP address
output = e2e.RunCommand(
"boundary", "connect",
output = e2e.RunCommand("boundary", e2e.WithArgs("connect",
"-target-id", newTarget.Id,
"-exec", "/usr/bin/ssh", "--",
"-l", c.TargetSshUser,
@@ -205,12 +167,12 @@ func TestConnectTargetCli(t *testing.T) {
"-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]
assert.Equal(t, c.TargetIp, hostIp, "SSH session did not return expected output")
require.Equal(t, c.TargetIp, hostIp, "SSH session did not return expected output")
t.Log("Successfully connected to target")
}
@@ -225,9 +187,9 @@ func TestCreateTargetApi(t *testing.T) {
err = c.validate()
require.NoError(t, err)
// Create boundary api client
client, err := boundary.NewApiClient()
require.NoError(t, err)
ctx := context.Background()
// Create an org
@@ -295,9 +257,10 @@ func TestCreateTargetApi(t *testing.T) {
// Create a target
tClient := targets.NewClient(client)
targetPort, err := strconv.ParseInt(c.TargetPort, 10, 32)
newTargetResult, err := tClient.Create(ctx, "tcp", newProject.Id,
targets.WithName("e2e Automated Test Target"),
targets.WithTcpTargetDefaultPort(22),
targets.WithTcpTargetDefaultPort(uint32(targetPort)),
)
require.NoError(t, err)
newTarget := newTargetResult.Item
@@ -0,0 +1,23 @@
path "auth/token/lookup-self" {
capabilities = ["read"]
}
path "auth/token/renew-self" {
capabilities = ["update"]
}
path "auth/token/revoke-self" {
capabilities = ["update"]
}
path "sys/leases/renew" {
capabilities = ["update"]
}
path "sys/leases/revoke" {
capabilities = ["update"]
}
path "sys/capabilities-self" {
capabilities = ["update"]
}
+100
View File
@@ -0,0 +1,100 @@
// Package vault provides methods for commonly used vault actions that are used in end-to-end tests.
package vault
import (
"errors"
"fmt"
"os"
"path"
"runtime"
"testing"
"github.com/hashicorp/boundary/testing/internal/e2e"
"github.com/kelseyhightower/envconfig"
"github.com/stretchr/testify/require"
)
type config struct {
VaultAddr string `envconfig:"VAULT_ADDR"` // e.g. "http://127.0.0.1:8200"
VaultToken string `envconfig:"VAULT_TOKEN"`
}
func (c *config) validate() error {
if c.VaultAddr == "" {
return errors.New("VaultAddr is empty. Set environment variable: VAULT_ADDR")
}
if c.VaultToken == "" {
return errors.New("VaultToken is empty. Set environment variable: VAULT_TOKEN")
}
return nil
}
func loadConfig() (*config, error) {
var c config
err := envconfig.Process("", &c)
if err != nil {
return nil, err
}
return &c, err
}
// Setup verifies if appropriate credentials are set and adds the boundary controller
// policy to vault. Returns the vault address.
func Setup(t testing.TB) (string, string) {
c, err := loadConfig()
require.NoError(t, err)
err = c.validate()
require.NoError(t, err)
_, filename, _, ok := runtime.Caller(0)
require.True(t, ok)
policyName := "boundary-controller"
output := e2e.RunCommand("vault", e2e.WithArgs("policy", "write", policyName,
path.Join(path.Dir(filename), "boundary-controller-policy.hcl")),
)
require.NoError(t, output.Err, string(output.Stderr))
t.Cleanup(func() {
output := e2e.RunCommand("vault", e2e.WithArgs("policy", "delete", policyName))
require.NoError(t, output.Err, string(output.Stderr))
})
return c.VaultAddr, policyName
}
// CreateKvPrivateKeyCredential creates a private key credential in vault and creates a vault policy
// to be able to read that credential. Returns the name of the policy.
func CreateKvPrivateKeyCredential(t testing.TB, secretName string, secretPath string, user string, keyPath string) string {
// Create policy file to read secret
kvPolicyFileName := "kv-policy-test.hcl"
kvPolicyFilePath := fmt.Sprintf("%s/%s", t.TempDir(), kvPolicyFileName)
f, err := os.Create(kvPolicyFilePath)
require.NoError(t, err)
_, err = f.WriteString(fmt.Sprintf("path \"%s/data/%s\" { capabilities = [\"read\"] }",
secretPath,
secretName,
))
require.NoError(t, err)
// Add policy to vault
policyName := "kv-read"
output := e2e.RunCommand("vault", e2e.WithArgs("policy", "write", policyName, kvPolicyFilePath))
require.NoError(t, output.Err, string(output.Stderr))
t.Cleanup(func() {
output := e2e.RunCommand("vault", e2e.WithArgs("policy", "delete", policyName))
require.NoError(t, output.Err, string(output.Stderr))
})
// Create secret
output = e2e.RunCommand("vault", e2e.WithArgs("kv", "put",
"-mount", secretPath,
secretName,
"username="+user,
"private_key=@"+keyPath,
))
require.NoError(t, output.Err, string(output.Stderr))
return policyName
}