test(e2e): Add test for vault oidc provider (#5384)
* test(e2e): Simplify vault address in docker network * test(e2e): Add oidc test using vault * Update enos/modules/docker_vault/main.tf Co-authored-by: Johan Brandhorst-Satzkorn <johan.brandhorst@gmail.com> --------- Co-authored-by: Johan Brandhorst-Satzkorn <johan.brandhorst@gmail.com>
This commit is contained in:
1 parent
20f11ab01b
commit
6ed077c69e
6 files changed
+334
-16
No files matched your search
@@ -3,6 +3,8 @@
|
||||
|
||||
# For this scenario to work, add the following line to /etc/hosts
|
||||
# 127.0.0.1 localhost boundary
|
||||
# 127.0.0.1 localhost worker
|
||||
# 127.0.0.1 localhost vault
|
||||
|
||||
scenario "e2e_docker_base_with_vault" {
|
||||
terraform_cli = terraform_cli.default
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"api_addr": "https://127.0.0.1:8300",
|
||||
"storage": {
|
||||
"file": {
|
||||
"path": "/vault/file"
|
||||
@@ -7,7 +8,7 @@
|
||||
"listener": [
|
||||
{
|
||||
"tcp": {
|
||||
"address": "0.0.0.0:8200",
|
||||
"address": "0.0.0.0:8300",
|
||||
"tls_disable": true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,6 +37,11 @@ variable "vault_port" {
|
||||
type = string
|
||||
default = "8300"
|
||||
}
|
||||
variable "vault_port_internal" {
|
||||
description = "Internal Port to use"
|
||||
type = string
|
||||
default = "8300"
|
||||
}
|
||||
|
||||
resource "docker_image" "vault" {
|
||||
name = var.image_name
|
||||
@@ -48,7 +53,7 @@ resource "docker_container" "vault" {
|
||||
name = var.container_name
|
||||
command = ["vault", "server", "-config", "/vault/config.d/config.json"]
|
||||
ports {
|
||||
internal = 8200
|
||||
internal = var.vault_port_internal
|
||||
external = var.vault_port
|
||||
}
|
||||
mounts {
|
||||
@@ -128,11 +133,11 @@ resource "enos_local_exec" "check_health" {
|
||||
}
|
||||
|
||||
output "address" {
|
||||
value = "0.0.0.0"
|
||||
value = "http://${var.container_name}:${var.vault_port}"
|
||||
}
|
||||
|
||||
output "address_internal" {
|
||||
value = var.container_name
|
||||
value = "http://${var.container_name}:${var.vault_port_internal}"
|
||||
}
|
||||
|
||||
output "token" {
|
||||
|
||||
@@ -286,12 +286,9 @@ resource "enos_local_exec" "get_go_version" {
|
||||
}
|
||||
|
||||
locals {
|
||||
go_version = var.go_version == "" ? enos_local_exec.get_go_version[0].stdout : var.go_version
|
||||
image_name = trimspace("${var.docker_mirror}/library/golang:${local.go_version}")
|
||||
|
||||
go_version = var.go_version == "" ? enos_local_exec.get_go_version[0].stdout : var.go_version
|
||||
image_name = trimspace("${var.docker_mirror}/library/golang:${local.go_version}")
|
||||
aws_ssh_private_key_path = abspath(var.aws_ssh_private_key_path)
|
||||
vault_addr = var.vault_addr != "" ? "http://${var.vault_addr}:${var.vault_port}" : ""
|
||||
vault_addr_internal = var.vault_addr_internal != "" ? "http://${var.vault_addr_internal}:8200" : local.vault_addr
|
||||
package_name = reverse(split("/", var.test_package))[0]
|
||||
}
|
||||
|
||||
@@ -317,10 +314,10 @@ resource "enos_local_exec" "run_e2e_test" {
|
||||
E2E_SSH_USER = var.target_user
|
||||
E2E_SSH_KEY_PATH = local.aws_ssh_private_key_path
|
||||
E2E_SSH_CA_KEY = var.target_ca_key
|
||||
VAULT_ADDR = local.vault_addr
|
||||
VAULT_ADDR_INTERNAL = local.vault_addr_internal
|
||||
VAULT_ADDR = var.vault_addr
|
||||
VAULT_ADDR_INTERNAL = var.vault_addr_internal
|
||||
VAULT_TOKEN = var.vault_root_token
|
||||
E2E_VAULT_ADDR = local.vault_addr_internal
|
||||
E2E_VAULT_ADDR = var.vault_addr_internal
|
||||
E2E_BUCKET_NAME = var.bucket_name
|
||||
E2E_BUCKET_ENDPOINT_URL = var.bucket_endpoint_url
|
||||
E2E_BUCKET_USER_ID = var.bucket_user_id
|
||||
|
||||
@@ -196,8 +196,6 @@ variable "worker_tag_egress" {
|
||||
|
||||
locals {
|
||||
aws_ssh_private_key_path = abspath(var.aws_ssh_private_key_path)
|
||||
vault_addr = var.vault_addr != "" ? "http://${var.vault_addr}:${var.vault_port}" : ""
|
||||
vault_addr_internal = var.vault_addr_internal != "" ? "http://${var.vault_addr_internal}:8200" : local.vault_addr
|
||||
aws_host_set_ips = jsonencode(var.aws_host_set_ips)
|
||||
}
|
||||
|
||||
@@ -213,9 +211,9 @@ resource "enos_local_exec" "run_e2e_test" {
|
||||
E2E_SSH_KEY_PATH = local.aws_ssh_private_key_path
|
||||
E2E_SSH_CA_KEY = var.target_ca_key
|
||||
E2E_SSH_CA_KEY_PUBLIC = var.target_ca_key_public
|
||||
VAULT_ADDR = local.vault_addr
|
||||
VAULT_ADDR = var.vault_addr
|
||||
VAULT_TOKEN = var.vault_root_token
|
||||
E2E_VAULT_ADDR = local.vault_addr_internal
|
||||
E2E_VAULT_ADDR = var.vault_addr_internal
|
||||
E2E_AWS_ACCESS_KEY_ID = var.aws_access_key_id
|
||||
E2E_AWS_SECRET_ACCESS_KEY = var.aws_secret_access_key
|
||||
E2E_AWS_HOST_SET_FILTER = var.aws_host_set_filter
|
||||
|
||||
@@ -0,0 +1,315 @@
|
||||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
package base_with_vault_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/boundary/api/authmethods"
|
||||
"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/require"
|
||||
)
|
||||
|
||||
func TestAuthMethodOidcVault(t *testing.T) {
|
||||
e2e.MaybeSkipTest(t)
|
||||
c, err := loadTestConfig()
|
||||
require.NoError(t, err)
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
t.Log("Setting up Vault OIDC provider...")
|
||||
// Configure vault authentication: Enable the userpass auth method at the
|
||||
// default path
|
||||
output := e2e.RunCommand(ctx, "vault", e2e.WithArgs("auth", "enable", "userpass"))
|
||||
require.NoError(t, output.Err, string(output.Stderr))
|
||||
t.Cleanup(func() {
|
||||
output := e2e.RunCommand(ctx, "vault", e2e.WithArgs("auth", "disable", "userpass"))
|
||||
require.NoError(t, output.Err, string(output.Stderr))
|
||||
})
|
||||
|
||||
// Configure vault authentication: Create a policy granting the user read capabilities on the authorization endpoint
|
||||
authPolicyPath := fmt.Sprintf("%s/%s", t.TempDir(), "auth-policy.hcl")
|
||||
_, err = os.Create(authPolicyPath)
|
||||
require.NoError(t, err)
|
||||
authPolicyFile, err := os.OpenFile(authPolicyPath, os.O_APPEND|os.O_WRONLY, 0o644)
|
||||
require.NoError(t, err)
|
||||
_, err = authPolicyFile.WriteString(fmt.Sprintf(
|
||||
`path "identity/oidc/provider/my-provider/authorize" { capabilities = [ "read" ] }%s`,
|
||||
"\n",
|
||||
))
|
||||
require.NoError(t, err)
|
||||
authPolicyName := vault.WritePolicy(t, ctx, authPolicyPath)
|
||||
t.Cleanup(func() {
|
||||
output := e2e.RunCommand(ctx, "vault",
|
||||
e2e.WithArgs("policy", "delete", authPolicyName),
|
||||
)
|
||||
require.NoError(t, output.Err, string(output.Stderr))
|
||||
})
|
||||
|
||||
// Configuration vault authentication: Create a user with the auth policy
|
||||
userName := "end-user"
|
||||
userPassword := "password"
|
||||
output = e2e.RunCommand(ctx, "vault",
|
||||
e2e.WithArgs(
|
||||
"write",
|
||||
fmt.Sprintf("auth/userpass/users/%s", userName),
|
||||
fmt.Sprintf("password=%s", userPassword),
|
||||
fmt.Sprintf("token_policies=%s", authPolicyName),
|
||||
"token_ttl=1h",
|
||||
),
|
||||
)
|
||||
require.NoError(t, output.Err, string(output.Stderr))
|
||||
|
||||
// Create an identity entity
|
||||
output = e2e.RunCommand(ctx, "vault",
|
||||
e2e.WithArgs(
|
||||
"write",
|
||||
"identity/entity",
|
||||
fmt.Sprintf("name=%s", userName),
|
||||
`metadata=email=vault@hashicorp.com`,
|
||||
`metadata=phone_number=123-456-7890`,
|
||||
"disabled=false",
|
||||
),
|
||||
)
|
||||
require.NoError(t, output.Err, string(output.Stderr))
|
||||
output = e2e.RunCommand(ctx, "vault",
|
||||
e2e.WithArgs(
|
||||
"read",
|
||||
"-field=id",
|
||||
fmt.Sprintf("identity/entity/name/%s", userName),
|
||||
),
|
||||
)
|
||||
require.NoError(t, output.Err, string(output.Stderr))
|
||||
entityId := strings.TrimSpace(string(output.Stdout))
|
||||
|
||||
// Create an identity group
|
||||
groupName := "engineering"
|
||||
output = e2e.RunCommand(ctx, "vault",
|
||||
e2e.WithArgs(
|
||||
"write",
|
||||
"identity/group",
|
||||
fmt.Sprintf("name=%s", groupName),
|
||||
fmt.Sprintf(`member_entity_ids=%s`, entityId),
|
||||
),
|
||||
)
|
||||
require.NoError(t, output.Err, string(output.Stderr))
|
||||
output = e2e.RunCommand(ctx, "vault",
|
||||
e2e.WithArgs(
|
||||
"read",
|
||||
"-field=id",
|
||||
fmt.Sprintf("identity/group/name/%s", groupName),
|
||||
),
|
||||
)
|
||||
require.NoError(t, output.Err, string(output.Stderr))
|
||||
groupId := strings.TrimSpace(string(output.Stdout))
|
||||
|
||||
// Get accessor value of the userpass authentication method.
|
||||
output = e2e.RunCommand(ctx, "vault",
|
||||
e2e.WithArgs(
|
||||
"auth",
|
||||
"list",
|
||||
"-detailed",
|
||||
"-format", "json",
|
||||
),
|
||||
)
|
||||
require.NoError(t, output.Err, string(output.Stderr))
|
||||
type AuthListUserPassAttributes struct {
|
||||
Accessor string `json:"accessor"`
|
||||
}
|
||||
type AuthListResponse struct {
|
||||
UserPass AuthListUserPassAttributes `json:"userpass/"`
|
||||
}
|
||||
var authListResult AuthListResponse
|
||||
err = json.Unmarshal(output.Stdout, &authListResult)
|
||||
require.NoError(t, err)
|
||||
userpassAccessor := authListResult.UserPass.Accessor
|
||||
|
||||
// Create an entity alias that maps the end-user entity with the end-user
|
||||
// userpass user
|
||||
output = e2e.RunCommand(ctx, "vault",
|
||||
e2e.WithArgs(
|
||||
"write",
|
||||
"identity/entity-alias",
|
||||
fmt.Sprintf("name=%s", userName),
|
||||
fmt.Sprintf(`canonical_id=%s`, entityId),
|
||||
fmt.Sprintf(`mount_accessor=%s`, userpassAccessor),
|
||||
),
|
||||
)
|
||||
require.NoError(t, output.Err, string(output.Stderr))
|
||||
|
||||
// Create an OIDC assignment, which describes the list of the Vault entities
|
||||
// and groups allowed to authenticate with this client.
|
||||
assignmentName := "my-assignment"
|
||||
output = e2e.RunCommand(ctx, "vault",
|
||||
e2e.WithArgs(
|
||||
"write",
|
||||
fmt.Sprintf("identity/oidc/assignment/%s", assignmentName),
|
||||
fmt.Sprintf(`entity_ids=%s`, entityId),
|
||||
fmt.Sprintf(`group_ids="%s"`, groupId),
|
||||
),
|
||||
)
|
||||
require.NoError(t, output.Err, string(output.Stderr))
|
||||
|
||||
// Create a key
|
||||
keyName := "my-key"
|
||||
output = e2e.RunCommand(ctx, "vault",
|
||||
e2e.WithArgs(
|
||||
"write",
|
||||
fmt.Sprintf("identity/oidc/key/%s", keyName),
|
||||
"allowed_client_ids=*",
|
||||
"verification_ttl=2h",
|
||||
"rotation_period=1h",
|
||||
"algorithm=RS256",
|
||||
),
|
||||
)
|
||||
require.NoError(t, output.Err, string(output.Stderr))
|
||||
|
||||
// Create an OIDC client
|
||||
oidcClientName := "boundary"
|
||||
output = e2e.RunCommand(ctx, "vault",
|
||||
e2e.WithArgs(
|
||||
"write",
|
||||
fmt.Sprintf("identity/oidc/client/%s", oidcClientName),
|
||||
"redirect_uris=http://127.0.0.1:9200/v1/auth-methods/oidc:authenticate:callback",
|
||||
fmt.Sprintf(`assignments=%s`, assignmentName),
|
||||
fmt.Sprintf(`key=%s`, keyName),
|
||||
"id_token_ttl=30m",
|
||||
"access_token_ttl=1h",
|
||||
),
|
||||
)
|
||||
require.NoError(t, output.Err, string(output.Stderr))
|
||||
output = e2e.RunCommand(ctx, "vault",
|
||||
e2e.WithArgs(
|
||||
"read",
|
||||
"-field=client_id",
|
||||
fmt.Sprintf("identity/oidc/client/%s", oidcClientName),
|
||||
),
|
||||
)
|
||||
require.NoError(t, output.Err, string(output.Stderr))
|
||||
clientId := strings.TrimSpace(string(output.Stdout))
|
||||
|
||||
// Define a Vault OIDC scope for the user
|
||||
userScopeTemplate := `{
|
||||
"username": {{identity.entity.name}},
|
||||
"contact": {
|
||||
"email": {{identity.entity.metadata.email}},
|
||||
"phone_number": {{identity.entity.metadata.phone_number}}
|
||||
}
|
||||
}`
|
||||
userScopeEncoded := base64.StdEncoding.EncodeToString([]byte(userScopeTemplate))
|
||||
output = e2e.RunCommand(ctx, "vault",
|
||||
e2e.WithArgs(
|
||||
"write",
|
||||
"identity/oidc/scope/user",
|
||||
`description="The user scope provides claims using Vault identity entity metadata"`,
|
||||
fmt.Sprintf(`template=%s`, userScopeEncoded),
|
||||
),
|
||||
)
|
||||
require.NoError(t, output.Err, string(output.Stderr))
|
||||
|
||||
// Define a Vault OIDC scope for the group
|
||||
groupScopeTemplate := `{
|
||||
"groups": {{identity.entity.groups.names}}
|
||||
}`
|
||||
groupScopeEncoded := base64.StdEncoding.EncodeToString([]byte(groupScopeTemplate))
|
||||
output = e2e.RunCommand(ctx, "vault",
|
||||
e2e.WithArgs(
|
||||
"write",
|
||||
"identity/oidc/scope/groups",
|
||||
`description="The groups scope provides the groups claim using Vault group membership"`,
|
||||
fmt.Sprintf(`template=%s`, groupScopeEncoded),
|
||||
),
|
||||
)
|
||||
require.NoError(t, output.Err, string(output.Stderr))
|
||||
|
||||
// Create a Vault OIDC provider
|
||||
providerName := "my-provider"
|
||||
output = e2e.RunCommand(ctx, "vault",
|
||||
e2e.WithArgs(
|
||||
"write",
|
||||
fmt.Sprintf("identity/oidc/provider/%s", providerName),
|
||||
fmt.Sprintf("allowed_client_ids=%s", clientId),
|
||||
"scopes_supported=groups,user",
|
||||
fmt.Sprintf("issuer=%s", c.VaultAddr),
|
||||
),
|
||||
)
|
||||
require.NoError(t, output.Err, string(output.Stderr))
|
||||
|
||||
// Get Issuer URL
|
||||
output = e2e.RunCommand(ctx, "curl",
|
||||
e2e.WithArgs(
|
||||
"-s",
|
||||
fmt.Sprintf("%s/v1/identity/oidc/provider/%s/.well-known/openid-configuration", c.VaultAddr, providerName),
|
||||
),
|
||||
)
|
||||
require.NoError(t, output.Err, string(output.Stderr))
|
||||
type oidcConfigResponse struct {
|
||||
Issuer string `json:"issuer"`
|
||||
}
|
||||
var oidcConfig oidcConfigResponse
|
||||
err = json.Unmarshal(output.Stdout, &oidcConfig)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Get client secret
|
||||
output = e2e.RunCommand(ctx, "vault",
|
||||
e2e.WithArgs(
|
||||
"read",
|
||||
"-field=client_secret",
|
||||
fmt.Sprintf("identity/oidc/client/%s", oidcClientName),
|
||||
),
|
||||
)
|
||||
require.NoError(t, output.Err, string(output.Stderr))
|
||||
clientSecret := strings.TrimSpace(string(output.Stdout))
|
||||
|
||||
// Create OIDC auth method in Boundary
|
||||
boundary.AuthenticateAdminCli(t, ctx)
|
||||
orgId, err := boundary.CreateOrgCli(t, ctx)
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() {
|
||||
ctx := context.Background()
|
||||
boundary.AuthenticateAdminCli(t, ctx)
|
||||
output := e2e.RunCommand(ctx, "boundary", e2e.WithArgs("scopes", "delete", "-id", orgId))
|
||||
require.NoError(t, output.Err, string(output.Stderr))
|
||||
})
|
||||
output = e2e.RunCommand(ctx, "boundary",
|
||||
e2e.WithArgs(
|
||||
"auth-methods", "create", "oidc",
|
||||
"-scope-id", orgId,
|
||||
"-issuer", oidcConfig.Issuer,
|
||||
"-client-id", clientId,
|
||||
"-client-secret", clientSecret,
|
||||
"-signing-algorithm", "RS256",
|
||||
"-api-url-prefix", "http://127.0.0.1:9200",
|
||||
"-claims-scopes", "groups",
|
||||
"-claims-scopes", "user",
|
||||
"-max-age", "20",
|
||||
"-name", "e2e Vault OIDC",
|
||||
"-format", "json",
|
||||
),
|
||||
)
|
||||
require.NoError(t, output.Err, string(output.Stderr))
|
||||
var createAuthMethodResult authmethods.AuthMethodCreateResult
|
||||
err = json.Unmarshal(output.Stdout, &createAuthMethodResult)
|
||||
require.NoError(t, err)
|
||||
authMethodId := createAuthMethodResult.Item.Id
|
||||
t.Logf("Created OIDC Auth Method: %s", authMethodId)
|
||||
|
||||
// Set the auth method to active-public
|
||||
output = e2e.RunCommand(ctx, "boundary",
|
||||
e2e.WithArgs(
|
||||
"auth-methods", "change-state", "oidc",
|
||||
"-id", authMethodId,
|
||||
"-state", "active-public",
|
||||
),
|
||||
)
|
||||
require.NoError(t, output.Err, string(output.Stderr))
|
||||
}
|
||||
Reference in new issue
Block a user