Skip to content

Commit

Permalink
feat: workload identity federation
Browse files Browse the repository at this point in the history
  • Loading branch information
henryde committed Mar 25, 2024
1 parent a60d551 commit 6f34d77
Show file tree
Hide file tree
Showing 26 changed files with 288 additions and 26 deletions.
22 changes: 11 additions & 11 deletions .github/workflows/workflow.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,18 @@ jobs:
name: Validate
runs-on: ubuntu-latest
steps:
- name: Check out code
uses: actions/checkout@v1
- name: Check out code
uses: actions/checkout@v1

- uses: hashicorp/setup-terraform@v1
with:
terraform_version: 1.0.10
- uses: hashicorp/setup-terraform@v3
with:
terraform_version: "1.5"

# note: we can only validate the example atm. see https://github.com/hashicorp/terraform/issues/28490
- run: terraform init -backend=false
working-directory: examples/basic-aws-integration
# note: we can only validate the example atm. see https://github.com/hashicorp/terraform/issues/28490
- run: terraform init -backend=false
working-directory: examples/basic-aws-integration

- run: terraform validate
working-directory: examples/basic-aws-integration
- run: terraform validate
working-directory: examples/basic-aws-integration

- run: terraform fmt -recursive -check
- run: terraform fmt -recursive -check
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Added ARNs of managed accounts roles to output
- Added meshStack access role to output
- Renamed metering related parts from kraken to metering
- Added workload identity federation
- Added option to disable access keys

## [v0.1.0]

