* feat(tests) Add Terraform testing to support Cross-account discovery **Problem** * To support cross-account DHC discovery, we need to add terraform configurations and tests to test against live AWS data. * However, there was nothing in place to support any AssumeRole testing, irrespective of cross-account. **Solution** * Added a new terraform file, iam_assume_role.tf, to contain the new terraform configurations for single-account AssumeRole and cross-role * Added a second terraform file, iam_cross_account.tf, to contain the new terraform configurations for cross-account * Refactored how the tests are organized. The previous static test cases used a system where a separate function was created for each test. This was moved to a dedicated file, e2e_host_static_test.go. * For AssumeRole tests (single account, cross role, and cross account), this system wasn't ideal since there are so many test cases and they are all extremely similar. A table driven system was better that passed in a set of variables to shared test helpers. A dedicated file, e2e_host_assumerole_test.go, was created to contain the testing infrastructure for AssumeRole tests. * e2e_host_test.go remains the main test runner that then calls e2e_host_assumerole_test.go and e2e_host_static_test.go * The terraform tests work by setting environment variables for the AWS account that you want to configure the terraform for. The existing setup worked for single-account and cross-role. However, to support cross-account, support had to be added for a second set of credentials. The second set of environment variables can now be set with the TARGET_ prefix. Some configuration in testing.go had to be added to support creating/destroying multiple terraform setups. **Testing** * The single account tests pass as expected. However, a bug was discovered during this process that will be addressed later. * The cross-role and cross-account tests failed as expected because this featured has not yet been delivered. * It was noticed that some of the existing static tests were flaky. The problem and fix were identified and will be addressed later.
207 lines
6.0 KiB
Terraform
207 lines
6.0 KiB
Terraform
# Copyright IBM Corp. 2021, 2026
|
|
# SPDX-License-Identifier: MPL-2.0
|
|
|
|
resource "random_id" "assume_role_name" {
|
|
prefix = "BoundaryPluginAssumeRole"
|
|
byte_length = 4
|
|
}
|
|
|
|
resource "aws_iam_role" "assume_role" {
|
|
name = random_id.assume_role_name.dec
|
|
|
|
assume_role_policy = jsonencode({
|
|
Version = "2012-10-17"
|
|
Statement = [
|
|
{
|
|
Action = ["sts:AssumeRole", "sts:SetSourceIdentity"]
|
|
Effect = "Allow"
|
|
Principal = {
|
|
AWS = data.aws_caller_identity.current.arn
|
|
}
|
|
},
|
|
# Also trusted by the cross-role principal role so it can be used
|
|
# as the target in two-hop AssumeRole tests.
|
|
{
|
|
Action = ["sts:AssumeRole", "sts:SetSourceIdentity"]
|
|
Effect = "Allow"
|
|
Principal = {
|
|
AWS = aws_iam_role.cross_role_principal.arn
|
|
}
|
|
},
|
|
]
|
|
})
|
|
|
|
tags = {
|
|
"boundary-demo" = local.hashicorp_email
|
|
}
|
|
}
|
|
|
|
resource "aws_iam_role_policy_attachment" "assume_role_ec2_describeinstances" {
|
|
role = aws_iam_role.assume_role.name
|
|
policy_arn = aws_iam_policy.ec2_describeinstances.arn
|
|
}
|
|
|
|
output "assume_role_arn" {
|
|
value = aws_iam_role.assume_role.arn
|
|
}
|
|
|
|
# Error case: role exists with ec2:DescribeInstances but trust policy
|
|
# does not allow the caller to assume it.
|
|
resource "random_id" "assume_role_no_trust_name" {
|
|
prefix = "BoundaryPluginNoTrust"
|
|
byte_length = 4
|
|
}
|
|
|
|
resource "aws_iam_role" "assume_role_no_trust" {
|
|
name = random_id.assume_role_no_trust_name.dec
|
|
|
|
assume_role_policy = jsonencode({
|
|
Version = "2012-10-17"
|
|
Statement = [
|
|
{
|
|
Action = ["sts:AssumeRole", "sts:SetSourceIdentity"]
|
|
Effect = "Deny"
|
|
Principal = {
|
|
AWS = data.aws_caller_identity.current.arn
|
|
}
|
|
},
|
|
]
|
|
})
|
|
|
|
tags = {
|
|
"boundary-demo" = local.hashicorp_email
|
|
}
|
|
}
|
|
|
|
output "assume_role_no_trust_arn" {
|
|
value = aws_iam_role.assume_role_no_trust.arn
|
|
}
|
|
|
|
# Error case: role exists and the caller can assume it, but has no
|
|
# ec2:DescribeInstances permission.
|
|
# Also trusted by cross_role_principal so this same role can be reused as the
|
|
# no-EC2-permission target in two-hop (cross-role) error case tests.
|
|
resource "random_id" "assume_role_no_ec2_permission_name" {
|
|
prefix = "BoundaryPluginNoEC2Perm"
|
|
byte_length = 4
|
|
}
|
|
|
|
resource "aws_iam_role" "assume_role_no_ec2_permission" {
|
|
name = random_id.assume_role_no_ec2_permission_name.dec
|
|
|
|
assume_role_policy = jsonencode({
|
|
Version = "2012-10-17"
|
|
Statement = [
|
|
{
|
|
Action = ["sts:AssumeRole", "sts:SetSourceIdentity"]
|
|
Effect = "Allow"
|
|
Principal = {
|
|
AWS = data.aws_caller_identity.current.arn
|
|
}
|
|
},
|
|
# Also trusted by the cross-role principal so it can serve as the
|
|
# no-EC2-permission target in two-hop error case tests.
|
|
{
|
|
Action = ["sts:AssumeRole", "sts:SetSourceIdentity"]
|
|
Effect = "Allow"
|
|
Principal = {
|
|
AWS = aws_iam_role.cross_role_principal.arn
|
|
}
|
|
},
|
|
]
|
|
})
|
|
|
|
tags = {
|
|
"boundary-demo" = local.hashicorp_email
|
|
}
|
|
}
|
|
|
|
output "assume_role_no_ec2_permission_arn" {
|
|
value = aws_iam_role.assume_role_no_ec2_permission.arn
|
|
}
|
|
|
|
# Cross-role (two-hop) principal role.
|
|
# The test runner can assume this role. It has permission to AssumeRole into
|
|
# aws_iam_role.assume_role (the target role) but has no ec2:DescribeInstances
|
|
# of its own. This simulates the principal side of a two-hop role chain: the
|
|
# plugin must assume the principal first, then use those credentials to assume
|
|
# the target role before calling DescribeInstances.
|
|
# Note: both roles are in the same AWS account. This tests the plugin's
|
|
# two-hop credential chain logic without requiring a second account.
|
|
resource "random_id" "cross_role_principal_name" {
|
|
prefix = "BoundaryPluginCRPrincipal"
|
|
byte_length = 4
|
|
}
|
|
|
|
resource "aws_iam_role" "cross_role_principal" {
|
|
name = random_id.cross_role_principal_name.dec
|
|
|
|
assume_role_policy = jsonencode({
|
|
Version = "2012-10-17"
|
|
Statement = [
|
|
{
|
|
Action = ["sts:AssumeRole", "sts:SetSourceIdentity"]
|
|
Effect = "Allow"
|
|
Principal = {
|
|
AWS = data.aws_caller_identity.current.arn
|
|
}
|
|
},
|
|
]
|
|
})
|
|
|
|
tags = {
|
|
"boundary-demo" = local.hashicorp_email
|
|
}
|
|
}
|
|
|
|
# Single policy granting cross_role_principal permission to assume all target
|
|
# roles it needs for both happy path and error case tests.
|
|
resource "aws_iam_policy" "cross_role_principal_assume_targets" {
|
|
name = "${random_id.cross_role_principal_name.dec}-assume-targets"
|
|
|
|
policy = jsonencode({
|
|
Version = "2012-10-17"
|
|
Statement = [
|
|
{
|
|
Action = ["sts:AssumeRole", "sts:SetSourceIdentity"]
|
|
Effect = "Allow"
|
|
Resource = [
|
|
aws_iam_role.assume_role.arn,
|
|
aws_iam_role.assume_role_no_ec2_permission.arn,
|
|
]
|
|
},
|
|
]
|
|
})
|
|
}
|
|
|
|
resource "aws_iam_role_policy_attachment" "cross_role_principal_assume_targets" {
|
|
role = aws_iam_role.cross_role_principal.name
|
|
policy_arn = aws_iam_policy.cross_role_principal_assume_targets.arn
|
|
}
|
|
|
|
output "cross_role_principal_arn" {
|
|
value = aws_iam_role.cross_role_principal.arn
|
|
}
|
|
|
|
# Error case: caller cannot assume the principal role (first hop denied).
|
|
# Reuses assume_role_no_trust — it already has a Deny trust policy for the
|
|
# caller, so it also serves as an unassumable principal in two-hop tests.
|
|
output "cross_role_principal_no_trust_arn" {
|
|
value = aws_iam_role.assume_role_no_trust.arn
|
|
}
|
|
|
|
# Error case: principal can be assumed, but the target role's trust policy
|
|
# does not allow the principal to assume it (second hop denied).
|
|
# Reuses assume_role_no_trust as the target — its Deny trust policy blocks
|
|
# all callers including cross_role_principal.
|
|
output "cross_role_target_no_trust_arn" {
|
|
value = aws_iam_role.assume_role_no_trust.arn
|
|
}
|
|
|
|
# Error case: both hops succeed but the target has no ec2:DescribeInstances.
|
|
# Reuses assume_role_no_ec2_permission — its trust policy allows
|
|
# cross_role_principal (see above), and it has no EC2 permission.
|
|
output "cross_role_target_no_ec2_permission_arn" {
|
|
value = aws_iam_role.assume_role_no_ec2_permission.arn
|
|
}
|