Expand Down
10 changes: 7 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -165,9 +165,9 @@ Before opening a Pull Request, we recommend following the below steps to get a f
| Name | Version |
|------|---------|
| <a name="provider_aws.automation"></a> [aws.automation](#provider\_aws.automation) | 5.37.0 |
| <a name="provider_aws.management"></a> [aws.management](#provider\_aws.management) | 5.37.0 |
| <a name="provider_aws.meshcloud"></a> [aws.meshcloud](#provider\_aws.meshcloud) | 5.37.0 |
| <a name="provider_aws.automation"></a> [aws.automation](#provider\_aws.automation) | 5.41.0 |
| <a name="provider_aws.management"></a> [aws.management](#provider\_aws.management) | 5.41.0 |
| <a name="provider_aws.meshcloud"></a> [aws.meshcloud](#provider\_aws.meshcloud) | 5.41.0 |
## Modules
Expand All @@ -183,6 +183,7 @@ Before opening a Pull Request, we recommend following the below steps to get a f
| Name | Type |
|------|------|
| [aws_iam_openid_connect_provider.meshstack](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_openid_connect_provider) | resource |
| [aws_caller_identity.automation](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/caller_identity) | data source |
| [aws_caller_identity.management](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/caller_identity) | data source |
| [aws_caller_identity.meshcloud](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/caller_identity) | data source |
Expand All @@ -198,11 +199,13 @@ Before opening a Pull Request, we recommend following the below steps to get a f
| <a name="input_cost_explorer_management_account_service_role_name"></a> [cost\_explorer\_management\_account\_service\_role\_name](#input\_cost\_explorer\_management\_account\_service\_role\_name) | Name of the custom role in the management account used by the cost explorer user. | `string` | `"MeshCostExplorerServiceRole"` | no |
| <a name="input_cost_explorer_meshcloud_account_service_user_name"></a> [cost\_explorer\_meshcloud\_account\_service\_user\_name](#input\_cost\_explorer\_meshcloud\_account\_service\_user\_name) | Name of the user using cost explorer service to collect metering data. | `string` | `"meshcloud-cost-explorer-user"` | no |
| <a name="input_cost_explorer_privileged_external_id"></a> [cost\_explorer\_privileged\_external\_id](#input\_cost\_explorer\_privileged\_external\_id) | Set this variable to a random UUID version 4. The external id is a secondary key to make an AssumeRole API call. | `string` | n/a | yes |
| <a name="input_create_access_keys"></a> [create\_access\_keys](#input\_create\_access\_keys) | Set to false to disable creation of any service account access keys. | `bool` | `true` | no |
| <a name="input_landing_zone_ou_arns"></a> [landing\_zone\_ou\_arns](#input\_landing\_zone\_ou\_arns) | Organizational Unit ARNs that are used in Landing Zones. We recommend to explicitly list the OU ARNs that meshStack should manage. | `list(string)` | <pre>[<br> "arn:aws:organizations::*:ou/o-*/ou-*"<br>]</pre> | no |
| <a name="input_management_account_service_role_name"></a> [management\_account\_service\_role\_name](#input\_management\_account\_service\_role\_name) | Name of the custom role in the management account. See https://docs.meshcloud.io/docs/meshstack.how-to.integrate-meshplatform-aws-manually.html#set-up-aws-account-2-management | `string` | `"MeshfedServiceRole"` | no |
| <a name="input_meshcloud_account_service_user_name"></a> [meshcloud\_account\_service\_user\_name](#input\_meshcloud\_account\_service\_user\_name) | Name of the meshfed-service user. This user is responsible for replication. | `string` | `"meshfed-service-user"` | no |
| <a name="input_replicator_privileged_external_id"></a> [replicator\_privileged\_external\_id](#input\_replicator\_privileged\_external\_id) | Set this variable to a random UUID version 4. The external id is a secondary key to make an AssumeRole API call. | `string` | n/a | yes |
| <a name="input_support_root_account_via_aws_sso"></a> [support\_root\_account\_via\_aws\_sso](#input\_support\_root\_account\_via\_aws\_sso) | Set to true to allow meshStack to manage the Organization's AWS Root account's access via AWS SSO. | `bool` | `false` | no |
| <a name="input_workload_identity_federation"></a> [workload\_identity\_federation](#input\_workload\_identity\_federation) | Set these options to add a trusted identity provider from meshStack to allow workload identity federation for authentication which can be used instead of access keys. | <pre>object({<br> issuer = string,<br> audience = string,<br> thumbprint = string,<br> replicator_subject = string,<br> kraken_subject = string<br> })</pre> | `null` | no |
## Outputs
Expand All @@ -219,4 +222,5 @@ Before opening a Pull Request, we recommend following the below steps to get a f
| <a name="output_replicator_aws_iam_keys"></a> [replicator\_aws\_iam\_keys](#output\_replicator\_aws\_iam\_keys) | You can access your credentials when you execute `terraform output replicator_aws_iam_keys` command |
| <a name="output_replicator_management_account_role_arn"></a> [replicator\_management\_account\_role\_arn](#output\_replicator\_management\_account\_role\_arn) | Amazon Resource Name (ARN) of Management Account Role for replicator |
| <a name="output_replicator_privileged_external_id"></a> [replicator\_privileged\_external\_id](#output\_replicator\_privileged\_external\_id) | Replicator privileged\_external\_id |
| <a name="output_replicator_workload_identity_federation_role"></a> [replicator\_workload\_identity\_federation\_role](#output\_replicator\_workload\_identity\_federation\_role) | n/a |
<!-- END_TF_DOCS -->
9 changes: 9 additions & 0 deletions identity_provider.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# in case of workload identity federation we must add the appropriate identity provider
resource "aws_iam_openid_connect_provider" "meshstack" {
provider = aws.meshcloud
count = var.workload_identity_federation != null ? 1 : 0

url = var.workload_identity_federation.issuer
client_id_list = [var.workload_identity_federation.audience]
thumbprint_list = [var.workload_identity_federation.thumbprint]
}
20 changes: 20 additions & 0 deletions main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,13 @@ module "meshcloud_account_metering_access" {
privileged_external_id = var.cost_explorer_privileged_external_id
management_account_service_role_name = var.cost_explorer_management_account_service_role_name
meshcloud_account_service_user_name = var.cost_explorer_meshcloud_account_service_user_name

workload_identity_federation = var.workload_identity_federation == null ? null : {
issuer = var.workload_identity_federation.issuer,
audience = var.workload_identity_federation.audience,
subject = var.workload_identity_federation.kraken_subject,
identity_provider_arn = aws_iam_openid_connect_provider.meshstack[0].arn
}
}

module "meshcloud_account_replicator_access" {
Expand All @@ -30,6 +37,13 @@ module "meshcloud_account_replicator_access" {
meshcloud_account_service_user_name = var.meshcloud_account_service_user_name
management_account_service_role_name = var.management_account_service_role_name
automation_account_service_role_name = var.automation_account_service_role_name

workload_identity_federation = var.workload_identity_federation == null ? null : {
issuer = var.workload_identity_federation.issuer,
audience = var.workload_identity_federation.audience,
subject = var.workload_identity_federation.replicator_subject,
identity_provider_arn = aws_iam_openid_connect_provider.meshstack[0].arn
}
}

module "management_account_metering_access" {
Expand All @@ -42,6 +56,8 @@ module "management_account_metering_access" {
management_account_service_role_name = var.cost_explorer_management_account_service_role_name
meshcloud_account_service_user_name = var.cost_explorer_meshcloud_account_service_user_name

allow_federated_role = var.workload_identity_federation != null

depends_on = [
module.meshcloud_account_metering_access
]
Expand All @@ -62,6 +78,8 @@ module "management_account_replicator_access" {
management_account_service_role_name = var.management_account_service_role_name
landing_zone_ou_arns = var.landing_zone_ou_arns

allow_federated_role = var.workload_identity_federation != null

depends_on = [
module.meshcloud_account_replicator_access
]
Expand All @@ -77,6 +95,8 @@ module "automation_account_replicator_access" {
meshcloud_account_service_user_name = var.meshcloud_account_service_user_name
automation_account_service_role_name = var.automation_account_service_role_name

allow_federated_role = var.workload_identity_federation != null

depends_on = [
module.meshcloud_account_replicator_access
]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

| Name | Version |
|------|---------|
| <a name="provider_aws"></a> [aws](#provider\_aws) | 5.37.0 |
| <a name="provider_aws"></a> [aws](#provider\_aws) | >= 2.7.0 |

## Modules

Expand All @@ -31,6 +31,7 @@ No modules.

| Name | Description | Type | Default | Required |
|------|-------------|------|---------|:--------:|
| <a name="input_allow_federated_role"></a> [allow\_federated\_role](#input\_allow\_federated\_role) | n/a | `bool` | `false` | no |
| <a name="input_management_account_service_role_name"></a> [management\_account\_service\_role\_name](#input\_management\_account\_service\_role\_name) | Name of the custom role in the management account used by the cost explorer user. | `string` | `"MeshCostExplorerServiceRole"` | no |
| <a name="input_meshcloud_account_id"></a> [meshcloud\_account\_id](#input\_meshcloud\_account\_id) | The ID of the meshcloud AWS Account. | `string` | n/a | yes |
| <a name="input_meshcloud_account_service_user_name"></a> [meshcloud\_account\_service\_user\_name](#input\_meshcloud\_account\_service\_user\_name) | Name of the user using cost explorer service to collect metering data. | `string` | `"meshcloud-cost-explorer-user"` | no |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,21 @@ data "aws_iam_policy_document" "cost_explorer_service_assume_role" {
version = "2012-10-17"
statement {
effect = "Allow"

principals {
type = "AWS"
identifiers = ["arn:${data.aws_partition.current.partition}:iam::${var.meshcloud_account_id}:user/${var.meshcloud_account_service_user_name}"]
}

dynamic "principals" {
for_each = var.allow_federated_role ? [true] : []

content {
type = "AWS"
identifiers = ["arn:${data.aws_partition.current.partition}:iam::${var.meshcloud_account_id}:role/${var.meshcloud_account_service_user_name}IdentityFederation"]
}
}

actions = ["sts:AssumeRole"]
condition {
test = "StringEquals"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,8 @@ variable "privileged_external_id" {
type = string
description = "Privileged external ID for the meshfed-service to use"
}

variable "allow_federated_role" {
type = bool
default = false
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

| Name | Version |
|------|---------|
| <a name="provider_aws"></a> [aws](#provider\_aws) | 5.37.0 |
| <a name="provider_aws"></a> [aws](#provider\_aws) | >= 2.7.0 |

## Modules

Expand All @@ -21,24 +21,30 @@ No modules.
|------|------|
| [aws_iam_access_key.meshcloud_cost_explorer](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_access_key) | resource |
| [aws_iam_policy.meshcloud_cost_explorer_user](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource |
| [aws_iam_role.assume_cost_explorer_role](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource |
| [aws_iam_role_policy_attachment.meshfed_service](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource |
| [aws_iam_user.meshcloud_cost_explorer](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_user) | resource |
| [aws_iam_user_policy_attachment.meshcloud_cost_explorer](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_user_policy_attachment) | resource |
| [aws_caller_identity.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/caller_identity) | data source |
| [aws_iam_policy_document.meshcloud_cost_explorer_user_assume_role](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source |
| [aws_iam_policy_document.workload_identity_federation](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source |
| [aws_partition.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/partition) | data source |

## Inputs

| Name | Description | Type | Default | Required |
|------|-------------|------|---------|:--------:|
| <a name="input_create_access_key"></a> [create\_access\_key](#input\_create\_access\_key) | Create access key for service account | `bool` | `true` | no |
| <a name="input_management_account_id"></a> [management\_account\_id](#input\_management\_account\_id) | The ID of the Management Account. | `string` | n/a | yes |
| <a name="input_management_account_service_role_name"></a> [management\_account\_service\_role\_name](#input\_management\_account\_service\_role\_name) | Name of the custom role in the management account used by the cost explorer user. | `string` | `"MeshCostExplorerServiceRole"` | no |
| <a name="input_meshcloud_account_service_user_name"></a> [meshcloud\_account\_service\_user\_name](#input\_meshcloud\_account\_service\_user\_name) | Name of the user using cost explorer service to collect metering data. | `string` | `"meshcloud-cost-explorer-user"` | no |
| <a name="input_privileged_external_id"></a> [privileged\_external\_id](#input\_privileged\_external\_id) | Privileged external ID for the cost-explorer-service to use | `string` | n/a | yes |
| <a name="input_workload_identity_federation"></a> [workload\_identity\_federation](#input\_workload\_identity\_federation) | n/a | <pre>object({<br> issuer = string,<br> audience = string,<br> subject = string,<br> identity_provider_arn = string<br> })</pre> | `null` | no |

## Outputs

| Name | Description |
|------|-------------|
| <a name="output_aws_iam_keys"></a> [aws\_iam\_keys](#output\_aws\_iam\_keys) | AWS access and secret keys for cost explorer user. |
| <a name="output_workload_identity_federation_role"></a> [workload\_identity\_federation\_role](#output\_workload\_identity\_federation\_role) | n/a |
<!-- END_TF_DOCS -->
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,31 @@ data "aws_iam_policy_document" "meshcloud_cost_explorer_user_assume_role" {
}
}
}

data "aws_iam_policy_document" "workload_identity_federation" {
count = var.workload_identity_federation == null ? 0 : 1
version = "2012-10-17"

statement {
effect = "Allow"
principals {
type = "Federated"
identifiers = [var.workload_identity_federation.identity_provider_arn]
}
actions = ["sts:AssumeRoleWithWebIdentity"]

condition {
test = "StringEquals"
variable = "${trimprefix(var.workload_identity_federation.issuer, "https://")}:aud"

values = [var.workload_identity_federation.audience]
}

condition {
test = "StringEquals"
variable = "${trimprefix(var.workload_identity_federation.issuer, "https://")}:sub"

values = [var.workload_identity_federation.subject]
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,13 @@ resource "aws_iam_user" "meshcloud_cost_explorer" {
}

resource "aws_iam_access_key" "meshcloud_cost_explorer" {
user = aws_iam_user.meshcloud_cost_explorer.name
count = var.create_access_key ? 1 : 0
user = aws_iam_user.meshcloud_cost_explorer.name
}

moved {
from = aws_iam_access_key.meshcloud_cost_explorer
to = aws_iam_access_key.meshcloud_cost_explorer[0]
}

resource "aws_iam_policy" "meshcloud_cost_explorer_user" {
Expand All @@ -16,3 +22,19 @@ resource "aws_iam_user_policy_attachment" "meshcloud_cost_explorer" {
user = aws_iam_user.meshcloud_cost_explorer.name
policy_arn = aws_iam_policy.meshcloud_cost_explorer_user.arn
}
#
# role which can be assumed by federated workload
resource "aws_iam_role" "assume_cost_explorer_role" {
count = var.workload_identity_federation == null ? 0 : 1

name = "${aws_iam_user.meshcloud_cost_explorer.name}IdentityFederation"
assume_role_policy = data.aws_iam_policy_document.workload_identity_federation[0].json
}

# attach permissions to assumed role
resource "aws_iam_role_policy_attachment" "meshfed_service" {
count = var.workload_identity_federation == null ? 0 : 1

role = aws_iam_role.assume_cost_explorer_role[0].name
policy_arn = aws_iam_policy.meshcloud_cost_explorer_user.arn
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
output "aws_iam_keys" {
description = "AWS access and secret keys for cost explorer user."
value = {
aws_access_key = aws_iam_access_key.meshcloud_cost_explorer.id
aws_secret_key = aws_iam_access_key.meshcloud_cost_explorer.secret
}
value = var.create_access_key ? {
aws_access_key = aws_iam_access_key.meshcloud_cost_explorer[0].id
aws_secret_key = aws_iam_access_key.meshcloud_cost_explorer[0].secret
} : null
sensitive = true
}

output "workload_identity_federation_role" {
value = var.workload_identity_federation == null ? null : aws_iam_role.assume_cost_explorer_role[0].arn
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,19 @@ variable "privileged_external_id" {
type = string
description = "Privileged external ID for the cost-explorer-service to use"
}

variable "create_access_key" {
type = bool
description = "Create access key for service account"
default = true
}

variable "workload_identity_federation" {
type = object({
issuer = string,
audience = string,
subject = string,
identity_provider_arn = string
})
default = null
}
Loading

0 comments on commit 6f34d77

Please sign in to comment.